migrate_audit_logs.js 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. const { batchedUpdate } = require('./helpers/batchedUpdate')
  2. const { promiseMapWithLimit, promisify } = require('../app/src/util/promises')
  3. const { db, ObjectId, waitForDb } = require('../app/src/infrastructure/mongodb')
  4. const sleep = promisify(setTimeout)
  5. const _ = require('lodash')
  6. async function main(options) {
  7. if (!options) {
  8. options = {}
  9. }
  10. _.defaults(options, {
  11. dryRun: process.env.DRY_RUN !== 'false',
  12. projectId: process.env.PROJECT_ID,
  13. userId: process.env.USER_ID,
  14. skipUsersMigration: process.env.SKIP_USERS_MIGRATION === 'true',
  15. writeConcurrency: parseInt(process.env.WRITE_CONCURRENCY, 10) || 10,
  16. letUserDoubleCheckInputsFor: parseInt(
  17. process.env.LET_USER_DOUBLE_CHECK_INPUTS_FOR || 10 * 1000,
  18. 10
  19. ),
  20. })
  21. await letUserDoubleCheckInputs(options)
  22. if (options.projectId) {
  23. console.log('migrating projectId=' + options.projectId)
  24. const project = await db.projects.findOne(
  25. { _id: ObjectId(options.projectId) },
  26. { _id: 1, auditLog: 1 }
  27. )
  28. if (!project || !project.auditLog) {
  29. console.error('unable to process project', project)
  30. return
  31. }
  32. await processProjectsBatch([project], options)
  33. } else if (options.userId) {
  34. console.log('migrating userId=' + options.userId)
  35. const user = await db.users.findOne(
  36. { _id: ObjectId(options.userId) },
  37. { _id: 1, auditLog: 1 }
  38. )
  39. if (!user || !user.auditLog) {
  40. console.error('unable to process user', user)
  41. return
  42. }
  43. await processUsersBatch([user], options)
  44. } else {
  45. if (!options.skipUsersMigration) {
  46. await batchedUpdate(
  47. 'users',
  48. { auditLog: { $exists: true } },
  49. async users => {
  50. await processUsersBatch(users, options)
  51. },
  52. { _id: 1, auditLog: 1 }
  53. )
  54. }
  55. // most projects are processed after its owner has been processed, but only those
  56. // users with an existing `auditLog` have been taken into consideration, leaving
  57. // some projects orphan. This batched update processes all remaining projects.
  58. await batchedUpdate(
  59. 'projects',
  60. { auditLog: { $exists: true } },
  61. async projects => {
  62. await processProjectsBatch(projects, options)
  63. },
  64. { _id: 1, auditLog: 1 }
  65. )
  66. }
  67. }
  68. async function processUsersBatch(users, options) {
  69. if (!users || users.length <= 0) {
  70. return
  71. }
  72. const entries = users
  73. .map(user => user.auditLog.map(log => ({ ...log, userId: user._id })))
  74. .flat()
  75. if (!options.dryRun && entries?.length > 0) {
  76. await db.userAuditLogEntries.insertMany(entries)
  77. }
  78. if (!options.dryRun) {
  79. const userIds = users.map(user => user._id)
  80. await db.users.updateMany(
  81. { _id: { $in: userIds } },
  82. { $unset: { auditLog: 1 } }
  83. )
  84. }
  85. await promiseMapWithLimit(options.writeConcurrency, users, async user => {
  86. const projects = await db.projects
  87. .find(
  88. { owner_ref: user._id, auditLog: { $exists: true } },
  89. { _id: 1, auditLog: 1 }
  90. )
  91. .toArray()
  92. await processProjectsBatch(projects, options)
  93. })
  94. }
  95. async function processProjectsBatch(projects, options) {
  96. if (!projects || projects.length <= 0) {
  97. return
  98. }
  99. const entries = projects
  100. .map(project =>
  101. project.auditLog.map(log => ({ ...log, projectId: project._id }))
  102. )
  103. .flat()
  104. if (!options.dryRun && entries?.length > 0) {
  105. await db.projectAuditLogEntries.insertMany(entries)
  106. }
  107. if (!options.dryRun) {
  108. const projectIds = projects.map(project => project._id)
  109. await db.projects.updateMany(
  110. { _id: { $in: projectIds } },
  111. { $unset: { auditLog: 1 } }
  112. )
  113. }
  114. }
  115. async function letUserDoubleCheckInputs(options) {
  116. const allOptions = {
  117. ...options,
  118. // batchedUpdate() environment variables
  119. BATCH_DESCENDING: process.env.BATCH_DESCENDING,
  120. BATCH_SIZE: process.env.BATCH_SIZE,
  121. VERBOSE_LOGGING: process.env.VERBOSE_LOGGING,
  122. BATCH_LAST_ID: process.env.BATCH_LAST_ID,
  123. BATCH_RANGE_END: process.env.BATCH_RANGE_END,
  124. SKIP_USERS_MIGRATION: process.env.SKIP_USERS_MIGRATION,
  125. }
  126. console.error('Options:', JSON.stringify(allOptions, null, 2))
  127. console.error(
  128. 'Waiting for you to double check inputs for',
  129. options.letUserDoubleCheckInputsFor,
  130. 'ms'
  131. )
  132. await sleep(options.letUserDoubleCheckInputsFor)
  133. }
  134. module.exports = main
  135. if (require.main === module) {
  136. waitForDb()
  137. .then(main)
  138. .then(() => {
  139. console.log('Done.')
  140. process.exit(0)
  141. })
  142. .catch(error => {
  143. console.error({ error })
  144. process.exit(1)
  145. })
  146. }