fix: cloning projects failed with zotero- #253

This commit is contained in:
David Rotermund
2026-10-25 18:03:17 +00:00
parent 868bed0ed9
commit f54bf1c0db
2 changed files with 97 additions and 1 deletions
@@ -68,6 +68,7 @@ async function refreshLinkedFile(
parentFolderId,
userId
) {
linkedFileData = _normalizeLegacyData(linkedFileData)
logger.debug(
{ projectId, userId, linkedFileData },
'refreshing Zotero linked file'
@@ -108,7 +109,7 @@ async function _getBibtex(linkedFileData) {
return await ZoteroApiClient.getLibraryBibtex(
userId,
linkedFileData.group_id, // == null for main library
linkedFileData.format || 'bibtex'
linkedFileData.format
)
} catch (err) {
@@ -129,6 +130,24 @@ async function _getBibtex(linkedFileData) {
}
}
// Files imported before "Zotero: fix for 6.3.0" use the old key names.
function _normalizeLegacyData(data) {
const {
zoteroGroupId,
importedByUserId,
importedByName,
bibFormat,
...rest
} = data
return {
...rest,
group_id: rest.group_id ?? zoteroGroupId ?? null,
importer_id: rest.importer_id ?? importedByUserId,
importer_name: rest.importer_name ?? importedByName,
format: rest.format ?? bibFormat,
}
}
function _sanitizeData(data) {
return {
provider: 'zotero',
@@ -0,0 +1,77 @@
import { batchedUpdate } from '@overleaf/mongo-utils/batchedUpdate.js'
import { promiseMapWithLimit } from '@overleaf/promise-utils'
import { db } from './lib/mongodb.mjs'
const tags = ['server-ce', 'server-pro']
const WRITE_CONCURRENCY = parseInt(process.env.WRITE_CONCURRENCY, 10) || 10
// The Zotero module used to store linkedFileData with its own key names.
// "Zotero: fix for 6.3.0" switched to the names accepted by the
// overleaf-editor-core schema, but files imported before that still carry the
// old keys, which document-updater rejects (e.g. when cloning the project).
const LEGACY_KEYS = [
'zoteroGroupId',
'importedByUserId',
'importedByName',
'bibFormat',
]
function hasLegacyKeys(data) {
return (
data?.provider === 'zotero' && LEGACY_KEYS.some(key => key in data)
)
}
function convert(data) {
const importerId = data.importer_id ?? data.importedByUserId
const format = data.format ?? data.bibFormat
return {
provider: 'zotero',
group_id: data.group_id ?? data.zoteroGroupId ?? null,
...(data.importedAt && { importedAt: data.importedAt }),
...(importerId && { importer_id: importerId.toString() }),
importer_name: data.importer_name ?? data.importedByName ?? 'Unknown',
format: format === 'biblatex' ? 'biblatex' : 'bibtex',
}
}
function collectUpdates(folder, mongoPath, updates) {
if (!folder) return
;(folder.fileRefs || []).forEach((file, i) => {
if (hasLegacyKeys(file?.linkedFileData)) {
updates[`${mongoPath}.fileRefs.${i}.linkedFileData`] = convert(
file.linkedFileData
)
}
})
;(folder.folders || []).forEach((subfolder, i) => {
collectUpdates(subfolder, `${mongoPath}.folders.${i}`, updates)
})
}
const migrate = async () => {
await batchedUpdate(
db.projects,
{},
async nextBatch => {
await promiseMapWithLimit(WRITE_CONCURRENCY, nextBatch, async project => {
const updates = {}
;(project.rootFolder || []).forEach((folder, i) => {
collectUpdates(folder, `rootFolder.${i}`, updates)
})
if (Object.keys(updates).length === 0) return
await db.projects.updateOne({ _id: project._id }, { $set: updates })
})
},
{ _id: 1, rootFolder: 1 }
)
}
const rollback = async () => {}
export default {
tags,
migrate,
rollback,
}