back_fill_dummy_doc_meta.js 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. const { promisify } = require('util')
  2. const { ObjectId, ReadPreference } = require('mongodb')
  3. const { db, waitForDb } = require('../app/src/infrastructure/mongodb')
  4. const sleep = promisify(setTimeout)
  5. const _ = require('lodash')
  6. const NOW_IN_S = Date.now() / 1000
  7. const ONE_WEEK_IN_S = 60 * 60 * 24 * 7
  8. const TEN_SECONDS = 10 * 1000
  9. const DUMMY_NAME = 'unknown.tex'
  10. const DUMMY_TIME = new Date('2021-04-12T00:00:00.000Z')
  11. const LRUCache = require('lru-cache')
  12. let deletedProjectsCache = null
  13. function getSecondsFromObjectId(id) {
  14. return id.getTimestamp().getTime() / 1000
  15. }
  16. async function main(options) {
  17. if (!options) {
  18. options = {}
  19. }
  20. _.defaults(options, {
  21. dryRun: process.env.DRY_RUN === 'true',
  22. cacheSize: parseInt(process.env.CACHE_SIZE, 10) || 100,
  23. firstProjectId: ObjectId(process.env.FIRST_PROJECT_ID),
  24. incrementByS: parseInt(process.env.INCREMENT_BY_S, 10) || ONE_WEEK_IN_S,
  25. batchSize: parseInt(process.env.BATCH_SIZE, 10) || 1000,
  26. stopAtS: parseInt(process.env.STOP_AT_S, 10) || NOW_IN_S,
  27. letUserDoubleCheckInputsFor:
  28. parseInt(process.env.LET_USER_DOUBLE_CHECK_INPUTS_FOR, 10) || TEN_SECONDS,
  29. })
  30. if (!options.firstProjectId) {
  31. console.error('Set FIRST_PROJECT_ID and re-run.')
  32. process.exit(1)
  33. }
  34. deletedProjectsCache = new LRUCache({
  35. max: options.cacheSize,
  36. })
  37. await letUserDoubleCheckInputs(options)
  38. await waitForDb()
  39. let startId = options.firstProjectId
  40. let nProcessed = 0
  41. while (getSecondsFromObjectId(startId) <= options.stopAtS) {
  42. const end = getSecondsFromObjectId(startId) + options.incrementByS
  43. let endId = ObjectId.createFromTime(end)
  44. const query = {
  45. project_id: {
  46. // include edge
  47. $gte: startId,
  48. // exclude edge
  49. $lt: endId,
  50. },
  51. deleted: true,
  52. name: {
  53. $exists: false,
  54. },
  55. }
  56. const docs = await db.docs
  57. .find(query, { readPreference: ReadPreference.SECONDARY })
  58. .project({ _id: 1, project_id: 1 })
  59. .limit(options.batchSize)
  60. .toArray()
  61. if (docs.length) {
  62. const docIds = docs.map(doc => doc._id)
  63. console.log('Back filling dummy meta data for', JSON.stringify(docIds))
  64. await processBatch(docs, options)
  65. nProcessed += docIds.length
  66. if (docs.length === options.batchSize) {
  67. endId = docs[docs.length - 1].project_id
  68. }
  69. }
  70. console.error('Processed %d until %s', nProcessed, endId)
  71. startId = endId
  72. }
  73. }
  74. async function getDeletedProject(projectId) {
  75. const cacheKey = projectId.toString()
  76. if (deletedProjectsCache.has(cacheKey)) {
  77. return deletedProjectsCache.get(cacheKey)
  78. }
  79. const deletedProject = await db.deletedProjects.findOne(
  80. { 'deleterData.deletedProjectId': projectId },
  81. {
  82. projection: {
  83. _id: 1,
  84. 'project.deletedDocs': 1,
  85. },
  86. }
  87. )
  88. deletedProjectsCache.set(cacheKey, deletedProject)
  89. return deletedProject
  90. }
  91. async function processBatch(docs, options) {
  92. for (const doc of docs) {
  93. const { _id: docId, project_id: projectId } = doc
  94. const deletedProject = await getDeletedProject(projectId)
  95. let name = DUMMY_NAME
  96. let deletedAt = DUMMY_TIME
  97. if (deletedProject) {
  98. const project = deletedProject.project
  99. if (project) {
  100. const deletedDoc =
  101. project.deletedDocs &&
  102. project.deletedDocs.find(deletedDoc => docId.equals(deletedDoc._id))
  103. if (deletedDoc) {
  104. console.log('Found deletedDoc for %s', docId)
  105. name = deletedDoc.name
  106. deletedAt = deletedDoc.deletedAt
  107. } else {
  108. console.log('Missing deletedDoc for %s', docId)
  109. }
  110. } else {
  111. console.log('Orphaned deleted doc %s (failed hard deletion)', docId)
  112. }
  113. } else {
  114. console.log('Orphaned deleted doc %s (no deletedProjects entry)', docId)
  115. }
  116. if (options.dryRun) continue
  117. await db.docs.updateOne({ _id: docId }, { $set: { name, deletedAt } })
  118. }
  119. }
  120. async function letUserDoubleCheckInputs(options) {
  121. console.error('Options:', JSON.stringify(options, null, 2))
  122. console.error(
  123. 'Waiting for you to double check inputs for',
  124. options.letUserDoubleCheckInputsFor,
  125. 'ms'
  126. )
  127. await sleep(options.letUserDoubleCheckInputsFor)
  128. }
  129. module.exports = main
  130. if (require.main === module) {
  131. main()
  132. .then(() => {
  133. console.error('Done.')
  134. process.exit(0)
  135. })
  136. .catch(error => {
  137. console.error({ error })
  138. process.exit(1)
  139. })
  140. }