S3ItemBackupService.ts 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  1. import * as uuid from 'uuid'
  2. import { PutObjectCommand, S3Client } from '@aws-sdk/client-s3'
  3. import { KeyParamsData } from '@standardnotes/responses'
  4. import { Logger } from 'winston'
  5. import { Item } from '../../Domain/Item/Item'
  6. import { ItemBackupServiceInterface } from '../../Domain/Item/ItemBackupServiceInterface'
  7. import { MapperInterface } from '@standardnotes/domain-core'
  8. import { ItemBackupRepresentation } from '../../Mapping/Backup/ItemBackupRepresentation'
  9. import { ItemHttpRepresentation } from '../../Mapping/Http/ItemHttpRepresentation'
  10. export class S3ItemBackupService implements ItemBackupServiceInterface {
  11. constructor(
  12. private s3BackupBucketName: string,
  13. private backupMapper: MapperInterface<Item, ItemBackupRepresentation>,
  14. private httpMapper: MapperInterface<Item, ItemHttpRepresentation>,
  15. private logger: Logger,
  16. private s3Client?: S3Client,
  17. ) {}
  18. async dump(item: Item): Promise<string> {
  19. if (!this.s3BackupBucketName || this.s3Client === undefined) {
  20. this.logger.warn('S3 backup not configured')
  21. return ''
  22. }
  23. const s3Key = uuid.v4()
  24. await this.s3Client.send(
  25. new PutObjectCommand({
  26. Bucket: this.s3BackupBucketName,
  27. Key: s3Key,
  28. Body: JSON.stringify({
  29. item: this.backupMapper.toProjection(item),
  30. }),
  31. }),
  32. )
  33. return s3Key
  34. }
  35. async backup(items: Item[], authParams: KeyParamsData, contentSizeLimit?: number): Promise<string[]> {
  36. if (!this.s3BackupBucketName || this.s3Client === undefined) {
  37. this.logger.warn('S3 backup not configured')
  38. return []
  39. }
  40. const fileNames = []
  41. let itemProjections: Array<ItemHttpRepresentation> = []
  42. let contentSizeCounter = 0
  43. for (const item of items) {
  44. const itemProjection = this.httpMapper.toProjection(item)
  45. if (contentSizeLimit === undefined) {
  46. itemProjections.push(itemProjection)
  47. continue
  48. }
  49. const itemContentSize = Buffer.byteLength(JSON.stringify(itemProjection))
  50. if (contentSizeCounter + itemContentSize <= contentSizeLimit) {
  51. itemProjections.push(itemProjection)
  52. contentSizeCounter += itemContentSize
  53. } else {
  54. const backupFileName = await this.createBackupFile(itemProjections, authParams)
  55. fileNames.push(backupFileName)
  56. itemProjections = [itemProjection]
  57. contentSizeCounter = itemContentSize
  58. }
  59. }
  60. if (itemProjections.length > 0) {
  61. const backupFileName = await this.createBackupFile(itemProjections, authParams)
  62. fileNames.push(backupFileName)
  63. }
  64. return fileNames
  65. }
  66. private async createBackupFile(
  67. itemRepresentations: ItemHttpRepresentation[],
  68. authParams: KeyParamsData,
  69. ): Promise<string> {
  70. const fileName = uuid.v4()
  71. await (this.s3Client as S3Client).send(
  72. new PutObjectCommand({
  73. Bucket: this.s3BackupBucketName,
  74. Key: fileName,
  75. Body: JSON.stringify({
  76. items: itemRepresentations,
  77. auth_params: authParams,
  78. }),
  79. }),
  80. )
  81. return fileName
  82. }
  83. }