Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
node_modules
.DS_Store
scripts/sync-production-to-local.sh
scripts/sync-staging-to-dev.sh
scripts/sync-staging-to-local.sh
scripts/sync-production-to-dev.sh
scripts/sync-production-to-staging.sh
4 changes: 1 addition & 3 deletions backend/common/errors/errorHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,7 @@ const errorHandler = (error, request, response, next) => {
}
}
}
console.log('ERROR', error.message);

// console.error('Unexpected error', error.toJSON());
console.log('FOOBAR', error.message);

return response.status(500).json({
message: 'Unexpected error',
Expand Down
44 changes: 38 additions & 6 deletions backend/common/services/sendgrid.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const sgMail = require('@sendgrid/mail');
const sgClient = require('@sendgrid/client');
const _ = require('lodash');
const moment = require('moment');
sgMail.setApiKey(global.gConfig.SENDGRID_API_KEY);
sgClient.setApiKey(global.gConfig.SENDGRID_API_KEY);

Expand Down Expand Up @@ -34,15 +35,46 @@ const sendgridAddRecipientsToList = (list_id, recipient_ids) => {
};

const SendgridService = {
buildAcceptanceEmail: (to, { event_name }) => {
return SendgridService.buildTemplateMessage(to, global.gConfig.SENDGRID_ACCEPTED_TEMPLATE, {
event_name
sendAcceptanceEmail: (event, user) => {
const msg = SendgridService.buildTemplateMessage(user.email, global.gConfig.SENDGRID_ACCEPTED_TEMPLATE, {
event_name: event.name,
first_name: user.firstName,
dashboard_link: `${global.gConfig.FRONTEND_URL}/dashboard/${event.slug}`
});
return SendgridService.send(msg);
},
buildRejectionEmail: (to, { event_name }) => {
return SendgridService.buildTemplateMessage(to, global.gConfig.SENDGRID_REJECTED_TEMPLATE, {
event_name
sendRejectionEmail: (event, user) => {
return Promise.resolve();
},
sendRegisteredEmail: (event, user) => {
const msg = SendgridService.buildTemplateMessage(user.email, global.gConfig.SENDGRID_GENERIC_TEMPLATE, {
header_image: event.logo.url,
subject: `Thanks for registering to ${event.name}!`,
subtitle: 'Awesome! Now just sit back and relax.',
body: `The application period ends <b>${moment(event.registrationEndTime).format(
'MMMM Do'
)}</b>, and we'll process all applications by <b>${moment(event.registrationEndTime)
.add(5, 'days')
.format(
'MMMM Do'
)}</b>. <br /> <br /> We'll send you an email once we've made the decision, but in the meantime you can click the link below to access your event dashboard, where you'll be able to see your registration status in real-time. If you're applying as a team, the event dashboard is where you can create and manage your team as well.`,
cta_text: 'Event dashboard',
cta_link: `${global.gConfig.FRONTEND_URL}/dashboard/${event.slug}`
});

return SendgridService.send(msg);
},
sendGenericEmail: (to, params) => {
const msg = SendgridService.buildTemplateMessage(to, global.gConfig.SENDGRID_GENERIC_TEMPLATE, {
subject: params.subject,
subtitle: params.subtitle,
header_image: params.header_image,
body: params.body,
cta_text: params.cta_text,
cta_link: params.cta_link
});

return SendgridService.send(msg);
},
buildTemplateMessage: (to, templateId, data) => {
return {
Expand Down
11 changes: 9 additions & 2 deletions backend/misc/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ const settings = {
value: process.env.SENDGRID_REJECTED_TEMPLATE,
required: true
},
SENDGRID_GENERIC_TEMPLATE: {
value: process.env.SENDGRID_GENERIC_TEMPLATE,
required: true
},
FRONTEND_URL: {
value: process.env.FRONTEND_URL,
default: '',
Expand All @@ -74,14 +78,18 @@ const settings = {
ENVIRONMENT_TAG: {
value: process.env.ENVIRONMENT_TAG,
default: 'none'
},
DEVTOOLS_ENABLED: {
value: process.env.DEVTOOLS_ENABLED === 'true' && process.env.NODE_ENV !== 'production',
default: false
}
};

const buildConfig = () => {
const config = {};
_.forOwn(settings, (obj, key) => {
if (!obj.value) {
if (obj.default) {
if (obj.default || obj.default === false) {
config[key] = obj.default;
} else {
throw new Error(
Expand All @@ -94,7 +102,6 @@ const buildConfig = () => {
});

console.log('Running app with config', config);
console.log('Running app with env', process.env);

return config;
};
Expand Down
35 changes: 35 additions & 0 deletions backend/modules/devtools/routes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
const express = require('express');
const router = express.Router();
const Registration = require('../registration/model');

router.route('/').get((req, res) => {
return res.status(200).send('DEVTOOLS HERE');
});

router.route('/anonymize-registrations').get(async (req, res) => {
const registrations = await Registration.find({});

const updates = registrations.map(registration => {
return {
updateOne: {
filter: {
_id: registration._id
},
update: {
$set: {
'answers.firstName': 'Anonymous',
'answers.lastName': 'Owl',
'answers.email':
'juuso.lappalainen+' + Math.floor(Math.random() * 1000000) + '@hackjunction.com'
}
}
}
};
});

const result = await Registration.bulkWrite(updates);

return res.status(200).json(result);
});

module.exports = router;
122 changes: 89 additions & 33 deletions backend/modules/email-task/controller.js
Original file line number Diff line number Diff line change
@@ -1,55 +1,111 @@
const EmailTask = require('./model');
const SendgridService = require('../../common/services/sendgrid');
const EmailTypes = require('./types');
const EventController = require('../event/controller');
const UserController = require('../user-profile/controller');
const shortid = require('shortid');
const Promise = require('bluebird');
const controller = {};

controller.createTask = (msg, taskParams) => {
controller.createTask = (userId, eventId, type, params, schedule) => {
const task = new EmailTask({
message: msg,
...taskParams
user: userId,
event: eventId,
type: type
});

if (schedule) {
task.schedule = schedule;
}

if (params) {
task.params = params;
}
return task.save().catch(err => {
if (err.code === 11000) {
//The task already exists, so it's ok
console.log('ALREADY EXISTS');
return Promise.resolve();
}
// For other types of errors, we'll want to throw the error normally
return Promise.reject();
return Promise.reject(err);
});
};

controller.sendEmail = (msg, taskParams) => {
return SendgridService.send(msg).catch(message => {
/** If sending the email fails, save a task to the DB and we can retry later */
return controller.createTask(message, taskParams);
controller.createAcceptedTask = async (userId, eventId, deliverNow = false) => {
const task = await controller.createTask(userId, eventId, EmailTypes.registrationAccepted);
if (deliverNow) {
return controller.deliverEmailTask(task);
}
return task;
};

controller.createRejectedTask = async (userId, eventId, deliverNow = false) => {
const task = await controller.createTask(userId, eventId, EmailTypes.registrationRejected);
if (deliverNow) {
return controller.deliverEmailTask(task);
}
return task;
};

controller.createRegisteredTask = async (userId, eventId, deliverNow = false) => {
const task = await controller.createTask(userId, eventId, EmailTypes.registrationReceived);
if (task && deliverNow) {
return controller.deliverEmailTask(task);
}
return task;
};

controller.createGenericTask = async (userId, eventId, uniqueId, msgParams, deliverNow = false) => {
if (!uniqueId) {
uniqueId = shortid.generate();
}
const task = await controller.createTask(userId, eventId, 'generic_' + uniqueId, msgParams);
if (task && deliverNow) {
return controller.deliverEmailTask(task);
}
return task;
};

controller.deliverEmailTask = async task => {
const [user, event] = await Promise.all([
UserController.getUserProfile(task.user),
EventController.getEventById(task.event)
]);
switch (task.type) {
case EmailTypes.registrationAccepted: {
await SendgridService.sendAcceptanceEmail(event, user);
break;
}
case EmailTypes.registrationRejected: {
await SendgridService.sendRejectionEmail(event, user);
break;
}
case EmailTypes.registrationReceived: {
await SendgridService.sendRegisteredEmail(event, user);
break;
}
default: {
await SendgridService.sendGenericEmail(user.email, task.params);
break;
}
}

/** Here we'll have success so we can set the task as delivered */
task.deliveredAt = Date.now();
return task.save();
};

controller.sendPreviewEmail = async (to, msgParams) => {
return SendgridService.sendGenericEmail(to, msgParams).catch(err => {
return;
});
};

controller.sendAcceptanceEmail = (event, user) => {
const msgParams = {
event_name: event.name
};
const msg = SendgridService.buildAcceptanceEmail(user.email, msgParams);
const taskParams = {
userId: user.userId,
eventId: event._id,
type: EmailTypes.registrationAccepted
};
return controller.sendEmail(msg, taskParams);
};

controller.sendRejectionEmail = (event, user) => {
const msgParams = {
event_name: event.name
};
const msg = SendgridService.buildRejectionEmail(user.email, msgParams);
const taskParams = {
userId: user.userId,
eventId: event._id,
type: EmailTypes.registrationRejected
};
return controller.sendEmail(msg, taskParams);
controller.sendBulkEmail = async (recipients, msgParams, event, uniqueId) => {
const promises = recipients.map(recipient => {
return controller.createGenericTask(recipient, event._id.toString(), uniqueId, msgParams, true);
});
return Promise.all(promises);
};

module.exports = controller;
29 changes: 21 additions & 8 deletions backend/modules/email-task/model.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,37 @@
const mongoose = require('mongoose');

const EmailTaskSchema = new mongoose.Schema({
message: mongoose.Mixed,
deliveredAt: {
type: Date
params: {
type: mongoose.Schema.Types.Mixed,
default: null
},
schedule: {
type: Date,
default: Date.now
},
eventId: String,
userId: String,
type: String
deliveredAt: {
type: Date,
default: null
},
event: {
type: String,
required: true
},
user: {
type: String,
required: true
},
type: {
type: String,
required: true
}
});

EmailTaskSchema.set('timestamps', true);
EmailTaskSchema.index(
{
eventId: 1,
userId: 1,
event: 1,
user: 1,
type: 1
},
{
Expand Down
30 changes: 30 additions & 0 deletions backend/modules/email-task/routes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
const express = require('express');
const router = express.Router();
const asyncHandler = require('express-async-handler');
const { Auth } = require('@hackjunction/shared');

const { hasToken } = require('../../common/middleware/token');
const { hasPermission } = require('../../common/middleware/permissions');
const { isEventOrganiser } = require('../../common/middleware/events');

const EmailTaskController = require('./controller');

const sendPreviewEmail = asyncHandler(async (req, res) => {
await EmailTaskController.sendPreviewEmail(req.body.to, req.body.params);
return res.status(200).json({});
});

const sendBulkEmail = asyncHandler(async (req, res) => {
await EmailTaskController.sendBulkEmail(req.body.recipients, req.body.params, req.event, req.body.uniqueId);
return res.status(200).json({});
});

router
.route('/:slug/preview')
.post(hasToken, hasPermission(Auth.Permissions.MANAGE_EVENT), isEventOrganiser, sendPreviewEmail);

router
.route('/:slug/send')
.post(hasToken, hasPermission(Auth.Permissions.MANAGE_EVENT), isEventOrganiser, sendBulkEmail);

module.exports = router;
3 changes: 2 additions & 1 deletion backend/modules/email-task/types.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const EmailTypes = {
registrationAccepted: 'registration-accepted',
registrationRejected: 'registration-rejected'
registrationRejected: 'registration-rejected',
registrationReceived: 'registration-received'
};

module.exports = EmailTypes;
Loading