Compare commits
46 Commits
alarmas-ob
...
WEBINT-334
| Author | SHA1 | Date | |
|---|---|---|---|
| e1450c6e97 | |||
| e40a19bbfb | |||
| fbdb64f3a1 | |||
| 9a29f49669 | |||
| c2081191ae | |||
| f0f3827fd0 | |||
| ee8f84bc57 | |||
| f95677d503 | |||
| 59b0b57ec2 | |||
| 9174b0b6a4 | |||
| e62c49ce91 | |||
| 32990b4dcd | |||
| da2413002b | |||
| fdbb81ba64 | |||
| 964ea6add9 | |||
| 602878acf4 | |||
| 0aa52feaac | |||
| 15b70309da | |||
| 7001fccbf7 | |||
| cffee785b2 | |||
| 33d260310c | |||
| e359acc1d5 | |||
| bb4bce4a6d | |||
| eac74ef0cd | |||
| 1dc4eb5648 | |||
| a35a6c2b60 | |||
| 1f78f4a3e1 | |||
| 1e98559f3a | |||
| ef0f860b9d | |||
| 0bff55379f | |||
| 4d34308a13 | |||
| 70bf73b0a4 | |||
| e3849d8217 | |||
| d9854a12a8 | |||
| 48d387a8da | |||
| 93d3e13793 | |||
| 031f5d5cf0 | |||
| 047669bab2 | |||
| 5ea5939e3a | |||
| 7ff3f13af4 | |||
| a9589f578b | |||
| a27e4b30d2 | |||
| 4168949b9e | |||
| e6ff54a15d | |||
| 3956797020 | |||
| 7d88359263 |
5
.env
5
.env
@@ -20,10 +20,13 @@ POSTGRES_DB=postgres
|
|||||||
POSTGRES_DATABASE=postgres
|
POSTGRES_DATABASE=postgres
|
||||||
POSTGRES_PORT=5433
|
POSTGRES_PORT=5433
|
||||||
POSTGRES_USER=postgres
|
POSTGRES_USER=postgres
|
||||||
POSTGRES_PASSWORD=1234
|
POSTGRES_PASSWORD='1234'
|
||||||
|
|
||||||
# Para el postgres local para generar el script de resultado de migraciones
|
# Para el postgres local para generar el script de resultado de migraciones
|
||||||
PGHOST=localhost
|
PGHOST=localhost
|
||||||
PGUSER=alvar
|
PGUSER=alvar
|
||||||
PGPASSWORD=alvar
|
PGPASSWORD=alvar
|
||||||
PGPORT=5433
|
PGPORT=5433
|
||||||
|
|
||||||
|
# Proxy
|
||||||
|
CONNECTIONS_URL=https://sim-connections.savefamilygps.net
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
/**
|
||||||
|
* Para la tarea WEBINT-328-Pausas-cacelaciones.
|
||||||
|
* Almacena las pausas/cancelaciones que no se han podido hacer porque la linea esta en
|
||||||
|
* "Test"
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
DO $$ BEGIN
|
||||||
|
CREATE TYPE SUSPENDTERMINATE AS ENUM ('suspend','terminate');
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS pause_cancel_tasks (
|
||||||
|
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
|
||||||
|
iccid TEXT NOT NULL,
|
||||||
|
operation_type SUSPENDTERMINATE,
|
||||||
|
last_checked TIMESTAMPTZ, -- Última vez que se ha comprobado que no esté en test
|
||||||
|
activation_date TIMESTAMPTZ, -- Fecha de activacion para comprobar si ha pasdo un mes
|
||||||
|
next_check TIMESTAMPTZ, -- Si se ha comprobado se asignará la siguiente fecha de revision
|
||||||
|
|
||||||
|
completed_date TIMESTAMPTZ, -- Cuando se ha completado, para bien o mal.
|
||||||
|
error TEXT,
|
||||||
|
action_data JSONB -- datos de la operacion original.
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Indice de las tareas que no han terminado
|
||||||
|
CREATE INDEX idx_pause_cancel_tasks_pending
|
||||||
|
ON pause_cancel_tasks (next_check)
|
||||||
|
WHERE completed_date IS NULL;
|
||||||
|
|
||||||
|
|
||||||
@@ -8,6 +8,8 @@ RUN corepack enable
|
|||||||
COPY ./dist/packages ./packages
|
COPY ./dist/packages ./packages
|
||||||
COPY ./.yarnrc.yml ./
|
COPY ./.yarnrc.yml ./
|
||||||
COPY ./docs ./docs
|
COPY ./docs ./docs
|
||||||
|
# Para las migraciones
|
||||||
|
COPY ./deployment ./deployment
|
||||||
|
|
||||||
COPY ./package.json ./
|
COPY ./package.json ./
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
cd /home
|
cd /home
|
||||||
|
|
||||||
cd /home/node/app && yarn start
|
cd /home/node/app
|
||||||
|
yarn migrate
|
||||||
|
yarn start
|
||||||
|
|||||||
@@ -69,7 +69,6 @@ pipeline {
|
|||||||
cleanRemote: false,
|
cleanRemote: false,
|
||||||
remoteDirectory: "$APP_REMOTE_PATH",
|
remoteDirectory: "$APP_REMOTE_PATH",
|
||||||
sourceFiles: "deployment/database/**/*",
|
sourceFiles: "deployment/database/**/*",
|
||||||
removePrefix: "deployment",
|
|
||||||
),
|
),
|
||||||
sshTransfer(
|
sshTransfer(
|
||||||
cleanRemote: false,
|
cleanRemote: false,
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ post {
|
|||||||
}
|
}
|
||||||
|
|
||||||
body:form-urlencoded {
|
body:form-urlencoded {
|
||||||
iccid: 8933201125068886692
|
iccid: 8935103196306448300
|
||||||
offer: SAVEFAMILY1
|
offer: SAVEFAMILY1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ post {
|
|||||||
}
|
}
|
||||||
|
|
||||||
body:form-urlencoded {
|
body:form-urlencoded {
|
||||||
iccid: 8933201125068886692
|
iccid: 8933201125068887054
|
||||||
}
|
}
|
||||||
|
|
||||||
settings {
|
settings {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ params:query {
|
|||||||
}
|
}
|
||||||
|
|
||||||
body:form-urlencoded {
|
body:form-urlencoded {
|
||||||
iccid: 8933201125065160414
|
iccid: 8935103196306448300
|
||||||
}
|
}
|
||||||
|
|
||||||
settings {
|
settings {
|
||||||
|
|||||||
21
docs/sim-api/ReActivate.bru
Normal file
21
docs/sim-api/ReActivate.bru
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
meta {
|
||||||
|
name: ReActivate
|
||||||
|
type: http
|
||||||
|
seq: 13
|
||||||
|
}
|
||||||
|
|
||||||
|
post {
|
||||||
|
url: {{baseurl}}/sim/reActivate
|
||||||
|
body: formUrlEncoded
|
||||||
|
auth: inherit
|
||||||
|
}
|
||||||
|
|
||||||
|
body:form-urlencoded {
|
||||||
|
iccid: 8935103196306448300
|
||||||
|
~offer: SAVEFAMILY1
|
||||||
|
}
|
||||||
|
|
||||||
|
settings {
|
||||||
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
vars {
|
vars {
|
||||||
baseurl: http://localhost:3000
|
baseurl: http://localhost:3000
|
||||||
}
|
}
|
||||||
|
color: #2E8A54
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
vars {
|
vars {
|
||||||
baseurl: https://sf-sims.savefamilygps.net
|
baseurl: https://sf-sims.savefamilygps.net
|
||||||
}
|
}
|
||||||
|
color: #CE4F3B
|
||||||
|
|||||||
4
docs/sim-api/environments/simconnections.bru
Normal file
4
docs/sim-api/environments/simconnections.bru
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
vars {
|
||||||
|
baseurl: http://sim-connections.savefamilygps.net
|
||||||
|
}
|
||||||
|
color: #C77A0F
|
||||||
20
docs/sim-api/test proxy.bru
Normal file
20
docs/sim-api/test proxy.bru
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
meta {
|
||||||
|
name: test proxy
|
||||||
|
type: http
|
||||||
|
seq: 14
|
||||||
|
}
|
||||||
|
|
||||||
|
get {
|
||||||
|
url: {{baseurl}}/simconnections/alai/select?iccid=1111111111111111111
|
||||||
|
body: none
|
||||||
|
auth: inherit
|
||||||
|
}
|
||||||
|
|
||||||
|
params:query {
|
||||||
|
iccid: 1111111111111111111
|
||||||
|
}
|
||||||
|
|
||||||
|
settings {
|
||||||
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
|
}
|
||||||
23
docs/sim-nos/Select Page.yml
Normal file
23
docs/sim-nos/Select Page.yml
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
info:
|
||||||
|
name: Select Page
|
||||||
|
type: http
|
||||||
|
seq: 6
|
||||||
|
|
||||||
|
http:
|
||||||
|
method: GET
|
||||||
|
url: "{{baseurl}}/selectPage"
|
||||||
|
params:
|
||||||
|
- name: iccid
|
||||||
|
value: "8935103196306448300"
|
||||||
|
type: query
|
||||||
|
disabled: true
|
||||||
|
body:
|
||||||
|
type: json
|
||||||
|
data: ""
|
||||||
|
auth: inherit
|
||||||
|
|
||||||
|
settings:
|
||||||
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
|
followRedirects: true
|
||||||
|
maxRedirects: 5
|
||||||
25
docs/sim-nos/Select.yml
Normal file
25
docs/sim-nos/Select.yml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
info:
|
||||||
|
name: Select
|
||||||
|
type: http
|
||||||
|
seq: 5
|
||||||
|
|
||||||
|
http:
|
||||||
|
method: GET
|
||||||
|
url: "{{baseurl}}/select?iccid=8935103196306448300"
|
||||||
|
params:
|
||||||
|
- name: iccid
|
||||||
|
value: "8935103196306448300"
|
||||||
|
type: query
|
||||||
|
body:
|
||||||
|
type: json
|
||||||
|
data: |-
|
||||||
|
{
|
||||||
|
"iccid": "8933201125068890066"
|
||||||
|
}
|
||||||
|
auth: inherit
|
||||||
|
|
||||||
|
settings:
|
||||||
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
|
followRedirects: true
|
||||||
|
maxRedirects: 5
|
||||||
7
docs/sim-nos/environments/local.yml
Normal file
7
docs/sim-nos/environments/local.yml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
name: local
|
||||||
|
color: "#2E8A54"
|
||||||
|
variables:
|
||||||
|
- name: baseurl
|
||||||
|
value: http://localhost:3001
|
||||||
|
- secret: true
|
||||||
|
name: token
|
||||||
7
docs/sim-nos/environments/prod.yml
Normal file
7
docs/sim-nos/environments/prod.yml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
name: prod
|
||||||
|
color: "#CE4F3B"
|
||||||
|
variables:
|
||||||
|
- name: baseurl
|
||||||
|
value: https://nosconnectcenter-api.iot-x.com
|
||||||
|
- secret: true
|
||||||
|
name: token
|
||||||
10
docs/sim-nos/opencollection.yml
Normal file
10
docs/sim-nos/opencollection.yml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
opencollection: 1.0.0
|
||||||
|
|
||||||
|
info:
|
||||||
|
name: sim-nos
|
||||||
|
bundled: false
|
||||||
|
extensions:
|
||||||
|
bruno:
|
||||||
|
ignore:
|
||||||
|
- node_modules
|
||||||
|
- .git
|
||||||
22
docs/sim-nos/subscriber actions.yml
Normal file
22
docs/sim-nos/subscriber actions.yml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
info:
|
||||||
|
name: subscriber actions
|
||||||
|
type: http
|
||||||
|
seq: 1
|
||||||
|
|
||||||
|
http:
|
||||||
|
method: GET
|
||||||
|
url: "{{baseurl}}/subscribers/{{iccid}}/actions"
|
||||||
|
auth:
|
||||||
|
type: bearer
|
||||||
|
token: "{{token}}"
|
||||||
|
|
||||||
|
runtime:
|
||||||
|
variables:
|
||||||
|
- name: iccid
|
||||||
|
value: "8935103196306448300"
|
||||||
|
|
||||||
|
settings:
|
||||||
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
|
followRedirects: true
|
||||||
|
maxRedirects: 5
|
||||||
22
docs/sim-nos/subscriber info.yml
Normal file
22
docs/sim-nos/subscriber info.yml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
info:
|
||||||
|
name: subscriber info
|
||||||
|
type: http
|
||||||
|
seq: 2
|
||||||
|
|
||||||
|
http:
|
||||||
|
method: GET
|
||||||
|
url: "{{baseurl}}/subscribers/{{iccid}}"
|
||||||
|
auth:
|
||||||
|
type: bearer
|
||||||
|
token: "{{token}}"
|
||||||
|
|
||||||
|
runtime:
|
||||||
|
variables:
|
||||||
|
- name: iccid
|
||||||
|
value: "8935103196306448300"
|
||||||
|
|
||||||
|
settings:
|
||||||
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
|
followRedirects: true
|
||||||
|
maxRedirects: 5
|
||||||
22
docs/sim-nos/subscriber products available.yml
Normal file
22
docs/sim-nos/subscriber products available.yml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
info:
|
||||||
|
name: subscriber products available
|
||||||
|
type: http
|
||||||
|
seq: 4
|
||||||
|
|
||||||
|
http:
|
||||||
|
method: GET
|
||||||
|
url: "{{baseurl}}/subscribers/{{iccid}}/products/available"
|
||||||
|
auth:
|
||||||
|
type: bearer
|
||||||
|
token: "{{token}}"
|
||||||
|
|
||||||
|
runtime:
|
||||||
|
variables:
|
||||||
|
- name: iccid
|
||||||
|
value: "8935103196306448300"
|
||||||
|
|
||||||
|
settings:
|
||||||
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
|
followRedirects: true
|
||||||
|
maxRedirects: 5
|
||||||
22
docs/sim-nos/subscribers.yml
Normal file
22
docs/sim-nos/subscribers.yml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
info:
|
||||||
|
name: subscribers
|
||||||
|
type: http
|
||||||
|
seq: 3
|
||||||
|
|
||||||
|
http:
|
||||||
|
method: GET
|
||||||
|
url: "{{baseurl}}/subscribers"
|
||||||
|
auth:
|
||||||
|
type: bearer
|
||||||
|
token: "{{token}}"
|
||||||
|
|
||||||
|
runtime:
|
||||||
|
variables:
|
||||||
|
- name: iccid
|
||||||
|
value: "8935103196306448300"
|
||||||
|
|
||||||
|
settings:
|
||||||
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
|
followRedirects: true
|
||||||
|
maxRedirects: 5
|
||||||
@@ -5,13 +5,13 @@ meta {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get {
|
get {
|
||||||
url: {{actionsUrl}}/massActions?massActionId=5192767
|
url: {{actionsUrl}}/massActions?massActionId=5363116
|
||||||
body: formUrlEncoded
|
body: formUrlEncoded
|
||||||
auth: bearer
|
auth: bearer
|
||||||
}
|
}
|
||||||
|
|
||||||
params:query {
|
params:query {
|
||||||
massActionId: 5192767
|
massActionId: 5363116
|
||||||
~identifier.identifierType: ICCID
|
~identifier.identifierType: ICCID
|
||||||
~identifier.identifiers: 8933201125065160463,8933201125065160422
|
~identifier.identifiers: 8933201125065160463,8933201125065160422
|
||||||
}
|
}
|
||||||
|
|||||||
10
package.json
10
package.json
@@ -7,11 +7,11 @@
|
|||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "vitest watch",
|
"test": "vitest watch",
|
||||||
"build": "yarn workspaces foreach -A --exclude sim-consumidor-nos run build && cp .env dist/ && yarn setup:runtime",
|
"build": "rm -rf ./dist && yarn workspaces foreach -Api run build && cp .env dist/ && yarn setup:runtime",
|
||||||
"setup:runtime": "mkdir -p dist/packages/node_modules && ln -sf ../sim-shared dist/packages/node_modules/sim-shared && ln -sf ../sf-consumidor-objenious dist/packages/node_modules/sim-consumidor-objenious",
|
"setup:runtime": "mkdir -p dist/packages/node_modules && ln -sf ../sim-shared dist/packages/node_modules/sim-shared && ln -sf ../sf-consumidor-objenious dist/packages/node_modules/sim-consumidor-objenious",
|
||||||
"start": "yarn setup:runtime && yarn workspaces foreach -Apiv --exclude sim-consumidor-nos run start",
|
"start": "yarn setup:runtime && yarn workspaces foreach -Apiv run start",
|
||||||
"typecheck": "npx tsc --noEmit",
|
"typecheck": "npx tsc --noEmit",
|
||||||
"dev": "yarn workspaces foreach -Apiv --exclude sim-consumidor-nos run dev ",
|
"dev": "yarn workspaces foreach -Apiv --exclude sim-objenious-cron run dev",
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"lint:fix": "eslint --fix .",
|
"lint:fix": "eslint --fix .",
|
||||||
"format": "prettier --write .",
|
"format": "prettier --write .",
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
"migrate": "yarn db-migrate -e .env -m deployment/database/migrations -t 99.0.0"
|
"migrate": "yarn db-migrate -e .env -m deployment/database/migrations -t 99.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sf-alvar/db-migrate": "1.0.3",
|
"@sf-alvar/db-migrate": "1.0.6",
|
||||||
"@tsconfig/node22": "^22.0.5",
|
"@tsconfig/node22": "^22.0.5",
|
||||||
"amqp-connection-manager": "^5.0.0",
|
"amqp-connection-manager": "^5.0.0",
|
||||||
"amqplib": "^0.10.9",
|
"amqplib": "^0.10.9",
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
"dotenv": "^17.2.3",
|
"dotenv": "^17.2.3",
|
||||||
"express": "^5.2.1",
|
"express": "^5.2.1",
|
||||||
"pg": "^8.18.0",
|
"pg": "^8.18.0",
|
||||||
"typescript": "^5.9.3",
|
"typescript": "^6.0.3",
|
||||||
"uuidv7": "^1.1.0",
|
"uuidv7": "^1.1.0",
|
||||||
"vite": "^7.3.1",
|
"vite": "^7.3.1",
|
||||||
"vite-tsconfig-paths": "^6.0.5"
|
"vite-tsconfig-paths": "^6.0.5"
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
PORT=3000
|
NOS_BASE_URL=localhost
|
||||||
RABBITMQ_USER=guest
|
APP_PORT=3001
|
||||||
RABBITMQ_PASSWORD=guest
|
APP_HOST="0.0.0.0"
|
||||||
|
|
||||||
ENVIORMENT=development
|
ENVIORMENT=development
|
||||||
|
|
||||||
|
NOS_ACCESS_TOKEN=2YGhecTr4+uKbVKxaqBlk2edsrHA2OQY
|
||||||
|
NOS_BASE_URL=https://nosconnectcenter-api.iot-x.com
|
||||||
|
|||||||
@@ -1,65 +1,178 @@
|
|||||||
import { EventBus } from "sim-shared/domain/EventBus.port.js";
|
|
||||||
import { ConsumeMessage } from "amqplib";
|
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 SimNosController {
|
export class SimNosController {
|
||||||
private eventBus: EventBus;
|
|
||||||
private activationUseCases: any;
|
|
||||||
|
|
||||||
private routes = new Map<string, () => void>([
|
|
||||||
["activate", async () => { console.log("caso de uso activate") }],
|
|
||||||
["pause", async () => { console.log("caso de uso pause") }],
|
|
||||||
["cancel", async () => { console.log("caso de uso cancel") }],
|
|
||||||
])
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
eventBus: EventBus
|
private uscases: SimNosUsecases,
|
||||||
|
private eventBus: EventBus,
|
||||||
) {
|
) {
|
||||||
this.eventBus = eventBus
|
|
||||||
|
|
||||||
// No se si hay un sistema mejor
|
|
||||||
// convertor en const () => {} para conservar el contexto??
|
|
||||||
this.recibeMsg = this.recibeMsg.bind(this)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async recibeMsg(msg: ConsumeMessage | null) {
|
private validateMsg(msg: ConsumeMessage | null) {
|
||||||
if (!this.validateActivationMsg(msg)) {
|
if (msg == undefined) return false;
|
||||||
throw new Error("Error consumiendo el mensaje no es valido")
|
const msgData = this.decodeMsg(msg) as SimEvents.general
|
||||||
}
|
if (msgData == undefined || msgData.payload == undefined) throw new Error("Mensaje invalido")
|
||||||
|
return msgData;
|
||||||
|
}
|
||||||
|
|
||||||
msg = msg!
|
private decodeMsg(msg: ConsumeMessage): object | undefined {
|
||||||
|
if (msg.content == undefined) {
|
||||||
const msgParsed = JSON.parse(String(msg.content))
|
console.warn('[Sim.controller] Mensaje vacío');
|
||||||
const msgKey = msg.fields.routingKey.split(".")
|
return undefined;
|
||||||
const accion = msgKey[2]
|
|
||||||
|
|
||||||
if (accion == undefined) {
|
|
||||||
console.error("La routingKey es incorrecta: " + accion)
|
|
||||||
this.eventBus.nack(msg)
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.routes.get(accion) == undefined) {
|
|
||||||
console.error("No hay una ruta definida para la accion")
|
|
||||||
this.eventBus.nack(msg)
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.routes.get(accion)!()
|
// Convertir el Buffer a String (UTF-8)
|
||||||
} catch (err) {
|
const contentJson = JSON.parse(Buffer.from(msg.content).toString('utf8'))
|
||||||
console.log("Error procesando el mensaje")
|
return contentJson;
|
||||||
this.eventBus.nack(msg)
|
|
||||||
} finally {
|
} catch (error) {
|
||||||
this.eventBus.ack(msg)
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODO:
|
* Metodo duplicado se puede generalizar la a una clase sharedController con las funciones basicas
|
||||||
* - Loguear motivos de la no validacion
|
*/
|
||||||
|
private async tryUseCase<T extends any>
|
||||||
|
(msg: ConsumeMessage, usecase: () => Promise<Result<string, T>>): Promise<Result<string, T>> {
|
||||||
|
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.
|
||||||
*/
|
*/
|
||||||
private validateActivationMsg(msg: ConsumeMessage | null) {
|
public selectREST() {
|
||||||
if (msg == undefined) return false;
|
return async (req: Request, res: Response) => {
|
||||||
return true;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
79
packages/sim-consumidor-nos/aplication/SimNOS.router.ts
Normal file
79
packages/sim-consumidor-nos/aplication/SimNOS.router.ts
Normal file
@@ -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 { SimNosController } from "./SimNOS.controller.js";
|
||||||
|
import { EventBus } from "sim-shared/domain/EventBus.port.js";
|
||||||
|
import { Result } from "sim-shared/domain/Result.js";
|
||||||
|
|
||||||
|
type FuncType = ((m: ConsumeMessage) => Promise<Result<string, any>>)
|
||||||
|
|
||||||
|
export class SimNosRouter {
|
||||||
|
private readonly routes: Map<string, FuncType>;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly simController: SimNosController,
|
||||||
|
private readonly eventBus: EventBus
|
||||||
|
) {
|
||||||
|
this.routes = new Map<string, FuncType>([
|
||||||
|
//["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<void> => {
|
||||||
|
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];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 { NosHttpClient } from "#infrastructure/NosHttpClient.js";
|
||||||
|
import { NosRepository } from "#infrastructure/NosRepository.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 SimNosUsecases {
|
||||||
|
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<T, R>(
|
||||||
|
func: (_: T) => Promise<Result<string, R>>,
|
||||||
|
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 {}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|||||||
39
packages/sim-consumidor-nos/aplication/httpValidators.ts
Normal file
39
packages/sim-consumidor-nos/aplication/httpValidators.ts
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import { BodyValidator, Validator } from "sim-shared/aplication/BodyValidator.js";
|
||||||
|
|
||||||
|
const iccidNotNull = <Validator<{ iccid: unknown }>>{
|
||||||
|
field: "iccid",
|
||||||
|
errorMsg: "El iccid no está definido",
|
||||||
|
validationFunc: (a: { iccid: unknown }) => {
|
||||||
|
return (a.iccid != null && a.iccid != undefined)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const iccidValueOrArray = <Validator<{ iccid: unknown }>>{
|
||||||
|
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 = <Validator<{ iccid: string | string[] }>>{
|
||||||
|
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,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
@@ -1,5 +1,16 @@
|
|||||||
import { loadEnvFile } from "node:process";
|
import { loadEnvFile } from "node:process";
|
||||||
loadEnvFile("../../.env")
|
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 = {
|
export const env = {
|
||||||
ENVIRONMENT: process.env.ENVIORMENT,
|
ENVIRONMENT: process.env.ENVIORMENT,
|
||||||
@@ -18,5 +29,12 @@ export const env = {
|
|||||||
RABBITMQ_SECURE: process.env.RABBITMQ_SECURE,
|
RABBITMQ_SECURE: process.env.RABBITMQ_SECURE,
|
||||||
RABBITMQ_RETRY_INTERVAL: process.env.RABBITMQ_INTERVAL,
|
RABBITMQ_RETRY_INTERVAL: process.env.RABBITMQ_INTERVAL,
|
||||||
RABBITMQ_VHOST: String(process.env.RABBITMQ_VHOST),
|
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)
|
||||||
};
|
};
|
||||||
|
|
||||||
72
packages/sim-consumidor-nos/config/eventBus.config.ts
Normal file
72
packages/sim-consumidor-nos/config/eventBus.config.ts
Normal file
@@ -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 = <RMQConnectionParams>{
|
||||||
|
username: rmqUser,
|
||||||
|
password: rmqPass,
|
||||||
|
vhost: rmqVhost,
|
||||||
|
hostname: rmqHost,
|
||||||
|
port: rmqPort,
|
||||||
|
secure: rmqSecure,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const QUEUES = {
|
||||||
|
NOS: "sim.nos",
|
||||||
|
NOSDLX: "sim.nos.dlx",
|
||||||
|
NOSDEL: "sim.nos.delayed",
|
||||||
|
}
|
||||||
|
|
||||||
|
const EXCHANGES = {
|
||||||
|
MAIN: "sim.exchange",
|
||||||
|
DLX: "sim.ex.nos.dlx",
|
||||||
|
DEL: "sim.ex.nos.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
|
||||||
|
const BASE_NOS_KEY = "sim.nos.#"
|
||||||
|
|
||||||
|
await channel.assertExchange(EXCHANGES.DEL, "topic")
|
||||||
|
await channel.assertExchange(EXCHANGES.DLX, "topic")
|
||||||
|
await channel.assertExchange(EXCHANGES.MAIN, "topic")
|
||||||
|
|
||||||
|
await channel.assertQueue(QUEUES.NOS)
|
||||||
|
await channel.assertQueue(QUEUES.NOSDLX)
|
||||||
|
await channel.assertQueue(QUEUES.NOSDEL, {
|
||||||
|
durable: true,
|
||||||
|
arguments: {
|
||||||
|
'x-message-ttl': DELAY,
|
||||||
|
'x-dead-letter-exchange': EXCHANGES.MAIN,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Cola dead-letter
|
||||||
|
await channel.bindQueue(QUEUES.NOSDLX, EXCHANGES.DLX, "sim.nos.#")
|
||||||
|
// Cola delay
|
||||||
|
await channel.bindQueue(QUEUES.NOSDEL, EXCHANGES.DEL, BASE_NOS_KEY)
|
||||||
|
// Cola nos -> main exchange
|
||||||
|
await channel.bindQueue(QUEUES.NOS, EXCHANGES.MAIN, BASE_NOS_KEY)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function startRMQClient() {
|
||||||
|
await rabbitmqEventBus.connect()
|
||||||
|
return rabbitmqEventBus
|
||||||
|
}
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
import { RabbitMQEventBus, RMQConnectionParams } from "sim-shared/infrastructure/RabbitMQEventBus.js"
|
|
||||||
import { env } from "./env"
|
|
||||||
|
|
||||||
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 = <RMQConnectionParams>{
|
|
||||||
username: rmqUser,
|
|
||||||
password: rmqPass,
|
|
||||||
vhost: rmqVhost,
|
|
||||||
hostname: rmqHost,
|
|
||||||
port: rmqPort,
|
|
||||||
secure: rmqSecure,
|
|
||||||
}
|
|
||||||
|
|
||||||
export const rabbitmqEventBus = new RabbitMQEventBus({
|
|
||||||
connectionParams: rmqConnOptions
|
|
||||||
})
|
|
||||||
|
|
||||||
export async function startRMQClient() {
|
|
||||||
await rabbitmqEventBus.connect().catch(async e => {
|
|
||||||
console.error("Error en la conexion RMQ")
|
|
||||||
await rabbitmqEventBus.connect()
|
|
||||||
})
|
|
||||||
|
|
||||||
// Bindings especificos, deberia meterlos en la clase
|
|
||||||
try {
|
|
||||||
await rabbitmqEventBus.channel?.assertQueue("sim.nos")
|
|
||||||
} catch {
|
|
||||||
console.log("[i] Cola de sims de nos creada")
|
|
||||||
await rabbitmqEventBus.channel?.bindQueue("sim.nos", "sim.exchange", "sim.nos.*")
|
|
||||||
}
|
|
||||||
|
|
||||||
return rabbitmqEventBus
|
|
||||||
}
|
|
||||||
18
packages/sim-consumidor-nos/config/postgreConfig.ts
Normal file
18
packages/sim-consumidor-nos/config/postgreConfig.ts
Normal file
@@ -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
|
||||||
|
})
|
||||||
131
packages/sim-consumidor-nos/domain/NosAPI.ts
Normal file
131
packages/sim-consumidor-nos/domain/NosAPI.ts
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
export namespace NosApi {
|
||||||
|
|
||||||
|
export type ActivationData = {
|
||||||
|
/**
|
||||||
|
The unique physical subscriber identifier:
|
||||||
|
Cellular - the ICCID
|
||||||
|
Non - IP - the EUI
|
||||||
|
Satellite - the IMEI
|
||||||
|
*/
|
||||||
|
physicalId: string,
|
||||||
|
|
||||||
|
/**
|
||||||
|
example: 447000000001
|
||||||
|
The unique network subscriber identifier:
|
||||||
|
|
||||||
|
Cellular subscriber - the MSISDN
|
||||||
|
Non - IP subscriber - the Device EUI
|
||||||
|
Satellite subscriber - the Subscription ID
|
||||||
|
*/
|
||||||
|
subscriberId: string,
|
||||||
|
|
||||||
|
/**
|
||||||
|
example: 9999
|
||||||
|
If the subscriber uses Circuit Switched Data(CSD), this field displays its data number.If the subscriber does not use CSD, this field is null.
|
||||||
|
*/
|
||||||
|
dataNumber: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
example: 172.0.0.1
|
||||||
|
The subscriber IP address.
|
||||||
|
*/
|
||||||
|
ip: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
example: 234150000000001
|
||||||
|
The subscriber IMSI.
|
||||||
|
*/
|
||||||
|
imsi: string
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
type OkResponse<T> = {
|
||||||
|
error?: undefined | null,
|
||||||
|
content: T
|
||||||
|
}
|
||||||
|
|
||||||
|
type ErrorResponse<E = GeneralError> = {
|
||||||
|
content?: undefined | null,
|
||||||
|
error: E
|
||||||
|
}
|
||||||
|
|
||||||
|
type GeneralError = {
|
||||||
|
children?: string[],
|
||||||
|
code: string,
|
||||||
|
message: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ActivateResponseOK = OkResponse<ActivationData>
|
||||||
|
export type ActivateResponseError = ErrorResponse
|
||||||
|
export type ActivateResponse = ActivateResponseOK | ActivateResponseError
|
||||||
|
|
||||||
|
export type LineDataResponseOK = OkResponse<LineData>
|
||||||
|
export type LineDataResponseError = ErrorResponse
|
||||||
|
export type LineDataResponse = LineDataResponseOK | LineDataResponseError
|
||||||
|
|
||||||
|
export type PageDataResponseOk = OkResponse<LineData[]>
|
||||||
|
export type PageDataResponseError = OkResponse<LineData[]>
|
||||||
|
export type PageResponse = PageDataResponseOk | PageDataResponseError
|
||||||
|
|
||||||
|
export type LineData = {
|
||||||
|
physicalId: string
|
||||||
|
subscriberId: string
|
||||||
|
imsi: string
|
||||||
|
nickname: string
|
||||||
|
operatorCode: string
|
||||||
|
tariffName: string
|
||||||
|
lineRental: number
|
||||||
|
contractLength: number
|
||||||
|
isBarred: boolean
|
||||||
|
isActive: boolean
|
||||||
|
terminateDate: any
|
||||||
|
groupId: number
|
||||||
|
subscriberType: any
|
||||||
|
connectionDate: string
|
||||||
|
expiryDate: string
|
||||||
|
networkState: NetworkState
|
||||||
|
billingState: BillingState
|
||||||
|
operatorName: string
|
||||||
|
imei: string
|
||||||
|
dataUsage?: number
|
||||||
|
eid?: string
|
||||||
|
smdpProvider?: string
|
||||||
|
related?: {
|
||||||
|
parent: RelatedItem,
|
||||||
|
profiles: RelatedItem[]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type RelatedItem = {
|
||||||
|
physicalId: string
|
||||||
|
isEnabledOnParent: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NetworkState = {
|
||||||
|
currentStateId: number
|
||||||
|
currentState: string
|
||||||
|
isTransferring: boolean
|
||||||
|
lastTransferred: number
|
||||||
|
isOnline: boolean
|
||||||
|
lastSeenOnline: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export type BillingState = {
|
||||||
|
currentStateId: number
|
||||||
|
currentState: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type BarData = {
|
||||||
|
product: string
|
||||||
|
description: string
|
||||||
|
enabled: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export type BarResponseOk = OkResponse<BarData>
|
||||||
|
export type BarResponseError = ErrorResponse
|
||||||
|
export type BarResponse = BarResponseOk | BarResponseError
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -1,14 +1,74 @@
|
|||||||
|
import express from "express"
|
||||||
import { startRMQClient } from "#config/eventBusConfig"
|
import cors from 'cors';
|
||||||
|
import { SimNosRouter } from "./aplication/SimNOS.router.js"
|
||||||
import { SimNosController } from "./aplication/SimNOS.controller.js"
|
import { SimNosController } from "./aplication/SimNOS.controller.js"
|
||||||
|
import { SimNosUsecases } from "./aplication/SimNOS.usecases.js"
|
||||||
|
import { NosHttpClient } from "./infrastructure/NosHttpClient.js"
|
||||||
|
import { env } from "#config/env/env.js"
|
||||||
|
import { NosRepository } from "./infrastructure/NosRepository.js"
|
||||||
|
import { OrderRepository } from "sim-shared/infrastructure/OrderRepository.js";
|
||||||
|
import { pgClient } from "#config/postgreConfig.js";
|
||||||
|
import { startRMQClient } from "#config/eventBus.config.js";
|
||||||
|
|
||||||
|
const RMQ_QUEUE = "sim.nos"
|
||||||
|
const NOS_BASE_URL = env.NOS_BASE_URL
|
||||||
|
const PORT = env.APP_PORT
|
||||||
|
const HOSTNAME = env.APP_HOST
|
||||||
|
|
||||||
async function startWorker() {
|
async function startWorker() {
|
||||||
|
// Instancia de dependencias
|
||||||
|
|
||||||
const rmqClient = await startRMQClient()
|
const rmqClient = await startRMQClient()
|
||||||
|
const nosHttpClient = new NosHttpClient(
|
||||||
|
NOS_BASE_URL
|
||||||
|
)
|
||||||
|
|
||||||
|
const nosRepository = new NosRepository(
|
||||||
|
nosHttpClient
|
||||||
|
)
|
||||||
|
|
||||||
|
const orderRepository = new OrderRepository(
|
||||||
|
pgClient
|
||||||
|
)
|
||||||
|
|
||||||
|
const simUsecases = new SimNosUsecases(
|
||||||
|
nosHttpClient,
|
||||||
|
nosRepository,
|
||||||
|
orderRepository
|
||||||
|
)
|
||||||
|
|
||||||
const simController = new SimNosController(
|
const simController = new SimNosController(
|
||||||
|
simUsecases,
|
||||||
rmqClient
|
rmqClient
|
||||||
)
|
)
|
||||||
|
|
||||||
rmqClient.consume("sim.nos", simController.recibeMsg)
|
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()
|
startWorker()
|
||||||
|
|||||||
41
packages/sim-consumidor-nos/infrastructure/NosHttpClient.ts
Normal file
41
packages/sim-consumidor-nos/infrastructure/NosHttpClient.ts
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import axios, { AxiosInstance } from "axios";
|
||||||
|
import { env } from "#config/env/env.js"
|
||||||
|
|
||||||
|
export class NosHttpClient {
|
||||||
|
public client: AxiosInstance;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private baseURL: string,
|
||||||
|
//private jwtManager: JWTProvider<any>
|
||||||
|
) {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
177
packages/sim-consumidor-nos/infrastructure/NosRepository.ts
Normal file
177
packages/sim-consumidor-nos/infrastructure/NosRepository.ts
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
import { Result } from "sim-shared/domain/Result.js";
|
||||||
|
import { NosHttpClient } from "./NosHttpClient.js";
|
||||||
|
import { NosApi } from "#domain/NosAPI.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<E, T>(promise: Promise<AxiosResponse<T>>): Promise<Result<string, T>> {
|
||||||
|
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<Result<string, NosApi.LineData>> {
|
||||||
|
const PATH = "/subscribers/" + iccid
|
||||||
|
console.log("PAth", PATH)
|
||||||
|
const lineRequest = this.httpClient.get<NosApi.LineDataResponseOK>(PATH)
|
||||||
|
const lineResponse = await this.manageNosRequest<string, NosApi.LineDataResponseOK>(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<Result<string, any>> {
|
||||||
|
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<string, any>(pageRequest)
|
||||||
|
if (pageResponse.error != undefined) {
|
||||||
|
return pageResponse
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
data: pageResponse.data.content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getLinesInfo(iccid: string[]) /*Promise<Result<string, NosApi.LineData>>*/ {
|
||||||
|
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<NosApi.LineDataResponseOK>(PATH)
|
||||||
|
const resp = await this.manageNosRequest<string, NosApi.LineDataResponseOK>(req)
|
||||||
|
|
||||||
|
if (resp.error != undefined) {
|
||||||
|
return resp
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
//@ts-expect-error
|
||||||
|
data: resp.data.content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async activateSim(iccid: string): Promise<Result<string, NosApi.ActivationData>> {
|
||||||
|
const PATH = '/provisioning'
|
||||||
|
const PRODUCT_ID = 1330 // No se que es, preguntar a Ivan
|
||||||
|
const data = {
|
||||||
|
productSetId: PRODUCT_ID
|
||||||
|
}
|
||||||
|
|
||||||
|
const req = this.httpClient.post<NosApi.ActivateResponseOK>(PATH, data)
|
||||||
|
const resp = await this.manageNosRequest<string, NosApi.ActivateResponseOK>(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<NosApi.BarResponseOk>(PATH, data)
|
||||||
|
const resp = await this.manageNosRequest<string, NosApi.BarResponseOk>(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<NosApi.BarResponseOk>(PATH, data)
|
||||||
|
const resp = await this.manageNosRequest<string, NosApi.BarResponseOk>(req)
|
||||||
|
|
||||||
|
if (resp.error != undefined) {
|
||||||
|
return resp
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
data: resp.data.content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,17 +1,8 @@
|
|||||||
{
|
{
|
||||||
"name": "sim-consumidor-nos",
|
"name": "sim-consumidor-nos",
|
||||||
"version": "1.0.0",
|
"type": "module",
|
||||||
"description": "consumidor generico de eventos de NOS",
|
"description": "consumidor generico de eventos de NOS",
|
||||||
"main": "index.ts",
|
"main": "index.ts",
|
||||||
"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"
|
|
||||||
},
|
|
||||||
"author": "",
|
|
||||||
"license": "ISC",
|
|
||||||
"packageManager": "yarn@4.12.0",
|
|
||||||
"imports": {
|
"imports": {
|
||||||
"#config/*.js": {
|
"#config/*.js": {
|
||||||
"types": "./config/*.ts",
|
"types": "./config/*.ts",
|
||||||
@@ -21,13 +12,13 @@
|
|||||||
"types": "./config/*.ts",
|
"types": "./config/*.ts",
|
||||||
"default": "./config/*.js"
|
"default": "./config/*.js"
|
||||||
},
|
},
|
||||||
"#adapters/*.js": {
|
"#infrastructure/*.js": {
|
||||||
"types": "./adapters/*.ts",
|
"types": "./infrastructure/*.ts",
|
||||||
"default": "./adapters/*.js"
|
"default": "./infrastructure/*.js"
|
||||||
},
|
},
|
||||||
"#adapters/*": {
|
"#infrastructure/*": {
|
||||||
"types": "./adapters/*.ts",
|
"types": "./infrastructure/*.ts",
|
||||||
"default": "./adapters/*.js"
|
"default": "./infrastructure/*.js"
|
||||||
},
|
},
|
||||||
"#domain/*.js": {
|
"#domain/*.js": {
|
||||||
"types": "./domain/*.ts",
|
"types": "./domain/*.ts",
|
||||||
@@ -37,29 +28,32 @@
|
|||||||
"types": "./domain/*.ts",
|
"types": "./domain/*.ts",
|
||||||
"default": "./domain/*.js"
|
"default": "./domain/*.js"
|
||||||
},
|
},
|
||||||
"#ports/*.js": {
|
"#aplication/*.js": {
|
||||||
"types": "./ports/*.ts",
|
"types": "./aplication/*.ts",
|
||||||
"default": "./ports/*.js"
|
"default": "./aplication/*.js"
|
||||||
},
|
},
|
||||||
"#ports/*": {
|
"#aplication/*": {
|
||||||
"types": "./ports/*.ts",
|
"types": "./aplication/*.ts",
|
||||||
"default": "./ports/*.js"
|
"default": "./aplication/*.js"
|
||||||
},
|
|
||||||
"#tests/*.js": {
|
|
||||||
"types": "./__tests__/*.ts",
|
|
||||||
"default": "./__tests__/*.js"
|
|
||||||
},
|
|
||||||
"#tests/*": {
|
|
||||||
"types": "./__tests__/*.ts",
|
|
||||||
"default": "./__tests__/*.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": {
|
"dependencies": {
|
||||||
"@tsconfig/node22": "*",
|
"@tsconfig/node22": "*",
|
||||||
"amqplib": "^0.10.9",
|
"amqplib": "^0.10.9",
|
||||||
"cors": "*",
|
"cors": "*",
|
||||||
"dotenv": "*",
|
"dotenv": "*",
|
||||||
"express": "*",
|
"express": "*",
|
||||||
|
"sim-shared": "sim-shared:*",
|
||||||
"typescript": "*"
|
"typescript": "*"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -70,6 +64,7 @@
|
|||||||
"@types/supertest": "*",
|
"@types/supertest": "*",
|
||||||
"prettier": "*",
|
"prettier": "*",
|
||||||
"supertest": "*",
|
"supertest": "*",
|
||||||
|
"tsc-alias": "^1.8.16",
|
||||||
"tsx": "*",
|
"tsx": "*",
|
||||||
"vitest": "*"
|
"vitest": "*"
|
||||||
}
|
}
|
||||||
|
|||||||
11
packages/sim-consumidor-nos/readme.md
Normal file
11
packages/sim-consumidor-nos/readme.md
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
# NOS
|
||||||
|
|
||||||
|
## Particularidades de las operaciones de NOS
|
||||||
|
|
||||||
|
- Documentación de la API: [DOC](https://pelion-help.iot-x.com/nos/en/Content/API/APIReference/API%20Reference.htm?tocpath=_____7)
|
||||||
|
- No se necesita la pre-activación de las SIM.
|
||||||
|
- La suspensión y reactivación se llama "bar" y "unbar".
|
||||||
|
- El token de Authentication dura exactamente 1 año, solo se puede refrescar
|
||||||
|
desde la web.
|
||||||
|
- En la documentación la URL de la API es <https://nos-api.iot-x.com> pero la
|
||||||
|
de producción es <https://nosconnectcenter-api.iot-x.com>.
|
||||||
5
packages/sim-consumidor-nos/test.env
Normal file
5
packages/sim-consumidor-nos/test.env
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
## ENV PARA DATOS DE TEST - shared nunca se lanza en produccion
|
||||||
|
|
||||||
|
NOTIFICATION_URL="https://sf-sim-activation.savefamilygps.net/send-activation-mail"
|
||||||
|
# NOTIFICATION_URL="localhost"
|
||||||
|
SIM_ACTIVATION_API_KEY=9e48c4ac-1ab0-4397-b3f3-6c239200dfe6
|
||||||
@@ -2,16 +2,40 @@
|
|||||||
"extends": "../../tsconfig.json",
|
"extends": "../../tsconfig.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"outDir": "../../dist",
|
"outDir": "../../dist",
|
||||||
"baseUrl": ".",
|
"rootDir": "../../",
|
||||||
|
"paths": {
|
||||||
|
"#config/*": [
|
||||||
|
"./config/*"
|
||||||
|
],
|
||||||
|
"#infrastructure/*": [
|
||||||
|
"./infrastructure/*"
|
||||||
|
],
|
||||||
|
"#domain/*": [
|
||||||
|
"./domain/*"
|
||||||
|
],
|
||||||
|
"#aplication/*": [
|
||||||
|
"./aplication/*"
|
||||||
|
],
|
||||||
|
"config/*": [
|
||||||
|
"./config/*"
|
||||||
|
],
|
||||||
|
"infrastructure/*": [
|
||||||
|
"./infrastructure/*"
|
||||||
|
],
|
||||||
|
"domain/*": [
|
||||||
|
"./domain/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"node_modules"
|
"node_modules"
|
||||||
],
|
],
|
||||||
"include": [
|
"include": [
|
||||||
"**/*.ts",
|
"**/*.ts",
|
||||||
"src/**/*.d.ts"
|
"**/*.d.ts",
|
||||||
|
"../../packages/sim-shared/**/*.ts"
|
||||||
],
|
],
|
||||||
"files": [
|
"files": [
|
||||||
"index.ts"
|
"index.ts"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
import { describe, it, beforeEach, mock, after } from "node:test";
|
||||||
|
import assert from "node:assert";
|
||||||
|
import { SimController } from "./Sim.controller.js";
|
||||||
|
import { EventBus } from "sim-shared/domain/EventBus.port.js";
|
||||||
|
import { SimUseCases } from "./Sim.usecases.js";
|
||||||
|
import { ConsumeMessage } from "amqplib";
|
||||||
|
import { postgrClient, pgPool } from "#config/postgreConfig.js";
|
||||||
|
import { httpInstance } from "#config/httpClient.config.js";
|
||||||
|
import { OrderRepository } from "sim-shared/infrastructure/OrderRepository.js";
|
||||||
|
import { PauseCancelTaskRepository } from "#adapters/PauseCancelTaskRepository.js";
|
||||||
|
import { ObjeniousOperationsRepository } from "sim-shared/infrastructure/ObjeniousOperationRepository.js";
|
||||||
|
import { ActionData } from "#domain/DTOs/objeniousapi.js";
|
||||||
|
|
||||||
|
describe("SimController Integration Tests (Real UseCases)", () => {
|
||||||
|
let eventBusMock: any;
|
||||||
|
let controller: SimController;
|
||||||
|
let useCases: SimUseCases;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
// Mock ONLY the event bus as requested
|
||||||
|
eventBusMock = {
|
||||||
|
publish: mock.fn(),
|
||||||
|
addSubscribers: mock.fn(),
|
||||||
|
consume: mock.fn(),
|
||||||
|
ack: mock.fn(async () => { }),
|
||||||
|
nack: mock.fn(async () => { }),
|
||||||
|
};
|
||||||
|
|
||||||
|
const operationRepository = new ObjeniousOperationsRepository(
|
||||||
|
httpInstance,
|
||||||
|
postgrClient,
|
||||||
|
);
|
||||||
|
const orderRepository = new OrderRepository(postgrClient);
|
||||||
|
const pauseRepository = new PauseCancelTaskRepository(postgrClient);
|
||||||
|
useCases = new SimUseCases({
|
||||||
|
httpClient: httpInstance,
|
||||||
|
operationRepository: operationRepository,
|
||||||
|
orderRepository: orderRepository,
|
||||||
|
pauseRepository: pauseRepository
|
||||||
|
});
|
||||||
|
// @ts-expect-error
|
||||||
|
useCases.findActivationDate = async (data: ActionData) => new Date()
|
||||||
|
|
||||||
|
controller = new SimController(eventBusMock as unknown as EventBus, useCases);
|
||||||
|
});
|
||||||
|
|
||||||
|
const createMockMsg = (payload: any): ConsumeMessage => {
|
||||||
|
return {
|
||||||
|
content: Buffer.from(JSON.stringify(payload)),
|
||||||
|
fields: {},
|
||||||
|
properties: {
|
||||||
|
headers: {
|
||||||
|
message_id: "test-correlation-id"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
} as unknown as ConsumeMessage;
|
||||||
|
};
|
||||||
|
|
||||||
|
after(async () => {
|
||||||
|
await pgPool.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("suspend", () => {
|
||||||
|
it("should call stage_suspend and interact with DB and EventBus", async () => {
|
||||||
|
const iccid = "test-iccid-suspend-" + Date.now();
|
||||||
|
const msg = createMockMsg({
|
||||||
|
key: "sim.test.pause",
|
||||||
|
payload: {
|
||||||
|
iccid: iccid
|
||||||
|
},
|
||||||
|
headers: {
|
||||||
|
message_id: "correlation-suspend-" + iccid
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const handler = controller.suspend();
|
||||||
|
await handler(msg);
|
||||||
|
|
||||||
|
// Verify that it reached the stage_suspend logic (which adds to pauseRepository)
|
||||||
|
// We can query the DB or check if ACK was called
|
||||||
|
assert.strictEqual(eventBusMock.ack.mock.callCount(), 1, "Message should be ACKed on success");
|
||||||
|
assert.strictEqual(eventBusMock.nack.mock.callCount(), 0, "Message should not be NACKed");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("terminate", () => {
|
||||||
|
it("should call stage_terminate and interact with DB and EventBus", async () => {
|
||||||
|
const iccid = "test-iccid-terminate-" + Date.now();
|
||||||
|
const msg = createMockMsg({
|
||||||
|
key: "sim.test.pause",
|
||||||
|
payload: {
|
||||||
|
iccid: iccid
|
||||||
|
},
|
||||||
|
headers: {
|
||||||
|
message_id: "correlation-terminate-" + iccid
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const handler = controller.terminate();
|
||||||
|
await handler(msg);
|
||||||
|
|
||||||
|
assert.strictEqual(eventBusMock.ack.mock.callCount(), 1, "Message should be ACKed on success");
|
||||||
|
assert.strictEqual(eventBusMock.nack.mock.callCount(), 0, "Message should not be NACKed");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Error Handling", () => {
|
||||||
|
it("should nack if message is invalid", async () => {
|
||||||
|
const msg = {
|
||||||
|
content: Buffer.from("invalid json"),
|
||||||
|
fields: {},
|
||||||
|
properties: {},
|
||||||
|
} as unknown as ConsumeMessage;
|
||||||
|
const handler = controller.suspend();
|
||||||
|
await assert.rejects(handler(msg), "Error de suspension consumiendo el mensaje no es valido");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -3,7 +3,7 @@ import { ConsumeMessage } from "amqplib";
|
|||||||
import { SimUseCases } from "./Sim.usecases.js";
|
import { SimUseCases } from "./Sim.usecases.js";
|
||||||
import { SimEvents } from "sim-shared/domain/SimEvents.js";
|
import { SimEvents } from "sim-shared/domain/SimEvents.js";
|
||||||
import { Result } from "sim-shared/domain/Result.js";
|
import { Result } from "sim-shared/domain/Result.js";
|
||||||
import { env } from "#config/env/index.js";
|
import { ActionData } from "#domain/DTOs/objeniousapi.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* La clase usa generadores de funciones para mantener el contexto
|
* La clase usa generadores de funciones para mantener el contexto
|
||||||
@@ -21,7 +21,6 @@ export class SimController {
|
|||||||
) {
|
) {
|
||||||
this.eventBus = eventBus
|
this.eventBus = eventBus
|
||||||
this.useCases = useCases
|
this.useCases = useCases
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private decodeMsg(msg: ConsumeMessage): object | undefined {
|
private decodeMsg(msg: ConsumeMessage): object | undefined {
|
||||||
@@ -37,6 +36,7 @@ export class SimController {
|
|||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error al decodificar JSON:', 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
|
// Aquí podrías decidir devolver el string crudo o null
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
@@ -108,7 +108,7 @@ export class SimController {
|
|||||||
return async (msg: ConsumeMessage) => {
|
return async (msg: ConsumeMessage) => {
|
||||||
let msgData;
|
let msgData;
|
||||||
try {
|
try {
|
||||||
msgData = this.validateMsg(msg) as SimEvents.pause
|
msgData = this.validateMsg(msg) as SimEvents.suspend
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error("Error de preactivacion consumiendo el mensaje no es valido" + String(e))
|
throw new Error("Error de preactivacion consumiendo el mensaje no es valido" + String(e))
|
||||||
}
|
}
|
||||||
@@ -135,7 +135,7 @@ export class SimController {
|
|||||||
return async (msg: ConsumeMessage) => {
|
return async (msg: ConsumeMessage) => {
|
||||||
let msgData;
|
let msgData;
|
||||||
try {
|
try {
|
||||||
msgData = this.validateMsg(msg) as SimEvents.pause
|
msgData = this.validateMsg(msg) as SimEvents.suspend
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error("Error de reactivacion consumiendo el mensaje no es valido" + String(e))
|
throw new Error("Error de reactivacion consumiendo el mensaje no es valido" + String(e))
|
||||||
}
|
}
|
||||||
@@ -157,11 +157,14 @@ export class SimController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lo mismo que pause
|
||||||
|
*/
|
||||||
public suspend() {
|
public suspend() {
|
||||||
return async (msg: ConsumeMessage) => {
|
return async (msg: ConsumeMessage) => {
|
||||||
let msgData;
|
let msgData;
|
||||||
try {
|
try {
|
||||||
msgData = this.validateMsg(msg) as SimEvents.pause
|
msgData = this.validateMsg(msg) as SimEvents.suspend
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error("Error de suspension consumiendo el mensaje no es valido" + String(e))
|
throw new Error("Error de suspension consumiendo el mensaje no es valido" + String(e))
|
||||||
}
|
}
|
||||||
@@ -171,14 +174,18 @@ export class SimController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const iccid = msgData.payload.iccid
|
const iccid = msgData.payload.iccid
|
||||||
const res = await this.tryUseCase(msg, this.useCases.suspend({
|
const suspendData: ActionData = {
|
||||||
correlation_id: msgData.headers?.message_id,
|
correlation_id: msgData.headers?.message_id,
|
||||||
dueDate: this.genDueDate(2 * 60).toISOString(),
|
dueDate: this.genDueDate(2 * 60).toISOString(),
|
||||||
identifier: {
|
identifier: {
|
||||||
identifierType: "ICCID",
|
identifierType: "ICCID",
|
||||||
identifiers: [iccid]
|
identifiers: [iccid] // Por algún motivo solo he puesto un iccd por identifier
|
||||||
}
|
}
|
||||||
}))
|
}
|
||||||
|
const useCaseRes = await this.tryUseCase(msg, this.useCases.stage_suspend(suspendData))
|
||||||
|
/*
|
||||||
|
const res = await this.tryUseCase(msg, this.useCases.suspend(actionData))
|
||||||
|
*/
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -187,7 +194,7 @@ export class SimController {
|
|||||||
return async (msg: ConsumeMessage) => {
|
return async (msg: ConsumeMessage) => {
|
||||||
let msgData;
|
let msgData;
|
||||||
try {
|
try {
|
||||||
msgData = this.validateMsg(msg) as SimEvents.pause
|
msgData = this.validateMsg(msg) as SimEvents.suspend
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error("Error consumiendo el mensaje no es valido" + String(e))
|
throw new Error("Error consumiendo el mensaje no es valido" + String(e))
|
||||||
}
|
}
|
||||||
@@ -195,16 +202,20 @@ export class SimController {
|
|||||||
if (msgData == undefined) {
|
if (msgData == undefined) {
|
||||||
return Promise.reject("Mensaje invalido")
|
return Promise.reject("Mensaje invalido")
|
||||||
}
|
}
|
||||||
|
|
||||||
const iccid = msgData.payload.iccid
|
const iccid = msgData.payload.iccid
|
||||||
console.log("Mensaje procesado", msgData)
|
const terminateActionData: ActionData = {
|
||||||
const res = await this.tryUseCase(msg, this.useCases.terminate({
|
|
||||||
correlation_id: msgData.headers?.message_id,
|
correlation_id: msgData.headers?.message_id,
|
||||||
dueDate: this.genDueDate(2 * 60).toISOString(),
|
dueDate: this.genDueDate(2 * 60).toISOString(),
|
||||||
identifier: {
|
identifier: {
|
||||||
identifierType: "ICCID",
|
identifierType: "ICCID",
|
||||||
identifiers: [iccid]
|
identifiers: [iccid]
|
||||||
}
|
}
|
||||||
}))
|
}
|
||||||
|
|
||||||
|
//const res = await this.tryUseCase(msg, this.useCases.terminate(terminateActionData))
|
||||||
|
const res = await this.tryUseCase(msg, this.useCases.stage_terminate(terminateActionData))
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export class SimRouter {
|
|||||||
["activate", this.simController.activate()],
|
["activate", this.simController.activate()],
|
||||||
["pause", this.simController.suspend()],
|
["pause", this.simController.suspend()],
|
||||||
["cancel", this.simController.terminate()],
|
["cancel", this.simController.terminate()],
|
||||||
["reActivate", this.simController.reActivate()],
|
["reactivate", this.simController.reActivate()],
|
||||||
["preActivate", this.simController.preActivate()]
|
["preActivate", this.simController.preActivate()]
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import { Result } from "sim-shared/domain/Result.js"
|
|||||||
import { ObjeniousOperation, IOperationsRepository as OperationsRepositoryPort } from "sim-shared/domain/operationsRepository.port.js"
|
import { ObjeniousOperation, IOperationsRepository as OperationsRepositoryPort } from "sim-shared/domain/operationsRepository.port.js"
|
||||||
import assert from "node:assert"
|
import assert from "node:assert"
|
||||||
import { OrderRepository } from "sim-shared/infrastructure/OrderRepository.js"
|
import { OrderRepository } from "sim-shared/infrastructure/OrderRepository.js"
|
||||||
|
import { CreatePauseCancelTaskDTO, PauseCancelTaskRepository } from "#adapters/PauseCancelTaskRepository.js"
|
||||||
|
import { ObjeniousOperationsRepository } from "sim-shared/infrastructure/ObjeniousOperationRepository.js"
|
||||||
|
|
||||||
// TODO:
|
// TODO:
|
||||||
// - Pasar a un archivo de DTOs
|
// - Pasar a un archivo de DTOs
|
||||||
@@ -12,21 +14,24 @@ import { OrderRepository } from "sim-shared/infrastructure/OrderRepository.js"
|
|||||||
|
|
||||||
export class SimUseCases {
|
export class SimUseCases {
|
||||||
private readonly httpClient: HttpClient
|
private readonly httpClient: HttpClient
|
||||||
private readonly operationRepository: OperationsRepositoryPort
|
private readonly objeniousRepository: ObjeniousOperationsRepository
|
||||||
private readonly orderRepository: OrderRepository
|
private readonly orderRepository: OrderRepository
|
||||||
|
private readonly pauseRepository: PauseCancelTaskRepository
|
||||||
|
|
||||||
constructor(args: {
|
constructor(args: {
|
||||||
httpClient: HttpClient,
|
httpClient: HttpClient,
|
||||||
operationRepository: OperationsRepositoryPort,
|
operationRepository: ObjeniousOperationsRepository,
|
||||||
orderRepository: OrderRepository
|
orderRepository: OrderRepository,
|
||||||
|
pauseRepository: PauseCancelTaskRepository
|
||||||
}) {
|
}) {
|
||||||
this.httpClient = args.httpClient
|
this.httpClient = args.httpClient
|
||||||
this.operationRepository = args.operationRepository
|
this.objeniousRepository = args.operationRepository
|
||||||
this.orderRepository = args.orderRepository
|
this.orderRepository = args.orderRepository
|
||||||
|
this.pauseRepository = args.pauseRepository
|
||||||
}
|
}
|
||||||
|
|
||||||
private async logOperation(data: ObjeniousOperation) {
|
private async logOperation(data: ObjeniousOperation) {
|
||||||
await this.operationRepository.createOperation({
|
await this.objeniousRepository.createOperation({
|
||||||
...data
|
...data
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -70,11 +75,14 @@ export class SimUseCases {
|
|||||||
operation: args.operation,
|
operation: args.operation,
|
||||||
iccids: String(args.iccid),
|
iccids: String(args.iccid),
|
||||||
status: "noMassID",
|
status: "noMassID",
|
||||||
request_id: response.data.requestId
|
request_id: response.data.requestId,
|
||||||
|
correlation_id: args.correlation_id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Esto tiene poco sentido si la operacion ya se
|
||||||
|
// tenia que haber creado en el generador
|
||||||
this.logOperation(operation)
|
this.logOperation(operation)
|
||||||
.then().catch(e => console.error(e))
|
.then().catch(e => console.error("Error login operation", e))
|
||||||
|
|
||||||
if (args.correlation_id != undefined) {
|
if (args.correlation_id != undefined) {
|
||||||
this.orderRepository.updateOrder({
|
this.orderRepository.updateOrder({
|
||||||
@@ -89,7 +97,6 @@ export class SimUseCases {
|
|||||||
error: undefined,
|
error: undefined,
|
||||||
data: true
|
data: true
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
error: String(response.status),
|
error: String(response.status),
|
||||||
@@ -109,6 +116,15 @@ export class SimUseCases {
|
|||||||
public activate(activationData: ActivationData): () => Promise<Result<string, boolean>> {
|
public activate(activationData: ActivationData): () => Promise<Result<string, boolean>> {
|
||||||
const OPERATION_URL = "/actions/activateLine"
|
const OPERATION_URL = "/actions/activateLine"
|
||||||
return async () => {
|
return async () => {
|
||||||
|
const iccid = activationData.identifier.identifiers
|
||||||
|
// Comporbación excepcional para saber si la linea está suspendida
|
||||||
|
const statusLinea = await this.objeniousRepository.getLinesAPI("ICCID", [String(iccid)])
|
||||||
|
console.log("statusLinea, ", iccid, statusLinea)
|
||||||
|
if (statusLinea.data != undefined && statusLinea.data[0].status.networkStatus == "SUSPENDED") {
|
||||||
|
const res = await this.reActivate(activationData)()
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
const req = this.httpClient.client.post(OPERATION_URL, {
|
const req = this.httpClient.client.post(OPERATION_URL, {
|
||||||
dueDate: activationData.dueDate,
|
dueDate: activationData.dueDate,
|
||||||
identifier: activationData.identifier,
|
identifier: activationData.identifier,
|
||||||
@@ -192,16 +208,29 @@ export class SimUseCases {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public reActivate(pauseData: ActionData): () => Promise<Result<string, boolean>> {
|
public reActivate(reactivateData: ActionData): () => Promise<Result<string, boolean>> {
|
||||||
const OPERATION_URL = "/actions/reactivateLine"
|
const OPERATION_URL = "/actions/reactivateLine"
|
||||||
return async () => {
|
return async () => {
|
||||||
const req = this.httpClient.client.post(OPERATION_URL, {
|
const req = this.httpClient.client.post(OPERATION_URL, {
|
||||||
...pauseData
|
...reactivateData
|
||||||
})
|
})
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await req
|
const response = await req
|
||||||
|
|
||||||
|
// Creacion de la operacion inicial, antes de tener los datos
|
||||||
|
const operation: ObjeniousOperation = {
|
||||||
|
operation: "reactivate",
|
||||||
|
iccids: reactivateData.identifier.identifiers[0],
|
||||||
|
status: "noMassID",
|
||||||
|
request_id: response.data.requestId,
|
||||||
|
correlation_id: reactivateData.correlation_id
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Esto tiene poco sentido si la operacion ya se
|
||||||
|
// tenia que haber creado en el generador
|
||||||
|
this.logOperation(operation)
|
||||||
|
.then().catch(e => console.error("Error login operation", e))
|
||||||
if (response.status == 200) {
|
if (response.status == 200) {
|
||||||
console.log("[o] Sim solicitud de reactivacion ", response.data)
|
console.log("[o] Sim solicitud de reactivacion ", response.data)
|
||||||
return <Result<string, boolean>>{
|
return <Result<string, boolean>>{
|
||||||
@@ -217,7 +246,7 @@ export class SimUseCases {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("[x] Error reactivacion", (error as AxiosError).response?.status)
|
console.error("[x] Error reactivacion", (error as AxiosError).response?.status)
|
||||||
return <Result<string, boolean>>{
|
return <Result<string, boolean>>{
|
||||||
error: "Error reactivando la sim" + pauseData.identifier,
|
error: "Error reactivando la sim" + reactivateData.identifier,
|
||||||
data: undefined
|
data: undefined
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -238,6 +267,164 @@ export class SimUseCases {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Metodo muy especifico para obtener la fecha e activacion o en su defecto
|
||||||
|
* la actual para aber cuando se va a completar el periodo de test de una linea
|
||||||
|
*/
|
||||||
|
private async findActivationDate(actionData: ActionData) {
|
||||||
|
const iccid = actionData.identifier.identifiers
|
||||||
|
const lineData = await this.objeniousRepository.getLinesAPI("ICCID", iccid)
|
||||||
|
let activationDate = new Date()
|
||||||
|
// Si no se pueden sacar datos de la linea guardo momentaneamente el error
|
||||||
|
// pero no se cancela la operacion, el error puede ser de objenious y no nos
|
||||||
|
// puede afectar
|
||||||
|
console.log("LineData", lineData.data)
|
||||||
|
if (lineData.error != undefined) {
|
||||||
|
console.error(lineData.error)
|
||||||
|
} else {
|
||||||
|
const activationDateStr = lineData.data[0].status.activationDate
|
||||||
|
if (activationDateStr != undefined && activationDateStr != "") {
|
||||||
|
activationDate = new Date(activationDateStr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return activationDate
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Paso previo a la suspension para evitar errores cuando el billing es test
|
||||||
|
*/
|
||||||
|
public stage_suspend(suspendData: ActionData): () => Promise<Result<string, boolean>> {
|
||||||
|
return async (): Promise<Result<string, boolean>> => {
|
||||||
|
const correlation_id = suspendData.correlation_id
|
||||||
|
const iccid = suspendData.identifier.identifiers
|
||||||
|
|
||||||
|
|
||||||
|
const operation: ObjeniousOperation = {
|
||||||
|
operation: "suspend",
|
||||||
|
iccids: iccid[0],
|
||||||
|
status: "running",
|
||||||
|
correlation_id: correlation_id
|
||||||
|
}
|
||||||
|
// No se registra hasta que no pase por la tabla de pausas
|
||||||
|
// this.logOperation(operation)
|
||||||
|
// .then().catch(e => console.error("Error login operation", e))
|
||||||
|
|
||||||
|
const fail = (error: string) => {
|
||||||
|
console.error("[Sim.usecases]", error)
|
||||||
|
if (correlation_id != undefined) {
|
||||||
|
this.orderRepository.updateOrder({
|
||||||
|
correlation_id: correlation_id,
|
||||||
|
new_status: "failed"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO REGISTRAR EL ORDER
|
||||||
|
if (correlation_id != undefined) {
|
||||||
|
await this.orderRepository.createOrder({
|
||||||
|
correlation_id: correlation_id,
|
||||||
|
order_type: "pause"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
let activationDate;
|
||||||
|
try {
|
||||||
|
activationDate = await this.findActivationDate(suspendData)
|
||||||
|
} catch (e) {
|
||||||
|
return {
|
||||||
|
error: String(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const newTask: CreatePauseCancelTaskDTO = {
|
||||||
|
iccid: iccid[0],
|
||||||
|
activation_date: activationDate,
|
||||||
|
next_check: undefined, // Que se haga instantaneamente al ser la primera
|
||||||
|
operation_type: "suspend",
|
||||||
|
action_data: suspendData
|
||||||
|
}
|
||||||
|
|
||||||
|
const taskCreated = await this.pauseRepository.addTask(newTask)
|
||||||
|
|
||||||
|
// Caso que la task no se pueda crear en la BDD
|
||||||
|
if (taskCreated.error != undefined) {
|
||||||
|
fail(taskCreated.error)
|
||||||
|
return {
|
||||||
|
error: taskCreated.error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Caso que se haya creado en la BDD
|
||||||
|
if (correlation_id != undefined) {
|
||||||
|
this.orderRepository.updateOrder({
|
||||||
|
correlation_id: correlation_id,
|
||||||
|
new_status: "running"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Paso previo a la suspension para evitar errores cuando el billing es test
|
||||||
|
*/
|
||||||
|
public stage_terminate(terminateData: ActionData): () => Promise<Result<string, boolean>> {
|
||||||
|
return async (): Promise<Result<string, boolean>> => {
|
||||||
|
const correlation_id = terminateData.correlation_id
|
||||||
|
const iccid = terminateData.identifier.identifiers[0]
|
||||||
|
|
||||||
|
const activationDate = await this.findActivationDate(terminateData)
|
||||||
|
const newTask: CreatePauseCancelTaskDTO = {
|
||||||
|
iccid: iccid,
|
||||||
|
activation_date: activationDate,
|
||||||
|
next_check: undefined, // Que se haga instantaneamente al ser la primera
|
||||||
|
operation_type: "terminate",
|
||||||
|
action_data: terminateData
|
||||||
|
}
|
||||||
|
|
||||||
|
const taskCreated = await this.pauseRepository.addTask(newTask)
|
||||||
|
|
||||||
|
const operation: ObjeniousOperation = {
|
||||||
|
operation: "terminate",
|
||||||
|
iccids: iccid,
|
||||||
|
status: "running",
|
||||||
|
correlation_id: correlation_id
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
this.logOperation(operation)
|
||||||
|
.then().catch(e => console.error("Error login operation", e))
|
||||||
|
*/
|
||||||
|
// Caso que la task no se pueda crear en la BDD
|
||||||
|
if (taskCreated.error != undefined) {
|
||||||
|
console.error("[Sim.usecases]", taskCreated.error)
|
||||||
|
if (correlation_id != undefined) {
|
||||||
|
this.orderRepository.updateOrder({
|
||||||
|
correlation_id: correlation_id,
|
||||||
|
new_status: "failed"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
error: taskCreated.error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Caso que se haya creado en la BDD
|
||||||
|
if (correlation_id != undefined) {
|
||||||
|
this.orderRepository.updateOrder({
|
||||||
|
correlation_id: correlation_id,
|
||||||
|
new_status: "running"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
public terminate(terminationData: ActionData): () => Promise<Result<string, boolean>> {
|
public terminate(terminationData: ActionData): () => Promise<Result<string, boolean>> {
|
||||||
const OPERATION_URL = "/actions/terminateLine"
|
const OPERATION_URL = "/actions/terminateLine"
|
||||||
return this.generateUseCase({
|
return this.generateUseCase({
|
||||||
|
|||||||
@@ -18,24 +18,26 @@ export const rmqConnOptions = <RMQConnectionParams>{
|
|||||||
secure: rmqSecure,
|
secure: rmqSecure,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const QUEUES = {
|
||||||
|
OBJ: "sim.objenious",
|
||||||
|
OBJDLX: "sim.objenious.dlx",
|
||||||
|
OBJDEL: "sim.objenious.delayed",
|
||||||
|
}
|
||||||
|
|
||||||
|
export const EXCHANGES = {
|
||||||
|
MAIN: "sim.exchange",
|
||||||
|
DLX: "sim.ex.objenious.dlx",
|
||||||
|
DEL: "sim.ex.objenious.delayed"
|
||||||
|
}
|
||||||
export const rabbitmqEventBus = new RabbitMQEventBus({
|
export const rabbitmqEventBus = new RabbitMQEventBus({
|
||||||
connectionParams: rmqConnOptions,
|
connectionParams: rmqConnOptions,
|
||||||
buildStructure: buildQueues,
|
buildStructure: buildQueues,
|
||||||
maxRetry: 5
|
maxRetry: 5,
|
||||||
|
delayedExchange: EXCHANGES.DEL,
|
||||||
|
dlxExchange: EXCHANGES.DLX
|
||||||
})
|
})
|
||||||
|
|
||||||
async function buildQueues(channel: Channel) {
|
async function buildQueues(channel: Channel) {
|
||||||
const QUEUES = {
|
|
||||||
OBJ: "sim.objenious",
|
|
||||||
DLX: "sim.objenious.dlx",
|
|
||||||
DEL: "sim.objenious.delayed"
|
|
||||||
}
|
|
||||||
|
|
||||||
const EXCHANGES = {
|
|
||||||
MAIN: "sim.exchange",
|
|
||||||
DLX: "sim.ex.objenious.dlx",
|
|
||||||
DEL: "sim.ex.objenious.delayed"
|
|
||||||
}
|
|
||||||
|
|
||||||
const DELAY = 10 * 1000
|
const DELAY = 10 * 1000
|
||||||
const BASE_OBENIOUS_KEY = "sim.objenious.#"
|
const BASE_OBENIOUS_KEY = "sim.objenious.#"
|
||||||
@@ -45,8 +47,8 @@ async function buildQueues(channel: Channel) {
|
|||||||
await channel.assertExchange(EXCHANGES.MAIN, "topic")
|
await channel.assertExchange(EXCHANGES.MAIN, "topic")
|
||||||
|
|
||||||
await channel.assertQueue(QUEUES.OBJ)
|
await channel.assertQueue(QUEUES.OBJ)
|
||||||
await channel.assertQueue(QUEUES.DLX)
|
await channel.assertQueue(QUEUES.OBJDLX)
|
||||||
await channel.assertQueue(QUEUES.DEL, {
|
await channel.assertQueue(QUEUES.OBJDEL, {
|
||||||
durable: true,
|
durable: true,
|
||||||
arguments: {
|
arguments: {
|
||||||
'x-message-ttl': DELAY,
|
'x-message-ttl': DELAY,
|
||||||
@@ -55,9 +57,9 @@ async function buildQueues(channel: Channel) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Cola dead-letter
|
// Cola dead-letter
|
||||||
await channel.bindQueue(QUEUES.DLX, EXCHANGES.DLX, "sim.objenious.#")
|
await channel.bindQueue(QUEUES.OBJDLX, EXCHANGES.DLX, "sim.objenious.#")
|
||||||
// Cola delay
|
// Cola delay
|
||||||
await channel.bindQueue(QUEUES.DEL, EXCHANGES.DEL, BASE_OBENIOUS_KEY)
|
await channel.bindQueue(QUEUES.OBJDEL, EXCHANGES.DEL, BASE_OBENIOUS_KEY)
|
||||||
// Cola objenious -> main exchange
|
// Cola objenious -> main exchange
|
||||||
await channel.bindQueue(QUEUES.OBJ, EXCHANGES.MAIN, BASE_OBENIOUS_KEY)
|
await channel.bindQueue(QUEUES.OBJ, EXCHANGES.MAIN, BASE_OBENIOUS_KEY)
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { HttpClient } from "sim-shared/infrastructure/HTTPClient.js"
|
import { HttpClient } from "sim-shared/infrastructure/HTTPClient.js"
|
||||||
import { JWTService } from "../aplication/JWT.service.js"
|
|
||||||
import { env } from "./env/index.js"
|
import { env } from "./env/index.js"
|
||||||
|
import { jwtService } from "./jwtService.config.js"
|
||||||
|
|
||||||
const OBJ_BASE_URL = env.OBJ_BASE_URL
|
const OBJ_BASE_URL = env.OBJ_BASE_URL
|
||||||
|
|
||||||
@@ -9,5 +9,5 @@ export const httpInstance = new HttpClient({
|
|||||||
headers: {
|
headers: {
|
||||||
"content-type": " application/json; charset=utf-8"
|
"content-type": " application/json; charset=utf-8"
|
||||||
},
|
},
|
||||||
jwtManager: new JWTService()
|
jwtManager: jwtService
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
import { GrantAccessRequestBody, JWTService } from "sim-shared/aplication/JWT.service.js"
|
||||||
|
import { env } from "./env/index.js"
|
||||||
|
import { JWTHeader } from "sim-shared/domain/JWT.js"
|
||||||
|
|
||||||
|
|
||||||
|
const PRIVATE_KEY_PATH = env.OBJ_PEM_PATH
|
||||||
|
|
||||||
|
const GET_TOKEN_URL = "https://idp.docapost.io/auth/realms/GETWAY/protocol/openid-connect/token"
|
||||||
|
const REFRESH_TOKEN_URL = GET_TOKEN_URL
|
||||||
|
|
||||||
|
const DEFAULT_BODY: GrantAccessRequestBody = {
|
||||||
|
grant_type: "client_credentials",
|
||||||
|
client_id: env.OBJ_CLIENT_ID,
|
||||||
|
client_assertion_type: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
|
||||||
|
client_assertion: env.OBJ_CLI_ASSERTION
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const DEFAULT_HEADERS = {
|
||||||
|
"content-type": "application/x-www-form-urlencoded"
|
||||||
|
}
|
||||||
|
|
||||||
|
const DEFAULT_HEADERS_JWT = {
|
||||||
|
alg: "RS256",
|
||||||
|
typ: "JWT",
|
||||||
|
kid: env.OBJ_KID,
|
||||||
|
}
|
||||||
|
|
||||||
|
const DEFAULT_DATA_JWT = {
|
||||||
|
sub: env.OBJ_CLIENT_ID,
|
||||||
|
iss: env.OBJ_CLIENT_ID,
|
||||||
|
aud: "https://idp.docapost.io/auth/realms/GETWAY",
|
||||||
|
jti: Date.now().toString(),
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function addIATHeaders(authHeaders: Object) {
|
||||||
|
const headers = <JWTHeader>{
|
||||||
|
...authHeaders,
|
||||||
|
sub: env.OBJ_CLIENT_ID,
|
||||||
|
iss: env.OBJ_CLIENT_ID,
|
||||||
|
aud: GET_TOKEN_URL,
|
||||||
|
jti: Date.now().toString(),
|
||||||
|
iat: Math.floor(Date.now() / 1000),
|
||||||
|
exp: Math.floor(Date.now() / 1000) + 5 * 60,
|
||||||
|
}
|
||||||
|
return headers
|
||||||
|
}
|
||||||
|
|
||||||
|
export const jwtService = new JWTService({
|
||||||
|
transformJWTHeaders: addIATHeaders,
|
||||||
|
defaultHeaders: DEFAULT_HEADERS,
|
||||||
|
defaultBody: DEFAULT_BODY,
|
||||||
|
defaultJWTHeaders: DEFAULT_HEADERS_JWT,
|
||||||
|
defaultJWTPayload: DEFAULT_DATA_JWT,
|
||||||
|
privateKeyPath: PRIVATE_KEY_PATH,
|
||||||
|
tokenUrl: GET_TOKEN_URL,
|
||||||
|
refreshTokenUrl: REFRESH_TOKEN_URL
|
||||||
|
})
|
||||||
@@ -8,6 +8,7 @@ import { SimUseCases } from "./aplication/Sim.usecases.js"
|
|||||||
import { SimController } from "./aplication/Sim.controller.js"
|
import { SimController } from "./aplication/Sim.controller.js"
|
||||||
import { SimRouter } from "./aplication/Sim.router.js"
|
import { SimRouter } from "./aplication/Sim.router.js"
|
||||||
import { OrderRepository } from "sim-shared/infrastructure/OrderRepository.js"
|
import { OrderRepository } from "sim-shared/infrastructure/OrderRepository.js"
|
||||||
|
import { PauseCancelTaskRepository } from "#adapters/PauseCancelTaskRepository.js"
|
||||||
|
|
||||||
async function startWorker() {
|
async function startWorker() {
|
||||||
const rmqClient = await startRMQClient()
|
const rmqClient = await startRMQClient()
|
||||||
@@ -18,15 +19,21 @@ async function startWorker() {
|
|||||||
|
|
||||||
await pgClient.checkDatabaseConnection()
|
await pgClient.checkDatabaseConnection()
|
||||||
|
|
||||||
const operationRepository = new ObjeniousOperationsRepository(pgClient)
|
const operationRepository = new ObjeniousOperationsRepository(
|
||||||
|
httpClient,
|
||||||
|
pgClient,
|
||||||
|
)
|
||||||
const orderRepository = new OrderRepository(pgClient)
|
const orderRepository = new OrderRepository(pgClient)
|
||||||
|
|
||||||
|
const pauseRepository = new PauseCancelTaskRepository(pgClient)
|
||||||
|
|
||||||
const simActivationController = new SimController(
|
const simActivationController = new SimController(
|
||||||
rmqClient,
|
rmqClient,
|
||||||
new SimUseCases({
|
new SimUseCases({
|
||||||
httpClient: httpClient,
|
httpClient: httpClient,
|
||||||
operationRepository: operationRepository,
|
operationRepository: operationRepository,
|
||||||
orderRepository: orderRepository
|
orderRepository: orderRepository,
|
||||||
|
pauseRepository: pauseRepository
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
const simRouter = new SimRouter(simActivationController, rmqClient)
|
const simRouter = new SimRouter(simActivationController, rmqClient)
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
import { after, before, describe, it } from "node:test";
|
||||||
|
import { CreatePauseCancelTaskDTO, PauseCancelTaskRepository } from "./PauseCancelTaskRepository.js";
|
||||||
|
import { postgrClient } from "#config/postgreConfig.js";
|
||||||
|
import assert from "node:assert";
|
||||||
|
|
||||||
|
const testTask: CreatePauseCancelTaskDTO = {
|
||||||
|
iccid: "1234",
|
||||||
|
operation_type: "suspend",
|
||||||
|
activation_date: new Date(),
|
||||||
|
next_check: new Date(),
|
||||||
|
action_data: {
|
||||||
|
dueDate: new Date().toString(),
|
||||||
|
correlation_id: "12223",
|
||||||
|
identifier: {
|
||||||
|
identifiers: ["1234"],
|
||||||
|
identifierType: "ICCID"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("Test PauseCancelTaskRepository - DB", () => {
|
||||||
|
|
||||||
|
const createdIds: number[] = [];
|
||||||
|
const pauseRepo = new PauseCancelTaskRepository(postgrClient)
|
||||||
|
|
||||||
|
before(() => {
|
||||||
|
})
|
||||||
|
|
||||||
|
after(() => {
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Should create a task", async () => {
|
||||||
|
const created = await pauseRepo.addTask(testTask)
|
||||||
|
assert.ok(created != undefined, "A value must be returned always")
|
||||||
|
assert.ok(created.error == undefined, "Should not return a error")
|
||||||
|
assert.ok(created.data != undefined, "Data must be returned")
|
||||||
|
createdIds.push(created.data.id)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Should update a existing task", async () => {
|
||||||
|
const updated = await pauseRepo.updateTask({
|
||||||
|
id: createdIds[0],
|
||||||
|
next_check: new Date()
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.ok(updated != undefined, "A value must be returned always")
|
||||||
|
assert.ok(updated.error == undefined, "Should not return a error")
|
||||||
|
assert.ok(updated.data != undefined, "Data must be returned")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Should finish a existing task", async () => {
|
||||||
|
const finish = await pauseRepo.finishTask({
|
||||||
|
id: createdIds[0],
|
||||||
|
error: "ok"
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.ok(finish != undefined, "A value must be returned always")
|
||||||
|
assert.ok(finish.error == undefined, "Should not return a error")
|
||||||
|
assert.ok(finish.data != undefined, "Data must be returned")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Should get at least 1 pending task", async () => {
|
||||||
|
const created = await pauseRepo.addTask(testTask)
|
||||||
|
const pending = await pauseRepo.getPending()
|
||||||
|
|
||||||
|
assert.ok(pending != undefined, "A value must be returned always")
|
||||||
|
assert.ok(pending.error == undefined, "Should not return a error")
|
||||||
|
assert.ok(pending.data != undefined, "Data must be returned")
|
||||||
|
|
||||||
|
console.log("--> ", pending.data[0])
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
import { Result } from "sim-shared/domain/Result.js";
|
||||||
|
import { QueryResult } from "pg";
|
||||||
|
import { PgClient } from "sim-shared/infrastructure/PgClient.js";
|
||||||
|
import { AxiosError } from "axios";
|
||||||
|
import { ActionData } from "#domain/DTOs/objeniousapi.js";
|
||||||
|
|
||||||
|
export type PauseCancelTask = {
|
||||||
|
id: number;
|
||||||
|
iccid: string;
|
||||||
|
operation_type: "suspend" | "terminate",
|
||||||
|
last_checked?: Date | null;
|
||||||
|
activation_date?: Date | null;
|
||||||
|
next_check?: Date | null;
|
||||||
|
completed_date?: Date | null;
|
||||||
|
error?: string | null;
|
||||||
|
action_data: ActionData
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CreatePauseCancelTaskDTO = Pick<PauseCancelTask, "iccid" | "activation_date" | "next_check" | "operation_type" | "action_data">
|
||||||
|
export type UpdatePauseCancelTaskDTO = Pick<PauseCancelTask, "id" | "next_check">
|
||||||
|
export type FinishPauseCancelTaskDTO = Pick<PauseCancelTask, "id" | "error">
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Repositorio para compensar los problemas de cacelcaiones/pausas de objenious a
|
||||||
|
* la hora aplicarlo sobre una linea con el billing a test.
|
||||||
|
*/
|
||||||
|
export class PauseCancelTaskRepository {
|
||||||
|
constructor(
|
||||||
|
private readonly pgClient: PgClient
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtiene las siguientes que se pueden lanzar, puede haber más pero
|
||||||
|
* estan pendientes
|
||||||
|
*/
|
||||||
|
public async getPending(): Promise<Result<string, PauseCancelTask[]>> {
|
||||||
|
const sql = `
|
||||||
|
SELECT * FROM pause_cancel_tasks
|
||||||
|
WHERE completed_date IS NULL
|
||||||
|
AND (next_check <= NOW() OR next_check IS NULL)
|
||||||
|
ORDER BY id ASC;
|
||||||
|
`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res: QueryResult<PauseCancelTask> = await this.pgClient.query(sql);
|
||||||
|
return {
|
||||||
|
data: res.rows
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
return {
|
||||||
|
error: (e as AxiosError).message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async addTask(task: CreatePauseCancelTaskDTO): Promise<Result<string, PauseCancelTask>> {
|
||||||
|
|
||||||
|
const sql = `
|
||||||
|
INSERT INTO pause_cancel_tasks (iccid, activation_date, next_check, last_checked, operation_type, action_data)
|
||||||
|
VALUES ($1, $2, $3, now(), $4, $5)
|
||||||
|
RETURNING *;
|
||||||
|
`;
|
||||||
|
try {
|
||||||
|
const values = [task.iccid, task.activation_date, task.next_check, task.operation_type, JSON.stringify(task.action_data)];
|
||||||
|
const res: QueryResult<PauseCancelTask> = await this.pgClient.query(sql, values);
|
||||||
|
return {
|
||||||
|
data: res.rows[0]
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
return {
|
||||||
|
error: (e as AxiosError).message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Se ha vuelto a comprobar la tarea pero sigue en test
|
||||||
|
*/
|
||||||
|
public async updateTask(updateData: UpdatePauseCancelTaskDTO): Promise<Result<string, PauseCancelTask>> {
|
||||||
|
|
||||||
|
const sql = `
|
||||||
|
UPDATE pause_cancel_tasks
|
||||||
|
SET last_checked = now(), next_check = $1
|
||||||
|
WHERE id = $2
|
||||||
|
RETURNING *;
|
||||||
|
`;
|
||||||
|
try {
|
||||||
|
const res = await this.pgClient.query<PauseCancelTask>(sql, [updateData.next_check, updateData.id]);
|
||||||
|
return {
|
||||||
|
data: res.rows[0]
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
return {
|
||||||
|
error: (e as AxiosError).message
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* La tarea ha termiando bien o mal
|
||||||
|
*/
|
||||||
|
public async finishTask(finishData: FinishPauseCancelTaskDTO) {
|
||||||
|
const sql = `
|
||||||
|
UPDATE pause_cancel_tasks
|
||||||
|
SET completed_date = NOW(), error = $1
|
||||||
|
WHERE id = $2
|
||||||
|
RETURNING *;
|
||||||
|
`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await this.pgClient.query(sql, [finishData.error, finishData.id]);
|
||||||
|
return {
|
||||||
|
data: res.rows[0]
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
return {
|
||||||
|
error: (e as AxiosError).message
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PauseCancelTask
|
||||||
@@ -53,7 +53,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1",
|
"test": "node --import tsx --test ./**/*.test.ts",
|
||||||
"dev": "tsx watch index.ts",
|
"dev": "tsx watch index.ts",
|
||||||
"build": "tsc --build && yarn tsc-alias -p tsconfig.json && cp .env package.json ../../dist/packages/sim-consumidor-objenious/",
|
"build": "tsc --build && yarn tsc-alias -p tsconfig.json && cp .env package.json ../../dist/packages/sim-consumidor-objenious/",
|
||||||
"start": "node ../../dist/packages/sim-consumidor-objenious/index.js",
|
"start": "node ../../dist/packages/sim-consumidor-objenious/index.js",
|
||||||
|
|||||||
@@ -9,6 +9,10 @@
|
|||||||
],
|
],
|
||||||
"include": [
|
"include": [
|
||||||
"**/*.ts",
|
"**/*.ts",
|
||||||
|
"**/*.d.ts",
|
||||||
"../../packages/sim-shared/**/*.ts"
|
"../../packages/sim-shared/**/*.ts"
|
||||||
|
],
|
||||||
|
"files": [
|
||||||
|
"index.ts"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { SimUsecases } from "./Sim.usecases.js"
|
|||||||
import { activationValidator, iccidValidator } from "./httpValidators.js"
|
import { activationValidator, iccidValidator } from "./httpValidators.js"
|
||||||
import { companyFromIccid } from "#domain/companies.js"
|
import { companyFromIccid } from "#domain/companies.js"
|
||||||
import { BodyValidator } from "sim-shared/aplication/BodyValidator.js"
|
import { BodyValidator } from "sim-shared/aplication/BodyValidator.js"
|
||||||
import { tryCatch } from "packages/sim-shared/domain/Result.js"
|
import { tryCatch } from "sim-shared/domain/Result.js"
|
||||||
|
|
||||||
|
|
||||||
export class SimController {
|
export class SimController {
|
||||||
@@ -36,7 +36,6 @@ export class SimController {
|
|||||||
}) {
|
}) {
|
||||||
return async (req: Request, res: Response) => {
|
return async (req: Request, res: Response) => {
|
||||||
const body = req.body
|
const body = req.body
|
||||||
|
|
||||||
// 1. Validacion del body
|
// 1. Validacion del body
|
||||||
if (args.validator != undefined) {
|
if (args.validator != undefined) {
|
||||||
const validationResult = args.validator.validate(body)
|
const validationResult = args.validator.validate(body)
|
||||||
@@ -132,6 +131,21 @@ export class SimController {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public reActivation() {
|
||||||
|
return this.controllerGenerator<{ iccid: string, offer: string }, { iccid: string, offer: string, compañia: string }>({
|
||||||
|
validator: iccidValidator,
|
||||||
|
mapBody: (b) => {
|
||||||
|
const { iccid, offer } = b
|
||||||
|
const compañia = companyFromIccid(iccid)
|
||||||
|
return { iccid, compañia, offer }
|
||||||
|
},
|
||||||
|
useCase: (args) => this.simUseCases.reActivation(args),
|
||||||
|
onError: (d, e) => console.error("[x] Error reactivacion: ", d, e),
|
||||||
|
onSuccess: console.log
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
public cancelation() {
|
public cancelation() {
|
||||||
return this.controllerGenerator<{ iccid: string }, { iccid: string, compañia: string }>({
|
return this.controllerGenerator<{ iccid: string }, { iccid: string, compañia: string }>({
|
||||||
validator: iccidValidator,
|
validator: iccidValidator,
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { OrderRepository } from "sim-shared/infrastructure/OrderRepository.js";
|
import { OrderRepository } from "sim-shared/infrastructure/OrderRepository.js";
|
||||||
import { Result } from "sim-shared/domain/Result.js";
|
import { Result } from "sim-shared/domain/Result.js";
|
||||||
import assert from "node:assert";
|
import assert from "node:assert";
|
||||||
import { EventBus } from "sim-shared/domain/EventBus.port";
|
import { SimEvents } from "sim-shared/domain/SimEvents.js";
|
||||||
import { SimEvents } from "sim-shared/domain/SimEvents";
|
|
||||||
import { uuidv7 } from "uuidv7";
|
import { uuidv7 } from "uuidv7";
|
||||||
import { CreateOrderDTO, OrderTracking, OrderType, OrderTypeOptions } from "sim-shared/domain/Order.js";
|
import { CreateOrderDTO, OrderTracking, OrderType, OrderTypeOptions } from "sim-shared/domain/Order.js";
|
||||||
|
import { EventBus } from "sim-shared/domain/EventBus.port.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Casos de uso de tarjetas sim. Garantiza que todos los metodos usan el mismo bus de mensajes
|
* Casos de uso de tarjetas sim. Garantiza que todos los metodos usan el mismo bus de mensajes
|
||||||
@@ -130,6 +130,36 @@ export class SimUsecases {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async reActivation(args: { iccid: string, compañia: string, offer: string }):
|
||||||
|
Promise<Result<string, { iccid: string, message_id: string, operation: "reactivate" }>> {
|
||||||
|
const activationEvent = <SimEvents.activation>{
|
||||||
|
key: `sim.${args.compañia}.reactivate`,
|
||||||
|
payload: {
|
||||||
|
iccid: args.iccid,
|
||||||
|
offer: args.offer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const activationWithId = this.addMessage_id(activationEvent)
|
||||||
|
console.log("[d] Reactivation ", activationWithId)
|
||||||
|
await this.eventBus.publish([activationWithId])
|
||||||
|
const createdOrder = await this.saveOrder<SimEvents.reActivation>(activationWithId)
|
||||||
|
|
||||||
|
if (createdOrder.error != undefined) {
|
||||||
|
console.error(createdOrder.error)
|
||||||
|
return {
|
||||||
|
error: createdOrder.error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: {
|
||||||
|
iccid: args.iccid,
|
||||||
|
operation: "reactivate",
|
||||||
|
message_id: createdOrder.data?.correlation_id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async preActivation(args: { iccid: string, compañia: string }):
|
async preActivation(args: { iccid: string, compañia: string }):
|
||||||
Promise<Result<string, { iccid: string, message_id: string, operation: "preactivation" }>> {
|
Promise<Result<string, { iccid: string, message_id: string, operation: "preactivation" }>> {
|
||||||
|
|
||||||
@@ -174,8 +204,10 @@ export class SimUsecases {
|
|||||||
|
|
||||||
const cancelationWithId = this.addMessage_id(cancelationEvent)
|
const cancelationWithId = this.addMessage_id(cancelationEvent)
|
||||||
console.log("[d] Cancelation ", cancelationWithId)
|
console.log("[d] Cancelation ", cancelationWithId)
|
||||||
|
|
||||||
await this.eventBus.publish([cancelationWithId])
|
await this.eventBus.publish([cancelationWithId])
|
||||||
const savedOrder = await this.saveOrder(cancelationWithId)
|
const savedOrder = await this.saveOrder(cancelationWithId)
|
||||||
|
|
||||||
if (savedOrder.error != undefined) {
|
if (savedOrder.error != undefined) {
|
||||||
console.error(savedOrder.error)
|
console.error(savedOrder.error)
|
||||||
return {
|
return {
|
||||||
@@ -205,11 +237,12 @@ export class SimUsecases {
|
|||||||
iccid: args.iccid
|
iccid: args.iccid
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const pauseWithId = this.addMessage_id(pauseEvent)
|
const pauseWithId = this.addMessage_id(pauseEvent)
|
||||||
console.log("[d] Pause", pauseWithId)
|
console.log("[d] Pause", pauseWithId)
|
||||||
await this.eventBus.publish([pauseWithId])
|
await this.eventBus.publish([pauseWithId])
|
||||||
await this.saveOrder(pauseWithId)
|
//await this.saveOrder(pauseWithId)
|
||||||
const savedOrder = await this.saveOrder(pauseWithId)
|
const savedOrder = await this.saveOrder<SimEvents.pause>(pauseWithId)
|
||||||
|
|
||||||
if (savedOrder.error != undefined) {
|
if (savedOrder.error != undefined) {
|
||||||
console.error(savedOrder.error)
|
console.error(savedOrder.error)
|
||||||
|
|||||||
@@ -22,4 +22,5 @@ export const env = {
|
|||||||
RABBITMQ_SECURE: process.env.RABBITMQ_SECURE,
|
RABBITMQ_SECURE: process.env.RABBITMQ_SECURE,
|
||||||
RABBITMQ_RETRY_INTERVAL: process.env.RABBITMQ_INTERVAL,
|
RABBITMQ_RETRY_INTERVAL: process.env.RABBITMQ_INTERVAL,
|
||||||
RABBITMQ_VHOST: String(process.env.RABBITMQ_VHOST),
|
RABBITMQ_VHOST: String(process.env.RABBITMQ_VHOST),
|
||||||
|
CONNECTIONS_URL: String(process.env.CONNECTIONS_URL)
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -18,7 +18,10 @@ export const rmqConnOptions = <RMQConnectionParams>{
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const rabbitmqEventBus = new RabbitMQEventBus({
|
export const rabbitmqEventBus = new RabbitMQEventBus({
|
||||||
connectionParams: rmqConnOptions
|
connectionParams: rmqConnOptions,
|
||||||
|
// La entrada de eventos no tiene que definir exchanges de dlx o delay pero es obligatorio
|
||||||
|
delayedExchange: "-",
|
||||||
|
dlxExchange: "-"
|
||||||
})
|
})
|
||||||
|
|
||||||
export async function startRMQClient() {
|
export async function startRMQClient() {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { simRoutes } from "./infrastructure/simRoutes.http.js"
|
|||||||
import { rabbitmqEventBus } from '#config/eventBusConfig.js';
|
import { rabbitmqEventBus } from '#config/eventBusConfig.js';
|
||||||
import { env } from "#config/env/index.js"
|
import { env } from "#config/env/index.js"
|
||||||
import { orderRoutes } from "#adapters/orderRoutes.http.js";
|
import { orderRoutes } from "#adapters/orderRoutes.http.js";
|
||||||
|
import { connectionsRoutes } from "#adapters/simconnectionsRoutes.js";
|
||||||
|
|
||||||
const PORT = env.API_PORT
|
const PORT = env.API_PORT
|
||||||
const HOSTNAME = "0.0.0.0"
|
const HOSTNAME = "0.0.0.0"
|
||||||
@@ -18,7 +19,6 @@ rabbitmqEventBus.connect()
|
|||||||
console.error("[!] El cliente RMQ no se ha podido iniciar", e)
|
console.error("[!] El cliente RMQ no se ha podido iniciar", e)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
// Middleware
|
// Middleware
|
||||||
app.use(cors());
|
app.use(cors());
|
||||||
|
|
||||||
@@ -26,6 +26,7 @@ app.use(express.json());
|
|||||||
app.use(express.urlencoded({ extended: true }));
|
app.use(express.urlencoded({ extended: true }));
|
||||||
|
|
||||||
app.use("/sim", simRoutes)
|
app.use("/sim", simRoutes)
|
||||||
|
app.use("/simconnections", connectionsRoutes)
|
||||||
app.use("/orders", orderRoutes)
|
app.use("/orders", orderRoutes)
|
||||||
|
|
||||||
app.use("/docs", express.static(path.join(process.cwd(), '../../docs')))
|
app.use("/docs", express.static(path.join(process.cwd(), '../../docs')))
|
||||||
@@ -37,4 +38,5 @@ app.get("/health", (req, res) => {
|
|||||||
app.listen(PORT, HOSTNAME, () => {
|
app.listen(PORT, HOSTNAME, () => {
|
||||||
console.log("[o] Servidor iniciado en el puerto %d", PORT)
|
console.log("[o] Servidor iniciado en el puerto %d", PORT)
|
||||||
})
|
})
|
||||||
|
|
||||||
export default {}
|
export default {}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { rabbitmqEventBus } from '#config/eventBusConfig.js';
|
import { rabbitmqEventBus } from '../config/eventBusConfig.js';
|
||||||
import { SimUsecases } from '../aplication/Sim.usecases.js';
|
import { SimUsecases } from '../aplication/Sim.usecases.js';
|
||||||
import { SimController } from '../aplication/Sim.controller.js';
|
import { SimController } from '../aplication/Sim.controller.js';
|
||||||
import { Router } from 'express';
|
import { Router } from 'express';
|
||||||
@@ -23,6 +23,7 @@ simRoutes.get("/status", () => { })
|
|||||||
simRoutes.post("/save", simController.save())
|
simRoutes.post("/save", simController.save())
|
||||||
|
|
||||||
simRoutes.post("/activate", simController.activation())
|
simRoutes.post("/activate", simController.activation())
|
||||||
|
simRoutes.post("/reActivate", simController.reActivation())
|
||||||
|
|
||||||
simRoutes.post("/preActivate", simController.preactivation())
|
simRoutes.post("/preActivate", simController.preactivation())
|
||||||
|
|
||||||
@@ -35,4 +36,5 @@ simRoutes.post("/test", simController.test())
|
|||||||
// Proceso especifico de ALAI para liberar sims canceladas
|
// Proceso especifico de ALAI para liberar sims canceladas
|
||||||
simRoutes.post("/free", simController.free())
|
simRoutes.post("/free", simController.free())
|
||||||
|
|
||||||
|
|
||||||
export { simRoutes }
|
export { simRoutes }
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
import { env } from "#config/env/index.js"
|
||||||
|
import { Router } from "express"
|
||||||
|
import { ClientRequest, IncomingMessage } from "http"
|
||||||
|
import { createProxyMiddleware } from "http-proxy-middleware"
|
||||||
|
import { Request } from "express"
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
connectionsRoutes.use("", createProxyMiddleware({
|
||||||
|
target: CONNECTIONS_URL,
|
||||||
|
changeOrigin: true,
|
||||||
|
pathRewrite: {
|
||||||
|
'^/': "/simconnections/"
|
||||||
|
},
|
||||||
|
on: {
|
||||||
|
proxyReq: (proxyReq: ClientRequest, req: Request) => {
|
||||||
|
const protocol = req.protocol;
|
||||||
|
const host = req.get('host');
|
||||||
|
const originalFullUrl = `${protocol}://${host}${req.originalUrl}`;
|
||||||
|
const destinationFullUrl = `${CONNECTIONS_URL}${proxyReq.path}`;
|
||||||
|
/*
|
||||||
|
constnsole.log('──────────────────────────────────────────────────');
|
||||||
|
console.log(`[PROXY_DEBUG]`);
|
||||||
|
console.log(` ENTRADA: ${originalFullUrl}`);
|
||||||
|
console.log(` MÉTODO : ${req.method}`);
|
||||||
|
console.log(` DESTINO: ${destinationFullUrl}`);
|
||||||
|
console.log('──────────────────────────────────────────────────');
|
||||||
|
*/
|
||||||
|
console.log(`[Proxy Req]: ${req.method} ${req.url} -> ${proxyReq.path}`);
|
||||||
|
},
|
||||||
|
proxyRes: (proxyRes, req, res) => {
|
||||||
|
console.log(`[Proxy Res] Status: ${proxyRes.statusCode} desde ${req.url}`);
|
||||||
|
},
|
||||||
|
error: (err, req, res) => {
|
||||||
|
console.error('[Proxy Error]:', err);
|
||||||
|
|
||||||
|
// Validamos que 'res' tenga el método 'status' (típico de Express Response)
|
||||||
|
if ('status' in res) {
|
||||||
|
//@ts-ignore
|
||||||
|
res.status(500).json({ message: 'Error interno en el Gateway' });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Rutas
|
||||||
|
/**
|
||||||
|
connectionsRoutes.post('/simconnections/alai/preactivate',);
|
||||||
|
connectionsRoutes.get('/simconnections/alai/pause',);
|
||||||
|
connectionsRoutes.post('/simconnections/alai/terminate',);
|
||||||
|
connectionsRoutes.get('/simconnections/alai/pauseByPhone',);
|
||||||
|
connectionsRoutes.get('/simconnections/alai/active',);
|
||||||
|
connectionsRoutes.get('/simconnections/alai/change_orderid',);
|
||||||
|
connectionsRoutes.get('/simconnections/alai/select',);
|
||||||
|
connectionsRoutes.get('/simconnections/alai/select-iccid',);
|
||||||
|
connectionsRoutes.get('/simconnections/alai/selectFromDb',);
|
||||||
|
connectionsRoutes.get('/simconnections/alai/selectPage',);
|
||||||
|
connectionsRoutes.post('/simconnections/alai/schedulePause',);
|
||||||
|
connectionsRoutes.get('/simconnections/shopify/getbyWP',);
|
||||||
|
connectionsRoutes.get('/simconnections/shopify/getbyWPS',);
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
|
connectionsRoutes.get('/simconnections/sim/associate',);
|
||||||
|
connectionsRoutes.post('/simconnections/sim/search',);
|
||||||
|
connectionsRoutes.post('/simconnections/sim/historic',);
|
||||||
|
connectionsRoutes.post('/simconnections/sim/update',);
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
|
connectionsRoutes.post('/simconnections/nos/activate',);
|
||||||
|
connectionsRoutes.get('/simconnections/nos/select',);
|
||||||
|
connectionsRoutes.get('/simconnections/nos/selectPage',);
|
||||||
|
|
||||||
|
//Unificación
|
||||||
|
connectionsRoutes.post('/simconnections/sim/active',); // True false
|
||||||
|
connectionsRoutes.patch('/simconnections/sim/pause',);
|
||||||
|
connectionsRoutes.get('/simconnections/sim/select',);
|
||||||
|
connectionsRoutes.get('/simconnections/sim/select-phone',);
|
||||||
|
**/
|
||||||
@@ -53,6 +53,7 @@
|
|||||||
"cors": "*",
|
"cors": "*",
|
||||||
"dotenv": "*",
|
"dotenv": "*",
|
||||||
"express": "*",
|
"express": "*",
|
||||||
|
"http-proxy-middleware": "^3.0.5",
|
||||||
"sim-shared": "sim-shared:*",
|
"sim-shared": "sim-shared:*",
|
||||||
"typescript": "*"
|
"typescript": "*"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
],
|
],
|
||||||
"include": [
|
"include": [
|
||||||
"**/*.ts",
|
"**/*.ts",
|
||||||
|
"**/*.d.ts",
|
||||||
"../../packages/sim-shared/**/*.ts"
|
"../../packages/sim-shared/**/*.ts"
|
||||||
],
|
],
|
||||||
"files": [
|
"files": [
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { HttpClient } from "sim-shared/infrastructure/HTTPClient.js"
|
import { HttpClient } from "sim-shared/infrastructure/HTTPClient.js"
|
||||||
import { env } from "./env/index.js"
|
import { env } from "./env/index.js"
|
||||||
import { JWTService } from "packages/sim-consumidor-objenious/aplication/JWT.service.js"
|
import { jwtService } from "./jwtService.config.js"
|
||||||
|
|
||||||
|
|
||||||
const OBJ_BASE_URL = env.OBJ_BASE_URL
|
const OBJ_BASE_URL = env.OBJ_BASE_URL
|
||||||
|
|
||||||
@@ -9,5 +10,5 @@ export const httpInstance = new HttpClient({
|
|||||||
headers: {
|
headers: {
|
||||||
"content-type": " application/json; charset=utf-8"
|
"content-type": " application/json; charset=utf-8"
|
||||||
},
|
},
|
||||||
jwtManager: new JWTService()
|
jwtManager: jwtService
|
||||||
})
|
})
|
||||||
|
|||||||
59
packages/sim-objenious-cron/config/jwtService.config.ts
Normal file
59
packages/sim-objenious-cron/config/jwtService.config.ts
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import { GrantAccessRequestBody, JWTService } from "sim-shared/aplication/JWT.service.js"
|
||||||
|
import { env } from "./env/index.js"
|
||||||
|
import { JWTHeader } from "sim-shared/domain/JWT.js"
|
||||||
|
|
||||||
|
|
||||||
|
const PRIVATE_KEY_PATH = env.OBJ_PEM_PATH
|
||||||
|
|
||||||
|
const GET_TOKEN_URL = "https://idp.docapost.io/auth/realms/GETWAY/protocol/openid-connect/token"
|
||||||
|
const REFRESH_TOKEN_URL = GET_TOKEN_URL
|
||||||
|
|
||||||
|
const DEFAULT_BODY: GrantAccessRequestBody = {
|
||||||
|
grant_type: "client_credentials",
|
||||||
|
client_id: env.OBJ_CLIENT_ID,
|
||||||
|
client_assertion_type: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
|
||||||
|
client_assertion: env.OBJ_CLI_ASSERTION
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const DEFAULT_HEADERS = {
|
||||||
|
"content-type": "application/x-www-form-urlencoded"
|
||||||
|
}
|
||||||
|
|
||||||
|
const DEFAULT_HEADERS_JWT = {
|
||||||
|
alg: "RS256",
|
||||||
|
typ: "JWT",
|
||||||
|
kid: env.OBJ_KID,
|
||||||
|
}
|
||||||
|
|
||||||
|
const DEFAULT_DATA_JWT = {
|
||||||
|
sub: env.OBJ_CLIENT_ID,
|
||||||
|
iss: env.OBJ_CLIENT_ID,
|
||||||
|
aud: "https://idp.docapost.io/auth/realms/GETWAY",
|
||||||
|
jti: Date.now().toString(),
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function addIATHeaders(authHeaders: Object) {
|
||||||
|
const headers = <JWTHeader>{
|
||||||
|
...authHeaders,
|
||||||
|
sub: env.OBJ_CLIENT_ID,
|
||||||
|
iss: env.OBJ_CLIENT_ID,
|
||||||
|
aud: GET_TOKEN_URL,
|
||||||
|
jti: Date.now().toString(),
|
||||||
|
iat: Math.floor(Date.now() / 1000),
|
||||||
|
exp: Math.floor(Date.now() / 1000) + 5 * 60,
|
||||||
|
}
|
||||||
|
return headers
|
||||||
|
}
|
||||||
|
|
||||||
|
export const jwtService = new JWTService({
|
||||||
|
transformJWTHeaders: addIATHeaders,
|
||||||
|
defaultHeaders: DEFAULT_HEADERS,
|
||||||
|
defaultBody: DEFAULT_BODY,
|
||||||
|
defaultJWTHeaders: DEFAULT_HEADERS_JWT,
|
||||||
|
defaultJWTPayload: DEFAULT_DATA_JWT,
|
||||||
|
privateKeyPath: PRIVATE_KEY_PATH,
|
||||||
|
tokenUrl: GET_TOKEN_URL,
|
||||||
|
refreshTokenUrl: REFRESH_TOKEN_URL
|
||||||
|
})
|
||||||
@@ -8,6 +8,9 @@ import { OrderRepository } from "sim-shared/infrastructure/OrderRepository.js"
|
|||||||
import { TaskVolcadoLineas } from "./tasks/volcado_lineas.js"
|
import { TaskVolcadoLineas } from "./tasks/volcado_lineas.js"
|
||||||
import { ObjeniousLinesRepository } from "./infranstructure/ObjeniousLinesRepository.js"
|
import { ObjeniousLinesRepository } from "./infranstructure/ObjeniousLinesRepository.js"
|
||||||
import { postgresClientIntranet } from "./config/intranetPostgresConfig.js"
|
import { postgresClientIntranet } from "./config/intranetPostgresConfig.js"
|
||||||
|
import { PauseCancelTaskRepository } from "sim-consumidor-objenious/infrastructure/PauseCancelTaskRepository.js"
|
||||||
|
import { PauseTerminateTask } from "./tasks/check_pause_terminate.js"
|
||||||
|
import { SimUseCases } from "sim-consumidor-objenious/aplication/Sim.usecases.js"
|
||||||
|
|
||||||
async function startCron() {
|
async function startCron() {
|
||||||
const commonSettings = {
|
const commonSettings = {
|
||||||
@@ -21,7 +24,10 @@ async function startCron() {
|
|||||||
console.log("[i] Comprobando conexion con la BDD ")
|
console.log("[i] Comprobando conexion con la BDD ")
|
||||||
await pgClient.checkDatabaseConnection()
|
await pgClient.checkDatabaseConnection()
|
||||||
|
|
||||||
const operationRepository = new ObjeniousOperationsRepository(pgClient)
|
const operationRepository = new ObjeniousOperationsRepository(
|
||||||
|
httpClient,
|
||||||
|
pgClient,
|
||||||
|
)
|
||||||
const orderRepository = new OrderRepository(pgClient)
|
const orderRepository = new OrderRepository(pgClient)
|
||||||
const objeniousLineRepository = new ObjeniousLinesRepository(postgresClientIntranet)
|
const objeniousLineRepository = new ObjeniousLinesRepository(postgresClientIntranet)
|
||||||
|
|
||||||
@@ -31,8 +37,32 @@ async function startCron() {
|
|||||||
httpClient,
|
httpClient,
|
||||||
)
|
)
|
||||||
|
|
||||||
const volcadoLineasTask = new TaskVolcadoLineas(httpClient, objeniousLineRepository)
|
const objeniosRepo = new ObjeniousOperationsRepository(
|
||||||
|
httpClient,
|
||||||
|
pgClient
|
||||||
|
)
|
||||||
|
|
||||||
|
const volcadoLineasTask = new TaskVolcadoLineas(
|
||||||
|
objeniousLineRepository,
|
||||||
|
objeniosRepo
|
||||||
|
)
|
||||||
|
|
||||||
|
const pauseRepo = new PauseCancelTaskRepository(pgClient)
|
||||||
|
const simUsecases = new SimUseCases({
|
||||||
|
httpClient: httpClient,
|
||||||
|
operationRepository: operationRepository,
|
||||||
|
orderRepository: orderRepository,
|
||||||
|
pauseRepository: pauseRepo
|
||||||
|
})
|
||||||
|
|
||||||
|
const pauseTask = new PauseTerminateTask(
|
||||||
|
objeniosRepo,
|
||||||
|
pauseRepo,
|
||||||
|
simUsecases,
|
||||||
|
orderRepository
|
||||||
|
)
|
||||||
|
|
||||||
|
await objTask.getPendingOperations()
|
||||||
const PERIODO_PETICIONES = 10 * 60 * 1000
|
const PERIODO_PETICIONES = 10 * 60 * 1000
|
||||||
const interval = setInterval(async () => {
|
const interval = setInterval(async () => {
|
||||||
try {
|
try {
|
||||||
@@ -51,7 +81,11 @@ async function startCron() {
|
|||||||
}
|
}
|
||||||
}, PERIODO_VOLCADO)
|
}, PERIODO_VOLCADO)
|
||||||
|
|
||||||
await volcadoLineasTask.loadLines()
|
await pauseTask.run()
|
||||||
|
const PERIODO_CANCELACIONES = 60 * 60 * 1000;
|
||||||
|
const clacelacionesInterval = setInterval(async () => {
|
||||||
|
await pauseTask.run()
|
||||||
|
}, PERIODO_CANCELACIONES)
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,10 +3,11 @@ import { OrderRepository } from "sim-shared/infrastructure/OrderRepository.js";
|
|||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { IOperationsRepository, Objenious, ObjeniousOperation, ObjeniousOperationChange, StatusEnum } from "sim-shared/domain/operationsRepository.port.js";
|
import { IOperationsRepository, Objenious, ObjeniousOperation, ObjeniousOperationChange, StatusEnum } from "sim-shared/domain/operationsRepository.port.js";
|
||||||
import { HttpClient } from "sim-shared/infrastructure/HTTPClient.js";
|
import { HttpClient } from "sim-shared/infrastructure/HTTPClient.js";
|
||||||
|
import { ObjeniousOperationsRepository } from "sim-shared/infrastructure/ObjeniousOperationRepository.js";
|
||||||
|
|
||||||
export class CheckObjeniousRequests {
|
export class CheckObjeniousRequests {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly operationsRepository: IOperationsRepository,
|
private readonly operationsRepository: ObjeniousOperationsRepository,
|
||||||
private readonly orderRepository: OrderRepository,
|
private readonly orderRepository: OrderRepository,
|
||||||
private readonly httpClient: HttpClient
|
private readonly httpClient: HttpClient
|
||||||
) {
|
) {
|
||||||
|
|||||||
191
packages/sim-objenious-cron/tasks/check_pause_terminate.ts
Normal file
191
packages/sim-objenious-cron/tasks/check_pause_terminate.ts
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
import { ObjeniousLine } from "sim-shared/domain/objeniousLine.js";
|
||||||
|
import { PauseCancelTaskRepository } from "sim-consumidor-objenious/infrastructure/PauseCancelTaskRepository.js";
|
||||||
|
import { ObjeniousOperationsRepository } from "sim-shared/infrastructure/ObjeniousOperationRepository.js";
|
||||||
|
import { SimUseCases } from "sim-consumidor-objenious/aplication/Sim.usecases.js";
|
||||||
|
import { OrderRepository } from "sim-shared/infrastructure/OrderRepository.js";
|
||||||
|
|
||||||
|
const logger =
|
||||||
|
{
|
||||||
|
log: (...data: any[]) => console.log("[i] [TaskPauseTerminate]", ...data),
|
||||||
|
error: (...data: any[]) => console.error("[x] [TaskPauseTerminate] ", ...data),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export class PauseTerminateTask {
|
||||||
|
constructor(
|
||||||
|
private readonly objeniousRepo: ObjeniousOperationsRepository,
|
||||||
|
private readonly pauseRepo: PauseCancelTaskRepository,
|
||||||
|
private readonly simUsecases: SimUseCases,
|
||||||
|
private readonly orderRepo: OrderRepository
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public async run() {
|
||||||
|
const finError = (err: any) => {
|
||||||
|
logger.error("Finalizado con errores proceso de comprobacion de lineas en pausa o canceladas")
|
||||||
|
logger.error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
const finExito = () => {
|
||||||
|
logger.log("Finalizado con exito proceso de comprobacion de lineas en pausa o canceladas")
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
logger.log("Iniciando proceso de comprobacion de lineas en pausa o canceladas")
|
||||||
|
|
||||||
|
// 1. Se comprueba cuantas peticiones hay qye revisar
|
||||||
|
const peticionesRevisar = await this.pauseRepo.getPending()
|
||||||
|
|
||||||
|
if (peticionesRevisar.error != undefined) {
|
||||||
|
finError(peticionesRevisar.error)
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.log(`Se van a revisar ${peticionesRevisar.data?.length} peticiones`)
|
||||||
|
if (peticionesRevisar.data == undefined || peticionesRevisar.data.length == 0) {
|
||||||
|
finExito()
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 2. Se comprueba que alguna de las lineas haya dejado de estar en estado de test
|
||||||
|
const iccids = peticionesRevisar.data.map(e => e.iccid)
|
||||||
|
const lineasActualizadas: ObjeniousLine[] = []
|
||||||
|
|
||||||
|
const lineGenerator = this.objeniousRepo.getLinesByStatusAPI({
|
||||||
|
iccids: iccids
|
||||||
|
})
|
||||||
|
|
||||||
|
let lines = await lineGenerator.next()
|
||||||
|
|
||||||
|
if (lines.value.error != undefined || lines.value.data == undefined) {
|
||||||
|
logger.error("Error cargando las lineas", lines.value.error)
|
||||||
|
finError(lines.value.error)
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
lineasActualizadas.push(...lines.value.data)
|
||||||
|
}
|
||||||
|
|
||||||
|
while (!lines.done) {
|
||||||
|
if (lines.value.error != undefined || lines.value.data == undefined) {
|
||||||
|
logger.error("Error cargando las lineas", lines.value.error)
|
||||||
|
finError(lines.value.error)
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
lineasActualizadas.push(...lines.value.data)
|
||||||
|
}
|
||||||
|
|
||||||
|
lines = await lineGenerator.next()
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Cargado: ", lineasActualizadas)
|
||||||
|
|
||||||
|
// 3. Se separan las lineas que se tienen que actualizar al no ser test
|
||||||
|
// y las que se tienen que reencolar al ser test
|
||||||
|
const lineasNoTest = lineasActualizadas.filter(e => e.status.billingStatus != "TEST")
|
||||||
|
const lineasTest = lineasActualizadas.filter(e => e.status.billingStatus == "TEST")
|
||||||
|
|
||||||
|
// 4. Las lineas de test se reencolan
|
||||||
|
// El proximo reintento es en 1 dia
|
||||||
|
const proximoReintento = new Date()
|
||||||
|
proximoReintento.setDate(new Date().getDate() + 1)
|
||||||
|
|
||||||
|
// 5. Reintentos en 1 dia
|
||||||
|
for (const linea of lineasTest) {
|
||||||
|
const lineaId = peticionesRevisar.data
|
||||||
|
.find(e => e.iccid == linea.identifier.iccid)?.id
|
||||||
|
|
||||||
|
if (lineaId == undefined) continue; // Esto puede ser un problema si se generaliza
|
||||||
|
|
||||||
|
this.pauseRepo.updateTask({
|
||||||
|
id: lineaId,
|
||||||
|
next_check: proximoReintento
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. Operaciones de pausa/cancelacion definitiva
|
||||||
|
for (const linea of lineasNoTest) {
|
||||||
|
const operacion = peticionesRevisar.data
|
||||||
|
.find(e => e.iccid == linea.identifier.iccid)
|
||||||
|
|
||||||
|
if (operacion == undefined) continue;
|
||||||
|
const dueDate = new Date()
|
||||||
|
dueDate.setMinutes(new Date().getMinutes() + 15)
|
||||||
|
|
||||||
|
const operacionTipo = operacion.operation_type
|
||||||
|
const actionData = operacion.action_data
|
||||||
|
const correlation_id = operacion.action_data.correlation_id
|
||||||
|
actionData.dueDate = dueDate.toISOString()
|
||||||
|
|
||||||
|
switch (linea.status.billingStatus) {
|
||||||
|
case "ACTIVATED":
|
||||||
|
let result = null;
|
||||||
|
|
||||||
|
// Se termina el proceso aqui pero pasa a ser una operación de
|
||||||
|
// objenious por lo que puede fallar y quedaria registrado en
|
||||||
|
// la tabla objenious_operation
|
||||||
|
switch (operacionTipo) {
|
||||||
|
case "suspend":
|
||||||
|
result = await this.simUsecases.suspend(actionData)()
|
||||||
|
break;
|
||||||
|
case "terminate":
|
||||||
|
result = await this.simUsecases.terminate(actionData)()
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result == undefined) {
|
||||||
|
logger.error("Operacion desconocida", operacion)
|
||||||
|
} else if (result?.error != undefined) {
|
||||||
|
// error usecase
|
||||||
|
logger.error(result.error)
|
||||||
|
await this.pauseRepo.finishTask({
|
||||||
|
id: operacion.id,
|
||||||
|
error: result.error
|
||||||
|
})
|
||||||
|
if (correlation_id != undefined)
|
||||||
|
await this.orderRepo.errorOrder({
|
||||||
|
correlation_id: correlation_id,
|
||||||
|
status: "dlx",
|
||||||
|
reason: result.error
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// ok
|
||||||
|
await this.pauseRepo.finishTask({ id: operacion.id })
|
||||||
|
if (correlation_id != undefined)
|
||||||
|
await this.orderRepo.finishOrder({ correlation_id })
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case "CANCELED":
|
||||||
|
await this.pauseRepo.finishTask({
|
||||||
|
id: operacion.id,
|
||||||
|
error: "billingStatus is CANCELED"
|
||||||
|
})
|
||||||
|
if (correlation_id != undefined)
|
||||||
|
await this.orderRepo.finishOrder({ correlation_id })
|
||||||
|
break;
|
||||||
|
case "SUSPENDED":
|
||||||
|
await this.pauseRepo.finishTask({
|
||||||
|
id: operacion.id,
|
||||||
|
error: "billingStatus is SUSPENDED"
|
||||||
|
})
|
||||||
|
if (correlation_id != undefined)
|
||||||
|
await this.orderRepo.finishOrder({ correlation_id })
|
||||||
|
break;
|
||||||
|
case "TEST":
|
||||||
|
// No puede ser
|
||||||
|
default:
|
||||||
|
logger.error("billingStatus desconocido", linea.status.billingStatus)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
finExito()
|
||||||
|
} catch (e) {
|
||||||
|
finError(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,94 +1,14 @@
|
|||||||
import assert from "node:assert";
|
import { lineToCreateLineDto, ObjeniousLine } from "sim-shared/domain/objeniousLine.js";
|
||||||
import { lineToCreateLineDto, ObjeniousLine, ObjeniousLineResponse } from "sim-shared/domain/objeniousLine.js";
|
|
||||||
import { tryCatch, Result } from "sim-shared/domain/Result.js";
|
|
||||||
import { HttpClient } from "sim-shared/infrastructure/HTTPClient.js";
|
|
||||||
import { ObjeniousLinesRepository } from "../infranstructure/ObjeniousLinesRepository.js";
|
import { ObjeniousLinesRepository } from "../infranstructure/ObjeniousLinesRepository.js";
|
||||||
import { AxiosResponse } from "axios";
|
import { ObjeniousOperationsRepository } from "sim-shared/infrastructure/ObjeniousOperationRepository.js";
|
||||||
import { constants } from "node:buffer";
|
|
||||||
|
|
||||||
const MAX_PAGE_SIZE = 100
|
|
||||||
|
|
||||||
export class TaskVolcadoLineas {
|
export class TaskVolcadoLineas {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly httpClient: HttpClient,
|
|
||||||
private readonly linesRepository: ObjeniousLinesRepository,
|
private readonly linesRepository: ObjeniousLinesRepository,
|
||||||
|
private readonly objeniousRepository: ObjeniousOperationsRepository
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Mover al repo
|
|
||||||
*/
|
|
||||||
private async * getLinesByStatus(args?: {
|
|
||||||
pageSize?: number,
|
|
||||||
pageNumber?: number,
|
|
||||||
status?: string
|
|
||||||
}): AsyncGenerator<Result<string, ObjeniousLine[]>, Result<string, ObjeniousLine[]>, any> {
|
|
||||||
|
|
||||||
const path = "/lines"
|
|
||||||
const pageSize = args?.pageSize ?? MAX_PAGE_SIZE;
|
|
||||||
|
|
||||||
let currentPage = args?.pageNumber ?? 0;
|
|
||||||
let totalPages: number | undefined = undefined; // Como limite de paginas, igual es pasarse pero hasta que se lea
|
|
||||||
|
|
||||||
const params: Record<string, string | number> = {}
|
|
||||||
|
|
||||||
const loadNextLine = async (page: number): Promise<Result<string, ObjeniousLine[]>> => {
|
|
||||||
if (args?.status != undefined) params["simStatus"] = args.status
|
|
||||||
params["pageSize"] = pageSize
|
|
||||||
params["pageNumber"] = page
|
|
||||||
console.log("Params", params)
|
|
||||||
console.log(`[i] Cargando pagina ${currentPage} de ${totalPages ?? "(desc)"}`)
|
|
||||||
const nextPage = await tryCatch<AxiosResponse<ObjeniousLineResponse>>(this.httpClient.client.get(path, {
|
|
||||||
params: params
|
|
||||||
}))
|
|
||||||
|
|
||||||
if (nextPage.error != undefined) {
|
|
||||||
console.error(nextPage.error.msg)
|
|
||||||
return {
|
|
||||||
error: nextPage.error.msg.message
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Se aumenta para la siguiente ejecucion
|
|
||||||
console.log(`[i] Página ${currentPage} completa, total: ${nextPage.data.data.totalPages}`)
|
|
||||||
totalPages = nextPage.data.data.totalPages
|
|
||||||
|
|
||||||
return {
|
|
||||||
data: nextPage.data.data.content
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// El inicio se ejecuta siempre
|
|
||||||
const lines = await loadNextLine(currentPage)
|
|
||||||
|
|
||||||
if (lines.error != undefined) {
|
|
||||||
console.error("[x] Error obteniendo las lineas, cancelando operación");
|
|
||||||
return {
|
|
||||||
error: "Error cargando lineas"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
currentPage++;
|
|
||||||
|
|
||||||
yield {
|
|
||||||
data: lines.data
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copia para evitar bucles infinitos por error de la api
|
|
||||||
const maxPages = totalPages
|
|
||||||
assert.ok(maxPages != undefined, "No se ha defindo el numero de paginas") // Nunca deberia pasar pero así se evitan bucles infnitos
|
|
||||||
console.log("maxPages", maxPages)
|
|
||||||
for (let i = currentPage; i < maxPages!; i++) {
|
|
||||||
console.log("Bucle i:", i, "page: ", currentPage)
|
|
||||||
yield await loadNextLine(currentPage);
|
|
||||||
currentPage++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
data: []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async saveLines(lines: ObjeniousLine[]) {
|
private async saveLines(lines: ObjeniousLine[]) {
|
||||||
const linesToCreate = lines.map(lineToCreateLineDto)
|
const linesToCreate = lines.map(lineToCreateLineDto)
|
||||||
@@ -107,7 +27,9 @@ export class TaskVolcadoLineas {
|
|||||||
console.log("[i] Iniciando task de volcado de lineas de Objenious")
|
console.log("[i] Iniciando task de volcado de lineas de Objenious")
|
||||||
// Carga todas las lineas en memoria, hay que comprobar que no se gaste demasiada
|
// Carga todas las lineas en memoria, hay que comprobar que no se gaste demasiada
|
||||||
|
|
||||||
const linesIterator = this.getLinesByStatus()
|
const linesIterator = this.objeniousRepository.getLinesByStatusAPI({
|
||||||
|
pageSize: 100
|
||||||
|
})
|
||||||
let lines = await linesIterator.next()
|
let lines = await linesIterator.next()
|
||||||
|
|
||||||
if (lines.value.error != undefined || lines.value.data == undefined) {
|
if (lines.value.error != undefined || lines.value.data == undefined) {
|
||||||
@@ -118,7 +40,6 @@ export class TaskVolcadoLineas {
|
|||||||
await this.saveLines(lines.value.data)
|
await this.saveLines(lines.value.data)
|
||||||
|
|
||||||
while (!lines.done) {
|
while (!lines.done) {
|
||||||
console.log()
|
|
||||||
lines = await linesIterator.next()
|
lines = await linesIterator.next()
|
||||||
if (lines.value.error != undefined || lines.value.data == undefined) {
|
if (lines.value.error != undefined || lines.value.data == undefined) {
|
||||||
console.error("[x] Error cargando las lineas a volcar", lines.value.error)
|
console.error("[x] Error cargando las lineas a volcar", lines.value.error)
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
],
|
],
|
||||||
"include": [
|
"include": [
|
||||||
"**/*.ts",
|
"**/*.ts",
|
||||||
|
"**/*.d.ts",
|
||||||
"../../packages/sim-shared/**/*.ts",
|
"../../packages/sim-shared/**/*.ts",
|
||||||
"../../packages/sim-consumidor-objenious/**/*.ts"
|
"../../packages/sim-consumidor-objenious/**/*.ts"
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
import { test, describe } from "vitest"
|
import { test, describe } from "vitest"
|
||||||
import { JWTService } from "./JWT.service.js"
|
import { jwtService } from "../config/jwtService.config.js"
|
||||||
|
|
||||||
describe("Tokens Objenious", () => {
|
describe("Tokens Objenious", () => {
|
||||||
const jwtService = new JWTService()
|
const jwt = jwtService
|
||||||
|
|
||||||
test("Solicicitud normal de auth", async () => {
|
test("Solicicitud normal de auth", async () => {
|
||||||
const token = await jwtService.getAccessToken()
|
const token = await jwt.getAccessToken()
|
||||||
console.log("acceso objenious", token)
|
console.log("acceso objenious", token)
|
||||||
}),
|
}),
|
||||||
|
|
||||||
test("Solicicitud de refresh de auth", async () => {
|
test("Solicicitud de refresh de auth", async () => {
|
||||||
const token = await jwtService.tryRefreshToken()
|
const token = await jwt.tryRefreshToken()
|
||||||
console.log("acceso refresh objenious", token)
|
console.log("acceso refresh objenious", token)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -4,24 +4,24 @@
|
|||||||
* el cliente HTTP
|
* el cliente HTTP
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { env } from "#config/env/index.js";
|
|
||||||
import fs from "fs"
|
import fs from "fs"
|
||||||
|
|
||||||
import {
|
import {
|
||||||
JWTToken,
|
JWTToken,
|
||||||
JWTHeader,
|
JWTHeader,
|
||||||
IJWTService
|
IJWTService,
|
||||||
|
JWTPayload
|
||||||
} from "sim-shared/domain/JWT.js"
|
} from "sim-shared/domain/JWT.js"
|
||||||
import axios, { AxiosError } from "axios";
|
import axios, { AxiosError } from "axios";
|
||||||
|
|
||||||
type GrantAccessRequestBody = {
|
export type GrantAccessRequestBody = {
|
||||||
grant_type: string,
|
grant_type: string,
|
||||||
client_id: string,
|
client_id: string,
|
||||||
client_assertion_type: string,
|
client_assertion_type: string,
|
||||||
client_assertion: string
|
client_assertion: string
|
||||||
}
|
}
|
||||||
|
|
||||||
type TokensRequestResponse = {
|
export type TokensRequestResponse = {
|
||||||
"access_token": string,
|
"access_token": string,
|
||||||
"expires_in": number,
|
"expires_in": number,
|
||||||
"refresh_token": string
|
"refresh_token": string
|
||||||
@@ -32,41 +32,6 @@ type TokensRequestResponse = {
|
|||||||
"scope": string
|
"scope": string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const PRIVATE_KEY_PATH = env.OBJ_PEM_PATH
|
|
||||||
|
|
||||||
const GET_TOKEN_URL = "https://idp.docapost.io/auth/realms/GETWAY/protocol/openid-connect/token"
|
|
||||||
const REFRESH_TOKEN_URL = GET_TOKEN_URL
|
|
||||||
|
|
||||||
const DEFAULT_BODY: GrantAccessRequestBody = {
|
|
||||||
grant_type: "client_credentials",
|
|
||||||
client_id: env.OBJ_CLIENT_ID,
|
|
||||||
client_assertion_type: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
|
|
||||||
client_assertion: env.OBJ_CLI_ASSERTION
|
|
||||||
}
|
|
||||||
|
|
||||||
const REFRESH_BODY = {
|
|
||||||
...DEFAULT_BODY,
|
|
||||||
grant_type: "refresh_token",
|
|
||||||
}
|
|
||||||
|
|
||||||
const DEFAULT_HEADERS = {
|
|
||||||
"content-type": "application/x-www-form-urlencoded"
|
|
||||||
}
|
|
||||||
|
|
||||||
function addIATHeaders(authHeaders: Object) {
|
|
||||||
const headers = <JWTHeader>{
|
|
||||||
...authHeaders,
|
|
||||||
sub: env.OBJ_CLIENT_ID,
|
|
||||||
iss: env.OBJ_CLIENT_ID,
|
|
||||||
aud: GET_TOKEN_URL,
|
|
||||||
jti: Date.now().toString(),
|
|
||||||
iat: Math.floor(Date.now() / 1000),
|
|
||||||
exp: Math.floor(Date.now() / 1000) + 5 * 60,
|
|
||||||
}
|
|
||||||
return headers
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ObjeniousTokenBody = any
|
export type ObjeniousTokenBody = any
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -82,27 +47,54 @@ export class JWTService implements IJWTService<ObjeniousTokenBody> {
|
|||||||
public authToken: JWTToken<ObjeniousTokenBody> | undefined;
|
public authToken: JWTToken<ObjeniousTokenBody> | undefined;
|
||||||
private refreshToken?: JWTToken<ObjeniousTokenBody> | undefined;
|
private refreshToken?: JWTToken<ObjeniousTokenBody> | undefined;
|
||||||
|
|
||||||
constructor(args?: {
|
// http
|
||||||
|
private transformHeaders?: (_: Object) => JWTHeader;
|
||||||
|
private defaultHttpHeaders: Record<string, string>;
|
||||||
|
private defaultBody: Record<string, string>;
|
||||||
|
|
||||||
|
// jwt
|
||||||
|
private defaultJWTHeaders: JWTHeader;
|
||||||
|
private defaultJWTPayload: JWTPayload<any>;
|
||||||
|
private privateKeyPath: string;
|
||||||
|
private tokenUrl: string;
|
||||||
|
private refreshTokenUrl: string;
|
||||||
|
|
||||||
|
|
||||||
|
constructor(args: {
|
||||||
token?: string // si se partiese de un token existente,
|
token?: string // si se partiese de un token existente,
|
||||||
refreshToken?: string
|
refreshToken?: string,
|
||||||
|
transformJWTHeaders?: (_: Object) => JWTHeader,
|
||||||
|
defaultHeaders: Record<string, string>,
|
||||||
|
defaultBody: Record<string, string>,
|
||||||
|
defaultJWTHeaders: JWTHeader,
|
||||||
|
defaultJWTPayload: JWTPayload<any>,
|
||||||
|
privateKeyPath: string,
|
||||||
|
tokenUrl: string,
|
||||||
|
refreshTokenUrl: string
|
||||||
}) {
|
}) {
|
||||||
if (args?.token != undefined) this.authToken = new JWTToken(args.token)
|
if (args?.token != undefined) this.authToken = new JWTToken(args.token)
|
||||||
if (args?.refreshToken != undefined) this.refreshToken = new JWTToken(args.refreshToken)
|
if (args?.refreshToken != undefined) this.refreshToken = new JWTToken(args.refreshToken)
|
||||||
|
if (args?.transformJWTHeaders != undefined) this.transformHeaders = args.transformJWTHeaders
|
||||||
|
|
||||||
|
this.defaultHttpHeaders = args.defaultHeaders;
|
||||||
|
this.defaultBody = args.defaultBody;
|
||||||
|
|
||||||
|
this.defaultJWTHeaders = args.defaultJWTHeaders;
|
||||||
|
this.defaultJWTPayload = args.defaultJWTPayload;
|
||||||
|
this.privateKeyPath = args.privateKeyPath;
|
||||||
|
|
||||||
|
this.tokenUrl = args.tokenUrl;
|
||||||
|
this.refreshTokenUrl = args.refreshTokenUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
private buildJwtBody() {
|
private buildJwtBody() {
|
||||||
const jwtHeaders = {
|
const jwtHeaders = this.defaultJWTHeaders
|
||||||
alg: "RS256",
|
|
||||||
typ: "JWT",
|
const jwtData = (this.transformHeaders) ?
|
||||||
kid: env.OBJ_KID
|
this.transformHeaders(this.defaultJWTPayload) :
|
||||||
}
|
this.defaultJWTPayload;
|
||||||
const jwtData = addIATHeaders({
|
|
||||||
sub: env.OBJ_CLIENT_ID,
|
const key = fs.readFileSync(this.privateKeyPath, "utf8")
|
||||||
iss: env.OBJ_CLIENT_ID,
|
|
||||||
aud: "https://idp.docapost.io/auth/realms/GETWAY",
|
|
||||||
jti: Date.now().toString(),
|
|
||||||
})
|
|
||||||
const key = fs.readFileSync(PRIVATE_KEY_PATH, "utf8")
|
|
||||||
const token = JWTToken.fromParts({
|
const token = JWTToken.fromParts({
|
||||||
header: jwtHeaders,
|
header: jwtHeaders,
|
||||||
payload: jwtData,
|
payload: jwtData,
|
||||||
@@ -116,14 +108,16 @@ export class JWTService implements IJWTService<ObjeniousTokenBody> {
|
|||||||
|
|
||||||
public async getNewAuthToken() {
|
public async getNewAuthToken() {
|
||||||
const bodyWithtoken = {
|
const bodyWithtoken = {
|
||||||
...DEFAULT_BODY,
|
...this.defaultBody,
|
||||||
client_assertion: this.buildJwtBody()
|
client_assertion: this.buildJwtBody()
|
||||||
}
|
}
|
||||||
|
|
||||||
const req = axios.post(GET_TOKEN_URL,
|
const headers = (this.transformHeaders) ? this.transformHeaders(this.defaultHttpHeaders) : this.defaultHttpHeaders;
|
||||||
|
|
||||||
|
const req = axios.post(this.tokenUrl,
|
||||||
bodyWithtoken,
|
bodyWithtoken,
|
||||||
{
|
{
|
||||||
headers: addIATHeaders(DEFAULT_HEADERS)
|
headers: headers
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -166,16 +160,21 @@ export class JWTService implements IJWTService<ObjeniousTokenBody> {
|
|||||||
if (this.refreshToken == undefined) throw new Error("El refreshToken no está definido")
|
if (this.refreshToken == undefined) throw new Error("El refreshToken no está definido")
|
||||||
if (this.refreshToken.isExpired()) throw new Error("El refreshToken ha expirado")
|
if (this.refreshToken.isExpired()) throw new Error("El refreshToken ha expirado")
|
||||||
|
|
||||||
|
const refreshBody = {
|
||||||
|
...this.defaultBody,
|
||||||
|
grant_type: "refresh_token",
|
||||||
|
}
|
||||||
|
|
||||||
const body = {
|
const body = {
|
||||||
...REFRESH_BODY,
|
...refreshBody,
|
||||||
client_assertion: this.buildJwtBody(),
|
client_assertion: this.buildJwtBody(),
|
||||||
refresh_token: this.refreshToken.rawToken
|
refresh_token: this.refreshToken.rawToken
|
||||||
}
|
}
|
||||||
|
|
||||||
const req = axios.post(REFRESH_TOKEN_URL,
|
const req = axios.post(this.refreshTokenUrl,
|
||||||
body,
|
body,
|
||||||
{
|
{
|
||||||
headers: DEFAULT_HEADERS
|
headers: this.defaultHttpHeaders
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -7,9 +7,12 @@
|
|||||||
import { env, loadEnvFile } from "node:process";
|
import { env, loadEnvFile } from "node:process";
|
||||||
import { Pool } from "pg";
|
import { Pool } from "pg";
|
||||||
import { PgClient } from "../infrastructure/PgClient.js";
|
import { PgClient } from "../infrastructure/PgClient.js";
|
||||||
|
import { HttpClient } from "../infrastructure/HTTPClient.js";
|
||||||
|
import { jwtService } from "./jwtService.config.js";
|
||||||
|
|
||||||
console.warn("[i!] Se está corriendo codigo de test")
|
console.warn("[i!] Se está corriendo codigo de test")
|
||||||
loadEnvFile("../../.env") // Global
|
loadEnvFile("../../.env") // Global
|
||||||
|
loadEnvFile("./test.env") // Local
|
||||||
|
|
||||||
// se hace una por servicio.
|
// se hace una por servicio.
|
||||||
export const pgPool = new Pool({
|
export const pgPool = new Pool({
|
||||||
@@ -24,4 +27,14 @@ export const postgresClient = new PgClient({
|
|||||||
pool: pgPool
|
pool: pgPool
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const OBJ_BASE_URL = "https://api-getway.objenious.com/ws"
|
||||||
|
export const httpObjClient = new HttpClient({
|
||||||
|
baseURL: OBJ_BASE_URL,
|
||||||
|
headers: {
|
||||||
|
"content-type": " application/json; charset=utf-8"
|
||||||
|
},
|
||||||
|
jwtManager: jwtService
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
console.warn(`[T] TEST DB : ${env.POSTGRES_DATABASE}@${env.POSTGRES_HOST}`)
|
console.warn(`[T] TEST DB : ${env.POSTGRES_DATABASE}@${env.POSTGRES_HOST}`)
|
||||||
|
|||||||
67
packages/sim-shared/config/jwtService.config.ts
Normal file
67
packages/sim-shared/config/jwtService.config.ts
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
import assert from "assert"
|
||||||
|
import { env, loadEnvFile } from "process"
|
||||||
|
import { GrantAccessRequestBody, JWTService } from "sim-shared/aplication/JWT.service.js"
|
||||||
|
import { JWTHeader } from "sim-shared/domain/JWT.js"
|
||||||
|
|
||||||
|
loadEnvFile("../../.env") // Global
|
||||||
|
loadEnvFile("./test.env") // Local
|
||||||
|
|
||||||
|
assert(env.OBJ_CLIENT_ID != undefined)
|
||||||
|
assert(env.OBJ_CLI_ASSERTION != undefined)
|
||||||
|
assert(env.OBJ_PEM_PATH != undefined)
|
||||||
|
|
||||||
|
const PRIVATE_KEY_PATH = env.OBJ_PEM_PATH
|
||||||
|
|
||||||
|
const GET_TOKEN_URL = "https://idp.docapost.io/auth/realms/GETWAY/protocol/openid-connect/token"
|
||||||
|
const REFRESH_TOKEN_URL = GET_TOKEN_URL
|
||||||
|
|
||||||
|
|
||||||
|
const DEFAULT_BODY: GrantAccessRequestBody = {
|
||||||
|
grant_type: "client_credentials",
|
||||||
|
client_id: env.OBJ_CLIENT_ID,
|
||||||
|
client_assertion_type: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
|
||||||
|
client_assertion: env.OBJ_CLI_ASSERTION
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const DEFAULT_HEADERS = {
|
||||||
|
"content-type": "application/x-www-form-urlencoded"
|
||||||
|
}
|
||||||
|
|
||||||
|
const DEFAULT_HEADERS_JWT = {
|
||||||
|
alg: "RS256",
|
||||||
|
typ: "JWT",
|
||||||
|
kid: env.OBJ_KID,
|
||||||
|
}
|
||||||
|
|
||||||
|
const DEFAULT_DATA_JWT = {
|
||||||
|
sub: env.OBJ_CLIENT_ID,
|
||||||
|
iss: env.OBJ_CLIENT_ID,
|
||||||
|
aud: "https://idp.docapost.io/auth/realms/GETWAY",
|
||||||
|
jti: Date.now().toString(),
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function addIATHeaders(authHeaders: Object) {
|
||||||
|
const headers = <JWTHeader>{
|
||||||
|
...authHeaders,
|
||||||
|
sub: env.OBJ_CLIENT_ID,
|
||||||
|
iss: env.OBJ_CLIENT_ID,
|
||||||
|
aud: GET_TOKEN_URL,
|
||||||
|
jti: Date.now().toString(),
|
||||||
|
iat: Math.floor(Date.now() / 1000),
|
||||||
|
exp: Math.floor(Date.now() / 1000) + 5 * 60,
|
||||||
|
}
|
||||||
|
return headers
|
||||||
|
}
|
||||||
|
|
||||||
|
export const jwtService = new JWTService({
|
||||||
|
transformJWTHeaders: addIATHeaders,
|
||||||
|
defaultHeaders: DEFAULT_HEADERS,
|
||||||
|
defaultBody: DEFAULT_BODY,
|
||||||
|
defaultJWTHeaders: DEFAULT_HEADERS_JWT,
|
||||||
|
defaultJWTPayload: DEFAULT_DATA_JWT,
|
||||||
|
privateKeyPath: PRIVATE_KEY_PATH,
|
||||||
|
tokenUrl: GET_TOKEN_URL,
|
||||||
|
refreshTokenUrl: REFRESH_TOKEN_URL
|
||||||
|
})
|
||||||
@@ -80,7 +80,8 @@ export type FinishOrderDTO =
|
|||||||
IdOrCorrelationID
|
IdOrCorrelationID
|
||||||
&
|
&
|
||||||
{
|
{
|
||||||
reason?: string
|
reason?: string,
|
||||||
|
end_date?: Date
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ErrorOrderDTO =
|
export type ErrorOrderDTO =
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export type Failure<E = Error> = {
|
|||||||
*/
|
*/
|
||||||
export type Result<E, D> = Failure<E> | Success<D>
|
export type Result<E, D> = Failure<E> | Success<D>
|
||||||
|
|
||||||
export async function tryCatch<T>(func: Promise<T>): Promise<Result<{ msg: Error }, T>> {
|
export async function tryCatch<T>(func: Promise<T>): Promise<Result<Error, T>> {
|
||||||
try {
|
try {
|
||||||
const res = await func;
|
const res = await func;
|
||||||
return {
|
return {
|
||||||
@@ -22,9 +22,8 @@ export async function tryCatch<T>(func: Promise<T>): Promise<Result<{ msg: Error
|
|||||||
}
|
}
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
return {
|
return {
|
||||||
error: {
|
error: e as Error
|
||||||
msg: e as Error
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export namespace SimEvents {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type reActivation = DomainEvent & {
|
export type reActivation = DomainEvent & {
|
||||||
key: `sim.${string}.reActivate`,
|
key: `sim.${string}.reactivate`,
|
||||||
payload: {
|
payload: {
|
||||||
iccid: string
|
iccid: string
|
||||||
},
|
},
|
||||||
@@ -47,6 +47,7 @@ export namespace SimEvents {
|
|||||||
options: {
|
options: {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
export type suspend = pause
|
||||||
|
|
||||||
export type free = DomainEvent & {
|
export type free = DomainEvent & {
|
||||||
key: `sim.${string}.free`,
|
key: `sim.${string}.free`,
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ export type ObjeniousLine = {
|
|||||||
commercialStatus: string, //"test",
|
commercialStatus: string, //"test",
|
||||||
commercialStatusDate: string, //"2026-03-17T11:41:01.493+00:00",
|
commercialStatusDate: string, //"2026-03-17T11:41:01.493+00:00",
|
||||||
networkStatus: string, // "ACTIVATED",
|
networkStatus: string, // "ACTIVATED",
|
||||||
billingStatus: string, //"TEST",
|
billingStatus: "ACTIVATED" | "SUSPENDED" | "CANCELED" | "TEST",
|
||||||
billingStatusChangeDate: string | null, // "2026-03-17T11:01:00.276+00:00",
|
billingStatusChangeDate: string | null, // "2026-03-17T11:01:00.276+00:00",
|
||||||
billingActivationDate: string | null //,
|
billingActivationDate: string | null //,
|
||||||
createdDate: string | null,//"2026-01-30T01:50:02.060+00:00"
|
createdDate: string | null,//"2026-01-30T01:50:02.060+00:00"
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export type ObjeniousOperation = {
|
|||||||
id?: number;
|
id?: number;
|
||||||
/** Uuid del mensaje asociado a la operacion */
|
/** Uuid del mensaje asociado a la operacion */
|
||||||
correlation_id?: string;
|
correlation_id?: string;
|
||||||
operation: "activate" | string; // TODO: completar y actualizar
|
operation: "activate" | "suspend" | "terminate" | string; // TODO: completar y actualizar
|
||||||
retry_count?: number;
|
retry_count?: number;
|
||||||
max_retry?: number;
|
max_retry?: number;
|
||||||
max_date_retry?: string | null;
|
max_date_retry?: string | null;
|
||||||
@@ -27,8 +27,7 @@ export type ObjeniousOperation = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type ObjeniousOperationChange = {
|
export type ObjeniousOperationChange = {
|
||||||
id?: number;
|
id?: number; operation_id: number;
|
||||||
operation_id: number;
|
|
||||||
info?: string | null;
|
info?: string | null;
|
||||||
error?: string | null;
|
error?: string | null;
|
||||||
new_status: StatusEnum;
|
new_status: StatusEnum;
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import { before, describe, it } from "node:test";
|
||||||
|
import { ObjeniousOperationsRepository } from "./ObjeniousOperationRepository.js";
|
||||||
|
import { httpObjClient, postgresClient } from "../config/config.test.js";
|
||||||
|
import { ObjeniousOperation } from "../domain/operationsRepository.port.js";
|
||||||
|
|
||||||
|
const correctOperation: ObjeniousOperation = {
|
||||||
|
iccids: "test",
|
||||||
|
operation: "activate",
|
||||||
|
status: "finished"
|
||||||
|
}
|
||||||
|
|
||||||
|
const errorOperation: ObjeniousOperation = {
|
||||||
|
iccids: "test",
|
||||||
|
operation: "terminate",
|
||||||
|
status: "error",
|
||||||
|
error: "mensaje de error"
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("[Integration] Test API requests", () => {
|
||||||
|
const repository = new ObjeniousOperationsRepository(
|
||||||
|
httpObjClient,
|
||||||
|
postgresClient
|
||||||
|
)
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
await repository.createOperation(correctOperation)
|
||||||
|
await repository.createOperation(errorOperation)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Read last operation by line", () => {
|
||||||
|
/**
|
||||||
|
* Objetivo:
|
||||||
|
* - Cuando se va a hacer una operacion de sim hay que cancelarla directamente si:
|
||||||
|
* - Ya hay una en curso del mismo tipo.
|
||||||
|
* - Ya ha terminado una del mismo tipo.
|
||||||
|
* - Se ignoran las erroneas
|
||||||
|
*/
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -1,14 +1,139 @@
|
|||||||
import { IOperationsRepository, ObjeniousOperation, ObjeniousOperationChange } from "sim-shared/domain/operationsRepository.port.js";
|
import { IOperationsRepository, ObjeniousOperation, ObjeniousOperationChange } from "sim-shared/domain/operationsRepository.port.js";
|
||||||
import { Result } from "sim-shared/domain/Result.js";
|
import { Result, tryCatch } from "sim-shared/domain/Result.js";
|
||||||
import { PgClient } from "sim-shared/infrastructure/PgClient.js";
|
import { PgClient } from "sim-shared/infrastructure/PgClient.js";
|
||||||
|
import { ObjeniousLine, ObjeniousLineResponse } from "../domain/objeniousLine.js";
|
||||||
|
import { HttpClient } from "./HTTPClient.js";
|
||||||
|
import assert from "node:assert";
|
||||||
|
import { AxiosResponse } from "axios";
|
||||||
|
|
||||||
export class ObjeniousOperationsRepository implements IOperationsRepository {
|
export class ObjeniousOperationsRepository implements IOperationsRepository {
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
private http: HttpClient,
|
||||||
private readonly pgClient: PgClient
|
private readonly pgClient: PgClient
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Consulta el estado de una o mas lineas directamente a la API de Objenious
|
||||||
|
* TODO: No hay paginacion como en getLinesByStatusAPI
|
||||||
|
*/
|
||||||
|
public async getLinesAPI(
|
||||||
|
identifierType: "ICCID" | "IMSI" | "IMEI" | "MSISDN" | "REFERENCE",
|
||||||
|
identifiers: string[]
|
||||||
|
): Promise<Result<string, ObjeniousLine[]>> {
|
||||||
|
if (identifiers.length == 0) {
|
||||||
|
return {
|
||||||
|
data: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Comprobar < MAX_PAGE_SIZE (Poco probable)
|
||||||
|
|
||||||
|
const path = "/lines"
|
||||||
|
const params = {
|
||||||
|
"identifier.identifierType": identifierType,
|
||||||
|
"identifier.identifiers": identifiers.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
const req = this.http.client.get<ObjeniousLineResponse>(path, {
|
||||||
|
params: params
|
||||||
|
})
|
||||||
|
|
||||||
|
const res = await tryCatch(req)
|
||||||
|
|
||||||
|
if (res.error != undefined) {
|
||||||
|
return {
|
||||||
|
error: res.error?.message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const lines = res.data.data.content
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: lines
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private MAX_PAGE_SIZE = 1000
|
||||||
|
public async * getLinesByStatusAPI(args?: {
|
||||||
|
pageSize?: number,
|
||||||
|
pageNumber?: number,
|
||||||
|
status?: string,
|
||||||
|
iccids?: string[]
|
||||||
|
}): AsyncGenerator<Result<string, ObjeniousLine[]>, Result<string, ObjeniousLine[]>, any> {
|
||||||
|
|
||||||
|
const path = "/lines"
|
||||||
|
const pageSize = args?.pageSize ?? this.MAX_PAGE_SIZE;
|
||||||
|
|
||||||
|
let currentPage = args?.pageNumber ?? 0;
|
||||||
|
let totalPages: number | undefined = undefined; // Como limite de paginas, igual es pasarse pero hasta que se lea
|
||||||
|
|
||||||
|
const params: Record<string, string | number> = {}
|
||||||
|
|
||||||
|
// Si se va a filtrar por iccids especificamente, en un futuro habra que ampliar el tipo de filtros
|
||||||
|
if (args?.iccids != undefined) {
|
||||||
|
params["identifier.identifierType"] = "ICCID"
|
||||||
|
params["identifier.identifiers"] = args.iccids.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadNextLine = async (page: number): Promise<Result<string, ObjeniousLine[]>> => {
|
||||||
|
if (args?.status != undefined) params["simStatus"] = args.status
|
||||||
|
params["pageSize"] = pageSize
|
||||||
|
params["pageNumber"] = page
|
||||||
|
console.log(`[i] Cargando pagina ${currentPage} de ${totalPages ?? "(desc)"}`)
|
||||||
|
const nextPage = await tryCatch<AxiosResponse<ObjeniousLineResponse>>(this.http.client.get(path, {
|
||||||
|
params: params
|
||||||
|
}))
|
||||||
|
|
||||||
|
if (nextPage.error != undefined) {
|
||||||
|
console.error(nextPage.error)
|
||||||
|
return {
|
||||||
|
error: nextPage.error.message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Se aumenta para la siguiente ejecucion
|
||||||
|
console.log(`[i] Página ${currentPage} completa, total: ${nextPage.data.data.totalPages}`)
|
||||||
|
totalPages = nextPage.data.data.totalPages
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: nextPage.data.data.content
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// El inicio se ejecuta siempre
|
||||||
|
const lines = await loadNextLine(currentPage)
|
||||||
|
|
||||||
|
if (lines.error != undefined) {
|
||||||
|
console.error("[x] Error obteniendo las lineas, cancelando operación");
|
||||||
|
return {
|
||||||
|
error: "Error cargando lineas"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
currentPage++;
|
||||||
|
|
||||||
|
yield {
|
||||||
|
data: lines.data
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copia para evitar bucles infinitos por error de la api
|
||||||
|
const maxPages = totalPages
|
||||||
|
assert.ok(maxPages != undefined, "No se ha defindo el numero de paginas") // Nunca deberia pasar pero así se evitan bucles infnitos
|
||||||
|
console.log("maxPages", maxPages)
|
||||||
|
for (let i = currentPage; i < maxPages!; i++) {
|
||||||
|
console.log("Bucle i:", i, "page: ", currentPage)
|
||||||
|
yield await loadNextLine(currentPage);
|
||||||
|
currentPage++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: []
|
||||||
|
}
|
||||||
|
}
|
||||||
async createOperation(data: ObjeniousOperation): Promise<Result<string, ObjeniousOperation>> {
|
async createOperation(data: ObjeniousOperation): Promise<Result<string, ObjeniousOperation>> {
|
||||||
const query = `
|
const query = `
|
||||||
INSERT INTO objenious_operation (operation, iccids, status, max_retry, request_id)
|
INSERT INTO objenious_operation (operation, iccids, status, max_retry, request_id)
|
||||||
@@ -21,6 +146,20 @@ export class ObjeniousOperationsRepository implements IOperationsRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async getLastOperationOfLine(iccid: string) {
|
||||||
|
const query = `
|
||||||
|
SELECT * FROM public.objenious_operation
|
||||||
|
WHERE iccids = $1 and error is null
|
||||||
|
ORDER BY id asc limit 1
|
||||||
|
`
|
||||||
|
const values = [iccid];
|
||||||
|
const { rows } = await this.pgClient.query(query, values);
|
||||||
|
return <Result<string, ObjeniousOperation>>{
|
||||||
|
data: rows[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async updateOperation(data: ObjeniousOperationChange): Promise<Result<string, ObjeniousOperation>> {
|
async updateOperation(data: ObjeniousOperationChange): Promise<Result<string, ObjeniousOperation>> {
|
||||||
const client = await this.pgClient.connect();
|
const client = await this.pgClient.connect();
|
||||||
const {
|
const {
|
||||||
@@ -46,7 +185,7 @@ export class ObjeniousOperationsRepository implements IOperationsRepository {
|
|||||||
request_id = COALESCE($4, request_id),
|
request_id = COALESCE($4, request_id),
|
||||||
mass_action_id = COALESCE($5, mass_action_id),
|
mass_action_id = COALESCE($5, mass_action_id),
|
||||||
last_change_date = now() at time zone 'utc',
|
last_change_date = now() at time zone 'utc',
|
||||||
end_date = CASE WHEN $2 IN ('finished') THEN now() at time zone 'utc' ELSE end_date END,
|
end_date = CASE WHEN $2 IN ('finished','error') THEN now() at time zone 'utc' ELSE end_date END,
|
||||||
objenious_status = $6
|
objenious_status = $6
|
||||||
WHERE id = $1`;
|
WHERE id = $1`;
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ describe("Test OrderRepository", {}, (ctx) => {
|
|||||||
before(async () => {
|
before(async () => {
|
||||||
// Order1
|
// Order1
|
||||||
const result1 = await orderRepo.createOrder(order1)
|
const result1 = await orderRepo.createOrder(order1)
|
||||||
assert(result1.data != undefined)
|
assert.ok(result1.data != undefined, result1.error as string)
|
||||||
testIds.push(result1.data.id)
|
testIds.push(result1.data.id)
|
||||||
|
|
||||||
// Order2 -> Para el test de crearOrder
|
// Order2 -> Para el test de crearOrder
|
||||||
|
|||||||
@@ -3,10 +3,9 @@
|
|||||||
*/
|
*/
|
||||||
import { PoolClient, QueryResult, QueryResultRow } from "pg";
|
import { PoolClient, QueryResult, QueryResultRow } from "pg";
|
||||||
import { CreateOrderDTO, ErrorOrderDTO, FinishOrderDTO, OrderTracking, UpdateOrderDTO } from "../domain/Order.js";
|
import { CreateOrderDTO, ErrorOrderDTO, FinishOrderDTO, OrderTracking, UpdateOrderDTO } from "../domain/Order.js";
|
||||||
import { Result } from "../domain/Result.js";
|
import { Result, tryCatch } from "../domain/Result.js";
|
||||||
import { PgClient } from "./PgClient.js";
|
import { PgClient } from "./PgClient.js";
|
||||||
import assert from "node:assert";
|
import assert from "node:assert";
|
||||||
import { error } from "node:console";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Agrupa todas las operaciones de *Order*.
|
* Agrupa todas las operaciones de *Order*.
|
||||||
@@ -19,9 +18,8 @@ import { error } from "node:console";
|
|||||||
*/
|
*/
|
||||||
export class OrderRepository {
|
export class OrderRepository {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly pgClient: PgClient
|
private readonly pgClient: PgClient,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -57,6 +55,8 @@ export class OrderRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* El tipo <T> representa el contenido del mensaje de los order
|
* El tipo <T> representa el contenido del mensaje de los order
|
||||||
*/
|
*/
|
||||||
@@ -191,6 +191,8 @@ export class OrderRepository {
|
|||||||
const orderId = currentOrderResult.data?.id
|
const orderId = currentOrderResult.data?.id
|
||||||
|
|
||||||
if (orderId == undefined) {
|
if (orderId == undefined) {
|
||||||
|
await client.query("ROLLBACK")
|
||||||
|
client.release()
|
||||||
return {
|
return {
|
||||||
error: "El order a actualizar no existe " + idType + ": " + idValue
|
error: "El order a actualizar no existe " + idType + ": " + idValue
|
||||||
}
|
}
|
||||||
@@ -261,7 +263,6 @@ export class OrderRepository {
|
|||||||
return updatedOrder
|
return updatedOrder
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public async finishOrder(args: FinishOrderDTO) {
|
public async finishOrder(args: FinishOrderDTO) {
|
||||||
const client = await this.pgClient.connect();
|
const client = await this.pgClient.connect();
|
||||||
assert((args.id != undefined) != (args.correlation_id != undefined))
|
assert((args.id != undefined) != (args.correlation_id != undefined))
|
||||||
@@ -281,6 +282,8 @@ export class OrderRepository {
|
|||||||
const orderId = currentOrderResult.data?.id
|
const orderId = currentOrderResult.data?.id
|
||||||
|
|
||||||
if (orderId == undefined) {
|
if (orderId == undefined) {
|
||||||
|
await client.query("ROLLBACK")
|
||||||
|
client.release()
|
||||||
return {
|
return {
|
||||||
error: "El order a actualizar no existe " + idType + ": " + idValue
|
error: "El order a actualizar no existe " + idType + ": " + idValue
|
||||||
}
|
}
|
||||||
@@ -299,8 +302,8 @@ export class OrderRepository {
|
|||||||
UPDATE order_tracking
|
UPDATE order_tracking
|
||||||
SET
|
SET
|
||||||
status = 'finished',
|
status = 'finished',
|
||||||
update_date = (now() at time zone 'utc'),
|
update_date = now(),
|
||||||
finish_date = (now() at time zone 'utc')
|
finish_date = now()
|
||||||
WHERE id = $1
|
WHERE id = $1
|
||||||
RETURNING id, status, update_date;
|
RETURNING id, status, update_date;
|
||||||
`
|
`
|
||||||
@@ -377,6 +380,7 @@ export class OrderRepository {
|
|||||||
|
|
||||||
const id = currentOrderResult.data.id // Saco el id para evitar busacr por correlation_id que es mas lento
|
const id = currentOrderResult.data.id // Saco el id para evitar busacr por correlation_id que es mas lento
|
||||||
const currentOrder = currentOrderResult.data!
|
const currentOrder = currentOrderResult.data!
|
||||||
|
//console.log("Current Order", currentOrder)
|
||||||
|
|
||||||
// 3. Si todo ok se actualiza el order
|
// 3. Si todo ok se actualiza el order
|
||||||
// Si el status es dlx se asume que ha terminado y no va a reintentarse
|
// Si el status es dlx se asume que ha terminado y no va a reintentarse
|
||||||
@@ -398,6 +402,8 @@ export class OrderRepository {
|
|||||||
client.query<{ id: number, status: string, update_date: string }>(uOrderTracking, vOrderTracking)
|
client.query<{ id: number, status: string, update_date: string }>(uOrderTracking, vOrderTracking)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
console.log("updatedOrderResult", updatedOrderResult)
|
||||||
|
|
||||||
if (updatedOrderResult.error != undefined) {
|
if (updatedOrderResult.error != undefined) {
|
||||||
await client.query("ROLLBACK")
|
await client.query("ROLLBACK")
|
||||||
client.release()
|
client.release()
|
||||||
@@ -420,7 +426,7 @@ export class OrderRepository {
|
|||||||
)
|
)
|
||||||
RETURNING id;
|
RETURNING id;
|
||||||
`
|
`
|
||||||
const vOrderHistory = [args.id, currentOrder.status, args.status, args.reason]
|
const vOrderHistory = [currentOrder.id, currentOrder.status, args.status, args.reason]
|
||||||
const newOrderHistoryResult = await this.getFirst(
|
const newOrderHistoryResult = await this.getFirst(
|
||||||
client.query<{ id: number }>(iOrderHistory, vOrderHistory)
|
client.query<{ id: number }>(iOrderHistory, vOrderHistory)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -21,15 +21,22 @@ export class RabbitMQEventBus implements EventBus {
|
|||||||
channel?: ChannelWrapper
|
channel?: ChannelWrapper
|
||||||
connected: Boolean = false
|
connected: Boolean = false
|
||||||
|
|
||||||
|
private delayedExchange: string;
|
||||||
|
private dlxExchange: string;
|
||||||
|
|
||||||
private connectionOptions: RMQConnectionParams
|
private connectionOptions: RMQConnectionParams
|
||||||
constructor(args: {
|
constructor(args: {
|
||||||
connectionParams: RMQConnectionParams,
|
connectionParams: RMQConnectionParams,
|
||||||
buildStructure?: (chan: Channel) => Promise<void>,
|
buildStructure?: (chan: Channel) => Promise<void>,
|
||||||
maxRetry?: number
|
maxRetry?: number,
|
||||||
|
delayedExchange: string,
|
||||||
|
dlxExchange: string
|
||||||
}) {
|
}) {
|
||||||
this.connectionOptions = args.connectionParams
|
this.connectionOptions = args.connectionParams
|
||||||
if (args.buildStructure != undefined) this.buildStructure = args.buildStructure
|
if (args.buildStructure != undefined) this.buildStructure = args.buildStructure
|
||||||
if (args.maxRetry != undefined) this.maxRetry = args.maxRetry
|
if (args.maxRetry != undefined) this.maxRetry = args.maxRetry
|
||||||
|
this.delayedExchange = args.delayedExchange
|
||||||
|
this.dlxExchange = args.dlxExchange
|
||||||
}
|
}
|
||||||
|
|
||||||
async consume(queue: string, callback: (msg: ConsumeMessage | null) => void) {
|
async consume(queue: string, callback: (msg: ConsumeMessage | null) => void) {
|
||||||
@@ -50,23 +57,25 @@ export class RabbitMQEventBus implements EventBus {
|
|||||||
async nack(msg: ConsumeMessage, requeue?: boolean) {
|
async nack(msg: ConsumeMessage, requeue?: boolean) {
|
||||||
if (this.channel == undefined) throw new Error("[RMQ] Canal no iniciallizado");
|
if (this.channel == undefined) throw new Error("[RMQ] Canal no iniciallizado");
|
||||||
|
|
||||||
console.log("NACK: ", msg.properties.headers)
|
console.log("[i] NACK: ", msg.properties.headers)
|
||||||
|
|
||||||
const headers = msg.properties.headers || {}
|
const headers = msg.properties.headers || {}
|
||||||
const numberRetry = headers['x-retry-count'] || 0
|
const numberRetry = headers['x-retry-count'] || 0
|
||||||
const routingKey = msg.fields.routingKey
|
const routingKey = msg.fields.routingKey
|
||||||
|
|
||||||
if (numberRetry < this.maxRetry) {
|
if (numberRetry < this.maxRetry) {
|
||||||
console.log("Delaying")
|
console.log("[i] Delaying ")
|
||||||
await this.channel.publish("sim.ex.objenious.delayed", routingKey, msg.content, {
|
// "sim.ex.objenious.delayed"
|
||||||
|
await this.channel.publish(this.delayedExchange, routingKey, msg.content, {
|
||||||
headers: {
|
headers: {
|
||||||
...headers,
|
...headers,
|
||||||
'x-retry-count': numberRetry + 1
|
'x-retry-count': numberRetry + 1
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
console.log("DeadLetter")
|
console.log("[i] DeadLetter")
|
||||||
await this.channel.publish("sim.ex.objenious.dlx", routingKey, msg.content, {
|
//"sim.ex.objenious.dlx"
|
||||||
|
await this.channel.publish(this.dlxExchange, routingKey, msg.content, {
|
||||||
headers: {
|
headers: {
|
||||||
...headers
|
...headers
|
||||||
}
|
}
|
||||||
|
|||||||
14
packages/sim-shared/test.env
Normal file
14
packages/sim-shared/test.env
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
## ENV PARA DATOS DE TEST - shared nunca se lanza en produccion
|
||||||
|
|
||||||
|
# claves de Objenious
|
||||||
|
OBJ_PEM_PATH=./obj.pem
|
||||||
|
OBJ_AUTHORIZATION=XOc7FtwXD8hUX2SFVX94XSty8wkOmChkwDNF09O_aIxPubMDdFUdCDCB4zpzSIxi8nOcTg7r_LM_nmd5qm7uLbksf_XArjI8iAyhjKz_2BAXPhmvKs4Fc9f3vv5LDfCVrPB9lP8P7rJ66_qnWs4jvhLQxSfn29m96hgXeCf8oySdIDUjN2q9Js3KAS5LL52Ri6ryvUeO1PvMhaPQMWRqoHIqTV1wPfPtiqQwcjUPmu5GeW164Kq1JLgV3KaGzfCZ9Qv9lbv30EJrukXxWuLCAhBS0kzrBXZoWvf2pb9uh3Am_93_dDxiIGQfIap9ZU_m8ZD1HPgvZOMCY6ZkxQconQ
|
||||||
|
OBJ_CLI_ASSERTION=XOc7FtwXD8hUX2SFVX94XSty8wkOmChkwDNF09O_aIxPubMDdFUdCDCB4zpzSIxi8nOcTg7r_LM_nmd5qm7uLbksf_XArjI8iAyhjKz_2BAXPhmvKs4Fc9f3vv5LDfCVrPB9lP8P7rJ66_qnWs4jvhLQxSfn29m96hgXeCf8oySdIDUjN2q9Js3KAS5LL52Ri6ryvUeO1PvMhaPQMWRqoHIqTV1wPfPtiqQwcjUPmu5GeW164Kq1JLgV3KaGzfCZ9Qv9lbv30EJrukXxWuLCAhBS0kzrBXZoWvf2pb9uh3Am_93_dDxiIGQfIap9ZU_m8ZD1HPgvZOMCY6ZkxQconQ
|
||||||
|
OBJ_CLIENT_ID=savefamily_rest_ws
|
||||||
|
OBJ_KID=xNfbMiyL1ORXGP8lElhcv8nVaG3EJKye4Lc1YoN3I1E
|
||||||
|
OBJ_BASE_URL=https://api-getway.objenious.com/ws
|
||||||
|
# OBJ_BASE_URL=https://api-getway.objenious.com/ws/test
|
||||||
|
|
||||||
|
NOTIFICATION_URL="https://sf-sim-activation.savefamilygps.net/send-activation-mail"
|
||||||
|
# NOTIFICATION_URL="localhost"
|
||||||
|
SIM_ACTIVATION_API_KEY=9e48c4ac-1ab0-4397-b3f3-6c239200dfe6
|
||||||
@@ -9,6 +9,6 @@
|
|||||||
],
|
],
|
||||||
"include": [
|
"include": [
|
||||||
"**/*.ts",
|
"**/*.ts",
|
||||||
"../../packages/sim-shared/**/*.ts",
|
"**/*.d.ts",
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"outDir": "dist",
|
"outDir": "dist",
|
||||||
"rootDir": "./",
|
"rootDir": "./",
|
||||||
"baseUrl": "./",
|
//"baseUrl": "./", //eliminar para v6
|
||||||
"composite": true,
|
"composite": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
@@ -12,6 +12,11 @@
|
|||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"module": "nodenext",
|
"module": "nodenext",
|
||||||
"moduleResolution": "nodenext",
|
"moduleResolution": "nodenext",
|
||||||
|
"paths": {
|
||||||
|
"sim-consumidor-objenious": [
|
||||||
|
"./packages/sim-consumidor-objenious/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"**/*.ts",
|
"**/*.ts",
|
||||||
@@ -20,5 +25,5 @@
|
|||||||
],
|
],
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"dist"
|
"dist"
|
||||||
]
|
],
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user