Files
overleaf-cep/services/web/modules/track-changes/app/src/TrackChangesController.js
2025-05-30 00:31:11 +02:00

195 lines
7.4 KiB
JavaScript

const ChatApiHandler = require('../../../../app/src/Features/Chat/ChatApiHandler')
const ChatManager = require('../../../../app/src/Features/Chat/ChatManager')
const EditorRealTimeController = require('../../../../app/src/Features/Editor/EditorRealTimeController')
const SessionManager = require('../../../../app/src/Features/Authentication/SessionManager')
const UserInfoManager = require('../../../../app/src/Features/User/UserInfoManager')
const DocstoreManager = require('../../../../app/src/Features/Docstore/DocstoreManager')
const DocumentUpdaterHandler = require('../../../../app/src/Features/DocumentUpdater/DocumentUpdaterHandler')
const CollaboratorsGetter = require('../../../../app/src/Features/Collaborators/CollaboratorsGetter')
const { Project } = require('../../../../app/src/models/Project')
const pLimit = require('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
// Flushing the project to mongo is not ideal. Is it possible to fetch the ranges from redis?
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) {
try {
const { project_id } = req.params
const memberIds = await CollaboratorsGetter.promises.getMemberIds(project_id)
// FIXME: Fails to display names in changes made by former project collaborators.
// See the alternative below. However, it requires flushing the project to mongo, which is not ideal.
const limit = pLimit(3)
const users = await Promise.all(
memberIds.map(memberId =>
limit(async () => {
const user = await UserInfoManager.promises.getPersonalInfo(memberId)
return user
})
)
)
users.push({_id: null}) // An anonymous user won't cause any harm
res.json(users.map(_transformId))
} catch (err) {
next(err)
}
},
/*
async getChangesUsers(req, res, next) {
try {
const { project_id } = req.params
await DocumentUpdaterHandler.promises.flushProjectToMongo(project_id)
const memberIds = new Set()
const ranges = await DocstoreManager.promises.getAllRanges(project_id)
ranges.forEach(range => {
;[...range.ranges?.changes || [], ...range.ranges?.comments || []].forEach(item => {
memberIds.add(item.metadata?.user_id)
})
})
const limit = pLimit(3)
const users = await Promise.all(
[...memberIds].map(memberId =>
limit(async () => {
if( memberId !== "anonymous-user") {
return await UserInfoManager.promises.getPersonalInfo(memberId)
} else {
return {_id: null}
}
})
)
)
res.json(users.map(_transformId))
} 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)
message.user = await UserInfoManager.promises.getPersonalInfo(user_id)
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, 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)
}
},
}
module.exports = TrackChangesController