4 Commits

Author SHA1 Message Date
7c2e86779a Pausa 2026-04-07 10:08:21 +02:00
1780cf1743 Pausa 2026-04-07 10:06:45 +02:00
10104c202f Merge branch 'main' into alarmas-objenious 2026-04-07 09:05:39 +02:00
ef8222ae30 Tipos y plantill 2026-03-24 13:25:03 +01:00
83 changed files with 474 additions and 2089 deletions

5
.env
View File

@@ -20,13 +20,10 @@ POSTGRES_DB=postgres
POSTGRES_DATABASE=postgres POSTGRES_DATABASE=postgres
POSTGRES_PORT=5433 POSTGRES_PORT=5433
POSTGRES_USER=postgres POSTGRES_USER=postgres
POSTGRES_PASSWORD='1234' POSTGRES_PASSWORD=1234
# Para el postgres local para generar el script de resultado de migraciones # Para el postgres local para generar el script de resultado de migraciones
PGHOST=localhost PGHOST=localhost
PGUSER=alvar PGUSER=alvar
PGPASSWORD=alvar PGPASSWORD=alvar
PGPORT=5433 PGPORT=5433
# Proxy
CONNECTIONS_URL=https://sim-connections.savefamilygps.net

View File

@@ -1,32 +0,0 @@
/**
* Para la tarea WEBINT-328-Pausas-cacelaciones.
* Almacena las pausas/cancelaciones que no se han podido hacer porque la linea esta en
* "Test"
*/
DO $$ BEGIN
CREATE TYPE SUSPENDTERMINATE AS ENUM ('suspend','terminate');
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
CREATE TABLE IF NOT EXISTS pause_cancel_tasks (
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
iccid TEXT NOT NULL,
operation_type SUSPENDTERMINATE,
last_checked TIMESTAMPTZ, -- Última vez que se ha comprobado que no esté en test
activation_date TIMESTAMPTZ, -- Fecha de activacion para comprobar si ha pasdo un mes
next_check TIMESTAMPTZ, -- Si se ha comprobado se asignará la siguiente fecha de revision
completed_date TIMESTAMPTZ, -- Cuando se ha completado, para bien o mal.
error TEXT,
action_data JSONB -- datos de la operacion original.
);
-- Indice de las tareas que no han terminado
CREATE INDEX idx_pause_cancel_tasks_pending
ON pause_cancel_tasks (next_check)
WHERE completed_date IS NULL;

View File

@@ -8,8 +8,6 @@ RUN corepack enable
COPY ./dist/packages ./packages COPY ./dist/packages ./packages
COPY ./.yarnrc.yml ./ COPY ./.yarnrc.yml ./
COPY ./docs ./docs COPY ./docs ./docs
# Para las migraciones
COPY ./deployment ./deployment
COPY ./package.json ./ COPY ./package.json ./

View File

@@ -1,6 +1,4 @@
#!/bin/sh #!/bin/sh
cd /home cd /home
cd /home/node/app cd /home/node/app && yarn start
yarn migrate
yarn start

View File

@@ -69,6 +69,7 @@ pipeline {
cleanRemote: false, cleanRemote: false,
remoteDirectory: "$APP_REMOTE_PATH", remoteDirectory: "$APP_REMOTE_PATH",
sourceFiles: "deployment/database/**/*", sourceFiles: "deployment/database/**/*",
removePrefix: "deployment",
), ),
sshTransfer( sshTransfer(
cleanRemote: false, cleanRemote: false,

View File

@@ -11,7 +11,7 @@ post {
} }
body:form-urlencoded { body:form-urlencoded {
iccid: 8933201125065160380 iccid: 8933201125068886692
offer: SAVEFAMILY1 offer: SAVEFAMILY1
} }

View File

@@ -11,7 +11,7 @@ post {
} }
body:form-urlencoded { body:form-urlencoded {
iccid: 8933201125068890074 iccid: 8933201125068886692
} }
settings { settings {

View File

@@ -15,7 +15,7 @@ params:query {
} }
body:form-urlencoded { body:form-urlencoded {
iccid: 8933201125068886700 iccid: 8933201125065160414
} }
settings { settings {

View File

@@ -1,21 +0,0 @@
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
}

View File

@@ -1,4 +1,3 @@
vars { vars {
baseurl: http://localhost:3000 baseurl: http://localhost:3000
} }
color: #2E8A54

View File

@@ -1,4 +1,3 @@
vars { vars {
baseurl: https://sf-sims.savefamilygps.net baseurl: https://sf-sims.savefamilygps.net
} }
color: #CE4F3B

View File

@@ -1,4 +0,0 @@
vars {
baseurl: http://sim-connections.savefamilygps.net
}
color: #C77A0F

View File

@@ -1,20 +0,0 @@
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
}

View File

@@ -5,13 +5,13 @@ meta {
} }
get { get {
url: {{actionsUrl}}/massActions?massActionId=5363116 url: {{actionsUrl}}/massActions?massActionId=5192767
body: formUrlEncoded body: formUrlEncoded
auth: bearer auth: bearer
} }
params:query { params:query {
massActionId: 5363116 massActionId: 5192767
~identifier.identifierType: ICCID ~identifier.identifierType: ICCID
~identifier.identifiers: 8933201125065160463,8933201125065160422 ~identifier.identifiers: 8933201125065160463,8933201125065160422
} }

View File

@@ -19,7 +19,7 @@
"migrate": "yarn db-migrate -e .env -m deployment/database/migrations -t 99.0.0" "migrate": "yarn db-migrate -e .env -m deployment/database/migrations -t 99.0.0"
}, },
"dependencies": { "dependencies": {
"@sf-alvar/db-migrate": "1.0.6", "@sf-alvar/db-migrate": "1.0.3",
"@tsconfig/node22": "^22.0.5", "@tsconfig/node22": "^22.0.5",
"amqp-connection-manager": "^5.0.0", "amqp-connection-manager": "^5.0.0",
"amqplib": "^0.10.9", "amqplib": "^0.10.9",

View File

@@ -0,0 +1 @@
PORT=3001

View File

@@ -0,0 +1,18 @@
import { Request, Response } from "express"
export class AlarmsController {
constructor() {
// Alarm usecases
}
public recibe() {
return (req: Request, res: Response) => {
const body = req.body()
const header = req.headers
}
}
}

View File

@@ -0,0 +1,14 @@
import { PgClient } from "sim-shared/infrastructure/PgClient.js";
export class AlarmUsecases {
constructor(
private alarmRepository: any
) {
}
public saveAlarm() {
}
}

View File

@@ -1,12 +1,14 @@
import { loadEnvFile } from "node:process"; import { loadEnvFile } from "node:process";
import path from "node:path"; loadEnvFile("../../.env")
loadEnvFile(path.join("../../.env")) // Global
export const env = { export const env = {
PORT: Number(process.env.PORT),
ENVIRONMENT: process.env.ENVIORMENT, ENVIRONMENT: process.env.ENVIORMENT,
API_PORT: parseInt(process.env.API_PORT ?? "3000"), 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 ?? "localhost"), RABBITMQ_HOST: String(process.env.RABBITMQ_HOST ?? "localhost"),
RABBITMQ_USER: String(process.env.RABBITMQ_USER ?? "test"), RABBITMQ_USER: String(process.env.RABBITMQ_USER ?? "test"),
RABBITMQ_PASSWORD: String(process.env.RABBITMQ_PASSWORD ?? "test"), RABBITMQ_PASSWORD: String(process.env.RABBITMQ_PASSWORD ?? "test"),
@@ -18,3 +20,4 @@ export const env = {
RABBITMQ_RETRY_INTERVAL: process.env.RABBITMQ_INTERVAL, RABBITMQ_RETRY_INTERVAL: process.env.RABBITMQ_INTERVAL,
RABBITMQ_VHOST: String(process.env.RABBITMQ_VHOST), RABBITMQ_VHOST: String(process.env.RABBITMQ_VHOST),
}; };

View File

@@ -0,0 +1,18 @@
import { Pool } from 'pg';
import { PgClient } from 'sim-shared/infrastructure/PgClient.js'
import { env } from './env/index.js';
// Configuracion de la conexion a la BDD, deberia ser la
// Misma para todos los servicios pero hasta que se unifique todo
// se hace una por servicio.
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
})

View File

@@ -0,0 +1,62 @@
export type ObjeniousAlarmType =
"PLMN_CHANGE" |
"COUNTRY_CHANGE" |
"OVER_CONSUMPTION_VOLUME" |
"UNDER_CONSUMPTION_VOLUME" |
"IMEI_CHANGE" |
"OVER_CONSUMPTION_VOLUME" |
string
export type ObjeniousAlarm = {
alarmId: Number,
emissionDate: Date //"07/02/2019 14:44:00"
alarmType: ObjeniousAlarmType,
lineInfos: {
msisdn: string,
iccid: string
},
change?: ObjeniousChangeTypes,
usage?: ObjeniousUsageTypes
}
export type ObjeniousChangeTypes = ChangePayload | ImeiChangePayload;
export type ObjeniousUsageTypes = UsageAlarmPayload | OverConsumptionPayload;
/**
* PLMN_CHANGE
* COUNTRY_CHANGE
* STATUS_CHANGE
* */
export type ChangePayload = {
previous: string,
current: string
}
/**
* IMEI_CHANGE
*/
export type ImeiChangePayload = {
previous: string,//"42947662490276",
current: string, //"74845559130697",
previousManufacturer: string, //"u-blox AG",
currentManufacturer: string, //"SierraWireless"
}
/**
* OVER_CONSUMPTION_VOLUME
* UNDER_CONSUMPTION_VOLUME
*/
export type UsageAlarmPayload = {
type: string,
threshold: string,
trigerValue: string
}
/**
* OVER_CONSUMPTION_VOLUME
*/
export type OverConsumptionPayload = {
type: string, //"data-sms-smsIN-voiceOUT",
threshold: string, // "500-200-100-10",
triggerValue: string, // "600-300-101-15"
}

View File

