find_dangling_comments.mjs 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. // @ts-check
  2. import minimist from 'minimist'
  3. import {
  4. db,
  5. ObjectId,
  6. READ_PREFERENCE_SECONDARY,
  7. } from '../app/src/infrastructure/mongodb.js'
  8. const OPTS = parseArgs()
  9. function parseArgs() {
  10. const args = minimist(process.argv.slice(2), {
  11. string: ['min-project-id', 'max-project-id'],
  12. boolean: ['help'],
  13. })
  14. if (args.help) {
  15. usage()
  16. process.exit(0)
  17. }
  18. return {
  19. minProjectId: args['min-project-id'] ?? null,
  20. maxProjectId: args['max-project-id'] ?? null,
  21. }
  22. }
  23. function usage() {
  24. console.log(`Usage: find_dangling_comments.mjs [OPTS]
  25. Options:
  26. --min-project-id Start scanning at this project id
  27. --max-project-id Stop scanning at this project id`)
  28. }
  29. async function main() {
  30. let projectsProcessed = 0
  31. let projectsFound = 0
  32. for await (const { projectId, threadIds } of fetchThreadIdsByProject()) {
  33. projectsProcessed += 1
  34. const danglingThreadIds = await findDanglingThreadIds(projectId, threadIds)
  35. if (danglingThreadIds.length > 0) {
  36. console.log(
  37. `Project ${projectId} has dangling threads: ${danglingThreadIds.join(', ')}`
  38. )
  39. projectsFound += 1
  40. }
  41. if (projectsProcessed % 100000 === 0) {
  42. console.log(
  43. `${projectsProcessed} projects processed - Last project: ${projectId}`
  44. )
  45. }
  46. }
  47. console.log(`${projectsFound} projects with dangling comments found`)
  48. }
  49. async function* fetchThreadIdsByProject() {
  50. const clauses = []
  51. clauses.push({
  52. deleted: { $ne: true },
  53. 'ranges.comments.0': { $exists: true },
  54. })
  55. if (OPTS.minProjectId != null) {
  56. clauses.push({ project_id: { $gte: new ObjectId(OPTS.minProjectId) } })
  57. }
  58. if (OPTS.maxProjectId != null) {
  59. clauses.push({ project_id: { $lte: new ObjectId(OPTS.maxProjectId) } })
  60. }
  61. const docs = db.docs.find(
  62. { $and: clauses },
  63. {
  64. sort: { project_id: 1 },
  65. projection: { project_id: 1, 'ranges.comments': 1 },
  66. readPreference: READ_PREFERENCE_SECONDARY,
  67. }
  68. )
  69. let projectId
  70. let threadIds = new Set()
  71. for await (const doc of docs) {
  72. if (projectId !== doc.project_id) {
  73. yield { projectId, threadIds }
  74. projectId = doc.project_id
  75. threadIds = new Set()
  76. }
  77. projectId = doc.project_id
  78. for (const comment of doc.ranges.comments) {
  79. threadIds.add(comment.op.t.toString())
  80. }
  81. }
  82. yield { projectId, threadIds }
  83. } /**
  84. * @param {string} projectId
  85. * @param {Set<string>} threadIds
  86. */
  87. async function findDanglingThreadIds(projectId, threadIds) {
  88. const rooms = await db.rooms.find(
  89. { project_id: projectId, thread_id: { $exists: true } },
  90. { readPreference: READ_PREFERENCE_SECONDARY }
  91. )
  92. const existingThreadIds = new Set()
  93. for await (const room of rooms) {
  94. existingThreadIds.add(room.thread_id.toString())
  95. }
  96. const danglingThreadIds = []
  97. for (const threadId of threadIds) {
  98. if (!existingThreadIds.has(threadId)) {
  99. danglingThreadIds.push(threadId)
  100. }
  101. }
  102. return danglingThreadIds
  103. }
  104. try {
  105. await main()
  106. process.exit(0)
  107. } catch (err) {
  108. console.error(err)
  109. process.exit(1)
  110. }