Todas las operaciones posibles de SIM
This commit is contained in:
@@ -23,6 +23,7 @@
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^17.2.3",
|
||||
"express": "^5.2.1",
|
||||
"pg": "^8.18.0",
|
||||
"typescript": "^5.9.3",
|
||||
"vite": "^7.3.1",
|
||||
"vite-tsconfig-paths": "^6.0.5"
|
||||
@@ -32,6 +33,7 @@
|
||||
"@types/cors": "^2.8.19",
|
||||
"@types/express": "^5.0.6",
|
||||
"@types/node": "^25.0.3",
|
||||
"@types/pg": "^8.16.0",
|
||||
"@types/supertest": "^6.0.3",
|
||||
"concurrently": "^9.2.1",
|
||||
"prettier": "^3.7.4",
|
||||
@@ -43,4 +45,4 @@
|
||||
"imports": {
|
||||
"#shared/*": "./dist/packages/shared/*.js"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
4
packages/shared/domain/Result.ts
Normal file
4
packages/shared/domain/Result.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export type Result<E, D> = {
|
||||
error: E | undefined,
|
||||
data: D | undefined
|
||||
}
|
||||
25
packages/shared/infrastructure/DBClient.ts
Normal file
25
packages/shared/infrastructure/DBClient.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { Pool, QueryResult, QueryResultRow } from "pg";
|
||||
|
||||
export class postgreClient {
|
||||
private pgPool: Pool;
|
||||
|
||||
constructor(args: {
|
||||
pool: Pool
|
||||
}) {
|
||||
this.pgPool = args.pool
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper para ejecutar consultas con tipado fuerte.
|
||||
* T es el formato de la respusta.
|
||||
* @param text - La consulta SQL (ej. 'SELECT * FROM users WHERE id = $1')
|
||||
* @param params - Los valores para los placeholders $1, $2, etc.
|
||||
*/
|
||||
public async postgreQuery<T extends QueryResultRow = any>(
|
||||
text: string,
|
||||
params?: any[]
|
||||
): Promise<QueryResult<T>> {
|
||||
return await this.pgPool.query(text, params);
|
||||
};
|
||||
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import { SimUseCases } from "./Sim.usecases.js";
|
||||
import { SimEvents } from "#shared/domain/SimEvents.js";
|
||||
import { constants } from "node:buffer";
|
||||
import { constrainedMemory } from "node:process";
|
||||
import { Result } from "#shared/domain/Result.js";
|
||||
|
||||
/**
|
||||
* La clase usa generadores de funciones para mantener el contexto
|
||||
@@ -42,66 +43,108 @@ export class SimController {
|
||||
}
|
||||
}
|
||||
|
||||
public activateSim() {
|
||||
return async (msg: ConsumeMessage) => {
|
||||
if (!this.validateActivationMsg(msg)) {
|
||||
throw new Error("Error consumiendo el mensaje no es valido")
|
||||
}
|
||||
const msgData = this.decodeMsg(msg) as SimEvents.activation
|
||||
|
||||
if (msgData == undefined || msgData.payload == undefined) throw new Error("Mensaje invalido")
|
||||
console.log("Mensaje procesado", msgData?.toString())
|
||||
|
||||
// TODO: Añadir un validador del mensaje
|
||||
|
||||
const iccid = msgData.payload.iccid
|
||||
|
||||
const headers = msg.properties.headers
|
||||
console.log("HEADERS: ", headers)
|
||||
|
||||
try {
|
||||
// Caso de uso de activaciones
|
||||
const result = await this.useCases.activate({
|
||||
dueDate: this.genDueDate(2 * 60).toISOString(),
|
||||
identifier: {
|
||||
identifierType: "ICCID",
|
||||
identifiers: [iccid]
|
||||
}
|
||||
})()
|
||||
|
||||
console.log("Resultado de la peticion", result)
|
||||
|
||||
if (result.error == undefined) {
|
||||
console.log("Ack", msgData)
|
||||
await this.eventBus.ack(msg)
|
||||
} else {
|
||||
console.log("Nack", msgData)
|
||||
await this.eventBus.nack(msg)
|
||||
}
|
||||
} catch (e) {
|
||||
this.eventBus.nack(msg)
|
||||
/**
|
||||
* Metodo general de lanzar un caso de uso con un mensaje, si el caso de uso es exitoso
|
||||
* se ACK el mesaje, si hay algún error se NACK.
|
||||
* TODO:
|
||||
* - Se podrian hacer genericos los parametros
|
||||
*/
|
||||
private async tryUseCase<T extends any>(msg: ConsumeMessage, usecase: () => Promise<Result<string, T>>) {
|
||||
try {
|
||||
const result = await usecase()
|
||||
if (result.error == undefined) {
|
||||
await this.eventBus.ack(msg)
|
||||
return result
|
||||
} else {
|
||||
console.error("Error general procesando el caso de uso", result.error)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Error general procesando el caso de uso")
|
||||
this.eventBus.nack(msg)
|
||||
}
|
||||
}
|
||||
|
||||
public pauseSim() {
|
||||
public activateSim() {
|
||||
return async (msg: ConsumeMessage) => {
|
||||
if (!this.validateActivationMsg(msg)) {
|
||||
throw new Error("Error consumiendo el mensaje no es valido")
|
||||
let msgData;
|
||||
try {
|
||||
msgData = this.validateMsg(msg) as SimEvents.activation
|
||||
} catch (e) {
|
||||
throw new Error("Error consumiendo el mensaje no es valido" + String(e))
|
||||
}
|
||||
const msgData = this.decodeMsg(msg)
|
||||
if (msgData == undefined) Promise.reject("Mensaje invalido")
|
||||
|
||||
if (msgData == undefined || msgData.payload == undefined) throw new Error("Mensaje invalido")
|
||||
const iccid = msgData.payload.iccid
|
||||
|
||||
this.tryUseCase(msg, this.useCases.activate({
|
||||
dueDate: this.genDueDate(2 * 60).toISOString(),
|
||||
identifier: {
|
||||
identifierType: "ICCID",
|
||||
identifiers: [iccid]
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
public suspendSim() {
|
||||
return async (msg: ConsumeMessage) => {
|
||||
let msgData;
|
||||
try {
|
||||
msgData = this.validateMsg(msg) as SimEvents.pause
|
||||
} catch (e) {
|
||||
throw new Error("Error consumiendo el mensaje no es valido" + String(e))
|
||||
}
|
||||
|
||||
if (msgData == undefined) {
|
||||
return Promise.reject("Mensaje invalido")
|
||||
}
|
||||
|
||||
const iccid = msgData.payload.iccid
|
||||
this.tryUseCase(msg, this.useCases.suspend({
|
||||
dueDate: this.genDueDate(2 * 60).toISOString(),
|
||||
identifier: {
|
||||
identifierType: "ICCID",
|
||||
identifiers: [iccid]
|
||||
}
|
||||
}))
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public terminate() {
|
||||
return async (msg: ConsumeMessage) => {
|
||||
let msgData;
|
||||
try {
|
||||
msgData = this.validateMsg(msg) as SimEvents.pause
|
||||
} catch (e) {
|
||||
throw new Error("Error consumiendo el mensaje no es valido" + String(e))
|
||||
}
|
||||
|
||||
if (msgData == undefined) {
|
||||
return Promise.reject("Mensaje invalido")
|
||||
}
|
||||
const iccid = msgData.payload.iccid
|
||||
console.log("Mensaje procesado", String(msgData))
|
||||
this.tryUseCase(msg, this.useCases.terminate({
|
||||
dueDate: this.genDueDate(2 * 60).toISOString(),
|
||||
identifier: {
|
||||
identifierType: "ICCID",
|
||||
identifiers: [iccid]
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO:
|
||||
* - Loguear motivos de la no validacion
|
||||
* - Añadir validadores inyectables
|
||||
*/
|
||||
private validateActivationMsg(msg: ConsumeMessage | null) {
|
||||
private validateMsg(msg: ConsumeMessage | null) {
|
||||
if (msg == undefined) return false;
|
||||
return true;
|
||||
const msgData = this.decodeMsg(msg) as SimEvents.general
|
||||
if (msgData == undefined || msgData.payload == undefined) throw new Error("Mensaje invalido")
|
||||
return msgData;
|
||||
}
|
||||
|
||||
private genDueDate(secondsDue: number) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ActivationData } from "#domain/DTOs/objeniousapi.js"
|
||||
import { HttpClient } from "#shared/infrastructure/HTTPClient.js"
|
||||
import { AxiosError } from "axios"
|
||||
import { error } from "node:console"
|
||||
import { Result } from "#shared/domain/Result"
|
||||
|
||||
// TODO: Pasar a un archivo de DTOs
|
||||
|
||||
@@ -16,8 +16,10 @@ export class SimUseCases {
|
||||
this.httpClient = args.httpClient
|
||||
}
|
||||
|
||||
public activate(activationData: ActivationData) {
|
||||
const OPERATION_URL = "/actions/preactivateLine"
|
||||
|
||||
|
||||
public activate(activationData: ActivationData): () => Promise<Result<string, boolean>> {
|
||||
const OPERATION_URL = "/actions/activateLine"
|
||||
return async () => {
|
||||
const req = this.httpClient.client.post(OPERATION_URL, {
|
||||
...activationData
|
||||
@@ -30,37 +32,97 @@ export class SimUseCases {
|
||||
|
||||
if (response.status == 200) {
|
||||
console.log("Activacion con exito", response.data.response)
|
||||
return {
|
||||
return <Result<string, boolean>>{
|
||||
error: undefined,
|
||||
ok: true
|
||||
data: true
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
error: response.status
|
||||
error: String(response.status),
|
||||
data: undefined
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("[Sim.usecase] Error activando ", (error as AxiosError).response?.status)
|
||||
return {
|
||||
error: "Error general de la petiacion"
|
||||
error: "Error general de la petiacion",
|
||||
data: undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public pause(activationData: ActivationData) {
|
||||
const OPERATION_URL = "/actions/pause"
|
||||
public reactivate(pauseData: ActivationData): () => Promise<Result<string, boolean>> {
|
||||
const OPERATION_URL = "/actions/reactivateLine"
|
||||
return async () => {
|
||||
const req = this.httpClient.client.post("/actions/pause", {
|
||||
...activationData
|
||||
const req = this.httpClient.client.post(OPERATION_URL, {
|
||||
...pauseData
|
||||
})
|
||||
|
||||
try {
|
||||
const e = await req
|
||||
console.log("Sim pausada con exito", e.data)
|
||||
return <Result<string, boolean>>{
|
||||
error: undefined,
|
||||
data: true
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error pausa", error)
|
||||
return <Result<string, boolean>>{
|
||||
error: "Error reactivando la sim" + pauseData.identifier,
|
||||
data: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public suspend(suspendData: ActivationData): () => Promise<Result<string, boolean>> {
|
||||
const OPERATION_URL = "/actions/suspendLine"
|
||||
return async () => {
|
||||
const req = this.httpClient.client.post(OPERATION_URL, {
|
||||
...suspendData
|
||||
})
|
||||
|
||||
try {
|
||||
const e = await req
|
||||
console.log("Sim pausada con exito", e.data)
|
||||
return <Result<string, boolean>>{
|
||||
error: undefined,
|
||||
data: true
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error pausa", error)
|
||||
return {
|
||||
error: "Error general pausando/suspendiendo la sim" + suspendData.identifier,
|
||||
data: undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public terminate(terminationData: ActivationData): () => Promise<Result<string, boolean>> {
|
||||
const OPERATION_URL = "/actions/terminateLine"
|
||||
return async () => {
|
||||
const req = this.httpClient.client.post(OPERATION_URL, {
|
||||
...terminationData
|
||||
})
|
||||
|
||||
try {
|
||||
const e = await req
|
||||
console.log("Sim pausada con exito", e.data)
|
||||
return <Result<string, boolean>>{
|
||||
error: undefined,
|
||||
data: true
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error pausa", error)
|
||||
return <Result<string, boolean>>{
|
||||
error: "Error cancelando/terminate la sim" + terminationData.identifier,
|
||||
data: undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
13
packages/sim-consumidor-objenious/config/postgreConfig.ts
Normal file
13
packages/sim-consumidor-objenious/config/postgreConfig.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Pool, QueryResult } from 'pg';
|
||||
import { env } from './env';
|
||||
|
||||
// Configuracion de la conexion a la BDD, deberia ser la
|
||||
// Misma para todos los servicios pero hasta que se unifique todo
|
||||
// se hace una por servicio.
|
||||
export const pgPool = new Pool({
|
||||
user: env.POSTGRES_USER,
|
||||
host: env.POSTGRES_HOST,
|
||||
database: env.POSTGRES_DATABASE,
|
||||
password: env.POSTGRES_PASSWORD,
|
||||
port: Number(env.POSTGRES_PORT) || 5432,
|
||||
});
|
||||
138
yarn.lock
138
yarn.lock
@@ -587,6 +587,17 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/pg@npm:^8.16.0":
|
||||
version: 8.16.0
|
||||
resolution: "@types/pg@npm:8.16.0"
|
||||
dependencies:
|
||||
"@types/node": "npm:*"
|
||||
pg-protocol: "npm:*"
|
||||
pg-types: "npm:^2.2.0"
|
||||
checksum: 10/c03346fbe87728a237f30a3d0a436b86ede88e1dc471782bf679a4d74d67ee2a96f953e7c04d73841d21b9db43a5bf2ccdf2cd4c75450ea57efd947049809b3a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/qs@npm:*":
|
||||
version: 6.14.0
|
||||
resolution: "@types/qs@npm:6.14.0"
|
||||
@@ -2197,6 +2208,87 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"pg-cloudflare@npm:^1.3.0":
|
||||
version: 1.3.0
|
||||
resolution: "pg-cloudflare@npm:1.3.0"
|
||||
checksum: 10/04007ebbd314bdc8e5983d5d0f71a942f1915d2312ed84894d55475010559665bcbb9941d55523d36be6386cd83490dffb5b1ea98ffbbc82a140ef5dfb68ab8d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"pg-connection-string@npm:^2.11.0":
|
||||
version: 2.11.0
|
||||
resolution: "pg-connection-string@npm:2.11.0"
|
||||
checksum: 10/0333bb1b7ddeac6fa5262920f82a983222c600d21ef14fdc5254b0d3cbb1763030d20c1e8e3c20fa767a6eda8f4b4773550954c06f3e072e5288b6fa9e9cae13
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"pg-int8@npm:1.0.1":
|
||||
version: 1.0.1
|
||||
resolution: "pg-int8@npm:1.0.1"
|
||||
checksum: 10/a1e3a05a69005ddb73e5f324b6b4e689868a447c5fa280b44cd4d04e6916a344ac289e0b8d2695d66e8e89a7fba023affb9e0e94778770ada5df43f003d664c9
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"pg-pool@npm:^3.11.0":
|
||||
version: 3.11.0
|
||||
resolution: "pg-pool@npm:3.11.0"
|
||||
peerDependencies:
|
||||
pg: ">=8.0"
|
||||
checksum: 10/51c77d99f17cf791333467352df8326e0f70f9c517eada65a5e7819b2422f6e655e52319f5406eb578504442ae5f399b6e1d023e41d0c199aaf82879a890db6d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"pg-protocol@npm:*, pg-protocol@npm:^1.11.0":
|
||||
version: 1.11.0
|
||||
resolution: "pg-protocol@npm:1.11.0"
|
||||
checksum: 10/a70b1b4a3fc5b1be80dfdd65c829a149b8bd9df7488f9c47e0b51c9413aec5eb6da0a9ae9812891d74cd9f2ee90c0e391984a41b64603e7375fcbb9e07070b08
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"pg-types@npm:2.2.0, pg-types@npm:^2.2.0":
|
||||
version: 2.2.0
|
||||
resolution: "pg-types@npm:2.2.0"
|
||||
dependencies:
|
||||
pg-int8: "npm:1.0.1"
|
||||
postgres-array: "npm:~2.0.0"
|
||||
postgres-bytea: "npm:~1.0.0"
|
||||
postgres-date: "npm:~1.0.4"
|
||||
postgres-interval: "npm:^1.1.0"
|
||||
checksum: 10/87a84d4baa91378d3a3da6076c69685eb905d1087bf73525ae1ba84b291b9dd8738c6716b333d8eac6cec91bf087237adc3e9281727365e9cbab0d9d072778b1
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"pg@npm:^8.18.0":
|
||||
version: 8.18.0
|
||||
resolution: "pg@npm:8.18.0"
|
||||
dependencies:
|
||||
pg-cloudflare: "npm:^1.3.0"
|
||||
pg-connection-string: "npm:^2.11.0"
|
||||
pg-pool: "npm:^3.11.0"
|
||||
pg-protocol: "npm:^1.11.0"
|
||||
pg-types: "npm:2.2.0"
|
||||
pgpass: "npm:1.0.5"
|
||||
peerDependencies:
|
||||
pg-native: ">=3.0.1"
|
||||
dependenciesMeta:
|
||||
pg-cloudflare:
|
||||
optional: true
|
||||
peerDependenciesMeta:
|
||||
pg-native:
|
||||
optional: true
|
||||
checksum: 10/91c622f179f60df08ab7aa9b05a890567ea47f2d7984377b64e88e1eba1c42787324b7fc5ff00e109a757f3329dc4b57c73502603ae2765d1827b2082abbdcfa
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"pgpass@npm:1.0.5":
|
||||
version: 1.0.5
|
||||
resolution: "pgpass@npm:1.0.5"
|
||||
dependencies:
|
||||
split2: "npm:^4.1.0"
|
||||
checksum: 10/0a6f3bf76e36bdb3c20a7e8033140c732767bba7e81f845f7489fc3123a2bd6e3b8e704f08cba86b117435414b5d2422e20ba9d5f2efb6f0c75c9efca73e8e87
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"picocolors@npm:^1.1.1":
|
||||
version: 1.1.1
|
||||
resolution: "picocolors@npm:1.1.1"
|
||||
@@ -2238,6 +2330,36 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"postgres-array@npm:~2.0.0":
|
||||
version: 2.0.0
|
||||
resolution: "postgres-array@npm:2.0.0"
|
||||
checksum: 10/aff99e79714d1271fe942fec4ffa2007b755e7e7dc3d2feecae3f1ceecb86fd3637c8138037fc3d9e7ec369231eeb136843c0b25927bf1ce295245a40ef849b4
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"postgres-bytea@npm:~1.0.0":
|
||||
version: 1.0.1
|
||||
resolution: "postgres-bytea@npm:1.0.1"
|
||||
checksum: 10/fc5fa49f59ac1f0eba841db55bd6b6c2232d1575d1734311e2097a2d5fd8b58e1239cbd64eeaf0b6752268fe7d2819e002bf90b0afd333be9f2b9d157d2cd7e7
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"postgres-date@npm:~1.0.4":
|
||||
version: 1.0.7
|
||||
resolution: "postgres-date@npm:1.0.7"
|
||||
checksum: 10/571ef45bec4551bb5d608c31b79987d7a895141f7d6c7b82e936a52d23d97474c770c6143e5cf8936c1cdc8b0dfd95e79f8136bf56a90164182a60f242c19f2b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"postgres-interval@npm:^1.1.0":
|
||||
version: 1.2.0
|
||||
resolution: "postgres-interval@npm:1.2.0"
|
||||
dependencies:
|
||||
xtend: "npm:^4.0.0"
|
||||
checksum: 10/746b71f93805ae33b03528e429dc624706d1f9b20ee81bf743263efb6a0cd79ae02a642a8a480dbc0f09547b4315ab7df6ce5ec0be77ed700bac42730f5c76b2
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"prettier@npm:*, prettier@npm:^3.7.4":
|
||||
version: 3.7.4
|
||||
resolution: "prettier@npm:3.7.4"
|
||||
@@ -2695,6 +2817,7 @@ __metadata:
|
||||
"@types/cors": "npm:^2.8.19"
|
||||
"@types/express": "npm:^5.0.6"
|
||||
"@types/node": "npm:^25.0.3"
|
||||
"@types/pg": "npm:^8.16.0"
|
||||
"@types/supertest": "npm:^6.0.3"
|
||||
amqp-connection-manager: "npm:^5.0.0"
|
||||
amqplib: "npm:^0.10.9"
|
||||
@@ -2703,6 +2826,7 @@ __metadata:
|
||||
cors: "npm:^2.8.5"
|
||||
dotenv: "npm:^17.2.3"
|
||||
express: "npm:^5.2.1"
|
||||
pg: "npm:^8.18.0"
|
||||
prettier: "npm:^3.7.4"
|
||||
supertest: "npm:^7.1.4"
|
||||
tsc-alias: "npm:^1.8.16"
|
||||
@@ -2801,6 +2925,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"split2@npm:^4.1.0":
|
||||
version: 4.2.0
|
||||
resolution: "split2@npm:4.2.0"
|
||||
checksum: 10/09bbefc11bcf03f044584c9764cd31a252d8e52cea29130950b26161287c11f519807c5e54bd9e5804c713b79c02cefe6a98f4688630993386be353e03f534ab
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ssri@npm:^13.0.0":
|
||||
version: 13.0.0
|
||||
resolution: "ssri@npm:13.0.0"
|
||||
@@ -3268,6 +3399,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"xtend@npm:^4.0.0":
|
||||
version: 4.0.2
|
||||
resolution: "xtend@npm:4.0.2"
|
||||
checksum: 10/ac5dfa738b21f6e7f0dd6e65e1b3155036d68104e67e5d5d1bde74892e327d7e5636a076f625599dc394330a731861e87343ff184b0047fef1360a7ec0a5a36a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"y18n@npm:^5.0.5":
|
||||
version: 5.0.8
|
||||
resolution: "y18n@npm:5.0.8"
|
||||
|
||||
Reference in New Issue
Block a user