Compare commits
48 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 70bf73b0a4 | |||
| e3849d8217 | |||
| d9854a12a8 | |||
| 48d387a8da | |||
| 93d3e13793 | |||
| 031f5d5cf0 | |||
| 047669bab2 | |||
| 5ea5939e3a | |||
| 7ff3f13af4 | |||
| a9589f578b | |||
| a27e4b30d2 | |||
| 4168949b9e | |||
| e6ff54a15d | |||
| 3956797020 | |||
| 7d88359263 | |||
| 1b6da651a6 | |||
| 9b305f887f | |||
| 9506b9e28e | |||
| 61c0edca07 | |||
| 9470b5605d | |||
| 9d63d23754 | |||
| a95655a2a6 | |||
| 025801a689 | |||
| 28880c4d99 | |||
| 5bb3bc554b | |||
| cfb907b840 | |||
| d5d7953fd2 | |||
| 96298aab25 | |||
| c17cca1e81 | |||
| 7264efcf79 | |||
| 8934bcd603 | |||
| bdd08dbc56 | |||
| 7d47fde806 | |||
| ad207fb732 | |||
| bd9081b5bc | |||
| a429e9d14a | |||
| 81eb986313 | |||
| 58bedc42f1 | |||
| b97f422261 | |||
| 7a7dc33724 | |||
| 7743bd1f0d | |||
| 2897d7aa3c | |||
| 0fd7eafcf3 | |||
| 71253d216e | |||
| aeea6cfefd | |||
| e8eb925834 | |||
| 7cf9cc60e6 | |||
| 1e9818d430 |
2
.env
2
.env
@@ -20,7 +20,7 @@ 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
|
||||||
|
|||||||
@@ -3,3 +3,9 @@ compressionLevel: mixed
|
|||||||
enableGlobalCache: false
|
enableGlobalCache: false
|
||||||
|
|
||||||
nodeLinker: node-modules
|
nodeLinker: node-modules
|
||||||
|
|
||||||
|
npmScopes:
|
||||||
|
sf-alvar:
|
||||||
|
npmRegistryServer: "https://git.savefamilygps.net/api/packages/SaveFamily/npm/"
|
||||||
|
|
||||||
|
npmRegistryServer: "https://registry.npmjs.org/"
|
||||||
|
|||||||
@@ -1,10 +1,3 @@
|
|||||||
#/bin/bash
|
#/bin/bash
|
||||||
rm deployment/database/init.sql
|
|
||||||
# cat deployment/database/*.sql >deployment/database/init.sql
|
|
||||||
cp deployment/database/esquema_final* deployment/database/init.sql
|
|
||||||
|
|
||||||
# compatibilidad con postgresql < 17
|
|
||||||
sed -i '/\\restrict/d' deployment/database/init.sql
|
|
||||||
sed -i '/\\unrestrict/d' deployment/database/init.sql
|
|
||||||
|
|
||||||
docker compose -f deployment/local/docker/docker-compose.yaml --project-directory ./ build
|
docker compose -f deployment/local/docker/docker-compose.yaml --project-directory ./ build
|
||||||
|
|||||||
20
deployment/database/base/xx-volcado-objenious.sql
Normal file
20
deployment/database/base/xx-volcado-objenious.sql
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
CREATE table if not exists objenious_lines (
|
||||||
|
id INT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
|
||||||
|
simId BIGINT UNIQUE,
|
||||||
|
status TEXT,
|
||||||
|
iccid TEXT NOT NULL,
|
||||||
|
msisdn TEXT,
|
||||||
|
imei TEXT,
|
||||||
|
imeiChangeDate TIMESTAMPTZ,
|
||||||
|
offerCode TEXT,
|
||||||
|
preactivationDate TIMESTAMPTZ, -- No viene con hora
|
||||||
|
activationDate TIMESTAMPTZ,
|
||||||
|
commercialStatus TEXT,
|
||||||
|
commercialStatusDate TIMESTAMPTZ,
|
||||||
|
billingStatus TEXT,
|
||||||
|
billingStatusChangeDate TIMESTAMPTZ,
|
||||||
|
billingActivationDate TIMESTAMPTZ,
|
||||||
|
createDate TIMESTAMPTZ,
|
||||||
|
raw JSONB,
|
||||||
|
hash TEXT
|
||||||
|
)
|
||||||
@@ -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;
|
||||||
|
|
||||||
|
|
||||||
@@ -6,12 +6,13 @@ WORKDIR /home/node/app
|
|||||||
RUN corepack enable
|
RUN corepack enable
|
||||||
|
|
||||||
COPY ./dist/packages ./packages
|
COPY ./dist/packages ./packages
|
||||||
|
COPY ./.yarnrc.yml ./
|
||||||
|
COPY ./docs ./docs
|
||||||
|
# Para las migraciones
|
||||||
|
COPY ./deployment ./deployment
|
||||||
|
|
||||||
COPY ./package.json ./
|
COPY ./package.json ./
|
||||||
|
|
||||||
# Force node-modules linker (no .yarnrc.yml in build context)
|
|
||||||
RUN echo 'nodeLinker: node-modules' > .yarnrc.yml
|
|
||||||
|
|
||||||
RUN yarn install
|
RUN yarn install
|
||||||
|
|
||||||
RUN mkdir -p dist && ln -sf ../packages dist/packages
|
RUN mkdir -p dist && ln -sf ../packages dist/packages
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -17,12 +17,12 @@ pipeline {
|
|||||||
sh 'npm install -g yarn'
|
sh 'npm install -g yarn'
|
||||||
sh 'corepack enable'
|
sh 'corepack enable'
|
||||||
sh 'corepack prepare yarn@4.12.0 --activate'
|
sh 'corepack prepare yarn@4.12.0 --activate'
|
||||||
sh 'yarn install'
|
sh 'yarn install --immutable'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
stage("🧱 Building") {
|
stage("🧱 Building") {
|
||||||
steps {
|
steps {
|
||||||
sh 'rm -rf dist/'
|
sh 'rm -rf dist/'
|
||||||
sh 'yarn run build'
|
sh 'yarn run build'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -60,11 +60,15 @@ pipeline {
|
|||||||
sourceFiles: "dist/**/*",
|
sourceFiles: "dist/**/*",
|
||||||
excludes: "dist/**/node_modules/**"
|
excludes: "dist/**/node_modules/**"
|
||||||
),
|
),
|
||||||
|
sshTransfer(
|
||||||
|
cleanRemote: false,
|
||||||
|
remoteDirectory: "$APP_REMOTE_PATH",
|
||||||
|
sourceFiles: "docs/**/*",
|
||||||
|
),
|
||||||
sshTransfer(
|
sshTransfer(
|
||||||
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,
|
||||||
@@ -88,6 +92,11 @@ pipeline {
|
|||||||
remoteDirectory: "$APP_REMOTE_PATH",
|
remoteDirectory: "$APP_REMOTE_PATH",
|
||||||
sourceFiles: "package.json",
|
sourceFiles: "package.json",
|
||||||
),
|
),
|
||||||
|
sshTransfer(
|
||||||
|
cleanRemote: false,
|
||||||
|
remoteDirectory: "$APP_REMOTE_PATH",
|
||||||
|
sourceFiles: ".yarnrc.yml",
|
||||||
|
),
|
||||||
sshTransfer(
|
sshTransfer(
|
||||||
cleanRemote: false,
|
cleanRemote: false,
|
||||||
execCommand: "sh $APP_REMOTE_PATH/rebuild.sh"
|
execCommand: "sh $APP_REMOTE_PATH/rebuild.sh"
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ COPY ./packages ./packages
|
|||||||
COPY tsconfig*.json ./
|
COPY tsconfig*.json ./
|
||||||
COPY .env* ./
|
COPY .env* ./
|
||||||
COPY ./.yarnrc.yml ./
|
COPY ./.yarnrc.yml ./
|
||||||
|
COPY ./docs ./docs
|
||||||
COPY ./deployment/local/docker/start.sh ./
|
COPY ./deployment/local/docker/start.sh ./
|
||||||
# Copiar el archivo de migrations? porque ahora no creo que se esté lanzando nada
|
# Copiar el archivo de migrations? porque ahora no creo que se esté lanzando nada
|
||||||
COPY ./deployment/database/migrations ./deployment/database/migrations
|
COPY ./deployment/database/migrations ./deployment/database/migrations
|
||||||
|
|||||||
@@ -40,6 +40,9 @@ services:
|
|||||||
- path: ./packages
|
- path: ./packages
|
||||||
action: sync
|
action: sync
|
||||||
target: /usr/local/app/packages
|
target: /usr/local/app/packages
|
||||||
|
- path: ./docs
|
||||||
|
action: sync
|
||||||
|
target: /usr/local/app/docs
|
||||||
- path: ./package.json
|
- path: ./package.json
|
||||||
action: rebuild
|
action: rebuild
|
||||||
ports:
|
ports:
|
||||||
@@ -72,7 +75,6 @@ services:
|
|||||||
- "${POSTGRES_PORT}:${POSTGRES_PORT}"
|
- "${POSTGRES_PORT}:${POSTGRES_PORT}"
|
||||||
volumes:
|
volumes:
|
||||||
- ./sql-data/:/var/lib/postgres/data
|
- ./sql-data/:/var/lib/postgres/data
|
||||||
- ./deployment/database/init.sql:/docker-entrypoint-initdb.d/init.sql
|
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
|
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
|
||||||
interval: 5s
|
interval: 5s
|
||||||
|
|||||||
25
docs/sim-api-documentation.html
Normal file
25
docs/sim-api-documentation.html
Normal file
File diff suppressed because one or more lines are too long
@@ -11,7 +11,7 @@ post {
|
|||||||
}
|
}
|
||||||
|
|
||||||
body:form-urlencoded {
|
body:form-urlencoded {
|
||||||
iccid: 8933201125068886692
|
iccid: 8933201125068890694
|
||||||
offer: SAVEFAMILY1
|
offer: SAVEFAMILY1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
16
docs/sim-api/Docs.bru
Normal file
16
docs/sim-api/Docs.bru
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
meta {
|
||||||
|
name: Docs
|
||||||
|
type: http
|
||||||
|
seq: 12
|
||||||
|
}
|
||||||
|
|
||||||
|
get {
|
||||||
|
url: {{baseurl}}/docs/sim-api-documentation.html
|
||||||
|
body: none
|
||||||
|
auth: inherit
|
||||||
|
}
|
||||||
|
|
||||||
|
settings {
|
||||||
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
|
}
|
||||||
@@ -15,7 +15,7 @@ params:query {
|
|||||||
}
|
}
|
||||||
|
|
||||||
body:form-urlencoded {
|
body:form-urlencoded {
|
||||||
iccid: 8933201125065160414
|
iccid: 8933201125068886692
|
||||||
}
|
}
|
||||||
|
|
||||||
settings {
|
settings {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -5,16 +5,16 @@ meta {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get {
|
get {
|
||||||
url: https://api-getway.objenious.com/ws/lines?pageSize=10&identifier.identifierType=ICCID&identifier.identifiers=8933201125065160455
|
url: https://api-getway.objenious.com/ws/lines?pageSize=1000&simStatus=ACTIVATED
|
||||||
body: formUrlEncoded
|
body: formUrlEncoded
|
||||||
auth: bearer
|
auth: bearer
|
||||||
}
|
}
|
||||||
|
|
||||||
params:query {
|
params:query {
|
||||||
pageSize: 10
|
pageSize: 1000
|
||||||
identifier.identifierType: ICCID
|
simStatus: ACTIVATED
|
||||||
identifier.identifiers: 8933201125065160455
|
~identifier.identifierType: ICCID
|
||||||
~simStatus: ACTIVATED
|
~identifier.identifiers: 8933201125065160455
|
||||||
}
|
}
|
||||||
|
|
||||||
auth:bearer {
|
auth:bearer {
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ body:form-urlencoded {
|
|||||||
}
|
}
|
||||||
|
|
||||||
vars:pre-request {
|
vars:pre-request {
|
||||||
params.id: 14557
|
params.id: 15102
|
||||||
}
|
}
|
||||||
|
|
||||||
settings {
|
settings {
|
||||||
|
|||||||
@@ -19,12 +19,12 @@
|
|||||||
"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.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",
|
||||||
"axios": "^1.13.3",
|
"axios": "^1.13.3",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"db-migrate": "http://gitea:3000/alvarsanmartin/herramienta-migracion.git",
|
|
||||||
"dotenv": "^17.2.3",
|
"dotenv": "^17.2.3",
|
||||||
"express": "^5.2.1",
|
"express": "^5.2.1",
|
||||||
"pg": "^8.18.0",
|
"pg": "^8.18.0",
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -37,6 +37,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;
|
||||||
}
|
}
|
||||||
@@ -86,7 +87,7 @@ export class SimController {
|
|||||||
const resp = await this.tryUseCase(msg, this.useCases.activate({
|
const resp = await this.tryUseCase(msg, this.useCases.activate({
|
||||||
correlation_id: msgData.headers?.message_id,
|
correlation_id: msgData.headers?.message_id,
|
||||||
dueDate: this.genDueDate(DUE_DATE_SECONDS).toISOString(),
|
dueDate: this.genDueDate(DUE_DATE_SECONDS).toISOString(),
|
||||||
customerAccountCode: env.OBJ_CUSTOMER_CODE,
|
customerAccountCode: "9.49411.10",
|
||||||
identifier: {
|
identifier: {
|
||||||
identifierType: "ICCID",
|
identifierType: "ICCID",
|
||||||
identifiers: [iccid]
|
identifiers: [iccid]
|
||||||
@@ -157,6 +158,9 @@ export class SimController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lo mismo que pause
|
||||||
|
*/
|
||||||
public suspend() {
|
public suspend() {
|
||||||
return async (msg: ConsumeMessage) => {
|
return async (msg: ConsumeMessage) => {
|
||||||
let msgData;
|
let msgData;
|
||||||
@@ -171,14 +175,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))
|
||||||
|
*/
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -195,16 +203,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))
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -73,6 +78,8 @@ export class SimUseCases {
|
|||||||
request_id: response.data.requestId
|
request_id: response.data.requestId
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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(e))
|
||||||
|
|
||||||
@@ -89,7 +96,6 @@ export class SimUseCases {
|
|||||||
error: undefined,
|
error: undefined,
|
||||||
data: true
|
data: true
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
error: String(response.status),
|
error: String(response.status),
|
||||||
@@ -110,7 +116,10 @@ export class SimUseCases {
|
|||||||
const OPERATION_URL = "/actions/activateLine"
|
const OPERATION_URL = "/actions/activateLine"
|
||||||
return async () => {
|
return async () => {
|
||||||
const req = this.httpClient.client.post(OPERATION_URL, {
|
const req = this.httpClient.client.post(OPERATION_URL, {
|
||||||
...activationData
|
dueDate: activationData.dueDate,
|
||||||
|
identifier: activationData.identifier,
|
||||||
|
customerAccountCode: activationData.customerAccountCode,
|
||||||
|
offer: activationData.offer
|
||||||
})
|
})
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -225,18 +234,151 @@ export class SimUseCases {
|
|||||||
const OPERATION_URL = "/actions/suspendLine"
|
const OPERATION_URL = "/actions/suspendLine"
|
||||||
return this.generateUseCase({
|
return this.generateUseCase({
|
||||||
correlation_id: suspendData.correlation_id,
|
correlation_id: suspendData.correlation_id,
|
||||||
operationPayload: suspendData,
|
operationPayload: {
|
||||||
|
dueDate: suspendData.dueDate,
|
||||||
|
identifier: suspendData.identifier
|
||||||
|
},
|
||||||
url: OPERATION_URL,
|
url: OPERATION_URL,
|
||||||
iccid: suspendData.identifier.identifiers[0], //
|
iccid: suspendData.identifier.identifiers[0], //
|
||||||
operation: "suspend"
|
operation: "suspend"
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 fail = (error: string) => {
|
||||||
|
console.error("[Sim.usecases]", error)
|
||||||
|
if (correlation_id != undefined) {
|
||||||
|
this.orderRepository.updateOrder({
|
||||||
|
correlation_id: correlation_id,
|
||||||
|
new_status: "failed"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 activationDate = await this.findActivationDate(terminateData)
|
||||||
|
const newTask: CreatePauseCancelTaskDTO = {
|
||||||
|
iccid: terminateData.identifier.identifiers[0],
|
||||||
|
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)
|
||||||
|
|
||||||
|
// 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({
|
||||||
correlation_id: terminationData.correlation_id,
|
correlation_id: terminationData.correlation_id,
|
||||||
operationPayload: terminationData,
|
operationPayload: {
|
||||||
|
dueDate: terminationData.dueDate,
|
||||||
|
identifier: terminationData.identifier
|
||||||
|
},
|
||||||
url: OPERATION_URL,
|
url: OPERATION_URL,
|
||||||
iccid: terminationData.identifier.identifiers[0], //
|
iccid: terminationData.identifier.identifiers[0], //
|
||||||
operation: "terminate"
|
operation: "terminate"
|
||||||
|
|||||||
@@ -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,126 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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",
|
||||||
|
|||||||
@@ -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)
|
||||||
@@ -79,7 +78,7 @@ export class SimController {
|
|||||||
...usecaseResult.error
|
...usecaseResult.error
|
||||||
}
|
}
|
||||||
}).send()
|
}).send()
|
||||||
args.onError(body, usecaseResult.error.msg.message)
|
args.onError(body, usecaseResult.error ?? "Error indefinido")
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -198,7 +198,7 @@ export class SimUsecases {
|
|||||||
* alias de bloquear / suspender en objenious
|
* alias de bloquear / suspender en objenious
|
||||||
*/
|
*/
|
||||||
async pause(args: { iccid: string, compañia: string }):
|
async pause(args: { iccid: string, compañia: string }):
|
||||||
Promise<Result<string, { iccid: string, message_id: string, operation: "cancelation" }>> {
|
Promise<Result<string, { iccid: string, message_id: string, operation: "pause" }>> {
|
||||||
const pauseEvent = <SimEvents.pause>{
|
const pauseEvent = <SimEvents.pause>{
|
||||||
key: `sim.${args.compañia}.pause`,
|
key: `sim.${args.compañia}.pause`,
|
||||||
payload: {
|
payload: {
|
||||||
@@ -222,7 +222,7 @@ export class SimUsecases {
|
|||||||
data: {
|
data: {
|
||||||
iccid: args.iccid,
|
iccid: args.iccid,
|
||||||
message_id: savedOrder.data.correlation_id,
|
message_id: savedOrder.data.correlation_id,
|
||||||
operation: "cancelation"
|
operation: "pause"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import express from "express"
|
import express from "express"
|
||||||
import cors from 'cors';
|
import cors from 'cors';
|
||||||
|
import path from 'path';
|
||||||
import { simRoutes } from "./infrastructure/simRoutes.http.js"
|
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"
|
||||||
@@ -27,6 +28,8 @@ app.use(express.urlencoded({ extended: true }));
|
|||||||
app.use("/sim", simRoutes)
|
app.use("/sim", simRoutes)
|
||||||
app.use("/orders", orderRoutes)
|
app.use("/orders", orderRoutes)
|
||||||
|
|
||||||
|
app.use("/docs", express.static(path.join(process.cwd(), '../../docs')))
|
||||||
|
|
||||||
app.get("/health", (req, res) => {
|
app.get("/health", (req, res) => {
|
||||||
res.status(200).json({ status: "ok" })
|
res.status(200).json({ status: "ok" })
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -7,6 +7,6 @@ OBJ_KID=xNfbMiyL1ORXGP8lElhcv8nVaG3EJKye4Lc1YoN3I1E
|
|||||||
OBJ_BASE_URL=https://api-getway.objenious.com/ws
|
OBJ_BASE_URL=https://api-getway.objenious.com/ws
|
||||||
# OBJ_BASE_URL=https://api-getway.objenious.com/ws/test
|
# OBJ_BASE_URL=https://api-getway.objenious.com/ws/test
|
||||||
|
|
||||||
# NOTIFICATION_URL="https://sf-sim-activation.savefamilygps.net/send-activation-mail"
|
NOTIFICATION_URL="https://sf-sim-activation.savefamilygps.net/send-activation-mail"
|
||||||
NOTIFICATION_URL="localhost"
|
# NOTIFICATION_URL="localhost"
|
||||||
SIM_ACTIVATION_API_KEY=9e48c4ac-1ab0-4397-b3f3-6c239200dfe6
|
SIM_ACTIVATION_API_KEY=9e48c4ac-1ab0-4397-b3f3-6c239200dfe6
|
||||||
|
|||||||
10
packages/sim-objenious-cron/config/env/index.ts
vendored
10
packages/sim-objenious-cron/config/env/index.ts
vendored
@@ -31,15 +31,15 @@ export const env = {
|
|||||||
OBJ_KID: String(process.env.OBJ_KID),
|
OBJ_KID: String(process.env.OBJ_KID),
|
||||||
OBJ_BASE_URL: String(process.env.OBJ_BASE_URL),
|
OBJ_BASE_URL: String(process.env.OBJ_BASE_URL),
|
||||||
|
|
||||||
NOTIFICATION_URL: String(process.env.NOTIFICATION_URL),
|
NOTIFICATION_URL: String(process.env.NOTIFICATION_URL ?? ""),
|
||||||
SIM_ACTIVATION_API_KEY: String(process.env.SIM_ACTIVATION_API_KEY)
|
SIM_ACTIVATION_API_KEY: String(process.env.SIM_ACTIVATION_API_KEY ?? "")
|
||||||
};
|
};
|
||||||
|
|
||||||
// assert las partes criticas
|
// assert las partes criticas
|
||||||
assert(env.RABBITMQ_PASSWORD != undefined)
|
assert(env.RABBITMQ_PASSWORD != undefined)
|
||||||
assert(env.RABBITMQ_USER != undefined)
|
assert(env.RABBITMQ_USER != undefined)
|
||||||
assert(env.SIM_ACTIVATION_API_KEY != undefined)
|
assert(env.SIM_ACTIVATION_API_KEY != "")
|
||||||
assert(env.NOTIFICATION_URL != undefined)
|
assert(env.NOTIFICATION_URL != "")
|
||||||
|
|
||||||
if (env.ENVIRONMENT == "production") {
|
if (env.ENVIRONMENT == "production") {
|
||||||
assert(env.RABBITMQ_PASSWORD != "guest")
|
assert(env.RABBITMQ_PASSWORD != "guest")
|
||||||
@@ -47,3 +47,5 @@ if (env.ENVIRONMENT == "production") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
console.log("[i] verificado env")
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
})
|
})
|
||||||
|
|||||||
20
packages/sim-objenious-cron/config/intranetPostgresConfig.ts
Normal file
20
packages/sim-objenious-cron/config/intranetPostgresConfig.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
/**
|
||||||
|
* Cliente de postgres para la intranet. Se usa solo porque hace falta para el
|
||||||
|
* volcado de datos, si se usa en mas partes algo estás haciendo mal.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Pool } from 'pg';
|
||||||
|
import { PgClient } from 'sim-shared/infrastructure/PgClient.js'
|
||||||
|
import { env } from './env/index.js';
|
||||||
|
|
||||||
|
export const pgPoolIntranet = new Pool({
|
||||||
|
user: env.POSTGRES_USER,
|
||||||
|
host: env.POSTGRES_HOST,
|
||||||
|
database: "intranet",
|
||||||
|
password: env.POSTGRES_PASSWORD,
|
||||||
|
port: Number(env.POSTGRES_PORT) || 5432,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const postgresClientIntranet = new PgClient({
|
||||||
|
pool: pgPoolIntranet
|
||||||
|
})
|
||||||
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
|
||||||
|
})
|
||||||
@@ -5,6 +5,12 @@ import { httpInstance } from "./config/httpClient.config.js"
|
|||||||
import { CheckObjeniousRequests } from "./tasks/check_objenious_request.js"
|
import { CheckObjeniousRequests } from "./tasks/check_objenious_request.js"
|
||||||
import { ObjeniousOperationsRepository } from "sim-shared/infrastructure/ObjeniousOperationRepository.js"
|
import { ObjeniousOperationsRepository } from "sim-shared/infrastructure/ObjeniousOperationRepository.js"
|
||||||
import { OrderRepository } from "sim-shared/infrastructure/OrderRepository.js"
|
import { OrderRepository } from "sim-shared/infrastructure/OrderRepository.js"
|
||||||
|
import { TaskVolcadoLineas } from "./tasks/volcado_lineas.js"
|
||||||
|
import { ObjeniousLinesRepository } from "./infranstructure/ObjeniousLinesRepository.js"
|
||||||
|
import { postgresClientIntranet } from "./config/intranetPostgresConfig.js"
|
||||||
|
import { PauseCancelTaskRepository } from "packages/sim-consumidor-objenious/infrastructure/PauseCancelTaskRepository.js"
|
||||||
|
import { PauseTerminateTask } from "./tasks/check_pause_terminate.js"
|
||||||
|
import { SimUseCases } from "packages/sim-consumidor-objenious/aplication/Sim.usecases.js"
|
||||||
|
|
||||||
async function startCron() {
|
async function startCron() {
|
||||||
const commonSettings = {
|
const commonSettings = {
|
||||||
@@ -14,10 +20,16 @@ async function startCron() {
|
|||||||
|
|
||||||
const httpClient = httpInstance
|
const httpClient = httpInstance
|
||||||
const pgClient = new PgClient({ pool: pgPool })
|
const pgClient = new PgClient({ pool: pgPool })
|
||||||
|
|
||||||
|
console.log("[i] Comprobando conexion con la BDD ")
|
||||||
await pgClient.checkDatabaseConnection()
|
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 objTask = new CheckObjeniousRequests(
|
const objTask = new CheckObjeniousRequests(
|
||||||
operationRepository,
|
operationRepository,
|
||||||
@@ -25,23 +37,56 @@ async function startCron() {
|
|||||||
httpClient,
|
httpClient,
|
||||||
)
|
)
|
||||||
|
|
||||||
await objTask.getPendingOperations()
|
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
|
||||||
|
)
|
||||||
|
|
||||||
|
const PERIODO_PETICIONES = 10 * 60 * 1000
|
||||||
const interval = setInterval(async () => {
|
const interval = setInterval(async () => {
|
||||||
console.log("Updating...")
|
try {
|
||||||
await objTask.getPendingOperations()
|
await objTask.getPendingOperations()
|
||||||
console.log("Update finished")
|
} catch (e) {
|
||||||
}, 10 * 60 * 1000)
|
console.error("[x] Error de actualizacion de las lineas ")
|
||||||
/*
|
}
|
||||||
const task = cron.createTask("* * * * *", async () => {
|
}, PERIODO_PETICIONES)
|
||||||
}
|
|
||||||
, {
|
const PERIODO_VOLCADO = 60 * 60 * 1000
|
||||||
...commonSettings,
|
const volcadoInterval = setInterval(async () => {
|
||||||
name: "Test"
|
try {
|
||||||
})
|
await volcadoLineasTask.loadLines()
|
||||||
*/
|
} catch (e) {
|
||||||
|
console.error("[x] Volcado de lineas de Objenious Fallido", e)
|
||||||
|
}
|
||||||
|
}, PERIODO_VOLCADO)
|
||||||
|
|
||||||
|
|
||||||
|
await pauseTask.run()
|
||||||
|
const PERIODO_CANCELACIONES = 60 * 60 * 1000;
|
||||||
|
const clacelacionesInterval = setInterval(async () => {
|
||||||
|
await pauseTask.run()
|
||||||
|
}, PERIODO_CANCELACIONES)
|
||||||
|
|
||||||
//await objTask.getPendingOperations()
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
import test, { after, before, describe } from "node:test";
|
||||||
|
import { CreateObjeniousLineDTO } from "sim-shared/domain/objeniousLine.js";
|
||||||
|
import { ObjeniousLinesRepository } from "./ObjeniousLinesRepository.js";
|
||||||
|
import { postgrClient } from "../config/postgreConfig.js";
|
||||||
|
import assert from "node:assert";
|
||||||
|
|
||||||
|
describe("Line insertion test", async () => {
|
||||||
|
//const pgClient = postgreClientIntranet
|
||||||
|
const pgClient = postgrClient // En prod hay que usar el de Intrantet para usar la otra base de datos
|
||||||
|
const lineRepository = new ObjeniousLinesRepository(pgClient)
|
||||||
|
const lineaTest: CreateObjeniousLineDTO = {
|
||||||
|
simId: 1234,
|
||||||
|
iccid: "9999999999999",
|
||||||
|
msisdn: "34654674732",
|
||||||
|
imei: "219789481293",
|
||||||
|
imeiChangeDate: new Date(),
|
||||||
|
offerCode: "SAVEFAMILY1",
|
||||||
|
status: "ACTIVATED",
|
||||||
|
preactivationDate: new Date(),
|
||||||
|
activationDate: new Date(),
|
||||||
|
commercialStatus: "test",
|
||||||
|
commercialStatusDate: new Date(),
|
||||||
|
billingStatus: "test",
|
||||||
|
billingStatusChangeDate: new Date(),
|
||||||
|
billingActivationDate: new Date(),
|
||||||
|
createDate: new Date(),
|
||||||
|
raw: { test: "test" } as any // Para este test no hace falta
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up before and after tests to ensure isolation
|
||||||
|
const cleanup = async () => {
|
||||||
|
await pgClient.query("DELETE FROM objenious_lines WHERE simId = 1234");
|
||||||
|
};
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
await cleanup()
|
||||||
|
})
|
||||||
|
|
||||||
|
after(async () => {
|
||||||
|
await cleanup()
|
||||||
|
})
|
||||||
|
|
||||||
|
test("Should insert new line", async () => {
|
||||||
|
const res = await lineRepository.insertOrUpdate(lineaTest)
|
||||||
|
assert.ok(res != undefined, "The line wasn't created")
|
||||||
|
})
|
||||||
|
|
||||||
|
test("Should not update a line if the hash is the same", async () => {
|
||||||
|
const res = await lineRepository.insertOrUpdate(lineaTest)
|
||||||
|
assert.ok(res == undefined, "The line have been updated")
|
||||||
|
})
|
||||||
|
|
||||||
|
test("Should update a line if the hash changes", async () => {
|
||||||
|
const updated = structuredClone(lineaTest)
|
||||||
|
lineaTest.billingActivationDate = new Date()
|
||||||
|
const res = await lineRepository.insertOrUpdate(lineaTest)
|
||||||
|
assert.ok(res != undefined, "The line have been updated")
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
/**
|
||||||
|
* Repositorio para el volcado de lineas de objenious en intranet
|
||||||
|
* solo para uso en el volcado.
|
||||||
|
*/
|
||||||
|
import { createHash } from "node:crypto";
|
||||||
|
import { PoolClient } from "pg";
|
||||||
|
import { CreateObjeniousLineDTO } from "sim-shared/domain/objeniousLine.js";
|
||||||
|
import { PgClient } from "sim-shared/infrastructure/PgClient.js";
|
||||||
|
|
||||||
|
export class ObjeniousLinesRepository {
|
||||||
|
constructor(
|
||||||
|
private pgClient: PgClient
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
private generateLineHash(data: CreateObjeniousLineDTO) {
|
||||||
|
try {
|
||||||
|
const lineStr = JSON.stringify(data)
|
||||||
|
const hash = createHash("sha256").update(lineStr).digest("base64url")
|
||||||
|
return hash
|
||||||
|
} catch (e) {
|
||||||
|
console.error("[x] Error generando el hash de la linea", data)
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async insertOrUpdate(data: CreateObjeniousLineDTO) {
|
||||||
|
const query = `
|
||||||
|
INSERT INTO objenious_lines (
|
||||||
|
simId,
|
||||||
|
iccid,
|
||||||
|
msisdn,
|
||||||
|
imei,
|
||||||
|
imeiChangeDate,
|
||||||
|
offerCode,
|
||||||
|
status,
|
||||||
|
preactivationDate,
|
||||||
|
activationDate,
|
||||||
|
commercialStatus,
|
||||||
|
commercialStatusDate,
|
||||||
|
billingStatus,
|
||||||
|
billingStatusChangeDate,
|
||||||
|
billingActivationDate,
|
||||||
|
createDate,
|
||||||
|
raw,
|
||||||
|
hash
|
||||||
|
) VALUES (
|
||||||
|
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17
|
||||||
|
)
|
||||||
|
ON CONFLICT (simId)
|
||||||
|
DO UPDATE SET
|
||||||
|
iccid = EXCLUDED.iccid,
|
||||||
|
msisdn = EXCLUDED.msisdn,
|
||||||
|
imei = EXCLUDED.imei,
|
||||||
|
imeiChangeDate = EXCLUDED.imeiChangeDate,
|
||||||
|
offerCode = EXCLUDED.offerCode,
|
||||||
|
status = EXCLUDED.status,
|
||||||
|
preactivationDate = EXCLUDED.preactivationDate,
|
||||||
|
activationDate = EXCLUDED.activationDate,
|
||||||
|
commercialStatus = EXCLUDED.commercialStatus,
|
||||||
|
commercialStatusDate = EXCLUDED.commercialStatusDate,
|
||||||
|
billingStatus = EXCLUDED.billingStatus,
|
||||||
|
billingStatusChangeDate = EXCLUDED.billingStatusChangeDate,
|
||||||
|
billingActivationDate = EXCLUDED.billingActivationDate,
|
||||||
|
raw = EXCLUDED.raw,
|
||||||
|
hash = EXCLUDED.hash
|
||||||
|
WHERE objenious_lines.hash IS DISTINCT FROM EXCLUDED.hash
|
||||||
|
RETURNING id;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const lineHash = this.generateLineHash(data)
|
||||||
|
|
||||||
|
if (lineHash == undefined) {
|
||||||
|
console.error("[x] Ignorando linea ", data)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const values = [
|
||||||
|
data.simId,
|
||||||
|
data.iccid,
|
||||||
|
data.msisdn,
|
||||||
|
data.imei,
|
||||||
|
data.imeiChangeDate,
|
||||||
|
data.offerCode,
|
||||||
|
data.status,
|
||||||
|
data.preactivationDate,
|
||||||
|
data.activationDate,
|
||||||
|
data.commercialStatus,
|
||||||
|
data.commercialStatusDate,
|
||||||
|
data.billingStatus,
|
||||||
|
data.billingStatusChangeDate,
|
||||||
|
data.billingActivationDate,
|
||||||
|
data.createDate || new Date(), // Default a ahora si no viene
|
||||||
|
JSON.stringify(data.raw), // El driver de pg requiere string o el objeto directo para JSONB
|
||||||
|
lineHash
|
||||||
|
];
|
||||||
|
|
||||||
|
let client: PoolClient | undefined = undefined;
|
||||||
|
try {
|
||||||
|
client = await this.pgClient.connect();
|
||||||
|
const res = await client.query<{ id: number }>(query, values);
|
||||||
|
return res.rows[0];
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error en la inserción:', err);
|
||||||
|
throw err;
|
||||||
|
} finally {
|
||||||
|
if (client != undefined) {
|
||||||
|
client.release()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,20 +5,6 @@
|
|||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.ts",
|
"main": "index.ts",
|
||||||
"imports": {
|
"imports": {
|
||||||
"#config/*.js": {
|
|
||||||
"types": "./config/*.ts",
|
|
||||||
"default": "./config/*.js"
|
|
||||||
},
|
|
||||||
"#config/*": {
|
|
||||||
"types": "./config/*.ts",
|
|
||||||
"default": "./config/*.js"
|
|
||||||
},
|
|
||||||
"#shared/*.js": {
|
|
||||||
"default": "../sim-shared/*.js"
|
|
||||||
},
|
|
||||||
"#shared/*": {
|
|
||||||
"default": "../sim-shared/*.js"
|
|
||||||
},
|
|
||||||
"#adapters/*.js": {
|
"#adapters/*.js": {
|
||||||
"types": "./infrastructure/*.ts",
|
"types": "./infrastructure/*.ts",
|
||||||
"default": "./infrastructure/*.js"
|
"default": "./infrastructure/*.js"
|
||||||
@@ -45,8 +31,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1",
|
"test": "node --import tsx --test ./**/*.test.ts",
|
||||||
"build": "tsc --build && tsc-alias -p tsconfig.json && cp package.json ../../dist/packages/sim-objenious-cron/",
|
"build": "tsc --build && tsc-alias -p tsconfig.json && cp .env package.json ../../dist/packages/sim-objenious-cron/",
|
||||||
"dev": "tsx watch index.ts",
|
"dev": "tsx watch index.ts",
|
||||||
"start": "node ../../dist/packages/sim-objenious-cron/index.js"
|
"start": "node ../../dist/packages/sim-objenious-cron/index.js"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import { env } from "#config/env/index.js";
|
import { env } from "../config/env/index.js";
|
||||||
import { OrderRepository } from "sim-shared/infrastructure/OrderRepository.js";
|
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 "packages/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
|
||||||
) {
|
) {
|
||||||
@@ -16,6 +17,7 @@ export class CheckObjeniousRequests {
|
|||||||
* TODO: meter a una funcion a parte task con los 3 pasos
|
* TODO: meter a una funcion a parte task con los 3 pasos
|
||||||
*/
|
*/
|
||||||
public async getPendingOperations() {
|
public async getPendingOperations() {
|
||||||
|
console.log("[i] Inicio revision de peticiones")
|
||||||
// 1. Se obtienen todas las operaciones pendientes de la BDD
|
// 1. Se obtienen todas las operaciones pendientes de la BDD
|
||||||
const pendingOperations = await this.operationsRepository.getPendingOperations()
|
const pendingOperations = await this.operationsRepository.getPendingOperations()
|
||||||
|
|
||||||
@@ -49,11 +51,14 @@ export class CheckObjeniousRequests {
|
|||||||
|
|
||||||
console.log("[cron] Solicitando status para", merged.map(e => e.id))
|
console.log("[cron] Solicitando status para", merged.map(e => e.id))
|
||||||
const result = await this.getMassActionsStatus(merged)
|
const result = await this.getMassActionsStatus(merged)
|
||||||
|
|
||||||
|
console.log("[o] Revisión de eventos completa")
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Para una lista de operaciones **con mass_action_id** se comprueba si han tenido alguna actualizacion
|
* Para una lista de operaciones **con mass_action_id** se comprueba si han tenido alguna actualizacion
|
||||||
* Devuelve el numero de operaciones comprobadas.
|
* Devuelve el numero de operaciones comprobadas.
|
||||||
|
* TODO: Esto va en un repositorio
|
||||||
*/
|
*/
|
||||||
private async getMassActionsStatus(requestList: ObjeniousOperation[]) {
|
private async getMassActionsStatus(requestList: ObjeniousOperation[]) {
|
||||||
if (requestList.length == 0) return 0;
|
if (requestList.length == 0) return 0;
|
||||||
@@ -119,9 +124,6 @@ export class CheckObjeniousRequests {
|
|||||||
|
|
||||||
if (uorStatus == "finished") {
|
if (uorStatus == "finished") {
|
||||||
console.log(" ****> Status", uorStatus)
|
console.log(" ****> Status", uorStatus)
|
||||||
if (uorStatus != "finished") {
|
|
||||||
console.error("!!! Notificando estado no finished")
|
|
||||||
}
|
|
||||||
const targetIccids = originalAction.iccids
|
const targetIccids = originalAction.iccids
|
||||||
const lineData = await this.getLineData(targetIccids)
|
const lineData = await this.getLineData(targetIccids)
|
||||||
console.log("[i] lineData", lineData.content[0])
|
console.log("[i] lineData", lineData.content[0])
|
||||||
@@ -136,7 +138,7 @@ export class CheckObjeniousRequests {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (originalAction.operation == "activation") {
|
if (originalAction.operation == "activate") {
|
||||||
this.notifyFinalization({
|
this.notifyFinalization({
|
||||||
...originalAction,
|
...originalAction,
|
||||||
msisdn
|
msisdn
|
||||||
@@ -215,7 +217,7 @@ export class CheckObjeniousRequests {
|
|||||||
const PATH = "/actions/requests/"
|
const PATH = "/actions/requests/"
|
||||||
const operationsList = structuredClone(requestList)
|
const operationsList = structuredClone(requestList)
|
||||||
|
|
||||||
|
// TODO: El for es gigantesco hay que simplificar partes
|
||||||
for (const request of operationsList) {
|
for (const request of operationsList) {
|
||||||
if (request.id == undefined) continue;
|
if (request.id == undefined) continue;
|
||||||
|
|
||||||
@@ -228,13 +230,50 @@ export class CheckObjeniousRequests {
|
|||||||
try {
|
try {
|
||||||
res = await req
|
res = await req
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Error comprobando el estado de ", request, e)
|
console.error("[x] Error comprobando el estado de ", request, e)
|
||||||
//todo actualizar el estado para incluir el error
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Casos de error o id no generada
|
||||||
|
if (res.data.massActionIds.length == 0) {
|
||||||
|
// Si no hay es que *puede* que haya un problema o no se ha generado todavia
|
||||||
|
const reports = res.data.actionRequestReports
|
||||||
|
// Se entiende que no hay report ni id = está a la espera
|
||||||
|
if (reports.length == 0) continue;
|
||||||
|
|
||||||
|
// ! Hay minimo un report -> se considera error y se para
|
||||||
|
const updateData: ObjeniousOperationChange = {
|
||||||
|
operation_id: request.id,
|
||||||
|
new_status: "error",
|
||||||
|
error: JSON.stringify(reports[0].actionRequestReportDataDTOs)
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateRes = await this.operationsRepository.updateOperation(updateData)
|
||||||
|
if (updateRes.error != undefined) {
|
||||||
|
console.error("[x] Error actualizando el estado de la operacion", updateData.error)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.correlation_id != undefined) {
|
||||||
|
this.orderRepository.errorOrder({
|
||||||
|
correlation_id: request.correlation_id,
|
||||||
|
status: "failed",
|
||||||
|
error: "MassId no obtenida",
|
||||||
|
reason: "MassId no obtenida",
|
||||||
|
stackTrace: JSON.stringify(reports[0].actionRequestReportDataDTOs)
|
||||||
|
}).then(e => {
|
||||||
|
if (e.error != undefined) {
|
||||||
|
console.error("[x] Error actualizando el estado del Order con correlation_id: ", request.correlation_id)
|
||||||
|
console.error(e.error)
|
||||||
|
}
|
||||||
|
}).catch(e => {
|
||||||
|
console.error("[x] Error actualizando el estado del Order con correlation_id: ", request.correlation_id)
|
||||||
|
})
|
||||||
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Modificacion del massId si ha habido un cambio
|
|
||||||
const massActionId = res.data.massActionIds[0]
|
const massActionId = res.data.massActionIds[0]
|
||||||
|
// 3. Modificacion del massId si ha habido un cambio
|
||||||
try {
|
try {
|
||||||
if (res.status == 200 && res.data != undefined && massActionId != undefined) {
|
if (res.status == 200 && res.data != undefined && massActionId != undefined) {
|
||||||
const updateData: ObjeniousOperationChange = {
|
const updateData: ObjeniousOperationChange = {
|
||||||
@@ -248,7 +287,7 @@ export class CheckObjeniousRequests {
|
|||||||
request.mass_action_id = String(massActionId)
|
request.mass_action_id = String(massActionId)
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log("Error actualizando el estado de ", request)
|
console.log("[x] Error actualizando el estado de ", request)
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -262,6 +301,8 @@ export class CheckObjeniousRequests {
|
|||||||
* al servicio que manda los mails
|
* al servicio que manda los mails
|
||||||
*/
|
*/
|
||||||
private async notifyFinalization(operation: ObjeniousOperation & { msisdn: string }) {
|
private async notifyFinalization(operation: ObjeniousOperation & { msisdn: string }) {
|
||||||
|
console.log("[i] Enviando activacion a", env.NOTIFICATION_URL)
|
||||||
|
console.log("[i] Operation", operation)
|
||||||
const req = axios.post(env.NOTIFICATION_URL, {
|
const req = axios.post(env.NOTIFICATION_URL, {
|
||||||
...operation,
|
...operation,
|
||||||
iccids: [operation.iccids]
|
iccids: [operation.iccids]
|
||||||
@@ -270,7 +311,17 @@ export class CheckObjeniousRequests {
|
|||||||
"x-apikey-sim-activation": env.SIM_ACTIVATION_API_KEY
|
"x-apikey-sim-activation": env.SIM_ACTIVATION_API_KEY
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
await req
|
try {
|
||||||
|
const res = await req
|
||||||
|
if (res.status != 200) {
|
||||||
|
console.error("[x] Error enviando el mail de confirmacion para ", operation, " status ", res.status, res.statusText)
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error("[x] Error enviando el mail de confirmacion para ", operation)
|
||||||
|
console.error(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
189
packages/sim-objenious-cron/tasks/check_pause_terminate.ts
Normal file
189
packages/sim-objenious-cron/tasks/check_pause_terminate.ts
Normal file
@@ -0,0 +1,189 @@
|
|||||||
|
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 "packages/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 exito = false;
|
||||||
|
let result = null;
|
||||||
|
// IMPORTANTE COMRPOBAR EL DUE DATE
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
54
packages/sim-objenious-cron/tasks/volcado_lineas.ts
Normal file
54
packages/sim-objenious-cron/tasks/volcado_lineas.ts
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import { lineToCreateLineDto, ObjeniousLine } from "sim-shared/domain/objeniousLine.js";
|
||||||
|
import { ObjeniousLinesRepository } from "../infranstructure/ObjeniousLinesRepository.js";
|
||||||
|
import { ObjeniousOperationsRepository } from "sim-shared/infrastructure/ObjeniousOperationRepository.js";
|
||||||
|
|
||||||
|
export class TaskVolcadoLineas {
|
||||||
|
constructor(
|
||||||
|
private readonly linesRepository: ObjeniousLinesRepository,
|
||||||
|
private readonly objeniousRepository: ObjeniousOperationsRepository
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private async saveLines(lines: ObjeniousLine[]) {
|
||||||
|
const linesToCreate = lines.map(lineToCreateLineDto)
|
||||||
|
let created: number[] = []
|
||||||
|
|
||||||
|
|
||||||
|
for (const line of linesToCreate) {
|
||||||
|
// Si es lento pasar a Promise.all
|
||||||
|
const res = await this.linesRepository.insertOrUpdate(line)
|
||||||
|
if (res?.id != undefined)
|
||||||
|
created.push(res.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async loadLines() {
|
||||||
|
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
|
||||||
|
|
||||||
|
const linesIterator = this.objeniousRepository.getLinesByStatusAPI({
|
||||||
|
pageSize: 100
|
||||||
|
})
|
||||||
|
let lines = await linesIterator.next()
|
||||||
|
|
||||||
|
if (lines.value.error != undefined || lines.value.data == undefined) {
|
||||||
|
console.error("[x] Error cargando las lineas a volcar", lines.value.error)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.saveLines(lines.value.data)
|
||||||
|
|
||||||
|
while (!lines.done) {
|
||||||
|
lines = await linesIterator.next()
|
||||||
|
if (lines.value.error != undefined || lines.value.data == undefined) {
|
||||||
|
console.error("[x] Error cargando las lineas a volcar", lines.value.error)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await this.saveLines(lines.value.data)
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("[i] Terminado task de volcado de lineas de Objenious")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
})
|
||||||
@@ -62,11 +62,14 @@ export type CreateOrderDTO = Pick<
|
|||||||
'correlation_id' | 'exchange' | 'routing_key' | 'order_type' | 'payload' | 'webhook_host' | 'webhook_endpoint'
|
'correlation_id' | 'exchange' | 'routing_key' | 'order_type' | 'payload' | 'webhook_host' | 'webhook_endpoint'
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export type UpdateOrderDTO =
|
type IdOrCorrelationID =
|
||||||
(
|
(
|
||||||
{ id: number, correlation_id?: never } |
|
{ id: number, correlation_id?: never } |
|
||||||
{ id?: never, correlation_id: string }
|
{ id?: never, correlation_id: string }
|
||||||
)
|
)
|
||||||
|
|
||||||
|
export type UpdateOrderDTO =
|
||||||
|
IdOrCorrelationID
|
||||||
&
|
&
|
||||||
{
|
{
|
||||||
new_status: OrderStatus,
|
new_status: OrderStatus,
|
||||||
@@ -74,12 +77,20 @@ export type UpdateOrderDTO =
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type FinishOrderDTO =
|
export type FinishOrderDTO =
|
||||||
(
|
IdOrCorrelationID
|
||||||
{ id: number, correlation_id?: never } |
|
|
||||||
{ id?: never, correlation_id: string }
|
|
||||||
)
|
|
||||||
&
|
&
|
||||||
{
|
{
|
||||||
reason?: string
|
reason?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ErrorOrderDTO =
|
||||||
|
IdOrCorrelationID
|
||||||
|
&
|
||||||
|
{
|
||||||
|
status: "failed" | "dlx",
|
||||||
|
reason: string,
|
||||||
|
error?: string,
|
||||||
|
stackTrace?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
144
packages/sim-shared/domain/objeniousLine.ts
Normal file
144
packages/sim-shared/domain/objeniousLine.ts
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
export type ObjeniousLineResponse = {
|
||||||
|
content: ObjeniousLine[],
|
||||||
|
offset: number,
|
||||||
|
pageNumber: number,
|
||||||
|
pageSize: number,
|
||||||
|
paged: boolean,
|
||||||
|
totalPages: number,
|
||||||
|
totalElements: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ObjeniousLine = {
|
||||||
|
identifier: {
|
||||||
|
simId: number,
|
||||||
|
iccid: string,
|
||||||
|
imsi: string,
|
||||||
|
msisdn: string,
|
||||||
|
amsisdn?: string,
|
||||||
|
imei: string
|
||||||
|
},
|
||||||
|
simCardType: {
|
||||||
|
code: string,
|
||||||
|
description: string
|
||||||
|
},
|
||||||
|
device: {
|
||||||
|
imei: string,
|
||||||
|
imeiChangeDate: string, //Fecha iso
|
||||||
|
deviceReference?: string | null,
|
||||||
|
manufacturer?: string | null,
|
||||||
|
},
|
||||||
|
customerAccount: {
|
||||||
|
code: string,
|
||||||
|
label: string,
|
||||||
|
address: {
|
||||||
|
address1: string,
|
||||||
|
address2: string,
|
||||||
|
address3: string,
|
||||||
|
zipCode: string,
|
||||||
|
city: string,
|
||||||
|
country: string,
|
||||||
|
state?: string | null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
offer: {
|
||||||
|
code: string,
|
||||||
|
description: string,
|
||||||
|
},
|
||||||
|
party: {
|
||||||
|
name: string,
|
||||||
|
code: string,
|
||||||
|
contractReference: string,
|
||||||
|
partyType: string,
|
||||||
|
},
|
||||||
|
lineCustomFields: {
|
||||||
|
custom1: {
|
||||||
|
label: string | null,
|
||||||
|
value: string | null
|
||||||
|
},
|
||||||
|
custom2: {
|
||||||
|
label: string | null,
|
||||||
|
value: string | null
|
||||||
|
},
|
||||||
|
custom3: {
|
||||||
|
label: string | null,
|
||||||
|
value: string | null
|
||||||
|
},
|
||||||
|
custom4: {
|
||||||
|
label: string | null,
|
||||||
|
value: string | null
|
||||||
|
},
|
||||||
|
custom5: {
|
||||||
|
label: string | null,
|
||||||
|
value: string | null
|
||||||
|
},
|
||||||
|
custom6: {
|
||||||
|
label: string | null,
|
||||||
|
value: string | null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
status: string,
|
||||||
|
preactivationDate: string | null, //"2026-03-17",
|
||||||
|
activationDate: string | null, //"2026-03-17T11:04:11.408+00:00",
|
||||||
|
commercialStatus: string, //"test",
|
||||||
|
commercialStatusDate: string, //"2026-03-17T11:41:01.493+00:00",
|
||||||
|
networkStatus: string, // "ACTIVATED",
|
||||||
|
billingStatus: "ACTIVATED" | "SUSPENDED" | "CANCELED" | "TEST",
|
||||||
|
billingStatusChangeDate: string | null, // "2026-03-17T11:01:00.276+00:00",
|
||||||
|
billingActivationDate: string | null //,
|
||||||
|
createdDate: string | null,//"2026-01-30T01:50:02.060+00:00"
|
||||||
|
},
|
||||||
|
services: string | null
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ObjeniousLineDb = {
|
||||||
|
id: number;
|
||||||
|
simId?: number;
|
||||||
|
iccid: string;
|
||||||
|
msisdn?: string;
|
||||||
|
imei?: string;
|
||||||
|
imeiChangeDate?: Date;
|
||||||
|
offerCode?: string;
|
||||||
|
status?: string;
|
||||||
|
preactivationDate?: Date | null;
|
||||||
|
activationDate?: Date | null;
|
||||||
|
commercialStatus?: string;
|
||||||
|
commercialStatusDate?: Date | null;
|
||||||
|
billingStatus?: string;
|
||||||
|
billingStatusChangeDate?: Date | null;
|
||||||
|
billingActivationDate?: Date | null;
|
||||||
|
createDate?: Date | null;
|
||||||
|
raw: ObjeniousLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
// DTO para inserción (omite el ID autogenerado)
|
||||||
|
export type CreateObjeniousLineDTO = Omit<ObjeniousLineDb, 'id'>;
|
||||||
|
|
||||||
|
export function lineToCreateLineDto(line: ObjeniousLine): CreateObjeniousLineDTO {
|
||||||
|
|
||||||
|
const dateOrNull = (data: string | null) => {
|
||||||
|
if (data == null) return null;
|
||||||
|
return new Date(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
const transformed: CreateObjeniousLineDTO = {
|
||||||
|
simId: line.identifier.simId,
|
||||||
|
iccid: line.identifier.iccid,
|
||||||
|
msisdn: line.identifier.msisdn,
|
||||||
|
imei: line.identifier.imei,
|
||||||
|
imeiChangeDate: new Date(line.device.imeiChangeDate),
|
||||||
|
offerCode: line.offer.code,
|
||||||
|
status: line.status.status,
|
||||||
|
preactivationDate: dateOrNull(line.status.preactivationDate),
|
||||||
|
activationDate: dateOrNull(line.status.activationDate),
|
||||||
|
commercialStatus: line.status.commercialStatus,
|
||||||
|
commercialStatusDate: dateOrNull(line.status.commercialStatusDate),
|
||||||
|
billingStatus: line.status.billingStatus,
|
||||||
|
billingStatusChangeDate: dateOrNull(line.status.activationDate),
|
||||||
|
billingActivationDate: dateOrNull(line.status.activationDate),
|
||||||
|
createDate: dateOrNull(line.status.activationDate),
|
||||||
|
raw: line
|
||||||
|
}
|
||||||
|
|
||||||
|
return transformed;
|
||||||
|
}
|
||||||
@@ -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: string;
|
operation: "activate" | 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;
|
||||||
@@ -46,10 +46,34 @@ export namespace Objenious {
|
|||||||
created: string,
|
created: string,
|
||||||
status: "NEW" | "RUNNING" | "OK" | "KO" | "REPLAYED" | "CANCELLED" | "CLOSED" | "DISABLED",
|
status: "NEW" | "RUNNING" | "OK" | "KO" | "REPLAYED" | "CANCELLED" | "CLOSED" | "DISABLED",
|
||||||
statusDate: string,
|
statusDate: string,
|
||||||
actionType: "PREACTIVATION_AND_ACTIVATION" | string, // todo: añadir el resto
|
actionType: ActionType
|
||||||
massActionIds: number[]
|
massActionIds: number[],
|
||||||
|
actionRequestReports:
|
||||||
|
{
|
||||||
|
requestId: string,
|
||||||
|
actionRequestReportDataDTOs: [
|
||||||
|
{
|
||||||
|
data: string,
|
||||||
|
newData: string | null,
|
||||||
|
iccid: string,
|
||||||
|
dataStatus: DataStatus
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}[],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type DataStatus = "DATA_INVALID_FORMAT" | "DATA_NOT_FOUND" | "DATA_NOT_ACTIVATED" | "SERVICE_DATA_NOT_ACTIVATED" |
|
||||||
|
"DATA_WRONG_STATUS" | "DATA_NOT_AUTHORIZED" | "DATA_CUSTOMER_ACCOUNT_NOT_AUTHORIZED" | "DATA_AMBIGUOUS" |
|
||||||
|
"NEW_DATA_INVALID_FORMAT" | "NEW_DATA_ALREADY_EXISTS" | "DUPLICATE_DATA" | "DATA_TERMINATION_VALIDATED" |
|
||||||
|
"DATA_TERMINATION_SECURISED" | "MAX_ALARM_INSTANCE" | "MAX_ALARM_INSTANCE_TO_CATCH_UP" |
|
||||||
|
"ACTIVATED_LINE_CANNOT_BE_TRANSFERED" | "ESIM_WRONG_STEP" | "ESIM_WRONG_PAIRED_VALUE" |
|
||||||
|
"ESIM_WRONG_DOWNLOAD_STATE" | "ESIM_WRONG_STATUS" | "ESIM_WRONG_FAMILY" | "ESIM_WRONG_CATEGORY" |
|
||||||
|
"ENTITY_STATUS_NOT_AUTHORIZED" | "LONG_LIFE_NOT_ALLOWED" | "RCARD_NOT_COMPATIBLE" | "APN_NOT_FOUND" |
|
||||||
|
"APN_OR_DNN_NOT_FOUND" | "APN_CONFIGURATION_NOT_FOUND" | "APN_CONFIGURATION_INVALID_PARAMETER_FILE" |
|
||||||
|
"IP_NOT_AVAILABLE" | "RADIUS_FIELD_LENGTH_NOT_ALLOWED" | "RADIUS_LOGIN_OR_PASSWORD_NOT_FOUND" | "RADIUS_PASSWORD_NOT_ALLOWED" |
|
||||||
|
"RADIUS_LOGIN_NOT_ALLOWED" | "NETWORK_NOT_ACTIVATED" | "CHANGE_CUSTOMER_ACCOUNT_NOT_AllOWED" | "CHANGE_OFFER_NOT_ALLOWED" |
|
||||||
|
"SIM_NOT_EUICC" | "OFFER_NOT_WSF_PALIER_FLOTTE_FR"
|
||||||
|
|
||||||
export type ActionType = "PREACTIVATION" | "PREACTIVATION_ACTIVATION" | "ACTIVATION" |
|
export type ActionType = "PREACTIVATION" | "PREACTIVATION_ACTIVATION" | "ACTIVATION" |
|
||||||
"STATUS_CHANGE" | "ICCID_CHANGE" | "EUICC_NOTIFICATION"
|
"STATUS_CHANGE" | "ICCID_CHANGE" | "EUICC_NOTIFICATION"
|
||||||
| "EUICC_AUDIT" | "MSISDN_CHANGE" | "ALARM_SETTING"
|
| "EUICC_AUDIT" | "MSISDN_CHANGE" | "ALARM_SETTING"
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import { describe, it } from "node:test";
|
||||||
|
import { ObjeniousOperationsRepository } from "./ObjeniousOperationRepository.js";
|
||||||
|
import { httpObjClient, postgresClient } from "../config/config.test.js";
|
||||||
|
|
||||||
|
describe("[Integration] Test API requests", () => {
|
||||||
|
const repository = new ObjeniousOperationsRepository(
|
||||||
|
httpObjClient,
|
||||||
|
postgresClient
|
||||||
|
)
|
||||||
|
|
||||||
|
it("Read /lines with multiple iccids", () => {
|
||||||
|
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -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)
|
||||||
@@ -46,7 +171,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
|
||||||
|
|||||||
@@ -2,11 +2,10 @@
|
|||||||
* TODO: Usar
|
* TODO: Usar
|
||||||
*/
|
*/
|
||||||
import { PoolClient, QueryResult, QueryResultRow } from "pg";
|
import { PoolClient, QueryResult, QueryResultRow } from "pg";
|
||||||
import { CreateOrderDTO, 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
|
||||||
}
|
}
|
||||||
@@ -353,22 +356,19 @@ export class OrderRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: tema de poder filtrar por correlation_id
|
// TODO: tema de poder filtrar por correlation_id
|
||||||
public async errorOrder(args: {
|
public async errorOrder(args: ErrorOrderDTO): Promise<Result<string, OrderTracking<any>>> {
|
||||||
id: number,
|
|
||||||
status: "failed" | "dlx",
|
|
||||||
reason: string,
|
|
||||||
error?: string,
|
|
||||||
stackTrace?: string
|
|
||||||
}) {
|
|
||||||
const client = await this.pgClient.connect();
|
const client = await this.pgClient.connect();
|
||||||
await client.query('BEGIN');
|
await client.query('BEGIN');
|
||||||
|
|
||||||
|
const idType = ('id' in args) ? "id" : "correlation_id"
|
||||||
|
const idValue = (args.id != undefined) ? args.id : args.correlation_id
|
||||||
|
|
||||||
// 1. Se consulta la order de base
|
// 1. Se consulta la order de base
|
||||||
const qCurrentOrder = `
|
const qCurrentOrder = `
|
||||||
SELECT * FROM order_tracking
|
SELECT * FROM order_tracking
|
||||||
WHERE id = $1
|
WHERE ${idType} = $1
|
||||||
`
|
`
|
||||||
const vCurrentOrder = [args.id]
|
const vCurrentOrder = [idValue]
|
||||||
|
|
||||||
const currentOrderResult = await this.getFirst(client.query<OrderTracking<any>>(qCurrentOrder, vCurrentOrder))
|
const currentOrderResult = await this.getFirst(client.query<OrderTracking<any>>(qCurrentOrder, vCurrentOrder))
|
||||||
|
|
||||||
@@ -378,6 +378,7 @@ export class OrderRepository {
|
|||||||
return currentOrderResult
|
return currentOrderResult
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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!
|
||||||
|
|
||||||
// 3. Si todo ok se actualiza el order
|
// 3. Si todo ok se actualiza el order
|
||||||
@@ -395,7 +396,7 @@ export class OrderRepository {
|
|||||||
WHERE id = $1
|
WHERE id = $1
|
||||||
RETURNING id, status, update_date;
|
RETURNING id, status, update_date;
|
||||||
`
|
`
|
||||||
const vOrderTracking = [args.id, args.status, args.error, args.stackTrace]
|
const vOrderTracking = [id, args.status, args.error, args.stackTrace]
|
||||||
const updatedOrderResult = await this.getFirst(
|
const updatedOrderResult = await this.getFirst(
|
||||||
client.query<{ id: number, status: string, update_date: string }>(uOrderTracking, vOrderTracking)
|
client.query<{ id: number, status: string, update_date: string }>(uOrderTracking, vOrderTracking)
|
||||||
)
|
)
|
||||||
|
|||||||
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
|
||||||
@@ -1,12 +1,3 @@
|
|||||||
#/bin/bash
|
#/bin/bash
|
||||||
rm deployment/database/init.sql
|
|
||||||
|
|
||||||
# init sql debe juntar todos los scripts de "base" (sin contar migraciones)
|
|
||||||
cat deployment/database/base/*.sql >deployment/database/init.sql
|
|
||||||
#cp deployment/database/esquema_final* deployment/database/init.sql
|
|
||||||
|
|
||||||
# compatibilidad con postgresql < 17
|
|
||||||
sed -i '/\\restrict/d' deployment/database/init.sql
|
|
||||||
sed -i '/\\unrestrict/d' deployment/database/init.sql
|
|
||||||
|
|
||||||
docker compose -f deployment/local/docker/docker-compose.yaml --project-directory ./ up --watch
|
docker compose -f deployment/local/docker/docker-compose.yaml --project-directory ./ up --watch
|
||||||
|
|||||||
32
yarn.lock
32
yarn.lock
@@ -452,6 +452,18 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@sf-alvar/db-migrate@npm:1.0.6":
|
||||||
|
version: 1.0.6
|
||||||
|
resolution: "@sf-alvar/db-migrate@npm:1.0.6::__archiveUrl=https%3A%2F%2Fgit.savefamilygps.net%2Fapi%2Fpackages%2FSaveFamily%2Fnpm%2F%2540sf-alvar%252Fdb-migrate%2F-%2F1.0.6%2Fdb-migrate-1.0.6.tgz"
|
||||||
|
dependencies:
|
||||||
|
pg: "npm:^8.18.0"
|
||||||
|
yargs: "npm:^18.0.0"
|
||||||
|
bin:
|
||||||
|
db-migrate: lib/index.js
|
||||||
|
checksum: 10/070f1388ff1c6fd2d24c3139d779e871bc0db94f11dd2013aa7eb5728e3c21e594bac0f4d46f8f3132391a9903cca56d5c864862c622d70f24e0db0ffcbbbf0e
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@standard-schema/spec@npm:^1.0.0":
|
"@standard-schema/spec@npm:^1.0.0":
|
||||||
version: 1.1.0
|
version: 1.1.0
|
||||||
resolution: "@standard-schema/spec@npm:1.1.0"
|
resolution: "@standard-schema/spec@npm:1.1.0"
|
||||||
@@ -1142,18 +1154,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"db-migrate@http://gitea:3000/alvarsanmartin/herramienta-migracion.git":
|
|
||||||
version: 1.1.0
|
|
||||||
resolution: "db-migrate@http://gitea:3000/alvarsanmartin/herramienta-migracion.git#commit=f84d68ba79161b9b06b747919979db00aac34b49"
|
|
||||||
dependencies:
|
|
||||||
pg: "npm:^8.18.0"
|
|
||||||
yargs: "npm:^18.0.0"
|
|
||||||
bin:
|
|
||||||
db-migrate: ./lib/index.js
|
|
||||||
checksum: 10/2468cfd14a5f218845f5437f530a68993a51b3998cdd9d0c7f28cdb810314200c471debac9ca19c34d4978907b7c4ced5c95e777eebd40c3baa795ad945d8892
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"debug@npm:4, debug@npm:^4.1.1, debug@npm:^4.3.4, debug@npm:^4.3.7, debug@npm:^4.4.0, debug@npm:^4.4.3":
|
"debug@npm:4, debug@npm:^4.1.1, debug@npm:^4.3.4, debug@npm:^4.3.7, debug@npm:^4.4.0, debug@npm:^4.4.3":
|
||||||
version: 4.4.3
|
version: 4.4.3
|
||||||
resolution: "debug@npm:4.4.3"
|
resolution: "debug@npm:4.4.3"
|
||||||
@@ -2372,13 +2372,13 @@ __metadata:
|
|||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"postcss@npm:^8.5.6":
|
"postcss@npm:^8.5.6":
|
||||||
version: 8.5.6
|
version: 8.5.8
|
||||||
resolution: "postcss@npm:8.5.6"
|
resolution: "postcss@npm:8.5.8"
|
||||||
dependencies:
|
dependencies:
|
||||||
nanoid: "npm:^3.3.11"
|
nanoid: "npm:^3.3.11"
|
||||||
picocolors: "npm:^1.1.1"
|
picocolors: "npm:^1.1.1"
|
||||||
source-map-js: "npm:^1.2.1"
|
source-map-js: "npm:^1.2.1"
|
||||||
checksum: 10/9e4fbe97574091e9736d0e82a591e29aa100a0bf60276a926308f8c57249698935f35c5d2f4e80de778d0cbb8dcffab4f383d85fd50c5649aca421c3df729b86
|
checksum: 10/cbacbfd7f767e2c820d4bf09a3a744834dd7d14f69ff08d1f57b1a7defce9ae5efcf31981890d9697a972a64e9965de677932ef28e4c8ba23a87aad45b82c459
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
@@ -2856,6 +2856,7 @@ __metadata:
|
|||||||
version: 0.0.0-use.local
|
version: 0.0.0-use.local
|
||||||
resolution: "sim-eventos@workspace:."
|
resolution: "sim-eventos@workspace:."
|
||||||
dependencies:
|
dependencies:
|
||||||
|
"@sf-alvar/db-migrate": "npm:1.0.6"
|
||||||
"@tsconfig/node22": "npm:^22.0.5"
|
"@tsconfig/node22": "npm:^22.0.5"
|
||||||
"@types/amqplib": "npm:^0.10.8"
|
"@types/amqplib": "npm:^0.10.8"
|
||||||
"@types/cors": "npm:^2.8.19"
|
"@types/cors": "npm:^2.8.19"
|
||||||
@@ -2868,7 +2869,6 @@ __metadata:
|
|||||||
axios: "npm:^1.13.3"
|
axios: "npm:^1.13.3"
|
||||||
concurrently: "npm:^9.2.1"
|
concurrently: "npm:^9.2.1"
|
||||||
cors: "npm:^2.8.5"
|
cors: "npm:^2.8.5"
|
||||||
db-migrate: "http://gitea:3000/alvarsanmartin/herramienta-migracion.git"
|
|
||||||
dotenv: "npm:^17.2.3"
|
dotenv: "npm:^17.2.3"
|
||||||
express: "npm:^5.2.1"
|
express: "npm:^5.2.1"
|
||||||
pg: "npm:^8.18.0"
|
pg: "npm:^8.18.0"
|
||||||
|
|||||||
Reference in New Issue
Block a user