[web] Add another partial fix for fix_malformed_filetree: use _id instead of path to locate data (#24101)

* Fix `fix_malformed_filetree`'s `fixName`

* Fix findUniqueName with missing names in siblings

* Add test showcasing another bug: shifted arrays in filetree folder

* Update `removeNulls` to use `_id`

* Update services/web/app/src/Features/Project/ProjectLocator.js

Co-authored-by: Jakob Ackermann <jakob.ackermann@overleaf.com>

* Add FIXME about file names uniqueness

* Rename `obj` to `project`

---------

Co-authored-by: Jakob Ackermann <jakob.ackermann@overleaf.com>
GitOrigin-RevId: 3ed795ae0621800603395f7b50626ac89c39199d
This commit is contained in:
Antoine Clausse
2025-04-08 08:05:32 +00:00
committed by Copybot
co-authored by Jakob Ackermann
parent 359b41ef57
commit 4d265f862b
3 changed files with 193 additions and 29 deletions
+38 -13
View File
@@ -8,7 +8,9 @@
*/
import mongodb from 'mongodb-legacy'
import { db } from '../app/src/infrastructure/mongodb.js'
import ProjectLocator from '../app/src/Features/Project/ProjectLocator.js'
import ProjectLocator, {
findDeep,
} from '../app/src/Features/Project/ProjectLocator.js'
import minimist from 'minimist'
import readline from 'node:readline'
import fs from 'node:fs'
@@ -72,7 +74,7 @@ async function processBadPath(projectId, mongoPath, _id) {
if (isRootFolder(mongoPath)) {
modifiedCount = await fixRootFolder(projectId)
} else if (isArrayElement(mongoPath)) {
modifiedCount = await removeNulls(projectId, parentPath(mongoPath))
modifiedCount = await removeNulls(projectId, _id)
} else if (isArray(mongoPath)) {
modifiedCount = await fixArray(projectId, mongoPath)
} else if (isFolderId(mongoPath)) {
@@ -83,7 +85,7 @@ async function processBadPath(projectId, mongoPath, _id) {
parentPath(parentPath(mongoPath))
)
} else if (isName(mongoPath)) {
modifiedCount = await fixName(projectId, mongoPath)
modifiedCount = await fixName(projectId, _id)
} else if (isHash(mongoPath)) {
console.error(`Missing file hash: ${projectId}/${_id} (${mongoPath})`)
console.error('SaaS: likely needs filestore restore')
@@ -164,10 +166,26 @@ async function fixRootFolder(projectId) {
/**
* Remove all nulls from the given docs/files/folders array
*/
async function removeNulls(projectId, path) {
async function removeNulls(projectId, _id) {
if (!_id) {
throw new Error('missing _id')
}
const project = await db.projects.findOne(
{ _id: new ObjectId(projectId) },
{ projection: { rootFolder: 1 } }
)
const foundResult = findDeep(project, obj => obj?._id?.toString() === _id)
if (!foundResult) return
const { path } = foundResult
const result = await db.projects.updateOne(
{ _id: new ObjectId(projectId), [path]: { $type: 'array' } },
{ $pull: { [path]: null } }
{ _id: new ObjectId(projectId) },
{
$pull: {
[`${path}.folders`]: null,
[`${path}.docs`]: null,
[`${path}.fileRefs`]: null,
},
}
)
return result.modifiedCount
}
@@ -208,19 +226,26 @@ async function removeElementsWithoutIds(projectId, path) {
/**
* Give a name to a file/doc/folder that doesn't have one
*/
async function fixName(projectId, path) {
async function fixName(projectId, _id) {
if (!_id) {
throw new Error('missing _id')
}
const project = await db.projects.findOne(
{ _id: new ObjectId(projectId) },
{ projection: { rootFolder: 1 } }
)
const arrayPath = parentPath(parentPath(path))
const array = ProjectLocator.findElementByMongoPath(project, arrayPath)
const existingNames = new Set(array.map(x => x.name))
const foundResult = findDeep(project, obj => obj?._id?.toString() === _id)
if (!foundResult) return
const { path } = foundResult
const array = ProjectLocator.findElementByMongoPath(project, parentPath(path))
const name =
path === 'rootFolder.0.name' ? 'rootFolder' : findUniqueName(existingNames)
path === 'rootFolder.0'
? 'rootFolder'
: findUniqueName(new Set(array.map(x => x?.name)))
const pathToName = `${path}.name`
const result = await db.projects.updateOne(
{ _id: new ObjectId(projectId), [path]: { $in: [null, ''] } },
{ $set: { [path]: name } }
{ _id: new ObjectId(projectId), [pathToName]: { $in: [null, ''] } },
{ $set: { [pathToName]: name } }
)
return result.modifiedCount
}