Explorar o código

feat(syncing-server): add shared vaults, invites, messages and notifications to sync response (#665)

* feat(syncing-server): add shared vaults, invites, messages and notifications to sync response

* fix(syncing-server): migration timestamps

* fix: issue with migrations for notifications
Karol Sójko hai 1 ano
pai
achega
efa4d7fc60
Modificáronse 41 ficheiros con 545 adicións e 113 borrados
  1. 2 2
      packages/api-gateway/src/Controller/v1/SharedVaultUsersController.ts
  2. 1 1
      packages/api-gateway/src/Service/Resolver/EndpointResolver.ts
  3. 0 16
      packages/auth/migrations/mysql/1688540448427-add-notifications.ts
  4. 0 16
      packages/auth/migrations/mysql/1688540448428-remove-notifications.ts
  5. 0 17
      packages/auth/migrations/sqlite/1688540623272-add-notifications.ts
  6. 0 17
      packages/auth/migrations/sqlite/1688540623273-remove-notifications.ts
  7. 0 16
      packages/syncing-server/migrations/mysql/1688540448427-add-notifications.ts
  8. 16 0
      packages/syncing-server/migrations/mysql/1689671563304-add-notifications.ts
  9. 3 3
      packages/syncing-server/migrations/sqlite/1689672099828-add-notifications.ts
  10. 32 14
      packages/syncing-server/src/Bootstrap/Container.ts
  11. 2 0
      packages/syncing-server/src/Bootstrap/Types.ts
  12. 11 3
      packages/syncing-server/src/Domain/Item/SyncResponse/SyncResponse20200115.ts
  13. 8 0
      packages/syncing-server/src/Domain/Item/SyncResponse/SyncResponseFactory20161215.spec.ts
  14. 55 1
      packages/syncing-server/src/Domain/Item/SyncResponse/SyncResponseFactory20200115.spec.ts
  15. 36 0
      packages/syncing-server/src/Domain/Item/SyncResponse/SyncResponseFactory20200115.ts
  16. 1 0
      packages/syncing-server/src/Domain/Message/MessageRepositoryInterface.ts
  17. 4 0
      packages/syncing-server/src/Domain/Notifications/NotificationRepositoryInterface.ts
  18. 1 0
      packages/syncing-server/src/Domain/SharedVault/User/Invite/SharedVaultInviteRepositoryInterface.ts
  19. 11 0
      packages/syncing-server/src/Domain/UseCase/Messaging/GetMessagesSentToUser/GetMessagesSentToUser.spec.ts
  20. 4 2
      packages/syncing-server/src/Domain/UseCase/Messaging/GetMessagesSentToUser/GetMessagesSentToUser.ts
  21. 1 0
      packages/syncing-server/src/Domain/UseCase/Messaging/GetMessagesSentToUser/GetMessagesSentToUserDTO.ts
  22. 43 0
      packages/syncing-server/src/Domain/UseCase/Messaging/GetUserNotifications/GetUserNotifications.spec.ts
  23. 22 0
      packages/syncing-server/src/Domain/UseCase/Messaging/GetUserNotifications/GetUserNotifications.ts
  24. 4 0
      packages/syncing-server/src/Domain/UseCase/Messaging/GetUserNotifications/GetUserNotificationsDTO.ts
  25. 12 0
      packages/syncing-server/src/Domain/UseCase/SharedVaults/GetSharedVaultInvitesSentToUser/GetSharedVaultInvitesSentToUser.spec.ts
  26. 4 0
      packages/syncing-server/src/Domain/UseCase/SharedVaults/GetSharedVaultInvitesSentToUser/GetSharedVaultInvitesSentToUser.ts
  27. 1 0
      packages/syncing-server/src/Domain/UseCase/SharedVaults/GetSharedVaultInvitesSentToUser/GetSharedVaultInvitesSentToUserDTO.ts
  28. 5 0
      packages/syncing-server/src/Domain/UseCase/Syncing/GetItems/GetItems.spec.ts
  29. 1 0
      packages/syncing-server/src/Domain/UseCase/Syncing/GetItems/GetItems.ts
  30. 1 0
      packages/syncing-server/src/Domain/UseCase/Syncing/GetItems/GetItemsResult.ts
  31. 122 1
      packages/syncing-server/src/Domain/UseCase/Syncing/SyncItems/SyncItems.spec.ts
  32. 48 0
      packages/syncing-server/src/Domain/UseCase/Syncing/SyncItems/SyncItems.ts
  33. 8 0
      packages/syncing-server/src/Domain/UseCase/Syncing/SyncItems/SyncItemsResponse.ts
  34. 1 1
      packages/syncing-server/src/Infra/InversifyExpressUtils/HomeServer/HomeServerSharedVaultInvitesController.ts
  35. 14 0
      packages/syncing-server/src/Infra/TypeORM/TypeORMMessageRepository.ts
  36. 26 1
      packages/syncing-server/src/Infra/TypeORM/TypeORMNotificationRepository.ts
  37. 14 0
      packages/syncing-server/src/Infra/TypeORM/TypeORMSharedVaultInviteRepository.ts
  38. 21 0
      packages/syncing-server/src/Mapping/Http/NotificationHttpMapper.ts
  39. 8 0
      packages/syncing-server/src/Mapping/Http/NotificationHttpRepresentation.ts
  40. 1 1
      packages/syncing-server/src/Mapping/Http/SharedVaultInviteHttpMapper.ts
  41. 1 1
      packages/syncing-server/src/Mapping/Http/SharedVaultInviteHttpRepresentation.ts

+ 2 - 2
packages/api-gateway/src/Controller/v1/SharedVaultUsersController.ts

@@ -21,7 +21,7 @@ export class SharedVaultUsersController extends BaseHttpController {
       response,
       this.endpointResolver.resolveEndpointOrMethodIdentifier(
         'GET',
-        '/shared-vaults/:sharedVaultUuid/users',
+        'shared-vaults/:sharedVaultUuid/users',
         request.params.sharedVaultUuid,
       ),
       request.body,
@@ -35,7 +35,7 @@ export class SharedVaultUsersController extends BaseHttpController {
       response,
       this.endpointResolver.resolveEndpointOrMethodIdentifier(
         'DELETE',
-        '/shared-vaults/:sharedVaultUuid/users/:userUuid',
+        'shared-vaults/:sharedVaultUuid/users/:userUuid',
         request.params.sharedVaultUuid,
         request.params.userUuid,
       ),

+ 1 - 1
packages/api-gateway/src/Service/Resolver/EndpointResolver.ts

@@ -100,7 +100,7 @@ export class EndpointResolver implements EndpointResolverInterface {
     const identifier = this.endpointToIdentifierMap.get(`[${method}]:${endpoint}`)
 
     if (!identifier) {
-      throw new Error(`Endpoint ${endpoint} not found`)
+      throw new Error(`Endpoint [${method}]:${endpoint} not found`)
     }
 
     return identifier

+ 0 - 16
packages/auth/migrations/mysql/1688540448427-add-notifications.ts

@@ -1,16 +0,0 @@
-import { MigrationInterface, QueryRunner } from 'typeorm'
-
-export class AddNotifications1688540448427 implements MigrationInterface {
-  name = 'AddNotifications1688540448427'
-
-  public async up(queryRunner: QueryRunner): Promise<void> {
-    await queryRunner.query(
-      'CREATE TABLE `notifications` (`uuid` varchar(36) NOT NULL, `user_uuid` varchar(36) NOT NULL, `type` varchar(36) NOT NULL, `payload` text NOT NULL, `created_at_timestamp` bigint NOT NULL, `updated_at_timestamp` bigint NOT NULL, INDEX `index_notifications_on_user_uuid` (`user_uuid`), PRIMARY KEY (`uuid`)) ENGINE=InnoDB',
-    )
-  }
-
-  public async down(queryRunner: QueryRunner): Promise<void> {
-    await queryRunner.query('DROP INDEX `index_notifications_on_user_uuid` ON `notifications`')
-    await queryRunner.query('DROP TABLE `notifications`')
-  }
-}

+ 0 - 16
packages/auth/migrations/mysql/1688540448428-remove-notifications.ts

@@ -1,16 +0,0 @@
-import { MigrationInterface, QueryRunner } from 'typeorm'
-
-export class RemoveNotifications1688540448428 implements MigrationInterface {
-  name = 'RemoveNotifications1688540448428'
-
-  public async up(queryRunner: QueryRunner): Promise<void> {
-    await queryRunner.query('DROP INDEX `index_notifications_on_user_uuid` ON `notifications`')
-    await queryRunner.query('DROP TABLE `notifications`')
-  }
-
-  public async down(queryRunner: QueryRunner): Promise<void> {
-    await queryRunner.query(
-      'CREATE TABLE `notifications` (`uuid` varchar(36) NOT NULL, `user_uuid` varchar(36) NOT NULL, `type` varchar(36) NOT NULL, `payload` text NOT NULL, `created_at_timestamp` bigint NOT NULL, `updated_at_timestamp` bigint NOT NULL, INDEX `index_notifications_on_user_uuid` (`user_uuid`), PRIMARY KEY (`uuid`)) ENGINE=InnoDB',
-    )
-  }
-}

+ 0 - 17
packages/auth/migrations/sqlite/1688540623272-add-notifications.ts

@@ -1,17 +0,0 @@
-import { MigrationInterface, QueryRunner } from 'typeorm'
-
-export class AddNotifications1688540623272 implements MigrationInterface {
-  name = 'AddNotifications1688540623272'
-
-  public async up(queryRunner: QueryRunner): Promise<void> {
-    await queryRunner.query(
-      'CREATE TABLE "notifications" ("uuid" varchar PRIMARY KEY NOT NULL, "user_uuid" varchar(36) NOT NULL, "type" varchar(36) NOT NULL, "payload" text NOT NULL, "created_at_timestamp" bigint NOT NULL, "updated_at_timestamp" bigint NOT NULL)',
-    )
-    await queryRunner.query('CREATE INDEX "index_notifications_on_user_uuid" ON "notifications" ("user_uuid") ')
-  }
-
-  public async down(queryRunner: QueryRunner): Promise<void> {
-    await queryRunner.query('DROP INDEX "index_notifications_on_user_uuid"')
-    await queryRunner.query('DROP TABLE "notifications"')
-  }
-}

+ 0 - 17
packages/auth/migrations/sqlite/1688540623273-remove-notifications.ts

@@ -1,17 +0,0 @@
-import { MigrationInterface, QueryRunner } from 'typeorm'
-
-export class RemoveNotifications1688540623273 implements MigrationInterface {
-  name = 'RemoveNotifications1688540623273'
-
-  public async up(queryRunner: QueryRunner): Promise<void> {
-    await queryRunner.query('DROP INDEX "index_notifications_on_user_uuid"')
-    await queryRunner.query('DROP TABLE "notifications"')
-  }
-
-  public async down(queryRunner: QueryRunner): Promise<void> {
-    await queryRunner.query(
-      'CREATE TABLE "notifications" ("uuid" varchar PRIMARY KEY NOT NULL, "user_uuid" varchar(36) NOT NULL, "type" varchar(36) NOT NULL, "payload" text NOT NULL, "created_at_timestamp" bigint NOT NULL, "updated_at_timestamp" bigint NOT NULL)',
-    )
-    await queryRunner.query('CREATE INDEX "index_notifications_on_user_uuid" ON "notifications" ("user_uuid") ')
-  }
-}

+ 0 - 16
packages/syncing-server/migrations/mysql/1688540448427-add-notifications.ts

@@ -1,16 +0,0 @@
-import { MigrationInterface, QueryRunner } from 'typeorm'
-
-export class AddNotifications1688540448427 implements MigrationInterface {
-  name = 'AddNotifications1688540448427'
-
-  public async up(queryRunner: QueryRunner): Promise<void> {
-    await queryRunner.query(
-      'CREATE TABLE `notifications` (`uuid` varchar(36) NOT NULL, `user_uuid` varchar(36) NOT NULL, `type` varchar(36) NOT NULL, `payload` text NOT NULL, `created_at_timestamp` bigint NOT NULL, `updated_at_timestamp` bigint NOT NULL, INDEX `index_notifications_on_user_uuid` (`user_uuid`), PRIMARY KEY (`uuid`)) ENGINE=InnoDB',
-    )
-  }
-
-  public async down(queryRunner: QueryRunner): Promise<void> {
-    await queryRunner.query('DROP INDEX `index_notifications_on_user_uuid` ON `notifications`')
-    await queryRunner.query('DROP TABLE `notifications`')
-  }
-}

+ 16 - 0
packages/syncing-server/migrations/mysql/1689671563304-add-notifications.ts

@@ -0,0 +1,16 @@
+import { MigrationInterface, QueryRunner } from 'typeorm'
+
+export class AddNotifications1689671563304 implements MigrationInterface {
+  name = 'AddNotifications1689671563304'
+
+  public async up(queryRunner: QueryRunner): Promise<void> {
+    await queryRunner.query(
+      'CREATE TABLE IF NOT EXISTS `notifications` (`uuid` varchar(36) NOT NULL, `user_uuid` varchar(36) NOT NULL, `type` varchar(36) NOT NULL, `payload` text NOT NULL, `created_at_timestamp` bigint NOT NULL, `updated_at_timestamp` bigint NOT NULL, INDEX `index_notifications_on_user_uuid` (`user_uuid`), PRIMARY KEY (`uuid`)) ENGINE=InnoDB',
+    )
+  }
+
+  public async down(queryRunner: QueryRunner): Promise<void> {
+    await queryRunner.query('DROP INDEX `index_notifications_on_user_uuid` ON `notifications`')
+    await queryRunner.query('DROP TABLE `notifications`')
+  }
+}

+ 3 - 3
packages/syncing-server/migrations/sqlite/1688540623272-add-notifications.ts → packages/syncing-server/migrations/sqlite/1689672099828-add-notifications.ts

@@ -1,11 +1,11 @@
 import { MigrationInterface, QueryRunner } from 'typeorm'
 
-export class AddNotifications1688540623272 implements MigrationInterface {
-  name = 'AddNotifications1688540623272'
+export class AddNotifications1689672099828 implements MigrationInterface {
+  name = 'AddNotifications1689672099828'
 
   public async up(queryRunner: QueryRunner): Promise<void> {
     await queryRunner.query(
-      'CREATE TABLE "notifications" ("uuid" varchar PRIMARY KEY NOT NULL, "user_uuid" varchar(36) NOT NULL, "type" varchar(36) NOT NULL, "payload" text NOT NULL, "created_at_timestamp" bigint NOT NULL, "updated_at_timestamp" bigint NOT NULL)',
+      'CREATE TABLE IF NOT EXISTS "notifications" ("uuid" varchar PRIMARY KEY NOT NULL, "user_uuid" varchar(36) NOT NULL, "type" varchar(36) NOT NULL, "payload" text NOT NULL, "created_at_timestamp" bigint NOT NULL, "updated_at_timestamp" bigint NOT NULL)',
     )
     await queryRunner.query('CREATE INDEX "index_notifications_on_user_uuid" ON "notifications" ("user_uuid") ')
   }

+ 32 - 14
packages/syncing-server/src/Bootstrap/Container.ts

@@ -147,6 +147,9 @@ import { DeleteAllMessagesSentToUser } from '../Domain/UseCase/Messaging/DeleteA
 import { DeleteMessage } from '../Domain/UseCase/Messaging/DeleteMessage/DeleteMessage'
 import { MessageHttpRepresentation } from '../Mapping/Http/MessageHttpRepresentation'
 import { MessageHttpMapper } from '../Mapping/Http/MessageHttpMapper'
+import { GetUserNotifications } from '../Domain/UseCase/Messaging/GetUserNotifications/GetUserNotifications'
+import { NotificationHttpMapper } from '../Mapping/Http/NotificationHttpMapper'
+import { NotificationHttpRepresentation } from '../Mapping/Http/NotificationHttpRepresentation'
 
 export class ContainerConfigLoader {
   private readonly DEFAULT_CONTENT_SIZE_TRANSFER_LIMIT = 10_000_000
@@ -340,6 +343,9 @@ export class ContainerConfigLoader {
     container
       .bind<MapperInterface<Message, MessageHttpRepresentation>>(TYPES.Sync_MessageHttpMapper)
       .toConstantValue(new MessageHttpMapper())
+    container
+      .bind<MapperInterface<Notification, NotificationHttpRepresentation>>(TYPES.Sync_NotificationHttpMapper)
+      .toConstantValue(new NotificationHttpMapper())
 
     // ORM
     container
@@ -550,6 +556,24 @@ export class ContainerConfigLoader {
           container.get(TYPES.Sync_Logger),
         ),
       )
+    container
+      .bind<GetUserNotifications>(TYPES.Sync_GetUserNotifications)
+      .toConstantValue(new GetUserNotifications(container.get(TYPES.Sync_NotificationRepository)))
+    container
+      .bind<GetSharedVaults>(TYPES.Sync_GetSharedVaults)
+      .toConstantValue(
+        new GetSharedVaults(
+          container.get(TYPES.Sync_SharedVaultUserRepository),
+          container.get(TYPES.Sync_SharedVaultRepository),
+        ),
+      )
+    container
+      .bind<GetSharedVaultInvitesSentToUser>(TYPES.Sync_GetSharedVaultInvitesSentToUser)
+      .toConstantValue(new GetSharedVaultInvitesSentToUser(container.get(TYPES.Sync_SharedVaultInviteRepository)))
+    container
+      .bind<GetMessagesSentToUser>(TYPES.Sync_GetMessagesSentToUser)
+      .toConstantValue(new GetMessagesSentToUser(container.get(TYPES.Sync_MessageRepository)))
+
     container
       .bind<SyncItems>(TYPES.Sync_SyncItems)
       .toConstantValue(
@@ -557,6 +581,10 @@ export class ContainerConfigLoader {
           container.get(TYPES.Sync_ItemRepository),
           container.get(TYPES.Sync_GetItems),
           container.get(TYPES.Sync_SaveItems),
+          container.get(TYPES.Sync_GetSharedVaults),
+          container.get(TYPES.Sync_GetSharedVaultInvitesSentToUser),
+          container.get(TYPES.Sync_GetMessagesSentToUser),
+          container.get(TYPES.Sync_GetUserNotifications),
         ),
       )
     container.bind<CheckIntegrity>(TYPES.Sync_CheckIntegrity).toDynamicValue((context: interfaces.Context) => {
@@ -621,9 +649,6 @@ export class ContainerConfigLoader {
     container
       .bind<GetSharedVaultInvitesSentByUser>(TYPES.Sync_GetSharedVaultInvitesSentByUser)
       .toConstantValue(new GetSharedVaultInvitesSentByUser(container.get(TYPES.Sync_SharedVaultInviteRepository)))
-    container
-      .bind<GetSharedVaultInvitesSentToUser>(TYPES.Sync_GetSharedVaultInvitesSentToUser)
-      .toConstantValue(new GetSharedVaultInvitesSentToUser(container.get(TYPES.Sync_SharedVaultInviteRepository)))
     container
       .bind<GetSharedVaultUsers>(TYPES.Sync_GetSharedVaultUsers)
       .toConstantValue(
@@ -646,14 +671,6 @@ export class ContainerConfigLoader {
           container.get(TYPES.Sync_AddNotificationForUser),
         ),
       )
-    container
-      .bind<GetSharedVaults>(TYPES.Sync_GetSharedVaults)
-      .toConstantValue(
-        new GetSharedVaults(
-          container.get(TYPES.Sync_SharedVaultUserRepository),
-          container.get(TYPES.Sync_SharedVaultRepository),
-        ),
-      )
     container
       .bind<CreateSharedVault>(TYPES.Sync_CreateSharedVault)
       .toConstantValue(
@@ -683,9 +700,6 @@ export class ContainerConfigLoader {
           container.get(TYPES.Sync_VALET_TOKEN_TTL),
         ),
       )
-    container
-      .bind<GetMessagesSentToUser>(TYPES.Sync_GetMessagesSentToUser)
-      .toConstantValue(new GetMessagesSentToUser(container.get(TYPES.Sync_MessageRepository)))
     container
       .bind<GetMessagesSentByUser>(TYPES.Sync_GetMessagesSentByUser)
       .toConstantValue(new GetMessagesSentByUser(container.get(TYPES.Sync_MessageRepository)))
@@ -717,6 +731,10 @@ export class ContainerConfigLoader {
           container.get(TYPES.Sync_ItemHttpMapper),
           container.get(TYPES.Sync_ItemConflictHttpMapper),
           container.get(TYPES.Sync_SavedItemHttpMapper),
+          container.get(TYPES.Sync_SharedVaultHttpMapper),
+          container.get(TYPES.Sync_SharedVaultInviteHttpMapper),
+          container.get(TYPES.Sync_MessageHttpMapper),
+          container.get(TYPES.Sync_NotificationHttpMapper),
         ),
       )
     container

+ 2 - 0
packages/syncing-server/src/Bootstrap/Types.ts

@@ -75,6 +75,7 @@ const TYPES = {
   Sync_UpdateExistingItem: Symbol.for('Sync_UpdateExistingItem'),
   Sync_GetItems: Symbol.for('Sync_GetItems'),
   Sync_SaveItems: Symbol.for('Sync_SaveItems'),
+  Sync_GetUserNotifications: Symbol.for('Sync_GetUserNotifications'),
   // Handlers
   Sync_AccountDeletionRequestedEventHandler: Symbol.for('Sync_AccountDeletionRequestedEventHandler'),
   Sync_DuplicateItemSyncedEventHandler: Symbol.for('Sync_DuplicateItemSyncedEventHandler'),
@@ -113,6 +114,7 @@ const TYPES = {
   Sync_SharedVaultInviteHttpMapper: Symbol.for('Sync_SharedVaultInviteHttpMapper'),
   Sync_MessagePersistenceMapper: Symbol.for('Sync_MessagePersistenceMapper'),
   Sync_MessageHttpMapper: Symbol.for('Sync_MessageHttpMapper'),
+  Sync_NotificationHttpMapper: Symbol.for('Sync_NotificationHttpMapper'),
   Sync_ItemPersistenceMapper: Symbol.for('Sync_ItemPersistenceMapper'),
   Sync_ItemHttpMapper: Symbol.for('Sync_ItemHttpMapper'),
   Sync_ItemHashHttpMapper: Symbol.for('Sync_ItemHashHttpMapper'),

+ 11 - 3
packages/syncing-server/src/Domain/Item/SyncResponse/SyncResponse20200115.ts

@@ -1,11 +1,19 @@
 import { ItemConflictHttpRepresentation } from '../../../Mapping/Http/ItemConflictHttpRepresentation'
 import { ItemHttpRepresentation } from '../../../Mapping/Http/ItemHttpRepresentation'
+import { MessageHttpRepresentation } from '../../../Mapping/Http/MessageHttpRepresentation'
+import { NotificationHttpRepresentation } from '../../../Mapping/Http/NotificationHttpRepresentation'
 import { SavedItemHttpRepresentation } from '../../../Mapping/Http/SavedItemHttpRepresentation'
+import { SharedVaultHttpRepresentation } from '../../../Mapping/Http/SharedVaultHttpRepresentation'
+import { SharedVaultInviteHttpRepresentation } from '../../../Mapping/Http/SharedVaultInviteHttpRepresentation'
 
 export type SyncResponse20200115 = {
-  retrieved_items: Array<ItemHttpRepresentation>
-  saved_items: Array<SavedItemHttpRepresentation>
-  conflicts: Array<ItemConflictHttpRepresentation>
+  retrieved_items: ItemHttpRepresentation[]
+  saved_items: SavedItemHttpRepresentation[]
+  conflicts: ItemConflictHttpRepresentation[]
   sync_token: string
   cursor_token?: string
+  messages: MessageHttpRepresentation[]
+  shared_vaults: SharedVaultHttpRepresentation[]
+  shared_vault_invites: SharedVaultInviteHttpRepresentation[]
+  notifications: NotificationHttpRepresentation[]
 }

+ 8 - 0
packages/syncing-server/src/Domain/Item/SyncResponse/SyncResponseFactory20161215.spec.ts

@@ -88,6 +88,10 @@ describe('SyncResponseFactory20161215', () => {
         ],
         syncToken: 'sync-test',
         cursorToken: 'cursor-test',
+        sharedVaults: [],
+        sharedVaultInvites: [],
+        messages: [],
+        notifications: [],
       }),
     ).toEqual({
       retrieved_items: [item1Projection],
@@ -133,6 +137,10 @@ describe('SyncResponseFactory20161215', () => {
         ],
         syncToken: 'sync-test',
         cursorToken: 'cursor-test',
+        sharedVaults: [],
+        sharedVaultInvites: [],
+        messages: [],
+        notifications: [],
       }),
     ).toEqual({
       retrieved_items: [],

+ 55 - 1
packages/syncing-server/src/Domain/Item/SyncResponse/SyncResponseFactory20200115.spec.ts

@@ -8,6 +8,14 @@ import { SyncResponseFactory20200115 } from './SyncResponseFactory20200115'
 import { ItemHttpRepresentation } from '../../../Mapping/Http/ItemHttpRepresentation'
 import { SavedItemHttpRepresentation } from '../../../Mapping/Http/SavedItemHttpRepresentation'
 import { ItemConflictHttpRepresentation } from '../../../Mapping/Http/ItemConflictHttpRepresentation'
+import { MessageHttpRepresentation } from '../../../Mapping/Http/MessageHttpRepresentation'
+import { NotificationHttpRepresentation } from '../../../Mapping/Http/NotificationHttpRepresentation'
+import { Notification } from '../../Notifications/Notification'
+import { SharedVaultHttpRepresentation } from '../../../Mapping/Http/SharedVaultHttpRepresentation'
+import { SharedVaultInviteHttpRepresentation } from '../../../Mapping/Http/SharedVaultInviteHttpRepresentation'
+import { Message } from '../../Message/Message'
+import { SharedVault } from '../../SharedVault/SharedVault'
+import { SharedVaultInvite } from '../../SharedVault/User/Invite/SharedVaultInvite'
 
 describe('SyncResponseFactory20200115', () => {
   let itemMapper: MapperInterface<Item, ItemHttpRepresentation>
@@ -19,8 +27,25 @@ describe('SyncResponseFactory20200115', () => {
   let item1: Item
   let item2: Item
   let itemConflict: ItemConflict
+  let sharedVault: SharedVault
+  let sharedVaultInvite: SharedVaultInvite
+  let message: Message
+  let notification: Notification
+  let sharedVaultMapper: MapperInterface<SharedVault, SharedVaultHttpRepresentation>
+  let sharedVaultInvitesMapper: MapperInterface<SharedVaultInvite, SharedVaultInviteHttpRepresentation>
+  let messageMapper: MapperInterface<Message, MessageHttpRepresentation>
+  let notificationMapper: MapperInterface<Notification, NotificationHttpRepresentation>
 
-  const createFactory = () => new SyncResponseFactory20200115(itemMapper, itemConflictMapper, savedItemMapper)
+  const createFactory = () =>
+    new SyncResponseFactory20200115(
+      itemMapper,
+      itemConflictMapper,
+      savedItemMapper,
+      sharedVaultMapper,
+      sharedVaultInvitesMapper,
+      messageMapper,
+      notificationMapper,
+    )
 
   beforeEach(() => {
     itemProjection = {
@@ -45,6 +70,27 @@ describe('SyncResponseFactory20200115', () => {
     item2 = {} as jest.Mocked<Item>
 
     itemConflict = {} as jest.Mocked<ItemConflict>
+
+    sharedVaultMapper = {} as jest.Mocked<MapperInterface<SharedVault, SharedVaultHttpRepresentation>>
+    sharedVaultMapper.toProjection = jest.fn().mockReturnValue({} as jest.Mocked<SharedVaultHttpRepresentation>)
+
+    sharedVaultInvitesMapper = {} as jest.Mocked<
+      MapperInterface<SharedVaultInvite, SharedVaultInviteHttpRepresentation>
+    >
+    sharedVaultInvitesMapper.toProjection = jest
+      .fn()
+      .mockReturnValue({} as jest.Mocked<SharedVaultInviteHttpRepresentation>)
+
+    messageMapper = {} as jest.Mocked<MapperInterface<Message, MessageHttpRepresentation>>
+    messageMapper.toProjection = jest.fn().mockReturnValue({} as jest.Mocked<MessageHttpRepresentation>)
+
+    notificationMapper = {} as jest.Mocked<MapperInterface<Notification, NotificationHttpRepresentation>>
+    notificationMapper.toProjection = jest.fn().mockReturnValue({} as jest.Mocked<NotificationHttpRepresentation>)
+
+    sharedVault = {} as jest.Mocked<SharedVault>
+    sharedVaultInvite = {} as jest.Mocked<SharedVaultInvite>
+    message = {} as jest.Mocked<Message>
+    notification = {} as jest.Mocked<Notification>
   })
 
   it('should turn sync items response into a sync response for API Version 20200115', async () => {
@@ -55,6 +101,10 @@ describe('SyncResponseFactory20200115', () => {
         conflicts: [itemConflict],
         syncToken: 'sync-test',
         cursorToken: 'cursor-test',
+        sharedVaults: [sharedVault],
+        sharedVaultInvites: [sharedVaultInvite],
+        messages: [message],
+        notifications: [notification],
       }),
     ).toEqual({
       retrieved_items: [itemProjection],
@@ -62,6 +112,10 @@ describe('SyncResponseFactory20200115', () => {
       conflicts: [itemConflictProjection],
       sync_token: 'sync-test',
       cursor_token: 'cursor-test',
+      shared_vaults: [{} as jest.Mocked<SharedVaultHttpRepresentation>],
+      shared_vault_invites: [{} as jest.Mocked<SharedVaultInviteHttpRepresentation>],
+      messages: [{} as jest.Mocked<MessageHttpRepresentation>],
+      notifications: [{} as jest.Mocked<NotificationHttpRepresentation>],
     })
   })
 })

+ 36 - 0
packages/syncing-server/src/Domain/Item/SyncResponse/SyncResponseFactory20200115.ts

@@ -8,12 +8,24 @@ import { SyncItemsResponse } from '../../UseCase/Syncing/SyncItems/SyncItemsResp
 import { ItemHttpRepresentation } from '../../../Mapping/Http/ItemHttpRepresentation'
 import { ItemConflictHttpRepresentation } from '../../../Mapping/Http/ItemConflictHttpRepresentation'
 import { SavedItemHttpRepresentation } from '../../../Mapping/Http/SavedItemHttpRepresentation'
+import { SharedVault } from '../../SharedVault/SharedVault'
+import { SharedVaultHttpRepresentation } from '../../../Mapping/Http/SharedVaultHttpRepresentation'
+import { SharedVaultInvite } from '../../SharedVault/User/Invite/SharedVaultInvite'
+import { SharedVaultInviteHttpRepresentation } from '../../../Mapping/Http/SharedVaultInviteHttpRepresentation'
+import { Message } from '../../Message/Message'
+import { MessageHttpRepresentation } from '../../../Mapping/Http/MessageHttpRepresentation'
+import { Notification } from '../../Notifications/Notification'
+import { NotificationHttpRepresentation } from '../../../Mapping/Http/NotificationHttpRepresentation'
 
 export class SyncResponseFactory20200115 implements SyncResponseFactoryInterface {
   constructor(
     private httpMapper: MapperInterface<Item, ItemHttpRepresentation>,
     private itemConflictMapper: MapperInterface<ItemConflict, ItemConflictHttpRepresentation>,
     private savedItemMapper: MapperInterface<Item, SavedItemHttpRepresentation>,
+    private sharedVaultMapper: MapperInterface<SharedVault, SharedVaultHttpRepresentation>,
+    private sharedVaultInvitesMapper: MapperInterface<SharedVaultInvite, SharedVaultInviteHttpRepresentation>,
+    private messageMapper: MapperInterface<Message, MessageHttpRepresentation>,
+    private notificationMapper: MapperInterface<Notification, NotificationHttpRepresentation>,
   ) {}
 
   async createResponse(syncItemsResponse: SyncItemsResponse): Promise<SyncResponse20200115> {
@@ -32,12 +44,36 @@ export class SyncResponseFactory20200115 implements SyncResponseFactoryInterface
       conflicts.push(this.itemConflictMapper.toProjection(itemConflict))
     }
 
+    const sharedVaults = []
+    for (const sharedVault of syncItemsResponse.sharedVaults) {
+      sharedVaults.push(this.sharedVaultMapper.toProjection(sharedVault))
+    }
+
+    const sharedVaultInvites = []
+    for (const sharedVaultInvite of syncItemsResponse.sharedVaultInvites) {
+      sharedVaultInvites.push(this.sharedVaultInvitesMapper.toProjection(sharedVaultInvite))
+    }
+
+    const messages = []
+    for (const contact of syncItemsResponse.messages) {
+      messages.push(this.messageMapper.toProjection(contact))
+    }
+
+    const notifications = []
+    for (const notification of syncItemsResponse.notifications) {
+      notifications.push(this.notificationMapper.toProjection(notification))
+    }
+
     return {
       retrieved_items: retrievedItems,
       saved_items: savedItems,
       conflicts,
       sync_token: syncItemsResponse.syncToken,
       cursor_token: syncItemsResponse.cursorToken,
+      messages,
+      shared_vaults: sharedVaults,
+      shared_vault_invites: sharedVaultInvites,
+      notifications,
     }
   }
 }

+ 1 - 0
packages/syncing-server/src/Domain/Message/MessageRepositoryInterface.ts

@@ -5,6 +5,7 @@ import { Message } from './Message'
 export interface MessageRepositoryInterface {
   findByUuid: (uuid: Uuid) => Promise<Message | null>
   findByRecipientUuid: (uuid: Uuid) => Promise<Message[]>
+  findByRecipientUuidUpdatedAfter: (uuid: Uuid, updatedAtTimestamp: number) => Promise<Message[]>
   findBySenderUuid: (uuid: Uuid) => Promise<Message[]>
   findByRecipientUuidAndReplaceabilityIdentifier: (dto: {
     recipientUuid: Uuid

+ 4 - 0
packages/syncing-server/src/Domain/Notifications/NotificationRepositoryInterface.ts

@@ -1,5 +1,9 @@
+import { Uuid } from '@standardnotes/domain-core'
+
 import { Notification } from './Notification'
 
 export interface NotificationRepositoryInterface {
   save(notification: Notification): Promise<void>
+  findByUserUuidUpdatedAfter(userUuid: Uuid, lastSyncTime: number): Promise<Notification[]>
+  findByUserUuid(userUuid: Uuid): Promise<Notification[]>
 }

+ 1 - 0
packages/syncing-server/src/Domain/SharedVault/User/Invite/SharedVaultInviteRepositoryInterface.ts

@@ -8,6 +8,7 @@ export interface SharedVaultInviteRepositoryInterface {
   remove(sharedVaultInvite: SharedVaultInvite): Promise<void>
   removeBySharedVaultUuid(sharedVaultUuid: Uuid): Promise<void>
   findByUserUuid(userUuid: Uuid): Promise<SharedVaultInvite[]>
+  findByUserUuidUpdatedAfter(userUuid: Uuid, updatedAtTimestamp: number): Promise<SharedVaultInvite[]>
   findBySenderUuid(senderUuid: Uuid): Promise<SharedVaultInvite[]>
   findByUserUuidAndSharedVaultUuid(dto: { userUuid: Uuid; sharedVaultUuid: Uuid }): Promise<SharedVaultInvite | null>
   findBySenderUuidAndSharedVaultUuid(dto: { senderUuid: Uuid; sharedVaultUuid: Uuid }): Promise<SharedVaultInvite[]>

+ 11 - 0
packages/syncing-server/src/Domain/UseCase/Messaging/GetMessagesSentToUser/GetMessagesSentToUser.spec.ts

@@ -9,6 +9,7 @@ describe('GetMessagesSentToUser', () => {
   beforeEach(() => {
     messageRepository = {} as jest.Mocked<MessageRepositoryInterface>
     messageRepository.findByRecipientUuid = jest.fn().mockReturnValue([])
+    messageRepository.findByRecipientUuidUpdatedAfter = jest.fn().mockReturnValue([])
   })
 
   it('should return messages sent to user', async () => {
@@ -20,6 +21,16 @@ describe('GetMessagesSentToUser', () => {
     expect(result.getValue()).toEqual([])
   })
 
+  it('should return messages sent to user updated after given time', async () => {
+    const useCase = createUseCase()
+    const result = await useCase.execute({
+      recipientUuid: '00000000-0000-0000-0000-000000000000',
+      lastSyncTime: 123,
+    })
+
+    expect(result.getValue()).toEqual([])
+  })
+
   it('should return error when recipient uuid is invalid', async () => {
     const useCase = createUseCase()
     const result = await useCase.execute({

+ 4 - 2
packages/syncing-server/src/Domain/UseCase/Messaging/GetMessagesSentToUser/GetMessagesSentToUser.ts

@@ -14,8 +14,10 @@ export class GetMessagesSentToUser implements UseCaseInterface<Message[]> {
     }
     const recipientUuid = recipientUuidOrError.getValue()
 
-    const messages = await this.messageRepository.findByRecipientUuid(recipientUuid)
+    if (dto.lastSyncTime) {
+      return Result.ok(await this.messageRepository.findByRecipientUuidUpdatedAfter(recipientUuid, dto.lastSyncTime))
+    }
 
-    return Result.ok(messages)
+    return Result.ok(await this.messageRepository.findByRecipientUuid(recipientUuid))
   }
 }

+ 1 - 0
packages/syncing-server/src/Domain/UseCase/Messaging/GetMessagesSentToUser/GetMessagesSentToUserDTO.ts

@@ -1,3 +1,4 @@
 export interface GetMessagesSentToUserDTO {
   recipientUuid: string
+  lastSyncTime?: number
 }

+ 43 - 0
packages/syncing-server/src/Domain/UseCase/Messaging/GetUserNotifications/GetUserNotifications.spec.ts

@@ -0,0 +1,43 @@
+import { NotificationRepositoryInterface } from '../../../Notifications/NotificationRepositoryInterface'
+import { GetUserNotifications } from './GetUserNotifications'
+
+describe('GetUserNotifications', () => {
+  let notificationRepository: NotificationRepositoryInterface
+
+  const createUseCase = () => new GetUserNotifications(notificationRepository)
+
+  beforeEach(() => {
+    notificationRepository = {} as jest.Mocked<NotificationRepositoryInterface>
+    notificationRepository.findByUserUuid = jest.fn().mockReturnValue([])
+    notificationRepository.findByUserUuidUpdatedAfter = jest.fn().mockReturnValue([])
+  })
+
+  it('should return notification for user', async () => {
+    const useCase = createUseCase()
+    const result = await useCase.execute({
+      userUuid: '00000000-0000-0000-0000-000000000000',
+    })
+
+    expect(result.getValue()).toEqual([])
+  })
+
+  it('should return notifications for user updated after given time', async () => {
+    const useCase = createUseCase()
+    const result = await useCase.execute({
+      userUuid: '00000000-0000-0000-0000-000000000000',
+      lastSyncTime: 123,
+    })
+
+    expect(result.getValue()).toEqual([])
+  })
+
+  it('should return error when user uuid is invalid', async () => {
+    const useCase = createUseCase()
+    const result = await useCase.execute({
+      userUuid: 'invalid',
+    })
+
+    expect(result.isFailed()).toBe(true)
+    expect(result.getError()).toBe('Given value is not a valid uuid: invalid')
+  })
+})

+ 22 - 0
packages/syncing-server/src/Domain/UseCase/Messaging/GetUserNotifications/GetUserNotifications.ts

@@ -0,0 +1,22 @@
+import { Result, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
+import { Notification } from '../../../Notifications/Notification'
+import { GetUserNotificationsDTO } from './GetUserNotificationsDTO'
+import { NotificationRepositoryInterface } from '../../../Notifications/NotificationRepositoryInterface'
+
+export class GetUserNotifications implements UseCaseInterface<Notification[]> {
+  constructor(private notificationRepository: NotificationRepositoryInterface) {}
+
+  async execute(dto: GetUserNotificationsDTO): Promise<Result<Notification[]>> {
+    const userUuidOrError = Uuid.create(dto.userUuid)
+    if (userUuidOrError.isFailed()) {
+      return Result.fail(userUuidOrError.getError())
+    }
+    const userUuid = userUuidOrError.getValue()
+
+    if (dto.lastSyncTime) {
+      return Result.ok(await this.notificationRepository.findByUserUuidUpdatedAfter(userUuid, dto.lastSyncTime))
+    }
+
+    return Result.ok(await this.notificationRepository.findByUserUuid(userUuid))
+  }
+}

+ 4 - 0
packages/syncing-server/src/Domain/UseCase/Messaging/GetUserNotifications/GetUserNotificationsDTO.ts

@@ -0,0 +1,4 @@
+export interface GetUserNotificationsDTO {
+  userUuid: string
+  lastSyncTime?: number
+}

+ 12 - 0
packages/syncing-server/src/Domain/UseCase/SharedVaults/GetSharedVaultInvitesSentToUser/GetSharedVaultInvitesSentToUser.spec.ts

@@ -23,6 +23,7 @@ describe('GetSharedVaultInvitesSentToUser', () => {
 
     sharedVaultInviteRepository = {} as jest.Mocked<SharedVaultInviteRepositoryInterface>
     sharedVaultInviteRepository.findByUserUuid = jest.fn().mockResolvedValue([invite])
+    sharedVaultInviteRepository.findByUserUuidUpdatedAfter = jest.fn().mockResolvedValue([invite])
   })
 
   it('should return invites sent to user', async () => {
@@ -35,6 +36,17 @@ describe('GetSharedVaultInvitesSentToUser', () => {
     expect(result.getValue()).toEqual([invite])
   })
 
+  it('should return invites sent to user updated after given time', async () => {
+    const useCase = createUseCase()
+
+    const result = await useCase.execute({
+      userUuid: '00000000-0000-0000-0000-000000000000',
+      lastSyncTime: 123,
+    })
+
+    expect(result.getValue()).toEqual([invite])
+  })
+
   it('should return empty array if no invites found', async () => {
     const useCase = createUseCase()
 

+ 4 - 0
packages/syncing-server/src/Domain/UseCase/SharedVaults/GetSharedVaultInvitesSentToUser/GetSharedVaultInvitesSentToUser.ts

@@ -13,6 +13,10 @@ export class GetSharedVaultInvitesSentToUser implements UseCaseInterface<SharedV
     }
     const userUuid = userUuidOrError.getValue()
 
+    if (dto.lastSyncTime) {
+      return Result.ok(await this.sharedVaultInviteRepository.findByUserUuidUpdatedAfter(userUuid, dto.lastSyncTime))
+    }
+
     return Result.ok(await this.sharedVaultInviteRepository.findByUserUuid(userUuid))
   }
 }

+ 1 - 0
packages/syncing-server/src/Domain/UseCase/SharedVaults/GetSharedVaultInvitesSentToUser/GetSharedVaultInvitesSentToUserDTO.ts

@@ -1,3 +1,4 @@
 export interface GetSharedVaultInvitesSentToUserDTO {
   userUuid: string
+  lastSyncTime?: number
 }

+ 5 - 0
packages/syncing-server/src/Domain/UseCase/Syncing/GetItems/GetItems.spec.ts

@@ -57,6 +57,7 @@ describe('GetItems', () => {
     expect(result.getValue()).toEqual({
       items: [item],
       cursorToken: undefined,
+      lastSyncTime: null,
     })
   })
 
@@ -76,6 +77,7 @@ describe('GetItems', () => {
     expect(result.getValue()).toEqual({
       items: [item],
       cursorToken: 'MjowLjAwMDEyMw==',
+      lastSyncTime: null,
     })
   })
 
@@ -93,6 +95,7 @@ describe('GetItems', () => {
     expect(result.getValue()).toEqual({
       items: [item],
       cursorToken: undefined,
+      lastSyncTime: 123.00000000000001,
     })
   })
 
@@ -113,6 +116,7 @@ describe('GetItems', () => {
     expect(result.getValue()).toEqual({
       items: [item],
       cursorToken: undefined,
+      lastSyncTime: 123,
     })
   })
 
@@ -147,6 +151,7 @@ describe('GetItems', () => {
     expect(result.getValue()).toEqual({
       items: [item],
       cursorToken: undefined,
+      lastSyncTime: null,
     })
   })
 })

+ 1 - 0
packages/syncing-server/src/Domain/UseCase/Syncing/GetItems/GetItems.ts

@@ -65,6 +65,7 @@ export class GetItems implements UseCaseInterface<GetItemsResult> {
     return Result.ok({
       items,
       cursorToken,
+      lastSyncTime,
     })
   }
 

+ 1 - 0
packages/syncing-server/src/Domain/UseCase/Syncing/GetItems/GetItemsResult.ts

@@ -3,4 +3,5 @@ import { Item } from '../../../Item/Item'
 export interface GetItemsResult {
   items: Item[]
   cursorToken?: string
+  lastSyncTime: number | null
 }

+ 122 - 1
packages/syncing-server/src/Domain/UseCase/Syncing/SyncItems/SyncItems.spec.ts

@@ -9,6 +9,10 @@ import { ContentType, Dates, Result, Timestamps, UniqueEntityId, Uuid } from '@s
 import { GetItems } from '../GetItems/GetItems'
 import { SaveItems } from '../SaveItems/SaveItems'
 import { ItemRepositoryInterface } from '../../../Item/ItemRepositoryInterface'
+import { GetSharedVaults } from '../../SharedVaults/GetSharedVaults/GetSharedVaults'
+import { GetMessagesSentToUser } from '../../Messaging/GetMessagesSentToUser/GetMessagesSentToUser'
+import { GetUserNotifications } from '../../Messaging/GetUserNotifications/GetUserNotifications'
+import { GetSharedVaultInvitesSentToUser } from '../../SharedVaults/GetSharedVaultInvitesSentToUser/GetSharedVaultInvitesSentToUser'
 
 describe('SyncItems', () => {
   let getItemsUseCase: GetItems
@@ -18,8 +22,21 @@ describe('SyncItems', () => {
   let item2: Item
   let item3: Item
   let itemHash: ItemHash
+  let getSharedVaultsUseCase: GetSharedVaults
+  let getSharedVaultInvitesSentToUserUseCase: GetSharedVaultInvitesSentToUser
+  let getMessagesSentToUser: GetMessagesSentToUser
+  let getUserNotifications: GetUserNotifications
 
-  const createUseCase = () => new SyncItems(itemRepository, getItemsUseCase, saveItemsUseCase)
+  const createUseCase = () =>
+    new SyncItems(
+      itemRepository,
+      getItemsUseCase,
+      saveItemsUseCase,
+      getSharedVaultsUseCase,
+      getSharedVaultInvitesSentToUserUseCase,
+      getMessagesSentToUser,
+      getUserNotifications,
+    )
 
   beforeEach(() => {
     item1 = Item.create(
@@ -104,6 +121,18 @@ describe('SyncItems', () => {
 
     itemRepository = {} as jest.Mocked<ItemRepositoryInterface>
     itemRepository.findAll = jest.fn().mockReturnValue([item3, item1])
+
+    getSharedVaultsUseCase = {} as jest.Mocked<GetSharedVaults>
+    getSharedVaultsUseCase.execute = jest.fn().mockReturnValue(Result.ok([]))
+
+    getSharedVaultInvitesSentToUserUseCase = {} as jest.Mocked<GetSharedVaultInvitesSentToUser>
+    getSharedVaultInvitesSentToUserUseCase.execute = jest.fn().mockReturnValue(Result.ok([]))
+
+    getMessagesSentToUser = {} as jest.Mocked<GetMessagesSentToUser>
+    getMessagesSentToUser.execute = jest.fn().mockReturnValue(Result.ok([]))
+
+    getUserNotifications = {} as jest.Mocked<GetUserNotifications>
+    getUserNotifications.execute = jest.fn().mockReturnValue(Result.ok([]))
   })
 
   it('should sync items', async () => {
@@ -126,6 +155,10 @@ describe('SyncItems', () => {
       retrievedItems: [item1],
       savedItems: [item2],
       syncToken: 'qwerty',
+      sharedVaults: [],
+      sharedVaultInvites: [],
+      notifications: [],
+      messages: [],
     })
 
     expect(getItemsUseCase.execute).toHaveBeenCalledWith({
@@ -162,6 +195,10 @@ describe('SyncItems', () => {
       retrievedItems: [item3, item1],
       savedItems: [item2],
       syncToken: 'qwerty',
+      sharedVaults: [],
+      sharedVaultInvites: [],
+      notifications: [],
+      messages: [],
     })
   })
 
@@ -219,6 +256,10 @@ describe('SyncItems', () => {
       retrievedItems: [item1],
       savedItems: [],
       syncToken: 'qwerty',
+      sharedVaults: [],
+      sharedVaultInvites: [],
+      notifications: [],
+      messages: [],
     })
   })
 
@@ -261,4 +302,84 @@ describe('SyncItems', () => {
 
     expect(result.isFailed()).toBeTruthy()
   })
+
+  it('should return error if get shared vaults fails', async () => {
+    getSharedVaultsUseCase.execute = jest.fn().mockReturnValue(Result.fail('error'))
+
+    const result = await createUseCase().execute({
+      userUuid: '1-2-3',
+      itemHashes: [itemHash],
+      computeIntegrityHash: false,
+      syncToken: 'foo',
+      readOnlyAccess: false,
+      sessionUuid: '2-3-4',
+      cursorToken: 'bar',
+      limit: 10,
+      contentType: 'Note',
+      apiVersion: ApiVersion.v20200115,
+      snjsVersion: '1.2.3',
+    })
+
+    expect(result.isFailed()).toBeTruthy()
+  })
+
+  it('should return error if get shared vault invites fails', async () => {
+    getSharedVaultInvitesSentToUserUseCase.execute = jest.fn().mockReturnValue(Result.fail('error'))
+
+    const result = await createUseCase().execute({
+      userUuid: '1-2-3',
+      itemHashes: [itemHash],
+      computeIntegrityHash: false,
+      syncToken: 'foo',
+      readOnlyAccess: false,
+      sessionUuid: '2-3-4',
+      cursorToken: 'bar',
+      limit: 10,
+      contentType: 'Note',
+      apiVersion: ApiVersion.v20200115,
+      snjsVersion: '1.2.3',
+    })
+
+    expect(result.isFailed()).toBeTruthy()
+  })
+
+  it('should return error if get messages fails', async () => {
+    getMessagesSentToUser.execute = jest.fn().mockReturnValue(Result.fail('error'))
+
+    const result = await createUseCase().execute({
+      userUuid: '1-2-3',
+      itemHashes: [itemHash],
+      computeIntegrityHash: false,
+      syncToken: 'foo',
+      readOnlyAccess: false,
+      sessionUuid: '2-3-4',
+      cursorToken: 'bar',
+      limit: 10,
+      contentType: 'Note',
+      apiVersion: ApiVersion.v20200115,
+      snjsVersion: '1.2.3',
+    })
+
+    expect(result.isFailed()).toBeTruthy()
+  })
+
+  it('should return error if get user notifications fails', async () => {
+    getUserNotifications.execute = jest.fn().mockReturnValue(Result.fail('error'))
+
+    const result = await createUseCase().execute({
+      userUuid: '1-2-3',
+      itemHashes: [itemHash],
+      computeIntegrityHash: false,
+      syncToken: 'foo',
+      readOnlyAccess: false,
+      sessionUuid: '2-3-4',
+      cursorToken: 'bar',
+      limit: 10,
+      contentType: 'Note',
+      apiVersion: ApiVersion.v20200115,
+      snjsVersion: '1.2.3',
+    })
+
+    expect(result.isFailed()).toBeTruthy()
+  })
 })

+ 48 - 0
packages/syncing-server/src/Domain/UseCase/Syncing/SyncItems/SyncItems.ts

@@ -7,12 +7,20 @@ import { SyncItemsResponse } from './SyncItemsResponse'
 import { ItemRepositoryInterface } from '../../../Item/ItemRepositoryInterface'
 import { GetItems } from '../GetItems/GetItems'
 import { SaveItems } from '../SaveItems/SaveItems'
+import { GetSharedVaults } from '../../SharedVaults/GetSharedVaults/GetSharedVaults'
+import { GetSharedVaultInvitesSentToUser } from '../../SharedVaults/GetSharedVaultInvitesSentToUser/GetSharedVaultInvitesSentToUser'
+import { GetMessagesSentToUser } from '../../Messaging/GetMessagesSentToUser/GetMessagesSentToUser'
+import { GetUserNotifications } from '../../Messaging/GetUserNotifications/GetUserNotifications'
 
 export class SyncItems implements UseCaseInterface<SyncItemsResponse> {
   constructor(
     private itemRepository: ItemRepositoryInterface,
     private getItemsUseCase: GetItems,
     private saveItemsUseCase: SaveItems,
+    private getSharedVaultsUseCase: GetSharedVaults,
+    private getSharedVaultInvitesSentToUserUseCase: GetSharedVaultInvitesSentToUser,
+    private getMessagesSentToUser: GetMessagesSentToUser,
+    private getUserNotifications: GetUserNotifications,
   ) {}
 
   async execute(dto: SyncItemsDTO): Promise<Result<SyncItemsResponse>> {
@@ -45,12 +53,52 @@ export class SyncItems implements UseCaseInterface<SyncItemsResponse> {
       retrievedItems = await this.frontLoadKeysItemsToTop(dto.userUuid, retrievedItems)
     }
 
+    const sharedVaultsOrError = await this.getSharedVaultsUseCase.execute({
+      userUuid: dto.userUuid,
+      lastSyncTime: getItemsResult.lastSyncTime ?? undefined,
+    })
+    if (sharedVaultsOrError.isFailed()) {
+      return Result.fail(sharedVaultsOrError.getError())
+    }
+    const sharedVaults = sharedVaultsOrError.getValue()
+
+    const sharedVaultInvitesOrError = await this.getSharedVaultInvitesSentToUserUseCase.execute({
+      userUuid: dto.userUuid,
+      lastSyncTime: getItemsResult.lastSyncTime ?? undefined,
+    })
+    if (sharedVaultInvitesOrError.isFailed()) {
+      return Result.fail(sharedVaultInvitesOrError.getError())
+    }
+    const sharedVaultInvites = sharedVaultInvitesOrError.getValue()
+
+    const messagesOrError = await this.getMessagesSentToUser.execute({
+      recipientUuid: dto.userUuid,
+      lastSyncTime: getItemsResult.lastSyncTime ?? undefined,
+    })
+    if (messagesOrError.isFailed()) {
+      return Result.fail(messagesOrError.getError())
+    }
+    const messages = messagesOrError.getValue()
+
+    const notificationsOrError = await this.getUserNotifications.execute({
+      userUuid: dto.userUuid,
+      lastSyncTime: getItemsResult.lastSyncTime ?? undefined,
+    })
+    if (notificationsOrError.isFailed()) {
+      return Result.fail(notificationsOrError.getError())
+    }
+    const notifications = notificationsOrError.getValue()
+
     const syncResponse: SyncItemsResponse = {
       retrievedItems,
       syncToken: saveItemsResult.syncToken,
       savedItems: saveItemsResult.savedItems,
       conflicts: saveItemsResult.conflicts,
       cursorToken: getItemsResult.cursorToken,
+      sharedVaultInvites,
+      sharedVaults,
+      messages,
+      notifications,
     }
 
     return Result.ok(syncResponse)

+ 8 - 0
packages/syncing-server/src/Domain/UseCase/Syncing/SyncItems/SyncItemsResponse.ts

@@ -1,10 +1,18 @@
 import { Item } from '../../../Item/Item'
 import { ItemConflict } from '../../../Item/ItemConflict'
+import { Message } from '../../../Message/Message'
+import { Notification } from '../../../Notifications/Notification'
+import { SharedVault } from '../../../SharedVault/SharedVault'
+import { SharedVaultInvite } from '../../../SharedVault/User/Invite/SharedVaultInvite'
 
 export type SyncItemsResponse = {
   retrievedItems: Array<Item>
   savedItems: Array<Item>
   conflicts: Array<ItemConflict>
   syncToken: string
+  sharedVaults: SharedVault[]
+  sharedVaultInvites: SharedVaultInvite[]
+  messages: Message[]
+  notifications: Notification[]
   cursorToken?: string
 }

+ 1 - 1
packages/syncing-server/src/Infra/InversifyExpressUtils/HomeServer/HomeServerSharedVaultInvitesController.ts

@@ -62,7 +62,7 @@ export class HomeServerSharedVaultInvitesController extends BaseHttpController {
     const result = await this.inviteUserToSharedVaultUseCase.execute({
       sharedVaultUuid: request.params.sharedVaultUuid,
       senderUuid: response.locals.user.uuid,
-      recipientUuid: request.body.recipient_uid,
+      recipientUuid: request.body.recipient_uuid,
       encryptedMessage: request.body.encrypted_message,
       permission: request.body.permission,
     })

+ 14 - 0
packages/syncing-server/src/Infra/TypeORM/TypeORMMessageRepository.ts

@@ -11,6 +11,20 @@ export class TypeORMMessageRepository implements MessageRepositoryInterface {
     private mapper: MapperInterface<Message, TypeORMMessage>,
   ) {}
 
+  async findByRecipientUuidUpdatedAfter(uuid: Uuid, updatedAtTimestamp: number): Promise<Message[]> {
+    const persistence = await this.ormRepository
+      .createQueryBuilder('message')
+      .where('message.recipient_uuid = :recipientUuid', {
+        recipientUuid: uuid.value,
+      })
+      .andWhere('message.updated_at_timestamp > :updatedAtTimestamp', {
+        updatedAtTimestamp,
+      })
+      .getMany()
+
+    return persistence.map((p) => this.mapper.toDomain(p))
+  }
+
   async findByRecipientUuid(uuid: Uuid): Promise<Message[]> {
     const persistence = await this.ormRepository
       .createQueryBuilder('message')

+ 26 - 1
packages/syncing-server/src/Infra/TypeORM/TypeORMNotificationRepository.ts

@@ -1,5 +1,5 @@
 import { Repository } from 'typeorm'
-import { MapperInterface } from '@standardnotes/domain-core'
+import { MapperInterface, Uuid } from '@standardnotes/domain-core'
 
 import { NotificationRepositoryInterface } from '../../Domain/Notifications/NotificationRepositoryInterface'
 import { TypeORMNotification } from './TypeORMNotification'
@@ -16,4 +16,29 @@ export class TypeORMNotificationRepository implements NotificationRepositoryInte
 
     await this.ormRepository.save(persistence)
   }
+
+  async findByUserUuidUpdatedAfter(uuid: Uuid, updatedAtTimestamp: number): Promise<Notification[]> {
+    const persistence = await this.ormRepository
+      .createQueryBuilder('notification')
+      .where('notification.user_uuid = :userUuid', {
+        userUuid: uuid.value,
+      })
+      .andWhere('notification.updated_at_timestamp > :updatedAtTimestamp', {
+        updatedAtTimestamp,
+      })
+      .getMany()
+
+    return persistence.map((p) => this.mapper.toDomain(p))
+  }
+
+  async findByUserUuid(uuid: Uuid): Promise<Notification[]> {
+    const persistence = await this.ormRepository
+      .createQueryBuilder('notification')
+      .where('notification.user_uuid = :userUuid', {
+        userUuid: uuid.value,
+      })
+      .getMany()
+
+    return persistence.map((p) => this.mapper.toDomain(p))
+  }
 }

+ 14 - 0
packages/syncing-server/src/Infra/TypeORM/TypeORMSharedVaultInviteRepository.ts

@@ -11,6 +11,20 @@ export class TypeORMSharedVaultInviteRepository implements SharedVaultInviteRepo
     private mapper: MapperInterface<SharedVaultInvite, TypeORMSharedVaultInvite>,
   ) {}
 
+  async findByUserUuidUpdatedAfter(userUuid: Uuid, updatedAtTimestamp: number): Promise<SharedVaultInvite[]> {
+    const persistence = await this.ormRepository
+      .createQueryBuilder('shared_vault_invite')
+      .where('shared_vault_invite.user_uuid = :userUuid', {
+        userUuid: userUuid.value,
+      })
+      .andWhere('shared_vault_invite.updated_at_timestamp > :updatedAtTimestamp', {
+        updatedAtTimestamp,
+      })
+      .getMany()
+
+    return persistence.map((p) => this.mapper.toDomain(p))
+  }
+
   async findBySenderUuidAndSharedVaultUuid(dto: {
     senderUuid: Uuid
     sharedVaultUuid: Uuid

+ 21 - 0
packages/syncing-server/src/Mapping/Http/NotificationHttpMapper.ts

@@ -0,0 +1,21 @@
+import { MapperInterface } from '@standardnotes/domain-core'
+
+import { Notification } from '../../Domain/Notifications/Notification'
+import { NotificationHttpRepresentation } from './NotificationHttpRepresentation'
+
+export class NotificationHttpMapper implements MapperInterface<Notification, NotificationHttpRepresentation> {
+  toDomain(_projection: NotificationHttpRepresentation): Notification {
+    throw new Error('Mapping from http representation to domain is not implemented.')
+  }
+
+  toProjection(domain: Notification): NotificationHttpRepresentation {
+    return {
+      uuid: domain.id.toString(),
+      user_uuid: domain.props.userUuid.value,
+      type: domain.props.type.value,
+      payload: domain.props.payload,
+      created_at_timestamp: domain.props.timestamps.createdAt,
+      updated_at_timestamp: domain.props.timestamps.updatedAt,
+    }
+  }
+}

+ 8 - 0
packages/syncing-server/src/Mapping/Http/NotificationHttpRepresentation.ts

@@ -0,0 +1,8 @@
+export interface NotificationHttpRepresentation {
+  uuid: string
+  user_uuid: string
+  type: string
+  payload: string
+  created_at_timestamp: number
+  updated_at_timestamp: number
+}

+ 1 - 1
packages/syncing-server/src/Mapping/Http/SharedVaultInviteHttpMapper.ts

@@ -17,7 +17,7 @@ export class SharedVaultInviteHttpMapper
       user_uuid: domain.props.userUuid.value,
       sender_uuid: domain.props.senderUuid.value,
       encrypted_message: domain.props.encryptedMessage,
-      permissions: domain.props.permission.value,
+      permission: domain.props.permission.value,
       created_at_timestamp: domain.props.timestamps.createdAt,
       updated_at_timestamp: domain.props.timestamps.updatedAt,
     }

+ 1 - 1
packages/syncing-server/src/Mapping/Http/SharedVaultInviteHttpRepresentation.ts

@@ -4,7 +4,7 @@ export interface SharedVaultInviteHttpRepresentation {
   user_uuid: string
   sender_uuid: string
   encrypted_message: string
-  permissions: string
+  permission: string
   created_at_timestamp: number
   updated_at_timestamp: number
 }