Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
d76cee2
Create travel grant module
juiceo Sep 24, 2019
d80dccd
Placeholder for travel grants page, test including material-ui
juiceo Sep 24, 2019
9e0567c
Improved filters
juiceo Sep 26, 2019
7589154
More improvements to filters
juiceo Sep 26, 2019
3ebb46c
Forms for adding custom filter groups
juiceo Sep 26, 2019
b096a8e
Skeleton travel grant automisation tool
juiceo Sep 30, 2019
122bf33
add filter types oneOf, containsOneOf
juiceo Sep 30, 2019
e11a52b
Allow multiple values in select component
juiceo Sep 30, 2019
5fe5402
Merge branch 'master' of https://github.com/hackjunction/hackplatform…
juiceo Sep 30, 2019
b452d7f
Add some pre-commit hooks for clean code
juiceo Sep 30, 2019
e28653b
remove unused script
juiceo Sep 30, 2019
8409ccf
edit lint path
juiceo Sep 30, 2019
97fdadc
remove unused pre-commit
juiceo Sep 30, 2019
22cbf0c
remove .pre-commit.sample
juiceo Sep 30, 2019
451c6f9
Finalize oneOf, containsOneOf filter UI, better travel grant tool amo…
juiceo Sep 30, 2019
bc35110
Delete travel-grants module from backend
juiceo Sep 30, 2019
7455723
Make travel grant field in registrations editable
juiceo Sep 30, 2019
a62ac22
Remove old travel grant code
juiceo Sep 30, 2019
327ea1a
Add travel grant field to registration edit modal
juiceo Sep 30, 2019
95a2355
Generic component for materialTabsLayout
juiceo Sep 30, 2019
b109cee
Add pre-commit hooks
juiceo Oct 1, 2019
5759b2e
Refactor organiser dashboard
juiceo Oct 1, 2019
b737cdb
travel grants stuff
juiceo Oct 1, 2019
1c69e2a
disable travel grant emails
juiceo Oct 1, 2019
1eae1f2
re-install babel-plugin-import
juiceo Oct 1, 2019
cc5adc2
Finalize travel grant feature
juiceo Oct 1, 2019
3cd1bd6
Fix buggy custom field validation
juiceo Oct 1, 2019
f3bc68e
Final wording of travel grant email
juiceo Oct 1, 2019
5991c57
Fix travel grant filtering issue
juiceo Oct 2, 2019
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
File renamed without changes.
2 changes: 1 addition & 1 deletion backend/common/errors/errorHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ const errorHandler = (error, request, response, next) => {
}
}
}
console.log('FOOBAR', error.message);
console.log('UNHANDLED ERROR', error.message);

