diff --git a/packages/sim-consumidor-objenious/aplication/Sim.controller.ts b/packages/sim-consumidor-objenious/aplication/Sim.controller.ts index 7712730..ab3ec1a 100644 --- a/packages/sim-consumidor-objenious/aplication/Sim.controller.ts +++ b/packages/sim-consumidor-objenious/aplication/Sim.controller.ts @@ -4,7 +4,6 @@ import { SimUseCases } from "./Sim.usecases.js"; import { SimEvents } from "sim-shared/domain/SimEvents.js"; import { Result } from "sim-shared/domain/Result.js"; import { env } from "#config/env/index.js"; -import { s } from "node_modules/vitest/dist/chunks/reporters.d.CWXNI2jG.js"; /** * La clase usa generadores de funciones para mantener el contexto @@ -84,7 +83,7 @@ export class SimController { throw new Error("Error activando la sim, no se ha especificado la oferta") } - const resp = this.tryUseCase(msg, this.useCases.activate({ + const resp = await this.tryUseCase(msg, this.useCases.activate({ dueDate: this.genDueDate(DUE_DATE_SECONDS).toISOString(), customerAccountCode: env.OBJ_CUSTOMER_CODE, identifier: { @@ -118,7 +117,7 @@ export class SimController { } const iccid = msgData.payload.iccid - this.tryUseCase(msg, this.useCases.preActivate({ + const res = await this.tryUseCase(msg, this.useCases.preActivate({ dueDate: this.genDueDate(2 * 60).toISOString(), identifier: { identifierType: "ICCID", @@ -144,7 +143,7 @@ export class SimController { } const iccid = msgData.payload.iccid - this.tryUseCase(msg, this.useCases.suspend({ + const res = await this.tryUseCase(msg, this.useCases.suspend({ dueDate: this.genDueDate(2 * 60).toISOString(), identifier: { identifierType: "ICCID", @@ -169,7 +168,7 @@ export class SimController { } const iccid = msgData.payload.iccid - this.tryUseCase(msg, this.useCases.suspend({ + const res = await this.tryUseCase(msg, this.useCases.suspend({ dueDate: this.genDueDate(2 * 60).toISOString(), identifier: { identifierType: "ICCID", @@ -194,7 +193,7 @@ export class SimController { } const iccid = msgData.payload.iccid console.log("Mensaje procesado", String(msgData)) - this.tryUseCase(msg, this.useCases.terminate({ + const res = await this.tryUseCase(msg, this.useCases.terminate({ dueDate: this.genDueDate(2 * 60).toISOString(), identifier: { identifierType: "ICCID", diff --git a/packages/sim-consumidor-objenious/aplication/Sim.router.ts b/packages/sim-consumidor-objenious/aplication/Sim.router.ts index 26b4085..67d8266 100644 --- a/packages/sim-consumidor-objenious/aplication/Sim.router.ts +++ b/packages/sim-consumidor-objenious/aplication/Sim.router.ts @@ -27,6 +27,8 @@ export class SimRouter { /** * 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) { diff --git a/packages/sim-consumidor-objenious/aplication/Sim.usecases.ts b/packages/sim-consumidor-objenious/aplication/Sim.usecases.ts index a059153..b79cad0 100644 --- a/packages/sim-consumidor-objenious/aplication/Sim.usecases.ts +++ b/packages/sim-consumidor-objenious/aplication/Sim.usecases.ts @@ -3,6 +3,8 @@ import { HttpClient } from "sim-shared/infrastructure/HTTPClient.js" import { AxiosError } from "axios" import { Result } from "sim-shared/domain/Result.js" import { ObjeniousOperation, IOperationsRepository as OperationsRepositoryPort } from "sim-shared/domain/operationsRepository.port.js" +import assert from "node:assert" +import { OrderRepository } from "sim-shared/infrastructure/OrderRepository.js" // TODO: // - Pasar a un archivo de DTOs @@ -11,12 +13,16 @@ import { ObjeniousOperation, IOperationsRepository as OperationsRepositoryPort } export class SimUseCases { private readonly httpClient: HttpClient private readonly operationRepository: OperationsRepositoryPort + private readonly orderRepository: OrderRepository + constructor(args: { httpClient: HttpClient, - operationRepository: OperationsRepositoryPort + operationRepository: OperationsRepositoryPort, + orderRepository: OrderRepository }) { this.httpClient = args.httpClient this.operationRepository = args.operationRepository + this.orderRepository = args.orderRepository } private async logOperation(data: ObjeniousOperation) { @@ -25,6 +31,81 @@ export class SimUseCases { }) } + /** + * Garantiza el flujo de todos los casos de uso de: + * - Petición según la acción + * - Control de errores + * - Siempre devuelve un Result + * - Almacena la operacion en la base de datos + * - Actualiza el estado del order + * + * Necesita: + * - Mas control según el codigo de error + */ + private generateUseCase< + PAYLOAD, + RESPONSETYPE extends { requestId: string } + >(args: { + correlation_id?: string, + url: string, + operation: string, + operationPayload: PAYLOAD, + iccid: string + onError: (_: any) => void + // on code response?? + }): () => Promise> { + return async () => { + const req = this.httpClient.client.post(args.url, { + ...args.operationPayload + }) + + try { + const response = await req; + + if (response.status == 200) { + assert(response.data.requestId != undefined) + + // Creacion de la operacion inicial, antes de tener los datos + const operation: ObjeniousOperation = { + operation: args.operation, + iccids: String(args.iccid), + status: "noMassID", + request_id: response.data.requestId + } + + this.logOperation(operation) + .then().catch(e => console.error(e)) + + if (args.correlation_id != undefined) { + this.orderRepository.updateOrder({ + correlation_id: args.correlation_id!, + new_status: "running", + }) + .then(e => console.log("Order actualizado: ", e)) + .catch(e => console.error("Error actualizando order", args.correlation_id)) + } + + return >{ + error: undefined, + data: true + } + + } else { + return { + error: String(response.status), + data: undefined + } + } + } catch (error) { + console.error(`[Sim.usecase] Error ${args.operation}`, (error as AxiosError).response?.status) + return { + error: "Error general de la peticion", + data: undefined + } + } + } + } + public activate(activationData: ActivationData): () => Promise> { const OPERATION_URL = "/actions/activateLine" return async () => { @@ -51,8 +132,6 @@ export class SimUseCases { error: undefined, data: true } - - } else { // muy mejorable el control de errores return { @@ -117,14 +196,22 @@ export class SimUseCases { }) try { - const e = await req - console.log("Sim reactivada con exito", e.data) - return >{ - error: undefined, - data: true + const response = await req + + if (response.status == 200) { + console.log("[o] Sim solicitud de reactivacion ", response.data) + return >{ + error: undefined, + data: true + } + } else { + return { + error: String(response.status), + data: undefined + } } } catch (error) { - console.error("Error reactivacion", error) + console.error("[x] Error reactivacion", (error as AxiosError).response?.status) return >{ error: "Error reactivando la sim" + pauseData.identifier, data: undefined @@ -141,11 +228,26 @@ export class SimUseCases { }) try { - const e = await req - console.log("Sim pausada/suspendida con exito", e.data) - return >{ - error: undefined, - data: true + const response = await req + if (response.status == 200) { + console.log("[o] Sim pausada/suspendida con exito", response.data) + const operation: ObjeniousOperation = { + operation: "susupend", + iccids: String(suspendData.identifier.identifiers), + status: "noMassID", + request_id: response.data.requestId + } + this.logOperation(operation).then().catch(e => console.error(e)) + return >{ + error: undefined, + data: true + } + } else { + // muy mejorable el control de errores + return { + error: String(response.status), + data: undefined + } } } catch (error) { console.error("[Pausa Use case] Error pausa") @@ -164,18 +266,30 @@ export class SimUseCases { ...terminationData }) - // TODO: para cuando estemos listos. - throw new Error("Peticion no reversible desactivada de momento") - try { - const e = await req - console.log("Sim cancelada con exito", e.data) - return >{ - error: undefined, - data: true + const response = await req + + if (response.status == 200) { + console.log("[o] Sim solicitud de cancelacion con exito", response.data) + const operation: ObjeniousOperation = { + operation: "terminate", + iccids: String(terminationData.identifier.identifiers), + status: "noMassID", + request_id: response.data.requestId + } + + return >{ + error: undefined, + data: true + } + } else { + return { + error: String(response.status), + data: undefined + } } } catch (error) { - console.error("Error pausa", error) + console.error("[x ] Error en la solicitud de terminacion", error) return >{ error: "Error cancelando/terminate la sim" + terminationData.identifier, data: undefined diff --git a/packages/sim-entrada-eventos/aplication/Sim.usecases.ts b/packages/sim-entrada-eventos/aplication/Sim.usecases.ts index 41f3d10..4e307f9 100644 --- a/packages/sim-entrada-eventos/aplication/Sim.usecases.ts +++ b/packages/sim-entrada-eventos/aplication/Sim.usecases.ts @@ -23,6 +23,9 @@ export class SimUsecases { this.orderRepository = args.orderRepository } + /** + * Añade un id de mensaje (correlation_id en ala base de datos) + */ private addMessage_id(event: SimEvents.general): SimEvents.general & { headers: { message_id: string } } { const uuid = uuidv7() return { @@ -158,6 +161,7 @@ export class SimUsecases { } } const pauseWithId = this.addMessage_id(pauseEvent) + console.log("[d] Cancelation ", pauseWithId) await this.eventBus.publish([pauseWithId]) await this.saveOrder(pauseWithId) return pauseWithId diff --git a/packages/sim-objenious-cron/index.ts b/packages/sim-objenious-cron/index.ts index 6c4cedb..5b44e8f 100644 --- a/packages/sim-objenious-cron/index.ts +++ b/packages/sim-objenious-cron/index.ts @@ -27,7 +27,7 @@ async function startCron() { console.log("Updating...") await objTask.getPendingOperations() console.log("Update finished") - }, 60 * 1000) + }, 10 * 60 * 1000) /* const task = cron.createTask("* * * * *", async () => { } diff --git a/packages/sim-objenious-cron/tasks/check_objenious_request.ts b/packages/sim-objenious-cron/tasks/check_objenious_request.ts index c92f210..b38fa7a 100644 --- a/packages/sim-objenious-cron/tasks/check_objenious_request.ts +++ b/packages/sim-objenious-cron/tasks/check_objenious_request.ts @@ -1,4 +1,5 @@ import { env } from "#config/env/index.js"; +import { OrderRepository } from "sim-shared/infrastructure/OrderRepository.js"; import axios from "axios"; import { IOperationsRepository, Objenious, ObjeniousOperation, ObjeniousOperationChange, StatusEnum } from "sim-shared/domain/operationsRepository.port.js"; import { HttpClient } from "sim-shared/infrastructure/HTTPClient.js"; @@ -6,6 +7,7 @@ import { HttpClient } from "sim-shared/infrastructure/HTTPClient.js"; export class CheckObjeniousRequests { constructor( private readonly operationsRepository: IOperationsRepository, + private readonly orderRepository: OrderRepository, private readonly httpClient: HttpClient ) { } @@ -91,8 +93,10 @@ export class CheckObjeniousRequests { // 2. Se comprueba si ha habido un cambio de estado const { id, status, info } = data - if (status != originalAction.objenious_status) { + const hasStatusChanged = status != originalAction.objenious_status + if (hasStatusChanged) { console.log("[cron] Actualizando", originalAction.id, originalAction.iccids, status) + /** Status convertido al que se usa en la aplicacion */ const uorStatus = this.mapStatus(status) const updateData: ObjeniousOperationChange = { operation_id: originalAction.id!, @@ -102,27 +106,33 @@ export class CheckObjeniousRequests { previous_status: originalAction.status } - originalAction.status = uorStatus; - originalAction.objenious_status = status; - originalAction.last_change_date = new Date().toISOString() - originalAction.end_date = originalAction.last_change_date - console.log(" ----> Status", uorStatus) + const updatedAction = structuredClone(originalAction) + + updatedAction.status = uorStatus; + updatedAction.objenious_status = status; + updatedAction.last_change_date = new Date().toISOString() + updatedAction.end_date = originalAction.last_change_date + if (uorStatus /*== "finished"*/) { console.log(" ****> Status", uorStatus) + if (uorStatus != "finished") { + console.error("!!! Notificando estado no finished") + } const targetIccids = originalAction.iccids const lineData = await this.getLineData(targetIccids) - console.log("lineData", lineData.content[0]) + console.log("[i] lineData", lineData.content[0]) const msisdn = lineData.content[0].identifier.msisdn this.notifyFinalization({ ...originalAction, msisdn }) + // TODO la accion no siempre es activacion! .then(e => { - console.log("Notificada la activacion de ", originalAction.iccids) + console.log("[o] Notificada la activacion de ", originalAction.iccids) }) .catch(e => { - console.error("Error enviando la activacion de ", originalAction) + console.error("[x] Error enviando la activacion de ", originalAction) console.error(e) }) } @@ -132,12 +142,12 @@ export class CheckObjeniousRequests { } try { - console.log("Subiendo un update") + console.log("[i] Subiendo un update") console.log(updateData) await this.operationsRepository.updateOperation(updateData) updated.push(originalAction) } catch (e) { - console.error("Error actualizando el estado de ", originalAction, e) + console.error("[x] Error actualizando el estado de ", originalAction, e) return; } } diff --git a/packages/sim-shared/domain/Order.ts b/packages/sim-shared/domain/Order.ts index 32b4d24..2ff6e73 100644 --- a/packages/sim-shared/domain/Order.ts +++ b/packages/sim-shared/domain/Order.ts @@ -61,3 +61,14 @@ export type CreateOrderDTO = Pick< OrderTracking, // Aqui realmente no importan los campos 'correlation_id' | 'exchange' | 'routing_key' | 'order_type' | 'payload' | 'webhook_host' | 'webhook_endpoint' >; + +export type UpdateOrderDTO = + ( + { id: number, correlation_id?: never } | + { id?: never, correlation_id: string } + ) + & + { + new_status: OrderStatus, + reason?: string + } diff --git a/packages/sim-shared/domain/Result.ts b/packages/sim-shared/domain/Result.ts index e030747..66e8e4b 100644 --- a/packages/sim-shared/domain/Result.ts +++ b/packages/sim-shared/domain/Result.ts @@ -4,11 +4,11 @@ export type Result = { error: E, - data: undefined + data?: undefined } | { - error: undefined, + error?: undefined, data: D } diff --git a/packages/sim-shared/infrastructure/OrderRepository.ts b/packages/sim-shared/infrastructure/OrderRepository.ts index 86ddb14..e0f9a6a 100644 --- a/packages/sim-shared/infrastructure/OrderRepository.ts +++ b/packages/sim-shared/infrastructure/OrderRepository.ts @@ -2,9 +2,11 @@ * TODO: Usar */ import { PoolClient, QueryResult, QueryResultRow } from "pg"; -import { CreateOrderDTO, OrderTracking } from "../domain/Order.js"; +import { CreateOrderDTO, OrderTracking, UpdateOrderDTO } from "../domain/Order.js"; import { Result } from "../domain/Result.js"; import { PgClient } from "./PgClient.js"; +import assert from "node:assert"; +import { error } from "node:console"; /** * Agrupa todas las operaciones de *Order*. @@ -164,25 +166,35 @@ export class OrderRepository { return result } + + /** * Actualizacion "correcta" del estado de un order */ - public async updateOrder(args: { - id: number, - new_status: string, - reason: string - }) { + public async updateOrder(args: UpdateOrderDTO): Promise>> { + // XOR id o correlation_id + assert((args.id != undefined) != (args.correlation_id != undefined)) const client = await this.pgClient.connect(); await client.query('BEGIN'); + const idType = ('id' in args) ? "correlation_id" : "id" + const idValue = (args.id != undefined) ? args.id : args.correlation_id + // 1. Se consulta la order de base const qCurrentOrder = ` SELECT * FROM order_tracking - WHERE id = $1 + WHERE ${idType} = $1 ` - const vCurrentOrder = [args.id] + const vCurrentOrder = [idValue] const currentOrderResult = await this.getFirst(client.query>(qCurrentOrder, vCurrentOrder)) + const orderId = currentOrderResult.data?.id + + if (orderId == undefined) { + return { + error: "El order a actualizar no existe " + idType + ": " + idValue + } + } if (currentOrderResult.error != undefined) { await client.query("ROLLBACK") @@ -201,7 +213,7 @@ export class OrderRepository { WHERE id = $1 RETURNING id, status, update_date; ` - const vOrderTracking = [args.id, args.new_status] + const vOrderTracking = [orderId, args.new_status] const updatedOrderResult = await this.getFirst( client.query<{ id: number, status: string, update_date: string }>(uOrderTracking, vOrderTracking) )