@@ -1,19 +1,22 @@
import express from "express" import express from "express"
import cors from 'cors'; import cors from 'cors';
import path from 'path'; import { env } from "#config/env/index.js"
import { env } from "packages/sim-shared/config/env/index.js" import { AlarmsController } from "aplication/Alarms.controller";
const PORT = env.API_PORT const PORT = env.PORT
const HOSTNAME = "0.0.0.0" const HOSTNAME = "0.0.0.0"
const app = express() const app = express()
// Instancias
const alarmsController = new AlarmsController();
// Middleware // Middleware
app.use(cors()); app.use(cors());
app.use(express.json()); app.use(express.json());
app.use(express.urlencoded({ extended: true })); app.use(express.urlencoded({ extended: true }));
app.use("/docs", express.static(path.join(process.cwd(), '../../docs'))) app.use("/fr/alarms", alarmsController.recibe())
app.get("/health", (req, res) => { app.get("/health", (req, res) => {
res.status(200).json({ status: "ok" }) res.status(200).json({ status: "ok" })
@@ -22,4 +25,5 @@ app.get("/health", (req, res) => {
app.listen(PORT, HOSTNAME, () => { app.listen(PORT, HOSTNAME, () => {
console.log("[o] Servidor iniciado en el puerto %d", PORT) console.log("[o] Servidor iniciado en el puerto %d", PORT)
}) })
export default {} export default {}

View File

@@ -0,0 +1,10 @@
import { PgClient } from "sim-shared/infrastructure/PgClient.js";
export class AlarmsRepository {
constructor(private pgClient: PgClient) {
}
public createAlarm() {
}
}

View File

@@ -1,72 +1,76 @@
{ {
"name": "sim-visualizador-tareas-back", "name": "sim-alarmas-objenious",
"version": "1.0.0", "version": "1.0.0",
"type": "module", "description": "Recibe las alarmas de los webhook de objenious",
"description": "", "main": "index.ts",
"main": "index.ts", "scripts": {
"imports": { "test": "node --import tsx --test ./**/*.test.ts",
"#config/*.js": { "build": "tsc --build && tsc-alias -p tsconfig.json && cp package.json ../../dist/packages/sim-alarmas-objenious/",
"types": "./config/*.ts", "dev": "tsx watch index.ts",
"default": "./config/*.js" "start": "node ../../dist/packages/sim-alarmas-objenious/index.js"
}, },
"#config/*": { "author": "",
"types": "./config/*.ts", "license": "ISC",
"default": "./config/*.js" "packageManager": "yarn@4.12.0",
}, "imports": {
"#adapters/*.js": { "#config/*.js": {
"types": "./infrastructure/*.ts", "types": "./config/*.ts",
"default": "./infrastructure/*.js" "default": "./config/*.js"
}, },
"#adapters/*": { "#config/*": {
"types": "./infrastructure/*.ts", "types": "./config/*.ts",
"default": "./infrastructure/*.js" "default": "./config/*.js"
}, },
"#domain/*.js": { "#adapters/*.js": {
"types": "./domain/*.ts", "types": "./adapters/*.ts",
"default": "./domain/*.js" "default": "./adapters/*.js"
}, },
"#domain/*": { "#adapters/*": {
"types": "./domain/*.ts", "types": "./adapters/*.ts",
"default": "./domain/*.js" "default": "./adapters/*.js"
}, },
"#ports/*.js": { "#domain/*.js": {
"types": "./ports/*.ts", "types": "./domain/*.ts",
"default": "./ports/*.js" "default": "./domain/*.js"
}, },
"#ports/*": { "#domain/*": {
"types": "./ports/*.ts", "types": "./domain/*.ts",
"default": "./ports/*.js" "default": "./domain/*.js"
} },
}, "#ports/*.js": {
"scripts": { "types": "./ports/*.ts",
"test": "node --import tsx --test ./**/*.test.ts", "default": "./ports/*.js"
"build": "tsc --build && tsc-alias -p tsconfig.json && cp package.json ../../dist/packages/sim-visualizador-tareas-back/", },
"dev": "tsx watch index.ts", "#ports/*": {
"start": "node ../../dist/packages/sim-visualizador-tareas-back/index.js" "types": "./ports/*.ts",
}, "default": "./ports/*.js"
"author": "", },
"license": "ISC", "#tests/*.js": {
"packageManager": "yarn@4.12.0", "types": "./__tests__/*.ts",
"dependencies": { "default": "./__tests__/*.js"
"@tsconfig/node22": "*", },
"amqplib": "^0.10.9", "#tests/*": {
"axios": "*", "types": "./__tests__/*.ts",
"cors": "*", "default": "./__tests__/*.js"
"dotenv": "*", }
"express": "*", },
"sim-shared": "sim-shared:*", "dependencies": {
"typescript": "*" "@tsconfig/node22": "*",
}, "amqplib": "^0.10.9",
"devDependencies": { "cors": "*",
"@types/amqplib": "^0.10.8", "dotenv": "*",
"@types/cors": "*", "express": "*",
"@types/express": "*", "typescript": "*"
"@types/node": "*", },
"@types/supertest": "*", "devDependencies": {
"prettier": "*", "@types/amqplib": "^0.10.8",
"supertest": "*", "@types/cors": "*",
"tsc-alias": "^1.8.16", "@types/express": "*",
"tsx": "*", "@types/node": "*",
"vitest": "*" "@types/supertest": "*",
} "prettier": "*",
} "supertest": "*",
"tsx": "*",
"vitest": "*"
}
}

View File

@@ -1,17 +1,17 @@
{ {
"extends": "../../tsconfig.json", "extends": "../../tsconfig.json",
"compilerOptions": { "compilerOptions": {
"outDir": "../../dist", "outDir": "../../dist",
"rootDir": "../../", "baseUrl": ".",
}, },
"exclude": [ "exclude": [
"node_modules" "node_modules"
], ],
"include": [ "include": [
"**/*.ts", "**/*.ts",
"../../packages/sim-shared/**/*.ts" "src/**/*.d.ts"
], ],
"files": [ "files": [
"config/env/index.ts" "index.ts"
] ]
} }

View File

@@ -1,3 +1,5 @@
NOS_BASE_URL=localhost PORT=3000
RABBITMQ_USER=guest
RABBITMQ_PASSWORD=guest
ENVIORMENT=development ENVIORMENT=development

View File

@@ -1,16 +1,5 @@
import { loadEnvFile } from "node:process"; import { loadEnvFile } from "node:process";
import path from "node:path"; loadEnvFile("../../.env")
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 = { export const env = {
ENVIRONMENT: process.env.ENVIORMENT, ENVIRONMENT: process.env.ENVIORMENT,
@@ -29,8 +18,5 @@ export const env = {
RABBITMQ_SECURE: process.env.RABBITMQ_SECURE, RABBITMQ_SECURE: process.env.RABBITMQ_SECURE,
RABBITMQ_RETRY_INTERVAL: process.env.RABBITMQ_INTERVAL, RABBITMQ_RETRY_INTERVAL: process.env.RABBITMQ_INTERVAL,
RABBITMQ_VHOST: String(process.env.RABBITMQ_VHOST), RABBITMQ_VHOST: String(process.env.RABBITMQ_VHOST),
// ESPECIFICO NOS
NOS_BASE_URL: String(process.env.NOS_BASE_URL)
}; };

View File

@@ -1,69 +0,0 @@
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
}

View File

@@ -1,5 +1,5 @@
import { RabbitMQEventBus, RMQConnectionParams } from "sim-shared/infrastructure/RabbitMQEventBus.js" import { RabbitMQEventBus, RMQConnectionParams } from "sim-shared/infrastructure/RabbitMQEventBus.js"
import { env } from "./env/index.js" import { env } from "./env"
const rmqUser = env.RABBITMQ_USER const rmqUser = env.RABBITMQ_USER
const rmqPass = env.RABBITMQ_PASSWORD const rmqPass = env.RABBITMQ_PASSWORD
@@ -22,5 +22,18 @@ export const rabbitmqEventBus = new RabbitMQEventBus({
}) })
export async function startRMQClient() { export async function startRMQClient() {
await rabbitmqEventBus.connect() 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
} }

View File

@@ -1,5 +1,5 @@
import { startRMQClient } from "#config/eventBus.config.js" import { startRMQClient } from "#config/eventBusConfig"
import { SimNosController } from "./aplication/SimNOS.controller.js" import { SimNosController } from "./aplication/SimNOS.controller.js"
async function startWorker() { async function startWorker() {

View File

@@ -7,8 +7,7 @@
"test": "echo \"Error: no test specified\" && exit 1", "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/", "build": "yarn tsc --project tsconfig.json && yarn tsc-alias && cp package.json ../../dist/packages/sim-consumidor-nos/",
"esbuild": "esbuild index.ts --platform=node", "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": "", "author": "",
"license": "ISC", "license": "ISC",

View File

@@ -1,16 +1,16 @@
import { test, describe } from "vitest" import { test, describe } from "vitest"
import { jwtService } from "../config/jwtService.config.js" import { JWTService } from "./JWT.service.js"
describe("Tokens Objenious", () => { describe("Tokens Objenious", () => {
const jwt = jwtService const jwtService = new JWTService()
test("Solicicitud normal de auth", async () => { test("Solicicitud normal de auth", async () => {
const token = await jwt.getAccessToken() const token = await jwtService.getAccessToken()
console.log("acceso objenious", token) console.log("acceso objenious", token)
}), }),
test("Solicicitud de refresh de auth", async () => { test("Solicicitud de refresh de auth", async () => {
const token = await jwt.tryRefreshToken() const token = await jwtService.tryRefreshToken()
console.log("acceso refresh objenious", token) console.log("acceso refresh objenious", token)
}) })
}) })

View File

@@ -4,24 +4,24 @@
* el cliente HTTP * el cliente HTTP
*/ */
import { env } from "#config/env/index.js";
import fs from "fs" import fs from "fs"
import { import {
JWTToken, JWTToken,
JWTHeader, JWTHeader,
IJWTService, IJWTService
JWTPayload
} from "sim-shared/domain/JWT.js" } from "sim-shared/domain/JWT.js"
import axios, { AxiosError } from "axios"; import axios, { AxiosError } from "axios";
export type GrantAccessRequestBody = { type GrantAccessRequestBody = {
grant_type: string, grant_type: string,
client_id: string, client_id: string,
client_assertion_type: string, client_assertion_type: string,
client_assertion: string client_assertion: string
} }
export type TokensRequestResponse = { type TokensRequestResponse = {
"access_token": string, "access_token": string,
"expires_in": number, "expires_in": number,
"refresh_token": string "refresh_token": string
@@ -32,6 +32,41 @@ export type TokensRequestResponse = {
"scope": string "scope": string
} }
const PRIVATE_KEY_PATH = env.OBJ_PEM_PATH
const GET_TOKEN_URL = "https://idp.docapost.io/auth/realms/GETWAY/protocol/openid-connect/token"
const REFRESH_TOKEN_URL = GET_TOKEN_URL
const DEFAULT_BODY: GrantAccessRequestBody = {
grant_type: "client_credentials",
client_id: env.OBJ_CLIENT_ID,
client_assertion_type: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
client_assertion: env.OBJ_CLI_ASSERTION
}
const REFRESH_BODY = {
...DEFAULT_BODY,
grant_type: "refresh_token",
}
const DEFAULT_HEADERS = {
"content-type": "application/x-www-form-urlencoded"
}
function addIATHeaders(authHeaders: Object) {
const headers = <JWTHeader>{
...authHeaders,
sub: env.OBJ_CLIENT_ID,
iss: env.OBJ_CLIENT_ID,
aud: GET_TOKEN_URL,
jti: Date.now().toString(),
iat: Math.floor(Date.now() / 1000),
exp: Math.floor(Date.now() / 1000) + 5 * 60,
}
return headers
}
export type ObjeniousTokenBody = any export type ObjeniousTokenBody = any
/** /**
@@ -47,54 +82,27 @@ export class JWTService implements IJWTService<ObjeniousTokenBody> {
public authToken: JWTToken<ObjeniousTokenBody> | undefined; public authToken: JWTToken<ObjeniousTokenBody> | undefined;
private refreshToken?: JWTToken<ObjeniousTokenBody> | undefined; private refreshToken?: JWTToken<ObjeniousTokenBody> | undefined;
// http constructor(args?: {
private transformHeaders?: (_: Object) => JWTHeader;
private defaultHttpHeaders: Record<string, string>;
private defaultBody: Record<string, string>;
// jwt
private defaultJWTHeaders: JWTHeader;
private defaultJWTPayload: JWTPayload<any>;
private privateKeyPath: string;
private tokenUrl: string;
private refreshTokenUrl: string;
constructor(args: {
token?: string // si se partiese de un token existente, token?: string // si se partiese de un token existente,
refreshToken?: string, refreshToken?: string
transformJWTHeaders?: (_: Object) => JWTHeader,
defaultHeaders: Record<string, string>,
defaultBody: Record<string, string>,
defaultJWTHeaders: JWTHeader,
defaultJWTPayload: JWTPayload<any>,
privateKeyPath: string,
tokenUrl: string,
refreshTokenUrl: string
}) { }) {
if (args?.token != undefined) this.authToken = new JWTToken(args.token) if (args?.token != undefined) this.authToken = new JWTToken(args.token)
if (args?.refreshToken != undefined) this.refreshToken = new JWTToken(args.refreshToken) if (args?.refreshToken != undefined) this.refreshToken = new JWTToken(args.refreshToken)
if (args?.transformJWTHeaders != undefined) this.transformHeaders = args.transformJWTHeaders
this.defaultHttpHeaders = args.defaultHeaders;
this.defaultBody = args.defaultBody;
this.defaultJWTHeaders = args.defaultJWTHeaders;
this.defaultJWTPayload = args.defaultJWTPayload;
this.privateKeyPath = args.privateKeyPath;
this.tokenUrl = args.tokenUrl;
this.refreshTokenUrl = args.refreshTokenUrl;
} }
private buildJwtBody() { private buildJwtBody() {
const jwtHeaders = this.defaultJWTHeaders const jwtHeaders = {
alg: "RS256",
const jwtData = (this.transformHeaders) ? typ: "JWT",
this.transformHeaders(this.defaultJWTPayload) : kid: env.OBJ_KID
this.defaultJWTPayload; }
const jwtData = addIATHeaders({
const key = fs.readFileSync(this.privateKeyPath, "utf8") sub: env.OBJ_CLIENT_ID,
iss: env.OBJ_CLIENT_ID,
aud: "https://idp.docapost.io/auth/realms/GETWAY",
jti: Date.now().toString(),
})
const key = fs.readFileSync(PRIVATE_KEY_PATH, "utf8")
const token = JWTToken.fromParts({ const token = JWTToken.fromParts({
header: jwtHeaders, header: jwtHeaders,
payload: jwtData, payload: jwtData,
@@ -108,16 +116,14 @@ export class JWTService implements IJWTService<ObjeniousTokenBody> {
public async getNewAuthToken() { public async getNewAuthToken() {
const bodyWithtoken = { const bodyWithtoken = {
...this.defaultBody, ...DEFAULT_BODY,
client_assertion: this.buildJwtBody() client_assertion: this.buildJwtBody()
} }
const headers = (this.transformHeaders) ? this.transformHeaders(this.defaultHttpHeaders) : this.defaultHttpHeaders; const req = axios.post(GET_TOKEN_URL,
const req = axios.post(this.tokenUrl,
bodyWithtoken, bodyWithtoken,
{ {
headers: headers headers: addIATHeaders(DEFAULT_HEADERS)
} }
) )
@@ -160,21 +166,16 @@ export class JWTService implements IJWTService<ObjeniousTokenBody> {
if (this.refreshToken == undefined) throw new Error("El refreshToken no está definido") if (this.refreshToken == undefined) throw new Error("El refreshToken no está definido")
if (this.refreshToken.isExpired()) throw new Error("El refreshToken ha expirado") if (this.refreshToken.isExpired()) throw new Error("El refreshToken ha expirado")
const refreshBody = {
...this.defaultBody,
grant_type: "refresh_token",
}
const body = { const body = {
...refreshBody, ...REFRESH_BODY,
client_assertion: this.buildJwtBody(), client_assertion: this.buildJwtBody(),
refresh_token: this.refreshToken.rawToken refresh_token: this.refreshToken.rawToken
} }
const req = axios.post(this.refreshTokenUrl, const req = axios.post(REFRESH_TOKEN_URL,
body, body,
{ {
headers: this.defaultHttpHeaders headers: DEFAULT_HEADERS
} }
) )

View File

@@ -1,118 +0,0 @@
import { describe, it, beforeEach, mock, after } from "node:test";
import assert from "node:assert";
import { SimController } from "./Sim.controller.js";
import { EventBus } from "sim-shared/domain/EventBus.port.js";
import { SimUseCases } from "./Sim.usecases.js";
import { ConsumeMessage } from "amqplib";
import { postgrClient, pgPool } from "#config/postgreConfig.js";
import { httpInstance } from "#config/httpClient.config.js";
import { OrderRepository } from "sim-shared/infrastructure/OrderRepository.js";
import { PauseCancelTaskRepository } from "#adapters/PauseCancelTaskRepository.js";
import { ObjeniousOperationsRepository } from "sim-shared/infrastructure/ObjeniousOperationRepository.js";
import { ActionData } from "#domain/DTOs/objeniousapi.js";
describe("SimController Integration Tests (Real UseCases)", () => {
let eventBusMock: any;
let controller: SimController;
let useCases: SimUseCases;
beforeEach(() => {
// Mock ONLY the event bus as requested
eventBusMock = {
publish: mock.fn(),
addSubscribers: mock.fn(),
consume: mock.fn(),
ack: mock.fn(async () => { }),
nack: mock.fn(async () => { }),
};
const operationRepository = new ObjeniousOperationsRepository(
httpInstance,
postgrClient,
);
const orderRepository = new OrderRepository(postgrClient);
const pauseRepository = new PauseCancelTaskRepository(postgrClient);
useCases = new SimUseCases({
httpClient: httpInstance,
operationRepository: operationRepository,
orderRepository: orderRepository,
pauseRepository: pauseRepository
});
// @ts-expect-error
useCases.findActivationDate = async (data: ActionData) => new Date()
controller = new SimController(eventBusMock as unknown as EventBus, useCases);
});
const createMockMsg = (payload: any): ConsumeMessage => {
return {
content: Buffer.from(JSON.stringify(payload)),
fields: {},
properties: {
headers: {
message_id: "test-correlation-id"
}
},
} as unknown as ConsumeMessage;
};
after(async () => {
await pgPool.end();
});
describe("suspend", () => {
it("should call stage_suspend and interact with DB and EventBus", async () => {
const iccid = "test-iccid-suspend-" + Date.now();
const msg = createMockMsg({
key: "sim.test.pause",
payload: {
iccid: iccid
},
headers: {
message_id: "correlation-suspend-" + iccid
}
});
const handler = controller.suspend();
await handler(msg);
// Verify that it reached the stage_suspend logic (which adds to pauseRepository)
// We can query the DB or check if ACK was called
assert.strictEqual(eventBusMock.ack.mock.callCount(), 1, "Message should be ACKed on success");
assert.strictEqual(eventBusMock.nack.mock.callCount(), 0, "Message should not be NACKed");
});
});
describe("terminate", () => {
it("should call stage_terminate and interact with DB and EventBus", async () => {
const iccid = "test-iccid-terminate-" + Date.now();
const msg = createMockMsg({
key: "sim.test.pause",
payload: {
iccid: iccid
},
headers: {
message_id: "correlation-terminate-" + iccid
}
});
const handler = controller.terminate();
await handler(msg);
assert.strictEqual(eventBusMock.ack.mock.callCount(), 1, "Message should be ACKed on success");
assert.strictEqual(eventBusMock.nack.mock.callCount(), 0, "Message should not be NACKed");
});
});
describe("Error Handling", () => {
it("should nack if message is invalid", async () => {
const msg = {
content: Buffer.from("invalid json"),
fields: {},
properties: {},
} as unknown as ConsumeMessage;
const handler = controller.suspend();
await assert.rejects(handler(msg), "Error de suspension consumiendo el mensaje no es valido");
});
});
});

View File

@@ -3,7 +3,7 @@ import { ConsumeMessage } from "amqplib";
import { SimUseCases } from "./Sim.usecases.js"; import { SimUseCases } from "./Sim.usecases.js";
import { SimEvents } from "sim-shared/domain/SimEvents.js"; import { SimEvents } from "sim-shared/domain/SimEvents.js";
import { Result } from "sim-shared/domain/Result.js"; import { Result } from "sim-shared/domain/Result.js";
import { ActionData } from "#domain/DTOs/objeniousapi.js"; import { env } from "#config/env/index.js";
/** /**
* La clase usa generadores de funciones para mantener el contexto * La clase usa generadores de funciones para mantener el contexto
@@ -37,7 +37,6 @@ export class SimController {
} catch (error) { } catch (error) {
console.error('Error al decodificar JSON:', error); console.error('Error al decodificar JSON:', error);
console.error(Buffer.from(msg.content).toString(("utf8")))
// Aquí podrías decidir devolver el string crudo o null // Aquí podrías decidir devolver el string crudo o null
return undefined; return undefined;
} }
@@ -158,9 +157,6 @@ export class SimController {
} }
} }
/**
* Lo mismo que pause
*/
public suspend() { public suspend() {
return async (msg: ConsumeMessage) => { return async (msg: ConsumeMessage) => {
let msgData; let msgData;
@@ -175,18 +171,14 @@ export class SimController {
} }
const iccid = msgData.payload.iccid const iccid = msgData.payload.iccid
const suspendData: ActionData = { const res = await this.tryUseCase(msg, this.useCases.suspend({
correlation_id: msgData.headers?.message_id, correlation_id: msgData.headers?.message_id,
dueDate: this.genDueDate(2 * 60).toISOString(), dueDate: this.genDueDate(2 * 60).toISOString(),
identifier: { identifier: {
identifierType: "ICCID", identifierType: "ICCID",
identifiers: [iccid] // Por algún motivo solo he puesto un iccd por identifier identifiers: [iccid]
} }
} }))
const useCaseRes = await this.tryUseCase(msg, this.useCases.stage_suspend(suspendData))
/*
const res = await this.tryUseCase(msg, this.useCases.suspend(actionData))
*/
} }
} }
@@ -203,20 +195,16 @@ export class SimController {
if (msgData == undefined) { if (msgData == undefined) {
return Promise.reject("Mensaje invalido") return Promise.reject("Mensaje invalido")
} }
const iccid = msgData.payload.iccid const iccid = msgData.payload.iccid
const terminateActionData: ActionData = { console.log("Mensaje procesado", msgData)
const res = await this.tryUseCase(msg, this.useCases.terminate({
correlation_id: msgData.headers?.message_id, correlation_id: msgData.headers?.message_id,
dueDate: this.genDueDate(2 * 60).toISOString(), dueDate: this.genDueDate(2 * 60).toISOString(),
identifier: { identifier: {
identifierType: "ICCID", identifierType: "ICCID",
identifiers: [iccid] identifiers: [iccid]
} }
} }))
//const res = await this.tryUseCase(msg, this.useCases.terminate(terminateActionData))
const res = await this.tryUseCase(msg, this.useCases.stage_terminate(terminateActionData))
} }
} }

View File

@@ -19,7 +19,7 @@ export class SimRouter {
["activate", this.simController.activate()], ["activate", this.simController.activate()],
["pause", this.simController.suspend()], ["pause", this.simController.suspend()],
["cancel", this.simController.terminate()], ["cancel", this.simController.terminate()],
["reactivate", this.simController.reActivate()], ["reActivate", this.simController.reActivate()],
["preActivate", this.simController.preActivate()] ["preActivate", this.simController.preActivate()]
]); ]);
} }

