fix_malformed_filetree.mjs 4.8 KB

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