Todas las operaciones posibles de SIM

This commit is contained in:
2026-02-02 16:59:12 +01:00
parent c1370d6609
commit 93a9a1a8e1
7 changed files with 344 additions and 57 deletions

View File

@@ -23,6 +23,7 @@
"cors": "^2.8.5",
"dotenv": "^17.2.3",
"express": "^5.2.1",
"pg": "^8.18.0",
"typescript": "^5.9.3",
"vite": "^7.3.1",
"vite-tsconfig-paths": "^6.0.5"
@@ -32,6 +33,7 @@
"@types/cors": "^2.8.19",
"@types/express": "^5.0.6",
"@types/node": "^25.0.3",
"@types/pg": "^8.16.0",
"@types/supertest": "^6.0.3",
"concurrently": "^9.2.1",
"prettier": "^3.7.4",
@@ -43,4 +45,4 @@
"imports": {
"#shared/*": "./dist/packages/shared/*.js"
}
}
}

View File

@@ -0,0 +1,4 @@
export type Result<E, D> = {
error: E | undefined,
data: D | undefined
}

View File

@@ -0,0 +1,25 @@
import { Pool, QueryResult, QueryResultRow } from "pg";
export class postgreClient {
private pgPool: Pool;
constructor(args: {
pool: Pool
}) {
this.pgPool = args.pool
}
/**
* Wrapper para ejecutar consultas con tipado fuerte.
* T es el formato de la respusta.
* @param text - La consulta SQL (ej. 'SELECT * FROM users WHERE id = $1')
* @param params - Los valores para los placeholders $1, $2, etc.
*/
public async postgreQuery<T extends QueryResultRow = any>(
text: string,
params?: any[]
): Promise<QueryResult<T>> {
return await this.pgPool.query(text, params);
};
}

View File

