From 98da4e513ba38a2ce4c4007187681b276d22c0bf Mon Sep 17 00:00:00 2001 From: Lyton Date: Mon, 11 Nov 2024 09:54:06 -0600 Subject: [PATCH] =?UTF-8?q?=F0=9F=93=A8=20feat:=20implemented=20user=20inv?= =?UTF-8?q?ite=20with=20sendgrid=20(#3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: implemented user invite with sendgrid --- api/.env.example | 16 +++ api/assets/pww-logo.png | Bin 0 -> 22150 bytes api/package-lock.json | 212 ++++++++++++++++++++++++++++++++++++++-- api/package.json | 7 +- api/src/routes/user.ts | 29 ++++++ api/src/server.ts | 9 +- 6 files changed, 259 insertions(+), 14 deletions(-) create mode 100644 api/.env.example create mode 100644 api/assets/pww-logo.png diff --git a/api/.env.example b/api/.env.example new file mode 100644 index 0000000..b167564 --- /dev/null +++ b/api/.env.example @@ -0,0 +1,16 @@ +## copy this file and rename it to .env. Fill in the values from Notion + +# server +PORT=8000 + +# email service +SEND_GRID_API_KEY=##check notion## +SEND_GRID_TEST_EMAIL=##check notion## + +# database +MONGO_URI=##check notion## + +# backend auth +CLIENT_ORIGIN_URL=http://localhost:4040 +AUTH0_AUDIENCE=##check notion## +AUTH0_DOMAIN=##check notion## diff --git a/api/assets/pww-logo.png b/api/assets/pww-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..c0f5a066c7ac226d9a665d8574c106dc83fb18e4 GIT binary patch literal 22150 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.*" + } + }, + "node_modules/@sendgrid/helpers": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@sendgrid/helpers/-/helpers-8.0.0.tgz", + "integrity": "sha512-Ze7WuW2Xzy5GT5WRx+yEv89fsg/pgy3T1E3FS0QEx0/VvRmigMZ5qyVGhJz4SxomegDkzXv/i0aFPpHKN8qdAA==", + "dependencies": { + "deepmerge": "^4.2.2" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/@sendgrid/mail": { + "version": "8.1.4", + "resolved": "https://registry.npmjs.org/@sendgrid/mail/-/mail-8.1.4.tgz", + "integrity": "sha512-MUpIZykD9ARie8LElYCqbcBhGGMaA/E6I7fEcG7Hc2An26QJyLtwOaKQ3taGp8xO8BICPJrSKuYV4bDeAJKFGQ==", + "dependencies": { + "@sendgrid/client": "^8.1.4", + "@sendgrid/helpers": "^8.0.0" + }, + "engines": { + "node": ">=12.*" + } + }, "node_modules/@tsconfig/node10": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", @@ -153,11 +189,11 @@ "dev": true }, "node_modules/@types/node": { - "version": "22.5.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.4.tgz", - "integrity": "sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg==", + "version": "22.8.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.8.1.tgz", + "integrity": "sha512-k6Gi8Yyo8EtrNtkHXutUu2corfDf9su95VYVP10aGYMMROM6SAItZi0w1XszA6RtWTHSVp5OeFof37w0IEqCQg==", "dependencies": { - "undici-types": "~6.19.2" + "undici-types": "~6.19.8" } }, "node_modules/@types/qs": { @@ -278,6 +314,21 @@ "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.7.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", + "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -400,6 +451,17 @@ "fsevents": "~2.3.2" } }, + "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/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -462,6 +524,14 @@ "ms": "2.0.0" } }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/define-data-property": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", @@ -478,6 +548,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "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", @@ -636,6 +714,38 @@ "node": ">= 0.8" } }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", + "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "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", @@ -1274,6 +1384,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/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -1806,6 +1921,32 @@ "sparse-bitfield": "^3.0.3" } }, + "@sendgrid/client": { + "version": "8.1.4", + "resolved": "https://registry.npmjs.org/@sendgrid/client/-/client-8.1.4.tgz", + "integrity": "sha512-VxZoQ82MpxmjSXLR3ZAE2OWxvQIW2k2G24UeRPr/SYX8HqWLV/8UBN15T2WmjjnEb5XSmFImTJOKDzzSeKr9YQ==", + "requires": { + "@sendgrid/helpers": "^8.0.0", + "axios": "^1.7.4" + } + }, + "@sendgrid/helpers": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@sendgrid/helpers/-/helpers-8.0.0.tgz", + "integrity": "sha512-Ze7WuW2Xzy5GT5WRx+yEv89fsg/pgy3T1E3FS0QEx0/VvRmigMZ5qyVGhJz4SxomegDkzXv/i0aFPpHKN8qdAA==", + "requires": { + "deepmerge": "^4.2.2" + } + }, + "@sendgrid/mail": { + "version": "8.1.4", + "resolved": "https://registry.npmjs.org/@sendgrid/mail/-/mail-8.1.4.tgz", + "integrity": "sha512-MUpIZykD9ARie8LElYCqbcBhGGMaA/E6I7fEcG7Hc2An26QJyLtwOaKQ3taGp8xO8BICPJrSKuYV4bDeAJKFGQ==", + "requires": { + "@sendgrid/client": "^8.1.4", + "@sendgrid/helpers": "^8.0.0" + } + }, "@tsconfig/node10": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", @@ -1891,11 +2032,11 @@ "dev": true }, "@types/node": { - "version": "22.5.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.4.tgz", - "integrity": "sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg==", + "version": "22.8.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.8.1.tgz", + "integrity": "sha512-k6Gi8Yyo8EtrNtkHXutUu2corfDf9su95VYVP10aGYMMROM6SAItZi0w1XszA6RtWTHSVp5OeFof37w0IEqCQg==", "requires": { - "undici-types": "~6.19.2" + "undici-types": "~6.19.8" } }, "@types/qs": { @@ -2001,6 +2142,21 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "axios": { + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", + "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", + "requires": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -2089,6 +2245,14 @@ "readdirp": "~3.6.0" } }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -2139,6 +2303,11 @@ "ms": "2.0.0" } }, + "deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==" + }, "define-data-property": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", @@ -2149,6 +2318,11 @@ "gopd": "^1.0.1" } }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" + }, "depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -2270,6 +2444,21 @@ "unpipe": "~1.0.0" } }, + "follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==" + }, + "form-data": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", + "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + }, "forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -2661,6 +2850,11 @@ "ipaddr.js": "1.9.1" } }, + "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==" + }, "punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", diff --git a/api/package.json b/api/package.json index a4c2d8f..de25796 100644 --- a/api/package.json +++ b/api/package.json @@ -2,10 +2,10 @@ "name": "api", "version": "1.0.0", "description": "", - "main": "src/index.ts", + "main": "src/server.ts", "scripts": { "build": "tsc", - "start": "node src/index.ts", + "start": "nodemon ./dist/server.js", "dev": "nodemon ./src/server.ts", "test": "echo \"Error: no test specified\" && exit 1", "prettier": "prettier --single-quote --write 'src/**/*.{js,ts}'", @@ -15,6 +15,7 @@ "author": "", "license": "ISC", "dependencies": { + "@sendgrid/mail": "^8.1.4", "cors": "^2.8.5", "dotenv": "^16.4.5", "express": "^4.21.0", @@ -26,7 +27,7 @@ "devDependencies": { "@types/cors": "^2.8.17", "@types/express": "^4.17.21", - "@types/node": "^22.5.4", + "@types/node": "^22.8.1", "@types/uuid": "^10.0.0", "body-parser": "^1.20.3", "prettier": "^3.3.3", diff --git a/api/src/routes/user.ts b/api/src/routes/user.ts index a734b81..ecfa60e 100644 --- a/api/src/routes/user.ts +++ b/api/src/routes/user.ts @@ -1,6 +1,7 @@ import express from "express"; import mongoose from "mongoose"; import dbConnect from "../config/db"; +import sgMail from "@sendgrid/mail"; const router = express.Router(); @@ -71,4 +72,32 @@ router.post("/test", async (req: any, res: any) => { return res.status(200).json({ name }); }); +router.post("/send-email", async (req: any, res: any) => { + try { + const SENDGRID_API_KEY = process.env.SEND_GRID_API_KEY || ""; + const SEND_GRID_TEST_EMAIL = process.env.SEND_GRID_TEST_EMAIL || ""; + + if (SENDGRID_API_KEY === "" || SEND_GRID_TEST_EMAIL === "") { + throw new Error("SendGrid API key or test email is missing"); + } + + const { email, name } = req.body; + + sgMail.setApiKey(SENDGRID_API_KEY); + + await sgMail.send({ + to: email, + from: SEND_GRID_TEST_EMAIL, + templateId: "d-7e26b82cf8624bafa4077b6ed73b52bf", + dynamicTemplateData: { + name: name, + }, + }); + + return res.status(200).json({ message: "Email successfully sent" }); + } catch (err) { + return res.status(400).json({ message: "Email sending failed" }); + } +}); + export default router; diff --git a/api/src/server.ts b/api/src/server.ts index cd7a639..c3f6ace 100644 --- a/api/src/server.ts +++ b/api/src/server.ts @@ -1,8 +1,11 @@ +import dotenv from "dotenv"; +dotenv.config({ path: path.resolve(__dirname, "../.env") }); + import express from "express"; import bodyParser from "body-parser"; import connectDB from "./config/db"; - import * as routes from "./routes/index"; +import path from "path"; var cors = require("cors"); @@ -16,4 +19,6 @@ app.use("/workshop", routes.workshop); connectDB(); -app.listen(process.env.PORT || 8000, () => console.log("Server running...")); +app.listen(process.env.PORT || 8000, () => + console.log(`Server running on port ${process.env.PORT || 8000}`), +);