Repositorio de codigos de activacion + test

This commit is contained in:
2026-03-10 12:38:55 +01:00
parent 5e76c4f48c
commit 07d49aab29
5 changed files with 134 additions and 1134 deletions

View File

@@ -29,17 +29,6 @@ AS $$
$$ $$
; ;
-- 2. Tabla de Tarjetas
-- Posiblemente haya que mantener solo el id e ignorar el PAN
CREATE TABLE payment_cards (
card_id UUID PRIMARY KEY DEFAULT uuid_generate_v7(),
user_id UUID REFERENCES users(user_id),
pan_last_four VARCHAR(4) NOT NULL,
card_holder_name VARCHAR(100) NOT NULL,
status VARCHAR(20) DEFAULT 'PENDING_ACTIVATION', -- PENDING, ACTIVE, BLOCKED, EXPIRED
created_at TIMESTAMPTZ DEFAULT NOW(),
activated_at TIMESTAMPTZ
);
-- 3. Tabla de Códigos de Activación -- 3. Tabla de Códigos de Activación
-- No creo que vaya a recibir confirmación de activación porque es de otro proyecto, -- No creo que vaya a recibir confirmación de activación porque es de otro proyecto,
@@ -47,8 +36,8 @@ CREATE TABLE payment_cards (
-- El algoritmo de hash es sha256 -- El algoritmo de hash es sha256
CREATE TABLE activation_codes ( CREATE TABLE activation_codes (
code_id UUID PRIMARY KEY DEFAULT uuid_generate_v7(), code_id UUID PRIMARY KEY DEFAULT uuid_generate_v7(),
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 NOT NULL, -- ID EXTERNO
code_plain TEXT NOT NULL, -- code_plain TEXT NOT NULL,
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, is_blocked BOOLEAN DEFAULT FALSE,
@@ -61,7 +50,7 @@ CREATE TABLE activation_codes (
-- Lo mismo, muy sobredimensionado, no creo que haya falta en este punto -- Lo mismo, muy sobredimensionado, no creo que haya falta en este punto
CREATE TABLE activation_logs ( CREATE TABLE activation_logs (
log_id UUID PRIMARY KEY DEFAULT uuid_generate_v7(), log_id UUID PRIMARY KEY DEFAULT uuid_generate_v7(),
card_id UUID REFERENCES payment_cards(card_id), card_id UUID NOT NULL, -- ID EXTERNO
code_id UUID REFERENCES activation_codes(code_id), code_id UUID REFERENCES activation_codes(code_id),
action_type VARCHAR(50) NOT NULL, -- TODO: CREAR ENUM'GENERATED', 'ATTEMPT_FAILED', 'ACTIVATED' action_type VARCHAR(50) NOT NULL, -- TODO: CREAR ENUM'GENERATED', 'ATTEMPT_FAILED', 'ACTIVATED'
ip_address INET, ip_address INET,

1226
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -5,7 +5,7 @@
"main": "index.js", "main": "index.js",
"type": "module", "type": "module",
"scripts": { "scripts": {
"test": "tsx --test", "test": "tsx --test --watch",
"dev": "tsx src/main.ts", "dev": "tsx src/main.ts",
"build": "tsc", "build": "tsc",
"build:esbuild": "esbuild --bundle src/main.ts --outdir=dist --platform=node --format=esm --packages=external", "build:esbuild": "esbuild --bundle src/main.ts --outdir=dist --platform=node --format=esm --packages=external",
@@ -24,10 +24,11 @@
"typescript": "^5.9.3" "typescript": "^5.9.3"
}, },
"dependencies": { "dependencies": {
"@sf-alvar/db-migrate": "^1.0.3",
"axios": "^1.13.6", "axios": "^1.13.6",
"db-migrate": "^0.11.14",
"dotenv": "^17.3.1", "dotenv": "^17.3.1",
"express": "^5.2.1", "express": "^5.2.1",
"pg": "^8.20.0" "pg": "^8.20.0",
"uuidv7": "^1.1.0"
} }
} }

View File

@@ -4,6 +4,7 @@ import { httpclient } from "config/httpclient.config.js";
import { pgClient } from "config/pgclient.config.js"; import { pgClient } from "config/pgclient.config.js";
import { NfcRepository } from "./Nfc.repository.js"; import { NfcRepository } from "./Nfc.repository.js";
import type { ServerContext } from "domain/ServerContext.js"; import type { ServerContext } from "domain/ServerContext.js";
import { uuidv7 } from "uuidv7";
describe("NfcRepository Integration Tests", () => { describe("NfcRepository Integration Tests", () => {
const serverContext: ServerContext = { const serverContext: ServerContext = {
@@ -12,7 +13,7 @@ describe("NfcRepository Integration Tests", () => {
}; };
const repo = new NfcRepository(serverContext); const repo = new NfcRepository(serverContext);
const testCardId = "test-card-" + Date.now(); const testCardId = uuidv7()
const testCode = "12345678"; const testCode = "12345678";
// Clean up before and after tests to ensure isolation // Clean up before and after tests to ensure isolation
@@ -61,12 +62,14 @@ describe("NfcRepository Integration Tests", () => {
}); });
test("findActivationCodes should return empty array if card has no codes", async () => { test("findActivationCodes should return empty array if card has no codes", async () => {
const result = await repo.findActivationCodes("non-existent-card"); const nonExistentCard = uuidv7()
const result = await repo.findActivationCodes(nonExistentCard);
if (result.error) { if (result.error) {
assert.fail(`findActivationCodes failed: ${result.error}`); assert.fail(`findActivationCodes failed: ${result.error}`);
} }
assert.ok(result.data, "Data should be returned"); assert.ok(result.data, "Data should be returned");
assert.strictEqual(result.data.length, 0); assert.strictEqual(result.data.length, 0);
}); });

View File

@@ -1,6 +1,7 @@
import type { ActivationCodeDTO } from "domain/NfcRegistry.js"; import type { ActivationCodeDTO } from "domain/NfcRegistry.js";
import type { Result } from "domain/Result.js"; import type { Result } from "domain/Result.js";
import type { ServerContext } from "domain/ServerContext.js"; import type { ServerContext } from "domain/ServerContext.js";
import { constrainedMemory } from "node:process";
// TODO: Pasar a Result<E,T> // TODO: Pasar a Result<E,T>
@@ -26,6 +27,7 @@ export class NfcRepository {
data: codeResult.rows data: codeResult.rows
} }
} catch (e) { } catch (e) {
console.error(e)
return { return {
error: e as string error: e as string
} }
@@ -33,6 +35,8 @@ export class NfcRepository {
} }
public async createActivationCode(args: { cardId: string, code: string }): Promise<Result<string, ActivationCodeDTO>> { public async createActivationCode(args: { cardId: string, code: string }): Promise<Result<string, ActivationCodeDTO>> {
const conn = await this.ctx.PostgresClient.connect()
await conn.query("BEGIN")
const query = ` const query = `
INSERT INTO activation_codes( INSERT INTO activation_codes(
card_id, card_id,
@@ -44,18 +48,19 @@ export class NfcRepository {
$2, $2,
digest($2,'sha256') digest($2,'sha256')
) )
RETURNING( RETURNING
code_id,card_id,code_plain,code_hash,is_used,is_blocked,failed_attempts,created_at,expires_at code_id,card_id,code_plain,code_hash,is_used,is_blocked,failed_attempts,created_at,expires_at
)
` `
const values = [args.cardId, args.code] const values = [args.cardId, args.code]
try { try {
const insertResult = await this.ctx.PostgresClient.query<ActivationCodeDTO>(query, values) const insertResult = await conn.query<ActivationCodeDTO>(query, values)
await conn.query("COMMIT")
return { return {
data: insertResult.rows[0]! data: insertResult.rows[0]!
} }
} catch (e) { } catch (e) {
console.error("Error createActivationCode: ", e) console.error("Error createActivationCode: ", e)
await conn.query("ROLLBACK")
return { return {
error: e as string error: e as string
} }