1 Commits

141 changed files with 957 additions and 172 deletions
+289 -155
View File
@@ -26,13 +26,15 @@
"class-transformer": "^0.5.1", "class-transformer": "^0.5.1",
"class-validator": "^0.14.1", "class-validator": "^0.14.1",
"dotenv": "^16.4.7", "dotenv": "^16.4.7",
"express": "^5.1.0",
"multer": "^1.4.5-lts.2",
"mysql2": "^3.14.0", "mysql2": "^3.14.0",
"passport": "^0.7.0", "passport": "^0.7.0",
"passport-jwt": "^4.0.1", "passport-jwt": "^4.0.1",
"qrcode": "^1.5.4", "qrcode": "^1.5.4",
"reflect-metadata": "^0.2.2", "reflect-metadata": "^0.2.2",
"rxjs": "^7.8.1", "rxjs": "^7.8.1",
"typeorm": "^0.3.21" "typeorm": "^0.3.22"
}, },
"devDependencies": { "devDependencies": {
"@eslint/eslintrc": "^3.2.0", "@eslint/eslintrc": "^3.2.0",
@@ -44,6 +46,7 @@
"@swc/core": "^1.10.7", "@swc/core": "^1.10.7",
"@types/express": "^5.0.0", "@types/express": "^5.0.0",
"@types/jest": "^29.5.14", "@types/jest": "^29.5.14",
"@types/multer": "^1.4.12",
"@types/node": "^22.10.7", "@types/node": "^22.10.7",
"@types/qrcode": "^1.5.5", "@types/qrcode": "^1.5.5",
"@types/supertest": "^6.0.2", "@types/supertest": "^6.0.2",
@@ -2441,6 +2444,189 @@
"@nestjs/core": "^11.0.0" "@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": { "node_modules/@nestjs/schematics": {
"version": "11.0.2", "version": "11.0.2",
"resolved": "https://registry.npmjs.org/@nestjs/schematics/-/schematics-11.0.2.tgz", "resolved": "https://registry.npmjs.org/@nestjs/schematics/-/schematics-11.0.2.tgz",
@@ -3323,6 +3509,16 @@
"integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==",
"license": "MIT" "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": { "node_modules/@types/node": {
"version": "22.13.10", "version": "22.13.10",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.10.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.10.tgz",
@@ -4172,6 +4368,7 @@
"version": "3.16.0", "version": "3.16.0",
"resolved": "https://registry.npmjs.org/ansis/-/ansis-3.16.0.tgz", "resolved": "https://registry.npmjs.org/ansis/-/ansis-3.16.0.tgz",
"integrity": "sha512-sU7d/tfZiYrsIAXbdL/CNZld5bCkruzwT5KmqmadCJYxuLxHAOBjidxD5+iLmN/6xEfjcQq1l7OpsiCBlc4LzA==", "integrity": "sha512-sU7d/tfZiYrsIAXbdL/CNZld5bCkruzwT5KmqmadCJYxuLxHAOBjidxD5+iLmN/6xEfjcQq1l7OpsiCBlc4LzA==",
"dev": true,
"license": "ISC", "license": "ISC",
"engines": { "engines": {
"node": ">=14" "node": ">=14"
@@ -4552,16 +4749,16 @@
} }
}, },
"node_modules/body-parser": { "node_modules/body-parser": {
"version": "2.1.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.1.0.tgz", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz",
"integrity": "sha512-/hPxh61E+ll0Ujp24Ilm64cykicul1ypfwjVttduAiEdtnJFvLePSrIPk+HMImtNv5270wOGCb1Tns2rybMkoQ==", "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"bytes": "^3.1.2", "bytes": "^3.1.2",
"content-type": "^1.0.5", "content-type": "^1.0.5",
"debug": "^4.4.0", "debug": "^4.4.0",
"http-errors": "^2.0.0", "http-errors": "^2.0.0",
"iconv-lite": "^0.5.2", "iconv-lite": "^0.6.3",
"on-finished": "^2.4.1", "on-finished": "^2.4.1",
"qs": "^6.14.0", "qs": "^6.14.0",
"raw-body": "^3.0.0", "raw-body": "^3.0.0",
@@ -5447,16 +5644,6 @@
"node": ">= 0.8" "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": { "node_modules/detect-newline": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz",
@@ -6011,46 +6198,45 @@
} }
}, },
"node_modules/express": { "node_modules/express": {
"version": "5.0.1", "version": "5.1.0",
"resolved": "https://registry.npmjs.org/express/-/express-5.0.1.tgz", "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz",
"integrity": "sha512-ORF7g6qGnD+YtUG9yx4DFoqCShNMmUKiXuT5oWMHiOvt/4WFbHC6yCwQMTSBMno7AqntNCAzzcnnjowRkTL9eQ==", "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"accepts": "^2.0.0", "accepts": "^2.0.0",
"body-parser": "^2.0.1", "body-parser": "^2.2.0",
"content-disposition": "^1.0.0", "content-disposition": "^1.0.0",
"content-type": "~1.0.4", "content-type": "^1.0.5",
"cookie": "0.7.1", "cookie": "^0.7.1",
"cookie-signature": "^1.2.1", "cookie-signature": "^1.2.1",
"debug": "4.3.6", "debug": "^4.4.0",
"depd": "2.0.0", "encodeurl": "^2.0.0",
"encodeurl": "~2.0.0", "escape-html": "^1.0.3",
"escape-html": "~1.0.3", "etag": "^1.8.1",
"etag": "~1.8.1", "finalhandler": "^2.1.0",
"finalhandler": "^2.0.0", "fresh": "^2.0.0",
"fresh": "2.0.0", "http-errors": "^2.0.0",
"http-errors": "2.0.0",
"merge-descriptors": "^2.0.0", "merge-descriptors": "^2.0.0",
"methods": "~1.1.2",
"mime-types": "^3.0.0", "mime-types": "^3.0.0",
"on-finished": "2.4.1", "on-finished": "^2.4.1",
"once": "1.4.0", "once": "^1.4.0",
"parseurl": "~1.3.3", "parseurl": "^1.3.3",
"proxy-addr": "~2.0.7", "proxy-addr": "^2.0.7",
"qs": "6.13.0", "qs": "^6.14.0",
"range-parser": "~1.2.1", "range-parser": "^1.2.1",
"router": "^2.0.0", "router": "^2.2.0",
"safe-buffer": "5.2.1",
"send": "^1.1.0", "send": "^1.1.0",
"serve-static": "^2.1.0", "serve-static": "^2.2.0",
"setprototypeof": "1.2.0", "statuses": "^2.0.1",
"statuses": "2.0.1", "type-is": "^2.0.1",
"type-is": "^2.0.0", "vary": "^1.1.2"
"utils-merge": "1.0.1",
"vary": "~1.1.2"
}, },
"engines": { "engines": {
"node": ">= 18" "node": ">= 18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
} }
}, },
"node_modules/express/node_modules/content-disposition": { "node_modules/express/node_modules/content-disposition": {
@@ -6065,29 +6251,21 @@
"node": ">= 0.6" "node": ">= 0.6"
} }
}, },
"node_modules/express/node_modules/debug": { "node_modules/express/node_modules/qs": {
"version": "4.3.6", "version": "6.14.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
"integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==",
"license": "MIT", "license": "BSD-3-Clause",
"dependencies": { "dependencies": {
"ms": "2.1.2" "side-channel": "^1.1.0"
}, },
"engines": { "engines": {
"node": ">=6.0" "node": ">=0.6"
}, },
"peerDependenciesMeta": { "funding": {
"supports-color": { "url": "https://github.com/sponsors/ljharb"
"optional": true
}
} }
}, },
"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": { "node_modules/ext-list": {
"version": "2.2.2", "version": "2.2.2",
"resolved": "https://registry.npmjs.org/ext-list/-/ext-list-2.2.2.tgz", "resolved": "https://registry.npmjs.org/ext-list/-/ext-list-2.2.2.tgz",
@@ -7067,12 +7245,12 @@
} }
}, },
"node_modules/iconv-lite": { "node_modules/iconv-lite": {
"version": "0.5.2", "version": "0.6.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.5.2.tgz", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
"integrity": "sha512-kERHXvpSaB4aU3eANwidg79K8FlrN77m8G9V+0vOR3HYaRifrlwMEpT7ZBJqLSEIHnEgJTHcWK82wwLwwKwtag==", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"safer-buffer": ">= 2.1.2 < 3" "safer-buffer": ">= 2.1.2 < 3.0.0"
}, },
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
@@ -8650,12 +8828,12 @@
} }
}, },
"node_modules/mime-types": { "node_modules/mime-types": {
"version": "3.0.0", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.0.tgz", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz",
"integrity": "sha512-XqoSHeCGjVClAmoGFG3lVFqQFRIrTVw2OH3axRqAcfaw+gHWIfnASS92AV+Rl/mk0MupgZTRHQOjxY6YVnzK5w==", "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"mime-db": "^1.53.0" "mime-db": "^1.54.0"
}, },
"engines": { "engines": {
"node": ">= 0.6" "node": ">= 0.6"
@@ -8734,9 +8912,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/multer": { "node_modules/multer": {
"version": "1.4.5-lts.1", "version": "1.4.5-lts.2",
"resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.1.tgz", "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.2.tgz",
"integrity": "sha512-ywPWvcDMeH+z9gQq5qYHCCy+ethsk4goepZ45GLD63fOu0YcNecQxi64nDs3qluZB+murG3/D4dJ7+dGctcCQQ==", "integrity": "sha512-VzGiVigcG9zUAoCNU+xShztrlr1auZOlurXynNvO9GiWD1/mTBbUljOKY+qMeazBqXgRnjzeEgJI/wyjJUHg9A==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"append-field": "^1.0.0", "append-field": "^1.0.0",
@@ -8824,18 +9002,6 @@
"node": ">= 8.0" "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": { "node_modules/named-placeholders": {
"version": "1.1.3", "version": "1.1.3",
"resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.3.tgz", "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.3.tgz",
@@ -9807,18 +9973,6 @@
"node": ">= 0.8" "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": { "node_modules/react-is": {
"version": "18.3.1", "version": "18.3.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
@@ -10022,11 +10176,13 @@
} }
}, },
"node_modules/router": { "node_modules/router": {
"version": "2.1.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/router/-/router-2.1.0.tgz", "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
"integrity": "sha512-/m/NSLxeYEgWNtyC+WtNHCF7jbGxOibVWKnn+1Psff4dJGOfoXP+MuC/f2CwSmyiHdOIzYnYFp4W6GxWfekaLA==", "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"debug": "^4.4.0",
"depd": "^2.0.0",
"is-promise": "^4.0.0", "is-promise": "^4.0.0",
"parseurl": "^1.3.3", "parseurl": "^1.3.3",
"path-to-regexp": "^8.0.0" "path-to-regexp": "^8.0.0"
@@ -10179,19 +10335,18 @@
} }
}, },
"node_modules/send": { "node_modules/send": {
"version": "1.1.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/send/-/send-1.1.0.tgz", "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz",
"integrity": "sha512-v67WcEouB5GxbTWL/4NeToqcZiAWEq90N888fczVArY8A79J0L4FD7vj5hm3eUMua5EpoQ59wa/oovY6TLvRUA==", "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"debug": "^4.3.5", "debug": "^4.3.5",
"destroy": "^1.2.0",
"encodeurl": "^2.0.0", "encodeurl": "^2.0.0",
"escape-html": "^1.0.3", "escape-html": "^1.0.3",
"etag": "^1.8.1", "etag": "^1.8.1",
"fresh": "^0.5.2", "fresh": "^2.0.0",
"http-errors": "^2.0.0", "http-errors": "^2.0.0",
"mime-types": "^2.1.35", "mime-types": "^3.0.1",
"ms": "^2.1.3", "ms": "^2.1.3",
"on-finished": "^2.4.1", "on-finished": "^2.4.1",
"range-parser": "^1.2.1", "range-parser": "^1.2.1",
@@ -10201,36 +10356,6 @@
"node": ">= 18" "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": { "node_modules/seq-queue": {
"version": "0.0.5", "version": "0.0.5",
"resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz", "resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz",
@@ -10247,15 +10372,15 @@
} }
}, },
"node_modules/serve-static": { "node_modules/serve-static": {
"version": "2.1.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.1.0.tgz", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz",
"integrity": "sha512-A3We5UfEjG8Z7VkDv6uItWw6HY2bBSBJT1KtVESn6EOoOr2jAxNhxWCLY3jDE2WcuHXByWju74ck3ZgLwL8xmA==", "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"encodeurl": "^2.0.0", "encodeurl": "^2.0.0",
"escape-html": "^1.0.3", "escape-html": "^1.0.3",
"parseurl": "^1.3.3", "parseurl": "^1.3.3",
"send": "^1.0.0" "send": "^1.2.0"
}, },
"engines": { "engines": {
"node": ">= 18" "node": ">= 18"
@@ -11394,9 +11519,9 @@
} }
}, },
"node_modules/type-is": { "node_modules/type-is": {
"version": "2.0.0", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.0.tgz", "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz",
"integrity": "sha512-gd0sGezQYCbWSbkZr75mln4YBidWUN60+devscpLF5mtRDUpiaTvKpBNrdaCvel1NdR2k6vclXybU5fBd2i+nw==", "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"content-type": "^1.0.5", "content-type": "^1.0.5",
@@ -11414,24 +11539,24 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/typeorm": { "node_modules/typeorm": {
"version": "0.3.21", "version": "0.3.22",
"resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.3.21.tgz", "resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.3.22.tgz",
"integrity": "sha512-lh4rUWl1liZGjyPTWpwcK8RNI5x4ekln+/JJOox1wCd7xbucYDOXWD+1cSzTN3L0wbTGxxOtloM5JlxbOxEufA==", "integrity": "sha512-P/Tsz3UpJ9+K0oryC0twK5PO27zejLYYwMsE8SISfZc1lVHX+ajigiOyWsKbuXpEFMjD9z7UjLzY3+ElVOMMDA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@sqltools/formatter": "^1.2.5", "@sqltools/formatter": "^1.2.5",
"ansis": "^3.9.0", "ansis": "^3.17.0",
"app-root-path": "^3.1.0", "app-root-path": "^3.1.0",
"buffer": "^6.0.3", "buffer": "^6.0.3",
"dayjs": "^1.11.9", "dayjs": "^1.11.13",
"debug": "^4.3.4", "debug": "^4.4.0",
"dotenv": "^16.0.3", "dotenv": "^16.4.7",
"glob": "^10.4.5", "glob": "^10.4.5",
"sha.js": "^2.4.11", "sha.js": "^2.4.11",
"sql-highlight": "^6.0.0", "sql-highlight": "^6.0.0",
"tslib": "^2.5.0", "tslib": "^2.8.1",
"uuid": "^11.0.5", "uuid": "^11.1.0",
"yargs": "^17.6.2" "yargs": "^17.7.2"
}, },
"bin": { "bin": {
"typeorm": "cli.js", "typeorm": "cli.js",
@@ -11445,12 +11570,12 @@
"url": "https://opencollective.com/typeorm" "url": "https://opencollective.com/typeorm"
}, },
"peerDependencies": { "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", "@sap/hana-client": "^2.12.25",
"better-sqlite3": "^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0", "better-sqlite3": "^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0",
"hdb-pool": "^0.1.6", "hdb-pool": "^0.1.6",
"ioredis": "^5.0.4", "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", "mssql": "^9.1.1 || ^10.0.1 || ^11.0.1",
"mysql2": "^2.2.5 || ^3.0.1", "mysql2": "^2.2.5 || ^3.0.1",
"oracledb": "^6.3.0", "oracledb": "^6.3.0",
@@ -11462,7 +11587,7 @@
"sql.js": "^1.4.0", "sql.js": "^1.4.0",
"sqlite3": "^5.0.3", "sqlite3": "^5.0.3",
"ts-node": "^10.7.0", "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": { "peerDependenciesMeta": {
"@google-cloud/spanner": { "@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": { "node_modules/typeorm/node_modules/brace-expansion": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+9 -1
View File
@@ -37,13 +37,15 @@
"class-transformer": "^0.5.1", "class-transformer": "^0.5.1",
"class-validator": "^0.14.1", "class-validator": "^0.14.1",
"dotenv": "^16.4.7", "dotenv": "^16.4.7",
"express": "^5.1.0",
"multer": "^1.4.5-lts.2",
"mysql2": "^3.14.0", "mysql2": "^3.14.0",
"passport": "^0.7.0", "passport": "^0.7.0",
"passport-jwt": "^4.0.1", "passport-jwt": "^4.0.1",
"qrcode": "^1.5.4", "qrcode": "^1.5.4",
"reflect-metadata": "^0.2.2", "reflect-metadata": "^0.2.2",
"rxjs": "^7.8.1", "rxjs": "^7.8.1",
"typeorm": "^0.3.21" "typeorm": "^0.3.22"
}, },
"devDependencies": { "devDependencies": {
"@eslint/eslintrc": "^3.2.0", "@eslint/eslintrc": "^3.2.0",
@@ -55,6 +57,7 @@
"@swc/core": "^1.10.7", "@swc/core": "^1.10.7",
"@types/express": "^5.0.0", "@types/express": "^5.0.0",
"@types/jest": "^29.5.14", "@types/jest": "^29.5.14",
"@types/multer": "^1.4.12",
"@types/node": "^22.10.7", "@types/node": "^22.10.7",
"@types/qrcode": "^1.5.5", "@types/qrcode": "^1.5.5",
"@types/supertest": "^6.0.2", "@types/supertest": "^6.0.2",
@@ -89,5 +92,10 @@
], ],
"coverageDirectory": "../coverage", "coverageDirectory": "../coverage",
"testEnvironment": "node" "testEnvironment": "node"
},
"compilerOptions": {
"outDir": "./dist"
} }
} }
+6 -1
View File
@@ -3,12 +3,16 @@ import { TypeOrmModule } from '@nestjs/typeorm';
import { AlumnosController } from './alumnos.controller'; import { AlumnosController } from './alumnos.controller';
import { AlumnosService } from './alumnos.service'; import { AlumnosService } from './alumnos.service';
import { RegistroAlumno } from './entities/registro-alumno.entity'; import { RegistroAlumno } from './entities/registro-alumno.entity';
import { ConfigService } from '@nestjs/config'; import { ConfigModule, ConfigService } from '@nestjs/config';
@Module({ @Module({
imports: [ imports: [
TypeOrmModule.forFeature([RegistroAlumno], 'alumnosConnection'), 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({ TypeOrmModule.forRootAsync({
name: 'alumnosConnection', name: 'alumnosConnection',
@@ -24,6 +28,7 @@ import { ConfigService } from '@nestjs/config';
retryAttempts: 10, retryAttempts: 10,
retryDelay: 3000, retryDelay: 3000,
connectTimeout: 30000, connectTimeout: 30000,
}), }),
inject: [ConfigService], inject: [ConfigService],
}), }),
+29 -9
View File
@@ -15,7 +15,7 @@ import { RespuestaParticipanteCerradaModule } from './respuesta_participante_cer
import { CuestionarioRespondidoModule } from './cuestionario_respondido/cuestionario_respondido.module'; import { CuestionarioRespondidoModule } from './cuestionario_respondido/cuestionario_respondido.module';
import { RespuestaParticipanteAbiertaModule } from './respuesta_participante_abierta/respuesta_participante_abierta.module'; import { RespuestaParticipanteAbiertaModule } from './respuesta_participante_abierta/respuesta_participante_abierta.module';
import { TypeOrmModule } from '@nestjs/typeorm'; import { TypeOrmModule } from '@nestjs/typeorm';
import { ConfigModule } from '@nestjs/config'; import { ConfigModule, ConfigService } from '@nestjs/config';
import { EventoModule } from './evento/evento.module'; import { EventoModule } from './evento/evento.module';
import { TipoUserModule } from './tipo_user/tipo_user.module'; import { TipoUserModule } from './tipo_user/tipo_user.module';
import { ParticipanteModule } from './participante/participante.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 { ValidacionesModule } from './validaciones/validaciones.module';
import { AlumnosModule } from './alumnos/alumnos.module'; import { AlumnosModule } from './alumnos/alumnos.module';
import { AuthModule } from './auth/auth.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({ @Module({
@@ -33,25 +36,40 @@ import { AuthModule } from './auth/auth.module';
ConfigModule.forRoot({ ConfigModule.forRoot({
isGlobal: true, isGlobal: true,
}), }),
TypeOrmModule.forRoot({ TypeOrmModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (config: ConfigService) => ({
type: config.get<'mysql'>('DB_TYPE'),
host: config.get<string>('DB_HOST'),
port: +(config.get<number>('DB_PORT') || 3306),
username: config.get<string>('DB_USERNAME'),
password: config.get<string>('DB_PASSWORD'),
database: config.get<string>('DB_DATABASE'),
entities: [__dirname + '/**/*.entity{.ts,.js}'],
synchronize: true,
//dropSchema: true, // elimina la base de datos
}),
/*
type: 'mysql', type: 'mysql',
host: process.env.db_host, //process.env.db_host host: process.env.db_host, //process.env.db_host
username: process.env.db_username, username: process.env.db_username,
database: process.env.db_database, database: process.env.db_database,
password: process.env.db_password, password: process.env.db_password,
port: Number(process.env.db_port), port: Number(process.env.db_port),
*/
//synchronize: true,
synchronize: true, //dropSchema: true, // elimina la base de datos
dropSchema: true, // elimina la base de datos
// logging: true, // Habilita los logs para depuración // 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 //esta es mi base de datos local
/* /*
type: 'mysql', type: 'mysql',
host: 'localhost', host: 'localhost',
port: 3306, //3306 port: 3306, //3306
@@ -64,9 +82,10 @@ import { AuthModule } from './auth/auth.module';
//extra //extra
retryAttempts: 10, // Intentos para reconectar //retryAttempts: 10, // Intentos para reconectar
retryDelay: 3000, // Tiempo entre reintentos //retryDelay: 3000, // Tiempo entre reintentos
}), }),
TypeOrmModule.forFeature([RegistroAlumno], 'alumnosConnection'),
AuthModule, AuthModule,
CuestionarioModule, CuestionarioModule,
TipoCuestionarioModule, TipoCuestionarioModule,
@@ -88,6 +107,7 @@ import { AuthModule } from './auth/auth.module';
ParticipanteEventoModule, ParticipanteEventoModule,
ValidacionesModule, ValidacionesModule,
AlumnosModule, AlumnosModule,
ImagenModule,
], ],
controllers: [AppController], controllers: [AppController],
+6
View File
@@ -1,10 +1,16 @@
import { IsString } from "class-validator";
export class CreateEventoDto { export class CreateEventoDto {
@IsString()
tipo_evento: string; tipo_evento: string;
@IsString()
nombre_evento: string; nombre_evento: string;
@IsString()
fecha_inicio: Date; fecha_inicio: Date;
@IsString()
fecha_fin: Date; fecha_fin: Date;
//agregar el id del administrador //agregar el id del administrador
+3
View File
@@ -1,3 +1,4 @@
import { Imagen } from "src/imagen/imagen.entity";
import { Participante } from "src/participante/participante.entity"; import { Participante } from "src/participante/participante.entity";
import { ParticipanteEvento } from "src/participante_evento/participante_evento.entity"; import { ParticipanteEvento } from "src/participante_evento/participante_evento.entity";
import { Column, Entity, ManyToMany, OneToMany, PrimaryGeneratedColumn } from "typeorm"; import { Column, Entity, ManyToMany, OneToMany, PrimaryGeneratedColumn } from "typeorm";
@@ -38,4 +39,6 @@ export class Evento {
@OneToMany(() => ParticipanteEvento, participanteEvento => participanteEvento.evento) @OneToMany(() => ParticipanteEvento, participanteEvento => participanteEvento.evento)
participanteEventos: ParticipanteEvento[]; participanteEventos: ParticipanteEvento[];
@OneToMany(() => Imagen, (imagen) => imagen.evento)
imagenes: Imagen[]
} }
+4 -3
View File
@@ -1,13 +1,14 @@
import { Module } from '@nestjs/common'; import { forwardRef, Module } from '@nestjs/common';
import { EventoService } from './evento.service'; import { EventoService } from './evento.service';
import { EventoController } from './evento.controller'; import { EventoController } from './evento.controller';
import { TypeOrmModule } from '@nestjs/typeorm'; import { TypeOrmModule } from '@nestjs/typeorm';
import { Evento } from './evento.entity'; import { Evento } from './evento.entity';
import { ImagenModule } from 'src/imagen/imagen.module';
@Module({ @Module({
imports: [TypeOrmModule.forFeature([Evento])], imports: [TypeOrmModule.forFeature([Evento]), forwardRef(() => ImagenModule)],
controllers: [EventoController], controllers: [EventoController],
providers: [EventoService], providers: [EventoService],
exports: [EventoService] exports: [EventoService, TypeOrmModule]
}) })
export class EventoModule {} export class EventoModule {}
+2 -2
View File
@@ -29,7 +29,7 @@ export class EventoService {
getEventos() { getEventos() {
return this.eventoRepository.find({ return this.eventoRepository.find({
relations: ['participantes'] relations: ['participanteEventos', 'imagenes'] //participante
}) })
} }
@@ -38,7 +38,7 @@ export class EventoService {
where: { where: {
id_evento id_evento
}, },
relations: ['participantes'] relations: ['participanteEventos', 'imagenes'],
}) })
if (!eventoFound) if (!eventoFound)
+47
View File
@@ -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;
}
+17
View File
@@ -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
}
+108
View File
@@ -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;
}
}
+210
View File
@@ -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' })
);
}
+25
View File
@@ -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;
}
+14
View File
@@ -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 {}
+169
View File
@@ -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<Imagen>,
@InjectRepository(Evento) private eventoRepository: Repository<Evento>
) {}
async getUltimoOrden(id_evento: number): Promise<number> {
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<Imagen> {
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<Imagen[]> {
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<string> {
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
},
});
+19 -1
View File
@@ -5,9 +5,14 @@ import { DocsModule } from './docs/docs.module';
import { DataSource } from 'typeorm'; import { DataSource } from 'typeorm';
import { TipoPregunta } from './tipo_pregunta/entities/tipo_pregunta.entity'; import { TipoPregunta } from './tipo_pregunta/entities/tipo_pregunta.entity';
import { ValidationPipe } from '@nestjs/common'; import { ValidationPipe } from '@nestjs/common';
import { NestExpressApplication } from '@nestjs/platform-express';
import { join, resolve } from 'path';
async function bootstrap() { async function bootstrap() {
const app = await NestFactory.create(AppModule); //const app = await NestFactory.create(AppModule);
const app = await NestFactory.create<NestExpressApplication>(AppModule);
// Configuración de validación global // Configuración de validación global
app.useGlobalPipes( app.useGlobalPipes(
@@ -15,6 +20,8 @@ async function bootstrap() {
whitelist: true, // Elimina propiedades no decoradas whitelist: true, // Elimina propiedades no decoradas
forbidNonWhitelisted: true, // Arroja error si hay propiedades no decoradas forbidNonWhitelisted: true, // Arroja error si hay propiedades no decoradas
transform: true, // Transforma los datos recibidos al tipo definido en el DTO transform: true, // Transforma los datos recibidos al tipo definido en el DTO
}) })
); );
@@ -23,8 +30,19 @@ async function bootstrap() {
origin: '*', origin: '*',
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'], methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
allowedHeaders: ['Content-Type', 'Authorization'], 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 // Swagger setup
DocsModule.setupSwagger(app); DocsModule.setupSwagger(app);
Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 243 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Some files were not shown because too many files have changed in this diff Show More