UpdateExistingItem.ts 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. import { ContentType, Dates, Result, Timestamps, UseCaseInterface, Uuid, Validator } from '@standardnotes/domain-core'
  2. import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
  3. import { TimerInterface } from '@standardnotes/time'
  4. import { Item } from '../../../Item/Item'
  5. import { UpdateExistingItemDTO } from './UpdateExistingItemDTO'
  6. import { ItemRepositoryInterface } from '../../../Item/ItemRepositoryInterface'
  7. import { DomainEventFactoryInterface } from '../../../Event/DomainEventFactoryInterface'
  8. import { SharedVaultAssociation } from '../../../SharedVault/SharedVaultAssociation'
  9. import { KeySystemAssociation } from '../../../KeySystem/KeySystemAssociation'
  10. import { ItemHash } from '../../../Item/ItemHash'
  11. export class UpdateExistingItem implements UseCaseInterface<Item> {
  12. constructor(
  13. private itemRepository: ItemRepositoryInterface,
  14. private timer: TimerInterface,
  15. private domainEventPublisher: DomainEventPublisherInterface,
  16. private domainEventFactory: DomainEventFactoryInterface,
  17. private revisionFrequency: number,
  18. ) {}
  19. async execute(dto: UpdateExistingItemDTO): Promise<Result<Item>> {
  20. let sessionUuid = null
  21. if (dto.sessionUuid) {
  22. const sessionUuidOrError = Uuid.create(dto.sessionUuid)
  23. if (sessionUuidOrError.isFailed()) {
  24. return Result.fail(sessionUuidOrError.getError())
  25. }
  26. sessionUuid = sessionUuidOrError.getValue()
  27. }
  28. dto.existingItem.props.updatedWithSession = sessionUuid
  29. const userUuidOrError = Uuid.create(dto.performingUserUuid)
  30. if (userUuidOrError.isFailed()) {
  31. return Result.fail(userUuidOrError.getError())
  32. }
  33. const userUuid = userUuidOrError.getValue()
  34. if (dto.itemHash.props.content) {
  35. dto.existingItem.props.content = dto.itemHash.props.content
  36. }
  37. if (dto.itemHash.props.content_type) {
  38. const contentTypeOrError = ContentType.create(dto.itemHash.props.content_type)
  39. if (contentTypeOrError.isFailed()) {
  40. return Result.fail(contentTypeOrError.getError())
  41. }
  42. const contentType = contentTypeOrError.getValue()
  43. dto.existingItem.props.contentType = contentType
  44. }
  45. if (dto.itemHash.props.deleted !== undefined) {
  46. dto.existingItem.props.deleted = dto.itemHash.props.deleted
  47. }
  48. let wasMarkedAsDuplicate = false
  49. if (dto.itemHash.props.duplicate_of) {
  50. const duplicateOfOrError = Uuid.create(dto.itemHash.props.duplicate_of)
  51. if (duplicateOfOrError.isFailed()) {
  52. return Result.fail(duplicateOfOrError.getError())
  53. }
  54. wasMarkedAsDuplicate = dto.existingItem.props.duplicateOf === null
  55. dto.existingItem.props.duplicateOf = duplicateOfOrError.getValue()
  56. }
  57. if (dto.itemHash.props.auth_hash) {
  58. dto.existingItem.props.authHash = dto.itemHash.props.auth_hash
  59. }
  60. if (dto.itemHash.props.enc_item_key) {
  61. dto.existingItem.props.encItemKey = dto.itemHash.props.enc_item_key
  62. }
  63. if (dto.itemHash.props.items_key_id) {
  64. dto.existingItem.props.itemsKeyId = dto.itemHash.props.items_key_id
  65. }
  66. const updatedAtTimestamp = this.timer.getTimestampInMicroseconds()
  67. const secondsFromLastUpdate = this.timer.convertMicrosecondsToSeconds(
  68. updatedAtTimestamp - dto.existingItem.props.timestamps.updatedAt,
  69. )
  70. const updatedAtDate = this.timer.convertMicrosecondsToDate(updatedAtTimestamp)
  71. let createdAtTimestamp: number
  72. let createdAtDate: Date
  73. if (dto.itemHash.props.created_at_timestamp) {
  74. createdAtTimestamp = dto.itemHash.props.created_at_timestamp
  75. createdAtDate = this.timer.convertMicrosecondsToDate(createdAtTimestamp)
  76. } else if (dto.itemHash.props.created_at) {
  77. createdAtTimestamp = this.timer.convertStringDateToMicroseconds(dto.itemHash.props.created_at)
  78. createdAtDate = this.timer.convertStringDateToDate(dto.itemHash.props.created_at)
  79. } else {
  80. return Result.fail('Created at timestamp is required.')
  81. }
  82. const datesOrError = Dates.create(createdAtDate, updatedAtDate)
  83. if (datesOrError.isFailed()) {
  84. return Result.fail(datesOrError.getError())
  85. }
  86. dto.existingItem.props.dates = datesOrError.getValue()
  87. const timestampsOrError = Timestamps.create(createdAtTimestamp, updatedAtTimestamp)
  88. if (timestampsOrError.isFailed()) {
  89. return Result.fail(timestampsOrError.getError())
  90. }
  91. dto.existingItem.props.timestamps = timestampsOrError.getValue()
  92. dto.existingItem.props.contentSize = Buffer.byteLength(JSON.stringify(dto.existingItem))
  93. if (
  94. dto.itemHash.representsASharedVaultItem() &&
  95. !this.itemIsAlreadyAssociatedWithTheSharedVault(dto.existingItem, dto.itemHash)
  96. ) {
  97. const sharedVaultUuidOrError = Uuid.create(dto.itemHash.props.shared_vault_uuid as string)
  98. if (sharedVaultUuidOrError.isFailed()) {
  99. return Result.fail(sharedVaultUuidOrError.getError())
  100. }
  101. const sharedVaultUuid = sharedVaultUuidOrError.getValue()
  102. const sharedVaultAssociationOrError = SharedVaultAssociation.create({
  103. lastEditedBy: userUuid,
  104. sharedVaultUuid,
  105. timestamps: Timestamps.create(
  106. this.timer.getTimestampInMicroseconds(),
  107. this.timer.getTimestampInMicroseconds(),
  108. ).getValue(),
  109. itemUuid: Uuid.create(dto.existingItem.id.toString()).getValue(),
  110. })
  111. if (sharedVaultAssociationOrError.isFailed()) {
  112. return Result.fail(sharedVaultAssociationOrError.getError())
  113. }
  114. dto.existingItem.props.sharedVaultAssociation = sharedVaultAssociationOrError.getValue()
  115. }
  116. if (
  117. dto.itemHash.hasDedicatedKeySystemAssociation() &&
  118. !this.itemIsAlreadyAssociatedWithTheKeySystem(dto.existingItem, dto.itemHash)
  119. ) {
  120. const keySystemIdentifiedValidationResult = Validator.isNotEmptyString(dto.itemHash.props.key_system_identifier)
  121. if (keySystemIdentifiedValidationResult.isFailed()) {
  122. return Result.fail(keySystemIdentifiedValidationResult.getError())
  123. }
  124. const keySystemIdentifier = dto.itemHash.props.key_system_identifier as string
  125. const keySystemAssociationOrError = KeySystemAssociation.create({
  126. itemUuid: Uuid.create(dto.existingItem.id.toString()).getValue(),
  127. timestamps: Timestamps.create(
  128. this.timer.getTimestampInMicroseconds(),
  129. this.timer.getTimestampInMicroseconds(),
  130. ).getValue(),
  131. keySystemIdentifier,
  132. })
  133. if (keySystemAssociationOrError.isFailed()) {
  134. return Result.fail(keySystemAssociationOrError.getError())
  135. }
  136. dto.existingItem.props.keySystemAssociation = keySystemAssociationOrError.getValue()
  137. }
  138. if (dto.itemHash.props.deleted === true) {
  139. dto.existingItem.props.deleted = true
  140. dto.existingItem.props.content = null
  141. dto.existingItem.props.contentSize = 0
  142. dto.existingItem.props.encItemKey = null
  143. dto.existingItem.props.authHash = null
  144. dto.existingItem.props.itemsKeyId = null
  145. }
  146. await this.itemRepository.save(dto.existingItem)
  147. if (secondsFromLastUpdate >= this.revisionFrequency) {
  148. if (
  149. dto.existingItem.props.contentType.value !== null &&
  150. [ContentType.TYPES.Note, ContentType.TYPES.File].includes(dto.existingItem.props.contentType.value)
  151. ) {
  152. await this.domainEventPublisher.publish(
  153. this.domainEventFactory.createItemRevisionCreationRequested(
  154. dto.existingItem.id.toString(),
  155. dto.existingItem.props.userUuid.value,
  156. ),
  157. )
  158. }
  159. }
  160. if (wasMarkedAsDuplicate) {
  161. await this.domainEventPublisher.publish(
  162. this.domainEventFactory.createDuplicateItemSyncedEvent(
  163. dto.existingItem.id.toString(),
  164. dto.existingItem.props.userUuid.value,
  165. ),
  166. )
  167. }
  168. return Result.ok(dto.existingItem)
  169. }
  170. private itemIsAlreadyAssociatedWithTheSharedVault(item: Item, itemHash: ItemHash): boolean {
  171. return (
  172. item.props.sharedVaultAssociation !== undefined &&
  173. item.props.sharedVaultAssociation.props.sharedVaultUuid.value === itemHash.props.shared_vault_uuid
  174. )
  175. }
  176. private itemIsAlreadyAssociatedWithTheKeySystem(item: Item, itemHash: ItemHash): boolean {
  177. return (
  178. item.props.keySystemAssociation !== undefined &&
  179. item.props.keySystemAssociation.props.keySystemIdentifier === itemHash.props.key_system_identifier
  180. )
  181. }
  182. }