cleanup_incorrectly_expired_projects.js 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. /**
  2. * This script cleans up active projects that were expired. It's meant to be used once.
  3. *
  4. * See https://github.com/overleaf/internal/issues/4532
  5. */
  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 { batchedUpdate } = require('./helpers/batchedUpdate')
  11. const DRY_RUN = process.env.DRY_RUN !== 'false'
  12. waitForDb()
  13. .then(main)
  14. .then(() => {
  15. process.exitCode = 0
  16. })
  17. .catch(error => {
  18. console.error({ error })
  19. process.exitCode = 1
  20. })
  21. .finally(() => {
  22. process.exit()
  23. })
  24. async function main() {
  25. await batchedUpdate(
  26. 'deletedProjects',
  27. { project: null },
  28. processDeletedProjects,
  29. {
  30. 'deleterData.deletedProjectId': 1,
  31. }
  32. )
  33. if (DRY_RUN) {
  34. console.log(
  35. '\nThis was a dry run. Re-run with DRY_RUN=false to delete broken refs and projects.'
  36. )
  37. }
  38. }
  39. async function processDeletedProjects(collection, deletedProjects) {
  40. const projectIds = deletedProjects.map(x => x.deleterData.deletedProjectId)
  41. const activeProjects = await db.projects
  42. .find(
  43. { _id: { $in: projectIds } },
  44. { projection: { _id: 1, rootFolder: 1 } }
  45. )
  46. .toArray()
  47. for (const activeProject of activeProjects) {
  48. await cleanupProject(activeProject)
  49. }
  50. }
  51. async function cleanupProject(project) {
  52. const { docIds, fileIds } = findRefsInFolder(project.rootFolder[0])
  53. const badDocIds = []
  54. const badFileIds = []
  55. for (const docId of docIds) {
  56. if (!(await docExists(docId))) {
  57. badDocIds.push(docId)
  58. }
  59. }
  60. for (const fileId of fileIds) {
  61. if (!(await fileExists(project._id, fileId))) {
  62. badFileIds.push(fileId)
  63. }
  64. }
  65. if (badDocIds.length === 0 && badFileIds.length === 0) {
  66. return
  67. }
  68. if (
  69. badDocIds.length === docIds.length &&
  70. badFileIds.length === fileIds.length
  71. ) {
  72. console.log(`Deleting project ${project._id}. All refs are bad.`)
  73. if (!DRY_RUN) {
  74. await db.projects.removeOne({ _id: project._id })
  75. }
  76. return
  77. }
  78. console.log(`Cleaning project ${project._id}:`)
  79. for (const docId of badDocIds) {
  80. console.log(` * Deleting bad doc ref ${docId}`)
  81. if (!DRY_RUN) {
  82. await ProjectEntityMongoUpdateHandler.promises.deleteEntity(
  83. project._id,
  84. docId,
  85. 'doc'
  86. )
  87. }
  88. }
  89. for (const fileId of badFileIds) {
  90. console.log(` * Deleting bad file ref ${fileId}`)
  91. if (!DRY_RUN) {
  92. await ProjectEntityMongoUpdateHandler.promises.deleteEntity(
  93. project._id,
  94. fileId,
  95. 'file'
  96. )
  97. }
  98. }
  99. }
  100. function findRefsInFolder(folder) {
  101. let docIds = folder.docs.map(doc => doc._id)
  102. let fileIds = folder.fileRefs.map(file => file._id)
  103. for (const subfolder of folder.folders) {
  104. const subrefs = findRefsInFolder(subfolder)
  105. docIds = docIds.concat(subrefs.docIds)
  106. fileIds = fileIds.concat(subrefs.fileIds)
  107. }
  108. return { docIds, fileIds }
  109. }
  110. async function docExists(docId) {
  111. const doc = await db.docs.findOne({ _id: docId })
  112. return doc != null
  113. }
  114. async function fileExists(projectId, fileId) {
  115. try {
  116. // Getting the file size to avoid downloading the whole file
  117. await FileStoreHandler.promises.getFileSize(projectId, fileId)
  118. } catch (err) {
  119. if (err instanceof Errors.NotFoundError) {
  120. return false
  121. }
  122. throw err
  123. }
  124. return true
  125. }