mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-05-30 20:31:34 +02:00
[web] Convert some Features files to ES modules (part 1) GitOrigin-RevId: d19b024efad315143e022143e2a2683df8071744
241 lines
6.7 KiB
JavaScript
241 lines
6.7 KiB
JavaScript
import logger from '@overleaf/logger'
|
|
import { Project } from '../../models/Project.js'
|
|
import ProjectGetter from '../Project/ProjectGetter.js'
|
|
import UserGetter from '../User/UserGetter.js'
|
|
import CollaboratorsHandler from './CollaboratorsHandler.js'
|
|
import EmailHandler from '../Email/EmailHandler.js'
|
|
import Errors from '../Errors/Errors.js'
|
|
import PrivilegeLevels from '../Authorization/PrivilegeLevels.js'
|
|
import TpdsProjectFlusher from '../ThirdPartyDataStore/TpdsProjectFlusher.js'
|
|
import ProjectAuditLogHandler from '../Project/ProjectAuditLogHandler.js'
|
|
import AnalyticsManager from '../Analytics/AnalyticsManager.js'
|
|
import OError from '@overleaf/o-error'
|
|
import TagsHandler from '../Tags/TagsHandler.js'
|
|
import { promiseMapWithLimit } from '@overleaf/promise-utils'
|
|
|
|
export default {
|
|
promises: {
|
|
transferOwnership,
|
|
transferAllProjectsToUser,
|
|
},
|
|
}
|
|
|
|
const TAG_COLOR_BLUE = '#434AF0'
|
|
|
|
/**
|
|
* @param {string} fromUserId
|
|
* @param {string} toUserId
|
|
* @param {string} ipAddress
|
|
* @return {Promise<{projectCount: number, newTagName: string}>}
|
|
*/
|
|
async function transferAllProjectsToUser({ fromUserId, toUserId, ipAddress }) {
|
|
// - Verify that both users exist
|
|
const fromUser = await UserGetter.promises.getUser(fromUserId, {
|
|
_id: 1,
|
|
email: 1,
|
|
})
|
|
const toUser = await UserGetter.promises.getUser(toUserId, { _id: 1 })
|
|
if (!fromUser) throw new OError('missing source user', { fromUserId })
|
|
if (!toUser) throw new OError('missing destination user', { toUserId })
|
|
if (fromUser._id.equals(toUser._id))
|
|
throw new OError('rejecting transfer between identical users', {
|
|
fromUserId,
|
|
toUserId,
|
|
})
|
|
logger.debug(
|
|
{ fromUserId, toUserId },
|
|
'started bulk transfer of all projects from one user to another'
|
|
)
|
|
// - Get all owned projects for fromUserId
|
|
const projects = await Project.find({ owner_ref: fromUserId }, { _id: 1 })
|
|
|
|
// - Create new tag on toUserId
|
|
const newTag = await TagsHandler.promises.createTag(
|
|
toUserId,
|
|
`transferred-from-${fromUser.email}`,
|
|
TAG_COLOR_BLUE,
|
|
{ truncate: true }
|
|
)
|
|
|
|
// - Add tag to projects (can happen before ownership is transferred)
|
|
await TagsHandler.promises.addProjectsToTag(
|
|
toUserId,
|
|
newTag._id,
|
|
projects.map(p => p._id)
|
|
)
|
|
|
|
// - Transfer all projects
|
|
await promiseMapWithLimit(5, projects, async project => {
|
|
await transferOwnership(project._id, toUserId, {
|
|
allowTransferToNonCollaborators: true,
|
|
skipEmails: true,
|
|
ipAddress,
|
|
})
|
|
})
|
|
|
|
logger.debug(
|
|
{ fromUserId, toUserId },
|
|
'finished bulk transfer of all projects from one user to another'
|
|
)
|
|
return { projectCount: projects.length, newTagName: newTag.name }
|
|
}
|
|
|
|
async function transferOwnership(projectId, newOwnerId, options = {}) {
|
|
const {
|
|
allowTransferToNonCollaborators,
|
|
sessionUserId,
|
|
skipEmails,
|
|
ipAddress,
|
|
} = options
|
|
|
|
// Fetch project and user
|
|
const [project, newOwner] = await Promise.all([
|
|
_getProject(projectId),
|
|
_getUser(newOwnerId),
|
|
])
|
|
|
|
// Exit early if the transferee is already the project owner
|
|
const previousOwnerId = project.owner_ref
|
|
if (previousOwnerId.equals(newOwnerId)) {
|
|
return
|
|
}
|
|
|
|
// Check that user is already a collaborator
|
|
if (
|
|
!allowTransferToNonCollaborators &&
|
|
!_userIsCollaborator(newOwner, project)
|
|
) {
|
|
throw new Errors.UserNotCollaboratorError({ info: { userId: newOwnerId } })
|
|
}
|
|
|
|
// Track the change of ownership in BigQuery.
|
|
AnalyticsManager.recordEventForUserInBackground(
|
|
previousOwnerId,
|
|
'project-ownership-transfer',
|
|
{ projectId, newOwnerId }
|
|
)
|
|
|
|
// Transfer ownership
|
|
await ProjectAuditLogHandler.promises.addEntry(
|
|
projectId,
|
|
'transfer-ownership',
|
|
sessionUserId,
|
|
ipAddress,
|
|
{ previousOwnerId, newOwnerId }
|
|
)
|
|
|
|
// Determine which permissions to give old owner based on
|
|
// new owner's existing permissions
|
|
const newPermissions =
|
|
_getUserPermissions(newOwner, project) || PrivilegeLevels.READ_ONLY
|
|
|
|
await _transferOwnership(
|
|
projectId,
|
|
previousOwnerId,
|
|
newOwnerId,
|
|
newPermissions
|
|
)
|
|
|
|
// Flush project to TPDS
|
|
await TpdsProjectFlusher.promises.flushProjectToTpds(projectId)
|
|
|
|
// Send confirmation emails
|
|
if (!skipEmails) {
|
|
const previousOwner = await UserGetter.promises.getUser(previousOwnerId)
|
|
await _sendEmails(project, previousOwner, newOwner)
|
|
}
|
|
}
|
|
|
|
async function _getProject(projectId) {
|
|
const project = await ProjectGetter.promises.getProject(projectId, {
|
|
owner_ref: 1,
|
|
collaberator_refs: 1,
|
|
readOnly_refs: 1,
|
|
reviewer_refs: 1,
|
|
name: 1,
|
|
})
|
|
if (project == null) {
|
|
throw new Errors.ProjectNotFoundError({ info: { projectId } })
|
|
}
|
|
return project
|
|
}
|
|
|
|
async function _getUser(userId) {
|
|
const user = await UserGetter.promises.getUser(userId)
|
|
if (user == null) {
|
|
throw new Errors.UserNotFoundError({ info: { userId } })
|
|
}
|
|
return user
|
|
}
|
|
|
|
function _getUserPermissions(user, project) {
|
|
const collaboratorIds = project.collaberator_refs || []
|
|
const readOnlyIds = project.readOnly_refs || []
|
|
const reviewerIds = project.reviewer_refs || []
|
|
if (collaboratorIds.some(collaboratorId => collaboratorId.equals(user._id))) {
|
|
return PrivilegeLevels.READ_AND_WRITE
|
|
} else if (
|
|
reviewerIds.some(collaboratorId => collaboratorId.equals(user._id))
|
|
) {
|
|
return PrivilegeLevels.REVIEW
|
|
} else if (
|
|
readOnlyIds.some(collaboratorId => collaboratorId.equals(user._id))
|
|
) {
|
|
return PrivilegeLevels.READ_ONLY
|
|
}
|
|
}
|
|
|
|
function _userIsCollaborator(user, project) {
|
|
return Boolean(_getUserPermissions(user, project))
|
|
}
|
|
|
|
async function _transferOwnership(
|
|
projectId,
|
|
previousOwnerId,
|
|
newOwnerId,
|
|
newPermissions
|
|
) {
|
|
await CollaboratorsHandler.promises.removeUserFromProject(
|
|
projectId,
|
|
newOwnerId
|
|
)
|
|
await Project.updateOne(
|
|
{ _id: projectId },
|
|
{ $set: { owner_ref: newOwnerId } }
|
|
).exec()
|
|
await CollaboratorsHandler.promises.addUserIdToProject(
|
|
projectId,
|
|
newOwnerId,
|
|
previousOwnerId,
|
|
newPermissions
|
|
)
|
|
}
|
|
|
|
async function _sendEmails(project, previousOwner, newOwner) {
|
|
if (previousOwner == null) {
|
|
// The previous owner didn't exist. This is not supposed to happen, but
|
|
// since we're changing the owner anyway, we'll just warn
|
|
logger.warn(
|
|
{ projectId: project._id, ownerId: previousOwner._id },
|
|
'Project owner did not exist before ownership transfer'
|
|
)
|
|
} else {
|
|
// Send confirmation emails
|
|
await Promise.all([
|
|
EmailHandler.promises.sendEmail(
|
|
'ownershipTransferConfirmationPreviousOwner',
|
|
{
|
|
to: previousOwner.email,
|
|
project,
|
|
newOwner,
|
|
}
|
|
),
|
|
EmailHandler.promises.sendEmail('ownershipTransferConfirmationNewOwner', {
|
|
to: newOwner.email,
|
|
project,
|
|
previousOwner,
|
|
}),
|
|
])
|
|
}
|
|
}
|