delete_dangling_file_refs.mjs 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  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 } from '../app/src/infrastructure/mongodb.js'
  7. import Errors from '../app/src/Features/Errors/Errors.js'
  8. import ProjectEntityMongoUpdateHandler from '../app/src/Features/Project/ProjectEntityMongoUpdateHandler.mjs'
  9. import { iterablePaths } from '../app/src/Features/Project/IterablePath.js'
  10. import { scriptRunner } from './lib/ScriptRunner.mjs'
  11. import HistoryManager from '../app/src/Features/History/HistoryManager.mjs'
  12. const { ObjectId } = mongodb
  13. const OPTIONS = parseArgs()
  14. function parseArgs() {
  15. const argv = minimist(process.argv.slice(2), {
  16. boolean: ['dry-run'],
  17. default: { 'dry-run': true },
  18. })
  19. const dryRun = argv['dry-run']
  20. const projectIds = argv._
  21. if (projectIds.length === 0) {
  22. console.log(`Usage: ${process.argv[1]} [--no-dry-run] PROJECT_ID ...`)
  23. process.exit(0)
  24. }
  25. return { projectIds, dryRun }
  26. }
  27. async function main() {
  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, fileRefs } = 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 fileRef of fileRefs) {
  57. if (!(await fileExists(project._id, fileRef.hash))) {
  58. await deleteFile(project._id, fileRef._id)
  59. }
  60. }
  61. }
  62. function findRefsInFolder(folder) {
  63. let docIds = folder.docs.map(doc => doc._id)
  64. let fileIds = folder.fileRefs.slice()
  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, hash) {
  77. try {
  78. // Getting the file size to avoid downloading the whole file
  79. await HistoryManager.promises.requestBlobWithProjectId(
  80. projectId,
  81. hash,
  82. 'HEAD'
  83. )
  84. } catch (err) {
  85. if (err instanceof Errors.NotFoundError) {
  86. return false
  87. }
  88. throw err
  89. }
  90. return true
  91. }
  92. async function deleteDoc(projectId, docId) {
  93. console.log(` * Deleting bad doc ref ${docId}`)
  94. if (!OPTIONS.dryRun) {
  95. await ProjectEntityMongoUpdateHandler.promises.deleteEntity(
  96. projectId,
  97. docId,
  98. 'doc',
  99. null // unset lastUpdatedBy
  100. )
  101. }
  102. }
  103. async function deleteFile(projectId, fileId) {
  104. console.log(` * Deleting bad file ref ${fileId}`)
  105. if (!OPTIONS.dryRun) {
  106. await ProjectEntityMongoUpdateHandler.promises.deleteEntity(
  107. projectId,
  108. fileId,
  109. 'file',
  110. null // unset lastUpdatedBy
  111. )
  112. }
  113. }
  114. try {
  115. await scriptRunner(main)
  116. process.exit(0)
  117. } catch (error) {
  118. console.error({ error })
  119. process.exit(1)
  120. }