Compare commits

...

4 commits

5 changed files with 253 additions and 1 deletions

View file

@ -4,7 +4,7 @@ const Path = require('path')
const Features = require('../../infrastructure/Features')
module.exports = ProjectEditorHandler = {
trackChangesAvailable: false,
trackChangesAvailable: true,
buildProjectModelView(
project,

View file

@ -1030,6 +1030,7 @@ module.exports = {
'launchpad',
'server-ce-scripts',
'user-activate',
'track-changes',
],
viewIncludes: {},

View file

@ -0,0 +1,172 @@
import ChatApiHandler from '../../../../app/src/Features/Chat/ChatApiHandler.js'
import ChatManager from '../../../../app/src/Features/Chat/ChatManager.js'
import EditorRealTimeController from '../../../../app/src/Features/Editor/EditorRealTimeController.js'
import SessionManager from '../../../../app/src/Features/Authentication/SessionManager.js'
import UserInfoManager from '../../../../app/src/Features/User/UserInfoManager.js'
import UserInfoController from '../../../../app/src/Features/User/UserInfoController.js'
import DocstoreManager from '../../../../app/src/Features/Docstore/DocstoreManager.js'
import DocumentUpdaterHandler from '../../../../app/src/Features/DocumentUpdater/DocumentUpdaterHandler.js'
import CollaboratorsGetter from '../../../../app/src/Features/Collaborators/CollaboratorsGetter.js'
import { Project } from '../../../../app/src/models/Project.js'
import pLimit from 'p-limit'
function _transformId(doc) {
if (doc._id) {
doc.id = doc._id
delete doc._id
}
return doc
}
const TrackChangesController = {
async trackChanges(req, res, next) {
try {
const { project_id } = req.params
let state = req.body.on || req.body.on_for
if (req.body.on_for_guests && !req.body.on) state.__guests__ = true
await Project.updateOne({_id: project_id}, {track_changes: state}).exec() //do not wait?
EditorRealTimeController.emitToRoom(project_id, 'toggle-track-changes', state)
res.sendStatus(204)
} catch (err) {
next(err)
}
},
async acceptChanges(req, res, next) {
try {
const { project_id, doc_id } = req.params
const change_ids = req.body.change_ids
EditorRealTimeController.emitToRoom(project_id, 'accept-changes', doc_id, change_ids)
await DocumentUpdaterHandler.promises.acceptChanges(project_id, doc_id, change_ids)
res.sendStatus(204)
} catch (err) {
next(err)
}
},
async getAllRanges(req, res, next) {
try {
const { project_id } = req.params
await DocumentUpdaterHandler.promises.flushProjectToMongo(project_id)
const ranges = await DocstoreManager.promises.getAllRanges(project_id)
res.json(ranges.map(_transformId))
} catch (err) {
next(err)
}
},
async getChangesUsers(req, res, next) {
// This route was previously used by the frontend to retrieve names of users who made changes or comments.
// review-panel-new no longer needs this for comments, but still relies on it for changes -
// although the frontend knows the names of the current owner and members, it depends on the data
// provided here to assign names to authors who have left the project but still have unaccepted changes.
try {
const { project_id } = req.params
const memberIds = await DocstoreManager.promises.getTrackedChangesUserIds(project_id)
const limit = pLimit(3)
const users = await Promise.all(
memberIds.map(memberId =>
limit(async () => {
const user = await UserInfoManager.promises.getPersonalInfo(memberId)
return UserInfoController.formatPersonalInfo(user)
})
)
)
res.json(users)
} catch (err) {
next(err)
}
},
async getThreads(req, res, next) {
try {
const { project_id } = req.params
const messages = await ChatApiHandler.promises.getThreads(project_id)
await ChatManager.promises.injectUserInfoIntoThreads(messages)
res.json(messages)
} catch (err) {
next(err)
}
},
async sendComment(req, res, next) {
try {
const { project_id, thread_id } = req.params
const { content } = req.body
const user_id = SessionManager.getLoggedInUserId(req.session)
if (!user_id) throw new Error('no logged-in user')
const message = await ChatApiHandler.promises.sendComment(project_id, thread_id, user_id, content)
const user = await UserInfoManager.promises.getPersonalInfo(user_id)
message.user = UserInfoController.formatPersonalInfo(user)
EditorRealTimeController.emitToRoom(project_id, 'new-comment', thread_id, message)
res.sendStatus(204)
} catch (err) {
next(err)
}
},
async editMessage(req, res, next) {
try {
const { project_id, thread_id, message_id } = req.params
const { content } = req.body
const user_id = SessionManager.getLoggedInUserId(req.session)
if (!user_id) throw new Error('no logged-in user')
await ChatApiHandler.promises.editMessage(project_id, thread_id, message_id, user_id, content)
EditorRealTimeController.emitToRoom(project_id, 'edit-message', thread_id, message_id, content)
res.sendStatus(204)
} catch (err) {
next(err)
}
},
async deleteMessage(req, res, next) {
try {
const { project_id, thread_id, message_id } = req.params
await ChatApiHandler.promises.deleteMessage(project_id, thread_id, message_id)
EditorRealTimeController.emitToRoom(project_id, 'delete-message', thread_id, message_id)
res.sendStatus(204)
} catch (err) {
next(err)
}
},
async resolveThread(req, res, next) {
try {
const { project_id, doc_id, thread_id } = req.params
const user_id = SessionManager.getLoggedInUserId(req.session)
if (!user_id) throw new Error('no logged-in user')
const user = await UserInfoManager.promises.getPersonalInfo(user_id)
await ChatApiHandler.promises.resolveThread(project_id, thread_id, user_id)
EditorRealTimeController.emitToRoom(
project_id,
'resolve-thread',
thread_id,
UserInfoController.formatPersonalInfo(user)
)
await DocumentUpdaterHandler.promises.resolveThread(project_id, doc_id, thread_id, user_id)
res.sendStatus(204)
} catch (err) {
next(err)
}
},
async reopenThread(req, res, next) {
try {
const { project_id, doc_id, thread_id } = req.params
const user_id = SessionManager.getLoggedInUserId(req.session)
if (!user_id) throw new Error('no logged-in user')
await ChatApiHandler.promises.reopenThread(project_id, thread_id)
EditorRealTimeController.emitToRoom(project_id, 'reopen-thread', thread_id)
await DocumentUpdaterHandler.promises.reopenThread(project_id, doc_id, thread_id, user_id)
res.sendStatus(204)
} catch (err) {
next(err)
}
},
async deleteThread(req, res, next) {
try {
const { project_id, doc_id, thread_id } = req.params
const user_id = SessionManager.getLoggedInUserId(req.session)
if (!user_id) throw new Error('no logged-in user')
await ChatApiHandler.promises.deleteThread(project_id, thread_id)
EditorRealTimeController.emitToRoom(project_id, 'delete-thread', thread_id)
await DocumentUpdaterHandler.promises.deleteThread(project_id, doc_id, thread_id, user_id)
res.sendStatus(204)
} catch (err) {
next(err)
}
},
}
export default TrackChangesController

View file

@ -0,0 +1,76 @@
import logger from '@overleaf/logger'
import AuthorizationMiddleware from '../../../../app/src/Features/Authorization/AuthorizationMiddleware.js'
import TrackChangesController from './TrackChangesController.mjs'
export default {
apply(webRouter) {
logger.debug({}, 'Init track-changes router')
webRouter.post(
'/project/:project_id/track_changes',
AuthorizationMiddleware.blockRestrictedUserFromProject,
AuthorizationMiddleware.ensureUserCanReadProject,
TrackChangesController.trackChanges
)
webRouter.post(
'/project/:project_id/doc/:doc_id/changes/accept',
AuthorizationMiddleware.blockRestrictedUserFromProject,
AuthorizationMiddleware.ensureUserCanReadProject,
TrackChangesController.acceptChanges
)
webRouter.get(
'/project/:project_id/ranges',
AuthorizationMiddleware.blockRestrictedUserFromProject,
AuthorizationMiddleware.ensureUserCanReadProject,
TrackChangesController.getAllRanges
)
webRouter.get(
'/project/:project_id/changes/users',
AuthorizationMiddleware.blockRestrictedUserFromProject,
AuthorizationMiddleware.ensureUserCanReadProject,
TrackChangesController.getChangesUsers
)
webRouter.get(
'/project/:project_id/threads',
AuthorizationMiddleware.blockRestrictedUserFromProject,
AuthorizationMiddleware.ensureUserCanReadProject,
TrackChangesController.getThreads
)
webRouter.post(
'/project/:project_id/thread/:thread_id/messages',
AuthorizationMiddleware.blockRestrictedUserFromProject,
AuthorizationMiddleware.ensureUserCanReadProject,
TrackChangesController.sendComment
)
webRouter.post(
'/project/:project_id/thread/:thread_id/messages/:message_id/edit',
AuthorizationMiddleware.blockRestrictedUserFromProject,
AuthorizationMiddleware.ensureUserCanReadProject,
TrackChangesController.editMessage
)
webRouter.delete(
'/project/:project_id/thread/:thread_id/messages/:message_id',
AuthorizationMiddleware.blockRestrictedUserFromProject,
AuthorizationMiddleware.ensureUserCanReadProject,
TrackChangesController.deleteMessage
)
webRouter.post(
'/project/:project_id/doc/:doc_id/thread/:thread_id/resolve',
AuthorizationMiddleware.blockRestrictedUserFromProject,
AuthorizationMiddleware.ensureUserCanReadProject,
TrackChangesController.resolveThread
)
webRouter.post(
'/project/:project_id/doc/:doc_id/thread/:thread_id/reopen',
AuthorizationMiddleware.blockRestrictedUserFromProject,
AuthorizationMiddleware.ensureUserCanReadProject,
TrackChangesController.reopenThread
)
webRouter.delete(
'/project/:project_id/doc/:doc_id/thread/:thread_id',
AuthorizationMiddleware.blockRestrictedUserFromProject,
AuthorizationMiddleware.ensureUserCanReadProject,
TrackChangesController.deleteThread
)
}
}

View file

@ -0,0 +1,3 @@
import TrackChangesRouter from './app/src/TrackChangesRouter.mjs'
const TrackChangesModule = { router: TrackChangesRouter }
export default TrackChangesModule