fix_malformed_filetree.mjs 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. /**
  2. * This script fixes problems found by the find_malformed_filetrees.js script.
  3. *
  4. * The script takes two arguments: the project id and the problemtatic path.
  5. * This is the output format of each line in the find_malformed_filetrees.js
  6. * script.
  7. */
  8. import mongodb from 'mongodb-legacy'
  9. import { db, waitForDb } from '../app/src/infrastructure/mongodb.js'
  10. import ProjectLocator from '../app/src/Features/Project/ProjectLocator.js'
  11. const { ObjectId } = mongodb
  12. async function main() {
  13. const { projectId, mongoPath } = parseArgs()
  14. await waitForDb()
  15. let modifiedCount
  16. if (isRootFolder(mongoPath)) {
  17. modifiedCount = await fixRootFolder(projectId)
  18. } else if (isArrayElement(mongoPath)) {
  19. modifiedCount = await removeNulls(projectId, parentPath(mongoPath))
  20. } else if (isArray(mongoPath)) {
  21. modifiedCount = await fixArray(projectId, mongoPath)
  22. } else if (isFolderId(mongoPath)) {
  23. modifiedCount = await fixFolderId(projectId, mongoPath)
  24. } else if (isDocOrFileId(mongoPath)) {
  25. modifiedCount = await removeElementsWithoutIds(
  26. projectId,
  27. parentPath(parentPath(mongoPath))
  28. )
  29. } else if (isName(mongoPath)) {
  30. modifiedCount = await fixName(projectId, mongoPath)
  31. } else {
  32. console.error(`Unexpected mongo path: ${mongoPath}`)
  33. process.exit(1)
  34. }
  35. console.log(`${modifiedCount} project(s) modified`)
  36. process.exit(0)
  37. }
  38. function parseArgs() {
  39. const args = process.argv.slice(2)
  40. if (args.length !== 2) {
  41. console.error('Usage: fix_malformed_filetree.js PROJECT_ID MONGO_PATH')
  42. process.exit(1)
  43. }
  44. const [projectId, mongoPath] = args
  45. return { projectId: new ObjectId(projectId), mongoPath }
  46. }
  47. function isRootFolder(path) {
  48. return path === 'rootFolder.0'
  49. }
  50. function isArray(path) {
  51. return /\.(docs|folders|fileRefs)$/.test(path)
  52. }
  53. function isArrayElement(path) {
  54. return /\.\d+$/.test(path)
  55. }
  56. function isFolderId(path) {
  57. return /\.folders\.\d+\._id$/.test(path)
  58. }
  59. function isDocOrFileId(path) {
  60. return /\.(docs|fileRefs)\.\d+\._id$/.test(path)
  61. }
  62. function isName(path) {
  63. return /\.name$/.test(path)
  64. }
  65. function parentPath(path) {
  66. return path.slice(0, path.lastIndexOf('.'))
  67. }
  68. /**
  69. * If the root folder structure is missing, set it up
  70. */
  71. async function fixRootFolder(projectId) {
  72. const result = await db.projects.updateOne(
  73. { _id: projectId, rootFolder: [] },
  74. {
  75. $set: {
  76. rootFolder: [
  77. {
  78. _id: new ObjectId(),
  79. name: 'rootFolder',
  80. folders: [],
  81. docs: [],
  82. fileRefs: [],
  83. },
  84. ],
  85. },
  86. }
  87. )
  88. return result.modifiedCount
  89. }
  90. /**
  91. * Remove all nulls from the given docs/files/folders array
  92. */
  93. async function removeNulls(projectId, path) {
  94. const result = await db.projects.updateOne(
  95. { _id: projectId, [path]: { $type: 'array' } },
  96. { $pull: { [path]: null } }
  97. )
  98. return result.modifiedCount
  99. }
  100. /**
  101. * If the element at the given path is not an array, set it to an empty array
  102. */
  103. async function fixArray(projectId, path) {
  104. const result = await db.projects.updateOne(
  105. { _id: projectId, [path]: { $not: { $type: 'array' } } },
  106. { $set: { [path]: [] } }
  107. )
  108. return result.modifiedCount
  109. }
  110. /**
  111. * Generate a missing id for a folder
  112. */
  113. async function fixFolderId(projectId, path) {
  114. const result = await db.projects.updateOne(
  115. { _id: projectId, [path]: { $exists: false } },
  116. { $set: { [path]: new ObjectId() } }
  117. )
  118. return result.modifiedCount
  119. }
  120. /**
  121. * Remove elements that don't have ids in the array at the given path
  122. */
  123. async function removeElementsWithoutIds(projectId, path) {
  124. const result = await db.projects.updateOne(
  125. { _id: projectId, [path]: { $type: 'array' } },
  126. { $pull: { [path]: { _id: null } } }
  127. )
  128. return result.modifiedCount
  129. }
  130. /**
  131. * Give a name to a file/doc/folder that doesn't have one
  132. */
  133. async function fixName(projectId, path) {
  134. const project = await db.projects.findOne(
  135. { _id: projectId },
  136. { projection: { rootFolder: 1 } }
  137. )
  138. const arrayPath = parentPath(parentPath(path))
  139. const array = ProjectLocator.findElementByMongoPath(project, arrayPath)
  140. const existingNames = new Set(array.map(x => x.name))
  141. const name = findUniqueName(existingNames)
  142. const result = await db.projects.updateOne(
  143. { _id: projectId, [path]: { $in: [null, ''] } },
  144. { $set: { [path]: name } }
  145. )
  146. return result.modifiedCount
  147. }
  148. function findUniqueName(existingFilenames) {
  149. let index = 0
  150. let filename = 'untitled'
  151. while (existingFilenames.has(filename)) {
  152. index += 1
  153. filename = `untitled-${index}`
  154. }
  155. return filename
  156. }
  157. try {
  158. await main()
  159. process.exit(0)
  160. } catch (error) {
  161. console.error(error)
  162. process.exit(1)
  163. }