export_institution_chat.mjs 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. import {
  2. db,
  3. READ_PREFERENCE_SECONDARY,
  4. waitForDb,
  5. ObjectId,
  6. } from '../app/src/infrastructure/mongodb.js'
  7. import minimist from 'minimist'
  8. import InstitutionHubsController from '../modules/metrics/app/src/InstitutionHubsController.mjs'
  9. function usage() {
  10. console.log(
  11. `Usage: node export_institution_chat.js --institution <id> --from <date> --to <date> [--pretty] [--help]
  12. --institution=ID
  13. The V1 institution ID
  14. --from=DATE
  15. The start of the report period. Specified as an ISO 8601 date string
  16. (e.g. 2024-08-01T00:00:00.000Z)
  17. --to=DATE
  18. The end of the report period. Specified as an ISO 8601 date string
  19. (e.g. 2024-09-01T00:00:00.000Z)
  20. --help
  21. Prints this help page\n`
  22. )
  23. }
  24. function parseArgs() {
  25. const argv = minimist(process.argv.slice(2), {
  26. string: ['institution', 'from', 'to'],
  27. bool: ['help'],
  28. default: {
  29. help: false,
  30. },
  31. })
  32. if (argv.help) {
  33. usage()
  34. process.exit(0)
  35. }
  36. if (!argv.institution || !argv.from || !argv.to) {
  37. usage()
  38. process.exit(1)
  39. }
  40. const institutionId = parseInt(argv.institution, 10)
  41. const from = new Date(argv.from).getTime()
  42. const to = new Date(argv.to).getTime()
  43. if (to < from) {
  44. console.error('The end date must be after the start date.')
  45. process.exit(1)
  46. }
  47. return { institutionId, from, to }
  48. }
  49. async function fetchInstitutionAndAffiliations(institutionId) {
  50. const { json: affiliations } =
  51. await InstitutionHubsController.promises.v1InstitutionsApi(
  52. institutionId,
  53. 'csv_affiliations'
  54. )
  55. return affiliations.filter(({ license }) => license === 'pro_plus')
  56. }
  57. function getUserMappings(affiliations) {
  58. const entries = affiliations.map(({ user_id: userId, email }) => [
  59. userId,
  60. email,
  61. ])
  62. return new Map(entries)
  63. }
  64. async function main() {
  65. const args = parseArgs()
  66. await waitForDb()
  67. const affiliations = await fetchInstitutionAndAffiliations(args.institutionId)
  68. const userMappings = getUserMappings(affiliations)
  69. const projectRecords = []
  70. for (const [userId, email] of userMappings.entries()) {
  71. projectRecords.push(...(await processUser(userId, email, args)))
  72. }
  73. console.log(JSON.stringify(projectRecords, null, 2))
  74. }
  75. async function processUser(userId, email, args) {
  76. const projectsOwnedByUser = db.projects.find(
  77. { owner_ref: new ObjectId(userId) },
  78. { projection: { name: 1 }, readPreference: READ_PREFERENCE_SECONDARY }
  79. )
  80. const projectRecords = []
  81. for await (const project of projectsOwnedByUser) {
  82. const hasMessages = await processProject(project, args)
  83. if (hasMessages) {
  84. projectRecords.push({
  85. projectId: project._id,
  86. owner: email,
  87. })
  88. }
  89. }
  90. return projectRecords
  91. }
  92. async function processProject(project, args) {
  93. const { _id: projectId } = project
  94. const globalRoom = await db.rooms.findOne(
  95. {
  96. project_id: new ObjectId(projectId),
  97. thread_id: { $exists: false },
  98. },
  99. { readPreference: READ_PREFERENCE_SECONDARY }
  100. )
  101. if (!globalRoom) {
  102. return null
  103. }
  104. const messages = await db.messages
  105. .find(
  106. {
  107. room_id: globalRoom._id,
  108. timestamp: { $gte: args.from, $lte: args.to },
  109. },
  110. {
  111. projection: {
  112. user_id: 1,
  113. timestamp: 1,
  114. },
  115. readPreference: READ_PREFERENCE_SECONDARY,
  116. }
  117. )
  118. .sort({ timestamp: 1 })
  119. .toArray()
  120. return messages.length > 0
  121. }
  122. try {
  123. await main()
  124. process.exit(0)
  125. } catch (err) {
  126. console.error(err)
  127. process.exit(1)
  128. }