check_removed_emails.mjs 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. // @ts-check
  2. import { db, ObjectId } from '../app/src/infrastructure/mongodb.js'
  3. import fs from 'node:fs/promises'
  4. import * as csv from 'csv'
  5. import { promisify } from 'node:util'
  6. import { scriptRunner } from './lib/ScriptRunner.mjs'
  7. import { READ_PREFERENCE_SECONDARY } from '@overleaf/mongo-utils/batchedUpdate.js'
  8. const CSV_FILENAME = '/tmp/unconfirmed_emails.csv'
  9. /**
  10. * @type {(csvString: string) => Promise<string[][]>}
  11. */
  12. const parseAsync = promisify(csv.parse)
  13. /**
  14. * Checks the fallout of services/web/scripts/remove_unconfirmed_emails.mjs
  15. * which wrongly removed some emails that have been confirmed by users
  16. */
  17. async function main(trackProgress) {
  18. console.time('check_removed_emails')
  19. const csvContent = await fs.readFile(CSV_FILENAME, 'utf8')
  20. const rows = await parseAsync(csvContent)
  21. rows.shift() // Remove header row
  22. const emailsByUserId = {}
  23. for (const [userId, email] of rows) {
  24. if (!emailsByUserId[userId]) {
  25. emailsByUserId[userId] = []
  26. }
  27. emailsByUserId[userId].push(email)
  28. }
  29. const userIds = Object.keys(emailsByUserId)
  30. let processedUsersCount = 0
  31. const counts = {
  32. /** @type {string[]} */
  33. userNotFound: [],
  34. /** @type {string[]} */
  35. notDeleted: [],
  36. deleted: 0,
  37. /** @type {string[]} */
  38. wasConfirmed: [],
  39. /** @type {string[]} */
  40. wasConfirmedLegacy: [],
  41. /** @type {string[]} */
  42. madePrimary: [],
  43. /** @type {string[]} */
  44. madeSecondary: [],
  45. /** @type {string[]} */
  46. isPrimary: [],
  47. /** @type {string[]} */
  48. isAddedAgain: [],
  49. }
  50. console.log('Total emails in the CSV:', rows.length)
  51. console.log('Total users in the CSV:', userIds.length)
  52. for (const userId of userIds) {
  53. const userEmails = emailsByUserId[userId]
  54. const user = await db.users.findOne(
  55. { _id: new ObjectId(userId) },
  56. { readPreference: READ_PREFERENCE_SECONDARY }
  57. )
  58. if (!user) {
  59. counts.userNotFound.push(userId)
  60. continue
  61. }
  62. for (const email of userEmails) {
  63. const deletionLog = await db.userAuditLogEntries.findOne(
  64. {
  65. userId: new ObjectId(userId),
  66. operation: 'remove-email',
  67. 'info.removedEmail': email,
  68. 'info.note': 'remove unconfirmed secondary emails',
  69. },
  70. { readPreference: READ_PREFERENCE_SECONDARY }
  71. )
  72. if (!deletionLog) {
  73. counts.notDeleted.push(email)
  74. continue
  75. }
  76. counts.deleted++
  77. if (user.email === email) {
  78. counts.isPrimary.push(email)
  79. }
  80. const confirmationLog = await db.userAuditLogEntries.findOne(
  81. {
  82. userId: new ObjectId(userId),
  83. operation: 'confirm-email-via-code',
  84. 'info.email': email,
  85. timestamp: { $gt: new Date('2025-02-25') },
  86. },
  87. { readPreference: READ_PREFERENCE_SECONDARY }
  88. )
  89. if (confirmationLog) {
  90. counts.wasConfirmed.push(email)
  91. }
  92. const confirmationLegacyLog = await db.userAuditLogEntries.findOne(
  93. {
  94. userId: new ObjectId(userId),
  95. operation: 'confirm-email',
  96. 'info.email': email,
  97. timestamp: { $gt: new Date('2025-02-25') },
  98. },
  99. { readPreference: READ_PREFERENCE_SECONDARY }
  100. )
  101. if (confirmationLegacyLog) {
  102. counts.wasConfirmedLegacy.push(email)
  103. }
  104. const madePrimaryLog = await db.userAuditLogEntries.findOne(
  105. {
  106. userId: new ObjectId(userId),
  107. operation: 'change-primary-email',
  108. 'info.newPrimaryEmail': email,
  109. timestamp: { $gt: new Date('2025-02-25') },
  110. },
  111. { readPreference: READ_PREFERENCE_SECONDARY }
  112. )
  113. if (madePrimaryLog) {
  114. counts.madePrimary.push(email)
  115. }
  116. const madeSecondaryLog = await db.userAuditLogEntries.findOne(
  117. {
  118. userId: new ObjectId(userId),
  119. operation: 'change-primary-email',
  120. 'info.oldPrimaryEmail': email,
  121. timestamp: { $gt: new Date('2025-02-25') },
  122. },
  123. { readPreference: READ_PREFERENCE_SECONDARY }
  124. )
  125. if (madeSecondaryLog) {
  126. counts.madeSecondary.push(email)
  127. }
  128. if (user.emails.some(item => item.email === email)) {
  129. counts.isAddedAgain.push(email)
  130. }
  131. }
  132. processedUsersCount++
  133. if (processedUsersCount % 100 === 0) {
  134. trackProgress(`Processed ${processedUsersCount} users`)
  135. }
  136. }
  137. console.log()
  138. console.log('Total emails in the CSV:', rows.length)
  139. console.log('Total users in the CSV:', userIds.length)
  140. console.log('Total users processed:', processedUsersCount)
  141. console.log()
  142. console.log('Users not found:', JSON.stringify(counts.userNotFound))
  143. console.log()
  144. console.log('Emails not deleted:', counts.notDeleted.length)
  145. console.log('Emails deleted:', counts.deleted)
  146. console.log()
  147. console.log('Emails that were confirmed:', counts.wasConfirmed.length)
  148. console.log(
  149. 'Emails that were confirmed:',
  150. JSON.stringify(counts.wasConfirmed)
  151. )
  152. console.log()
  153. console.log(
  154. 'Emails that were confirmed (legacy):',
  155. counts.wasConfirmedLegacy.length
  156. )
  157. console.log(
  158. 'Emails that were confirmed (legacy):',
  159. JSON.stringify(counts.wasConfirmedLegacy)
  160. )
  161. console.log()
  162. console.log('Emails that are primary:', counts.isPrimary.length)
  163. console.log('Emails that are primary:', JSON.stringify(counts.isPrimary))
  164. console.log()
  165. console.log('Emails that were made primary:', counts.madePrimary.length)
  166. console.log(
  167. 'Emails that were made primary:',
  168. JSON.stringify(counts.madePrimary)
  169. )
  170. console.log()
  171. console.log('Emails that were made secondary:', counts.madeSecondary.length)
  172. console.log(
  173. 'Emails that were made secondary:',
  174. JSON.stringify(counts.madeSecondary)
  175. )
  176. console.log()
  177. console.log('Emails that were added again:', counts.isAddedAgain.length)
  178. console.log(
  179. 'Emails that were added again:',
  180. JSON.stringify(counts.isAddedAgain)
  181. )
  182. console.log()
  183. console.timeEnd('check_removed_emails')
  184. console.log()
  185. }
  186. try {
  187. await scriptRunner(main)
  188. process.exit(0)
  189. } catch (error) {
  190. console.error(error)
  191. process.exit(1)
  192. }