fix_malformed_filetree.js 4.6 KB

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