Orders con endpoints para monitorizacion

This commit is contained in:
2026-02-25 12:20:52 +01:00
parent c416114c50
commit 02c80cd503
16 changed files with 373 additions and 63 deletions

View File

@@ -0,0 +1,110 @@
import { BodyValidator } from "sim-shared/aplication/BodyValidator.js"
import { OrderUsecases } from "./Order.usecases.js"
import { Request, Response } from "express"
import { PaginationArgs } from "#domain/common.js"
export class OrderController {
private orderUseCases: OrderUsecases
constructor(args: {
orderUseCases: OrderUsecases
}) {
this.orderUseCases = args.orderUseCases
}
public getById(args: { id: number }) {
return this.controllerGenerator<{ id: number }, { id: number }>({
validator: undefined,
useCase: this.orderUseCases.getById(args),
onError: (data, error) => { console.error(error) },
onSuccess: (data) => console.log(data)
})
}
public getPending(args: PaginationArgs) {
return this.controllerGenerator<{ id: number }, { id: number }>({
validator: undefined,
useCase: this.orderUseCases.getPending(args),
onError: (data, error) => { console.error(error) },
onSuccess: (data) => console.log(data)
})
}
public getByQueueId(args: { message_id: string }) {
return this.controllerGenerator({
validator: undefined,
useCase: this.orderUseCases.getByQueueId(args),
onError: (data, error) => { console.error(error) },
onSuccess: (data) => console.log(data)
})
}
/**
* TODO:
* - En proceso de validacion, tiene varios problemas
* - Está copiado, planteado inyectarlo
*
* Abstrae el proceso de
* Peticion -> validacion del body -> map del body -> useCase -> OK/ERR
*
* <O> Representa el dato original
* <P> Representa el dato después del mapeo
*/
public controllerGenerator<O extends Object, P extends Object>(args: {
validator?: BodyValidator<O>,
mapBody?: (body: O) => P,
useCase: (args: P) => Promise<any>,
onError: (args: O | P, error: string) => void,
onSuccess: (args: P) => void,
}) {
return async (req: Request, res: Response) => {
const body = req.body
// 1. Validacion del body
try {
if (args.validator != undefined)
args.validator.validate(body)
} catch (e) {
if (args.onError != undefined) args.onError(body, e as string)
res.status(422).json({
errors: {
msg: e
}
})
}
// 2. Transformacion del body
let data: P = body;
try {
if (args.mapBody != undefined)
data = args.mapBody(body)
} catch (e) {
res.status(422).json({
errors: {
msg: "Error parseando el body: " + e
}
})
}
// 3. Aplicacion del UseCase
try {
const usecaseResult = await args.useCase(data)
// 4. Se devuelve al usuario el caso de exito
res.status(200).json(
usecaseResult
).send()
args.onSuccess(data)
} catch (err) {
// 4.1 Error del caso de uso
res.status(500).json({
errors: {
msg: "Error general:" + err
}
}).send()
return;
}
}
}
}

View File

@@ -0,0 +1,37 @@
import { PaginationArgs } from "#domain/common.js";
import { OrderRepository } from "sim-shared/infrastructure/OrderRepository.js";
export class OrderUsecases {
private orderRepository: OrderRepository;
constructor(args: {
orderRepository: OrderRepository
}
) {
this.orderRepository = args.orderRepository
}
public getById(args: {
id: number
}) {
return async () => {
return await this.orderRepository.getOrderById(args)
}
}
public getByQueueId(args: {
message_id: string
}) {
return async () => {
return await this.orderRepository.getOrderByQueueId(args)
}
}
public getPending(args: PaginationArgs & {
}) {
return async () => {
return await this.orderRepository.getPendingOrders(args)
}
}
}

View File

