import { EventBus } from "sim-shared/domain/EventBus.port.js"; import { ConsumeMessage } from "amqplib"; import { SimUseCases } from "./Sim.usecases.js"; import { SimEvents } from "sim-shared/domain/SimEvents.js"; import { Result } from "sim-shared/domain/Result.js"; /** * La clase usa generadores de funciones para mantener el contexto * el proceso se hace en 2 partes: * Controlador - handlers -> Router -> Ejecucion * */ export class SimController { private eventBus: EventBus; private useCases: SimUseCases constructor( eventBus: EventBus, useCases: SimUseCases ) { this.eventBus = eventBus this.useCases = useCases } private decodeMsg(msg: ConsumeMessage): object | undefined { if (msg.content == undefined) { console.warn('[Sim.controller] Mensaje vacío'); return undefined; } try { // Convertir el Buffer a String (UTF-8) const contentJson = JSON.parse(Buffer.from(msg.content).toString('utf8')) return contentJson; } catch (error) { console.error('Error al decodificar JSON:', error); // Aquí podrías decidir devolver el string crudo o null return undefined; } } /** * Metodo general de lanzar un caso de uso con un mensaje, si el caso de uso es exitoso * se ACK el mesaje, si hay algún error se NACK. * TODO: * - Se podrian hacer genericos los parametros */ private async tryUseCase(msg: ConsumeMessage, usecase: () => Promise>) { try { const result = await usecase() if (result.error == undefined) { await this.eventBus.ack(msg) return result } else { console.error("Error general procesando el caso de uso", result.error) this.eventBus.nack(msg) } } catch (e) { console.error("Error general procesando el caso de uso") this.eventBus.nack(msg) } } public activate() { const DUE_DATE_SECONDS = 2 * 60 return async (msg: ConsumeMessage) => { let msgData; try { msgData = this.validateMsg(msg) as SimEvents.activation } catch (e) { throw new Error("Error consumiendo el mensaje no es valido" + String(e)) } const iccid = msgData.payload.iccid const offer = msgData.payload.offer if (offer == undefined) { this.eventBus.nack(msg) throw new Error("Error activando la sim, no se ha especificado la oferta") } const resp = await this.tryUseCase(msg, this.useCases.activate({ correlation_id: msgData.headers?.message_id, dueDate: this.genDueDate(DUE_DATE_SECONDS).toISOString(), customerAccountCode: "9.49411.10", identifier: { identifierType: "ICCID", identifiers: [iccid] }, offer: { code: offer, services: [] } })) // TODO: // - Crear un registro de operación // - Si ha salido bien id de operación -> webhook? // - Si ha salido mal notificar solo cuando se manda a dlx ?? } } public preActivate() { return async (msg: ConsumeMessage) => { let msgData; try { msgData = this.validateMsg(msg) as SimEvents.pause } catch (e) { throw new Error("Error de preactivacion consumiendo el mensaje no es valido" + String(e)) } if (msgData == undefined) { return Promise.reject("Mensaje invalido") } const iccid = msgData.payload.iccid const res = await this.tryUseCase(msg, this.useCases.preActivate({ correlation_id: msgData.headers?.message_id, dueDate: this.genDueDate(2 * 60).toISOString(), identifier: { identifierType: "ICCID", identifiers: [iccid] } })) } } public reActivate() { return async (msg: ConsumeMessage) => { let msgData; try { msgData = this.validateMsg(msg) as SimEvents.pause } catch (e) { throw new Error("Error de reactivacion consumiendo el mensaje no es valido" + String(e)) } if (msgData == undefined) { return Promise.reject("Mensaje invalido") } const iccid = msgData.payload.iccid const res = await this.tryUseCase(msg, this.useCases.reActivate({ correlation_id: msgData.headers?.message_id, dueDate: this.genDueDate(2 * 60).toISOString(), identifier: { identifierType: "ICCID", identifiers: [iccid] } })) } } /** * Lo mismo que pause */ public suspend() { return async (msg: ConsumeMessage) => { let msgData; try { msgData = this.validateMsg(msg) as SimEvents.pause } catch (e) { throw new Error("Error de suspension consumiendo el mensaje no es valido" + String(e)) } if (msgData == undefined) { return Promise.reject("Mensaje invalido") } const iccid = msgData.payload.iccid const res = await this.tryUseCase(msg, this.useCases.suspend({ correlation_id: msgData.headers?.message_id, dueDate: this.genDueDate(2 * 60).toISOString(), identifier: { identifierType: "ICCID", identifiers: [iccid] // Por algún motivo solo he puesto un iccd por identifier } })) } } public terminate() { return async (msg: ConsumeMessage) => { let msgData; try { msgData = this.validateMsg(msg) as SimEvents.pause } catch (e) { throw new Error("Error consumiendo el mensaje no es valido" + String(e)) } if (msgData == undefined) { return Promise.reject("Mensaje invalido") } const iccid = msgData.payload.iccid console.log("Mensaje procesado", msgData) const res = await this.tryUseCase(msg, this.useCases.terminate({ correlation_id: msgData.headers?.message_id, dueDate: this.genDueDate(2 * 60).toISOString(), identifier: { identifierType: "ICCID", identifiers: [iccid] } })) } } /** * TODO: * - Loguear motivos de la no validacion * - Añadir validadores inyectables */ private validateMsg(msg: ConsumeMessage | null) { if (msg == undefined) return false; const msgData = this.decodeMsg(msg) as SimEvents.general if (msgData == undefined || msgData.payload == undefined) throw new Error("Mensaje invalido") return msgData; } private genDueDate(secondsDue: number) { const now = Date.now() + secondsDue * 1000 const dueDate = new Date(now) return dueDate } }