diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..af4a840 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,19 @@ +{ + "name": "carga_api", + "dockerComposeFile": [ + "../infra/docker-compose.yml" + ], + "service": "api", + "workspaceFolder": "/app", + "runServices": [ + "database" + ], + "settings": { + "terminal.integrated.defaultProfile.linux": "bash" + }, + "appPort": [ + 3051 + ], + "shutdownAction": "stopCompose", + "remoteUser": "node" +} \ No newline at end of file diff --git a/.env(Example).txt b/.env(Example).txt new file mode 100644 index 0000000..3db2053 --- /dev/null +++ b/.env(Example).txt @@ -0,0 +1,18 @@ +API_PORT = 4000 +JWT_SECRET = "your_jwt_secret" +JWT_EXPIRES_IN = "1h" +DB_HOST = localhost +DB_PORT = 32769 +DB_USER = root +DB_PASS = root +DB_NAME = carga_test +DB_SYNC = true +//api +API_URL = //URL web service + + + //Gmail configuration, llaves de acceso + EMAIL_USER = + CLIENT_ID = + CLIENT_SECRET = + REFRESH_TOKEN = \ No newline at end of file diff --git a/example_envi.txt b/example_envi.txt new file mode 100644 index 0000000..deeba02 --- /dev/null +++ b/example_envi.txt @@ -0,0 +1,10 @@ +API_PORT=3051 + + +DB_HOST=database +DB_PORT=3306 +DB_USER=root +DB_PASS=root +DB_NAME=carga_test +DB_SYNC=true +DB_LOGGING=true \ No newline at end of file diff --git a/infra/Dockerfile b/infra/Dockerfile new file mode 100644 index 0000000..cd0e134 --- /dev/null +++ b/infra/Dockerfile @@ -0,0 +1,30 @@ +# Usa una imagen base liviana de Node.js +FROM node:22.10.0-alpine + +# Crea un usuario no root para mayor seguridad +RUN adduser -D appuser + +# Establece directorio de trabajo +WORKDIR /app + +# Instala herramientas para compilar dependencias nativas +RUN apk add --no-cache python3 make g++ + +# Copia e instala dependencias +COPY package*.json ./ +RUN npm install + +# Copia el resto del proyecto +COPY . . + +# Da permisos a todo el proyecto a appuser +RUN chown -R appuser:appuser /app + +# Cambia a ese usuario +USER appuser + +# Expone el puerto +EXPOSE 3051 + +# Comando para desarrollo +CMD ["npm", "run", "start:dev"] diff --git a/infra/docker-compose.yml b/infra/docker-compose.yml new file mode 100644 index 0000000..083ccc6 --- /dev/null +++ b/infra/docker-compose.yml @@ -0,0 +1,17 @@ +version: '3.9' + +services: + database: + image: mariadb:latest + container_name: carga_db + restart: always + env_file: + - ../.env + ports: + - "${DB_PORT}:3306" + volumes: + - mariadb_data:/var/lib/mysql + - ./init.sql:/docker-entrypoint-initdb.d/init.sql + +volumes: + mariadb_data: diff --git a/infra/init_data.sql b/infra/init_data.sql new file mode 100644 index 0000000..40de51b --- /dev/null +++ b/infra/init_data.sql @@ -0,0 +1,7 @@ +-- init_insert_data.sql +USE carga_test; + +INSERT IGNORE INTO origen (fuente) VALUES + ('redes'), + ('AT'), + ('escolares'); diff --git a/package-lock.json b/package-lock.json index bc7f2cf..68cf1dd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,10 +10,27 @@ "license": "UNLICENSED", "dependencies": { "@nestjs/common": "^11.0.1", + "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.0.1", - "@nestjs/platform-express": "^11.0.1", + "@nestjs/jwt": "^11.0.0", + "@nestjs/passport": "^11.0.5", + "@nestjs/platform-express": "^11.1.3", + "@nestjs/schedule": "^6.0.0", + "@nestjs/swagger": "^11.2.0", + "@nestjs/typeorm": "^11.0.0", + "bcrypt": "^6.0.0", + "class-transformer": "^0.5.1", + "class-validator": "^0.14.2", + "dotenv": "^16.5.0", + "exceljs": "^4.4.0", + "multer": "^2.0.1", + "mysql2": "^3.14.1", + "nodemailer": "^7.0.3", + "passport": "^0.7.0", + "passport-jwt": "^4.0.1", "reflect-metadata": "^0.2.2", - "rxjs": "^7.8.1" + "rxjs": "^7.8.1", + "typeorm": "^0.3.24" }, "devDependencies": { "@eslint/eslintrc": "^3.2.0", @@ -23,9 +40,12 @@ "@nestjs/testing": "^11.0.1", "@swc/cli": "^0.6.0", "@swc/core": "^1.10.7", + "@types/bcrypt": "^5.0.2", "@types/express": "^5.0.0", "@types/jest": "^29.5.14", - "@types/node": "^22.10.7", + "@types/multer": "^1.4.13", + "@types/node": "^22.15.31", + "@types/passport-jwt": "^4.0.1", "@types/supertest": "^6.0.2", "eslint": "^9.18.0", "eslint-config-prettier": "^10.0.1", @@ -732,7 +752,7 @@ "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@jridgewell/trace-mapping": "0.3.9" @@ -745,7 +765,7 @@ "version": "0.3.9", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", @@ -906,6 +926,47 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@fast-csv/format": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/@fast-csv/format/-/format-4.3.5.tgz", + "integrity": "sha512-8iRn6QF3I8Ak78lNAa+Gdl5MJJBM5vRHivFtMRUWINdevNo00K7OXxS2PshawLKTejVwieIlPmK5YlLu6w4u8A==", + "license": "MIT", + "dependencies": { + "@types/node": "^14.0.1", + "lodash.escaperegexp": "^4.1.2", + "lodash.isboolean": "^3.0.3", + "lodash.isequal": "^4.5.0", + "lodash.isfunction": "^3.0.9", + "lodash.isnil": "^4.0.0" + } + }, + "node_modules/@fast-csv/format/node_modules/@types/node": { + "version": "14.18.63", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.63.tgz", + "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==", + "license": "MIT" + }, + "node_modules/@fast-csv/parse": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/@fast-csv/parse/-/parse-4.3.6.tgz", + "integrity": "sha512-uRsLYksqpbDmWaSmzvJcuApSEe38+6NQZBUsuAyMZKqHxH0g1wcJgsKUvN3WC8tewaqFjBMMGrkHmC+T7k8LvA==", + "license": "MIT", + "dependencies": { + "@types/node": "^14.0.1", + "lodash.escaperegexp": "^4.1.2", + "lodash.groupby": "^4.6.0", + "lodash.isfunction": "^3.0.9", + "lodash.isnil": "^4.0.0", + "lodash.isundefined": "^3.0.1", + "lodash.uniq": "^4.5.0" + } + }, + "node_modules/@fast-csv/parse/node_modules/@types/node": { + "version": "14.18.63", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.63.tgz", + "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==", + "license": "MIT" + }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -1294,7 +1355,6 @@ "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, "license": "ISC", "dependencies": { "string-width": "^5.1.2", @@ -1312,7 +1372,6 @@ "version": "6.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -1325,14 +1384,12 @@ "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, "license": "MIT" }, "node_modules/@isaacs/cliui/node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, "license": "MIT", "dependencies": { "eastasianwidth": "^0.2.0", @@ -1350,7 +1407,6 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^6.1.0", @@ -1860,7 +1916,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=6.0.0" @@ -1891,7 +1947,7 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { @@ -1914,6 +1970,12 @@ "node": ">=8" } }, + "node_modules/@microsoft/tsdoc": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.15.1.tgz", + "integrity": "sha512-4aErSrCR/On/e5G2hDP0wjooqDdauzEbIq8hIkIe5pXV0rtWJZvdCEKL0ykZxex+IxIwBp0eGeV48hQN07dXtw==", + "license": "MIT" + }, "node_modules/@napi-rs/nice": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@napi-rs/nice/-/nice-1.0.1.tgz", @@ -2465,6 +2527,33 @@ } } }, + "node_modules/@nestjs/config": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@nestjs/config/-/config-4.0.2.tgz", + "integrity": "sha512-McMW6EXtpc8+CwTUwFdg6h7dYcBUpH5iUILCclAsa+MbCEvC9ZKu4dCHRlJqALuhjLw97pbQu62l4+wRwGeZqA==", + "license": "MIT", + "dependencies": { + "dotenv": "16.4.7", + "dotenv-expand": "12.0.1", + "lodash": "4.17.21" + }, + "peerDependencies": { + "@nestjs/common": "^10.0.0 || ^11.0.0", + "rxjs": "^7.1.0" + } + }, + "node_modules/@nestjs/config/node_modules/dotenv": { + "version": "16.4.7", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", + "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/@nestjs/core": { "version": "11.1.3", "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-11.1.3.tgz", @@ -2506,6 +2595,49 @@ } } }, + "node_modules/@nestjs/jwt": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/@nestjs/jwt/-/jwt-11.0.0.tgz", + "integrity": "sha512-v7YRsW3Xi8HNTsO+jeHSEEqelX37TVWgwt+BcxtkG/OfXJEOs6GZdbdza200d6KqId1pJQZ6UPj1F0M6E+mxaA==", + "license": "MIT", + "dependencies": { + "@types/jsonwebtoken": "9.0.7", + "jsonwebtoken": "9.0.2" + }, + "peerDependencies": { + "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0" + } + }, + "node_modules/@nestjs/mapped-types": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@nestjs/mapped-types/-/mapped-types-2.1.0.tgz", + "integrity": "sha512-W+n+rM69XsFdwORF11UqJahn4J3xi4g/ZEOlJNL6KoW5ygWSmBB2p0S2BZ4FQeS/NDH72e6xIcu35SfJnE8bXw==", + "license": "MIT", + "peerDependencies": { + "@nestjs/common": "^10.0.0 || ^11.0.0", + "class-transformer": "^0.4.0 || ^0.5.0", + "class-validator": "^0.13.0 || ^0.14.0", + "reflect-metadata": "^0.1.12 || ^0.2.0" + }, + "peerDependenciesMeta": { + "class-transformer": { + "optional": true + }, + "class-validator": { + "optional": true + } + } + }, + "node_modules/@nestjs/passport": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/@nestjs/passport/-/passport-11.0.5.tgz", + "integrity": "sha512-ulQX6mbjlws92PIM15Naes4F4p2JoxGnIJuUsdXQPT+Oo2sqQmENEZXM7eYuimocfHnKlcfZOuyzbA33LwUlOQ==", + "license": "MIT", + "peerDependencies": { + "@nestjs/common": "^10.0.0 || ^11.0.0", + "passport": "^0.5.0 || ^0.6.0 || ^0.7.0" + } + }, "node_modules/@nestjs/platform-express": { "version": "11.1.3", "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-11.1.3.tgz", @@ -2527,6 +2659,19 @@ "@nestjs/core": "^11.0.0" } }, + "node_modules/@nestjs/schedule": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@nestjs/schedule/-/schedule-6.0.0.tgz", + "integrity": "sha512-aQySMw6tw2nhitELXd3EiRacQRgzUKD9mFcUZVOJ7jPLqIBvXOyvRWLsK9SdurGA+jjziAlMef7iB5ZEFFoQpw==", + "license": "MIT", + "dependencies": { + "cron": "4.3.0" + }, + "peerDependencies": { + "@nestjs/common": "^10.0.0 || ^11.0.0", + "@nestjs/core": "^10.0.0 || ^11.0.0" + } + }, "node_modules/@nestjs/schematics": { "version": "11.0.5", "resolved": "https://registry.npmjs.org/@nestjs/schematics/-/schematics-11.0.5.tgz", @@ -2625,6 +2770,39 @@ "tslib": "^2.1.0" } }, + "node_modules/@nestjs/swagger": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-11.2.0.tgz", + "integrity": "sha512-5wolt8GmpNcrQv34tIPUtPoV1EeFbCetm40Ij3+M0FNNnf2RJ3FyWfuQvI8SBlcJyfaounYVTKzKHreFXsUyOg==", + "license": "MIT", + "dependencies": { + "@microsoft/tsdoc": "0.15.1", + "@nestjs/mapped-types": "2.1.0", + "js-yaml": "4.1.0", + "lodash": "4.17.21", + "path-to-regexp": "8.2.0", + "swagger-ui-dist": "5.21.0" + }, + "peerDependencies": { + "@fastify/static": "^8.0.0", + "@nestjs/common": "^11.0.1", + "@nestjs/core": "^11.0.1", + "class-transformer": "*", + "class-validator": "*", + "reflect-metadata": "^0.1.12 || ^0.2.0" + }, + "peerDependenciesMeta": { + "@fastify/static": { + "optional": true + }, + "class-transformer": { + "optional": true + }, + "class-validator": { + "optional": true + } + } + }, "node_modules/@nestjs/testing": { "version": "11.1.3", "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-11.1.3.tgz", @@ -2653,6 +2831,19 @@ } } }, + "node_modules/@nestjs/typeorm": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/@nestjs/typeorm/-/typeorm-11.0.0.tgz", + "integrity": "sha512-SOeUQl70Lb2OfhGkvnh4KXWlsd+zA08RuuQgT7kKbzivngxzSo1Oc7Usu5VxCxACQC9wc2l9esOHILSJeK7rJA==", + "license": "MIT", + "peerDependencies": { + "@nestjs/common": "^10.0.0 || ^11.0.0", + "@nestjs/core": "^10.0.0 || ^11.0.0", + "reflect-metadata": "^0.1.13 || ^0.2.0", + "rxjs": "^7.2.0", + "typeorm": "^0.3.0" + } + }, "node_modules/@noble/hashes": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", @@ -2730,6 +2921,16 @@ "@noble/hashes": "^1.1.5" } }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@pkgr/core": { "version": "0.2.7", "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.7.tgz", @@ -2743,6 +2944,13 @@ "url": "https://opencollective.com/pkgr" } }, + "node_modules/@scarf/scarf": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz", + "integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==", + "hasInstallScript": true, + "license": "Apache-2.0" + }, "node_modules/@sec-ant/readable-stream": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", @@ -2790,6 +2998,12 @@ "@sinonjs/commons": "^3.0.0" } }, + "node_modules/@sqltools/formatter": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@sqltools/formatter/-/formatter-1.2.5.tgz", + "integrity": "sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==", + "license": "MIT" + }, "node_modules/@swc/cli": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/@swc/cli/-/cli-0.6.0.tgz", @@ -3128,28 +3342,28 @@ "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/@tsconfig/node12": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/@tsconfig/node14": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/@tsconfig/node16": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/@types/babel__core": { @@ -3197,6 +3411,16 @@ "@babel/types": "^7.20.7" } }, + "node_modules/@types/bcrypt": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-5.0.2.tgz", + "integrity": "sha512-6atioO8Y75fNcbmj0G7UjI9lXN2pQ/IGJ2FWT4a/btd0Lk9lQalHLKhkgKVZ3r+spnmWUKfbMi1GEe9wyHQfNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/body-parser": { "version": "1.19.6", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", @@ -3348,6 +3572,21 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.7.tgz", + "integrity": "sha512-ugo316mmTYBl2g81zDFnZ7cfxlut3o+/EQdaP7J8QN2kY6lJ22hmQYCK5EHcJHbrW+dkCGSCPgbG8JtYj6qSrg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/luxon": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.6.2.tgz", + "integrity": "sha512-R/BdP7OxEMc44l2Ex5lSXHoIXTB2JLNa3y2QISIbr58U/YcsffyQrYW//hZSdrfxrjRZj3GcUoxMPGdO8gSYuw==", + "license": "MIT" + }, "node_modules/@types/methods": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz", @@ -3362,16 +3601,57 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/multer": { + "version": "1.4.13", + "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.13.tgz", + "integrity": "sha512-bhhdtPw7JqCiEfC9Jimx5LqX9BDIPJEh2q/fQ4bqbBPtyEZYr3cvF22NwG0DmPZNYA0CAf2CnqDB4KIGGpJcaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express": "*" + } + }, "node_modules/@types/node": { "version": "22.15.31", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.31.tgz", "integrity": "sha512-jnVe5ULKl6tijxUhvQeNbQG/84fHfg+yMak02cT8QVhBx/F05rAVxCGBYYTh2EKz22D6JF5ktXuNwdx7b9iEGw==", - "dev": true, "license": "MIT", "dependencies": { "undici-types": "~6.21.0" } }, + "node_modules/@types/passport": { + "version": "1.0.17", + "resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.17.tgz", + "integrity": "sha512-aciLyx+wDwT2t2/kJGJR2AEeBz0nJU4WuRX04Wu9Dqc5lSUtwu0WERPHYsLhF9PtseiAMPBGNUOtFjxZ56prsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/passport-jwt": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/passport-jwt/-/passport-jwt-4.0.1.tgz", + "integrity": "sha512-Y0Ykz6nWP4jpxgEUYq8NoVZeCQPo1ZndJLfapI249g1jHChvRfZRO/LS3tqu26YgAS/laI1qx98sYGz0IalRXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/jsonwebtoken": "*", + "@types/passport-strategy": "*" + } + }, + "node_modules/@types/passport-strategy": { + "version": "0.2.38", + "resolved": "https://registry.npmjs.org/@types/passport-strategy/-/passport-strategy-0.2.38.tgz", + "integrity": "sha512-GC6eMqqojOooq993Tmnmp7AUTbbQSgilyvpCYQjT+H6JfG/g6RGc7nXEniZlp0zyKJ0WUdOiZWLBZft9Yug1uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express": "*", + "@types/passport": "*" + } + }, "node_modules/@types/qs": { "version": "6.14.0", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", @@ -3440,6 +3720,12 @@ "@types/superagent": "^8.1.0" } }, + "node_modules/@types/validator": { + "version": "13.15.1", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.15.1.tgz", + "integrity": "sha512-9gG6ogYcoI2mCMLdcO0NYI0AYrbxIjv0MDmy/5Ywo6CpWWrqYayc+mmgxRsCgtcGJm9BSbXkMsmxGah1iGHAAQ==", + "license": "MIT" + }, "node_modules/@types/yargs": { "version": "17.0.33", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", @@ -4466,7 +4752,7 @@ "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "dev": true, + "devOptional": true, "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -4489,7 +4775,7 @@ "version": "8.3.4", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "acorn": "^8.11.0" @@ -4597,7 +4883,6 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -4610,7 +4895,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -4626,7 +4910,6 @@ "version": "3.17.0", "resolved": "https://registry.npmjs.org/ansis/-/ansis-3.17.0.tgz", "integrity": "sha512-0qWUglt9JEqLFr3w1I1pbrChn1grhaiAR2ocX1PP/flRmxgtwTzPFFFnfIlD6aMOLQZgSuCRlidD70lvx8yhzg==", - "dev": true, "license": "ISC", "engines": { "node": ">=14" @@ -4659,6 +4942,15 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/app-root-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-3.1.0.tgz", + "integrity": "sha512-biN3PwB2gUtjaYy/isrU3aNWI5w+fAfvHkSvCKeQGxhmYpwKFUxudR3Yya+KqVRHBmEDYh+/lTozYCFbmzX4nA==", + "license": "MIT", + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/append-field": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", @@ -4686,18 +4978,123 @@ ], "license": "MIT" }, + "node_modules/archiver": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.2.tgz", + "integrity": "sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==", + "license": "MIT", + "dependencies": { + "archiver-utils": "^2.1.0", + "async": "^3.2.4", + "buffer-crc32": "^0.2.1", + "readable-stream": "^3.6.0", + "readdir-glob": "^1.1.2", + "tar-stream": "^2.2.0", + "zip-stream": "^4.1.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/archiver-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", + "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", + "license": "MIT", + "dependencies": { + "glob": "^7.1.4", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^2.0.0" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/archiver-utils/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/archiver-utils/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/archiver-utils/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/archiver-utils/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/archiver/node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "license": "MIT", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/arg": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, "license": "Python-2.0" }, "node_modules/array-timsort": { @@ -4718,7 +5115,6 @@ "version": "3.2.6", "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", - "dev": true, "license": "MIT" }, "node_modules/asynckit": { @@ -4728,6 +5124,15 @@ "dev": true, "license": "MIT" }, + "node_modules/aws-ssl-profiles": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz", + "integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==", + "license": "MIT", + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/b4a": { "version": "1.6.7", "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.7.tgz", @@ -4865,7 +5270,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, "license": "MIT" }, "node_modules/bare-events": { @@ -4880,7 +5284,6 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, "funding": [ { "type": "github", @@ -4897,6 +5300,29 @@ ], "license": "MIT" }, + "node_modules/bcrypt": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-6.0.0.tgz", + "integrity": "sha512-cU8v/EGSrnH+HnxV2z0J7/blxH8gq7Xh2JFT6Aroax7UohdmiJJlxApMxtKfuI7z68NvvVcmR78k2LbT6efhRg==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "node-addon-api": "^8.3.0", + "node-gyp-build": "^4.8.4" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/big-integer": { + "version": "1.6.52", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz", + "integrity": "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==", + "license": "Unlicense", + "engines": { + "node": ">=0.6" + } + }, "node_modules/bin-version": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/bin-version/-/bin-version-6.0.0.tgz", @@ -4932,11 +5358,23 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/binary": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/binary/-/binary-0.3.0.tgz", + "integrity": "sha512-D4H1y5KYwpJgK8wk1Cue5LLPgmwHKYSChkbspQg5JtVuR5ulGckxfR62H3AE9UDkdMC8yyXlqYihuz3Aqg2XZg==", + "license": "MIT", + "dependencies": { + "buffers": "~0.1.1", + "chainsaw": "~0.1.0" + }, + "engines": { + "node": "*" + } + }, "node_modules/bl": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "dev": true, "license": "MIT", "dependencies": { "buffer": "^5.5.0", @@ -4944,6 +5382,12 @@ "readable-stream": "^3.4.0" } }, + "node_modules/bluebird": { + "version": "3.4.7", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz", + "integrity": "sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==", + "license": "MIT" + }, "node_modules/body-parser": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", @@ -4968,7 +5412,6 @@ "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -5048,7 +5491,6 @@ "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, "funding": [ { "type": "github", @@ -5073,18 +5515,40 @@ "version": "0.2.13", "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", - "dev": true, "license": "MIT", "engines": { "node": "*" } }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "license": "MIT" }, + "node_modules/buffer-indexof-polyfill": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/buffer-indexof-polyfill/-/buffer-indexof-polyfill-1.0.2.tgz", + "integrity": "sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A==", + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/buffers": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz", + "integrity": "sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ==", + "engines": { + "node": ">=0.2.0" + } + }, "node_modules/busboy": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", @@ -5204,6 +5668,18 @@ ], "license": "CC-BY-4.0" }, + "node_modules/chainsaw": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz", + "integrity": "sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ==", + "license": "MIT/X11", + "dependencies": { + "traverse": ">=0.3.0 <0.4" + }, + "engines": { + "node": "*" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -5287,6 +5763,23 @@ "dev": true, "license": "MIT" }, + "node_modules/class-transformer": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz", + "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==", + "license": "MIT" + }, + "node_modules/class-validator": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.2.tgz", + "integrity": "sha512-3kMVRF2io8N8pY1IFIXlho9r8IPUUIfHe2hYVtiebvAzU2XeQFXTv+XI4WX+TnXmtwXMDcjngcpkiPM0O9PvLw==", + "license": "MIT", + "dependencies": { + "@types/validator": "^13.11.8", + "libphonenumber-js": "^1.11.1", + "validator": "^13.9.0" + } + }, "node_modules/cli-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", @@ -5343,7 +5836,6 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, "license": "ISC", "dependencies": { "string-width": "^4.2.0", @@ -5358,7 +5850,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -5368,7 +5859,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -5381,7 +5871,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", @@ -5427,7 +5916,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -5440,7 +5928,6 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, "license": "MIT" }, "node_modules/combined-stream": { @@ -5493,11 +5980,25 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/compress-commons": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.2.tgz", + "integrity": "sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==", + "license": "MIT", + "dependencies": { + "buffer-crc32": "^0.2.13", + "crc32-stream": "^4.0.2", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, "license": "MIT" }, "node_modules/concat-stream": { @@ -5582,7 +6083,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "dev": true, "license": "MIT" }, "node_modules/cors": { @@ -5625,6 +6125,31 @@ } } }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "license": "Apache-2.0", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/crc32-stream": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.3.tgz", + "integrity": "sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==", + "license": "MIT", + "dependencies": { + "crc-32": "^1.2.0", + "readable-stream": "^3.4.0" + }, + "engines": { + "node": ">= 10" + } + }, "node_modules/create-jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", @@ -5651,14 +6176,26 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true, + "devOptional": true, "license": "MIT" }, + "node_modules/cron": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/cron/-/cron-4.3.0.tgz", + "integrity": "sha512-ciiYNLfSlF9MrDqnbMdRWFiA6oizSF7kA1osPP9lRzNu0Uu+AWog1UKy7SkckiDY2irrNjeO6qLyKnXC8oxmrw==", + "license": "MIT", + "dependencies": { + "@types/luxon": "~3.6.0", + "luxon": "~3.6.0" + }, + "engines": { + "node": ">=18.x" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -5669,6 +6206,12 @@ "node": ">= 8" } }, + "node_modules/dayjs": { + "version": "1.11.13", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", + "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==", + "license": "MIT" + }, "node_modules/debug": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", @@ -5719,7 +6262,6 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.6.0.tgz", "integrity": "sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA==", - "dev": true, "license": "MIT", "peerDependencies": { "babel-plugin-macros": "^3.1.0" @@ -5780,6 +6322,15 @@ "node": ">=0.4.0" } }, + "node_modules/denque": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", + "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.10" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -5814,7 +6365,7 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true, + "devOptional": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.3.1" @@ -5830,6 +6381,33 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/dotenv": { + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz", + "integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dotenv-expand": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-12.0.1.tgz", + "integrity": "sha512-LaKRbou8gt0RNID/9RoI+J2rvXsBRPMV7p+ElHlPhcSARbCPDYcYG2s1TIzAfWv4YSgyY5taidWzzs31lNV3yQ==", + "license": "BSD-2-Clause", + "dependencies": { + "dotenv": "^16.4.5" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -5844,13 +6422,60 @@ "node": ">= 0.4" } }, + "node_modules/duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==", + "license": "BSD-3-Clause", + "dependencies": { + "readable-stream": "^2.0.2" + } + }, + "node_modules/duplexer2/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/duplexer2/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/duplexer2/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true, "license": "MIT" }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -5897,7 +6522,6 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, "license": "MIT" }, "node_modules/encodeurl": { @@ -5909,6 +6533,15 @@ "node": ">= 0.8" } }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, "node_modules/enhanced-resolve": { "version": "5.18.1", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz", @@ -5990,7 +6623,6 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -6250,6 +6882,44 @@ "node": ">=0.8.x" } }, + "node_modules/exceljs": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/exceljs/-/exceljs-4.4.0.tgz", + "integrity": "sha512-XctvKaEMaj1Ii9oDOqbW/6e1gXknSY4g/aLCDicOXqBE4M0nRWkUu0PTp++UPNzoFY12BNHMfs/VadKIS6llvg==", + "license": "MIT", + "dependencies": { + "archiver": "^5.0.0", + "dayjs": "^1.8.34", + "fast-csv": "^4.3.1", + "jszip": "^3.10.1", + "readable-stream": "^3.6.0", + "saxes": "^5.0.1", + "tmp": "^0.2.0", + "unzipper": "^0.10.11", + "uuid": "^8.3.0" + }, + "engines": { + "node": ">=8.3.0" + } + }, + "node_modules/exceljs/node_modules/tmp": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", + "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==", + "license": "MIT", + "engines": { + "node": ">=14.14" + } + }, + "node_modules/exceljs/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", @@ -6416,6 +7086,19 @@ "node": ">=0.10.0" } }, + "node_modules/fast-csv": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/fast-csv/-/fast-csv-4.3.6.tgz", + "integrity": "sha512-2RNSpuwwsJGP0frGsOmTb9oUF+VkFSM4SyLTDgwf2ciHWTarN0lQTC+F2f/t5J9QjW+c65VFIAAu85GsvMIusw==", + "license": "MIT", + "dependencies": { + "@fast-csv/format": "4.3.5", + "@fast-csv/parse": "4.3.6" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -6711,7 +7394,6 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", - "dev": true, "license": "ISC", "dependencies": { "cross-spawn": "^7.0.6", @@ -6838,6 +7520,12 @@ "node": ">= 0.8" } }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "license": "MIT" + }, "node_modules/fs-extra": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", @@ -6864,7 +7552,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true, "license": "ISC" }, "node_modules/fsevents": { @@ -6882,6 +7569,22 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/fstream": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz", + "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "dependencies": { + "graceful-fs": "^4.1.2", + "inherits": "~2.0.0", + "mkdirp": ">=0.5 0", + "rimraf": "2" + }, + "engines": { + "node": ">=0.6" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -6891,6 +7594,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/generate-function": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", + "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", + "license": "MIT", + "dependencies": { + "is-property": "^1.0.2" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -6905,7 +7617,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, "license": "ISC", "engines": { "node": "6.* || 8.* || >= 10.*" @@ -7096,7 +7807,6 @@ "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, "license": "ISC" }, "node_modules/graphemer": { @@ -7271,6 +7981,12 @@ "node": ">= 4" } }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "license": "MIT" + }, "node_modules/import-fresh": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", @@ -7323,7 +8039,6 @@ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, "license": "ISC", "dependencies": { "once": "^1.3.0", @@ -7392,7 +8107,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -7457,6 +8171,12 @@ "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", "license": "MIT" }, + "node_modules/is-property": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==", + "license": "MIT" + }, "node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", @@ -7483,11 +8203,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, "license": "ISC" }, "node_modules/istanbul-lib-coverage": { @@ -8287,7 +9012,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -8370,6 +9094,91 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "license": "MIT", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "license": "(MIT OR GPL-3.0-or-later)", + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, + "node_modules/jszip/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/jszip/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/jszip/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/jwa": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz", + "integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "license": "MIT", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -8400,6 +9209,48 @@ "node": ">=6" } }, + "node_modules/lazystream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", + "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", + "license": "MIT", + "dependencies": { + "readable-stream": "^2.0.5" + }, + "engines": { + "node": ">= 0.6.3" + } + }, + "node_modules/lazystream/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/lazystream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/lazystream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -8424,6 +9275,21 @@ "node": ">= 0.8.0" } }, + "node_modules/libphonenumber-js": { + "version": "1.12.9", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.12.9.tgz", + "integrity": "sha512-VWwAdNeJgN7jFOD+wN4qx83DTPMVPPAUyx9/TUkBXKLiNkuWWk6anV0439tgdtwaJDrEdqkvdN22iA6J4bUCZg==", + "license": "MIT" + }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "license": "MIT", + "dependencies": { + "immediate": "~3.0.5" + } + }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -8431,6 +9297,12 @@ "dev": true, "license": "MIT" }, + "node_modules/listenercount": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/listenercount/-/listenercount-1.0.1.tgz", + "integrity": "sha512-3mk/Zag0+IJxeDrxSgaDPy4zZ3w05PRZeJNnlWhzFz5OkX49J4krc+A8X2d2M69vGMBEX0uyl8M+W+8gH+kBqQ==", + "license": "ISC" + }, "node_modules/load-esm": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/load-esm/-/load-esm-1.0.2.tgz", @@ -8480,7 +9352,97 @@ "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true, + "license": "MIT" + }, + "node_modules/lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==", + "license": "MIT" + }, + "node_modules/lodash.difference": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", + "integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==", + "license": "MIT" + }, + "node_modules/lodash.escaperegexp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", + "integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==", + "license": "MIT" + }, + "node_modules/lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==", + "license": "MIT" + }, + "node_modules/lodash.groupby": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.groupby/-/lodash.groupby-4.6.0.tgz", + "integrity": "sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw==", + "license": "MIT" + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", + "deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.", + "license": "MIT" + }, + "node_modules/lodash.isfunction": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz", + "integrity": "sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==", + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" + }, + "node_modules/lodash.isnil": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/lodash.isnil/-/lodash.isnil-4.0.0.tgz", + "integrity": "sha512-up2Mzq3545mwVnMhTDMdfoG1OurpA/s5t88JmQX809eH3C8491iu2sfKhTfhQtKY78oPNhiaHJUpT/dUDAAtng==", + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" + }, + "node_modules/lodash.isundefined": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash.isundefined/-/lodash.isundefined-3.0.1.tgz", + "integrity": "sha512-MXB1is3s899/cD8jheYYE2V9qTHwKvt+npCwpD+1Sxm3Q3cECXCiYHjeHWXNwr6Q0SOBPrYUDxendrO6goVTEA==", "license": "MIT" }, "node_modules/lodash.memoize": { @@ -8497,6 +9459,24 @@ "dev": true, "license": "MIT" }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" + }, + "node_modules/lodash.union": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", + "integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==", + "license": "MIT" + }, + "node_modules/lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", + "license": "MIT" + }, "node_modules/log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", @@ -8514,6 +9494,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "license": "Apache-2.0" + }, "node_modules/lowercase-keys": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", @@ -8537,6 +9523,30 @@ "yallist": "^3.0.2" } }, + "node_modules/lru.min": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.2.tgz", + "integrity": "sha512-Nv9KddBcQSlQopmBHXSsZVY5xsdlZkdH/Iey0BlcBYggMd4two7cZnKOK9vmy3nY0O5RGH99z1PCeTpPqszUYg==", + "license": "MIT", + "engines": { + "bun": ">=1.0.0", + "deno": ">=1.30.0", + "node": ">=8.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wellwelwel" + } + }, + "node_modules/luxon": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.6.1.tgz", + "integrity": "sha512-tJLxrKJhO2ukZ5z0gyjY1zPh3Rh88Ej9P7jNrZiHMUXHae1yvI2imgOZtL1TO8TW6biMMKfTtAOoEJANgtWBMQ==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/magic-string": { "version": "0.30.17", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", @@ -8567,7 +9577,7 @@ "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true, + "devOptional": true, "license": "ISC" }, "node_modules/makeerror": { @@ -8738,7 +9748,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -8760,7 +9769,6 @@ "version": "7.1.2", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "dev": true, "license": "ISC", "engines": { "node": ">=16 || 14 >=14.17" @@ -8855,6 +9863,47 @@ "node": "^18.17.0 || >=20.5.0" } }, + "node_modules/mysql2": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.14.1.tgz", + "integrity": "sha512-7ytuPQJjQB8TNAYX/H2yhL+iQOnIBjAMam361R7UAL0lOVXWjtdrmoL9HYKqKoLp/8UUTRcvo1QPvK9KL7wA8w==", + "license": "MIT", + "dependencies": { + "aws-ssl-profiles": "^1.1.1", + "denque": "^2.1.0", + "generate-function": "^2.3.1", + "iconv-lite": "^0.6.3", + "long": "^5.2.1", + "lru.min": "^1.0.0", + "named-placeholders": "^1.1.3", + "seq-queue": "^0.0.5", + "sqlstring": "^2.3.2" + }, + "engines": { + "node": ">= 8.0" + } + }, + "node_modules/named-placeholders": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.3.tgz", + "integrity": "sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==", + "license": "MIT", + "dependencies": { + "lru-cache": "^7.14.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/named-placeholders/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -8885,6 +9934,15 @@ "dev": true, "license": "MIT" }, + "node_modules/node-addon-api": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.4.0.tgz", + "integrity": "sha512-D9DI/gXHvVmjHS08SVch0Em8G5S1P+QWtU31appcKT/8wFSPRcdHadIFSAntdMMVM5zz+/DL+bL/gz3UDppqtg==", + "license": "MIT", + "engines": { + "node": "^18 || ^20 || >= 21" + } + }, "node_modules/node-emoji": { "version": "1.11.0", "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.11.0.tgz", @@ -8895,6 +9953,17 @@ "lodash": "^4.17.21" } }, + "node_modules/node-gyp-build": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", + "license": "MIT", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -8909,11 +9978,19 @@ "dev": true, "license": "MIT" }, + "node_modules/nodemailer": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.3.tgz", + "integrity": "sha512-Ajq6Sz1x7cIK3pN6KesGTah+1gnwMnx5gKl3piQlQQE/PwyJ4Mbc8is2psWYxK3RJTVeqsDaCv8ZzXLCDHMTZw==", + "license": "MIT-0", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -9134,9 +10211,14 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "dev": true, "license": "BlueOak-1.0.0" }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "license": "(MIT AND Zlib)" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -9178,6 +10260,42 @@ "node": ">= 0.8" } }, + "node_modules/passport": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/passport/-/passport-0.7.0.tgz", + "integrity": "sha512-cPLl+qZpSc+ireUvt+IzqbED1cHHkDoVYMo30jbJIdOOjQ1MQYZBPiNvmi8UM6lJuOpTPXJGZQk0DtC4y61MYQ==", + "license": "MIT", + "dependencies": { + "passport-strategy": "1.x.x", + "pause": "0.0.1", + "utils-merge": "^1.0.1" + }, + "engines": { + "node": ">= 0.4.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jaredhanson" + } + }, + "node_modules/passport-jwt": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/passport-jwt/-/passport-jwt-4.0.1.tgz", + "integrity": "sha512-UCKMDYhNuGOBE9/9Ycuoyh7vP6jpeTp/+sfMJl7nLff/t6dps+iaeE0hhNkKN8/HZHcJ7lCdOyDxHdDoxoSvdQ==", + "license": "MIT", + "dependencies": { + "jsonwebtoken": "^9.0.0", + "passport-strategy": "^1.0.0" + } + }, + "node_modules/passport-strategy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", + "integrity": "sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA==", + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -9192,7 +10310,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -9202,7 +10319,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -9261,6 +10377,11 @@ "node": ">=8" } }, + "node_modules/pause": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", + "integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==" + }, "node_modules/peek-readable": { "version": "5.4.2", "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-5.4.2.tgz", @@ -9468,6 +10589,12 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, "node_modules/prompts": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", @@ -9626,6 +10753,36 @@ "node": ">= 6" } }, + "node_modules/readdir-glob": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz", + "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.1.0" + } + }, + "node_modules/readdir-glob/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/readdir-glob/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/readdirp": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", @@ -9660,7 +10817,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -9795,6 +10951,40 @@ "node": ">=0.10.0" } }, + "node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/router": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", @@ -9870,6 +11060,18 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "license": "MIT" }, + "node_modules/saxes": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", + "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/schema-utils": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", @@ -9917,7 +11119,6 @@ "version": "7.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -9977,6 +11178,11 @@ "node": ">= 18" } }, + "node_modules/seq-queue": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz", + "integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==" + }, "node_modules/serialize-javascript": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", @@ -10002,17 +11208,35 @@ "node": ">= 18" } }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "license": "MIT" + }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "license": "ISC" }, + "node_modules/sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "license": "(MIT AND BSD-3-Clause)", + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + }, + "bin": { + "sha.js": "bin.js" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" @@ -10025,7 +11249,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -10107,7 +11330,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, "license": "ISC", "engines": { "node": ">=14" @@ -10197,6 +11419,31 @@ "dev": true, "license": "BSD-3-Clause" }, + "node_modules/sql-highlight": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/sql-highlight/-/sql-highlight-6.1.0.tgz", + "integrity": "sha512-ed7OK4e9ywpE7pgRMkMQmZDPKSVdm0oX5IEtZiKnFucSF0zu6c80GZBe38UqHuVhTWJ9xsKgSMjCG2bml86KvA==", + "funding": [ + "https://github.com/scriptcoded/sql-highlight?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/scriptcoded" + } + ], + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/sqlstring": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz", + "integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/stack-utils": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", @@ -10301,7 +11548,6 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -10317,7 +11563,6 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -10332,7 +11577,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -10342,7 +11586,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -10355,7 +11598,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -10365,7 +11607,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -10378,7 +11619,6 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^6.0.1" @@ -10395,7 +11635,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -10408,7 +11647,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -10535,6 +11773,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/swagger-ui-dist": { + "version": "5.21.0", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.21.0.tgz", + "integrity": "sha512-E0K3AB6HvQd8yQNSMR7eE5bk+323AUxjtCz/4ZNKiahOlPhPJxqn3UPIGs00cyY/dhrTDJ61L7C/a8u6zhGrZg==", + "license": "Apache-2.0", + "dependencies": { + "@scarf/scarf": "=1.4.0" + } + }, "node_modules/symbol-observable": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz", @@ -10863,6 +12110,15 @@ "url": "https://github.com/sponsors/Borewit" } }, + "node_modules/traverse": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz", + "integrity": "sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ==", + "license": "MIT/X11", + "engines": { + "node": "*" + } + }, "node_modules/tree-kill": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", @@ -10974,7 +12230,7 @@ "version": "10.9.2", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@cspotcode/source-map-support": "^0.8.0", @@ -11117,11 +12373,222 @@ "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", "license": "MIT" }, + "node_modules/typeorm": { + "version": "0.3.24", + "resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.3.24.tgz", + "integrity": "sha512-4IrHG7A0tY8l5gEGXfW56VOMfUVWEkWlH/h5wmcyZ+V8oCiLj7iTPp0lEjMEZVrxEkGSdP9ErgTKHKXQApl/oA==", + "license": "MIT", + "dependencies": { + "@sqltools/formatter": "^1.2.5", + "ansis": "^3.17.0", + "app-root-path": "^3.1.0", + "buffer": "^6.0.3", + "dayjs": "^1.11.13", + "debug": "^4.4.0", + "dedent": "^1.6.0", + "dotenv": "^16.4.7", + "glob": "^10.4.5", + "sha.js": "^2.4.11", + "sql-highlight": "^6.0.0", + "tslib": "^2.8.1", + "uuid": "^11.1.0", + "yargs": "^17.7.2" + }, + "bin": { + "typeorm": "cli.js", + "typeorm-ts-node-commonjs": "cli-ts-node-commonjs.js", + "typeorm-ts-node-esm": "cli-ts-node-esm.js" + }, + "engines": { + "node": ">=16.13.0" + }, + "funding": { + "url": "https://opencollective.com/typeorm" + }, + "peerDependencies": { + "@google-cloud/spanner": "^5.18.0 || ^6.0.0 || ^7.0.0", + "@sap/hana-client": "^2.12.25", + "better-sqlite3": "^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0", + "hdb-pool": "^0.1.6", + "ioredis": "^5.0.4", + "mongodb": "^5.8.0 || ^6.0.0", + "mssql": "^9.1.1 || ^10.0.1 || ^11.0.1", + "mysql2": "^2.2.5 || ^3.0.1", + "oracledb": "^6.3.0", + "pg": "^8.5.1", + "pg-native": "^3.0.0", + "pg-query-stream": "^4.0.0", + "redis": "^3.1.1 || ^4.0.0", + "reflect-metadata": "^0.1.14 || ^0.2.0", + "sql.js": "^1.4.0", + "sqlite3": "^5.0.3", + "ts-node": "^10.7.0", + "typeorm-aurora-data-api-driver": "^2.0.0 || ^3.0.0" + }, + "peerDependenciesMeta": { + "@google-cloud/spanner": { + "optional": true + }, + "@sap/hana-client": { + "optional": true + }, + "better-sqlite3": { + "optional": true + }, + "hdb-pool": { + "optional": true + }, + "ioredis": { + "optional": true + }, + "mongodb": { + "optional": true + }, + "mssql": { + "optional": true + }, + "mysql2": { + "optional": true + }, + "oracledb": { + "optional": true + }, + "pg": { + "optional": true + }, + "pg-native": { + "optional": true + }, + "pg-query-stream": { + "optional": true + }, + "redis": { + "optional": true + }, + "sql.js": { + "optional": true + }, + "sqlite3": { + "optional": true + }, + "ts-node": { + "optional": true + }, + "typeorm-aurora-data-api-driver": { + "optional": true + } + } + }, + "node_modules/typeorm/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/typeorm/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/typeorm/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/typeorm/node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/typeorm/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, + "node_modules/typeorm/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/typeorm/node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/typescript": { "version": "5.8.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -11193,7 +12660,6 @@ "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true, "license": "MIT" }, "node_modules/universalify": { @@ -11215,6 +12681,54 @@ "node": ">= 0.8" } }, + "node_modules/unzipper": { + "version": "0.10.14", + "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.10.14.tgz", + "integrity": "sha512-ti4wZj+0bQTiX2KmKWuwj7lhV+2n//uXEotUmGuQqrbVZSEGFMbI68+c6JCQ8aAmUWYvtHEz2A8K6wXvueR/6g==", + "license": "MIT", + "dependencies": { + "big-integer": "^1.6.17", + "binary": "~0.3.0", + "bluebird": "~3.4.1", + "buffer-indexof-polyfill": "~1.0.0", + "duplexer2": "~0.1.4", + "fstream": "^1.0.12", + "graceful-fs": "^4.2.2", + "listenercount": "~1.0.1", + "readable-stream": "~2.3.6", + "setimmediate": "~1.0.4" + } + }, + "node_modules/unzipper/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/unzipper/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/unzipper/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/update-browserslist-db": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", @@ -11262,11 +12776,33 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "license": "MIT" }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, "node_modules/v8-compile-cache-lib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/v8-to-istanbul": { @@ -11284,6 +12820,15 @@ "node": ">=10.12.0" } }, + "node_modules/validator": { + "version": "13.15.15", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.15.tgz", + "integrity": "sha512-BgWVbCI72aIQy937xbawcs+hrVaN/CZ2UwutgaJ36hGqRrLNM+f5LUT/YPRbo8IV/ASeFzXszezV+y2+rq3l8A==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -11544,7 +13089,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -11586,7 +13130,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", @@ -11604,7 +13147,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -11614,7 +13156,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -11673,6 +13214,12 @@ "dev": true, "license": "ISC" }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "license": "MIT" + }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", @@ -11686,7 +13233,6 @@ "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, "license": "ISC", "engines": { "node": ">=10" @@ -11703,7 +13249,6 @@ "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, "license": "MIT", "dependencies": { "cliui": "^8.0.1", @@ -11722,7 +13267,6 @@ "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, "license": "ISC", "engines": { "node": ">=12" @@ -11746,7 +13290,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=6" @@ -11777,6 +13321,62 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zip-stream": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.1.tgz", + "integrity": "sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==", + "license": "MIT", + "dependencies": { + "archiver-utils": "^3.0.4", + "compress-commons": "^4.1.2", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/zip-stream/node_modules/archiver-utils": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-3.0.4.tgz", + "integrity": "sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==", + "license": "MIT", + "dependencies": { + "glob": "^7.2.3", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/zip-stream/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } } } } diff --git a/package.json b/package.json index 0d7d4c2..d9cf5de 100644 --- a/package.json +++ b/package.json @@ -21,10 +21,27 @@ }, "dependencies": { "@nestjs/common": "^11.0.1", + "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.0.1", - "@nestjs/platform-express": "^11.0.1", + "@nestjs/jwt": "^11.0.0", + "@nestjs/passport": "^11.0.5", + "@nestjs/platform-express": "^11.1.3", + "@nestjs/schedule": "^6.0.0", + "@nestjs/swagger": "^11.2.0", + "@nestjs/typeorm": "^11.0.0", + "bcrypt": "^6.0.0", + "class-transformer": "^0.5.1", + "class-validator": "^0.14.2", + "dotenv": "^16.5.0", + "exceljs": "^4.4.0", + "multer": "^2.0.1", + "mysql2": "^3.14.1", + "nodemailer": "^7.0.3", + "passport": "^0.7.0", + "passport-jwt": "^4.0.1", "reflect-metadata": "^0.2.2", - "rxjs": "^7.8.1" + "rxjs": "^7.8.1", + "typeorm": "^0.3.24" }, "devDependencies": { "@eslint/eslintrc": "^3.2.0", @@ -34,9 +51,12 @@ "@nestjs/testing": "^11.0.1", "@swc/cli": "^0.6.0", "@swc/core": "^1.10.7", + "@types/bcrypt": "^5.0.2", "@types/express": "^5.0.0", "@types/jest": "^29.5.14", - "@types/node": "^22.10.7", + "@types/multer": "^1.4.13", + "@types/node": "^22.15.31", + "@types/passport-jwt": "^4.0.1", "@types/supertest": "^6.0.2", "eslint": "^9.18.0", "eslint-config-prettier": "^10.0.1", diff --git a/src/app.module.ts b/src/app.module.ts index 8662803..a98f4f9 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -1,10 +1,67 @@ -import { Module } from '@nestjs/common'; +import { Logger, Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; +import { ConfigModule, ConfigService } from '@nestjs/config'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import * as entities from './entities/entities'; +import { ExcelModule } from './excel/excel.module'; +import { AuthModule } from './auth/auth.module'; +import { MovimientoModule } from './movimiento/movimiento.module'; +import { UsuariosModule } from './usuarios/usuarios.module'; +import { MailService } from './mail/mail.service'; +import { MailModule } from './mail/mail.module'; +import { ScheduleModule } from '@nestjs/schedule'; + + @Module({ - imports: [], + imports: [ + ScheduleModule.forRoot(), + ConfigModule.forRoot({ isGlobal: true }), + TypeOrmModule.forRootAsync({ + imports: [ConfigModule], + inject: [ConfigService], + useFactory: async (config: ConfigService) => { + const dbConfig = { + type: 'mysql' as const, + host: config.get('DB_HOST', 'database'), + port: config.get('DB_PORT', 3306), + username: config.get('DB_USER', 'root'), + // no prints en claro en prod: + password: config.get('DB_PASS', ''), + database: config.get('DB_NAME', 'test'), + dropSchema: false, // solo para desarrollo + }; + Logger.log('[DB CONFIG] ' + JSON.stringify({ + ...dbConfig, + password: '***' + }), 'TypeORM'); + return { + ...dbConfig, + entities: Object.values(entities), + synchronize: config.get('DB_SYNC', true), + + logging: ['error', 'warn', 'info', 'query', 'schema'], + logger: 'advanced-console', + + retryAttempts: 5, + retryDelay: 2000, + + }; + }, + }), + TypeOrmModule.forFeature(Object.values(entities)), + + + ExcelModule, + AuthModule, + MovimientoModule, + UsuariosModule, + MailModule + + ], controllers: [AppController], providers: [AppService], }) -export class AppModule {} +export class AppModule { } diff --git a/src/auth/auth.controller.ts b/src/auth/auth.controller.ts new file mode 100644 index 0000000..a49b9de --- /dev/null +++ b/src/auth/auth.controller.ts @@ -0,0 +1,48 @@ +import { AuthDocumentation } from './auth.documentation'; +import { Controller, Post, Body, UseGuards, Request, Get } from '@nestjs/common'; +import { AuthService } from './auth.service'; +import { LoginDto } from './dto/login.dto'; +import { AuthGuard } from '@nestjs/passport'; +import { RegisterDto } from './dto/register.dto'; +import { ApiBearerAuth } from '@nestjs/swagger'; + +@Controller() +export class AuthController { + constructor(private readonly authService: AuthService) { } + + @Post('login') + @AuthDocumentation.login() + async login(@Body() dto: LoginDto) { + const user = await this.authService.validateUser(dto.email, dto.password); + if (!user) { + throw new Error('Invalid credentials'); + } + return this.authService.login(user); + } + + + @UseGuards(AuthGuard('jwt')) + @ApiBearerAuth('bearer') + @UseGuards(AuthGuard('jwt')) + @Get('profile') + @AuthDocumentation.bearerAuth() + getProfile(@Request() req) { + // req.user viene de JwtStrategy.validate() + return { + userId: req.user.userId, + email: req.user.email, + }; + } + + + + + @Post('register') + @AuthDocumentation.register() // ver siguiente sección + async register(@Body() dto: RegisterDto) { + return this.authService.register(dto); + } + + + +} diff --git a/src/auth/auth.documentation.ts b/src/auth/auth.documentation.ts new file mode 100644 index 0000000..537a7f6 --- /dev/null +++ b/src/auth/auth.documentation.ts @@ -0,0 +1,104 @@ +import { applyDecorators } from '@nestjs/common'; +import { ApiTags, ApiOperation, ApiBody, ApiConsumes, ApiResponse } from '@nestjs/swagger'; +import { Origen } from 'src/entities/entities'; + +export class AuthDocumentation { + /** + * Decorators Swagger para el endpoint POST /login + */ + static login() { + return applyDecorators( + ApiTags('Auth'), + ApiOperation({ + summary: 'Iniciar sesión', + description: 'Autentica a un usuario con correo y contraseña, retorna un JWT en caso de éxito.' + }), + ApiConsumes('application/json'), + ApiBody({ + schema: { + type: 'object', + properties: { + email: { type: 'string', format: 'email', example: 'usuario@dominio.com' }, + password: { type: 'string', minLength: 6, example: 'miPassword123' } + }, + required: ['email', 'password'] + } + }), + ApiResponse({ + status: 200, + description: 'Autenticación exitosa. Retorna token JWT.', + schema: { + type: 'object', + properties: { + access_token: { type: 'string', example: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...' } + } + } + }), + ApiResponse({ + status: 401, + description: 'Credenciales inválidas.', + schema: { + type: 'object', + properties: { + statusCode: { type: 'integer', example: 401 }, + message: { type: 'string', example: 'Credenciales inválidas' }, + error: { type: 'string', example: 'Unauthorized' } + } + } + }) + ); + } + + /** + * Decorators para proteger rutas con JWT + */ + static bearerAuth() { + return applyDecorators( + ApiOperation({ summary: 'Requiere autenticación JWT' }), + ApiResponse({ status: 401, description: 'Token no provisto o inválido.' }), + ApiResponse({ status: 403, description: 'Token válido pero sin permisos.' }) + ); + } + + + + + static register() { + return applyDecorators( + ApiTags('Auth'), + ApiOperation({ + summary: 'Registrar usuario', + description: 'Crea un nuevo usuario con correo y contraseña.' + }), + ApiConsumes('application/json'), + ApiBody({ + schema: { + type: 'object', + properties: { + email: { type: 'string', format: 'email', example: 'nuevo@dominio.com' }, + password: { type: 'string', example: 'Password123' }, + origen: { type: 'string', example: 'RED', description: 'Origen del usuario' } + + } + } + }), + ApiResponse({ + status: 201, + description: 'Usuario creado correctamente', + schema: { + type: 'object', + properties: { + id_usuario: { type: 'number', example: 1 }, + correo: { type: 'string', example: 'nuevo@dominio.com' }, + + origen: { type: 'string', example: 'RED' } + + + } + } + }), + ApiResponse({ status: 409, description: 'El correo ya está registrado.' }), + ); + } + +} diff --git a/src/auth/auth.module.ts b/src/auth/auth.module.ts new file mode 100644 index 0000000..2b406e0 --- /dev/null +++ b/src/auth/auth.module.ts @@ -0,0 +1,28 @@ +// src/auth/auth.module.ts +import { Module } from '@nestjs/common'; +import { JwtModule } from '@nestjs/jwt'; +import { PassportModule } from '@nestjs/passport'; +import { ConfigService, ConfigModule } from '@nestjs/config'; +import { AuthService } from './auth.service'; +import { AuthController } from './auth.controller'; +import { JwtStrategy } from './jwt.strategy'; +import { Origen, UsuariosDelSistema } from '../entities/entities'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +@Module({ + imports: [ + TypeOrmModule.forFeature([UsuariosDelSistema, Origen]), + PassportModule, + JwtModule.registerAsync({ + imports: [ConfigModule], + inject: [ConfigService], + useFactory: async (cs: ConfigService) => ({ + secret: cs.get('JWT_SECRET'), + signOptions: { expiresIn: cs.get('JWT_EXPIRES_IN') }, + }), + }), + ], + providers: [AuthService, JwtStrategy], + controllers: [AuthController], +}) +export class AuthModule { } diff --git a/src/auth/auth.service.ts b/src/auth/auth.service.ts new file mode 100644 index 0000000..0bacb35 --- /dev/null +++ b/src/auth/auth.service.ts @@ -0,0 +1,78 @@ +// src/auth/auth.service.ts +import { ConflictException, Injectable, UnauthorizedException } from '@nestjs/common'; +import { JwtService } from '@nestjs/jwt'; +import { Or, Repository } from 'typeorm'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Origen, UsuariosDelSistema } from '../entities/entities'; +import * as bcrypt from 'bcrypt'; +import { RegisterDto } from './dto/register.dto'; + +@Injectable() +export class AuthService { + constructor( + @InjectRepository(UsuariosDelSistema) + private readonly userRepo: Repository, + @InjectRepository(Origen) + private readonly origenRepo: Repository, + + private readonly jwtService: JwtService, + ) { } + + // 1) Valida email/password + async validateUser(email: string, pass: string): Promise { + const user = await this.userRepo.findOne({ where: { correo: email }, relations: ['origen'] }); + if (!user) throw new UnauthorizedException('Credenciales inválidas'); + const match = await bcrypt.compare(pass, user.contraseña); + if (!match) throw new UnauthorizedException('Credenciales inválidas'); + return user; + } + + // 2) Genera el JWT + async login(user: UsuariosDelSistema) { + if (!user) + throw new UnauthorizedException('Usuario no encontrado'); + if (!user.origen) + throw new UnauthorizedException('Usuario no encontrado, tiene origen vacío'); + const payload = { sub: user.id_usuario, email: user.correo, tipo: user.origen.origen }; + return { + access_token: this.jwtService.sign(payload), + }; + } + + + + + async register(dto: RegisterDto) { + // 1) Verificar duplicado + const exists = await this.userRepo.findOne({ where: { correo: dto.email } }); + if (exists) { + throw new ConflictException('El correo ya está registrado'); + } + + // 2) Hashear contraseña + const hash = await bcrypt.hash(dto.password, 10); + + let origen = await this.origenRepo.findOne({ where: { origen: dto.origen } }); + + if (!origen) { + origen = this.origenRepo.create({ origen: dto.origen }); + origen = await this.origenRepo.save(origen); + } + + + // 3) Crear entidad Usuario + const user = this.userRepo.create({ + correo: dto.email, + contraseña: hash, + origen: origen, + + }); + + // 4) Guardar en BD + const saved = await this.userRepo.save(user); + + // 5) Devolver sin contraseña + const { contraseña, ...result } = saved; + return result; + } +} diff --git a/src/auth/dto/login.dto.ts b/src/auth/dto/login.dto.ts new file mode 100644 index 0000000..9bb4e2f --- /dev/null +++ b/src/auth/dto/login.dto.ts @@ -0,0 +1,11 @@ +// src/auth/dto/login.dto.ts +import { IsEmail, IsString, MinLength } from 'class-validator'; + +export class LoginDto { + @IsEmail() + email: string; + + @IsString() + @MinLength(6) + password: string; +} diff --git a/src/auth/dto/register.dto.ts b/src/auth/dto/register.dto.ts new file mode 100644 index 0000000..d8bded3 --- /dev/null +++ b/src/auth/dto/register.dto.ts @@ -0,0 +1,15 @@ +import { IsEmail, IsString, MinLength, MaxLength, IsOptional, IsNumber } from 'class-validator'; + +export class RegisterDto { + @IsEmail() + email: string; + + @IsString() + @MinLength(6) + password: string; + + + @IsString() + @MaxLength(60) + origen: string; +} diff --git a/src/auth/jwt.strategy.ts b/src/auth/jwt.strategy.ts new file mode 100644 index 0000000..ddf6e23 --- /dev/null +++ b/src/auth/jwt.strategy.ts @@ -0,0 +1,27 @@ +import { Injectable, UnauthorizedException } from '@nestjs/common'; +import { PassportStrategy } from '@nestjs/passport'; +import { Strategy, ExtractJwt, StrategyOptions } from 'passport-jwt'; +import { ConfigService } from '@nestjs/config'; + +@Injectable() +export class JwtStrategy extends PassportStrategy(Strategy) { + constructor(configService: ConfigService) { + const secret = configService.get('JWT_SECRET'); + if (!secret) { + throw new Error('JWT_SECRET is not defined'); + } + + const opts: StrategyOptions = { + jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), + ignoreExpiration: false, + secretOrKey: secret, + }; + + super(opts); + } + + async validate(payload: any) { + // Devuelve lo que estará disponible en Request.user + return { userId: payload.sub, email: payload.email, origen: payload.tipo }; + } +} diff --git a/src/entities/entities.ts b/src/entities/entities.ts new file mode 100644 index 0000000..5a0ee55 --- /dev/null +++ b/src/entities/entities.ts @@ -0,0 +1,225 @@ +import { ServActivos } from 'src/usuarios/entities/servActivos.entitie'; +import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, OneToMany, JoinColumn, PrimaryColumn, OneToOne } from 'typeorm'; + +@Entity({ name: 'origen' }) +export class Origen { + @PrimaryGeneratedColumn({ name: 'id_origen', type: 'int' }) + id_origen: number; + + @Column({ type: 'varchar', length: 30 }) + origen: string; + + @OneToMany(() => Movimiento, movimiento => movimiento.origen) + movimientos: Movimiento[]; + + @OneToMany(() => UsuariosDelSistema, uds => uds.origen) + usuariosDelSistema: UsuariosDelSistema[]; +} + +@Entity({ name: 'genero' }) +export class Genero { + @PrimaryGeneratedColumn({ name: 'id_genero', type: 'int', }) + id_genero: number; + + @Column({ type: 'varchar', length: 20, nullable: true }) + genero: string; + + @OneToMany(() => Usuario, usuario => usuario.genero) + usuarios: Usuario[]; +} + +@Entity({ name: 'carrera' }) +export class Carrera { + @PrimaryGeneratedColumn({ name: 'id_carrera', type: 'int' }) + id_carrera: number; + + @Column({ type: 'varchar', length: 100 }) + carrera: string; + + @Column({ type: 'varchar', length: 6 }) + clave: string; + + @OneToMany(() => CarreraUsuario, cu => cu.carrera) + carreraUsuarios: CarreraUsuario[]; +} + +@Entity({ name: 'movimiento' }) +export class Movimiento { + @PrimaryGeneratedColumn({ name: 'id_mov', type: 'int' }) + id_mov: number; + + @Column({ name: 'fecha_mov', type: 'datetime' }) + fecha_mov: Date; + + @Column({ type: 'varchar', length: 100 }) + status: string; + + @Column({ + type: 'varchar', + length: 200, + nullable: true, // permite NULL + }) + observaciones?: string; + + @Column({ type: 'bit' }) + flag: boolean; + + @Column({ + type: 'varchar', + length: 200, + nullable: true, // permite NULL + }) + reporte?: string; + + @OneToMany(() => Usuario, usuario => usuario.movimiento) + usuario: Usuario[]; + + + @ManyToOne(() => Origen, origen => origen.movimientos) + @JoinColumn({ name: 'id_origen' }) + origen: Origen; +} + + +@Entity({ name: 'usuario' }) +export class Usuario { + @PrimaryGeneratedColumn({ name: 'id_usuario', type: 'int' }) + id_usuario: number; + + @Column({ + type: 'varchar', + unique: true, + nullable: true, + default: null, + }) + num_cuenta: string; + + @Column({ type: 'varchar', length: 60 }) + nombre: string; + + @Column({ name: 'a_paterno', type: 'varchar', length: 60 }) + a_paterno: string; + + @Column({ name: 'a_materno', type: 'varchar', length: 60 }) + a_materno: string; + + @Column({ + type: 'varchar', + length: 15, + nullable: true, + default: null, + }) + rfc: string | null; + + @Column({ name: 'fecha_nacimiento', type: 'varchar', nullable: true, length: 8 }) + fecha_nacimiento: string | null; + + @Column({ type: 'int', nullable: true, }) + generacion: number | null; + + @ManyToOne(() => Genero, genero => genero.usuarios, { nullable: true }) + @JoinColumn({ name: 'id_genero' }) + genero: Genero | null; + + + @OneToMany(() => CarreraUsuario, cu => cu.usuario) + carreraUsuarios: CarreraUsuario[]; + + @OneToMany(() => UsuarioTipoUsuario, utu => utu.usuario) + usuarioTipos: UsuarioTipoUsuario[]; + + @ManyToOne(() => Movimiento, movimiento => movimiento.usuario) + @JoinColumn({ name: 'id_movimiento' }) + movimiento: Movimiento; + + @OneToOne(() => ServActivos, (servActivo) => servActivo.usuario,) + servActivo: ServActivos; +} + + + +@Entity({ name: 'carrera_usuario' }) +export class CarreraUsuario { + @PrimaryGeneratedColumn({ name: 'id_carrera_usuario', type: 'int' }) + id_carrera_usuario: number; + + @ManyToOne(() => Carrera, carrera => carrera.carreraUsuarios) + @JoinColumn({ name: 'id_carrera' }) + carrera: Carrera; + + @ManyToOne(() => Usuario, usuario => usuario.carreraUsuarios) + @JoinColumn({ name: 'id_usuario' }) + usuario: Usuario; +} + +@Entity({ name: 'usuariosDelSistema' }) +export class UsuariosDelSistema { + @PrimaryGeneratedColumn({ name: 'id_usuario', type: 'int' }) + id_usuario: string; + + @Column({ + type: 'varchar', + length: 100, // ajusta longitud según tu necesidad + unique: true, + nullable: true, + default: null, + }) + correo: string | null; + + @Column({ type: 'varchar', length: 60 }) + contraseña: string; + + @ManyToOne(() => Origen, origen => origen.usuariosDelSistema) + @JoinColumn({ name: 'id_origen' }) + origen: Origen; +} + + + +@Entity({ name: 'equipo' }) +export class Equipo { + @PrimaryGeneratedColumn({ name: 'id_equipo', type: 'int' }) + id_equipo: number; + + @Column({ type: 'varchar', length: 10 }) + equipo: string; + + @Column({ name: 'numero_serie', type: 'varchar', length: 30 }) + numero_serie: string; +} + +@Entity({ name: 'tipo_usuario' }) +export class TipoUsuario { + @PrimaryGeneratedColumn({ name: 'id_tipo_usuario', type: 'int' }) + id_tipo_usuario: number; + + @Column({ name: 'tipo_usuario', type: 'varchar', length: 20 }) + tipo_usuario: string; + + @OneToMany(() => UsuarioTipoUsuario, utu => utu.tipoUsuario) + usuariosTipos: UsuarioTipoUsuario[]; +} + + + + + + + +@Entity({ name: 'usuario_tipo_usuario' }) +export class UsuarioTipoUsuario { + @PrimaryGeneratedColumn({ name: 'id_user_tipo_usuario', type: 'int' }) + id_user_tipo_usuario: number; + + @ManyToOne(() => TipoUsuario, tipo => tipo.usuariosTipos) + @JoinColumn({ name: 'id_tipo_usuario' }) + tipoUsuario: TipoUsuario; + + @ManyToOne(() => Usuario, usuario => usuario.usuarioTipos) + @JoinColumn({ name: 'id_usuario' }) + usuario: Usuario; +} + + +export { ServActivos }; + diff --git a/src/excel/dto/cargaIndividual.dto.ts b/src/excel/dto/cargaIndividual.dto.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/excel/excel.controller.ts b/src/excel/excel.controller.ts new file mode 100644 index 0000000..1554c5e --- /dev/null +++ b/src/excel/excel.controller.ts @@ -0,0 +1,307 @@ +// src/excel/excel.controller.ts +import { Controller, Post, Get, UseGuards, Request, Res, UploadedFile, UseInterceptors, HttpStatus, Param, ParseIntPipe, Body, BadRequestException } from '@nestjs/common'; +import { AuthGuard } from '@nestjs/passport'; +import { ApiBearerAuth } from '@nestjs/swagger'; +import { FileInterceptor } from '@nestjs/platform-express'; +import { Response } from 'express'; +import { ExcelDocumentation } from './excel.documentation'; +import { ExcelService } from './excel.service'; +import { MovimientoService } from '../movimiento/movimiento.service'; +import { MailService } from 'src/mail/mail.service'; +import { CreateUsuarioDto } from 'src/usuarios/dto/create-usuario.dto'; +import { Origen } from 'src/entities/entities'; +import { UsuariosService } from 'src/usuarios/usuarios.service'; + +@UseGuards(AuthGuard('jwt')) +@ApiBearerAuth('bearer') +@Controller('excel') +export class ExcelController { + constructor( + private readonly excelService: ExcelService, + private readonly movimientoService: MovimientoService, + private readonly mailService: MailService, + private readonly usuarioService: UsuariosService // Asegúrate de importar el servicio de correo + ) { } + + + @Post('carga') + async cargaUsuario(@Request() req, @Body() usuario: CreateUsuarioDto) { + console.log('Usuario recibido:', usuario); + if (req.user.origen != 'LOAD') { + throw new BadRequestException('Origen no permitido para carga') + } + const alta = await this.usuarioService.cargaIndividual(usuario, req.user.origen) + console.log('Alta de usuario:', alta); + await this.movimientoService.updateStatus(alta.movId, 'SUCCESS') + + this.mailService.sendMail({ + to: req.user.email, // Asegúrate de que el usuario tenga un email + subject: 'Carga de datos exitosa', + text: "La carga de datos se ha realizado con éxito.", + html: `

