Proceso completo de solicitud de activacion
This commit is contained in:
@@ -10,12 +10,8 @@ post {
|
|||||||
auth: inherit
|
auth: inherit
|
||||||
}
|
}
|
||||||
|
|
||||||
params:query {
|
|
||||||
:
|
|
||||||
}
|
|
||||||
|
|
||||||
body:form-urlencoded {
|
body:form-urlencoded {
|
||||||
iccid: 12339912344
|
iccid: 8933201124059175967
|
||||||
}
|
}
|
||||||
|
|
||||||
settings {
|
settings {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export namespace SimEvents {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type activation = DomainEvent & {
|
export type activation = DomainEvent & {
|
||||||
key: "sim.activation",
|
key: `sim.${string}.activation`,
|
||||||
payload: {
|
payload: {
|
||||||
iccid: string
|
iccid: string
|
||||||
},
|
},
|
||||||
@@ -20,7 +20,7 @@ export namespace SimEvents {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type cancelation = DomainEvent & {
|
export type cancelation = DomainEvent & {
|
||||||
key: "sim.cancelation",
|
key: `sim.${string}.cancelation`,
|
||||||
payload: {
|
payload: {
|
||||||
iccid: string
|
iccid: string
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -30,12 +30,12 @@ export class HttpClient {
|
|||||||
async (config) => {
|
async (config) => {
|
||||||
// Idealmente estas condiciones no deberian de darse si mantenemos el
|
// Idealmente estas condiciones no deberian de darse si mantenemos el
|
||||||
// token valido de forma preventiva
|
// token valido de forma preventiva
|
||||||
const token = this.jwtManager.authToken?.rawToken
|
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 peticion")
|
||||||
|
|
||||||
config.headers.Authorization = `Bearer ${this.jwtManager.authToken!.rawToken}`
|
config.headers.Authorization = `Bearer ${this.jwtManager.authToken!.rawToken}`
|
||||||
console.log("request completa", config.headers, config.data)
|
console.log("request completa", config.data)
|
||||||
return config;
|
return config;
|
||||||
},
|
},
|
||||||
(error) => Promise.reject(error)
|
(error) => Promise.reject(error)
|
||||||
@@ -49,7 +49,7 @@ export class HttpClient {
|
|||||||
async (error) => {
|
async (error) => {
|
||||||
// TODO: Esta parte no tiene tipos, hay que asegurar el error
|
// TODO: Esta parte no tiene tipos, hay que asegurar el error
|
||||||
const req = error.config
|
const req = error.config
|
||||||
console.error("[http] Error en la respuesta ", error)
|
console.error("[http] Error en la respuesta ", error.response.data)
|
||||||
if (error.response?.status == 401) {
|
if (error.response?.status == 401) {
|
||||||
this.jwtManager.getAccessToken()
|
this.jwtManager.getAccessToken()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -122,7 +122,6 @@ export class RabbitMQEventBus implements EventBus {
|
|||||||
protected async createChannel() {
|
protected async createChannel() {
|
||||||
if (this.connection == undefined) throw new Error("[RMQ] Intentando crear un canal sin una conexion")
|
if (this.connection == undefined) throw new Error("[RMQ] Intentando crear un canal sin una conexion")
|
||||||
const channel = this.connection.createChannel({
|
const channel = this.connection.createChannel({
|
||||||
json: true,
|
|
||||||
setup: (channel: Channel) => {
|
setup: (channel: Channel) => {
|
||||||
// Exchanges comunes a todos
|
// Exchanges comunes a todos
|
||||||
channel.assertExchange("sim.exchange", "topic", { durable: true })
|
channel.assertExchange("sim.exchange", "topic", { durable: true })
|
||||||
|
|||||||
@@ -4,3 +4,4 @@ OBJ_AUTHORIZATION=XOc7FtwXD8hUX2SFVX94XSty8wkOmChkwDNF09O_aIxPubMDdFUdCDCB4zpzSI
|
|||||||
OBJ_CLI_ASSERTION=XOc7FtwXD8hUX2SFVX94XSty8wkOmChkwDNF09O_aIxPubMDdFUdCDCB4zpzSIxi8nOcTg7r_LM_nmd5qm7uLbksf_XArjI8iAyhjKz_2BAXPhmvKs4Fc9f3vv5LDfCVrPB9lP8P7rJ66_qnWs4jvhLQxSfn29m96hgXeCf8oySdIDUjN2q9Js3KAS5LL52Ri6ryvUeO1PvMhaPQMWRqoHIqTV1wPfPtiqQwcjUPmu5GeW164Kq1JLgV3KaGzfCZ9Qv9lbv30EJrukXxWuLCAhBS0kzrBXZoWvf2pb9uh3Am_93_dDxiIGQfIap9ZU_m8ZD1HPgvZOMCY6ZkxQconQ
|
OBJ_CLI_ASSERTION=XOc7FtwXD8hUX2SFVX94XSty8wkOmChkwDNF09O_aIxPubMDdFUdCDCB4zpzSIxi8nOcTg7r_LM_nmd5qm7uLbksf_XArjI8iAyhjKz_2BAXPhmvKs4Fc9f3vv5LDfCVrPB9lP8P7rJ66_qnWs4jvhLQxSfn29m96hgXeCf8oySdIDUjN2q9Js3KAS5LL52Ri6ryvUeO1PvMhaPQMWRqoHIqTV1wPfPtiqQwcjUPmu5GeW164Kq1JLgV3KaGzfCZ9Qv9lbv30EJrukXxWuLCAhBS0kzrBXZoWvf2pb9uh3Am_93_dDxiIGQfIap9ZU_m8ZD1HPgvZOMCY6ZkxQconQ
|
||||||
OBJ_CLIENT_ID=savefamily_rest_ws
|
OBJ_CLIENT_ID=savefamily_rest_ws
|
||||||
OBJ_KID=xNfbMiyL1ORXGP8lElhcv8nVaG3EJKye4Lc1YoN3I1E
|
OBJ_KID=xNfbMiyL1ORXGP8lElhcv8nVaG3EJKye4Lc1YoN3I1E
|
||||||
|
OBJ_BASE_URL=https://api-getway.objenious.com/ws
|
||||||
|
|||||||
@@ -118,11 +118,7 @@ export class JWTService {
|
|||||||
return token
|
return token
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getAccessToken() {
|
public async getNewTokens() {
|
||||||
if (this.authToken != undefined && !this.authToken.isExpired()) {
|
|
||||||
console.warn("Se está intentado conseguir un token sin expirar el anterior")
|
|
||||||
}
|
|
||||||
|
|
||||||
const bodyWithtoken = {
|
const bodyWithtoken = {
|
||||||
...DEFAULT_BODY,
|
...DEFAULT_BODY,
|
||||||
client_assertion: this.buildJwtBody()
|
client_assertion: this.buildJwtBody()
|
||||||
@@ -151,6 +147,25 @@ export class JWTService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async getAccessToken() {
|
||||||
|
// Caso 1: El token actual es valido
|
||||||
|
if (this.authToken != undefined && !this.authToken.isExpired()) {
|
||||||
|
return this.authToken
|
||||||
|
}
|
||||||
|
|
||||||
|
// Caso 2: El token ha expirado pero se puede refrescar
|
||||||
|
if (this.authToken?.isExpired() && this.refreshToken != undefined && !this.refreshToken.isExpired()) {
|
||||||
|
await this.tryRefreshToken()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Caso 3: Ningún token es valido
|
||||||
|
await this.getNewTokens()
|
||||||
|
|
||||||
|
if (this.authToken == undefined) throw new Error("Error obteniendo tokens de auth")
|
||||||
|
|
||||||
|
return this.authToken
|
||||||
|
}
|
||||||
|
|
||||||
public async tryRefreshToken() {
|
public async tryRefreshToken() {
|
||||||
if (this.refreshToken == undefined) throw new Error("El refreshToken no está definido")
|
if (this.refreshToken == undefined) throw new Error("El refreshToken no está definido")
|
||||||
if (this.refreshToken.isExpired()) throw new Error("El refreshToken ha expirado")
|
if (this.refreshToken.isExpired()) throw new Error("El refreshToken ha expirado")
|
||||||
|
|||||||
@@ -1,7 +1,14 @@
|
|||||||
import { EventBus } from "#shared/domain/EventBus.port";
|
import { EventBus } from "#shared/domain/EventBus.port";
|
||||||
import { ConsumeMessage } from "amqplib";
|
import { ConsumeMessage } from "amqplib";
|
||||||
import { SimUseCases } from "./Sim.usecases";
|
import { SimUseCases } from "./Sim.usecases";
|
||||||
|
import { SimEvents } from "#shared/domain/SimEvents";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* La clase usa generadores de funciones para mantener el contexto
|
||||||
|
* el proceso se hace en 2 partes:
|
||||||
|
* Controlador - handlers -> Router -> Ejecucion
|
||||||
|
*
|
||||||
|
*/
|
||||||
export class SimController {
|
export class SimController {
|
||||||
private eventBus: EventBus;
|
private eventBus: EventBus;
|
||||||
private useCases: SimUseCases
|
private useCases: SimUseCases
|
||||||
@@ -15,33 +22,61 @@ export class SimController {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async activateSim(msg: ConsumeMessage | null) {
|
private decodeMsg(msg: ConsumeMessage): object | undefined {
|
||||||
return async () => {
|
if (msg.content == undefined) {
|
||||||
if (!this.validateActivationMsg(msg)) {
|
console.warn('[Sim.controller] Mensaje vacío');
|
||||||
throw new Error("Error consumiendo el mensaje no es valido")
|
return undefined;
|
||||||
}
|
}
|
||||||
const msgData = Buffer.from(JSON.parse(msg?.content.toString("utf-8") || "{}").data)
|
|
||||||
console.log("Mensaje procesado", String(msgData))
|
|
||||||
|
|
||||||
// Caso de uso de activaciones
|
try {
|
||||||
await this.useCases.activate({
|
// Convertir el Buffer a String (UTF-8)
|
||||||
dueDate: new Date().toString(),
|
const contentJson = JSON.parse(Buffer.from(msg.content).toString('utf8'))
|
||||||
identifier: {
|
return contentJson;
|
||||||
identifierType: "ICCID",
|
|
||||||
identifiers: ["1234"]
|
} catch (error) {
|
||||||
}
|
console.error('Error al decodificar JSON:', error);
|
||||||
})()
|
// Aquí podrías decidir devolver el string crudo o null
|
||||||
// TODO: comprobar el resultado de la opreacion
|
return undefined;
|
||||||
this.eventBus.ack(msg!)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async pauseSim(msg: ConsumeMessage | null) {
|
public activateSim() {
|
||||||
return async () => {
|
return async (msg: ConsumeMessage) => {
|
||||||
if (!this.validateActivationMsg(msg)) {
|
if (!this.validateActivationMsg(msg)) {
|
||||||
throw new Error("Error consumiendo el mensaje no es valido")
|
throw new Error("Error consumiendo el mensaje no es valido")
|
||||||
}
|
}
|
||||||
const msgData = Buffer.from(JSON.parse(msg?.content.toString("utf-8") || "{}").data)
|
const msgData = this.decodeMsg(msg) as SimEvents.activation
|
||||||
|
|
||||||
|
if (msgData == undefined || msgData.payload == undefined) Promise.reject("Mensaje invalido")
|
||||||
|
console.log("Mensaje procesado", msgData?.toString())
|
||||||
|
|
||||||
|
// TODO: Añadir un validador del mensaje
|
||||||
|
|
||||||
|
const iccid = msgData.payload.iccid
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Caso de uso de activaciones
|
||||||
|
await this.useCases.activate({
|
||||||
|
dueDate: this.genDueDate(2 * 60).toISOString(),
|
||||||
|
identifier: {
|
||||||
|
identifierType: "ICCID",
|
||||||
|
identifiers: [iccid]
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
this.eventBus.ack(msg)
|
||||||
|
} catch (e) {
|
||||||
|
this.eventBus.nack(msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public pauseSim() {
|
||||||
|
return async (msg: ConsumeMessage) => {
|
||||||
|
if (!this.validateActivationMsg(msg)) {
|
||||||
|
throw new Error("Error consumiendo el mensaje no es valido")
|
||||||
|
}
|
||||||
|
const msgData = this.decodeMsg(msg)
|
||||||
|
if (msgData == undefined) Promise.reject("Mensaje invalido")
|
||||||
console.log("Mensaje procesado", String(msgData))
|
console.log("Mensaje procesado", String(msgData))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -54,4 +89,10 @@ export class SimController {
|
|||||||
if (msg == undefined) return false;
|
if (msg == undefined) return false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private genDueDate(secondsDue: number) {
|
||||||
|
const now = Date.now() + secondsDue * 1000
|
||||||
|
const dueDate = new Date(now)
|
||||||
|
return dueDate
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,48 +1,62 @@
|
|||||||
/**
|
/**
|
||||||
* Dirige cada mensaje dependiendo de el tipo de accion que contenga
|
* 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 { ConsumeMessage } from "amqplib";
|
||||||
import { SimController } from "./Sim.controller";
|
import { SimController } from "./Sim.controller";
|
||||||
|
|
||||||
export class SimRouter {
|
export class SimRouter {
|
||||||
private simController: SimController
|
private readonly routes: Map<string, (m: ConsumeMessage) => Promise<any>>;
|
||||||
|
|
||||||
private routeMap: Map<string, (m: ConsumeMessage | null) => void> = new Map()
|
constructor(private readonly simController: SimController) {
|
||||||
|
this.routes = new Map([
|
||||||
constructor(simController: SimController) {
|
["activate", this.simController.activateSim()],
|
||||||
this.simController = simController
|
["pause", this.simController.pauseSim()],
|
||||||
|
]);
|
||||||
// No me gusta que se defina en el constructor
|
|
||||||
this.routeMap = new Map([
|
|
||||||
["activate", this.simController.activateSim],
|
|
||||||
["pause", this.simController.pauseSim],
|
|
||||||
])
|
|
||||||
this.route = this.route.bind(this)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public route(msg: ConsumeMessage | null) {
|
/**
|
||||||
if (msg == undefined) {
|
* Enruta el mensaje a la acción correspondiente basándose en la routing key
|
||||||
console.error("Mensaje vacio")
|
*/
|
||||||
|
public route = async (msg: ConsumeMessage | null): Promise<void> => {
|
||||||
|
if (!msg) {
|
||||||
|
console.error("[Router] Mensaje vacío");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const routingKey = msg.fields.routingKey
|
const action = this.extractAction(msg);
|
||||||
const action = routingKey.split(".")[2]
|
|
||||||
|
|
||||||
if (action == undefined) {
|
if (!action) {
|
||||||
console.error("La routing key no tiene una accion definida")
|
console.error("[Router] La routing key no tiene una acción definida", msg.fields.routingKey);
|
||||||
console.error(msg.fields)
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const accionEjecutable = this.routeMap.get(action)
|
const handler = this.routes.get(action);
|
||||||
|
|
||||||
if (accionEjecutable == undefined) {
|
if (!handler) {
|
||||||
console.error("La accion del mensaje no tiene un controlador asociado")
|
console.error(`[Router] La acción '${action}' no tiene un controlador asociado`);
|
||||||
} else {
|
return;
|
||||||
console.log("Ejecutado operacion", action)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
console.log("[Router] Ejecutando operación:", action);
|
||||||
|
|
||||||
|
// El controlador devuelve una función (thunk) que debe ser ejecutada
|
||||||
|
const executeParams = await handler(msg);
|
||||||
|
|
||||||
|
if (typeof executeParams === "function") {
|
||||||
|
await executeParams();
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`[Router] Error al ejecutar la operación '${action}':`, error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
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];
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export class SimUseCases {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public activate(activationData: ActivationData) {
|
public activate(activationData: ActivationData) {
|
||||||
const OPERATION_URL = "/actions/preactivate"
|
const OPERATION_URL = "/actions/preactivateLine"
|
||||||
return async () => {
|
return async () => {
|
||||||
const req = this.httpClient.client.post(OPERATION_URL, {
|
const req = this.httpClient.client.post(OPERATION_URL, {
|
||||||
...activationData
|
...activationData
|
||||||
@@ -23,7 +23,7 @@ export class SimUseCases {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const e = await req
|
const e = await req
|
||||||
console.log("Activacion con exito", e.data)
|
console.log("Activacion con exito", e.data.response)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error activando ", error)
|
console.error("Error activando ", error)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
import { ActivationData } from "#domain/DTOs/objeniousapi"
|
|
||||||
import { HttpClient } from "#shared/infrastructure/HTTPClient"
|
|
||||||
|
|
||||||
|
|
||||||
export class SimActivationUseCase {
|
|
||||||
private httpClient: HttpClient
|
|
||||||
|
|
||||||
constructor(args: {
|
|
||||||
|
|
||||||
httpClient: HttpClient
|
|
||||||
}) {
|
|
||||||
this.httpClient = args.httpClient
|
|
||||||
}
|
|
||||||
|
|
||||||
public async run(activationData: ActivationData,) {
|
|
||||||
const req = this.httpClient.client.post("/actions/preactivate", {
|
|
||||||
...activationData
|
|
||||||
})
|
|
||||||
|
|
||||||
try {
|
|
||||||
const e = await req
|
|
||||||
console.log("Activacion con exito", e.data)
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error activando ", error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -27,7 +27,8 @@ export const env = {
|
|||||||
OBJ_AUTHOIZATION: String(process.env.OBJ_ATHORIZATION),
|
OBJ_AUTHOIZATION: String(process.env.OBJ_ATHORIZATION),
|
||||||
OBJ_CLI_ASSERTION: String(process.env.OBJ_CLI_ASSERTION),
|
OBJ_CLI_ASSERTION: String(process.env.OBJ_CLI_ASSERTION),
|
||||||
OBJ_CLIENT_ID: String(process.env.OBJ_CLIENT_ID),
|
OBJ_CLIENT_ID: String(process.env.OBJ_CLIENT_ID),
|
||||||
OBJ_KID: String(process.env.OBJ_KID)
|
OBJ_KID: String(process.env.OBJ_KID),
|
||||||
|
OBJ_BASE_URL: String(process.env.OBJ_BASE_URL)
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import { JWTService } from "aplication/JWT.service"
|
import { JWTService } from "aplication/JWT.service"
|
||||||
import { HttpClient } from "#shared/infrastructure/HTTPClient"
|
import { HttpClient } from "#shared/infrastructure/HTTPClient"
|
||||||
|
import { env } from "./env"
|
||||||
|
|
||||||
// TODO: mover a shared/infrastructure para usar en el resto de servicios
|
const OBJ_BASE_URL = env.OBJ_BASE_URL
|
||||||
|
|
||||||
export const httpInstance = new HttpClient({
|
export const httpInstance = new HttpClient({
|
||||||
baseURL: "https://api-getway.objenious.com/ws/",
|
baseURL: OBJ_BASE_URL,
|
||||||
headers: {
|
headers: {
|
||||||
"content-type": "application/x-www-form-urlencoded"
|
"content-type": " application/json; charset=utf-8"
|
||||||
},
|
},
|
||||||
jwtManager: new JWTService()
|
jwtManager: new JWTService()
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ const COMPAÑIASICCID = new Map<string, string>(
|
|||||||
[
|
[
|
||||||
["3490", "alai"],
|
["3490", "alai"],
|
||||||
["3510", "nos"],
|
["3510", "nos"],
|
||||||
["3399", "objenious"]
|
["3320", "objenious"]
|
||||||
])
|
])
|
||||||
|
|
||||||
export class SimController {
|
export class SimController {
|
||||||
|
|||||||
@@ -46,16 +46,6 @@ export class SimUsecases {
|
|||||||
return this.eventBus.publish([activationEvent])
|
return this.eventBus.publish([activationEvent])
|
||||||
}
|
}
|
||||||
|
|
||||||
async cancelation(args: { iccid: string }) {
|
|
||||||
const cancelationEvent = <SimEvents.cancelation>{
|
|
||||||
key: "sim.cancelation",
|
|
||||||
payload: {
|
|
||||||
iccid: args.iccid
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.eventBus.publish([cancelationEvent])
|
|
||||||
}
|
|
||||||
|
|
||||||
async pause(args: { iccid: string }) {
|
async pause(args: { iccid: string }) {
|
||||||
const cancelationEvent = <SimEvents.pause>{
|
const cancelationEvent = <SimEvents.pause>{
|
||||||
|
|||||||
Reference in New Issue
Block a user