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
This commit is contained in:
parent
f714aaa0e9
commit
efa4d7fc60
41 changed files with 545 additions and 113 deletions
|
@ -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,
|
||||
),
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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`')
|
||||
}
|
||||
}
|
|
@ -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',
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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"')
|
||||
}
|
||||
}
|
|
@ -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") ')
|
||||
}
|
||||
}
|
|
@ -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,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`')
|
||||
}
|
||||
}
|
|
@ -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") ')
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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'),
|
||||
|
|
|
@ -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[]
|
||||
}
|
||||
|
|
|
@ -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: [],
|
||||
|
|
|
@ -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>],
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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[]>
|
||||
}
|
||||
|
|
|
@ -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[]>
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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,3 +1,4 @@
|
|||
export interface GetMessagesSentToUserDTO {
|
||||
recipientUuid: string
|
||||
lastSyncTime?: number
|
||||
}
|
||||
|
|
|
@ -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')
|
||||
})
|
||||
})
|
|
@ -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))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
export interface GetUserNotificationsDTO {
|
||||
userUuid: string
|
||||
lastSyncTime?: number
|
||||
}
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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,3 +1,4 @@
|
|||
export interface GetSharedVaultInvitesSentToUserDTO {
|
||||
userUuid: string
|
||||
lastSyncTime?: number
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -65,6 +65,7 @@ export class GetItems implements UseCaseInterface<GetItemsResult> {
|
|||
return Result.ok({
|
||||
items,
|
||||
cursorToken,
|
||||
lastSyncTime,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -3,4 +3,5 @@ import { Item } from '../../../Item/Item'
|
|||
export interface GetItemsResult {
|
||||
items: Item[]
|
||||
cursorToken?: string
|
||||
lastSyncTime: number | null
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
export interface NotificationHttpRepresentation {
|
||||
uuid: string
|
||||
user_uuid: string
|
||||
type: string
|
||||
payload: string
|
||||
created_at_timestamp: number
|
||||
updated_at_timestamp: number
|
||||
}
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue