1328 lines
46 KiB
TypeScript
1328 lines
46 KiB
TypeScript
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,
|
|
SysAdminDeleteWapServerParams,
|
|
SysAdminDeleteWapServerResult,
|
|
SysAdminWapContainerDto,
|
|
SysAdminWapServerAuditParams,
|
|
SysAdminWapServerAuditResult,
|
|
SysAdminWapServerRecalculateCountParams,
|
|
SysAdminWapServerRecalculateCountResult,
|
|
SysAdminWapServerDetachResult,
|
|
SysAdminWapServerDeleteBotResult,
|
|
SysAdminWapServerOrganizationActionParams,
|
|
SysAdminWapServerQrResult,
|
|
SendBotMessageParams,
|
|
ValidateBotEventStatus,
|
|
BOT_STATE,
|
|
SERVER_STATE,
|
|
BOT_SESSION_STATUS,
|
|
CreateWapServerParams,
|
|
VALIDATE_BOT_ENVENT_ERRORS,
|
|
PaginateWapServerResults,
|
|
PaginateWapServerParams,
|
|
UpdateWapServerParams,
|
|
} from "./WapServer.Interface";
|
|
import PlanSubscriptionsList from "../PlanSubscriptions/PlanSubscriptons";
|
|
import { PlanFeatures } from "../Plans/Plans.interface";
|
|
import axios, { AxiosError } from "axios";
|
|
import { isNull } from "../../helpers/IsNull";
|
|
|
|
const BOT_ADMIN_ACTION_TIMEOUT_MS = 10000;
|
|
const BOT_ADMIN_RESTART_STATUS_RETRIES = 5;
|
|
const BOT_ADMIN_RESTART_STATUS_DELAY_MS = 1000;
|
|
const MONGO_OBJECT_ID_REGEX = /^[a-fA-F0-9]{24}$/;
|
|
const WAP_SERVER_METADATA_HOSTS = new Set(["169.254.169.254", "metadata.google.internal"]);
|
|
|
|
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 normalizeBotAdminApiUrl(url: string): string {
|
|
const rawUrl = String(url).trim();
|
|
|
|
if (!rawUrl) {
|
|
throw new Error("URL is required");
|
|
}
|
|
|
|
if (/\s|[\x00-\x1F\x7F]/.test(rawUrl)) {
|
|
throw new Error("URL must not contain whitespace or control characters");
|
|
}
|
|
|
|
const hasProtocol = /^[a-z][a-z0-9+.-]*:\/\//i.test(rawUrl);
|
|
const normalizedInput = hasProtocol ? rawUrl : `http://${rawUrl}`;
|
|
let parsedUrl: URL;
|
|
|
|
try {
|
|
parsedUrl = new URL(normalizedInput);
|
|
} catch (error) {
|
|
throw new Error("URL must be a valid HTTP(S) origin");
|
|
}
|
|
|
|
if (parsedUrl.protocol !== "http:" && parsedUrl.protocol !== "https:") {
|
|
throw new Error("URL protocol must be http or https");
|
|
}
|
|
|
|
if (!parsedUrl.hostname) {
|
|
throw new Error("URL host is required");
|
|
}
|
|
|
|
if (parsedUrl.username || parsedUrl.password) {
|
|
throw new Error("URL must not include credentials");
|
|
}
|
|
|
|
const path = decodeURIComponent(parsedUrl.pathname);
|
|
if ((parsedUrl.pathname && parsedUrl.pathname !== "/") || path.includes("..")) {
|
|
throw new Error("URL path is not allowed; use only the origin");
|
|
}
|
|
|
|
if (parsedUrl.search || parsedUrl.hash) {
|
|
throw new Error("URL must not include query string or hash");
|
|
}
|
|
|
|
if (WAP_SERVER_METADATA_HOSTS.has(parsedUrl.hostname.toLowerCase())) {
|
|
throw new Error("URL host is not allowed");
|
|
}
|
|
|
|
return `${parsedUrl.origin}/`;
|
|
}
|
|
|
|
private getBotAdminApiUrl(url: string): string {
|
|
//return `https://${url}:${process.env.DEFAULT_BOT_ADMIN_PORT}/`;
|
|
//return `https://${url}/`;
|
|
return this.normalizeBotAdminApiUrl(url);
|
|
}
|
|
|
|
private formatResponseBody(data: unknown): string {
|
|
if (data === undefined || data === null) {
|
|
return "empty body";
|
|
}
|
|
|
|
const body = typeof data === "string" ? data : JSON.stringify(data);
|
|
|
|
return body.length > 1000 ? `${body.slice(0, 1000)}...` : body;
|
|
}
|
|
|
|
private createBotAdminContainersError(error: unknown, targetUrl: string): Error {
|
|
if (axios.isAxiosError(error)) {
|
|
const status = error.response?.status;
|
|
const body = this.formatResponseBody(error.response?.data);
|
|
const statusText = status ? `status ${status}` : error.code || "no response";
|
|
|
|
return new Error(`Bot-admin containers request failed for ${targetUrl}: ${statusText}; body: ${body}`);
|
|
}
|
|
|
|
if (error instanceof Error && error.message) {
|
|
return new Error(`Bot-admin containers request failed for ${targetUrl}: ${error.message}`);
|
|
}
|
|
|
|
return new Error(`Bot-admin containers request failed for ${targetUrl}: unknown error`);
|
|
}
|
|
|
|
private createBotAdminActionError(error: unknown, targetUrl: string): Error {
|
|
if (axios.isAxiosError(error)) {
|
|
const status = error.response?.status;
|
|
const body = this.formatResponseBody(error.response?.data);
|
|
const statusText = status ? `status ${status}` : error.code || "no response";
|
|
|
|
return new Error(`Bot-admin request failed for ${targetUrl}: ${statusText}; body: ${body}`);
|
|
}
|
|
|
|
if (error instanceof Error && error.message) {
|
|
return new Error(`Bot-admin request failed for ${targetUrl}: ${error.message}`);
|
|
}
|
|
|
|
return new Error(`Bot-admin request failed for ${targetUrl}: unknown error`);
|
|
}
|
|
|
|
private isBotAdminMissingContainerError(error: unknown): boolean {
|
|
if (!axios.isAxiosError(error)) {
|
|
return false;
|
|
}
|
|
|
|
const status = error.response?.status;
|
|
const body = this.formatResponseBody(error.response?.data).toLowerCase();
|
|
|
|
// Be conservative: only allow detach to continue for explicit missing-container signals.
|
|
return status === 404 || body.includes("not found") || body.includes("no such container");
|
|
}
|
|
|
|
private async getSysAdminServerActionTarget(data: SysAdminWapServerOrganizationActionParams): Promise<{
|
|
server: any;
|
|
baseUrl: string;
|
|
encodedOrganizationId: string;
|
|
}> {
|
|
if (!data.serverId) {
|
|
throw new Error("Server id is required");
|
|
}
|
|
|
|
if (!data.organizationId) {
|
|
throw new Error("Organization id is required");
|
|
}
|
|
|
|
if (!MONGO_OBJECT_ID_REGEX.test(data.organizationId)) {
|
|
throw new Error("Organization id must be a valid Mongo ObjectId");
|
|
}
|
|
|
|
const server = await this.servers.serverList.findOne({ _id: data.serverId });
|
|
|
|
if (!server) {
|
|
throw new Error("WAP server not found");
|
|
}
|
|
|
|
return { server, baseUrl: this.getBotAdminApiUrl(server.ipv4), encodedOrganizationId: encodeURIComponent(data.organizationId) };
|
|
}
|
|
|
|
private async postSysAdminBotAction(
|
|
data: SysAdminWapServerOrganizationActionParams,
|
|
action: "start" | "stop",
|
|
): Promise<void> {
|
|
const { baseUrl, encodedOrganizationId } = await this.getSysAdminServerActionTarget(data);
|
|
const targetUrl = `${baseUrl}containers/${encodedOrganizationId}/${action}`;
|
|
|
|
try {
|
|
const response = await axios.post(targetUrl, {}, {
|
|
headers: { "Content-Type": "application/json" },
|
|
timeout: BOT_ADMIN_ACTION_TIMEOUT_MS,
|
|
});
|
|
const result: CreateBotResult = response.data;
|
|
|
|
if (result.error) {
|
|
throw new Error(result.error);
|
|
}
|
|
} catch (error) {
|
|
throw this.createBotAdminActionError(error, targetUrl);
|
|
}
|
|
}
|
|
|
|
private async deleteSysAdminRuntimeBot(
|
|
baseUrl: string,
|
|
encodedOrganizationId: string,
|
|
allowMissingContainer = false,
|
|
): Promise<{ botDeleted: boolean; botAlreadyMissing: boolean }> {
|
|
const targetUrl = `${baseUrl}containers/${encodedOrganizationId}`;
|
|
|
|
try {
|
|
const response = await axios.delete(targetUrl, { timeout: BOT_ADMIN_ACTION_TIMEOUT_MS });
|
|
const result: CreateBotResult = response.data;
|
|
|
|
if (result.error) {
|
|
throw new Error(result.error);
|
|
}
|
|
|
|
return { botDeleted: true, botAlreadyMissing: false };
|
|
} catch (error) {
|
|
if (allowMissingContainer && this.isBotAdminMissingContainerError(error)) {
|
|
return { botDeleted: false, botAlreadyMissing: true };
|
|
}
|
|
|
|
throw this.createBotAdminActionError(error, targetUrl);
|
|
}
|
|
}
|
|
|
|
private async waitForSysAdminBotStopped(data: SysAdminWapServerOrganizationActionParams): Promise<void> {
|
|
const { baseUrl, encodedOrganizationId } = await this.getSysAdminServerActionTarget(data);
|
|
const targetUrl = `${baseUrl}containers/${encodedOrganizationId}/status`;
|
|
|
|
for (let attempt = 0; attempt < BOT_ADMIN_RESTART_STATUS_RETRIES; attempt += 1) {
|
|
try {
|
|
const response = await axios.post(targetUrl, {}, { timeout: BOT_ADMIN_ACTION_TIMEOUT_MS });
|
|
const status = String(response.data?.status || "").toLowerCase();
|
|
|
|
if (!status.includes("up")) {
|
|
return;
|
|
}
|
|
} catch (error) {
|
|
if (axios.isAxiosError(error) && error.response?.status === 500) {
|
|
return;
|
|
}
|
|
|
|
throw this.createBotAdminActionError(error, targetUrl);
|
|
}
|
|
|
|
await new Promise((resolve) => setTimeout(resolve, BOT_ADMIN_RESTART_STATUS_DELAY_MS));
|
|
}
|
|
|
|
throw new Error("Bot did not stop before restart timeout; start was not attempted.");
|
|
}
|
|
|
|
private extractOrganizationIdFromContainer(container: any): string | undefined {
|
|
const names = Array.isArray(container?.Names) ? container.Names : [];
|
|
|
|
for (const rawName of names) {
|
|
const name = String(rawName || "").replace(/^\//, "");
|
|
const match = name.match(/^(.+)_bot_\d+$/);
|
|
if (match && match[1]) {
|
|
return match[1];
|
|
}
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
private sanitizeContainer(container: any): SysAdminWapContainerDto {
|
|
const names = Array.isArray(container?.Names) ? container.Names : [];
|
|
const firstName = names.length > 0 ? String(names[0]).replace(/^\//, "") : undefined;
|
|
|
|
return {
|
|
id: container?.Id,
|
|
name: firstName,
|
|
organizationId: this.extractOrganizationIdFromContainer(container),
|
|
image: container?.Image,
|
|
state: container?.State,
|
|
status: container?.Status,
|
|
ports: Array.isArray(container?.Ports) ? container.Ports : [],
|
|
created: container?.Created,
|
|
};
|
|
}
|
|
|
|
private async getSysAdminRuntimeContainers(server: any): Promise<SysAdminWapContainerDto[]> {
|
|
const containersUrl = this.getBotAdminApiUrl(server.ipv4) + "containers";
|
|
|
|
try {
|
|
const response = await axios.get(containersUrl, {
|
|
headers: { "Content-Type": "application/json" },
|
|
timeout: BOT_ADMIN_ACTION_TIMEOUT_MS,
|
|
});
|
|
|
|
return Array.isArray(response.data)
|
|
? response.data.map((container: any) => this.sanitizeContainer(container))
|
|
: [];
|
|
} catch (error) {
|
|
throw this.createBotAdminContainersError(error, containersUrl);
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
public async sysAdminCreateWapServer(data: CreateWapServerParams): Promise<IWapServer> {
|
|
if (!data.name || !String(data.name).trim()) {
|
|
throw new Error("Name is required");
|
|
}
|
|
|
|
if (!data.ipv4 || !String(data.ipv4).trim()) {
|
|
throw new Error("IPv4 is required");
|
|
}
|
|
|
|
if (!Number.isFinite(data.port) || data.port < 0) {
|
|
throw new Error("Port must be a finite non-negative number");
|
|
}
|
|
|
|
if (!Number.isFinite(data.maxBots) || data.maxBots < 0) {
|
|
throw new Error("Max bots must be a finite non-negative number");
|
|
}
|
|
|
|
return await this.servers.create({
|
|
name: String(data.name).trim(),
|
|
description: data.description ? String(data.description).trim() : "",
|
|
ipv4: this.normalizeBotAdminApiUrl(data.ipv4),
|
|
ipv6: data.ipv6 ? String(data.ipv6).trim() : "",
|
|
countBots: 0,
|
|
port: data.port,
|
|
maxBots: data.maxBots,
|
|
active: data.active === undefined ? true : Boolean(data.active),
|
|
} as CreateWapServerParams & { countBots: number });
|
|
}
|
|
|
|
public async sysAdminUpdateWapServer(data: UpdateWapServerParams): Promise<IWapServer> {
|
|
if (!data.serverId) {
|
|
throw new Error("Server id is required");
|
|
}
|
|
|
|
if (!MONGO_OBJECT_ID_REGEX.test(data.serverId)) {
|
|
throw new Error("Server id must be a valid Mongo ObjectId");
|
|
}
|
|
|
|
const server = await this.servers.serverList.findOne({ _id: data.serverId });
|
|
|
|
if (!server) {
|
|
throw new Error("WAP server not found");
|
|
}
|
|
|
|
if (data.name !== undefined) {
|
|
server.name = String(data.name).trim();
|
|
}
|
|
|
|
if (data.ipv4 !== undefined) {
|
|
server.ipv4 = this.normalizeBotAdminApiUrl(data.ipv4);
|
|
}
|
|
|
|
if (data.port !== undefined) {
|
|
if (!Number.isFinite(data.port) || data.port < 0) {
|
|
throw new Error("Port must be a finite non-negative number");
|
|
}
|
|
|
|
server.port = data.port;
|
|
}
|
|
|
|
if (data.maxBots !== undefined) {
|
|
if (!Number.isFinite(data.maxBots) || data.maxBots < 0) {
|
|
throw new Error("Max bots must be a finite non-negative number");
|
|
}
|
|
|
|
server.maxBots = data.maxBots;
|
|
}
|
|
|
|
if (data.active !== undefined) {
|
|
server.active = Boolean(data.active);
|
|
}
|
|
|
|
await server.save();
|
|
|
|
return server;
|
|
}
|
|
|
|
public async sysAdminAuditWapServer(data: SysAdminWapServerAuditParams): Promise<SysAdminWapServerAuditResult> {
|
|
if (!data.serverId) {
|
|
throw new Error("Server id is required");
|
|
}
|
|
|
|
if (!MONGO_OBJECT_ID_REGEX.test(data.serverId)) {
|
|
throw new Error("Server id must be a valid Mongo ObjectId");
|
|
}
|
|
|
|
const server = await this.servers.serverList.findOne({ _id: data.serverId });
|
|
|
|
if (!server) {
|
|
throw new Error("WAP server not found");
|
|
}
|
|
|
|
const assignedCompanies = await CompaniesManager.companies.find({ wapServerId: data.serverId });
|
|
const assignedOrganizations = assignedCompanies.map((company: any) => ({
|
|
id: String(company.id || company._id),
|
|
name: company.name,
|
|
}));
|
|
const assignedIds = new Set(assignedOrganizations.map((organization) => organization.id));
|
|
|
|
const detectedBots = await this.getSysAdminRuntimeContainers(server);
|
|
const runtimeOrganizationIds = new Set(
|
|
detectedBots
|
|
.map((container) => container.organizationId)
|
|
.filter((organizationId): organizationId is string => Boolean(organizationId)),
|
|
);
|
|
|
|
const validBots = detectedBots.filter(
|
|
(container) => container.organizationId && assignedIds.has(container.organizationId),
|
|
);
|
|
const ghostBots = detectedBots.filter(
|
|
(container) => !container.organizationId || !assignedIds.has(container.organizationId),
|
|
);
|
|
const missingBots = assignedOrganizations.filter(
|
|
(organization) => !runtimeOrganizationIds.has(organization.id),
|
|
);
|
|
const expectedCountBots = assignedOrganizations.length;
|
|
const storedCountBots = server.countBots ?? 0;
|
|
const runtimeCountBots = detectedBots.length;
|
|
|
|
return {
|
|
serverId: String(server.id || server._id),
|
|
assignedOrganizations,
|
|
detectedBots,
|
|
validBots,
|
|
ghostBots,
|
|
missingBots,
|
|
expectedCountBots,
|
|
storedCountBots,
|
|
runtimeCountBots,
|
|
countMismatch: storedCountBots !== expectedCountBots || runtimeCountBots !== expectedCountBots,
|
|
};
|
|
}
|
|
|
|
public async sysAdminRecalculateWapServerCount(
|
|
data: SysAdminWapServerRecalculateCountParams,
|
|
): Promise<SysAdminWapServerRecalculateCountResult> {
|
|
if (!data.serverId) {
|
|
throw new Error("Server id is required");
|
|
}
|
|
|
|
if (!MONGO_OBJECT_ID_REGEX.test(data.serverId)) {
|
|
throw new Error("Server id must be a valid Mongo ObjectId");
|
|
}
|
|
|
|
const server = await this.servers.serverList.findOne({ _id: data.serverId });
|
|
|
|
if (!server) {
|
|
throw new Error("WAP server not found");
|
|
}
|
|
|
|
const assignedCompanies = await CompaniesManager.companies.find({ wapServerId: data.serverId });
|
|
const before = server.countBots;
|
|
const after = assignedCompanies.length;
|
|
|
|
server.countBots = after;
|
|
await server.save();
|
|
|
|
return { serverId: String(server.id || server._id), before, after };
|
|
}
|
|
|
|
public async sysAdminDeleteWapServer(data: SysAdminDeleteWapServerParams): Promise<SysAdminDeleteWapServerResult> {
|
|
if (!data.serverId) {
|
|
throw new Error("Server id is required");
|
|
}
|
|
|
|
if (!MONGO_OBJECT_ID_REGEX.test(data.serverId)) {
|
|
throw new Error("Server id must be a valid Mongo ObjectId");
|
|
}
|
|
|
|
const server = await this.servers.serverList.findOne({ _id: data.serverId });
|
|
|
|
if (!server) {
|
|
throw new Error("WAP server not found");
|
|
}
|
|
|
|
const force = data.force === true;
|
|
|
|
if (!force && server.active) {
|
|
throw new Error("WAP server must be inactive before deletion");
|
|
}
|
|
|
|
const assignedCompanies = await CompaniesManager.companies.find({ wapServerId: data.serverId });
|
|
|
|
if (!force && assignedCompanies.length > 0) {
|
|
throw new Error(`WAP server cannot be deleted because it has ${assignedCompanies.length} associated bot(s) in DB`);
|
|
}
|
|
|
|
let detectedBots: SysAdminWapContainerDto[] = [];
|
|
let auditFailed = false;
|
|
let auditMessage: string | undefined;
|
|
|
|
try {
|
|
detectedBots = await this.getSysAdminRuntimeContainers(server);
|
|
} catch (error) {
|
|
const message = error instanceof Error && error.message ? error.message : "unknown error";
|
|
|
|
if (!force) {
|
|
throw new Error(`WAP server cannot be deleted because bot-admin could not be audited. Verify runtime bots manually before deleting. ${message}`);
|
|
}
|
|
|
|
auditFailed = true;
|
|
auditMessage = message;
|
|
}
|
|
|
|
if (!force && detectedBots.length > 0) {
|
|
throw new Error(`WAP server cannot be deleted because bot-admin detected ${detectedBots.length} runtime bot(s)`);
|
|
}
|
|
|
|
let deletedRuntimeBots = 0;
|
|
let skippedRuntimeBots = 0;
|
|
|
|
if (force) {
|
|
const baseUrl = this.getBotAdminApiUrl(server.ipv4);
|
|
|
|
for (const bot of detectedBots) {
|
|
if (!bot.organizationId) {
|
|
skippedRuntimeBots += 1;
|
|
continue;
|
|
}
|
|
|
|
try {
|
|
const result = await this.deleteSysAdminRuntimeBot(baseUrl, encodeURIComponent(bot.organizationId), true);
|
|
|
|
if (result.botDeleted) {
|
|
deletedRuntimeBots += 1;
|
|
}
|
|
} catch (_error) {
|
|
skippedRuntimeBots += 1;
|
|
}
|
|
}
|
|
|
|
for (const company of assignedCompanies) {
|
|
const companyDocument = company as typeof company & { save: () => Promise<unknown> };
|
|
|
|
companyDocument.wapServerId = undefined;
|
|
await companyDocument.save();
|
|
}
|
|
}
|
|
|
|
await this.servers.delete(data.serverId);
|
|
|
|
return {
|
|
serverId: data.serverId,
|
|
deleted: true,
|
|
...(force
|
|
? {
|
|
force: true,
|
|
detachedOrganizations: assignedCompanies.length,
|
|
deletedRuntimeBots,
|
|
skippedRuntimeBots,
|
|
...(auditFailed ? { auditFailed: true, auditMessage } : {}),
|
|
}
|
|
: {}),
|
|
};
|
|
}
|
|
|
|
public async sysAdminStartBot(data: SysAdminWapServerOrganizationActionParams): Promise<void> {
|
|
await this.postSysAdminBotAction(data, "start");
|
|
}
|
|
|
|
public async sysAdminStopBot(data: SysAdminWapServerOrganizationActionParams): Promise<void> {
|
|
await this.postSysAdminBotAction(data, "stop");
|
|
}
|
|
|
|
public async sysAdminRestartBot(data: SysAdminWapServerOrganizationActionParams): Promise<void> {
|
|
await this.postSysAdminBotAction(data, "stop");
|
|
await this.waitForSysAdminBotStopped(data);
|
|
|
|
try {
|
|
await this.postSysAdminBotAction(data, "start");
|
|
} catch (error) {
|
|
const message = error instanceof Error && error.message ? error.message : "unknown error";
|
|
|
|
throw new Error(`Bot restart stopped the bot, but start failed. The bot may be stopped. ${message}`);
|
|
}
|
|
}
|
|
|
|
public async sysAdminGetQr(data: SysAdminWapServerOrganizationActionParams): Promise<SysAdminWapServerQrResult> {
|
|
const { baseUrl, encodedOrganizationId } = await this.getSysAdminServerActionTarget(data);
|
|
const targetUrl = `${baseUrl}containers/${encodedOrganizationId}/qr`;
|
|
|
|
try {
|
|
const response = await axios.get(targetUrl, {
|
|
responseType: "arraybuffer",
|
|
timeout: BOT_ADMIN_ACTION_TIMEOUT_MS,
|
|
});
|
|
const qr = Buffer.from(response.data, "binary").toString("base64");
|
|
|
|
return { serverId: data.serverId, organizationId: data.organizationId, qr };
|
|
} catch (error) {
|
|
throw this.createBotAdminActionError(error, targetUrl);
|
|
}
|
|
}
|
|
|
|
public async sysAdminDeleteBot(
|
|
data: SysAdminWapServerOrganizationActionParams,
|
|
): Promise<SysAdminWapServerDeleteBotResult> {
|
|
const { server, baseUrl, encodedOrganizationId } = await this.getSysAdminServerActionTarget(data);
|
|
const before = server.countBots ?? 0;
|
|
|
|
await this.deleteSysAdminRuntimeBot(baseUrl, encodedOrganizationId);
|
|
|
|
// Deleting the runtime bot intentionally keeps the DB assignment; detach is a separate sysadmin action.
|
|
const assignedCompanies = await CompaniesManager.companies.find({ wapServerId: data.serverId });
|
|
const after = assignedCompanies.length;
|
|
server.countBots = after;
|
|
await server.save();
|
|
|
|
return { serverId: data.serverId, organizationId: data.organizationId, assignmentRemains: true, before, after };
|
|
}
|
|
|
|
public async sysAdminDetachOrganizationServer(
|
|
data: SysAdminWapServerOrganizationActionParams,
|
|
): Promise<SysAdminWapServerDetachResult> {
|
|
const { server, baseUrl, encodedOrganizationId } = await this.getSysAdminServerActionTarget(data);
|
|
const company = await CompaniesManager.companies.findOne({ _id: data.organizationId });
|
|
|
|
if (!company) {
|
|
throw new Error("Organization not found");
|
|
}
|
|
|
|
if (String(company.wapServerId || "") !== data.serverId) {
|
|
throw new Error("Organization is not assigned to this WAP server");
|
|
}
|
|
|
|
const before = server.countBots ?? 0;
|
|
const deleteResult = await this.deleteSysAdminRuntimeBot(baseUrl, encodedOrganizationId, true);
|
|
|
|
company.wapServerId = undefined;
|
|
await company.save();
|
|
|
|
const assignedCompanies = await CompaniesManager.companies.find({ wapServerId: data.serverId });
|
|
const after = assignedCompanies.length;
|
|
server.countBots = after;
|
|
await server.save();
|
|
|
|
return { serverId: data.serverId, organizationId: data.organizationId, ...deleteResult, before, after };
|
|
}
|
|
}
|
|
|
|
const WapServerList = new WapServerManager();
|
|
|
|
export default WapServerList;
|