diff --git a/package-lock.json b/package-lock.json index 5e57e9d..22b081e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,13 +26,15 @@ "class-transformer": "^0.5.1", "class-validator": "^0.14.1", "dotenv": "^16.4.7", + "express": "^5.1.0", + "multer": "^1.4.5-lts.2", "mysql2": "^3.14.0", "passport": "^0.7.0", "passport-jwt": "^4.0.1", "qrcode": "^1.5.4", "reflect-metadata": "^0.2.2", "rxjs": "^7.8.1", - "typeorm": "^0.3.21" + "typeorm": "^0.3.22" }, "devDependencies": { "@eslint/eslintrc": "^3.2.0", @@ -44,6 +46,7 @@ "@swc/core": "^1.10.7", "@types/express": "^5.0.0", "@types/jest": "^29.5.14", + "@types/multer": "^1.4.12", "@types/node": "^22.10.7", "@types/qrcode": "^1.5.5", "@types/supertest": "^6.0.2", @@ -2441,6 +2444,189 @@ "@nestjs/core": "^11.0.0" } }, + "node_modules/@nestjs/platform-express/node_modules/content-disposition": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", + "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@nestjs/platform-express/node_modules/debug": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", + "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", + "license": "MIT", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@nestjs/platform-express/node_modules/express": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.0.1.tgz", + "integrity": "sha512-ORF7g6qGnD+YtUG9yx4DFoqCShNMmUKiXuT5oWMHiOvt/4WFbHC6yCwQMTSBMno7AqntNCAzzcnnjowRkTL9eQ==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.0.1", + "content-disposition": "^1.0.0", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "^1.2.1", + "debug": "4.3.6", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "^2.0.0", + "fresh": "2.0.0", + "http-errors": "2.0.0", + "merge-descriptors": "^2.0.0", + "methods": "~1.1.2", + "mime-types": "^3.0.0", + "on-finished": "2.4.1", + "once": "1.4.0", + "parseurl": "~1.3.3", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "router": "^2.0.0", + "safe-buffer": "5.2.1", + "send": "^1.1.0", + "serve-static": "^2.1.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "^2.0.0", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@nestjs/platform-express/node_modules/express/node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@nestjs/platform-express/node_modules/express/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@nestjs/platform-express/node_modules/express/node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@nestjs/platform-express/node_modules/express/node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@nestjs/platform-express/node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@nestjs/platform-express/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@nestjs/platform-express/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@nestjs/platform-express/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "license": "MIT" + }, + "node_modules/@nestjs/platform-express/node_modules/multer": { + "version": "1.4.5-lts.1", + "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.1.tgz", + "integrity": "sha512-ywPWvcDMeH+z9gQq5qYHCCy+ethsk4goepZ45GLD63fOu0YcNecQxi64nDs3qluZB+murG3/D4dJ7+dGctcCQQ==", + "license": "MIT", + "dependencies": { + "append-field": "^1.0.0", + "busboy": "^1.0.0", + "concat-stream": "^1.5.2", + "mkdirp": "^0.5.4", + "object-assign": "^4.1.1", + "type-is": "^1.6.4", + "xtend": "^4.0.0" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/@nestjs/platform-express/node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/@nestjs/schematics": { "version": "11.0.2", "resolved": "https://registry.npmjs.org/@nestjs/schematics/-/schematics-11.0.2.tgz", @@ -3323,6 +3509,16 @@ "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", "license": "MIT" }, + "node_modules/@types/multer": { + "version": "1.4.12", + "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.12.tgz", + "integrity": "sha512-pQ2hoqvXiJt2FP9WQVLPRO+AmiIm/ZYkavPlIQnx282u4ZrVdztx0pkh3jjpQt0Kz+YI0YhSG264y08UJKoUQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express": "*" + } + }, "node_modules/@types/node": { "version": "22.13.10", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.10.tgz", @@ -4172,6 +4368,7 @@ "version": "3.16.0", "resolved": "https://registry.npmjs.org/ansis/-/ansis-3.16.0.tgz", "integrity": "sha512-sU7d/tfZiYrsIAXbdL/CNZld5bCkruzwT5KmqmadCJYxuLxHAOBjidxD5+iLmN/6xEfjcQq1l7OpsiCBlc4LzA==", + "dev": true, "license": "ISC", "engines": { "node": ">=14" @@ -4552,16 +4749,16 @@ } }, "node_modules/body-parser": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.1.0.tgz", - "integrity": "sha512-/hPxh61E+ll0Ujp24Ilm64cykicul1ypfwjVttduAiEdtnJFvLePSrIPk+HMImtNv5270wOGCb1Tns2rybMkoQ==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", + "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", "license": "MIT", "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.0", "http-errors": "^2.0.0", - "iconv-lite": "^0.5.2", + "iconv-lite": "^0.6.3", "on-finished": "^2.4.1", "qs": "^6.14.0", "raw-body": "^3.0.0", @@ -5447,16 +5644,6 @@ "node": ">= 0.8" } }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "license": "MIT", - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, "node_modules/detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -6011,46 +6198,45 @@ } }, "node_modules/express": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/express/-/express-5.0.1.tgz", - "integrity": "sha512-ORF7g6qGnD+YtUG9yx4DFoqCShNMmUKiXuT5oWMHiOvt/4WFbHC6yCwQMTSBMno7AqntNCAzzcnnjowRkTL9eQ==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", + "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", "license": "MIT", "dependencies": { "accepts": "^2.0.0", - "body-parser": "^2.0.1", + "body-parser": "^2.2.0", "content-disposition": "^1.0.0", - "content-type": "~1.0.4", - "cookie": "0.7.1", + "content-type": "^1.0.5", + "cookie": "^0.7.1", "cookie-signature": "^1.2.1", - "debug": "4.3.6", - "depd": "2.0.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "^2.0.0", - "fresh": "2.0.0", - "http-errors": "2.0.0", + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", - "methods": "~1.1.2", "mime-types": "^3.0.0", - "on-finished": "2.4.1", - "once": "1.4.0", - "parseurl": "~1.3.3", - "proxy-addr": "~2.0.7", - "qs": "6.13.0", - "range-parser": "~1.2.1", - "router": "^2.0.0", - "safe-buffer": "5.2.1", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", "send": "^1.1.0", - "serve-static": "^2.1.0", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "^2.0.0", - "utils-merge": "1.0.1", - "vary": "~1.1.2" + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" }, "engines": { "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/express/node_modules/content-disposition": { @@ -6065,29 +6251,21 @@ "node": ">= 0.6" } }, - "node_modules/express/node_modules/debug": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", - "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", - "license": "MIT", + "node_modules/express/node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "license": "BSD-3-Clause", "dependencies": { - "ms": "2.1.2" + "side-channel": "^1.1.0" }, "engines": { - "node": ">=6.0" + "node": ">=0.6" }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/express/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "license": "MIT" - }, "node_modules/ext-list": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/ext-list/-/ext-list-2.2.2.tgz", @@ -7067,12 +7245,12 @@ } }, "node_modules/iconv-lite": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.5.2.tgz", - "integrity": "sha512-kERHXvpSaB4aU3eANwidg79K8FlrN77m8G9V+0vOR3HYaRifrlwMEpT7ZBJqLSEIHnEgJTHcWK82wwLwwKwtag==", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "license": "MIT", "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" + "safer-buffer": ">= 2.1.2 < 3.0.0" }, "engines": { "node": ">=0.10.0" @@ -8650,12 +8828,12 @@ } }, "node_modules/mime-types": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.0.tgz", - "integrity": "sha512-XqoSHeCGjVClAmoGFG3lVFqQFRIrTVw2OH3axRqAcfaw+gHWIfnASS92AV+Rl/mk0MupgZTRHQOjxY6YVnzK5w==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", "license": "MIT", "dependencies": { - "mime-db": "^1.53.0" + "mime-db": "^1.54.0" }, "engines": { "node": ">= 0.6" @@ -8734,9 +8912,9 @@ "license": "MIT" }, "node_modules/multer": { - "version": "1.4.5-lts.1", - "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.1.tgz", - "integrity": "sha512-ywPWvcDMeH+z9gQq5qYHCCy+ethsk4goepZ45GLD63fOu0YcNecQxi64nDs3qluZB+murG3/D4dJ7+dGctcCQQ==", + "version": "1.4.5-lts.2", + "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.2.tgz", + "integrity": "sha512-VzGiVigcG9zUAoCNU+xShztrlr1auZOlurXynNvO9GiWD1/mTBbUljOKY+qMeazBqXgRnjzeEgJI/wyjJUHg9A==", "license": "MIT", "dependencies": { "append-field": "^1.0.0", @@ -8824,18 +9002,6 @@ "node": ">= 8.0" } }, - "node_modules/mysql2/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/named-placeholders": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.3.tgz", @@ -9807,18 +9973,6 @@ "node": ">= 0.8" } }, - "node_modules/raw-body/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/react-is": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", @@ -10022,11 +10176,13 @@ } }, "node_modules/router": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/router/-/router-2.1.0.tgz", - "integrity": "sha512-/m/NSLxeYEgWNtyC+WtNHCF7jbGxOibVWKnn+1Psff4dJGOfoXP+MuC/f2CwSmyiHdOIzYnYFp4W6GxWfekaLA==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", "license": "MIT", "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", "is-promise": "^4.0.0", "parseurl": "^1.3.3", "path-to-regexp": "^8.0.0" @@ -10179,19 +10335,18 @@ } }, "node_modules/send": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/send/-/send-1.1.0.tgz", - "integrity": "sha512-v67WcEouB5GxbTWL/4NeToqcZiAWEq90N888fczVArY8A79J0L4FD7vj5hm3eUMua5EpoQ59wa/oovY6TLvRUA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", + "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", "license": "MIT", "dependencies": { "debug": "^4.3.5", - "destroy": "^1.2.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", - "fresh": "^0.5.2", + "fresh": "^2.0.0", "http-errors": "^2.0.0", - "mime-types": "^2.1.35", + "mime-types": "^3.0.1", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", @@ -10201,36 +10356,6 @@ "node": ">= 18" } }, - "node_modules/send/node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/send/node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/send/node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/seq-queue": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz", @@ -10247,15 +10372,15 @@ } }, "node_modules/serve-static": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.1.0.tgz", - "integrity": "sha512-A3We5UfEjG8Z7VkDv6uItWw6HY2bBSBJT1KtVESn6EOoOr2jAxNhxWCLY3jDE2WcuHXByWju74ck3ZgLwL8xmA==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", "license": "MIT", "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "parseurl": "^1.3.3", - "send": "^1.0.0" + "send": "^1.2.0" }, "engines": { "node": ">= 18" @@ -11394,9 +11519,9 @@ } }, "node_modules/type-is": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.0.tgz", - "integrity": "sha512-gd0sGezQYCbWSbkZr75mln4YBidWUN60+devscpLF5mtRDUpiaTvKpBNrdaCvel1NdR2k6vclXybU5fBd2i+nw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", "license": "MIT", "dependencies": { "content-type": "^1.0.5", @@ -11414,24 +11539,24 @@ "license": "MIT" }, "node_modules/typeorm": { - "version": "0.3.21", - "resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.3.21.tgz", - "integrity": "sha512-lh4rUWl1liZGjyPTWpwcK8RNI5x4ekln+/JJOox1wCd7xbucYDOXWD+1cSzTN3L0wbTGxxOtloM5JlxbOxEufA==", + "version": "0.3.22", + "resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.3.22.tgz", + "integrity": "sha512-P/Tsz3UpJ9+K0oryC0twK5PO27zejLYYwMsE8SISfZc1lVHX+ajigiOyWsKbuXpEFMjD9z7UjLzY3+ElVOMMDA==", "license": "MIT", "dependencies": { "@sqltools/formatter": "^1.2.5", - "ansis": "^3.9.0", + "ansis": "^3.17.0", "app-root-path": "^3.1.0", "buffer": "^6.0.3", - "dayjs": "^1.11.9", - "debug": "^4.3.4", - "dotenv": "^16.0.3", + "dayjs": "^1.11.13", + "debug": "^4.4.0", + "dotenv": "^16.4.7", "glob": "^10.4.5", "sha.js": "^2.4.11", "sql-highlight": "^6.0.0", - "tslib": "^2.5.0", - "uuid": "^11.0.5", - "yargs": "^17.6.2" + "tslib": "^2.8.1", + "uuid": "^11.1.0", + "yargs": "^17.7.2" }, "bin": { "typeorm": "cli.js", @@ -11445,12 +11570,12 @@ "url": "https://opencollective.com/typeorm" }, "peerDependencies": { - "@google-cloud/spanner": "^5.18.0", + "@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", + "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", @@ -11462,7 +11587,7 @@ "sql.js": "^1.4.0", "sqlite3": "^5.0.3", "ts-node": "^10.7.0", - "typeorm-aurora-data-api-driver": "^2.0.0" + "typeorm-aurora-data-api-driver": "^2.0.0 || ^3.0.0" }, "peerDependenciesMeta": { "@google-cloud/spanner": { @@ -11518,6 +11643,15 @@ } } }, + "node_modules/typeorm/node_modules/ansis": { + "version": "3.17.0", + "resolved": "https://registry.npmjs.org/ansis/-/ansis-3.17.0.tgz", + "integrity": "sha512-0qWUglt9JEqLFr3w1I1pbrChn1grhaiAR2ocX1PP/flRmxgtwTzPFFFnfIlD6aMOLQZgSuCRlidD70lvx8yhzg==", + "license": "ISC", + "engines": { + "node": ">=14" + } + }, "node_modules/typeorm/node_modules/brace-expansion": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", diff --git a/package.json b/package.json index 8c10e20..f83c4af 100644 --- a/package.json +++ b/package.json @@ -37,13 +37,15 @@ "class-transformer": "^0.5.1", "class-validator": "^0.14.1", "dotenv": "^16.4.7", + "express": "^5.1.0", + "multer": "^1.4.5-lts.2", "mysql2": "^3.14.0", "passport": "^0.7.0", "passport-jwt": "^4.0.1", "qrcode": "^1.5.4", "reflect-metadata": "^0.2.2", "rxjs": "^7.8.1", - "typeorm": "^0.3.21" + "typeorm": "^0.3.22" }, "devDependencies": { "@eslint/eslintrc": "^3.2.0", @@ -55,6 +57,7 @@ "@swc/core": "^1.10.7", "@types/express": "^5.0.0", "@types/jest": "^29.5.14", + "@types/multer": "^1.4.12", "@types/node": "^22.10.7", "@types/qrcode": "^1.5.5", "@types/supertest": "^6.0.2", @@ -89,5 +92,10 @@ ], "coverageDirectory": "../coverage", "testEnvironment": "node" + }, + "compilerOptions": { + "outDir": "./dist" + } + } diff --git a/src/alumnos/alumnos.module.ts b/src/alumnos/alumnos.module.ts index 9a4935d..69d3b48 100644 --- a/src/alumnos/alumnos.module.ts +++ b/src/alumnos/alumnos.module.ts @@ -3,12 +3,16 @@ import { TypeOrmModule } from '@nestjs/typeorm'; import { AlumnosController } from './alumnos.controller'; import { AlumnosService } from './alumnos.service'; import { RegistroAlumno } from './entities/registro-alumno.entity'; -import { ConfigService } from '@nestjs/config'; +import { ConfigModule, ConfigService } from '@nestjs/config'; @Module({ imports: [ TypeOrmModule.forFeature([RegistroAlumno], 'alumnosConnection'), + ConfigModule.forRoot({ + isGlobal: true, // importante si querés que esté disponible en todos lados + envFilePath: '.env', // opcional si el archivo ya es '.env' + }), TypeOrmModule.forRootAsync({ name: 'alumnosConnection', @@ -24,6 +28,7 @@ import { ConfigService } from '@nestjs/config'; retryAttempts: 10, retryDelay: 3000, connectTimeout: 30000, + }), inject: [ConfigService], }), diff --git a/src/app.module.ts b/src/app.module.ts index 053ee00..4cc1e9b 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -15,7 +15,7 @@ import { RespuestaParticipanteCerradaModule } from './respuesta_participante_cer import { CuestionarioRespondidoModule } from './cuestionario_respondido/cuestionario_respondido.module'; import { RespuestaParticipanteAbiertaModule } from './respuesta_participante_abierta/respuesta_participante_abierta.module'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { ConfigModule } from '@nestjs/config'; +import { ConfigModule, ConfigService } from '@nestjs/config'; import { EventoModule } from './evento/evento.module'; import { TipoUserModule } from './tipo_user/tipo_user.module'; import { ParticipanteModule } from './participante/participante.module'; @@ -26,6 +26,9 @@ import { ParticipanteEventoModule } from './participante_evento/participante_eve import { ValidacionesModule } from './validaciones/validaciones.module'; import { AlumnosModule } from './alumnos/alumnos.module'; import { AuthModule } from './auth/auth.module'; +import { RegistroAlumno } from './alumnos/entities/registro-alumno.entity'; +import { ImagenController } from './imagen/imagen.controller'; +import { ImagenModule } from './imagen/imagen.module'; @Module({ @@ -33,25 +36,40 @@ import { AuthModule } from './auth/auth.module'; ConfigModule.forRoot({ isGlobal: true, }), - TypeOrmModule.forRoot({ + TypeOrmModule.forRootAsync({ + imports: [ConfigModule], + inject: [ConfigService], + useFactory: (config: ConfigService) => ({ + type: config.get<'mysql'>('DB_TYPE'), + host: config.get('DB_HOST'), + port: +(config.get('DB_PORT') || 3306), + username: config.get('DB_USERNAME'), + password: config.get('DB_PASSWORD'), + database: config.get('DB_DATABASE'), + entities: [__dirname + '/**/*.entity{.ts,.js}'], + synchronize: true, + //dropSchema: true, // elimina la base de datos + }), + + /* type: 'mysql', host: process.env.db_host, //process.env.db_host username: process.env.db_username, database: process.env.db_database, password: process.env.db_password, port: Number(process.env.db_port), - + */ - synchronize: true, - dropSchema: true, // elimina la base de datos + //synchronize: true, + //dropSchema: true, // elimina la base de datos // logging: true, // Habilita los logs para depuración - autoLoadEntities: true, // Carga automáticamente las entidades + //autoLoadEntities: true, // Carga automáticamente las entidades //esta es mi base de datos local - /* + /* type: 'mysql', host: 'localhost', port: 3306, //3306 @@ -64,9 +82,10 @@ import { AuthModule } from './auth/auth.module'; //extra - retryAttempts: 10, // Intentos para reconectar - retryDelay: 3000, // Tiempo entre reintentos + //retryAttempts: 10, // Intentos para reconectar + //retryDelay: 3000, // Tiempo entre reintentos }), + TypeOrmModule.forFeature([RegistroAlumno], 'alumnosConnection'), AuthModule, CuestionarioModule, TipoCuestionarioModule, @@ -88,6 +107,7 @@ import { AuthModule } from './auth/auth.module'; ParticipanteEventoModule, ValidacionesModule, AlumnosModule, + ImagenModule, ], controllers: [AppController], diff --git a/src/evento/dto/create-evento.dto.ts b/src/evento/dto/create-evento.dto.ts index 5fa9fd6..19840c9 100644 --- a/src/evento/dto/create-evento.dto.ts +++ b/src/evento/dto/create-evento.dto.ts @@ -1,10 +1,16 @@ +import { IsString } from "class-validator"; + export class CreateEventoDto { + @IsString() tipo_evento: string; + @IsString() nombre_evento: string; + @IsString() fecha_inicio: Date; + @IsString() fecha_fin: Date; //agregar el id del administrador diff --git a/src/evento/evento.entity.ts b/src/evento/evento.entity.ts index 046a042..99b67ad 100644 --- a/src/evento/evento.entity.ts +++ b/src/evento/evento.entity.ts @@ -1,3 +1,4 @@ +import { Imagen } from "src/imagen/imagen.entity"; import { Participante } from "src/participante/participante.entity"; import { ParticipanteEvento } from "src/participante_evento/participante_evento.entity"; import { Column, Entity, ManyToMany, OneToMany, PrimaryGeneratedColumn } from "typeorm"; @@ -38,4 +39,6 @@ export class Evento { @OneToMany(() => ParticipanteEvento, participanteEvento => participanteEvento.evento) participanteEventos: ParticipanteEvento[]; + @OneToMany(() => Imagen, (imagen) => imagen.evento) + imagenes: Imagen[] } \ No newline at end of file diff --git a/src/evento/evento.module.ts b/src/evento/evento.module.ts index 58e904d..7b86a7e 100644 --- a/src/evento/evento.module.ts +++ b/src/evento/evento.module.ts @@ -1,13 +1,14 @@ -import { Module } from '@nestjs/common'; +import { forwardRef, Module } from '@nestjs/common'; import { EventoService } from './evento.service'; import { EventoController } from './evento.controller'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Evento } from './evento.entity'; +import { ImagenModule } from 'src/imagen/imagen.module'; @Module({ - imports: [TypeOrmModule.forFeature([Evento])], + imports: [TypeOrmModule.forFeature([Evento]), forwardRef(() => ImagenModule)], controllers: [EventoController], providers: [EventoService], - exports: [EventoService] + exports: [EventoService, TypeOrmModule] }) export class EventoModule {} diff --git a/src/evento/evento.service.ts b/src/evento/evento.service.ts index eaf5d9f..900c720 100644 --- a/src/evento/evento.service.ts +++ b/src/evento/evento.service.ts @@ -29,7 +29,7 @@ export class EventoService { getEventos() { return this.eventoRepository.find({ - relations: ['participantes'] + relations: ['participanteEventos', 'imagenes'] //participante }) } @@ -38,7 +38,7 @@ export class EventoService { where: { id_evento }, - relations: ['participantes'] + relations: ['participanteEventos', 'imagenes'], }) if (!eventoFound) diff --git a/src/imagen/dto/create-imagen.dto.ts b/src/imagen/dto/create-imagen.dto.ts new file mode 100644 index 0000000..1d42002 --- /dev/null +++ b/src/imagen/dto/create-imagen.dto.ts @@ -0,0 +1,47 @@ +import { ApiProperty, ApiSchema } from "@nestjs/swagger"; +import { Transform, Type } from "class-transformer"; +import { IsBoolean, IsInt, IsOptional, IsString } from "class-validator"; + + +@ApiSchema({ name: 'CreateImagenRequest' }) +class CreateCatDto {} + + +export class CreateImagenDto { + /* + url: string + orden: number + tipo: string + + + evento: { + id_evento: number + } // si usas relaciones + + + evento: number +*/ + @IsOptional() + @IsString() + url: string; + + @Type(() => Number) + @IsInt() + orden: number; + + @IsString() + tipo: string; + + @Type(() => Number) + @IsInt() // tiene que ser el id del evento + evento: number; + + // Campo extra para controlar lógica, no se guarda + @IsOptional() + @IsBoolean() + @ApiProperty({ + type: Boolean + }) + @Transform(({ value }) => value === 'true' || value === true) + force_orden?: boolean; +} \ No newline at end of file diff --git a/src/imagen/dto/update.imagen.dto.ts b/src/imagen/dto/update.imagen.dto.ts new file mode 100644 index 0000000..2f12d5d --- /dev/null +++ b/src/imagen/dto/update.imagen.dto.ts @@ -0,0 +1,17 @@ +import { Type } from "class-transformer" +import { IsInt, IsOptional, IsString } from "class-validator" + +export class UpdateImagenDto { + @IsString() + @IsOptional() + url?: string + + @Type(() => Number) + @IsOptional() + @IsInt() + orden?: number + + @IsOptional() + @IsString() + tipo?: string +} \ No newline at end of file diff --git a/src/imagen/imagen.controller.ts b/src/imagen/imagen.controller.ts new file mode 100644 index 0000000..ce961c0 --- /dev/null +++ b/src/imagen/imagen.controller.ts @@ -0,0 +1,108 @@ +import { BadRequestException, Body, Controller, Delete, Get, HttpException, HttpStatus, Param, ParseIntPipe, Patch, Post, Res, UploadedFile, UseInterceptors } from '@nestjs/common'; +import { ImagenService, imageStorage } from './imagen.service'; +import { UpdateImagenDto } from './dto/update.imagen.dto'; +import { FileInterceptor } from '@nestjs/platform-express'; +import { extname, join } from 'path'; +import { diskStorage } from 'multer'; +import { CreateImagenDto } from './dto/create-imagen.dto'; +import { existsSync } from 'fs'; +import { Response } from 'express'; +import { ImagenApiDocumentation } from './imagen.documentation'; // Importar la documentación + + +@ImagenApiDocumentation.ApiController +@Controller('imagen') +export class ImagenController { + constructor(private imagenService: ImagenService) {} + + @Get() + getImagenes() { + return this.imagenService.getImagenes() + } + + @Get('evento/:id_evento') + @ImagenApiDocumentation.ApiGetImagenEvento // Aplicando la documentación al método + getImagenEvento(@Param('id_evento', ParseIntPipe) id_evento: number) { + return this.imagenService.getImagenEvento(id_evento) + } + + @Get(':id') + @ImagenApiDocumentation.ApiGetOne + getImagen(@Param('id', ParseIntPipe) id_imagen: number) { + return this.imagenService.getImagen(id_imagen) + } + + @Get('visual/:id') + @ImagenApiDocumentation.ApiGetVisual // Aplicando la documentación al método + async getVisualImagen( + @Param('id') id_imagen: number, + @Res() res: Response + ) { + const url = await this.imagenService.getVisualImagen(id_imagen); + + const rutaImagen = join(process.cwd(), url); + if (!existsSync(rutaImagen)) { + throw new HttpException('Imagen no encontrada', HttpStatus.NOT_FOUND); + } + + return res.sendFile(rutaImagen); + } + + @Post() + @ImagenApiDocumentation.ApiCreate + @UseInterceptors(FileInterceptor('imagen', { + storage: diskStorage({ + destination: './uploads', + filename: (req, file, cb) => { + const uniqueName = Date.now() + '-' + Math.round(Math.random() * 1e9); + const extension = extname(file.originalname); + cb(null, `${uniqueName}${extension}`); + }, + }), + })) + async subirImagen(@UploadedFile() file: Express.Multer.File, @Body() imagen: CreateImagenDto,) { + + if (!file) { + throw new BadRequestException('No se ha subido ningún archivo'); + } + + if (!imagen.evento) { + throw new BadRequestException('Falta el id_evento en los datos'); + } + + const url = `/uploads/${file.filename}`; + + return this.imagenService.crearImagen({ + ...imagen, + url, + evento: imagen.evento, + }); + } + + + @Delete(':id') + @ImagenApiDocumentation.ApiRemove + deleteImagen(@Param('id', ParseIntPipe) id_imagen: number) { + return this.imagenService.deleteImagen(id_imagen) + } + + @Patch(':id') + @ImagenApiDocumentation.ApiUpdate + @UseInterceptors(FileInterceptor('imagen', { storage: imageStorage })) // Interceptor para manejar la subida de archivo + async updateImagen( + @Param('id') id: number, + @UploadedFile() file: Express.Multer.File, // Recibe el archivo cargado + @Body() imagen: UpdateImagenDto, + ) { + if (file) { + // Si se sube un nuevo archivo, actualiza la URL + imagen.url = `/uploads/${file.filename}`; + } + const newImagen = await this.imagenService.updateImagen(id, imagen); + return newImagen; + } + + + +} + diff --git a/src/imagen/imagen.documentation.ts b/src/imagen/imagen.documentation.ts new file mode 100644 index 0000000..9e199a1 --- /dev/null +++ b/src/imagen/imagen.documentation.ts @@ -0,0 +1,210 @@ +import { ApiBody, ApiOperation, ApiParam, ApiResponse, ApiTags } from '@nestjs/swagger'; +import { applyDecorators, Get, HttpException, HttpStatus, Param, ParseIntPipe, Res } from '@nestjs/common'; +import { join } from 'path'; +import { existsSync } from 'fs'; + + +export class ImagenApiDocumentation { + + // Decoradores para toda la clase del controlador + static ApiController = ApiTags('Imágenes'); + // Documentación para obtener una imagen visual por ID + static ApiGetVisual = applyDecorators( + ApiOperation({ + summary: 'Obtener imagen visual por ID', + description: 'Retorna una imagen visual específica dada su ID.' + }), + ApiParam({ + name: 'id', + description: 'ID de la imagen visual', + required: true, + type: 'number', + example: 1 + }), + ApiResponse({ + status: 200, + description: 'Imagen visual encontrada correctamente.', + content: { + 'image/png': { + schema: { type: 'string', format: 'binary' } + } + } + }), + ApiResponse({ status: 404, description: 'Imagen no encontrada.' }), + ApiResponse({ status: 500, description: 'Error interno del servidor.' }) + ); + + // Documentación para obtener las imágenes asociadas a un evento + static ApiGetImagenEvento = applyDecorators( + ApiOperation({ + summary: 'Obtener imágenes por evento', + description: 'Retorna una lista de imágenes asociadas a un evento específico.' + }), + ApiParam({ + name: 'id_evento', + description: 'ID del evento', + required: true, + type: 'number', + example: 10 + }), + ApiResponse({ + status: 200, + description: 'Imágenes del evento obtenidas correctamente.', + schema: { + type: 'array', + items: { + type: 'object', + properties: { + id_imagen: { type: 'number', example: 1 }, + url: { type: 'string', example: '/uploads/imagen1.png' }, + evento_id: { type: 'number', example: 10 } + } + } + } + }), + ApiResponse({ status: 404, description: 'Evento no encontrado.' }), + ApiResponse({ status: 500, description: 'Error interno del servidor.' }) + ); + + + + + // Documentación para crear una nueva imagen + static ApiCreate = applyDecorators( + ApiOperation({ + summary: 'Crear una nueva imagen', + description: 'Crea una nueva imagen asociada a un evento específico.' + }), + ApiBody({ + description: 'Datos para crear una nueva imagen', + schema: { + type: 'object', + required: ['orden', 'tipo', 'evento'], + properties: { + url: { type: 'string', example: '/uploads/imagen1.png' }, + orden: { type: 'number', example: 1 }, + tipo: { type: 'string', example: 'cabezera' }, + evento: { type: 'number', example: 5 } + } + } + }), + ApiResponse({ + status: 201, + description: 'Imagen creada correctamente.', + schema: { + type: 'object', + properties: { + id_imagen: { type: 'number', example: 1 }, + url: { type: 'string', example: '/uploads/imagen1.png' }, + orden: { type: 'number', example: 1 }, + tipo: { type: 'string', example: 'cabezera' }, + evento: { + type: 'object', + properties: { + id_evento: { type: 'number', example: 1 } + } + } + } + } + }), + ApiResponse({ status: 400, description: 'Datos inválidos para crear la imagen.' }), + ApiResponse({ status: 500, description: 'Error interno del servidor.' }) + ); + + // Documentación para actualizar una imagen + static ApiUpdate = applyDecorators( + ApiOperation({ + summary: 'Actualizar una imagen', + description: 'Permite actualizar los datos de una imagen existente' + }), + ApiParam({ + name: 'id', + description: 'ID de la imagen a actualizar', + required: true, + type: 'number', + example: 1 + }), + ApiBody({ + description: 'Datos a actualizar de la imagen', + schema: { + type: 'object', + properties: { + url: { type: 'string', example: '/uploads/1745549896986-789608318_updated.png' }, + descripcion: { type: 'string', example: 'Nueva imagen de ejemplo para actualización' } + } + } + }), + ApiResponse({ + status: 200, + description: 'Imagen actualizada correctamente', + schema: { + type: 'object', + properties: { + affected: { type: 'number', example: 1 } + } + } + }), + ApiResponse({ status: 400, description: 'Datos inválidos para la actualización de la imagen.' }), + ApiResponse({ status: 404, description: 'Imagen no encontrada.' }), + ApiResponse({ status: 500, description: 'Error interno del servidor' }) + ); + + // Documentación para obtener una imagen por ID + static ApiGetOne = applyDecorators( + ApiOperation({ + summary: 'Obtener una imagen por ID', + description: 'Permite obtener los detalles de una imagen a partir de su ID.' + }), + ApiParam({ + name: 'id', + description: 'ID de la imagen', + required: true, + type: 'number', + example: 1 + }), + ApiResponse({ + status: 200, + description: 'Imagen encontrada correctamente', + schema: { + type: 'object', + properties: { + id_imagen: { type: 'number', example: 1 }, + url: { type: 'string', example: '/uploads/1745549896986-789608318.png' }, + orden: { type: 'number', example: '1'}, + tipo: { type: 'string', example: 'Cabezera'}, + //descripcion: { type: 'string', example: 'Imagen de muestra para la documentación' } + } + } + }), + ApiResponse({ status: 404, description: 'Imagen no encontrada' }), + ApiResponse({ status: 500, description: 'Error interno del servidor' }) + ); + + // Documentación para eliminar una imagen + static ApiRemove = applyDecorators( + ApiOperation({ + summary: 'Eliminar una imagen', + description: 'Permite eliminar una imagen a partir de su ID.' + }), + ApiParam({ + name: 'id', + description: 'ID de la imagen a eliminar', + required: true, + type: 'number', + example: 1 + }), + ApiResponse({ + status: 200, + description: 'Imagen eliminada correctamente', + schema: { + type: 'object', + properties: { + affected: { type: 'number', example: 1 } + } + } + }), + ApiResponse({ status: 404, description: 'Imagen no encontrada' }), + ApiResponse({ status: 500, description: 'Error interno del servidor' }) + ); + +} diff --git a/src/imagen/imagen.entity.ts b/src/imagen/imagen.entity.ts new file mode 100644 index 0000000..553a188 --- /dev/null +++ b/src/imagen/imagen.entity.ts @@ -0,0 +1,25 @@ +import { Type } from "class-transformer"; +import { IsInt } from "class-validator"; +import { Evento } from "src/evento/evento.entity"; +import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from "typeorm"; + +@Entity() +export class Imagen { + @PrimaryGeneratedColumn() + id_imagen: number + + @Column() + url?: string + + @Column() + @Type(() => Number) + orden: number + + @Column() + tipo: string + + @ManyToOne(() => Evento, (evento) => evento.imagenes, { onDelete: 'CASCADE' }) + @IsInt() + @Type(() => Number) + evento: Evento; +} \ No newline at end of file diff --git a/src/imagen/imagen.module.ts b/src/imagen/imagen.module.ts new file mode 100644 index 0000000..6ca78df --- /dev/null +++ b/src/imagen/imagen.module.ts @@ -0,0 +1,14 @@ +import { forwardRef, Module } from '@nestjs/common'; +import { ImagenService } from './imagen.service'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { Imagen } from './imagen.entity'; +import { EventoModule } from 'src/evento/evento.module'; +import { ImagenController } from './imagen.controller'; + +@Module({ + imports: [TypeOrmModule.forFeature([Imagen]), forwardRef(() => EventoModule)], //forwardRef(() => EventoModule + controllers: [ImagenController], + providers: [ImagenService], + exports: [ImagenService] +}) +export class ImagenModule {} diff --git a/src/imagen/imagen.service.ts b/src/imagen/imagen.service.ts new file mode 100644 index 0000000..ffb75ef --- /dev/null +++ b/src/imagen/imagen.service.ts @@ -0,0 +1,169 @@ +import { HttpException, HttpStatus, Injectable, NotFoundException } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Imagen } from './imagen.entity'; +import { Repository } from 'typeorm'; +import { Evento } from 'src/evento/evento.entity'; +import { CreateImagenDto } from './dto/create-imagen.dto'; +import { UpdateImagenDto } from './dto/update.imagen.dto'; +import { extname } from 'path'; +import { diskStorage } from 'multer'; + +@Injectable() +export class ImagenService { + constructor( + @InjectRepository(Imagen) private imagenRepository: Repository, + @InjectRepository(Evento) private eventoRepository: Repository + ) {} + + async getUltimoOrden(id_evento: number): Promise { + const [ultimo] = await this.imagenRepository.find({ + where: { + evento: { id_evento }, // Filtrar por evento + }, + order: { + orden: 'DESC', + }, + take: 1, + }); + + return ultimo?.orden ?? 0; + } + + + + async crearImagen(imagen: CreateImagenDto): Promise { + const evento = await this.eventoRepository.findOneBy({ id_evento: imagen.evento }); + + if (!evento) { + throw new NotFoundException('Evento no encontrado'); + } + + const ordenExiste = await this.imagenRepository.findOneBy({ + orden: imagen.orden, + evento: { id_evento: imagen.evento }, + }); + + if (ordenExiste) { + if (imagen.force_orden === false || imagen.force_orden === undefined) { + throw new HttpException('Este orden ya existe', HttpStatus.CONFLICT) + } + } + + + if (imagen.force_orden === true) { + const imagenExistente = await this.imagenRepository.findOne({ + where: { + orden: imagen.orden, + evento: { id_evento: imagen.evento }, + }, + relations: ['evento'], + }); + + if (imagenExistente) { + const ultimoOrden = await this.getUltimoOrden(imagen.evento); + imagenExistente.orden = ultimoOrden + 1; + await this.imagenRepository.save(imagenExistente); // Guardar imagen existente con nuevo orden + } + } + + // Crear y guardar nueva imagen + const nuevaImagen = this.imagenRepository.create({ + ...imagen, + evento: { id_evento: imagen.evento }, // para la relación ManyToOne + }); + + return this.imagenRepository.save(nuevaImagen); + } + + + getImagenes(): Promise { + return this.imagenRepository.find() + } + + async getImagen(id_imagen: number) { + const imagenFound = await this.imagenRepository.findOne({ + where: { + id_imagen + } + }) + + if (!imagenFound) { + return new HttpException('Imagen not found', HttpStatus.NOT_FOUND); + } + + return imagenFound + } + + async getImagenEvento(id_evento: number) { + const imagenEventoFound = await this.eventoRepository.findOne({ + where: { + id_evento + } + }) + + if (!imagenEventoFound) { + return new HttpException('Evento not found', HttpStatus.NOT_FOUND); + } + + return this.imagenRepository.find({ + where: { + evento: { + id_evento + } + } + }) + } + + async getVisualImagen(id_imagen: number): Promise { + const imagenFound = await this.imagenRepository.findOne({ + where: { + id_imagen + }, + }); + + if (!imagenFound || !imagenFound.url) { + throw new HttpException('Imagen not found', HttpStatus.NOT_FOUND); + } + + return imagenFound.url; + } + + + async deleteImagen(id_imagen: number) { + const result = await this.imagenRepository.delete({ id_imagen }) + + if (result.affected === 0) { + return new HttpException('Imagen not found', HttpStatus.NOT_FOUND); + } + + return result + } + + async updateImagen(id_imagen: number, imagen: UpdateImagenDto) { + const imagenFound = await this.imagenRepository.findOne({ + where: { + id_imagen + } + }); + + if (!imagenFound) { + throw new HttpException('Imagen not found', HttpStatus.NOT_FOUND); + } + + if (imagen.url) { + imagenFound.url = imagen.url; + } + + const updatedImagen = Object.assign(imagenFound, imagen); + return this.imagenRepository.save(updatedImagen); + } +} + + +export const imageStorage = diskStorage({ + destination: './uploads', // Carpeta donde se almacenarán las imágenes + filename: (req, file, callback) => { + const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9); // Genera un nombre único + callback(null, uniqueSuffix + extname(file.originalname)); // Usa la extensión original del archivo + }, +}); \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index bdbcf72..b78bfa2 100644 --- a/src/main.ts +++ b/src/main.ts @@ -5,9 +5,14 @@ import { DocsModule } from './docs/docs.module'; import { DataSource } from 'typeorm'; import { TipoPregunta } from './tipo_pregunta/entities/tipo_pregunta.entity'; import { ValidationPipe } from '@nestjs/common'; +import { NestExpressApplication } from '@nestjs/platform-express'; +import { join, resolve } from 'path'; + async function bootstrap() { - const app = await NestFactory.create(AppModule); + //const app = await NestFactory.create(AppModule); + + const app = await NestFactory.create(AppModule); // Configuración de validación global app.useGlobalPipes( @@ -15,6 +20,8 @@ async function bootstrap() { whitelist: true, // Elimina propiedades no decoradas forbidNonWhitelisted: true, // Arroja error si hay propiedades no decoradas transform: true, // Transforma los datos recibidos al tipo definido en el DTO + + }) ); @@ -23,8 +30,19 @@ async function bootstrap() { origin: '*', methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'], allowedHeaders: ['Content-Type', 'Authorization'], + }); + + // Verifica que la carpeta uploads esté correctamente ubicada + const uploadsPath = resolve(__dirname, '..', 'uploads'); + + // Configurar los archivos estáticos para que se sirvan desde /uploads + app.useStaticAssets(uploadsPath, { + prefix: '/uploads', // Esto asegura que los archivos sean accesibles en http://localhost:4200/uploads + }); + + // Swagger setup DocsModule.setupSwagger(app); diff --git a/uploads/1745588502437-915379986.png b/uploads/1745588502437-915379986.png new file mode 100644 index 0000000..b48d51d Binary files /dev/null and b/uploads/1745588502437-915379986.png differ diff --git a/uploads/1745590786107-801176054.png b/uploads/1745590786107-801176054.png new file mode 100644 index 0000000..6663227 Binary files /dev/null and b/uploads/1745590786107-801176054.png differ diff --git a/uploads/1745607818019-813008776.png b/uploads/1745607818019-813008776.png new file mode 100644 index 0000000..8499728 Binary files /dev/null and b/uploads/1745607818019-813008776.png differ diff --git a/uploads/1745607901303-179430664.png b/uploads/1745607901303-179430664.png new file mode 100644 index 0000000..6c8d06c Binary files /dev/null and b/uploads/1745607901303-179430664.png differ diff --git a/uploads/1745607945192-847753303.png b/uploads/1745607945192-847753303.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745607945192-847753303.png differ diff --git a/uploads/1745608045859-405193397.png b/uploads/1745608045859-405193397.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745608045859-405193397.png differ diff --git a/uploads/1745608058156-500888973.png b/uploads/1745608058156-500888973.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745608058156-500888973.png differ diff --git a/uploads/1745609803026-621226798.png b/uploads/1745609803026-621226798.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745609803026-621226798.png differ diff --git a/uploads/1745610448267-554602617.png b/uploads/1745610448267-554602617.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745610448267-554602617.png differ diff --git a/uploads/1745782698388-417811017.png b/uploads/1745782698388-417811017.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745782698388-417811017.png differ diff --git a/uploads/1745782810652-779715719.png b/uploads/1745782810652-779715719.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745782810652-779715719.png differ diff --git a/uploads/1745782942741-934944907.png b/uploads/1745782942741-934944907.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745782942741-934944907.png differ diff --git a/uploads/1745783173708-531586426.png b/uploads/1745783173708-531586426.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745783173708-531586426.png differ diff --git a/uploads/1745783238278-605375586.png b/uploads/1745783238278-605375586.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745783238278-605375586.png differ diff --git a/uploads/1745783280065-864114540.png b/uploads/1745783280065-864114540.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745783280065-864114540.png differ diff --git a/uploads/1745783339764-542394184.png b/uploads/1745783339764-542394184.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745783339764-542394184.png differ diff --git a/uploads/1745783404258-689232392.png b/uploads/1745783404258-689232392.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745783404258-689232392.png differ diff --git a/uploads/1745783481174-870430821.png b/uploads/1745783481174-870430821.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745783481174-870430821.png differ diff --git a/uploads/1745783488008-910026857.png b/uploads/1745783488008-910026857.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745783488008-910026857.png differ diff --git a/uploads/1745783489499-194428924.png b/uploads/1745783489499-194428924.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745783489499-194428924.png differ diff --git a/uploads/1745783501706-597432167.png b/uploads/1745783501706-597432167.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745783501706-597432167.png differ diff --git a/uploads/1745783559720-597855812.png b/uploads/1745783559720-597855812.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745783559720-597855812.png differ diff --git a/uploads/1745783740703-464736041.png b/uploads/1745783740703-464736041.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745783740703-464736041.png differ diff --git a/uploads/1745783746688-934302438.png b/uploads/1745783746688-934302438.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745783746688-934302438.png differ diff --git a/uploads/1745783835448-410102442.png b/uploads/1745783835448-410102442.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745783835448-410102442.png differ diff --git a/uploads/1745783841444-28135923.png b/uploads/1745783841444-28135923.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745783841444-28135923.png differ diff --git a/uploads/1745783842922-831312863.png b/uploads/1745783842922-831312863.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745783842922-831312863.png differ diff --git a/uploads/1745783843756-979241748.png b/uploads/1745783843756-979241748.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745783843756-979241748.png differ diff --git a/uploads/1745783844550-286851931.png b/uploads/1745783844550-286851931.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745783844550-286851931.png differ diff --git a/uploads/1745783845298-890749257.png b/uploads/1745783845298-890749257.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745783845298-890749257.png differ diff --git a/uploads/1745783855679-390906189.png b/uploads/1745783855679-390906189.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745783855679-390906189.png differ diff --git a/uploads/1745783861272-744384437.png b/uploads/1745783861272-744384437.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745783861272-744384437.png differ diff --git a/uploads/1745783902333-330562882.png b/uploads/1745783902333-330562882.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745783902333-330562882.png differ diff --git a/uploads/1745783945438-660894005.png b/uploads/1745783945438-660894005.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745783945438-660894005.png differ diff --git a/uploads/1745783961266-398470558.png b/uploads/1745783961266-398470558.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745783961266-398470558.png differ diff --git a/uploads/1745784576405-643303002.png b/uploads/1745784576405-643303002.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745784576405-643303002.png differ diff --git a/uploads/1745784583583-474619254.png b/uploads/1745784583583-474619254.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745784583583-474619254.png differ diff --git a/uploads/1745784586314-475885468.png b/uploads/1745784586314-475885468.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745784586314-475885468.png differ diff --git a/uploads/1745784619094-15245803.png b/uploads/1745784619094-15245803.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745784619094-15245803.png differ diff --git a/uploads/1745784625569-44908201.png b/uploads/1745784625569-44908201.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745784625569-44908201.png differ diff --git a/uploads/1745785258683-364916817.png b/uploads/1745785258683-364916817.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745785258683-364916817.png differ diff --git a/uploads/1745786069963-577634867.png b/uploads/1745786069963-577634867.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745786069963-577634867.png differ diff --git a/uploads/1745786074916-256855117.png b/uploads/1745786074916-256855117.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745786074916-256855117.png differ diff --git a/uploads/1745786088644-866249807.png b/uploads/1745786088644-866249807.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745786088644-866249807.png differ diff --git a/uploads/1745786112337-531736979.png b/uploads/1745786112337-531736979.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745786112337-531736979.png differ diff --git a/uploads/1745786187994-704954495.png b/uploads/1745786187994-704954495.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745786187994-704954495.png differ diff --git a/uploads/1745786191642-138479283.png b/uploads/1745786191642-138479283.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745786191642-138479283.png differ diff --git a/uploads/1745786420018-550943449.png b/uploads/1745786420018-550943449.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745786420018-550943449.png differ diff --git a/uploads/1745786429159-933034550.png b/uploads/1745786429159-933034550.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745786429159-933034550.png differ diff --git a/uploads/1745786620186-351946945.png b/uploads/1745786620186-351946945.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745786620186-351946945.png differ diff --git a/uploads/1745786638320-272295799.png b/uploads/1745786638320-272295799.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745786638320-272295799.png differ diff --git a/uploads/1745786640812-515749335.png b/uploads/1745786640812-515749335.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745786640812-515749335.png differ diff --git a/uploads/1745786649681-615771575.png b/uploads/1745786649681-615771575.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745786649681-615771575.png differ diff --git a/uploads/1745786654764-319731865.png b/uploads/1745786654764-319731865.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745786654764-319731865.png differ diff --git a/uploads/1745786657182-134241930.png b/uploads/1745786657182-134241930.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745786657182-134241930.png differ diff --git a/uploads/1745786660567-315087335.png b/uploads/1745786660567-315087335.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745786660567-315087335.png differ diff --git a/uploads/1745786795750-903191321.png b/uploads/1745786795750-903191321.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745786795750-903191321.png differ diff --git a/uploads/1745786857870-709698721.png b/uploads/1745786857870-709698721.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745786857870-709698721.png differ diff --git a/uploads/1745786864012-139667281.png b/uploads/1745786864012-139667281.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745786864012-139667281.png differ diff --git a/uploads/1745787125617-938552161.png b/uploads/1745787125617-938552161.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745787125617-938552161.png differ diff --git a/uploads/1745787129548-80572005.png b/uploads/1745787129548-80572005.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745787129548-80572005.png differ diff --git a/uploads/1745787144940-709553625.png b/uploads/1745787144940-709553625.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745787144940-709553625.png differ diff --git a/uploads/1745787159183-887180833.png b/uploads/1745787159183-887180833.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745787159183-887180833.png differ diff --git a/uploads/1745787170576-132924113.png b/uploads/1745787170576-132924113.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745787170576-132924113.png differ diff --git a/uploads/1745787269138-761919431.png b/uploads/1745787269138-761919431.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745787269138-761919431.png differ diff --git a/uploads/1745787275406-149365898.png b/uploads/1745787275406-149365898.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745787275406-149365898.png differ diff --git a/uploads/1745787299438-320856105.png b/uploads/1745787299438-320856105.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745787299438-320856105.png differ diff --git a/uploads/1745787353258-947428468.png b/uploads/1745787353258-947428468.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745787353258-947428468.png differ diff --git a/uploads/1745862652637-375061957.png b/uploads/1745862652637-375061957.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745862652637-375061957.png differ diff --git a/uploads/1745862746470-559202926.png b/uploads/1745862746470-559202926.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745862746470-559202926.png differ diff --git a/uploads/1745862803624-656345193.png b/uploads/1745862803624-656345193.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745862803624-656345193.png differ diff --git a/uploads/1745863389400-956774181.png b/uploads/1745863389400-956774181.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745863389400-956774181.png differ diff --git a/uploads/1745863400866-438733588.png b/uploads/1745863400866-438733588.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745863400866-438733588.png differ diff --git a/uploads/1745863419794-371280176.png b/uploads/1745863419794-371280176.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745863419794-371280176.png differ diff --git a/uploads/1745863422125-492370524.png b/uploads/1745863422125-492370524.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745863422125-492370524.png differ diff --git a/uploads/1745863427576-109097031.png b/uploads/1745863427576-109097031.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745863427576-109097031.png differ diff --git a/uploads/1745863437739-119219003.png b/uploads/1745863437739-119219003.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745863437739-119219003.png differ diff --git a/uploads/1745863473881-714112612.png b/uploads/1745863473881-714112612.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745863473881-714112612.png differ diff --git a/uploads/1745863490743-19393051.png b/uploads/1745863490743-19393051.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745863490743-19393051.png differ diff --git a/uploads/1745863514514-214046041.png b/uploads/1745863514514-214046041.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745863514514-214046041.png differ diff --git a/uploads/1745863522096-147329113.png b/uploads/1745863522096-147329113.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745863522096-147329113.png differ diff --git a/uploads/1745863528898-624571627.png b/uploads/1745863528898-624571627.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745863528898-624571627.png differ diff --git a/uploads/1745863536505-551389720.png b/uploads/1745863536505-551389720.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745863536505-551389720.png differ diff --git a/uploads/1745863543115-889167421.png b/uploads/1745863543115-889167421.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745863543115-889167421.png differ diff --git a/uploads/1745863544835-577875971.png b/uploads/1745863544835-577875971.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745863544835-577875971.png differ diff --git a/uploads/1745863565809-907964944.png b/uploads/1745863565809-907964944.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745863565809-907964944.png differ diff --git a/uploads/1745863758431-549995066.png b/uploads/1745863758431-549995066.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745863758431-549995066.png differ diff --git a/uploads/1745863764598-867777247.png b/uploads/1745863764598-867777247.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745863764598-867777247.png differ diff --git a/uploads/1745863772646-496089121.png b/uploads/1745863772646-496089121.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745863772646-496089121.png differ diff --git a/uploads/1745863780893-631766678.png b/uploads/1745863780893-631766678.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745863780893-631766678.png differ diff --git a/uploads/1745863938468-166902509.png b/uploads/1745863938468-166902509.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745863938468-166902509.png differ diff --git a/uploads/1745864934600-388017502.png b/uploads/1745864934600-388017502.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745864934600-388017502.png differ diff --git a/uploads/1745864946827-718952100.png b/uploads/1745864946827-718952100.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745864946827-718952100.png differ diff --git a/uploads/1745864958553-868321183.png b/uploads/1745864958553-868321183.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745864958553-868321183.png differ diff --git a/uploads/1745864983528-303304406.png b/uploads/1745864983528-303304406.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745864983528-303304406.png differ diff --git a/uploads/1745865006038-753208661.png b/uploads/1745865006038-753208661.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745865006038-753208661.png differ diff --git a/uploads/1745865155065-450262307.png b/uploads/1745865155065-450262307.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745865155065-450262307.png differ diff --git a/uploads/1745865179202-348522892.png b/uploads/1745865179202-348522892.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745865179202-348522892.png differ diff --git a/uploads/1745865184236-710840896.png b/uploads/1745865184236-710840896.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745865184236-710840896.png differ diff --git a/uploads/1745865198247-164755199.png b/uploads/1745865198247-164755199.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745865198247-164755199.png differ diff --git a/uploads/1745865203395-652578142.png b/uploads/1745865203395-652578142.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745865203395-652578142.png differ diff --git a/uploads/1745865283356-496088738.png b/uploads/1745865283356-496088738.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745865283356-496088738.png differ diff --git a/uploads/1745865637601-425779015.png b/uploads/1745865637601-425779015.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745865637601-425779015.png differ diff --git a/uploads/1745865642302-128090924.png b/uploads/1745865642302-128090924.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745865642302-128090924.png differ diff --git a/uploads/1745865651968-25518929.png b/uploads/1745865651968-25518929.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745865651968-25518929.png differ diff --git a/uploads/1745865657508-709886143.png b/uploads/1745865657508-709886143.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745865657508-709886143.png differ diff --git a/uploads/1745865752135-937619870.png b/uploads/1745865752135-937619870.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745865752135-937619870.png differ diff --git a/uploads/1745865766958-110801002.png b/uploads/1745865766958-110801002.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745865766958-110801002.png differ diff --git a/uploads/1745865774642-685944112.png b/uploads/1745865774642-685944112.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745865774642-685944112.png differ diff --git a/uploads/1745865800583-373505583.png b/uploads/1745865800583-373505583.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1745865800583-373505583.png differ diff --git a/uploads/1746108207246-163581370.png b/uploads/1746108207246-163581370.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1746108207246-163581370.png differ diff --git a/uploads/1746108231425-568590400.png b/uploads/1746108231425-568590400.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1746108231425-568590400.png differ diff --git a/uploads/1746108242369-258347270.png b/uploads/1746108242369-258347270.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1746108242369-258347270.png differ diff --git a/uploads/1746108246429-197314367.png b/uploads/1746108246429-197314367.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1746108246429-197314367.png differ diff --git a/uploads/1746108352033-301808876.png b/uploads/1746108352033-301808876.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1746108352033-301808876.png differ diff --git a/uploads/1746108411805-457280297.png b/uploads/1746108411805-457280297.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1746108411805-457280297.png differ diff --git a/uploads/1746108416798-573353784.png b/uploads/1746108416798-573353784.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1746108416798-573353784.png differ diff --git a/uploads/1746108425911-248387676.png b/uploads/1746108425911-248387676.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1746108425911-248387676.png differ diff --git a/uploads/1746108438610-474025348.png b/uploads/1746108438610-474025348.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1746108438610-474025348.png differ diff --git a/uploads/1746108442060-7483396.png b/uploads/1746108442060-7483396.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1746108442060-7483396.png differ diff --git a/uploads/1746108769711-859998586.png b/uploads/1746108769711-859998586.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1746108769711-859998586.png differ diff --git a/uploads/1746108773370-750273763.png b/uploads/1746108773370-750273763.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1746108773370-750273763.png differ diff --git a/uploads/1746108786719-114876629.png b/uploads/1746108786719-114876629.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1746108786719-114876629.png differ diff --git a/uploads/1746108797570-672503643.png b/uploads/1746108797570-672503643.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1746108797570-672503643.png differ diff --git a/uploads/1746108812992-921296400.png b/uploads/1746108812992-921296400.png new file mode 100644 index 0000000..2091993 Binary files /dev/null and b/uploads/1746108812992-921296400.png differ