From ca75f00e22c013f1c9c9bfa41c2df013b18ddae8 Mon Sep 17 00:00:00 2001 From: Alvar San Martin Date: Wed, 28 Jan 2026 13:42:27 +0100 Subject: [PATCH] Modelo de activacion de sim con token automatico --- packages/shared/infrastructure/HTTPClient.ts | 61 ++++++++++++++++++- .../shared/infrastructure/RabbitMQEventBus.ts | 18 ++++-- .../aplication/JWT.service.ts | 10 ++- .../aplication/SimActivation.controller.ts | 26 ++++++-- .../aplication/SimActivation.usecase.ts | 41 +++++++++++++ .../{eventBusConfig.ts => eventBus.config.ts} | 0 .../config/httpClient.config.ts | 12 ++++ packages/sim-consumidor-objenious/index.ts | 16 ++++- .../sim-consumidor-objenious/tsconfig.json | 4 +- .../aplication/Sim.controller.ts | 34 +++++------ 10 files changed, 184 insertions(+), 38 deletions(-) create mode 100644 packages/sim-consumidor-objenious/aplication/SimActivation.usecase.ts rename packages/sim-consumidor-objenious/config/{eventBusConfig.ts => eventBus.config.ts} (100%) create mode 100644 packages/sim-consumidor-objenious/config/httpClient.config.ts diff --git a/packages/shared/infrastructure/HTTPClient.ts b/packages/shared/infrastructure/HTTPClient.ts index f8c164e..749f302 100644 --- a/packages/shared/infrastructure/HTTPClient.ts +++ b/packages/shared/infrastructure/HTTPClient.ts @@ -1,7 +1,62 @@ -export class HTTPClient { +import { AxiosInstance, create as axiosCreate } from "axios" +import { JWTToken } from "../domain/JWT" + +export type JWTProvider = { + /** El servidor está solicitando un token nuevo o refrescando el actual*/ + isRefreshing: boolean + authToken: JWTToken | undefined + tryRefreshToken: () => Promise> + getAccessToken: () => Promise> +} + +export class HttpClient { + + public client: AxiosInstance + private jwtManager: JWTProvider<{}> + + constructor(args: { + baseURL: string, + headers: Object, + jwtManager: JWTProvider<{}> // todo: asociar el tipo de token + }) { + this.client = axiosCreate({ + ...args + }) + + this.jwtManager = args.jwtManager + + + this.client.interceptors.request.use( + async (config) => { + // Idealmente estas condiciones no deberian de darse si mantenemos el + // token valido de forma preventiva + const token = this.jwtManager.authToken?.rawToken + + if (token == undefined) throw new Error("No se ha obtenido el token para la peticion") + + config.headers.Authorization = `Bearer ${this.jwtManager.authToken!.rawToken}` + console.log("request completa", config.headers, config.data) + return config; + }, + (error) => Promise.reject(error) + ) + + // No deberia usarlos de momento + this.client.interceptors.response.use( + (response) => { + return response; + }, + async (error) => { + // TODO: Esta parte no tiene tipos, hay que asegurar el error + const req = error.config + console.error("[http] Error en la respuesta ", error) + if (error.response?.status == 401) { + this.jwtManager.getAccessToken() + } + } + ) - constructor() { - // JWT? } + } diff --git a/packages/shared/infrastructure/RabbitMQEventBus.ts b/packages/shared/infrastructure/RabbitMQEventBus.ts index 7d24999..66ccbfd 100644 --- a/packages/shared/infrastructure/RabbitMQEventBus.ts +++ b/packages/shared/infrastructure/RabbitMQEventBus.ts @@ -13,8 +13,6 @@ export type RMQConnectionParams = { secure: boolean } -const RETRY_DELAY = 1000 -const PREFETCH_LIMIT = 1 export class RabbitMQEventBus implements EventBus { private buildStructure?: (chan: Channel) => void @@ -126,11 +124,20 @@ export class RabbitMQEventBus implements EventBus { } protected async createConfirmChannel() { - if (this.connection == undefined) throw new Error("Intentando crear un canal sin una conexion") - const channel = this.connection?.createChannel({ + if (this.connection == undefined) throw new Error("[RMQ] Intentando crear un canal sin una conexion") + const channel = this.connection.createChannel({ json: true, setup: (channel: Channel) => { - if (this.buildStructure != undefined) this.buildStructure(channel) + // Exchanges comunes a todos + channel.assertExchange("sim.exchange", "topic", { durable: true }) + channel.assertExchange("sim.dlx", "topic", { durable: true }) + + // Estructuras propias de cada servicio + if (this.buildStructure != undefined) { + this.buildStructure(channel) + } else { + console.warn("[i] Se ha creado un canal sin garantizar que exista la/s cola/s que se van a usar") + } }, }) @@ -144,6 +151,5 @@ export class RabbitMQEventBus implements EventBus { }); return channel; - } } diff --git a/packages/sim-consumidor-objenious/aplication/JWT.service.ts b/packages/sim-consumidor-objenious/aplication/JWT.service.ts index 82f12ad..276a483 100644 --- a/packages/sim-consumidor-objenious/aplication/JWT.service.ts +++ b/packages/sim-consumidor-objenious/aplication/JWT.service.ts @@ -77,10 +77,13 @@ function addIATHeaders(authHeaders: Object) { * El servicio gestiona un par de tokens auth - refresh para las * operaciones de Objenious. * Se puede partir de tokens existentes. + * + * Debe tener un cliente HTTP propio para que no le afecten los + * interceptores, sino puede haber bucles de refresco de token */ export class JWTService { - // Igual no deberia mantener estado - private authToken?: JWTToken<{}> + public isRefreshing: boolean = false; + public authToken: JWTToken<{}> | undefined private refreshToken?: JWTToken<{}> constructor(args?: { @@ -132,6 +135,7 @@ export class JWTService { } ) + this.isRefreshing = true; let res; try { res = (await req).data as TokensRequestResponse; @@ -142,6 +146,8 @@ export class JWTService { const errorString = "No se ha podido conseguir el token de acceso de OBJENIOUS" console.error(errorString, (e as AxiosError).response?.data) throw new Error(errorString) + } finally { + this.isRefreshing = false; } } diff --git a/packages/sim-consumidor-objenious/aplication/SimActivation.controller.ts b/packages/sim-consumidor-objenious/aplication/SimActivation.controller.ts index 9072fb1..17d84b8 100644 --- a/packages/sim-consumidor-objenious/aplication/SimActivation.controller.ts +++ b/packages/sim-consumidor-objenious/aplication/SimActivation.controller.ts @@ -1,28 +1,42 @@ import { EventBus } from "#shared/domain/EventBus.port"; import { ConsumeMessage } from "amqplib"; +import { SimActivationUseCase } from "./SimActivation.usecase"; export class SimActivationController { private eventBus: EventBus; - private activationUseCases: any; + private useCases: { + activation: SimActivationUseCase + } constructor( - eventBus: EventBus + eventBus: EventBus, + useCases: { + activation: SimActivationUseCase + } ) { this.eventBus = eventBus - + this.useCases = useCases // No se si hay un sistema mejor // convertor en const () => {} para conservar el contexto?? this.activateSim = this.activateSim.bind(this) } - public activateSim(msg: ConsumeMessage | null) { + public async activateSim(msg: ConsumeMessage | null) { if (!this.validateActivationMsg(msg)) { throw new Error("Error consumiendo el mensaje no es valido") } - console.log("mensaje procesado", String(msg?.content)) + const msgData = Buffer.from(JSON.parse(msg?.content.toString("utf-8") || "{}").data) + console.log("Mensaje procesado", String(msgData)) // Caso de uso de activaciones - // + await this.useCases.activation.run({ + dueDate: new Date().toString(), + identifier: { + identifierType: "ICCID", + identifiers: ["1234"] + } + }) + // TODO: comprobar el resultado de la opreacion this.eventBus.ack(msg!) } diff --git a/packages/sim-consumidor-objenious/aplication/SimActivation.usecase.ts b/packages/sim-consumidor-objenious/aplication/SimActivation.usecase.ts new file mode 100644 index 0000000..89ac540 --- /dev/null +++ b/packages/sim-consumidor-objenious/aplication/SimActivation.usecase.ts @@ -0,0 +1,41 @@ +import { HttpClient } from "#shared/infrastructure/HTTPClient" + +// TODO: Pasar a un archivo de DTOs + +export type ActivationData = { + dueDate: string, // isodate + filter?: {} // no se si hace falta + identifier: { + identifiers: string[] + identifierType: "IMSI" | "MSISDN" | "REFERENCE" | "ICCID" | "IMEI" + } +} + +export type ResponseError = { + error: string, + detail: Object[] +} + +export class SimActivationUseCase { + private httpClient: HttpClient + + constructor(args: { + + httpClient: HttpClient + }) { + this.httpClient = args.httpClient + } + + public async run(activationData: ActivationData,) { + const req = this.httpClient.client.post("/actions/preactivate", { + ...activationData + }) + + try { + const e = await req + console.log("Activacion con exito", e.data) + } catch (error) { + console.error("Error activando ", error) + } + } +} diff --git a/packages/sim-consumidor-objenious/config/eventBusConfig.ts b/packages/sim-consumidor-objenious/config/eventBus.config.ts similarity index 100% rename from packages/sim-consumidor-objenious/config/eventBusConfig.ts rename to packages/sim-consumidor-objenious/config/eventBus.config.ts diff --git a/packages/sim-consumidor-objenious/config/httpClient.config.ts b/packages/sim-consumidor-objenious/config/httpClient.config.ts new file mode 100644 index 0000000..a346b28 --- /dev/null +++ b/packages/sim-consumidor-objenious/config/httpClient.config.ts @@ -0,0 +1,12 @@ +import { JWTService } from "aplication/JWT.service" +import { HttpClient } from "#shared/infrastructure/HTTPClient" + +// TODO: mover a shared/infrastructure para usar en el resto de servicios + +export const httpInstance = new HttpClient({ + baseURL: "https://api-getway.objenious.com/ws/", + headers: { + "content-type": "application/x-www-form-urlencoded" + }, + jwtManager: new JWTService() +}) diff --git a/packages/sim-consumidor-objenious/index.ts b/packages/sim-consumidor-objenious/index.ts index 6687b93..9080c6f 100644 --- a/packages/sim-consumidor-objenious/index.ts +++ b/packages/sim-consumidor-objenious/index.ts @@ -1,12 +1,22 @@ -import { startRMQClient } from "#config/eventBusConfig" +import { startRMQClient } from "#config/eventBus.config" +import { httpInstance } from "#config/httpClient.config" import { SimActivationController } from "aplication/SimActivation.controller" +import { SimActivationUseCase } from "aplication/SimActivation.usecase" async function startWorker() { const rmqClient = await startRMQClient() - const simActivationController = new SimActivationController(rmqClient) + const httpClient = httpInstance + const simActivationController = new SimActivationController( + rmqClient, + { + activation: new SimActivationUseCase({ + httpClient: httpClient + }) + } + ) - rmqClient.consume("sim.activations", simActivationController.activateSim) + rmqClient.consume("sim.objenious", simActivationController.activateSim) } startWorker() diff --git a/packages/sim-consumidor-objenious/tsconfig.json b/packages/sim-consumidor-objenious/tsconfig.json index 5abd440..2a42c7d 100644 --- a/packages/sim-consumidor-objenious/tsconfig.json +++ b/packages/sim-consumidor-objenious/tsconfig.json @@ -32,7 +32,9 @@ ], "include": [ "**/*.ts", - "src/**/*.d.ts" + "src/**/*.d.ts", + "../shared/aplication/JWT.service.ts", + "../shared/aplication/JWT.service.test.ts" ], "files": [ "index.ts" diff --git a/packages/sim-entrada-eventos/aplication/Sim.controller.ts b/packages/sim-entrada-eventos/aplication/Sim.controller.ts index 4ddd631..ae34354 100644 --- a/packages/sim-entrada-eventos/aplication/Sim.controller.ts +++ b/packages/sim-entrada-eventos/aplication/Sim.controller.ts @@ -55,6 +55,7 @@ export class SimController { msg: "Error general de activation" } }).send() + return; } } @@ -67,6 +68,10 @@ export class SimController { try { await this.simUseCases.cancelation({ iccid }) + res.status(200).json({ + iccid: iccid, + operation: "cancelation" + }) } catch (err) { console.error("Error cancelando la sim ", req.body) res.status(500).json({ @@ -76,10 +81,6 @@ export class SimController { }) } - res.status(200).json({ - iccid: iccid, - operation: "cancelation" - }) } async pause(req: Request, res: Response) { @@ -92,6 +93,10 @@ export class SimController { try { await this.simUseCases.cancelation({ iccid }) + res.status(200).json({ + iccid: iccid, + operation: "cancelation" + }) } catch (err) { console.error("Error pausando la sim ", req.body) res.status(500).json({ @@ -100,11 +105,6 @@ export class SimController { } }) } - - res.status(200).json({ - iccid: iccid, - operation: "cancelation" - }) } async free(req: Request, res: Response) { @@ -117,6 +117,10 @@ export class SimController { try { await this.simUseCases.cancelation({ iccid }) + res.status(200).json({ + iccid: iccid, + operation: "liberacion" + }) } catch (err) { console.error("Error liberando la sim ", req.body) res.status(500).json({ @@ -126,10 +130,6 @@ export class SimController { }) } - res.status(200).json({ - iccid: iccid, - operation: "liberacion" - }) } async save(req: Request, res: Response) { @@ -142,6 +142,10 @@ export class SimController { try { await this.simUseCases.cancelation({ iccid }) + res.status(200).json({ + iccid: iccid, + operation: "cancelation" + }) } catch (err) { console.error("Error activando la sim ", req.body) res.status(500).json({ @@ -151,10 +155,6 @@ export class SimController { }) } - res.status(200).json({ - iccid: iccid, - operation: "cancelation" - }) } private validateBody(body: any, res: Response) {