remove_emails_with_commas.mjs 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. // @ts-check
  2. import minimist from 'minimist'
  3. import fs from 'node:fs/promises'
  4. import * as csv from 'csv'
  5. import { promisify } from 'node:util'
  6. import UserAuditLogHandler from '../app/src/Features/User/UserAuditLogHandler.js'
  7. import { db } from '../app/src/infrastructure/mongodb.js'
  8. const CSV_FILENAME = '/tmp/emails-with-commas.csv'
  9. /**
  10. * @type {(csvString: string) => Promise<string[][]>}
  11. */
  12. const parseAsync = promisify(csv.parse)
  13. function usage() {
  14. console.log('Usage: node remove_emails_with_commas.mjs')
  15. console.log(`Read emails from ${CSV_FILENAME} and remove them from users.`)
  16. console.log('Add support+<encoded_email>@overleaf.com instead.')
  17. console.log('Options:')
  18. console.log(' --commit apply the changes\n')
  19. process.exit(0)
  20. }
  21. const { commit, help } = minimist(process.argv.slice(2), {
  22. boolean: ['commit', 'help'],
  23. alias: { help: 'h' },
  24. default: { commit: false },
  25. })
  26. async function consumeCsvFileAndUpdate() {
  27. console.time('remove_emails_with_commas')
  28. const csvContent = await fs.readFile(CSV_FILENAME, 'utf8')
  29. const rows = await parseAsync(csvContent)
  30. const emailsWithComma = rows.map(row => row[0])
  31. console.log('Total emails in the CSV:', emailsWithComma.length)
  32. const unexpectedValidEmails = emailsWithComma.filter(
  33. str => !str.includes(',')
  34. )
  35. if (unexpectedValidEmails.length > 0) {
  36. throw new Error(
  37. 'CSV file contains unexpected valid emails: ' +
  38. JSON.stringify(emailsWithComma)
  39. )
  40. }
  41. let updatedUsersCount = 0
  42. for (const oldEmail of emailsWithComma) {
  43. const encodedEmail = oldEmail
  44. .replaceAll('_', '_5f')
  45. .replaceAll('@', '_40')
  46. .replaceAll(',', '_2c')
  47. .replaceAll('<', '_60')
  48. .replaceAll('>', '_62')
  49. const newEmail = `support+${encodedEmail}@overleaf.com`
  50. console.log(oldEmail, '->', newEmail)
  51. const user = await db.users.findOne({ email: oldEmail })
  52. if (!user) {
  53. console.log('User not found for email:', oldEmail)
  54. continue
  55. }
  56. if (commit) {
  57. await db.users.updateOne(
  58. { _id: user._id },
  59. {
  60. $set: { email: newEmail },
  61. $pull: { emails: { email: oldEmail } },
  62. }
  63. )
  64. await db.users.updateOne(
  65. { _id: user._id },
  66. {
  67. $addToSet: {
  68. emails: {
  69. email: newEmail,
  70. createdAt: Date.now(),
  71. reversedHostname: 'moc.faelrevo',
  72. },
  73. },
  74. }
  75. )
  76. await UserAuditLogHandler.promises.addEntry(
  77. user._id,
  78. 'remove-email',
  79. undefined,
  80. undefined,
  81. {
  82. removedEmail: oldEmail,
  83. script: true,
  84. note: 'remove primary email containing commas',
  85. }
  86. )
  87. updatedUsersCount++
  88. }
  89. }
  90. console.log('Updated users:', updatedUsersCount)
  91. if (!commit) {
  92. console.log('Note: this was a dry-run. No changes were made.')
  93. }
  94. console.log()
  95. console.timeEnd('remove_emails_with_commas')
  96. console.log()
  97. }
  98. try {
  99. if (help) usage()
  100. else await consumeCsvFileAndUpdate()
  101. process.exit(0)
  102. } catch (error) {
  103. console.error(error)
  104. process.exit(1)
  105. }