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
+18
View File
@@ -0,0 +1,18 @@
import express from "express";
import dotenv from "dotenv";
import containerRoutes from "./routes/containers";
dotenv.config();
const app = express()
const PORT = process.env.DEFAULT_ADMIN_PORT || 3000;
app.use(express.json());
// Rutas
app.use("/containers", containerRoutes);
// Iniciar el servidor
app.listen(PORT, () => {
console.log(`Server is running on http://localhost:${PORT}`);
});
+194
View File
@@ -0,0 +1,194 @@
import { Router } from "express";
import {
createContainer,
listContainers,
removeContainer,
startContainer,
stopContainer,
getAvailablePort,
existsContainer,
getContainerByOrganization,
} from "../services/dockerService";
import axios from "axios";
export enum BOT_STATE {
"EXISTS" = "exists",
"NOT_EXISTS" = "not_exists",
}
export enum SERVER_STATE {
"RUNNING" = "running",
"NOT_WORKING" = "not_working",
}
const router = Router();
const getContainerApiUrl = (name: string, port: string): string => {
const isProduction = process.env.NODE_ENV === "production";
const baseUrl = isProduction ? name : "localhost";
const url = `http:/${baseUrl}:${port}/`;
return url;
};
// Obtener todos los contenedores
router.get("/", async (req, res) => {
try {
const containers = await listContainers();
res.json(containers);
} catch (error: any) {
res.status(500).json({ error: error.message });
}
});
// Iniciar un contenedor por ID
router.post("/:id/start", async (req, res) => {
try {
const organizationId = req.params.id;
const containerInfo = await getContainerByOrganization(organizationId);
await startContainer(containerInfo.Id);
res.json({ message: `El bot ${containerInfo.Id} ha sido iniciado` });
} catch (error: any) {
if (error.message.indexOf("already started") >= 0) {
res.json({ message: `El bot se encontraba iniciado anteriormente.` });
return;
}
res.status(500).json({ error: error.message });
}
});
// Obtener el QR de conexion para el bot de una organization.
router.get("/:id/qr", async (req, res) => {
try {
const organizationId = req.params.id;
const containerInfo = await getContainerByOrganization(organizationId);
const containerName = containerInfo.Names[0].toString();
const containerPort = containerInfo.Ports[0].PublicPort.toString();
const url = getContainerApiUrl(containerName, containerPort);
const response = await axios.get(url, { responseType: "arraybuffer" });
res.setHeader("Content-type", "image/png");
res.send(response.data);
} catch (error: any) {
res.status(500).json({ error: error.message });
}
});
// Detener un contenedor por ID
router.post("/:id/stop", async (req, res) => {
try {
const organizationId = req.params.id;
const containerInfo = await getContainerByOrganization(organizationId);
await stopContainer(containerInfo.Id);
res.json({ message: `El bot de la organizacion ${organizationId} ha sido detenido` });
} catch (error: any) {
console.log(error.message);
res.status(500).json({ error: error.message });
}
});
// Eliminar un contenedor por ID
router.delete("/:id", async (req, res) => {
try {
const organizationId = req.params.id;
const containerInfo = await getContainerByOrganization(organizationId);
await removeContainer(containerInfo.Id);
res.json({ message: `El bot de la organizacion ${organizationId} removed` });
} catch (error: any) {
res.status(500).json({ error: error.message });
}
});
// Crear un nuevo contenedor
router.post("/create", async (req, res) => {
try {
const { organizationId } = req.body;
const port = await getAvailablePort();
if (!port) {
res.status(400).json({ error: "Port is required" });
return;
}
if (await existsContainer(organizationId)) {
res.json({ message: `Ya existe un bot de whatsapp creado para esta organizacion` });
return;
}
const containerName = `${organizationId}_bot_${port}`;
const imageName = "txbot";
const containerId = await createContainer(port, containerName, imageName);
console.log(
`Se ha creado un nuevo bot en el contenedor ${containerId} para la organizacion ${organizationId}`
);
res.json({ message: `El bot ha sido creado con exito`, port, organizationId });
} catch (error: any) {
res.status(500).json({ error: error.message });
}
});
// Consultar el estado de un contenedor par auna organizationId
router.post("/:id/status", async (req, res) => {
try {
const organizationId = req.params.id;
const info = await getContainerByOrganization(organizationId);
res.json({
state: BOT_STATE.EXISTS,
status: info.Status,
serverState: SERVER_STATE.RUNNING,
});
} catch (error: any) {
res.status(500).json({ error: error.message, code: 404 });
}
});
export interface MessageData {
number: string;
message: string;
}
router.post("/:id/send", async (req, res) => {
try {
const organizationId = req.params.id;
const data = req.body as MessageData;
const info = await getContainerByOrganization(organizationId);
const containerName = info.Names[0].toString();
const containerPort = info.Ports[0].PublicPort.toString();
const url = getContainerApiUrl(containerName, containerPort);
const result = await axios.post(url + "v1/messages", data);
if (result.status !== 200) {
throw new Error("Ha ocurrido un error al enviar el mensaje" + result);
}
res.json({ message: `Mensaje enviado` });
} catch (error: any) {
res.status(500).json({ error: error.message });
}
});
router.post("/:id/session", async (req, res) => {
try {
const organizationId = req.params.id;
const data = req.body as MessageData;
const info = await getContainerByOrganization(organizationId);
const containerName = info.Names[0].toString();
const containerPort = info.Ports[0].PublicPort.toString();
const url = getContainerApiUrl(containerName, containerPort);
const result = await axios.post(url + "v1/session", data);
if (result.status !== 200) {
throw new Error("Ha ocurrido un error al enviar el mensaje" + result);
}
const resultData = result.data;
res.json({ message: `${resultData}` });
} catch (error: any) {
console.log(error);
res.status(500).json({ error: error.message });
}
});
export default router;
+152
View File
@@ -0,0 +1,152 @@
import Docker from "dockerode";
const docker = new Docker();
/**
* Listar todos los contenedores.
*/
export const listContainers = async () => {
return docker.listContainers({ all: true, filters: { ancestor: ["txbot:latest"] } });
};
export const existsContainer = async (organizationId: string): Promise<boolean> => {
const contenedores = await listContainers();
const contenedoresNombres = Array.from(contenedores, (item) =>
item.Names[0].split("_bot")[0].substring(1)
);
if (contenedoresNombres.includes(organizationId)) {
return true;
}
return false;
};
export const getOrganizationBotPort = async (organizationId: string): Promise<number> => {
const contenedores = await listContainers();
for (const contenedor of contenedores) {
if (contenedor.Names.includes(organizationId)) {
const port = contenedor.Ports[0].PublicPort;
return port;
}
}
return -1;
};
export const getAvailablePort = async (): Promise<number> => {
const contenedores = await listContainers();
let defaultPort = process.env.DEFAULT_BOT_START_PORT
? parseInt(process.env.DEFAULT_BOT_START_PORT)
: -1;
if (contenedores.length == 0) {
return defaultPort;
}
contenedores.sort((a, b) => b.Created - a.Created);
//Obtengo el contenedor mas reciente.
const lastContainerCreated = contenedores.shift();
if (!lastContainerCreated) {
return defaultPort;
}
const lastPort = lastContainerCreated.Ports;
if (!lastPort) {
return defaultPort;
}
if (lastPort.length == 0) {
return defaultPort;
}
return lastPort[0].PublicPort + 1;
};
/**
* Iniciar un contenedor por ID.
*/
export const startContainer = async (containerId: string) => {
const container = docker.getContainer(containerId);
await container.start();
};
/**
* Detener un contenedor por ID.
*/
export const stopContainer = async (containerId: string) => {
const container = docker.getContainer(containerId);
await container.stop();
};
export const createContainer = async (
port: number,
name: string,
image: string
): Promise<string> => {
try {
// Crear un contenedor con las configuraciones especificadas
const container = await docker.createContainer({
Image: image,
name,
Env: [`PORT=${port}`],
ExposedPorts: {
[`${port}/tcp`]: {}, // Define el puerto expuesto
},
HostConfig: {
PortBindings: {
[`${port}/tcp`]: [{ HostPort: `${port}` }], // Mapea el puerto entre el host y el contenedor
},
},
NetworkingConfig: {
EndpointsConfig: {
"bot-admin-api_tx-net": {}, // importante: debe coincidir con el nombre exacto de la red
},
},
});
// Iniciar el contenedor creado
await container.start();
// Devolver el ID del contenedor como resultado
return container.id;
} catch (error: any) {
throw new Error(`Error al crear el contenedor: ${error.message}`);
}
};
export const removeContainer = async (containerId: string): Promise<void> => {
try {
// Obtiene el contenedor por su ID
const container = docker.getContainer(containerId);
// Verifica si el contenedor existe antes de intentar eliminarlo
const containerInfo = await container.inspect();
if (containerInfo.State.Running) {
// Si el contenedor está corriendo, debe detenerse primero
await container.stop();
}
// Elimina el contenedor
await container.remove();
} catch (error: any) {
throw new Error(`Error al eliminar el contenedor: ${error.message}`);
}
};
export const getContainerByOrganization = async (
organizationId: string
): Promise<Docker.ContainerInfo> => {
const contenedores = await listContainers();
for (const contenedor of contenedores) {
if (contenedor.Names.some((item) => item.includes(organizationId))) {
return contenedor;
}
}
throw new Error("Container not found");
};