delete_dangling_file_refs.js 3.3 KB

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