@@ -4,6 +4,7 @@ import { SimUseCases } from "./Sim.usecases.js";
import { SimEvents } from "#shared/domain/SimEvents.js";
import { constants } from "node:buffer";
import { constrainedMemory } from "node:process";
import { Result } from "#shared/domain/Result.js";
/**
* La clase usa generadores de funciones para mantener el contexto
@@ -42,66 +43,108 @@ export class SimController {
}
}
public activateSim() {
return async (msg: ConsumeMessage) => {
if (!this.validateActivationMsg(msg)) {
throw new Error("Error consumiendo el mensaje no es valido")
}
const msgData = this.decodeMsg(msg) as SimEvents.activation
if (msgData == undefined || msgData.payload == undefined) throw new Error("Mensaje invalido")
console.log("Mensaje procesado", msgData?.toString())
// TODO: Añadir un validador del mensaje
const iccid = msgData.payload.iccid
const headers = msg.properties.headers
console.log("HEADERS: ", headers)
try {
// Caso de uso de activaciones
const result = await this.useCases.activate({
dueDate: this.genDueDate(2 * 60).toISOString(),
identifier: {
identifierType: "ICCID",
identifiers: [iccid]
}
})()
console.log("Resultado de la peticion", result)
if (result.error == undefined) {
console.log("Ack", msgData)
await this.eventBus.ack(msg)
} else {
console.log("Nack", msgData)
await this.eventBus.nack(msg)
}
} catch (e) {
this.eventBus.nack(msg)
/**
* Metodo general de lanzar un caso de uso con un mensaje, si el caso de uso es exitoso
* se ACK el mesaje, si hay algún error se NACK.
* TODO:
* - Se podrian hacer genericos los parametros
*/
private async tryUseCase<T extends any>(msg: ConsumeMessage, usecase: () => Promise<Result<string, T>>) {
try {
const result = await usecase()
if (result.error == undefined) {
await this.eventBus.ack(msg)
return result
} else {
console.error("Error general procesando el caso de uso", result.error)
}
} catch (e) {
console.error("Error general procesando el caso de uso")
this.eventBus.nack(msg)
}
}
public pauseSim() {
public activateSim() {
return async (msg: ConsumeMessage) => {
if (!this.validateActivationMsg(msg)) {
throw new Error("Error consumiendo el mensaje no es valido")
let msgData;
try {
msgData = this.validateMsg(msg) as SimEvents.activation
} catch (e) {
throw new Error("Error consumiendo el mensaje no es valido" + String(e))
}
const msgData = this.decodeMsg(msg)
if (msgData == undefined) Promise.reject("Mensaje invalido")
if (msgData == undefined || msgData.payload == undefined) throw new Error("Mensaje invalido")
const iccid = msgData.payload.iccid
this.tryUseCase(msg, this.useCases.activate({
dueDate: this.genDueDate(2 * 60).toISOString(),
identifier: {
identifierType: "ICCID",
identifiers: [iccid]
}
}))
}
}
public suspendSim() {
return async (msg: ConsumeMessage) => {
let msgData;
try {
msgData = this.validateMsg(msg) as SimEvents.pause
} catch (e) {
throw new Error("Error consumiendo el mensaje no es valido" + String(e))
}
if (msgData == undefined) {
return Promise.reject("Mensaje invalido")
}
const iccid = msgData.payload.iccid
this.tryUseCase(msg, this.useCases.suspend({
dueDate: this.genDueDate(2 * 60).toISOString(),
identifier: {
identifierType: "ICCID",
identifiers: [iccid]
}
}))
}
}
public terminate() {
return async (msg: ConsumeMessage) => {
let msgData;
try {
msgData = this.validateMsg(msg) as SimEvents.pause
} catch (e) {
throw new Error("Error consumiendo el mensaje no es valido" + String(e))
}
if (msgData == undefined) {
return Promise.reject("Mensaje invalido")
}
const iccid = msgData.payload.iccid
console.log("Mensaje procesado", String(msgData))
this.tryUseCase(msg, this.useCases.terminate({
dueDate: this.genDueDate(2 * 60).toISOString(),
identifier: {
identifierType: "ICCID",
identifiers: [iccid]
}
}))
}
}
/**
* TODO:
* - Loguear motivos de la no validacion
* - Añadir validadores inyectables
*/
private validateActivationMsg(msg: ConsumeMessage | null) {
private validateMsg(msg: ConsumeMessage | null) {
if (msg == undefined) return false;
return true;
const msgData = this.decodeMsg(msg) as SimEvents.general
if (msgData == undefined || msgData.payload == undefined) throw new Error("Mensaje invalido")
return msgData;
}
private genDueDate(secondsDue: number) {

View File

@@ -1,7 +1,7 @@
import { ActivationData } from "#domain/DTOs/objeniousapi.js"
import { HttpClient } from "#shared/infrastructure/HTTPClient.js"
import { AxiosError } from "axios"
import { error } from "node:console"
import { Result } from "#shared/domain/Result"
// TODO: Pasar a un archivo de DTOs
@@ -16,8 +16,10 @@ export class SimUseCases {
this.httpClient = args.httpClient
}
public activate(activationData: ActivationData) {
const OPERATION_URL = "/actions/preactivateLine"
public activate(activationData: ActivationData): () => Promise<Result<string, boolean>> {
const OPERATION_URL = "/actions/activateLine"
return async () => {
const req = this.httpClient.client.post(OPERATION_URL, {
...activationData
@@ -30,37 +32,97 @@ export class SimUseCases {
if (response.status == 200) {
console.log("Activacion con exito", response.data.response)
return {
return <Result<string, boolean>>{
error: undefined,
ok: true
data: true
}
} else {
return {
error: response.status
error: String(response.status),
data: undefined
}
}
} catch (error) {
console.error("[Sim.usecase] Error activando ", (error as AxiosError).response?.status)
return {
error: "Error general de la petiacion"
error: "Error general de la petiacion",
data: undefined
}
}
}
}
public pause(activationData: ActivationData) {
const OPERATION_URL = "/actions/pause"
public reactivate(pauseData: ActivationData): () => Promise<Result<string, boolean>> {
const OPERATION_URL = "/actions/reactivateLine"
return async () => {
const req = this.httpClient.client.post("/actions/pause", {
...activationData
const req = this.httpClient.client.post(OPERATION_URL, {
...pauseData
})
try {
const e = await req
console.log("Sim pausada con exito", e.data)
return <Result<string, boolean>>{
error: undefined,
data: true
}
} catch (error) {
console.error("Error pausa", error)
return <Result<string, boolean>>{
error: "Error reactivando la sim" + pauseData.identifier,
data: true
}
}
}
}
public suspend(suspendData: ActivationData): () => Promise<Result<string, boolean>> {
const OPERATION_URL = "/actions/suspendLine"
return async () => {
const req = this.httpClient.client.post(OPERATION_URL, {
...suspendData
})
try {
const e = await req
console.log("Sim pausada con exito", e.data)
return <Result<string, boolean>>{
error: undefined,
data: true
}
} catch (error) {
console.error("Error pausa", error)
return {
error: "Error general pausando/suspendiendo la sim" + suspendData.identifier,
data: undefined
}
}
}
}
public terminate(terminationData: ActivationData): () => Promise<Result<string, boolean>> {
const OPERATION_URL = "/actions/terminateLine"
return async () => {
const req = this.httpClient.client.post(OPERATION_URL, {
...terminationData
})
try {
const e = await req
console.log("Sim pausada con exito", e.data)
return <Result<string, boolean>>{
error: undefined,
data: true
}
} catch (error) {
console.error("Error pausa", error)
return <Result<string, boolean>>{
error: "Error cancelando/terminate la sim" + terminationData.identifier,
data: undefined
}
}
}
}
}

View File

@@ -0,0 +1,13 @@
import { Pool, QueryResult } from 'pg';
import { env } from './env';
// 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) || 5432,
});

138
yarn.lock
View File

@@ -587,6 +587,17 @@ __metadata:
languageName: node
linkType: hard
"@types/pg@npm:^8.16.0":
version: 8.16.0
resolution: "@types/pg@npm:8.16.0"
dependencies:
"@types/node": "npm:*"
pg-protocol: "npm:*"
pg-types: "npm:^2.2.0"
checksum: 10/c03346fbe87728a237f30a3d0a436b86ede88e1dc471782bf679a4d74d67ee2a96f953e7c04d73841d21b9db43a5bf2ccdf2cd4c75450ea57efd947049809b3a
languageName: node
linkType: hard
"@types/qs@npm:*":
version: 6.14.0
resolution: "@types/qs@npm:6.14.0"
@@ -2197,6 +2208,87 @@ __metadata:
languageName: node
linkType: hard
"pg-cloudflare@npm:^1.3.0":
version: 1.3.0
resolution: "pg-cloudflare@npm:1.3.0"
checksum: 10/04007ebbd314bdc8e5983d5d0f71a942f1915d2312ed84894d55475010559665bcbb9941d55523d36be6386cd83490dffb5b1ea98ffbbc82a140ef5dfb68ab8d
languageName: node
linkType: hard
"pg-connection-string@npm:^2.11.0":
version: 2.11.0
resolution: "pg-connection-string@npm:2.11.0"
checksum: 10/0333bb1b7ddeac6fa5262920f82a983222c600d21ef14fdc5254b0d3cbb1763030d20c1e8e3c20fa767a6eda8f4b4773550954c06f3e072e5288b6fa9e9cae13
languageName: node
linkType: hard
"pg-int8@npm:1.0.1":
version: 1.0.1
resolution: "pg-int8@npm:1.0.1"
checksum: 10/a1e3a05a69005ddb73e5f324b6b4e689868a447c5fa280b44cd4d04e6916a344ac289e0b8d2695d66e8e89a7fba023affb9e0e94778770ada5df43f003d664c9
languageName: node
linkType: hard
"pg-pool@npm:^3.11.0":
version: 3.11.0
resolution: "pg-pool@npm:3.11.0"
peerDependencies:
pg: ">=8.0"
checksum: 10/51c77d99f17cf791333467352df8326e0f70f9c517eada65a5e7819b2422f6e655e52319f5406eb578504442ae5f399b6e1d023e41d0c199aaf82879a890db6d
languageName: node
linkType: hard
"pg-protocol@npm:*, pg-protocol@npm:^1.11.0":
version: 1.11.0
resolution: "pg-protocol@npm:1.11.0"
checksum: 10/a70b1b4a3fc5b1be80dfdd65c829a149b8bd9df7488f9c47e0b51c9413aec5eb6da0a9ae9812891d74cd9f2ee90c0e391984a41b64603e7375fcbb9e07070b08
languageName: node
linkType: hard
"pg-types@npm:2.2.0, pg-types@npm:^2.2.0":
version: 2.2.0
resolution: "pg-types@npm:2.2.0"
dependencies:
pg-int8: "npm:1.0.1"
postgres-array: "npm:~2.0.0"
postgres-bytea: "npm:~1.0.0"
postgres-date: "npm:~1.0.4"
postgres-interval: "npm:^1.1.0"
checksum: 10/87a84d4baa91378d3a3da6076c69685eb905d1087bf73525ae1ba84b291b9dd8738c6716b333d8eac6cec91bf087237adc3e9281727365e9cbab0d9d072778b1
languageName: node
linkType: hard
"pg@npm:^8.18.0":
version: 8.18.0
resolution: "pg@npm:8.18.0"
dependencies:
pg-cloudflare: "npm:^1.3.0"
pg-connection-string: "npm:^2.11.0"
pg-pool: "npm:^3.11.0"
pg-protocol: "npm:^1.11.0"
pg-types: "npm:2.2.0"
pgpass: "npm:1.0.5"
peerDependencies:
pg-native: ">=3.0.1"
dependenciesMeta:
pg-cloudflare:
optional: true
peerDependenciesMeta:
pg-native:
optional: true
checksum: 10/91c622f179f60df08ab7aa9b05a890567ea47f2d7984377b64e88e1eba1c42787324b7fc5ff00e109a757f3329dc4b57c73502603ae2765d1827b2082abbdcfa
languageName: node
linkType: hard
"pgpass@npm:1.0.5":
version: 1.0.5
resolution: "pgpass@npm:1.0.5"
dependencies:
split2: "npm:^4.1.0"
checksum: 10/0a6f3bf76e36bdb3c20a7e8033140c732767bba7e81f845f7489fc3123a2bd6e3b8e704f08cba86b117435414b5d2422e20ba9d5f2efb6f0c75c9efca73e8e87
languageName: node
linkType: hard
"picocolors@npm:^1.1.1":
version: 1.1.1
resolution: "picocolors@npm:1.1.1"
@@ -2238,6 +2330,36 @@ __metadata:
languageName: node
linkType: hard
"postgres-array@npm:~2.0.0":
version: 2.0.0
resolution: "postgres-array@npm:2.0.0"
checksum: 10/aff99e79714d1271fe942fec4ffa2007b755e7e7dc3d2feecae3f1ceecb86fd3637c8138037fc3d9e7ec369231eeb136843c0b25927bf1ce295245a40ef849b4
languageName: node
linkType: hard
"postgres-bytea@npm:~1.0.0":
version: 1.0.1
resolution: "postgres-bytea@npm:1.0.1"
checksum: 10/fc5fa49f59ac1f0eba841db55bd6b6c2232d1575d1734311e2097a2d5fd8b58e1239cbd64eeaf0b6752268fe7d2819e002bf90b0afd333be9f2b9d157d2cd7e7
languageName: node
linkType: hard
"postgres-date@npm:~1.0.4":
version: 1.0.7
resolution: "postgres-date@npm:1.0.7"
checksum: 10/571ef45bec4551bb5d608c31b79987d7a895141f7d6c7b82e936a52d23d97474c770c6143e5cf8936c1cdc8b0dfd95e79f8136bf56a90164182a60f242c19f2b
languageName: node
linkType: hard
"postgres-interval@npm:^1.1.0":
version: 1.2.0
resolution: "postgres-interval@npm:1.2.0"
dependencies:
xtend: "npm:^4.0.0"
checksum: 10/746b71f93805ae33b03528e429dc624706d1f9b20ee81bf743263efb6a0cd79ae02a642a8a480dbc0f09547b4315ab7df6ce5ec0be77ed700bac42730f5c76b2
languageName: node
linkType: hard
"prettier@npm:*, prettier@npm:^3.7.4":
version: 3.7.4
resolution: "prettier@npm:3.7.4"
@@ -2695,6 +2817,7 @@ __metadata:
"@types/cors": "npm:^2.8.19"
"@types/express": "npm:^5.0.6"
"@types/node": "npm:^25.0.3"
"@types/pg": "npm:^8.16.0"
"@types/supertest": "npm:^6.0.3"
amqp-connection-manager: "npm:^5.0.0"
amqplib: "npm:^0.10.9"
@@ -2703,6 +2826,7 @@ __metadata:
cors: "npm:^2.8.5"
dotenv: "npm:^17.2.3"
express: "npm:^5.2.1"
pg: "npm:^8.18.0"
prettier: "npm:^3.7.4"
supertest: "npm:^7.1.4"
tsc-alias: "npm:^1.8.16"
@@ -2801,6 +2925,13 @@ __metadata:
languageName: node
linkType: hard
"split2@npm:^4.1.0":
version: 4.2.0
resolution: "split2@npm:4.2.0"
checksum: 10/09bbefc11bcf03f044584c9764cd31a252d8e52cea29130950b26161287c11f519807c5e54bd9e5804c713b79c02cefe6a98f4688630993386be353e03f534ab
languageName: node
linkType: hard
"ssri@npm:^13.0.0":
version: 13.0.0
resolution: "ssri@npm:13.0.0"
@@ -3268,6 +3399,13 @@ __metadata:
languageName: node
linkType: hard
"xtend@npm:^4.0.0":
version: 4.0.2
resolution: "xtend@npm:4.0.2"
checksum: 10/ac5dfa738b21f6e7f0dd6e65e1b3155036d68104e67e5d5d1bde74892e327d7e5636a076f625599dc394330a731861e87343ff184b0047fef1360a7ec0a5a36a
languageName: node
linkType: hard
"y18n@npm:^5.0.5":
version: 5.0.8
resolution: "y18n@npm:5.0.8"