@vritti/api-sdk 0.3.15 → 0.4.0
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/auth.cjs +178 -152
- package/dist/auth.cjs.map +1 -1
- package/dist/auth.d.cts +25 -43
- package/dist/auth.d.ts +25 -43
- package/dist/auth.js +101 -72
- package/dist/auth.js.map +1 -1
- package/dist/data-table.cjs +4 -5
- package/dist/data-table.cjs.map +1 -1
- package/dist/data-table.js +4 -5
- package/dist/data-table.js.map +1 -1
- package/dist/email.cjs +397 -422
- package/dist/email.cjs.map +1 -1
- package/dist/email.d.cts +15 -0
- package/dist/email.d.ts +15 -0
- package/dist/email.js +387 -422
- package/dist/email.js.map +1 -1
- package/dist/index.d.cts +15 -9
- package/dist/index.d.ts +15 -9
- package/dist/nats.cjs +30 -36
- package/dist/nats.cjs.map +1 -1
- package/dist/nats.d.cts +20 -9
- package/dist/nats.d.ts +20 -9
- package/dist/nats.js +30 -36
- package/dist/nats.js.map +1 -1
- package/dist/root.cjs +15 -4
- package/dist/root.cjs.map +1 -1
- package/dist/root.js +15 -4
- package/dist/root.js.map +1 -1
- package/package.json +1 -1
package/dist/email.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/email/email.module.ts","../src/email/email.service.ts"],"sourcesContent":["import { Global, Module } from '@nestjs/common';\nimport { ConfigModule } from '@nestjs/config';\nimport { EmailService } from './email.service';\n\n@Global()\n@Module({\n imports: [ConfigModule],\n providers: [EmailService],\n exports: [EmailService],\n})\nexport class EmailModule {}\n","import { BrevoClient, BrevoError, BrevoTimeoutError } from '@getbrevo/brevo';\nimport { Injectable, Logger } from '@nestjs/common';\nimport { ConfigService } from '@nestjs/config';\n\n@Injectable()\nexport class EmailService {\n private readonly logger = new Logger(EmailService.name);\n private readonly brevoClient: BrevoClient;\n private readonly senderEmail: string;\n private readonly senderName: string;\n\n constructor(private readonly configService: ConfigService) {\n const apiKey = this.configService.get<string>('BREVO_API_KEY');\n\n if (!apiKey) {\n this.logger.error('BREVO_API_KEY is not configured. Email sending will fail.');\n throw new Error('Email service configuration error: Missing BREVO_API_KEY');\n }\n\n // Initialize Brevo client with built-in retry support\n this.brevoClient = new BrevoClient({ apiKey, maxRetries: 3 });\n\n // Get sender configuration\n const senderEmail = this.configService.get<string>('SENDER_EMAIL');\n const senderName = this.configService.get<string>('SENDER_NAME');\n\n if (!senderEmail || !senderName) {\n this.logger.error('Sender email or name is not configured.');\n throw new Error('Email service configuration error: Missing SENDER_EMAIL or SENDER_NAME');\n }\n\n this.senderEmail = senderEmail;\n this.senderName = senderName;\n\n this.logger.log('Brevo email service initialized successfully');\n }\n\n // Sends an email verification OTP to the given recipient\n async sendVerificationEmail(email: string, otp: string, expiresAt: Date, displayName?: string): Promise<void> {\n const name = displayName || 'there';\n const expiryMinutes = Math.ceil((expiresAt.getTime() - Date.now()) / 60_000);\n const subject = 'Verify Your Email - Vritti AI Cloud';\n\n const htmlContent = `\n <!DOCTYPE html>\n <html>\n <head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n </head>\n <body style=\"margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; background-color: #f5f5f5;\">\n <table role=\"presentation\" style=\"width: 100%; border-collapse: collapse;\">\n <tr>\n <td style=\"padding: 40px 20px;\">\n <table role=\"presentation\" style=\"max-width: 600px; margin: 0 auto; background-color: #ffffff; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);\">\n <!-- Header -->\n <tr>\n <td style=\"padding: 40px 40px 20px; text-align: center; border-bottom: 1px solid #e0e0e0;\">\n <h1 style=\"margin: 0; color: #1a1a1a; font-size: 24px; font-weight: 600;\">Email Verification</h1>\n </td>\n </tr>\n\n <!-- Content -->\n <tr>\n <td style=\"padding: 40px;\">\n <p style=\"margin: 0 0 20px; color: #333333; font-size: 16px; line-height: 1.6;\">\n Hello <strong>${name}</strong>,\n </p>\n <p style=\"margin: 0 0 30px; color: #333333; font-size: 16px; line-height: 1.6;\">\n Thank you for signing up with Vritti AI Cloud. Please use the following verification code to complete your registration:\n </p>\n\n <!-- OTP Box -->\n <div style=\"background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 30px; border-radius: 8px; text-align: center; margin: 30px 0;\">\n <div style=\"color: #ffffff; font-size: 36px; font-weight: bold; letter-spacing: 10px; font-family: 'Courier New', monospace;\">\n ${otp}\n </div>\n </div>\n\n <p style=\"margin: 30px 0 20px; color: #666666; font-size: 14px; line-height: 1.6;\">\n <strong>Important:</strong> This code will expire in <strong>${expiryMinutes} minute${expiryMinutes === 1 ? '' : 's'}</strong>.\n </p>\n <p style=\"margin: 0; color: #666666; font-size: 14px; line-height: 1.6;\">\n If you didn't request this verification, please ignore this email.\n </p>\n </td>\n </tr>\n\n <!-- Footer -->\n <tr>\n <td style=\"padding: 30px 40px; border-top: 1px solid #e0e0e0; text-align: center;\">\n <p style=\"margin: 0; color: #999999; font-size: 12px; line-height: 1.5;\">\n Vritti AI Cloud - Cloud Management Platform\n </p>\n <p style=\"margin: 8px 0 0; color: #999999; font-size: 12px; line-height: 1.5;\">\n This is an automated message, please do not reply.\n </p>\n </td>\n </tr>\n </table>\n </td>\n </tr>\n </table>\n </body>\n </html>\n `;\n\n const textContent = `\nHello ${name},\n\nThank you for signing up with Vritti AI Cloud. Please use the following verification code to complete your registration:\n\nVerification Code: ${otp}\n\nThis code will expire in ${expiryMinutes} minute${expiryMinutes === 1 ? '' : 's'}.\n\nIf you didn't request this verification, please ignore this email.\n\n---\nVritti AI Cloud - Cloud Management Platform\nThis is an automated message, please do not reply.\n `.trim();\n\n await this.sendEmail({\n to: [{ email, name }],\n subject,\n htmlContent,\n textContent,\n });\n\n this.logger.log(`Verification email sent to ${email}`);\n }\n\n // Sends a password reset OTP to the given recipient\n async sendPasswordResetEmail(email: string, otp: string, expiresAt: Date, displayName?: string): Promise<void> {\n const name = displayName || 'there';\n const expiryMinutes = Math.ceil((expiresAt.getTime() - Date.now()) / 60_000);\n const subject = 'Reset Your Password - Vritti AI Cloud';\n\n const htmlContent = `\n <!DOCTYPE html>\n <html>\n <head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n </head>\n <body style=\"margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; background-color: #f5f5f5;\">\n <table role=\"presentation\" style=\"width: 100%; border-collapse: collapse;\">\n <tr>\n <td style=\"padding: 40px 20px;\">\n <table role=\"presentation\" style=\"max-width: 600px; margin: 0 auto; background-color: #ffffff; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);\">\n <!-- Header -->\n <tr>\n <td style=\"padding: 40px 40px 20px; text-align: center; border-bottom: 1px solid #e0e0e0;\">\n <h1 style=\"margin: 0; color: #1a1a1a; font-size: 24px; font-weight: 600;\">Password Reset</h1>\n </td>\n </tr>\n\n <!-- Content -->\n <tr>\n <td style=\"padding: 40px;\">\n <p style=\"margin: 0 0 20px; color: #333333; font-size: 16px; line-height: 1.6;\">\n Hello <strong>${name}</strong>,\n </p>\n <p style=\"margin: 0 0 30px; color: #333333; font-size: 16px; line-height: 1.6;\">\n We received a request to reset your password. Use the following code to complete the process:\n </p>\n\n <!-- OTP Box -->\n <div style=\"background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); padding: 30px; border-radius: 8px; text-align: center; margin: 30px 0;\">\n <div style=\"color: #ffffff; font-size: 36px; font-weight: bold; letter-spacing: 10px; font-family: 'Courier New', monospace;\">\n ${otp}\n </div>\n </div>\n\n <p style=\"margin: 30px 0 20px; color: #666666; font-size: 14px; line-height: 1.6;\">\n <strong>Important:</strong> This code will expire in <strong>${expiryMinutes} minute${expiryMinutes === 1 ? '' : 's'}</strong>.\n </p>\n <p style=\"margin: 0 0 20px; color: #666666; font-size: 14px; line-height: 1.6;\">\n If you didn't request a password reset, please ignore this email and your password will remain unchanged.\n </p>\n <div style=\"background-color: #fff3cd; border-left: 4px solid #ffc107; padding: 15px; margin-top: 20px; border-radius: 4px;\">\n <p style=\"margin: 0; color: #856404; font-size: 13px; line-height: 1.5;\">\n <strong>Security Tip:</strong> Never share this code with anyone. Vritti will never ask for your verification code.\n </p>\n </div>\n </td>\n </tr>\n\n <!-- Footer -->\n <tr>\n <td style=\"padding: 30px 40px; border-top: 1px solid #e0e0e0; text-align: center;\">\n <p style=\"margin: 0; color: #999999; font-size: 12px; line-height: 1.5;\">\n Vritti AI Cloud - Cloud Management Platform\n </p>\n <p style=\"margin: 8px 0 0; color: #999999; font-size: 12px; line-height: 1.5;\">\n This is an automated message, please do not reply.\n </p>\n </td>\n </tr>\n </table>\n </td>\n </tr>\n </table>\n </body>\n </html>\n `;\n\n const textContent = `\nHello ${name},\n\nWe received a request to reset your password. Use the following code to complete the process:\n\nReset Code: ${otp}\n\nThis code will expire in ${expiryMinutes} minute${expiryMinutes === 1 ? '' : 's'}.\n\nIf you didn't request a password reset, please ignore this email and your password will remain unchanged.\n\nSECURITY TIP: Never share this code with anyone. Vritti will never ask for your verification code.\n\n---\nVritti AI Cloud - Cloud Management Platform\nThis is an automated message, please do not reply.\n `.trim();\n\n await this.sendEmail({\n to: [{ email, name }],\n subject,\n htmlContent,\n textContent,\n });\n\n this.logger.log(`Password reset email sent to ${email}`);\n }\n\n // Sends an email change notification to the old address with a revert link\n async sendEmailChangeNotification(\n oldEmail: string,\n newEmail: string,\n revertToken: string,\n revertExpiresAt: Date,\n displayName?: string,\n ): Promise<void> {\n const name = displayName || 'there';\n const subject = 'Your Email Address Has Been Changed - Vritti AI Cloud';\n\n // Calculate hours until expiry\n const hoursUntilExpiry = Math.floor((revertExpiresAt.getTime() - Date.now()) / (1000 * 60 * 60));\n\n // TODO: Replace with actual frontend URL from config\n const revertLink = `https://local.vrittiai.com:3012/settings/profile/revert-email?token=${revertToken}`;\n\n const htmlContent = `\n <!DOCTYPE html>\n <html>\n <head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n </head>\n <body style=\"margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; background-color: #f5f5f5;\">\n <table role=\"presentation\" style=\"width: 100%; border-collapse: collapse;\">\n <tr>\n <td style=\"padding: 40px 20px;\">\n <table role=\"presentation\" style=\"max-width: 600px; margin: 0 auto; background-color: #ffffff; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);\">\n <!-- Header -->\n <tr>\n <td style=\"padding: 40px 40px 20px; text-align: center; border-bottom: 1px solid #e0e0e0;\">\n <h1 style=\"margin: 0; color: #1a1a1a; font-size: 24px; font-weight: 600;\">Email Address Changed</h1>\n </td>\n </tr>\n\n <!-- Content -->\n <tr>\n <td style=\"padding: 40px;\">\n <p style=\"margin: 0 0 20px; color: #333333; font-size: 16px; line-height: 1.6;\">\n Hello <strong>${name}</strong>,\n </p>\n <p style=\"margin: 0 0 30px; color: #333333; font-size: 16px; line-height: 1.6;\">\n We're writing to inform you that your Vritti AI Cloud email address has been successfully changed.\n </p>\n\n <div style=\"background-color: #f8f9fa; padding: 20px; border-radius: 8px; margin: 30px 0;\">\n <p style=\"margin: 0 0 10px; color: #666666; font-size: 14px;\">\n <strong>Previous Email:</strong>\n </p>\n <p style=\"margin: 0 0 20px; color: #333333; font-size: 16px; font-family: monospace;\">\n ${oldEmail}\n </p>\n <p style=\"margin: 0 0 10px; color: #666666; font-size: 14px;\">\n <strong>New Email:</strong>\n </p>\n <p style=\"margin: 0; color: #333333; font-size: 16px; font-family: monospace;\">\n ${newEmail}\n </p>\n </div>\n\n <div style=\"background-color: #fff3cd; border-left: 4px solid #ffc107; padding: 20px; margin: 30px 0; border-radius: 4px;\">\n <p style=\"margin: 0 0 15px; color: #856404; font-size: 14px; line-height: 1.6;\">\n <strong>Didn't make this change?</strong>\n </p>\n <p style=\"margin: 0 0 20px; color: #856404; font-size: 14px; line-height: 1.6;\">\n If you did not authorize this change, you can revert it within the next <strong>${hoursUntilExpiry} hours</strong> by clicking the button below:\n </p>\n <div style=\"text-align: center;\">\n <a href=\"${revertLink}\" style=\"display: inline-block; padding: 12px 30px; background-color: #dc3545; color: #ffffff; text-decoration: none; border-radius: 6px; font-weight: 600; font-size: 14px;\">\n Revert Email Change\n </a>\n </div>\n </div>\n\n <p style=\"margin: 30px 0 0; color: #666666; font-size: 14px; line-height: 1.6;\">\n If you made this change, you can safely ignore this email.\n </p>\n </td>\n </tr>\n\n <!-- Footer -->\n <tr>\n <td style=\"padding: 30px 40px; border-top: 1px solid #e0e0e0; text-align: center;\">\n <p style=\"margin: 0; color: #999999; font-size: 12px; line-height: 1.5;\">\n Vritti AI Cloud - Cloud Management Platform\n </p>\n <p style=\"margin: 8px 0 0; color: #999999; font-size: 12px; line-height: 1.5;\">\n This is an automated message, please do not reply.\n </p>\n </td>\n </tr>\n </table>\n </td>\n </tr>\n </table>\n </body>\n </html>\n `;\n\n const textContent = `\nHello ${name},\n\nWe're writing to inform you that your Vritti AI Cloud email address has been successfully changed.\n\nPrevious Email: ${oldEmail}\nNew Email: ${newEmail}\n\nDIDN'T MAKE THIS CHANGE?\n\nIf you did not authorize this change, you can revert it within the next ${hoursUntilExpiry} hours by visiting:\n${revertLink}\n\nIf you made this change, you can safely ignore this email.\n\n---\nVritti AI Cloud - Cloud Management Platform\nThis is an automated message, please do not reply.\n `.trim();\n\n await this.sendEmail({\n to: [{ email: oldEmail, name }],\n subject,\n htmlContent,\n textContent,\n });\n\n this.logger.log(`Email change notification sent to ${oldEmail}`);\n }\n\n // Sends a confirmation to the restored email address after a revert\n async sendEmailRevertConfirmation(email: string, displayName?: string): Promise<void> {\n const name = displayName || 'there';\n const subject = 'Email Address Change Reverted - Vritti AI Cloud';\n\n const htmlContent = `\n <!DOCTYPE html>\n <html>\n <head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n </head>\n <body style=\"margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; background-color: #f5f5f5;\">\n <table role=\"presentation\" style=\"width: 100%; border-collapse: collapse;\">\n <tr>\n <td style=\"padding: 40px 20px;\">\n <table role=\"presentation\" style=\"max-width: 600px; margin: 0 auto; background-color: #ffffff; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);\">\n <!-- Header -->\n <tr>\n <td style=\"padding: 40px 40px 20px; text-align: center; border-bottom: 1px solid #e0e0e0;\">\n <h1 style=\"margin: 0; color: #1a1a1a; font-size: 24px; font-weight: 600;\">Email Change Reverted</h1>\n </td>\n </tr>\n\n <!-- Content -->\n <tr>\n <td style=\"padding: 40px;\">\n <p style=\"margin: 0 0 20px; color: #333333; font-size: 16px; line-height: 1.6;\">\n Hello <strong>${name}</strong>,\n </p>\n <p style=\"margin: 0 0 30px; color: #333333; font-size: 16px; line-height: 1.6;\">\n Your recent email address change has been successfully reverted. Your email is now:\n </p>\n\n <div style=\"background-color: #d4edda; padding: 20px; border-radius: 8px; margin: 30px 0; text-align: center;\">\n <p style=\"margin: 0; color: #155724; font-size: 18px; font-weight: 600; font-family: monospace;\">\n ${email}\n </p>\n </div>\n\n <p style=\"margin: 30px 0 20px; color: #666666; font-size: 14px; line-height: 1.6;\">\n If you did not request this revert, please contact our support team immediately.\n </p>\n </td>\n </tr>\n\n <!-- Footer -->\n <tr>\n <td style=\"padding: 30px 40px; border-top: 1px solid #e0e0e0; text-align: center;\">\n <p style=\"margin: 0; color: #999999; font-size: 12px; line-height: 1.5;\">\n Vritti AI Cloud - Cloud Management Platform\n </p>\n <p style=\"margin: 8px 0 0; color: #999999; font-size: 12px; line-height: 1.5;\">\n This is an automated message, please do not reply.\n </p>\n </td>\n </tr>\n </table>\n </td>\n </tr>\n </table>\n </body>\n </html>\n `;\n\n const textContent = `\nHello ${name},\n\nYour recent email address change has been successfully reverted. Your email is now:\n\n${email}\n\nIf you did not request this revert, please contact our support team immediately.\n\n---\nVritti AI Cloud - Cloud Management Platform\nThis is an automated message, please do not reply.\n `.trim();\n\n await this.sendEmail({\n to: [{ email, name }],\n subject,\n htmlContent,\n textContent,\n });\n\n this.logger.log(`Email revert confirmation sent to ${email}`);\n }\n\n // Sends an invite email to a new portal user with their set-password link\n async sendInviteEmail(params: { to: string; name: string; inviteUrl: string }): Promise<void> {\n const { to, name, inviteUrl } = params;\n const subject = 'You have been invited to Vritti AI';\n\n const htmlContent = `\n <!DOCTYPE html>\n <html>\n <head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n </head>\n <body style=\"margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; background-color: #f5f5f5;\">\n <table role=\"presentation\" style=\"width: 100%; border-collapse: collapse;\">\n <tr>\n <td style=\"padding: 40px 20px;\">\n <table role=\"presentation\" style=\"max-width: 600px; margin: 0 auto; background-color: #ffffff; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);\">\n <!-- Header -->\n <tr>\n <td style=\"padding: 40px 40px 20px; text-align: center; border-bottom: 1px solid #e0e0e0;\">\n <h1 style=\"margin: 0; color: #1a1a1a; font-size: 24px; font-weight: 600;\">You're Invited</h1>\n </td>\n </tr>\n\n <!-- Content -->\n <tr>\n <td style=\"padding: 40px;\">\n <p style=\"margin: 0 0 20px; color: #333333; font-size: 16px; line-height: 1.6;\">\n Hello <strong>${name}</strong>,\n </p>\n <p style=\"margin: 0 0 30px; color: #333333; font-size: 16px; line-height: 1.6;\">\n You have been invited to join Vritti AI. Click the button below to set your password and get started.\n </p>\n\n <div style=\"text-align: center; margin: 30px 0;\">\n <a href=\"${inviteUrl}\" style=\"display: inline-block; padding: 14px 32px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: #ffffff; text-decoration: none; border-radius: 6px; font-weight: 600; font-size: 16px;\">\n Set Your Password\n </a>\n </div>\n\n <p style=\"margin: 30px 0 0; color: #666666; font-size: 14px; line-height: 1.6;\">\n If you did not expect this invitation, you can safely ignore this email.\n </p>\n </td>\n </tr>\n\n <!-- Footer -->\n <tr>\n <td style=\"padding: 30px 40px; border-top: 1px solid #e0e0e0; text-align: center;\">\n <p style=\"margin: 0; color: #999999; font-size: 12px; line-height: 1.5;\">\n Vritti AI Cloud - Cloud Management Platform\n </p>\n <p style=\"margin: 8px 0 0; color: #999999; font-size: 12px; line-height: 1.5;\">\n This is an automated message, please do not reply.\n </p>\n </td>\n </tr>\n </table>\n </td>\n </tr>\n </table>\n </body>\n </html>\n `;\n\n const textContent = `\nHello ${name},\n\nYou have been invited to join Vritti AI. Visit the link below to set your password and get started:\n\n${inviteUrl}\n\nIf you did not expect this invitation, you can safely ignore this email.\n\n---\nVritti AI Cloud - Cloud Management Platform\nThis is an automated message, please do not reply.\n `.trim();\n\n await this.sendEmail({\n to: [{ email: to, name }],\n subject,\n htmlContent,\n textContent,\n });\n\n this.logger.log(`Invite email sent to ${to}`);\n }\n\n // Sends a transactional email with custom subject, HTML, and text content\n async sendTransactionalEmail(params: {\n to: { email: string; name?: string };\n subject: string;\n htmlContent: string;\n textContent: string;\n }): Promise<void> {\n await this.sendEmail({\n to: [params.to],\n subject: params.subject,\n htmlContent: params.htmlContent,\n textContent: params.textContent,\n });\n this.logger.log(`Transactional email sent to ${params.to.email}`);\n }\n\n // Verifies Brevo API connectivity — a 400 response means the API is reachable\n async verifyConnection(): Promise<boolean> {\n try {\n await this.brevoClient.transactionalEmails.sendTransacEmail({\n sender: { email: this.senderEmail, name: this.senderName },\n to: [{ email: this.senderEmail }],\n subject: 'Connection Test',\n htmlContent: '<p>Test</p>',\n });\n return true;\n } catch (err) {\n // A 400 error means the API is reachable but params are incomplete — still a successful connection test\n if (err instanceof BrevoError && err.statusCode === 400) {\n return true;\n }\n this.logger.error('Brevo connection verification failed:', err);\n return false;\n }\n }\n\n // Sends a transactional email via Brevo — retries handled internally by BrevoClient\n private async sendEmail(emailData: {\n to: Array<{ email: string; name?: string }>;\n subject: string;\n htmlContent: string;\n textContent: string;\n }): Promise<void> {\n try {\n const result = await this.brevoClient.transactionalEmails.sendTransacEmail({\n sender: { email: this.senderEmail, name: this.senderName },\n to: emailData.to,\n subject: emailData.subject,\n htmlContent: emailData.htmlContent,\n textContent: emailData.textContent,\n });\n this.logger.debug(`Email sent successfully. Message ID: ${result.messageId}`);\n } catch (err) {\n if (err instanceof BrevoTimeoutError) {\n this.logger.error('Brevo request timed out after retries.');\n throw new Error('Email sending failed: timeout');\n }\n if (err instanceof BrevoError) {\n if (err.statusCode === 429) {\n this.logger.error('Brevo rate limit exceeded after retries.');\n throw new Error('Email sending failed: rate limit exceeded');\n }\n if (err.statusCode === 401) {\n this.logger.error('Brevo authentication failed. Check your API key.');\n throw new Error('Email service authentication failed');\n }\n if (err.statusCode === 400) {\n this.logger.error('Bad request to Brevo API:', err.message);\n throw new Error(`Invalid email parameters: ${err.message}`);\n }\n this.logger.error(`Brevo API error ${err.statusCode}:`, err.message);\n throw new Error(`Email sending failed: ${err.message}`);\n }\n throw err;\n }\n }\n}\n"],"mappings":"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","names":["Global","Module","ConfigModule","BrevoClient","BrevoError","BrevoTimeoutError","Injectable","Logger","ConfigService","EmailService","logger","Logger","name","brevoClient","senderEmail","senderName","configService","apiKey","get","error","Error","BrevoClient","maxRetries","log","sendVerificationEmail","email","otp","expiresAt","displayName","expiryMinutes","Math","ceil","getTime","Date","now","subject","htmlContent","textContent","trim","sendEmail","to","sendPasswordResetEmail","sendEmailChangeNotification","oldEmail","newEmail","revertToken","revertExpiresAt","hoursUntilExpiry","floor","revertLink","sendEmailRevertConfirmation","sendInviteEmail","params","inviteUrl","sendTransactionalEmail","verifyConnection","transactionalEmails","sendTransacEmail","sender","err","BrevoError","statusCode","emailData","result","debug","messageId","BrevoTimeoutError","message","EmailModule","imports","ConfigModule","providers","EmailService","exports"]}
|
|
1
|
+
{"version":3,"sources":["../src/email/email.module.ts","../src/email/email.service.ts","../src/pluralize.ts"],"sourcesContent":["import { Global, Module } from '@nestjs/common';\nimport { ConfigModule } from '@nestjs/config';\nimport { EmailService } from './email.service';\n\n@Global()\n@Module({\n imports: [ConfigModule],\n providers: [EmailService],\n exports: [EmailService],\n})\nexport class EmailModule {}\n","import { BrevoClient, BrevoError, BrevoTimeoutError } from '@getbrevo/brevo';\nimport { Injectable, Logger } from '@nestjs/common';\nimport { ConfigService } from '@nestjs/config';\nimport { pluralize } from '../pluralize';\n\n// Email-safe font stacks. Web fonts only land in clients that allow them; the fallbacks carry the rest.\nconst EMAIL_SANS = \"'IBM Plex Sans',Helvetica,Arial,sans-serif\";\nconst EMAIL_MONO = \"'JetBrains Mono','Courier New',Courier,monospace\";\n\n// Brand marks inlined from apps/cloud-web/src/assets/vritti_cloud_{light,dark}.svg, sized for the email\n// header (viewBox 407x67 -> 170x28). Inline SVG renders in Apple Mail and iOS Mail; Gmail, Outlook and\n// Yahoo strip <svg> from email bodies, so configure EMAIL_LOGO_LIGHT_URL with a hosted PNG to reach those.\nconst VRITTI_CLOUD_LOGO_LIGHT = `<svg width=\"170\" height=\"28\" viewBox=\"0 0 407 67\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M78.9948 52L66.7748 37.44H72.2868L84.5588 51.948V52H78.9948ZM47.3788 52V14.612H78.5788C79.7228 14.612 80.7628 14.9067 81.6988 15.496C82.6695 16.0507 83.4321 16.796 83.9868 17.732C84.5415 18.668 84.8188 19.708 84.8188 20.852V31.824C84.8188 32.968 84.5415 34.008 83.9868 34.944C83.4321 35.88 82.6695 36.6427 81.6988 37.232C80.7628 37.7867 79.7228 38.064 78.5788 38.064H51.5908V52H47.3788ZM53.6188 33.8H78.5788C79.1335 33.8 79.6015 33.6093 79.9828 33.228C80.3988 32.8467 80.6068 32.3787 80.6068 31.824V20.852C80.6068 20.2973 80.3988 19.8293 79.9828 19.448C79.6015 19.032 79.1335 18.824 78.5788 18.824H53.6188C53.0641 18.824 52.5788 19.032 52.1628 19.448C51.7815 19.8293 51.5908 20.2973 51.5908 20.852V31.824C51.5908 32.3787 51.7815 32.8467 52.1628 33.228C52.5788 33.6093 53.0641 33.8 53.6188 33.8ZM90.809 52V14.56H95.073V52H90.809ZM116.178 52V18.772H99.538V14.56H136.978V18.772H120.39V52H116.178ZM155.635 52V18.772H138.995V14.56H176.435V18.772H159.847V52H155.635ZM180.844 52V14.56H185.108V52H180.844Z\" fill=\"#0066CC\"/><path d=\"M218.027 50.336C215.479 49.244 213.295 47.736 211.371 45.864C209.499 43.94 207.991 41.756 206.899 39.208C205.807 36.66 205.235 34.008 205.235 31.2C205.235 28.34 205.807 25.636 206.899 23.088C207.991 20.592 209.499 18.408 211.371 16.536C213.295 14.612 215.479 13.156 218.027 12.064C220.523 10.972 223.227 10.4 226.087 10.4C228.947 10.4 231.651 10.92 234.147 12.012C236.643 13.104 238.879 14.612 240.803 16.536L235.863 21.372C233.107 18.668 229.831 17.368 226.087 17.368C224.163 17.368 222.343 17.732 220.679 18.408C218.963 19.136 217.507 20.124 216.259 21.424C215.011 22.672 214.023 24.128 213.295 25.792C212.567 27.508 212.203 29.276 212.203 31.2C212.203 33.124 212.567 34.892 213.295 36.608C214.023 38.272 215.011 39.728 216.311 40.976C217.559 42.276 219.015 43.264 220.679 43.992C222.395 44.668 224.163 45.032 226.087 45.032L234.147 50.388C231.651 51.48 228.947 52 226.087 52C223.227 52 220.523 51.428 218.027 50.336ZM252.177 45.032H272.977V52H245.261L252.177 45.032ZM245.261 10.4H252.177V11.544V35.62L245.261 42.588V11.544V10.4ZM295.636 10.4C301.408 10.4 306.244 12.48 310.3 16.536C314.408 20.592 316.488 25.48 316.488 31.2C316.488 36.972 314.408 41.86 310.3 45.864C306.244 49.92 301.408 52 295.636 52C289.864 52 285.028 49.92 280.972 45.864C276.916 41.808 274.836 36.972 274.836 31.2C274.836 25.48 276.916 20.592 280.972 16.536C284.976 12.48 289.864 10.4 295.636 10.4ZM295.636 17.368C291.84 17.368 288.564 18.668 285.808 21.424C283.104 24.128 281.804 27.404 281.804 31.2C281.804 34.996 283.104 38.324 285.808 41.028C288.512 43.732 291.84 45.032 295.636 45.032C299.432 45.032 302.708 43.732 305.412 41.028C308.168 38.272 309.468 34.996 309.468 31.2C309.468 27.404 308.116 24.128 305.412 21.424C302.708 18.72 299.432 17.368 295.636 17.368ZM352.723 34.684H359.639C359.639 39.468 357.975 43.576 354.543 46.956C351.163 50.336 347.003 52 342.271 52C337.539 52 333.431 50.336 330.051 46.956C326.671 43.576 325.007 39.416 325.007 34.684V10.4H331.923V34.684C331.923 37.544 332.911 39.988 334.887 41.964C336.915 44.044 339.411 45.032 342.271 45.032C345.131 45.032 347.575 43.992 349.603 41.964C351.631 39.936 352.723 37.492 352.723 34.684ZM396.621 23.192C397.713 25.688 398.233 28.34 398.233 31.2C398.233 34.06 397.713 36.712 396.621 39.208C395.477 41.756 394.021 43.94 392.149 45.864C390.277 47.736 388.041 49.244 385.493 50.336C382.997 51.428 380.293 52 377.433 52H363.601L370.517 45.032H377.433C379.357 45.032 381.177 44.668 382.841 43.992C384.505 43.264 386.013 42.276 387.261 40.976C388.509 39.676 389.497 38.22 390.225 36.556C390.953 34.84 391.317 33.072 391.317 31.2C391.317 29.328 390.953 27.56 390.225 25.844C389.497 24.18 388.509 22.724 387.209 21.424C385.961 20.176 384.453 19.136 382.789 18.408C381.125 17.732 379.305 17.368 377.433 17.368H370.517L363.601 10.4H377.433C380.293 10.4 382.945 10.972 385.493 12.064C387.989 13.156 390.225 14.664 392.097 16.536C394.021 18.46 395.477 20.644 396.621 23.192Z\" fill=\"url(#paint0_linear_56_379)\"/><path d=\"M7.99999 14H1.30874C0.614275 14.0193 0 14 0 14L18.8085 51.5C19.7652 52.3585 19.9594 52.3219 19.8087 51.5C19.3792 51.0014 19.4775 50.4593 20.3087 49C21.1421 46.2568 21.2304 43.8491 19.3087 41.5C18.2179 39.4696 18.9309 37.1826 20.8087 34L16.8087 26.5L14.8087 22.5L12.3087 18L11 15C10.5113 14.44 10.1886 14.2085 9.5 14H8.49999H7.99999Z\" fill=\"url(#paint1_linear_56_379)\"/><path d=\"M35.0507 14C37.2595 14.0004 39.0507 15.7911 39.0507 18C39.0507 20.2089 37.2595 21.9996 35.0507 22C34.4331 21.9999 33.8472 21.8594 33.3251 21.6094C33.1714 21.6936 33.0228 21.829 32.8553 22.0293L24.3583 35.0234C24.2882 35.1904 24.2274 35.3388 24.1805 35.4658C24.1331 35.5943 24.1019 35.6986 24.0927 35.7783C24.0834 35.8587 24.099 35.9018 24.1229 35.9238C24.1488 35.9469 24.2031 35.9626 24.3085 35.9502C26.1439 35.5539 27.4169 35.1177 28.4637 34.5029C29.5109 33.8878 30.3371 33.0905 31.2772 31.9678L33.3827 28.5957C33.1697 28.1068 33.0507 27.5673 33.0507 27C33.0507 24.791 34.8417 23.0002 37.0507 23C39.2597 23.0002 41.0507 24.791 41.0507 27C41.0507 29.0504 39.5075 30.7394 37.5194 30.9717C37.459 30.9962 37.397 31.0224 37.3339 31.0469L37.3173 31.0527L37.3007 31.0479C37.2448 31.0305 37.1894 31.0136 37.1356 30.9971C37.1076 30.9977 37.0789 31 37.0507 31C36.4652 30.9999 35.9092 30.8725 35.4081 30.6465C35.0278 30.6575 34.6938 30.7719 34.3505 31.0352L21.3573 51.5264L21.3534 51.5342L21.3466 51.5391C21.2336 51.6277 21.1425 51.6842 21.0507 51.6836C20.9809 51.6826 20.9167 51.6494 20.8505 51.5967L20.7821 51.5371C20.6425 51.4109 20.5854 51.2341 20.5887 51.0195C20.5921 50.8061 20.6543 50.5492 20.7577 50.2539C20.9644 49.664 21.3412 48.8997 21.7675 47.9834C22.0915 46.9083 22.1753 46.1149 22.0751 45.3691C21.9746 44.6219 21.6897 43.9177 21.2704 43.0215V43.0205C20.4116 41.1274 20.0867 40.2768 20.0155 39.6953C19.9796 39.4019 20.0079 39.1768 20.0624 38.9248C20.117 38.6719 20.1966 38.3975 20.2665 37.9912L20.2675 37.9824L20.2723 37.9736L31.2723 19.9736H31.2733C31.3431 19.8632 31.3907 19.7685 31.4198 19.6807C31.1829 19.1697 31.0507 18.6002 31.0507 18C31.0507 15.7911 32.8418 14.0004 35.0507 14ZM36.9999 25C35.8954 25.0001 34.9999 25.8955 34.9999 27C34.9999 28.1045 35.8954 28.9999 36.9999 29C38.1043 28.9999 38.9999 28.1045 38.9999 27C38.9999 25.8955 38.1043 25.0001 36.9999 25ZM34.9999 16C33.8953 16 32.9999 16.8954 32.9999 18C32.9999 19.1046 33.8953 20 34.9999 20C36.1044 19.9999 36.9999 19.1045 36.9999 18C36.9999 16.8955 36.1044 16.0001 34.9999 16Z\" fill=\"url(#paint2_linear_56_379)\"/><defs><linearGradient id=\"paint0_linear_56_379\" x1=\"306\" y1=\"5\" x2=\"306\" y2=\"67\" gradientUnits=\"userSpaceOnUse\"><stop stop-color=\"#9E9E9E\"/><stop offset=\"1\" stop-color=\"#464646\"/></linearGradient><linearGradient id=\"paint1_linear_56_379\" x1=\"19.5\" y1=\"52\" x2=\"0.999996\" y2=\"16\" gradientUnits=\"userSpaceOnUse\"><stop stop-color=\"#0959B9\"/><stop offset=\"1\" stop-color=\"#1B74D1\"/></linearGradient><linearGradient id=\"paint2_linear_56_379\" x1=\"30.5245\" y1=\"14\" x2=\"30.5245\" y2=\"51.6836\" gradientUnits=\"userSpaceOnUse\"><stop stop-color=\"#9E9E9E\"/><stop offset=\"1\" stop-color=\"#464646\"/></linearGradient></defs></svg>`;\n\nconst VRITTI_CLOUD_LOGO_DARK = `<svg width=\"170\" height=\"28\" viewBox=\"0 0 407 67\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M78.9948 52L66.7748 37.44H72.2868L84.5588 51.948V52H78.9948ZM47.3788 52V14.612H78.5788C79.7228 14.612 80.7628 14.9067 81.6988 15.496C82.6695 16.0507 83.4321 16.796 83.9868 17.732C84.5415 18.668 84.8188 19.708 84.8188 20.852V31.824C84.8188 32.968 84.5415 34.008 83.9868 34.944C83.4321 35.88 82.6695 36.6427 81.6988 37.232C80.7628 37.7867 79.7228 38.064 78.5788 38.064H51.5908V52H47.3788ZM53.6188 33.8H78.5788C79.1335 33.8 79.6015 33.6093 79.9828 33.228C80.3988 32.8467 80.6068 32.3787 80.6068 31.824V20.852C80.6068 20.2973 80.3988 19.8293 79.9828 19.448C79.6015 19.032 79.1335 18.824 78.5788 18.824H53.6188C53.0641 18.824 52.5788 19.032 52.1628 19.448C51.7815 19.8293 51.5908 20.2973 51.5908 20.852V31.824C51.5908 32.3787 51.7815 32.8467 52.1628 33.228C52.5788 33.6093 53.0641 33.8 53.6188 33.8ZM90.809 52V14.56H95.073V52H90.809ZM116.178 52V18.772H99.538V14.56H136.978V18.772H120.39V52H116.178ZM155.635 52V18.772H138.995V14.56H176.435V18.772H159.847V52H155.635ZM180.844 52V14.56H185.108V52H180.844Z\" fill=\"#0066CC\"/><path d=\"M218.027 50.336C215.479 49.244 213.295 47.736 211.371 45.864C209.499 43.94 207.991 41.756 206.899 39.208C205.807 36.66 205.235 34.008 205.235 31.2C205.235 28.34 205.807 25.636 206.899 23.088C207.991 20.592 209.499 18.408 211.371 16.536C213.295 14.612 215.479 13.156 218.027 12.064C220.523 10.972 223.227 10.4 226.087 10.4C228.947 10.4 231.651 10.92 234.147 12.012C236.643 13.104 238.879 14.612 240.803 16.536L235.863 21.372C233.107 18.668 229.831 17.368 226.087 17.368C224.163 17.368 222.343 17.732 220.679 18.408C218.963 19.136 217.507 20.124 216.259 21.424C215.011 22.672 214.023 24.128 213.295 25.792C212.567 27.508 212.203 29.276 212.203 31.2C212.203 33.124 212.567 34.892 213.295 36.608C214.023 38.272 215.011 39.728 216.311 40.976C217.559 42.276 219.015 43.264 220.679 43.992C222.395 44.668 224.163 45.032 226.087 45.032L234.147 50.388C231.651 51.48 228.947 52 226.087 52C223.227 52 220.523 51.428 218.027 50.336ZM252.177 45.032H272.977V52H245.261L252.177 45.032ZM245.261 10.4H252.177V11.544V35.62L245.261 42.588V11.544V10.4ZM295.636 10.4C301.408 10.4 306.244 12.48 310.3 16.536C314.408 20.592 316.488 25.48 316.488 31.2C316.488 36.972 314.408 41.86 310.3 45.864C306.244 49.92 301.408 52 295.636 52C289.864 52 285.028 49.92 280.972 45.864C276.916 41.808 274.836 36.972 274.836 31.2C274.836 25.48 276.916 20.592 280.972 16.536C284.976 12.48 289.864 10.4 295.636 10.4ZM295.636 17.368C291.84 17.368 288.564 18.668 285.808 21.424C283.104 24.128 281.804 27.404 281.804 31.2C281.804 34.996 283.104 38.324 285.808 41.028C288.512 43.732 291.84 45.032 295.636 45.032C299.432 45.032 302.708 43.732 305.412 41.028C308.168 38.272 309.468 34.996 309.468 31.2C309.468 27.404 308.116 24.128 305.412 21.424C302.708 18.72 299.432 17.368 295.636 17.368ZM352.723 34.684H359.639C359.639 39.468 357.975 43.576 354.543 46.956C351.163 50.336 347.003 52 342.271 52C337.539 52 333.431 50.336 330.051 46.956C326.671 43.576 325.007 39.416 325.007 34.684V10.4H331.923V34.684C331.923 37.544 332.911 39.988 334.887 41.964C336.915 44.044 339.411 45.032 342.271 45.032C345.131 45.032 347.575 43.992 349.603 41.964C351.631 39.936 352.723 37.492 352.723 34.684ZM396.621 23.192C397.713 25.688 398.233 28.34 398.233 31.2C398.233 34.06 397.713 36.712 396.621 39.208C395.477 41.756 394.021 43.94 392.149 45.864C390.277 47.736 388.041 49.244 385.493 50.336C382.997 51.428 380.293 52 377.433 52H363.601L370.517 45.032H377.433C379.357 45.032 381.177 44.668 382.841 43.992C384.505 43.264 386.013 42.276 387.261 40.976C388.509 39.676 389.497 38.22 390.225 36.556C390.953 34.84 391.317 33.072 391.317 31.2C391.317 29.328 390.953 27.56 390.225 25.844C389.497 24.18 388.509 22.724 387.209 21.424C385.961 20.176 384.453 19.136 382.789 18.408C381.125 17.732 379.305 17.368 377.433 17.368H370.517L363.601 10.4H377.433C380.293 10.4 382.945 10.972 385.493 12.064C387.989 13.156 390.225 14.664 392.097 16.536C394.021 18.46 395.477 20.644 396.621 23.192Z\" fill=\"url(#paint0_linear_92_90)\"/><path d=\"M7.99999 14H1.30874C0.614275 14.0193 0 14 0 14L18.8085 51.5C19.7652 52.3585 19.9594 52.3219 19.8087 51.5C19.3792 51.0014 19.4775 50.4593 20.3087 49C21.1421 46.2568 21.2304 43.8491 19.3087 41.5C18.2179 39.4696 18.9309 37.1826 20.8087 34L16.8087 26.5L14.8087 22.5L12.3087 18L11 15C10.5113 14.44 10.1886 14.2085 9.5 14H8.49999H7.99999Z\" fill=\"url(#paint1_linear_92_90)\"/><path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M35.0497 14C37.2588 14 39.0497 15.7909 39.0497 18C39.0497 20.2091 37.2588 22 35.0497 22C34.4321 21.9998 33.8463 21.8595 33.3241 21.6094C33.1708 21.6937 33.0224 21.8295 32.8554 22.0293L24.3583 35.0234C24.2882 35.1903 24.2274 35.3389 24.1805 35.4658C24.1332 35.5943 24.1019 35.6986 24.0927 35.7783C24.0834 35.8586 24.099 35.9018 24.1229 35.9238C24.1489 35.9468 24.2034 35.9625 24.3085 35.9502C26.1438 35.5539 27.4169 35.1177 28.4638 34.5029C29.5108 33.8878 30.3372 33.0905 31.2772 31.9678L33.3817 28.5967C33.1686 28.1076 33.0497 27.5675 33.0497 27C33.0497 24.7912 34.841 23.0006 37.0497 23C39.2588 23 41.0497 24.7909 41.0497 27C41.0497 29.0499 39.5078 30.7378 37.5204 30.9707C37.4596 30.9954 37.3974 31.0223 37.3339 31.0469L37.3173 31.0527L37.3007 31.0479C37.2448 31.0305 37.1894 31.0136 37.1356 30.9971C37.1074 30.9977 37.0781 31 37.0497 31C36.4643 30.9999 35.9082 30.8726 35.4071 30.6465C35.0273 30.6577 34.6935 30.7722 34.3505 31.0352L21.3573 51.5264L21.3534 51.5342L21.3466 51.5391C21.2337 51.6276 21.1424 51.6841 21.0507 51.6836C20.981 51.6826 20.9167 51.6493 20.8505 51.5967L20.7821 51.5371C20.6425 51.4109 20.5854 51.2341 20.5888 51.0195C20.5921 50.8061 20.6543 50.5492 20.7577 50.2539C20.9644 49.664 21.3413 48.8996 21.7675 47.9834C22.0915 46.9083 22.1753 46.1149 22.0751 45.3691C21.9746 44.6219 21.6897 43.9177 21.2704 43.0215V43.0205C20.4116 41.1274 20.0867 40.2768 20.0155 39.6953C19.9796 39.4019 20.008 39.1768 20.0624 38.9248C20.117 38.672 20.1966 38.3974 20.2665 37.9912L20.2675 37.9824L20.2723 37.9736L31.2723 19.9736H31.2733C31.3431 19.8632 31.3907 19.7685 31.4198 19.6807C31.1829 19.1697 31.0497 18.6003 31.0497 18C31.0497 15.7912 32.841 14.0006 35.0497 14ZM36.9999 25C35.8955 25.0002 34.9999 25.8956 34.9999 27C34.9999 28.1044 35.8955 28.9998 36.9999 29C38.1042 28.9998 38.9999 28.1044 38.9999 27C38.9999 25.8956 38.1042 25.0002 36.9999 25ZM34.9999 16C33.8953 16 32.9999 16.8954 32.9999 18C32.9999 19.1046 33.8953 20 34.9999 20C36.1044 19.9999 36.9999 19.1045 36.9999 18C36.9999 16.8955 36.1044 16.0001 34.9999 16Z\" fill=\"url(#paint2_linear_92_90)\"/><defs><linearGradient id=\"paint0_linear_92_90\" x1=\"306\" y1=\"5\" x2=\"306\" y2=\"67\" gradientUnits=\"userSpaceOnUse\"><stop stop-color=\"#D9D9D9\"/><stop offset=\"1\" stop-color=\"#737373\"/></linearGradient><linearGradient id=\"paint1_linear_92_90\" x1=\"19.5\" y1=\"52\" x2=\"0.999996\" y2=\"16\" gradientUnits=\"userSpaceOnUse\"><stop stop-color=\"#0959B9\"/><stop offset=\"1\" stop-color=\"#1B74D1\"/></linearGradient><linearGradient id=\"paint2_linear_92_90\" x1=\"30.5246\" y1=\"14\" x2=\"30.5246\" y2=\"51.6836\" gradientUnits=\"userSpaceOnUse\"><stop stop-color=\"#D9D9D9\"/><stop offset=\"1\" stop-color=\"#737373\"/></linearGradient></defs></svg>`;\n\ntype EmailNoticeTone = 'warn' | 'danger' | 'success';\ntype EmailButtonTone = 'primary' | 'danger';\n\nconst EMAIL_NOTICE_TONES: Record<\n EmailNoticeTone,\n { background: string; color: string; boxClass: string; textClass: string }\n> = {\n warn: { background: '#fdf6e7', color: '#8a6410', boxClass: 'e-warnbox', textClass: 'e-warn' },\n danger: { background: '#fdeceb', color: '#a32b21', boxClass: 'e-dangerbox', textClass: 'e-danger' },\n success: { background: '#eaf6ee', color: '#1c6b3f', boxClass: 'e-successbox', textClass: 'e-success' },\n};\n\nconst EMAIL_BUTTON_TONES: Record<EmailButtonTone, string> = {\n primary: '#1b74d1',\n danger: '#c0392b',\n};\n\n@Injectable()\nexport class EmailService {\n private readonly logger = new Logger(EmailService.name);\n private readonly brevoClient: BrevoClient;\n private readonly senderEmail: string;\n private readonly senderName: string;\n private readonly logoLightUrl?: string;\n private readonly logoDarkUrl?: string;\n private readonly frontendBaseUrl?: string;\n\n constructor(private readonly configService: ConfigService) {\n const apiKey = this.configService.get<string>('BREVO_API_KEY');\n\n if (!apiKey) {\n this.logger.error('BREVO_API_KEY is not configured. Email sending will fail.');\n throw new Error('Email service configuration error: Missing BREVO_API_KEY');\n }\n\n // Initialize Brevo client with built-in retry support\n this.brevoClient = new BrevoClient({ apiKey, maxRetries: 3 });\n\n // Get sender configuration\n const senderEmail = this.configService.get<string>('SENDER_EMAIL');\n const senderName = this.configService.get<string>('SENDER_NAME');\n\n if (!senderEmail || !senderName) {\n this.logger.error('Sender email or name is not configured.');\n throw new Error('Email service configuration error: Missing SENDER_EMAIL or SENDER_NAME');\n }\n\n this.senderEmail = senderEmail;\n this.senderName = senderName;\n\n // Hosted brand marks for email headers. Must be PNG - most clients block SVG.\n this.logoLightUrl = this.configService.get<string>('EMAIL_LOGO_LIGHT_URL');\n this.logoDarkUrl = this.configService.get<string>('EMAIL_LOGO_DARK_URL') || this.logoLightUrl;\n\n if (!this.logoLightUrl) {\n this.logger.warn(\n 'EMAIL_LOGO_LIGHT_URL is not configured. Emails fall back to an inline SVG wordmark, which Gmail, Outlook and Yahoo strip.',\n );\n }\n\n // Base URL for links back into the web app. Optional at boot so consumers that send no\n // link-bearing emails still start; the senders that need it throw with a clear message.\n this.frontendBaseUrl = this.configService.get<string>('FRONTEND_BASE_URL')?.replace(/\\/+$/, '');\n\n if (!this.frontendBaseUrl) {\n this.logger.warn('FRONTEND_BASE_URL is not configured. Emails containing web app links will fail to send.');\n }\n\n this.logger.log('Brevo email service initialized successfully');\n }\n\n // Sends an email verification OTP to the given recipient\n async sendVerificationEmail(email: string, otp: string, expiresAt: Date, displayName?: string): Promise<void> {\n const name = displayName || 'there';\n const expiry = this.formatExpiry(expiresAt);\n\n const htmlContent = this.renderEmailShell({\n preheader: `Your verification code is ${otp}. It expires in ${expiry}.`,\n heading: 'Verify Your Email',\n body: [\n this.emailText(`Hello ${name} — enter the code below to finish setting up your Vritti AI Cloud account.`),\n this.emailCode('Verification Code', otp),\n this.emailNotice(`This code expires ${expiry} after it was sent.`, 'warn'),\n this.emailDivider(),\n this.emailMuted(\n \"Didn't request this? No account was created. You can ignore this email and the code will expire on its own.\",\n ),\n ].join(''),\n });\n\n const textContent = this.renderTextShell([\n `Hello ${name},`,\n 'Enter the code below to finish setting up your Vritti AI Cloud account.',\n `Verification Code: ${otp}`,\n `This code expires ${expiry} after it was sent.`,\n \"Didn't request this? No account was created. You can ignore this email and the code will expire on its own.\",\n ]);\n\n await this.sendEmail({\n to: [{ email, name }],\n subject: 'Verify Your Email - Vritti AI Cloud',\n htmlContent,\n textContent,\n });\n\n this.logger.log(`Verification email sent to ${email}`);\n }\n\n // Sends a password reset OTP to the given recipient\n async sendPasswordResetEmail(email: string, otp: string, expiresAt: Date, displayName?: string): Promise<void> {\n const name = displayName || 'there';\n const expiry = this.formatExpiry(expiresAt);\n\n const htmlContent = this.renderEmailShell({\n preheader: `Your password reset code is ${otp}. It expires in ${expiry}.`,\n heading: 'Reset Your Password',\n body: [\n this.emailText(\n `Hello ${name} — we received a request to reset your password. Enter the code below to choose a new one.`,\n ),\n this.emailCode('Reset Code', otp),\n this.emailNotice(`This code expires ${expiry} after it was sent.`, 'warn'),\n this.emailNotice(\n '<strong>Never share this code.</strong> Vritti will never ask you for it by email, phone or chat.',\n 'danger',\n ),\n this.emailDivider(),\n this.emailMuted(\n \"Didn't request a reset? You can ignore this email — your password stays unchanged and the code will expire on its own.\",\n ),\n ].join(''),\n });\n\n const textContent = this.renderTextShell([\n `Hello ${name},`,\n 'We received a request to reset your password. Enter the code below to choose a new one.',\n `Reset Code: ${otp}`,\n `This code expires ${expiry} after it was sent.`,\n 'Never share this code. Vritti will never ask you for it by email, phone or chat.',\n \"Didn't request a reset? You can ignore this email - your password stays unchanged and the code will expire on its own.\",\n ]);\n\n await this.sendEmail({\n to: [{ email, name }],\n subject: 'Reset Your Password - Vritti AI Cloud',\n htmlContent,\n textContent,\n });\n\n this.logger.log(`Password reset email sent to ${email}`);\n }\n\n // Sends an email change notification to the old address with a revert link\n async sendEmailChangeNotification(\n oldEmail: string,\n newEmail: string,\n revertToken: string,\n revertExpiresAt: Date,\n displayName?: string,\n ): Promise<void> {\n const name = displayName || 'there';\n const hoursUntilExpiry = Math.floor((revertExpiresAt.getTime() - Date.now()) / 3_600_000);\n const window = `${hoursUntilExpiry} ${pluralize('hour', hoursUntilExpiry)}`;\n\n const revertLink = `${this.requireFrontendBaseUrl()}/settings/profile/revert-email?token=${revertToken}`;\n\n const htmlContent = this.renderEmailShell({\n preheader: `Your Vritti AI Cloud email address is now ${newEmail}.`,\n heading: 'Your Email Address Changed',\n body: [\n this.emailText(`Hello ${name} — the email address on your Vritti AI Cloud account has been changed.`),\n this.emailFields([\n { label: 'Previous Email', value: oldEmail },\n { label: 'New Email', value: newEmail },\n ]),\n this.emailNotice(\n `<strong>Didn't make this change?</strong> You can revert it within the next ${window}.`,\n 'danger',\n ),\n this.emailButton(revertLink, 'Revert Email Change', 'danger'),\n this.emailDivider(),\n this.emailMuted('If you made this change yourself, no action is needed — you can ignore this email.'),\n ].join(''),\n });\n\n const textContent = this.renderTextShell([\n `Hello ${name},`,\n 'The email address on your Vritti AI Cloud account has been changed.',\n `Previous Email: ${oldEmail}`,\n `New Email: ${newEmail}`,\n `Didn't make this change? You can revert it within the next ${window}:`,\n revertLink,\n 'If you made this change yourself, no action is needed - you can ignore this email.',\n ]);\n\n await this.sendEmail({\n to: [{ email: oldEmail, name }],\n subject: 'Your Email Address Has Been Changed - Vritti AI Cloud',\n htmlContent,\n textContent,\n });\n\n this.logger.log(`Email change notification sent to ${oldEmail}`);\n }\n\n // Sends a confirmation to the restored email address after a revert\n async sendEmailRevertConfirmation(email: string, displayName?: string): Promise<void> {\n const name = displayName || 'there';\n\n const htmlContent = this.renderEmailShell({\n preheader: `Your Vritti AI Cloud email address is back to ${email}.`,\n heading: 'Email Change Reverted',\n body: [\n this.emailText(\n `Hello ${name} — the recent change to your email address has been reverted. Your account email is now:`,\n ),\n this.emailNotice(email, 'success', true),\n this.emailDivider(),\n this.emailMuted(\"Didn't request this revert? Contact our support team immediately.\"),\n ].join(''),\n });\n\n const textContent = this.renderTextShell([\n `Hello ${name},`,\n 'The recent change to your email address has been reverted. Your account email is now:',\n email,\n \"Didn't request this revert? Contact our support team immediately.\",\n ]);\n\n await this.sendEmail({\n to: [{ email, name }],\n subject: 'Email Address Change Reverted - Vritti AI Cloud',\n htmlContent,\n textContent,\n });\n\n this.logger.log(`Email revert confirmation sent to ${email}`);\n }\n\n // Sends an invite email to a new portal user with their set-password link\n async sendInviteEmail(params: { to: string; name: string; inviteUrl: string }): Promise<void> {\n const { to, name, inviteUrl } = params;\n\n const htmlContent = this.renderEmailShell({\n preheader: 'Set your password to activate your Vritti AI Cloud account.',\n heading: \"You're Invited\",\n body: [\n this.emailText(\n `Hello ${name} — you've been invited to join Vritti AI Cloud. Set your password to activate your account.`,\n ),\n this.emailButton(inviteUrl, 'Set Your Password'),\n this.emailNotice('This invite link is single-use and expires once your password is set.', 'warn'),\n this.emailDivider(),\n this.emailMuted(\n `If the button doesn't work, copy this link into your browser:<br><span style=\"word-break:break-all;\">${inviteUrl}</span>`,\n ),\n ].join(''),\n });\n\n const textContent = this.renderTextShell([\n `Hello ${name},`,\n \"You've been invited to join Vritti AI Cloud. Visit the link below to set your password and activate your account:\",\n inviteUrl,\n 'This invite link is single-use and expires once your password is set.',\n ]);\n\n await this.sendEmail({\n to: [{ email: to, name }],\n subject: 'You have been invited to Vritti AI',\n htmlContent,\n textContent,\n });\n\n this.logger.log(`Invite email sent to ${to}`);\n }\n\n // Sends a transactional email with custom subject, HTML, and text content\n async sendTransactionalEmail(params: {\n to: { email: string; name?: string };\n subject: string;\n htmlContent: string;\n textContent: string;\n }): Promise<void> {\n await this.sendEmail({\n to: [params.to],\n subject: params.subject,\n htmlContent: params.htmlContent,\n textContent: params.textContent,\n });\n this.logger.log(`Transactional email sent to ${params.to.email}`);\n }\n\n // Verifies Brevo API connectivity — a 400 response means the API is reachable\n async verifyConnection(): Promise<boolean> {\n try {\n await this.brevoClient.transactionalEmails.sendTransacEmail({\n sender: { email: this.senderEmail, name: this.senderName },\n to: [{ email: this.senderEmail }],\n subject: 'Connection Test',\n htmlContent: '<p>Test</p>',\n });\n return true;\n } catch (err) {\n // A 400 error means the API is reachable but params are incomplete — still a successful connection test\n if (err instanceof BrevoError && err.statusCode === 400) {\n return true;\n }\n this.logger.error('Brevo connection verification failed:', err);\n return false;\n }\n }\n\n // Sends a transactional email via Brevo — retries handled internally by BrevoClient\n // Renders the shared transactional email shell: brand header, card, heading, body rows, footer\n private renderEmailShell(params: { preheader: string; heading: string; body: string }): string {\n const { preheader, heading, body } = params;\n\n return `\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<meta name=\"x-apple-disable-message-reformatting\">\n<meta name=\"color-scheme\" content=\"light dark\">\n<meta name=\"supported-color-schemes\" content=\"light dark\">\n<title>${heading}</title>\n<!--[if mso]>\n<style>body,table,td,a{font-family:Arial,Helvetica,sans-serif !important;}</style>\n<![endif]-->\n<style>\n @media only screen and (max-width:620px){\n .m-pad{padding-left:24px !important;padding-right:24px !important;}\n .m-code{font-size:32px !important;letter-spacing:8px !important;}\n .m-h1{font-size:26px !important;line-height:32px !important;}\n }\n @media (prefers-color-scheme:dark){\n .e-bg{background-color:#0f1214 !important;}\n .e-card{background-color:#1c2126 !important;border-color:#2b3238 !important;}\n .e-h1{color:#f0f3f5 !important;}\n .e-body{color:#a3adb8 !important;}\n .e-panel{background-color:#14181c !important;border-color:#2b3238 !important;}\n .e-label{color:#8b96a2 !important;}\n .e-code{color:#63a8ee !important;}\n .e-value{color:#f0f3f5 !important;}\n .e-warnbox{background-color:#2a2317 !important;}\n .e-warn{color:#e0b45e !important;}\n .e-dangerbox{background-color:#2b1a18 !important;}\n .e-danger{color:#ef8a7f !important;}\n .e-successbox{background-color:#16261c !important;}\n .e-success{color:#5fc98a !important;}\n .e-rule{background-color:#2b3238 !important;}\n .e-muted{color:#8b96a2 !important;}\n .e-footer{color:#79838e !important;}\n .e-logo-light{display:none !important;}\n .e-logo-dark{display:block !important;}\n }\n</style>\n</head>\n<body class=\"e-bg\" style=\"margin:0;padding:0;background-color:#f7f9fb;\">\n<span style=\"display:none!important;visibility:hidden;opacity:0;color:transparent;height:0;width:0;overflow:hidden;mso-hide:all;\">${preheader}</span>\n\n<table role=\"presentation\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" width=\"100%\" class=\"e-bg\" style=\"background-color:#f7f9fb;\">\n <tr>\n <td align=\"center\" style=\"padding:40px 12px 56px 12px;\">\n\n <table role=\"presentation\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" width=\"600\" style=\"width:600px;max-width:600px;\">\n\n <tr>\n <td style=\"padding:0 0 20px 2px;\">\n ${this.renderEmailBrand()}\n </td>\n </tr>\n\n <tr>\n <td class=\"e-card\" style=\"background-color:#ffffff;border:1px solid #e6ebf2;border-radius:12px;\">\n <table role=\"presentation\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" width=\"100%\">\n\n <tr>\n <td class=\"m-pad m-h1 e-h1\" style=\"padding:40px 40px 0 40px;font-family:${EMAIL_SANS};font-size:28px;line-height:34px;mso-line-height-rule:exactly;color:#1b2434;font-weight:600;letter-spacing:-0.4px;\">\n ${heading}\n </td>\n </tr>\n${body}\n <tr><td style=\"height:36px;line-height:36px;font-size:0;\"> </td></tr>\n\n </table>\n </td>\n </tr>\n\n <tr>\n <td class=\"m-pad e-footer\" style=\"padding:22px 2px 0 2px;font-family:${EMAIL_SANS};font-size:12px;line-height:20px;mso-line-height-rule:exactly;color:#8a95a5;letter-spacing:0.01em;\">\n Vritti AI Cloud<br>\n Automated message — replies aren't monitored.\n </td>\n </tr>\n\n </table>\n\n </td>\n </tr>\n</table>\n</body>\n</html>\n `.trim();\n }\n\n // Builds the plain-text alternative from the same blocks the HTML shell renders\n private renderTextShell(blocks: string[]): string {\n const footer = ['---', 'Vritti AI Cloud', \"Automated message - replies aren't monitored.\"].join('\\n');\n return [...blocks, footer].join('\\n\\n');\n }\n\n // Body paragraph\n private emailText(html: string): string {\n return `\n <tr>\n <td class=\"m-pad e-body\" style=\"padding:14px 40px 0 40px;font-family:${EMAIL_SANS};font-size:15px;line-height:25px;mso-line-height-rule:exactly;color:#5d6b80;letter-spacing:0.01em;\">\n ${html}\n </td>\n </tr>`;\n }\n\n // Monospace OTP tile with an uppercase label\n private emailCode(label: string, code: string): string {\n return `\n <tr>\n <td class=\"m-pad\" style=\"padding:28px 40px 0 40px;\">\n <table role=\"presentation\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" width=\"100%\" class=\"e-panel\" style=\"background-color:#f7f9fb;border:1px solid #e6ebf2;border-radius:10px;\">\n <tr>\n <td align=\"center\" class=\"e-label\" style=\"padding:18px 16px 6px 16px;font-family:${EMAIL_SANS};font-size:11px;line-height:16px;mso-line-height-rule:exactly;letter-spacing:1.6px;text-transform:uppercase;color:#7b8798;font-weight:500;\">\n ${label}\n </td>\n </tr>\n <tr>\n <td align=\"center\" class=\"m-code e-code\" style=\"padding:0 16px 20px 16px;font-family:${EMAIL_MONO};font-size:38px;line-height:46px;mso-line-height-rule:exactly;letter-spacing:11px;color:#1b74d1;font-weight:600;text-indent:11px;\">\n ${code}\n </td>\n </tr>\n </table>\n </td>\n </tr>`;\n }\n\n // Tinted callout. `center` renders the content as a centred mono value instead of a sentence\n private emailNotice(html: string, tone: EmailNoticeTone, center = false): string {\n const palette = EMAIL_NOTICE_TONES[tone];\n const typography = center\n ? `font-family:${EMAIL_MONO};font-size:17px;line-height:26px;font-weight:600;`\n : `font-family:${EMAIL_SANS};font-size:13px;line-height:19px;letter-spacing:0.01em;`;\n\n return `\n <tr>\n <td class=\"m-pad\" style=\"padding:16px 40px 0 40px;\">\n <table role=\"presentation\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" width=\"100%\" class=\"${palette.boxClass}\" style=\"background-color:${palette.background};border-radius:8px;\">\n <tr>\n <td align=\"${center ? 'center' : 'left'}\" class=\"${palette.textClass}\" style=\"padding:${center ? '16px 14px' : '11px 14px'};${typography}mso-line-height-rule:exactly;color:${palette.color};\">\n ${html}\n </td>\n </tr>\n </table>\n </td>\n </tr>`;\n }\n\n // Label/value list rendered with monospace values\n private emailFields(fields: { label: string; value: string }[]): string {\n const rows = fields\n .map(\n (field, index) => `\n <tr>\n <td class=\"e-label\" style=\"padding:${index === 0 ? '18px' : '14px'} 18px 0 18px;font-family:${EMAIL_SANS};font-size:11px;line-height:16px;mso-line-height-rule:exactly;letter-spacing:1.6px;text-transform:uppercase;color:#7b8798;font-weight:500;\">\n ${field.label}\n </td>\n </tr>\n <tr>\n <td class=\"e-value\" style=\"padding:4px 18px ${index === fields.length - 1 ? '18px' : '0'} 18px;font-family:${EMAIL_MONO};font-size:15px;line-height:23px;mso-line-height-rule:exactly;color:#1b2434;word-break:break-all;\">\n ${field.value}\n </td>\n </tr>`,\n )\n .join('');\n\n return `\n <tr>\n <td class=\"m-pad\" style=\"padding:28px 40px 0 40px;\">\n <table role=\"presentation\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" width=\"100%\" class=\"e-panel\" style=\"background-color:#f7f9fb;border:1px solid #e6ebf2;border-radius:10px;\">${rows}\n </table>\n </td>\n </tr>`;\n }\n\n // Solid CTA button. Colours are theme-independent so the fill reads the same in light and dark\n private emailButton(href: string, label: string, tone: EmailButtonTone = 'primary'): string {\n const background = EMAIL_BUTTON_TONES[tone];\n\n return `\n <tr>\n <td class=\"m-pad\" style=\"padding:24px 40px 0 40px;\">\n <!--[if mso]>\n <v:roundrect xmlns:v=\"urn:schemas-microsoft-com:vml\" xmlns:w=\"urn:schemas-microsoft-com:office:word\" href=\"${href}\" style=\"height:44px;v-text-anchor:middle;width:240px;\" arcsize=\"20%\" stroke=\"f\" fillcolor=\"${background}\">\n <w:anchorlock/>\n <center style=\"color:#ffffff;font-family:Arial,sans-serif;font-size:15px;font-weight:600;\">${label}</center>\n </v:roundrect>\n <![endif]-->\n <!--[if !mso]><!-->\n <a href=\"${href}\" style=\"display:inline-block;padding:13px 28px;background-color:${background};color:#ffffff;text-decoration:none;border-radius:8px;font-family:${EMAIL_SANS};font-size:15px;line-height:18px;font-weight:600;letter-spacing:0.01em;\">${label}</a>\n <!--<![endif]-->\n </td>\n </tr>`;\n }\n\n // Hairline rule separating the body from the closing note\n private emailDivider(): string {\n return `\n <tr>\n <td class=\"m-pad\" style=\"padding:28px 40px 0 40px;\">\n <table role=\"presentation\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" width=\"100%\">\n <tr><td height=\"1\" class=\"e-rule\" style=\"height:1px;line-height:1px;font-size:0;background-color:#eef2f7;\"> </td></tr>\n </table>\n </td>\n </tr>`;\n }\n\n // Small closing note below the divider\n private emailMuted(html: string): string {\n return `\n <tr>\n <td class=\"m-pad e-muted\" style=\"padding:18px 40px 0 40px;font-family:${EMAIL_SANS};font-size:13px;line-height:21px;mso-line-height-rule:exactly;color:#7b8798;letter-spacing:0.01em;\">\n ${html}\n </td>\n </tr>`;\n }\n\n // Renders the email header brand mark: a hosted logo when configured, inline SVG otherwise\n private renderEmailBrand(): string {\n const imgStyle = 'border:0;outline:none;text-decoration:none;width:170px;height:auto;';\n\n // A hosted PNG is preferred: it is the only form Gmail, Outlook and Yahoo will render\n if (this.logoLightUrl) {\n return `<img src=\"${this.logoLightUrl}\" width=\"170\" height=\"28\" alt=\"Vritti AI Cloud\" class=\"e-logo-light\" style=\"display:block;${imgStyle}\">\n <img src=\"${this.logoDarkUrl}\" width=\"170\" height=\"28\" alt=\"Vritti AI Cloud\" class=\"e-logo-dark\" style=\"display:none;${imgStyle}\">`;\n }\n\n // Both marks ship; the dark one is revealed by the prefers-color-scheme rule\n return `<div class=\"e-logo-light\" style=\"display:block;font-size:0;line-height:0;\">${VRITTI_CLOUD_LOGO_LIGHT}</div>\n <div class=\"e-logo-dark\" style=\"display:none;font-size:0;line-height:0;\">${VRITTI_CLOUD_LOGO_DARK}</div>`;\n }\n\n // Frontend base URL for emails that link back into the web app\n private requireFrontendBaseUrl(): string {\n if (!this.frontendBaseUrl) {\n throw new Error('Email service configuration error: Missing FRONTEND_BASE_URL');\n }\n return this.frontendBaseUrl;\n }\n\n // Renders an OTP expiry window as a pluralised minute count\n private formatExpiry(expiresAt: Date): string {\n const minutes = Math.ceil((expiresAt.getTime() - Date.now()) / 60_000);\n return `${minutes} ${pluralize('minute', minutes)}`;\n }\n private async sendEmail(emailData: {\n to: Array<{ email: string; name?: string }>;\n subject: string;\n htmlContent: string;\n textContent: string;\n }): Promise<void> {\n try {\n const result = await this.brevoClient.transactionalEmails.sendTransacEmail({\n sender: { email: this.senderEmail, name: this.senderName },\n to: emailData.to,\n subject: emailData.subject,\n htmlContent: emailData.htmlContent,\n textContent: emailData.textContent,\n });\n this.logger.debug(`Email sent successfully. Message ID: ${result.messageId}`);\n } catch (err) {\n if (err instanceof BrevoTimeoutError) {\n this.logger.error('Brevo request timed out after retries.');\n throw new Error('Email sending failed: timeout');\n }\n if (err instanceof BrevoError) {\n if (err.statusCode === 429) {\n this.logger.error('Brevo rate limit exceeded after retries.');\n throw new Error('Email sending failed: rate limit exceeded');\n }\n if (err.statusCode === 401) {\n this.logger.error('Brevo authentication failed. Check your API key.');\n throw new Error('Email service authentication failed');\n }\n if (err.statusCode === 400) {\n this.logger.error('Bad request to Brevo API:', err.message);\n throw new Error(`Invalid email parameters: ${err.message}`);\n }\n this.logger.error(`Brevo API error ${err.statusCode}:`, err.message);\n throw new Error(`Email sending failed: ${err.message}`);\n }\n throw err;\n }\n }\n}\n","// Re-export pluralize-esm's default export as the subpath entry point\nexport { default as pluralize } from 'pluralize-esm';\n"],"mappings":"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","names":["Global","Module","ConfigModule","BrevoClient","BrevoError","BrevoTimeoutError","Injectable","Logger","ConfigService","pluralize","default","EMAIL_SANS","EMAIL_MONO","VRITTI_CLOUD_LOGO_LIGHT","VRITTI_CLOUD_LOGO_DARK","EMAIL_NOTICE_TONES","warn","background","color","boxClass","textClass","danger","success","EMAIL_BUTTON_TONES","primary","EmailService","logger","Logger","name","brevoClient","senderEmail","senderName","logoLightUrl","logoDarkUrl","frontendBaseUrl","configService","apiKey","get","error","Error","BrevoClient","maxRetries","replace","log","sendVerificationEmail","email","otp","expiresAt","displayName","expiry","formatExpiry","htmlContent","renderEmailShell","preheader","heading","body","emailText","emailCode","emailNotice","emailDivider","emailMuted","join","textContent","renderTextShell","sendEmail","to","subject","sendPasswordResetEmail","sendEmailChangeNotification","oldEmail","newEmail","revertToken","revertExpiresAt","hoursUntilExpiry","Math","floor","getTime","Date","now","window","pluralize","revertLink","requireFrontendBaseUrl","emailFields","label","value","emailButton","sendEmailRevertConfirmation","sendInviteEmail","params","inviteUrl","sendTransactionalEmail","verifyConnection","transactionalEmails","sendTransacEmail","sender","err","BrevoError","statusCode","renderEmailBrand","trim","blocks","footer","html","code","tone","center","palette","typography","fields","rows","map","field","index","length","href","imgStyle","minutes","ceil","emailData","result","debug","messageId","BrevoTimeoutError","message","EmailModule","imports","ConfigModule","providers","EmailService","exports"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -41,7 +41,7 @@ declare class RequestService {
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
declare const AUTH_CONFIG: unique symbol;
|
|
44
|
-
type OnAuthenticatedCallback = (requestService: RequestService,
|
|
44
|
+
type OnAuthenticatedCallback = (requestService: RequestService, auth: NonNullable<FastifyRequest['auth']>) => void | Promise<void>;
|
|
45
45
|
type TokenExpiryString = `${number}${'s' | 'm' | 'h' | 'd' | 'w' | 'y'}`;
|
|
46
46
|
interface TokenExpiry {
|
|
47
47
|
access: TokenExpiryString;
|
|
@@ -118,19 +118,25 @@ interface RefreshTokenPayload {
|
|
|
118
118
|
type DecodedRefreshToken = RefreshTokenPayload & JwtClaims;
|
|
119
119
|
|
|
120
120
|
declare module 'fastify' {
|
|
121
|
-
interface
|
|
121
|
+
interface VrittiAuthBase {
|
|
122
|
+
}
|
|
123
|
+
interface VrittiSessionAuth extends VrittiAuthBase {
|
|
124
|
+
kind: 'session';
|
|
122
125
|
userId: string;
|
|
123
126
|
sessionId: string;
|
|
124
127
|
sessionType: string;
|
|
125
|
-
/**
|
|
126
|
-
* What an app credential is for, when the caller is an app rather than a
|
|
127
|
-
* person. Set by the server's `onAuthenticated` hook and compared against the
|
|
128
|
-
* types passed to `@RequireApp(...)`.
|
|
129
|
-
*/
|
|
130
|
-
appType?: string;
|
|
131
128
|
}
|
|
129
|
+
interface VrittiAppAuth extends VrittiAuthBase {
|
|
130
|
+
kind: 'app';
|
|
131
|
+
appId: string;
|
|
132
|
+
appType: string;
|
|
133
|
+
}
|
|
134
|
+
interface VrittiCloudAuth extends VrittiAuthBase {
|
|
135
|
+
kind: 'cloud';
|
|
136
|
+
}
|
|
137
|
+
type VrittiAuth = VrittiSessionAuth | VrittiAppAuth | VrittiCloudAuth;
|
|
132
138
|
interface FastifyRequest {
|
|
133
|
-
|
|
139
|
+
auth?: VrittiAuth;
|
|
134
140
|
authConfig?: AuthConfig;
|
|
135
141
|
cookies?: Record<string, string>;
|
|
136
142
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -41,7 +41,7 @@ declare class RequestService {
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
declare const AUTH_CONFIG: unique symbol;
|
|
44
|
-
type OnAuthenticatedCallback = (requestService: RequestService,
|
|
44
|
+
type OnAuthenticatedCallback = (requestService: RequestService, auth: NonNullable<FastifyRequest['auth']>) => void | Promise<void>;
|
|
45
45
|
type TokenExpiryString = `${number}${'s' | 'm' | 'h' | 'd' | 'w' | 'y'}`;
|
|
46
46
|
interface TokenExpiry {
|
|
47
47
|
access: TokenExpiryString;
|
|
@@ -118,19 +118,25 @@ interface RefreshTokenPayload {
|
|
|
118
118
|
type DecodedRefreshToken = RefreshTokenPayload & JwtClaims;
|
|
119
119
|
|
|
120
120
|
declare module 'fastify' {
|
|
121
|
-
interface
|
|
121
|
+
interface VrittiAuthBase {
|
|
122
|
+
}
|
|
123
|
+
interface VrittiSessionAuth extends VrittiAuthBase {
|
|
124
|
+
kind: 'session';
|
|
122
125
|
userId: string;
|
|
123
126
|
sessionId: string;
|
|
124
127
|
sessionType: string;
|
|
125
|
-
/**
|
|
126
|
-
* What an app credential is for, when the caller is an app rather than a
|
|
127
|
-
* person. Set by the server's `onAuthenticated` hook and compared against the
|
|
128
|
-
* types passed to `@RequireApp(...)`.
|
|
129
|
-
*/
|
|
130
|
-
appType?: string;
|
|
131
128
|
}
|
|
129
|
+
interface VrittiAppAuth extends VrittiAuthBase {
|
|
130
|
+
kind: 'app';
|
|
131
|
+
appId: string;
|
|
132
|
+
appType: string;
|
|
133
|
+
}
|
|
134
|
+
interface VrittiCloudAuth extends VrittiAuthBase {
|
|
135
|
+
kind: 'cloud';
|
|
136
|
+
}
|
|
137
|
+
type VrittiAuth = VrittiSessionAuth | VrittiAppAuth | VrittiCloudAuth;
|
|
132
138
|
interface FastifyRequest {
|
|
133
|
-
|
|
139
|
+
auth?: VrittiAuth;
|
|
134
140
|
authConfig?: AuthConfig;
|
|
135
141
|
cookies?: Record<string, string>;
|
|
136
142
|
}
|
package/dist/nats.cjs
CHANGED
|
@@ -182,13 +182,16 @@ var import_common4 = require("@nestjs/common");
|
|
|
182
182
|
|
|
183
183
|
// src/nats/nats-context.ts
|
|
184
184
|
var NATS_HEADER_KEYS = {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
185
|
+
orgId: "x-org-id",
|
|
186
|
+
userId: "x-user-id",
|
|
187
|
+
siteId: "x-site-id",
|
|
188
|
+
legalEntityId: "x-le-id",
|
|
189
|
+
siteGroupId: "x-sg-id",
|
|
190
|
+
siteTimezone: "x-site-timezone",
|
|
191
|
+
siteCurrencyCode: "x-site-currency-code"
|
|
192
|
+
};
|
|
193
|
+
var HEADER_FALLBACKS = {
|
|
194
|
+
siteTimezone: "UTC"
|
|
192
195
|
};
|
|
193
196
|
function getHeader(headers, key) {
|
|
194
197
|
if (!headers) return void 0;
|
|
@@ -201,18 +204,13 @@ function getHeader(headers, key) {
|
|
|
201
204
|
__name(getHeader, "getHeader");
|
|
202
205
|
function parseNatsHeaders(headers) {
|
|
203
206
|
if (!headers) return null;
|
|
204
|
-
const orgId = getHeader(headers, NATS_HEADER_KEYS.
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
legalEntityId: getHeader(headers, NATS_HEADER_KEYS.LE_ID) || "",
|
|
212
|
-
siteGroupId: getHeader(headers, NATS_HEADER_KEYS.SITE_GROUP_ID) || "",
|
|
213
|
-
siteTimezone: getHeader(headers, NATS_HEADER_KEYS.SITE_TIMEZONE) || "UTC",
|
|
214
|
-
siteCurrencyCode: getHeader(headers, NATS_HEADER_KEYS.SITE_CURRENCY_CODE) || ""
|
|
215
|
-
};
|
|
207
|
+
const orgId = getHeader(headers, NATS_HEADER_KEYS.orgId);
|
|
208
|
+
if (!orgId) return null;
|
|
209
|
+
const parsed = {};
|
|
210
|
+
for (const [field, key] of Object.entries(NATS_HEADER_KEYS)) {
|
|
211
|
+
parsed[field] = getHeader(headers, key) || HEADER_FALLBACKS[field] || "";
|
|
212
|
+
}
|
|
213
|
+
return parsed;
|
|
216
214
|
}
|
|
217
215
|
__name(parseNatsHeaders, "parseNatsHeaders");
|
|
218
216
|
|
|
@@ -291,7 +289,7 @@ var NatsClientService = class {
|
|
|
291
289
|
this.contextResolver = contextResolver;
|
|
292
290
|
this.clients = clients;
|
|
293
291
|
}
|
|
294
|
-
// Unwraps the GraphQL { req, reply } context wrapper so
|
|
292
|
+
// Unwraps the GraphQL { req, reply } context wrapper so auth is visible across both transports
|
|
295
293
|
get request() {
|
|
296
294
|
return resolveInjectedRequest(this.injectedRequest);
|
|
297
295
|
}
|
|
@@ -304,11 +302,11 @@ var NatsClientService = class {
|
|
|
304
302
|
].join(", ")}]`);
|
|
305
303
|
}
|
|
306
304
|
if (!this.cachedContext) {
|
|
307
|
-
const
|
|
308
|
-
if (!
|
|
309
|
-
throw new Error("No
|
|
305
|
+
const context = await this.contextResolver(this.request);
|
|
306
|
+
if (!context) {
|
|
307
|
+
throw new Error("No auth context on request \u2014 is the auth guard active for this route?");
|
|
310
308
|
}
|
|
311
|
-
this.cachedContext =
|
|
309
|
+
this.cachedContext = context;
|
|
312
310
|
}
|
|
313
311
|
const headers = contextToHeaders(this.cachedContext);
|
|
314
312
|
const record = new import_microservices2.NatsRecordBuilder(data ?? {}).setHeaders(headers).build();
|
|
@@ -333,13 +331,9 @@ NatsClientService = _ts_decorate2([
|
|
|
333
331
|
], NatsClientService);
|
|
334
332
|
function contextToHeaders(ctx) {
|
|
335
333
|
const hdrs = (0, import_nats.headers)();
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
if (ctx.legalEntityId) hdrs.set(NATS_HEADER_KEYS.LE_ID, ctx.legalEntityId);
|
|
340
|
-
if (ctx.siteGroupId) hdrs.set(NATS_HEADER_KEYS.SITE_GROUP_ID, ctx.siteGroupId);
|
|
341
|
-
if (ctx.siteTimezone) hdrs.set(NATS_HEADER_KEYS.SITE_TIMEZONE, ctx.siteTimezone);
|
|
342
|
-
if (ctx.siteCurrencyCode) hdrs.set(NATS_HEADER_KEYS.SITE_CURRENCY_CODE, ctx.siteCurrencyCode);
|
|
334
|
+
for (const [field, key] of Object.entries(NATS_HEADER_KEYS)) {
|
|
335
|
+
if (ctx[field]) hdrs.set(key, ctx[field]);
|
|
336
|
+
}
|
|
343
337
|
return hdrs;
|
|
344
338
|
}
|
|
345
339
|
__name(contextToHeaders, "contextToHeaders");
|
|
@@ -382,10 +376,10 @@ var NatsMicroserviceClientService = class {
|
|
|
382
376
|
].join(", ")}]`);
|
|
383
377
|
}
|
|
384
378
|
const headers = {
|
|
385
|
-
[NATS_HEADER_KEYS.
|
|
386
|
-
[NATS_HEADER_KEYS.
|
|
387
|
-
[NATS_HEADER_KEYS.
|
|
388
|
-
[NATS_HEADER_KEYS.
|
|
379
|
+
[NATS_HEADER_KEYS.orgId]: natsHeaders2.orgId,
|
|
380
|
+
[NATS_HEADER_KEYS.userId]: natsHeaders2.userId,
|
|
381
|
+
[NATS_HEADER_KEYS.siteId]: natsHeaders2.siteId,
|
|
382
|
+
[NATS_HEADER_KEYS.siteTimezone]: natsHeaders2.siteTimezone
|
|
389
383
|
};
|
|
390
384
|
const record = new import_microservices3.NatsRecordBuilder(data ?? {}).setHeaders(headers).build();
|
|
391
385
|
return client.send({
|
|
@@ -439,7 +433,7 @@ var NatsClientModule = class _NatsClientModule {
|
|
|
439
433
|
}
|
|
440
434
|
return clients;
|
|
441
435
|
}
|
|
442
|
-
// Gateway mode — request-scoped, resolves context from
|
|
436
|
+
// Gateway mode — request-scoped, resolves context from the request via callback
|
|
443
437
|
static forRoot(asyncOptions) {
|
|
444
438
|
const optionsProvider = {
|
|
445
439
|
provide: NATS_MODULE_OPTIONS,
|
package/dist/nats.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/nats.ts","../src/filters/rpc-problem-exception.filter.ts","../src/exceptions/conflict.exception.ts","../src/exceptions/base-field.exception.ts","../src/filters/pg-error.translator.ts","../src/nats/decorators/nats-headers.decorator.ts","../src/nats/nats-context.ts","../src/nats/nats-client.module.ts","../src/nats/constants.ts","../src/nats/nats-client.service.ts","../src/context/resolve-request.ts","../src/nats/nats-microservice-client.service.ts"],"sourcesContent":["// NATS + microservice exports — kept out of the main barrel so non-NATS consumers never load @nestjs/microservices\n\n// RPC exception filter (microservice side) — moved out of the main filters barrel\nexport { RpcProblemExceptionFilter } from './filters/rpc-problem-exception.filter';\nexport { RpcSiteCurrencyCode, RpcSiteId, RpcNatsHeaders } from './nats/decorators/nats-headers.decorator';\nexport type {\n ContextResolverFn,\n NatsHeaders,\n NatsMicroserviceModuleAsyncOptions,\n NatsRootModuleAsyncOptions,\n NatsServiceConfig,\n} from './nats/index';\nexport { NatsClientModule } from './nats/nats-client.module';\nexport { NatsClientService } from './nats/nats-client.service';\nexport { NATS_HEADER_KEYS, parseNatsHeaders } from './nats/nats-context';\nexport { NatsMicroserviceClientService } from './nats/nats-microservice-client.service';\n","import { type ArgumentsHost, Catch, type HttpException, HttpStatus, Logger } from '@nestjs/common';\nimport { RpcException } from '@nestjs/microservices';\nimport { type Observable, throwError } from 'rxjs';\nimport { tryTranslatePgError } from './pg-error.translator';\n\ninterface FieldError {\n field: string;\n message: string;\n}\n\ninterface ProblemPayload {\n type: string;\n label?: string;\n detail: string;\n message: string;\n errors: FieldError[];\n status: number;\n statusCode: number;\n}\n\n@Catch()\nexport class RpcProblemExceptionFilter {\n private readonly logger = new Logger(RpcProblemExceptionFilter.name);\n\n catch(exception: unknown, _host: ArgumentsHost): Observable<never> {\n // Translate raw Postgres errors into a ConflictException before the rest of the filter handles them.\n const translatedPgError = tryTranslatePgError(exception);\n if (translatedPgError) exception = translatedPgError;\n\n if (exception instanceof RpcException) {\n return throwError(() => exception.getError());\n }\n\n if (this.isHttpException(exception)) {\n const status = exception.getStatus();\n const response = exception.getResponse();\n const payload = this.toProblemPayload(response, status);\n // Log 4xx/5xx so the microservice terminal shows what actually failed (previously silent)\n const log = status >= HttpStatus.INTERNAL_SERVER_ERROR ? this.logger.error : this.logger.warn;\n log.call(this.logger, `RPC ${status}: ${payload.detail}`);\n return throwError(() => payload);\n }\n\n if (exception instanceof Error) {\n const cause = (exception as { cause?: unknown }).cause;\n const causeMessage = cause instanceof Error ? cause.message : undefined;\n const causeStack = cause instanceof Error ? cause.stack : undefined;\n this.logger.error(causeMessage ?? exception.message, causeStack ?? exception.stack);\n if (cause && cause !== exception) {\n this.logger.error(`Wrapped by: ${exception.message}`);\n }\n return throwError(() =>\n this.toProblemPayload(\n {\n type: 'about:blank',\n detail: causeMessage ?? exception.message,\n errors: [],\n },\n HttpStatus.INTERNAL_SERVER_ERROR,\n ),\n );\n }\n\n this.logger.error(`Unhandled non-error exception: ${JSON.stringify(exception)}`);\n return throwError(() =>\n this.toProblemPayload(\n {\n type: 'about:blank',\n detail: 'An unexpected error occurred',\n errors: [],\n },\n HttpStatus.INTERNAL_SERVER_ERROR,\n ),\n );\n }\n\n private toProblemPayload(response: unknown, status: number): ProblemPayload {\n if (typeof response === 'string') {\n return {\n type: 'about:blank',\n detail: response,\n message: response,\n errors: [],\n status,\n statusCode: status,\n };\n }\n\n const obj = (response ?? {}) as Record<string, unknown>;\n // Prefer RFC 9457 `detail`, then Nest/@nestjs-common's `message` (string or class-validator array),\n // so exceptions thrown as `@nestjs/common` errors still surface their real message across NATS.\n const detail =\n typeof obj.detail === 'string'\n ? obj.detail\n : typeof obj.message === 'string'\n ? obj.message\n : Array.isArray(obj.message)\n ? obj.message.join(', ')\n : 'Request failed';\n return {\n type: typeof obj.type === 'string' ? obj.type : 'about:blank',\n label: typeof obj.label === 'string' ? obj.label : undefined,\n detail,\n message: detail,\n errors: Array.isArray(obj.errors) ? (obj.errors as FieldError[]) : [],\n status,\n statusCode: status,\n };\n }\n\n private isHttpException(error: unknown): error is HttpException {\n return (\n error instanceof Error &&\n typeof (error as { getStatus?: unknown }).getStatus === 'function' &&\n typeof (error as { getResponse?: unknown }).getResponse === 'function'\n );\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { HttpProblemException, type ProblemOptions } from './base-field.exception';\n\nexport class ConflictException extends HttpProblemException {\n constructor(detailOrOptions?: string | ProblemOptions) {\n super(detailOrOptions ?? 'Conflict', HttpStatus.CONFLICT);\n }\n}\n","import { HttpException, HttpStatus } from '@nestjs/common';\nimport type { FieldError } from '../types/error-response.types';\n\n// Re-export FieldError for backwards compatibility\nexport type { FieldError } from '../types/error-response.types';\n\nexport interface ProblemOptions {\n type?: string;\n label?: string;\n detail?: string;\n errors?: FieldError[];\n}\n\nexport abstract class HttpProblemException extends HttpException {\n constructor(detailOrOptions: string | ProblemOptions, httpStatus: HttpStatus) {\n const options = typeof detailOrOptions === 'string' ? { detail: detailOrOptions } : detailOrOptions;\n\n super(\n {\n type: options.type ?? 'about:blank',\n label: options.label,\n detail: options.detail,\n errors: options.errors ?? [],\n },\n httpStatus,\n );\n }\n}\n","import { ConflictException } from '../exceptions/conflict.exception';\n\nconst PG_UNIQUE_VIOLATION = '23505';\n\nexport type PgErrorShape = {\n code?: string;\n message?: string;\n constraint?: string;\n table?: string;\n schema?: string;\n column?: string;\n detail?: string;\n hint?: string;\n};\n\n// Returns a ConflictException for a Postgres unique-violation error, otherwise undefined.\nexport function tryTranslatePgError(error: unknown): ConflictException | undefined {\n const pgError = findPgError(error);\n if (!pgError) return undefined;\n const { code, constraint, table, detail } = pgError;\n if (code !== PG_UNIQUE_VIOLATION) return undefined;\n return new ConflictException({\n label: 'Duplicate Entry',\n detail: detail?.trim() || 'A record with these values already exists.',\n errors: [],\n ...(constraint || table ? { meta: { constraint, table } } : {}),\n });\n}\n\n// Walks up to N levels of `.cause` looking for a pg-shaped error (has SQLSTATE `code`), capping depth.\nexport function findPgError(error: unknown, depth = 0): PgErrorShape | undefined {\n if (!error || typeof error !== 'object' || depth > 5) return undefined;\n const candidate = error as PgErrorShape & { cause?: unknown };\n if (typeof candidate.code === 'string') return candidate;\n return findPgError(candidate.cause, depth + 1);\n}\n","import { createParamDecorator, type ExecutionContext, InternalServerErrorException } from '@nestjs/common';\nimport { NatsContext } from '@nestjs/microservices';\nimport { parseNatsHeaders } from '../nats-context';\n\n// Extracts parsed NatsContext from NATS message headers\nexport const RpcNatsHeaders = createParamDecorator((_data: unknown, ctx: ExecutionContext) => {\n const rpcCtx = ctx.switchToRpc().getContext<NatsContext>();\n return parseNatsHeaders(rpcCtx.getHeaders());\n});\n\n// Extracts siteId from NATS headers — throws if missing or empty\nexport const RpcSiteId = createParamDecorator((_data: unknown, ctx: ExecutionContext): string => {\n const rpcCtx = ctx.switchToRpc().getContext<NatsContext>();\n const headers = parseNatsHeaders(rpcCtx.getHeaders());\n if (!headers?.siteId) throw new InternalServerErrorException('Missing siteId in NATS headers.');\n return headers.siteId;\n});\n\n// Extracts siteCurrencyCode from NATS headers — throws if missing or empty\nexport const RpcSiteCurrencyCode = createParamDecorator((_data: unknown, ctx: ExecutionContext): string => {\n const rpcCtx = ctx.switchToRpc().getContext<NatsContext>();\n const headers = parseNatsHeaders(rpcCtx.getHeaders());\n if (!headers?.siteCurrencyCode) throw new InternalServerErrorException('Missing siteCurrencyCode in NATS headers.');\n return headers.siteCurrencyCode;\n});\n","export interface NatsHeaders {\n orgId: string;\n userId: string;\n siteId: string;\n legalEntityId: string;\n siteGroupId: string;\n siteTimezone: string;\n siteCurrencyCode: string;\n}\n\n// Header keys for NATS context transport\nexport const NATS_HEADER_KEYS = {\n ORG_ID: 'x-org-id',\n USER_ID: 'x-user-id',\n SITE_ID: 'x-site-id',\n LE_ID: 'x-le-id',\n SITE_GROUP_ID: 'x-sg-id',\n SITE_TIMEZONE: 'x-site-timezone',\n SITE_CURRENCY_CODE: 'x-site-currency-code',\n} as const;\n\n// Reads a header value from either a plain object or a NATS MsgHdrsImpl\nfunction getHeader(headers: unknown, key: string): string | undefined {\n if (!headers) return undefined;\n // MsgHdrsImpl uses .get(), plain objects use bracket access\n if (typeof (headers as { get?: unknown }).get === 'function') {\n const val = (headers as { get(key: string): string[] }).get(key);\n return Array.isArray(val) ? val[0] : (val as string | undefined);\n }\n return (headers as Record<string, string>)[key];\n}\n\n// Parses NATS message headers into a NatsHeaders object — siteId is optional (empty for org-level contexts)\nexport function parseNatsHeaders(headers: unknown): NatsHeaders | null {\n if (!headers) return null;\n\n const orgId = getHeader(headers, NATS_HEADER_KEYS.ORG_ID);\n const userId = getHeader(headers, NATS_HEADER_KEYS.USER_ID);\n\n if (!orgId || !userId) return null;\n\n return {\n orgId,\n userId,\n siteId: getHeader(headers, NATS_HEADER_KEYS.SITE_ID) || '',\n legalEntityId: getHeader(headers, NATS_HEADER_KEYS.LE_ID) || '',\n siteGroupId: getHeader(headers, NATS_HEADER_KEYS.SITE_GROUP_ID) || '',\n siteTimezone: getHeader(headers, NATS_HEADER_KEYS.SITE_TIMEZONE) || 'UTC',\n siteCurrencyCode: getHeader(headers, NATS_HEADER_KEYS.SITE_CURRENCY_CODE) || '',\n };\n}\n","import { type DynamicModule, Global, Logger, Module, type OnModuleDestroy, type Provider } from '@nestjs/common';\nimport { ConfigModule, ConfigService } from '@nestjs/config';\nimport type { ClientProxy } from '@nestjs/microservices';\nimport { ClientProxyFactory, Transport } from '@nestjs/microservices';\nimport { NATS_CONTEXT_RESOLVER, NATS_MODULE_OPTIONS } from './constants';\nimport type {\n NatsMicroserviceModuleAsyncOptions,\n NatsModuleBaseOptions,\n NatsRootModuleAsyncOptions,\n NatsRootModuleOptions,\n} from './nats-client.interfaces';\nimport { NATS_CLIENTS, NatsClientService } from './nats-client.service';\nimport { NATS_MS_CLIENTS, NatsMicroserviceClientService } from './nats-microservice-client.service';\n\nconst NATS_MS_OPTIONS = Symbol('NATS_MS_OPTIONS');\n\n@Global()\n@Module({})\nexport class NatsClientModule implements OnModuleDestroy {\n private static readonly logger = new Logger(NatsClientModule.name);\n private static allClients: ClientProxy[] = [];\n\n async onModuleDestroy() {\n await Promise.all(NatsClientModule.allClients.map((c) => c.close()));\n NatsClientModule.allClients = [];\n }\n\n // Builds a Map of named NATS ClientProxy instances\n private static buildClients(options: NatsModuleBaseOptions, natsUrl: string): Map<string, ClientProxy> {\n const clients = new Map<string, ClientProxy>();\n\n for (const svc of options.services) {\n const proxy = ClientProxyFactory.create({\n transport: Transport.NATS,\n options: { servers: [natsUrl] },\n });\n clients.set(svc.name, proxy);\n NatsClientModule.allClients.push(proxy);\n NatsClientModule.logger.log(`Registered NATS client: ${svc.name} → ${natsUrl}`);\n }\n\n return clients;\n }\n\n // Gateway mode — request-scoped, resolves context from sessionInfo via callback\n static forRoot(asyncOptions: NatsRootModuleAsyncOptions): DynamicModule {\n const optionsProvider: Provider = {\n provide: NATS_MODULE_OPTIONS,\n useFactory: asyncOptions.useFactory,\n inject: asyncOptions.inject || [],\n };\n\n const resolverProvider: Provider = {\n provide: NATS_CONTEXT_RESOLVER,\n useFactory: (options: NatsRootModuleOptions) => options.contextResolver,\n inject: [NATS_MODULE_OPTIONS],\n };\n\n const clientsProvider: Provider = {\n provide: NATS_CLIENTS,\n useFactory: (options: NatsRootModuleOptions, config: ConfigService): Map<string, ClientProxy> => {\n const natsUrl = options.natsUrl ?? config.get<string>('NATS_URL', 'nats://localhost:4222');\n return NatsClientModule.buildClients(options, natsUrl);\n },\n inject: [NATS_MODULE_OPTIONS, ConfigService],\n };\n\n return {\n module: NatsClientModule,\n imports: [ConfigModule, ...(asyncOptions.imports ?? [])],\n providers: [optionsProvider, resolverProvider, clientsProvider, NatsClientService],\n exports: [NatsClientService],\n };\n }\n\n // Microservice mode — singleton, forwards context from incoming NATS payload\n static forMicroservice(asyncOptions: NatsMicroserviceModuleAsyncOptions): DynamicModule {\n const optionsProvider: Provider = {\n provide: NATS_MS_OPTIONS,\n useFactory: asyncOptions.useFactory,\n inject: asyncOptions.inject || [],\n };\n\n const clientsProvider: Provider = {\n provide: NATS_MS_CLIENTS,\n useFactory: (options: NatsModuleBaseOptions, config: ConfigService): Map<string, ClientProxy> => {\n const natsUrl = options.natsUrl ?? config.get<string>('NATS_URL', 'nats://localhost:4222');\n return NatsClientModule.buildClients(options, natsUrl);\n },\n inject: [NATS_MS_OPTIONS, ConfigService],\n };\n\n return {\n module: NatsClientModule,\n imports: [ConfigModule, ...(asyncOptions.imports ?? [])],\n providers: [optionsProvider, clientsProvider, NatsMicroserviceClientService],\n exports: [NatsMicroserviceClientService],\n };\n }\n}\n","export const NATS_MODULE_OPTIONS = Symbol('NATS_MODULE_OPTIONS');\nexport const NATS_CONTEXT_RESOLVER = Symbol('NATS_CONTEXT_RESOLVER');\n","// Pulls the fastify module augmentation into this entry's dts graph — tsup builds each entry in isolation\nimport '../types/fastify-augmentation';\nimport { Inject, Injectable, Scope } from '@nestjs/common';\nimport { REQUEST } from '@nestjs/core';\nimport type { ClientProxy } from '@nestjs/microservices';\nimport { NatsRecordBuilder } from '@nestjs/microservices';\nimport type { FastifyRequest } from 'fastify';\nimport { headers as natsHeaders } from 'nats';\nimport { resolveInjectedRequest } from '../context/resolve-request';\nimport { NATS_CONTEXT_RESOLVER } from './constants';\nimport type { ContextResolverFn } from './nats-client.interfaces';\nimport { NATS_HEADER_KEYS, type NatsHeaders } from './nats-context';\n\nexport const NATS_CLIENTS = Symbol('NATS_CLIENTS');\n\n@Injectable({ scope: Scope.REQUEST })\nexport class NatsClientService {\n private cachedContext: NatsHeaders | null = null;\n\n constructor(\n @Inject(REQUEST) private readonly injectedRequest: FastifyRequest,\n @Inject(NATS_CONTEXT_RESOLVER) private readonly contextResolver: ContextResolverFn,\n @Inject(NATS_CLIENTS) private readonly clients: Map<string, ClientProxy>,\n ) {}\n\n // Unwraps the GraphQL { req, reply } context wrapper so sessionInfo is visible across both transports\n private get request(): FastifyRequest {\n return resolveInjectedRequest(this.injectedRequest);\n }\n\n // Sends a message to a named microservice with NatsHeaders as NATS headers\n async send<T>(service: string, cmd: string, data?: object): Promise<T> {\n const client = this.clients.get(service);\n if (!client) {\n throw new Error(\n `NATS service \"${service}\" is not registered. Available: [${[...this.clients.keys()].join(', ')}]`,\n );\n }\n\n if (!this.cachedContext) {\n const sessionInfo = this.request.sessionInfo;\n if (!sessionInfo) {\n throw new Error('No sessionInfo on request — is the auth guard active?');\n }\n this.cachedContext = await this.contextResolver(sessionInfo);\n }\n\n const headers = contextToHeaders(this.cachedContext);\n const record = new NatsRecordBuilder(data ?? {}).setHeaders(headers).build();\n\n return client.send<T>({ cmd }, record).toPromise() as Promise<T>;\n }\n}\n\n// Converts NatsHeaders to a NATS MsgHdrs object for NATS transport\nfunction contextToHeaders(ctx: NatsHeaders): import('nats').MsgHdrs {\n const hdrs = natsHeaders();\n hdrs.set(NATS_HEADER_KEYS.ORG_ID, ctx.orgId);\n hdrs.set(NATS_HEADER_KEYS.USER_ID, ctx.userId);\n if (ctx.siteId) hdrs.set(NATS_HEADER_KEYS.SITE_ID, ctx.siteId);\n if (ctx.legalEntityId) hdrs.set(NATS_HEADER_KEYS.LE_ID, ctx.legalEntityId);\n if (ctx.siteGroupId) hdrs.set(NATS_HEADER_KEYS.SITE_GROUP_ID, ctx.siteGroupId);\n if (ctx.siteTimezone) hdrs.set(NATS_HEADER_KEYS.SITE_TIMEZONE, ctx.siteTimezone);\n if (ctx.siteCurrencyCode) hdrs.set(NATS_HEADER_KEYS.SITE_CURRENCY_CODE, ctx.siteCurrencyCode);\n return hdrs;\n}\n","import type { FastifyRequest } from 'fastify';\n\n// Unwraps the @Inject(REQUEST) value to the real Fastify request (GraphQL injects a { req, reply } context).\nexport function resolveInjectedRequest(injected: FastifyRequest): FastifyRequest {\n const candidate = injected as unknown as { headers?: unknown; req?: FastifyRequest };\n if (candidate && candidate.headers === undefined && candidate.req) {\n return candidate.req;\n }\n return injected;\n}\n","import { Inject, Injectable } from '@nestjs/common';\nimport type { ClientProxy } from '@nestjs/microservices';\nimport { NatsRecordBuilder } from '@nestjs/microservices';\nimport { NATS_HEADER_KEYS, type NatsHeaders } from './nats-context';\n\nexport const NATS_MS_CLIENTS = Symbol('NATS_MS_CLIENTS');\n\n@Injectable()\nexport class NatsMicroserviceClientService {\n constructor(@Inject(NATS_MS_CLIENTS) private readonly clients: Map<string, ClientProxy>) {}\n\n // Forwards a message to another microservice with NatsHeaders\n async send<T>(service: string, cmd: string, natsHeaders: NatsHeaders, data?: object): Promise<T> {\n const client = this.clients.get(service);\n if (!client) {\n throw new Error(\n `NATS service \"${service}\" is not registered. Available: [${[...this.clients.keys()].join(', ')}]`,\n );\n }\n\n const headers: Record<string, string> = {\n [NATS_HEADER_KEYS.ORG_ID]: natsHeaders.orgId,\n [NATS_HEADER_KEYS.USER_ID]: natsHeaders.userId,\n [NATS_HEADER_KEYS.SITE_ID]: natsHeaders.siteId,\n [NATS_HEADER_KEYS.SITE_TIMEZONE]: natsHeaders.siteTimezone,\n };\n\n const record = new NatsRecordBuilder(data ?? {}).setHeaders(headers).build();\n return client.send<T>({ cmd }, record).toPromise() as Promise<T>;\n }\n}\n"],"mappings":"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","names":["import_common","import_common","HttpProblemException","HttpException","detailOrOptions","httpStatus","options","detail","type","label","errors","ConflictException","HttpProblemException","detailOrOptions","HttpStatus","CONFLICT","PG_UNIQUE_VIOLATION","tryTranslatePgError","error","pgError","findPgError","undefined","code","constraint","table","detail","ConflictException","label","trim","errors","meta","depth","candidate","cause","RpcProblemExceptionFilter","logger","Logger","name","catch","exception","_host","translatedPgError","tryTranslatePgError","RpcException","throwError","getError","isHttpException","status","getStatus","response","getResponse","payload","toProblemPayload","log","HttpStatus","INTERNAL_SERVER_ERROR","error","warn","call","detail","Error","cause","causeMessage","message","undefined","causeStack","stack","type","errors","JSON","stringify","statusCode","obj","Array","isArray","join","label","import_common","NATS_HEADER_KEYS","ORG_ID","USER_ID","SITE_ID","LE_ID","SITE_GROUP_ID","SITE_TIMEZONE","SITE_CURRENCY_CODE","getHeader","headers","key","undefined","get","val","Array","isArray","parseNatsHeaders","orgId","userId","siteId","legalEntityId","siteGroupId","siteTimezone","siteCurrencyCode","RpcNatsHeaders","createParamDecorator","_data","ctx","rpcCtx","switchToRpc","getContext","parseNatsHeaders","getHeaders","RpcSiteId","headers","siteId","InternalServerErrorException","RpcSiteCurrencyCode","siteCurrencyCode","import_common","import_microservices","NATS_MODULE_OPTIONS","Symbol","NATS_CONTEXT_RESOLVER","import_common","import_microservices","resolveInjectedRequest","injected","candidate","headers","undefined","req","NATS_CLIENTS","Symbol","NatsClientService","cachedContext","injectedRequest","contextResolver","clients","request","resolveInjectedRequest","send","service","cmd","data","client","get","Error","keys","join","sessionInfo","headers","contextToHeaders","record","NatsRecordBuilder","setHeaders","build","toPromise","scope","Scope","REQUEST","ctx","hdrs","natsHeaders","set","NATS_HEADER_KEYS","ORG_ID","orgId","USER_ID","userId","siteId","SITE_ID","legalEntityId","LE_ID","siteGroupId","SITE_GROUP_ID","siteTimezone","SITE_TIMEZONE","siteCurrencyCode","SITE_CURRENCY_CODE","import_common","import_microservices","NATS_MS_CLIENTS","Symbol","NatsMicroserviceClientService","clients","send","service","cmd","natsHeaders","data","client","get","Error","keys","join","headers","NATS_HEADER_KEYS","ORG_ID","orgId","USER_ID","userId","SITE_ID","siteId","SITE_TIMEZONE","siteTimezone","record","NatsRecordBuilder","setHeaders","build","toPromise","NATS_MS_OPTIONS","Symbol","NatsClientModule","logger","Logger","name","allClients","onModuleDestroy","Promise","all","map","c","close","buildClients","options","natsUrl","clients","Map","svc","services","proxy","ClientProxyFactory","create","transport","Transport","NATS","servers","set","push","log","forRoot","asyncOptions","optionsProvider","provide","NATS_MODULE_OPTIONS","useFactory","inject","resolverProvider","NATS_CONTEXT_RESOLVER","contextResolver","clientsProvider","NATS_CLIENTS","config","get","ConfigService","module","imports","ConfigModule","providers","NatsClientService","exports","forMicroservice","NATS_MS_CLIENTS","NatsMicroserviceClientService"]}
|
|
1
|
+
{"version":3,"sources":["../src/nats.ts","../src/filters/rpc-problem-exception.filter.ts","../src/exceptions/conflict.exception.ts","../src/exceptions/base-field.exception.ts","../src/filters/pg-error.translator.ts","../src/nats/decorators/nats-headers.decorator.ts","../src/nats/nats-context.ts","../src/nats/nats-client.module.ts","../src/nats/constants.ts","../src/nats/nats-client.service.ts","../src/context/resolve-request.ts","../src/nats/nats-microservice-client.service.ts"],"sourcesContent":["// NATS + microservice exports — kept out of the main barrel so non-NATS consumers never load @nestjs/microservices\n\n// RPC exception filter (microservice side) — moved out of the main filters barrel\nexport { RpcProblemExceptionFilter } from './filters/rpc-problem-exception.filter';\nexport { RpcSiteCurrencyCode, RpcSiteId, RpcNatsHeaders } from './nats/decorators/nats-headers.decorator';\nexport type {\n ContextResolverFn,\n NatsHeaders,\n NatsMicroserviceModuleAsyncOptions,\n NatsRootModuleAsyncOptions,\n NatsServiceConfig,\n} from './nats/index';\nexport { NatsClientModule } from './nats/nats-client.module';\nexport { NatsClientService } from './nats/nats-client.service';\nexport { NATS_HEADER_KEYS, parseNatsHeaders } from './nats/nats-context';\nexport { NatsMicroserviceClientService } from './nats/nats-microservice-client.service';\n","import { type ArgumentsHost, Catch, type HttpException, HttpStatus, Logger } from '@nestjs/common';\nimport { RpcException } from '@nestjs/microservices';\nimport { type Observable, throwError } from 'rxjs';\nimport { tryTranslatePgError } from './pg-error.translator';\n\ninterface FieldError {\n field: string;\n message: string;\n}\n\ninterface ProblemPayload {\n type: string;\n label?: string;\n detail: string;\n message: string;\n errors: FieldError[];\n status: number;\n statusCode: number;\n}\n\n@Catch()\nexport class RpcProblemExceptionFilter {\n private readonly logger = new Logger(RpcProblemExceptionFilter.name);\n\n catch(exception: unknown, _host: ArgumentsHost): Observable<never> {\n // Translate raw Postgres errors into a ConflictException before the rest of the filter handles them.\n const translatedPgError = tryTranslatePgError(exception);\n if (translatedPgError) exception = translatedPgError;\n\n if (exception instanceof RpcException) {\n return throwError(() => exception.getError());\n }\n\n if (this.isHttpException(exception)) {\n const status = exception.getStatus();\n const response = exception.getResponse();\n const payload = this.toProblemPayload(response, status);\n // Log 4xx/5xx so the microservice terminal shows what actually failed (previously silent)\n const log = status >= HttpStatus.INTERNAL_SERVER_ERROR ? this.logger.error : this.logger.warn;\n log.call(this.logger, `RPC ${status}: ${payload.detail}`);\n return throwError(() => payload);\n }\n\n if (exception instanceof Error) {\n const cause = (exception as { cause?: unknown }).cause;\n const causeMessage = cause instanceof Error ? cause.message : undefined;\n const causeStack = cause instanceof Error ? cause.stack : undefined;\n this.logger.error(causeMessage ?? exception.message, causeStack ?? exception.stack);\n if (cause && cause !== exception) {\n this.logger.error(`Wrapped by: ${exception.message}`);\n }\n return throwError(() =>\n this.toProblemPayload(\n {\n type: 'about:blank',\n detail: causeMessage ?? exception.message,\n errors: [],\n },\n HttpStatus.INTERNAL_SERVER_ERROR,\n ),\n );\n }\n\n this.logger.error(`Unhandled non-error exception: ${JSON.stringify(exception)}`);\n return throwError(() =>\n this.toProblemPayload(\n {\n type: 'about:blank',\n detail: 'An unexpected error occurred',\n errors: [],\n },\n HttpStatus.INTERNAL_SERVER_ERROR,\n ),\n );\n }\n\n private toProblemPayload(response: unknown, status: number): ProblemPayload {\n if (typeof response === 'string') {\n return {\n type: 'about:blank',\n detail: response,\n message: response,\n errors: [],\n status,\n statusCode: status,\n };\n }\n\n const obj = (response ?? {}) as Record<string, unknown>;\n // Prefer RFC 9457 `detail`, then Nest/@nestjs-common's `message` (string or class-validator array),\n // so exceptions thrown as `@nestjs/common` errors still surface their real message across NATS.\n const detail =\n typeof obj.detail === 'string'\n ? obj.detail\n : typeof obj.message === 'string'\n ? obj.message\n : Array.isArray(obj.message)\n ? obj.message.join(', ')\n : 'Request failed';\n return {\n type: typeof obj.type === 'string' ? obj.type : 'about:blank',\n label: typeof obj.label === 'string' ? obj.label : undefined,\n detail,\n message: detail,\n errors: Array.isArray(obj.errors) ? (obj.errors as FieldError[]) : [],\n status,\n statusCode: status,\n };\n }\n\n private isHttpException(error: unknown): error is HttpException {\n return (\n error instanceof Error &&\n typeof (error as { getStatus?: unknown }).getStatus === 'function' &&\n typeof (error as { getResponse?: unknown }).getResponse === 'function'\n );\n }\n}\n","import { HttpStatus } from '@nestjs/common';\nimport { HttpProblemException, type ProblemOptions } from './base-field.exception';\n\nexport class ConflictException extends HttpProblemException {\n constructor(detailOrOptions?: string | ProblemOptions) {\n super(detailOrOptions ?? 'Conflict', HttpStatus.CONFLICT);\n }\n}\n","import { HttpException, HttpStatus } from '@nestjs/common';\nimport type { FieldError } from '../types/error-response.types';\n\n// Re-export FieldError for backwards compatibility\nexport type { FieldError } from '../types/error-response.types';\n\nexport interface ProblemOptions {\n type?: string;\n label?: string;\n detail?: string;\n errors?: FieldError[];\n}\n\nexport abstract class HttpProblemException extends HttpException {\n constructor(detailOrOptions: string | ProblemOptions, httpStatus: HttpStatus) {\n const options = typeof detailOrOptions === 'string' ? { detail: detailOrOptions } : detailOrOptions;\n\n super(\n {\n type: options.type ?? 'about:blank',\n label: options.label,\n detail: options.detail,\n errors: options.errors ?? [],\n },\n httpStatus,\n );\n }\n}\n","import { ConflictException } from '../exceptions/conflict.exception';\n\nconst PG_UNIQUE_VIOLATION = '23505';\n\nexport type PgErrorShape = {\n code?: string;\n message?: string;\n constraint?: string;\n table?: string;\n schema?: string;\n column?: string;\n detail?: string;\n hint?: string;\n};\n\n// Returns a ConflictException for a Postgres unique-violation error, otherwise undefined.\nexport function tryTranslatePgError(error: unknown): ConflictException | undefined {\n const pgError = findPgError(error);\n if (!pgError) return undefined;\n const { code, constraint, table, detail } = pgError;\n if (code !== PG_UNIQUE_VIOLATION) return undefined;\n return new ConflictException({\n label: 'Duplicate Entry',\n detail: detail?.trim() || 'A record with these values already exists.',\n errors: [],\n ...(constraint || table ? { meta: { constraint, table } } : {}),\n });\n}\n\n// Walks up to N levels of `.cause` looking for a pg-shaped error (has SQLSTATE `code`), capping depth.\nexport function findPgError(error: unknown, depth = 0): PgErrorShape | undefined {\n if (!error || typeof error !== 'object' || depth > 5) return undefined;\n const candidate = error as PgErrorShape & { cause?: unknown };\n if (typeof candidate.code === 'string') return candidate;\n return findPgError(candidate.cause, depth + 1);\n}\n","import { createParamDecorator, type ExecutionContext, InternalServerErrorException } from '@nestjs/common';\nimport { NatsContext } from '@nestjs/microservices';\nimport { parseNatsHeaders } from '../nats-context';\n\n// Extracts parsed NatsContext from NATS message headers\nexport const RpcNatsHeaders = createParamDecorator((_data: unknown, ctx: ExecutionContext) => {\n const rpcCtx = ctx.switchToRpc().getContext<NatsContext>();\n return parseNatsHeaders(rpcCtx.getHeaders());\n});\n\n// Extracts siteId from NATS headers — throws if missing or empty\nexport const RpcSiteId = createParamDecorator((_data: unknown, ctx: ExecutionContext): string => {\n const rpcCtx = ctx.switchToRpc().getContext<NatsContext>();\n const headers = parseNatsHeaders(rpcCtx.getHeaders());\n if (!headers?.siteId) throw new InternalServerErrorException('Missing siteId in NATS headers.');\n return headers.siteId;\n});\n\n// Extracts siteCurrencyCode from NATS headers — throws if missing or empty\nexport const RpcSiteCurrencyCode = createParamDecorator((_data: unknown, ctx: ExecutionContext): string => {\n const rpcCtx = ctx.switchToRpc().getContext<NatsContext>();\n const headers = parseNatsHeaders(rpcCtx.getHeaders());\n if (!headers?.siteCurrencyCode) throw new InternalServerErrorException('Missing siteCurrencyCode in NATS headers.');\n return headers.siteCurrencyCode;\n});\n","export interface NatsHeaders {\n orgId: string;\n userId: string;\n siteId: string;\n legalEntityId: string;\n siteGroupId: string;\n siteTimezone: string;\n siteCurrencyCode: string;\n}\n\n// The single source of truth for context transport, both directions. Adding a field means\n// adding one entry here plus one on NatsHeaders — encode and parse both pick it up.\nexport const NATS_HEADER_KEYS = {\n orgId: 'x-org-id',\n userId: 'x-user-id',\n siteId: 'x-site-id',\n legalEntityId: 'x-le-id',\n siteGroupId: 'x-sg-id',\n siteTimezone: 'x-site-timezone',\n siteCurrencyCode: 'x-site-currency-code',\n} as const satisfies Record<keyof NatsHeaders, string>;\n\n// Applied when a header is absent, so a missing optional field lands on the same value the\n// producing side would have sent for \"not set\"\nconst HEADER_FALLBACKS: Partial<Record<keyof NatsHeaders, string>> = {\n siteTimezone: 'UTC',\n};\n\n// Reads a header value from either a plain object or a NATS MsgHdrsImpl\nfunction getHeader(headers: unknown, key: string): string | undefined {\n if (!headers) return undefined;\n // MsgHdrsImpl uses .get(), plain objects use bracket access\n if (typeof (headers as { get?: unknown }).get === 'function') {\n const val = (headers as { get(key: string): string[] }).get(key);\n return Array.isArray(val) ? val[0] : (val as string | undefined);\n }\n return (headers as Record<string, string>)[key];\n}\n\n/**\n * Parses NATS message headers into a NatsHeaders object.\n *\n * Only `orgId` is required. It is what scopes every row the receiving service will read, so\n * without it there is no safe way to run — the caller returns null and the interceptor decides.\n *\n * `userId` is deliberately NOT required. A control-plane call has no user behind it, and an\n * earlier version rejected the whole context when it was blank — which meant the receiving\n * service skipped RLS entirely, ran with `app.org_id` unset, and matched no rows. Losing the\n * tenant because there was no user is a far worse failure than an empty acting principal.\n */\nexport function parseNatsHeaders(headers: unknown): NatsHeaders | null {\n if (!headers) return null;\n\n const orgId = getHeader(headers, NATS_HEADER_KEYS.orgId);\n if (!orgId) return null;\n\n const parsed = {} as NatsHeaders;\n for (const [field, key] of Object.entries(NATS_HEADER_KEYS) as [keyof NatsHeaders, string][]) {\n parsed[field] = getHeader(headers, key) || HEADER_FALLBACKS[field] || '';\n }\n return parsed;\n}\n","import { type DynamicModule, Global, Logger, Module, type OnModuleDestroy, type Provider } from '@nestjs/common';\nimport { ConfigModule, ConfigService } from '@nestjs/config';\nimport type { ClientProxy } from '@nestjs/microservices';\nimport { ClientProxyFactory, Transport } from '@nestjs/microservices';\nimport { NATS_CONTEXT_RESOLVER, NATS_MODULE_OPTIONS } from './constants';\nimport type {\n NatsMicroserviceModuleAsyncOptions,\n NatsModuleBaseOptions,\n NatsRootModuleAsyncOptions,\n NatsRootModuleOptions,\n} from './nats-client.interfaces';\nimport { NATS_CLIENTS, NatsClientService } from './nats-client.service';\nimport { NATS_MS_CLIENTS, NatsMicroserviceClientService } from './nats-microservice-client.service';\n\nconst NATS_MS_OPTIONS = Symbol('NATS_MS_OPTIONS');\n\n@Global()\n@Module({})\nexport class NatsClientModule implements OnModuleDestroy {\n private static readonly logger = new Logger(NatsClientModule.name);\n private static allClients: ClientProxy[] = [];\n\n async onModuleDestroy() {\n await Promise.all(NatsClientModule.allClients.map((c) => c.close()));\n NatsClientModule.allClients = [];\n }\n\n // Builds a Map of named NATS ClientProxy instances\n private static buildClients(options: NatsModuleBaseOptions, natsUrl: string): Map<string, ClientProxy> {\n const clients = new Map<string, ClientProxy>();\n\n for (const svc of options.services) {\n const proxy = ClientProxyFactory.create({\n transport: Transport.NATS,\n options: { servers: [natsUrl] },\n });\n clients.set(svc.name, proxy);\n NatsClientModule.allClients.push(proxy);\n NatsClientModule.logger.log(`Registered NATS client: ${svc.name} → ${natsUrl}`);\n }\n\n return clients;\n }\n\n // Gateway mode — request-scoped, resolves context from the request via callback\n static forRoot(asyncOptions: NatsRootModuleAsyncOptions): DynamicModule {\n const optionsProvider: Provider = {\n provide: NATS_MODULE_OPTIONS,\n useFactory: asyncOptions.useFactory,\n inject: asyncOptions.inject || [],\n };\n\n const resolverProvider: Provider = {\n provide: NATS_CONTEXT_RESOLVER,\n useFactory: (options: NatsRootModuleOptions) => options.contextResolver,\n inject: [NATS_MODULE_OPTIONS],\n };\n\n const clientsProvider: Provider = {\n provide: NATS_CLIENTS,\n useFactory: (options: NatsRootModuleOptions, config: ConfigService): Map<string, ClientProxy> => {\n const natsUrl = options.natsUrl ?? config.get<string>('NATS_URL', 'nats://localhost:4222');\n return NatsClientModule.buildClients(options, natsUrl);\n },\n inject: [NATS_MODULE_OPTIONS, ConfigService],\n };\n\n return {\n module: NatsClientModule,\n imports: [ConfigModule, ...(asyncOptions.imports ?? [])],\n providers: [optionsProvider, resolverProvider, clientsProvider, NatsClientService],\n exports: [NatsClientService],\n };\n }\n\n // Microservice mode — singleton, forwards context from incoming NATS payload\n static forMicroservice(asyncOptions: NatsMicroserviceModuleAsyncOptions): DynamicModule {\n const optionsProvider: Provider = {\n provide: NATS_MS_OPTIONS,\n useFactory: asyncOptions.useFactory,\n inject: asyncOptions.inject || [],\n };\n\n const clientsProvider: Provider = {\n provide: NATS_MS_CLIENTS,\n useFactory: (options: NatsModuleBaseOptions, config: ConfigService): Map<string, ClientProxy> => {\n const natsUrl = options.natsUrl ?? config.get<string>('NATS_URL', 'nats://localhost:4222');\n return NatsClientModule.buildClients(options, natsUrl);\n },\n inject: [NATS_MS_OPTIONS, ConfigService],\n };\n\n return {\n module: NatsClientModule,\n imports: [ConfigModule, ...(asyncOptions.imports ?? [])],\n providers: [optionsProvider, clientsProvider, NatsMicroserviceClientService],\n exports: [NatsMicroserviceClientService],\n };\n }\n}\n","export const NATS_MODULE_OPTIONS = Symbol('NATS_MODULE_OPTIONS');\nexport const NATS_CONTEXT_RESOLVER = Symbol('NATS_CONTEXT_RESOLVER');\n","// Pulls the fastify module augmentation into this entry's dts graph — tsup builds each entry in isolation\nimport '../types/fastify-augmentation';\nimport { Inject, Injectable, Scope } from '@nestjs/common';\nimport { REQUEST } from '@nestjs/core';\nimport type { ClientProxy } from '@nestjs/microservices';\nimport { NatsRecordBuilder } from '@nestjs/microservices';\nimport type { FastifyRequest } from 'fastify';\nimport { headers as natsHeaders } from 'nats';\nimport { resolveInjectedRequest } from '../context/resolve-request';\nimport { NATS_CONTEXT_RESOLVER } from './constants';\nimport type { ContextResolverFn } from './nats-client.interfaces';\nimport { NATS_HEADER_KEYS, type NatsHeaders } from './nats-context';\n\nexport const NATS_CLIENTS = Symbol('NATS_CLIENTS');\n\n@Injectable({ scope: Scope.REQUEST })\nexport class NatsClientService {\n private cachedContext: NatsHeaders | null = null;\n\n constructor(\n @Inject(REQUEST) private readonly injectedRequest: FastifyRequest,\n @Inject(NATS_CONTEXT_RESOLVER) private readonly contextResolver: ContextResolverFn,\n @Inject(NATS_CLIENTS) private readonly clients: Map<string, ClientProxy>,\n ) {}\n\n // Unwraps the GraphQL { req, reply } context wrapper so auth is visible across both transports\n private get request(): FastifyRequest {\n return resolveInjectedRequest(this.injectedRequest);\n }\n\n // Sends a message to a named microservice with NatsHeaders as NATS headers\n async send<T>(service: string, cmd: string, data?: object): Promise<T> {\n const client = this.clients.get(service);\n if (!client) {\n throw new Error(\n `NATS service \"${service}\" is not registered. Available: [${[...this.clients.keys()].join(', ')}]`,\n );\n }\n\n if (!this.cachedContext) {\n // The resolver takes the whole request rather than one auth field: which caller kinds\n // exist, and what each contributes, is the consuming server's model — this side only\n // knows that something has to produce headers.\n const context = await this.contextResolver(this.request);\n if (!context) {\n throw new Error('No auth context on request — is the auth guard active for this route?');\n }\n this.cachedContext = context;\n }\n\n const headers = contextToHeaders(this.cachedContext);\n const record = new NatsRecordBuilder(data ?? {}).setHeaders(headers).build();\n\n return client.send<T>({ cmd }, record).toPromise() as Promise<T>;\n }\n}\n\n// Converts NatsHeaders to a NATS MsgHdrs object for NATS transport.\n//\n// Driven by the key map rather than a field-per-line, so a new context field travels the\n// moment it is added to NATS_HEADER_KEYS. Empty values are omitted — parseNatsHeaders applies\n// the same defaults on the way back, so sending a blank would just restate them.\nfunction contextToHeaders(ctx: NatsHeaders): import('nats').MsgHdrs {\n const hdrs = natsHeaders();\n for (const [field, key] of Object.entries(NATS_HEADER_KEYS) as [keyof NatsHeaders, string][]) {\n if (ctx[field]) hdrs.set(key, ctx[field]);\n }\n return hdrs;\n}\n","import type { FastifyRequest } from 'fastify';\n\n// Unwraps the @Inject(REQUEST) value to the real Fastify request (GraphQL injects a { req, reply } context).\nexport function resolveInjectedRequest(injected: FastifyRequest): FastifyRequest {\n const candidate = injected as unknown as { headers?: unknown; req?: FastifyRequest };\n if (candidate && candidate.headers === undefined && candidate.req) {\n return candidate.req;\n }\n return injected;\n}\n","import { Inject, Injectable } from '@nestjs/common';\nimport type { ClientProxy } from '@nestjs/microservices';\nimport { NatsRecordBuilder } from '@nestjs/microservices';\nimport { NATS_HEADER_KEYS, type NatsHeaders } from './nats-context';\n\nexport const NATS_MS_CLIENTS = Symbol('NATS_MS_CLIENTS');\n\n@Injectable()\nexport class NatsMicroserviceClientService {\n constructor(@Inject(NATS_MS_CLIENTS) private readonly clients: Map<string, ClientProxy>) {}\n\n // Forwards a message to another microservice with NatsHeaders\n async send<T>(service: string, cmd: string, natsHeaders: NatsHeaders, data?: object): Promise<T> {\n const client = this.clients.get(service);\n if (!client) {\n throw new Error(\n `NATS service \"${service}\" is not registered. Available: [${[...this.clients.keys()].join(', ')}]`,\n );\n }\n\n const headers: Record<string, string> = {\n [NATS_HEADER_KEYS.orgId]: natsHeaders.orgId,\n [NATS_HEADER_KEYS.userId]: natsHeaders.userId,\n [NATS_HEADER_KEYS.siteId]: natsHeaders.siteId,\n [NATS_HEADER_KEYS.siteTimezone]: natsHeaders.siteTimezone,\n };\n\n const record = new NatsRecordBuilder(data ?? {}).setHeaders(headers).build();\n return client.send<T>({ cmd }, record).toPromise() as Promise<T>;\n }\n}\n"],"mappings":"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","names":["import_common","import_common","HttpProblemException","HttpException","detailOrOptions","httpStatus","options","detail","type","label","errors","ConflictException","HttpProblemException","detailOrOptions","HttpStatus","CONFLICT","PG_UNIQUE_VIOLATION","tryTranslatePgError","error","pgError","findPgError","undefined","code","constraint","table","detail","ConflictException","label","trim","errors","meta","depth","candidate","cause","RpcProblemExceptionFilter","logger","Logger","name","catch","exception","_host","translatedPgError","tryTranslatePgError","RpcException","throwError","getError","isHttpException","status","getStatus","response","getResponse","payload","toProblemPayload","log","HttpStatus","INTERNAL_SERVER_ERROR","error","warn","call","detail","Error","cause","causeMessage","message","undefined","causeStack","stack","type","errors","JSON","stringify","statusCode","obj","Array","isArray","join","label","import_common","NATS_HEADER_KEYS","orgId","userId","siteId","legalEntityId","siteGroupId","siteTimezone","siteCurrencyCode","HEADER_FALLBACKS","getHeader","headers","key","undefined","get","val","Array","isArray","parseNatsHeaders","parsed","field","Object","entries","RpcNatsHeaders","createParamDecorator","_data","ctx","rpcCtx","switchToRpc","getContext","parseNatsHeaders","getHeaders","RpcSiteId","headers","siteId","InternalServerErrorException","RpcSiteCurrencyCode","siteCurrencyCode","import_common","import_microservices","NATS_MODULE_OPTIONS","Symbol","NATS_CONTEXT_RESOLVER","import_common","import_microservices","resolveInjectedRequest","injected","candidate","headers","undefined","req","NATS_CLIENTS","Symbol","NatsClientService","cachedContext","injectedRequest","contextResolver","clients","request","resolveInjectedRequest","send","service","cmd","data","client","get","Error","keys","join","context","headers","contextToHeaders","record","NatsRecordBuilder","setHeaders","build","toPromise","scope","Scope","REQUEST","ctx","hdrs","natsHeaders","field","key","Object","entries","NATS_HEADER_KEYS","set","import_common","import_microservices","NATS_MS_CLIENTS","Symbol","NatsMicroserviceClientService","clients","send","service","cmd","natsHeaders","data","client","get","Error","keys","join","headers","NATS_HEADER_KEYS","orgId","userId","siteId","siteTimezone","record","NatsRecordBuilder","setHeaders","build","toPromise","NATS_MS_OPTIONS","Symbol","NatsClientModule","logger","Logger","name","allClients","onModuleDestroy","Promise","all","map","c","close","buildClients","options","natsUrl","clients","Map","svc","services","proxy","ClientProxyFactory","create","transport","Transport","NATS","servers","set","push","log","forRoot","asyncOptions","optionsProvider","provide","NATS_MODULE_OPTIONS","useFactory","inject","resolverProvider","NATS_CONTEXT_RESOLVER","contextResolver","clientsProvider","NATS_CLIENTS","config","get","ConfigService","module","imports","ConfigModule","providers","NatsClientService","exports","forMicroservice","NATS_MS_CLIENTS","NatsMicroserviceClientService"]}
|