diff --git a/package.json b/package.json index 158e8d2..918107c 100644 --- a/package.json +++ b/package.json @@ -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" } -} \ No newline at end of file +} diff --git a/packages/shared/domain/Result.ts b/packages/shared/domain/Result.ts new file mode 100644 index 0000000..f2b8b6f --- /dev/null +++ b/packages/shared/domain/Result.ts @@ -0,0 +1,4 @@ +export type Result = { + error: E | undefined, + data: D | undefined +} diff --git a/packages/shared/infrastructure/DBClient.ts b/packages/shared/infrastructure/DBClient.ts new file mode 100644 index 0000000..d6aeee3 --- /dev/null +++ b/packages/shared/infrastructure/DBClient.ts @@ -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( + text: string, + params?: any[] + ): Promise> { + return await this.pgPool.query(text, params); + }; + +} diff --git a/packages/sim-consumidor-objenious/aplication/Sim.controller.ts b/packages/sim-consumidor-objenious/aplication/Sim.controller.ts index 9d6e129..8523177 100644 --- a/packages/sim-consumidor-objenious/aplication/Sim.controller.ts +++ b/packages/sim-consumidor-objenious/aplication/Sim.controller.ts @@ -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(msg: ConsumeMessage, usecase: () => Promise>) { + try { + const result = await usecase() + if (result.error == undefined) { + await this.eventBus.ack(msg) + return result + } else { + console.error("Error general procesando el caso de uso", result.error) } + } 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) { diff --git a/packages/sim-consumidor-objenious/aplication/Sim.usecases.ts b/packages/sim-consumidor-objenious/aplication/Sim.usecases.ts index 066ab6b..542110c 100644 --- a/packages/sim-consumidor-objenious/aplication/Sim.usecases.ts +++ b/packages/sim-consumidor-objenious/aplication/Sim.usecases.ts @@ -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> { + 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 >{ 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> { + 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 >{ + error: undefined, + data: true + } } catch (error) { console.error("Error pausa", error) + return >{ + error: "Error reactivando la sim" + pauseData.identifier, + data: true + } } } } + + public suspend(suspendData: ActivationData): () => Promise> { + 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 >{ + 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> { + 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 >{ + error: undefined, + data: true + } + } catch (error) { + console.error("Error pausa", error) + return >{ + error: "Error cancelando/terminate la sim" + terminationData.identifier, + data: undefined + } + } + } + } + + } diff --git a/packages/sim-consumidor-objenious/config/postgreConfig.ts b/packages/sim-consumidor-objenious/config/postgreConfig.ts new file mode 100644 index 0000000..5a9418f --- /dev/null +++ b/packages/sim-consumidor-objenious/config/postgreConfig.ts @@ -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, +}); diff --git a/yarn.lock b/yarn.lock index 5eb5a59..e962808 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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"