@vritti/api-sdk 0.1.8 → 0.2.2

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 CHANGED
@@ -31,6 +31,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  // src/index.ts
32
32
  var index_exports = {};
33
33
  __export(index_exports, {
34
+ AccessToken: () => AccessToken,
34
35
  AuthConfigModule: () => AuthConfigModule,
35
36
  BadGatewayException: () => BadGatewayException,
36
37
  BadRequestException: () => BadRequestException,
@@ -38,12 +39,15 @@ __export(index_exports, {
38
39
  CorrelationIdMiddleware: () => CorrelationIdMiddleware,
39
40
  DEFAULT_CORRELATION_HEADER: () => DEFAULT_CORRELATION_HEADER,
40
41
  DatabaseModule: () => DatabaseModule,
42
+ EmailModule: () => EmailModule,
43
+ EmailService: () => EmailService,
41
44
  ForbiddenException: () => ForbiddenException2,
42
45
  GoneException: () => GoneException,
43
46
  HttpExceptionFilter: () => HttpExceptionFilter,
44
47
  HttpLoggerInterceptor: () => HttpLoggerInterceptor,
45
48
  HttpProblemException: () => HttpProblemException,
46
49
  InternalServerErrorException: () => InternalServerErrorException3,
50
+ JwtAuthService: () => JwtAuthService,
47
51
  LOGGER_MODULE_OPTIONS: () => LOGGER_MODULE_OPTIONS,
48
52
  LoggerModule: () => LoggerModule,
49
53
  LoggerService: () => LoggerService,
@@ -56,17 +60,23 @@ __export(index_exports, {
56
60
  PrimaryBaseRepository: () => PrimaryBaseRepository,
57
61
  PrimaryDatabaseService: () => PrimaryDatabaseService,
58
62
  Public: () => Public,
63
+ RESET_KEY: () => RESET_KEY,
64
+ RefreshTokenCookie: () => RefreshTokenCookie,
59
65
  RequestTimeoutException: () => RequestTimeoutException,
66
+ Reset: () => Reset,
67
+ RootModule: () => RootModule,
60
68
  SKIP_CSRF_KEY: () => SKIP_CSRF_KEY,
69
+ SelectOptionsQueryDto: () => SelectOptionsQueryDto,
61
70
  ServiceUnavailableException: () => ServiceUnavailableException,
71
+ SessionData: () => SessionData,
62
72
  SkipCsrf: () => SkipCsrf,
63
- SseAuthGuard: () => SseAuthGuard,
64
73
  Tenant: () => Tenant,
65
74
  TenantBaseRepository: () => TenantBaseRepository,
66
75
  TenantContextService: () => TenantContextService,
67
76
  TenantDatabaseService: () => TenantDatabaseService,
77
+ TokenType: () => TokenType,
68
78
  TooManyRequestsException: () => TooManyRequestsException,
69
- UnauthorizedException: () => UnauthorizedException5,
79
+ UnauthorizedException: () => UnauthorizedException3,
70
80
  UnprocessableEntityException: () => UnprocessableEntityException,
71
81
  UnsupportedMediaTypeException: () => UnsupportedMediaTypeException,
72
82
  UserId: () => UserId,
@@ -83,8 +93,11 @@ __export(index_exports, {
83
93
  getHttpStatusTitle: () => getHttpStatusTitle,
84
94
  getJwtExpiry: () => getJwtExpiry,
85
95
  getRefreshCookieOptions: () => getRefreshCookieOptions,
96
+ getTokenExpiry: () => getTokenExpiry,
86
97
  hashToken: () => hashToken,
98
+ jwtConfigFactory: () => jwtConfigFactory,
87
99
  normalizePhoneNumber: () => normalizePhoneNumber,
100
+ parseExpiryToMs: () => parseExpiryToMs,
88
101
  resetConfig: () => resetConfig,
89
102
  runWithCorrelationContext: () => runWithCorrelationContext,
90
103
  updateCorrelationContext: () => updateCorrelationContext,
@@ -93,10 +106,10 @@ __export(index_exports, {
93
106
  module.exports = __toCommonJS(index_exports);
94
107
 
95
108
  // src/auth/auth-config.module.ts
96
- var import_common6 = require("@nestjs/common");
109
+ var import_common7 = require("@nestjs/common");
97
110
  var import_config4 = require("@nestjs/config");
98
111
  var import_core3 = require("@nestjs/core");
99
- var import_jwt2 = require("@nestjs/jwt");
112
+ var import_jwt4 = require("@nestjs/jwt");
100
113
 
101
114
  // src/request/request.module.ts
102
115
  var import_common2 = require("@nestjs/common");
@@ -118,8 +131,7 @@ var defaultConfig = {
118
131
  jwt: {
119
132
  accessTokenExpiry: "15m",
120
133
  refreshTokenExpiry: "30d",
121
- onboardingTokenExpiry: "24h",
122
- validateTokenBinding: true
134
+ onboardingTokenExpiry: "24h"
123
135
  },
124
136
  guard: {
125
137
  tenantHeaderName: "x-tenant-id",
@@ -210,11 +222,7 @@ var RequestService = class {
210
222
  constructor(request) {
211
223
  this.request = request;
212
224
  }
213
- /**
214
- * Extract tenant identifier from request headers
215
- * Priority: x-tenant-id > x-subdomain
216
- * @returns Tenant identifier or null if not found
217
- */
225
+ // Extracts tenant identifier from x-tenant-id or x-subdomain request header
218
226
  getTenantIdentifier() {
219
227
  const getHeader = /* @__PURE__ */ __name((key) => {
220
228
  const value = this.request.headers?.[key];
@@ -222,11 +230,7 @@ var RequestService = class {
222
230
  }, "getHeader");
223
231
  return getHeader("x-tenant-id") || getHeader("x-subdomain") || null;
224
232
  }
225
- /**
226
- * Extract access token from Authorization header
227
- * Format: "Bearer <token>"
228
- * @returns Access token or null if not found
229
- */
233
+ // Extracts the bearer access token from the Authorization header
230
234
  getAccessToken() {
231
235
  const authHeader = this.request.headers?.authorization;
232
236
  if (!authHeader) {
@@ -235,11 +239,7 @@ var RequestService = class {
235
239
  const [type, token] = authHeader.split(" ") ?? [];
236
240
  return type === "Bearer" && token ? token : null;
237
241
  }
238
- /**
239
- * Extract refresh token from httpOnly cookie
240
- * Cookie name is configurable via api-sdk config
241
- * @returns Refresh token or null if not found
242
- */
242
+ // Extracts the refresh token from the configured httpOnly cookie
243
243
  getRefreshToken() {
244
244
  try {
245
245
  const cookies = this.request.cookies;
@@ -255,18 +255,11 @@ var RequestService = class {
255
255
  return null;
256
256
  }
257
257
  }
258
- /**
259
- * Get a specific header value
260
- * @param key Header key
261
- * @returns Header value (string, array, or undefined)
262
- */
258
+ // Returns the value of a specific request header by key
263
259
  getHeader(key) {
264
260
  return this.request.headers?.[key];
265
261
  }
266
- /**
267
- * Get all headers
268
- * @returns Record of all headers
269
- */
262
+ // Returns all request headers
270
263
  getAllHeaders() {
271
264
  return this.request.headers || {};
272
265
  }
@@ -309,251 +302,20 @@ RequestModule = _ts_decorate2([
309
302
 
310
303
  // src/auth/guards/vritti-auth.guard.ts
311
304
  var import_common5 = require("@nestjs/common");
305
+ var import_constants = require("@nestjs/common/constants");
312
306
  var import_config2 = require("@nestjs/config");
313
307
  var import_core2 = require("@nestjs/core");
314
308
  var import_jwt = require("@nestjs/jwt");
315
309
 
316
- // src/auth/decorators/skip-csrf.decorator.ts
310
+ // src/auth/decorators/reset.decorator.ts
317
311
  var import_common3 = require("@nestjs/common");
318
- var SKIP_CSRF_KEY = "skipCsrf";
319
- var SkipCsrf = /* @__PURE__ */ __name(() => (0, import_common3.SetMetadata)(SKIP_CSRF_KEY, true), "SkipCsrf");
312
+ var RESET_KEY = "isReset";
313
+ var Reset = /* @__PURE__ */ __name(() => (0, import_common3.SetMetadata)(RESET_KEY, true), "Reset");
320
314
 
321
- // src/database/services/primary-database.service.ts
315
+ // src/auth/decorators/skip-csrf.decorator.ts
322
316
  var import_common4 = require("@nestjs/common");
323
- var import_drizzle_orm = require("drizzle-orm");
324
- var import_node_postgres = require("drizzle-orm/node-postgres");
325
- var import_pg = require("pg");
326
-
327
- // src/database/constants.ts
328
- var DATABASE_MODULE_OPTIONS = Symbol("DATABASE_MODULE_OPTIONS");
329
-
330
- // src/database/services/primary-database.service.ts
331
- function _ts_decorate3(decorators, target, key, desc) {
332
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
333
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
334
- 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;
335
- return c > 3 && r && Object.defineProperty(target, key, r), r;
336
- }
337
- __name(_ts_decorate3, "_ts_decorate");
338
- function _ts_metadata2(k, v) {
339
- if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
340
- }
341
- __name(_ts_metadata2, "_ts_metadata");
342
- function _ts_param2(paramIndex, decorator) {
343
- return function(target, key) {
344
- decorator(target, key, paramIndex);
345
- };
346
- }
347
- __name(_ts_param2, "_ts_param");
348
- var PrimaryDatabaseService = class _PrimaryDatabaseService {
349
- static {
350
- __name(this, "PrimaryDatabaseService");
351
- }
352
- options;
353
- logger = new import_common4.Logger(_PrimaryDatabaseService.name);
354
- /** PostgreSQL connection pool */
355
- pool = null;
356
- /** Drizzle database instance */
357
- db = null;
358
- /** In-memory cache: Map<tenantIdentifier, TenantConfig> */
359
- tenantConfigCache = /* @__PURE__ */ new Map();
360
- /** Cache TTL in milliseconds */
361
- cacheTTL;
362
- constructor(options) {
363
- this.options = options;
364
- this.cacheTTL = options.connectionCacheTTL || 3e5;
365
- }
366
- async onModuleInit() {
367
- if (this.options.primaryDb) {
368
- await this.initializeDrizzleClient();
369
- }
370
- }
371
- /**
372
- * Initialize connection to primary database using Drizzle
373
- */
374
- async initializeDrizzleClient() {
375
- try {
376
- const databaseUrl = this.buildPrimaryDbUrl();
377
- this.pool = new import_pg.Pool({
378
- connectionString: databaseUrl,
379
- max: this.options.maxConnections || 10
380
- });
381
- this.logger.debug(`Schema keys passed to drizzle: [${Object.keys(this.options.drizzleSchema || {}).join(", ")}]`);
382
- this.logger.debug(`Relations keys passed to drizzle: [${Object.keys(this.options.drizzleRelations || {}).join(", ")}]`);
383
- this.db = (0, import_node_postgres.drizzle)({
384
- client: this.pool,
385
- schema: this.options.drizzleSchema,
386
- relations: this.options.drizzleRelations
387
- });
388
- this.logger.debug(`Drizzle query keys after init: [${Object.keys(this.db.query || {}).join(", ")}]`);
389
- await this.pool.query("SELECT 1");
390
- this.logger.log("Connected to primary database (tenant registry)");
391
- } catch (error) {
392
- this.logger.error("Failed to connect to primary database", error);
393
- throw new import_common4.InternalServerErrorException("Failed to initialize tenant registry");
394
- }
395
- }
396
- /**
397
- * Build connection URL from primary database properties
398
- */
399
- buildPrimaryDbUrl() {
400
- if (!this.options.primaryDb) {
401
- throw new Error("Primary database configuration not provided");
402
- }
403
- const { host, port = 5432, username, password, database, schema = "public", sslMode = "require" } = this.options.primaryDb;
404
- let url = `postgresql://${username}:${encodeURIComponent(password)}@${host}:${port}/${database}`;
405
- const params = new URLSearchParams();
406
- if (schema) {
407
- params.set("schema", schema);
408
- }
409
- params.set("sslmode", sslMode);
410
- const queryString = params.toString();
411
- if (queryString) {
412
- url += `?${queryString}`;
413
- }
414
- this.logger.debug(`Primary DB connection URL: ${this.maskPassword(url)}`);
415
- return url;
416
- }
417
- /**
418
- * Mask password in connection URL for logging
419
- */
420
- maskPassword(url) {
421
- return url.replace(/:([^@]+)@/, ":****@");
422
- }
423
- /**
424
- * Get tenant configuration by identifier (ID or subdomain)
425
- *
426
- * @param tenantIdentifier Tenant ID or subdomain
427
- * @returns Tenant configuration or null if not found
428
- */
429
- async getTenantInfo(tenantIdentifier) {
430
- const cached = this.tenantConfigCache.get(tenantIdentifier);
431
- if (cached) {
432
- this.logger.debug(`Cache hit for tenant: ${tenantIdentifier}`);
433
- return cached;
434
- }
435
- try {
436
- if (!this.db) {
437
- throw new Error("Primary database client not initialized");
438
- }
439
- this.logger.debug(`Querying primary database for tenant: ${tenantIdentifier}`);
440
- const schema = this.options.drizzleSchema;
441
- const { tenants, tenantDatabaseConfigs } = schema;
442
- const result = await this.db.select().from(tenants).leftJoin(tenantDatabaseConfigs, (0, import_drizzle_orm.eq)(tenants.id, tenantDatabaseConfigs.tenantId)).where((0, import_drizzle_orm.or)((0, import_drizzle_orm.eq)(tenants.id, tenantIdentifier), (0, import_drizzle_orm.eq)(tenants.subdomain, tenantIdentifier))).limit(1);
443
- if (!result.length) {
444
- this.logger.warn(`Tenant not found: ${tenantIdentifier}`);
445
- return null;
446
- }
447
- const row = result[0];
448
- const tenant = row.tenants;
449
- const config = row.tenant_database_configs;
450
- if (tenant.status !== "ACTIVE") {
451
- this.logger.warn(`Tenant not active: ${tenantIdentifier}`);
452
- return null;
453
- }
454
- const info = {
455
- id: tenant.id,
456
- subdomain: tenant.subdomain,
457
- type: tenant.dbType,
458
- status: tenant.status,
459
- // For SHARED tenants: schema name
460
- schemaName: config?.dbSchema || void 0,
461
- // For DEDICATED tenants: database configuration from TenantDatabaseConfig table
462
- databaseName: config?.dbName || void 0,
463
- databaseHost: config?.dbHost || void 0,
464
- databasePort: config?.dbPort || void 0,
465
- databaseUsername: config?.dbUsername ? this.decrypt(config.dbUsername) : void 0,
466
- databasePassword: config?.dbPassword ? this.decrypt(config.dbPassword) : void 0,
467
- databaseSslMode: config?.dbSslMode || void 0,
468
- connectionPoolSize: config?.connectionPoolSize || void 0
469
- };
470
- this.cacheInfo(info);
471
- return info;
472
- } catch (error) {
473
- this.logger.error(`Failed to fetch tenant info: ${tenantIdentifier}`, error);
474
- throw new import_common4.InternalServerErrorException("Failed to resolve tenant");
475
- }
476
- }
477
- /**
478
- * Cache tenant information with TTL
479
- */
480
- cacheInfo(info) {
481
- this.tenantConfigCache.set(info.id, info);
482
- this.tenantConfigCache.set(info.subdomain, info);
483
- setTimeout(() => {
484
- this.tenantConfigCache.delete(info.id);
485
- this.tenantConfigCache.delete(info.subdomain);
486
- this.logger.debug(`Cache expired for tenant: ${info.subdomain}`);
487
- }, this.cacheTTL);
488
- }
489
- /**
490
- * Clear cached tenant information
491
- *
492
- * Useful when tenant settings are updated and cache needs to be invalidated
493
- *
494
- * @param tenantIdentifier Tenant ID or subdomain
495
- */
496
- clearTenantCache(tenantIdentifier) {
497
- const config = this.tenantConfigCache.get(tenantIdentifier);
498
- if (config) {
499
- this.tenantConfigCache.delete(config.id);
500
- this.tenantConfigCache.delete(config.subdomain);
501
- this.logger.log(`Cleared cache for tenant: ${tenantIdentifier}`);
502
- }
503
- }
504
- /**
505
- * Clear all cached tenant configurations
506
- */
507
- clearAllCaches() {
508
- const size = this.tenantConfigCache.size;
509
- this.tenantConfigCache.clear();
510
- this.logger.log(`Cleared ${size} cached tenant configs`);
511
- }
512
- /**
513
- * Get the Drizzle database instance for the primary database.
514
- * This is a synchronous property that returns the initialized Drizzle client.
515
- *
516
- * @returns Primary database Drizzle instance
517
- * @throws Error if primary database client is not initialized
518
- */
519
- get drizzleClient() {
520
- if (!this.db) {
521
- throw new Error("Primary database client not initialized");
522
- }
523
- return this.db;
524
- }
525
- /**
526
- * Get the Drizzle schema
527
- */
528
- get schema() {
529
- return this.options.drizzleSchema;
530
- }
531
- /**
532
- * Decrypt database credentials
533
- *
534
- * Override this method to implement your encryption strategy
535
- *
536
- * @param encrypted Encrypted value
537
- * @returns Decrypted value
538
- */
539
- decrypt(encrypted) {
540
- return encrypted;
541
- }
542
- async onModuleDestroy() {
543
- if (this.pool) {
544
- await this.pool.end();
545
- this.logger.log("Disconnected from primary database");
546
- }
547
- }
548
- };
549
- PrimaryDatabaseService = _ts_decorate3([
550
- (0, import_common4.Injectable)(),
551
- _ts_param2(0, (0, import_common4.Inject)(DATABASE_MODULE_OPTIONS)),
552
- _ts_metadata2("design:type", Function),
553
- _ts_metadata2("design:paramtypes", [
554
- typeof DatabaseModuleOptions === "undefined" ? Object : DatabaseModuleOptions
555
- ])
556
- ], PrimaryDatabaseService);
317
+ var SKIP_CSRF_KEY = "skipCsrf";
318
+ var SkipCsrf = /* @__PURE__ */ __name(() => (0, import_common4.SetMetadata)(SKIP_CSRF_KEY, true), "SkipCsrf");
557
319
 
558
320
  // src/auth/utils/token-hash.util.ts
559
321
  var crypto = __toESM(require("crypto"), 1);
@@ -569,17 +331,17 @@ function verifyTokenHash(token, expectedHash) {
569
331
  __name(verifyTokenHash, "verifyTokenHash");
570
332
 
571
333
  // src/auth/guards/vritti-auth.guard.ts
572
- function _ts_decorate4(decorators, target, key, desc) {
334
+ function _ts_decorate3(decorators, target, key, desc) {
573
335
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
574
336
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
575
337
  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
338
  return c > 3 && r && Object.defineProperty(target, key, r), r;
577
339
  }
578
- __name(_ts_decorate4, "_ts_decorate");
579
- function _ts_metadata3(k, v) {
340
+ __name(_ts_decorate3, "_ts_decorate");
341
+ function _ts_metadata2(k, v) {
580
342
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
581
343
  }
582
- __name(_ts_metadata3, "_ts_metadata");
344
+ __name(_ts_metadata2, "_ts_metadata");
583
345
  var VrittiAuthGuard = class _VrittiAuthGuard {
584
346
  static {
585
347
  __name(this, "VrittiAuthGuard");
@@ -587,14 +349,12 @@ var VrittiAuthGuard = class _VrittiAuthGuard {
587
349
  reflector;
588
350
  _configService;
589
351
  jwtService;
590
- primaryDatabase;
591
352
  requestService;
592
353
  logger = new import_common5.Logger(_VrittiAuthGuard.name);
593
- constructor(reflector, _configService, jwtService, primaryDatabase, requestService) {
354
+ constructor(reflector, _configService, jwtService, requestService) {
594
355
  this.reflector = reflector;
595
356
  this._configService = _configService;
596
357
  this.jwtService = jwtService;
597
- this.primaryDatabase = primaryDatabase;
598
358
  this.requestService = requestService;
599
359
  }
600
360
  async canActivate(context) {
@@ -612,69 +372,44 @@ var VrittiAuthGuard = class _VrittiAuthGuard {
612
372
  context.getClass()
613
373
  ]);
614
374
  if (isPublic) {
615
- this.logger.debug("Public endpoint detected, skipping authentication");
616
375
  return true;
617
376
  }
618
377
  const isOnboarding = this.reflector.getAllAndOverride("isOnboarding", [
619
378
  context.getHandler(),
620
379
  context.getClass()
621
380
  ]);
381
+ const isReset = this.reflector.getAllAndOverride(RESET_KEY, [
382
+ context.getHandler(),
383
+ context.getClass()
384
+ ]);
385
+ const isSseEndpoint = this.reflector.get(import_constants.SSE_METADATA, context.getHandler());
386
+ if (isSseEndpoint) {
387
+ return this.handleSseAuth(request, isOnboarding);
388
+ }
622
389
  try {
623
390
  const accessToken = this.requestService.getAccessToken();
624
391
  if (!accessToken) {
625
- this.logger.warn("Access token not found in Authorization header");
626
392
  throw new import_common5.UnauthorizedException("Access token not found");
627
393
  }
628
- const decodedToken = this.jwtService.decode(accessToken);
629
- if (!decodedToken) {
630
- this.logger.warn("Failed to decode access token");
631
- throw new import_common5.UnauthorizedException("Invalid token format");
632
- }
633
- if (isOnboarding) {
634
- if (decodedToken.type !== "onboarding") {
635
- this.logger.warn("Onboarding endpoint requires onboarding token");
636
- throw new import_common5.UnauthorizedException("This endpoint requires an onboarding token");
637
- }
638
- const validatedToken2 = this.validateAccessToken(accessToken);
639
- this.logger.debug("Onboarding token validated successfully");
640
- this.validateRefreshTokenBinding(context, validatedToken2);
641
- const userId2 = validatedToken2.userId;
642
- request.user = {
643
- id: userId2
644
- };
645
- return true;
646
- }
647
- if (decodedToken.type === "onboarding") {
648
- this.logger.warn("Regular endpoint accessed with onboarding token");
649
- throw new import_common5.UnauthorizedException("Onboarding tokens cannot access this endpoint");
650
- }
651
- const validatedToken = this.validateAccessToken(accessToken);
652
- this.logger.debug("Access token validated successfully");
653
- this.validateRefreshTokenBinding(context, validatedToken);
654
- const userId = validatedToken.userId;
655
- request.user = {
656
- id: userId
657
- };
658
- const tenantIdentifier = this.requestService.getTenantIdentifier();
659
- if (!tenantIdentifier) {
660
- this.logger.warn("Tenant identifier not found in request");
661
- throw new import_common5.UnauthorizedException("Tenant identifier not found");
394
+ const decodedAccessToken = this.validateAccessToken(accessToken);
395
+ if (decodedAccessToken.tokenType !== "access") {
396
+ throw new import_common5.UnauthorizedException("Invalid token type");
662
397
  }
663
- this.logger.debug(`Tenant identifier extracted: ${tenantIdentifier}`);
664
- if (tenantIdentifier === "cloud") {
665
- this.logger.debug("Platform admin access detected, skipping tenant database validation");
666
- return true;
398
+ this.validateRefreshTokenBinding(decodedAccessToken);
399
+ if (isOnboarding && decodedAccessToken.sessionType !== "ONBOARDING") {
400
+ throw new import_common5.UnauthorizedException("This endpoint requires an onboarding session");
667
401
  }
668
- const tenantInfo = await this.primaryDatabase.getTenantInfo(tenantIdentifier);
669
- if (!tenantInfo) {
670
- this.logger.warn(`Invalid tenant: ${tenantIdentifier}`);
671
- throw new import_common5.UnauthorizedException("Invalid tenant");
402
+ if (isReset && decodedAccessToken.sessionType !== "RESET") {
403
+ throw new import_common5.UnauthorizedException("This endpoint requires a reset session");
672
404
  }
673
- if (tenantInfo.status !== "ACTIVE") {
674
- this.logger.warn(`Tenant ${tenantIdentifier} has status: ${tenantInfo.status}`);
675
- throw new import_common5.UnauthorizedException(`Tenant is ${tenantInfo.status}`);
405
+ if (!isOnboarding && !isReset && (decodedAccessToken.sessionType === "ONBOARDING" || decodedAccessToken.sessionType === "RESET")) {
406
+ throw new import_common5.UnauthorizedException(`${decodedAccessToken.sessionType} sessions cannot access this endpoint`);
676
407
  }
677
- this.logger.debug(`Tenant validated: ${tenantInfo.subdomain} (${tenantInfo.type})`);
408
+ request.sessionInfo = {
409
+ userId: decodedAccessToken.userId,
410
+ sessionId: decodedAccessToken.sessionId,
411
+ sessionType: decodedAccessToken.sessionType
412
+ };
678
413
  return true;
679
414
  } catch (error) {
680
415
  if (error instanceof import_common5.UnauthorizedException) {
@@ -684,109 +419,91 @@ var VrittiAuthGuard = class _VrittiAuthGuard {
684
419
  throw new import_common5.UnauthorizedException("Authentication failed");
685
420
  }
686
421
  }
687
- /**
688
- * Validate access token with proper expiry checks
689
- * Throws UnauthorizedException if token is invalid or expired
690
- */
422
+ // Validates JWT signature, expiry, and not-before claims
691
423
  validateAccessToken(token) {
692
424
  try {
693
- const decoded = this.jwtService.verify(token);
694
- this.logger.debug(`Access token decoded for user: ${decoded.userId}`);
695
- if (decoded.exp) {
696
- const expiryTime = decoded.exp * 1e3;
697
- const currentTime = Date.now();
698
- const timeRemaining = expiryTime - currentTime;
699
- this.logger.debug(`Access token valid for ${Math.floor(timeRemaining / 1e3)} more seconds`);
700
- }
701
- return decoded;
425
+ return this.jwtService.verify(token);
702
426
  } catch (error) {
703
- if (error instanceof import_common5.UnauthorizedException) {
704
- throw error;
705
- }
427
+ if (error instanceof import_common5.UnauthorizedException) throw error;
706
428
  const jwtError = error;
707
429
  if (jwtError?.name === "TokenExpiredError") {
708
- this.logger.warn(`Access token expired at: ${jwtError?.expiredAt}`);
709
430
  throw new import_common5.UnauthorizedException("Access token has expired");
710
431
  }
711
432
  if (jwtError?.name === "JsonWebTokenError") {
712
- this.logger.warn(`Access token verification failed: ${jwtError?.message}`);
713
433
  throw new import_common5.UnauthorizedException("Invalid access token");
714
434
  }
715
435
  if (jwtError?.name === "NotBeforeError") {
716
- this.logger.warn("Access token used before valid (nbf claim)");
717
436
  throw new import_common5.UnauthorizedException("Access token not yet valid");
718
437
  }
719
- this.logger.error("Unexpected error validating access token", error);
720
438
  throw new import_common5.UnauthorizedException("Access token validation failed");
721
439
  }
722
440
  }
723
- /**
724
- * Validate that the access token is bound to the refresh token in the cookie.
725
- * This prevents token theft - a stolen access token is useless without the
726
- * corresponding refresh token cookie.
727
- *
728
- * @param context - The execution context containing the request
729
- * @param validatedToken - The decoded and validated JWT token
730
- * @throws UnauthorizedException if token binding validation fails
731
- */
732
- validateRefreshTokenBinding(context, validatedToken) {
733
- const config = getConfig();
734
- if (!config.jwt.validateTokenBinding) {
735
- this.logger.debug("Token binding validation is disabled");
736
- return;
737
- }
738
- if (!validatedToken.refreshTokenHash) {
739
- this.logger.debug("Token does not contain refreshTokenHash, skipping binding validation");
740
- return;
441
+ // Validates that the access token is bound to the refresh token in the cookie
442
+ validateRefreshTokenBinding(decodedAccessToken) {
443
+ if (!decodedAccessToken.refreshTokenHash) {
444
+ throw new import_common5.UnauthorizedException("Token missing refresh token binding");
741
445
  }
742
- const request = context.switchToHttp().getRequest();
743
- const cookies = request.cookies || {};
744
- const refreshToken = cookies[config.cookie.refreshCookieName];
446
+ const refreshToken = this.requestService.getRefreshToken();
745
447
  if (!refreshToken) {
746
- this.logger.warn("Session validation failed - refresh token cookie not found");
747
448
  throw new import_common5.UnauthorizedException("Session validation failed");
748
449
  }
749
- if (!verifyTokenHash(refreshToken, validatedToken.refreshTokenHash)) {
750
- this.logger.warn("Session validation failed - token binding mismatch");
450
+ if (!verifyTokenHash(refreshToken, decodedAccessToken.refreshTokenHash)) {
751
451
  throw new import_common5.UnauthorizedException("Session validation failed");
752
452
  }
753
- this.logger.debug("Token binding validated successfully");
754
453
  }
755
- /**
756
- * Validate CSRF token for state-changing requests
757
- * Uses Fastify's csrf-protection plugin for token validation
758
- *
759
- * @param request - Fastify request object
760
- * @param reply - Fastify reply object
761
- * @throws ForbiddenException if CSRF validation fails
762
- */
454
+ // Authenticates SSE connections using the refresh token httpOnly cookie
455
+ handleSseAuth(request, isOnboarding) {
456
+ const refreshToken = this.requestService.getRefreshToken();
457
+ if (!refreshToken) {
458
+ throw new import_common5.UnauthorizedException("Authentication required");
459
+ }
460
+ let decoded;
461
+ try {
462
+ decoded = this.jwtService.verify(refreshToken);
463
+ } catch {
464
+ throw new import_common5.UnauthorizedException("Invalid or expired session");
465
+ }
466
+ if (decoded.tokenType !== "refresh") {
467
+ throw new import_common5.UnauthorizedException("Invalid token type");
468
+ }
469
+ if (isOnboarding && decoded.sessionType !== "ONBOARDING") {
470
+ throw new import_common5.UnauthorizedException("This endpoint requires an onboarding session");
471
+ }
472
+ request.sessionInfo = {
473
+ userId: decoded.userId,
474
+ sessionId: decoded.sessionId,
475
+ sessionType: decoded.sessionType
476
+ };
477
+ return true;
478
+ }
479
+ // Validates CSRF token for state-changing requests
763
480
  async validateCsrf(request, reply) {
764
481
  const safeMethods = [
765
482
  "GET",
766
483
  "HEAD",
767
484
  "OPTIONS"
768
485
  ];
769
- if (safeMethods.includes(request.method)) {
770
- return;
771
- }
486
+ if (safeMethods.includes(request.method)) return;
772
487
  try {
773
488
  const fastifyInstance = request.server;
774
- if (!fastifyInstance.csrfProtection) {
775
- this.logger.error("CSRF protection plugin not found. Ensure @fastify/csrf-protection is registered.");
489
+ const csrfProtection = fastifyInstance.csrfProtection;
490
+ if (!csrfProtection) {
776
491
  throw new import_common5.ForbiddenException("CSRF protection not configured");
777
492
  }
778
493
  await new Promise((resolve, reject) => {
779
- fastifyInstance.csrfProtection(request, reply, (err) => {
780
- if (err) {
781
- reject(err);
782
- } else {
783
- resolve();
784
- }
494
+ const originalSend = reply.send.bind(reply);
495
+ reply.send = () => {
496
+ reply.send = originalSend;
497
+ reject(new Error("CSRF validation failed"));
498
+ return reply;
499
+ };
500
+ csrfProtection(request, reply, (err) => {
501
+ reply.send = originalSend;
502
+ if (err) reject(err);
503
+ else resolve();
785
504
  });
786
505
  });
787
- this.logger.debug(`CSRF validation successful for ${request.method} ${request.url}`);
788
506
  } catch (error) {
789
- this.logger.warn(`CSRF validation failed for ${request.method} ${request.url}: ${error instanceof Error ? error.message : "Unknown error"}`);
790
507
  throw new import_common5.ForbiddenException({
791
508
  errors: [
792
509
  {
@@ -799,54 +516,166 @@ var VrittiAuthGuard = class _VrittiAuthGuard {
799
516
  }
800
517
  }
801
518
  };
802
- VrittiAuthGuard = _ts_decorate4([
519
+ VrittiAuthGuard = _ts_decorate3([
803
520
  (0, import_common5.Injectable)({
804
521
  scope: import_common5.Scope.REQUEST
805
522
  }),
806
- _ts_metadata3("design:type", Function),
807
- _ts_metadata3("design:paramtypes", [
523
+ _ts_metadata2("design:type", Function),
524
+ _ts_metadata2("design:paramtypes", [
808
525
  typeof import_core2.Reflector === "undefined" ? Object : import_core2.Reflector,
809
526
  typeof import_config2.ConfigService === "undefined" ? Object : import_config2.ConfigService,
810
527
  typeof import_jwt.JwtService === "undefined" ? Object : import_jwt.JwtService,
811
- typeof PrimaryDatabaseService === "undefined" ? Object : PrimaryDatabaseService,
812
528
  typeof RequestService === "undefined" ? Object : RequestService
813
529
  ])
814
530
  ], VrittiAuthGuard);
815
531
 
816
- // src/auth/auth-config.module.ts
817
- function _ts_decorate5(decorators, target, key, desc) {
532
+ // src/auth/services/jwt-auth.service.ts
533
+ var import_common6 = require("@nestjs/common");
534
+ var import_config3 = require("@nestjs/config");
535
+ var import_jwt2 = require("@nestjs/jwt");
536
+
537
+ // src/utils/time.utils.ts
538
+ function parseExpiryToMs(expiry) {
539
+ const match = expiry.match(/^(\d+)([smhdwy])$/);
540
+ if (!match) throw new Error(`Invalid expiry format: ${expiry}`);
541
+ const value = Number.parseInt(match[1], 10);
542
+ const multipliers = {
543
+ s: 1e3,
544
+ m: 6e4,
545
+ h: 36e5,
546
+ d: 864e5,
547
+ w: 6048e5,
548
+ y: 31536e6
549
+ };
550
+ return value * multipliers[match[2]];
551
+ }
552
+ __name(parseExpiryToMs, "parseExpiryToMs");
553
+
554
+ // src/auth/jwt.config.ts
555
+ var jwtConfigFactory = /* @__PURE__ */ __name((configService) => ({
556
+ secret: configService.getOrThrow("JWT_SECRET"),
557
+ signOptions: {
558
+ issuer: "vritti-api"
559
+ }
560
+ }), "jwtConfigFactory");
561
+ var getTokenExpiry = /* @__PURE__ */ __name((configService) => ({
562
+ access: configService.getOrThrow("ACCESS_TOKEN_EXPIRY"),
563
+ refresh: configService.getOrThrow("REFRESH_TOKEN_EXPIRY")
564
+ }), "getTokenExpiry");
565
+ var TokenType = /* @__PURE__ */ (function(TokenType2) {
566
+ TokenType2["ACCESS"] = "access";
567
+ TokenType2["REFRESH"] = "refresh";
568
+ return TokenType2;
569
+ })({});
570
+
571
+ // src/auth/services/jwt-auth.service.ts
572
+ function _ts_decorate4(decorators, target, key, desc) {
818
573
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
819
574
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
820
575
  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;
821
576
  return c > 3 && r && Object.defineProperty(target, key, r), r;
822
577
  }
823
- __name(_ts_decorate5, "_ts_decorate");
824
- var AuthConfigModule = class _AuthConfigModule {
578
+ __name(_ts_decorate4, "_ts_decorate");
579
+ function _ts_metadata3(k, v) {
580
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
581
+ }
582
+ __name(_ts_metadata3, "_ts_metadata");
583
+ var JwtAuthService = class _JwtAuthService {
825
584
  static {
826
- __name(this, "AuthConfigModule");
585
+ __name(this, "JwtAuthService");
827
586
  }
828
- /**
829
- * Register the auth module with async configuration
830
- *
831
- * This method:
832
- * 1. Configures JwtModule with JWT_SECRET from ConfigService
833
- * 2. Provides VrittiAuthGuard globally (applies to all routes)
834
- * 3. Exports JwtModule for use in other modules (e.g., for signing tokens)
835
- *
836
- * @returns Dynamic module configuration
837
- */
838
- static forRootAsync() {
839
- return {
840
- module: _AuthConfigModule,
841
- imports: [
842
- import_config4.ConfigModule,
843
- RequestModule,
844
- import_jwt2.JwtModule.registerAsync({
845
- imports: [
846
- import_config4.ConfigModule
847
- ],
848
- inject: [
849
- import_config4.ConfigService
587
+ jwtService;
588
+ configService;
589
+ logger = new import_common6.Logger(_JwtAuthService.name);
590
+ tokenExpiry;
591
+ constructor(jwtService, configService) {
592
+ this.jwtService = jwtService;
593
+ this.configService = configService;
594
+ this.tokenExpiry = getTokenExpiry(configService);
595
+ }
596
+ // Generates an access token bound to the given refresh token
597
+ generateAccessToken(userId, sessionId, sessionType, refreshToken) {
598
+ return this.jwtService.sign({
599
+ sessionType,
600
+ tokenType: TokenType.ACCESS,
601
+ userId,
602
+ sessionId,
603
+ refreshTokenHash: hashToken(refreshToken)
604
+ }, {
605
+ expiresIn: this.tokenExpiry.access
606
+ });
607
+ }
608
+ // Generates a refresh token for session persistence
609
+ generateRefreshToken(userId, sessionId, sessionType) {
610
+ return this.jwtService.sign({
611
+ sessionType,
612
+ tokenType: TokenType.REFRESH,
613
+ userId,
614
+ sessionId
615
+ }, {
616
+ expiresIn: this.tokenExpiry.refresh
617
+ });
618
+ }
619
+ // Signs an arbitrary payload with optional JWT options
620
+ sign(payload, options) {
621
+ return this.jwtService.sign(payload, options);
622
+ }
623
+ // Verifies a token and ensures it matches the expected token type
624
+ verify(token, expectedType) {
625
+ try {
626
+ const payload = this.jwtService.verify(token);
627
+ if (payload.tokenType !== expectedType) {
628
+ throw new Error(`Expected ${expectedType} token, got ${payload.tokenType}`);
629
+ }
630
+ return payload;
631
+ } catch (error) {
632
+ this.logger.error(`Failed to verify ${expectedType} token`, error);
633
+ throw error;
634
+ }
635
+ }
636
+ // Returns the expiry as a Date for the given token type
637
+ getExpiryTime(type) {
638
+ return new Date(Date.now() + parseExpiryToMs(this.tokenExpiry[type]));
639
+ }
640
+ // Returns the token lifetime in seconds for the given type
641
+ getExpiryInSeconds(type) {
642
+ return Math.floor(parseExpiryToMs(this.tokenExpiry[type]) / 1e3);
643
+ }
644
+ };
645
+ JwtAuthService = _ts_decorate4([
646
+ (0, import_common6.Injectable)(),
647
+ _ts_metadata3("design:type", Function),
648
+ _ts_metadata3("design:paramtypes", [
649
+ typeof import_jwt2.JwtService === "undefined" ? Object : import_jwt2.JwtService,
650
+ typeof import_config3.ConfigService === "undefined" ? Object : import_config3.ConfigService
651
+ ])
652
+ ], JwtAuthService);
653
+
654
+ // src/auth/auth-config.module.ts
655
+ function _ts_decorate5(decorators, target, key, desc) {
656
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
657
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
658
+ 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;
659
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
660
+ }
661
+ __name(_ts_decorate5, "_ts_decorate");
662
+ var AuthConfigModule = class _AuthConfigModule {
663
+ static {
664
+ __name(this, "AuthConfigModule");
665
+ }
666
+ // Registers JWT and global VrittiAuthGuard with async config
667
+ static forRootAsync() {
668
+ return {
669
+ module: _AuthConfigModule,
670
+ imports: [
671
+ import_config4.ConfigModule,
672
+ RequestModule,
673
+ import_jwt4.JwtModule.registerAsync({
674
+ imports: [
675
+ import_config4.ConfigModule
676
+ ],
677
+ inject: [
678
+ import_config4.ConfigService
850
679
  ],
851
680
  useFactory: /* @__PURE__ */ __name((config) => ({
852
681
  secret: config.get("JWT_SECRET"),
@@ -865,41 +694,84 @@ var AuthConfigModule = class _AuthConfigModule {
865
694
  {
866
695
  provide: import_core3.APP_GUARD,
867
696
  useClass: VrittiAuthGuard
868
- }
697
+ },
698
+ JwtAuthService
869
699
  ],
870
700
  exports: [
871
- import_jwt2.JwtModule
701
+ import_jwt4.JwtModule,
702
+ JwtAuthService
872
703
  ]
873
704
  };
874
705
  }
875
706
  };
876
707
  AuthConfigModule = _ts_decorate5([
877
- (0, import_common6.Global)(),
878
- (0, import_common6.Module)({})
708
+ (0, import_common7.Global)(),
709
+ (0, import_common7.Module)({})
879
710
  ], AuthConfigModule);
880
711
 
712
+ // src/auth/decorators/access-token.decorator.ts
713
+ var import_common8 = require("@nestjs/common");
714
+ var AccessToken = (0, import_common8.createParamDecorator)((_data, ctx) => {
715
+ const request = ctx.switchToHttp().getRequest();
716
+ const authHeader = request.headers.authorization;
717
+ return authHeader?.replace("Bearer ", "") || "";
718
+ });
719
+
881
720
  // src/auth/decorators/onboarding.decorator.ts
882
- var import_common7 = require("@nestjs/common");
883
- var Onboarding = /* @__PURE__ */ __name(() => (0, import_common7.SetMetadata)("isOnboarding", true), "Onboarding");
721
+ var import_common9 = require("@nestjs/common");
722
+ var Onboarding = /* @__PURE__ */ __name(() => (0, import_common9.SetMetadata)("isOnboarding", true), "Onboarding");
884
723
 
885
724
  // src/auth/decorators/public.decorator.ts
886
- var import_common8 = require("@nestjs/common");
887
- var Public = /* @__PURE__ */ __name(() => (0, import_common8.SetMetadata)("isPublic", true), "Public");
725
+ var import_common10 = require("@nestjs/common");
726
+ var Public = /* @__PURE__ */ __name(() => (0, import_common10.SetMetadata)("isPublic", true), "Public");
727
+
728
+ // src/auth/decorators/refresh-token-cookie.decorator.ts
729
+ var import_common11 = require("@nestjs/common");
730
+ var RefreshTokenCookie = (0, import_common11.createParamDecorator)((_data, ctx) => {
731
+ const request = ctx.switchToHttp().getRequest();
732
+ const cookies = request.cookies ?? {};
733
+ const config = getConfig();
734
+ return cookies[config.cookie.refreshCookieName];
735
+ });
736
+
737
+ // src/auth/decorators/session-data.decorator.ts
738
+ var import_common12 = require("@nestjs/common");
739
+ var SessionData = (0, import_common12.createParamDecorator)((_data, ctx) => {
740
+ const request = ctx.switchToHttp().getRequest();
741
+ const sessionInfo = request.sessionInfo;
742
+ if (!sessionInfo?.sessionId) {
743
+ throw new Error("Session info not found on request. Ensure route is protected by auth guard.");
744
+ }
745
+ return {
746
+ userId: sessionInfo.userId,
747
+ sessionId: sessionInfo.sessionId,
748
+ sessionType: sessionInfo.sessionType
749
+ };
750
+ });
888
751
 
889
752
  // src/auth/decorators/user-id.decorator.ts
890
- var import_common9 = require("@nestjs/common");
891
- var UserId = (0, import_common9.createParamDecorator)((_data, ctx) => {
753
+ var import_common13 = require("@nestjs/common");
754
+ var UserId = (0, import_common13.createParamDecorator)((_data, ctx) => {
892
755
  const request = ctx.switchToHttp().getRequest();
893
- const user = request.user;
894
- if (!user?.id) {
756
+ const sessionInfo = request.sessionInfo;
757
+ if (!sessionInfo?.userId) {
895
758
  throw new Error("User ID not found on request. Ensure route is protected by auth guard.");
896
759
  }
897
- return user.id;
760
+ return sessionInfo.userId;
898
761
  });
899
762
 
900
- // src/auth/guards/sse-auth.guard.ts
901
- var import_common10 = require("@nestjs/common");
902
- var import_jwt3 = require("@nestjs/jwt");
763
+ // src/database/database.module.ts
764
+ var import_common17 = require("@nestjs/common");
765
+ var import_core4 = require("@nestjs/core");
766
+
767
+ // src/database/constants.ts
768
+ var DATABASE_MODULE_OPTIONS = Symbol("DATABASE_MODULE_OPTIONS");
769
+
770
+ // src/database/services/primary-database.service.ts
771
+ var import_common14 = require("@nestjs/common");
772
+ var import_drizzle_orm = require("drizzle-orm");
773
+ var import_node_postgres = require("drizzle-orm/node-postgres");
774
+ var import_pg = require("pg");
903
775
  function _ts_decorate6(decorators, target, key, desc) {
904
776
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
905
777
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
@@ -911,102 +783,184 @@ function _ts_metadata4(k, v) {
911
783
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
912
784
  }
913
785
  __name(_ts_metadata4, "_ts_metadata");
914
- var SSE_ALLOWED_ORIGINS = [
915
- "http://localhost:5173",
916
- "http://localhost:3001",
917
- "http://localhost:3012",
918
- "http://localhost:5174",
919
- "http://local.vrittiai.com:3012",
920
- "http://cloud.local.vrittiai.com:3012",
921
- "https://local.vrittiai.com:3012",
922
- "https://cloud.local.vrittiai.com:3012"
923
- ];
924
- var SseAuthGuard = class _SseAuthGuard {
786
+ function _ts_param2(paramIndex, decorator) {
787
+ return function(target, key) {
788
+ decorator(target, key, paramIndex);
789
+ };
790
+ }
791
+ __name(_ts_param2, "_ts_param");
792
+ var PrimaryDatabaseService = class _PrimaryDatabaseService {
925
793
  static {
926
- __name(this, "SseAuthGuard");
794
+ __name(this, "PrimaryDatabaseService");
927
795
  }
928
- jwtService;
929
- logger = new import_common10.Logger(_SseAuthGuard.name);
930
- constructor(jwtService) {
931
- this.jwtService = jwtService;
796
+ options;
797
+ logger = new import_common14.Logger(_PrimaryDatabaseService.name);
798
+ pool = null;
799
+ db = null;
800
+ tenantConfigCache = /* @__PURE__ */ new Map();
801
+ cacheTTL;
802
+ constructor(options) {
803
+ this.options = options;
804
+ this.cacheTTL = options.connectionCacheTTL || 3e5;
932
805
  }
933
- async canActivate(context) {
934
- const request = context.switchToHttp().getRequest();
935
- const response = context.switchToHttp().getResponse();
936
- this.setCorsHeaders(request, response);
937
- const token = request.query?.token;
938
- if (!token) {
939
- this.logger.warn("SSE authentication failed: token not found in query params");
940
- throw new import_common10.UnauthorizedException("Authentication required");
806
+ async onModuleInit() {
807
+ if (this.options.primaryDb) {
808
+ await this.initializeDrizzleClient();
941
809
  }
810
+ }
811
+ // Initializes connection to primary database using Drizzle
812
+ async initializeDrizzleClient() {
942
813
  try {
943
- const decodedToken = this.jwtService.decode(token);
944
- if (!decodedToken) {
945
- this.logger.warn("SSE authentication failed: invalid token format");
946
- throw new import_common10.UnauthorizedException("Invalid token format");
947
- }
948
- if (decodedToken.type !== "onboarding") {
949
- this.logger.warn("SSE authentication failed: endpoint requires onboarding token");
950
- throw new import_common10.UnauthorizedException("This endpoint requires an onboarding token");
951
- }
952
- const validatedToken = this.jwtService.verify(token);
953
- this.logger.debug(`SSE token validated for user: ${validatedToken.userId}`);
954
- request.user = {
955
- id: validatedToken.userId
956
- };
957
- return true;
814
+ const databaseUrl = this.buildPrimaryDbUrl();
815
+ this.pool = new import_pg.Pool({
816
+ connectionString: databaseUrl,
817
+ max: this.options.maxConnections || 10
818
+ });
819
+ this.logger.debug(`Schema keys passed to drizzle: [${Object.keys(this.options.drizzleSchema || {}).join(", ")}]`);
820
+ this.logger.debug(`Relations keys passed to drizzle: [${Object.keys(this.options.drizzleRelations || {}).join(", ")}]`);
821
+ this.db = (0, import_node_postgres.drizzle)({
822
+ client: this.pool,
823
+ schema: this.options.drizzleSchema,
824
+ relations: this.options.drizzleRelations
825
+ });
826
+ this.logger.debug(`Drizzle query keys after init: [${Object.keys(this.db.query || {}).join(", ")}]`);
827
+ await this.pool.query("SELECT 1");
828
+ this.logger.log("Connected to primary database (tenant registry)");
958
829
  } catch (error) {
959
- if (error instanceof import_common10.UnauthorizedException) {
960
- throw error;
830
+ this.logger.error("Failed to connect to primary database", error);
831
+ throw new import_common14.InternalServerErrorException("Failed to initialize tenant registry");
832
+ }
833
+ }
834
+ // Builds the PostgreSQL connection URL from primary database config properties
835
+ buildPrimaryDbUrl() {
836
+ if (!this.options.primaryDb) {
837
+ throw new Error("Primary database configuration not provided");
838
+ }
839
+ const { host, port = 5432, username, password, database, schema = "public", sslMode = "require" } = this.options.primaryDb;
840
+ let url = `postgresql://${username}:${encodeURIComponent(password)}@${host}:${port}/${database}`;
841
+ const params = new URLSearchParams();
842
+ if (schema) {
843
+ params.set("schema", schema);
844
+ }
845
+ params.set("sslmode", sslMode);
846
+ const queryString = params.toString();
847
+ if (queryString) {
848
+ url += `?${queryString}`;
849
+ }
850
+ this.logger.debug(`Primary DB connection URL: ${this.maskPassword(url)}`);
851
+ return url;
852
+ }
853
+ // Masks password in connection URL for safe logging
854
+ maskPassword(url) {
855
+ return url.replace(/:([^@]+)@/, ":****@");
856
+ }
857
+ // Retrieves tenant configuration by ID or subdomain, with in-memory caching
858
+ async getTenantInfo(tenantIdentifier) {
859
+ const cached = this.tenantConfigCache.get(tenantIdentifier);
860
+ if (cached) {
861
+ this.logger.debug(`Cache hit for tenant: ${tenantIdentifier}`);
862
+ return cached;
863
+ }
864
+ try {
865
+ if (!this.db) {
866
+ throw new Error("Primary database client not initialized");
961
867
  }
962
- const jwtError = error;
963
- if (jwtError?.name === "TokenExpiredError") {
964
- this.logger.warn("SSE authentication failed: token expired");
965
- throw new import_common10.UnauthorizedException("Token has expired");
868
+ this.logger.debug(`Querying primary database for tenant: ${tenantIdentifier}`);
869
+ const schema = this.options.drizzleSchema;
870
+ const { tenants, tenantDatabaseConfigs } = schema;
871
+ const result = await this.db.select().from(tenants).leftJoin(tenantDatabaseConfigs, (0, import_drizzle_orm.eq)(tenants.id, tenantDatabaseConfigs.tenantId)).where((0, import_drizzle_orm.or)((0, import_drizzle_orm.eq)(tenants.id, tenantIdentifier), (0, import_drizzle_orm.eq)(tenants.subdomain, tenantIdentifier))).limit(1);
872
+ if (!result.length) {
873
+ this.logger.warn(`Tenant not found: ${tenantIdentifier}`);
874
+ return null;
966
875
  }
967
- if (jwtError?.name === "JsonWebTokenError") {
968
- this.logger.warn(`SSE authentication failed: ${jwtError?.message}`);
969
- throw new import_common10.UnauthorizedException("Invalid token");
876
+ const row = result[0];
877
+ const tenant = row.tenants;
878
+ const config = row.tenant_database_configs;
879
+ if (tenant.status !== "ACTIVE") {
880
+ this.logger.warn(`Tenant not active: ${tenantIdentifier}`);
881
+ return null;
970
882
  }
971
- this.logger.error("Unexpected error in SSE auth guard", error);
972
- throw new import_common10.UnauthorizedException("Authentication failed");
883
+ const info = {
884
+ id: tenant.id,
885
+ subdomain: tenant.subdomain,
886
+ type: tenant.dbType,
887
+ status: tenant.status,
888
+ // For SHARED tenants: schema name
889
+ schemaName: config?.dbSchema || void 0,
890
+ // For DEDICATED tenants: database configuration from TenantDatabaseConfig table
891
+ databaseName: config?.dbName || void 0,
892
+ databaseHost: config?.dbHost || void 0,
893
+ databasePort: config?.dbPort || void 0,
894
+ databaseUsername: config?.dbUsername ? this.decrypt(config.dbUsername) : void 0,
895
+ databasePassword: config?.dbPassword ? this.decrypt(config.dbPassword) : void 0,
896
+ databaseSslMode: config?.dbSslMode || void 0,
897
+ connectionPoolSize: config?.connectionPoolSize || void 0
898
+ };
899
+ this.cacheInfo(info);
900
+ return info;
901
+ } catch (error) {
902
+ this.logger.error(`Failed to fetch tenant info: ${tenantIdentifier}`, error);
903
+ throw new import_common14.InternalServerErrorException("Failed to resolve tenant");
904
+ }
905
+ }
906
+ // Caches tenant info by both ID and subdomain with TTL expiration
907
+ cacheInfo(info) {
908
+ this.tenantConfigCache.set(info.id, info);
909
+ this.tenantConfigCache.set(info.subdomain, info);
910
+ setTimeout(() => {
911
+ this.tenantConfigCache.delete(info.id);
912
+ this.tenantConfigCache.delete(info.subdomain);
913
+ this.logger.debug(`Cache expired for tenant: ${info.subdomain}`);
914
+ }, this.cacheTTL);
915
+ }
916
+ // Clears cached tenant info for the given ID or subdomain
917
+ clearTenantCache(tenantIdentifier) {
918
+ const config = this.tenantConfigCache.get(tenantIdentifier);
919
+ if (config) {
920
+ this.tenantConfigCache.delete(config.id);
921
+ this.tenantConfigCache.delete(config.subdomain);
922
+ this.logger.log(`Cleared cache for tenant: ${tenantIdentifier}`);
923
+ }
924
+ }
925
+ // Clears all cached tenant configurations
926
+ clearAllCaches() {
927
+ const size = this.tenantConfigCache.size;
928
+ this.tenantConfigCache.clear();
929
+ this.logger.log(`Cleared ${size} cached tenant configs`);
930
+ }
931
+ // Returns the initialized Drizzle client, throwing if not yet initialized
932
+ get drizzleClient() {
933
+ if (!this.db) {
934
+ throw new Error("Primary database client not initialized");
973
935
  }
936
+ return this.db;
937
+ }
938
+ // Returns the Drizzle schema passed in module options
939
+ get schema() {
940
+ return this.options.drizzleSchema;
974
941
  }
975
- /**
976
- * Set CORS headers for SSE responses
977
- * Must be called before any potential exceptions
978
- */
979
- setCorsHeaders(request, response) {
980
- const origin = request.headers.origin;
981
- if (origin && SSE_ALLOWED_ORIGINS.includes(origin)) {
982
- response.header("Access-Control-Allow-Origin", origin);
983
- response.header("Access-Control-Allow-Credentials", "true");
984
- this.logger.debug(`CORS headers set for origin: ${origin}`);
985
- } else if (origin) {
986
- this.logger.warn(`SSE request from unauthorized origin: ${origin}`);
942
+ // Decrypts a database credential value (placeholder for actual decryption)
943
+ decrypt(encrypted) {
944
+ return encrypted;
945
+ }
946
+ async onModuleDestroy() {
947
+ if (this.pool) {
948
+ await this.pool.end();
949
+ this.logger.log("Disconnected from primary database");
987
950
  }
988
951
  }
989
952
  };
990
- SseAuthGuard = _ts_decorate6([
991
- (0, import_common10.Injectable)({
992
- scope: import_common10.Scope.REQUEST
993
- }),
953
+ PrimaryDatabaseService = _ts_decorate6([
954
+ (0, import_common14.Injectable)(),
955
+ _ts_param2(0, (0, import_common14.Inject)(DATABASE_MODULE_OPTIONS)),
994
956
  _ts_metadata4("design:type", Function),
995
957
  _ts_metadata4("design:paramtypes", [
996
- typeof import_jwt3.JwtService === "undefined" ? Object : import_jwt3.JwtService
958
+ typeof DatabaseModuleOptions === "undefined" ? Object : DatabaseModuleOptions
997
959
  ])
998
- ], SseAuthGuard);
999
-
1000
- // src/database/database.module.ts
1001
- var import_common15 = require("@nestjs/common");
1002
- var import_core5 = require("@nestjs/core");
1003
-
1004
- // src/database/interceptors/message-tenant-context.interceptor.ts
1005
- var import_common12 = require("@nestjs/common");
1006
- var import_operators = require("rxjs/operators");
960
+ ], PrimaryDatabaseService);
1007
961
 
1008
962
  // src/database/services/tenant-context.service.ts
1009
- var import_common11 = require("@nestjs/common");
963
+ var import_common15 = require("@nestjs/common");
1010
964
  function _ts_decorate7(decorators, target, key, desc) {
1011
965
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1012
966
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
@@ -1019,79 +973,47 @@ var TenantContextService = class {
1019
973
  __name(this, "TenantContextService");
1020
974
  }
1021
975
  tenantInfo = null;
1022
- /**
1023
- * Set tenant information for this request/message
1024
- *
1025
- * This is typically called by:
1026
- * - TenantContextInterceptor (for HTTP requests in gateway)
1027
- * - MessageTenantContextInterceptor (for RabbitMQ messages in microservices)
1028
- * - Manual context setup in message handlers
1029
- *
1030
- * @param tenantInfo Complete tenant information
1031
- * @throws Error if tenant context is already set (prevents accidental overwrites)
1032
- */
976
+ // Sets tenant info for this request, throwing if already set to prevent overwrites
1033
977
  setTenant(tenantInfo) {
1034
978
  if (this.tenantInfo) {
1035
979
  throw new Error("Tenant context already set for this request");
1036
980
  }
1037
981
  this.tenantInfo = tenantInfo;
1038
982
  }
1039
- /**
1040
- * Get tenant information for this request/message
1041
- *
1042
- * @returns Tenant information
1043
- * @throws UnauthorizedException if tenant context hasn't been set
1044
- */
983
+ // Returns the tenant info for this request, throwing if context is not set
1045
984
  getTenant() {
1046
985
  if (!this.tenantInfo) {
1047
- throw new import_common11.UnauthorizedException("Tenant context not set");
986
+ throw new import_common15.UnauthorizedException("Tenant context not set");
1048
987
  }
1049
988
  return this.tenantInfo;
1050
989
  }
1051
- /**
1052
- * Check if tenant context has been set
1053
- *
1054
- * @returns true if tenant context is available
1055
- */
990
+ // Returns true if tenant context has been set for this request
1056
991
  hasTenant() {
1057
992
  return this.tenantInfo !== null;
1058
993
  }
1059
- /**
1060
- * Clear tenant context
1061
- *
1062
- * This is useful for cleanup in RabbitMQ message handlers
1063
- * after the message has been processed.
1064
- *
1065
- * HTTP requests don't need manual cleanup as the service
1066
- * instance is destroyed when the request ends.
1067
- */
994
+ // Clears the tenant context (useful for RabbitMQ message handler cleanup)
1068
995
  clearTenant() {
1069
996
  this.tenantInfo = null;
1070
997
  }
1071
- /**
1072
- * Get tenant ID safely (returns null if not set)
1073
- *
1074
- * @returns Tenant ID or null
1075
- */
998
+ // Returns the tenant ID or null if context is not set
1076
999
  getTenantIdSafe() {
1077
1000
  return this.tenantInfo?.id ?? null;
1078
1001
  }
1079
- /**
1080
- * Get tenant subdomain safely (returns null if not set)
1081
- *
1082
- * @returns Tenant subdomain or null
1083
- */
1002
+ // Returns the tenant subdomain or null if context is not set
1084
1003
  getTenantSubdomainSafe() {
1085
1004
  return this.tenantInfo?.subdomain ?? null;
1086
1005
  }
1087
1006
  };
1088
1007
  TenantContextService = _ts_decorate7([
1089
- (0, import_common11.Injectable)({
1090
- scope: import_common11.Scope.REQUEST
1008
+ (0, import_common15.Injectable)({
1009
+ scope: import_common15.Scope.REQUEST
1091
1010
  })
1092
1011
  ], TenantContextService);
1093
1012
 
1094
- // src/database/interceptors/message-tenant-context.interceptor.ts
1013
+ // src/database/services/tenant-database.service.ts
1014
+ var import_common16 = require("@nestjs/common");
1015
+ var import_node_postgres2 = require("drizzle-orm/node-postgres");
1016
+ var import_pg2 = require("pg");
1095
1017
  function _ts_decorate8(decorators, target, key, desc) {
1096
1018
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1097
1019
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
@@ -1103,217 +1025,36 @@ function _ts_metadata5(k, v) {
1103
1025
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
1104
1026
  }
1105
1027
  __name(_ts_metadata5, "_ts_metadata");
1106
- var MessageTenantContextInterceptor = class _MessageTenantContextInterceptor {
1028
+ function _ts_param3(paramIndex, decorator) {
1029
+ return function(target, key) {
1030
+ decorator(target, key, paramIndex);
1031
+ };
1032
+ }
1033
+ __name(_ts_param3, "_ts_param");
1034
+ var TenantDatabaseService = class _TenantDatabaseService {
1107
1035
  static {
1108
- __name(this, "MessageTenantContextInterceptor");
1036
+ __name(this, "TenantDatabaseService");
1109
1037
  }
1038
+ options;
1110
1039
  tenantContext;
1111
- logger = new import_common12.Logger(_MessageTenantContextInterceptor.name);
1112
- constructor(tenantContext) {
1113
- this.tenantContext = tenantContext;
1114
- }
1115
- intercept(context, next) {
1116
- const contextType = context.getType();
1117
- if (contextType === "rpc") {
1118
- const rpcContext = context.switchToRpc();
1119
- const payload = rpcContext.getData();
1120
- if (payload?.tenant) {
1121
- const tenant = payload.tenant;
1122
- this.logger.debug(`Setting tenant context from message: ${tenant.subdomain}`);
1123
- try {
1124
- this.tenantContext.setTenant(tenant);
1125
- this.logger.log(`Tenant context set: ${tenant.subdomain} (${tenant.type})`);
1126
- } catch (error) {
1127
- this.logger.error("Failed to set tenant context from message", error);
1128
- }
1129
- } else {
1130
- this.logger.warn("Message payload missing tenant information");
1131
- }
1132
- }
1133
- return next.handle().pipe((0, import_operators.tap)({
1134
- next: /* @__PURE__ */ __name(() => {
1135
- this.cleanupContext();
1136
- }, "next"),
1137
- error: /* @__PURE__ */ __name(() => {
1138
- this.cleanupContext();
1139
- }, "error"),
1140
- complete: /* @__PURE__ */ __name(() => {
1141
- this.cleanupContext();
1142
- }, "complete")
1143
- }));
1144
- }
1145
- /**
1146
- * Clean up tenant context after message is processed
1147
- */
1148
- cleanupContext() {
1149
- if (this.tenantContext.hasTenant()) {
1150
- const tenant = this.tenantContext.getTenantIdSafe();
1151
- this.tenantContext.clearTenant();
1152
- this.logger.debug(`Cleaned up tenant context: ${tenant}`);
1153
- }
1154
- }
1155
- };
1156
- MessageTenantContextInterceptor = _ts_decorate8([
1157
- (0, import_common12.Injectable)({
1158
- scope: import_common12.Scope.REQUEST
1159
- }),
1160
- _ts_metadata5("design:type", Function),
1161
- _ts_metadata5("design:paramtypes", [
1162
- typeof TenantContextService === "undefined" ? Object : TenantContextService
1163
- ])
1164
- ], MessageTenantContextInterceptor);
1165
-
1166
- // src/database/interceptors/tenant-context.interceptor.ts
1167
- var import_common13 = require("@nestjs/common");
1168
- var import_core4 = require("@nestjs/core");
1169
- function _ts_decorate9(decorators, target, key, desc) {
1170
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1171
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
1172
- 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;
1173
- return c > 3 && r && Object.defineProperty(target, key, r), r;
1174
- }
1175
- __name(_ts_decorate9, "_ts_decorate");
1176
- function _ts_metadata6(k, v) {
1177
- if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
1178
- }
1179
- __name(_ts_metadata6, "_ts_metadata");
1180
- var TenantContextInterceptor = class _TenantContextInterceptor {
1181
- static {
1182
- __name(this, "TenantContextInterceptor");
1183
- }
1184
- reflector;
1185
- tenantContext;
1186
- primaryDatabase;
1187
- requestService;
1188
- logger = new import_common13.Logger(_TenantContextInterceptor.name);
1189
- constructor(reflector, tenantContext, primaryDatabase, requestService) {
1190
- this.reflector = reflector;
1191
- this.tenantContext = tenantContext;
1192
- this.primaryDatabase = primaryDatabase;
1193
- this.requestService = requestService;
1194
- }
1195
- async intercept(context, next) {
1196
- const request = context.switchToHttp().getRequest();
1197
- this.logger.debug(`Processing request: ${request.method} ${request.url}`);
1198
- const isPublic = this.reflector.getAllAndOverride("isPublic", [
1199
- context.getHandler(),
1200
- context.getClass()
1201
- ]);
1202
- try {
1203
- const tenantIdentifier = this.requestService.getTenantIdentifier();
1204
- if (isPublic && !tenantIdentifier) {
1205
- this.logger.debug("Public endpoint without tenant identifier, skipping tenant context setup");
1206
- return next.handle();
1207
- }
1208
- if (!tenantIdentifier) {
1209
- throw new import_common13.UnauthorizedException("Tenant identifier not found in request");
1210
- }
1211
- this.logger.debug(`Tenant identifier extracted: ${tenantIdentifier}`);
1212
- if (tenantIdentifier === "cloud") {
1213
- this.logger.log("Cloud platform access detected, skipping tenant context setup");
1214
- return next.handle();
1215
- }
1216
- const tenantInfo = await this.primaryDatabase.getTenantInfo(tenantIdentifier);
1217
- if (!tenantInfo) {
1218
- this.logger.warn(`Invalid tenant: ${tenantIdentifier}`);
1219
- throw new import_common13.UnauthorizedException("Invalid tenant");
1220
- }
1221
- if (tenantInfo.status !== "ACTIVE") {
1222
- this.logger.warn(`Tenant ${tenantIdentifier} has status: ${tenantInfo.status}`);
1223
- throw new import_common13.UnauthorizedException(`Tenant is ${tenantInfo.status}`);
1224
- }
1225
- this.logger.debug(`Tenant config loaded: ${tenantInfo.subdomain} (${tenantInfo.type})`);
1226
- this.tenantContext.setTenant(tenantInfo);
1227
- request.tenant = tenantInfo;
1228
- this.logger.log(`Tenant context set: ${tenantInfo.subdomain}`);
1229
- } catch (error) {
1230
- this.logger.error("Failed to set tenant context", error);
1231
- throw error;
1232
- }
1233
- return next.handle();
1234
- }
1235
- };
1236
- TenantContextInterceptor = _ts_decorate9([
1237
- (0, import_common13.Injectable)({
1238
- scope: import_common13.Scope.REQUEST
1239
- }),
1240
- _ts_metadata6("design:type", Function),
1241
- _ts_metadata6("design:paramtypes", [
1242
- typeof import_core4.Reflector === "undefined" ? Object : import_core4.Reflector,
1243
- typeof TenantContextService === "undefined" ? Object : TenantContextService,
1244
- typeof PrimaryDatabaseService === "undefined" ? Object : PrimaryDatabaseService,
1245
- typeof RequestService === "undefined" ? Object : RequestService
1246
- ])
1247
- ], TenantContextInterceptor);
1248
-
1249
- // src/database/services/tenant-database.service.ts
1250
- var import_common14 = require("@nestjs/common");
1251
- var import_node_postgres2 = require("drizzle-orm/node-postgres");
1252
- var import_pg2 = require("pg");
1253
- function _ts_decorate10(decorators, target, key, desc) {
1254
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1255
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
1256
- 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;
1257
- return c > 3 && r && Object.defineProperty(target, key, r), r;
1258
- }
1259
- __name(_ts_decorate10, "_ts_decorate");
1260
- function _ts_metadata7(k, v) {
1261
- if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
1262
- }
1263
- __name(_ts_metadata7, "_ts_metadata");
1264
- function _ts_param3(paramIndex, decorator) {
1265
- return function(target, key) {
1266
- decorator(target, key, paramIndex);
1267
- };
1268
- }
1269
- __name(_ts_param3, "_ts_param");
1270
- var TenantDatabaseService = class _TenantDatabaseService {
1271
- static {
1272
- __name(this, "TenantDatabaseService");
1273
- }
1274
- options;
1275
- tenantContext;
1276
- logger = new import_common14.Logger(_TenantDatabaseService.name);
1277
- /** Connection pool: Map<cacheKey, TenantConnection> */
1040
+ logger = new import_common16.Logger(_TenantDatabaseService.name);
1278
1041
  clients = /* @__PURE__ */ new Map();
1279
- /** Track last usage time for idle connection cleanup */
1280
1042
  clientLastUsed = /* @__PURE__ */ new Map();
1281
- /** Cleanup interval timer */
1282
1043
  cleanupInterval;
1283
1044
  constructor(options, tenantContext) {
1284
1045
  this.options = options;
1285
1046
  this.tenantContext = tenantContext;
1286
1047
  this.startConnectionCleaner();
1287
1048
  }
1288
- /**
1289
- * Get the Drizzle client for the current tenant's database.
1290
- * This returns the tenant-scoped database client.
1291
- *
1292
- * @returns Tenant-scoped Drizzle database instance
1293
- * @throws UnauthorizedException if tenant context not set
1294
- * @throws InternalServerErrorException if connection fails
1295
- */
1049
+ // Returns the Drizzle client scoped to the current tenant's database
1296
1050
  get drizzleClient() {
1297
1051
  return this.getDbClient();
1298
1052
  }
1299
- /**
1300
- * Get the Drizzle schema
1301
- */
1053
+ // Returns the Drizzle schema passed in module options
1302
1054
  get schema() {
1303
1055
  return this.options.drizzleSchema;
1304
1056
  }
1305
- /**
1306
- * Get tenant-scoped database client for the current request/message
1307
- *
1308
- * This method:
1309
- * 1. Gets tenant info from TenantContextService
1310
- * 2. Builds a connection URL based on tenant type
1311
- * 3. Returns cached client if exists, otherwise creates new one
1312
- *
1313
- * @returns Drizzle database instance
1314
- * @throws UnauthorizedException if tenant context not set
1315
- * @throws InternalServerErrorException if connection fails
1316
- */
1057
+ // Returns a cached or new Drizzle client for the current tenant context
1317
1058
  getDbClient() {
1318
1059
  const tenant = this.tenantContext.getTenant();
1319
1060
  const cacheKey = this.buildCacheKey(tenant);
@@ -1329,9 +1070,7 @@ var TenantDatabaseService = class _TenantDatabaseService {
1329
1070
  this.clientLastUsed.set(cacheKey, Date.now());
1330
1071
  return connection.db;
1331
1072
  }
1332
- /**
1333
- * Create a new database client for the given tenant (synchronous)
1334
- */
1073
+ // Creates a new pool and Drizzle client for the given tenant
1335
1074
  createDbClientSync(tenant) {
1336
1075
  try {
1337
1076
  const databaseUrl = this.buildTenantDbUrl(tenant);
@@ -1350,12 +1089,10 @@ var TenantDatabaseService = class _TenantDatabaseService {
1350
1089
  };
1351
1090
  } catch (error) {
1352
1091
  this.logger.error(`Failed to create database connection for tenant: ${tenant.subdomain}`, error);
1353
- throw new import_common14.InternalServerErrorException("Failed to connect to tenant database");
1092
+ throw new import_common16.InternalServerErrorException("Failed to connect to tenant database");
1354
1093
  }
1355
1094
  }
1356
- /**
1357
- * Build connection URL for tenant (dedicated database)
1358
- */
1095
+ // Builds the PostgreSQL connection URL for a dedicated tenant database
1359
1096
  buildTenantDbUrl(tenant) {
1360
1097
  const { databaseHost, databasePort, databaseName, databaseUsername, databasePassword, databaseSslMode } = tenant;
1361
1098
  if (!databaseHost || !databaseName || !databaseUsername) {
@@ -1367,15 +1104,11 @@ var TenantDatabaseService = class _TenantDatabaseService {
1367
1104
  this.logger.debug(`Tenant connection URL: ${this.maskPassword(connectionUrl)}`);
1368
1105
  return connectionUrl;
1369
1106
  }
1370
- /**
1371
- * Build cache key for connection pooling
1372
- */
1107
+ // Builds a cache key for connection pooling from tenant database coordinates
1373
1108
  buildCacheKey(tenant) {
1374
1109
  return `${tenant.type}:${tenant.databaseName}@${tenant.databaseHost}`;
1375
1110
  }
1376
- /**
1377
- * Start periodic cleanup of idle connections
1378
- */
1111
+ // Starts a periodic interval to close idle database connections
1379
1112
  startConnectionCleaner() {
1380
1113
  const interval = this.options.connectionCacheTTL || 3e5;
1381
1114
  this.cleanupInterval = setInterval(() => {
@@ -1383,9 +1116,7 @@ var TenantDatabaseService = class _TenantDatabaseService {
1383
1116
  }, interval);
1384
1117
  this.logger.log(`Connection cleanup scheduled every ${interval / 1e3} seconds`);
1385
1118
  }
1386
- /**
1387
- * Clean up idle connections that haven't been used recently
1388
- */
1119
+ // Closes and removes connections that have been idle beyond the TTL
1389
1120
  async cleanupIdleConnections() {
1390
1121
  const now = Date.now();
1391
1122
  const maxIdle = this.options.connectionCacheTTL || 3e5;
@@ -1410,18 +1141,14 @@ var TenantDatabaseService = class _TenantDatabaseService {
1410
1141
  this.logger.log(`Cleaned up ${cleaned} idle connections`);
1411
1142
  }
1412
1143
  }
1413
- /**
1414
- * Get current connection pool statistics
1415
- */
1144
+ // Returns the current number of active pooled connections and their tenant keys
1416
1145
  getPoolStats() {
1417
1146
  return {
1418
1147
  activeConnections: this.clients.size,
1419
1148
  tenants: Array.from(this.clients.keys())
1420
1149
  };
1421
1150
  }
1422
- /**
1423
- * Mask password in connection URL for logging
1424
- */
1151
+ // Masks password in connection URL for safe logging
1425
1152
  maskPassword(url) {
1426
1153
  return url.replace(/:([^@]+)@/, ":****@");
1427
1154
  }
@@ -1442,87 +1169,37 @@ var TenantDatabaseService = class _TenantDatabaseService {
1442
1169
  this.logger.log("All database connections closed");
1443
1170
  }
1444
1171
  };
1445
- TenantDatabaseService = _ts_decorate10([
1446
- (0, import_common14.Injectable)(),
1447
- _ts_param3(0, (0, import_common14.Inject)(DATABASE_MODULE_OPTIONS)),
1448
- _ts_metadata7("design:type", Function),
1449
- _ts_metadata7("design:paramtypes", [
1172
+ TenantDatabaseService = _ts_decorate8([
1173
+ (0, import_common16.Injectable)(),
1174
+ _ts_param3(0, (0, import_common16.Inject)(DATABASE_MODULE_OPTIONS)),
1175
+ _ts_metadata5("design:type", Function),
1176
+ _ts_metadata5("design:paramtypes", [
1450
1177
  typeof DatabaseModuleOptions === "undefined" ? Object : DatabaseModuleOptions,
1451
1178
  typeof TenantContextService === "undefined" ? Object : TenantContextService
1452
1179
  ])
1453
1180
  ], TenantDatabaseService);
1454
1181
 
1455
1182
  // src/database/database.module.ts
1456
- function _ts_decorate11(decorators, target, key, desc) {
1183
+ function _ts_decorate9(decorators, target, key, desc) {
1457
1184
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1458
1185
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
1459
1186
  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;
1460
1187
  return c > 3 && r && Object.defineProperty(target, key, r), r;
1461
1188
  }
1462
- __name(_ts_decorate11, "_ts_decorate");
1189
+ __name(_ts_decorate9, "_ts_decorate");
1463
1190
  var DatabaseModule = class _DatabaseModule {
1464
1191
  static {
1465
1192
  __name(this, "DatabaseModule");
1466
1193
  }
1467
- /**
1468
- * Configure DatabaseModule for Gateway/HTTP mode (multi-tenant web servers)
1469
- *
1470
- * This mode is for API Gateways that handle HTTP requests:
1471
- * - Automatically registers TenantContextInterceptor
1472
- * - Extracts tenant from subdomain or x-tenant-id header
1473
- * - Queries primary database for tenant configuration
1474
- * - Provides PrimaryDatabaseService for tenant lookup
1475
- *
1476
- * @param options Async configuration options
1477
- * @returns Dynamic module configuration with HTTP interceptor
1478
- *
1479
- * @example
1480
- * DatabaseModule.forServer({
1481
- * inject: [ConfigService],
1482
- * useFactory: (config: ConfigService) => ({
1483
- * primaryDb: {
1484
- * host: config.get('PRIMARY_DB_HOST'),
1485
- * port: config.get('PRIMARY_DB_PORT'),
1486
- * username: config.get('PRIMARY_DB_USERNAME'),
1487
- * password: config.get('PRIMARY_DB_PASSWORD'),
1488
- * database: config.get('PRIMARY_DB_DATABASE'),
1489
- * },
1490
- * prismaClientConstructor: PrismaClient,
1491
- * }),
1492
- * })
1493
- */
1194
+ // Configures the module for gateway/HTTP mode with TenantContextInterceptor
1494
1195
  static forServer(options) {
1495
1196
  return _DatabaseModule.createDynamicModule(options, "server");
1496
1197
  }
1497
- /**
1498
- * Configure DatabaseModule for Microservice/Messaging mode (RabbitMQ workers)
1499
- *
1500
- * This mode is for microservices that process messages from queues:
1501
- * - Automatically registers MessageTenantContextInterceptor
1502
- * - Extracts tenant from RabbitMQ message patterns
1503
- * - No primary database needed (tenant comes from message context)
1504
- *
1505
- * @param options Async configuration options
1506
- * @returns Dynamic module configuration with message interceptor
1507
- *
1508
- * @example
1509
- * DatabaseModule.forMicroservice({
1510
- * inject: [ConfigService],
1511
- * useFactory: (config: ConfigService) => ({
1512
- * prismaClientConstructor: PrismaClient,
1513
- * }),
1514
- * })
1515
- */
1198
+ // Configures the module for microservice mode with MessageTenantContextInterceptor
1516
1199
  static forMicroservice(options) {
1517
1200
  return _DatabaseModule.createDynamicModule(options, "microservice");
1518
1201
  }
1519
- /**
1520
- * Internal helper to create dynamic module with conditional interceptor registration
1521
- *
1522
- * @param options Configuration options
1523
- * @param mode Mode of operation (gateway or microservice)
1524
- * @returns Dynamic module configuration
1525
- */
1202
+ // Creates the dynamic module configuration with the appropriate interceptor for the given mode
1526
1203
  static createDynamicModule(options, mode) {
1527
1204
  const asyncProvider = {
1528
1205
  provide: DATABASE_MODULE_OPTIONS,
@@ -1532,25 +1209,14 @@ var DatabaseModule = class _DatabaseModule {
1532
1209
  const providers = [
1533
1210
  // Required for external packages - NestJS global Reflector not available
1534
1211
  {
1535
- provide: import_core5.Reflector,
1536
- useClass: import_core5.Reflector
1212
+ provide: import_core4.Reflector,
1213
+ useClass: import_core4.Reflector
1537
1214
  },
1538
1215
  asyncProvider,
1539
1216
  TenantContextService,
1540
1217
  PrimaryDatabaseService,
1541
1218
  TenantDatabaseService
1542
1219
  ];
1543
- if (mode === "server") {
1544
- providers.push({
1545
- provide: import_core5.APP_INTERCEPTOR,
1546
- useClass: TenantContextInterceptor
1547
- });
1548
- } else {
1549
- providers.push({
1550
- provide: import_core5.APP_INTERCEPTOR,
1551
- useClass: MessageTenantContextInterceptor
1552
- });
1553
- }
1554
1220
  return {
1555
1221
  module: _DatabaseModule,
1556
1222
  imports: [
@@ -1566,14 +1232,14 @@ var DatabaseModule = class _DatabaseModule {
1566
1232
  };
1567
1233
  }
1568
1234
  };
1569
- DatabaseModule = _ts_decorate11([
1570
- (0, import_common15.Global)(),
1571
- (0, import_common15.Module)({})
1235
+ DatabaseModule = _ts_decorate9([
1236
+ (0, import_common17.Global)(),
1237
+ (0, import_common17.Module)({})
1572
1238
  ], DatabaseModule);
1573
1239
 
1574
1240
  // src/database/decorators/tenant.decorator.ts
1575
- var import_common16 = require("@nestjs/common");
1576
- var Tenant = (0, import_common16.createParamDecorator)((_data, ctx) => {
1241
+ var import_common18 = require("@nestjs/common");
1242
+ var Tenant = (0, import_common18.createParamDecorator)((_data, ctx) => {
1577
1243
  const request = ctx.switchToHttp().getRequest();
1578
1244
  const tenantContext = request.app?.get?.(TenantContextService);
1579
1245
  if (!tenantContext) {
@@ -1582,8 +1248,117 @@ var Tenant = (0, import_common16.createParamDecorator)((_data, ctx) => {
1582
1248
  return tenantContext.getTenant();
1583
1249
  });
1584
1250
 
1251
+ // src/database/dto/select-options-query.dto.ts
1252
+ var import_swagger = require("@nestjs/swagger");
1253
+ var import_class_transformer = require("class-transformer");
1254
+ var import_class_validator = require("class-validator");
1255
+ function _ts_decorate10(decorators, target, key, desc) {
1256
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1257
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
1258
+ 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;
1259
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
1260
+ }
1261
+ __name(_ts_decorate10, "_ts_decorate");
1262
+ function _ts_metadata6(k, v) {
1263
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
1264
+ }
1265
+ __name(_ts_metadata6, "_ts_metadata");
1266
+ var SelectOptionsQueryDto = class {
1267
+ static {
1268
+ __name(this, "SelectOptionsQueryDto");
1269
+ }
1270
+ search;
1271
+ limit;
1272
+ offset;
1273
+ values;
1274
+ excludeIds;
1275
+ valueKey;
1276
+ labelKey;
1277
+ groupIdKey;
1278
+ };
1279
+ _ts_decorate10([
1280
+ (0, import_swagger.ApiPropertyOptional)({
1281
+ description: "Search term to filter by label",
1282
+ example: "united"
1283
+ }),
1284
+ (0, import_class_validator.IsOptional)(),
1285
+ (0, import_class_validator.IsString)(),
1286
+ _ts_metadata6("design:type", String)
1287
+ ], SelectOptionsQueryDto.prototype, "search", void 0);
1288
+ _ts_decorate10([
1289
+ (0, import_swagger.ApiPropertyOptional)({
1290
+ description: "Maximum number of results",
1291
+ example: 20,
1292
+ default: 20
1293
+ }),
1294
+ (0, import_class_validator.IsOptional)(),
1295
+ (0, import_class_transformer.Type)(() => Number),
1296
+ (0, import_class_validator.IsInt)(),
1297
+ (0, import_class_validator.Min)(1),
1298
+ _ts_metadata6("design:type", Number)
1299
+ ], SelectOptionsQueryDto.prototype, "limit", void 0);
1300
+ _ts_decorate10([
1301
+ (0, import_swagger.ApiPropertyOptional)({
1302
+ description: "Number of results to skip",
1303
+ example: 0,
1304
+ default: 0
1305
+ }),
1306
+ (0, import_class_validator.IsOptional)(),
1307
+ (0, import_class_transformer.Type)(() => Number),
1308
+ (0, import_class_validator.IsInt)(),
1309
+ (0, import_class_validator.Min)(0),
1310
+ _ts_metadata6("design:type", Number)
1311
+ ], SelectOptionsQueryDto.prototype, "offset", void 0);
1312
+ _ts_decorate10([
1313
+ (0, import_swagger.ApiPropertyOptional)({
1314
+ description: "Comma-separated values to fetch specific options",
1315
+ example: "1,2,3"
1316
+ }),
1317
+ (0, import_class_validator.IsOptional)(),
1318
+ (0, import_class_validator.IsString)(),
1319
+ _ts_metadata6("design:type", String)
1320
+ ], SelectOptionsQueryDto.prototype, "values", void 0);
1321
+ _ts_decorate10([
1322
+ (0, import_swagger.ApiPropertyOptional)({
1323
+ description: "Comma-separated IDs to exclude from results (already selected)",
1324
+ example: "5,10"
1325
+ }),
1326
+ (0, import_class_validator.IsOptional)(),
1327
+ (0, import_class_validator.IsString)(),
1328
+ _ts_metadata6("design:type", String)
1329
+ ], SelectOptionsQueryDto.prototype, "excludeIds", void 0);
1330
+ _ts_decorate10([
1331
+ (0, import_swagger.ApiPropertyOptional)({
1332
+ description: "Column name for option value",
1333
+ example: "id",
1334
+ default: "id"
1335
+ }),
1336
+ (0, import_class_validator.IsOptional)(),
1337
+ (0, import_class_validator.IsString)(),
1338
+ _ts_metadata6("design:type", String)
1339
+ ], SelectOptionsQueryDto.prototype, "valueKey", void 0);
1340
+ _ts_decorate10([
1341
+ (0, import_swagger.ApiPropertyOptional)({
1342
+ description: "Column name for option label",
1343
+ example: "name",
1344
+ default: "name"
1345
+ }),
1346
+ (0, import_class_validator.IsOptional)(),
1347
+ (0, import_class_validator.IsString)(),
1348
+ _ts_metadata6("design:type", String)
1349
+ ], SelectOptionsQueryDto.prototype, "labelKey", void 0);
1350
+ _ts_decorate10([
1351
+ (0, import_swagger.ApiPropertyOptional)({
1352
+ description: "Column name for group ID",
1353
+ example: "regionId"
1354
+ }),
1355
+ (0, import_class_validator.IsOptional)(),
1356
+ (0, import_class_validator.IsString)(),
1357
+ _ts_metadata6("design:type", String)
1358
+ ], SelectOptionsQueryDto.prototype, "groupIdKey", void 0);
1359
+
1585
1360
  // src/database/repositories/primary-base.repository.ts
1586
- var import_common17 = require("@nestjs/common");
1361
+ var import_common19 = require("@nestjs/common");
1587
1362
  var import_drizzle_orm2 = require("drizzle-orm");
1588
1363
  function snakeToCamel(str) {
1589
1364
  return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
@@ -1596,34 +1371,10 @@ var PrimaryBaseRepository = class {
1596
1371
  database;
1597
1372
  table;
1598
1373
  logger;
1599
- /**
1600
- * The table name extracted from the Drizzle table at runtime.
1601
- * Stored in camelCase to match Drizzle's query object keys.
1602
- * Example: 'email_verifications' -> 'emailVerifications'
1603
- */
1604
1374
  tableName;
1605
- /**
1606
- * Lazy getter for Drizzle client.
1607
- * Accesses the client from the database service only when needed,
1608
- * avoiding initialization timing issues with NestJS lifecycle.
1609
- */
1610
1375
  get db() {
1611
1376
  return this.database.drizzleClient;
1612
1377
  }
1613
- /**
1614
- * Model query API for THIS repository's table (Drizzle v2 relational queries)
1615
- * Scoped to only the table this repository manages.
1616
- * Returns a type-safe wrapper around Drizzle's RelationalQueryBuilder.
1617
- *
1618
- * @example
1619
- * ```typescript
1620
- * // Use relational queries with v2 object-based where syntax
1621
- * const user = await this.model.findFirst({
1622
- * where: { id },
1623
- * with: { posts: true, profile: true }
1624
- * });
1625
- * ```
1626
- */
1627
1378
  get model() {
1628
1379
  const query = this.database.drizzleClient.query;
1629
1380
  const queryKeys = Object.keys(query || {});
@@ -1634,60 +1385,24 @@ var PrimaryBaseRepository = class {
1634
1385
  }
1635
1386
  return model;
1636
1387
  }
1637
- /**
1638
- * Create a new repository instance
1639
- *
1640
- * @param database - The primary database service
1641
- * @param table - The Drizzle table schema object
1642
- *
1643
- * @example
1644
- * ```typescript
1645
- * import { users } from '@/db/schema';
1646
- *
1647
- * constructor(database: PrimaryDatabaseService) {
1648
- * super(database, users);
1649
- * }
1650
- * ```
1651
- */
1652
1388
  constructor(database, table) {
1653
1389
  this.database = database;
1654
1390
  this.table = table;
1655
1391
  const dbTableName = (0, import_drizzle_orm2.getTableName)(table);
1656
1392
  this.tableName = snakeToCamel(dbTableName);
1657
- this.logger = new import_common17.Logger(this.constructor.name);
1393
+ this.logger = new import_common19.Logger(this.constructor.name);
1658
1394
  this.logger.debug(`Initialized ${this.constructor.name}`);
1659
1395
  this.logger.debug(`Table name: '${dbTableName}' -> query key: '${this.tableName}'`);
1660
1396
  }
1661
- /**
1662
- * Create a new record
1663
- *
1664
- * @param data - The data to create the record with
1665
- * @returns Promise resolving to the created record
1666
- *
1667
- * @example
1668
- * ```typescript
1669
- * const user = await userRepository.create({
1670
- * email: 'user@example.com',
1671
- * firstName: 'John'
1672
- * });
1673
- * ```
1674
- */
1397
+ // Creates a new record and returns it
1675
1398
  async create(data) {
1676
1399
  this.logger.log("Creating record");
1677
1400
  const results = await this.db.insert(this.table).values(data).returning();
1678
- return results[0];
1679
- }
1680
- /**
1681
- * Find a single record by ID
1682
- *
1683
- * @param id - The record ID
1684
- * @returns Promise resolving to the record or undefined if not found
1685
- *
1686
- * @example
1687
- * ```typescript
1688
- * const user = await userRepository.findById('user-id-123');
1689
- * ```
1690
- */
1401
+ const record = results[0];
1402
+ if (!record) throw new Error(`${this.tableName}: database operation returned no record`);
1403
+ return record;
1404
+ }
1405
+ // Finds a single record by primary key ID
1691
1406
  async findById(id) {
1692
1407
  this.logger.debug(`Finding record by ID: ${id}`);
1693
1408
  return this.model.findFirst({
@@ -1696,105 +1411,29 @@ var PrimaryBaseRepository = class {
1696
1411
  }
1697
1412
  });
1698
1413
  }
1699
- /**
1700
- * Find a single record with custom where clause (Drizzle v2 object-based syntax)
1701
- *
1702
- * @param where - Object-based filter condition
1703
- * @returns Promise resolving to the record or undefined if not found
1704
- *
1705
- * @example
1706
- * ```typescript
1707
- * // Simple equality
1708
- * const user = await userRepository.findOne({ email: 'user@example.com' });
1709
- *
1710
- * // With operators
1711
- * const user = await userRepository.findOne({ age: { gte: 18 } });
1712
- *
1713
- * // Multiple conditions (AND)
1714
- * const user = await userRepository.findOne({
1715
- * email: 'user@example.com',
1716
- * status: 'ACTIVE'
1717
- * });
1718
- * ```
1719
- */
1414
+ // Finds a single record matching the given where filter
1720
1415
  async findOne(where) {
1721
1416
  this.logger.debug("Finding record with custom query");
1722
1417
  return this.model.findFirst({
1723
1418
  where
1724
1419
  });
1725
1420
  }
1726
- /**
1727
- * Find multiple records (Drizzle v2 object-based syntax)
1728
- *
1729
- * @param options - Query options (where, orderBy, limit, offset)
1730
- * @returns Promise resolving to an array of records
1731
- *
1732
- * @example
1733
- * ```typescript
1734
- * // Find all users
1735
- * const users = await userRepository.findMany();
1736
- *
1737
- * // Find with filtering and pagination (v2 object syntax)
1738
- * const users = await userRepository.findMany({
1739
- * where: { accountStatus: 'ACTIVE' },
1740
- * orderBy: { createdAt: 'desc' },
1741
- * limit: 10,
1742
- * offset: 0
1743
- * });
1744
- *
1745
- * // Multiple conditions
1746
- * const users = await userRepository.findMany({
1747
- * where: {
1748
- * AND: [
1749
- * { status: 'ACTIVE' },
1750
- * { age: { gte: 18 } }
1751
- * ]
1752
- * }
1753
- * });
1754
- * ```
1755
- */
1421
+ // Finds multiple records with optional filtering, ordering, and pagination
1756
1422
  async findMany(options) {
1757
1423
  this.logger.debug("Finding multiple records");
1758
1424
  return this.model.findMany(options);
1759
1425
  }
1760
- /**
1761
- * Update a record by ID
1762
- *
1763
- * @param id - The record ID
1764
- * @param data - The data to update
1765
- * @returns Promise resolving to the updated record
1766
- *
1767
- * @example
1768
- * ```typescript
1769
- * const user = await userRepository.update('user-id-123', {
1770
- * firstName: 'Jane'
1771
- * });
1772
- * ```
1773
- */
1426
+ // Updates a record by ID and returns the updated record
1774
1427
  async update(id, data) {
1775
1428
  this.logger.log(`Updating record with ID: ${id}`);
1776
1429
  const idColumn = this.table.id;
1430
+ if (!idColumn) throw new Error(`Table '${this.tableName}' has no 'id' column`);
1777
1431
  const results = await this.db.update(this.table).set(data).where((0, import_drizzle_orm2.eq)(idColumn, id)).returning();
1778
- return results[0];
1779
- }
1780
- /**
1781
- * Update multiple records
1782
- *
1783
- * @param where - SQL condition to match records
1784
- * @param data - The data to update
1785
- * @returns Promise resolving to the count of updated records
1786
- *
1787
- * @example
1788
- * ```typescript
1789
- * import { eq } from 'drizzle-orm';
1790
- *
1791
- * const result = await userRepository.updateMany(
1792
- * eq(users.accountStatus, 'PENDING'),
1793
- * { accountStatus: 'ACTIVE' }
1794
- * );
1795
- * console.log(`Updated ${result.count} users`);
1796
- * ```
1797
- */
1432
+ const record = results[0];
1433
+ if (!record) throw new Error(`${this.tableName}: database operation returned no record`);
1434
+ return record;
1435
+ }
1436
+ // Updates all records matching the SQL condition and returns the affected count
1798
1437
  async updateMany(where, data) {
1799
1438
  this.logger.log("Updating multiple records");
1800
1439
  const result = await this.db.update(this.table).set(data).where(where);
@@ -1802,39 +1441,17 @@ var PrimaryBaseRepository = class {
1802
1441
  count: result.rowCount ?? 0
1803
1442
  };
1804
1443
  }
1805
- /**
1806
- * Delete a record by ID
1807
- *
1808
- * @param id - The record ID
1809
- * @returns Promise resolving to the deleted record
1810
- *
1811
- * @example
1812
- * ```typescript
1813
- * const user = await userRepository.delete('user-id-123');
1814
- * ```
1815
- */
1444
+ // Deletes a record by ID and returns the deleted record
1816
1445
  async delete(id) {
1817
1446
  this.logger.log(`Deleting record with ID: ${id}`);
1818
1447
  const idColumn = this.table.id;
1448
+ if (!idColumn) throw new Error(`Table '${this.tableName}' has no 'id' column`);
1819
1449
  const results = await this.db.delete(this.table).where((0, import_drizzle_orm2.eq)(idColumn, id)).returning();
1820
- return results[0];
1821
- }
1822
- /**
1823
- * Delete multiple records
1824
- *
1825
- * @param where - SQL condition to match records
1826
- * @returns Promise resolving to the count of deleted records
1827
- *
1828
- * @example
1829
- * ```typescript
1830
- * import { lt } from 'drizzle-orm';
1831
- *
1832
- * const result = await userRepository.deleteMany(
1833
- * lt(users.createdAt, new Date('2020-01-01'))
1834
- * );
1835
- * console.log(`Deleted ${result.count} users`);
1836
- * ```
1837
- */
1450
+ const record = results[0];
1451
+ if (!record) throw new Error(`${this.tableName}: database operation returned no record`);
1452
+ return record;
1453
+ }
1454
+ // Deletes all records matching the SQL condition and returns the affected count
1838
1455
  async deleteMany(where) {
1839
1456
  this.logger.log("Deleting multiple records");
1840
1457
  const result = await this.db.delete(this.table).where(where);
@@ -1842,25 +1459,7 @@ var PrimaryBaseRepository = class {
1842
1459
  count: result.rowCount ?? 0
1843
1460
  };
1844
1461
  }
1845
- /**
1846
- * Count records
1847
- *
1848
- * @param where - Optional SQL condition to filter records
1849
- * @returns Promise resolving to the count of records
1850
- *
1851
- * @example
1852
- * ```typescript
1853
- * import { eq } from 'drizzle-orm';
1854
- *
1855
- * // Count all users
1856
- * const total = await userRepository.count();
1857
- *
1858
- * // Count active users
1859
- * const activeCount = await userRepository.count(
1860
- * eq(users.accountStatus, 'ACTIVE')
1861
- * );
1862
- * ```
1863
- */
1462
+ // Counts records matching the optional SQL condition
1864
1463
  async count(where) {
1865
1464
  this.logger.debug("Counting records");
1866
1465
  let query = this.db.select({
@@ -1872,29 +1471,124 @@ var PrimaryBaseRepository = class {
1872
1471
  const results = await query;
1873
1472
  return results[0].count;
1874
1473
  }
1875
- /**
1876
- * Check if a record exists
1877
- *
1878
- * @param where - SQL condition to match records
1879
- * @returns Promise resolving to true if at least one record exists, false otherwise
1880
- *
1881
- * @example
1882
- * ```typescript
1883
- * import { eq } from 'drizzle-orm';
1884
- *
1885
- * const emailExists = await userRepository.exists(
1886
- * eq(users.email, 'user@example.com')
1887
- * );
1888
- * ```
1889
- */
1474
+ // Returns true if at least one record matches the SQL condition
1890
1475
  async exists(where) {
1891
1476
  const count = await this.count(where);
1892
1477
  return count > 0;
1893
1478
  }
1479
+ // Finds records formatted as select dropdown options with optional search, pagination, and grouping
1480
+ async findForSelect(config) {
1481
+ this.logger.debug("Finding records for select dropdown");
1482
+ const parsedValues = typeof config.values === "string" ? config.values.split(",").map((v) => v.trim()).filter(Boolean) : config.values;
1483
+ const parsedExcludeIds = typeof config.excludeIds === "string" ? config.excludeIds.split(",").map((v) => v.trim()).filter(Boolean) : config.excludeIds ?? [];
1484
+ const tableColumns = this.table;
1485
+ const valueCol = tableColumns[config.value];
1486
+ if (!valueCol) throw new Error(`Column '${config.value}' not found in table '${this.tableName}'`);
1487
+ const labelCol = tableColumns[config.label];
1488
+ if (!labelCol) throw new Error(`Column '${config.label}' not found in table '${this.tableName}'`);
1489
+ if (parsedValues && parsedValues.length > 0) {
1490
+ const selectCols = {
1491
+ value: valueCol,
1492
+ label: labelCol
1493
+ };
1494
+ if (config.groupId) {
1495
+ const groupIdCol = tableColumns[config.groupId];
1496
+ if (groupIdCol) selectCols.groupId = groupIdCol;
1497
+ }
1498
+ const rows2 = await this.db.select(selectCols).from(this.table).where((0, import_drizzle_orm2.inArray)(valueCol, parsedValues));
1499
+ return {
1500
+ options: rows2.map((row) => ({
1501
+ value: row.value,
1502
+ label: String(row.label),
1503
+ ...config.groupId && row.groupId != null ? {
1504
+ groupId: row.groupId
1505
+ } : {}
1506
+ })),
1507
+ hasMore: false,
1508
+ ...config.groups ? {
1509
+ groups: config.groups
1510
+ } : {}
1511
+ };
1512
+ }
1513
+ const selectFields = {
1514
+ value: valueCol,
1515
+ label: labelCol,
1516
+ totalCount: import_drizzle_orm2.sql`count(*) over()`.mapWith(Number)
1517
+ };
1518
+ if (config.groupId) {
1519
+ const groupIdCol = tableColumns[config.groupId];
1520
+ if (groupIdCol) selectFields.groupId = groupIdCol;
1521
+ }
1522
+ const conditions = [];
1523
+ if (config.search) {
1524
+ conditions.push((0, import_drizzle_orm2.ilike)(labelCol, `%${config.search}%`));
1525
+ }
1526
+ if (parsedExcludeIds.length > 0) {
1527
+ conditions.push((0, import_drizzle_orm2.notInArray)(valueCol, parsedExcludeIds));
1528
+ }
1529
+ if (config.where) {
1530
+ for (const [field, val] of Object.entries(config.where)) {
1531
+ const column = tableColumns[field];
1532
+ if (column) {
1533
+ conditions.push((0, import_drizzle_orm2.eq)(column, val));
1534
+ }
1535
+ }
1536
+ }
1537
+ const orderByKey = config.orderBy ? Object.keys(config.orderBy)[0] : void 0;
1538
+ const orderByCol = orderByKey ? tableColumns[orderByKey] ?? labelCol : labelCol;
1539
+ const limit = Number(config.limit) || 20;
1540
+ const offset = Number(config.offset) || 0;
1541
+ let query = this.db.select(selectFields).from(this.table).$dynamic();
1542
+ if (conditions.length > 0) {
1543
+ query = query.where(conditions.length === 1 ? conditions[0] : (0, import_drizzle_orm2.and)(...conditions));
1544
+ }
1545
+ const orderClauses = [];
1546
+ if (config.groupId) {
1547
+ const groupIdCol = tableColumns[config.groupId];
1548
+ if (groupIdCol) orderClauses.push((0, import_drizzle_orm2.asc)(groupIdCol));
1549
+ }
1550
+ orderClauses.push((0, import_drizzle_orm2.asc)(orderByCol));
1551
+ query = query.orderBy(...orderClauses).limit(limit).offset(offset);
1552
+ const rows = await query;
1553
+ const totalCount = rows.length > 0 ? rows[0].totalCount : 0;
1554
+ const options = rows.map((row) => ({
1555
+ value: row.value,
1556
+ label: String(row.label),
1557
+ ...config.groupId && row.groupId != null ? {
1558
+ groupId: row.groupId
1559
+ } : {}
1560
+ }));
1561
+ let resolvedGroups = config.groups;
1562
+ if (config.groupTable && config.groupId) {
1563
+ const groupTableColumns = config.groupTable;
1564
+ const groupIdKey = config.groupIdKey ?? "id";
1565
+ const groupNameKey = config.groupLabelKey ?? "name";
1566
+ const groupIdCol = groupTableColumns[groupIdKey];
1567
+ if (!groupIdCol) throw new Error(`Column '${groupIdKey}' not found in group table`);
1568
+ const groupNameCol = groupTableColumns[groupNameKey];
1569
+ if (!groupNameCol) throw new Error(`Column '${groupNameKey}' not found in group table`);
1570
+ const groupRows = await this.db.select({
1571
+ id: groupIdCol,
1572
+ name: groupNameCol
1573
+ }).from(config.groupTable).orderBy((0, import_drizzle_orm2.asc)(groupNameCol));
1574
+ resolvedGroups = groupRows.map((r) => ({
1575
+ id: r.id,
1576
+ name: String(r.name)
1577
+ }));
1578
+ }
1579
+ return {
1580
+ options,
1581
+ hasMore: offset + limit < totalCount,
1582
+ totalCount,
1583
+ ...resolvedGroups ? {
1584
+ groups: resolvedGroups
1585
+ } : {}
1586
+ };
1587
+ }
1894
1588
  };
1895
1589
 
1896
1590
  // src/database/repositories/tenant-base.repository.ts
1897
- var import_common18 = require("@nestjs/common");
1591
+ var import_common20 = require("@nestjs/common");
1898
1592
  var import_drizzle_orm3 = require("drizzle-orm");
1899
1593
  var TenantBaseRepository = class {
1900
1594
  static {
@@ -1903,133 +1597,43 @@ var TenantBaseRepository = class {
1903
1597
  database;
1904
1598
  table;
1905
1599
  logger;
1906
- /**
1907
- * The table name extracted from the Drizzle table at runtime.
1908
- * Used to access the query API for this repository's table.
1909
- */
1910
1600
  tableName;
1911
- /**
1912
- * Lazy getter for Drizzle client.
1913
- * Accesses the client from the database service only when needed,
1914
- * avoiding initialization timing issues with NestJS lifecycle.
1915
- */
1916
1601
  get db() {
1917
1602
  return this.database.drizzleClient;
1918
1603
  }
1919
- /**
1920
- * Model query API for THIS repository's table (Prisma-like syntax)
1921
- * Scoped to only the table this repository manages
1922
- *
1923
- * @example
1924
- * ```typescript
1925
- * // Use relational queries with type safety
1926
- * const product = await this.model.findFirst({
1927
- * where: eq(products.id, id),
1928
- * with: { category: true, variants: true }
1929
- * });
1930
- * ```
1931
- */
1932
1604
  get model() {
1933
1605
  return this.database.drizzleClient.query[this.tableName];
1934
1606
  }
1935
- /**
1936
- * Create a new repository instance
1937
- *
1938
- * @param database - The tenant database service
1939
- * @param table - The Drizzle table schema object
1940
- *
1941
- * @example
1942
- * ```typescript
1943
- * import { products } from '@/db/schema';
1944
- *
1945
- * constructor(database: TenantDatabaseService) {
1946
- * super(database, products);
1947
- * }
1948
- * ```
1949
- */
1950
1607
  constructor(database, table) {
1951
1608
  this.database = database;
1952
1609
  this.table = table;
1953
1610
  this.tableName = (0, import_drizzle_orm3.getTableName)(table);
1954
- this.logger = new import_common18.Logger(this.constructor.name);
1611
+ this.logger = new import_common20.Logger(this.constructor.name);
1955
1612
  this.logger.debug(`Initialized ${this.constructor.name}`);
1956
1613
  }
1957
- /**
1958
- * Create a new record
1959
- *
1960
- * @param data - The data to create the record with
1961
- * @returns Promise resolving to the created record
1962
- *
1963
- * @example
1964
- * ```typescript
1965
- * const product = await productRepository.create({
1966
- * name: 'Widget',
1967
- * sku: 'WDG-001',
1968
- * price: 9.99
1969
- * });
1970
- * ```
1971
- */
1614
+ // Creates a new record and returns it
1972
1615
  async create(data) {
1973
1616
  this.logger.log("Creating record");
1974
1617
  const results = await this.db.insert(this.table).values(data).returning();
1975
- return results[0];
1976
- }
1977
- /**
1978
- * Find a single record by ID
1979
- *
1980
- * @param id - The record ID
1981
- * @returns Promise resolving to the record or null if not found
1982
- *
1983
- * @example
1984
- * ```typescript
1985
- * const product = await productRepository.findById('product-id-123');
1986
- * ```
1987
- */
1618
+ const record = results[0];
1619
+ if (!record) throw new Error(`${this.tableName}: database operation returned no record`);
1620
+ return record;
1621
+ }
1622
+ // Finds a single record by primary key ID
1988
1623
  async findById(id) {
1989
1624
  this.logger.debug(`Finding record by ID: ${id}`);
1990
1625
  const idColumn = this.table.id;
1626
+ if (!idColumn) throw new Error(`Table '${this.tableName}' has no 'id' column`);
1991
1627
  const results = await this.db.select().from(this.table).where((0, import_drizzle_orm3.eq)(idColumn, id)).limit(1);
1992
1628
  return results[0] ?? null;
1993
1629
  }
1994
- /**
1995
- * Find a single record with custom where clause
1996
- *
1997
- * @param where - SQL condition
1998
- * @returns Promise resolving to the record or null if not found
1999
- *
2000
- * @example
2001
- * ```typescript
2002
- * import { eq } from 'drizzle-orm';
2003
- * const product = await productRepository.findOne(eq(products.sku, 'WDG-001'));
2004
- * ```
2005
- */
1630
+ // Finds a single record matching the given SQL condition
2006
1631
  async findOne(where) {
2007
1632
  this.logger.debug("Finding record with custom query");
2008
1633
  const results = await this.db.select().from(this.table).where(where).limit(1);
2009
1634
  return results[0] ?? null;
2010
1635
  }
2011
- /**
2012
- * Find multiple records
2013
- *
2014
- * @param options - Query options (where, orderBy, limit, offset)
2015
- * @returns Promise resolving to an array of records
2016
- *
2017
- * @example
2018
- * ```typescript
2019
- * import { eq, desc } from 'drizzle-orm';
2020
- *
2021
- * // Find all products
2022
- * const products = await productRepository.findMany();
2023
- *
2024
- * // Find with filtering and pagination
2025
- * const products = await productRepository.findMany({
2026
- * where: eq(products.status, 'ACTIVE'),
2027
- * orderBy: desc(products.createdAt),
2028
- * limit: 10,
2029
- * offset: 0
2030
- * });
2031
- * ```
2032
- */
1636
+ // Finds multiple records with optional SQL filtering, ordering, and pagination
2033
1637
  async findMany(options) {
2034
1638
  this.logger.debug("Finding multiple records");
2035
1639
  let query = this.db.select().from(this.table).$dynamic();
@@ -2047,44 +1651,17 @@ var TenantBaseRepository = class {
2047
1651
  }
2048
1652
  return await query;
2049
1653
  }
2050
- /**
2051
- * Update a record by ID
2052
- *
2053
- * @param id - The record ID
2054
- * @param data - The data to update
2055
- * @returns Promise resolving to the updated record
2056
- *
2057
- * @example
2058
- * ```typescript
2059
- * const product = await productRepository.update('product-id-123', {
2060
- * price: 12.99
2061
- * });
2062
- * ```
2063
- */
1654
+ // Updates a record by ID and returns the updated record
2064
1655
  async update(id, data) {
2065
1656
  this.logger.log(`Updating record with ID: ${id}`);
2066
1657
  const idColumn = this.table.id;
1658
+ if (!idColumn) throw new Error(`Table '${this.tableName}' has no 'id' column`);
2067
1659
  const results = await this.db.update(this.table).set(data).where((0, import_drizzle_orm3.eq)(idColumn, id)).returning();
2068
- return results[0];
2069
- }
2070
- /**
2071
- * Update multiple records
2072
- *
2073
- * @param where - SQL condition to match records
2074
- * @param data - The data to update
2075
- * @returns Promise resolving to the count of updated records
2076
- *
2077
- * @example
2078
- * ```typescript
2079
- * import { eq } from 'drizzle-orm';
2080
- *
2081
- * const result = await productRepository.updateMany(
2082
- * eq(products.status, 'PENDING'),
2083
- * { status: 'ACTIVE' }
2084
- * );
2085
- * console.log(`Updated ${result.count} products`);
2086
- * ```
2087
- */
1660
+ const record = results[0];
1661
+ if (!record) throw new Error(`${this.tableName}: database operation returned no record`);
1662
+ return record;
1663
+ }
1664
+ // Updates all records matching the SQL condition and returns the affected count
2088
1665
  async updateMany(where, data) {
2089
1666
  this.logger.log("Updating multiple records");
2090
1667
  const result = await this.db.update(this.table).set(data).where(where);
@@ -2092,39 +1669,17 @@ var TenantBaseRepository = class {
2092
1669
  count: result.rowCount ?? 0
2093
1670
  };
2094
1671
  }
2095
- /**
2096
- * Delete a record by ID
2097
- *
2098
- * @param id - The record ID
2099
- * @returns Promise resolving to the deleted record
2100
- *
2101
- * @example
2102
- * ```typescript
2103
- * const product = await productRepository.delete('product-id-123');
2104
- * ```
2105
- */
1672
+ // Deletes a record by ID and returns the deleted record
2106
1673
  async delete(id) {
2107
1674
  this.logger.log(`Deleting record with ID: ${id}`);
2108
1675
  const idColumn = this.table.id;
1676
+ if (!idColumn) throw new Error(`Table '${this.tableName}' has no 'id' column`);
2109
1677
  const results = await this.db.delete(this.table).where((0, import_drizzle_orm3.eq)(idColumn, id)).returning();
2110
- return results[0];
2111
- }
2112
- /**
2113
- * Delete multiple records
2114
- *
2115
- * @param where - SQL condition to match records
2116
- * @returns Promise resolving to the count of deleted records
2117
- *
2118
- * @example
2119
- * ```typescript
2120
- * import { lt } from 'drizzle-orm';
2121
- *
2122
- * const result = await productRepository.deleteMany(
2123
- * lt(products.createdAt, new Date('2020-01-01'))
2124
- * );
2125
- * console.log(`Deleted ${result.count} products`);
2126
- * ```
2127
- */
1678
+ const record = results[0];
1679
+ if (!record) throw new Error(`${this.tableName}: database operation returned no record`);
1680
+ return record;
1681
+ }
1682
+ // Deletes all records matching the SQL condition and returns the affected count
2128
1683
  async deleteMany(where) {
2129
1684
  this.logger.log("Deleting multiple records");
2130
1685
  const result = await this.db.delete(this.table).where(where);
@@ -2132,25 +1687,7 @@ var TenantBaseRepository = class {
2132
1687
  count: result.rowCount ?? 0
2133
1688
  };
2134
1689
  }
2135
- /**
2136
- * Count records
2137
- *
2138
- * @param where - Optional SQL condition to filter records
2139
- * @returns Promise resolving to the count of records
2140
- *
2141
- * @example
2142
- * ```typescript
2143
- * import { eq } from 'drizzle-orm';
2144
- *
2145
- * // Count all products
2146
- * const total = await productRepository.count();
2147
- *
2148
- * // Count active products
2149
- * const activeCount = await productRepository.count(
2150
- * eq(products.status, 'ACTIVE')
2151
- * );
2152
- * ```
2153
- */
1690
+ // Counts records matching the optional SQL condition
2154
1691
  async count(where) {
2155
1692
  this.logger.debug("Counting records");
2156
1693
  let query = this.db.select({
@@ -2162,257 +1699,909 @@ var TenantBaseRepository = class {
2162
1699
  const results = await query;
2163
1700
  return results[0].count;
2164
1701
  }
2165
- /**
2166
- * Check if a record exists
2167
- *
2168
- * @param where - SQL condition to match records
2169
- * @returns Promise resolving to true if at least one record exists, false otherwise
2170
- *
2171
- * @example
2172
- * ```typescript
2173
- * import { eq } from 'drizzle-orm';
2174
- *
2175
- * const skuExists = await productRepository.exists(
2176
- * eq(products.sku, 'WDG-001')
2177
- * );
2178
- * ```
2179
- */
1702
+ // Returns true if at least one record matches the SQL condition
2180
1703
  async exists(where) {
2181
1704
  const count = await this.count(where);
2182
1705
  return count > 0;
2183
1706
  }
2184
- };
2185
-
2186
- // src/exceptions/bad-gateway.exception.ts
2187
- var import_common20 = require("@nestjs/common");
2188
-
2189
- // src/exceptions/base-field.exception.ts
2190
- var import_common19 = require("@nestjs/common");
2191
- var HttpProblemException = class extends import_common19.HttpException {
2192
- static {
2193
- __name(this, "HttpProblemException");
2194
- }
2195
- constructor(detailOrOptions, httpStatus) {
2196
- const options = typeof detailOrOptions === "string" ? {
2197
- detail: detailOrOptions
2198
- } : detailOrOptions;
2199
- super({
2200
- type: options.type ?? "about:blank",
2201
- label: options.label,
2202
- detail: options.detail,
2203
- errors: options.errors ?? []
2204
- }, httpStatus);
1707
+ // Finds records formatted as select dropdown options with optional search, pagination, and grouping
1708
+ async findForSelect(config) {
1709
+ this.logger.debug("Finding records for select dropdown");
1710
+ const parsedValues = typeof config.values === "string" ? config.values.split(",").map((v) => v.trim()).filter(Boolean) : config.values;
1711
+ const parsedExcludeIds = typeof config.excludeIds === "string" ? config.excludeIds.split(",").map((v) => v.trim()).filter(Boolean) : config.excludeIds ?? [];
1712
+ const tableColumns = this.table;
1713
+ const valueCol = tableColumns[config.value];
1714
+ if (!valueCol) throw new Error(`Column '${config.value}' not found in table '${this.tableName}'`);
1715
+ const labelCol = tableColumns[config.label];
1716
+ if (!labelCol) throw new Error(`Column '${config.label}' not found in table '${this.tableName}'`);
1717
+ if (parsedValues && parsedValues.length > 0) {
1718
+ const selectCols = {
1719
+ value: valueCol,
1720
+ label: labelCol
1721
+ };
1722
+ if (config.groupId) {
1723
+ const groupIdCol = tableColumns[config.groupId];
1724
+ if (groupIdCol) selectCols.groupId = groupIdCol;
1725
+ }
1726
+ const rows2 = await this.db.select(selectCols).from(this.table).where((0, import_drizzle_orm3.inArray)(valueCol, parsedValues));
1727
+ return {
1728
+ options: rows2.map((row) => ({
1729
+ value: row.value,
1730
+ label: String(row.label),
1731
+ ...config.groupId && row.groupId != null ? {
1732
+ groupId: row.groupId
1733
+ } : {}
1734
+ })),
1735
+ hasMore: false,
1736
+ ...config.groups ? {
1737
+ groups: config.groups
1738
+ } : {}
1739
+ };
1740
+ }
1741
+ const selectFields = {
1742
+ value: valueCol,
1743
+ label: labelCol,
1744
+ totalCount: import_drizzle_orm3.sql`count(*) over()`.mapWith(Number)
1745
+ };
1746
+ if (config.groupId) {
1747
+ const groupIdCol = tableColumns[config.groupId];
1748
+ if (groupIdCol) selectFields.groupId = groupIdCol;
1749
+ }
1750
+ const conditions = [];
1751
+ if (config.search) {
1752
+ conditions.push((0, import_drizzle_orm3.ilike)(labelCol, `%${config.search}%`));
1753
+ }
1754
+ if (parsedExcludeIds.length > 0) {
1755
+ conditions.push((0, import_drizzle_orm3.notInArray)(valueCol, parsedExcludeIds));
1756
+ }
1757
+ if (config.where) {
1758
+ for (const [field, val] of Object.entries(config.where)) {
1759
+ const column = tableColumns[field];
1760
+ if (column) {
1761
+ conditions.push((0, import_drizzle_orm3.eq)(column, val));
1762
+ }
1763
+ }
1764
+ }
1765
+ const orderByKey = config.orderBy ? Object.keys(config.orderBy)[0] : void 0;
1766
+ const orderByCol = orderByKey ? tableColumns[orderByKey] ?? labelCol : labelCol;
1767
+ const limit = Number(config.limit) || 20;
1768
+ const offset = Number(config.offset) || 0;
1769
+ let query = this.db.select(selectFields).from(this.table).$dynamic();
1770
+ if (conditions.length > 0) {
1771
+ query = query.where(conditions.length === 1 ? conditions[0] : (0, import_drizzle_orm3.and)(...conditions));
1772
+ }
1773
+ const orderClauses = [];
1774
+ if (config.groupId) {
1775
+ const groupIdCol = tableColumns[config.groupId];
1776
+ if (groupIdCol) orderClauses.push((0, import_drizzle_orm3.asc)(groupIdCol));
1777
+ }
1778
+ orderClauses.push((0, import_drizzle_orm3.asc)(orderByCol));
1779
+ query = query.orderBy(...orderClauses).limit(limit).offset(offset);
1780
+ const rows = await query;
1781
+ const totalCount = rows.length > 0 ? rows[0].totalCount : 0;
1782
+ const options = rows.map((row) => ({
1783
+ value: row.value,
1784
+ label: String(row.label),
1785
+ ...config.groupId && row.groupId != null ? {
1786
+ groupId: row.groupId
1787
+ } : {}
1788
+ }));
1789
+ let resolvedGroups = config.groups;
1790
+ if (config.groupTable && config.groupId) {
1791
+ const groupTableColumns = config.groupTable;
1792
+ const groupIdKey = config.groupIdKey ?? "id";
1793
+ const groupNameKey = config.groupLabelKey ?? "name";
1794
+ const groupIdCol = groupTableColumns[groupIdKey];
1795
+ if (!groupIdCol) throw new Error(`Column '${groupIdKey}' not found in group table`);
1796
+ const groupNameCol = groupTableColumns[groupNameKey];
1797
+ if (!groupNameCol) throw new Error(`Column '${groupNameKey}' not found in group table`);
1798
+ const groupRows = await this.db.select({
1799
+ id: groupIdCol,
1800
+ name: groupNameCol
1801
+ }).from(config.groupTable).orderBy((0, import_drizzle_orm3.asc)(groupNameCol));
1802
+ resolvedGroups = groupRows.map((r) => ({
1803
+ id: r.id,
1804
+ name: String(r.name)
1805
+ }));
1806
+ }
1807
+ return {
1808
+ options,
1809
+ hasMore: offset + limit < totalCount,
1810
+ totalCount,
1811
+ ...resolvedGroups ? {
1812
+ groups: resolvedGroups
1813
+ } : {}
1814
+ };
2205
1815
  }
2206
1816
  };
2207
1817
 
2208
- // src/exceptions/bad-gateway.exception.ts
2209
- var BadGatewayException = class extends HttpProblemException {
2210
- static {
2211
- __name(this, "BadGatewayException");
2212
- }
2213
- constructor(detailOrOptions) {
2214
- super(detailOrOptions ?? "Bad Gateway", import_common20.HttpStatus.BAD_GATEWAY);
2215
- }
2216
- };
1818
+ // src/email/email.module.ts
1819
+ var import_common22 = require("@nestjs/common");
1820
+ var import_config7 = require("@nestjs/config");
2217
1821
 
2218
- // src/exceptions/bad-request.exception.ts
1822
+ // src/email/email.service.ts
1823
+ var import_brevo = require("@getbrevo/brevo");
2219
1824
  var import_common21 = require("@nestjs/common");
2220
- var BadRequestException = class extends HttpProblemException {
1825
+ var import_config6 = require("@nestjs/config");
1826
+ function _ts_decorate11(decorators, target, key, desc) {
1827
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1828
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
1829
+ 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;
1830
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
1831
+ }
1832
+ __name(_ts_decorate11, "_ts_decorate");
1833
+ function _ts_metadata7(k, v) {
1834
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
1835
+ }
1836
+ __name(_ts_metadata7, "_ts_metadata");
1837
+ var EmailService = class _EmailService {
2221
1838
  static {
2222
- __name(this, "BadRequestException");
2223
- }
2224
- constructor(detailOrOptions) {
2225
- super(detailOrOptions ?? "Bad Request", import_common21.HttpStatus.BAD_REQUEST);
2226
- }
2227
- };
1839
+ __name(this, "EmailService");
1840
+ }
1841
+ configService;
1842
+ logger = new import_common21.Logger(_EmailService.name);
1843
+ brevoClient;
1844
+ senderEmail;
1845
+ senderName;
1846
+ constructor(configService) {
1847
+ this.configService = configService;
1848
+ const apiKey = this.configService.get("BREVO_API_KEY");
1849
+ if (!apiKey) {
1850
+ this.logger.error("BREVO_API_KEY is not configured. Email sending will fail.");
1851
+ throw new Error("Email service configuration error: Missing BREVO_API_KEY");
1852
+ }
1853
+ this.brevoClient = new import_brevo.BrevoClient({
1854
+ apiKey,
1855
+ maxRetries: 3
1856
+ });
1857
+ const senderEmail = this.configService.get("SENDER_EMAIL");
1858
+ const senderName = this.configService.get("SENDER_NAME");
1859
+ if (!senderEmail || !senderName) {
1860
+ this.logger.error("Sender email or name is not configured.");
1861
+ throw new Error("Email service configuration error: Missing SENDER_EMAIL or SENDER_NAME");
1862
+ }
1863
+ this.senderEmail = senderEmail;
1864
+ this.senderName = senderName;
1865
+ this.logger.log("Brevo email service initialized successfully");
1866
+ }
1867
+ // Sends an email verification OTP to the given recipient
1868
+ async sendVerificationEmail(email, otp, expiresAt, displayName) {
1869
+ const name = displayName || "there";
1870
+ const expiryMinutes = Math.ceil((expiresAt.getTime() - Date.now()) / 6e4);
1871
+ const subject = "Verify Your Email - Vritti AI Cloud";
1872
+ const htmlContent = `
1873
+ <!DOCTYPE html>
1874
+ <html>
1875
+ <head>
1876
+ <meta charset="UTF-8">
1877
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
1878
+ </head>
1879
+ <body style="margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; background-color: #f5f5f5;">
1880
+ <table role="presentation" style="width: 100%; border-collapse: collapse;">
1881
+ <tr>
1882
+ <td style="padding: 40px 20px;">
1883
+ <table role="presentation" style="max-width: 600px; margin: 0 auto; background-color: #ffffff; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
1884
+ <!-- Header -->
1885
+ <tr>
1886
+ <td style="padding: 40px 40px 20px; text-align: center; border-bottom: 1px solid #e0e0e0;">
1887
+ <h1 style="margin: 0; color: #1a1a1a; font-size: 24px; font-weight: 600;">Email Verification</h1>
1888
+ </td>
1889
+ </tr>
2228
1890
 
2229
- // src/exceptions/conflict.exception.ts
2230
- var import_common22 = require("@nestjs/common");
2231
- var ConflictException = class extends HttpProblemException {
2232
- static {
2233
- __name(this, "ConflictException");
2234
- }
2235
- constructor(detailOrOptions) {
2236
- super(detailOrOptions ?? "Conflict", import_common22.HttpStatus.CONFLICT);
2237
- }
2238
- };
1891
+ <!-- Content -->
1892
+ <tr>
1893
+ <td style="padding: 40px;">
1894
+ <p style="margin: 0 0 20px; color: #333333; font-size: 16px; line-height: 1.6;">
1895
+ Hello <strong>${name}</strong>,
1896
+ </p>
1897
+ <p style="margin: 0 0 30px; color: #333333; font-size: 16px; line-height: 1.6;">
1898
+ Thank you for signing up with Vritti AI Cloud. Please use the following verification code to complete your registration:
1899
+ </p>
2239
1900
 
2240
- // src/exceptions/forbidden.exception.ts
2241
- var import_common23 = require("@nestjs/common");
2242
- var ForbiddenException2 = class extends HttpProblemException {
2243
- static {
2244
- __name(this, "ForbiddenException");
2245
- }
2246
- constructor(detailOrOptions) {
2247
- super(detailOrOptions ?? "Forbidden", import_common23.HttpStatus.FORBIDDEN);
2248
- }
2249
- };
1901
+ <!-- OTP Box -->
1902
+ <div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 30px; border-radius: 8px; text-align: center; margin: 30px 0;">
1903
+ <div style="color: #ffffff; font-size: 36px; font-weight: bold; letter-spacing: 10px; font-family: 'Courier New', monospace;">
1904
+ ${otp}
1905
+ </div>
1906
+ </div>
2250
1907
 
2251
- // src/exceptions/gone.exception.ts
2252
- var import_common24 = require("@nestjs/common");
2253
- var GoneException = class extends HttpProblemException {
2254
- static {
2255
- __name(this, "GoneException");
2256
- }
2257
- constructor(detailOrOptions) {
2258
- super(detailOrOptions ?? "Gone", import_common24.HttpStatus.GONE);
2259
- }
2260
- };
1908
+ <p style="margin: 30px 0 20px; color: #666666; font-size: 14px; line-height: 1.6;">
1909
+ <strong>Important:</strong> This code will expire in <strong>${expiryMinutes} minute${expiryMinutes === 1 ? "" : "s"}</strong>.
1910
+ </p>
1911
+ <p style="margin: 0; color: #666666; font-size: 14px; line-height: 1.6;">
1912
+ If you didn't request this verification, please ignore this email.
1913
+ </p>
1914
+ </td>
1915
+ </tr>
2261
1916
 
2262
- // src/exceptions/internal-server-error.exception.ts
2263
- var import_common25 = require("@nestjs/common");
2264
- var InternalServerErrorException3 = class extends HttpProblemException {
2265
- static {
2266
- __name(this, "InternalServerErrorException");
2267
- }
2268
- constructor(detailOrOptions) {
2269
- super(detailOrOptions ?? "Internal Server Error", import_common25.HttpStatus.INTERNAL_SERVER_ERROR);
2270
- }
2271
- };
1917
+ <!-- Footer -->
1918
+ <tr>
1919
+ <td style="padding: 30px 40px; border-top: 1px solid #e0e0e0; text-align: center;">
1920
+ <p style="margin: 0; color: #999999; font-size: 12px; line-height: 1.5;">
1921
+ Vritti AI Cloud - Cloud Management Platform
1922
+ </p>
1923
+ <p style="margin: 8px 0 0; color: #999999; font-size: 12px; line-height: 1.5;">
1924
+ This is an automated message, please do not reply.
1925
+ </p>
1926
+ </td>
1927
+ </tr>
1928
+ </table>
1929
+ </td>
1930
+ </tr>
1931
+ </table>
1932
+ </body>
1933
+ </html>
1934
+ `;
1935
+ const textContent = `
1936
+ Hello ${name},
2272
1937
 
2273
- // src/exceptions/method-not-allowed.exception.ts
2274
- var import_common26 = require("@nestjs/common");
2275
- var MethodNotAllowedException = class extends HttpProblemException {
2276
- static {
2277
- __name(this, "MethodNotAllowedException");
2278
- }
2279
- constructor(detailOrOptions) {
2280
- super(detailOrOptions ?? "Method Not Allowed", import_common26.HttpStatus.METHOD_NOT_ALLOWED);
2281
- }
1938
+ Thank you for signing up with Vritti AI Cloud. Please use the following verification code to complete your registration:
1939
+
1940
+ Verification Code: ${otp}
1941
+
1942
+ This code will expire in ${expiryMinutes} minute${expiryMinutes === 1 ? "" : "s"}.
1943
+
1944
+ If you didn't request this verification, please ignore this email.
1945
+
1946
+ ---
1947
+ Vritti AI Cloud - Cloud Management Platform
1948
+ This is an automated message, please do not reply.
1949
+ `.trim();
1950
+ await this.sendEmail({
1951
+ to: [
1952
+ {
1953
+ email,
1954
+ name
1955
+ }
1956
+ ],
1957
+ subject,
1958
+ htmlContent,
1959
+ textContent
1960
+ });
1961
+ this.logger.log(`Verification email sent to ${email}`);
1962
+ }
1963
+ // Sends a password reset OTP to the given recipient
1964
+ async sendPasswordResetEmail(email, otp, expiresAt, displayName) {
1965
+ const name = displayName || "there";
1966
+ const expiryMinutes = Math.ceil((expiresAt.getTime() - Date.now()) / 6e4);
1967
+ const subject = "Reset Your Password - Vritti AI Cloud";
1968
+ const htmlContent = `
1969
+ <!DOCTYPE html>
1970
+ <html>
1971
+ <head>
1972
+ <meta charset="UTF-8">
1973
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
1974
+ </head>
1975
+ <body style="margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; background-color: #f5f5f5;">
1976
+ <table role="presentation" style="width: 100%; border-collapse: collapse;">
1977
+ <tr>
1978
+ <td style="padding: 40px 20px;">
1979
+ <table role="presentation" style="max-width: 600px; margin: 0 auto; background-color: #ffffff; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
1980
+ <!-- Header -->
1981
+ <tr>
1982
+ <td style="padding: 40px 40px 20px; text-align: center; border-bottom: 1px solid #e0e0e0;">
1983
+ <h1 style="margin: 0; color: #1a1a1a; font-size: 24px; font-weight: 600;">Password Reset</h1>
1984
+ </td>
1985
+ </tr>
1986
+
1987
+ <!-- Content -->
1988
+ <tr>
1989
+ <td style="padding: 40px;">
1990
+ <p style="margin: 0 0 20px; color: #333333; font-size: 16px; line-height: 1.6;">
1991
+ Hello <strong>${name}</strong>,
1992
+ </p>
1993
+ <p style="margin: 0 0 30px; color: #333333; font-size: 16px; line-height: 1.6;">
1994
+ We received a request to reset your password. Use the following code to complete the process:
1995
+ </p>
1996
+
1997
+ <!-- OTP Box -->
1998
+ <div style="background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); padding: 30px; border-radius: 8px; text-align: center; margin: 30px 0;">
1999
+ <div style="color: #ffffff; font-size: 36px; font-weight: bold; letter-spacing: 10px; font-family: 'Courier New', monospace;">
2000
+ ${otp}
2001
+ </div>
2002
+ </div>
2003
+
2004
+ <p style="margin: 30px 0 20px; color: #666666; font-size: 14px; line-height: 1.6;">
2005
+ <strong>Important:</strong> This code will expire in <strong>${expiryMinutes} minute${expiryMinutes === 1 ? "" : "s"}</strong>.
2006
+ </p>
2007
+ <p style="margin: 0 0 20px; color: #666666; font-size: 14px; line-height: 1.6;">
2008
+ If you didn't request a password reset, please ignore this email and your password will remain unchanged.
2009
+ </p>
2010
+ <div style="background-color: #fff3cd; border-left: 4px solid #ffc107; padding: 15px; margin-top: 20px; border-radius: 4px;">
2011
+ <p style="margin: 0; color: #856404; font-size: 13px; line-height: 1.5;">
2012
+ <strong>Security Tip:</strong> Never share this code with anyone. Vritti will never ask for your verification code.
2013
+ </p>
2014
+ </div>
2015
+ </td>
2016
+ </tr>
2017
+
2018
+ <!-- Footer -->
2019
+ <tr>
2020
+ <td style="padding: 30px 40px; border-top: 1px solid #e0e0e0; text-align: center;">
2021
+ <p style="margin: 0; color: #999999; font-size: 12px; line-height: 1.5;">
2022
+ Vritti AI Cloud - Cloud Management Platform
2023
+ </p>
2024
+ <p style="margin: 8px 0 0; color: #999999; font-size: 12px; line-height: 1.5;">
2025
+ This is an automated message, please do not reply.
2026
+ </p>
2027
+ </td>
2028
+ </tr>
2029
+ </table>
2030
+ </td>
2031
+ </tr>
2032
+ </table>
2033
+ </body>
2034
+ </html>
2035
+ `;
2036
+ const textContent = `
2037
+ Hello ${name},
2038
+
2039
+ We received a request to reset your password. Use the following code to complete the process:
2040
+
2041
+ Reset Code: ${otp}
2042
+
2043
+ This code will expire in ${expiryMinutes} minute${expiryMinutes === 1 ? "" : "s"}.
2044
+
2045
+ If you didn't request a password reset, please ignore this email and your password will remain unchanged.
2046
+
2047
+ SECURITY TIP: Never share this code with anyone. Vritti will never ask for your verification code.
2048
+
2049
+ ---
2050
+ Vritti AI Cloud - Cloud Management Platform
2051
+ This is an automated message, please do not reply.
2052
+ `.trim();
2053
+ await this.sendEmail({
2054
+ to: [
2055
+ {
2056
+ email,
2057
+ name
2058
+ }
2059
+ ],
2060
+ subject,
2061
+ htmlContent,
2062
+ textContent
2063
+ });
2064
+ this.logger.log(`Password reset email sent to ${email}`);
2065
+ }
2066
+ // Sends an email change notification to the old address with a revert link
2067
+ async sendEmailChangeNotification(oldEmail, newEmail, revertToken, revertExpiresAt, displayName) {
2068
+ const name = displayName || "there";
2069
+ const subject = "Your Email Address Has Been Changed - Vritti AI Cloud";
2070
+ const hoursUntilExpiry = Math.floor((revertExpiresAt.getTime() - Date.now()) / (1e3 * 60 * 60));
2071
+ const revertLink = `https://local.vrittiai.com:3012/settings/profile/revert-email?token=${revertToken}`;
2072
+ const htmlContent = `
2073
+ <!DOCTYPE html>
2074
+ <html>
2075
+ <head>
2076
+ <meta charset="UTF-8">
2077
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
2078
+ </head>
2079
+ <body style="margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; background-color: #f5f5f5;">
2080
+ <table role="presentation" style="width: 100%; border-collapse: collapse;">
2081
+ <tr>
2082
+ <td style="padding: 40px 20px;">
2083
+ <table role="presentation" style="max-width: 600px; margin: 0 auto; background-color: #ffffff; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
2084
+ <!-- Header -->
2085
+ <tr>
2086
+ <td style="padding: 40px 40px 20px; text-align: center; border-bottom: 1px solid #e0e0e0;">
2087
+ <h1 style="margin: 0; color: #1a1a1a; font-size: 24px; font-weight: 600;">Email Address Changed</h1>
2088
+ </td>
2089
+ </tr>
2090
+
2091
+ <!-- Content -->
2092
+ <tr>
2093
+ <td style="padding: 40px;">
2094
+ <p style="margin: 0 0 20px; color: #333333; font-size: 16px; line-height: 1.6;">
2095
+ Hello <strong>${name}</strong>,
2096
+ </p>
2097
+ <p style="margin: 0 0 30px; color: #333333; font-size: 16px; line-height: 1.6;">
2098
+ We're writing to inform you that your Vritti AI Cloud email address has been successfully changed.
2099
+ </p>
2100
+
2101
+ <div style="background-color: #f8f9fa; padding: 20px; border-radius: 8px; margin: 30px 0;">
2102
+ <p style="margin: 0 0 10px; color: #666666; font-size: 14px;">
2103
+ <strong>Previous Email:</strong>
2104
+ </p>
2105
+ <p style="margin: 0 0 20px; color: #333333; font-size: 16px; font-family: monospace;">
2106
+ ${oldEmail}
2107
+ </p>
2108
+ <p style="margin: 0 0 10px; color: #666666; font-size: 14px;">
2109
+ <strong>New Email:</strong>
2110
+ </p>
2111
+ <p style="margin: 0; color: #333333; font-size: 16px; font-family: monospace;">
2112
+ ${newEmail}
2113
+ </p>
2114
+ </div>
2115
+
2116
+ <div style="background-color: #fff3cd; border-left: 4px solid #ffc107; padding: 20px; margin: 30px 0; border-radius: 4px;">
2117
+ <p style="margin: 0 0 15px; color: #856404; font-size: 14px; line-height: 1.6;">
2118
+ <strong>Didn't make this change?</strong>
2119
+ </p>
2120
+ <p style="margin: 0 0 20px; color: #856404; font-size: 14px; line-height: 1.6;">
2121
+ If you did not authorize this change, you can revert it within the next <strong>${hoursUntilExpiry} hours</strong> by clicking the button below:
2122
+ </p>
2123
+ <div style="text-align: center;">
2124
+ <a href="${revertLink}" style="display: inline-block; padding: 12px 30px; background-color: #dc3545; color: #ffffff; text-decoration: none; border-radius: 6px; font-weight: 600; font-size: 14px;">
2125
+ Revert Email Change
2126
+ </a>
2127
+ </div>
2128
+ </div>
2129
+
2130
+ <p style="margin: 30px 0 0; color: #666666; font-size: 14px; line-height: 1.6;">
2131
+ If you made this change, you can safely ignore this email.
2132
+ </p>
2133
+ </td>
2134
+ </tr>
2135
+
2136
+ <!-- Footer -->
2137
+ <tr>
2138
+ <td style="padding: 30px 40px; border-top: 1px solid #e0e0e0; text-align: center;">
2139
+ <p style="margin: 0; color: #999999; font-size: 12px; line-height: 1.5;">
2140
+ Vritti AI Cloud - Cloud Management Platform
2141
+ </p>
2142
+ <p style="margin: 8px 0 0; color: #999999; font-size: 12px; line-height: 1.5;">
2143
+ This is an automated message, please do not reply.
2144
+ </p>
2145
+ </td>
2146
+ </tr>
2147
+ </table>
2148
+ </td>
2149
+ </tr>
2150
+ </table>
2151
+ </body>
2152
+ </html>
2153
+ `;
2154
+ const textContent = `
2155
+ Hello ${name},
2156
+
2157
+ We're writing to inform you that your Vritti AI Cloud email address has been successfully changed.
2158
+
2159
+ Previous Email: ${oldEmail}
2160
+ New Email: ${newEmail}
2161
+
2162
+ DIDN'T MAKE THIS CHANGE?
2163
+
2164
+ If you did not authorize this change, you can revert it within the next ${hoursUntilExpiry} hours by visiting:
2165
+ ${revertLink}
2166
+
2167
+ If you made this change, you can safely ignore this email.
2168
+
2169
+ ---
2170
+ Vritti AI Cloud - Cloud Management Platform
2171
+ This is an automated message, please do not reply.
2172
+ `.trim();
2173
+ await this.sendEmail({
2174
+ to: [
2175
+ {
2176
+ email: oldEmail,
2177
+ name
2178
+ }
2179
+ ],
2180
+ subject,
2181
+ htmlContent,
2182
+ textContent
2183
+ });
2184
+ this.logger.log(`Email change notification sent to ${oldEmail}`);
2185
+ }
2186
+ // Sends a confirmation to the restored email address after a revert
2187
+ async sendEmailRevertConfirmation(email, displayName) {
2188
+ const name = displayName || "there";
2189
+ const subject = "Email Address Change Reverted - Vritti AI Cloud";
2190
+ const htmlContent = `
2191
+ <!DOCTYPE html>
2192
+ <html>
2193
+ <head>
2194
+ <meta charset="UTF-8">
2195
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
2196
+ </head>
2197
+ <body style="margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; background-color: #f5f5f5;">
2198
+ <table role="presentation" style="width: 100%; border-collapse: collapse;">
2199
+ <tr>
2200
+ <td style="padding: 40px 20px;">
2201
+ <table role="presentation" style="max-width: 600px; margin: 0 auto; background-color: #ffffff; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
2202
+ <!-- Header -->
2203
+ <tr>
2204
+ <td style="padding: 40px 40px 20px; text-align: center; border-bottom: 1px solid #e0e0e0;">
2205
+ <h1 style="margin: 0; color: #1a1a1a; font-size: 24px; font-weight: 600;">Email Change Reverted</h1>
2206
+ </td>
2207
+ </tr>
2208
+
2209
+ <!-- Content -->
2210
+ <tr>
2211
+ <td style="padding: 40px;">
2212
+ <p style="margin: 0 0 20px; color: #333333; font-size: 16px; line-height: 1.6;">
2213
+ Hello <strong>${name}</strong>,
2214
+ </p>
2215
+ <p style="margin: 0 0 30px; color: #333333; font-size: 16px; line-height: 1.6;">
2216
+ Your recent email address change has been successfully reverted. Your email is now:
2217
+ </p>
2218
+
2219
+ <div style="background-color: #d4edda; padding: 20px; border-radius: 8px; margin: 30px 0; text-align: center;">
2220
+ <p style="margin: 0; color: #155724; font-size: 18px; font-weight: 600; font-family: monospace;">
2221
+ ${email}
2222
+ </p>
2223
+ </div>
2224
+
2225
+ <p style="margin: 30px 0 20px; color: #666666; font-size: 14px; line-height: 1.6;">
2226
+ If you did not request this revert, please contact our support team immediately.
2227
+ </p>
2228
+ </td>
2229
+ </tr>
2230
+
2231
+ <!-- Footer -->
2232
+ <tr>
2233
+ <td style="padding: 30px 40px; border-top: 1px solid #e0e0e0; text-align: center;">
2234
+ <p style="margin: 0; color: #999999; font-size: 12px; line-height: 1.5;">
2235
+ Vritti AI Cloud - Cloud Management Platform
2236
+ </p>
2237
+ <p style="margin: 8px 0 0; color: #999999; font-size: 12px; line-height: 1.5;">
2238
+ This is an automated message, please do not reply.
2239
+ </p>
2240
+ </td>
2241
+ </tr>
2242
+ </table>
2243
+ </td>
2244
+ </tr>
2245
+ </table>
2246
+ </body>
2247
+ </html>
2248
+ `;
2249
+ const textContent = `
2250
+ Hello ${name},
2251
+
2252
+ Your recent email address change has been successfully reverted. Your email is now:
2253
+
2254
+ ${email}
2255
+
2256
+ If you did not request this revert, please contact our support team immediately.
2257
+
2258
+ ---
2259
+ Vritti AI Cloud - Cloud Management Platform
2260
+ This is an automated message, please do not reply.
2261
+ `.trim();
2262
+ await this.sendEmail({
2263
+ to: [
2264
+ {
2265
+ email,
2266
+ name
2267
+ }
2268
+ ],
2269
+ subject,
2270
+ htmlContent,
2271
+ textContent
2272
+ });
2273
+ this.logger.log(`Email revert confirmation sent to ${email}`);
2274
+ }
2275
+ // Verifies Brevo API connectivity — a 400 response means the API is reachable
2276
+ async verifyConnection() {
2277
+ try {
2278
+ await this.brevoClient.transactionalEmails.sendTransacEmail({
2279
+ sender: {
2280
+ email: this.senderEmail,
2281
+ name: this.senderName
2282
+ },
2283
+ to: [
2284
+ {
2285
+ email: this.senderEmail
2286
+ }
2287
+ ],
2288
+ subject: "Connection Test",
2289
+ htmlContent: "<p>Test</p>"
2290
+ });
2291
+ return true;
2292
+ } catch (err) {
2293
+ if (err instanceof import_brevo.BrevoError && err.statusCode === 400) {
2294
+ return true;
2295
+ }
2296
+ this.logger.error("Brevo connection verification failed:", err);
2297
+ return false;
2298
+ }
2299
+ }
2300
+ // Sends a transactional email via Brevo — retries handled internally by BrevoClient
2301
+ async sendEmail(emailData) {
2302
+ try {
2303
+ const result = await this.brevoClient.transactionalEmails.sendTransacEmail({
2304
+ sender: {
2305
+ email: this.senderEmail,
2306
+ name: this.senderName
2307
+ },
2308
+ to: emailData.to,
2309
+ subject: emailData.subject,
2310
+ htmlContent: emailData.htmlContent,
2311
+ textContent: emailData.textContent
2312
+ });
2313
+ this.logger.debug(`Email sent successfully. Message ID: ${result.messageId}`);
2314
+ } catch (err) {
2315
+ if (err instanceof import_brevo.BrevoTimeoutError) {
2316
+ this.logger.error("Brevo request timed out after retries.");
2317
+ throw new Error("Email sending failed: timeout");
2318
+ }
2319
+ if (err instanceof import_brevo.BrevoError) {
2320
+ if (err.statusCode === 429) {
2321
+ this.logger.error("Brevo rate limit exceeded after retries.");
2322
+ throw new Error("Email sending failed: rate limit exceeded");
2323
+ }
2324
+ if (err.statusCode === 401) {
2325
+ this.logger.error("Brevo authentication failed. Check your API key.");
2326
+ throw new Error("Email service authentication failed");
2327
+ }
2328
+ if (err.statusCode === 400) {
2329
+ this.logger.error("Bad request to Brevo API:", err.message);
2330
+ throw new Error(`Invalid email parameters: ${err.message}`);
2331
+ }
2332
+ this.logger.error(`Brevo API error ${err.statusCode}:`, err.message);
2333
+ throw new Error(`Email sending failed: ${err.message}`);
2334
+ }
2335
+ throw err;
2336
+ }
2337
+ }
2282
2338
  };
2339
+ EmailService = _ts_decorate11([
2340
+ (0, import_common21.Injectable)(),
2341
+ _ts_metadata7("design:type", Function),
2342
+ _ts_metadata7("design:paramtypes", [
2343
+ typeof import_config6.ConfigService === "undefined" ? Object : import_config6.ConfigService
2344
+ ])
2345
+ ], EmailService);
2283
2346
 
2284
- // src/exceptions/not-acceptable.exception.ts
2347
+ // src/email/email.module.ts
2348
+ function _ts_decorate12(decorators, target, key, desc) {
2349
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
2350
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
2351
+ 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;
2352
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
2353
+ }
2354
+ __name(_ts_decorate12, "_ts_decorate");
2355
+ var EmailModule = class {
2356
+ static {
2357
+ __name(this, "EmailModule");
2358
+ }
2359
+ };
2360
+ EmailModule = _ts_decorate12([
2361
+ (0, import_common22.Global)(),
2362
+ (0, import_common22.Module)({
2363
+ imports: [
2364
+ import_config7.ConfigModule
2365
+ ],
2366
+ providers: [
2367
+ EmailService
2368
+ ],
2369
+ exports: [
2370
+ EmailService
2371
+ ]
2372
+ })
2373
+ ], EmailModule);
2374
+
2375
+ // src/exceptions/bad-gateway.exception.ts
2376
+ var import_common24 = require("@nestjs/common");
2377
+
2378
+ // src/exceptions/base-field.exception.ts
2379
+ var import_common23 = require("@nestjs/common");
2380
+ var HttpProblemException = class extends import_common23.HttpException {
2381
+ static {
2382
+ __name(this, "HttpProblemException");
2383
+ }
2384
+ constructor(detailOrOptions, httpStatus) {
2385
+ const options = typeof detailOrOptions === "string" ? {
2386
+ detail: detailOrOptions
2387
+ } : detailOrOptions;
2388
+ super({
2389
+ type: options.type ?? "about:blank",
2390
+ label: options.label,
2391
+ detail: options.detail,
2392
+ errors: options.errors ?? []
2393
+ }, httpStatus);
2394
+ }
2395
+ };
2396
+
2397
+ // src/exceptions/bad-gateway.exception.ts
2398
+ var BadGatewayException = class extends HttpProblemException {
2399
+ static {
2400
+ __name(this, "BadGatewayException");
2401
+ }
2402
+ constructor(detailOrOptions) {
2403
+ super(detailOrOptions ?? "Bad Gateway", import_common24.HttpStatus.BAD_GATEWAY);
2404
+ }
2405
+ };
2406
+
2407
+ // src/exceptions/bad-request.exception.ts
2408
+ var import_common25 = require("@nestjs/common");
2409
+ var BadRequestException = class extends HttpProblemException {
2410
+ static {
2411
+ __name(this, "BadRequestException");
2412
+ }
2413
+ constructor(detailOrOptions) {
2414
+ super(detailOrOptions ?? "Bad Request", import_common25.HttpStatus.BAD_REQUEST);
2415
+ }
2416
+ };
2417
+
2418
+ // src/exceptions/conflict.exception.ts
2419
+ var import_common26 = require("@nestjs/common");
2420
+ var ConflictException = class extends HttpProblemException {
2421
+ static {
2422
+ __name(this, "ConflictException");
2423
+ }
2424
+ constructor(detailOrOptions) {
2425
+ super(detailOrOptions ?? "Conflict", import_common26.HttpStatus.CONFLICT);
2426
+ }
2427
+ };
2428
+
2429
+ // src/exceptions/forbidden.exception.ts
2285
2430
  var import_common27 = require("@nestjs/common");
2431
+ var ForbiddenException2 = class extends HttpProblemException {
2432
+ static {
2433
+ __name(this, "ForbiddenException");
2434
+ }
2435
+ constructor(detailOrOptions) {
2436
+ super(detailOrOptions ?? "Forbidden", import_common27.HttpStatus.FORBIDDEN);
2437
+ }
2438
+ };
2439
+
2440
+ // src/exceptions/gone.exception.ts
2441
+ var import_common28 = require("@nestjs/common");
2442
+ var GoneException = class extends HttpProblemException {
2443
+ static {
2444
+ __name(this, "GoneException");
2445
+ }
2446
+ constructor(detailOrOptions) {
2447
+ super(detailOrOptions ?? "Gone", import_common28.HttpStatus.GONE);
2448
+ }
2449
+ };
2450
+
2451
+ // src/exceptions/internal-server-error.exception.ts
2452
+ var import_common29 = require("@nestjs/common");
2453
+ var InternalServerErrorException3 = class extends HttpProblemException {
2454
+ static {
2455
+ __name(this, "InternalServerErrorException");
2456
+ }
2457
+ constructor(detailOrOptions) {
2458
+ super(detailOrOptions ?? "Internal Server Error", import_common29.HttpStatus.INTERNAL_SERVER_ERROR);
2459
+ }
2460
+ };
2461
+
2462
+ // src/exceptions/method-not-allowed.exception.ts
2463
+ var import_common30 = require("@nestjs/common");
2464
+ var MethodNotAllowedException = class extends HttpProblemException {
2465
+ static {
2466
+ __name(this, "MethodNotAllowedException");
2467
+ }
2468
+ constructor(detailOrOptions) {
2469
+ super(detailOrOptions ?? "Method Not Allowed", import_common30.HttpStatus.METHOD_NOT_ALLOWED);
2470
+ }
2471
+ };
2472
+
2473
+ // src/exceptions/not-acceptable.exception.ts
2474
+ var import_common31 = require("@nestjs/common");
2286
2475
  var NotAcceptableException = class extends HttpProblemException {
2287
2476
  static {
2288
2477
  __name(this, "NotAcceptableException");
2289
2478
  }
2290
2479
  constructor(detailOrOptions) {
2291
- super(detailOrOptions ?? "Not Acceptable", import_common27.HttpStatus.NOT_ACCEPTABLE);
2480
+ super(detailOrOptions ?? "Not Acceptable", import_common31.HttpStatus.NOT_ACCEPTABLE);
2292
2481
  }
2293
2482
  };
2294
2483
 
2295
2484
  // src/exceptions/not-found.exception.ts
2296
- var import_common28 = require("@nestjs/common");
2485
+ var import_common32 = require("@nestjs/common");
2297
2486
  var NotFoundException = class extends HttpProblemException {
2298
2487
  static {
2299
2488
  __name(this, "NotFoundException");
2300
2489
  }
2301
2490
  constructor(detailOrOptions) {
2302
- super(detailOrOptions ?? "Not Found", import_common28.HttpStatus.NOT_FOUND);
2491
+ super(detailOrOptions ?? "Not Found", import_common32.HttpStatus.NOT_FOUND);
2303
2492
  }
2304
2493
  };
2305
2494
 
2306
2495
  // src/exceptions/not-implemented.exception.ts
2307
- var import_common29 = require("@nestjs/common");
2496
+ var import_common33 = require("@nestjs/common");
2308
2497
  var NotImplementedException = class extends HttpProblemException {
2309
2498
  static {
2310
2499
  __name(this, "NotImplementedException");
2311
2500
  }
2312
2501
  constructor(detailOrOptions) {
2313
- super(detailOrOptions ?? "Not Implemented", import_common29.HttpStatus.NOT_IMPLEMENTED);
2502
+ super(detailOrOptions ?? "Not Implemented", import_common33.HttpStatus.NOT_IMPLEMENTED);
2314
2503
  }
2315
2504
  };
2316
2505
 
2317
2506
  // src/exceptions/payload-too-large.exception.ts
2318
- var import_common30 = require("@nestjs/common");
2507
+ var import_common34 = require("@nestjs/common");
2319
2508
  var PayloadTooLargeException = class extends HttpProblemException {
2320
2509
  static {
2321
2510
  __name(this, "PayloadTooLargeException");
2322
2511
  }
2323
2512
  constructor(detailOrOptions) {
2324
- super(detailOrOptions ?? "Payload Too Large", import_common30.HttpStatus.PAYLOAD_TOO_LARGE);
2513
+ super(detailOrOptions ?? "Payload Too Large", import_common34.HttpStatus.PAYLOAD_TOO_LARGE);
2325
2514
  }
2326
2515
  };
2327
2516
 
2328
2517
  // src/exceptions/request-timeout.exception.ts
2329
- var import_common31 = require("@nestjs/common");
2518
+ var import_common35 = require("@nestjs/common");
2330
2519
  var RequestTimeoutException = class extends HttpProblemException {
2331
2520
  static {
2332
2521
  __name(this, "RequestTimeoutException");
2333
2522
  }
2334
2523
  constructor(detailOrOptions) {
2335
- super(detailOrOptions ?? "Request Timeout", import_common31.HttpStatus.REQUEST_TIMEOUT);
2524
+ super(detailOrOptions ?? "Request Timeout", import_common35.HttpStatus.REQUEST_TIMEOUT);
2336
2525
  }
2337
2526
  };
2338
2527
 
2339
2528
  // src/exceptions/service-unavailable.exception.ts
2340
- var import_common32 = require("@nestjs/common");
2529
+ var import_common36 = require("@nestjs/common");
2341
2530
  var ServiceUnavailableException = class extends HttpProblemException {
2342
2531
  static {
2343
2532
  __name(this, "ServiceUnavailableException");
2344
2533
  }
2345
2534
  constructor(detailOrOptions) {
2346
- super(detailOrOptions ?? "Service Unavailable", import_common32.HttpStatus.SERVICE_UNAVAILABLE);
2535
+ super(detailOrOptions ?? "Service Unavailable", import_common36.HttpStatus.SERVICE_UNAVAILABLE);
2347
2536
  }
2348
2537
  };
2349
2538
 
2350
2539
  // src/exceptions/too-many-requests.exception.ts
2351
- var import_common33 = require("@nestjs/common");
2540
+ var import_common37 = require("@nestjs/common");
2352
2541
  var TooManyRequestsException = class extends HttpProblemException {
2353
2542
  static {
2354
2543
  __name(this, "TooManyRequestsException");
2355
2544
  }
2356
2545
  constructor(detailOrOptions) {
2357
- super(detailOrOptions ?? "Too Many Requests", import_common33.HttpStatus.TOO_MANY_REQUESTS);
2546
+ super(detailOrOptions ?? "Too Many Requests", import_common37.HttpStatus.TOO_MANY_REQUESTS);
2358
2547
  }
2359
2548
  };
2360
2549
 
2361
2550
  // src/exceptions/unauthorized.exception.ts
2362
- var import_common34 = require("@nestjs/common");
2363
- var UnauthorizedException5 = class extends HttpProblemException {
2551
+ var import_common38 = require("@nestjs/common");
2552
+ var UnauthorizedException3 = class extends HttpProblemException {
2364
2553
  static {
2365
2554
  __name(this, "UnauthorizedException");
2366
2555
  }
2367
2556
  constructor(detailOrOptions) {
2368
- super(detailOrOptions ?? "Unauthorized", import_common34.HttpStatus.UNAUTHORIZED);
2557
+ super(detailOrOptions ?? "Unauthorized", import_common38.HttpStatus.UNAUTHORIZED);
2369
2558
  }
2370
2559
  };
2371
2560
 
2372
2561
  // src/exceptions/unprocessable-entity.exception.ts
2373
- var import_common35 = require("@nestjs/common");
2562
+ var import_common39 = require("@nestjs/common");
2374
2563
  var UnprocessableEntityException = class extends HttpProblemException {
2375
2564
  static {
2376
2565
  __name(this, "UnprocessableEntityException");
2377
2566
  }
2378
2567
  constructor(detailOrOptions) {
2379
- super(detailOrOptions ?? "Unprocessable Entity", import_common35.HttpStatus.UNPROCESSABLE_ENTITY);
2568
+ super(detailOrOptions ?? "Unprocessable Entity", import_common39.HttpStatus.UNPROCESSABLE_ENTITY);
2380
2569
  }
2381
2570
  };
2382
2571
 
2383
2572
  // src/exceptions/unsupported-media-type.exception.ts
2384
- var import_common36 = require("@nestjs/common");
2573
+ var import_common40 = require("@nestjs/common");
2385
2574
  var UnsupportedMediaTypeException = class extends HttpProblemException {
2386
2575
  static {
2387
2576
  __name(this, "UnsupportedMediaTypeException");
2388
2577
  }
2389
2578
  constructor(detailOrOptions) {
2390
- super(detailOrOptions ?? "Unsupported Media Type", import_common36.HttpStatus.UNSUPPORTED_MEDIA_TYPE);
2579
+ super(detailOrOptions ?? "Unsupported Media Type", import_common40.HttpStatus.UNSUPPORTED_MEDIA_TYPE);
2391
2580
  }
2392
2581
  };
2393
2582
 
2394
2583
  // src/exceptions/validation.exception.ts
2395
- var import_common37 = require("@nestjs/common");
2584
+ var import_common41 = require("@nestjs/common");
2396
2585
  var ValidationException = class extends HttpProblemException {
2397
2586
  static {
2398
2587
  __name(this, "ValidationException");
2399
2588
  }
2400
2589
  constructor(detailOrOptions) {
2401
- super(detailOrOptions ?? "Validation Failed", import_common37.HttpStatus.BAD_REQUEST);
2590
+ super(detailOrOptions ?? "Validation Failed", import_common41.HttpStatus.BAD_REQUEST);
2402
2591
  }
2403
2592
  };
2404
2593
 
2405
2594
  // src/filters/http-exception.filter.ts
2406
- var import_common38 = require("@nestjs/common");
2407
- function _ts_decorate12(decorators, target, key, desc) {
2595
+ var import_common42 = require("@nestjs/common");
2596
+ function _ts_decorate13(decorators, target, key, desc) {
2408
2597
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
2409
2598
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
2410
2599
  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;
2411
2600
  return c > 3 && r && Object.defineProperty(target, key, r), r;
2412
2601
  }
2413
- __name(_ts_decorate12, "_ts_decorate");
2602
+ __name(_ts_decorate13, "_ts_decorate");
2414
2603
  function getHttpStatusTitle(status) {
2415
- const enumKey = Object.entries(import_common38.HttpStatus).find(([key, value]) => value === status && Number.isNaN(Number(key)))?.[0];
2604
+ const enumKey = Object.entries(import_common42.HttpStatus).find(([key, value]) => value === status && Number.isNaN(Number(key)))?.[0];
2416
2605
  if (!enumKey) {
2417
2606
  return "Error";
2418
2607
  }
@@ -2423,17 +2612,17 @@ var HttpExceptionFilter = class _HttpExceptionFilter {
2423
2612
  static {
2424
2613
  __name(this, "HttpExceptionFilter");
2425
2614
  }
2426
- logger = new import_common38.Logger(_HttpExceptionFilter.name);
2615
+ logger = new import_common42.Logger(_HttpExceptionFilter.name);
2427
2616
  catch(exception, host) {
2428
2617
  const ctx = host.switchToHttp();
2429
2618
  const response = ctx.getResponse();
2430
2619
  const request = ctx.getRequest();
2431
- let status = import_common38.HttpStatus.INTERNAL_SERVER_ERROR;
2620
+ let status = import_common42.HttpStatus.INTERNAL_SERVER_ERROR;
2432
2621
  let type = "about:blank";
2433
2622
  let label;
2434
2623
  let detail = "Internal server error";
2435
2624
  let errors = [];
2436
- if (exception instanceof import_common38.HttpException) {
2625
+ if (exception instanceof import_common42.HttpException) {
2437
2626
  status = exception.getStatus();
2438
2627
  const exceptionResponse = exception.getResponse();
2439
2628
  if (typeof exceptionResponse === "object" && exceptionResponse !== null) {
@@ -2483,226 +2672,16 @@ var HttpExceptionFilter = class _HttpExceptionFilter {
2483
2672
  response.header("Content-Type", "application/problem+json").status(status).send(problemDetails);
2484
2673
  }
2485
2674
  };
2486
- HttpExceptionFilter = _ts_decorate12([
2487
- (0, import_common38.Catch)()
2675
+ HttpExceptionFilter = _ts_decorate13([
2676
+ (0, import_common42.Catch)()
2488
2677
  ], HttpExceptionFilter);
2489
2678
 
2490
- // src/utils/phone.utils.ts
2491
- var CALLING_CODE_TO_COUNTRY = {
2492
- // 3-digit codes
2493
- "355": "AL",
2494
- "213": "DZ",
2495
- "376": "AD",
2496
- "244": "AO",
2497
- "672": "AQ",
2498
- "374": "AM",
2499
- "297": "AW",
2500
- "994": "AZ",
2501
- "973": "BH",
2502
- "880": "BD",
2503
- "375": "BY",
2504
- "501": "BZ",
2505
- "229": "BJ",
2506
- "975": "BT",
2507
- "591": "BO",
2508
- "387": "BA",
2509
- "267": "BW",
2510
- "673": "BN",
2511
- "359": "BG",
2512
- "226": "BF",
2513
- "257": "BI",
2514
- "855": "KH",
2515
- "237": "CM",
2516
- "238": "CV",
2517
- "236": "CF",
2518
- "235": "TD",
2519
- "269": "KM",
2520
- "242": "CG",
2521
- "243": "CD",
2522
- "506": "CR",
2523
- "385": "HR",
2524
- "357": "CY",
2525
- "420": "CZ",
2526
- "253": "DJ",
2527
- "593": "EC",
2528
- "503": "SV",
2529
- "240": "GQ",
2530
- "291": "ER",
2531
- "372": "EE",
2532
- "251": "ET",
2533
- "679": "FJ",
2534
- "358": "FI",
2535
- "241": "GA",
2536
- "220": "GM",
2537
- "995": "GE",
2538
- "233": "GH",
2539
- "350": "GI",
2540
- "299": "GL",
2541
- "502": "GT",
2542
- "224": "GN",
2543
- "245": "GW",
2544
- "592": "GY",
2545
- "509": "HT",
2546
- "504": "HN",
2547
- "354": "IS",
2548
- "964": "IQ",
2549
- "353": "IE",
2550
- "972": "IL",
2551
- "225": "CI",
2552
- "962": "JO",
2553
- "254": "KE",
2554
- "686": "KI",
2555
- "965": "KW",
2556
- "996": "KG",
2557
- "856": "LA",
2558
- "371": "LV",
2559
- "961": "LB",
2560
- "266": "LS",
2561
- "231": "LR",
2562
- "218": "LY",
2563
- "423": "LI",
2564
- "370": "LT",
2565
- "352": "LU",
2566
- "389": "MK",
2567
- "261": "MG",
2568
- "265": "MW",
2569
- "960": "MV",
2570
- "223": "ML",
2571
- "356": "MT",
2572
- "692": "MH",
2573
- "222": "MR",
2574
- "230": "MU",
2575
- "262": "YT",
2576
- "691": "FM",
2577
- "373": "MD",
2578
- "377": "MC",
2579
- "976": "MN",
2580
- "382": "ME",
2581
- "258": "MZ",
2582
- "264": "NA",
2583
- "674": "NR",
2584
- "977": "NP",
2585
- "505": "NI",
2586
- "227": "NE",
2587
- "234": "NG",
2588
- "683": "NU",
2589
- "968": "OM",
2590
- "680": "PW",
2591
- "970": "PS",
2592
- "507": "PA",
2593
- "675": "PG",
2594
- "595": "PY",
2595
- "351": "PT",
2596
- "974": "QA",
2597
- "250": "RW",
2598
- "685": "WS",
2599
- "378": "SM",
2600
- "239": "ST",
2601
- "966": "SA",
2602
- "221": "SN",
2603
- "381": "RS",
2604
- "248": "SC",
2605
- "232": "SL",
2606
- "421": "SK",
2607
- "386": "SI",
2608
- "677": "SB",
2609
- "252": "SO",
2610
- "211": "SS",
2611
- "249": "SD",
2612
- "597": "SR",
2613
- "268": "SZ",
2614
- "963": "SY",
2615
- "992": "TJ",
2616
- "255": "TZ",
2617
- "228": "TG",
2618
- "676": "TO",
2619
- "216": "TN",
2620
- "993": "TM",
2621
- "688": "TV",
2622
- "256": "UG",
2623
- "380": "UA",
2624
- "971": "AE",
2625
- "598": "UY",
2626
- "998": "UZ",
2627
- "678": "VU",
2628
- "379": "VA",
2629
- "967": "YE",
2630
- "260": "ZM",
2631
- "263": "ZW",
2632
- // 2-digit codes
2633
- "93": "AF",
2634
- "54": "AR",
2635
- "61": "AU",
2636
- "43": "AT",
2637
- "32": "BE",
2638
- "55": "BR",
2639
- "56": "CL",
2640
- "86": "CN",
2641
- "57": "CO",
2642
- "53": "CU",
2643
- "45": "DK",
2644
- "20": "EG",
2645
- "33": "FR",
2646
- "49": "DE",
2647
- "30": "GR",
2648
- "36": "HU",
2649
- "91": "IN",
2650
- "62": "ID",
2651
- "98": "IR",
2652
- "39": "IT",
2653
- "81": "JP",
2654
- "82": "KR",
2655
- "60": "MY",
2656
- "52": "MX",
2657
- "31": "NL",
2658
- "64": "NZ",
2659
- "47": "NO",
2660
- "92": "PK",
2661
- "51": "PE",
2662
- "63": "PH",
2663
- "48": "PL",
2664
- "40": "RO",
2665
- "65": "SG",
2666
- "27": "ZA",
2667
- "34": "ES",
2668
- "94": "LK",
2669
- "46": "SE",
2670
- "41": "CH",
2671
- "66": "TH",
2672
- "90": "TR",
2673
- "44": "GB",
2674
- "58": "VE",
2675
- "84": "VN",
2676
- // 1-digit codes (shared codes default to most common country)
2677
- "1": "US",
2678
- "7": "RU"
2679
- };
2680
- function extractCountryFromPhone(phone) {
2681
- const digits = phone.startsWith("+") ? phone.slice(1) : phone;
2682
- for (const length of [
2683
- 3,
2684
- 2,
2685
- 1
2686
- ]) {
2687
- const prefix = digits.slice(0, length);
2688
- if (CALLING_CODE_TO_COUNTRY[prefix]) {
2689
- return CALLING_CODE_TO_COUNTRY[prefix];
2690
- }
2691
- }
2692
- return void 0;
2693
- }
2694
- __name(extractCountryFromPhone, "extractCountryFromPhone");
2695
- function normalizePhoneNumber(phone) {
2696
- return phone.startsWith("+") ? phone : `+${phone}`;
2697
- }
2698
- __name(normalizePhoneNumber, "normalizePhoneNumber");
2699
-
2700
2679
  // src/logger/interceptors/http-logger.interceptor.ts
2701
- var import_common40 = require("@nestjs/common");
2702
- var import_operators2 = require("rxjs/operators");
2680
+ var import_common44 = require("@nestjs/common");
2681
+ var import_operators = require("rxjs/operators");
2703
2682
 
2704
2683
  // src/logger/services/logger.service.ts
2705
- var import_common39 = require("@nestjs/common");
2684
+ var import_common43 = require("@nestjs/common");
2706
2685
  var import_winston = require("winston");
2707
2686
  var import_winston_daily_rotate_file = __toESM(require("winston-daily-rotate-file"), 1);
2708
2687
 
@@ -2740,13 +2719,13 @@ function addCorrelationIdToResponse(reply, correlationId, headerName = DEFAULT_C
2740
2719
  __name(addCorrelationIdToResponse, "addCorrelationIdToResponse");
2741
2720
 
2742
2721
  // src/logger/services/logger.service.ts
2743
- function _ts_decorate13(decorators, target, key, desc) {
2722
+ function _ts_decorate14(decorators, target, key, desc) {
2744
2723
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
2745
2724
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
2746
2725
  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;
2747
2726
  return c > 3 && r && Object.defineProperty(target, key, r), r;
2748
2727
  }
2749
- __name(_ts_decorate13, "_ts_decorate");
2728
+ __name(_ts_decorate14, "_ts_decorate");
2750
2729
  function _ts_metadata8(k, v) {
2751
2730
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
2752
2731
  }
@@ -2778,10 +2757,7 @@ var LoggerService = class _LoggerService {
2778
2757
  this.activeLogger = this.createWinstonLogger(options);
2779
2758
  }
2780
2759
  }
2781
- /**
2782
- * Creates a Winston logger instance with inline configuration.
2783
- * Consolidates winston-config.factory.ts logic.
2784
- */
2760
+ // Creates a Winston logger instance with inline transports and format configuration
2785
2761
  createWinstonLogger(opts) {
2786
2762
  const level = opts.level ?? "debug";
2787
2763
  const logFormat = opts.format ?? "text";
@@ -2872,9 +2848,7 @@ ${trace}`;
2872
2848
  setContext(context) {
2873
2849
  this.context = context;
2874
2850
  }
2875
- /**
2876
- * Unified internal logging method that handles both Winston and NestJS Logger.
2877
- */
2851
+ // Dispatches a log entry to either the Winston or NestJS logger implementation
2878
2852
  _log(level, message, context, trace) {
2879
2853
  const ctx = context ?? this.context;
2880
2854
  if ("format" in this.activeLogger && "transports" in this.activeLogger) {
@@ -2902,9 +2876,7 @@ ${trace}`;
2902
2876
  }
2903
2877
  }
2904
2878
  }
2905
- /**
2906
- * Logs with custom metadata (Winston only).
2907
- */
2879
+ // Logs a message with custom metadata fields (Winston only)
2908
2880
  logWithMetadata(level, message, metadata, context) {
2909
2881
  const ctx = context ?? this.context;
2910
2882
  if ("format" in this.activeLogger && "transports" in this.activeLogger) {
@@ -2931,10 +2903,7 @@ ${trace}`;
2931
2903
  }
2932
2904
  return String(message);
2933
2905
  }
2934
- /**
2935
- * Enriches metadata with correlation context from AsyncLocalStorage.
2936
- * Inline from winston-logger.service.ts
2937
- */
2906
+ // Enriches metadata with correlation context from AsyncLocalStorage
2938
2907
  enrichMetadata(metadata = {}, context, trace) {
2939
2908
  const enriched = {
2940
2909
  ...metadata
@@ -2958,10 +2927,10 @@ ${trace}`;
2958
2927
  return childLogger;
2959
2928
  }
2960
2929
  };
2961
- LoggerService = _ts_decorate13([
2962
- (0, import_common39.Injectable)(),
2963
- _ts_param4(0, (0, import_common39.Optional)()),
2964
- _ts_param4(1, (0, import_common39.Optional)()),
2930
+ LoggerService = _ts_decorate14([
2931
+ (0, import_common43.Injectable)(),
2932
+ _ts_param4(0, (0, import_common43.Optional)()),
2933
+ _ts_param4(1, (0, import_common43.Optional)()),
2965
2934
  _ts_metadata8("design:type", Function),
2966
2935
  _ts_metadata8("design:paramtypes", [
2967
2936
  typeof LoggerModuleOptions === "undefined" ? Object : LoggerModuleOptions,
@@ -2970,13 +2939,13 @@ LoggerService = _ts_decorate13([
2970
2939
  ], LoggerService);
2971
2940
 
2972
2941
  // src/logger/interceptors/http-logger.interceptor.ts
2973
- function _ts_decorate14(decorators, target, key, desc) {
2942
+ function _ts_decorate15(decorators, target, key, desc) {
2974
2943
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
2975
2944
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
2976
2945
  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;
2977
2946
  return c > 3 && r && Object.defineProperty(target, key, r), r;
2978
2947
  }
2979
- __name(_ts_decorate14, "_ts_decorate");
2948
+ __name(_ts_decorate15, "_ts_decorate");
2980
2949
  function _ts_metadata9(k, v) {
2981
2950
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
2982
2951
  }
@@ -3012,12 +2981,12 @@ var HttpLoggerInterceptor = class {
3012
2981
  if (this.enableRequestLog) {
3013
2982
  this.logRequest(request);
3014
2983
  }
3015
- return next.handle().pipe((0, import_operators2.tap)(() => {
2984
+ return next.handle().pipe((0, import_operators.tap)(() => {
3016
2985
  if (this.enableResponseLog) {
3017
2986
  const duration = Date.now() - startTime;
3018
2987
  this.logResponse(request, response, duration);
3019
2988
  }
3020
- }), (0, import_operators2.catchError)((error) => {
2989
+ }), (0, import_operators.catchError)((error) => {
3021
2990
  const duration = Date.now() - startTime;
3022
2991
  this.logError(request, response, duration, error);
3023
2992
  throw error;
@@ -3065,6 +3034,7 @@ var HttpLoggerInterceptor = class {
3065
3034
  try {
3066
3035
  const correlationContext = getCorrelationContext();
3067
3036
  const statusCode = response.statusCode || 500;
3037
+ const err = error;
3068
3038
  const metadata = {
3069
3039
  type: "http_error",
3070
3040
  method: request.method,
@@ -3072,25 +3042,25 @@ var HttpLoggerInterceptor = class {
3072
3042
  statusCode,
3073
3043
  duration,
3074
3044
  correlationId: correlationContext?.correlationId,
3075
- errorName: error?.name || "Error",
3076
- errorMessage: error?.message || "Unknown error"
3045
+ errorName: err.name || "Error",
3046
+ errorMessage: err.message || "Unknown error"
3077
3047
  };
3078
- if (error?.stack) {
3079
- metadata.trace = error.stack;
3048
+ if (err.stack) {
3049
+ metadata.trace = err.stack;
3080
3050
  }
3081
- if (error?.response) {
3082
- metadata.errorDetails = error.response;
3051
+ if (err.response) {
3052
+ metadata.errorDetails = err.response;
3083
3053
  }
3084
- const message = `ERROR ${request.method} ${request.url} ${statusCode} - ${error?.message || "Unknown error"}`;
3054
+ const message = `ERROR ${request.method} ${request.url} ${statusCode} - ${err.message || "Unknown error"}`;
3085
3055
  this.logger.logWithMetadata("error", message, metadata);
3086
3056
  } catch (loggingError) {
3087
3057
  this.logger.error("Failed to log HTTP error", loggingError.stack);
3088
3058
  }
3089
3059
  }
3090
3060
  };
3091
- HttpLoggerInterceptor = _ts_decorate14([
3092
- (0, import_common40.Injectable)(),
3093
- _ts_param5(1, (0, import_common40.Optional)()),
3061
+ HttpLoggerInterceptor = _ts_decorate15([
3062
+ (0, import_common44.Injectable)(),
3063
+ _ts_param5(1, (0, import_common44.Optional)()),
3094
3064
  _ts_metadata9("design:type", Function),
3095
3065
  _ts_metadata9("design:paramtypes", [
3096
3066
  typeof LoggerService === "undefined" ? Object : LoggerService,
@@ -3099,17 +3069,17 @@ HttpLoggerInterceptor = _ts_decorate14([
3099
3069
  ], HttpLoggerInterceptor);
3100
3070
 
3101
3071
  // src/logger/logger.module.ts
3102
- var import_common42 = require("@nestjs/common");
3072
+ var import_common46 = require("@nestjs/common");
3103
3073
 
3104
3074
  // src/logger/middleware/correlation-id.middleware.ts
3105
- var import_common41 = require("@nestjs/common");
3106
- function _ts_decorate15(decorators, target, key, desc) {
3075
+ var import_common45 = require("@nestjs/common");
3076
+ function _ts_decorate16(decorators, target, key, desc) {
3107
3077
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3108
3078
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
3109
3079
  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;
3110
3080
  return c > 3 && r && Object.defineProperty(target, key, r), r;
3111
3081
  }
3112
- __name(_ts_decorate15, "_ts_decorate");
3082
+ __name(_ts_decorate16, "_ts_decorate");
3113
3083
  function _ts_metadata10(k, v) {
3114
3084
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
3115
3085
  }
@@ -3124,9 +3094,7 @@ var CorrelationIdMiddleware = class {
3124
3094
  this.includeInResponse = options.includeInResponse ?? true;
3125
3095
  this.responseHeader = options.responseHeader ?? DEFAULT_CORRELATION_HEADER;
3126
3096
  }
3127
- /**
3128
- * Middleware handler for processing requests.
3129
- */
3097
+ // Generates and stores a correlation ID for the incoming request
3130
3098
  use(_req, reply, next) {
3131
3099
  const correlationId = generateCorrelationId();
3132
3100
  if (this.includeInResponse) {
@@ -3138,11 +3106,7 @@ var CorrelationIdMiddleware = class {
3138
3106
  next();
3139
3107
  });
3140
3108
  }
3141
- /**
3142
- * Fastify hook handler for onRequest.
3143
- * This is an async function that returns a Promise, ensuring the AsyncLocalStorage
3144
- * context persists throughout the entire request lifecycle.
3145
- */
3109
+ // Fastify onRequest hook that initializes correlation context in AsyncLocalStorage
3146
3110
  async onRequest(_req, reply) {
3147
3111
  const correlationId = generateCorrelationId();
3148
3112
  if (this.includeInResponse) {
@@ -3156,8 +3120,8 @@ var CorrelationIdMiddleware = class {
3156
3120
  }
3157
3121
  }
3158
3122
  };
3159
- CorrelationIdMiddleware = _ts_decorate15([
3160
- (0, import_common41.Injectable)(),
3123
+ CorrelationIdMiddleware = _ts_decorate16([
3124
+ (0, import_common45.Injectable)(),
3161
3125
  _ts_metadata10("design:type", Function),
3162
3126
  _ts_metadata10("design:paramtypes", [
3163
3127
  typeof CorrelationIdMiddlewareOptions === "undefined" ? Object : CorrelationIdMiddlewareOptions
@@ -3165,13 +3129,13 @@ CorrelationIdMiddleware = _ts_decorate15([
3165
3129
  ], CorrelationIdMiddleware);
3166
3130
 
3167
3131
  // src/logger/logger.module.ts
3168
- function _ts_decorate16(decorators, target, key, desc) {
3132
+ function _ts_decorate17(decorators, target, key, desc) {
3169
3133
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3170
3134
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
3171
3135
  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;
3172
3136
  return c > 3 && r && Object.defineProperty(target, key, r), r;
3173
3137
  }
3174
- __name(_ts_decorate16, "_ts_decorate");
3138
+ __name(_ts_decorate17, "_ts_decorate");
3175
3139
  var LOGGER_MODULE_OPTIONS = Symbol("LOGGER_MODULE_OPTIONS");
3176
3140
  var DEFAULT_LOGGER_OPTIONS = {
3177
3141
  provider: "winston",
@@ -3181,9 +3145,6 @@ var DEFAULT_LOGGER_OPTIONS = {
3181
3145
  maxFiles: "14d"
3182
3146
  };
3183
3147
  var ENVIRONMENT_PRESETS = {
3184
- /**
3185
- * Development preset - maximum verbosity for local development
3186
- */
3187
3148
  development: {
3188
3149
  provider: "winston",
3189
3150
  level: "debug",
@@ -3197,9 +3158,6 @@ var ENVIRONMENT_PRESETS = {
3197
3158
  slowRequestThreshold: 1e3
3198
3159
  }
3199
3160
  },
3200
- /**
3201
- * Staging preset - moderate verbosity with file logging
3202
- */
3203
3161
  staging: {
3204
3162
  provider: "winston",
3205
3163
  level: "log",
@@ -3213,9 +3171,6 @@ var ENVIRONMENT_PRESETS = {
3213
3171
  slowRequestThreshold: 3e3
3214
3172
  }
3215
3173
  },
3216
- /**
3217
- * Production preset - minimal verbosity with all safety features enabled
3218
- */
3219
3174
  production: {
3220
3175
  provider: "winston",
3221
3176
  level: "warn",
@@ -3229,9 +3184,6 @@ var ENVIRONMENT_PRESETS = {
3229
3184
  slowRequestThreshold: 5e3
3230
3185
  }
3231
3186
  },
3232
- /**
3233
- * Test preset - errors only, minimal features for faster test execution
3234
- */
3235
3187
  test: {
3236
3188
  provider: "winston",
3237
3189
  level: "error",
@@ -3260,12 +3212,12 @@ function mergeWithDefaults(options = {}) {
3260
3212
  __name(mergeWithDefaults, "mergeWithDefaults");
3261
3213
  function createDefaultLoggerProvider(options) {
3262
3214
  return {
3263
- provide: import_common42.Logger,
3215
+ provide: import_common46.Logger,
3264
3216
  useFactory: /* @__PURE__ */ __name(() => {
3265
- const logger = new import_common42.Logger();
3266
- if (options.level && typeof logger.setLogLevels === "function") {
3217
+ const logger = new import_common46.Logger();
3218
+ if (options.level) {
3267
3219
  const levels = getLevelsUpTo(options.level);
3268
- logger.setLogLevels(levels);
3220
+ logger.setLogLevels?.(levels);
3269
3221
  }
3270
3222
  return logger;
3271
3223
  }, "useFactory")
@@ -3292,7 +3244,7 @@ function createLoggerProviders(options = {}) {
3292
3244
  inject: [
3293
3245
  LOGGER_MODULE_OPTIONS,
3294
3246
  {
3295
- token: import_common42.Logger,
3247
+ token: import_common46.Logger,
3296
3248
  optional: true
3297
3249
  }
3298
3250
  ]
@@ -3347,37 +3299,7 @@ var LoggerModule = class _LoggerModule {
3347
3299
  static {
3348
3300
  __name(this, "LoggerModule");
3349
3301
  }
3350
- /**
3351
- * Configures the logger module with static options.
3352
- *
3353
- * Users must explicitly pass `environment` to select a preset.
3354
- * All preset values can be overridden by passing explicit options.
3355
- *
3356
- * @param options - Logger configuration options
3357
- * @returns Dynamic module configuration
3358
- *
3359
- * @example
3360
- * ```typescript
3361
- * // Production preset with app name
3362
- * LoggerModule.forRoot({
3363
- * environment: 'production',
3364
- * appName: 'my-service'
3365
- * })
3366
- *
3367
- * // Development preset with custom level
3368
- * LoggerModule.forRoot({
3369
- * environment: 'development',
3370
- * level: 'verbose',
3371
- * enableFileLogger: true
3372
- * })
3373
- *
3374
- * // Use default NestJS logger
3375
- * LoggerModule.forRoot({
3376
- * provider: 'default',
3377
- * environment: 'development'
3378
- * })
3379
- * ```
3380
- */
3302
+ // Configures the logger module with static options and environment preset
3381
3303
  static forRoot(options = {}) {
3382
3304
  const providers = createLoggerProviders(options);
3383
3305
  return {
@@ -3391,49 +3313,7 @@ var LoggerModule = class _LoggerModule {
3391
3313
  ]
3392
3314
  };
3393
3315
  }
3394
- /**
3395
- * Configures the logger module with async options.
3396
- *
3397
- * Supports dynamic configuration using:
3398
- * - `useFactory`: Factory function with dependency injection
3399
- * - `useClass`: Class implementing `LoggerOptionsFactory`
3400
- * - `useExisting`: Existing provider implementing `LoggerOptionsFactory`
3401
- *
3402
- * Options from the factory/class are merged with environment preset defaults.
3403
- *
3404
- * @param options - Async configuration options
3405
- * @returns Dynamic module configuration
3406
- *
3407
- * @example
3408
- * ```typescript
3409
- * // Factory with ConfigService
3410
- * LoggerModule.forRootAsync({
3411
- * imports: [ConfigModule],
3412
- * useFactory: (config: ConfigService) => ({
3413
- * environment: config.get('NODE_ENV', 'development'),
3414
- * provider: config.get('LOG_PROVIDER', 'winston'),
3415
- * level: config.get('LOG_LEVEL'),
3416
- * appName: config.get('APP_NAME'),
3417
- * }),
3418
- * inject: [ConfigService]
3419
- * })
3420
- *
3421
- * // Factory class
3422
- * @Injectable()
3423
- * class LoggerConfigService implements LoggerOptionsFactory {
3424
- * createLoggerOptions(): LoggerModuleOptions {
3425
- * return {
3426
- * environment: 'production',
3427
- * appName: 'my-service'
3428
- * };
3429
- * }
3430
- * }
3431
- *
3432
- * LoggerModule.forRootAsync({
3433
- * useClass: LoggerConfigService
3434
- * })
3435
- * ```
3436
- */
3316
+ // Configures the logger module with async options (useFactory, useClass, useExisting)
3437
3317
  static forRootAsync(options) {
3438
3318
  const asyncProviders = _LoggerModule.createAsyncProviders(options);
3439
3319
  return {
@@ -3443,13 +3323,13 @@ var LoggerModule = class _LoggerModule {
3443
3323
  ...asyncProviders,
3444
3324
  // Default logger provider
3445
3325
  {
3446
- provide: import_common42.Logger,
3326
+ provide: import_common46.Logger,
3447
3327
  useFactory: /* @__PURE__ */ __name((opts) => {
3448
3328
  if (opts.provider === "default") {
3449
- const logger = new import_common42.Logger();
3450
- if (opts.level && typeof logger.setLogLevels === "function") {
3329
+ const logger = new import_common46.Logger();
3330
+ if (opts.level) {
3451
3331
  const levels = getLevelsUpTo(opts.level);
3452
- logger.setLogLevels(levels);
3332
+ logger.setLogLevels?.(levels);
3453
3333
  }
3454
3334
  return logger;
3455
3335
  }
@@ -3468,7 +3348,7 @@ var LoggerModule = class _LoggerModule {
3468
3348
  inject: [
3469
3349
  LOGGER_MODULE_OPTIONS,
3470
3350
  {
3471
- token: import_common42.Logger,
3351
+ token: import_common46.Logger,
3472
3352
  optional: true
3473
3353
  }
3474
3354
  ]
@@ -3507,15 +3387,10 @@ var LoggerModule = class _LoggerModule {
3507
3387
  ]
3508
3388
  };
3509
3389
  }
3510
- /**
3511
- * Configures middleware for the module.
3512
- * Middleware is registered globally in main.ts using Fastify hooks.
3513
- */
3390
+ // Middleware registration is handled globally in main.ts via Fastify hooks
3514
3391
  configure(_consumer) {
3515
3392
  }
3516
- /**
3517
- * Creates async providers for dynamic module configuration.
3518
- */
3393
+ // Creates async providers for dynamic module configuration
3519
3394
  static createAsyncProviders(options) {
3520
3395
  if (options.useFactory) {
3521
3396
  return [
@@ -3533,9 +3408,7 @@ var LoggerModule = class _LoggerModule {
3533
3408
  }
3534
3409
  return providers;
3535
3410
  }
3536
- /**
3537
- * Creates the async options provider.
3538
- */
3411
+ // Creates the DI provider that resolves and merges async logger options
3539
3412
  static createAsyncOptionsProvider(options) {
3540
3413
  if (options.useFactory) {
3541
3414
  return {
@@ -3574,12 +3447,413 @@ var LoggerModule = class _LoggerModule {
3574
3447
  throw new Error("LoggerModule.forRootAsync() requires one of: useFactory, useClass, or useExisting");
3575
3448
  }
3576
3449
  };
3577
- LoggerModule = _ts_decorate16([
3578
- (0, import_common42.Global)(),
3579
- (0, import_common42.Module)({})
3450
+ LoggerModule = _ts_decorate17([
3451
+ (0, import_common46.Global)(),
3452
+ (0, import_common46.Module)({})
3580
3453
  ], LoggerModule);
3454
+
3455
+ // src/root/root.module.ts
3456
+ var import_common52 = require("@nestjs/common");
3457
+
3458
+ // src/root/controllers/app.controller.ts
3459
+ var import_common49 = require("@nestjs/common");
3460
+ var import_swagger3 = require("@nestjs/swagger");
3461
+
3462
+ // src/root/docs/app.docs.ts
3463
+ var import_common47 = require("@nestjs/common");
3464
+ var import_swagger2 = require("@nestjs/swagger");
3465
+ function ApiHealthCheck() {
3466
+ return (0, import_common47.applyDecorators)((0, import_swagger2.ApiOperation)({
3467
+ summary: "Health check endpoint"
3468
+ }), (0, import_swagger2.ApiResponse)({
3469
+ status: 200,
3470
+ description: "Returns a welcome message indicating the API is running",
3471
+ type: String
3472
+ }));
3473
+ }
3474
+ __name(ApiHealthCheck, "ApiHealthCheck");
3475
+
3476
+ // src/root/services/app.service.ts
3477
+ var import_common48 = require("@nestjs/common");
3478
+ function _ts_decorate18(decorators, target, key, desc) {
3479
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3480
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
3481
+ 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;
3482
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
3483
+ }
3484
+ __name(_ts_decorate18, "_ts_decorate");
3485
+ var AppService = class {
3486
+ static {
3487
+ __name(this, "AppService");
3488
+ }
3489
+ // Returns the API welcome message
3490
+ getHello() {
3491
+ return `Hello World!`;
3492
+ }
3493
+ };
3494
+ AppService = _ts_decorate18([
3495
+ (0, import_common48.Injectable)()
3496
+ ], AppService);
3497
+
3498
+ // src/root/controllers/app.controller.ts
3499
+ function _ts_decorate19(decorators, target, key, desc) {
3500
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3501
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
3502
+ 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;
3503
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
3504
+ }
3505
+ __name(_ts_decorate19, "_ts_decorate");
3506
+ function _ts_metadata11(k, v) {
3507
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
3508
+ }
3509
+ __name(_ts_metadata11, "_ts_metadata");
3510
+ var AppController = class {
3511
+ static {
3512
+ __name(this, "AppController");
3513
+ }
3514
+ appService;
3515
+ constructor(appService) {
3516
+ this.appService = appService;
3517
+ }
3518
+ // Returns a welcome message indicating the API is running
3519
+ getHello() {
3520
+ return this.appService.getHello();
3521
+ }
3522
+ };
3523
+ _ts_decorate19([
3524
+ (0, import_common49.Get)(),
3525
+ Public(),
3526
+ ApiHealthCheck(),
3527
+ _ts_metadata11("design:type", Function),
3528
+ _ts_metadata11("design:paramtypes", []),
3529
+ _ts_metadata11("design:returntype", String)
3530
+ ], AppController.prototype, "getHello", null);
3531
+ AppController = _ts_decorate19([
3532
+ (0, import_swagger3.ApiTags)("Health"),
3533
+ (0, import_common49.Controller)(),
3534
+ _ts_metadata11("design:type", Function),
3535
+ _ts_metadata11("design:paramtypes", [
3536
+ typeof AppService === "undefined" ? Object : AppService
3537
+ ])
3538
+ ], AppController);
3539
+
3540
+ // src/root/controllers/csrf.controller.ts
3541
+ var import_common51 = require("@nestjs/common");
3542
+ var import_swagger5 = require("@nestjs/swagger");
3543
+
3544
+ // src/root/docs/csrf.docs.ts
3545
+ var import_common50 = require("@nestjs/common");
3546
+ var import_swagger4 = require("@nestjs/swagger");
3547
+ function ApiGetCsrfToken() {
3548
+ return (0, import_common50.applyDecorators)((0, import_swagger4.ApiOperation)({
3549
+ summary: "Get CSRF token",
3550
+ description: "Generates and returns a CSRF token that must be included in all state-changing requests (POST, PUT, PATCH, DELETE). The token should be sent in the X-CSRF-Token header."
3551
+ }), (0, import_swagger4.ApiResponse)({
3552
+ status: 200,
3553
+ description: "CSRF token generated successfully",
3554
+ schema: {
3555
+ type: "object",
3556
+ properties: {
3557
+ csrfToken: {
3558
+ type: "string",
3559
+ description: "The CSRF token to use in subsequent requests",
3560
+ example: "abc123xyz789"
3561
+ }
3562
+ },
3563
+ required: [
3564
+ "csrfToken"
3565
+ ]
3566
+ }
3567
+ }));
3568
+ }
3569
+ __name(ApiGetCsrfToken, "ApiGetCsrfToken");
3570
+
3571
+ // src/root/controllers/csrf.controller.ts
3572
+ function _ts_decorate20(decorators, target, key, desc) {
3573
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3574
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
3575
+ 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;
3576
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
3577
+ }
3578
+ __name(_ts_decorate20, "_ts_decorate");
3579
+ function _ts_metadata12(k, v) {
3580
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
3581
+ }
3582
+ __name(_ts_metadata12, "_ts_metadata");
3583
+ function _ts_param6(paramIndex, decorator) {
3584
+ return function(target, key) {
3585
+ decorator(target, key, paramIndex);
3586
+ };
3587
+ }
3588
+ __name(_ts_param6, "_ts_param");
3589
+ var CsrfController = class {
3590
+ static {
3591
+ __name(this, "CsrfController");
3592
+ }
3593
+ // Generates a CSRF token via Fastify's csrf-protection plugin
3594
+ getToken(reply) {
3595
+ const csrfToken = reply.generateCsrf();
3596
+ return {
3597
+ csrfToken
3598
+ };
3599
+ }
3600
+ };
3601
+ _ts_decorate20([
3602
+ (0, import_common51.Get)("token"),
3603
+ Public(),
3604
+ (0, import_common51.HttpCode)(import_common51.HttpStatus.OK),
3605
+ ApiGetCsrfToken(),
3606
+ _ts_param6(0, (0, import_common51.Res)({
3607
+ passthrough: true
3608
+ })),
3609
+ _ts_metadata12("design:type", Function),
3610
+ _ts_metadata12("design:paramtypes", [
3611
+ typeof FastifyReply === "undefined" ? Object : FastifyReply
3612
+ ]),
3613
+ _ts_metadata12("design:returntype", Object)
3614
+ ], CsrfController.prototype, "getToken", null);
3615
+ CsrfController = _ts_decorate20([
3616
+ (0, import_swagger5.ApiTags)("CSRF"),
3617
+ (0, import_common51.Controller)("csrf")
3618
+ ], CsrfController);
3619
+
3620
+ // src/root/root.module.ts
3621
+ function _ts_decorate21(decorators, target, key, desc) {
3622
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3623
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
3624
+ 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;
3625
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
3626
+ }
3627
+ __name(_ts_decorate21, "_ts_decorate");
3628
+ var RootModule = class {
3629
+ static {
3630
+ __name(this, "RootModule");
3631
+ }
3632
+ };
3633
+ RootModule = _ts_decorate21([
3634
+ (0, import_common52.Module)({
3635
+ controllers: [
3636
+ AppController,
3637
+ CsrfController
3638
+ ],
3639
+ providers: [
3640
+ AppService
3641
+ ]
3642
+ })
3643
+ ], RootModule);
3644
+
3645
+ // src/utils/phone.utils.ts
3646
+ var CALLING_CODE_TO_COUNTRY = {
3647
+ // 3-digit codes
3648
+ "355": "AL",
3649
+ "213": "DZ",
3650
+ "376": "AD",
3651
+ "244": "AO",
3652
+ "672": "AQ",
3653
+ "374": "AM",
3654
+ "297": "AW",
3655
+ "994": "AZ",
3656
+ "973": "BH",
3657
+ "880": "BD",
3658
+ "375": "BY",
3659
+ "501": "BZ",
3660
+ "229": "BJ",
3661
+ "975": "BT",
3662
+ "591": "BO",
3663
+ "387": "BA",
3664
+ "267": "BW",
3665
+ "673": "BN",
3666
+ "359": "BG",
3667
+ "226": "BF",
3668
+ "257": "BI",
3669
+ "855": "KH",
3670
+ "237": "CM",
3671
+ "238": "CV",
3672
+ "236": "CF",
3673
+ "235": "TD",
3674
+ "269": "KM",
3675
+ "242": "CG",
3676
+ "243": "CD",
3677
+ "506": "CR",
3678
+ "385": "HR",
3679
+ "357": "CY",
3680
+ "420": "CZ",
3681
+ "253": "DJ",
3682
+ "593": "EC",
3683
+ "503": "SV",
3684
+ "240": "GQ",
3685
+ "291": "ER",
3686
+ "372": "EE",
3687
+ "251": "ET",
3688
+ "679": "FJ",
3689
+ "358": "FI",
3690
+ "241": "GA",
3691
+ "220": "GM",
3692
+ "995": "GE",
3693
+ "233": "GH",
3694
+ "350": "GI",
3695
+ "299": "GL",
3696
+ "502": "GT",
3697
+ "224": "GN",
3698
+ "245": "GW",
3699
+ "592": "GY",
3700
+ "509": "HT",
3701
+ "504": "HN",
3702
+ "354": "IS",
3703
+ "964": "IQ",
3704
+ "353": "IE",
3705
+ "972": "IL",
3706
+ "225": "CI",
3707
+ "962": "JO",
3708
+ "254": "KE",
3709
+ "686": "KI",
3710
+ "965": "KW",
3711
+ "996": "KG",
3712
+ "856": "LA",
3713
+ "371": "LV",
3714
+ "961": "LB",
3715
+ "266": "LS",
3716
+ "231": "LR",
3717
+ "218": "LY",
3718
+ "423": "LI",
3719
+ "370": "LT",
3720
+ "352": "LU",
3721
+ "389": "MK",
3722
+ "261": "MG",
3723
+ "265": "MW",
3724
+ "960": "MV",
3725
+ "223": "ML",
3726
+ "356": "MT",
3727
+ "692": "MH",
3728
+ "222": "MR",
3729
+ "230": "MU",
3730
+ "262": "YT",
3731
+ "691": "FM",
3732
+ "373": "MD",
3733
+ "377": "MC",
3734
+ "976": "MN",
3735
+ "382": "ME",
3736
+ "258": "MZ",
3737
+ "264": "NA",
3738
+ "674": "NR",
3739
+ "977": "NP",
3740
+ "505": "NI",
3741
+ "227": "NE",
3742
+ "234": "NG",
3743
+ "683": "NU",
3744
+ "968": "OM",
3745
+ "680": "PW",
3746
+ "970": "PS",
3747
+ "507": "PA",
3748
+ "675": "PG",
3749
+ "595": "PY",
3750
+ "351": "PT",
3751
+ "974": "QA",
3752
+ "250": "RW",
3753
+ "685": "WS",
3754
+ "378": "SM",
3755
+ "239": "ST",
3756
+ "966": "SA",
3757
+ "221": "SN",
3758
+ "381": "RS",
3759
+ "248": "SC",
3760
+ "232": "SL",
3761
+ "421": "SK",
3762
+ "386": "SI",
3763
+ "677": "SB",
3764
+ "252": "SO",
3765
+ "211": "SS",
3766
+ "249": "SD",
3767
+ "597": "SR",
3768
+ "268": "SZ",
3769
+ "963": "SY",
3770
+ "992": "TJ",
3771
+ "255": "TZ",
3772
+ "228": "TG",
3773
+ "676": "TO",
3774
+ "216": "TN",
3775
+ "993": "TM",
3776
+ "688": "TV",
3777
+ "256": "UG",
3778
+ "380": "UA",
3779
+ "971": "AE",
3780
+ "598": "UY",
3781
+ "998": "UZ",
3782
+ "678": "VU",
3783
+ "379": "VA",
3784
+ "967": "YE",
3785
+ "260": "ZM",
3786
+ "263": "ZW",
3787
+ // 2-digit codes
3788
+ "93": "AF",
3789
+ "54": "AR",
3790
+ "61": "AU",
3791
+ "43": "AT",
3792
+ "32": "BE",
3793
+ "55": "BR",
3794
+ "56": "CL",
3795
+ "86": "CN",
3796
+ "57": "CO",
3797
+ "53": "CU",
3798
+ "45": "DK",
3799
+ "20": "EG",
3800
+ "33": "FR",
3801
+ "49": "DE",
3802
+ "30": "GR",
3803
+ "36": "HU",
3804
+ "91": "IN",
3805
+ "62": "ID",
3806
+ "98": "IR",
3807
+ "39": "IT",
3808
+ "81": "JP",
3809
+ "82": "KR",
3810
+ "60": "MY",
3811
+ "52": "MX",
3812
+ "31": "NL",
3813
+ "64": "NZ",
3814
+ "47": "NO",
3815
+ "92": "PK",
3816
+ "51": "PE",
3817
+ "63": "PH",
3818
+ "48": "PL",
3819
+ "40": "RO",
3820
+ "65": "SG",
3821
+ "27": "ZA",
3822
+ "34": "ES",
3823
+ "94": "LK",
3824
+ "46": "SE",
3825
+ "41": "CH",
3826
+ "66": "TH",
3827
+ "90": "TR",
3828
+ "44": "GB",
3829
+ "58": "VE",
3830
+ "84": "VN",
3831
+ // 1-digit codes (shared codes default to most common country)
3832
+ "1": "US",
3833
+ "7": "RU"
3834
+ };
3835
+ function extractCountryFromPhone(phone) {
3836
+ const digits = phone.startsWith("+") ? phone.slice(1) : phone;
3837
+ for (const length of [
3838
+ 3,
3839
+ 2,
3840
+ 1
3841
+ ]) {
3842
+ const prefix = digits.slice(0, length);
3843
+ if (CALLING_CODE_TO_COUNTRY[prefix]) {
3844
+ return CALLING_CODE_TO_COUNTRY[prefix];
3845
+ }
3846
+ }
3847
+ return void 0;
3848
+ }
3849
+ __name(extractCountryFromPhone, "extractCountryFromPhone");
3850
+ function normalizePhoneNumber(phone) {
3851
+ return phone.startsWith("+") ? phone : `+${phone}`;
3852
+ }
3853
+ __name(normalizePhoneNumber, "normalizePhoneNumber");
3581
3854
  // Annotate the CommonJS export names for ESM import in node:
3582
3855
  0 && (module.exports = {
3856
+ AccessToken,
3583
3857
  AuthConfigModule,
3584
3858
  BadGatewayException,
3585
3859
  BadRequestException,
@@ -3587,12 +3861,15 @@ LoggerModule = _ts_decorate16([
3587
3861
  CorrelationIdMiddleware,
3588
3862
  DEFAULT_CORRELATION_HEADER,
3589
3863
  DatabaseModule,
3864
+ EmailModule,
3865
+ EmailService,
3590
3866
  ForbiddenException,
3591
3867
  GoneException,
3592
3868
  HttpExceptionFilter,
3593
3869
  HttpLoggerInterceptor,
3594
3870
  HttpProblemException,
3595
3871
  InternalServerErrorException,
3872
+ JwtAuthService,
3596
3873
  LOGGER_MODULE_OPTIONS,
3597
3874
  LoggerModule,
3598
3875
  LoggerService,
@@ -3605,15 +3882,21 @@ LoggerModule = _ts_decorate16([
3605
3882
  PrimaryBaseRepository,
3606
3883
  PrimaryDatabaseService,
3607
3884
  Public,
3885
+ RESET_KEY,
3886
+ RefreshTokenCookie,
3608
3887
  RequestTimeoutException,
3888
+ Reset,
3889
+ RootModule,
3609
3890
  SKIP_CSRF_KEY,
3891
+ SelectOptionsQueryDto,
3610
3892
  ServiceUnavailableException,
3893
+ SessionData,
3611
3894
  SkipCsrf,
3612
- SseAuthGuard,
3613
3895
  Tenant,
3614
3896
  TenantBaseRepository,
3615
3897
  TenantContextService,
3616
3898
  TenantDatabaseService,
3899
+ TokenType,
3617
3900
  TooManyRequestsException,
3618
3901
  UnauthorizedException,
3619
3902
  UnprocessableEntityException,
@@ -3632,8 +3915,11 @@ LoggerModule = _ts_decorate16([
3632
3915
  getHttpStatusTitle,
3633
3916
  getJwtExpiry,
3634
3917
  getRefreshCookieOptions,
3918
+ getTokenExpiry,
3635
3919
  hashToken,
3920
+ jwtConfigFactory,
3636
3921
  normalizePhoneNumber,
3922
+ parseExpiryToMs,
3637
3923
  resetConfig,
3638
3924
  runWithCorrelationContext,
3639
3925
  updateCorrelationContext,