Refactorizacion para permitir la inyeccion de dependencias en el
servicio de HTTP
This commit is contained in:
@@ -1,11 +1,45 @@
|
||||
/**
|
||||
* Herramientas para gestionar todos los trabajos con JWT, los tipos
|
||||
* definidos aquí deben de ser sufcientemente generales para usarse
|
||||
* en todos los proyectos.
|
||||
*
|
||||
* Las cabeceras de un token y la firma son standard para todos, pero
|
||||
* el body puede variar con contenido nuevo.
|
||||
*/
|
||||
|
||||
import { sign } from "node:crypto"
|
||||
|
||||
export type JWTHeader = {
|
||||
alg: string,
|
||||
typ: string,
|
||||
kid: string
|
||||
export interface IJWTService<T> {
|
||||
/* Obtener un token de auth sin comprobar nada */
|
||||
getNewAuthToken: () => Promise<JWTToken<T>>,
|
||||
/* Obtener un token valido -> sino refrescar -> sin pillar un token nuevo */
|
||||
getAccessToken: () => Promise<JWTToken<T>>,
|
||||
/* Intenta refrescar el token actual */
|
||||
tryRefreshToken: () => Promise<JWTToken<T>>,
|
||||
}
|
||||
|
||||
export type JWTHeader = {
|
||||
/* algoritmo de firma */
|
||||
alg: string,
|
||||
/* tipo de token */
|
||||
typ: string,
|
||||
/* key ID */
|
||||
kid?: string
|
||||
/* content type */
|
||||
cty?: string
|
||||
/**/
|
||||
iss?: string,
|
||||
sub?: string,
|
||||
aud?: string,
|
||||
jti?: string,
|
||||
iat?: number,
|
||||
exp?: number,
|
||||
}
|
||||
|
||||
/*
|
||||
* Define los campos basicos del payload de un token, en <T> se
|
||||
* añaden los campos espeficos de la comunicacion
|
||||
* */
|
||||
export type JWTPayload<T> = {
|
||||
/** (Issuer) Quién emitió el token */
|
||||
iss?: string;
|
||||
@@ -45,29 +79,33 @@ export type JWT<T> = {
|
||||
}
|
||||
|
||||
// todo pasar a la clase JWT
|
||||
function signJWT(args: {
|
||||
algorythm: "sha256" | string,
|
||||
data: string,
|
||||
privateKey: string
|
||||
}) {
|
||||
function signJWT(args: SignatureOptions) {
|
||||
const signature = sign(
|
||||
args.algorythm,
|
||||
Buffer.from(args.data),
|
||||
args.privateKey
|
||||
)
|
||||
return signature
|
||||
return signature.toString("base64url")
|
||||
}
|
||||
|
||||
export type SignatureOptions = {
|
||||
algorythm: "sha256" | string,
|
||||
data: string,
|
||||
privateKey: string,
|
||||
}
|
||||
|
||||
export class JWTToken<T> {
|
||||
|
||||
public rawToken: string
|
||||
private decodedPayload: JWTPayload<T> | undefined
|
||||
private signatureFunc = signJWT
|
||||
|
||||
constructor(
|
||||
token: string
|
||||
token: string,
|
||||
externalSignatureFunc?: (args: SignatureOptions) => string
|
||||
) {
|
||||
this.rawToken = token
|
||||
this.decodedPayload = this.decodePayload()
|
||||
if (externalSignatureFunc != undefined) this.signatureFunc = externalSignatureFunc
|
||||
}
|
||||
|
||||
public static fromParts<T>(args: {
|
||||
@@ -76,10 +114,13 @@ export class JWTToken<T> {
|
||||
sigantureData?: {
|
||||
privateKey: string,
|
||||
algorythm: string
|
||||
}
|
||||
},
|
||||
signatureFunc?: (args: SignatureOptions) => string
|
||||
}) {
|
||||
const strHeader = JSON.stringify(args.header)
|
||||
const base64Header = Buffer.from(strHeader).toString("base64url")
|
||||
const signatureFunc = args.signatureFunc ?? signJWT
|
||||
|
||||
let token = base64Header
|
||||
|
||||
if (args.payload != undefined) {
|
||||
@@ -89,11 +130,11 @@ export class JWTToken<T> {
|
||||
}
|
||||
|
||||
if (args.sigantureData != undefined) {
|
||||
const base64signature = signJWT({
|
||||
const base64signature = signatureFunc({
|
||||
algorythm: args.sigantureData.algorythm,
|
||||
privateKey: args.sigantureData.privateKey,
|
||||
data: token
|
||||
}).toString("base64url")
|
||||
data: token,
|
||||
})
|
||||
token += ("." + base64signature)
|
||||
}
|
||||
|
||||
@@ -116,4 +157,14 @@ export class JWTToken<T> {
|
||||
if (expirationDate * 1000 <= now.getTime()) return true
|
||||
return false
|
||||
}
|
||||
|
||||
public isValid() {
|
||||
throw new Error("No implementado")
|
||||
}
|
||||
|
||||
public verifySignature() {
|
||||
throw new Error("No implementado")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
96
packages/sim-shared/domain/operationsRepository.port.ts
Normal file
96
packages/sim-shared/domain/operationsRepository.port.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
import { Result } from "sim-shared/domain/Result.js";
|
||||
|
||||
export type StatusEnum = 'error' | 'finished' | 'noRequestId' | 'running' | 'noMassID';
|
||||
|
||||
export interface IOperationsRepository {
|
||||
createOperation(data: ObjeniousOperation): Promise<Result<string, ObjeniousOperation>>
|
||||
updateOperation(data: ObjeniousOperationChange): Promise<Result<string, ObjeniousOperation>>
|
||||
getPendingOperations(): Promise<Result<string, ObjeniousOperation[]>>
|
||||
}
|
||||
|
||||
export type ObjeniousOperation = {
|
||||
id?: number;
|
||||
operation: string;
|
||||
retry_count?: number;
|
||||
max_retry?: number;
|
||||
max_date_retry?: Date | null;
|
||||
iccids: string[];
|
||||
request_id?: string;
|
||||
mass_action_id?: string;
|
||||
end_date?: Date | null;
|
||||
error?: string | null;
|
||||
status: StatusEnum;
|
||||
objenious_status?: string;
|
||||
last_change_date?: string;
|
||||
}
|
||||
|
||||
export type ObjeniousOperationChange = {
|
||||
id?: number;
|
||||
operation_id: number;
|
||||
info?: string | null;
|
||||
error?: string | null;
|
||||
new_status: StatusEnum;
|
||||
previous_status?: StatusEnum;
|
||||
new_objenious_status?: string;
|
||||
previous_objenious_status?: string;
|
||||
new_request_id?: string;
|
||||
new_mass_action_id?: string;
|
||||
}
|
||||
|
||||
export namespace Objenious {
|
||||
export type Status = "En Cours" | "Terminé";
|
||||
export type Identifier = "IMSI" | "MSISDN" | "REFERENCE" | "ICCID" | "IMEI"
|
||||
export type ResponseGetRequestById = {
|
||||
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[]
|
||||
}
|
||||
|
||||
export type ActionType = "PREACTIVATION" | "PREACTIVATION_ACTIVATION" | "ACTIVATION" |
|
||||
"STATUS_CHANGE" | "ICCID_CHANGE" | "EUICC_NOTIFICATION"
|
||||
| "EUICC_AUDIT" | "MSISDN_CHANGE" | "ALARM_SETTING"
|
||||
| "ALARM_UNSETTING" | "CUSTOM_FIELDS_UPDATE" | "SERVICE_CHANGE"
|
||||
| "SEND_SMS" | "CHANGE_OFFER" | "PORT_IN"
|
||||
| "PORT_OUT" | "CHANGE_CUSTOMER_ACCOUNT" | "SIMCARD_TRANSFER"
|
||||
| "GEO_LOCATION" | "UPDATE_COMMITMENT" | "COACH_M2M"
|
||||
| "PP_RECHARGE" | "TERMINATION_DFE" | "DO_ORDER_PAYMENT"
|
||||
| "DO_TOP_UP" | "RADIUS_READ" | "RADIUS_UPDATE"
|
||||
| "RADIUS_SYNCHRONIZE" | "NETWORK_RESET" | "UPDATE_YORK_COMMUNITY"
|
||||
| "SUSPENSION" | "REACTIVATION" | "RESILIATION"
|
||||
| "SORTIE_TEST" | "VALIDATION_RESILIATION" | "REFUS_RESILIATION";
|
||||
|
||||
export type ParametersGetMassAction = {
|
||||
massActionId?: string,
|
||||
unitActionId?: string,
|
||||
createDateMin?: string,
|
||||
createDateMax?: string,
|
||||
dueDateMin?: string,
|
||||
dueDateMax?: string,
|
||||
endDateMin?: string,
|
||||
endDateMax?: string,
|
||||
"identifier.identifiers"?: string[],
|
||||
"identifier.identifierType"?: Identifier,
|
||||
actionTypes?: ActionType,
|
||||
errorCodes?: string[],
|
||||
pageNumber?: number,
|
||||
pageSize?: number
|
||||
}
|
||||
|
||||
export type ResponseGetMassAction = {
|
||||
id: number,
|
||||
loginCreator?: string,
|
||||
longinCancellor?: string,
|
||||
actionType: ActionType,
|
||||
dueDate?: string,
|
||||
targetActionNumber?: number,
|
||||
created: string,
|
||||
started?: string,
|
||||
ended?: string,
|
||||
status: string,
|
||||
info?: string
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import axios, { AxiosInstance } from "axios"
|
||||
import { JWTToken } from "../domain/JWT.js"
|
||||
import { IJWTService, JWTToken } from "../domain/JWT.js"
|
||||
|
||||
// Cambiar por IJWRGeneralService
|
||||
|
||||
export type JWTProvider<T> = {
|
||||
/** El servidor está solicitando un token nuevo o refrescando el actual*/
|
||||
@@ -13,11 +15,13 @@ export class HttpClient {
|
||||
|
||||
public client: AxiosInstance
|
||||
private jwtManager: JWTProvider<{}>
|
||||
private jwtService: IJWTService<any> | undefined;
|
||||
|
||||
constructor(args: {
|
||||
baseURL: string,
|
||||
headers: Object,
|
||||
jwtManager: JWTProvider<{}> // todo: asociar el tipo de token
|
||||
jwtManager: JWTProvider<{}> // todo: asociar el tipo de token,
|
||||
jwtService?: IJWTService<any>
|
||||
}) {
|
||||
this.client = axios.create({
|
||||
...args
|
||||
@@ -25,6 +29,7 @@ export class HttpClient {
|
||||
|
||||
this.jwtManager = args.jwtManager
|
||||
|
||||
if (args.jwtService != undefined) this.jwtService = args.jwtService
|
||||
|
||||
this.client.interceptors.request.use(
|
||||
async (config) => {
|
||||
|
||||
96
packages/sim-shared/infrastructure/OperationRepository.ts
Normal file
96
packages/sim-shared/infrastructure/OperationRepository.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
import { IOperationsRepository, ObjeniousOperation, ObjeniousOperationChange } from "sim-shared/domain/operationsRepository.port.js";
|
||||
import { Result } from "sim-shared/domain/Result.js";
|
||||
import { PgClient } from "sim-shared/infrastructure/PgClient.js";
|
||||
|
||||
export class OperationsRepository implements IOperationsRepository {
|
||||
|
||||
constructor(
|
||||
private readonly pgClient: PgClient
|
||||
) {
|
||||
}
|
||||
|
||||
async createOperation(data: ObjeniousOperation): Promise<Result<string, ObjeniousOperation>> {
|
||||
const query = `
|
||||
INSERT INTO objenious_operation (operation, iccids, status, max_retry, request_id)
|
||||
VALUES ($1, $2, $3, $4, $5)
|
||||
RETURNING *`;
|
||||
const iccids = JSON.stringify(data.iccids)
|
||||
const values = [data.operation, iccids, data.status, data.max_retry, data.request_id];
|
||||
const { rows } = await this.pgClient.query(query, values);
|
||||
return <Result<string, ObjeniousOperation>>{
|
||||
data: rows[0]
|
||||
}
|
||||
}
|
||||
|
||||
async updateOperation(data: ObjeniousOperationChange): Promise<Result<string, ObjeniousOperation>> {
|
||||
const client = await this.pgClient.connect();
|
||||
const {
|
||||
new_status,
|
||||
previous_status,
|
||||
error,
|
||||
new_request_id,
|
||||
new_mass_action_id,
|
||||
operation_id,
|
||||
new_objenious_status,
|
||||
previous_objenious_status
|
||||
} = data
|
||||
|
||||
try {
|
||||
await client.query('BEGIN');
|
||||
// 1. Actualizar objenious_operation (la operacion base)
|
||||
const updateParams = [operation_id, new_status, error, new_request_id, new_mass_action_id, new_objenious_status]
|
||||
const updateOpQuery = `
|
||||
UPDATE objenious_operation
|
||||
SET
|
||||
status = $2::status_enum,
|
||||
error = COALESCE($3,error),
|
||||
request_id = COALESCE($4, request_id),
|
||||
mass_action_id = COALESCE($5, mass_action_id),
|
||||
last_change_date = now(),
|
||||
end_date = CASE WHEN $2 IN ('finished') THEN now() ELSE end_date END,
|
||||
objenious_status = $6
|
||||
WHERE id = $1`;
|
||||
|
||||
const operation = await client.query<ObjeniousOperation>(
|
||||
updateOpQuery, updateParams)
|
||||
|
||||
// 2. Nuevo registro en objenious_operation_change (indica un cambio de estado)
|
||||
const insertChangeQuery = `
|
||||
INSERT INTO objenious_operation_change (operation_id, new_status, previous_status, error, new_request_id, new_mass_action_id, new_objenious_status, previous_objenious_status)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`;
|
||||
await client.query(insertChangeQuery, [operation_id, new_status, previous_status, error, new_request_id, new_mass_action_id, new_objenious_status, previous_objenious_status]);
|
||||
await client.query('COMMIT');
|
||||
|
||||
return <Result<string, ObjeniousOperation>>{
|
||||
data: operation.rows[0]
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Error añadiendo actualizacion de la operation", e)
|
||||
console.error("datos errorneos", data)
|
||||
await client.query('ROLLBACK');
|
||||
return <Result<string, ObjeniousOperation>>{
|
||||
data: undefined,
|
||||
error: e
|
||||
}
|
||||
} finally {
|
||||
client.release();
|
||||
}
|
||||
}
|
||||
|
||||
async getPendingOperations(): Promise<Result<string, ObjeniousOperation[]>> {
|
||||
// Aprovecha el índice 'pending_operations'
|
||||
const query = `SELECT * FROM objenious_operation WHERE end_date IS NULL ORDER BY start_date ASC`;
|
||||
try {
|
||||
const { rows } = await this.pgClient.query<ObjeniousOperation>(query);
|
||||
return {
|
||||
error: undefined,
|
||||
data: rows
|
||||
};
|
||||
} catch (e) {
|
||||
return {
|
||||
error: String(e),
|
||||
data: undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -57,4 +57,4 @@
|
||||
"tsx": "*",
|
||||
"vitest": "*"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,6 @@
|
||||
],
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
"../../packages/sim-shared/**/*.ts"
|
||||
"../../packages/sim-shared/**/*.ts",
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user