check_duplicate_collaborators.mjs 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. #!/usr/bin/env node
  2. /**
  3. * Script to check for and optionally fix duplicate collaborators in projects
  4. *
  5. * A duplicate collaborator is when the same user id appears in multiple collaborator
  6. * arrays for the same project (collaberator_refs, readOnly_refs, reviewer_refs, etc.)
  7. *
  8. * If "--fix" is used, this script will remove users from higher privilege roles and keeps them in lower privilege roles
  9. *
  10. * Usage:
  11. * node scripts/check_duplicate_collaborators.mjs [--fix] [--project-id=<id>]
  12. */
  13. import {
  14. batchedUpdate,
  15. READ_PREFERENCE_SECONDARY,
  16. } from '@overleaf/mongo-utils/batchedUpdate.js'
  17. import { db, ObjectId } from '../app/src/infrastructure/mongodb.js'
  18. import minimist from 'minimist'
  19. import { scriptRunner } from './lib/ScriptRunner.mjs'
  20. const args = minimist(process.argv.slice(2), {
  21. boolean: ['fix'],
  22. string: ['project-id', 'start-date', 'end-date'],
  23. default: {
  24. fix: false,
  25. },
  26. })
  27. async function fixDuplicateCollaborators(project, trackProgress) {
  28. const dryRun = !args.fix
  29. const removeCollaboratorRefs = []
  30. const removeReviewerRefs = []
  31. for (const reviewerRef of project.reviewer_refs || []) {
  32. if (includesId(project.readOnly_refs, reviewerRef)) {
  33. removeReviewerRefs.push(reviewerRef) // remove from reviewer_refs (keep read-only)
  34. }
  35. if (includesId(project.collaberator_refs, reviewerRef)) {
  36. removeCollaboratorRefs.push(reviewerRef) // remove from collaberator_refs (keep reviewer)
  37. }
  38. }
  39. if (
  40. !dryRun &&
  41. (removeCollaboratorRefs.length > 0 || removeReviewerRefs.length > 0)
  42. ) {
  43. await db.projects.updateOne(
  44. { _id: project._id },
  45. {
  46. $pull: {
  47. collaberator_refs: { $in: removeCollaboratorRefs },
  48. reviewer_refs: { $in: removeReviewerRefs },
  49. },
  50. }
  51. )
  52. }
  53. const action = args.fix ? 'Removed' : 'Found duplicates in'
  54. if (removeCollaboratorRefs.length > 0) {
  55. trackProgress(
  56. `${action} collaborators from project ${project._id}:`,
  57. removeCollaboratorRefs
  58. )
  59. }
  60. if (removeReviewerRefs.length > 0) {
  61. trackProgress(
  62. `${action} reviewers from project ${project._id}:`,
  63. removeReviewerRefs
  64. )
  65. }
  66. }
  67. async function main(trackProgress) {
  68. if (!args['start-date'] && !args['project-id']) {
  69. console.error(
  70. 'Please provide either --start-date or --project-id argument.'
  71. )
  72. process.exit(1)
  73. }
  74. if (args['project-id']) {
  75. const projectId = new ObjectId(args['project-id'])
  76. const project = await db.projects.findOne(
  77. { _id: projectId },
  78. {
  79. readPreference: READ_PREFERENCE_SECONDARY,
  80. projection: {
  81. _id: 1,
  82. collaberator_refs: 1,
  83. readOnly_refs: 1,
  84. reviewer_refs: 1,
  85. },
  86. }
  87. )
  88. if (!project) {
  89. console.error(`Project with id ${projectId} not found`)
  90. process.exit(1)
  91. }
  92. await fixDuplicateCollaborators(project, trackProgress)
  93. return
  94. }
  95. let projectsProcessed = 0
  96. await batchedUpdate(
  97. db.projects,
  98. {
  99. reviewer_refs: { $ne: [] },
  100. $or: [{ readOnly_refs: { $ne: [] } }, { collaberator_refs: { $ne: [] } }],
  101. },
  102. /**
  103. * @param {Array<Project>} projects
  104. * @return {Promise<void>}
  105. */
  106. async function projects(projects) {
  107. for (const project of projects) {
  108. projectsProcessed += 1
  109. if (projectsProcessed % 10000 === 0) {
  110. console.log(projectsProcessed, 'projects processed')
  111. }
  112. await fixDuplicateCollaborators(project, trackProgress)
  113. }
  114. },
  115. {
  116. _id: 1,
  117. collaberator_refs: 1,
  118. readOnly_refs: 1,
  119. reviewer_refs: 1,
  120. },
  121. undefined,
  122. {
  123. trackProgress,
  124. BATCH_RANGE_START: new Date(args['start-date']).toISOString(),
  125. BATCH_RANGE_END: args['end-date']
  126. ? new Date(args['end-date']).toISOString()
  127. : new Date().toISOString(),
  128. }
  129. )
  130. }
  131. function includesId(array, id) {
  132. return array?.some(item => item.toString() === id.toString())
  133. }
  134. try {
  135. await scriptRunner(main)
  136. process.exit()
  137. } catch (error) {
  138. console.error(error)
  139. process.exit(1)
  140. }