back_fill_deleted_files.js 2.9 KB

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