diff --git a/services/web/scripts/fix_malformed_filetree.js b/services/web/scripts/fix_malformed_filetree.js index ff2dd8da79..a00e098972 100644 --- a/services/web/scripts/fix_malformed_filetree.js +++ b/services/web/scripts/fix_malformed_filetree.js @@ -1,19 +1,34 @@ +/** + * This script fixes problems found by the find_malformed_filetrees.js script. + * + * The script takes two arguments: the project id and the problemtatic path. + * This is the output format of each line in the find_malformed_filetrees.js + * script. + */ const { ObjectId } = require('mongodb') const { db, waitForDb } = require('../app/src/infrastructure/mongodb') +const ProjectLocator = require('../app/src/Features/Project/ProjectLocator') async function main() { const { projectId, mongoPath } = parseArgs() await waitForDb() - const pathSegments = mongoPath.split('.') - const lastPathSegment = pathSegments[pathSegments.length - 1] let modifiedCount - if (mongoPath === 'rootFolder.0') { + if (isRootFolder(mongoPath)) { modifiedCount = await fixRootFolder(projectId) - } else if (endsWithNumber(mongoPath)) { - modifiedCount = await removeNullFolders(projectId, parentPath(mongoPath)) - } else if (['docs', 'folders', 'fileRefs'].includes(lastPathSegment)) { - modifiedCount = await ensureElementIsArray(projectId, mongoPath) + } else if (isArrayElement(mongoPath)) { + modifiedCount = await removeNulls(projectId, parentPath(mongoPath)) + } else if (isArray(mongoPath)) { + modifiedCount = await fixArray(projectId, mongoPath) + } else if (isFolderId(mongoPath)) { + modifiedCount = await fixFolderId(projectId, mongoPath) + } else if (isDocOrFileId(mongoPath)) { + modifiedCount = await removeElementsWithoutIds( + projectId, + parentPath(parentPath(mongoPath)) + ) + } else if (isName(mongoPath)) { + modifiedCount = await fixName(projectId, mongoPath) } else { console.error(`Unexpected mongo path: ${mongoPath}`) process.exit(1) @@ -33,10 +48,30 @@ function parseArgs() { return { projectId: ObjectId(projectId), mongoPath } } -function endsWithNumber(path) { +function isRootFolder(path) { + return path === 'rootFolder.0' +} + +function isArray(path) { + return /\.(docs|folders|fileRefs)$/.test(path) +} + +function isArrayElement(path) { return /\.\d+$/.test(path) } +function isFolderId(path) { + return /\.folders\.\d+\._id$/.test(path) +} + +function isDocOrFileId(path) { + return /\.(docs|fileRefs)\.\d+\._id$/.test(path) +} + +function isName(path) { + return /\.name$/.test(path) +} + function parentPath(path) { return path.slice(0, path.lastIndexOf('.')) } @@ -65,12 +100,12 @@ async function fixRootFolder(projectId) { } /** - * Remove all null entries from the given folders array + * Remove all nulls from the given docs/files/folders array */ -async function removeNullFolders(projectId, foldersPath) { +async function removeNulls(projectId, path) { const result = await db.projects.updateOne( - { _id: projectId, [foldersPath]: { $exists: true } }, - { $pull: { [foldersPath]: null } } + { _id: projectId, [path]: { $type: 'array' } }, + { $pull: { [path]: null } } ) return result.modifiedCount } @@ -78,7 +113,7 @@ async function removeNullFolders(projectId, foldersPath) { /** * If the element at the given path is not an array, set it to an empty array */ -async function ensureElementIsArray(projectId, path) { +async function fixArray(projectId, path) { const result = await db.projects.updateOne( { _id: projectId, [path]: { $not: { $type: 'array' } } }, { $set: { [path]: [] } } @@ -86,6 +121,57 @@ async function ensureElementIsArray(projectId, path) { return result.modifiedCount } +/** + * Generate a missing id for a folder + */ +async function fixFolderId(projectId, path) { + const result = await db.projects.updateOne( + { _id: projectId, [path]: { $exists: false } }, + { $set: { [path]: ObjectId() } } + ) + return result.modifiedCount +} + +/** + * Remove elements that don't have ids in the array at the given path + */ +async function removeElementsWithoutIds(projectId, path) { + const result = await db.projects.updateOne( + { _id: projectId, [path]: { $type: 'array' } }, + { $pull: { [path]: { _id: null } } } + ) + return result.modifiedCount +} + +/** + * Give a name to a file/doc/folder that doesn't have one + */ +async function fixName(projectId, path) { + const project = await db.projects.findOne( + { _id: 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 name = findUniqueName(existingNames) + const result = await db.projects.updateOne( + { _id: projectId, [path]: { $exists: false } }, + { $set: { [path]: name } } + ) + return result.modifiedCount +} + +function findUniqueName(existingFilenames) { + let index = 0 + let filename = 'untitled' + while (existingFilenames.has(filename)) { + index += 1 + filename = `untitled-${index}` + } + return filename +} + main() .then(() => { process.exit(0)