From 81fef54fc566650493fbd2c9ddcbd1429a980bf0 Mon Sep 17 00:00:00 2001 From: Sebastian <sebastian.mihalache@thinslices.com> Date: Fri, 23 Feb 2018 12:47:00 +0200 Subject: [PATCH] feat(component-invite): add HE invite functionality --- .../src/controllers/inviteCollectionRole.js | 60 +++++ .../src/controllers/inviteGlobalRole.js | 39 +++ packages/component-invite/src/routes/post.js | 105 ++------ .../src/tests/fixtures/users.js | 16 ++ .../component-invite/src/tests/post.test.js | 99 ++++---- packages/component-mail-service/src/Mail.js | 39 ++- .../src/templates/assign-handling-editor.html | 232 ++++++++++++++++++ .../src/templates/assign-handling-editor.txt | 6 + packages/component-send-email/package.json | 1 - 9 files changed, 463 insertions(+), 134 deletions(-) create mode 100644 packages/component-invite/src/controllers/inviteCollectionRole.js create mode 100644 packages/component-invite/src/controllers/inviteGlobalRole.js create mode 100644 packages/component-mail-service/src/templates/assign-handling-editor.html create mode 100644 packages/component-mail-service/src/templates/assign-handling-editor.txt diff --git a/packages/component-invite/src/controllers/inviteCollectionRole.js b/packages/component-invite/src/controllers/inviteCollectionRole.js new file mode 100644 index 000000000..0c670970f --- /dev/null +++ b/packages/component-invite/src/controllers/inviteCollectionRole.js @@ -0,0 +1,60 @@ +const logger = require('@pubsweet/logger') +const config = require('config') +const helpers = require('../helpers/helpers') +const mailService = require('pubsweet-component-mail-service') + +const configRoles = config.get('roles') + +module.exports = async ( + email, + role, + reqUser, + res, + collectionId, + models, + dashboardUrl, +) => { + if (reqUser.admin) { + logger.error(`admin tried to invite a ${role} to a collection`) + + return res.status(403).json({ + error: `admin cannot invite an ${role} to a collection`, + }) + } + + if (!configRoles.collection.includes(role)) { + logger.error(`invitation has been attempted with invalid role: ${role}`) + return res + .status(403) + .json({ error: `Role ${role} cannot be set on collections` }) + } + + try { + await models.Collection.find(collectionId) + } catch (e) { + const notFoundError = await helpers.handleNotFoundError(e, 'collection') + return res.status(notFoundError.status).json({ + error: notFoundError.message, + }) + } + + try { + const user = await models.User.findByEmail(email) + user.roles.push(role) + await mailService.setupAssignEmail( + user.email, + 'assign-handling-editor', + dashboardUrl, + ) + + // await helpers.createNewTeam(collection.id, user, models.Team) + + // add team to collection.teams + return res.status(200).json(user) + } catch (e) { + const notFoundError = await helpers.handleNotFoundError(e, 'user') + return res.status(notFoundError.status).json({ + error: notFoundError.message, + }) + } +} diff --git a/packages/component-invite/src/controllers/inviteGlobalRole.js b/packages/component-invite/src/controllers/inviteGlobalRole.js new file mode 100644 index 000000000..107da28af --- /dev/null +++ b/packages/component-invite/src/controllers/inviteGlobalRole.js @@ -0,0 +1,39 @@ +const logger = require('@pubsweet/logger') +const helpers = require('../helpers/helpers') +const mailService = require('pubsweet-component-mail-service') + +module.exports = async (body, UserModel, res) => { + const { email, role, firstName, lastName, affiliation, title } = body + + try { + const user = await UserModel.findByEmail(email) + + if (user) { + logger.error('someone tried to invite existing user') + return res.status(400).json({ error: 'User already exists' }) + } + } catch (e) { + if (e.name !== 'NotFoundError') { + logger.error(e) + return res.status(500).json({ error: e.details[0].message }) + } + + const newUser = await helpers.createNewUser( + email, + role, + firstName, + lastName, + affiliation, + title, + UserModel, + ) + + await mailService.setupInviteEmail( + newUser.email, + 'invite', + newUser.passwordResetToken, + ) + + return res.status(200).json(newUser) + } +} diff --git a/packages/component-invite/src/routes/post.js b/packages/component-invite/src/routes/post.js index 8284ef47e..e3c8b16c4 100644 --- a/packages/component-invite/src/routes/post.js +++ b/packages/component-invite/src/routes/post.js @@ -1,5 +1,4 @@ const logger = require('@pubsweet/logger') -const mailService = require('pubsweet-component-mail-service') const get = require('lodash/get') const config = require('config') const helpers = require('../helpers/helpers') @@ -7,7 +6,7 @@ const helpers = require('../helpers/helpers') const configRoles = config.get('roles') module.exports = models => async (req, res) => { - const { email, role, firstName, lastName, affiliation, title } = req.body + const { email, role } = req.body if (!helpers.checkForUndefinedParams(email, role)) { res.status(400).json({ error: 'Email and role are required' }) @@ -15,94 +14,38 @@ module.exports = models => async (req, res) => { return } - const collectionId = get(req, 'params.collectionId') const reqUser = await models.User.find(req.user) - let collection - - if (reqUser.admin && collectionId) { - res.status(403).json({ - error: `admin cannot invite an ${role} to a collection`, - }) - logger.error(`admin tried to invite a ${role} to a collection`) - return - } else if (reqUser.admin) { - reqUser.roles = reqUser.roles || ['admin'] - const inviteRight = helpers.hasInviteRight(configRoles, reqUser.roles, role) - if (!inviteRight.success) { - res.status(inviteRight.status).json({ - error: inviteRight.message, - }) - logger.error(`incorrect role when inviting a ${role}`) - return - } - } else if (collectionId) { - if (!configRoles.collection.includes(role)) { - res - .status(403) - .json({ error: `Role ${role} cannot be set on collections` }) - logger.error(`invitation has been attempted with invalid role: ${role}`) - return - } - - const inviteRight = helpers.hasInviteRight(configRoles, reqUser.roles, role) - if (!inviteRight.success) { - res.status(inviteRight.status).json({ - error: inviteRight.message, - }) - return - } - try { - collection = await models.Collection.find(collectionId) - } catch (e) { - const notFoundError = helpers.handleNotFoundError(e, 'collection') - res.status(notFoundError.status).json({ - error: notFoundError.message, - }) - return - } - } else { - res.status(403).json({ - error: `${reqUser.roles || - 'undefined roles'} cannot invite a ${role} without a collection`, + const collectionId = get(req, 'params.collectionId') + if (reqUser.admin) reqUser.roles = reqUser.roles || ['admin'] + const inviteRight = helpers.hasInviteRight(configRoles, reqUser.roles, role) + if (!inviteRight.success) { + res.status(inviteRight.status).json({ + error: inviteRight.message, }) - logger.error(`cannot invite manuscript roles without a collection`) + logger.error(`incorrect role when inviting a ${role}`) return } - try { - const user = await models.User.findByEmail(email) - - if (user) { - res.status(400).json({ error: 'User already exists' }) - logger.error('someone tried to invite existing user') - return - } - } catch (e) { - if (e.name !== 'NotFoundError') { - res.status(500).json({ error: e.details[0].message }) - logger.error(e) - return - } - - const newUser = await helpers.createNewUser( + if (collectionId) { + return require('../controllers/inviteCollectionRole')( email, role, - firstName, - lastName, - affiliation, - title, - models.User, + reqUser, + res, + collectionId, + models, + `${req.protocol}://${req.get('host')}`, ) + } - if (collection) - await helpers.createNewTeam(collection.id, newUser, models.Team) - - await mailService.setupInviteEmail( - newUser.email, - 'invite', - newUser.passwordResetToken, + if (reqUser.admin) + return require('../controllers/inviteGlobalRole')( + req.body, + models.User, + res, ) - res.status(200).json(newUser) - } + res.status(403).json({ + error: `${reqUser.roles} cannot invite a ${role} without a collection`, + }) } diff --git a/packages/component-invite/src/tests/fixtures/users.js b/packages/component-invite/src/tests/fixtures/users.js index 0e2075304..d592b48ae 100644 --- a/packages/component-invite/src/tests/fixtures/users.js +++ b/packages/component-invite/src/tests/fixtures/users.js @@ -32,6 +32,22 @@ const users = { id: 'handling123', roles: ['handlingEditor'], }, + author: { + type: 'user', + username: 'author', + email: 'author@example.com', + password: 'test', + admin: false, + id: 'author123', + roles: ['author'], + passwordResetToken: 'token123', + firstName: 'leopold', + lastName: 'smith', + affiliation: 'MIT', + title: 'mr', + save: jest.fn(() => users.authorUser), + isConfirmed: false, + }, } module.exports = users diff --git a/packages/component-invite/src/tests/post.test.js b/packages/component-invite/src/tests/post.test.js index a21c3effb..03db53ddd 100644 --- a/packages/component-invite/src/tests/post.test.js +++ b/packages/component-invite/src/tests/post.test.js @@ -10,6 +10,7 @@ const TeamMock = require('./mocks/Team') jest.mock('pubsweet-component-mail-service', () => ({ setupInviteEmail: jest.fn(), + setupAssignEmail: jest.fn(), })) const chance = new Chance() const globalRoles = ['editorInChief', 'author', 'admin'] @@ -60,18 +61,16 @@ const notFoundError = new Error() notFoundError.name = 'NotFoundError' notFoundError.status = 404 -const adminUser = fixtures.users.admin -const editorInChiefUser = fixtures.users.editorInChief -const handlingEditorUser = fixtures.users.handlingEditor - +const { admin, editorInChief, handlingEditor, author } = fixtures.users +const { standardCollection } = fixtures.collections describe('Post invite route handler', () => { it('should return success when the admin invites a global role', async () => { const req = httpMocks.createRequest({ body, }) - req.user = adminUser + req.user = admin const res = httpMocks.createResponse() - const models = buildModels(notFoundError, adminUser, notFoundError) + const models = buildModels(notFoundError, admin, notFoundError) await require('../routes/post')(models)(req, res) expect(res.statusCode).toBe(200) @@ -85,10 +84,10 @@ describe('Post invite route handler', () => { const req = httpMocks.createRequest({ body, }) - req.user = adminUser + req.user = admin req.params.collectionId = '123' const res = httpMocks.createResponse() - const models = buildModels(notFoundError, adminUser) + const models = buildModels(notFoundError, admin) await require('../routes/post')(models)(req, res) expect(res.statusCode).toBe(403) const data = JSON.parse(res._getData()) @@ -102,9 +101,9 @@ describe('Post invite route handler', () => { const req = httpMocks.createRequest({ body, }) - req.user = adminUser + req.user = admin const res = httpMocks.createResponse() - const models = buildModels(notFoundError, adminUser) + const models = buildModels(notFoundError, admin) await require('../routes/post')(models)(req, res) expect(res.statusCode).toBe(403) const data = JSON.parse(res._getData()) @@ -115,9 +114,9 @@ describe('Post invite route handler', () => { const req = httpMocks.createRequest({ body, }) - req.user = adminUser + req.user = admin const res = httpMocks.createResponse() - const models = buildModels(notFoundError, adminUser) + const models = buildModels(notFoundError, admin) await require('../routes/post')(models)(req, res) expect(res.statusCode).toBe(400) const data = JSON.parse(res._getData()) @@ -130,28 +129,46 @@ describe('Post invite route handler', () => { const req = httpMocks.createRequest({ body, }) - req.user = editorInChiefUser + req.user = editorInChief req.params.collectionId = '123' const res = httpMocks.createResponse() - const models = buildModels(notFoundError, editorInChiefUser) + const models = buildModels(notFoundError, editorInChief) await require('../routes/post')(models)(req, res) expect(res.statusCode).toBe(403) const data = JSON.parse(res._getData()) - expect(data.error).toEqual(`Role ${body.role} cannot be set on collections`) + expect(data.error).toEqual( + `${req.user.roles[0]} cannot invite a ${body.role}`, + ) }) - it('should return an error when an non-admin invites without a collection', async () => { - body.role = manuscriptRoles[random(0, manuscriptRoles.length - 1)] + it('should return an error when an editorInChief invites a handlingEditor without a collection', async () => { + body.role = 'handlingEditor' body.admin = false const req = httpMocks.createRequest({ body, }) - req.user = editorInChiefUser + req.user = editorInChief const res = httpMocks.createResponse() - const models = buildModels(notFoundError, editorInChiefUser) + const models = buildModels(notFoundError, editorInChief) + await require('../routes/post')(models)(req, res) + expect(res.statusCode).toBe(403) + const data = JSON.parse(res._getData()) + expect(data.error).toEqual( + `${req.user.roles} cannot invite a ${body.role} without a collection`, + ) + }) + it('should return an error when an handlingEditor invites a reviewer without a collection', async () => { + body.role = 'reviewer' + body.admin = false + const req = httpMocks.createRequest({ + body, + }) + req.user = handlingEditor + const res = httpMocks.createResponse() + + const models = buildModels(notFoundError, handlingEditor) await require('../routes/post')(models)(req, res) expect(res.statusCode).toBe(403) - // console.log(res._getData()) const data = JSON.parse(res._getData()) expect(data.error).toEqual( `${req.user.roles} cannot invite a ${body.role} without a collection`, @@ -163,57 +180,51 @@ describe('Post invite route handler', () => { const req = httpMocks.createRequest({ body, }) - req.user = adminUser + req.user = admin const res = httpMocks.createResponse() - const models = buildModels(notFoundError, adminUser, editorInChiefUser) + const models = buildModels(notFoundError, admin, editorInChief) await require('../routes/post')(models)(req, res) expect(res.statusCode).toBe(400) const data = JSON.parse(res._getData()) expect(data.error).toEqual('User already exists') }) - it('should return success when the editor in chief invites a handling Editor with a collection', async () => { - body.role = 'handlingEditor' - body.admin = false + it('should return success when the editor in chief invites a handlingEditor with a collection', async () => { + const body = { + email: author.email, + role: 'handlingEditor', + } const req = httpMocks.createRequest({ body, }) - req.user = editorInChiefUser + req.user = editorInChief req.params.collectionId = '123' const res = httpMocks.createResponse() - const models = buildModels( - fixtures.collections.standardCollection, - editorInChiefUser, - notFoundError, - ) + const models = buildModels(standardCollection, editorInChief, author) await require('../routes/post')(models)(req, res) expect(res.statusCode).toBe(200) const data = JSON.parse(res._getData()) - expect(data.roles[0]).toEqual(body.role) - expect(data.firstName).toEqual(body.firstName) + expect(data.roles).toContain(body.role) expect(data.email).toEqual(body.email) }) - it('should return success when the handlintEditor invites a reviewer with a collection', async () => { - body.role = 'reviewer' - body.admin = false + it('should return success when the handlingEditor invites a reviewer with a collection', async () => { + const body = { + email: author.email, + role: 'reviewer', + } const req = httpMocks.createRequest({ body, }) - req.user = handlingEditorUser + req.user = handlingEditor req.params.collectionId = '123' const res = httpMocks.createResponse() - const models = buildModels( - fixtures.collections.standardCollection, - handlingEditorUser, - notFoundError, - ) + const models = buildModels(standardCollection, handlingEditor, author) await require('../routes/post')(models)(req, res) expect(res.statusCode).toBe(200) const data = JSON.parse(res._getData()) - expect(data.roles[0]).toEqual(body.role) - expect(data.firstName).toEqual(body.firstName) + expect(data.roles).toContain(body.role) expect(data.email).toEqual(body.email) }) }) diff --git a/packages/component-mail-service/src/Mail.js b/packages/component-mail-service/src/Mail.js index cbcc4b2db..246ef41f3 100644 --- a/packages/component-mail-service/src/Mail.js +++ b/packages/component-mail-service/src/Mail.js @@ -7,14 +7,9 @@ const config = require('config') const resetUrl = config.get('invite-reset-password.url') module.exports = { - setupInviteEmail: async (email, emailType, token, comment = '') => { + setupInviteEmail: async (email, emailType, token) => { let subject - const htmlFile = readFile(`${__dirname}/templates/${emailType}.html`) - const textFile = readFile(`${__dirname}/templates/${emailType}.txt`) let replacements = {} - const htmlTemplate = handlebars.compile(htmlFile) - const textTemplate = handlebars.compile(textFile) - switch (emailType) { case 'invite': subject = 'Hindawi Invitation' @@ -30,11 +25,39 @@ module.exports = { break } - const htmlBody = htmlTemplate(replacements) - const textBody = textTemplate(replacements) + const { htmlBody, textBody } = getEmailBody(emailType, replacements) Email.send(email, subject, textBody, htmlBody) }, + setupAssignEmail: async (email, emailType, dashBoardUrl) => { + let subject + let replacements = {} + switch (emailType) { + case 'assign-handling-editor': + subject = 'Hindawi Handling Editor Invitation' + replacements = { + url: dashBoardUrl, + } + break + default: + subject = 'Welcome to Hindawi!' + break + } + + const { htmlBody, textBody } = getEmailBody(emailType, replacements) + + Email.send(email, subject, textBody, htmlBody) + }, +} + +const getEmailBody = (emailType, replacements) => { + const htmlFile = readFile(`${__dirname}/templates/${emailType}.html`) + const textFile = readFile(`${__dirname}/templates/${emailType}.txt`) + const htmlTemplate = handlebars.compile(htmlFile) + const textTemplate = handlebars.compile(textFile) + const htmlBody = htmlTemplate(replacements) + const textBody = textTemplate(replacements) + return { htmlBody, textBody } } const readFile = path => diff --git a/packages/component-mail-service/src/templates/assign-handling-editor.html b/packages/component-mail-service/src/templates/assign-handling-editor.html new file mode 100644 index 000000000..72a0f88f6 --- /dev/null +++ b/packages/component-mail-service/src/templates/assign-handling-editor.html @@ -0,0 +1,232 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html data-editor-version="2" class="sg-campaigns" xmlns="http://www.w3.org/1999/xhtml"> + +<head> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1" /> + <!--[if !mso]><!--> + <meta http-equiv="X-UA-Compatible" content="IE=Edge" /> + <!--<![endif]--> + <!--[if (gte mso 9)|(IE)]> + <xml> + <o:OfficeDocumentSettings> + <o:AllowPNG/> + <o:PixelsPerInch>96</o:PixelsPerInch> + </o:OfficeDocumentSettings> + </xml> + <![endif]--> + <!--[if (gte mso 9)|(IE)]> + <style type="text/css"> + body {width: 600px;margin: 0 auto;} + table {border-collapse: collapse;} + table, td {mso-table-lspace: 0pt;mso-table-rspace: 0pt;} + img {-ms-interpolation-mode: bicubic;} + </style> + <![endif]--> + + <style type="text/css"> + body, + p, + div { + font-family: helvetica, arial, sans-serif; + font-size: 14px; + } + + body { + color: #626262; + } + + body a { + color: #0D78F2; + text-decoration: none; + } + + p { + margin: 0; + padding: 0; + } + + table.wrapper { + width: 100% !important; + table-layout: fixed; + -webkit-font-smoothing: antialiased; + -webkit-text-size-adjust: 100%; + -moz-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; + } + + img.max-width { + max-width: 100% !important; + } + + .column.of-2 { + width: 50%; + } + + .column.of-3 { + width: 33.333%; + } + + .column.of-4 { + width: 25%; + } + + @media screen and (max-width:480px) { + .preheader .rightColumnContent, + .footer .rightColumnContent { + text-align: left !important; + } + .preheader .rightColumnContent div, + .preheader .rightColumnContent span, + .footer .rightColumnContent div, + .footer .rightColumnContent span { + text-align: left !important; + } + .preheader .rightColumnContent, + .preheader .leftColumnContent { + font-size: 80% !important; + padding: 5px 0; + } + table.wrapper-mobile { + width: 100% !important; + table-layout: fixed; + } + img.max-width { + height: auto !important; + max-width: 480px !important; + } + a.bulletproof-button { + display: block !important; + width: auto !important; + font-size: 80%; + padding-left: 0 !important; + padding-right: 0 !important; + } + .columns { + width: 100% !important; + } + .column { + display: block !important; + width: 100% !important; + padding-left: 0 !important; + padding-right: 0 !important; + margin-left: 0 !important; + margin-right: 0 !important; + } + } + </style> + <!--user entered Head Start--> + + <!--End Head user entered--> +</head> + +<body> + <center class="wrapper" data-link-color="#0D78F2" data-body-style="font-size: 14px; font-family: helvetica,arial,sans-serif; color: #626262; background-color: #F4F4F4;"> + <div class="webkit"> + <table cellpadding="0" cellspacing="0" border="0" width="100%" class="wrapper" bgcolor="#F4F4F4"> + <tr> + <td valign="top" bgcolor="#F4F4F4" width="100%"> + <table width="100%" role="content-container" class="outer" align="center" cellpadding="0" cellspacing="0" border="0"> + <tr> + <td width="100%"> + <table width="100%" cellpadding="0" cellspacing="0" border="0"> + <tr> + <td> + <!--[if mso]> + <center> + <table><tr><td width="600"> + <![endif]--> + <table width="100%" cellpadding="0" cellspacing="0" border="0" style="width: 100%; max-width:600px;" align="center"> + <tr> + <td role="modules-container" style="padding: 0px 0px 0px 0px; color: #626262; text-align: left;" bgcolor="#F4F4F4" width="100%" + align="left"> + + <table class="module preheader preheader-hide" role="module" data-type="preheader" border="0" cellpadding="0" cellspacing="0" + width="100%" style="display: none !important; mso-hide: all; visibility: hidden; opacity: 0; color: transparent; height: 0; width: 0;"> + <tr> + <td role="module-content"> + <p>handling editor</p> + </td> + </tr> + </table> + + <table class="wrapper" role="module" data-type="image" border="0" cellpadding="0" cellspacing="0" width="100%" style="table-layout: fixed;"> + <tr> + <td style="font-size:6px;line-height:10px;padding:20px 0px 20px 0px;" valign="top" align="center"> + <img class="max-width" border="0" style="display:block;color:#000000;text-decoration:none;font-family:Helvetica, arial, sans-serif;font-size:16px;max-width:10% !important;width:10%;height:auto !important;" + src="https://marketing-image-production.s3.amazonaws.com/uploads/bb39b20cf15e52c1c0933676e25f2b2402737c6560b8098c204ad6932b84eb2058804376dbc4db138c7a21dcaed9325bde36185648afac5bc97e3d73d4e12718.png" + alt="" width="60"> + </td> + </tr> + </table> + + <table class="module" role="module" data-type="text" border="0" cellpadding="0" cellspacing="0" width="100%" style="table-layout: fixed;"> + <tr> + <td style="padding:30px 23px 0px 23px;background-color:#ffffff;" height="100%" valign="top" bgcolor="#ffffff"> + <h1 style="text-align: center;">You have been assigned as a Handling Editor to a manuscript.</h1> + + <div style="text-align: center;">Please click on the link below to access your dashboard.</div> + + <div style="text-align: center;"> </div> + + <div style="text-align: center;"> </div> + + </td> + </tr> + </table> + <table border="0" cellPadding="0" cellSpacing="0" class="module" data-role="module-button" data-type="button" role="module" + style="table-layout:fixed" width="100%"> + <tbody> + <tr> + <td align="center" bgcolor="#FFFFFF" class="outer-td" style="padding:0px 0px 30px 0px;background-color:#FFFFFF"> + <table border="0" cellPadding="0" cellSpacing="0" class="button-css__deep-table___2OZyb wrapper-mobile" + style="text-align:center"> + <tbody> + <tr> + <td align="center" bgcolor="#0d78f2" class="inner-td" style="border-radius:6px;font-size:16px;text-align:center;background-color:inherit"> + <a href="{{ url }}" style="background-color:#0d78f2;border:1px solid #333333;border-color:#0d78f2;border-radius:0px;border-width:1px;color:#ffffff;display:inline-block;font-family:arial,helvetica,sans-serif;font-size:16px;font-weight:normal;letter-spacing:0px;line-height:16px;padding:12px 18px 12px 18px;text-align:center;text-decoration:none" + target="_blank">VIEW DASHBOARD</a> + </td> + </tr> + </tbody> + </table> + </td> + </tr> + </tbody> + </table> + <div data-role="module-unsubscribe" class="module unsubscribe-css__unsubscribe___2CDlR" role="module" + data-type="unsubscribe" style="color:#444444;font-size:12px;line-height:20px;padding:16px 16px 16px 16px;text-align:center"> + <div class="Unsubscribe--addressLine"> + <p class="Unsubscribe--senderName" style="font-family:Arial, Helvetica, sans-serif;font-size:12px;line-height:20px">Hindawi Publishing Corporation</p> + <p style="font-family:Arial, Helvetica, sans-serif;font-size:12px;line-height:20px"> + <span class="Unsubscribe--senderAddress">315 Madison Ave, Third Floor, Suite 3070</span>, + <span class="Unsubscribe--senderCity">NEW YORK</span>, + <span class="Unsubscribe--senderState">NY</span> + <span class="Unsubscribe--senderZip">10017</span> + </p> + </div> + <p style="font-family:Arial, Helvetica, sans-serif;font-size:12px;line-height:20px"> + <a class="Unsubscribe--unsubscribeLink" href="[Unsubscribe]">Unsubscribe</a> + </p> + </div> + </td> + </tr> + </table> + <!--[if mso]> + </td></tr></table> + </center> + <![endif]--> + </td> + </tr> + </table> + </td> + </tr> + </table> + </td> + </tr> + </table> + </div> + </center> +</body> + +</html> \ No newline at end of file diff --git a/packages/component-mail-service/src/templates/assign-handling-editor.txt b/packages/component-mail-service/src/templates/assign-handling-editor.txt new file mode 100644 index 000000000..a1c6edf27 --- /dev/null +++ b/packages/component-mail-service/src/templates/assign-handling-editor.txt @@ -0,0 +1,6 @@ +You have been assigned as a Handling Editor to a manuscript. +Please click on the link below to access your dashboard +{{ url }} VIEW DASHBOARD +Hindawi Publishing Corporation +315 Madison Ave, Third Floor, Suite 307 +New York, NY 10017 \ No newline at end of file diff --git a/packages/component-send-email/package.json b/packages/component-send-email/package.json index abbf0d925..42a1c5d72 100644 --- a/packages/component-send-email/package.json +++ b/packages/component-send-email/package.json @@ -12,7 +12,6 @@ }, "dependencies": { "aws-sdk": "^2.185.0", - "body-parser": "^1.17.2", "nodemailer": "^4.4.2" }, "peerDependencies": { -- GitLab