back_fill_deleted_files.js 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. const { batchedUpdate } = require('./helpers/batchedUpdate')
  2. const { promiseMapWithLimit, promisify } = require('../app/src/util/promises')
  3. const { db } = require('../app/src/infrastructure/mongodb')
  4. const sleep = promisify(setTimeout)
  5. const _ = require('lodash')
  6. async function main(options) {
  7. if (!options) {
  8. options = {}
  9. }
  10. _.defaults(options, {
  11. writeConcurrency: parseInt(process.env.WRITE_CONCURRENCY, 10) || 10,
  12. performCleanup: process.argv.includes('--perform-cleanup'),
  13. fixPartialInserts: process.argv.includes('--fix-partial-inserts'),
  14. letUserDoubleCheckInputsFor: parseInt(
  15. process.env.LET_USER_DOUBLE_CHECK_INPUTS_FOR || 10 * 1000,
  16. 10
  17. ),
  18. })
  19. await letUserDoubleCheckInputs(options)
  20. await batchedUpdate(
  21. 'projects',
  22. // array is not empty ~ array has one item
  23. { 'deletedFiles.0': { $exists: true } },
  24. async projects => {
  25. await processBatch(projects, options)
  26. },
  27. { _id: 1, deletedFiles: 1 }
  28. )
  29. }
  30. async function processBatch(projects, options) {
  31. await promiseMapWithLimit(
  32. options.writeConcurrency,
  33. projects,
  34. async project => {
  35. await processProject(project, options)
  36. }
  37. )
  38. }
  39. async function processProject(project, options) {
  40. await backFillFiles(project, options)
  41. if (options.performCleanup) {
  42. await cleanupProject(project)
  43. }
  44. }
  45. async function backFillFiles(project, options) {
  46. const projectId = project._id
  47. filterDuplicatesInPlace(project)
  48. project.deletedFiles.forEach(file => {
  49. file.projectId = projectId
  50. })
  51. if (options.fixPartialInserts) {
  52. await fixPartialInserts(project)
  53. } else {
  54. await db.deletedFiles.insertMany(project.deletedFiles)
  55. }
  56. }
  57. function filterDuplicatesInPlace(project) {
  58. const fileIds = new Set()
  59. project.deletedFiles = project.deletedFiles.filter(file => {
  60. const id = file._id.toString()
  61. if (fileIds.has(id)) return false
  62. fileIds.add(id)
  63. return true
  64. })
  65. }
  66. async function fixPartialInserts(project) {
  67. const seenFileIds = new Set(
  68. (
  69. await db.deletedFiles
  70. .find(
  71. { _id: { $in: project.deletedFiles.map(file => file._id) } },
  72. { projection: { _id: 1 } }
  73. )
  74. .toArray()
  75. ).map(file => file._id.toString())
  76. )
  77. project.deletedFiles = project.deletedFiles.filter(file => {
  78. const id = file._id.toString()
  79. if (seenFileIds.has(id)) return false
  80. seenFileIds.add(id)
  81. return true
  82. })
  83. if (project.deletedFiles.length > 0) {
  84. await db.deletedFiles.insertMany(project.deletedFiles)
  85. }
  86. }
  87. async function cleanupProject(project) {
  88. await db.projects.updateOne(
  89. { _id: project._id },
  90. { $set: { deletedFiles: [] } }
  91. )
  92. }
  93. async function letUserDoubleCheckInputs(options) {
  94. if (options.performCleanup) {
  95. console.error('BACK FILLING AND PERFORMING CLEANUP')
  96. } else {
  97. console.error(
  98. 'BACK FILLING ONLY - You will need to rerun with --perform-cleanup'
  99. )
  100. }
  101. console.error(
  102. 'Waiting for you to double check inputs for',
  103. options.letUserDoubleCheckInputsFor,
  104. 'ms'
  105. )
  106. await sleep(options.letUserDoubleCheckInputsFor)
  107. }
  108. module.exports = main
  109. if (require.main === module) {
  110. main()
  111. .then(() => {
  112. process.exit(0)
  113. })
  114. .catch(error => {
  115. console.error({ error })
  116. process.exit(1)
  117. })
  118. }