Skip to content

Commit

Permalink
fix(boards): send mail to new board members (#714)
Browse files Browse the repository at this point in the history
* fix(boards): send mail to new board members

* chore: work on stuff

* chore: revert changes to config

* fix: add cron for sending new board emails

* chore: fix starting the job

* chore: fix typo

* Update test/assets/core-permissions-full.json

---------

Co-authored-by: Rik Smale <WikiRik@users.noreply.github.com>
  • Loading branch information
WikiRik and WikiRik authored Dec 7, 2024
1 parent 5b07762 commit 8006b80
Show file tree
Hide file tree
Showing 12 changed files with 311 additions and 3 deletions.
36 changes: 35 additions & 1 deletion lib/boards.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const { Sequelize, sequelize } = require('./sequelize');
const core = require('./core');
const mailer = require('./mailer');
const config = require('../config');
const constants = require('./constants');

exports.createBoard = async (req, res) => {
if (!req.permissions.manage_boards[req.body.body_id] && !req.permissions.manage_boards.global) {
Expand All @@ -28,7 +29,7 @@ exports.createBoard = async (req, res) => {
}

await sequelize.transaction(async (t) => {
await Board.create(req.body, { transaction: t });
const createdBoard = await Board.create(req.body, { transaction: t });

await mailer.sendMail({
to: config.new_board_notifications,
Expand All @@ -40,6 +41,8 @@ exports.createBoard = async (req, res) => {
positions
}
});

await this.sendNewBoardEmail(createdBoard.id, true);
});

return res.json({
Expand Down Expand Up @@ -200,3 +203,34 @@ exports.deleteBoard = async (req, res) => {
data: req.board
});
};

exports.sendNewBoardEmail = async (id, newBoard) => {
const board = await Board.findByPk(id);

if (!board) {
return;
}

if (!newBoard && !moment(board.start_date).isSame(moment(), 'day')) {
return;
}

if (newBoard && moment(board.end_date).isBefore(moment())) {
return;
}

const memberIds = [board.president, board.secretary, board.treasurer];

if (board.other_members) {
memberIds.push(...board.other_members.map((member) => member.user_id));
}

const mails = await core.getMails(memberIds.join(','));

await mailer.sendMail({
to: mails.map((member) => member.notification_email),
subject: constants.MAIL_SUBJECTS.NEW_BOARD_EMAIL,
template: 'network_board_welcome.html',
parameters: {}
});
};
5 changes: 5 additions & 0 deletions lib/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module.exports = {
MAIL_SUBJECTS: {
NEW_BOARD_EMAIL: 'MyAEGEE: Congratulations on the start of your board term!'
}
};
37 changes: 37 additions & 0 deletions lib/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ const makeRequest = (options) => {
resolveWithFullResponse: options.resolveWithFullResponse || false
};

if (options.body) {
requestOptions.body = options.body;
}

return request(requestOptions);
};

Expand Down Expand Up @@ -92,3 +96,36 @@ module.exports.fetchUser = async (user, token) => {
name: userRequest.data.first_name + ' ' + userRequest.data.last_name
};
};

module.exports.getMails = async (ids, token) => {
let adminToken = '';

if (!token) {
// Getting access and refresh token.
const authRequest = await makeRequest({
url: config.core.url + ':' + config.core.port + '/login',
method: 'POST',
body: {
username: config.core.user.login,
password: config.core.user.password
}
});

if (typeof authRequest !== 'object') {
throw new Error('Malformed response when fetching auth request: ' + authRequest);
}

if (!authRequest.success) {
throw new Error('Error fetching auth request: ' + JSON.stringify(authRequest));
}

adminToken = authRequest.access_token;
}

const mails = await makeRequest({
url: config.core.url + ':' + config.core.port + '/members_email?query=' + ids,
token: token || adminToken
});

return mails.data;
};
14 changes: 14 additions & 0 deletions lib/cron.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
const { CronJob } = require('cron');
const { Board } = require('../models');
const boards = require('./boards');

module.exports.sendAllEmails = async () => {
const allBoards = await Board.findAll({});
for (const board of allBoards) {
boards.sendNewBoardEmail(board.id);
}
};

module.exports.job = new CronJob('0 10 * * *', async () => {
await module.exports.sendAllEmails();
}, null, true, 'Europe/Brussels');
2 changes: 1 addition & 1 deletion lib/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ exports.getPermissions = (user, corePermissions, managePermissions) => {
view_board: hasPermission(corePermissions, 'view:board'),
manage_antenna_criteria: hasPermission(corePermissions, 'global:manage_network:antenna_criteria'),
manage_netcom_assignment: hasPermission(corePermissions, 'global:manage_network:netcom_assignment'),
send_mails: hasPermission(corePermissions, 'global:manage_network:fulfilment_email')
send_mails: hasPermission(corePermissions, 'global:manage_network:fulfilment_email'),
};

permissions.manage_boards = {
Expand Down
2 changes: 2 additions & 0 deletions lib/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const mailComponent = require('./mail_component');
const metrics = require('./metrics');
const endpointsMetrics = require('./endpoints_metrics');
const db = require('./sequelize');
const { job } = require('./cron');

const server = express();
server.use(bodyParser.json());
Expand Down Expand Up @@ -68,6 +69,7 @@ async function startServer() {
app = localApp;
log.info({ host: 'http://localhost:' + config.port }, 'Up and running, listening');
await db.authenticate();
job.start();
return res();
});
/* istanbul ignore next */
Expand Down
26 changes: 26 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"db:create": "sequelize db:create",
"db:setup": "sequelize db:drop; sequelize db:create; sequelize db:migrate",
"db:migrate": "sequelize db:migrate",
"test": "NODE_ENV=test npm run db:setup && jest test/api/*.js --runInBand --forceExit",
"test": "NODE_ENV=test npm run db:setup && jest test/**/*.test.js --runInBand --forceExit",
"test:ci": "NODE_ENV=test npm run db:setup && jest --runInBand --forceExit",
"open-coverage": "open-cli coverage/lcov-report/index.html",
"prepare": "husky install"
Expand Down Expand Up @@ -61,6 +61,7 @@
"@bugsnag/js": "^7.25.0",
"body-parser": "^1.20.2",
"bunyan": "^1.8.15",
"cron": "^3.2.1",
"express": "^4.21.1",
"express-promise-router": "^4.1.1",
"joi": "^17.12.3",
Expand Down
19 changes: 19 additions & 0 deletions test/assets/core-notification-mails.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"success": true,
"data": [{
"id": 1,
"notification_email": "admin@example.com"
},{
"id": 2,
"notification_email": "test@example.com"
},{
"id": 3,
"notification_email": "example@example.com"
},{
"id": 4,
"notification_email": "mail@example.com"
},{
"id": 5,
"notification_email": "testing@example.com"
}]
}
60 changes: 60 additions & 0 deletions test/scripts/mock.js
Original file line number Diff line number Diff line change
Expand Up @@ -210,13 +210,71 @@ exports.mockCoreMailer = (options) => {
.reply(200, { success: true });
};

