Endpoints NOS

This commit is contained in:
2026-04-21 10:11:21 +02:00
parent 32990b4dcd
commit e62c49ce91
13 changed files with 292 additions and 10 deletions

View 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
View 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

View File

@@ -1,5 +1,7 @@
name: local
color: "#2E8A54"
variables:
- name: baseulr
value: http://localhost:3000
- name: baseurl
value: http://localhost:3001
- secret: true
name: token

View File

@@ -1,4 +1,6 @@
NOS_BASE_URL=localhost
APP_PORT=3001
APP_HOST="0.0.0.0"
ENVIORMENT=development

View File

@@ -1,9 +1,10 @@
import { ConsumeMessage } from "amqplib";
import { Request, Response } from "express"
import { SimNosUsecases } from "./SimNOS.usecases";
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 { error } from "node:console";
import { iccidValidator } from "./httpValidators";
export class SimNosController {
@@ -107,4 +108,73 @@ export class SimNosController {
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(402).json(validateBody)
return;
}
const iccid: string | string[] = body.iccid
console.log("ICCID", iccid)
if (Array.isArray(iccid)) {
const usecaseRes = this.uscases.selectMany({ iccid })
} else {
const usecaseRes = await this.uscases.selectOne({ iccid })
console.log(usecaseRes)
if (usecaseRes.error != undefined) {
res.status(500).json(usecaseRes)
} else {
res.send(usecaseRes.data)
}
return;
}
res.status(200).json(validateBody)
}
}
public selectPageREST() {
return async (req: Request, res: Response) => {
const { query } = req
const body = {
iccid: query.iccid as string
}
console.log("Evento page", body)
/*
const validateBody = iccidValidator.validate(body);
if (validateBody.error != undefined) {
res.status(402).json(validateBody)
return;
}
*/
const iccid: string | string[] = body.iccid
console.log("ICCID", iccid)
const usecaseRes = await this.uscases.selectPage({ iccid })
if (usecaseRes.error != undefined) {
res.status(500).json(usecaseRes)
} else {
res.status(200).send(usecaseRes.data)
}
res.status(200).json({ ok: "true" })
}
}
}

View File

@@ -56,4 +56,23 @@ export class SimNosUsecases {
throw new Error("No hay termination para NOS")
}
public async selectOne(args: {
iccid: string
}) {
const res = await this.nosRepository.getLineInfo(args.iccid)
return res
}
public async selectPage(args: {
}) {
const res = await this.nosRepository.getLinePage(args)
return res
}
public selectMany(args: {
iccid: string[]
}) {
return {}
}
}

View 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,
]
)

View File

