cleanup_incorrectly_expired_projects.js 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  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('deletedProjects', {}, processDeletedProjects, {
  26. 'deleterData.deletedProjectId': 1,
  27. })
  28. if (DRY_RUN) {
  29. console.log(
  30. '\nThis was a dry run. Re-run with DRY_RUN=false to delete broken refs and projects.'
  31. )
  32. }
  33. }
  34. async function processDeletedProjects(collection, deletedProjects) {
  35. const projectIds = deletedProjects.map(x => x.deleterData.deletedProjectId)
  36. const activeProjects = await db.projects
  37. .find(
  38. { _id: { $in: projectIds } },
  39. { projection: { _id: 1, rootFolder: 1 } }
  40. )
  41. .toArray()
  42. for (const activeProject of activeProjects) {
  43. await cleanupProject(activeProject)
  44. }
  45. }
  46. async function cleanupProject(project) {
  47. const { docIds, fileIds } = findRefsInFolder(project.rootFolder[0])
  48. const badDocIds = []
  49. const badFileIds = []
  50. for (const docId of docIds) {
  51. if (!(await docExists(docId))) {
  52. badDocIds.push(docId)
  53. }
  54. }
  55. for (const fileId of fileIds) {
  56. if (!(await fileExists(project._id, fileId))) {
  57. badFileIds.push(fileId)
  58. }
  59. }
  60. if (badDocIds.length === 0 && badFileIds.length === 0) {
  61. return
  62. }
  63. if (
  64. badDocIds.length === docIds.length &&
  65. badFileIds.length === fileIds.length
  66. ) {
  67. console.log(`Deleting project ${project._id}. All refs are bad.`)
  68. if (!DRY_RUN) {
  69. await db.projects.removeOne({ _id: project._id })
  70. }
  71. return
  72. }
  73. console.log(`Cleaning project ${project._id}:`)
  74. for (const docId of badDocIds) {
  75. console.log(` * Deleting bad doc ref ${docId}`)
  76. if (!DRY_RUN) {
  77. await ProjectEntityMongoUpdateHandler.promises.deleteEntity(
  78. project._id,
  79. docId,
  80. 'doc'
  81. )
  82. }
  83. }
  84. for (const fileId of badFileIds) {
  85. console.log(` * Deleting bad file ref ${fileId}`)
  86. if (!DRY_RUN) {
  87. await ProjectEntityMongoUpdateHandler.promises.deleteEntity(
  88. project._id,
  89. fileId,
  90. 'file'
  91. )
  92. }
  93. }
  94. }
  95. function findRefsInFolder(folder) {
  96. let docIds = folder.docs.map(doc => doc._id)
  97. let fileIds = folder.fileRefs.map(file => file._id)
  98. for (const subfolder of folder.folders) {
  99. const subrefs = findRefsInFolder(subfolder)
  100. docIds = docIds.concat(subrefs.docIds)
  101. fileIds = fileIds.concat(subrefs.fileIds)
  102. }
  103. return { docIds, fileIds }
  104. }
  105. async function docExists(docId) {
  106. const doc = await db.docs.findOne({ _id: docId })
  107. return doc != null
  108. }
  109. async function fileExists(projectId, fileId) {
  110. try {
  111. // Getting the file size to avoid downloading the whole file
  112. await FileStoreHandler.promises.getFileSize(projectId, fileId)
  113. } catch (err) {
  114. if (err instanceof Errors.NotFoundError) {
  115. return false
  116. }
  117. throw err
  118. }
  119. return true
  120. }