@vritti/api-sdk 0.0.6 → 0.0.7
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 +512 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +446 -2
- package/dist/index.d.ts +446 -2
- package/dist/index.js +491 -1
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/auth/auth-config.module.ts","../src/request/request.module.ts","../src/request/services/request.service.ts","../src/auth/guards/vritti-auth.guard.ts","../src/database/services/primary-database.service.ts","../src/database/constants.ts","../src/database/database.module.ts","../src/database/interceptors/message-tenant-context.interceptor.ts","../src/database/services/tenant-context.service.ts","../src/database/interceptors/tenant-context.interceptor.ts","../src/database/services/tenant-database.service.ts","../src/database/repositories/primary-base.repository.ts","../src/database/repositories/tenant-base.repository.ts","../src/database/decorators/tenant.decorator.ts","../src/auth/decorators/onboarding.decorator.ts","../src/auth/decorators/public.decorator.ts","../src/http/http.module.ts","../src/http/guards/csrf.guard.ts","../src/http/filters/http-exception.filter.ts"],"sourcesContent":["// Core modules\nexport { AuthConfigModule } from './auth/auth-config.module';\nexport { DatabaseModule } from './database/database.module';\n\n// Services\nexport { TenantContextService } from './database/services/tenant-context.service';\nexport { TenantDatabaseService } from './database/services/tenant-database.service';\nexport { PrimaryDatabaseService } from './database/services/primary-database.service';\n\n// Repositories\nexport { PrimaryBaseRepository } from './database/repositories/primary-base.repository';\nexport { TenantBaseRepository } from './database/repositories/tenant-base.repository';\n\n// Guards\nexport { VrittiAuthGuard } from './auth/guards/vritti-auth.guard';\n\n// Decorators\nexport { Tenant } from './database/decorators/tenant.decorator';\nexport { Onboarding } from './auth/decorators/onboarding.decorator';\nexport { Public } from './auth/decorators/public.decorator';\n\n// Interfaces\nexport * from './database/interfaces';\n\n// HTTP utilities\nexport { HttpModule } from './http/http.module';\nexport { CsrfGuard } from './http/guards/csrf.guard';\nexport { HttpExceptionFilter } from './http/filters/http-exception.filter';\n","import { DynamicModule, Global, Module } from '@nestjs/common';\nimport { ConfigModule, ConfigService } from '@nestjs/config';\nimport { APP_GUARD } from '@nestjs/core';\nimport { JwtModule } from '@nestjs/jwt';\nimport { RequestModule } from '../request/request.module';\nimport { VrittiAuthGuard } from './guards/vritti-auth.guard';\n\n/**\n * Global authentication configuration module\n *\n * This module provides:\n * - JWT token verification (JwtModule)\n * - Global authentication guard (VrittiAuthGuard)\n * - Support for @Public and @Onboarding decorators\n *\n * ## Features:\n * - Automatically applies VrittiAuthGuard to all routes\n * - Configures JwtModule with JWT_SECRET from environment\n * - Exports JwtModule for token generation in services\n *\n * ## Usage in Application:\n *\n * @example\n * // In app.module.ts\n * @Module({\n * imports: [\n * ConfigModule.forRoot({ isGlobal: true }),\n *\n * // Auth configuration (global guard + JWT)\n * AuthConfigModule.forRootAsync(),\n *\n * // Database configuration (Gateway mode)\n * DatabaseModule.forServer({\n * useFactory: (config: ConfigService) => ({\n * primaryDb: {\n * host: config.get('PRIMARY_DB_HOST'),\n * // ... other config\n * },\n * prismaClientConstructor: PrismaClient,\n * }),\n * inject: [ConfigService],\n * }),\n * ],\n * })\n * export class AppModule {}\n *\n * ## Environment Variables Required:\n * - JWT_SECRET: Secret key to verify access tokens (required)\n * - JWT_REFRESH_SECRET: Secret key for refresh tokens (optional, falls back to JWT_SECRET)\n *\n * ## Bypass Authentication:\n *\n * @example\n * // Skip authentication on specific endpoints\n * @Public()\n * @Post('auth/login')\n * async login() { ... }\n *\n * @example\n * // Onboarding endpoints (only accept onboarding tokens)\n * @Onboarding()\n * @Post('onboarding/verify-email')\n * async verifyEmail(@Request() req) {\n * const userId = req.user.id; // Available from guard\n * ...\n * }\n */\n@Global()\n@Module({})\nexport class AuthConfigModule {\n /**\n * Register the auth module with async configuration\n *\n * This method:\n * 1. Configures JwtModule with JWT_SECRET from ConfigService\n * 2. Provides VrittiAuthGuard globally (applies to all routes)\n * 3. Exports JwtModule for use in other modules (e.g., for signing tokens)\n *\n * @returns Dynamic module configuration\n */\n static forRootAsync(): DynamicModule {\n return {\n module: AuthConfigModule,\n imports: [\n ConfigModule,\n RequestModule,\n JwtModule.registerAsync({\n imports: [ConfigModule],\n inject: [ConfigService],\n useFactory: (config: ConfigService) => ({\n secret: config.get<string>('JWT_SECRET'),\n signOptions: {\n algorithm: 'HS256',\n },\n }),\n }),\n ],\n providers: [\n {\n provide: APP_GUARD,\n useClass: VrittiAuthGuard,\n },\n ],\n exports: [\n JwtModule, // Export for use in other modules (e.g., generating tokens)\n ],\n };\n }\n}\n","import { Global, Module } from '@nestjs/common';\nimport { RequestService } from './services/request.service';\n\n@Global()\n@Module({\n providers: [RequestService],\n exports: [RequestService],\n})\nexport class RequestModule {}\n","import { Inject, Injectable, Scope } from '@nestjs/common';\nimport { REQUEST } from '@nestjs/core';\nimport type { FastifyRequest } from 'fastify';\n\n@Injectable({ scope: Scope.REQUEST })\nexport class RequestService {\n constructor(@Inject(REQUEST) private readonly request: FastifyRequest) {}\n\n /**\n * Extract tenant identifier from request headers\n * Priority: x-tenant-id > x-subdomain\n * @returns Tenant identifier or null if not found\n */\n getTenantIdentifier(): string | null {\n const getHeader = (key: string) => {\n const value = this.request.headers?.[key];\n return Array.isArray(value) ? value[0] : value;\n };\n\n return getHeader('x-tenant-id') || getHeader('x-subdomain') || null;\n }\n\n /**\n * Extract access token from Authorization header\n * Format: \"Bearer <token>\"\n * @returns Access token or null if not found\n */\n getAccessToken(): string | null {\n const authHeader = this.request.headers?.authorization;\n if (!authHeader) {\n return null;\n }\n const [type, token] = authHeader.split(' ') ?? [];\n return type === 'Bearer' && token ? token : null;\n }\n\n /**\n * Extract refresh token from session-id cookie\n * Cookie name: session-id\n * @returns Refresh token or null if not found\n */\n getRefreshToken(): string | null {\n try {\n const cookies = (this.request as unknown as { cookies?: Record<string, string> }).cookies;\n if (cookies && typeof cookies === 'object') {\n const sessionId = cookies['session-id'];\n if (sessionId) {\n return sessionId;\n }\n }\n return null;\n } catch (error: unknown) {\n return null;\n }\n }\n\n /**\n * Get a specific header value\n * @param key Header key\n * @returns Header value (string, array, or undefined)\n */\n getHeader(key: string): string | string[] | undefined {\n return this.request.headers?.[key];\n }\n\n /**\n * Get all headers\n * @returns Record of all headers\n */\n getAllHeaders(): FastifyRequest['headers'] {\n return this.request.headers || {};\n }\n}\n","import {\n CanActivate,\n ExecutionContext,\n Injectable,\n Logger,\n Scope,\n UnauthorizedException,\n} from '@nestjs/common';\nimport { ConfigService } from '@nestjs/config';\nimport { Reflector } from '@nestjs/core';\nimport { JwtService } from '@nestjs/jwt';\nimport { FastifyRequest } from 'fastify';\nimport * as jwt from 'jsonwebtoken';\nimport { PrimaryDatabaseService } from '../../database/services/primary-database.service';\nimport { RequestService } from '../../request/services/request.service';\n\n// Type for decoded JWT token\ninterface DecodedToken {\n userId?: string;\n type?: string;\n exp?: number;\n nbf?: number;\n iat?: number;\n [key: string]: unknown;\n}\n\n/**\n * Vritti Authentication Guard - Validates JWT tokens and tenant context\n *\n * This guard performs comprehensive validation and attaches user data to request.\n *\n * Validation Flow:\n * 1. Checks if endpoint is marked with @Public() decorator → skip all validation\n * 2. Checks if endpoint is marked with @Onboarding() decorator:\n * - Requires token type='onboarding'\n * - Validates JWT signature and expiry only\n * - Skips tenant and refresh token validation\n * - Attaches user data to request.user\n * 3. For regular endpoints (no decorator):\n * - Rejects tokens with type='onboarding'\n * - Validates access token (JWT signature, expiry, nbf)\n * - Validates refresh token from session-id cookie\n * - Validates tenant exists and is ACTIVE\n * - Attaches user data to request.user\n *\n * Token Format:\n * - Access Token: \"Authorization: Bearer <jwt_token>\"\n * - Refresh Token: \"session-id\" cookie\n *\n * Token Types:\n * - type='onboarding': Limited access during registration flow (@Onboarding endpoints only)\n * - type='access': Full access to authenticated endpoints\n *\n * Environment Variables Required:\n * - JWT_SECRET: Secret key to verify access tokens (required)\n * - JWT_REFRESH_SECRET: Secret key for refresh tokens (optional, falls back to JWT_SECRET)\n *\n * Error Responses:\n * - 401: Invalid/expired access token\n * - 401: Invalid/expired refresh token\n * - 401: Tenant not found or inactive\n * - 401: Tenant identifier not found\n * - 401: Token type mismatch (onboarding token on regular endpoint or vice versa)\n *\n * @example\n * // Automatically registered by AuthConfigModule.forRootAsync()\n * // No manual registration needed\n * //\n * // Internal registration uses useExisting pattern:\n * // providers: [\n * // VrittiAuthGuard,\n * // {\n * // provide: APP_GUARD,\n * // useExisting: VrittiAuthGuard,\n * // },\n * // ]\n *\n * @example\n * // Bypass guard with @Public() decorator\n * @Public()\n * @Post('auth/login')\n * async login(@Body() dto: LoginDto) { ... }\n *\n * @example\n * // Restrict to onboarding tokens with @Onboarding() decorator\n * @Onboarding()\n * @Post('onboarding/verify-email')\n * async verifyEmail(@Request() req) {\n * const userId = req.user.id; // Available from guard\n * ...\n * }\n */\n@Injectable({ scope: Scope.REQUEST })\nexport class VrittiAuthGuard implements CanActivate {\n private readonly logger = new Logger(VrittiAuthGuard.name);\n\n constructor(\n private readonly reflector: Reflector,\n private readonly configService: ConfigService,\n private readonly jwtService: JwtService,\n private readonly primaryDatabase: PrimaryDatabaseService,\n private readonly requestService: RequestService,\n ) {}\n\n async canActivate(context: ExecutionContext): Promise<boolean> {\n const request = context.switchToHttp().getRequest<FastifyRequest>();\n\n // Step 1: Check if endpoint is marked as @Public()\n const isPublic = this.reflector.getAllAndOverride<boolean>('isPublic', [\n context.getHandler(),\n context.getClass(),\n ]);\n\n if (isPublic) {\n this.logger.debug('Public endpoint detected, skipping authentication');\n return true;\n }\n\n // Step 2: Check if endpoint is marked as @Onboarding()\n const isOnboarding = this.reflector.getAllAndOverride<boolean>('isOnboarding', [\n context.getHandler(),\n context.getClass(),\n ]);\n\n try {\n // Extract and validate access token\n const accessToken = this.requestService.getAccessToken();\n if (!accessToken) {\n this.logger.warn('Access token not found in Authorization header');\n throw new UnauthorizedException('Access token not found');\n }\n\n // Decode token to check type (without full validation yet)\n const decodedToken = this.jwtService.decode(accessToken) as DecodedToken;\n if (!decodedToken) {\n this.logger.warn('Failed to decode access token');\n throw new UnauthorizedException('Invalid token format');\n }\n\n // Step 3: Handle @Onboarding endpoints\n if (isOnboarding) {\n // Only accept onboarding tokens\n if (decodedToken.type !== 'onboarding') {\n this.logger.warn('Onboarding endpoint requires onboarding token');\n throw new UnauthorizedException('This endpoint requires an onboarding token');\n }\n\n // Validate JWT signature and expiry only\n const validatedToken = this.validateAccessToken(accessToken);\n this.logger.debug('Onboarding token validated successfully');\n\n // Attach user data to request (use userId field from our tokens, fallback to sub for standard JWT)\n const userId = (validatedToken as any).userId;\n (request as any).user = { id: userId };\n\n return true;\n }\n\n // Step 4: Handle regular endpoints - reject onboarding tokens\n if (decodedToken.type === 'onboarding') {\n this.logger.warn('Regular endpoint accessed with onboarding token');\n throw new UnauthorizedException('Onboarding tokens cannot access this endpoint');\n }\n\n // Step 5: Validate access token\n const validatedToken = this.validateAccessToken(accessToken);\n this.logger.debug('Access token validated successfully');\n\n // Step 6: Validate refresh token from session-id cookie\n const refreshToken = this.requestService.getRefreshToken();\n if (!refreshToken) {\n this.logger.warn('Refresh token (session-id) not found in cookies');\n throw new UnauthorizedException('Refresh token not found');\n }\n\n this.validateRefreshToken(refreshToken);\n this.logger.debug('Refresh token validated successfully');\n\n // Step 7: Attach user data to request (use userId field from our tokens, fallback to sub for standard JWT)\n const userId = (validatedToken as any).userId;\n (request as any).user = { id: userId };\n\n // Step 8: Extract tenant identifier using RequestService\n const tenantIdentifier = this.requestService.getTenantIdentifier();\n\n if (!tenantIdentifier) {\n this.logger.warn('Tenant identifier not found in request');\n throw new UnauthorizedException('Tenant identifier not found');\n }\n\n this.logger.debug(`Tenant identifier extracted: ${tenantIdentifier}`);\n\n // Step 9: Skip database validation for platform admin (cloud.vritti.com)\n if (tenantIdentifier === 'cloud') {\n this.logger.debug('Platform admin access detected, skipping tenant database validation');\n return true;\n }\n\n // Step 10: Fetch tenant details from primary database\n const tenantInfo = await this.primaryDatabase.getTenantInfo(tenantIdentifier);\n\n if (!tenantInfo) {\n this.logger.warn(`Invalid tenant: ${tenantIdentifier}`);\n throw new UnauthorizedException('Invalid tenant');\n }\n\n // Step 11: Validate tenant is ACTIVE\n if (tenantInfo.status !== 'ACTIVE') {\n this.logger.warn(`Tenant ${tenantIdentifier} has status: ${tenantInfo.status}`);\n throw new UnauthorizedException(`Tenant is ${tenantInfo.status}`);\n }\n\n this.logger.debug(`Tenant validated: ${tenantInfo.subdomain} (${tenantInfo.type})`);\n\n return true;\n } catch (error) {\n if (error instanceof UnauthorizedException) {\n throw error;\n }\n this.logger.error('Unexpected error in auth guard', error);\n throw new UnauthorizedException('Authentication failed');\n }\n }\n\n /**\n * Validate access token with proper expiry checks\n * Throws UnauthorizedException if token is invalid or expired\n */\n private validateAccessToken(token: string): DecodedToken {\n try {\n const decoded = this.jwtService.verify<DecodedToken>(token);\n\n this.logger.debug(`Access token decoded for user: ${(decoded as any).userId}`);\n\n // Check expiry explicitly (JwtService already validates, but we log it)\n if (decoded.exp) {\n const expiryTime = decoded.exp * 1000; // Convert to milliseconds\n const currentTime = Date.now();\n\n const timeRemaining = expiryTime - currentTime;\n this.logger.debug(\n `Access token valid for ${Math.floor(timeRemaining / 1000)} more seconds`,\n );\n }\n\n return decoded;\n } catch (error: unknown) {\n if (error instanceof UnauthorizedException) {\n throw error;\n }\n\n const jwtError = error as { name?: string; message?: string; expiredAt?: string };\n if (jwtError?.name === 'TokenExpiredError') {\n this.logger.warn(`Access token expired at: ${jwtError?.expiredAt}`);\n throw new UnauthorizedException('Access token has expired');\n }\n\n if (jwtError?.name === 'JsonWebTokenError') {\n this.logger.warn(`Access token verification failed: ${jwtError?.message}`);\n throw new UnauthorizedException('Invalid access token');\n }\n\n if (jwtError?.name === 'NotBeforeError') {\n this.logger.warn('Access token used before valid (nbf claim)');\n throw new UnauthorizedException('Access token not yet valid');\n }\n\n this.logger.error('Unexpected error validating access token', error);\n throw new UnauthorizedException('Access token validation failed');\n }\n }\n\n /**\n * Validate refresh token with proper expiry checks\n * Throws UnauthorizedException if token is invalid or expired\n */\n private validateRefreshToken(token: string): void {\n const jwtSecret =\n this.configService.get<string>('JWT_REFRESH_SECRET') ||\n this.configService.get<string>('JWT_SECRET');\n this.validateRefreshTokenWithSecret(token, jwtSecret);\n }\n\n /**\n * Helper to validate refresh token with specific secret\n */\n private validateRefreshTokenWithSecret(token: string, secret: string | undefined): void {\n if (!secret) {\n this.logger.error('JWT secret not configured for refresh token validation');\n throw new UnauthorizedException('Server configuration error');\n }\n\n try {\n const decoded = jwt.verify(token, secret, {\n algorithms: ['HS256', 'HS512', 'RS256'],\n }) as DecodedToken;\n\n this.logger.debug(`Refresh token decoded for user: ${(decoded as any).userId}`);\n\n // Check expiry explicitly\n if (decoded.exp) {\n const expiryTime = decoded.exp * 1000; // Convert to milliseconds\n const currentTime = Date.now();\n\n if (currentTime > expiryTime) {\n this.logger.warn('Refresh token has expired');\n throw new UnauthorizedException('Refresh token has expired. Please login again');\n }\n\n const timeRemaining = expiryTime - currentTime;\n this.logger.debug(\n `Refresh token valid for ${Math.floor(timeRemaining / 1000)} more seconds`,\n );\n }\n } catch (error: unknown) {\n if (error instanceof UnauthorizedException) {\n throw error;\n }\n\n const jwtError = error as { name?: string; message?: string; expiredAt?: string };\n if (jwtError?.name === 'TokenExpiredError') {\n this.logger.warn(`Refresh token expired at: ${jwtError?.expiredAt}`);\n throw new UnauthorizedException('Refresh token has expired. Please login again');\n }\n\n if (jwtError?.name === 'JsonWebTokenError') {\n this.logger.warn(`Refresh token verification failed: ${jwtError?.message}`);\n throw new UnauthorizedException('Invalid refresh token');\n }\n\n if (jwtError?.name === 'NotBeforeError') {\n this.logger.warn('Refresh token used before valid (nbf claim)');\n throw new UnauthorizedException('Refresh token not yet valid');\n }\n\n this.logger.error('Unexpected error validating refresh token', error);\n throw new UnauthorizedException('Refresh token validation failed');\n }\n }\n}\n","import {\n Inject,\n Injectable,\n InternalServerErrorException,\n Logger,\n OnModuleDestroy,\n OnModuleInit,\n} from '@nestjs/common';\nimport { DATABASE_MODULE_OPTIONS } from '../constants';\nimport type { DatabaseModuleOptions, TenantInfo } from '../interfaces';\n\n/**\n * Service responsible for querying the primary database to resolve tenant configurations\n *\n * This service:\n * - Connects to the primary database (tenant registry)\n * - Queries tenant metadata (database location, credentials, etc.)\n * - Caches tenant configs in memory to reduce database load\n * - Only used in GATEWAY MODE (microservices receive tenant config from messages)\n *\n * @example\n * // In API Gateway\n * const config = await primaryDatabase.getTenantConfig('acme');\n * // Returns: { id, slug, type, databaseHost, databaseName, ... }\n */\n@Injectable()\nexport class PrimaryDatabaseService implements OnModuleInit, OnModuleDestroy {\n private readonly logger = new Logger(PrimaryDatabaseService.name);\n\n /** Primary database client for querying tenant registry */\n private primaryDbClient: any;\n\n /** In-memory cache: Map<tenantIdentifier, TenantConfig> */\n private readonly tenantConfigCache = new Map<string, TenantInfo>();\n\n /** Cache TTL in milliseconds */\n private readonly cacheTTL: number;\n\n constructor(\n @Inject(DATABASE_MODULE_OPTIONS)\n private readonly options: DatabaseModuleOptions,\n ) {\n this.cacheTTL = options.connectionCacheTTL || 300000; // 5 minutes default\n }\n\n async onModuleInit() {\n // Only initialize if we have primary database config (gateway mode)\n if (this.options.primaryDb) {\n await this.initializePrimaryDbClient();\n }\n }\n\n /**\n * Initialize connection to primary database\n */\n private async initializePrimaryDbClient(): Promise<void> {\n try {\n const PrimaryDbClient = this.options.prismaClientConstructor;\n\n // Build connection URL from individual properties\n const databaseUrl = this.buildPrimaryDbUrl();\n\n this.primaryDbClient = new PrimaryDbClient({\n datasources: {\n db: {\n url: databaseUrl,\n },\n },\n log: ['error', 'warn'],\n });\n\n await this.primaryDbClient.$connect();\n this.logger.log('Connected to primary database (tenant registry)');\n } catch (error) {\n this.logger.error('Failed to connect to primary database', error);\n throw new InternalServerErrorException('Failed to initialize tenant registry');\n }\n }\n\n /**\n * Build connection URL from primary database properties\n */\n private buildPrimaryDbUrl(): string {\n if (!this.options.primaryDb) {\n throw new Error('Primary database configuration not provided');\n }\n\n const {\n host,\n port = 5432,\n username,\n password,\n database,\n schema = 'public',\n sslMode = 'require',\n } = this.options.primaryDb;\n\n // Build base URL\n let url = `postgresql://${username}:${password}@${host}:${port}/${database}`;\n\n // Add query parameters\n const params = new URLSearchParams();\n if (schema) {\n params.set('schema', schema);\n }\n params.set('sslmode', sslMode);\n\n const queryString = params.toString();\n if (queryString) {\n url += `?${queryString}`;\n }\n\n this.logger.debug(`Primary DB connection URL: ${this.maskPassword(url)}`);\n\n return url;\n }\n\n /**\n * Mask password in connection URL for logging\n */\n private maskPassword(url: string): string {\n return url.replace(/:([^@]+)@/, ':****@');\n }\n\n /**\n * Get tenant configuration by identifier (ID or slug)\n *\n * @param tenantIdentifier Tenant ID or slug\n * @returns Tenant configuration or null if not found\n */\n async getTenantInfo(tenantIdentifier: string): Promise<TenantInfo | null> {\n // Check cache first\n const cached = this.tenantConfigCache.get(tenantIdentifier);\n if (cached) {\n this.logger.debug(`Cache hit for tenant: ${tenantIdentifier}`);\n return cached;\n }\n\n // Query primary database\n try {\n if (!this.primaryDbClient) {\n throw new Error('Primary database client not initialized');\n }\n\n this.logger.debug(`Querying primary database for tenant: ${tenantIdentifier}`);\n\n const tenant = await this.primaryDbClient.tenant.findFirst({\n where: {\n OR: [{ id: tenantIdentifier }, { subdomain: tenantIdentifier }],\n status: 'ACTIVE',\n },\n include: {\n databaseConfig: true,\n },\n });\n\n if (!tenant) {\n this.logger.warn(`Tenant not found: ${tenantIdentifier}`);\n return null;\n }\n\n // Build info object - map from separated tables\n const info: TenantInfo = {\n id: tenant.id,\n subdomain: tenant.subdomain,\n type: tenant.dbType,\n status: tenant.status,\n // For SHARED tenants: schema name\n schemaName: tenant.databaseConfig?.dbSchema || undefined,\n // For DEDICATED tenants: database configuration from TenantDatabaseConfig table\n databaseName: tenant.databaseConfig?.dbName || undefined,\n databaseHost: tenant.databaseConfig?.dbHost || undefined,\n databasePort: tenant.databaseConfig?.dbPort || undefined,\n databaseUsername: tenant.databaseConfig?.dbUsername\n ? this.decrypt(tenant.databaseConfig.dbUsername)\n : undefined,\n databasePassword: tenant.databaseConfig?.dbPassword\n ? this.decrypt(tenant.databaseConfig.dbPassword)\n : undefined,\n databaseSslMode: tenant.databaseConfig?.dbSslMode || undefined,\n connectionPoolSize: tenant.databaseConfig?.connectionPoolSize || undefined,\n };\n\n // Cache by both ID and slug\n this.cacheInfo(info);\n\n return info;\n } catch (error) {\n this.logger.error(`Failed to fetch tenant info: ${tenantIdentifier}`, error);\n throw new InternalServerErrorException('Failed to resolve tenant');\n }\n }\n\n /**\n * Cache tenant information with TTL\n */\n private cacheInfo(info: TenantInfo): void {\n this.tenantConfigCache.set(info.id, info);\n this.tenantConfigCache.set(info.subdomain, info);\n\n // Set expiration\n setTimeout(() => {\n this.tenantConfigCache.delete(info.id);\n this.tenantConfigCache.delete(info.subdomain);\n this.logger.debug(`Cache expired for tenant: ${info.subdomain}`);\n }, this.cacheTTL);\n }\n\n /**\n * Clear cached tenant information\n *\n * Useful when tenant settings are updated and cache needs to be invalidated\n *\n * @param tenantIdentifier Tenant ID or slug\n */\n clearTenantCache(tenantIdentifier: string): void {\n const config = this.tenantConfigCache.get(tenantIdentifier);\n if (config) {\n this.tenantConfigCache.delete(config.id);\n this.tenantConfigCache.delete(config.subdomain);\n this.logger.log(`Cleared cache for tenant: ${tenantIdentifier}`);\n }\n }\n\n /**\n * Clear all cached tenant configurations\n */\n clearAllCaches(): void {\n const size = this.tenantConfigCache.size;\n this.tenantConfigCache.clear();\n this.logger.log(`Cleared ${size} cached tenant configs`);\n }\n\n /**\n * Get the Prisma client for the primary database.\n * This is a synchronous property that returns the initialized Prisma client.\n *\n * @returns Primary database client instance\n * @throws Error if primary database client is not initialized\n */\n get prismaClient(): any {\n if (!this.primaryDbClient) {\n throw new Error('Primary database client not initialized');\n }\n return this.primaryDbClient;\n }\n\n /**\n * Decrypt database credentials\n *\n * Override this method to implement your encryption strategy\n *\n * @param encrypted Encrypted value\n * @returns Decrypted value\n */\n private decrypt(encrypted: string): string {\n // TODO: Implement actual decryption using this.options.encryptionKey\n // For now, return as-is (assumes unencrypted or encryption happens elsewhere)\n return encrypted;\n }\n\n async onModuleDestroy() {\n if (this.primaryDbClient) {\n await this.primaryDbClient.$disconnect();\n this.logger.log('Disconnected from primary database');\n }\n }\n}\n","export const DATABASE_MODULE_OPTIONS = Symbol('DATABASE_MODULE_OPTIONS');\n","import { DynamicModule, Global, Module, Provider } from '@nestjs/common';\nimport { APP_INTERCEPTOR } from '@nestjs/core';\nimport { RequestModule } from '../request/request.module';\nimport { DATABASE_MODULE_OPTIONS } from './constants';\nimport { MessageTenantContextInterceptor } from './interceptors/message-tenant-context.interceptor';\nimport { TenantContextInterceptor } from './interceptors/tenant-context.interceptor';\n\nimport type { DatabaseModuleOptions } from './interfaces';\nimport { PrimaryDatabaseService } from './services/primary-database.service';\nimport { TenantContextService } from './services/tenant-context.service';\nimport { TenantDatabaseService } from './services/tenant-database.service';\n\n/**\n * Dynamic module for multi-tenant database management\n *\n * This module provides:\n * - Tenant context management (request-scoped)\n * - Database connection pooling\n * - Dynamic schema/cluster routing\n * - Support for both gateway and microservice modes\n *\n * ## Gateway Mode (API Gateway)\n * - Use DatabaseModule.forServer() method\n * - Automatically extracts tenant from subdomain, falls back to x-tenant-id header\n * - Provide primaryDb configuration and prismaClientConstructor\n * - Automatically queries primary DB for tenant config\n * - Automatically registers TenantContextInterceptor globally\n * - No manual interceptor registration needed\n *\n * ## Microservice Mode (RabbitMQ Workers)\n * - Use DatabaseModule.forMicroservice() method\n * - Only provide prismaClientConstructor\n * - Tenant context comes from RabbitMQ messages\n * - Automatically registers MessageTenantContextInterceptor globally\n * - No manual interceptor registration needed\n *\n * @example\n * // Gateway configuration\n * DatabaseModule.forServer({\n * inject: [ConfigService],\n * useFactory: (config: ConfigService) => ({\n * primaryDb: {\n * host: config.get('PRIMARY_DB_HOST'),\n * port: config.get('PRIMARY_DB_PORT'),\n * username: config.get('PRIMARY_DB_USERNAME'),\n * password: config.get('PRIMARY_DB_PASSWORD'),\n * database: config.get('PRIMARY_DB_DATABASE'),\n * },\n * prismaClientConstructor: PrismaClient,\n * }),\n * })\n *\n * @example\n * // Microservice configuration\n * DatabaseModule.forMicroservice({\n * inject: [ConfigService],\n * useFactory: (config: ConfigService) => ({\n * prismaClientConstructor: PrismaClient,\n * }),\n * })\n */\n@Global()\n@Module({})\nexport class DatabaseModule {\n /**\n * Configure DatabaseModule for Gateway/HTTP mode (multi-tenant web servers)\n *\n * This mode is for API Gateways that handle HTTP requests:\n * - Automatically registers TenantContextInterceptor\n * - Extracts tenant from subdomain or x-tenant-id header\n * - Queries primary database for tenant configuration\n * - Provides PrimaryDatabaseService for tenant lookup\n *\n * @param options Async configuration options\n * @returns Dynamic module configuration with HTTP interceptor\n *\n * @example\n * DatabaseModule.forServer({\n * inject: [ConfigService],\n * useFactory: (config: ConfigService) => ({\n * primaryDb: {\n * host: config.get('PRIMARY_DB_HOST'),\n * port: config.get('PRIMARY_DB_PORT'),\n * username: config.get('PRIMARY_DB_USERNAME'),\n * password: config.get('PRIMARY_DB_PASSWORD'),\n * database: config.get('PRIMARY_DB_DATABASE'),\n * },\n * prismaClientConstructor: PrismaClient,\n * }),\n * })\n */\n static forServer(options: {\n useFactory: (...args: any[]) => Promise<DatabaseModuleOptions> | DatabaseModuleOptions;\n inject?: any[];\n }): DynamicModule {\n return this.createDynamicModule(options, 'server');\n }\n\n /**\n * Configure DatabaseModule for Microservice/Messaging mode (RabbitMQ workers)\n *\n * This mode is for microservices that process messages from queues:\n * - Automatically registers MessageTenantContextInterceptor\n * - Extracts tenant from RabbitMQ message patterns\n * - No primary database needed (tenant comes from message context)\n *\n * @param options Async configuration options\n * @returns Dynamic module configuration with message interceptor\n *\n * @example\n * DatabaseModule.forMicroservice({\n * inject: [ConfigService],\n * useFactory: (config: ConfigService) => ({\n * prismaClientConstructor: PrismaClient,\n * }),\n * })\n */\n static forMicroservice(options: {\n useFactory: (...args: any[]) => Promise<DatabaseModuleOptions> | DatabaseModuleOptions;\n inject?: any[];\n }): DynamicModule {\n return this.createDynamicModule(options, 'microservice');\n }\n\n /**\n * Internal helper to create dynamic module with conditional interceptor registration\n *\n * @param options Configuration options\n * @param mode Mode of operation (gateway or microservice)\n * @returns Dynamic module configuration\n */\n private static createDynamicModule(\n options: {\n useFactory: (...args: any[]) => Promise<DatabaseModuleOptions> | DatabaseModuleOptions;\n inject?: any[];\n },\n mode: 'server' | 'microservice',\n ): DynamicModule {\n const asyncProvider: Provider = {\n provide: DATABASE_MODULE_OPTIONS,\n useFactory: options.useFactory,\n inject: options.inject || [],\n };\n\n const providers: Provider[] = [\n asyncProvider,\n TenantContextService,\n PrimaryDatabaseService,\n TenantDatabaseService,\n ];\n\n // Conditionally add interceptor based on mode\n if (mode === 'server') {\n providers.push({\n provide: APP_INTERCEPTOR,\n useClass: TenantContextInterceptor,\n });\n } else {\n providers.push({\n provide: APP_INTERCEPTOR,\n useClass: MessageTenantContextInterceptor,\n });\n }\n\n return {\n module: DatabaseModule,\n imports: [RequestModule], // Import RequestModule for gateway interceptor\n providers,\n exports: [TenantDatabaseService, TenantContextService, PrimaryDatabaseService, asyncProvider],\n };\n }\n}\n","import {\n CallHandler,\n ExecutionContext,\n Injectable,\n Logger,\n NestInterceptor,\n Scope,\n} from '@nestjs/common';\nimport { Observable } from 'rxjs';\nimport { tap } from 'rxjs/operators';\nimport type { TenantInfo } from '../interfaces';\nimport { TenantContextService } from '../services/tenant-context.service';\n\n/**\n * Interceptor that extracts tenant context from RabbitMQ messages (Microservice Mode)\n *\n * This interceptor:\n * 1. Extracts tenant info from RabbitMQ message payload\n * 2. Sets it in REQUEST-SCOPED TenantContextService\n * 3. Cleans up after message is processed\n *\n * Expected message format:\n * {\n * dto: { ... },\n * tenant: {\n * tenantId: 'abc-123',\n * tenantSlug: 'acme',\n * tenantType: 'ENTERPRISE',\n * databaseHost: 'enterprise-1.aws.com',\n * databaseName: 'acme_db',\n * ...\n * }\n * }\n *\n * @example\n * // Automatically registered by DatabaseModule.forMicroservice()\n * // No manual registration needed\n * DatabaseModule.forMicroservice({\n * inject: [ConfigService],\n * useFactory: (config: ConfigService) => ({\n * prismaClientConstructor: PrismaClient,\n * }),\n * })\n */\n@Injectable({ scope: Scope.REQUEST })\nexport class MessageTenantContextInterceptor implements NestInterceptor {\n private readonly logger = new Logger(MessageTenantContextInterceptor.name);\n\n constructor(private readonly tenantContext: TenantContextService) {}\n\n intercept(context: ExecutionContext, next: CallHandler): Observable<any> {\n const contextType = context.getType();\n\n // Only handle RabbitMQ/microservice messages\n if (contextType === 'rpc') {\n const rpcContext = context.switchToRpc();\n const payload = rpcContext.getData();\n\n // Extract tenant from message payload\n if (payload && payload.tenant) {\n const tenant = payload.tenant as TenantInfo;\n\n this.logger.debug(`Setting tenant context from message: ${tenant.subdomain}`);\n\n try {\n this.tenantContext.setTenant(tenant);\n this.logger.log(`Tenant context set: ${tenant.subdomain} (${tenant.type})`);\n } catch (error) {\n this.logger.error('Failed to set tenant context from message', error);\n }\n } else {\n this.logger.warn('Message payload missing tenant information');\n }\n }\n\n // Execute handler and clean up after\n return next.handle().pipe(\n tap({\n next: () => {\n this.cleanupContext();\n },\n error: () => {\n this.cleanupContext();\n },\n complete: () => {\n this.cleanupContext();\n },\n }),\n );\n }\n\n /**\n * Clean up tenant context after message is processed\n */\n private cleanupContext(): void {\n if (this.tenantContext.hasTenant()) {\n const tenant = this.tenantContext.getTenantIdSafe();\n this.tenantContext.clearTenant();\n this.logger.debug(`Cleaned up tenant context: ${tenant}`);\n }\n }\n}\n","import { Injectable, Scope, UnauthorizedException } from '@nestjs/common';\nimport { TenantInfo } from '../interfaces';\n\n/**\n * Request-scoped service that holds tenant context for the current request or RabbitMQ message\n *\n * IMPORTANT: This service is REQUEST-SCOPED, meaning NestJS creates a new instance\n * for each HTTP request or RabbitMQ message. This ensures tenant isolation and\n * prevents cross-tenant data leaks in concurrent scenarios.\n *\n * @example\n * // In a controller or service\n * constructor(private readonly tenantContext: TenantContextService) {}\n *\n * async handleRequest() {\n * const tenant = this.tenantContext.getTenant();\n * console.log(`Processing request for tenant: ${tenant.tenantSlug}`);\n * }\n */\n@Injectable({ scope: Scope.REQUEST })\nexport class TenantContextService {\n private tenantInfo: TenantInfo | null = null;\n\n /**\n * Set tenant information for this request/message\n *\n * This is typically called by:\n * - TenantContextInterceptor (for HTTP requests in gateway)\n * - MessageTenantContextInterceptor (for RabbitMQ messages in microservices)\n * - Manual context setup in message handlers\n *\n * @param tenantInfo Complete tenant information\n * @throws Error if tenant context is already set (prevents accidental overwrites)\n */\n setTenant(tenantInfo: TenantInfo): void {\n if (this.tenantInfo) {\n throw new Error('Tenant context already set for this request');\n }\n this.tenantInfo = tenantInfo;\n }\n\n /**\n * Get tenant information for this request/message\n *\n * @returns Tenant information\n * @throws UnauthorizedException if tenant context hasn't been set\n */\n getTenant(): TenantInfo {\n if (!this.tenantInfo) {\n throw new UnauthorizedException('Tenant context not set');\n }\n return this.tenantInfo;\n }\n\n /**\n * Check if tenant context has been set\n *\n * @returns true if tenant context is available\n */\n hasTenant(): boolean {\n return this.tenantInfo !== null;\n }\n\n /**\n * Clear tenant context\n *\n * This is useful for cleanup in RabbitMQ message handlers\n * after the message has been processed.\n *\n * HTTP requests don't need manual cleanup as the service\n * instance is destroyed when the request ends.\n */\n clearTenant(): void {\n this.tenantInfo = null;\n }\n\n /**\n * Get tenant ID safely (returns null if not set)\n *\n * @returns Tenant ID or null\n */\n getTenantIdSafe(): string | null {\n return this.tenantInfo?.id ?? null;\n }\n\n /**\n * Get tenant subdomain safely (returns null if not set)\n *\n * @returns Tenant subdomain or null\n */\n getTenantSubdomainSafe(): string | null {\n return this.tenantInfo?.subdomain ?? null;\n }\n}\n","import {\n CallHandler,\n ExecutionContext,\n Injectable,\n Logger,\n NestInterceptor,\n Scope,\n UnauthorizedException,\n} from '@nestjs/common';\nimport { Reflector } from '@nestjs/core';\nimport type { FastifyRequest } from 'fastify';\nimport { Observable } from 'rxjs';\nimport { RequestService } from '../../request';\nimport { PrimaryDatabaseService } from '../services/primary-database.service';\nimport { TenantContextService } from '../services/tenant-context.service';\n\n/**\n * Interceptor that extracts tenant context from HTTP requests (Gateway Mode)\n *\n * This interceptor runs BEFORE the controller and:\n * 1. Checks if endpoint is marked with @Public() decorator\n * 2. Extracts tenant identifier from request using RequestService\n * 3. For public endpoints without tenant info → skip tenant context setup\n * 4. For all other requests → queries primary database for tenant configuration\n * 5. Stores tenant info in REQUEST-SCOPED TenantContextService\n *\n * Tenant resolution order:\n * - First: x-tenant-id header\n * - Fallback: x-subdomain header\n *\n * Public Endpoints:\n * - Endpoints marked with @Public() decorator can work with OR without tenant context\n * - If no tenant info in headers → skip tenant context setup (useful for OAuth, registration)\n * - If tenant info present in headers → setup tenant context (multi-tenant public APIs)\n *\n * Only used in API Gateway. Microservices use MessageTenantContextInterceptor instead.\n *\n * @example\n * // Public endpoint without tenant (OAuth callback, registration)\n * @Public()\n * @Get('onboarding/oauth/google')\n * async oauthGoogle() { ... }\n * // No x-tenant-id header → skips tenant context\n *\n * @example\n * // Public endpoint with tenant (multi-tenant public API)\n * @Public()\n * @Get('public/data')\n * async getPublicData() { ... }\n * // x-tenant-id: acme → sets tenant context for 'acme'\n */\n@Injectable({ scope: Scope.REQUEST })\nexport class TenantContextInterceptor implements NestInterceptor {\n private readonly logger = new Logger(TenantContextInterceptor.name);\n\n constructor(\n private readonly reflector: Reflector,\n private readonly tenantContext: TenantContextService,\n private readonly primaryDatabase: PrimaryDatabaseService,\n private readonly requestService: RequestService,\n ) {}\n\n async intercept(context: ExecutionContext, next: CallHandler): Promise<Observable<any>> {\n const request = context.switchToHttp().getRequest<FastifyRequest>();\n\n this.logger.debug(`Processing request: ${request.method} ${request.url}`);\n\n // Check if endpoint is marked as @Public()\n const isPublic = this.reflector.getAllAndOverride<boolean>('isPublic', [\n context.getHandler(),\n context.getClass(),\n ]);\n\n try {\n // Extract tenant identifier using RequestService (no code duplication)\n const tenantIdentifier = this.requestService.getTenantIdentifier();\n\n // Skip tenant context for public endpoints without tenant info\n if (isPublic && !tenantIdentifier) {\n this.logger.debug('Public endpoint without tenant identifier, skipping tenant context setup');\n return next.handle();\n }\n\n if (!tenantIdentifier) {\n throw new UnauthorizedException('Tenant identifier not found in request');\n }\n\n this.logger.debug(`Tenant identifier extracted: ${tenantIdentifier}`);\n\n // Special case: cloud.vritti.com (platform admin)\n if (tenantIdentifier === 'cloud') {\n this.logger.log('Cloud platform access detected, skipping tenant context setup');\n return next.handle();\n }\n\n // Query primary database for tenant configuration\n const tenantInfo = await this.primaryDatabase.getTenantInfo(tenantIdentifier);\n\n if (!tenantInfo) {\n this.logger.warn(`Invalid tenant: ${tenantIdentifier}`);\n throw new UnauthorizedException('Invalid tenant');\n }\n\n if (tenantInfo.status !== 'ACTIVE') {\n this.logger.warn(`Tenant ${tenantIdentifier} has status: ${tenantInfo.status}`);\n throw new UnauthorizedException(`Tenant is ${tenantInfo.status}`);\n }\n\n this.logger.debug(`Tenant config loaded: ${tenantInfo.subdomain} (${tenantInfo.type})`);\n\n // Store in REQUEST-SCOPED context\n this.tenantContext.setTenant(tenantInfo);\n\n // Also attach to request object for easy access\n (request as any).tenant = tenantInfo;\n\n this.logger.log(`Tenant context set: ${tenantInfo.subdomain}`);\n } catch (error) {\n this.logger.error('Failed to set tenant context', error);\n throw error;\n }\n\n return next.handle();\n }\n}\n","import {\n Inject,\n Injectable,\n InternalServerErrorException,\n Logger,\n OnModuleDestroy,\n} from '@nestjs/common';\nimport { DATABASE_MODULE_OPTIONS } from '../constants';\nimport type { DatabaseModuleOptions, TenantInfo } from '../interfaces';\nimport { TenantContextService } from './tenant-context.service';\n\n/**\n * Service responsible for managing tenant-scoped database connections\n *\n * This service:\n * - Maintains a connection pool (Map<cacheKey, DbClient>)\n * - Creates new connections dynamically based on tenant context\n * - Reuses existing connections for the same tenant\n * - Supports both cloud schemas and enterprise databases\n * - Automatically cleans up idle connections\n *\n * @example\n * // In a controller or service\n * const dbClient = await this.tenantDatabase.getDbClient<PrismaClient>();\n * const users = await dbClient.user.findMany();\n */\n@Injectable()\nexport class TenantDatabaseService implements OnModuleDestroy {\n private readonly logger = new Logger(TenantDatabaseService.name);\n\n /** Connection pool: Map<cacheKey, DbClient> */\n private readonly clients = new Map<string, any>();\n\n /** Track last usage time for idle connection cleanup */\n private readonly clientLastUsed = new Map<string, number>();\n\n /** Cleanup interval timer */\n private cleanupInterval?: NodeJS.Timeout;\n\n constructor(\n @Inject(DATABASE_MODULE_OPTIONS)\n private readonly options: DatabaseModuleOptions,\n private readonly tenantContext: TenantContextService,\n ) {\n this.startConnectionCleaner();\n }\n\n /**\n * Get the Prisma client for the current tenant's database.\n * This returns the tenant-scoped database client.\n *\n * @returns Tenant-scoped database client instance\n * @throws UnauthorizedException if tenant context not set\n * @throws InternalServerErrorException if connection fails\n */\n get prismaClient(): any {\n return this.getDbClient();\n }\n\n /**\n * Get tenant-scoped database client for the current request/message\n *\n * This method:\n * 1. Gets tenant info from TenantContextService\n * 2. Builds a connection URL based on tenant type\n * 3. Returns cached client if exists, otherwise creates new one\n *\n * @returns Promise<Database client instance>\n * @throws UnauthorizedException if tenant context not set\n * @throws InternalServerErrorException if connection fails\n *\n * @example\n * const dbClient = await tenantDatabase.getDbClient<PrismaClient>();\n * const users = await dbClient.user.findMany();\n */\n private async getDbClient<T = any>(): Promise<T> {\n const tenant = this.tenantContext.getTenant();\n\n const cacheKey = this.buildCacheKey(tenant);\n\n // Check if connection already exists\n if (this.clients.has(cacheKey)) {\n this.clientLastUsed.set(cacheKey, Date.now());\n this.logger.debug(`Reusing cached connection: ${cacheKey}`);\n return this.clients.get(cacheKey) as T;\n }\n\n // Create new connection\n this.logger.log(`Creating new database connection: ${cacheKey}`);\n const client = await this.createDbClient(tenant);\n this.clients.set(cacheKey, client);\n this.clientLastUsed.set(cacheKey, Date.now());\n\n return client as T;\n }\n\n /**\n * Create a new database client for the given tenant\n */\n private async createDbClient(tenant: TenantInfo): Promise<any> {\n try {\n // Build tenant-specific database URL\n const databaseUrl = this.buildTenantDbUrl(tenant);\n\n // Load Prisma client constructor\n const PrismaClient = await this.options.prismaClientConstructor;\n\n // Create new instance with tenant-specific connection\n const client = new PrismaClient({\n datasources: {\n db: { url: databaseUrl },\n },\n log: ['error', 'warn'],\n });\n\n await client.$connect();\n this.logger.log(`Connected to database for tenant: ${tenant.subdomain}`);\n\n return client;\n } catch (error) {\n this.logger.error(\n `Failed to create database connection for tenant: ${tenant.subdomain}`,\n error,\n );\n throw new InternalServerErrorException('Failed to connect to tenant database');\n }\n }\n\n /**\n * Build connection URL for enterprise tenant (dedicated database)\n */\n private buildTenantDbUrl(tenant: TenantInfo): string {\n const {\n databaseHost,\n databasePort,\n databaseName,\n databaseUsername,\n databasePassword,\n databaseSslMode,\n } = tenant;\n\n if (!databaseHost || !databaseName || !databaseUsername) {\n throw new Error(`Enterprise tenant ${tenant.subdomain} missing database configuration`);\n }\n\n const port = databasePort || 5432;\n const sslMode = databaseSslMode || 'require';\n const connectionUrl = `postgresql://${databaseUsername}:${databasePassword}@${databaseHost}:${port}/${databaseName}?sslmode=${sslMode}`;\n\n this.logger.debug(`Enterprise connection URL: ${this.maskPassword(connectionUrl)}`);\n\n return connectionUrl;\n }\n\n /**\n * Build cache key for connection pooling\n */\n private buildCacheKey(tenant: TenantInfo): string {\n return `${tenant.type}:${tenant.databaseName}@${tenant.databaseHost}`;\n }\n\n /**\n * Start periodic cleanup of idle connections\n */\n private startConnectionCleaner(): void {\n const interval = this.options.connectionCacheTTL || 300000; // 5 minutes\n\n this.cleanupInterval = setInterval(() => {\n this.cleanupIdleConnections();\n }, interval);\n\n this.logger.log(`Connection cleanup scheduled every ${interval / 1000} seconds`);\n }\n\n /**\n * Clean up idle connections that haven't been used recently\n */\n private cleanupIdleConnections(): void {\n const now = Date.now();\n const maxIdle = this.options.connectionCacheTTL || 300000;\n\n let cleaned = 0;\n\n for (const [key, lastUsed] of this.clientLastUsed.entries()) {\n if (now - lastUsed > maxIdle) {\n const client = this.clients.get(key);\n if (client) {\n client\n .$disconnect()\n .then(() => {\n this.logger.debug(`Cleaned up idle connection: ${key}`);\n })\n .catch((error: any) => {\n this.logger.error(`Error disconnecting idle client: ${key}`, error);\n });\n\n this.clients.delete(key);\n this.clientLastUsed.delete(key);\n cleaned++;\n }\n }\n }\n\n if (cleaned > 0) {\n this.logger.log(`Cleaned up ${cleaned} idle connections`);\n }\n }\n\n /**\n * Get current connection pool statistics\n */\n getPoolStats(): {\n activeConnections: number;\n tenants: string[];\n } {\n return {\n activeConnections: this.clients.size,\n tenants: Array.from(this.clients.keys()),\n };\n }\n\n /**\n * Mask password in connection URL for logging\n */\n private maskPassword(url: string): string {\n return url.replace(/:([^@]+)@/, ':****@');\n }\n\n async onModuleDestroy() {\n // Stop cleanup interval\n if (this.cleanupInterval) {\n clearInterval(this.cleanupInterval);\n }\n\n // Disconnect all clients\n this.logger.log(`Disconnecting ${this.clients.size} database connections`);\n\n const disconnectPromises = Array.from(this.clients.entries()).map(async ([key, client]) => {\n try {\n await client.$disconnect();\n this.logger.debug(`Disconnected: ${key}`);\n } catch (error) {\n this.logger.error(`Error disconnecting client: ${key}`, error);\n }\n });\n\n await Promise.all(disconnectPromises);\n this.logger.log('All database connections closed');\n }\n}\n","import { Logger } from '@nestjs/common';\nimport { PrimaryDatabaseService } from '../services/primary-database.service';\n\n/**\n * Abstract base repository for primary database operations.\n * Provides common CRUD operations with automatic logging.\n *\n * @template TModel - The Prisma model type\n * @template TCreateDTO - DTO type for create operations\n * @template TUpdateDTO - DTO type for update operations\n *\n * @example\n * ```typescript\n * // Using the model delegate pattern (RECOMMENDED)\n * // Type-safe, IDE autocomplete, refactor-friendly\n * @Injectable()\n * export class UserRepository extends PrimaryBaseRepository<\n * User,\n * CreateUserDto,\n * UpdateUserDto\n * > {\n * constructor(database: PrimaryDatabaseService) {\n * super(database, (prisma) => prisma.user); // ✅ Type-safe with autocomplete!\n * }\n *\n * // Add custom methods as needed\n * async findByEmail(email: string): Promise<User | null> {\n * return this.model.findUnique({ where: { email } });\n * }\n * }\n *\n * // Short syntax is also supported\n * @Injectable()\n * export class TenantRepository extends PrimaryBaseRepository<Tenant> {\n * constructor(database: PrimaryDatabaseService) {\n * super(database, (p) => p.tenant); // ✅ Concise!\n * }\n * }\n *\n * // Works with complex model names\n * @Injectable()\n * export class EmailVerificationRepository extends PrimaryBaseRepository<EmailVerification> {\n * constructor(database: PrimaryDatabaseService) {\n * super(database, (p) => p.emailVerification); // ✅ Matches Prisma naming\n * }\n * }\n * ```\n */\nexport abstract class PrimaryBaseRepository<\n TModel,\n TCreateDTO = any,\n TUpdateDTO = any,\n> {\n protected readonly logger: Logger;\n private readonly modelGetter: (prisma: any) => any;\n\n /**\n * Lazy getter for Prisma client.\n * Accesses the client from the database service only when needed,\n * avoiding initialization timing issues with NestJS lifecycle.\n */\n protected get prisma(): any {\n return this.database.prismaClient;\n }\n\n /**\n * Lazy getter for the Prisma model delegate.\n * Returns the specific model (e.g., prisma.user, prisma.tenant) for this repository.\n */\n protected get model(): any {\n return this.modelGetter(this.prisma);\n }\n\n /**\n * Create a new repository instance\n *\n * @param database - The primary database service\n * @param getModel - Function that returns the Prisma model delegate from the client\n *\n * @example\n * ```typescript\n * // Standard usage with full parameter name\n * constructor(database: PrimaryDatabaseService) {\n * super(database, (prisma) => prisma.user);\n * }\n *\n * // Short syntax\n * constructor(database: PrimaryDatabaseService) {\n * super(database, (p) => p.user);\n * }\n *\n * // Complex model names\n * constructor(database: PrimaryDatabaseService) {\n * super(database, (p) => p.emailVerification);\n * }\n * ```\n */\n constructor(\n protected readonly database: PrimaryDatabaseService,\n getModel: (prisma: any) => any,\n ) {\n this.logger = new Logger(this.constructor.name);\n this.modelGetter = getModel;\n this.logger.debug(`Initialized ${this.constructor.name}`);\n }\n\n /**\n * Create a new record\n *\n * @param data - The data to create the record with\n * @returns Promise resolving to the created record\n *\n * @example\n * ```typescript\n * const user = await userRepository.create({\n * email: 'user@example.com',\n * name: 'John Doe'\n * });\n * ```\n */\n async create(data: TCreateDTO): Promise<TModel> {\n this.logger.log('Creating record');\n return await this.model.create({ data });\n }\n\n /**\n * Find a single record by ID\n *\n * @param id - The record ID\n * @returns Promise resolving to the record or null if not found\n *\n * @example\n * ```typescript\n * const user = await userRepository.findById('user-id-123');\n * ```\n */\n async findById(id: string): Promise<TModel | null> {\n this.logger.debug(`Finding record by ID: ${id}`);\n return await this.model.findUnique({ where: { id } });\n }\n\n /**\n * Find a single record with custom where clause\n *\n * @param where - The where clause or findUnique args\n * @returns Promise resolving to the record or null if not found\n *\n * @example\n * ```typescript\n * // Simple where clause\n * const user = await userRepository.findOne({ email: 'user@example.com' });\n *\n * // With include\n * const user = await userRepository.findOne({\n * where: { email: 'user@example.com' },\n * include: { posts: true }\n * });\n * ```\n */\n async findOne(where: any): Promise<TModel | null> {\n this.logger.debug('Finding record with custom query');\n return await this.model.findUnique(\n typeof where === 'object' && 'where' in where ? where : { where },\n );\n }\n\n /**\n * Find multiple records\n *\n * @param args - Prisma findMany arguments (where, orderBy, take, skip, etc.)\n * @returns Promise resolving to an array of records\n *\n * @example\n * ```typescript\n * // Find all users\n * const users = await userRepository.findMany();\n *\n * // Find with filtering and pagination\n * const users = await userRepository.findMany({\n * where: { status: 'ACTIVE' },\n * orderBy: { createdAt: 'desc' },\n * take: 10,\n * skip: 0\n * });\n * ```\n */\n async findMany(args?: any): Promise<TModel[]> {\n this.logger.debug('Finding multiple records');\n return await this.model.findMany(args);\n }\n\n /**\n * Update a record by ID\n *\n * @param id - The record ID\n * @param data - The data to update\n * @returns Promise resolving to the updated record\n *\n * @example\n * ```typescript\n * const user = await userRepository.update('user-id-123', {\n * name: 'Jane Doe'\n * });\n * ```\n */\n async update(id: string, data: TUpdateDTO): Promise<TModel> {\n this.logger.log(`Updating record with ID: ${id}`);\n return await this.model.update({ where: { id }, data });\n }\n\n /**\n * Update multiple records\n *\n * @param where - The where clause to match records\n * @param data - The data to update\n * @returns Promise resolving to the count of updated records\n *\n * @example\n * ```typescript\n * const result = await userRepository.updateMany(\n * { status: 'PENDING' },\n * { status: 'ACTIVE' }\n * );\n * console.log(`Updated ${result.count} users`);\n * ```\n */\n async updateMany(where: any, data: TUpdateDTO): Promise<{ count: number }> {\n this.logger.log('Updating multiple records');\n return await this.model.updateMany({ where, data });\n }\n\n /**\n * Delete a record by ID\n *\n * @param id - The record ID\n * @returns Promise resolving to the deleted record\n *\n * @example\n * ```typescript\n * const user = await userRepository.delete('user-id-123');\n * ```\n */\n async delete(id: string): Promise<TModel> {\n this.logger.log(`Deleting record with ID: ${id}`);\n return await this.model.delete({ where: { id } });\n }\n\n /**\n * Delete multiple records\n *\n * @param where - The where clause to match records\n * @returns Promise resolving to the count of deleted records\n *\n * @example\n * ```typescript\n * const result = await userRepository.deleteMany({\n * status: 'INACTIVE',\n * createdAt: { lt: new Date('2020-01-01') }\n * });\n * console.log(`Deleted ${result.count} users`);\n * ```\n */\n async deleteMany(where: any): Promise<{ count: number }> {\n this.logger.log('Deleting multiple records');\n return await this.model.deleteMany({ where });\n }\n\n /**\n * Count records\n *\n * @param where - Optional where clause to filter records\n * @returns Promise resolving to the count of records\n *\n * @example\n * ```typescript\n * // Count all users\n * const total = await userRepository.count();\n *\n * // Count active users\n * const activeCount = await userRepository.count({ status: 'ACTIVE' });\n * ```\n */\n async count(where?: any): Promise<number> {\n this.logger.debug('Counting records');\n return await this.model.count({ where });\n }\n\n /**\n * Check if a record exists\n *\n * @param where - The where clause to match records\n * @returns Promise resolving to true if at least one record exists, false otherwise\n *\n * @example\n * ```typescript\n * const emailExists = await userRepository.exists({\n * email: 'user@example.com'\n * });\n * ```\n */\n async exists(where: any): Promise<boolean> {\n const count = await this.model.count({ where });\n return count > 0;\n }\n}\n","import { Logger } from '@nestjs/common';\nimport { TenantDatabaseService } from '../services/tenant-database.service';\n\n/**\n * Abstract base repository for tenant-scoped database operations.\n * All operations are automatically scoped to the current tenant.\n *\n * @template TModel - The Prisma model type\n * @template TCreateDTO - DTO type for create operations\n * @template TUpdateDTO - DTO type for update operations\n *\n * @example\n * ```typescript\n * // Using the model delegate pattern (RECOMMENDED)\n * // Type-safe, IDE autocomplete, refactor-friendly\n * @Injectable()\n * export class ProductRepository extends TenantBaseRepository<\n * Product,\n * CreateProductDto,\n * UpdateProductDto\n * > {\n * constructor(database: TenantDatabaseService) {\n * super(database, (prisma) => prisma.product); // ✅ Type-safe with autocomplete!\n * }\n *\n * // Add custom methods as needed\n * async findBySku(sku: string): Promise<Product | null> {\n * return this.model.findUnique({ where: { sku } });\n * }\n * }\n *\n * // Short syntax is also supported\n * @Injectable()\n * export class OrderRepository extends TenantBaseRepository<Order> {\n * constructor(database: TenantDatabaseService) {\n * super(database, (p) => p.order); // ✅ Concise!\n * }\n * }\n *\n * // Works with complex model names\n * @Injectable()\n * export class InventoryItemRepository extends TenantBaseRepository<InventoryItem> {\n * constructor(database: TenantDatabaseService) {\n * super(database, (p) => p.inventoryItem); // ✅ Matches Prisma naming\n * }\n * }\n * ```\n */\nexport abstract class TenantBaseRepository<\n TModel,\n TCreateDTO = any,\n TUpdateDTO = any,\n> {\n protected readonly logger: Logger;\n private readonly modelGetter: (prisma: any) => any;\n\n /**\n * Lazy getter for Prisma client.\n * Accesses the client from the database service only when needed,\n * avoiding initialization timing issues with NestJS lifecycle.\n */\n protected get prisma(): any {\n return this.database.prismaClient;\n }\n\n /**\n * Lazy getter for the Prisma model delegate.\n * Returns the specific model (e.g., prisma.product, prisma.order) for this repository.\n */\n protected get model(): any {\n return this.modelGetter(this.prisma);\n }\n\n /**\n * Create a new repository instance\n *\n * @param database - The tenant database service\n * @param getModel - Function that returns the Prisma model delegate from the client\n *\n * @example\n * ```typescript\n * // Standard usage with full parameter name\n * constructor(database: TenantDatabaseService) {\n * super(database, (prisma) => prisma.product);\n * }\n *\n * // Short syntax\n * constructor(database: TenantDatabaseService) {\n * super(database, (p) => p.product);\n * }\n *\n * // Complex model names\n * constructor(database: TenantDatabaseService) {\n * super(database, (p) => p.inventoryItem);\n * }\n * ```\n */\n constructor(\n protected readonly database: TenantDatabaseService,\n getModel: (prisma: any) => any,\n ) {\n this.logger = new Logger(this.constructor.name);\n this.modelGetter = getModel;\n this.logger.debug(`Initialized ${this.constructor.name}`);\n }\n\n /**\n * Create a new record\n *\n * @param data - The data to create the record with\n * @returns Promise resolving to the created record\n *\n * @example\n * ```typescript\n * const product = await productRepository.create({\n * name: 'Widget',\n * sku: 'WDG-001',\n * price: 9.99\n * });\n * ```\n */\n async create(data: TCreateDTO): Promise<TModel> {\n this.logger.log('Creating record');\n return await this.model.create({ data });\n }\n\n /**\n * Find a single record by ID\n *\n * @param id - The record ID\n * @returns Promise resolving to the record or null if not found\n *\n * @example\n * ```typescript\n * const product = await productRepository.findById('product-id-123');\n * ```\n */\n async findById(id: string): Promise<TModel | null> {\n this.logger.debug(`Finding record by ID: ${id}`);\n return await this.model.findUnique({ where: { id } });\n }\n\n /**\n * Find a single record with custom where clause\n *\n * @param where - The where clause or findUnique args\n * @returns Promise resolving to the record or null if not found\n *\n * @example\n * ```typescript\n * // Simple where clause\n * const product = await productRepository.findOne({ sku: 'WDG-001' });\n *\n * // With include\n * const product = await productRepository.findOne({\n * where: { sku: 'WDG-001' },\n * include: { category: true }\n * });\n * ```\n */\n async findOne(where: any): Promise<TModel | null> {\n this.logger.debug('Finding record with custom query');\n return await this.model.findUnique(\n typeof where === 'object' && 'where' in where ? where : { where },\n );\n }\n\n /**\n * Find multiple records\n *\n * @param args - Prisma findMany arguments (where, orderBy, take, skip, etc.)\n * @returns Promise resolving to an array of records\n *\n * @example\n * ```typescript\n * // Find all products\n * const products = await productRepository.findMany();\n *\n * // Find with filtering and pagination\n * const products = await productRepository.findMany({\n * where: { status: 'ACTIVE' },\n * orderBy: { createdAt: 'desc' },\n * take: 10,\n * skip: 0\n * });\n * ```\n */\n async findMany(args?: any): Promise<TModel[]> {\n this.logger.debug('Finding multiple records');\n return await this.model.findMany(args);\n }\n\n /**\n * Update a record by ID\n *\n * @param id - The record ID\n * @param data - The data to update\n * @returns Promise resolving to the updated record\n *\n * @example\n * ```typescript\n * const product = await productRepository.update('product-id-123', {\n * price: 12.99\n * });\n * ```\n */\n async update(id: string, data: TUpdateDTO): Promise<TModel> {\n this.logger.log(`Updating record with ID: ${id}`);\n return await this.model.update({ where: { id }, data });\n }\n\n /**\n * Update multiple records\n *\n * @param where - The where clause to match records\n * @param data - The data to update\n * @returns Promise resolving to the count of updated records\n *\n * @example\n * ```typescript\n * const result = await productRepository.updateMany(\n * { status: 'PENDING' },\n * { status: 'ACTIVE' }\n * );\n * console.log(`Updated ${result.count} products`);\n * ```\n */\n async updateMany(where: any, data: TUpdateDTO): Promise<{ count: number }> {\n this.logger.log('Updating multiple records');\n return await this.model.updateMany({ where, data });\n }\n\n /**\n * Delete a record by ID\n *\n * @param id - The record ID\n * @returns Promise resolving to the deleted record\n *\n * @example\n * ```typescript\n * const product = await productRepository.delete('product-id-123');\n * ```\n */\n async delete(id: string): Promise<TModel> {\n this.logger.log(`Deleting record with ID: ${id}`);\n return await this.model.delete({ where: { id } });\n }\n\n /**\n * Delete multiple records\n *\n * @param where - The where clause to match records\n * @returns Promise resolving to the count of deleted records\n *\n * @example\n * ```typescript\n * const result = await productRepository.deleteMany({\n * status: 'INACTIVE',\n * createdAt: { lt: new Date('2020-01-01') }\n * });\n * console.log(`Deleted ${result.count} products`);\n * ```\n */\n async deleteMany(where: any): Promise<{ count: number }> {\n this.logger.log('Deleting multiple records');\n return await this.model.deleteMany({ where });\n }\n\n /**\n * Count records\n *\n * @param where - Optional where clause to filter records\n * @returns Promise resolving to the count of records\n *\n * @example\n * ```typescript\n * // Count all products\n * const total = await productRepository.count();\n *\n * // Count active products\n * const activeCount = await productRepository.count({ status: 'ACTIVE' });\n * ```\n */\n async count(where?: any): Promise<number> {\n this.logger.debug('Counting records');\n return await this.model.count({ where });\n }\n\n /**\n * Check if a record exists\n *\n * @param where - The where clause to match records\n * @returns Promise resolving to true if at least one record exists, false otherwise\n *\n * @example\n * ```typescript\n * const skuExists = await productRepository.exists({\n * sku: 'WDG-001'\n * });\n * ```\n */\n async exists(where: any): Promise<boolean> {\n const count = await this.model.count({ where });\n return count > 0;\n }\n}\n","import { createParamDecorator, ExecutionContext } from '@nestjs/common';\nimport { TenantInfo } from '../interfaces';\nimport { TenantContextService } from '../services/tenant-context.service';\n\n/**\n * Parameter decorator that injects tenant metadata into controller method\n *\n * This decorator retrieves tenant information (ID, slug, type, etc.)\n * from the REQUEST-SCOPED TenantContextService.\n *\n * Useful for:\n * - Logging tenant-specific information\n * - Implementing tenant-specific business logic\n * - Auditing and tracking\n * - Conditional feature flags\n *\n * @returns TenantInfo object with tenant metadata\n *\n * @example\n * // Access tenant metadata\n * @Get('info')\n * async getTenantInfo(@Tenant() tenant: TenantInfo) {\n * return {\n * id: tenant.id,\n * subdomain: tenant.subdomain,\n * type: tenant.type,\n * };\n * }\n *\n * @example\n * // Use for logging\n * @Post()\n * async createUser(\n * @Body() dto: CreateUserDto,\n * @Tenant() tenant: TenantInfo,\n * ) {\n * this.logger.log(`Creating user for tenant: ${tenant.subdomain}`);\n * // ...\n * }\n *\n * @example\n * // Conditional business logic\n * @Get('features')\n * async getFeatures(@Tenant() tenant: TenantInfo) {\n * if (tenant.type === 'ENTERPRISE') {\n * return ['feature-a', 'feature-b', 'feature-c'];\n * }\n * return ['feature-a'];\n * }\n */\nexport const Tenant = createParamDecorator((data: unknown, ctx: ExecutionContext): TenantInfo => {\n const request = ctx.switchToHttp().getRequest();\n\n // Get from TenantContextService\n const tenantContext = request.app?.get?.(TenantContextService);\n\n if (!tenantContext) {\n throw new Error('TenantContextService not found.');\n }\n\n return tenantContext.getTenant();\n});\n","import { SetMetadata } from '@nestjs/common';\n\n/**\n * Onboarding Decorator - Marks endpoints that require onboarding token\n *\n * Use this decorator on controllers or route handlers that should only be\n * accessible during the onboarding flow with JWT tokens containing type='onboarding'.\n *\n * These endpoints:\n * - Accept ONLY tokens with type='onboarding'\n * - Reject regular access tokens (type='access')\n * - Skip tenant validation and refresh token checks\n * - Only validate JWT signature and expiry\n *\n * Useful for:\n * - Email/phone verification during onboarding\n * - Onboarding status checks\n * - Resending OTPs during registration\n *\n * @example\n * // On a controller method\n * @Post('verify-email')\n * @Onboarding()\n * async verifyEmail(@Request() req, @Body() dto: VerifyEmailDto) {\n * const userId = req.user.id; // Available from VrittiAuthGuard\n * return this.service.verifyEmail(userId, dto.otp);\n * }\n *\n * @example\n * // Multiple onboarding endpoints\n * @Controller('onboarding')\n * export class OnboardingController {\n * @Post('verify-email')\n * @Onboarding()\n * async verifyEmail() { ... }\n *\n * @Post('resend-otp')\n * @Onboarding()\n * async resendOtp() { ... }\n * }\n */\nexport const Onboarding = () => SetMetadata('isOnboarding', true);\n","import { SetMetadata } from '@nestjs/common';\n\n/**\n * Public Decorator - Marks endpoints that don't require authentication\n *\n * Use this decorator on controllers or route handlers to bypass VrittiAuthGuard\n * tenant validation. Useful for:\n * - Login/signup endpoints\n * - Health checks\n * - Public documentation endpoints\n * - Webhook endpoints that don't require tenant context\n *\n * @example\n * // On a controller method\n * @Public()\n * @Post('auth/login')\n * async login(@Body() dto: LoginDto) {\n * return this.authService.login(dto);\n * }\n *\n * @example\n * // On an entire controller\n * @Public()\n * @Controller('health')\n * export class HealthController {\n * @Get()\n * check() {\n * return { status: 'ok' };\n * }\n * }\n */\nexport const Public = () => SetMetadata('isPublic', true);\n","import { Module } from '@nestjs/common';\nimport { CsrfGuard } from './guards/csrf.guard';\n\n/**\n * HTTP Module\n *\n * Provides HTTP utilities including:\n * - CSRF Guard for request protection\n * - HTTP Exception Filter for standardized error responses\n *\n * Usage:\n * Import this module to access HTTP guards and filters.\n * Guards and filters are registered globally in the main application.\n */\n@Module({\n providers: [CsrfGuard],\n exports: [CsrfGuard],\n})\nexport class HttpModule {}\n","import {\n CanActivate,\n ExecutionContext,\n ForbiddenException,\n Injectable,\n Logger,\n} from '@nestjs/common';\nimport { Reflector } from '@nestjs/core';\nimport { FastifyReply, FastifyRequest } from 'fastify';\n\n/**\n * CSRF Guard\n *\n * Global guard that automatically protects all state-changing requests (POST, PUT, PATCH, DELETE)\n * from CSRF attacks using Fastify's csrf-protection plugin.\n *\n * Flow:\n * 1. Skip safe methods (GET, HEAD, OPTIONS)\n * 2. Skip endpoints marked with @Public()\n * 3. Validate CSRF token for all other requests\n *\n * Token Sources (in priority order by @fastify/csrf-protection):\n * 1. req.headers['csrf-token']\n * 2. req.headers['xsrf-token']\n * 3. req.headers['x-csrf-token']\n * 4. req.headers['x-xsrf-token']\n * 5. req.body._csrf\n *\n * This guard should be registered globally in main.ts after CSRF plugin registration.\n */\n@Injectable()\nexport class CsrfGuard implements CanActivate {\n private readonly logger = new Logger(CsrfGuard.name);\n\n constructor(private reflector: Reflector) {}\n\n async canActivate(context: ExecutionContext): Promise<boolean> {\n const request = context.switchToHttp().getRequest<FastifyRequest>();\n const reply = context.switchToHttp().getResponse<FastifyReply>();\n\n // Skip CSRF check for safe methods (GET, HEAD, OPTIONS)\n const safeMethods = ['GET', 'HEAD', 'OPTIONS'];\n if (safeMethods.includes(request.method)) {\n return true;\n }\n\n\n // Validate CSRF token using Fastify's csrfProtection hook\n try {\n // Access the Fastify instance's CSRF protection\n const fastifyInstance = request.server as any;\n\n if (!fastifyInstance.csrfProtection) {\n this.logger.error(\n 'CSRF protection plugin not found. Ensure @fastify/csrf-protection is registered.',\n );\n throw new ForbiddenException('CSRF protection not configured');\n }\n\n // Call the CSRF protection hook\n // This validates the token from headers or body against the signed cookie\n await new Promise<void>((resolve, reject) => {\n fastifyInstance.csrfProtection(request, reply, (err?: Error) => {\n if (err) {\n reject(err);\n } else {\n resolve();\n }\n });\n });\n\n this.logger.debug(\n `CSRF validation successful for ${request.method} ${request.url}`,\n );\n\n return true;\n } catch (error) {\n this.logger.warn(\n `CSRF validation failed for ${request.method} ${request.url}: ${error instanceof Error ? error.message : 'Unknown error'}`,\n );\n\n throw new ForbiddenException({\n errors: [\n {\n field: 'csrf',\n message: 'Invalid or missing CSRF token',\n },\n ],\n message: 'CSRF validation failed',\n });\n }\n }\n}\n","import {\n ExceptionFilter,\n Catch,\n ArgumentsHost,\n HttpException,\n HttpStatus,\n Logger,\n} from '@nestjs/common';\nimport { FastifyReply } from 'fastify';\n\n/**\n * Error response format\n */\ninterface ErrorField {\n field: string;\n message: string;\n}\n\ninterface ErrorResponse {\n errors: ErrorField[];\n message?: string;\n statusCode: number;\n timestamp: string;\n path: string;\n}\n\n/**\n * Global HTTP Exception Filter\n *\n * Standardizes all error responses in the format:\n * {\n * errors: [{ field: string, message: string }],\n * message?: string,\n * statusCode: number,\n * timestamp: string,\n * path: string\n * }\n *\n * Handles:\n * - Validation errors (class-validator) - Converts to field-specific errors\n * - HTTP exceptions - Maps to standardized format\n * - Unknown errors - Returns generic 500 error\n */\n@Catch()\nexport class HttpExceptionFilter implements ExceptionFilter {\n private readonly logger = new Logger(HttpExceptionFilter.name);\n\n catch(exception: unknown, host: ArgumentsHost) {\n const ctx = host.switchToHttp();\n const reply = ctx.getResponse<FastifyReply>();\n const request = ctx.getRequest();\n\n let status = HttpStatus.INTERNAL_SERVER_ERROR;\n let errors: ErrorField[] = [];\n let message: string | undefined;\n\n // Handle HttpException (includes BadRequestException for validation)\n if (exception instanceof HttpException) {\n status = exception.getStatus();\n const exceptionResponse = exception.getResponse();\n\n if (typeof exceptionResponse === 'object' && exceptionResponse !== null) {\n const responseObj = exceptionResponse as any;\n\n // Handle class-validator validation errors\n if (Array.isArray(responseObj.message)) {\n errors = this.parseValidationErrors(responseObj.message);\n message = 'Validation failed';\n } else if (responseObj.message) {\n // Handle single error message\n errors = [\n {\n field: 'general',\n message: responseObj.message,\n },\n ];\n message = responseObj.message;\n }\n } else if (typeof exceptionResponse === 'string') {\n errors = [\n {\n field: 'general',\n message: exceptionResponse,\n },\n ];\n message = exceptionResponse;\n }\n } else if (exception instanceof Error) {\n // Handle regular Error objects\n this.logger.error(\n `Unhandled error: ${exception.message}`,\n exception.stack,\n );\n\n errors = [\n {\n field: 'general',\n message: 'Internal server error',\n },\n ];\n message = 'An unexpected error occurred';\n } else {\n // Handle unknown exceptions\n this.logger.error('Unknown exception type', exception);\n\n errors = [\n {\n field: 'general',\n message: 'Internal server error',\n },\n ];\n message = 'An unexpected error occurred';\n }\n\n // Build error response\n const errorResponse: ErrorResponse = {\n errors,\n message,\n statusCode: status,\n timestamp: new Date().toISOString(),\n path: request.url,\n };\n\n // Log error details\n if (status >= 500) {\n this.logger.error(\n `HTTP ${status} Error: ${JSON.stringify(errorResponse)}`,\n exception instanceof Error ? exception.stack : undefined,\n );\n } else {\n this.logger.warn(`HTTP ${status} Error: ${JSON.stringify(errorResponse)}`);\n }\n\n reply.status(status).send(errorResponse);\n }\n\n /**\n * Parse class-validator error messages into field-specific errors\n */\n private parseValidationErrors(messages: any[]): ErrorField[] {\n const errors: ErrorField[] = [];\n\n for (const msg of messages) {\n if (typeof msg === 'string') {\n // Simple string message\n errors.push({\n field: 'general',\n message: msg,\n });\n } else if (typeof msg === 'object' && msg.property && msg.constraints) {\n // class-validator error format\n const field = msg.property;\n const constraintMessages = Object.values(msg.constraints) as string[];\n\n for (const constraintMsg of constraintMessages) {\n errors.push({\n field,\n message: constraintMsg,\n });\n }\n }\n }\n\n return errors.length > 0\n ? errors\n : [{ field: 'general', message: 'Validation failed' }];\n }\n}\n"],"mappings":"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","names":["import_common","import_config","import_core","import_jwt","import_common","RequestService","request","getTenantIdentifier","getHeader","key","value","headers","Array","isArray","getAccessToken","authHeader","authorization","type","token","split","getRefreshToken","cookies","sessionId","error","getAllHeaders","scope","Scope","REQUEST","RequestModule","providers","RequestService","exports","import_common","import_core","import_common","DATABASE_MODULE_OPTIONS","Symbol","PrimaryDatabaseService","logger","Logger","name","primaryDbClient","tenantConfigCache","Map","cacheTTL","options","connectionCacheTTL","onModuleInit","primaryDb","initializePrimaryDbClient","PrimaryDbClient","prismaClientConstructor","databaseUrl","buildPrimaryDbUrl","datasources","db","url","log","$connect","error","InternalServerErrorException","Error","host","port","username","password","database","schema","sslMode","params","URLSearchParams","set","queryString","toString","debug","maskPassword","replace","getTenantInfo","tenantIdentifier","cached","get","tenant","findFirst","where","OR","id","subdomain","status","include","databaseConfig","warn","info","type","dbType","schemaName","dbSchema","undefined","databaseName","dbName","databaseHost","dbHost","databasePort","dbPort","databaseUsername","dbUsername","decrypt","databasePassword","dbPassword","databaseSslMode","dbSslMode","connectionPoolSize","cacheInfo","setTimeout","delete","clearTenantCache","config","clearAllCaches","size","clear","prismaClient","encrypted","onModuleDestroy","$disconnect","VrittiAuthGuard","logger","Logger","name","reflector","configService","jwtService","primaryDatabase","requestService","canActivate","context","request","switchToHttp","getRequest","isPublic","getAllAndOverride","getHandler","getClass","debug","isOnboarding","accessToken","getAccessToken","warn","UnauthorizedException","decodedToken","decode","type","validatedToken","validateAccessToken","userId","user","id","refreshToken","getRefreshToken","validateRefreshToken","tenantIdentifier","getTenantIdentifier","tenantInfo","getTenantInfo","status","subdomain","error","token","decoded","verify","exp","expiryTime","currentTime","Date","now","timeRemaining","Math","floor","jwtError","expiredAt","message","jwtSecret","get","validateRefreshTokenWithSecret","secret","algorithms","scope","Scope","REQUEST","AuthConfigModule","forRootAsync","module","imports","ConfigModule","RequestModule","JwtModule","registerAsync","inject","ConfigService","useFactory","config","secret","get","signOptions","algorithm","providers","provide","APP_GUARD","useClass","VrittiAuthGuard","exports","import_common","import_core","import_common","import_common","TenantContextService","tenantInfo","setTenant","Error","getTenant","UnauthorizedException","hasTenant","clearTenant","getTenantIdSafe","id","getTenantSubdomainSafe","subdomain","scope","Scope","REQUEST","MessageTenantContextInterceptor","logger","Logger","name","tenantContext","intercept","context","next","contextType","getType","rpcContext","switchToRpc","payload","getData","tenant","debug","subdomain","setTenant","log","type","error","warn","handle","pipe","tap","cleanupContext","complete","hasTenant","getTenantIdSafe","clearTenant","scope","Scope","REQUEST","import_common","import_core","TenantContextInterceptor","logger","Logger","name","reflector","tenantContext","primaryDatabase","requestService","intercept","context","next","request","switchToHttp","getRequest","debug","method","url","isPublic","getAllAndOverride","getHandler","getClass","tenantIdentifier","getTenantIdentifier","handle","UnauthorizedException","log","tenantInfo","getTenantInfo","warn","status","subdomain","type","setTenant","tenant","error","scope","Scope","REQUEST","import_common","TenantDatabaseService","logger","Logger","name","clients","Map","clientLastUsed","cleanupInterval","options","tenantContext","startConnectionCleaner","prismaClient","getDbClient","tenant","getTenant","cacheKey","buildCacheKey","has","set","Date","now","debug","get","log","client","createDbClient","databaseUrl","buildTenantDbUrl","PrismaClient","prismaClientConstructor","datasources","db","url","$connect","subdomain","error","InternalServerErrorException","databaseHost","databasePort","databaseName","databaseUsername","databasePassword","databaseSslMode","Error","port","sslMode","connectionUrl","maskPassword","type","interval","connectionCacheTTL","setInterval","cleanupIdleConnections","maxIdle","cleaned","key","lastUsed","entries","$disconnect","then","catch","delete","getPoolStats","activeConnections","size","tenants","Array","from","keys","replace","onModuleDestroy","clearInterval","disconnectPromises","map","Promise","all","DatabaseModule","forServer","options","createDynamicModule","forMicroservice","mode","asyncProvider","provide","DATABASE_MODULE_OPTIONS","useFactory","inject","providers","TenantContextService","PrimaryDatabaseService","TenantDatabaseService","push","APP_INTERCEPTOR","useClass","TenantContextInterceptor","MessageTenantContextInterceptor","module","imports","RequestModule","exports","import_common","PrimaryBaseRepository","logger","modelGetter","prisma","database","prismaClient","model","getModel","Logger","name","debug","create","data","log","findById","id","findUnique","where","findOne","findMany","args","update","updateMany","delete","deleteMany","count","exists","import_common","TenantBaseRepository","logger","modelGetter","prisma","database","prismaClient","model","getModel","Logger","name","debug","create","data","log","findById","id","findUnique","where","findOne","findMany","args","update","updateMany","delete","deleteMany","count","exists","import_common","Tenant","createParamDecorator","data","ctx","request","switchToHttp","getRequest","tenantContext","app","get","TenantContextService","Error","getTenant","import_common","Onboarding","SetMetadata","import_common","Public","SetMetadata","import_common","import_common","import_core","CsrfGuard","logger","Logger","name","reflector","canActivate","context","request","switchToHttp","getRequest","reply","getResponse","safeMethods","includes","method","fastifyInstance","server","csrfProtection","error","ForbiddenException","Promise","resolve","reject","err","debug","url","warn","Error","message","errors","field","HttpModule","providers","CsrfGuard","exports","import_common","HttpExceptionFilter","logger","Logger","name","catch","exception","host","ctx","switchToHttp","reply","getResponse","request","getRequest","status","HttpStatus","INTERNAL_SERVER_ERROR","errors","message","HttpException","getStatus","exceptionResponse","responseObj","Array","isArray","parseValidationErrors","field","Error","error","stack","errorResponse","statusCode","timestamp","Date","toISOString","path","url","JSON","stringify","undefined","warn","send","messages","msg","push","property","constraints","constraintMessages","Object","values","constraintMsg","length"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/auth/auth-config.module.ts","../src/request/request.module.ts","../src/request/services/request.service.ts","../src/auth/guards/vritti-auth.guard.ts","../src/database/services/primary-database.service.ts","../src/database/constants.ts","../src/database/database.module.ts","../src/database/interceptors/message-tenant-context.interceptor.ts","../src/database/services/tenant-context.service.ts","../src/database/interceptors/tenant-context.interceptor.ts","../src/database/services/tenant-database.service.ts","../src/database/repositories/primary-base.repository.ts","../src/database/repositories/tenant-base.repository.ts","../src/database/decorators/tenant.decorator.ts","../src/auth/decorators/onboarding.decorator.ts","../src/auth/decorators/public.decorator.ts","../src/http/http.module.ts","../src/http/guards/csrf.guard.ts","../src/http/filters/http-exception.filter.ts","../src/filters/http-exception.filter.ts","../src/exceptions/base-field.exception.ts","../src/exceptions/bad-request.exception.ts","../src/exceptions/unauthorized.exception.ts","../src/exceptions/forbidden.exception.ts","../src/exceptions/not-found.exception.ts","../src/exceptions/conflict.exception.ts","../src/exceptions/internal-server-error.exception.ts","../src/exceptions/validation.exception.ts","../src/exceptions/unprocessable-entity.exception.ts","../src/exceptions/too-many-requests.exception.ts","../src/exceptions/service-unavailable.exception.ts","../src/exceptions/method-not-allowed.exception.ts","../src/exceptions/gone.exception.ts","../src/exceptions/not-acceptable.exception.ts","../src/exceptions/request-timeout.exception.ts","../src/exceptions/payload-too-large.exception.ts","../src/exceptions/unsupported-media-type.exception.ts","../src/exceptions/not-implemented.exception.ts","../src/exceptions/bad-gateway.exception.ts"],"sourcesContent":["// Core modules\nexport { AuthConfigModule } from './auth/auth-config.module';\nexport { DatabaseModule } from './database/database.module';\n\n// Services\nexport { TenantContextService } from './database/services/tenant-context.service';\nexport { TenantDatabaseService } from './database/services/tenant-database.service';\nexport { PrimaryDatabaseService } from './database/services/primary-database.service';\n\n// Repositories\nexport { PrimaryBaseRepository } from './database/repositories/primary-base.repository';\nexport { TenantBaseRepository } from './database/repositories/tenant-base.repository';\n\n// Guards\nexport { VrittiAuthGuard } from './auth/guards/vritti-auth.guard';\n\n// Decorators\nexport { Tenant } from './database/decorators/tenant.decorator';\nexport { Onboarding } from './auth/decorators/onboarding.decorator';\nexport { Public } from './auth/decorators/public.decorator';\n\n// Interfaces\nexport * from './database/interfaces';\n\n// HTTP utilities\nexport { HttpModule } from './http/http.module';\nexport { CsrfGuard } from './http/guards/csrf.guard';\nexport { HttpExceptionFilter } from './http/filters/http-exception.filter';\n\n// RFC 7807 Filters\nexport * from './filters';\n\n// RFC 7807 Types (using named exports to avoid conflicts)\nexport type { ProblemDetails, ApiErrorResponse } from './types';\n\n// Exceptions\nexport * from './exceptions';\n","import { DynamicModule, Global, Module } from '@nestjs/common';\nimport { ConfigModule, ConfigService } from '@nestjs/config';\nimport { APP_GUARD } from '@nestjs/core';\nimport { JwtModule } from '@nestjs/jwt';\nimport { RequestModule } from '../request/request.module';\nimport { VrittiAuthGuard } from './guards/vritti-auth.guard';\n\n/**\n * Global authentication configuration module\n *\n * This module provides:\n * - JWT token verification (JwtModule)\n * - Global authentication guard (VrittiAuthGuard)\n * - Support for @Public and @Onboarding decorators\n *\n * ## Features:\n * - Automatically applies VrittiAuthGuard to all routes\n * - Configures JwtModule with JWT_SECRET from environment\n * - Exports JwtModule for token generation in services\n *\n * ## Usage in Application:\n *\n * @example\n * // In app.module.ts\n * @Module({\n * imports: [\n * ConfigModule.forRoot({ isGlobal: true }),\n *\n * // Auth configuration (global guard + JWT)\n * AuthConfigModule.forRootAsync(),\n *\n * // Database configuration (Gateway mode)\n * DatabaseModule.forServer({\n * useFactory: (config: ConfigService) => ({\n * primaryDb: {\n * host: config.get('PRIMARY_DB_HOST'),\n * // ... other config\n * },\n * prismaClientConstructor: PrismaClient,\n * }),\n * inject: [ConfigService],\n * }),\n * ],\n * })\n * export class AppModule {}\n *\n * ## Environment Variables Required:\n * - JWT_SECRET: Secret key to verify access tokens (required)\n * - JWT_REFRESH_SECRET: Secret key for refresh tokens (optional, falls back to JWT_SECRET)\n *\n * ## Bypass Authentication:\n *\n * @example\n * // Skip authentication on specific endpoints\n * @Public()\n * @Post('auth/login')\n * async login() { ... }\n *\n * @example\n * // Onboarding endpoints (only accept onboarding tokens)\n * @Onboarding()\n * @Post('onboarding/verify-email')\n * async verifyEmail(@Request() req) {\n * const userId = req.user.id; // Available from guard\n * ...\n * }\n */\n@Global()\n@Module({})\nexport class AuthConfigModule {\n /**\n * Register the auth module with async configuration\n *\n * This method:\n * 1. Configures JwtModule with JWT_SECRET from ConfigService\n * 2. Provides VrittiAuthGuard globally (applies to all routes)\n * 3. Exports JwtModule for use in other modules (e.g., for signing tokens)\n *\n * @returns Dynamic module configuration\n */\n static forRootAsync(): DynamicModule {\n return {\n module: AuthConfigModule,\n imports: [\n ConfigModule,\n RequestModule,\n JwtModule.registerAsync({\n imports: [ConfigModule],\n inject: [ConfigService],\n useFactory: (config: ConfigService) => ({\n secret: config.get<string>('JWT_SECRET'),\n signOptions: {\n algorithm: 'HS256',\n },\n }),\n }),\n ],\n providers: [\n {\n provide: APP_GUARD,\n useClass: VrittiAuthGuard,\n },\n ],\n exports: [\n JwtModule, // Export for use in other modules (e.g., generating tokens)\n ],\n };\n }\n}\n","import { Global, Module } from '@nestjs/common';\nimport { RequestService } from './services/request.service';\n\n@Global()\n@Module({\n providers: [RequestService],\n exports: [RequestService],\n})\nexport class RequestModule {}\n","import { Inject, Injectable, Scope } from '@nestjs/common';\nimport { REQUEST } from '@nestjs/core';\nimport type { FastifyRequest } from 'fastify';\n\n@Injectable({ scope: Scope.REQUEST })\nexport class RequestService {\n constructor(@Inject(REQUEST) private readonly request: FastifyRequest) {}\n\n /**\n * Extract tenant identifier from request headers\n * Priority: x-tenant-id > x-subdomain\n * @returns Tenant identifier or null if not found\n */\n getTenantIdentifier(): string | null {\n const getHeader = (key: string) => {\n const value = this.request.headers?.[key];\n return Array.isArray(value) ? value[0] : value;\n };\n\n return getHeader('x-tenant-id') || getHeader('x-subdomain') || null;\n }\n\n /**\n * Extract access token from Authorization header\n * Format: \"Bearer <token>\"\n * @returns Access token or null if not found\n */\n getAccessToken(): string | null {\n const authHeader = this.request.headers?.authorization;\n if (!authHeader) {\n return null;\n }\n const [type, token] = authHeader.split(' ') ?? [];\n return type === 'Bearer' && token ? token : null;\n }\n\n /**\n * Extract refresh token from session-id cookie\n * Cookie name: session-id\n * @returns Refresh token or null if not found\n */\n getRefreshToken(): string | null {\n try {\n const cookies = (this.request as unknown as { cookies?: Record<string, string> }).cookies;\n if (cookies && typeof cookies === 'object') {\n const sessionId = cookies['session-id'];\n if (sessionId) {\n return sessionId;\n }\n }\n return null;\n } catch (error: unknown) {\n return null;\n }\n }\n\n /**\n * Get a specific header value\n * @param key Header key\n * @returns Header value (string, array, or undefined)\n */\n getHeader(key: string): string | string[] | undefined {\n return this.request.headers?.[key];\n }\n\n /**\n * Get all headers\n * @returns Record of all headers\n */\n getAllHeaders(): FastifyRequest['headers'] {\n return this.request.headers || {};\n }\n}\n","import {\n CanActivate,\n ExecutionContext,\n Injectable,\n Logger,\n Scope,\n UnauthorizedException,\n} from '@nestjs/common';\nimport { ConfigService } from '@nestjs/config';\nimport { Reflector } from '@nestjs/core';\nimport { JwtService } from '@nestjs/jwt';\nimport { FastifyRequest } from 'fastify';\nimport * as jwt from 'jsonwebtoken';\nimport { PrimaryDatabaseService } from '../../database/services/primary-database.service';\nimport { RequestService } from '../../request/services/request.service';\n\n// Type for decoded JWT token\ninterface DecodedToken {\n userId?: string;\n type?: string;\n exp?: number;\n nbf?: number;\n iat?: number;\n [key: string]: unknown;\n}\n\n/**\n * Vritti Authentication Guard - Validates JWT tokens and tenant context\n *\n * This guard performs comprehensive validation and attaches user data to request.\n *\n * Validation Flow:\n * 1. Checks if endpoint is marked with @Public() decorator → skip all validation\n * 2. Checks if endpoint is marked with @Onboarding() decorator:\n * - Requires token type='onboarding'\n * - Validates JWT signature and expiry only\n * - Skips tenant and refresh token validation\n * - Attaches user data to request.user\n * 3. For regular endpoints (no decorator):\n * - Rejects tokens with type='onboarding'\n * - Validates access token (JWT signature, expiry, nbf)\n * - Validates refresh token from session-id cookie\n * - Validates tenant exists and is ACTIVE\n * - Attaches user data to request.user\n *\n * Token Format:\n * - Access Token: \"Authorization: Bearer <jwt_token>\"\n * - Refresh Token: \"session-id\" cookie\n *\n * Token Types:\n * - type='onboarding': Limited access during registration flow (@Onboarding endpoints only)\n * - type='access': Full access to authenticated endpoints\n *\n * Environment Variables Required:\n * - JWT_SECRET: Secret key to verify access tokens (required)\n * - JWT_REFRESH_SECRET: Secret key for refresh tokens (optional, falls back to JWT_SECRET)\n *\n * Error Responses:\n * - 401: Invalid/expired access token\n * - 401: Invalid/expired refresh token\n * - 401: Tenant not found or inactive\n * - 401: Tenant identifier not found\n * - 401: Token type mismatch (onboarding token on regular endpoint or vice versa)\n *\n * @example\n * // Automatically registered by AuthConfigModule.forRootAsync()\n * // No manual registration needed\n * //\n * // Internal registration uses useExisting pattern:\n * // providers: [\n * // VrittiAuthGuard,\n * // {\n * // provide: APP_GUARD,\n * // useExisting: VrittiAuthGuard,\n * // },\n * // ]\n *\n * @example\n * // Bypass guard with @Public() decorator\n * @Public()\n * @Post('auth/login')\n * async login(@Body() dto: LoginDto) { ... }\n *\n * @example\n * // Restrict to onboarding tokens with @Onboarding() decorator\n * @Onboarding()\n * @Post('onboarding/verify-email')\n * async verifyEmail(@Request() req) {\n * const userId = req.user.id; // Available from guard\n * ...\n * }\n */\n@Injectable({ scope: Scope.REQUEST })\nexport class VrittiAuthGuard implements CanActivate {\n private readonly logger = new Logger(VrittiAuthGuard.name);\n\n constructor(\n private readonly reflector: Reflector,\n private readonly configService: ConfigService,\n private readonly jwtService: JwtService,\n private readonly primaryDatabase: PrimaryDatabaseService,\n private readonly requestService: RequestService,\n ) {}\n\n async canActivate(context: ExecutionContext): Promise<boolean> {\n const request = context.switchToHttp().getRequest<FastifyRequest>();\n\n // Step 1: Check if endpoint is marked as @Public()\n const isPublic = this.reflector.getAllAndOverride<boolean>('isPublic', [\n context.getHandler(),\n context.getClass(),\n ]);\n\n if (isPublic) {\n this.logger.debug('Public endpoint detected, skipping authentication');\n return true;\n }\n\n // Step 2: Check if endpoint is marked as @Onboarding()\n const isOnboarding = this.reflector.getAllAndOverride<boolean>('isOnboarding', [\n context.getHandler(),\n context.getClass(),\n ]);\n\n try {\n // Extract and validate access token\n const accessToken = this.requestService.getAccessToken();\n if (!accessToken) {\n this.logger.warn('Access token not found in Authorization header');\n throw new UnauthorizedException('Access token not found');\n }\n\n // Decode token to check type (without full validation yet)\n const decodedToken = this.jwtService.decode(accessToken) as DecodedToken;\n if (!decodedToken) {\n this.logger.warn('Failed to decode access token');\n throw new UnauthorizedException('Invalid token format');\n }\n\n // Step 3: Handle @Onboarding endpoints\n if (isOnboarding) {\n // Only accept onboarding tokens\n if (decodedToken.type !== 'onboarding') {\n this.logger.warn('Onboarding endpoint requires onboarding token');\n throw new UnauthorizedException('This endpoint requires an onboarding token');\n }\n\n // Validate JWT signature and expiry only\n const validatedToken = this.validateAccessToken(accessToken);\n this.logger.debug('Onboarding token validated successfully');\n\n // Attach user data to request (use userId field from our tokens, fallback to sub for standard JWT)\n const userId = (validatedToken as any).userId;\n (request as any).user = { id: userId };\n\n return true;\n }\n\n // Step 4: Handle regular endpoints - reject onboarding tokens\n if (decodedToken.type === 'onboarding') {\n this.logger.warn('Regular endpoint accessed with onboarding token');\n throw new UnauthorizedException('Onboarding tokens cannot access this endpoint');\n }\n\n // Step 5: Validate access token\n const validatedToken = this.validateAccessToken(accessToken);\n this.logger.debug('Access token validated successfully');\n\n // Step 6: Validate refresh token from session-id cookie\n const refreshToken = this.requestService.getRefreshToken();\n if (!refreshToken) {\n this.logger.warn('Refresh token (session-id) not found in cookies');\n throw new UnauthorizedException('Refresh token not found');\n }\n\n this.validateRefreshToken(refreshToken);\n this.logger.debug('Refresh token validated successfully');\n\n // Step 7: Attach user data to request (use userId field from our tokens, fallback to sub for standard JWT)\n const userId = (validatedToken as any).userId;\n (request as any).user = { id: userId };\n\n // Step 8: Extract tenant identifier using RequestService\n const tenantIdentifier = this.requestService.getTenantIdentifier();\n\n if (!tenantIdentifier) {\n this.logger.warn('Tenant identifier not found in request');\n throw new UnauthorizedException('Tenant identifier not found');\n }\n\n this.logger.debug(`Tenant identifier extracted: ${tenantIdentifier}`);\n\n // Step 9: Skip database validation for platform admin (cloud.vritti.com)\n if (tenantIdentifier === 'cloud') {\n this.logger.debug('Platform admin access detected, skipping tenant database validation');\n return true;\n }\n\n // Step 10: Fetch tenant details from primary database\n const tenantInfo = await this.primaryDatabase.getTenantInfo(tenantIdentifier);\n\n if (!tenantInfo) {\n this.logger.warn(`Invalid tenant: ${tenantIdentifier}`);\n throw new UnauthorizedException('Invalid tenant');\n }\n\n // Step 11: Validate tenant is ACTIVE\n if (tenantInfo.status !== 'ACTIVE') {\n this.logger.warn(`Tenant ${tenantIdentifier} has status: ${tenantInfo.status}`);\n throw new UnauthorizedException(`Tenant is ${tenantInfo.status}`);\n }\n\n this.logger.debug(`Tenant validated: ${tenantInfo.subdomain} (${tenantInfo.type})`);\n\n return true;\n } catch (error) {\n if (error instanceof UnauthorizedException) {\n throw error;\n }\n this.logger.error('Unexpected error in auth guard', error);\n throw new UnauthorizedException('Authentication failed');\n }\n }\n\n /**\n * Validate access token with proper expiry checks\n * Throws UnauthorizedException if token is invalid or expired\n */\n private validateAccessToken(token: string): DecodedToken {\n try {\n const decoded = this.jwtService.verify<DecodedToken>(token);\n\n this.logger.debug(`Access token decoded for user: ${(decoded as any).userId}`);\n\n // Check expiry explicitly (JwtService already validates, but we log it)\n if (decoded.exp) {\n const expiryTime = decoded.exp * 1000; // Convert to milliseconds\n const currentTime = Date.now();\n\n const timeRemaining = expiryTime - currentTime;\n this.logger.debug(\n `Access token valid for ${Math.floor(timeRemaining / 1000)} more seconds`,\n );\n }\n\n return decoded;\n } catch (error: unknown) {\n if (error instanceof UnauthorizedException) {\n throw error;\n }\n\n const jwtError = error as { name?: string; message?: string; expiredAt?: string };\n if (jwtError?.name === 'TokenExpiredError') {\n this.logger.warn(`Access token expired at: ${jwtError?.expiredAt}`);\n throw new UnauthorizedException('Access token has expired');\n }\n\n if (jwtError?.name === 'JsonWebTokenError') {\n this.logger.warn(`Access token verification failed: ${jwtError?.message}`);\n throw new UnauthorizedException('Invalid access token');\n }\n\n if (jwtError?.name === 'NotBeforeError') {\n this.logger.warn('Access token used before valid (nbf claim)');\n throw new UnauthorizedException('Access token not yet valid');\n }\n\n this.logger.error('Unexpected error validating access token', error);\n throw new UnauthorizedException('Access token validation failed');\n }\n }\n\n /**\n * Validate refresh token with proper expiry checks\n * Throws UnauthorizedException if token is invalid or expired\n */\n private validateRefreshToken(token: string): void {\n const jwtSecret =\n this.configService.get<string>('JWT_REFRESH_SECRET') ||\n this.configService.get<string>('JWT_SECRET');\n this.validateRefreshTokenWithSecret(token, jwtSecret);\n }\n\n /**\n * Helper to validate refresh token with specific secret\n */\n private validateRefreshTokenWithSecret(token: string, secret: string | undefined): void {\n if (!secret) {\n this.logger.error('JWT secret not configured for refresh token validation');\n throw new UnauthorizedException('Server configuration error');\n }\n\n try {\n const decoded = jwt.verify(token, secret, {\n algorithms: ['HS256', 'HS512', 'RS256'],\n }) as DecodedToken;\n\n this.logger.debug(`Refresh token decoded for user: ${(decoded as any).userId}`);\n\n // Check expiry explicitly\n if (decoded.exp) {\n const expiryTime = decoded.exp * 1000; // Convert to milliseconds\n const currentTime = Date.now();\n\n if (currentTime > expiryTime) {\n this.logger.warn('Refresh token has expired');\n throw new UnauthorizedException('Refresh token has expired. Please login again');\n }\n\n const timeRemaining = expiryTime - currentTime;\n this.logger.debug(\n `Refresh token valid for ${Math.floor(timeRemaining / 1000)} more seconds`,\n );\n }\n } catch (error: unknown) {\n if (error instanceof UnauthorizedException) {\n throw error;\n }\n\n const jwtError = error as { name?: string; message?: string; expiredAt?: string };\n if (jwtError?.name === 'TokenExpiredError') {\n this.logger.warn(`Refresh token expired at: ${jwtError?.expiredAt}`);\n throw new UnauthorizedException('Refresh token has expired. Please login again');\n }\n\n if (jwtError?.name === 'JsonWebTokenError') {\n this.logger.warn(`Refresh token verification failed: ${jwtError?.message}`);\n throw new UnauthorizedException('Invalid refresh token');\n }\n\n if (jwtError?.name === 'NotBeforeError') {\n this.logger.warn('Refresh token used before valid (nbf claim)');\n throw new UnauthorizedException('Refresh token not yet valid');\n }\n\n this.logger.error('Unexpected error validating refresh token', error);\n throw new UnauthorizedException('Refresh token validation failed');\n }\n }\n}\n","import {\n Inject,\n Injectable,\n InternalServerErrorException,\n Logger,\n OnModuleDestroy,\n OnModuleInit,\n} from '@nestjs/common';\nimport { DATABASE_MODULE_OPTIONS } from '../constants';\nimport type { DatabaseModuleOptions, TenantInfo } from '../interfaces';\n\n/**\n * Service responsible for querying the primary database to resolve tenant configurations\n *\n * This service:\n * - Connects to the primary database (tenant registry)\n * - Queries tenant metadata (database location, credentials, etc.)\n * - Caches tenant configs in memory to reduce database load\n * - Only used in GATEWAY MODE (microservices receive tenant config from messages)\n *\n * @example\n * // In API Gateway\n * const config = await primaryDatabase.getTenantConfig('acme');\n * // Returns: { id, slug, type, databaseHost, databaseName, ... }\n */\n@Injectable()\nexport class PrimaryDatabaseService implements OnModuleInit, OnModuleDestroy {\n private readonly logger = new Logger(PrimaryDatabaseService.name);\n\n /** Primary database client for querying tenant registry */\n private primaryDbClient: any;\n\n /** In-memory cache: Map<tenantIdentifier, TenantConfig> */\n private readonly tenantConfigCache = new Map<string, TenantInfo>();\n\n /** Cache TTL in milliseconds */\n private readonly cacheTTL: number;\n\n constructor(\n @Inject(DATABASE_MODULE_OPTIONS)\n private readonly options: DatabaseModuleOptions,\n ) {\n this.cacheTTL = options.connectionCacheTTL || 300000; // 5 minutes default\n }\n\n async onModuleInit() {\n // Only initialize if we have primary database config (gateway mode)\n if (this.options.primaryDb) {\n await this.initializePrimaryDbClient();\n }\n }\n\n /**\n * Initialize connection to primary database\n */\n private async initializePrimaryDbClient(): Promise<void> {\n try {\n const PrimaryDbClient = this.options.prismaClientConstructor;\n\n // Build connection URL from individual properties\n const databaseUrl = this.buildPrimaryDbUrl();\n\n this.primaryDbClient = new PrimaryDbClient({\n datasources: {\n db: {\n url: databaseUrl,\n },\n },\n log: ['error', 'warn'],\n });\n\n await this.primaryDbClient.$connect();\n this.logger.log('Connected to primary database (tenant registry)');\n } catch (error) {\n this.logger.error('Failed to connect to primary database', error);\n throw new InternalServerErrorException('Failed to initialize tenant registry');\n }\n }\n\n /**\n * Build connection URL from primary database properties\n */\n private buildPrimaryDbUrl(): string {\n if (!this.options.primaryDb) {\n throw new Error('Primary database configuration not provided');\n }\n\n const {\n host,\n port = 5432,\n username,\n password,\n database,\n schema = 'public',\n sslMode = 'require',\n } = this.options.primaryDb;\n\n // Build base URL\n let url = `postgresql://${username}:${password}@${host}:${port}/${database}`;\n\n // Add query parameters\n const params = new URLSearchParams();\n if (schema) {\n params.set('schema', schema);\n }\n params.set('sslmode', sslMode);\n\n const queryString = params.toString();\n if (queryString) {\n url += `?${queryString}`;\n }\n\n this.logger.debug(`Primary DB connection URL: ${this.maskPassword(url)}`);\n\n return url;\n }\n\n /**\n * Mask password in connection URL for logging\n */\n private maskPassword(url: string): string {\n return url.replace(/:([^@]+)@/, ':****@');\n }\n\n /**\n * Get tenant configuration by identifier (ID or slug)\n *\n * @param tenantIdentifier Tenant ID or slug\n * @returns Tenant configuration or null if not found\n */\n async getTenantInfo(tenantIdentifier: string): Promise<TenantInfo | null> {\n // Check cache first\n const cached = this.tenantConfigCache.get(tenantIdentifier);\n if (cached) {\n this.logger.debug(`Cache hit for tenant: ${tenantIdentifier}`);\n return cached;\n }\n\n // Query primary database\n try {\n if (!this.primaryDbClient) {\n throw new Error('Primary database client not initialized');\n }\n\n this.logger.debug(`Querying primary database for tenant: ${tenantIdentifier}`);\n\n const tenant = await this.primaryDbClient.tenant.findFirst({\n where: {\n OR: [{ id: tenantIdentifier }, { subdomain: tenantIdentifier }],\n status: 'ACTIVE',\n },\n include: {\n databaseConfig: true,\n },\n });\n\n if (!tenant) {\n this.logger.warn(`Tenant not found: ${tenantIdentifier}`);\n return null;\n }\n\n // Build info object - map from separated tables\n const info: TenantInfo = {\n id: tenant.id,\n subdomain: tenant.subdomain,\n type: tenant.dbType,\n status: tenant.status,\n // For SHARED tenants: schema name\n schemaName: tenant.databaseConfig?.dbSchema || undefined,\n // For DEDICATED tenants: database configuration from TenantDatabaseConfig table\n databaseName: tenant.databaseConfig?.dbName || undefined,\n databaseHost: tenant.databaseConfig?.dbHost || undefined,\n databasePort: tenant.databaseConfig?.dbPort || undefined,\n databaseUsername: tenant.databaseConfig?.dbUsername\n ? this.decrypt(tenant.databaseConfig.dbUsername)\n : undefined,\n databasePassword: tenant.databaseConfig?.dbPassword\n ? this.decrypt(tenant.databaseConfig.dbPassword)\n : undefined,\n databaseSslMode: tenant.databaseConfig?.dbSslMode || undefined,\n connectionPoolSize: tenant.databaseConfig?.connectionPoolSize || undefined,\n };\n\n // Cache by both ID and slug\n this.cacheInfo(info);\n\n return info;\n } catch (error) {\n this.logger.error(`Failed to fetch tenant info: ${tenantIdentifier}`, error);\n throw new InternalServerErrorException('Failed to resolve tenant');\n }\n }\n\n /**\n * Cache tenant information with TTL\n */\n private cacheInfo(info: TenantInfo): void {\n this.tenantConfigCache.set(info.id, info);\n this.tenantConfigCache.set(info.subdomain, info);\n\n // Set expiration\n setTimeout(() => {\n this.tenantConfigCache.delete(info.id);\n this.tenantConfigCache.delete(info.subdomain);\n this.logger.debug(`Cache expired for tenant: ${info.subdomain}`);\n }, this.cacheTTL);\n }\n\n /**\n * Clear cached tenant information\n *\n * Useful when tenant settings are updated and cache needs to be invalidated\n *\n * @param tenantIdentifier Tenant ID or slug\n */\n clearTenantCache(tenantIdentifier: string): void {\n const config = this.tenantConfigCache.get(tenantIdentifier);\n if (config) {\n this.tenantConfigCache.delete(config.id);\n this.tenantConfigCache.delete(config.subdomain);\n this.logger.log(`Cleared cache for tenant: ${tenantIdentifier}`);\n }\n }\n\n /**\n * Clear all cached tenant configurations\n */\n clearAllCaches(): void {\n const size = this.tenantConfigCache.size;\n this.tenantConfigCache.clear();\n this.logger.log(`Cleared ${size} cached tenant configs`);\n }\n\n /**\n * Get the Prisma client for the primary database.\n * This is a synchronous property that returns the initialized Prisma client.\n *\n * @returns Primary database client instance\n * @throws Error if primary database client is not initialized\n */\n get prismaClient(): any {\n if (!this.primaryDbClient) {\n throw new Error('Primary database client not initialized');\n }\n return this.primaryDbClient;\n }\n\n /**\n * Decrypt database credentials\n *\n * Override this method to implement your encryption strategy\n *\n * @param encrypted Encrypted value\n * @returns Decrypted value\n */\n private decrypt(encrypted: string): string {\n // TODO: Implement actual decryption using this.options.encryptionKey\n // For now, return as-is (assumes unencrypted or encryption happens elsewhere)\n return encrypted;\n }\n\n async onModuleDestroy() {\n if (this.primaryDbClient) {\n await this.primaryDbClient.$disconnect();\n this.logger.log('Disconnected from primary database');\n }\n }\n}\n","export const DATABASE_MODULE_OPTIONS = Symbol('DATABASE_MODULE_OPTIONS');\n","import { DynamicModule, Global, Module, Provider } from '@nestjs/common';\nimport { APP_INTERCEPTOR } from '@nestjs/core';\nimport { RequestModule } from '../request/request.module';\nimport { DATABASE_MODULE_OPTIONS } from './constants';\nimport { MessageTenantContextInterceptor } from './interceptors/message-tenant-context.interceptor';\nimport { TenantContextInterceptor } from './interceptors/tenant-context.interceptor';\n\nimport type { DatabaseModuleOptions } from './interfaces';\nimport { PrimaryDatabaseService } from './services/primary-database.service';\nimport { TenantContextService } from './services/tenant-context.service';\nimport { TenantDatabaseService } from './services/tenant-database.service';\n\n/**\n * Dynamic module for multi-tenant database management\n *\n * This module provides:\n * - Tenant context management (request-scoped)\n * - Database connection pooling\n * - Dynamic schema/cluster routing\n * - Support for both gateway and microservice modes\n *\n * ## Gateway Mode (API Gateway)\n * - Use DatabaseModule.forServer() method\n * - Automatically extracts tenant from subdomain, falls back to x-tenant-id header\n * - Provide primaryDb configuration and prismaClientConstructor\n * - Automatically queries primary DB for tenant config\n * - Automatically registers TenantContextInterceptor globally\n * - No manual interceptor registration needed\n *\n * ## Microservice Mode (RabbitMQ Workers)\n * - Use DatabaseModule.forMicroservice() method\n * - Only provide prismaClientConstructor\n * - Tenant context comes from RabbitMQ messages\n * - Automatically registers MessageTenantContextInterceptor globally\n * - No manual interceptor registration needed\n *\n * @example\n * // Gateway configuration\n * DatabaseModule.forServer({\n * inject: [ConfigService],\n * useFactory: (config: ConfigService) => ({\n * primaryDb: {\n * host: config.get('PRIMARY_DB_HOST'),\n * port: config.get('PRIMARY_DB_PORT'),\n * username: config.get('PRIMARY_DB_USERNAME'),\n * password: config.get('PRIMARY_DB_PASSWORD'),\n * database: config.get('PRIMARY_DB_DATABASE'),\n * },\n * prismaClientConstructor: PrismaClient,\n * }),\n * })\n *\n * @example\n * // Microservice configuration\n * DatabaseModule.forMicroservice({\n * inject: [ConfigService],\n * useFactory: (config: ConfigService) => ({\n * prismaClientConstructor: PrismaClient,\n * }),\n * })\n */\n@Global()\n@Module({})\nexport class DatabaseModule {\n /**\n * Configure DatabaseModule for Gateway/HTTP mode (multi-tenant web servers)\n *\n * This mode is for API Gateways that handle HTTP requests:\n * - Automatically registers TenantContextInterceptor\n * - Extracts tenant from subdomain or x-tenant-id header\n * - Queries primary database for tenant configuration\n * - Provides PrimaryDatabaseService for tenant lookup\n *\n * @param options Async configuration options\n * @returns Dynamic module configuration with HTTP interceptor\n *\n * @example\n * DatabaseModule.forServer({\n * inject: [ConfigService],\n * useFactory: (config: ConfigService) => ({\n * primaryDb: {\n * host: config.get('PRIMARY_DB_HOST'),\n * port: config.get('PRIMARY_DB_PORT'),\n * username: config.get('PRIMARY_DB_USERNAME'),\n * password: config.get('PRIMARY_DB_PASSWORD'),\n * database: config.get('PRIMARY_DB_DATABASE'),\n * },\n * prismaClientConstructor: PrismaClient,\n * }),\n * })\n */\n static forServer(options: {\n useFactory: (...args: any[]) => Promise<DatabaseModuleOptions> | DatabaseModuleOptions;\n inject?: any[];\n }): DynamicModule {\n return this.createDynamicModule(options, 'server');\n }\n\n /**\n * Configure DatabaseModule for Microservice/Messaging mode (RabbitMQ workers)\n *\n * This mode is for microservices that process messages from queues:\n * - Automatically registers MessageTenantContextInterceptor\n * - Extracts tenant from RabbitMQ message patterns\n * - No primary database needed (tenant comes from message context)\n *\n * @param options Async configuration options\n * @returns Dynamic module configuration with message interceptor\n *\n * @example\n * DatabaseModule.forMicroservice({\n * inject: [ConfigService],\n * useFactory: (config: ConfigService) => ({\n * prismaClientConstructor: PrismaClient,\n * }),\n * })\n */\n static forMicroservice(options: {\n useFactory: (...args: any[]) => Promise<DatabaseModuleOptions> | DatabaseModuleOptions;\n inject?: any[];\n }): DynamicModule {\n return this.createDynamicModule(options, 'microservice');\n }\n\n /**\n * Internal helper to create dynamic module with conditional interceptor registration\n *\n * @param options Configuration options\n * @param mode Mode of operation (gateway or microservice)\n * @returns Dynamic module configuration\n */\n private static createDynamicModule(\n options: {\n useFactory: (...args: any[]) => Promise<DatabaseModuleOptions> | DatabaseModuleOptions;\n inject?: any[];\n },\n mode: 'server' | 'microservice',\n ): DynamicModule {\n const asyncProvider: Provider = {\n provide: DATABASE_MODULE_OPTIONS,\n useFactory: options.useFactory,\n inject: options.inject || [],\n };\n\n const providers: Provider[] = [\n asyncProvider,\n TenantContextService,\n PrimaryDatabaseService,\n TenantDatabaseService,\n ];\n\n // Conditionally add interceptor based on mode\n if (mode === 'server') {\n providers.push({\n provide: APP_INTERCEPTOR,\n useClass: TenantContextInterceptor,\n });\n } else {\n providers.push({\n provide: APP_INTERCEPTOR,\n useClass: MessageTenantContextInterceptor,\n });\n }\n\n return {\n module: DatabaseModule,\n imports: [RequestModule], // Import RequestModule for gateway interceptor\n providers,\n exports: [TenantDatabaseService, TenantContextService, PrimaryDatabaseService, asyncProvider],\n };\n }\n}\n","import {\n CallHandler,\n ExecutionContext,\n Injectable,\n Logger,\n NestInterceptor,\n Scope,\n} from '@nestjs/common';\nimport { Observable } from 'rxjs';\nimport { tap } from 'rxjs/operators';\nimport type { TenantInfo } from '../interfaces';\nimport { TenantContextService } from '../services/tenant-context.service';\n\n/**\n * Interceptor that extracts tenant context from RabbitMQ messages (Microservice Mode)\n *\n * This interceptor:\n * 1. Extracts tenant info from RabbitMQ message payload\n * 2. Sets it in REQUEST-SCOPED TenantContextService\n * 3. Cleans up after message is processed\n *\n * Expected message format:\n * {\n * dto: { ... },\n * tenant: {\n * tenantId: 'abc-123',\n * tenantSlug: 'acme',\n * tenantType: 'ENTERPRISE',\n * databaseHost: 'enterprise-1.aws.com',\n * databaseName: 'acme_db',\n * ...\n * }\n * }\n *\n * @example\n * // Automatically registered by DatabaseModule.forMicroservice()\n * // No manual registration needed\n * DatabaseModule.forMicroservice({\n * inject: [ConfigService],\n * useFactory: (config: ConfigService) => ({\n * prismaClientConstructor: PrismaClient,\n * }),\n * })\n */\n@Injectable({ scope: Scope.REQUEST })\nexport class MessageTenantContextInterceptor implements NestInterceptor {\n private readonly logger = new Logger(MessageTenantContextInterceptor.name);\n\n constructor(private readonly tenantContext: TenantContextService) {}\n\n intercept(context: ExecutionContext, next: CallHandler): Observable<any> {\n const contextType = context.getType();\n\n // Only handle RabbitMQ/microservice messages\n if (contextType === 'rpc') {\n const rpcContext = context.switchToRpc();\n const payload = rpcContext.getData();\n\n // Extract tenant from message payload\n if (payload && payload.tenant) {\n const tenant = payload.tenant as TenantInfo;\n\n this.logger.debug(`Setting tenant context from message: ${tenant.subdomain}`);\n\n try {\n this.tenantContext.setTenant(tenant);\n this.logger.log(`Tenant context set: ${tenant.subdomain} (${tenant.type})`);\n } catch (error) {\n this.logger.error('Failed to set tenant context from message', error);\n }\n } else {\n this.logger.warn('Message payload missing tenant information');\n }\n }\n\n // Execute handler and clean up after\n return next.handle().pipe(\n tap({\n next: () => {\n this.cleanupContext();\n },\n error: () => {\n this.cleanupContext();\n },\n complete: () => {\n this.cleanupContext();\n },\n }),\n );\n }\n\n /**\n * Clean up tenant context after message is processed\n */\n private cleanupContext(): void {\n if (this.tenantContext.hasTenant()) {\n const tenant = this.tenantContext.getTenantIdSafe();\n this.tenantContext.clearTenant();\n this.logger.debug(`Cleaned up tenant context: ${tenant}`);\n }\n }\n}\n","import { Injectable, Scope, UnauthorizedException } from '@nestjs/common';\nimport { TenantInfo } from '../interfaces';\n\n/**\n * Request-scoped service that holds tenant context for the current request or RabbitMQ message\n *\n * IMPORTANT: This service is REQUEST-SCOPED, meaning NestJS creates a new instance\n * for each HTTP request or RabbitMQ message. This ensures tenant isolation and\n * prevents cross-tenant data leaks in concurrent scenarios.\n *\n * @example\n * // In a controller or service\n * constructor(private readonly tenantContext: TenantContextService) {}\n *\n * async handleRequest() {\n * const tenant = this.tenantContext.getTenant();\n * console.log(`Processing request for tenant: ${tenant.tenantSlug}`);\n * }\n */\n@Injectable({ scope: Scope.REQUEST })\nexport class TenantContextService {\n private tenantInfo: TenantInfo | null = null;\n\n /**\n * Set tenant information for this request/message\n *\n * This is typically called by:\n * - TenantContextInterceptor (for HTTP requests in gateway)\n * - MessageTenantContextInterceptor (for RabbitMQ messages in microservices)\n * - Manual context setup in message handlers\n *\n * @param tenantInfo Complete tenant information\n * @throws Error if tenant context is already set (prevents accidental overwrites)\n */\n setTenant(tenantInfo: TenantInfo): void {\n if (this.tenantInfo) {\n throw new Error('Tenant context already set for this request');\n }\n this.tenantInfo = tenantInfo;\n }\n\n /**\n * Get tenant information for this request/message\n *\n * @returns Tenant information\n * @throws UnauthorizedException if tenant context hasn't been set\n */\n getTenant(): TenantInfo {\n if (!this.tenantInfo) {\n throw new UnauthorizedException('Tenant context not set');\n }\n return this.tenantInfo;\n }\n\n /**\n * Check if tenant context has been set\n *\n * @returns true if tenant context is available\n */\n hasTenant(): boolean {\n return this.tenantInfo !== null;\n }\n\n /**\n * Clear tenant context\n *\n * This is useful for cleanup in RabbitMQ message handlers\n * after the message has been processed.\n *\n * HTTP requests don't need manual cleanup as the service\n * instance is destroyed when the request ends.\n */\n clearTenant(): void {\n this.tenantInfo = null;\n }\n\n /**\n * Get tenant ID safely (returns null if not set)\n *\n * @returns Tenant ID or null\n */\n getTenantIdSafe(): string | null {\n return this.tenantInfo?.id ?? null;\n }\n\n /**\n * Get tenant subdomain safely (returns null if not set)\n *\n * @returns Tenant subdomain or null\n */\n getTenantSubdomainSafe(): string | null {\n return this.tenantInfo?.subdomain ?? null;\n }\n}\n","import {\n CallHandler,\n ExecutionContext,\n Injectable,\n Logger,\n NestInterceptor,\n Scope,\n UnauthorizedException,\n} from '@nestjs/common';\nimport { Reflector } from '@nestjs/core';\nimport type { FastifyRequest } from 'fastify';\nimport { Observable } from 'rxjs';\nimport { RequestService } from '../../request';\nimport { PrimaryDatabaseService } from '../services/primary-database.service';\nimport { TenantContextService } from '../services/tenant-context.service';\n\n/**\n * Interceptor that extracts tenant context from HTTP requests (Gateway Mode)\n *\n * This interceptor runs BEFORE the controller and:\n * 1. Checks if endpoint is marked with @Public() decorator\n * 2. Extracts tenant identifier from request using RequestService\n * 3. For public endpoints without tenant info → skip tenant context setup\n * 4. For all other requests → queries primary database for tenant configuration\n * 5. Stores tenant info in REQUEST-SCOPED TenantContextService\n *\n * Tenant resolution order:\n * - First: x-tenant-id header\n * - Fallback: x-subdomain header\n *\n * Public Endpoints:\n * - Endpoints marked with @Public() decorator can work with OR without tenant context\n * - If no tenant info in headers → skip tenant context setup (useful for OAuth, registration)\n * - If tenant info present in headers → setup tenant context (multi-tenant public APIs)\n *\n * Only used in API Gateway. Microservices use MessageTenantContextInterceptor instead.\n *\n * @example\n * // Public endpoint without tenant (OAuth callback, registration)\n * @Public()\n * @Get('onboarding/oauth/google')\n * async oauthGoogle() { ... }\n * // No x-tenant-id header → skips tenant context\n *\n * @example\n * // Public endpoint with tenant (multi-tenant public API)\n * @Public()\n * @Get('public/data')\n * async getPublicData() { ... }\n * // x-tenant-id: acme → sets tenant context for 'acme'\n */\n@Injectable({ scope: Scope.REQUEST })\nexport class TenantContextInterceptor implements NestInterceptor {\n private readonly logger = new Logger(TenantContextInterceptor.name);\n\n constructor(\n private readonly reflector: Reflector,\n private readonly tenantContext: TenantContextService,\n private readonly primaryDatabase: PrimaryDatabaseService,\n private readonly requestService: RequestService,\n ) {}\n\n async intercept(context: ExecutionContext, next: CallHandler): Promise<Observable<any>> {\n const request = context.switchToHttp().getRequest<FastifyRequest>();\n\n this.logger.debug(`Processing request: ${request.method} ${request.url}`);\n\n // Check if endpoint is marked as @Public()\n const isPublic = this.reflector.getAllAndOverride<boolean>('isPublic', [\n context.getHandler(),\n context.getClass(),\n ]);\n\n try {\n // Extract tenant identifier using RequestService (no code duplication)\n const tenantIdentifier = this.requestService.getTenantIdentifier();\n\n // Skip tenant context for public endpoints without tenant info\n if (isPublic && !tenantIdentifier) {\n this.logger.debug('Public endpoint without tenant identifier, skipping tenant context setup');\n return next.handle();\n }\n\n if (!tenantIdentifier) {\n throw new UnauthorizedException('Tenant identifier not found in request');\n }\n\n this.logger.debug(`Tenant identifier extracted: ${tenantIdentifier}`);\n\n // Special case: cloud.vritti.com (platform admin)\n if (tenantIdentifier === 'cloud') {\n this.logger.log('Cloud platform access detected, skipping tenant context setup');\n return next.handle();\n }\n\n // Query primary database for tenant configuration\n const tenantInfo = await this.primaryDatabase.getTenantInfo(tenantIdentifier);\n\n if (!tenantInfo) {\n this.logger.warn(`Invalid tenant: ${tenantIdentifier}`);\n throw new UnauthorizedException('Invalid tenant');\n }\n\n if (tenantInfo.status !== 'ACTIVE') {\n this.logger.warn(`Tenant ${tenantIdentifier} has status: ${tenantInfo.status}`);\n throw new UnauthorizedException(`Tenant is ${tenantInfo.status}`);\n }\n\n this.logger.debug(`Tenant config loaded: ${tenantInfo.subdomain} (${tenantInfo.type})`);\n\n // Store in REQUEST-SCOPED context\n this.tenantContext.setTenant(tenantInfo);\n\n // Also attach to request object for easy access\n (request as any).tenant = tenantInfo;\n\n this.logger.log(`Tenant context set: ${tenantInfo.subdomain}`);\n } catch (error) {\n this.logger.error('Failed to set tenant context', error);\n throw error;\n }\n\n return next.handle();\n }\n}\n","import {\n Inject,\n Injectable,\n InternalServerErrorException,\n Logger,\n OnModuleDestroy,\n} from '@nestjs/common';\nimport { DATABASE_MODULE_OPTIONS } from '../constants';\nimport type { DatabaseModuleOptions, TenantInfo } from '../interfaces';\nimport { TenantContextService } from './tenant-context.service';\n\n/**\n * Service responsible for managing tenant-scoped database connections\n *\n * This service:\n * - Maintains a connection pool (Map<cacheKey, DbClient>)\n * - Creates new connections dynamically based on tenant context\n * - Reuses existing connections for the same tenant\n * - Supports both cloud schemas and enterprise databases\n * - Automatically cleans up idle connections\n *\n * @example\n * // In a controller or service\n * const dbClient = await this.tenantDatabase.getDbClient<PrismaClient>();\n * const users = await dbClient.user.findMany();\n */\n@Injectable()\nexport class TenantDatabaseService implements OnModuleDestroy {\n private readonly logger = new Logger(TenantDatabaseService.name);\n\n /** Connection pool: Map<cacheKey, DbClient> */\n private readonly clients = new Map<string, any>();\n\n /** Track last usage time for idle connection cleanup */\n private readonly clientLastUsed = new Map<string, number>();\n\n /** Cleanup interval timer */\n private cleanupInterval?: NodeJS.Timeout;\n\n constructor(\n @Inject(DATABASE_MODULE_OPTIONS)\n private readonly options: DatabaseModuleOptions,\n private readonly tenantContext: TenantContextService,\n ) {\n this.startConnectionCleaner();\n }\n\n /**\n * Get the Prisma client for the current tenant's database.\n * This returns the tenant-scoped database client.\n *\n * @returns Tenant-scoped database client instance\n * @throws UnauthorizedException if tenant context not set\n * @throws InternalServerErrorException if connection fails\n */\n get prismaClient(): any {\n return this.getDbClient();\n }\n\n /**\n * Get tenant-scoped database client for the current request/message\n *\n * This method:\n * 1. Gets tenant info from TenantContextService\n * 2. Builds a connection URL based on tenant type\n * 3. Returns cached client if exists, otherwise creates new one\n *\n * @returns Promise<Database client instance>\n * @throws UnauthorizedException if tenant context not set\n * @throws InternalServerErrorException if connection fails\n *\n * @example\n * const dbClient = await tenantDatabase.getDbClient<PrismaClient>();\n * const users = await dbClient.user.findMany();\n */\n private async getDbClient<T = any>(): Promise<T> {\n const tenant = this.tenantContext.getTenant();\n\n const cacheKey = this.buildCacheKey(tenant);\n\n // Check if connection already exists\n if (this.clients.has(cacheKey)) {\n this.clientLastUsed.set(cacheKey, Date.now());\n this.logger.debug(`Reusing cached connection: ${cacheKey}`);\n return this.clients.get(cacheKey) as T;\n }\n\n // Create new connection\n this.logger.log(`Creating new database connection: ${cacheKey}`);\n const client = await this.createDbClient(tenant);\n this.clients.set(cacheKey, client);\n this.clientLastUsed.set(cacheKey, Date.now());\n\n return client as T;\n }\n\n /**\n * Create a new database client for the given tenant\n */\n private async createDbClient(tenant: TenantInfo): Promise<any> {\n try {\n // Build tenant-specific database URL\n const databaseUrl = this.buildTenantDbUrl(tenant);\n\n // Load Prisma client constructor\n const PrismaClient = await this.options.prismaClientConstructor;\n\n // Create new instance with tenant-specific connection\n const client = new PrismaClient({\n datasources: {\n db: { url: databaseUrl },\n },\n log: ['error', 'warn'],\n });\n\n await client.$connect();\n this.logger.log(`Connected to database for tenant: ${tenant.subdomain}`);\n\n return client;\n } catch (error) {\n this.logger.error(\n `Failed to create database connection for tenant: ${tenant.subdomain}`,\n error,\n );\n throw new InternalServerErrorException('Failed to connect to tenant database');\n }\n }\n\n /**\n * Build connection URL for enterprise tenant (dedicated database)\n */\n private buildTenantDbUrl(tenant: TenantInfo): string {\n const {\n databaseHost,\n databasePort,\n databaseName,\n databaseUsername,\n databasePassword,\n databaseSslMode,\n } = tenant;\n\n if (!databaseHost || !databaseName || !databaseUsername) {\n throw new Error(`Enterprise tenant ${tenant.subdomain} missing database configuration`);\n }\n\n const port = databasePort || 5432;\n const sslMode = databaseSslMode || 'require';\n const connectionUrl = `postgresql://${databaseUsername}:${databasePassword}@${databaseHost}:${port}/${databaseName}?sslmode=${sslMode}`;\n\n this.logger.debug(`Enterprise connection URL: ${this.maskPassword(connectionUrl)}`);\n\n return connectionUrl;\n }\n\n /**\n * Build cache key for connection pooling\n */\n private buildCacheKey(tenant: TenantInfo): string {\n return `${tenant.type}:${tenant.databaseName}@${tenant.databaseHost}`;\n }\n\n /**\n * Start periodic cleanup of idle connections\n */\n private startConnectionCleaner(): void {\n const interval = this.options.connectionCacheTTL || 300000; // 5 minutes\n\n this.cleanupInterval = setInterval(() => {\n this.cleanupIdleConnections();\n }, interval);\n\n this.logger.log(`Connection cleanup scheduled every ${interval / 1000} seconds`);\n }\n\n /**\n * Clean up idle connections that haven't been used recently\n */\n private cleanupIdleConnections(): void {\n const now = Date.now();\n const maxIdle = this.options.connectionCacheTTL || 300000;\n\n let cleaned = 0;\n\n for (const [key, lastUsed] of this.clientLastUsed.entries()) {\n if (now - lastUsed > maxIdle) {\n const client = this.clients.get(key);\n if (client) {\n client\n .$disconnect()\n .then(() => {\n this.logger.debug(`Cleaned up idle connection: ${key}`);\n })\n .catch((error: any) => {\n this.logger.error(`Error disconnecting idle client: ${key}`, error);\n });\n\n this.clients.delete(key);\n this.clientLastUsed.delete(key);\n cleaned++;\n }\n }\n }\n\n if (cleaned > 0) {\n this.logger.log(`Cleaned up ${cleaned} idle connections`);\n }\n }\n\n /**\n * Get current connection pool statistics\n */\n getPoolStats(): {\n activeConnections: number;\n tenants: string[];\n } {\n return {\n activeConnections: this.clients.size,\n tenants: Array.from(this.clients.keys()),\n };\n }\n\n /**\n * Mask password in connection URL for logging\n */\n private maskPassword(url: string): string {\n return url.replace(/:([^@]+)@/, ':****@');\n }\n\n async onModuleDestroy() {\n // Stop cleanup interval\n if (this.cleanupInterval) {\n clearInterval(this.cleanupInterval);\n }\n\n // Disconnect all clients\n this.logger.log(`Disconnecting ${this.clients.size} database connections`);\n\n const disconnectPromises = Array.from(this.clients.entries()).map(async ([key, client]) => {\n try {\n await client.$disconnect();\n this.logger.debug(`Disconnected: ${key}`);\n } catch (error) {\n this.logger.error(`Error disconnecting client: ${key}`, error);\n }\n });\n\n await Promise.all(disconnectPromises);\n this.logger.log('All database connections closed');\n }\n}\n","import { Logger } from '@nestjs/common';\nimport { PrimaryDatabaseService } from '../services/primary-database.service';\n\n/**\n * Abstract base repository for primary database operations.\n * Provides common CRUD operations with automatic logging.\n *\n * @template TModel - The Prisma model type\n * @template TCreateDTO - DTO type for create operations\n * @template TUpdateDTO - DTO type for update operations\n *\n * @example\n * ```typescript\n * // Using the model delegate pattern (RECOMMENDED)\n * // Type-safe, IDE autocomplete, refactor-friendly\n * @Injectable()\n * export class UserRepository extends PrimaryBaseRepository<\n * User,\n * CreateUserDto,\n * UpdateUserDto\n * > {\n * constructor(database: PrimaryDatabaseService) {\n * super(database, (prisma) => prisma.user); // ✅ Type-safe with autocomplete!\n * }\n *\n * // Add custom methods as needed\n * async findByEmail(email: string): Promise<User | null> {\n * return this.model.findUnique({ where: { email } });\n * }\n * }\n *\n * // Short syntax is also supported\n * @Injectable()\n * export class TenantRepository extends PrimaryBaseRepository<Tenant> {\n * constructor(database: PrimaryDatabaseService) {\n * super(database, (p) => p.tenant); // ✅ Concise!\n * }\n * }\n *\n * // Works with complex model names\n * @Injectable()\n * export class EmailVerificationRepository extends PrimaryBaseRepository<EmailVerification> {\n * constructor(database: PrimaryDatabaseService) {\n * super(database, (p) => p.emailVerification); // ✅ Matches Prisma naming\n * }\n * }\n * ```\n */\nexport abstract class PrimaryBaseRepository<\n TModel,\n TCreateDTO = any,\n TUpdateDTO = any,\n> {\n protected readonly logger: Logger;\n private readonly modelGetter: (prisma: any) => any;\n\n /**\n * Lazy getter for Prisma client.\n * Accesses the client from the database service only when needed,\n * avoiding initialization timing issues with NestJS lifecycle.\n */\n protected get prisma(): any {\n return this.database.prismaClient;\n }\n\n /**\n * Lazy getter for the Prisma model delegate.\n * Returns the specific model (e.g., prisma.user, prisma.tenant) for this repository.\n */\n protected get model(): any {\n return this.modelGetter(this.prisma);\n }\n\n /**\n * Create a new repository instance\n *\n * @param database - The primary database service\n * @param getModel - Function that returns the Prisma model delegate from the client\n *\n * @example\n * ```typescript\n * // Standard usage with full parameter name\n * constructor(database: PrimaryDatabaseService) {\n * super(database, (prisma) => prisma.user);\n * }\n *\n * // Short syntax\n * constructor(database: PrimaryDatabaseService) {\n * super(database, (p) => p.user);\n * }\n *\n * // Complex model names\n * constructor(database: PrimaryDatabaseService) {\n * super(database, (p) => p.emailVerification);\n * }\n * ```\n */\n constructor(\n protected readonly database: PrimaryDatabaseService,\n getModel: (prisma: any) => any,\n ) {\n this.logger = new Logger(this.constructor.name);\n this.modelGetter = getModel;\n this.logger.debug(`Initialized ${this.constructor.name}`);\n }\n\n /**\n * Create a new record\n *\n * @param data - The data to create the record with\n * @returns Promise resolving to the created record\n *\n * @example\n * ```typescript\n * const user = await userRepository.create({\n * email: 'user@example.com',\n * name: 'John Doe'\n * });\n * ```\n */\n async create(data: TCreateDTO): Promise<TModel> {\n this.logger.log('Creating record');\n return await this.model.create({ data });\n }\n\n /**\n * Find a single record by ID\n *\n * @param id - The record ID\n * @returns Promise resolving to the record or null if not found\n *\n * @example\n * ```typescript\n * const user = await userRepository.findById('user-id-123');\n * ```\n */\n async findById(id: string): Promise<TModel | null> {\n this.logger.debug(`Finding record by ID: ${id}`);\n return await this.model.findUnique({ where: { id } });\n }\n\n /**\n * Find a single record with custom where clause\n *\n * @param where - The where clause or findUnique args\n * @returns Promise resolving to the record or null if not found\n *\n * @example\n * ```typescript\n * // Simple where clause\n * const user = await userRepository.findOne({ email: 'user@example.com' });\n *\n * // With include\n * const user = await userRepository.findOne({\n * where: { email: 'user@example.com' },\n * include: { posts: true }\n * });\n * ```\n */\n async findOne(where: any): Promise<TModel | null> {\n this.logger.debug('Finding record with custom query');\n return await this.model.findUnique(\n typeof where === 'object' && 'where' in where ? where : { where },\n );\n }\n\n /**\n * Find multiple records\n *\n * @param args - Prisma findMany arguments (where, orderBy, take, skip, etc.)\n * @returns Promise resolving to an array of records\n *\n * @example\n * ```typescript\n * // Find all users\n * const users = await userRepository.findMany();\n *\n * // Find with filtering and pagination\n * const users = await userRepository.findMany({\n * where: { status: 'ACTIVE' },\n * orderBy: { createdAt: 'desc' },\n * take: 10,\n * skip: 0\n * });\n * ```\n */\n async findMany(args?: any): Promise<TModel[]> {\n this.logger.debug('Finding multiple records');\n return await this.model.findMany(args);\n }\n\n /**\n * Update a record by ID\n *\n * @param id - The record ID\n * @param data - The data to update\n * @returns Promise resolving to the updated record\n *\n * @example\n * ```typescript\n * const user = await userRepository.update('user-id-123', {\n * name: 'Jane Doe'\n * });\n * ```\n */\n async update(id: string, data: TUpdateDTO): Promise<TModel> {\n this.logger.log(`Updating record with ID: ${id}`);\n return await this.model.update({ where: { id }, data });\n }\n\n /**\n * Update multiple records\n *\n * @param where - The where clause to match records\n * @param data - The data to update\n * @returns Promise resolving to the count of updated records\n *\n * @example\n * ```typescript\n * const result = await userRepository.updateMany(\n * { status: 'PENDING' },\n * { status: 'ACTIVE' }\n * );\n * console.log(`Updated ${result.count} users`);\n * ```\n */\n async updateMany(where: any, data: TUpdateDTO): Promise<{ count: number }> {\n this.logger.log('Updating multiple records');\n return await this.model.updateMany({ where, data });\n }\n\n /**\n * Delete a record by ID\n *\n * @param id - The record ID\n * @returns Promise resolving to the deleted record\n *\n * @example\n * ```typescript\n * const user = await userRepository.delete('user-id-123');\n * ```\n */\n async delete(id: string): Promise<TModel> {\n this.logger.log(`Deleting record with ID: ${id}`);\n return await this.model.delete({ where: { id } });\n }\n\n /**\n * Delete multiple records\n *\n * @param where - The where clause to match records\n * @returns Promise resolving to the count of deleted records\n *\n * @example\n * ```typescript\n * const result = await userRepository.deleteMany({\n * status: 'INACTIVE',\n * createdAt: { lt: new Date('2020-01-01') }\n * });\n * console.log(`Deleted ${result.count} users`);\n * ```\n */\n async deleteMany(where: any): Promise<{ count: number }> {\n this.logger.log('Deleting multiple records');\n return await this.model.deleteMany({ where });\n }\n\n /**\n * Count records\n *\n * @param where - Optional where clause to filter records\n * @returns Promise resolving to the count of records\n *\n * @example\n * ```typescript\n * // Count all users\n * const total = await userRepository.count();\n *\n * // Count active users\n * const activeCount = await userRepository.count({ status: 'ACTIVE' });\n * ```\n */\n async count(where?: any): Promise<number> {\n this.logger.debug('Counting records');\n return await this.model.count({ where });\n }\n\n /**\n * Check if a record exists\n *\n * @param where - The where clause to match records\n * @returns Promise resolving to true if at least one record exists, false otherwise\n *\n * @example\n * ```typescript\n * const emailExists = await userRepository.exists({\n * email: 'user@example.com'\n * });\n * ```\n */\n async exists(where: any): Promise<boolean> {\n const count = await this.model.count({ where });\n return count > 0;\n }\n}\n","import { Logger } from '@nestjs/common';\nimport { TenantDatabaseService } from '../services/tenant-database.service';\n\n/**\n * Abstract base repository for tenant-scoped database operations.\n * All operations are automatically scoped to the current tenant.\n *\n * @template TModel - The Prisma model type\n * @template TCreateDTO - DTO type for create operations\n * @template TUpdateDTO - DTO type for update operations\n *\n * @example\n * ```typescript\n * // Using the model delegate pattern (RECOMMENDED)\n * // Type-safe, IDE autocomplete, refactor-friendly\n * @Injectable()\n * export class ProductRepository extends TenantBaseRepository<\n * Product,\n * CreateProductDto,\n * UpdateProductDto\n * > {\n * constructor(database: TenantDatabaseService) {\n * super(database, (prisma) => prisma.product); // ✅ Type-safe with autocomplete!\n * }\n *\n * // Add custom methods as needed\n * async findBySku(sku: string): Promise<Product | null> {\n * return this.model.findUnique({ where: { sku } });\n * }\n * }\n *\n * // Short syntax is also supported\n * @Injectable()\n * export class OrderRepository extends TenantBaseRepository<Order> {\n * constructor(database: TenantDatabaseService) {\n * super(database, (p) => p.order); // ✅ Concise!\n * }\n * }\n *\n * // Works with complex model names\n * @Injectable()\n * export class InventoryItemRepository extends TenantBaseRepository<InventoryItem> {\n * constructor(database: TenantDatabaseService) {\n * super(database, (p) => p.inventoryItem); // ✅ Matches Prisma naming\n * }\n * }\n * ```\n */\nexport abstract class TenantBaseRepository<\n TModel,\n TCreateDTO = any,\n TUpdateDTO = any,\n> {\n protected readonly logger: Logger;\n private readonly modelGetter: (prisma: any) => any;\n\n /**\n * Lazy getter for Prisma client.\n * Accesses the client from the database service only when needed,\n * avoiding initialization timing issues with NestJS lifecycle.\n */\n protected get prisma(): any {\n return this.database.prismaClient;\n }\n\n /**\n * Lazy getter for the Prisma model delegate.\n * Returns the specific model (e.g., prisma.product, prisma.order) for this repository.\n */\n protected get model(): any {\n return this.modelGetter(this.prisma);\n }\n\n /**\n * Create a new repository instance\n *\n * @param database - The tenant database service\n * @param getModel - Function that returns the Prisma model delegate from the client\n *\n * @example\n * ```typescript\n * // Standard usage with full parameter name\n * constructor(database: TenantDatabaseService) {\n * super(database, (prisma) => prisma.product);\n * }\n *\n * // Short syntax\n * constructor(database: TenantDatabaseService) {\n * super(database, (p) => p.product);\n * }\n *\n * // Complex model names\n * constructor(database: TenantDatabaseService) {\n * super(database, (p) => p.inventoryItem);\n * }\n * ```\n */\n constructor(\n protected readonly database: TenantDatabaseService,\n getModel: (prisma: any) => any,\n ) {\n this.logger = new Logger(this.constructor.name);\n this.modelGetter = getModel;\n this.logger.debug(`Initialized ${this.constructor.name}`);\n }\n\n /**\n * Create a new record\n *\n * @param data - The data to create the record with\n * @returns Promise resolving to the created record\n *\n * @example\n * ```typescript\n * const product = await productRepository.create({\n * name: 'Widget',\n * sku: 'WDG-001',\n * price: 9.99\n * });\n * ```\n */\n async create(data: TCreateDTO): Promise<TModel> {\n this.logger.log('Creating record');\n return await this.model.create({ data });\n }\n\n /**\n * Find a single record by ID\n *\n * @param id - The record ID\n * @returns Promise resolving to the record or null if not found\n *\n * @example\n * ```typescript\n * const product = await productRepository.findById('product-id-123');\n * ```\n */\n async findById(id: string): Promise<TModel | null> {\n this.logger.debug(`Finding record by ID: ${id}`);\n return await this.model.findUnique({ where: { id } });\n }\n\n /**\n * Find a single record with custom where clause\n *\n * @param where - The where clause or findUnique args\n * @returns Promise resolving to the record or null if not found\n *\n * @example\n * ```typescript\n * // Simple where clause\n * const product = await productRepository.findOne({ sku: 'WDG-001' });\n *\n * // With include\n * const product = await productRepository.findOne({\n * where: { sku: 'WDG-001' },\n * include: { category: true }\n * });\n * ```\n */\n async findOne(where: any): Promise<TModel | null> {\n this.logger.debug('Finding record with custom query');\n return await this.model.findUnique(\n typeof where === 'object' && 'where' in where ? where : { where },\n );\n }\n\n /**\n * Find multiple records\n *\n * @param args - Prisma findMany arguments (where, orderBy, take, skip, etc.)\n * @returns Promise resolving to an array of records\n *\n * @example\n * ```typescript\n * // Find all products\n * const products = await productRepository.findMany();\n *\n * // Find with filtering and pagination\n * const products = await productRepository.findMany({\n * where: { status: 'ACTIVE' },\n * orderBy: { createdAt: 'desc' },\n * take: 10,\n * skip: 0\n * });\n * ```\n */\n async findMany(args?: any): Promise<TModel[]> {\n this.logger.debug('Finding multiple records');\n return await this.model.findMany(args);\n }\n\n /**\n * Update a record by ID\n *\n * @param id - The record ID\n * @param data - The data to update\n * @returns Promise resolving to the updated record\n *\n * @example\n * ```typescript\n * const product = await productRepository.update('product-id-123', {\n * price: 12.99\n * });\n * ```\n */\n async update(id: string, data: TUpdateDTO): Promise<TModel> {\n this.logger.log(`Updating record with ID: ${id}`);\n return await this.model.update({ where: { id }, data });\n }\n\n /**\n * Update multiple records\n *\n * @param where - The where clause to match records\n * @param data - The data to update\n * @returns Promise resolving to the count of updated records\n *\n * @example\n * ```typescript\n * const result = await productRepository.updateMany(\n * { status: 'PENDING' },\n * { status: 'ACTIVE' }\n * );\n * console.log(`Updated ${result.count} products`);\n * ```\n */\n async updateMany(where: any, data: TUpdateDTO): Promise<{ count: number }> {\n this.logger.log('Updating multiple records');\n return await this.model.updateMany({ where, data });\n }\n\n /**\n * Delete a record by ID\n *\n * @param id - The record ID\n * @returns Promise resolving to the deleted record\n *\n * @example\n * ```typescript\n * const product = await productRepository.delete('product-id-123');\n * ```\n */\n async delete(id: string): Promise<TModel> {\n this.logger.log(`Deleting record with ID: ${id}`);\n return await this.model.delete({ where: { id } });\n }\n\n /**\n * Delete multiple records\n *\n * @param where - The where clause to match records\n * @returns Promise resolving to the count of deleted records\n *\n * @example\n * ```typescript\n * const result = await productRepository.deleteMany({\n * status: 'INACTIVE',\n * createdAt: { lt: new Date('2020-01-01') }\n * });\n * console.log(`Deleted ${result.count} products`);\n * ```\n */\n async deleteMany(where: any): Promise<{ count: number }> {\n this.logger.log('Deleting multiple records');\n return await this.model.deleteMany({ where });\n }\n\n /**\n * Count records\n *\n * @param where - Optional where clause to filter records\n * @returns Promise resolving to the count of records\n *\n * @example\n * ```typescript\n * // Count all products\n * const total = await productRepository.count();\n *\n * // Count active products\n * const activeCount = await productRepository.count({ status: 'ACTIVE' });\n * ```\n */\n async count(where?: any): Promise<number> {\n this.logger.debug('Counting records');\n return await this.model.count({ where });\n }\n\n /**\n * Check if a record exists\n *\n * @param where - The where clause to match records\n * @returns Promise resolving to true if at least one record exists, false otherwise\n *\n * @example\n * ```typescript\n * const skuExists = await productRepository.exists({\n * sku: 'WDG-001'\n * });\n * ```\n */\n async exists(where: any): Promise<boolean> {\n const count = await this.model.count({ where });\n return count > 0;\n }\n}\n","import { createParamDecorator, ExecutionContext } from '@nestjs/common';\nimport { TenantInfo } from '../interfaces';\nimport { TenantContextService } from '../services/tenant-context.service';\n\n/**\n * Parameter decorator that injects tenant metadata into controller method\n *\n * This decorator retrieves tenant information (ID, slug, type, etc.)\n * from the REQUEST-SCOPED TenantContextService.\n *\n * Useful for:\n * - Logging tenant-specific information\n * - Implementing tenant-specific business logic\n * - Auditing and tracking\n * - Conditional feature flags\n *\n * @returns TenantInfo object with tenant metadata\n *\n * @example\n * // Access tenant metadata\n * @Get('info')\n * async getTenantInfo(@Tenant() tenant: TenantInfo) {\n * return {\n * id: tenant.id,\n * subdomain: tenant.subdomain,\n * type: tenant.type,\n * };\n * }\n *\n * @example\n * // Use for logging\n * @Post()\n * async createUser(\n * @Body() dto: CreateUserDto,\n * @Tenant() tenant: TenantInfo,\n * ) {\n * this.logger.log(`Creating user for tenant: ${tenant.subdomain}`);\n * // ...\n * }\n *\n * @example\n * // Conditional business logic\n * @Get('features')\n * async getFeatures(@Tenant() tenant: TenantInfo) {\n * if (tenant.type === 'ENTERPRISE') {\n * return ['feature-a', 'feature-b', 'feature-c'];\n * }\n * return ['feature-a'];\n * }\n */\nexport const Tenant = createParamDecorator((data: unknown, ctx: ExecutionContext): TenantInfo => {\n const request = ctx.switchToHttp().getRequest();\n\n // Get from TenantContextService\n const tenantContext = request.app?.get?.(TenantContextService);\n\n if (!tenantContext) {\n throw new Error('TenantContextService not found.');\n }\n\n return tenantContext.getTenant();\n});\n","import { SetMetadata } from '@nestjs/common';\n\n/**\n * Onboarding Decorator - Marks endpoints that require onboarding token\n *\n * Use this decorator on controllers or route handlers that should only be\n * accessible during the onboarding flow with JWT tokens containing type='onboarding'.\n *\n * These endpoints:\n * - Accept ONLY tokens with type='onboarding'\n * - Reject regular access tokens (type='access')\n * - Skip tenant validation and refresh token checks\n * - Only validate JWT signature and expiry\n *\n * Useful for:\n * - Email/phone verification during onboarding\n * - Onboarding status checks\n * - Resending OTPs during registration\n *\n * @example\n * // On a controller method\n * @Post('verify-email')\n * @Onboarding()\n * async verifyEmail(@Request() req, @Body() dto: VerifyEmailDto) {\n * const userId = req.user.id; // Available from VrittiAuthGuard\n * return this.service.verifyEmail(userId, dto.otp);\n * }\n *\n * @example\n * // Multiple onboarding endpoints\n * @Controller('onboarding')\n * export class OnboardingController {\n * @Post('verify-email')\n * @Onboarding()\n * async verifyEmail() { ... }\n *\n * @Post('resend-otp')\n * @Onboarding()\n * async resendOtp() { ... }\n * }\n */\nexport const Onboarding = () => SetMetadata('isOnboarding', true);\n","import { SetMetadata } from '@nestjs/common';\n\n/**\n * Public Decorator - Marks endpoints that don't require authentication\n *\n * Use this decorator on controllers or route handlers to bypass VrittiAuthGuard\n * tenant validation. Useful for:\n * - Login/signup endpoints\n * - Health checks\n * - Public documentation endpoints\n * - Webhook endpoints that don't require tenant context\n *\n * @example\n * // On a controller method\n * @Public()\n * @Post('auth/login')\n * async login(@Body() dto: LoginDto) {\n * return this.authService.login(dto);\n * }\n *\n * @example\n * // On an entire controller\n * @Public()\n * @Controller('health')\n * export class HealthController {\n * @Get()\n * check() {\n * return { status: 'ok' };\n * }\n * }\n */\nexport const Public = () => SetMetadata('isPublic', true);\n","import { Module } from '@nestjs/common';\nimport { CsrfGuard } from './guards/csrf.guard';\n\n/**\n * HTTP Module\n *\n * Provides HTTP utilities including:\n * - CSRF Guard for request protection\n * - HTTP Exception Filter for standardized error responses\n *\n * Usage:\n * Import this module to access HTTP guards and filters.\n * Guards and filters are registered globally in the main application.\n */\n@Module({\n providers: [CsrfGuard],\n exports: [CsrfGuard],\n})\nexport class HttpModule {}\n","import {\n CanActivate,\n ExecutionContext,\n ForbiddenException,\n Injectable,\n Logger,\n} from '@nestjs/common';\nimport { Reflector } from '@nestjs/core';\nimport { FastifyReply, FastifyRequest } from 'fastify';\n\n/**\n * CSRF Guard\n *\n * Global guard that automatically protects all state-changing requests (POST, PUT, PATCH, DELETE)\n * from CSRF attacks using Fastify's csrf-protection plugin.\n *\n * Flow:\n * 1. Skip safe methods (GET, HEAD, OPTIONS)\n * 2. Skip endpoints marked with @Public()\n * 3. Validate CSRF token for all other requests\n *\n * Token Sources (in priority order by @fastify/csrf-protection):\n * 1. req.headers['csrf-token']\n * 2. req.headers['xsrf-token']\n * 3. req.headers['x-csrf-token']\n * 4. req.headers['x-xsrf-token']\n * 5. req.body._csrf\n *\n * This guard should be registered globally in main.ts after CSRF plugin registration.\n */\n@Injectable()\nexport class CsrfGuard implements CanActivate {\n private readonly logger = new Logger(CsrfGuard.name);\n\n constructor(private reflector: Reflector) {}\n\n async canActivate(context: ExecutionContext): Promise<boolean> {\n const request = context.switchToHttp().getRequest<FastifyRequest>();\n const reply = context.switchToHttp().getResponse<FastifyReply>();\n\n // Skip CSRF check for safe methods (GET, HEAD, OPTIONS)\n const safeMethods = ['GET', 'HEAD', 'OPTIONS'];\n if (safeMethods.includes(request.method)) {\n return true;\n }\n\n\n // Validate CSRF token using Fastify's csrfProtection hook\n try {\n // Access the Fastify instance's CSRF protection\n const fastifyInstance = request.server as any;\n\n if (!fastifyInstance.csrfProtection) {\n this.logger.error(\n 'CSRF protection plugin not found. Ensure @fastify/csrf-protection is registered.',\n );\n throw new ForbiddenException('CSRF protection not configured');\n }\n\n // Call the CSRF protection hook\n // This validates the token from headers or body against the signed cookie\n await new Promise<void>((resolve, reject) => {\n fastifyInstance.csrfProtection(request, reply, (err?: Error) => {\n if (err) {\n reject(err);\n } else {\n resolve();\n }\n });\n });\n\n this.logger.debug(\n `CSRF validation successful for ${request.method} ${request.url}`,\n );\n\n return true;\n } catch (error) {\n this.logger.warn(\n `CSRF validation failed for ${request.method} ${request.url}: ${error instanceof Error ? error.message : 'Unknown error'}`,\n );\n\n throw new ForbiddenException({\n errors: [\n {\n field: 'csrf',\n message: 'Invalid or missing CSRF token',\n },\n ],\n message: 'CSRF validation failed',\n });\n }\n }\n}\n","import {\n ExceptionFilter,\n Catch,\n ArgumentsHost,\n HttpException,\n HttpStatus,\n Logger,\n} from '@nestjs/common';\nimport { FastifyReply } from 'fastify';\n\n/**\n * Error response format\n */\ninterface ErrorField {\n field: string;\n message: string;\n}\n\ninterface ErrorResponse {\n errors: ErrorField[];\n message?: string;\n statusCode: number;\n timestamp: string;\n path: string;\n}\n\n/**\n * Global HTTP Exception Filter\n *\n * Standardizes all error responses in the format:\n * {\n * errors: [{ field: string, message: string }],\n * message?: string,\n * statusCode: number,\n * timestamp: string,\n * path: string\n * }\n *\n * Handles:\n * - Validation errors (class-validator) - Converts to field-specific errors\n * - HTTP exceptions - Maps to standardized format\n * - Unknown errors - Returns generic 500 error\n */\n@Catch()\nexport class HttpExceptionFilter implements ExceptionFilter {\n private readonly logger = new Logger(HttpExceptionFilter.name);\n\n catch(exception: unknown, host: ArgumentsHost) {\n const ctx = host.switchToHttp();\n const reply = ctx.getResponse<FastifyReply>();\n const request = ctx.getRequest();\n\n let status = HttpStatus.INTERNAL_SERVER_ERROR;\n let errors: ErrorField[] = [];\n let message: string | undefined;\n\n // Handle HttpException (includes BadRequestException for validation)\n if (exception instanceof HttpException) {\n status = exception.getStatus();\n const exceptionResponse = exception.getResponse();\n\n if (typeof exceptionResponse === 'object' && exceptionResponse !== null) {\n const responseObj = exceptionResponse as any;\n\n // Handle class-validator validation errors\n if (Array.isArray(responseObj.message)) {\n errors = this.parseValidationErrors(responseObj.message);\n message = 'Validation failed';\n } else if (responseObj.message) {\n // Handle single error message\n errors = [\n {\n field: 'general',\n message: responseObj.message,\n },\n ];\n message = responseObj.message;\n }\n } else if (typeof exceptionResponse === 'string') {\n errors = [\n {\n field: 'general',\n message: exceptionResponse,\n },\n ];\n message = exceptionResponse;\n }\n } else if (exception instanceof Error) {\n // Handle regular Error objects\n this.logger.error(\n `Unhandled error: ${exception.message}`,\n exception.stack,\n );\n\n errors = [\n {\n field: 'general',\n message: 'Internal server error',\n },\n ];\n message = 'An unexpected error occurred';\n } else {\n // Handle unknown exceptions\n this.logger.error('Unknown exception type', exception);\n\n errors = [\n {\n field: 'general',\n message: 'Internal server error',\n },\n ];\n message = 'An unexpected error occurred';\n }\n\n // Build error response\n const errorResponse: ErrorResponse = {\n errors,\n message,\n statusCode: status,\n timestamp: new Date().toISOString(),\n path: request.url,\n };\n\n // Log error details\n if (status >= 500) {\n this.logger.error(\n `HTTP ${status} Error: ${JSON.stringify(errorResponse)}`,\n exception instanceof Error ? exception.stack : undefined,\n );\n } else {\n this.logger.warn(`HTTP ${status} Error: ${JSON.stringify(errorResponse)}`);\n }\n\n reply.status(status).send(errorResponse);\n }\n\n /**\n * Parse class-validator error messages into field-specific errors\n */\n private parseValidationErrors(messages: any[]): ErrorField[] {\n const errors: ErrorField[] = [];\n\n for (const msg of messages) {\n if (typeof msg === 'string') {\n // Simple string message\n errors.push({\n field: 'general',\n message: msg,\n });\n } else if (typeof msg === 'object' && msg.property && msg.constraints) {\n // class-validator error format\n const field = msg.property;\n const constraintMessages = Object.values(msg.constraints) as string[];\n\n for (const constraintMsg of constraintMessages) {\n errors.push({\n field,\n message: constraintMsg,\n });\n }\n }\n }\n\n return errors.length > 0\n ? errors\n : [{ field: 'general', message: 'Validation failed' }];\n }\n}\n","import {\n ExceptionFilter,\n Catch,\n ArgumentsHost,\n HttpException,\n HttpStatus,\n Logger,\n} from '@nestjs/common';\nimport type { FastifyReply } from 'fastify';\nimport type { ApiErrorResponse, FieldError } from '../types/error-response.types';\n\n/**\n * Converts an HTTP status code to its corresponding title string.\n * Uses the HttpStatus enum to map status codes to human-readable titles.\n *\n * @param status - The HTTP status code\n * @returns The human-readable title for the status code\n *\n * @example\n * getHttpStatusTitle(400) // Returns: \"Bad Request\"\n * getHttpStatusTitle(404) // Returns: \"Not Found\"\n * getHttpStatusTitle(500) // Returns: \"Internal Server Error\"\n */\nexport function getHttpStatusTitle(status: number): string {\n // Find the enum key for the given status code\n const enumKey = Object.entries(HttpStatus).find(\n ([key, value]) => value === status && isNaN(Number(key))\n )?.[0];\n\n if (!enumKey) {\n return 'Error';\n }\n\n // Convert enum key to title case (e.g., BAD_REQUEST -> Bad Request)\n return enumKey\n .split('_')\n .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())\n .join(' ');\n}\n\n/**\n * Global HTTP Exception Filter implementing RFC 7807 Problem Details\n *\n * Transforms all exceptions into a standardized RFC 7807 format:\n * {\n * title: string, // Human-readable status title\n * status: number, // HTTP status code\n * detail: string, // Detailed error description\n * errors: FieldError[] // Field-specific error messages\n * }\n *\n * Handles:\n * - Custom field exceptions from @vritti/api-sdk (BaseFieldException)\n * - Class-validator DTO validation errors\n * - Standard NestJS HTTP exceptions\n * - Unknown errors\n */\n@Catch()\nexport class HttpExceptionFilter implements ExceptionFilter {\n private readonly logger = new Logger(HttpExceptionFilter.name);\n\n catch(exception: unknown, host: ArgumentsHost) {\n const ctx = host.switchToHttp();\n const response = ctx.getResponse<FastifyReply>();\n\n let status = HttpStatus.INTERNAL_SERVER_ERROR;\n let errors: FieldError[] = [];\n let detail = 'Internal server error';\n\n if (exception instanceof HttpException) {\n status = exception.getStatus();\n const exceptionResponse = exception.getResponse();\n\n if (typeof exceptionResponse === 'object' && exceptionResponse !== null) {\n const responseObj = exceptionResponse as any;\n\n // Handle custom field exceptions from @vritti/api-sdk\n if (responseObj.errors && Array.isArray(responseObj.errors)) {\n errors = responseObj.errors;\n detail = responseObj.detail || exception.message;\n }\n // Handle class-validator DTO validation errors\n else if (responseObj.message && Array.isArray(responseObj.message)) {\n errors = responseObj.message.map((msg: any) => {\n if (typeof msg === 'object' && msg.property && msg.constraints) {\n return {\n field: msg.property,\n message: Object.values(msg.constraints)[0] as string,\n };\n }\n return { message: typeof msg === 'string' ? msg : JSON.stringify(msg) };\n });\n detail = 'Validation failed';\n }\n // Handle standard NestJS exceptions\n else if (responseObj.message) {\n errors = [{ message: Array.isArray(responseObj.message) ? responseObj.message.join(', ') : responseObj.message }];\n detail = responseObj.error || exception.message;\n }\n } else if (typeof exceptionResponse === 'string') {\n errors = [{ message: exceptionResponse }];\n detail = exceptionResponse;\n }\n } else {\n // Unknown errors\n this.logger.error('Unexpected error:', exception);\n errors = [{ message: 'An unexpected error occurred' }];\n }\n\n const problemDetails: ApiErrorResponse = {\n title: getHttpStatusTitle(status),\n status,\n detail,\n errors,\n };\n\n response.status(status).send(problemDetails);\n }\n}\n","import { HttpException, HttpStatus } from '@nestjs/common';\n\nexport interface FieldError {\n field?: string;\n message: string;\n}\n\nexport abstract class BaseFieldException extends HttpException {\n constructor(\n statusOrMessageOrErrors: HttpStatus | string | FieldError[],\n messageOrStatus?: string | HttpStatus,\n statusOrDetail?: HttpStatus | string,\n detail?: string\n ) {\n let errors: FieldError[];\n let httpStatus: HttpStatus;\n let finalDetail: string | undefined;\n\n if (Array.isArray(statusOrMessageOrErrors)) {\n // (errors: FieldError[], httpStatus: HttpStatus, detail?: string)\n errors = statusOrMessageOrErrors;\n httpStatus = messageOrStatus as HttpStatus || HttpStatus.BAD_REQUEST;\n finalDetail = typeof statusOrDetail === 'string' ? statusOrDetail : undefined;\n } else if (typeof statusOrMessageOrErrors === 'string' && typeof messageOrStatus === 'string') {\n // (field: string, message: string, httpStatus: HttpStatus, detail?: string)\n errors = [{ field: statusOrMessageOrErrors, message: messageOrStatus }];\n httpStatus = statusOrDetail as HttpStatus;\n finalDetail = detail;\n } else if (typeof statusOrMessageOrErrors === 'string') {\n // (message: string, httpStatus: HttpStatus, detail?: string)\n errors = [{ message: statusOrMessageOrErrors }];\n httpStatus = messageOrStatus as HttpStatus;\n finalDetail = typeof statusOrDetail === 'string' ? statusOrDetail : undefined;\n } else {\n errors = [{ message: 'An error occurred' }];\n httpStatus = statusOrMessageOrErrors;\n finalDetail = undefined;\n }\n\n const response = finalDetail !== undefined ? { errors, detail: finalDetail } : { errors };\n super(response, httpStatus);\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { BaseFieldException, FieldError } from './base-field.exception';\n\n/**\n * Exception thrown when a request is malformed or contains invalid data (HTTP 400).\n *\n * @example\n * // Simple message\n * throw new BadRequestException('Invalid request data');\n *\n * // Field-specific error\n * throw new BadRequestException('email', 'Invalid email format');\n *\n * // With detail\n * throw new BadRequestException('email', 'Invalid email format', 'Email must be in valid format');\n *\n * // Multiple field errors\n * throw new BadRequestException([\n * { field: 'email', message: 'Invalid email' },\n * { field: 'password', message: 'Password too short' }\n * ]);\n */\nexport class BadRequestException extends BaseFieldException {\n constructor(\n messageOrField: string | FieldError[],\n fieldMessageOrDetail?: string,\n detail?: string\n ) {\n if (Array.isArray(messageOrField)) {\n // (errors: FieldError[], detail?: string)\n super(messageOrField, HttpStatus.BAD_REQUEST, fieldMessageOrDetail);\n } else if (detail !== undefined) {\n // (field: string, message: string, detail: string)\n super(messageOrField, fieldMessageOrDetail!, HttpStatus.BAD_REQUEST, detail);\n } else if (fieldMessageOrDetail) {\n // (field: string, message: string)\n super(messageOrField, fieldMessageOrDetail, HttpStatus.BAD_REQUEST);\n } else {\n // (message: string)\n super(messageOrField, HttpStatus.BAD_REQUEST);\n }\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { BaseFieldException, FieldError } from './base-field.exception';\n\n/**\n * Exception thrown when authentication is required or has failed (HTTP 401).\n *\n * @example\n * // Simple message\n * throw new UnauthorizedException('Authentication required');\n *\n * // Field-specific error\n * throw new UnauthorizedException('token', 'Invalid or expired token');\n *\n * // With detail\n * throw new UnauthorizedException('token', 'Invalid token', 'Please login again');\n *\n * // Multiple field errors\n * throw new UnauthorizedException([\n * { field: 'token', message: 'Token expired' }\n * ]);\n */\nexport class UnauthorizedException extends BaseFieldException {\n constructor(\n messageOrField: string | FieldError[],\n fieldMessageOrDetail?: string,\n detail?: string\n ) {\n if (Array.isArray(messageOrField)) {\n // (errors: FieldError[], detail?: string)\n super(messageOrField, HttpStatus.UNAUTHORIZED, fieldMessageOrDetail);\n } else if (detail !== undefined) {\n // (field: string, message: string, detail: string)\n super(messageOrField, fieldMessageOrDetail!, HttpStatus.UNAUTHORIZED, detail);\n } else if (fieldMessageOrDetail) {\n // (field: string, message: string)\n super(messageOrField, fieldMessageOrDetail, HttpStatus.UNAUTHORIZED);\n } else {\n // (message: string)\n super(messageOrField, HttpStatus.UNAUTHORIZED);\n }\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { BaseFieldException, FieldError } from './base-field.exception';\n\n/**\n * Exception thrown when the user does not have permission to access a resource (HTTP 403).\n *\n * @example\n * // Simple message\n * throw new ForbiddenException('Access denied');\n *\n * // Field-specific error\n * throw new ForbiddenException('resource', 'You do not have permission');\n *\n * // With detail\n * throw new ForbiddenException('resource', 'Access denied', 'Admin role required');\n *\n * // Multiple field errors\n * throw new ForbiddenException([\n * { field: 'action', message: 'Insufficient permissions' }\n * ]);\n */\nexport class ForbiddenException extends BaseFieldException {\n constructor(\n messageOrField: string | FieldError[],\n fieldMessageOrDetail?: string,\n detail?: string\n ) {\n if (Array.isArray(messageOrField)) {\n // (errors: FieldError[], detail?: string)\n super(messageOrField, HttpStatus.FORBIDDEN, fieldMessageOrDetail);\n } else if (detail !== undefined) {\n // (field: string, message: string, detail: string)\n super(messageOrField, fieldMessageOrDetail!, HttpStatus.FORBIDDEN, detail);\n } else if (fieldMessageOrDetail) {\n // (field: string, message: string)\n super(messageOrField, fieldMessageOrDetail, HttpStatus.FORBIDDEN);\n } else {\n // (message: string)\n super(messageOrField, HttpStatus.FORBIDDEN);\n }\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { BaseFieldException, FieldError } from './base-field.exception';\n\n/**\n * Exception thrown when a requested resource cannot be found (HTTP 404).\n *\n * @example\n * // Simple message\n * throw new NotFoundException('Resource not found');\n *\n * // Field-specific error\n * throw new NotFoundException('userId', 'User not found');\n *\n * // With detail\n * throw new NotFoundException('userId', 'User not found', 'No user exists with the provided ID');\n *\n * // Multiple field errors\n * throw new NotFoundException([\n * { field: 'userId', message: 'User does not exist' }\n * ]);\n */\nexport class NotFoundException extends BaseFieldException {\n constructor(\n messageOrField: string | FieldError[],\n fieldMessageOrDetail?: string,\n detail?: string\n ) {\n if (Array.isArray(messageOrField)) {\n // (errors: FieldError[], detail?: string)\n super(messageOrField, HttpStatus.NOT_FOUND, fieldMessageOrDetail);\n } else if (detail !== undefined) {\n // (field: string, message: string, detail: string)\n super(messageOrField, fieldMessageOrDetail!, HttpStatus.NOT_FOUND, detail);\n } else if (fieldMessageOrDetail) {\n // (field: string, message: string)\n super(messageOrField, fieldMessageOrDetail, HttpStatus.NOT_FOUND);\n } else {\n // (message: string)\n super(messageOrField, HttpStatus.NOT_FOUND);\n }\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { BaseFieldException, FieldError } from './base-field.exception';\n\n/**\n * Exception thrown when a request conflicts with the current state (HTTP 409).\n * Commonly used for duplicate resources or concurrent modification issues.\n *\n * @example\n * // Simple message\n * throw new ConflictException('Resource already exists');\n *\n * // Field-specific error\n * throw new ConflictException('email', 'Email already registered');\n *\n * // With detail\n * throw new ConflictException('email', 'Email already exists', 'Try logging in instead');\n *\n * // Multiple field errors\n * throw new ConflictException([\n * { field: 'email', message: 'Email already in use' }\n * ]);\n */\nexport class ConflictException extends BaseFieldException {\n constructor(\n messageOrField: string | FieldError[],\n fieldMessageOrDetail?: string,\n detail?: string\n ) {\n if (Array.isArray(messageOrField)) {\n // (errors: FieldError[], detail?: string)\n super(messageOrField, HttpStatus.CONFLICT, fieldMessageOrDetail);\n } else if (detail !== undefined) {\n // (field: string, message: string, detail: string)\n super(messageOrField, fieldMessageOrDetail!, HttpStatus.CONFLICT, detail);\n } else if (fieldMessageOrDetail) {\n // (field: string, message: string)\n super(messageOrField, fieldMessageOrDetail, HttpStatus.CONFLICT);\n } else {\n // (message: string)\n super(messageOrField, HttpStatus.CONFLICT);\n }\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { BaseFieldException, FieldError } from './base-field.exception';\n\n/**\n * Exception thrown when an unexpected server error occurs (HTTP 500).\n *\n * @example\n * // Simple message\n * throw new InternalServerErrorException('An unexpected error occurred');\n *\n * // Field-specific error\n * throw new InternalServerErrorException('database', 'Database connection failed');\n *\n * // With detail\n * throw new InternalServerErrorException('database', 'Connection failed', 'Please try again later');\n *\n * // Multiple field errors\n * throw new InternalServerErrorException([\n * { field: 'system', message: 'Internal error' }\n * ]);\n */\nexport class InternalServerErrorException extends BaseFieldException {\n constructor(\n messageOrField: string | FieldError[],\n fieldMessageOrDetail?: string,\n detail?: string\n ) {\n if (Array.isArray(messageOrField)) {\n // (errors: FieldError[], detail?: string)\n super(messageOrField, HttpStatus.INTERNAL_SERVER_ERROR, fieldMessageOrDetail);\n } else if (detail !== undefined) {\n // (field: string, message: string, detail: string)\n super(messageOrField, fieldMessageOrDetail!, HttpStatus.INTERNAL_SERVER_ERROR, detail);\n } else if (fieldMessageOrDetail) {\n // (field: string, message: string)\n super(messageOrField, fieldMessageOrDetail, HttpStatus.INTERNAL_SERVER_ERROR);\n } else {\n // (message: string)\n super(messageOrField, HttpStatus.INTERNAL_SERVER_ERROR);\n }\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { BaseFieldException, FieldError } from './base-field.exception';\n\n/**\n * Exception thrown when request validation fails (HTTP 400).\n * Typically used for form validation or DTO validation errors.\n *\n * @example\n * // Multiple validation errors\n * throw new ValidationException([\n * { field: 'email', message: 'Invalid email format' },\n * { field: 'password', message: 'Password must be at least 8 characters' }\n * ]);\n *\n * // With detail\n * throw new ValidationException(\n * [{ field: 'email', message: 'Invalid format' }],\n * 'Please correct the errors and try again'\n * );\n */\nexport class ValidationException extends BaseFieldException {\n constructor(errors: FieldError[], detail?: string) {\n super(errors, HttpStatus.BAD_REQUEST, detail);\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { BaseFieldException, FieldError } from './base-field.exception';\n\n/**\n * Exception thrown when the request is well-formed but contains semantic errors (HTTP 422).\n * Used for business logic validation failures that prevent processing.\n *\n * @example\n * // Simple message\n * throw new UnprocessableEntityException('Cannot process the request');\n *\n * // Field-specific error\n * throw new UnprocessableEntityException('age', 'Age must be 18 or older');\n *\n * // With detail\n * throw new UnprocessableEntityException('quantity', 'Insufficient stock', 'Only 5 items available');\n *\n * // Multiple field errors\n * throw new UnprocessableEntityException([\n * { field: 'startDate', message: 'Start date must be before end date' },\n * { field: 'endDate', message: 'End date cannot be in the past' }\n * ]);\n */\nexport class UnprocessableEntityException extends BaseFieldException {\n constructor(\n messageOrField: string | FieldError[],\n fieldMessageOrDetail?: string,\n detail?: string\n ) {\n if (Array.isArray(messageOrField)) {\n // (errors: FieldError[], detail?: string)\n super(messageOrField, HttpStatus.UNPROCESSABLE_ENTITY, fieldMessageOrDetail);\n } else if (detail !== undefined) {\n // (field: string, message: string, detail: string)\n super(messageOrField, fieldMessageOrDetail!, HttpStatus.UNPROCESSABLE_ENTITY, detail);\n } else if (fieldMessageOrDetail) {\n // (field: string, message: string)\n super(messageOrField, fieldMessageOrDetail, HttpStatus.UNPROCESSABLE_ENTITY);\n } else {\n // (message: string)\n super(messageOrField, HttpStatus.UNPROCESSABLE_ENTITY);\n }\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { BaseFieldException, FieldError } from './base-field.exception';\n\n/**\n * Exception thrown when rate limiting is triggered (HTTP 429).\n * Used to prevent abuse and ensure fair resource usage.\n *\n * @example\n * // Simple message\n * throw new TooManyRequestsException('Too many requests');\n *\n * // Field-specific error\n * throw new TooManyRequestsException('api', 'Rate limit exceeded');\n *\n * // With detail\n * throw new TooManyRequestsException('api', 'Rate limit exceeded', 'Try again in 60 seconds');\n *\n * // Multiple field errors\n * throw new TooManyRequestsException([\n * { field: 'requests', message: 'Rate limit exceeded for this endpoint' }\n * ]);\n */\nexport class TooManyRequestsException extends BaseFieldException {\n constructor(\n messageOrField: string | FieldError[],\n fieldMessageOrDetail?: string,\n detail?: string\n ) {\n if (Array.isArray(messageOrField)) {\n // (errors: FieldError[], detail?: string)\n super(messageOrField, HttpStatus.TOO_MANY_REQUESTS, fieldMessageOrDetail);\n } else if (detail !== undefined) {\n // (field: string, message: string, detail: string)\n super(messageOrField, fieldMessageOrDetail!, HttpStatus.TOO_MANY_REQUESTS, detail);\n } else if (fieldMessageOrDetail) {\n // (field: string, message: string)\n super(messageOrField, fieldMessageOrDetail, HttpStatus.TOO_MANY_REQUESTS);\n } else {\n // (message: string)\n super(messageOrField, HttpStatus.TOO_MANY_REQUESTS);\n }\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { BaseFieldException, FieldError } from './base-field.exception';\n\n/**\n * Exception thrown when the service is temporarily unavailable (HTTP 503).\n * Used during maintenance, overload, or temporary outages.\n *\n * @example\n * // Simple message\n * throw new ServiceUnavailableException('Service temporarily unavailable');\n *\n * // Field-specific error\n * throw new ServiceUnavailableException('service', 'Scheduled maintenance in progress');\n *\n * // With detail\n * throw new ServiceUnavailableException('service', 'Maintenance', 'Service will be back at 2 PM EST');\n *\n * // Multiple field errors\n * throw new ServiceUnavailableException([\n * { field: 'database', message: 'Database is temporarily unavailable' }\n * ]);\n */\nexport class ServiceUnavailableException extends BaseFieldException {\n constructor(\n messageOrField: string | FieldError[],\n fieldMessageOrDetail?: string,\n detail?: string\n ) {\n if (Array.isArray(messageOrField)) {\n // (errors: FieldError[], detail?: string)\n super(messageOrField, HttpStatus.SERVICE_UNAVAILABLE, fieldMessageOrDetail);\n } else if (detail !== undefined) {\n // (field: string, message: string, detail: string)\n super(messageOrField, fieldMessageOrDetail!, HttpStatus.SERVICE_UNAVAILABLE, detail);\n } else if (fieldMessageOrDetail) {\n // (field: string, message: string)\n super(messageOrField, fieldMessageOrDetail, HttpStatus.SERVICE_UNAVAILABLE);\n } else {\n // (message: string)\n super(messageOrField, HttpStatus.SERVICE_UNAVAILABLE);\n }\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { BaseFieldException, FieldError } from './base-field.exception';\n\n/**\n * Exception thrown when an HTTP method is not supported for the endpoint (HTTP 405).\n * For example, when a POST is sent to a GET-only endpoint.\n *\n * @example\n * // Simple message\n * throw new MethodNotAllowedException('Method not allowed');\n *\n * // Field-specific error\n * throw new MethodNotAllowedException('method', 'POST method not allowed on this endpoint');\n *\n * // With detail\n * throw new MethodNotAllowedException('method', 'Not allowed', 'Only GET and PUT are supported');\n *\n * // Multiple field errors\n * throw new MethodNotAllowedException([\n * { field: 'method', message: 'DELETE is not allowed on this resource' }\n * ]);\n */\nexport class MethodNotAllowedException extends BaseFieldException {\n constructor(\n messageOrField: string | FieldError[],\n fieldMessageOrDetail?: string,\n detail?: string\n ) {\n if (Array.isArray(messageOrField)) {\n // (errors: FieldError[], detail?: string)\n super(messageOrField, HttpStatus.METHOD_NOT_ALLOWED, fieldMessageOrDetail);\n } else if (detail !== undefined) {\n // (field: string, message: string, detail: string)\n super(messageOrField, fieldMessageOrDetail!, HttpStatus.METHOD_NOT_ALLOWED, detail);\n } else if (fieldMessageOrDetail) {\n // (field: string, message: string)\n super(messageOrField, fieldMessageOrDetail, HttpStatus.METHOD_NOT_ALLOWED);\n } else {\n // (message: string)\n super(messageOrField, HttpStatus.METHOD_NOT_ALLOWED);\n }\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { BaseFieldException, FieldError } from './base-field.exception';\n\n/**\n * Exception thrown when a resource has been permanently removed (HTTP 410).\n * Unlike 404, this indicates the resource existed but is intentionally gone.\n *\n * @example\n * // Simple message\n * throw new GoneException('Resource permanently deleted');\n *\n * // Field-specific error\n * throw new GoneException('account', 'Account has been permanently deleted');\n *\n * // With detail\n * throw new GoneException('account', 'Deleted', 'This account was removed on user request');\n *\n * // Multiple field errors\n * throw new GoneException([\n * { field: 'resource', message: 'This content has been permanently removed' }\n * ]);\n */\nexport class GoneException extends BaseFieldException {\n constructor(\n messageOrField: string | FieldError[],\n fieldMessageOrDetail?: string,\n detail?: string\n ) {\n if (Array.isArray(messageOrField)) {\n // (errors: FieldError[], detail?: string)\n super(messageOrField, HttpStatus.GONE, fieldMessageOrDetail);\n } else if (detail !== undefined) {\n // (field: string, message: string, detail: string)\n super(messageOrField, fieldMessageOrDetail!, HttpStatus.GONE, detail);\n } else if (fieldMessageOrDetail) {\n // (field: string, message: string)\n super(messageOrField, fieldMessageOrDetail, HttpStatus.GONE);\n } else {\n // (message: string)\n super(messageOrField, HttpStatus.GONE);\n }\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { BaseFieldException, FieldError } from './base-field.exception';\n\n/**\n * Exception thrown when content negotiation fails (HTTP 406).\n * Used when the server cannot produce a response matching the Accept headers.\n *\n * @example\n * // Simple message\n * throw new NotAcceptableException('Requested format not available');\n *\n * // Field-specific error\n * throw new NotAcceptableException('accept', 'Cannot produce response in requested format');\n *\n * // With detail\n * throw new NotAcceptableException('accept', 'Format not supported', 'Only JSON is available');\n *\n * // Multiple field errors\n * throw new NotAcceptableException([\n * { field: 'contentType', message: 'XML format is not supported' }\n * ]);\n */\nexport class NotAcceptableException extends BaseFieldException {\n constructor(\n messageOrField: string | FieldError[],\n fieldMessageOrDetail?: string,\n detail?: string\n ) {\n if (Array.isArray(messageOrField)) {\n // (errors: FieldError[], detail?: string)\n super(messageOrField, HttpStatus.NOT_ACCEPTABLE, fieldMessageOrDetail);\n } else if (detail !== undefined) {\n // (field: string, message: string, detail: string)\n super(messageOrField, fieldMessageOrDetail!, HttpStatus.NOT_ACCEPTABLE, detail);\n } else if (fieldMessageOrDetail) {\n // (field: string, message: string)\n super(messageOrField, fieldMessageOrDetail, HttpStatus.NOT_ACCEPTABLE);\n } else {\n // (message: string)\n super(messageOrField, HttpStatus.NOT_ACCEPTABLE);\n }\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { BaseFieldException, FieldError } from './base-field.exception';\n\n/**\n * Exception thrown when a request takes too long to process (HTTP 408).\n * Used when the client or server times out while waiting for completion.\n *\n * @example\n * // Simple message\n * throw new RequestTimeoutException('Request timeout');\n *\n * // Field-specific error\n * throw new RequestTimeoutException('operation', 'Operation timed out');\n *\n * // With detail\n * throw new RequestTimeoutException('query', 'Database query timeout', 'Try with fewer filters');\n *\n * // Multiple field errors\n * throw new RequestTimeoutException([\n * { field: 'processing', message: 'Request took too long to complete' }\n * ]);\n */\nexport class RequestTimeoutException extends BaseFieldException {\n constructor(\n messageOrField: string | FieldError[],\n fieldMessageOrDetail?: string,\n detail?: string\n ) {\n if (Array.isArray(messageOrField)) {\n // (errors: FieldError[], detail?: string)\n super(messageOrField, HttpStatus.REQUEST_TIMEOUT, fieldMessageOrDetail);\n } else if (detail !== undefined) {\n // (field: string, message: string, detail: string)\n super(messageOrField, fieldMessageOrDetail!, HttpStatus.REQUEST_TIMEOUT, detail);\n } else if (fieldMessageOrDetail) {\n // (field: string, message: string)\n super(messageOrField, fieldMessageOrDetail, HttpStatus.REQUEST_TIMEOUT);\n } else {\n // (message: string)\n super(messageOrField, HttpStatus.REQUEST_TIMEOUT);\n }\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { BaseFieldException, FieldError } from './base-field.exception';\n\n/**\n * Exception thrown when request payload exceeds size limits (HTTP 413).\n * Commonly used for file upload size restrictions or large request bodies.\n *\n * @example\n * // Simple message\n * throw new PayloadTooLargeException('Request payload too large');\n *\n * // Field-specific error\n * throw new PayloadTooLargeException('file', 'File size exceeds maximum allowed');\n *\n * // With detail\n * throw new PayloadTooLargeException('file', 'File too large', 'Maximum size is 10MB');\n *\n * // Multiple field errors\n * throw new PayloadTooLargeException([\n * { field: 'upload', message: 'File exceeds 10MB limit' }\n * ]);\n */\nexport class PayloadTooLargeException extends BaseFieldException {\n constructor(\n messageOrField: string | FieldError[],\n fieldMessageOrDetail?: string,\n detail?: string\n ) {\n if (Array.isArray(messageOrField)) {\n // (errors: FieldError[], detail?: string)\n super(messageOrField, HttpStatus.PAYLOAD_TOO_LARGE, fieldMessageOrDetail);\n } else if (detail !== undefined) {\n // (field: string, message: string, detail: string)\n super(messageOrField, fieldMessageOrDetail!, HttpStatus.PAYLOAD_TOO_LARGE, detail);\n } else if (fieldMessageOrDetail) {\n // (field: string, message: string)\n super(messageOrField, fieldMessageOrDetail, HttpStatus.PAYLOAD_TOO_LARGE);\n } else {\n // (message: string)\n super(messageOrField, HttpStatus.PAYLOAD_TOO_LARGE);\n }\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { BaseFieldException, FieldError } from './base-field.exception';\n\n/**\n * Exception thrown when the media type of the request is not supported (HTTP 415).\n * Used when the Content-Type header specifies an unsupported format.\n *\n * @example\n * // Simple message\n * throw new UnsupportedMediaTypeException('Unsupported media type');\n *\n * // Field-specific error\n * throw new UnsupportedMediaTypeException('contentType', 'XML is not supported');\n *\n * // With detail\n * throw new UnsupportedMediaTypeException('contentType', 'Not supported', 'Only JSON and form-data are accepted');\n *\n * // Multiple field errors\n * throw new UnsupportedMediaTypeException([\n * { field: 'contentType', message: 'application/xml is not supported' }\n * ]);\n */\nexport class UnsupportedMediaTypeException extends BaseFieldException {\n constructor(\n messageOrField: string | FieldError[],\n fieldMessageOrDetail?: string,\n detail?: string\n ) {\n if (Array.isArray(messageOrField)) {\n // (errors: FieldError[], detail?: string)\n super(messageOrField, HttpStatus.UNSUPPORTED_MEDIA_TYPE, fieldMessageOrDetail);\n } else if (detail !== undefined) {\n // (field: string, message: string, detail: string)\n super(messageOrField, fieldMessageOrDetail!, HttpStatus.UNSUPPORTED_MEDIA_TYPE, detail);\n } else if (fieldMessageOrDetail) {\n // (field: string, message: string)\n super(messageOrField, fieldMessageOrDetail, HttpStatus.UNSUPPORTED_MEDIA_TYPE);\n } else {\n // (message: string)\n super(messageOrField, HttpStatus.UNSUPPORTED_MEDIA_TYPE);\n }\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { BaseFieldException, FieldError } from './base-field.exception';\n\n/**\n * Exception thrown when a feature or endpoint is not yet implemented (HTTP 501).\n * Used for planned but unavailable functionality.\n *\n * @example\n * // Simple message\n * throw new NotImplementedException('Feature not yet implemented');\n *\n * // Field-specific error\n * throw new NotImplementedException('feature', 'This feature is coming soon');\n *\n * // With detail\n * throw new NotImplementedException('export', 'Not implemented', 'PDF export will be available in v2.0');\n *\n * // Multiple field errors\n * throw new NotImplementedException([\n * { field: 'functionality', message: 'This functionality is not available yet' }\n * ]);\n */\nexport class NotImplementedException extends BaseFieldException {\n constructor(\n messageOrField: string | FieldError[],\n fieldMessageOrDetail?: string,\n detail?: string\n ) {\n if (Array.isArray(messageOrField)) {\n // (errors: FieldError[], detail?: string)\n super(messageOrField, HttpStatus.NOT_IMPLEMENTED, fieldMessageOrDetail);\n } else if (detail !== undefined) {\n // (field: string, message: string, detail: string)\n super(messageOrField, fieldMessageOrDetail!, HttpStatus.NOT_IMPLEMENTED, detail);\n } else if (fieldMessageOrDetail) {\n // (field: string, message: string)\n super(messageOrField, fieldMessageOrDetail, HttpStatus.NOT_IMPLEMENTED);\n } else {\n // (message: string)\n super(messageOrField, HttpStatus.NOT_IMPLEMENTED);\n }\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { BaseFieldException, FieldError } from './base-field.exception';\n\n/**\n * Exception thrown when a gateway or proxy receives an invalid response (HTTP 502).\n * Used when a server acting as a gateway gets an error from an upstream server.\n *\n * @example\n * // Simple message\n * throw new BadGatewayException('Bad gateway');\n *\n * // Field-specific error\n * throw new BadGatewayException('upstream', 'Upstream service returned invalid response');\n *\n * // With detail\n * throw new BadGatewayException('proxy', 'Gateway error', 'Payment service is not responding correctly');\n *\n * // Multiple field errors\n * throw new BadGatewayException([\n * { field: 'gateway', message: 'Invalid response from upstream server' }\n * ]);\n */\nexport class BadGatewayException extends BaseFieldException {\n constructor(\n messageOrField: string | FieldError[],\n fieldMessageOrDetail?: string,\n detail?: string\n ) {\n if (Array.isArray(messageOrField)) {\n // (errors: FieldError[], detail?: string)\n super(messageOrField, HttpStatus.BAD_GATEWAY, fieldMessageOrDetail);\n } else if (detail !== undefined) {\n // (field: string, message: string, detail: string)\n super(messageOrField, fieldMessageOrDetail!, HttpStatus.BAD_GATEWAY, detail);\n } else if (fieldMessageOrDetail) {\n // (field: string, message: string)\n super(messageOrField, fieldMessageOrDetail, HttpStatus.BAD_GATEWAY);\n } else {\n // (message: string)\n super(messageOrField, HttpStatus.BAD_GATEWAY);\n }\n }\n}\n"],"mappings":"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","names":["ForbiddenException","InternalServerErrorException","UnauthorizedException","import_common","import_config","import_core","import_jwt","import_common","RequestService","request","getTenantIdentifier","getHeader","key","value","headers","Array","isArray","getAccessToken","authHeader","authorization","type","token","split","getRefreshToken","cookies","sessionId","error","getAllHeaders","scope","Scope","REQUEST","RequestModule","providers","RequestService","exports","import_common","import_core","import_common","DATABASE_MODULE_OPTIONS","Symbol","PrimaryDatabaseService","logger","Logger","name","primaryDbClient","tenantConfigCache","Map","cacheTTL","options","connectionCacheTTL","onModuleInit","primaryDb","initializePrimaryDbClient","PrimaryDbClient","prismaClientConstructor","databaseUrl","buildPrimaryDbUrl","datasources","db","url","log","$connect","error","InternalServerErrorException","Error","host","port","username","password","database","schema","sslMode","params","URLSearchParams","set","queryString","toString","debug","maskPassword","replace","getTenantInfo","tenantIdentifier","cached","get","tenant","findFirst","where","OR","id","subdomain","status","include","databaseConfig","warn","info","type","dbType","schemaName","dbSchema","undefined","databaseName","dbName","databaseHost","dbHost","databasePort","dbPort","databaseUsername","dbUsername","decrypt","databasePassword","dbPassword","databaseSslMode","dbSslMode","connectionPoolSize","cacheInfo","setTimeout","delete","clearTenantCache","config","clearAllCaches","size","clear","prismaClient","encrypted","onModuleDestroy","$disconnect","VrittiAuthGuard","logger","Logger","name","reflector","configService","jwtService","primaryDatabase","requestService","canActivate","context","request","switchToHttp","getRequest","isPublic","getAllAndOverride","getHandler","getClass","debug","isOnboarding","accessToken","getAccessToken","warn","UnauthorizedException","decodedToken","decode","type","validatedToken","validateAccessToken","userId","user","id","refreshToken","getRefreshToken","validateRefreshToken","tenantIdentifier","getTenantIdentifier","tenantInfo","getTenantInfo","status","subdomain","error","token","decoded","verify","exp","expiryTime","currentTime","Date","now","timeRemaining","Math","floor","jwtError","expiredAt","message","jwtSecret","get","validateRefreshTokenWithSecret","secret","algorithms","scope","Scope","REQUEST","AuthConfigModule","forRootAsync","module","imports","ConfigModule","RequestModule","JwtModule","registerAsync","inject","ConfigService","useFactory","config","secret","get","signOptions","algorithm","providers","provide","APP_GUARD","useClass","VrittiAuthGuard","exports","import_common","import_core","import_common","import_common","TenantContextService","tenantInfo","setTenant","Error","getTenant","UnauthorizedException","hasTenant","clearTenant","getTenantIdSafe","id","getTenantSubdomainSafe","subdomain","scope","Scope","REQUEST","MessageTenantContextInterceptor","logger","Logger","name","tenantContext","intercept","context","next","contextType","getType","rpcContext","switchToRpc","payload","getData","tenant","debug","subdomain","setTenant","log","type","error","warn","handle","pipe","tap","cleanupContext","complete","hasTenant","getTenantIdSafe","clearTenant","scope","Scope","REQUEST","import_common","import_core","TenantContextInterceptor","logger","Logger","name","reflector","tenantContext","primaryDatabase","requestService","intercept","context","next","request","switchToHttp","getRequest","debug","method","url","isPublic","getAllAndOverride","getHandler","getClass","tenantIdentifier","getTenantIdentifier","handle","UnauthorizedException","log","tenantInfo","getTenantInfo","warn","status","subdomain","type","setTenant","tenant","error","scope","Scope","REQUEST","import_common","TenantDatabaseService","logger","Logger","name","clients","Map","clientLastUsed","cleanupInterval","options","tenantContext","startConnectionCleaner","prismaClient","getDbClient","tenant","getTenant","cacheKey","buildCacheKey","has","set","Date","now","debug","get","log","client","createDbClient","databaseUrl","buildTenantDbUrl","PrismaClient","prismaClientConstructor","datasources","db","url","$connect","subdomain","error","InternalServerErrorException","databaseHost","databasePort","databaseName","databaseUsername","databasePassword","databaseSslMode","Error","port","sslMode","connectionUrl","maskPassword","type","interval","connectionCacheTTL","setInterval","cleanupIdleConnections","maxIdle","cleaned","key","lastUsed","entries","$disconnect","then","catch","delete","getPoolStats","activeConnections","size","tenants","Array","from","keys","replace","onModuleDestroy","clearInterval","disconnectPromises","map","Promise","all","DatabaseModule","forServer","options","createDynamicModule","forMicroservice","mode","asyncProvider","provide","DATABASE_MODULE_OPTIONS","useFactory","inject","providers","TenantContextService","PrimaryDatabaseService","TenantDatabaseService","push","APP_INTERCEPTOR","useClass","TenantContextInterceptor","MessageTenantContextInterceptor","module","imports","RequestModule","exports","import_common","PrimaryBaseRepository","logger","modelGetter","prisma","database","prismaClient","model","getModel","Logger","name","debug","create","data","log","findById","id","findUnique","where","findOne","findMany","args","update","updateMany","delete","deleteMany","count","exists","import_common","TenantBaseRepository","logger","modelGetter","prisma","database","prismaClient","model","getModel","Logger","name","debug","create","data","log","findById","id","findUnique","where","findOne","findMany","args","update","updateMany","delete","deleteMany","count","exists","import_common","Tenant","createParamDecorator","data","ctx","request","switchToHttp","getRequest","tenantContext","app","get","TenantContextService","Error","getTenant","import_common","Onboarding","SetMetadata","import_common","Public","SetMetadata","import_common","import_common","import_core","CsrfGuard","logger","Logger","name","reflector","canActivate","context","request","switchToHttp","getRequest","reply","getResponse","safeMethods","includes","method","fastifyInstance","server","csrfProtection","error","ForbiddenException","Promise","resolve","reject","err","debug","url","warn","Error","message","errors","field","HttpModule","providers","CsrfGuard","exports","import_common","HttpExceptionFilter","logger","Logger","name","catch","exception","host","ctx","switchToHttp","reply","getResponse","request","getRequest","status","HttpStatus","INTERNAL_SERVER_ERROR","errors","message","HttpException","getStatus","exceptionResponse","responseObj","Array","isArray","parseValidationErrors","field","Error","error","stack","errorResponse","statusCode","timestamp","Date","toISOString","path","url","JSON","stringify","undefined","warn","send","messages","msg","push","property","constraints","constraintMessages","Object","values","constraintMsg","length","import_common","getHttpStatusTitle","status","enumKey","Object","entries","HttpStatus","find","key","value","isNaN","Number","split","map","word","charAt","toUpperCase","slice","toLowerCase","join","HttpExceptionFilter","logger","Logger","name","catch","exception","host","ctx","switchToHttp","response","getResponse","INTERNAL_SERVER_ERROR","errors","detail","HttpException","getStatus","exceptionResponse","responseObj","Array","isArray","message","msg","property","constraints","field","values","JSON","stringify","error","problemDetails","title","send","import_common","BaseFieldException","HttpException","statusOrMessageOrErrors","messageOrStatus","statusOrDetail","detail","errors","httpStatus","finalDetail","Array","isArray","HttpStatus","BAD_REQUEST","undefined","field","message","response","import_common","BadRequestException","BaseFieldException","messageOrField","fieldMessageOrDetail","detail","Array","isArray","HttpStatus","BAD_REQUEST","undefined","import_common","UnauthorizedException","BaseFieldException","messageOrField","fieldMessageOrDetail","detail","Array","isArray","HttpStatus","UNAUTHORIZED","undefined","import_common","ForbiddenException","BaseFieldException","messageOrField","fieldMessageOrDetail","detail","Array","isArray","HttpStatus","FORBIDDEN","undefined","import_common","NotFoundException","BaseFieldException","messageOrField","fieldMessageOrDetail","detail","Array","isArray","HttpStatus","NOT_FOUND","undefined","import_common","ConflictException","BaseFieldException","messageOrField","fieldMessageOrDetail","detail","Array","isArray","HttpStatus","CONFLICT","undefined","import_common","InternalServerErrorException","BaseFieldException","messageOrField","fieldMessageOrDetail","detail","Array","isArray","HttpStatus","INTERNAL_SERVER_ERROR","undefined","import_common","ValidationException","BaseFieldException","errors","detail","HttpStatus","BAD_REQUEST","import_common","UnprocessableEntityException","BaseFieldException","messageOrField","fieldMessageOrDetail","detail","Array","isArray","HttpStatus","UNPROCESSABLE_ENTITY","undefined","import_common","TooManyRequestsException","BaseFieldException","messageOrField","fieldMessageOrDetail","detail","Array","isArray","HttpStatus","TOO_MANY_REQUESTS","undefined","import_common","ServiceUnavailableException","BaseFieldException","messageOrField","fieldMessageOrDetail","detail","Array","isArray","HttpStatus","SERVICE_UNAVAILABLE","undefined","import_common","MethodNotAllowedException","BaseFieldException","messageOrField","fieldMessageOrDetail","detail","Array","isArray","HttpStatus","METHOD_NOT_ALLOWED","undefined","import_common","GoneException","BaseFieldException","messageOrField","fieldMessageOrDetail","detail","Array","isArray","HttpStatus","GONE","undefined","import_common","NotAcceptableException","BaseFieldException","messageOrField","fieldMessageOrDetail","detail","Array","isArray","HttpStatus","NOT_ACCEPTABLE","undefined","import_common","RequestTimeoutException","BaseFieldException","messageOrField","fieldMessageOrDetail","detail","Array","isArray","HttpStatus","REQUEST_TIMEOUT","undefined","import_common","PayloadTooLargeException","BaseFieldException","messageOrField","fieldMessageOrDetail","detail","Array","isArray","HttpStatus","PAYLOAD_TOO_LARGE","undefined","import_common","UnsupportedMediaTypeException","BaseFieldException","messageOrField","fieldMessageOrDetail","detail","Array","isArray","HttpStatus","UNSUPPORTED_MEDIA_TYPE","undefined","import_common","NotImplementedException","BaseFieldException","messageOrField","fieldMessageOrDetail","detail","Array","isArray","HttpStatus","NOT_IMPLEMENTED","undefined","import_common","BadGatewayException","BaseFieldException","messageOrField","fieldMessageOrDetail","detail","Array","isArray","HttpStatus","BAD_GATEWAY","undefined"]}
|