Compare commits

..

8 Commits

35 changed files with 551 additions and 128 deletions

View File

@@ -24,7 +24,6 @@ jobs:
fail-fast: false
matrix:
secondary_db_enabled: [true, false]
transition_mode_enabled: [true, false]
runs-on: ubuntu-latest
services:
@@ -51,7 +50,6 @@ jobs:
DB_TYPE: mysql
CACHE_TYPE: redis
SECONDARY_DB_ENABLED: ${{ matrix.secondary_db_enabled }}
TRANSITION_MODE_ENABLED: ${{ matrix.transition_mode_enabled }}
- name: Wait for server to start
run: docker/is-available.sh http://localhost:3123 $(pwd)/logs
@@ -75,7 +73,6 @@ jobs:
db_type: [mysql, sqlite]
cache_type: [redis, memory]
secondary_db_enabled: [true, false]
transition_mode_enabled: [true, false]
runs-on: ubuntu-latest
@@ -145,7 +142,6 @@ jobs:
echo "REDIS_URL=redis://localhost:6379" >> packages/home-server/.env
echo "CACHE_TYPE=${{ matrix.cache_type }}" >> packages/home-server/.env
echo "SECONDARY_DB_ENABLED=${{ matrix.secondary_db_enabled }}" >> packages/home-server/.env
echo "TRANSITION_MODE_ENABLED=${{ matrix.transition_mode_enabled }}" >> packages/home-server/.env
echo "MONGO_HOST=localhost" >> packages/home-server/.env
echo "MONGO_PORT=27017" >> packages/home-server/.env
echo "MONGO_DATABASE=standardnotes" >> packages/home-server/.env

View File

@@ -24,7 +24,6 @@ services:
DB_TYPE: "${DB_TYPE}"
CACHE_TYPE: "${CACHE_TYPE}"
SECONDARY_DB_ENABLED: "${SECONDARY_DB_ENABLED}"
TRANSITION_MODE_ENABLED: "${TRANSITION_MODE_ENABLED}"
container_name: server-ci
ports:
- 3123:3000

View File

@@ -68,9 +68,6 @@ fi
if [ -z "$SECONDARY_DB_ENABLED" ]; then
export SECONDARY_DB_ENABLED=false
fi
if [ -z "$TRANSITION_MODE_ENABLED" ]; then
export TRANSITION_MODE_ENABLED=false
fi
export DB_MIGRATIONS_PATH="dist/migrations/*.js"
#########

View File

