10 Commits

Author SHA1 Message Date
7c2e86779a Pausa 2026-04-07 10:08:21 +02:00
1780cf1743 Pausa 2026-04-07 10:06:45 +02:00
10104c202f Merge branch 'main' into alarmas-objenious 2026-04-07 09:05:39 +02:00
1b6da651a6 Ajustado el periodo de comprobaciones 2026-03-27 12:47:10 +01:00
9b305f887f Test .env ajustado 2026-03-27 12:24:20 +01:00
9506b9e28e Error de nombre de activacion 2026-03-27 10:59:15 +01:00
61c0edca07 Logs del envio 2026-03-27 10:52:03 +01:00
9470b5605d Pribando el env 2026-03-27 10:50:03 +01:00
9d63d23754 Mejor gestion de errores para los order 2026-03-26 12:21:28 +01:00
ef8222ae30 Tipos y plantill 2026-03-24 13:25:03 +01:00
19 changed files with 387 additions and 38 deletions

View File

@@ -0,0 +1 @@
PORT=3001

View File

@@ -0,0 +1,18 @@
import { Request, Response } from "express"
export class AlarmsController {
constructor() {
// Alarm usecases
}
public recibe() {
return (req: Request, res: Response) => {
const body = req.body()
const header = req.headers
}
}
}

View File

@@ -0,0 +1,14 @@
import { PgClient } from "sim-shared/infrastructure/PgClient.js";
export class AlarmUsecases {
constructor(
private alarmRepository: any
) {
}
public saveAlarm() {
}
}

View File

@@ -0,0 +1,23 @@
import { loadEnvFile } from "node:process";
loadEnvFile("../../.env")
export const env = {
PORT: Number(process.env.PORT),
ENVIRONMENT: process.env.ENVIORMENT,
POSTGRES_USER: process.env.POSTGRES_USER,
POSTGRES_PASSWORD: process.env.POSTGRES_PASSWORD,
POSTGRES_PORT: process.env.POSTGRES_PORT,
POSTGRES_HOST: process.env.POSTGRES_HOST,
POSTGRES_DATABASE: process.env.POSTGRES_DATABASE,
RABBITMQ_HOST: String(process.env.RABBITMQ_HOST ?? "localhost"),
RABBITMQ_USER: String(process.env.RABBITMQ_USER ?? "test"),
RABBITMQ_PASSWORD: String(process.env.RABBITMQ_PASSWORD ?? "test"),
RABBITMQ_EXCHANGE: String(process.env.RABBITMQ_EXCHANGE ?? "/"),
RABBITMQ_PORT: parseInt(process.env.RABBITMQ_PORT ?? "5672"),
RABBITMQ_MODULENAME: process.env.MODULENAME,
RABBITMQ_TTL: process.env.RABBITMQ_TTL,
RABBITMQ_SECURE: process.env.RABBITMQ_SECURE,
RABBITMQ_RETRY_INTERVAL: process.env.RABBITMQ_INTERVAL,
RABBITMQ_VHOST: String(process.env.RABBITMQ_VHOST),
};

View File

@@ -0,0 +1,18 @@
import { Pool } from 'pg';
import { PgClient } from 'sim-shared/infrastructure/PgClient.js'
import { env } from './env/index.js';
// Configuracion de la conexion a la BDD, deberia ser la
// Misma para todos los servicios pero hasta que se unifique todo
// se hace una por servicio.
export const pgPool = new Pool({
user: env.POSTGRES_USER,
host: env.POSTGRES_HOST,
database: env.POSTGRES_DATABASE,
password: env.POSTGRES_PASSWORD,
port: Number(env.POSTGRES_PORT) || 5432,
});
export const postgresClient = new PgClient({
pool: pgPool
})

View File

