diff options
author | Raphael Kabo <mail@raphaelkabo.com> | 2022-04-25 19:03:24 +0100 |
---|---|---|
committer | Raphael Kabo <mail@raphaelkabo.com> | 2022-04-25 19:03:24 +0100 |
commit | 1c8348d553988f5474c8d7896c2804dc1d62159a (patch) | |
tree | 16990fae56e848e430312c2238be89268f937a75 | |
parent | 168c2430e1e727429b76871cc32a9951e4391658 (diff) |
feat: Add subscribe to group functionality
-rwxr-xr-x | models/EventGroup.js | 10 | ||||
-rwxr-xr-x | routes.js | 118 | ||||
-rw-r--r-- | views/emails/subscribed.handlebars | 9 | ||||
-rwxr-xr-x | views/eventgroup.handlebars | 35 |
4 files changed, 171 insertions, 1 deletions
diff --git a/models/EventGroup.js b/models/EventGroup.js index 6d2893b..c70ef95 100755 --- a/models/EventGroup.js +++ b/models/EventGroup.js @@ -1,5 +1,12 @@ const mongoose = require('mongoose'); +const Subscriber = new mongoose.Schema({ + email: { + type: String, + trim: true + }, +}) + const EventGroupSchema = new mongoose.Schema({ id: { type: String, @@ -43,7 +50,8 @@ const EventGroupSchema = new mongoose.Schema({ trim: true, default: true }, - events: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Event' }] + events: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Event' }], + subscribers: [Subscriber], }); module.exports = mongoose.model('EventGroup', EventGroupSchema); @@ -741,6 +741,46 @@ router.post('/newevent', async (req, res) => { } }); } + // If the event was added to a group, send an email to any group + // subscribers + if (event.eventGroup && sendEmails) { + EventGroup.findOne({ _id: event.eventGroup._id }) + .then((eventGroup) => { + const subscribers = eventGroup.subscribers.reduce((acc, current) => { + if (acc.includes(current.email)) { + return acc; + } + return [ current.email, ...acc ]; + }, []); + subscribers.forEach(emailAddress => { + req.app.get('hbsInstance').renderView('./views/emails/eventgroupupdated.handlebars', { siteName, siteLogo, domain, eventID: req.params.eventID, eventGroupName: eventGroup.name, eventName: event.name, eventID: event.id, eventGroupID: eventGroup.id, emailAddress: encodeURIComponent(emailAddress), cache: true, layout: 'email.handlebars' }, function (err, html) { + const msg = { + to: emailAddress, + from: { + name: siteName, + email: contactEmail, + }, + subject: `${siteName}: New event in ${eventGroup.name}`, + html, + }; + switch (mailService) { + case 'sendgrid': + sgMail.send(msg).catch(e => { + console.error(e.toString()); + res.status(500).end(); + }); + break; + case 'nodemailer': + nodemailerTransporter.sendMail(msg).catch(e => { + console.error(e.toString()); + res.status(500).end(); + }); + break; + } + }); + }); + }); + } res.writeHead(302, { 'Location': '/' + eventID + '?e=' + editToken }); @@ -1533,6 +1573,84 @@ router.post('/removeattendee/:eventID/:attendeeID', (req, res) => { }); }); +/* + * Create an email subscription on an event group. + */ +router.post('/subscribe/:eventGroupID', (req, res) => { + const subscriber = { + email: req.body.emailAddress, + }; + if (!subscriber.email) { + return res.sendStatus(500); + } + + EventGroup.findOne(({ + id: req.params.eventGroupID, + })) + .then((eventGroup) => { + if (!eventGroup) { + return res.sendStatus(404); + } + eventGroup.subscribers.push(subscriber); + eventGroup.save(); + if (sendEmails) { + req.app.get('hbsInstance').renderView('./views/emails/subscribed.handlebars', { eventGroupName: eventGroup.name, eventGroupID: eventGroup.id, emailAddress: encodeURIComponent(subscriber.email), siteName, siteLogo, domain, cache: true, layout: 'email.handlebars' }, function (err, html) { + const msg = { + to: subscriber.email, + from: { + name: siteName, + email: contactEmail, + }, + subject: `${siteName}: You have subscribed to an event group`, + html, + }; + switch (mailService) { + case 'sendgrid': + sgMail.send(msg).catch(e => { + console.error(e.toString()); + res.status(500).end(); + }); + break; + case 'nodemailer': + nodemailerTransporter.sendMail(msg).catch(e => { + console.error(e.toString()); + res.status(500).end(); + }); + break; + } + }); + } + return res.redirect(`/group/${eventGroup.id}`) + }) + .catch((error) => { + addToLog("addSubscription", "error", "Attempt to subscribe " + req.body.emailAddress + " to event group " + req.params.eventGroupID + " failed with error: " + error); + return res.sendStatus(500); + }); +}); + +/* + * Delete an existing email subscription on an event group. + */ +router.get('/unsubscribe/:eventGroupID', (req, res) => { + const email = req.query.email; + console.log(email); + if (!email) { + return res.sendStatus(500); + } + + EventGroup.update( + { id: req.params.eventGroupID }, + { $pull: { subscribers: { email } } } + ) + .then(response => { + return res.redirect('/'); + }) + .catch((error) => { + addToLog("removeSubscription", "error", "Attempt to unsubscribe " + req.query.email + " from event group " + req.params.eventGroupID + " failed with error: " + error); + return res.sendStatus(500); + }); +}); + router.post('/post/comment/:eventID', (req, res) => { let commentID = nanoid(); const newComment = { diff --git a/views/emails/subscribed.handlebars b/views/emails/subscribed.handlebars new file mode 100644 index 0000000..3a3c4ad --- /dev/null +++ b/views/emails/subscribed.handlebars @@ -0,0 +1,9 @@ +<p style="font-family: sans-serif; font-size: 14px; font-weight: normal; margin: 0; Margin-bottom: 15px;">You have been subscribed to the event group '{{eventGroupName}}' on {{siteName}}.</p> +<p style="font-family: sans-serif; font-size: 14px; font-weight: normal; margin: +0; Margin-bottom: 15px;">You will receive emails when new events are added to +the group, and can unsubscribe at any time.</p> +<p style="font-family: sans-serif; font-size: 14px; font-weight: normal; margin: 0; Margin-bottom: 15px;">Love,</p> +<p style="font-family: sans-serif; font-size: 14px; font-weight: normal; margin: 0; Margin-bottom: 15px;">{{siteName}}</p> +<hr/> +<p style="font-family: sans-serif; font-size: 14px; font-weight: normal; margin: 0; Margin-bottom: 15px;"><strong>Hold up - I don't want to receive these emails any more!</strong></p> +<p style="font-family: sans-serif; font-size: 14px; font-weight: normal; margin: 0; Margin-bottom: 15px;">If you didn't subscribe yourself to this event group on {{siteName}}, someone may have accidentally typed your email instead of theirs. <a href="https://{{domain}}/unsubscribe/{{eventGroupID}}?email={{emailAddress}}">Click here to unsubscribe</a>.</p> diff --git a/views/eventgroup.handlebars b/views/eventgroup.handlebars index 25b29d0..9afee2c 100755 --- a/views/eventgroup.handlebars +++ b/views/eventgroup.handlebars @@ -76,6 +76,11 @@ </div> <div class="col-lg-3" id="eventActions"> <aside class="btn-group-vertical d-flex" role="group" aria-label="Event actions"> + <button type="button" class="btn btn-outline-secondary btn-sm" + data-event-id="{{eventGroupData.id}}" data-toggle="modal" + data-target="#subscribeModal"> + <i class="fas fa-envelope"></i> Subscribe to updates + </button> <button type="button" id="exportICS" class="btn btn-outline-secondary btn-sm" data-event-id="{{eventGroupData.id}}"> <i class="fas fa-download"></i> Export as ICS @@ -157,6 +162,36 @@ {{/if}} +<div class="modal fade" id="subscribeModal" tabindex="-1" role="dialog" + aria-labelledby="subscribeModalLabel" aria-hidden="true"> + <div class="modal-dialog" role="document"> + <div class="modal-content"> + <div class="modal-header"> + <h5 class="modal-title" id="subscribeModalLabel">Subscribe to '{{eventGroupData.name}}'</h5> + <button type="button" class="close" data-dismiss="modal" aria-label="Close"> + <span aria-hidden="true">×</span> + </button> + </div> + <form id="subscribeForm" action="/subscribe/{{eventGroupData.id}}" method="post"> + <div class="modal-body"> + <div class="form-group"> + <p class="form-text small">Enter your email address to receive updates + whenever a new event is created in this group.</p> + </div> + <div class="form-group"> + <input type="email" class="form-control" id="emailAddress" + name="emailAddress" placeholder="email@example.com" data-validation="required"> + </div> + </div> + <div class="modal-footer"> + <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button> + <button type="submit" class="btn btn-success">Subscribe</button> + </div> + </form> + </div> + </div> +</div> + <div class="modal fade" id="editTokenModal" tabindex="-1" role="dialog" aria-labelledby="editTokenModalLabel" aria-hidden="true"> <div class="modal-dialog" role="document"> <div class="modal-content"> |