first commit

This commit is contained in:
2026-07-16 20:48:43 -03:00
commit 5696cee264
1111 changed files with 322270 additions and 0 deletions
+720
View File
@@ -0,0 +1,720 @@
import UsersManager from "../Users/Users";
import CompaniesManager from "../Companies/Companies";
import { NoPermissionMessage } from "../../helpers/NoPermissionMessage";
import EmployeesList from "../Employees/Employee";
import { EmployeeRoles } from "../Employees/Employees.Interface";
import { WapServerAdapterMongoose } from "./WapServer.Adapter.Mongoose";
import {
AssingAvailableServerParams,
BotCheckSession,
BotCheckStatus,
BotEventParams,
BotEventError,
BotView,
IWapServer,
IWapServerManager,
SendBotMessageParams,
ValidateBotEventStatus,
BOT_STATE,
SERVER_STATE,
BOT_SESSION_STATUS,
VALIDATE_BOT_ENVENT_ERRORS,
PaginateWapServerResults,
PaginateWapServerParams,
} from "./WapServer.Interface";
import PlanSubscriptionsList from "../PlanSubscriptions/PlanSubscriptons";
import { PlanFeatures } from "../Plans/Plans.interface";
import axios, { AxiosError } from "axios";
import { isNull } from "../../helpers/IsNull";
export interface CreateBotResult {
organizationId: string;
port: number;
error: string;
message: string;
}
class WapServerManager implements IWapServerManager {
servers: WapServerAdapterMongoose;
constructor() {
this.servers = new WapServerAdapterMongoose();
}
public async botView(data: AssingAvailableServerParams): Promise<BotView> {
const sessionUser = await UsersManager.users.findOne({ _id: data.sessionUser });
if (!sessionUser) {
throw new Error("El usuario no es valido.");
}
const companyCheck = await CompaniesManager.companies.findOne({ _id: data.companyId });
if (!companyCheck) {
throw new Error("La compañia no existe");
}
if (!(await EmployeesList.checkPermission(companyCheck, data.sessionUser, EmployeeRoles.ADMIN))) {
throw new Error(NoPermissionMessage());
}
const validationStatus = await this.validateBotEvent(data.sessionUser, data.companyId);
if (!validationStatus.status) {
switch (validationStatus.code) {
case VALIDATE_BOT_ENVENT_ERRORS.NO_SERVER_ASSIGNED:
return {
runningState: "dead",
serverId: undefined,
serverName: undefined,
serverDescription: undefined,
serverState: SERVER_STATE.NOT_ASSIGNED,
exists: false,
automaticNotifications: isNull<boolean>(companyCheck.automaticNotifications, false),
};
default:
throw new Error(validationStatus.error);
}
}
if (!companyCheck.wapServerId) {
return {
runningState: "dead",
serverId: undefined,
serverName: undefined,
serverDescription: undefined,
serverState: SERVER_STATE.NOT_WORKING,
exists: false,
automaticNotifications: isNull<boolean>(companyCheck.automaticNotifications, false),
};
}
const server = await this.servers.serverList.findOne({ _id: companyCheck.wapServerId });
if (server) {
try {
const chkStatus = await this.checkBot({
sessionUser: data.sessionUser,
companyId: data.companyId,
});
let botSession = BOT_SESSION_STATUS.UNLOGED;
if (chkStatus.status.indexOf("Up") >= 0) {
botSession = (await this.checkSession(data)).session;
}
return {
serverId: server.id,
serverName: server.name,
serverDescription: server.description,
serverState: chkStatus.serverState,
exists: chkStatus.state == BOT_STATE.EXISTS ? true : false,
runningState: chkStatus.status,
botSession: botSession,
automaticNotifications: isNull<boolean>(companyCheck.automaticNotifications, false),
} as BotView;
} catch (error) {
return {
serverId: server.id,
serverName: server.name,
serverDescription: server.description,
serverState: SERVER_STATE.NOT_WORKING,
exists: false,
runningState: "dead",
automaticNotifications: isNull<boolean>(companyCheck.automaticNotifications, false),
};
}
}
return {
serverId: undefined,
serverName: undefined,
serverDescription: undefined,
exists: false,
runningState: "dead",
automaticNotifications: isNull<boolean>(companyCheck.automaticNotifications, false),
};
}
public async findServers(): Promise<IWapServer[]> {
const servidores = await this.servers.find({});
return servidores;
}
private getBotAdminApiUrl(url: string): string {
//return `https://${url}:${process.env.DEFAULT_BOT_ADMIN_PORT}/`;
//return `https://${url}/`;
return url;
}
public async getAvailableServer(): Promise<IWapServer> {
const servidores = await this.servers.find({ active: true });
if (servidores.length == 0) {
throw new Error("No se encontraron servidores disponibles");
}
const servidoresConCapacidad = servidores.filter((s) => {
return s.countBots < s.maxBots;
});
if (servidoresConCapacidad.length == 0) {
throw new Error("No se encontraron servidores disponibles");
}
const returnServer = servidoresConCapacidad.sort((a, b) => a.countBots - b.countBots).shift();
if (!returnServer) {
throw new Error("No se encontraron servidores disponibles");
}
return returnServer;
}
public async quitServer(data: AssingAvailableServerParams): Promise<void> {
const sessionUser = await UsersManager.users.findOne({ _id: data.sessionUser });
if (!sessionUser) {
throw new Error("No se ha encontrado el usuario o no tiene permisos para realizar esta acción");
}
//Validar que la organizacion exista.
const companyCheck = await CompaniesManager.companies.findOne({ _id: data.companyId });
if (!companyCheck) {
throw new Error("La compañia no existe");
}
//Validar que el usuario sea el administrador de la organizacion.
if (!(await EmployeesList.checkPermission(companyCheck, data.sessionUser, EmployeeRoles.ADMIN))) {
throw new Error(NoPermissionMessage());
}
//Descontar 1 al contador de bots del servidor.
if (companyCheck && companyCheck.wapServerId) {
const server = await this.servers.serverList.findOne({
_id: companyCheck.wapServerId,
});
if (server) {
server.countBots = server.countBots - 1;
if (server.countBots < 0) {
server.countBots = 0;
}
await server.save();
}
}
companyCheck.wapServerId = undefined;
await companyCheck.save();
}
public async assignAvailableServer(data: AssingAvailableServerParams): Promise<IWapServer> {
const sessionUser = await UsersManager.users.findOne({ _id: data.sessionUser });
if (!sessionUser) {
throw new Error("No se ha encontrado el usuario o no tiene permisos para realizar esta acción");
}
//Validar que la organizacion exista.
const companyCheck = await CompaniesManager.companies.findOne({ _id: data.companyId });
if (!companyCheck) {
throw new Error("La compañia no existe");
}
//Validar que el usuario sea el administrador de la organizacion.
if (!(await EmployeesList.checkPermission(companyCheck, data.sessionUser, EmployeeRoles.ADMIN))) {
throw new Error(NoPermissionMessage());
}
//Obtener el id del usuario owner de la organizacion.
const organizationOwner = companyCheck.ownerId;
//Validar que el owner de la organizacion tenga un plan que permita bots.
if (
!PlanSubscriptionsList.checkFeature({
userId: organizationOwner,
feature: PlanFeatures.HAS_BOT,
})
) {
throw new Error("El plan de la organizacion no permite bots.");
}
//Validar que el plan este activo.
if (!PlanSubscriptionsList.isSubscriptionActive({ sessionUser: organizationOwner })) {
throw new Error("La organizacion no tiene una suscripcion activa.");
}
//Validar que la organizacion no tenga ningun servidor asignado.
let organizationServerId = companyCheck.wapServerId;
if (organizationServerId) {
throw new Error("La organizacion ya tiene un servidor asignado.");
}
//Obtener el servidor con menos bots activos.
const availableServer = await this.getAvailableServer();
if (!availableServer) {
throw new Error("No se encontraron servidores disponibles.");
}
const server = await this.servers.serverList.findOne({ _id: availableServer.id });
if (!server) {
throw new Error("No se encontro el servidor");
}
//Guardar en la coleccion Companies los datos del servidor asignado.
await CompaniesManager.updateCompany({
wapServerId: availableServer.id,
id: data.companyId,
sessionUser: data.sessionUser,
});
//Incrementear la cantidad de bots en el servidor elegido.
server.countBots = server.countBots + 1;
await server.save();
return server;
}
private async validateBotEvent(
sessionUserId: string,
companyId?: string,
): Promise<ValidateBotEventStatus> {
if (!sessionUserId) {
return {
status: false,
error: "No se ha encontrado el usuario",
code: VALIDATE_BOT_ENVENT_ERRORS.INVALID_USER,
};
}
const sessionUser = await UsersManager.users.findOne({ _id: sessionUserId });
if (!sessionUser) {
return {
status: false,
error: "No se ha encontrado el usuario o no tiene permisos para realizar esta acción",
code: VALIDATE_BOT_ENVENT_ERRORS.INVALID_USER,
};
}
if (!companyId) {
return {
status: false,
error: "No se ha encontrado la compañia",
code: VALIDATE_BOT_ENVENT_ERRORS.INVALID_ORG,
};
}
//Validar que la organizacion exista.
const companyCheck = await CompaniesManager.companies.findOne({ _id: companyId });
if (!companyCheck) {
return {
status: false,
error: "La compañia no existe",
code: VALIDATE_BOT_ENVENT_ERRORS.INVALID_ORG,
};
}
//Validar que el usuario sea el administrador de la organizacion.
if (!(await EmployeesList.checkPermission(companyCheck, sessionUserId, EmployeeRoles.ADMIN))) {
return {
status: false,
error: NoPermissionMessage(),
code: VALIDATE_BOT_ENVENT_ERRORS.NO_PERMISSION,
};
}
//Obtener el id del usuario owner de la organizacion.
const organizationOwner = companyCheck.ownerId;
const checkFeature = await PlanSubscriptionsList.checkFeature({
userId: organizationOwner,
feature: PlanFeatures.WAP_NOTIFICATIONS,
});
//Validar que el owner de la organizacion tenga un plan que permita bots.
if (!checkFeature) {
const serverAssigned = await this.servers.serverList.findOne({
_id: companyCheck.wapServerId,
});
if (!serverAssigned) {
return {
status: false,
error: "No se ha asignado un servidor a la compañia",
code: VALIDATE_BOT_ENVENT_ERRORS.NO_SERVER_ASSIGNED,
};
}
return {
status: false,
error: "El plan de la organizacion no permite bots.",
code: VALIDATE_BOT_ENVENT_ERRORS.NO_BOT_ALLOWED,
serverUrl: serverAssigned.ipv4,
};
}
//Validar que el plan este activo.
const checkSubscriptionActive = await PlanSubscriptionsList.isSubscriptionActive({
sessionUser: organizationOwner,
});
if (!checkSubscriptionActive) {
return {
status: false,
error: "La organizacion no tiene una suscripcion activa.",
code: VALIDATE_BOT_ENVENT_ERRORS.NO_SUBSCRIPTION_ACTIVE,
};
}
const serverAssigned = await this.servers.serverList.findOne({
_id: companyCheck.wapServerId,
});
if (!serverAssigned) {
return {
status: false,
error: "No se ha asignado un servidor a la compañia",
code: VALIDATE_BOT_ENVENT_ERRORS.NO_SERVER_ASSIGNED,
};
}
return {
status: true,
error: "",
serverUrl: serverAssigned.ipv4,
};
}
public async createBot(data: BotEventParams): Promise<void> {
const validationStatus = await this.validateBotEvent(data.sessionUser, data.companyId);
if (!validationStatus.status) {
throw new Error(validationStatus.error);
}
const url = validationStatus.serverUrl;
if (!url) {
throw new Error("No se ha asignado un servidor a la organizacion");
}
try {
const dataApi = {
organizationId: data.companyId,
};
const response = await axios.post(this.getBotAdminApiUrl(url) + "containers/create", dataApi, {
headers: {
"Content-Type": "application/json",
},
});
const result: CreateBotResult = response.data;
if (result.error) {
throw new Error(result.error);
}
//Podria guardar el puerto asignado al bot.
//Pero en realidad no hace falta, ya que conociendo el servidor
//Donde se ejecuta el bot puedo averiguar el puerto si el que se encarga
//de enviar el mensaje de wap es el mismo bot admin.
//La idea seria que el server de tx le envia un post al bot admin para enviar un mensaje de wap y luego
//el bot admin averigua cual es el puerto donde se esta ejecutando el bot y envia la solicitud.
//De esta manera nunca hay una conexion directa entre la api principal (expuesta) y el bot.
//Por lo tanto por el momento solo retorno sin novedades...
return;
} catch (error) {
console.log(error);
throw new Error("Ha ocurrido un error al crear el bot.");
}
}
public async startBot(data: BotEventParams): Promise<void> {
const validationStatus = await this.validateBotEvent(data.sessionUser, data.companyId);
if (!validationStatus.status) {
throw new Error(validationStatus.error);
}
const url = validationStatus.serverUrl;
if (!url) {
throw new Error("No se ha asignado un servidor a la organizacion");
}
try {
const dataApi = {};
const response = await axios.post(
this.getBotAdminApiUrl(url) + "containers/" + data.companyId + "/start",
dataApi,
{
headers: {
"Content-Type": "application/json",
},
},
);
const result: CreateBotResult = response.data;
if (result.error) {
throw new Error(result.error);
}
return;
} catch (error) {
if (error instanceof AxiosError) {
const axiosError = error as AxiosError;
if (axiosError.response) {
const responseError = axiosError.response;
console.log(responseError.data);
}
}
throw new Error("Ha ocurrido un error al iniciar el bot.");
}
}
public async stopBot(data: BotEventParams): Promise<void> {
const validationStatus = await this.validateBotEvent(data.sessionUser, data.companyId);
if (!validationStatus.status) {
throw new Error(validationStatus.error);
}
const url = validationStatus.serverUrl;
if (!url) {
throw new Error("No se ha asignado un servidor a la organizacion");
}
try {
const dataApi = {};
const response = await axios.post(
this.getBotAdminApiUrl(url) + "containers/" + data.companyId + "/stop",
dataApi,
{
headers: {
"Content-Type": "application/json",
},
},
);
const result: CreateBotResult = response.data;
if (result.error) {
throw new Error(result.error);
}
return;
} catch (error) {
console.log(error);
throw new Error("Ha ocurrido un error al detener el bot.");
}
}
public async deleteBot(data: BotEventParams): Promise<void> {
//Como quiere borrarlo no voy a validar si tiene una suscripcion activa.
const validationStatus = await this.validateBotEvent(data.sessionUser, data.companyId);
/*if (!validationStatus.status) {
throw new Error(validationStatus.error);
}*/
const url = validationStatus.serverUrl;
if (!url) {
throw new Error("No se ha asignado un servidor a la organizacion");
}
try {
const response = await axios.delete(this.getBotAdminApiUrl(url) + "containers/" + data.companyId);
const result: CreateBotResult = response.data;
if (result.error) {
throw new Error(result.error);
}
return;
} catch (error) {
throw new Error("Ha ocurrido un error al detener el bot.");
}
}
public async checkBot(data: BotEventParams): Promise<BotCheckStatus> {
const validationStatus = await this.validateBotEvent(data.sessionUser, data.companyId);
if (!validationStatus.status) {
throw new Error(validationStatus.error);
}
const url = validationStatus.serverUrl;
if (!url) {
throw new Error("No se ha asignado un servidor a la organizacion");
}
try {
const dataApi = {};
const response = await axios.post(
this.getBotAdminApiUrl(url) + "containers/" + data.companyId + "/status",
dataApi,
{
headers: {
"Content-Type": "application/json",
},
},
);
return response.data as BotCheckStatus;
} catch (error) {
if (error instanceof AxiosError) {
const errorAxios: AxiosError = error;
if (errorAxios.response) {
const errorData = errorAxios.response.data as BotEventError;
if (errorData.code == 404) {
return {
state: BOT_STATE.NOT_EXISTS,
status: "dead",
serverState: SERVER_STATE.RUNNING,
};
} else {
throw new Error("Ha ocurrido un error al obtener el estado del bot.");
}
} else {
//no hay comunicacion con el bot-api-admin.
return {
state: BOT_STATE.NOT_EXISTS,
status: "dead",
serverState: SERVER_STATE.NOT_WORKING,
};
throw new Error("Ha ocurrido un error al obtener el estado del bot.");
}
} else {
throw new Error("Ha ocurrido un error al obtener el estado del bot.");
}
}
}
public async checkSession(data: BotEventParams): Promise<BotCheckSession> {
const validationStatus = await this.validateBotEvent(data.sessionUser, data.companyId);
if (!validationStatus.status) {
throw new Error(validationStatus.error);
}
const url = validationStatus.serverUrl;
if (!url) {
throw new Error("No se ha asignado un servidor a la organizacion");
}
try {
const dataApi = {};
const response = await axios.post(
this.getBotAdminApiUrl(url) + "containers/" + data.companyId + "/session",
dataApi,
{
headers: {
"Content-Type": "application/json",
},
},
);
if (response.data.message == "ok") {
return {
session: BOT_SESSION_STATUS.LOGED,
};
}
return {
session: BOT_SESSION_STATUS.UNLOGED,
};
} catch (error) {
console.log(error);
throw new Error("Ha ocurrido un error al obtener el estado del bot.");
}
}
public async sendMessage(data: SendBotMessageParams): Promise<void> {
const validationStatus = await this.validateBotEvent(data.sessionUser, data.companyId);
if (!validationStatus.status) {
throw new Error(validationStatus.error);
}
const url = validationStatus.serverUrl;
if (!url) {
throw new Error("No se ha asignado un servidor a la organizacion");
}
try {
const dataApi = {
number: data.number,
message: data.message,
};
const response = await axios.post(
this.getBotAdminApiUrl(url) + "containers/" + data.companyId + "/send",
dataApi,
{
headers: {
"Content-Type": "application/json",
},
},
);
const result: CreateBotResult = response.data;
if (result.error) {
throw new Error(result.error);
}
return;
} catch (error) {
throw new Error("Ha ocurrido un error al enviar el mensaje.");
}
}
public async getQr(data: BotEventParams): Promise<string> {
const validationStatus = await this.validateBotEvent(data.sessionUser, data.companyId);
if (!validationStatus.status) {
throw new Error(validationStatus.error);
}
const url = validationStatus.serverUrl;
if (!url) {
throw new Error("No se ha asignado un servidor a la organizacion");
}
try {
const response = await axios.get(
this.getBotAdminApiUrl(url) + "containers/" + data.companyId + "/qr",
{ responseType: "arraybuffer" },
);
const base64Image = Buffer.from(response.data, "binary").toString("base64");
return base64Image;
} catch (error) {
throw new Error("Ha ocurrido un error al obtener el estado del bot.");
}
}
public async sysAdminGetWapServers(data: PaginateWapServerParams): Promise<PaginateWapServerResults> {
const servers = await this.servers.paginate(data);
return servers;
}
}
const WapServerList = new WapServerManager();
export default WapServerList;