exports.mockCoreLogin = (options) => {
if (options.netError) {
return nock(`${config.core.url}:${config.core.port}`)
.persist()
.post('/login')
.replyWithError('Some random error.');
}

if (options.badResponse) {
return nock(`${config.core.url}:${config.core.port}`)
.persist()
.post('/login')
.reply(500, 'Some error happened.');
}

if (options.unsuccessfulResponse) {
return nock(`${config.core.url}:${config.core.port}`)
.persist()
.post('/login')
.reply(500, { success: false, message: 'Some error' });
}

return nock(`${config.core.url}:${config.core.port}`)
.persist()
.post('/login')
.reply(200, { success: true, access_token: '1', refresh_token: '1' });
};

exports.mockCoreMails = (options) => {
if (options.netError) {
return nock(`${config.core.url}:${config.core.port}`)
.persist()
.get(/\/members_email\?query=[0-9,].*/)
.replyWithError('Some random error.');
}

if (options.badResponse) {
return nock(`${config.core.url}:${config.core.port}`)
.persist()
.get(/\/members_email\?query=[0-9,].*/)
.reply(500, 'Some error happened.');
}

if (options.unsuccessfulResponse) {
return nock(`${config.core.url}:${config.core.port}`)
.persist()
.get(/\/members_email\?query=[0-9,].*/)
.reply(500, { success: false, message: 'Some error' });
}

return nock(`${config.core.url}:${config.core.port}`)
.persist()
.get(/\/members_email\?query=[0-9,].*/)
.replyWithFile(200, path.join(__dirname, '..', 'assets', 'core-notification-mails.json'));
};

exports.mockAll = (options = {}) => {
nock.cleanAll();
const omsCoreStub = exports.mockCore(options.core || {});
const omsMainPermissionsStub = exports.mockCoreMainPermissions(options.mainPermissions || {});
const omsManagePermissionsStub = exports.mockCoreManagePermissions(options.managePermissions || {});
const omsCoreBodyStub = exports.mockCoreBody(options.body || {});
const omsCoreMemberStub = exports.mockCoreMember(options.member || {});
const coreLoginStub = exports.mockCoreLogin(options.login || {});
const coreMailsStub = exports.mockCoreMails(options.mails || {});
const omsMailerStub = exports.mockCoreMailer(options.mailer || {});

return {
Expand All @@ -225,6 +283,8 @@ exports.mockAll = (options = {}) => {
omsManagePermissionsStub,
omsCoreBodyStub,
omsCoreMemberStub,
coreLoginStub,
coreMailsStub,
omsMailerStub
};
};
24 changes: 24 additions & 0 deletions test/unit/cron.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
const { Board } = require('../../models');
const boards = require('../../lib/boards');
const cron = require('../../lib/cron');

jest.mock('../../models');
jest.mock('../../lib/boards');

describe('Cron Job Tests', () => {
beforeEach(() => {
jest.clearAllMocks();
});

test('sendAllEmails should fetch all boards and send emails', async () => {
const mockBoards = [{ id: 1 }, { id: 2 }];
Board.findAll.mockResolvedValue(mockBoards);

await cron.sendAllEmails();

expect(Board.findAll).toHaveBeenCalledTimes(1);
expect(boards.sendNewBoardEmail).toHaveBeenCalledTimes(mockBoards.length);
expect(boards.sendNewBoardEmail).toHaveBeenCalledWith(1);
expect(boards.sendNewBoardEmail).toHaveBeenCalledWith(2);
});
});
Loading

0 comments on commit 8006b80

Please sign in to comment.