@@ -3,7 +3,6 @@ import { SimUsecases } from "./Sim.usecases.js"
import { activationValidator, iccidValidator } from "./httpValidators.js"
import { companyFromIccid } from "#domain/companies.js"
import { BodyValidator } from "sim-shared/aplication/BodyValidator.js"
import { error } from "node:console"
export class SimController {
@@ -30,7 +29,7 @@ export class SimController {
public controllerGenerator<O extends Object, P extends Object>(args: {
validator?: BodyValidator<O>,
mapBody?: (body: O) => P,
useCase: (args: P) => Promise<void>,
useCase: (args: P) => Promise<any>,
onError: (args: O | P, error: string) => void,
onSuccess: (args: P) => void,
}) {
@@ -66,10 +65,13 @@ export class SimController {
// 3. Aplicacion del UseCase
try {
const usecaseResult = await args.useCase(data)
// 4. Se devuelve al usuario el caso de exito
res.status(200).json(
usecaseResult
).send()
args.onSuccess(data)
} catch (err) {
// 4.1 Error del caso de uso
res.status(500).json({
errors: {
msg: "Error general:" + err
@@ -77,13 +79,14 @@ export class SimController {
}).send()
return;
}
}
}
public preactivationTest() {
return this.controllerGenerator({
public test() {
return this.controllerGenerator<{ iccid: string, offer: string }, { iccid: string }>({
validator: iccidValidator,
useCase: this.simUseCases.test,
useCase: (args) => this.simUseCases.test(args),
onError: (data, error) => console.error(error),
onSuccess: (data) => {
console.log("OK", data)

View File

@@ -1,9 +1,10 @@
import { OrderRepository } from "sim-shared/infrastructure/OrderRepository.js";
import { Result } from "sim-shared/domain/Result.js";
import assert from "node:assert";
import { EventBus } from "sim-shared/domain/EventBus.port";
import { SimEvents } from "sim-shared/domain/SimEvents";
import { uuidv7 } from "uuidv7";
import { CreateOrderDTO, OrderType } from "sim-shared/domain/Order.js";
import { CreateOrderDTO, OrderTracking, OrderType, OrderTypeOptions } from "sim-shared/domain/Order.js";
/**
* Casos de uso de tarjetas sim. Garantiza que todos los metodos usan el mismo bus de mensajes
@@ -22,40 +23,61 @@ export class SimUsecases {
this.orderRepository = args.orderRepository
}
async test(args: { iccid: string }) {
assert(args.iccid != undefined)
private addMessage_id(event: SimEvents.general): SimEvents.general & { headers: { message_id: string } } {
const uuid = uuidv7()
const event = <SimEvents.general>{
key: `sim.test.test`,
payload: {
iccid: args.iccid
},
return {
...event,
headers: {
...event.headers,
message_id: uuid
}
}
}
const publish = await this.eventBus.publish([event])
/**
* TODO:
* De momento solo para mensajes publicados de 1 en 1 y si se les ha añadido cabecera
* Si se ha saltado el proceso de añadir un ID no se
*/
if (publish.success.length == 1) {
if (event.headers?.message_id != undefined) {
const orderType = (event.key.split(".")[2] as OrderType ?? "unknown")
assert(orderType)
const order: CreateOrderDTO = {
correlation_id: event.headers.message_id,
order_type: orderType,
routing_key: event.key,
payload: event
}
this.orderRepository.createOrder(order)
/**
* El tipo T es el tipo del payload del Order
*/
private async saveOrder<T extends any>(event: SimEvents.general): Promise<Result<string, OrderTracking<T>>> {
if (event.headers?.message_id == undefined) {
return <Result<string, any>>{
error: "El evento no tiene una cabecera message_id definido"
}
}
const orderType = (event.key.split(".")[2] as OrderType ?? "unknown")
// Estoy pensando en la posibilidad de pasarlo a unknown
if (!OrderTypeOptions.has(orderType)) {
return <Result<string, any>>{
error: `El evento no tiene un tipo valido: ${orderType} no existe como tipo valido`
}
}
const order: CreateOrderDTO = {
correlation_id: event.headers.message_id,
order_type: orderType,
routing_key: event.key,
payload: event
}
const result = await this.orderRepository.createOrder<T>(order)
return result;
}
async test(args: { iccid: string }) {
assert(args.iccid != undefined)
const event = <SimEvents.general>{
key: `sim.test.unknown`,
payload: {
iccid: args.iccid
}
}
const eventWithId = this.addMessage_id(event)
const publish = await this.eventBus.publish([eventWithId])
await this.saveOrder(eventWithId)
return eventWithId
}
/**
@@ -85,8 +107,11 @@ export class SimUsecases {
offer: args.offer
}
}
console.log("[d] Activation ", activationEvent)
return this.eventBus.publish([activationEvent])
const activationWithId = this.addMessage_id(activationEvent)
console.log("[d] Activation ", activationWithId)
await this.eventBus.publish([activationWithId])
this.saveOrder(activationWithId)
}
async preActivation(args: { iccid: string, compañia: string }) {

View File

@@ -0,0 +1,6 @@
export type PaginationArgs = {
limit?: number,
offset?: number,
start?: number
}

View File

@@ -3,6 +3,7 @@ import cors from 'cors';
import { simRoutes } from "./infrastructure/simRoutes.http.js"
import { rabbitmqEventBus } from '#config/eventBusConfig.js';
import { env } from "#config/env/index.js"
import { orderRoutes } from "#adapters/orderRoutes.http.js";
const PORT = env.API_PORT
const HOSTNAME = "0.0.0.0"
@@ -24,6 +25,7 @@ app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use("/sim", simRoutes)
app.use("/orders", orderRoutes)
app.get("/health", (req, res) => {
res.status(200).json({ status: "ok" })

View File

@@ -0,0 +1,31 @@
/**
* Rutas para consultar el estado de los order
*/
import { OrderRepository } from "sim-shared/infrastructure/OrderRepository.js"
import { Router } from "express"
import { postgresClient } from '#config/postgreConfig.js';
const orderRoutes = Router()
// orderRepository no se trata como singleton
const orderRepository = new OrderRepository(postgresClient)
/**
* Todas las orders, o un resumen, admite filtros
* por:
* - status
* - fecha inicio
* - fecha fin
* - pendientes
* */
orderRoutes.get("/")
/** Order por id (uuid del mensaje) */
orderRoutes.get("/{id}")
orderRoutes.get("/{status}")
export { orderRoutes }

View File

@@ -30,6 +30,8 @@ simRoutes.post("/pause", simController.pause())
simRoutes.post("/cancel", simController.cancelation())
simRoutes.post("/test", simController.test())
// Proceso especifico de ALAI para liberar sims canceladas
simRoutes.post("/free", simController.free())

View File

@@ -13,12 +13,6 @@
"types": "./config/*.ts",
"default": "./config/*.js"
},
"#shared/*.js": {
"default": "../sim-shared/*.js"
},
"#shared/*": {
"default": "../sim-shared/*.js"
},
"#adapters/*.js": {
"types": "./infrastructure/*.ts",
"default": "./infrastructure/*.js"