@vritti/api-sdk 0.1.8 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +2044 -1758
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +214 -2277
- package/dist/index.d.ts +214 -2277
- package/dist/index.js +1998 -1725
- package/dist/index.js.map +1 -1
- package/package.json +24 -6
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: () =>
|
|
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
|
|
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
|
|
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/
|
|
310
|
+
// src/auth/decorators/reset.decorator.ts
|
|
317
311
|
var import_common3 = require("@nestjs/common");
|
|
318
|
-
var
|
|
319
|
-
var
|
|
312
|
+
var RESET_KEY = "isReset";
|
|
313
|
+
var Reset = /* @__PURE__ */ __name(() => (0, import_common3.SetMetadata)(RESET_KEY, true), "Reset");
|
|
320
314
|
|
|
321
|
-
// src/
|
|
315
|
+
// src/auth/decorators/skip-csrf.decorator.ts
|
|
322
316
|
var import_common4 = require("@nestjs/common");
|
|
323
|
-
var
|
|
324
|
-
var
|
|
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
|
|
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(
|
|
579
|
-
function
|
|
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(
|
|
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,
|
|
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
|
|
629
|
-
if (
|
|
630
|
-
|
|
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.
|
|
664
|
-
if (
|
|
665
|
-
|
|
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
|
-
|
|
669
|
-
|
|
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 (
|
|
674
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
725
|
-
|
|
726
|
-
|
|
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
|
|
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,
|
|
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
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
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
|
-
|
|
775
|
-
|
|
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
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
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 =
|
|
519
|
+
VrittiAuthGuard = _ts_decorate3([
|
|
803
520
|
(0, import_common5.Injectable)({
|
|
804
521
|
scope: import_common5.Scope.REQUEST
|
|
805
522
|
}),
|
|
806
|
-
|
|
807
|
-
|
|
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
|
|
817
|
-
|
|
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(
|
|
824
|
-
|
|
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, "
|
|
585
|
+
__name(this, "JwtAuthService");
|
|
827
586
|
}
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
return {
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
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
|
-
|
|
701
|
+
import_jwt4.JwtModule,
|
|
702
|
+
JwtAuthService
|
|
872
703
|
]
|
|
873
704
|
};
|
|
874
705
|
}
|
|
875
706
|
};
|
|
876
707
|
AuthConfigModule = _ts_decorate5([
|
|
877
|
-
(0,
|
|
878
|
-
(0,
|
|
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
|
|
883
|
-
var Onboarding = /* @__PURE__ */ __name(() => (0,
|
|
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
|
|
887
|
-
var Public = /* @__PURE__ */ __name(() => (0,
|
|
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
|
|
891
|
-
var UserId = (0,
|
|
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
|
|
894
|
-
if (!
|
|
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
|
|
760
|
+
return sessionInfo.userId;
|
|
898
761
|
});
|
|
899
762
|
|
|
900
|
-
// src/
|
|
901
|
-
var
|
|
902
|
-
var
|
|
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
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
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, "
|
|
794
|
+
__name(this, "PrimaryDatabaseService");
|
|
927
795
|
}
|
|
928
|
-
|
|
929
|
-
logger = new
|
|
930
|
-
|
|
931
|
-
|
|
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
|
|
934
|
-
|
|
935
|
-
|
|
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
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
}
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
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
|
-
|
|
960
|
-
|
|
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
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
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
|
-
|
|
968
|
-
|
|
969
|
-
|
|
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
|
-
|
|
972
|
-
|
|
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
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
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
|
-
|
|
991
|
-
(0,
|
|
992
|
-
|
|
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
|
|
958
|
+
typeof DatabaseModuleOptions === "undefined" ? Object : DatabaseModuleOptions
|
|
997
959
|
])
|
|
998
|
-
],
|
|
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
|
|
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
|
|
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,
|
|
1090
|
-
scope:
|
|
1008
|
+
(0, import_common15.Injectable)({
|
|
1009
|
+
scope: import_common15.Scope.REQUEST
|
|
1091
1010
|
})
|
|
1092
1011
|
], TenantContextService);
|
|
1093
1012
|
|
|
1094
|
-
// src/database/
|
|
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
|
-
|
|
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, "
|
|
1036
|
+
__name(this, "TenantDatabaseService");
|
|
1109
1037
|
}
|
|
1038
|
+
options;
|
|
1110
1039
|
tenantContext;
|
|
1111
|
-
logger = new
|
|
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
|
|
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 =
|
|
1446
|
-
(0,
|
|
1447
|
-
_ts_param3(0, (0,
|
|
1448
|
-
|
|
1449
|
-
|
|
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
|
|
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(
|
|
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:
|
|
1536
|
-
useClass:
|
|
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 =
|
|
1570
|
-
(0,
|
|
1571
|
-
(0,
|
|
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
|
|
1576
|
-
var Tenant = (0,
|
|
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
|
|
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
|
|
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
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
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
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
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
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
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
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
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
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
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
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
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/
|
|
2209
|
-
var
|
|
2210
|
-
|
|
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/
|
|
1822
|
+
// src/email/email.service.ts
|
|
1823
|
+
var import_brevo = require("@getbrevo/brevo");
|
|
2219
1824
|
var import_common21 = require("@nestjs/common");
|
|
2220
|
-
var
|
|
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, "
|
|
2223
|
-
}
|
|
2224
|
-
|
|
2225
|
-
|
|
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
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
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
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
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
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
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
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
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
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
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/
|
|
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",
|
|
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
|
|
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",
|
|
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
|
|
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",
|
|
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
|
|
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",
|
|
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
|
|
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",
|
|
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
|
|
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",
|
|
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
|
|
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",
|
|
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
|
|
2363
|
-
var
|
|
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",
|
|
2557
|
+
super(detailOrOptions ?? "Unauthorized", import_common38.HttpStatus.UNAUTHORIZED);
|
|
2369
2558
|
}
|
|
2370
2559
|
};
|
|
2371
2560
|
|
|
2372
2561
|
// src/exceptions/unprocessable-entity.exception.ts
|
|
2373
|
-
var
|
|
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",
|
|
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
|
|
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",
|
|
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
|
|
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",
|
|
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
|
|
2407
|
-
function
|
|
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(
|
|
2602
|
+
__name(_ts_decorate13, "_ts_decorate");
|
|
2414
2603
|
function getHttpStatusTitle(status) {
|
|
2415
|
-
const enumKey = Object.entries(
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
2487
|
-
(0,
|
|
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
|
|
2702
|
-
var
|
|
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
|
|
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
|
|
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(
|
|
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 =
|
|
2962
|
-
(0,
|
|
2963
|
-
_ts_param4(0, (0,
|
|
2964
|
-
_ts_param4(1, (0,
|
|
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
|
|
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(
|
|
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,
|
|
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,
|
|
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:
|
|
3076
|
-
errorMessage:
|
|
3045
|
+
errorName: err.name || "Error",
|
|
3046
|
+
errorMessage: err.message || "Unknown error"
|
|
3077
3047
|
};
|
|
3078
|
-
if (
|
|
3079
|
-
metadata.trace =
|
|
3048
|
+
if (err.stack) {
|
|
3049
|
+
metadata.trace = err.stack;
|
|
3080
3050
|
}
|
|
3081
|
-
if (
|
|
3082
|
-
metadata.errorDetails =
|
|
3051
|
+
if (err.response) {
|
|
3052
|
+
metadata.errorDetails = err.response;
|
|
3083
3053
|
}
|
|
3084
|
-
const message = `ERROR ${request.method} ${request.url} ${statusCode} - ${
|
|
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 =
|
|
3092
|
-
(0,
|
|
3093
|
-
_ts_param5(1, (0,
|
|
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
|
|
3072
|
+
var import_common46 = require("@nestjs/common");
|
|
3103
3073
|
|
|
3104
3074
|
// src/logger/middleware/correlation-id.middleware.ts
|
|
3105
|
-
var
|
|
3106
|
-
function
|
|
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(
|
|
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 =
|
|
3160
|
-
(0,
|
|
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
|
|
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(
|
|
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:
|
|
3215
|
+
provide: import_common46.Logger,
|
|
3264
3216
|
useFactory: /* @__PURE__ */ __name(() => {
|
|
3265
|
-
const logger = new
|
|
3266
|
-
if (options.level
|
|
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:
|
|
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:
|
|
3326
|
+
provide: import_common46.Logger,
|
|
3447
3327
|
useFactory: /* @__PURE__ */ __name((opts) => {
|
|
3448
3328
|
if (opts.provider === "default") {
|
|
3449
|
-
const logger = new
|
|
3450
|
-
if (opts.level
|
|
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:
|
|
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 =
|
|
3578
|
-
(0,
|
|
3579
|
-
(0,
|
|
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,
|