remove_unwanted_ieee_collabratec_users.mjs 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. import path from 'node:path'
  2. import fs from 'node:fs'
  3. import AnalyticsManager from '../app/src/Features/Analytics/AnalyticsManager.mjs'
  4. import { waitForDb, db } from '../app/src/infrastructure/mongodb.mjs'
  5. import { Subscription } from '../app/src/models/Subscription.mjs'
  6. import minimist from 'minimist'
  7. import { promiseMapWithLimit } from '@overleaf/promise-utils'
  8. import _ from 'lodash'
  9. import { scriptRunner } from './lib/ScriptRunner.mjs'
  10. /**
  11. * This script is used to remove some users from the IEEEPublications group.
  12. *
  13. * Parameters:
  14. * --filename: the filename of the JSON file containing emails of users that
  15. * should **not** be removed
  16. * --commit: if present, the script will commit the changes to the database.
  17. *
  18. * Usage:
  19. * - dry run:
  20. * node scripts/remove_unwanted_ieee_collabratec_users.mjs --filename=emails-to-keep.json
  21. * - commit:
  22. * node scripts/remove_unwanted_ieee_collabratec_users.mjs --filename=emails-to-keep.json --commit
  23. */
  24. let COMMIT = false
  25. let EMAILS_FILENAME
  26. /**
  27. * The IEEE have provided us with a list of active users that should not be removed
  28. * This method retrieves those users.
  29. */
  30. function getActiveUserEmails(filename) {
  31. const data = fs.readFileSync(path.join(import.meta.dirname, filename), 'utf8')
  32. const emailsArray = JSON.parse(data)
  33. const emailsSet = new Set(emailsArray)
  34. console.log(
  35. `Read ${emailsSet.size} (${emailsArray.length} in array) emails from ${filename}`
  36. )
  37. return emailsSet
  38. }
  39. async function getIEEEUsers() {
  40. const results = await db.subscriptions
  41. .aggregate([
  42. { $match: { teamName: 'IEEEPublications' } },
  43. { $unwind: '$member_ids' },
  44. {
  45. $lookup: {
  46. from: 'users',
  47. localField: 'member_ids',
  48. foreignField: '_id',
  49. as: 'member_details',
  50. },
  51. },
  52. {
  53. $project: {
  54. _id: 1,
  55. teamName: 1,
  56. 'member_details._id': 1,
  57. 'member_details.analyticsId': 1,
  58. 'member_details.labsProgram': 1,
  59. 'member_details.email': 1,
  60. 'member_details.emails.email': 1,
  61. },
  62. },
  63. ])
  64. .toArray()
  65. return results
  66. .map(subscription => subscription.member_details[0])
  67. .filter(Boolean)
  68. }
  69. async function main() {
  70. const start = performance.now()
  71. if (!EMAILS_FILENAME) {
  72. throw new Error('No email filename provided')
  73. }
  74. await waitForDb()
  75. const subscription = await Subscription.findOne({
  76. teamName: 'IEEEPublications',
  77. })
  78. if (!subscription) {
  79. console.error(`No IEEEPublications group subscription found so quitting`)
  80. return
  81. }
  82. /**
  83. * @type {string[]}
  84. */
  85. const oldMemberIds = subscription.member_ids.map(id => id.toString())
  86. console.log(
  87. `Found ${oldMemberIds.length} members_ids in IEEEPublications group`
  88. )
  89. const usersArray = await getIEEEUsers()
  90. console.log(
  91. `Found ${usersArray.length} users in IEEEPublications group. (${oldMemberIds.length - usersArray.length} missing)`
  92. )
  93. const activeUsers = getActiveUserEmails(EMAILS_FILENAME)
  94. const activeUsersFound = new Set()
  95. /**
  96. * @type {string[]}
  97. */
  98. const memberIdsToRemove = []
  99. let index = 0
  100. // Then go through each IEEEPublications group member to see if we need to remove them
  101. await promiseMapWithLimit(10, usersArray, async userDetails => {
  102. if (index % 1000 === 0)
  103. console.log(
  104. `progress: ${index} / ${usersArray.length} (${memberIdsToRemove.length} to remove)`
  105. )
  106. index = index + 1
  107. if (COMMIT) {
  108. await AnalyticsManager.setUserPropertyForMongoUser(
  109. userDetails,
  110. 'ieee-retirement',
  111. true
  112. )
  113. }
  114. for (const email of userDetails.emails) {
  115. if (activeUsers.has(email.email)) {
  116. activeUsersFound.add(email.email)
  117. return
  118. }
  119. }
  120. memberIdsToRemove.push(userDetails._id.toString())
  121. })
  122. console.log(`Found ${memberIdsToRemove.length} users to remove`)
  123. /**
  124. * @type {string[]}
  125. */
  126. const memberIdsToKeep = _.difference(oldMemberIds, memberIdsToRemove)
  127. console.log(`Keeping ${memberIdsToKeep.length} users`)
  128. if (COMMIT) {
  129. await Subscription.updateOne(
  130. { teamName: 'IEEEPublications' },
  131. { member_ids: memberIdsToKeep }
  132. )
  133. }
  134. console.log(`Found ${activeUsersFound.size} active users`)
  135. const activeUsersNotFound = Array.from(activeUsers).filter(
  136. user => !activeUsersFound.has(user)
  137. )
  138. console.log(`${activeUsersNotFound.length} IEEE active users not found:`)
  139. console.log(activeUsersNotFound)
  140. const subscriptionAfter = await Subscription.findOne({
  141. teamName: 'IEEEPublications',
  142. })
  143. console.log(
  144. `There are now ${subscriptionAfter?.member_ids?.length} member_ids in IEEEPublications group`
  145. )
  146. const end = performance.now()
  147. console.log(`Took ${end - start} ms`)
  148. }
  149. const setup = () => {
  150. const argv = minimist(process.argv.slice(2))
  151. COMMIT = argv.commit !== undefined
  152. EMAILS_FILENAME = argv.filename
  153. if (!COMMIT) {
  154. console.warn('Doing dry run. Add --commit to commit changes')
  155. }
  156. }
  157. setup()
  158. try {
  159. await scriptRunner(main)
  160. process.exit(0)
  161. } catch (error) {
  162. console.error(error)
  163. process.exit(1)
  164. }