Base + compilacion simple
This commit is contained in:
2
build.local.sh
Executable file
2
build.local.sh
Executable file
@@ -0,0 +1,2 @@
|
||||
#/bin/bash
|
||||
docker compose -f deployment/local/docker/docker-compose.yaml --project-directory ./ build
|
||||
0
deployment/database/init.sql
Normal file
0
deployment/database/init.sql
Normal file
@@ -1,3 +1,6 @@
|
||||
-- Me he pasado mucho con todo el proceso, lo más probable es que haya que eliminar
|
||||
-- la parte de control de activacion que eso ya no lo lleva la aplicacion.
|
||||
|
||||
CREATE EXTENSION pgcrypto; -- para los random bytes
|
||||
-- 1. Función de generacion de uuidv7 copiada de github porque no está en postgre 16
|
||||
CREATE OR REPLACE FUNCTION
|
||||
@@ -27,7 +30,7 @@ $$
|
||||
;
|
||||
|
||||
-- 2. Tabla de Tarjetas
|
||||
-- NUNCA se guarda el numero completo (PAN). Solo los últimos 4 dígitos.
|
||||
-- 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),
|
||||
@@ -39,18 +42,23 @@ CREATE TABLE payment_cards (
|
||||
);
|
||||
|
||||
-- 3. Tabla de Códigos de Activación
|
||||
-- No creo que vaya a recibir confirmación de activación porque es de otro proyecto,
|
||||
-- pero por lo menos se mantiene el registro de cuando se ha creado.
|
||||
-- El algoritmo de hash es sha256
|
||||
CREATE TABLE activation_codes (
|
||||
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?
|
||||
code_plain TEXT NOT NULL, --
|
||||
code_hash TEXT NOT NULL, -- Guardar el código hasheado, el original se imprime y se manda
|
||||
is_used BOOLEAN DEFAULT FALSE,
|
||||
is_blocked BOOLEAN DEFAULT FALSE,
|
||||
failed_attempts INT DEFAULT 0,
|
||||
expires_at TIMESTAMPTZ NOT NULL,
|
||||
expires_at TIMESTAMPTZ, -- Si es nulo es permanenete (Solo para pruebas)
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- 4. Registro de Auditoría y Dispositivos (Log de Uso)
|
||||
-- Lo mismo, muy sobredimensionado, no creo que haya falta en este punto
|
||||
CREATE TABLE activation_logs (
|
||||
log_id UUID PRIMARY KEY DEFAULT uuid_generate_v7(),
|
||||
card_id UUID REFERENCES payment_cards(card_id),
|
||||
@@ -62,6 +70,8 @@ CREATE TABLE activation_logs (
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Trigger para cuando se haga una actualizacion en activation_codes
|
||||
-- se supone que cuando se intenta activar la tarjeta.
|
||||
CREATE OR REPLACE FUNCTION log_activation_attempt()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
-- 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
|
||||
@@ -47,7 +48,7 @@ BEGIN
|
||||
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;
|
||||
|
||||
@@ -1,22 +1,24 @@
|
||||
# Stage base para coordinar las fases de build y ejecucion
|
||||
FROM node:22-alpine AS base
|
||||
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=latest
|
||||
WORKDIR /usr/local/app
|
||||
|
||||
COPY ./package.json ./package.lock ./
|
||||
#RUN apk --no-cache add git
|
||||
WORKDIR /app
|
||||
|
||||
COPY ./package.json ./package-lock.json ./
|
||||
COPY ./src ./src
|
||||
# copia el codigo en general
|
||||
COPY tsconfig*.json ./
|
||||
COPY tsconfig.json ./
|
||||
COPY .env* ./
|
||||
COPY ./.yarnrc.yml ./
|
||||
COPY ./deployment/local/docker/start.sh ./
|
||||
COPY ./deployment/local/start.sh ./
|
||||
# Copiar el archivo de migrations? porque ahora no creo que se esté lanzando nada
|
||||
COPY ./deployment/database/migrations ./deployment/database/migrations
|
||||
RUN npm install --production && \
|
||||
npm build && \
|
||||
chmod +x start.sh
|
||||
|
||||
RUN npm config set registry https://git.savefamilygps.net/api/packages/SaveFamily/npm/ &&\
|
||||
echo "registry=https://registry.npmjs.org/" >> .npmrc &&\
|
||||
npm install &&\
|
||||
ls && npm run build &&\
|
||||
chmod +x start.sh
|
||||
EXPOSE ${PORT}
|
||||
ENTRYPOINT [ "./start.sh" ]
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@ networks:
|
||||
|
||||
services:
|
||||
sf-nfc-api:
|
||||
container_name: sf-nfc-api
|
||||
image: sf-nfc-api
|
||||
container_name: sf-nfc-server
|
||||
image: sf-nfc-server
|
||||
build:
|
||||
context: ./
|
||||
dockerfile: deployment/local/docker/Dockerfile.local
|
||||
@@ -45,13 +45,13 @@ services:
|
||||
env_file:
|
||||
- .env
|
||||
ports:
|
||||
- "${POSTGRES_PORT}:${POSTGRES_PORT}"
|
||||
- "${POSTGRES_PORT}:5432"
|
||||
volumes:
|
||||
- ./sql-data/:/var/lib/postgres/data
|
||||
- ./deployment/database/init.sql:/docker-entrypoint-initdb.d/init.sql
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
|
||||
interval: 5s
|
||||
retries: 5
|
||||
start_period: 5s
|
||||
timeout: 5s
|
||||
command: -p 5432
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#!/bin/sh
|
||||
echo "Lanzando migraciones e iniciando servidor"
|
||||
npm run migrate && npm run start
|
||||
npm run build
|
||||
npm run start
|
||||
|
||||
544
package-lock.json
generated
544
package-lock.json
generated
@@ -19,7 +19,9 @@
|
||||
"@types/express": "^5.0.6",
|
||||
"@types/node": "^25.3.3",
|
||||
"@types/pg": "^8.18.0",
|
||||
"esbuild": "0.27.3",
|
||||
"ts-node": "^10.9.2",
|
||||
"tsx": "^4.21.0",
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
},
|
||||
@@ -45,6 +47,448 @@
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.27.3",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz",
|
||||
"integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"aix"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm": {
|
||||
"version": "0.27.3",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz",
|
||||
"integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm64": {
|
||||
"version": "0.27.3",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz",
|
||||
"integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-x64": {
|
||||
"version": "0.27.3",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz",
|
||||
"integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-arm64": {
|
||||
"version": "0.27.3",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz",
|
||||
"integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-x64": {
|
||||
"version": "0.27.3",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz",
|
||||
"integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-arm64": {
|
||||
"version": "0.27.3",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz",
|
||||
"integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-x64": {
|
||||
"version": "0.27.3",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz",
|
||||
"integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm": {
|
||||
"version": "0.27.3",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz",
|
||||
"integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm64": {
|
||||
"version": "0.27.3",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz",
|
||||
"integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ia32": {
|
||||
"version": "0.27.3",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz",
|
||||
"integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-loong64": {
|
||||
"version": "0.27.3",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz",
|
||||
"integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-mips64el": {
|
||||
"version": "0.27.3",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz",
|
||||
"integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==",
|
||||
"cpu": [
|
||||
"mips64el"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ppc64": {
|
||||
"version": "0.27.3",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz",
|
||||
"integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-riscv64": {
|
||||
"version": "0.27.3",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz",
|
||||
"integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-s390x": {
|
||||
"version": "0.27.3",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz",
|
||||
"integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-x64": {
|
||||
"version": "0.27.3",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz",
|
||||
"integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-arm64": {
|
||||
"version": "0.27.3",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz",
|
||||
"integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-x64": {
|
||||
"version": "0.27.3",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz",
|
||||
"integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-arm64": {
|
||||
"version": "0.27.3",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz",
|
||||
"integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-x64": {
|
||||
"version": "0.27.3",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz",
|
||||
"integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openharmony-arm64": {
|
||||
"version": "0.27.3",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz",
|
||||
"integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openharmony"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/sunos-x64": {
|
||||
"version": "0.27.3",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz",
|
||||
"integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"sunos"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-arm64": {
|
||||
"version": "0.27.3",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz",
|
||||
"integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-ia32": {
|
||||
"version": "0.27.3",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz",
|
||||
"integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-x64": {
|
||||
"version": "0.27.3",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz",
|
||||
"integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/resolve-uri": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
||||
@@ -726,6 +1170,48 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.27.3",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz",
|
||||
"integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"esbuild": "bin/esbuild"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@esbuild/aix-ppc64": "0.27.3",
|
||||
"@esbuild/android-arm": "0.27.3",
|
||||
"@esbuild/android-arm64": "0.27.3",
|
||||
"@esbuild/android-x64": "0.27.3",
|
||||
"@esbuild/darwin-arm64": "0.27.3",
|
||||
"@esbuild/darwin-x64": "0.27.3",
|
||||
"@esbuild/freebsd-arm64": "0.27.3",
|
||||
"@esbuild/freebsd-x64": "0.27.3",
|
||||
"@esbuild/linux-arm": "0.27.3",
|
||||
"@esbuild/linux-arm64": "0.27.3",
|
||||
"@esbuild/linux-ia32": "0.27.3",
|
||||
"@esbuild/linux-loong64": "0.27.3",
|
||||
"@esbuild/linux-mips64el": "0.27.3",
|
||||
"@esbuild/linux-ppc64": "0.27.3",
|
||||
"@esbuild/linux-riscv64": "0.27.3",
|
||||
"@esbuild/linux-s390x": "0.27.3",
|
||||
"@esbuild/linux-x64": "0.27.3",
|
||||
"@esbuild/netbsd-arm64": "0.27.3",
|
||||
"@esbuild/netbsd-x64": "0.27.3",
|
||||
"@esbuild/openbsd-arm64": "0.27.3",
|
||||
"@esbuild/openbsd-x64": "0.27.3",
|
||||
"@esbuild/openharmony-arm64": "0.27.3",
|
||||
"@esbuild/sunos-x64": "0.27.3",
|
||||
"@esbuild/win32-arm64": "0.27.3",
|
||||
"@esbuild/win32-ia32": "0.27.3",
|
||||
"@esbuild/win32-x64": "0.27.3"
|
||||
}
|
||||
},
|
||||
"node_modules/escape-html": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
||||
@@ -911,6 +1397,21 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/function-bind": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||
@@ -966,6 +1467,19 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/get-tsconfig": {
|
||||
"version": "4.13.6",
|
||||
"resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.6.tgz",
|
||||
"integrity": "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"resolve-pkg-maps": "^1.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/gopd": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||
@@ -1658,6 +2172,16 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/resolve-pkg-maps": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
|
||||
"integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/revalidator": {
|
||||
"version": "0.1.8",
|
||||
"resolved": "https://registry.npmjs.org/revalidator/-/revalidator-0.1.8.tgz",
|
||||
@@ -1971,6 +2495,26 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/tsx": {
|
||||
"version": "4.21.0",
|
||||
"resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz",
|
||||
"integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"esbuild": "~0.27.0",
|
||||
"get-tsconfig": "^4.7.5"
|
||||
},
|
||||
"bin": {
|
||||
"tsx": "dist/cli.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "~2.3.3"
|
||||
}
|
||||
},
|
||||
"node_modules/tunnel-ssh": {
|
||||
"version": "4.1.6",
|
||||
"resolved": "https://registry.npmjs.org/tunnel-ssh/-/tunnel-ssh-4.1.6.tgz",
|
||||
|
||||
@@ -5,9 +5,10 @@
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"test": "tsx --test",
|
||||
"dev": "tsx src/main.ts",
|
||||
"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"
|
||||
},
|
||||
@@ -17,7 +18,9 @@
|
||||
"@types/express": "^5.0.6",
|
||||
"@types/node": "^25.3.3",
|
||||
"@types/pg": "^8.18.0",
|
||||
"esbuild": "0.27.3",
|
||||
"ts-node": "^10.9.2",
|
||||
"tsx": "^4.21.0",
|
||||
"typescript": "^5.9.3"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
2
run.local.sh
Executable file
2
run.local.sh
Executable file
@@ -0,0 +1,2 @@
|
||||
#/bin/bash
|
||||
docker compose -f ./deployment/local/docker/docker-compose.yaml --project-directory ./ up --watch
|
||||
35
src/aplication/BodyValidator.ts
Normal file
35
src/aplication/BodyValidator.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import type { Result } from "domain/Result.js"
|
||||
|
||||
export type Validator<T extends Object> = {
|
||||
field: keyof T,
|
||||
errorMsg: string,
|
||||
validationFunc: (obj: T) => boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Ejecuta una lista de validadores en orden, si alguno
|
||||
* falla devuelve un Error
|
||||
*/
|
||||
export class BodyValidator<T extends Object> {
|
||||
validatorList: Validator<T>[] = []
|
||||
constructor(
|
||||
validators: Validator<T>[]
|
||||
) {
|
||||
this.validatorList = validators
|
||||
}
|
||||
|
||||
public validate(obj: T): Result<{ msg: string, field: string }, boolean> {
|
||||
for (const validator of this.validatorList) {
|
||||
if (validator.validationFunc(obj) == false)
|
||||
return {
|
||||
error: {
|
||||
msg: validator.errorMsg,
|
||||
field: String(validator.field)
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
data: true
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import type { ServerContext } from "domain/ServerContext.js";
|
||||
import type { NfcUsecases } from "./Nfc.usecases.js";
|
||||
import type { Request, Response } from "express"
|
||||
import { baseValidator } from "./validators.js";
|
||||
import { error } from "node:console";
|
||||
|
||||
export class NfcController {
|
||||
private ctx: ServerContext;
|
||||
private nfcUsecases: NfcUsecases;
|
||||
|
||||
constructor(args: {
|
||||
serverContext: ServerContext,
|
||||
nfcUsecases: NfcUsecases
|
||||
}) {
|
||||
this.ctx = args.serverContext
|
||||
this.nfcUsecases = args.nfcUsecases
|
||||
}
|
||||
|
||||
public generateActivationTag() {
|
||||
return (req: Request, res: Response) => {
|
||||
const body = req.body
|
||||
const validate = baseValidator.validate(body)
|
||||
|
||||
if (validate.error != undefined) {
|
||||
res.status(422).json({
|
||||
error: validate.error
|
||||
})
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
45
src/aplication/Nfc.usecases.test.ts
Normal file
45
src/aplication/Nfc.usecases.test.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { httpclient } from "config/httpclient.config.js";
|
||||
import { pgClient } from "config/pgclient.config.js";
|
||||
import type { ServerContext } from "domain/ServerContext.js";
|
||||
import test, { describe } from "node:test";
|
||||
import { NfcUsecases } from "./Nfc.usecases.js";
|
||||
import assert from "node:assert";
|
||||
|
||||
|
||||
describe("NFC activation code", () => {
|
||||
const serverContext: ServerContext = {
|
||||
PostgresClient: pgClient,
|
||||
HttpClient: httpclient
|
||||
}
|
||||
|
||||
const nfcUsecases = new NfcUsecases(serverContext)
|
||||
|
||||
test("Should generate 8 digit codes", () => {
|
||||
// @ts-expect-error
|
||||
const code = nfcUsecases.generateActivationCode()
|
||||
|
||||
assert(code != undefined)
|
||||
assert(code.length == 8)
|
||||
})
|
||||
|
||||
test("Generated codes must be validated by its function", () => {
|
||||
// @ts-expect-error
|
||||
const code = nfcUsecases.generateActivationCode()
|
||||
// @ts-expect-error
|
||||
const validation = nfcUsecases.validateActivationCode({
|
||||
code
|
||||
})
|
||||
console.log("Codigo, validation", code, validation)
|
||||
assert(validation == true)
|
||||
})
|
||||
|
||||
test("Invalid codes must be invalidated by its function", () => {
|
||||
const code = "10000000" // Claramente 0 % 32 no es 1
|
||||
// @ts-expect-error
|
||||
const validation = nfcUsecases.validateActivationCode({
|
||||
code
|
||||
})
|
||||
|
||||
assert(validation == false)
|
||||
})
|
||||
})
|
||||
@@ -1,9 +1,85 @@
|
||||
import type { ServerContext } from "domain/ServerContext.js";
|
||||
import { labelTemplate } from "./labelTemplate.js";
|
||||
|
||||
export class NfcUsecases {
|
||||
private ctx: ServerContext;
|
||||
constructor(
|
||||
serverContext: ServerContext
|
||||
) {
|
||||
this.ctx = serverContext
|
||||
}
|
||||
|
||||
private getRandomDayOffset() {
|
||||
const ONE_DAY_MS = 24 * 60 * 60 * 1000;
|
||||
const TEN_DAYS_MS = 10 * ONE_DAY_MS;
|
||||
|
||||
const randomOffset = (Math.random() * (2 * TEN_DAYS_MS)) - TEN_DAYS_MS;
|
||||
|
||||
return Math.floor(randomOffset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Devuelve un código de activación para una tarjeta con la forma
|
||||
* [Caracter validacion][7 caracteres]
|
||||
* El modulo de la suma de los 7 caracteres % RADIX da el valor entero del caracter
|
||||
* de validacion
|
||||
*/
|
||||
private generateActivationCode() {
|
||||
const RADIX = 32
|
||||
// offset de +-10 dias para evitar ataques (se podria adivianr el código)
|
||||
const randomOffset = this.getRandomDayOffset()
|
||||
const milis = Date
|
||||
.now()
|
||||
const milisOffset = milis + randomOffset
|
||||
const code = milisOffset.toString(RADIX)
|
||||
.slice(2) // La parte de año / mes no es significativa
|
||||
|
||||
// Algoritmo de validacion = mod(sum(caracteres))
|
||||
const validation = this.generataValidationChar({
|
||||
code: code,
|
||||
radix: RADIX
|
||||
})
|
||||
|
||||
return validation + code
|
||||
}
|
||||
|
||||
private generataValidationChar(args: {
|
||||
code: string,
|
||||
radix: number
|
||||
}) {
|
||||
const validation = (args.code
|
||||
.split("")
|
||||
.map(e => Number.parseInt(e, args.radix))
|
||||
.reduce((acc, curr) => acc + curr) % args.radix).toString(args.radix)
|
||||
|
||||
return validation
|
||||
}
|
||||
|
||||
private validateActivationCode(args: {
|
||||
code: string,
|
||||
radix?: number
|
||||
}) {
|
||||
const radix = args.radix ?? 32
|
||||
const values = args.code.slice(1)
|
||||
const validationChar = args.code.slice(0, 1)
|
||||
const testValidationChar = this.generataValidationChar({
|
||||
code: values,
|
||||
radix: radix
|
||||
})
|
||||
return validationChar == testValidationChar
|
||||
}
|
||||
|
||||
public generateActivationLabel() {
|
||||
return () => {
|
||||
const codigo = this.generateActivationCode()
|
||||
const label = labelTemplate(codigo)
|
||||
|
||||
// Introducir en la bdd
|
||||
|
||||
return {
|
||||
code: codigo,
|
||||
label: label
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
22
src/aplication/labelTemplate.ts
Normal file
22
src/aplication/labelTemplate.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
export function labelTemplate(code: string) {
|
||||
return `
|
||||
^XA
|
||||
|
||||
---------- SET LABEL SIZE (30mm x 30mm) ----------
|
||||
^PW240
|
||||
^LL240
|
||||
|
||||
---------- CENTERED QR CODE ----------
|
||||
^FO50,20
|
||||
^BQN,2,7
|
||||
^FD${code}^FS
|
||||
|
||||
---------- BOTTOM CENTERED TEXT ----------
|
||||
^FO0,195
|
||||
^A0N,25,25
|
||||
^FB240,1,0,C
|
||||
^FD${code}^FS
|
||||
|
||||
^XZ
|
||||
`
|
||||
}
|
||||
9
src/aplication/validators.ts
Normal file
9
src/aplication/validators.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { BodyValidator, type Validator } from "./BodyValidator.js";
|
||||
|
||||
const cardIdExists: Validator<{ cardId?: string }> = {
|
||||
field: "cardId",
|
||||
validationFunc: (body) => body.cardId != undefined,
|
||||
errorMsg: "El campo cardId esta undefined"
|
||||
}
|
||||
|
||||
export const baseValidator = new BodyValidator([cardIdExists])
|
||||
@@ -1,12 +1,28 @@
|
||||
export type nfcRegsitry = {
|
||||
account_id: string,
|
||||
account_number: string
|
||||
export type CardDTO = {
|
||||
card_id: string
|
||||
}
|
||||
|
||||
export type activationCodes = {
|
||||
id: number,
|
||||
code: string,
|
||||
account_id: string,
|
||||
creation_date: string,
|
||||
expiration_date: string
|
||||
export type ActivationCodeDTO = {
|
||||
code_id: string,
|
||||
card_id: string,
|
||||
code_plain: string,
|
||||
code_hash: string,
|
||||
is_used: boolean,
|
||||
is_blocked: boolean,
|
||||
failed_attempts: number,
|
||||
expires_at?: string,
|
||||
created_at: string,
|
||||
}
|
||||
|
||||
export type ActivationCodeCreateDTO = Pick<ActivationCodeDTO, "card_id" | "code_plain">
|
||||
|
||||
export type ActivationLogDTO = {
|
||||
log_id: string,
|
||||
card_id: string,
|
||||
code_id: string,
|
||||
action_type: string,
|
||||
ip_address: string,
|
||||
device_info: Record<string, any>,
|
||||
geo_location: string,
|
||||
created_at: Date,
|
||||
}
|
||||
|
||||
29
src/domain/Result.ts
Normal file
29
src/domain/Result.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
export type Success<D> = {
|
||||
error?: undefined | null,
|
||||
data: D
|
||||
}
|
||||
|
||||
export type Failure<E = Error> = {
|
||||
data?: undefined | null,
|
||||
error: E
|
||||
}
|
||||
|
||||
/**
|
||||
* Result<Error,Data>
|
||||
*/
|
||||
export type Result<E, D> = Failure<E> | Success<D>
|
||||
|
||||
export async function tryCatch<T>(func: Promise<T>): Promise<Result<{ msg: Error }, T>> {
|
||||
try {
|
||||
const res = await func;
|
||||
return {
|
||||
data: res
|
||||
}
|
||||
} catch (e: unknown) {
|
||||
return {
|
||||
error: {
|
||||
msg: e as Error
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
73
src/infrastructure/Nfc.repository.test.ts
Normal file
73
src/infrastructure/Nfc.repository.test.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import test, { describe, before, after } from "node:test";
|
||||
import assert from "node:assert";
|
||||
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";
|
||||
|
||||
describe("NfcRepository Integration Tests", () => {
|
||||
const serverContext: ServerContext = {
|
||||
PostgresClient: pgClient,
|
||||
HttpClient: httpclient
|
||||
};
|
||||
|
||||
const repo = new NfcRepository(serverContext);
|
||||
const testCardId = "test-card-" + Date.now();
|
||||
const testCode = "12345678";
|
||||
|
||||
// Clean up before and after tests to ensure isolation
|
||||
const cleanup = async () => {
|
||||
await pgClient.query("DELETE FROM activation_codes WHERE card_id = $1", [testCardId]);
|
||||
};
|
||||
|
||||
before(async () => {
|
||||
await cleanup();
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await cleanup();
|
||||
});
|
||||
|
||||
test("createActivationCode should insert a record into the database", async () => {
|
||||
const result = await repo.createActivationCode({
|
||||
cardId: testCardId,
|
||||
code: testCode
|
||||
});
|
||||
|
||||
if (result.error) {
|
||||
assert.fail(`createActivationCode failed: ${result.error}`);
|
||||
}
|
||||
|
||||
assert.ok(result.data, "Data should be returned");
|
||||
assert.strictEqual(result.data.card_id, testCardId);
|
||||
assert.strictEqual(result.data.code_plain, testCode);
|
||||
assert.ok(result.data.code_hash, "Hash should be generated");
|
||||
});
|
||||
|
||||
test("findActivationCodes should retrieve the inserted record", async () => {
|
||||
// We assume the previous test inserted the record, but lets be safe or just rely on sequence
|
||||
const result = await repo.findActivationCodes(testCardId);
|
||||
|
||||
if (result.error) {
|
||||
assert.fail(`findActivationCodes failed: ${result.error}`);
|
||||
}
|
||||
|
||||
assert.ok(result.data, "Data should be returned");
|
||||
assert.ok(Array.isArray(result.data));
|
||||
|
||||
const found = result.data.find(code => code.code_plain === testCode);
|
||||
assert.ok(found, "The inserted code should be found");
|
||||
assert.strictEqual(found.card_id, testCardId);
|
||||
});
|
||||
|
||||
test("findActivationCodes should return empty array if card has no codes", async () => {
|
||||
const result = await repo.findActivationCodes("non-existent-card");
|
||||
|
||||
if (result.error) {
|
||||
assert.fail(`findActivationCodes failed: ${result.error}`);
|
||||
}
|
||||
|
||||
assert.ok(result.data, "Data should be returned");
|
||||
assert.strictEqual(result.data.length, 0);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,66 @@
|
||||
import type { ActivationCodeDTO } from "domain/NfcRegistry.js";
|
||||
import type { Result } from "domain/Result.js";
|
||||
import type { ServerContext } from "domain/ServerContext.js";
|
||||
|
||||
// TODO: Pasar a Result<E,T>
|
||||
|
||||
export class NfcRepository {
|
||||
private ctx: ServerContext;
|
||||
|
||||
constructor(serverConext: ServerContext) {
|
||||
this.ctx = serverConext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Devuleve todos los códigos de activación que ha podido tener una tarjeta
|
||||
*/
|
||||
public async findActivationCodes(cardId: string, args?: {}): Promise<Result<string, ActivationCodeDTO[]>> {
|
||||
const query = `
|
||||
SELECT * FROM activation_codes
|
||||
WHERE card_id = $1
|
||||
`
|
||||
const values = [cardId]
|
||||
try {
|
||||
const codeResult = await this.ctx.PostgresClient.query<ActivationCodeDTO>(query, values)
|
||||
return {
|
||||
data: codeResult.rows
|
||||
}
|
||||
} catch (e) {
|
||||
return {
|
||||
error: e as string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async createActivationCode(args: { cardId: string, code: string }): Promise<Result<string, ActivationCodeDTO>> {
|
||||
const query = `
|
||||
INSERT INTO activation_codes(
|
||||
card_id,
|
||||
code_plain,
|
||||
code_hash
|
||||
)
|
||||
VALUES (
|
||||
$1,
|
||||
$2,
|
||||
digest($2,'sha256')
|
||||
)
|
||||
RETURNING(
|
||||
code_id,card_id,code_plain,code_hash,is_used,is_blocked,failed_attempts,created_at,expires_at
|
||||
)
|
||||
`
|
||||
const values = [args.cardId, args.code]
|
||||
try {
|
||||
const insertResult = await this.ctx.PostgresClient.query<ActivationCodeDTO>(query, values)
|
||||
return {
|
||||
data: insertResult.rows[0]!
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Error createActivationCode: ", e)
|
||||
return {
|
||||
error: e as string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
2
stop.local.sh
Executable file
2
stop.local.sh
Executable file
@@ -0,0 +1,2 @@
|
||||
#/bin/bash
|
||||
docker compose -f deployment/local/docker/docker-compose.yaml --project-directory ./ down -v
|
||||
@@ -7,9 +7,9 @@
|
||||
"outDir": "./dist",
|
||||
// Environment Settings
|
||||
// See also https://aka.ms/tsconfig/module
|
||||
"module": "nodenext",
|
||||
"target": "esnext",
|
||||
"moduleResolution": "nodenext",
|
||||
"module": "esnext",
|
||||
"target": "es2022",
|
||||
"moduleResolution": "bundler",
|
||||
// For nodejs:
|
||||
"lib": [
|
||||
"esnext"
|
||||
|
||||
Reference in New Issue
Block a user