diff --git a/packages/component-invite/src/HandleInvitation.js b/packages/component-invite/src/HandleInvitation.js index d3c5c37336388cfd5779fdd0c5f6a1fc812cdbe0..7ecdd0cafec79703d13c52d1157c766205128286 100644 --- a/packages/component-invite/src/HandleInvitation.js +++ b/packages/component-invite/src/HandleInvitation.js @@ -15,6 +15,11 @@ const HandleInvitation = app => { authBearer, require('./routes/getCollectionUsers')(app.locals.models), ) + app.delete( + '/api/collections/:collectionId/users/:userId', + authBearer, + require('./routes/deleteInvitation')(app.locals.models), + ) } module.exports = HandleInvitation diff --git a/packages/component-invite/src/controllers/assignCollectionRole.js b/packages/component-invite/src/controllers/assignCollectionRole.js index 36a16362ff3ab1e7002ce38375849053bd9802a9..20481676a89a3bf36e74d7ec9f25261bcc5901a3 100644 --- a/packages/component-invite/src/controllers/assignCollectionRole.js +++ b/packages/component-invite/src/controllers/assignCollectionRole.js @@ -3,6 +3,7 @@ const config = require('config') const helpers = require('../helpers/helpers') const teamHelper = require('../helpers/Team') const mailService = require('pubsweet-component-mail-service') +const inviteHelper = require('../helpers/Invitation') const configRoles = config.get('roles') @@ -79,7 +80,7 @@ module.exports = async ( // getting the updated user from the DB - creating a team also updates the user user = await models.User.findByEmail(email) - user = await teamHelper.setupInvitation(user, role, collectionId, team.id) + user = await inviteHelper.setupInvitation(user, role, collectionId, team.id) try { await mailService.setupAssignEmail( diff --git a/packages/component-invite/src/helpers/Invitation.js b/packages/component-invite/src/helpers/Invitation.js new file mode 100644 index 0000000000000000000000000000000000000000..417a3e4c1a28958d2260bf07319504741fbbd34d --- /dev/null +++ b/packages/component-invite/src/helpers/Invitation.js @@ -0,0 +1,41 @@ +const revokeInvitation = async (user, collectionId, type) => { + const filteredInvitations = user.invitations.filter( + invitation => + invitation.collectionId !== collectionId && invitation.type !== type, + ) + + user.invitations = filteredInvitations + await user.save() +} + +const getInviteData = (invitations, collectionId, role) => { + const matchingInvitation = invitations.find( + invite => invite.type === role && invite.collectionId === collectionId, + ) + let status = 'pending' + if (matchingInvitation.isAccepted) { + status = 'accepted' + } else if (matchingInvitation.hasAnswer) { + status = 'refused' + } + + const { timestamp } = matchingInvitation + return { timestamp, status } +} + +const setupInvitation = async (user, role, collectionId, teamId) => { + const invitation = { + type: role, + hasAnswer: false, + isAccepted: false, + collectionId, + timestamp: Date.now(), + teamId, + } + user.invitations = user.invitations || [] + user.invitations.push(invitation) + user = await user.save() + return user +} + +module.exports = { getInviteData, revokeInvitation, setupInvitation } diff --git a/packages/component-invite/src/helpers/Team.js b/packages/component-invite/src/helpers/Team.js index ae6758c967a6ddb379699ffeb1332d81967b55cc..298e0c50834ae3296dda91d19d08de5962df22bc 100644 --- a/packages/component-invite/src/helpers/Team.js +++ b/packages/component-invite/src/helpers/Team.js @@ -113,26 +113,13 @@ const getMatchingTeams = (teams, TeamModel, collectionId, role) => }) .filter(Boolean) -const setupInvitation = async (user, role, collectionId, teamId) => { - const invitation = { - type: role, - hasAnswer: false, - isAccepted: false, - collectionId, - timestamp: Date.now(), - teamId, - } - user.invitations = user.invitations || [] - user.invitations.push(invitation) - user = await user.save() - return user -} - const removeTeamMember = async (teamId, userId, TeamModel) => { - const team = await TeamModel.find(teamId) + let team = await TeamModel.find(teamId) const members = team.members.filter(member => member !== userId) team.members = members - await TeamModel.updateProperties(team) + + team = await team.updateProperties(team) + await team.save() } @@ -151,21 +138,6 @@ const getTeamMembersByCollection = async (collectionId, role, TeamModel) => { return members } -const getInviteData = (invitations, collectionId, role) => { - const matchingInvitation = invitations.find( - invite => invite.type === role && invite.collectionId === collectionId, - ) - let status = 'pending' - if (matchingInvitation.isAccepted) { - status = 'accepted' - } else if (matchingInvitation.hasAnswer) { - status = 'refused' - } - - const { timestamp } = matchingInvitation - return { timestamp, status } -} - const getTeamByGroupAndCollection = async (collectionId, role, TeamModel) => { const teams = await TeamModel.all() return teams.find( @@ -181,9 +153,7 @@ module.exports = { setupEiCTeams, setupManuscriptTeam, getMatchingTeams, - setupInvitation, removeTeamMember, getTeamMembersByCollection, - getInviteData, getTeamByGroupAndCollection, } diff --git a/packages/component-invite/src/helpers/helpers.js b/packages/component-invite/src/helpers/helpers.js index 2ec5f5f9f7d8ce52f0bba0f12417d8a3f9e09128..3be4674565565f5f29588f90cfb27f3f6172aa28 100644 --- a/packages/component-invite/src/helpers/helpers.js +++ b/packages/component-invite/src/helpers/helpers.js @@ -84,10 +84,12 @@ const createNewUser = async ( UserModel, role, ) => { + const username = uuid.v4().slice(0, 8) + const password = uuid.v4() const userBody = { - username: uuid.v4().slice(0, 8), + username, email, - password: uuid.v4(), + password, passwordResetToken: crypto.randomBytes(32).toString('hex'), isConfirmed: false, firstName, @@ -98,7 +100,9 @@ const createNewUser = async ( admin: role === 'admin', handlingEditor: role === 'handlingEditor', } + let newUser = new UserModel(userBody) + try { newUser = await newUser.save() return newUser diff --git a/packages/component-invite/src/routes/deleteInvitation.js b/packages/component-invite/src/routes/deleteInvitation.js new file mode 100644 index 0000000000000000000000000000000000000000..d8c77d72d2d0717ae32a44291a97f2e33aec5ca2 --- /dev/null +++ b/packages/component-invite/src/routes/deleteInvitation.js @@ -0,0 +1,63 @@ +const helpers = require('../helpers/helpers') +const teamHelper = require('../helpers/Team') +const config = require('config') +const inviteHelper = require('../helpers/Invitation') +const mailService = require('pubsweet-component-mail-service') +const logger = require('@pubsweet/logger') + +const configRoles = config.get('roles') +module.exports = models => async (req, res) => { + const { role } = req.query + if (!helpers.checkForUndefinedParams(role)) { + res.status(400).json({ error: 'Role is required' }) + return + } + + if (!configRoles.collection.includes(role)) { + res.status(400).json({ error: `Role ${role} is invalid` }) + return + } + + const reqUser = await models.User.find(req.user) + if (!reqUser.editorInChief) { + res.status(400).json({ error: 'The request user must be Editor in Chief' }) + return + } + + const { collectionId, userId } = req.params + try { + await models.Collection.find(collectionId) + let user = await models.User.find(userId) + const team = await teamHelper.getTeamByGroupAndCollection( + collectionId, + role, + models.Team, + ) + + if (team === undefined) { + res.status(400).json({ + error: `The requested collection does not have a ${role} Team`, + }) + return + } + await inviteHelper.revokeInvitation(user, collectionId, role) + user = await models.User.find(userId) + await teamHelper.removeTeamMember(team.id, userId, models.Team) + try { + await mailService.setupRevokeInvitationEmail( + user.email, + 'revoke-handling-editor', + ) + + return res.status(204).json() + } catch (e) { + logger.error(e.message) + return res.status(500).json({ error: 'Email could not be sent.' }) + } + } catch (e) { + const notFoundError = await helpers.handleNotFoundError(e, 'item') + return res.status(notFoundError.status).json({ + error: notFoundError.message, + }) + } +} diff --git a/packages/component-invite/src/routes/getCollectionUsers.js b/packages/component-invite/src/routes/getCollectionUsers.js index 16903452e5d2a81805b118dd5f3886dbe549d987..94570732b4731e170f6e9d6a076258631d1e2de7 100644 --- a/packages/component-invite/src/routes/getCollectionUsers.js +++ b/packages/component-invite/src/routes/getCollectionUsers.js @@ -1,6 +1,7 @@ const helpers = require('../helpers/helpers') const teamHelper = require('../helpers/Team') const config = require('config') +const inviteHelper = require('../helpers/Invitation') const configRoles = config.get('roles') module.exports = models => async (req, res) => { @@ -39,7 +40,7 @@ module.exports = models => async (req, res) => { const membersData = members.map(async member => { const user = await models.User.find(member) - const { timestamp, status } = teamHelper.getInviteData( + const { timestamp, status } = inviteHelper.getInviteData( user.invitations, collectionId, role, diff --git a/packages/component-invite/src/tests/deleteInvitation.test.js b/packages/component-invite/src/tests/deleteInvitation.test.js new file mode 100644 index 0000000000000000000000000000000000000000..30e994b12abf5365195570f6d48892815709e6f7 --- /dev/null +++ b/packages/component-invite/src/tests/deleteInvitation.test.js @@ -0,0 +1,107 @@ +process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0' +process.env.SUPPRESS_NO_CONFIG_WARNING = true + +const httpMocks = require('node-mocks-http') +const fixtures = require('./fixtures/fixtures') +const Model = require('./helpers/Model') + +jest.mock('pubsweet-component-mail-service', () => ({ + setupRevokeInvitationEmail: jest.fn(), +})) +const { standardCollection, noTeamCollection } = fixtures.collections +const { editorInChief, admin, handlingEditor } = fixtures.users +const { heTeam } = fixtures.teams +const query = { + role: 'handlingEditor', +} +const deleteInvitationPath = '../routes/deleteInvitation' +const models = Model.build() + +describe('Delete Invitation route handler', () => { + it('should return success when the role is correct, the collection exists and the request user is editorInChief ', async () => { + const req = httpMocks.createRequest() + req.query = query + req.params.collectionId = standardCollection.id + req.params.userId = handlingEditor.id + req.user = editorInChief.id + const res = httpMocks.createResponse() + const initialSize = handlingEditor.invitations.length + await require(deleteInvitationPath)(models)(req, res) + expect(res.statusCode).toBe(204) + expect(heTeam.members).not.toContain(handlingEditor.id) + expect(handlingEditor.invitations).toHaveLength(initialSize - 1) + }) + it('should return an error when the role parameter is missing', async () => { + delete query.role + const req = httpMocks.createRequest() + req.query = query + req.user = editorInChief.id + const res = httpMocks.createResponse() + await require(deleteInvitationPath)(models)(req, res) + expect(res.statusCode).toBe(400) + const data = JSON.parse(res._getData()) + expect(data.error).toEqual('Role is required') + query.role = 'handlingEditor' + }) + it('should return an error when the collection does not exist', async () => { + const req = httpMocks.createRequest() + req.query = query + req.params.collectionId = 'invalid-id' + req.user = editorInChief.id + const res = httpMocks.createResponse() + await require(deleteInvitationPath)(models)(req, res) + expect(res.statusCode).toBe(404) + const data = JSON.parse(res._getData()) + expect(data.error).toEqual('item not found') + }) + it('should return an error when the user does not exist', async () => { + const req = httpMocks.createRequest() + req.query = query + req.params.collectionId = standardCollection.id + req.params.userId = 'invalid-id' + req.user = editorInChief.id + const res = httpMocks.createResponse() + await require(deleteInvitationPath)(models)(req, res) + expect(res.statusCode).toBe(404) + const data = JSON.parse(res._getData()) + expect(data.error).toEqual('item not found') + }) + it('should return an error when the role is invalid', async () => { + query.role = 'invalidRole' + const req = httpMocks.createRequest() + req.query = query + req.params.collectionId = standardCollection.id + req.user = editorInChief.id + const res = httpMocks.createResponse() + await require(deleteInvitationPath)(models)(req, res) + expect(res.statusCode).toBe(400) + const data = JSON.parse(res._getData()) + expect(data.error).toEqual(`Role ${query.role} is invalid`) + query.role = 'handlingEditor' + }) + it('should return an error when the request user is not editorInChief', async () => { + const req = httpMocks.createRequest() + req.query = query + req.params.collectionId = standardCollection.id + req.user = admin.id + const res = httpMocks.createResponse() + await require(deleteInvitationPath)(models)(req, res) + expect(res.statusCode).toBe(400) + const data = JSON.parse(res._getData()) + expect(data.error).toEqual('The request user must be Editor in Chief') + }) + it('should return an error when the collection does not have a the requested role team', async () => { + const req = httpMocks.createRequest() + req.query = query + req.params.collectionId = noTeamCollection.id + req.params.userId = handlingEditor.id + req.user = editorInChief.id + const res = httpMocks.createResponse() + await require(deleteInvitationPath)(models)(req, res) + expect(res.statusCode).toBe(400) + const data = JSON.parse(res._getData()) + expect(data.error).toEqual( + `The requested collection does not have a ${query.role} Team`, + ) + }) +}) diff --git a/packages/component-invite/src/tests/fixtures/teamIDs.js b/packages/component-invite/src/tests/fixtures/teamIDs.js index f8e1c9f2285384a39ccb39de082abf773c7cfb02..74f1842bb26490263c8f58d52cee2b662c72b6cd 100644 --- a/packages/component-invite/src/tests/fixtures/teamIDs.js +++ b/packages/component-invite/src/tests/fixtures/teamIDs.js @@ -1,10 +1,10 @@ const Chance = require('chance') const chance = new Chance() +const heID = chance.guid() +const revID = chance.guid() -const teamIDs = { - heTeam: chance.guid(), - reviewerTeam: chance.guid(), +module.exports = { + heTeamID: heID, + reviewerTeamID: revID, } - -module.exports = { teamIDs } diff --git a/packages/component-invite/src/tests/fixtures/teams.js b/packages/component-invite/src/tests/fixtures/teams.js index 84de7cd6b6284772d1516308692709f0031a8f6d..1cfc277b62badf8a17111524e4e0e62622628795 100644 --- a/packages/component-invite/src/tests/fixtures/teams.js +++ b/packages/component-invite/src/tests/fixtures/teams.js @@ -1,6 +1,6 @@ const users = require('./users') const collections = require('./collections') -const { heTeam, reviewerTeam } = require('./teamIDs') +const { heTeamID, reviewerTeamID } = require('./teamIDs') const { standardCollection } = collections const { editorInChief, handlingEditor, reviewer, invitedHandlingEditor } = users @@ -34,7 +34,7 @@ const teams = { members: [handlingEditor.id, invitedHandlingEditor.id], save: jest.fn(() => teams.heTeam), updateProperties: jest.fn(() => teams.heTeam), - id: heTeam, + id: heTeamID, }, reviewerTeam: { teamType: { @@ -50,7 +50,7 @@ const teams = { members: [reviewer.id], save: jest.fn(() => teams.reviewerTeam), updateProperties: jest.fn(() => teams.reviewerTeam), - id: reviewerTeam, + id: reviewerTeamID, }, } module.exports = teams diff --git a/packages/component-invite/src/tests/fixtures/users.js b/packages/component-invite/src/tests/fixtures/users.js index a04b7ff4c5e65b0b444f1a085b2c6fcc1254ed4e..53e00485ec4413381309488acb126c2ccb4b2dfe 100644 --- a/packages/component-invite/src/tests/fixtures/users.js +++ b/packages/component-invite/src/tests/fixtures/users.js @@ -1,5 +1,5 @@ const { standardCollection } = require('./collections') -const { heTeam, reviewerTeam } = require('./teamIDs') +const { heTeamID, reviewerTeamID } = require('./teamIDs') const users = { admin: { @@ -42,10 +42,10 @@ const users = { isAccepted: false, collectionId: standardCollection.id, timestamp: Date.now(), - teamId: heTeam, + teamId: heTeamID, }, ], - teams: [heTeam], + teams: [heTeamID], save: jest.fn(() => users.handlingEditor), editorInChief: false, }, @@ -78,7 +78,7 @@ const users = { title: 'Ms', save: jest.fn(() => users.reviewer), isConfirmed: false, - teams: [reviewerTeam], + teams: [reviewerTeamID], invitations: [ { type: 'reviewer', @@ -86,7 +86,7 @@ const users = { isAccepted: false, collectionId: '123', timestamp: Date.now(), - teamId: reviewerTeam, + teamId: reviewerTeamID, }, ], }, @@ -106,10 +106,10 @@ const users = { isAccepted: false, collectionId: standardCollection.id, timestamp: Date.now(), - teamId: heTeam, + teamId: heTeamID, }, ], - teams: [heTeam], + teams: [heTeamID], save: jest.fn(() => users.handlingEditor), editorInChief: false, }, diff --git a/packages/component-mail-service/package.json b/packages/component-mail-service/package.json index cb79ab3a4626231243fd6149417cd338d179da7a..5a8328e59fbbeab1b75e18cabfa51879627adc0d 100644 --- a/packages/component-mail-service/package.json +++ b/packages/component-mail-service/package.json @@ -4,7 +4,9 @@ "description": "mail service component for pubsweet", "license": "MIT", "author": "Collaborative Knowledge Foundation", - "files": ["src"], + "files": [ + "src" + ], "main": "index.js", "repository": { "type": "git", @@ -12,13 +14,13 @@ "path": "component-mail-service" }, "dependencies": { + "@pubsweet/component-send-email": "0.2.1", "body-parser": "^1.17.2", "handlebars": "^4.0.11" }, "peerDependencies": { "@pubsweet/logger": "^0.0.1", - "pubsweet-server": "^1.0.1", - "@pubsweet/component-send-email": "^0.1.0" + "pubsweet-server": "^1.0.1" }, "publishConfig": { "access": "public" diff --git a/packages/component-mail-service/src/Mail.js b/packages/component-mail-service/src/Mail.js index df5e75ba50eb06507cae43596d20af68a18e8f76..11d1f29f20684c90abf2c8e8f0df3e2f3baeb2fc 100644 --- a/packages/component-mail-service/src/Mail.js +++ b/packages/component-mail-service/src/Mail.js @@ -51,6 +51,28 @@ module.exports = { break } + const { htmlBody, textBody } = getEmailBody(emailType, replacements) + const mailData = { + from: config.get('mailer.from'), + to: email, + subject, + text: textBody, + html: htmlBody, + } + return Email.send(mailData) + }, + setupRevokeInvitationEmail: async (email, emailType) => { + let subject + const replacements = {} + switch (emailType) { + case 'revoke-handling-editor': + subject = 'Invitation has been Cancelled' + break + default: + subject = 'Welcome to Hindawi!' + break + } + const { htmlBody, textBody } = getEmailBody(emailType, replacements) const mailData = { from: config.get('mailer.from'), diff --git a/packages/component-mail-service/src/templates/revoke-handling-editor.html b/packages/component-mail-service/src/templates/revoke-handling-editor.html new file mode 100644 index 0000000000000000000000000000000000000000..1570b897d6c628c28036b391150ac9717ee1c4be --- /dev/null +++ b/packages/component-mail-service/src/templates/revoke-handling-editor.html @@ -0,0 +1,211 @@ + +<!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>revoke 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;">Your Handling Editor invitation to a manuscript has been revoked.</h1> + + <div style="text-align: center;"> </div> + + <div style="text-align: center;"> </div> + + </td> + </tr> + </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/revoke-handling-editor.txt b/packages/component-mail-service/src/templates/revoke-handling-editor.txt new file mode 100644 index 0000000000000000000000000000000000000000..e871a6f4c2222bc614910755c4514cfa2f2b3249 --- /dev/null +++ b/packages/component-mail-service/src/templates/revoke-handling-editor.txt @@ -0,0 +1,4 @@ +Your Handling Editor invitation to a manuscript has been revoked. +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/.gitignore b/packages/component-send-email/.gitignore deleted file mode 100644 index 3614a810088d89d9ccaa28d82401545634874a18..0000000000000000000000000000000000000000 --- a/packages/component-send-email/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -_build/ -api/ -logs/ -node_modules/ -uploads/ -.env.* -.env -config/local*.* \ No newline at end of file diff --git a/packages/component-send-email/README.md b/packages/component-send-email/README.md deleted file mode 100644 index 6107c057207697467a41159bcc3f0eee270ae181..0000000000000000000000000000000000000000 --- a/packages/component-send-email/README.md +++ /dev/null @@ -1,42 +0,0 @@ -# AWS SES - -In order to use `component-aws-ses` you first need to have a `.env` file containing AWS data in the root folder of the starting point of your application. - -The `.env` file contain the following data: -```bash -AWS_SES_SECRET_KEY = <secretKey> -AWS_SES_ACCESS_KEY = <accessKey> -EMAIL_SENDER = verified_ses_sender@domain.com -AWS_SES_REGION = region-name -``` - -Then, as soon as possible in your app you should add the `dotenv` package: -```js -require('dotenv').config() -``` - -# `component-aws-ses` API -A list of endpoints that help you upload, download and delete S3 files. - -## Send an email [POST] -#### Request -`POST /api/email` -#### Request body - -All parameters are `required` -```json -{ - "email": "to_email@domain.com", - "subject": "Example subject", - "textBody": "this is an email", - "htmlBody": "<p><b>This</b> is an <i>email</i>" -} -``` -#### Response -```json -HTTP/1.1 204 -``` - - - - diff --git a/packages/component-send-email/index.js b/packages/component-send-email/index.js deleted file mode 100644 index 638be56beb2320e02d60ee4601a570c689d7ba95..0000000000000000000000000000000000000000 --- a/packages/component-send-email/index.js +++ /dev/null @@ -1,3 +0,0 @@ -require('dotenv').config() - -module.exports = require('./src/SendEmail') diff --git a/packages/component-send-email/package.json b/packages/component-send-email/package.json deleted file mode 100644 index 42a1c5d72bf92faf735616688e10c221a5b0cf05..0000000000000000000000000000000000000000 --- a/packages/component-send-email/package.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "@pubsweet/component-send-email", - "version": "0.2.0", - "description": "xpub aws ses configured for faraday", - "license": "MIT", - "files": [ - "src" - ], - "repository": { - "type": "git", - "url": "https://gitlab.coko.foundation/xpub/xpub" - }, - "dependencies": { - "aws-sdk": "^2.185.0", - "nodemailer": "^4.4.2" - }, - "peerDependencies": { - "@pubsweet/logger": "^0.0.1" - } -} diff --git a/packages/component-send-email/src/SendEmail.js b/packages/component-send-email/src/SendEmail.js deleted file mode 100644 index fa16450219d8b3b8cd29d8987f92afd3859f7b42..0000000000000000000000000000000000000000 --- a/packages/component-send-email/src/SendEmail.js +++ /dev/null @@ -1,10 +0,0 @@ -const nodemailer = require('nodemailer') -const config = require('config') - -const mailerConfig = require(config.mailer.path) -module.exports = { - send: mailData => { - const transporter = nodemailer.createTransport(mailerConfig.transport) - return transporter.sendMail(mailData) - }, -} diff --git a/yarn.lock b/yarn.lock index 8ffb522b4d849f6b522ee7d55e86bb0a682a39e8..5f06aabe0960b5f12163e9e80553996086a8d8cf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -90,6 +90,13 @@ node-mocks-http "^1.6.6" nodemailer "^4.4.2" +"@pubsweet/component-send-email@0.2.1": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@pubsweet/component-send-email/-/component-send-email-0.2.1.tgz#9e70412cf20ce618ff42d14e4b09005e96d31ba2" + dependencies: + aws-sdk "^2.185.0" + nodemailer "^4.4.2" + "@pubsweet/db-manager@^1.0.3": version "1.0.3" resolved "https://registry.yarnpkg.com/@pubsweet/db-manager/-/db-manager-1.0.3.tgz#e8784c87eee958ed17daa12f54625774bd2fd277"