Compare commits
83 Commits
WEBINT-334
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 3f7c052572 | |||
| 3d9e2a6e9b | |||
| f94ee799d0 | |||
| d1e5892a0d | |||
| b14464da39 | |||
| cf6204e231 | |||
| 9d8788db39 | |||
| 79ceb74604 | |||
| d02b1d48bf | |||
| 1fb50323b0 | |||
| 985c85da59 | |||
| 23c61097a8 | |||
| 5372045bd7 | |||
| 72d61b8376 | |||
| 526c094494 | |||
| 474f7b7c68 | |||
| f8692e3e2e | |||
| 62715fae34 | |||
| f8678a68bb | |||
| 8f8ae22f23 | |||
| 2611147eb3 | |||
| b0b3badd5c | |||
| 2b812098fb | |||
| 3146efec64 | |||
| 44e4b98e35 | |||
| 6adb4d5c95 | |||
| bcb1a28164 | |||
| d5798602e2 | |||
| 1f94a89520 | |||
| 44bbc8f17c | |||
| 9e6877c329 | |||
| d5883ba75e | |||
| 421d0aa705 | |||
| 69b5958296 | |||
| 86478b1073 | |||
| b9e3e1784f | |||
| 976cf1c3d2 | |||
| e4ba1576e5 | |||
| 4baa9f708f | |||
| 6fb25e6055 | |||
| 63698ee1aa | |||
| 410f659db0 | |||
| 08c972e720 | |||
| c4e4d87303 | |||
| 9c74fb9a7b | |||
| 1d7c2b2946 | |||
| d2c86396b1 | |||
| 3cf5c3695e | |||
| 7dda25fbfb | |||
| eefb7c5a79 | |||
| 13944a64d2 | |||
| 07e60690ab | |||
| 036ae20ac3 | |||
| 189de6c0fb | |||
| 113d9f3786 | |||
| 331d920379 | |||
| a615fc2b81 | |||
| f98097d11d | |||
| 3e76c3c931 | |||
| bb31efb271 | |||
| c9733113cf | |||
| 4c1d6ac2c4 | |||
| 07e7a0d457 | |||
| 48361ab33f | |||
| a3c7c224b1 | |||
| 324aec3001 | |||
| 05e941710b | |||
| f78a333e1e | |||
| 01c55cba0f | |||
| 10b2ae244c | |||
| 2dba2ebfae | |||
| d7eb4ad326 | |||
| d818441bde | |||
| c91965567d | |||
| d063b47bec | |||
| 6112de297b | |||
| 166c940295 | |||
| 246e4cb83b | |||
| 4517796ef3 | |||
| 858932f260 | |||
| a84f600fa2 | |||
| 4e02ea021d | |||
| 9ec127433d |
32
.env
32
.env
@@ -1,32 +0,0 @@
|
||||
PORT=3000
|
||||
API_HOSTNAME=0.0.0.0
|
||||
RABBITMQ_USER=guest
|
||||
RABBITMQ_PASSWORD=guest
|
||||
|
||||
ENVIORMENT=development
|
||||
|
||||
#RABBITMQ_HOST=rabbitmq-sim-broker
|
||||
RABBITMQ_HOST=localhost
|
||||
RABBITMQ_PORT=5672
|
||||
RABBITMQ_USER=guest
|
||||
RABBITMQ_PASSWORD=guest
|
||||
RABBITMQ_SECURE=false
|
||||
RABBITMQ_VHOST=sim-vhost
|
||||
|
||||
# Hay cosas que unificar de varios servicios
|
||||
#POSTGRES_HOST=postgresql-sim
|
||||
POSTGRES_HOST=localhost
|
||||
POSTGRES_DB=postgres
|
||||
POSTGRES_DATABASE=postgres
|
||||
POSTGRES_PORT=5433
|
||||
POSTGRES_USER=postgres
|
||||
POSTGRES_PASSWORD='1234'
|
||||
|
||||
# Para el postgres local para generar el script de resultado de migraciones
|
||||
PGHOST=localhost
|
||||
PGUSER=alvar
|
||||
PGPASSWORD=alvar
|
||||
PGPORT=5433
|
||||
|
||||
# Proxy
|
||||
CONNECTIONS_URL=https://sim-connections.savefamilygps.net
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -16,7 +16,11 @@ node_modules
|
||||
#!.yarn/cache
|
||||
.pnp.*
|
||||
|
||||
# Certificados
|
||||
*.pem
|
||||
|
||||
*.p12
|
||||
*.key
|
||||
|
||||
dist/*
|
||||
|
||||
.env
|
||||
|
||||
@@ -2,10 +2,12 @@ compressionLevel: mixed
|
||||
|
||||
enableGlobalCache: false
|
||||
|
||||
enableScripts: true
|
||||
|
||||
nodeLinker: node-modules
|
||||
|
||||
npmRegistryServer: "https://registry.npmjs.org/"
|
||||
|
||||
npmScopes:
|
||||
sf-alvar:
|
||||
npmRegistryServer: "https://git.savefamilygps.net/api/packages/SaveFamily/npm/"
|
||||
|
||||
npmRegistryServer: "https://registry.npmjs.org/"
|
||||
|
||||
23
README.md
23
README.md
@@ -12,13 +12,13 @@ La compañia a la que pertenece cada peticion y por tanto el servicio que lo va
|
||||
|
||||
## Decisiones pendientes
|
||||
|
||||
- [x] La capa worker según acción y la de operaciones de proveedores, se podrían unir en una sola con un enrutamiento por acción y compañía, pasando de tener claves `sim.[acción]` a `sim.[compañia].[acción]`. *Se ha aplicado el cambio ahora las routing keys tienen la estructura `sim.[compañia].[acción]`*
|
||||
- [x] La estructura de RMQ se genera por medio del JSON, igual habría que definir cada cola en el worker que la consuma para poder añadir workers sin parar el RMQ. *Se ha aplicado el cambio, ahora solo se define en el json el broker principal para garantizar que exita sin servicios consumidores. Sin embargo tal como estan estructurdos los proyectos no es posible reiniciar solo un servicio*
|
||||
- [x] La capa worker según acción y la de operaciones de proveedores, se podrían unir en una sola con un enrutamiento por acción y compañía, pasando de tener claves `sim.[acción]` a `sim.[compañia].[acción]`. _Se ha aplicado el cambio ahora las routing keys tienen la estructura `sim.[compañia].[acción]`_
|
||||
- [x] La estructura de RMQ se genera por medio del JSON, igual habría que definir cada cola en el worker que la consuma para poder añadir workers sin parar el RMQ. _Se ha aplicado el cambio, ahora solo se define en el json el broker principal para garantizar que exita sin servicios consumidores. Sin embargo tal como estan estructurdos los proyectos no es posible reiniciar solo un servicio_
|
||||
- [ ] Versionado de la API.
|
||||
- [x] Método para sacar la compañía a partir del iccid, o buscar en la BDD si no es posible. *De momento es un objeto Map en el servicio de gateway*
|
||||
- [ ] Cola de mensajes que no se han podido procesar. Distinguir según error de red; se reintenta; o error del propio mensaje; se envía a la cola de errores. v2 Se ha creado una cola de delay pero no se distingue el tipo de error, despues de n reintentos el mensaje va a la cola de dead-letter.
|
||||
- [ ] Seguimiento de las peticiones de Objenious, por cada peticion hay qye hacer un seguimiento del request y de los mass action para saber si las activaciones han tenido exito. Habria que crear otra cola para consultar cada x tiempo o mejor un cron?
|
||||
- [ ] Actualizar en la base de datos el estado de las peticiones de las sim y añadir el número de telefono cuando se activen o cuando se cumpla una accion.
|
||||
- [x] Método para sacar la compañía a partir del iccid, o buscar en la BDD si no es posible. _De momento es un objeto Map en el servicio de gateway_
|
||||
- [ ] Cola de mensajes que no se han podido procesar. Distinguir según error de red; se reintenta; o error del propio mensaje; se envía a la cola de errores. v2 Se ha creado una cola de delay pero no se distingue el tipo de error, después de n reintentos el mensaje va a la cola de dead-letter.
|
||||
- [x] Seguimiento de las peticiones de Objenious, por cada peticion hay qye hacer un seguimiento del request y de los mass action para saber si las activaciones han tenido exito. Habria que crear otra cola para consultar cada x tiempo o mejor un cron?
|
||||
- [x] Actualizar en la base de datos el estado de las peticiones de las sim y añadir el número de telefono cuando se activen o cuando se cumpla una accion.
|
||||
|
||||
## Versión con consumidores basados en la compañia
|
||||
|
||||
@@ -32,8 +32,15 @@ OBJENIOUS (33)2011a
|
||||
|
||||
## Diagrama de las colas de Rabbitmq
|
||||
|
||||
Actualmente la topologia de las colas consiste en un exchage principal que recibe todos los mensajes y los redistribuye en las colas de cada empresa y a la de logs. Para evitar reintentos de mensajes instantaneos, que podrian ser inutiles si algún servicio se ha caido, se ha añadido una cola de delay que alamcena los mesajes fallidos durante n segundos antes de ser reenviados al exchange principal. Si despues de n reintentos el mensaje sigue fallando se envia a la cola de dead-letter para ser procesado manualmente.
|
||||
Actualmente la topología de las colas consiste en un exchage principal que recibe todos los mensajes y los redistribuye en las colas de cada empresa y a la de logs. Para evitar reintentos de mensajes instantáneos, que podrían ser inútiles si algún servicio se ha caído, se ha añadido una cola de delay que almacena los mensajes fallidos durante n segundos antes de ser reenviados al exchange principal. Si después de n reintentos el mensaje sigue fallando se envía a la cola de dead-letter para ser procesado manualmente.
|
||||
|
||||

|
||||
|
||||
La decisión del numero de reintentos y la cola de dlx se hace en los servicios, con una configuracion global en shared.
|
||||
La decisión del numero de reintentos y la cola de dlx se hace en los servicios, con una configuración global en shared.
|
||||
|
||||
## Puertos internos para comunicaciones entre sub-servicios
|
||||
|
||||
- **3000**: Gateway (sim-entrada-eventos)
|
||||
- **3001**: Consumidor NOS (sim-consumidor-nos)
|
||||
- **3002**: Consumidor Objenious (sim-consumidor-objenious)
|
||||
- **3003**: Consumidor Alai (sim-consumidor-alai)
|
||||
|
||||
@@ -24,7 +24,6 @@ CREATE TABLE IF NOT EXISTS order_tracking (
|
||||
payload JSONB, -- Duda si es optimo guardar la copia, es útil en caso de fallo
|
||||
|
||||
-- Campos de reintentos?
|
||||
|
||||
status order_status NOT NULL DEFAULT 'pending',
|
||||
retry_count INT DEFAULT 0,
|
||||
error_message TEXT, -- Razón del fallo
|
||||
|
||||
@@ -6,6 +6,8 @@ networks:
|
||||
external: true
|
||||
internal:
|
||||
driver: bridge
|
||||
volumes:
|
||||
rabbitmq_data:
|
||||
|
||||
services:
|
||||
rabbitmq-sim-broker:
|
||||
@@ -28,6 +30,7 @@ services:
|
||||
entrypoint: ["bash", "/usr/local/bin/docker-entrypoint-wrapper.sh"]
|
||||
command: ["rabbitmq-server"]
|
||||
volumes:
|
||||
- rabbitmq_data:/var/lib/rabbitmq
|
||||
- ./rabbit/docker-entrypoint-wrapper.sh:/usr/local/bin/docker-entrypoint-wrapper.sh:ro
|
||||
- ./rabbitmq_plugins/enabled_plugins:/etc/rabbitmq/enabled_plugins:ro
|
||||
- ./rabbit/rabbitmq.conf:/etc/rabbitmq/rabbitmq.conf:ro
|
||||
@@ -72,7 +75,10 @@ services:
|
||||
- ${PORT}
|
||||
volumes:
|
||||
- ./.env:/home/node/app/.env:ro
|
||||
- ./sim-consumidor-nos.env:/home/node/app/packages/sim-consumidor-nos/.env:ro
|
||||
- ./sim-consumidor-alai.env:/home/node/app/packages/sim-consumidor-alai/.env:ro
|
||||
- ./sim-consumidor-objenious.env:/home/node/app/packages/sim-consumidor-objenious/.env:ro
|
||||
- ./wsaccess_alaisecure_com_cert_client_new.p12:/home/node/app/packages/sim-consumidor-alai/certificates/wsaccess_alaisecure_com_cert_client_new.p12:ro
|
||||
- ./sim-objenious-cron.env:/home/node/app/packages/sim-objenious-cron/.env:ro
|
||||
- ./obj.pem:/home/node/app/packages/sim-consumidor-objenious/obj.pem:ro
|
||||
- ./obj.pem:/home/node/app/packages/sim-objenious-cron/obj.pem:ro
|
||||
|
||||
@@ -23,7 +23,7 @@ pipeline {
|
||||
stage("🧱 Building") {
|
||||
steps {
|
||||
sh 'rm -rf dist/'
|
||||
sh 'yarn run build'
|
||||
sh 'yarn run build:prod'
|
||||
}
|
||||
}
|
||||
stage("🏗 Deploying") {
|
||||
@@ -38,14 +38,40 @@ pipeline {
|
||||
cleanRemote: false,
|
||||
execCommand: "mkdir -p $APP_REMOTE_PATH"
|
||||
),
|
||||
sshTransfer(
|
||||
cleanRemote: false,
|
||||
execCommand: "rm -rf $APP_REMOTE_PATH/dist"
|
||||
),
|
||||
sshTransfer(
|
||||
cleanRemote: false,
|
||||
execCommand: "ls -la $BASE_REMOTE_PATH/vault/savefamily/sf-sims/"
|
||||
),
|
||||
sshTransfer(
|
||||
cleanRemote: false,
|
||||
remoteDirectory: "$APP_REMOTE_PATH",
|
||||
sourceFiles: "dist/**/*",
|
||||
excludes: "dist/**/node_modules/**"
|
||||
),
|
||||
sshTransfer(
|
||||
cleanRemote: false,
|
||||
execCommand: "ln -sf $BASE_REMOTE_PATH/vault/savefamily/sf-sims/.env $APP_REMOTE_PATH/.env"
|
||||
),
|
||||
sshTransfer(
|
||||
cleanRemote: false,
|
||||
execCommand: "ln -sf $BASE_REMOTE_PATH/vault/savefamily/sf-sims/wsaccess_alaisecure_com_cert_client_new.p12 $APP_REMOTE_PATH/wsaccess_alaisecure_com_cert_client_new.p12"
|
||||
),
|
||||
sshTransfer(
|
||||
cleanRemote: false,
|
||||
execCommand: "ln -sf $BASE_REMOTE_PATH/vault/savefamily/sf-sims/sim-consumidor-objenious.env $APP_REMOTE_PATH/sim-consumidor-objenious.env"
|
||||
),
|
||||
sshTransfer(
|
||||
cleanRemote: false,
|
||||
execCommand: "ln -sf $BASE_REMOTE_PATH/vault/savefamily/sf-sims/sim-consumidor-nos.env $APP_REMOTE_PATH/sim-consumidor-nos.env"
|
||||
),
|
||||
sshTransfer(
|
||||
cleanRemote: false,
|
||||
execCommand: "ln -sf $BASE_REMOTE_PATH/vault/savefamily/sf-sims/sim-consumidor-alai.env $APP_REMOTE_PATH/sim-consumidor-alai.env"
|
||||
),
|
||||
sshTransfer(
|
||||
cleanRemote: false,
|
||||
execCommand: "ln -sf $BASE_REMOTE_PATH/vault/savefamily/sf-sims/sim-objenious-cron.env $APP_REMOTE_PATH/sim-objenious-cron.env"
|
||||
@@ -54,12 +80,6 @@ pipeline {
|
||||
cleanRemote: false,
|
||||
execCommand: "ln -sf $BASE_REMOTE_PATH/vault/savefamily/sf-sims/obj.pem $APP_REMOTE_PATH/obj.pem"
|
||||
),
|
||||
sshTransfer(
|
||||
cleanRemote: false,
|
||||
remoteDirectory: "$APP_REMOTE_PATH",
|
||||
sourceFiles: "dist/**/*",
|
||||
excludes: "dist/**/node_modules/**"
|
||||
),
|
||||
sshTransfer(
|
||||
cleanRemote: false,
|
||||
remoteDirectory: "$APP_REMOTE_PATH",
|
||||
|
||||
@@ -1,90 +1,92 @@
|
||||
{
|
||||
"rabbit_version": "4.2.2",
|
||||
"rabbitmq_version": "4.2.2",
|
||||
"product_name": "RabbitMQ",
|
||||
"product_version": "4.2.2",
|
||||
"users": [
|
||||
{
|
||||
"name": "RABBITMQ_USER_PLACEHOLDER",
|
||||
"password": "RABBITMQ_PASSWORD_PLACEHOLDER",
|
||||
"tags": ["administrator"]
|
||||
}
|
||||
],
|
||||
"vhosts": [
|
||||
{
|
||||
"name": "sim-vhost"
|
||||
}
|
||||
],
|
||||
"permissions": [
|
||||
{
|
||||
"user": "RABBITMQ_USER_PLACEHOLDER",
|
||||
"vhost": "sim-vhost",
|
||||
"configure": ".*",
|
||||
"write": ".*",
|
||||
"read": ".*"
|
||||
}
|
||||
],
|
||||
"topic_permissions": [],
|
||||
"parameters": [],
|
||||
"global_parameters": [
|
||||
{
|
||||
"name": "cluster_name",
|
||||
"value": "rabbit@a8d5c6e08439"
|
||||
},
|
||||
{
|
||||
"name": "internal_cluster_id",
|
||||
"value": "rabbitmq-cluster-id-gXeBLbsUC2W2tU0Bx_QY_w"
|
||||
}
|
||||
],
|
||||
"policies": [
|
||||
{
|
||||
"vhost": "sim-vhost",
|
||||
"name": "pol.sim.dlx",
|
||||
"pattern": "sim.*",
|
||||
"apply-to": "queues",
|
||||
"definition": {
|
||||
"dead-letter-exchange": "sim.dlx"
|
||||
},
|
||||
"priority": 7
|
||||
}
|
||||
],
|
||||
"exchanges": [
|
||||
{
|
||||
"name": "sim.exchange",
|
||||
"vhost": "sim-vhost",
|
||||
"type": "topic",
|
||||
"durable": true,
|
||||
"auto_delete": false,
|
||||
"internal": false,
|
||||
"argurments": {}
|
||||
},
|
||||
{
|
||||
"name": "sim.dlx",
|
||||
"vhost": "sim-vhost",
|
||||
"type": "topic",
|
||||
"durable": true,
|
||||
"auto_delete": false,
|
||||
"internal": false,
|
||||
"argurments": {}
|
||||
}
|
||||
],
|
||||
"queues": [
|
||||
{
|
||||
"name": "sim.logs",
|
||||
"vhost": "sim-vhost",
|
||||
"durable": true,
|
||||
"auto_delete": false,
|
||||
"arguments": {}
|
||||
}
|
||||
],
|
||||
"bindings": [
|
||||
{
|
||||
"source": "sim.exchange",
|
||||
"vhost": "sim-vhost",
|
||||
"destination": "sim.logs",
|
||||
"destination_type": "queue",
|
||||
"routing_key": "sim.#",
|
||||
"arguments": {}
|
||||
}
|
||||
]
|
||||
"rabbit_version": "4.2.2",
|
||||
"rabbitmq_version": "4.2.2",
|
||||
"product_name": "RabbitMQ",
|
||||
"product_version": "4.2.2",
|
||||
"users": [
|
||||
{
|
||||
"name": "RABBITMQ_USER_PLACEHOLDER",
|
||||
"password": "RABBITMQ_PASSWORD_PLACEHOLDER",
|
||||
"tags": [
|
||||
"administrator"
|
||||
]
|
||||
}
|
||||
],
|
||||
"vhosts": [
|
||||
{
|
||||
"name": "sim-vhost"
|
||||
}
|
||||
],
|
||||
"permissions": [
|
||||
{
|
||||
"user": "RABBITMQ_USER_PLACEHOLDER",
|
||||
"vhost": "sim-vhost",
|
||||
"configure": ".*",
|
||||
"write": ".*",
|
||||
"read": ".*"
|
||||
}
|
||||
],
|
||||
"topic_permissions": [],
|
||||
"parameters": [],
|
||||
"global_parameters": [
|
||||
{
|
||||
"name": "cluster_name",
|
||||
"value": "rabbit@a8d5c6e08439"
|
||||
},
|
||||
{
|
||||
"name": "internal_cluster_id",
|
||||
"value": "rabbitmq-cluster-id-gXeBLbsUC2W2tU0Bx_QY_w"
|
||||
}
|
||||
],
|
||||
"policies": [
|
||||
{
|
||||
"vhost": "sim-vhost",
|
||||
"name": "pol.sim.dlx",
|
||||
"pattern": "sim.*",
|
||||
"apply-to": "queues",
|
||||
"definition": {
|
||||
"dead-letter-exchange": "sim.dlx"
|
||||
},
|
||||
"priority": 7
|
||||
}
|
||||
],
|
||||
"exchanges": [
|
||||
{
|
||||
"name": "sim.exchange",
|
||||
"vhost": "sim-vhost",
|
||||
"type": "topic",
|
||||
"durable": true,
|
||||
"auto_delete": false,
|
||||
"internal": false,
|
||||
"argurments": {}
|
||||
},
|
||||
{
|
||||
"name": "sim.dlx",
|
||||
"vhost": "sim-vhost",
|
||||
"type": "topic",
|
||||
"durable": true,
|
||||
"auto_delete": false,
|
||||
"internal": false,
|
||||
"argurments": {}
|
||||
}
|
||||
],
|
||||
"queues": [
|
||||
{
|
||||
"name": "sim.logs",
|
||||
"vhost": "sim-vhost",
|
||||
"durable": true,
|
||||
"auto_delete": false,
|
||||
"arguments": {}
|
||||
}
|
||||
],
|
||||
"bindings": [
|
||||
{
|
||||
"source": "sim.exchange",
|
||||
"vhost": "sim-vhost",
|
||||
"destination": "sim.logs",
|
||||
"destination_type": "queue",
|
||||
"routing_key": "sim.#",
|
||||
"arguments": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -7,6 +7,7 @@ networks:
|
||||
services:
|
||||
rabbitmq-sim-broker:
|
||||
container_name: rabbitmq-sim-broker
|
||||
hostname: rabbitmq-sim
|
||||
image: "rabbitmq:4.2.2-management"
|
||||
ports:
|
||||
- "5672:5672"
|
||||
@@ -23,6 +24,7 @@ services:
|
||||
RABBITMQ_DEFAULT_USER: ${RABBITMQ_USER}
|
||||
RABBITMQ_DEFAULT_PASS: ${RABBITMQ_PASSWORD}
|
||||
volumes:
|
||||
- ./rabbitmq-data/:/var/lib/rabbitmq/
|
||||
- ./rabbitmq_plugins/enabled_plugins:/etc/rabbitmq/enabled_plugins:ro
|
||||
- ./deployment/local/rabbit/rabbitmq.conf:/etc/rabbitmq/rabbitmq.conf:ro
|
||||
- ./deployment/local/rabbit/definitions.json:/etc/rabbitmq/definitions.json:ro
|
||||
|
||||
32
docs/sim-alai/Change External ID.yml
Normal file
32
docs/sim-alai/Change External ID.yml
Normal file
@@ -0,0 +1,32 @@
|
||||
info:
|
||||
name: Change External ID
|
||||
type: http
|
||||
seq: 7
|
||||
|
||||
http:
|
||||
method: GET
|
||||
url: "{{baseurl}}/v1/subscription/{{subscription}}?action=MODIFY"
|
||||
params:
|
||||
- name: action
|
||||
value: MODIFY
|
||||
type: query
|
||||
body:
|
||||
type: json
|
||||
data: |-
|
||||
{
|
||||
"externalID":""
|
||||
}
|
||||
auth:
|
||||
type: bearer
|
||||
token: "{{alai_token}}"
|
||||
|
||||
runtime:
|
||||
variables:
|
||||
- name: subscription
|
||||
value: asdasdasd
|
||||
|
||||
settings:
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
followRedirects: true
|
||||
maxRedirects: 5
|
||||
22
docs/sim-alai/IMEI of subscription.yml
Normal file
22
docs/sim-alai/IMEI of subscription.yml
Normal file
@@ -0,0 +1,22 @@
|
||||
info:
|
||||
name: IMEI of subscription
|
||||
type: http
|
||||
seq: 5
|
||||
|
||||
http:
|
||||
method: GET
|
||||
url: "{{baseurl}}/v1/subscription/{{subscription}}/imei"
|
||||
auth:
|
||||
type: bearer
|
||||
token: "{{alai_token}}"
|
||||
|
||||
runtime:
|
||||
variables:
|
||||
- name: subscription
|
||||
value: SID1848557_TS1766417781101_0
|
||||
|
||||
settings:
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
followRedirects: true
|
||||
maxRedirects: 5
|
||||
51
docs/sim-alai/Login.yml
Normal file
51
docs/sim-alai/Login.yml
Normal file
@@ -0,0 +1,51 @@
|
||||
info:
|
||||
name: Login
|
||||
type: http
|
||||
seq: 2
|
||||
|
||||
http:
|
||||
method: POST
|
||||
url: "{{baseurl}}/v1/auth/login"
|
||||
body:
|
||||
type: json
|
||||
data: |-
|
||||
{
|
||||
"username": "{{username}}",
|
||||
"password": "{{password}}",
|
||||
"brandID": "{{brandId}}"
|
||||
}
|
||||
auth: inherit
|
||||
|
||||
runtime:
|
||||
scripts:
|
||||
- type: after-response
|
||||
code: |-
|
||||
const data = res.getBody();
|
||||
|
||||
if (data.status != 200) {
|
||||
console.error("Error de login: ", data)
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (data && data.accessToken) {
|
||||
|
||||
bru.setEnvVar("alai_token", data.accessToken);
|
||||
|
||||
if (data.tokenType) {
|
||||
bru.setEnvVar("alai_token_type", data.tokenType);
|
||||
}
|
||||
|
||||
console.log("Token guardado correctamente");
|
||||
} else {
|
||||
console.error("No se pudo encontrar el accessToken en la respuesta");
|
||||
}
|
||||
|
||||
settings:
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
followRedirects: true
|
||||
maxRedirects: 5
|
||||
|
||||
docs: |-
|
||||
Necesita un certificado p12 (PFX) y la contraseña asociada para efectuar la operacion.
|
||||
Collection Settings => ClientCertificates => Add Certificate
|
||||
16
docs/sim-alai/New Order.yml
Normal file
16
docs/sim-alai/New Order.yml
Normal file
@@ -0,0 +1,16 @@
|
||||
info:
|
||||
name: New Order
|
||||
type: http
|
||||
seq: 3
|
||||
|
||||
http:
|
||||
method: POST
|
||||
url: "{{baseurl}}/v1/order"
|
||||
auth:
|
||||
type: bearer
|
||||
|
||||
settings:
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
followRedirects: true
|
||||
maxRedirects: 5
|
||||
22
docs/sim-alai/SIM.yml
Normal file
22
docs/sim-alai/SIM.yml
Normal file
@@ -0,0 +1,22 @@
|
||||
info:
|
||||
name: SIM
|
||||
type: http
|
||||
seq: 4
|
||||
|
||||
http:
|
||||
method: GET
|
||||
url: "{{baseurl}}/v1/sim/{{iccid}}"
|
||||
auth:
|
||||
type: bearer
|
||||
token: "{{alai_token}}"
|
||||
|
||||
runtime:
|
||||
variables:
|
||||
- name: iccid
|
||||
value: "8934909001500561503"
|
||||
|
||||
settings:
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
followRedirects: true
|
||||
maxRedirects: 5
|
||||
22
docs/sim-alai/Subscription.yml
Normal file
22
docs/sim-alai/Subscription.yml
Normal file
@@ -0,0 +1,22 @@
|
||||
info:
|
||||
name: Subscription
|
||||
type: http
|
||||
seq: 4
|
||||
|
||||
http:
|
||||
method: GET
|
||||
url: "{{baseurl}}/v1/subscription/{{subscription}}"
|
||||
auth:
|
||||
type: bearer
|
||||
token: "{{alai_token}}"
|
||||
|
||||
runtime:
|
||||
variables:
|
||||
- name: subscription
|
||||
value: SID1776275_TS1759238704226_0
|
||||
|
||||
settings:
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
followRedirects: true
|
||||
maxRedirects: 5
|
||||
BIN
docs/sim-alai/certificates/alai_cert.p12
Normal file
BIN
docs/sim-alai/certificates/alai_cert.p12
Normal file
Binary file not shown.
7
docs/sim-alai/environments/local.yml
Normal file
7
docs/sim-alai/environments/local.yml
Normal file
@@ -0,0 +1,7 @@
|
||||
name: local
|
||||
color: "#2E8A54"
|
||||
variables:
|
||||
- name: baseurl
|
||||
value: http://localhost:3002
|
||||
- secret: true
|
||||
name: token
|
||||
15
docs/sim-alai/environments/prod.yml
Normal file
15
docs/sim-alai/environments/prod.yml
Normal file
@@ -0,0 +1,15 @@
|
||||
name: prod
|
||||
color: "#CE4F3B"
|
||||
variables:
|
||||
- name: baseurl
|
||||
value: https://wsaccess.alaisecure.com/bssrest
|
||||
- name: username
|
||||
value: palomaibanez
|
||||
- name: password
|
||||
value: palomaibanez123
|
||||
- secret: true
|
||||
name: certPasswd
|
||||
- name: brandId
|
||||
value: savefamily
|
||||
- name: alai_token
|
||||
value: eyJhbGciOiJIUzM4NCJ9.eyJiciI6InNhdmVmYW1pbHkiLCJpcCI6Ijg4LjE1LjE1Ny4xNjciLCJzdWIiOiJwYWxvbWFpYmFuZXoiLCJzIjoiRVdTMTY0YWJhYWRlNjA3ZDAyIiwicG9zIjoic2F2ZWZhbWlseUNhYyIsImlkV3NVc2VyIjoiODYiLCJpc012bmEiOmZhbHNlLCJkb21haW4iOiJBbGFpfHNhdmVmYW1pbHkiLCJpYXQiOjE3NzgxNTEzMzYsImV4cCI6MTc3ODE2MjEzNn0.zCFBJJsa0Krc7n5vUFF00z9Tq7m0dRlCGzs2Od67jaLCCn-mnIyyU424PkazacRW
|
||||
26
docs/sim-alai/opencollection.yml
Normal file
26
docs/sim-alai/opencollection.yml
Normal file
@@ -0,0 +1,26 @@
|
||||
opencollection: 1.0.0
|
||||
|
||||
info:
|
||||
name: sim-alai
|
||||
config:
|
||||
proxy:
|
||||
inherit: true
|
||||
config:
|
||||
protocol: http
|
||||
hostname: ""
|
||||
port: ""
|
||||
auth:
|
||||
username: ""
|
||||
password: ""
|
||||
bypassProxy: ""
|
||||
clientCertificates:
|
||||
- domain: wsaccess.alaisecure.com
|
||||
type: pkcs12
|
||||
pkcs12FilePath: certificates\alai_cert.p12
|
||||
passphrase: iHaaek+zyzWz6cH6rg==
|
||||
bundled: false
|
||||
extensions:
|
||||
bruno:
|
||||
ignore:
|
||||
- node_modules
|
||||
- .git
|
||||
File diff suppressed because one or more lines are too long
@@ -6,16 +6,80 @@ meta {
|
||||
|
||||
post {
|
||||
url: {{baseurl}}/sim/activate
|
||||
body: formUrlEncoded
|
||||
body: json
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"iccid": "8934909001500561503"
|
||||
}
|
||||
}
|
||||
|
||||
body:form-urlencoded {
|
||||
iccid: 8935103196306448300
|
||||
offer: SAVEFAMILY1
|
||||
iccid: 123
|
||||
offer: mensual
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
}
|
||||
|
||||
docs {
|
||||
Campos de entrada:
|
||||
```ts
|
||||
// Header requerido
|
||||
// > content-type:application/x-www-form-urlencoded
|
||||
// > content-type:application/json
|
||||
// Cualquiera de los 2 es valido
|
||||
|
||||
// Esquema body
|
||||
{
|
||||
iccid: string,
|
||||
offer: "mensual" | "anual" | "SAVEFAMILY1" | "SAVEFAMILY2"
|
||||
webhook?: string,
|
||||
}
|
||||
```
|
||||
|
||||
En el campo `offer` "mensual" equivale a "SAVEFAMILY2" y "anual" a "SAVEFAMILY1" porque se mantien los códigos de Oferta de Objenious por compatibilidad pero se espera usar "mensual" y "anual" y hacer la conversión en el servicio de cada proveedor.
|
||||
|
||||
Para las llamadas al webhook se va a usar siempre el metodo `POST`, ahora mismo no se firman los mensajes. Se introduce la URL completa tal que `https://dominion.com/v1/endpoint`.
|
||||
|
||||
Respuestas:
|
||||
- **200**: OK
|
||||
``` ts
|
||||
// Esquema
|
||||
{
|
||||
iccid: string,
|
||||
operation: string,
|
||||
message_id: string, //uuidv7
|
||||
}
|
||||
```
|
||||
``` json
|
||||
// Ejemplo
|
||||
{
|
||||
"iccid": "89332011250651xxxxx",
|
||||
"operation": "activation",
|
||||
"message_id": "019dbeaf-8abb-7783-8b51-94fbd9f0b0df"
|
||||
}
|
||||
```
|
||||
|
||||
*iccid*: Confirmación del iccid enviado.
|
||||
*operation*: Confirmación de la operacion que se ha aplicado.
|
||||
*message_id*: Id de la operación, para consultar en orders.
|
||||
|
||||
> A futuro se va a incluir un campo `"ref":[]` para añadir los enlaces a las consultas de la operación. El body va a permitir tambien json.
|
||||
|
||||
- **402**: Algún campo es incorrecto
|
||||
Se indica que campo es incorrecto, si hubiese mas de uno solo aparecería el primero en comprobarse.
|
||||
```json
|
||||
"errors": {
|
||||
"msg": "La longitud del iccid es incorrecta debera ser de 19 caracteres",
|
||||
"field": "iccid"
|
||||
}
|
||||
```
|
||||
|
||||
- **500**: Error general
|
||||
Ha ocurrido un error imprevisto durante la
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
meta {
|
||||
name: Activation Email Health
|
||||
type: http
|
||||
seq: 8
|
||||
seq: 9
|
||||
}
|
||||
|
||||
post {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
meta {
|
||||
name: Activation Email
|
||||
type: http
|
||||
seq: 6
|
||||
seq: 8
|
||||
}
|
||||
|
||||
post {
|
||||
|
||||
20
docs/sim-api/Alai/Preactivate.bru
Normal file
20
docs/sim-api/Alai/Preactivate.bru
Normal file
@@ -0,0 +1,20 @@
|
||||
meta {
|
||||
name: Preactivate
|
||||
type: http
|
||||
seq: 2
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{baseAlai}}/preactivate?iccid=8934909001400027654
|
||||
body: none
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
params:query {
|
||||
iccid: 8934909001400027654
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
}
|
||||
20
docs/sim-api/Alai/Select SIM.bru
Normal file
20
docs/sim-api/Alai/Select SIM.bru
Normal file
@@ -0,0 +1,20 @@
|
||||
meta {
|
||||
name: Select SIM
|
||||
type: http
|
||||
seq: 1
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{baseAlai}}/select/?iccid=8934909001500561503
|
||||
body: none
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
params:query {
|
||||
iccid: 8934909001500561503
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
}
|
||||
8
docs/sim-api/Alai/folder.bru
Normal file
8
docs/sim-api/Alai/folder.bru
Normal file
@@ -0,0 +1,8 @@
|
||||
meta {
|
||||
name: Alai
|
||||
seq: 14
|
||||
}
|
||||
|
||||
auth {
|
||||
mode: inherit
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
meta {
|
||||
name: Cancel
|
||||
type: http
|
||||
seq: 1
|
||||
seq: 4
|
||||
}
|
||||
|
||||
post {
|
||||
@@ -11,7 +11,7 @@ post {
|
||||
}
|
||||
|
||||
body:form-urlencoded {
|
||||
iccid: 8933201125068887054
|
||||
iccid: 8933201125068889894
|
||||
}
|
||||
|
||||
settings {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
meta {
|
||||
name: Docs
|
||||
type: http
|
||||
seq: 12
|
||||
seq: 11
|
||||
}
|
||||
|
||||
get {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
meta {
|
||||
name: Health
|
||||
type: http
|
||||
seq: 5
|
||||
seq: 7
|
||||
}
|
||||
|
||||
get {
|
||||
|
||||
16
docs/sim-api/Nos/Select.bru
Normal file
16
docs/sim-api/Nos/Select.bru
Normal file
@@ -0,0 +1,16 @@
|
||||
meta {
|
||||
name: Select
|
||||
type: http
|
||||
seq: 1
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{baseurl}}/portugal/select
|
||||
body: none
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
}
|
||||
8
docs/sim-api/Nos/folder.bru
Normal file
8
docs/sim-api/Nos/folder.bru
Normal file
@@ -0,0 +1,8 @@
|
||||
meta {
|
||||
name: Nos
|
||||
seq: 15
|
||||
}
|
||||
|
||||
auth {
|
||||
mode: inherit
|
||||
}
|
||||
26
docs/sim-api/Objenious/France Suspended Lines.bru
Normal file
26
docs/sim-api/Objenious/France Suspended Lines.bru
Normal file
@@ -0,0 +1,26 @@
|
||||
meta {
|
||||
name: France Suspended Lines
|
||||
type: http
|
||||
seq: 16
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{baseurl}}/france/lines?status=SUSPENDED&limit=100
|
||||
body: none
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
params:query {
|
||||
status: SUSPENDED
|
||||
limit: 100
|
||||
}
|
||||
|
||||
vars:pre-request {
|
||||
iccid: 8933201125065160331
|
||||
~baseurl: http://localhost:3002
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
}
|
||||
21
docs/sim-api/Objenious/France Suspended Time.bru
Normal file
21
docs/sim-api/Objenious/France Suspended Time.bru
Normal file
@@ -0,0 +1,21 @@
|
||||
meta {
|
||||
name: France Suspended Time
|
||||
type: http
|
||||
seq: 15
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{baseurl}}/france/lines/{{iccid}}/suspended-time
|
||||
body: none
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
vars:pre-request {
|
||||
iccid: 8933201125065160331
|
||||
~baseurl: http://localhost:3002
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
}
|
||||
8
docs/sim-api/Objenious/folder.bru
Normal file
8
docs/sim-api/Objenious/folder.bru
Normal file
@@ -0,0 +1,8 @@
|
||||
meta {
|
||||
name: Objenious
|
||||
seq: 16
|
||||
}
|
||||
|
||||
auth {
|
||||
mode: inherit
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
meta {
|
||||
name: Get pending orders
|
||||
type: http
|
||||
seq: 11
|
||||
seq: 10
|
||||
}
|
||||
|
||||
get {
|
||||
@@ -5,7 +5,7 @@ meta {
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{baseurl}}/orders/
|
||||
url: {{baseurl}}/orders/019dbeaf-8abb-7783-8b51-94fbd9f0b0df
|
||||
body: none
|
||||
auth: inherit
|
||||
}
|
||||
@@ -5,15 +5,11 @@ meta {
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{baseurl}}/orders/message_id/019c93d3-014a-711d-b958-03dd629be78d
|
||||
url: {{baseurl}}/orders/message_id/019dbeaf-8abb-7783-8b51-94fbd9f0b0df
|
||||
body: none
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
params:query {
|
||||
~message_id: 019c93d3-014a-711d-b958-03dd629be78d
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
45
docs/sim-api/Orders/folder.bru
Normal file
45
docs/sim-api/Orders/folder.bru
Normal file
@@ -0,0 +1,45 @@
|
||||
meta {
|
||||
name: Orders
|
||||
seq: 3
|
||||
}
|
||||
|
||||
auth {
|
||||
mode: inherit
|
||||
}
|
||||
|
||||
docs {
|
||||
# Orders
|
||||
|
||||
Los *order* representan ordenes que se hacen al servidor y representan en que estado se encuentran las peticiones. Los *order* se generan cuando se solicita una operacion y devuelven su identificador en el campo `message_id` de todas las respuestas a peticiones que requieran cambios. Los identificadores de `order` son UUIDv7, aunque tambien tienen asociado un id tradicional BIGINT en la BDD.
|
||||
|
||||
## Ciclo de vida
|
||||
|
||||
Cuando se crea un *order* comienza en estado `pending`, inicando que ha entrado en la cola y está pendiente de iniciarse; una vez se ha consumido por un servicio pasa a estado `running` indicando que la operacion asociada al *order* ha comenzado, el order continuara en este estado durante un tiempo indefinido (pueden pasar semanas para algunos casos), hasta que la tara finalize correctamente o con errores. En el caso que la tarea finalize con éxito el *order* pasará a estado `finished`, en caso de que haya habido un error el estado será `failed` y se almacenará el error en los campos `error_message` y opcionalemente en `error_stacktrace` según gravedad del error.
|
||||
|
||||
- Caso normal
|
||||
`pending` -> `running` -> `finished`
|
||||
|
||||
- Error durante el consumo
|
||||
`pending` -> `failed`
|
||||
|
||||
- Error durante la operacion
|
||||
`pending` -> `running` -> `failed`
|
||||
|
||||
## Endpoints
|
||||
Estan sujetos a cambios en cuanto a mostrar información
|
||||
|
||||
- [WIP]**GET** /orders?{query}
|
||||
Devuelve todos los orders con un campo que tenga el valor especificado en la query
|
||||
|
||||
- **GET** /orders/{id}
|
||||
Devuelve el order objetivo según su UUID de mensaje (No según el uuid de mensaje)
|
||||
|
||||
- **GET** /orders/base_id/{id}
|
||||
Devuelve el id según su id de la bdd, no es el metodo normal de usar la api
|
||||
|
||||
- **GET** /orders/pending
|
||||
Devuelve todas las order que no hayan finalizado
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
meta {
|
||||
name: Pause
|
||||
type: http
|
||||
seq: 1
|
||||
seq: 5
|
||||
}
|
||||
|
||||
post {
|
||||
@@ -15,7 +15,7 @@ params:query {
|
||||
}
|
||||
|
||||
body:form-urlencoded {
|
||||
iccid: 8935103196306448300
|
||||
iccid: 8933201125065160331
|
||||
}
|
||||
|
||||
settings {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
meta {
|
||||
name: Preactivate
|
||||
type: http
|
||||
seq: 1
|
||||
seq: 6
|
||||
}
|
||||
|
||||
post {
|
||||
@@ -15,7 +15,9 @@ params:query {
|
||||
}
|
||||
|
||||
body:form-urlencoded {
|
||||
iccid: 8933201125065160380
|
||||
iccid: 8934909001500954922
|
||||
offer: mensual
|
||||
orderId: test
|
||||
}
|
||||
|
||||
settings {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
meta {
|
||||
name: ReActivate
|
||||
type: http
|
||||
seq: 13
|
||||
seq: 12
|
||||
}
|
||||
|
||||
post {
|
||||
@@ -11,7 +11,7 @@ post {
|
||||
}
|
||||
|
||||
body:form-urlencoded {
|
||||
iccid: 8935103196306448300
|
||||
iccid: 8934909001500561503
|
||||
~offer: SAVEFAMILY1
|
||||
}
|
||||
|
||||
|
||||
20
docs/sim-api/Select.bru
Normal file
20
docs/sim-api/Select.bru
Normal file
@@ -0,0 +1,20 @@
|
||||
meta {
|
||||
name: Select
|
||||
type: http
|
||||
seq: 13
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{baseurl}}/sim/select?iccid=8935103196306448300
|
||||
body: none
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
params:query {
|
||||
iccid: 8935103196306448300
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
meta {
|
||||
name: Test Order
|
||||
type: http
|
||||
seq: 9
|
||||
seq: 10
|
||||
}
|
||||
|
||||
post {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
docs {
|
||||
Los endpoint tienen unos campos comunes de entrada:
|
||||
Todos los endpoint tienen unos campos comunes de entrada:
|
||||
|
||||
```ts
|
||||
{
|
||||
iccid: string,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
vars {
|
||||
baseurl: http://localhost:3000
|
||||
baseAlai: http://localhost:3002
|
||||
}
|
||||
color: #2E8A54
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
meta {
|
||||
name: test proxy
|
||||
type: http
|
||||
seq: 14
|
||||
seq: 13
|
||||
}
|
||||
|
||||
get {
|
||||
|
||||
24
docs/sim-objenious/Alarms/Alarm by id.bru
Normal file
24
docs/sim-objenious/Alarms/Alarm by id.bru
Normal file
@@ -0,0 +1,24 @@
|
||||
meta {
|
||||
name: Alarm by id
|
||||
type: http
|
||||
seq: 2
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{baseUrl}}alarms/{{alarmId}}
|
||||
body: none
|
||||
auth: bearer
|
||||
}
|
||||
|
||||
auth:bearer {
|
||||
token: {{ws-access-token-partenaire}}
|
||||
}
|
||||
|
||||
vars:pre-request {
|
||||
alarmId: 2439
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
}
|
||||
28
docs/sim-objenious/Alarms/Alerts.bru
Normal file
28
docs/sim-objenious/Alarms/Alerts.bru
Normal file
@@ -0,0 +1,28 @@
|
||||
meta {
|
||||
name: Alerts
|
||||
type: http
|
||||
seq: 3
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{baseUrl}}alarms/alerts?pageNumber=100
|
||||
body: none
|
||||
auth: bearer
|
||||
}
|
||||
|
||||
params:query {
|
||||
pageNumber: 100
|
||||
}
|
||||
|
||||
auth:bearer {
|
||||
token: {{ws-access-token-partenaire}}
|
||||
}
|
||||
|
||||
vars:pre-request {
|
||||
alarmId: 2439
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
}
|
||||
20
docs/sim-objenious/Alarms/All Alarms.bru
Normal file
20
docs/sim-objenious/Alarms/All Alarms.bru
Normal file
@@ -0,0 +1,20 @@
|
||||
meta {
|
||||
name: All Alarms
|
||||
type: http
|
||||
seq: 1
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{baseUrl}}alarms
|
||||
body: none
|
||||
auth: bearer
|
||||
}
|
||||
|
||||
auth:bearer {
|
||||
token: {{ws-access-token-partenaire}}
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
}
|
||||
8
docs/sim-objenious/Alarms/folder.bru
Normal file
8
docs/sim-objenious/Alarms/folder.bru
Normal file
@@ -0,0 +1,8 @@
|
||||
meta {
|
||||
name: Alarms
|
||||
seq: 21
|
||||
}
|
||||
|
||||
auth {
|
||||
mode: inherit
|
||||
}
|
||||
38
docs/sim-objenious/Alerts.bru
Normal file
38
docs/sim-objenious/Alerts.bru
Normal file
@@ -0,0 +1,38 @@
|
||||
meta {
|
||||
name: Alerts
|
||||
type: http
|
||||
seq: 23
|
||||
}
|
||||
|
||||
get {
|
||||
url: https://api-getway.objenious.com/ws/alarms
|
||||
body: formUrlEncoded
|
||||
auth: bearer
|
||||
}
|
||||
|
||||
auth:bearer {
|
||||
token: {{ws-access-token-partenaire}}
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"identifier": {
|
||||
"identifiers": ["8933201124059175967"],
|
||||
"identifierType": "ICCID"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
body:form-urlencoded {
|
||||
~identifier.identifierType: "ICCID"
|
||||
~identifier.identifiers: ["8933201124059175967"]
|
||||
}
|
||||
|
||||
vars:pre-request {
|
||||
~id: 5187320
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
}
|
||||
@@ -5,16 +5,16 @@ meta {
|
||||
}
|
||||
|
||||
get {
|
||||
url: https://api-getway.objenious.com/ws/lines?pageSize=1000&simStatus=ACTIVATED
|
||||
url: https://api-getway.objenious.com/ws/lines?identifier.identifierType=ICCID&identifier.identifiers=8933201125065160455
|
||||
body: formUrlEncoded
|
||||
auth: bearer
|
||||
}
|
||||
|
||||
params:query {
|
||||
pageSize: 1000
|
||||
simStatus: ACTIVATED
|
||||
~identifier.identifierType: ICCID
|
||||
~identifier.identifiers: 8933201125065160455
|
||||
identifier.identifierType: ICCID
|
||||
identifier.identifiers: 8933201125065160455
|
||||
~pageSize: 1000
|
||||
~simStatus: ACTIVATED
|
||||
}
|
||||
|
||||
auth:bearer {
|
||||
|
||||
38
docs/sim-objenious/Consumption details.bru
Normal file
38
docs/sim-objenious/Consumption details.bru
Normal file
@@ -0,0 +1,38 @@
|
||||
meta {
|
||||
name: Consumption details
|
||||
type: http
|
||||
seq: 21
|
||||
}
|
||||
|
||||
get {
|
||||
url: https://api-getway.objenious.com/ws/diagXL/massHistories
|
||||
body: formUrlEncoded
|
||||
auth: bearer
|
||||
}
|
||||
|
||||
auth:bearer {
|
||||
token: {{ws-access-token-partenaire}}
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"identifier": {
|
||||
"identifiers": ["8933201124059175967"],
|
||||
"identifierType": "ICCID"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
body:form-urlencoded {
|
||||
identifier.identifierType: "ICCID"
|
||||
identifier.identifiers: ["8933201125068889373"]
|
||||
}
|
||||
|
||||
vars:pre-request {
|
||||
~id: 5187320
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
}
|
||||
41
docs/sim-objenious/Line by iccid.bru
Normal file
41
docs/sim-objenious/Line by iccid.bru
Normal file
@@ -0,0 +1,41 @@
|
||||
meta {
|
||||
name: Line by iccid
|
||||
type: http
|
||||
seq: 22
|
||||
}
|
||||
|
||||
get {
|
||||
url: https://api-getway.objenious.com/ws/lines?pageSize=1000&simStatus=ACTIVATED
|
||||
body: formUrlEncoded
|
||||
auth: bearer
|
||||
}
|
||||
|
||||
params:query {
|
||||
pageSize: 1000
|
||||
simStatus: ACTIVATED
|
||||
~identifier.identifierType: ICCID
|
||||
~identifier.identifiers: 8933201125065160455
|
||||
}
|
||||
|
||||
auth:bearer {
|
||||
token: {{ws-access-token-partenaire}}
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"identifier": {
|
||||
"identifiers": ["8933201124059175967"],
|
||||
"identifierType": "ICCID"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
body:form-urlencoded {
|
||||
~identifier.identifierType: "ICCID"
|
||||
~identifier.identifiers: ["8933201124059175967"]
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
}
|
||||
@@ -1,17 +1,18 @@
|
||||
{
|
||||
"name": "sim-eventos",
|
||||
"version": "1.0.0",
|
||||
"packageManager": "yarn@4.12.0",
|
||||
"packageManager": "yarn@4.14.1",
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
],
|
||||
"scripts": {
|
||||
"test": "vitest watch",
|
||||
"build": "rm -rf ./dist && yarn workspaces foreach -Api run build && cp .env dist/ && yarn setup:runtime",
|
||||
"build": "rm -rf ./dist && yarn workspaces foreach -Api run build && yarn setup:runtime",
|
||||
"build:prod": "rm -rf ./dist && yarn workspaces foreach -Api run build:prod && yarn setup:runtime",
|
||||
"setup:runtime": "mkdir -p dist/packages/node_modules && ln -sf ../sim-shared dist/packages/node_modules/sim-shared && ln -sf ../sf-consumidor-objenious dist/packages/node_modules/sim-consumidor-objenious",
|
||||
"start": "yarn setup:runtime && yarn workspaces foreach -Apiv run start",
|
||||
"start": "yarn workspaces foreach -Apiv run start",
|
||||
"typecheck": "npx tsc --noEmit",
|
||||
"dev": "yarn workspaces foreach -Apiv --exclude sim-objenious-cron run dev",
|
||||
"dev": "yarn workspaces foreach -Apiv run dev",
|
||||
"lint": "eslint .",
|
||||
"lint:fix": "eslint --fix .",
|
||||
"format": "prettier --write .",
|
||||
|
||||
1
packages/_template/config/env/index.ts
vendored
1
packages/_template/config/env/index.ts
vendored
@@ -1,5 +1,4 @@
|
||||
export const env = {
|
||||
ENVIRONMENT: process.env.ENVIORMENT,
|
||||
POSTGRES_USER: process.env.POSTGRES_USER,
|
||||
POSTGRES_PASSWORD: process.env.POSTGRES_PASSWORD,
|
||||
POSTGRES_PORT: process.env.POSTGRES_PORT,
|
||||
|
||||
43
packages/sim-consumidor-alai/aplication/AlaiTokenManager.ts
Normal file
43
packages/sim-consumidor-alai/aplication/AlaiTokenManager.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { AlaiRepository } from "#infrastructure/AlaiRepository.js";
|
||||
import { JWTToken } from "sim-shared/domain/JWT.js";
|
||||
import { JWTProvider } from "sim-shared/infrastructure/HTTPClient.js";
|
||||
import { httpsAgent } from "#config/httpsAgent.js";
|
||||
|
||||
export class AlaiTokenManager implements JWTProvider<{}> {
|
||||
|
||||
isRefreshing: boolean = false;
|
||||
authToken: JWTToken<{}> | undefined;
|
||||
|
||||
private async getNewAuthToken() {
|
||||
// TODO: Si no funcionase hay que reprogramar los mensajes para ser
|
||||
// consumidos mas tarde.
|
||||
const res = await AlaiRepository.login(httpsAgent);
|
||||
|
||||
if (res.error != undefined) {
|
||||
console.error("Error obteniendo el token de ALAI", res.error)
|
||||
} else {
|
||||
console.log("Obtenido token de ALAI: ", res)
|
||||
this.authToken = new JWTToken(res.data.accessToken)
|
||||
}
|
||||
}
|
||||
|
||||
public tryRefreshToken(): Promise<JWTToken<{}>> {
|
||||
// En Alai no existe el concepto de refresh, se solicita otro token nuevo
|
||||
return this.getAccessToken()
|
||||
};
|
||||
|
||||
public async getAccessToken(): Promise<JWTToken<{}>> {
|
||||
// Caso 1: El token actual es valido
|
||||
if (this.authToken != undefined && !this.authToken.isExpired()) {
|
||||
return this.authToken
|
||||
} else {
|
||||
// Caso 2: El token actual no existe o ha expirado
|
||||
await this.getNewAuthToken()
|
||||
}
|
||||
|
||||
// Si después de todo no se ha generado el token es un error catastrofico
|
||||
if (this.authToken == undefined) throw new Error("Error obteniendo tokens de auth")
|
||||
|
||||
return this.authToken
|
||||
};
|
||||
}
|
||||
50
packages/sim-consumidor-alai/aplication/DebugTokenManager.ts
Normal file
50
packages/sim-consumidor-alai/aplication/DebugTokenManager.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { JWTToken } from "sim-shared/domain/JWT.js";
|
||||
import { JWTProvider } from "sim-shared/infrastructure/HTTPClient.js";
|
||||
import { LegacyJWTTokenRepository } from "#infrastructure/LegacyJWTTokensRepository.js";
|
||||
import { env } from "#config/env/env.js";
|
||||
|
||||
const tokenDir = String(env.ALAI_CERTIFICATES_DIR)
|
||||
const tokenFile = ".debugToken"
|
||||
|
||||
/**
|
||||
* Usa un token guardado a mano en archivo para no gastar tokens de Alai
|
||||
*/
|
||||
export class DebugTokenManager implements JWTProvider<{}> {
|
||||
|
||||
isRefreshing: boolean = false;
|
||||
authToken: JWTToken<{}> | undefined;
|
||||
|
||||
private async getNewAuthToken() {
|
||||
// TODO: Si no funcionase hay que reprogramar los mensajes para ser
|
||||
// consumidos mas tarde.
|
||||
const res = LegacyJWTTokenRepository.getTokenFromFile(tokenDir, tokenFile)
|
||||
|
||||
|
||||
if (res.error != undefined) {
|
||||
console.error("Error obteniendo el token de ALAI", res.error)
|
||||
} else {
|
||||
this.authToken = new JWTToken(res.data)
|
||||
console.log("[d] Token DEBUG: ", this.authToken)
|
||||
}
|
||||
}
|
||||
|
||||
public tryRefreshToken(): Promise<JWTToken<{}>> {
|
||||
// En Alai no existe el concepto de refresh, se solicita otro token nuevo
|
||||
return this.getAccessToken()
|
||||
};
|
||||
|
||||
public async getAccessToken(): Promise<JWTToken<{}>> {
|
||||
// Caso 1: El token actual es valido
|
||||
if (this.authToken != undefined && !this.authToken.isExpired()) {
|
||||
return this.authToken
|
||||
} else {
|
||||
// Caso 2: El token actual no existe o ha expirado
|
||||
await this.getNewAuthToken()
|
||||
}
|
||||
|
||||
// Si después de todo no se ha generado el token es un error catastrofico
|
||||
if (this.authToken == undefined) throw new Error("Error obteniendo tokens de auth")
|
||||
|
||||
return this.authToken
|
||||
};
|
||||
}
|
||||
225
packages/sim-consumidor-alai/aplication/SimAlai.controller.ts
Normal file
225
packages/sim-consumidor-alai/aplication/SimAlai.controller.ts
Normal file
@@ -0,0 +1,225 @@
|
||||
import { ConsumeMessage } from "amqplib";
|
||||
import { Request, Response } from "express"
|
||||
import { SimAlaiUsecases } from "./SimAlai.usecases.js";
|
||||
import { EventBus } from "sim-shared/domain/EventBus.port.js";
|
||||
import { Result } from "sim-shared/domain/Result.js";
|
||||
import { SimEvents } from "sim-shared/domain/SimEvents.js";
|
||||
import { iccidValidator } from "./httpValidators.js";
|
||||
import { alaiSimToCommonSim } from "#domain/transformers.js";
|
||||
|
||||
type ErrorUsecase = {
|
||||
msg: string,
|
||||
stackTrace?: string
|
||||
}
|
||||
|
||||
export class SimAlaiController {
|
||||
|
||||
constructor(
|
||||
private uscases: SimAlaiUsecases,
|
||||
private eventBus: EventBus,
|
||||
) {
|
||||
}
|
||||
|
||||
private validateMsg(msg: ConsumeMessage | null) {
|
||||
if (msg == undefined) return false;
|
||||
const msgData = this.decodeMsg(msg) as SimEvents.general
|
||||
if (msgData == undefined || msgData.payload == undefined) throw new Error("Mensaje invalido")
|
||||
return msgData;
|
||||
}
|
||||
|
||||
private decodeMsg(msg: ConsumeMessage): object | undefined {
|
||||
if (msg.content == undefined) {
|
||||
console.warn('[Sim.controller] Mensaje vacío');
|
||||
return undefined;
|
||||
}
|
||||
|
||||
try {
|
||||
// Convertir el Buffer a String (UTF-8)
|
||||
const contentJson = JSON.parse(Buffer.from(msg.content).toString('utf8'))
|
||||
return contentJson;
|
||||
|
||||
} catch (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
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Metodo duplicado se puede generalizar la a una clase sharedController con las funciones basicas
|
||||
* TODO: meter un check de 429
|
||||
*/
|
||||
private async tryUseCase<T extends any>
|
||||
(msg: ConsumeMessage, usecase: () => Promise<Result<ErrorUsecase, T>>): Promise<Result<ErrorUsecase, T>> {
|
||||
try {
|
||||
const result = await usecase()
|
||||
if (result.error == undefined) {
|
||||
await this.eventBus.ack(msg)
|
||||
return result
|
||||
} else {
|
||||
console.error("Error procesando el caso de uso (Alai)", result.error)
|
||||
this.eventBus.nack(msg)
|
||||
return result
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Error general procesando el caso de uso (Alai)")
|
||||
this.eventBus.nack(msg)
|
||||
return {
|
||||
error: {
|
||||
msg: String(e),
|
||||
stackTrace: String(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public activate() {
|
||||
return async (msg: ConsumeMessage) => {
|
||||
console.log("[i] Evento activate ", msg.fields)
|
||||
const data = this.validateMsg(msg) as SimEvents.activation
|
||||
const iccid = data.payload.iccid
|
||||
const correlation_id = data.headers?.message_id
|
||||
const externalId = data.payload.orderId
|
||||
|
||||
const res = await this.tryUseCase(msg, this.uscases.activate({
|
||||
iccid: iccid,
|
||||
correlation_id: correlation_id,
|
||||
}))
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
public preactivate() {
|
||||
return async (msg: ConsumeMessage) => {
|
||||
console.log("[i] Evento preactivate ", msg)
|
||||
const data = this.validateMsg(msg) as SimEvents.preActivation
|
||||
const iccid = data.payload.iccid
|
||||
const correlation_id = data.headers?.message_id
|
||||
const externalId = data.payload.orderId
|
||||
|
||||
console.log("MSG:", data, data.headers)
|
||||
|
||||
const res = await this.tryUseCase(msg, this.uscases.preactivate({
|
||||
iccid: iccid,
|
||||
correlation_id: correlation_id,
|
||||
externalId: externalId
|
||||
}))
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
public suspend() {
|
||||
return async (msg: ConsumeMessage) => {
|
||||
console.log("Evento suspend ", msg.fields)
|
||||
const data = this.validateMsg(msg) as SimEvents.suspend
|
||||
const iccid = data.payload.iccid
|
||||
const correlation_id = data.headers?.message_id
|
||||
const res = await this.tryUseCase(msg, this.uscases.suspend({
|
||||
iccid: iccid,
|
||||
correlation_id: correlation_id
|
||||
}))
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public reActivate() {
|
||||
return async (msg: ConsumeMessage) => {
|
||||
console.log("Evento reActivate ", msg.fields)
|
||||
const data = this.validateMsg(msg) as SimEvents.reActivation
|
||||
const iccid = data.payload.iccid
|
||||
const correlation_id = data.headers?.message_id
|
||||
const res = await this.tryUseCase(msg, this.uscases.reactivate({
|
||||
iccid: iccid,
|
||||
correlation_id: correlation_id
|
||||
}))
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public terminate() {
|
||||
return async (msg: ConsumeMessage) => {
|
||||
console.log("Evento reActivate ", msg.fields, msg)
|
||||
const data = this.validateMsg(msg) as SimEvents.reActivation
|
||||
const iccid = data.payload.iccid
|
||||
const correlation_id = data.headers?.message_id
|
||||
const res = await this.tryUseCase(msg, this.uscases.terminate({
|
||||
iccid: iccid,
|
||||
correlation_id: correlation_id
|
||||
}))
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Select especificamente por REST para evitar pasar por las colas.
|
||||
* La respuesta es instantanea no se tiene que registrar como operación.
|
||||
*/
|
||||
public selectREST() {
|
||||
return async (req: Request, res: Response) => {
|
||||
const { query } = req
|
||||
const body = { iccid: query.iccid as string }
|
||||
console.log("Evento select", body)
|
||||
const validateBody = iccidValidator.validate(body);
|
||||
|
||||
if (validateBody.error != undefined) {
|
||||
res.status(422).json(validateBody)
|
||||
return;
|
||||
}
|
||||
|
||||
const iccid: string | string[] = body.iccid
|
||||
|
||||
if (Array.isArray(iccid)) {
|
||||
// TODO: Automatizar la paginacion
|
||||
//const usecaseRes = this.uscases.selectMany({ iccid })
|
||||
} else {
|
||||
//const usecaseRes = await this.uscases.selectOne(iccid)
|
||||
const usecaseRes = await this.uscases.selectCompleteSim(iccid)
|
||||
if (usecaseRes.error != undefined) {
|
||||
res.status(500).json(usecaseRes)
|
||||
return;
|
||||
} else {
|
||||
const { sim, subscription, imei } = usecaseRes.data
|
||||
const simData = alaiSimToCommonSim(sim, subscription, imei)
|
||||
res.send(simData)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
res.status(200).json(validateBody)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
public selectPageREST() {
|
||||
return async (req: Request, res: Response) => {
|
||||
const { offset, limit, filter, orderBy } = req.query
|
||||
const params = {
|
||||
offset: (offset != undefined) ? Number(offset) : undefined,
|
||||
limit: (limit != undefined) ? Number(limit) : undefined,
|
||||
filter: (filter != undefined) ? String(filter) : undefined,
|
||||
orderBy: (orderBy != undefined) ? String(orderBy) : undefined
|
||||
}
|
||||
|
||||
const usecaseRes = await this.uscases.selectPage(params)
|
||||
|
||||
if (usecaseRes.error != undefined) {
|
||||
res.status(500).json(usecaseRes)
|
||||
return;
|
||||
} else {
|
||||
res.status(200).send(usecaseRes.data)
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
**/
|
||||
}
|
||||
|
||||
|
||||
79
packages/sim-consumidor-alai/aplication/SimAlai.router.ts
Normal file
79
packages/sim-consumidor-alai/aplication/SimAlai.router.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
/**
|
||||
* Dirige cada mensaje dependiendo de el tipo de acción que contenga
|
||||
* Podría hacerse con varias colas, pero así se controla mejor que
|
||||
* las operaciones se hagan de 1 en 1.
|
||||
*/
|
||||
|
||||
import { ConsumeMessage } from "amqplib";
|
||||
import { EventBus } from "sim-shared/domain/EventBus.port.js";
|
||||
import { Result } from "sim-shared/domain/Result.js";
|
||||
import { SimAlaiController } from "./SimAlai.controller.js";
|
||||
|
||||
type FuncType = ((m: ConsumeMessage) => Promise<Result<{ msg: string, stackTrace?: string }, any>>)
|
||||
|
||||
export class SimAlaiRouter {
|
||||
private readonly routes: Map<string, FuncType>;
|
||||
|
||||
// WIP
|
||||
constructor(
|
||||
private readonly simController: SimAlaiController,
|
||||
private readonly eventBus: EventBus
|
||||
) {
|
||||
this.routes = new Map<string, FuncType>([
|
||||
["activate", this.simController.activate()],
|
||||
["pause", this.simController.suspend()],
|
||||
["reactivate", this.simController.reActivate()],
|
||||
["cancel", this.simController.terminate()],
|
||||
["preactivate", this.simController.preactivate()]
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enruta el mensaje a la acción correspondiente basándose en la routing key
|
||||
* TODO: No estoy seguro que deba meter el nack aqui
|
||||
* - De moemento el ack-nack se gestiona en los controller, por si acaso hay casos
|
||||
* limite en
|
||||
*/
|
||||
public route = async (msg: ConsumeMessage | null): Promise<void> => {
|
||||
if (!msg) {
|
||||
console.error("[Router] Mensaje vacío");
|
||||
return;
|
||||
}
|
||||
|
||||
const action = this.extractAction(msg);
|
||||
|
||||
if (!action) {
|
||||
console.error("[Router] La routing key no tiene una acción definida", msg.fields.routingKey);
|
||||
this.eventBus.nack(msg)
|
||||
return;
|
||||
}
|
||||
|
||||
const handler = this.routes.get(action);
|
||||
|
||||
if (!handler) {
|
||||
console.error(`[Router] La acción '${action}' no tiene un controlador asociado`);
|
||||
this.eventBus.nack(msg)
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
console.log("[Router] Ejecutando operación:", action);
|
||||
|
||||
// El controlador devuelve una función (thunk) que debe ser ejecutada
|
||||
const executeParams = handler(msg);
|
||||
|
||||
if (typeof executeParams === "function") {
|
||||
const res = await executeParams;
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error(`[Router] Error al ejecutar la operación '${action}':`, error);
|
||||
this.eventBus.nack(msg)
|
||||
}
|
||||
};
|
||||
|
||||
private extractAction(msg: ConsumeMessage): string | undefined {
|
||||
// Se asume que la acción está en la tercera posición: domain.compañia.accion
|
||||
return msg.fields.routingKey.split(".")[2];
|
||||
}
|
||||
}
|
||||
302
packages/sim-consumidor-alai/aplication/SimAlai.usecases.ts
Normal file
302
packages/sim-consumidor-alai/aplication/SimAlai.usecases.ts
Normal file
@@ -0,0 +1,302 @@
|
||||
/**
|
||||
* Documentación de referencia:
|
||||
* https://pelion-help.iot-x.com/nos/en-US/Content/API/APIReference/API%20Reference.htm?tocpath=_____7
|
||||
*
|
||||
* En nos el correlation_id ya va a ser obligatorio en todos los mensajes
|
||||
*
|
||||
* TODO:
|
||||
* - Control de errores más preciso
|
||||
*
|
||||
*/
|
||||
import { AlaiAPI } from "#domain/AlaiAPI.js";
|
||||
import { AlaiRepository } from "#infrastructure/AlaiRepository.js";
|
||||
import { ErrorOrderDTO, FinishOrderDTO, UpdateOrderDTO } from "sim-shared/domain/Order.js";
|
||||
import { Result } from "sim-shared/domain/Result.js";
|
||||
import { HttpClient } from "sim-shared/infrastructure/HTTPClient.js";
|
||||
import { OrderRepository } from "sim-shared/infrastructure/OrderRepository.js";
|
||||
|
||||
export class SimAlaiUsecases {
|
||||
constructor(
|
||||
private httpClient: HttpClient,
|
||||
private alaiRepository: AlaiRepository,
|
||||
private orderRepository: OrderRepository
|
||||
) {
|
||||
}
|
||||
|
||||
private async setRunning(correlation_id: string) {
|
||||
const updateData: UpdateOrderDTO = {
|
||||
new_status: "running",
|
||||
correlation_id: correlation_id
|
||||
}
|
||||
const order = await this.orderRepository.updateOrder(updateData)
|
||||
return order
|
||||
}
|
||||
|
||||
private async setFinished(correlation_id: string) {
|
||||
// En NOS el updateOrder se hace con el correlation_id que viene en la cabecera del
|
||||
// mensaje consumido
|
||||
const updateData: FinishOrderDTO = {
|
||||
correlation_id: correlation_id
|
||||
}
|
||||
const order = await this.orderRepository.finishOrder(updateData)
|
||||
return order
|
||||
}
|
||||
|
||||
private async setFailed(correlation_id: string, reason: string, stackTrace?: string) {
|
||||
// En NOS el updateOrder se hace con el correlation_id que viene en la cabecera del
|
||||
// mensaje consumido
|
||||
const updateData: ErrorOrderDTO = {
|
||||
status: "failed",
|
||||
correlation_id: correlation_id,
|
||||
reason: reason,
|
||||
error: reason,
|
||||
stackTrace: stackTrace
|
||||
}
|
||||
|
||||
console.log("SET FAILED DATA:", updateData)
|
||||
const order = await this.orderRepository.errorOrder(updateData)
|
||||
console.log("SET FAILED RES:", order)
|
||||
return order
|
||||
}
|
||||
|
||||
/**
|
||||
* Gestiona el ciclo de vida de una petición. No aplica
|
||||
* a peticiones de lectura (no pasan por la cola y no generan un order)
|
||||
*/
|
||||
public usecaseTemplate<T, R>(
|
||||
func: (_: T) => Promise<Result<{ msg: string, stackTrace?: string }, R>>,
|
||||
args: T,
|
||||
correlation_id?: string | undefined
|
||||
) {
|
||||
return async (): Promise<Result<{ msg: string, stackTrace?: string }, R>> => {
|
||||
// Operacion pending -> running
|
||||
if (correlation_id != undefined)
|
||||
this.setRunning(correlation_id)
|
||||
.then()
|
||||
.catch(e => console.error("Error actualizando el order", e))
|
||||
else
|
||||
console.warn("[!] Se ha lanzado una caso de uso sin correlation_id")
|
||||
|
||||
try {
|
||||
const res = await func(args)
|
||||
|
||||
if (res.error != undefined) {
|
||||
console.log("Error peticion: ", res, correlation_id)
|
||||
if (correlation_id != undefined)
|
||||
this.setFailed(correlation_id, res.error.msg, res.error.stackTrace)
|
||||
.then(e => console.log("failed", e))
|
||||
.catch(e => console.error(e))
|
||||
return res;
|
||||
} else {
|
||||
if (correlation_id != undefined)
|
||||
this.setFinished(correlation_id).then()
|
||||
return res;
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
if (correlation_id != undefined)
|
||||
this.setFailed(correlation_id, "Error general de operacion de SIM (NOS) ", String(e)).then()
|
||||
return {
|
||||
error: {
|
||||
msg: "Error general de operacion de SIM (NOS) " + String(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public activate(args: {
|
||||
iccid: string,
|
||||
correlation_id: string | undefined,
|
||||
}) {
|
||||
return this.usecaseTemplate(async (iccid /*iccid*/) => {
|
||||
const sim = await this.alaiRepository.getSimByICCID(iccid)
|
||||
if (sim.error != undefined) {
|
||||
return sim
|
||||
}
|
||||
|
||||
if (sim.data == undefined) {
|
||||
return {
|
||||
error: {
|
||||
msg: `La sim ${iccid} no se ha encontrado`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const subscriptionId = sim.data.subscription!.id
|
||||
|
||||
if (subscriptionId == undefined) {
|
||||
return {
|
||||
error: {
|
||||
msg: `La sim ${iccid} no tiene un id de subscripción`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const activationRes = await this.alaiRepository.activateSubscription(subscriptionId)
|
||||
return activationRes
|
||||
|
||||
}, args.iccid, args.correlation_id)
|
||||
}
|
||||
|
||||
public preactivate(args: {
|
||||
iccid: string,
|
||||
correlation_id: string | undefined,
|
||||
externalId: string | undefined // Por compatibilidad
|
||||
}) {
|
||||
const inputargs = {
|
||||
iccid: args.iccid,
|
||||
externalId: args.externalId
|
||||
}
|
||||
return this.usecaseTemplate(async (args) => {
|
||||
const order = await this.alaiRepository.createOrder()
|
||||
if (order.error != undefined) {
|
||||
return order
|
||||
}
|
||||
const orderId = order.data.id
|
||||
const reserve = await this.alaiRepository.createReserve(orderId, args.iccid)
|
||||
if (reserve.error != undefined) {
|
||||
return reserve
|
||||
}
|
||||
|
||||
const applyOrder = await this.alaiRepository.applyOrder(orderId)
|
||||
if (applyOrder.error != undefined) {
|
||||
// TODO: gestion del error
|
||||
// reusar el orderId
|
||||
return applyOrder
|
||||
}
|
||||
|
||||
const preactivatedSim = await this.alaiRepository.getSimByICCID(args.iccid)
|
||||
if (preactivatedSim.error != undefined) {
|
||||
return preactivatedSim
|
||||
}
|
||||
|
||||
// TODO: Controlar sim no encotrada (No deberia pasar)
|
||||
const subscriptionId = preactivatedSim.data!.subscription!.id
|
||||
if (args.externalId) {
|
||||
const externalIdAdded = await this.alaiRepository.changeExternalId(subscriptionId, args.externalId)
|
||||
if (externalIdAdded.error != undefined) {
|
||||
return externalIdAdded
|
||||
}
|
||||
}
|
||||
|
||||
// En connections acaba buscando el numero.
|
||||
const subscription = await this.alaiRepository.getSubscriptionById(subscriptionId)
|
||||
return subscription
|
||||
}, inputargs, args.correlation_id)
|
||||
}
|
||||
|
||||
public suspend(args: {
|
||||
iccid: string,
|
||||
correlation_id: string | undefined
|
||||
}) {
|
||||
return this.usecaseTemplate(async (args) => {
|
||||
const subscription = await this.alaiRepository.getSimByICCID(args.iccid)
|
||||
if (subscription.error != undefined) {
|
||||
return subscription
|
||||
}
|
||||
|
||||
// TODO: Controlar que no se encuentre la subscription
|
||||
const subscriptionid = subscription.data?.subscription?.id
|
||||
const suspension = this.alaiRepository.pauseSubscription(subscriptionid!)
|
||||
return suspension
|
||||
}, args, args.correlation_id)
|
||||
}
|
||||
|
||||
|
||||
public reactivate(args: {
|
||||
iccid: string,
|
||||
correlation_id: string | undefined
|
||||
}) {
|
||||
return this.usecaseTemplate(async (args) => {
|
||||
const subscription = await this.alaiRepository.getSimByICCID(args.iccid)
|
||||
if (subscription.error != undefined) {
|
||||
return subscription
|
||||
}
|
||||
const subscriptionid = subscription.data?.subscription?.id
|
||||
// TODO: Controlar que no se encuentre la subscription
|
||||
const suspension = this.alaiRepository.unPauseSubscription(subscriptionid!)
|
||||
return suspension
|
||||
}, args, args.correlation_id)
|
||||
}
|
||||
|
||||
|
||||
public terminate(args: {
|
||||
iccid: string,
|
||||
correlation_id: string | undefined
|
||||
}) {
|
||||
return this.usecaseTemplate(async (args) => {
|
||||
const subscription = await this.alaiRepository.getSimByICCID(args.iccid)
|
||||
if (subscription.error != undefined) {
|
||||
return subscription
|
||||
}
|
||||
|
||||
// TODO: Controlar que no se encuentre la subscription
|
||||
const suspension = this.alaiRepository.terminateSubscription(subscription.data!.id)
|
||||
return suspension
|
||||
}, args, args.correlation_id)
|
||||
}
|
||||
|
||||
public async selectOne(iccid: string) {
|
||||
const sim = await this.alaiRepository.getSimByICCID(iccid)
|
||||
return sim
|
||||
}
|
||||
|
||||
/**
|
||||
* Para sacar los datos de una liena hay que sacar sim -> subscripcion -> imei
|
||||
* son 3 llamadas distintas.
|
||||
*/
|
||||
public async selectCompleteSim(iccid: string): Promise<Result<{ msg: string, stackTrace?: string }, {
|
||||
sim: AlaiAPI.Sim,
|
||||
subscription?: AlaiAPI.Subscription,
|
||||
imei?: AlaiAPI.GetImeiSubscriptionDTO
|
||||
}>> {
|
||||
const sim = await this.alaiRepository.getSimByICCID(iccid)
|
||||
|
||||
if (sim.error != undefined) {
|
||||
return sim
|
||||
}
|
||||
|
||||
if (sim.data == undefined) {
|
||||
return {
|
||||
error: {
|
||||
msg: `La sim ${iccid} no se ha encontrado`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// En este caso la tarjeta no se ha preactivado, por lo que no tiene subscripcion
|
||||
if (sim.data.subscription == undefined) {
|
||||
return {
|
||||
data: {
|
||||
sim: sim.data,
|
||||
subscription: undefined,
|
||||
imei: undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const subscriptionId = sim.data.subscription.id
|
||||
const subscription = await this.alaiRepository.getSubscriptionById(subscriptionId)
|
||||
|
||||
if (subscription.error != undefined) {
|
||||
return subscription
|
||||
}
|
||||
|
||||
const imei = await this.alaiRepository.getImeiFromSubscription(subscriptionId)
|
||||
if (imei.error != undefined) {
|
||||
return imei
|
||||
}
|
||||
|
||||
return {
|
||||
data: {
|
||||
sim: sim.data!,
|
||||
subscription: subscription.data!,
|
||||
imei: imei.data!
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
55
packages/sim-consumidor-alai/aplication/SslService.ts
Normal file
55
packages/sim-consumidor-alai/aplication/SslService.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import { Result } from "sim-shared/domain/Result.js";
|
||||
|
||||
export type P12Cert = {
|
||||
cainfo: string,
|
||||
p12cert: string
|
||||
}
|
||||
|
||||
export type SSLCert = {
|
||||
cainfo: string,
|
||||
sslcert: string,
|
||||
keypem: string
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO:
|
||||
* - Se ha usado https.Agent en su lugar, eliminar si no se usa
|
||||
*/
|
||||
export class SSLCertificateLoader {
|
||||
|
||||
constructor(
|
||||
private certificatesDir: string,
|
||||
) {
|
||||
}
|
||||
|
||||
public loadCertificatesP12(caFile: string, certFile: string): Result<string, P12Cert> {
|
||||
try {
|
||||
const cainfo = fs.readFileSync(path.resolve(this.certificatesDir, caFile)).toString();
|
||||
const p12cert = fs.readFileSync(path.resolve(this.certificatesDir, certFile)).toString();
|
||||
return { data: { cainfo, p12cert } };
|
||||
} catch (e) {
|
||||
console.error("[x] Error cargando los certificados P12", e)
|
||||
return {
|
||||
error: String(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public loadCertificatesSSL(caFile: string, certFile: string, keyFile: string): Result<string, SSLCert> {
|
||||
try {
|
||||
const cainfo = fs.readFileSync(path.resolve(this.certificatesDir, caFile)).toString();
|
||||
const sslcert = fs.readFileSync(path.resolve(this.certificatesDir, certFile), { encoding: null }).toString();
|
||||
const keypem = fs.readFileSync(path.resolve(this.certificatesDir, keyFile), { encoding: null }).toString();
|
||||
return { data: { cainfo, sslcert, keypem } };
|
||||
} catch (e) {
|
||||
console.error("[x] Error cargando los certificados SSL", e)
|
||||
return {
|
||||
error: String(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
39
packages/sim-consumidor-alai/aplication/httpValidators.ts
Normal file
39
packages/sim-consumidor-alai/aplication/httpValidators.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { BodyValidator, Validator } from "sim-shared/aplication/BodyValidator.js";
|
||||
|
||||
const iccidNotNull = <Validator<{ iccid: unknown }>>{
|
||||
field: "iccid",
|
||||
errorMsg: "El iccid no está definido",
|
||||
validationFunc: (a: { iccid: unknown }) => {
|
||||
return (a.iccid != null && a.iccid != undefined)
|
||||
}
|
||||
}
|
||||
|
||||
const iccidValueOrArray = <Validator<{ iccid: unknown }>>{
|
||||
field: "iccid",
|
||||
errorMsg: "El iccid debe de ser un único valor o una lista",
|
||||
validationFunc: (a: { iccid: unknown }) => {
|
||||
return (typeof a.iccid == "string" || Array.isArray(a.iccid))
|
||||
}
|
||||
}
|
||||
|
||||
const iccidLongitudValidator = <Validator<{ iccid: string | string[] }>>{
|
||||
field: "iccid",
|
||||
errorMsg: "La longitud del iccid/s es incorrecta debera ser de 19 caracteres",
|
||||
validationFunc: (a: { iccid: string | string[] }) => {
|
||||
if (Array.isArray(a.iccid)) {
|
||||
const res = (a.iccid as string[]).filter(e => e.length != 19)
|
||||
if (res.length > 0) return false;
|
||||
} else {
|
||||
return (a.iccid as string).length == 19
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
export const iccidValidator = new BodyValidator<{ iccid: string | string[] }>(
|
||||
[
|
||||
iccidNotNull,
|
||||
iccidValueOrArray,
|
||||
iccidLongitudValidator,
|
||||
]
|
||||
)
|
||||
|
||||
1
packages/sim-consumidor-alai/certificates/.debugToken
Normal file
1
packages/sim-consumidor-alai/certificates/.debugToken
Normal file
@@ -0,0 +1 @@
|
||||
eyJhbGciOiJIUzM4NCJ9.eyJiciI6InNhdmVmYW1pbHkiLCJpcCI6Ijg4LjE1LjE1Ny4xNjciLCJzdWIiOiJwYWxvbWFpYmFuZXoiLCJzIjoiRVdTMTY3MzRhYTM2MDY1M2EwIiwicG9zIjoic2F2ZWZhbWlseUNhYyIsImlkV3NVc2VyIjoiODYiLCJpc012bmEiOmZhbHNlLCJkb21haW4iOiJBbGFpfHNhdmVmYW1pbHkiLCJpYXQiOjE3Nzg2ODQ0NjIsImV4cCI6MTc3ODY5NTI2Mn0.wMWgjaOErm5clang7ErYzREU56okgpXWzq1zihT4lOfUDRQ005r-nCHJu7rpilj1
|
||||
Binary file not shown.
61
packages/sim-consumidor-alai/config/env/env.ts
vendored
Normal file
61
packages/sim-consumidor-alai/config/env/env.ts
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
import { loadEnvFile } from "node:process";
|
||||
import path from "node:path";
|
||||
import assert from "node:assert";
|
||||
|
||||
try {
|
||||
loadEnvFile(path.join("../../.env")) // Global
|
||||
} catch (e) {
|
||||
console.error("Error cargando el .env desde ../../.env")
|
||||
}
|
||||
|
||||
try {
|
||||
loadEnvFile(path.join("./.env")) // base
|
||||
} catch (e) {
|
||||
console.error("Error cargando el .env desde ./.env")
|
||||
}
|
||||
|
||||
|
||||
export const env = {
|
||||
ENVIRONMENT: process.env.ENVIORMENT,
|
||||
POSTGRES_USER: process.env.POSTGRES_USER,
|
||||
POSTGRES_PASSWORD: process.env.POSTGRES_PASSWORD,
|
||||
POSTGRES_PORT: process.env.POSTGRES_PORT,
|
||||
POSTGRES_HOST: process.env.POSTGRES_HOST,
|
||||
POSTGRES_DATABASE: process.env.POSTGRES_DATABASE,
|
||||
RABBITMQ_HOST: String(process.env.RABBITMQ_HOST ?? "localhost"),
|
||||
RABBITMQ_USER: String(process.env.RABBITMQ_USER ?? "test"),
|
||||
RABBITMQ_PASSWORD: String(process.env.RABBITMQ_PASSWORD ?? "test"),
|
||||
RABBITMQ_EXCHANGE: String(process.env.RABBITMQ_EXCHANGE ?? "/"),
|
||||
RABBITMQ_PORT: parseInt(process.env.RABBITMQ_PORT ?? "5672"),
|
||||
RABBITMQ_MODULENAME: process.env.MODULENAME,
|
||||
RABBITMQ_TTL: process.env.RABBITMQ_TTL,
|
||||
RABBITMQ_SECURE: process.env.RABBITMQ_SECURE,
|
||||
RABBITMQ_RETRY_INTERVAL: process.env.RABBITMQ_INTERVAL,
|
||||
RABBITMQ_VHOST: String(process.env.RABBITMQ_VHOST),
|
||||
|
||||
ALAI_PORT: parseInt(process.env.ALAI_PORT ?? "3002"),
|
||||
ALAI_HOST: String(process.env.ALAI_HOST),
|
||||
|
||||
// ESPECIFICO ALAI
|
||||
ALAI_API_URL: process.env.ALAI_API_URL,
|
||||
ALAI_CERTIFICATES_DIR: process.env.ALAI_CERTIFICATES_DIR,
|
||||
ALAI_CERTIFICATE_NAME: process.env.ALAI_CERTIFICATE_NAME,
|
||||
ALAI_CERTIFICATE_PASSWORD: process.env.ALAI_CERTIFICATE_PASSWORD,
|
||||
ALAI_USERNAME: process.env.ALAI_USERNAME,
|
||||
ALAI_PASSWORD: process.env.ALAI_PASSWORD,
|
||||
ALAI_BRANDID: process.env.ALAI_BRANDID,
|
||||
|
||||
ALAI_PACKAGE: process.env.ALAI_PACKAGE,
|
||||
ALAI_SUBSCRIBER_ID: process.env.ALAI_SUBSCRIBER_ID
|
||||
};
|
||||
|
||||
assert.ok(env.ALAI_SUBSCRIBER_ID != undefined, "ALAI_SUBSCRIBER_ID no definido")
|
||||
assert.ok(env.ALAI_PACKAGE != undefined, "ALAI_PACKAGE no definido")
|
||||
assert.ok(env.ALAI_USERNAME != undefined, "ALAI_USERNAME no definido")
|
||||
assert.ok(env.ALAI_PASSWORD != undefined, "ALAI_PASSWORD no definido")
|
||||
assert.ok(env.ALAI_BRANDID != undefined, "ALAI_BRANDID no definido")
|
||||
assert.ok(env.ALAI_API_URL != undefined, "ALAI_API_URL no definido")
|
||||
|
||||
assert.ok(env.ALAI_CERTIFICATE_NAME != undefined, "ALAI_CERTIFICATE_NAME no definido")
|
||||
assert.ok(env.ALAI_CERTIFICATES_DIR != undefined, "ALAI_CERTIFICATES_DIR no definido")
|
||||
assert.ok(env.ALAI_CERTIFICATE_PASSWORD != undefined, "ALAI_CERTIFICATE_PASSWORD no definido")
|
||||
72
packages/sim-consumidor-alai/config/eventBus.config.ts
Normal file
72
packages/sim-consumidor-alai/config/eventBus.config.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import { RabbitMQEventBus, RMQConnectionParams } from "sim-shared/infrastructure/RabbitMQEventBus.js"
|
||||
import { Channel } from "amqp-connection-manager"
|
||||
import { env } from "./env/env.js"
|
||||
|
||||
const rmqUser = env.RABBITMQ_USER
|
||||
const rmqPass = env.RABBITMQ_PASSWORD
|
||||
const rmqHost = env.RABBITMQ_HOST
|
||||
const rmqPort = Number(env.RABBITMQ_PORT)
|
||||
const rmqSecure = false
|
||||
const rmqVhost = env.RABBITMQ_VHOST
|
||||
|
||||
export const rmqConnOptions = <RMQConnectionParams>{
|
||||
username: rmqUser,
|
||||
password: rmqPass,
|
||||
vhost: rmqVhost,
|
||||
hostname: rmqHost,
|
||||
port: rmqPort,
|
||||
secure: rmqSecure,
|
||||
}
|
||||
|
||||
|
||||
const BASE_ALAI_KEY = "sim.alai.#"
|
||||
const QUEUES = {
|
||||
MAIN: "sim.alai",
|
||||
DLX: "sim.alai.dlx",
|
||||
DELAY: "sim.alai.delayed",
|
||||
}
|
||||
|
||||
const EXCHANGES = {
|
||||
MAIN: "sim.exchange",
|
||||
DLX: "sim.ex.alai.dlx",
|
||||
DEL: "sim.ex.alai.delayed"
|
||||
}
|
||||
|
||||
export const rabbitmqEventBus = new RabbitMQEventBus({
|
||||
connectionParams: rmqConnOptions,
|
||||
buildStructure: buildQueues,
|
||||
maxRetry: 2,
|
||||
delayedExchange: EXCHANGES.DEL,
|
||||
dlxExchange: EXCHANGES.DLX
|
||||
})
|
||||
|
||||
async function buildQueues(channel: Channel) {
|
||||
|
||||
const DELAY = 10 * 1000
|
||||
|
||||
await channel.assertExchange(EXCHANGES.DEL, "topic")
|
||||
await channel.assertExchange(EXCHANGES.DLX, "topic")
|
||||
await channel.assertExchange(EXCHANGES.MAIN, "topic")
|
||||
|
||||
await channel.assertQueue(QUEUES.MAIN)
|
||||
await channel.assertQueue(QUEUES.DLX)
|
||||
await channel.assertQueue(QUEUES.DELAY, {
|
||||
durable: true,
|
||||
arguments: {
|
||||
'x-message-ttl': DELAY,
|
||||
'x-dead-letter-exchange': EXCHANGES.MAIN,
|
||||
}
|
||||
})
|
||||
|
||||
// Cola dead-letter
|
||||
await channel.bindQueue(QUEUES.DLX, EXCHANGES.DLX, BASE_ALAI_KEY)
|
||||
// Cola delay
|
||||
await channel.bindQueue(QUEUES.DELAY, EXCHANGES.DEL, BASE_ALAI_KEY)
|
||||
// Cola nos -> main exchange
|
||||
await channel.bindQueue(QUEUES.MAIN, EXCHANGES.MAIN, BASE_ALAI_KEY)
|
||||
}
|
||||
|
||||
export async function startRMQClient() {
|
||||
await rabbitmqEventBus.connect()
|
||||
return rabbitmqEventBus
|
||||
}
|
||||
20
packages/sim-consumidor-alai/config/httpClient.config.ts
Normal file
20
packages/sim-consumidor-alai/config/httpClient.config.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { HttpClient } from "sim-shared/infrastructure/HTTPClient.js"
|
||||
import { AlaiTokenManager } from "#aplication/AlaiTokenManager.js"
|
||||
import { env } from "#config/env/env.js";
|
||||
import { httpsAgent } from "./httpsAgent.js"
|
||||
import { DebugTokenManager } from "#aplication/DebugTokenManager.js";
|
||||
|
||||
const tokenManager = new AlaiTokenManager()
|
||||
const debugTokenManagr = new DebugTokenManager()
|
||||
//console.error("USANDO DebugTokenManager! Eliminar en prod")
|
||||
|
||||
|
||||
export const alaiHttp = new HttpClient({
|
||||
baseURL: env.ALAI_API_URL as string,
|
||||
headers: {
|
||||
"content-type": "application/json"
|
||||
},
|
||||
jwtManager: tokenManager,
|
||||
//jwtManager: debugTokenManagr,
|
||||
httpsAgent: httpsAgent
|
||||
})
|
||||
14
packages/sim-consumidor-alai/config/httpsAgent.ts
Normal file
14
packages/sim-consumidor-alai/config/httpsAgent.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import fs from 'fs';
|
||||
import https from 'https';
|
||||
import { env } from './env/env.js';
|
||||
import path from 'path';
|
||||
|
||||
const certificatesDir = String(env.ALAI_CERTIFICATES_DIR)
|
||||
const certificateName = String(env.ALAI_CERTIFICATE_NAME)
|
||||
const certificatePassword = String(env.ALAI_CERTIFICATE_PASSWORD)
|
||||
|
||||
export const httpsAgent = new https.Agent({
|
||||
pfx: fs.readFileSync(path.join(certificatesDir, certificateName)),
|
||||
passphrase: certificatePassword
|
||||
});
|
||||
|
||||
18
packages/sim-consumidor-alai/config/postgreConfig.ts
Normal file
18
packages/sim-consumidor-alai/config/postgreConfig.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Pool, QueryResult } from 'pg';
|
||||
import { PgClient } from 'sim-shared/infrastructure/PgClient.js'
|
||||
import { env } from './env/env.js';
|
||||
|
||||
// Configuracion de la conexion a la BDD, deberia ser la
|
||||
// Misma para todos los servicios pero hasta que se unifique todo
|
||||
// se hace una por servicio.
|
||||
export const pgPool = new Pool({
|
||||
user: env.POSTGRES_USER,
|
||||
host: env.POSTGRES_HOST,
|
||||
database: env.POSTGRES_DATABASE,
|
||||
password: env.POSTGRES_PASSWORD,
|
||||
port: Number(env.POSTGRES_PORT) || 5433,
|
||||
});
|
||||
|
||||
export const pgClient = new PgClient({
|
||||
pool: pgPool
|
||||
})
|
||||
294
packages/sim-consumidor-alai/domain/AlaiAPI.ts
Normal file
294
packages/sim-consumidor-alai/domain/AlaiAPI.ts
Normal file
@@ -0,0 +1,294 @@
|
||||
import { StringMappingType } from "typescript"
|
||||
|
||||
export namespace AlaiAPI {
|
||||
|
||||
export type LoginResponseDTO = {
|
||||
accessToken: string,
|
||||
tokenType: string,
|
||||
refreshToken: string,
|
||||
expiresIn: string // isodate
|
||||
}
|
||||
|
||||
/**
|
||||
Hardcodeado en:
|
||||
sf-sim-connections/context/infrastructure/api/alaiService.js
|
||||
const data = {
|
||||
type: "RETAIL",
|
||||
salesChannel: "OWN_CALLCENTER",
|
||||
status: "CONFIRMED",
|
||||
packages: [{ id: "Tarifa_250MB_100MIN_5SMS" }],
|
||||
subscriber: { id: "16216" }
|
||||
};
|
||||
*/
|
||||
export type CreateOrderDTO = {
|
||||
type: "RETAIL" | string,
|
||||
salesChannel: "OWN_CALLCENTER" | string,
|
||||
status: "CONFIRMED" | string,
|
||||
packages: { id: "Tarifa_250MB_100MIN_5SMS" | string }[],
|
||||
subscriber: {
|
||||
id: string
|
||||
}
|
||||
}
|
||||
|
||||
type OrderPackage = {
|
||||
id: string,
|
||||
name: string,
|
||||
packagePrices: unknown,
|
||||
packageInstance: {
|
||||
id: string,
|
||||
name: string,
|
||||
links: Link[]
|
||||
}
|
||||
}
|
||||
|
||||
type Link = {
|
||||
rel: string,
|
||||
href: string,
|
||||
hreflang: string,
|
||||
media: string,
|
||||
title: string,
|
||||
type: string,
|
||||
deprecation: string,
|
||||
profile: string,
|
||||
name: string
|
||||
}
|
||||
|
||||
export type UpdateSubscriptionDTO = {
|
||||
location: string
|
||||
}
|
||||
|
||||
export type ApplyOrderDTO = UpdateSubscriptionDTO
|
||||
|
||||
export type Subscription = {
|
||||
id: string,
|
||||
name: string,
|
||||
domain: string,
|
||||
status: Status,
|
||||
networkStatus: NetworkStatus,
|
||||
type: "RETAIL" | string,
|
||||
portabilityStatus: "NO_PORTABILITY" | string,
|
||||
billingType: string,
|
||||
creationDate: string, // ISODATE
|
||||
firstActivationDate: string, // ISODATE
|
||||
terminationDate: string, // ISODATE
|
||||
balance: number,
|
||||
balanceExpirationDate: string, // ISODATE
|
||||
lastTrafficDate: string, // ISODATE
|
||||
externalName: string,
|
||||
language: string,
|
||||
ntwID: string,
|
||||
publicIdentity: string,
|
||||
externalID: string,
|
||||
lastMsisdnID: string,
|
||||
msisdn: {
|
||||
id: string,
|
||||
name: string,
|
||||
links: Link[]
|
||||
},
|
||||
lastIccID: string,
|
||||
priceplan: {
|
||||
id: string,
|
||||
name: string,
|
||||
pricePlanName: string
|
||||
},
|
||||
salesData: {
|
||||
salesChannel: string,
|
||||
salesPerson: string,
|
||||
},
|
||||
address: {
|
||||
country: string,
|
||||
state: string,
|
||||
county: string,
|
||||
city: string,
|
||||
street: string,
|
||||
postalCode: string,
|
||||
number: string,
|
||||
description: string,
|
||||
neighborhood: string,
|
||||
typeSettlement: string,
|
||||
normalized: boolean,
|
||||
externalID: string,
|
||||
externalType: string,
|
||||
spainSpecial: {
|
||||
externalRefList:
|
||||
{
|
||||
refId: string,
|
||||
refType: string
|
||||
}[],
|
||||
streetType: number,
|
||||
ineCityCode: string,
|
||||
ineSingularEntityCode: string,
|
||||
floor: string,
|
||||
door: string,
|
||||
apartmentNumber: string,
|
||||
staircaseNumber: string,
|
||||
streetNrLast: string,
|
||||
streetNrLastSuffix: string,
|
||||
subUnitNumber: string,
|
||||
buildingName: string,
|
||||
homeID: string
|
||||
},
|
||||
iranSpecial: unknown,
|
||||
mexicoSpecial: unknown,
|
||||
brazilSpecial: unknown,
|
||||
},
|
||||
msisdnList: {
|
||||
id: string,
|
||||
name: string,
|
||||
links: Link[]
|
||||
}[],
|
||||
terminalList: {
|
||||
id: string,
|
||||
name: string,
|
||||
links: Link[]
|
||||
}[]
|
||||
|
||||
}
|
||||
|
||||
export type CreateOrderResponseDTO = {
|
||||
id: string,
|
||||
name: string,
|
||||
domain: string,
|
||||
orderCode: string,
|
||||
externalID: string,
|
||||
type: string,
|
||||
status: string,
|
||||
saleStatus: string,
|
||||
distributionStatus: string,
|
||||
description: string,
|
||||
salesChannel: string,
|
||||
salesPerson: string,
|
||||
deliveryType: string,
|
||||
distributionInfo: {
|
||||
providerID: string,
|
||||
providerReference: string,
|
||||
providerTracking: string,
|
||||
cashOnDelivery: boolean,
|
||||
prepaidShipping: boolean,
|
||||
description: string,
|
||||
events: {
|
||||
status: string,
|
||||
observations: string,
|
||||
date: string | Date,
|
||||
expectedDeliveryDate: string | Date,
|
||||
completedDeliveryDate: string | Date,
|
||||
}[]
|
||||
},
|
||||
packages: OrderPackage[],
|
||||
subscription: {
|
||||
id: string,
|
||||
name: string,
|
||||
links: Link[]
|
||||
}
|
||||
subscriber: {
|
||||
id: string,
|
||||
name: string,
|
||||
links: Link[]
|
||||
}
|
||||
brand: {
|
||||
id: string,
|
||||
name: string,
|
||||
links: Link[]
|
||||
}
|
||||
pos: {
|
||||
id: string,
|
||||
name: string,
|
||||
links: Link[]
|
||||
}
|
||||
links: Link[]
|
||||
}
|
||||
|
||||
export type NetworkStatus =
|
||||
"ACTIVE" |
|
||||
"BLOCKED" |
|
||||
"DEACTIVATE" |
|
||||
"FRAUD" |
|
||||
"PRE_ACTIVE"
|
||||
|
||||
export type Status =
|
||||
"ABORTED" |
|
||||
"ACTIVE" |
|
||||
"BLOCKEDCORE" |
|
||||
"BLOCKEDFRAUD" |
|
||||
"CANCELLED" |
|
||||
"CONFIGURING" |
|
||||
"DELETED" |
|
||||
"PRE_ACTIVE" |
|
||||
"TERMINATED"
|
||||
|
||||
export type Sim = {
|
||||
id: string,
|
||||
name: string,
|
||||
simCode: string,
|
||||
puk: string,
|
||||
puk2: string,
|
||||
pin: string,
|
||||
pin2: string,
|
||||
status: string,
|
||||
storeStatus: string,
|
||||
statusEsim: string,
|
||||
pool: {
|
||||
id: string,
|
||||
name: string,
|
||||
links: Link[]
|
||||
},
|
||||
sourcePool: {
|
||||
id: string,
|
||||
name: string,
|
||||
links: Link[]
|
||||
},
|
||||
subscription?: {
|
||||
id: string,
|
||||
name: string,
|
||||
links: Link[]
|
||||
},
|
||||
imsi: {
|
||||
id: string,
|
||||
name: string,
|
||||
links: Link[]
|
||||
},
|
||||
msisdn: {
|
||||
id: string,
|
||||
name: string,
|
||||
links: Link[]
|
||||
},
|
||||
distributedPos: {
|
||||
id: string,
|
||||
name: string,
|
||||
links: Link[]
|
||||
},
|
||||
pkgi: {
|
||||
id: string,
|
||||
name: string,
|
||||
links: Link[]
|
||||
},
|
||||
links: Link[]
|
||||
}
|
||||
|
||||
export type GetImeiSubscriptionDTO = {
|
||||
links: Link[],
|
||||
content: {
|
||||
id: string,
|
||||
sim: {
|
||||
id: string,
|
||||
links: Link[]
|
||||
},
|
||||
imsi: string,
|
||||
lastChange: string, //ISODATE
|
||||
lastUpdate: string, //ISODATE
|
||||
model: string,
|
||||
subscription: {
|
||||
id: string,
|
||||
links: Link[]
|
||||
},
|
||||
links: Link[]
|
||||
}[],
|
||||
page: {
|
||||
size: number,
|
||||
totalElements: number,
|
||||
totalPages: number,
|
||||
number: number
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
62
packages/sim-consumidor-alai/domain/transformers.ts
Normal file
62
packages/sim-consumidor-alai/domain/transformers.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import { Result } from "sim-shared/domain/Result.js";
|
||||
import { AlaiAPI } from "./AlaiAPI.js";
|
||||
import { CommonSim } from "sim-shared/domain/CommonSim.js";
|
||||
|
||||
const alaiStates = new Map<AlaiAPI.Status, CommonSim<any>["billing_status"]>([
|
||||
["ABORTED", "SUSPENDED"],
|
||||
["ACTIVE", "ACTIVE"],
|
||||
["BLOCKEDCORE", "SUSPENDED"],
|
||||
["BLOCKEDFRAUD", "SUSPENDED"],
|
||||
["CANCELLED", "TERMINATED"],
|
||||
["CONFIGURING", "SUSPENDED"],
|
||||
["DELETED", "TERMINATED"],
|
||||
["PRE_ACTIVE", "PREACTIVATED"],
|
||||
["TERMINATED", "TERMINATED"],
|
||||
|
||||
])
|
||||
|
||||
const alaiNetworkStates = new Map<AlaiAPI.NetworkStatus, CommonSim<any>["network_status"]>([
|
||||
["ACTIVE", "ACTIVE"],
|
||||
["PRE_ACTIVE", "PREACTIVATED"],
|
||||
["BLOCKED", "SUSPENDED"],
|
||||
["DEACTIVATE", "SUSPENDED"],
|
||||
["FRAUD", "TERMINATED"]
|
||||
])
|
||||
|
||||
export function alaiSimToCommonSim(alaiSim: AlaiAPI.Sim, alaiSubscription?: AlaiAPI.Subscription, imeiSubscription?: AlaiAPI.GetImeiSubscriptionDTO):
|
||||
Result<string, CommonSim<
|
||||
{
|
||||
sim: AlaiAPI.Sim,
|
||||
subscription?: AlaiAPI.Subscription,
|
||||
imeiSubscription?: AlaiAPI.GetImeiSubscriptionDTO
|
||||
}
|
||||
>> {
|
||||
|
||||
const billingStatus = (alaiSubscription == undefined) ? "AVAILABLE" : alaiStates.get(alaiSubscription?.status ?? "") ?? "UNKNOWN"
|
||||
const networkStatus = (alaiSubscription == undefined) ? "AVAILABLE" : alaiNetworkStates.get(alaiSubscription.networkStatus) ?? "UNKNOWN"
|
||||
|
||||
const commonSim: CommonSim<{
|
||||
sim: AlaiAPI.Sim,
|
||||
subscription?: AlaiAPI.Subscription,
|
||||
imeiSubscription?: AlaiAPI.GetImeiSubscriptionDTO
|
||||
}> = {
|
||||
company: "ALAI",
|
||||
tariff: alaiSubscription?.name,
|
||||
iccid: alaiSim.id,
|
||||
msisdn: alaiSubscription?.lastMsisdnID,
|
||||
billing_status: billingStatus,
|
||||
network_status: networkStatus,
|
||||
raw: {
|
||||
subscription: alaiSubscription,
|
||||
sim: alaiSim,
|
||||
imeiSubscription: imeiSubscription
|
||||
},
|
||||
imei: imeiSubscription?.content[0]?.id ?? "0",
|
||||
preactivation_date: (alaiSubscription != undefined) ? new Date(alaiSubscription.creationDate) : undefined,
|
||||
activation_date: (alaiSubscription != undefined) ? new Date(alaiSubscription.firstActivationDate) : undefined
|
||||
}
|
||||
|
||||
return {
|
||||
data: commonSim
|
||||
}
|
||||
}
|
||||
78
packages/sim-consumidor-alai/index.ts
Normal file
78
packages/sim-consumidor-alai/index.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import express from "express"
|
||||
import cors from 'cors';
|
||||
import { env } from "#config/env/env.js"
|
||||
import { startRMQClient } from "#config/eventBus.config.js";
|
||||
import { SimAlaiRouter } from "#aplication/SimAlai.router.js";
|
||||
import { SimAlaiController } from "#aplication/SimAlai.controller.js";
|
||||
import { SimAlaiUsecases } from "#aplication/SimAlai.usecases.js";
|
||||
import { alaiHttp } from "#config/httpClient.config.js";
|
||||
import { AlaiRepository } from "#infrastructure/AlaiRepository.js";
|
||||
import { OrderRepository } from "sim-shared/infrastructure/OrderRepository.js";
|
||||
import { pgClient } from "#config/postgreConfig.js";
|
||||
|
||||
const RMQ_QUEUE = "sim.alai"
|
||||
const PORT = env.ALAI_PORT
|
||||
const HOSTNAME = env.ALAI_HOST
|
||||
|
||||
async function startWorker() {
|
||||
// Instancia de dependencias
|
||||
|
||||
const rmqClient = await startRMQClient()
|
||||
|
||||
const orderRepository = new OrderRepository(pgClient)
|
||||
|
||||
const alaiRepository = new AlaiRepository(alaiHttp)
|
||||
|
||||
const alaiUsecases = new SimAlaiUsecases(
|
||||
alaiHttp,
|
||||
alaiRepository,
|
||||
orderRepository
|
||||
)
|
||||
|
||||
const alaiController = new SimAlaiController(
|
||||
alaiUsecases,
|
||||
rmqClient
|
||||
)
|
||||
|
||||
const simRouter = new SimAlaiRouter(
|
||||
alaiController,
|
||||
rmqClient
|
||||
)
|
||||
|
||||
// RMQ
|
||||
rmqClient.consume(RMQ_QUEUE, simRouter.route)
|
||||
.then(() => console.log("Cliente rmq creado con exito"))
|
||||
.catch(e => console.error("Error conectando con RABBITMQ", e))
|
||||
|
||||
// Express
|
||||
const app = express()
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
app.use(express.urlencoded({ extended: true }));
|
||||
|
||||
// WIP
|
||||
app.get("/select", alaiController.selectREST())
|
||||
app.get("/health",
|
||||
(req, res) => res.json({
|
||||
ok: "alai"
|
||||
}))
|
||||
//app.get("/selectPage", alaiController.selectPageREST())
|
||||
|
||||
app.listen(PORT, HOSTNAME, (e) => {
|
||||
if (e == undefined) {
|
||||
console.log("[o] Servidor (Alai) iniciado en el puerto %d", PORT)
|
||||
} else {
|
||||
console.error("Error express ", e)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
startWorker()
|
||||
.then(e => {
|
||||
console.log("[o] Worker de SIM de Alai iniciado")
|
||||
})
|
||||
.catch(e => {
|
||||
console.log("[x] Error iniciando worker de SIM de Alai", e)
|
||||
})
|
||||
|
||||
export default {}
|
||||
228
packages/sim-consumidor-alai/infrastructure/AlaiRepository.ts
Normal file
228
packages/sim-consumidor-alai/infrastructure/AlaiRepository.ts
Normal file
@@ -0,0 +1,228 @@
|
||||
import { AlaiAPI } from "#domain/AlaiAPI.js";
|
||||
import axios, { AxiosError, AxiosResponse } from "axios";
|
||||
import { Result } from "sim-shared/domain/Result.js";
|
||||
import { env } from "#config/env/env.js";
|
||||
import { HttpClient } from "sim-shared/infrastructure/HTTPClient.js";
|
||||
import https from "https"
|
||||
|
||||
type ErrorRepo = {
|
||||
msg: string,
|
||||
stackTrace?: string
|
||||
}
|
||||
|
||||
export class AlaiRepository {
|
||||
constructor(
|
||||
private httpClient: HttpClient
|
||||
) {
|
||||
}
|
||||
|
||||
private async manageRequest<E, T>(promiseReq: Promise<AxiosResponse<T>>): Promise<Result<ErrorRepo, T>> {
|
||||
try {
|
||||
const res = await promiseReq
|
||||
return {
|
||||
data: res.data
|
||||
}
|
||||
} catch (e) {
|
||||
if (axios.isAxiosError(e)) {
|
||||
console.log("ERROR REQUEST ", e.response)
|
||||
const error = e as AxiosError
|
||||
return {
|
||||
error: {
|
||||
msg: error.code + " : " + String(error.response?.statusText),
|
||||
stackTrace: JSON.stringify(error.response?.data)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
error: {
|
||||
msg: String(e),
|
||||
stackTrace: String(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static async login(httpsAgent: https.Agent): Promise<Result<ErrorRepo, AlaiAPI.LoginResponseDTO>> {
|
||||
const alaiUrl = env.ALAI_API_URL
|
||||
const endpoint = "/v1/auth/login"
|
||||
const fullUrl = alaiUrl + endpoint
|
||||
const data = {
|
||||
"username": env.ALAI_USERNAME,
|
||||
"password": env.ALAI_PASSWORD,
|
||||
"brandID": env.ALAI_BRANDID
|
||||
}
|
||||
|
||||
try {
|
||||
const loginRes = await axios.post<AlaiAPI.LoginResponseDTO>(fullUrl, data, { httpsAgent })
|
||||
return {
|
||||
data: loginRes.data
|
||||
}
|
||||
} catch (e) {
|
||||
if (axios.isAxiosError(e)) {
|
||||
const error = e as AxiosError
|
||||
return {
|
||||
error: {
|
||||
msg: error.code + " : " + String(error.response?.statusText),
|
||||
stackTrace: String(error)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
error: {
|
||||
msg: String(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Los orders son la unidad que envuelve las peticiones para garantizar ideponencia.
|
||||
*/
|
||||
public async createOrder() {
|
||||
// POST
|
||||
const endpoint = "/v1/order"
|
||||
const data: AlaiAPI.CreateOrderDTO = {
|
||||
type: "RETAIL",
|
||||
salesChannel: "OWN_CALLCENTER",
|
||||
status: "CONFIRMED",
|
||||
packages: [{ id: env.ALAI_PACKAGE as string }],
|
||||
subscriber: { id: env.ALAI_SUBSCRIBER_ID as string }
|
||||
}
|
||||
const promReq = this.httpClient.post<AlaiAPI.CreateOrderResponseDTO>(endpoint, data)
|
||||
const res = await this.manageRequest(promReq)
|
||||
return res
|
||||
}
|
||||
|
||||
public async applyOrder(orderId: string) {
|
||||
const endpoint = `/v1/order/${orderId}`
|
||||
const params = new URLSearchParams([
|
||||
["action", "APPLY"]
|
||||
])
|
||||
const promReq = this.httpClient.patch<AlaiAPI.ApplyOrderDTO>(endpoint, undefined, { params: params })
|
||||
const res = await this.manageRequest(promReq)
|
||||
return res
|
||||
}
|
||||
|
||||
/*
|
||||
* Ver estado del order para debug
|
||||
*/
|
||||
public async getOrder(orderId: string) {
|
||||
const endpoint = `/v1/order/${orderId}`
|
||||
const promReq = this.httpClient.post<AlaiAPI.CreateOrderResponseDTO>(endpoint, undefined)
|
||||
const res = await this.manageRequest(promReq)
|
||||
return res
|
||||
}
|
||||
|
||||
/**
|
||||
* Antes se usaba PATCH /v1/sim/{iccid}/{orderId} pero en la docu ha pasado a POST
|
||||
*/
|
||||
public async createReserve(orderId: string, iccid: string): Promise<Result<ErrorRepo, AlaiAPI.CreateOrderResponseDTO>> {
|
||||
const endpoint = `/v1/sim/${iccid}/order/${orderId}`
|
||||
// Crear la reserva no usa datos en el body
|
||||
const promReq = this.httpClient.post<AlaiAPI.CreateOrderResponseDTO>(endpoint, undefined)
|
||||
const res = await this.manageRequest(promReq)
|
||||
return res
|
||||
}
|
||||
|
||||
/**
|
||||
* IMPORTANTE:
|
||||
* - En el campo subscription viene el id para los cambios de estado, no se hacen
|
||||
* sobre la sim, sino sobre la subscription
|
||||
*/
|
||||
public async getSimByICCID(iccid: string) {
|
||||
const endpoint = `/v1/sim/${iccid}`
|
||||
const promReq = this.httpClient.get<AlaiAPI.Sim | undefined>(endpoint, undefined)
|
||||
const res = await this.manageRequest(promReq)
|
||||
return res
|
||||
}
|
||||
|
||||
public async pauseSubscription(subscriptionId: string) {
|
||||
const endpoint = `/v1/subscription/${subscriptionId}`
|
||||
// En teoria ahora se usa ["action", "BLOCK"] pero no he probado
|
||||
const params = new URLSearchParams([
|
||||
["action", "CHANGE_STATUS"]
|
||||
])
|
||||
const data = {
|
||||
status: "BLOCKEDCORE"
|
||||
}
|
||||
const promReq = this.httpClient.patch<AlaiAPI.UpdateSubscriptionDTO | undefined>(endpoint, data, { params: params })
|
||||
const res = await this.manageRequest(promReq)
|
||||
return res
|
||||
}
|
||||
|
||||
public async activateSubscription(subscriptionId: string) {
|
||||
const endpoint = `/v1/subscription/${subscriptionId}`
|
||||
// En teoria ahora se usa ["action", "UNBLOCK"] pero no he probado
|
||||
const params = new URLSearchParams([
|
||||
["action", "CHANGE_STATUS"]
|
||||
])
|
||||
|
||||
const data = {
|
||||
"status": "ACTIVE"
|
||||
}
|
||||
|
||||
const promReq = this.httpClient.patch<AlaiAPI.UpdateSubscriptionDTO | undefined>(endpoint, data, { params: params })
|
||||
const res = await this.manageRequest(promReq)
|
||||
return res
|
||||
}
|
||||
|
||||
public async unPauseSubscription(subscriptionId: string) {
|
||||
const endpoint = `/v1/subscription/${subscriptionId}`
|
||||
// En teoria ahora se usa ["action", "UNBLOCK"] pero no he probado
|
||||
const params = new URLSearchParams([
|
||||
["action", "UNBLOCK"]
|
||||
])
|
||||
|
||||
const rawParams = {
|
||||
"action": "UNBLOCK"
|
||||
}
|
||||
|
||||
const data = {
|
||||
status: "ACTIVE"
|
||||
}
|
||||
const promReq = this.httpClient.patch<AlaiAPI.UpdateSubscriptionDTO | undefined>(endpoint, undefined, { params: rawParams })
|
||||
const res = await this.manageRequest(promReq)
|
||||
return res
|
||||
}
|
||||
|
||||
public async terminateSubscription(subscriptionId: string) {
|
||||
const endpoint = `/v1/subscription/${subscriptionId}`
|
||||
// Esta llamada si es de acuerdo a la docu
|
||||
const params = new URLSearchParams([
|
||||
["action", "TERMINATE"]
|
||||
])
|
||||
const promReq = this.httpClient.patch<AlaiAPI.UpdateSubscriptionDTO | undefined>(endpoint, undefined, { params: params })
|
||||
const res = await this.manageRequest(promReq)
|
||||
return res
|
||||
}
|
||||
|
||||
public async changeExternalId(subscriptionId: string, externalId: string) {
|
||||
const endpoint = `/v1/subscription/${subscriptionId}`
|
||||
// Esta llamada si es de acuerdo a la docu
|
||||
const params = new URLSearchParams([
|
||||
["action", "MODIFY"]
|
||||
])
|
||||
const data = {
|
||||
externalID: externalId
|
||||
}
|
||||
const promReq = this.httpClient.patch<AlaiAPI.UpdateSubscriptionDTO | undefined>(endpoint, data, { params: params })
|
||||
const res = await this.manageRequest(promReq)
|
||||
return res
|
||||
}
|
||||
|
||||
public async getSubscriptionById(subscriptionId: string) {
|
||||
const endpoint = `/v1/subscription/${subscriptionId}`
|
||||
const promReq = this.httpClient.get<AlaiAPI.Subscription | undefined>(endpoint)
|
||||
const res = await this.manageRequest(promReq)
|
||||
return res
|
||||
}
|
||||
|
||||
public async getImeiFromSubscription(subscriptionId: string) {
|
||||
const endpoint = `/v1/subscription/${subscriptionId}/imei`
|
||||
const promReq = this.httpClient.get<AlaiAPI.GetImeiSubscriptionDTO | undefined>(endpoint)
|
||||
const res = await this.manageRequest(promReq)
|
||||
return res
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
import path from "path";
|
||||
import fs from 'fs';
|
||||
import { Result } from "sim-shared/domain/Result.js";
|
||||
import { PgClient } from "sim-shared/infrastructure/PgClient.js";
|
||||
|
||||
type TokenLine = {
|
||||
id: number,
|
||||
url: string,
|
||||
user_name: string,
|
||||
pass: string,
|
||||
brand_id: string,
|
||||
cert_file: string,
|
||||
key_file: string,
|
||||
ca_file: string,
|
||||
p12_file: string,
|
||||
cert_password: string,
|
||||
token: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Repositorio para usar los tokens guardados en la bdd de intranet o en un archivo
|
||||
*/
|
||||
export class LegacyJWTTokenRepository {
|
||||
constructor(
|
||||
// En prod (no deberia usarse) tiene que apuntar a intranet
|
||||
private pgClient: PgClient
|
||||
) {
|
||||
}
|
||||
|
||||
public async getTokenFromDB(): Promise<Result<string, string>> {
|
||||
const query = "SELECT * FROM alai_api_credentials;"
|
||||
|
||||
try {
|
||||
const res = await this.pgClient.query<TokenLine>(query)
|
||||
if (res.rowCount == 0) {
|
||||
return {
|
||||
error: "Error recuperando el token actual"
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
data: res.rows[0].token
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
return {
|
||||
error: String(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static getTokenFromFile(dir: string, file: string): Result<string, string> {
|
||||
try {
|
||||
const tokenPath = path.join(dir, file)
|
||||
const fileContent = fs.readFileSync(tokenPath).toString()
|
||||
return {
|
||||
data: fileContent
|
||||
}
|
||||
} catch (e) {
|
||||
return {
|
||||
error: "[d!] No se ha podido leer el archivo del token de debug" + String(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
72
packages/sim-consumidor-alai/package.json
Normal file
72
packages/sim-consumidor-alai/package.json
Normal file
@@ -0,0 +1,72 @@
|
||||
{
|
||||
"name": "sim-consumidor-alai",
|
||||
"type": "module",
|
||||
"description": "consumidor generico de eventos de alai",
|
||||
"main": "index.ts",
|
||||
"imports": {
|
||||
"#config/*.js": {
|
||||
"types": "./config/*.ts",
|
||||
"default": "./config/*.js"
|
||||
},
|
||||
"#config/*": {
|
||||
"types": "./config/*.ts",
|
||||
"default": "./config/*.js"
|
||||
},
|
||||
"#infrastructure/*.js": {
|
||||
"types": "./infrastructure/*.ts",
|
||||
"default": "./infrastructure/*.js"
|
||||
},
|
||||
"#infrastructure/*": {
|
||||
"types": "./infrastructure/*.ts",
|
||||
"default": "./infrastructure/*.js"
|
||||
},
|
||||
"#domain/*.js": {
|
||||
"types": "./domain/*.ts",
|
||||
"default": "./domain/*.js"
|
||||
},
|
||||
"#domain/*": {
|
||||
"types": "./domain/*.ts",
|
||||
"default": "./domain/*.js"
|
||||
},
|
||||
"#aplication/*.js": {
|
||||
"types": "./aplication/*.ts",
|
||||
"default": "./aplication/*.js"
|
||||
},
|
||||
"#aplication/*": {
|
||||
"types": "./aplication/*.ts",
|
||||
"default": "./aplication/*.js"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"build": "yarn tsc --project tsconfig.json && yarn tsc-alias && cp -P .env package.json ../../dist/packages/sim-consumidor-alai/ && cp -r certificates/ ../../dist/packages/sim-consumidor-alai/",
|
||||
"build:prod": "yarn tsc --project tsconfig.json && yarn tsc-alias && cp -P package.json ../../dist/packages/sim-consumidor-alai/ && mkdir ../../dist/packages/sim-consumidor-alai/certificates",
|
||||
"esbuild": "esbuild index.ts --platform=node",
|
||||
"start": "node ../../dist/packages/sim-consumidor-alai/index.js",
|
||||
"dev": "tsx watch index.ts"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"packageManager": "yarn@4.12.0",
|
||||
"dependencies": {
|
||||
"@tsconfig/node22": "*",
|
||||
"amqplib": "^0.10.9",
|
||||
"cors": "*",
|
||||
"dotenv": "*",
|
||||
"express": "*",
|
||||
"sim-shared": "sim-shared:*",
|
||||
"typescript": "*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/amqplib": "^0.10.8",
|
||||
"@types/cors": "*",
|
||||
"@types/express": "*",
|
||||
"@types/node": "*",
|
||||
"@types/supertest": "*",
|
||||
"prettier": "*",
|
||||
"supertest": "*",
|
||||
"tsc-alias": "^1.8.16",
|
||||
"tsx": "*",
|
||||
"vitest": "*"
|
||||
}
|
||||
}
|
||||
5
packages/sim-consumidor-alai/readme.md
Normal file
5
packages/sim-consumidor-alai/readme.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# Alai
|
||||
|
||||
## Particularidades de las operaciones de Alai
|
||||
|
||||
TODO: Copiar de obsidian
|
||||
41
packages/sim-consumidor-alai/tsconfig.json
Normal file
41
packages/sim-consumidor-alai/tsconfig.json
Normal file
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../dist",
|
||||
"rootDir": "../../",
|
||||
"paths": {
|
||||
"#config/*": [
|
||||
"./config/*"
|
||||
],
|
||||
"#infrastructure/*": [
|
||||
"./infrastructure/*"
|
||||
],
|
||||
"#domain/*": [
|
||||
"./domain/*"
|
||||
],
|
||||
"#aplication/*": [
|
||||
"./aplication/*"
|
||||
],
|
||||
"config/*": [
|
||||
"./config/*"
|
||||
],
|
||||
"infrastructure/*": [
|
||||
"./infrastructure/*"
|
||||
],
|
||||
"domain/*": [
|
||||
"./domain/*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
],
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
"**/*.d.ts",
|
||||
"../../packages/sim-shared/**/*.ts"
|
||||
],
|
||||
"files": [
|
||||
"index.ts"
|
||||
]
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
NOS_BASE_URL=localhost
|
||||
APP_PORT=3001
|
||||
APP_HOST="0.0.0.0"
|
||||
|
||||
ENVIORMENT=development
|
||||
|
||||
NOS_ACCESS_TOKEN=2YGhecTr4+uKbVKxaqBlk2edsrHA2OQY
|
||||
NOS_BASE_URL=https://nosconnectcenter-api.iot-x.com
|
||||
@@ -5,6 +5,8 @@ import { EventBus } from "sim-shared/domain/EventBus.port.js";
|
||||
import { Result } from "sim-shared/domain/Result.js";
|
||||
import { SimEvents } from "sim-shared/domain/SimEvents.js";
|
||||
import { iccidValidator } from "./httpValidators.js";
|
||||
import { error } from "node:console";
|
||||
import { nosSimToCommonSim } from "#domain/transformers.js";
|
||||
|
||||
export class SimNosController {
|
||||
|
||||
@@ -127,7 +129,7 @@ export class SimNosController {
|
||||
const validateBody = iccidValidator.validate(body);
|
||||
|
||||
if (validateBody.error != undefined) {
|
||||
res.status(402).json(validateBody)
|
||||
res.status(422).json(validateBody)
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -142,12 +144,17 @@ export class SimNosController {
|
||||
res.status(500).json(usecaseRes)
|
||||
return;
|
||||
} else {
|
||||
res.send(usecaseRes.data)
|
||||
const simComun = nosSimToCommonSim(usecaseRes.data)
|
||||
res.status(200).send({ data: simComun })
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
res.status(200).json(validateBody)
|
||||
res.status(501).json({
|
||||
errors: {
|
||||
msg: "No está implementada la busqueda por lista de iccid"
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,7 +175,7 @@ export class SimNosController {
|
||||
res.status(500).json(usecaseRes)
|
||||
return;
|
||||
} else {
|
||||
res.status(200).send(usecaseRes.data)
|
||||
res.status(200).send(usecaseRes)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ try {
|
||||
}
|
||||
|
||||
export const env = {
|
||||
ENVIRONMENT: process.env.ENVIORMENT,
|
||||
POSTGRES_USER: process.env.POSTGRES_USER,
|
||||
POSTGRES_PASSWORD: process.env.POSTGRES_PASSWORD,
|
||||
POSTGRES_PORT: process.env.POSTGRES_PORT,
|
||||
@@ -30,8 +29,8 @@ export const env = {
|
||||
RABBITMQ_RETRY_INTERVAL: process.env.RABBITMQ_INTERVAL,
|
||||
RABBITMQ_VHOST: String(process.env.RABBITMQ_VHOST),
|
||||
|
||||
APP_PORT: Number(process.env.APP_PORT),
|
||||
APP_HOST: String(process.env.APP_HOST),
|
||||
APP_PORT: Number(process.env.NOS_PORT ?? 3001),
|
||||
APP_HOST: String(process.env.NOS_HOST ?? "0.0.0.0"),
|
||||
|
||||
// ESPECIFICO NOS
|
||||
NOS_BASE_URL: String(process.env.NOS_BASE_URL),
|
||||
|
||||
@@ -103,7 +103,7 @@ export namespace NosApi {
|
||||
|
||||
export type NetworkState = {
|
||||
currentStateId: number
|
||||
currentState: string
|
||||
currentState: "active" | "barred" | "terminated"
|
||||
isTransferring: boolean
|
||||
lastTransferred: number
|
||||
isOnline: boolean
|
||||
@@ -112,7 +112,7 @@ export namespace NosApi {
|
||||
|
||||
export type BillingState = {
|
||||
currentStateId: number
|
||||
currentState: string
|
||||
currentState: "active" | "terminated"
|
||||
}
|
||||
|
||||
export type BarData = {
|
||||
|
||||
39
packages/sim-consumidor-nos/domain/transformers.ts
Normal file
39
packages/sim-consumidor-nos/domain/transformers.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { CommonSim } from "sim-shared/domain/CommonSim.js";
|
||||
import { NosApi } from "./NosAPI.js";
|
||||
|
||||
const billingStates = new Map<
|
||||
NosApi.LineData["billingState"]["currentState"],
|
||||
CommonSim<any>["billing_status"]>([
|
||||
["active", "ACTIVE"],
|
||||
["terminated", "TERMINATED"]
|
||||
])
|
||||
|
||||
const networkStates = new Map<
|
||||
NosApi.LineData["networkState"]["currentState"],
|
||||
CommonSim<any>["network_status"]
|
||||
>([
|
||||
["active", "ACTIVE"],
|
||||
["terminated", "TERMINATED"],
|
||||
["barred", "SUSPENDED"]
|
||||
])
|
||||
|
||||
export function nosSimToCommonSim(nosSim: NosApi.LineData): CommonSim<NosApi.LineData> {
|
||||
const billingState = billingStates.get(nosSim.billingState.currentState) ?? "UNKNOWN"
|
||||
const networkState = networkStates.get(nosSim.networkState.currentState) ?? "UNKNOWN"
|
||||
|
||||
const commonSim: CommonSim<NosApi.LineData> = {
|
||||
company: "NOS",
|
||||
tariff: nosSim.tariffName,
|
||||
iccid: nosSim.physicalId,
|
||||
msisdn: nosSim.subscriberId,
|
||||
billing_status: billingState!,
|
||||
network_status: networkState!,
|
||||
raw: nosSim,
|
||||
imei: nosSim.imei,
|
||||
activation_date: new Date(nosSim.connectionDate),
|
||||
termination_date: new Date(nosSim.terminateDate),
|
||||
suspension_date: null // NOS no especifica la fecha de de 'barred' que equivale a la suspension
|
||||
}
|
||||
|
||||
return commonSim
|
||||
}
|
||||
@@ -63,7 +63,7 @@ async function startWorker() {
|
||||
|
||||
app.listen(PORT, HOSTNAME, (e) => {
|
||||
if (e == undefined) {
|
||||
console.log("[o] Servidor iniciado en el puerto %d", PORT)
|
||||
console.log("[o] Servidor (NOS) iniciado en el puerto %d", PORT)
|
||||
} else {
|
||||
console.error("Error express ", e)
|
||||
}
|
||||
|
||||
@@ -39,7 +39,8 @@
|
||||
},
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"build": "yarn tsc --project tsconfig.json && yarn tsc-alias && cp package.json ../../dist/packages/sim-consumidor-nos/",
|
||||
"build": "yarn tsc --project tsconfig.json && yarn tsc-alias && cp -P .env package.json ../../dist/packages/sim-consumidor-nos/",
|
||||
"build:prod": "yarn tsc --project tsconfig.json && yarn tsc-alias && cp -P package.json ../../dist/packages/sim-consumidor-nos/",
|
||||
"esbuild": "esbuild index.ts --platform=node",
|
||||
"start": "node ../../dist/packages/sim-consumidor-nos/index.js",
|
||||
"dev": "tsx watch index.ts"
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
# 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_CUSTOMER_CODE=9.49411.10
|
||||
//OBJ_BASE_URL=https://api-getway.objenious.com/ws/test
|
||||
@@ -10,6 +10,7 @@ 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";
|
||||
import { ObjeniousLinesRepository } from "sim-shared/infrastructure/ObjeniousLinesRepository.js";
|
||||
|
||||
describe("SimController Integration Tests (Real UseCases)", () => {
|
||||
let eventBusMock: any;
|
||||
@@ -32,11 +33,13 @@ describe("SimController Integration Tests (Real UseCases)", () => {
|
||||
);
|
||||
const orderRepository = new OrderRepository(postgrClient);
|
||||
const pauseRepository = new PauseCancelTaskRepository(postgrClient);
|
||||
const linesRepository = new ObjeniousLinesRepository(postgrClient) // tiene que apuntar a "intranet"
|
||||
useCases = new SimUseCases({
|
||||
httpClient: httpInstance,
|
||||
operationRepository: operationRepository,
|
||||
orderRepository: orderRepository,
|
||||
pauseRepository: pauseRepository
|
||||
pauseRepository: pauseRepository,
|
||||
objeniousLinesRepository: linesRepository
|
||||
});
|
||||
// @ts-expect-error
|
||||
useCases.findActivationDate = async (data: ActionData) => new Date()
|
||||
|
||||
@@ -4,6 +4,11 @@ import { SimUseCases } from "./Sim.usecases.js";
|
||||
import { SimEvents } from "sim-shared/domain/SimEvents.js";
|
||||
import { Result } from "sim-shared/domain/Result.js";
|
||||
import { ActionData } from "#domain/DTOs/objeniousapi.js";
|
||||
import { Request, Response } from "express"
|
||||
import { PaginationArgs, QueryPaginationArgs } from "sim-shared/domain/PaginationArgs.js";
|
||||
import { paginationValidator } from "./httpValidators.js";
|
||||
import { error } from "node:console";
|
||||
import { objeniousSimToCommon } from "#domain/transformers.js";
|
||||
|
||||
/**
|
||||
* La clase usa generadores de funciones para mantener el contexto
|
||||
@@ -219,6 +224,58 @@ export class SimController {
|
||||
}
|
||||
}
|
||||
|
||||
public queryLines() {
|
||||
const DEFAULT_LIMIT = 1000
|
||||
const DEFAULT_OFFSET = 0
|
||||
|
||||
return async (req: Request, res: Response) => {
|
||||
const queryParams = req.query
|
||||
|
||||
const paginationArgs: QueryPaginationArgs = {
|
||||
limit: queryParams.limit as string | undefined,
|
||||
offset: queryParams.offset as string | undefined
|
||||
}
|
||||
|
||||
const validationRes = paginationValidator.validate(paginationArgs)
|
||||
if (validationRes.error != undefined) {
|
||||
res.status(422).json(validationRes)
|
||||
return;
|
||||
}
|
||||
|
||||
const paginationValues = {
|
||||
limit: (queryParams.limit != undefined) ? Number(queryParams.limit) : DEFAULT_LIMIT,
|
||||
offset: (queryParams.offset != undefined) ? Number(queryParams.offset) : DEFAULT_OFFSET
|
||||
}
|
||||
|
||||
const status = req.query.status
|
||||
|
||||
const queryRes = await this.useCases.getLinesByQuery({ status: status as string | undefined }, paginationValues)
|
||||
res.json(queryRes)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Una única linea para /select
|
||||
*/
|
||||
public queryLine() {
|
||||
return async (req: Request, res: Response) => {
|
||||
const queryParams = req.query
|
||||
const queryArgs = {
|
||||
iccid: queryParams.iccid as string // La validacion de iccid se ha tenido que hacer en el gateway
|
||||
}
|
||||
|
||||
const line = await this.useCases.getLineByIccid(queryArgs.iccid)
|
||||
if (line.error != undefined) {
|
||||
res.status(line.error.code).json(line)
|
||||
return;
|
||||
}
|
||||
|
||||
const commonLine = objeniousSimToCommon(line.data)
|
||||
|
||||
res.status(200).json({ data: commonLine })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO:
|
||||
* - Loguear motivos de la no validacion
|
||||
|
||||
@@ -20,7 +20,7 @@ export class SimRouter {
|
||||
["pause", this.simController.suspend()],
|
||||
["cancel", this.simController.terminate()],
|
||||
["reactivate", this.simController.reActivate()],
|
||||
["preActivate", this.simController.preActivate()]
|
||||
["preactivate", this.simController.preActivate()]
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,9 @@ import assert from "node:assert"
|
||||
import { OrderRepository } from "sim-shared/infrastructure/OrderRepository.js"
|
||||
import { CreatePauseCancelTaskDTO, PauseCancelTaskRepository } from "#adapters/PauseCancelTaskRepository.js"
|
||||
import { ObjeniousOperationsRepository } from "sim-shared/infrastructure/ObjeniousOperationRepository.js"
|
||||
import { ObjeniousLinesRepository } from "sim-shared/infrastructure/ObjeniousLinesRepository.js"
|
||||
import { error } from "node:console"
|
||||
import { ObjeniousLine, ObjeniousLineDb } from "sim-shared/domain/objeniousLine.js"
|
||||
|
||||
// TODO:
|
||||
// - Pasar a un archivo de DTOs
|
||||
@@ -17,17 +20,19 @@ export class SimUseCases {
|
||||
private readonly objeniousRepository: ObjeniousOperationsRepository
|
||||
private readonly orderRepository: OrderRepository
|
||||
private readonly pauseRepository: PauseCancelTaskRepository
|
||||
|
||||
private readonly objeniousLinesRepository: ObjeniousLinesRepository
|
||||
constructor(args: {
|
||||
httpClient: HttpClient,
|
||||
operationRepository: ObjeniousOperationsRepository,
|
||||
orderRepository: OrderRepository,
|
||||
pauseRepository: PauseCancelTaskRepository
|
||||
pauseRepository: PauseCancelTaskRepository,
|
||||
objeniousLinesRepository: ObjeniousLinesRepository
|
||||
}) {
|
||||
this.httpClient = args.httpClient
|
||||
this.objeniousRepository = args.operationRepository
|
||||
this.orderRepository = args.orderRepository
|
||||
this.pauseRepository = args.pauseRepository
|
||||
this.objeniousLinesRepository = args.objeniousLinesRepository
|
||||
}
|
||||
|
||||
private async logOperation(data: ObjeniousOperation) {
|
||||
@@ -119,7 +124,6 @@ export class SimUseCases {
|
||||
const iccid = activationData.identifier.identifiers
|
||||
// Comporbación excepcional para saber si la linea está suspendida
|
||||
const statusLinea = await this.objeniousRepository.getLinesAPI("ICCID", [String(iccid)])
|
||||
console.log("statusLinea, ", iccid, statusLinea)
|
||||
if (statusLinea.data != undefined && statusLinea.data[0].status.networkStatus == "SUSPENDED") {
|
||||
const res = await this.reActivate(activationData)()
|
||||
return res;
|
||||
@@ -181,7 +185,7 @@ export class SimUseCases {
|
||||
console.log("Sim preactivada con exito", resp.data)
|
||||
const operation: ObjeniousOperation = {
|
||||
correlation_id: preActivateData.correlation_id,
|
||||
operation: "preActivate",
|
||||
operation: "preactivate",
|
||||
iccids: String(preActivateData.identifier.identifiers),
|
||||
status: "noMassID",
|
||||
request_id: resp.data.requestId
|
||||
@@ -269,7 +273,7 @@ export class SimUseCases {
|
||||
|
||||
/**
|
||||
* Metodo muy especifico para obtener la fecha e activacion o en su defecto
|
||||
* la actual para aber cuando se va a completar el periodo de test de una linea
|
||||
* la actual para saber cuando se va a completar el periodo de test de una linea
|
||||
*/
|
||||
private async findActivationDate(actionData: ActionData) {
|
||||
const iccid = actionData.identifier.identifiers
|
||||
@@ -278,7 +282,7 @@ export class SimUseCases {
|
||||
// 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)
|
||||
//console.log("LineData", lineData.data)
|
||||
if (lineData.error != undefined) {
|
||||
console.error(lineData.error)
|
||||
} else {
|
||||
@@ -321,13 +325,14 @@ export class SimUseCases {
|
||||
}
|
||||
|
||||
// TODO REGISTRAR EL ORDER
|
||||
/*
|
||||
if (correlation_id != undefined) {
|
||||
await this.orderRepository.createOrder({
|
||||
correlation_id: correlation_id,
|
||||
order_type: "pause"
|
||||
})
|
||||
}
|
||||
|
||||
*/
|
||||
let activationDate;
|
||||
try {
|
||||
activationDate = await this.findActivationDate(suspendData)
|
||||
@@ -434,10 +439,81 @@ export class SimUseCases {
|
||||
identifier: terminationData.identifier
|
||||
},
|
||||
url: OPERATION_URL,
|
||||
iccid: terminationData.identifier.identifiers[0], //
|
||||
iccid: terminationData.identifier.identifiers[0],
|
||||
operation: "terminate"
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Calcula el tiempo que una linea ha estado en suspensión
|
||||
*/
|
||||
public async getSuspendedTime(iccid: string):
|
||||
Promise<Result<string, { total_milliseconds: number, total_days: number }>> {
|
||||
try {
|
||||
const result = await this.objeniousRepository.getSuspendedTime(iccid);
|
||||
if (result.error !== undefined) {
|
||||
return { error: result.error as string, data: undefined };
|
||||
}
|
||||
return {
|
||||
data: {
|
||||
total_milliseconds: result.data!.total_milliseconds,
|
||||
total_days: result.data!.total_days
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("[Sim.usecases] Error getting suspended time", error);
|
||||
return { error: "Error getting suspended time", data: undefined };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Busqueda de líneas **en nuestro volcado** según una query y con paginacion
|
||||
*/
|
||||
public async getLinesByQuery(query: { status?: string | undefined }, pagination: { limit: number, offset: number })
|
||||
: Promise<Result<string, {
|
||||
data: ObjeniousLineDb[],
|
||||
offset: number,
|
||||
rowCount: number
|
||||
}>> {
|
||||
try {
|
||||
|
||||
const linesQuery = await this.objeniousLinesRepository.getLinesByStatus(query, pagination)
|
||||
return {
|
||||
data: linesQuery,
|
||||
}
|
||||
} catch (e) {
|
||||
return {
|
||||
error: String(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public async getLineByIccid(iccid: string):
|
||||
Promise<Result<{ msg: string, code: number }, ObjeniousLine>> {
|
||||
const line = await this.objeniousRepository.getLineByIccid(iccid)
|
||||
|
||||
if (line.error != undefined) {
|
||||
return {
|
||||
error: {
|
||||
msg: "Error general buscando la sim",
|
||||
code: 500
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (line.data.length == 0) {
|
||||
return {
|
||||
error: {
|
||||
msg: "Sim no encontrada",
|
||||
code: 204
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
data: line.data[0]
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import { BodyValidator, Validator } from "sim-shared/aplication/BodyValidator.js";
|
||||
import { QueryPaginationArgs } from "sim-shared/domain/PaginationArgs.js";
|
||||
|
||||
const limitPositiveOrUndefined = <Validator<QueryPaginationArgs>>{
|
||||
field: "limit",
|
||||
validationFunc: (args) => (args.limit == undefined || !isNaN(+args.limit) && parseInt(args.limit) >= 0),
|
||||
errorMsg: "El campo limit debe ser un numero o undefined (default 0)"
|
||||
}
|
||||
const offsetPositiveOrUndefined = <Validator<QueryPaginationArgs>>{
|
||||
field: "offset",
|
||||
validationFunc: (args) => (args.offset == undefined || isNaN(+args.offset) && parseInt(args.offset) >= 1),
|
||||
errorMsg: "El campo offset debe ser un numero o undefined (default 0)"
|
||||
}
|
||||
|
||||
export const paginationValidator = new BodyValidator<QueryPaginationArgs & {}>([
|
||||
limitPositiveOrUndefined,
|
||||
offsetPositiveOrUndefined
|
||||
])
|
||||
@@ -4,7 +4,10 @@ import path from "node:path";
|
||||
loadEnvFile(path.join("../../.env")) // Global
|
||||
loadEnvFile(path.join("./.env")) // base
|
||||
|
||||
|
||||
export const env = {
|
||||
PORT: parseInt(process.env.OBJENIOUS_CONSUMER_PORT || "3002"),
|
||||
|
||||
ENVIRONMENT: process.env.ENVIORMENT,
|
||||
POSTGRES_USER: process.env.POSTGRES_USER,
|
||||
POSTGRES_PASSWORD: process.env.POSTGRES_PASSWORD,
|
||||
|
||||
@@ -46,8 +46,8 @@ async function buildQueues(channel: Channel) {
|
||||
await channel.assertExchange(EXCHANGES.DLX, "topic")
|
||||
await channel.assertExchange(EXCHANGES.MAIN, "topic")
|
||||
|
||||
await channel.assertQueue(QUEUES.OBJ)
|
||||
await channel.assertQueue(QUEUES.OBJDLX)
|
||||
await channel.assertQueue(QUEUES.OBJ, { durable: true })
|
||||
await channel.assertQueue(QUEUES.OBJDLX, { durable: true })
|
||||
await channel.assertQueue(QUEUES.OBJDEL, {
|
||||
durable: true,
|
||||
arguments: {
|
||||
|
||||
@@ -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
|
||||
})
|
||||
@@ -31,7 +31,6 @@ const DEFAULT_DATA_JWT = {
|
||||
iss: env.OBJ_CLIENT_ID,
|
||||
aud: "https://idp.docapost.io/auth/realms/GETWAY",
|
||||
jti: Date.now().toString(),
|
||||
|
||||
}
|
||||
|
||||
function addIATHeaders(authHeaders: Object) {
|
||||
|
||||
46
packages/sim-consumidor-objenious/domain/transformers.ts
Normal file
46
packages/sim-consumidor-objenious/domain/transformers.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { CommonSim } from "sim-shared/domain/CommonSim.js";
|
||||
import { ObjeniousLine } from "sim-shared/domain/objeniousLine.js";
|
||||
|
||||
type ObjeniousBillingStates = ObjeniousLine["status"]["billingStatus"]
|
||||
type ObjeniousNetworkStates = ObjeniousLine["status"]["networkStatus"]
|
||||
|
||||
//"PREACTIVATED" | "ACTIVE" | "SUSPENDED" | "TERMINATED" | "UNKNOWN",
|
||||
const objeiousStates = new Map<ObjeniousBillingStates, CommonSim<any>["billing_status"]>([
|
||||
["ACTIVATED", "ACTIVE"],
|
||||
["CANCELED", "TERMINATED"],
|
||||
["SUSPENDED", "SUSPENDED"],
|
||||
["TEST", "PREACTIVATED"]
|
||||
])
|
||||
|
||||
const objeiousNetworkStates = new Map<ObjeniousNetworkStates, CommonSim<any>["network_status"]>([
|
||||
["ACTIVATED", "ACTIVE"],
|
||||
["CANCELED", "TERMINATED"],
|
||||
["SUSPENDED", "SUSPENDED"],
|
||||
["BARRED", "SUSPENDED"]
|
||||
])
|
||||
|
||||
export function objeniousSimToCommon(objSim: ObjeniousLine): CommonSim<ObjeniousLine> {
|
||||
const status = objeiousStates.get(objSim.status.billingStatus) ?? "UNKNOWN"
|
||||
const networkStatus = objeiousNetworkStates.get(objSim.status.networkStatus) ?? "UNKNOWN"
|
||||
|
||||
const preDate = objSim.status.preactivationDate
|
||||
const actDate = objSim.status.activationDate
|
||||
|
||||
const preactivate = (preDate) ? new Date(preDate) : null
|
||||
const activate = (actDate) ? new Date(actDate) : null
|
||||
|
||||
const common: CommonSim<ObjeniousLine> = {
|
||||
company: "OBJ",
|
||||
tariff: objSim.offer.code,
|
||||
iccid: objSim.identifier.iccid,
|
||||
msisdn: objSim.identifier.msisdn,
|
||||
billing_status: status,
|
||||
network_status: networkStatus,
|
||||
raw: objSim,
|
||||
imei: objSim.identifier.imei,
|
||||
preactivation_date: preactivate,
|
||||
activation_date: activate
|
||||
}
|
||||
|
||||
return common
|
||||
}
|
||||
@@ -10,6 +10,13 @@ import { SimRouter } from "./aplication/Sim.router.js"
|
||||
import { OrderRepository } from "sim-shared/infrastructure/OrderRepository.js"
|
||||
import { PauseCancelTaskRepository } from "#adapters/PauseCancelTaskRepository.js"
|
||||
|
||||
import express from "express"
|
||||
import cors from "cors"
|
||||
import assert from "node:assert";
|
||||
import { env } from "#config/env/index.js"
|
||||
import { ObjeniousLinesRepository } from "sim-shared/infrastructure/ObjeniousLinesRepository.js"
|
||||
import { postgresClientIntranet } from "#config/intranetPostgresConfig.js"
|
||||
|
||||
async function startWorker() {
|
||||
const rmqClient = await startRMQClient()
|
||||
|
||||
@@ -26,20 +33,77 @@ async function startWorker() {
|
||||
const orderRepository = new OrderRepository(pgClient)
|
||||
|
||||
const pauseRepository = new PauseCancelTaskRepository(pgClient)
|
||||
|
||||
const simActivationController = new SimController(
|
||||
rmqClient,
|
||||
new SimUseCases({
|
||||
httpClient: httpClient,
|
||||
operationRepository: operationRepository,
|
||||
orderRepository: orderRepository,
|
||||
pauseRepository: pauseRepository
|
||||
})
|
||||
const linesRepository = new ObjeniousLinesRepository(
|
||||
postgresClientIntranet
|
||||
)
|
||||
const simRouter = new SimRouter(simActivationController, rmqClient)
|
||||
|
||||
const simUseCases = new SimUseCases({
|
||||
httpClient: httpClient,
|
||||
operationRepository: operationRepository,
|
||||
orderRepository: orderRepository,
|
||||
pauseRepository: pauseRepository,
|
||||
objeniousLinesRepository: linesRepository
|
||||
})
|
||||
|
||||
const simController = new SimController(
|
||||
rmqClient,
|
||||
simUseCases
|
||||
)
|
||||
const simRouter = new SimRouter(simController, rmqClient)
|
||||
|
||||
// de momento solo una cola por simplificar
|
||||
rmqClient.consume("sim.objenious", simRouter.route)
|
||||
|
||||
// Servidor express
|
||||
const port = env.PORT
|
||||
const app = express()
|
||||
app.use(cors())
|
||||
app.use(express.json())
|
||||
|
||||
app.get("/health", async (req, res) => {
|
||||
res.json({ ok: "true" })
|
||||
})
|
||||
|
||||
// TODO: meter el template de controller con los validadores
|
||||
app.get("/lines/:iccid/suspended-time", async (req, res) => {
|
||||
const iccid = req.params.iccid
|
||||
if (!iccid) {
|
||||
res.status(400).json({ error: "iccid is required" })
|
||||
return
|
||||
}
|
||||
|
||||
const result = await simUseCases.getSuspendedTime(iccid)
|
||||
if (result.error !== undefined) {
|
||||
res.status(500).json({ error: result.error })
|
||||
return
|
||||
}
|
||||
|
||||
res.json(result) // {data:{...}} || {error:{...}}
|
||||
})
|
||||
|
||||
/**
|
||||
* Opciones query:
|
||||
* - state
|
||||
*
|
||||
* Respuestas:
|
||||
* - OK: data: {
|
||||
* lines: ObjeniousLineDb[],
|
||||
* offset: number,
|
||||
* rowCount: number
|
||||
* }
|
||||
*
|
||||
* - ERR: error: {
|
||||
* message: string
|
||||
* }
|
||||
*/
|
||||
app.get("/lines", simController.queryLines())
|
||||
app.get("/select", simController.queryLine())
|
||||
|
||||
|
||||
assert.ok(port, "Puerto del servicio no definido")
|
||||
app.listen(port, () => {
|
||||
console.log(`[o] HTTP server listening on port ${port}`)
|
||||
})
|
||||
}
|
||||
|
||||
startWorker()
|
||||
|
||||
@@ -13,10 +13,10 @@
|
||||
"types": "./config/*.ts",
|
||||
"default": "./config/*.js"
|
||||
},
|
||||
"#shared/*.js": {
|
||||
"sim-shared/*.js": {
|
||||
"default": "../sim-shared/*.js"
|
||||
},
|
||||
"#shared/*": {
|
||||
"sim-shared/*": {
|
||||
"default": "../sim-shared/*.js"
|
||||
},
|
||||
"#adapters/*.js": {
|
||||
@@ -55,7 +55,8 @@
|
||||
"scripts": {
|
||||
"test": "node --import tsx --test ./**/*.test.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 -P ./.env ./package.json ../../dist/packages/sim-consumidor-objenious/",
|
||||
"build:prod": "tsc --build && yarn tsc-alias -p tsconfig.json && cp -P ./package.json ../../dist/packages/sim-consumidor-objenious/",
|
||||
"start": "node ../../dist/packages/sim-consumidor-objenious/index.js",
|
||||
"type:test": "tsc --noEmit"
|
||||
},
|
||||
|
||||
0
packages/sim-entrada-eventos/README.md
Normal file
0
packages/sim-entrada-eventos/README.md
Normal file
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user