diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..11ddd8d --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +node_modules +# Keep environment variables out of version control +.env diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..5e272f2 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,7 @@ +FROM node:latest +RUN mkdir -p /app +WORKDIR /app +COPY . /app +RUN npm install +EXPOSE 3000 +CMD ["npm","run", "dev"] \ No newline at end of file diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..76c4af1 --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,17 @@ +version: '3.8' +services: + database: + image: mysql:latest + environment: + MYSQL_ROOT_PASSWORD: root + ports: + - 3306:3306 + app: + image: application + container_name: application + build: + context: . + dockerfile: Dockerfile + ports: + - 3000:3000 + \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..c73760e --- /dev/null +++ b/package.json @@ -0,0 +1,33 @@ +{ + "name": "nodejs-code-challenge", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "dev": "ts-node-dev src/app.ts ", + "start": "ts-node-dev src/app.ts " + }, + "keywords": [], + "author": "", + "license": "ISC", + "devDependencies": { + "@types/bcryptjs": "^2.4.6", + "@types/express": "^4.17.21", + "@types/jsonwebtoken": "^9.0.6", + "@types/node": "^20.11.28", + "prisma": "^5.11.0", + "ts-node": "^10.9.2", + "ts-node-dev": "^2.0.0", + "typescript": "^5.4.2" + }, + "dependencies": { + "@prisma/client": "5.11.0", + "axios": "^1.6.8", + "bcryptjs": "^2.4.3", + "dotenv": "^16.4.5", + "express": "^4.18.3", + "jsonwebtoken": "^9.0.2", + "pokedex-promise-v2": "^4.2.0" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..0816aad --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,1217 @@ +lockfileVersion: '6.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +dependencies: + '@prisma/client': + specifier: 5.11.0 + version: 5.11.0(prisma@5.11.0) + axios: + specifier: ^1.6.8 + version: 1.6.8 + bcryptjs: + specifier: ^2.4.3 + version: 2.4.3 + dotenv: + specifier: ^16.4.5 + version: 16.4.5 + express: + specifier: ^4.18.3 + version: 4.18.3 + jsonwebtoken: + specifier: ^9.0.2 + version: 9.0.2 + pokedex-promise-v2: + specifier: ^4.2.0 + version: 4.2.0 + +devDependencies: + '@types/express': + specifier: ^4.17.21 + version: 4.17.21 + '@types/jsonwebtoken': + specifier: ^9.0.6 + version: 9.0.6 + '@types/node': + specifier: ^20.11.28 + version: 20.11.28 + prisma: + specifier: ^5.11.0 + version: 5.11.0 + ts-node: + specifier: ^10.9.2 + version: 10.9.2(@types/node@20.11.28)(typescript@5.4.2) + ts-node-dev: + specifier: ^2.0.0 + version: 2.0.0(@types/node@20.11.28)(typescript@5.4.2) + typescript: + specifier: ^5.4.2 + version: 5.4.2 + +packages: + + /@cspotcode/source-map-support@0.8.1: + resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} + engines: {node: '>=12'} + dependencies: + '@jridgewell/trace-mapping': 0.3.9 + dev: true + + /@jridgewell/resolve-uri@3.1.2: + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + dev: true + + /@jridgewell/sourcemap-codec@1.4.15: + resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} + dev: true + + /@jridgewell/trace-mapping@0.3.9: + resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.4.15 + dev: true + + /@prisma/client@5.11.0(prisma@5.11.0): + resolution: {integrity: sha512-SWshvS5FDXvgJKM/a0y9nDC1rqd7KG0Q6ZVzd+U7ZXK5soe73DJxJJgbNBt2GNXOa+ysWB4suTpdK5zfFPhwiw==} + engines: {node: '>=16.13'} + requiresBuild: true + peerDependencies: + prisma: '*' + peerDependenciesMeta: + prisma: + optional: true + dependencies: + prisma: 5.11.0 + dev: false + + /@prisma/debug@5.11.0: + resolution: {integrity: sha512-N6yYr3AbQqaiUg+OgjkdPp3KPW1vMTAgtKX6+BiB/qB2i1TjLYCrweKcUjzOoRM5BriA4idrkTej9A9QqTfl3A==} + + /@prisma/engines-version@5.11.0-15.efd2449663b3d73d637ea1fd226bafbcf45b3102: + resolution: {integrity: sha512-WXCuyoymvrS4zLz4wQagSsc3/nE6CHy8znyiMv8RKazKymOMd5o9FP5RGwGHAtgoxd+aB/BWqxuP/Ckfu7/3MA==} + + /@prisma/engines@5.11.0: + resolution: {integrity: sha512-gbrpQoBTYWXDRqD+iTYMirDlF9MMlQdxskQXbhARhG6A/uFQjB7DZMYocMQLoiZXO/IskfDOZpPoZE8TBQKtEw==} + requiresBuild: true + dependencies: + '@prisma/debug': 5.11.0 + '@prisma/engines-version': 5.11.0-15.efd2449663b3d73d637ea1fd226bafbcf45b3102 + '@prisma/fetch-engine': 5.11.0 + '@prisma/get-platform': 5.11.0 + + /@prisma/fetch-engine@5.11.0: + resolution: {integrity: sha512-994viazmHTJ1ymzvWugXod7dZ42T2ROeFuH6zHPcUfp/69+6cl5r9u3NFb6bW8lLdNjwLYEVPeu3hWzxpZeC0w==} + dependencies: + '@prisma/debug': 5.11.0 + '@prisma/engines-version': 5.11.0-15.efd2449663b3d73d637ea1fd226bafbcf45b3102 + '@prisma/get-platform': 5.11.0 + + /@prisma/get-platform@5.11.0: + resolution: {integrity: sha512-rxtHpMLxNTHxqWuGOLzR2QOyQi79rK1u1XYAVLZxDGTLz/A+uoDnjz9veBFlicrpWjwuieM4N6jcnjj/DDoidw==} + dependencies: + '@prisma/debug': 5.11.0 + + /@tsconfig/node10@1.0.9: + resolution: {integrity: sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==} + dev: true + + /@tsconfig/node12@1.0.11: + resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} + dev: true + + /@tsconfig/node14@1.0.3: + resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} + dev: true + + /@tsconfig/node16@1.0.4: + resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} + dev: true + + /@types/body-parser@1.19.5: + resolution: {integrity: sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==} + dependencies: + '@types/connect': 3.4.38 + '@types/node': 20.11.28 + dev: true + + /@types/connect@3.4.38: + resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} + dependencies: + '@types/node': 20.11.28 + dev: true + + /@types/express-serve-static-core@4.17.43: + resolution: {integrity: sha512-oaYtiBirUOPQGSWNGPWnzyAFJ0BP3cwvN4oWZQY+zUBwpVIGsKUkpBpSztp74drYcjavs7SKFZ4DX1V2QeN8rg==} + dependencies: + '@types/node': 20.11.28 + '@types/qs': 6.9.12 + '@types/range-parser': 1.2.7 + '@types/send': 0.17.4 + dev: true + + /@types/express@4.17.21: + resolution: {integrity: sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==} + dependencies: + '@types/body-parser': 1.19.5 + '@types/express-serve-static-core': 4.17.43 + '@types/qs': 6.9.12 + '@types/serve-static': 1.15.5 + dev: true + + /@types/http-errors@2.0.4: + resolution: {integrity: sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==} + dev: true + + /@types/jsonwebtoken@9.0.6: + resolution: {integrity: sha512-/5hndP5dCjloafCXns6SZyESp3Ldq7YjH3zwzwczYnjxIT0Fqzk5ROSYVGfFyczIue7IUEj8hkvLbPoLQ18vQw==} + dependencies: + '@types/node': 20.11.28 + dev: true + + /@types/mime@1.3.5: + resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} + dev: true + + /@types/mime@3.0.4: + resolution: {integrity: sha512-iJt33IQnVRkqeqC7PzBHPTC6fDlRNRW8vjrgqtScAhrmMwe8c4Eo7+fUGTa+XdWrpEgpyKWMYmi2dIwMAYRzPw==} + dev: true + + /@types/node@20.11.28: + resolution: {integrity: sha512-M/GPWVS2wLkSkNHVeLkrF2fD5Lx5UC4PxA0uZcKc6QqbIQUJyW1jVjueJYi1z8n0I5PxYrtpnPnWglE+y9A0KA==} + dependencies: + undici-types: 5.26.5 + dev: true + + /@types/qs@6.9.12: + resolution: {integrity: sha512-bZcOkJ6uWrL0Qb2NAWKa7TBU+mJHPzhx9jjLL1KHF+XpzEcR7EXHvjbHlGtR/IsP1vyPrehuS6XqkmaePy//mg==} + dev: true + + /@types/range-parser@1.2.7: + resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} + dev: true + + /@types/send@0.17.4: + resolution: {integrity: sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==} + dependencies: + '@types/mime': 1.3.5 + '@types/node': 20.11.28 + dev: true + + /@types/serve-static@1.15.5: + resolution: {integrity: sha512-PDRk21MnK70hja/YF8AHfC7yIsiQHn1rcXx7ijCFBX/k+XQJhQT/gw3xekXKJvx+5SXaMMS8oqQy09Mzvz2TuQ==} + dependencies: + '@types/http-errors': 2.0.4 + '@types/mime': 3.0.4 + '@types/node': 20.11.28 + dev: true + + /@types/strip-bom@3.0.0: + resolution: {integrity: sha512-xevGOReSYGM7g/kUBZzPqCrR/KYAo+F0yiPc85WFTJa0MSLtyFTVTU6cJu/aV4mid7IffDIWqo69THF2o4JiEQ==} + dev: true + + /@types/strip-json-comments@0.0.30: + resolution: {integrity: sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==} + dev: true + + /accepts@1.3.8: + resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} + engines: {node: '>= 0.6'} + dependencies: + mime-types: 2.1.35 + negotiator: 0.6.3 + dev: false + + /acorn-walk@8.3.2: + resolution: {integrity: sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==} + engines: {node: '>=0.4.0'} + dev: true + + /acorn@8.11.3: + resolution: {integrity: sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==} + engines: {node: '>=0.4.0'} + hasBin: true + dev: true + + /anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + dev: true + + /arg@4.1.3: + resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} + dev: true + + /array-flatten@1.1.1: + resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} + dev: false + + /asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + dev: false + + /axios@1.6.8: + resolution: {integrity: sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==} + dependencies: + follow-redirects: 1.15.6 + form-data: 4.0.0 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + dev: false + + /balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + dev: true + + /bcryptjs@2.4.3: + resolution: {integrity: sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==} + dev: false + + /binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} + dev: true + + /body-parser@1.20.2: + resolution: {integrity: sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + http-errors: 2.0.0 + iconv-lite: 0.4.24 + on-finished: 2.4.1 + qs: 6.11.0 + raw-body: 2.5.2 + type-is: 1.6.18 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + dev: false + + /brace-expansion@1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + dev: true + + /braces@3.0.2: + resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} + engines: {node: '>=8'} + dependencies: + fill-range: 7.0.1 + dev: true + + /buffer-equal-constant-time@1.0.1: + resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} + dev: false + + /buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + dev: true + + /bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + dev: false + + /call-bind@1.0.7: + resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==} + engines: {node: '>= 0.4'} + dependencies: + es-define-property: 1.0.0 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.2.4 + set-function-length: 1.2.2 + dev: false + + /chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} + dependencies: + anymatch: 3.1.3 + braces: 3.0.2 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + dev: true + + /clone@2.1.2: + resolution: {integrity: sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==} + engines: {node: '>=0.8'} + dev: false + + /combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + dependencies: + delayed-stream: 1.0.0 + dev: false + + /concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + dev: true + + /content-disposition@0.5.4: + resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} + engines: {node: '>= 0.6'} + dependencies: + safe-buffer: 5.2.1 + dev: false + + /content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + dev: false + + /cookie-signature@1.0.6: + resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} + dev: false + + /cookie@0.5.0: + resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==} + engines: {node: '>= 0.6'} + dev: false + + /create-require@1.1.1: + resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} + dev: true + + /debug@2.6.9: + resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.0.0 + dev: false + + /define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} + dependencies: + es-define-property: 1.0.0 + es-errors: 1.3.0 + gopd: 1.0.1 + dev: false + + /delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + dev: false + + /depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + dev: false + + /destroy@1.2.0: + resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + dev: false + + /diff@4.0.2: + resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} + engines: {node: '>=0.3.1'} + dev: true + + /dotenv@16.4.5: + resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==} + engines: {node: '>=12'} + dev: false + + /dynamic-dedupe@0.3.0: + resolution: {integrity: sha512-ssuANeD+z97meYOqd50e04Ze5qp4bPqo8cCkI4TRjZkzAUgIDTrXV1R8QCdINpiI+hw14+rYazvTRdQrz0/rFQ==} + dependencies: + xtend: 4.0.2 + dev: true + + /ecdsa-sig-formatter@1.0.11: + resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} + dependencies: + safe-buffer: 5.2.1 + dev: false + + /ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + dev: false + + /encodeurl@1.0.2: + resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} + engines: {node: '>= 0.8'} + dev: false + + /es-define-property@1.0.0: + resolution: {integrity: sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==} + engines: {node: '>= 0.4'} + dependencies: + get-intrinsic: 1.2.4 + dev: false + + /es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + dev: false + + /escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + dev: false + + /etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + dev: false + + /express@4.18.3: + resolution: {integrity: sha512-6VyCijWQ+9O7WuVMTRBTl+cjNNIzD5cY5mQ1WM8r/LEkI2u8EYpOotESNwzNlyCn3g+dmjKYI6BmNneSr/FSRw==} + engines: {node: '>= 0.10.0'} + dependencies: + accepts: 1.3.8 + array-flatten: 1.1.1 + body-parser: 1.20.2 + content-disposition: 0.5.4 + content-type: 1.0.5 + cookie: 0.5.0 + cookie-signature: 1.0.6 + debug: 2.6.9 + depd: 2.0.0 + encodeurl: 1.0.2 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 1.2.0 + fresh: 0.5.2 + http-errors: 2.0.0 + merge-descriptors: 1.0.1 + methods: 1.1.2 + on-finished: 2.4.1 + parseurl: 1.3.3 + path-to-regexp: 0.1.7 + proxy-addr: 2.0.7 + qs: 6.11.0 + range-parser: 1.2.1 + safe-buffer: 5.2.1 + send: 0.18.0 + serve-static: 1.15.0 + setprototypeof: 1.2.0 + statuses: 2.0.1 + type-is: 1.6.18 + utils-merge: 1.0.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + dev: false + + /fill-range@7.0.1: + resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} + engines: {node: '>=8'} + dependencies: + to-regex-range: 5.0.1 + dev: true + + /finalhandler@1.2.0: + resolution: {integrity: sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==} + engines: {node: '>= 0.8'} + dependencies: + debug: 2.6.9 + encodeurl: 1.0.2 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.1 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + dev: false + + /follow-redirects@1.15.6: + resolution: {integrity: sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + dev: false + + /form-data@4.0.0: + resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} + engines: {node: '>= 6'} + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.35 + dev: false + + /forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + dev: false + + /fresh@0.5.2: + resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} + engines: {node: '>= 0.6'} + dev: false + + /fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + dev: true + + /fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + /get-intrinsic@1.2.4: + resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==} + engines: {node: '>= 0.4'} + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + has-proto: 1.0.3 + has-symbols: 1.0.3 + hasown: 2.0.2 + dev: false + + /glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + dependencies: + is-glob: 4.0.3 + dev: true + + /glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + dev: true + + /gopd@1.0.1: + resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} + dependencies: + get-intrinsic: 1.2.4 + dev: false + + /has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + dependencies: + es-define-property: 1.0.0 + dev: false + + /has-proto@1.0.3: + resolution: {integrity: sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==} + engines: {node: '>= 0.4'} + dev: false + + /has-symbols@1.0.3: + resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} + engines: {node: '>= 0.4'} + dev: false + + /hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + dependencies: + function-bind: 1.1.2 + + /http-errors@2.0.0: + resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} + engines: {node: '>= 0.8'} + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.1 + toidentifier: 1.0.1 + dev: false + + /iconv-lite@0.4.24: + resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} + engines: {node: '>=0.10.0'} + dependencies: + safer-buffer: 2.1.2 + dev: false + + /inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + dev: true + + /inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + /ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + dev: false + + /is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + dependencies: + binary-extensions: 2.3.0 + dev: true + + /is-core-module@2.13.1: + resolution: {integrity: sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==} + dependencies: + hasown: 2.0.2 + dev: true + + /is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + dev: true + + /is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + dependencies: + is-extglob: 2.1.1 + dev: true + + /is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + dev: true + + /jsonwebtoken@9.0.2: + resolution: {integrity: sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==} + engines: {node: '>=12', npm: '>=6'} + dependencies: + jws: 3.2.2 + lodash.includes: 4.3.0 + lodash.isboolean: 3.0.3 + lodash.isinteger: 4.0.4 + lodash.isnumber: 3.0.3 + lodash.isplainobject: 4.0.6 + lodash.isstring: 4.0.1 + lodash.once: 4.1.1 + ms: 2.1.3 + semver: 7.6.0 + dev: false + + /jwa@1.4.1: + resolution: {integrity: sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==} + dependencies: + buffer-equal-constant-time: 1.0.1 + ecdsa-sig-formatter: 1.0.11 + safe-buffer: 5.2.1 + dev: false + + /jws@3.2.2: + resolution: {integrity: sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==} + dependencies: + jwa: 1.4.1 + safe-buffer: 5.2.1 + dev: false + + /lodash.includes@4.3.0: + resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==} + dev: false + + /lodash.isboolean@3.0.3: + resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==} + dev: false + + /lodash.isinteger@4.0.4: + resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==} + dev: false + + /lodash.isnumber@3.0.3: + resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==} + dev: false + + /lodash.isplainobject@4.0.6: + resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} + dev: false + + /lodash.isstring@4.0.1: + resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==} + dev: false + + /lodash.once@4.1.1: + resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} + dev: false + + /lru-cache@6.0.0: + resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} + engines: {node: '>=10'} + dependencies: + yallist: 4.0.0 + dev: false + + /make-error@1.3.6: + resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + dev: true + + /media-typer@0.3.0: + resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} + engines: {node: '>= 0.6'} + dev: false + + /merge-descriptors@1.0.1: + resolution: {integrity: sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==} + dev: false + + /methods@1.1.2: + resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} + engines: {node: '>= 0.6'} + dev: false + + /mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + dev: false + + /mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + dependencies: + mime-db: 1.52.0 + dev: false + + /mime@1.6.0: + resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} + engines: {node: '>=4'} + hasBin: true + dev: false + + /minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + dependencies: + brace-expansion: 1.1.11 + dev: true + + /minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + dev: true + + /mkdirp@1.0.4: + resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} + engines: {node: '>=10'} + hasBin: true + dev: true + + /ms@2.0.0: + resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} + dev: false + + /ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + dev: false + + /negotiator@0.6.3: + resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} + engines: {node: '>= 0.6'} + dev: false + + /node-cache@5.1.2: + resolution: {integrity: sha512-t1QzWwnk4sjLWaQAS8CHgOJ+RAfmHpxFWmc36IWTiWHQfs0w5JDMBS1b1ZxQteo0vVVuWJvIUKHDkkeK7vIGCg==} + engines: {node: '>= 8.0.0'} + dependencies: + clone: 2.1.2 + dev: false + + /normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + dev: true + + /object-inspect@1.13.1: + resolution: {integrity: sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==} + dev: false + + /on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + dependencies: + ee-first: 1.1.1 + dev: false + + /once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + dependencies: + wrappy: 1.0.2 + dev: true + + /p-map@7.0.1: + resolution: {integrity: sha512-2wnaR0XL/FDOj+TgpDuRb2KTjLnu3Fma6b1ZUwGY7LcqenMcvP/YFpjpbPKY6WVGsbuJZRuoUz8iPrt8ORnAFw==} + engines: {node: '>=18'} + dev: false + + /parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + dev: false + + /path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + dev: true + + /path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + dev: true + + /path-to-regexp@0.1.7: + resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==} + dev: false + + /picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + dev: true + + /pokedex-promise-v2@4.2.0: + resolution: {integrity: sha512-7BnpWbq/aIMTDLsy0ciNRgkP5NGLQ2OcKI0QuePNdxbB1ZAmjxNZ1D7x6sfVgZwMFM+eewrr0eeStCzZ0XwiWw==} + engines: {node: '>=18'} + dependencies: + axios: 1.6.8 + node-cache: 5.1.2 + p-map: 7.0.1 + transitivePeerDependencies: + - debug + dev: false + + /prisma@5.11.0: + resolution: {integrity: sha512-KCLiug2cs0Je7kGkQBN9jDWoZ90ogE/kvZTUTgz2h94FEo8pczCkPH7fPNXkD1sGU7Yh65risGGD1HQ5DF3r3g==} + engines: {node: '>=16.13'} + hasBin: true + requiresBuild: true + dependencies: + '@prisma/engines': 5.11.0 + + /proxy-addr@2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + dev: false + + /proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + dev: false + + /qs@6.11.0: + resolution: {integrity: sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==} + engines: {node: '>=0.6'} + dependencies: + side-channel: 1.0.6 + dev: false + + /range-parser@1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} + dev: false + + /raw-body@2.5.2: + resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==} + engines: {node: '>= 0.8'} + dependencies: + bytes: 3.1.2 + http-errors: 2.0.0 + iconv-lite: 0.4.24 + unpipe: 1.0.0 + dev: false + + /readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + dependencies: + picomatch: 2.3.1 + dev: true + + /resolve@1.22.8: + resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} + hasBin: true + dependencies: + is-core-module: 2.13.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + dev: true + + /rimraf@2.7.1: + resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==} + hasBin: true + dependencies: + glob: 7.2.3 + dev: true + + /safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + dev: false + + /safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + dev: false + + /semver@7.6.0: + resolution: {integrity: sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==} + engines: {node: '>=10'} + hasBin: true + dependencies: + lru-cache: 6.0.0 + dev: false + + /send@0.18.0: + resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==} + engines: {node: '>= 0.8.0'} + dependencies: + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + encodeurl: 1.0.2 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 0.5.2 + http-errors: 2.0.0 + mime: 1.6.0 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.1 + transitivePeerDependencies: + - supports-color + dev: false + + /serve-static@1.15.0: + resolution: {integrity: sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==} + engines: {node: '>= 0.8.0'} + dependencies: + encodeurl: 1.0.2 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 0.18.0 + transitivePeerDependencies: + - supports-color + dev: false + + /set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.2.4 + gopd: 1.0.1 + has-property-descriptors: 1.0.2 + dev: false + + /setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + dev: false + + /side-channel@1.0.6: + resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + get-intrinsic: 1.2.4 + object-inspect: 1.13.1 + dev: false + + /source-map-support@0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + dev: true + + /source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + dev: true + + /statuses@2.0.1: + resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} + engines: {node: '>= 0.8'} + dev: false + + /strip-bom@3.0.0: + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} + dev: true + + /strip-json-comments@2.0.1: + resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} + engines: {node: '>=0.10.0'} + dev: true + + /supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + dev: true + + /to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + dependencies: + is-number: 7.0.0 + dev: true + + /toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + dev: false + + /tree-kill@1.2.2: + resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} + hasBin: true + dev: true + + /ts-node-dev@2.0.0(@types/node@20.11.28)(typescript@5.4.2): + resolution: {integrity: sha512-ywMrhCfH6M75yftYvrvNarLEY+SUXtUvU8/0Z6llrHQVBx12GiFk5sStF8UdfE/yfzk9IAq7O5EEbTQsxlBI8w==} + engines: {node: '>=0.8.0'} + hasBin: true + peerDependencies: + node-notifier: '*' + typescript: '*' + peerDependenciesMeta: + node-notifier: + optional: true + dependencies: + chokidar: 3.6.0 + dynamic-dedupe: 0.3.0 + minimist: 1.2.8 + mkdirp: 1.0.4 + resolve: 1.22.8 + rimraf: 2.7.1 + source-map-support: 0.5.21 + tree-kill: 1.2.2 + ts-node: 10.9.2(@types/node@20.11.28)(typescript@5.4.2) + tsconfig: 7.0.0 + typescript: 5.4.2 + transitivePeerDependencies: + - '@swc/core' + - '@swc/wasm' + - '@types/node' + dev: true + + /ts-node@10.9.2(@types/node@20.11.28)(typescript@5.4.2): + resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} + hasBin: true + peerDependencies: + '@swc/core': '>=1.2.50' + '@swc/wasm': '>=1.2.50' + '@types/node': '*' + typescript: '>=2.7' + peerDependenciesMeta: + '@swc/core': + optional: true + '@swc/wasm': + optional: true + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.9 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.4 + '@types/node': 20.11.28 + acorn: 8.11.3 + acorn-walk: 8.3.2 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 5.4.2 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + dev: true + + /tsconfig@7.0.0: + resolution: {integrity: sha512-vZXmzPrL+EmC4T/4rVlT2jNVMWCi/O4DIiSj3UHg1OE5kCKbk4mfrXc6dZksLgRM/TZlKnousKH9bbTazUWRRw==} + dependencies: + '@types/strip-bom': 3.0.0 + '@types/strip-json-comments': 0.0.30 + strip-bom: 3.0.0 + strip-json-comments: 2.0.1 + dev: true + + /type-is@1.6.18: + resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} + engines: {node: '>= 0.6'} + dependencies: + media-typer: 0.3.0 + mime-types: 2.1.35 + dev: false + + /typescript@5.4.2: + resolution: {integrity: sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==} + engines: {node: '>=14.17'} + hasBin: true + dev: true + + /undici-types@5.26.5: + resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + dev: true + + /unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + dev: false + + /utils-merge@1.0.1: + resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} + engines: {node: '>= 0.4.0'} + dev: false + + /v8-compile-cache-lib@3.0.1: + resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} + dev: true + + /vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + dev: false + + /wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + dev: true + + /xtend@4.0.2: + resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} + engines: {node: '>=0.4'} + dev: true + + /yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + dev: false + + /yn@3.1.1: + resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} + engines: {node: '>=6'} + dev: true diff --git a/prisma/db.ts b/prisma/db.ts new file mode 100644 index 0000000..2df702b --- /dev/null +++ b/prisma/db.ts @@ -0,0 +1,4 @@ +import { PrismaClient } from "@prisma/client"; + +let prisma: PrismaClient = new PrismaClient(); +export default prisma; \ No newline at end of file diff --git a/prisma/schema.prisma b/prisma/schema.prisma new file mode 100644 index 0000000..19c6f28 --- /dev/null +++ b/prisma/schema.prisma @@ -0,0 +1,60 @@ +// This is your Prisma schema file, +// learn more about it in the docs: https://pris.ly/d/prisma-schema + +// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions? +// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init + +generator client { + provider = "prisma-client-js" +} + +datasource db { + provider = "mysql" + url = env("DATABASE_URL") +} + +model User { + user_id Int @id @default(autoincrement()) + user_name String @unique + user_password String + user_lastname String + user_email String + pokemons Pokemon[] + favorites Favorite[] +} + +model Pokemon { + user_pokemon_id Int @id @default(autoincrement()) + pokemon_id Int + pokemon_name String + user_id Int + user User @relation(fields: [user_id], references: [user_id], onDelete: Cascade) + Favorite Favorite[] + typeByPokemon TypeByPokemon[] + + @@unique([pokemon_id, user_id]) +} + +model Favorite { + favorites_id Int @id @default(autoincrement()) + pokemon_id Int + user_id Int + user User @relation(fields: [user_id], references: [user_id], onDelete: Cascade) + pokemon Pokemon @relation(fields: [pokemon_id], references: [user_pokemon_id], onDelete: Cascade) + + @@unique([user_id, pokemon_id]) +} + +model Type { + type_id Int @id @default(autoincrement()) + type_name String + typeByPokemon TypeByPokemon[] +} + +model TypeByPokemon { + typeByPokemon_id Int @id @default(autoincrement()) + type_id Int + pokemon_id Int + pokemon Pokemon @relation(fields: [pokemon_id], references: [user_pokemon_id]) + type Type @relation(fields: [type_id], references: [type_id]) +} diff --git a/src/Index.ts b/src/Index.ts new file mode 100644 index 0000000..877188b --- /dev/null +++ b/src/Index.ts @@ -0,0 +1,80 @@ +import express, { Request, Response } from 'express'; +import prisma from './prisma/client'; +import axios from 'axios"; +import jwt from 'jsonwebtoken'; +import dotenv from 'dotenv'; + +dotenv.config(); + +const app = express(); +app.use(express.json()); + +const PORT = process.env.PORT || 3000; +const JWT_SECRET = process.env.JWT_SECRET || 'secretkey'; + +const authentication = (req: Request, res: Response, next: ()=> void) => { + const authHeader = req.headers['authorization']; + const token = authHeader && authHeader.split(' ')[1]; + if (token == null) return res.sendStatus(401); + + jwt.verify(token, JWT_SECRET, (err: any, user: any) => { + if(err) return res.sendStatus(403); + req.user = user; + next(); + }); +}; + +app.post('/register', async (req, res) => { + const { username, password } = req.body; + try{ + const user = await prisma.user.create({ + data:{ + username, + password + } + }); + res.json(user); + } catch (err) { + res.status(400).json({ message: `Error al registrar usuario: ${err.message}`}); + } +}); + +app.post('/login', async (req, res) => { + + const { username, password } = req.body; + + const user = await prisma.user.findUnique({ + where: {username}, + }); + + if (!user || !user.password !== password) { + return res.status(400).json({ message: 'Usuario o contraseña inválido.' }); + } + + const accessToken = jwt.sign({ username}, JWT_SECRET); + res.json({ accessToken }); + +}); + +app.post('/apiPokemon', authenticateToken, async (req, res) => { + const { pokemonName } = req.body; + try{ + const response = await axios.get(`https://pokeapi.co/api/v2/pokemon/${pokemonName}`); + + const pokemon = await prisma.pokemon.create({ + data:{ + name: response.data.name, + type: response.data.type[0].type_name, + userId: req.user.id, + } + }); + + res.json(pokemon); + } catch (err) { + res.status(400).json({message: `Error al guardar pokemon ${err.message}`}); + } +}); + +app.listen(PORT, () => { + console.log(`Corriendo en el puerto ${PORT}`); +}); \ No newline at end of file diff --git a/src/app.ts b/src/app.ts new file mode 100644 index 0000000..9f64366 --- /dev/null +++ b/src/app.ts @@ -0,0 +1,16 @@ +import express from 'express'; +import userRoutes from './routes/UserRoutes'; +import pokemonRoutes from './routes/PokemonRoutes'; +import * as Auth from './middleware/Auth'; + +const app = express(); + +app.use(express.json()); + +app.use('/user', userRoutes); + +app.use('/pokemon', Auth.authenticateToken, pokemonRoutes); + +app.listen(3000); + +export default app; \ No newline at end of file diff --git a/src/important.md b/src/important.md new file mode 100644 index 0000000..c4d4a81 --- /dev/null +++ b/src/important.md @@ -0,0 +1,9 @@ +# Comandos + +# Iniciar los servicios + +- docker compose up + +# Hacer push, generar db y client desde terminal + +- docker exec -it application sh -c "npx prisma db push" diff --git a/src/middleware/Auth.ts b/src/middleware/Auth.ts new file mode 100644 index 0000000..8adb92b --- /dev/null +++ b/src/middleware/Auth.ts @@ -0,0 +1,48 @@ +import express, { Request, Response } from 'express'; +import jwt from 'jsonwebtoken'; +import * as User from '../services/UserService'; +import { IUser } from '../models/User'; +import { verify } from 'crypto'; + + +const JWT_SECRET = '123456'; + +export const authenticateToken = async (req: Request, res: Response, next: () => void) => { + + const authHeader = req.headers['authorization']; + const token = authHeader && authHeader.split(' ')[1]; + + if (token == null) return res.sendStatus(401); + + const Verify = jwt.verify(token, JWT_SECRET!); + if(Verify){ + const user = await getUser(req, res); + if(user===null){ + return res.sendStatus(401); + }else{ + next(); + } + }else{ + return res.sendStatus(401); + } +}; + +export const getUser = async (req: Request, res: Response) : Promise => { + const authHeader = req.headers['authorization']; + + const token = authHeader && authHeader.split(' ')[1]; + + const Verify = jwt.verify(token!, JWT_SECRET!) as jwt.JwtPayload; + + const user = await User.getUserByID(Verify.userId)!; + + return user!; +} + +export const getToken = (user: IUser) : string => { + + const accessToken = jwt.sign({ userId: user.user_id }, JWT_SECRET); + + return accessToken; +}; + diff --git a/src/models/Favorite.ts b/src/models/Favorite.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/models/Pokemon.ts b/src/models/Pokemon.ts new file mode 100644 index 0000000..80e119b --- /dev/null +++ b/src/models/Pokemon.ts @@ -0,0 +1,7 @@ + +export interface IPokemon { + user_pokemon_id: number, + pokemon_id: number, + pokemon_name: string, + user_id: number, +} \ No newline at end of file diff --git a/src/models/PokemonType.ts b/src/models/PokemonType.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/models/User.ts b/src/models/User.ts new file mode 100644 index 0000000..b05c12c --- /dev/null +++ b/src/models/User.ts @@ -0,0 +1,8 @@ + +export interface IUser{ + user_id: number, + user_name: string, + user_lastname: string, + user_password: string, + user_email: string, +} \ No newline at end of file diff --git a/src/models/apiModels/pokemon.ts b/src/models/apiModels/pokemon.ts new file mode 100644 index 0000000..2320302 --- /dev/null +++ b/src/models/apiModels/pokemon.ts @@ -0,0 +1,500 @@ + +export interface IPokemon { + abilities: Ability[]; + base_experience: number; + cries: Cries; + forms: Species[]; + game_indices: GameIndex[]; + height: number; + held_items: any[]; + id: number; + is_default: boolean; + location_area_encounters: string; + moves: Move[]; + name: string; + order: number; + past_abilities: any[]; + past_types: any[]; + species: Species; + sprites: Sprites; + stats: Stat[]; + types: Type[]; + weight: number; +} + +export interface Ability { + ability: Species; + is_hidden: boolean; + slot: number; +} + +export interface Species { + name: string; + url: string; +} + +export interface Cries { + latest: string; + legacy: string; +} + +export interface GameIndex { + game_index: number; + version: Species; +} + +export interface Move { + move: Species; + version_group_details: VersionGroupDetail[]; +} + +export interface VersionGroupDetail { + level_learned_at: number; + move_learn_method: Species; + version_group: Species; +} + +export interface GenerationV { + "black-white": Sprites; +} + +export interface GenerationIv { + "diamond-pearl": Sprites; + "heartgold-soulsilver": Sprites; + platinum: Sprites; +} + +export interface Versions { + "generation-i": GenerationI; + "generation-ii": GenerationIi; + "generation-iii": GenerationIii; + "generation-iv": GenerationIv; + "generation-v": GenerationV; + "generation-vi": { [key: string]: Home }; + "generation-vii": GenerationVii; + "generation-viii": GenerationViii; +} + +export interface Other { + dream_world: DreamWorld; + home: Home; + "official-artwork": OfficialArtwork; + showdown: Sprites; +} + +export interface Sprites { + back_default: string; + back_female: null; + back_shiny: string; + back_shiny_female: null; + front_default: string; + front_female: null; + front_shiny: string; + front_shiny_female: null; + other?: Other; + versions?: Versions; + animated?: Sprites; +} + +export interface GenerationI { + "red-blue": RedBlue; + yellow: RedBlue; +} + +export interface RedBlue { + back_default: string; + back_gray: string; + back_transparent: string; + front_default: string; + front_gray: string; + front_transparent: string; +} + +export interface GenerationIi { + crystal: Crystal; + gold: Gold; + silver: Gold; +} + +export interface Crystal { + back_default: string; + back_shiny: string; + back_shiny_transparent: string; + back_transparent: string; + front_default: string; + front_shiny: string; + front_shiny_transparent: string; + front_transparent: string; +} + +export interface Gold { + back_default: string; + back_shiny: string; + front_default: string; + front_shiny: string; + front_transparent?: string; +} + +export interface GenerationIii { + emerald: OfficialArtwork; + "firered-leafgreen": Gold; + "ruby-sapphire": Gold; +} + +export interface OfficialArtwork { + front_default: string; + front_shiny: string; +} + +export interface Home { + front_default: string; + front_female: null; + front_shiny: string; + front_shiny_female: null; +} + +export interface GenerationVii { + icons: DreamWorld; + "ultra-sun-ultra-moon": Home; +} + +export interface DreamWorld { + front_default: string; + front_female: null; +} + +export interface GenerationViii { + icons: DreamWorld; +} + +export interface Stat { + base_stat: number; + effort: number; + stat: Species; +} + +export interface Type { + slot: number; + type: Species; +} + +// Converts JSON strings to/from your types +// and asserts the results of JSON.parse at runtime +export class Convert { + public static toWelcome(json: string): IPokemon { + return cast(JSON.parse(json), r("Welcome")); + } + + public static welcomeToJson(value: IPokemon): string { + return JSON.stringify(uncast(value, r("Welcome")), null, 2); + } +} + +function invalidValue(typ: any, val: any, key: any, parent: any = ''): never { + const prettyTyp = prettyTypeName(typ); + const parentText = parent ? ` on ${parent}` : ''; + const keyText = key ? ` for key "${key}"` : ''; + throw Error(`Invalid value${keyText}${parentText}. Expected ${prettyTyp} but got ${JSON.stringify(val)}`); +} + +function prettyTypeName(typ: any): string { + if (Array.isArray(typ)) { + if (typ.length === 2 && typ[0] === undefined) { + return `an optional ${prettyTypeName(typ[1])}`; + } else { + return `one of [${typ.map(a => { return prettyTypeName(a); }).join(", ")}]`; + } + } else if (typeof typ === "object" && typ.literal !== undefined) { + return typ.literal; + } else { + return typeof typ; + } +} + +function jsonToJSProps(typ: any): any { + if (typ.jsonToJS === undefined) { + const map: any = {}; + typ.props.forEach((p: any) => map[p.json] = { key: p.js, typ: p.typ }); + typ.jsonToJS = map; + } + return typ.jsonToJS; +} + +function jsToJSONProps(typ: any): any { + if (typ.jsToJSON === undefined) { + const map: any = {}; + typ.props.forEach((p: any) => map[p.js] = { key: p.json, typ: p.typ }); + typ.jsToJSON = map; + } + return typ.jsToJSON; +} + +function transform(val: any, typ: any, getProps: any, key: any = '', parent: any = ''): any { + function transformPrimitive(typ: string, val: any): any { + if (typeof typ === typeof val) return val; + return invalidValue(typ, val, key, parent); + } + + function transformUnion(typs: any[], val: any): any { + // val must validate against one typ in typs + const l = typs.length; + for (let i = 0; i < l; i++) { + const typ = typs[i]; + try { + return transform(val, typ, getProps); + } catch (_) {} + } + return invalidValue(typs, val, key, parent); + } + + function transformEnum(cases: string[], val: any): any { + if (cases.indexOf(val) !== -1) return val; + return invalidValue(cases.map(a => { return l(a); }), val, key, parent); + } + + function transformArray(typ: any, val: any): any { + // val must be an array with no invalid elements + if (!Array.isArray(val)) return invalidValue(l("array"), val, key, parent); + return val.map(el => transform(el, typ, getProps)); + } + + function transformDate(val: any): any { + if (val === null) { + return null; + } + const d = new Date(val); + if (isNaN(d.valueOf())) { + return invalidValue(l("Date"), val, key, parent); + } + return d; + } + + function transformObject(props: { [k: string]: any }, additional: any, val: any): any { + if (val === null || typeof val !== "object" || Array.isArray(val)) { + return invalidValue(l(ref || "object"), val, key, parent); + } + const result: any = {}; + Object.getOwnPropertyNames(props).forEach(key => { + const prop = props[key]; + const v = Object.prototype.hasOwnProperty.call(val, key) ? val[key] : undefined; + result[prop.key] = transform(v, prop.typ, getProps, key, ref); + }); + Object.getOwnPropertyNames(val).forEach(key => { + if (!Object.prototype.hasOwnProperty.call(props, key)) { + result[key] = transform(val[key], additional, getProps, key, ref); + } + }); + return result; + } + + if (typ === "any") return val; + if (typ === null) { + if (val === null) return val; + return invalidValue(typ, val, key, parent); + } + if (typ === false) return invalidValue(typ, val, key, parent); + let ref: any = undefined; + while (typeof typ === "object" && typ.ref !== undefined) { + ref = typ.ref; + typ = typeMap[typ.ref]; + } + if (Array.isArray(typ)) return transformEnum(typ, val); + if (typeof typ === "object") { + return typ.hasOwnProperty("unionMembers") ? transformUnion(typ.unionMembers, val) + : typ.hasOwnProperty("arrayItems") ? transformArray(typ.arrayItems, val) + : typ.hasOwnProperty("props") ? transformObject(getProps(typ), typ.additional, val) + : invalidValue(typ, val, key, parent); + } + // Numbers can be parsed by Date but shouldn't be. + if (typ === Date && typeof val !== "number") return transformDate(val); + return transformPrimitive(typ, val); +} + +function cast(val: any, typ: any): T { + return transform(val, typ, jsonToJSProps); +} + +function uncast(val: T, typ: any): any { + return transform(val, typ, jsToJSONProps); +} + +function l(typ: any) { + return { literal: typ }; +} + +function a(typ: any) { + return { arrayItems: typ }; +} + +function u(...typs: any[]) { + return { unionMembers: typs }; +} + +function o(props: any[], additional: any) { + return { props, additional }; +} + +function m(additional: any) { + return { props: [], additional }; +} + +function r(name: string) { + return { ref: name }; +} + +const typeMap: any = { + "Welcome": o([ + { json: "abilities", js: "abilities", typ: a(r("Ability")) }, + { json: "base_experience", js: "base_experience", typ: 0 }, + { json: "cries", js: "cries", typ: r("Cries") }, + { json: "forms", js: "forms", typ: a(r("Species")) }, + { json: "game_indices", js: "game_indices", typ: a(r("GameIndex")) }, + { json: "height", js: "height", typ: 0 }, + { json: "held_items", js: "held_items", typ: a("any") }, + { json: "id", js: "id", typ: 0 }, + { json: "is_default", js: "is_default", typ: true }, + { json: "location_area_encounters", js: "location_area_encounters", typ: "" }, + { json: "moves", js: "moves", typ: a(r("Move")) }, + { json: "name", js: "name", typ: "" }, + { json: "order", js: "order", typ: 0 }, + { json: "past_abilities", js: "past_abilities", typ: a("any") }, + { json: "past_types", js: "past_types", typ: a("any") }, + { json: "species", js: "species", typ: r("Species") }, + { json: "sprites", js: "sprites", typ: r("Sprites") }, + { json: "stats", js: "stats", typ: a(r("Stat")) }, + { json: "types", js: "types", typ: a(r("Type")) }, + { json: "weight", js: "weight", typ: 0 }, + ], false), + "Ability": o([ + { json: "ability", js: "ability", typ: r("Species") }, + { json: "is_hidden", js: "is_hidden", typ: true }, + { json: "slot", js: "slot", typ: 0 }, + ], false), + "Species": o([ + { json: "name", js: "name", typ: "" }, + { json: "url", js: "url", typ: "" }, + ], false), + "Cries": o([ + { json: "latest", js: "latest", typ: "" }, + { json: "legacy", js: "legacy", typ: "" }, + ], false), + "GameIndex": o([ + { json: "game_index", js: "game_index", typ: 0 }, + { json: "version", js: "version", typ: r("Species") }, + ], false), + "Move": o([ + { json: "move", js: "move", typ: r("Species") }, + { json: "version_group_details", js: "version_group_details", typ: a(r("VersionGroupDetail")) }, + ], false), + "VersionGroupDetail": o([ + { json: "level_learned_at", js: "level_learned_at", typ: 0 }, + { json: "move_learn_method", js: "move_learn_method", typ: r("Species") }, + { json: "version_group", js: "version_group", typ: r("Species") }, + ], false), + "GenerationV": o([ + { json: "black-white", js: "black-white", typ: r("Sprites") }, + ], false), + "GenerationIv": o([ + { json: "diamond-pearl", js: "diamond-pearl", typ: r("Sprites") }, + { json: "heartgold-soulsilver", js: "heartgold-soulsilver", typ: r("Sprites") }, + { json: "platinum", js: "platinum", typ: r("Sprites") }, + ], false), + "Versions": o([ + { json: "generation-i", js: "generation-i", typ: r("GenerationI") }, + { json: "generation-ii", js: "generation-ii", typ: r("GenerationIi") }, + { json: "generation-iii", js: "generation-iii", typ: r("GenerationIii") }, + { json: "generation-iv", js: "generation-iv", typ: r("GenerationIv") }, + { json: "generation-v", js: "generation-v", typ: r("GenerationV") }, + { json: "generation-vi", js: "generation-vi", typ: m(r("Home")) }, + { json: "generation-vii", js: "generation-vii", typ: r("GenerationVii") }, + { json: "generation-viii", js: "generation-viii", typ: r("GenerationViii") }, + ], false), + "Other": o([ + { json: "dream_world", js: "dream_world", typ: r("DreamWorld") }, + { json: "home", js: "home", typ: r("Home") }, + { json: "official-artwork", js: "official-artwork", typ: r("OfficialArtwork") }, + { json: "showdown", js: "showdown", typ: r("Sprites") }, + ], false), + "Sprites": o([ + { json: "back_default", js: "back_default", typ: "" }, + { json: "back_female", js: "back_female", typ: null }, + { json: "back_shiny", js: "back_shiny", typ: "" }, + { json: "back_shiny_female", js: "back_shiny_female", typ: null }, + { json: "front_default", js: "front_default", typ: "" }, + { json: "front_female", js: "front_female", typ: null }, + { json: "front_shiny", js: "front_shiny", typ: "" }, + { json: "front_shiny_female", js: "front_shiny_female", typ: null }, + { json: "other", js: "other", typ: u(undefined, r("Other")) }, + { json: "versions", js: "versions", typ: u(undefined, r("Versions")) }, + { json: "animated", js: "animated", typ: u(undefined, r("Sprites")) }, + ], false), + "GenerationI": o([ + { json: "red-blue", js: "red-blue", typ: r("RedBlue") }, + { json: "yellow", js: "yellow", typ: r("RedBlue") }, + ], false), + "RedBlue": o([ + { json: "back_default", js: "back_default", typ: "" }, + { json: "back_gray", js: "back_gray", typ: "" }, + { json: "back_transparent", js: "back_transparent", typ: "" }, + { json: "front_default", js: "front_default", typ: "" }, + { json: "front_gray", js: "front_gray", typ: "" }, + { json: "front_transparent", js: "front_transparent", typ: "" }, + ], false), + "GenerationIi": o([ + { json: "crystal", js: "crystal", typ: r("Crystal") }, + { json: "gold", js: "gold", typ: r("Gold") }, + { json: "silver", js: "silver", typ: r("Gold") }, + ], false), + "Crystal": o([ + { json: "back_default", js: "back_default", typ: "" }, + { json: "back_shiny", js: "back_shiny", typ: "" }, + { json: "back_shiny_transparent", js: "back_shiny_transparent", typ: "" }, + { json: "back_transparent", js: "back_transparent", typ: "" }, + { json: "front_default", js: "front_default", typ: "" }, + { json: "front_shiny", js: "front_shiny", typ: "" }, + { json: "front_shiny_transparent", js: "front_shiny_transparent", typ: "" }, + { json: "front_transparent", js: "front_transparent", typ: "" }, + ], false), + "Gold": o([ + { json: "back_default", js: "back_default", typ: "" }, + { json: "back_shiny", js: "back_shiny", typ: "" }, + { json: "front_default", js: "front_default", typ: "" }, + { json: "front_shiny", js: "front_shiny", typ: "" }, + { json: "front_transparent", js: "front_transparent", typ: u(undefined, "") }, + ], false), + "GenerationIii": o([ + { json: "emerald", js: "emerald", typ: r("OfficialArtwork") }, + { json: "firered-leafgreen", js: "firered-leafgreen", typ: r("Gold") }, + { json: "ruby-sapphire", js: "ruby-sapphire", typ: r("Gold") }, + ], false), + "OfficialArtwork": o([ + { json: "front_default", js: "front_default", typ: "" }, + { json: "front_shiny", js: "front_shiny", typ: "" }, + ], false), + "Home": o([ + { json: "front_default", js: "front_default", typ: "" }, + { json: "front_female", js: "front_female", typ: null }, + { json: "front_shiny", js: "front_shiny", typ: "" }, + { json: "front_shiny_female", js: "front_shiny_female", typ: null }, + ], false), + "GenerationVii": o([ + { json: "icons", js: "icons", typ: r("DreamWorld") }, + { json: "ultra-sun-ultra-moon", js: "ultra-sun-ultra-moon", typ: r("Home") }, + ], false), + "DreamWorld": o([ + { json: "front_default", js: "front_default", typ: "" }, + { json: "front_female", js: "front_female", typ: null }, + ], false), + "GenerationViii": o([ + { json: "icons", js: "icons", typ: r("DreamWorld") }, + ], false), + "Stat": o([ + { json: "base_stat", js: "base_stat", typ: 0 }, + { json: "effort", js: "effort", typ: 0 }, + { json: "stat", js: "stat", typ: r("Species") }, + ], false), + "Type": o([ + { json: "slot", js: "slot", typ: 0 }, + { json: "type", js: "type", typ: r("Species") }, + ], false), +}; diff --git a/src/models/apiModels/type.ts b/src/models/apiModels/type.ts new file mode 100644 index 0000000..bd9c4d9 --- /dev/null +++ b/src/models/apiModels/type.ts @@ -0,0 +1,245 @@ + +export interface IType { + damage_relations: DamageRelations; + game_indices: GameIndex[]; + generation: Generation; + id: number; + move_damage_class: Generation; + moves: Generation[]; + name: string; + names: Name[]; + past_damage_relations: any[]; + pokemon: Pokemon[]; +} + +export interface DamageRelations { + double_damage_from: Generation[]; + double_damage_to: Generation[]; + half_damage_from: Generation[]; + half_damage_to: Generation[]; + no_damage_from: any[]; + no_damage_to: Generation[]; +} + +export interface Generation { + name: string; + url: string; +} + +export interface GameIndex { + game_index: number; + generation: Generation; +} + +export interface Name { + language: Generation; + name: string; +} + +export interface Pokemon { + pokemon: Generation; + slot: number; +} + +// Converts JSON strings to/from your types +// and asserts the results of JSON.parse at runtime +export class Convert { + public static toWelcome(json: string): Welcome { + return cast(JSON.parse(json), r("Welcome")); + } + + public static welcomeToJson(value: Welcome): string { + return JSON.stringify(uncast(value, r("Welcome")), null, 2); + } +} + +function invalidValue(typ: any, val: any, key: any, parent: any = ''): never { + const prettyTyp = prettyTypeName(typ); + const parentText = parent ? ` on ${parent}` : ''; + const keyText = key ? ` for key "${key}"` : ''; + throw Error(`Invalid value${keyText}${parentText}. Expected ${prettyTyp} but got ${JSON.stringify(val)}`); +} + +function prettyTypeName(typ: any): string { + if (Array.isArray(typ)) { + if (typ.length === 2 && typ[0] === undefined) { + return `an optional ${prettyTypeName(typ[1])}`; + } else { + return `one of [${typ.map(a => { return prettyTypeName(a); }).join(", ")}]`; + } + } else if (typeof typ === "object" && typ.literal !== undefined) { + return typ.literal; + } else { + return typeof typ; + } +} + +function jsonToJSProps(typ: any): any { + if (typ.jsonToJS === undefined) { + const map: any = {}; + typ.props.forEach((p: any) => map[p.json] = { key: p.js, typ: p.typ }); + typ.jsonToJS = map; + } + return typ.jsonToJS; +} + +function jsToJSONProps(typ: any): any { + if (typ.jsToJSON === undefined) { + const map: any = {}; + typ.props.forEach((p: any) => map[p.js] = { key: p.json, typ: p.typ }); + typ.jsToJSON = map; + } + return typ.jsToJSON; +} + +function transform(val: any, typ: any, getProps: any, key: any = '', parent: any = ''): any { + function transformPrimitive(typ: string, val: any): any { + if (typeof typ === typeof val) return val; + return invalidValue(typ, val, key, parent); + } + + function transformUnion(typs: any[], val: any): any { + // val must validate against one typ in typs + const l = typs.length; + for (let i = 0; i < l; i++) { + const typ = typs[i]; + try { + return transform(val, typ, getProps); + } catch (_) {} + } + return invalidValue(typs, val, key, parent); + } + + function transformEnum(cases: string[], val: any): any { + if (cases.indexOf(val) !== -1) return val; + return invalidValue(cases.map(a => { return l(a); }), val, key, parent); + } + + function transformArray(typ: any, val: any): any { + // val must be an array with no invalid elements + if (!Array.isArray(val)) return invalidValue(l("array"), val, key, parent); + return val.map(el => transform(el, typ, getProps)); + } + + function transformDate(val: any): any { + if (val === null) { + return null; + } + const d = new Date(val); + if (isNaN(d.valueOf())) { + return invalidValue(l("Date"), val, key, parent); + } + return d; + } + + function transformObject(props: { [k: string]: any }, additional: any, val: any): any { + if (val === null || typeof val !== "object" || Array.isArray(val)) { + return invalidValue(l(ref || "object"), val, key, parent); + } + const result: any = {}; + Object.getOwnPropertyNames(props).forEach(key => { + const prop = props[key]; + const v = Object.prototype.hasOwnProperty.call(val, key) ? val[key] : undefined; + result[prop.key] = transform(v, prop.typ, getProps, key, ref); + }); + Object.getOwnPropertyNames(val).forEach(key => { + if (!Object.prototype.hasOwnProperty.call(props, key)) { + result[key] = transform(val[key], additional, getProps, key, ref); + } + }); + return result; + } + + if (typ === "any") return val; + if (typ === null) { + if (val === null) return val; + return invalidValue(typ, val, key, parent); + } + if (typ === false) return invalidValue(typ, val, key, parent); + let ref: any = undefined; + while (typeof typ === "object" && typ.ref !== undefined) { + ref = typ.ref; + typ = typeMap[typ.ref]; + } + if (Array.isArray(typ)) return transformEnum(typ, val); + if (typeof typ === "object") { + return typ.hasOwnProperty("unionMembers") ? transformUnion(typ.unionMembers, val) + : typ.hasOwnProperty("arrayItems") ? transformArray(typ.arrayItems, val) + : typ.hasOwnProperty("props") ? transformObject(getProps(typ), typ.additional, val) + : invalidValue(typ, val, key, parent); + } + // Numbers can be parsed by Date but shouldn't be. + if (typ === Date && typeof val !== "number") return transformDate(val); + return transformPrimitive(typ, val); +} + +function cast(val: any, typ: any): T { + return transform(val, typ, jsonToJSProps); +} + +function uncast(val: T, typ: any): any { + return transform(val, typ, jsToJSONProps); +} + +function l(typ: any) { + return { literal: typ }; +} + +function a(typ: any) { + return { arrayItems: typ }; +} + +function u(...typs: any[]) { + return { unionMembers: typs }; +} + +function o(props: any[], additional: any) { + return { props, additional }; +} + +function m(additional: any) { + return { props: [], additional }; +} + +function r(name: string) { + return { ref: name }; +} + +const typeMap: any = { + "Welcome": o([ + { json: "damage_relations", js: "damage_relations", typ: r("DamageRelations") }, + { json: "game_indices", js: "game_indices", typ: a(r("GameIndex")) }, + { json: "generation", js: "generation", typ: r("Generation") }, + { json: "id", js: "id", typ: 0 }, + { json: "move_damage_class", js: "move_damage_class", typ: r("Generation") }, + { json: "moves", js: "moves", typ: a(r("Generation")) }, + { json: "name", js: "name", typ: "" }, + { json: "names", js: "names", typ: a(r("Name")) }, + { json: "past_damage_relations", js: "past_damage_relations", typ: a("any") }, + { json: "pokemon", js: "pokemon", typ: a(r("Pokemon")) }, + ], false), + "DamageRelations": o([ + { json: "double_damage_from", js: "double_damage_from", typ: a(r("Generation")) }, + { json: "double_damage_to", js: "double_damage_to", typ: a(r("Generation")) }, + { json: "half_damage_from", js: "half_damage_from", typ: a(r("Generation")) }, + { json: "half_damage_to", js: "half_damage_to", typ: a(r("Generation")) }, + { json: "no_damage_from", js: "no_damage_from", typ: a("any") }, + { json: "no_damage_to", js: "no_damage_to", typ: a(r("Generation")) }, + ], false), + "Generation": o([ + { json: "name", js: "name", typ: "" }, + { json: "url", js: "url", typ: "" }, + ], false), + "GameIndex": o([ + { json: "game_index", js: "game_index", typ: 0 }, + { json: "generation", js: "generation", typ: r("Generation") }, + ], false), + "Name": o([ + { json: "language", js: "language", typ: r("Generation") }, + { json: "name", js: "name", typ: "" }, + ], false), + "Pokemon": o([ + { json: "pokemon", js: "pokemon", typ: r("Generation") }, + { json: "slot", js: "slot", typ: 0 }, + ], false), +}; diff --git a/src/routes/AuthRoutes.ts b/src/routes/AuthRoutes.ts new file mode 100644 index 0000000..ebc7781 --- /dev/null +++ b/src/routes/AuthRoutes.ts @@ -0,0 +1,24 @@ +import express, { Request, Response } from 'express'; +import * as auth from '../middleware/Auth'; +import * as User from '../services/UserService'; +import bcrypjs from 'bcryptjs'; + +const app = express(); +app.use(express.json()); + + +app.post('/login', async (req, res) => { + const { username } = req.body; + + const user = await User.findUserByUsername(username); + + if (!user) { + return res.status(400).json({ message: 'El usuario no existe' }); + } + + const accessToken = auth.getToken(user); + + res.json({ accessToken }); + }); + + \ No newline at end of file diff --git a/src/routes/PokemonRoutes.ts b/src/routes/PokemonRoutes.ts new file mode 100644 index 0000000..1b72869 --- /dev/null +++ b/src/routes/PokemonRoutes.ts @@ -0,0 +1,97 @@ +// Importar Express y otras dependencias +import express, { Request, Response } from 'express'; +import dotenv from 'dotenv'; +import * as Pokemon from '../services/PokemonService'; +import * as Auth from '../middleware/Auth'; + +dotenv.config(); + +const app = express.Router(); + +// **** Agregar Pokémon ***************************************** +app.post('/add', async (req, res) => { + const { pokemon } = req.body; + try { + const user = await Auth.getUser(req, res); + + const response = await Pokemon.getApiPokemon(pokemon, user.user_id); + + if(!response) { + return res.status(404).json({ message: 'Pokémon no encontrado' }); + } + + const pokemonAdd = await Pokemon.addPokemonToUser(response); + res.json(pokemonAdd); + + } catch (error: any) { + res.status(400).json({ message: error.toString() }); + } +}); + +// **** Obtener Pokémons por usuario ******************************** +app.get('/list', async (req, res) => { + try { + const user = await Auth.getUser(req, res); + const pokemons = await Pokemon.getAllUserPokemons(user.user_id); + res.json(pokemons); + } catch (error) { + res.status(500).json({ message: 'Error al obtener los Pokémon' }); + } +}); + +// **** Agregar Pokémon como favorito ******************************** +app.post('/favorite/', async (req: Request, res: Response) => { + const { pokemon } = req.body; + const user = await Auth.getUser(req, res); + try { + // Verificar si el Pokémon existe y pertenece al usuario + let pokemonExist = await Pokemon.getPokemonById(parseInt(pokemon, 10), user.user_id); + + //Si el pokemon no ha sido atrapado, lo atrapamos y agregamos a favoritos + if (pokemonExist === null) { + + const pokemonApi = await Pokemon.getApiPokemon(pokemon, user.user_id); + if(pokemonApi === null) { + return res.status(404).json({ message: 'Pokémon no encontrado' }); + } + //Agregamos el Pokémon a la colección + pokemonExist = await Pokemon.addPokemonToUser(pokemonApi!); + + } + const addPokemon = await Pokemon.addPokemonToFavorites( + user.user_id, + pokemonExist.user_pokemon_id, + ); + res.json(addPokemon); + + } catch (error) { + res.status(500).json({ message: 'Error al marcar el Pokémon como favorito' }); + } +}); + +// **** Eliminar Pokémon de los atrapados ******************************** +app.delete('/delete/:id', async (req, res) => { + const { id } = req.params; + try { + const user = await Auth.getUser(req, res); + await Pokemon.deletePokemonById(parseInt(id, 10), user.user_id); + + res.json({ message: 'Pokémon eliminado correctamente' }); + } catch (error) { + res.status(500).json({ message: 'Error al eliminar el Pokémon' }); + } +}); + +// **** Eliminar Pokémon de los favoritos ******************************** +app.delete('/favorite/delete/:id', async (req, res) => { + const { id } = req.params; + try { + const user = await Auth.getUser(req, res); + await Pokemon.deleteFavoritePokemonById(parseInt(id, 10), user.user_id); + res.json({ message: 'Pokémon eliminado de Favoritos correctamente' }); + } catch (error) { + res.status(500).json({ message: 'Error al eliminar el Pokémon Favorito' }); + } +}); + +export default app; diff --git a/src/routes/UserRoutes.ts b/src/routes/UserRoutes.ts new file mode 100644 index 0000000..4ea9890 --- /dev/null +++ b/src/routes/UserRoutes.ts @@ -0,0 +1,73 @@ +import express, { Request, Response } from 'express'; +import prisma from '../../prisma/db'; +import jwt from 'jsonwebtoken'; +import dotenv from 'dotenv'; +import * as UserService from '../services/UserService'; +import * as AuthService from '../services/AuthService'; +import * as Auth from '../middleware/Auth'; + +//Models +import {IUser} from '../models/User'; + +dotenv.config(); + +const app = express.Router(); + +app.post('/register', async (req, res) => { + const { username, lastname, email, password } = req.body; + + if(username === undefined || lastname === undefined || email === undefined || password === undefined) + { + return res.status(400).json({ message:"Parámetros faltantes"}); + } + + try { + const user : IUser = { + user_id: 1, + user_name: username, + user_lastname: lastname, + user_email: email, + user_password: password, + }; + + const userAdded = await UserService.registerUser(user); + + res.json(userAdded); + } catch (error) { + res.status(400).json({ message: 'Error al registrar el usuario' }); + } +}); + +app.post('/login', async (req, res) => { + const { username, password } = req.body; + if(username === undefined || password === undefined) { + return res.status(400).json({ message:"Parámetros faltantes"}); + } + + const user = await UserService.findUserByUsername(username); + + const hashedPassword = await AuthService.hashPassword(password); + const isMatch = await AuthService.comparePasswords(password, hashedPassword); + + if (!user || !isMatch) { + return res.status(400).json({ message: 'Credenciales inválidas' }); + } + const accessToken = Auth.getToken(user); + res.json({ accessToken }); +}); + +app.get('/user', async (req, res) => { + try { + const user = await prisma.user.findUnique({ + where: { user_name: req.body.username }, + }); + if (!user) { + return res.status(404).json({ message: 'Usuario no encontrado' }); + } + res.json(user); + } catch (error) { + res.status(500).json({ message: 'Error al obtener información del usuario' }); + } +}); + +export default app; diff --git a/src/services/AuthService.ts b/src/services/AuthService.ts new file mode 100644 index 0000000..bbcc49c --- /dev/null +++ b/src/services/AuthService.ts @@ -0,0 +1,12 @@ +import bcrypt from 'bcryptjs'; + +export const hashPassword = async (password: string): Promise =>{ + const saltRounds = 10; + const hashedPassword = await bcrypt.hash(password, saltRounds); + return hashedPassword; +}; + +export const comparePasswords = async (plainPassword: string, hashedPassword: string): Promise =>{ + const isMatch = await bcrypt.compare(plainPassword, hashedPassword); + return isMatch; +}; diff --git a/src/services/PokemonService.ts b/src/services/PokemonService.ts new file mode 100644 index 0000000..035b156 --- /dev/null +++ b/src/services/PokemonService.ts @@ -0,0 +1,137 @@ +import axios, { AxiosError } from 'axios'; +import { Prisma, PrismaClient } from '@prisma/client'; +import { IPokemon } from '../models/Pokemon'; +import { IPokemon as PokemonModel} from '../models/apiModels/pokemon'; +import { Console } from 'console'; +import { PrismaClientKnownRequestError } from '@prisma/client/runtime/library'; +const prisma = new PrismaClient(); + +// Obtener un Pokémon del Api +export const getApiPokemon = async (pokemon: string, userId: number) => { + try{ + const response = await axios.get(`https://pokeapi.co/api/v2/pokemon/${pokemon}`); + if(response.status !== 200) { + return null; + } + const pokemonResponse = response.data as PokemonModel; + const pokemonModel : IPokemon = { + user_pokemon_id: -1, + pokemon_id: pokemonResponse.id, + pokemon_name: pokemonResponse.name, + user_id: userId + }; + + return pokemonModel; + + } catch (error) { + return null; + } +}; + +// Función para agregar un Pokémon a la colección de un usuario +export const addPokemonToUser = async (pokemon: IPokemon) => { + try { + const pokemonAdd = await prisma.pokemon.create({ + data: { + pokemon_id: pokemon.pokemon_id, + pokemon_name: pokemon.pokemon_name, + user_id: pokemon.user_id + }, + }); + return pokemonAdd; + } catch (error: any) { + if (error.code === "P2002") { + throw new Error('Pókemon duplicado'); + } + else{ + throw new Error('Error al agregar el Pokémon'); + } + } +}; + +// Función para obtener todos los Pokémon de un usuario +export const getAllUserPokemons = async (userId: number) => { + try { + const pokemons = await prisma.pokemon.findMany({ + where: { user_id: userId }, + }); + return pokemons; + } catch (error) { + throw new Error('Error al obtener los Pokémon del usuario'); + } +}; + +// Función para obtener un Pokémon de la colección de un usuario por su ID +export const getPokemonById = async (pokemonId: number, userId: number) => { + try { + const pokemon = await prisma.pokemon.findUnique({ + where: { pokemon_id_user_id: {pokemon_id: pokemonId, user_id: userId}}, + }); + return pokemon; + } catch (error) { + throw new Error('Error al obtener información del Pokémon'); + } +}; + +// Función para agregar un Pokémon a favoritos +export const addPokemonToFavorites = async (userId: number, pokemonId: number) => { + try { + const pokemon = await prisma.favorite.create({ + data: { + pokemon_id: pokemonId, + user_id: userId + }, + }); + return pokemon; + } catch (error) { + throw new Error('Error al agregar el Pokémon'); + } +}; + +// Función para obtener todos los Pokémon Favoritos de un usuario +export const getAllUserFavoritesPokemons = async (userId: number) => { + try { + const pokemons = await prisma.favorite.findMany({ + where: { user_id: userId }, + }); + return pokemons; + } catch (error) { + throw new Error('Error al obtener los Pokémon Favoritos del usuario'); + } +}; + +// Función para obtener un Pokémon Favorito de un usuario por su ID +export const getFavoritePokemonById = async (pokemonId: number, userId: number) => { + try { + const pokemon = await prisma.favorite.findMany({ + where: { pokemon_id: pokemonId, user_id: userId}, + }); + return pokemon; + } catch (error) { + throw new Error('Error al obtener información del Pokémon'); + } +}; + +//Eliminar Pokemon de la colección +export const deletePokemonById = async (PokemonId: number, UserId: number) => { + try { + const deletedPokemon = await prisma.pokemon.deleteMany({ + where: { pokemon_id : PokemonId, user_id : UserId }, + }); + return deletedPokemon; + } catch (error) { + throw new Error('Error al eliminar el Pokémon'); + } + }; + + //Eliminar Pokemon de Favoritos +export const deleteFavoritePokemonById = async (PokemonId: number, UserId: number) => { + try { + const deletedPokemon = await prisma.favorite.deleteMany({ + where: { pokemon_id : PokemonId, user_id : UserId }, + }); + return deletedPokemon; + } catch (error) { + throw new Error('Error al eliminar el Pokémon'); + } +}; \ No newline at end of file diff --git a/src/services/PokemonTypeServcice.ts b/src/services/PokemonTypeServcice.ts new file mode 100644 index 0000000..61f079c --- /dev/null +++ b/src/services/PokemonTypeServcice.ts @@ -0,0 +1,36 @@ +// Importar Prisma para interactuar con la base de datos +import { PrismaClient } from '@prisma/client'; +const prisma = new PrismaClient(); + +export const getAllPokemonTypes = async () => { + try { + const pokemonTypes = await prisma.type.findMany(); + return pokemonTypes; + } catch (error) { + throw new Error('Error al obtener los tipos de Pokémon'); + } +}; + +export const getPokemonTypeById = async (typeId: number) => { + try { + const pokemonType = await prisma.type.findUnique({ + where: { id: typeId }, + }); + return pokemonType; + } catch (error) { + throw new Error('Error al obtener información del tipo de Pokémon'); + } +}; + +export const createPokemonType = async (typeName: string) => { + try { + const pokemonType = await prisma.type.create({ + data: { + name: typeName, + }, + }); + return pokemonType; + } catch (error) { + throw new Error('Error al crear el tipo de Pokémon'); + } +}; \ No newline at end of file diff --git a/src/services/UserService.ts b/src/services/UserService.ts new file mode 100644 index 0000000..ba8bde9 --- /dev/null +++ b/src/services/UserService.ts @@ -0,0 +1,52 @@ +import { PrismaClient } from '@prisma/client'; +const prisma = new PrismaClient(); +import * as Auth from './AuthService'; + +//Models +import { IUser } from '../models/User'; + +export const registerUser = async (userNew: IUser) => { + try { + + const hashedPassword = await Auth.hashPassword(userNew.user_password); + + const user = await prisma.user.create({ + data: { + user_name: userNew.user_name, + user_lastname: userNew.user_lastname, + user_password : hashedPassword, + user_email: userNew.user_email + }, + }); + return user; + } catch (error: any) { + if (error.code === "P2002") { + throw new Error('Usuario duplicado'); + } + else{ + throw new Error('Error al registrar el usuario'); + } + } +}; + +export const findUserByUsername = async (username: string) => { + try { + const user = await prisma.user.findUnique({ + where: { user_name: username }, + }); + return user; + } catch (error) { + throw new Error('Error al buscar el usuario'); + } +}; + +export const getUserByID = async (userId: number) : Promise => { + try { + const user = await prisma.user.findUnique({ + where: { user_id: userId } , + }); + return user; + } catch (error) { + throw new Error('Error al buscar el usuario'); + } +}; \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..f2ec3d1 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,11 @@ + { + "compilerOptions": { + "target": "ES3", + "module": "CommonJS", + "outDir": "./dist", + "strict": true, + "esModuleInterop": true, + }, + "include": ["src/**/*.ts"], + "exclude": ["node_modules"] + } \ No newline at end of file