From fdbb81ba64dc3a584f8d6395e9a46c723661bede Mon Sep 17 00:00:00 2001 From: Alvar San Martin Date: Thu, 16 Apr 2026 17:46:32 +0200 Subject: [PATCH] Inicio port NOS --- packages/sim-consumidor-nos/.env | 3 + .../aplication/SimNOS.controller.ts | 73 +++++------------- .../aplication/SimNOS.router.ts | 76 +++++++++++++++++++ .../aplication/SimNOS.usecases.ts | 35 +++++++++ .../config/env/{index.ts => env.ts} | 3 +- .../config/eventBus.config.ts | 2 +- packages/sim-consumidor-nos/domain/NosAPI.ts | 48 ++++++++++++ packages/sim-consumidor-nos/index.ts | 29 ++++++- .../infrastructure/NosHttpClient.ts | 29 +++++++ .../infrastructure/NosJwtService.ts | 0 10 files changed, 241 insertions(+), 57 deletions(-) create mode 100644 packages/sim-consumidor-nos/aplication/SimNOS.router.ts rename packages/sim-consumidor-nos/config/env/{index.ts => env.ts} (91%) create mode 100644 packages/sim-consumidor-nos/domain/NosAPI.ts create mode 100644 packages/sim-consumidor-nos/infrastructure/NosHttpClient.ts create mode 100644 packages/sim-consumidor-nos/infrastructure/NosJwtService.ts diff --git a/packages/sim-consumidor-nos/.env b/packages/sim-consumidor-nos/.env index 597b9fa..3574f36 100644 --- a/packages/sim-consumidor-nos/.env +++ b/packages/sim-consumidor-nos/.env @@ -1,3 +1,6 @@ NOS_BASE_URL=localhost ENVIORMENT=development + +NOS_ACCESS_TOKEN=2YGhecTr4+uKbVKxaqBlk2edsrHA2OQY +NOS_BASE_URL=https://nosconnectcenter-api.iot-x.com diff --git a/packages/sim-consumidor-nos/aplication/SimNOS.controller.ts b/packages/sim-consumidor-nos/aplication/SimNOS.controller.ts index bfa4a0f..c7427aa 100644 --- a/packages/sim-consumidor-nos/aplication/SimNOS.controller.ts +++ b/packages/sim-consumidor-nos/aplication/SimNOS.controller.ts @@ -1,65 +1,34 @@ -import { EventBus } from "sim-shared/domain/EventBus.port.js"; import { ConsumeMessage } from "amqplib"; +import { SimNosUsecases } from "./SimNOS.usecases"; export class SimNosController { - private eventBus: EventBus; - private activationUseCases: any; - - private routes = new Map void>([ - ["activate", async () => { console.log("caso de uso activate") }], - ["pause", async () => { console.log("caso de uso pause") }], - ["cancel", async () => { console.log("caso de uso cancel") }], - ]) constructor( - eventBus: EventBus + uscases: SimNosUsecases ) { - this.eventBus = eventBus - - // No se si hay un sistema mejor - // convertor en const () => {} para conservar el contexto?? - this.recibeMsg = this.recibeMsg.bind(this) } - public async recibeMsg(msg: ConsumeMessage | null) { - if (!this.validateActivationMsg(msg)) { - throw new Error("Error consumiendo el mensaje no es valido") - } - - msg = msg! - - const msgParsed = JSON.parse(String(msg.content)) - const msgKey = msg.fields.routingKey.split(".") - const accion = msgKey[2] - - if (accion == undefined) { - console.error("La routingKey es incorrecta: " + accion) - this.eventBus.nack(msg) - return; - } - - if (this.routes.get(accion) == undefined) { - console.error("No hay una ruta definida para la accion") - this.eventBus.nack(msg) - return; - } - - try { - this.routes.get(accion)!() - } catch (err) { - console.log("Error procesando el mensaje") - this.eventBus.nack(msg) - } finally { - this.eventBus.ack(msg) + public activate() { + return async (msg: ConsumeMessage) => { + console.log("Evento activate ", msg) } } - /** - * TODO: - * - Loguear motivos de la no validacion - */ - private validateActivationMsg(msg: ConsumeMessage | null) { - if (msg == undefined) return false; - return true; + public suspend() { + return async (msg: ConsumeMessage) => { + console.log("Evento suspend ", msg) + } + } + + public terminate() { + return async (msg: ConsumeMessage) => { + console.log("Evento termiante ", msg) + } + } + + public preActivate() { + return async (msg: ConsumeMessage) => { + console.log("Evento preActivate ", msg) + } } } diff --git a/packages/sim-consumidor-nos/aplication/SimNOS.router.ts b/packages/sim-consumidor-nos/aplication/SimNOS.router.ts new file mode 100644 index 0000000..f99dfef --- /dev/null +++ b/packages/sim-consumidor-nos/aplication/SimNOS.router.ts @@ -0,0 +1,76 @@ +/** + * Dirige cada mensaje dependiendo de el tipo de acción que contenga + * Podría hacerse con varias colas, pero así se controla mejor que + * las operaciones se hagan de 1 en 1. + */ + +import { ConsumeMessage } from "amqplib"; +import { SimNosController } from "./SimNOS.controller.js"; +import { EventBus } from "sim-shared/domain/EventBus.port.js"; + +export class SimNosRouter { + private readonly routes: Map Promise)>; + + constructor( + private readonly simController: SimNosController, + private readonly eventBus: EventBus + ) { + this.routes = new Map([ + ["select", undefined], + ["activate", this.simController.activate()], + ["pause", this.simController.suspend()], + ["cancel", this.simController.terminate()], + //["reactivate", this.simController.reActivate()], + ["preActivate", this.simController.preActivate()] + ]); + } + + /** + * Enruta el mensaje a la acción correspondiente basándose en la routing key + * TODO: No estoy seguro que deba meter el nack aqui + * - De moemento el ack-nack se gestiona en los controller, por si acaso hay casos + * limite en + */ + public route = async (msg: ConsumeMessage | null): Promise => { + if (!msg) { + console.error("[Router] Mensaje vacío"); + return; + } + + const action = this.extractAction(msg); + + if (!action) { + console.error("[Router] La routing key no tiene una acción definida", msg.fields.routingKey); + this.eventBus.nack(msg) + return; + } + + const handler = this.routes.get(action); + + if (!handler) { + console.error(`[Router] La acción '${action}' no tiene un controlador asociado`); + this.eventBus.nack(msg) + return; + } + + try { + console.log("[Router] Ejecutando operación:", action); + + // El controlador devuelve una función (thunk) que debe ser ejecutada + const executeParams = await handler(msg); + + if (typeof executeParams === "function") { + await executeParams(); + } + + } catch (error) { + console.error(`[Router] Error al ejecutar la operación '${action}':`, error); + this.eventBus.nack(msg) + } + }; + + private extractAction(msg: ConsumeMessage): string | undefined { + // Se asume que la acción está en la tercera posición: domain.compañia.accion + return msg.fields.routingKey.split(".")[2]; + } +} diff --git a/packages/sim-consumidor-nos/aplication/SimNOS.usecases.ts b/packages/sim-consumidor-nos/aplication/SimNOS.usecases.ts index e69de29..0352c88 100644 --- a/packages/sim-consumidor-nos/aplication/SimNOS.usecases.ts +++ b/packages/sim-consumidor-nos/aplication/SimNOS.usecases.ts @@ -0,0 +1,35 @@ +/** + * Documentación de referencia: + * https://pelion-help.iot-x.com/nos/en-US/Content/API/APIReference/API%20Reference.htm?tocpath=_____7 + * + * TODO: + * - Control de errores más preciso + * + */ + +import { NosHttpClient } from "infrastructure/NosHttpClient"; + +export class SimNosUsecases { + constructor( + private httpClient: NosHttpClient + ) { + } + + public activate() { + const PATH = '/provisioning' + const PRODUCT_ID = 1330 // No se que es, preguntar a Ivan + + return async (args: { iccid: string }) => { + const data = { + productSetId: PRODUCT_ID + } + try { + const res = await this.httpClient.client.post(PATH, data) + } catch (e) { + console.error(e) + } + + } + } + +} diff --git a/packages/sim-consumidor-nos/config/env/index.ts b/packages/sim-consumidor-nos/config/env/env.ts similarity index 91% rename from packages/sim-consumidor-nos/config/env/index.ts rename to packages/sim-consumidor-nos/config/env/env.ts index 4aecfbb..ffbb536 100644 --- a/packages/sim-consumidor-nos/config/env/index.ts +++ b/packages/sim-consumidor-nos/config/env/env.ts @@ -31,6 +31,7 @@ export const env = { RABBITMQ_VHOST: String(process.env.RABBITMQ_VHOST), // ESPECIFICO NOS - NOS_BASE_URL: String(process.env.NOS_BASE_URL) + NOS_BASE_URL: String(process.env.NOS_BASE_URL), + NOS_ACCESS_TOKEN: String(process.env.NOS_ACCESS_TOKEN) }; diff --git a/packages/sim-consumidor-nos/config/eventBus.config.ts b/packages/sim-consumidor-nos/config/eventBus.config.ts index 5c84601..2757b6d 100644 --- a/packages/sim-consumidor-nos/config/eventBus.config.ts +++ b/packages/sim-consumidor-nos/config/eventBus.config.ts @@ -1,6 +1,6 @@ import { RabbitMQEventBus, RMQConnectionParams } from "sim-shared/infrastructure/RabbitMQEventBus.js" import { Channel } from "amqp-connection-manager" -import { env } from "./env/index.js" +import { env } from "./env/env.js" const rmqUser = env.RABBITMQ_USER const rmqPass = env.RABBITMQ_PASSWORD diff --git a/packages/sim-consumidor-nos/domain/NosAPI.ts b/packages/sim-consumidor-nos/domain/NosAPI.ts new file mode 100644 index 0000000..77bbc04 --- /dev/null +++ b/packages/sim-consumidor-nos/domain/NosAPI.ts @@ -0,0 +1,48 @@ +export namespace NosApi { + export type ActivateResponseOK = { + /** + The unique physical subscriber identifier: + Cellular - the ICCID + Non - IP - the EUI + Satellite - the IMEI + */ + physicalId: string, + + /** + example: 447000000001 + The unique network subscriber identifier: + + Cellular subscriber - the MSISDN + Non - IP subscriber - the Device EUI + Satellite subscriber - the Subscription ID + */ + subscriberId: string, + + /** + example: 9999 + If the subscriber uses Circuit Switched Data(CSD), this field displays its data number.If the subscriber does not use CSD, this field is null. + */ + dataNumber: string + + /** + example: 172.0.0.1 + The subscriber IP address. + */ + ip: string + + /** + example: 234150000000001 + The subscriber IMSI. + */ + imsi: string + } + + export type ActivateResponseError = { + error: { + children: string, + code: string, + messafe: string + } + + } +} diff --git a/packages/sim-consumidor-nos/index.ts b/packages/sim-consumidor-nos/index.ts index 3214b9d..ac6cfca 100644 --- a/packages/sim-consumidor-nos/index.ts +++ b/packages/sim-consumidor-nos/index.ts @@ -1,14 +1,37 @@ import { startRMQClient } from "#config/eventBus.config.js" +import { SimNosRouter } from "aplication/SimNOS.router.js" import { SimNosController } from "./aplication/SimNOS.controller.js" +import { SimNosUsecases } from "aplication/SimNOS.usecases.js" +import { NosHttpClient } from "infrastructure/NosHttpClient.js" +import { env } from "#config/env/env.js" + +const RMQ_QUEUE = "sim.nos" +const NOS_BASE_URL = env.NOS_BASE_URL async function startWorker() { + // Instancia de dependencias + const rmqClient = await startRMQClient() - const simController = new SimNosController( - rmqClient + const nosHttpClient = new NosHttpClient( + NOS_BASE_URL ) - rmqClient.consume("sim.nos", simController.recibeMsg) + const simUsecases = new SimNosUsecases( + nosHttpClient + ) + + const simController = new SimNosController( + simUsecases + ) + + const simRouter = new SimNosRouter( + simController, + rmqClient + + ) + + rmqClient.consume(RMQ_QUEUE, simRouter.route) } startWorker() diff --git a/packages/sim-consumidor-nos/infrastructure/NosHttpClient.ts b/packages/sim-consumidor-nos/infrastructure/NosHttpClient.ts new file mode 100644 index 0000000..35e24ab --- /dev/null +++ b/packages/sim-consumidor-nos/infrastructure/NosHttpClient.ts @@ -0,0 +1,29 @@ +import axios, { AxiosInstance } from "axios"; +import { env } from "#config/env/env.js" + +export class NosHttpClient { + public client: AxiosInstance; + + constructor( + private baseURL: string, + //private jwtManager: JWTProvider + ) { + this.client = axios.create({ + baseURL: baseURL + }) + + // Interceptor para los headers fijos + this.client.interceptors.request.use( + async (config) => { + // Configuracion especifica de NOS (El token simepre es el mismo?) + const token = env.NOS_ACCESS_TOKEN; + config.headers.Authorization = `Bearer ${token}` + config.headers.set("content-type", "application/json") + return config + }, + (error) => Promise.reject(error) + ) + } + + +} diff --git a/packages/sim-consumidor-nos/infrastructure/NosJwtService.ts b/packages/sim-consumidor-nos/infrastructure/NosJwtService.ts new file mode 100644 index 0000000..e69de29