Merge pull request #14945 from overleaf/em-promisify-collaborators-invite-handler

Promisify CollaboratorsInviteHandler

GitOrigin-RevId: 070f7938eb1c306905b3b70bef212a09b57cdf20
This commit is contained in:
Eric Mc Sween 2023-10-04 08:20:23 -04:00 committed by Copybot
parent 947f3c8921
commit 872904d73e
4 changed files with 656 additions and 1049 deletions

View file

@ -1,13 +1,9 @@
/* eslint-disable
n/handle-callback-err,
max-len,
*/
let CollaboratorsEmailHandler
const { callbackify } = require('util')
const { Project } = require('../../models/Project')
const EmailHandler = require('../Email/EmailHandler')
const Settings = require('@overleaf/settings')
module.exports = CollaboratorsEmailHandler = {
const CollaboratorsEmailHandler = {
_buildInviteUrl(project, invite) {
return (
`${Settings.siteUrl}/project/${project._id}/invite/token/${invite.token}?` +
@ -18,22 +14,29 @@ module.exports = CollaboratorsEmailHandler = {
)
},
notifyUserOfProjectInvite(projectId, email, invite, sendingUser, callback) {
Project.findOne({ _id: projectId })
async notifyUserOfProjectInvite(projectId, email, invite, sendingUser) {
const project = await Project.findOne({ _id: projectId })
.select('name owner_ref')
.populate('owner_ref')
.exec(function (err, project) {
const emailOptions = {
to: email,
replyTo: project.owner_ref.email,
project: {
name: project.name,
},
inviteUrl: CollaboratorsEmailHandler._buildInviteUrl(project, invite),
owner: project.owner_ref,
sendingUser_id: sendingUser._id,
}
EmailHandler.sendEmail('projectInvite', emailOptions, callback)
})
.exec()
const emailOptions = {
to: email,
replyTo: project.owner_ref.email,
project: {
name: project.name,
},
inviteUrl: CollaboratorsEmailHandler._buildInviteUrl(project, invite),
owner: project.owner_ref,
sendingUser_id: sendingUser._id,
}
await EmailHandler.promises.sendEmail('projectInvite', emailOptions)
},
}
module.exports = {
promises: CollaboratorsEmailHandler,
notifyUserOfProjectInvite: callbackify(
CollaboratorsEmailHandler.notifyUserOfProjectInvite
),
_buildInviteUrl: CollaboratorsEmailHandler._buildInviteUrl,
}

View file

@ -1,17 +1,5 @@
/* eslint-disable
n/handle-callback-err,
max-len,
no-unused-vars,
*/
// TODO: This file was created by bulk-decaffeinate.
// Fix any style issues and re-enable lint.
/*
* decaffeinate suggestions:
* DS207: Consider shorter variations of null checks
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
const { callbackify, promisify } = require('util')
const { ProjectInvite } = require('../../models/ProjectInvite')
const OError = require('@overleaf/o-error')
const logger = require('@overleaf/logger')
const CollaboratorsEmailHandler = require('./CollaboratorsEmailHandler')
const CollaboratorsHandler = require('./CollaboratorsHandler')
@ -20,335 +8,196 @@ const ProjectGetter = require('../Project/ProjectGetter')
const Errors = require('../Errors/Errors')
const Crypto = require('crypto')
const NotificationsBuilder = require('../Notifications/NotificationsBuilder')
const { promisifyAll } = require('../../util/promises')
const randomBytes = promisify(Crypto.randomBytes)
const CollaboratorsInviteHandler = {
getAllInvites(projectId, callback) {
if (callback == null) {
callback = function () {}
}
async getAllInvites(projectId) {
logger.debug({ projectId }, 'fetching invites for project')
ProjectInvite.find({ projectId }, function (err, invites) {
if (err != null) {
OError.tag(err, 'error getting invites from mongo', {
projectId,
})
return callback(err)
}
logger.debug(
{ projectId, count: invites.length },
'found invites for project'
)
callback(null, invites)
})
},
getInviteCount(projectId, callback) {
if (callback == null) {
callback = function () {}
}
logger.debug({ projectId }, 'counting invites for project')
ProjectInvite.countDocuments({ projectId }, function (err, count) {
if (err != null) {
OError.tag(err, 'error getting invites from mongo', {
projectId,
})
return callback(err)
}
callback(null, count)
})
},
_trySendInviteNotification(projectId, sendingUser, invite, callback) {
if (callback == null) {
callback = function () {}
}
const { email } = invite
UserGetter.getUserByAnyEmail(
email,
{ _id: 1 },
function (err, existingUser) {
if (err != null) {
OError.tag(err, 'error checking if user exists', {
projectId,
email,
})
return callback(err)
}
if (existingUser == null) {
logger.debug(
{ projectId, email },
'no existing user found, returning'
)
return callback(null)
}
ProjectGetter.getProject(
projectId,
{ _id: 1, name: 1 },
function (err, project) {
if (err != null) {
OError.tag(err, 'error getting project', {
projectId,
email,
})
return callback(err)
}
if (project == null) {
logger.debug(
{ projectId },
'no project found while sending notification, returning'
)
return callback(null)
}
NotificationsBuilder.projectInvite(
invite,
project,
sendingUser,
existingUser
).create(callback)
}
)
}
const invites = await ProjectInvite.find({ projectId }).exec()
logger.debug(
{ projectId, count: invites.length },
'found invites for project'
)
return invites
},
_tryCancelInviteNotification(inviteId, callback) {
if (callback == null) {
callback = function () {}
}
NotificationsBuilder.projectInvite(
{ _id: inviteId },
null,
null,
null
).read(callback)
async getInviteCount(projectId) {
logger.debug({ projectId }, 'counting invites for project')
const count = await ProjectInvite.countDocuments({ projectId }).exec()
return count
},
_sendMessages(projectId, sendingUser, invite, callback) {
if (callback == null) {
callback = function () {}
async _trySendInviteNotification(projectId, sendingUser, invite) {
const { email } = invite
const existingUser = await UserGetter.promises.getUserByAnyEmail(email, {
_id: 1,
})
if (existingUser == null) {
logger.debug({ projectId, email }, 'no existing user found, returning')
return null
}
const project = await ProjectGetter.promises.getProject(projectId, {
_id: 1,
name: 1,
})
if (project == null) {
logger.debug(
{ projectId },
'no project found while sending notification, returning'
)
return null
}
await NotificationsBuilder.promises
.projectInvite(invite, project, sendingUser, existingUser)
.create()
},
async _tryCancelInviteNotification(inviteId) {
return await NotificationsBuilder.promises
.projectInvite({ _id: inviteId }, null, null, null)
.read()
},
async _sendMessages(projectId, sendingUser, invite) {
logger.debug(
{ projectId, inviteId: invite._id },
'sending notification and email for invite'
)
CollaboratorsEmailHandler.notifyUserOfProjectInvite(
await CollaboratorsEmailHandler.promises.notifyUserOfProjectInvite(
projectId,
invite.email,
invite,
sendingUser
)
await CollaboratorsInviteHandler._trySendInviteNotification(
projectId,
sendingUser,
function (err) {
if (err != null) {
return callback(err)
}
CollaboratorsInviteHandler._trySendInviteNotification(
projectId,
sendingUser,
invite,
function (err) {
if (err != null) {
return callback(err)
}
callback()
}
)
}
invite
)
},
inviteToProject(projectId, sendingUser, email, privileges, callback) {
if (callback == null) {
callback = function () {}
}
async inviteToProject(projectId, sendingUser, email, privileges) {
logger.debug(
{ projectId, sendingUserId: sendingUser._id, email, privileges },
'adding invite'
)
Crypto.randomBytes(24, function (err, buffer) {
if (err != null) {
OError.tag(err, 'error generating random token', {
projectId,
sendingUserId: sendingUser._id,
email,
})
return callback(err)
}
const token = buffer.toString('hex')
const invite = new ProjectInvite({
email,
token,
sendingUserId: sendingUser._id,
projectId,
privileges,
})
invite.save(function (err, invite) {
if (err != null) {
OError.tag(err, 'error saving token', {
projectId,
sendingUserId: sendingUser._id,
email,
})
return callback(err)
}
// Send email and notification in background
CollaboratorsInviteHandler._sendMessages(
projectId,
sendingUser,
invite,
function (err) {
if (err != null) {
return logger.err(
{ err, projectId, email },
'error sending messages for invite'
)
}
}
)
callback(null, invite)
})
const buffer = await randomBytes(24)
const token = buffer.toString('hex')
let invite = new ProjectInvite({
email,
token,
sendingUserId: sendingUser._id,
projectId,
privileges,
})
invite = await invite.save()
// Send email and notification in background
CollaboratorsInviteHandler._sendMessages(
projectId,
sendingUser,
invite
).catch(err => {
logger.err({ err, projectId, email }, 'error sending messages for invite')
})
return invite
},
revokeInvite(projectId, inviteId, callback) {
if (callback == null) {
callback = function () {}
}
async revokeInvite(projectId, inviteId) {
logger.debug({ projectId, inviteId }, 'removing invite')
ProjectInvite.deleteOne({ projectId, _id: inviteId }, function (err) {
if (err != null) {
OError.tag(err, 'error removing invite', {
projectId,
inviteId,
})
return callback(err)
}
CollaboratorsInviteHandler._tryCancelInviteNotification(
inviteId,
function () {}
)
callback(null)
})
},
resendInvite(projectId, sendingUser, inviteId, callback) {
if (callback == null) {
callback = function () {}
}
logger.debug({ projectId, inviteId }, 'resending invite email')
ProjectInvite.findOne({ _id: inviteId, projectId }, function (err, invite) {
if (err != null) {
OError.tag(err, 'error finding invite', {
projectId,
inviteId,
})
return callback(err)
}
if (invite == null) {
await ProjectInvite.deleteOne({ projectId, _id: inviteId }).exec()
CollaboratorsInviteHandler._tryCancelInviteNotification(inviteId).catch(
err => {
logger.err(
{ err, projectId, inviteId },
'no invite found, nothing to resend'
'failed to cancel invite notification'
)
return callback(null)
}
CollaboratorsInviteHandler._sendMessages(
projectId,
sendingUser,
invite,
function (err) {
if (err != null) {
OError.tag(err, 'error resending invite messages', {
projectId,
inviteId,
})
return callback(err)
}
callback(null)
}
)
})
},
getInviteByToken(projectId, tokenString, callback) {
if (callback == null) {
callback = function () {}
}
logger.debug({ projectId }, 'fetching invite by token')
ProjectInvite.findOne(
{ projectId, token: tokenString },
function (err, invite) {
if (err != null) {
OError.tag(err, 'error fetching invite', {
projectId,
})
return callback(err)
}
if (invite == null) {
logger.err({ err, projectId }, 'no invite found')
return callback(null, null)
}
callback(null, invite)
}
)
},
acceptInvite(projectId, tokenString, user, callback) {
if (callback == null) {
callback = function () {}
}
logger.debug({ projectId, userId: user._id }, 'accepting invite')
CollaboratorsInviteHandler.getInviteByToken(
async resendInvite(projectId, sendingUser, inviteId) {
logger.debug({ projectId, inviteId }, 'resending invite email')
const invite = await ProjectInvite.findOne({
_id: inviteId,
projectId,
tokenString,
function (err, invite) {
if (err != null) {
OError.tag(err, 'error finding invite', {
projectId,
tokenString,
})
return callback(err)
}
if (!invite) {
err = new Errors.NotFoundError('no matching invite found')
logger.debug({ err, projectId }, 'no matching invite found')
return callback(err)
}
const inviteId = invite._id
CollaboratorsHandler.addUserIdToProject(
projectId,
invite.sendingUserId,
user._id,
invite.privileges,
function (err) {
if (err != null) {
OError.tag(err, 'error adding user to project', {
projectId,
inviteId,
userId: user._id,
})
return callback(err)
}
// Remove invite
logger.debug({ projectId, inviteId }, 'removing invite')
ProjectInvite.deleteOne({ _id: inviteId }, function (err) {
if (err != null) {
OError.tag(err, 'error removing invite', {
projectId,
inviteId,
})
return callback(err)
}
CollaboratorsInviteHandler._tryCancelInviteNotification(
inviteId,
function () {}
)
callback()
})
}
}).exec()
if (invite == null) {
logger.warn({ projectId, inviteId }, 'no invite found, nothing to resend')
return
}
await CollaboratorsInviteHandler._sendMessages(
projectId,
sendingUser,
invite
)
},
async getInviteByToken(projectId, tokenString) {
logger.debug({ projectId }, 'fetching invite by token')
const invite = await ProjectInvite.findOne({
projectId,
token: tokenString,
}).exec()
if (invite == null) {
logger.err({ projectId }, 'no invite found')
return null
}
return invite
},
async acceptInvite(projectId, tokenString, user) {
logger.debug({ projectId, userId: user._id }, 'accepting invite')
const invite = await CollaboratorsInviteHandler.getInviteByToken(
projectId,
tokenString
)
if (!invite) {
throw new Errors.NotFoundError('no matching invite found')
}
const inviteId = invite._id
CollaboratorsHandler.promises.addUserIdToProject(
projectId,
invite.sendingUserId,
user._id,
invite.privileges
)
// Remove invite
logger.debug({ projectId, inviteId }, 'removing invite')
await ProjectInvite.deleteOne({ _id: inviteId }).exec()
CollaboratorsInviteHandler._tryCancelInviteNotification(inviteId).catch(
err => {
logger.error(
{ err, projectId, inviteId },
'failed to cancel invite notification'
)
}
)
},
}
module.exports = CollaboratorsInviteHandler
module.exports.promises = promisifyAll(CollaboratorsInviteHandler)
module.exports = {
promises: CollaboratorsInviteHandler,
getAllInvites: callbackify(CollaboratorsInviteHandler.getAllInvites),
getInviteCount: callbackify(CollaboratorsInviteHandler.getInviteCount),
inviteToProject: callbackify(CollaboratorsInviteHandler.inviteToProject),
revokeInvite: callbackify(CollaboratorsInviteHandler.revokeInvite),
resendInvite: callbackify(CollaboratorsInviteHandler.resendInvite),
getInviteByToken: callbackify(CollaboratorsInviteHandler.getInviteByToken),
acceptInvite: callbackify(CollaboratorsInviteHandler.acceptInvite),
_trySendInviteNotification: callbackify(
CollaboratorsInviteHandler._trySendInviteNotification
),
_tryCancelInviteNotification: callbackify(
CollaboratorsInviteHandler._tryCancelInviteNotification
),
_sendMessages: callbackify(CollaboratorsInviteHandler._sendMessages),
}

View file

@ -283,6 +283,9 @@ NotificationsBuilder.promises = {
groupInvitation: function (userId, groupId, managedUsersEnabled) {
return promisifyAll(groupInvitation(userId, groupId, managedUsersEnabled))
},
projectInvite(invite, project, sendingUser, user) {
return promisifyAll(projectInvite(invite, project, sendingUser, user))
},
}
module.exports = NotificationsBuilder