diff --git a/src/controllers/gupshup-webhook.controller.ts b/src/controllers/gupshup-webhook.controller.ts new file mode 100644 index 00000000..ba7ab5d1 --- /dev/null +++ b/src/controllers/gupshup-webhook.controller.ts @@ -0,0 +1,91 @@ +import { + Body, + Controller, + HttpCode, + HttpStatus, + Logger, + Post, + Req, +} from "@nestjs/common"; +import { ApiTags } from "@nestjs/swagger"; +import { Request } from "express"; +import { Auth } from "src/decorators/auth.decorator"; +import { Public } from "src/decorators/public.decorator"; +import { AuthType } from "src/enums/auth-type.enum"; + +@Auth(AuthType.None) +@Controller("webhook/whatsapp/gupshup") +@ApiTags("Solid Core") +export class GupshupWebhookController { + private readonly logger = new Logger(GupshupWebhookController.name); + + @Public() + @Post() + @HttpCode(HttpStatus.OK) + async receiveWebhook(@Req() req: Request, @Body() body: unknown) { + const userAgent = req.headers["user-agent"] ?? null; + this.logger.log( + `Received Gupshup WhatsApp webhook${userAgent ? ` from ${userAgent}` : ""}`, + ); + this.logger.debug(`Gupshup webhook payload: ${JSON.stringify(body)}`); + + const statusInfo = this.extractStatusInfo(body); + if (statusInfo) { + this.logger.log( + `Gupshup delivery update: status=${statusInfo.status ?? "unknown"}, messageId=${statusInfo.messageId ?? "n/a"}, destination=${statusInfo.destination ?? "n/a"}, reason=${statusInfo.reason ?? "n/a"}`, + ); + } + + return { + success: true, + message: "Webhook received", + }; + } + + private extractStatusInfo(body: unknown): { + status?: string; + messageId?: string; + destination?: string; + reason?: string; + } | null { + if (!body || typeof body !== "object") { + return null; + } + + const payload = body as Record; + + const status = + this.asString(payload.status) || + this.asString(payload.messageStatus) || + this.asString((payload.payload as Record)?.status) || + this.asString((payload.payload as Record)?.type); + + const messageId = + this.asString(payload.messageId) || + this.asString(payload.id) || + this.asString((payload.payload as Record)?.id) || + this.asString((payload.payload as Record)?.messageId); + + const destination = + this.asString(payload.destination) || + this.asString(payload.phone) || + this.asString((payload.payload as Record)?.destination) || + this.asString((payload.payload as Record)?.phone); + + const reason = + this.asString(payload.reason) || + this.asString(payload.error) || + this.asString((payload.payload as Record)?.reason) || + this.asString((payload.payload as Record)?.error); + + if (!status && !messageId && !destination && !reason) { + return null; + } + + return { status, messageId, destination, reason }; + } + + private asString(value: unknown): string | undefined { + return typeof value === "string" && value.trim() ? value.trim() : undefined; + } +} diff --git a/src/controllers/meta-cloud-whatsapp-webhook.controller.ts b/src/controllers/meta-cloud-whatsapp-webhook.controller.ts new file mode 100644 index 00000000..1c97c706 --- /dev/null +++ b/src/controllers/meta-cloud-whatsapp-webhook.controller.ts @@ -0,0 +1,155 @@ +import { + Body, + Controller, + Get, + HttpCode, + HttpStatus, + Logger, + Post, + Query, + Res, +} from "@nestjs/common"; +import { ApiTags } from "@nestjs/swagger"; +import { Response } from "express"; +import { Auth } from "src/decorators/auth.decorator"; +import { Public } from "src/decorators/public.decorator"; +import { AuthType } from "src/enums/auth-type.enum"; +import { SettingService } from "src/services/setting.service"; +import type { SolidCoreSetting } from "src/services/settings/default-settings-provider.service"; + +@Auth(AuthType.None) +@Controller("webhook/whatsapp/meta-cloud") +@ApiTags("Solid Core") +export class MetaCloudWhatsappWebhookController { + private readonly logger = new Logger(MetaCloudWhatsappWebhookController.name); + + constructor(private readonly settingService: SettingService) {} + + @Public() + @Get() + verifyWebhook(@Query() query: Record, @Res() res: Response) { + const mode = this.resolveQueryValue(query, "hub.mode", "hub_mode", "mode"); + const verifyToken = this.resolveQueryValue( + query, + "hub.verify_token", + "hub_verify_token", + "verify_token", + ); + const challenge = this.resolveQueryValue( + query, + "hub.challenge", + "hub_challenge", + "challenge", + ); + + const configuredVerifyToken = + this.settingService.getConfigValue( + "metaWhatsappWebhookVerifyToken", + ) || process.env.COMMON_META_WHATSAPP_WEBHOOK_VERIFY_TOKEN; + + const isVerificationCall = mode === "subscribe"; + const tokenMatches = + !!configuredVerifyToken && verifyToken === configuredVerifyToken; + + if (isVerificationCall && tokenMatches && challenge) { + this.logger.log("Meta Cloud WhatsApp webhook verified successfully."); + res.writeHead(HttpStatus.OK, { + "Content-Type": "text/plain", + "Content-Length": Buffer.byteLength(String(challenge)), + }); + res.write(String(challenge)); + return res.end(); + } + + this.logger.warn( + `Meta Cloud webhook verification failed. mode=${mode ?? "n/a"}, tokenMatch=${tokenMatches}`, + ); + + res.writeHead(HttpStatus.FORBIDDEN, { "Content-Type": "text/plain" }); + return res.end("Webhook verification failed"); + } + + @Public() + @Post() + @HttpCode(HttpStatus.OK) + async receiveWebhook(@Body() body: unknown) { + this.logger.log("Received Meta Cloud WhatsApp webhook"); + this.logger.debug(`Meta Cloud webhook payload: ${JSON.stringify(body)}`); + + const statusInfo = this.extractStatusInfo(body); + if (statusInfo) { + this.logger.log( + `Meta Cloud delivery update: status=${statusInfo.status ?? "unknown"}, messageId=${statusInfo.messageId ?? "n/a"}, destination=${statusInfo.destination ?? "n/a"}, reason=${statusInfo.reason ?? "n/a"}`, + ); + } + + return { + success: true, + message: "Webhook received", + }; + } + + private extractStatusInfo(body: unknown): { + status?: string; + messageId?: string; + destination?: string; + reason?: string; + } | null { + if (!body || typeof body !== "object") { + return null; + } + + const payload = body as Record; + const entries = payload.entry as Array> | undefined; + const changes = entries?.[0]?.changes as Array> | undefined; + const value = changes?.[0]?.value as Record | undefined; + const statuses = value?.statuses as Array> | undefined; + const status = statuses?.[0]; + + if (!status) { + return null; + } + + const errors = status.errors as Array> | undefined; + + return { + status: this.asString(status.status), + messageId: this.asString(status.id), + destination: this.asString(status.recipient_id), + reason: + this.asString(errors?.[0]?.title) || this.asString(errors?.[0]?.message), + }; + } + + private asString(value: unknown): string | undefined { + return typeof value === "string" && value.trim() ? value.trim() : undefined; + } + + private resolveQueryValue( + query: Record, + ...keys: string[] + ): string | undefined { + for (const key of keys) { + const directValue = this.asString(query[key]); + if (directValue) { + return directValue; + } + } + + const hubRaw = query.hub; + if (hubRaw && typeof hubRaw === "object" && !Array.isArray(hubRaw)) { + const hub = hubRaw as Record; + for (const key of ["mode", "verify_token", "challenge"]) { + const value = this.asString(hub[key]); + if ( + value && + keys.some((candidate) => candidate.endsWith(key) || candidate === key) + ) { + return value; + } + } + } + + return undefined; + } +} diff --git a/src/factories/whatsapp.factory.ts b/src/factories/whatsapp.factory.ts index fc1b4949..9938c17c 100644 --- a/src/factories/whatsapp.factory.ts +++ b/src/factories/whatsapp.factory.ts @@ -1,42 +1,43 @@ -import { Inject, Injectable, Logger } from "@nestjs/common"; -import { ConfigType } from "@nestjs/config"; +import { Injectable, Logger } from "@nestjs/common"; import { ModuleRef } from "@nestjs/core"; import { SolidRegistry } from "src/helpers/solid-registry"; import { IWhatsAppTransport } from "src/interfaces"; import { SettingService } from "src/services/setting.service"; import type { SolidCoreSetting } from "src/services/settings/default-settings-provider.service"; -function norm(s?: string) { - return s?.trim().toLowerCase(); -} - -// This factory will be use to return a mail service instance, using the configured environment variables @Injectable() export class WhatsAppFactory { - private readonly logger = new Logger(this.constructor.name); - constructor( - private readonly moduleRef: ModuleRef, // Use the module ref to dynamically resolve the mail service - private readonly solidRegistry: SolidRegistry, - private readonly settingService: SettingService, - ) { } - - getWhatsappService(name: string = null): IWhatsAppTransport { - // This is the default provider - const whatsappServiceName = name || this.settingService.getConfigValue("whatsappProvider"); - if (!whatsappServiceName) { - throw new Error("Unable to resolve whatsapp provider") - } - const whatsappProviders = this.solidRegistry.getWhatsappProviders(); - - // Return the instance which matches the whatsappServiceName - if (!whatsappProviders.length) { - // throw new Error("No mail providers are registered."); - this.logger.error("No whatsapp providers are registered."); - } - - const whatsappServiceProvider = whatsappProviders.find(provider => provider.name === whatsappServiceName); - - return whatsappServiceProvider.instance as IWhatsAppTransport; + private readonly logger = new Logger(WhatsAppFactory.name); + + constructor( + private readonly moduleRef: ModuleRef, + private readonly solidRegistry: SolidRegistry, + private readonly settingService: SettingService, + ) {} + + getWhatsappService(name?: string): IWhatsAppTransport { + const providerKey = + name || + this.settingService.getConfigValue("whatsappProvider"); + + if (!providerKey) { + throw new Error("Unable to resolve whatsapp provider"); + } + + const whatsappProviders = this.solidRegistry.getWhatsappProviders(); + + if (!whatsappProviders.length) { + throw new Error("No whatsapp providers are registered."); + } + + const whatsappServiceProvider = whatsappProviders.find((provider) => + provider.name?.toLowerCase().includes(providerKey.toLowerCase()), + ); + + if (!whatsappServiceProvider) { + throw new Error(`WhatsApp provider '${providerKey}' not found`); } -} \ No newline at end of file + return whatsappServiceProvider.instance as IWhatsAppTransport; + } +} diff --git a/src/index.ts b/src/index.ts index 4ff238df..800b4617 100755 --- a/src/index.ts +++ b/src/index.ts @@ -323,6 +323,7 @@ export * from './services/solid-introspect.service' export * from './services/user.service' export * from './services/view-metadata.service' export * from './services/whatsapp/Msg91WhatsappService' //rename +export * from './services/whatsapp/GupshupOtpWhatsappService' export * from './services/setting.service' export * from './services/encryption.service' export * from './services/info.service' diff --git a/src/listeners/user-registration.listener.ts b/src/listeners/user-registration.listener.ts index 91703e9f..9915aa83 100755 --- a/src/listeners/user-registration.listener.ts +++ b/src/listeners/user-registration.listener.ts @@ -1,14 +1,57 @@ - import { User } from "../entities/user.entity"; import { OnEvent } from "@nestjs/event-emitter"; import { Injectable, Logger } from "@nestjs/common"; import { EventDetails, EventType } from "../interfaces"; +import { WhatsAppFactory } from "src/factories/whatsapp.factory"; @Injectable() export class UserRegistrationListener { - private logger = new Logger(UserRegistrationListener.name); - @OnEvent(EventType.USER_REGISTERED) - handleUserRegistration(event: EventDetails) { - this.logger.log(`User registered with details: ${JSON.stringify(event.payload)}`); + private readonly logger = new Logger(UserRegistrationListener.name); + + constructor(private readonly whatsAppFactory: WhatsAppFactory) {} + + @OnEvent(EventType.USER_REGISTERED) + async handleUserRegistration(event: EventDetails) { + this.logger.log(`User registered with details: ${JSON.stringify(event.payload)}`); + + const notifyTo = process.env.WHATSAPP_EVENT_NOTIFY_TO; + if (!notifyTo) { + this.logger.debug("WHATSAPP_EVENT_NOTIFY_TO not set. Skipping registration WhatsApp notification."); + return; + } + + try { + const whatsappService = this.whatsAppFactory.getWhatsappService(); + const username = event.payload?.username || "User"; + const userId = event.payload?.id || "N/A"; + + await whatsappService.sendWhatsAppMessage( + notifyTo, + "registration_event", + { + payload: { + channel: "whatsapp", + source: process.env.COMMON_GUPSHUP_WHATSAPP_SOURCE, + destination: notifyTo, + "src.name": process.env.COMMON_GUPSHUP_APP_NAME || "solidx", + message: { + type: "text", + text: `New user registered: ${username} (id: ${userId})`, + }, + }, + }, + ); + + this.logger.log(`Sent registration WhatsApp notification to ${notifyTo}`); + } catch (error: any) { + const status = error?.response?.status; + const responseData = error?.response?.data; + const errorMessage = error?.message; + const stack = error?.stack; + + this.logger.error( + `Failed to send registration WhatsApp notification to ${notifyTo}. status=${status ?? "unknown"}, response=${typeof responseData === "object" ? JSON.stringify(responseData) : responseData}, message=${errorMessage}, stack=${stack}`, + ); } -} \ No newline at end of file + } +} diff --git a/src/services/authentication.service.ts b/src/services/authentication.service.ts index 4c98bd02..4d46daa2 100755 --- a/src/services/authentication.service.ts +++ b/src/services/authentication.service.ts @@ -50,6 +50,7 @@ import { SettingService } from "./setting.service"; import { UserActivityHistoryService } from "./user-activity-history.service"; import { UserService } from "./user.service"; import { SmsFactory } from "src/factories/sms.factory"; +import { WhatsAppFactory } from "src/factories/whatsapp.factory"; import { SolidRegistry } from "src/helpers/solid-registry"; enum LoginProvider { @@ -79,6 +80,7 @@ export class AuthenticationService { private readonly mailServiceFactory: MailFactory, // private readonly smsService: Msg91OTPService, private readonly smsFactory: SmsFactory, + private readonly whatsAppFactory: WhatsAppFactory, private readonly eventEmitter: EventEmitter2, private readonly settingService: SettingService, private readonly roleMetadataService: RoleMetadataService, @@ -193,11 +195,22 @@ export class AuthenticationService { return this.performSignUp(signUpDto, new User(), this.userRepository); } - private async performSignUp(signUpDto: SignUpDto, entity: T, repo: Repository): Promise { + private async performSignUp( + signUpDto: SignUpDto, + entity: T, + repo: Repository, + ): Promise { try { - const onForcePasswordChange = this.settingService.getConfigValue("forceChangePasswordOnFirstLogin"); - const activateUserOnRegistration = this.settingService.getConfigValue("activateUserOnRegistration"); - const defaultRole = this.settingService.getConfigValue("defaultRole"); + const onForcePasswordChange = + this.settingService.getConfigValue( + "forceChangePasswordOnFirstLogin", + ); + const activateUserOnRegistration = + this.settingService.getConfigValue( + "activateUserOnRegistration", + ); + const defaultRole = + this.settingService.getConfigValue("defaultRole"); var { user, pwd, autoGeneratedPwd } = await this.populateForSignup( entity, @@ -211,14 +224,19 @@ export class AuthenticationService { } const savedUser = await repo.save(user); const userRoles = signUpDto.roles ?? []; - if ((signUpDto.roles?.length ?? 0) === 0 && signUpDto.username !== "sa" && defaultRole) { + if ( + (signUpDto.roles?.length ?? 0) === 0 && + signUpDto.username !== "sa" && + defaultRole + ) { userRoles.push(defaultRole); } await this.handlePostSignup(savedUser, userRoles, pwd, autoGeneratedPwd); + await this.handlePasswordlessSignupOtp(savedUser, signUpDto, autoGeneratedPwd, repo); + this.triggerRegistrationEvent(savedUser); return savedUser; - } - catch (err: any) { + } catch (err: any) { const pgUniqueViolationErrorCode = "23505"; if (err.code === pgUniqueViolationErrorCode) { throw new ConflictException( @@ -231,6 +249,42 @@ export class AuthenticationService { } } + private async handlePasswordlessSignupOtp( + user: T, + signUpDto: SignUpDto, + autoGeneratedPwd: string, + repo: Repository, + ): Promise { + const isPasswordProvided = !!signUpDto.password; + const isAutoGeneratedPassword = !!autoGeneratedPwd; + if (isPasswordProvided || isAutoGeneratedPassword) { + return; + } + + if (!user.mobile) { + this.logger.warn( + `Skipping passwordless signup OTP WhatsApp notification for user ${user.username}: mobile is missing.`, + ); + return; + } + + const isPasswordlessRegistrationEnabled = + await this.isPasswordlessRegistrationEnabled(); + if (!isPasswordlessRegistrationEnabled) { + return; + } + + await this.assignRegistrationOtp( + PasswordlessRegistrationValidateWhatSources.MOBILE, + user, + ); + await repo.save(user); + await this.notifyUserOnOtpInitiateRegistration( + user, + PasswordlessRegistrationValidateWhatSources.MOBILE, + ); + } + /** @deprecated Use IExtensionUserCreationProvider instead. Kept for backward compatibility. */ async signupForExtensionUser( signUpDto: SignUpDto, @@ -602,7 +656,7 @@ export class AuthenticationService { const companyLogo = await this.getCompanyLogo(); if ( registrationValidationSource === - PasswordlessLoginValidateWhatSources.EMAIL + PasswordlessRegistrationValidateWhatSources.EMAIL ) { const mailService = this.mailServiceFactory.getMailService(); mailService.sendEmailUsingTemplate( @@ -632,25 +686,141 @@ export class AuthenticationService { } if ( registrationValidationSource === - PasswordlessLoginValidateWhatSources.MOBILE + PasswordlessRegistrationValidateWhatSources.MOBILE ) { - const smsService = this.smsFactory.getSmsService(); - smsService.sendSMSUsingTemplate( + const templateParams = { + solidAppName: + this.settingService.getConfigValue("appTitle"), + otp: user.mobileVerificationTokenOnRegistration, + mobileVerificationTokenOnRegistration: + user.mobileVerificationTokenOnRegistration, + firstName: user.username, + fullName: user.fullName ? user.fullName : user.username, + companyLogoUrl: companyLogo, + }; + + const whatsappDestination = this.normalizeWhatsAppDestination( user.mobile, - "otp-on-register", - { - solidAppName: - this.settingService.getConfigValue("appTitle"), - otp: user.mobileVerificationTokenOnRegistration, - mobileVerificationTokenOnRegistration: - user.mobileVerificationTokenOnRegistration, - firstName: user.username, - fullName: user.fullName ? user.fullName : user.username, - companyLogoUrl: companyLogo, - }, - this.settingService.getConfigValue("shouldQueueSms"), ); + const gupshupTemplateAppName = + process.env.COMMON_GUPSHUP_APP_NAME || "Gupshup"; + const whatsappTemplateId = + this.settingService.getConfigValue( + "otpWhatsappTemplateId", + ) || "common_otp"; + const whatsappIndependentEnabled = + this.settingService.getConfigValue( + "otpWhatsappIndependentEnabled", + ) !== false; + + let smsSent = false; + let whatsappSent = false; + let smsErrorMessage: string | undefined; + let whatsappErrorMessage: string | undefined; + + this.logger.debug( + `OTP SMS send attempt: destination=${user.mobile}, whatsappDestination=${whatsappDestination}`, + ); + + try { + const smsService = this.smsFactory.getSmsService(); + await smsService.sendSMSUsingTemplate( + user.mobile, + "otp-on-register", + templateParams, + false, + ); + smsSent = true; + } catch (smsError: any) { + smsErrorMessage = smsError?.message; + this.logger.warn( + `OTP SMS failed: destination=${user.mobile}, message=${smsErrorMessage}`, + ); + } + + if (whatsappIndependentEnabled) { + if (!whatsappDestination) { + whatsappErrorMessage = "Normalized WhatsApp destination is empty"; + this.logger.error( + `Independent OTP WhatsApp skipped: destination=${user.mobile}, message=${whatsappErrorMessage}`, + ); + } else { + this.logger.debug( + `Independent OTP WhatsApp send attempt: destination=${user.mobile}, whatsappDestination=${whatsappDestination}, templateId=${whatsappTemplateId}`, + ); + try { + await this.sendOtpToWhatsappProvider( + whatsappDestination, + String(whatsappTemplateId), + String(gupshupTemplateAppName), + String(templateParams.otp || ""), + ); + whatsappSent = true; + this.logger.log( + `Independent OTP WhatsApp success: destination=${user.mobile}, whatsappDestination=${whatsappDestination}, templateId=${whatsappTemplateId}`, + ); + } catch (waError: any) { + whatsappErrorMessage = waError?.message; + this.logger.error( + `Independent OTP WhatsApp failed: destination=${user.mobile}, whatsappDestination=${whatsappDestination}, templateId=${whatsappTemplateId}, message=${whatsappErrorMessage}`, + ); + } + } + } + + if (!smsSent && !whatsappSent) { + throw new Error( + `OTP delivery failed on both channels. smsError=${smsErrorMessage || "unknown"}, whatsappError=${whatsappErrorMessage || "disabled/unknown"}`, + ); + } + } + } + + private normalizeWhatsAppDestination(rawMobile: string): string { + const raw = (rawMobile || "").trim(); + let sanitized = raw.replace(/[^\d+]/g, ""); + + if (sanitized.startsWith("00")) { + sanitized = `+${sanitized.slice(2)}`; + } + + const defaultDialCode = String( + this.settingService.getConfigValue( + "otpDefaultCountryDialCode", + ) || "", + ).replace(/\D/g, ""); + + if (sanitized.startsWith("+")) { + const e164Digits = sanitized.slice(1).replace(/\D/g, ""); + if (e164Digits.length >= 8 && e164Digits.length <= 15) { + return e164Digits; + } + } + + const digits = sanitized.replace(/\D/g, ""); + + if (digits.length >= 11 && digits.length <= 15) { + return digits; } + + if (digits.length === 10 && defaultDialCode) { + return `${defaultDialCode}${digits}`; + } + + return digits; + } + + private async sendOtpToWhatsappProvider( + destination: string, + templateId: string, + appName: string, + otp: string, + ): Promise { + const whatsappService = this.whatsAppFactory.getWhatsappService(); + await whatsappService.sendWhatsAppMessage(destination, null, { + type: "text", + text: `${appName} OTP is ${otp}. It is valid for 10 mins.`, + }); } async otpConfirmRegistration(confirmSignUpDto: OTPConfirmOTPDto) { @@ -753,7 +923,7 @@ export class AuthenticationService { this.resolvePasswordlessValidationSource(); if ( registrationValidationSource === - PasswordlessLoginValidateWhatSources.EMAIL + PasswordlessRegistrationValidateWhatSources.EMAIL ) { if (!user.emailVerifiedOnRegistrationAt) { return false; @@ -761,7 +931,7 @@ export class AuthenticationService { } if ( registrationValidationSource === - PasswordlessLoginValidateWhatSources.MOBILE + PasswordlessRegistrationValidateWhatSources.MOBILE ) { if (!user.mobileVerifiedOnRegistrationAt) { return false; @@ -876,7 +1046,7 @@ export class AuthenticationService { const dummyOtp = this.getDummyOtpForUser(user); if (!dummyOtp) { await this.assignLoginOtp(user, type); - this.notifyUserOnOtpInititateLogin(user, type); + await this.notifyUserOnOtpInititateLogin(user, type); } return this.buildLoginOtpResponse(user, type); } @@ -1001,21 +1171,88 @@ export class AuthenticationService { ); } if (loginType === PasswordlessLoginValidateWhatSources.MOBILE) { - const smsService = this.smsFactory.getSmsService(); - smsService.sendSMSUsingTemplate( - user.mobile, - "otp-on-login", - { - solidAppName: - this.settingService.getConfigValue("appTitle"), - otp: user.mobileVerificationTokenOnLogin, - mobileVerificationTokenOnLogin: user.mobileVerificationTokenOnLogin, - firstName: user.username, - fullName: user.fullName ? user.fullName : user.username, - companyLogoUrl: companyLogo, - }, - this.settingService.getConfigValue("shouldQueueSms"), + const templateParams = { + solidAppName: + this.settingService.getConfigValue("appTitle"), + otp: user.mobileVerificationTokenOnLogin, + mobileVerificationTokenOnLogin: user.mobileVerificationTokenOnLogin, + firstName: user.username, + fullName: user.fullName ? user.fullName : user.username, + companyLogoUrl: companyLogo, + }; + + const whatsappDestination = this.normalizeWhatsAppDestination(user.mobile); + const gupshupTemplateAppName = + process.env.COMMON_GUPSHUP_APP_NAME || "Gupshup"; + const whatsappTemplateId = + this.settingService.getConfigValue( + "otpWhatsappTemplateId", + ) || "common_otp"; + const whatsappIndependentEnabled = + this.settingService.getConfigValue( + "otpWhatsappIndependentEnabled", + ) !== false; + + let smsSent = false; + let whatsappSent = false; + let smsErrorMessage: string | undefined; + let whatsappErrorMessage: string | undefined; + + this.logger.debug( + `OTP LOGIN SMS send attempt: destination=${user.mobile}, whatsappDestination=${whatsappDestination}`, ); + + try { + const smsService = this.smsFactory.getSmsService(); + await smsService.sendSMSUsingTemplate( + user.mobile, + "otp-on-login", + templateParams, + false, + ); + smsSent = true; + } catch (smsError: any) { + smsErrorMessage = smsError?.message; + this.logger.warn( + `OTP LOGIN SMS failed: destination=${user.mobile}, message=${smsErrorMessage}`, + ); + } + + if (whatsappIndependentEnabled) { + if (!whatsappDestination) { + whatsappErrorMessage = "Normalized WhatsApp destination is empty"; + this.logger.error( + `OTP LOGIN WhatsApp skipped: destination=${user.mobile}, message=${whatsappErrorMessage}`, + ); + } else { + this.logger.debug( + `OTP LOGIN WhatsApp send attempt: destination=${user.mobile}, whatsappDestination=${whatsappDestination}, templateId=${whatsappTemplateId}`, + ); + try { + await this.sendOtpToWhatsappProvider( + whatsappDestination, + String(whatsappTemplateId), + String(gupshupTemplateAppName), + String(templateParams.otp || ""), + ); + whatsappSent = true; + this.logger.log( + `OTP LOGIN WhatsApp success: destination=${user.mobile}, whatsappDestination=${whatsappDestination}, templateId=${whatsappTemplateId}`, + ); + } catch (waError: any) { + whatsappErrorMessage = waError?.message; + this.logger.error( + `OTP LOGIN WhatsApp failed: destination=${user.mobile}, whatsappDestination=${whatsappDestination}, templateId=${whatsappTemplateId}, message=${whatsappErrorMessage}`, + ); + } + } + } + + if (!smsSent && !whatsappSent) { + throw new Error( + `OTP LOGIN delivery failed on both channels. smsError=${smsErrorMessage || "unknown"}, whatsappError=${whatsappErrorMessage || "disabled/unknown"}`, + ); + } } } @@ -1294,7 +1531,7 @@ export class AuthenticationService { // Assuming all users do not have mobile as mandatory. if ( forgotPasswordSendVerificationTokenOn == - ForgotPasswordSendVerificationTokenOn.MOBILE && + ForgotPasswordSendVerificationTokenOn.MOBILE && user.mobile ) { const smsService = this.smsFactory.getSmsService(); @@ -1323,12 +1560,10 @@ export class AuthenticationService { const user = await this.resolveUserByVerificationToken( confirmForgotPasswordDto.verificationToken, ); - if (!user) - throw new UnauthorizedException("Invalid verification token"); + if (!user) throw new UnauthorizedException("Invalid verification token"); if (user.lastLoginProvider !== "local") throw new UnauthorizedException(ERROR_MESSAGES.INVALID_CREDENTIALS); - if (!user.active) - throw new UnauthorizedException("User is inactive"); + if (!user.active) throw new UnauthorizedException("User is inactive"); // 1) Atomically consume the token (only one request can succeed) const { affected } = await m @@ -1420,7 +1655,7 @@ export class AuthenticationService { // Assuming all users do not have mobile as mandatory. if ( forgotPasswordSendVerificationTokenOn == - ForgotPasswordSendVerificationTokenOn.MOBILE && + ForgotPasswordSendVerificationTokenOn.MOBILE && user.mobile ) { const smsService = this.smsFactory.getSmsService(); diff --git a/src/services/settings/default-settings-provider.service.ts b/src/services/settings/default-settings-provider.service.ts index 5a1ac4b1..bdb7d4f3 100644 --- a/src/services/settings/default-settings-provider.service.ts +++ b/src/services/settings/default-settings-provider.service.ts @@ -779,6 +779,46 @@ const getSolidCoreSettings = (isProd: boolean) => value: parseInt(process.env.IAM_OTP_EXPIRY ?? "10"), level: SettingLevel.SystemEnv, }, + { + moduleName: "solid-core", + key: "otpWhatsappFallbackEnabled", + value: (process.env.IAM_OTP_WHATSAPP_FALLBACK_ENABLED ?? "true") === "true", + level: SettingLevel.SystemAdminEditable, + label: "OTP WhatsApp Fallback Enabled", + group: "authentication-settings", + sortOrder: 85, + controlType: "boolean", + }, + { + moduleName: "solid-core", + key: "otpWhatsappTemplateId", + value: process.env.IAM_OTP_WHATSAPP_TEMPLATE_ID ?? "common_otp", + level: SettingLevel.SystemAdminEditable, + label: "OTP WhatsApp Template ID", + group: "authentication-settings", + sortOrder: 86, + controlType: "shortText", + }, + { + moduleName: "solid-core", + key: "otpWhatsappIndependentEnabled", + value: (process.env.IAM_OTP_WHATSAPP_INDEPENDENT_ENABLED ?? "true") === "true", + level: SettingLevel.SystemAdminEditable, + label: "OTP WhatsApp Independent Enabled", + group: "authentication-settings", + sortOrder: 87, + controlType: "boolean", + }, + { + moduleName: "solid-core", + key: "otpDefaultCountryDialCode", + value: process.env.IAM_OTP_DEFAULT_COUNTRY_DIAL_CODE ?? "", + level: SettingLevel.SystemAdminEditable, + label: "OTP Default Country Dial Code", + group: "authentication-settings", + sortOrder: 88, + controlType: "shortText", + }, { moduleName: "solid-core", key: "forgotPasswordVerificationTokenExpiry", @@ -1178,6 +1218,64 @@ const getSolidCoreSettings = (isProd: boolean) => sortOrder: 30, controlType: "shortText", }, + { + moduleName: "solid-core", + key: "metaWhatsappApiUrl", + value: process.env.COMMON_META_WHATSAPP_API_URL || "https://graph.facebook.com", + level: SettingLevel.SystemAdminReadonly, + label: "Meta WhatsApp API URL", + group: "whatsapp-settings", + sortOrder: 40, + controlType: "shortText", + }, + { + moduleName: "solid-core", + key: "metaWhatsappApiVersion", + value: process.env.COMMON_META_WHATSAPP_API_VERSION || "v23.0", + level: SettingLevel.SystemAdminReadonly, + label: "Meta WhatsApp API Version", + group: "whatsapp-settings", + sortOrder: 50, + controlType: "shortText", + }, + { + moduleName: "solid-core", + key: "metaWhatsappPhoneNumberId", + value: process.env.COMMON_META_WHATSAPP_PHONE_NUMBER_ID, + level: SettingLevel.SystemAdminReadonly, + label: "Meta WhatsApp Phone Number ID", + group: "whatsapp-settings", + sortOrder: 60, + controlType: "shortText", + }, + { + moduleName: "solid-core", + key: "metaWhatsappBusinessAccountId", + value: process.env.COMMON_META_WHATSAPP_BUSINESS_ACCOUNT_ID, + level: SettingLevel.SystemAdminReadonly, + label: "Meta WhatsApp Business Account ID", + group: "whatsapp-settings", + sortOrder: 70, + controlType: "shortText", + }, + { + moduleName: "solid-core", + key: "metaWhatsappAccessToken", + value: process.env.COMMON_META_WHATSAPP_ACCESS_TOKEN, + level: SettingLevel.SystemEnv, + }, + { + moduleName: "solid-core", + key: "metaWhatsappWebhookVerifyToken", + value: process.env.COMMON_META_WHATSAPP_WEBHOOK_VERIFY_TOKEN, + level: SettingLevel.SystemEnv, + }, + { + moduleName: "solid-core", + key: "metaWhatsappAppSecret", + value: process.env.COMMON_META_WHATSAPP_APP_SECRET, + level: SettingLevel.SystemEnv, + }, ] as const satisfies SettingDefinition[]; // 2. diff --git a/src/services/user.service.ts b/src/services/user.service.ts index d13e96d3..b527ef68 100755 --- a/src/services/user.service.ts +++ b/src/services/user.service.ts @@ -285,7 +285,7 @@ export class UserService extends CRUDService { } async resolveUserOnOauthFacebook(oauthUserDto: OauthUserDto): Promise { - const normalizedEmail = oauthUserDto.email?.trim().toLowerCase() || null; + const normalizedEmail = oauthUserDto.email?.trim().toLowerCase(); let user: User | null = null; if (oauthUserDto.providerId) { diff --git a/src/services/whatsapp/GupshupOtpWhatsappService.ts b/src/services/whatsapp/GupshupOtpWhatsappService.ts new file mode 100644 index 00000000..fccfc12b --- /dev/null +++ b/src/services/whatsapp/GupshupOtpWhatsappService.ts @@ -0,0 +1,174 @@ +import { HttpService } from "@nestjs/axios"; +import { Injectable, Logger } from "@nestjs/common"; +import { AxiosError } from "axios"; +import { WhatsAppProvider } from "src/decorators/whatsapp-provider.decorator"; +import { IWhatsAppTransport } from "src/interfaces"; + +@Injectable() +@WhatsAppProvider() +export class GupshupOtpWhatsappService implements IWhatsAppTransport { + private readonly logger = new Logger(GupshupOtpWhatsappService.name); + + constructor(private readonly httpService: HttpService) {} + + async sendWhatsAppMessage( + to: string, + templateId: string, + parameters: any, + parentEntity?: any, + parentEntityId?: any, + ): Promise { + if (!to) { + throw new Error("WhatsApp destination number is required"); + } + + const payload = parameters?.payload; + if (payload?.message?.type === "text" && payload?.message?.text) { + await this.sendTextMessage( + payload.destination || to, + payload.message.text, + payload.source, + payload["src.name"], + ); + return { to, templateId, parameters, parentEntity, parentEntityId }; + } + + if (parameters?.type === "text" && parameters?.text) { + await this.sendTextMessage(to, parameters.text); + return { to, templateId, parameters, parentEntity, parentEntityId }; + } + + if (!templateId) { + throw new Error("WhatsApp templateId is required for template message"); + } + + const bodyParams = Array.isArray(parameters) + ? parameters + : Array.isArray(parameters?.body) + ? parameters.body + : []; + + await this.sendOtpTemplate(to, templateId, bodyParams.map((x) => String(x))); + return { to, templateId, parameters, parentEntity, parentEntityId }; + } + + private async sendTextMessage( + destination: string, + text: string, + sourceOverride?: string, + appNameOverride?: string, + ): Promise { + const apiKey = + process.env.COMMON_GUPSHUP_WHATSAPP_API_KEY || + process.env.GUPSHUP_API_KEY; + const msgUrl = + process.env.COMMON_GUPSHUP_WHATSAPP_API_URL || + process.env.GUPSHUP_API_URL || + "https://api.gupshup.io/wa/api/v1/msg"; + const source = + sourceOverride || + process.env.COMMON_GUPSHUP_WHATSAPP_SOURCE || + process.env.GUPSHUP_SOURCE_NUMBER; + const appName = + appNameOverride || process.env.COMMON_GUPSHUP_APP_NAME || "solidx"; + + if (!apiKey || !msgUrl || !source) { + throw new Error("Missing Gupshup WhatsApp configuration for text message"); + } + + const rawDestination = destination.replace(/\D/g, ""); + const params = new URLSearchParams(); + params.append("channel", "whatsapp"); + params.append("source", source); + params.append("destination", rawDestination); + params.append("src.name", appName); + params.append("message", JSON.stringify({ type: "text", text })); + + await this.httpService.axiosRef.post(msgUrl, params.toString(), { + headers: { + apikey: apiKey, + "Content-Type": "application/x-www-form-urlencoded", + }, + }); + } + + async sendOtpTemplate( + destination: string, + templateId: string, + bodyParams: string[], + ): Promise { + const apiKey = + process.env.COMMON_GUPSHUP_WHATSAPP_API_KEY || + process.env.GUPSHUP_API_KEY; + const baseUrl = + process.env.COMMON_GUPSHUP_WHATSAPP_API_URL || + process.env.GUPSHUP_API_URL || + "https://api.gupshup.io/wa/api/v1/msg"; + const templateUrl = + process.env.COMMON_GUPSHUP_WHATSAPP_TEMPLATE_API_URL || + baseUrl.replace(/\/msg$/, "/template/msg"); + const source = + process.env.COMMON_GUPSHUP_WHATSAPP_SOURCE || + process.env.GUPSHUP_SOURCE_NUMBER; + const appName = process.env.COMMON_GUPSHUP_APP_NAME || "solidx"; + + if (!apiKey || !templateUrl || !source) { + throw new Error("Missing Gupshup OTP WhatsApp configuration"); + } + + if (!destination || !String(destination).trim()) { + throw new Error("WhatsApp destination is empty"); + } + + try { + const rawDestination = destination.replace(/\D/g, ""); + const params = new URLSearchParams(); + params.append("channel", "whatsapp"); + params.append("source", source); + params.append("destination", rawDestination); + params.append( + "to", + rawDestination.startsWith("+") + ? rawDestination + : `+${rawDestination}`, + ); + params.append("src.name", appName); + params.append( + "template", + JSON.stringify({ + id: templateId, + params: bodyParams, + }), + ); + + this.logger.debug( + `Gupshup OTP outbound (template): endpoint=${templateUrl}, source=${source}, destination=${rawDestination}, to=${rawDestination.startsWith("+") ? rawDestination : `+${rawDestination}`}, templateId=${templateId}, params=${JSON.stringify(bodyParams)}`, + ); + + const response = await this.httpService.axiosRef.post( + templateUrl, + params.toString(), + { + headers: { + apikey: apiKey, + "Content-Type": "application/x-www-form-urlencoded", + }, + }, + ); + + this.logger.debug( + `Gupshup response: ${response.status}, data=${JSON.stringify(response.data)}`, + ); + + this.logger.debug( + `Independent OTP WhatsApp sent to ${rawDestination} template=${templateId} via template endpoint`, + ); + } catch (error) { + const axiosError = error as AxiosError; + this.logger.error( + `Independent OTP WhatsApp failed: destination=${destination}, templateId=${templateId}, status=${axiosError.response?.status ?? "unknown"}, response=${typeof axiosError.response?.data === "object" ? JSON.stringify(axiosError.response?.data) : axiosError.response?.data}, url=${templateUrl}`, + ); + throw error; + } + } +} diff --git a/src/services/whatsapp/MetaCloudWhatsappService.ts b/src/services/whatsapp/MetaCloudWhatsappService.ts new file mode 100644 index 00000000..c709766b --- /dev/null +++ b/src/services/whatsapp/MetaCloudWhatsappService.ts @@ -0,0 +1,253 @@ +import { HttpService } from "@nestjs/axios"; +import { Injectable, Logger } from "@nestjs/common"; +import { AxiosError } from "axios"; +import { WhatsAppProvider } from "src/decorators/whatsapp-provider.decorator"; +import { IWhatsAppTransport } from "src/interfaces"; +import { QueueMessage } from "src/interfaces/mq"; +import { SettingService } from "src/services/setting.service"; +import type { SolidCoreSetting } from "src/services/settings/default-settings-provider.service"; + +type MetaTemplatePayload = { + type?: "template"; + templateName?: string; + templateId?: string; + languageCode?: string; + body?: string[]; + headerText?: string; + imageLink?: string; +}; + +type MetaTextPayload = { + type?: "text"; + text?: string; + previewUrl?: boolean; +}; + +@Injectable() +@WhatsAppProvider() +export class MetaCloudWhatsappService implements IWhatsAppTransport { + readonly logger = new Logger(MetaCloudWhatsappService.name); + + constructor( + private readonly httpService: HttpService, + private readonly settingService: SettingService, + ) {} + + async sendWhatsAppMessage( + to: string, + templateId: string, + parameters: any, + parentEntity?: any, + parentEntityId?: any, + ): Promise { + const message: QueueMessage = { + payload: { + to, + templateId, + ...parameters, + }, + parentEntity, + parentEntityId, + }; + + await this.sendWhatsAppMessageSynchronously(message); + return message; + } + + async sendWhatsAppMessageSynchronously(message: QueueMessage): Promise { + const phoneNumberId = this.settingService.getConfigValue( + "metaWhatsappPhoneNumberId", + ); + const apiVersion = this.settingService.getConfigValue( + "metaWhatsappApiVersion", + ); + const apiBaseUrl = this.settingService.getConfigValue( + "metaWhatsappApiUrl", + ); + const accessToken = this.settingService.getConfigValue( + "metaWhatsappAccessToken", + ); + + if (!phoneNumberId || !apiVersion || !apiBaseUrl || !accessToken) { + throw new Error( + "Missing Meta WhatsApp configuration. Set metaWhatsappApiUrl, metaWhatsappApiVersion, metaWhatsappPhoneNumberId, metaWhatsappAccessToken.", + ); + } + + const requestBody = this.createWhatsappRequest(message); + const url = `${String(apiBaseUrl).replace(/\/$/, "")}/${apiVersion}/${phoneNumberId}/messages`; + + try { + await this.httpService.axiosRef.post(url, requestBody, { + headers: { + Authorization: `Bearer ${accessToken}`, + "Content-Type": "application/json", + }, + }); + this.logger.debug( + `Sent Meta WhatsApp message to ${message.payload?.to} with type ${requestBody.type}`, + ); + } catch (error) { + const axiosError = error as AxiosError; + const status = axiosError.response?.status; + const data = axiosError.response?.data; + this.logger.error( + `Meta WhatsApp send failed: status=${status ?? "unknown"}, url=${url}, response=${typeof data === "object" ? JSON.stringify(data) : data}`, + ); + throw error; + } + } + + private createWhatsappRequest(message: QueueMessage): any { + const payload = message?.payload ?? {}; + + if (payload?.payload) { + const normalizedFromWrappedPayload = this.normalizeWrappedPayload(payload.payload); + if (normalizedFromWrappedPayload) { + return normalizedFromWrappedPayload; + } + return payload.payload; + } + + const to = this.normalizePhone(payload.to); + const templatePayload: MetaTemplatePayload = payload; + const textPayload: MetaTextPayload = payload; + + if (textPayload.type === "text" || textPayload.text) { + return { + messaging_product: "whatsapp", + recipient_type: "individual", + to, + type: "text", + text: { + body: textPayload.text ?? "", + preview_url: Boolean(textPayload.previewUrl), + }, + }; + } + + const templateName = payload.templateId || templatePayload.templateId || templatePayload.templateName; + if (!templateName) { + throw new Error( + "Meta WhatsApp template name is missing. Provide templateId or parameters.templateName.", + ); + } + + const components: any[] = []; + if (templatePayload.headerText) { + components.push({ + type: "header", + parameters: [ + { + type: "text", + text: templatePayload.headerText, + }, + ], + }); + } else if (templatePayload.imageLink) { + components.push({ + type: "header", + parameters: [ + { + type: "image", + image: { link: templatePayload.imageLink }, + }, + ], + }); + } + + if (Array.isArray(templatePayload.body) && templatePayload.body.length > 0) { + components.push({ + type: "body", + parameters: templatePayload.body.map((entry) => ({ + type: "text", + text: String(entry), + })), + }); + } + + return { + messaging_product: "whatsapp", + recipient_type: "individual", + to, + type: "template", + template: { + name: templateName, + language: { + code: templatePayload.languageCode || "en", + }, + ...(components.length > 0 ? { components } : {}), + }, + }; + } + + private normalizeWrappedPayload(payload: any): any | null { + if (!payload || typeof payload !== "object") { + return null; + } + + if (payload.messaging_product && payload.to && payload.type) { + return payload; + } + + const destination = payload.destination || payload.to; + const message = payload.message || {}; + if (!destination || !message.type) { + return null; + } + + if (message.type === "text" && message.text) { + return { + messaging_product: "whatsapp", + recipient_type: "individual", + to: this.normalizePhone(destination), + type: "text", + text: { + body: String(message.text), + preview_url: false, + }, + }; + } + + if (message.type === "template" && message.template?.id) { + const params = Array.isArray(message.template?.params) + ? message.template.params + : []; + return { + messaging_product: "whatsapp", + recipient_type: "individual", + to: this.normalizePhone(destination), + type: "template", + template: { + name: String(message.template.id), + language: { + code: "en", + }, + ...(params.length > 0 + ? { + components: [ + { + type: "body", + parameters: params.map((entry: any) => ({ + type: "text", + text: String(entry), + })), + }, + ], + } + : {}), + }, + }; + } + + return null; + } + + private normalizePhone(phone: string): string { + const digits = String(phone || "").replace(/\D/g, ""); + if (!digits) { + throw new Error("Destination phone number is required for WhatsApp message."); + } + return digits; + } +} diff --git a/src/solid-core.module.ts b/src/solid-core.module.ts index 0baf971b..a2a640d5 100755 --- a/src/solid-core.module.ts +++ b/src/solid-core.module.ts @@ -1,7 +1,7 @@ -import 'multer'; -import { Global, MiddlewareConsumer, Module, NestModule } from '@nestjs/common'; -import * as express from 'express'; -import { ConfigModule, ConfigService } from '@nestjs/config'; +import "multer"; +import { Global, MiddlewareConsumer, Module, NestModule } from "@nestjs/common"; +import * as express from "express"; +import { ConfigModule, ConfigService } from "@nestjs/config"; import { APP_FILTER, APP_GUARD, @@ -9,136 +9,141 @@ import { DiscoveryService, MetadataScanner, Reflector, -} from '@nestjs/core'; -import { MulterModule } from '@nestjs/platform-express'; -import { TypeOrmModule } from '@nestjs/typeorm'; -import { RemoveFieldsCommand } from './commands/remove-fields.command'; -import { FieldMetadataController } from './controllers/field-metadata.controller'; -import { MediaStorageProviderMetadataController } from './controllers/media-storage-provider-metadata.controller'; -import { ModelMetadataController } from './controllers/model-metadata.controller'; -import { ModuleMetadataController } from './controllers/module-metadata.controller'; -import { TestController } from './controllers/test.controller'; -import { FieldMetadata } from './entities/field-metadata.entity'; -import { ListOfValues } from './entities/list-of-values.entity'; -import { MediaStorageProviderMetadata } from './entities/media-storage-provider-metadata.entity'; -import { Media } from './entities/media.entity'; -import { ModelMetadata } from './entities/model-metadata.entity'; -import { ModuleMetadata } from './entities/module-metadata.entity'; -import { CommandService } from './helpers/command.service'; -import { SchematicService } from './helpers/schematic.service'; -import { ListOfValuesSelectionProvider } from './services/selection-providers/list-of-values-selection-providers.service'; -import { PseudoForeignKeySelectionProvider } from './services/selection-providers/pseudo-foreign-key-selection-provider.service'; -import { ModuleMetadataSeederService } from './seeders/module-metadata-seeder.service'; -import { ModuleTestDataService } from './seeders/module-test-data.service'; -import { CrudHelperService } from './services/crud-helper.service'; -import { FieldMetadataService } from './services/field-metadata.service'; -import { ListOfValuesService } from './services/list-of-values.service'; +} from "@nestjs/core"; +import { MulterModule } from "@nestjs/platform-express"; +import { TypeOrmModule } from "@nestjs/typeorm"; +import { RemoveFieldsCommand } from "./commands/remove-fields.command"; +import { FieldMetadataController } from "./controllers/field-metadata.controller"; +import { MediaStorageProviderMetadataController } from "./controllers/media-storage-provider-metadata.controller"; +import { ModelMetadataController } from "./controllers/model-metadata.controller"; +import { ModuleMetadataController } from "./controllers/module-metadata.controller"; +import { TestController } from "./controllers/test.controller"; +import { FieldMetadata } from "./entities/field-metadata.entity"; +import { ListOfValues } from "./entities/list-of-values.entity"; +import { MediaStorageProviderMetadata } from "./entities/media-storage-provider-metadata.entity"; +import { Media } from "./entities/media.entity"; +import { ModelMetadata } from "./entities/model-metadata.entity"; +import { ModuleMetadata } from "./entities/module-metadata.entity"; +import { CommandService } from "./helpers/command.service"; +import { SchematicService } from "./helpers/schematic.service"; +import { ListOfValuesSelectionProvider } from "./services/selection-providers/list-of-values-selection-providers.service"; +import { PseudoForeignKeySelectionProvider } from "./services/selection-providers/pseudo-foreign-key-selection-provider.service"; +import { ModuleMetadataSeederService } from "./seeders/module-metadata-seeder.service"; +import { ModuleTestDataService } from "./seeders/module-test-data.service"; +import { CrudHelperService } from "./services/crud-helper.service"; +import { FieldMetadataService } from "./services/field-metadata.service"; +import { ListOfValuesService } from "./services/list-of-values.service"; // import { MediaStorageProviderMetadataSeederService } from './services/media-storage-provider-metadata-seeder.service'; -import { MediaStorageProviderMetadataService } from './services/media-storage-provider-metadata.service'; -import { MediaService } from './services/media.service'; -import { ModelMetadataService } from './services/model-metadata.service'; -import { ModuleMetadataService } from './services/module-metadata.service'; -import { SolidIntrospectService } from './services/solid-introspect.service'; +import { MediaStorageProviderMetadataService } from "./services/media-storage-provider-metadata.service"; +import { MediaService } from "./services/media.service"; +import { ModelMetadataService } from "./services/model-metadata.service"; +import { ModuleMetadataService } from "./services/module-metadata.service"; +import { SolidIntrospectService } from "./services/solid-introspect.service"; // import { ListOfComputedFieldProvider } from './providers/list-of-computed-field-provider.service'; -import { ServeStaticModule } from '@nestjs/serve-static'; -import { join } from 'path'; -import { RefreshModelCommand } from './commands/refresh-model.command'; -import { MediaController } from './controllers/media.controller'; +import { ServeStaticModule } from "@nestjs/serve-static"; +import { join } from "path"; +import { RefreshModelCommand } from "./commands/refresh-model.command"; +import { MediaController } from "./controllers/media.controller"; -import { RefreshModuleCommand } from './commands/refresh-module.command'; -import { ModelMetadataSubscriber } from './subscribers/model-metadata.subscriber'; +import { RefreshModuleCommand } from "./commands/refresh-module.command"; +import { ModelMetadataSubscriber } from "./subscribers/model-metadata.subscriber"; -import { ViewMetadataController } from './controllers/view-metadata.controller'; -import { ViewMetadata } from './entities/view-metadata.entity'; -import { ViewMetadataService } from './services/view-metadata.service'; +import { ViewMetadataController } from "./controllers/view-metadata.controller"; +import { ViewMetadata } from "./entities/view-metadata.entity"; +import { ViewMetadataService } from "./services/view-metadata.service"; -import { ActionMetadataController } from './controllers/action-metadata.controller'; -import { ActionMetadata } from './entities/action-metadata.entity'; -import { ActionMetadataService } from './services/action-metadata.service'; +import { ActionMetadataController } from "./controllers/action-metadata.controller"; +import { ActionMetadata } from "./entities/action-metadata.entity"; +import { ActionMetadataService } from "./services/action-metadata.service"; -import { FacebookAuthenticationController } from './controllers/facebook-authentication.controller'; -import { MicrosoftAuthenticationController } from './controllers/microsoft-authentication.controller'; -import { FacebookOAuthStrategy } from './passport-strategies/facebook-oauth.strategy'; -import { MicrosoftOAuthStrategy } from './passport-strategies/microsoft-oauth.strategy'; +import { FacebookAuthenticationController } from "./controllers/facebook-authentication.controller"; +import { MicrosoftAuthenticationController } from "./controllers/microsoft-authentication.controller"; +import { FacebookOAuthStrategy } from "./passport-strategies/facebook-oauth.strategy"; +import { MicrosoftOAuthStrategy } from "./passport-strategies/microsoft-oauth.strategy"; -import { HttpModule } from '@nestjs/axios'; -import { JwtModule } from '@nestjs/jwt'; -import { SeedCommand } from './commands/seed.command'; -import { TestDataCommand } from './commands/test-data.command'; -import { TestRunCommand } from './commands/run-tests.command'; -import { TestCommand } from './commands/test.command'; -import { AuthenticationController } from './controllers/authentication.controller'; -import { EmailTemplateController } from './controllers/email-template.controller'; -import { GoogleAuthenticationController } from './controllers/google-authentication.controller'; -import { MenuItemMetadataController } from './controllers/menu-item-metadata.controller'; -import { MqMessageQueueController } from './controllers/mq-message-queue.controller'; -import { MqMessageController } from './controllers/mq-message.controller'; -import { OTPAuthenticationController } from './controllers/otp-authentication.controller'; -import { ServiceController } from './controllers/service.controller'; -import { SmsTemplateController } from './controllers/sms-template.controller'; -import { TestQueueController } from './controllers/test-queue.controller'; -import { EmailAttachment } from './entities/email-attachment.entity'; -import { EmailTemplate } from './entities/email-template.entity'; -import { MenuItemMetadata } from './entities/menu-item-metadata.entity'; -import { MqMessageQueue } from './entities/mq-message-queue.entity'; -import { MqMessage } from './entities/mq-message.entity'; -import { SmsTemplate } from './entities/sms-template.entity'; -import { AccessTokenGuard } from './guards/access-token.guard'; -import { ApiKeyGuard } from './guards/api-key.guard'; -import { AuthenticationGuard } from './guards/authentication.guard'; -import { PermissionsGuard } from './guards/permissions.guard'; -import { SolidRegistry } from './helpers/solid-registry'; -import { LoggingInterceptor } from './interceptors/logging.interceptor'; -import { ApiEmailQueuePublisher } from './jobs/rabbitmq/api-email-publisher.service'; -import { ApiEmailQueueSubscriber } from './jobs/rabbitmq/api-email-subscriber.service'; -import { TestQueuePublisherDatabase } from './jobs/database/test-queue-publisher-database.service'; -import { TestQueueSubscriberDatabase } from './jobs/database/test-queue-subscriber-database.service'; -import { TestQueuePublisherRedis } from './jobs/redis/test-queue-publisher-redis.service'; -import { TestQueueSubscriberRedis } from './jobs/redis/test-queue-subscriber-redis.service'; -import { Msg91WhatsappQueuePublisher } from './jobs/rabbitmq/msg91-whatsapp-publisher.service'; -import { Msg91WhatsappQueueSubscriber } from './jobs/rabbitmq/msg91-whatsapp-subscriber.service'; -import { Msg91OTPQueuePublisher } from './jobs/rabbitmq/msg91-otp-publisher.service'; -import { Msg91OTPQueueSubscriber } from './jobs/rabbitmq/msg91-otp-subscriber.service'; -import { Msg91SmsQueuePublisher } from './jobs/rabbitmq/msg91-sms-publisher.service'; -import { Msg91SmsQueueSubscriber } from './jobs/rabbitmq/msg91-sms-subscriber.service'; -import { SmtpEmailQueuePublisherRabbitmq } from './jobs/rabbitmq/smtp-email-publisher.service'; -import { SmtpEmailQueueSubscriberRabbitmq } from './jobs/rabbitmq/smtp-email-subscriber.service'; -import { TestQueuePublisher } from './jobs/rabbitmq/test-queue-publisher.service'; -import { TestQueueSubscriber } from './jobs/rabbitmq/test-queue-subscriber.service'; -import { ChatterQueuePublisherRabbitmq } from './jobs/rabbitmq/chatter-queue-publisher.service'; -import { ChatterQueueSubscriberRabbitmq } from './jobs/rabbitmq/chatter-queue-subscriber.service'; -import { ChatterQueuePublisherDatabase } from './jobs/database/chatter-queue-publisher-database.service'; -import { ChatterQueueSubscriberDatabase } from './jobs/database/chatter-queue-subscriber-database.service'; -import { ApiEmailQueuePublisherRedis } from './jobs/redis/api-email-publisher-redis.service'; -import { ApiEmailQueueSubscriberRedis } from './jobs/redis/api-email-subscriber-redis.service'; -import { ChatterQueuePublisherRedis } from './jobs/redis/chatter-queue-publisher-redis.service'; -import { ChatterQueueSubscriberRedis } from './jobs/redis/chatter-queue-subscriber-redis.service'; -import { ComputedFieldEvaluationPublisherRedis } from './jobs/redis/computed-field-evaluation-publisher-redis.service'; -import { ComputedFieldEvaluationSubscriberRedis } from './jobs/redis/computed-field-evaluation-subscriber-redis.service'; -import { GenerateCodePublisherRedis } from './jobs/redis/generate-code-publisher-redis.service'; -import { GenerateCodeSubscriberRedis } from './jobs/redis/generate-code-subscriber-redis.service'; -import { Msg91OTPQueuePublisherRedis } from './jobs/redis/msg91-otp-publisher-redis.service'; -import { Msg91OTPQueueSubscriberRedis } from './jobs/redis/msg91-otp-subscriber-redis.service'; -import { Msg91SmsQueuePublisherRedis } from './jobs/redis/msg91-sms-publisher-redis.service'; -import { Msg91SmsQueueSubscriberRedis } from './jobs/redis/msg91-sms-subscriber-redis.service'; -import { Msg91WhatsappQueuePublisherRedis } from './jobs/redis/msg91-whatsapp-publisher-redis.service'; -import { Msg91WhatsappQueueSubscriberRedis } from './jobs/redis/msg91-whatsapp-subscriber-redis.service'; -import { SmtpEmailQueuePublisherRedis } from './jobs/redis/smtp-email-publisher-redis.service'; -import { SmtpEmailQueueSubscriberRedis } from './jobs/redis/smtp-email-subscriber-redis.service'; -import { Three60WhatsappQueuePublisherRedis } from './jobs/redis/three60-whatsapp-publisher-redis.service'; -import { Three60WhatsappQueueSubscriberRedis } from './jobs/redis/three60-whatsapp-subscriber-redis.service'; -import { TriggerMcpClientPublisherRedis } from './jobs/redis/trigger-mcp-client-publisher-redis.service'; -import { TriggerMcpClientSubscriberRedis } from './jobs/redis/trigger-mcp-client-subscriber-redis.service'; -import { TwilioSmsQueuePublisherRedis } from './jobs/redis/twilio-sms-publisher-redis.service'; -import { TwilioSmsQueueSubscriberRedis } from './jobs/redis/twilio-sms-subscriber-redis.service'; -import { UserRegistrationListener } from './listeners/user-registration.listener'; -import { GoogleOauthStrategy } from './passport-strategies/google-oauth.strategy'; -import { ApiKeyService } from './services/api-key.service'; -import { AuthenticationService } from './services/authentication.service'; -import { BcryptService } from './services/bcrypt.service'; -import { UuidExternalIdEntityComputedFieldProvider } from './services/computed-fields/entity/uuid-externalid-entity-computed-field-provider.service'; -import { UuidExternalIdComputedFieldProvider } from './services/computed-fields/uuid-external-id-computed-field-provider.service'; -import { EmailTemplateService } from './services/email-template.service'; +import { GupshupOtpWhatsappService } from "./services/whatsapp/GupshupOtpWhatsappService"; +import { MetaCloudWhatsappService } from "./services/whatsapp/MetaCloudWhatsappService"; +import { GupshupWebhookController } from "./controllers/gupshup-webhook.controller"; +import { MetaCloudWhatsappWebhookController } from "./controllers/meta-cloud-whatsapp-webhook.controller"; + +import { HttpModule } from "@nestjs/axios"; +import { JwtModule } from "@nestjs/jwt"; +import { SeedCommand } from "./commands/seed.command"; +import { TestDataCommand } from "./commands/test-data.command"; +import { TestRunCommand } from "./commands/run-tests.command"; +import { TestCommand } from "./commands/test.command"; +import { AuthenticationController } from "./controllers/authentication.controller"; +import { EmailTemplateController } from "./controllers/email-template.controller"; +import { GoogleAuthenticationController } from "./controllers/google-authentication.controller"; +import { MenuItemMetadataController } from "./controllers/menu-item-metadata.controller"; +import { MqMessageQueueController } from "./controllers/mq-message-queue.controller"; +import { MqMessageController } from "./controllers/mq-message.controller"; +import { OTPAuthenticationController } from "./controllers/otp-authentication.controller"; +import { ServiceController } from "./controllers/service.controller"; +import { SmsTemplateController } from "./controllers/sms-template.controller"; +import { TestQueueController } from "./controllers/test-queue.controller"; +import { EmailAttachment } from "./entities/email-attachment.entity"; +import { EmailTemplate } from "./entities/email-template.entity"; +import { MenuItemMetadata } from "./entities/menu-item-metadata.entity"; +import { MqMessageQueue } from "./entities/mq-message-queue.entity"; +import { MqMessage } from "./entities/mq-message.entity"; +import { SmsTemplate } from "./entities/sms-template.entity"; +import { AccessTokenGuard } from "./guards/access-token.guard"; +import { ApiKeyGuard } from "./guards/api-key.guard"; +import { AuthenticationGuard } from "./guards/authentication.guard"; +import { PermissionsGuard } from "./guards/permissions.guard"; +import { SolidRegistry } from "./helpers/solid-registry"; +import { LoggingInterceptor } from "./interceptors/logging.interceptor"; +import { ApiEmailQueuePublisher } from "./jobs/rabbitmq/api-email-publisher.service"; +import { ApiEmailQueueSubscriber } from "./jobs/rabbitmq/api-email-subscriber.service"; +import { TestQueuePublisherDatabase } from "./jobs/database/test-queue-publisher-database.service"; +import { TestQueueSubscriberDatabase } from "./jobs/database/test-queue-subscriber-database.service"; +import { TestQueuePublisherRedis } from "./jobs/redis/test-queue-publisher-redis.service"; +import { TestQueueSubscriberRedis } from "./jobs/redis/test-queue-subscriber-redis.service"; +import { Msg91WhatsappQueuePublisher } from "./jobs/rabbitmq/msg91-whatsapp-publisher.service"; +import { Msg91WhatsappQueueSubscriber } from "./jobs/rabbitmq/msg91-whatsapp-subscriber.service"; +import { Msg91OTPQueuePublisher } from "./jobs/rabbitmq/msg91-otp-publisher.service"; +import { Msg91OTPQueueSubscriber } from "./jobs/rabbitmq/msg91-otp-subscriber.service"; +import { Msg91SmsQueuePublisher } from "./jobs/rabbitmq/msg91-sms-publisher.service"; +import { Msg91SmsQueueSubscriber } from "./jobs/rabbitmq/msg91-sms-subscriber.service"; +import { SmtpEmailQueuePublisherRabbitmq } from "./jobs/rabbitmq/smtp-email-publisher.service"; +import { SmtpEmailQueueSubscriberRabbitmq } from "./jobs/rabbitmq/smtp-email-subscriber.service"; +import { TestQueuePublisher } from "./jobs/rabbitmq/test-queue-publisher.service"; +import { TestQueueSubscriber } from "./jobs/rabbitmq/test-queue-subscriber.service"; +import { ChatterQueuePublisherRabbitmq } from "./jobs/rabbitmq/chatter-queue-publisher.service"; +import { ChatterQueueSubscriberRabbitmq } from "./jobs/rabbitmq/chatter-queue-subscriber.service"; +import { ChatterQueuePublisherDatabase } from "./jobs/database/chatter-queue-publisher-database.service"; +import { ChatterQueueSubscriberDatabase } from "./jobs/database/chatter-queue-subscriber-database.service"; +import { ApiEmailQueuePublisherRedis } from "./jobs/redis/api-email-publisher-redis.service"; +import { ApiEmailQueueSubscriberRedis } from "./jobs/redis/api-email-subscriber-redis.service"; +import { ChatterQueuePublisherRedis } from "./jobs/redis/chatter-queue-publisher-redis.service"; +import { ChatterQueueSubscriberRedis } from "./jobs/redis/chatter-queue-subscriber-redis.service"; +import { ComputedFieldEvaluationPublisherRedis } from "./jobs/redis/computed-field-evaluation-publisher-redis.service"; +import { ComputedFieldEvaluationSubscriberRedis } from "./jobs/redis/computed-field-evaluation-subscriber-redis.service"; +import { GenerateCodePublisherRedis } from "./jobs/redis/generate-code-publisher-redis.service"; +import { GenerateCodeSubscriberRedis } from "./jobs/redis/generate-code-subscriber-redis.service"; +import { Msg91OTPQueuePublisherRedis } from "./jobs/redis/msg91-otp-publisher-redis.service"; +import { Msg91OTPQueueSubscriberRedis } from "./jobs/redis/msg91-otp-subscriber-redis.service"; +import { Msg91SmsQueuePublisherRedis } from "./jobs/redis/msg91-sms-publisher-redis.service"; +import { Msg91SmsQueueSubscriberRedis } from "./jobs/redis/msg91-sms-subscriber-redis.service"; +import { Msg91WhatsappQueuePublisherRedis } from "./jobs/redis/msg91-whatsapp-publisher-redis.service"; +import { Msg91WhatsappQueueSubscriberRedis } from "./jobs/redis/msg91-whatsapp-subscriber-redis.service"; +import { SmtpEmailQueuePublisherRedis } from "./jobs/redis/smtp-email-publisher-redis.service"; +import { SmtpEmailQueueSubscriberRedis } from "./jobs/redis/smtp-email-subscriber-redis.service"; +import { Three60WhatsappQueuePublisherRedis } from "./jobs/redis/three60-whatsapp-publisher-redis.service"; +import { Three60WhatsappQueueSubscriberRedis } from "./jobs/redis/three60-whatsapp-subscriber-redis.service"; +import { TriggerMcpClientPublisherRedis } from "./jobs/redis/trigger-mcp-client-publisher-redis.service"; +import { TriggerMcpClientSubscriberRedis } from "./jobs/redis/trigger-mcp-client-subscriber-redis.service"; +import { TwilioSmsQueuePublisherRedis } from "./jobs/redis/twilio-sms-publisher-redis.service"; +import { TwilioSmsQueueSubscriberRedis } from "./jobs/redis/twilio-sms-subscriber-redis.service"; +import { UserRegistrationListener } from "./listeners/user-registration.listener"; +import { GoogleOauthStrategy } from "./passport-strategies/google-oauth.strategy"; +import { ApiKeyService } from "./services/api-key.service"; +import { AuthenticationService } from "./services/authentication.service"; +import { BcryptService } from "./services/bcrypt.service"; +import { UuidExternalIdEntityComputedFieldProvider } from "./services/computed-fields/entity/uuid-externalid-entity-computed-field-provider.service"; +import { UuidExternalIdComputedFieldProvider } from "./services/computed-fields/uuid-external-id-computed-field-provider.service"; +import { EmailTemplateService } from "./services/email-template.service"; import { DiskFileService, S3FileService, @@ -146,38 +151,38 @@ import { DiskStoragePathBuilder, S3StoragePathBuilder, StoragePathBuilderFactory, -} from './services/file'; -import { HashingService } from './services/hashing.service'; -import { ElasticEmailService } from './services/mail/elastic-email.service'; -import { SMTPEMailService } from './services/mail/smtp-email.service'; -import { MenuItemMetadataService } from './services/menu-item-metadata.service'; -import { MqMessageQueueService } from './services/mq-message-queue.service'; -import { MqMessageService } from './services/mq-message.service'; -import { PdfService } from './services/pdf.service'; -import { RefreshTokenIdsStorageService } from './services/refresh-token-ids-storage.service'; -import { SsoCodeStorageService } from './services/sso-code-storage.service'; -import { ListOfModelsSelectionProvider } from './services/selection-providers/list-of-models-selection-provider.service'; -import { TinyUrlService } from './services/short-url/tiny-url.service'; -import { SmsTemplateService } from './services/sms-template.service'; -import { Msg91OTPService } from './services/sms/Msg91OTPService'; -import { Msg91SMSService } from './services/sms/Msg91SMSService'; +} from "./services/file"; +import { HashingService } from "./services/hashing.service"; +import { ElasticEmailService } from "./services/mail/elastic-email.service"; +import { SMTPEMailService } from "./services/mail/smtp-email.service"; +import { MenuItemMetadataService } from "./services/menu-item-metadata.service"; +import { MqMessageQueueService } from "./services/mq-message-queue.service"; +import { MqMessageService } from "./services/mq-message.service"; +import { PdfService } from "./services/pdf.service"; +import { RefreshTokenIdsStorageService } from "./services/refresh-token-ids-storage.service"; +import { SsoCodeStorageService } from "./services/sso-code-storage.service"; +import { ListOfModelsSelectionProvider } from "./services/selection-providers/list-of-models-selection-provider.service"; +import { TinyUrlService } from "./services/short-url/tiny-url.service"; +import { SmsTemplateService } from "./services/sms-template.service"; +import { Msg91OTPService } from "./services/sms/Msg91OTPService"; +import { Msg91SMSService } from "./services/sms/Msg91SMSService"; // import { UserService } from './services/user.service'; -import { Msg91WhatsappService } from './services/whatsapp/Msg91WhatsappService'; -import { SoftDeleteAwareEventSubscriber } from './subscribers/soft-delete-aware-event.subscriber'; +import { Msg91WhatsappService } from "./services/whatsapp/Msg91WhatsappService"; +import { SoftDeleteAwareEventSubscriber } from "./subscribers/soft-delete-aware-event.subscriber"; -import { PermissionMetadataController } from './controllers/permission-metadata.controller'; -import { PermissionMetadata } from './entities/permission-metadata.entity'; -import { PermissionMetadataService } from './services/permission-metadata.service'; +import { PermissionMetadataController } from "./controllers/permission-metadata.controller"; +import { PermissionMetadata } from "./entities/permission-metadata.entity"; +import { PermissionMetadataService } from "./services/permission-metadata.service"; -import { ScheduleModule } from '@nestjs/schedule'; -import { ClsModule } from 'nestjs-cls'; -import { AiInteractionController } from './controllers/ai-interaction.controller'; -import { ChatterMessageDetailsController } from './controllers/chatter-message-details.controller'; -import { ChatterMessageController } from './controllers/chatter-message.controller'; -import { DashboardQuestionSqlDatasetConfigController } from './controllers/dashboard-question-sql-dataset-config.controller'; -import { DashboardQuestionController } from './controllers/dashboard-question.controller'; -import { DashboardVariableController } from './controllers/dashboard-variable.controller'; -import { DashboardLayoutController } from './controllers/dashboard-layout.controller'; +import { ScheduleModule } from "@nestjs/schedule"; +import { ClsModule } from "nestjs-cls"; +import { AiInteractionController } from "./controllers/ai-interaction.controller"; +import { ChatterMessageDetailsController } from "./controllers/chatter-message-details.controller"; +import { ChatterMessageController } from "./controllers/chatter-message.controller"; +import { DashboardQuestionSqlDatasetConfigController } from "./controllers/dashboard-question-sql-dataset-config.controller"; +import { DashboardQuestionController } from "./controllers/dashboard-question.controller"; +import { DashboardVariableController } from "./controllers/dashboard-variable.controller"; +import { DashboardLayoutController } from "./controllers/dashboard-layout.controller"; import { DashboardController } from './controllers/dashboard.controller'; import { ExportTemplateController } from './controllers/export-template.controller'; @@ -241,41 +246,41 @@ import { Msg91SmsQueueSubscriberDatabase } from './jobs/database/msg91-sms-subsc import { SmtpEmailQueuePublisherDatabase } from './jobs/database/smtp-email-publisher-database.service'; import { SmtpEmailQueueSubscriberDatabase } from './jobs/database/smtp-email-subscriber-database.service'; -import { TwilioSmsQueuePublisherDatabase } from './jobs/database/twilio-sms-publisher-database.service'; -import { TwilioSmsQueueSubscriberDatabase } from './jobs/database/twilio-sms-subscriber-database.service'; +import { TwilioSmsQueuePublisherDatabase } from "./jobs/database/twilio-sms-publisher-database.service"; +import { TwilioSmsQueueSubscriberDatabase } from "./jobs/database/twilio-sms-subscriber-database.service"; // import { ThrottlerModule } from '@nestjs/throttler'; -import { IngestCommand } from './commands/ingest.command'; -import { MailFactory } from './factories/mail.factory'; -import { ErrorMapperService } from './helpers/error-mapper.service'; -import { SolidCoreErrorCodesProvider } from './helpers/solid-core-error-codes-provider.service'; -import { ComputedFieldEvaluationPublisherRabbitmq } from './jobs/rabbitmq/computed-field-evaluation-publisher.service'; -import { ComputedFieldEvaluationSubscriberRabbitmq } from './jobs/rabbitmq/computed-field-evaluation-subscriber.service'; -import { Msg91WhatsappQueuePublisherDatabase } from './jobs/database/msg91-whatsapp-publisher-database.service'; -import { Msg91WhatsappQueueSubscriberDatabase } from './jobs/database/msg91-whatsapp-subscriber-database.service'; -import { Three60WhatsappQueuePublisherDatabase } from './jobs/database/three60-whatsapp-publisher-database.service'; -import { Three60WhatsappQueueSubscriberDatabase } from './jobs/database/three60-whatsapp-subscriber-database.service'; -import { TriggerMcpClientPublisherDatabase } from './jobs/database/trigger-mcp-client-publisher-database.service'; -import { TriggerMcpClientSubscriberDatabase } from './jobs/database/trigger-mcp-client-subscriber-database.service'; -import { GenerateCodePublisherRabbitmq } from './jobs/rabbitmq/generate-code-publisher.service'; -import { GenerateCodeSubscriberRabbitmq } from './jobs/rabbitmq/generate-code-subscriber.service'; -import { Three60WhatsappQueuePublisher } from './jobs/rabbitmq/three60-whatsapp-publisher.service'; -import { Three60WhatsappQueueSubscriber } from './jobs/rabbitmq/three60-whatsapp-subscriber.service'; -import { TriggerMcpClientPublisherRabbitmq } from './jobs/rabbitmq/trigger-mcp-client-publisher.service'; -import { TriggerMcpClientSubscriberRabbitmq } from './jobs/rabbitmq/trigger-mcp-client-subscriber.service'; -import { TwilioSmsQueuePublisherRabbitmq } from './jobs/rabbitmq/twilio-sms-publisher.service'; -import { TwilioSmsQueueSubscriberRabbitmq } from './jobs/rabbitmq/twilio-sms-subscriber.service'; -import { DashboardMapper } from './mappers/dashboard-mapper'; -import { ListOfValuesMapper } from './mappers/list-of-values-mapper'; -import { ActionMetadataRepository } from './repository/action-metadata.repository'; -import { AiInteractionRepository } from './repository/ai-interaction.repository'; -import { ChatterMessageDetailsRepository } from './repository/chatter-message-details.repository'; -import { ChatterMessageRepository } from './repository/chatter-message.repository'; -import { DashboardQuestionSqlDatasetConfigRepository } from './repository/dashboard-question-sql-dataset-config.repository'; -import { DashboardQuestionRepository } from './repository/dashboard-question.repository'; -import { DashboardVariableRepository } from './repository/dashboard-variable.repository'; -import { DashboardRepository } from './repository/dashboard.repository'; -import { DashboardLayoutRepository } from './repository/dashboard-layout.repository'; +import { IngestCommand } from "./commands/ingest.command"; +import { MailFactory } from "./factories/mail.factory"; +import { ErrorMapperService } from "./helpers/error-mapper.service"; +import { SolidCoreErrorCodesProvider } from "./helpers/solid-core-error-codes-provider.service"; +import { ComputedFieldEvaluationPublisherRabbitmq } from "./jobs/rabbitmq/computed-field-evaluation-publisher.service"; +import { ComputedFieldEvaluationSubscriberRabbitmq } from "./jobs/rabbitmq/computed-field-evaluation-subscriber.service"; +import { Msg91WhatsappQueuePublisherDatabase } from "./jobs/database/msg91-whatsapp-publisher-database.service"; +import { Msg91WhatsappQueueSubscriberDatabase } from "./jobs/database/msg91-whatsapp-subscriber-database.service"; +import { Three60WhatsappQueuePublisherDatabase } from "./jobs/database/three60-whatsapp-publisher-database.service"; +import { Three60WhatsappQueueSubscriberDatabase } from "./jobs/database/three60-whatsapp-subscriber-database.service"; +import { TriggerMcpClientPublisherDatabase } from "./jobs/database/trigger-mcp-client-publisher-database.service"; +import { TriggerMcpClientSubscriberDatabase } from "./jobs/database/trigger-mcp-client-subscriber-database.service"; +import { GenerateCodePublisherRabbitmq } from "./jobs/rabbitmq/generate-code-publisher.service"; +import { GenerateCodeSubscriberRabbitmq } from "./jobs/rabbitmq/generate-code-subscriber.service"; +import { Three60WhatsappQueuePublisher } from "./jobs/rabbitmq/three60-whatsapp-publisher.service"; +import { Three60WhatsappQueueSubscriber } from "./jobs/rabbitmq/three60-whatsapp-subscriber.service"; +import { TriggerMcpClientPublisherRabbitmq } from "./jobs/rabbitmq/trigger-mcp-client-publisher.service"; +import { TriggerMcpClientSubscriberRabbitmq } from "./jobs/rabbitmq/trigger-mcp-client-subscriber.service"; +import { TwilioSmsQueuePublisherRabbitmq } from "./jobs/rabbitmq/twilio-sms-publisher.service"; +import { TwilioSmsQueueSubscriberRabbitmq } from "./jobs/rabbitmq/twilio-sms-subscriber.service"; +import { DashboardMapper } from "./mappers/dashboard-mapper"; +import { ListOfValuesMapper } from "./mappers/list-of-values-mapper"; +import { ActionMetadataRepository } from "./repository/action-metadata.repository"; +import { AiInteractionRepository } from "./repository/ai-interaction.repository"; +import { ChatterMessageDetailsRepository } from "./repository/chatter-message-details.repository"; +import { ChatterMessageRepository } from "./repository/chatter-message.repository"; +import { DashboardQuestionSqlDatasetConfigRepository } from "./repository/dashboard-question-sql-dataset-config.repository"; +import { DashboardQuestionRepository } from "./repository/dashboard-question.repository"; +import { DashboardVariableRepository } from "./repository/dashboard-variable.repository"; +import { DashboardRepository } from "./repository/dashboard.repository"; +import { DashboardLayoutRepository } from "./repository/dashboard-layout.repository"; import { EmailTemplateRepository } from './repository/email-template.repository'; import { ExportTemplateRepository } from './repository/export-template.repository'; @@ -446,13 +451,13 @@ import { Entity } from 'typeorm'; CacheModule.registerAsync(CacheManagerOptions), ScheduleModule.forRoot(), ServeStaticModule.forRoot({ - rootPath: join(process.cwd(), 'media-files-storage'), - serveRoot: '/media-files-storage', + rootPath: join(process.cwd(), "media-files-storage"), + serveRoot: "/media-files-storage", serveStaticOptions: { setHeaders: (res /*, path, stat*/) => { // Allow use of these files from a different origin (e.g., :3000 UI) // Use 'same-site' if both origins are on the same site (localhost:* counts as same-site) - res.setHeader('Cross-Origin-Resource-Policy', 'cross-origin'); // or 'same-site' + res.setHeader("Cross-Origin-Resource-Policy", "cross-origin"); // or 'same-site' // If you need to load into without tainting or fetch images via XHR, // you can also expose CORS here (not needed for simple ): @@ -463,7 +468,7 @@ import { Entity } from 'typeorm'; MulterModule.registerAsync({ imports: [ConfigModule], useFactory: async (configService: ConfigService) => ({ - dest: process.env.AB_MEDIA_UPLOAD_DIR ?? 'media-uploads', + dest: process.env.AB_MEDIA_UPLOAD_DIR ?? "media-uploads", }), inject: [ConfigService], }), @@ -503,6 +508,8 @@ import { Entity } from 'typeorm'; ModuleMetadataController, MqMessageController, MqMessageQueueController, + GupshupWebhookController, + MetaCloudWhatsappWebhookController, OTPAuthenticationController, PermissionMetadataController, RoleMetadataController, @@ -599,6 +606,8 @@ import { Entity } from 'typeorm'; Msg91SMSService, Msg91OTPService, Msg91WhatsappService, + MetaCloudWhatsappService, + GupshupOtpWhatsappService, TwilioSMSService, SmsTemplateService, EmailTemplateService, @@ -897,9 +906,9 @@ export class SolidCoreModule implements NestModule { configure(consumer: MiddlewareConsumer) { consumer .apply( - express.json({ limit: '10mb' }), - express.urlencoded({ limit: '10mb', extended: true }), + express.json({ limit: "10mb" }), + express.urlencoded({ limit: "10mb", extended: true }), ) - .forRoutes('*'); + .forRoutes("*"); } }