From 62a3d38612f370a81fafc5cf309fb53c4762bdf3 Mon Sep 17 00:00:00 2001
From: scissorsneedfoodtoo
Date: Wed, 19 Jul 2023 14:29:31 +0900
Subject: [PATCH 01/16] feat: get basic proxy working
---
apps/pokeapi-proxy/README.md | 1 +
apps/pokeapi-proxy/package-lock.json | 708 +++++++++++++++++++++++++++
apps/pokeapi-proxy/package.json | 17 +
apps/pokeapi-proxy/public/index.html | 25 +
apps/pokeapi-proxy/public/style.css | 25 +
apps/pokeapi-proxy/sample.env | 2 +
apps/pokeapi-proxy/server.mjs | 89 ++++
7 files changed, 867 insertions(+)
create mode 100644 apps/pokeapi-proxy/README.md
create mode 100644 apps/pokeapi-proxy/package-lock.json
create mode 100644 apps/pokeapi-proxy/package.json
create mode 100644 apps/pokeapi-proxy/public/index.html
create mode 100644 apps/pokeapi-proxy/public/style.css
create mode 100644 apps/pokeapi-proxy/sample.env
create mode 100644 apps/pokeapi-proxy/server.mjs
diff --git a/apps/pokeapi-proxy/README.md b/apps/pokeapi-proxy/README.md
new file mode 100644
index 000000000..2015aaa69
--- /dev/null
+++ b/apps/pokeapi-proxy/README.md
@@ -0,0 +1 @@
+# PokéAPI Proxy
diff --git a/apps/pokeapi-proxy/package-lock.json b/apps/pokeapi-proxy/package-lock.json
new file mode 100644
index 000000000..69b2d44ce
--- /dev/null
+++ b/apps/pokeapi-proxy/package-lock.json
@@ -0,0 +1,708 @@
+{
+ "name": "pokeapi-proxy",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "pokeapi-proxy",
+ "version": "1.0.0",
+ "license": "MIT",
+ "dependencies": {
+ "axios": "1.4.0",
+ "dotenv": "16.3.1",
+ "express": "4.18.2",
+ "node-cache": "5.1.2"
+ }
+ },
+ "node_modules/accepts": {
+ "version": "1.3.8",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
+ "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
+ "dependencies": {
+ "mime-types": "~2.1.34",
+ "negotiator": "0.6.3"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/array-flatten": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
+ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
+ },
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
+ },
+ "node_modules/axios": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz",
+ "integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==",
+ "dependencies": {
+ "follow-redirects": "^1.15.0",
+ "form-data": "^4.0.0",
+ "proxy-from-env": "^1.1.0"
+ }
+ },
+ "node_modules/body-parser": {
+ "version": "1.20.1",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz",
+ "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==",
+ "dependencies": {
+ "bytes": "3.1.2",
+ "content-type": "~1.0.4",
+ "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.1",
+ "type-is": "~1.6.18",
+ "unpipe": "1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8",
+ "npm": "1.2.8000 || >= 1.4.16"
+ }
+ },
+ "node_modules/bytes": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
+ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/call-bind": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
+ "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
+ "dependencies": {
+ "function-bind": "^1.1.1",
+ "get-intrinsic": "^1.0.2"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/clone": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
+ "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==",
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "node_modules/combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "dependencies": {
+ "delayed-stream": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/content-disposition": {
+ "version": "0.5.4",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
+ "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
+ "dependencies": {
+ "safe-buffer": "5.2.1"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/content-type": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
+ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
+ "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie-signature": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
+ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
+ },
+ "node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/depd": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+ "engines": {
+ "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==",
+ "engines": {
+ "node": ">= 0.8",
+ "npm": "1.2.8000 || >= 1.4.16"
+ }
+ },
+ "node_modules/dotenv": {
+ "version": "16.3.1",
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz",
+ "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/motdotla/dotenv?sponsor=1"
+ }
+ },
+ "node_modules/ee-first": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
+ },
+ "node_modules/encodeurl": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
+ "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/escape-html": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
+ },
+ "node_modules/etag": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/express": {
+ "version": "4.18.2",
+ "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz",
+ "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==",
+ "dependencies": {
+ "accepts": "~1.3.8",
+ "array-flatten": "1.1.1",
+ "body-parser": "1.20.1",
+ "content-disposition": "0.5.4",
+ "content-type": "~1.0.4",
+ "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"
+ },
+ "engines": {
+ "node": ">= 0.10.0"
+ }
+ },
+ "node_modules/finalhandler": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
+ "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==",
+ "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"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/follow-redirects": {
+ "version": "1.15.2",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
+ "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/RubenVerborgh"
+ }
+ ],
+ "engines": {
+ "node": ">=4.0"
+ },
+ "peerDependenciesMeta": {
+ "debug": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/form-data": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
+ "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/forwarded": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
+ "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/fresh": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
+ "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
+ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
+ },
+ "node_modules/get-intrinsic": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz",
+ "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==",
+ "dependencies": {
+ "function-bind": "^1.1.1",
+ "has": "^1.0.3",
+ "has-proto": "^1.0.1",
+ "has-symbols": "^1.0.3"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
+ "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
+ "dependencies": {
+ "function-bind": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4.0"
+ }
+ },
+ "node_modules/has-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz",
+ "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-symbols": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
+ "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/http-errors": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
+ "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
+ "dependencies": {
+ "depd": "2.0.0",
+ "inherits": "2.0.4",
+ "setprototypeof": "1.2.0",
+ "statuses": "2.0.1",
+ "toidentifier": "1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/iconv-lite": {
+ "version": "0.4.24",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+ "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
+ },
+ "node_modules/ipaddr.js": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
+ "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "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==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/merge-descriptors": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
+ "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w=="
+ },
+ "node_modules/methods": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
+ "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
+ "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
+ "bin": {
+ "mime": "cli.js"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "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==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "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==",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
+ },
+ "node_modules/negotiator": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
+ "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/node-cache": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/node-cache/-/node-cache-5.1.2.tgz",
+ "integrity": "sha512-t1QzWwnk4sjLWaQAS8CHgOJ+RAfmHpxFWmc36IWTiWHQfs0w5JDMBS1b1ZxQteo0vVVuWJvIUKHDkkeK7vIGCg==",
+ "dependencies": {
+ "clone": "2.x"
+ },
+ "engines": {
+ "node": ">= 8.0.0"
+ }
+ },
+ "node_modules/object-inspect": {
+ "version": "1.12.3",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz",
+ "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/on-finished": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
+ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+ "dependencies": {
+ "ee-first": "1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/parseurl": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/path-to-regexp": {
+ "version": "0.1.7",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
+ "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ=="
+ },
+ "node_modules/proxy-addr": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
+ "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
+ "dependencies": {
+ "forwarded": "0.2.0",
+ "ipaddr.js": "1.9.1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/proxy-from-env": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
+ },
+ "node_modules/qs": {
+ "version": "6.11.0",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
+ "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
+ "dependencies": {
+ "side-channel": "^1.0.4"
+ },
+ "engines": {
+ "node": ">=0.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/range-parser": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/raw-body": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz",
+ "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==",
+ "dependencies": {
+ "bytes": "3.1.2",
+ "http-errors": "2.0.0",
+ "iconv-lite": "0.4.24",
+ "unpipe": "1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
+ },
+ "node_modules/send": {
+ "version": "0.18.0",
+ "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
+ "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==",
+ "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"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/send/node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
+ },
+ "node_modules/serve-static": {
+ "version": "1.15.0",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz",
+ "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==",
+ "dependencies": {
+ "encodeurl": "~1.0.2",
+ "escape-html": "~1.0.3",
+ "parseurl": "~1.3.3",
+ "send": "0.18.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/setprototypeof": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
+ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
+ },
+ "node_modules/side-channel": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
+ "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
+ "dependencies": {
+ "call-bind": "^1.0.0",
+ "get-intrinsic": "^1.0.2",
+ "object-inspect": "^1.9.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/statuses": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
+ "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/toidentifier": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
+ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
+ "engines": {
+ "node": ">=0.6"
+ }
+ },
+ "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==",
+ "dependencies": {
+ "media-typer": "0.3.0",
+ "mime-types": "~2.1.24"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/unpipe": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+ "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/utils-merge": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
+ "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
+ "engines": {
+ "node": ">= 0.4.0"
+ }
+ },
+ "node_modules/vary": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ }
+ }
+}
diff --git a/apps/pokeapi-proxy/package.json b/apps/pokeapi-proxy/package.json
new file mode 100644
index 000000000..634f89034
--- /dev/null
+++ b/apps/pokeapi-proxy/package.json
@@ -0,0 +1,17 @@
+{
+ "name": "pokeapi-proxy",
+ "version": "1.0.0",
+ "description": "PokéAPI Proxy With Cache",
+ "main": "server.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1",
+ "start": "node server.mjs"
+ },
+ "license": "MIT",
+ "dependencies": {
+ "axios": "1.4.0",
+ "dotenv": "16.3.1",
+ "express": "4.18.2",
+ "node-cache": "5.1.2"
+ }
+}
diff --git a/apps/pokeapi-proxy/public/index.html b/apps/pokeapi-proxy/public/index.html
new file mode 100644
index 000000000..c11462556
--- /dev/null
+++ b/apps/pokeapi-proxy/public/index.html
@@ -0,0 +1,25 @@
+
+
+
+ Stock Price Checker Proxy
+
+
+
+
+
+
+
+
+
+
+ Stock Price Checker Proxy
+
+
+
+ Usage:
+ GET https://stock-price-checker-proxy.freecodecamp.rocks/v1/stock/[symbol]/quote
+ Where:
+ symbol = msft | goog | aapl | ...
+
+
+
diff --git a/apps/pokeapi-proxy/public/style.css b/apps/pokeapi-proxy/public/style.css
new file mode 100644
index 000000000..30d4eb288
--- /dev/null
+++ b/apps/pokeapi-proxy/public/style.css
@@ -0,0 +1,25 @@
+/* styles */
+/* called by your view template */
+
+* {
+ box-sizing: border-box;
+}
+
+body {
+ font-family: "Benton Sans", "Helvetica Neue", helvetica, arial, sans-serif;
+ margin: 2em;
+}
+
+h1 {
+ font-style: italic;
+ color: #373fff;
+}
+
+.bold {
+ font-weight: bold;
+}
+
+p {
+ max-width: 600px;
+}
+
diff --git a/apps/pokeapi-proxy/sample.env b/apps/pokeapi-proxy/sample.env
new file mode 100644
index 000000000..f13bc7d7a
--- /dev/null
+++ b/apps/pokeapi-proxy/sample.env
@@ -0,0 +1,2 @@
+PORT=3000
+CACHE_TTL_MINUTES=
diff --git a/apps/pokeapi-proxy/server.mjs b/apps/pokeapi-proxy/server.mjs
new file mode 100644
index 000000000..41fad0e8c
--- /dev/null
+++ b/apps/pokeapi-proxy/server.mjs
@@ -0,0 +1,89 @@
+import 'dotenv/config';
+import express from 'express';
+import axios from 'axios';
+import NodeCache from 'node-cache';
+const portNum = process.env.PORT || 3000;
+const app = express();
+// const cache = new NodeCache({ stdTTL: process.env.CACHE_TTL_MINUTES * 60, checkperiod: 120 });
+const cache = new NodeCache({ stdTTL: 30, checkperiod: 120 }); // testing cache
+
+app.use(express.static('public'));
+
+const checkCache = async (req, res, next) => {
+ const { pokemonIdOrName } = req.params;
+
+ try {
+ const cachedPokemonData = cache.get(pokemonIdOrName);
+
+ if (cachedPokemonData) {
+ console.log('Serving cached data');
+ return res.send(cachedPokemonData);
+ }
+
+ next();
+ } catch (err) {
+ next(err);
+ }
+};
+
+const getPokemonData = async (req, res, next) => {
+ try {
+ console.log('Fetching new data');
+ const { pokemonIdOrName } = req.params;
+ const pokeAPIResponse = await axios.get(
+ `https://pokeapi.co/api/v2/pokemon/${pokemonIdOrName}`
+ );
+ const { data } = pokeAPIResponse;
+ const { base_experience, height, id, name, sprites, stats, types, weight } =
+ data;
+
+ // Remove unnecessary data for the required project
+ const simplifiedPokemonData = {
+ base_experience,
+ height,
+ id,
+ name,
+ sprites: Object.keys(sprites)
+ .filter(key => typeof sprites[key] === 'string')
+ .reduce((obj, key) => {
+ obj[key] = sprites[key].replace(
+ 'https://raw.githubusercontent.com/PokeAPI/sprites/master/',
+ 'https://cdn.freecodecamp.org/pokeapi/'
+ );
+ return obj;
+ }, {}),
+ stats,
+ types,
+ weight
+ };
+
+ // Cache simplified data and send it as a response
+ cache.set(pokemonIdOrName, simplifiedPokemonData);
+ res.send(simplifiedPokemonData);
+ } catch (err) {
+ next(err);
+ }
+};
+
+app.get('/', (req, res) => {
+ res.sendFile(__dirname + '/views/index.html');
+});
+
+app.get('/api/pokemon/:pokemonIdOrName', checkCache, getPokemonData);
+
+// eslint-disable-next-line no-unused-vars
+app.use((err, req, res, next) => {
+ console.log(err.message, err.stack);
+
+ // Handle Axios errors first and mimic the response from the PokéAPI
+ if (err?.response?.status === 404) {
+ res.status(404).send('Not Found');
+ } else {
+ // Handle other errors
+ res.status(500).send('Internal server error');
+ }
+});
+
+app.listen(portNum, () => {
+ console.log(`Listening on port ${portNum}`);
+});
From 6162acacadd1d503b57dceabc85fcd2d69059243 Mon Sep 17 00:00:00 2001
From: scissorsneedfoodtoo
Date: Wed, 19 Jul 2023 19:12:15 +0900
Subject: [PATCH 02/16] fix: refactor project to something closer to domain
driven design
---
.../api/pokemon/pokemon.handlers.mjs | 53 ++++++++++++
.../api/pokemon/pokemon.middleware.mjs | 18 ++++
.../api/pokemon/pokemon.routes.mjs | 8 ++
apps/pokeapi-proxy/api/utils/cache.mjs | 9 ++
.../api/utils/fetch-from-pokeapi.mjs | 6 ++
apps/pokeapi-proxy/server.mjs | 83 +++----------------
6 files changed, 107 insertions(+), 70 deletions(-)
create mode 100644 apps/pokeapi-proxy/api/pokemon/pokemon.handlers.mjs
create mode 100644 apps/pokeapi-proxy/api/pokemon/pokemon.middleware.mjs
create mode 100644 apps/pokeapi-proxy/api/pokemon/pokemon.routes.mjs
create mode 100644 apps/pokeapi-proxy/api/utils/cache.mjs
create mode 100644 apps/pokeapi-proxy/api/utils/fetch-from-pokeapi.mjs
diff --git a/apps/pokeapi-proxy/api/pokemon/pokemon.handlers.mjs b/apps/pokeapi-proxy/api/pokemon/pokemon.handlers.mjs
new file mode 100644
index 000000000..ce8ced65f
--- /dev/null
+++ b/apps/pokeapi-proxy/api/pokemon/pokemon.handlers.mjs
@@ -0,0 +1,53 @@
+import { fetchFromPokeAPI } from '../utils/fetch-from-pokeapi.mjs';
+import { setCache } from '../utils/cache.mjs';
+
+export const getPokemonData = async (req, res, next) => {
+ try {
+ const { pokemonIdOrName } = req.params;
+ const { data } = await fetchFromPokeAPI(
+ `https://pokeapi.co/api/v2/pokemon/${pokemonIdOrName}`
+ );
+ const {
+ base_experience,
+ height,
+ id,
+ name,
+ order,
+ sprites,
+ stats,
+ types,
+ weight
+ } = data;
+
+ // Remove unnecessary data for the required project
+ const simplifiedPokemonData = {
+ base_experience,
+ height,
+ id,
+ name,
+ order,
+ sprites: Object.keys(sprites)
+ .filter(key => typeof sprites[key] === 'string')
+ .reduce((obj, key) => {
+ obj[key] = sprites[key].replace(
+ 'https://raw.githubusercontent.com/PokeAPI/sprites/master/',
+ 'https://cdn.freecodecamp.org/pokeapi/'
+ );
+ return obj;
+ }, {}),
+ stats,
+ types,
+ weight
+ };
+
+ // Cache simplified data and send it as a response
+ setCache(pokemonIdOrName, simplifiedPokemonData);
+ res.send(simplifiedPokemonData);
+ } catch (err) {
+ // Attempt to set the status code and message from the Axios response object
+ next({
+ statusCode: err?.response?.status,
+ message: err?.response?.data
+ });
+ }
+};
diff --git a/apps/pokeapi-proxy/api/pokemon/pokemon.middleware.mjs b/apps/pokeapi-proxy/api/pokemon/pokemon.middleware.mjs
new file mode 100644
index 000000000..615d478d7
--- /dev/null
+++ b/apps/pokeapi-proxy/api/pokemon/pokemon.middleware.mjs
@@ -0,0 +1,18 @@
+import { getCache } from '../utils/cache.mjs';
+
+export const checkCache = async (req, res, next) => {
+ const { pokemonIdOrName } = req.params;
+
+ try {
+ const cachedPokemonData = getCache(pokemonIdOrName);
+
+ if (cachedPokemonData) {
+ console.log('Serving cached data');
+ return res.send(cachedPokemonData);
+ }
+
+ next();
+ } catch (err) {
+ next(err);
+ }
+};
diff --git a/apps/pokeapi-proxy/api/pokemon/pokemon.routes.mjs b/apps/pokeapi-proxy/api/pokemon/pokemon.routes.mjs
new file mode 100644
index 000000000..c430abd83
--- /dev/null
+++ b/apps/pokeapi-proxy/api/pokemon/pokemon.routes.mjs
@@ -0,0 +1,8 @@
+import { getPokemonData } from './pokemon.handlers.mjs';
+import { checkCache } from './pokemon.middleware.mjs';
+import express from 'express';
+const router = express.Router();
+
+router.get('/pokemon/:pokemonIdOrName', checkCache, getPokemonData);
+
+export { router };
diff --git a/apps/pokeapi-proxy/api/utils/cache.mjs b/apps/pokeapi-proxy/api/utils/cache.mjs
new file mode 100644
index 000000000..55e378abf
--- /dev/null
+++ b/apps/pokeapi-proxy/api/utils/cache.mjs
@@ -0,0 +1,9 @@
+import NodeCache from 'node-cache';
+const cache = new NodeCache({
+ stdTTL: process.env.CACHE_TTL_MINUTES * 60,
+ checkperiod: 120
+});
+
+export const getCache = key => cache.get(key);
+
+export const setCache = (key, data) => cache.set(key, data);
diff --git a/apps/pokeapi-proxy/api/utils/fetch-from-pokeapi.mjs b/apps/pokeapi-proxy/api/utils/fetch-from-pokeapi.mjs
new file mode 100644
index 000000000..1f3a99cf5
--- /dev/null
+++ b/apps/pokeapi-proxy/api/utils/fetch-from-pokeapi.mjs
@@ -0,0 +1,6 @@
+import axios from 'axios';
+
+export const fetchFromPokeAPI = async url => {
+ console.log('Fetching from PokéAPI');
+ return await axios.get(url);
+};
diff --git a/apps/pokeapi-proxy/server.mjs b/apps/pokeapi-proxy/server.mjs
index 41fad0e8c..2fff20383 100644
--- a/apps/pokeapi-proxy/server.mjs
+++ b/apps/pokeapi-proxy/server.mjs
@@ -1,89 +1,32 @@
import 'dotenv/config';
import express from 'express';
-import axios from 'axios';
-import NodeCache from 'node-cache';
+import { router as pokemonRouter } from './api/pokemon/pokemon.routes.mjs';
const portNum = process.env.PORT || 3000;
const app = express();
-// const cache = new NodeCache({ stdTTL: process.env.CACHE_TTL_MINUTES * 60, checkperiod: 120 });
-const cache = new NodeCache({ stdTTL: 30, checkperiod: 120 }); // testing cache
app.use(express.static('public'));
-const checkCache = async (req, res, next) => {
- const { pokemonIdOrName } = req.params;
-
- try {
- const cachedPokemonData = cache.get(pokemonIdOrName);
-
- if (cachedPokemonData) {
- console.log('Serving cached data');
- return res.send(cachedPokemonData);
- }
-
- next();
- } catch (err) {
- next(err);
- }
-};
-
-const getPokemonData = async (req, res, next) => {
- try {
- console.log('Fetching new data');
- const { pokemonIdOrName } = req.params;
- const pokeAPIResponse = await axios.get(
- `https://pokeapi.co/api/v2/pokemon/${pokemonIdOrName}`
- );
- const { data } = pokeAPIResponse;
- const { base_experience, height, id, name, sprites, stats, types, weight } =
- data;
-
- // Remove unnecessary data for the required project
- const simplifiedPokemonData = {
- base_experience,
- height,
- id,
- name,
- sprites: Object.keys(sprites)
- .filter(key => typeof sprites[key] === 'string')
- .reduce((obj, key) => {
- obj[key] = sprites[key].replace(
- 'https://raw.githubusercontent.com/PokeAPI/sprites/master/',
- 'https://cdn.freecodecamp.org/pokeapi/'
- );
- return obj;
- }, {}),
- stats,
- types,
- weight
- };
-
- // Cache simplified data and send it as a response
- cache.set(pokemonIdOrName, simplifiedPokemonData);
- res.send(simplifiedPokemonData);
- } catch (err) {
- next(err);
- }
-};
-
app.get('/', (req, res) => {
res.sendFile(__dirname + '/views/index.html');
});
-app.get('/api/pokemon/:pokemonIdOrName', checkCache, getPokemonData);
+app.use('/api', pokemonRouter);
+
+// Invalid path error handler
+app.use((req, res, next) => {
+ res.status(404).send('Invalid path');
+});
-// eslint-disable-next-line no-unused-vars
+// Global error handler
app.use((err, req, res, next) => {
- console.log(err.message, err.stack);
+ err.statusCode = err.statusCode || 500;
+ err.message = err.message || 'Internal server error';
- // Handle Axios errors first and mimic the response from the PokéAPI
- if (err?.response?.status === 404) {
- res.status(404).send('Not Found');
- } else {
- // Handle other errors
- res.status(500).send('Internal server error');
- }
+ console.error(err.statusCode, err.message);
+ res.status(err.statusCode).send(err.message);
});
+// Listen for requests
app.listen(portNum, () => {
console.log(`Listening on port ${portNum}`);
});
From be14527e4944e385735005a3521dc6f6141c3603 Mon Sep 17 00:00:00 2001
From: scissorsneedfoodtoo
Date: Wed, 19 Jul 2023 19:14:38 +0900
Subject: [PATCH 03/16] fix: set default CACHE_TTL_MINUTES in sample.env
---
apps/pokeapi-proxy/sample.env | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/apps/pokeapi-proxy/sample.env b/apps/pokeapi-proxy/sample.env
index f13bc7d7a..28d886113 100644
--- a/apps/pokeapi-proxy/sample.env
+++ b/apps/pokeapi-proxy/sample.env
@@ -1,2 +1,2 @@
PORT=3000
-CACHE_TTL_MINUTES=
+CACHE_TTL_MINUTES=30
From f2287a5382858d9d079615f6dca8cfca7c0be685 Mon Sep 17 00:00:00 2001
From: scissorsneedfoodtoo
Date: Thu, 20 Jul 2023 17:32:53 +0900
Subject: [PATCH 04/16] feat: add content and styling for landing proxy landing
page
---
apps/pokeapi-proxy/public/index.html | 112 +++++++++++++++++++++++----
apps/pokeapi-proxy/public/style.css | 66 +++++++++++++---
2 files changed, 151 insertions(+), 27 deletions(-)
diff --git a/apps/pokeapi-proxy/public/index.html b/apps/pokeapi-proxy/public/index.html
index c11462556..4ecccc465 100644
--- a/apps/pokeapi-proxy/public/index.html
+++ b/apps/pokeapi-proxy/public/index.html
@@ -1,25 +1,107 @@
- Stock Price Checker Proxy
-
-
-
-
-
+
+
+ PokéAPI Proxy
+
+
-
-
- Stock Price Checker Proxy
-
-
- Usage:
- GET https://stock-price-checker-proxy.freecodecamp.rocks/v1/stock/[symbol]/quote
- Where:
- symbol = msft | goog | aapl | ...
+ PokéAPI Proxy
+
+
Usage
+
+ Use the endpoint
+ https://pokeapi-proxy.freecodecamp.rocks/api/pokemon/{name-or-id}
+ to get data for a Pokémon, where {name-or-id} is the
+ Pokémon's name or its id number in the
+ official Pokédex .
+
+
+ Note that the name should be lowercase, have special characters
+ removed, and dash separated.
+
+
Example Requests
+
Click any of the example requests below to see its response.
+
Pikachu:
+
+
Nidoran♀ (Female):
+
+
Mr. Mime:
+
+
diff --git a/apps/pokeapi-proxy/public/style.css b/apps/pokeapi-proxy/public/style.css
index 30d4eb288..efb11a8c9 100644
--- a/apps/pokeapi-proxy/public/style.css
+++ b/apps/pokeapi-proxy/public/style.css
@@ -1,25 +1,67 @@
-/* styles */
-/* called by your view template */
-
-* {
+html {
box-sizing: border-box;
+ font-size: 16px;
+}
+
+*,
+*:before,
+*:after {
+ box-sizing: inherit;
}
body {
- font-family: "Benton Sans", "Helvetica Neue", helvetica, arial, sans-serif;
- margin: 2em;
+ font-family: Lato, sans-serif;
+ background-color: #0a0a23;
}
-h1 {
- font-style: italic;
- color: #373fff;
+main {
+ margin: auto;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
}
-.bold {
- font-weight: bold;
+h1,
+h2,
+h3,
+p,
+code,
+pre,
+ul {
+ color: #fff;
+}
+
+.usage {
+ padding: 15px;
+ background-color: #0a0a23;
+ max-width: 95%;
+ border: 1px solid #fff;
}
p {
- max-width: 600px;
+ margin-bottom: 1em;
+ line-height: 1.5rem;
+}
+
+code {
+ padding: 1px 4px;
+ overflow-wrap: anywhere;
+ background-color: #3b3b4f;
+ color: #dfdfe2;
+}
+
+ul {
+ margin: 1em;
+ padding-left: 20px;
}
+li {
+ margin-bottom: 0.5em;
+}
+
+@media (min-width: 768px) {
+ .usage {
+ max-width: 70%;
+ }
+}
From 8d14f4e48739d2475f74e9667b1dac0ab95e02bf Mon Sep 17 00:00:00 2001
From: scissorsneedfoodtoo
Date: Fri, 21 Jul 2023 11:04:25 +0900
Subject: [PATCH 05/16] =?UTF-8?q?feat:=20dockerize=20Pok=C3=A9API=20proxy?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
apps/pokeapi-proxy/.dockerignore | 6 ++++++
docker-compose.yml | 13 +++++++++++++
port-map.json | 1 +
sample.env | 3 +++
4 files changed, 23 insertions(+)
create mode 100644 apps/pokeapi-proxy/.dockerignore
diff --git a/apps/pokeapi-proxy/.dockerignore b/apps/pokeapi-proxy/.dockerignore
new file mode 100644
index 000000000..6d6356b88
--- /dev/null
+++ b/apps/pokeapi-proxy/.dockerignore
@@ -0,0 +1,6 @@
+.env
+.git
+.gitignore
+.dockerignore
+node_modules
+Dockerfile
diff --git a/docker-compose.yml b/docker-compose.yml
index b72027b0f..b8d772180 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -316,6 +316,19 @@ services:
ports:
- 50260:3000
+ pokeapi-proxy:
+ container_name: pokeapi-proxy
+ restart: unless-stopped
+ networks:
+ - proxy
+ build:
+ context: ./apps/pokeapi-proxy
+ dockerfile: ./Dockerfile
+ environment:
+ - CACHE_TTL_HOURS=${POKEAPI_PROXY_CACHE_TTL_HOURS}
+ ports:
+ - 50400:3000
+
pokemon-search-app:
container_name: pokemon-search-app
restart: unless-stopped
diff --git a/port-map.json b/port-map.json
index 508cea374..b94b3dc81 100644
--- a/port-map.json
+++ b/port-map.json
@@ -22,6 +22,7 @@
"palindrome-checker": 50300,
"personal-library": 50210,
"personal-portfolio": 50260,
+ "pokeapi-proxy": 50400,
"pokemon-search-app": 50340,
"product-landing-page": 50240,
"random-quote-machine": 50105,
diff --git a/sample.env b/sample.env
index fd32b1466..017fc4e42 100644
--- a/sample.env
+++ b/sample.env
@@ -37,6 +37,9 @@ MANAGE_A_BOOK_TRADING_CLUB_APP_URL=http://localhost:3000
# Personal Library
PERSONAL_LIBRARY_DB=mongodb://mongo:27017/personal-library
+# PokéAPI Proxy
+POKEAPI_PROXY_CACHE_TTL_MINUTES=30
+
# Stock Price Checker
STOCK_PRICE_CHECKER_DB_URI=mongodb://mongo:27017/stock-price-checker
From b6f410b12163e981cbd42c21a6700ad557eb225f Mon Sep 17 00:00:00 2001
From: scissorsneedfoodtoo
Date: Tue, 25 Jul 2023 13:57:28 +0900
Subject: [PATCH 06/16] fix: use hours to set ttl for caching, update sample
env vars
---
apps/pokeapi-proxy/api/utils/cache.mjs | 2 +-
apps/pokeapi-proxy/sample.env | 2 +-
sample.env | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/apps/pokeapi-proxy/api/utils/cache.mjs b/apps/pokeapi-proxy/api/utils/cache.mjs
index 55e378abf..c16b68706 100644
--- a/apps/pokeapi-proxy/api/utils/cache.mjs
+++ b/apps/pokeapi-proxy/api/utils/cache.mjs
@@ -1,6 +1,6 @@
import NodeCache from 'node-cache';
const cache = new NodeCache({
- stdTTL: process.env.CACHE_TTL_MINUTES * 60,
+ stdTTL: process.env.CACHE_TTL_HOURS * 3600, // Convert hours to seconds
checkperiod: 120
});
diff --git a/apps/pokeapi-proxy/sample.env b/apps/pokeapi-proxy/sample.env
index 28d886113..019ede173 100644
--- a/apps/pokeapi-proxy/sample.env
+++ b/apps/pokeapi-proxy/sample.env
@@ -1,2 +1,2 @@
PORT=3000
-CACHE_TTL_MINUTES=30
+CACHE_TTL_HOURS=12
diff --git a/sample.env b/sample.env
index 017fc4e42..eddc0e712 100644
--- a/sample.env
+++ b/sample.env
@@ -38,7 +38,7 @@ MANAGE_A_BOOK_TRADING_CLUB_APP_URL=http://localhost:3000
PERSONAL_LIBRARY_DB=mongodb://mongo:27017/personal-library
# PokéAPI Proxy
-POKEAPI_PROXY_CACHE_TTL_MINUTES=30
+POKEAPI_PROXY_CACHE_TTL_HOURS=12
# Stock Price Checker
STOCK_PRICE_CHECKER_DB_URI=mongodb://mongo:27017/stock-price-checker
From dc4a4701abd6161be2580e00b915e8aaa2645052 Mon Sep 17 00:00:00 2001
From: scissorsneedfoodtoo
Date: Tue, 25 Jul 2023 16:42:38 +0900
Subject: [PATCH 07/16] =?UTF-8?q?feat:=20add=20note=20to=20landing=20page?=
=?UTF-8?q?=20about=20the=20format=20for=20pok=C3=A9mon=20with=20sex=20sym?=
=?UTF-8?q?bols=20as=20part=20of=20their=20name?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
apps/pokeapi-proxy/public/index.html | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/apps/pokeapi-proxy/public/index.html b/apps/pokeapi-proxy/public/index.html
index 4ecccc465..96bd7373c 100644
--- a/apps/pokeapi-proxy/public/index.html
+++ b/apps/pokeapi-proxy/public/index.html
@@ -31,7 +31,9 @@ Usage
Note that the name should be lowercase, have special characters
- removed, and dash separated.
+ removed, and dash separated. Also, if the Pokémon has either ♀ or ♀ as
+ part of its name, the format is {name-f} or
+ {name-m}, respectively.
Example Requests
Click any of the example requests below to see its response.
@@ -55,7 +57,7 @@ Pikachu:
- Nidoran♀ (Female):
+ Nidoran♀:
Date: Tue, 25 Jul 2023 18:34:15 +0900
Subject: [PATCH 08/16] =?UTF-8?q?feat:=20add=20middleware=20and=20utility?=
=?UTF-8?q?=20function=20cache=20and=20validate=20all=20pok=C3=A9mon=20nam?=
=?UTF-8?q?es=20and=20ids=20served=20by=20Pok=C3=A9API?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../api/pokemon/pokemon.handlers.mjs | 2 +-
.../api/pokemon/pokemon.middleware.mjs | 24 +++++++++++++--
.../api/pokemon/pokemon.routes.mjs | 4 +--
.../api/utils/is-valid-name-or-id.mjs | 30 +++++++++++++++++++
4 files changed, 55 insertions(+), 5 deletions(-)
create mode 100644 apps/pokeapi-proxy/api/utils/is-valid-name-or-id.mjs
diff --git a/apps/pokeapi-proxy/api/pokemon/pokemon.handlers.mjs b/apps/pokeapi-proxy/api/pokemon/pokemon.handlers.mjs
index ce8ced65f..aaaf87f2a 100644
--- a/apps/pokeapi-proxy/api/pokemon/pokemon.handlers.mjs
+++ b/apps/pokeapi-proxy/api/pokemon/pokemon.handlers.mjs
@@ -44,7 +44,7 @@ export const getPokemonData = async (req, res, next) => {
setCache(pokemonIdOrName, simplifiedPokemonData);
res.send(simplifiedPokemonData);
} catch (err) {
- // Attempt to set the status code and message from the Axios response object
+ // Set status code and message from the Axios response object
next({
statusCode: err?.response?.status,
message: err?.response?.data
diff --git a/apps/pokeapi-proxy/api/pokemon/pokemon.middleware.mjs b/apps/pokeapi-proxy/api/pokemon/pokemon.middleware.mjs
index 615d478d7..27cdf37d1 100644
--- a/apps/pokeapi-proxy/api/pokemon/pokemon.middleware.mjs
+++ b/apps/pokeapi-proxy/api/pokemon/pokemon.middleware.mjs
@@ -1,13 +1,14 @@
import { getCache } from '../utils/cache.mjs';
+import { isValidNameOrId } from '../utils/is-valid-name-or-id.mjs';
-export const checkCache = async (req, res, next) => {
+export const checkCacheForPokemonData = (req, res, next) => {
const { pokemonIdOrName } = req.params;
try {
const cachedPokemonData = getCache(pokemonIdOrName);
if (cachedPokemonData) {
- console.log('Serving cached data');
+ console.log('Serving cached Pokémon data');
return res.send(cachedPokemonData);
}
@@ -16,3 +17,22 @@ export const checkCache = async (req, res, next) => {
next(err);
}
};
+
+export const validateNameOrId = async (req, res, next) => {
+ const { pokemonIdOrName } = req.params;
+ const isValidPath = await isValidNameOrId(pokemonIdOrName);
+
+ try {
+ if (isValidPath) {
+ console.log('Is a valid Pokémon name or id');
+ return next();
+ }
+
+ throw new Error();
+ } catch (err) {
+ next({
+ statusCode: 404,
+ message: 'Not Found: Is not a valid Pokémon name or id'
+ });
+ }
+};
diff --git a/apps/pokeapi-proxy/api/pokemon/pokemon.routes.mjs b/apps/pokeapi-proxy/api/pokemon/pokemon.routes.mjs
index c430abd83..66e3fb9d1 100644
--- a/apps/pokeapi-proxy/api/pokemon/pokemon.routes.mjs
+++ b/apps/pokeapi-proxy/api/pokemon/pokemon.routes.mjs
@@ -1,8 +1,8 @@
import { getPokemonData } from './pokemon.handlers.mjs';
-import { checkCache } from './pokemon.middleware.mjs';
+import { checkCacheForPokemonData, validateNameOrId } from './pokemon.middleware.mjs';
import express from 'express';
const router = express.Router();
-router.get('/pokemon/:pokemonIdOrName', checkCache, getPokemonData);
+router.get('/pokemon/:pokemonIdOrName', checkCacheForPokemonData, validateNameOrId, getPokemonData);
export { router };
diff --git a/apps/pokeapi-proxy/api/utils/is-valid-name-or-id.mjs b/apps/pokeapi-proxy/api/utils/is-valid-name-or-id.mjs
new file mode 100644
index 000000000..fb48d1810
--- /dev/null
+++ b/apps/pokeapi-proxy/api/utils/is-valid-name-or-id.mjs
@@ -0,0 +1,30 @@
+import { fetchFromPokeAPI } from './fetch-from-pokeapi.mjs';
+import { getCache, setCache } from './cache.mjs';
+
+export const isValidNameOrId = async (pokemonIdOrName) => {
+ try {
+ const cachedValidNamesAndIds = getCache('validNamesAndIds');
+
+ if (cachedValidNamesAndIds) {
+ console.log('Checking valid names and ids in cache');
+ return cachedValidNamesAndIds.includes(pokemonIdOrName);
+ } else {
+ const { data } = await fetchFromPokeAPI(
+ `https://pokeapi.co/api/v2/pokemon/?limit=9000`
+ );
+
+ const validNamesAndIds = data.results.reduce((arr, currObj) => {
+ arr.push(currObj.name);
+ arr.push(currObj.url.split('/').filter(Boolean).pop());
+ return arr;
+ }, []);
+
+ console.log('Setting valid names and ids in cache');
+ setCache('validNamesAndIds', validNamesAndIds);
+ return validNamesAndIds.includes(pokemonIdOrName);
+ }
+ } catch (err) {
+ console.log(err);
+ return err;
+ }
+};
From add8b6f5f37c604a735a3a5cdc9125fe580febe4 Mon Sep 17 00:00:00 2001
From: scissorsneedfoodtoo
Date: Tue, 25 Jul 2023 18:37:47 +0900
Subject: [PATCH 09/16] =?UTF-8?q?feat:=20cache=20by=20id=20and=20name=20wh?=
=?UTF-8?q?enever=20fetching=20a=20valid=20pok=C3=A9mon=20from=20Pok=C3=A9?=
=?UTF-8?q?API?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
apps/pokeapi-proxy/api/pokemon/pokemon.handlers.mjs | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/apps/pokeapi-proxy/api/pokemon/pokemon.handlers.mjs b/apps/pokeapi-proxy/api/pokemon/pokemon.handlers.mjs
index aaaf87f2a..becd4c59e 100644
--- a/apps/pokeapi-proxy/api/pokemon/pokemon.handlers.mjs
+++ b/apps/pokeapi-proxy/api/pokemon/pokemon.handlers.mjs
@@ -40,8 +40,9 @@ export const getPokemonData = async (req, res, next) => {
weight
};
- // Cache simplified data and send it as a response
- setCache(pokemonIdOrName, simplifiedPokemonData);
+ // Cache simplified data by id and name, then send it as a response
+ setCache(simplifiedPokemonData.id, simplifiedPokemonData);
+ setCache(simplifiedPokemonData.name, simplifiedPokemonData);
res.send(simplifiedPokemonData);
} catch (err) {
// Set status code and message from the Axios response object
From 44b5a5542a27bf69c92a082072f8fcf51ca14ce1 Mon Sep 17 00:00:00 2001
From: scissorsneedfoodtoo
Date: Wed, 26 Jul 2023 18:06:34 +0900
Subject: [PATCH 10/16] fix: simplify middleware and error handling, prettify
code
---
.../api/pokemon/pokemon.handlers.mjs | 12 ++---
.../api/pokemon/pokemon.middleware.mjs | 48 ++++++++++++++-----
.../api/pokemon/pokemon.routes.mjs | 12 ++++-
.../api/utils/fetch-from-pokeapi.mjs | 6 ---
.../api/utils/is-valid-name-or-id.mjs | 30 ------------
apps/pokeapi-proxy/server.mjs | 27 +++++++----
6 files changed, 67 insertions(+), 68 deletions(-)
delete mode 100644 apps/pokeapi-proxy/api/utils/fetch-from-pokeapi.mjs
delete mode 100644 apps/pokeapi-proxy/api/utils/is-valid-name-or-id.mjs
diff --git a/apps/pokeapi-proxy/api/pokemon/pokemon.handlers.mjs b/apps/pokeapi-proxy/api/pokemon/pokemon.handlers.mjs
index becd4c59e..c7f9b6ecd 100644
--- a/apps/pokeapi-proxy/api/pokemon/pokemon.handlers.mjs
+++ b/apps/pokeapi-proxy/api/pokemon/pokemon.handlers.mjs
@@ -1,10 +1,11 @@
-import { fetchFromPokeAPI } from '../utils/fetch-from-pokeapi.mjs';
+import axios from 'axios';
import { setCache } from '../utils/cache.mjs';
export const getPokemonData = async (req, res, next) => {
try {
const { pokemonIdOrName } = req.params;
- const { data } = await fetchFromPokeAPI(
+ console.log('Fetching Pokémon data from PokéAPI');
+ const { data } = await axios.get(
`https://pokeapi.co/api/v2/pokemon/${pokemonIdOrName}`
);
const {
@@ -43,12 +44,9 @@ export const getPokemonData = async (req, res, next) => {
// Cache simplified data by id and name, then send it as a response
setCache(simplifiedPokemonData.id, simplifiedPokemonData);
setCache(simplifiedPokemonData.name, simplifiedPokemonData);
+
res.send(simplifiedPokemonData);
} catch (err) {
- // Set status code and message from the Axios response object
- next({
- statusCode: err?.response?.status,
- message: err?.response?.data
- });
+ next(err);
}
};
diff --git a/apps/pokeapi-proxy/api/pokemon/pokemon.middleware.mjs b/apps/pokeapi-proxy/api/pokemon/pokemon.middleware.mjs
index 27cdf37d1..261415ebf 100644
--- a/apps/pokeapi-proxy/api/pokemon/pokemon.middleware.mjs
+++ b/apps/pokeapi-proxy/api/pokemon/pokemon.middleware.mjs
@@ -1,5 +1,5 @@
-import { getCache } from '../utils/cache.mjs';
-import { isValidNameOrId } from '../utils/is-valid-name-or-id.mjs';
+import axios from 'axios';
+import { getCache, setCache } from '../utils/cache.mjs';
export const checkCacheForPokemonData = (req, res, next) => {
const { pokemonIdOrName } = req.params;
@@ -19,20 +19,42 @@ export const checkCacheForPokemonData = (req, res, next) => {
};
export const validateNameOrId = async (req, res, next) => {
- const { pokemonIdOrName } = req.params;
- const isValidPath = await isValidNameOrId(pokemonIdOrName);
-
try {
- if (isValidPath) {
- console.log('Is a valid Pokémon name or id');
+ const { pokemonIdOrName } = req.params;
+ const cachedValidNamesAndIds = getCache('validNamesAndIds');
+
+ if (cachedValidNamesAndIds) {
+ console.log('Checking valid names and ids in cache');
+ if (cachedValidNamesAndIds.includes(pokemonIdOrName)) {
+ return next();
+ }
+ }
+
+ console.log('Fetching valid names and ids from PokéAPI');
+ const { data } = await axios.get(
+ `https://pokeapi.co/api/v2/pokemon/?limit=9000`
+ );
+
+ const validNamesAndIds = data.results.reduce((arr, currObj) => {
+ arr.push(currObj.name);
+ arr.push(currObj.url.split('/').filter(Boolean).pop());
+ return arr;
+ }, []);
+
+ console.log('Setting valid names and ids in cache');
+ setCache('validNamesAndIds', validNamesAndIds);
+
+ if (validNamesAndIds.includes(pokemonIdOrName)) {
return next();
}
-
- throw new Error();
+
+ // Set custom error status code and message
+ const invalidPokemonErr = new Error();
+ invalidPokemonErr.statusCode = 404;
+ invalidPokemonErr.message = 'Invalid Pokémon name or id';
+
+ throw invalidPokemonErr;
} catch (err) {
- next({
- statusCode: 404,
- message: 'Not Found: Is not a valid Pokémon name or id'
- });
+ next(err);
}
};
diff --git a/apps/pokeapi-proxy/api/pokemon/pokemon.routes.mjs b/apps/pokeapi-proxy/api/pokemon/pokemon.routes.mjs
index 66e3fb9d1..2b45288eb 100644
--- a/apps/pokeapi-proxy/api/pokemon/pokemon.routes.mjs
+++ b/apps/pokeapi-proxy/api/pokemon/pokemon.routes.mjs
@@ -1,8 +1,16 @@
import { getPokemonData } from './pokemon.handlers.mjs';
-import { checkCacheForPokemonData, validateNameOrId } from './pokemon.middleware.mjs';
+import {
+ checkCacheForPokemonData,
+ validateNameOrId
+} from './pokemon.middleware.mjs';
import express from 'express';
const router = express.Router();
-router.get('/pokemon/:pokemonIdOrName', checkCacheForPokemonData, validateNameOrId, getPokemonData);
+router.get(
+ '/pokemon/:pokemonIdOrName',
+ checkCacheForPokemonData,
+ validateNameOrId,
+ getPokemonData
+);
export { router };
diff --git a/apps/pokeapi-proxy/api/utils/fetch-from-pokeapi.mjs b/apps/pokeapi-proxy/api/utils/fetch-from-pokeapi.mjs
deleted file mode 100644
index 1f3a99cf5..000000000
--- a/apps/pokeapi-proxy/api/utils/fetch-from-pokeapi.mjs
+++ /dev/null
@@ -1,6 +0,0 @@
-import axios from 'axios';
-
-export const fetchFromPokeAPI = async url => {
- console.log('Fetching from PokéAPI');
- return await axios.get(url);
-};
diff --git a/apps/pokeapi-proxy/api/utils/is-valid-name-or-id.mjs b/apps/pokeapi-proxy/api/utils/is-valid-name-or-id.mjs
deleted file mode 100644
index fb48d1810..000000000
--- a/apps/pokeapi-proxy/api/utils/is-valid-name-or-id.mjs
+++ /dev/null
@@ -1,30 +0,0 @@
-import { fetchFromPokeAPI } from './fetch-from-pokeapi.mjs';
-import { getCache, setCache } from './cache.mjs';
-
-export const isValidNameOrId = async (pokemonIdOrName) => {
- try {
- const cachedValidNamesAndIds = getCache('validNamesAndIds');
-
- if (cachedValidNamesAndIds) {
- console.log('Checking valid names and ids in cache');
- return cachedValidNamesAndIds.includes(pokemonIdOrName);
- } else {
- const { data } = await fetchFromPokeAPI(
- `https://pokeapi.co/api/v2/pokemon/?limit=9000`
- );
-
- const validNamesAndIds = data.results.reduce((arr, currObj) => {
- arr.push(currObj.name);
- arr.push(currObj.url.split('/').filter(Boolean).pop());
- return arr;
- }, []);
-
- console.log('Setting valid names and ids in cache');
- setCache('validNamesAndIds', validNamesAndIds);
- return validNamesAndIds.includes(pokemonIdOrName);
- }
- } catch (err) {
- console.log(err);
- return err;
- }
-};
diff --git a/apps/pokeapi-proxy/server.mjs b/apps/pokeapi-proxy/server.mjs
index 2fff20383..cd3f7ce2c 100644
--- a/apps/pokeapi-proxy/server.mjs
+++ b/apps/pokeapi-proxy/server.mjs
@@ -4,27 +4,34 @@ import { router as pokemonRouter } from './api/pokemon/pokemon.routes.mjs';
const portNum = process.env.PORT || 3000;
const app = express();
+// Serve static content and landing page
app.use(express.static('public'));
app.get('/', (req, res) => {
res.sendFile(__dirname + '/views/index.html');
});
+// API routes
app.use('/api', pokemonRouter);
-// Invalid path error handler
-app.use((req, res, next) => {
- res.status(404).send('Invalid path');
-});
-
-// Global error handler
-app.use((err, req, res, next) => {
- err.statusCode = err.statusCode || 500;
- err.message = err.message || 'Internal server error';
+// Error handlers
+const errorHandler = (err, req, res, next) => {
+ // Check for Axios status codes and statusText, or other error messages,
+ // otherwise set defaults
+ err.statusCode = err?.response?.status || err.statusCode || 500;
+ err.message =
+ err?.response?.statusText || err.message || 'Internal server error';
console.error(err.statusCode, err.message);
res.status(err.statusCode).send(err.message);
-});
+};
+
+const invalidPathHandler = (req, res, next) => {
+ res.status(404).send('Invalid path');
+};
+
+app.use(errorHandler);
+app.use(invalidPathHandler);
// Listen for requests
app.listen(portNum, () => {
From c70dccc557a3148b09b23c02ef6eab5a867a2eae Mon Sep 17 00:00:00 2001
From: scissorsneedfoodtoo
Date: Tue, 19 Sep 2023 18:51:46 +0900
Subject: [PATCH 11/16] feat: add route to get all pokemon names and routes,
refactor to improve caching
---
.../api/pokemon/pokemon.handlers.mjs | 48 ++++++++++++++++--
.../api/pokemon/pokemon.middleware.mjs | 49 ++++++-------------
.../api/pokemon/pokemon.routes.mjs | 12 +++--
3 files changed, 68 insertions(+), 41 deletions(-)
diff --git a/apps/pokeapi-proxy/api/pokemon/pokemon.handlers.mjs b/apps/pokeapi-proxy/api/pokemon/pokemon.handlers.mjs
index c7f9b6ecd..17260eaad 100644
--- a/apps/pokeapi-proxy/api/pokemon/pokemon.handlers.mjs
+++ b/apps/pokeapi-proxy/api/pokemon/pokemon.handlers.mjs
@@ -1,5 +1,46 @@
import axios from 'axios';
-import { setCache } from '../utils/cache.mjs';
+import { getCache, setCache } from '../utils/cache.mjs';
+
+export const getAllPokemonNamesAndRoutes = async (req, res, next) => {
+ try {
+ const { pokemonIdOrName } = req.params;
+ // Attempt to get all Pokémon names and routes from cache
+ let allPokemonNamesAndRoutes = getCache('allPokemonNamesAndRoutes');
+
+ if (!allPokemonNamesAndRoutes) {
+ console.log('Fetching all Pokémon names and routes from PokéAPI');
+ const { data } = await axios.get(
+ `https://pokeapi.co/api/v2/pokemon/?limit=9000`
+ );
+
+ allPokemonNamesAndRoutes = data.results.map(obj => {
+ const { name, url } = obj;
+ return {
+ name,
+ url: url.replace(
+ 'https://pokeapi.co/api/v2/',
+ `${req.protocol}://${req.get('host')}/api/`
+ )
+ };
+ });
+
+ // Cache all Pokémon names and routes
+ setCache('allPokemonNamesAndRoutes', allPokemonNamesAndRoutes);
+ }
+
+ if (pokemonIdOrName) {
+ // User is requesting a specific Pokémon, so pass the data to the next middleware
+ // for id or name validation
+ res.locals.allPokemonNamesAndRoutes = allPokemonNamesAndRoutes;
+ next();
+ } else {
+ // User is requesting all Pokémon names and routes, so send the data as a response
+ res.send(allPokemonNamesAndRoutes);
+ }
+ } catch (err) {
+ next(err);
+ }
+};
export const getPokemonData = async (req, res, next) => {
try {
@@ -30,10 +71,7 @@ export const getPokemonData = async (req, res, next) => {
sprites: Object.keys(sprites)
.filter(key => typeof sprites[key] === 'string')
.reduce((obj, key) => {
- obj[key] = sprites[key].replace(
- 'https://raw.githubusercontent.com/PokeAPI/sprites/master/',
- 'https://cdn.freecodecamp.org/pokeapi/'
- );
+ obj[key] = sprites[key];
return obj;
}, {}),
stats,
diff --git a/apps/pokeapi-proxy/api/pokemon/pokemon.middleware.mjs b/apps/pokeapi-proxy/api/pokemon/pokemon.middleware.mjs
index 261415ebf..858b118c7 100644
--- a/apps/pokeapi-proxy/api/pokemon/pokemon.middleware.mjs
+++ b/apps/pokeapi-proxy/api/pokemon/pokemon.middleware.mjs
@@ -1,15 +1,14 @@
-import axios from 'axios';
-import { getCache, setCache } from '../utils/cache.mjs';
+import { getCache } from '../utils/cache.mjs';
-export const checkCacheForPokemonData = (req, res, next) => {
+export const checkCache = (req, res, next) => {
const { pokemonIdOrName } = req.params;
try {
- const cachedPokemonData = getCache(pokemonIdOrName);
+ const cachedData = getCache(pokemonIdOrName || 'allPokemonNamesAndRoutes');
- if (cachedPokemonData) {
- console.log('Serving cached Pokémon data');
- return res.send(cachedPokemonData);
+ if (cachedData) {
+ console.log('Serving cached data');
+ return res.send(cachedData);
}
next();
@@ -21,39 +20,23 @@ export const checkCacheForPokemonData = (req, res, next) => {
export const validateNameOrId = async (req, res, next) => {
try {
const { pokemonIdOrName } = req.params;
- const cachedValidNamesAndIds = getCache('validNamesAndIds');
-
- if (cachedValidNamesAndIds) {
- console.log('Checking valid names and ids in cache');
- if (cachedValidNamesAndIds.includes(pokemonIdOrName)) {
- return next();
- }
- }
-
- console.log('Fetching valid names and ids from PokéAPI');
- const { data } = await axios.get(
- `https://pokeapi.co/api/v2/pokemon/?limit=9000`
- );
-
- const validNamesAndIds = data.results.reduce((arr, currObj) => {
+ const allPokemonNamesAndRoutes = res.locals.allPokemonNamesAndRoutes;
+ const validNamesAndIds = allPokemonNamesAndRoutes.reduce((arr, currObj) => {
arr.push(currObj.name);
arr.push(currObj.url.split('/').filter(Boolean).pop());
return arr;
}, []);
- console.log('Setting valid names and ids in cache');
- setCache('validNamesAndIds', validNamesAndIds);
-
if (validNamesAndIds.includes(pokemonIdOrName)) {
- return next();
+ next();
+ } else {
+ // Set custom error status code and message
+ const invalidPokemonErr = new Error();
+ invalidPokemonErr.statusCode = 404;
+ invalidPokemonErr.message = 'Invalid Pokémon name or id';
+
+ throw invalidPokemonErr;
}
-
- // Set custom error status code and message
- const invalidPokemonErr = new Error();
- invalidPokemonErr.statusCode = 404;
- invalidPokemonErr.message = 'Invalid Pokémon name or id';
-
- throw invalidPokemonErr;
} catch (err) {
next(err);
}
diff --git a/apps/pokeapi-proxy/api/pokemon/pokemon.routes.mjs b/apps/pokeapi-proxy/api/pokemon/pokemon.routes.mjs
index 2b45288eb..63bfa99f5 100644
--- a/apps/pokeapi-proxy/api/pokemon/pokemon.routes.mjs
+++ b/apps/pokeapi-proxy/api/pokemon/pokemon.routes.mjs
@@ -1,14 +1,20 @@
-import { getPokemonData } from './pokemon.handlers.mjs';
+import { getAllPokemonNamesAndRoutes, getPokemonData } from './pokemon.handlers.mjs';
import {
- checkCacheForPokemonData,
+ checkCache,
validateNameOrId
} from './pokemon.middleware.mjs';
import express from 'express';
const router = express.Router();
+router.get('/pokemon',
+ checkCache,
+ getAllPokemonNamesAndRoutes
+);
+
router.get(
'/pokemon/:pokemonIdOrName',
- checkCacheForPokemonData,
+ checkCache,
+ getAllPokemonNamesAndRoutes,
validateNameOrId,
getPokemonData
);
From d50ffa07c97a4df368d60315a0bfeb09ff07ebd7 Mon Sep 17 00:00:00 2001
From: scissorsneedfoodtoo
Date: Tue, 19 Sep 2023 19:05:38 +0900
Subject: [PATCH 12/16] feat: add ids to the list of all valid pokemon
---
apps/pokeapi-proxy/api/pokemon/pokemon.handlers.mjs | 1 +
1 file changed, 1 insertion(+)
diff --git a/apps/pokeapi-proxy/api/pokemon/pokemon.handlers.mjs b/apps/pokeapi-proxy/api/pokemon/pokemon.handlers.mjs
index 17260eaad..d7f7f5260 100644
--- a/apps/pokeapi-proxy/api/pokemon/pokemon.handlers.mjs
+++ b/apps/pokeapi-proxy/api/pokemon/pokemon.handlers.mjs
@@ -16,6 +16,7 @@ export const getAllPokemonNamesAndRoutes = async (req, res, next) => {
allPokemonNamesAndRoutes = data.results.map(obj => {
const { name, url } = obj;
return {
+ id: Number(url.split('/').filter(Boolean).pop()),
name,
url: url.replace(
'https://pokeapi.co/api/v2/',
From b148a181899a225f0ebc8b051d91a681d16b522d Mon Sep 17 00:00:00 2001
From: scissorsneedfoodtoo
Date: Tue, 19 Sep 2023 19:06:46 +0900
Subject: [PATCH 13/16] feat: add /pokemon route description and examples to
the landing page
---
apps/pokeapi-proxy/public/index.html | 24 +++++++++++++++++++-----
1 file changed, 19 insertions(+), 5 deletions(-)
diff --git a/apps/pokeapi-proxy/public/index.html b/apps/pokeapi-proxy/public/index.html
index 96bd7373c..2d3e0ec54 100644
--- a/apps/pokeapi-proxy/public/index.html
+++ b/apps/pokeapi-proxy/public/index.html
@@ -20,23 +20,37 @@
PokéAPI Proxy
Usage
+
+ Use the endpoint
+ https://pokeapi-proxy.freecodecamp.rocks/api/pokemon
+ to see a list of all valid Pokémon names, id numbers, and URLs.
+
Use the endpoint
https://pokeapi-proxy.freecodecamp.rocks/api/pokemon/{name-or-id}
to get data for a Pokémon, where {name-or-id} is the
- Pokémon's name or its id number in the
- official Pokédex .
+ Pokémon's name or id number.
- Note that the name should be lowercase, have special characters
- removed, and dash separated. Also, if the Pokémon has either ♀ or ♀ as
- part of its name, the format is {name-f} or
+ Note: Pokémon names should be in lowercase, have special characters
+ removed, and be dash separated. Also, if the Pokémon has either ♀ or ♀
+ as part of its name, the format is {name-f} or
{name-m}, respectively.
Example Requests
Click any of the example requests below to see its response.
+
All valid Pokémon:
+
Pikachu:
From 5c45ad642fe9434f49303001c07bb7c3266d5532 Mon Sep 17 00:00:00 2001
From: scissorsneedfoodtoo
Date: Tue, 19 Sep 2023 19:11:38 +0900
Subject: [PATCH 14/16] feat: update README.md
---
README.md | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/README.md b/README.md
index 503fb71bb..a02b08258 100644
--- a/README.md
+++ b/README.md
@@ -180,6 +180,11 @@
### Main Curriculum
+- PokéAPI Proxy
+
+ - [Project description](https://www.freecodecamp.org/learn/2022/javascript-algorithms-and-data-structures/pokemon-search-app-project/build-a-pokemon-search-app)
+ - [Landing page](https://pokeapi-proxy.freecodecamp.rocks/)
+
- Stock Price Checker Proxy
- [Project description](https://www.freecodecamp.org/learn/information-security/information-security-projects/stock-price-checker)
- [Landing page](https://stock-price-checker-proxy.freecodecamp.rocks/)
From e340c75faf84d9aae88c2beedf7e1d4651fbfccd Mon Sep 17 00:00:00 2001
From: scissorsneedfoodtoo
Date: Tue, 19 Sep 2023 19:16:29 +0900
Subject: [PATCH 15/16] fix: add Dockerfile, set TTL env var
---
apps/pokeapi-proxy/Dockerfile | 14 ++++++++++++++
1 file changed, 14 insertions(+)
create mode 100644 apps/pokeapi-proxy/Dockerfile
diff --git a/apps/pokeapi-proxy/Dockerfile b/apps/pokeapi-proxy/Dockerfile
new file mode 100644
index 000000000..f2db11aeb
--- /dev/null
+++ b/apps/pokeapi-proxy/Dockerfile
@@ -0,0 +1,14 @@
+FROM node:18-bullseye-slim
+
+WORKDIR /app
+
+# Copy over all the files in the project directory to /app early
+# for rollup bundling
+COPY . .
+
+ENV PORT=3000
+ENV CACHE_TTL_HOURS=${POKEAPI_PROXY_CACHE_TTL_HOURS}
+
+RUN npm ci
+
+CMD ["npm", "start"]
From 36ba96e7ec3dc79b4d15f9569c59e8641b0c8d93 Mon Sep 17 00:00:00 2001
From: scissorsneedfoodtoo
Date: Wed, 20 Sep 2023 13:52:14 +0900
Subject: [PATCH 16/16] fix: rename function to get all resources from /pokemon
endpoint
---
.../api/pokemon/pokemon.handlers.mjs | 44 +++++++++++--------
.../api/pokemon/pokemon.middleware.mjs | 16 ++++---
.../api/pokemon/pokemon.routes.mjs | 15 +++----
3 files changed, 40 insertions(+), 35 deletions(-)
diff --git a/apps/pokeapi-proxy/api/pokemon/pokemon.handlers.mjs b/apps/pokeapi-proxy/api/pokemon/pokemon.handlers.mjs
index d7f7f5260..71f526216 100644
--- a/apps/pokeapi-proxy/api/pokemon/pokemon.handlers.mjs
+++ b/apps/pokeapi-proxy/api/pokemon/pokemon.handlers.mjs
@@ -1,42 +1,48 @@
import axios from 'axios';
import { getCache, setCache } from '../utils/cache.mjs';
-export const getAllPokemonNamesAndRoutes = async (req, res, next) => {
+export const getPokemonEndpointResources = async (req, res, next) => {
try {
const { pokemonIdOrName } = req.params;
- // Attempt to get all Pokémon names and routes from cache
- let allPokemonNamesAndRoutes = getCache('allPokemonNamesAndRoutes');
+ // Attempt to get all resources for the Pokémon endpoint from the cache
+ let pokemonEndpointResources = getCache('pokemonEndpointResources');
- if (!allPokemonNamesAndRoutes) {
- console.log('Fetching all Pokémon names and routes from PokéAPI');
+ if (!pokemonEndpointResources) {
+ console.log(
+ 'Fetching all resources for the Pokémon endpoint from PokéAPI'
+ );
const { data } = await axios.get(
`https://pokeapi.co/api/v2/pokemon/?limit=9000`
);
+ const { count, results } = data;
- allPokemonNamesAndRoutes = data.results.map(obj => {
- const { name, url } = obj;
- return {
- id: Number(url.split('/').filter(Boolean).pop()),
- name,
- url: url.replace(
- 'https://pokeapi.co/api/v2/',
- `${req.protocol}://${req.get('host')}/api/`
- )
- };
- });
+ pokemonEndpointResources = {
+ count,
+ results: results.map(obj => {
+ const { name, url } = obj;
+ return {
+ id: Number(url.split('/').filter(Boolean).pop()),
+ name,
+ url: url.replace(
+ 'https://pokeapi.co/api/v2/',
+ `${req.protocol}://${req.get('host')}/api/`
+ )
+ };
+ })
+ };
// Cache all Pokémon names and routes
- setCache('allPokemonNamesAndRoutes', allPokemonNamesAndRoutes);
+ setCache('pokemonEndpointResources', pokemonEndpointResources);
}
if (pokemonIdOrName) {
// User is requesting a specific Pokémon, so pass the data to the next middleware
// for id or name validation
- res.locals.allPokemonNamesAndRoutes = allPokemonNamesAndRoutes;
+ res.locals.pokemonEndpointResources = pokemonEndpointResources;
next();
} else {
// User is requesting all Pokémon names and routes, so send the data as a response
- res.send(allPokemonNamesAndRoutes);
+ res.send(pokemonEndpointResources);
}
} catch (err) {
next(err);
diff --git a/apps/pokeapi-proxy/api/pokemon/pokemon.middleware.mjs b/apps/pokeapi-proxy/api/pokemon/pokemon.middleware.mjs
index 858b118c7..9ecff0bd0 100644
--- a/apps/pokeapi-proxy/api/pokemon/pokemon.middleware.mjs
+++ b/apps/pokeapi-proxy/api/pokemon/pokemon.middleware.mjs
@@ -4,7 +4,7 @@ export const checkCache = (req, res, next) => {
const { pokemonIdOrName } = req.params;
try {
- const cachedData = getCache(pokemonIdOrName || 'allPokemonNamesAndRoutes');
+ const cachedData = getCache(pokemonIdOrName || 'pokemonEndpointResources');
if (cachedData) {
console.log('Serving cached data');
@@ -20,12 +20,14 @@ export const checkCache = (req, res, next) => {
export const validateNameOrId = async (req, res, next) => {
try {
const { pokemonIdOrName } = req.params;
- const allPokemonNamesAndRoutes = res.locals.allPokemonNamesAndRoutes;
- const validNamesAndIds = allPokemonNamesAndRoutes.reduce((arr, currObj) => {
- arr.push(currObj.name);
- arr.push(currObj.url.split('/').filter(Boolean).pop());
- return arr;
- }, []);
+ const validNamesAndIds = res.locals.pokemonEndpointResources.results.reduce(
+ (arr, currObj) => {
+ arr.push(currObj.name);
+ arr.push(currObj.url.split('/').filter(Boolean).pop());
+ return arr;
+ },
+ []
+ );
if (validNamesAndIds.includes(pokemonIdOrName)) {
next();
diff --git a/apps/pokeapi-proxy/api/pokemon/pokemon.routes.mjs b/apps/pokeapi-proxy/api/pokemon/pokemon.routes.mjs
index 63bfa99f5..b877214d5 100644
--- a/apps/pokeapi-proxy/api/pokemon/pokemon.routes.mjs
+++ b/apps/pokeapi-proxy/api/pokemon/pokemon.routes.mjs
@@ -1,20 +1,17 @@
-import { getAllPokemonNamesAndRoutes, getPokemonData } from './pokemon.handlers.mjs';
import {
- checkCache,
- validateNameOrId
-} from './pokemon.middleware.mjs';
+ getPokemonEndpointResources,
+ getPokemonData
+} from './pokemon.handlers.mjs';
+import { checkCache, validateNameOrId } from './pokemon.middleware.mjs';
import express from 'express';
const router = express.Router();
-router.get('/pokemon',
- checkCache,
- getAllPokemonNamesAndRoutes
-);
+router.get('/pokemon', checkCache, getPokemonEndpointResources);
router.get(
'/pokemon/:pokemonIdOrName',
checkCache,
- getAllPokemonNamesAndRoutes,
+ getPokemonEndpointResources,
validateNameOrId,
getPokemonData
);