Refactor de jwt y base de la bdd de pausas-cancelaciones
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* Para la tarea WEBINT-328-Pausas-cacelaciones.
|
||||
* Almacena las pausas/cancelaciones que no se han podido hacer porque la linea esta en
|
||||
* "Test"
|
||||
*/
|
||||
|
||||
CREATE TABLE IF NOT EXISTS pause_cancel_tasks (
|
||||
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
|
||||
iccid TEXT NOT NULL,
|
||||
|
||||
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
|
||||
);
|
||||
|
||||
-- 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;
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ import { ConsumeMessage } from "amqplib";
|
||||
import { SimUseCases } from "./Sim.usecases.js";
|
||||
import { SimEvents } from "sim-shared/domain/SimEvents.js";
|
||||
import { Result } from "sim-shared/domain/Result.js";
|
||||
import { env } from "#config/env/index.js";
|
||||
|
||||
/**
|
||||
* La clase usa generadores de funciones para mantener el contexto
|
||||
@@ -157,6 +156,9 @@ export class SimController {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lo mismo que pause
|
||||
*/
|
||||
public suspend() {
|
||||
return async (msg: ConsumeMessage) => {
|
||||
let msgData;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { HttpClient } from "sim-shared/infrastructure/HTTPClient.js"
|
||||
import { JWTService } from "../aplication/JWT.service.js"
|
||||
import { env } from "./env/index.js"
|
||||
import { jwtService } from "./jwtService.config.js"
|
||||
|
||||
const OBJ_BASE_URL = env.OBJ_BASE_URL
|
||||
|
||||
@@ -9,5 +9,5 @@ export const httpInstance = new HttpClient({
|
||||
headers: {
|
||||
"content-type": " application/json; charset=utf-8"
|
||||
},
|
||||
jwtManager: new JWTService()
|
||||
jwtManager: jwtService
|
||||
})
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import { GrantAccessRequestBody, JWTService } from "sim-shared/aplication/JWT.service.js"
|
||||
import { env } from "./env/index.js"
|
||||
import { JWTHeader } from "sim-shared/domain/JWT.js"
|
||||
|
||||
|
||||
const PRIVATE_KEY_PATH = env.OBJ_PEM_PATH
|
||||
|
||||
const GET_TOKEN_URL = "https://idp.docapost.io/auth/realms/GETWAY/protocol/openid-connect/token"
|
||||
const REFRESH_TOKEN_URL = GET_TOKEN_URL
|
||||
|
||||
const DEFAULT_BODY: GrantAccessRequestBody = {
|
||||
grant_type: "client_credentials",
|
||||
client_id: env.OBJ_CLIENT_ID,
|
||||
client_assertion_type: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
|
||||
client_assertion: env.OBJ_CLI_ASSERTION
|
||||
}
|
||||
|
||||
|
||||
const DEFAULT_HEADERS = {
|
||||
"content-type": "application/x-www-form-urlencoded"
|
||||
}
|
||||
|
||||
const DEFAULT_HEADERS_JWT = {
|
||||
alg: "RS256",
|
||||
typ: "JWT",
|
||||
kid: env.OBJ_KID,
|
||||
}
|
||||
|
||||
const DEFAULT_DATA_JWT = {
|
||||
sub: env.OBJ_CLIENT_ID,
|
||||
iss: env.OBJ_CLIENT_ID,
|
||||
aud: "https://idp.docapost.io/auth/realms/GETWAY",
|
||||
jti: Date.now().toString(),
|
||||
|
||||
}
|
||||
|
||||
function addIATHeaders(authHeaders: Object) {
|
||||
const headers = <JWTHeader>{
|
||||
...authHeaders,
|
||||
sub: env.OBJ_CLIENT_ID,
|
||||
iss: env.OBJ_CLIENT_ID,
|
||||
aud: GET_TOKEN_URL,
|
||||
jti: Date.now().toString(),
|
||||
iat: Math.floor(Date.now() / 1000),
|
||||
exp: Math.floor(Date.now() / 1000) + 5 * 60,
|
||||
}
|
||||
return headers
|
||||
}
|
||||
|
||||
export const jwtService = new JWTService({
|
||||
transformJWTHeaders: addIATHeaders,
|
||||
defaultHeaders: DEFAULT_HEADERS,
|
||||
defaultBody: DEFAULT_BODY,
|
||||
defaultJWTHeaders: DEFAULT_HEADERS_JWT,
|
||||
defaultJWTPayload: DEFAULT_DATA_JWT,
|
||||
privateKeyPath: PRIVATE_KEY_PATH,
|
||||
tokenUrl: GET_TOKEN_URL,
|
||||
refreshTokenUrl: REFRESH_TOKEN_URL
|
||||
})
|
||||
@@ -1,6 +1,7 @@
|
||||
import { HttpClient } from "sim-shared/infrastructure/HTTPClient.js"
|
||||
import { env } from "./env/index.js"
|
||||
import { JWTService } from "packages/sim-consumidor-objenious/aplication/JWT.service.js"
|
||||
import { jwtService } from "./jwtService.config.js"
|
||||
|
||||
|
||||
const OBJ_BASE_URL = env.OBJ_BASE_URL
|
||||
|
||||
@@ -9,5 +10,5 @@ export const httpInstance = new HttpClient({
|
||||
headers: {
|
||||
"content-type": " application/json; charset=utf-8"
|
||||
},
|
||||
jwtManager: new JWTService()
|
||||
jwtManager: jwtService
|
||||
})
|
||||
|
||||
59
packages/sim-objenious-cron/config/jwtService.config.ts
Normal file
59
packages/sim-objenious-cron/config/jwtService.config.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { GrantAccessRequestBody, JWTService } from "sim-shared/aplication/JWT.service.js"
|
||||
import { env } from "./env/index.js"
|
||||
import { JWTHeader } from "sim-shared/domain/JWT.js"
|
||||
|
||||
|
||||
const PRIVATE_KEY_PATH = env.OBJ_PEM_PATH
|
||||
|
||||
const GET_TOKEN_URL = "https://idp.docapost.io/auth/realms/GETWAY/protocol/openid-connect/token"
|
||||
const REFRESH_TOKEN_URL = GET_TOKEN_URL
|
||||
|
||||
const DEFAULT_BODY: GrantAccessRequestBody = {
|
||||
grant_type: "client_credentials",
|
||||
client_id: env.OBJ_CLIENT_ID,
|
||||
client_assertion_type: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
|
||||
client_assertion: env.OBJ_CLI_ASSERTION
|
||||
}
|
||||
|
||||
|
||||
const DEFAULT_HEADERS = {
|
||||
"content-type": "application/x-www-form-urlencoded"
|
||||
}
|
||||
|
||||
const DEFAULT_HEADERS_JWT = {
|
||||
alg: "RS256",
|
||||
typ: "JWT",
|
||||
kid: env.OBJ_KID,
|
||||
}
|
||||
|
||||
const DEFAULT_DATA_JWT = {
|
||||
sub: env.OBJ_CLIENT_ID,
|
||||
iss: env.OBJ_CLIENT_ID,
|
||||
aud: "https://idp.docapost.io/auth/realms/GETWAY",
|
||||
jti: Date.now().toString(),
|
||||
|
||||
}
|
||||
|
||||
function addIATHeaders(authHeaders: Object) {
|
||||
const headers = <JWTHeader>{
|
||||
...authHeaders,
|
||||
sub: env.OBJ_CLIENT_ID,
|
||||
iss: env.OBJ_CLIENT_ID,
|
||||
aud: GET_TOKEN_URL,
|
||||
jti: Date.now().toString(),
|
||||
iat: Math.floor(Date.now() / 1000),
|
||||
exp: Math.floor(Date.now() / 1000) + 5 * 60,
|
||||
}
|
||||
return headers
|
||||
}
|
||||
|
||||
export const jwtService = new JWTService({
|
||||
transformJWTHeaders: addIATHeaders,
|
||||
defaultHeaders: DEFAULT_HEADERS,
|
||||
defaultBody: DEFAULT_BODY,
|
||||
defaultJWTHeaders: DEFAULT_HEADERS_JWT,
|
||||
defaultJWTPayload: DEFAULT_DATA_JWT,
|
||||
privateKeyPath: PRIVATE_KEY_PATH,
|
||||
tokenUrl: GET_TOKEN_URL,
|
||||
refreshTokenUrl: REFRESH_TOKEN_URL
|
||||
})
|
||||
@@ -21,7 +21,10 @@ async function startCron() {
|
||||
console.log("[i] Comprobando conexion con la BDD ")
|
||||
await pgClient.checkDatabaseConnection()
|
||||
|
||||
const operationRepository = new ObjeniousOperationsRepository(pgClient)
|
||||
const operationRepository = new ObjeniousOperationsRepository(
|
||||
httpClient,
|
||||
pgClient,
|
||||
)
|
||||
const orderRepository = new OrderRepository(pgClient)
|
||||
const objeniousLineRepository = new ObjeniousLinesRepository(postgresClientIntranet)
|
||||
|
||||
@@ -31,7 +34,15 @@ async function startCron() {
|
||||
httpClient,
|
||||
)
|
||||
|
||||
const volcadoLineasTask = new TaskVolcadoLineas(httpClient, objeniousLineRepository)
|
||||
const objeniosRepo = new ObjeniousOperationsRepository(
|
||||
httpClient,
|
||||
pgClient
|
||||
)
|
||||
|
||||
const volcadoLineasTask = new TaskVolcadoLineas(
|
||||
objeniousLineRepository,
|
||||
objeniosRepo
|
||||
)
|
||||
|
||||
const PERIODO_PETICIONES = 10 * 60 * 1000
|
||||
const interval = setInterval(async () => {
|
||||
|
||||
@@ -1,94 +1,14 @@
|
||||
import assert from "node:assert";
|
||||
import { lineToCreateLineDto, ObjeniousLine, ObjeniousLineResponse } from "sim-shared/domain/objeniousLine.js";
|
||||
import { tryCatch, Result } from "sim-shared/domain/Result.js";
|
||||
import { HttpClient } from "sim-shared/infrastructure/HTTPClient.js";
|
||||
import { lineToCreateLineDto, ObjeniousLine } from "sim-shared/domain/objeniousLine.js";
|
||||
import { ObjeniousLinesRepository } from "../infranstructure/ObjeniousLinesRepository.js";
|
||||
import { AxiosResponse } from "axios";
|
||||
import { constants } from "node:buffer";
|
||||
|
||||
const MAX_PAGE_SIZE = 100
|
||||
import { ObjeniousOperationsRepository } from "packages/sim-shared/infrastructure/ObjeniousOperationRepository.js";
|
||||
|
||||
export class TaskVolcadoLineas {
|
||||
constructor(
|
||||
private readonly httpClient: HttpClient,
|
||||
private readonly linesRepository: ObjeniousLinesRepository,
|
||||
private readonly objeniousRepository: ObjeniousOperationsRepository
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Mover al repo
|
||||
*/
|
||||
private async * getLinesByStatus(args?: {
|
||||
pageSize?: number,
|
||||
pageNumber?: number,
|
||||
status?: string
|
||||
}): AsyncGenerator<Result<string, ObjeniousLine[]>, Result<string, ObjeniousLine[]>, any> {
|
||||
|
||||
const path = "/lines"
|
||||
const pageSize = args?.pageSize ?? MAX_PAGE_SIZE;
|
||||
|
||||
let currentPage = args?.pageNumber ?? 0;
|
||||
let totalPages: number | undefined = undefined; // Como limite de paginas, igual es pasarse pero hasta que se lea
|
||||
|
||||
const params: Record<string, string | number> = {}
|
||||
|
||||
const loadNextLine = async (page: number): Promise<Result<string, ObjeniousLine[]>> => {
|
||||
if (args?.status != undefined) params["simStatus"] = args.status
|
||||
params["pageSize"] = pageSize
|
||||
params["pageNumber"] = page
|
||||
console.log("Params", params)
|
||||
console.log(`[i] Cargando pagina ${currentPage} de ${totalPages ?? "(desc)"}`)
|
||||
const nextPage = await tryCatch<AxiosResponse<ObjeniousLineResponse>>(this.httpClient.client.get(path, {
|
||||
params: params
|
||||
}))
|
||||
|
||||
if (nextPage.error != undefined) {
|
||||
console.error(nextPage.error.msg)
|
||||
return {
|
||||
error: nextPage.error.msg.message
|
||||
}
|
||||
}
|
||||
|
||||
// Se aumenta para la siguiente ejecucion
|
||||
console.log(`[i] Página ${currentPage} completa, total: ${nextPage.data.data.totalPages}`)
|
||||
totalPages = nextPage.data.data.totalPages
|
||||
|
||||
return {
|
||||
data: nextPage.data.data.content
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// El inicio se ejecuta siempre
|
||||
const lines = await loadNextLine(currentPage)
|
||||
|
||||
if (lines.error != undefined) {
|
||||
console.error("[x] Error obteniendo las lineas, cancelando operación");
|
||||
return {
|
||||
error: "Error cargando lineas"
|
||||
}
|
||||
}
|
||||
|
||||
currentPage++;
|
||||
|
||||
yield {
|
||||
data: lines.data
|
||||
}
|
||||
|
||||
// Copia para evitar bucles infinitos por error de la api
|
||||
const maxPages = totalPages
|
||||
assert.ok(maxPages != undefined, "No se ha defindo el numero de paginas") // Nunca deberia pasar pero así se evitan bucles infnitos
|
||||
console.log("maxPages", maxPages)
|
||||
for (let i = currentPage; i < maxPages!; i++) {
|
||||
console.log("Bucle i:", i, "page: ", currentPage)
|
||||
yield await loadNextLine(currentPage);
|
||||
currentPage++;
|
||||
}
|
||||
|
||||
return {
|
||||
data: []
|
||||
}
|
||||
}
|
||||
|
||||
private async saveLines(lines: ObjeniousLine[]) {
|
||||
const linesToCreate = lines.map(lineToCreateLineDto)
|
||||
@@ -107,7 +27,9 @@ export class TaskVolcadoLineas {
|
||||
console.log("[i] Iniciando task de volcado de lineas de Objenious")
|
||||
// Carga todas las lineas en memoria, hay que comprobar que no se gaste demasiada
|
||||
|
||||
const linesIterator = this.getLinesByStatus()
|
||||
const linesIterator = this.objeniousRepository.getLinesByStatusAPI({
|
||||
pageSize: 100
|
||||
})
|
||||
let lines = await linesIterator.next()
|
||||
|
||||
if (lines.value.error != undefined || lines.value.data == undefined) {
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import { test, describe } from "vitest"
|
||||
import { JWTService } from "./JWT.service.js"
|
||||
import { jwtService } from "../config/jwtService.config.js"
|
||||
|
||||
describe("Tokens Objenious", () => {
|
||||
const jwtService = new JWTService()
|
||||
const jwt = jwtService
|
||||
|
||||
test("Solicicitud normal de auth", async () => {
|
||||
const token = await jwtService.getAccessToken()
|
||||
const token = await jwt.getAccessToken()
|
||||
console.log("acceso objenious", token)
|
||||
}),
|
||||
|
||||
test("Solicicitud de refresh de auth", async () => {
|
||||
const token = await jwtService.tryRefreshToken()
|
||||
const token = await jwt.tryRefreshToken()
|
||||
console.log("acceso refresh objenious", token)
|
||||
})
|
||||
})
|
||||
@@ -4,24 +4,24 @@
|
||||
* el cliente HTTP
|
||||
*/
|
||||
|
||||
import { env } from "#config/env/index.js";
|
||||
import fs from "fs"
|
||||
|
||||
import {
|
||||
JWTToken,
|
||||
JWTHeader,
|
||||
IJWTService
|
||||
IJWTService,
|
||||
JWTPayload
|
||||
} from "sim-shared/domain/JWT.js"
|
||||
import axios, { AxiosError } from "axios";
|
||||
|
||||
type GrantAccessRequestBody = {
|
||||
export type GrantAccessRequestBody = {
|
||||
grant_type: string,
|
||||
client_id: string,
|
||||
client_assertion_type: string,
|
||||
client_assertion: string
|
||||
}
|
||||
|
||||
type TokensRequestResponse = {
|
||||
export type TokensRequestResponse = {
|
||||
"access_token": string,
|
||||
"expires_in": number,
|
||||
"refresh_token": string
|
||||
@@ -32,41 +32,6 @@ type TokensRequestResponse = {
|
||||
"scope": string
|
||||
}
|
||||
|
||||
|
||||
const PRIVATE_KEY_PATH = env.OBJ_PEM_PATH
|
||||
|
||||
const GET_TOKEN_URL = "https://idp.docapost.io/auth/realms/GETWAY/protocol/openid-connect/token"
|
||||
const REFRESH_TOKEN_URL = GET_TOKEN_URL
|
||||
|
||||
const DEFAULT_BODY: GrantAccessRequestBody = {
|
||||
grant_type: "client_credentials",
|
||||
client_id: env.OBJ_CLIENT_ID,
|
||||
client_assertion_type: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
|
||||
client_assertion: env.OBJ_CLI_ASSERTION
|
||||
}
|
||||
|
||||
const REFRESH_BODY = {
|
||||
...DEFAULT_BODY,
|
||||
grant_type: "refresh_token",
|
||||
}
|
||||
|
||||
const DEFAULT_HEADERS = {
|
||||
"content-type": "application/x-www-form-urlencoded"
|
||||
}
|
||||
|
||||
function addIATHeaders(authHeaders: Object) {
|
||||
const headers = <JWTHeader>{
|
||||
...authHeaders,
|
||||
sub: env.OBJ_CLIENT_ID,
|
||||
iss: env.OBJ_CLIENT_ID,
|
||||
aud: GET_TOKEN_URL,
|
||||
jti: Date.now().toString(),
|
||||
iat: Math.floor(Date.now() / 1000),
|
||||
exp: Math.floor(Date.now() / 1000) + 5 * 60,
|
||||
}
|
||||
return headers
|
||||
}
|
||||
|
||||
export type ObjeniousTokenBody = any
|
||||
|
||||
/**
|
||||
@@ -82,27 +47,54 @@ export class JWTService implements IJWTService<ObjeniousTokenBody> {
|
||||
public authToken: JWTToken<ObjeniousTokenBody> | undefined;
|
||||
private refreshToken?: JWTToken<ObjeniousTokenBody> | undefined;
|
||||
|
||||
constructor(args?: {
|
||||
// http
|
||||
private transformHeaders?: (_: Object) => JWTHeader;
|
||||
private defaultHttpHeaders: Record<string, string>;
|
||||
private defaultBody: Record<string, string>;
|
||||
|
||||
// jwt
|
||||
private defaultJWTHeaders: JWTHeader;
|
||||
private defaultJWTPayload: JWTPayload<any>;
|
||||
private privateKeyPath: string;
|
||||
private tokenUrl: string;
|
||||
private refreshTokenUrl: string;
|
||||
|
||||
|
||||
constructor(args: {
|
||||
token?: string // si se partiese de un token existente,
|
||||
refreshToken?: string
|
||||
refreshToken?: string,
|
||||
transformJWTHeaders?: (_: Object) => JWTHeader,
|
||||
defaultHeaders: Record<string, string>,
|
||||
defaultBody: Record<string, string>,
|
||||
defaultJWTHeaders: JWTHeader,
|
||||
defaultJWTPayload: JWTPayload<any>,
|
||||
privateKeyPath: string,
|
||||
tokenUrl: string,
|
||||
refreshTokenUrl: string
|
||||
}) {
|
||||
if (args?.token != undefined) this.authToken = new JWTToken(args.token)
|
||||
if (args?.refreshToken != undefined) this.refreshToken = new JWTToken(args.refreshToken)
|
||||
if (args?.transformJWTHeaders != undefined) this.transformHeaders = args.transformJWTHeaders
|
||||
|
||||
this.defaultHttpHeaders = args.defaultHeaders;
|
||||
this.defaultBody = args.defaultBody;
|
||||
|
||||
this.defaultJWTHeaders = args.defaultJWTHeaders;
|
||||
this.defaultJWTPayload = args.defaultJWTPayload;
|
||||
this.privateKeyPath = args.privateKeyPath;
|
||||
|
||||
this.tokenUrl = args.tokenUrl;
|
||||
this.refreshTokenUrl = args.refreshTokenUrl;
|
||||
}
|
||||
|
||||
private buildJwtBody() {
|
||||
const jwtHeaders = {
|
||||
alg: "RS256",
|
||||
typ: "JWT",
|
||||
kid: env.OBJ_KID
|
||||
}
|
||||
const jwtData = addIATHeaders({
|
||||
sub: env.OBJ_CLIENT_ID,
|
||||
iss: env.OBJ_CLIENT_ID,
|
||||
aud: "https://idp.docapost.io/auth/realms/GETWAY",
|
||||
jti: Date.now().toString(),
|
||||
})
|
||||
const key = fs.readFileSync(PRIVATE_KEY_PATH, "utf8")
|
||||
const jwtHeaders = this.defaultJWTHeaders
|
||||
|
||||
const jwtData = (this.transformHeaders) ?
|
||||
this.transformHeaders(this.defaultJWTPayload) :
|
||||
this.defaultJWTPayload;
|
||||
|
||||
const key = fs.readFileSync(this.privateKeyPath, "utf8")
|
||||
const token = JWTToken.fromParts({
|
||||
header: jwtHeaders,
|
||||
payload: jwtData,
|
||||
@@ -116,14 +108,16 @@ export class JWTService implements IJWTService<ObjeniousTokenBody> {
|
||||
|
||||
public async getNewAuthToken() {
|
||||
const bodyWithtoken = {
|
||||
...DEFAULT_BODY,
|
||||
...this.defaultBody,
|
||||
client_assertion: this.buildJwtBody()
|
||||
}
|
||||
|
||||
const req = axios.post(GET_TOKEN_URL,
|
||||
const headers = (this.transformHeaders) ? this.transformHeaders(this.defaultHttpHeaders) : this.defaultHttpHeaders;
|
||||
|
||||
const req = axios.post(this.tokenUrl,
|
||||
bodyWithtoken,
|
||||
{
|
||||
headers: addIATHeaders(DEFAULT_HEADERS)
|
||||
headers: headers
|
||||
}
|
||||
)
|
||||
|
||||
@@ -166,16 +160,21 @@ export class JWTService implements IJWTService<ObjeniousTokenBody> {
|
||||
if (this.refreshToken == undefined) throw new Error("El refreshToken no está definido")
|
||||
if (this.refreshToken.isExpired()) throw new Error("El refreshToken ha expirado")
|
||||
|
||||
const refreshBody = {
|
||||
...this.defaultBody,
|
||||
grant_type: "refresh_token",
|
||||
}
|
||||
|
||||
const body = {
|
||||
...REFRESH_BODY,
|
||||
...refreshBody,
|
||||
client_assertion: this.buildJwtBody(),
|
||||
refresh_token: this.refreshToken.rawToken
|
||||
}
|
||||
|
||||
const req = axios.post(REFRESH_TOKEN_URL,
|
||||
const req = axios.post(this.refreshTokenUrl,
|
||||
body,
|
||||
{
|
||||
headers: DEFAULT_HEADERS
|
||||
headers: this.defaultHttpHeaders
|
||||
}
|
||||
)
|
||||
|
||||
@@ -7,9 +7,12 @@
|
||||
import { env, loadEnvFile } from "node:process";
|
||||
import { Pool } from "pg";
|
||||
import { PgClient } from "../infrastructure/PgClient.js";
|
||||
import { HttpClient } from "../infrastructure/HTTPClient.js";
|
||||
import { jwtService } from "./jwtService.config.js";
|
||||
|
||||
console.warn("[i!] Se está corriendo codigo de test")
|
||||
loadEnvFile("../../.env") // Global
|
||||
loadEnvFile("./test.env") // Local
|
||||
|
||||
// se hace una por servicio.
|
||||
export const pgPool = new Pool({
|
||||
@@ -24,4 +27,14 @@ export const postgresClient = new PgClient({
|
||||
pool: pgPool
|
||||
})
|
||||
|
||||
const OBJ_BASE_URL = "https://api-getway.objenious.com/ws"
|
||||
export const httpObjClient = new HttpClient({
|
||||
baseURL: OBJ_BASE_URL,
|
||||
headers: {
|
||||
"content-type": " application/json; charset=utf-8"
|
||||
},
|
||||
jwtManager: jwtService
|
||||
})
|
||||
|
||||
|
||||
console.warn(`[T] TEST DB : ${env.POSTGRES_DATABASE}@${env.POSTGRES_HOST}`)
|
||||
|
||||
67
packages/sim-shared/config/jwtService.config.ts
Normal file
67
packages/sim-shared/config/jwtService.config.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import assert from "assert"
|
||||
import { env, loadEnvFile } from "process"
|
||||
import { GrantAccessRequestBody, JWTService } from "sim-shared/aplication/JWT.service.js"
|
||||
import { JWTHeader } from "sim-shared/domain/JWT.js"
|
||||
|
||||
loadEnvFile("../../.env") // Global
|
||||
loadEnvFile("./test.env") // Local
|
||||
|
||||
assert(env.OBJ_CLIENT_ID != undefined)
|
||||
assert(env.OBJ_CLI_ASSERTION != undefined)
|
||||
assert(env.OBJ_PEM_PATH != undefined)
|
||||
|
||||
const PRIVATE_KEY_PATH = env.OBJ_PEM_PATH
|
||||
|
||||
const GET_TOKEN_URL = "https://idp.docapost.io/auth/realms/GETWAY/protocol/openid-connect/token"
|
||||
const REFRESH_TOKEN_URL = GET_TOKEN_URL
|
||||
|
||||
|
||||
const DEFAULT_BODY: GrantAccessRequestBody = {
|
||||
grant_type: "client_credentials",
|
||||
client_id: env.OBJ_CLIENT_ID,
|
||||
client_assertion_type: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
|
||||
client_assertion: env.OBJ_CLI_ASSERTION
|
||||
}
|
||||
|
||||
|
||||
const DEFAULT_HEADERS = {
|
||||
"content-type": "application/x-www-form-urlencoded"
|
||||
}
|
||||
|
||||
const DEFAULT_HEADERS_JWT = {
|
||||
alg: "RS256",
|
||||
typ: "JWT",
|
||||
kid: env.OBJ_KID,
|
||||
}
|
||||
|
||||
const DEFAULT_DATA_JWT = {
|
||||
sub: env.OBJ_CLIENT_ID,
|
||||
iss: env.OBJ_CLIENT_ID,
|
||||
aud: "https://idp.docapost.io/auth/realms/GETWAY",
|
||||
jti: Date.now().toString(),
|
||||
|
||||
}
|
||||
|
||||
function addIATHeaders(authHeaders: Object) {
|
||||
const headers = <JWTHeader>{
|
||||
...authHeaders,
|
||||
sub: env.OBJ_CLIENT_ID,
|
||||
iss: env.OBJ_CLIENT_ID,
|
||||
aud: GET_TOKEN_URL,
|
||||
jti: Date.now().toString(),
|
||||
iat: Math.floor(Date.now() / 1000),
|
||||
exp: Math.floor(Date.now() / 1000) + 5 * 60,
|
||||
}
|
||||
return headers
|
||||
}
|
||||
|
||||
export const jwtService = new JWTService({
|
||||
transformJWTHeaders: addIATHeaders,
|
||||
defaultHeaders: DEFAULT_HEADERS,
|
||||
defaultBody: DEFAULT_BODY,
|
||||
defaultJWTHeaders: DEFAULT_HEADERS_JWT,
|
||||
defaultJWTPayload: DEFAULT_DATA_JWT,
|
||||
privateKeyPath: PRIVATE_KEY_PATH,
|
||||
tokenUrl: GET_TOKEN_URL,
|
||||
refreshTokenUrl: REFRESH_TOKEN_URL
|
||||
})
|
||||
@@ -14,7 +14,7 @@ export type Failure<E = Error> = {
|
||||
*/
|
||||
export type Result<E, D> = Failure<E> | Success<D>
|
||||
|
||||
export async function tryCatch<T>(func: Promise<T>): Promise<Result<{ msg: Error }, T>> {
|
||||
export async function tryCatch<T>(func: Promise<T>): Promise<Result<Error, T>> {
|
||||
try {
|
||||
const res = await func;
|
||||
return {
|
||||
@@ -22,9 +22,8 @@ export async function tryCatch<T>(func: Promise<T>): Promise<Result<{ msg: Error
|
||||
}
|
||||
} catch (e: unknown) {
|
||||
return {
|
||||
error: {
|
||||
msg: e as Error
|
||||
}
|
||||
error: e as Error
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import { describe, it } from "node:test";
|
||||
import { ObjeniousOperationsRepository } from "./ObjeniousOperationRepository.js";
|
||||
import { httpObjClient, postgresClient } from "../config/config.test.js";
|
||||
|
||||
describe("[Integration] Test API requests", () => {
|
||||
const repository = new ObjeniousOperationsRepository(
|
||||
httpObjClient,
|
||||
postgresClient
|
||||
)
|
||||
|
||||
it("Read /lines with multiple iccids", () => {
|
||||
|
||||
})
|
||||
})
|
||||
@@ -1,14 +1,131 @@
|
||||
import { IOperationsRepository, ObjeniousOperation, ObjeniousOperationChange } from "sim-shared/domain/operationsRepository.port.js";
|
||||
import { Result } from "sim-shared/domain/Result.js";
|
||||
import { Result, tryCatch } from "sim-shared/domain/Result.js";
|
||||
import { PgClient } from "sim-shared/infrastructure/PgClient.js";
|
||||
import { ObjeniousLine, ObjeniousLineResponse } from "../domain/objeniousLine.js";
|
||||
import { HttpClient } from "./HTTPClient.js";
|
||||
import assert from "node:assert";
|
||||
import { AxiosResponse } from "axios";
|
||||
|
||||
export class ObjeniousOperationsRepository implements IOperationsRepository {
|
||||
|
||||
constructor(
|
||||
private http: HttpClient,
|
||||
private readonly pgClient: PgClient
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Consulta el estado de una o mas lineas directamente a la API de Objenious
|
||||
*/
|
||||
public async getLinesAPI(
|
||||
identifierType: "ICCID" | "IMSI" | "IMEI" | "MSISDN" | "REFERENCE",
|
||||
identifiers: string[]
|
||||
): Promise<Result<string, ObjeniousLine[]>> {
|
||||
if (identifiers.length == 0) {
|
||||
return {
|
||||
data: []
|
||||
}
|
||||
}
|
||||
|
||||
// Comprobar < MAX_PAGE_SIZE (Poco probable)
|
||||
|
||||
const path = "/lines"
|
||||
const params = {
|
||||
"identifier.identifierType": identifierType,
|
||||
"identifier.identifiers": identifiers.toString()
|
||||
}
|
||||
|
||||
const req = this.http.client.get<ObjeniousLine[]>(path, {
|
||||
params: params
|
||||
})
|
||||
|
||||
const res = await tryCatch(req)
|
||||
|
||||
if (res.error != undefined) {
|
||||
return {
|
||||
error: res.error?.message
|
||||
}
|
||||
}
|
||||
|
||||
const lines = res.data.data
|
||||
|
||||
return {
|
||||
data: lines
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private MAX_PAGE_SIZE = 1000
|
||||
public async * getLinesByStatusAPI(args?: {
|
||||
pageSize?: number,
|
||||
pageNumber?: number,
|
||||
status?: string
|
||||
}): AsyncGenerator<Result<string, ObjeniousLine[]>, Result<string, ObjeniousLine[]>, any> {
|
||||
|
||||
const path = "/lines"
|
||||
const pageSize = args?.pageSize ?? this.MAX_PAGE_SIZE;
|
||||
|
||||
let currentPage = args?.pageNumber ?? 0;
|
||||
let totalPages: number | undefined = undefined; // Como limite de paginas, igual es pasarse pero hasta que se lea
|
||||
|
||||
const params: Record<string, string | number> = {}
|
||||
|
||||
const loadNextLine = async (page: number): Promise<Result<string, ObjeniousLine[]>> => {
|
||||
if (args?.status != undefined) params["simStatus"] = args.status
|
||||
params["pageSize"] = pageSize
|
||||
params["pageNumber"] = page
|
||||
console.log(`[i] Cargando pagina ${currentPage} de ${totalPages ?? "(desc)"}`)
|
||||
const nextPage = await tryCatch<AxiosResponse<ObjeniousLineResponse>>(this.http.client.get(path, {
|
||||
params: params
|
||||
}))
|
||||
|
||||
if (nextPage.error != undefined) {
|
||||
console.error(nextPage.error)
|
||||
return {
|
||||
error: nextPage.error.message
|
||||
}
|
||||
}
|
||||
|
||||
// Se aumenta para la siguiente ejecucion
|
||||
console.log(`[i] Página ${currentPage} completa, total: ${nextPage.data.data.totalPages}`)
|
||||
totalPages = nextPage.data.data.totalPages
|
||||
|
||||
return {
|
||||
data: nextPage.data.data.content
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// El inicio se ejecuta siempre
|
||||
const lines = await loadNextLine(currentPage)
|
||||
|
||||
if (lines.error != undefined) {
|
||||
console.error("[x] Error obteniendo las lineas, cancelando operación");
|
||||
return {
|
||||
error: "Error cargando lineas"
|
||||
}
|
||||
}
|
||||
|
||||
currentPage++;
|
||||
|
||||
yield {
|
||||
data: lines.data
|
||||
}
|
||||
|
||||
// Copia para evitar bucles infinitos por error de la api
|
||||
const maxPages = totalPages
|
||||
assert.ok(maxPages != undefined, "No se ha defindo el numero de paginas") // Nunca deberia pasar pero así se evitan bucles infnitos
|
||||
console.log("maxPages", maxPages)
|
||||
for (let i = currentPage; i < maxPages!; i++) {
|
||||
console.log("Bucle i:", i, "page: ", currentPage)
|
||||
yield await loadNextLine(currentPage);
|
||||
currentPage++;
|
||||
}
|
||||
|
||||
return {
|
||||
data: []
|
||||
}
|
||||
}
|
||||
async createOperation(data: ObjeniousOperation): Promise<Result<string, ObjeniousOperation>> {
|
||||
const query = `
|
||||
INSERT INTO objenious_operation (operation, iccids, status, max_retry, request_id)
|
||||
|
||||
@@ -27,7 +27,7 @@ describe("Test OrderRepository", {}, (ctx) => {
|
||||
before(async () => {
|
||||
// Order1
|
||||
const result1 = await orderRepo.createOrder(order1)
|
||||
assert(result1.data != undefined)
|
||||
assert.ok(result1.data != undefined, result1.error as string)
|
||||
testIds.push(result1.data.id)
|
||||
|
||||
// Order2 -> Para el test de crearOrder
|
||||
|
||||
@@ -3,10 +3,9 @@
|
||||
*/
|
||||
import { PoolClient, QueryResult, QueryResultRow } from "pg";
|
||||
import { CreateOrderDTO, ErrorOrderDTO, FinishOrderDTO, OrderTracking, UpdateOrderDTO } from "../domain/Order.js";
|
||||
import { Result } from "../domain/Result.js";
|
||||
import { Result, tryCatch } from "../domain/Result.js";
|
||||
import { PgClient } from "./PgClient.js";
|
||||
import assert from "node:assert";
|
||||
import { error } from "node:console";
|
||||
|
||||
/**
|
||||
* Agrupa todas las operaciones de *Order*.
|
||||
@@ -19,9 +18,8 @@ import { error } from "node:console";
|
||||
*/
|
||||
export class OrderRepository {
|
||||
constructor(
|
||||
private readonly pgClient: PgClient
|
||||
private readonly pgClient: PgClient,
|
||||
) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -57,6 +55,8 @@ export class OrderRepository {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* El tipo <T> representa el contenido del mensaje de los order
|
||||
*/
|
||||
|
||||
14
packages/sim-shared/test.env
Normal file
14
packages/sim-shared/test.env
Normal file
@@ -0,0 +1,14 @@
|
||||
## ENV PARA DATOS DE TEST - shared nunca se lanza en produccion
|
||||
|
||||
# claves de Objenious
|
||||
OBJ_PEM_PATH=./obj.pem
|
||||
OBJ_AUTHORIZATION=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_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"
|
||||
SIM_ACTIVATION_API_KEY=9e48c4ac-1ab0-4397-b3f3-6c239200dfe6
|
||||
Reference in New Issue
Block a user