Files
overleaf-cep/services/project-history/app/js/LabelsManager.js
Jakob Ackermann 5a6c066847 [web] allow admins to clone projects with ranges and entire history (#32739)
* [web] add consistent aria-label to editing/reviewing toggle

* [docstore] add endpoint for getting all docs with ranges

* [history-v1] fix schema of chunkId when deleting old history chunk

* [web] skip duplicate project lookup for resolving rootDocPath

* [web] ignore new limits for root doc path when making debug copy

* [web] allow admins to clone projects with ranges and entire history

* [web] fix tests

* [history-v1] re-order params for cloning project

* [web] fix duplicate import of logger after merge

* [project-history] re-order params for cloning project history metadata

GitOrigin-RevId: 7fa35b4f90885dd453150a348d491ba0ec8de412
2026-04-15 08:05:49 +00:00

196 lines
4.7 KiB
JavaScript

import OError from '@overleaf/o-error'
import { db, ObjectId } from './mongodb.js'
import * as HistoryStoreManager from './HistoryStoreManager.js'
import * as UpdatesProcessor from './UpdatesProcessor.js'
import * as WebApiManager from './WebApiManager.js'
export function cloneLabels(sourceProjectId, targetProjectId, callback) {
db.projectHistoryLabels
.find({ project_id: new ObjectId(sourceProjectId) })
.project({ _id: 0, project_id: 0 })
.toArray((err, labels) => {
if (err) return callback(OError.tag(err))
if (labels.length === 0) return callback()
db.projectHistoryLabels.insertMany(
labels.map(label => ({
...label,
project_id: new ObjectId(targetProjectId),
})),
err => {
if (err) return callback(OError.tag(err))
callback()
}
)
})
}
export function getLabels(projectId, callback) {
_toObjectId(projectId, function (error, projectId) {
if (error) {
return callback(OError.tag(error))
}
db.projectHistoryLabels
.find({ project_id: new ObjectId(projectId) })
.toArray(function (error, labels) {
if (error) {
return callback(OError.tag(error))
}
const formattedLabels = labels.map(_formatLabel)
callback(null, formattedLabels)
})
})
}
export function createLabel(
projectId,
userId,
version,
comment,
createdAt,
shouldValidateExists,
callback
) {
const validateVersionExists = function (callback) {
if (shouldValidateExists === false) {
callback()
} else {
_validateChunkExistsForVersion(projectId.toString(), version, callback)
}
}
_toObjectId(projectId, userId, function (error, projectId, userId) {
if (error) {
return callback(OError.tag(error))
}
validateVersionExists(function (error) {
if (error) {
return callback(OError.tag(error))
}
createdAt = createdAt != null ? new Date(createdAt) : new Date()
const label = {
project_id: new ObjectId(projectId),
comment,
version,
created_at: createdAt,
}
if (userId) {
label.user_id = userId
}
db.projectHistoryLabels.insertOne(label, function (error, confirmation) {
if (error) {
return callback(OError.tag(error))
}
label._id = confirmation.insertedId
callback(null, _formatLabel(label))
})
})
})
}
export function deleteLabelForUser(projectId, userId, labelId, callback) {
_toObjectId(
projectId,
userId,
labelId,
function (error, projectId, userId, labelId) {
if (error) {
return callback(OError.tag(error))
}
db.projectHistoryLabels.deleteOne(
{
_id: new ObjectId(labelId),
project_id: new ObjectId(projectId),
user_id: new ObjectId(userId),
},
callback
)
}
)
}
export function deleteLabel(projectId, labelId, callback) {
_toObjectId(projectId, labelId, function (error, projectId, labelId) {
if (error) {
return callback(OError.tag(error))
}
db.projectHistoryLabels.deleteOne(
{
_id: new ObjectId(labelId),
project_id: new ObjectId(projectId),
},
callback
)
})
}
export function transferLabels(fromUserId, toUserId, callback) {
_toObjectId(fromUserId, toUserId, function (error, fromUserId, toUserId) {
if (error) {
return callback(OError.tag(error))
}
db.projectHistoryLabels.updateMany(
{
user_id: fromUserId,
},
{
$set: { user_id: toUserId },
},
callback
)
})
}
function _toObjectId(...args1) {
const adjustedLength = Math.max(args1.length, 1)
const args = args1.slice(0, adjustedLength - 1)
const callback = args1[adjustedLength - 1]
try {
const ids = args.map(id => {
if (id) {
return new ObjectId(id)
} else {
return undefined
}
})
callback(null, ...ids)
} catch (error) {
callback(error)
}
}
function _formatLabel(label) {
return {
id: label._id,
comment: label.comment,
version: label.version,
user_id: label.user_id,
created_at: label.created_at,
}
}
function _validateChunkExistsForVersion(projectId, version, callback) {
UpdatesProcessor.processUpdatesForProject(projectId, function (error) {
if (error) {
return callback(error)
}
WebApiManager.getHistoryId(projectId, function (error, historyId) {
if (error) {
return callback(error)
}
HistoryStoreManager.getChunkAtVersion(
projectId,
historyId,
version,
function (error) {
if (error) {
return callback(error)
}
callback()
}
)
})
})
}