@@ -0,0 +1,62 @@
export type ObjeniousAlarmType =
"PLMN_CHANGE" |
"COUNTRY_CHANGE" |
"OVER_CONSUMPTION_VOLUME" |
"UNDER_CONSUMPTION_VOLUME" |
"IMEI_CHANGE" |
"OVER_CONSUMPTION_VOLUME" |
string
export type ObjeniousAlarm = {
alarmId: Number,
emissionDate: Date //"07/02/2019 14:44:00"
alarmType: ObjeniousAlarmType,
lineInfos: {
msisdn: string,
iccid: string
},
change?: ObjeniousChangeTypes,
usage?: ObjeniousUsageTypes
}
export type ObjeniousChangeTypes = ChangePayload | ImeiChangePayload;
export type ObjeniousUsageTypes = UsageAlarmPayload | OverConsumptionPayload;
/**
* PLMN_CHANGE
* COUNTRY_CHANGE
* STATUS_CHANGE
* */
export type ChangePayload = {
previous: string,
current: string
}
/**
* IMEI_CHANGE
*/
export type ImeiChangePayload = {
previous: string,//"42947662490276",
current: string, //"74845559130697",
previousManufacturer: string, //"u-blox AG",
currentManufacturer: string, //"SierraWireless"
}
/**
* OVER_CONSUMPTION_VOLUME
* UNDER_CONSUMPTION_VOLUME
*/
export type UsageAlarmPayload = {
type: string,
threshold: string,
trigerValue: string
}
/**
* OVER_CONSUMPTION_VOLUME
*/
export type OverConsumptionPayload = {
type: string, //"data-sms-smsIN-voiceOUT",
threshold: string, // "500-200-100-10",
triggerValue: string, // "600-300-101-15"
}

View File

@@ -0,0 +1,29 @@
import express from "express"
import cors from 'cors';
import { env } from "#config/env/index.js"
import { AlarmsController } from "aplication/Alarms.controller";
const PORT = env.PORT
const HOSTNAME = "0.0.0.0"
const app = express()
// Instancias
const alarmsController = new AlarmsController();
// Middleware
app.use(cors());
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use("/fr/alarms", alarmsController.recibe())
app.get("/health", (req, res) => {
res.status(200).json({ status: "ok" })
})
app.listen(PORT, HOSTNAME, () => {
console.log("[o] Servidor iniciado en el puerto %d", PORT)
})
export default {}

View File

@@ -0,0 +1,10 @@
import { PgClient } from "sim-shared/infrastructure/PgClient.js";
export class AlarmsRepository {
constructor(private pgClient: PgClient) {
}
public createAlarm() {
}
}

View File

@@ -0,0 +1,76 @@
{
"name": "sim-alarmas-objenious",
"version": "1.0.0",
"description": "Recibe las alarmas de los webhook de objenious",
"main": "index.ts",
"scripts": {
"test": "node --import tsx --test ./**/*.test.ts",
"build": "tsc --build && tsc-alias -p tsconfig.json && cp package.json ../../dist/packages/sim-alarmas-objenious/",
"dev": "tsx watch index.ts",
"start": "node ../../dist/packages/sim-alarmas-objenious/index.js"
},
"author": "",
"license": "ISC",
"packageManager": "yarn@4.12.0",
"imports": {
"#config/*.js": {
"types": "./config/*.ts",
"default": "./config/*.js"
},
"#config/*": {
"types": "./config/*.ts",
"default": "./config/*.js"
},
"#adapters/*.js": {
"types": "./adapters/*.ts",
"default": "./adapters/*.js"
},
"#adapters/*": {
"types": "./adapters/*.ts",
"default": "./adapters/*.js"
},
"#domain/*.js": {
"types": "./domain/*.ts",
"default": "./domain/*.js"
},
"#domain/*": {
"types": "./domain/*.ts",
"default": "./domain/*.js"
},
"#ports/*.js": {
"types": "./ports/*.ts",
"default": "./ports/*.js"
},
"#ports/*": {
"types": "./ports/*.ts",
"default": "./ports/*.js"
},
"#tests/*.js": {
"types": "./__tests__/*.ts",
"default": "./__tests__/*.js"
},
"#tests/*": {
"types": "./__tests__/*.ts",
"default": "./__tests__/*.js"
}
},
"dependencies": {
"@tsconfig/node22": "*",
"amqplib": "^0.10.9",
"cors": "*",
"dotenv": "*",
"express": "*",
"typescript": "*"
},
"devDependencies": {
"@types/amqplib": "^0.10.8",
"@types/cors": "*",
"@types/express": "*",
"@types/node": "*",
"@types/supertest": "*",
"prettier": "*",
"supertest": "*",
"tsx": "*",
"vitest": "*"
}
}

View File

@@ -0,0 +1,17 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "../../dist",
"baseUrl": ".",
},
"exclude": [
"node_modules"
],
"include": [
"**/*.ts",
"src/**/*.d.ts"
],
"files": [
"index.ts"
]
}

View File

