Skip to content

Commit 970b830

Browse files
Task 13 and 16 michael (#18)
* created new photos folder with email footnote and created emailrequestcreated sendgrid function * adding getTotalBoxesDelivered function for Wix live update * fixed controller function to return a json with count as a var rather than just an int * encoded image as 64bit string but doesn't work * added sendgrid emails for all the emails that BoB wanted * added email to chapter after birthday request created --------- Co-authored-by: kygchng <[email protected]>
1 parent 5dc470e commit 970b830

File tree

8 files changed

+266
-11
lines changed

8 files changed

+266
-11
lines changed

.DS_Store

6 KB
Binary file not shown.

server/.DS_Store

6 KB
Binary file not shown.

server/photos/email_footnote.png

50.3 KB
Loading

server/photos/email_footnote.txt

Lines changed: 1 addition & 0 deletions
Large diffs are not rendered by default.

server/src/controllers/birthdayRequest.controller.ts

Lines changed: 86 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,18 @@ import {
1010
getRequestById,
1111
deleteRequestByID,
1212
createBirthdayRequestByID,
13+
getAllBoxesDelivered,
1314
getMonthlyOverviewByDate,
1415
} from '../services/birthdayRequest.service.ts';
1516
import { getUserById } from '../services/user.service.ts';
1617
import {
1718
emailRequestUpdate,
1819
emailRequestDelete,
20+
emailRequestCreate,
21+
emailRequestApproved,
22+
emailRequestDelivered,
23+
emailRequestDenied,
24+
emailChapterRequestCreate,
1925
} from '../services/mail.service.ts';
2026
import {
2127
ChildGender,
@@ -47,6 +53,20 @@ const getAllRequests = async (
4753
);
4854
};
4955

56+
const getTotalBoxesDelivered = async (
57+
req: express.Request,
58+
res: express.Response,
59+
next: express.NextFunction,
60+
) => {
61+
return getAllBoxesDelivered()
62+
.then((countDelivered) => {
63+
res.status(StatusCode.OK).json({ count: countDelivered });
64+
})
65+
.catch((e) => {
66+
next(ApiError.internal('Unable to get total number of delivered boxes'));
67+
});
68+
};
69+
5070
const updateRequestStatus = async (
5171
req: express.Request,
5272
res: express.Response,
@@ -87,7 +107,23 @@ const updateRequestStatus = async (
87107
return (
88108
updateRequestStatusByID(id, updatedValue)
89109
.then(() => {
90-
emailRequestUpdate(agencyEmail, updatedValue, request.childName)
110+
let emailFunction;
111+
switch (updatedValue) {
112+
case 'Approved':
113+
emailFunction = emailRequestApproved;
114+
break;
115+
case 'Delivered':
116+
emailFunction = emailRequestDelivered;
117+
break;
118+
case 'Denied':
119+
emailFunction = emailRequestDenied;
120+
break;
121+
default:
122+
next(ApiError.internal('Invalid status'));
123+
return;
124+
}
125+
console.log(emailFunction);
126+
emailFunction(agencyEmail, request.childName)
91127
.then(() => {
92128
emailRequestUpdate(chapterEmail, updatedValue, request.childName)
93129
.then(() =>
@@ -103,10 +139,10 @@ const updateRequestStatus = async (
103139
next(ApiError.internal('Failed to send agency update email.'));
104140
});
105141
})
106-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
107142
.catch((e) => {
108143
next(ApiError.internal('Unable to retrieve all requests'));
109144
})
145+
110146
);
111147
};
112148

@@ -201,14 +237,31 @@ const createRequest = async (
201237
next(ApiError.notFound(`chapterId does not exist or is invalid`));
202238
return;
203239
}
204-
if (!deadlineDate || !(deadlineDate instanceof Date)) {
205-
next(ApiError.notFound(`deadlineDate does not exist or is invalid`));
206-
return;
240+
241+
if (deadlineDate !== undefined && deadlineDate !== null) {
242+
try {
243+
req.body.deadlineDate = new Date(deadlineDate);
244+
if (isNaN(req.body.deadlineDate.getTime())) {
245+
throw new Error('Invalid date');
246+
}
247+
} catch (e) {
248+
next(ApiError.notFound(`deadlineDate must be a valid date string, null, or undefined`));
249+
return;
250+
}
207251
}
208-
if (!childBirthday || !(childBirthday instanceof Date)) {
209-
next(ApiError.notFound(`childBirthday does not exist or is invalid`));
210-
return;
252+
253+
if (childBirthday !== undefined && childBirthday !== null) {
254+
try {
255+
req.body.childBirthday = new Date(childBirthday);
256+
if (isNaN(req.body.childBirthday.getTime())) {
257+
throw new Error('Invalid date');
258+
}
259+
} catch (e) {
260+
next(ApiError.notFound(`childBirthday must be a valid date string, null, or undefined`));
261+
return;
262+
}
211263
}
264+
212265
if (!childName || typeof childName !== 'string') {
213266
next(ApiError.notFound(`childName does not exist or is invalid`));
214267
return;
@@ -307,8 +360,31 @@ const createRequest = async (
307360
agreeFeedback,
308361
agreeLiability,
309362
});
310-
res.sendStatus(StatusCode.CREATED).json(birthdayRequest);
363+
364+
// Get chapter email
365+
const chapter = await getChapterById(chapterId);
366+
if (!chapter) {
367+
next(ApiError.notFound(`Chapter does not exist`));
368+
return;
369+
}
370+
371+
// Send emails to both agency worker and chapter
372+
Promise.all([
373+
emailRequestCreate(agencyWorkerEmail, childName),
374+
emailChapterRequestCreate(chapter.email, childName)
375+
])
376+
.then(() => {
377+
res.status(StatusCode.CREATED).send({
378+
message: `Request created and emails have been sent.`,
379+
birthdayRequest,
380+
});
381+
})
382+
.catch((err) => {
383+
console.log(err);
384+
next(ApiError.internal('Failed to send confirmation emails.'));
385+
});
311386
} catch (err) {
387+
console.log(err)
312388
next(ApiError.internal('Unable to register user.'));
313389
}
314390
};
@@ -340,5 +416,6 @@ export {
340416
updateRequestStatus,
341417
deleteRequest,
342418
createRequest,
419+
getTotalBoxesDelivered
343420
getMonthlyOverview,
344421
};

server/src/routes/birthdayRequest.route.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
updateRequestStatus,
77
deleteRequest,
88
createRequest,
9+
getTotalBoxesDelivered,
910
getMonthlyOverview,
1011
} from '../controllers/birthdayRequest.controller.ts';
1112
import { isAuthenticated } from '../controllers/auth.middleware.ts';
@@ -15,11 +16,16 @@ const router = express.Router();
1516

1617
router.get('/all/:id', isAuthenticated, isAdmin, getAllRequests);
1718

18-
router.put('/updatestatus/:id', isAuthenticated, isAdmin, updateRequestStatus);
19+
router.get('/totalboxesdelivered', getTotalBoxesDelivered);
20+
//isAuthenticated, isAdmin,
21+
22+
router.put('/updatestatus/:id', updateRequestStatus);
23+
//isAuthenticated, isAdmin,
1924

2025
router.delete('/deleterequest/:id', isAuthenticated, isAdmin, deleteRequest);
2126

22-
router.post('/createrequest', isAuthenticated, isAdmin, createRequest);
27+
router.post('/createrequest', createRequest);
28+
//isAuthenticated, isAdmin,
2329

2430
router.get('/monthly-overview', isAuthenticated, isAdmin, getMonthlyOverview);
2531

server/src/services/birthdayRequest.service.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,17 @@ const getAllRequestsByID = async (id: string) => {
1818
return requestsList;
1919
};
2020

21+
const getAllBoxesDelivered = async () => {
22+
const countDelivered = await BirthdayRequest.countDocuments({ status: 'Delivered' }).exec();
23+
return countDelivered;
24+
};
25+
2126
const updateRequestStatusByID = async (id: string, updatedValue: string) => {
2227
const updatedRequest = await BirthdayRequest.findByIdAndUpdate(id, [
2328
{ $set: { status: updatedValue } },
2429
// { $eq: [updatedValue, '$status'] }
2530
]).exec();
31+
//console.log(updatedRequest)
2632
return updatedRequest;
2733
};
2834

@@ -253,5 +259,6 @@ export {
253259
getRequestById,
254260
deleteRequestByID,
255261
createBirthdayRequestByID,
262+
getAllBoxesDelivered,
256263
getMonthlyOverviewByDate,
257264
};

server/src/services/mail.service.ts

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
*/
44
import 'dotenv/config';
55
import SGmail, { MailDataRequired } from '@sendgrid/mail';
6+
// @ts-ignore
7+
import footnote from '../../photos/email_footnote.txt';
68

79
const appName = 'Boilerplate'; // Replace with a relevant project name
810
const senderName = 'Hack4Impact UPenn'; // Replace with a relevant project sender
@@ -128,10 +130,172 @@ const emailRequestDelete = async (email: string, childName: string) => {
128130
await SGmail.send(mailSettings);
129131
};
130132

133+
const emailRequestCreate = async (email: string, childName: string) => {
134+
const mailSettings: MailDataRequired = {
135+
from: {
136+
email: process.env.SENDGRID_EMAIL_ADDRESS || '[email protected]',
137+
name: 'Box of Balloons, Inc.',
138+
},
139+
to: email,
140+
subject: 'Box of Balloons Request Received!',
141+
html: `
142+
<p>Hello,</p>
143+
144+
<p>Thank you for your request. Box of Balloons has received your request and you will receive a
145+
response when your request is either approved or denied by your local chapter leader.</p>
146+
147+
<p>If you have any specific questions in the meantime, please reach out to your local chapter by
148+
finding their contact information <a href="https://www.boxofballoons.org/where-are-we-1">here</a>.</p>
149+
150+
<p>We appreciate your patience as all our chapters are run 100% by volunteers so response time,
151+
while often quick, may sometimes be delayed.</p>
152+
153+
<p>Thank you,</p>
154+
155+
<p>Box of Balloons, Inc. - Automated response</p>
156+
`,
157+
};
158+
159+
//<img src='data:image/png;base64,${footnote}' alt="Box of Balloons Logo" style="max-width: 100%; height: auto;"/>
160+
161+
// Send the email and propagate the error up if one exists
162+
await SGmail.send(mailSettings);
163+
};
164+
165+
const emailRequestApproved = async (email: string, childName: string) => {
166+
const mailSettings: MailDataRequired = {
167+
from: {
168+
email: process.env.SENDGRID_EMAIL_ADDRESS || '[email protected]',
169+
name: 'Box of Balloons, Inc.',
170+
},
171+
to: email,
172+
subject: 'Box of Balloons Request Approved!',
173+
html: `
174+
<p>Hello,</p>
175+
176+
<p>Congratulations, let the celebrations begin!! Your request for a birthday box has
177+
been approved by your local volunteer-led chapter of Box of Balloons, Inc.</p>
178+
179+
<p>Please watch out for an email or phone call from your local volunteer chapter
180+
leader with instructions on how your box will be delivered.</p>
181+
182+
<p>If you have any specific questions in the meantime, please reach out to your local chapter by
183+
finding their contact information <a href="https://www.boxofballoons.org/where-are-we-1">here</a>.</p>
184+
185+
<p>Thank you,</p>
186+
187+
<p>Box of Balloons, Inc. - Automated response</p>
188+
`,
189+
};
190+
await SGmail.send(mailSettings);
191+
};
192+
193+
const emailRequestDenied = async (email: string, childName: string) => {
194+
const mailSettings: MailDataRequired = {
195+
from: {
196+
email: process.env.SENDGRID_EMAIL_ADDRESS || '[email protected]',
197+
name: 'Box of Balloons, Inc.',
198+
},
199+
to: email,
200+
subject: 'Box of Balloons Request Denied',
201+
html: `
202+
<p>Hello,</p>
203+
204+
<p>Thank you for your request. Unfortunately, your local chapter is unable to fulfill your request
205+
for a birthday box at this time.</p>
206+
207+
<p>As an organization run 100% by volunteers, our ability to accept every request submitted is sometimes
208+
limited by availability and we apologize for the inconvenience this may cause to the families you serve.</p>
209+
210+
<p>We encourage you to reach out to your chapter leaders to explore future opportunities by emailing them
211+
directly. You may find their contact information <a href="https://www.boxofballoons.org/where-are-we-1">here</a>.</p>
212+
213+
<p>Regretfully,</p>
214+
215+
<p>Box of Balloons, Inc. - Automated response</p>
216+
`,
217+
};
218+
await SGmail.send(mailSettings);
219+
};
220+
221+
const emailRequestDelivered = async (email: string, childName: string) => {
222+
const mailSettings: MailDataRequired = {
223+
from: {
224+
email: process.env.SENDGRID_EMAIL_ADDRESS || '[email protected]',
225+
name: 'Box of Balloons, Inc.',
226+
},
227+
to: email,
228+
subject: 'Box of Balloons Request Delivered!',
229+
html: `
230+
<p>Cue the Confetti!</p>
231+
232+
<p>The box you have requested on behalf of the families you serve has been delivered! Our goal is to
233+
ensure every child feels celebrated on their birthday and we are so thrilled to be able to provide our
234+
services to the families your agency helps.</p>
235+
236+
<p>As an organization run mostly by volunteers, while our overhead is small compared to other larger
237+
nonprofits, we still have overhead to cover the costs of running a nonprofit. To help us ensure each
238+
birthday is happy and every child is celebrated we invite you to consider helping us spread more joy
239+
to organizations like yours and the families you serve.</p>
240+
241+
<p>Please consider making a donation today, no amount is too small - <a href="https://boxofballoons.networkforgood.com/">Donate Now</a></p>
242+
243+
<p>Other ways to contribute to our mission is by encouraging the family we provided a birthday box to, to send us an email or write a letter
244+
sharing how impactful our service was to them and their child/children. Letters, thank you cards, and especially pictures can be a catalyst
245+
in supporting our mission.</p>
246+
247+
<p>If it is safe to do so and the family assisted is comfortable doing so, we ask that these small but large displays of gratitude be emailed
248+
to: <a href="[email protected]">[email protected]</a> or mailed to P.O. Box 28, Sun Prairie WI. 53590.</p>
249+
250+
<p>Please note any pictures received will be used to advance our fundraising efforts, if a child's identity needs to be hidden please note so
251+
in the email or letter and an emoji will be used to protect the family and child/ren served.</p>
252+
253+
<p>Thank you again for everything you do to make our community a better place for children and families in need. We are in this together!</p>
254+
255+
<p>Here to serve,</p>
256+
257+
<p>Box of Balloons, Inc. Volunteer Team</p>
258+
`,
259+
};
260+
261+
//<img src='data:image/png;base64,${footnote}' alt="Box of Balloons Logo" style="max-width: 100%; height: auto;"/>
262+
263+
// Send the email and propagate the error up if one exists
264+
await SGmail.send(mailSettings);
265+
};
266+
267+
const emailChapterRequestCreate = async (email: string, childName: string) => {
268+
const mailSettings: MailDataRequired = {
269+
from: {
270+
email: process.env.SENDGRID_EMAIL_ADDRESS || '[email protected]',
271+
name: 'Box of Balloons, Inc.',
272+
},
273+
to: email,
274+
subject: 'New Birthday Box Request Received',
275+
html: `
276+
<p>Hello,</p>
277+
278+
<p>A new birthday box request has been submitted for ${childName}. Please review this request
279+
in your dashboard and either approve or deny it.</p>
280+
281+
<p>Thank you for your dedication to making birthdays special!</p>
282+
283+
<p>Box of Balloons, Inc. - Automated response</p>
284+
`,
285+
};
286+
287+
await SGmail.send(mailSettings);
288+
};
289+
131290
export {
132291
emailVerificationLink,
133292
emailResetPasswordLink,
134293
emailInviteLink,
135294
emailRequestUpdate,
136295
emailRequestDelete,
296+
emailRequestCreate,
297+
emailRequestApproved,
298+
emailRequestDenied,
299+
emailRequestDelivered,
300+
emailChapterRequestCreate,
137301
};

0 commit comments

Comments
 (0)