View File

@@ -5,8 +5,6 @@ import { Result } from "sim-shared/domain/Result.js"
import { ObjeniousOperation, IOperationsRepository as OperationsRepositoryPort } from "sim-shared/domain/operationsRepository.port.js" import { ObjeniousOperation, IOperationsRepository as OperationsRepositoryPort } from "sim-shared/domain/operationsRepository.port.js"
import assert from "node:assert" import assert from "node:assert"
import { OrderRepository } from "sim-shared/infrastructure/OrderRepository.js" import { OrderRepository } from "sim-shared/infrastructure/OrderRepository.js"
import { CreatePauseCancelTaskDTO, PauseCancelTaskRepository } from "#adapters/PauseCancelTaskRepository.js"
import { ObjeniousOperationsRepository } from "sim-shared/infrastructure/ObjeniousOperationRepository.js"
// TODO: // TODO:
// - Pasar a un archivo de DTOs // - Pasar a un archivo de DTOs
@@ -14,24 +12,21 @@ import { ObjeniousOperationsRepository } from "sim-shared/infrastructure/Objenio
export class SimUseCases { export class SimUseCases {
private readonly httpClient: HttpClient private readonly httpClient: HttpClient
private readonly objeniousRepository: ObjeniousOperationsRepository private readonly operationRepository: OperationsRepositoryPort
private readonly orderRepository: OrderRepository private readonly orderRepository: OrderRepository
private readonly pauseRepository: PauseCancelTaskRepository
constructor(args: { constructor(args: {
httpClient: HttpClient, httpClient: HttpClient,
operationRepository: ObjeniousOperationsRepository, operationRepository: OperationsRepositoryPort,
orderRepository: OrderRepository, orderRepository: OrderRepository
pauseRepository: PauseCancelTaskRepository
}) { }) {
this.httpClient = args.httpClient this.httpClient = args.httpClient
this.objeniousRepository = args.operationRepository this.operationRepository = args.operationRepository
this.orderRepository = args.orderRepository this.orderRepository = args.orderRepository
this.pauseRepository = args.pauseRepository
} }
private async logOperation(data: ObjeniousOperation) { private async logOperation(data: ObjeniousOperation) {
await this.objeniousRepository.createOperation({ await this.operationRepository.createOperation({
...data ...data
}) })
} }
@@ -75,14 +70,11 @@ export class SimUseCases {
operation: args.operation, operation: args.operation,
iccids: String(args.iccid), iccids: String(args.iccid),
status: "noMassID", 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) this.logOperation(operation)
.then().catch(e => console.error("Error login operation", e)) .then().catch(e => console.error(e))
if (args.correlation_id != undefined) { if (args.correlation_id != undefined) {
this.orderRepository.updateOrder({ this.orderRepository.updateOrder({
@@ -97,6 +89,7 @@ export class SimUseCases {
error: undefined, error: undefined,
data: true data: true
} }
} else { } else {
return { return {
error: String(response.status), error: String(response.status),
@@ -116,15 +109,6 @@ export class SimUseCases {
public activate(activationData: ActivationData): () => Promise<Result<string, boolean>> { public activate(activationData: ActivationData): () => Promise<Result<string, boolean>> {
const OPERATION_URL = "/actions/activateLine" const OPERATION_URL = "/actions/activateLine"
return async () => { 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, { const req = this.httpClient.client.post(OPERATION_URL, {
dueDate: activationData.dueDate, dueDate: activationData.dueDate,
identifier: activationData.identifier, identifier: activationData.identifier,
@@ -208,29 +192,16 @@ export class SimUseCases {
} }
} }
public reActivate(reactivateData: ActionData): () => Promise<Result<string, boolean>> { public reActivate(pauseData: ActionData): () => Promise<Result<string, boolean>> {
const OPERATION_URL = "/actions/reactivateLine" const OPERATION_URL = "/actions/reactivateLine"
return async () => { return async () => {
const req = this.httpClient.client.post(OPERATION_URL, { const req = this.httpClient.client.post(OPERATION_URL, {
...reactivateData ...pauseData
}) })
try { try {
const response = await req 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) { if (response.status == 200) {
console.log("[o] Sim solicitud de reactivacion ", response.data) console.log("[o] Sim solicitud de reactivacion ", response.data)
return <Result<string, boolean>>{ return <Result<string, boolean>>{
@@ -246,7 +217,7 @@ export class SimUseCases {
} catch (error) { } catch (error) {
console.error("[x] Error reactivacion", (error as AxiosError).response?.status) console.error("[x] Error reactivacion", (error as AxiosError).response?.status)
return <Result<string, boolean>>{ return <Result<string, boolean>>{
error: "Error reactivando la sim" + reactivateData.identifier, error: "Error reactivando la sim" + pauseData.identifier,
data: undefined data: undefined
} }
} }
@@ -267,164 +238,6 @@ export class SimUseCases {
}) })
} }
/**
* Metodo muy especifico para obtener la fecha e activacion o en su defecto
* la actual para aber cuando se va a completar el periodo de test de una linea
*/
private async findActivationDate(actionData: ActionData) {
const iccid = actionData.identifier.identifiers
const lineData = await this.objeniousRepository.getLinesAPI("ICCID", iccid)
let activationDate = new Date()
// Si no se pueden sacar datos de la linea guardo momentaneamente el error
// pero no se cancela la operacion, el error puede ser de objenious y no nos
// puede afectar
console.log("LineData", lineData.data)
if (lineData.error != undefined) {
console.error(lineData.error)
} else {
const activationDateStr = lineData.data[0].status.activationDate
if (activationDateStr != undefined && activationDateStr != "") {
activationDate = new Date(activationDateStr)
}
}
return activationDate
}
/**
* Paso previo a la suspension para evitar errores cuando el billing es test
*/
public stage_suspend(suspendData: ActionData): () => Promise<Result<string, boolean>> {
return async (): Promise<Result<string, boolean>> => {
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) {
this.orderRepository.updateOrder({
correlation_id: correlation_id,
new_status: "failed"
})
}
}
// 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)
} catch (e) {
return {
error: String(e)
}
}
const newTask: CreatePauseCancelTaskDTO = {
iccid: iccid[0],
activation_date: activationDate,
next_check: undefined, // Que se haga instantaneamente al ser la primera
operation_type: "suspend",
action_data: suspendData
}
const taskCreated = await this.pauseRepository.addTask(newTask)
// Caso que la task no se pueda crear en la BDD
if (taskCreated.error != undefined) {
fail(taskCreated.error)
return {
error: taskCreated.error
}
}
// Caso que se haya creado en la BDD
if (correlation_id != undefined) {
this.orderRepository.updateOrder({
correlation_id: correlation_id,
new_status: "running"
})
}
return {
data: true
}
}
}
/**
* Paso previo a la suspension para evitar errores cuando el billing es test
*/
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: iccid,
activation_date: activationDate,
next_check: undefined, // Que se haga instantaneamente al ser la primera
operation_type: "terminate",
action_data: terminateData
}
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)
if (correlation_id != undefined) {
this.orderRepository.updateOrder({
correlation_id: correlation_id,
new_status: "failed"
})
}
return {
error: taskCreated.error
}
}
// Caso que se haya creado en la BDD
if (correlation_id != undefined) {
this.orderRepository.updateOrder({
correlation_id: correlation_id,
new_status: "running"
})
}
return {
data: true
}
}
}
public terminate(terminationData: ActionData): () => Promise<Result<string, boolean>> { public terminate(terminationData: ActionData): () => Promise<Result<string, boolean>> {
const OPERATION_URL = "/actions/terminateLine" const OPERATION_URL = "/actions/terminateLine"
return this.generateUseCase({ return this.generateUseCase({

View File

@@ -27,8 +27,8 @@ export const rabbitmqEventBus = new RabbitMQEventBus({
async function buildQueues(channel: Channel) { async function buildQueues(channel: Channel) {
const QUEUES = { const QUEUES = {
OBJ: "sim.objenious", OBJ: "sim.objenious",
OBJDLX: "sim.objenious.dlx", DLX: "sim.objenious.dlx",
OBJDEL: "sim.objenious.delayed", DEL: "sim.objenious.delayed"
} }
const EXCHANGES = { const EXCHANGES = {
@@ -45,8 +45,8 @@ async function buildQueues(channel: Channel) {
await channel.assertExchange(EXCHANGES.MAIN, "topic") await channel.assertExchange(EXCHANGES.MAIN, "topic")
await channel.assertQueue(QUEUES.OBJ) await channel.assertQueue(QUEUES.OBJ)
await channel.assertQueue(QUEUES.OBJDLX) await channel.assertQueue(QUEUES.DLX)
await channel.assertQueue(QUEUES.OBJDEL, { await channel.assertQueue(QUEUES.DEL, {
durable: true, durable: true,
arguments: { arguments: {
'x-message-ttl': DELAY, 'x-message-ttl': DELAY,

View File

@@ -1,6 +1,6 @@
import { HttpClient } from "sim-shared/infrastructure/HTTPClient.js" import { HttpClient } from "sim-shared/infrastructure/HTTPClient.js"
import { JWTService } from "../aplication/JWT.service.js"
import { env } from "./env/index.js" import { env } from "./env/index.js"
import { jwtService } from "./jwtService.config.js"
const OBJ_BASE_URL = env.OBJ_BASE_URL const OBJ_BASE_URL = env.OBJ_BASE_URL
@@ -9,5 +9,5 @@ export const httpInstance = new HttpClient({
headers: { headers: {
"content-type": " application/json; charset=utf-8" "content-type": " application/json; charset=utf-8"
}, },
jwtManager: jwtService jwtManager: new JWTService()
}) })

View File

@@ -1,59 +0,0 @@
import { GrantAccessRequestBody, JWTService } from "sim-shared/aplication/JWT.service.js"
import { env } from "./env/index.js"
import { JWTHeader } from "sim-shared/domain/JWT.js"
const PRIVATE_KEY_PATH = env.OBJ_PEM_PATH
const GET_TOKEN_URL = "https://idp.docapost.io/auth/realms/GETWAY/protocol/openid-connect/token"
const REFRESH_TOKEN_URL = GET_TOKEN_URL
const DEFAULT_BODY: GrantAccessRequestBody = {
grant_type: "client_credentials",
client_id: env.OBJ_CLIENT_ID,
client_assertion_type: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
client_assertion: env.OBJ_CLI_ASSERTION
}
const DEFAULT_HEADERS = {
"content-type": "application/x-www-form-urlencoded"
}
const DEFAULT_HEADERS_JWT = {
alg: "RS256",
typ: "JWT",
kid: env.OBJ_KID,
}
const DEFAULT_DATA_JWT = {
sub: env.OBJ_CLIENT_ID,
iss: env.OBJ_CLIENT_ID,
aud: "https://idp.docapost.io/auth/realms/GETWAY",
jti: Date.now().toString(),
}
function addIATHeaders(authHeaders: Object) {
const headers = <JWTHeader>{
...authHeaders,
sub: env.OBJ_CLIENT_ID,
iss: env.OBJ_CLIENT_ID,
aud: GET_TOKEN_URL,
jti: Date.now().toString(),
iat: Math.floor(Date.now() / 1000),
exp: Math.floor(Date.now() / 1000) + 5 * 60,
}
return headers
}
export const jwtService = new JWTService({
transformJWTHeaders: addIATHeaders,
defaultHeaders: DEFAULT_HEADERS,
defaultBody: DEFAULT_BODY,
defaultJWTHeaders: DEFAULT_HEADERS_JWT,
defaultJWTPayload: DEFAULT_DATA_JWT,
privateKeyPath: PRIVATE_KEY_PATH,
tokenUrl: GET_TOKEN_URL,
refreshTokenUrl: REFRESH_TOKEN_URL
})

View File

@@ -8,7 +8,6 @@ import { SimUseCases } from "./aplication/Sim.usecases.js"
import { SimController } from "./aplication/Sim.controller.js" import { SimController } from "./aplication/Sim.controller.js"
import { SimRouter } from "./aplication/Sim.router.js" import { SimRouter } from "./aplication/Sim.router.js"
import { OrderRepository } from "sim-shared/infrastructure/OrderRepository.js" import { OrderRepository } from "sim-shared/infrastructure/OrderRepository.js"
import { PauseCancelTaskRepository } from "#adapters/PauseCancelTaskRepository.js"
async function startWorker() { async function startWorker() {
const rmqClient = await startRMQClient() const rmqClient = await startRMQClient()
@@ -19,21 +18,15 @@ async function startWorker() {
await pgClient.checkDatabaseConnection() await pgClient.checkDatabaseConnection()
const operationRepository = new ObjeniousOperationsRepository( const operationRepository = new ObjeniousOperationsRepository(pgClient)
httpClient,
pgClient,
)
const orderRepository = new OrderRepository(pgClient) const orderRepository = new OrderRepository(pgClient)
const pauseRepository = new PauseCancelTaskRepository(pgClient)
const simActivationController = new SimController( const simActivationController = new SimController(
rmqClient, rmqClient,
new SimUseCases({ new SimUseCases({
httpClient: httpClient, httpClient: httpClient,
operationRepository: operationRepository, operationRepository: operationRepository,
orderRepository: orderRepository, orderRepository: orderRepository
pauseRepository: pauseRepository
}) })
) )
const simRouter = new SimRouter(simActivationController, rmqClient) const simRouter = new SimRouter(simActivationController, rmqClient)

View File

@@ -1,72 +0,0 @@
import { after, before, describe, it } from "node:test";
import { CreatePauseCancelTaskDTO, PauseCancelTaskRepository } from "./PauseCancelTaskRepository.js";
import { postgrClient } from "#config/postgreConfig.js";
import assert from "node:assert";
const testTask: CreatePauseCancelTaskDTO = {
iccid: "1234",
operation_type: "suspend",
activation_date: new Date(),
next_check: new Date(),
action_data: {
dueDate: new Date().toString(),
correlation_id: "12223",
identifier: {
identifiers: ["1234"],
identifierType: "ICCID"
}
}
}
describe("Test PauseCancelTaskRepository - DB", () => {
const createdIds: number[] = [];
const pauseRepo = new PauseCancelTaskRepository(postgrClient)
before(() => {
})
after(() => {
})
it("Should create a task", async () => {
const created = await pauseRepo.addTask(testTask)
assert.ok(created != undefined, "A value must be returned always")
assert.ok(created.error == undefined, "Should not return a error")
assert.ok(created.data != undefined, "Data must be returned")
createdIds.push(created.data.id)
})
it("Should update a existing task", async () => {
const updated = await pauseRepo.updateTask({
id: createdIds[0],
next_check: new Date()
})
assert.ok(updated != undefined, "A value must be returned always")
assert.ok(updated.error == undefined, "Should not return a error")
assert.ok(updated.data != undefined, "Data must be returned")
})
it("Should finish a existing task", async () => {
const finish = await pauseRepo.finishTask({
id: createdIds[0],
error: "ok"
})
assert.ok(finish != undefined, "A value must be returned always")
assert.ok(finish.error == undefined, "Should not return a error")
assert.ok(finish.data != undefined, "Data must be returned")
})
it("Should get at least 1 pending task", async () => {
const created = await pauseRepo.addTask(testTask)
const pending = await pauseRepo.getPending()
assert.ok(pending != undefined, "A value must be returned always")
assert.ok(pending.error == undefined, "Should not return a error")
assert.ok(pending.data != undefined, "Data must be returned")
console.log("--> ", pending.data[0])
})
})

View File

@@ -1,126 +0,0 @@
import { Result } from "sim-shared/domain/Result.js";
import { QueryResult } from "pg";
import { PgClient } from "sim-shared/infrastructure/PgClient.js";
import { AxiosError } from "axios";
import { ActionData } from "#domain/DTOs/objeniousapi.js";
export type PauseCancelTask = {
id: number;
iccid: string;
operation_type: "suspend" | "terminate",
last_checked?: Date | null;
activation_date?: Date | null;
next_check?: Date | null;
completed_date?: Date | null;
error?: string | null;
action_data: ActionData
}
export type CreatePauseCancelTaskDTO = Pick<PauseCancelTask, "iccid" | "activation_date" | "next_check" | "operation_type" | "action_data">
export type UpdatePauseCancelTaskDTO = Pick<PauseCancelTask, "id" | "next_check">
export type FinishPauseCancelTaskDTO = Pick<PauseCancelTask, "id" | "error">
/**
* Repositorio para compensar los problemas de cacelcaiones/pausas de objenious a
* la hora aplicarlo sobre una linea con el billing a test.
*/
export class PauseCancelTaskRepository {
constructor(
private readonly pgClient: PgClient
) {
}
/**
* Obtiene las siguientes que se pueden lanzar, puede haber más pero
* estan pendientes
*/
public async getPending(): Promise<Result<string, PauseCancelTask[]>> {
const sql = `
SELECT * FROM pause_cancel_tasks
WHERE completed_date IS NULL
AND (next_check <= NOW() OR next_check IS NULL)
ORDER BY id ASC;
`;
try {
const res: QueryResult<PauseCancelTask> = await this.pgClient.query(sql);
return {
data: res.rows
}
} catch (e) {
return {
error: (e as AxiosError).message
}
}
}
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, action_data)
VALUES ($1, $2, $3, now(), $4, $5)
RETURNING *;
`;
try {
const values = [task.iccid, task.activation_date, task.next_check, task.operation_type, JSON.stringify(task.action_data)];
const res: QueryResult<PauseCancelTask> = await this.pgClient.query(sql, values);
return {
data: res.rows[0]
}
} catch (e) {
return {
error: (e as AxiosError).message
}
}
}
/**
* Se ha vuelto a comprobar la tarea pero sigue en test
*/
public async updateTask(updateData: UpdatePauseCancelTaskDTO): Promise<Result<string, PauseCancelTask>> {
const sql = `
UPDATE pause_cancel_tasks
SET last_checked = now(), next_check = $1
WHERE id = $2
RETURNING *;
`;
try {
const res = await this.pgClient.query<PauseCancelTask>(sql, [updateData.next_check, updateData.id]);
return {
data: res.rows[0]
}
} catch (e) {
return {
error: (e as AxiosError).message
}
}
}
/**
* La tarea ha termiando bien o mal
*/
public async finishTask(finishData: FinishPauseCancelTaskDTO) {
const sql = `
UPDATE pause_cancel_tasks
SET completed_date = NOW(), error = $1
WHERE id = $2
RETURNING *;
`;
try {
const res = await this.pgClient.query(sql, [finishData.error, finishData.id]);
return {
data: res.rows[0]
}
} catch (e) {
return {
error: (e as AxiosError).message
}
}
}
}

View File

@@ -53,7 +53,7 @@
} }
}, },
"scripts": { "scripts": {
"test": "node --import tsx --test ./**/*.test.ts", "test": "echo \"Error: no test specified\" && exit 1",
"dev": "tsx watch index.ts", "dev": "tsx watch index.ts",
"build": "tsc --build && yarn tsc-alias -p tsconfig.json && cp .env package.json ../../dist/packages/sim-consumidor-objenious/", "build": "tsc --build && yarn tsc-alias -p tsconfig.json && cp .env package.json ../../dist/packages/sim-consumidor-objenious/",
"start": "node ../../dist/packages/sim-consumidor-objenious/index.js", "start": "node ../../dist/packages/sim-consumidor-objenious/index.js",

View File

@@ -36,6 +36,7 @@ export class SimController {
}) { }) {
return async (req: Request, res: Response) => { return async (req: Request, res: Response) => {
const body = req.body const body = req.body
// 1. Validacion del body // 1. Validacion del body
if (args.validator != undefined) { if (args.validator != undefined) {
const validationResult = args.validator.validate(body) const validationResult = args.validator.validate(body)
@@ -131,21 +132,6 @@ 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() { public cancelation() {
return this.controllerGenerator<{ iccid: string }, { iccid: string, compañia: string }>({ return this.controllerGenerator<{ iccid: string }, { iccid: string, compañia: string }>({
validator: iccidValidator, validator: iccidValidator,

View File

@@ -130,36 +130,6 @@ 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 }): async preActivation(args: { iccid: string, compañia: string }):
Promise<Result<string, { iccid: string, message_id: string, operation: "preactivation" }>> { Promise<Result<string, { iccid: string, message_id: string, operation: "preactivation" }>> {
@@ -204,10 +174,8 @@ export class SimUsecases {
const cancelationWithId = this.addMessage_id(cancelationEvent) const cancelationWithId = this.addMessage_id(cancelationEvent)
console.log("[d] Cancelation ", cancelationWithId) console.log("[d] Cancelation ", cancelationWithId)
await this.eventBus.publish([cancelationWithId]) await this.eventBus.publish([cancelationWithId])
const savedOrder = await this.saveOrder(cancelationWithId) const savedOrder = await this.saveOrder(cancelationWithId)
if (savedOrder.error != undefined) { if (savedOrder.error != undefined) {
console.error(savedOrder.error) console.error(savedOrder.error)
return { return {
@@ -237,12 +205,11 @@ export class SimUsecases {
iccid: args.iccid iccid: args.iccid
} }
} }
const pauseWithId = this.addMessage_id(pauseEvent) const pauseWithId = this.addMessage_id(pauseEvent)
console.log("[d] Pause", pauseWithId) console.log("[d] Pause", pauseWithId)
await this.eventBus.publish([pauseWithId]) await this.eventBus.publish([pauseWithId])
//await this.saveOrder(pauseWithId) await this.saveOrder(pauseWithId)
const savedOrder = await this.saveOrder<SimEvents.pause>(pauseWithId) const savedOrder = await this.saveOrder(pauseWithId)
if (savedOrder.error != undefined) { if (savedOrder.error != undefined) {
console.error(savedOrder.error) console.error(savedOrder.error)

View File

@@ -22,5 +22,4 @@ export const env = {
RABBITMQ_SECURE: process.env.RABBITMQ_SECURE, RABBITMQ_SECURE: process.env.RABBITMQ_SECURE,
RABBITMQ_RETRY_INTERVAL: process.env.RABBITMQ_INTERVAL, RABBITMQ_RETRY_INTERVAL: process.env.RABBITMQ_INTERVAL,
RABBITMQ_VHOST: String(process.env.RABBITMQ_VHOST), RABBITMQ_VHOST: String(process.env.RABBITMQ_VHOST),
CONNECTIONS_URL: String(process.env.CONNECTIONS_URL)
}; };

View File

@@ -5,7 +5,6 @@ import { simRoutes } from "./infrastructure/simRoutes.http.js"
import { rabbitmqEventBus } from '#config/eventBusConfig.js'; import { rabbitmqEventBus } from '#config/eventBusConfig.js';
import { env } from "#config/env/index.js" import { env } from "#config/env/index.js"
import { orderRoutes } from "#adapters/orderRoutes.http.js"; import { orderRoutes } from "#adapters/orderRoutes.http.js";
import { connectionsRoutes } from "#adapters/simconnectionsRoutes.js";
const PORT = env.API_PORT const PORT = env.API_PORT
const HOSTNAME = "0.0.0.0" const HOSTNAME = "0.0.0.0"
@@ -27,7 +26,6 @@ app.use(express.json());
app.use(express.urlencoded({ extended: true })); app.use(express.urlencoded({ extended: true }));
app.use("/sim", simRoutes) app.use("/sim", simRoutes)
app.use("/simconnections", connectionsRoutes)
app.use("/orders", orderRoutes) app.use("/orders", orderRoutes)
app.use("/docs", express.static(path.join(process.cwd(), '../../docs'))) app.use("/docs", express.static(path.join(process.cwd(), '../../docs')))

View File

@@ -23,7 +23,6 @@ simRoutes.get("/status", () => { })
simRoutes.post("/save", simController.save()) simRoutes.post("/save", simController.save())
simRoutes.post("/activate", simController.activation()) simRoutes.post("/activate", simController.activation())
simRoutes.post("/reActivate", simController.reActivation())
simRoutes.post("/preActivate", simController.preactivation()) simRoutes.post("/preActivate", simController.preactivation())
@@ -36,5 +35,4 @@ simRoutes.post("/test", simController.test())
// Proceso especifico de ALAI para liberar sims canceladas // Proceso especifico de ALAI para liberar sims canceladas
simRoutes.post("/free", simController.free()) simRoutes.post("/free", simController.free())
export { simRoutes } export { simRoutes }

View File

@@ -1,87 +0,0 @@
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',);
**/

View File

@@ -53,7 +53,6 @@
"cors": "*", "cors": "*",
"dotenv": "*", "dotenv": "*",
"express": "*", "express": "*",
"http-proxy-middleware": "^3.0.5",
"sim-shared": "sim-shared:*", "sim-shared": "sim-shared:*",
"typescript": "*" "typescript": "*"
}, },

View File

@@ -1,7 +1,6 @@
import { HttpClient } from "sim-shared/infrastructure/HTTPClient.js" import { HttpClient } from "sim-shared/infrastructure/HTTPClient.js"
import { env } from "./env/index.js" import { env } from "./env/index.js"
import { jwtService } from "./jwtService.config.js" import { JWTService } from "packages/sim-consumidor-objenious/aplication/JWT.service.js"
const OBJ_BASE_URL = env.OBJ_BASE_URL const OBJ_BASE_URL = env.OBJ_BASE_URL
@@ -10,5 +9,5 @@ export const httpInstance = new HttpClient({
headers: { headers: {
"content-type": " application/json; charset=utf-8" "content-type": " application/json; charset=utf-8"
}, },
jwtManager: jwtService jwtManager: new JWTService()
}) })

View File

@@ -1,59 +0,0 @@
import { GrantAccessRequestBody, JWTService } from "sim-shared/aplication/JWT.service.js"
import { env } from "./env/index.js"
import { JWTHeader } from "sim-shared/domain/JWT.js"
const PRIVATE_KEY_PATH = env.OBJ_PEM_PATH
const GET_TOKEN_URL = "https://idp.docapost.io/auth/realms/GETWAY/protocol/openid-connect/token"
const REFRESH_TOKEN_URL = GET_TOKEN_URL
const DEFAULT_BODY: GrantAccessRequestBody = {
grant_type: "client_credentials",
client_id: env.OBJ_CLIENT_ID,
client_assertion_type: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
client_assertion: env.OBJ_CLI_ASSERTION
}
const DEFAULT_HEADERS = {
"content-type": "application/x-www-form-urlencoded"
}
const DEFAULT_HEADERS_JWT = {
alg: "RS256",
typ: "JWT",
kid: env.OBJ_KID,
}
const DEFAULT_DATA_JWT = {
sub: env.OBJ_CLIENT_ID,
iss: env.OBJ_CLIENT_ID,
aud: "https://idp.docapost.io/auth/realms/GETWAY",
jti: Date.now().toString(),
}
function addIATHeaders(authHeaders: Object) {
const headers = <JWTHeader>{
...authHeaders,
sub: env.OBJ_CLIENT_ID,
iss: env.OBJ_CLIENT_ID,
aud: GET_TOKEN_URL,
jti: Date.now().toString(),
iat: Math.floor(Date.now() / 1000),
exp: Math.floor(Date.now() / 1000) + 5 * 60,
}
return headers
}
export const jwtService = new JWTService({
transformJWTHeaders: addIATHeaders,
defaultHeaders: DEFAULT_HEADERS,
defaultBody: DEFAULT_BODY,
defaultJWTHeaders: DEFAULT_HEADERS_JWT,
defaultJWTPayload: DEFAULT_DATA_JWT,
privateKeyPath: PRIVATE_KEY_PATH,
tokenUrl: GET_TOKEN_URL,
refreshTokenUrl: REFRESH_TOKEN_URL
})

View File

@@ -8,9 +8,6 @@ import { OrderRepository } from "sim-shared/infrastructure/OrderRepository.js"
import { TaskVolcadoLineas } from "./tasks/volcado_lineas.js" import { TaskVolcadoLineas } from "./tasks/volcado_lineas.js"
import { ObjeniousLinesRepository } from "./infranstructure/ObjeniousLinesRepository.js" import { ObjeniousLinesRepository } from "./infranstructure/ObjeniousLinesRepository.js"
import { postgresClientIntranet } from "./config/intranetPostgresConfig.js" import { postgresClientIntranet } from "./config/intranetPostgresConfig.js"
import { PauseCancelTaskRepository } from "packages/sim-consumidor-objenious/infrastructure/PauseCancelTaskRepository.js"
import { PauseTerminateTask } from "./tasks/check_pause_terminate.js"
import { SimUseCases } from "packages/sim-consumidor-objenious/aplication/Sim.usecases.js"
async function startCron() { async function startCron() {
const commonSettings = { const commonSettings = {
@@ -24,10 +21,7 @@ async function startCron() {
console.log("[i] Comprobando conexion con la BDD ") console.log("[i] Comprobando conexion con la BDD ")
await pgClient.checkDatabaseConnection() await pgClient.checkDatabaseConnection()
const operationRepository = new ObjeniousOperationsRepository( const operationRepository = new ObjeniousOperationsRepository(pgClient)
httpClient,
pgClient,
)
const orderRepository = new OrderRepository(pgClient) const orderRepository = new OrderRepository(pgClient)
const objeniousLineRepository = new ObjeniousLinesRepository(postgresClientIntranet) const objeniousLineRepository = new ObjeniousLinesRepository(postgresClientIntranet)
@@ -37,32 +31,8 @@ async function startCron() {
httpClient, httpClient,
) )
const objeniosRepo = new ObjeniousOperationsRepository( const volcadoLineasTask = new TaskVolcadoLineas(httpClient, objeniousLineRepository)
httpClient,
pgClient
)
const volcadoLineasTask = new TaskVolcadoLineas(
objeniousLineRepository,
objeniosRepo
)
const pauseRepo = new PauseCancelTaskRepository(pgClient)
const simUsecases = new SimUseCases({
httpClient: httpClient,
operationRepository: operationRepository,
orderRepository: orderRepository,
pauseRepository: pauseRepo
})
const pauseTask = new PauseTerminateTask(
objeniosRepo,
pauseRepo,
simUsecases,
orderRepository
)
await objTask.getPendingOperations()
const PERIODO_PETICIONES = 10 * 60 * 1000 const PERIODO_PETICIONES = 10 * 60 * 1000
const interval = setInterval(async () => { const interval = setInterval(async () => {
try { try {
@@ -81,11 +51,7 @@ async function startCron() {
} }
}, PERIODO_VOLCADO) }, PERIODO_VOLCADO)
await pauseTask.run() await volcadoLineasTask.loadLines()
const PERIODO_CANCELACIONES = 60 * 60 * 1000;
const clacelacionesInterval = setInterval(async () => {
await pauseTask.run()
}, PERIODO_CANCELACIONES)
} }

View File

@@ -3,11 +3,10 @@ import { OrderRepository } from "sim-shared/infrastructure/OrderRepository.js";
import axios from "axios"; import axios from "axios";
import { IOperationsRepository, Objenious, ObjeniousOperation, ObjeniousOperationChange, StatusEnum } from "sim-shared/domain/operationsRepository.port.js"; import { IOperationsRepository, Objenious, ObjeniousOperation, ObjeniousOperationChange, StatusEnum } from "sim-shared/domain/operationsRepository.port.js";
import { HttpClient } from "sim-shared/infrastructure/HTTPClient.js"; import { HttpClient } from "sim-shared/infrastructure/HTTPClient.js";
import { ObjeniousOperationsRepository } from "packages/sim-shared/infrastructure/ObjeniousOperationRepository.js";
export class CheckObjeniousRequests { export class CheckObjeniousRequests {
constructor( constructor(
private readonly operationsRepository: ObjeniousOperationsRepository, private readonly operationsRepository: IOperationsRepository,
private readonly orderRepository: OrderRepository, private readonly orderRepository: OrderRepository,
private readonly httpClient: HttpClient private readonly httpClient: HttpClient
) { ) {

View File

@@ -1,191 +0,0 @@
import { ObjeniousLine } from "sim-shared/domain/objeniousLine.js";
import { PauseCancelTaskRepository } from "sim-consumidor-objenious/infrastructure/PauseCancelTaskRepository.js";
import { ObjeniousOperationsRepository } from "sim-shared/infrastructure/ObjeniousOperationRepository.js";
import { SimUseCases } from "sim-consumidor-objenious/aplication/Sim.usecases.js";
import { OrderRepository } from "packages/sim-shared/infrastructure/OrderRepository.js";
const logger =
{
log: (...data: any[]) => console.log("[i] [TaskPauseTerminate]", ...data),
error: (...data: any[]) => console.error("[x] [TaskPauseTerminate] ", ...data),
}
export class PauseTerminateTask {
constructor(
private readonly objeniousRepo: ObjeniousOperationsRepository,
private readonly pauseRepo: PauseCancelTaskRepository,
private readonly simUsecases: SimUseCases,
private readonly orderRepo: OrderRepository
) {
}
public async run() {
const finError = (err: any) => {
logger.error("Finalizado con errores proceso de comprobacion de lineas en pausa o canceladas")
logger.error(err)
}
const finExito = () => {
logger.log("Finalizado con exito proceso de comprobacion de lineas en pausa o canceladas")
}
try {
logger.log("Iniciando proceso de comprobacion de lineas en pausa o canceladas")
// 1. Se comprueba cuantas peticiones hay qye revisar
const peticionesRevisar = await this.pauseRepo.getPending()
if (peticionesRevisar.error != undefined) {
finError(peticionesRevisar.error)
return 1;
}
logger.log(`Se van a revisar ${peticionesRevisar.data?.length} peticiones`)
if (peticionesRevisar.data == undefined || peticionesRevisar.data.length == 0) {
finExito()
return 0;
}
// 2. Se comprueba que alguna de las lineas haya dejado de estar en estado de test
const iccids = peticionesRevisar.data.map(e => e.iccid)
const lineasActualizadas: ObjeniousLine[] = []
const lineGenerator = this.objeniousRepo.getLinesByStatusAPI({
iccids: iccids
})
let lines = await lineGenerator.next()
if (lines.value.error != undefined || lines.value.data == undefined) {
logger.error("Error cargando las lineas", lines.value.error)
finError(lines.value.error)
return 1;
} else {
lineasActualizadas.push(...lines.value.data)
}
while (!lines.done) {
if (lines.value.error != undefined || lines.value.data == undefined) {
logger.error("Error cargando las lineas", lines.value.error)
finError(lines.value.error)
return 1;
} else {
lineasActualizadas.push(...lines.value.data)
}
lines = await lineGenerator.next()
}
console.log("Cargado: ", lineasActualizadas)
// 3. Se separan las lineas que se tienen que actualizar al no ser test
// y las que se tienen que reencolar al ser test
const lineasNoTest = lineasActualizadas.filter(e => e.status.billingStatus != "TEST")
const lineasTest = lineasActualizadas.filter(e => e.status.billingStatus == "TEST")
// 4. Las lineas de test se reencolan
// El proximo reintento es en 1 dia
const proximoReintento = new Date()
proximoReintento.setDate(new Date().getDate() + 1)
// 5. Reintentos en 1 dia
for (const linea of lineasTest) {
const lineaId = peticionesRevisar.data
.find(e => e.iccid == linea.identifier.iccid)?.id
if (lineaId == undefined) continue; // Esto puede ser un problema si se generaliza
this.pauseRepo.updateTask({
id: lineaId,
next_check: proximoReintento
})
}
// 6. Operaciones de pausa/cancelacion definitiva
for (const linea of lineasNoTest) {
const operacion = peticionesRevisar.data
.find(e => e.iccid == linea.identifier.iccid)
if (operacion == undefined) continue;
const dueDate = new Date()
dueDate.setMinutes(new Date().getMinutes() + 15)
const operacionTipo = operacion.operation_type
const actionData = operacion.action_data
const correlation_id = operacion.action_data.correlation_id
actionData.dueDate = dueDate.toISOString()
switch (linea.status.billingStatus) {
case "ACTIVATED":
let result = null;
// 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)()
break;
case "terminate":
result = await this.simUsecases.terminate(actionData)()
break;
default:
break;
}
if (result == undefined) {
logger.error("Operacion desconocida", operacion)
} else if (result?.error != undefined) {
// error usecase
logger.error(result.error)
await this.pauseRepo.finishTask({
id: operacion.id,
error: result.error
})
if (correlation_id != undefined)
await this.orderRepo.errorOrder({
correlation_id: correlation_id,
status: "dlx",
reason: result.error
})
} else {
// ok
await this.pauseRepo.finishTask({ id: operacion.id })
if (correlation_id != undefined)
await this.orderRepo.finishOrder({ correlation_id })
}
break;
case "CANCELED":
await this.pauseRepo.finishTask({
id: operacion.id,
error: "billingStatus is CANCELED"
})
if (correlation_id != undefined)
await this.orderRepo.finishOrder({ correlation_id })
break;
case "SUSPENDED":
await this.pauseRepo.finishTask({
id: operacion.id,
error: "billingStatus is SUSPENDED"
})
if (correlation_id != undefined)
await this.orderRepo.finishOrder({ correlation_id })
break;
case "TEST":
// No puede ser
default:
logger.error("billingStatus desconocido", linea.status.billingStatus)
}
}
finExito()
} catch (e) {
finError(e)
}
return 0
}
}

View File

@@ -1,14 +1,94 @@
import { lineToCreateLineDto, ObjeniousLine } from "sim-shared/domain/objeniousLine.js"; import assert from "node:assert";
import { lineToCreateLineDto, ObjeniousLine, ObjeniousLineResponse } from "sim-shared/domain/objeniousLine.js";
import { tryCatch, Result } from "sim-shared/domain/Result.js";
import { HttpClient } from "sim-shared/infrastructure/HTTPClient.js";
import { ObjeniousLinesRepository } from "../infranstructure/ObjeniousLinesRepository.js"; import { ObjeniousLinesRepository } from "../infranstructure/ObjeniousLinesRepository.js";
import { ObjeniousOperationsRepository } from "sim-shared/infrastructure/ObjeniousOperationRepository.js"; import { AxiosResponse } from "axios";
import { constants } from "node:buffer";
const MAX_PAGE_SIZE = 100
export class TaskVolcadoLineas { export class TaskVolcadoLineas {
constructor( constructor(
private readonly httpClient: HttpClient,
private readonly linesRepository: ObjeniousLinesRepository, private readonly linesRepository: ObjeniousLinesRepository,
private readonly objeniousRepository: ObjeniousOperationsRepository
) { ) {
} }
/**
* Mover al repo
*/
private async * getLinesByStatus(args?: {
pageSize?: number,
pageNumber?: number,
status?: string
}): AsyncGenerator<Result<string, ObjeniousLine[]>, Result<string, ObjeniousLine[]>, any> {
const path = "/lines"
const pageSize = args?.pageSize ?? MAX_PAGE_SIZE;
let currentPage = args?.pageNumber ?? 0;
let totalPages: number | undefined = undefined; // Como limite de paginas, igual es pasarse pero hasta que se lea
const params: Record<string, string | number> = {}
const loadNextLine = async (page: number): Promise<Result<string, ObjeniousLine[]>> => {
if (args?.status != undefined) params["simStatus"] = args.status
params["pageSize"] = pageSize
params["pageNumber"] = page
console.log("Params", params)
console.log(`[i] Cargando pagina ${currentPage} de ${totalPages ?? "(desc)"}`)
const nextPage = await tryCatch<AxiosResponse<ObjeniousLineResponse>>(this.httpClient.client.get(path, {
params: params
}))
if (nextPage.error != undefined) {
console.error(nextPage.error.msg)
return {
error: nextPage.error.msg.message
}
}
// Se aumenta para la siguiente ejecucion
console.log(`[i] Página ${currentPage} completa, total: ${nextPage.data.data.totalPages}`)
totalPages = nextPage.data.data.totalPages
return {
data: nextPage.data.data.content
}
}
// El inicio se ejecuta siempre
const lines = await loadNextLine(currentPage)
if (lines.error != undefined) {
console.error("[x] Error obteniendo las lineas, cancelando operación");
return {
error: "Error cargando lineas"
}
}
currentPage++;
yield {
data: lines.data
}
// Copia para evitar bucles infinitos por error de la api
const maxPages = totalPages
assert.ok(maxPages != undefined, "No se ha defindo el numero de paginas") // Nunca deberia pasar pero así se evitan bucles infnitos
console.log("maxPages", maxPages)
for (let i = currentPage; i < maxPages!; i++) {
console.log("Bucle i:", i, "page: ", currentPage)
yield await loadNextLine(currentPage);
currentPage++;
}
return {
data: []
}
}
private async saveLines(lines: ObjeniousLine[]) { private async saveLines(lines: ObjeniousLine[]) {
const linesToCreate = lines.map(lineToCreateLineDto) const linesToCreate = lines.map(lineToCreateLineDto)
@@ -27,9 +107,7 @@ export class TaskVolcadoLineas {
console.log("[i] Iniciando task de volcado de lineas de Objenious") console.log("[i] Iniciando task de volcado de lineas de Objenious")
// Carga todas las lineas en memoria, hay que comprobar que no se gaste demasiada // Carga todas las lineas en memoria, hay que comprobar que no se gaste demasiada
const linesIterator = this.objeniousRepository.getLinesByStatusAPI({ const linesIterator = this.getLinesByStatus()
pageSize: 100
})
let lines = await linesIterator.next() let lines = await linesIterator.next()
if (lines.value.error != undefined || lines.value.data == undefined) { if (lines.value.error != undefined || lines.value.data == undefined) {
@@ -40,6 +118,7 @@ export class TaskVolcadoLineas {
await this.saveLines(lines.value.data) await this.saveLines(lines.value.data)
while (!lines.done) { while (!lines.done) {
console.log()
lines = await linesIterator.next() lines = await linesIterator.next()
if (lines.value.error != undefined || lines.value.data == undefined) { if (lines.value.error != undefined || lines.value.data == undefined) {
console.error("[x] Error cargando las lineas a volcar", lines.value.error) console.error("[x] Error cargando las lineas a volcar", lines.value.error)

View File

@@ -1,15 +0,0 @@
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)
}
}

View File

@@ -7,12 +7,9 @@
import { env, loadEnvFile } from "node:process"; import { env, loadEnvFile } from "node:process";
import { Pool } from "pg"; import { Pool } from "pg";
import { PgClient } from "../infrastructure/PgClient.js"; import { PgClient } from "../infrastructure/PgClient.js";
import { HttpClient } from "../infrastructure/HTTPClient.js";
import { jwtService } from "./jwtService.config.js";
console.warn("[i!] Se está corriendo codigo de test") console.warn("[i!] Se está corriendo codigo de test")
loadEnvFile("../../.env") // Global loadEnvFile("../../.env") // Global
loadEnvFile("./test.env") // Local
// se hace una por servicio. // se hace una por servicio.
export const pgPool = new Pool({ export const pgPool = new Pool({
@@ -27,14 +24,4 @@ export const postgresClient = new PgClient({
pool: pgPool pool: pgPool
}) })
const OBJ_BASE_URL = "https://api-getway.objenious.com/ws"
export const httpObjClient = new HttpClient({
baseURL: OBJ_BASE_URL,
headers: {
"content-type": " application/json; charset=utf-8"
},
jwtManager: jwtService
})
console.warn(`[T] TEST DB : ${env.POSTGRES_DATABASE}@${env.POSTGRES_HOST}`) console.warn(`[T] TEST DB : ${env.POSTGRES_DATABASE}@${env.POSTGRES_HOST}`)

View File

@@ -1,67 +0,0 @@
import assert from "assert"
import { env, loadEnvFile } from "process"
import { GrantAccessRequestBody, JWTService } from "sim-shared/aplication/JWT.service.js"
import { JWTHeader } from "sim-shared/domain/JWT.js"
loadEnvFile("../../.env") // Global
loadEnvFile("./test.env") // Local
assert(env.OBJ_CLIENT_ID != undefined)
assert(env.OBJ_CLI_ASSERTION != undefined)
assert(env.OBJ_PEM_PATH != undefined)
const PRIVATE_KEY_PATH = env.OBJ_PEM_PATH
const GET_TOKEN_URL = "https://idp.docapost.io/auth/realms/GETWAY/protocol/openid-connect/token"
const REFRESH_TOKEN_URL = GET_TOKEN_URL
const DEFAULT_BODY: GrantAccessRequestBody = {
grant_type: "client_credentials",
client_id: env.OBJ_CLIENT_ID,
client_assertion_type: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
client_assertion: env.OBJ_CLI_ASSERTION
}
const DEFAULT_HEADERS = {
"content-type": "application/x-www-form-urlencoded"
}
const DEFAULT_HEADERS_JWT = {
alg: "RS256",
typ: "JWT",
kid: env.OBJ_KID,
}
const DEFAULT_DATA_JWT = {
sub: env.OBJ_CLIENT_ID,
iss: env.OBJ_CLIENT_ID,
aud: "https://idp.docapost.io/auth/realms/GETWAY",
jti: Date.now().toString(),
}
function addIATHeaders(authHeaders: Object) {
const headers = <JWTHeader>{
...authHeaders,
sub: env.OBJ_CLIENT_ID,
iss: env.OBJ_CLIENT_ID,
aud: GET_TOKEN_URL,
jti: Date.now().toString(),
iat: Math.floor(Date.now() / 1000),
exp: Math.floor(Date.now() / 1000) + 5 * 60,
}
return headers
}
export const jwtService = new JWTService({
transformJWTHeaders: addIATHeaders,
defaultHeaders: DEFAULT_HEADERS,
defaultBody: DEFAULT_BODY,
defaultJWTHeaders: DEFAULT_HEADERS_JWT,
defaultJWTPayload: DEFAULT_DATA_JWT,
privateKeyPath: PRIVATE_KEY_PATH,
tokenUrl: GET_TOKEN_URL,
refreshTokenUrl: REFRESH_TOKEN_URL
})

View File

@@ -80,8 +80,7 @@ export type FinishOrderDTO =
IdOrCorrelationID IdOrCorrelationID
& &
{ {
reason?: string, reason?: string
end_date?: Date
} }
export type ErrorOrderDTO = export type ErrorOrderDTO =

View File

@@ -1,7 +0,0 @@
export interface Queue {
name: string;
messages: number;
ready: number;
unacked: number;
consumers: number;
}

View File

@@ -14,7 +14,7 @@ export type Failure<E = Error> = {
*/ */
export type Result<E, D> = Failure<E> | Success<D> export type Result<E, D> = Failure<E> | Success<D>
export async function tryCatch<T>(func: Promise<T>): Promise<Result<Error, T>> { export async function tryCatch<T>(func: Promise<T>): Promise<Result<{ msg: Error }, T>> {
try { try {
const res = await func; const res = await func;
return { return {
@@ -22,8 +22,9 @@ export async function tryCatch<T>(func: Promise<T>): Promise<Result<Error, T>> {
} }
} catch (e: unknown) { } catch (e: unknown) {
return { return {
error: e as Error error: {
msg: e as Error
}
} }
} }
} }

View File

@@ -24,7 +24,7 @@ export namespace SimEvents {
} }
export type reActivation = DomainEvent & { export type reActivation = DomainEvent & {
key: `sim.${string}.reactivate`, key: `sim.${string}.reActivate`,
payload: { payload: {
iccid: string iccid: string
}, },

View File

@@ -83,7 +83,7 @@ export type ObjeniousLine = {
commercialStatus: string, //"test", commercialStatus: string, //"test",
commercialStatusDate: string, //"2026-03-17T11:41:01.493+00:00", commercialStatusDate: string, //"2026-03-17T11:41:01.493+00:00",
networkStatus: string, // "ACTIVATED", networkStatus: string, // "ACTIVATED",
billingStatus: "ACTIVATED" | "SUSPENDED" | "CANCELED" | "TEST", billingStatus: string, //"TEST",
billingStatusChangeDate: string | null, // "2026-03-17T11:01:00.276+00:00", billingStatusChangeDate: string | null, // "2026-03-17T11:01:00.276+00:00",
billingActivationDate: string | null //, billingActivationDate: string | null //,
createdDate: string | null,//"2026-01-30T01:50:02.060+00:00" createdDate: string | null,//"2026-01-30T01:50:02.060+00:00"

View File

@@ -12,7 +12,7 @@ export type ObjeniousOperation = {
id?: number; id?: number;
/** Uuid del mensaje asociado a la operacion */ /** Uuid del mensaje asociado a la operacion */
correlation_id?: string; correlation_id?: string;
operation: "activate" | "suspend" | "terminate" | string; // TODO: completar y actualizar operation: "activate" | string; // TODO: completar y actualizar
retry_count?: number; retry_count?: number;
max_retry?: number; max_retry?: number;
max_date_retry?: string | null; max_date_retry?: string | null;
@@ -27,7 +27,8 @@ export type ObjeniousOperation = {
} }
export type ObjeniousOperationChange = { export type ObjeniousOperationChange = {
id?: number; operation_id: number; id?: number;
operation_id: number;
info?: string | null; info?: string | null;
error?: string | null; error?: string | null;
new_status: StatusEnum; new_status: StatusEnum;

View File

@@ -1,39 +0,0 @@
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(
httpObjClient,
postgresClient
)
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
*/
})
})

View File

@@ -1,139 +1,14 @@
import { IOperationsRepository, ObjeniousOperation, ObjeniousOperationChange } from "sim-shared/domain/operationsRepository.port.js"; import { IOperationsRepository, ObjeniousOperation, ObjeniousOperationChange } from "sim-shared/domain/operationsRepository.port.js";
import { Result, tryCatch } from "sim-shared/domain/Result.js"; import { Result } from "sim-shared/domain/Result.js";
import { PgClient } from "sim-shared/infrastructure/PgClient.js"; import { PgClient } from "sim-shared/infrastructure/PgClient.js";
import { ObjeniousLine, ObjeniousLineResponse } from "../domain/objeniousLine.js";
import { HttpClient } from "./HTTPClient.js";
import assert from "node:assert";
import { AxiosResponse } from "axios";
export class ObjeniousOperationsRepository implements IOperationsRepository { export class ObjeniousOperationsRepository implements IOperationsRepository {
constructor( constructor(
private http: HttpClient,
private readonly pgClient: PgClient private readonly pgClient: PgClient
) { ) {
} }
/**
* Consulta el estado de una o mas lineas directamente a la API de Objenious
* TODO: No hay paginacion como en getLinesByStatusAPI
*/
public async getLinesAPI(
identifierType: "ICCID" | "IMSI" | "IMEI" | "MSISDN" | "REFERENCE",
identifiers: string[]
): Promise<Result<string, ObjeniousLine[]>> {
if (identifiers.length == 0) {
return {
data: []
}
}
// Comprobar < MAX_PAGE_SIZE (Poco probable)
const path = "/lines"
const params = {
"identifier.identifierType": identifierType,
"identifier.identifiers": identifiers.toString()
}
const req = this.http.client.get<ObjeniousLineResponse>(path, {
params: params
})
const res = await tryCatch(req)
if (res.error != undefined) {
return {
error: res.error?.message
}
}
const lines = res.data.data.content
return {
data: lines
}
}
private MAX_PAGE_SIZE = 1000
public async * getLinesByStatusAPI(args?: {
pageSize?: number,
pageNumber?: number,
status?: string,
iccids?: string[]
}): AsyncGenerator<Result<string, ObjeniousLine[]>, Result<string, ObjeniousLine[]>, any> {
const path = "/lines"
const pageSize = args?.pageSize ?? this.MAX_PAGE_SIZE;
let currentPage = args?.pageNumber ?? 0;
let totalPages: number | undefined = undefined; // Como limite de paginas, igual es pasarse pero hasta que se lea
const params: Record<string, string | number> = {}
// Si se va a filtrar por iccids especificamente, en un futuro habra que ampliar el tipo de filtros
if (args?.iccids != undefined) {
params["identifier.identifierType"] = "ICCID"
params["identifier.identifiers"] = args.iccids.toString()
}
const loadNextLine = async (page: number): Promise<Result<string, ObjeniousLine[]>> => {
if (args?.status != undefined) params["simStatus"] = args.status
params["pageSize"] = pageSize
params["pageNumber"] = page
console.log(`[i] Cargando pagina ${currentPage} de ${totalPages ?? "(desc)"}`)
const nextPage = await tryCatch<AxiosResponse<ObjeniousLineResponse>>(this.http.client.get(path, {
params: params
}))
if (nextPage.error != undefined) {
console.error(nextPage.error)
return {
error: nextPage.error.message
}
}
// Se aumenta para la siguiente ejecucion
console.log(`[i] Página ${currentPage} completa, total: ${nextPage.data.data.totalPages}`)
totalPages = nextPage.data.data.totalPages
return {
data: nextPage.data.data.content
}
}
// El inicio se ejecuta siempre
const lines = await loadNextLine(currentPage)
if (lines.error != undefined) {
console.error("[x] Error obteniendo las lineas, cancelando operación");
return {
error: "Error cargando lineas"
}
}
currentPage++;
yield {
data: lines.data
}
// Copia para evitar bucles infinitos por error de la api
const maxPages = totalPages
assert.ok(maxPages != undefined, "No se ha defindo el numero de paginas") // Nunca deberia pasar pero así se evitan bucles infnitos
console.log("maxPages", maxPages)
for (let i = currentPage; i < maxPages!; i++) {
console.log("Bucle i:", i, "page: ", currentPage)
yield await loadNextLine(currentPage);
currentPage++;
}
return {
data: []
}
}
async createOperation(data: ObjeniousOperation): Promise<Result<string, ObjeniousOperation>> { async createOperation(data: ObjeniousOperation): Promise<Result<string, ObjeniousOperation>> {
const query = ` const query = `
INSERT INTO objenious_operation (operation, iccids, status, max_retry, request_id) INSERT INTO objenious_operation (operation, iccids, status, max_retry, request_id)
@@ -146,20 +21,6 @@ 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>> { async updateOperation(data: ObjeniousOperationChange): Promise<Result<string, ObjeniousOperation>> {
const client = await this.pgClient.connect(); const client = await this.pgClient.connect();
const { const {
@@ -185,7 +46,7 @@ export class ObjeniousOperationsRepository implements IOperationsRepository {
request_id = COALESCE($4, request_id), request_id = COALESCE($4, request_id),
mass_action_id = COALESCE($5, mass_action_id), mass_action_id = COALESCE($5, mass_action_id),
last_change_date = now() at time zone 'utc', last_change_date = now() at time zone 'utc',
end_date = CASE WHEN $2 IN ('finished','error') THEN now() at time zone 'utc' ELSE end_date END, end_date = CASE WHEN $2 IN ('finished') THEN now() at time zone 'utc' ELSE end_date END,
objenious_status = $6 objenious_status = $6
WHERE id = $1`; WHERE id = $1`;

View File

@@ -27,7 +27,7 @@ describe("Test OrderRepository", {}, (ctx) => {
before(async () => { before(async () => {
// Order1 // Order1
const result1 = await orderRepo.createOrder(order1) const result1 = await orderRepo.createOrder(order1)
assert.ok(result1.data != undefined, result1.error as string) assert(result1.data != undefined)
testIds.push(result1.data.id) testIds.push(result1.data.id)
// Order2 -> Para el test de crearOrder // Order2 -> Para el test de crearOrder

View File

@@ -3,9 +3,10 @@
*/ */
import { PoolClient, QueryResult, QueryResultRow } from "pg"; import { PoolClient, QueryResult, QueryResultRow } from "pg";
import { CreateOrderDTO, ErrorOrderDTO, FinishOrderDTO, OrderTracking, UpdateOrderDTO } from "../domain/Order.js"; import { CreateOrderDTO, ErrorOrderDTO, FinishOrderDTO, OrderTracking, UpdateOrderDTO } from "../domain/Order.js";
import { Result, tryCatch } from "../domain/Result.js"; import { Result } from "../domain/Result.js";
import { PgClient } from "./PgClient.js"; import { PgClient } from "./PgClient.js";
import assert from "node:assert"; import assert from "node:assert";
import { error } from "node:console";
/** /**
* Agrupa todas las operaciones de *Order*. * Agrupa todas las operaciones de *Order*.
@@ -18,8 +19,9 @@ import assert from "node:assert";
*/ */
export class OrderRepository { export class OrderRepository {
constructor( constructor(
private readonly pgClient: PgClient, private readonly pgClient: PgClient
) { ) {
} }
/** /**
@@ -55,8 +57,6 @@ export class OrderRepository {
} }
} }
/** /**
* El tipo <T> representa el contenido del mensaje de los order * El tipo <T> representa el contenido del mensaje de los order
*/ */
@@ -191,8 +191,6 @@ export class OrderRepository {
const orderId = currentOrderResult.data?.id const orderId = currentOrderResult.data?.id
if (orderId == undefined) { if (orderId == undefined) {
await client.query("ROLLBACK")
client.release()
return { return {
error: "El order a actualizar no existe " + idType + ": " + idValue error: "El order a actualizar no existe " + idType + ": " + idValue
} }
@@ -263,6 +261,7 @@ export class OrderRepository {
return updatedOrder return updatedOrder
} }
public async finishOrder(args: FinishOrderDTO) { public async finishOrder(args: FinishOrderDTO) {
const client = await this.pgClient.connect(); const client = await this.pgClient.connect();
assert((args.id != undefined) != (args.correlation_id != undefined)) assert((args.id != undefined) != (args.correlation_id != undefined))
@@ -282,8 +281,6 @@ export class OrderRepository {
const orderId = currentOrderResult.data?.id const orderId = currentOrderResult.data?.id
if (orderId == undefined) { if (orderId == undefined) {
await client.query("ROLLBACK")
client.release()
return { return {
error: "El order a actualizar no existe " + idType + ": " + idValue error: "El order a actualizar no existe " + idType + ": " + idValue
} }
@@ -302,8 +299,8 @@ export class OrderRepository {
UPDATE order_tracking UPDATE order_tracking
SET SET
status = 'finished', status = 'finished',
update_date = now(), update_date = (now() at time zone 'utc'),
finish_date = now() finish_date = (now() at time zone 'utc')
WHERE id = $1 WHERE id = $1
RETURNING id, status, update_date; RETURNING id, status, update_date;
` `

View File

@@ -1,37 +0,0 @@
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;
}
}
}

View File

@@ -1,14 +0,0 @@
## ENV PARA DATOS DE TEST - shared nunca se lanza en produccion
# claves de Objenious
OBJ_PEM_PATH=./obj.pem
OBJ_AUTHORIZATION=XOc7FtwXD8hUX2SFVX94XSty8wkOmChkwDNF09O_aIxPubMDdFUdCDCB4zpzSIxi8nOcTg7r_LM_nmd5qm7uLbksf_XArjI8iAyhjKz_2BAXPhmvKs4Fc9f3vv5LDfCVrPB9lP8P7rJ66_qnWs4jvhLQxSfn29m96hgXeCf8oySdIDUjN2q9Js3KAS5LL52Ri6ryvUeO1PvMhaPQMWRqoHIqTV1wPfPtiqQwcjUPmu5GeW164Kq1JLgV3KaGzfCZ9Qv9lbv30EJrukXxWuLCAhBS0kzrBXZoWvf2pb9uh3Am_93_dDxiIGQfIap9ZU_m8ZD1HPgvZOMCY6ZkxQconQ
OBJ_CLI_ASSERTION=XOc7FtwXD8hUX2SFVX94XSty8wkOmChkwDNF09O_aIxPubMDdFUdCDCB4zpzSIxi8nOcTg7r_LM_nmd5qm7uLbksf_XArjI8iAyhjKz_2BAXPhmvKs4Fc9f3vv5LDfCVrPB9lP8P7rJ66_qnWs4jvhLQxSfn29m96hgXeCf8oySdIDUjN2q9Js3KAS5LL52Ri6ryvUeO1PvMhaPQMWRqoHIqTV1wPfPtiqQwcjUPmu5GeW164Kq1JLgV3KaGzfCZ9Qv9lbv30EJrukXxWuLCAhBS0kzrBXZoWvf2pb9uh3Am_93_dDxiIGQfIap9ZU_m8ZD1HPgvZOMCY6ZkxQconQ
OBJ_CLIENT_ID=savefamily_rest_ws
OBJ_KID=xNfbMiyL1ORXGP8lElhcv8nVaG3EJKye4Lc1YoN3I1E
OBJ_BASE_URL=https://api-getway.objenious.com/ws
# OBJ_BASE_URL=https://api-getway.objenious.com/ws/test
NOTIFICATION_URL="https://sf-sim-activation.savefamilygps.net/send-activation-mail"
# NOTIFICATION_URL="localhost"
SIM_ACTIVATION_API_KEY=9e48c4ac-1ab0-4397-b3f3-6c239200dfe6

View File

@@ -1,107 +0,0 @@
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>
`
}

View File

@@ -1,47 +0,0 @@
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(),
}
}
}

View File

@@ -1,22 +0,0 @@
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
}

View File

@@ -1,13 +0,0 @@
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 })

View File

@@ -1,8 +0,0 @@
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!
})

View File

@@ -1,14 +0,0 @@
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;
}

View File

@@ -1,45 +0,0 @@
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);
});

View File

@@ -1,12 +0,0 @@
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
}

View File

@@ -1,8 +0,0 @@
{
"folders": [
{
"path": "."
}
],
"settings": {}
}

View File

@@ -452,15 +452,15 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@sf-alvar/db-migrate@npm:1.0.6": "@sf-alvar/db-migrate@npm:1.0.3":
version: 1.0.6 version: 1.0.3
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" 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"
dependencies: dependencies:
pg: "npm:^8.18.0" pg: "npm:^8.18.0"
yargs: "npm:^18.0.0" yargs: "npm:^18.0.0"
bin: bin:
db-migrate: lib/index.js db-migrate: lib/index.js
checksum: 10/070f1388ff1c6fd2d24c3139d779e871bc0db94f11dd2013aa7eb5728e3c21e594bac0f4d46f8f3132391a9903cca56d5c864862c622d70f24e0db0ffcbbbf0e checksum: 10/2b5745a5ce60456fc7fee1e6a8580978a520fedd8abbbc695557847cdf2b36aa5e1d795721ad35bc151fc9373dfa023bde73d6f43ba412b17293a1822c09fe6b
languageName: node languageName: node
linkType: hard linkType: hard
@@ -576,15 +576,6 @@ __metadata:
languageName: node languageName: node
linkType: hard 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": "@types/methods@npm:^1.1.4":
version: 1.1.4 version: 1.1.4
resolution: "@types/methods@npm:1.1.4" resolution: "@types/methods@npm:1.1.4"
@@ -1163,7 +1154,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"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": "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":
version: 4.4.3 version: 4.4.3
resolution: "debug@npm:4.4.3" resolution: "debug@npm:4.4.3"
dependencies: dependencies:
@@ -1422,13 +1413,6 @@ __metadata:
languageName: node languageName: node
linkType: hard 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": "expect-type@npm:^1.2.2":
version: 1.3.0 version: 1.3.0
resolution: "expect-type@npm:1.3.0" resolution: "expect-type@npm:1.3.0"
@@ -1543,16 +1527,6 @@ __metadata:
languageName: node languageName: node
linkType: hard 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": "follow-redirects@npm:^1.15.11":
version: 1.15.11 version: 1.15.11
resolution: "follow-redirects@npm:1.15.11" resolution: "follow-redirects@npm:1.15.11"
@@ -1814,31 +1788,6 @@ __metadata:
languageName: node languageName: node
linkType: hard 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": "https-proxy-agent@npm:^7.0.1":
version: 7.0.6 version: 7.0.6
resolution: "https-proxy-agent@npm:7.0.6" resolution: "https-proxy-agent@npm:7.0.6"
@@ -1916,7 +1865,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"is-glob@npm:^4.0.1, is-glob@npm:^4.0.3, is-glob@npm:~4.0.1": "is-glob@npm:^4.0.1, is-glob@npm:~4.0.1":
version: 4.0.3 version: 4.0.3
resolution: "is-glob@npm:4.0.3" resolution: "is-glob@npm:4.0.3"
dependencies: dependencies:
@@ -1932,13 +1881,6 @@ __metadata:
languageName: node languageName: node
linkType: hard 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": "is-promise@npm:^4.0.0":
version: 4.0.0 version: 4.0.0
resolution: "is-promise@npm:4.0.0" resolution: "is-promise@npm:4.0.0"
@@ -2900,7 +2842,6 @@ __metadata:
cors: "npm:*" cors: "npm:*"
dotenv: "npm:*" dotenv: "npm:*"
express: "npm:*" express: "npm:*"
http-proxy-middleware: "npm:^3.0.5"
prettier: "npm:*" prettier: "npm:*"
sim-shared: "sim-shared:*" sim-shared: "sim-shared:*"
supertest: "npm:*" supertest: "npm:*"
@@ -2915,7 +2856,7 @@ __metadata:
version: 0.0.0-use.local version: 0.0.0-use.local
resolution: "sim-eventos@workspace:." resolution: "sim-eventos@workspace:."
dependencies: dependencies:
"@sf-alvar/db-migrate": "npm:1.0.6" "@sf-alvar/db-migrate": "npm:1.0.3"
"@tsconfig/node22": "npm:^22.0.5" "@tsconfig/node22": "npm:^22.0.5"
"@types/amqplib": "npm:^0.10.8" "@types/amqplib": "npm:^0.10.8"
"@types/cors": "npm:^2.8.19" "@types/cors": "npm:^2.8.19"