Compare commits

..

4 Commits

22 changed files with 679 additions and 80 deletions
+8
View File
@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.13.7](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.13.6...@standardnotes/home-server@1.13.7) (2023-07-24)
**Note:** Version bump only for package @standardnotes/home-server
## [1.13.6](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.13.5...@standardnotes/home-server@1.13.6) (2023-07-24)
**Note:** Version bump only for package @standardnotes/home-server
## [1.13.5](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.13.4...@standardnotes/home-server@1.13.5) (2023-07-21)
**Note:** Version bump only for package @standardnotes/home-server
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/home-server",
"version": "1.13.5",
"version": "1.13.7",
"engines": {
"node": ">=18.0.0 <21.0.0"
},
+12
View File
@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.69.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.68.4...@standardnotes/syncing-server@1.69.0) (2023-07-24)
### Features
* **syncing-server:** determin shared vault operation type ([#669](https://github.com/standardnotes/syncing-server-js/issues/669)) ([71721ab](https://github.com/standardnotes/syncing-server-js/commit/71721ab1982b65feb4c84b44b267a249b573c537))
## [1.68.4](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.68.3...@standardnotes/syncing-server@1.68.4) (2023-07-24)
### Bug Fixes
* **syncing-server:** force remove shared vault owner when removing shared vault ([f77e29d](https://github.com/standardnotes/syncing-server-js/commit/f77e29d3c9c9a28be3c5624d2c9bf0ffd6348377))
## [1.68.3](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.68.2...@standardnotes/syncing-server@1.68.3) (2023-07-21)
**Note:** Version bump only for package @standardnotes/syncing-server
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/syncing-server",
"version": "1.68.3",
"version": "1.69.0",
"engines": {
"node": ">=18.0.0 <21.0.0"
},
@@ -1,6 +1,7 @@
import { ContentType, Dates, Timestamps, UniqueEntityId, Uuid } from '@standardnotes/domain-core'
import { Item } from './Item'
import { SharedVaultAssociation } from '../SharedVault/SharedVaultAssociation'
describe('Item', () => {
it('should create an aggregate', () => {
@@ -44,4 +45,56 @@ describe('Item', () => {
expect(entityOrError.isFailed()).toBeFalsy()
expect(() => entityOrError.getValue().uuid).toThrow()
})
it('should tell if an item is associated with a shared vault', () => {
const entityOrError = Item.create({
duplicateOf: null,
itemsKeyId: 'items-key-id',
content: 'content',
contentType: ContentType.create(ContentType.TYPES.Note).getValue(),
encItemKey: 'enc-item-key',
authHash: 'auth-hash',
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
deleted: false,
updatedWithSession: null,
dates: Dates.create(new Date(123), new Date(123)).getValue(),
timestamps: Timestamps.create(123, 123).getValue(),
sharedVaultAssociation: SharedVaultAssociation.create({
itemUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
sharedVaultUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
lastEditedBy: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
timestamps: Timestamps.create(123, 123).getValue(),
}).getValue(),
})
expect(entityOrError.isFailed()).toBeFalsy()
expect(
entityOrError
.getValue()
.isAssociatedWithSharedVault(Uuid.create('00000000-0000-0000-0000-000000000000').getValue()),
).toBeTruthy()
})
it('should tell that an item is not associated with a shared vault', () => {
const entityOrError = Item.create({
duplicateOf: null,
itemsKeyId: 'items-key-id',
content: 'content',
contentType: ContentType.create(ContentType.TYPES.Note).getValue(),
encItemKey: 'enc-item-key',
authHash: 'auth-hash',
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
deleted: false,
updatedWithSession: null,
dates: Dates.create(new Date(123), new Date(123)).getValue(),
timestamps: Timestamps.create(123, 123).getValue(),
})
expect(entityOrError.isFailed()).toBeFalsy()
expect(
entityOrError
.getValue()
.isAssociatedWithSharedVault(Uuid.create('00000000-0000-0000-0000-000000000000').getValue()),
).toBeFalsy()
})
})
@@ -12,6 +12,31 @@ export class Item extends Aggregate<ItemProps> {
return uuidOrError.getValue()
}
get sharedVaultUuid(): Uuid | null {
if (!this.props.sharedVaultAssociation) {
return null
}
return this.props.sharedVaultAssociation.props.sharedVaultUuid
}
isAssociatedWithSharedVault(sharedVaultUuid: Uuid): boolean {
const associatedSharedVaultUuid = this.sharedVaultUuid
if (!associatedSharedVaultUuid) {
return false
}
return associatedSharedVaultUuid.equals(sharedVaultUuid)
}
isAssociatedWithKeySystem(keySystemIdentifier: string): boolean {
if (!this.props.keySystemAssociation) {
return false
}
return this.props.keySystemAssociation.props.keySystemIdentifier === keySystemIdentifier
}
private constructor(props: ItemProps, id?: UniqueEntityId) {
super(props, id)
}
@@ -0,0 +1,38 @@
import { ContentType } from '@standardnotes/domain-core'
import { ItemHash } from './ItemHash'
describe('ItemHash', () => {
it('should create a value object', () => {
const valueOrError = ItemHash.create({
uuid: '00000000-0000-0000-0000-000000000000',
content_type: ContentType.TYPES.Note,
user_uuid: '00000000-0000-0000-0000-000000000000',
content: 'foobar',
created_at: '2020-01-01T00:00:00.000Z',
updated_at: '2020-01-01T00:00:00.000Z',
created_at_timestamp: 123,
updated_at_timestamp: 123,
key_system_identifier: null,
shared_vault_uuid: null,
})
expect(valueOrError.isFailed()).toBeFalsy()
})
it('should return error if shared vault uuid is not valid', () => {
const valueOrError = ItemHash.create({
uuid: '00000000-0000-0000-0000-000000000000',
content_type: ContentType.TYPES.Note,
user_uuid: '00000000-0000-0000-0000-000000000000',
content: 'foobar',
created_at: '2020-01-01T00:00:00.000Z',
updated_at: '2020-01-01T00:00:00.000Z',
created_at_timestamp: 123,
updated_at_timestamp: 123,
key_system_identifier: null,
shared_vault_uuid: 'invalid',
})
expect(valueOrError.isFailed()).toBeTruthy()
})
})
@@ -1,4 +1,4 @@
import { Result, ValueObject } from '@standardnotes/domain-core'
import { Result, Uuid, ValueObject } from '@standardnotes/domain-core'
import { ItemHashProps } from './ItemHashProps'
@@ -8,6 +8,13 @@ export class ItemHash extends ValueObject<ItemHashProps> {
}
static create(props: ItemHashProps): Result<ItemHash> {
if (props.shared_vault_uuid) {
const sharedVaultUuidOrError = Uuid.create(props.shared_vault_uuid)
if (sharedVaultUuidOrError.isFailed()) {
return Result.fail<ItemHash>(sharedVaultUuidOrError.getError())
}
}
return Result.ok<ItemHash>(new ItemHash(props))
}
@@ -15,6 +22,14 @@ export class ItemHash extends ValueObject<ItemHashProps> {
return this.props.shared_vault_uuid !== null
}
get sharedVaultUuid(): Uuid | null {
if (!this.representsASharedVaultItem()) {
return null
}
return Uuid.create(this.props.shared_vault_uuid as string).getValue()
}
hasDedicatedKeySystemAssociation(): boolean {
return this.props.key_system_identifier !== null
}
@@ -0,0 +1,95 @@
import { ContentType } from '@standardnotes/domain-core'
import { ItemHash } from '../Item/ItemHash'
import { SharedVaultOperationOnItem } from './SharedVaultOperationOnItem'
describe('SharedVaultOperationOnItem', () => {
let itemHash: ItemHash
beforeEach(() => {
itemHash = ItemHash.create({
uuid: '2-3-4',
content_type: ContentType.TYPES.Note,
user_uuid: '00000000-0000-0000-0000-000000000000',
content: 'foobar',
created_at: '2020-01-01T00:00:00.000Z',
updated_at: '2020-01-01T00:00:00.000Z',
created_at_timestamp: 123,
updated_at_timestamp: 123,
key_system_identifier: null,
shared_vault_uuid: null,
}).getValue()
})
it('should create a value object', () => {
const valueOrError = SharedVaultOperationOnItem.create({
incomingItemHash: itemHash,
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
targetSharedVaultUuid: '00000000-0000-0000-0000-000000000000',
type: SharedVaultOperationOnItem.TYPES.AddToSharedVault,
userUuid: '00000000-0000-0000-0000-000000000000',
})
expect(valueOrError.isFailed()).toBeFalsy()
})
it('should return error if user uuid is not valid', () => {
const valueOrError = SharedVaultOperationOnItem.create({
incomingItemHash: itemHash,
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
targetSharedVaultUuid: '00000000-0000-0000-0000-000000000000',
type: SharedVaultOperationOnItem.TYPES.AddToSharedVault,
userUuid: 'invalid',
})
expect(valueOrError.isFailed()).toBeTruthy()
})
it('should return error if shared vault uuid is not valid', () => {
const valueOrError = SharedVaultOperationOnItem.create({
incomingItemHash: itemHash,
sharedVaultUuid: 'invalid',
targetSharedVaultUuid: '00000000-0000-0000-0000-000000000000',
type: SharedVaultOperationOnItem.TYPES.AddToSharedVault,
userUuid: '00000000-0000-0000-0000-000000000000',
})
expect(valueOrError.isFailed()).toBeTruthy()
})
it('should return error if shared vault operation type is invalid', () => {
const valueOrError = SharedVaultOperationOnItem.create({
incomingItemHash: itemHash,
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
targetSharedVaultUuid: '00000000-0000-0000-0000-000000000000',
type: 'invalid',
userUuid: '00000000-0000-0000-0000-000000000000',
})
expect(valueOrError.isFailed()).toBeTruthy()
})
it('should return error if target shared vault uuid is not valid', () => {
const valueOrError = SharedVaultOperationOnItem.create({
incomingItemHash: itemHash,
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
targetSharedVaultUuid: 'invalid',
type: SharedVaultOperationOnItem.TYPES.AddToSharedVault,
userUuid: '00000000-0000-0000-0000-000000000000',
})
expect(valueOrError.isFailed()).toBeTruthy()
})
it('should return error if operation type is move to other shared vault and target shared vault uuid is not provided', () => {
const valueOrError = SharedVaultOperationOnItem.create({
incomingItemHash: itemHash,
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
targetSharedVaultUuid: undefined,
type: SharedVaultOperationOnItem.TYPES.MoveToOtherSharedVault,
userUuid: '00000000-0000-0000-0000-000000000000',
})
expect(valueOrError.isFailed()).toBeTruthy()
})
})
@@ -0,0 +1,47 @@
import { ValueObject, Result, Uuid } from '@standardnotes/domain-core'
import { SharedVaultOperationOnItemProps } from './SharedVaultOperationOnItemProps'
export class SharedVaultOperationOnItem extends ValueObject<SharedVaultOperationOnItemProps> {
static readonly TYPES = {
AddToSharedVault: 'add-to-shared-vault',
RemoveFromSharedVault: 'remove-from-shared-vault',
MoveToOtherSharedVault: 'move-to-other-shared-vault',
SaveToSharedVault: 'save-to-shared-vault',
CreateToSharedVault: 'create-to-shared-vault',
}
private constructor(props: SharedVaultOperationOnItemProps) {
super(props)
}
static create(props: SharedVaultOperationOnItemProps): Result<SharedVaultOperationOnItem> {
const userUuidOrError = Uuid.create(props.userUuid)
if (userUuidOrError.isFailed()) {
return Result.fail<SharedVaultOperationOnItem>(userUuidOrError.getError())
}
const isValidType = Object.values(this.TYPES).includes(props.type)
if (!isValidType) {
return Result.fail<SharedVaultOperationOnItem>(`Invalid shared vault operation type: ${props.type}`)
}
const sharedVaultUuidOrError = Uuid.create(props.sharedVaultUuid)
if (sharedVaultUuidOrError.isFailed()) {
return Result.fail<SharedVaultOperationOnItem>(sharedVaultUuidOrError.getError())
}
if (props.targetSharedVaultUuid) {
const targetSharedVaultUuidOrError = Uuid.create(props.targetSharedVaultUuid)
if (targetSharedVaultUuidOrError.isFailed()) {
return Result.fail<SharedVaultOperationOnItem>(targetSharedVaultUuidOrError.getError())
}
}
if (props.type === this.TYPES.MoveToOtherSharedVault && !props.targetSharedVaultUuid) {
return Result.fail<SharedVaultOperationOnItem>('Missing target shared vault uuid')
}
return Result.ok<SharedVaultOperationOnItem>(new SharedVaultOperationOnItem(props))
}
}
@@ -0,0 +1,11 @@
import { Item } from '../Item/Item'
import { ItemHash } from '../Item/ItemHash'
export interface SharedVaultOperationOnItemProps {
incomingItemHash: ItemHash
userUuid: string
type: string
sharedVaultUuid: string
targetSharedVaultUuid?: string
existingItem?: Item
}
@@ -42,6 +42,7 @@ export class DeleteSharedVault implements UseCaseInterface<void> {
originatorUuid: originatorUuid.value,
sharedVaultUuid: sharedVaultUuid.value,
userUuid: sharedVaultUser.props.userUuid.value,
forceRemoveOwner: true,
})
if (result.isFailed()) {
@@ -0,0 +1,223 @@
import { ContentType, Uuid, Dates, Timestamps, UniqueEntityId, Result } from '@standardnotes/domain-core'
import { Item } from '../../../Item/Item'
import { ItemHash } from '../../../Item/ItemHash'
import { DetermineSharedVaultOperationOnItem } from './DetermineSharedVaultOperationOnItem'
import { SharedVaultOperationOnItem } from '../../../SharedVault/SharedVaultOperationOnItem'
import { SharedVaultAssociation } from '../../../SharedVault/SharedVaultAssociation'
describe('DetermineSharedVaultOperationOnItem', () => {
let itemHash: ItemHash
let existingItem: Item
const createUseCase = () => new DetermineSharedVaultOperationOnItem()
beforeEach(() => {
itemHash = ItemHash.create({
uuid: '2-3-4',
content_type: ContentType.TYPES.Note,
user_uuid: '00000000-0000-0000-0000-000000000000',
content: 'foobar',
created_at: '2020-01-01T00:00:00.000Z',
updated_at: '2020-01-01T00:00:00.000Z',
created_at_timestamp: 123,
updated_at_timestamp: 123,
key_system_identifier: null,
shared_vault_uuid: null,
}).getValue()
existingItem = Item.create(
{
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
updatedWithSession: null,
content: 'foobar',
contentType: ContentType.create(ContentType.TYPES.Note).getValue(),
encItemKey: null,
authHash: null,
itemsKeyId: null,
duplicateOf: null,
deleted: false,
dates: Dates.create(new Date(1616164633241311), new Date(1616164633241311)).getValue(),
timestamps: Timestamps.create(1616164633241311, 1616164633241311).getValue(),
},
new UniqueEntityId('00000000-0000-0000-0000-000000000000'),
).getValue()
})
it('should return an error if user uuid is invalid', async () => {
const useCase = createUseCase()
const result = await useCase.execute({
userUuid: 'invalid',
existingItem,
itemHash,
})
expect(result.isFailed()).toBeTruthy()
expect(result.getError()).toEqual('Given value is not a valid uuid: invalid')
})
it('should return an operation representing moving to another shared vault', async () => {
existingItem = Item.create({
...existingItem.props,
sharedVaultAssociation: SharedVaultAssociation.create({
itemUuid: existingItem.uuid,
lastEditedBy: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
sharedVaultUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
timestamps: Timestamps.create(123, 123).getValue(),
}).getValue(),
}).getValue()
const useCase = createUseCase()
const result = await useCase.execute({
userUuid: '00000000-0000-0000-0000-000000000000',
existingItem,
itemHash: ItemHash.create({
...itemHash.props,
shared_vault_uuid: '00000000-0000-0000-0000-000000000001',
}).getValue(),
})
expect(result.isFailed()).toBeFalsy()
expect(result.getValue().props.type).toEqual(SharedVaultOperationOnItem.TYPES.MoveToOtherSharedVault)
expect(result.getValue().props.sharedVaultUuid).toEqual('00000000-0000-0000-0000-000000000000')
expect(result.getValue().props.targetSharedVaultUuid).toEqual('00000000-0000-0000-0000-000000000001')
})
it('should return an operation representing removing from shared vault', async () => {
existingItem = Item.create({
...existingItem.props,
sharedVaultAssociation: SharedVaultAssociation.create({
itemUuid: existingItem.uuid,
lastEditedBy: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
sharedVaultUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
timestamps: Timestamps.create(123, 123).getValue(),
}).getValue(),
}).getValue()
const useCase = createUseCase()
const result = await useCase.execute({
userUuid: '00000000-0000-0000-0000-000000000000',
existingItem,
itemHash: ItemHash.create({
...itemHash.props,
shared_vault_uuid: null,
}).getValue(),
})
expect(result.isFailed()).toBeFalsy()
expect(result.getValue().props.type).toEqual(SharedVaultOperationOnItem.TYPES.RemoveFromSharedVault)
expect(result.getValue().props.sharedVaultUuid).toEqual('00000000-0000-0000-0000-000000000000')
})
it('should return an operation representing adding to shared vault', async () => {
const useCase = createUseCase()
const result = await useCase.execute({
userUuid: '00000000-0000-0000-0000-000000000000',
existingItem,
itemHash: ItemHash.create({
...itemHash.props,
shared_vault_uuid: '00000000-0000-0000-0000-000000000001',
}).getValue(),
})
expect(result.isFailed()).toBeFalsy()
expect(result.getValue().props.type).toEqual(SharedVaultOperationOnItem.TYPES.AddToSharedVault)
expect(result.getValue().props.sharedVaultUuid).toEqual('00000000-0000-0000-0000-000000000001')
})
it('should return an operation representing saving to shared vault', async () => {
existingItem = Item.create({
...existingItem.props,
sharedVaultAssociation: SharedVaultAssociation.create({
itemUuid: existingItem.uuid,
lastEditedBy: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
sharedVaultUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
timestamps: Timestamps.create(123, 123).getValue(),
}).getValue(),
}).getValue()
const useCase = createUseCase()
const result = await useCase.execute({
userUuid: '00000000-0000-0000-0000-000000000000',
existingItem,
itemHash: ItemHash.create({
...itemHash.props,
shared_vault_uuid: '00000000-0000-0000-0000-000000000000',
}).getValue(),
})
expect(result.isFailed()).toBeFalsy()
expect(result.getValue().props.type).toEqual(SharedVaultOperationOnItem.TYPES.SaveToSharedVault)
expect(result.getValue().props.sharedVaultUuid).toEqual('00000000-0000-0000-0000-000000000000')
})
it('should return an operation representing creating to shared vault', async () => {
const useCase = createUseCase()
const result = await useCase.execute({
userUuid: '00000000-0000-0000-0000-000000000000',
existingItem: null,
itemHash: ItemHash.create({
...itemHash.props,
shared_vault_uuid: '00000000-0000-0000-0000-000000000001',
}).getValue(),
})
expect(result.isFailed()).toBeFalsy()
expect(result.getValue().props.type).toEqual(SharedVaultOperationOnItem.TYPES.CreateToSharedVault)
expect(result.getValue().props.sharedVaultUuid).toEqual('00000000-0000-0000-0000-000000000001')
})
it('should return an error if both existing and incoming item hash do not have shared vault uuid', async () => {
const useCase = createUseCase()
const result = await useCase.execute({
userUuid: '00000000-0000-0000-0000-000000000000',
existingItem: null,
itemHash: ItemHash.create({
...itemHash.props,
shared_vault_uuid: null,
}).getValue(),
})
expect(result.isFailed()).toBeTruthy()
expect(result.getError()).toEqual('Invalid save operation')
})
it('should return error if operation could not be determined based on input values', async () => {
const useCase = createUseCase()
const result = await useCase.execute({
userUuid: '00000000-0000-0000-0000-000000000000',
existingItem: null,
itemHash,
})
expect(result.isFailed()).toBeTruthy()
expect(result.getError()).toEqual('Invalid save operation')
})
it('should return error if shared vault operation on item could not be created', async () => {
const mock = jest.spyOn(SharedVaultOperationOnItem, 'create')
mock.mockImplementationOnce(() => Result.fail('error'))
const useCase = createUseCase()
const result = await useCase.execute({
userUuid: '00000000-0000-0000-0000-000000000000',
existingItem: null,
itemHash: ItemHash.create({
...itemHash.props,
shared_vault_uuid: '00000000-0000-0000-0000-000000000001',
}).getValue(),
})
expect(result.isFailed()).toBeTruthy()
mock.mockRestore()
})
})
@@ -0,0 +1,87 @@
import { Result, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
import { SharedVaultOperationOnItem } from '../../../SharedVault/SharedVaultOperationOnItem'
import { DetermineSharedVaultOperationOnItemDTO } from './DetermineSharedVaultOperationOnItemDTO'
import { Item } from '../../../Item/Item'
export class DetermineSharedVaultOperationOnItem implements UseCaseInterface<SharedVaultOperationOnItem> {
async execute(dto: DetermineSharedVaultOperationOnItemDTO): Promise<Result<SharedVaultOperationOnItem>> {
const userUuidOrError = Uuid.create(dto.userUuid)
if (userUuidOrError.isFailed()) {
return Result.fail(userUuidOrError.getError())
}
const userUuid = userUuidOrError.getValue()
let existingItemSharedVaultUuid = null
if (dto.existingItem) {
existingItemSharedVaultUuid = dto.existingItem.sharedVaultUuid
}
const targetItemSharedVaultUuid = dto.itemHash.sharedVaultUuid
if (!existingItemSharedVaultUuid && !targetItemSharedVaultUuid) {
return Result.fail('Invalid save operation')
}
const isMovingToOtherSharedVault =
dto.existingItem &&
existingItemSharedVaultUuid &&
targetItemSharedVaultUuid &&
!existingItemSharedVaultUuid.equals(targetItemSharedVaultUuid)
const isRemovingFromSharedVault = dto.existingItem && existingItemSharedVaultUuid && !targetItemSharedVaultUuid
const isAddingToSharedVault = dto.existingItem && !existingItemSharedVaultUuid && targetItemSharedVaultUuid
const isSavingToSharedVault =
dto.existingItem &&
existingItemSharedVaultUuid &&
targetItemSharedVaultUuid &&
existingItemSharedVaultUuid.equals(targetItemSharedVaultUuid)
let operationOrError: Result<SharedVaultOperationOnItem>
if (isMovingToOtherSharedVault) {
operationOrError = SharedVaultOperationOnItem.create({
existingItem: dto.existingItem as Item,
sharedVaultUuid: (existingItemSharedVaultUuid as Uuid).value,
targetSharedVaultUuid: targetItemSharedVaultUuid.value,
incomingItemHash: dto.itemHash,
userUuid: userUuid.value,
type: SharedVaultOperationOnItem.TYPES.MoveToOtherSharedVault,
})
} else if (isRemovingFromSharedVault) {
operationOrError = SharedVaultOperationOnItem.create({
existingItem: dto.existingItem as Item,
sharedVaultUuid: (existingItemSharedVaultUuid as Uuid).value,
incomingItemHash: dto.itemHash,
userUuid: userUuid.value,
type: SharedVaultOperationOnItem.TYPES.RemoveFromSharedVault,
})
} else if (isAddingToSharedVault) {
operationOrError = SharedVaultOperationOnItem.create({
existingItem: dto.existingItem as Item,
sharedVaultUuid: targetItemSharedVaultUuid.value,
incomingItemHash: dto.itemHash,
userUuid: userUuid.value,
type: SharedVaultOperationOnItem.TYPES.AddToSharedVault,
})
} else if (isSavingToSharedVault) {
operationOrError = SharedVaultOperationOnItem.create({
existingItem: dto.existingItem as Item,
sharedVaultUuid: (existingItemSharedVaultUuid as Uuid).value,
incomingItemHash: dto.itemHash,
userUuid: userUuid.value,
type: SharedVaultOperationOnItem.TYPES.SaveToSharedVault,
})
} else {
operationOrError = SharedVaultOperationOnItem.create({
sharedVaultUuid: (targetItemSharedVaultUuid as Uuid).value,
incomingItemHash: dto.itemHash,
userUuid: userUuid.value,
type: SharedVaultOperationOnItem.TYPES.CreateToSharedVault,
})
}
if (operationOrError.isFailed()) {
return Result.fail(operationOrError.getError())
}
return Result.ok(operationOrError.getValue())
}
}
@@ -0,0 +1,8 @@
import { ItemHash } from '../../../Item/ItemHash'
import { Item } from '../../../Item/Item'
export interface DetermineSharedVaultOperationOnItemDTO {
userUuid: string
itemHash: ItemHash
existingItem: Item | null
}
@@ -102,6 +102,26 @@ describe('RemoveUserFromSharedVault', () => {
expect(result.getError()).toBe('Only owner can remove users from shared vault')
})
it('should remove shared vault user if user is owner and is being force removed', async () => {
sharedVault = SharedVault.create({
fileUploadBytesLimit: 100,
fileUploadBytesUsed: 2,
userUuid: Uuid.create('00000000-0000-0000-0000-000000000002').getValue(),
timestamps: Timestamps.create(123, 123).getValue(),
}).getValue()
sharedVaultRepository.findByUuid = jest.fn().mockResolvedValue(sharedVault)
const useCase = createUseCase()
await useCase.execute({
originatorUuid: '00000000-0000-0000-0000-000000000002',
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
userUuid: '00000000-0000-0000-0000-000000000001',
forceRemoveOwner: true,
})
expect(sharedVaultUserRepository.remove).toHaveBeenCalledWith(sharedVaultUser)
})
it('should return error when user is owner of shared vault', async () => {
const useCase = createUseCase()
const result = await useCase.execute({
@@ -42,7 +42,7 @@ export class RemoveUserFromSharedVault implements UseCaseInterface<void> {
}
const removingOwner = sharedVault.props.userUuid.equals(userUuid)
if (removingOwner) {
if (removingOwner && !dto.forceRemoveOwner) {
return Result.fail('Owner cannot be removed from shared vault')
}
@@ -2,4 +2,5 @@ export interface RemoveUserFromSharedVaultDTO {
sharedVaultUuid: string
originatorUuid: string
userUuid: string
forceRemoveOwner?: boolean
}
@@ -303,23 +303,6 @@ describe('SaveNewItem', () => {
})
describe('when item hash represents a shared vault item', () => {
it('returns a failure if the shared vault uuid is invalid', async () => {
const useCase = createUseCase()
itemHash1 = ItemHash.create({
...itemHash1.props,
shared_vault_uuid: '1-2-3',
}).getValue()
const result = await useCase.execute({
userUuid: '00000000-0000-0000-0000-000000000000',
sessionUuid: '00000000-0000-0000-0000-000000000001',
itemHash: itemHash1,
})
expect(result.isFailed()).toBeTruthy()
})
it('should create a shared vault association between the item and the shared vault', async () => {
const useCase = createUseCase()
@@ -89,15 +89,9 @@ export class SaveNewItem implements UseCaseInterface<Item> {
let sharedVaultAssociation = undefined
if (dto.itemHash.representsASharedVaultItem()) {
const sharedVaultUuidOrError = Uuid.create(dto.itemHash.props.shared_vault_uuid as string)
if (sharedVaultUuidOrError.isFailed()) {
return Result.fail(sharedVaultUuidOrError.getError())
}
const sharedVaultUuid = sharedVaultUuidOrError.getValue()
const sharedVaultAssociationOrError = SharedVaultAssociation.create({
lastEditedBy: userUuid,
sharedVaultUuid,
sharedVaultUuid: dto.itemHash.sharedVaultUuid as Uuid,
timestamps: Timestamps.create(
this.timer.getTimestampInMicroseconds(),
this.timer.getTimestampInMicroseconds(),
@@ -327,23 +327,6 @@ describe('UpdateExistingItem', () => {
expect(item1.props.sharedVaultAssociation.id.toString()).toEqual(idBefore)
})
it('should return error if shared vault uuid is invalid', async () => {
const useCase = createUseCase()
const itemHash = ItemHash.create({
...itemHash1.props,
shared_vault_uuid: 'invalid-uuid',
}).getValue()
const result = await useCase.execute({
existingItem: item1,
itemHash,
sessionUuid: '00000000-0000-0000-0000-000000000000',
performingUserUuid: '00000000-0000-0000-0000-000000000000',
})
expect(result.isFailed()).toBeTruthy()
})
it('should return error if shared vault association could not be created', async () => {
const useCase = createUseCase()
@@ -1,4 +1,13 @@
import { ContentType, Dates, Result, Timestamps, UseCaseInterface, Uuid, Validator } from '@standardnotes/domain-core'
import {
ContentType,
Dates,
Result,
Timestamps,
UniqueEntityId,
UseCaseInterface,
Uuid,
Validator,
} from '@standardnotes/domain-core'
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
import { TimerInterface } from '@standardnotes/time'
@@ -8,7 +17,6 @@ import { ItemRepositoryInterface } from '../../../Item/ItemRepositoryInterface'
import { DomainEventFactoryInterface } from '../../../Event/DomainEventFactoryInterface'
import { SharedVaultAssociation } from '../../../SharedVault/SharedVaultAssociation'
import { KeySystemAssociation } from '../../../KeySystem/KeySystemAssociation'
import { ItemHash } from '../../../Item/ItemHash'
export class UpdateExistingItem implements UseCaseInterface<Item> {
constructor(
@@ -105,25 +113,26 @@ export class UpdateExistingItem implements UseCaseInterface<Item> {
dto.existingItem.props.contentSize = Buffer.byteLength(JSON.stringify(dto.existingItem))
if (
dto.itemHash.representsASharedVaultItem() &&
!this.itemIsAlreadyAssociatedWithTheSharedVault(dto.existingItem, dto.itemHash)
) {
const sharedVaultUuidOrError = Uuid.create(dto.itemHash.props.shared_vault_uuid as string)
if (sharedVaultUuidOrError.isFailed()) {
return Result.fail(sharedVaultUuidOrError.getError())
}
const sharedVaultUuid = sharedVaultUuidOrError.getValue()
if (dto.itemHash.representsASharedVaultItem()) {
const sharedVaultAssociationOrError = SharedVaultAssociation.create(
{
lastEditedBy: userUuid,
sharedVaultUuid: dto.itemHash.sharedVaultUuid as Uuid,
timestamps: Timestamps.create(
dto.existingItem.props.sharedVaultAssociation
? dto.existingItem.props.sharedVaultAssociation.props.timestamps.createdAt
: this.timer.getTimestampInMicroseconds(),
this.timer.getTimestampInMicroseconds(),
).getValue(),
itemUuid: Uuid.create(dto.existingItem.id.toString()).getValue(),
},
new UniqueEntityId(
dto.existingItem.props.sharedVaultAssociation
? dto.existingItem.props.sharedVaultAssociation.id.toString()
: undefined,
),
)
const sharedVaultAssociationOrError = SharedVaultAssociation.create({
lastEditedBy: userUuid,
sharedVaultUuid,
timestamps: Timestamps.create(
this.timer.getTimestampInMicroseconds(),
this.timer.getTimestampInMicroseconds(),
).getValue(),
itemUuid: Uuid.create(dto.existingItem.id.toString()).getValue(),
})
if (sharedVaultAssociationOrError.isFailed()) {
return Result.fail(sharedVaultAssociationOrError.getError())
}
@@ -133,7 +142,7 @@ export class UpdateExistingItem implements UseCaseInterface<Item> {
if (
dto.itemHash.hasDedicatedKeySystemAssociation() &&
!this.itemIsAlreadyAssociatedWithTheKeySystem(dto.existingItem, dto.itemHash)
!dto.existingItem.isAssociatedWithKeySystem(dto.itemHash.props.key_system_identifier as string)
) {
const keySystemIdentifiedValidationResult = Validator.isNotEmptyString(dto.itemHash.props.key_system_identifier)
if (keySystemIdentifiedValidationResult.isFailed()) {
@@ -192,18 +201,4 @@ export class UpdateExistingItem implements UseCaseInterface<Item> {
return Result.ok(dto.existingItem)
}
private itemIsAlreadyAssociatedWithTheSharedVault(item: Item, itemHash: ItemHash): boolean {
return (
item.props.sharedVaultAssociation !== undefined &&
item.props.sharedVaultAssociation.props.sharedVaultUuid.value === itemHash.props.shared_vault_uuid
)
}
private itemIsAlreadyAssociatedWithTheKeySystem(item: Item, itemHash: ItemHash): boolean {
return (
item.props.keySystemAssociation !== undefined &&
item.props.keySystemAssociation.props.keySystemIdentifier === itemHash.props.key_system_identifier
)
}
}