Procedimiento de activacion
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
CREATE EXTENSION pgcrypto; -- para los random bytes
|
CREATE EXTENSION pgcrypto; -- para los random bytes
|
||||||
-- 1. Función de generacion de uuidv7 copiada porque no esta en postgre 19
|
-- 1. Función de generacion de uuidv7 copiada de github porque no está en postgre 16
|
||||||
CREATE OR REPLACE FUNCTION
|
CREATE OR REPLACE FUNCTION
|
||||||
uuid_generate_v7()
|
uuid_generate_v7()
|
||||||
RETURNS
|
RETURNS
|
||||||
@@ -44,6 +44,8 @@ CREATE TABLE activation_codes (
|
|||||||
card_id UUID REFERENCES payment_cards(card_id), -- Una tarjeta, maximo un un código activo borrar o solo con expires_at?
|
card_id UUID REFERENCES payment_cards(card_id), -- Una tarjeta, maximo un un código activo borrar o solo con expires_at?
|
||||||
code_hash TEXT NOT NULL, -- Guardar el código hasheado, el original se imprime y se manda
|
code_hash TEXT NOT NULL, -- Guardar el código hasheado, el original se imprime y se manda
|
||||||
is_used BOOLEAN DEFAULT FALSE,
|
is_used BOOLEAN DEFAULT FALSE,
|
||||||
|
is_blocked BOOLEAN DEFAULT FALSE,
|
||||||
|
failed_attempts INT DEFAULT 0,
|
||||||
expires_at TIMESTAMPTZ NOT NULL,
|
expires_at TIMESTAMPTZ NOT NULL,
|
||||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||||
);
|
);
|
||||||
@@ -59,3 +61,27 @@ CREATE TABLE activation_logs (
|
|||||||
geo_location VARCHAR(100), -- Opcional: Ciudad/País derivado de IP
|
geo_location VARCHAR(100), -- Opcional: Ciudad/País derivado de IP
|
||||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION log_activation_attempt()
|
||||||
|
RETURNS TRIGGER AS $$
|
||||||
|
BEGIN
|
||||||
|
-- Si el intento falló (esto lo controlas desde tu lógica de UPDATE)
|
||||||
|
-- Hay que barajar si es viable hacer los updates de failed_attempts desde aquí
|
||||||
|
-- y nó desde código.
|
||||||
|
IF NEW.failed_attempts > OLD.failed_attempts THEN
|
||||||
|
INSERT INTO activation_logs (card_id, action_type, created_at)
|
||||||
|
VALUES (NEW.card_id, 'FAILED_ATTEMPT', NOW());
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- Bloqueo automático si llega al límite
|
||||||
|
IF NEW.failed_attempts >= 3 THEN
|
||||||
|
NEW.is_blocked := TRUE;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
RETURN NEW;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
CREATE TRIGGER trg_check_attempts
|
||||||
|
BEFORE UPDATE ON activation_codes
|
||||||
|
FOR EACH ROW EXECUTE FUNCTION log_activation_attempt();
|
||||||
|
|||||||
95
deployment/database/migrations/1.0.1_Proc-activate-card.sql
Normal file
95
deployment/database/migrations/1.0.1_Proc-activate-card.sql
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
-- He separado el procedimiento de activacion en otro archivo por comodidad de desarrollo
|
||||||
|
-- creo que es mas seguro meter la lógica de activaciones directamente en la bdd que en
|
||||||
|
-- un server node y asi evitamos problemas de consistencia entre versiones.
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION activate_payment_card(
|
||||||
|
p_card_id UUID, -- uuidv7 de la tarjeta
|
||||||
|
p_input_code TEXT, -- El código que mete el usuario, puede ser incorrecto en este punto
|
||||||
|
p_ip_address INET, -- Datos de origen de la activación
|
||||||
|
p_device_info JSONB -- Datos del dispositivo de origen
|
||||||
|
)
|
||||||
|
RETURNS TABLE (
|
||||||
|
success BOOLEAN,
|
||||||
|
message TEXT
|
||||||
|
) AS $$
|
||||||
|
DECLARE
|
||||||
|
v_code_hash TEXT;
|
||||||
|
v_is_used BOOLEAN;
|
||||||
|
v_is_blocked BOOLEAN;
|
||||||
|
v_expires_at TIMESTAMPTZ;
|
||||||
|
v_failed_attempts INT;
|
||||||
|
v_max_attempts CONSTANT INT := 3;
|
||||||
|
BEGIN
|
||||||
|
-- 1. Obtener datos del código y bloquear la fila para actualización (FOR UPDATE)
|
||||||
|
-- sería raro que 2 clientes intentasen modificar la tarjeta a la vez.
|
||||||
|
SELECT code_hash, is_used, is_blocked, expires_at, failed_attempts
|
||||||
|
INTO v_code_hash, v_is_used, v_is_blocked, v_expires_at, v_failed_attempts
|
||||||
|
FROM activation_codes
|
||||||
|
WHERE card_id = p_card_id
|
||||||
|
FOR UPDATE;
|
||||||
|
|
||||||
|
-- 2. Validaciones:
|
||||||
|
-- 2.1 Si no existe ninguna tarjeta con ese card_id
|
||||||
|
IF NOT FOUND THEN
|
||||||
|
RETURN QUERY SELECT FALSE, 'CARD_NOT_FOUND';
|
||||||
|
RETURN;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- 2.2 Si el código introducido ya ha sido usado
|
||||||
|
IF v_is_used THEN
|
||||||
|
RETURN QUERY SELECT FALSE, 'ALREADY_ACTIVATED';
|
||||||
|
RETURN;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- 2.3 Si el código ya ha sido bloqueado o se ha intentado demasiadas veces
|
||||||
|
IF v_is_blocked OR v_failed_attempts >= v_max_attempts THEN
|
||||||
|
RETURN QUERY SELECT FALSE, 'CODE_BLOCKED';
|
||||||
|
RETURN;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
|
||||||
|
IF v_expires_at < NOW() THEN
|
||||||
|
RETURN QUERY SELECT FALSE, 'CODE_EXPIRED';
|
||||||
|
RETURN;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- 3. Verificación del Hash del codigo que ha introducido el usuario
|
||||||
|
-- En la bdd guardo el hash, al procedimiento se introduce el codigo per-se
|
||||||
|
-- Si el código NO coincide:
|
||||||
|
IF v_code_hash != crypt(p_input_code, v_code_hash) THEN
|
||||||
|
UPDATE activation_codes
|
||||||
|
-- 3.1 Control de intentos
|
||||||
|
SET failed_attempts = failed_attempts + 1,
|
||||||
|
is_blocked = (failed_attempts + 1 >= v_max_attempts)
|
||||||
|
WHERE card_id = p_card_id;
|
||||||
|
-- 3.2 Se loguea el fallo
|
||||||
|
INSERT INTO activation_logs (card_id, action_type, ip_address, device_info)
|
||||||
|
VALUES (p_card_id, 'FAILED_ATTEMPT', p_ip_address, p_device_info);
|
||||||
|
|
||||||
|
RETURN QUERY SELECT FALSE, 'INVALID_CODE';
|
||||||
|
RETURN;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- 4. Si el código ES correcto:
|
||||||
|
-- Marcar código como usado
|
||||||
|
UPDATE activation_codes
|
||||||
|
SET is_used = TRUE
|
||||||
|
WHERE card_id = p_card_id;
|
||||||
|
|
||||||
|
-- Activar la tarjeta
|
||||||
|
UPDATE payment_cards
|
||||||
|
SET status = 'ACTIVE',
|
||||||
|
activated_at = NOW()
|
||||||
|
WHERE card_id = p_card_id;
|
||||||
|
|
||||||
|
-- Registrar éxito en auditoría
|
||||||
|
INSERT INTO activation_logs (card_id, action_type, ip_address, device_info)
|
||||||
|
VALUES (p_card_id, 'ACTIVATION_SUCCESS', p_ip_address, p_device_info);
|
||||||
|
|
||||||
|
RETURN QUERY SELECT TRUE, 'SUCCESS';
|
||||||
|
|
||||||
|
EXCEPTION WHEN OTHERS THEN
|
||||||
|
-- En caso de error inesperado, Postgres hace rollback automático
|
||||||
|
RETURN QUERY SELECT FALSE, 'INTERNAL_ERROR: ' || SQLERRM;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
132
package-lock.json
generated
132
package-lock.json
generated
@@ -9,6 +9,7 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"axios": "^1.13.6",
|
||||||
"db-migrate": "^0.11.14",
|
"db-migrate": "^0.11.14",
|
||||||
"dotenv": "^17.3.1",
|
"dotenv": "^17.3.1",
|
||||||
"express": "^5.2.1",
|
"express": "^5.2.1",
|
||||||
@@ -295,6 +296,23 @@
|
|||||||
"integrity": "sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==",
|
"integrity": "sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/asynckit": {
|
||||||
|
"version": "0.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||||
|
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/axios": {
|
||||||
|
"version": "1.13.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.6.tgz",
|
||||||
|
"integrity": "sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"follow-redirects": "^1.15.11",
|
||||||
|
"form-data": "^4.0.5",
|
||||||
|
"proxy-from-env": "^1.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/balanced-match": {
|
"node_modules/balanced-match": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||||
@@ -425,6 +443,18 @@
|
|||||||
"node": ">=0.1.90"
|
"node": ">=0.1.90"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/combined-stream": {
|
||||||
|
"version": "1.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||||
|
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"delayed-stream": "~1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/content-disposition": {
|
"node_modules/content-disposition": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz",
|
||||||
@@ -576,6 +606,15 @@
|
|||||||
"node": ">=4.0.0"
|
"node": ">=4.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/delayed-stream": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/depd": {
|
"node_modules/depd": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
||||||
@@ -672,6 +711,21 @@
|
|||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/es-set-tostringtag": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"es-errors": "^1.3.0",
|
||||||
|
"get-intrinsic": "^1.2.6",
|
||||||
|
"has-tostringtag": "^1.0.2",
|
||||||
|
"hasown": "^2.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/escape-html": {
|
"node_modules/escape-html": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
||||||
@@ -782,6 +836,63 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/follow-redirects": {
|
||||||
|
"version": "1.15.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
|
||||||
|
"integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"debug": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/form-data": {
|
||||||
|
"version": "4.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
|
||||||
|
"integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"asynckit": "^0.4.0",
|
||||||
|
"combined-stream": "^1.0.8",
|
||||||
|
"es-set-tostringtag": "^2.1.0",
|
||||||
|
"hasown": "^2.0.2",
|
||||||
|
"mime-types": "^2.1.12"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/form-data/node_modules/mime-db": {
|
||||||
|
"version": "1.52.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||||
|
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/form-data/node_modules/mime-types": {
|
||||||
|
"version": "2.1.35",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||||
|
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"mime-db": "1.52.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/forwarded": {
|
"node_modules/forwarded": {
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
|
||||||
@@ -879,6 +990,21 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/has-tostringtag": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"has-symbols": "^1.0.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/hasown": {
|
"node_modules/hasown": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||||
@@ -1425,6 +1551,12 @@
|
|||||||
"node": ">= 0.10"
|
"node": ">= 0.10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/proxy-from-env": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/qs": {
|
"node_modules/qs": {
|
||||||
"version": "6.15.0",
|
"version": "6.15.0",
|
||||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz",
|
"resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz",
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
"typescript": "^5.9.3"
|
"typescript": "^5.9.3"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"axios": "^1.13.6",
|
||||||
"db-migrate": "^0.11.14",
|
"db-migrate": "^0.11.14",
|
||||||
"dotenv": "^17.3.1",
|
"dotenv": "^17.3.1",
|
||||||
"express": "^5.2.1",
|
"express": "^5.2.1",
|
||||||
|
|||||||
28
src/adapters/HTTPClient.adapter.ts
Normal file
28
src/adapters/HTTPClient.adapter.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import axios, { type AxiosInstance } from "axios"
|
||||||
|
|
||||||
|
|
||||||
|
export class HttpClient {
|
||||||
|
|
||||||
|
public client: AxiosInstance
|
||||||
|
|
||||||
|
constructor(args: {
|
||||||
|
baseURL: string,
|
||||||
|
headers: Object,
|
||||||
|
}) {
|
||||||
|
this.client = axios.create({
|
||||||
|
...args
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
this.client.interceptors.request.use(
|
||||||
|
// Plantilla
|
||||||
|
)
|
||||||
|
|
||||||
|
this.client.interceptors.response.use(
|
||||||
|
// Plantilla
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
41
src/adapters/PGClient.adapter.ts
Normal file
41
src/adapters/PGClient.adapter.ts
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import { Pool, type QueryResult, type QueryResultRow } from "pg";
|
||||||
|
|
||||||
|
export class PgClient {
|
||||||
|
private pgPool: Pool;
|
||||||
|
|
||||||
|
constructor(args: {
|
||||||
|
pool: Pool
|
||||||
|
}) {
|
||||||
|
this.pgPool = args.pool
|
||||||
|
}
|
||||||
|
|
||||||
|
public connect() {
|
||||||
|
return this.pgPool.connect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 query<T extends QueryResultRow = any>(
|
||||||
|
text: string,
|
||||||
|
params?: any[]
|
||||||
|
): Promise<QueryResult<T>> {
|
||||||
|
return await this.pgPool.query(text, params);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Función para validar la conexión al inicio.
|
||||||
|
*/
|
||||||
|
public async checkDatabaseConnection(): Promise<void> {
|
||||||
|
const client = await this.pgPool.connect();
|
||||||
|
const res = await client.query('SELECT NOW()');
|
||||||
|
console.log(`[o] Database connected successfully at: ${res.rows[0].now}`);
|
||||||
|
client.release(); // Liberamos el cliente de vuelta al pool
|
||||||
|
return;
|
||||||
|
// Si algo falla se tiene que propagar
|
||||||
|
};
|
||||||
|
}
|
||||||
0
src/aplication/Nfc.controller.ts
Normal file
0
src/aplication/Nfc.controller.ts
Normal file
9
src/aplication/Nfc.usecases.ts
Normal file
9
src/aplication/Nfc.usecases.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import type { ServerContext } from "domain/ServerContext.js";
|
||||||
|
|
||||||
|
export class NfcUsecases {
|
||||||
|
constructor(
|
||||||
|
serverContext: ServerContext
|
||||||
|
) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,7 +6,7 @@ export const env = {
|
|||||||
HOST: process.env.HOST,
|
HOST: process.env.HOST,
|
||||||
|
|
||||||
POSTGRES_HOST: process.env.POSTGRES_HOST,
|
POSTGRES_HOST: process.env.POSTGRES_HOST,
|
||||||
POSTGRES_PORT: process.env.POSTGRES_PORT,
|
POSTGRES_PORT: Number(process.env.POSTGRES_PORT),
|
||||||
POSTGRES_USER: process.env.POSTGRES_USER,
|
POSTGRES_USER: process.env.POSTGRES_USER,
|
||||||
POSTGRES_PASSWORD: process.env.POSTGRES_PASSWORD
|
POSTGRES_PASSWORD: process.env.POSTGRES_PASSWORD
|
||||||
}
|
}
|
||||||
|
|||||||
3
src/config/httpclient.config.ts
Normal file
3
src/config/httpclient.config.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import { HttpClient } from "adapters/HTTPClient.adapter.js";
|
||||||
|
|
||||||
|
export const httpclient = new HttpClient({ baseURL: "", headers: {} })
|
||||||
20
src/config/pgclient.config.ts
Normal file
20
src/config/pgclient.config.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { Pool } from "pg";
|
||||||
|
import { PgClient } from "adapters/PGClient.adapter.js";
|
||||||
|
import { env } from "./env.config.js";
|
||||||
|
import assert from "node:assert";
|
||||||
|
|
||||||
|
const { POSTGRES_HOST, POSTGRES_PORT, POSTGRES_USER, POSTGRES_PASSWORD } = env
|
||||||
|
|
||||||
|
assert(POSTGRES_HOST != undefined)
|
||||||
|
assert(Number.isInteger(POSTGRES_PORT))
|
||||||
|
|
||||||
|
const pool = new Pool({
|
||||||
|
host: POSTGRES_HOST,
|
||||||
|
port: POSTGRES_PORT,
|
||||||
|
user: POSTGRES_USER,
|
||||||
|
password: POSTGRES_PASSWORD
|
||||||
|
})
|
||||||
|
|
||||||
|
export const pgClient = new PgClient({
|
||||||
|
pool
|
||||||
|
})
|
||||||
7
src/domain/ServerContext.ts
Normal file
7
src/domain/ServerContext.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import type { HttpClient } from "adapters/HTTPClient.adapter.js"
|
||||||
|
import type { PgClient } from "adapters/PGClient.adapter.js"
|
||||||
|
|
||||||
|
export type ServerContext = {
|
||||||
|
PostgresClient: PgClient,
|
||||||
|
HttpClient: HttpClient
|
||||||
|
}
|
||||||
0
src/infrastructure/Nfc.repository.ts
Normal file
0
src/infrastructure/Nfc.repository.ts
Normal file
11
src/main.ts
11
src/main.ts
@@ -1,13 +1,22 @@
|
|||||||
|
import assert from 'assert';
|
||||||
import express, { Router, type Request, type Response } from 'express';
|
import express, { Router, type Request, type Response } from 'express';
|
||||||
import { errorHandler } from './aplication/middleware.js';
|
import { errorHandler } from './aplication/middleware.js';
|
||||||
import { env } from './config/env.config.js';
|
import { env } from './config/env.config.js';
|
||||||
import assert from 'assert';
|
|
||||||
|
import type { ServerContext } from 'domain/ServerContext.js';
|
||||||
|
import { httpclient } from 'config/httpclient.config.js';
|
||||||
|
import { pgClient } from 'config/pgclient.config.js';
|
||||||
|
|
||||||
const PORT = env.PORT
|
const PORT = env.PORT
|
||||||
const HOSTNAME = env.HOST
|
const HOSTNAME = env.HOST
|
||||||
|
|
||||||
assert(HOSTNAME != undefined)
|
assert(HOSTNAME != undefined)
|
||||||
|
|
||||||
|
const serverContext: ServerContext = {
|
||||||
|
HttpClient: httpclient,
|
||||||
|
PostgresClient: pgClient
|
||||||
|
}
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.get("/health", (req: Request, res: Response) => {
|
router.get("/health", (req: Request, res: Response) => {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
// Visit https://aka.ms/tsconfig to read more about this file
|
// Visit https://aka.ms/tsconfig to read more about this file
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
// File Layout
|
// File Layout
|
||||||
"baseUrl": "./",
|
"baseUrl": "./src",
|
||||||
"rootDir": "./src",
|
"rootDir": "./src",
|
||||||
"outDir": "./dist",
|
"outDir": "./dist",
|
||||||
// Environment Settings
|
// Environment Settings
|
||||||
|
|||||||
Reference in New Issue
Block a user