@@ -3,6 +3,16 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.146.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.145.0...@standardnotes/auth-server@1.146.0) (2023-09-22)
### Bug Fixes
* **auth:** register specs ([f9b1f40](https://github.com/standardnotes/server/commit/f9b1f40ddf2d733d106ea64b9a7c4b38c5ec43ce))
### Features
* remove transition mode from code ([5001496](https://github.com/standardnotes/server/commit/5001496c7bc1df9e20c2d88ebf52ed77f868110c))
# [1.145.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.144.0...@standardnotes/auth-server@1.145.0) (2023-09-22)
### Features

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/auth-server",
"version": "1.145.0",
"version": "1.146.0",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -595,9 +595,6 @@ export class ContainerConfigLoader {
container
.bind(TYPES.Auth_READONLY_USERS)
.toConstantValue(env.get('READONLY_USERS', true) ? env.get('READONLY_USERS', true).split(',') : [])
container
.bind(TYPES.Auth_TRANSITION_MODE_ENABLED)
.toConstantValue(env.get('TRANSITION_MODE_ENABLED', true) === 'true')
if (isConfiguredForInMemoryCache) {
container

View File

@@ -105,7 +105,6 @@ const TYPES = {
Auth_U2F_EXPECTED_ORIGIN: Symbol.for('Auth_U2F_EXPECTED_ORIGIN'),
Auth_U2F_REQUIRE_USER_VERIFICATION: Symbol.for('Auth_U2F_REQUIRE_USER_VERIFICATION'),
Auth_READONLY_USERS: Symbol.for('Auth_READONLY_USERS'),
Auth_TRANSITION_MODE_ENABLED: Symbol.for('Auth_TRANSITION_MODE_ENABLED'),
// use cases
Auth_AuthenticateUser: Symbol.for('Auth_AuthenticateUser'),
Auth_AuthenticateRequest: Symbol.for('Auth_AuthenticateRequest'),

View File

@@ -21,19 +21,9 @@ describe('Register', () => {
let user: User
let crypter: CrypterInterface
let timer: TimerInterface
let transitionModeEnabled = false
const createUseCase = () =>
new Register(
userRepository,
roleRepository,
authResponseFactory,
crypter,
false,
settingService,
timer,
transitionModeEnabled,
)
new Register(userRepository, roleRepository, authResponseFactory, crypter, false, settingService, timer)
beforeEach(() => {
userRepository = {} as jest.Mocked<UserRepositoryInterface>
@@ -94,45 +84,7 @@ describe('Register', () => {
expect(settingService.applyDefaultSettingsUponRegistration).toHaveBeenCalled()
})
it('should register a new user with default role', async () => {
const role = new Role()
role.name = 'role1'
roleRepository.findOneByName = jest.fn().mockReturnValue(role)
expect(
await createUseCase().execute({
email: 'test@test.te',
password: 'asdzxc',
updatedWithUserAgent: 'Mozilla',
apiVersion: '20200115',
ephemeralSession: false,
version: '004',
pwCost: 11,
pwSalt: 'qweqwe',
pwNonce: undefined,
}),
).toEqual({ success: true, authResponse: { foo: 'bar' } })
expect(userRepository.save).toHaveBeenCalledWith({
email: 'test@test.te',
encryptedPassword: expect.any(String),
encryptedServerKey: 'test',
serverEncryptionVersion: 1,
pwCost: 11,
pwNonce: undefined,
pwSalt: 'qweqwe',
updatedWithUserAgent: 'Mozilla',
uuid: expect.any(String),
version: '004',
createdAt: new Date(1),
updatedAt: new Date(1),
roles: Promise.resolve([role]),
})
})
it('should register a new user with default role and transition role', async () => {
transitionModeEnabled = true
const role = new Role()
role.name = RoleName.NAMES.CoreUser
@@ -249,7 +201,6 @@ describe('Register', () => {
true,
settingService,
timer,
transitionModeEnabled,
).execute({
email: 'test@test.te',
password: 'asdzxc',

View File

@@ -27,7 +27,6 @@ export class Register implements UseCaseInterface {
@inject(TYPES.Auth_DISABLE_USER_REGISTRATION) private disableUserRegistration: boolean,
@inject(TYPES.Auth_SettingService) private settingService: SettingServiceInterface,
@inject(TYPES.Auth_Timer) private timer: TimerInterface,
@inject(TYPES.Auth_TRANSITION_MODE_ENABLED) private transitionModeEnabled: boolean,
) {}
async execute(dto: RegisterDTO): Promise<RegisterResponse> {
@@ -78,11 +77,9 @@ export class Register implements UseCaseInterface {
if (defaultRole) {
roles.push(defaultRole)
}
if (this.transitionModeEnabled) {
const transitionRole = await this.roleRepository.findOneByName(RoleName.NAMES.TransitionUser)
if (transitionRole) {
roles.push(transitionRole)
}
const transitionRole = await this.roleRepository.findOneByName(RoleName.NAMES.TransitionUser)
if (transitionRole) {
roles.push(transitionRole)
}
user.roles = Promise.resolve(roles)

View File

@@ -16,5 +16,3 @@ MONGO_PORT=27017
MONGO_USERNAME=standardnotes
MONGO_PASSWORD=standardnotes
MONGO_DATABASE=standardnotes
TRANSITION_MODE_ENABLED=false

View File

@@ -3,6 +3,16 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.16.0](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.80...@standardnotes/home-server@1.16.0) (2023-09-22)
### Features
* remove transition mode from code ([5001496](https://github.com/standardnotes/server/commit/5001496c7bc1df9e20c2d88ebf52ed77f868110c))
## [1.15.80](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.79...@standardnotes/home-server@1.15.80) (2023-09-22)
**Note:** Version bump only for package @standardnotes/home-server
## [1.15.79](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.78...@standardnotes/home-server@1.15.79) (2023-09-22)
**Note:** Version bump only for package @standardnotes/home-server

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/home-server",
"version": "1.15.79",
"version": "1.16.0",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -3,6 +3,13 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.36.6](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.36.5...@standardnotes/revisions-server@1.36.6) (2023-09-22)
### Bug Fixes
* add more logs to transition process ([0562b0a](https://github.com/standardnotes/server/commit/0562b0a621eb878026fbdc0346b6170e815b64bf))
* remove excessive logs ([15ed1fd](https://github.com/standardnotes/server/commit/15ed1fd789aba306cbec6a23e88d5c1f837dabc0))
## [1.36.5](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.36.4...@standardnotes/revisions-server@1.36.5) (2023-09-22)
### Bug Fixes

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/revisions-server",
"version": "1.36.5",
"version": "1.36.6",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -38,8 +38,12 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
}
const revisionsToSkipInIntegrityCheck = migrationResult.getValue()
this.logger.info(`[${dto.userUuid}] Revisions migrated`)
await this.allowForSecondaryDatabaseToCatchUp()
this.logger.info(`[${dto.userUuid}] Checking integrity between primary and secondary database`)
const integrityCheckResult = await this.checkIntegrityBetweenPrimaryAndSecondaryDatabase(
userUuid,
revisionsToSkipInIntegrityCheck,
@@ -86,9 +90,6 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
if (revisionInSecondary !== null) {
if (revisionInSecondary.isIdenticalTo(revision)) {
this.logger.info(
`[${userUuid.value}] Revision ${revision.id.toString()} already exists in secondary database`,
)
continue
}
if (revisionInSecondary.props.dates.updatedAt > revision.props.dates.updatedAt) {

View File

@@ -3,6 +3,23 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.105.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.104.0...@standardnotes/syncing-server@1.105.0) (2023-09-22)
### Bug Fixes
* add more logs to transition process ([0562b0a](https://github.com/standardnotes/syncing-server-js/commit/0562b0a621eb878026fbdc0346b6170e815b64bf))
* remove excessive logs ([15ed1fd](https://github.com/standardnotes/syncing-server-js/commit/15ed1fd789aba306cbec6a23e88d5c1f837dabc0))
### Features
* **syncing-server:** transfer shared vault ownership to designated survivor upon account deletion ([#845](https://github.com/standardnotes/syncing-server-js/issues/845)) ([0a1080c](https://github.com/standardnotes/syncing-server-js/commit/0a1080ce2a0fb021309a960de2c40193acab46eb))
# [1.104.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.103.1...@standardnotes/syncing-server@1.104.0) (2023-09-22)
### Features
* **syncing-server:** add designated survivors in fetching shared vaults response ([#844](https://github.com/standardnotes/syncing-server-js/issues/844)) ([bcd95cd](https://github.com/standardnotes/syncing-server-js/commit/bcd95cdbe9054d4ca39d5dc0486b6a0c0b6f52da))
## [1.103.1](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.103.0...@standardnotes/syncing-server@1.103.1) (2023-09-22)
### Bug Fixes

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/syncing-server",
"version": "1.103.1",
"version": "1.105.0",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -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(
@@ -871,6 +851,7 @@ export class ContainerConfigLoader {
.bind<DesignateSurvivor>(TYPES.Sync_DesignateSurvivor)
.toConstantValue(
new DesignateSurvivor(
container.get<SharedVaultRepositoryInterface>(TYPES.Sync_SharedVaultRepository),
container.get<SharedVaultUserRepositoryInterface>(TYPES.Sync_SharedVaultUserRepository),
container.get<TimerInterface>(TYPES.Sync_Timer),
container.get<DomainEventFactoryInterface>(TYPES.Sync_DomainEventFactory),
@@ -886,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

View File

@@ -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'),

View File

@@ -8,4 +8,5 @@ export interface SharedVaultUserRepositoryInterface {
remove(sharedVault: SharedVaultUser): Promise<void>
removeBySharedVaultUuid(sharedVaultUuid: Uuid): Promise<void>
findByUserUuidAndSharedVaultUuid(dto: { userUuid: Uuid; sharedVaultUuid: Uuid }): Promise<SharedVaultUser | null>
findDesignatedSurvivorBySharedVaultUuid(sharedVaultUuid: Uuid): Promise<SharedVaultUser | null>
}

View File

@@ -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()
})
})
})

View File

@@ -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()) {

View File

@@ -5,8 +5,12 @@ import { DesignateSurvivor } from './DesignateSurvivor'
import { TimerInterface } from '@standardnotes/time'
import { DomainEventInterface, DomainEventPublisherInterface } from '@standardnotes/domain-events'
import { DomainEventFactoryInterface } from '../../../Event/DomainEventFactoryInterface'
import { SharedVault } from '../../../SharedVault/SharedVault'
import { SharedVaultRepositoryInterface } from '../../../SharedVault/SharedVaultRepositoryInterface'
describe('DesignateSurvivor', () => {
let sharedVault: SharedVault
let sharedVaultRepository: SharedVaultRepositoryInterface
let sharedVaultUserRepository: SharedVaultUserRepositoryInterface
let sharedVaultUser: SharedVaultUser
let sharedVaultOwner: SharedVaultUser
@@ -15,9 +19,25 @@ describe('DesignateSurvivor', () => {
let domainEventPublisher: DomainEventPublisherInterface
const createUseCase = () =>
new DesignateSurvivor(sharedVaultUserRepository, timer, domainEventFactory, domainEventPublisher)
new DesignateSurvivor(
sharedVaultRepository,
sharedVaultUserRepository,
timer,
domainEventFactory,
domainEventPublisher,
)
beforeEach(() => {
sharedVault = SharedVault.create({
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
timestamps: Timestamps.create(123, 123).getValue(),
fileUploadBytesUsed: 123,
}).getValue()
sharedVaultRepository = {} as jest.Mocked<SharedVaultRepositoryInterface>
sharedVaultRepository.findByUuid = jest.fn().mockReturnValue(sharedVault)
sharedVaultRepository.save = jest.fn()
timer = {} as jest.Mocked<TimerInterface>
timer.getTimestampInMicroseconds = jest.fn().mockReturnValue(123)
@@ -86,6 +106,20 @@ describe('DesignateSurvivor', () => {
expect(result.isFailed()).toBe(true)
})
it('should fail if shared vault is not found', async () => {
sharedVaultRepository.findByUuid = jest.fn().mockReturnValue(null)
const useCase = createUseCase()
const result = await useCase.execute({
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
userUuid: '00000000-0000-0000-0000-000000000000',
originatorUuid: '00000000-0000-0000-0000-000000000002',
})
expect(result.isFailed()).toBe(true)
})
it('should fail if shared vault user is not found', async () => {
sharedVaultUserRepository.findBySharedVaultUuid = jest.fn().mockReturnValue([sharedVaultOwner])

View File

@@ -12,9 +12,11 @@ import {
import { SharedVaultUserRepositoryInterface } from '../../../SharedVault/User/SharedVaultUserRepositoryInterface'
import { DesignateSurvivorDTO } from './DesignateSurvivorDTO'
import { DomainEventFactoryInterface } from '../../../Event/DomainEventFactoryInterface'
import { SharedVaultRepositoryInterface } from '../../../SharedVault/SharedVaultRepositoryInterface'
export class DesignateSurvivor implements UseCaseInterface<void> {
constructor(
private sharedVaultRepository: SharedVaultRepositoryInterface,
private sharedVaultUserRepository: SharedVaultUserRepositoryInterface,
private timer: TimerInterface,
private domainEventFactory: DomainEventFactoryInterface,
@@ -40,6 +42,11 @@ export class DesignateSurvivor implements UseCaseInterface<void> {
}
const originatorUuid = originatorUuidOrError.getValue()
const sharedVault = await this.sharedVaultRepository.findByUuid(sharedVaultUuid)
if (!sharedVault) {
return Result.fail('Shared vault not found')
}
const sharedVaultUsers = await this.sharedVaultUserRepository.findBySharedVaultUuid(sharedVaultUuid)
let sharedVaultExistingSurvivor: SharedVaultUser | undefined
let toBeDesignatedAsASurvivor: SharedVaultUser | undefined
@@ -92,6 +99,13 @@ export class DesignateSurvivor implements UseCaseInterface<void> {
}),
)
sharedVault.props.timestamps = Timestamps.create(
sharedVault.props.timestamps.createdAt,
this.timer.getTimestampInMicroseconds(),
).getValue()
await this.sharedVaultRepository.save(sharedVault)
return Result.ok()
}
}

View File

@@ -40,7 +40,7 @@ describe('GetSharedVaults', () => {
userUuid: '00000000-0000-0000-0000-000000000000',
})
expect(result.getValue()).toEqual([sharedVault])
expect(result.getValue().sharedVaults).toEqual([sharedVault])
})
it('returns empty array if no shared vaults found', async () => {
@@ -52,7 +52,7 @@ describe('GetSharedVaults', () => {
userUuid: '00000000-0000-0000-0000-000000000000',
})
expect(result.getValue()).toEqual([])
expect(result.getValue().sharedVaults).toEqual([])
})
it('returns error if user uuid is invalid', async () => {
@@ -64,4 +64,17 @@ describe('GetSharedVaults', () => {
expect(result.isFailed()).toBeTruthy()
})
it('should fetch designated survivors if includeDesignatedSurvivors is true', async () => {
sharedVaultUserRepository.findDesignatedSurvivorBySharedVaultUuid = jest.fn().mockResolvedValue(sharedVaultUser)
const useCase = createUseCase()
const result = await useCase.execute({
userUuid: '00000000-0000-0000-0000-000000000000',
includeDesignatedSurvivors: true,
})
expect(result.getValue().designatedSurvivors).toEqual([sharedVaultUser])
})
})

View File

@@ -1,17 +1,28 @@
import { Result, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
import { Result, SharedVaultUser, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
import { SharedVault } from '../../../SharedVault/SharedVault'
import { GetSharedVaultsDTO } from './GetSharedVaultsDTO'
import { SharedVaultUserRepositoryInterface } from '../../../SharedVault/User/SharedVaultUserRepositoryInterface'
import { SharedVaultRepositoryInterface } from '../../../SharedVault/SharedVaultRepositoryInterface'
export class GetSharedVaults implements UseCaseInterface<SharedVault[]> {
export class GetSharedVaults
implements
UseCaseInterface<{
sharedVaults: SharedVault[]
designatedSurvivors: SharedVaultUser[]
}>
{
constructor(
private sharedVaultUserRepository: SharedVaultUserRepositoryInterface,
private sharedVaultRepository: SharedVaultRepositoryInterface,
) {}
async execute(dto: GetSharedVaultsDTO): Promise<Result<SharedVault[]>> {
async execute(dto: GetSharedVaultsDTO): Promise<
Result<{
sharedVaults: SharedVault[]
designatedSurvivors: SharedVaultUser[]
}>
> {
const userUuidOrError = Uuid.create(dto.userUuid)
if (userUuidOrError.isFailed()) {
return Result.fail(userUuidOrError.getError())
@@ -25,11 +36,29 @@ export class GetSharedVaults implements UseCaseInterface<SharedVault[]> {
)
if (sharedVaultUuids.length === 0) {
return Result.ok([])
return Result.ok({
sharedVaults: [],
designatedSurvivors: [],
})
}
const sharedVaults = await this.sharedVaultRepository.findByUuids(sharedVaultUuids, dto.lastSyncTime)
return Result.ok(sharedVaults)
const designatedSurvivors = []
if (dto.includeDesignatedSurvivors) {
for (const sharedVault of sharedVaults) {
const designatedSurvivor = await this.sharedVaultUserRepository.findDesignatedSurvivorBySharedVaultUuid(
sharedVault.uuid,
)
if (designatedSurvivor) {
designatedSurvivors.push(designatedSurvivor)
}
}
}
return Result.ok({
sharedVaults,
designatedSurvivors,
})
}
}

View File

@@ -1,4 +1,5 @@
export interface GetSharedVaultsDTO {
userUuid: string
includeDesignatedSurvivors?: boolean
lastSyncTime?: number
}

View File

@@ -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()
})
})

View File

@@ -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()
}
}

View File

@@ -0,0 +1,5 @@
export interface TransferSharedVaultDTO {
sharedVaultUid: string
fromUserUuid: string
toUserUuid: string
}

View File

@@ -136,7 +136,7 @@ describe('SyncItems', () => {
itemRepositoryResolver.resolve = jest.fn().mockReturnValue(itemRepository)
getSharedVaultsUseCase = {} as jest.Mocked<GetSharedVaults>
getSharedVaultsUseCase.execute = jest.fn().mockReturnValue(Result.ok([]))
getSharedVaultsUseCase.execute = jest.fn().mockReturnValue(Result.ok({ sharedVaults: [], designatedSurivors: [] }))
getSharedVaultInvitesSentToUserUseCase = {} as jest.Mocked<GetSharedVaultInvitesSentToUser>
getSharedVaultInvitesSentToUserUseCase.execute = jest.fn().mockReturnValue(Result.ok([]))

View File

@@ -73,12 +73,13 @@ export class SyncItems implements UseCaseInterface<SyncItemsResponse> {
const sharedVaultsOrError = await this.getSharedVaultsUseCase.execute({
userUuid: dto.userUuid,
includeDesignatedSurvivors: false,
lastSyncTime: getItemsResult.lastSyncTime ?? undefined,
})
if (sharedVaultsOrError.isFailed()) {
return Result.fail(sharedVaultsOrError.getError())
}
const sharedVaults = sharedVaultsOrError.getValue()
const sharedVaultsResult = sharedVaultsOrError.getValue()
const sharedVaultInvitesOrError = await this.getSharedVaultInvitesSentToUserUseCase.execute({
userUuid: dto.userUuid,
@@ -114,7 +115,7 @@ export class SyncItems implements UseCaseInterface<SyncItemsResponse> {
conflicts: saveItemsResult.conflicts,
cursorToken: getItemsResult.cursorToken,
sharedVaultInvites,
sharedVaults,
sharedVaults: sharedVaultsResult.sharedVaults,
messages,
notifications,
}

View File

@@ -39,8 +39,12 @@ export class TransitionItemsFromPrimaryToSecondaryDatabaseForUser implements Use
}
const itemsToSkipInIntegrityCheck = migrationResult.getValue()
this.logger.info(`[${dto.userUuid}] Items migrated`)
await this.allowForSecondaryDatabaseToCatchUp()
this.logger.info(`[${dto.userUuid}] Checking integrity between primary and secondary database`)
const integrityCheckResult = await this.checkIntegrityBetweenPrimaryAndSecondaryDatabase(
userUuid,
itemsToSkipInIntegrityCheck,
@@ -95,7 +99,6 @@ export class TransitionItemsFromPrimaryToSecondaryDatabaseForUser implements Use
if (itemInSecondary !== null) {
if (itemInSecondary.isIdenticalTo(item)) {
this.logger.info(`[${userUuid.value}] Item ${item.uuid.value} already exists in secondary database`)
continue
}
if (itemInSecondary.props.timestamps.updatedAt > item.props.timestamps.updatedAt) {

View File

@@ -36,23 +36,30 @@ export class BaseSharedVaultsController extends BaseHttpController {
}
async getSharedVaults(_request: Request, response: Response): Promise<results.JsonResult> {
const result = await this.getSharedVaultsUseCase.execute({
const resultOrError = await this.getSharedVaultsUseCase.execute({
userUuid: response.locals.user.uuid,
includeDesignatedSurvivors: true,
})
if (result.isFailed()) {
if (resultOrError.isFailed()) {
return this.json(
{
error: {
message: result.getError(),
message: resultOrError.getError(),
},
},
HttpStatusCode.BadRequest,
)
}
const result = resultOrError.getValue()
return this.json({
sharedVaults: result.getValue().map((sharedVault) => this.sharedVaultHttpMapper.toProjection(sharedVault)),
sharedVaults: result.sharedVaults.map((sharedVault) => this.sharedVaultHttpMapper.toProjection(sharedVault)),
designatedSurvivors: result.designatedSurvivors.map((designatedSurvivor) => ({
sharedVaultUuid: designatedSurvivor.props.sharedVaultUuid.value,
userUuid: designatedSurvivor.props.userUuid.value,
})),
})
}

View File

@@ -10,6 +10,24 @@ export class TypeORMSharedVaultUserRepository implements SharedVaultUserReposito
private mapper: MapperInterface<SharedVaultUser, TypeORMSharedVaultUser>,
) {}
async findDesignatedSurvivorBySharedVaultUuid(sharedVaultUuid: Uuid): Promise<SharedVaultUser | null> {
const persistence = await this.ormRepository
.createQueryBuilder('shared_vault_user')
.where('shared_vault_user.shared_vault_uuid = :sharedVaultUuid', {
sharedVaultUuid: sharedVaultUuid.value,
})
.andWhere('shared_vault_user.is_designated_survivor = :isDesignatedSurvivor', {
isDesignatedSurvivor: true,
})
.getOne()
if (persistence === null) {
return null
}
return this.mapper.toDomain(persistence)
}
async removeBySharedVaultUuid(sharedVaultUuid: Uuid): Promise<void> {
await this.ormRepository
.createQueryBuilder('shared_vault_user')