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
|
CREATE EXTENSION pgcrypto; -- para los random bytes
|
||||||
-- 1. Función de generacion de uuidv7 copiada de github porque no está en postgre 16
|
-- 1. Función de generacion de uuidv7 copiada de github porque no está en postgre 16
|
||||||
CREATE OR REPLACE FUNCTION
|
CREATE OR REPLACE FUNCTION
|
||||||
@@ -27,7 +30,7 @@ $$
|
|||||||
;
|
;
|
||||||
|
|
||||||
-- 2. Tabla de Tarjetas
|
-- 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 (
|
CREATE TABLE payment_cards (
|
||||||
card_id UUID PRIMARY KEY DEFAULT uuid_generate_v7(),
|
card_id UUID PRIMARY KEY DEFAULT uuid_generate_v7(),
|
||||||
user_id UUID REFERENCES users(user_id),
|
user_id UUID REFERENCES users(user_id),
|
||||||
@@ -39,18 +42,23 @@ CREATE TABLE payment_cards (
|
|||||||
);
|
);
|
||||||
|
|
||||||
-- 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,
|
||||||
|
-- pero por lo menos se mantiene el registro de cuando se ha creado.
|
||||||
|
-- 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 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
|
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,
|
||||||
failed_attempts INT DEFAULT 0,
|
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()
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||||
);
|
);
|
||||||
|
|
||||||
-- 4. Registro de Auditoría y Dispositivos (Log de Uso)
|
-- 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 (
|
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 REFERENCES payment_cards(card_id),
|
||||||
@@ -62,6 +70,8 @@ CREATE TABLE activation_logs (
|
|||||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
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()
|
CREATE OR REPLACE FUNCTION log_activation_attempt()
|
||||||
RETURNS TRIGGER AS $$
|
RETURNS TRIGGER AS $$
|
||||||
BEGIN
|
BEGIN
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
-- He separado el procedimiento de activacion en otro archivo por comodidad de desarrollo
|
-- 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
|
-- 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.
|
-- un server node y asi evitamos problemas de consistencia entre versiones.
|
||||||
|
--
|
||||||
|
|
||||||
CREATE OR REPLACE FUNCTION activate_payment_card(
|
CREATE OR REPLACE FUNCTION activate_payment_card(
|
||||||
p_card_id UUID, -- uuidv7 de la tarjeta
|
p_card_id UUID, -- uuidv7 de la tarjeta
|
||||||
@@ -47,7 +48,7 @@ BEGIN
|
|||||||
RETURN;
|
RETURN;
|
||||||
END IF;
|
END IF;
|
||||||
|
|
||||||
|
-- 2.4 Si el código es demasiado viejo
|
||||||
IF v_expires_at < NOW() THEN
|
IF v_expires_at < NOW() THEN
|
||||||
RETURN QUERY SELECT FALSE, 'CODE_EXPIRED';
|
RETURN QUERY SELECT FALSE, 'CODE_EXPIRED';
|
||||||
RETURN;
|
RETURN;
|
||||||
|
|||||||
@@ -1,22 +1,24 @@
|
|||||||
# Stage base para coordinar las fases de build y ejecucion
|
# 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
|
# Hace falta para la herramienta de migraciones, cuando se publique se
|
||||||
# sustituira por el paquete de npm
|
# sustituira por el paquete de npm
|
||||||
RUN apk --no-cache add git=latest
|
#RUN apk --no-cache add git
|
||||||
WORKDIR /usr/local/app
|
WORKDIR /app
|
||||||
|
|
||||||
COPY ./package.json ./package.lock ./
|
|
||||||
|
|
||||||
|
COPY ./package.json ./package-lock.json ./
|
||||||
|
COPY ./src ./src
|
||||||
# copia el codigo en general
|
# copia el codigo en general
|
||||||
COPY tsconfig*.json ./
|
COPY tsconfig.json ./
|
||||||
COPY .env* ./
|
COPY .env* ./
|
||||||
COPY ./.yarnrc.yml ./
|
COPY ./deployment/local/start.sh ./
|
||||||
COPY ./deployment/local/docker/start.sh ./
|
|
||||||
# Copiar el archivo de migrations? porque ahora no creo que se esté lanzando nada
|
# Copiar el archivo de migrations? porque ahora no creo que se esté lanzando nada
|
||||||
COPY ./deployment/database/migrations ./deployment/database/migrations
|
COPY ./deployment/database/migrations ./deployment/database/migrations
|
||||||
RUN npm install --production && \
|
|
||||||
npm build && \
|
RUN npm config set registry https://git.savefamilygps.net/api/packages/SaveFamily/npm/ &&\
|
||||||
chmod +x start.sh
|
echo "registry=https://registry.npmjs.org/" >> .npmrc &&\
|
||||||
|
npm install &&\
|
||||||
|
ls && npm run build &&\
|
||||||
|
chmod +x start.sh
|
||||||
EXPOSE ${PORT}
|
EXPOSE ${PORT}
|
||||||
ENTRYPOINT [ "./start.sh" ]
|
ENTRYPOINT [ "./start.sh" ]
|
||||||
|
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ networks:
|
|||||||
|
|
||||||
services:
|
services:
|
||||||
sf-nfc-api:
|
sf-nfc-api:
|
||||||
container_name: sf-nfc-api
|
container_name: sf-nfc-server
|
||||||
image: sf-nfc-api
|
image: sf-nfc-server
|
||||||
build:
|
build:
|
||||||
context: ./
|
context: ./
|
||||||
dockerfile: deployment/local/docker/Dockerfile.local
|
dockerfile: deployment/local/docker/Dockerfile.local
|
||||||
@@ -45,13 +45,13 @@ services:
|
|||||||
env_file:
|
env_file:
|
||||||
- .env
|
- .env
|
||||||
ports:
|
ports:
|
||||||
- "${POSTGRES_PORT}:${POSTGRES_PORT}"
|
- "${POSTGRES_PORT}:5432"
|
||||||
volumes:
|
volumes:
|
||||||
- ./sql-data/:/var/lib/postgres/data
|
- ./sql-data/:/var/lib/postgres/data
|
||||||
- ./deployment/database/init.sql:/docker-entrypoint-initdb.d/init.sql
|
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
|
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
|
||||||
interval: 5s
|
interval: 5s
|
||||||
retries: 5
|
retries: 5
|
||||||
start_period: 5s
|
start_period: 5s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
|
command: -p 5432
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
echo "Lanzando migraciones e iniciando servidor"
|
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/express": "^5.0.6",
|
||||||
"@types/node": "^25.3.3",
|
"@types/node": "^25.3.3",
|
||||||
"@types/pg": "^8.18.0",
|
"@types/pg": "^8.18.0",
|
||||||
|
"esbuild": "0.27.3",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
|
"tsx": "^4.21.0",
|
||||||
"typescript": "^5.9.3"
|
"typescript": "^5.9.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -45,6 +47,448 @@
|
|||||||
"node": ">=12"
|
"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": {
|
"node_modules/@jridgewell/resolve-uri": {
|
||||||
"version": "3.1.2",
|
"version": "3.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
||||||
@@ -726,6 +1170,48 @@
|
|||||||
"node": ">= 0.4"
|
"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": {
|
"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",
|
||||||
@@ -911,6 +1397,21 @@
|
|||||||
"node": ">= 0.8"
|
"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": {
|
"node_modules/function-bind": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||||
@@ -966,6 +1467,19 @@
|
|||||||
"node": ">= 0.4"
|
"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": {
|
"node_modules/gopd": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||||
@@ -1658,6 +2172,16 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"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": {
|
"node_modules/revalidator": {
|
||||||
"version": "0.1.8",
|
"version": "0.1.8",
|
||||||
"resolved": "https://registry.npmjs.org/revalidator/-/revalidator-0.1.8.tgz",
|
"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": {
|
"node_modules/tunnel-ssh": {
|
||||||
"version": "4.1.6",
|
"version": "4.1.6",
|
||||||
"resolved": "https://registry.npmjs.org/tunnel-ssh/-/tunnel-ssh-4.1.6.tgz",
|
"resolved": "https://registry.npmjs.org/tunnel-ssh/-/tunnel-ssh-4.1.6.tgz",
|
||||||
|
|||||||
@@ -5,9 +5,10 @@
|
|||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1",
|
"test": "tsx --test",
|
||||||
"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",
|
||||||
"start": "node dist/main.js",
|
"start": "node dist/main.js",
|
||||||
"migrate": "db-migrate -e .env -m ./deployment/database/migrations/ -t 99.0.0"
|
"migrate": "db-migrate -e .env -m ./deployment/database/migrations/ -t 99.0.0"
|
||||||
},
|
},
|
||||||
@@ -17,7 +18,9 @@
|
|||||||
"@types/express": "^5.0.6",
|
"@types/express": "^5.0.6",
|
||||||
"@types/node": "^25.3.3",
|
"@types/node": "^25.3.3",
|
||||||
"@types/pg": "^8.18.0",
|
"@types/pg": "^8.18.0",
|
||||||
|
"esbuild": "0.27.3",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
|
"tsx": "^4.21.0",
|
||||||
"typescript": "^5.9.3"
|
"typescript": "^5.9.3"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"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 type { ServerContext } from "domain/ServerContext.js";
|
||||||
|
import { labelTemplate } from "./labelTemplate.js";
|
||||||
|
|
||||||
export class NfcUsecases {
|
export class NfcUsecases {
|
||||||
|
private ctx: ServerContext;
|
||||||
constructor(
|
constructor(
|
||||||
serverContext: ServerContext
|
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 = {
|
export type CardDTO = {
|
||||||
account_id: string,
|
card_id: string
|
||||||
account_number: string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type activationCodes = {
|
export type ActivationCodeDTO = {
|
||||||
id: number,
|
code_id: string,
|
||||||
code: string,
|
card_id: string,
|
||||||
account_id: string,
|
code_plain: string,
|
||||||
creation_date: string,
|
code_hash: string,
|
||||||
expiration_date: 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",
|
"outDir": "./dist",
|
||||||
// Environment Settings
|
// Environment Settings
|
||||||
// See also https://aka.ms/tsconfig/module
|
// See also https://aka.ms/tsconfig/module
|
||||||
"module": "nodenext",
|
"module": "esnext",
|
||||||
"target": "esnext",
|
"target": "es2022",
|
||||||
"moduleResolution": "nodenext",
|
"moduleResolution": "bundler",
|
||||||
// For nodejs:
|
// For nodejs:
|
||||||
"lib": [
|
"lib": [
|
||||||
"esnext"
|
"esnext"
|
||||||
|
|||||||
Reference in New Issue
Block a user