feat: Add Meta WAP Credentials management
- Introduced Meta WAP Credentials model and interface for managing WhatsApp Business credentials. - Implemented Mongoose adapter for Meta WAP Credentials with upsert and find functionalities. - Created service layer for handling Meta WAP Credentials operations including save, find, test, and disable. - Added API controllers for Meta WAP Credentials with endpoints for saving, finding, testing, and disabling credentials. - Updated Notifications Manager to utilize Meta WAP Credentials for sending WhatsApp messages. - Enhanced the front-end to manage Meta WAP Credentials with forms for input and validation. - Implemented UI components for displaying and interacting with Meta WAP Credentials.
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
import { Document, FilterQuery, Model, Schema, model } from "mongoose";
|
||||
import {
|
||||
IMetaWapCredential,
|
||||
IMetaWapCredentialAdapter,
|
||||
META_WAP_CREDENTIAL_STATUS,
|
||||
} from "./MetaWapCredentials.Interface";
|
||||
|
||||
export interface IMetaWapCredentialDocument extends Omit<IMetaWapCredential, "id">, Document {}
|
||||
|
||||
export class MetaWapCredentialsAdapterMongoose implements IMetaWapCredentialAdapter {
|
||||
schema: Schema;
|
||||
metaWapCredentialList: Model<IMetaWapCredentialDocument>;
|
||||
|
||||
constructor() {
|
||||
this.schema = new Schema({
|
||||
companyId: {
|
||||
type: Schema.Types.ObjectId,
|
||||
required: true,
|
||||
ref: "Company",
|
||||
unique: true,
|
||||
index: true,
|
||||
},
|
||||
phoneNumberId: { type: String, required: true },
|
||||
wabaId: { type: String, required: true },
|
||||
encryptedAccessToken: { type: String, required: true },
|
||||
accessTokenIv: { type: String, required: true },
|
||||
accessTokenAuthTag: { type: String, required: true },
|
||||
encryptionKeyVersion: { type: String, required: false, default: "v1" },
|
||||
status: {
|
||||
type: String,
|
||||
required: true,
|
||||
enum: Object.values(META_WAP_CREDENTIAL_STATUS),
|
||||
default: META_WAP_CREDENTIAL_STATUS.CONFIGURED,
|
||||
index: true,
|
||||
},
|
||||
lastTestedAt: { type: Date, required: false },
|
||||
lastTestError: { type: String, required: false },
|
||||
createdAt: { type: Date, required: true, default: Date.now },
|
||||
updatedAt: { type: Date, required: true, default: Date.now },
|
||||
});
|
||||
|
||||
this.metaWapCredentialList = model<IMetaWapCredentialDocument>("MetaWapCredential", this.schema);
|
||||
}
|
||||
|
||||
private buildSearchCriteria(filters: { companyId?: string }): FilterQuery<IMetaWapCredential> {
|
||||
const searchCriteria: FilterQuery<IMetaWapCredential> = {};
|
||||
|
||||
if (filters.companyId) searchCriteria.companyId = filters.companyId;
|
||||
|
||||
return searchCriteria;
|
||||
}
|
||||
|
||||
public async findOne(filters: { companyId?: string }): Promise<IMetaWapCredentialDocument | null> {
|
||||
const searchCriteria = this.buildSearchCriteria(filters);
|
||||
return this.metaWapCredentialList.findOne(searchCriteria).exec();
|
||||
}
|
||||
|
||||
public async upsert(data: IMetaWapCredential): Promise<IMetaWapCredential> {
|
||||
const { companyId, ...updateData } = data;
|
||||
|
||||
return this.metaWapCredentialList
|
||||
.findOneAndUpdate({ companyId }, { ...updateData, updatedAt: Date.now() }, { new: true, upsert: true })
|
||||
.exec();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
import { IMetaWapCredentialDocument } from "./MetaWapCredentials.Adapter.Mongoose";
|
||||
|
||||
export enum META_WAP_CREDENTIAL_STATUS {
|
||||
CONFIGURED = "CONFIGURED",
|
||||
TESTED = "TESTED",
|
||||
INVALID = "INVALID",
|
||||
DISABLED = "DISABLED",
|
||||
}
|
||||
|
||||
export type SaveMetaWapCredentialParams = {
|
||||
companyId: string;
|
||||
phoneNumberId: string;
|
||||
wabaId: string;
|
||||
accessToken: string;
|
||||
sessionUser: string;
|
||||
};
|
||||
|
||||
export type FindMetaWapCredentialParams = {
|
||||
companyId: string;
|
||||
sessionUser: string;
|
||||
};
|
||||
|
||||
export type TestMetaWapCredentialParams = {
|
||||
companyId: string;
|
||||
phoneNumber: string;
|
||||
templateName: string;
|
||||
languageCode: string;
|
||||
templateVariables?: string[];
|
||||
sessionUser: string;
|
||||
};
|
||||
|
||||
export type DisableMetaWapCredentialParams = {
|
||||
companyId: string;
|
||||
sessionUser: string;
|
||||
};
|
||||
|
||||
export interface IMetaWapCredential {
|
||||
id?: string;
|
||||
companyId: string;
|
||||
phoneNumberId: string;
|
||||
wabaId: string;
|
||||
encryptedAccessToken: string;
|
||||
accessTokenIv: string;
|
||||
accessTokenAuthTag: string;
|
||||
encryptionKeyVersion?: string;
|
||||
status: META_WAP_CREDENTIAL_STATUS;
|
||||
lastTestedAt?: Date;
|
||||
lastTestError?: string;
|
||||
createdAt?: Date;
|
||||
updatedAt?: Date;
|
||||
}
|
||||
|
||||
export type MetaWapCredentialView = Omit<
|
||||
IMetaWapCredential,
|
||||
"encryptedAccessToken" | "accessTokenIv" | "accessTokenAuthTag"
|
||||
>;
|
||||
|
||||
export type TestMetaWapCredentialResult = {
|
||||
success: boolean;
|
||||
message: string;
|
||||
testedAt: Date;
|
||||
};
|
||||
|
||||
export interface IMetaWapCredentialAdapter {
|
||||
findOne(filters: { companyId?: string }): Promise<IMetaWapCredentialDocument | null>;
|
||||
upsert(data: IMetaWapCredential): Promise<IMetaWapCredential>;
|
||||
}
|
||||
|
||||
export interface IMetaWapCredentialManager {
|
||||
metaWapCredentials: IMetaWapCredentialAdapter;
|
||||
save(data: SaveMetaWapCredentialParams): Promise<MetaWapCredentialView>;
|
||||
find(data: FindMetaWapCredentialParams): Promise<MetaWapCredentialView>;
|
||||
test(data: TestMetaWapCredentialParams): Promise<TestMetaWapCredentialResult>;
|
||||
disable(data: DisableMetaWapCredentialParams): Promise<MetaWapCredentialView>;
|
||||
}
|
||||
@@ -0,0 +1,242 @@
|
||||
import crypto from "crypto";
|
||||
import { validatePermissionsByCompany, validateSessionUser } from "../../helpers/check";
|
||||
import { NotificationsManager } from "../Notifications/Notifications";
|
||||
import { MetaWapCredentialsAdapterMongoose } from "./MetaWapCredentials.Adapter.Mongoose";
|
||||
import {
|
||||
DisableMetaWapCredentialParams,
|
||||
FindMetaWapCredentialParams,
|
||||
IMetaWapCredential,
|
||||
IMetaWapCredentialManager,
|
||||
META_WAP_CREDENTIAL_STATUS,
|
||||
MetaWapCredentialView,
|
||||
SaveMetaWapCredentialParams,
|
||||
TestMetaWapCredentialParams,
|
||||
TestMetaWapCredentialResult,
|
||||
} from "./MetaWapCredentials.Interface";
|
||||
|
||||
export type MetaWapConnectionCredentials = {
|
||||
phoneNumberId: string;
|
||||
authToken: string;
|
||||
};
|
||||
|
||||
class MetaWapCredentialsManager implements IMetaWapCredentialManager {
|
||||
metaWapCredentials: MetaWapCredentialsAdapterMongoose;
|
||||
|
||||
constructor() {
|
||||
this.metaWapCredentials = new MetaWapCredentialsAdapterMongoose();
|
||||
}
|
||||
|
||||
private getMasterKey(): Buffer {
|
||||
const configuredKey = process.env.META_CREDENTIALS_MASTER_KEY;
|
||||
|
||||
if (!configuredKey) {
|
||||
throw new Error("META_CREDENTIALS_MASTER_KEY no está configurada");
|
||||
}
|
||||
|
||||
const key = /^[a-fA-F0-9]{64}$/.test(configuredKey)
|
||||
? Buffer.from(configuredKey, "hex")
|
||||
: Buffer.from(configuredKey, "base64");
|
||||
|
||||
if (key.length !== 32) {
|
||||
throw new Error("META_CREDENTIALS_MASTER_KEY debe tener 32 bytes en base64 o 64 caracteres hexadecimales");
|
||||
}
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
private encryptAccessToken(accessToken: string): {
|
||||
encryptedAccessToken: string;
|
||||
accessTokenIv: string;
|
||||
accessTokenAuthTag: string;
|
||||
} {
|
||||
const iv = crypto.randomBytes(12);
|
||||
const cipher = crypto.createCipheriv(
|
||||
"aes-256-gcm",
|
||||
crypto.createSecretKey(new Uint8Array(this.getMasterKey())),
|
||||
new Uint8Array(iv)
|
||||
);
|
||||
const encryptedAccessToken = cipher.update(accessToken, "utf8", "base64") + cipher.final("base64");
|
||||
|
||||
return {
|
||||
encryptedAccessToken,
|
||||
accessTokenIv: iv.toString("base64"),
|
||||
accessTokenAuthTag: cipher.getAuthTag().toString("base64"),
|
||||
};
|
||||
}
|
||||
|
||||
private decryptAccessToken(credential: IMetaWapCredential): string {
|
||||
const decipher = crypto.createDecipheriv(
|
||||
"aes-256-gcm",
|
||||
crypto.createSecretKey(new Uint8Array(this.getMasterKey())),
|
||||
new Uint8Array(Buffer.from(credential.accessTokenIv, "base64"))
|
||||
);
|
||||
decipher.setAuthTag(new Uint8Array(Buffer.from(credential.accessTokenAuthTag, "base64")));
|
||||
|
||||
return decipher.update(credential.encryptedAccessToken, "base64", "utf8") + decipher.final("utf8");
|
||||
}
|
||||
|
||||
private sanitizeCredential(credential: IMetaWapCredential): MetaWapCredentialView {
|
||||
const rawCredential = typeof (credential as any).toObject === "function"
|
||||
? (credential as any).toObject()
|
||||
: credential;
|
||||
const { encryptedAccessToken, accessTokenIv, accessTokenAuthTag, ...safeCredential } = rawCredential;
|
||||
return safeCredential;
|
||||
}
|
||||
|
||||
private async validateCompanyAdmin(companyId: string, sessionUser: string): Promise<void> {
|
||||
await validateSessionUser({ sessionUser });
|
||||
|
||||
if (!companyId || !companyId.trim()) {
|
||||
throw new Error("Se requiere una organización para operar credenciales de Meta");
|
||||
}
|
||||
|
||||
await validatePermissionsByCompany({ companyId, sessionUser });
|
||||
}
|
||||
|
||||
private async requireConfiguredCredentials(companyId: string): Promise<IMetaWapCredential> {
|
||||
const credential = await this.metaWapCredentials.findOne({ companyId });
|
||||
|
||||
if (!credential) {
|
||||
throw new Error("La organización no tiene credenciales de Meta configuradas");
|
||||
}
|
||||
|
||||
if (credential.status === META_WAP_CREDENTIAL_STATUS.DISABLED) {
|
||||
throw new Error("Las credenciales de Meta están desactivadas");
|
||||
}
|
||||
|
||||
return credential;
|
||||
}
|
||||
|
||||
public async save(data: SaveMetaWapCredentialParams): Promise<MetaWapCredentialView> {
|
||||
await this.validateCompanyAdmin(data.companyId, data.sessionUser);
|
||||
|
||||
if (!data.phoneNumberId || !data.phoneNumberId.trim()) {
|
||||
throw new Error("phoneNumberId es obligatorio");
|
||||
}
|
||||
|
||||
if (!data.wabaId || !data.wabaId.trim()) {
|
||||
throw new Error("wabaId es obligatorio");
|
||||
}
|
||||
|
||||
if (!data.accessToken || !data.accessToken.trim()) {
|
||||
throw new Error("accessToken es obligatorio");
|
||||
}
|
||||
|
||||
const { encryptedAccessToken, accessTokenIv, accessTokenAuthTag } = this.encryptAccessToken(data.accessToken);
|
||||
|
||||
const credential = await this.metaWapCredentials.upsert({
|
||||
companyId: data.companyId,
|
||||
phoneNumberId: data.phoneNumberId,
|
||||
wabaId: data.wabaId,
|
||||
encryptedAccessToken,
|
||||
accessTokenIv,
|
||||
accessTokenAuthTag,
|
||||
encryptionKeyVersion: process.env.META_CREDENTIALS_KEY_VERSION || "v1",
|
||||
status: META_WAP_CREDENTIAL_STATUS.CONFIGURED,
|
||||
});
|
||||
|
||||
return this.sanitizeCredential(credential);
|
||||
}
|
||||
|
||||
public async find(data: FindMetaWapCredentialParams): Promise<MetaWapCredentialView> {
|
||||
await this.validateCompanyAdmin(data.companyId, data.sessionUser);
|
||||
|
||||
const credential = await this.metaWapCredentials.findOne({ companyId: data.companyId });
|
||||
|
||||
if (!credential) {
|
||||
throw new Error("La organización no tiene credenciales de Meta configuradas");
|
||||
}
|
||||
|
||||
return this.sanitizeCredential(credential);
|
||||
}
|
||||
|
||||
public async test(data: TestMetaWapCredentialParams): Promise<TestMetaWapCredentialResult> {
|
||||
await this.validateCompanyAdmin(data.companyId, data.sessionUser);
|
||||
|
||||
const credential = await this.requireConfiguredCredentials(data.companyId);
|
||||
|
||||
const authToken = this.decryptAccessToken(credential);
|
||||
|
||||
try {
|
||||
await NotificationsManager.sendWhatsAppTemplate(
|
||||
{
|
||||
companyId: data.companyId,
|
||||
phoneNumber: data.phoneNumber,
|
||||
templateName: data.templateName,
|
||||
languageCode: data.languageCode,
|
||||
templateVariables: data.templateVariables,
|
||||
},
|
||||
{
|
||||
phoneNumberId: credential.phoneNumberId,
|
||||
authToken,
|
||||
}
|
||||
);
|
||||
|
||||
await this.metaWapCredentials.upsert({
|
||||
companyId: data.companyId,
|
||||
phoneNumberId: credential.phoneNumberId,
|
||||
wabaId: credential.wabaId,
|
||||
encryptedAccessToken: credential.encryptedAccessToken,
|
||||
accessTokenIv: credential.accessTokenIv,
|
||||
accessTokenAuthTag: credential.accessTokenAuthTag,
|
||||
encryptionKeyVersion: credential.encryptionKeyVersion,
|
||||
status: META_WAP_CREDENTIAL_STATUS.TESTED,
|
||||
lastTestedAt: new Date(),
|
||||
lastTestError: undefined,
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Test sent successfully",
|
||||
testedAt: new Date(),
|
||||
};
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
|
||||
await this.metaWapCredentials.upsert({
|
||||
companyId: data.companyId,
|
||||
phoneNumberId: credential.phoneNumberId,
|
||||
wabaId: credential.wabaId,
|
||||
encryptedAccessToken: credential.encryptedAccessToken,
|
||||
accessTokenIv: credential.accessTokenIv,
|
||||
accessTokenAuthTag: credential.accessTokenAuthTag,
|
||||
encryptionKeyVersion: credential.encryptionKeyVersion,
|
||||
status: META_WAP_CREDENTIAL_STATUS.INVALID,
|
||||
lastTestedAt: new Date(),
|
||||
lastTestError: errorMessage,
|
||||
});
|
||||
|
||||
throw new Error(`Test failed: ${errorMessage}`);
|
||||
}
|
||||
}
|
||||
|
||||
public async disable(data: DisableMetaWapCredentialParams): Promise<MetaWapCredentialView> {
|
||||
await this.validateCompanyAdmin(data.companyId, data.sessionUser);
|
||||
|
||||
const credential = await this.requireConfiguredCredentials(data.companyId);
|
||||
|
||||
const updated = await this.metaWapCredentials.upsert({
|
||||
companyId: data.companyId,
|
||||
phoneNumberId: credential.phoneNumberId,
|
||||
wabaId: credential.wabaId,
|
||||
encryptedAccessToken: credential.encryptedAccessToken,
|
||||
accessTokenIv: credential.accessTokenIv,
|
||||
accessTokenAuthTag: credential.accessTokenAuthTag,
|
||||
encryptionKeyVersion: credential.encryptionKeyVersion,
|
||||
status: META_WAP_CREDENTIAL_STATUS.DISABLED,
|
||||
});
|
||||
|
||||
return this.sanitizeCredential(updated);
|
||||
}
|
||||
|
||||
public async getCredentialsForSending(companyId: string): Promise<MetaWapConnectionCredentials> {
|
||||
const credential = await this.requireConfiguredCredentials(companyId);
|
||||
|
||||
return {
|
||||
phoneNumberId: credential.phoneNumberId,
|
||||
authToken: this.decryptAccessToken(credential),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default new MetaWapCredentialsManager();
|
||||
@@ -1,15 +1,21 @@
|
||||
import axios from "axios";
|
||||
import { INotificationsAdapter, NotificationDataMeta } from "./Notifications.Interface";
|
||||
|
||||
export type MetaConnectionCredentials = {
|
||||
phoneNumberId: string;
|
||||
authToken: string;
|
||||
};
|
||||
|
||||
export class NotificationsMetaAdapter implements INotificationsAdapter<NotificationDataMeta> {
|
||||
// ── Datos hardcodeados para pruebas (etapa 1) ──
|
||||
private readonly phoneNumberId = "1379557278565544"; // ej: "1379557278565544"
|
||||
private readonly authToken =
|
||||
"EAAeZCf8kXotYBSuVCLO7smu3jGxv7qqndFn3OZB6E55QZBRKB2ZCPmOkylVfseI8PuZBzGBIrGa3QO0hUJZCGq7GwwJBgaxhu4A07LJZCOna82ZAQZAYZBljlQVApTurK1Hj0YHVZA4cnSdQa5xKmoYBa8YTuzyqrC1ZC2s9qbIvWHImUkVUgG2ps7F25ZAZCMISympdL1c34iO2IDLW2NGndy3o4E11K0c1FA3RFTCQJJXPmuddK360KLz5Tpj8BOTZC12FztytNQkwqpHZBBdpCpJfrOyQy546"; // tu token de Meta
|
||||
private readonly credentials: MetaConnectionCredentials;
|
||||
private readonly apiVersion = "v25.0";
|
||||
|
||||
constructor(credentials: MetaConnectionCredentials) {
|
||||
this.credentials = credentials;
|
||||
}
|
||||
|
||||
get baseUrl(): string {
|
||||
return `https://graph.facebook.com/${this.apiVersion}/${this.phoneNumberId}/messages`;
|
||||
return `https://graph.facebook.com/${this.apiVersion}/${this.credentials.phoneNumberId}/messages`;
|
||||
}
|
||||
|
||||
async send(data: NotificationDataMeta): Promise<void> {
|
||||
@@ -38,11 +44,11 @@ export class NotificationsMetaAdapter implements INotificationsAdapter<Notificat
|
||||
|
||||
const response = await axios.post(this.baseUrl, templateBody, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${this.authToken}`,
|
||||
Authorization: `Bearer ${this.credentials.authToken}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
return response.data;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ export enum NOTIFICATION_TYPES {
|
||||
}
|
||||
|
||||
export type NotificationDataMeta = {
|
||||
companyId: string;
|
||||
phoneNumber: string;
|
||||
templateName: string;
|
||||
languageCode: string;
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
} from "./Notifications.Interface";
|
||||
|
||||
import { NotificationsWhatsappBotAdapter } from "./Notifications.Adapter.Bot";
|
||||
import { NotificationsMetaAdapter } from "./Notifications.Adapter.Meta";
|
||||
import { MetaConnectionCredentials, NotificationsMetaAdapter } from "./Notifications.Adapter.Meta";
|
||||
import { NotificationsSystemAdapter } from "./Notifications.Adapter.System";
|
||||
import { NotificationsDonWebAdapter } from "./Notifications.Adapter.donweb";
|
||||
|
||||
@@ -14,13 +14,11 @@ class NotificationsMngr {
|
||||
//notificationsNodeMailer: NotificationsNodemailerAdapter;
|
||||
notificationsDonWeb: NotificationsDonWebAdapter;
|
||||
notificationsBot: NotificationsWhatsappBotAdapter;
|
||||
notificationsMeta: NotificationsMetaAdapter;
|
||||
notificationsSystem: NotificationsSystemAdapter;
|
||||
constructor() {
|
||||
//this.notificationsNodeMailer = new NotificationsNodemailerAdapter();
|
||||
this.notificationsDonWeb = new NotificationsDonWebAdapter();
|
||||
this.notificationsBot = new NotificationsWhatsappBotAdapter();
|
||||
this.notificationsMeta = new NotificationsMetaAdapter();
|
||||
this.notificationsSystem = new NotificationsSystemAdapter();
|
||||
}
|
||||
|
||||
@@ -33,8 +31,9 @@ class NotificationsMngr {
|
||||
await this.notificationsBot.send(data);
|
||||
}
|
||||
|
||||
async sendWhatsAppTemplate(data: NotificationDataMeta): Promise<void> {
|
||||
await this.notificationsMeta.send(data);
|
||||
async sendWhatsAppTemplate(data: NotificationDataMeta, credentials: MetaConnectionCredentials): Promise<void> {
|
||||
const notificationsMeta = new NotificationsMetaAdapter(credentials);
|
||||
await notificationsMeta.send(data);
|
||||
}
|
||||
|
||||
async sendSystemNotification(data: NotificationDataSystem): Promise<void> {
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
import { Body, Controller, Middlewares, Post, Response, Route, SuccessResponse } from "tsoa";
|
||||
import { authenticateMiddleware } from "../../middleware/authentication";
|
||||
import { rateLimiter } from "../../middleware/ratelimiter";
|
||||
import { ApiValidationError } from "../../Models/Server.Error.model";
|
||||
import {
|
||||
DisableMetaWapCredentialParams,
|
||||
FindMetaWapCredentialParams,
|
||||
MetaWapCredentialView,
|
||||
SaveMetaWapCredentialParams,
|
||||
TestMetaWapCredentialParams,
|
||||
TestMetaWapCredentialResult,
|
||||
} from "../../Models/MetaWapCredentials/MetaWapCredentials.Interface";
|
||||
import { MetaWapCredentialsService } from "./MetaWapCredentials.Service";
|
||||
|
||||
@Route("meta-credentials/save")
|
||||
@Middlewares(authenticateMiddleware)
|
||||
export class MetaWapCredentialsSaveController extends Controller {
|
||||
@SuccessResponse(200, "Done")
|
||||
@Response<ApiValidationError>(500, "Ha ocurrido un error")
|
||||
@Post()
|
||||
public async save(
|
||||
@Body() requestBody: SaveMetaWapCredentialParams
|
||||
): Promise<MetaWapCredentialView | ApiValidationError> {
|
||||
try {
|
||||
const credential = await new MetaWapCredentialsService().save(requestBody);
|
||||
this.setStatus(200);
|
||||
return credential;
|
||||
} catch (e) {
|
||||
const errorOccurred: Error = e as Error;
|
||||
this.setStatus(500);
|
||||
return new ApiValidationError(500, errorOccurred.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Route("meta-credentials/find")
|
||||
@Middlewares(authenticateMiddleware)
|
||||
export class MetaWapCredentialsFindController extends Controller {
|
||||
@SuccessResponse(200, "Done")
|
||||
@Response<ApiValidationError>(500, "Ha ocurrido un error")
|
||||
@Post()
|
||||
public async find(
|
||||
@Body() requestBody: FindMetaWapCredentialParams
|
||||
): Promise<MetaWapCredentialView | ApiValidationError> {
|
||||
try {
|
||||
const credential = await new MetaWapCredentialsService().find(requestBody);
|
||||
this.setStatus(200);
|
||||
return credential;
|
||||
} catch (e) {
|
||||
const errorOccurred: Error = e as Error;
|
||||
this.setStatus(500);
|
||||
return new ApiValidationError(500, errorOccurred.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Route("meta-credentials/test")
|
||||
@Middlewares([rateLimiter, authenticateMiddleware])
|
||||
export class MetaWapCredentialsTestController extends Controller {
|
||||
@SuccessResponse(200, "Done")
|
||||
@Response<ApiValidationError>(500, "Ha ocurrido un error")
|
||||
@Post()
|
||||
public async test(
|
||||
@Body() requestBody: TestMetaWapCredentialParams
|
||||
): Promise<TestMetaWapCredentialResult | ApiValidationError> {
|
||||
try {
|
||||
const result = await new MetaWapCredentialsService().test(requestBody);
|
||||
this.setStatus(200);
|
||||
return result;
|
||||
} catch (e) {
|
||||
const errorOccurred: Error = e as Error;
|
||||
this.setStatus(500);
|
||||
return new ApiValidationError(500, errorOccurred.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Route("meta-credentials/disable")
|
||||
@Middlewares(authenticateMiddleware)
|
||||
export class MetaWapCredentialsDisableController extends Controller {
|
||||
@SuccessResponse(200, "Done")
|
||||
@Response<ApiValidationError>(500, "Ha ocurrido un error")
|
||||
@Post()
|
||||
public async disable(
|
||||
@Body() requestBody: DisableMetaWapCredentialParams
|
||||
): Promise<MetaWapCredentialView | ApiValidationError> {
|
||||
try {
|
||||
const credential = await new MetaWapCredentialsService().disable(requestBody);
|
||||
this.setStatus(200);
|
||||
return credential;
|
||||
} catch (e) {
|
||||
const errorOccurred: Error = e as Error;
|
||||
this.setStatus(500);
|
||||
return new ApiValidationError(500, errorOccurred.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { connect } from "mongoose";
|
||||
import MetaWapCredentials from "../../Models/MetaWapCredentials/MetaWapCredentials";
|
||||
import {
|
||||
DisableMetaWapCredentialParams,
|
||||
FindMetaWapCredentialParams,
|
||||
MetaWapCredentialView,
|
||||
SaveMetaWapCredentialParams,
|
||||
TestMetaWapCredentialParams,
|
||||
TestMetaWapCredentialResult,
|
||||
} from "../../Models/MetaWapCredentials/MetaWapCredentials.Interface";
|
||||
|
||||
export class MetaWapCredentialsService {
|
||||
public async save(data: SaveMetaWapCredentialParams): Promise<MetaWapCredentialView> {
|
||||
await connect(`${process.env.DATABASE_CONNECTION}`);
|
||||
return await MetaWapCredentials.save(data);
|
||||
}
|
||||
|
||||
public async find(data: FindMetaWapCredentialParams): Promise<MetaWapCredentialView> {
|
||||
await connect(`${process.env.DATABASE_CONNECTION}`);
|
||||
return await MetaWapCredentials.find(data);
|
||||
}
|
||||
|
||||
public async test(data: TestMetaWapCredentialParams): Promise<TestMetaWapCredentialResult> {
|
||||
await connect(`${process.env.DATABASE_CONNECTION}`);
|
||||
return await MetaWapCredentials.test(data);
|
||||
}
|
||||
|
||||
public async disable(data: DisableMetaWapCredentialParams): Promise<MetaWapCredentialView> {
|
||||
await connect(`${process.env.DATABASE_CONNECTION}`);
|
||||
return await MetaWapCredentials.disable(data);
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import { Body, Controller, Middlewares, Post, Route, SuccessResponse } from "tso
|
||||
import { rateLimiter } from "../../middleware/ratelimiter";
|
||||
import { systemTokenMiddleware } from "../../middleware/systemToken";
|
||||
import { NotificationsManager } from "../../Models/Notifications/Notifications";
|
||||
import MetaWapCredentialsManager from "../../Models/MetaWapCredentials/MetaWapCredentials";
|
||||
import { ApiVoidResult } from "../../Models/Api.VoidResult.type";
|
||||
|
||||
interface SendEmailByContentParams {
|
||||
@@ -23,6 +24,7 @@ interface SendWapByContentParams {
|
||||
|
||||
interface SendWhatsAppTemplateParams {
|
||||
systemToken: string;
|
||||
companyId: string;
|
||||
phoneNumber: string;
|
||||
templateName: string;
|
||||
languageCode: string;
|
||||
@@ -72,45 +74,24 @@ export class SendWapByContentController extends Controller {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TEMPORARY TEST ENDPOINT — remove after testing meta_wap integration
|
||||
* No auth required. Just for curl testing sendWhatsAppTemplate flow.
|
||||
*/
|
||||
@Route("notifications/test-meta-send")
|
||||
export class TestMetaSendController extends Controller {
|
||||
@SuccessResponse(200, "Done")
|
||||
@Post()
|
||||
public async testMetaSend(@Body() requestBody: SendWhatsAppTemplateParams): Promise<ApiVoidResult> {
|
||||
try {
|
||||
await NotificationsManager.sendWhatsAppTemplate({
|
||||
phoneNumber: requestBody.phoneNumber,
|
||||
templateName: requestBody.templateName,
|
||||
languageCode: requestBody.languageCode,
|
||||
templateVariables: requestBody.templateVariables,
|
||||
});
|
||||
|
||||
this.setStatus(200);
|
||||
return { message: "Test sent successfully", success: true };
|
||||
} catch (error) {
|
||||
const msg = error instanceof Error ? error.message : String(error);
|
||||
this.setStatus(500);
|
||||
return { message: `Test failed: ${msg}`, success: false };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Route("notifications/send-whatsapp-template")
|
||||
@Middlewares([rateLimiter, systemTokenMiddleware])
|
||||
export class SendWhatsAppTemplateController extends Controller {
|
||||
@SuccessResponse(200, "Done")
|
||||
@Post()
|
||||
public async sendWhatsAppTemplate(@Body() requestBody: SendWhatsAppTemplateParams): Promise<ApiVoidResult> {
|
||||
await NotificationsManager.sendWhatsAppTemplate({
|
||||
phoneNumber: requestBody.phoneNumber,
|
||||
templateName: requestBody.templateName,
|
||||
languageCode: requestBody.languageCode,
|
||||
templateVariables: requestBody.templateVariables,
|
||||
});
|
||||
const credentials = await MetaWapCredentialsManager.getCredentialsForSending(requestBody.companyId);
|
||||
|
||||
await NotificationsManager.sendWhatsAppTemplate(
|
||||
{
|
||||
companyId: requestBody.companyId,
|
||||
phoneNumber: requestBody.phoneNumber,
|
||||
templateName: requestBody.templateName,
|
||||
languageCode: requestBody.languageCode,
|
||||
templateVariables: requestBody.templateVariables,
|
||||
},
|
||||
credentials
|
||||
);
|
||||
|
||||
this.setStatus(200);
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user