@vritti/api-sdk 0.0.2 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +617 -43
- package/dist/index.cjs +1503 -312
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +963 -169
- package/dist/index.d.ts +963 -169
- package/dist/index.js +1484 -309
- package/dist/index.js.map +1 -1
- package/package.json +8 -1
package/dist/index.js
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
var __defProp = Object.defineProperty;
|
|
2
2
|
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
3
3
|
|
|
4
|
-
// src/
|
|
5
|
-
import { Global, Module } from "@nestjs/common";
|
|
4
|
+
// src/auth/auth-config.module.ts
|
|
5
|
+
import { Global as Global2, Module as Module2 } from "@nestjs/common";
|
|
6
|
+
import { ConfigModule, ConfigService as ConfigService2 } from "@nestjs/config";
|
|
7
|
+
import { APP_GUARD } from "@nestjs/core";
|
|
8
|
+
import { JwtModule } from "@nestjs/jwt";
|
|
6
9
|
|
|
7
|
-
// src/
|
|
8
|
-
|
|
10
|
+
// src/request/request.module.ts
|
|
11
|
+
import { Global, Module } from "@nestjs/common";
|
|
9
12
|
|
|
10
|
-
// src/
|
|
11
|
-
import { Inject, Injectable,
|
|
13
|
+
// src/request/services/request.service.ts
|
|
14
|
+
import { Inject, Injectable, Scope } from "@nestjs/common";
|
|
15
|
+
import { REQUEST } from "@nestjs/core";
|
|
12
16
|
function _ts_decorate(decorators, target, key, desc) {
|
|
13
17
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
14
18
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
@@ -26,6 +30,141 @@ function _ts_param(paramIndex, decorator) {
|
|
|
26
30
|
};
|
|
27
31
|
}
|
|
28
32
|
__name(_ts_param, "_ts_param");
|
|
33
|
+
var RequestService = class {
|
|
34
|
+
static {
|
|
35
|
+
__name(this, "RequestService");
|
|
36
|
+
}
|
|
37
|
+
request;
|
|
38
|
+
constructor(request) {
|
|
39
|
+
this.request = request;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Extract tenant identifier from request headers
|
|
43
|
+
* Priority: x-tenant-id > x-subdomain
|
|
44
|
+
* @returns Tenant identifier or null if not found
|
|
45
|
+
*/
|
|
46
|
+
getTenantIdentifier() {
|
|
47
|
+
const getHeader = /* @__PURE__ */ __name((key) => {
|
|
48
|
+
const value = this.request.headers?.[key];
|
|
49
|
+
return Array.isArray(value) ? value[0] : value;
|
|
50
|
+
}, "getHeader");
|
|
51
|
+
return getHeader("x-tenant-id") || getHeader("x-subdomain") || null;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Extract access token from Authorization header
|
|
55
|
+
* Format: "Bearer <token>"
|
|
56
|
+
* @returns Access token or null if not found
|
|
57
|
+
*/
|
|
58
|
+
getAccessToken() {
|
|
59
|
+
const authHeader = this.request.headers?.authorization;
|
|
60
|
+
if (!authHeader) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
const [type, token] = authHeader.split(" ") ?? [];
|
|
64
|
+
return type === "Bearer" && token ? token : null;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Extract refresh token from session-id cookie
|
|
68
|
+
* Cookie name: session-id
|
|
69
|
+
* @returns Refresh token or null if not found
|
|
70
|
+
*/
|
|
71
|
+
getRefreshToken() {
|
|
72
|
+
try {
|
|
73
|
+
const cookies = this.request.cookies;
|
|
74
|
+
if (cookies && typeof cookies === "object") {
|
|
75
|
+
const sessionId = cookies["session-id"];
|
|
76
|
+
if (sessionId) {
|
|
77
|
+
return sessionId;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return null;
|
|
81
|
+
} catch (error) {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Get a specific header value
|
|
87
|
+
* @param key Header key
|
|
88
|
+
* @returns Header value (string, array, or undefined)
|
|
89
|
+
*/
|
|
90
|
+
getHeader(key) {
|
|
91
|
+
return this.request.headers?.[key];
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Get all headers
|
|
95
|
+
* @returns Record of all headers
|
|
96
|
+
*/
|
|
97
|
+
getAllHeaders() {
|
|
98
|
+
return this.request.headers || {};
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
RequestService = _ts_decorate([
|
|
102
|
+
Injectable({
|
|
103
|
+
scope: Scope.REQUEST
|
|
104
|
+
}),
|
|
105
|
+
_ts_param(0, Inject(REQUEST)),
|
|
106
|
+
_ts_metadata("design:type", Function),
|
|
107
|
+
_ts_metadata("design:paramtypes", [
|
|
108
|
+
typeof FastifyRequest === "undefined" ? Object : FastifyRequest
|
|
109
|
+
])
|
|
110
|
+
], RequestService);
|
|
111
|
+
|
|
112
|
+
// src/request/request.module.ts
|
|
113
|
+
function _ts_decorate2(decorators, target, key, desc) {
|
|
114
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
115
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
116
|
+
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;
|
|
117
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
118
|
+
}
|
|
119
|
+
__name(_ts_decorate2, "_ts_decorate");
|
|
120
|
+
var RequestModule = class {
|
|
121
|
+
static {
|
|
122
|
+
__name(this, "RequestModule");
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
RequestModule = _ts_decorate2([
|
|
126
|
+
Global(),
|
|
127
|
+
Module({
|
|
128
|
+
providers: [
|
|
129
|
+
RequestService
|
|
130
|
+
],
|
|
131
|
+
exports: [
|
|
132
|
+
RequestService
|
|
133
|
+
]
|
|
134
|
+
})
|
|
135
|
+
], RequestModule);
|
|
136
|
+
|
|
137
|
+
// src/auth/guards/vritti-auth.guard.ts
|
|
138
|
+
import { Injectable as Injectable3, Logger as Logger2, Scope as Scope2, UnauthorizedException } from "@nestjs/common";
|
|
139
|
+
import { ConfigService } from "@nestjs/config";
|
|
140
|
+
import { Reflector } from "@nestjs/core";
|
|
141
|
+
import { JwtService } from "@nestjs/jwt";
|
|
142
|
+
import * as jwt from "jsonwebtoken";
|
|
143
|
+
|
|
144
|
+
// src/database/services/primary-database.service.ts
|
|
145
|
+
import { Inject as Inject2, Injectable as Injectable2, InternalServerErrorException, Logger } from "@nestjs/common";
|
|
146
|
+
|
|
147
|
+
// src/database/constants.ts
|
|
148
|
+
var DATABASE_MODULE_OPTIONS = Symbol("DATABASE_MODULE_OPTIONS");
|
|
149
|
+
|
|
150
|
+
// src/database/services/primary-database.service.ts
|
|
151
|
+
function _ts_decorate3(decorators, target, key, desc) {
|
|
152
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
153
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
154
|
+
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;
|
|
155
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
156
|
+
}
|
|
157
|
+
__name(_ts_decorate3, "_ts_decorate");
|
|
158
|
+
function _ts_metadata2(k, v) {
|
|
159
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
160
|
+
}
|
|
161
|
+
__name(_ts_metadata2, "_ts_metadata");
|
|
162
|
+
function _ts_param2(paramIndex, decorator) {
|
|
163
|
+
return function(target, key) {
|
|
164
|
+
decorator(target, key, paramIndex);
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
__name(_ts_param2, "_ts_param");
|
|
29
168
|
var PrimaryDatabaseService = class _PrimaryDatabaseService {
|
|
30
169
|
static {
|
|
31
170
|
__name(this, "PrimaryDatabaseService");
|
|
@@ -123,10 +262,13 @@ var PrimaryDatabaseService = class _PrimaryDatabaseService {
|
|
|
123
262
|
id: tenantIdentifier
|
|
124
263
|
},
|
|
125
264
|
{
|
|
126
|
-
|
|
265
|
+
subdomain: tenantIdentifier
|
|
127
266
|
}
|
|
128
267
|
],
|
|
129
268
|
status: "ACTIVE"
|
|
269
|
+
},
|
|
270
|
+
include: {
|
|
271
|
+
databaseConfig: true
|
|
130
272
|
}
|
|
131
273
|
});
|
|
132
274
|
if (!tenant) {
|
|
@@ -136,16 +278,18 @@ var PrimaryDatabaseService = class _PrimaryDatabaseService {
|
|
|
136
278
|
const info = {
|
|
137
279
|
id: tenant.id,
|
|
138
280
|
subdomain: tenant.subdomain,
|
|
139
|
-
type: tenant.
|
|
281
|
+
type: tenant.dbType,
|
|
140
282
|
status: tenant.status,
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
283
|
+
// For SHARED tenants: schema name
|
|
284
|
+
schemaName: tenant.databaseConfig?.dbSchema || void 0,
|
|
285
|
+
// For DEDICATED tenants: database configuration from TenantDatabaseConfig table
|
|
286
|
+
databaseName: tenant.databaseConfig?.dbName || void 0,
|
|
287
|
+
databaseHost: tenant.databaseConfig?.dbHost || void 0,
|
|
288
|
+
databasePort: tenant.databaseConfig?.dbPort || void 0,
|
|
289
|
+
databaseUsername: tenant.databaseConfig?.dbUsername ? this.decrypt(tenant.databaseConfig.dbUsername) : void 0,
|
|
290
|
+
databasePassword: tenant.databaseConfig?.dbPassword ? this.decrypt(tenant.databaseConfig.dbPassword) : void 0,
|
|
291
|
+
databaseSslMode: tenant.databaseConfig?.dbSslMode || void 0,
|
|
292
|
+
connectionPoolSize: tenant.databaseConfig?.connectionPoolSize || void 0
|
|
149
293
|
};
|
|
150
294
|
this.cacheInfo(info);
|
|
151
295
|
return info;
|
|
@@ -190,16 +334,15 @@ var PrimaryDatabaseService = class _PrimaryDatabaseService {
|
|
|
190
334
|
this.logger.log(`Cleared ${size} cached tenant configs`);
|
|
191
335
|
}
|
|
192
336
|
/**
|
|
193
|
-
* Get
|
|
194
|
-
*
|
|
195
|
-
* This is useful for platform admin operations (creating tenants, billing, etc.)
|
|
337
|
+
* Get the Prisma client for the primary database.
|
|
338
|
+
* This is a synchronous property that returns the initialized Prisma client.
|
|
196
339
|
*
|
|
197
340
|
* @returns Primary database client instance
|
|
198
341
|
* @throws Error if primary database client is not initialized
|
|
199
342
|
*/
|
|
200
|
-
|
|
343
|
+
get prismaClient() {
|
|
201
344
|
if (!this.primaryDbClient) {
|
|
202
|
-
throw new Error("Primary database client not initialized
|
|
345
|
+
throw new Error("Primary database client not initialized");
|
|
203
346
|
}
|
|
204
347
|
return this.primaryDbClient;
|
|
205
348
|
}
|
|
@@ -221,24 +364,312 @@ var PrimaryDatabaseService = class _PrimaryDatabaseService {
|
|
|
221
364
|
}
|
|
222
365
|
}
|
|
223
366
|
};
|
|
224
|
-
PrimaryDatabaseService =
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
367
|
+
PrimaryDatabaseService = _ts_decorate3([
|
|
368
|
+
Injectable2(),
|
|
369
|
+
_ts_param2(0, Inject2(DATABASE_MODULE_OPTIONS)),
|
|
370
|
+
_ts_metadata2("design:type", Function),
|
|
371
|
+
_ts_metadata2("design:paramtypes", [
|
|
229
372
|
typeof DatabaseModuleOptions === "undefined" ? Object : DatabaseModuleOptions
|
|
230
373
|
])
|
|
231
374
|
], PrimaryDatabaseService);
|
|
232
375
|
|
|
376
|
+
// src/auth/guards/vritti-auth.guard.ts
|
|
377
|
+
function _ts_decorate4(decorators, target, key, desc) {
|
|
378
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
379
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
380
|
+
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;
|
|
381
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
382
|
+
}
|
|
383
|
+
__name(_ts_decorate4, "_ts_decorate");
|
|
384
|
+
function _ts_metadata3(k, v) {
|
|
385
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
386
|
+
}
|
|
387
|
+
__name(_ts_metadata3, "_ts_metadata");
|
|
388
|
+
var VrittiAuthGuard = class _VrittiAuthGuard {
|
|
389
|
+
static {
|
|
390
|
+
__name(this, "VrittiAuthGuard");
|
|
391
|
+
}
|
|
392
|
+
reflector;
|
|
393
|
+
configService;
|
|
394
|
+
jwtService;
|
|
395
|
+
primaryDatabase;
|
|
396
|
+
requestService;
|
|
397
|
+
logger = new Logger2(_VrittiAuthGuard.name);
|
|
398
|
+
constructor(reflector, configService, jwtService, primaryDatabase, requestService) {
|
|
399
|
+
this.reflector = reflector;
|
|
400
|
+
this.configService = configService;
|
|
401
|
+
this.jwtService = jwtService;
|
|
402
|
+
this.primaryDatabase = primaryDatabase;
|
|
403
|
+
this.requestService = requestService;
|
|
404
|
+
}
|
|
405
|
+
async canActivate(context) {
|
|
406
|
+
const request = context.switchToHttp().getRequest();
|
|
407
|
+
const isPublic = this.reflector.getAllAndOverride("isPublic", [
|
|
408
|
+
context.getHandler(),
|
|
409
|
+
context.getClass()
|
|
410
|
+
]);
|
|
411
|
+
if (isPublic) {
|
|
412
|
+
this.logger.debug("Public endpoint detected, skipping authentication");
|
|
413
|
+
return true;
|
|
414
|
+
}
|
|
415
|
+
const isOnboarding = this.reflector.getAllAndOverride("isOnboarding", [
|
|
416
|
+
context.getHandler(),
|
|
417
|
+
context.getClass()
|
|
418
|
+
]);
|
|
419
|
+
try {
|
|
420
|
+
const accessToken = this.requestService.getAccessToken();
|
|
421
|
+
if (!accessToken) {
|
|
422
|
+
this.logger.warn("Access token not found in Authorization header");
|
|
423
|
+
throw new UnauthorizedException("Access token not found");
|
|
424
|
+
}
|
|
425
|
+
const decodedToken = this.jwtService.decode(accessToken);
|
|
426
|
+
if (!decodedToken) {
|
|
427
|
+
this.logger.warn("Failed to decode access token");
|
|
428
|
+
throw new UnauthorizedException("Invalid token format");
|
|
429
|
+
}
|
|
430
|
+
if (isOnboarding) {
|
|
431
|
+
if (decodedToken.type !== "onboarding") {
|
|
432
|
+
this.logger.warn("Onboarding endpoint requires onboarding token");
|
|
433
|
+
throw new UnauthorizedException("This endpoint requires an onboarding token");
|
|
434
|
+
}
|
|
435
|
+
const validatedToken2 = this.validateAccessToken(accessToken);
|
|
436
|
+
this.logger.debug("Onboarding token validated successfully");
|
|
437
|
+
const userId2 = validatedToken2.userId;
|
|
438
|
+
request.user = {
|
|
439
|
+
id: userId2
|
|
440
|
+
};
|
|
441
|
+
return true;
|
|
442
|
+
}
|
|
443
|
+
if (decodedToken.type === "onboarding") {
|
|
444
|
+
this.logger.warn("Regular endpoint accessed with onboarding token");
|
|
445
|
+
throw new UnauthorizedException("Onboarding tokens cannot access this endpoint");
|
|
446
|
+
}
|
|
447
|
+
const validatedToken = this.validateAccessToken(accessToken);
|
|
448
|
+
this.logger.debug("Access token validated successfully");
|
|
449
|
+
const refreshToken = this.requestService.getRefreshToken();
|
|
450
|
+
if (!refreshToken) {
|
|
451
|
+
this.logger.warn("Refresh token (session-id) not found in cookies");
|
|
452
|
+
throw new UnauthorizedException("Refresh token not found");
|
|
453
|
+
}
|
|
454
|
+
this.validateRefreshToken(refreshToken);
|
|
455
|
+
this.logger.debug("Refresh token validated successfully");
|
|
456
|
+
const userId = validatedToken.userId;
|
|
457
|
+
request.user = {
|
|
458
|
+
id: userId
|
|
459
|
+
};
|
|
460
|
+
const tenantIdentifier = this.requestService.getTenantIdentifier();
|
|
461
|
+
if (!tenantIdentifier) {
|
|
462
|
+
this.logger.warn("Tenant identifier not found in request");
|
|
463
|
+
throw new UnauthorizedException("Tenant identifier not found");
|
|
464
|
+
}
|
|
465
|
+
this.logger.debug(`Tenant identifier extracted: ${tenantIdentifier}`);
|
|
466
|
+
if (tenantIdentifier === "cloud") {
|
|
467
|
+
this.logger.debug("Platform admin access detected, skipping tenant database validation");
|
|
468
|
+
return true;
|
|
469
|
+
}
|
|
470
|
+
const tenantInfo = await this.primaryDatabase.getTenantInfo(tenantIdentifier);
|
|
471
|
+
if (!tenantInfo) {
|
|
472
|
+
this.logger.warn(`Invalid tenant: ${tenantIdentifier}`);
|
|
473
|
+
throw new UnauthorizedException("Invalid tenant");
|
|
474
|
+
}
|
|
475
|
+
if (tenantInfo.status !== "ACTIVE") {
|
|
476
|
+
this.logger.warn(`Tenant ${tenantIdentifier} has status: ${tenantInfo.status}`);
|
|
477
|
+
throw new UnauthorizedException(`Tenant is ${tenantInfo.status}`);
|
|
478
|
+
}
|
|
479
|
+
this.logger.debug(`Tenant validated: ${tenantInfo.subdomain} (${tenantInfo.type})`);
|
|
480
|
+
return true;
|
|
481
|
+
} catch (error) {
|
|
482
|
+
if (error instanceof UnauthorizedException) {
|
|
483
|
+
throw error;
|
|
484
|
+
}
|
|
485
|
+
this.logger.error("Unexpected error in auth guard", error);
|
|
486
|
+
throw new UnauthorizedException("Authentication failed");
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
/**
|
|
490
|
+
* Validate access token with proper expiry checks
|
|
491
|
+
* Throws UnauthorizedException if token is invalid or expired
|
|
492
|
+
*/
|
|
493
|
+
validateAccessToken(token) {
|
|
494
|
+
try {
|
|
495
|
+
const decoded = this.jwtService.verify(token);
|
|
496
|
+
this.logger.debug(`Access token decoded for user: ${decoded.userId}`);
|
|
497
|
+
if (decoded.exp) {
|
|
498
|
+
const expiryTime = decoded.exp * 1e3;
|
|
499
|
+
const currentTime = Date.now();
|
|
500
|
+
const timeRemaining = expiryTime - currentTime;
|
|
501
|
+
this.logger.debug(`Access token valid for ${Math.floor(timeRemaining / 1e3)} more seconds`);
|
|
502
|
+
}
|
|
503
|
+
return decoded;
|
|
504
|
+
} catch (error) {
|
|
505
|
+
if (error instanceof UnauthorizedException) {
|
|
506
|
+
throw error;
|
|
507
|
+
}
|
|
508
|
+
const jwtError = error;
|
|
509
|
+
if (jwtError?.name === "TokenExpiredError") {
|
|
510
|
+
this.logger.warn(`Access token expired at: ${jwtError?.expiredAt}`);
|
|
511
|
+
throw new UnauthorizedException("Access token has expired");
|
|
512
|
+
}
|
|
513
|
+
if (jwtError?.name === "JsonWebTokenError") {
|
|
514
|
+
this.logger.warn(`Access token verification failed: ${jwtError?.message}`);
|
|
515
|
+
throw new UnauthorizedException("Invalid access token");
|
|
516
|
+
}
|
|
517
|
+
if (jwtError?.name === "NotBeforeError") {
|
|
518
|
+
this.logger.warn("Access token used before valid (nbf claim)");
|
|
519
|
+
throw new UnauthorizedException("Access token not yet valid");
|
|
520
|
+
}
|
|
521
|
+
this.logger.error("Unexpected error validating access token", error);
|
|
522
|
+
throw new UnauthorizedException("Access token validation failed");
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
/**
|
|
526
|
+
* Validate refresh token with proper expiry checks
|
|
527
|
+
* Throws UnauthorizedException if token is invalid or expired
|
|
528
|
+
*/
|
|
529
|
+
validateRefreshToken(token) {
|
|
530
|
+
const jwtSecret = this.configService.get("JWT_REFRESH_SECRET") || this.configService.get("JWT_SECRET");
|
|
531
|
+
this.validateRefreshTokenWithSecret(token, jwtSecret);
|
|
532
|
+
}
|
|
533
|
+
/**
|
|
534
|
+
* Helper to validate refresh token with specific secret
|
|
535
|
+
*/
|
|
536
|
+
validateRefreshTokenWithSecret(token, secret) {
|
|
537
|
+
if (!secret) {
|
|
538
|
+
this.logger.error("JWT secret not configured for refresh token validation");
|
|
539
|
+
throw new UnauthorizedException("Server configuration error");
|
|
540
|
+
}
|
|
541
|
+
try {
|
|
542
|
+
const decoded = jwt.verify(token, secret, {
|
|
543
|
+
algorithms: [
|
|
544
|
+
"HS256",
|
|
545
|
+
"HS512",
|
|
546
|
+
"RS256"
|
|
547
|
+
]
|
|
548
|
+
});
|
|
549
|
+
this.logger.debug(`Refresh token decoded for user: ${decoded.userId}`);
|
|
550
|
+
if (decoded.exp) {
|
|
551
|
+
const expiryTime = decoded.exp * 1e3;
|
|
552
|
+
const currentTime = Date.now();
|
|
553
|
+
if (currentTime > expiryTime) {
|
|
554
|
+
this.logger.warn("Refresh token has expired");
|
|
555
|
+
throw new UnauthorizedException("Refresh token has expired. Please login again");
|
|
556
|
+
}
|
|
557
|
+
const timeRemaining = expiryTime - currentTime;
|
|
558
|
+
this.logger.debug(`Refresh token valid for ${Math.floor(timeRemaining / 1e3)} more seconds`);
|
|
559
|
+
}
|
|
560
|
+
} catch (error) {
|
|
561
|
+
if (error instanceof UnauthorizedException) {
|
|
562
|
+
throw error;
|
|
563
|
+
}
|
|
564
|
+
const jwtError = error;
|
|
565
|
+
if (jwtError?.name === "TokenExpiredError") {
|
|
566
|
+
this.logger.warn(`Refresh token expired at: ${jwtError?.expiredAt}`);
|
|
567
|
+
throw new UnauthorizedException("Refresh token has expired. Please login again");
|
|
568
|
+
}
|
|
569
|
+
if (jwtError?.name === "JsonWebTokenError") {
|
|
570
|
+
this.logger.warn(`Refresh token verification failed: ${jwtError?.message}`);
|
|
571
|
+
throw new UnauthorizedException("Invalid refresh token");
|
|
572
|
+
}
|
|
573
|
+
if (jwtError?.name === "NotBeforeError") {
|
|
574
|
+
this.logger.warn("Refresh token used before valid (nbf claim)");
|
|
575
|
+
throw new UnauthorizedException("Refresh token not yet valid");
|
|
576
|
+
}
|
|
577
|
+
this.logger.error("Unexpected error validating refresh token", error);
|
|
578
|
+
throw new UnauthorizedException("Refresh token validation failed");
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
};
|
|
582
|
+
VrittiAuthGuard = _ts_decorate4([
|
|
583
|
+
Injectable3({
|
|
584
|
+
scope: Scope2.REQUEST
|
|
585
|
+
}),
|
|
586
|
+
_ts_metadata3("design:type", Function),
|
|
587
|
+
_ts_metadata3("design:paramtypes", [
|
|
588
|
+
typeof Reflector === "undefined" ? Object : Reflector,
|
|
589
|
+
typeof ConfigService === "undefined" ? Object : ConfigService,
|
|
590
|
+
typeof JwtService === "undefined" ? Object : JwtService,
|
|
591
|
+
typeof PrimaryDatabaseService === "undefined" ? Object : PrimaryDatabaseService,
|
|
592
|
+
typeof RequestService === "undefined" ? Object : RequestService
|
|
593
|
+
])
|
|
594
|
+
], VrittiAuthGuard);
|
|
595
|
+
|
|
596
|
+
// src/auth/auth-config.module.ts
|
|
597
|
+
function _ts_decorate5(decorators, target, key, desc) {
|
|
598
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
599
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
600
|
+
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;
|
|
601
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
602
|
+
}
|
|
603
|
+
__name(_ts_decorate5, "_ts_decorate");
|
|
604
|
+
var AuthConfigModule = class _AuthConfigModule {
|
|
605
|
+
static {
|
|
606
|
+
__name(this, "AuthConfigModule");
|
|
607
|
+
}
|
|
608
|
+
/**
|
|
609
|
+
* Register the auth module with async configuration
|
|
610
|
+
*
|
|
611
|
+
* This method:
|
|
612
|
+
* 1. Configures JwtModule with JWT_SECRET from ConfigService
|
|
613
|
+
* 2. Provides VrittiAuthGuard globally (applies to all routes)
|
|
614
|
+
* 3. Exports JwtModule for use in other modules (e.g., for signing tokens)
|
|
615
|
+
*
|
|
616
|
+
* @returns Dynamic module configuration
|
|
617
|
+
*/
|
|
618
|
+
static forRootAsync() {
|
|
619
|
+
return {
|
|
620
|
+
module: _AuthConfigModule,
|
|
621
|
+
imports: [
|
|
622
|
+
ConfigModule,
|
|
623
|
+
RequestModule,
|
|
624
|
+
JwtModule.registerAsync({
|
|
625
|
+
imports: [
|
|
626
|
+
ConfigModule
|
|
627
|
+
],
|
|
628
|
+
inject: [
|
|
629
|
+
ConfigService2
|
|
630
|
+
],
|
|
631
|
+
useFactory: /* @__PURE__ */ __name((config) => ({
|
|
632
|
+
secret: config.get("JWT_SECRET"),
|
|
633
|
+
signOptions: {
|
|
634
|
+
algorithm: "HS256"
|
|
635
|
+
}
|
|
636
|
+
}), "useFactory")
|
|
637
|
+
})
|
|
638
|
+
],
|
|
639
|
+
providers: [
|
|
640
|
+
{
|
|
641
|
+
provide: APP_GUARD,
|
|
642
|
+
useClass: VrittiAuthGuard
|
|
643
|
+
}
|
|
644
|
+
],
|
|
645
|
+
exports: [
|
|
646
|
+
JwtModule
|
|
647
|
+
]
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
};
|
|
651
|
+
AuthConfigModule = _ts_decorate5([
|
|
652
|
+
Global2(),
|
|
653
|
+
Module2({})
|
|
654
|
+
], AuthConfigModule);
|
|
655
|
+
|
|
656
|
+
// src/database/database.module.ts
|
|
657
|
+
import { Global as Global3, Module as Module3 } from "@nestjs/common";
|
|
658
|
+
import { APP_INTERCEPTOR } from "@nestjs/core";
|
|
659
|
+
|
|
660
|
+
// src/database/interceptors/message-tenant-context.interceptor.ts
|
|
661
|
+
import { Injectable as Injectable5, Logger as Logger3, Scope as Scope4 } from "@nestjs/common";
|
|
662
|
+
import { tap } from "rxjs/operators";
|
|
663
|
+
|
|
233
664
|
// src/database/services/tenant-context.service.ts
|
|
234
|
-
import { Injectable as
|
|
235
|
-
function
|
|
665
|
+
import { Injectable as Injectable4, Scope as Scope3, UnauthorizedException as UnauthorizedException2 } from "@nestjs/common";
|
|
666
|
+
function _ts_decorate6(decorators, target, key, desc) {
|
|
236
667
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
237
668
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
238
669
|
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;
|
|
239
670
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
240
671
|
}
|
|
241
|
-
__name(
|
|
672
|
+
__name(_ts_decorate6, "_ts_decorate");
|
|
242
673
|
var TenantContextService = class {
|
|
243
674
|
static {
|
|
244
675
|
__name(this, "TenantContextService");
|
|
@@ -269,7 +700,7 @@ var TenantContextService = class {
|
|
|
269
700
|
*/
|
|
270
701
|
getTenant() {
|
|
271
702
|
if (!this.tenantInfo) {
|
|
272
|
-
throw new
|
|
703
|
+
throw new UnauthorizedException2("Tenant context not set");
|
|
273
704
|
}
|
|
274
705
|
return this.tenantInfo;
|
|
275
706
|
}
|
|
@@ -310,70 +741,224 @@ var TenantContextService = class {
|
|
|
310
741
|
return this.tenantInfo?.subdomain ?? null;
|
|
311
742
|
}
|
|
312
743
|
};
|
|
313
|
-
TenantContextService =
|
|
314
|
-
|
|
315
|
-
scope:
|
|
744
|
+
TenantContextService = _ts_decorate6([
|
|
745
|
+
Injectable4({
|
|
746
|
+
scope: Scope3.REQUEST
|
|
316
747
|
})
|
|
317
748
|
], TenantContextService);
|
|
318
749
|
|
|
319
|
-
// src/database/
|
|
320
|
-
|
|
321
|
-
function _ts_decorate3(decorators, target, key, desc) {
|
|
750
|
+
// src/database/interceptors/message-tenant-context.interceptor.ts
|
|
751
|
+
function _ts_decorate7(decorators, target, key, desc) {
|
|
322
752
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
323
753
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
324
754
|
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;
|
|
325
755
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
326
756
|
}
|
|
327
|
-
__name(
|
|
328
|
-
function
|
|
757
|
+
__name(_ts_decorate7, "_ts_decorate");
|
|
758
|
+
function _ts_metadata4(k, v) {
|
|
329
759
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
330
760
|
}
|
|
331
|
-
__name(
|
|
332
|
-
|
|
333
|
-
return function(target, key) {
|
|
334
|
-
decorator(target, key, paramIndex);
|
|
335
|
-
};
|
|
336
|
-
}
|
|
337
|
-
__name(_ts_param2, "_ts_param");
|
|
338
|
-
var TenantDatabaseService = class _TenantDatabaseService {
|
|
761
|
+
__name(_ts_metadata4, "_ts_metadata");
|
|
762
|
+
var MessageTenantContextInterceptor = class _MessageTenantContextInterceptor {
|
|
339
763
|
static {
|
|
340
|
-
__name(this, "
|
|
764
|
+
__name(this, "MessageTenantContextInterceptor");
|
|
341
765
|
}
|
|
342
|
-
options;
|
|
343
766
|
tenantContext;
|
|
344
|
-
logger = new
|
|
345
|
-
|
|
346
|
-
clients = /* @__PURE__ */ new Map();
|
|
347
|
-
/** Track last usage time for idle connection cleanup */
|
|
348
|
-
clientLastUsed = /* @__PURE__ */ new Map();
|
|
349
|
-
/** Cleanup interval timer */
|
|
350
|
-
cleanupInterval;
|
|
351
|
-
constructor(options, tenantContext) {
|
|
352
|
-
this.options = options;
|
|
767
|
+
logger = new Logger3(_MessageTenantContextInterceptor.name);
|
|
768
|
+
constructor(tenantContext) {
|
|
353
769
|
this.tenantContext = tenantContext;
|
|
354
|
-
this.startConnectionCleaner();
|
|
355
770
|
}
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
771
|
+
intercept(context, next) {
|
|
772
|
+
const contextType = context.getType();
|
|
773
|
+
if (contextType === "rpc") {
|
|
774
|
+
const rpcContext = context.switchToRpc();
|
|
775
|
+
const payload = rpcContext.getData();
|
|
776
|
+
if (payload && payload.tenant) {
|
|
777
|
+
const tenant = payload.tenant;
|
|
778
|
+
this.logger.debug(`Setting tenant context from message: ${tenant.subdomain}`);
|
|
779
|
+
try {
|
|
780
|
+
this.tenantContext.setTenant(tenant);
|
|
781
|
+
this.logger.log(`Tenant context set: ${tenant.subdomain} (${tenant.type})`);
|
|
782
|
+
} catch (error) {
|
|
783
|
+
this.logger.error("Failed to set tenant context from message", error);
|
|
784
|
+
}
|
|
785
|
+
} else {
|
|
786
|
+
this.logger.warn("Message payload missing tenant information");
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
return next.handle().pipe(tap({
|
|
790
|
+
next: /* @__PURE__ */ __name(() => {
|
|
791
|
+
this.cleanupContext();
|
|
792
|
+
}, "next"),
|
|
793
|
+
error: /* @__PURE__ */ __name(() => {
|
|
794
|
+
this.cleanupContext();
|
|
795
|
+
}, "error"),
|
|
796
|
+
complete: /* @__PURE__ */ __name(() => {
|
|
797
|
+
this.cleanupContext();
|
|
798
|
+
}, "complete")
|
|
799
|
+
}));
|
|
800
|
+
}
|
|
801
|
+
/**
|
|
802
|
+
* Clean up tenant context after message is processed
|
|
803
|
+
*/
|
|
804
|
+
cleanupContext() {
|
|
805
|
+
if (this.tenantContext.hasTenant()) {
|
|
806
|
+
const tenant = this.tenantContext.getTenantIdSafe();
|
|
807
|
+
this.tenantContext.clearTenant();
|
|
808
|
+
this.logger.debug(`Cleaned up tenant context: ${tenant}`);
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
};
|
|
812
|
+
MessageTenantContextInterceptor = _ts_decorate7([
|
|
813
|
+
Injectable5({
|
|
814
|
+
scope: Scope4.REQUEST
|
|
815
|
+
}),
|
|
816
|
+
_ts_metadata4("design:type", Function),
|
|
817
|
+
_ts_metadata4("design:paramtypes", [
|
|
818
|
+
typeof TenantContextService === "undefined" ? Object : TenantContextService
|
|
819
|
+
])
|
|
820
|
+
], MessageTenantContextInterceptor);
|
|
821
|
+
|
|
822
|
+
// src/database/interceptors/tenant-context.interceptor.ts
|
|
823
|
+
import { Injectable as Injectable6, Logger as Logger4, Scope as Scope5, UnauthorizedException as UnauthorizedException3 } from "@nestjs/common";
|
|
824
|
+
function _ts_decorate8(decorators, target, key, desc) {
|
|
825
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
826
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
827
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
828
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
829
|
+
}
|
|
830
|
+
__name(_ts_decorate8, "_ts_decorate");
|
|
831
|
+
function _ts_metadata5(k, v) {
|
|
832
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
833
|
+
}
|
|
834
|
+
__name(_ts_metadata5, "_ts_metadata");
|
|
835
|
+
var TenantContextInterceptor = class _TenantContextInterceptor {
|
|
836
|
+
static {
|
|
837
|
+
__name(this, "TenantContextInterceptor");
|
|
838
|
+
}
|
|
839
|
+
tenantContext;
|
|
840
|
+
primaryDatabase;
|
|
841
|
+
requestService;
|
|
842
|
+
logger = new Logger4(_TenantContextInterceptor.name);
|
|
843
|
+
constructor(tenantContext, primaryDatabase, requestService) {
|
|
844
|
+
this.tenantContext = tenantContext;
|
|
845
|
+
this.primaryDatabase = primaryDatabase;
|
|
846
|
+
this.requestService = requestService;
|
|
847
|
+
}
|
|
848
|
+
async intercept(context, next) {
|
|
849
|
+
const request = context.switchToHttp().getRequest();
|
|
850
|
+
this.logger.debug(`Processing request: ${request.method} ${request.url}`);
|
|
851
|
+
try {
|
|
852
|
+
const tenantIdentifier = this.requestService.getTenantIdentifier();
|
|
853
|
+
if (!tenantIdentifier) {
|
|
854
|
+
throw new UnauthorizedException3("Tenant identifier not found in request");
|
|
855
|
+
}
|
|
856
|
+
this.logger.debug(`Tenant identifier extracted: ${tenantIdentifier}`);
|
|
857
|
+
if (tenantIdentifier === "cloud") {
|
|
858
|
+
this.logger.log("Cloud platform access detected, skipping tenant context setup");
|
|
859
|
+
return next.handle();
|
|
860
|
+
}
|
|
861
|
+
const tenantInfo = await this.primaryDatabase.getTenantInfo(tenantIdentifier);
|
|
862
|
+
if (!tenantInfo) {
|
|
863
|
+
this.logger.warn(`Invalid tenant: ${tenantIdentifier}`);
|
|
864
|
+
throw new UnauthorizedException3("Invalid tenant");
|
|
865
|
+
}
|
|
866
|
+
if (tenantInfo.status !== "ACTIVE") {
|
|
867
|
+
this.logger.warn(`Tenant ${tenantIdentifier} has status: ${tenantInfo.status}`);
|
|
868
|
+
throw new UnauthorizedException3(`Tenant is ${tenantInfo.status}`);
|
|
869
|
+
}
|
|
870
|
+
this.logger.debug(`Tenant config loaded: ${tenantInfo.subdomain} (${tenantInfo.type})`);
|
|
871
|
+
this.tenantContext.setTenant(tenantInfo);
|
|
872
|
+
request.tenant = tenantInfo;
|
|
873
|
+
this.logger.log(`Tenant context set: ${tenantInfo.subdomain}`);
|
|
874
|
+
} catch (error) {
|
|
875
|
+
this.logger.error("Failed to set tenant context", error);
|
|
876
|
+
throw error;
|
|
877
|
+
}
|
|
878
|
+
return next.handle();
|
|
879
|
+
}
|
|
880
|
+
};
|
|
881
|
+
TenantContextInterceptor = _ts_decorate8([
|
|
882
|
+
Injectable6({
|
|
883
|
+
scope: Scope5.REQUEST
|
|
884
|
+
}),
|
|
885
|
+
_ts_metadata5("design:type", Function),
|
|
886
|
+
_ts_metadata5("design:paramtypes", [
|
|
887
|
+
typeof TenantContextService === "undefined" ? Object : TenantContextService,
|
|
888
|
+
typeof PrimaryDatabaseService === "undefined" ? Object : PrimaryDatabaseService,
|
|
889
|
+
typeof RequestService === "undefined" ? Object : RequestService
|
|
890
|
+
])
|
|
891
|
+
], TenantContextInterceptor);
|
|
892
|
+
|
|
893
|
+
// src/database/services/tenant-database.service.ts
|
|
894
|
+
import { Inject as Inject3, Injectable as Injectable7, InternalServerErrorException as InternalServerErrorException2, Logger as Logger5 } from "@nestjs/common";
|
|
895
|
+
function _ts_decorate9(decorators, target, key, desc) {
|
|
896
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
897
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
898
|
+
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;
|
|
899
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
900
|
+
}
|
|
901
|
+
__name(_ts_decorate9, "_ts_decorate");
|
|
902
|
+
function _ts_metadata6(k, v) {
|
|
903
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
904
|
+
}
|
|
905
|
+
__name(_ts_metadata6, "_ts_metadata");
|
|
906
|
+
function _ts_param3(paramIndex, decorator) {
|
|
907
|
+
return function(target, key) {
|
|
908
|
+
decorator(target, key, paramIndex);
|
|
909
|
+
};
|
|
910
|
+
}
|
|
911
|
+
__name(_ts_param3, "_ts_param");
|
|
912
|
+
var TenantDatabaseService = class _TenantDatabaseService {
|
|
913
|
+
static {
|
|
914
|
+
__name(this, "TenantDatabaseService");
|
|
915
|
+
}
|
|
916
|
+
options;
|
|
917
|
+
tenantContext;
|
|
918
|
+
logger = new Logger5(_TenantDatabaseService.name);
|
|
919
|
+
/** Connection pool: Map<cacheKey, DbClient> */
|
|
920
|
+
clients = /* @__PURE__ */ new Map();
|
|
921
|
+
/** Track last usage time for idle connection cleanup */
|
|
922
|
+
clientLastUsed = /* @__PURE__ */ new Map();
|
|
923
|
+
/** Cleanup interval timer */
|
|
924
|
+
cleanupInterval;
|
|
925
|
+
constructor(options, tenantContext) {
|
|
926
|
+
this.options = options;
|
|
927
|
+
this.tenantContext = tenantContext;
|
|
928
|
+
this.startConnectionCleaner();
|
|
929
|
+
}
|
|
930
|
+
/**
|
|
931
|
+
* Get the Prisma client for the current tenant's database.
|
|
932
|
+
* This returns the tenant-scoped database client.
|
|
933
|
+
*
|
|
934
|
+
* @returns Tenant-scoped database client instance
|
|
935
|
+
* @throws UnauthorizedException if tenant context not set
|
|
936
|
+
* @throws InternalServerErrorException if connection fails
|
|
937
|
+
*/
|
|
938
|
+
get prismaClient() {
|
|
939
|
+
return this.getDbClient();
|
|
940
|
+
}
|
|
941
|
+
/**
|
|
942
|
+
* Get tenant-scoped database client for the current request/message
|
|
943
|
+
*
|
|
944
|
+
* This method:
|
|
945
|
+
* 1. Gets tenant info from TenantContextService
|
|
946
|
+
* 2. Builds a connection URL based on tenant type
|
|
947
|
+
* 3. Returns cached client if exists, otherwise creates new one
|
|
948
|
+
*
|
|
949
|
+
* @returns Promise<Database client instance>
|
|
950
|
+
* @throws UnauthorizedException if tenant context not set
|
|
951
|
+
* @throws InternalServerErrorException if connection fails
|
|
952
|
+
*
|
|
953
|
+
* @example
|
|
954
|
+
* const dbClient = await tenantDatabase.getDbClient<PrismaClient>();
|
|
955
|
+
* const users = await dbClient.user.findMany();
|
|
956
|
+
*/
|
|
957
|
+
async getDbClient() {
|
|
958
|
+
const tenant = this.tenantContext.getTenant();
|
|
959
|
+
const cacheKey = this.buildCacheKey(tenant);
|
|
960
|
+
if (this.clients.has(cacheKey)) {
|
|
961
|
+
this.clientLastUsed.set(cacheKey, Date.now());
|
|
377
962
|
this.logger.debug(`Reusing cached connection: ${cacheKey}`);
|
|
378
963
|
return this.clients.get(cacheKey);
|
|
379
964
|
}
|
|
@@ -497,87 +1082,116 @@ var TenantDatabaseService = class _TenantDatabaseService {
|
|
|
497
1082
|
this.logger.log("All database connections closed");
|
|
498
1083
|
}
|
|
499
1084
|
};
|
|
500
|
-
TenantDatabaseService =
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
1085
|
+
TenantDatabaseService = _ts_decorate9([
|
|
1086
|
+
Injectable7(),
|
|
1087
|
+
_ts_param3(0, Inject3(DATABASE_MODULE_OPTIONS)),
|
|
1088
|
+
_ts_metadata6("design:type", Function),
|
|
1089
|
+
_ts_metadata6("design:paramtypes", [
|
|
505
1090
|
typeof DatabaseModuleOptions === "undefined" ? Object : DatabaseModuleOptions,
|
|
506
1091
|
typeof TenantContextService === "undefined" ? Object : TenantContextService
|
|
507
1092
|
])
|
|
508
1093
|
], TenantDatabaseService);
|
|
509
1094
|
|
|
510
1095
|
// src/database/database.module.ts
|
|
511
|
-
function
|
|
1096
|
+
function _ts_decorate10(decorators, target, key, desc) {
|
|
512
1097
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
513
1098
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
514
1099
|
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;
|
|
515
1100
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
516
1101
|
}
|
|
517
|
-
__name(
|
|
1102
|
+
__name(_ts_decorate10, "_ts_decorate");
|
|
518
1103
|
var DatabaseModule = class _DatabaseModule {
|
|
519
1104
|
static {
|
|
520
1105
|
__name(this, "DatabaseModule");
|
|
521
1106
|
}
|
|
522
1107
|
/**
|
|
523
|
-
*
|
|
1108
|
+
* Configure DatabaseModule for Gateway/HTTP mode (multi-tenant web servers)
|
|
524
1109
|
*
|
|
525
|
-
*
|
|
526
|
-
*
|
|
1110
|
+
* This mode is for API Gateways that handle HTTP requests:
|
|
1111
|
+
* - Automatically registers TenantContextInterceptor
|
|
1112
|
+
* - Extracts tenant from subdomain or x-tenant-id header
|
|
1113
|
+
* - Queries primary database for tenant configuration
|
|
1114
|
+
* - Provides PrimaryDatabaseService for tenant lookup
|
|
1115
|
+
*
|
|
1116
|
+
* @param options Async configuration options
|
|
1117
|
+
* @returns Dynamic module configuration with HTTP interceptor
|
|
1118
|
+
*
|
|
1119
|
+
* @example
|
|
1120
|
+
* DatabaseModule.forServer({
|
|
1121
|
+
* inject: [ConfigService],
|
|
1122
|
+
* useFactory: (config: ConfigService) => ({
|
|
1123
|
+
* primaryDb: {
|
|
1124
|
+
* host: config.get('PRIMARY_DB_HOST'),
|
|
1125
|
+
* port: config.get('PRIMARY_DB_PORT'),
|
|
1126
|
+
* username: config.get('PRIMARY_DB_USERNAME'),
|
|
1127
|
+
* password: config.get('PRIMARY_DB_PASSWORD'),
|
|
1128
|
+
* database: config.get('PRIMARY_DB_DATABASE'),
|
|
1129
|
+
* },
|
|
1130
|
+
* prismaClientConstructor: PrismaClient,
|
|
1131
|
+
* }),
|
|
1132
|
+
* })
|
|
527
1133
|
*/
|
|
528
|
-
static
|
|
529
|
-
|
|
530
|
-
{
|
|
531
|
-
provide: DATABASE_MODULE_OPTIONS,
|
|
532
|
-
useValue: options
|
|
533
|
-
},
|
|
534
|
-
TenantDatabaseService,
|
|
535
|
-
TenantContextService,
|
|
536
|
-
PrimaryDatabaseService
|
|
537
|
-
];
|
|
538
|
-
return {
|
|
539
|
-
module: _DatabaseModule,
|
|
540
|
-
providers,
|
|
541
|
-
exports: [
|
|
542
|
-
TenantDatabaseService,
|
|
543
|
-
TenantContextService,
|
|
544
|
-
PrimaryDatabaseService
|
|
545
|
-
]
|
|
546
|
-
};
|
|
1134
|
+
static forServer(options) {
|
|
1135
|
+
return this.createDynamicModule(options, "server");
|
|
547
1136
|
}
|
|
548
1137
|
/**
|
|
549
|
-
*
|
|
1138
|
+
* Configure DatabaseModule for Microservice/Messaging mode (RabbitMQ workers)
|
|
550
1139
|
*
|
|
551
|
-
*
|
|
1140
|
+
* This mode is for microservices that process messages from queues:
|
|
1141
|
+
* - Automatically registers MessageTenantContextInterceptor
|
|
1142
|
+
* - Extracts tenant from RabbitMQ message patterns
|
|
1143
|
+
* - No primary database needed (tenant comes from message context)
|
|
552
1144
|
*
|
|
553
1145
|
* @param options Async configuration options
|
|
554
|
-
* @returns Dynamic module configuration
|
|
1146
|
+
* @returns Dynamic module configuration with message interceptor
|
|
555
1147
|
*
|
|
556
1148
|
* @example
|
|
557
|
-
* DatabaseModule.
|
|
558
|
-
*
|
|
559
|
-
* useFactory:
|
|
560
|
-
* cloudDatabaseUrl: config.get('CLOUD_DATABASE_URL'),
|
|
1149
|
+
* DatabaseModule.forMicroservice({
|
|
1150
|
+
* inject: [ConfigService],
|
|
1151
|
+
* useFactory: (config: ConfigService) => ({
|
|
561
1152
|
* prismaClientConstructor: PrismaClient,
|
|
562
|
-
* tenantResolver: 'subdomain',
|
|
563
1153
|
* }),
|
|
564
|
-
* inject: [ConfigService],
|
|
565
1154
|
* })
|
|
566
1155
|
*/
|
|
567
|
-
static
|
|
1156
|
+
static forMicroservice(options) {
|
|
1157
|
+
return this.createDynamicModule(options, "microservice");
|
|
1158
|
+
}
|
|
1159
|
+
/**
|
|
1160
|
+
* Internal helper to create dynamic module with conditional interceptor registration
|
|
1161
|
+
*
|
|
1162
|
+
* @param options Configuration options
|
|
1163
|
+
* @param mode Mode of operation (gateway or microservice)
|
|
1164
|
+
* @returns Dynamic module configuration
|
|
1165
|
+
*/
|
|
1166
|
+
static createDynamicModule(options, mode) {
|
|
568
1167
|
const asyncProvider = {
|
|
569
1168
|
provide: DATABASE_MODULE_OPTIONS,
|
|
570
1169
|
useFactory: options.useFactory,
|
|
571
1170
|
inject: options.inject || []
|
|
572
1171
|
};
|
|
1172
|
+
const providers = [
|
|
1173
|
+
asyncProvider,
|
|
1174
|
+
TenantContextService,
|
|
1175
|
+
PrimaryDatabaseService,
|
|
1176
|
+
TenantDatabaseService
|
|
1177
|
+
];
|
|
1178
|
+
if (mode === "server") {
|
|
1179
|
+
providers.push({
|
|
1180
|
+
provide: APP_INTERCEPTOR,
|
|
1181
|
+
useClass: TenantContextInterceptor
|
|
1182
|
+
});
|
|
1183
|
+
} else {
|
|
1184
|
+
providers.push({
|
|
1185
|
+
provide: APP_INTERCEPTOR,
|
|
1186
|
+
useClass: MessageTenantContextInterceptor
|
|
1187
|
+
});
|
|
1188
|
+
}
|
|
573
1189
|
return {
|
|
574
1190
|
module: _DatabaseModule,
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
TenantContextService,
|
|
578
|
-
PrimaryDatabaseService,
|
|
579
|
-
TenantDatabaseService
|
|
1191
|
+
imports: [
|
|
1192
|
+
RequestModule
|
|
580
1193
|
],
|
|
1194
|
+
providers,
|
|
581
1195
|
exports: [
|
|
582
1196
|
TenantDatabaseService,
|
|
583
1197
|
TenantContextService,
|
|
@@ -587,216 +1201,549 @@ var DatabaseModule = class _DatabaseModule {
|
|
|
587
1201
|
};
|
|
588
1202
|
}
|
|
589
1203
|
};
|
|
590
|
-
DatabaseModule =
|
|
591
|
-
|
|
592
|
-
|
|
1204
|
+
DatabaseModule = _ts_decorate10([
|
|
1205
|
+
Global3(),
|
|
1206
|
+
Module3({})
|
|
593
1207
|
], DatabaseModule);
|
|
594
1208
|
|
|
595
|
-
// src/database/
|
|
596
|
-
import {
|
|
597
|
-
|
|
598
|
-
function _ts_decorate5(decorators, target, key, desc) {
|
|
599
|
-
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
600
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
601
|
-
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;
|
|
602
|
-
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
603
|
-
}
|
|
604
|
-
__name(_ts_decorate5, "_ts_decorate");
|
|
605
|
-
function _ts_metadata3(k, v) {
|
|
606
|
-
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
607
|
-
}
|
|
608
|
-
__name(_ts_metadata3, "_ts_metadata");
|
|
609
|
-
var MessageTenantContextInterceptor = class _MessageTenantContextInterceptor {
|
|
1209
|
+
// src/database/repositories/primary-base.repository.ts
|
|
1210
|
+
import { Logger as Logger6 } from "@nestjs/common";
|
|
1211
|
+
var PrimaryBaseRepository = class {
|
|
610
1212
|
static {
|
|
611
|
-
__name(this, "
|
|
612
|
-
}
|
|
613
|
-
tenantContext;
|
|
614
|
-
logger = new Logger3(_MessageTenantContextInterceptor.name);
|
|
615
|
-
constructor(tenantContext) {
|
|
616
|
-
this.tenantContext = tenantContext;
|
|
617
|
-
}
|
|
618
|
-
intercept(context, next) {
|
|
619
|
-
const contextType = context.getType();
|
|
620
|
-
if (contextType === "rpc") {
|
|
621
|
-
const rpcContext = context.switchToRpc();
|
|
622
|
-
const payload = rpcContext.getData();
|
|
623
|
-
if (payload && payload.tenant) {
|
|
624
|
-
const tenant = payload.tenant;
|
|
625
|
-
this.logger.debug(`Setting tenant context from message: ${tenant.subdomain}`);
|
|
626
|
-
try {
|
|
627
|
-
this.tenantContext.setTenant(tenant);
|
|
628
|
-
this.logger.log(`Tenant context set: ${tenant.subdomain} (${tenant.type})`);
|
|
629
|
-
} catch (error) {
|
|
630
|
-
this.logger.error("Failed to set tenant context from message", error);
|
|
631
|
-
}
|
|
632
|
-
} else {
|
|
633
|
-
this.logger.warn("Message payload missing tenant information");
|
|
634
|
-
}
|
|
635
|
-
}
|
|
636
|
-
return next.handle().pipe(tap({
|
|
637
|
-
next: /* @__PURE__ */ __name(() => {
|
|
638
|
-
this.cleanupContext();
|
|
639
|
-
}, "next"),
|
|
640
|
-
error: /* @__PURE__ */ __name(() => {
|
|
641
|
-
this.cleanupContext();
|
|
642
|
-
}, "error"),
|
|
643
|
-
complete: /* @__PURE__ */ __name(() => {
|
|
644
|
-
this.cleanupContext();
|
|
645
|
-
}, "complete")
|
|
646
|
-
}));
|
|
1213
|
+
__name(this, "PrimaryBaseRepository");
|
|
647
1214
|
}
|
|
1215
|
+
database;
|
|
1216
|
+
logger;
|
|
1217
|
+
modelGetter;
|
|
648
1218
|
/**
|
|
649
|
-
*
|
|
1219
|
+
* Lazy getter for Prisma client.
|
|
1220
|
+
* Accesses the client from the database service only when needed,
|
|
1221
|
+
* avoiding initialization timing issues with NestJS lifecycle.
|
|
650
1222
|
*/
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
const tenant = this.tenantContext.getTenantIdSafe();
|
|
654
|
-
this.tenantContext.clearTenant();
|
|
655
|
-
this.logger.debug(`Cleaned up tenant context: ${tenant}`);
|
|
656
|
-
}
|
|
1223
|
+
get prisma() {
|
|
1224
|
+
return this.database.prismaClient;
|
|
657
1225
|
}
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
_ts_metadata3("design:paramtypes", [
|
|
665
|
-
typeof TenantContextService === "undefined" ? Object : TenantContextService
|
|
666
|
-
])
|
|
667
|
-
], MessageTenantContextInterceptor);
|
|
668
|
-
|
|
669
|
-
// src/database/interceptors/tenant-context.interceptor.ts
|
|
670
|
-
import { Inject as Inject3, Injectable as Injectable5, Logger as Logger4, Scope as Scope3, UnauthorizedException as UnauthorizedException2 } from "@nestjs/common";
|
|
671
|
-
|
|
672
|
-
// src/database/utils/subdomain-parser.util.ts
|
|
673
|
-
function extractSubdomain(host) {
|
|
674
|
-
if (!host) {
|
|
675
|
-
return null;
|
|
1226
|
+
/**
|
|
1227
|
+
* Lazy getter for the Prisma model delegate.
|
|
1228
|
+
* Returns the specific model (e.g., prisma.user, prisma.tenant) for this repository.
|
|
1229
|
+
*/
|
|
1230
|
+
get model() {
|
|
1231
|
+
return this.modelGetter(this.prisma);
|
|
676
1232
|
}
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
1233
|
+
/**
|
|
1234
|
+
* Create a new repository instance
|
|
1235
|
+
*
|
|
1236
|
+
* @param database - The primary database service
|
|
1237
|
+
* @param getModel - Function that returns the Prisma model delegate from the client
|
|
1238
|
+
*
|
|
1239
|
+
* @example
|
|
1240
|
+
* ```typescript
|
|
1241
|
+
* // Standard usage with full parameter name
|
|
1242
|
+
* constructor(database: PrimaryDatabaseService) {
|
|
1243
|
+
* super(database, (prisma) => prisma.user);
|
|
1244
|
+
* }
|
|
1245
|
+
*
|
|
1246
|
+
* // Short syntax
|
|
1247
|
+
* constructor(database: PrimaryDatabaseService) {
|
|
1248
|
+
* super(database, (p) => p.user);
|
|
1249
|
+
* }
|
|
1250
|
+
*
|
|
1251
|
+
* // Complex model names
|
|
1252
|
+
* constructor(database: PrimaryDatabaseService) {
|
|
1253
|
+
* super(database, (p) => p.emailVerification);
|
|
1254
|
+
* }
|
|
1255
|
+
* ```
|
|
1256
|
+
*/
|
|
1257
|
+
constructor(database, getModel) {
|
|
1258
|
+
this.database = database;
|
|
1259
|
+
this.logger = new Logger6(this.constructor.name);
|
|
1260
|
+
this.modelGetter = getModel;
|
|
1261
|
+
this.logger.debug(`Initialized ${this.constructor.name}`);
|
|
680
1262
|
}
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
1263
|
+
/**
|
|
1264
|
+
* Create a new record
|
|
1265
|
+
*
|
|
1266
|
+
* @param data - The data to create the record with
|
|
1267
|
+
* @returns Promise resolving to the created record
|
|
1268
|
+
*
|
|
1269
|
+
* @example
|
|
1270
|
+
* ```typescript
|
|
1271
|
+
* const user = await userRepository.create({
|
|
1272
|
+
* email: 'user@example.com',
|
|
1273
|
+
* name: 'John Doe'
|
|
1274
|
+
* });
|
|
1275
|
+
* ```
|
|
1276
|
+
*/
|
|
1277
|
+
async create(data) {
|
|
1278
|
+
this.logger.log("Creating record");
|
|
1279
|
+
return await this.model.create({
|
|
1280
|
+
data
|
|
1281
|
+
});
|
|
684
1282
|
}
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
1283
|
+
/**
|
|
1284
|
+
* Find a single record by ID
|
|
1285
|
+
*
|
|
1286
|
+
* @param id - The record ID
|
|
1287
|
+
* @returns Promise resolving to the record or null if not found
|
|
1288
|
+
*
|
|
1289
|
+
* @example
|
|
1290
|
+
* ```typescript
|
|
1291
|
+
* const user = await userRepository.findById('user-id-123');
|
|
1292
|
+
* ```
|
|
1293
|
+
*/
|
|
1294
|
+
async findById(id) {
|
|
1295
|
+
this.logger.debug(`Finding record by ID: ${id}`);
|
|
1296
|
+
return await this.model.findUnique({
|
|
1297
|
+
where: {
|
|
1298
|
+
id
|
|
1299
|
+
}
|
|
1300
|
+
});
|
|
1301
|
+
}
|
|
1302
|
+
/**
|
|
1303
|
+
* Find a single record with custom where clause
|
|
1304
|
+
*
|
|
1305
|
+
* @param where - The where clause or findUnique args
|
|
1306
|
+
* @returns Promise resolving to the record or null if not found
|
|
1307
|
+
*
|
|
1308
|
+
* @example
|
|
1309
|
+
* ```typescript
|
|
1310
|
+
* // Simple where clause
|
|
1311
|
+
* const user = await userRepository.findOne({ email: 'user@example.com' });
|
|
1312
|
+
*
|
|
1313
|
+
* // With include
|
|
1314
|
+
* const user = await userRepository.findOne({
|
|
1315
|
+
* where: { email: 'user@example.com' },
|
|
1316
|
+
* include: { posts: true }
|
|
1317
|
+
* });
|
|
1318
|
+
* ```
|
|
1319
|
+
*/
|
|
1320
|
+
async findOne(where) {
|
|
1321
|
+
this.logger.debug("Finding record with custom query");
|
|
1322
|
+
return await this.model.findUnique(typeof where === "object" && "where" in where ? where : {
|
|
1323
|
+
where
|
|
1324
|
+
});
|
|
1325
|
+
}
|
|
1326
|
+
/**
|
|
1327
|
+
* Find multiple records
|
|
1328
|
+
*
|
|
1329
|
+
* @param args - Prisma findMany arguments (where, orderBy, take, skip, etc.)
|
|
1330
|
+
* @returns Promise resolving to an array of records
|
|
1331
|
+
*
|
|
1332
|
+
* @example
|
|
1333
|
+
* ```typescript
|
|
1334
|
+
* // Find all users
|
|
1335
|
+
* const users = await userRepository.findMany();
|
|
1336
|
+
*
|
|
1337
|
+
* // Find with filtering and pagination
|
|
1338
|
+
* const users = await userRepository.findMany({
|
|
1339
|
+
* where: { status: 'ACTIVE' },
|
|
1340
|
+
* orderBy: { createdAt: 'desc' },
|
|
1341
|
+
* take: 10,
|
|
1342
|
+
* skip: 0
|
|
1343
|
+
* });
|
|
1344
|
+
* ```
|
|
1345
|
+
*/
|
|
1346
|
+
async findMany(args) {
|
|
1347
|
+
this.logger.debug("Finding multiple records");
|
|
1348
|
+
return await this.model.findMany(args);
|
|
1349
|
+
}
|
|
1350
|
+
/**
|
|
1351
|
+
* Update a record by ID
|
|
1352
|
+
*
|
|
1353
|
+
* @param id - The record ID
|
|
1354
|
+
* @param data - The data to update
|
|
1355
|
+
* @returns Promise resolving to the updated record
|
|
1356
|
+
*
|
|
1357
|
+
* @example
|
|
1358
|
+
* ```typescript
|
|
1359
|
+
* const user = await userRepository.update('user-id-123', {
|
|
1360
|
+
* name: 'Jane Doe'
|
|
1361
|
+
* });
|
|
1362
|
+
* ```
|
|
1363
|
+
*/
|
|
1364
|
+
async update(id, data) {
|
|
1365
|
+
this.logger.log(`Updating record with ID: ${id}`);
|
|
1366
|
+
return await this.model.update({
|
|
1367
|
+
where: {
|
|
1368
|
+
id
|
|
1369
|
+
},
|
|
1370
|
+
data
|
|
1371
|
+
});
|
|
1372
|
+
}
|
|
1373
|
+
/**
|
|
1374
|
+
* Update multiple records
|
|
1375
|
+
*
|
|
1376
|
+
* @param where - The where clause to match records
|
|
1377
|
+
* @param data - The data to update
|
|
1378
|
+
* @returns Promise resolving to the count of updated records
|
|
1379
|
+
*
|
|
1380
|
+
* @example
|
|
1381
|
+
* ```typescript
|
|
1382
|
+
* const result = await userRepository.updateMany(
|
|
1383
|
+
* { status: 'PENDING' },
|
|
1384
|
+
* { status: 'ACTIVE' }
|
|
1385
|
+
* );
|
|
1386
|
+
* console.log(`Updated ${result.count} users`);
|
|
1387
|
+
* ```
|
|
1388
|
+
*/
|
|
1389
|
+
async updateMany(where, data) {
|
|
1390
|
+
this.logger.log("Updating multiple records");
|
|
1391
|
+
return await this.model.updateMany({
|
|
1392
|
+
where,
|
|
1393
|
+
data
|
|
1394
|
+
});
|
|
1395
|
+
}
|
|
1396
|
+
/**
|
|
1397
|
+
* Delete a record by ID
|
|
1398
|
+
*
|
|
1399
|
+
* @param id - The record ID
|
|
1400
|
+
* @returns Promise resolving to the deleted record
|
|
1401
|
+
*
|
|
1402
|
+
* @example
|
|
1403
|
+
* ```typescript
|
|
1404
|
+
* const user = await userRepository.delete('user-id-123');
|
|
1405
|
+
* ```
|
|
1406
|
+
*/
|
|
1407
|
+
async delete(id) {
|
|
1408
|
+
this.logger.log(`Deleting record with ID: ${id}`);
|
|
1409
|
+
return await this.model.delete({
|
|
1410
|
+
where: {
|
|
1411
|
+
id
|
|
1412
|
+
}
|
|
1413
|
+
});
|
|
1414
|
+
}
|
|
1415
|
+
/**
|
|
1416
|
+
* Delete multiple records
|
|
1417
|
+
*
|
|
1418
|
+
* @param where - The where clause to match records
|
|
1419
|
+
* @returns Promise resolving to the count of deleted records
|
|
1420
|
+
*
|
|
1421
|
+
* @example
|
|
1422
|
+
* ```typescript
|
|
1423
|
+
* const result = await userRepository.deleteMany({
|
|
1424
|
+
* status: 'INACTIVE',
|
|
1425
|
+
* createdAt: { lt: new Date('2020-01-01') }
|
|
1426
|
+
* });
|
|
1427
|
+
* console.log(`Deleted ${result.count} users`);
|
|
1428
|
+
* ```
|
|
1429
|
+
*/
|
|
1430
|
+
async deleteMany(where) {
|
|
1431
|
+
this.logger.log("Deleting multiple records");
|
|
1432
|
+
return await this.model.deleteMany({
|
|
1433
|
+
where
|
|
1434
|
+
});
|
|
1435
|
+
}
|
|
1436
|
+
/**
|
|
1437
|
+
* Count records
|
|
1438
|
+
*
|
|
1439
|
+
* @param where - Optional where clause to filter records
|
|
1440
|
+
* @returns Promise resolving to the count of records
|
|
1441
|
+
*
|
|
1442
|
+
* @example
|
|
1443
|
+
* ```typescript
|
|
1444
|
+
* // Count all users
|
|
1445
|
+
* const total = await userRepository.count();
|
|
1446
|
+
*
|
|
1447
|
+
* // Count active users
|
|
1448
|
+
* const activeCount = await userRepository.count({ status: 'ACTIVE' });
|
|
1449
|
+
* ```
|
|
1450
|
+
*/
|
|
1451
|
+
async count(where) {
|
|
1452
|
+
this.logger.debug("Counting records");
|
|
1453
|
+
return await this.model.count({
|
|
1454
|
+
where
|
|
1455
|
+
});
|
|
1456
|
+
}
|
|
1457
|
+
/**
|
|
1458
|
+
* Check if a record exists
|
|
1459
|
+
*
|
|
1460
|
+
* @param where - The where clause to match records
|
|
1461
|
+
* @returns Promise resolving to true if at least one record exists, false otherwise
|
|
1462
|
+
*
|
|
1463
|
+
* @example
|
|
1464
|
+
* ```typescript
|
|
1465
|
+
* const emailExists = await userRepository.exists({
|
|
1466
|
+
* email: 'user@example.com'
|
|
1467
|
+
* });
|
|
1468
|
+
* ```
|
|
1469
|
+
*/
|
|
1470
|
+
async exists(where) {
|
|
1471
|
+
const count = await this.model.count({
|
|
1472
|
+
where
|
|
1473
|
+
});
|
|
1474
|
+
return count > 0;
|
|
1475
|
+
}
|
|
1476
|
+
};
|
|
688
1477
|
|
|
689
|
-
// src/database/
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
693
|
-
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;
|
|
694
|
-
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
695
|
-
}
|
|
696
|
-
__name(_ts_decorate6, "_ts_decorate");
|
|
697
|
-
function _ts_metadata4(k, v) {
|
|
698
|
-
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
699
|
-
}
|
|
700
|
-
__name(_ts_metadata4, "_ts_metadata");
|
|
701
|
-
function _ts_param3(paramIndex, decorator) {
|
|
702
|
-
return function(target, key) {
|
|
703
|
-
decorator(target, key, paramIndex);
|
|
704
|
-
};
|
|
705
|
-
}
|
|
706
|
-
__name(_ts_param3, "_ts_param");
|
|
707
|
-
var TenantContextInterceptor = class _TenantContextInterceptor {
|
|
1478
|
+
// src/database/repositories/tenant-base.repository.ts
|
|
1479
|
+
import { Logger as Logger7 } from "@nestjs/common";
|
|
1480
|
+
var TenantBaseRepository = class {
|
|
708
1481
|
static {
|
|
709
|
-
__name(this, "
|
|
1482
|
+
__name(this, "TenantBaseRepository");
|
|
710
1483
|
}
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
1484
|
+
database;
|
|
1485
|
+
logger;
|
|
1486
|
+
modelGetter;
|
|
1487
|
+
/**
|
|
1488
|
+
* Lazy getter for Prisma client.
|
|
1489
|
+
* Accesses the client from the database service only when needed,
|
|
1490
|
+
* avoiding initialization timing issues with NestJS lifecycle.
|
|
1491
|
+
*/
|
|
1492
|
+
get prisma() {
|
|
1493
|
+
return this.database.prismaClient;
|
|
719
1494
|
}
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
1495
|
+
/**
|
|
1496
|
+
* Lazy getter for the Prisma model delegate.
|
|
1497
|
+
* Returns the specific model (e.g., prisma.product, prisma.order) for this repository.
|
|
1498
|
+
*/
|
|
1499
|
+
get model() {
|
|
1500
|
+
return this.modelGetter(this.prisma);
|
|
1501
|
+
}
|
|
1502
|
+
/**
|
|
1503
|
+
* Create a new repository instance
|
|
1504
|
+
*
|
|
1505
|
+
* @param database - The tenant database service
|
|
1506
|
+
* @param getModel - Function that returns the Prisma model delegate from the client
|
|
1507
|
+
*
|
|
1508
|
+
* @example
|
|
1509
|
+
* ```typescript
|
|
1510
|
+
* // Standard usage with full parameter name
|
|
1511
|
+
* constructor(database: TenantDatabaseService) {
|
|
1512
|
+
* super(database, (prisma) => prisma.product);
|
|
1513
|
+
* }
|
|
1514
|
+
*
|
|
1515
|
+
* // Short syntax
|
|
1516
|
+
* constructor(database: TenantDatabaseService) {
|
|
1517
|
+
* super(database, (p) => p.product);
|
|
1518
|
+
* }
|
|
1519
|
+
*
|
|
1520
|
+
* // Complex model names
|
|
1521
|
+
* constructor(database: TenantDatabaseService) {
|
|
1522
|
+
* super(database, (p) => p.inventoryItem);
|
|
1523
|
+
* }
|
|
1524
|
+
* ```
|
|
1525
|
+
*/
|
|
1526
|
+
constructor(database, getModel) {
|
|
1527
|
+
this.database = database;
|
|
1528
|
+
this.logger = new Logger7(this.constructor.name);
|
|
1529
|
+
this.modelGetter = getModel;
|
|
1530
|
+
this.logger.debug(`Initialized ${this.constructor.name}`);
|
|
1531
|
+
}
|
|
1532
|
+
/**
|
|
1533
|
+
* Create a new record
|
|
1534
|
+
*
|
|
1535
|
+
* @param data - The data to create the record with
|
|
1536
|
+
* @returns Promise resolving to the created record
|
|
1537
|
+
*
|
|
1538
|
+
* @example
|
|
1539
|
+
* ```typescript
|
|
1540
|
+
* const product = await productRepository.create({
|
|
1541
|
+
* name: 'Widget',
|
|
1542
|
+
* sku: 'WDG-001',
|
|
1543
|
+
* price: 9.99
|
|
1544
|
+
* });
|
|
1545
|
+
* ```
|
|
1546
|
+
*/
|
|
1547
|
+
async create(data) {
|
|
1548
|
+
this.logger.log("Creating record");
|
|
1549
|
+
return await this.model.create({
|
|
1550
|
+
data
|
|
1551
|
+
});
|
|
1552
|
+
}
|
|
1553
|
+
/**
|
|
1554
|
+
* Find a single record by ID
|
|
1555
|
+
*
|
|
1556
|
+
* @param id - The record ID
|
|
1557
|
+
* @returns Promise resolving to the record or null if not found
|
|
1558
|
+
*
|
|
1559
|
+
* @example
|
|
1560
|
+
* ```typescript
|
|
1561
|
+
* const product = await productRepository.findById('product-id-123');
|
|
1562
|
+
* ```
|
|
1563
|
+
*/
|
|
1564
|
+
async findById(id) {
|
|
1565
|
+
this.logger.debug(`Finding record by ID: ${id}`);
|
|
1566
|
+
return await this.model.findUnique({
|
|
1567
|
+
where: {
|
|
1568
|
+
id
|
|
741
1569
|
}
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
1570
|
+
});
|
|
1571
|
+
}
|
|
1572
|
+
/**
|
|
1573
|
+
* Find a single record with custom where clause
|
|
1574
|
+
*
|
|
1575
|
+
* @param where - The where clause or findUnique args
|
|
1576
|
+
* @returns Promise resolving to the record or null if not found
|
|
1577
|
+
*
|
|
1578
|
+
* @example
|
|
1579
|
+
* ```typescript
|
|
1580
|
+
* // Simple where clause
|
|
1581
|
+
* const product = await productRepository.findOne({ sku: 'WDG-001' });
|
|
1582
|
+
*
|
|
1583
|
+
* // With include
|
|
1584
|
+
* const product = await productRepository.findOne({
|
|
1585
|
+
* where: { sku: 'WDG-001' },
|
|
1586
|
+
* include: { category: true }
|
|
1587
|
+
* });
|
|
1588
|
+
* ```
|
|
1589
|
+
*/
|
|
1590
|
+
async findOne(where) {
|
|
1591
|
+
this.logger.debug("Finding record with custom query");
|
|
1592
|
+
return await this.model.findUnique(typeof where === "object" && "where" in where ? where : {
|
|
1593
|
+
where
|
|
1594
|
+
});
|
|
751
1595
|
}
|
|
752
1596
|
/**
|
|
753
|
-
*
|
|
1597
|
+
* Find multiple records
|
|
1598
|
+
*
|
|
1599
|
+
* @param args - Prisma findMany arguments (where, orderBy, take, skip, etc.)
|
|
1600
|
+
* @returns Promise resolving to an array of records
|
|
1601
|
+
*
|
|
1602
|
+
* @example
|
|
1603
|
+
* ```typescript
|
|
1604
|
+
* // Find all products
|
|
1605
|
+
* const products = await productRepository.findMany();
|
|
1606
|
+
*
|
|
1607
|
+
* // Find with filtering and pagination
|
|
1608
|
+
* const products = await productRepository.findMany({
|
|
1609
|
+
* where: { status: 'ACTIVE' },
|
|
1610
|
+
* orderBy: { createdAt: 'desc' },
|
|
1611
|
+
* take: 10,
|
|
1612
|
+
* skip: 0
|
|
1613
|
+
* });
|
|
1614
|
+
* ```
|
|
754
1615
|
*/
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
1616
|
+
async findMany(args) {
|
|
1617
|
+
this.logger.debug("Finding multiple records");
|
|
1618
|
+
return await this.model.findMany(args);
|
|
1619
|
+
}
|
|
1620
|
+
/**
|
|
1621
|
+
* Update a record by ID
|
|
1622
|
+
*
|
|
1623
|
+
* @param id - The record ID
|
|
1624
|
+
* @param data - The data to update
|
|
1625
|
+
* @returns Promise resolving to the updated record
|
|
1626
|
+
*
|
|
1627
|
+
* @example
|
|
1628
|
+
* ```typescript
|
|
1629
|
+
* const product = await productRepository.update('product-id-123', {
|
|
1630
|
+
* price: 12.99
|
|
1631
|
+
* });
|
|
1632
|
+
* ```
|
|
1633
|
+
*/
|
|
1634
|
+
async update(id, data) {
|
|
1635
|
+
this.logger.log(`Updating record with ID: ${id}`);
|
|
1636
|
+
return await this.model.update({
|
|
1637
|
+
where: {
|
|
1638
|
+
id
|
|
1639
|
+
},
|
|
1640
|
+
data
|
|
1641
|
+
});
|
|
1642
|
+
}
|
|
1643
|
+
/**
|
|
1644
|
+
* Update multiple records
|
|
1645
|
+
*
|
|
1646
|
+
* @param where - The where clause to match records
|
|
1647
|
+
* @param data - The data to update
|
|
1648
|
+
* @returns Promise resolving to the count of updated records
|
|
1649
|
+
*
|
|
1650
|
+
* @example
|
|
1651
|
+
* ```typescript
|
|
1652
|
+
* const result = await productRepository.updateMany(
|
|
1653
|
+
* { status: 'PENDING' },
|
|
1654
|
+
* { status: 'ACTIVE' }
|
|
1655
|
+
* );
|
|
1656
|
+
* console.log(`Updated ${result.count} products`);
|
|
1657
|
+
* ```
|
|
1658
|
+
*/
|
|
1659
|
+
async updateMany(where, data) {
|
|
1660
|
+
this.logger.log("Updating multiple records");
|
|
1661
|
+
return await this.model.updateMany({
|
|
1662
|
+
where,
|
|
1663
|
+
data
|
|
1664
|
+
});
|
|
1665
|
+
}
|
|
1666
|
+
/**
|
|
1667
|
+
* Delete a record by ID
|
|
1668
|
+
*
|
|
1669
|
+
* @param id - The record ID
|
|
1670
|
+
* @returns Promise resolving to the deleted record
|
|
1671
|
+
*
|
|
1672
|
+
* @example
|
|
1673
|
+
* ```typescript
|
|
1674
|
+
* const product = await productRepository.delete('product-id-123');
|
|
1675
|
+
* ```
|
|
1676
|
+
*/
|
|
1677
|
+
async delete(id) {
|
|
1678
|
+
this.logger.log(`Deleting record with ID: ${id}`);
|
|
1679
|
+
return await this.model.delete({
|
|
1680
|
+
where: {
|
|
1681
|
+
id
|
|
761
1682
|
}
|
|
762
|
-
}
|
|
763
|
-
return tenantIdentifier;
|
|
1683
|
+
});
|
|
764
1684
|
}
|
|
765
1685
|
/**
|
|
766
|
-
*
|
|
767
|
-
*
|
|
1686
|
+
* Delete multiple records
|
|
1687
|
+
*
|
|
1688
|
+
* @param where - The where clause to match records
|
|
1689
|
+
* @returns Promise resolving to the count of deleted records
|
|
1690
|
+
*
|
|
1691
|
+
* @example
|
|
1692
|
+
* ```typescript
|
|
1693
|
+
* const result = await productRepository.deleteMany({
|
|
1694
|
+
* status: 'INACTIVE',
|
|
1695
|
+
* createdAt: { lt: new Date('2020-01-01') }
|
|
1696
|
+
* });
|
|
1697
|
+
* console.log(`Deleted ${result.count} products`);
|
|
1698
|
+
* ```
|
|
768
1699
|
*/
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
return extractSubdomain(host);
|
|
1700
|
+
async deleteMany(where) {
|
|
1701
|
+
this.logger.log("Deleting multiple records");
|
|
1702
|
+
return await this.model.deleteMany({
|
|
1703
|
+
where
|
|
1704
|
+
});
|
|
775
1705
|
}
|
|
776
1706
|
/**
|
|
777
|
-
*
|
|
778
|
-
*
|
|
1707
|
+
* Count records
|
|
1708
|
+
*
|
|
1709
|
+
* @param where - Optional where clause to filter records
|
|
1710
|
+
* @returns Promise resolving to the count of records
|
|
1711
|
+
*
|
|
1712
|
+
* @example
|
|
1713
|
+
* ```typescript
|
|
1714
|
+
* // Count all products
|
|
1715
|
+
* const total = await productRepository.count();
|
|
1716
|
+
*
|
|
1717
|
+
* // Count active products
|
|
1718
|
+
* const activeCount = await productRepository.count({ status: 'ACTIVE' });
|
|
1719
|
+
* ```
|
|
779
1720
|
*/
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
}
|
|
785
|
-
|
|
1721
|
+
async count(where) {
|
|
1722
|
+
this.logger.debug("Counting records");
|
|
1723
|
+
return await this.model.count({
|
|
1724
|
+
where
|
|
1725
|
+
});
|
|
1726
|
+
}
|
|
1727
|
+
/**
|
|
1728
|
+
* Check if a record exists
|
|
1729
|
+
*
|
|
1730
|
+
* @param where - The where clause to match records
|
|
1731
|
+
* @returns Promise resolving to true if at least one record exists, false otherwise
|
|
1732
|
+
*
|
|
1733
|
+
* @example
|
|
1734
|
+
* ```typescript
|
|
1735
|
+
* const skuExists = await productRepository.exists({
|
|
1736
|
+
* sku: 'WDG-001'
|
|
1737
|
+
* });
|
|
1738
|
+
* ```
|
|
1739
|
+
*/
|
|
1740
|
+
async exists(where) {
|
|
1741
|
+
const count = await this.model.count({
|
|
1742
|
+
where
|
|
1743
|
+
});
|
|
1744
|
+
return count > 0;
|
|
786
1745
|
}
|
|
787
1746
|
};
|
|
788
|
-
TenantContextInterceptor = _ts_decorate6([
|
|
789
|
-
Injectable5({
|
|
790
|
-
scope: Scope3.REQUEST
|
|
791
|
-
}),
|
|
792
|
-
_ts_param3(2, Inject3(DATABASE_MODULE_OPTIONS)),
|
|
793
|
-
_ts_metadata4("design:type", Function),
|
|
794
|
-
_ts_metadata4("design:paramtypes", [
|
|
795
|
-
typeof TenantContextService === "undefined" ? Object : TenantContextService,
|
|
796
|
-
typeof PrimaryDatabaseService === "undefined" ? Object : PrimaryDatabaseService,
|
|
797
|
-
typeof DatabaseModuleOptions === "undefined" ? Object : DatabaseModuleOptions
|
|
798
|
-
])
|
|
799
|
-
], TenantContextInterceptor);
|
|
800
1747
|
|
|
801
1748
|
// src/database/decorators/tenant.decorator.ts
|
|
802
1749
|
import { createParamDecorator } from "@nestjs/common";
|
|
@@ -808,14 +1755,242 @@ var Tenant = createParamDecorator((data, ctx) => {
|
|
|
808
1755
|
}
|
|
809
1756
|
return tenantContext.getTenant();
|
|
810
1757
|
});
|
|
1758
|
+
|
|
1759
|
+
// src/auth/decorators/onboarding.decorator.ts
|
|
1760
|
+
import { SetMetadata } from "@nestjs/common";
|
|
1761
|
+
var Onboarding = /* @__PURE__ */ __name(() => SetMetadata("isOnboarding", true), "Onboarding");
|
|
1762
|
+
|
|
1763
|
+
// src/auth/decorators/public.decorator.ts
|
|
1764
|
+
import { SetMetadata as SetMetadata2 } from "@nestjs/common";
|
|
1765
|
+
var Public = /* @__PURE__ */ __name(() => SetMetadata2("isPublic", true), "Public");
|
|
1766
|
+
|
|
1767
|
+
// src/http/http.module.ts
|
|
1768
|
+
import { Module as Module4 } from "@nestjs/common";
|
|
1769
|
+
|
|
1770
|
+
// src/http/guards/csrf.guard.ts
|
|
1771
|
+
import { ForbiddenException, Injectable as Injectable8, Logger as Logger8 } from "@nestjs/common";
|
|
1772
|
+
import { Reflector as Reflector2 } from "@nestjs/core";
|
|
1773
|
+
function _ts_decorate11(decorators, target, key, desc) {
|
|
1774
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
1775
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
1776
|
+
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;
|
|
1777
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
1778
|
+
}
|
|
1779
|
+
__name(_ts_decorate11, "_ts_decorate");
|
|
1780
|
+
function _ts_metadata7(k, v) {
|
|
1781
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
1782
|
+
}
|
|
1783
|
+
__name(_ts_metadata7, "_ts_metadata");
|
|
1784
|
+
var CsrfGuard = class _CsrfGuard {
|
|
1785
|
+
static {
|
|
1786
|
+
__name(this, "CsrfGuard");
|
|
1787
|
+
}
|
|
1788
|
+
reflector;
|
|
1789
|
+
logger = new Logger8(_CsrfGuard.name);
|
|
1790
|
+
constructor(reflector) {
|
|
1791
|
+
this.reflector = reflector;
|
|
1792
|
+
}
|
|
1793
|
+
async canActivate(context) {
|
|
1794
|
+
const request = context.switchToHttp().getRequest();
|
|
1795
|
+
const reply = context.switchToHttp().getResponse();
|
|
1796
|
+
const safeMethods = [
|
|
1797
|
+
"GET",
|
|
1798
|
+
"HEAD",
|
|
1799
|
+
"OPTIONS"
|
|
1800
|
+
];
|
|
1801
|
+
if (safeMethods.includes(request.method)) {
|
|
1802
|
+
return true;
|
|
1803
|
+
}
|
|
1804
|
+
try {
|
|
1805
|
+
const fastifyInstance = request.server;
|
|
1806
|
+
if (!fastifyInstance.csrfProtection) {
|
|
1807
|
+
this.logger.error("CSRF protection plugin not found. Ensure @fastify/csrf-protection is registered.");
|
|
1808
|
+
throw new ForbiddenException("CSRF protection not configured");
|
|
1809
|
+
}
|
|
1810
|
+
await new Promise((resolve, reject) => {
|
|
1811
|
+
fastifyInstance.csrfProtection(request, reply, (err) => {
|
|
1812
|
+
if (err) {
|
|
1813
|
+
reject(err);
|
|
1814
|
+
} else {
|
|
1815
|
+
resolve();
|
|
1816
|
+
}
|
|
1817
|
+
});
|
|
1818
|
+
});
|
|
1819
|
+
this.logger.debug(`CSRF validation successful for ${request.method} ${request.url}`);
|
|
1820
|
+
return true;
|
|
1821
|
+
} catch (error) {
|
|
1822
|
+
this.logger.warn(`CSRF validation failed for ${request.method} ${request.url}: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
1823
|
+
throw new ForbiddenException({
|
|
1824
|
+
errors: [
|
|
1825
|
+
{
|
|
1826
|
+
field: "csrf",
|
|
1827
|
+
message: "Invalid or missing CSRF token"
|
|
1828
|
+
}
|
|
1829
|
+
],
|
|
1830
|
+
message: "CSRF validation failed"
|
|
1831
|
+
});
|
|
1832
|
+
}
|
|
1833
|
+
}
|
|
1834
|
+
};
|
|
1835
|
+
CsrfGuard = _ts_decorate11([
|
|
1836
|
+
Injectable8(),
|
|
1837
|
+
_ts_metadata7("design:type", Function),
|
|
1838
|
+
_ts_metadata7("design:paramtypes", [
|
|
1839
|
+
typeof Reflector2 === "undefined" ? Object : Reflector2
|
|
1840
|
+
])
|
|
1841
|
+
], CsrfGuard);
|
|
1842
|
+
|
|
1843
|
+
// src/http/http.module.ts
|
|
1844
|
+
function _ts_decorate12(decorators, target, key, desc) {
|
|
1845
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
1846
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
1847
|
+
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;
|
|
1848
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
1849
|
+
}
|
|
1850
|
+
__name(_ts_decorate12, "_ts_decorate");
|
|
1851
|
+
var HttpModule = class {
|
|
1852
|
+
static {
|
|
1853
|
+
__name(this, "HttpModule");
|
|
1854
|
+
}
|
|
1855
|
+
};
|
|
1856
|
+
HttpModule = _ts_decorate12([
|
|
1857
|
+
Module4({
|
|
1858
|
+
providers: [
|
|
1859
|
+
CsrfGuard
|
|
1860
|
+
],
|
|
1861
|
+
exports: [
|
|
1862
|
+
CsrfGuard
|
|
1863
|
+
]
|
|
1864
|
+
})
|
|
1865
|
+
], HttpModule);
|
|
1866
|
+
|
|
1867
|
+
// src/http/filters/http-exception.filter.ts
|
|
1868
|
+
import { Catch, HttpException, HttpStatus, Logger as Logger9 } from "@nestjs/common";
|
|
1869
|
+
function _ts_decorate13(decorators, target, key, desc) {
|
|
1870
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
1871
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
1872
|
+
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;
|
|
1873
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
1874
|
+
}
|
|
1875
|
+
__name(_ts_decorate13, "_ts_decorate");
|
|
1876
|
+
var HttpExceptionFilter = class _HttpExceptionFilter {
|
|
1877
|
+
static {
|
|
1878
|
+
__name(this, "HttpExceptionFilter");
|
|
1879
|
+
}
|
|
1880
|
+
logger = new Logger9(_HttpExceptionFilter.name);
|
|
1881
|
+
catch(exception, host) {
|
|
1882
|
+
const ctx = host.switchToHttp();
|
|
1883
|
+
const reply = ctx.getResponse();
|
|
1884
|
+
const request = ctx.getRequest();
|
|
1885
|
+
let status = HttpStatus.INTERNAL_SERVER_ERROR;
|
|
1886
|
+
let errors = [];
|
|
1887
|
+
let message;
|
|
1888
|
+
if (exception instanceof HttpException) {
|
|
1889
|
+
status = exception.getStatus();
|
|
1890
|
+
const exceptionResponse = exception.getResponse();
|
|
1891
|
+
if (typeof exceptionResponse === "object" && exceptionResponse !== null) {
|
|
1892
|
+
const responseObj = exceptionResponse;
|
|
1893
|
+
if (Array.isArray(responseObj.message)) {
|
|
1894
|
+
errors = this.parseValidationErrors(responseObj.message);
|
|
1895
|
+
message = "Validation failed";
|
|
1896
|
+
} else if (responseObj.message) {
|
|
1897
|
+
errors = [
|
|
1898
|
+
{
|
|
1899
|
+
field: "general",
|
|
1900
|
+
message: responseObj.message
|
|
1901
|
+
}
|
|
1902
|
+
];
|
|
1903
|
+
message = responseObj.message;
|
|
1904
|
+
}
|
|
1905
|
+
} else if (typeof exceptionResponse === "string") {
|
|
1906
|
+
errors = [
|
|
1907
|
+
{
|
|
1908
|
+
field: "general",
|
|
1909
|
+
message: exceptionResponse
|
|
1910
|
+
}
|
|
1911
|
+
];
|
|
1912
|
+
message = exceptionResponse;
|
|
1913
|
+
}
|
|
1914
|
+
} else if (exception instanceof Error) {
|
|
1915
|
+
this.logger.error(`Unhandled error: ${exception.message}`, exception.stack);
|
|
1916
|
+
errors = [
|
|
1917
|
+
{
|
|
1918
|
+
field: "general",
|
|
1919
|
+
message: "Internal server error"
|
|
1920
|
+
}
|
|
1921
|
+
];
|
|
1922
|
+
message = "An unexpected error occurred";
|
|
1923
|
+
} else {
|
|
1924
|
+
this.logger.error("Unknown exception type", exception);
|
|
1925
|
+
errors = [
|
|
1926
|
+
{
|
|
1927
|
+
field: "general",
|
|
1928
|
+
message: "Internal server error"
|
|
1929
|
+
}
|
|
1930
|
+
];
|
|
1931
|
+
message = "An unexpected error occurred";
|
|
1932
|
+
}
|
|
1933
|
+
const errorResponse = {
|
|
1934
|
+
errors,
|
|
1935
|
+
message,
|
|
1936
|
+
statusCode: status,
|
|
1937
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1938
|
+
path: request.url
|
|
1939
|
+
};
|
|
1940
|
+
if (status >= 500) {
|
|
1941
|
+
this.logger.error(`HTTP ${status} Error: ${JSON.stringify(errorResponse)}`, exception instanceof Error ? exception.stack : void 0);
|
|
1942
|
+
} else {
|
|
1943
|
+
this.logger.warn(`HTTP ${status} Error: ${JSON.stringify(errorResponse)}`);
|
|
1944
|
+
}
|
|
1945
|
+
reply.status(status).send(errorResponse);
|
|
1946
|
+
}
|
|
1947
|
+
/**
|
|
1948
|
+
* Parse class-validator error messages into field-specific errors
|
|
1949
|
+
*/
|
|
1950
|
+
parseValidationErrors(messages) {
|
|
1951
|
+
const errors = [];
|
|
1952
|
+
for (const msg of messages) {
|
|
1953
|
+
if (typeof msg === "string") {
|
|
1954
|
+
errors.push({
|
|
1955
|
+
field: "general",
|
|
1956
|
+
message: msg
|
|
1957
|
+
});
|
|
1958
|
+
} else if (typeof msg === "object" && msg.property && msg.constraints) {
|
|
1959
|
+
const field = msg.property;
|
|
1960
|
+
const constraintMessages = Object.values(msg.constraints);
|
|
1961
|
+
for (const constraintMsg of constraintMessages) {
|
|
1962
|
+
errors.push({
|
|
1963
|
+
field,
|
|
1964
|
+
message: constraintMsg
|
|
1965
|
+
});
|
|
1966
|
+
}
|
|
1967
|
+
}
|
|
1968
|
+
}
|
|
1969
|
+
return errors.length > 0 ? errors : [
|
|
1970
|
+
{
|
|
1971
|
+
field: "general",
|
|
1972
|
+
message: "Validation failed"
|
|
1973
|
+
}
|
|
1974
|
+
];
|
|
1975
|
+
}
|
|
1976
|
+
};
|
|
1977
|
+
HttpExceptionFilter = _ts_decorate13([
|
|
1978
|
+
Catch()
|
|
1979
|
+
], HttpExceptionFilter);
|
|
811
1980
|
export {
|
|
1981
|
+
AuthConfigModule,
|
|
1982
|
+
CsrfGuard,
|
|
812
1983
|
DatabaseModule,
|
|
813
|
-
|
|
1984
|
+
HttpExceptionFilter,
|
|
1985
|
+
HttpModule,
|
|
1986
|
+
Onboarding,
|
|
1987
|
+
PrimaryBaseRepository,
|
|
814
1988
|
PrimaryDatabaseService,
|
|
1989
|
+
Public,
|
|
815
1990
|
Tenant,
|
|
816
|
-
|
|
1991
|
+
TenantBaseRepository,
|
|
817
1992
|
TenantContextService,
|
|
818
1993
|
TenantDatabaseService,
|
|
819
|
-
|
|
1994
|
+
VrittiAuthGuard
|
|
820
1995
|
};
|
|
821
1996
|
//# sourceMappingURL=index.js.map
|