diff --git a/.gitignore b/.gitignore
index a48bd98..89f6ea7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -39,3 +39,4 @@ jspm_packages/
.env
.env.test
.env.local
+sendgrid.env
diff --git a/README.md b/README.md
index 56f693a..8403ae1 100644
--- a/README.md
+++ b/README.md
@@ -72,24 +72,24 @@ avatarUr(not required): string,
}
```
-| Method | URL | Description |
-| -------- | ------------------------- | --------------------------------------------------------------------------------- |
-| [GET] | /children | Returns an array containing all existing children.|
-| [POST] | /children | Requires a username, name, and age. Returns the name, profile_id, and parent_id.|
-| [GET] | /children/:child_id | Returns the child with the given 'id'.|
-| [PUT] | /children/:child_id | Returns the updated child object|
-| [DELETE] | /children/:child_id | Returns the name of the child deleted|
-| [GET] | /children/:child_id/enrollments | Returns an array filled with event objects with the specified `id`. |
-| [POST] | /children/:child_id/enrollments | Returns the event object with the specified `id`. Enrolls a student. |
-| [PUT] | /children/enrollments/ | Returns the event object with the specified `id`. Updates a student's enrollments. (Not Implemented)|
-| [DELETE] | /children/enrollments/:id | Returns the event object with the specified `id`. Unenrolls student from course. (Not Implemented)|
-
+| Method | URL | Description |
+| -------- | ------------------------------- | ----------------------------------------------------------------------------------------------------------- |
+| [GET] | /children | Returns an array containing all existing children. |
+| [POST] | /children | Requires a username, name, and age. Returns the name, profile_id, and parent_id. |
+| [GET] | /children/:child_id | Returns the child with the given 'id'. |
+| [PUT] | /children/:child_id | Returns the updated child object |
+| [DELETE] | /children/:child_id | Returns the name of the child deleted |
+| [GET] | /children/:child_id/enrollments | Returns an array filled with event objects with the specified `id`. |
+| [POST] | /children/:child_id/enrollments | Returns the event object with the specified `id`. Enrolls a student. |
+| [PUT] | /children/enrollments/ | Returns the event object with the specified `id`. Updates a student's enrollments. (Not Implemented) |
+| [DELETE] | /children/enrollments/:id | Returns the event object with the specified `id`. Unenrolls student from course. (Not Implemented) |
Instructors
```
{
instructor_id: INCREMENT (primary key, auto-increments, generated by database),
+ instructor_name: STRING (required),
rating: INTEGER (required),
availability: STRING (optional),
bio: STRING (required),
@@ -140,6 +140,7 @@ avatarUr(not required): string,
end_date: DATE (required),
location: STRING (required),
number_of_sessions: INTEGER (required),
+ instructor_name: STRING (required)
}
```
@@ -147,7 +148,7 @@ avatarUr(not required): string,
| -------- | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------- |
| [GET] | /course | Returns an array containing all course objects |
| [GET] | /course/:course_id | Returns the course object with the specified `course_id`. |
-| [POST] | /course | --needs to be fleshed out-- |
+| [POST] | /course | --needs to be fleshed out-- |
| [PUT] | /course/:course_id | Updates and returns the updated course object with the specified `course_id`. |
| [DELETE] | /course/:course_id | Deletes the course object with the specified `course_id` and returns a message containing the deleted course_id on successful deletion |
@@ -180,8 +181,8 @@ avatarUr(not required): string,
}
```
-| Method | URL | Description |
-| -------- | ------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
+| Method | URL | Description |
+| -------- | ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| [GET] | /conversation_id/ | Returns an array filled with inbox event objects. |
| [GET] | /conversation_id/:profile_id/ | Retrieves an inbox with the specified inbox_id BUG(?): incorrectly labeled as profile_id in codebase rather than inbox_id |
| [POST] | /conversation_id/ | Creates an inbox and returns the newly created inbox. |
@@ -300,3 +301,47 @@ Visual Database Schema: https://dbdesigner.page.link/WTZRbVeTR7EzLvs86 \*Curr
[Loom Video PT4](https://www.loom.com/share/7da5fc043d3149afb05876c28df9bd3d)
+
+Email Service
+In api/email, the emailHelper.js file contains the following functions: sendEmail and addToList. SendEmail sends an API request to SendGrid to send a templated email to the email address its given. The other function: addToList adds the email and name to a specified SendGrid contact list (they're added to all no matter what, then also to the specified list id). Any function that wants to use sendEmail and addToList will need to import it into their file (see profileRouter.js). Parameters can be passed into it, like the toEmail, name, template_id or list_ids (which is an array so it could have more than 1 list_ids there). The parameters to use are created in the file that's calling the sendEmail function.
+
+SendGrid's npm package info and how to set up the API key (also listed below): https://www.npmjs.com/package/@sendgrid/mail. The npm package is @sendgrid/mail. Very lightweight, highly supported, and heavily downloaded. (SendGrid also supports Java)
+
+Loom walkthrough on backend: https://www.loom.com/share/a7d867ee6f9f4bca9a4a3e911bca3de4
+Loom how to see if it's working: https://www.loom.com/share/7acaa082c8454ac4bf17e0670aec18d7
+
+To set up SendGrid at https://sendgrid.com/:
+
+- Create an account with SendGrid
+- Create an API (replace YOUR_API_KEY with the API you receive from SendGrid into the below terminal command)
+- In your terminal, do these commands:
+ echo "export SENDGRID_API_KEY='YOUR_API_KEY'" > sendgrid.env
+ echo "sendgrid.env" >> .gitignore
+ source ./sendgrid.env
+
+SendGrid will look at its own .env file for the API key. Unclear as to whether it's possible to put other environment variables there - it's been inconsistent in testing. (Send new keys via the first and third commands listed above. The second one only adds sendgrid.env to .gitignore so you don't share secrets.) When accessing SendGrid's .env file, use process.env.SENDGRID_API_KEY, just as if you had put the key in the .env file.
+
+Templates exist on SendGrid's site, under "Dynamic Templates." When requesting an email be sent, you'll use the template_id as one of the parameters you pass on to sendEmail. If you want a starting spot for what your emails could say (pending stakeholder approval), here's some copy: https://docs.google.com/document/d/1WZQ6Njj0Xt_eXLAEWm0nYA7eqACByFinpyrh7EjcU8U/edit?usp=sharing
+
+Dynamic template data: the template will be looking for something from the json request that matches the field you put in the template. For example, "name": "Some Name" coming from your email request will match {{ name }} in the template. Double curly braces make that connection.
+
+Potential templates to be created plus an idea for what the template_id will look like (could update this list as the templates are created on SendGrid - the template_id is generated by SendGrid when you create a Dynamic Template):
+
+EmailType = {
+WELCOME_EMAIL_PARENT: 'd-026a2f461bdd480098be08a2cb949eea', (All 3 welcome emails go out automatically upon post from the profileRouter. Change template id based on role_id.)
+WELCOME_EMAIL_INSTRUCTOR: 'd-a4de80911362438bb35d481efa068398',
+WELCOME_EMAIL_STUDENT: 'd-026a2f461bdd480098be08a2cb949eea',
+INSTRUCTOR_SUBMITTED_APPLICATION: 'd-026a2f461bdd480098be08bxhsyeyyy', (Goes out when an instructor submits an application and lets them know about the next step in the approval process.)
+INSTRUCTOR_APPLICATION_REVIEW: 'd-026a2f461bdd480098be08bxhsyeyyy', email to admin or staff that an application is ready to be approved or denied
+CHANGE_PASSWORD: 'd-026a2f4hsyw20098be08a2cb949eea',
+PURCHASED_COURSE_PARENT: 'd-026a2f461bdd480098be08bxhsyeyyy', (Upon enrollment of a child student, to include the start date, time, and Zoom link)
+PURCHASED_COURSE_STUDENT: 'd-026a2f461bdd480098be08bxhsyeyyy', (Upon enrollment of a child student, email sent to student to include the start date, time, and Zoom link)
+INSTRUCTOR_IS_APPROVED: 'd-026a2f461bdd480098be08bxhsyeyyy', (Congrats, you've been approved, with link to create a course)
+INSTRUCTOR_IS_REJECTED: 'd-026a2f461bdd480098be08bxhsyeyyy', (Sorry, friend! We'll keep your info on file for future needs)
+STUDENT_CLASS_REMINDER: 'd-026a2f461bdd480098be08bxhsyeyyy', (Could be 2 emails to land 1 hour, or 5 minutes before class starts, with the class start time and Zoom link)
+PARENT_CLASS_REMINDER: 'd-026a2f461bdd480098be08bxhsyeyyy', (Same as above with different wording)
+};
+
+AddToList: First up , add list(s) to the SendGrid account. In addition to the default "all" list, we added instructors, parents, and students. From a marketing perspective, these are the major groups, each requiring a different kind of information, and will potentially need different mass email types.
+
+As part of the request in emailHelper.js, you'll send the id(s) of the contact list (list_ids, an array) you want to add the email to, plus any additional data you want to add to the request, like the email address (as 'email') and name (as 'first_name'). If you use custom fields, make sure the fields are already set up in SendGrid or SG won't know where to map them.
diff --git a/__tests__/routes/emailHelper.test.js b/__tests__/routes/emailHelper.test.js
new file mode 100644
index 0000000..5862a39
--- /dev/null
+++ b/__tests__/routes/emailHelper.test.js
@@ -0,0 +1,105 @@
+const express = require('express');
+
+const { sendEmail, addToList } = require('../../api/email/emailHelper');
+const server = express();
+
+server.use(express.json());
+
+jest.mock('../../api/email/emailHelper.js');
+jest.mock('../../api/email/emailHelper.js', () =>
+ jest.fn((req, res, next) => next())
+);
+
+const newStudent = {
+ dynamic_template_data: {
+ name: 'New Student Here',
+ },
+ to: 'someperson@somewhere.com',
+ // The from email must be the email address of a verified sender in SendGrid account. If/when you verify the domain, an email coming from the domain is likely good enough.
+ from: 'someperson@somewhere.com',
+ template_id: 'd-a6dacc6241f9484a96554a13bbdcd971',
+};
+
+const newParent = {
+ dynamic_template_data: {
+ name: 'New Parent Here',
+ },
+ to: 'someperson@somewhere.com',
+ from: 'someperson@somewhere.com',
+ template_id: 'd-19b895416ae74cea97e285c4401fcc1f',
+};
+
+const newInstructor = {
+ dynamic_template_data: {
+ name: 'New Parent Here',
+ },
+ to: 'someperson@somewhere.com',
+ from: 'someperson@somewhere.com',
+ template_id: 'd-a4de80911362438bb35d481efa068398',
+};
+
+const newStudentContact = {
+ list_ids: ['e7b598d9-23ca-48df-a62b-53470b5d1d86'],
+ email: 'someperson@somewhere.com',
+ name: 'new student firstname',
+};
+
+const newParentContact = {
+ list_ids: ['e7b598d9-23ca-48df-a62b-53470b5d1d86'],
+ email: 'someperson@somewhere.com',
+ name: 'new parent firstname',
+};
+
+const newInstructorContact = {
+ list_ids: ['e7b598d9-23ca-48df-a62b-53470b5d1d86'],
+ email: 'someperson@somewhere.com',
+ name: 'new instructor firstname',
+};
+
+describe('Send different email types', () => {
+ describe('Send an email to a new student', () => {
+ it('Should return 202 when it successfully posts a new student email to SendGrid', async () => {
+ const res = await sendEmail(newStudent);
+ expect(res.status).toBe(202);
+ expect(res.headers.date.length).not.toBe(0);
+ });
+ });
+ describe('Send an email to a new parent', () => {
+ it('Should return 202 when it successfully posts a new parent email to SendGrid', async () => {
+ const res = await sendEmail(newParent);
+ expect(res.status).toBe(202);
+ expect(res.headers.date.length).not.toBe(0);
+ });
+ });
+ describe('Send an email to a new instructor', () => {
+ it('Should return 202 when it successfully posts a new instructor email to SendGrid', async () => {
+ const res = await sendEmail(newInstructor);
+ expect(res.status).toBe(202);
+ expect(res.headers.date.length).not.toBe(0);
+ });
+ });
+});
+
+describe('Add users to a contact list on SendGrid', () => {
+ describe('Add a new student to a contact list', () => {
+ it('Should return 202 when it successfully adds a new student to a contact list', async () => {
+ const res = await addToList(newStudentContact);
+ expect(res.status).toBe(202);
+ expect(res.headers.date.length).not.toBe(0);
+ });
+ });
+ describe('Add a new parent to a contact list', () => {
+ it('Should return 202 when it successfully adds a new parent to a contact list', async () => {
+ const res = await addToList(newParentContact);
+ expect(res.status).toBe(202);
+ expect(res.headers.date.length).not.toBe(0);
+ });
+ });
+ describe('Add a new instructor to a contact list', () => {
+ it('Should return 202 when it successfully adds a new instructor to a contact list', async () => {
+ const res = await addToList(newInstructorContact);
+ expect(res.status).toBe(202);
+ expect(res.headers.date.length).not.toBe(0);
+ });
+ });
+});
diff --git a/api/courses/coursesModel.js b/api/courses/coursesModel.js
index b414949..5c6891c 100644
--- a/api/courses/coursesModel.js
+++ b/api/courses/coursesModel.js
@@ -2,7 +2,7 @@ const db = require('../../data/db-config');
const getAllCourses = async () => {
return await db('courses as c')
- .select('c.*', 'p.program_name', 'i.instructor_id')
+ .select('c.*', 'p.program_name', 'i.instructor_id', 'i.instructor_name')
.leftJoin('programs as p', 'p.program_id', 'c.program_id')
.leftJoin('instructors as i', 'c.instructor_id', 'i.instructor_id');
};
diff --git a/api/email/emailHelper.js b/api/email/emailHelper.js
new file mode 100644
index 0000000..a9e342f
--- /dev/null
+++ b/api/email/emailHelper.js
@@ -0,0 +1,41 @@
+const sgMail = require('@sendgrid/mail');
+sgMail.setApiKey(process.env.SENDGRID_API_KEY);
+
+const sendEmail = (data) => {
+ sgMail
+ .send(data)
+ .then((response) => {
+ console.log(JSON.stringify(response));
+ })
+ .catch((error) => {
+ console.error(error);
+ });
+};
+
+const addToList = (data) => {
+ let request = require('request');
+ let options = {
+ method: 'PUT',
+ url: 'https://api.sendgrid.com/v3/marketing/contacts',
+ headers: {
+ 'content-type': 'application/json',
+ authorization: 'Bearer ' + process.env.SENDGRID_API_KEY,
+ },
+ body: {
+ list_ids: data.list_ids,
+ contacts: [
+ {
+ email: data.email,
+ first_name: data.name,
+ },
+ ],
+ },
+ json: true,
+ };
+ request(options, function (error, response, body) {
+ if (error) throw new Error(error);
+ console.log(body);
+ });
+};
+
+module.exports = { sendEmail, addToList };
diff --git a/api/parent/parentRouter.js b/api/parent/parentRouter.js
index 8218cf4..15c2e62 100644
--- a/api/parent/parentRouter.js
+++ b/api/parent/parentRouter.js
@@ -1,8 +1,64 @@
const express = require('express');
const authRequired = require('../middleware/authRequired');
+const {
+ roleAuthenticationParent,
+} = require('../middleware/roleAuthentication.js');
+const {
+ checkChildObject,
+ checkChildExist,
+} = require('../children/ChildrenMiddleware');
const Parents = require('./parentModel');
+const Children = require('../children/childrenModel');
const router = express.Router();
+router.post(
+ '/',
+ authRequired,
+ roleAuthenticationParent,
+ checkChildObject,
+ async function (req, res, next) {
+ const { profile_id } = req.profile;
+ try {
+ let newChild = await Children.addChild(profile_id, req.body);
+ res.status(201).json(newChild);
+ } catch (error) {
+ next(error);
+ }
+ }
+);
+
+router.put(
+ '/:child_id',
+ authRequired,
+ roleAuthenticationParent,
+ checkChildExist,
+ async function (req, res, next) {
+ const { child_id } = req.params;
+ try {
+ let [updatedChild] = await Children.updateChild(child_id, req.body);
+ res.status(200).json(updatedChild);
+ } catch (error) {
+ next(error);
+ }
+ }
+);
+
+router.delete(
+ '/:child_id',
+ authRequired,
+ roleAuthenticationParent,
+ checkChildExist,
+ async function (req, res, next) {
+ const { child_id } = req.params;
+ try {
+ let { name } = await Children.removeChild(child_id);
+ res.status(200).json({ name });
+ } catch (error) {
+ next(error);
+ }
+ }
+);
+
router.get('/:profile_id/children', authRequired, function (req, res) {
const { profile_id } = req.params;
diff --git a/api/profile/profileRouter.js b/api/profile/profileRouter.js
index 403b499..b4dc980 100644
--- a/api/profile/profileRouter.js
+++ b/api/profile/profileRouter.js
@@ -2,6 +2,7 @@ const express = require('express');
const authRequired = require('../middleware/authRequired');
const ownerAuthorization = require('../middleware/ownerAuthorization');
const Profiles = require('./profileModel');
+const { sendEmail, addToList } = require('../email/emailHelper');
const router = express.Router();
const {
checkProfileObject,
@@ -159,7 +160,7 @@ router.get(
* @swagger
* /profile:
* post:
- * summary: Add a profile
+ * summary: Add a profile, send a welcome email, add to contact list (all by default, then specified per role)
* security:
* - okta: []
* tags:
@@ -193,23 +194,85 @@ router.get(
*/
router.post('/', checkProfileObject, async (req, res) => {
const profile = req.body;
- try {
- await Profiles.findById(profile.okta_id).then(async (pf) => {
- if (pf == undefined) {
- await Profiles.create(profile).then((profile) =>
- res
- .status(200)
- .json({ message: 'profile created', profile: profile[0] })
- );
- } else {
- res.status(400).json({ message: 'profile already exists' });
- }
- });
- } catch (e) {
- console.error(e);
- res.status(500).json({ message: e.message });
+ const profileExists = await Profiles.findById(profile.okta_id);
+ if (profileExists) {
+ res.status(400).json({ message: 'profile already exists' });
+ } else {
+ const newProfile = await Profiles.create(profile);
+ if (!newProfile) {
+ res.status(404).json({
+ message: 'There was an error saving the profile to the database.',
+ });
+ }
+ if (newProfile[0].role_id === 3 || newProfile[0].role_id === '3') {
+ const instructorWelcomeMessage = {
+ dynamic_template_data: {
+ name: newProfile[0].name,
+ },
+ to: newProfile[0].email,
+ from: 'someone@somewhere.com', // verified sender in SendGrid account. Try to put this in env - hardcoded here because it wasn't working there.
+ template_id: 'd-a4de80911362438bb35d481efa068398',
+ };
+ const instructorList = {
+ list_ids: ['e7b598d9-23ca-48df-a62b-53470b5d1d86'],
+ email: newProfile[0].email,
+ name: newProfile[0].name,
+ };
+ sendEmail(instructorWelcomeMessage);
+ addToList(instructorList);
+ res.status(200).json({
+ message: 'instructor profile created',
+ profile: newProfile[0],
+ });
+ } else if (newProfile[0].role_id === 4 || newProfile[0].role_id === '4') {
+ const parentWelcomeMessage = {
+ dynamic_template_data: {
+ name: newProfile[0].name,
+ },
+ to: newProfile[0].email,
+ from: 'someone@somewhere.com',
+ template_id: 'd-19b895416ae74cea97e285c4401fcc1f',
+ };
+ const parentList = {
+ list: 'e7b598d9-23ca-48df-a62b-53470b5d1d86',
+ email: newProfile[0].email,
+ name: newProfile[0].name,
+ };
+ sendEmail(parentWelcomeMessage);
+ addToList(parentList);
+ res.status(200).json({
+ message: 'parent profile created',
+ profile: newProfile[0],
+ });
+ } else if (newProfile[0].role_id === 5 || newProfile[0].role_id === '5') {
+ const studentWelcomeMessage = {
+ dynamic_template_data: {
+ name: newProfile[0].name,
+ },
+ to: newProfile[0].email,
+ from: 'someone@somewhere.com',
+ template_id: 'd-a6dacc6241f9484a96554a13bbdcd971',
+ };
+ const studentList = {
+ list: '4dd72555-266f-4f8e-b595-ecc1f7ff8f28',
+ email: newProfile[0].email,
+ name: newProfile[0].name,
+ };
+ sendEmail(studentWelcomeMessage);
+ addToList(studentList);
+ res.status(200).json({
+ message: 'parent profile created',
+ profile: newProfile[0],
+ });
+ } else {
+ res.status(200).json({
+ message: 'profile created',
+ profile: newProfile[0],
+ });
+ }
}
});
+
/**
* @swagger
* /profile:
diff --git a/data/migrations/20211102124801_profiles.js b/data/migrations/20211102124801_profiles.js
index f5bdff5..0d97779 100644
--- a/data/migrations/20211102124801_profiles.js
+++ b/data/migrations/20211102124801_profiles.js
@@ -56,6 +56,7 @@ exports.up = (knex) => {
.createTable('instructors', function (table) {
table.increments('instructor_id');
+ table.string('instructor_name').notNullable();
table.integer('rating').notNullable();
table.string('availability');
table.string('bio').notNullable();
diff --git a/data/seeds/004_instructors.js b/data/seeds/004_instructors.js
index 777b489..dfd0af4 100644
--- a/data/seeds/004_instructors.js
+++ b/data/seeds/004_instructors.js
@@ -2,6 +2,7 @@ exports.seed = function (knex) {
return knex('instructors').insert([
{
profile_id: 3,
+ instructor_name: 'Brianne Caplan',
rating: 2,
bio: 'I love spaghetti and code, but not the two together.',
status: false,
@@ -9,6 +10,7 @@ exports.seed = function (knex) {
},
{
profile_id: 8,
+ instructor_name: 'Adam Smith',
rating: 5,
bio: 'Coding is life.',
status: true,
diff --git a/package-lock.json b/package-lock.json
index 5c8f40d..5030950 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -11,6 +11,7 @@
"dependencies": {
"@okta/jwt-verifier": "^2.0.0",
"@okta/okta-sdk-nodejs": "^6.4.0",
+ "@sendgrid/mail": "^7.7.0",
"axios": "^0.21.1",
"cookie-parser": "~1.4.4",
"cors": "^2.8.5",
@@ -1355,6 +1356,49 @@
"node": ">=12.0"
}
},
+ "node_modules/@sendgrid/client": {
+ "version": "7.7.0",
+ "resolved": "https://registry.npmjs.org/@sendgrid/client/-/client-7.7.0.tgz",
+ "integrity": "sha512-SxH+y8jeAQSnDavrTD0uGDXYIIkFylCo+eDofVmZLQ0f862nnqbC3Vd1ej6b7Le7lboyzQF6F7Fodv02rYspuA==",
+ "dependencies": {
+ "@sendgrid/helpers": "^7.7.0",
+ "axios": "^0.26.0"
+ },
+ "engines": {
+ "node": "6.* || 8.* || >=10.*"
+ }
+ },
+ "node_modules/@sendgrid/client/node_modules/axios": {
+ "version": "0.26.1",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz",
+ "integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==",
+ "dependencies": {
+ "follow-redirects": "^1.14.8"
+ }
+ },
+ "node_modules/@sendgrid/helpers": {
+ "version": "7.7.0",
+ "resolved": "https://registry.npmjs.org/@sendgrid/helpers/-/helpers-7.7.0.tgz",
+ "integrity": "sha512-3AsAxfN3GDBcXoZ/y1mzAAbKzTtUZ5+ZrHOmWQ279AuaFXUNCh9bPnRpN504bgveTqoW+11IzPg3I0WVgDINpw==",
+ "dependencies": {
+ "deepmerge": "^4.2.2"
+ },
+ "engines": {
+ "node": ">= 6.0.0"
+ }
+ },
+ "node_modules/@sendgrid/mail": {
+ "version": "7.7.0",
+ "resolved": "https://registry.npmjs.org/@sendgrid/mail/-/mail-7.7.0.tgz",
+ "integrity": "sha512-5+nApPE9wINBvHSUxwOxkkQqM/IAAaBYoP9hw7WwgDNQPxraruVqHizeTitVtKGiqWCKm2mnjh4XGN3fvFLqaw==",
+ "dependencies": {
+ "@sendgrid/client": "^7.7.0",
+ "@sendgrid/helpers": "^7.7.0"
+ },
+ "engines": {
+ "node": "6.* || 8.* || >=10.*"
+ }
+ },
"node_modules/@sindresorhus/is": {
"version": "0.14.0",
"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz",
@@ -3157,7 +3201,6 @@
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz",
"integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==",
- "dev": true,
"engines": {
"node": ">=0.10.0"
}
@@ -13274,6 +13317,42 @@
"safe-flat": "^2.0.2"
}
},
+ "@sendgrid/client": {
+ "version": "7.7.0",
+ "resolved": "https://registry.npmjs.org/@sendgrid/client/-/client-7.7.0.tgz",
+ "integrity": "sha512-SxH+y8jeAQSnDavrTD0uGDXYIIkFylCo+eDofVmZLQ0f862nnqbC3Vd1ej6b7Le7lboyzQF6F7Fodv02rYspuA==",
+ "requires": {
+ "@sendgrid/helpers": "^7.7.0",
+ "axios": "^0.26.0"
+ },
+ "dependencies": {
+ "axios": {
+ "version": "0.26.1",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz",
+ "integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==",
+ "requires": {
+ "follow-redirects": "^1.14.8"
+ }
+ }
+ }
+ },
+ "@sendgrid/helpers": {
+ "version": "7.7.0",
+ "resolved": "https://registry.npmjs.org/@sendgrid/helpers/-/helpers-7.7.0.tgz",
+ "integrity": "sha512-3AsAxfN3GDBcXoZ/y1mzAAbKzTtUZ5+ZrHOmWQ279AuaFXUNCh9bPnRpN504bgveTqoW+11IzPg3I0WVgDINpw==",
+ "requires": {
+ "deepmerge": "^4.2.2"
+ }
+ },
+ "@sendgrid/mail": {
+ "version": "7.7.0",
+ "resolved": "https://registry.npmjs.org/@sendgrid/mail/-/mail-7.7.0.tgz",
+ "integrity": "sha512-5+nApPE9wINBvHSUxwOxkkQqM/IAAaBYoP9hw7WwgDNQPxraruVqHizeTitVtKGiqWCKm2mnjh4XGN3fvFLqaw==",
+ "requires": {
+ "@sendgrid/client": "^7.7.0",
+ "@sendgrid/helpers": "^7.7.0"
+ }
+ },
"@sindresorhus/is": {
"version": "0.14.0",
"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz",
@@ -14696,8 +14775,7 @@
"deepmerge": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz",
- "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==",
- "dev": true
+ "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg=="
},
"defer-to-connect": {
"version": "1.1.3",
diff --git a/package.json b/package.json
index df17cf4..107f360 100644
--- a/package.json
+++ b/package.json
@@ -10,6 +10,7 @@
"migrate": "knex migrate:latest --knexfile config/knexfile.js",
"rollback": "knex migrate:rollback --knexfile config/knexfile.js",
"watch:dev": "nodemon",
+ "email": "node api/email/emailHelper.js",
"lint": "npx eslint .",
"lint:fix": "npx eslint --fix .",
"format": "npx prettier --write \"**/*.+(js|jsx|json|yml|yaml|css|md)\"",
@@ -64,6 +65,7 @@
"dependencies": {
"@okta/jwt-verifier": "^2.0.0",
"@okta/okta-sdk-nodejs": "^6.4.0",
+ "@sendgrid/mail": "^7.7.0",
"axios": "^0.21.1",
"cookie-parser": "~1.4.4",
"cors": "^2.8.5",