diff --git a/packages/sim-consumidor-alai/.env b/packages/sim-consumidor-alai/.env index 1a67319..9620081 100644 --- a/packages/sim-consumidor-alai/.env +++ b/packages/sim-consumidor-alai/.env @@ -1,6 +1,14 @@ -APP_PORT=3002 -APP_HOST="0.0.0.0" +ALAI_PORT=3002 +ALAI_HOST="0.0.0.0" ENVIORMENT=development +ALAI_API_URL=https://wsaccess.alaisecure.com/bssrest + ALAI_CERTIFICATES_DIR=./certificates/ +ALAI_USERNAME=palomaibanez +ALAI_PASSWORD=palomaibanez1234 +ALAI_BRANDID=savefamily + +ALAI_PACKAGE=Tarifa_250MB_100MIN_5SMS +ALAI_SUBSCRIBER_ID="16216" diff --git a/packages/sim-consumidor-alai/aplication/AlaiTokenManager.ts b/packages/sim-consumidor-alai/aplication/AlaiTokenManager.ts new file mode 100644 index 0000000..4653c92 --- /dev/null +++ b/packages/sim-consumidor-alai/aplication/AlaiTokenManager.ts @@ -0,0 +1,40 @@ +import { AlaiRepository } from "#infrastructure/AlaiRepository.js"; +import { JWTToken } from "sim-shared/domain/JWT.js"; +import { JWTProvider } from "sim-shared/infrastructure/HTTPClient.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(); + + if (res.error != undefined) { + console.error("Error obteniendo el token de ALAI", res.error) + } + } + + public tryRefreshToken(): Promise> { + // En Alai no existe el concepto de refresh, se solicita otro token nuevo + return this.getAccessToken() + }; + + public async getAccessToken(): Promise> { + // 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 + }; +} diff --git a/packages/sim-consumidor-alai/config/env/env.ts b/packages/sim-consumidor-alai/config/env/env.ts index 80f0352..bf6019f 100644 --- a/packages/sim-consumidor-alai/config/env/env.ts +++ b/packages/sim-consumidor-alai/config/env/env.ts @@ -1,16 +1,18 @@ import { loadEnvFile } from "node:process"; import path from "node:path"; +import assert from "node:assert"; + +try { + loadEnvFile(path.join("../../.env")) // Global +} catch (e) { + console.error("Error cargando el .env desde ../../.env") +} try { loadEnvFile(path.join("./.env")) // base } catch (e) { console.error("Error cargando el .env desde ./.env") } -try { - loadEnvFile(path.join("../../.env")) // Global -} catch (e) { - console.error("Error cargando el .env desde ../../.env") -} export const env = { ENVIRONMENT: process.env.ENVIORMENT, @@ -30,11 +32,23 @@ 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), + ALAI_PORT: Number(process.env.APP_PORT), + ALAI_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) + // ESPECIFICO ALAI + ALAI_API_URL: process.env.ALAI_API_URL, + ALAI_CERTIFICATES_DIR: process.env.ALAI_CERTIFICATES_DIR, + ALAI_USERNAME: process.env.ALAI_USERNAME, + ALAI_PASSWORD: process.env.ALAI_PASSWORD, + ALAI_BRANDID: process.env.ALAI_BRANDID, + + ALAI_PACKAGE: process.env.ALAI_PACKAGE, + ALAI_SUBSCRIBER_ID: process.env.ALAI_SUBSCRIBER_ID }; +assert.ok(env.ALAI_SUBSCRIBER_ID != undefined, "ALAI_SUBSCRIBER_ID no definido") +assert.ok(env.ALAI_PACKAGE != undefined, "ALAI_PACKAGE no definido") +assert.ok(env.ALAI_USERNAME != undefined, "ALAI_USERNAME no definido") +assert.ok(env.ALAI_PASSWORD != undefined, "ALAI_PASSWORD no definido") +assert.ok(env.ALAI_BRANDID != undefined, "ALAI_BRANDID no definido") +assert.ok(env.ALAI_API_URL != undefined, "ALAI_API_URL no definido") diff --git a/packages/sim-consumidor-alai/config/httpClient.config.ts b/packages/sim-consumidor-alai/config/httpClient.config.ts new file mode 100644 index 0000000..02b28d3 --- /dev/null +++ b/packages/sim-consumidor-alai/config/httpClient.config.ts @@ -0,0 +1,11 @@ +import { HttpClient } from "sim-shared/infrastructure/HTTPClient.js" +import { AlaiTokenManager } from "#aplication/AlaiTokenManager.js" +import { env } from "#config/env/env.js"; + +const tokenManager = new AlaiTokenManager() + +export const alaiHttp = new HttpClient({ + baseURL: env.ALAI_API_URL as string, + headers: {}, + jwtManager: tokenManager +}) diff --git a/packages/sim-consumidor-alai/domain/AlaiAPI.ts b/packages/sim-consumidor-alai/domain/AlaiAPI.ts index 8b13789..680b473 100644 --- a/packages/sim-consumidor-alai/domain/AlaiAPI.ts +++ b/packages/sim-consumidor-alai/domain/AlaiAPI.ts @@ -1 +1,106 @@ +export namespace AlaiAPI { + export type LoginResponseDTO = { + accessToken: string, + tokenType: string, + refreshToken: string, + expiresIn: string // isodate + } + + /** + Hardcodeado en: + sf-sim-connections/context/infrastructure/api/alaiService.js + const data = { + type: "RETAIL", + salesChannel: "OWN_CALLCENTER", + status: "CONFIRMED", + packages: [{ id: "Tarifa_250MB_100MIN_5SMS" }], + subscriber: { id: "16216" } + }; + */ + export type CreateOrderDTO = { + type: "RETAIL" | string, + salesChannel: "OWN_CALLCENTER" | string, + status: "CONFIRMED" | string, + packages: { id: "Tarifa_250MB_100MIN_5SMS" | string }[], + subscriber: { + id: string + } + } + + type OrderPackage = { + id: string, + name: string, + packagePrices: unknown, + packageInstance: { + id: string, + name: string, + links: Link[] + } + } + + type Link = { + rel: string, + href: string, + hreflang: string, + media: string, + title: string, + type: string, + deprecation: string, + profile: string, + name: string + } + + export type CreateOrderResponseDTO = { + id: string, + name: string, + domain: string, + orderCode: string, + externalID: string, + type: string, + status: string, + saleStatus: string, + distributionStatus: string, + description: string, + salesChannel: string, + salesPerson: string, + deliveryType: string, + distributionInfo: { + providerID: string, + providerReference: string, + providerTracking: string, + cashOnDelivery: boolean, + prepaidShipping: boolean, + description: string, + events: { + status: string, + observations: string, + date: string | Date, + expectedDeliveryDate: string | Date, + completedDeliveryDate: string | Date, + }[] + }, + packages: OrderPackage[], + subscription: { + id: string, + name: string, + links: Link[] + } + subscriber: { + id: string, + name: string, + links: Link[] + } + brand: { + id: string, + name: string, + links: Link[] + } + pos: { + id: string, + name: string, + links: Link[] + } + links: Link[] + } +} diff --git a/packages/sim-consumidor-alai/infrastructure/AlaiHttpClient.ts b/packages/sim-consumidor-alai/infrastructure/AlaiHttpClient.ts deleted file mode 100644 index 5e12de7..0000000 --- a/packages/sim-consumidor-alai/infrastructure/AlaiHttpClient.ts +++ /dev/null @@ -1,41 +0,0 @@ -import axios, { AxiosInstance } from "axios"; -import { env } from "#config/env/env.js" - -export class AlaiHttpClient { - public client: AxiosInstance; - - constructor( - private baseURL: string, - //private jwtManager: JWTProvider - ) { - this.client = axios.create({ - baseURL: baseURL - }) - - // Interceptor para los headers fijos - this.client.interceptors.request.use( - async (config) => { - // Configuracion especifica de NOS (El token simepre es el mismo?) - const token = env.NOS_ACCESS_TOKEN; - config.headers.Authorization = `Bearer ${token}` - config.headers.set("content-type", "application/json") - return config - }, - (error) => Promise.reject(error) - ) - } - - get post() { - return this.client.post - } - - get patch() { - return this.client.patch - } - - get get() { - return this.client.get - } - - -} diff --git a/packages/sim-consumidor-alai/infrastructure/AlaiRepository.ts b/packages/sim-consumidor-alai/infrastructure/AlaiRepository.ts index 9f55269..f1f63cd 100644 --- a/packages/sim-consumidor-alai/infrastructure/AlaiRepository.ts +++ b/packages/sim-consumidor-alai/infrastructure/AlaiRepository.ts @@ -1,23 +1,16 @@ -import { Result } from "sim-shared/domain/Result.js"; -import { NosHttpClient } from "./AlaiHttpClient.js"; -import { NosApi } from "#domain/AlaiAPI.js"; +import { AlaiAPI } from "#domain/AlaiAPI.js"; import axios, { AxiosError, AxiosResponse } from "axios"; +import { AlaiHttpClient } from "./AlaiHttpClient.js"; +import { Result } from "sim-shared/domain/Result.js"; +import { env } from "#config/env/env.js"; -export class NosRepository { +export class AlaiRepository { constructor( - private httpClient: NosHttpClient + private httpClient: AlaiHttpClient ) { } - /** - * E => Tipo de error - * T => Tipo de dato para cod 200 - * - * TODO: - * - Mejor gestion de los errores - * - E no se aplica todavia por no hacer la transformacion del error - */ - private async manageNosRequest(promise: Promise>): Promise> { + private async manageRequest(promise: Promise>): Promise> { try { const res = await promise return { @@ -37,141 +30,66 @@ export class NosRepository { } } - public async getLineInfo(iccid: string): Promise> { - const PATH = "/subscribers/" + iccid - console.log("PAth", PATH) - const lineRequest = this.httpClient.get(PATH) - const lineResponse = await this.manageNosRequest(lineRequest) - - if (lineResponse.error != undefined) { - return lineResponse - } else { - return { - data: lineResponse.data.content - } - } - } - - /** - * El metodo de NOS de paginar las lineas - * maximo por pagina 100, default 25 - * no devuelve el offset ni el numero de elementos restantes - * hay que llevar la cuenta - */ - public async getLinePage(args: { - limit?: number, - offset?: number, - filter?: string, - orderBy?: string - }): Promise> { - const PATH = "/subscribers" - - const LIMIT = 100 - const options = { - limit: args.limit ?? LIMIT, - offset: args.offset ?? 0, - filter: args.filter, - orderBy: args.orderBy - } - - const pageRequest = this.httpClient.get(PATH, { - params: options - }) - - const pageResponse = await this.manageNosRequest(pageRequest) - if (pageResponse.error != undefined) { - return pageResponse - } else { - return { - data: pageResponse.data.content - } - } - } - - public async getLinesInfo(iccid: string[]) /*Promise>*/ { - 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(PATH) - const resp = await this.manageNosRequest(req) - - if (resp.error != undefined) { - return resp - } else { - return { - //@ts-expect-error - data: resp.data.content - } - } - } - - public async activateSim(iccid: string): Promise> { - const PATH = '/provisioning' - const PRODUCT_ID = 1330 // No se que es, preguntar a Ivan + public static async login(): Promise> { + const alaiUrl = env.ALAI_API_URL + const endpoint = "/v1/auth/login" + const fullUrl = alaiUrl + endpoint const data = { - productSetId: PRODUCT_ID + "username": env.ALAI_USERNAME, + "password": env.ALAI_PASSWORD, + "brandID": env.ALAI_BRANDID } - const req = this.httpClient.post(PATH, data) - const resp = await this.manageNosRequest(req) - - if (resp.error != undefined) { - return resp - } else { + try { + const loginRes = await axios.post(fullUrl, data) return { - data: resp.data.content + data: loginRes.data + } + } catch (e) { + if (axios.isAxiosError(e)) { + const error = e as AxiosError + return { + error: error.code + " : " + String(error.response?.statusText) + } + } else { + return { + error: String(e) + } } } } /** - * "A bar is a service provisioning action that results in a subscriber being blocked from accessing an operator's network. The bar remains in place until the operator is sent an unbar request." - * Se entiende que un "bar" es una suspension temporal + * Los orders son la unidad que envuelve las peticiones para garantizar ideponencia. */ - public async bar(iccid: string) { - const PATH = `/subscribers/${iccid}/products` - const data = { - product: "BAR DN TOTAL", - action: "enable" - } - - const req = this.httpClient.patch(PATH, data) - const resp = await this.manageNosRequest(req) - - if (resp.error != undefined) { - return resp - } else { - return { - data: resp.data.content - } + public async createOrder() { + // POST + const endpoint = "/v1/order" + const data: AlaiAPI.CreateOrderDTO = { + type: "RETAIL", + salesChannel: "OWN_CALLCENTER", + status: "CONFIRMED", + packages: [{ id: env.ALAI_PACKAGE as string }], + subscriber: { id: env.ALAI_SUBSCRIBER_ID as string } } + const promReq = this.httpClient.post(endpoint, data) + const res = await this.manageRequest(promReq) + return res } - public async unbar(iccid: string) { - const PATH = `/subscribers/${iccid}/products` - const data = { - product: "BAR DN TOTAL", - action: "disable" - } - - const req = this.httpClient.patch(PATH, data) - const resp = await this.manageNosRequest(req) - - if (resp.error != undefined) { - return resp - } else { - return { - data: resp.data.content - } - } - + /* + * Ver estado del order + */ + public async getOrder(orderId: string) { + const ENDPOINT = `/v1/order/${orderId}` } + /** + * + */ + public async createReserve(order: string, iccid: string) { + const ENDPOINT = `/v1/sim/${iccid}/order/${order}` + + + } } diff --git a/packages/sim-consumidor-objenious/config/jwtService.config.ts b/packages/sim-consumidor-objenious/config/jwtService.config.ts index 3d9bf78..b7319ab 100644 --- a/packages/sim-consumidor-objenious/config/jwtService.config.ts +++ b/packages/sim-consumidor-objenious/config/jwtService.config.ts @@ -31,7 +31,6 @@ const DEFAULT_DATA_JWT = { iss: env.OBJ_CLIENT_ID, aud: "https://idp.docapost.io/auth/realms/GETWAY", jti: Date.now().toString(), - } function addIATHeaders(authHeaders: Object) { diff --git a/packages/sim-shared/infrastructure/HTTPClient.ts b/packages/sim-shared/infrastructure/HTTPClient.ts index 00084d1..6ed8490 100644 --- a/packages/sim-shared/infrastructure/HTTPClient.ts +++ b/packages/sim-shared/infrastructure/HTTPClient.ts @@ -19,7 +19,7 @@ export class HttpClient { constructor(args: { baseURL: string, - headers: Object, + headers: Record, jwtManager: JWTProvider<{}> // todo: asociar el tipo de token, jwtService?: IJWTService }) { @@ -37,7 +37,7 @@ export class HttpClient { // token valido de forma preventiva const token = await this.jwtManager.getAccessToken() - if (token == undefined) throw new Error("No se ha obtenido el token para la peticion") + if (token == undefined) throw new Error("No se ha obtenido el token para la petición") config.headers.Authorization = `Bearer ${this.jwtManager.authToken!.rawToken}` console.log("request completa", config.data) @@ -50,8 +50,7 @@ export class HttpClient { this.client.interceptors.response.use( (response) => { return response; - }, - async (error) => { + }, async (error) => { // TODO: Esta parte no tiene tipos, hay que asegurar el error const req = error.config console.error("[http] Error en la respuesta ", error, error.response) @@ -61,8 +60,17 @@ export class HttpClient { return Promise.reject(error) } ) - } + get get() { + return this.client.get + } + get post() { + return this.client.post + } + + get patch() { + return this.client.patch + } }