return response.status(500).json({
message: 'Unexpected error',
Expand Down
64 changes: 64 additions & 0 deletions backend/common/services/sendgrid.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,70 @@ const SendgridService = {

return SendgridService.send(msg);
},

sendTravelGrantAcceptedEmail: (event, user, params) => {
const msg = SendgridService.buildTemplateMessage(user.email, global.gConfig.SENDGRID_GENERIC_TEMPLATE, {
header_image: event.coverImage.url,
subject: `Your travel grant for ${event.name} has been confirmed`,
subtitle: `You have been granted a travel grant of up to ${params.amount}€`,
body: `This means that we will assist you with your travel costs to Junction 2019, up to the amount above. Please note that the following conditions apply:
<ul>
<li>
The travel grant is valid for travel from ${params.countryOfTravel} to ${event.name}. If you are travelling from somewhere else,
Junction reserves the right to change your travel grant class and/or amount.
</li>
<li>
Travel grants are only available to participants who have checked in at the venue.
</li>
<li>
You will need to supply receipt(s) of your travel to ${event.name}, which clearly shows the total cost of your trip, per traveller. You may provide additional details in the travel receipt file.
</li>
<li>
You have to submit the travel receipt file and additional required information before 24th November 23:59 Finnish Time.
</li>
</ul>

You will be able to submit your receipts and other information required for receiving the travel grant via the registration platform once you have checked in to the event. See you soon!

Psst, please note that the transaction will be made in Euros, so please make sure you have a bank account able to receive Euro payments available.
`,
cta_text: 'Event dashboard',
cta_link: `${global.gConfig.FRONTEND_URL}/dashboard/${event.slug}`
});

return SendgridService.send(msg);
},
sendTravelGrantRejectedEmail: (event, user) => {
const msg = SendgridService.buildTemplateMessage(user.email, global.gConfig.SENDGRID_GENERIC_TEMPLATE, {
header_image: event.coverImage.url,
subject: `Your travel grant status for ${event.name}`,
subtitle: `Unfortunately we we're unable to give you a travel grant this time...`,
body: `
We would have loved to give everyone a travel grant, but unfortunately we have a limited budget and you
didn't quite make the cut this time.
<br/>
<br/>
Don't worry, it's nothing personal – we want to give out travel
grants as evenly as possible to our participants, and thus we divided the travel grant applicants to
geographical areas by the country of travel – these are called travel grant classes. We then gave out
the travel grants for confirmed participants in order of registration time within that travel grant class.
This time there were more people applying for travel grants in your travel grant class than we had budget
for and the travel grants were given to those who applied to ${event.name} before you.
<br/>
<br/>
Hopefully you can still make it to ${event.name} despite this - it's going to be awesome!
<br/>
<br/>
If you won't be able to travel to the event due to not receiving a travel grant, or won't be able to make
it for some other reason, please be so kind and cancel your registration via the Event Dashboard so we can
accept someone from the waitlist.
`,
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,
Expand Down
28 changes: 28 additions & 0 deletions backend/modules/email-task/controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const SendgridService = require('../../common/services/sendgrid');
const EmailTypes = require('./types');
const EventController = require('../event/controller');
const UserController = require('../user-profile/controller');
const RegistrationController = require('../registration/controller');
const shortid = require('shortid');
const Promise = require('bluebird');
const controller = {};
Expand Down Expand Up @@ -55,6 +56,25 @@ controller.createRegisteredTask = async (userId, eventId, deliverNow = false) =>
return task;
};

controller.createTravelGrantAcceptedTask = async (registration, deliverNow = false) => {
const task = await controller.createTask(registration.user, registration.event, EmailTypes.travelGrantAccepted, {
amount: registration.travelGrant,
countryOfTravel: registration.answers.countryOfTravel
});
if (task && deliverNow) {
return controller.deliverEmailTask(task);
}
return task;
};

controller.createTravelGrantRejectedTask = async (registration, deliverNow = false) => {
const task = await controller.createTask(registration.user, registration.event, EmailTypes.travelGrantRejected);
if (task && deliverNow) {
return controller.deliverEmailTask(task);
}
return task;
};

controller.createGenericTask = async (userId, eventId, uniqueId, msgParams, deliverNow = false) => {
if (!uniqueId) {
uniqueId = shortid.generate();
Expand Down Expand Up @@ -84,6 +104,14 @@ controller.deliverEmailTask = async task => {
await SendgridService.sendRegisteredEmail(event, user);
break;
}
case EmailTypes.travelGrantAccepted: {
await SendgridService.sendTravelGrantAcceptedEmail(event, user, task.params);
break;
}
case EmailTypes.travelGrantRejected: {
await SendgridService.sendTravelGrantRejectedEmail(event, user, task.params);
break;
}
default: {
await SendgridService.sendGenericEmail(user.email, task.params);
break;
Expand Down
4 changes: 3 additions & 1 deletion backend/modules/email-task/types.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
const EmailTypes = {
registrationAccepted: 'registration-accepted',
registrationRejected: 'registration-rejected',
registrationReceived: 'registration-received'
registrationReceived: 'registration-received',
travelGrantRejected: 'travelgrant-rejected',
travelGrantAccepted: 'travelgrant-accepted'
};

module.exports = EmailTypes;
35 changes: 35 additions & 0 deletions backend/modules/filter-group/controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
const FilterGroup = require('./model');

const controller = {};
const { NotFoundError } = require('../../common/errors/errors');

controller.createFilterGroup = (label, description, createdBy, eventId, filters) => {
const filterGroup = new FilterGroup({
label,
description,
createdBy,
filters,
event: eventId
});

return filterGroup.save();
};

controller.editFilterGroup = (label, description, sub, eventId, filters) => {
return FilterGroup.findOne({ label, event: eventId }).then(filterGroup => {
if (!filterGroup) throw new NotFoundError(`Filter group with label ${label} does not exist`);
filterGroup.description = description;
filterGroup.filters = filters;
return filterGroup.save();
});
};

controller.deleteFilterGroup = (label, eventId) => {
return FilterGroup.findOneAndRemove({ label, event: eventId });
};

controller.getFilterGroupsForEvent = eventId => {
return FilterGroup.find({ event: eventId });
};

module.exports = controller;
47 changes: 47 additions & 0 deletions backend/modules/filter-group/model.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
const mongoose = require('mongoose');
const {FilterTypes} = require('@hackjunction/shared');

const FilterGroupSchema = new mongoose.Schema({
label: {
type: String,
required: true
},
description: {
type: String
},
event: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Event',
required: true
},
createdBy: {
type: String,
required: true
},
filters: [
{
label: {
type: String
},
path: {
type: String,
required: true
},
type: {
type: String,
enum: Object.keys(FilterTypes.filterTypes),
required: true
},
value: {
type: mongoose.Mixed
}
}
]
});

FilterGroupSchema.set('timestamps', true);
FilterGroupSchema.index({ event: 1, label: 1 }, { unique: true });

const FilterGroup = mongoose.model('FilterGroup', FilterGroupSchema);

module.exports = FilterGroup;
60 changes: 60 additions & 0 deletions backend/modules/filter-group/routes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
const express = require('express');
const router = express.Router();
const asyncHandler = require('express-async-handler');

const { Auth } = require('@hackjunction/shared');

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

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

const createFilterGroup = asyncHandler(async (req, res) => {
console.log('CREATING FILTER GROUP');

const { label, description, filters } = req.body;
const { sub } = req.user;
const { _id } = req.event;
console.log('CREATING FILTER GROUP');

const filterGroup = await FilterGroupController.createFilterGroup(label, description, sub, _id.toString(), filters);
console.log('CREATING FILTER GROUP');

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

const editFilterGroup = asyncHandler(async (req, res) => {
const { label, description, filters } = req.body;
const { sub } = req.user;
const { _id } = req.event;

const filterGroup = await FilterGroupController.editFilterGroup(label, description, sub, _id.toString(), filters);
return res.status(200).json(filterGroup);
});

const deleteFilterGroup = asyncHandler(async (req, res) => {
const { label } = req.body;
const { _id } = req.event;

const filterGroup = await FilterGroupController.deleteFilterGroup(label, _id.toString());

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

const getFilterGroupsForEvent = asyncHandler(async (req, res) => {
const { _id } = req.event;

const filterGroups = await FilterGroupController.getFilterGroupsForEvent(_id.toString());

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

router
.route('/:slug')
.get(hasToken, hasPermission(Auth.Permissions.MANAGE_EVENT), isEventOrganiser, getFilterGroupsForEvent)
.post(hasToken, hasPermission(Auth.Permissions.MANAGE_EVENT), isEventOrganiser, createFilterGroup)
.patch(hasToken, hasPermission(Auth.Permissions.MANAGE_EVENT), isEventOrganiser, editFilterGroup)
.delete(hasToken, hasPermission(Auth.Permissions.MANAGE_EVENT), isEventOrganiser, deleteFilterGroup);

module.exports = router;
65 changes: 52 additions & 13 deletions backend/modules/registration/controller.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const _ = require('lodash');
const Promise = require('bluebird');
const { RegistrationStatuses } = require('@hackjunction/shared');
const { RegistrationStatuses, RegistrationFields, FieldTypes } = require('@hackjunction/shared');
const Registration = require('./model');
const { NotFoundError, ForbiddenError } = require('../../common/errors/errors');
const UserProfileController = require('../user-profile/controller');
Expand Down Expand Up @@ -74,19 +74,26 @@ controller.getRegistrationsForEvent = eventId => {
}).then(registrations => {
/** Do some minor optimisation here to cut down on size */
return registrations.map(reg => {
reg.answers = _.mapValues(reg.answers, answer => {
if (typeof answer === 'string' && answer.length > 50) {
return answer.slice(0, 10) + '...';
}
if (typeof answer === 'object' && Object.keys(answer).length > 0) {
return _.mapValues(answer, subAnswer => {
if (typeof subAnswer === 'string' && subAnswer.length > 50) {
return subAnswer.slice(0, 10);
reg.answers = _.mapValues(reg.answers, (answer, field) => {
const fieldType = RegistrationFields.getFieldType(field);
switch (fieldType) {
case FieldTypes.LONG_TEXT.id:
if (answer && answer.length > 10) {
return answer.slice(0, 10) + '...';
}
return answer;
default: {
if (typeof answer === 'object' && !Array.isArray(answer) && Object.keys(answer).length > 0) {
return _.mapValues(answer, subAnswer => {
if (typeof subAnswer === 'string' && subAnswer.length > 50) {
return subAnswer.slice(0, 10);
}
return subAnswer;
});
}
return subAnswer;
});
return answer;
}
}
return answer;
});
return reg;
});
Expand Down Expand Up @@ -126,7 +133,7 @@ controller.assignRegistrationForEvent = data => {
};

controller.bulkEditRegistrations = (eventId, registrationIds, edits) => {
const cleanedEdits = _.pick(edits, ['status', 'tags', 'rating', 'assignedTo']);
const cleanedEdits = _.pick(edits, ['status', 'tags', 'rating', 'assignedTo', 'travelGrant']);
return Registration.updateMany(
{
event: eventId,
Expand All @@ -138,6 +145,37 @@ controller.bulkEditRegistrations = (eventId, registrationIds, edits) => {
);
};

controller.bulkAssignTravelGrants = (eventId, grants) => {
const updates = grants.map(({ _id, amount }) => {
return Registration.findById(_id).then(reg => {
reg.travelGrant = amount;
return reg.save();
});
});

return Promise.all(updates);
};

controller.rejectPendingTravelGrants = eventId => {
return Registration.find({
event: eventId,
status: {
$in: ['confirmed', 'checkedIn']
},
travelGrant: {
$exists: false
},
'answers.needsTravelGrant': true
}).then(registrations => {
const promises = registrations.map(registration => {
registration.travelGrant = 0;
return registration.save();
});

return Promise.all(promises);
});
};

controller.getFullRegistration = (eventId, registrationId) => {
return Registration.findById(registrationId).then(registration => {
if (!registration || registration.event.toString() !== eventId) {
Expand All @@ -155,6 +193,7 @@ controller.editRegistration = (registrationId, event, data, user) => {
registration.ratedBy = user.sub;
registration.tags = data.tags;
registration.assignedTo = data.assignedTo;
registration.travelGrant = data.travelGrant;
return registration.save();
});
};
Expand Down
Loading