From c204e6091582ecdead5b4ae4ab3ae9a2b400b618 Mon Sep 17 00:00:00 2001 From: Duy Nguyen Date: Sat, 8 Jun 2024 18:13:02 +0700 Subject: [PATCH 1/2] feat(smtp): add smtp mail server and api-key check --- .env.example | 3 + package-lock.json | 618 +++++++++++++++--- package.json | 6 +- src/api-docs/openAPIDocumentGenerator.ts | 15 +- src/api-docs/openAPIHeaderBuilders.ts | 5 + src/api-docs/openAPIResponseBuilders.ts | 2 +- src/common/middleware/apiKeyHandler.ts | 23 + src/common/models/serviceResponse.ts | 14 +- src/common/utils/envConfig.ts | 1 + src/common/utils/httpHandlers.ts | 13 +- .../articleReader/articleReaderRouter.ts | 12 +- src/routes/smtpMail/smtpMailModel.ts | 58 ++ src/routes/smtpMail/smtpMailRouter.ts | 87 +++ .../youtubeTranscript/transcriptRouter.ts | 12 +- src/server.ts | 3 + 15 files changed, 788 insertions(+), 84 deletions(-) create mode 100644 src/api-docs/openAPIHeaderBuilders.ts create mode 100644 src/common/middleware/apiKeyHandler.ts create mode 100644 src/routes/smtpMail/smtpMailModel.ts create mode 100644 src/routes/smtpMail/smtpMailRouter.ts diff --git a/.env.example b/.env.example index e486e19..a75afc8 100644 --- a/.env.example +++ b/.env.example @@ -8,3 +8,6 @@ CORS_ORIGIN="http://localhost:*" # Allowed CORS origin, adjust as necessary # Rate Limiting COMMON_RATE_LIMIT_WINDOW_MS="1000" # Window size for rate limiting (ms) COMMON_RATE_LIMIT_MAX_REQUESTS="20" # Max number of requests per window per IP + +# Your API Key in the x-api-key header +API_KEY=your_api_key_here \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index aac23d6..d5b4b0e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,6 +7,7 @@ "": { "name": "typingmind-proxy", "version": "1.2.1", + "hasInstallScript": true, "license": "MIT", "dependencies": { "@asteasolutions/zod-to-openapi": "^6.4.0", @@ -25,6 +26,7 @@ "helmet": "^7.1.0", "http-status-codes": "^2.3.0", "jsdom": "^24.0.0", + "nodemailer": "^6.9.13", "path": "^0.12.7", "pino-http": "^9.0.0", "swagger-ui-express": "^5.0.0", @@ -38,6 +40,7 @@ "@release-it/conventional-changelog": "^8.0.1", "@types/cors": "^2.8.17", "@types/express": "^4.17.21", + "@types/nodemailer": "^6.4.15", "@types/supertest": "^6.0.2", "@types/swagger-ui-express": "^4.1.6", "@typescript-eslint/eslint-plugin": "^7.1.0", @@ -1506,9 +1509,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.12.1.tgz", - "integrity": "sha512-iU2Sya8hNn1LhsYyf0N+L4Gf9Qc+9eBTJJJsaOGUp+7x4n2M9dxTt8UvhJl3oeftSjblSlpCfvjA/IfP3g5VjQ==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.18.0.tgz", + "integrity": "sha512-Tya6xypR10giZV1XzxmH5wr25VcZSncG0pZIjfePT0OVBvqNEurzValetGNarVrGiq66EBVAFn15iYX4w6FKgQ==", "cpu": [ "arm" ], @@ -1519,9 +1522,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.12.1.tgz", - "integrity": "sha512-wlzcWiH2Ir7rdMELxFE5vuM7D6TsOcJ2Yw0c3vaBR3VOsJFVTx9xvwnAvhgU5Ii8Gd6+I11qNHwndDscIm0HXg==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.18.0.tgz", + "integrity": "sha512-avCea0RAP03lTsDhEyfy+hpfr85KfyTctMADqHVhLAF3MlIkq83CP8UfAHUssgXTYd+6er6PaAhx/QGv4L1EiA==", "cpu": [ "arm64" ], @@ -1532,9 +1535,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.12.1.tgz", - "integrity": "sha512-YRXa1+aZIFN5BaImK+84B3uNK8C6+ynKLPgvn29X9s0LTVCByp54TB7tdSMHDR7GTV39bz1lOmlLDuedgTwwHg==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.18.0.tgz", + "integrity": "sha512-IWfdwU7KDSm07Ty0PuA/W2JYoZ4iTj3TUQjkVsO/6U+4I1jN5lcR71ZEvRh52sDOERdnNhhHU57UITXz5jC1/w==", "cpu": [ "arm64" ], @@ -1545,9 +1548,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.12.1.tgz", - "integrity": "sha512-opjWJ4MevxeA8FhlngQWPBOvVWYNPFkq6/25rGgG+KOy0r8clYwL1CFd+PGwRqqMFVQ4/Qd3sQu5t7ucP7C/Uw==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.18.0.tgz", + "integrity": "sha512-n2LMsUz7Ynu7DoQrSQkBf8iNrjOGyPLrdSg802vk6XT3FtsgX6JbE8IHRvposskFm9SNxzkLYGSq9QdpLYpRNA==", "cpu": [ "x64" ], @@ -1558,9 +1561,22 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.12.1.tgz", - "integrity": "sha512-uBkwaI+gBUlIe+EfbNnY5xNyXuhZbDSx2nzzW8tRMjUmpScd6lCQYKY2V9BATHtv5Ef2OBq6SChEP8h+/cxifQ==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.18.0.tgz", + "integrity": "sha512-C/zbRYRXFjWvz9Z4haRxcTdnkPt1BtCkz+7RtBSuNmKzMzp3ZxdM28Mpccn6pt28/UWUCTXa+b0Mx1k3g6NOMA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.18.0.tgz", + "integrity": "sha512-l3m9ewPgjQSXrUMHg93vt0hYCGnrMOcUpTz6FLtbwljo2HluS4zTXFy2571YQbisTnfTKPZ01u/ukJdQTLGh9A==", "cpu": [ "arm" ], @@ -1571,9 +1587,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.12.1.tgz", - "integrity": "sha512-0bK9aG1kIg0Su7OcFTlexkVeNZ5IzEsnz1ept87a0TUgZ6HplSgkJAnFpEVRW7GRcikT4GlPV0pbtVedOaXHQQ==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.18.0.tgz", + "integrity": "sha512-rJ5D47d8WD7J+7STKdCUAgmQk49xuFrRi9pZkWoRD1UeSMakbcepWXPF8ycChBoAqs1pb2wzvbY6Q33WmN2ftw==", "cpu": [ "arm64" ], @@ -1584,9 +1600,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.12.1.tgz", - "integrity": "sha512-qB6AFRXuP8bdkBI4D7UPUbE7OQf7u5OL+R94JE42Z2Qjmyj74FtDdLGeriRyBDhm4rQSvqAGCGC01b8Fu2LthQ==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.18.0.tgz", + "integrity": "sha512-be6Yx37b24ZwxQ+wOQXXLZqpq4jTckJhtGlWGZs68TgdKXJgw54lUUoFYrg6Zs/kjzAQwEwYbp8JxZVzZLRepQ==", "cpu": [ "arm64" ], @@ -1596,10 +1612,23 @@ "linux" ] }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.18.0.tgz", + "integrity": "sha512-hNVMQK+qrA9Todu9+wqrXOHxFiD5YmdEi3paj6vP02Kx1hjd2LLYR2eaN7DsEshg09+9uzWi2W18MJDlG0cxJA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.12.1.tgz", - "integrity": "sha512-sHig3LaGlpNgDj5o8uPEoGs98RII8HpNIqFtAI8/pYABO8i0nb1QzT0JDoXF/pxzqO+FkxvwkHZo9k0NJYDedg==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.18.0.tgz", + "integrity": "sha512-ROCM7i+m1NfdrsmvwSzoxp9HFtmKGHEqu5NNDiZWQtXLA8S5HBCkVvKAxJ8U+CVctHwV2Gb5VUaK7UAkzhDjlg==", "cpu": [ "riscv64" ], @@ -1609,10 +1638,23 @@ "linux" ] }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.18.0.tgz", + "integrity": "sha512-0UyyRHyDN42QL+NbqevXIIUnKA47A+45WyasO+y2bGJ1mhQrfrtXUpTxCOrfxCR4esV3/RLYyucGVPiUsO8xjg==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.12.1.tgz", - "integrity": "sha512-nD3YcUv6jBJbBNFvSbp0IV66+ba/1teuBcu+fBBPZ33sidxitc6ErhON3JNavaH8HlswhWMC3s5rgZpM4MtPqQ==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.18.0.tgz", + "integrity": "sha512-xuglR2rBVHA5UsI8h8UbX4VJ470PtGCf5Vpswh7p2ukaqBGFTnsfzxUBetoWBWymHMxbIG0Cmx7Y9qDZzr648w==", "cpu": [ "x64" ], @@ -1623,9 +1665,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.12.1.tgz", - "integrity": "sha512-7/XVZqgBby2qp/cO0TQ8uJK+9xnSdJ9ct6gSDdEr4MfABrjTyrW6Bau7HQ73a2a5tPB7hno49A0y1jhWGDN9OQ==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.18.0.tgz", + "integrity": "sha512-LKaqQL9osY/ir2geuLVvRRs+utWUNilzdE90TpyoX0eNqPzWjRm14oMEE+YLve4k/NAqCdPkGYDaDF5Sw+xBfg==", "cpu": [ "x64" ], @@ -1636,9 +1678,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.12.1.tgz", - "integrity": "sha512-CYc64bnICG42UPL7TrhIwsJW4QcKkIt9gGlj21gq3VV0LL6XNb1yAdHVp1pIi9gkts9gGcT3OfUYHjGP7ETAiw==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.18.0.tgz", + "integrity": "sha512-7J6TkZQFGo9qBKH0pk2cEVSRhJbL6MtfWxth7Y5YmZs57Pi+4x6c2dStAUvaQkHQLnEQv1jzBUW43GvZW8OFqA==", "cpu": [ "arm64" ], @@ -1649,9 +1691,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.12.1.tgz", - "integrity": "sha512-LN+vnlZ9g0qlHGlS920GR4zFCqAwbv2lULrR29yGaWP9u7wF5L7GqWu9Ah6/kFZPXPUkpdZwd//TNR+9XC9hvA==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.18.0.tgz", + "integrity": "sha512-Txjh+IxBPbkUB9+SXZMpv+b/vnTEtFyfWZgJ6iyCmt2tdx0OF5WhFowLmnh8ENGNpfUlUZkdI//4IEmhwPieNg==", "cpu": [ "ia32" ], @@ -1662,9 +1704,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.12.1.tgz", - "integrity": "sha512-n+vkrSyphvmU0qkQ6QBNXCGr2mKjhP08mPRM/Xp5Ck2FV4NrHU+y6axzDeixUrCBHVUS51TZhjqrKBBsHLKb2Q==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.18.0.tgz", + "integrity": "sha512-UOo5FdvOL0+eIVTgS4tIdbW+TtnBLWg1YBCcU2KWM7nuNwRz9bksDX1bekJJCpu25N1DVWaCwnT39dVQxzqS8g==", "cpu": [ "x64" ], @@ -1870,6 +1912,15 @@ "undici-types": "~5.26.4" } }, + "node_modules/@types/nodemailer": { + "version": "6.4.15", + "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.15.tgz", + "integrity": "sha512-0EBJxawVNjPkng1zm2vopRctuWVCxk34JcIlRuXSf54habUWdz1FB7wHDqOqvDa8Mtpt0Q3LTXQkAs2LNyK5jQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/normalize-package-data": { "version": "2.4.4", "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", @@ -3718,9 +3769,9 @@ "dev": true }, "node_modules/cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", "engines": { "node": ">= 0.6" } @@ -5016,16 +5067,16 @@ } }, "node_modules/express": { - "version": "4.18.3", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.3.tgz", - "integrity": "sha512-6VyCijWQ+9O7WuVMTRBTl+cjNNIzD5cY5mQ1WM8r/LEkI2u8EYpOotESNwzNlyCn3g+dmjKYI6BmNneSr/FSRw==", + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", + "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", "body-parser": "1.20.2", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.5.0", + "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", @@ -8079,6 +8130,14 @@ "url": "https://opencollective.com/node-fetch" } }, + "node_modules/nodemailer": { + "version": "6.9.13", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.13.tgz", + "integrity": "sha512-7o38Yogx6krdoBf3jCAqnIN4oSQFx+fMa0I7dK1D+me9kBxx12D+/33wSb+fhOCtIxvYJ+4x4IMEhmhCKfAiOA==", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/normalize-package-data": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.0.tgz", @@ -8862,9 +8921,9 @@ } }, "node_modules/postcss": { - "version": "8.4.35", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz", - "integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==", + "version": "8.4.38", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", "dev": true, "funding": [ { @@ -8883,7 +8942,7 @@ "dependencies": { "nanoid": "^3.3.7", "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" + "source-map-js": "^1.2.0" }, "engines": { "node": "^10 || ^12 || >=14" @@ -9768,9 +9827,9 @@ } }, "node_modules/rollup": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.12.1.tgz", - "integrity": "sha512-ggqQKvx/PsB0FaWXhIvVkSWh7a/PCLQAsMjBc+nA2M8Rv2/HG0X6zvixAB7KyZBRtifBUhy5k8voQX/mRnABPg==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.18.0.tgz", + "integrity": "sha512-QmJz14PX3rzbJCN1SG4Xe/bAAX2a6NpCP8ab2vfu2GiUr8AQcr2nCV/oEO3yneFarB67zk8ShlIyWb2LGTb3Sg==", "dev": true, "dependencies": { "@types/estree": "1.0.5" @@ -9783,19 +9842,22 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.12.1", - "@rollup/rollup-android-arm64": "4.12.1", - "@rollup/rollup-darwin-arm64": "4.12.1", - "@rollup/rollup-darwin-x64": "4.12.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.12.1", - "@rollup/rollup-linux-arm64-gnu": "4.12.1", - "@rollup/rollup-linux-arm64-musl": "4.12.1", - "@rollup/rollup-linux-riscv64-gnu": "4.12.1", - "@rollup/rollup-linux-x64-gnu": "4.12.1", - "@rollup/rollup-linux-x64-musl": "4.12.1", - "@rollup/rollup-win32-arm64-msvc": "4.12.1", - "@rollup/rollup-win32-ia32-msvc": "4.12.1", - "@rollup/rollup-win32-x64-msvc": "4.12.1", + "@rollup/rollup-android-arm-eabi": "4.18.0", + "@rollup/rollup-android-arm64": "4.18.0", + "@rollup/rollup-darwin-arm64": "4.18.0", + "@rollup/rollup-darwin-x64": "4.18.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.18.0", + "@rollup/rollup-linux-arm-musleabihf": "4.18.0", + "@rollup/rollup-linux-arm64-gnu": "4.18.0", + "@rollup/rollup-linux-arm64-musl": "4.18.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.18.0", + "@rollup/rollup-linux-riscv64-gnu": "4.18.0", + "@rollup/rollup-linux-s390x-gnu": "4.18.0", + "@rollup/rollup-linux-x64-gnu": "4.18.0", + "@rollup/rollup-linux-x64-musl": "4.18.0", + "@rollup/rollup-win32-arm64-msvc": "4.18.0", + "@rollup/rollup-win32-ia32-msvc": "4.18.0", + "@rollup/rollup-win32-x64-msvc": "4.18.0", "fsevents": "~2.3.2" } }, @@ -10270,9 +10332,9 @@ } }, "node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", "dev": true, "engines": { "node": ">=0.10.0" @@ -11521,14 +11583,14 @@ } }, "node_modules/vite": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.5.tgz", - "integrity": "sha512-BdN1xh0Of/oQafhU+FvopafUp6WaYenLU/NFoL5WyJL++GxkNfieKzBhM24H3HVsPQrlAqB7iJYTHabzaRed5Q==", + "version": "5.2.13", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.13.tgz", + "integrity": "sha512-SSq1noJfY9pR3I1TUENL3rQYDQCFqgD+lM6fTRAM8Nv6Lsg5hDLaXkjETVeBt+7vZBCMoibD+6IWnT2mJ+Zb/A==", "dev": true, "dependencies": { - "esbuild": "^0.19.3", - "postcss": "^8.4.35", - "rollup": "^4.2.0" + "esbuild": "^0.20.1", + "postcss": "^8.4.38", + "rollup": "^4.13.0" }, "bin": { "vite": "bin/vite.js" @@ -11616,6 +11678,412 @@ } } }, + "node_modules/vite/node_modules/@esbuild/aix-ppc64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", + "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz", + "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz", + "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz", + "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz", + "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz", + "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz", + "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz", + "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz", + "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz", + "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ia32": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz", + "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-loong64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz", + "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-mips64el": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz", + "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ppc64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz", + "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-riscv64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz", + "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-s390x": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz", + "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz", + "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/netbsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz", + "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/openbsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz", + "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/sunos-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz", + "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz", + "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-ia32": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz", + "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz", + "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/esbuild": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", + "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.20.2", + "@esbuild/android-arm": "0.20.2", + "@esbuild/android-arm64": "0.20.2", + "@esbuild/android-x64": "0.20.2", + "@esbuild/darwin-arm64": "0.20.2", + "@esbuild/darwin-x64": "0.20.2", + "@esbuild/freebsd-arm64": "0.20.2", + "@esbuild/freebsd-x64": "0.20.2", + "@esbuild/linux-arm": "0.20.2", + "@esbuild/linux-arm64": "0.20.2", + "@esbuild/linux-ia32": "0.20.2", + "@esbuild/linux-loong64": "0.20.2", + "@esbuild/linux-mips64el": "0.20.2", + "@esbuild/linux-ppc64": "0.20.2", + "@esbuild/linux-riscv64": "0.20.2", + "@esbuild/linux-s390x": "0.20.2", + "@esbuild/linux-x64": "0.20.2", + "@esbuild/netbsd-x64": "0.20.2", + "@esbuild/openbsd-x64": "0.20.2", + "@esbuild/sunos-x64": "0.20.2", + "@esbuild/win32-arm64": "0.20.2", + "@esbuild/win32-ia32": "0.20.2", + "@esbuild/win32-x64": "0.20.2" + } + }, "node_modules/vitest": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.3.1.tgz", diff --git a/package.json b/package.json index 2604f11..faf5550 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,9 @@ "test:watch": "vitest", "test:coverage": "vitest --coverage", "prepare": "husky install", - "release": "release-it" + "release": "release-it", + "postinstall": "npm run typings", + "typings": "typings install" }, "dependencies": { "@asteasolutions/zod-to-openapi": "^6.4.0", @@ -34,6 +36,7 @@ "helmet": "^7.1.0", "http-status-codes": "^2.3.0", "jsdom": "^24.0.0", + "nodemailer": "^6.9.13", "path": "^0.12.7", "pino-http": "^9.0.0", "swagger-ui-express": "^5.0.0", @@ -47,6 +50,7 @@ "@release-it/conventional-changelog": "^8.0.1", "@types/cors": "^2.8.17", "@types/express": "^4.17.21", + "@types/nodemailer": "^6.4.15", "@types/supertest": "^6.0.2", "@types/swagger-ui-express": "^4.1.6", "@typescript-eslint/eslint-plugin": "^7.1.0", diff --git a/src/api-docs/openAPIDocumentGenerator.ts b/src/api-docs/openAPIDocumentGenerator.ts index b08b956..e1a9614 100644 --- a/src/api-docs/openAPIDocumentGenerator.ts +++ b/src/api-docs/openAPIDocumentGenerator.ts @@ -2,10 +2,23 @@ import { OpenApiGeneratorV3, OpenAPIRegistry } from '@asteasolutions/zod-to-open import { articleReaderRegistry } from '@/routes/articleReader/articleReaderRouter'; import { healthCheckRegistry } from '@/routes/healthCheck/healthCheckRouter'; +import { smtpMailRegistry } from '@/routes/smtpMail/smtpMailRouter'; import { transcriptRegistry } from '@/routes/youtubeTranscript/transcriptRouter'; export function generateOpenAPIDocument() { - const registry = new OpenAPIRegistry([healthCheckRegistry, transcriptRegistry, articleReaderRegistry]); + const registry = new OpenAPIRegistry([ + healthCheckRegistry, + transcriptRegistry, + articleReaderRegistry, + smtpMailRegistry, + ]); + + registry.registerComponent('headers', 'x-api-key', { + example: '1234', + required: true, + description: 'The API Key you were given in the developer portal', + }); + const generator = new OpenApiGeneratorV3(registry.definitions); return generator.generateDocument({ diff --git a/src/api-docs/openAPIHeaderBuilders.ts b/src/api-docs/openAPIHeaderBuilders.ts new file mode 100644 index 0000000..3cf7fff --- /dev/null +++ b/src/api-docs/openAPIHeaderBuilders.ts @@ -0,0 +1,5 @@ +import { z } from 'zod'; + +export const apiKeyHeader = z.object({ + 'x-api-key': z.string(), +}); diff --git a/src/api-docs/openAPIResponseBuilders.ts b/src/api-docs/openAPIResponseBuilders.ts index e352762..5baf4c3 100644 --- a/src/api-docs/openAPIResponseBuilders.ts +++ b/src/api-docs/openAPIResponseBuilders.ts @@ -9,7 +9,7 @@ export function createApiResponse(schema: z.ZodTypeAny, description: string, sta description, content: { 'application/json': { - schema: ServiceResponseSchema(schema), + schema: ServiceResponseSchema(schema, statusCode == StatusCodes.OK, statusCode), }, }, }, diff --git a/src/common/middleware/apiKeyHandler.ts b/src/common/middleware/apiKeyHandler.ts new file mode 100644 index 0000000..f6499b5 --- /dev/null +++ b/src/common/middleware/apiKeyHandler.ts @@ -0,0 +1,23 @@ +import { NextFunction, Request, Response } from 'express'; +import { StatusCodes } from 'http-status-codes'; + +import { env } from '@/common/utils/envConfig'; + +import { ResponseStatus, ServiceResponse } from '../models/serviceResponse'; +import { handleServiceResponse } from '../utils/httpHandlers'; + +export const apiKeyHandler = (req: Request, res: Response, next: NextFunction) => { + const apiKeyHeader = req.headers['x-api-key']; + if (!apiKeyHeader || apiKeyHeader !== env.API_KEY) { + return handleServiceResponse( + new ServiceResponse( + ResponseStatus.Failed, + 'Please input your x-api-key in the header!', + null, + StatusCodes.UNAUTHORIZED + ), + res + ); + } + next(); +}; diff --git a/src/common/models/serviceResponse.ts b/src/common/models/serviceResponse.ts index 9ccc04b..d66d674 100644 --- a/src/common/models/serviceResponse.ts +++ b/src/common/models/serviceResponse.ts @@ -19,10 +19,18 @@ export class ServiceResponse { } } -export const ServiceResponseSchema = (dataSchema: T) => +export const ServiceResponseSchema = ( + dataSchema: T, + success: boolean = true, + statusCode: number = 200 +) => z.object({ - success: z.boolean(), + success: z.boolean().openapi({ + example: success, + }), message: z.string(), responseObject: dataSchema.optional(), - statusCode: z.number(), + statusCode: z.number().openapi({ + example: statusCode, + }), }); diff --git a/src/common/utils/envConfig.ts b/src/common/utils/envConfig.ts index 8d61ff6..a5f1382 100644 --- a/src/common/utils/envConfig.ts +++ b/src/common/utils/envConfig.ts @@ -10,4 +10,5 @@ export const env = cleanEnv(process.env, { CORS_ORIGIN: str({ default: '*' }), COMMON_RATE_LIMIT_MAX_REQUESTS: num({ default: 100 }), COMMON_RATE_LIMIT_WINDOW_MS: num({ default: 60000 }), + API_KEY: str(), }); diff --git a/src/common/utils/httpHandlers.ts b/src/common/utils/httpHandlers.ts index c8d3efe..f17339d 100644 --- a/src/common/utils/httpHandlers.ts +++ b/src/common/utils/httpHandlers.ts @@ -13,7 +13,18 @@ export const validateRequest = (schema: ZodSchema) => (req: Request, res: Respon schema.parse({ body: req.body, query: req.query, params: req.params }); next(); } catch (err) { - const errorMessage = `Invalid input: ${(err as ZodError).errors.map((e) => e.message).join(', ')}`; + const errorMessage = `Invalid input: ${(err as ZodError).errors.map((e) => `${e.path}: ${e.message} ${e.code}`).join(', ')}`; + const statusCode = StatusCodes.BAD_REQUEST; + res.status(statusCode).send(new ServiceResponse(ResponseStatus.Failed, errorMessage, null, statusCode)); + } +}; + +export const validateRequestBody = (schema: ZodSchema) => (req: Request, res: Response, next: NextFunction) => { + try { + schema.parse(req.body); + next(); + } catch (err) { + const errorMessage = `Invalid input: ${(err as ZodError).errors.map((e) => `${e.path}: ${e.message} ${e.code}`).join(', ')}`; const statusCode = StatusCodes.BAD_REQUEST; res.status(statusCode).send(new ServiceResponse(ResponseStatus.Failed, errorMessage, null, statusCode)); } diff --git a/src/routes/articleReader/articleReaderRouter.ts b/src/routes/articleReader/articleReaderRouter.ts index 4e76679..2214b59 100644 --- a/src/routes/articleReader/articleReaderRouter.ts +++ b/src/routes/articleReader/articleReaderRouter.ts @@ -6,7 +6,9 @@ import got from 'got'; import { StatusCodes } from 'http-status-codes'; import { JSDOM } from 'jsdom'; +import { apiKeyHeader } from '@/api-docs/openAPIHeaderBuilders'; import { createApiResponse } from '@/api-docs/openAPIResponseBuilders'; +import { apiKeyHandler } from '@/common/middleware/apiKeyHandler'; import { ResponseStatus, ServiceResponse } from '@/common/models/serviceResponse'; import { handleServiceResponse } from '@/common/utils/httpHandlers'; @@ -62,10 +64,15 @@ const fetchAndCleanContent = async (url: string) => { export const articleReaderRouter: Router = (() => { const router = express.Router(); + router.use(apiKeyHandler); + articleReaderRegistry.registerPath({ method: 'get', path: '/content', tags: ['Article Reader'], + request: { + headers: [apiKeyHeader], + }, responses: createApiResponse(ArticleReaderSchema, 'Success'), }); @@ -88,7 +95,10 @@ export const articleReaderRouter: Router = (() => { } catch (error) { console.error(`Error fetching content ${(error as Error).message}`); const errorMessage = `Error fetching content $${(error as Error).message}`; - return new ServiceResponse(ResponseStatus.Failed, errorMessage, null, StatusCodes.INTERNAL_SERVER_ERROR); + handleServiceResponse( + new ServiceResponse(ResponseStatus.Failed, errorMessage, null, StatusCodes.INTERNAL_SERVER_ERROR), + res + ); } }); diff --git a/src/routes/smtpMail/smtpMailModel.ts b/src/routes/smtpMail/smtpMailModel.ts new file mode 100644 index 0000000..3156756 --- /dev/null +++ b/src/routes/smtpMail/smtpMailModel.ts @@ -0,0 +1,58 @@ +import { extendZodWithOpenApi } from '@asteasolutions/zod-to-openapi'; +import { z } from 'zod'; + +extendZodWithOpenApi(z); + +export type SmtpRequest = z.infer; + +export const SmtpRequestSchema = z + .object({ + host: z.string().openapi({ + description: 'SMPT email host', + example: 'smtp.example.com', + }), + port: z.number().openapi({ + description: 'SMPT port', + example: 465, + }), + secure: z.boolean().optional().default(false).openapi({ + description: 'Use `true` for port 465, `false` for all other ports', + }), + auth: z.object({ + username: z.string(), + password: z.string(), + }), + mailContent: z.object({ + from: z.string().openapi({ + example: 'admin@typingmind.com, dev@typingmind.com', + description: `The e-mail address of the sender. All e-mail addresses can be plain 'sender@server.com' or formatted 'Sender Name '`, + }), + to: z.string().openapi({ + example: 'admin@typingmind.com, dev@typingmind.com', + description: 'Comma separated list of recipients e-mail addresses that will appear on the To: field', + }), + subject: z.string(), + text: z.string().optional(), + html: z.string().optional(), + cc: z.string().optional().openapi({ + example: 'admin@typingmind.com, dev@typingmind.com', + description: 'Comma separated list of recipients e-mail addresses that will appear on the Cc: field', + }), + bcc: z.string().optional().openapi({ + example: 'admin@typingmind.com, dev@typingmind.com', + description: 'Comma separated list recipients e-mail addresses that will appear on the Bcc: field', + }), + }), + }) + .openapi({ + description: 'Send SMPT Email Request', + }); + +export type SmtpResponse = z.infer; + +export const SmtpResponseSchema = z + .object({ + messageId: z.string().optional(), + response: z.string().optional(), + }) + .optional(); diff --git a/src/routes/smtpMail/smtpMailRouter.ts b/src/routes/smtpMail/smtpMailRouter.ts new file mode 100644 index 0000000..1f59f47 --- /dev/null +++ b/src/routes/smtpMail/smtpMailRouter.ts @@ -0,0 +1,87 @@ +import { OpenAPIRegistry } from '@asteasolutions/zod-to-openapi'; +import express, { Request, Response, Router } from 'express'; +import { StatusCodes } from 'http-status-codes'; +import nodemailer from 'nodemailer'; + +import { apiKeyHeader } from '@/api-docs/openAPIHeaderBuilders'; +import { createApiResponse } from '@/api-docs/openAPIResponseBuilders'; +import { apiKeyHandler } from '@/common/middleware/apiKeyHandler'; +import { ResponseStatus, ServiceResponse } from '@/common/models/serviceResponse'; +import { handleServiceResponse, validateRequestBody } from '@/common/utils/httpHandlers'; + +import { SmtpRequest, SmtpRequestSchema, SmtpResponseSchema } from './smtpMailModel'; + +export const smtpMailRegistry = new OpenAPIRegistry(); +smtpMailRegistry.register('SmtpRequest', SmtpRequestSchema); +smtpMailRegistry.register('SmtpResponse', SmtpResponseSchema); + +smtpMailRegistry.registerPath({ + method: 'post', + path: '/send-smtp-mail', + tags: ['SMTP Mail'], + request: { + headers: [apiKeyHeader], + body: { + content: { + 'application/json': { + schema: SmtpRequestSchema, + }, + }, + }, + }, + responses: { + ...createApiResponse(SmtpResponseSchema, 'OK'), + ...createApiResponse(SmtpResponseSchema, 'Please input your x-api-key', StatusCodes.UNAUTHORIZED), + }, +}); + +export const smtpMailRouter: Router = (() => { + const router = express.Router(); + + router.use(apiKeyHandler); + + router.post('/', validateRequestBody(SmtpRequestSchema), async (_req: Request, res: Response) => { + const { auth, host, port, secure, mailContent }: SmtpRequest = _req.body; + try { + const transporter = nodemailer.createTransport({ + auth: { + user: auth.username, + pass: auth.password, + }, + port: port, + host: host, + secure: secure, + }); + + const mailOptions = { + from: mailContent.from, + to: mailContent.to, + subject: mailContent.subject || '', + text: mailContent.text || '', + html: mailContent.html || '', + cc: mailContent.cc, + bcc: mailContent.bcc, + }; + + const info = await transporter.sendMail(mailOptions); + const serviceResponse = new ServiceResponse( + ResponseStatus.Success, + 'Your mail is sent!', + { + messageId: info.messageId, + response: info.response, + }, + StatusCodes.OK + ); + handleServiceResponse(serviceResponse, res); + } catch (error) { + console.log(`${(error as Error).stack}`); + const errorMessage = `Error sending your email: ${(error as Error).message}`; + handleServiceResponse( + new ServiceResponse(ResponseStatus.Failed, errorMessage, null, StatusCodes.INTERNAL_SERVER_ERROR), + res + ); + } + }); + return router; +})(); diff --git a/src/routes/youtubeTranscript/transcriptRouter.ts b/src/routes/youtubeTranscript/transcriptRouter.ts index 8597bc7..1e28f47 100644 --- a/src/routes/youtubeTranscript/transcriptRouter.ts +++ b/src/routes/youtubeTranscript/transcriptRouter.ts @@ -3,7 +3,9 @@ import express, { Request, Response, Router } from 'express'; import { StatusCodes } from 'http-status-codes'; import { YoutubeTranscript } from 'youtube-transcript'; +import { apiKeyHeader } from '@/api-docs/openAPIHeaderBuilders'; import { createApiResponse } from '@/api-docs/openAPIResponseBuilders'; +import { apiKeyHandler } from '@/common/middleware/apiKeyHandler'; import { ResponseStatus, ServiceResponse } from '@/common/models/serviceResponse'; import { handleServiceResponse } from '@/common/utils/httpHandlers'; @@ -15,10 +17,15 @@ transcriptRegistry.register('Transcript', TranscriptSchema); export const transcriptRouter: Router = (() => { const router = express.Router(); + router.use(apiKeyHandler); + transcriptRegistry.registerPath({ method: 'get', path: '/transcript', tags: ['Youtube Transcript'], + request: { + headers: [apiKeyHeader], + }, responses: createApiResponse(TranscriptSchema, 'Success'), }); @@ -46,7 +53,10 @@ export const transcriptRouter: Router = (() => { handleServiceResponse(serviceResponse, res); } catch (error) { const errorMessage = `Error fetching transcript $${(error as Error).message}`; - return new ServiceResponse(ResponseStatus.Failed, errorMessage, null, StatusCodes.INTERNAL_SERVER_ERROR); + handleServiceResponse( + new ServiceResponse(ResponseStatus.Failed, errorMessage, null, StatusCodes.INTERNAL_SERVER_ERROR), + res + ); } }); return router; diff --git a/src/server.ts b/src/server.ts index 806cef3..4116373 100644 --- a/src/server.ts +++ b/src/server.ts @@ -11,6 +11,7 @@ import requestLogger from '@/common/middleware/requestLogger'; import { healthCheckRouter } from '@/routes/healthCheck/healthCheckRouter'; import { articleReaderRouter } from './routes/articleReader/articleReaderRouter'; +import { smtpMailRouter } from './routes/smtpMail/smtpMailRouter'; import { transcriptRouter } from './routes/youtubeTranscript/transcriptRouter'; const logger = pino({ name: 'server start' }); const app: Express = express(); @@ -36,6 +37,8 @@ app.use('/health-check', healthCheckRouter); app.use('/images', express.static('public/images')); app.use('/transcript', transcriptRouter); app.use('/get-content', articleReaderRouter); +app.use('/send-smtp-mail', smtpMailRouter); + // Swagger UI app.use(openAPIRouter); From 2bc314bac2345b0d009835f35c692e4cfe95392e Mon Sep 17 00:00:00 2001 From: Duy Nguyen Date: Mon, 10 Jun 2024 19:15:52 +0700 Subject: [PATCH 2/2] chore: remove typings install --- package.json | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/package.json b/package.json index faf5550..5e4177c 100644 --- a/package.json +++ b/package.json @@ -15,9 +15,7 @@ "test:watch": "vitest", "test:coverage": "vitest --coverage", "prepare": "husky install", - "release": "release-it", - "postinstall": "npm run typings", - "typings": "typings install" + "release": "release-it" }, "dependencies": { "@asteasolutions/zod-to-openapi": "^6.4.0",