GetItems.ts 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. import { Result, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
  2. import { Time, TimerInterface } from '@standardnotes/time'
  3. import { Item } from '../../../Item/Item'
  4. import { GetItemsResult } from './GetItemsResult'
  5. import { ItemQuery } from '../../../Item/ItemQuery'
  6. import { ItemRepositoryInterface } from '../../../Item/ItemRepositoryInterface'
  7. import { ItemTransferCalculatorInterface } from '../../../Item/ItemTransferCalculatorInterface'
  8. import { GetItemsDTO } from './GetItemsDTO'
  9. import { SharedVaultUserRepositoryInterface } from '../../../SharedVault/User/SharedVaultUserRepositoryInterface'
  10. export class GetItems implements UseCaseInterface<GetItemsResult> {
  11. private readonly DEFAULT_ITEMS_LIMIT = 150
  12. private readonly SYNC_TOKEN_VERSION = 2
  13. constructor(
  14. private itemRepository: ItemRepositoryInterface,
  15. private sharedVaultUserRepository: SharedVaultUserRepositoryInterface,
  16. private contentSizeTransferLimit: number,
  17. private itemTransferCalculator: ItemTransferCalculatorInterface,
  18. private timer: TimerInterface,
  19. private maxItemsSyncLimit: number,
  20. ) {}
  21. async execute(dto: GetItemsDTO): Promise<Result<GetItemsResult>> {
  22. const lastSyncTimeOrError = this.getLastSyncTime(dto)
  23. if (lastSyncTimeOrError.isFailed()) {
  24. return Result.fail(lastSyncTimeOrError.getError())
  25. }
  26. const lastSyncTime = lastSyncTimeOrError.getValue()
  27. const userUuidOrError = Uuid.create(dto.userUuid)
  28. if (userUuidOrError.isFailed()) {
  29. return Result.fail(userUuidOrError.getError())
  30. }
  31. const userUuid = userUuidOrError.getValue()
  32. const syncTimeComparison = dto.cursorToken ? '>=' : '>'
  33. const limit = dto.limit === undefined || dto.limit < 1 ? this.DEFAULT_ITEMS_LIMIT : dto.limit
  34. const upperBoundLimit = limit < this.maxItemsSyncLimit ? limit : this.maxItemsSyncLimit
  35. const sharedVaultUsers = await this.sharedVaultUserRepository.findByUserUuid(userUuid)
  36. const userSharedVaultUuids = sharedVaultUsers.map((sharedVaultUser) => sharedVaultUser.props.sharedVaultUuid.value)
  37. const exclusiveSharedVaultUuids = dto.sharedVaultUuids
  38. ? dto.sharedVaultUuids.filter((sharedVaultUuid) => userSharedVaultUuids.includes(sharedVaultUuid))
  39. : undefined
  40. const itemQuery: ItemQuery = {
  41. userUuid: userUuid.value,
  42. lastSyncTime: lastSyncTime ?? undefined,
  43. syncTimeComparison,
  44. contentType: dto.contentType,
  45. deleted: lastSyncTime ? undefined : false,
  46. sortBy: 'updated_at_timestamp',
  47. sortOrder: 'ASC',
  48. limit: upperBoundLimit,
  49. includeSharedVaultUuids: !dto.sharedVaultUuids ? userSharedVaultUuids : undefined,
  50. exclusiveSharedVaultUuids,
  51. }
  52. const itemUuidsToFetch = await this.itemTransferCalculator.computeItemUuidsToFetch(
  53. itemQuery,
  54. this.contentSizeTransferLimit,
  55. )
  56. let items: Array<Item> = []
  57. if (itemUuidsToFetch.length > 0) {
  58. items = await this.itemRepository.findAll({
  59. uuids: itemUuidsToFetch,
  60. sortBy: 'updated_at_timestamp',
  61. sortOrder: 'ASC',
  62. })
  63. }
  64. const totalItemsCount = await this.itemRepository.countAll(itemQuery)
  65. let cursorToken = undefined
  66. if (totalItemsCount > upperBoundLimit) {
  67. const lastSyncTime = items[items.length - 1].props.timestamps.updatedAt / Time.MicrosecondsInASecond
  68. cursorToken = Buffer.from(`${this.SYNC_TOKEN_VERSION}:${lastSyncTime}`, 'utf-8').toString('base64')
  69. }
  70. return Result.ok({
  71. items,
  72. cursorToken,
  73. lastSyncTime,
  74. })
  75. }
  76. private getLastSyncTime(dto: GetItemsDTO): Result<number | null> {
  77. let token = dto.syncToken
  78. if (dto.cursorToken !== undefined && dto.cursorToken !== null) {
  79. token = dto.cursorToken
  80. }
  81. if (!token) {
  82. return Result.ok(null)
  83. }
  84. const decodedToken = Buffer.from(token, 'base64').toString('utf-8')
  85. const tokenParts = decodedToken.split(':')
  86. const version = tokenParts.shift()
  87. switch (version) {
  88. case '1':
  89. return Result.ok(this.timer.convertStringDateToMicroseconds(tokenParts.join(':')))
  90. case '2':
  91. return Result.ok(+tokenParts[0] * Time.MicrosecondsInASecond)
  92. default:
  93. return Result.fail('Sync token is missing version part')
  94. }
  95. }
  96. }