diff --git a/deployment/database/migrations/1.1.2_AlterCardIdToVarchar.sql b/deployment/database/migrations/1.1.2_AlterCardIdToVarchar.sql new file mode 100644 index 0000000..4dc48be --- /dev/null +++ b/deployment/database/migrations/1.1.2_AlterCardIdToVarchar.sql @@ -0,0 +1,101 @@ +-- Alter the activation_codes table to accept a 9 digit string for card_id instead of a UUID +ALTER TABLE activation_codes ALTER COLUMN card_id TYPE VARCHAR(9); + +-- Alter the activation_logs table to accept a 9 digit string for card_id instead of a UUID +ALTER TABLE activation_logs ALTER COLUMN card_id TYPE VARCHAR(9); + +-- Drop the old procedure which accepted UUID +DROP FUNCTION IF EXISTS activate_payment_card(UUID, TEXT, INET, JSONB); + +-- Recreate it to accept VARCHAR(9) +CREATE OR REPLACE FUNCTION activate_payment_card( + p_card_id VARCHAR(9), -- 9 digit string 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; + + -- 2.4 Si el código es demasiado viejo + 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; diff --git a/deployment/develop/docker/Dockerfile b/deployment/develop/docker/Dockerfile index 541390b..419e677 100644 --- a/deployment/develop/docker/Dockerfile +++ b/deployment/develop/docker/Dockerfile @@ -7,14 +7,11 @@ COPY ./src ./src COPY tsconfig.json ./ -COPY ./start.sh ./ - COPY ./deployment/database/migrations ./deployment/database/migrations - +COPY ./deployment/develop/start.sh ./ RUN npm config set @sf-alvar:registry https://git.savefamilygps.net/api/packages/SaveFamily/npm/ RUN npm install -RUN chmod +x start.sh EXPOSE ${PORT} CMD [ "npm" ,"run", "build-start" ] diff --git a/deployment/develop/docker/docker-compose.yaml b/deployment/develop/docker/docker-compose.yaml index 87b20b2..8c9a51f 100644 --- a/deployment/develop/docker/docker-compose.yaml +++ b/deployment/develop/docker/docker-compose.yaml @@ -28,7 +28,7 @@ services: test: [ "CMD-SHELL", - "wget -q --spider http://localhost:${PORT:-3000}/health || exit 1", + "wget -q --spider http://127.0.0.1:${PORT:-3000}/health || exit 1", ] interval: 10s timeout: 5s diff --git a/deployment/local/docker/Dockerfile.local b/deployment/local/docker/Dockerfile.local index 760632d..06aaaff 100644 --- a/deployment/local/docker/Dockerfile.local +++ b/deployment/local/docker/Dockerfile.local @@ -1,9 +1,7 @@ # Stage base para coordinar las fases de build y ejecucion FROM node:22-alpine -# Hace falta para la herramienta de migraciones, cuando se publique se -# sustituira por el paquete de npm -#RUN apk --no-cache add git -WORKDIR /app + +WORKDIR /home/node/app COPY ./package.json ./package-lock.json ./ COPY ./src ./src diff --git a/deployment/local/docker/docker-compose.yaml b/deployment/local/docker/docker-compose.yaml index 363a840..57f24ea 100644 --- a/deployment/local/docker/docker-compose.yaml +++ b/deployment/local/docker/docker-compose.yaml @@ -29,7 +29,7 @@ services: test: [ "CMD-SHELL", - 'node -e "fetch(''http://localhost:'' + (process.env.PORT || 3000) + ''/health'').then(r => { if (!r.ok) process.exit(1) }).catch(() => process.exit(1))"', + "wget -q --spider http://127.0.0.1:${PORT:-3000}/health || exit 1", ] interval: 10s timeout: 5s diff --git a/docs/nfc-server/environments/Local.yml b/docs/nfc-server/environments/Local.yml index 95e8b09..90053ea 100644 --- a/docs/nfc-server/environments/Local.yml +++ b/docs/nfc-server/environments/Local.yml @@ -1,5 +1,5 @@ -name: Local development server +name: Local color: "#2E8A54" variables: - name: baseUrl - value: http://localhost:3000 + value: http://127.0.0.1:3000 diff --git a/package-lock.json b/package-lock.json index 8655c54..b28e72a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "@sf-alvar/db-migrate": "^1.0.4", + "@sf-alvar/db-migrate": "^1.0.6", "axios": "^1.13.6", "cors": "^2.8.6", "dotenv": "^17.3.1", @@ -2616,9 +2616,9 @@ "license": "BSD-3-Clause" }, "node_modules/@sf-alvar/db-migrate": { - "version": "1.0.4", - "resolved": "https://git.savefamilygps.net/api/packages/SaveFamily/npm/%40sf-alvar%2Fdb-migrate/-/1.0.4/db-migrate-1.0.4.tgz", - "integrity": "sha512-Ac18zzpvSEyjG+ESYZmuqmgCMuLbr4uuBoC8Xjm2rv62fTh1fxe7jqG79agvydz7QMQ1ZL5fC83htpm7zUTV1w==", + "version": "1.0.6", + "resolved": "https://git.savefamilygps.net/api/packages/SaveFamily/npm/%40sf-alvar%2Fdb-migrate/-/1.0.6/db-migrate-1.0.6.tgz", + "integrity": "sha512-TvvbF30Y1pgi6RMejXnc68AmzIXH/OBzCyUNWzwQYY21islPmXjI8gtG6C2qq7We2s4h35hvPe208vRqQnQU5g==", "license": "ISC", "dependencies": { "pg": "^8.18.0", diff --git a/package.json b/package.json index a1eeca2..219edd4 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "build": "tsc", "build:esbuild": "esbuild --bundle src/main.ts --outdir=dist --platform=node --format=esm --packages=external", "start": "node dist/main.js", - "migrate": "db-migrate -e .env -m ./deployment/database/migrations/ -t 99.0.0", + "migrate": "node ./node_modules/@sf-alvar/db-migrate/lib/index.js -e .env -m ./deployment/database/migrations/ -t 99.0.0", "build-start": "npm run build:esbuild && npm run start" }, "author": "", @@ -28,7 +28,7 @@ "typescript": "^5.9.3" }, "dependencies": { - "@sf-alvar/db-migrate": "^1.0.4", + "@sf-alvar/db-migrate": "^1.0.6", "axios": "^1.13.6", "cors": "^2.8.6", "dotenv": "^17.3.1", @@ -36,4 +36,4 @@ "pg": "^8.20.0", "uuidv7": "^1.1.0" } -} +} \ No newline at end of file diff --git a/src/aplication/Nfc.controller.ts b/src/aplication/Nfc.controller.ts index c99ec45..3dd9d2e 100644 --- a/src/aplication/Nfc.controller.ts +++ b/src/aplication/Nfc.controller.ts @@ -17,6 +17,7 @@ export class NfcController { public generateActivationTag() { return async (req: Request, res: Response) => { + console.log(("REQ!", req)) const body = req.body const validate = baseValidator.validate(body) diff --git a/src/aplication/validators.ts b/src/aplication/validators.ts index 508825d..8907b84 100644 --- a/src/aplication/validators.ts +++ b/src/aplication/validators.ts @@ -1,4 +1,3 @@ -import { UUID, uuidv7 } from "uuidv7"; import { BodyValidator, type Validator } from "./BodyValidator.js"; const cardIdExists: Validator<{ card_id?: string }> = { @@ -7,10 +6,10 @@ const cardIdExists: Validator<{ card_id?: string }> = { errorMsg: "El campo card_id esta undefined" } -const cardIdIsUUIDv7: Validator<{ card_id: string }> = { +const cardIdIsToken: Validator<{ card_id: string }> = { field: "card_id", - validationFunc: (body) => UUID.parse(body.card_id) != undefined, - errorMsg: "El campo card_id no es un uuidv7" + validationFunc: (body) => typeof body.card_id === 'string' && body.card_id.length === 9, + errorMsg: "El campo card_id no tiene una logitdud de 9 digitos" } -export const baseValidator = new BodyValidator([cardIdExists, cardIdIsUUIDv7]) +export const baseValidator = new BodyValidator([cardIdExists, cardIdIsToken]) diff --git a/src/infrastructure/Nfc.repository.test.ts b/src/infrastructure/Nfc.repository.test.ts index 0a1a559..fca50bc 100644 --- a/src/infrastructure/Nfc.repository.test.ts +++ b/src/infrastructure/Nfc.repository.test.ts @@ -4,7 +4,7 @@ import { httpclient } from "config/httpclient.config.js"; import { pgClient } from "config/pgclient.config.js"; import { NfcRepository } from "./Nfc.repository.js"; import type { ServerContext } from "domain/ServerContext.js"; -import { uuidv7 } from "uuidv7"; + await describe("NfcRepository Integration Tests", async () => { const serverContext: ServerContext = { @@ -13,7 +13,7 @@ await describe("NfcRepository Integration Tests", async () => { }; const repo = new NfcRepository(serverContext); - const testCardId = uuidv7() + const testCardId = "123456789"; const testCode = "12345678"; let lastCodeGenerated: string | undefined = undefined; @@ -66,7 +66,7 @@ await describe("NfcRepository Integration Tests", async () => { }); await test("findActivationCodes should return empty array if card has no codes", async () => { - const nonExistentCard = uuidv7() + const nonExistentCard = "987654321"; const result = await repo.findActivationCodes(nonExistentCard); if (result.error) {