backfill_hashed_emails.mjs 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. import minimist from 'minimist'
  2. import logger from '@overleaf/logger'
  3. import { batchedUpdate } from '@overleaf/mongo-utils/batchedUpdate.js'
  4. import AnalyticsManager from '../../app/src/Features/Analytics/AnalyticsManager.js'
  5. import { db } from '../../app/src/infrastructure/mongodb.js'
  6. import { gracefulShutdown } from '../../app/src/infrastructure/GracefulShutdown.js'
  7. /**
  8. * @typedef {(import('../../app/src/Features/Analytics/types').EmailChangePayload)} EmailChangePayload
  9. */
  10. async function getConfiguration() {
  11. const { commit, endDate, verbose } = minimist(process.argv.slice(2), {
  12. boolean: ['commit', 'verbose'],
  13. string: ['endDate'],
  14. })
  15. // Check endDate is valid
  16. if (endDate) {
  17. if (Number.isNaN(new Date(endDate).getTime())) {
  18. logger.error({ endDate }, 'endDate is not a valid date')
  19. usage()
  20. await shutdown()
  21. }
  22. }
  23. return {
  24. commit,
  25. endDate,
  26. verbose,
  27. }
  28. }
  29. function usage() {
  30. console.log(
  31. 'Usage: node scripts/analytics/backfill_hashed_emails.mjs [--commit] [--endDate date]'
  32. )
  33. console.log('--commit: actually perform the updates (default: dry run)')
  34. console.log('--verbose: enable verbose logging')
  35. console.log(
  36. '--endDate: only process emails created before this date (ISO format)'
  37. )
  38. }
  39. function processBatch(users) {
  40. for (const user of users) {
  41. if (user.emails) {
  42. for (const emailDocument of user.emails) {
  43. if (endDate && emailDocument.createdAt > new Date(endDate)) {
  44. logger.debug(
  45. {
  46. email: emailDocument.email,
  47. createdAt: emailDocument.createdAt,
  48. },
  49. 'Skipping email created after endDate'
  50. )
  51. continue
  52. }
  53. /** @type {EmailChangePayload} */
  54. const payload = {
  55. userId: user._id,
  56. email: emailDocument.email,
  57. createdAt: emailDocument.createdAt,
  58. action: 'created',
  59. emailConfirmedAt: emailDocument.confirmedAt,
  60. emailCreatedAt: emailDocument.createdAt,
  61. isPrimary: user.email === emailDocument.email,
  62. }
  63. logger.debug(
  64. {
  65. payload,
  66. id: user._id,
  67. email: emailDocument.email,
  68. userEmail: user.email,
  69. },
  70. 'Registering email creation'
  71. )
  72. if (commit) {
  73. AnalyticsManager.registerEmailChange(payload)
  74. }
  75. }
  76. }
  77. }
  78. }
  79. async function shutdown() {
  80. await gracefulShutdown()
  81. }
  82. const { commit, endDate, verbose } = await getConfiguration()
  83. logger.logger.level(verbose ? 'debug' : 'info')
  84. const query = {}
  85. if (endDate) {
  86. logger.debug({ endDate }, 'Processing emails created before endDate')
  87. query.emails = {
  88. $exists: true,
  89. $elemMatch: { createdAt: { $lte: new Date(endDate) } },
  90. }
  91. } else {
  92. logger.debug({}, 'Processing all emails')
  93. }
  94. if (!commit) {
  95. logger.info({}, 'Dry run mode (no changes will be made)')
  96. }
  97. try {
  98. const processed = await batchedUpdate(
  99. db.users,
  100. query,
  101. processBatch,
  102. {
  103. email: 1,
  104. emails: 1,
  105. _id: 1,
  106. },
  107. {
  108. readPreference: 'secondaryPreferred',
  109. }
  110. )
  111. logger.info({ processed }, 'Emails processed')
  112. } catch (error) {
  113. logger.error({ error }, 'Error during processing')
  114. }
  115. await shutdown()