EmailBackupRequestedEventHandler.ts 3.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  1. import {
  2. DomainEventHandlerInterface,
  3. DomainEventPublisherInterface,
  4. EmailBackupRequestedEvent,
  5. } from '@standardnotes/domain-events'
  6. import { EmailLevel } from '@standardnotes/domain-core'
  7. import { Logger } from 'winston'
  8. import { DomainEventFactoryInterface } from '../Event/DomainEventFactoryInterface'
  9. import { ItemBackupServiceInterface } from '../Item/ItemBackupServiceInterface'
  10. import { ItemRepositoryInterface } from '../Item/ItemRepositoryInterface'
  11. import { ItemTransferCalculatorInterface } from '../Item/ItemTransferCalculatorInterface'
  12. import { ItemQuery } from '../Item/ItemQuery'
  13. import { getBody, getSubject } from '../Email/EmailBackupAttachmentCreated'
  14. export class EmailBackupRequestedEventHandler implements DomainEventHandlerInterface {
  15. constructor(
  16. private primaryItemRepository: ItemRepositoryInterface,
  17. private secondaryItemRepository: ItemRepositoryInterface | null,
  18. private itemBackupService: ItemBackupServiceInterface,
  19. private domainEventPublisher: DomainEventPublisherInterface,
  20. private domainEventFactory: DomainEventFactoryInterface,
  21. private emailAttachmentMaxByteSize: number,
  22. private itemTransferCalculator: ItemTransferCalculatorInterface,
  23. private s3BackupBucketName: string,
  24. private logger: Logger,
  25. ) {}
  26. async handle(event: EmailBackupRequestedEvent): Promise<void> {
  27. await this.requestEmailWithBackupFile(event, this.primaryItemRepository)
  28. if (this.secondaryItemRepository) {
  29. await this.requestEmailWithBackupFile(event, this.secondaryItemRepository)
  30. }
  31. }
  32. private async requestEmailWithBackupFile(
  33. event: EmailBackupRequestedEvent,
  34. itemRepository: ItemRepositoryInterface,
  35. ): Promise<void> {
  36. const itemQuery: ItemQuery = {
  37. userUuid: event.payload.userUuid,
  38. sortBy: 'updated_at_timestamp',
  39. sortOrder: 'ASC',
  40. deleted: false,
  41. }
  42. const itemContentSizeDescriptors = await itemRepository.findContentSizeForComputingTransferLimit(itemQuery)
  43. const itemUuidBundles = await this.itemTransferCalculator.computeItemUuidBundlesToFetch(
  44. itemContentSizeDescriptors,
  45. this.emailAttachmentMaxByteSize,
  46. )
  47. const backupFileNames: string[] = []
  48. for (const itemUuidBundle of itemUuidBundles) {
  49. const items = await itemRepository.findAll({
  50. uuids: itemUuidBundle,
  51. sortBy: 'updated_at_timestamp',
  52. sortOrder: 'ASC',
  53. })
  54. const bundleBackupFileNames = await this.itemBackupService.backup(
  55. items,
  56. event.payload.keyParams,
  57. this.emailAttachmentMaxByteSize,
  58. )
  59. backupFileNames.push(...bundleBackupFileNames)
  60. }
  61. const dateOnly = new Date().toISOString().substring(0, 10)
  62. let bundleIndex = 1
  63. for (const backupFileName of backupFileNames) {
  64. await this.domainEventPublisher.publish(
  65. this.domainEventFactory.createEmailRequestedEvent({
  66. body: getBody(event.payload.keyParams.identifier as string),
  67. level: EmailLevel.LEVELS.System,
  68. messageIdentifier: 'DATA_BACKUP',
  69. subject: getSubject(bundleIndex++, backupFileNames.length, dateOnly),
  70. userEmail: event.payload.keyParams.identifier as string,
  71. sender: 'backups@standardnotes.org',
  72. attachments: [
  73. {
  74. fileName: backupFileName,
  75. filePath: this.s3BackupBucketName,
  76. attachmentFileName: `SN-Data-${dateOnly}.txt`,
  77. attachmentContentType: 'application/json',
  78. },
  79. ],
  80. }),
  81. )
  82. }
  83. this.logger.info(`Email with backup requested for user ${event.payload.userUuid}`)
  84. }
  85. }