La carga de datos se ha realizado con éxito. ` + alta, + }); + + await this.excelService.enviarCargaMasiva(); + + + + } + + + @Post('verify') + @UseInterceptors(FileInterceptor('file')) + @ExcelDocumentation.verifyExcel() + async verifyExcel(@UploadedFile() file: Express.Multer.File, @Request() req) { + const userId: number = req.user.userId; // ahora sí existe + const status = await this.excelService.validateFile(file.buffer); + const errors = status.errors; + await this.movimientoService.log( + + 'VERIFY', + errors.length ? 'FAILED' : 'SUCCESS', + errors.join('; ') + ); + return { valid: errors.length === 0, errors }; + } + + @Post('load') + @UseInterceptors(FileInterceptor('file')) + @ExcelDocumentation.loadExcel() + async loadExcel(@Request() req, @UploadedFile() file: Express.Multer.File) { + const origen = req.user.origen; // ahora sí existe + if (origen != 'LOAD') { + await this.movimientoService.log( + origen, + 'FAILED', + 'Origen no permitido para carga' + ); + throw new Error('Origen no permitido para carga.'); + } + try { + const result = await this.excelService.loadFile(file.buffer); + //Actualizar el estado de los movimientos + if (!result) { + this.mailService.sendMail({ + to: req.user.email, // Asegúrate de que el usuario tenga un email + subject: 'Carga de datos Fallida', + text: "No se pudieron subir datos.", + html: '

No se pudieron subir datos.

', + }); + throw new Error('No se pudo procesar el archivo.'); + } + + if (result.inserted == 0) { + this.mailService.sendMail({ + to: req.user.email, // Asegúrate de que el usuario tenga un email + subject: 'Carga de datos Fallida', + text: "No se pudieron subir datos.", + html: `

