Переглянути джерело

[web] Re-add remove_unwanted_ieee_collabratec_users script (#32603)

* Revert "Merge pull request #19398 from overleaf/rh-rm-ieee-notification"

This reverts commit 14ec3e50ed4913b815620f5215df59b17fc03054, reversing
changes made to 326352c7c459063bfddf98937e830565c5422ce2.

* Convert remove_unwanted_ieee_collabratec_users to ESM

* Use scriptRunner

* Apply suggestions from code review

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
GitOrigin-RevId: f7d9a4c74173e38789b053792597f761d91efa4f
Antoine Clausse 4 місяців тому
батько
коміт
2f35f2bb65

+ 195 - 0
services/web/scripts/remove_unwanted_ieee_collabratec_users.mjs

@@ -0,0 +1,195 @@
+import path from 'node:path'
+import fs from 'node:fs'
+import AnalyticsManager from '../app/src/Features/Analytics/AnalyticsManager.mjs'
+import { waitForDb, db } from '../app/src/infrastructure/mongodb.mjs'
+import { Subscription } from '../app/src/models/Subscription.mjs'
+import minimist from 'minimist'
+import { promiseMapWithLimit } from '@overleaf/promise-utils'
+import _ from 'lodash'
+import { scriptRunner } from './lib/ScriptRunner.mjs'
+
+/**
+ * This script is used to remove some users from the IEEEPublications group.
+ *
+ * Parameters:
+ *  --filename: the filename of the JSON file containing emails of users that
+ *              should **not** be removed
+ *  --commit: if present, the script will commit the changes to the database.
+ *
+ * Usage:
+ *   - dry run:
+ *     node scripts/remove_unwanted_ieee_collabratec_users.mjs --filename=emails-to-keep.json
+ *   - commit:
+ *     node scripts/remove_unwanted_ieee_collabratec_users.mjs --filename=emails-to-keep.json --commit
+ */
+
+let COMMIT = false
+let EMAILS_FILENAME
+
+/**
+ * The IEEE have provided us with a list of active users that should not be removed
+ * This method retrieves those users.
+ */
+function getActiveUserEmails(filename) {
+  const data = fs.readFileSync(path.join(import.meta.dirname, filename), 'utf8')
+  const emailsArray = JSON.parse(data)
+  const emailsSet = new Set(emailsArray)
+  console.log(
+    `Read ${emailsSet.size} (${emailsArray.length} in array) emails from ${filename}`
+  )
+  return emailsSet
+}
+
+async function getIEEEUsers() {
+  const results = await db.subscriptions
+    .aggregate([
+      { $match: { teamName: 'IEEEPublications' } },
+      { $unwind: '$member_ids' },
+      {
+        $lookup: {
+          from: 'users',
+          localField: 'member_ids',
+          foreignField: '_id',
+          as: 'member_details',
+        },
+      },
+      {
+        $project: {
+          _id: 1,
+          teamName: 1,
+          'member_details._id': 1,
+          'member_details.email': 1,
+          'member_details.emails.email': 1,
+        },
+      },
+    ])
+    .toArray()
+
+  return results
+    .map(subscription => subscription.member_details[0])
+    .filter(Boolean)
+}
+
+async function main() {
+  const start = performance.now()
+
+  if (!EMAILS_FILENAME) {
+    throw new Error('No email filename provided')
+  }
+
+  await waitForDb()
+  const subscription = await Subscription.findOne({
+    teamName: 'IEEEPublications',
+  })
+
+  if (!subscription) {
+    console.error(`No IEEEPublications group subscription found so quitting`)
+    return
+  }
+
+  /**
+   * @type {string[]}
+   */
+  const oldMemberIds = subscription.member_ids.map(id => id.toString())
+
+  console.log(
+    `Found ${oldMemberIds.length} members_ids in IEEEPublications group`
+  )
+
+  const usersArray = await getIEEEUsers()
+  console.log(
+    `Found ${usersArray.length} users in IEEEPublications group. (${oldMemberIds.length - usersArray.length} missing)`
+  )
+
+  const activeUsers = getActiveUserEmails(EMAILS_FILENAME)
+
+  const activeUsersFound = new Set()
+
+  /**
+   * @type {string[]}
+   */
+  const memberIdsToRemove = []
+
+  let index = 0
+
+  // Then go through each IEEEPublications group member to see if we need to remove them
+  await promiseMapWithLimit(10, usersArray, async userDetails => {
+    if (index % 1000 === 0)
+      console.log(
+        `progress: ${index} / ${usersArray.length} (${memberIdsToRemove.length} to remove)`
+      )
+
+    index = index + 1
+
+    if (COMMIT) {
+      await AnalyticsManager.setUserPropertyForUser(
+        userDetails._id.toString(),
+        'ieee-retirement',
+        true
+      )
+    }
+
+    for (const email of userDetails.emails) {
+      if (activeUsers.has(email.email)) {
+        activeUsersFound.add(email.email)
+        return
+      }
+    }
+
+    memberIdsToRemove.push(userDetails._id.toString())
+  })
+
+  console.log(`Found ${memberIdsToRemove.length} users to remove`)
+
+  /**
+   * @type {string[]}
+   */
+  const memberIdsToKeep = _.difference(oldMemberIds, memberIdsToRemove)
+
+  console.log(`Keeping ${memberIdsToKeep.length} users`)
+
+  if (COMMIT) {
+    await Subscription.updateOne(
+      { teamName: 'IEEEPublications' },
+      { member_ids: memberIdsToKeep }
+    )
+  }
+
+  console.log(`Found ${activeUsersFound.size} active users`)
+
+  const activeUsersNotFound = Array.from(activeUsers).filter(
+    user => !activeUsersFound.has(user)
+  )
+
+  console.log(`${activeUsersNotFound.length} IEEE active users not found:`)
+  console.log(activeUsersNotFound)
+
+  const subscriptionAfter = await Subscription.findOne({
+    teamName: 'IEEEPublications',
+  })
+  console.log(
+    `There are now ${subscriptionAfter?.member_ids?.length} member_ids in IEEEPublications group`
+  )
+
+  const end = performance.now()
+  console.log(`Took ${end - start} ms`)
+}
+
+const setup = () => {
+  const argv = minimist(process.argv.slice(2))
+  COMMIT = argv.commit !== undefined
+  EMAILS_FILENAME = argv.filename
+  if (!COMMIT) {
+    console.warn('Doing dry run. Add --commit to commit changes')
+  }
+}
+
+setup()
+
+try {
+  await scriptRunner(main)
+  process.exit(0)
+} catch (error) {
+  console.error(error)
+  process.exit(1)
+}