From d1403ed700d795dcea81787155e8b1e1bdd572ee Mon Sep 17 00:00:00 2001 From: NIYOMUGABO BERNARD <85235653+niyobern@users.noreply.github.com> Date: Thu, 2 May 2024 13:27:44 +0000 Subject: [PATCH] feat: Created email templates for account confirmation and password reset --- package-lock.json | 79 +++++++++++++++++++++- package.json | 2 + src/emails/index.ts | 62 ++++++++++++++++++ src/emails/templates/confirm.html | 105 ++++++++++++++++++++++++++++++ src/emails/templates/reset.html | 105 ++++++++++++++++++++++++++++++ 5 files changed, 352 insertions(+), 1 deletion(-) create mode 100644 src/emails/index.ts create mode 100644 src/emails/templates/confirm.html create mode 100644 src/emails/templates/reset.html diff --git a/package-lock.json b/package-lock.json index 087b8c1e..d947c2e9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,10 +9,12 @@ "version": "1.0.0", "license": "ISC", "dependencies": { + "axios": "^1.6.8", "check-code-coverage": "^1.10.5", "cors": "^2.8.5", "dotenv": "^16.4.5", "express": "^4.19.2", + "handlebars": "^4.7.8", "jest": "^29.7.0", "morgan": "^1.10.0", "nodemon": "^3.1.0", @@ -2222,6 +2224,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/axios": { + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz", + "integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/babel-jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", @@ -4042,6 +4054,25 @@ "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", "dev": true }, + "node_modules/follow-redirects": { + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/for-each": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", @@ -4389,6 +4420,26 @@ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true }, + "node_modules/handlebars": { + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, "node_modules/has-bigints": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", @@ -5859,7 +5910,6 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -5951,6 +6001,11 @@ "node": ">= 0.6" } }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" + }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -6685,6 +6740,11 @@ "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/pstree.remy": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", @@ -8173,6 +8233,18 @@ "node": ">=14.17" } }, + "node_modules/uglify-js": { + "version": "3.17.4", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", + "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/unbox-primitive": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", @@ -8365,6 +8437,11 @@ "node": ">=0.10.0" } }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==" + }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", diff --git a/package.json b/package.json index cb7e167c..3005f1e1 100644 --- a/package.json +++ b/package.json @@ -24,10 +24,12 @@ }, "homepage": "https://github.com/atlp-rwanda/dynamites-ecomm-be#readme", "dependencies": { + "axios": "^1.6.8", "check-code-coverage": "^1.10.5", "cors": "^2.8.5", "dotenv": "^16.4.5", "express": "^4.19.2", + "handlebars": "^4.7.8", "jest": "^29.7.0", "morgan": "^1.10.0", "nodemon": "^3.1.0", diff --git a/src/emails/index.ts b/src/emails/index.ts new file mode 100644 index 00000000..de6ed64e --- /dev/null +++ b/src/emails/index.ts @@ -0,0 +1,62 @@ +import axios from 'axios'; +import handlebars from 'handlebars'; +import fs from 'fs'; + + +type EmailType = 'confirm' | 'reset'; +type Data = { + name: string; + link: string; +}; +/** + * Sends an email of the specified type to the recipient using the provided data. + * + * @param emailType - The type of email to send. Must be either "confirm" or "reset". + * @param recipient - The email address of the recipient. + * @param data - The data to be used for generating the email content. A name and link are required. + * @returns A Promise that resolves to the response from the email service. + * @throws An error if there is an issue sending the email. + */ +async function sendEmail(emailType: EmailType, recipient: string, data: Data) { + const templatePath = `/workspaces/dynamites-ecomm-be/src/emails/templates/${emailType}.html`; + try { + // Read the Handlebars template file + const templateFile = fs.readFileSync(templatePath, 'utf-8'); + + // Compile the template + const template = handlebars.compile(templateFile); + + // Generate the HTML content using the template and data + const html = template(data); + + // Send the Email + + const domain = process.env.MAILERSEND_DOMAIN + const key = process.env.MAILERSEND_TOKEN + const body = { + 'from': { + 'email': `info@${domain}`, + }, + 'to': [ + { + 'email': recipient + } + ], + 'subject': 'Verification Email', + 'html': html + } + const mailersend = 'https://api.mailersend.com/v1/email' + const response = await axios.post(mailersend, body, { + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${key}` + } + }); + return response + } catch (error) { + // console.error('Error sending email:', error); + throw new Error('Error sending email'); + } +} + +export default sendEmail; diff --git a/src/emails/templates/confirm.html b/src/emails/templates/confirm.html new file mode 100644 index 00000000..e146a65a --- /dev/null +++ b/src/emails/templates/confirm.html @@ -0,0 +1,105 @@ + + +
+ + + + + + + +