Compare commits
24 Commits
WEBINT-328
...
WEBINT-175
| Author | SHA1 | Date | |
|---|---|---|---|
| 30f1819a4e | |||
| 964ea6add9 | |||
| 602878acf4 | |||
| 0aa52feaac | |||
| 15b70309da | |||
| 7001fccbf7 | |||
| cffee785b2 | |||
| 33d260310c | |||
| e359acc1d5 | |||
| bb4bce4a6d | |||
| eac74ef0cd | |||
| 1dc4eb5648 | |||
| a35a6c2b60 | |||
| 1f78f4a3e1 | |||
| 1e98559f3a | |||
| ef0f860b9d | |||
| 0bff55379f | |||
| 4d34308a13 | |||
| 70bf73b0a4 | |||
| e3849d8217 | |||
| d9854a12a8 | |||
| 48d387a8da | |||
| 93d3e13793 | |||
| cbbc0f6edb |
5
.env
5
.env
@@ -20,10 +20,13 @@ POSTGRES_DB=postgres
|
||||
POSTGRES_DATABASE=postgres
|
||||
POSTGRES_PORT=5433
|
||||
POSTGRES_USER=postgres
|
||||
POSTGRES_PASSWORD=1234
|
||||
POSTGRES_PASSWORD='1234'
|
||||
|
||||
# Para el postgres local para generar el script de resultado de migraciones
|
||||
PGHOST=localhost
|
||||
PGUSER=alvar
|
||||
PGPASSWORD=alvar
|
||||
PGPORT=5433
|
||||
|
||||
# Proxy
|
||||
CONNECTIONS_URL=https://sim-connections.savefamilygps.net
|
||||
|
||||
@@ -8,6 +8,8 @@ RUN corepack enable
|
||||
COPY ./dist/packages ./packages
|
||||
COPY ./.yarnrc.yml ./
|
||||
COPY ./docs ./docs
|
||||
# Para las migraciones
|
||||
COPY ./deployment ./deployment
|
||||
|
||||
COPY ./package.json ./
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
#!/bin/sh
|
||||
cd /home
|
||||
|
||||
cd /home/node/app && yarn start
|
||||
cd /home/node/app
|
||||
yarn migrate
|
||||
yarn start
|
||||
|
||||
@@ -69,7 +69,6 @@ pipeline {
|
||||
cleanRemote: false,
|
||||
remoteDirectory: "$APP_REMOTE_PATH",
|
||||
sourceFiles: "deployment/database/**/*",
|
||||
removePrefix: "deployment",
|
||||
),
|
||||
sshTransfer(
|
||||
cleanRemote: false,
|
||||
|
||||
@@ -11,7 +11,7 @@ post {
|
||||
}
|
||||
|
||||
body:form-urlencoded {
|
||||
iccid: 8933201125068886692
|
||||
iccid: 8933201125065160380
|
||||
offer: SAVEFAMILY1
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ post {
|
||||
}
|
||||
|
||||
body:form-urlencoded {
|
||||
iccid: 8933201125068886692
|
||||
iccid: 8933201125068890074
|
||||
}
|
||||
|
||||
settings {
|
||||
|
||||
@@ -15,7 +15,7 @@ params:query {
|
||||
}
|
||||
|
||||
body:form-urlencoded {
|
||||
iccid: 8933201125068886692
|
||||
iccid: 8933201125068886700
|
||||
}
|
||||
|
||||
settings {
|
||||
|
||||
21
docs/sim-api/ReActivate.bru
Normal file
21
docs/sim-api/ReActivate.bru
Normal file
@@ -0,0 +1,21 @@
|
||||
meta {
|
||||
name: ReActivate
|
||||
type: http
|
||||
seq: 13
|
||||
}
|
||||
|
||||
post {
|
||||
url: {{baseurl}}/sim/reActivate
|
||||
body: formUrlEncoded
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
body:form-urlencoded {
|
||||
iccid: 8933201125065160380
|
||||
~offer: SAVEFAMILY1
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
}
|
||||
4
docs/sim-api/environments/simconnections.bru
Normal file
4
docs/sim-api/environments/simconnections.bru
Normal file
@@ -0,0 +1,4 @@
|
||||
vars {
|
||||
baseurl: http://sim-connections.savefamilygps.net
|
||||
}
|
||||
color: #C77A0F
|
||||
20
docs/sim-api/test proxy.bru
Normal file
20
docs/sim-api/test proxy.bru
Normal file
@@ -0,0 +1,20 @@
|
||||
meta {
|
||||
name: test proxy
|
||||
type: http
|
||||
seq: 14
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{baseurl}}/simconnections/alai/select?iccid=1111111111111111111
|
||||
body: none
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
params:query {
|
||||
iccid: 1111111111111111111
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
}
|
||||
@@ -5,13 +5,13 @@ meta {
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{actionsUrl}}/massActions?massActionId=5192767
|
||||
url: {{actionsUrl}}/massActions?massActionId=5363116
|
||||
body: formUrlEncoded
|
||||
auth: bearer
|
||||
}
|
||||
|
||||
params:query {
|
||||
massActionId: 5192767
|
||||
massActionId: 5363116
|
||||
~identifier.identifierType: ICCID
|
||||
~identifier.identifiers: 8933201125065160463,8933201125065160422
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
"migrate": "yarn db-migrate -e .env -m deployment/database/migrations -t 99.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@sf-alvar/db-migrate": "1.0.3",
|
||||
"@sf-alvar/db-migrate": "1.0.6",
|
||||
"@tsconfig/node22": "^22.0.5",
|
||||
"amqp-connection-manager": "^5.0.0",
|
||||
"amqplib": "^0.10.9",
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
PORT=3000
|
||||
RABBITMQ_USER=guest
|
||||
RABBITMQ_PASSWORD=guest
|
||||
NOS_BASE_URL=localhost
|
||||
|
||||
ENVIORMENT=development
|
||||
|
||||
16
packages/sim-consumidor-nos/config/env/index.ts
vendored
16
packages/sim-consumidor-nos/config/env/index.ts
vendored
@@ -1,5 +1,16 @@
|
||||
import { loadEnvFile } from "node:process";
|
||||
loadEnvFile("../../.env")
|
||||
import path from "node:path";
|
||||
|
||||
try {
|
||||
loadEnvFile(path.join("./.env")) // base
|
||||
} catch (e) {
|
||||
console.error("Error cargando el .env desde ./.env")
|
||||
}
|
||||
try {
|
||||
loadEnvFile(path.join("../../.env")) // Global
|
||||
} catch (e) {
|
||||
console.error("Error cargando el .env desde ../../.env")
|
||||
}
|
||||
|
||||
export const env = {
|
||||
ENVIRONMENT: process.env.ENVIORMENT,
|
||||
@@ -18,5 +29,8 @@ export const env = {
|
||||
RABBITMQ_SECURE: process.env.RABBITMQ_SECURE,
|
||||
RABBITMQ_RETRY_INTERVAL: process.env.RABBITMQ_INTERVAL,
|
||||
RABBITMQ_VHOST: String(process.env.RABBITMQ_VHOST),
|
||||
|
||||
// ESPECIFICO NOS
|
||||
NOS_BASE_URL: String(process.env.NOS_BASE_URL)
|
||||
};
|
||||
|
||||
|
||||
69
packages/sim-consumidor-nos/config/eventBus.config.ts
Normal file
69
packages/sim-consumidor-nos/config/eventBus.config.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import { RabbitMQEventBus, RMQConnectionParams } from "sim-shared/infrastructure/RabbitMQEventBus.js"
|
||||
import { Channel } from "amqp-connection-manager"
|
||||
import { env } from "./env/index.js"
|
||||
|
||||
const rmqUser = env.RABBITMQ_USER
|
||||
const rmqPass = env.RABBITMQ_PASSWORD
|
||||
const rmqHost = env.RABBITMQ_HOST
|
||||
const rmqPort = Number(env.RABBITMQ_PORT)
|
||||
const rmqSecure = false
|
||||
const rmqVhost = env.RABBITMQ_VHOST
|
||||
|
||||
export const rmqConnOptions = <RMQConnectionParams>{
|
||||
username: rmqUser,
|
||||
password: rmqPass,
|
||||
vhost: rmqVhost,
|
||||
hostname: rmqHost,
|
||||
port: rmqPort,
|
||||
secure: rmqSecure,
|
||||
}
|
||||
|
||||
export const rabbitmqEventBus = new RabbitMQEventBus({
|
||||
connectionParams: rmqConnOptions,
|
||||
buildStructure: buildQueues,
|
||||
maxRetry: 5
|
||||
})
|
||||
|
||||
async function buildQueues(channel: Channel) {
|
||||
const QUEUES = {
|
||||
NOS: "sim.nos",
|
||||
NOSDLX: "sim.nos.dlx",
|
||||
NOSDEL: "sim.nos.delayed",
|
||||
}
|
||||
|
||||
const EXCHANGES = {
|
||||
MAIN: "sim.exchange",
|
||||
DLX: "sim.ex.nos.dlx",
|
||||
DEL: "sim.ex.nos.delayed"
|
||||
}
|
||||
|
||||
const DELAY = 10 * 1000
|
||||
const BASE_NOS_KEY = "sim.nos.#"
|
||||
|
||||
await channel.assertExchange(EXCHANGES.DEL, "topic")
|
||||
await channel.assertExchange(EXCHANGES.DLX, "topic")
|
||||
await channel.assertExchange(EXCHANGES.MAIN, "topic")
|
||||
|
||||
await channel.assertQueue(QUEUES.NOS)
|
||||
await channel.assertQueue(QUEUES.NOSDLX)
|
||||
await channel.assertQueue(QUEUES.NOSDEL, {
|
||||
durable: true,
|
||||
arguments: {
|
||||
'x-message-ttl': DELAY,
|
||||
'x-dead-letter-exchange': EXCHANGES.MAIN,
|
||||
}
|
||||
})
|
||||
|
||||
// Cola dead-letter
|
||||
await channel.bindQueue(QUEUES.NOSDLX, EXCHANGES.DLX, "sim.nos.#")
|
||||
// Cola delay
|
||||
await channel.bindQueue(QUEUES.NOSDEL, EXCHANGES.DEL, BASE_NOS_KEY)
|
||||
// Cola nos -> main exchange
|
||||
await channel.bindQueue(QUEUES.NOS, EXCHANGES.MAIN, BASE_NOS_KEY)
|
||||
|
||||
}
|
||||
|
||||
export async function startRMQClient() {
|
||||
await rabbitmqEventBus.connect()
|
||||
return rabbitmqEventBus
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
import { startRMQClient } from "#config/eventBusConfig"
|
||||
import { startRMQClient } from "#config/eventBus.config.js"
|
||||
import { SimNosController } from "./aplication/SimNOS.controller.js"
|
||||
|
||||
async function startWorker() {
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"build": "yarn tsc --project tsconfig.json && yarn tsc-alias && cp package.json ../../dist/packages/sim-consumidor-nos/",
|
||||
"esbuild": "esbuild index.ts --platform=node",
|
||||
"start": "node ../../dist/packages/sim-consumidor-nos/index.js"
|
||||
"start": "node ../../dist/packages/sim-consumidor-nos/index.js",
|
||||
"dev": "tsx watch index.ts"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
|
||||
@@ -19,7 +19,7 @@ export class SimRouter {
|
||||
["activate", this.simController.activate()],
|
||||
["pause", this.simController.suspend()],
|
||||
["cancel", this.simController.terminate()],
|
||||
["reActivate", this.simController.reActivate()],
|
||||
["reactivate", this.simController.reActivate()],
|
||||
["preActivate", this.simController.preActivate()]
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -75,13 +75,14 @@ export class SimUseCases {
|
||||
operation: args.operation,
|
||||
iccids: String(args.iccid),
|
||||
status: "noMassID",
|
||||
request_id: response.data.requestId
|
||||
request_id: response.data.requestId,
|
||||
correlation_id: args.correlation_id
|
||||
}
|
||||
|
||||
// TODO: Esto tiene poco sentido si la operacion ya se
|
||||
// tenia que haber creado en el generador
|
||||
this.logOperation(operation)
|
||||
.then().catch(e => console.error(e))
|
||||
.then().catch(e => console.error("Error login operation", e))
|
||||
|
||||
if (args.correlation_id != undefined) {
|
||||
this.orderRepository.updateOrder({
|
||||
@@ -115,6 +116,15 @@ export class SimUseCases {
|
||||
public activate(activationData: ActivationData): () => Promise<Result<string, boolean>> {
|
||||
const OPERATION_URL = "/actions/activateLine"
|
||||
return async () => {
|
||||
const iccid = activationData.identifier.identifiers
|
||||
// Comporbación excepcional para saber si la linea está suspendida
|
||||
const statusLinea = await this.objeniousRepository.getLinesAPI("ICCID", [String(iccid)])
|
||||
console.log("statusLinea, ", iccid, statusLinea)
|
||||
if (statusLinea.data != undefined && statusLinea.data[0].status.networkStatus == "SUSPENDED") {
|
||||
const res = await this.reActivate(activationData)()
|
||||
return res;
|
||||
}
|
||||
|
||||
const req = this.httpClient.client.post(OPERATION_URL, {
|
||||
dueDate: activationData.dueDate,
|
||||
identifier: activationData.identifier,
|
||||
@@ -198,16 +208,29 @@ export class SimUseCases {
|
||||
}
|
||||
}
|
||||
|
||||
public reActivate(pauseData: ActionData): () => Promise<Result<string, boolean>> {
|
||||
public reActivate(reactivateData: ActionData): () => Promise<Result<string, boolean>> {
|
||||
const OPERATION_URL = "/actions/reactivateLine"
|
||||
return async () => {
|
||||
const req = this.httpClient.client.post(OPERATION_URL, {
|
||||
...pauseData
|
||||
...reactivateData
|
||||
})
|
||||
|
||||
try {
|
||||
const response = await req
|
||||
|
||||
// Creacion de la operacion inicial, antes de tener los datos
|
||||
const operation: ObjeniousOperation = {
|
||||
operation: "reactivate",
|
||||
iccids: reactivateData.identifier.identifiers[0],
|
||||
status: "noMassID",
|
||||
request_id: response.data.requestId,
|
||||
correlation_id: reactivateData.correlation_id
|
||||
}
|
||||
|
||||
// TODO: Esto tiene poco sentido si la operacion ya se
|
||||
// tenia que haber creado en el generador
|
||||
this.logOperation(operation)
|
||||
.then().catch(e => console.error("Error login operation", e))
|
||||
if (response.status == 200) {
|
||||
console.log("[o] Sim solicitud de reactivacion ", response.data)
|
||||
return <Result<string, boolean>>{
|
||||
@@ -223,7 +246,7 @@ export class SimUseCases {
|
||||
} catch (error) {
|
||||
console.error("[x] Error reactivacion", (error as AxiosError).response?.status)
|
||||
return <Result<string, boolean>>{
|
||||
error: "Error reactivando la sim" + pauseData.identifier,
|
||||
error: "Error reactivando la sim" + reactivateData.identifier,
|
||||
data: undefined
|
||||
}
|
||||
}
|
||||
@@ -276,6 +299,17 @@ export class SimUseCases {
|
||||
const correlation_id = suspendData.correlation_id
|
||||
const iccid = suspendData.identifier.identifiers
|
||||
|
||||
|
||||
const operation: ObjeniousOperation = {
|
||||
operation: "suspend",
|
||||
iccids: iccid[0],
|
||||
status: "running",
|
||||
correlation_id: correlation_id
|
||||
}
|
||||
// No se registra hasta que no pase por la tabla de pausas
|
||||
// this.logOperation(operation)
|
||||
// .then().catch(e => console.error("Error login operation", e))
|
||||
|
||||
const fail = (error: string) => {
|
||||
console.error("[Sim.usecases]", error)
|
||||
if (correlation_id != undefined) {
|
||||
@@ -286,6 +320,14 @@ export class SimUseCases {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO REGISTRAR EL ORDER
|
||||
if (correlation_id != undefined) {
|
||||
await this.orderRepository.createOrder({
|
||||
correlation_id: correlation_id,
|
||||
order_type: "pause"
|
||||
})
|
||||
}
|
||||
|
||||
let activationDate;
|
||||
try {
|
||||
activationDate = await this.findActivationDate(suspendData)
|
||||
@@ -332,10 +374,11 @@ export class SimUseCases {
|
||||
public stage_terminate(terminateData: ActionData): () => Promise<Result<string, boolean>> {
|
||||
return async (): Promise<Result<string, boolean>> => {
|
||||
const correlation_id = terminateData.correlation_id
|
||||
const iccid = terminateData.identifier.identifiers[0]
|
||||
|
||||
const activationDate = await this.findActivationDate(terminateData)
|
||||
const newTask: CreatePauseCancelTaskDTO = {
|
||||
iccid: terminateData.identifier.identifiers[0],
|
||||
iccid: iccid,
|
||||
activation_date: activationDate,
|
||||
next_check: undefined, // Que se haga instantaneamente al ser la primera
|
||||
operation_type: "terminate",
|
||||
@@ -344,6 +387,17 @@ export class SimUseCases {
|
||||
|
||||
const taskCreated = await this.pauseRepository.addTask(newTask)
|
||||
|
||||
const operation: ObjeniousOperation = {
|
||||
operation: "terminate",
|
||||
iccids: iccid,
|
||||
status: "running",
|
||||
correlation_id: correlation_id
|
||||
}
|
||||
|
||||
/**
|
||||
this.logOperation(operation)
|
||||
.then().catch(e => console.error("Error login operation", e))
|
||||
*/
|
||||
// Caso que la task no se pueda crear en la BDD
|
||||
if (taskCreated.error != undefined) {
|
||||
console.error("[Sim.usecases]", taskCreated.error)
|
||||
|
||||
@@ -27,8 +27,8 @@ export const rabbitmqEventBus = new RabbitMQEventBus({
|
||||
async function buildQueues(channel: Channel) {
|
||||
const QUEUES = {
|
||||
OBJ: "sim.objenious",
|
||||
DLX: "sim.objenious.dlx",
|
||||
DEL: "sim.objenious.delayed"
|
||||
OBJDLX: "sim.objenious.dlx",
|
||||
OBJDEL: "sim.objenious.delayed",
|
||||
}
|
||||
|
||||
const EXCHANGES = {
|
||||
@@ -45,8 +45,8 @@ async function buildQueues(channel: Channel) {
|
||||
await channel.assertExchange(EXCHANGES.MAIN, "topic")
|
||||
|
||||
await channel.assertQueue(QUEUES.OBJ)
|
||||
await channel.assertQueue(QUEUES.DLX)
|
||||
await channel.assertQueue(QUEUES.DEL, {
|
||||
await channel.assertQueue(QUEUES.OBJDLX)
|
||||
await channel.assertQueue(QUEUES.OBJDEL, {
|
||||
durable: true,
|
||||
arguments: {
|
||||
'x-message-ttl': DELAY,
|
||||
|
||||
@@ -57,7 +57,7 @@ export class PauseCancelTaskRepository {
|
||||
public async addTask(task: CreatePauseCancelTaskDTO): Promise<Result<string, PauseCancelTask>> {
|
||||
|
||||
const sql = `
|
||||
INSERT INTO pause_cancel_tasks (iccid, activation_date, next_check, last_checked, operation_type, actionData)
|
||||
INSERT INTO pause_cancel_tasks (iccid, activation_date, next_check, last_checked, operation_type, action_data)
|
||||
VALUES ($1, $2, $3, now(), $4, $5)
|
||||
RETURNING *;
|
||||
`;
|
||||
|
||||
@@ -131,6 +131,21 @@ export class SimController {
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
public reActivation() {
|
||||
return this.controllerGenerator<{ iccid: string, offer: string }, { iccid: string, offer: string, compañia: string }>({
|
||||
validator: iccidValidator,
|
||||
mapBody: (b) => {
|
||||
const { iccid, offer } = b
|
||||
const compañia = companyFromIccid(iccid)
|
||||
return { iccid, compañia, offer }
|
||||
},
|
||||
useCase: (args) => this.simUseCases.reActivation(args),
|
||||
onError: (d, e) => console.error("[x] Error reactivacion: ", d, e),
|
||||
onSuccess: console.log
|
||||
})
|
||||
}
|
||||
|
||||
public cancelation() {
|
||||
return this.controllerGenerator<{ iccid: string }, { iccid: string, compañia: string }>({
|
||||
validator: iccidValidator,
|
||||
|
||||
@@ -130,6 +130,36 @@ export class SimUsecases {
|
||||
}
|
||||
}
|
||||
|
||||
async reActivation(args: { iccid: string, compañia: string, offer: string }):
|
||||
Promise<Result<string, { iccid: string, message_id: string, operation: "reactivate" }>> {
|
||||
const activationEvent = <SimEvents.activation>{
|
||||
key: `sim.${args.compañia}.reactivate`,
|
||||
payload: {
|
||||
iccid: args.iccid,
|
||||
offer: args.offer
|
||||
}
|
||||
}
|
||||
const activationWithId = this.addMessage_id(activationEvent)
|
||||
console.log("[d] Reactivation ", activationWithId)
|
||||
await this.eventBus.publish([activationWithId])
|
||||
const createdOrder = await this.saveOrder<SimEvents.reActivation>(activationWithId)
|
||||
|
||||
if (createdOrder.error != undefined) {
|
||||
console.error(createdOrder.error)
|
||||
return {
|
||||
error: createdOrder.error
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
data: {
|
||||
iccid: args.iccid,
|
||||
operation: "reactivate",
|
||||
message_id: createdOrder.data?.correlation_id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async preActivation(args: { iccid: string, compañia: string }):
|
||||
Promise<Result<string, { iccid: string, message_id: string, operation: "preactivation" }>> {
|
||||
|
||||
@@ -174,8 +204,10 @@ export class SimUsecases {
|
||||
|
||||
const cancelationWithId = this.addMessage_id(cancelationEvent)
|
||||
console.log("[d] Cancelation ", cancelationWithId)
|
||||
|
||||
await this.eventBus.publish([cancelationWithId])
|
||||
const savedOrder = await this.saveOrder(cancelationWithId)
|
||||
|
||||
if (savedOrder.error != undefined) {
|
||||
console.error(savedOrder.error)
|
||||
return {
|
||||
@@ -205,11 +237,12 @@ export class SimUsecases {
|
||||
iccid: args.iccid
|
||||
}
|
||||
}
|
||||
|
||||
const pauseWithId = this.addMessage_id(pauseEvent)
|
||||
console.log("[d] Pause", pauseWithId)
|
||||
await this.eventBus.publish([pauseWithId])
|
||||
await this.saveOrder(pauseWithId)
|
||||
const savedOrder = await this.saveOrder(pauseWithId)
|
||||
//await this.saveOrder(pauseWithId)
|
||||
const savedOrder = await this.saveOrder<SimEvents.pause>(pauseWithId)
|
||||
|
||||
if (savedOrder.error != undefined) {
|
||||
console.error(savedOrder.error)
|
||||
|
||||
@@ -22,4 +22,5 @@ export const env = {
|
||||
RABBITMQ_SECURE: process.env.RABBITMQ_SECURE,
|
||||
RABBITMQ_RETRY_INTERVAL: process.env.RABBITMQ_INTERVAL,
|
||||
RABBITMQ_VHOST: String(process.env.RABBITMQ_VHOST),
|
||||
CONNECTIONS_URL: String(process.env.CONNECTIONS_URL)
|
||||
};
|
||||
|
||||
@@ -5,6 +5,7 @@ import { simRoutes } from "./infrastructure/simRoutes.http.js"
|
||||
import { rabbitmqEventBus } from '#config/eventBusConfig.js';
|
||||
import { env } from "#config/env/index.js"
|
||||
import { orderRoutes } from "#adapters/orderRoutes.http.js";
|
||||
import { connectionsRoutes } from "#adapters/simconnectionsRoutes.js";
|
||||
|
||||
const PORT = env.API_PORT
|
||||
const HOSTNAME = "0.0.0.0"
|
||||
@@ -26,6 +27,7 @@ app.use(express.json());
|
||||
app.use(express.urlencoded({ extended: true }));
|
||||
|
||||
app.use("/sim", simRoutes)
|
||||
app.use("/simconnections", connectionsRoutes)
|
||||
app.use("/orders", orderRoutes)
|
||||
|
||||
app.use("/docs", express.static(path.join(process.cwd(), '../../docs')))
|
||||
|
||||
@@ -23,6 +23,7 @@ simRoutes.get("/status", () => { })
|
||||
simRoutes.post("/save", simController.save())
|
||||
|
||||
simRoutes.post("/activate", simController.activation())
|
||||
simRoutes.post("/reActivate", simController.reActivation())
|
||||
|
||||
simRoutes.post("/preActivate", simController.preactivation())
|
||||
|
||||
@@ -35,4 +36,5 @@ simRoutes.post("/test", simController.test())
|
||||
// Proceso especifico de ALAI para liberar sims canceladas
|
||||
simRoutes.post("/free", simController.free())
|
||||
|
||||
|
||||
export { simRoutes }
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
import { env } from "#config/env/index.js"
|
||||
import { Router } from "express"
|
||||
import { ClientRequest, IncomingMessage } from "http"
|
||||
import { createProxyMiddleware } from "http-proxy-middleware"
|
||||
import { Request } from "express"
|
||||
|
||||
export const connectionsRoutes = Router()
|
||||
|
||||
const CONNECTIONS_URL = env.CONNECTIONS_URL// TODO: Meter al ENV
|
||||
//const CONNECTIONS_URL = "http://sf-nfc-server.savefamilygps.net"
|
||||
|
||||
console.log("CONNURL: ", CONNECTIONS_URL)
|
||||
|
||||
connectionsRoutes.use("", createProxyMiddleware({
|
||||
target: CONNECTIONS_URL,
|
||||
changeOrigin: true,
|
||||
pathRewrite: {
|
||||
'^/': "/simconnections/"
|
||||
},
|
||||
on: {
|
||||
proxyReq: (proxyReq: ClientRequest, req: Request) => {
|
||||
const protocol = req.protocol;
|
||||
const host = req.get('host');
|
||||
const originalFullUrl = `${protocol}://${host}${req.originalUrl}`;
|
||||
const destinationFullUrl = `${CONNECTIONS_URL}${proxyReq.path}`;
|
||||
/*
|
||||
constnsole.log('──────────────────────────────────────────────────');
|
||||
console.log(`[PROXY_DEBUG]`);
|
||||
console.log(` ENTRADA: ${originalFullUrl}`);
|
||||
console.log(` MÉTODO : ${req.method}`);
|
||||
console.log(` DESTINO: ${destinationFullUrl}`);
|
||||
console.log('──────────────────────────────────────────────────');
|
||||
*/
|
||||
console.log(`[Proxy Req]: ${req.method} ${req.url} -> ${proxyReq.path}`);
|
||||
},
|
||||
proxyRes: (proxyRes, req, res) => {
|
||||
console.log(`[Proxy Res] Status: ${proxyRes.statusCode} desde ${req.url}`);
|
||||
},
|
||||
error: (err, req, res) => {
|
||||
console.error('[Proxy Error]:', err);
|
||||
|
||||
// Validamos que 'res' tenga el método 'status' (típico de Express Response)
|
||||
if ('status' in res) {
|
||||
//@ts-ignore
|
||||
res.status(500).json({ message: 'Error interno en el Gateway' });
|
||||
}
|
||||
},
|
||||
}
|
||||
}))
|
||||
|
||||
|
||||
|
||||
// Rutas
|
||||
/**
|
||||
connectionsRoutes.post('/simconnections/alai/preactivate',);
|
||||
connectionsRoutes.get('/simconnections/alai/pause',);
|
||||
connectionsRoutes.post('/simconnections/alai/terminate',);
|
||||
connectionsRoutes.get('/simconnections/alai/pauseByPhone',);
|
||||
connectionsRoutes.get('/simconnections/alai/active',);
|
||||
connectionsRoutes.get('/simconnections/alai/change_orderid',);
|
||||
connectionsRoutes.get('/simconnections/alai/select',);
|
||||
connectionsRoutes.get('/simconnections/alai/select-iccid',);
|
||||
connectionsRoutes.get('/simconnections/alai/selectFromDb',);
|
||||
connectionsRoutes.get('/simconnections/alai/selectPage',);
|
||||
connectionsRoutes.post('/simconnections/alai/schedulePause',);
|
||||
connectionsRoutes.get('/simconnections/shopify/getbyWP',);
|
||||
connectionsRoutes.get('/simconnections/shopify/getbyWPS',);
|
||||
|
||||
///
|
||||
|
||||
connectionsRoutes.get('/simconnections/sim/associate',);
|
||||
connectionsRoutes.post('/simconnections/sim/search',);
|
||||
connectionsRoutes.post('/simconnections/sim/historic',);
|
||||
connectionsRoutes.post('/simconnections/sim/update',);
|
||||
|
||||
///
|
||||
|
||||
connectionsRoutes.post('/simconnections/nos/activate',);
|
||||
connectionsRoutes.get('/simconnections/nos/select',);
|
||||
connectionsRoutes.get('/simconnections/nos/selectPage',);
|
||||
|
||||
//Unificación
|
||||
connectionsRoutes.post('/simconnections/sim/active',); // True false
|
||||
connectionsRoutes.patch('/simconnections/sim/pause',);
|
||||
connectionsRoutes.get('/simconnections/sim/select',);
|
||||
connectionsRoutes.get('/simconnections/sim/select-phone',);
|
||||
**/
|
||||
@@ -53,6 +53,7 @@
|
||||
"cors": "*",
|
||||
"dotenv": "*",
|
||||
"express": "*",
|
||||
"http-proxy-middleware": "^3.0.5",
|
||||
"sim-shared": "sim-shared:*",
|
||||
"typescript": "*"
|
||||
},
|
||||
|
||||
@@ -62,6 +62,7 @@ async function startCron() {
|
||||
orderRepository
|
||||
)
|
||||
|
||||
await objTask.getPendingOperations()
|
||||
const PERIODO_PETICIONES = 10 * 60 * 1000
|
||||
const interval = setInterval(async () => {
|
||||
try {
|
||||
@@ -80,7 +81,6 @@ async function startCron() {
|
||||
}
|
||||
}, PERIODO_VOLCADO)
|
||||
|
||||
|
||||
await pauseTask.run()
|
||||
const PERIODO_CANCELACIONES = 60 * 60 * 1000;
|
||||
const clacelacionesInterval = setInterval(async () => {
|
||||
|
||||
@@ -118,9 +118,11 @@ export class PauseTerminateTask {
|
||||
|
||||
switch (linea.status.billingStatus) {
|
||||
case "ACTIVATED":
|
||||
let exito = false;
|
||||
let result = null;
|
||||
// IMPORTANTE COMRPOBAR EL DUE DATE
|
||||
|
||||
// Se termina el proceso aqui pero pasa a ser una operación de
|
||||
// objenious por lo que puede fallar y quedaria registrado en
|
||||
// la tabla objenious_operation
|
||||
switch (operacionTipo) {
|
||||
case "suspend":
|
||||
result = await this.simUsecases.suspend(actionData)()
|
||||
|
||||
25
packages/sim-rabbit-monitor/index.ts
Normal file
25
packages/sim-rabbit-monitor/index.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import express from "express"
|
||||
import cors from 'cors';
|
||||
import path from 'path';
|
||||
import { env } from "packages/sim-shared/config/env/index.js"
|
||||
|
||||
const PORT = env.API_PORT
|
||||
const HOSTNAME = "0.0.0.0"
|
||||
const app = express()
|
||||
|
||||
// Middleware
|
||||
app.use(cors());
|
||||
|
||||
app.use(express.json());
|
||||
app.use(express.urlencoded({ extended: true }));
|
||||
|
||||
app.use("/docs", express.static(path.join(process.cwd(), '../../docs')))
|
||||
|
||||
app.get("/health", (req, res) => {
|
||||
res.status(200).json({ status: "ok" })
|
||||
})
|
||||
|
||||
app.listen(PORT, HOSTNAME, () => {
|
||||
console.log("[o] Servidor iniciado en el puerto %d", PORT)
|
||||
})
|
||||
export default {}
|
||||
15
packages/sim-shared/aplication/Rabbit.usecases.ts
Normal file
15
packages/sim-shared/aplication/Rabbit.usecases.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { RabbitManagementClient } from "../infrastructure/RabbitManagementClient.js";
|
||||
import { Queue } from "../domain/Queue.js";
|
||||
|
||||
export class RabbitUseCases {
|
||||
private client: RabbitManagementClient
|
||||
|
||||
constructor(client: RabbitManagementClient) {
|
||||
this.client = client
|
||||
}
|
||||
|
||||
public async getQueuesStatus(): Promise<Queue[]> {
|
||||
const queues = await this.client.getQueues()
|
||||
return queues.sort((a, b) => b.messages - a.messages)
|
||||
}
|
||||
}
|
||||
20
packages/sim-shared/config/env/index.ts
vendored
Normal file
20
packages/sim-shared/config/env/index.ts
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
import { loadEnvFile } from "node:process";
|
||||
import path from "node:path";
|
||||
|
||||
|
||||
loadEnvFile(path.join("../../.env")) // Global
|
||||
|
||||
export const env = {
|
||||
ENVIRONMENT: process.env.ENVIORMENT,
|
||||
API_PORT: parseInt(process.env.API_PORT ?? "3000"),
|
||||
RABBITMQ_HOST: String(process.env.RABBITMQ_HOST ?? "localhost"),
|
||||
RABBITMQ_USER: String(process.env.RABBITMQ_USER ?? "test"),
|
||||
RABBITMQ_PASSWORD: String(process.env.RABBITMQ_PASSWORD ?? "test"),
|
||||
RABBITMQ_EXCHANGE: String(process.env.RABBITMQ_EXCHANGE ?? "/"),
|
||||
RABBITMQ_PORT: parseInt(process.env.RABBITMQ_PORT ?? "5672"),
|
||||
RABBITMQ_MODULENAME: process.env.MODULENAME,
|
||||
RABBITMQ_TTL: process.env.RABBITMQ_TTL,
|
||||
RABBITMQ_SECURE: process.env.RABBITMQ_SECURE,
|
||||
RABBITMQ_RETRY_INTERVAL: process.env.RABBITMQ_INTERVAL,
|
||||
RABBITMQ_VHOST: String(process.env.RABBITMQ_VHOST),
|
||||
};
|
||||
@@ -1,5 +1,5 @@
|
||||
import { RabbitMQEventBus, RMQConnectionParams } from "sim-shared/infrastructure/RabbitMQEventBus.js"
|
||||
import { env } from "./env"
|
||||
import { env } from "./env/index.js"
|
||||
|
||||
const rmqUser = env.RABBITMQ_USER
|
||||
const rmqPass = env.RABBITMQ_PASSWORD
|
||||
@@ -22,18 +22,5 @@ export const rabbitmqEventBus = new RabbitMQEventBus({
|
||||
})
|
||||
|
||||
export async function startRMQClient() {
|
||||
await rabbitmqEventBus.connect().catch(async e => {
|
||||
console.error("Error en la conexion RMQ")
|
||||
await rabbitmqEventBus.connect()
|
||||
})
|
||||
|
||||
// Bindings especificos, deberia meterlos en la clase
|
||||
try {
|
||||
await rabbitmqEventBus.channel?.assertQueue("sim.nos")
|
||||
} catch {
|
||||
console.log("[i] Cola de sims de nos creada")
|
||||
await rabbitmqEventBus.channel?.bindQueue("sim.nos", "sim.exchange", "sim.nos.*")
|
||||
}
|
||||
|
||||
return rabbitmqEventBus
|
||||
await rabbitmqEventBus.connect()
|
||||
}
|
||||
@@ -80,7 +80,8 @@ export type FinishOrderDTO =
|
||||
IdOrCorrelationID
|
||||
&
|
||||
{
|
||||
reason?: string
|
||||
reason?: string,
|
||||
end_date?: Date
|
||||
}
|
||||
|
||||
export type ErrorOrderDTO =
|
||||
|
||||
7
packages/sim-shared/domain/Queue.ts
Normal file
7
packages/sim-shared/domain/Queue.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export interface Queue {
|
||||
name: string;
|
||||
messages: number;
|
||||
ready: number;
|
||||
unacked: number;
|
||||
consumers: number;
|
||||
}
|
||||
@@ -24,7 +24,7 @@ export namespace SimEvents {
|
||||
}
|
||||
|
||||
export type reActivation = DomainEvent & {
|
||||
key: `sim.${string}.reActivate`,
|
||||
key: `sim.${string}.reactivate`,
|
||||
payload: {
|
||||
iccid: string
|
||||
},
|
||||
|
||||
@@ -12,7 +12,7 @@ export type ObjeniousOperation = {
|
||||
id?: number;
|
||||
/** Uuid del mensaje asociado a la operacion */
|
||||
correlation_id?: string;
|
||||
operation: "activate" | string; // TODO: completar y actualizar
|
||||
operation: "activate" | "suspend" | "terminate" | string; // TODO: completar y actualizar
|
||||
retry_count?: number;
|
||||
max_retry?: number;
|
||||
max_date_retry?: string | null;
|
||||
@@ -27,8 +27,7 @@ export type ObjeniousOperation = {
|
||||
}
|
||||
|
||||
export type ObjeniousOperationChange = {
|
||||
id?: number;
|
||||
operation_id: number;
|
||||
id?: number; operation_id: number;
|
||||
info?: string | null;
|
||||
error?: string | null;
|
||||
new_status: StatusEnum;
|
||||
|
||||
@@ -1,6 +1,20 @@
|
||||
import { describe, it } from "node:test";
|
||||
import { before, describe, it } from "node:test";
|
||||
import { ObjeniousOperationsRepository } from "./ObjeniousOperationRepository.js";
|
||||
import { httpObjClient, postgresClient } from "../config/config.test.js";
|
||||
import { ObjeniousOperation } from "../domain/operationsRepository.port.js";
|
||||
|
||||
const correctOperation: ObjeniousOperation = {
|
||||
iccids: "test",
|
||||
operation: "activate",
|
||||
status: "finished"
|
||||
}
|
||||
|
||||
const errorOperation: ObjeniousOperation = {
|
||||
iccids: "test",
|
||||
operation: "terminate",
|
||||
status: "error",
|
||||
error: "mensaje de error"
|
||||
}
|
||||
|
||||
describe("[Integration] Test API requests", () => {
|
||||
const repository = new ObjeniousOperationsRepository(
|
||||
@@ -8,7 +22,18 @@ describe("[Integration] Test API requests", () => {
|
||||
postgresClient
|
||||
)
|
||||
|
||||
it("Read /lines with multiple iccids", () => {
|
||||
before(async () => {
|
||||
await repository.createOperation(correctOperation)
|
||||
await repository.createOperation(errorOperation)
|
||||
})
|
||||
|
||||
it("Read last operation by line", () => {
|
||||
/**
|
||||
* Objetivo:
|
||||
* - Cuando se va a hacer una operacion de sim hay que cancelarla directamente si:
|
||||
* - Ya hay una en curso del mismo tipo.
|
||||
* - Ya ha terminado una del mismo tipo.
|
||||
* - Se ignoran las erroneas
|
||||
*/
|
||||
})
|
||||
})
|
||||
|
||||
@@ -146,6 +146,20 @@ export class ObjeniousOperationsRepository implements IOperationsRepository {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async getLastOperationOfLine(iccid: string) {
|
||||
const query = `
|
||||
SELECT * FROM public.objenious_operation
|
||||
WHERE iccids = $1 and error is null
|
||||
ORDER BY id asc limit 1
|
||||
`
|
||||
const values = [iccid];
|
||||
const { rows } = await this.pgClient.query(query, values);
|
||||
return <Result<string, ObjeniousOperation>>{
|
||||
data: rows[0]
|
||||
}
|
||||
}
|
||||
|
||||
async updateOperation(data: ObjeniousOperationChange): Promise<Result<string, ObjeniousOperation>> {
|
||||
const client = await this.pgClient.connect();
|
||||
const {
|
||||
|
||||
@@ -302,8 +302,8 @@ export class OrderRepository {
|
||||
UPDATE order_tracking
|
||||
SET
|
||||
status = 'finished',
|
||||
update_date = (now() at time zone 'utc'),
|
||||
finish_date = (now() at time zone 'utc')
|
||||
update_date = now(),
|
||||
finish_date = now()
|
||||
WHERE id = $1
|
||||
RETURNING id, status, update_date;
|
||||
`
|
||||
|
||||
37
packages/sim-shared/infrastructure/RabbitManagementClient.ts
Normal file
37
packages/sim-shared/infrastructure/RabbitManagementClient.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
|
||||
import axios, { AxiosInstance } from "axios";
|
||||
import { Queue } from "sim-shared/domain/Queue.js";
|
||||
|
||||
export class RabbitManagementClient {
|
||||
private client: AxiosInstance;
|
||||
|
||||
constructor(args: {
|
||||
baseURL: string;
|
||||
user: string;
|
||||
password: string;
|
||||
}) {
|
||||
this.client = axios.create({
|
||||
baseURL: args.baseURL,
|
||||
auth: {
|
||||
username: args.user,
|
||||
password: args.password,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
public async getQueues(): Promise<Queue[]> {
|
||||
try {
|
||||
const response = await this.client.get("/queues")
|
||||
return response.data.map((q: any) => ({
|
||||
name: q.name,
|
||||
messages: q.messages,
|
||||
ready: q.messages_ready,
|
||||
unacked: q.messages_unacknowledged,
|
||||
consumers: q.consumers
|
||||
}))
|
||||
} catch (err) {
|
||||
console.error("[RabbitManagementClient] Error obteniendo colas", err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
import { Request, Response } from "express";
|
||||
import { DashboardUseCases } from "./Dashboard.usecases.js";
|
||||
import { QueueSummary } from "#domain/Dashboard.js";
|
||||
import { OrderTracking } from "packages/sim-shared/domain/Order.js";
|
||||
|
||||
export class DashboardController {
|
||||
constructor(private readonly useCases: DashboardUseCases) {}
|
||||
|
||||
public async getQueuesFragment(req: Request, res: Response) {
|
||||
const data = await this.useCases.getDashboardData()
|
||||
res.send(renderQueuesHtml(data.queues))
|
||||
}
|
||||
|
||||
public async getOrdersFragment(req: Request, res: Response) {
|
||||
const data = await this.useCases.getDashboardData()
|
||||
res.send(renderOrdersHtml(data.pendingOrders))
|
||||
}
|
||||
|
||||
public async getFullFragment(req: Request, res: Response) {
|
||||
const data = await this.useCases.getDashboardData();
|
||||
res.send(`
|
||||
${renderQueuesHtml(data.queues)}
|
||||
${renderOrdersHtml(data.pendingOrders)}
|
||||
<p class="updated-at">Actualizado: ${new Date(data.generatedAt).toLocaleTimeString("es-ES")}</p>
|
||||
`)
|
||||
}
|
||||
}
|
||||
|
||||
function queueBadgeClass(count: number): string {
|
||||
if (count === 0 ) return "badge--ok"
|
||||
if (count < 10) return "badge--warn"
|
||||
return "badge--error"
|
||||
}
|
||||
|
||||
function orderBadgeClass(status: string): string {
|
||||
if (status === 'finished' ) return "badge--ok"
|
||||
if (status == 'pending') return "badge--warn"
|
||||
return "badge--error"
|
||||
}
|
||||
|
||||
function renderQueuesHtml(queues: QueueSummary): string {
|
||||
const row = (label: string, q: typeof queues.main) => `
|
||||
<tr>
|
||||
<td>${label}</td>
|
||||
<td>${q.name}</td>
|
||||
<td><span class="badge ${queueBadgeClass(q.messages)}">${q.messages}</span></td>
|
||||
<td>${q.ready}</td>
|
||||
<td>${q.unacked}</td>
|
||||
<td>${q.consumers}</td>
|
||||
</tr>
|
||||
`
|
||||
return `
|
||||
<section id="queues-section">
|
||||
<h2>Estado de las colas</h2>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Tipo</th>
|
||||
<th>Cola</th>
|
||||
<th>Total</th>
|
||||
<th>Ready</th>
|
||||
<th>Unacked</th>
|
||||
<th>Consumers</th>
|
||||
<tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${row("Principal", queues.main)}
|
||||
${row("Reintentos", queues.retry)}
|
||||
${row("Fallidos (DLX)", queues.dlx)}
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
`
|
||||
}
|
||||
|
||||
function renderOrdersHtml(orders: OrderTracking<unknown>[]): string {
|
||||
const rows = orders.map(o => `
|
||||
<tr>
|
||||
<td>${o.id}</td>
|
||||
<td>${o.correlation_id}</td>
|
||||
<td>${o.order_type}</td>
|
||||
<td><span class="badge ${orderBadgeClass(o.status)}">${o.status}</span></td>
|
||||
<td>${o.retry_count ?? 0}</td>
|
||||
<td>${new Date(o.start_date).toLocaleString("es-ES")}</td>
|
||||
</tr>
|
||||
`
|
||||
).join("")
|
||||
|
||||
return `
|
||||
<section id="orders-section">
|
||||
<h2>Tareas pendientes (${orders.length})</h2>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Correlation ID</th>
|
||||
<th>Tipo</th>
|
||||
<th>Estado</th>
|
||||
<th>Reintentos</th>
|
||||
<th>Inicio</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>${rows}</tbody>
|
||||
</table>
|
||||
</section>
|
||||
`
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import { RabbitManagementClient } from "packages/sim-shared/infrastructure/RabbitManagementClient.js";
|
||||
import { OrderRepository } from "packages/sim-shared/infrastructure/OrderRepository.js";
|
||||
import { DashboardData, QueueSummary } from "#domain/Dashboard.js";
|
||||
import { Queue } from "packages/sim-shared/domain/Queue.js";
|
||||
import { env } from "#config/env/index.js";
|
||||
|
||||
const EMPTY_QUEUE: Queue = { name: "", messages: 0, ready: 0, unacked: 0, consumers: 0}
|
||||
|
||||
export class DashboardUseCases {
|
||||
constructor(
|
||||
private readonly rabbitClient: RabbitManagementClient,
|
||||
private readonly orderRepo: OrderRepository,
|
||||
) {}
|
||||
|
||||
public async getDashboardData(): Promise<DashboardData> {
|
||||
//si uno peta, no rompe al otro
|
||||
const [queuesResult, pendingResult] = await Promise.allSettled([
|
||||
this.rabbitClient.getQueues(),
|
||||
this.orderRepo.getPendingOrders({ limit: 100 }), //por poner un límite
|
||||
])
|
||||
|
||||
let queues: QueueSummary = { main: EMPTY_QUEUE, retry: EMPTY_QUEUE, dlx: EMPTY_QUEUE}
|
||||
|
||||
if (queuesResult.status === 'fulfilled') {
|
||||
const all = queuesResult.value
|
||||
const find = (name: string) => all.find(q => q.name === name) ?? {...EMPTY_QUEUE, name}
|
||||
queues = {
|
||||
main: find(env.QUEUE_MAIN!),
|
||||
retry: find(env.QUEUE_RETRY!),
|
||||
dlx: find(env.QUEUE_DLX!)
|
||||
}
|
||||
} else {
|
||||
console.error('[Dasboard] Error obteniendo colas: ', queuesResult.reason)
|
||||
}
|
||||
const pendingOrders = (pendingResult.status === "fulfilled" && !pendingResult.value.error)
|
||||
? pendingResult.value.data ?? []
|
||||
: [];
|
||||
if (pendingResult.status === 'rejected') {
|
||||
console.error('[Dashboard]Error obteniendo tareas: ', pendingResult.reason)
|
||||
}
|
||||
return {
|
||||
queues,
|
||||
pendingOrders,
|
||||
generatedAt: new Date().toISOString(),
|
||||
}
|
||||
}
|
||||
}
|
||||
22
packages/sim-visualizador-tareas-back/config/env/index.ts
vendored
Normal file
22
packages/sim-visualizador-tareas-back/config/env/index.ts
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
import { loadEnvFile } from "node:process";
|
||||
import path from "node:path";
|
||||
|
||||
loadEnvFile(path.join("../../env"))
|
||||
|
||||
export const env = {
|
||||
API_PORT: parseInt(process.env.API_PORT ?? "3010"),
|
||||
POSTGRES_USER: process.env.POSTGRES_USER,
|
||||
POSTGRES_PASSWORD: process.env.POSTGRES_PASSWORD,
|
||||
POSTGRES_PORT: process.env.POSTGRES_PORT,
|
||||
POSTGRES_HOST: process.env.POSTGRES_HOST,
|
||||
POSTGRES_DATABASE: process.env.POSTGRES_DATABASE,
|
||||
RABBITMQ_HOST: String(process.env.RABBITMQ_HOST),
|
||||
RABBITMQ_PORT: process.env.RABBITMQ_PORT,
|
||||
RABBITMQ_USER: String(process.env.RABBITMQ_USER),
|
||||
RABBITMQ_PASSWORD: String(process.env.RABBITMQ_PASSWORD),
|
||||
RABBITMQ_SECURE: process.env.RABBITMQ_SECURE,
|
||||
RABBITMQ_VHOST: process.env.RABBITMQ_VHOST,
|
||||
QUEUE_MAIN: process.env.QUEUE_MAIN,
|
||||
QUEUE_RETRY: process.env.QUEUE_RETRY,
|
||||
QUEUE_DLX: process.env.QUEUE_DLX
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { Pool } from "pg";
|
||||
import { PgClient } from "packages/sim-shared/infrastructure/PgClient.js";
|
||||
import { env } from "./env/index.js";
|
||||
|
||||
export const pgPool = new Pool({
|
||||
user: env.POSTGRES_USER!,
|
||||
host: env.POSTGRES_HOST!,
|
||||
database: env.POSTGRES_DATABASE!,
|
||||
password: env.POSTGRES_PASSWORD!,
|
||||
port: Number(env.POSTGRES_PORT) || 5432
|
||||
})
|
||||
|
||||
export const postgresClient = new PgClient({ pool: pgPool })
|
||||
@@ -0,0 +1,8 @@
|
||||
import { RabbitManagementClient } from "packages/sim-shared/infrastructure/RabbitManagementClient.js";
|
||||
import { env } from "./env/index.js";
|
||||
|
||||
export const rabbitManagementClient = new RabbitManagementClient({
|
||||
baseURL: env.RABBITMQ_HOST!,
|
||||
user: env.RABBITMQ_USER!,
|
||||
password: env.RABBITMQ_PASSWORD!
|
||||
})
|
||||
14
packages/sim-visualizador-tareas-back/domain/Dashboard.ts
Normal file
14
packages/sim-visualizador-tareas-back/domain/Dashboard.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { Queue } from "packages/sim-shared/domain/Queue.js";
|
||||
import { OrderTracking } from "packages/sim-shared/domain/Order.js";
|
||||
|
||||
export type QueueSummary = {
|
||||
main: Queue;
|
||||
retry: Queue;
|
||||
dlx: Queue;
|
||||
}
|
||||
|
||||
export interface DashboardData {
|
||||
queues: QueueSummary;
|
||||
pendingOrders: OrderTracking<unknown>[];
|
||||
generatedAt: string;
|
||||
}
|
||||
45
packages/sim-visualizador-tareas-back/index.ts
Normal file
45
packages/sim-visualizador-tareas-back/index.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import express from "express";
|
||||
import cors from "cors";
|
||||
import path from "path";
|
||||
import { env } from "#config/env/index.js";
|
||||
import { pgPool, postgresClient } from "#config/postgreConfig.js";
|
||||
import { OrderRepository } from "packages/sim-shared/infrastructure/OrderRepository.js";
|
||||
import { DashboardController } from "./aplication/Dashboard.controller.js";
|
||||
import { DashboardUseCases } from "./aplication/Dashboard.usecases.js";
|
||||
import { createDashboardRouter } from "#adapters/dashboardRoutes.http.js";
|
||||
import { PgClient } from "packages/sim-shared/infrastructure/PgClient.js";
|
||||
import { RabbitManagementClient } from "packages/sim-shared/infrastructure/RabbitManagementClient.js";
|
||||
|
||||
const pgClient = new PgClient({
|
||||
pool: pgPool,
|
||||
})
|
||||
|
||||
const rabbitClient = new RabbitManagementClient({
|
||||
baseURL: env.RABBITMQ_HOST,
|
||||
user: env.RABBITMQ_USER,
|
||||
password: env.RABBITMQ_PASSWORD,
|
||||
})
|
||||
|
||||
const orderRepo = new OrderRepository(pgClient)
|
||||
const useCases = new DashboardUseCases(rabbitClient, orderRepo)
|
||||
const controller = new DashboardController(useCases)
|
||||
const dashboardRouter = createDashboardRouter(controller)
|
||||
|
||||
const app = express();
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
app.use(express.static(path.join(process.cwd(), "../sim-visualizador-tareas-front")));
|
||||
app.use("/", dashboardRouter);
|
||||
app.get("/health", (_req, res) => res.status(200).json({ status: "ok" }));
|
||||
|
||||
async function main() {
|
||||
await pgClient.checkDatabaseConnection();
|
||||
app.listen(Number(env.API_PORT), "0.0.0.0", () => {
|
||||
console.log("[o] Visualizador iniciado en el puerto %d", env.API_PORT);
|
||||
});
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error("[x] Error arrancando el visualizador:", err);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
import { Router } from "express";
|
||||
import { DashboardController } from "../aplication/Dashboard.controller.js";
|
||||
|
||||
export function createDashboardRouter(controller: DashboardController): Router {
|
||||
const router = Router()
|
||||
|
||||
router.get("/fragments/queues", (req, res) => controller.getQueuesFragment(req, res))
|
||||
router.get("/fragments/orders", (req, res) => controller.getOrdersFragment(req, res))
|
||||
router.get("/fragments/dashboard", (req, res) => controller.getFullFragment(req, res))
|
||||
|
||||
return router
|
||||
}
|
||||
72
packages/sim-visualizador-tareas-back/package.json
Normal file
72
packages/sim-visualizador-tareas-back/package.json
Normal file
@@ -0,0 +1,72 @@
|
||||
{
|
||||
"name": "sim-visualizador-tareas-back",
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"description": "",
|
||||
"main": "index.ts",
|
||||
"imports": {
|
||||
"#config/*.js": {
|
||||
"types": "./config/*.ts",
|
||||
"default": "./config/*.js"
|
||||
},
|
||||
"#config/*": {
|
||||
"types": "./config/*.ts",
|
||||
"default": "./config/*.js"
|
||||
},
|
||||
"#adapters/*.js": {
|
||||
"types": "./infrastructure/*.ts",
|
||||
"default": "./infrastructure/*.js"
|
||||
},
|
||||
"#adapters/*": {
|
||||
"types": "./infrastructure/*.ts",
|
||||
"default": "./infrastructure/*.js"
|
||||
},
|
||||
"#domain/*.js": {
|
||||
"types": "./domain/*.ts",
|
||||
"default": "./domain/*.js"
|
||||
},
|
||||
"#domain/*": {
|
||||
"types": "./domain/*.ts",
|
||||
"default": "./domain/*.js"
|
||||
},
|
||||
"#ports/*.js": {
|
||||
"types": "./ports/*.ts",
|
||||
"default": "./ports/*.js"
|
||||
},
|
||||
"#ports/*": {
|
||||
"types": "./ports/*.ts",
|
||||
"default": "./ports/*.js"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"test": "node --import tsx --test ./**/*.test.ts",
|
||||
"build": "tsc --build && tsc-alias -p tsconfig.json && cp package.json ../../dist/packages/sim-visualizador-tareas-back/",
|
||||
"dev": "tsx watch index.ts",
|
||||
"start": "node ../../dist/packages/sim-visualizador-tareas-back/index.js"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"packageManager": "yarn@4.12.0",
|
||||
"dependencies": {
|
||||
"@tsconfig/node22": "*",
|
||||
"amqplib": "^0.10.9",
|
||||
"axios": "*",
|
||||
"cors": "*",
|
||||
"dotenv": "*",
|
||||
"express": "*",
|
||||
"sim-shared": "sim-shared:*",
|
||||
"typescript": "*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/amqplib": "^0.10.8",
|
||||
"@types/cors": "*",
|
||||
"@types/express": "*",
|
||||
"@types/node": "*",
|
||||
"@types/supertest": "*",
|
||||
"prettier": "*",
|
||||
"supertest": "*",
|
||||
"tsc-alias": "^1.8.16",
|
||||
"tsx": "*",
|
||||
"vitest": "*"
|
||||
}
|
||||
}
|
||||
17
packages/sim-visualizador-tareas-back/tsconfig.json
Normal file
17
packages/sim-visualizador-tareas-back/tsconfig.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../dist",
|
||||
"rootDir": "../../",
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
],
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
"../../packages/sim-shared/**/*.ts"
|
||||
],
|
||||
"files": [
|
||||
"config/env/index.ts"
|
||||
]
|
||||
}
|
||||
0
packages/sim-visualizador-tareas-front/index.html
Normal file
0
packages/sim-visualizador-tareas-front/index.html
Normal file
8
sf-sim.code-workspace
Normal file
8
sf-sim.code-workspace
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"folders": [
|
||||
{
|
||||
"path": "."
|
||||
}
|
||||
],
|
||||
"settings": {}
|
||||
}
|
||||
73
yarn.lock
73
yarn.lock
@@ -452,15 +452,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@sf-alvar/db-migrate@npm:1.0.3":
|
||||
version: 1.0.3
|
||||
resolution: "@sf-alvar/db-migrate@npm:1.0.3::__archiveUrl=https%3A%2F%2Fgit.savefamilygps.net%2Fapi%2Fpackages%2FSaveFamily%2Fnpm%2F%2540sf-alvar%252Fdb-migrate%2F-%2F1.0.3%2Fdb-migrate-1.0.3.tgz"
|
||||
"@sf-alvar/db-migrate@npm:1.0.6":
|
||||
version: 1.0.6
|
||||
resolution: "@sf-alvar/db-migrate@npm:1.0.6::__archiveUrl=https%3A%2F%2Fgit.savefamilygps.net%2Fapi%2Fpackages%2FSaveFamily%2Fnpm%2F%2540sf-alvar%252Fdb-migrate%2F-%2F1.0.6%2Fdb-migrate-1.0.6.tgz"
|
||||
dependencies:
|
||||
pg: "npm:^8.18.0"
|
||||
yargs: "npm:^18.0.0"
|
||||
bin:
|
||||
db-migrate: lib/index.js
|
||||
checksum: 10/2b5745a5ce60456fc7fee1e6a8580978a520fedd8abbbc695557847cdf2b36aa5e1d795721ad35bc151fc9373dfa023bde73d6f43ba412b17293a1822c09fe6b
|
||||
checksum: 10/070f1388ff1c6fd2d24c3139d779e871bc0db94f11dd2013aa7eb5728e3c21e594bac0f4d46f8f3132391a9903cca56d5c864862c622d70f24e0db0ffcbbbf0e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -576,6 +576,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/http-proxy@npm:^1.17.15":
|
||||
version: 1.17.17
|
||||
resolution: "@types/http-proxy@npm:1.17.17"
|
||||
dependencies:
|
||||
"@types/node": "npm:*"
|
||||
checksum: 10/893e46e12be576baa471cf2fc13a4f0e413eaf30a5850de8fdbea3040e138ad4171234c59b986cf7137ff20a1582b254bf0c44cfd715d5ed772e1ab94dd75cd1
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/methods@npm:^1.1.4":
|
||||
version: 1.1.4
|
||||
resolution: "@types/methods@npm:1.1.4"
|
||||
@@ -1154,7 +1163,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"debug@npm:4, debug@npm:^4.1.1, debug@npm:^4.3.4, debug@npm:^4.3.7, debug@npm:^4.4.0, debug@npm:^4.4.3":
|
||||
"debug@npm:4, debug@npm:^4.1.1, debug@npm:^4.3.4, debug@npm:^4.3.6, debug@npm:^4.3.7, debug@npm:^4.4.0, debug@npm:^4.4.3":
|
||||
version: 4.4.3
|
||||
resolution: "debug@npm:4.4.3"
|
||||
dependencies:
|
||||
@@ -1413,6 +1422,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"eventemitter3@npm:^4.0.0":
|
||||
version: 4.0.7
|
||||
resolution: "eventemitter3@npm:4.0.7"
|
||||
checksum: 10/8030029382404942c01d0037079f1b1bc8fed524b5849c237b80549b01e2fc49709e1d0c557fa65ca4498fc9e24cff1475ef7b855121fcc15f9d61f93e282346
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"expect-type@npm:^1.2.2":
|
||||
version: 1.3.0
|
||||
resolution: "expect-type@npm:1.3.0"
|
||||
@@ -1527,6 +1543,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"follow-redirects@npm:^1.0.0":
|
||||
version: 1.16.0
|
||||
resolution: "follow-redirects@npm:1.16.0"
|
||||
peerDependenciesMeta:
|
||||
debug:
|
||||
optional: true
|
||||
checksum: 10/3fbe3d80b3b544c22705d837aa5d4a0d07a740d913534a2620b0a004c610af4148e3b58723536dd099aaa1c9d3a155964bde9665d6e5cb331460809a1fc572fd
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"follow-redirects@npm:^1.15.11":
|
||||
version: 1.15.11
|
||||
resolution: "follow-redirects@npm:1.15.11"
|
||||
@@ -1788,6 +1814,31 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"http-proxy-middleware@npm:^3.0.5":
|
||||
version: 3.0.5
|
||||
resolution: "http-proxy-middleware@npm:3.0.5"
|
||||
dependencies:
|
||||
"@types/http-proxy": "npm:^1.17.15"
|
||||
debug: "npm:^4.3.6"
|
||||
http-proxy: "npm:^1.18.1"
|
||||
is-glob: "npm:^4.0.3"
|
||||
is-plain-object: "npm:^5.0.0"
|
||||
micromatch: "npm:^4.0.8"
|
||||
checksum: 10/83c1956be6451a5f4a2f3c7b3d84085dbd47e1efb5bb684c1ed668a6606c18c7c07be823b0dbba1326955b64cf88de2672492940b0b48d140215fbdb06105c9a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"http-proxy@npm:^1.18.1":
|
||||
version: 1.18.1
|
||||
resolution: "http-proxy@npm:1.18.1"
|
||||
dependencies:
|
||||
eventemitter3: "npm:^4.0.0"
|
||||
follow-redirects: "npm:^1.0.0"
|
||||
requires-port: "npm:^1.0.0"
|
||||
checksum: 10/2489e98aba70adbfd8b9d41ed1ff43528be4598c88616c558b109a09eaffe4bb35e551b6c75ac42ed7d948bb7530a22a2be6ef4f0cecacb5927be139f4274594
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"https-proxy-agent@npm:^7.0.1":
|
||||
version: 7.0.6
|
||||
resolution: "https-proxy-agent@npm:7.0.6"
|
||||
@@ -1865,7 +1916,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"is-glob@npm:^4.0.1, is-glob@npm:~4.0.1":
|
||||
"is-glob@npm:^4.0.1, is-glob@npm:^4.0.3, is-glob@npm:~4.0.1":
|
||||
version: 4.0.3
|
||||
resolution: "is-glob@npm:4.0.3"
|
||||
dependencies:
|
||||
@@ -1881,6 +1932,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"is-plain-object@npm:^5.0.0":
|
||||
version: 5.0.0
|
||||
resolution: "is-plain-object@npm:5.0.0"
|
||||
checksum: 10/e32d27061eef62c0847d303125440a38660517e586f2f3db7c9d179ae5b6674ab0f469d519b2e25c147a1a3bc87156d0d5f4d8821e0ce4a9ee7fe1fcf11ce45c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"is-promise@npm:^4.0.0":
|
||||
version: 4.0.0
|
||||
resolution: "is-promise@npm:4.0.0"
|
||||
@@ -2842,6 +2900,7 @@ __metadata:
|
||||
cors: "npm:*"
|
||||
dotenv: "npm:*"
|
||||
express: "npm:*"
|
||||
http-proxy-middleware: "npm:^3.0.5"
|
||||
prettier: "npm:*"
|
||||
sim-shared: "sim-shared:*"
|
||||
supertest: "npm:*"
|
||||
@@ -2856,7 +2915,7 @@ __metadata:
|
||||
version: 0.0.0-use.local
|
||||
resolution: "sim-eventos@workspace:."
|
||||
dependencies:
|
||||
"@sf-alvar/db-migrate": "npm:1.0.3"
|
||||
"@sf-alvar/db-migrate": "npm:1.0.6"
|
||||
"@tsconfig/node22": "npm:^22.0.5"
|
||||
"@types/amqplib": "npm:^0.10.8"
|
||||
"@types/cors": "npm:^2.8.19"
|
||||
|
||||
Reference in New Issue
Block a user