Se han insertado ${result.inserted} registros.

+

Errores encontrados:

+
    ${result.errors.map(error => `
  • ${error}
  • `).join('')}
`, + }); + + return result + } + + console.log(result.id_movimiento) + await this.movimientoService.updateStatus( + result.id_movimiento, + 'SUCCESS', + `inserted=${result.inserted}` + ); + + this.mailService.sendMail({ + to: req.user.email, // Asegúrate de que el usuario tenga un email + subject: 'Carga de datos exitosa', + text: "La carga de datos se ha realizado con éxito.", + html: `

La carga de datos se ha realizado con éxito. Se han insertado ${result.inserted} registros.

+

Errores encontrados:

+
    ${result.errors.map(error => `
  • ${error}
  • `).join('')}
`, + }); + + await this.excelService.enviarCargaMasiva(); + + + return result + + } catch (err) { + await this.movimientoService.log( + + 'LOAD', + 'FAILED', + err.message + ); + throw err; + } + } + + @Get('download') + @ExcelDocumentation.downloadExcel() + async downloadData(@Request() req, @Res() res: Response) { + const origen = req.user.origen; // ahora sí existe + console.log('Origen de descarga:', origen); + console.log('Usuario:', req.user); + + if (origen == 'SOLICITA') { + try { + const csv = await this.excelService.generateCsvSolicita(); + await this.movimientoService.log( + + origen, + 'SUCCESS', + 'descarga de csv', + `size=${csv.length}` + ); + return res + .status(HttpStatus.OK) + .header('Content-Type', 'text/csv') + .header('Content-Disposition', 'attachment; filename="usuarios_solicita.csv"') + .send(csv); + } catch (err) { + await this.movimientoService.log( + origen, + 'FAILED', + 'Origen no permitido para descarga' + ); + return res + .status(HttpStatus.FORBIDDEN) + .json({ statusCode: 403, message: 'Origen no permitido para descarga.' }); + } + } else if (origen == 'CORREO' || origen == 'LOAD') { + try { + const csv = await this.excelService.generateCsvCorreo(); + await this.movimientoService.log( + + origen, + 'SUCCESS', + 'descarga de csv', + `size=${csv.length}` + ); + let filename + if (origen == 'CORREO') { + filename = 'usuarios_correo.csv'; + } + else { + filename = 'usuarios.csv'; + } + + return res + .status(HttpStatus.OK) + .header('Content-Type', 'text/csv') + .header('Content-Disposition', `attachment; filename="${filename}"`) + .send(csv); + } catch (err) { + await this.movimientoService.log( + + origen, + 'FAILED', + err.message + ); + return res + .status(HttpStatus.INTERNAL_SERVER_ERROR) + .json({ statusCode: 500, message: 'Error generando descarga.' }); + } + } else if (origen == 'AT') { + try { + const csv = await this.excelService.generateCsvAT(); + await this.movimientoService.log( + + origen, + 'SUCCESS', + 'descarga de csv', + `size=${csv.length}` + ); + return res + .status(HttpStatus.OK) + .header('Content-Type', 'text/csv') + .header('Content-Disposition', 'attachment; filename="usuarios_AT.csv"') + .send(csv); + } catch (err) { + await this.movimientoService.log( + + origen, + 'FAILED', + err.message + ); + return res + .status(HttpStatus.INTERNAL_SERVER_ERROR) + .json({ statusCode: 500, message: 'Error generando descarga.' }); + } + } else if (origen == 'RED') { + try { + const csv = await this.excelService.generateCsvRed(); + await this.movimientoService.log( + + origen, + 'SUCCESS', + 'descarga de csv', + `size=${csv.length}` + ); + return res + .status(HttpStatus.OK) + .header('Content-Type', 'text/csv') + .header('Content-Disposition', 'attachment; filename="usuarios_red.csv"') + .send(csv); + } catch (err) { + await this.movimientoService.log( + + origen, + 'FAILED', + err.message + ); + return res + .status(HttpStatus.INTERNAL_SERVER_ERROR) + .json({ statusCode: 500, message: 'Error generando descarga.' }); + } + } else { + await this.movimientoService.log( + + origen, + 'FAILED', + 'Origen no permitido para descarga' + ); + return res + .status(HttpStatus.FORBIDDEN) + .json({ statusCode: 403, message: 'Origen no permitido para descarga.' }); + } + + } + + //Agregarlo en su propio controller + @Get('movimientos') + async getMovimientos(@Request() req) { + + const movimientos = await this.movimientoService.findAll(req.user.origen); + return movimientos; + } + + @Post('activar-servicios/:id_movimiento') + async activarServicios( + @Param('id_movimiento', ParseIntPipe) id_movimiento: number, + @Request() req, + ): Promise<{ message: string }> { + const origen = req.user.origen; + + if (!origen) { + throw new Error('No se encontró origen en el JWT'); + } + + await this.excelService.activarServicios(id_movimiento, origen); + + return { message: 'Servicios activados correctamente' }; + } + + + @Get('cargaIndividual') + async cargarIndividual( + @Request() req) { + const origen = req.user.origen; // ahora sí existe + return this.movimientoService.findAllCargaIndv(origen); + + } + + + + + +} \ No newline at end of file diff --git a/src/excel/excel.documentation.ts b/src/excel/excel.documentation.ts new file mode 100644 index 0000000..b93d5ca --- /dev/null +++ b/src/excel/excel.documentation.ts @@ -0,0 +1,137 @@ +import { applyDecorators } from '@nestjs/common'; +import { ApiTags, ApiOperation, ApiConsumes, ApiBody, ApiResponse } from '@nestjs/swagger'; + +export class ExcelDocumentation { + /** + * Decorators Swagger para el endpoint POST /excel/verify + */ + static verifyExcel() { + return applyDecorators( + ApiTags('Excel'), + ApiOperation({ + summary: 'Verificar archivo Excel', + description: 'Valida duplicados, filas en blanco y formato básico de los datos sin realizar carga en la base.' + }), + ApiConsumes('multipart/form-data'), + ApiBody({ + schema: { + type: 'object', + properties: { + file: { + type: 'string', + format: 'binary', + description: 'Archivo Excel (.xlsx) con usuarios a validar' + } + } + } + }), + ApiResponse({ + status: 200, + description: 'Resultado de la validación', + schema: { + type: 'object', + properties: { + valid: { type: 'boolean', example: false }, + errors: { + type: 'array', + items: { type: 'string' }, + example: ['Fila 2: cuenta duplicada "42515101".', 'Fila 3: fecha de nacimiento inválida "1988051".'] + } + } + } + }), + ApiResponse({ status: 400, description: 'No se recibió archivo o formato inválido.' }) + ); + } + + /** + * Decorators Swagger para el endpoint POST /excel/load + */ + static loadExcel() { + return applyDecorators( + ApiTags('Excel'), + ApiOperation({ + summary: 'Cargar archivo Excel', + description: 'Valida y persiste los datos en la base de datos. Retorna el número de registros insertados.' + }), + ApiConsumes('multipart/form-data'), + ApiBody({ + schema: { + type: 'object', + properties: { + file: { + type: 'string', + format: 'binary', + description: 'Archivo Excel (.xlsx) con usuarios a cargar' + } + } + } + }), + ApiResponse({ + status: 201, + description: 'Usuarios insertados correctamente', + schema: { + type: 'object', + properties: { + inserted: { type: 'number', example: 3 } + } + } + }), + ApiResponse({ + status: 400, + description: 'Errores de validación o carga', + schema: { + type: 'object', + properties: { + errors: { + type: 'array', + items: { type: 'string' }, + example: ['Fila 5: rfc contiene caracteres inválidos.'] + } + } + } + }) + ); + } + + + + + + + static downloadExcel() { + return applyDecorators( + ApiTags('Excel'), + //ApiBearerAuth('bearer'), + ApiOperation({ + summary: 'Descargar usuarios', + description: 'Exporta todos los usuarios en un archivo TSV (o CSV) y registra el movimiento.' + }), + ApiResponse({ + status: 200, + description: 'TSV generado correctamente', + content: { + 'text/tab-separated-values': { + schema: { type: 'string', example: 'num_cuenta\\tnombre\\t...\\n...' } + } + } + }), + ApiResponse({ status: 401, description: 'No autorizado' }), + ApiResponse({ status: 500, description: 'Error al generar descarga' }), + ); + } + + + + + + + + + + + + + + +} diff --git a/src/excel/excel.module.ts b/src/excel/excel.module.ts new file mode 100644 index 0000000..8fcef3b --- /dev/null +++ b/src/excel/excel.module.ts @@ -0,0 +1,22 @@ +import { Module } from '@nestjs/common'; +import { ExcelService } from './excel.service'; +import { ExcelController } from './excel.controller'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { Usuario, Genero, Carrera, CarreraUsuario, UsuarioTipoUsuario, TipoUsuario, UsuariosDelSistema } from '../entities/entities'; +import { MovimientoModule } from 'src/movimiento/movimiento.module'; +import { ServActivos } from 'src/usuarios/entities/servActivos.entitie'; +import { MailModule } from 'src/mail/mail.module'; +import { UsuariosService } from 'src/usuarios/usuarios.service'; + + +@Module({ + imports: [ + TypeOrmModule.forFeature([Usuario, Genero, Carrera, CarreraUsuario, UsuarioTipoUsuario, TipoUsuario, ServActivos, UsuariosDelSistema]), + MovimientoModule, + MailModule, + + ], + providers: [ExcelService, UsuariosService], + controllers: [ExcelController], +}) +export class ExcelModule { } diff --git a/src/excel/excel.service.ts b/src/excel/excel.service.ts new file mode 100644 index 0000000..05e156c --- /dev/null +++ b/src/excel/excel.service.ts @@ -0,0 +1,755 @@ +// src/excel/excel.service.ts +import { Injectable, BadRequestException, Logger } from '@nestjs/common'; +import { Workbook } from 'exceljs'; +import { DataSource, In, Repository } from 'typeorm'; +import { InjectRepository } from '@nestjs/typeorm'; +import { CarreraUsuario, TipoUsuario, Usuario, UsuariosDelSistema, UsuarioTipoUsuario } from '../entities/entities'; +import { Genero } from '../entities/entities'; +import { Carrera } from '../entities/entities'; +import { MovimientoService } from 'src/movimiento/movimiento.service'; +import { ServActivos } from 'src/usuarios/entities/servActivos.entitie'; +import { MailService } from 'src/mail/mail.service'; + +export interface UsuarioRow { + cuenta: string; + nombreCompleto: string; + clave: string; + nomCarr: string; + gen: string; // generación + fechnac: string; // YYYYMMDD + apellidopa: string; + apellidoma: string; + nombres: string; + sexo: string; // m/f o texto + tipo: string; + correo: string; + rfc: string; +} + +@Injectable() +export class ExcelService { + private readonly logger = new Logger(ExcelService.name); + + constructor( + @InjectRepository(Usuario) + private readonly usuarioRepo: Repository, + @InjectRepository(Genero) + private readonly generoRepo: Repository, + @InjectRepository(Carrera) + private readonly carreraRepo: Repository, + @InjectRepository(CarreraUsuario) + private readonly carreraUsuarioRepo: Repository, + @InjectRepository(UsuarioTipoUsuario) + private readonly usuarioTipoUsuarioRepo: Repository, + @InjectRepository(TipoUsuario) + private readonly tipoUsuarioRepo: Repository, + @InjectRepository(ServActivos) + private readonly servActivosRepo: Repository, + @InjectRepository(UsuariosDelSistema) + private readonly usuariosDelSistemaRepo: Repository, + private readonly movimientoService: MovimientoService, + private readonly mailService: MailService, + private readonly dataSource: DataSource + + // … inyecta otros repositorios si los usarás + ) { } + + /** Lee el buffer del Excel y devuelve un arreglo de filas tipadas */ + private async parseFile(buffer: Buffer): Promise { + const wb = new Workbook(); + await wb.xlsx.load(buffer); + const sheet = wb.worksheets[0]; + const rows: UsuarioRow[] = []; + + // Asume que la primera fila es encabezados + sheet.eachRow((row, idx) => { + if (idx === 1) return; // salto encabezados + + // 1) Asegurarnos de que row.values no sea null/undefined + if (!row.values) { + this.logger.warn(`Fila ${idx + 1}: row.values vacío, se omite.`); + return; + } + + // 2) row.values[0] es null, así que slice(1) sí existe + const [ + cuenta, + nombreCompleto, + clave, + nomCarr, + gen, + fechnac, + apellidopa, + apellidoma, + nombres, + sexo, + tipo, + correo, + rfc, + ] = (row.values as any[]).slice(1); + + rows.push({ + cuenta, + nombreCompleto, + clave, + nomCarr, + gen, + fechnac, + apellidopa, + apellidoma, + nombres, + sexo, + tipo, + correo, + rfc, + }); + }); + + return rows; + } + + /** Valida duplicados, celdas vacías y patrones básicos */ + validateRows(rows: UsuarioRow[]) { + const errors: string[] = []; + const seen = new Set(); + const rowsGood: UsuarioRow[] = []; + let flagError: boolean = false; + + rows.forEach((r, i) => { + const rowNum = i + 2; // por el encabezado + + // Campos que no pueden quedar vacíos + ['cuenta', 'tipo'].forEach(field => { + const raw = (r as any)[field]; + const str = raw != null ? raw.toString().trim() : ''; + if (!str) { + errors.push(`Fila ${rowNum}: campo "${field}" está vacío.`); + flagError = true; + } + }); + + // El número de cuenta debe ser de nueve dígitos para los alumnos + if (r.cuenta && !/^\d{9}$/.test(r.cuenta.toString().trim()) && r.tipo.trim() === 'Licenciatura') { + errors.push(`Fila ${rowNum}: cuenta "${r.cuenta}" debe tener 9 dígitos.`); + flagError = true; + } + + + + // Duplicados de "cuenta" + const cuentaStr = r.cuenta != null ? r.cuenta.toString().trim() : ''; + if (seen.has(cuentaStr)) { + errors.push(`Fila ${rowNum}: cuenta duplicada "${cuentaStr}".`); + flagError = true; + } else { + seen.add(cuentaStr); + } + + + + + const genStr = r.gen != null ? r.gen.toString().trim() : ''; + if (genStr && !/^[0-9]{4}$/.test(genStr)) { + errors.push(`Fila ${rowNum}: generación inválida "${genStr}".`); + flagError = true; + } + + + // Fecha en formato YYYYMMDD + const fechaStr = r.fechnac != null ? r.fechnac.toString().trim() : ''; + if (!/^[0-9]{8}$/.test(fechaStr)) { + errors.push(`Fila ${rowNum}: fecha de nacimiento inválida "${fechaStr}".`); + flagError = true; + } + + // RFC alfanumérico + const rfcStr = r.rfc != null ? r.rfc.toString().trim() : ''; + if (rfcStr && !/^[A-Z0-9]+$/.test(rfcStr)) { + errors.push(`Fila ${rowNum}: RFC contiene caracteres no válidos.`); + flagError = true; + } + + if (!flagError) { + rowsGood.push(r) + } else { + flagError = false; // reset para la siguiente fila + } + + + + }); + + console.log(rowsGood); + + return { errors, rowsGood }; + } + + + /** + * Solo valida: retorna lista de errores (vacío = ok) + */ + async validateFile(buffer: Buffer) { + const rows = await this.parseFile(buffer); + const status = this.validateRows(rows); + return status; + } + + /** + * Valida y luego persiste usuarios y relaciones básicas + */ + async loadFile(buffer: Buffer) { + const file = await this.parseFile(buffer); + const status = this.validateRows(file); + const errs = status.errors; + const rows = status.rowsGood; + + if (rows.length === 0) { + throw new BadRequestException('No se encontraron filas válidas para cargar. ' + errs); + } + + const queryRunner = this.dataSource.createQueryRunner(); + await queryRunner.connect(); + await queryRunner.startTransaction(); + + try { + const movimiento = await this.movimientoService.logger( + queryRunner, + 'LOAD', + 'LOADING', + undefined, + 'CARGA MASIVA DE USUARIOS' + ); + + let contador = 0; + + for (const r of rows) { + + const whereCond: Array<{ num_cuenta?: string; rfc?: string }> = [{ num_cuenta: r.cuenta }]; + if (r.rfc) { + whereCond.push({ rfc: r.rfc }); + } + + const userExists = await queryRunner.manager.findOne(Usuario, { + where: whereCond, + }); + + + + if (userExists) { + errs.push(`Cuenta ${r.cuenta} ya existe, se omite.`); + contador++; + continue; + } + + if (!r.tipo || !r.nombres || !r.apellidopa || !r.apellidoma) { + errs.push(`Fila con cuenta ${r.cuenta} tiene campos faltantes.`); + contador++; + continue; + } + + let genero = await queryRunner.manager.findOne(Genero, { where: { genero: r.sexo } }); + if (!genero) { + genero = queryRunner.manager.create(Genero, { genero: r.sexo }); + await queryRunner.manager.save(genero); + } + + let carrera + if (r.clave || r.nomCarr) { + carrera = await queryRunner.manager.findOne(Carrera, { where: { clave: r.clave } }); + if (!carrera) { + carrera = queryRunner.manager.create(Carrera, { + clave: r.clave, + carrera: r.nomCarr.slice(0, 100), + }); + await queryRunner.manager.save(carrera); + } + + } else { + carrera = null; + } + + let tipo = await queryRunner.manager.findOne(TipoUsuario, { + where: { tipo_usuario: r.tipo.trim() }, + }); + if (!tipo) { + tipo = queryRunner.manager.create(TipoUsuario, { tipo_usuario: r.tipo.trim() }); + await queryRunner.manager.save(tipo); + } + + const generacion = /^[0-9]{4}$/.test(r.gen?.toString().trim()) + ? parseInt(r.gen.toString().trim(), 10) + : null; + + const usuario = queryRunner.manager.create(Usuario, { + num_cuenta: r.cuenta, + nombre: r.nombres.trim().split(' ')[0], + a_paterno: r.apellidopa, + a_materno: r.apellidoma, + rfc: r.rfc, + fecha_nacimiento: r.fechnac, + generacion, + genero, + movimiento, + }); + const user = await queryRunner.manager.save(usuario); + + if (carrera) { + const carreraUsuario = queryRunner.manager.create(CarreraUsuario, { + usuario: user, + carrera, + }); + await queryRunner.manager.save(carreraUsuario); + } + + const usuarioTipo = queryRunner.manager.create(UsuarioTipoUsuario, { + usuario: user, + tipoUsuario: tipo, + }); + await queryRunner.manager.save(usuarioTipo); + + const servData = { + usuario: user, + RedStatus: 'Inactivo', + ATStatus: 'Inactivo', + CorreoStatus: 'Inactivo', + PrestamosStatus: 'Inactivo', + }; + + switch (r.tipo.trim()) { + case 'Diplomado': + Object.assign(servData, { Correo: true }); + break; + case 'Extra Largo': + Object.assign(servData, { Correo: true, Prestamos: true }); + break; + case 'Servicio Social': + Object.assign(servData, { Correo: true, AT: true, Red: true }); + break; + case 'Idiomas R (UNAM)': + case 'Idiomas Sabatino': + Object.assign(servData, { Correo: true, Red: true }); + break; + case 'Reinscrito': + case 'Posgrado': + case 'Intercambio UNAM': + case 'Movilidad': + case 'Ampliación de Conocimiento': + case 'Licenciatura': + case 'Profesor': + Object.assign(servData, { Correo: true, AT: true, Red: true, Prestamos: true }); + break; + case 'Trabajadores': + Object.assign(servData, { Red: true }); + break; + } + + const serv = queryRunner.manager.create(ServActivos, servData); + await queryRunner.manager.save(serv); + } + + await queryRunner.commitTransaction(); + return { inserted: rows.length - contador, id_movimiento: movimiento.id_mov, errors: errs }; + } catch (err) { + await queryRunner.rollbackTransaction(); + throw err; + } finally { + await queryRunner.release(); + } + } + + + + + + + // async generateCsv(): Promise { + // const users = await this.usuarioRepo.find({ + // relations: ['genero', 'carreraUsuarios'], // si quieres más info + // select: ['num_cuenta', 'nombre', 'a_paterno', 'a_materno', 'rfc', 'fecha_nacimiento', 'generacion'], + // }); + + // const header = [ + // 'num_cuenta', 'nombre', 'a_paterno', 'a_materno', + // 'carrera', 'rfc', 'fecha_nacimiento', 'generacion' + // ].join('\t') + '\n'; + + // const rows = users.map(u => [ + // u.num_cuenta, + // u.nombre, + // u.a_paterno, + // u.a_materno, + // u.rfc ?? '', + // u.fecha_nacimiento, + // (u.generacion != null ? u.generacion.toString() : '') + + // ].join('\t')).join('\n'); + + // return header + rows; + // } + + + async generateCsvCorreo(): Promise { + + + + const tipos = [ + 'Diplomado', + 'Extra Largo', + 'Ampliación de Conocimiento', + 'Servicio Social', + 'Movilidad', + 'Intercambio UNAM', + 'Idiomas Sabatino', + 'Idiomas R (UNAM)', + 'Trabajadores', + 'Profesor', + 'Oyente', + 'Posgrado', + 'Reinscrito', + 'Licenciatura', + ]; + + const usuarios = await this.usuarioRepo.find({ + where: { + usuarioTipos: { + tipoUsuario: { + tipo_usuario: In(tipos), + }, + }, + servActivo: { + CorreoStatus: 'Inactivo', + Correo: true, + }, + }, + relations: [ + 'genero', + 'carreraUsuarios', + 'carreraUsuarios.carrera', + 'usuarioTipos', + 'usuarioTipos.tipoUsuario', + ], + }); + + const header = [ + 'num_cuenta', 'nombre', 'a_paterno', 'a_materno', + 'carreras', 'rfc', 'fecha_nacimiento', 'generacion', 'tipos_usuario' + ].join(',') + '\n'; + + const rows = usuarios.map(u => { + const carreras = (u.carreraUsuarios ?? []) + .map(cu => cu.carrera?.carrera ?? '') + .join(' | '); + + const tiposUsuario = (u.usuarioTipos ?? []) + .map(utu => utu.tipoUsuario?.tipo_usuario ?? '') + .join(' | '); + + return [ + u.num_cuenta ?? '', + u.nombre ?? '', + u.a_paterno ?? '', + u.a_materno ?? '', + carreras, + u.rfc ?? '', + u.fecha_nacimiento ?? '', + u.generacion?.toString() ?? '', + tiposUsuario + ].join(','); + }).join('\n'); + + const tablaCompleta = header + rows; + return tablaCompleta; + + } + + async generateCsvSolicita(): Promise { + + + + const tipos = [ + + 'Extra Largo', + 'Ampliación de Conocimiento', + 'Movilidad', + 'Intercambio UNAM', + 'Profesor', + 'Posgrado', + 'Reinscrito', + 'Licenciatura', + ]; + + const usuarios = await this.usuarioRepo.find({ + where: { + usuarioTipos: { + tipoUsuario: { + tipo_usuario: In(tipos), + }, + }, + servActivo: { + PrestamosStatus: 'Inactivo', // solo los que no tienen servicio activo + Prestamos: true, // solo los que tienen el servicio de préstamos activo + } + }, + relations: [ + 'genero', + 'carreraUsuarios', // relación intermedia + 'carreraUsuarios.carrera', // carrera asociada a través de la intermedia + 'usuarioTipos', + 'usuarioTipos.tipoUsuario', + ], + + }); + const header = [ + 'num_cuenta', 'nombre', 'a_paterno', 'a_materno', + 'carreras', 'rfc', 'tipos_usuario' + ].join(',') + '\n'; + + const rows = usuarios.map(u => { + const carreras = (u.carreraUsuarios ?? []) + .map(cu => cu.carrera?.carrera ?? '') // obtenemos solo el nombre + .join(' | '); // une todas las carreras en un solo string + + const tiposUsuario = (u.usuarioTipos ?? []) + .map(utu => utu.tipoUsuario?.tipo_usuario ?? '') + .join(' | '); // une todos los tipos de usuario en un solo string + + return [ + u.num_cuenta ?? '', + u.nombre ?? '', + u.a_paterno ?? '', + u.a_materno ?? '', + carreras ?? '', + u.rfc ?? '', + tiposUsuario + ].join(','); + }).join('\n'); + + const tablaCompleta = header + rows; + + return tablaCompleta; + } + + async generateCsvRed(): Promise { + + + + const tipos = [ + 'Ampliación de Conocimiento', + 'Servicio Social', + 'Movilidad', + 'Intercambio UNAM', + 'Idiomas Sabatino', + 'Idiomas R (UNAM)', + 'Trabajadores', + 'Profesor', + 'Posgrado', + 'Reinscrito', + 'Licenciatura', + ]; + + const usuarios = await this.usuarioRepo.find({ + where: { + usuarioTipos: { + tipoUsuario: { + tipo_usuario: In(tipos), + }, + }, + servActivo: { + RedStatus: 'Inactivo', + Red: true, // solo los que tienen el servicio de red activo + } + }, + relations: [ + 'genero', + 'carreraUsuarios', // relación intermedia + 'carreraUsuarios.carrera', // carrera asociada a través de la intermedia + + ], + + }); + + + + + const header = [ + 'num_cuenta', 'fecha_nacimiento' + ].join(',') + '\n'; + + const rows = usuarios.map(u => { + return [ + u.num_cuenta, + u.fecha_nacimiento, + ].join(','); + }).join('\n'); + + const tablaCompleta = header + rows; + console.log(tablaCompleta); + console.log('Usuarios encontrados:', usuarios); + return tablaCompleta; + } + + async generateCsvAT(): Promise { + + + + const tipos = [ + 'Ampliación de Conocimiento', + 'Servicio Social', + 'Movilidad', + 'Intercambio UNAM', + 'Profesor', + 'Posgrado', + 'Reinscrito', + 'Licenciatura', + ]; + + const usuarios = await this.usuarioRepo.find({ + where: { + usuarioTipos: { + tipoUsuario: { + tipo_usuario: In(tipos), + }, + }, + servActivo: { + ATStatus: 'Inactivo', + AT: true + }, + }, + relations: [ + 'genero', + 'carreraUsuarios', // relación intermedia + 'carreraUsuarios.carrera', // carrera asociada a través de la intermedia + 'usuarioTipos', + 'usuarioTipos.tipoUsuario', + ], + + }); + + + + + const header = [ + 'num_cuenta', 'nombre', 'a_paterno', 'a_materno', + 'carreras', 'fecha_nacimiento', 'generacion', 'tipos_usuario' + ].join(',') + '\n'; + + const rows = usuarios.map(u => { + const carreras = (u.carreraUsuarios ?? []) + .map(cu => cu.carrera?.carrera ?? '') // obtenemos solo el nombre + .join(' | '); // une todas las carreras en un solo string + + const tiposUsuario = (u.usuarioTipos ?? []) + .map(utu => utu.tipoUsuario?.tipo_usuario ?? '') + .join(' | '); // une todos los tipos de usuario en un solo string + + return [ + u.num_cuenta ?? '', + u.nombre ?? '', + u.a_paterno ?? '', + u.a_materno ?? '', + carreras ?? '', + u.fecha_nacimiento ?? '', + u.generacion?.toString() ?? '', + tiposUsuario + ].join(','); + }).join('\n'); + + const tablaCompleta = header + rows; + + return tablaCompleta; + } + + + + //Funcion que se le debe hacer su propio módulo + async activarServicios(id_movimiento: number, origen: string): Promise { + if (!['AT', 'SOLICITA', 'CORREO', 'RED'].includes(origen)) { + throw new BadRequestException(`Origen ${origen} no válido`); + } + + const usuarios = await this.usuarioRepo.find({ + where: { movimiento: { id_mov: id_movimiento } }, + }); + + if (!usuarios || usuarios.length === 0) { + throw new BadRequestException('No se encontró el usuario asociado al movimiento'); + } + + for (const usuario of usuarios) { + const servActivos = await this.servActivosRepo.findOne({ + where: { usuario: { id_usuario: usuario.id_usuario } }, + }); + + if (!servActivos) { + throw new BadRequestException( + `No se encontraron servicios activos para el usuario con ID ${usuario.id_usuario}`, + ); + } + + const timestamp = 'Activo ' + new Date().toISOString(); + + if (origen === 'AT' && servActivos.AT) { + servActivos.ATStatus = timestamp; + } else if (origen === 'RED' && servActivos.Red) { + servActivos.RedStatus = timestamp; + } else if (origen === 'CORREO' && servActivos.Correo) { + servActivos.CorreoStatus = timestamp; + } else if (origen === 'SOLICITA' && servActivos.Prestamos) { + servActivos.PrestamosStatus = timestamp; + } else { + continue; + } + + await this.servActivosRepo.save(servActivos); + console.log(`Servicio ${servActivos}`); + } + + } + + async enviarCargaMasiva(): Promise { + const usuarios_red = await this.usuariosDelSistemaRepo.findOne({ where: { origen: { origen: 'RED' } } }); + const usuarios_at = await this.usuariosDelSistemaRepo.findOne({ where: { origen: { origen: 'AT' } } }); + const usuarios_correo = await this.usuariosDelSistemaRepo.findOne({ where: { origen: { origen: 'CORREO' } } }); + const usuarios_solicita = await this.usuariosDelSistemaRepo.findOne({ where: { origen: { origen: 'SOLICITA' } } }); + if (!usuarios_red || !usuarios_at || !usuarios_correo || !usuarios_solicita) { + throw new BadRequestException('No se encontraron usuarios para enviar correo'); + } + + // Aquí puedes enviar el correo usando MailService + await this.mailService.sendMail({ + to: usuarios_red.correo ?? '', + subject: 'Carga de Usuarios', + text: 'Se ha realizado una carga de usuarios en el sistema.', + html: '', + }); + await this.mailService.sendMail({ + to: usuarios_at.correo ?? '', + subject: 'Carga de Usuarios', + text: 'Se ha realizado una carga de usuarios en el sistema.', + html: '', + }); + await this.mailService.sendMail({ + to: usuarios_correo.correo ?? '', + subject: 'Carga de Usuarios', + text: 'Se ha realizado una carga de usuarios en el sistema.', + html: '', + }); + await this.mailService.sendMail({ + to: usuarios_solicita.correo ?? '', + subject: 'Carga de Usuarios', + text: 'Se ha realizado una carga de usuarios en el sistema.', + html: '', + }); + + + + console.log(`Correo enviados`); + } + + + + + + + +} diff --git a/src/helpers/exception.filter.ts b/src/helpers/exception.filter.ts new file mode 100644 index 0000000..12a8305 --- /dev/null +++ b/src/helpers/exception.filter.ts @@ -0,0 +1,54 @@ +import { + Catch, + ArgumentsHost, + ExceptionFilter, + HttpException, +} from '@nestjs/common'; +import { Response } from 'express'; + +@Catch() +export class GlobalExceptionFilter implements ExceptionFilter { + catch(exception: any, host: ArgumentsHost) { + const ctx = host.switchToHttp(); + const response = ctx.getResponse(); + + let status = 500; // Valor por defecto para errores no controlados + let message = 'Internal server error'; + + if (exception instanceof HttpException) { + status = exception.getStatus(); + const responseBody = exception.getResponse(); + + // Evitar el problema de circular structure usando JSON-safe conversion + message = + typeof responseBody === 'object' + ? this.safeStringify(responseBody) + : (responseBody as string); + } else { + // Puedes mapear el error a un código HTTP y mensaje adecuados + status = 400; + message = (exception as any).sqlMessage || exception.message; + + console.error('Unhandled Exception:', exception); // Log de errores no controlados + } + + response.status(status).json({ + statusCode: status, + message, + timestamp: new Date().toISOString(), + path: ctx.getRequest().url, + }); + } + + // ✅ Método para manejar objetos circulares usando WeakSet + private safeStringify(obj: any): string { + const seen = new WeakSet(); + return JSON.stringify(obj, (key, value) => { + if (typeof value === 'object' && value !== null) { + if (seen.has(value)) return '[Circular]'; + seen.add(value); + } + return value; + }); + } +} \ No newline at end of file diff --git a/src/mail/dto/send-email.dto.ts b/src/mail/dto/send-email.dto.ts new file mode 100644 index 0000000..1ecf6fe --- /dev/null +++ b/src/mail/dto/send-email.dto.ts @@ -0,0 +1,25 @@ +import { IsString, IsEmail, IsDate, IsOptional } from 'class-validator'; +import { Type } from 'class-transformer'; + +export class SendCorreoDto { + @IsEmail() + to: string; + + @IsString() + @IsOptional() + subject: string; + + @IsString() + @IsOptional() + text: string; + + + + @IsString() + @IsOptional() + html: string; + + + + +} \ No newline at end of file diff --git a/src/mail/mail.controller.ts b/src/mail/mail.controller.ts new file mode 100644 index 0000000..bae8558 --- /dev/null +++ b/src/mail/mail.controller.ts @@ -0,0 +1,31 @@ +// src/excel/excel.controller.ts +import { Controller, Post, Get, UseGuards, Request, Res, UploadedFile, UseInterceptors, HttpStatus, Param, ParseIntPipe, Body } from '@nestjs/common'; +import { SendCorreoDto } from './dto/send-email.dto'; +import { MailService } from './mail.service'; + + +@Controller('mail') +export class MailController { + constructor( + private readonly mailService: MailService + ) { } + + @Post('send') + async sendMail(@Body() body: SendCorreoDto) { + const result = + await this.mailService.sendMail(body) + .then((value) => { + console.log("Se mandaron los correos", value); + + }) + .catch((err) => { + console.log(err) + }) + + + // sistem + return result; + + + } +} \ No newline at end of file diff --git a/src/mail/mail.module.ts b/src/mail/mail.module.ts new file mode 100644 index 0000000..2018716 --- /dev/null +++ b/src/mail/mail.module.ts @@ -0,0 +1,12 @@ +// src/movimiento/movimiento.module.ts +import { Module } from '@nestjs/common'; +import { MailService } from './mail.service'; +import { MailController } from './mail.controller'; + +@Module({ + imports: [], + controllers: [MailController], + providers: [MailService], + exports: [MailService], +}) +export class MailModule { } diff --git a/src/mail/mail.service.ts b/src/mail/mail.service.ts new file mode 100644 index 0000000..8b0a898 --- /dev/null +++ b/src/mail/mail.service.ts @@ -0,0 +1,76 @@ +import { Injectable, UnauthorizedException } from '@nestjs/common'; +import * as nodemailer from 'nodemailer'; +import { SendCorreoDto } from 'src/mail/dto/send-email.dto'; + + + + +@Injectable() +export class MailService { + + async sendMail(sendEmail: SendCorreoDto) { + + + + + + + const transporter = nodemailer.createTransport({ + + + service: 'gmail', + auth: { + type: 'OAuth2', + user: process.env.EMAIL_USER, // tu correo de Gmail + clientId: process.env.CLIENT_ID, // tu Client ID de OAuth2 + clientSecret: process.env.CLIENT_SECRET, // tu Client Secret de OAuth2 + refreshToken: process.env.REFRESH_TOKEN, // tu Refresh Token de OAuth2 + //accessToken: sistem.accessToken, // opcional + }, + pool: true, + maxConnections: 1, + maxMessages: 100, + rateDelta: 2000, + rateLimit: 1 + + + }); + + + + + + + const mailOptions = { + from: process.env.EMAIL_USER, // tu correo de Gmail + to: sendEmail.to, + subject: sendEmail.subject, + text: sendEmail.text, + html: sendEmail.html, + }; + + + console.log(mailOptions); + + + + + let resMail = await transporter.sendMail(mailOptions); + if (!resMail) throw new Error("") + + + const statusTexto = resMail.accepted.length > 0 ? "Enviado" : "Fallido"; + + + + if (!resMail) { + throw new Error("fallo") + } + + + return (statusTexto); + } + + + +} diff --git a/src/main.ts b/src/main.ts index f76bc8d..5eeb055 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,8 +1,64 @@ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; +import { ValidationPipe } from '@nestjs/common'; +import { GlobalExceptionFilter } from './helpers/exception.filter'; +import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger'; async function bootstrap() { const app = await NestFactory.create(AppModule); - await app.listen(process.env.PORT ?? 3000); + app.enableCors({ exposedHeaders: ['Content-Disposition'] }); + + app.useGlobalPipes( + new ValidationPipe({ + transform: true, + }), + ); + + app.useGlobalFilters(new GlobalExceptionFilter()); + + const config = new DocumentBuilder() + .setTitle('Documentacion de la api de carga masiva de CEDETEC') + .setDescription( + 'El objetivo es mejorar la gestion interna y la integridad de los datos', + ) + .setVersion('1.0') + .addBearerAuth( + { + type: 'http', + scheme: 'bearer', + bearerFormat: 'JWT', + name: 'Authorization', + in: 'header', + }, + 'bearer', // nombre del security definition + ) + .build(); + // .addServer(process.env.SWAGGER_SERVER_URL) // descomentar parael servidor de acatlan + + const document = SwaggerModule.createDocument(app, config); + + SwaggerModule.setup('documentation', app, document); + + const port = process.env.API_PORT || 4000; + await app.listen(port, '0.0.0.0'); + + console.log( + `[main] Aplicación iniciada en el puerto ${port} a las ${new Date().toISOString()}`, + ); + console.log( + `\n\x1b[36m=========================================================\x1b[0m`, + ); + console.log(`\x1b[36m SISTEMA DE CARGA MASIVA - API\x1b[0m`); + console.log( + `\x1b[36m=========================================================\x1b[0m`, + ); + console.log(`\x1b[32m✅ API corriendo en:\x1b[0m http://localhost:${port}`); + console.log( + `\x1b[32m✅ Documentación disponible en:\x1b[0m http://localhost:${port}/documentation`, + ); + + console.log( + `\x1b[36m=========================================================\x1b[0m\n`, + ); } bootstrap(); diff --git a/src/movimiento/movimiento.module.ts b/src/movimiento/movimiento.module.ts new file mode 100644 index 0000000..c93d9ad --- /dev/null +++ b/src/movimiento/movimiento.module.ts @@ -0,0 +1,12 @@ +// src/movimiento/movimiento.module.ts +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { MovimientoService } from './movimiento.service'; +import { Movimiento, Origen, Usuario } from '../entities/entities'; + +@Module({ + imports: [TypeOrmModule.forFeature([Movimiento, Origen, Usuario])], + providers: [MovimientoService], + exports: [MovimientoService], +}) +export class MovimientoModule { } diff --git a/src/movimiento/movimiento.service.ts b/src/movimiento/movimiento.service.ts new file mode 100644 index 0000000..337632c --- /dev/null +++ b/src/movimiento/movimiento.service.ts @@ -0,0 +1,254 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { QueryRunner, Repository } from 'typeorm'; +import { Movimiento, Usuario } from '../entities/entities'; +import { Origen } from '../entities/entities'; + +@Injectable() +export class MovimientoService { + constructor( + @InjectRepository(Movimiento) + private readonly movRepo: Repository, + @InjectRepository(Origen) + private readonly origenRepo: Repository, + @InjectRepository(Usuario) + private readonly usuarioRepo: Repository, + ) { } + + /** Crea un registro de movimiento */ + async log( + origen: string, + status: string, + observaciones?: string, + reporte?: string, + flag = false, + ): Promise { + // 1) Obtén el origen o falla si no existe + const origin = await this.origenRepo.findOne({ where: { origen } }); + let origenRepo = origin; + if (!origin) { + const newOrigin = this.origenRepo.create({ origen }); + origenRepo = await this.origenRepo.save(newOrigin); + } + if (!origenRepo) { + throw new Error(`No se pudo obtener o crear el origen: ${origen}`); + } + + // 2) Crea el objeto movimiento + const movimiento = this.movRepo.create({ + fecha_mov: new Date(), + status, + observaciones, + reporte, + flag, + origen: { id_origen: origenRepo.id_origen }, + }); + + // 3) Guarda y devuelve el ID + const saved = await this.movRepo.save(movimiento); + return saved; + } + + async logger( + queryRunner: QueryRunner, + origenNombre: string, + status: string, + observaciones?: string, + reporte?: string + ): Promise { + const origenEntidad = await queryRunner.manager.findOne(Origen, { + where: { origen: origenNombre }, + }); + + if (!origenEntidad) { + throw new Error(`Origen no encontrado: ${origenNombre}`); + } + + const nuevoMov = queryRunner.manager.create(Movimiento, { + fecha_mov: new Date(), + status, + observaciones, + reporte, + origen: origenEntidad, + flag: false, + }); + + return await queryRunner.manager.save(nuevoMov); + } + + + + + /** Actualiza el status de un movimiento existente */ + async updateStatus(id_movimiento: number, nuevoStatus: string, observaciones?: string): Promise { + const movimiento = await this.movRepo.findOne({ where: { id_mov: id_movimiento } }); + + if (!movimiento) { + throw new Error(`Movimiento con ID ${id_movimiento} no encontrado`); + } + + movimiento.status = nuevoStatus; + movimiento.observaciones = observaciones || movimiento.observaciones; // opcional: actualizar observaciones + movimiento.fecha_mov = new Date(); // opcional: actualizar fecha + + await this.movRepo.save(movimiento); + } + + async findAll(origen: string): Promise<{ move: any[]; button: boolean }> { + const statusFieldMap = { + AT: 'ATStatus', + RED: 'RedStatus', + CORREO: 'CorreoStatus', + SOLICITA: 'PrestamosStatus', + } as const; + + const FieldMap = { + AT: 'AT', + RED: 'Red', + CORREO: 'Correo', + SOLICITA: 'Prestamos', + } as const; + + + + const field = statusFieldMap[origen as keyof typeof statusFieldMap]; + const origenField = FieldMap[origen as keyof typeof FieldMap]; + + if (!field) { + const movimientos = await this.movRepo.find({ + where: { + origen: { origen: 'LOAD' }, + status: 'SUCCESS', + reporte: 'CARGA MASIVA DE USUARIOS', + + }, + }); + + const mov = await this.movRepo.find({ + where: { + origen: { origen: 'SISTEMA' }, + status: 'SUCCESS', + reporte: 'CARGA MASIVA WEB SERVICE', + + }, + }); + + const mov2 = await this.movRepo.find({ + where: { + + reporte: 'CARGA INDIVIDUAL DE USUARIOS', + status: 'SUCCESS', + + + }, + }); + console.log('movimientos:', mov2) + + + + + const todosLosMovimientos = [...movimientos, ...mov, ...mov2]; + console.log(todosLosMovimientos) + return { move: todosLosMovimientos, button: false }; + } + + const movimientos = await this.movRepo.find({ + where: { + origen: { origen: 'LOAD' }, + reporte: 'CARGA MASIVA DE USUARIOS', + status: 'SUCCESS', + usuario: { + servActivo: { + [origenField]: true, + + [field]: 'Inactivo' + }, + }, + }, + }); + const mov = await this.movRepo.find({ + where: { + origen: { origen: 'SISTEMA' }, + reporte: 'CARGA MASIVA WEB SERVICE', + status: 'SUCCESS', + usuario: { + servActivo: { + [origenField]: true, + [field]: 'Inactivo' + }, + }, + }, + }); + const mov2 = await this.movRepo.find({ + where: { + + reporte: 'CARGA INDIVIDUAL DE USUARIOS', + status: 'SUCCESS', + usuario: { + servActivo: { + [origenField]: true, + [field]: 'Inactivo' + }, + }, + }, + relations: ['usuario'], + }); + const todosLosMovimientos = [...movimientos, ...mov, ...mov2]; + + return { move: todosLosMovimientos, button: true }; + } + + async findAllCargaIndv(origen): Promise { + + const statusFieldMap = { + AT: 'ATStatus', + RED: 'RedStatus', + CORREO: 'CorreoStatus', + SOLICITA: 'PrestamosStatus', + } as const; + + const FieldMap = { + AT: 'AT', + RED: 'Red', + CORREO: 'Correo', + SOLICITA: 'Prestamos', + } as const; + + + + + + const field = statusFieldMap[origen as keyof typeof statusFieldMap]; + + const origenField = FieldMap[origen as keyof typeof FieldMap]; + + if (!field) { + throw new Error(`Origen desconocido: ${origen}`); + } + + const usuarios = await this.usuarioRepo.find({ + where: { + movimiento: { + + reporte: 'CARGA INDIVIDUAL DE USUARIOS', + }, + + servActivo: { + [field]: 'Inactivo', + [origenField]: true, + }, + + }, + }); + + + return usuarios; + } + + + + + + + +} diff --git a/src/usuarios/dto/create-usuario.dto.ts b/src/usuarios/dto/create-usuario.dto.ts new file mode 100644 index 0000000..27578fa --- /dev/null +++ b/src/usuarios/dto/create-usuario.dto.ts @@ -0,0 +1,46 @@ +import { IsNumber, IsOptional, IsString, Length } from "class-validator"; + +export class CreateUsuarioDto { + @IsOptional() + @IsString() + num_cuenta?: string; + + @IsOptional() + @IsString() + carrera?: string; + + + @IsString() + tipo_usuario: string; + + @IsOptional() + @IsString() + clave_carrera?: string; + + @IsString() + nombre: string; + + @IsString() + a_paterno: string; + + @IsString() + a_materno: string; + + @IsOptional() + @IsString() + @Length(10, 13) + rfc?: string; + + @IsOptional() + @IsString() + @Length(8, 8) + fecha_nacimiento?: string; + + @IsOptional() + @IsNumber() + generacion?: number; + + @IsOptional() + @IsString() + genero?: string; +} \ No newline at end of file diff --git a/src/usuarios/dto/update-usuario.dto.ts b/src/usuarios/dto/update-usuario.dto.ts new file mode 100644 index 0000000..74d19e2 --- /dev/null +++ b/src/usuarios/dto/update-usuario.dto.ts @@ -0,0 +1,4 @@ +import { PartialType } from '@nestjs/swagger'; +import { CreateUsuarioDto } from './create-usuario.dto'; + +export class UpdateUsuarioDto extends PartialType(CreateUsuarioDto) {} diff --git a/src/usuarios/entities/servActivos.entitie.ts b/src/usuarios/entities/servActivos.entitie.ts new file mode 100644 index 0000000..d3980ba --- /dev/null +++ b/src/usuarios/entities/servActivos.entitie.ts @@ -0,0 +1,39 @@ +import { Usuario } from "src/entities/entities"; +import { Column, Entity, JoinColumn, OneToOne, PrimaryGeneratedColumn } from "typeorm"; + +@Entity({ name: 'servActivos' }) +export class ServActivos { + @PrimaryGeneratedColumn({ name: 'id_servActivos' }) + id_servActivos: number; + + @OneToOne(() => Usuario, (usuario) => usuario.servActivo, { onDelete: 'CASCADE' }) + @JoinColumn({ name: 'id_usuario' }) + usuario: Usuario; + + + @Column({ type: 'boolean', default: false }) + Red: boolean; + + @Column({ type: 'boolean', default: false }) + Prestamos: boolean; + + @Column({ type: 'boolean', default: false }) + AT: boolean; + + @Column({ type: 'boolean', default: false }) + Correo: boolean; + + @Column({ type: 'varchar', length: 40, nullable: true }) + RedStatus: string; + + @Column({ type: 'varchar', length: 40, nullable: true }) + PrestamosStatus: string; + + @Column({ type: 'varchar', length: 40, nullable: true }) + ATStatus: string; + + @Column({ type: 'varchar', length: 40, nullable: true }) + CorreoStatus: string; + + +} diff --git a/src/usuarios/usuarios.controller.ts b/src/usuarios/usuarios.controller.ts new file mode 100644 index 0000000..9625640 --- /dev/null +++ b/src/usuarios/usuarios.controller.ts @@ -0,0 +1,48 @@ +import { Controller, Get, Post, Body, Patch, Param, Delete, UseGuards } from '@nestjs/common'; +import { UsuariosService } from './usuarios.service'; +import { CreateUsuarioDto } from './dto/create-usuario.dto'; +import { UpdateUsuarioDto } from './dto/update-usuario.dto'; +import { AuthGuard } from '@nestjs/passport'; +import { ApiBearerAuth } from '@nestjs/swagger'; + + +@Controller('usuarios') +export class UsuariosController { + constructor(private readonly usuariosService: UsuariosService) { } + + + + @Post('users') + finsdAll() { + return this.usuariosService.enviarSolicitud(); + } + + @Get(':noCuenta') + findOne(@Param('noCuenta') noCuenta: string) { + return this.usuariosService.findOneStatus(noCuenta); + } + + + + // @UseGuards(AuthGuard('jwt')) + // @ApiBearerAuth('bearer') + // @Post() + // create(@Body() noCuenta: string) { + // return this.usuariosService.altaIndividual(createUsuarioDto); + // } + + // @Get() + // findAll() { + // return this.usuariosService.findAll(); + // } + + // @Patch(':id') + // update(@Param('id') id: string, @Body() updateUsuarioDto: UpdateUsuarioDto) { + // return this.usuariosService.update(+id, updateUsuarioDto); + // } + + // @Delete(':id') + // remove(@Param('id') id: string) { + // return this.usuariosService.remove(+id); + // } +} diff --git a/src/usuarios/usuarios.module.ts b/src/usuarios/usuarios.module.ts new file mode 100644 index 0000000..ff4062c --- /dev/null +++ b/src/usuarios/usuarios.module.ts @@ -0,0 +1,21 @@ +import { Module } from '@nestjs/common'; +import { UsuariosService } from './usuarios.service'; +import { UsuariosController } from './usuarios.controller'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { Carrera, CarreraUsuario, Genero, TipoUsuario, Usuario, UsuariosDelSistema, UsuarioTipoUsuario } from 'src/entities/entities'; +import { MovimientoModule } from 'src/movimiento/movimiento.module'; +import { ServActivos } from './entities/servActivos.entitie'; +import { MailService } from 'src/mail/mail.service'; +import { ExcelService } from 'src/excel/excel.service'; + +@Module({ + imports: [ + TypeOrmModule.forFeature([Usuario, Genero, Carrera, CarreraUsuario, ServActivos, TipoUsuario, UsuarioTipoUsuario, UsuariosDelSistema]), + MovimientoModule + + ], + controllers: [UsuariosController], + providers: [UsuariosService, MailService, ExcelService], + exports: [TypeOrmModule.forFeature([ServActivos])], +}) +export class UsuariosModule { } diff --git a/src/usuarios/usuarios.service.ts b/src/usuarios/usuarios.service.ts new file mode 100644 index 0000000..9d6583d --- /dev/null +++ b/src/usuarios/usuarios.service.ts @@ -0,0 +1,635 @@ +import { Injectable, Logger, NotFoundException } from '@nestjs/common'; +import { CreateUsuarioDto } from './dto/create-usuario.dto'; +import { UpdateUsuarioDto } from './dto/update-usuario.dto'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Carrera, CarreraUsuario, Genero, TipoUsuario, Usuario, UsuarioTipoUsuario } from 'src/entities/entities'; +import { Repository } from 'typeorm'; +import { ServActivos } from './entities/servActivos.entitie'; +import { MovimientoService } from 'src/movimiento/movimiento.service'; +import { MailService } from 'src/mail/mail.service'; +import { error } from 'console'; +import { Cron } from '@nestjs/schedule'; +import { ExcelService } from 'src/excel/excel.service'; + + + + +@Injectable() +export class UsuariosService { + private readonly logger = new Logger(UsuariosService.name); + + constructor( + @InjectRepository(Usuario) + private readonly usuarioRepository: Repository, + @InjectRepository(Carrera) + private readonly carreraRepository: Repository, + @InjectRepository(ServActivos) + private readonly servActivosRepository: Repository, + @InjectRepository(TipoUsuario) + private readonly tipoUsuarioRepository: Repository, + @InjectRepository(Genero) + private readonly generoRepository: Repository, + @InjectRepository(CarreraUsuario) + private readonly carreraUsuario: Repository, + @InjectRepository(UsuarioTipoUsuario) + private readonly usuarioTipoUsuario: Repository, + private readonly movimientoService: MovimientoService, + private readonly mailService: MailService, + private readonly excelService: ExcelService, // Asegúrate de importar el servicio de Excel + + ) { } + + altaIndividual(createUsuarioDto: CreateUsuarioDto) { + return 'This action adds a new usuario'; + } + + async findOneStatus(noCuenta: string) { + const usuario = await this.usuarioRepository.findOne({ + where: { num_cuenta: noCuenta }, + + }); + + if (!usuario) { + throw new NotFoundException('Usuario no encontrado'); + } + + const servActivo = await this.servActivosRepository.findOne({ + where: { usuario: { id_usuario: usuario.id_usuario } }, + }); + + if (!servActivo) { + throw new NotFoundException('Servicios no encontrados para el usuario'); + } + + const resultado: { + servicio: string; + status: string; + fecha_activacion: string | null; + }[] = []; + + const mapServicio = (nombre: string, estado: string | undefined) => { + if (!estado) return; + + if (estado.startsWith('Activo')) { + const fechaCompleta = estado.substring(7); // "2025-06-25T14:26:21.427Z" + const fechaSolo = fechaCompleta.substring(0, 10); // "2025-06-25" + resultado.push({ + servicio: nombre, + status: 'Activo', + fecha_activacion: fechaSolo, + }); + } else { + resultado.push({ + servicio: nombre, + status: 'Inactivo', + fecha_activacion: null, + }); + } + }; + + + await mapServicio('Red', servActivo.RedStatus); + await mapServicio('Correo', servActivo.CorreoStatus); + await mapServicio('Préstamo de equipo PC Puma', servActivo.PrestamosStatus); + await mapServicio('Préstamo de equipo en CEDETEC', servActivo.ATStatus); + + + + if (resultado.length === 0) { + return [{ + servicio: 'Ninguno', + status: 'No hay servicios activos para este usuario', + fecha_activacion: null, + }]; + } + + + + + return { resultado, usuario: usuario.nombre }; + } + + + + + update(id: number, updateUsuarioDto: UpdateUsuarioDto) { + return `This action updates a #${id} usuario`; + } + + + + + + @Cron('0 0 0 10,25 * *') + async tareaMensual() { + this.logger.log('Ejecutando consulta mensual de profesores'); + await this.enviarSolicitud(); // tu función de importación + } + + async enviarSolicitud() { + const mov = await this.movimientoService.log( + 'SISTEMA', + 'LOADING', + 'Enviando solicitud de consulta masiva de profesores al WebService', + 'CARGA MASIVA WEB SERVICE' + ); + + + interface DatosEntrada { + Nombre: string; + ApellidoPaterno: string; + ApellidoMaterno: string; + NumeroTrabajador: string; + RFC: string; + } + + interface DatosRespuesta { + numeroTrabajador: string; + nombre: string; + apellidoPaterno: string; + apellidoMaterno: string; + nombreCarrera: string; + fechaNacimiento: string; + rfc: string; + genero: string; + correoPersonal: string; + nombramiento: string; + unidadResponsable: string; + curp: string; + } + + const apiUrl = process.env.API_URL; + if (!apiUrl) { + console.error("API_URL no está definida en las variables de entorno"); + return; + } + + const entrada: DatosEntrada = { + Nombre: "", + ApellidoPaterno: "", + ApellidoMaterno: "", + NumeroTrabajador: "", + RFC: "" + }; + + try { + const response = await fetch(apiUrl, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(entrada) + }); + + if (response.status === 400) { + const errorText = await response.text(); + console.error("Error 400:", errorText); + return; + } else if (response.status === 204) { + console.log("No se encontraron datos conforme a los criterios de búsqueda"); + return; + } + + if (!response.ok) throw new Error("Error en la petición: " + response.statusText); + + const json: DatosRespuesta[] = await response.json(); + + let nuevos = 0; + let actualizados = 0; + + let existe = await this.tipoUsuarioRepository.findOne({ where: { tipo_usuario: 'Profesor' } }); + if (!existe) { + const tipoUsuario = this.tipoUsuarioRepository.create({ tipo_usuario: 'Profesor' }); + existe = await this.tipoUsuarioRepository.save(tipoUsuario); + } + + for (const prof of json) { + try { + if (!prof.rfc || !prof.nombre) { + console.warn('Profesor con datos incompletos detectado:', prof); + continue; + } + + const existente = await this.usuarioRepository.findOne({ + where: { rfc: prof.rfc }, + relations: ['genero'] + }); + + if (!existente) { + let genero: Genero | null = null; + if (prof.genero === 'M' || prof.genero === 'F') { + const generoTexto = prof.genero === 'M' ? 'Masculino' : 'Femenino'; + genero = await this.generoRepository.findOne({ where: { genero: generoTexto } }); + if (!genero) { + genero = await this.generoRepository.create({ genero: generoTexto }); + await this.generoRepository.save(genero); + } + } + + let carrera = await this.carreraRepository.findOne({ where: { carrera: prof.nombreCarrera.trim() } }) + + if (!carrera) { + const nuevaCarrera = await this.carreraRepository.create({ carrera: prof.nombreCarrera.trim(), clave: '' }); + carrera = await this.carreraRepository.save(nuevaCarrera); + } + + + + const nuevo = await this.usuarioRepository.create({ + num_cuenta: prof.numeroTrabajador == '' ? undefined : prof.numeroTrabajador, + nombre: prof.nombre, + a_paterno: prof.apellidoPaterno, + a_materno: prof.apellidoMaterno, + rfc: prof.rfc, + fecha_nacimiento: prof.fechaNacimiento, + genero, + + movimiento: mov + }); + + const savedUser = await this.usuarioRepository.save(nuevo); + + const servActivo = await this.servActivosRepository.create({ + ATStatus: 'Inactivo', + RedStatus: 'Inactivo', + PrestamosStatus: 'Inactivo', + CorreoStatus: 'Inactivo', + usuario: savedUser, + AT: prof.numeroTrabajador !== '', + Red: prof.numeroTrabajador !== '', + Prestamos: prof.numeroTrabajador !== '', + Correo: true + }); + + await this.servActivosRepository.save(servActivo); + + const userCarrera = await this.carreraUsuario.create({ carrera, usuario: savedUser }); + const usuarioTipo = await this.usuarioTipoUsuario.create({ tipoUsuario: existe, usuario: savedUser }); + + await this.carreraUsuario.save(userCarrera); + await this.usuarioTipoUsuario.save(usuarioTipo); + + nuevos++; + } else { + let cambios = false; + + + if (existente.nombre !== prof.nombre) { + existente.nombre = prof.nombre; + const servActivo = await this.servActivosRepository.findOne({ where: { usuario: { id_usuario: existente.id_usuario } } }); + + + if (servActivo) { + servActivo.Red = true; + servActivo.AT = true; + servActivo.Prestamos = true; + + + servActivo.ATStatus = 'Inactivo'; + servActivo.PrestamosStatus = 'Inactivo'; + servActivo.RedStatus = 'Inactivo'; + await this.servActivosRepository.save(servActivo); + } else { + console.warn('No se encontró el servicio activo para el usuario:', existente); + } + cambios = true; + } + + if (existente.a_paterno !== prof.apellidoPaterno) { + existente.a_paterno = prof.apellidoPaterno; + const servActivo = await this.servActivosRepository.findOne({ where: { usuario: { id_usuario: existente.id_usuario } } }); + + if (servActivo) { + servActivo.Red = true; + servActivo.AT = true; + servActivo.Prestamos = true; + + + servActivo.ATStatus = 'Inactivo'; + servActivo.PrestamosStatus = 'Inactivo'; + servActivo.RedStatus = 'Inactivo'; + await this.servActivosRepository.save(servActivo); + } else { + console.warn('No se encontró el servicio activo para el usuario:', existente); + } + + cambios = true; + } + + + if (existente.a_materno !== prof.apellidoMaterno) { + + existente.a_materno = prof.apellidoMaterno; + const servActivo = await this.servActivosRepository.findOne({ where: { usuario: { id_usuario: existente.id_usuario } } }); + + if (servActivo) { + servActivo.Red = true; + servActivo.AT = true; + servActivo.Prestamos = true; + + + servActivo.ATStatus = 'Inactivo'; + servActivo.PrestamosStatus = 'Inactivo'; + servActivo.RedStatus = 'Inactivo'; + await this.servActivosRepository.save(servActivo); + } else { + console.warn('No se encontró el servicio activo para el usuario:', existente); + } + + cambios = true; + } + + if (existente.rfc !== prof.rfc) { + existente.rfc = prof.rfc; + const servActivo = await this.servActivosRepository.findOne({ where: { usuario: { id_usuario: existente.id_usuario } } }); + + if (servActivo) { + servActivo.Red = true; + + servActivo.Prestamos = true; + + + servActivo.PrestamosStatus = 'Inactivo'; + servActivo.RedStatus = 'Inactivo'; + + await this.servActivosRepository.save(servActivo); + } else { + console.warn('No se encontró el servicio activo para el usuario:', existente); + } + cambios = true; + } + + if (existente.fecha_nacimiento !== prof.fechaNacimiento) { + existente.fecha_nacimiento = prof.fechaNacimiento; + const servActivo = await this.servActivosRepository.findOne({ where: { usuario: { id_usuario: existente.id_usuario } } }); + + if (servActivo) { + + + servActivo.Prestamos = true; + servActivo.Correo = true; + servActivo.PrestamosStatus = 'Inactivo'; + servActivo.CorreoStatus = 'Inactivo'; + + await this.servActivosRepository.save(servActivo); + } else { + console.warn('No se encontró el servicio activo para el usuario:', existente); + } + cambios = true; + } + + if (prof.numeroTrabajador && existente.num_cuenta !== prof.numeroTrabajador) { + existente.num_cuenta = prof.numeroTrabajador; + + + const servActivo = await this.servActivosRepository.findOne({ where: { usuario: { id_usuario: existente.id_usuario } } }); + if (servActivo) { + servActivo.Red = true; + servActivo.AT = true; + servActivo.Correo = true; + servActivo.Prestamos = true; + + servActivo.CorreoStatus = 'Inactivo'; + servActivo.ATStatus = 'Inactivo'; + servActivo.PrestamosStatus = 'Inactivo'; + servActivo.RedStatus = 'Inactivo'; + await this.servActivosRepository.save(servActivo); + } else { + console.warn('No se encontró el servicio activo para el usuario:', existente); + } + cambios = true; + } + + if (cambios) { + existente.movimiento = mov; + await this.usuarioRepository.save(existente); + + actualizados++; + } + } + + console.log(`Progreso -> Nuevos: ${nuevos}, Actualizados: ${actualizados}`); + } catch (innerError) { + console.error('Error procesando profesor:', prof, innerError); + } + } + this.movimientoService.updateStatus(mov.id_mov, 'SUCCESS', 'Carga masiva de web Service completa: ' + `Nuevos: ${nuevos}, Actualizados: ${actualizados}`) + if (!process.env.EMAIL_USER) { + throw new Error() + } + this.mailService.sendMail({ + to: process.env.EMAIL_USER, + subject: 'Carga de datos exitosa', + text: 'Carga masiva de web Service completa: ' + `Nuevos: ${nuevos}, Actualizados: ${actualizados}`, + html: '

' + }) + + this.excelService.enviarCargaMasiva(); + + return `Importación finalizada. Nuevos: ${nuevos}, Actualizados: ${actualizados}`; + + } catch (error) { + this.logger.error('Error al consultar profesores desde el WebService', error); + throw error; + } + } + + + async cargaIndividual(usuario: CreateUsuarioDto, origen: string) { + const queryRunner = this.usuarioRepository.manager.connection.createQueryRunner(); + await queryRunner.connect(); + await queryRunner.startTransaction(); + + try { + const existente = await queryRunner.manager.findOne(Usuario, { + where: { num_cuenta: usuario.num_cuenta }, + relations: ['genero'], + }); + + if (existente) { + throw new Error('Ya existe este usuario'); + } + + const mov = await this.movimientoService.logger(queryRunner, origen, 'LOADING', undefined, 'CARGA INDIVIDUAL DE USUARIOS'); + + let genero: Genero | null = null; + if (usuario.genero === 'M' || usuario.genero === 'F') { + const generoTexto = usuario.genero === 'M' ? 'Masculino' : 'Femenino'; + genero = await queryRunner.manager.findOne(Genero, { where: { genero: generoTexto } }); + if (!genero) { + genero = queryRunner.manager.create(Genero, { genero: generoTexto }); + await queryRunner.manager.save(genero); + } + } + + let carrera = await queryRunner.manager.findOne(Carrera, { where: { carrera: usuario.carrera } }); + if (!carrera) { + const nuevaCarrera = queryRunner.manager.create(Carrera, { carrera: usuario.carrera, clave: usuario.clave_carrera }); + carrera = await queryRunner.manager.save(nuevaCarrera); + } + + + const nuevo = queryRunner.manager.create(Usuario, { + num_cuenta: usuario.num_cuenta, + nombre: usuario.nombre, + a_paterno: usuario.a_paterno, + a_materno: usuario.a_materno, + rfc: usuario.rfc ?? null, + fecha_nacimiento: usuario.fecha_nacimiento, + generacion: usuario.generacion ?? null, + genero, + movimiento: mov, + }); + + const savedUser = await queryRunner.manager.save(nuevo); + + const userCarrera = queryRunner.manager.create(CarreraUsuario, { carrera, usuario: savedUser }); + + const FieldMap = [ + 'Diplomado', 'Extra Largo', 'Servicio Social', 'Idiomas R (UNAM)', 'Idiomas Sabatino', + 'Reinscrito', 'Posgrado', 'Intercambio UNAM', 'Movilidad', 'Ampliación de Conocimiento', + 'Licenciatura', 'Profesor', 'Trabajadores', + ]; + + if (!FieldMap.includes(usuario.tipo_usuario)) { + throw new Error(`Tipo de usuario no permitido: ${usuario.tipo_usuario}`); + } + + // Verifica si el tipo de usuario ya existe + let tipoUsuario = await queryRunner.manager.findOne(TipoUsuario, { + where: { tipo_usuario: usuario.tipo_usuario }, + }); + + // Si no existe, lo crea y guarda + if (!tipoUsuario) { + tipoUsuario = queryRunner.manager.create(TipoUsuario, { + tipo_usuario: usuario.tipo_usuario, + }); + tipoUsuario = await queryRunner.manager.save(tipoUsuario); + } + + // Luego crea la relación con el usuario ya guardado + const usuarioTipo = queryRunner.manager.create(UsuarioTipoUsuario, { + tipoUsuario, + usuario: savedUser, + }); + await queryRunner.manager.save(usuarioTipo); + + + + + + switch (usuario.tipo_usuario.trim()) { + + case 'Diplomado': + + const servActivosDiplomado = this.servActivosRepository.create({ + Correo: true, + usuario: savedUser, + RedStatus: 'Inactivo', + ATStatus: 'Inactivo', + CorreoStatus: 'Inactivo', + PrestamosStatus: 'Inactivo', + }); + await queryRunner.manager.save(servActivosDiplomado); + + break; + + case 'Extra Largo': + const servActivosExtraLargo = this.servActivosRepository.create({ + Prestamos: true, + Correo: true, + usuario: savedUser, + RedStatus: 'Inactivo', + ATStatus: 'Inactivo', + CorreoStatus: 'Inactivo', + PrestamosStatus: 'Inactivo', + + }); + await queryRunner.manager.save(servActivosExtraLargo); + break; + + case 'Servicio Social': + const servActivosServicio = this.servActivosRepository.create({ + Red: true, + AT: true, + Correo: true, + usuario: savedUser, + RedStatus: 'Inactivo', + ATStatus: 'Inactivo', + CorreoStatus: 'Inactivo', + PrestamosStatus: 'Inactivo', + }); + await queryRunner.manager.save(servActivosServicio); + break; + + + + case 'Idiomas R (UNAM)': + case 'Idiomas Sabatino': + const servActivosIdiomas = this.servActivosRepository.create({ + Red: true, + Correo: true, + usuario: savedUser, + RedStatus: 'Inactivo', + ATStatus: 'Inactivo', + CorreoStatus: 'Inactivo', + PrestamosStatus: 'Inactivo', + }); + await queryRunner.manager.save(servActivosIdiomas); + + break; + + + + + case 'Reinscrito': + case 'Posgrado': + case 'Intercambio UNAM': + case 'Movilidad': + case 'Ampliación de Conocimiento': + case 'Licenciatura': + case 'Profesor': + const servActivos = this.servActivosRepository.create({ + + Red: true, + AT: true, + Correo: true, + Prestamos: true, + usuario: savedUser, + RedStatus: 'Inactivo', + ATStatus: 'Inactivo', + CorreoStatus: 'Inactivo', + PrestamosStatus: 'Inactivo', + }); + await queryRunner.manager.save(servActivos); + + break; + + + + case 'Trabajadores': + const servActivosTrabajadores = this.servActivosRepository.create({ + Red: true, + usuario: savedUser, + RedStatus: 'Inactivo', + + }); + await queryRunner.manager.save(servActivosTrabajadores); + break; + } + + + console.log(savedUser) + await queryRunner.commitTransaction(); + + return { saved: savedUser, movId: mov.id_mov }; + + } catch (error) { + await queryRunner.rollbackTransaction(); + throw error; + } finally { + await queryRunner.release(); + } + } +} + +