feat(syncing-server): transfer shared vault ownership to designated survivor upon account deletion (#845)
This commit is contained in:
parent
4802d7e876
commit
0a1080ce2a
7 changed files with 357 additions and 35 deletions
|
@ -170,6 +170,7 @@ import { RemoveItemsFromSharedVault } from '../Domain/UseCase/SharedVaults/Remov
|
|||
import { SharedVaultRemovedEventHandler } from '../Domain/Handler/SharedVaultRemovedEventHandler'
|
||||
import { DesignateSurvivor } from '../Domain/UseCase/SharedVaults/DesignateSurvivor/DesignateSurvivor'
|
||||
import { RemoveUserFromSharedVaults } from '../Domain/UseCase/SharedVaults/RemoveUserFromSharedVaults/RemoveUserFromSharedVaults'
|
||||
import { TransferSharedVault } from '../Domain/UseCase/SharedVaults/TransferSharedVault/TransferSharedVault'
|
||||
|
||||
export class ContainerConfigLoader {
|
||||
private readonly DEFAULT_CONTENT_SIZE_TRANSFER_LIMIT = 10_000_000
|
||||
|
@ -782,27 +783,6 @@ export class ContainerConfigLoader {
|
|||
container.get(TYPES.Sync_Timer),
|
||||
),
|
||||
)
|
||||
container
|
||||
.bind<DeleteSharedVault>(TYPES.Sync_DeleteSharedVault)
|
||||
.toConstantValue(
|
||||
new DeleteSharedVault(
|
||||
container.get<SharedVaultRepositoryInterface>(TYPES.Sync_SharedVaultRepository),
|
||||
container.get<SharedVaultUserRepositoryInterface>(TYPES.Sync_SharedVaultUserRepository),
|
||||
container.get<SharedVaultInviteRepositoryInterface>(TYPES.Sync_SharedVaultInviteRepository),
|
||||
container.get<RemoveUserFromSharedVault>(TYPES.Sync_RemoveSharedVaultUser),
|
||||
container.get<DeclineInviteToSharedVault>(TYPES.Sync_DeclineInviteToSharedVault),
|
||||
container.get<DomainEventFactoryInterface>(TYPES.Sync_DomainEventFactory),
|
||||
container.get<DomainEventPublisherInterface>(TYPES.Sync_DomainEventPublisher),
|
||||
),
|
||||
)
|
||||
container
|
||||
.bind<DeleteSharedVaults>(TYPES.Sync_DeleteSharedVaults)
|
||||
.toConstantValue(
|
||||
new DeleteSharedVaults(
|
||||
container.get<SharedVaultRepositoryInterface>(TYPES.Sync_SharedVaultRepository),
|
||||
container.get<DeleteSharedVault>(TYPES.Sync_DeleteSharedVault),
|
||||
),
|
||||
)
|
||||
container
|
||||
.bind<CreateSharedVaultFileValetToken>(TYPES.Sync_CreateSharedVaultFileValetToken)
|
||||
.toConstantValue(
|
||||
|
@ -887,6 +867,37 @@ export class ContainerConfigLoader {
|
|||
container.get<Logger>(TYPES.Sync_Logger),
|
||||
),
|
||||
)
|
||||
container
|
||||
.bind<TransferSharedVault>(TYPES.Sync_TransferSharedVault)
|
||||
.toConstantValue(
|
||||
new TransferSharedVault(
|
||||
container.get<SharedVaultRepositoryInterface>(TYPES.Sync_SharedVaultRepository),
|
||||
container.get<SharedVaultUserRepositoryInterface>(TYPES.Sync_SharedVaultUserRepository),
|
||||
container.get<TimerInterface>(TYPES.Sync_Timer),
|
||||
),
|
||||
)
|
||||
container
|
||||
.bind<DeleteSharedVault>(TYPES.Sync_DeleteSharedVault)
|
||||
.toConstantValue(
|
||||
new DeleteSharedVault(
|
||||
container.get<SharedVaultRepositoryInterface>(TYPES.Sync_SharedVaultRepository),
|
||||
container.get<SharedVaultUserRepositoryInterface>(TYPES.Sync_SharedVaultUserRepository),
|
||||
container.get<SharedVaultInviteRepositoryInterface>(TYPES.Sync_SharedVaultInviteRepository),
|
||||
container.get<RemoveUserFromSharedVault>(TYPES.Sync_RemoveSharedVaultUser),
|
||||
container.get<DeclineInviteToSharedVault>(TYPES.Sync_DeclineInviteToSharedVault),
|
||||
container.get<DomainEventFactoryInterface>(TYPES.Sync_DomainEventFactory),
|
||||
container.get<DomainEventPublisherInterface>(TYPES.Sync_DomainEventPublisher),
|
||||
container.get<TransferSharedVault>(TYPES.Sync_TransferSharedVault),
|
||||
),
|
||||
)
|
||||
container
|
||||
.bind<DeleteSharedVaults>(TYPES.Sync_DeleteSharedVaults)
|
||||
.toConstantValue(
|
||||
new DeleteSharedVaults(
|
||||
container.get<SharedVaultRepositoryInterface>(TYPES.Sync_SharedVaultRepository),
|
||||
container.get<DeleteSharedVault>(TYPES.Sync_DeleteSharedVault),
|
||||
),
|
||||
)
|
||||
|
||||
// Services
|
||||
container
|
||||
|
|
|
@ -89,6 +89,7 @@ const TYPES = {
|
|||
Sync_RemoveItemsFromSharedVault: Symbol.for('Sync_RemoveItemsFromSharedVault'),
|
||||
Sync_DesignateSurvivor: Symbol.for('Sync_DesignateSurvivor'),
|
||||
Sync_RemoveUserFromSharedVaults: Symbol.for('Sync_RemoveUserFromSharedVaults'),
|
||||
Sync_TransferSharedVault: Symbol.for('Sync_TransferSharedVault'),
|
||||
// Handlers
|
||||
Sync_AccountDeletionRequestedEventHandler: Symbol.for('Sync_AccountDeletionRequestedEventHandler'),
|
||||
Sync_DuplicateItemSyncedEventHandler: Symbol.for('Sync_DuplicateItemSyncedEventHandler'),
|
||||
|
|
|
@ -10,6 +10,7 @@ import { RemoveUserFromSharedVault } from '../RemoveUserFromSharedVault/RemoveUs
|
|||
import { DeclineInviteToSharedVault } from '../DeclineInviteToSharedVault/DeclineInviteToSharedVault'
|
||||
import { SharedVaultInvite } from '../../../SharedVault/User/Invite/SharedVaultInvite'
|
||||
import { DomainEventFactoryInterface } from '../../../Event/DomainEventFactoryInterface'
|
||||
import { TransferSharedVault } from '../TransferSharedVault/TransferSharedVault'
|
||||
|
||||
describe('DeleteSharedVault', () => {
|
||||
let sharedVaultRepository: SharedVaultRepositoryInterface
|
||||
|
@ -22,6 +23,7 @@ describe('DeleteSharedVault', () => {
|
|||
let sharedVaultInvite: SharedVaultInvite
|
||||
let domainEventFactory: DomainEventFactoryInterface
|
||||
let domainEventPublisher: DomainEventPublisherInterface
|
||||
let transferSharedVault: TransferSharedVault
|
||||
|
||||
const createUseCase = () =>
|
||||
new DeleteSharedVault(
|
||||
|
@ -32,9 +34,13 @@ describe('DeleteSharedVault', () => {
|
|||
declineInviteToSharedVault,
|
||||
domainEventFactory,
|
||||
domainEventPublisher,
|
||||
transferSharedVault,
|
||||
)
|
||||
|
||||
beforeEach(() => {
|
||||
transferSharedVault = {} as jest.Mocked<TransferSharedVault>
|
||||
transferSharedVault.execute = jest.fn().mockReturnValue(Result.ok())
|
||||
|
||||
sharedVault = SharedVault.create({
|
||||
fileUploadBytesUsed: 2,
|
||||
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
|
@ -53,6 +59,7 @@ describe('DeleteSharedVault', () => {
|
|||
}).getValue()
|
||||
sharedVaultUserRepository = {} as jest.Mocked<SharedVaultUserRepositoryInterface>
|
||||
sharedVaultUserRepository.findBySharedVaultUuid = jest.fn().mockResolvedValue([sharedVaultUser])
|
||||
sharedVaultUserRepository.findDesignatedSurvivorBySharedVaultUuid = jest.fn().mockResolvedValue(null)
|
||||
|
||||
sharedVaultInvite = SharedVaultInvite.create({
|
||||
encryptedMessage: 'test',
|
||||
|
@ -171,7 +178,6 @@ describe('DeleteSharedVault', () => {
|
|||
|
||||
expect(result.isFailed()).toBeTruthy()
|
||||
expect(sharedVaultRepository.remove).not.toHaveBeenCalled()
|
||||
expect(declineInviteToSharedVault.execute).not.toHaveBeenCalled()
|
||||
expect(removeUserFromSharedVault.execute).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
|
@ -187,6 +193,59 @@ describe('DeleteSharedVault', () => {
|
|||
expect(result.isFailed()).toBeTruthy()
|
||||
expect(sharedVaultRepository.remove).not.toHaveBeenCalled()
|
||||
expect(declineInviteToSharedVault.execute).toHaveBeenCalled()
|
||||
expect(removeUserFromSharedVault.execute).toHaveBeenCalled()
|
||||
expect(removeUserFromSharedVault.execute).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
describe('when shared vault has designated survivor', () => {
|
||||
beforeEach(() => {
|
||||
sharedVaultUserRepository.findDesignatedSurvivorBySharedVaultUuid = jest.fn().mockResolvedValue(sharedVaultUser)
|
||||
})
|
||||
|
||||
it('should transfer shared vault to designated survivor', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
|
||||
originatorUuid: '00000000-0000-0000-0000-000000000000',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBeFalsy()
|
||||
expect(sharedVaultRepository.remove).not.toHaveBeenCalled()
|
||||
expect(declineInviteToSharedVault.execute).toHaveBeenCalled()
|
||||
expect(removeUserFromSharedVault.execute).toHaveBeenCalled()
|
||||
expect(transferSharedVault.execute).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should fail if transfering shared vault to designated survivor fails', async () => {
|
||||
transferSharedVault.execute = jest.fn().mockReturnValue(Result.fail('failed'))
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
|
||||
originatorUuid: '00000000-0000-0000-0000-000000000000',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBeTruthy()
|
||||
expect(sharedVaultRepository.remove).not.toHaveBeenCalled()
|
||||
expect(declineInviteToSharedVault.execute).toHaveBeenCalled()
|
||||
expect(removeUserFromSharedVault.execute).not.toHaveBeenCalled()
|
||||
expect(transferSharedVault.execute).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should fail if removing owner from shared vault fails', async () => {
|
||||
removeUserFromSharedVault.execute = jest.fn().mockReturnValue(Result.fail('failed'))
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
|
||||
originatorUuid: '00000000-0000-0000-0000-000000000000',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBeTruthy()
|
||||
expect(sharedVaultRepository.remove).not.toHaveBeenCalled()
|
||||
expect(declineInviteToSharedVault.execute).toHaveBeenCalled()
|
||||
expect(removeUserFromSharedVault.execute).toHaveBeenCalled()
|
||||
expect(transferSharedVault.execute).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -8,6 +8,7 @@ import { SharedVaultInviteRepositoryInterface } from '../../../SharedVault/User/
|
|||
import { RemoveUserFromSharedVault } from '../RemoveUserFromSharedVault/RemoveUserFromSharedVault'
|
||||
import { DeclineInviteToSharedVault } from '../DeclineInviteToSharedVault/DeclineInviteToSharedVault'
|
||||
import { DomainEventFactoryInterface } from '../../../Event/DomainEventFactoryInterface'
|
||||
import { TransferSharedVault } from '../TransferSharedVault/TransferSharedVault'
|
||||
|
||||
export class DeleteSharedVault implements UseCaseInterface<void> {
|
||||
constructor(
|
||||
|
@ -18,6 +19,7 @@ export class DeleteSharedVault implements UseCaseInterface<void> {
|
|||
private declineInviteToSharedVault: DeclineInviteToSharedVault,
|
||||
private domainEventFactory: DomainEventFactoryInterface,
|
||||
private domainEventPublisher: DomainEventPublisherInterface,
|
||||
private transferSharedVault: TransferSharedVault,
|
||||
) {}
|
||||
|
||||
async execute(dto: DeleteSharedVaultDTO): Promise<Result<void>> {
|
||||
|
@ -42,13 +44,11 @@ export class DeleteSharedVault implements UseCaseInterface<void> {
|
|||
return Result.fail('Shared vault does not belong to the user')
|
||||
}
|
||||
|
||||
const sharedVaultUsers = await this.sharedVaultUserRepository.findBySharedVaultUuid(sharedVaultUuid)
|
||||
for (const sharedVaultUser of sharedVaultUsers) {
|
||||
const result = await this.removeUserFromSharedVault.execute({
|
||||
originatorUuid: originatorUuid.value,
|
||||
sharedVaultUuid: sharedVaultUuid.value,
|
||||
userUuid: sharedVaultUser.props.userUuid.value,
|
||||
forceRemoveOwner: true,
|
||||
const sharedVaultInvites = await this.sharedVaultInviteRepository.findBySharedVaultUuid(sharedVaultUuid)
|
||||
for (const sharedVaultInvite of sharedVaultInvites) {
|
||||
const result = await this.declineInviteToSharedVault.execute({
|
||||
inviteUuid: sharedVaultInvite.id.toString(),
|
||||
userUuid: sharedVaultInvite.props.userUuid.value,
|
||||
})
|
||||
|
||||
if (result.isFailed()) {
|
||||
|
@ -56,11 +56,39 @@ export class DeleteSharedVault implements UseCaseInterface<void> {
|
|||
}
|
||||
}
|
||||
|
||||
const sharedVaultInvites = await this.sharedVaultInviteRepository.findBySharedVaultUuid(sharedVaultUuid)
|
||||
for (const sharedVaultInvite of sharedVaultInvites) {
|
||||
const result = await this.declineInviteToSharedVault.execute({
|
||||
inviteUuid: sharedVaultInvite.id.toString(),
|
||||
userUuid: sharedVaultInvite.props.userUuid.value,
|
||||
const sharedVaultDesignatedSurvivor =
|
||||
await this.sharedVaultUserRepository.findDesignatedSurvivorBySharedVaultUuid(sharedVaultUuid)
|
||||
if (sharedVaultDesignatedSurvivor) {
|
||||
const result = await this.transferSharedVault.execute({
|
||||
sharedVaultUid: sharedVaultUuid.value,
|
||||
fromUserUuid: originatorUuid.value,
|
||||
toUserUuid: sharedVaultDesignatedSurvivor.props.userUuid.value,
|
||||
})
|
||||
|
||||
if (result.isFailed()) {
|
||||
return Result.fail(result.getError())
|
||||
}
|
||||
|
||||
const removingOwnerFromSharedVaultResult = await this.removeUserFromSharedVault.execute({
|
||||
originatorUuid: originatorUuid.value,
|
||||
sharedVaultUuid: sharedVaultUuid.value,
|
||||
userUuid: originatorUuid.value,
|
||||
forceRemoveOwner: true,
|
||||
})
|
||||
if (removingOwnerFromSharedVaultResult.isFailed()) {
|
||||
return Result.fail(removingOwnerFromSharedVaultResult.getError())
|
||||
}
|
||||
|
||||
return Result.ok()
|
||||
}
|
||||
|
||||
const sharedVaultUsers = await this.sharedVaultUserRepository.findBySharedVaultUuid(sharedVaultUuid)
|
||||
for (const sharedVaultUser of sharedVaultUsers) {
|
||||
const result = await this.removeUserFromSharedVault.execute({
|
||||
originatorUuid: originatorUuid.value,
|
||||
sharedVaultUuid: sharedVaultUuid.value,
|
||||
userUuid: sharedVaultUser.props.userUuid.value,
|
||||
forceRemoveOwner: true,
|
||||
})
|
||||
|
||||
if (result.isFailed()) {
|
||||
|
|
|
@ -0,0 +1,148 @@
|
|||
import { TimerInterface } from '@standardnotes/time'
|
||||
import { SharedVaultUser, SharedVaultUserPermission, Timestamps, Uuid } from '@standardnotes/domain-core'
|
||||
|
||||
import { SharedVaultRepositoryInterface } from '../../../SharedVault/SharedVaultRepositoryInterface'
|
||||
import { SharedVaultUserRepositoryInterface } from '../../../SharedVault/User/SharedVaultUserRepositoryInterface'
|
||||
import { TransferSharedVault } from './TransferSharedVault'
|
||||
import { SharedVault } from '../../../SharedVault/SharedVault'
|
||||
|
||||
describe('TransferSharedVault', () => {
|
||||
let sharedVault: SharedVault
|
||||
let sharedVaultUser: SharedVaultUser
|
||||
let sharedVaultRepository: SharedVaultRepositoryInterface
|
||||
let sharedVaultUserRepository: SharedVaultUserRepositoryInterface
|
||||
let timer: TimerInterface
|
||||
|
||||
const createUseCase = () => new TransferSharedVault(sharedVaultRepository, sharedVaultUserRepository, timer)
|
||||
|
||||
beforeEach(() => {
|
||||
sharedVault = SharedVault.create({
|
||||
fileUploadBytesUsed: 2,
|
||||
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
timestamps: Timestamps.create(123, 123).getValue(),
|
||||
}).getValue()
|
||||
|
||||
sharedVaultUser = SharedVaultUser.create({
|
||||
permission: SharedVaultUserPermission.create(SharedVaultUserPermission.PERMISSIONS.Read).getValue(),
|
||||
sharedVaultUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
timestamps: Timestamps.create(123, 123).getValue(),
|
||||
isDesignatedSurvivor: false,
|
||||
}).getValue()
|
||||
|
||||
sharedVaultRepository = {} as jest.Mocked<SharedVaultRepositoryInterface>
|
||||
sharedVaultRepository.findByUuid = jest.fn().mockResolvedValue(sharedVault)
|
||||
sharedVaultRepository.save = jest.fn()
|
||||
|
||||
sharedVaultUserRepository = {} as jest.Mocked<SharedVaultUserRepositoryInterface>
|
||||
sharedVaultUserRepository.findByUserUuidAndSharedVaultUuid = jest.fn().mockResolvedValue(sharedVaultUser)
|
||||
sharedVaultUserRepository.save = jest.fn()
|
||||
|
||||
timer = {} as jest.Mocked<TimerInterface>
|
||||
timer.getTimestampInMicroseconds = jest.fn().mockReturnValue(123)
|
||||
})
|
||||
|
||||
it('should transfer shared vault to another user', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
sharedVaultUid: '00000000-0000-0000-0000-000000000000',
|
||||
fromUserUuid: '00000000-0000-0000-0000-000000000000',
|
||||
toUserUuid: '00000000-0000-0000-0000-000000000000',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(false)
|
||||
expect(sharedVaultRepository.save).toHaveBeenCalled()
|
||||
expect(sharedVaultUserRepository.save).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should fail if shared vault does not exist', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
sharedVaultRepository.findByUuid = jest.fn().mockResolvedValue(null)
|
||||
|
||||
const result = await useCase.execute({
|
||||
sharedVaultUid: '00000000-0000-0000-0000-000000000000',
|
||||
fromUserUuid: '00000000-0000-0000-0000-000000000000',
|
||||
toUserUuid: '00000000-0000-0000-0000-000000000000',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
expect(sharedVaultRepository.save).not.toHaveBeenCalled()
|
||||
expect(sharedVaultUserRepository.save).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should fail if shared vault does not belong to user', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
sharedVault.props.userUuid = Uuid.create('00000000-0000-0000-0000-000000000001').getValue()
|
||||
|
||||
const result = await useCase.execute({
|
||||
sharedVaultUid: '00000000-0000-0000-0000-000000000000',
|
||||
fromUserUuid: '00000000-0000-0000-0000-000000000000',
|
||||
toUserUuid: '00000000-0000-0000-0000-000000000000',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
expect(sharedVaultRepository.save).not.toHaveBeenCalled()
|
||||
expect(sharedVaultUserRepository.save).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should fail if new owner is not a member of shared vault', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
sharedVaultUserRepository.findByUserUuidAndSharedVaultUuid = jest.fn().mockResolvedValue(null)
|
||||
|
||||
const result = await useCase.execute({
|
||||
sharedVaultUid: '00000000-0000-0000-0000-000000000000',
|
||||
fromUserUuid: '00000000-0000-0000-0000-000000000000',
|
||||
toUserUuid: '00000000-0000-0000-0000-000000000000',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
expect(sharedVaultRepository.save).not.toHaveBeenCalled()
|
||||
expect(sharedVaultUserRepository.save).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should fail if shared vault uuid is invalid', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
sharedVaultUid: 'invalid',
|
||||
fromUserUuid: '00000000-0000-0000-0000-000000000000',
|
||||
toUserUuid: '00000000-0000-0000-0000-000000000000',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
expect(sharedVaultRepository.save).not.toHaveBeenCalled()
|
||||
expect(sharedVaultUserRepository.save).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should fail if from user uuid is invalid', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
sharedVaultUid: '00000000-0000-0000-0000-000000000000',
|
||||
fromUserUuid: 'invalid',
|
||||
toUserUuid: '00000000-0000-0000-0000-000000000000',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
expect(sharedVaultRepository.save).not.toHaveBeenCalled()
|
||||
expect(sharedVaultUserRepository.save).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should fail if to user uuid is invalid', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
sharedVaultUid: '00000000-0000-0000-0000-000000000000',
|
||||
fromUserUuid: '00000000-0000-0000-0000-000000000000',
|
||||
toUserUuid: 'invalid',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
expect(sharedVaultRepository.save).not.toHaveBeenCalled()
|
||||
expect(sharedVaultUserRepository.save).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
|
@ -0,0 +1,70 @@
|
|||
import { Result, SharedVaultUserPermission, Timestamps, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
|
||||
import { SharedVaultRepositoryInterface } from '../../../SharedVault/SharedVaultRepositoryInterface'
|
||||
import { TransferSharedVaultDTO } from './TransferSharedVaultDTO'
|
||||
import { SharedVaultUserRepositoryInterface } from '../../../SharedVault/User/SharedVaultUserRepositoryInterface'
|
||||
|
||||
export class TransferSharedVault implements UseCaseInterface<void> {
|
||||
constructor(
|
||||
private sharedVaultRepository: SharedVaultRepositoryInterface,
|
||||
private sharedVaultUserRepository: SharedVaultUserRepositoryInterface,
|
||||
private timer: TimerInterface,
|
||||
) {}
|
||||
|
||||
async execute(dto: TransferSharedVaultDTO): Promise<Result<void>> {
|
||||
const sharedVaultUuidOrError = Uuid.create(dto.sharedVaultUid)
|
||||
if (sharedVaultUuidOrError.isFailed()) {
|
||||
return Result.fail(sharedVaultUuidOrError.getError())
|
||||
}
|
||||
const sharedVaultUuid = sharedVaultUuidOrError.getValue()
|
||||
|
||||
const fromUserUuidOrError = Uuid.create(dto.fromUserUuid)
|
||||
if (fromUserUuidOrError.isFailed()) {
|
||||
return Result.fail(fromUserUuidOrError.getError())
|
||||
}
|
||||
const fromUserUuid = fromUserUuidOrError.getValue()
|
||||
|
||||
const toUserUuidOrError = Uuid.create(dto.toUserUuid)
|
||||
if (toUserUuidOrError.isFailed()) {
|
||||
return Result.fail(toUserUuidOrError.getError())
|
||||
}
|
||||
const toUserUuid = toUserUuidOrError.getValue()
|
||||
|
||||
const sharedVault = await this.sharedVaultRepository.findByUuid(sharedVaultUuid)
|
||||
if (!sharedVault) {
|
||||
return Result.fail('Shared vault not found')
|
||||
}
|
||||
|
||||
if (!sharedVault.props.userUuid.equals(fromUserUuid)) {
|
||||
return Result.fail('Shared vault does not belong to this user')
|
||||
}
|
||||
|
||||
const newOwner = await this.sharedVaultUserRepository.findByUserUuidAndSharedVaultUuid({
|
||||
userUuid: toUserUuid,
|
||||
sharedVaultUuid: sharedVaultUuid,
|
||||
})
|
||||
if (!newOwner) {
|
||||
return Result.fail('New owner is not a member of this shared vault')
|
||||
}
|
||||
|
||||
newOwner.props.isDesignatedSurvivor = false
|
||||
newOwner.props.permission = SharedVaultUserPermission.create(SharedVaultUserPermission.PERMISSIONS.Admin).getValue()
|
||||
newOwner.props.timestamps = Timestamps.create(
|
||||
newOwner.props.timestamps.createdAt,
|
||||
this.timer.getTimestampInMicroseconds(),
|
||||
).getValue()
|
||||
|
||||
await this.sharedVaultUserRepository.save(newOwner)
|
||||
|
||||
sharedVault.props.userUuid = toUserUuid
|
||||
sharedVault.props.timestamps = Timestamps.create(
|
||||
sharedVault.props.timestamps.createdAt,
|
||||
this.timer.getTimestampInMicroseconds(),
|
||||
).getValue()
|
||||
|
||||
await this.sharedVaultRepository.save(sharedVault)
|
||||
|
||||
return Result.ok()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
export interface TransferSharedVaultDTO {
|
||||
sharedVaultUid: string
|
||||
fromUserUuid: string
|
||||
toUserUuid: string
|
||||
}
|
Loading…
Reference in a new issue