From 9ec127433dbe92d5f9476eb0d44841e434d596f0 Mon Sep 17 00:00:00 2001 From: Alvar San Martin Date: Thu, 23 Apr 2026 13:18:50 +0200 Subject: [PATCH 01/27] Template alai --- docs/sim-alai/environments/local.yml | 7 + docs/sim-alai/environments/prod.yml | 7 + docs/sim-alai/opencollection.yml | 10 + docs/sim-api/Activate.bru | 2 +- docs/sim-api/Cancel.bru | 2 +- packages/sim-consumidor-alai/.env | 6 + .../aplication/SimAlai.controller.ts | 178 ++++++++++++++++++ .../aplication/SimAlai.router.ts | 79 ++++++++ .../aplication/SimAlai.usecases.ts | 156 +++++++++++++++ .../aplication/SslService.ts | 51 +++++ .../aplication/httpValidators.ts | 39 ++++ .../sim-consumidor-alai/config/env/env.ts | 40 ++++ .../config/eventBus.config.ts | 72 +++++++ .../config/postgreConfig.ts | 18 ++ .../sim-consumidor-alai/domain/AlaiAPI.ts | 1 + packages/sim-consumidor-alai/index.ts | 55 ++++++ .../infrastructure/AlaiHttpClient.ts | 41 ++++ .../infrastructure/AlaiJwtService.ts | 0 .../infrastructure/AlaiRepository.test.ts | 0 .../infrastructure/AlaiRepository.ts | 177 +++++++++++++++++ packages/sim-consumidor-alai/package.json | 71 +++++++ packages/sim-consumidor-alai/readme.md | 5 + packages/sim-consumidor-alai/tsconfig.json | 41 ++++ 23 files changed, 1056 insertions(+), 2 deletions(-) create mode 100644 docs/sim-alai/environments/local.yml create mode 100644 docs/sim-alai/environments/prod.yml create mode 100644 docs/sim-alai/opencollection.yml create mode 100644 packages/sim-consumidor-alai/.env create mode 100644 packages/sim-consumidor-alai/aplication/SimAlai.controller.ts create mode 100644 packages/sim-consumidor-alai/aplication/SimAlai.router.ts create mode 100644 packages/sim-consumidor-alai/aplication/SimAlai.usecases.ts create mode 100644 packages/sim-consumidor-alai/aplication/SslService.ts create mode 100644 packages/sim-consumidor-alai/aplication/httpValidators.ts create mode 100644 packages/sim-consumidor-alai/config/env/env.ts create mode 100644 packages/sim-consumidor-alai/config/eventBus.config.ts create mode 100644 packages/sim-consumidor-alai/config/postgreConfig.ts create mode 100644 packages/sim-consumidor-alai/domain/AlaiAPI.ts create mode 100644 packages/sim-consumidor-alai/index.ts create mode 100644 packages/sim-consumidor-alai/infrastructure/AlaiHttpClient.ts create mode 100644 packages/sim-consumidor-alai/infrastructure/AlaiJwtService.ts create mode 100644 packages/sim-consumidor-alai/infrastructure/AlaiRepository.test.ts create mode 100644 packages/sim-consumidor-alai/infrastructure/AlaiRepository.ts create mode 100644 packages/sim-consumidor-alai/package.json create mode 100644 packages/sim-consumidor-alai/readme.md create mode 100644 packages/sim-consumidor-alai/tsconfig.json diff --git a/docs/sim-alai/environments/local.yml b/docs/sim-alai/environments/local.yml new file mode 100644 index 0000000..678d231 --- /dev/null +++ b/docs/sim-alai/environments/local.yml @@ -0,0 +1,7 @@ +name: local +color: "#2E8A54" +variables: + - name: baseurl + value: http://localhost:3001 + - secret: true + name: token diff --git a/docs/sim-alai/environments/prod.yml b/docs/sim-alai/environments/prod.yml new file mode 100644 index 0000000..080fcff --- /dev/null +++ b/docs/sim-alai/environments/prod.yml @@ -0,0 +1,7 @@ +name: prod +color: "#CE4F3B" +variables: + - name: baseurl + value: https://nosconnectcenter-api.iot-x.com + - secret: true + name: token diff --git a/docs/sim-alai/opencollection.yml b/docs/sim-alai/opencollection.yml new file mode 100644 index 0000000..297aed6 --- /dev/null +++ b/docs/sim-alai/opencollection.yml @@ -0,0 +1,10 @@ +opencollection: 1.0.0 + +info: + name: sim-alai +bundled: false +extensions: + bruno: + ignore: + - node_modules + - .git diff --git a/docs/sim-api/Activate.bru b/docs/sim-api/Activate.bru index 9b392e4..bbc6a60 100644 --- a/docs/sim-api/Activate.bru +++ b/docs/sim-api/Activate.bru @@ -11,7 +11,7 @@ post { } body:form-urlencoded { - iccid: 8935103196306448300 + iccid: 8933201125065156057 offer: SAVEFAMILY1 } diff --git a/docs/sim-api/Cancel.bru b/docs/sim-api/Cancel.bru index 5632418..3e31352 100644 --- a/docs/sim-api/Cancel.bru +++ b/docs/sim-api/Cancel.bru @@ -11,7 +11,7 @@ post { } body:form-urlencoded { - iccid: 8933201125068887054 + iccid: 8933201125068889894 } settings { diff --git a/packages/sim-consumidor-alai/.env b/packages/sim-consumidor-alai/.env new file mode 100644 index 0000000..1a67319 --- /dev/null +++ b/packages/sim-consumidor-alai/.env @@ -0,0 +1,6 @@ +APP_PORT=3002 +APP_HOST="0.0.0.0" + +ENVIORMENT=development + +ALAI_CERTIFICATES_DIR=./certificates/ diff --git a/packages/sim-consumidor-alai/aplication/SimAlai.controller.ts b/packages/sim-consumidor-alai/aplication/SimAlai.controller.ts new file mode 100644 index 0000000..1c4aac4 --- /dev/null +++ b/packages/sim-consumidor-alai/aplication/SimAlai.controller.ts @@ -0,0 +1,178 @@ +import { ConsumeMessage } from "amqplib"; +import { Request, Response } from "express" +import { SimNosUsecases } from "./SimNOS.usecases.js"; +import { EventBus } from "sim-shared/domain/EventBus.port.js"; +import { Result } from "sim-shared/domain/Result.js"; +import { SimEvents } from "sim-shared/domain/SimEvents.js"; +import { iccidValidator } from "./httpValidators.js"; + +export class SimAlaiController { + + constructor( + private uscases: SimNosUsecases, + private eventBus: EventBus, + ) { + } + + 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 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); + console.error(Buffer.from(msg.content).toString(("utf8"))) + // Aquí podrías decidir devolver el string crudo o null + return undefined; + } + } + + /** + * Metodo duplicado se puede generalizar la a una clase sharedController con las funciones basicas + */ + private async tryUseCase + (msg: ConsumeMessage, usecase: () => Promise>): Promise> { + try { + const result = await usecase() + if (result.error == undefined) { + await this.eventBus.ack(msg) + return result + } else { + console.error("Error procesando el caso de uso (NOS)", result.error) + this.eventBus.nack(msg) + return result + } + } catch (e) { + console.error("Error general procesando el caso de uso (NOS)") + this.eventBus.nack(msg) + return { + error: String(e) + } + } + } + + public activate() { + return async (msg: ConsumeMessage) => { + console.log("[i] Evento activate ", msg.fields) + const data = this.validateMsg(msg) as SimEvents.activation + const iccid = data.payload.iccid + const correlation_id = data.headers?.message_id + const res = await this.tryUseCase(msg, this.uscases.activate({ + iccid: iccid, + correlation_id: correlation_id + })) + + return res; + } + } + + public suspend() { + return async (msg: ConsumeMessage) => { + console.log("Evento suspend ", msg.fields) + const data = this.validateMsg(msg) as SimEvents.suspend + const iccid = data.payload.iccid + const correlation_id = data.headers?.message_id + const res = await this.tryUseCase(msg, this.uscases.suspend({ + iccid: iccid, + correlation_id: correlation_id + })) + + return res; + } + } + + public terminate() { + return async (msg: ConsumeMessage) => { + console.log("Evento termiante no soportado ", msg.fields) + } + } + + public reActivate() { + return async (msg: ConsumeMessage) => { + console.log("Evento reActivate ", msg.fields) + const data = this.validateMsg(msg) as SimEvents.reActivation + const iccid = data.payload.iccid + const correlation_id = data.headers?.message_id + const res = await this.tryUseCase(msg, this.uscases.reactivate({ + iccid: iccid, + correlation_id: correlation_id + })) + + return res; + } + } + + /** + * Select especificamente por REST para evitar pasar por las colas. + * La respuesta es instantanea no se tiene que registrar como operación. + */ + public selectREST() { + return async (req: Request, res: Response) => { + const { query } = req + const body = { iccid: query.iccid as string } + console.log("Evento select", body) + const validateBody = iccidValidator.validate(body); + + if (validateBody.error != undefined) { + res.status(402).json(validateBody) + return; + } + + const iccid: string | string[] = body.iccid + + if (Array.isArray(iccid)) { + // TODO: Automatizar la paginacion + //const usecaseRes = this.uscases.selectMany({ iccid }) + } else { + const usecaseRes = await this.uscases.selectOne({ iccid }) + if (usecaseRes.error != undefined) { + res.status(500).json(usecaseRes) + return; + } else { + res.send(usecaseRes.data) + return; + } + } + + res.status(200).json(validateBody) + } + } + + + public selectPageREST() { + return async (req: Request, res: Response) => { + const { offset, limit, filter, orderBy } = req.query + const params = { + offset: (offset != undefined) ? Number(offset) : undefined, + limit: (limit != undefined) ? Number(limit) : undefined, + filter: (filter != undefined) ? String(filter) : undefined, + orderBy: (orderBy != undefined) ? String(orderBy) : undefined + } + + const usecaseRes = await this.uscases.selectPage(params) + + if (usecaseRes.error != undefined) { + res.status(500).json(usecaseRes) + return; + } else { + res.status(200).send(usecaseRes.data) + return; + } + } + } +} + + diff --git a/packages/sim-consumidor-alai/aplication/SimAlai.router.ts b/packages/sim-consumidor-alai/aplication/SimAlai.router.ts new file mode 100644 index 0000000..01fd517 --- /dev/null +++ b/packages/sim-consumidor-alai/aplication/SimAlai.router.ts @@ -0,0 +1,79 @@ +/** + * 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 { EventBus } from "sim-shared/domain/EventBus.port.js"; +import { Result } from "sim-shared/domain/Result.js"; +import { SimAlaiController } from "./SimAlai.controller.js"; + +type FuncType = ((m: ConsumeMessage) => Promise>) + +export class SimNosRouter { + private readonly routes: Map; + + constructor( + private readonly simController: SimAlaiController, + private readonly eventBus: EventBus + ) { + this.routes = new Map([ + //["select", undefined], + ["activate", this.simController.activate()], + ["pause", this.simController.suspend()], + ["reactivate", this.simController.reActivate()], + //["cancel", this.simController.terminate()], + //["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 = handler(msg); + + if (typeof executeParams === "function") { + const res = 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-alai/aplication/SimAlai.usecases.ts b/packages/sim-consumidor-alai/aplication/SimAlai.usecases.ts new file mode 100644 index 0000000..96707ee --- /dev/null +++ b/packages/sim-consumidor-alai/aplication/SimAlai.usecases.ts @@ -0,0 +1,156 @@ +/** + * Documentación de referencia: + * https://pelion-help.iot-x.com/nos/en-US/Content/API/APIReference/API%20Reference.htm?tocpath=_____7 + * + * En nos el correlation_id ya va a ser obligatorio en todos los mensajes + * + * TODO: + * - Control de errores más preciso + * + */ +import { AlaiHttpClient } from "#infrastructure/NosHttpClient.js"; +import { NosRepository } from "#infrastructure/AlaiRepository.js"; +import { ErrorOrderDTO, FinishOrderDTO, UpdateOrderDTO } from "sim-shared/domain/Order.js"; +import { Result } from "sim-shared/domain/Result.js"; +import { OrderRepository } from "sim-shared/infrastructure/OrderRepository.js"; + +export class SimAlaiUsecases { + constructor( + private httpClient: NosHttpClient, + private nosRepository: NosRepository, + private orderRepository: OrderRepository + ) { + } + + private async setRunning(correlation_id: string) { + // En NOS el updateOrder se hace con el correlation_id que viene en la cabecera del + // mensaje consumido + const updateData: UpdateOrderDTO = { + new_status: "running", + correlation_id: correlation_id + } + const order = await this.orderRepository.updateOrder(updateData) + return order + } + + private async setFinished(correlation_id: string) { + // En NOS el updateOrder se hace con el correlation_id que viene en la cabecera del + // mensaje consumido + const updateData: FinishOrderDTO = { + correlation_id: correlation_id + } + const order = await this.orderRepository.finishOrder(updateData) + return order + } + + private async setFailed(correlation_id: string, reason: string, detail?: string) { + // En NOS el updateOrder se hace con el correlation_id que viene en la cabecera del + // mensaje consumido + const updateData: ErrorOrderDTO = { + status: "failed", + correlation_id: correlation_id, + reason: reason, + error: reason, + stackTrace: detail + } + + console.log("SET FAILED DATA:", updateData) + const order = await this.orderRepository.errorOrder(updateData) + console.log("SET FAILED RES:", order) + return order + } + + public usecaseTemplate( + func: (_: T) => Promise>, + args: T, + correlation_id?: string | undefined + ) { + return async () => { + // Operacion pending -> running + if (correlation_id != undefined) + this.setRunning(correlation_id) + .then() + .catch(e => console.error("Error actualizando el order", e)) + + try { + const res = await func(args) + + if (res.error != undefined) { + console.log("Error peticion: ", res, correlation_id) + if (correlation_id != undefined) + this.setFailed(correlation_id, res.error) + .then(e => console.log("failed", e)) + .catch(e => console.error(e)) + return res; + } else { + if (correlation_id != undefined) + this.setFinished(correlation_id).then() + return res; + } + + } catch (e) { + if (correlation_id != undefined) + this.setFailed(correlation_id, "Error general de operacion de SIM (NOS) ", String(e)).then() + return { + error: "Error general de operacion de SIM (NOS) " + String(e) + } + } + + } + } + + public activate(args: { + iccid: string, + correlation_id?: string + }) { + return this.usecaseTemplate( + (args) => this.nosRepository.activateSim(args), args.iccid, args.correlation_id) + } + + public suspend(args: { + iccid: string, + correlation_id?: string + }) { + return this.usecaseTemplate( + (args) => this.nosRepository.bar(args), args.iccid, args.correlation_id) + } + + public reactivate(args: { + iccid: string, + correlation_id?: string + }) { + return this.usecaseTemplate( + (args) => this.nosRepository.unbar(args), args.iccid, args.correlation_id) + } + + public terminate(args: { iccid: string }) { + throw new Error("No hay termination para NOS") + } + + /* Importante: Las operaciones de lectua no dejan registro en orders */ + + public async selectOne(args: { + iccid: string + }) { + const res = await this.nosRepository.getLineInfo(args.iccid) + return res + } + + public async selectPage(args: { + offset?: number, + limit?: number, + filter?: string, + orderBy?: string + }) { + const res = await this.nosRepository.getLinePage(args) + return res + } + + /** + public selectMany(args: { + iccid: string[] + }) { + return {} + } +*/ +} diff --git a/packages/sim-consumidor-alai/aplication/SslService.ts b/packages/sim-consumidor-alai/aplication/SslService.ts new file mode 100644 index 0000000..7846367 --- /dev/null +++ b/packages/sim-consumidor-alai/aplication/SslService.ts @@ -0,0 +1,51 @@ +import fs from "fs"; +import path from "path"; +import { Result } from "sim-shared/domain/Result.js"; + +export type P12Cert = { + cainfo: string, + p12cert: string +} + +export type SSLCert = { + cainfo: string, + sslcert: string, + keypem: string +} + +export class SSLCertificateLoader { + + constructor( + private certificatesDir: string, + ) { + } + + public loadCertificatesP12(caFile: string, certFile: string): Result { + try { + const cainfo = fs.readFileSync(path.resolve(this.certificatesDir, caFile)).toString(); + const p12cert = fs.readFileSync(path.resolve(this.certificatesDir, certFile)).toString(); + return { data: { cainfo, p12cert } }; + } catch (e) { + console.error("[x] Error cargando los certificados P12", e) + return { + error: String(e) + } + } + } + + public loadCertificatesSSL(caFile: string, certFile: string, keyFile: string): Result { + try { + const cainfo = fs.readFileSync(path.resolve(this.certificatesDir, caFile)).toString(); + const sslcert = fs.readFileSync(path.resolve(this.certificatesDir, certFile), { encoding: null }).toString(); + const keypem = fs.readFileSync(path.resolve(this.certificatesDir, keyFile), { encoding: null }).toString(); + return { data: { cainfo, sslcert, keypem } }; + } catch (e) { + console.error("[x] Error cargando los certificados SSL", e) + return { + error: String(e) + } + } + } + +} + diff --git a/packages/sim-consumidor-alai/aplication/httpValidators.ts b/packages/sim-consumidor-alai/aplication/httpValidators.ts new file mode 100644 index 0000000..b452272 --- /dev/null +++ b/packages/sim-consumidor-alai/aplication/httpValidators.ts @@ -0,0 +1,39 @@ +import { BodyValidator, Validator } from "sim-shared/aplication/BodyValidator.js"; + +const iccidNotNull = >{ + field: "iccid", + errorMsg: "El iccid no está definido", + validationFunc: (a: { iccid: unknown }) => { + return (a.iccid != null && a.iccid != undefined) + } +} + +const iccidValueOrArray = >{ + field: "iccid", + errorMsg: "El iccid debe de ser un único valor o una lista", + validationFunc: (a: { iccid: unknown }) => { + return (typeof a.iccid == "string" || Array.isArray(a.iccid)) + } +} + +const iccidLongitudValidator = >{ + field: "iccid", + errorMsg: "La longitud del iccid/s es incorrecta debera ser de 19 caracteres", + validationFunc: (a: { iccid: string | string[] }) => { + if (Array.isArray(a.iccid)) { + const res = (a.iccid as string[]).filter(e => e.length != 19) + if (res.length > 0) return false; + } else { + return (a.iccid as string).length == 19 + } + }, +} + +export const iccidValidator = new BodyValidator<{ iccid: string | string[] }>( + [ + iccidNotNull, + iccidValueOrArray, + iccidLongitudValidator, + ] +) + diff --git a/packages/sim-consumidor-alai/config/env/env.ts b/packages/sim-consumidor-alai/config/env/env.ts new file mode 100644 index 0000000..80f0352 --- /dev/null +++ b/packages/sim-consumidor-alai/config/env/env.ts @@ -0,0 +1,40 @@ +import { loadEnvFile } from "node:process"; +import path from "node:path"; + +try { + loadEnvFile(path.join("./.env")) // base +} catch (e) { + console.error("Error cargando el .env desde ./.env") +} +try { + loadEnvFile(path.join("../../.env")) // Global +} catch (e) { + console.error("Error cargando el .env desde ../../.env") +} + +export const env = { + ENVIRONMENT: process.env.ENVIORMENT, + POSTGRES_USER: process.env.POSTGRES_USER, + POSTGRES_PASSWORD: process.env.POSTGRES_PASSWORD, + POSTGRES_PORT: process.env.POSTGRES_PORT, + POSTGRES_HOST: process.env.POSTGRES_HOST, + POSTGRES_DATABASE: process.env.POSTGRES_DATABASE, + RABBITMQ_HOST: String(process.env.RABBITMQ_HOST ?? "localhost"), + RABBITMQ_USER: String(process.env.RABBITMQ_USER ?? "test"), + RABBITMQ_PASSWORD: String(process.env.RABBITMQ_PASSWORD ?? "test"), + RABBITMQ_EXCHANGE: String(process.env.RABBITMQ_EXCHANGE ?? "/"), + RABBITMQ_PORT: parseInt(process.env.RABBITMQ_PORT ?? "5672"), + RABBITMQ_MODULENAME: process.env.MODULENAME, + RABBITMQ_TTL: process.env.RABBITMQ_TTL, + RABBITMQ_SECURE: process.env.RABBITMQ_SECURE, + RABBITMQ_RETRY_INTERVAL: process.env.RABBITMQ_INTERVAL, + RABBITMQ_VHOST: String(process.env.RABBITMQ_VHOST), + + APP_PORT: Number(process.env.APP_PORT), + APP_HOST: String(process.env.APP_HOST), + + // ESPECIFICO NOS + NOS_BASE_URL: String(process.env.NOS_BASE_URL), + NOS_ACCESS_TOKEN: String(process.env.NOS_ACCESS_TOKEN) +}; + diff --git a/packages/sim-consumidor-alai/config/eventBus.config.ts b/packages/sim-consumidor-alai/config/eventBus.config.ts new file mode 100644 index 0000000..c794b99 --- /dev/null +++ b/packages/sim-consumidor-alai/config/eventBus.config.ts @@ -0,0 +1,72 @@ +import { RabbitMQEventBus, RMQConnectionParams } from "sim-shared/infrastructure/RabbitMQEventBus.js" +import { Channel } from "amqp-connection-manager" +import { env } from "./env/env.js" + +const rmqUser = env.RABBITMQ_USER +const rmqPass = env.RABBITMQ_PASSWORD +const rmqHost = env.RABBITMQ_HOST +const rmqPort = Number(env.RABBITMQ_PORT) +const rmqSecure = false +const rmqVhost = env.RABBITMQ_VHOST + +export const rmqConnOptions = { + username: rmqUser, + password: rmqPass, + vhost: rmqVhost, + hostname: rmqHost, + port: rmqPort, + secure: rmqSecure, +} + + +const BASE_ALAI_KEY = "sim.alai.#" +const QUEUES = { + MAIN: "sim.alai", + DLX: "sim.alai.dlx", + DELAY: "sim.alai.delayed", +} + +const EXCHANGES = { + MAIN: "sim.exchange", + DLX: "sim.ex.alai.dlx", + DEL: "sim.ex.alai.delayed" +} + +export const rabbitmqEventBus = new RabbitMQEventBus({ + connectionParams: rmqConnOptions, + buildStructure: buildQueues, + maxRetry: 2, + delayedExchange: EXCHANGES.DEL, + dlxExchange: EXCHANGES.DLX +}) + +async function buildQueues(channel: Channel) { + + const DELAY = 10 * 1000 + + await channel.assertExchange(EXCHANGES.DEL, "topic") + await channel.assertExchange(EXCHANGES.DLX, "topic") + await channel.assertExchange(EXCHANGES.MAIN, "topic") + + await channel.assertQueue(QUEUES.MAIN) + await channel.assertQueue(QUEUES.DLX) + await channel.assertQueue(QUEUES.DELAY, { + durable: true, + arguments: { + 'x-message-ttl': DELAY, + 'x-dead-letter-exchange': EXCHANGES.MAIN, + } + }) + + // Cola dead-letter + await channel.bindQueue(QUEUES.DLX, EXCHANGES.DLX, BASE_ALAI_KEY) + // Cola delay + await channel.bindQueue(QUEUES.DELAY, EXCHANGES.DEL, BASE_ALAI_KEY) + // Cola nos -> main exchange + await channel.bindQueue(QUEUES.MAIN, EXCHANGES.MAIN, BASE_ALAI_KEY) +} + +export async function startRMQClient() { + await rabbitmqEventBus.connect() + return rabbitmqEventBus +} diff --git a/packages/sim-consumidor-alai/config/postgreConfig.ts b/packages/sim-consumidor-alai/config/postgreConfig.ts new file mode 100644 index 0000000..9c67604 --- /dev/null +++ b/packages/sim-consumidor-alai/config/postgreConfig.ts @@ -0,0 +1,18 @@ +import { Pool, QueryResult } from 'pg'; +import { PgClient } from 'sim-shared/infrastructure/PgClient.js' +import { env } from './env/env.js'; + +// Configuracion de la conexion a la BDD, deberia ser la +// Misma para todos los servicios pero hasta que se unifique todo +// se hace una por servicio. +export const pgPool = new Pool({ + user: env.POSTGRES_USER, + host: env.POSTGRES_HOST, + database: env.POSTGRES_DATABASE, + password: env.POSTGRES_PASSWORD, + port: Number(env.POSTGRES_PORT) || 5433, +}); + +export const pgClient = new PgClient({ + pool: pgPool +}) diff --git a/packages/sim-consumidor-alai/domain/AlaiAPI.ts b/packages/sim-consumidor-alai/domain/AlaiAPI.ts new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/packages/sim-consumidor-alai/domain/AlaiAPI.ts @@ -0,0 +1 @@ + diff --git a/packages/sim-consumidor-alai/index.ts b/packages/sim-consumidor-alai/index.ts new file mode 100644 index 0000000..d2a4527 --- /dev/null +++ b/packages/sim-consumidor-alai/index.ts @@ -0,0 +1,55 @@ +import express from "express" +import cors from 'cors'; +import { env } from "#config/env/env.js" +import { pgClient } from "#config/postgreConfig.js"; +import { startRMQClient } from "#config/eventBus.config.js"; +import { SimNosRouter } from "#aplication/SimAlai.router.js"; + +const RMQ_QUEUE = "sim.alai" +const NOS_BASE_URL = env.NOS_BASE_URL +const PORT = env.APP_PORT +const HOSTNAME = env.APP_HOST + +async function startWorker() { + // Instancia de dependencias + + const rmqClient = await startRMQClient() + + const simRouter = new SimNosRouter( + simController, + rmqClient + ) + + // RMQ + rmqClient.consume(RMQ_QUEUE, simRouter.route) + .then(() => console.log("Cliente rmq creado con exito")) + .catch(e => console.error("Error conectando con RABBITMQ", e)) + + // Express + const app = express() + app.use(cors()); + app.use(express.json()); + app.use(express.urlencoded({ extended: true })); + + app.get("/select", simController.selectREST()) + app.get("/selectPage", simController.selectPageREST()) + + app.listen(PORT, HOSTNAME, (e) => { + if (e == undefined) { + console.log("[o] Servidor iniciado en el puerto %d", PORT) + } else { + console.error("Error express ", e) + } + }) + +} + +startWorker() + .then(e => { + console.log("[o] Worker de SIM de NOS iniciado") + }) + .catch(e => { + console.log("[x] Error iniciando worker de SIM de NOS") + }) + +export default {} diff --git a/packages/sim-consumidor-alai/infrastructure/AlaiHttpClient.ts b/packages/sim-consumidor-alai/infrastructure/AlaiHttpClient.ts new file mode 100644 index 0000000..5e12de7 --- /dev/null +++ b/packages/sim-consumidor-alai/infrastructure/AlaiHttpClient.ts @@ -0,0 +1,41 @@ +import axios, { AxiosInstance } from "axios"; +import { env } from "#config/env/env.js" + +export class AlaiHttpClient { + 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) + ) + } + + get post() { + return this.client.post + } + + get patch() { + return this.client.patch + } + + get get() { + return this.client.get + } + + +} diff --git a/packages/sim-consumidor-alai/infrastructure/AlaiJwtService.ts b/packages/sim-consumidor-alai/infrastructure/AlaiJwtService.ts new file mode 100644 index 0000000..e69de29 diff --git a/packages/sim-consumidor-alai/infrastructure/AlaiRepository.test.ts b/packages/sim-consumidor-alai/infrastructure/AlaiRepository.test.ts new file mode 100644 index 0000000..e69de29 diff --git a/packages/sim-consumidor-alai/infrastructure/AlaiRepository.ts b/packages/sim-consumidor-alai/infrastructure/AlaiRepository.ts new file mode 100644 index 0000000..9f55269 --- /dev/null +++ b/packages/sim-consumidor-alai/infrastructure/AlaiRepository.ts @@ -0,0 +1,177 @@ +import { Result } from "sim-shared/domain/Result.js"; +import { NosHttpClient } from "./AlaiHttpClient.js"; +import { NosApi } from "#domain/AlaiAPI.js"; +import axios, { AxiosError, AxiosResponse } from "axios"; + +export class NosRepository { + constructor( + private httpClient: NosHttpClient + ) { + } + + /** + * E => Tipo de error + * T => Tipo de dato para cod 200 + * + * TODO: + * - Mejor gestion de los errores + * - E no se aplica todavia por no hacer la transformacion del error + */ + private async manageNosRequest(promise: Promise>): Promise> { + try { + const res = await promise + return { + data: res.data + } + } catch (e) { + if (axios.isAxiosError(e)) { + const error = e as AxiosError + return { + error: error.code + " : " + String(error.response?.statusText) + } + } else { + return { + error: String(e) + } + } + } + } + + public async getLineInfo(iccid: string): Promise> { + const PATH = "/subscribers/" + iccid + console.log("PAth", PATH) + const lineRequest = this.httpClient.get(PATH) + const lineResponse = await this.manageNosRequest(lineRequest) + + if (lineResponse.error != undefined) { + return lineResponse + } else { + return { + data: lineResponse.data.content + } + } + } + + /** + * El metodo de NOS de paginar las lineas + * maximo por pagina 100, default 25 + * no devuelve el offset ni el numero de elementos restantes + * hay que llevar la cuenta + */ + public async getLinePage(args: { + limit?: number, + offset?: number, + filter?: string, + orderBy?: string + }): Promise> { + const PATH = "/subscribers" + + const LIMIT = 100 + const options = { + limit: args.limit ?? LIMIT, + offset: args.offset ?? 0, + filter: args.filter, + orderBy: args.orderBy + } + + const pageRequest = this.httpClient.get(PATH, { + params: options + }) + + const pageResponse = await this.manageNosRequest(pageRequest) + if (pageResponse.error != undefined) { + return pageResponse + } else { + return { + data: pageResponse.data.content + } + } + } + + public async getLinesInfo(iccid: string[]) /*Promise>*/ { + throw new Error("NOS no permite buscar iccid en bulk, se puede hacer un apaño pero está en proceso") + const PATH = "/subscribers" + const LIMIT = 100 + + const steps = Math.ceil(iccid.length / LIMIT) + const options = { + limit: LIMIT, + offset: 0, + } + + const req = this.httpClient.post(PATH) + const resp = await this.manageNosRequest(req) + + if (resp.error != undefined) { + return resp + } else { + return { + //@ts-expect-error + data: resp.data.content + } + } + } + + public async activateSim(iccid: string): Promise> { + const PATH = '/provisioning' + const PRODUCT_ID = 1330 // No se que es, preguntar a Ivan + const data = { + productSetId: PRODUCT_ID + } + + const req = this.httpClient.post(PATH, data) + const resp = await this.manageNosRequest(req) + + if (resp.error != undefined) { + return resp + } else { + return { + data: resp.data.content + } + } + } + + /** + * "A bar is a service provisioning action that results in a subscriber being blocked from accessing an operator's network. The bar remains in place until the operator is sent an unbar request." + * Se entiende que un "bar" es una suspension temporal + */ + public async bar(iccid: string) { + const PATH = `/subscribers/${iccid}/products` + const data = { + product: "BAR DN TOTAL", + action: "enable" + } + + const req = this.httpClient.patch(PATH, data) + const resp = await this.manageNosRequest(req) + + if (resp.error != undefined) { + return resp + } else { + return { + data: resp.data.content + } + } + } + + public async unbar(iccid: string) { + const PATH = `/subscribers/${iccid}/products` + const data = { + product: "BAR DN TOTAL", + action: "disable" + } + + const req = this.httpClient.patch(PATH, data) + const resp = await this.manageNosRequest(req) + + if (resp.error != undefined) { + return resp + } else { + return { + data: resp.data.content + } + } + + } + +} diff --git a/packages/sim-consumidor-alai/package.json b/packages/sim-consumidor-alai/package.json new file mode 100644 index 0000000..3005079 --- /dev/null +++ b/packages/sim-consumidor-alai/package.json @@ -0,0 +1,71 @@ +{ + "name": "sim-consumidor-alai", + "type": "module", + "description": "consumidor generico de eventos de alai", + "main": "index.ts", + "imports": { + "#config/*.js": { + "types": "./config/*.ts", + "default": "./config/*.js" + }, + "#config/*": { + "types": "./config/*.ts", + "default": "./config/*.js" + }, + "#infrastructure/*.js": { + "types": "./infrastructure/*.ts", + "default": "./infrastructure/*.js" + }, + "#infrastructure/*": { + "types": "./infrastructure/*.ts", + "default": "./infrastructure/*.js" + }, + "#domain/*.js": { + "types": "./domain/*.ts", + "default": "./domain/*.js" + }, + "#domain/*": { + "types": "./domain/*.ts", + "default": "./domain/*.js" + }, + "#aplication/*.js": { + "types": "./aplication/*.ts", + "default": "./aplication/*.js" + }, + "#aplication/*": { + "types": "./aplication/*.ts", + "default": "./aplication/*.js" + } + }, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "build": "yarn tsc --project tsconfig.json && yarn tsc-alias && cp package.json ../../dist/packages/sim-consumidor-nos/", + "esbuild": "esbuild index.ts --platform=node", + "start": "node ../../dist/packages/sim-consumidor-nos/index.js", + "dev": "tsx watch index.ts" + }, + "author": "", + "license": "ISC", + "packageManager": "yarn@4.12.0", + "dependencies": { + "@tsconfig/node22": "*", + "amqplib": "^0.10.9", + "cors": "*", + "dotenv": "*", + "express": "*", + "sim-shared": "sim-shared:*", + "typescript": "*" + }, + "devDependencies": { + "@types/amqplib": "^0.10.8", + "@types/cors": "*", + "@types/express": "*", + "@types/node": "*", + "@types/supertest": "*", + "prettier": "*", + "supertest": "*", + "tsc-alias": "^1.8.16", + "tsx": "*", + "vitest": "*" + } +} diff --git a/packages/sim-consumidor-alai/readme.md b/packages/sim-consumidor-alai/readme.md new file mode 100644 index 0000000..750d04f --- /dev/null +++ b/packages/sim-consumidor-alai/readme.md @@ -0,0 +1,5 @@ +# Alai + +## Particularidades de las operaciones de Alai + +TODO: Copiar de obsidian diff --git a/packages/sim-consumidor-alai/tsconfig.json b/packages/sim-consumidor-alai/tsconfig.json new file mode 100644 index 0000000..119ff00 --- /dev/null +++ b/packages/sim-consumidor-alai/tsconfig.json @@ -0,0 +1,41 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "../../dist", + "rootDir": "../../", + "paths": { + "#config/*": [ + "./config/*" + ], + "#infrastructure/*": [ + "./infrastructure/*" + ], + "#domain/*": [ + "./domain/*" + ], + "#aplication/*": [ + "./aplication/*" + ], + "config/*": [ + "./config/*" + ], + "infrastructure/*": [ + "./infrastructure/*" + ], + "domain/*": [ + "./domain/*" + ] + } + }, + "exclude": [ + "node_modules" + ], + "include": [ + "**/*.ts", + "**/*.d.ts", + "../../packages/sim-shared/**/*.ts" + ], + "files": [ + "index.ts" + ] +} \ No newline at end of file From 4e02ea021d78c912eb59c8755a99f59705a54484 Mon Sep 17 00:00:00 2001 From: Alvar San Martin Date: Mon, 27 Apr 2026 09:33:55 +0200 Subject: [PATCH 02/27] Docs orders --- .../database/migrations/1.0.0_orders.sql | 1 - docs/sim-alai/Login.yml | 51 +++++++++++++ docs/sim-alai/New Order.yml | 15 ++++ docs/sim-alai/certificates/alai_cert.p12 | Bin 0 -> 6183 bytes docs/sim-alai/environments/local.yml | 2 +- docs/sim-alai/environments/prod.yml | 10 ++- docs/sim-alai/opencollection.yml | 16 ++++ docs/sim-api/Activate.bru | 70 ++++++++++++++++- docs/sim-api/Activation Email.bru | 2 +- docs/sim-api/Cancel.bru | 2 +- docs/sim-api/Docs.bru | 2 +- docs/sim-api/Health.bru | 2 +- .../{ => Orders}/Get pending orders.bru | 2 +- docs/sim-api/{ => Orders}/Order by id.bru | 2 +- .../{ => Orders}/Orders by message_id.bru | 6 +- docs/sim-api/Orders/folder.bru | 44 +++++++++++ docs/sim-api/Pause.bru | 2 +- docs/sim-api/Preactivate.bru | 2 +- docs/sim-api/ReActivate.bru | 2 +- docs/sim-api/collection.bru | 3 +- docs/sim-api/test proxy.bru | 2 +- .../aplication/SimAlai.controller.ts | 8 +- ...saccess_alaisecure_com_cert_client_new.p12 | Bin 0 -> 6183 bytes packages/sim-entrada-eventos/README.md | 0 .../aplication/Order.controller.ts | 13 +++- .../aplication/Order.usecases.ts | 7 ++ .../aplication/httpValidators.ts | 8 +- .../infrastructure/orderRoutes.http.ts | 8 +- .../infrastructure/simconnectionsRoutes.ts | 3 +- packages/sim-shared/domain/Order.ts | 17 ++++- .../infrastructure/OrderRepository.test.ts | 15 +++- .../infrastructure/OrderRepository.ts | 72 +++++++++++++++++- 32 files changed, 349 insertions(+), 40 deletions(-) create mode 100644 docs/sim-alai/Login.yml create mode 100644 docs/sim-alai/New Order.yml create mode 100644 docs/sim-alai/certificates/alai_cert.p12 rename docs/sim-api/{ => Orders}/Get pending orders.bru (94%) rename docs/sim-api/{ => Orders}/Order by id.bru (68%) rename docs/sim-api/{ => Orders}/Orders by message_id.bru (50%) create mode 100644 docs/sim-api/Orders/folder.bru create mode 100644 packages/sim-consumidor-alai/certificates/wsaccess_alaisecure_com_cert_client_new.p12 create mode 100644 packages/sim-entrada-eventos/README.md diff --git a/deployment/database/migrations/1.0.0_orders.sql b/deployment/database/migrations/1.0.0_orders.sql index 912710a..3278e9f 100644 --- a/deployment/database/migrations/1.0.0_orders.sql +++ b/deployment/database/migrations/1.0.0_orders.sql @@ -24,7 +24,6 @@ CREATE TABLE IF NOT EXISTS order_tracking ( payload JSONB, -- Duda si es optimo guardar la copia, es útil en caso de fallo -- Campos de reintentos? - status order_status NOT NULL DEFAULT 'pending', retry_count INT DEFAULT 0, error_message TEXT, -- Razón del fallo diff --git a/docs/sim-alai/Login.yml b/docs/sim-alai/Login.yml new file mode 100644 index 0000000..0256ae9 --- /dev/null +++ b/docs/sim-alai/Login.yml @@ -0,0 +1,51 @@ +info: + name: Login + type: http + seq: 2 + +http: + method: POST + url: "{{baseurl}}/v1/auth/login" + body: + type: json + data: |- + { + "username": "{{username}}", + "password": "{{password}}", + "brandID": "{{brandId}}" + } + auth: inherit + +runtime: + scripts: + - type: after-response + code: |- + const data = res.getBody(); + + if (data.staus != 200) { + console.error("Error de login: ", data) + return 1; + } + + if (data && data.accessToken) { + + bru.setEnvVar("alai_token", data.accessToken); + + if (data.tokenType) { + bru.setEnvVar("alai_token_type", data.tokenType); + } + + console.log("Token guardado correctamente"); + } else { + console.error("No se pudo encontrar el accessToken en la respuesta"); + } + +settings: + encodeUrl: true + timeout: 0 + followRedirects: true + maxRedirects: 5 + +docs: |- + Necesita un certificado p12 (PFX) y la contraseña asociada para efectuar la operacion. + Collection Settings => ClientCertificates => Add Certificate diff --git a/docs/sim-alai/New Order.yml b/docs/sim-alai/New Order.yml new file mode 100644 index 0000000..e342c26 --- /dev/null +++ b/docs/sim-alai/New Order.yml @@ -0,0 +1,15 @@ +info: + name: New Order + type: http + seq: 3 + +http: + method: POST + url: "{{baseurl}}/v1/order" + auth: inherit + +settings: + encodeUrl: true + timeout: 0 + followRedirects: true + maxRedirects: 5 diff --git a/docs/sim-alai/certificates/alai_cert.p12 b/docs/sim-alai/certificates/alai_cert.p12 new file mode 100644 index 0000000000000000000000000000000000000000..86217c6474c8032bdc1485aba51f4c088c4a789d GIT binary patch literal 6183 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 NVTw>a_djL&{{ZHzxsm_? literal 0 HcmV?d00001 diff --git a/docs/sim-alai/environments/local.yml b/docs/sim-alai/environments/local.yml index 678d231..db39cbc 100644 --- a/docs/sim-alai/environments/local.yml +++ b/docs/sim-alai/environments/local.yml @@ -2,6 +2,6 @@ name: local color: "#2E8A54" variables: - name: baseurl - value: http://localhost:3001 + value: http://localhost:3002 - secret: true name: token diff --git a/docs/sim-alai/environments/prod.yml b/docs/sim-alai/environments/prod.yml index 080fcff..58204fa 100644 --- a/docs/sim-alai/environments/prod.yml +++ b/docs/sim-alai/environments/prod.yml @@ -2,6 +2,12 @@ name: prod color: "#CE4F3B" variables: - name: baseurl - value: https://nosconnectcenter-api.iot-x.com + value: https://wsaccess.alaisecure.com/bssrest + - name: username + value: palomaibanez + - name: password + value: palomaibanez123 - secret: true - name: token + name: certPasswd + - name: brandId + value: savefamily diff --git a/docs/sim-alai/opencollection.yml b/docs/sim-alai/opencollection.yml index 297aed6..6d6bccb 100644 --- a/docs/sim-alai/opencollection.yml +++ b/docs/sim-alai/opencollection.yml @@ -2,6 +2,22 @@ opencollection: 1.0.0 info: name: sim-alai +config: + proxy: + inherit: true + config: + protocol: http + hostname: "" + port: "" + auth: + username: "" + password: "" + bypassProxy: "" + clientCertificates: + - domain: wsaccess.alaisecure.com + type: pkcs12 + pkcs12FilePath: certificates\alai_cert.p12 + passphrase: iHaaek+zyzWz6cH6rg== bundled: false extensions: bruno: diff --git a/docs/sim-api/Activate.bru b/docs/sim-api/Activate.bru index bbc6a60..46a58b0 100644 --- a/docs/sim-api/Activate.bru +++ b/docs/sim-api/Activate.bru @@ -6,16 +6,80 @@ meta { post { url: {{baseurl}}/sim/activate - body: formUrlEncoded + body: json auth: inherit } +body:json { + { + "iccid": "1234" + } +} + body:form-urlencoded { - iccid: 8933201125065156057 - offer: SAVEFAMILY1 + iccid: 123 + offer: mensual } settings { encodeUrl: true timeout: 0 } + +docs { + Campos de entrada: + ```ts + // Header requerido + // > content-type:application/x-www-form-urlencoded + // > content-type:application/json + // Cualquiera de los 2 es valido + + // Esquema body + { + iccid: string, + offer: "mensual" | "anual" | "SAVEFAMILY1" | "SAVEFAMILY2" + webhook?: string, + } + ``` + + En el campo `offer` "mensual" equivale a "SAVEFAMILY2" y "anual" a "SAVEFAMILY1" porque se mantien los códigos de Oferta de Objenious por compatibilidad pero se espera usar "mensual" y "anual" y hacer la conversión en el servicio de cada proveedor. + + Para las llamadas al webhook se va a usar siempre el metodo `POST`, ahora mismo no se firman los mensajes. Se introduce la URL completa tal que `https://dominion.com/v1/endpoint`. + + Respuestas: + - **200**: OK + ``` ts + // Esquema + { + iccid: string, + operation: string, + message_id: string, //uuidv7 + } + ``` + ``` json + // Ejemplo + { + "iccid": "89332011250651xxxxx", + "operation": "activation", + "message_id": "019dbeaf-8abb-7783-8b51-94fbd9f0b0df" + } + ``` + + *iccid*: Confirmación del iccid enviado. + *operation*: Confirmación de la operacion que se ha aplicado. + *message_id*: Id de la operación, para consultar en orders. + + > A futuro se va a incluir un campo `"ref":[]` para añadir los enlaces a las consultas de la operación. El body va a permitir tambien json. + + - **402**: Algún campo es incorrecto + Se indica que campo es incorrecto, si hubiese mas de uno solo aparecería el primero en comprobarse. + ```json + "errors": { + "msg": "La longitud del iccid es incorrecta debera ser de 19 caracteres", + "field": "iccid" + } + ``` + + - **500**: Error general + Ha ocurrido un error imprevisto durante la +} diff --git a/docs/sim-api/Activation Email.bru b/docs/sim-api/Activation Email.bru index 8f72b9e..2d7dec1 100644 --- a/docs/sim-api/Activation Email.bru +++ b/docs/sim-api/Activation Email.bru @@ -1,7 +1,7 @@ meta { name: Activation Email type: http - seq: 6 + seq: 7 } post { diff --git a/docs/sim-api/Cancel.bru b/docs/sim-api/Cancel.bru index 3e31352..a4f77ec 100644 --- a/docs/sim-api/Cancel.bru +++ b/docs/sim-api/Cancel.bru @@ -1,7 +1,7 @@ meta { name: Cancel type: http - seq: 1 + seq: 3 } post { diff --git a/docs/sim-api/Docs.bru b/docs/sim-api/Docs.bru index 613bf7b..d2e6145 100644 --- a/docs/sim-api/Docs.bru +++ b/docs/sim-api/Docs.bru @@ -1,7 +1,7 @@ meta { name: Docs type: http - seq: 12 + seq: 10 } get { diff --git a/docs/sim-api/Health.bru b/docs/sim-api/Health.bru index 5c7748b..ab5d247 100644 --- a/docs/sim-api/Health.bru +++ b/docs/sim-api/Health.bru @@ -1,7 +1,7 @@ meta { name: Health type: http - seq: 5 + seq: 6 } get { diff --git a/docs/sim-api/Get pending orders.bru b/docs/sim-api/Orders/Get pending orders.bru similarity index 94% rename from docs/sim-api/Get pending orders.bru rename to docs/sim-api/Orders/Get pending orders.bru index 70b9eed..3888958 100644 --- a/docs/sim-api/Get pending orders.bru +++ b/docs/sim-api/Orders/Get pending orders.bru @@ -1,7 +1,7 @@ meta { name: Get pending orders type: http - seq: 11 + seq: 10 } get { diff --git a/docs/sim-api/Order by id.bru b/docs/sim-api/Orders/Order by id.bru similarity index 68% rename from docs/sim-api/Order by id.bru rename to docs/sim-api/Orders/Order by id.bru index 80104c0..6d3d66a 100644 --- a/docs/sim-api/Order by id.bru +++ b/docs/sim-api/Orders/Order by id.bru @@ -5,7 +5,7 @@ meta { } get { - url: {{baseurl}}/orders/ + url: {{baseurl}}/orders/019dbeaf-8abb-7783-8b51-94fbd9f0b0df body: none auth: inherit } diff --git a/docs/sim-api/Orders by message_id.bru b/docs/sim-api/Orders/Orders by message_id.bru similarity index 50% rename from docs/sim-api/Orders by message_id.bru rename to docs/sim-api/Orders/Orders by message_id.bru index a37a6a6..787fd86 100644 --- a/docs/sim-api/Orders by message_id.bru +++ b/docs/sim-api/Orders/Orders by message_id.bru @@ -5,15 +5,11 @@ meta { } get { - url: {{baseurl}}/orders/message_id/019c93d3-014a-711d-b958-03dd629be78d + url: {{baseurl}}/orders/message_id/019dbeaf-8abb-7783-8b51-94fbd9f0b0df body: none auth: inherit } -params:query { - ~message_id: 019c93d3-014a-711d-b958-03dd629be78d -} - settings { encodeUrl: true timeout: 0 diff --git a/docs/sim-api/Orders/folder.bru b/docs/sim-api/Orders/folder.bru new file mode 100644 index 0000000..6623108 --- /dev/null +++ b/docs/sim-api/Orders/folder.bru @@ -0,0 +1,44 @@ +meta { + name: Orders +} + +auth { + mode: inherit +} + +docs { + # Orders + + Los *order* representan ordenes que se hacen al servidor y representan en que estado se encuentran las peticiones. Los *order* se generan cuando se solicita una operacion y devuelven su identificador en el campo `message_id` de todas las respuestas a peticiones que requieran cambios. Los identificadores de `order` son UUIDv7, aunque tambien tienen asociado un id tradicional BIGINT en la BDD. + + ## Ciclo de vida + + Cuando se crea un *order* comienza en estado `pending`, inicando que ha entrado en la cola y está pendiente de iniciarse; una vez se ha consumido por un servicio pasa a estado `running` indicando que la operacion asociada al *order* ha comenzado, el order continuara en este estado durante un tiempo indefinido (pueden pasar semanas para algunos casos), hasta que la tara finalize correctamente o con errores. En el caso que la tarea finalize con éxito el *order* pasará a estado `finished`, en caso de que haya habido un error el estado será `failed` y se almacenará el error en los campos `error_message` y opcionalemente en `error_stacktrace` según gravedad del error. + + - Caso normal + `pending` -> `running` -> `finished` + + - Error durante el consumo + `pending` -> `failed` + + - Error durante la operacion + `pending` -> `running` -> `failed` + + ## Endpoints + Estan sujetos a cambios en cuanto a mostrar información + + - [WIP]**GET** /orders?{query} + Devuelve todos los orders con un campo que tenga el valor especificado en la query + + - **GET** /orders/{id} + Devuelve el order objetivo según su UUID de mensaje (No según el uuid de mensaje) + + - **GET** /orders/base_id/{id} + Devuelve el id según su id de la bdd, no es el metodo normal de usar la api + + - **GET** /orders/pending + Devuelve todas las order que no hayan finalizado + + + +} diff --git a/docs/sim-api/Pause.bru b/docs/sim-api/Pause.bru index cfb5e5e..255b8ec 100644 --- a/docs/sim-api/Pause.bru +++ b/docs/sim-api/Pause.bru @@ -1,7 +1,7 @@ meta { name: Pause type: http - seq: 1 + seq: 4 } post { diff --git a/docs/sim-api/Preactivate.bru b/docs/sim-api/Preactivate.bru index c9053de..ed73ab1 100644 --- a/docs/sim-api/Preactivate.bru +++ b/docs/sim-api/Preactivate.bru @@ -1,7 +1,7 @@ meta { name: Preactivate type: http - seq: 1 + seq: 5 } post { diff --git a/docs/sim-api/ReActivate.bru b/docs/sim-api/ReActivate.bru index 6477b85..8fd13df 100644 --- a/docs/sim-api/ReActivate.bru +++ b/docs/sim-api/ReActivate.bru @@ -1,7 +1,7 @@ meta { name: ReActivate type: http - seq: 13 + seq: 11 } post { diff --git a/docs/sim-api/collection.bru b/docs/sim-api/collection.bru index 5fe677a..1ec9cfd 100644 --- a/docs/sim-api/collection.bru +++ b/docs/sim-api/collection.bru @@ -1,5 +1,6 @@ docs { - Los endpoint tienen unos campos comunes de entrada: + Todos los endpoint tienen unos campos comunes de entrada: + ```ts { iccid: string, diff --git a/docs/sim-api/test proxy.bru b/docs/sim-api/test proxy.bru index d4e17b0..cb85bc8 100644 --- a/docs/sim-api/test proxy.bru +++ b/docs/sim-api/test proxy.bru @@ -1,7 +1,7 @@ meta { name: test proxy type: http - seq: 14 + seq: 12 } get { diff --git a/packages/sim-consumidor-alai/aplication/SimAlai.controller.ts b/packages/sim-consumidor-alai/aplication/SimAlai.controller.ts index 1c4aac4..7e401da 100644 --- a/packages/sim-consumidor-alai/aplication/SimAlai.controller.ts +++ b/packages/sim-consumidor-alai/aplication/SimAlai.controller.ts @@ -1,6 +1,6 @@ import { ConsumeMessage } from "amqplib"; import { Request, Response } from "express" -import { SimNosUsecases } from "./SimNOS.usecases.js"; +import { SimAlaiUsecases } from "./SimAlai.usecases.js"; import { EventBus } from "sim-shared/domain/EventBus.port.js"; import { Result } from "sim-shared/domain/Result.js"; import { SimEvents } from "sim-shared/domain/SimEvents.js"; @@ -9,7 +9,7 @@ import { iccidValidator } from "./httpValidators.js"; export class SimAlaiController { constructor( - private uscases: SimNosUsecases, + private uscases: SimAlaiUsecases, private eventBus: EventBus, ) { } @@ -51,12 +51,12 @@ export class SimAlaiController { await this.eventBus.ack(msg) return result } else { - console.error("Error procesando el caso de uso (NOS)", result.error) + console.error("Error procesando el caso de uso (Alai)", result.error) this.eventBus.nack(msg) return result } } catch (e) { - console.error("Error general procesando el caso de uso (NOS)") + console.error("Error general procesando el caso de uso (Alai)") this.eventBus.nack(msg) return { error: String(e) diff --git a/packages/sim-consumidor-alai/certificates/wsaccess_alaisecure_com_cert_client_new.p12 b/packages/sim-consumidor-alai/certificates/wsaccess_alaisecure_com_cert_client_new.p12 new file mode 100644 index 0000000000000000000000000000000000000000..86217c6474c8032bdc1485aba51f4c088c4a789d GIT binary patch literal 6183 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 NVTw>a_djL&{{ZHzxsm_? literal 0 HcmV?d00001 diff --git a/packages/sim-entrada-eventos/README.md b/packages/sim-entrada-eventos/README.md new file mode 100644 index 0000000..e69de29 diff --git a/packages/sim-entrada-eventos/aplication/Order.controller.ts b/packages/sim-entrada-eventos/aplication/Order.controller.ts index e52bf96..f924e96 100644 --- a/packages/sim-entrada-eventos/aplication/Order.controller.ts +++ b/packages/sim-entrada-eventos/aplication/Order.controller.ts @@ -32,14 +32,23 @@ export class OrderController { } public getByQueueId() { - return this.controllerGenerator<{ correlation_id: string }, { correlation_id: string }>({ + return this.controllerGenerator<{ uuid: string }, { correlation_id: string }>({ validator: uuidValidator, + mapBody: (e) => ({ correlation_id: e.uuid }), useCase: this.orderUseCases.getByQueueId(), onError: (data, error) => { console.error(error) }, onSuccess: (data) => console.log(data) }) } + public getByQuery() { + return this.controllerGenerator({ + validator: undefined, + useCase: this.orderUseCases.getByQuery(), + onError: (data, error) => { console.error(error) }, + onSuccess: (data) => console.log(data) + }) + } /** * TODO: @@ -77,7 +86,7 @@ export class OrderController { }) } - // 2. Transformacion del body + // 2. Transformacion del body O => P let data: P = body; try { if (args.mapBody != undefined) diff --git a/packages/sim-entrada-eventos/aplication/Order.usecases.ts b/packages/sim-entrada-eventos/aplication/Order.usecases.ts index 83b389b..01cc8a6 100644 --- a/packages/sim-entrada-eventos/aplication/Order.usecases.ts +++ b/packages/sim-entrada-eventos/aplication/Order.usecases.ts @@ -1,4 +1,5 @@ import { PaginationArgs } from "#domain/common.js"; +import { OrderQuery } from "sim-shared/domain/Order.js"; import { OrderRepository } from "sim-shared/infrastructure/OrderRepository.js"; @@ -36,4 +37,10 @@ export class OrderUsecases { } } + // WIP + public getByQuery() { + return async (args: OrderQuery) => { + return await this.orderRepository.getOrdersByQuery(args) + } + } } diff --git a/packages/sim-entrada-eventos/aplication/httpValidators.ts b/packages/sim-entrada-eventos/aplication/httpValidators.ts index ad566f8..b055b40 100644 --- a/packages/sim-entrada-eventos/aplication/httpValidators.ts +++ b/packages/sim-entrada-eventos/aplication/httpValidators.ts @@ -32,10 +32,10 @@ const offerExists = >{ validationFunc: (a: { offer: string }) => offers.has(a.offer), } -const isUuidv7 = >{ - field: "correlation_id", +const isUuidv7 = >{ + field: "uuid", errorMsg: "El uuid no es un uuidv7 valido", - validationFunc: (a) => a.correlation_id != undefined && a.correlation_id.length < 36 + validationFunc: (a) => a.uuid != undefined && a.uuid.length < 36 } const definedId = >{ @@ -73,7 +73,7 @@ export const iccidValidator = new BodyValidator<{ iccid: string }>( ] ) -export const uuidValidator = new BodyValidator<{ correlation_id?: string }>([ +export const uuidValidator = new BodyValidator<{ uuid?: string }>([ isUuidv7 ]) diff --git a/packages/sim-entrada-eventos/infrastructure/orderRoutes.http.ts b/packages/sim-entrada-eventos/infrastructure/orderRoutes.http.ts index 2c1fbfb..9d4d574 100644 --- a/packages/sim-entrada-eventos/infrastructure/orderRoutes.http.ts +++ b/packages/sim-entrada-eventos/infrastructure/orderRoutes.http.ts @@ -28,13 +28,17 @@ const orderController = new OrderController({ * */ orderRoutes.get("/", (req, res) => { res.send("ok") }) -orderRoutes.get("/message_id/:correlation_id", orderController.getByQueueId()) +/* + * Ahora es el id de bdd + * */ +orderRoutes.get("/message_id/:id", orderController.getById()) /** Operaciones pendientes */ orderRoutes.get("/pending", orderController.getPending()) /** Order por id (uuid del mensaje) */ -orderRoutes.get("/:id", orderController.getById()) +// TODO: falla +orderRoutes.get("/:id", orderController.getByQueueId()) export { orderRoutes } diff --git a/packages/sim-entrada-eventos/infrastructure/simconnectionsRoutes.ts b/packages/sim-entrada-eventos/infrastructure/simconnectionsRoutes.ts index 3f04022..b66e154 100644 --- a/packages/sim-entrada-eventos/infrastructure/simconnectionsRoutes.ts +++ b/packages/sim-entrada-eventos/infrastructure/simconnectionsRoutes.ts @@ -9,8 +9,7 @@ export const connectionsRoutes = Router() const CONNECTIONS_URL = env.CONNECTIONS_URL// TODO: Meter al ENV //const CONNECTIONS_URL = "http://sf-nfc-server.savefamilygps.net" -console.log("CONNURL: ", CONNECTIONS_URL) - +//console.log("CONNURL: ", CONNECTIONS_URL) connectionsRoutes.use("", createProxyMiddleware({ target: CONNECTIONS_URL, changeOrigin: true, diff --git a/packages/sim-shared/domain/Order.ts b/packages/sim-shared/domain/Order.ts index df1c2f3..027228e 100644 --- a/packages/sim-shared/domain/Order.ts +++ b/packages/sim-shared/domain/Order.ts @@ -94,4 +94,19 @@ export type ErrorOrderDTO = stackTrace?: string } - +/* + * Se considera cada entrada de conditions como un filtro sobre un campo + * cada fila se podrá expresar como campo:filtro + * ```json + * { + * "value": "-gte 200" // Un valor >= 200 + * "text": "-eq 'busqueda' " // El campo tiene que ser exactamente 'busqueada' + * } + * ``` + * TODO: sacar opciones de paginación + * */ +export type OrderQuery = { + conditions: Record, + limit?: number | undefined, + offset?: number | undefined, +} diff --git a/packages/sim-shared/infrastructure/OrderRepository.test.ts b/packages/sim-shared/infrastructure/OrderRepository.test.ts index 4636fe3..e5f1ce2 100644 --- a/packages/sim-shared/infrastructure/OrderRepository.test.ts +++ b/packages/sim-shared/infrastructure/OrderRepository.test.ts @@ -1,8 +1,9 @@ import { before, describe, it } from "node:test"; import { OrderRepository } from "./OrderRepository.js"; -import { CreateOrderDTO } from "../domain/Order.js"; +import { CreateOrderDTO, OrderQuery } from "../domain/Order.js"; import { postgresClient } from "../config/config.test.js"; import assert from "node:assert"; +import { Query } from "pg"; const order1 = { correlation_id: "fakeRMQid-1234", @@ -169,4 +170,16 @@ describe("Test OrderRepository", {}, (ctx) => { assert(result.data.status === "dlx") assert(result.data.finish_date != null) }) + + it("Query generates with parameters", async () => { + const params: OrderQuery = { + conditions: { + status: "-eq 'pending'" + } + } + //@ts-expect-error + const res = orderRepo.generateTableQuery("test", params) + console.log("Query:", res) + assert.ok(res != undefined) + }) }) diff --git a/packages/sim-shared/infrastructure/OrderRepository.ts b/packages/sim-shared/infrastructure/OrderRepository.ts index 26d05f0..1faa675 100644 --- a/packages/sim-shared/infrastructure/OrderRepository.ts +++ b/packages/sim-shared/infrastructure/OrderRepository.ts @@ -2,7 +2,7 @@ * TODO: Usar */ import { PoolClient, QueryResult, QueryResultRow } from "pg"; -import { CreateOrderDTO, ErrorOrderDTO, FinishOrderDTO, OrderTracking, UpdateOrderDTO } from "../domain/Order.js"; +import { CreateOrderDTO, ErrorOrderDTO, FinishOrderDTO, OrderQuery, OrderTracking, UpdateOrderDTO } from "../domain/Order.js"; import { Result, tryCatch } from "../domain/Result.js"; import { PgClient } from "./PgClient.js"; import assert from "node:assert"; @@ -55,7 +55,77 @@ export class OrderRepository { } } + /** + * Mapeo de prefijos a operadores SQL + */ + private OPERATOR_MAP: Record = { + "-eq": "=", + "-neq": "!=", + "-gt": ">", + "-gte": ">=", + "-lt": "<", + "-lte": "<=", + "-like": "LIKE", + }; + /** + * Tabla general para sacar datos de la tabla en base a unas condiciones + * TODO: + * - Dar la opción de generar los campos a devolver en vez de * + * - Garantizar el numero de parametros de respuesta + */ + private generateTableQuery(table: string, query: OrderQuery) { + const { conditions, limit, offset } = query; + const whereClauses: string[] = []; + const queryValues: any[] = []; + + let paramIndex = 1; // Para los parametros de PostgreSQL ($1, $2) (que empiezan por 1) + + for (const [column, filter] of Object.entries(conditions)) { + const match = filter.match(/^(-\w+)\s+(.+)$/); + + if (match) { + const [_, prefix, value] = match; + const operator = this.OPERATOR_MAP[prefix]; + + if (operator) { + // Eliminación de comillas + const cleanValue = value.replace(/^'|'$/g, ""); + + whereClauses.push(`${column} ${operator} $${paramIndex}`); + queryValues.push(operator === "LIKE" ? `%${cleanValue}%` : cleanValue); + paramIndex++; + } + } + } + + // 2. Query completa + // TODO: Cambair el * por parametros + let sql = `SELECT * FROM ${table}`; + + if (whereClauses.length > 0) { + sql += ` WHERE ${whereClauses.join(" AND ")}`; + } + + // 3. Paginacion + if (limit !== undefined) { + sql += ` LIMIT ${Number(limit)}`; + } + if (offset !== undefined) { + sql += ` OFFSET ${Number(offset)}`; + } + + return { + sql, + values: queryValues, + }; + } + + public async getOrdersByQuery(args: OrderQuery) { + const query = this.generateTableQuery('order_tracking', args) + const queryPromise = this.pgClient.query>>(query.sql, query.values) + const result = await this.getAll(queryPromise) + } /** * El tipo representa el contenido del mensaje de los order From a84f600fa2f35cb2cbf5949a77987fe25d5adaf7 Mon Sep 17 00:00:00 2001 From: Alvar San Martin Date: Mon, 27 Apr 2026 11:05:09 +0200 Subject: [PATCH 03/27] Test orders --- .../sim-shared/aplication/JWT.service.test.ts | 6 +-- .../infrastructure/OrderRepository.test.ts | 16 ++++---- test_api.ts | 37 +++++++++++++++++++ 3 files changed, 47 insertions(+), 12 deletions(-) create mode 100644 test_api.ts diff --git a/packages/sim-shared/aplication/JWT.service.test.ts b/packages/sim-shared/aplication/JWT.service.test.ts index 1d37d7e..28553d7 100644 --- a/packages/sim-shared/aplication/JWT.service.test.ts +++ b/packages/sim-shared/aplication/JWT.service.test.ts @@ -1,7 +1,7 @@ import { test, describe } from "vitest" import { jwtService } from "../config/jwtService.config.js" - -describe("Tokens Objenious", () => { +/* +describe("Tokens Objenious", (test) => { const jwt = jwtService test("Solicicitud normal de auth", async () => { @@ -14,4 +14,4 @@ describe("Tokens Objenious", () => { console.log("acceso refresh objenious", token) }) }) - +*/ diff --git a/packages/sim-shared/infrastructure/OrderRepository.test.ts b/packages/sim-shared/infrastructure/OrderRepository.test.ts index e5f1ce2..e07117e 100644 --- a/packages/sim-shared/infrastructure/OrderRepository.test.ts +++ b/packages/sim-shared/infrastructure/OrderRepository.test.ts @@ -3,7 +3,6 @@ import { OrderRepository } from "./OrderRepository.js"; import { CreateOrderDTO, OrderQuery } from "../domain/Order.js"; import { postgresClient } from "../config/config.test.js"; import assert from "node:assert"; -import { Query } from "pg"; const order1 = { correlation_id: "fakeRMQid-1234", @@ -30,11 +29,6 @@ describe("Test OrderRepository", {}, (ctx) => { const result1 = await orderRepo.createOrder(order1) assert.ok(result1.data != undefined, result1.error as string) testIds.push(result1.data.id) - - // Order2 -> Para el test de crearOrder - // const result2 = await orderRepo.createOrder(order2) - // assert(result2.data != undefined) - // testIds.push(result2.data.id) }) it("Insert new Order", async () => { @@ -75,7 +69,6 @@ describe("Test OrderRepository", {}, (ctx) => { it("Get pending orders should return all pending orders in ASC order", async () => { // We already have 'testId' from before block - // Insert two more orders const orderA = { ...order1, correlation_id: "pending-A" } const orderB = { ...order1, correlation_id: "pending-B" } @@ -174,12 +167,17 @@ describe("Test OrderRepository", {}, (ctx) => { it("Query generates with parameters", async () => { const params: OrderQuery = { conditions: { - status: "-eq 'pending'" + status: "-eq 'pending'", + retry_count: "-gte 2", + webhook_host: "-eq NULL" + } } //@ts-expect-error const res = orderRepo.generateTableQuery("test", params) console.log("Query:", res) - assert.ok(res != undefined) + assert.ok(res != undefined, "Query must be defined") + assert.ok(res.values.length == 3, "Query parameters must be the same as conditions") + }) }) diff --git a/test_api.ts b/test_api.ts new file mode 100644 index 0000000..ac3571d --- /dev/null +++ b/test_api.ts @@ -0,0 +1,37 @@ +import { NosHttpClient } from "./packages/sim-consumidor-nos/infrastructure/NosHttpClient.js"; +import { NosRepository } from "./packages/sim-consumidor-nos/infrastructure/NosRepository.js"; +import { env } from "./packages/sim-consumidor-nos/config/env/env.js"; + +async function main() { + console.log("NOS_BASE_URL", env.NOS_BASE_URL); + const client = new NosHttpClient(env.NOS_BASE_URL); + + // Try to get a subscriber + const res = await client.get("/subscribers", { params: { limit: 1 } }); + console.log("SUBSCRIBER:", res.data.content[0].physicalId); + + const iccid = res.data.content[0].physicalId; + + try { + const history = await client.get(`/subscribers/${iccid}/history`); + console.log("HISTORY:", history.data); + } catch(e) { + console.error("HISTORY ERROR"); + } + + try { + const audit = await client.get(`/subscribers/${iccid}/audit`); + console.log("AUDIT:", audit.data); + } catch(e) { + console.error("AUDIT ERROR"); + } + + try { + const actions = await client.get(`/subscribers/${iccid}/actions`); + console.log("ACTIONS:", actions.data); + } catch(e) { + console.error("ACTIONS ERROR"); + } +} + +main().catch(console.error); From 858932f2606ff4b1f95c67e2bd2533d9a6555edb Mon Sep 17 00:00:00 2001 From: Alvar San Martin Date: Mon, 27 Apr 2026 11:11:47 +0200 Subject: [PATCH 04/27] Test --- packages/sim-shared/infrastructure/OrderRepository.test.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/sim-shared/infrastructure/OrderRepository.test.ts b/packages/sim-shared/infrastructure/OrderRepository.test.ts index e07117e..f2c75ea 100644 --- a/packages/sim-shared/infrastructure/OrderRepository.test.ts +++ b/packages/sim-shared/infrastructure/OrderRepository.test.ts @@ -169,8 +169,7 @@ describe("Test OrderRepository", {}, (ctx) => { conditions: { status: "-eq 'pending'", retry_count: "-gte 2", - webhook_host: "-eq NULL" - + webhook_host: "-null NULL" } } //@ts-expect-error From bb31efb271139a96feaa6224c3a736809baf0402 Mon Sep 17 00:00:00 2001 From: Alvar San Martin Date: Wed, 29 Apr 2026 17:08:30 +0200 Subject: [PATCH 05/27] Merge main -> migracion alai --- .env | 32 ------- .gitignore | 2 + README.md | 22 +++-- deployment/develop/docker/docker-compose.yaml | 1 + deployment/develop/jenkinsfile.groovy | 4 + deployment/local/docker/docker-compose.yaml | 2 + docs/sim-api-documentation.html | 2 +- docs/sim-api/Cancel.bru | 4 + docs/sim-api/France Suspended Lines.bru | 26 ++++++ docs/sim-api/France Suspended Time.bru | 21 +++++ docs/sim-api/Pause.bru | 2 +- docs/sim-objenious/Consumption details.bru | 38 +++++++++ package.json | 6 +- packages/_template/config/env/index.ts | 1 - packages/sim-consumidor-nos/.env | 8 -- packages/sim-consumidor-nos/config/env/env.ts | 1 - packages/sim-consumidor-objenious/.env | 2 + .../aplication/Sim.controller.test.ts | 5 +- .../aplication/Sim.controller.ts | 33 ++++++++ .../aplication/Sim.usecases.ts | 62 ++++++++++++-- .../aplication/httpValidators.ts | 18 ++++ .../config/env/index.ts | 3 + .../config/intranetPostgresConfig.ts | 20 +++++ packages/sim-consumidor-objenious/index.ts | 83 ++++++++++++++++--- .../sim-consumidor-objenious/package.json | 4 +- .../aplication/Order.controller.ts | 2 +- .../aplication/Order.usecases.ts | 2 +- .../sim-entrada-eventos/config/env/index.ts | 5 +- packages/sim-entrada-eventos/domain/common.ts | 6 -- packages/sim-entrada-eventos/index.ts | 4 + .../infrastructure/franceRoutes.http.ts | 33 ++++++++ .../sim-objenious-cron/config/env/index.ts | 2 - packages/sim-objenious-cron/index.ts | 10 ++- .../tasks/check_objenious_request.ts | 5 ++ .../tasks/check_pause_terminate.ts | 3 +- .../tasks/volcado_lineas.ts | 2 +- packages/sim-shared/domain/PaginationArgs.ts | 14 ++++ .../domain/operationsRepository.port.ts | 1 + .../ObjeniousLinesRepository.test.ts | 6 +- .../ObjeniousLinesRepository.ts | 54 +++++++++++- .../ObjeniousOperationRepository.test.ts | 39 ++++++++- .../ObjeniousOperationRepository.ts | 62 ++++++++++++++ .../infrastructure/OrderRepository.test.ts | 1 - 43 files changed, 555 insertions(+), 98 deletions(-) delete mode 100644 .env create mode 100644 docs/sim-api/France Suspended Lines.bru create mode 100644 docs/sim-api/France Suspended Time.bru create mode 100644 docs/sim-objenious/Consumption details.bru delete mode 100644 packages/sim-consumidor-nos/.env create mode 100644 packages/sim-consumidor-objenious/aplication/httpValidators.ts create mode 100644 packages/sim-consumidor-objenious/config/intranetPostgresConfig.ts delete mode 100644 packages/sim-entrada-eventos/domain/common.ts create mode 100644 packages/sim-entrada-eventos/infrastructure/franceRoutes.http.ts create mode 100644 packages/sim-shared/domain/PaginationArgs.ts rename packages/{sim-objenious-cron/infranstructure => sim-shared/infrastructure}/ObjeniousLinesRepository.test.ts (87%) rename packages/{sim-objenious-cron/infranstructure => sim-shared/infrastructure}/ObjeniousLinesRepository.ts (72%) diff --git a/.env b/.env deleted file mode 100644 index d72f207..0000000 --- a/.env +++ /dev/null @@ -1,32 +0,0 @@ -PORT=3000 -API_HOSTNAME=0.0.0.0 -RABBITMQ_USER=guest -RABBITMQ_PASSWORD=guest - -ENVIORMENT=development - -#RABBITMQ_HOST=rabbitmq-sim-broker -RABBITMQ_HOST=localhost -RABBITMQ_PORT=5672 -RABBITMQ_USER=guest -RABBITMQ_PASSWORD=guest -RABBITMQ_SECURE=false -RABBITMQ_VHOST=sim-vhost - -# Hay cosas que unificar de varios servicios -#POSTGRES_HOST=postgresql-sim -POSTGRES_HOST=localhost -POSTGRES_DB=postgres -POSTGRES_DATABASE=postgres -POSTGRES_PORT=5433 -POSTGRES_USER=postgres -POSTGRES_PASSWORD='1234' - -# Para el postgres local para generar el script de resultado de migraciones -PGHOST=localhost -PGUSER=alvar -PGPASSWORD=alvar -PGPORT=5433 - -# Proxy -CONNECTIONS_URL=https://sim-connections.savefamilygps.net diff --git a/.gitignore b/.gitignore index 195e0ed..f69b410 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,5 @@ node_modules dist/* + +.env diff --git a/README.md b/README.md index c060e9a..6328041 100644 --- a/README.md +++ b/README.md @@ -12,13 +12,13 @@ La compañia a la que pertenece cada peticion y por tanto el servicio que lo va ## Decisiones pendientes -- [x] La capa worker según acción y la de operaciones de proveedores, se podrían unir en una sola con un enrutamiento por acción y compañía, pasando de tener claves `sim.[acción]` a `sim.[compañia].[acción]`. *Se ha aplicado el cambio ahora las routing keys tienen la estructura `sim.[compañia].[acción]`* -- [x] La estructura de RMQ se genera por medio del JSON, igual habría que definir cada cola en el worker que la consuma para poder añadir workers sin parar el RMQ. *Se ha aplicado el cambio, ahora solo se define en el json el broker principal para garantizar que exita sin servicios consumidores. Sin embargo tal como estan estructurdos los proyectos no es posible reiniciar solo un servicio* +- [x] La capa worker según acción y la de operaciones de proveedores, se podrían unir en una sola con un enrutamiento por acción y compañía, pasando de tener claves `sim.[acción]` a `sim.[compañia].[acción]`. _Se ha aplicado el cambio ahora las routing keys tienen la estructura `sim.[compañia].[acción]`_ +- [x] La estructura de RMQ se genera por medio del JSON, igual habría que definir cada cola en el worker que la consuma para poder añadir workers sin parar el RMQ. _Se ha aplicado el cambio, ahora solo se define en el json el broker principal para garantizar que exita sin servicios consumidores. Sin embargo tal como estan estructurdos los proyectos no es posible reiniciar solo un servicio_ - [ ] Versionado de la API. -- [x] Método para sacar la compañía a partir del iccid, o buscar en la BDD si no es posible. *De momento es un objeto Map en el servicio de gateway* -- [ ] Cola de mensajes que no se han podido procesar. Distinguir según error de red; se reintenta; o error del propio mensaje; se envía a la cola de errores. v2 Se ha creado una cola de delay pero no se distingue el tipo de error, despues de n reintentos el mensaje va a la cola de dead-letter. -- [ ] Seguimiento de las peticiones de Objenious, por cada peticion hay qye hacer un seguimiento del request y de los mass action para saber si las activaciones han tenido exito. Habria que crear otra cola para consultar cada x tiempo o mejor un cron? -- [ ] Actualizar en la base de datos el estado de las peticiones de las sim y añadir el número de telefono cuando se activen o cuando se cumpla una accion. +- [x] Método para sacar la compañía a partir del iccid, o buscar en la BDD si no es posible. _De momento es un objeto Map en el servicio de gateway_ +- [ ] Cola de mensajes que no se han podido procesar. Distinguir según error de red; se reintenta; o error del propio mensaje; se envía a la cola de errores. v2 Se ha creado una cola de delay pero no se distingue el tipo de error, después de n reintentos el mensaje va a la cola de dead-letter. +- [x] Seguimiento de las peticiones de Objenious, por cada peticion hay qye hacer un seguimiento del request y de los mass action para saber si las activaciones han tenido exito. Habria que crear otra cola para consultar cada x tiempo o mejor un cron? +- [x] Actualizar en la base de datos el estado de las peticiones de las sim y añadir el número de telefono cuando se activen o cuando se cumpla una accion. ## Versión con consumidores basados en la compañia @@ -32,8 +32,14 @@ OBJENIOUS (33)2011a ## Diagrama de las colas de Rabbitmq -Actualmente la topologia de las colas consiste en un exchage principal que recibe todos los mensajes y los redistribuye en las colas de cada empresa y a la de logs. Para evitar reintentos de mensajes instantaneos, que podrian ser inutiles si algún servicio se ha caido, se ha añadido una cola de delay que alamcena los mesajes fallidos durante n segundos antes de ser reenviados al exchange principal. Si despues de n reintentos el mensaje sigue fallando se envia a la cola de dead-letter para ser procesado manualmente. +Actualmente la topología de las colas consiste en un exchage principal que recibe todos los mensajes y los redistribuye en las colas de cada empresa y a la de logs. Para evitar reintentos de mensajes instantáneos, que podrían ser inútiles si algún servicio se ha caído, se ha añadido una cola de delay que almacena los mensajes fallidos durante n segundos antes de ser reenviados al exchange principal. Si después de n reintentos el mensaje sigue fallando se envía a la cola de dead-letter para ser procesado manualmente. ![img](./imgs/diagrama-rabbit.png) -La decisión del numero de reintentos y la cola de dlx se hace en los servicios, con una configuracion global en shared. +La decisión del numero de reintentos y la cola de dlx se hace en los servicios, con una configuración global en shared. + +## Puertos internos para comunicaciones entre sub-servicios + +- **3000**: Gateway (sim-entrada-eventos) +- **3001**: Consumidor NOS (sim-consumidor-nos) +- **3002**: Consumidor Objenious (sim-consumidor-objenious) diff --git a/deployment/develop/docker/docker-compose.yaml b/deployment/develop/docker/docker-compose.yaml index 0ff7ea6..d634523 100644 --- a/deployment/develop/docker/docker-compose.yaml +++ b/deployment/develop/docker/docker-compose.yaml @@ -72,6 +72,7 @@ services: - ${PORT} volumes: - ./.env:/home/node/app/.env:ro + - ./sim-consumidor-nos.env:/home/node/app/packages/sim-consumidor-nos/.env:ro - ./sim-consumidor-objenious.env:/home/node/app/packages/sim-consumidor-objenious/.env:ro - ./sim-objenious-cron.env:/home/node/app/packages/sim-objenious-cron/.env:ro - ./obj.pem:/home/node/app/packages/sim-consumidor-objenious/obj.pem:ro diff --git a/deployment/develop/jenkinsfile.groovy b/deployment/develop/jenkinsfile.groovy index 291e0fe..3ca865d 100644 --- a/deployment/develop/jenkinsfile.groovy +++ b/deployment/develop/jenkinsfile.groovy @@ -46,6 +46,10 @@ pipeline { cleanRemote: false, execCommand: "ln -sf $BASE_REMOTE_PATH/vault/savefamily/sf-sims/sim-consumidor-objenious.env $APP_REMOTE_PATH/sim-consumidor-objenious.env" ), + sshTransfer( + cleanRemote: false, + execCommand: "ln -sf $BASE_REMOTE_PATH/vault/savefamily/sf-sims/sim-consumidor-nos.env $APP_REMOTE_PATH/sim-consumidor-nos.env" + ), sshTransfer( cleanRemote: false, execCommand: "ln -sf $BASE_REMOTE_PATH/vault/savefamily/sf-sims/sim-objenious-cron.env $APP_REMOTE_PATH/sim-objenious-cron.env" diff --git a/deployment/local/docker/docker-compose.yaml b/deployment/local/docker/docker-compose.yaml index 8c75daf..cb8322d 100644 --- a/deployment/local/docker/docker-compose.yaml +++ b/deployment/local/docker/docker-compose.yaml @@ -7,6 +7,7 @@ networks: services: rabbitmq-sim-broker: container_name: rabbitmq-sim-broker + hostname: rabbitmq-sim image: "rabbitmq:4.2.2-management" ports: - "5672:5672" @@ -23,6 +24,7 @@ services: RABBITMQ_DEFAULT_USER: ${RABBITMQ_USER} RABBITMQ_DEFAULT_PASS: ${RABBITMQ_PASSWORD} volumes: + - ./rabbitmq-data/:/var/lib/rabbitmq/ - ./rabbitmq_plugins/enabled_plugins:/etc/rabbitmq/enabled_plugins:ro - ./deployment/local/rabbit/rabbitmq.conf:/etc/rabbitmq/rabbitmq.conf:ro - ./deployment/local/rabbit/definitions.json:/etc/rabbitmq/definitions.json:ro diff --git a/docs/sim-api-documentation.html b/docs/sim-api-documentation.html index 630a58f..86b95b6 100644 --- a/docs/sim-api-documentation.html +++ b/docs/sim-api-documentation.html @@ -14,7 +14,7 @@