From 9c533ca58e5c17bcfdc99598b14ce95b0258de33 Mon Sep 17 00:00:00 2001 From: David <33458145+davidmcpowell@users.noreply.github.com> Date: Thu, 28 Mar 2024 13:39:36 +0000 Subject: [PATCH] Merge pull request #17645 from overleaf/dp-ac-ieee-deprecation-script-remove-users Create a script to remove users from IEEECollabratec GitOrigin-RevId: 028537722534ba0091cb8c50c8d0a8e6084af22d --- .../remove_unwanted_ieee_collabratec_users.js | 196 ++++++++++++++++++ 1 file changed, 196 insertions(+) create mode 100644 services/web/scripts/remove_unwanted_ieee_collabratec_users.js diff --git a/services/web/scripts/remove_unwanted_ieee_collabratec_users.js b/services/web/scripts/remove_unwanted_ieee_collabratec_users.js new file mode 100644 index 0000000000..909b6b5489 --- /dev/null +++ b/services/web/scripts/remove_unwanted_ieee_collabratec_users.js @@ -0,0 +1,196 @@ +const path = require('path') +const fs = require('fs') +const AnalyticsManager = require('../app/src/Features/Analytics/AnalyticsManager') +const { waitForDb } = require('../app/src/infrastructure/mongodb') +const { Subscription } = require('../app/src/models/Subscription') +const minimist = require('minimist') +const { db } = require('../app/src/infrastructure/mongodb') +const { promiseMapWithLimit } = require('@overleaf/promise-utils') +const _ = require('lodash') + +/** + * This script is used to remove some users from the IEEECollabratec 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.js --filename=emails-to-keep.json + * - commit: + * node scripts/remove_unwanted_ieee_collabratec_users.js --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(__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: 'IEEECollabratec' } }, + { $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: 'IEEECollabratec', + }) + + if (!subscription) { + console.error(`No IEEECollabratec 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 IEEECollabratec group` + ) + + const usersArray = await getIEEEUsers() + console.log( + `Found ${usersArray.length} users in IEEECollabratec 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 collabratec user 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: 'IEEECollabratec' }, + { 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: 'IEEECollabratec', + }) + console.log( + `There are now ${subscriptionAfter?.member_ids?.length} members_ids in IEEECollabratec 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() + +main() + .then(() => { + process.exit(0) + }) + .catch(err => { + console.error(err) + process.exit(1) + })