Modelo de activacion de sim con token automatico

This commit is contained in:
2026-01-28 13:42:27 +01:00
parent ad217a277b
commit ca75f00e22
10 changed files with 184 additions and 38 deletions

View File

@@ -1,7 +1,62 @@
export class HTTPClient {
import { AxiosInstance, create as axiosCreate } from "axios"
import { JWTToken } from "../domain/JWT"
export type JWTProvider<T> = {
/** El servidor está solicitando un token nuevo o refrescando el actual*/
isRefreshing: boolean
authToken: JWTToken<T> | undefined
tryRefreshToken: () => Promise<JWTToken<T>>
getAccessToken: () => Promise<JWTToken<T>>
}
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?
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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!)
}

View File

@@ -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)
}
}
}

View File

@@ -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()
})

View File

@@ -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()

View File

@@ -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"

View File

@@ -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) {