@@ -30,6 +30,9 @@ export const env = {
RABBITMQ_RETRY_INTERVAL: process.env.RABBITMQ_INTERVAL,
RABBITMQ_VHOST: String(process.env.RABBITMQ_VHOST),
APP_PORT: Number(process.env.APP_PORT),
APP_HOST: String(process.env.APP_HOST),
// ESPECIFICO NOS
NOS_BASE_URL: String(process.env.NOS_BASE_URL),
NOS_ACCESS_TOKEN: String(process.env.NOS_ACCESS_TOKEN)

View File

@@ -63,6 +63,10 @@ export namespace NosApi {
export type LineDataResponseError = ErrorResponse
export type LineDataResponse = LineDataResponseOK | LineDataResponseError
export type PageDataResponseOk = OkResponse<LineData[]>
export type PageDataResponseError = OkResponse<LineData[]>
export type PageResponse = PageDataResponseOk | PageDataResponseError
export type LineData = {
physicalId: string
subscriberId: string

View File

@@ -1,13 +1,17 @@
import express from "express"
import cors from 'cors';
import { startRMQClient } from "#config/eventBus.config.js"
import { SimNosRouter } from "aplication/SimNOS.router.js"
import { SimNosController } from "./aplication/SimNOS.controller.js"
import { SimNosUsecases } from "aplication/SimNOS.usecases.js"
import { NosHttpClient } from "infrastructure/NosHttpClient.js"
import { env } from "#config/env/env.js"
import { NosRepository } from "infrastructure/NosRepository.js"
const RMQ_QUEUE = "sim.nos"
const NOS_BASE_URL = env.NOS_BASE_URL
const PORT = env.APP_PORT
const HOSTNAME = env.APP_HOST
async function startWorker() {
// Instancia de dependencias
@@ -17,21 +21,47 @@ async function startWorker() {
NOS_BASE_URL
)
const simUsecases = new SimNosUsecases(
const nosRepository = new NosRepository(
nosHttpClient
)
const simUsecases = new SimNosUsecases(
nosHttpClient,
nosRepository
)
const simController = new SimNosController(
simUsecases
simUsecases,
rmqClient
)
const simRouter = new SimNosRouter(
simController,
rmqClient
)
// RMQ
rmqClient.consume(RMQ_QUEUE, simRouter.route)
.then(() => console.log("Cliente rmq creado con exito"))
.catch(e => console.error("Error conectando con RABBITMQ", e))
// Express
const app = express()
app.use(cors());
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.get("/select", simController.selectREST())
app.get("/selectPage", simController.selectPageREST())
app.listen(PORT, HOSTNAME, (e) => {
if (e == undefined) {
console.log("[o] Servidor iniciado en el puerto %d", PORT)
} else {
console.error("Error express ", e)
}
})
}
startWorker()

View File

@@ -27,11 +27,11 @@ export class NosRepository {
if (axios.isAxiosError(e)) {
const error = e as AxiosError
return {
error: error.code + " : " + JSON.stringify(error.response)
error: error.code + " : " + String(error.response?.statusText)
}
} else {
return {
error: JSON.stringify(e)
error: String(e)
}
}
}
@@ -39,6 +39,59 @@ export class NosRepository {
public async getLineInfo(iccid: string): Promise<Result<string, NosApi.LineData>> {
const PATH = "/subscribers/" + iccid
console.log("PAth", PATH)
const lineRequest = this.httpClient.get<NosApi.LineDataResponseOK>(PATH)
const lineResponse = await this.manageNosRequest<string, NosApi.LineDataResponseOK>(lineRequest)
if (lineResponse.error != undefined) {
return lineResponse
} else {
return {
data: lineResponse.data.content
}
}
}
public async getLinePage(args: {
limit?: number,
offset?: number,
filter?: string,
orderBy?: string
}): Promise<Result<string, any>> {
const PATH = "/subscribers"
const LIMIT = 100
const options = {
limit: LIMIT,
offset: 0,
filter: args.filter,
orderBy: args.orderBy
}
const pageRequest = this.httpClient.get(PATH, {
params: options
})
const pageResponse = await this.manageNosRequest<string, any>(pageRequest)
if (pageResponse.error != undefined) {
return pageResponse
} else {
return {
data: pageResponse.data.content
}
}
}
public async getLinesInfo(iccid: string[]) /*Promise<Result<string, NosApi.LineData>>*/ {
throw new Error("NOS no permite buscar iccid en bulk, se puede hacer un apaño pero está en proceso")
const PATH = "/subscribers"
const LIMIT = 100
const steps = Math.ceil(iccid.length / LIMIT)
const options = {
limit: LIMIT,
offset: 0,
}
const req = this.httpClient.post<NosApi.LineDataResponseOK>(PATH)
const resp = await this.manageNosRequest<string, NosApi.LineDataResponseOK>(req)
@@ -47,6 +100,7 @@ export class NosRepository {
return resp
} else {
return {
//@ts-expect-error
data: resp.data.content
}
}

View File

@@ -0,0 +1,11 @@
# NOS
## Particularidades de las operaciones de NOS
- Documentación de la API: [DOC](https://pelion-help.iot-x.com/nos/en/Content/API/APIReference/API%20Reference.htm?tocpath=_____7)
- No se necesita la pre-activación de las SIM.
- La suspensión y reactivación se llama "bar" y "unbar".
- El token de Authentication dura exactamente 1 año, solo se puede refrescar
desde la web.
- En la documentación la URL de la API es <https://nos-api.iot-x.com> pero la
de producción es <https://nosconnectcenter-api.iot-x.com>.

View File

@@ -19,7 +19,6 @@ rabbitmqEventBus.connect()
console.error("[!] El cliente RMQ no se ha podido iniciar", e)
})
// Middleware
app.use(cors());
@@ -39,4 +38,5 @@ app.get("/health", (req, res) => {
app.listen(PORT, HOSTNAME, () => {
console.log("[o] Servidor iniciado en el puerto %d", PORT)
})
export default {}