delete_dangling_file_refs.mjs 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. /**
  2. * This script deletes dangling doc and file refs in projects
  3. */
  4. import minimist from 'minimist'
  5. import mongodb from 'mongodb-legacy'
  6. import { db, waitForDb } from '../app/src/infrastructure/mongodb.js'
  7. import Errors from '../app/src/Features/Errors/Errors.js'
  8. import FileStoreHandler from '../app/src/Features/FileStore/FileStoreHandler.js'
  9. import ProjectEntityMongoUpdateHandler from '../app/src/Features/Project/ProjectEntityMongoUpdateHandler.js'
  10. import { iterablePaths } from '../app/src/Features/Project/IterablePath.js'
  11. const { ObjectId } = mongodb
  12. const OPTIONS = parseArgs()
  13. function parseArgs() {
  14. const argv = minimist(process.argv.slice(2), {
  15. boolean: ['dry-run'],
  16. default: { 'dry-run': true },
  17. })
  18. const dryRun = argv['dry-run']
  19. const projectIds = argv._
  20. if (projectIds.length === 0) {
  21. console.log(`Usage: ${process.argv[1]} [--no-dry-run] PROJECT_ID ...`)
  22. process.exit(0)
  23. }
  24. return { projectIds, dryRun }
  25. }
  26. async function main() {
  27. await waitForDb()
  28. const projects = await getProjects()
  29. for (const project of projects) {
  30. await processProject(project)
  31. }
  32. if (OPTIONS.dryRun) {
  33. console.log(
  34. '\nThis was a dry run. Re-run with --no-dry-run to delete broken refs.'
  35. )
  36. }
  37. }
  38. async function getProjects() {
  39. const projectIds = OPTIONS.projectIds.map(id => new ObjectId(id))
  40. const projects = await db.projects
  41. .find(
  42. { _id: { $in: projectIds } },
  43. { projection: { _id: 1, rootFolder: 1 } }
  44. )
  45. .toArray()
  46. return projects
  47. }
  48. async function processProject(project) {
  49. console.log(`Processing project ${project._id}`)
  50. const { docIds, fileIds } = findRefsInFolder(project.rootFolder[0])
  51. for (const docId of docIds) {
  52. if (!(await docExists(docId))) {
  53. await deleteDoc(project._id, docId)
  54. }
  55. }
  56. for (const fileId of fileIds) {
  57. if (!(await fileExists(project._id, fileId))) {
  58. await deleteFile(project._id, fileId)
  59. }
  60. }
  61. }
  62. function findRefsInFolder(folder) {
  63. let docIds = folder.docs.map(doc => doc._id)
  64. let fileIds = folder.fileRefs.map(file => file._id)
  65. for (const subfolder of iterablePaths(folder, 'folders')) {
  66. const subrefs = findRefsInFolder(subfolder)
  67. docIds = docIds.concat(subrefs.docIds)
  68. fileIds = fileIds.concat(subrefs.fileIds)
  69. }
  70. return { docIds, fileIds }
  71. }
  72. async function docExists(docId) {
  73. const doc = await db.docs.findOne({ _id: docId })
  74. return doc != null
  75. }
  76. async function fileExists(projectId, fileId) {
  77. try {
  78. // Getting the file size to avoid downloading the whole file
  79. await FileStoreHandler.promises.getFileSize(projectId, fileId)
  80. } catch (err) {
  81. if (err instanceof Errors.NotFoundError) {
  82. return false
  83. }
  84. throw err
  85. }
  86. return true
  87. }
  88. async function deleteDoc(projectId, docId) {
  89. console.log(` * Deleting bad doc ref ${docId}`)
  90. if (!OPTIONS.dryRun) {
  91. await ProjectEntityMongoUpdateHandler.promises.deleteEntity(
  92. projectId,
  93. docId,
  94. 'doc'
  95. )
  96. }
  97. }
  98. async function deleteFile(projectId, fileId) {
  99. console.log(` * Deleting bad file ref ${fileId}`)
  100. if (!OPTIONS.dryRun) {
  101. await ProjectEntityMongoUpdateHandler.promises.deleteEntity(
  102. projectId,
  103. fileId,
  104. 'file'
  105. )
  106. }
  107. }
  108. try {
  109. await main()
  110. process.exit(0)
  111. } catch (error) {
  112. console.error({ error })
  113. process.exit(1)
  114. }