@@ -7,6 +7,6 @@ OBJ_KID=xNfbMiyL1ORXGP8lElhcv8nVaG3EJKye4Lc1YoN3I1E
OBJ_BASE_URL=https://api-getway.objenious.com/ws
# OBJ_BASE_URL=https://api-getway.objenious.com/ws/test
# NOTIFICATION_URL="https://sf-sim-activation.savefamilygps.net/send-activation-mail"
NOTIFICATION_URL="localhost"
NOTIFICATION_URL="https://sf-sim-activation.savefamilygps.net/send-activation-mail"
# NOTIFICATION_URL="localhost"
SIM_ACTIVATION_API_KEY=9e48c4ac-1ab0-4397-b3f3-6c239200dfe6

View File

@@ -31,15 +31,15 @@ export const env = {
OBJ_KID: String(process.env.OBJ_KID),
OBJ_BASE_URL: String(process.env.OBJ_BASE_URL),
NOTIFICATION_URL: String(process.env.NOTIFICATION_URL),
SIM_ACTIVATION_API_KEY: String(process.env.SIM_ACTIVATION_API_KEY)
NOTIFICATION_URL: String(process.env.NOTIFICATION_URL ?? ""),
SIM_ACTIVATION_API_KEY: String(process.env.SIM_ACTIVATION_API_KEY ?? "")
};
// assert las partes criticas
assert(env.RABBITMQ_PASSWORD != undefined)
assert(env.RABBITMQ_USER != undefined)
assert(env.SIM_ACTIVATION_API_KEY != undefined)
assert(env.NOTIFICATION_URL != undefined)
assert(env.SIM_ACTIVATION_API_KEY != "")
assert(env.NOTIFICATION_URL != "")
if (env.ENVIRONMENT == "production") {
assert(env.RABBITMQ_PASSWORD != "guest")
@@ -47,3 +47,5 @@ if (env.ENVIRONMENT == "production") {
}
console.log("[i] verificado env")

View File

@@ -10,7 +10,7 @@ import { env } from './env/index.js';
export const pgPoolIntranet = new Pool({
user: env.POSTGRES_USER,
host: env.POSTGRES_HOST,
database: "postgres",
database: "intranet",
password: env.POSTGRES_PASSWORD,
port: Number(env.POSTGRES_PORT) || 5432,
});

View File

@@ -33,7 +33,7 @@ async function startCron() {
const volcadoLineasTask = new TaskVolcadoLineas(httpClient, objeniousLineRepository)
const PERIODO_PETICIONES = 10 * 60 * 60
const PERIODO_PETICIONES = 10 * 60 * 1000
const interval = setInterval(async () => {
try {
await objTask.getPendingOperations()

View File

@@ -32,7 +32,7 @@
},
"scripts": {
"test": "node --import tsx --test ./**/*.test.ts",
"build": "tsc --build && tsc-alias -p tsconfig.json && cp package.json ../../dist/packages/sim-objenious-cron/",
"build": "tsc --build && tsc-alias -p tsconfig.json && cp .env package.json ../../dist/packages/sim-objenious-cron/",
"dev": "tsx watch index.ts",
"start": "node ../../dist/packages/sim-objenious-cron/index.js"
},

View File

@@ -123,9 +123,6 @@ export class CheckObjeniousRequests {
if (uorStatus == "finished") {
console.log(" ****> Status", uorStatus)
if (uorStatus != "finished") {
console.error("!!! Notificando estado no finished")
}
const targetIccids = originalAction.iccids
const lineData = await this.getLineData(targetIccids)
console.log("[i] lineData", lineData.content[0])
@@ -140,7 +137,7 @@ export class CheckObjeniousRequests {
})
}
if (originalAction.operation == "activation") {
if (originalAction.operation == "activate") {
this.notifyFinalization({
...originalAction,
msisdn
@@ -219,7 +216,7 @@ export class CheckObjeniousRequests {
const PATH = "/actions/requests/"
const operationsList = structuredClone(requestList)
// TODO: El for es gigantesco hay que simplificar partes
for (const request of operationsList) {
if (request.id == undefined) continue;
@@ -232,13 +229,50 @@ export class CheckObjeniousRequests {
try {
res = await req
} catch (e) {
console.error("Error comprobando el estado de ", request, e)
//todo actualizar el estado para incluir el error
console.error("[x] Error comprobando el estado de ", request, e)
continue;
}
// 2. Casos de error o id no generada
if (res.data.massActionIds.length == 0) {
// Si no hay es que *puede* que haya un problema o no se ha generado todavia
const reports = res.data.actionRequestReports
// Se entiende que no hay report ni id = está a la espera
if (reports.length == 0) continue;
// ! Hay minimo un report -> se considera error y se para
const updateData: ObjeniousOperationChange = {
operation_id: request.id,
new_status: "error",
error: JSON.stringify(reports[0].actionRequestReportDataDTOs)
}
const updateRes = await this.operationsRepository.updateOperation(updateData)
if (updateRes.error != undefined) {
console.error("[x] Error actualizando el estado de la operacion", updateData.error)
}
if (request.correlation_id != undefined) {
this.orderRepository.errorOrder({
correlation_id: request.correlation_id,
status: "failed",
error: "MassId no obtenida",
reason: "MassId no obtenida",
stackTrace: JSON.stringify(reports[0].actionRequestReportDataDTOs)
}).then(e => {
if (e.error != undefined) {
console.error("[x] Error actualizando el estado del Order con correlation_id: ", request.correlation_id)
console.error(e.error)
}
}).catch(e => {
console.error("[x] Error actualizando el estado del Order con correlation_id: ", request.correlation_id)
})
}
continue;
}
// 2. Modificacion del massId si ha habido un cambio
const massActionId = res.data.massActionIds[0]
// 3. Modificacion del massId si ha habido un cambio
try {
if (res.status == 200 && res.data != undefined && massActionId != undefined) {
const updateData: ObjeniousOperationChange = {
@@ -252,7 +286,7 @@ export class CheckObjeniousRequests {
request.mass_action_id = String(massActionId)
}
} catch (e) {
console.log("Error actualizando el estado de ", request)
console.log("[x] Error actualizando el estado de ", request)
continue;
}
}
@@ -266,6 +300,8 @@ export class CheckObjeniousRequests {
* al servicio que manda los mails
*/
private async notifyFinalization(operation: ObjeniousOperation & { msisdn: string }) {
console.log("[i] Enviando activacion a", env.NOTIFICATION_URL)
console.log("[i] Operation", operation)
const req = axios.post(env.NOTIFICATION_URL, {
...operation,
iccids: [operation.iccids]
@@ -274,7 +310,17 @@ export class CheckObjeniousRequests {
"x-apikey-sim-activation": env.SIM_ACTIVATION_API_KEY
}
})
await req
try {
const res = await req
if (res.status != 200) {
console.error("[x] Error enviando el mail de confirmacion para ", operation, " status ", res.status, res.statusText)
}
} catch (e) {
console.error("[x] Error enviando el mail de confirmacion para ", operation)
console.error(e)
}
}
}

View File

@@ -62,11 +62,14 @@ export type CreateOrderDTO = Pick<
'correlation_id' | 'exchange' | 'routing_key' | 'order_type' | 'payload' | 'webhook_host' | 'webhook_endpoint'
>;
export type UpdateOrderDTO =
type IdOrCorrelationID =
(
{ id: number, correlation_id?: never } |
{ id?: never, correlation_id: string }
)
export type UpdateOrderDTO =
IdOrCorrelationID
&
{
new_status: OrderStatus,
@@ -74,12 +77,20 @@ export type UpdateOrderDTO =
}
export type FinishOrderDTO =
(
{ id: number, correlation_id?: never } |
{ id?: never, correlation_id: string }
)
IdOrCorrelationID
&
{
reason?: string
}
export type ErrorOrderDTO =
IdOrCorrelationID
&
{
status: "failed" | "dlx",
reason: string,
error?: string,
stackTrace?: string
}

View File

@@ -12,7 +12,7 @@ export type ObjeniousOperation = {
id?: number;
/** Uuid del mensaje asociado a la operacion */
correlation_id?: string;
operation: string;
operation: "activate" | string; // TODO: completar y actualizar
retry_count?: number;
max_retry?: number;
max_date_retry?: string | null;
@@ -46,10 +46,34 @@ export namespace Objenious {
created: string,
status: "NEW" | "RUNNING" | "OK" | "KO" | "REPLAYED" | "CANCELLED" | "CLOSED" | "DISABLED",
statusDate: string,
actionType: "PREACTIVATION_AND_ACTIVATION" | string, // todo: añadir el resto
massActionIds: number[]
actionType: ActionType
massActionIds: number[],
actionRequestReports:
{
requestId: string,
actionRequestReportDataDTOs: [
{
data: string,
newData: string | null,
iccid: string,
dataStatus: DataStatus
}
]
}[],
}
export type DataStatus = "DATA_INVALID_FORMAT" | "DATA_NOT_FOUND" | "DATA_NOT_ACTIVATED" | "SERVICE_DATA_NOT_ACTIVATED" |
"DATA_WRONG_STATUS" | "DATA_NOT_AUTHORIZED" | "DATA_CUSTOMER_ACCOUNT_NOT_AUTHORIZED" | "DATA_AMBIGUOUS" |
"NEW_DATA_INVALID_FORMAT" | "NEW_DATA_ALREADY_EXISTS" | "DUPLICATE_DATA" | "DATA_TERMINATION_VALIDATED" |
"DATA_TERMINATION_SECURISED" | "MAX_ALARM_INSTANCE" | "MAX_ALARM_INSTANCE_TO_CATCH_UP" |
"ACTIVATED_LINE_CANNOT_BE_TRANSFERED" | "ESIM_WRONG_STEP" | "ESIM_WRONG_PAIRED_VALUE" |
"ESIM_WRONG_DOWNLOAD_STATE" | "ESIM_WRONG_STATUS" | "ESIM_WRONG_FAMILY" | "ESIM_WRONG_CATEGORY" |
"ENTITY_STATUS_NOT_AUTHORIZED" | "LONG_LIFE_NOT_ALLOWED" | "RCARD_NOT_COMPATIBLE" | "APN_NOT_FOUND" |
"APN_OR_DNN_NOT_FOUND" | "APN_CONFIGURATION_NOT_FOUND" | "APN_CONFIGURATION_INVALID_PARAMETER_FILE" |
"IP_NOT_AVAILABLE" | "RADIUS_FIELD_LENGTH_NOT_ALLOWED" | "RADIUS_LOGIN_OR_PASSWORD_NOT_FOUND" | "RADIUS_PASSWORD_NOT_ALLOWED" |
"RADIUS_LOGIN_NOT_ALLOWED" | "NETWORK_NOT_ACTIVATED" | "CHANGE_CUSTOMER_ACCOUNT_NOT_AllOWED" | "CHANGE_OFFER_NOT_ALLOWED" |
"SIM_NOT_EUICC" | "OFFER_NOT_WSF_PALIER_FLOTTE_FR"
export type ActionType = "PREACTIVATION" | "PREACTIVATION_ACTIVATION" | "ACTIVATION" |
"STATUS_CHANGE" | "ICCID_CHANGE" | "EUICC_NOTIFICATION"
| "EUICC_AUDIT" | "MSISDN_CHANGE" | "ALARM_SETTING"

View File

@@ -2,7 +2,7 @@
* TODO: Usar
*/
import { PoolClient, QueryResult, QueryResultRow } from "pg";
import { CreateOrderDTO, FinishOrderDTO, OrderTracking, UpdateOrderDTO } from "../domain/Order.js";
import { CreateOrderDTO, ErrorOrderDTO, FinishOrderDTO, OrderTracking, UpdateOrderDTO } from "../domain/Order.js";
import { Result } from "../domain/Result.js";
import { PgClient } from "./PgClient.js";
import assert from "node:assert";
@@ -353,22 +353,19 @@ export class OrderRepository {
}
// TODO: tema de poder filtrar por correlation_id
public async errorOrder(args: {
id: number,
status: "failed" | "dlx",
reason: string,
error?: string,
stackTrace?: string
}) {
public async errorOrder(args: ErrorOrderDTO): Promise<Result<string, OrderTracking<any>>> {
const client = await this.pgClient.connect();
await client.query('BEGIN');
const idType = ('id' in args) ? "id" : "correlation_id"
const idValue = (args.id != undefined) ? args.id : args.correlation_id
// 1. Se consulta la order de base
const qCurrentOrder = `
SELECT * FROM order_tracking
WHERE id = $1
WHERE ${idType} = $1
`
const vCurrentOrder = [args.id]
const vCurrentOrder = [idValue]
const currentOrderResult = await this.getFirst(client.query<OrderTracking<any>>(qCurrentOrder, vCurrentOrder))
@@ -378,6 +375,7 @@ export class OrderRepository {
return currentOrderResult
}
const id = currentOrderResult.data.id // Saco el id para evitar busacr por correlation_id que es mas lento
const currentOrder = currentOrderResult.data!
// 3. Si todo ok se actualiza el order
@@ -395,7 +393,7 @@ export class OrderRepository {
WHERE id = $1
RETURNING id, status, update_date;
`
const vOrderTracking = [args.id, args.status, args.error, args.stackTrace]
const vOrderTracking = [id, args.status, args.error, args.stackTrace]
const updatedOrderResult = await this.getFirst(
client.query<{ id: number, status: string, update_date: string }>(uOrderTracking, vOrderTracking)
)