Compare commits
29 Commits
1.5.2
...
WEBINT-335
| Author | SHA1 | Date | |
|---|---|---|---|
| 86478b1073 | |||
| 976cf1c3d2 | |||
| e4ba1576e5 | |||
| 4baa9f708f | |||
| 6fb25e6055 | |||
| 63698ee1aa | |||
| 410f659db0 | |||
| 08c972e720 | |||
| c4e4d87303 | |||
| 9c74fb9a7b | |||
| 1d7c2b2946 | |||
| d2c86396b1 | |||
| 3cf5c3695e | |||
| 7dda25fbfb | |||
| eefb7c5a79 | |||
| 13944a64d2 | |||
| 07e60690ab | |||
| 036ae20ac3 | |||
| 189de6c0fb | |||
| 113d9f3786 | |||
| 331d920379 | |||
| a615fc2b81 | |||
| f98097d11d | |||
| 3e76c3c931 | |||
| bb31efb271 | |||
| 858932f260 | |||
| a84f600fa2 | |||
| 4e02ea021d | |||
| 9ec127433d |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -16,8 +16,10 @@ node_modules
|
||||
#!.yarn/cache
|
||||
.pnp.*
|
||||
|
||||
# Certificados
|
||||
*.pem
|
||||
|
||||
*.p12
|
||||
*.key
|
||||
|
||||
dist/*
|
||||
|
||||
|
||||
@@ -43,3 +43,4 @@ La decisión del numero de reintentos y la cola de dlx se hace en los 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
|
||||
|
||||
@@ -44,19 +44,27 @@ pipeline {
|
||||
),
|
||||
sshTransfer(
|
||||
cleanRemote: false,
|
||||
execCommand: "ln -sf $BASE_REMOTE_PATH/vault/savefamily/sf-sims/sim-consumidor-objenious.env $APP_REMOTE_PATH/sim-consumidor-objenious.env"
|
||||
execCommand: "ln -sf $BASE_REMOTE_PATH/vault/savefamily/sf-sims/sim-consumidor-objenious.env $APP_REMOTE_PATH/packages/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"
|
||||
execCommand: "ln -sf $BASE_REMOTE_PATH/vault/savefamily/sf-sims/sim-consumidor-nos.env $APP_REMOTE_PATH/packages/sim-consumidor-nos/.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"
|
||||
execCommand: "ln -sf $BASE_REMOTE_PATH/vault/savefamily/sf-sims/sim-consumidor-alai.env $APP_REMOTE_PATH/packages/sim-consumidor-alai/.env"
|
||||
),
|
||||
sshTransfer(
|
||||
cleanRemote: false,
|
||||
execCommand: "ln -sf $BASE_REMOTE_PATH/vault/savefamily/sf-sims/obj.pem $APP_REMOTE_PATH/obj.pem"
|
||||
execCommand: "ln -sf $BASE_REMOTE_PATH/vault/savefamily/sf-sims/wsaccess_alaisecure_com_cert_client_new.p12 $APP_REMOTE_PATH/packages/wsaccess_alaisecure_com_cert_client_new.p12"
|
||||
),
|
||||
sshTransfer(
|
||||
cleanRemote: false,
|
||||
execCommand: "ln -sf $BASE_REMOTE_PATH/vault/savefamily/sf-sims/sim-objenious-cron.env $APP_REMOTE_PATH/packages/sim-objenious-cron/.env"
|
||||
),
|
||||
sshTransfer(
|
||||
cleanRemote: false,
|
||||
execCommand: "ln -sf $BASE_REMOTE_PATH/vault/savefamily/sf-sims/obj.pem $APP_REMOTE_PATH/packages/sim-consumidor-objenious/obj.pem"
|
||||
),
|
||||
sshTransfer(
|
||||
cleanRemote: false,
|
||||
|
||||
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
|
||||
@@ -6,16 +6,80 @@ meta {
|
||||
|
||||
post {
|
||||
url: {{baseurl}}/sim/activate
|
||||
body: formUrlEncoded
|
||||
body: json
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"iccid": "1234"
|
||||
}
|
||||
}
|
||||
|
||||
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: 8933201125068890892
|
||||
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
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
meta {
|
||||
name: France Suspended Lines
|
||||
type: http
|
||||
seq: 17
|
||||
seq: 16
|
||||
}
|
||||
|
||||
get {
|
||||
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 {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
meta {
|
||||
name: Preactivate
|
||||
type: http
|
||||
seq: 1
|
||||
seq: 6
|
||||
}
|
||||
|
||||
post {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
meta {
|
||||
name: ReActivate
|
||||
type: http
|
||||
seq: 13
|
||||
seq: 12
|
||||
}
|
||||
|
||||
post {
|
||||
|
||||
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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
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
|
||||
};
|
||||
}
|
||||
195
packages/sim-consumidor-alai/aplication/SimAlai.controller.ts
Normal file
195
packages/sim-consumidor-alai/aplication/SimAlai.controller.ts
Normal file
@@ -0,0 +1,195 @@
|
||||
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";
|
||||
|
||||
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<string, T>>): Promise<Result<string, T>> {
|
||||
try {
|
||||
const result = await usecase()
|
||||
if (result.error == undefined) {
|
||||
await this.eventBus.ack(msg)
|
||||
return result
|
||||
} else {
|
||||
console.error("Error procesando el caso de uso (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: String(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public activate() {
|
||||
return async (msg: ConsumeMessage) => {
|
||||
console.log("[i] Evento activate ", msg.fields)
|
||||
const data = this.validateMsg(msg) as SimEvents.activation
|
||||
const iccid = data.payload.iccid
|
||||
const correlation_id = data.headers?.message_id
|
||||
const res = await this.tryUseCase(msg, this.uscases.activate({
|
||||
iccid: iccid,
|
||||
correlation_id: correlation_id
|
||||
}))
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
public suspend() {
|
||||
return async (msg: ConsumeMessage) => {
|
||||
console.log("Evento suspend ", msg.fields)
|
||||
const data = this.validateMsg(msg) as SimEvents.suspend
|
||||
const iccid = data.payload.iccid
|
||||
const correlation_id = data.headers?.message_id
|
||||
const res = await this.tryUseCase(msg, this.uscases.suspend({
|
||||
iccid: iccid,
|
||||
correlation_id: correlation_id
|
||||
}))
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public 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)
|
||||
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<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.activate()]
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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];
|
||||
}
|
||||
}
|
||||
262
packages/sim-consumidor-alai/aplication/SimAlai.usecases.ts
Normal file
262
packages/sim-consumidor-alai/aplication/SimAlai.usecases.ts
Normal file
@@ -0,0 +1,262 @@
|
||||
/**
|
||||
* 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 { ConsumeMessage } from "amqplib";
|
||||
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";
|
||||
import { isOmittedExpression } from "typescript";
|
||||
|
||||
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, detail?: string) {
|
||||
// En NOS el updateOrder se hace con el correlation_id que viene en la cabecera del
|
||||
// mensaje consumido
|
||||
const updateData: ErrorOrderDTO = {
|
||||
status: "failed",
|
||||
correlation_id: correlation_id,
|
||||
reason: reason,
|
||||
error: reason,
|
||||
stackTrace: detail
|
||||
}
|
||||
|
||||
console.log("SET FAILED DATA:", updateData)
|
||||
const order = await this.orderRepository.errorOrder(updateData)
|
||||
console.log("SET FAILED RES:", order)
|
||||
return order
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<string, R>>,
|
||||
args: T,
|
||||
correlation_id?: string | undefined
|
||||
) {
|
||||
return async (): Promise<Result<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)
|
||||
.then(e => console.log("failed", e))
|
||||
.catch(e => console.error(e))
|
||||
return res;
|
||||
} else {
|
||||
if (correlation_id != undefined)
|
||||
this.setFinished(correlation_id).then()
|
||||
return res;
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
if (correlation_id != undefined)
|
||||
this.setFailed(correlation_id, "Error general de operacion de SIM (NOS) ", String(e)).then()
|
||||
return {
|
||||
error: "Error general de operacion de SIM (NOS) " + String(e)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public activate(args: {
|
||||
iccid: string,
|
||||
correlation_id: string | undefined
|
||||
}) {
|
||||
return this.usecaseTemplate(async (iccid /*iccid*/) => {
|
||||
const order = await this.alaiRepository.createOrder()
|
||||
if (order.error != undefined) {
|
||||
// Falla el crearse un order (problema de servidor, token, etc)
|
||||
console.error(order.error)
|
||||
return order
|
||||
}
|
||||
const reserved = await this.alaiRepository.createReserve(order.data.id, iccid)
|
||||
return reserved
|
||||
}, 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) {
|
||||
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 suspension = this.alaiRepository.pauseSubscription(subscription.data!.id)
|
||||
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
|
||||
}
|
||||
|
||||
// TODO: Controlar que no se encuentre la subscription
|
||||
const suspension = this.alaiRepository.unPauseSubscription(subscription.data!.id)
|
||||
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<string, {
|
||||
sim: AlaiAPI.Sim,
|
||||
subscription: AlaiAPI.Subscription,
|
||||
imei: AlaiAPI.GetImeiSubscriptionDTO
|
||||
}>> {
|
||||
const sim = await this.alaiRepository.getSimByICCID(iccid)
|
||||
|
||||
if (sim.error != undefined) {
|
||||
return sim
|
||||
}
|
||||
|
||||
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.eyJiciI6InNhdmVmYW1pbHkiLCJpcCI6Ijg4LjE1LjE1Ny4xNjciLCJzdWIiOiJwYWxvbWFpYmFuZXoiLCJzIjoiRVdTMTY0NmFmNjNlZGUyMjgzIiwicG9zIjoic2F2ZWZhbWlseUNhYyIsImlkV3NVc2VyIjoiODYiLCJpc012bmEiOmZhbHNlLCJkb21haW4iOiJBbGFpfHNhdmVmYW1pbHkiLCJpYXQiOjE3Nzc4OTk3MzcsImV4cCI6MTc3NzkxMDUzN30.PvTTRhUpKlslGOerQsLY4RLBXdQ5FIVvUKb_1ZK4b2Zggt04KZhwX0d-XoLAcP93
|
||||
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
|
||||
}
|
||||
17
packages/sim-consumidor-alai/config/httpClient.config.ts
Normal file
17
packages/sim-consumidor-alai/config/httpClient.config.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
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: {},
|
||||
jwtManager: tokenManager,
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
47
packages/sim-consumidor-alai/domain/transformers.ts
Normal file
47
packages/sim-consumidor-alai/domain/transformers.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
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<AlaiAPI.Subscription>> {
|
||||
|
||||
const status = alaiStates.get(alaiSubscription.status) ?? "UNKNOWN"
|
||||
const networkStatus = alaiNetworkStates.get(alaiSubscription.networkStatus) ?? "UNKNOWN"
|
||||
|
||||
const commonSim: CommonSim<AlaiAPI.Subscription> = {
|
||||
company: "ALAI",
|
||||
tariff: alaiSubscription.name,
|
||||
iccid: alaiSim.id,
|
||||
msisdn: alaiSubscription.lastMsisdnID,
|
||||
billing_status: status,
|
||||
network_status: networkStatus,
|
||||
raw: alaiSubscription,
|
||||
imei: imeiSubscription.content[0].id ?? "0",
|
||||
preactivation_date: new Date(alaiSubscription.creationDate),
|
||||
activation_date: new Date(alaiSubscription.firstActivationDate)
|
||||
}
|
||||
|
||||
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 {}
|
||||
190
packages/sim-consumidor-alai/infrastructure/AlaiRepository.ts
Normal file
190
packages/sim-consumidor-alai/infrastructure/AlaiRepository.ts
Normal file
@@ -0,0 +1,190 @@
|
||||
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"
|
||||
|
||||
export class AlaiRepository {
|
||||
constructor(
|
||||
private httpClient: HttpClient
|
||||
) {
|
||||
}
|
||||
|
||||
private async manageRequest<E, T>(promiseReq: Promise<AxiosResponse<T>>): Promise<Result<string, T>> {
|
||||
try {
|
||||
const res = await promiseReq
|
||||
return {
|
||||
data: res.data
|
||||
}
|
||||
} catch (e) {
|
||||
if (axios.isAxiosError(e)) {
|
||||
const error = e as AxiosError
|
||||
return {
|
||||
error: error.code + " : " + String(error.response?.statusText)
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
error: String(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static async login(httpsAgent: https.Agent): Promise<Result<string, 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: error.code + " : " + String(error.response?.statusText)
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
error: 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<string, 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 unPauseSubscription(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 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
71
packages/sim-consumidor-alai/package.json
Normal file
71
packages/sim-consumidor-alai/package.json
Normal file
@@ -0,0 +1,71 @@
|
||||
{
|
||||
"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 package.json ../../dist/packages/sim-consumidor-nos/ && cp -r certificates/ ../../dist/packages/sim-consumidor-alai/",
|
||||
"esbuild": "esbuild index.ts --platform=node",
|
||||
"start": "node ../../dist/packages/sim-consumidor-nos/index.js",
|
||||
"dev": "tsx watch index.ts"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"packageManager": "yarn@4.12.0",
|
||||
"dependencies": {
|
||||
"@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"
|
||||
]
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,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),
|
||||
APP_HOST: String(process.env.NOS_HOST),
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
# claves de Objenious
|
||||
HOST=0.0.0.0
|
||||
|
||||
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
|
||||
@@ -7,6 +7,8 @@ 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
|
||||
@@ -236,7 +238,7 @@ export class SimController {
|
||||
|
||||
const validationRes = paginationValidator.validate(paginationArgs)
|
||||
if (validationRes.error != undefined) {
|
||||
res.status(402).json(validationRes)
|
||||
res.status(422).json(validationRes)
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -252,6 +254,28 @@ export class SimController {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
||||
@@ -488,4 +488,32 @@ export class SimUseCases {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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]
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -97,6 +97,7 @@ async function startWorker() {
|
||||
* }
|
||||
*/
|
||||
app.get("/lines", simController.queryLines())
|
||||
app.get("/select", simController.queryLine())
|
||||
|
||||
|
||||
assert.ok(port, "Puerto del servicio no definido")
|
||||
|
||||
0
packages/sim-entrada-eventos/README.md
Normal file
0
packages/sim-entrada-eventos/README.md
Normal file
@@ -32,14 +32,23 @@ export class OrderController {
|
||||
}
|
||||
|
||||
public getByQueueId() {
|
||||
return this.controllerGenerator<{ correlation_id: string }, { correlation_id: string }>({
|
||||
return this.controllerGenerator<{ uuid: string }, { correlation_id: string }>({
|
||||
validator: uuidValidator,
|
||||
mapBody: (e) => ({ correlation_id: e.uuid }),
|
||||
useCase: this.orderUseCases.getByQueueId(),
|
||||
onError: (data, error) => { console.error(error) },
|
||||
onSuccess: (data) => console.log(data)
|
||||
})
|
||||
}
|
||||
|
||||
public getByQuery() {
|
||||
return this.controllerGenerator({
|
||||
validator: undefined,
|
||||
useCase: this.orderUseCases.getByQuery(),
|
||||
onError: (data, error) => { console.error(error) },
|
||||
onSuccess: (data) => console.log(data)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO:
|
||||
@@ -77,7 +86,7 @@ export class OrderController {
|
||||
})
|
||||
}
|
||||
|
||||
// 2. Transformacion del body
|
||||
// 2. Transformacion del body O => P
|
||||
let data: P = body;
|
||||
try {
|
||||
if (args.mapBody != undefined)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { OrderQuery } from "sim-shared/domain/Order.js";
|
||||
import { PaginationArgs } from "sim-shared/domain/PaginationArgs.js";
|
||||
import { OrderRepository } from "sim-shared/infrastructure/OrderRepository.js";
|
||||
|
||||
@@ -36,4 +37,10 @@ export class OrderUsecases {
|
||||
}
|
||||
}
|
||||
|
||||
// WIP
|
||||
public getByQuery() {
|
||||
return async (args: OrderQuery) => {
|
||||
return await this.orderRepository.getOrdersByQuery(args)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ import { activationValidator, iccidValidator } from "./httpValidators.js"
|
||||
import { companyFromIccid } from "#domain/companies.js"
|
||||
import { BodyValidator } from "sim-shared/aplication/BodyValidator.js"
|
||||
import { tryCatch } from "sim-shared/domain/Result.js"
|
||||
import { mapCompanyService } from "#config/servicesProxy.js"
|
||||
import axios, { AxiosError, isAxiosError } from "axios"
|
||||
|
||||
|
||||
export class SimController {
|
||||
@@ -208,6 +210,59 @@ export class SimController {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Select no pasa por la cola de eventos al ser de solo lectura.
|
||||
* Cada uno de los servicios de los proveedores tiene que aderirse al
|
||||
* modelo común de datos de SIM + campo "raw"
|
||||
*
|
||||
* De momento se va a buscar por iccid, mas adlante por movil u otro criterio
|
||||
*/
|
||||
public select() {
|
||||
return async (req: Request, res: Response) => {
|
||||
console.log("SELECT: ", req.query)
|
||||
const iccid = req.query.iccid as string
|
||||
const validationRes = iccidValidator.validate({ iccid: iccid })
|
||||
if (validationRes.error != undefined) {
|
||||
res.status(422).json({
|
||||
errors: {
|
||||
...validationRes.error
|
||||
}
|
||||
})
|
||||
return;
|
||||
}
|
||||
|
||||
const company = companyFromIccid(iccid)
|
||||
const url = mapCompanyService.get(company)
|
||||
const endpoint = "/select"
|
||||
|
||||
if (url == undefined) {
|
||||
console.error("[x] Error buscando el servicio para el select del iccid ", iccid)
|
||||
}
|
||||
|
||||
try {
|
||||
const respSelect = await axios.get(url + endpoint, { params: req.query })
|
||||
res.json(respSelect.data)
|
||||
|
||||
} catch (err) {
|
||||
if (isAxiosError(err)) {
|
||||
const axiosErr = err as AxiosError
|
||||
res.status(axiosErr.status ?? 500).json(err)
|
||||
} else {
|
||||
res.status(500).json({
|
||||
errors: {
|
||||
msg: "Error general buscando la sim"
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* -- WIP
|
||||
* Esta funcion se plantea para guardar tarjetas que no han llegado desde
|
||||
* un operador conocido
|
||||
*/
|
||||
public save() {
|
||||
return async (req: Request, res: Response) => {
|
||||
try {
|
||||
|
||||
@@ -270,5 +270,6 @@ export class SimUsecases {
|
||||
|
||||
return this.eventBus.publish([cancelationEvent])
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -32,10 +32,10 @@ const offerExists = <Validator<{ offer: string }>>{
|
||||
validationFunc: (a: { offer: string }) => offers.has(a.offer),
|
||||
}
|
||||
|
||||
const isUuidv7 = <Validator<{ correlation_id?: string }>>{
|
||||
field: "correlation_id",
|
||||
const isUuidv7 = <Validator<{ uuid?: string }>>{
|
||||
field: "uuid",
|
||||
errorMsg: "El uuid no es un uuidv7 valido",
|
||||
validationFunc: (a) => a.correlation_id != undefined && a.correlation_id.length < 36
|
||||
validationFunc: (a) => a.uuid != undefined && a.uuid.length < 36
|
||||
}
|
||||
|
||||
const definedId = <Validator<{ id?: number }>>{
|
||||
@@ -56,12 +56,27 @@ const validNumericId = <Validator<{ id?: number }>>{
|
||||
validationFunc: (e) => e.id! >= 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Por un problema arrastrado de alai, se tiene que guardar el orderId del pedido
|
||||
* de la sim en un campo de Alai.
|
||||
*/
|
||||
const ifAlaiOrderId = <Validator<{ iccid: string, orderId?: string }>>{
|
||||
field: "orderId",
|
||||
errorMsg: "Es necesario incluir un id de pedido (orderId) en las activaciones de Alai",
|
||||
validationFunc: (e) => {
|
||||
const company = companyFromIccid(e.iccid)
|
||||
if (company == "alai" && e.orderId == undefined) return false
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
export const activationValidator = new BodyValidator<{ iccid: string, offer: string }>(
|
||||
[
|
||||
iccidRequired,
|
||||
iccidLongitudValidator,
|
||||
iccidWithValidCompany,
|
||||
offerExists,
|
||||
ifAlaiOrderId
|
||||
]
|
||||
)
|
||||
|
||||
@@ -73,7 +88,7 @@ export const iccidValidator = new BodyValidator<{ iccid: string }>(
|
||||
]
|
||||
)
|
||||
|
||||
export const uuidValidator = new BodyValidator<{ correlation_id?: string }>([
|
||||
export const uuidValidator = new BodyValidator<{ uuid?: string }>([
|
||||
isUuidv7
|
||||
])
|
||||
|
||||
|
||||
@@ -24,6 +24,8 @@ export const env = {
|
||||
RABBITMQ_RETRY_INTERVAL: process.env.RABBITMQ_INTERVAL,
|
||||
RABBITMQ_VHOST: String(process.env.RABBITMQ_VHOST),
|
||||
CONNECTIONS_URL: String(process.env.CONNECTIONS_URL),
|
||||
|
||||
OBJENIOUS_CONSUMER_URL: process.env.OBJENIOUS_CONSUMER_URL,
|
||||
NOS_CONSUMER_URL: process.env.NOS_CONSUMER_URL,
|
||||
ALAI_CONSUMER_URL: process.env.ALAI_CONSUMER_URL,
|
||||
};
|
||||
|
||||
10
packages/sim-entrada-eventos/config/servicesProxy.ts
Normal file
10
packages/sim-entrada-eventos/config/servicesProxy.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { env } from "./env/index.js";
|
||||
|
||||
|
||||
export const mapCompanyService = new Map([
|
||||
["alai", env.ALAI_CONSUMER_URL],
|
||||
["nos", env.NOS_CONSUMER_URL],
|
||||
["objenious", env.OBJENIOUS_CONSUMER_URL]
|
||||
])
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import { env } from "#config/env/index.js"
|
||||
import { orderRoutes } from "#adapters/orderRoutes.http.js";
|
||||
import { connectionsRoutes } from "#adapters/simconnectionsRoutes.js";
|
||||
import { franceRoutes } from "#adapters/franceRoutes.http.js";
|
||||
import { spainRoutes } from "#adapters/spainRoutes.http.js";
|
||||
|
||||
const PORT = env.API_PORT
|
||||
const HOSTNAME = "0.0.0.0"
|
||||
@@ -35,6 +36,11 @@ app.use("/docs", express.static(path.join(process.cwd(), '../../docs')))
|
||||
// Rutas especificas para casos especiales como el tiempo de suspension de francia
|
||||
app.use("/france", franceRoutes)
|
||||
|
||||
// Rutas especificas de España (Alai)
|
||||
app.use("/spain", spainRoutes)
|
||||
|
||||
//TODO: app.use("/portugal", portugalRoutes)
|
||||
|
||||
app.get("/health", (req, res) => {
|
||||
res.status(200).json({ status: "ok" })
|
||||
})
|
||||
|
||||
@@ -23,7 +23,7 @@ franceRoutes.use("", createProxyMiddleware({
|
||||
const host = req.get('host');
|
||||
const originalFullUrl = `${protocol}://${host}${req.originalUrl}`;
|
||||
const destinationFullUrl = `${FRANCE_URL}${proxyReq.path}`;
|
||||
//console.log(`[Proxy Req]: ${req.method} ${req.url} -> ${proxyReq.path}`);
|
||||
console.log(`[Proxy FR]: ${req.method} ${req.url} -> ${proxyReq.path}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,13 +28,17 @@ const orderController = new OrderController({
|
||||
* */
|
||||
orderRoutes.get("/", (req, res) => { res.send("ok") })
|
||||
|
||||
orderRoutes.get("/message_id/:correlation_id", orderController.getByQueueId())
|
||||
/*
|
||||
* Ahora es el id de bdd
|
||||
* */
|
||||
orderRoutes.get("/message_id/:id", orderController.getById())
|
||||
|
||||
/** Operaciones pendientes */
|
||||
orderRoutes.get("/pending", orderController.getPending())
|
||||
|
||||
/** Order por id (uuid del mensaje) */
|
||||
orderRoutes.get("/:id", orderController.getById())
|
||||
// TODO: falla
|
||||
orderRoutes.get("/:id", orderController.getByQueueId())
|
||||
|
||||
export { orderRoutes }
|
||||
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import { Router, Request } from "express"
|
||||
import { ClientRequest, } from "http"
|
||||
import { createProxyMiddleware } from "http-proxy-middleware"
|
||||
import { env } from "#config/env/index.js"
|
||||
import assert from "node:assert"
|
||||
|
||||
const portugalRoutes = Router()
|
||||
|
||||
const PORTUGAL_URL = env.NOS_CONSUMER_URL
|
||||
assert.ok(PORTUGAL_URL, "No se ha definido una URL para el servicio consumidor de Francia")
|
||||
|
||||
portugalRoutes.use("", createProxyMiddleware({
|
||||
target: PORTUGAL_URL,
|
||||
changeOrigin: true,
|
||||
pathRewrite: {
|
||||
'^/spain/*': '/'
|
||||
},
|
||||
|
||||
on: {
|
||||
proxyReq: (proxyReq: ClientRequest, req: Request) => {
|
||||
/* Debug de las peticiones */
|
||||
const protocol = req.protocol;
|
||||
const host = req.get('host');
|
||||
const originalFullUrl = `${protocol}://${host}${req.originalUrl}`;
|
||||
const destinationFullUrl = `${PORTUGAL_URL}${proxyReq.path}`;
|
||||
console.log(`[Proxy PT]: ${req.method} ${req.url} -> ${proxyReq.path}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
))
|
||||
|
||||
export { portugalRoutes }
|
||||
@@ -1,9 +1,11 @@
|
||||
import { rabbitmqEventBus } from '../config/eventBusConfig.js';
|
||||
import { SimUsecases } from '../aplication/Sim.usecases.js';
|
||||
import { SimController } from '../aplication/Sim.controller.js';
|
||||
import { Router } from 'express';
|
||||
import { OrderRepository } from 'sim-shared/infrastructure/OrderRepository.js';
|
||||
import { postgresClient } from '#config/postgreConfig.js';
|
||||
import { createProxyMiddleware } from 'http-proxy-middleware';
|
||||
import { ClientRequest, } from "http"
|
||||
import { Router, Request } from "express"
|
||||
|
||||
const simRoutes = Router()
|
||||
const orderRepository = new OrderRepository(postgresClient)
|
||||
@@ -37,4 +39,6 @@ simRoutes.post("/test", simController.test())
|
||||
simRoutes.post("/free", simController.free())
|
||||
|
||||
|
||||
// WIP
|
||||
simRoutes.get("/select", simController.select())
|
||||
export { simRoutes }
|
||||
|
||||
@@ -9,8 +9,7 @@ export const connectionsRoutes = Router()
|
||||
const CONNECTIONS_URL = env.CONNECTIONS_URL// TODO: Meter al ENV
|
||||
//const CONNECTIONS_URL = "http://sf-nfc-server.savefamilygps.net"
|
||||
|
||||
console.log("CONNURL: ", CONNECTIONS_URL)
|
||||
|
||||
//console.log("CONNURL: ", CONNECTIONS_URL)
|
||||
connectionsRoutes.use("", createProxyMiddleware({
|
||||
target: CONNECTIONS_URL,
|
||||
changeOrigin: true,
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import { Router, Request } from "express"
|
||||
import { ClientRequest, } from "http"
|
||||
import { createProxyMiddleware } from "http-proxy-middleware"
|
||||
import { env } from "#config/env/index.js"
|
||||
import assert from "node:assert"
|
||||
|
||||
const spainRoutes = Router()
|
||||
|
||||
const SPAIN_URL = env.ALAI_CONSUMER_URL
|
||||
assert.ok(SPAIN_URL, "No se ha definido una URL para el servicio consumidor de Francia")
|
||||
|
||||
spainRoutes.use("", createProxyMiddleware({
|
||||
target: SPAIN_URL,
|
||||
changeOrigin: true,
|
||||
pathRewrite: {
|
||||
'^/spain/*': '/'
|
||||
},
|
||||
|
||||
on: {
|
||||
proxyReq: (proxyReq: ClientRequest, req: Request) => {
|
||||
/* Debug de las peticiones */
|
||||
const protocol = req.protocol;
|
||||
const host = req.get('host');
|
||||
const originalFullUrl = `${protocol}://${host}${req.originalUrl}`;
|
||||
const destinationFullUrl = `${SPAIN_URL}${proxyReq.path}`;
|
||||
console.log(`[Proxy ES]: ${req.method} ${req.url} -> ${proxyReq.path}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
))
|
||||
|
||||
//orderRoutes.get("/:iccid/suspended-time",)
|
||||
export { spainRoutes }
|
||||
@@ -1,7 +1,7 @@
|
||||
import { test, describe } from "vitest"
|
||||
import { jwtService } from "../config/jwtService.config.js"
|
||||
|
||||
describe("Tokens Objenious", () => {
|
||||
/*
|
||||
describe("Tokens Objenious", (test) => {
|
||||
const jwt = jwtService
|
||||
|
||||
test("Solicicitud normal de auth", async () => {
|
||||
@@ -14,4 +14,4 @@ describe("Tokens Objenious", () => {
|
||||
console.log("acceso refresh objenious", token)
|
||||
})
|
||||
})
|
||||
|
||||
*/
|
||||
|
||||
26
packages/sim-shared/domain/CommonSim.ts
Normal file
26
packages/sim-shared/domain/CommonSim.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
export type CommonSim<T> = {
|
||||
company: "NOS" | "OBJ" | "ALAI",
|
||||
iccid: string,
|
||||
msisdn: string,
|
||||
tariff: string, // Depende de la compañia
|
||||
billing_status: "PREACTIVATED" | "ACTIVE" | "SUSPENDED" | "TERMINATED" | "UNKNOWN",
|
||||
network_status: "AVAILABLE" | "PREACTIVATED" | "ACTIVE" | "SUSPENDED" | "TERMINATED" | "UNKNOWN",
|
||||
preactivation_date?: Date | null,
|
||||
activation_date?: Date | null,
|
||||
suspension_date?: Date | null,
|
||||
termination_date?: Date | null,
|
||||
imei?: string,
|
||||
raw: T
|
||||
}
|
||||
|
||||
/**
|
||||
* Acorde a una peticion rest donde `raw` va a depender de `company`
|
||||
*/
|
||||
export type CommonSimDTO = CommonSim<Record<string, string>> & {
|
||||
preactivation_date?: string | null,
|
||||
activation_date?: string | null,
|
||||
suspension_date?: string | null,
|
||||
termination_date?: string | null,
|
||||
}
|
||||
|
||||
|
||||
@@ -94,4 +94,19 @@ export type ErrorOrderDTO =
|
||||
stackTrace?: string
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Se considera cada entrada de conditions como un filtro sobre un campo
|
||||
* cada fila se podrá expresar como campo:filtro
|
||||
* ```json
|
||||
* {
|
||||
* "value": "-gte 200" // Un valor >= 200
|
||||
* "text": "-eq 'busqueda' " // El campo tiene que ser exactamente 'busqueada'
|
||||
* }
|
||||
* ```
|
||||
* TODO: sacar opciones de paginación
|
||||
* */
|
||||
export type OrderQuery = {
|
||||
conditions: Record<string, string>,
|
||||
limit?: number | undefined,
|
||||
offset?: number | undefined,
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user