Compare commits
204 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3f7c052572 | |||
| 3d9e2a6e9b | |||
| f94ee799d0 | |||
| d1e5892a0d | |||
| b14464da39 | |||
| cf6204e231 | |||
| 9d8788db39 | |||
| 79ceb74604 | |||
| d02b1d48bf | |||
| 1fb50323b0 | |||
| 985c85da59 | |||
| 23c61097a8 | |||
| 5372045bd7 | |||
| 72d61b8376 | |||
| 526c094494 | |||
| 474f7b7c68 | |||
| f8692e3e2e | |||
| 62715fae34 | |||
| f8678a68bb | |||
| 8f8ae22f23 | |||
| 2611147eb3 | |||
| b0b3badd5c | |||
| 2b812098fb | |||
| 3146efec64 | |||
| 44e4b98e35 | |||
| 6adb4d5c95 | |||
| bcb1a28164 | |||
| d5798602e2 | |||
| 1f94a89520 | |||
| 44bbc8f17c | |||
| 9e6877c329 | |||
| d5883ba75e | |||
| 421d0aa705 | |||
| 69b5958296 | |||
| 86478b1073 | |||
| b9e3e1784f | |||
| 976cf1c3d2 | |||
| e4ba1576e5 | |||
| 4baa9f708f | |||
| 6fb25e6055 | |||
| 63698ee1aa | |||
| 410f659db0 | |||
| 08c972e720 | |||
| c4e4d87303 | |||
| 9c74fb9a7b | |||
| 1d7c2b2946 | |||
| d2c86396b1 | |||
| 3cf5c3695e | |||
| 7dda25fbfb | |||
| eefb7c5a79 | |||
| 13944a64d2 | |||
| 07e60690ab | |||
| 036ae20ac3 | |||
| 189de6c0fb | |||
| 113d9f3786 | |||
| 331d920379 | |||
| a615fc2b81 | |||
| f98097d11d | |||
| 3e76c3c931 | |||
| bb31efb271 | |||
| c9733113cf | |||
| 4c1d6ac2c4 | |||
| 07e7a0d457 | |||
| 48361ab33f | |||
| a3c7c224b1 | |||
| 324aec3001 | |||
| 05e941710b | |||
| f78a333e1e | |||
| 01c55cba0f | |||
| 10b2ae244c | |||
| 2dba2ebfae | |||
| d7eb4ad326 | |||
| d818441bde | |||
| c91965567d | |||
| d063b47bec | |||
| 6112de297b | |||
| 166c940295 | |||
| 246e4cb83b | |||
| 4517796ef3 | |||
| 858932f260 | |||
| a84f600fa2 | |||
| 4e02ea021d | |||
| 9ec127433d | |||
| e1450c6e97 | |||
| e40a19bbfb | |||
| fbdb64f3a1 | |||
| 9a29f49669 | |||
| c2081191ae | |||
| f0f3827fd0 | |||
| ee8f84bc57 | |||
| f95677d503 | |||
| 59b0b57ec2 | |||
| 9174b0b6a4 | |||
| e62c49ce91 | |||
| 32990b4dcd | |||
| da2413002b | |||
| fdbb81ba64 | |||
| 964ea6add9 | |||
| 602878acf4 | |||
| 0aa52feaac | |||
| 15b70309da | |||
| 7001fccbf7 | |||
| cffee785b2 | |||
| 33d260310c | |||
| e359acc1d5 | |||
| bb4bce4a6d | |||
| eac74ef0cd | |||
| 1dc4eb5648 | |||
| a35a6c2b60 | |||
| 1f78f4a3e1 | |||
| 1e98559f3a | |||
| ef0f860b9d | |||
| 0bff55379f | |||
| 4d34308a13 | |||
| 70bf73b0a4 | |||
| e3849d8217 | |||
| d9854a12a8 | |||
| 48d387a8da | |||
| 93d3e13793 | |||
| 031f5d5cf0 | |||
| 047669bab2 | |||
| 5ea5939e3a | |||
| 7ff3f13af4 | |||
| a9589f578b | |||
| a27e4b30d2 | |||
| 4168949b9e | |||
| e6ff54a15d | |||
| 3956797020 | |||
| 7d88359263 | |||
| 1b6da651a6 | |||
| 9b305f887f | |||
| 9506b9e28e | |||
| 61c0edca07 | |||
| 9470b5605d | |||
| 9d63d23754 | |||
| a95655a2a6 | |||
| 025801a689 | |||
| 28880c4d99 | |||
| 5bb3bc554b | |||
| cfb907b840 | |||
| d5d7953fd2 | |||
| 96298aab25 | |||
| c17cca1e81 | |||
| 7264efcf79 | |||
| 8934bcd603 | |||
| bdd08dbc56 | |||
| 7d47fde806 | |||
| ad207fb732 | |||
| bd9081b5bc | |||
| a429e9d14a | |||
| 81eb986313 | |||
| 58bedc42f1 | |||
| b97f422261 | |||
| 7a7dc33724 | |||
| 7743bd1f0d | |||
| 2897d7aa3c | |||
| 0fd7eafcf3 | |||
| 71253d216e | |||
| aeea6cfefd | |||
| e8eb925834 | |||
| 7cf9cc60e6 | |||
| 1e9818d430 | |||
| 39c0e87758 | |||
| 5771972e2a | |||
| ea13403dc3 | |||
| 8d9a9b84b8 | |||
| 9b92f3506b | |||
| 1798118f6b | |||
| eba2b8c569 | |||
| b6b2cf6cc8 | |||
| a0faa2d105 | |||
| d323f804fc | |||
| 978454754c | |||
| b6091b15da | |||
| a6794a061b | |||
| fafea3ce04 | |||
| 992f639f35 | |||
| f57309b06a | |||
| 3be2b8f20d | |||
| 4853fec7ff | |||
| 04a6e50b7a | |||
| 8ca3d095e6 | |||
| ca1144b55c | |||
| 18422fbe38 | |||
| f221035c8b | |||
| 02c80cd503 | |||
| c416114c50 | |||
| e329b36933 | |||
| 5c64c84e2a | |||
| fc319372be | |||
| 12dae135b5 | |||
| b208c9c301 | |||
| 1583ae539e | |||
| b6ec37c339 | |||
| 459523666f | |||
| 8427613114 | |||
| 5d3465fd97 | |||
| 39a2622cb1 | |||
| 0a42e4776d | |||
| 44fea21a56 | |||
| 46ac54f7ab | |||
| 2c9bf9dd93 | |||
| 19b2958a9c | |||
| a39b84e107 |
23
.env
23
.env
@@ -1,23 +0,0 @@
|
||||
PORT=3000
|
||||
RABBITMQ_USER=guest
|
||||
RABBITMQ_PASSWORD=guest
|
||||
|
||||
ENVIORMENT=development
|
||||
|
||||
#RABBITMQ_HOST=rabbitmq-sim-broker
|
||||
RABBITMQ_HOST=localhost
|
||||
RABBITMQ_PORT=5672
|
||||
RABBITMQ_USER=guest
|
||||
RABBITMQ_PASSWORD=guest
|
||||
RABBITMQ_SECURE=false
|
||||
RABBITMQ_VHOST=sim-vhost
|
||||
|
||||
# Hay cosas que unificar de varios servicios
|
||||
POSTGRES_DB=postgres
|
||||
POSTGRES_DATABASE=postgres
|
||||
#POSTGRES_HOST=postgresql-sim
|
||||
POSTGRES_HOST=localhost
|
||||
POSTGRES_PORT=5432
|
||||
DEV_POSTGRES_PORT=5432
|
||||
POSTGRES_USER=postgres
|
||||
POSTGRES_PASSWORD=1234
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -16,7 +16,11 @@ node_modules
|
||||
#!.yarn/cache
|
||||
.pnp.*
|
||||
|
||||
# Certificados
|
||||
*.pem
|
||||
|
||||
*.p12
|
||||
*.key
|
||||
|
||||
dist/*
|
||||
|
||||
.env
|
||||
|
||||
@@ -2,4 +2,12 @@ compressionLevel: mixed
|
||||
|
||||
enableGlobalCache: false
|
||||
|
||||
enableScripts: true
|
||||
|
||||
nodeLinker: node-modules
|
||||
|
||||
npmRegistryServer: "https://registry.npmjs.org/"
|
||||
|
||||
npmScopes:
|
||||
sf-alvar:
|
||||
npmRegistryServer: "https://git.savefamilygps.net/api/packages/SaveFamily/npm/"
|
||||
|
||||
23
README.md
23
README.md
@@ -12,13 +12,13 @@ La compañia a la que pertenece cada peticion y por tanto el servicio que lo va
|
||||
|
||||
## Decisiones pendientes
|
||||
|
||||
- [x] La capa worker según acción y la de operaciones de proveedores, se podrían unir en una sola con un enrutamiento por acción y compañía, pasando de tener claves `sim.[acción]` a `sim.[compañia].[acción]`. *Se ha aplicado el cambio ahora las routing keys tienen la estructura `sim.[compañia].[acción]`*
|
||||
- [x] La estructura de RMQ se genera por medio del JSON, igual habría que definir cada cola en el worker que la consuma para poder añadir workers sin parar el RMQ. *Se ha aplicado el cambio, ahora solo se define en el json el broker principal para garantizar que exita sin servicios consumidores. Sin embargo tal como estan estructurdos los proyectos no es posible reiniciar solo un servicio*
|
||||
- [x] La capa worker según acción y la de operaciones de proveedores, se podrían unir en una sola con un enrutamiento por acción y compañía, pasando de tener claves `sim.[acción]` a `sim.[compañia].[acción]`. _Se ha aplicado el cambio ahora las routing keys tienen la estructura `sim.[compañia].[acción]`_
|
||||
- [x] La estructura de RMQ se genera por medio del JSON, igual habría que definir cada cola en el worker que la consuma para poder añadir workers sin parar el RMQ. _Se ha aplicado el cambio, ahora solo se define en el json el broker principal para garantizar que exita sin servicios consumidores. Sin embargo tal como estan estructurdos los proyectos no es posible reiniciar solo un servicio_
|
||||
- [ ] Versionado de la API.
|
||||
- [x] Método para sacar la compañía a partir del iccid, o buscar en la BDD si no es posible. *De momento es un objeto Map en el servicio de gateway*
|
||||
- [ ] Cola de mensajes que no se han podido procesar. Distinguir según error de red; se reintenta; o error del propio mensaje; se envía a la cola de errores. v2 Se ha creado una cola de delay pero no se distingue el tipo de error, despues de n reintentos el mensaje va a la cola de dead-letter.
|
||||
- [ ] Seguimiento de las peticiones de Objenious, por cada peticion hay qye hacer un seguimiento del request y de los mass action para saber si las activaciones han tenido exito. Habria que crear otra cola para consultar cada x tiempo o mejor un cron?
|
||||
- [ ] Actualizar en la base de datos el estado de las peticiones de las sim y añadir el número de telefono cuando se activen o cuando se cumpla una accion.
|
||||
- [x] Método para sacar la compañía a partir del iccid, o buscar en la BDD si no es posible. _De momento es un objeto Map en el servicio de gateway_
|
||||
- [ ] Cola de mensajes que no se han podido procesar. Distinguir según error de red; se reintenta; o error del propio mensaje; se envía a la cola de errores. v2 Se ha creado una cola de delay pero no se distingue el tipo de error, después de n reintentos el mensaje va a la cola de dead-letter.
|
||||
- [x] Seguimiento de las peticiones de Objenious, por cada peticion hay qye hacer un seguimiento del request y de los mass action para saber si las activaciones han tenido exito. Habria que crear otra cola para consultar cada x tiempo o mejor un cron?
|
||||
- [x] Actualizar en la base de datos el estado de las peticiones de las sim y añadir el número de telefono cuando se activen o cuando se cumpla una accion.
|
||||
|
||||
## Versión con consumidores basados en la compañia
|
||||
|
||||
@@ -32,8 +32,15 @@ OBJENIOUS (33)2011a
|
||||
|
||||
## Diagrama de las colas de Rabbitmq
|
||||
|
||||
Actualmente la topologia de las colas consiste en un exchage principal que recibe todos los mensajes y los redistribuye en las colas de cada empresa y a la de logs. Para evitar reintentos de mensajes instantaneos, que podrian ser inutiles si algún servicio se ha caido, se ha añadido una cola de delay que alamcena los mesajes fallidos durante n segundos antes de ser reenviados al exchange principal. Si despues de n reintentos el mensaje sigue fallando se envia a la cola de dead-letter para ser procesado manualmente.
|
||||
Actualmente la topología de las colas consiste en un exchage principal que recibe todos los mensajes y los redistribuye en las colas de cada empresa y a la de logs. Para evitar reintentos de mensajes instantáneos, que podrían ser inútiles si algún servicio se ha caído, se ha añadido una cola de delay que almacena los mensajes fallidos durante n segundos antes de ser reenviados al exchange principal. Si después de n reintentos el mensaje sigue fallando se envía a la cola de dead-letter para ser procesado manualmente.
|
||||
|
||||

|
||||
|
||||
La decisión del numero de reintentos y la cola de dlx se hace en los servicios, con una configuracion global en shared.
|
||||
La decisión del numero de reintentos y la cola de dlx se hace en los servicios, con una configuración global en shared.
|
||||
|
||||
## Puertos internos para comunicaciones entre sub-servicios
|
||||
|
||||
- **3000**: Gateway (sim-entrada-eventos)
|
||||
- **3001**: Consumidor NOS (sim-consumidor-nos)
|
||||
- **3002**: Consumidor Objenious (sim-consumidor-objenious)
|
||||
- **3003**: Consumidor Alai (sim-consumidor-alai)
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#/bin/bash
|
||||
rm deployment/database/init.sql
|
||||
cat deployment/database/*.sql >deployment/database/init.sql
|
||||
|
||||
docker compose -f deployment/local/docker/docker-compose.yaml --project-directory ./ build
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
# stage base para coordinar las fases de build y ejecucion
|
||||
FROM node:22-alpine AS base
|
||||
WORKDIR /usr/local/app
|
||||
COPY ./package.json ./yarn.lock ./
|
||||
COPY ./package.json ./
|
||||
#COPY ./package.json ./yarn.lock ./
|
||||
RUN corepack enable && \
|
||||
corepack prepare yarn@4.12.0 --activate
|
||||
# copia el codigo en general
|
||||
|
||||
@@ -4,11 +4,13 @@ CREATE TYPE status_enum AS ENUM ('noRequestID','noMassID','running','finished','
|
||||
-- Tabla para gestionar las peticiones de cambio de objenious.
|
||||
-- Para una o mas lineas se pueden lanzar operacione que no sabemos
|
||||
-- con certeza cuando van a terminar.
|
||||
-- Estas tablas está fuertemente ligadas al sistema que usa la plataforma
|
||||
-- de objenioius y no debe unsarse para otra compañia.
|
||||
CREATE TABLE if not exists objenious_operation (
|
||||
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
|
||||
retry_count INT DEFAULT 0,
|
||||
max_retry INT DEFAULT 5,
|
||||
max_date_retry TIMESTAMP DEFAULT NULL,
|
||||
retry_count INT DEFAULT 0, -- No implementado en codigo
|
||||
max_retry INT DEFAULT 5, -- No implementado en codigo
|
||||
max_date_retry TIMESTAMP DEFAULT NULL, -- No implementado en codigo
|
||||
iccids TEXT,
|
||||
request_id TEXT,
|
||||
mass_action_id TEXT,
|
||||
@@ -24,7 +26,7 @@ CREATE TABLE if not exists objenious_operation (
|
||||
-- operaciones pendientes para revisar
|
||||
CREATE INDEX IF NOT EXISTS pending_operations
|
||||
ON objenious_operation(start_date)
|
||||
WHERE end_date IS NULL;
|
||||
WHERE end_date IS NULL;
|
||||
|
||||
CREATE TABLE if not exists objenious_operation_change (
|
||||
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
|
||||
20
deployment/database/base/xx-volcado-objenious.sql
Normal file
20
deployment/database/base/xx-volcado-objenious.sql
Normal file
@@ -0,0 +1,20 @@
|
||||
CREATE table if not exists objenious_lines (
|
||||
id INT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
|
||||
simId BIGINT UNIQUE,
|
||||
status TEXT,
|
||||
iccid TEXT NOT NULL,
|
||||
msisdn TEXT,
|
||||
imei TEXT,
|
||||
imeiChangeDate TIMESTAMPTZ,
|
||||
offerCode TEXT,
|
||||
preactivationDate TIMESTAMPTZ, -- No viene con hora
|
||||
activationDate TIMESTAMPTZ,
|
||||
commercialStatus TEXT,
|
||||
commercialStatusDate TIMESTAMPTZ,
|
||||
billingStatus TEXT,
|
||||
billingStatusChangeDate TIMESTAMPTZ,
|
||||
billingActivationDate TIMESTAMPTZ,
|
||||
createDate TIMESTAMPTZ,
|
||||
raw JSONB,
|
||||
hash TEXT
|
||||
)
|
||||
106
deployment/database/generateSchema.sh
Executable file
106
deployment/database/generateSchema.sh
Executable file
@@ -0,0 +1,106 @@
|
||||
#!/bin/bash
|
||||
# --- Para que siempre se ejecute en el mismo path
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
# --- Configuración por defecto ---
|
||||
MIGRATIONS_DIR="./migrations"
|
||||
OUTPUT_FILE_PREFIX="esquema_final"
|
||||
DB_NAME="temp_schema_build_$(date +%s)"
|
||||
|
||||
# --- Función de Ayuda ---
|
||||
usage() {
|
||||
echo "Uso: $0 -v <version> [-e <ruta_env>]"
|
||||
echo " -v Versión semántica objetivo (ej: 1.2.0)"
|
||||
echo " -e (Opcional) Ruta al archivo .env para cargar variables"
|
||||
echo " Los archivos de verions tienen que tener el formato x.x.x_descripcion.sql (Es importante la _ para serpar las partes) "
|
||||
exit 1
|
||||
}
|
||||
|
||||
# --- Procesar Argumentos (Flags) ---
|
||||
# v: obligatorio
|
||||
# e: opcionar
|
||||
while getopts "v:e:" opt; do
|
||||
case $opt in
|
||||
v) TARGET_VERSION="$OPTARG" ;;
|
||||
e) ENV_PATH="$OPTARG" ;;
|
||||
*) usage ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Validar que la versión esté presente
|
||||
if [ -z "$TARGET_VERSION" ]; then
|
||||
echo "Error: La versión es obligatoria."
|
||||
usage
|
||||
fi
|
||||
|
||||
# --- Cargar variables de entorno ---
|
||||
if [ ! -z "$ENV_PATH" ]; then
|
||||
if [ -f "$ENV_PATH" ]; then
|
||||
echo "~> Cargando configuración desde: $ENV_PATH"
|
||||
# Exporta automáticamente las variables definidas en el archivo
|
||||
set -o allexport
|
||||
source "$ENV_PATH"
|
||||
set +o allexport
|
||||
else
|
||||
echo "Error: No se encontró el archivo .env en: $ENV_PATH"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "!> No se especificó archivo .env, usando variables del sistema actual"
|
||||
fi
|
||||
|
||||
# echo "Debug: Usuario es '$PGUSER'"
|
||||
# echo "Debug: Host es '$PGHOST'"
|
||||
# echo "Debug: Password es '$PGPASSWORD'" # Cuidado con mostrar esto
|
||||
|
||||
# --- Función de limpieza (Safety Net) ---
|
||||
cleanup() {
|
||||
echo "~> Limpiando: Eliminando base de datos temporal '$DB_NAME'"
|
||||
# Usamos las variables de conexión cargadas (si las hay)
|
||||
dropdb $DB_NAME --if-exists 2>/dev/null
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
# --- Inicio del Proceso ---
|
||||
echo "~> Iniciando build para versión: $TARGET_VERSION"
|
||||
|
||||
# 1. Crear BD temporal
|
||||
# Nota: Si tu .env tiene PGHOST, la BD se creará allí. Si no, en localhost.
|
||||
createdb $DB_NAME
|
||||
|
||||
# 2. Ejecutar script base (si existe)
|
||||
rm -rf init.sql
|
||||
cat base/*.sql >init.sql
|
||||
if [ -f "init.sql" ]; then
|
||||
echo "~> Ejecutando init.sql..."
|
||||
psql -d $DB_NAME -f init.sql >/dev/null
|
||||
fi
|
||||
|
||||
# 3. Iterar y filtrar migraciones
|
||||
echo "~> Aplicando migraciones hasta la versión $TARGET_VERSION..."
|
||||
|
||||
for f in $(ls $MIGRATIONS_DIR/*.sql | sort -V); do
|
||||
FILENAME=$(basename "$f")
|
||||
# Extraer versión (Asume formato V1.0.0_desc.sql o 1.0.0_desc.sql)
|
||||
FILE_VER=$(echo "$FILENAME" | sed -E 's/^V//' | awk -F_ '{print $1}')
|
||||
|
||||
# Comparación semántica
|
||||
echo "comparando $TARGET_VERSION con $FILE_VER"
|
||||
LOWEST=$(echo -e "$TARGET_VERSION\n$FILE_VER" | sort -V | head -n1)
|
||||
|
||||
if [ "$LOWEST" == "$FILE_VER" ] || [ "$FILE_VER" == "$TARGET_VERSION" ]; then
|
||||
echo "~> Aplicando: $FILENAME ($FILE_VER)"
|
||||
psql -d $DB_NAME -f "$f" >/dev/null
|
||||
else
|
||||
echo "~> Saltando: $FILENAME ($FILE_VER) - Mayor que objetivo"
|
||||
fi
|
||||
done
|
||||
|
||||
# 4. Generar nombre de archivo de salida
|
||||
OUTPUT_FILE="${OUTPUT_FILE_PREFIX}_v${TARGET_VERSION}.sql"
|
||||
|
||||
# 5. Extraer el esquema FINAL
|
||||
echo "~> Generando $OUTPUT_FILE ---"
|
||||
pg_dump -d $DB_NAME -s --no-owner --no-privileges >$OUTPUT_FILE
|
||||
|
||||
echo "o> Esquema guardado en $OUTPUT_FILE"
|
||||
@@ -1,150 +0,0 @@
|
||||
-- eliminar los drop para prod
|
||||
drop domain if exists imei_type cascade;
|
||||
CREATE DOMAIN imei_type as varchar(15);
|
||||
drop domain if exists iccid_type cascade;
|
||||
CREATE DOMAIN iccid_type as varchar(22);
|
||||
drop domain if exists imsi_type cascade;
|
||||
CREATE DOMAIN imsi_type as varchar(15);
|
||||
|
||||
|
||||
CREATE table if not exists sim_cards (
|
||||
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
|
||||
imei imei_type,
|
||||
iccid iccid_type,
|
||||
imsi imsi_type,
|
||||
user_id BIGINT,
|
||||
subscription_id BIGINT,
|
||||
created_at TIMESTAMP,
|
||||
last_update TIMESTAMP,
|
||||
deleted_at TIMESTAMP
|
||||
);
|
||||
|
||||
|
||||
CREATE TABLE if not exists sim_envio (
|
||||
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
|
||||
codigo_origen TEXT,
|
||||
codigo_distrito TEXT,
|
||||
pedido_id BIGINT,
|
||||
sim_id BIGINT,
|
||||
|
||||
fecha_envio TIMESTAMP,
|
||||
fecha_email TIMESTAMP,
|
||||
is_preactivado BOOLEAN,
|
||||
fecha_devolucion TIMESTAMP,
|
||||
created_at TIMESTAMP,
|
||||
|
||||
CONSTRAINT fk_sim_id
|
||||
FOREIGN KEY(sim_id) REFERENCES sim_cards(id)
|
||||
);
|
||||
|
||||
-- Mock, No es parte de SIMs
|
||||
CREATE TABLE if not exists sf_subscription (
|
||||
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY
|
||||
);
|
||||
|
||||
-- No habria que meterle las propiedades del tipo de subscripcion
|
||||
CREATE TABLE if not exists sim_subscription_types (
|
||||
id INT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
|
||||
subscription TEXT NOT NULL,
|
||||
created_at TIMESTAMP,
|
||||
updated_at TIMESTAMP,
|
||||
deleted_at TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE if not exists sim_company (
|
||||
id INT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
|
||||
name TEXT,
|
||||
created_at TIMESTAMP,
|
||||
updated_at TIMESTAMP,
|
||||
deleted_at TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE sim_subscription (
|
||||
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
|
||||
company_id INT,
|
||||
subscription_type_id INT,
|
||||
sim_id BIGINT,
|
||||
order_id BIGINT,
|
||||
|
||||
created_at TIMESTAMP,
|
||||
updated_at TIMESTAMP,
|
||||
deleted_at TIMESTAMP,
|
||||
|
||||
CONSTRAINT fk_sim_id
|
||||
FOREIGN KEY(sim_id) REFERENCES sim_cards(id),
|
||||
|
||||
CONSTRAINT fk_company_id
|
||||
FOREIGN KEY(company_id) REFERENCES sim_company(id),
|
||||
|
||||
CONSTRAINT fk_subscription_type_id
|
||||
FOREIGN KEY(subscription_type_id) REFERENCES sim_subscription_types(id)
|
||||
);
|
||||
|
||||
CREATE TABLE if not exists sim_subscription_operations (
|
||||
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
|
||||
sim_id BIGINT,
|
||||
operation_type TEXT NOT NULL,
|
||||
happened_at TIMESTAMP,
|
||||
|
||||
CONSTRAINT valid_operations CHECK (
|
||||
operation_type in ('free','preactivate','activate','pause','cancel')
|
||||
),
|
||||
|
||||
CONSTRAINT fk_subscription_id
|
||||
FOREIGN KEY(sim_id)
|
||||
REFERENCES sim_subscription(id)
|
||||
);
|
||||
|
||||
-- Se supone que indica un cambio
|
||||
CREATE TABLE sim_subscription_historic (
|
||||
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
|
||||
subscription_id BIGINT,
|
||||
iccid iccid_type,
|
||||
company_id INT
|
||||
);
|
||||
|
||||
CREATE TYPE status_enum AS ENUM ('noRequestID','noMassID','running','finished','error','other');
|
||||
|
||||
-- Tabla para gestionar las peticiones de cambio de objenious.
|
||||
-- Para una o mas lineas se pueden lanzar operacione que no sabemos
|
||||
-- con certeza cuando van a terminar.
|
||||
CREATE TABLE if not exists objenious_operation (
|
||||
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
|
||||
retry_count INT DEFAULT 0,
|
||||
max_retry INT DEFAULT 5,
|
||||
max_date_retry TIMESTAMP DEFAULT NULL,
|
||||
iccids TEXT,
|
||||
request_id TEXT,
|
||||
mass_action_id TEXT,
|
||||
operation TEXT NOT NULL,
|
||||
start_date TIMESTAMP NOT NULL DEFAULT now(),
|
||||
last_change_date TIMESTAMP NOT NULL DEFAULT now(),
|
||||
end_date TIMESTAMP,
|
||||
error TEXT,
|
||||
status status_enum,
|
||||
objenious_status TEXT
|
||||
);
|
||||
|
||||
-- operaciones pendientes para revisar
|
||||
CREATE INDEX IF NOT EXISTS pending_operations
|
||||
ON objenious_operation(start_date)
|
||||
WHERE end_date IS NULL;
|
||||
|
||||
CREATE TABLE if not exists objenious_operation_change (
|
||||
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
|
||||
operation_id BIGINT,
|
||||
creation_date TIMESTAMP NOT NULL DEFAULT now(),
|
||||
error TEXT,
|
||||
new_status status_enum,
|
||||
previous_status status_enum,
|
||||
new_objenious_status TEXT,
|
||||
previous_objenious_status TEXT,
|
||||
new_request_id TEXT,
|
||||
new_mass_action_id TEXT,
|
||||
|
||||
CONSTRAINT fk_operation_id
|
||||
FOREIGN KEY(operation_id) REFERENCES objenious_operation(id)
|
||||
);
|
||||
|
||||
CREATE INDEX operation_change
|
||||
ON objenious_operation_change(operation_id);
|
||||
48
deployment/database/migrations/0.1.0_objenious.sql
Normal file
48
deployment/database/migrations/0.1.0_objenious.sql
Normal file
@@ -0,0 +1,48 @@
|
||||
|
||||
CREATE TYPE status_enum AS ENUM ('noRequestID','noMassID','running','finished','error','other');
|
||||
|
||||
-- Tabla para gestionar las peticiones de cambio de objenious.
|
||||
-- Para una o mas lineas se pueden lanzar operacione que no sabemos
|
||||
-- con certeza cuando van a terminar.
|
||||
-- Estas tablas está fuertemente ligadas al sistema que usa la plataforma
|
||||
-- de objenioius y no debe unsarse para otra compañia.
|
||||
CREATE TABLE if not exists objenious_operation (
|
||||
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
|
||||
retry_count INT DEFAULT 0, -- No implementado en codigo
|
||||
max_retry INT DEFAULT 5, -- No implementado en codigo
|
||||
max_date_retry TIMESTAMP DEFAULT NULL, -- No implementado en codigo
|
||||
iccids TEXT,
|
||||
request_id TEXT,
|
||||
mass_action_id TEXT,
|
||||
operation TEXT NOT NULL,
|
||||
start_date TIMESTAMP NOT NULL DEFAULT now(),
|
||||
last_change_date TIMESTAMP NOT NULL DEFAULT now(),
|
||||
end_date TIMESTAMP,
|
||||
error TEXT,
|
||||
status status_enum,
|
||||
objenious_status TEXT
|
||||
);
|
||||
|
||||
-- operaciones pendientes para revisar
|
||||
CREATE INDEX IF NOT EXISTS pending_operations
|
||||
ON objenious_operation(start_date)
|
||||
WHERE end_date IS NULL;
|
||||
|
||||
CREATE TABLE if not exists objenious_operation_change (
|
||||
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
|
||||
operation_id BIGINT,
|
||||
creation_date TIMESTAMP NOT NULL DEFAULT now(),
|
||||
error TEXT,
|
||||
new_status status_enum,
|
||||
previous_status status_enum,
|
||||
new_objenious_status TEXT,
|
||||
previous_objenious_status TEXT,
|
||||
new_request_id TEXT,
|
||||
new_mass_action_id TEXT,
|
||||
|
||||
CONSTRAINT fk_operation_id
|
||||
FOREIGN KEY(operation_id) REFERENCES objenious_operation(id)
|
||||
);
|
||||
|
||||
CREATE INDEX operation_change
|
||||
ON objenious_operation_change(operation_id);
|
||||
66
deployment/database/migrations/1.0.0_orders.sql
Normal file
66
deployment/database/migrations/1.0.0_orders.sql
Normal file
@@ -0,0 +1,66 @@
|
||||
|
||||
-- Tablas para el seguimiento de las operaciones de SIM sin importar
|
||||
-- la cmpañia.
|
||||
DO $$ BEGIN
|
||||
CREATE TYPE order_types AS ENUM ('activate','preactivate','cancel','pause','reactivate','unknown');
|
||||
CREATE TYPE order_status AS ENUM (
|
||||
'pending', -- Mensaje creado/enviado a RabbitMQ
|
||||
'running', -- Consumidor ha cogido el mensaje (opcional)
|
||||
'finished', -- Procesado correctamente
|
||||
'failed', -- Falló, pero podría reintentarse (Pasar a delay?)
|
||||
'dlx' -- Falló definitivamente y está en Dead Letter Exchange
|
||||
);
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS order_tracking (
|
||||
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
|
||||
correlation_id VARCHAR(255) NOT NULL, -- ID compartido con RabbitMQ (message_id)
|
||||
exchange VARCHAR(100), -- Exchange al que se envia (de momento solo hay 1 principal sin contar delay y dlx)
|
||||
routing_key VARCHAR(100), -- Routing key del mensaje
|
||||
order_type order_types NOT NULL DEFAULT 'unknown',
|
||||
|
||||
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
|
||||
error_stacktrace TEXT,
|
||||
|
||||
start_date TIMESTAMP NOT NULL DEFAULT (now() at time zone 'utc'),
|
||||
update_date TIMESTAMP NOT NULL DEFAULT (now() at time zone 'utc'),
|
||||
finish_date TIMESTAMP
|
||||
);
|
||||
|
||||
-- Busqueda según id de rabbit
|
||||
CREATE INDEX IF NOT EXISTS idx_order_correlation
|
||||
ON order_tracking(correlation_id);
|
||||
-- Ordenenes que todavia no han finalizado
|
||||
CREATE INDEX IF NOT EXISTS pending_orders
|
||||
ON order_tracking(start_date)
|
||||
WHERE order_tracking.finish_date IS NULL;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS order_history(
|
||||
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
|
||||
order_id BIGINT NOT NULL,
|
||||
previous_status order_status NOT NULL, -- Siempre hay un estado anterior, para casos excepcioneale "unknown"
|
||||
new_status order_status NOT NULL,
|
||||
change_reason TEXT,
|
||||
change_date TIMESTAMP NOT NULL DEFAULT (now() at time zone 'utc'),
|
||||
|
||||
CONSTRAINT fk_order_id
|
||||
FOREIGN KEY(order_id)
|
||||
REFERENCES order_tracking(id)
|
||||
ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- fk de order
|
||||
CREATE INDEX IF NOT EXISTS idx_order_id
|
||||
ON order_history(order_id);
|
||||
|
||||
-- busquedas por fecha
|
||||
CREATE INDEX IF NOT EXISTS idx_order_change_date
|
||||
ON order_history(change_date);
|
||||
|
||||
12
deployment/database/migrations/1.0.1_utc.sql
Normal file
12
deployment/database/migrations/1.0.1_utc.sql
Normal file
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* Fechas modificadas para que todas sean en base a 'UTC'
|
||||
* */
|
||||
ALTER TABLE objenious_operation
|
||||
ALTER COLUMN start_date SET DEFAULT (now() at time zone 'utc'),
|
||||
ALTER COLUMN last_change_date SET DEFAULT (now() at time zone 'utc');
|
||||
|
||||
ALTER TABLE objenious_operation_change
|
||||
ALTER COLUMN creation_date SET DEFAULT (now() at time zone 'utc');
|
||||
|
||||
|
||||
|
||||
30
deployment/database/migrations/1.0.2_timezones.sql
Normal file
30
deployment/database/migrations/1.0.2_timezones.sql
Normal file
@@ -0,0 +1,30 @@
|
||||
|
||||
/*
|
||||
* Fechas modificadas para que se puedan hacer query en base a la zona horaria objetivo
|
||||
* SELECT col_date at time zone 'cet' -- devuleve la fecha en esa zona
|
||||
* SELECT col_date -- devuleve la fecha en UTC con el offset de la zona horaria
|
||||
*
|
||||
* */
|
||||
|
||||
ALTER TABLE objenious_operation
|
||||
ALTER COLUMN start_date SET DATA TYPE TIMESTAMP WITH TIME ZONE,
|
||||
ALTER COLUMN start_date SET DEFAULT now(),
|
||||
ALTER COLUMN last_change_date SET DATA TYPE TIMESTAMP WITH TIME ZONE,
|
||||
ALTER COLUMN last_change_date SET DEFAULT now(),
|
||||
ALTER COLUMN end_date SET DATA TYPE TIMESTAMP WITH TIME ZONE;
|
||||
|
||||
ALTER TABLE objenious_operation_change
|
||||
ALTER COLUMN creation_date SET DATA TYPE TIMESTAMP WITH TIME ZONE,
|
||||
ALTER COLUMN creation_date SET DEFAULT now();
|
||||
|
||||
ALTER TABLE order_tracking
|
||||
ALTER COLUMN start_date SET DATA TYPE TIMESTAMP WITH TIME ZONE,
|
||||
ALTER COLUMN start_date SET DEFAULT now(),
|
||||
ALTER COLUMN update_date SET DATA TYPE TIMESTAMP WITH TIME ZONE,
|
||||
ALTER COLUMN update_date SET DEFAULT now(),
|
||||
ALTER COLUMN finish_date SET DATA TYPE TIMESTAMP WITH TIME ZONE;
|
||||
|
||||
ALTER TABLE order_history
|
||||
ALTER COLUMN change_date SET DATA TYPE TIMESTAMP WITH TIME ZONE,
|
||||
ALTER COLUMN change_date SET DEFAULT now();
|
||||
|
||||
10
deployment/database/migrations/1.1.0_webhook-order.sql
Normal file
10
deployment/database/migrations/1.1.0_webhook-order.sql
Normal file
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* A que endpoint actualizar el estado de los order, si se especificase.
|
||||
* Se asume que siempre se usa POST.
|
||||
* Se separa host de enpoint para dejar host como default el origen de la
|
||||
* peticion anterior y poder hacer filtrados
|
||||
*/
|
||||
|
||||
ALTER TABLE order_tracking
|
||||
ADD COLUMN webhook_host TEXT,
|
||||
ADD COLUMN webhook_endpoint TEXT;
|
||||
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* En la tabla de orders de objenious no hay forma de saber a a que mensaje está Solicitando
|
||||
* cada operación.
|
||||
*/
|
||||
|
||||
ALTER TABLE objenious_operation
|
||||
ADD COLUMN correlation_id TEXT;
|
||||
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Para la tarea WEBINT-328-Pausas-cacelaciones.
|
||||
* Almacena las pausas/cancelaciones que no se han podido hacer porque la linea esta en
|
||||
* "Test"
|
||||
*/
|
||||
|
||||
|
||||
DO $$ BEGIN
|
||||
CREATE TYPE SUSPENDTERMINATE AS ENUM ('suspend','terminate');
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS pause_cancel_tasks (
|
||||
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
|
||||
iccid TEXT NOT NULL,
|
||||
operation_type SUSPENDTERMINATE,
|
||||
last_checked TIMESTAMPTZ, -- Última vez que se ha comprobado que no esté en test
|
||||
activation_date TIMESTAMPTZ, -- Fecha de activacion para comprobar si ha pasdo un mes
|
||||
next_check TIMESTAMPTZ, -- Si se ha comprobado se asignará la siguiente fecha de revision
|
||||
|
||||
completed_date TIMESTAMPTZ, -- Cuando se ha completado, para bien o mal.
|
||||
error TEXT,
|
||||
action_data JSONB -- datos de la operacion original.
|
||||
);
|
||||
|
||||
-- Indice de las tareas que no han terminado
|
||||
CREATE INDEX idx_pause_cancel_tasks_pending
|
||||
ON pause_cancel_tasks (next_check)
|
||||
WHERE completed_date IS NULL;
|
||||
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
# --- Release image ---
|
||||
FROM node:22-alpine AS release
|
||||
RUN apk --no-cache add git
|
||||
WORKDIR /home/node/app
|
||||
|
||||
RUN corepack enable
|
||||
|
||||
COPY ./dist/packages ./packages
|
||||
COPY ./.yarnrc.yml ./
|
||||
COPY ./docs ./docs
|
||||
# Para las migraciones
|
||||
COPY ./deployment ./deployment
|
||||
|
||||
COPY ./package.json ./
|
||||
|
||||
# Force node-modules linker (no .yarnrc.yml in build context)
|
||||
RUN echo 'nodeLinker: node-modules' > .yarnrc.yml
|
||||
|
||||
RUN yarn install
|
||||
|
||||
RUN mkdir -p dist && ln -sf ../packages dist/packages
|
||||
@@ -19,4 +21,5 @@ COPY ./entrypoint.sh ./
|
||||
RUN chmod +x entrypoint.sh
|
||||
|
||||
EXPOSE ${PORT:-3000}
|
||||
|
||||
ENTRYPOINT ["./entrypoint.sh"]
|
||||
|
||||
@@ -6,6 +6,8 @@ networks:
|
||||
external: true
|
||||
internal:
|
||||
driver: bridge
|
||||
volumes:
|
||||
rabbitmq_data:
|
||||
|
||||
services:
|
||||
rabbitmq-sim-broker:
|
||||
@@ -28,6 +30,7 @@ services:
|
||||
entrypoint: ["bash", "/usr/local/bin/docker-entrypoint-wrapper.sh"]
|
||||
command: ["rabbitmq-server"]
|
||||
volumes:
|
||||
- rabbitmq_data:/var/lib/rabbitmq
|
||||
- ./rabbit/docker-entrypoint-wrapper.sh:/usr/local/bin/docker-entrypoint-wrapper.sh:ro
|
||||
- ./rabbitmq_plugins/enabled_plugins:/etc/rabbitmq/enabled_plugins:ro
|
||||
- ./rabbit/rabbitmq.conf:/etc/rabbitmq/rabbitmq.conf:ro
|
||||
@@ -72,7 +75,10 @@ services:
|
||||
- ${PORT}
|
||||
volumes:
|
||||
- ./.env:/home/node/app/.env:ro
|
||||
- ./sim-consumidor-nos.env:/home/node/app/packages/sim-consumidor-nos/.env:ro
|
||||
- ./sim-consumidor-alai.env:/home/node/app/packages/sim-consumidor-alai/.env:ro
|
||||
- ./sim-consumidor-objenious.env:/home/node/app/packages/sim-consumidor-objenious/.env:ro
|
||||
- ./wsaccess_alaisecure_com_cert_client_new.p12:/home/node/app/packages/sim-consumidor-alai/certificates/wsaccess_alaisecure_com_cert_client_new.p12:ro
|
||||
- ./sim-objenious-cron.env:/home/node/app/packages/sim-objenious-cron/.env:ro
|
||||
- ./obj.pem:/home/node/app/packages/sim-consumidor-objenious/obj.pem:ro
|
||||
- ./obj.pem:/home/node/app/packages/sim-objenious-cron/obj.pem:ro
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
#!/bin/sh
|
||||
cd /home
|
||||
|
||||
cd /home/node/app && yarn start
|
||||
cd /home/node/app
|
||||
yarn migrate
|
||||
yarn start
|
||||
|
||||
@@ -22,8 +22,8 @@ pipeline {
|
||||
}
|
||||
stage("🧱 Building") {
|
||||
steps {
|
||||
sh 'rm -rf dist/'
|
||||
sh 'yarn run build'
|
||||
sh 'rm -rf dist/'
|
||||
sh 'yarn run build:prod'
|
||||
}
|
||||
}
|
||||
stage("🏗 Deploying") {
|
||||
@@ -38,14 +38,40 @@ pipeline {
|
||||
cleanRemote: false,
|
||||
execCommand: "mkdir -p $APP_REMOTE_PATH"
|
||||
),
|
||||
sshTransfer(
|
||||
cleanRemote: false,
|
||||
execCommand: "rm -rf $APP_REMOTE_PATH/dist"
|
||||
),
|
||||
sshTransfer(
|
||||
cleanRemote: false,
|
||||
execCommand: "ls -la $BASE_REMOTE_PATH/vault/savefamily/sf-sims/"
|
||||
),
|
||||
sshTransfer(
|
||||
cleanRemote: false,
|
||||
remoteDirectory: "$APP_REMOTE_PATH",
|
||||
sourceFiles: "dist/**/*",
|
||||
excludes: "dist/**/node_modules/**"
|
||||
),
|
||||
sshTransfer(
|
||||
cleanRemote: false,
|
||||
execCommand: "ln -sf $BASE_REMOTE_PATH/vault/savefamily/sf-sims/.env $APP_REMOTE_PATH/.env"
|
||||
),
|
||||
sshTransfer(
|
||||
cleanRemote: false,
|
||||
execCommand: "ln -sf $BASE_REMOTE_PATH/vault/savefamily/sf-sims/wsaccess_alaisecure_com_cert_client_new.p12 $APP_REMOTE_PATH/wsaccess_alaisecure_com_cert_client_new.p12"
|
||||
),
|
||||
sshTransfer(
|
||||
cleanRemote: false,
|
||||
execCommand: "ln -sf $BASE_REMOTE_PATH/vault/savefamily/sf-sims/sim-consumidor-objenious.env $APP_REMOTE_PATH/sim-consumidor-objenious.env"
|
||||
),
|
||||
sshTransfer(
|
||||
cleanRemote: false,
|
||||
execCommand: "ln -sf $BASE_REMOTE_PATH/vault/savefamily/sf-sims/sim-consumidor-nos.env $APP_REMOTE_PATH/sim-consumidor-nos.env"
|
||||
),
|
||||
sshTransfer(
|
||||
cleanRemote: false,
|
||||
execCommand: "ln -sf $BASE_REMOTE_PATH/vault/savefamily/sf-sims/sim-consumidor-alai.env $APP_REMOTE_PATH/sim-consumidor-alai.env"
|
||||
),
|
||||
sshTransfer(
|
||||
cleanRemote: false,
|
||||
execCommand: "ln -sf $BASE_REMOTE_PATH/vault/savefamily/sf-sims/sim-objenious-cron.env $APP_REMOTE_PATH/sim-objenious-cron.env"
|
||||
@@ -57,14 +83,12 @@ pipeline {
|
||||
sshTransfer(
|
||||
cleanRemote: false,
|
||||
remoteDirectory: "$APP_REMOTE_PATH",
|
||||
sourceFiles: "dist/**/*",
|
||||
excludes: "dist/**/node_modules/**"
|
||||
sourceFiles: "docs/**/*",
|
||||
),
|
||||
sshTransfer(
|
||||
cleanRemote: false,
|
||||
remoteDirectory: "$APP_REMOTE_PATH",
|
||||
sourceFiles: "deployment/database/**/*",
|
||||
removePrefix: "deployment",
|
||||
),
|
||||
sshTransfer(
|
||||
cleanRemote: false,
|
||||
@@ -88,6 +112,11 @@ pipeline {
|
||||
remoteDirectory: "$APP_REMOTE_PATH",
|
||||
sourceFiles: "package.json",
|
||||
),
|
||||
sshTransfer(
|
||||
cleanRemote: false,
|
||||
remoteDirectory: "$APP_REMOTE_PATH",
|
||||
sourceFiles: ".yarnrc.yml",
|
||||
),
|
||||
sshTransfer(
|
||||
cleanRemote: false,
|
||||
execCommand: "sh $APP_REMOTE_PATH/rebuild.sh"
|
||||
|
||||
@@ -1,90 +1,92 @@
|
||||
{
|
||||
"rabbit_version": "4.2.2",
|
||||
"rabbitmq_version": "4.2.2",
|
||||
"product_name": "RabbitMQ",
|
||||
"product_version": "4.2.2",
|
||||
"users": [
|
||||
{
|
||||
"name": "RABBITMQ_USER_PLACEHOLDER",
|
||||
"password": "RABBITMQ_PASSWORD_PLACEHOLDER",
|
||||
"tags": ["administrator"]
|
||||
}
|
||||
],
|
||||
"vhosts": [
|
||||
{
|
||||
"name": "sim-vhost"
|
||||
}
|
||||
],
|
||||
"permissions": [
|
||||
{
|
||||
"user": "RABBITMQ_USER_PLACEHOLDER",
|
||||
"vhost": "sim-vhost",
|
||||
"configure": ".*",
|
||||
"write": ".*",
|
||||
"read": ".*"
|
||||
}
|
||||
],
|
||||
"topic_permissions": [],
|
||||
"parameters": [],
|
||||
"global_parameters": [
|
||||
{
|
||||
"name": "cluster_name",
|
||||
"value": "rabbit@a8d5c6e08439"
|
||||
},
|
||||
{
|
||||
"name": "internal_cluster_id",
|
||||
"value": "rabbitmq-cluster-id-gXeBLbsUC2W2tU0Bx_QY_w"
|
||||
}
|
||||
],
|
||||
"policies": [
|
||||
{
|
||||
"vhost": "sim-vhost",
|
||||
"name": "pol.sim.dlx",
|
||||
"pattern": "sim.*",
|
||||
"apply-to": "queues",
|
||||
"definition": {
|
||||
"dead-letter-exchange": "sim.dlx"
|
||||
},
|
||||
"priority": 7
|
||||
}
|
||||
],
|
||||
"exchanges": [
|
||||
{
|
||||
"name": "sim.exchange",
|
||||
"vhost": "sim-vhost",
|
||||
"type": "topic",
|
||||
"durable": true,
|
||||
"auto_delete": false,
|
||||
"internal": false,
|
||||
"argurments": {}
|
||||
},
|
||||
{
|
||||
"name": "sim.dlx",
|
||||
"vhost": "sim-vhost",
|
||||
"type": "topic",
|
||||
"durable": true,
|
||||
"auto_delete": false,
|
||||
"internal": false,
|
||||
"argurments": {}
|
||||
}
|
||||
],
|
||||
"queues": [
|
||||
{
|
||||
"name": "sim.logs",
|
||||
"vhost": "sim-vhost",
|
||||
"durable": true,
|
||||
"auto_delete": false,
|
||||
"arguments": {}
|
||||
}
|
||||
],
|
||||
"bindings": [
|
||||
{
|
||||
"source": "sim.exchange",
|
||||
"vhost": "sim-vhost",
|
||||
"destination": "sim.logs",
|
||||
"destination_type": "queue",
|
||||
"routing_key": "sim.#",
|
||||
"arguments": {}
|
||||
}
|
||||
]
|
||||
"rabbit_version": "4.2.2",
|
||||
"rabbitmq_version": "4.2.2",
|
||||
"product_name": "RabbitMQ",
|
||||
"product_version": "4.2.2",
|
||||
"users": [
|
||||
{
|
||||
"name": "RABBITMQ_USER_PLACEHOLDER",
|
||||
"password": "RABBITMQ_PASSWORD_PLACEHOLDER",
|
||||
"tags": [
|
||||
"administrator"
|
||||
]
|
||||
}
|
||||
],
|
||||
"vhosts": [
|
||||
{
|
||||
"name": "sim-vhost"
|
||||
}
|
||||
],
|
||||
"permissions": [
|
||||
{
|
||||
"user": "RABBITMQ_USER_PLACEHOLDER",
|
||||
"vhost": "sim-vhost",
|
||||
"configure": ".*",
|
||||
"write": ".*",
|
||||
"read": ".*"
|
||||
}
|
||||
],
|
||||
"topic_permissions": [],
|
||||
"parameters": [],
|
||||
"global_parameters": [
|
||||
{
|
||||
"name": "cluster_name",
|
||||
"value": "rabbit@a8d5c6e08439"
|
||||
},
|
||||
{
|
||||
"name": "internal_cluster_id",
|
||||
"value": "rabbitmq-cluster-id-gXeBLbsUC2W2tU0Bx_QY_w"
|
||||
}
|
||||
],
|
||||
"policies": [
|
||||
{
|
||||
"vhost": "sim-vhost",
|
||||
"name": "pol.sim.dlx",
|
||||
"pattern": "sim.*",
|
||||
"apply-to": "queues",
|
||||
"definition": {
|
||||
"dead-letter-exchange": "sim.dlx"
|
||||
},
|
||||
"priority": 7
|
||||
}
|
||||
],
|
||||
"exchanges": [
|
||||
{
|
||||
"name": "sim.exchange",
|
||||
"vhost": "sim-vhost",
|
||||
"type": "topic",
|
||||
"durable": true,
|
||||
"auto_delete": false,
|
||||
"internal": false,
|
||||
"argurments": {}
|
||||
},
|
||||
{
|
||||
"name": "sim.dlx",
|
||||
"vhost": "sim-vhost",
|
||||
"type": "topic",
|
||||
"durable": true,
|
||||
"auto_delete": false,
|
||||
"internal": false,
|
||||
"argurments": {}
|
||||
}
|
||||
],
|
||||
"queues": [
|
||||
{
|
||||
"name": "sim.logs",
|
||||
"vhost": "sim-vhost",
|
||||
"durable": true,
|
||||
"auto_delete": false,
|
||||
"arguments": {}
|
||||
}
|
||||
],
|
||||
"bindings": [
|
||||
{
|
||||
"source": "sim.exchange",
|
||||
"vhost": "sim-vhost",
|
||||
"destination": "sim.logs",
|
||||
"destination_type": "queue",
|
||||
"routing_key": "sim.#",
|
||||
"arguments": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
28
deployment/local/docker/Dockerfile.dev
Normal file
28
deployment/local/docker/Dockerfile.dev
Normal file
@@ -0,0 +1,28 @@
|
||||
# Stage base para coordinar las fases de build y ejecucion
|
||||
FROM node:22-alpine AS base
|
||||
# Hace falta para la herramienta de migraciones, cuando se publique se
|
||||
# sustituira por el paquete de npm
|
||||
RUN apk --no-cache add git
|
||||
WORKDIR /usr/local/app
|
||||
RUN corepack enable && \
|
||||
corepack prepare yarn@4.12.0 --activate
|
||||
|
||||
COPY ./package.json ./yarn.lock ./
|
||||
COPY ./packages ./packages
|
||||
|
||||
# copia el codigo en general
|
||||
COPY tsconfig*.json ./
|
||||
COPY .env* ./
|
||||
COPY ./.yarnrc.yml ./
|
||||
COPY ./docs ./docs
|
||||
COPY ./deployment/local/docker/start.sh ./
|
||||
# Copiar el archivo de migrations? porque ahora no creo que se esté lanzando nada
|
||||
COPY ./deployment/database/migrations ./deployment/database/migrations
|
||||
RUN yarn install && \
|
||||
yarn cache clean && \
|
||||
yarn build && \
|
||||
chmod +x start.sh
|
||||
EXPOSE ${PORT}
|
||||
ENTRYPOINT [ "./start.sh" ]
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ networks:
|
||||
services:
|
||||
rabbitmq-sim-broker:
|
||||
container_name: rabbitmq-sim-broker
|
||||
hostname: rabbitmq-sim
|
||||
image: "rabbitmq:4.2.2-management"
|
||||
ports:
|
||||
- "5672:5672"
|
||||
@@ -23,15 +24,17 @@ services:
|
||||
RABBITMQ_DEFAULT_USER: ${RABBITMQ_USER}
|
||||
RABBITMQ_DEFAULT_PASS: ${RABBITMQ_PASSWORD}
|
||||
volumes:
|
||||
- ./rabbitmq-data/:/var/lib/rabbitmq/
|
||||
- ./rabbitmq_plugins/enabled_plugins:/etc/rabbitmq/enabled_plugins:ro
|
||||
- ./deployment/rabbit/rabbitmq.conf:/etc/rabbitmq/rabbitmq.conf:ro
|
||||
- ./deployment/rabbit/definitions.json:/etc/rabbitmq/definitions.json:ro
|
||||
- ./deployment/local/rabbit/rabbitmq.conf:/etc/rabbitmq/rabbitmq.conf:ro
|
||||
- ./deployment/local/rabbit/definitions.json:/etc/rabbitmq/definitions.json:ro
|
||||
|
||||
sim-gateway:
|
||||
container_name: sim-gateway
|
||||
sf-sims-api:
|
||||
container_name: sf-sims-api
|
||||
image: sf-sims-api
|
||||
build:
|
||||
context: ./
|
||||
dockerfile: deployment/Dockerfile.dev
|
||||
dockerfile: deployment/local/docker/Dockerfile.dev
|
||||
args:
|
||||
PORT: "${PORT:-3000}"
|
||||
develop:
|
||||
@@ -39,6 +42,9 @@ services:
|
||||
- path: ./packages
|
||||
action: sync
|
||||
target: /usr/local/app/packages
|
||||
- path: ./docs
|
||||
action: sync
|
||||
target: /usr/local/app/docs
|
||||
- path: ./package.json
|
||||
action: rebuild
|
||||
ports:
|
||||
@@ -46,19 +52,31 @@ services:
|
||||
env_file:
|
||||
- .env
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test:
|
||||
[
|
||||
"CMD-SHELL",
|
||||
'node -e "fetch(''http://localhost:'' + (process.env.PORT || 3000) + ''/health'').then(r => { if (!r.ok) process.exit(1) }).catch(() => process.exit(1))"',
|
||||
]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 15s
|
||||
depends_on:
|
||||
rabbitmq-sim-broker:
|
||||
condition: service_healthy
|
||||
postgresql-sim:
|
||||
condition: service_healthy
|
||||
|
||||
postgresql-sim:
|
||||
container_name: postgresql-sim
|
||||
image: postgres:16.1
|
||||
env_file:
|
||||
- .env
|
||||
ports:
|
||||
- "5432:${DEV_POSTGRES_PORT}"
|
||||
- "${POSTGRES_PORT}:${POSTGRES_PORT}"
|
||||
volumes:
|
||||
- ./sql-data/:/var/lib/postgres/data
|
||||
- ./deployment/database/init.sql:/docker-entrypoint-initdb.d/init.sql
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
|
||||
interval: 5s
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
#!/bin/bash
|
||||
|
||||
cd /mnt/docker-storage/containers/savefamily/sf-shopify-orders
|
||||
# cd /mnt/docker-storage/containers/savefamily/sf-shopify-orders
|
||||
cd /mnt/docker-storage/containers/savefamily/sf-sims-api
|
||||
|
||||
docker stop sf-shopify-orders-api || true
|
||||
docker rm sf-shopify-orders-api || true
|
||||
docker rmi sf-shopify-orders-api || true
|
||||
docker stop sf-sims-api || true
|
||||
docker rm sf-sims-api || true
|
||||
docker rmi sf-sims-api || true
|
||||
|
||||
docker compose -f docker-compose.yaml up --build -d
|
||||
|
||||
3
deployment/local/docker/start.sh
Normal file
3
deployment/local/docker/start.sh
Normal file
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
echo "Lanzando migraciones e iniciando servidor"
|
||||
yarn migrate && yarn start
|
||||
@@ -1,6 +1,3 @@
|
||||
default_user = guest
|
||||
default_pass = guest
|
||||
|
||||
listeners.tcp.default = 5672
|
||||
management.tcp.port = 15672
|
||||
|
||||
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
|
||||
25
docs/sim-api-documentation.html
Normal file
25
docs/sim-api-documentation.html
Normal file
File diff suppressed because one or more lines are too long
@@ -6,16 +6,80 @@ meta {
|
||||
|
||||
post {
|
||||
url: {{baseurl}}/sim/activate
|
||||
body: formUrlEncoded
|
||||
body: json
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"iccid": "8934909001500561503"
|
||||
}
|
||||
}
|
||||
|
||||
body:form-urlencoded {
|
||||
iccid: 8933201125065160406
|
||||
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
|
||||
}
|
||||
|
||||
16
docs/sim-api/Activation Email Health.bru
Normal file
16
docs/sim-api/Activation Email Health.bru
Normal file
@@ -0,0 +1,16 @@
|
||||
meta {
|
||||
name: Activation Email Health
|
||||
type: http
|
||||
seq: 9
|
||||
}
|
||||
|
||||
post {
|
||||
url: https://sf-sim-activation.savefamily.net/health
|
||||
body: none
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
}
|
||||
42
docs/sim-api/Activation Email.bru
Normal file
42
docs/sim-api/Activation Email.bru
Normal file
@@ -0,0 +1,42 @@
|
||||
meta {
|
||||
name: Activation Email
|
||||
type: http
|
||||
seq: 8
|
||||
}
|
||||
|
||||
post {
|
||||
url: https://sf-sim-activation.savefamily.net/send-activation-mail
|
||||
body: json
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
headers {
|
||||
x-apikey-sim-activation: 9e48c4ac-1ab0-4397-b3f3-6c239200dfe6
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"id": "11",
|
||||
"retry_count": 0,
|
||||
"max_retry": null,
|
||||
"max_date_retry": null,
|
||||
"iccids": [
|
||||
"8933201125068886080"
|
||||
],
|
||||
"request_id": "14362",
|
||||
"mass_action_id": "5208468",
|
||||
"operation": "activate",
|
||||
"start_date": "2026-02-13T11:08:42.499Z",
|
||||
"last_change_date": "2026-02-16T09:24:36.073Z",
|
||||
"end_date": "2026-02-16T09:24:36.073Z",
|
||||
"error": null,
|
||||
"status": "finished",
|
||||
"objenious_status": "Terminé",
|
||||
"msisdn": "33764399870"
|
||||
}
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
}
|
||||
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,10 +11,45 @@ post {
|
||||
}
|
||||
|
||||
body:form-urlencoded {
|
||||
iccid: 8933201124059176320
|
||||
iccid: 8933201125068889894
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
}
|
||||
|
||||
docs {
|
||||
El endpoint recibe como body
|
||||
```
|
||||
{
|
||||
iccid: string,
|
||||
update_webhook?: string
|
||||
}
|
||||
```
|
||||
|
||||
`update_webhook` está en desarrollo, pero será donde se mande la actualizacion de la cancelación cuando haya una respuesta de la API externa.
|
||||
|
||||
Si la llamada tiene exito devuelve:
|
||||
``` json
|
||||
{
|
||||
data: {
|
||||
iccid: string,
|
||||
message_id: string,
|
||||
operation: "cancelation"
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
message_id se usará para la llamada /orders/message_id/}{message_id}
|
||||
|
||||
Si la llamada falla devolvera:
|
||||
```json
|
||||
{
|
||||
errors: {
|
||||
msg: string
|
||||
... (campos extra de gestion del error)
|
||||
}
|
||||
}
|
||||
```
|
||||
}
|
||||
|
||||
16
docs/sim-api/Docs.bru
Normal file
16
docs/sim-api/Docs.bru
Normal file
@@ -0,0 +1,16 @@
|
||||
meta {
|
||||
name: Docs
|
||||
type: http
|
||||
seq: 11
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{baseurl}}/docs/sim-api-documentation.html
|
||||
body: none
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
meta {
|
||||
name: Health
|
||||
type: http
|
||||
seq: 5
|
||||
seq: 7
|
||||
}
|
||||
|
||||
get {
|
||||
|
||||
16
docs/sim-api/Nos/Select.bru
Normal file
16
docs/sim-api/Nos/Select.bru
Normal file
@@ -0,0 +1,16 @@
|
||||
meta {
|
||||
name: Select
|
||||
type: http
|
||||
seq: 1
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{baseurl}}/portugal/select
|
||||
body: none
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
}
|
||||
8
docs/sim-api/Nos/folder.bru
Normal file
8
docs/sim-api/Nos/folder.bru
Normal file
@@ -0,0 +1,8 @@
|
||||
meta {
|
||||
name: Nos
|
||||
seq: 15
|
||||
}
|
||||
|
||||
auth {
|
||||
mode: inherit
|
||||
}
|
||||
26
docs/sim-api/Objenious/France Suspended Lines.bru
Normal file
26
docs/sim-api/Objenious/France Suspended Lines.bru
Normal file
@@ -0,0 +1,26 @@
|
||||
meta {
|
||||
name: France Suspended Lines
|
||||
type: http
|
||||
seq: 16
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{baseurl}}/france/lines?status=SUSPENDED&limit=100
|
||||
body: none
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
params:query {
|
||||
status: SUSPENDED
|
||||
limit: 100
|
||||
}
|
||||
|
||||
vars:pre-request {
|
||||
iccid: 8933201125065160331
|
||||
~baseurl: http://localhost:3002
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
}
|
||||
21
docs/sim-api/Objenious/France Suspended Time.bru
Normal file
21
docs/sim-api/Objenious/France Suspended Time.bru
Normal file
@@ -0,0 +1,21 @@
|
||||
meta {
|
||||
name: France Suspended Time
|
||||
type: http
|
||||
seq: 15
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{baseurl}}/france/lines/{{iccid}}/suspended-time
|
||||
body: none
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
vars:pre-request {
|
||||
iccid: 8933201125065160331
|
||||
~baseurl: http://localhost:3002
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
}
|
||||
8
docs/sim-api/Objenious/folder.bru
Normal file
8
docs/sim-api/Objenious/folder.bru
Normal file
@@ -0,0 +1,8 @@
|
||||
meta {
|
||||
name: Objenious
|
||||
seq: 16
|
||||
}
|
||||
|
||||
auth {
|
||||
mode: inherit
|
||||
}
|
||||
16
docs/sim-api/Orders/Get pending orders.bru
Normal file
16
docs/sim-api/Orders/Get pending orders.bru
Normal file
@@ -0,0 +1,16 @@
|
||||
meta {
|
||||
name: Get pending orders
|
||||
type: http
|
||||
seq: 10
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{baseurl}}/orders/pending
|
||||
body: none
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
}
|
||||
16
docs/sim-api/Orders/Order by id.bru
Normal file
16
docs/sim-api/Orders/Order by id.bru
Normal file
@@ -0,0 +1,16 @@
|
||||
meta {
|
||||
name: Order by id
|
||||
type: http
|
||||
seq: 9
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{baseurl}}/orders/019dbeaf-8abb-7783-8b51-94fbd9f0b0df
|
||||
body: none
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
}
|
||||
16
docs/sim-api/Orders/Orders by message_id.bru
Normal file
16
docs/sim-api/Orders/Orders by message_id.bru
Normal file
@@ -0,0 +1,16 @@
|
||||
meta {
|
||||
name: Orders by message_id
|
||||
type: http
|
||||
seq: 12
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{baseurl}}/orders/message_id/019dbeaf-8abb-7783-8b51-94fbd9f0b0df
|
||||
body: none
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
}
|
||||
45
docs/sim-api/Orders/folder.bru
Normal file
45
docs/sim-api/Orders/folder.bru
Normal file
@@ -0,0 +1,45 @@
|
||||
meta {
|
||||
name: Orders
|
||||
seq: 3
|
||||
}
|
||||
|
||||
auth {
|
||||
mode: inherit
|
||||
}
|
||||
|
||||
docs {
|
||||
# Orders
|
||||
|
||||
Los *order* representan ordenes que se hacen al servidor y representan en que estado se encuentran las peticiones. Los *order* se generan cuando se solicita una operacion y devuelven su identificador en el campo `message_id` de todas las respuestas a peticiones que requieran cambios. Los identificadores de `order` son UUIDv7, aunque tambien tienen asociado un id tradicional BIGINT en la BDD.
|
||||
|
||||
## Ciclo de vida
|
||||
|
||||
Cuando se crea un *order* comienza en estado `pending`, inicando que ha entrado en la cola y está pendiente de iniciarse; una vez se ha consumido por un servicio pasa a estado `running` indicando que la operacion asociada al *order* ha comenzado, el order continuara en este estado durante un tiempo indefinido (pueden pasar semanas para algunos casos), hasta que la tara finalize correctamente o con errores. En el caso que la tarea finalize con éxito el *order* pasará a estado `finished`, en caso de que haya habido un error el estado será `failed` y se almacenará el error en los campos `error_message` y opcionalemente en `error_stacktrace` según gravedad del error.
|
||||
|
||||
- Caso normal
|
||||
`pending` -> `running` -> `finished`
|
||||
|
||||
- Error durante el consumo
|
||||
`pending` -> `failed`
|
||||
|
||||
- Error durante la operacion
|
||||
`pending` -> `running` -> `failed`
|
||||
|
||||
## Endpoints
|
||||
Estan sujetos a cambios en cuanto a mostrar información
|
||||
|
||||
- [WIP]**GET** /orders?{query}
|
||||
Devuelve todos los orders con un campo que tenga el valor especificado en la query
|
||||
|
||||
- **GET** /orders/{id}
|
||||
Devuelve el order objetivo según su UUID de mensaje (No según el uuid de mensaje)
|
||||
|
||||
- **GET** /orders/base_id/{id}
|
||||
Devuelve el id según su id de la bdd, no es el metodo normal de usar la api
|
||||
|
||||
- **GET** /orders/pending
|
||||
Devuelve todas las order que no hayan finalizado
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
meta {
|
||||
name: Pause
|
||||
type: http
|
||||
seq: 1
|
||||
seq: 5
|
||||
}
|
||||
|
||||
post {
|
||||
@@ -15,7 +15,7 @@ params:query {
|
||||
}
|
||||
|
||||
body:form-urlencoded {
|
||||
iccid: 8933201125065160414
|
||||
iccid: 8933201125065160331
|
||||
}
|
||||
|
||||
settings {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
meta {
|
||||
name: Preactivate
|
||||
type: http
|
||||
seq: 1
|
||||
seq: 6
|
||||
}
|
||||
|
||||
post {
|
||||
@@ -15,7 +15,9 @@ params:query {
|
||||
}
|
||||
|
||||
body:form-urlencoded {
|
||||
iccid: 8933201125065160380
|
||||
iccid: 8934909001500954922
|
||||
offer: mensual
|
||||
orderId: test
|
||||
}
|
||||
|
||||
settings {
|
||||
|
||||
21
docs/sim-api/ReActivate.bru
Normal file
21
docs/sim-api/ReActivate.bru
Normal file
@@ -0,0 +1,21 @@
|
||||
meta {
|
||||
name: ReActivate
|
||||
type: http
|
||||
seq: 12
|
||||
}
|
||||
|
||||
post {
|
||||
url: {{baseurl}}/sim/reActivate
|
||||
body: formUrlEncoded
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
body:form-urlencoded {
|
||||
iccid: 8934909001500561503
|
||||
~offer: SAVEFAMILY1
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
}
|
||||
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
|
||||
}
|
||||
21
docs/sim-api/Test Order.bru
Normal file
21
docs/sim-api/Test Order.bru
Normal file
@@ -0,0 +1,21 @@
|
||||
meta {
|
||||
name: Test Order
|
||||
type: http
|
||||
seq: 10
|
||||
}
|
||||
|
||||
post {
|
||||
url: {{baseurl}}/sim/test
|
||||
body: formUrlEncoded
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
body:form-urlencoded {
|
||||
iccid: 8933201125065160999
|
||||
offer: SAVEFAMILY1
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
}
|
||||
35
docs/sim-api/collection.bru
Normal file
35
docs/sim-api/collection.bru
Normal file
@@ -0,0 +1,35 @@
|
||||
docs {
|
||||
Todos los endpoint tienen unos campos comunes de entrada:
|
||||
|
||||
```ts
|
||||
{
|
||||
iccid: string,
|
||||
update_webhook?: string
|
||||
}
|
||||
```
|
||||
|
||||
`update_webhook` está en desarrollo, pero será donde se mande la actualizacion de la cancelación cuando haya una respuesta de la API externa.
|
||||
|
||||
Si la llamada tiene exito devuelve:
|
||||
```ts
|
||||
{
|
||||
data: {
|
||||
iccid: string,
|
||||
message_id: string,
|
||||
operation: string,
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
message_id se usará para la llamada /orders/message_id/}{message_id}
|
||||
|
||||
Si la llamada falla devolvera:
|
||||
```ts
|
||||
{
|
||||
errors: {
|
||||
msg: string
|
||||
... (campos extra de gestion del error)
|
||||
}
|
||||
}
|
||||
```
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
vars {
|
||||
baseurl: http://localhost:3000
|
||||
baseAlai: http://localhost:3002
|
||||
}
|
||||
color: #2E8A54
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
vars {
|
||||
baseurl: https://sf-sims.savefamilygps.net
|
||||
}
|
||||
color: #CE4F3B
|
||||
|
||||
4
docs/sim-api/environments/simconnections.bru
Normal file
4
docs/sim-api/environments/simconnections.bru
Normal file
@@ -0,0 +1,4 @@
|
||||
vars {
|
||||
baseurl: http://sim-connections.savefamilygps.net
|
||||
}
|
||||
color: #C77A0F
|
||||
20
docs/sim-api/test proxy.bru
Normal file
20
docs/sim-api/test proxy.bru
Normal file
@@ -0,0 +1,20 @@
|
||||
meta {
|
||||
name: test proxy
|
||||
type: http
|
||||
seq: 13
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{baseurl}}/simconnections/alai/select?iccid=1111111111111111111
|
||||
body: none
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
params:query {
|
||||
iccid: 1111111111111111111
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
}
|
||||
23
docs/sim-nos/Select Page.yml
Normal file
23
docs/sim-nos/Select Page.yml
Normal file
@@ -0,0 +1,23 @@
|
||||
info:
|
||||
name: Select Page
|
||||
type: http
|
||||
seq: 6
|
||||
|
||||
http:
|
||||
method: GET
|
||||
url: "{{baseurl}}/selectPage"
|
||||
params:
|
||||
- name: iccid
|
||||
value: "8935103196306448300"
|
||||
type: query
|
||||
disabled: true
|
||||
body:
|
||||
type: json
|
||||
data: ""
|
||||
auth: inherit
|
||||
|
||||
settings:
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
followRedirects: true
|
||||
maxRedirects: 5
|
||||
25
docs/sim-nos/Select.yml
Normal file
25
docs/sim-nos/Select.yml
Normal file
@@ -0,0 +1,25 @@
|
||||
info:
|
||||
name: Select
|
||||
type: http
|
||||
seq: 5
|
||||
|
||||
http:
|
||||
method: GET
|
||||
url: "{{baseurl}}/select?iccid=8935103196306448300"
|
||||
params:
|
||||
- name: iccid
|
||||
value: "8935103196306448300"
|
||||
type: query
|
||||
body:
|
||||
type: json
|
||||
data: |-
|
||||
{
|
||||
"iccid": "8933201125068890066"
|
||||
}
|
||||
auth: inherit
|
||||
|
||||
settings:
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
followRedirects: true
|
||||
maxRedirects: 5
|
||||
7
docs/sim-nos/environments/local.yml
Normal file
7
docs/sim-nos/environments/local.yml
Normal file
@@ -0,0 +1,7 @@
|
||||
name: local
|
||||
color: "#2E8A54"
|
||||
variables:
|
||||
- name: baseurl
|
||||
value: http://localhost:3001
|
||||
- secret: true
|
||||
name: token
|
||||
7
docs/sim-nos/environments/prod.yml
Normal file
7
docs/sim-nos/environments/prod.yml
Normal file
@@ -0,0 +1,7 @@
|
||||
name: prod
|
||||
color: "#CE4F3B"
|
||||
variables:
|
||||
- name: baseurl
|
||||
value: https://nosconnectcenter-api.iot-x.com
|
||||
- secret: true
|
||||
name: token
|
||||
10
docs/sim-nos/opencollection.yml
Normal file
10
docs/sim-nos/opencollection.yml
Normal file
@@ -0,0 +1,10 @@
|
||||
opencollection: 1.0.0
|
||||
|
||||
info:
|
||||
name: sim-nos
|
||||
bundled: false
|
||||
extensions:
|
||||
bruno:
|
||||
ignore:
|
||||
- node_modules
|
||||
- .git
|
||||
22
docs/sim-nos/subscriber actions.yml
Normal file
22
docs/sim-nos/subscriber actions.yml
Normal file
@@ -0,0 +1,22 @@
|
||||
info:
|
||||
name: subscriber actions
|
||||
type: http
|
||||
seq: 1
|
||||
|
||||
http:
|
||||
method: GET
|
||||
url: "{{baseurl}}/subscribers/{{iccid}}/actions"
|
||||
auth:
|
||||
type: bearer
|
||||
token: "{{token}}"
|
||||
|
||||
runtime:
|
||||
variables:
|
||||
- name: iccid
|
||||
value: "8935103196306448300"
|
||||
|
||||
settings:
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
followRedirects: true
|
||||
maxRedirects: 5
|
||||
22
docs/sim-nos/subscriber info.yml
Normal file
22
docs/sim-nos/subscriber info.yml
Normal file
@@ -0,0 +1,22 @@
|
||||
info:
|
||||
name: subscriber info
|
||||
type: http
|
||||
seq: 2
|
||||
|
||||
http:
|
||||
method: GET
|
||||
url: "{{baseurl}}/subscribers/{{iccid}}"
|
||||
auth:
|
||||
type: bearer
|
||||
token: "{{token}}"
|
||||
|
||||
runtime:
|
||||
variables:
|
||||
- name: iccid
|
||||
value: "8935103196306448300"
|
||||
|
||||
settings:
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
followRedirects: true
|
||||
maxRedirects: 5
|
||||
22
docs/sim-nos/subscriber products available.yml
Normal file
22
docs/sim-nos/subscriber products available.yml
Normal file
@@ -0,0 +1,22 @@
|
||||
info:
|
||||
name: subscriber products available
|
||||
type: http
|
||||
seq: 4
|
||||
|
||||
http:
|
||||
method: GET
|
||||
url: "{{baseurl}}/subscribers/{{iccid}}/products/available"
|
||||
auth:
|
||||
type: bearer
|
||||
token: "{{token}}"
|
||||
|
||||
runtime:
|
||||
variables:
|
||||
- name: iccid
|
||||
value: "8935103196306448300"
|
||||
|
||||
settings:
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
followRedirects: true
|
||||
maxRedirects: 5
|
||||
22
docs/sim-nos/subscribers.yml
Normal file
22
docs/sim-nos/subscribers.yml
Normal file
@@ -0,0 +1,22 @@
|
||||
info:
|
||||
name: subscribers
|
||||
type: http
|
||||
seq: 3
|
||||
|
||||
http:
|
||||
method: GET
|
||||
url: "{{baseurl}}/subscribers"
|
||||
auth:
|
||||
type: bearer
|
||||
token: "{{token}}"
|
||||
|
||||
runtime:
|
||||
variables:
|
||||
- name: iccid
|
||||
value: "8935103196306448300"
|
||||
|
||||
settings:
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
followRedirects: true
|
||||
maxRedirects: 5
|
||||
38
docs/sim-objenious/Alarmas disponibles.bru
Normal file
38
docs/sim-objenious/Alarmas disponibles.bru
Normal file
@@ -0,0 +1,38 @@
|
||||
meta {
|
||||
name: Alarmas disponibles
|
||||
type: http
|
||||
seq: 20
|
||||
}
|
||||
|
||||
get {
|
||||
url: https://api-getway.objenious.com/ws/alarms
|
||||
body: formUrlEncoded
|
||||
auth: bearer
|
||||
}
|
||||
|
||||
auth:bearer {
|
||||
token: {{ws-access-token-partenaire}}
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"identifier": {
|
||||
"identifiers": ["8933201124059175967"],
|
||||
"identifierType": "ICCID"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
body:form-urlencoded {
|
||||
~identifier.identifierType: "ICCID"
|
||||
~identifier.identifiers: ["8933201124059175967"]
|
||||
}
|
||||
|
||||
vars:pre-request {
|
||||
~id: 5187320
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
}
|
||||
24
docs/sim-objenious/Alarms/Alarm by id.bru
Normal file
24
docs/sim-objenious/Alarms/Alarm by id.bru
Normal file
@@ -0,0 +1,24 @@
|
||||
meta {
|
||||
name: Alarm by id
|
||||
type: http
|
||||
seq: 2
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{baseUrl}}alarms/{{alarmId}}
|
||||
body: none
|
||||
auth: bearer
|
||||
}
|
||||
|
||||
auth:bearer {
|
||||
token: {{ws-access-token-partenaire}}
|
||||
}
|
||||
|
||||
vars:pre-request {
|
||||
alarmId: 2439
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
}
|
||||
28
docs/sim-objenious/Alarms/Alerts.bru
Normal file
28
docs/sim-objenious/Alarms/Alerts.bru
Normal file
@@ -0,0 +1,28 @@
|
||||
meta {
|
||||
name: Alerts
|
||||
type: http
|
||||
seq: 3
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{baseUrl}}alarms/alerts?pageNumber=100
|
||||
body: none
|
||||
auth: bearer
|
||||
}
|
||||
|
||||
params:query {
|
||||
pageNumber: 100
|
||||
}
|
||||
|
||||
auth:bearer {
|
||||
token: {{ws-access-token-partenaire}}
|
||||
}
|
||||
|
||||
vars:pre-request {
|
||||
alarmId: 2439
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
}
|
||||
20
docs/sim-objenious/Alarms/All Alarms.bru
Normal file
20
docs/sim-objenious/Alarms/All Alarms.bru
Normal file
@@ -0,0 +1,20 @@
|
||||
meta {
|
||||
name: All Alarms
|
||||
type: http
|
||||
seq: 1
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{baseUrl}}alarms
|
||||
body: none
|
||||
auth: bearer
|
||||
}
|
||||
|
||||
auth:bearer {
|
||||
token: {{ws-access-token-partenaire}}
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
}
|
||||
8
docs/sim-objenious/Alarms/folder.bru
Normal file
8
docs/sim-objenious/Alarms/folder.bru
Normal file
@@ -0,0 +1,8 @@
|
||||
meta {
|
||||
name: Alarms
|
||||
seq: 21
|
||||
}
|
||||
|
||||
auth {
|
||||
mode: inherit
|
||||
}
|
||||
38
docs/sim-objenious/Alerts.bru
Normal file
38
docs/sim-objenious/Alerts.bru
Normal file
@@ -0,0 +1,38 @@
|
||||
meta {
|
||||
name: Alerts
|
||||
type: http
|
||||
seq: 23
|
||||
}
|
||||
|
||||
get {
|
||||
url: https://api-getway.objenious.com/ws/alarms
|
||||
body: formUrlEncoded
|
||||
auth: bearer
|
||||
}
|
||||
|
||||
auth:bearer {
|
||||
token: {{ws-access-token-partenaire}}
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"identifier": {
|
||||
"identifiers": ["8933201124059175967"],
|
||||
"identifierType": "ICCID"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
body:form-urlencoded {
|
||||
~identifier.identifierType: "ICCID"
|
||||
~identifier.identifiers: ["8933201124059175967"]
|
||||
}
|
||||
|
||||
vars:pre-request {
|
||||
~id: 5187320
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
}
|
||||
@@ -5,15 +5,15 @@ meta {
|
||||
}
|
||||
|
||||
get {
|
||||
url: https://api-getway.objenious.com/ws/lines?pageSize=10&identifier.identifierType=ICCID&identifier.identifiers=8933201125065160455
|
||||
url: https://api-getway.objenious.com/ws/lines?identifier.identifierType=ICCID&identifier.identifiers=8933201125065160455
|
||||
body: formUrlEncoded
|
||||
auth: bearer
|
||||
}
|
||||
|
||||
params:query {
|
||||
pageSize: 10
|
||||
identifier.identifierType: ICCID
|
||||
identifier.identifiers: 8933201125065160455
|
||||
~pageSize: 1000
|
||||
~simStatus: ACTIVATED
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
}
|
||||
@@ -37,7 +37,7 @@ body:form-urlencoded {
|
||||
}
|
||||
|
||||
vars:pre-request {
|
||||
params.id: 14111
|
||||
params.id: 15102
|
||||
}
|
||||
|
||||
settings {
|
||||
|
||||
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
|
||||
}
|
||||
@@ -5,13 +5,13 @@ meta {
|
||||
}
|
||||
|
||||
get {
|
||||
url: {{actionsUrl}}/massActions?massActionId=5192767
|
||||
url: {{actionsUrl}}/massActions?massActionId=5363116
|
||||
body: formUrlEncoded
|
||||
auth: bearer
|
||||
}
|
||||
|
||||
params:query {
|
||||
massActionId: 5192767
|
||||
massActionId: 5363116
|
||||
~identifier.identifierType: ICCID
|
||||
~identifier.identifiers: 8933201125065160463,8933201125065160422
|
||||
}
|
||||
|
||||
1843
package-lock.json
generated
1843
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
17
package.json
17
package.json
@@ -1,22 +1,26 @@
|
||||
{
|
||||
"name": "sim-eventos",
|
||||
"packageManager": "yarn@4.12.0",
|
||||
"version": "1.0.0",
|
||||
"packageManager": "yarn@4.14.1",
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
],
|
||||
"scripts": {
|
||||
"test": "vitest watch",
|
||||
"build": "yarn workspaces foreach -A --exclude sim-consumidor-nos run build && cp .env dist/ && yarn setup:runtime",
|
||||
"build": "rm -rf ./dist && yarn workspaces foreach -Api run build && yarn setup:runtime",
|
||||
"build:prod": "rm -rf ./dist && yarn workspaces foreach -Api run build:prod && yarn setup:runtime",
|
||||
"setup:runtime": "mkdir -p dist/packages/node_modules && ln -sf ../sim-shared dist/packages/node_modules/sim-shared && ln -sf ../sf-consumidor-objenious dist/packages/node_modules/sim-consumidor-objenious",
|
||||
"start": "yarn setup:runtime && yarn workspaces foreach -Apiv --exclude sim-consumidor-nos run start",
|
||||
"start": "yarn workspaces foreach -Apiv run start",
|
||||
"typecheck": "npx tsc --noEmit",
|
||||
"dev": "yarn workspaces foreach -Apiv --exclude sim-consumidor-nos run dev ",
|
||||
"dev": "yarn workspaces foreach -Apiv run dev",
|
||||
"lint": "eslint .",
|
||||
"lint:fix": "eslint --fix .",
|
||||
"format": "prettier --write .",
|
||||
"format:check": "prettier --check ."
|
||||
"format:check": "prettier --check .",
|
||||
"migrate": "yarn db-migrate -e .env -m deployment/database/migrations -t 99.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@sf-alvar/db-migrate": "1.0.6",
|
||||
"@tsconfig/node22": "^22.0.5",
|
||||
"amqp-connection-manager": "^5.0.0",
|
||||
"amqplib": "^0.10.9",
|
||||
@@ -25,7 +29,8 @@
|
||||
"dotenv": "^17.2.3",
|
||||
"express": "^5.2.1",
|
||||
"pg": "^8.18.0",
|
||||
"typescript": "^5.9.3",
|
||||
"typescript": "^6.0.3",
|
||||
"uuidv7": "^1.1.0",
|
||||
"vite": "^7.3.1",
|
||||
"vite-tsconfig-paths": "^6.0.5"
|
||||
},
|
||||
|
||||
1
packages/_template/config/env/index.ts
vendored
1
packages/_template/config/env/index.ts
vendored
@@ -1,5 +1,4 @@
|
||||
export const env = {
|
||||
ENVIRONMENT: process.env.ENVIORMENT,
|
||||
POSTGRES_USER: process.env.POSTGRES_USER,
|
||||
POSTGRES_PASSWORD: process.env.POSTGRES_PASSWORD,
|
||||
POSTGRES_PORT: process.env.POSTGRES_PORT,
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
console.log("Template")
|
||||
console.log(new Date().toISOString())
|
||||
|
||||
export default {}
|
||||
|
||||
43
packages/sim-consumidor-alai/aplication/AlaiTokenManager.ts
Normal file
43
packages/sim-consumidor-alai/aplication/AlaiTokenManager.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { AlaiRepository } from "#infrastructure/AlaiRepository.js";
|
||||
import { JWTToken } from "sim-shared/domain/JWT.js";
|
||||
import { JWTProvider } from "sim-shared/infrastructure/HTTPClient.js";
|
||||
import { httpsAgent } from "#config/httpsAgent.js";
|
||||
|
||||
export class AlaiTokenManager implements JWTProvider<{}> {
|
||||
|
||||
isRefreshing: boolean = false;
|
||||
authToken: JWTToken<{}> | undefined;
|
||||
|
||||
private async getNewAuthToken() {
|
||||
// TODO: Si no funcionase hay que reprogramar los mensajes para ser
|
||||
// consumidos mas tarde.
|
||||
const res = await AlaiRepository.login(httpsAgent);
|
||||
|
||||
if (res.error != undefined) {
|
||||
console.error("Error obteniendo el token de ALAI", res.error)
|
||||
} else {
|
||||
console.log("Obtenido token de ALAI: ", res)
|
||||
this.authToken = new JWTToken(res.data.accessToken)
|
||||
}
|
||||
}
|
||||
|
||||
public tryRefreshToken(): Promise<JWTToken<{}>> {
|
||||
// En Alai no existe el concepto de refresh, se solicita otro token nuevo
|
||||
return this.getAccessToken()
|
||||
};
|
||||
|
||||
public async getAccessToken(): Promise<JWTToken<{}>> {
|
||||
// Caso 1: El token actual es valido
|
||||
if (this.authToken != undefined && !this.authToken.isExpired()) {
|
||||
return this.authToken
|
||||
} else {
|
||||
// Caso 2: El token actual no existe o ha expirado
|
||||
await this.getNewAuthToken()
|
||||
}
|
||||
|
||||
// Si después de todo no se ha generado el token es un error catastrofico
|
||||
if (this.authToken == undefined) throw new Error("Error obteniendo tokens de auth")
|
||||
|
||||
return this.authToken
|
||||
};
|
||||
}
|
||||
50
packages/sim-consumidor-alai/aplication/DebugTokenManager.ts
Normal file
50
packages/sim-consumidor-alai/aplication/DebugTokenManager.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { JWTToken } from "sim-shared/domain/JWT.js";
|
||||
import { JWTProvider } from "sim-shared/infrastructure/HTTPClient.js";
|
||||
import { LegacyJWTTokenRepository } from "#infrastructure/LegacyJWTTokensRepository.js";
|
||||
import { env } from "#config/env/env.js";
|
||||
|
||||
const tokenDir = String(env.ALAI_CERTIFICATES_DIR)
|
||||
const tokenFile = ".debugToken"
|
||||
|
||||
/**
|
||||
* Usa un token guardado a mano en archivo para no gastar tokens de Alai
|
||||
*/
|
||||
export class DebugTokenManager implements JWTProvider<{}> {
|
||||
|
||||
isRefreshing: boolean = false;
|
||||
authToken: JWTToken<{}> | undefined;
|
||||
|
||||
private async getNewAuthToken() {
|
||||
// TODO: Si no funcionase hay que reprogramar los mensajes para ser
|
||||
// consumidos mas tarde.
|
||||
const res = LegacyJWTTokenRepository.getTokenFromFile(tokenDir, tokenFile)
|
||||
|
||||
|
||||
if (res.error != undefined) {
|
||||
console.error("Error obteniendo el token de ALAI", res.error)
|
||||
} else {
|
||||
this.authToken = new JWTToken(res.data)
|
||||
console.log("[d] Token DEBUG: ", this.authToken)
|
||||
}
|
||||
}
|
||||
|
||||
public tryRefreshToken(): Promise<JWTToken<{}>> {
|
||||
// En Alai no existe el concepto de refresh, se solicita otro token nuevo
|
||||
return this.getAccessToken()
|
||||
};
|
||||
|
||||
public async getAccessToken(): Promise<JWTToken<{}>> {
|
||||
// Caso 1: El token actual es valido
|
||||
if (this.authToken != undefined && !this.authToken.isExpired()) {
|
||||
return this.authToken
|
||||
} else {
|
||||
// Caso 2: El token actual no existe o ha expirado
|
||||
await this.getNewAuthToken()
|
||||
}
|
||||
|
||||
// Si después de todo no se ha generado el token es un error catastrofico
|
||||
if (this.authToken == undefined) throw new Error("Error obteniendo tokens de auth")
|
||||
|
||||
return this.authToken
|
||||
};
|
||||
}
|
||||
225
packages/sim-consumidor-alai/aplication/SimAlai.controller.ts
Normal file
225
packages/sim-consumidor-alai/aplication/SimAlai.controller.ts
Normal file
@@ -0,0 +1,225 @@
|
||||
import { ConsumeMessage } from "amqplib";
|
||||
import { Request, Response } from "express"
|
||||
import { SimAlaiUsecases } from "./SimAlai.usecases.js";
|
||||
import { EventBus } from "sim-shared/domain/EventBus.port.js";
|
||||
import { Result } from "sim-shared/domain/Result.js";
|
||||
import { SimEvents } from "sim-shared/domain/SimEvents.js";
|
||||
import { iccidValidator } from "./httpValidators.js";
|
||||
import { alaiSimToCommonSim } from "#domain/transformers.js";
|
||||
|
||||
type ErrorUsecase = {
|
||||
msg: string,
|
||||
stackTrace?: string
|
||||
}
|
||||
|
||||
export class SimAlaiController {
|
||||
|
||||
constructor(
|
||||
private uscases: SimAlaiUsecases,
|
||||
private eventBus: EventBus,
|
||||
) {
|
||||
}
|
||||
|
||||
private validateMsg(msg: ConsumeMessage | null) {
|
||||
if (msg == undefined) return false;
|
||||
const msgData = this.decodeMsg(msg) as SimEvents.general
|
||||
if (msgData == undefined || msgData.payload == undefined) throw new Error("Mensaje invalido")
|
||||
return msgData;
|
||||
}
|
||||
|
||||
private decodeMsg(msg: ConsumeMessage): object | undefined {
|
||||
if (msg.content == undefined) {
|
||||
console.warn('[Sim.controller] Mensaje vacío');
|
||||
return undefined;
|
||||
}
|
||||
|
||||
try {
|
||||
// Convertir el Buffer a String (UTF-8)
|
||||
const contentJson = JSON.parse(Buffer.from(msg.content).toString('utf8'))
|
||||
return contentJson;
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error al decodificar JSON:', error);
|
||||
console.error(Buffer.from(msg.content).toString(("utf8")))
|
||||
// Aquí podrías decidir devolver el string crudo o null
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Metodo duplicado se puede generalizar la a una clase sharedController con las funciones basicas
|
||||
* TODO: meter un check de 429
|
||||
*/
|
||||
private async tryUseCase<T extends any>
|
||||
(msg: ConsumeMessage, usecase: () => Promise<Result<ErrorUsecase, T>>): Promise<Result<ErrorUsecase, T>> {
|
||||
try {
|
||||
const result = await usecase()
|
||||
if (result.error == undefined) {
|
||||
await this.eventBus.ack(msg)
|
||||
return result
|
||||
} else {
|
||||
console.error("Error procesando el caso de uso (Alai)", result.error)
|
||||
this.eventBus.nack(msg)
|
||||
return result
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Error general procesando el caso de uso (Alai)")
|
||||
this.eventBus.nack(msg)
|
||||
return {
|
||||
error: {
|
||||
msg: String(e),
|
||||
stackTrace: String(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public activate() {
|
||||
return async (msg: ConsumeMessage) => {
|
||||
console.log("[i] Evento activate ", msg.fields)
|
||||
const data = this.validateMsg(msg) as SimEvents.activation
|
||||
const iccid = data.payload.iccid
|
||||
const correlation_id = data.headers?.message_id
|
||||
const externalId = data.payload.orderId
|
||||
|
||||
const res = await this.tryUseCase(msg, this.uscases.activate({
|
||||
iccid: iccid,
|
||||
correlation_id: correlation_id,
|
||||
}))
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
public preactivate() {
|
||||
return async (msg: ConsumeMessage) => {
|
||||
console.log("[i] Evento preactivate ", msg)
|
||||
const data = this.validateMsg(msg) as SimEvents.preActivation
|
||||
const iccid = data.payload.iccid
|
||||
const correlation_id = data.headers?.message_id
|
||||
const externalId = data.payload.orderId
|
||||
|
||||
console.log("MSG:", data, data.headers)
|
||||
|
||||
const res = await this.tryUseCase(msg, this.uscases.preactivate({
|
||||
iccid: iccid,
|
||||
correlation_id: correlation_id,
|
||||
externalId: externalId
|
||||
}))
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
public suspend() {
|
||||
return async (msg: ConsumeMessage) => {
|
||||
console.log("Evento suspend ", msg.fields)
|
||||
const data = this.validateMsg(msg) as SimEvents.suspend
|
||||
const iccid = data.payload.iccid
|
||||
const correlation_id = data.headers?.message_id
|
||||
const res = await this.tryUseCase(msg, this.uscases.suspend({
|
||||
iccid: iccid,
|
||||
correlation_id: correlation_id
|
||||
}))
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public reActivate() {
|
||||
return async (msg: ConsumeMessage) => {
|
||||
console.log("Evento reActivate ", msg.fields)
|
||||
const data = this.validateMsg(msg) as SimEvents.reActivation
|
||||
const iccid = data.payload.iccid
|
||||
const correlation_id = data.headers?.message_id
|
||||
const res = await this.tryUseCase(msg, this.uscases.reactivate({
|
||||
iccid: iccid,
|
||||
correlation_id: correlation_id
|
||||
}))
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public terminate() {
|
||||
return async (msg: ConsumeMessage) => {
|
||||
console.log("Evento reActivate ", msg.fields, msg)
|
||||
const data = this.validateMsg(msg) as SimEvents.reActivation
|
||||
const iccid = data.payload.iccid
|
||||
const correlation_id = data.headers?.message_id
|
||||
const res = await this.tryUseCase(msg, this.uscases.terminate({
|
||||
iccid: iccid,
|
||||
correlation_id: correlation_id
|
||||
}))
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Select especificamente por REST para evitar pasar por las colas.
|
||||
* La respuesta es instantanea no se tiene que registrar como operación.
|
||||
*/
|
||||
public selectREST() {
|
||||
return async (req: Request, res: Response) => {
|
||||
const { query } = req
|
||||
const body = { iccid: query.iccid as string }
|
||||
console.log("Evento select", body)
|
||||
const validateBody = iccidValidator.validate(body);
|
||||
|
||||
if (validateBody.error != undefined) {
|
||||
res.status(422).json(validateBody)
|
||||
return;
|
||||
}
|
||||
|
||||
const iccid: string | string[] = body.iccid
|
||||
|
||||
if (Array.isArray(iccid)) {
|
||||
// TODO: Automatizar la paginacion
|
||||
//const usecaseRes = this.uscases.selectMany({ iccid })
|
||||
} else {
|
||||
//const usecaseRes = await this.uscases.selectOne(iccid)
|
||||
const usecaseRes = await this.uscases.selectCompleteSim(iccid)
|
||||
if (usecaseRes.error != undefined) {
|
||||
res.status(500).json(usecaseRes)
|
||||
return;
|
||||
} else {
|
||||
const { sim, subscription, imei } = usecaseRes.data
|
||||
const simData = alaiSimToCommonSim(sim, subscription, imei)
|
||||
res.send(simData)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
res.status(200).json(validateBody)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
public selectPageREST() {
|
||||
return async (req: Request, res: Response) => {
|
||||
const { offset, limit, filter, orderBy } = req.query
|
||||
const params = {
|
||||
offset: (offset != undefined) ? Number(offset) : undefined,
|
||||
limit: (limit != undefined) ? Number(limit) : undefined,
|
||||
filter: (filter != undefined) ? String(filter) : undefined,
|
||||
orderBy: (orderBy != undefined) ? String(orderBy) : undefined
|
||||
}
|
||||
|
||||
const usecaseRes = await this.uscases.selectPage(params)
|
||||
|
||||
if (usecaseRes.error != undefined) {
|
||||
res.status(500).json(usecaseRes)
|
||||
return;
|
||||
} else {
|
||||
res.status(200).send(usecaseRes.data)
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
**/
|
||||
}
|
||||
|
||||
|
||||
79
packages/sim-consumidor-alai/aplication/SimAlai.router.ts
Normal file
79
packages/sim-consumidor-alai/aplication/SimAlai.router.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
/**
|
||||
* Dirige cada mensaje dependiendo de el tipo de acción que contenga
|
||||
* Podría hacerse con varias colas, pero así se controla mejor que
|
||||
* las operaciones se hagan de 1 en 1.
|
||||
*/
|
||||
|
||||
import { ConsumeMessage } from "amqplib";
|
||||
import { EventBus } from "sim-shared/domain/EventBus.port.js";
|
||||
import { Result } from "sim-shared/domain/Result.js";
|
||||
import { SimAlaiController } from "./SimAlai.controller.js";
|
||||
|
||||
type FuncType = ((m: ConsumeMessage) => Promise<Result<{ msg: string, stackTrace?: string }, any>>)
|
||||
|
||||
export class SimAlaiRouter {
|
||||
private readonly routes: Map<string, FuncType>;
|
||||
|
||||
// WIP
|
||||
constructor(
|
||||
private readonly simController: SimAlaiController,
|
||||
private readonly eventBus: EventBus
|
||||
) {
|
||||
this.routes = new Map<string, FuncType>([
|
||||
["activate", this.simController.activate()],
|
||||
["pause", this.simController.suspend()],
|
||||
["reactivate", this.simController.reActivate()],
|
||||
["cancel", this.simController.terminate()],
|
||||
["preactivate", this.simController.preactivate()]
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enruta el mensaje a la acción correspondiente basándose en la routing key
|
||||
* TODO: No estoy seguro que deba meter el nack aqui
|
||||
* - De moemento el ack-nack se gestiona en los controller, por si acaso hay casos
|
||||
* limite en
|
||||
*/
|
||||
public route = async (msg: ConsumeMessage | null): Promise<void> => {
|
||||
if (!msg) {
|
||||
console.error("[Router] Mensaje vacío");
|
||||
return;
|
||||
}
|
||||
|
||||
const action = this.extractAction(msg);
|
||||
|
||||
if (!action) {
|
||||
console.error("[Router] La routing key no tiene una acción definida", msg.fields.routingKey);
|
||||
this.eventBus.nack(msg)
|
||||
return;
|
||||
}
|
||||
|
||||
const handler = this.routes.get(action);
|
||||
|
||||
if (!handler) {
|
||||
console.error(`[Router] La acción '${action}' no tiene un controlador asociado`);
|
||||
this.eventBus.nack(msg)
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
console.log("[Router] Ejecutando operación:", action);
|
||||
|
||||
// El controlador devuelve una función (thunk) que debe ser ejecutada
|
||||
const executeParams = handler(msg);
|
||||
|
||||
if (typeof executeParams === "function") {
|
||||
const res = await executeParams;
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error(`[Router] Error al ejecutar la operación '${action}':`, error);
|
||||
this.eventBus.nack(msg)
|
||||
}
|
||||
};
|
||||
|
||||
private extractAction(msg: ConsumeMessage): string | undefined {
|
||||
// Se asume que la acción está en la tercera posición: domain.compañia.accion
|
||||
return msg.fields.routingKey.split(".")[2];
|
||||
}
|
||||
}
|
||||
302
packages/sim-consumidor-alai/aplication/SimAlai.usecases.ts
Normal file
302
packages/sim-consumidor-alai/aplication/SimAlai.usecases.ts
Normal file
@@ -0,0 +1,302 @@
|
||||
/**
|
||||
* Documentación de referencia:
|
||||
* https://pelion-help.iot-x.com/nos/en-US/Content/API/APIReference/API%20Reference.htm?tocpath=_____7
|
||||
*
|
||||
* En nos el correlation_id ya va a ser obligatorio en todos los mensajes
|
||||
*
|
||||
* TODO:
|
||||
* - Control de errores más preciso
|
||||
*
|
||||
*/
|
||||
import { AlaiAPI } from "#domain/AlaiAPI.js";
|
||||
import { AlaiRepository } from "#infrastructure/AlaiRepository.js";
|
||||
import { ErrorOrderDTO, FinishOrderDTO, UpdateOrderDTO } from "sim-shared/domain/Order.js";
|
||||
import { Result } from "sim-shared/domain/Result.js";
|
||||
import { HttpClient } from "sim-shared/infrastructure/HTTPClient.js";
|
||||
import { OrderRepository } from "sim-shared/infrastructure/OrderRepository.js";
|
||||
|
||||
export class SimAlaiUsecases {
|
||||
constructor(
|
||||
private httpClient: HttpClient,
|
||||
private alaiRepository: AlaiRepository,
|
||||
private orderRepository: OrderRepository
|
||||
) {
|
||||
}
|
||||
|
||||
private async setRunning(correlation_id: string) {
|
||||
const updateData: UpdateOrderDTO = {
|
||||
new_status: "running",
|
||||
correlation_id: correlation_id
|
||||
}
|
||||
const order = await this.orderRepository.updateOrder(updateData)
|
||||
return order
|
||||
}
|
||||
|
||||
private async setFinished(correlation_id: string) {
|
||||
// En NOS el updateOrder se hace con el correlation_id que viene en la cabecera del
|
||||
// mensaje consumido
|
||||
const updateData: FinishOrderDTO = {
|
||||
correlation_id: correlation_id
|
||||
}
|
||||
const order = await this.orderRepository.finishOrder(updateData)
|
||||
return order
|
||||
}
|
||||
|
||||
private async setFailed(correlation_id: string, reason: string, stackTrace?: string) {
|
||||
// En NOS el updateOrder se hace con el correlation_id que viene en la cabecera del
|
||||
// mensaje consumido
|
||||
const updateData: ErrorOrderDTO = {
|
||||
status: "failed",
|
||||
correlation_id: correlation_id,
|
||||
reason: reason,
|
||||
error: reason,
|
||||
stackTrace: stackTrace
|
||||
}
|
||||
|
||||
console.log("SET FAILED DATA:", updateData)
|
||||
const order = await this.orderRepository.errorOrder(updateData)
|
||||
console.log("SET FAILED RES:", order)
|
||||
return order
|
||||
}
|
||||
|
||||
/**
|
||||
* Gestiona el ciclo de vida de una petición. No aplica
|
||||
* a peticiones de lectura (no pasan por la cola y no generan un order)
|
||||
*/
|
||||
public usecaseTemplate<T, R>(
|
||||
func: (_: T) => Promise<Result<{ msg: string, stackTrace?: string }, R>>,
|
||||
args: T,
|
||||
correlation_id?: string | undefined
|
||||
) {
|
||||
return async (): Promise<Result<{ msg: string, stackTrace?: string }, R>> => {
|
||||
// Operacion pending -> running
|
||||
if (correlation_id != undefined)
|
||||
this.setRunning(correlation_id)
|
||||
.then()
|
||||
.catch(e => console.error("Error actualizando el order", e))
|
||||
else
|
||||
console.warn("[!] Se ha lanzado una caso de uso sin correlation_id")
|
||||
|
||||
try {
|
||||
const res = await func(args)
|
||||
|
||||
if (res.error != undefined) {
|
||||
console.log("Error peticion: ", res, correlation_id)
|
||||
if (correlation_id != undefined)
|
||||
this.setFailed(correlation_id, res.error.msg, res.error.stackTrace)
|
||||
.then(e => console.log("failed", e))
|
||||
.catch(e => console.error(e))
|
||||
return res;
|
||||
} else {
|
||||
if (correlation_id != undefined)
|
||||
this.setFinished(correlation_id).then()
|
||||
return res;
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
if (correlation_id != undefined)
|
||||
this.setFailed(correlation_id, "Error general de operacion de SIM (NOS) ", String(e)).then()
|
||||
return {
|
||||
error: {
|
||||
msg: "Error general de operacion de SIM (NOS) " + String(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public activate(args: {
|
||||
iccid: string,
|
||||
correlation_id: string | undefined,
|
||||
}) {
|
||||
return this.usecaseTemplate(async (iccid /*iccid*/) => {
|
||||
const sim = await this.alaiRepository.getSimByICCID(iccid)
|
||||
if (sim.error != undefined) {
|
||||
return sim
|
||||
}
|
||||
|
||||
if (sim.data == undefined) {
|
||||
return {
|
||||
error: {
|
||||
msg: `La sim ${iccid} no se ha encontrado`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const subscriptionId = sim.data.subscription!.id
|
||||
|
||||
if (subscriptionId == undefined) {
|
||||
return {
|
||||
error: {
|
||||
msg: `La sim ${iccid} no tiene un id de subscripción`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const activationRes = await this.alaiRepository.activateSubscription(subscriptionId)
|
||||
return activationRes
|
||||
|
||||
}, args.iccid, args.correlation_id)
|
||||
}
|
||||
|
||||
public preactivate(args: {
|
||||
iccid: string,
|
||||
correlation_id: string | undefined,
|
||||
externalId: string | undefined // Por compatibilidad
|
||||
}) {
|
||||
const inputargs = {
|
||||
iccid: args.iccid,
|
||||
externalId: args.externalId
|
||||
}
|
||||
return this.usecaseTemplate(async (args) => {
|
||||
const order = await this.alaiRepository.createOrder()
|
||||
if (order.error != undefined) {
|
||||
return order
|
||||
}
|
||||
const orderId = order.data.id
|
||||
const reserve = await this.alaiRepository.createReserve(orderId, args.iccid)
|
||||
if (reserve.error != undefined) {
|
||||
return reserve
|
||||
}
|
||||
|
||||
const applyOrder = await this.alaiRepository.applyOrder(orderId)
|
||||
if (applyOrder.error != undefined) {
|
||||
// TODO: gestion del error
|
||||
// reusar el orderId
|
||||
return applyOrder
|
||||
}
|
||||
|
||||
const preactivatedSim = await this.alaiRepository.getSimByICCID(args.iccid)
|
||||
if (preactivatedSim.error != undefined) {
|
||||
return preactivatedSim
|
||||
}
|
||||
|
||||
// TODO: Controlar sim no encotrada (No deberia pasar)
|
||||
const subscriptionId = preactivatedSim.data!.subscription!.id
|
||||
if (args.externalId) {
|
||||
const externalIdAdded = await this.alaiRepository.changeExternalId(subscriptionId, args.externalId)
|
||||
if (externalIdAdded.error != undefined) {
|
||||
return externalIdAdded
|
||||
}
|
||||
}
|
||||
|
||||
// En connections acaba buscando el numero.
|
||||
const subscription = await this.alaiRepository.getSubscriptionById(subscriptionId)
|
||||
return subscription
|
||||
}, inputargs, args.correlation_id)
|
||||
}
|
||||
|
||||
public suspend(args: {
|
||||
iccid: string,
|
||||
correlation_id: string | undefined
|
||||
}) {
|
||||
return this.usecaseTemplate(async (args) => {
|
||||
const subscription = await this.alaiRepository.getSimByICCID(args.iccid)
|
||||
if (subscription.error != undefined) {
|
||||
return subscription
|
||||
}
|
||||
|
||||
// TODO: Controlar que no se encuentre la subscription
|
||||
const subscriptionid = subscription.data?.subscription?.id
|
||||
const suspension = this.alaiRepository.pauseSubscription(subscriptionid!)
|
||||
return suspension
|
||||
}, args, args.correlation_id)
|
||||
}
|
||||
|
||||
|
||||
public reactivate(args: {
|
||||
iccid: string,
|
||||
correlation_id: string | undefined
|
||||
}) {
|
||||
return this.usecaseTemplate(async (args) => {
|
||||
const subscription = await this.alaiRepository.getSimByICCID(args.iccid)
|
||||
if (subscription.error != undefined) {
|
||||
return subscription
|
||||
}
|
||||
const subscriptionid = subscription.data?.subscription?.id
|
||||
// TODO: Controlar que no se encuentre la subscription
|
||||
const suspension = this.alaiRepository.unPauseSubscription(subscriptionid!)
|
||||
return suspension
|
||||
}, args, args.correlation_id)
|
||||
}
|
||||
|
||||
|
||||
public terminate(args: {
|
||||
iccid: string,
|
||||
correlation_id: string | undefined
|
||||
}) {
|
||||
return this.usecaseTemplate(async (args) => {
|
||||
const subscription = await this.alaiRepository.getSimByICCID(args.iccid)
|
||||
if (subscription.error != undefined) {
|
||||
return subscription
|
||||
}
|
||||
|
||||
// TODO: Controlar que no se encuentre la subscription
|
||||
const suspension = this.alaiRepository.terminateSubscription(subscription.data!.id)
|
||||
return suspension
|
||||
}, args, args.correlation_id)
|
||||
}
|
||||
|
||||
public async selectOne(iccid: string) {
|
||||
const sim = await this.alaiRepository.getSimByICCID(iccid)
|
||||
return sim
|
||||
}
|
||||
|
||||
/**
|
||||
* Para sacar los datos de una liena hay que sacar sim -> subscripcion -> imei
|
||||
* son 3 llamadas distintas.
|
||||
*/
|
||||
public async selectCompleteSim(iccid: string): Promise<Result<{ msg: string, stackTrace?: string }, {
|
||||
sim: AlaiAPI.Sim,
|
||||
subscription?: AlaiAPI.Subscription,
|
||||
imei?: AlaiAPI.GetImeiSubscriptionDTO
|
||||
}>> {
|
||||
const sim = await this.alaiRepository.getSimByICCID(iccid)
|
||||
|
||||
if (sim.error != undefined) {
|
||||
return sim
|
||||
}
|
||||
|
||||
if (sim.data == undefined) {
|
||||
return {
|
||||
error: {
|
||||
msg: `La sim ${iccid} no se ha encontrado`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// En este caso la tarjeta no se ha preactivado, por lo que no tiene subscripcion
|
||||
if (sim.data.subscription == undefined) {
|
||||
return {
|
||||
data: {
|
||||
sim: sim.data,
|
||||
subscription: undefined,
|
||||
imei: undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const subscriptionId = sim.data.subscription.id
|
||||
const subscription = await this.alaiRepository.getSubscriptionById(subscriptionId)
|
||||
|
||||
if (subscription.error != undefined) {
|
||||
return subscription
|
||||
}
|
||||
|
||||
const imei = await this.alaiRepository.getImeiFromSubscription(subscriptionId)
|
||||
if (imei.error != undefined) {
|
||||
return imei
|
||||
}
|
||||
|
||||
return {
|
||||
data: {
|
||||
sim: sim.data!,
|
||||
subscription: subscription.data!,
|
||||
imei: imei.data!
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
55
packages/sim-consumidor-alai/aplication/SslService.ts
Normal file
55
packages/sim-consumidor-alai/aplication/SslService.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import { Result } from "sim-shared/domain/Result.js";
|
||||
|
||||
export type P12Cert = {
|
||||
cainfo: string,
|
||||
p12cert: string
|
||||
}
|
||||
|
||||
export type SSLCert = {
|
||||
cainfo: string,
|
||||
sslcert: string,
|
||||
keypem: string
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO:
|
||||
* - Se ha usado https.Agent en su lugar, eliminar si no se usa
|
||||
*/
|
||||
export class SSLCertificateLoader {
|
||||
|
||||
constructor(
|
||||
private certificatesDir: string,
|
||||
) {
|
||||
}
|
||||
|
||||
public loadCertificatesP12(caFile: string, certFile: string): Result<string, P12Cert> {
|
||||
try {
|
||||
const cainfo = fs.readFileSync(path.resolve(this.certificatesDir, caFile)).toString();
|
||||
const p12cert = fs.readFileSync(path.resolve(this.certificatesDir, certFile)).toString();
|
||||
return { data: { cainfo, p12cert } };
|
||||
} catch (e) {
|
||||
console.error("[x] Error cargando los certificados P12", e)
|
||||
return {
|
||||
error: String(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public loadCertificatesSSL(caFile: string, certFile: string, keyFile: string): Result<string, SSLCert> {
|
||||
try {
|
||||
const cainfo = fs.readFileSync(path.resolve(this.certificatesDir, caFile)).toString();
|
||||
const sslcert = fs.readFileSync(path.resolve(this.certificatesDir, certFile), { encoding: null }).toString();
|
||||
const keypem = fs.readFileSync(path.resolve(this.certificatesDir, keyFile), { encoding: null }).toString();
|
||||
return { data: { cainfo, sslcert, keypem } };
|
||||
} catch (e) {
|
||||
console.error("[x] Error cargando los certificados SSL", e)
|
||||
return {
|
||||
error: String(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
39
packages/sim-consumidor-alai/aplication/httpValidators.ts
Normal file
39
packages/sim-consumidor-alai/aplication/httpValidators.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { BodyValidator, Validator } from "sim-shared/aplication/BodyValidator.js";
|
||||
|
||||
const iccidNotNull = <Validator<{ iccid: unknown }>>{
|
||||
field: "iccid",
|
||||
errorMsg: "El iccid no está definido",
|
||||
validationFunc: (a: { iccid: unknown }) => {
|
||||
return (a.iccid != null && a.iccid != undefined)
|
||||
}
|
||||
}
|
||||
|
||||
const iccidValueOrArray = <Validator<{ iccid: unknown }>>{
|
||||
field: "iccid",
|
||||
errorMsg: "El iccid debe de ser un único valor o una lista",
|
||||
validationFunc: (a: { iccid: unknown }) => {
|
||||
return (typeof a.iccid == "string" || Array.isArray(a.iccid))
|
||||
}
|
||||
}
|
||||
|
||||
const iccidLongitudValidator = <Validator<{ iccid: string | string[] }>>{
|
||||
field: "iccid",
|
||||
errorMsg: "La longitud del iccid/s es incorrecta debera ser de 19 caracteres",
|
||||
validationFunc: (a: { iccid: string | string[] }) => {
|
||||
if (Array.isArray(a.iccid)) {
|
||||
const res = (a.iccid as string[]).filter(e => e.length != 19)
|
||||
if (res.length > 0) return false;
|
||||
} else {
|
||||
return (a.iccid as string).length == 19
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
export const iccidValidator = new BodyValidator<{ iccid: string | string[] }>(
|
||||
[
|
||||
iccidNotNull,
|
||||
iccidValueOrArray,
|
||||
iccidLongitudValidator,
|
||||
]
|
||||
)
|
||||
|
||||
1
packages/sim-consumidor-alai/certificates/.debugToken
Normal file
1
packages/sim-consumidor-alai/certificates/.debugToken
Normal file
@@ -0,0 +1 @@
|
||||
eyJhbGciOiJIUzM4NCJ9.eyJiciI6InNhdmVmYW1pbHkiLCJpcCI6Ijg4LjE1LjE1Ny4xNjciLCJzdWIiOiJwYWxvbWFpYmFuZXoiLCJzIjoiRVdTMTY3MzRhYTM2MDY1M2EwIiwicG9zIjoic2F2ZWZhbWlseUNhYyIsImlkV3NVc2VyIjoiODYiLCJpc012bmEiOmZhbHNlLCJkb21haW4iOiJBbGFpfHNhdmVmYW1pbHkiLCJpYXQiOjE3Nzg2ODQ0NjIsImV4cCI6MTc3ODY5NTI2Mn0.wMWgjaOErm5clang7ErYzREU56okgpXWzq1zihT4lOfUDRQ005r-nCHJu7rpilj1
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user