Compare commits

..

2 Commits

38 changed files with 453 additions and 100 deletions

View File

@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [2.25.4](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.25.3...@standardnotes/analytics@2.25.4) (2023-07-26)
**Note:** Version bump only for package @standardnotes/analytics
## [2.25.3](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.25.2...@standardnotes/analytics@2.25.3) (2023-07-21)
**Note:** Version bump only for package @standardnotes/analytics

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/analytics",
"version": "2.25.3",
"version": "2.25.4",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.67.3](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.67.2...@standardnotes/api-gateway@1.67.3) (2023-07-26)
**Note:** Version bump only for package @standardnotes/api-gateway
## [1.67.2](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.67.1...@standardnotes/api-gateway@1.67.2) (2023-07-21)
**Note:** Version bump only for package @standardnotes/api-gateway

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/api-gateway",
"version": "1.67.2",
"version": "1.67.3",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.126.3](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.126.2...@standardnotes/auth-server@1.126.3) (2023-07-26)
**Note:** Version bump only for package @standardnotes/auth-server
## [1.126.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.126.1...@standardnotes/auth-server@1.126.2) (2023-07-21)
**Note:** Version bump only for package @standardnotes/auth-server

View File

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

View File

@@ -3,6 +3,12 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.23.4](https://github.com/standardnotes/server/compare/@standardnotes/domain-core@1.23.3...@standardnotes/domain-core@1.23.4) (2023-07-26)
### Bug Fixes
* **syncing-server:** persisting aggregate changes from root ([#674](https://github.com/standardnotes/server/issues/674)) ([c34f548](https://github.com/standardnotes/server/commit/c34f548e45bbd8defb8d490936e90755fd284e78))
## [1.23.3](https://github.com/standardnotes/server/compare/@standardnotes/domain-core@1.23.2...@standardnotes/domain-core@1.23.3) (2023-07-21)
### Bug Fixes

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/domain-core",
"version": "1.23.3",
"version": "1.23.4",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -1,5 +1,20 @@
/* istanbul ignore file */
import { Change } from './Change'
import { Entity } from './Entity'
export abstract class Aggregate<T> extends Entity<T> {}
export abstract class Aggregate<T> extends Entity<T> {
private changesOnAggregateRoot: Change[] = []
addChange(change: Change): void {
this.changesOnAggregateRoot.push(change)
}
flushChanges(): void {
this.changesOnAggregateRoot = []
}
getChanges(): Change[] {
return this.changesOnAggregateRoot
}
}

View File

@@ -0,0 +1,26 @@
/* istanbul ignore file */
import { ChangeProps } from './ChangeProps'
import { Result } from './Result'
export class Change {
static readonly TYPES = {
Add: 'add',
Remove: 'remove',
Modify: 'modify',
}
public readonly props: ChangeProps
constructor(props: ChangeProps) {
this.props = Object.freeze(props)
}
static create(props: ChangeProps): Result<Change> {
if (!Object.values(Change.TYPES).includes(props.changeType)) {
return Result.fail('Invalid change type')
}
return Result.ok(new Change(props))
}
}

View File

@@ -0,0 +1,9 @@
/* istanbul ignore file */
import { Entity } from './Entity'
export interface ChangeProps {
aggregateRootUuid: string
changeType: string
changeData: Entity<unknown>
}

View File

@@ -27,6 +27,8 @@ export * from './Common/Uuid'
export * from './Common/UuidProps'
export * from './Core/Aggregate'
export * from './Core/Change'
export * from './Core/ChangeProps'
export * from './Core/Entity'
export * from './Core/Id'
export * from './Core/Result'

View File

@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.11.11](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.11.10...@standardnotes/event-store@1.11.11) (2023-07-26)
**Note:** Version bump only for package @standardnotes/event-store
## [1.11.10](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.11.9...@standardnotes/event-store@1.11.10) (2023-07-21)
**Note:** Version bump only for package @standardnotes/event-store

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/event-store",
"version": "1.11.10",
"version": "1.11.11",
"description": "Event Store Service",
"private": true,
"main": "dist/src/index.js",

View File

@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.19.13](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.19.12...@standardnotes/files-server@1.19.13) (2023-07-26)
**Note:** Version bump only for package @standardnotes/files-server
## [1.19.12](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.19.11...@standardnotes/files-server@1.19.12) (2023-07-21)
**Note:** Version bump only for package @standardnotes/files-server

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/files-server",
"version": "1.19.12",
"version": "1.19.13",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.13.11](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.13.10...@standardnotes/home-server@1.13.11) (2023-07-26)
**Note:** Version bump only for package @standardnotes/home-server
## [1.13.10](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.13.9...@standardnotes/home-server@1.13.10) (2023-07-25)
**Note:** Version bump only for package @standardnotes/home-server

View File

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

View File

@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.25.4](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.25.3...@standardnotes/revisions-server@1.25.4) (2023-07-26)
**Note:** Version bump only for package @standardnotes/revisions-server
## [1.25.3](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.25.2...@standardnotes/revisions-server@1.25.3) (2023-07-21)
**Note:** Version bump only for package @standardnotes/revisions-server

View File

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

View File

@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.20.13](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.20.12...@standardnotes/scheduler-server@1.20.13) (2023-07-26)
**Note:** Version bump only for package @standardnotes/scheduler-server
## [1.20.12](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.20.11...@standardnotes/scheduler-server@1.20.12) (2023-07-21)
**Note:** Version bump only for package @standardnotes/scheduler-server

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/scheduler-server",
"version": "1.20.12",
"version": "1.20.13",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.21.18](https://github.com/standardnotes/server/compare/@standardnotes/settings@1.21.17...@standardnotes/settings@1.21.18) (2023-07-26)
**Note:** Version bump only for package @standardnotes/settings
## [1.21.17](https://github.com/standardnotes/server/compare/@standardnotes/settings@1.21.16...@standardnotes/settings@1.21.17) (2023-07-21)
**Note:** Version bump only for package @standardnotes/settings

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/settings",
"version": "1.21.17",
"version": "1.21.18",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -3,6 +3,12 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.70.3](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.70.2...@standardnotes/syncing-server@1.70.3) (2023-07-26)
### Bug Fixes
* **syncing-server:** persisting aggregate changes from root ([#674](https://github.com/standardnotes/syncing-server-js/issues/674)) ([c34f548](https://github.com/standardnotes/syncing-server-js/commit/c34f548e45bbd8defb8d490936e90755fd284e78))
## [1.70.2](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.70.1...@standardnotes/syncing-server@1.70.2) (2023-07-25)
### Bug Fixes

View File

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

View File

@@ -2,6 +2,7 @@ import { ContentType, Dates, Timestamps, UniqueEntityId, Uuid } from '@standardn
import { Item } from './Item'
import { SharedVaultAssociation } from '../SharedVault/SharedVaultAssociation'
import { KeySystemAssociation } from '../KeySystem/KeySystemAssociation'
describe('Item', () => {
it('should create an aggregate', () => {
@@ -97,4 +98,155 @@ describe('Item', () => {
.isAssociatedWithSharedVault(Uuid.create('00000000-0000-0000-0000-000000000000').getValue()),
).toBeFalsy()
})
it('should tell if an item is associated with a key system', () => {
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(),
keySystemAssociation: KeySystemAssociation.create({
itemUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
keySystemIdentifier: 'key-system-identifier',
timestamps: Timestamps.create(123, 123).getValue(),
}).getValue(),
})
expect(entityOrError.isFailed()).toBeFalsy()
expect(entityOrError.getValue().isAssociatedWithKeySystem('key-system-identifier')).toBeTruthy()
})
it('should tell that an item is not associated with a key system', () => {
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().isAssociatedWithKeySystem('key-system-identifier')).toBeFalsy()
})
it('should set shared vault association', () => {
const 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()
const entity = 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(),
}).getValue()
entity.setSharedVaultAssociation(sharedVaultAssociation)
expect(entity.props.sharedVaultAssociation).toEqual(sharedVaultAssociation)
expect(entity.getChanges()).toHaveLength(1)
})
it('should unset a shared vault association', () => {
const entity = 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(),
}).getValue()
entity.unsetSharedVaultAssociation()
expect(entity.props.sharedVaultAssociation).toBeUndefined()
expect(entity.getChanges()).toHaveLength(1)
})
it('should set key system association', () => {
const keySystemAssociation = KeySystemAssociation.create({
itemUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
keySystemIdentifier: 'key-system-identifier',
timestamps: Timestamps.create(123, 123).getValue(),
}).getValue()
const entity = 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(),
}).getValue()
entity.setKeySystemAssociation(keySystemAssociation)
expect(entity.props.keySystemAssociation).toEqual(keySystemAssociation)
expect(entity.getChanges()).toHaveLength(1)
})
it('should unset a key system association', () => {
const entity = 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(),
keySystemAssociation: KeySystemAssociation.create({
itemUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
keySystemIdentifier: 'key-system-identifier',
timestamps: Timestamps.create(123, 123).getValue(),
}).getValue(),
}).getValue()
entity.unsetKeySystemAssociation()
expect(entity.props.keySystemAssociation).toBeUndefined()
expect(entity.getChanges()).toHaveLength(1)
})
})

View File

@@ -1,8 +1,23 @@
import { Aggregate, Result, UniqueEntityId, Uuid } from '@standardnotes/domain-core'
import { Aggregate, Change, Result, UniqueEntityId, Uuid } from '@standardnotes/domain-core'
import { ItemProps } from './ItemProps'
import { SharedVaultAssociation } from '../SharedVault/SharedVaultAssociation'
import { KeySystemAssociation } from '../KeySystem/KeySystemAssociation'
export class Item extends Aggregate<ItemProps> {
private constructor(props: ItemProps, id?: UniqueEntityId) {
super(props, id)
}
static create(props: ItemProps, id?: UniqueEntityId): Result<Item> {
if (!props.contentSize) {
const contentSize = Buffer.byteLength(JSON.stringify(props))
props.contentSize = contentSize
}
return Result.ok<Item>(new Item(props, id))
}
get uuid(): Uuid {
const uuidOrError = Uuid.create(this._id.toString())
if (uuidOrError.isFailed()) {
@@ -40,16 +55,57 @@ export class Item extends Aggregate<ItemProps> {
return this.props.keySystemAssociation.props.keySystemIdentifier === keySystemIdentifier
}
private constructor(props: ItemProps, id?: UniqueEntityId) {
super(props, id)
setSharedVaultAssociation(sharedVaultAssociation: SharedVaultAssociation): void {
this.addChange(
Change.create({
aggregateRootUuid: this.uuid.value,
changeType: this.props.sharedVaultAssociation ? Change.TYPES.Modify : Change.TYPES.Add,
changeData: sharedVaultAssociation,
}).getValue(),
)
this.props.sharedVaultAssociation = sharedVaultAssociation
}
static create(props: ItemProps, id?: UniqueEntityId): Result<Item> {
if (!props.contentSize) {
const contentSize = Buffer.byteLength(JSON.stringify(props))
props.contentSize = contentSize
unsetSharedVaultAssociation(): void {
if (!this.props.sharedVaultAssociation) {
return
}
return Result.ok<Item>(new Item(props, id))
this.addChange(
Change.create({
aggregateRootUuid: this.uuid.value,
changeType: Change.TYPES.Remove,
changeData: this.props.sharedVaultAssociation,
}).getValue(),
)
this.props.sharedVaultAssociation = undefined
}
setKeySystemAssociation(keySystemAssociation: KeySystemAssociation): void {
this.addChange(
Change.create({
aggregateRootUuid: this.uuid.value,
changeType: this.props.keySystemAssociation ? Change.TYPES.Modify : Change.TYPES.Add,
changeData: keySystemAssociation,
}).getValue(),
)
this.props.keySystemAssociation = keySystemAssociation
}
unsetKeySystemAssociation(): void {
if (!this.props.keySystemAssociation) {
return
}
this.addChange(
Change.create({
aggregateRootUuid: this.uuid.value,
changeType: Change.TYPES.Remove,
changeData: this.props.keySystemAssociation,
}).getValue(),
)
this.props.keySystemAssociation = undefined
}
}

View File

@@ -72,24 +72,11 @@ describe('SharedVaultFilter', () => {
.mockResolvedValueOnce(null)
})
it('should return as passed if the item hash does not represent a shared vault item', async () => {
it('should return as passed if the item hash does not represent a shared vault item and existing item is not a shared vault item', async () => {
itemHash = ItemHash.create({
...itemHash.props,
shared_vault_uuid: null,
}).getValue()
const filter = createFilter()
const result = await filter.check({
apiVersion: '001',
existingItem: existingItem,
itemHash: itemHash,
userUuid: '00000000-0000-0000-0000-000000000000',
})
expect(result.passed).toBe(true)
})
it('should return as passed if the item is not a shared vault item', async () => {
existingItem = Item.create({
...existingItem.props,
sharedVaultAssociation: undefined,

View File

@@ -16,7 +16,7 @@ export class SharedVaultFilter implements ItemSaveRuleInterface {
) {}
async check(dto: ItemSaveValidationDTO): Promise<ItemSaveRuleResult> {
if (!dto.itemHash.representsASharedVaultItem() || !dto.existingItem?.isAssociatedWithASharedVault()) {
if (!dto.itemHash.representsASharedVaultItem() && !dto.existingItem?.isAssociatedWithASharedVault()) {
return {
passed: true,
}

View File

@@ -87,7 +87,27 @@ export class SaveNewItem implements UseCaseInterface<Item> {
}
const timestamps = timestampsOrError.getValue()
let sharedVaultAssociation = undefined
const itemOrError = Item.create(
{
updatedWithSession,
content: dto.itemHash.props.content ?? null,
userUuid,
contentType,
encItemKey: dto.itemHash.props.enc_item_key ?? null,
authHash: dto.itemHash.props.auth_hash ?? null,
itemsKeyId: dto.itemHash.props.items_key_id ?? null,
duplicateOf,
deleted: dto.itemHash.props.deleted ?? false,
dates,
timestamps,
},
new UniqueEntityId(uuid.value),
)
if (itemOrError.isFailed()) {
return Result.fail(itemOrError.getError())
}
const newItem = itemOrError.getValue()
if (dto.itemHash.representsASharedVaultItem()) {
const sharedVaultAssociationOrError = SharedVaultAssociation.create({
lastEditedBy: userUuid,
@@ -101,10 +121,9 @@ export class SaveNewItem implements UseCaseInterface<Item> {
if (sharedVaultAssociationOrError.isFailed()) {
return Result.fail(sharedVaultAssociationOrError.getError())
}
sharedVaultAssociation = sharedVaultAssociationOrError.getValue()
newItem.setSharedVaultAssociation(sharedVaultAssociationOrError.getValue())
}
let keySystemAssociation = undefined
if (dto.itemHash.hasDedicatedKeySystemAssociation()) {
const keySystemIdentifiedValidationResult = Validator.isNotEmptyString(dto.itemHash.props.key_system_identifier)
if (keySystemIdentifiedValidationResult.isFailed()) {
@@ -123,32 +142,9 @@ export class SaveNewItem implements UseCaseInterface<Item> {
if (keySystemAssociationOrError.isFailed()) {
return Result.fail(keySystemAssociationOrError.getError())
}
keySystemAssociation = keySystemAssociationOrError.getValue()
newItem.setKeySystemAssociation(keySystemAssociationOrError.getValue())
}
const itemOrError = Item.create(
{
updatedWithSession,
content: dto.itemHash.props.content ?? null,
userUuid,
contentType,
encItemKey: dto.itemHash.props.enc_item_key ?? null,
authHash: dto.itemHash.props.auth_hash ?? null,
itemsKeyId: dto.itemHash.props.items_key_id ?? null,
duplicateOf,
deleted: dto.itemHash.props.deleted ?? false,
dates,
timestamps,
keySystemAssociation,
sharedVaultAssociation,
},
new UniqueEntityId(uuid.value),
)
if (itemOrError.isFailed()) {
return Result.fail(itemOrError.getError())
}
const newItem = itemOrError.getValue()
await this.itemRepository.save(newItem)
if (contentType.value !== null && [ContentType.TYPES.Note, ContentType.TYPES.File].includes(contentType.value)) {

View File

@@ -177,7 +177,7 @@ describe('SyncItems', () => {
})
})
it('should sync items and return items keys on top for first sync', async () => {
it('should sync items and return items keys on top for first sync that is not a shared vault exclusive sync', async () => {
const result = await createUseCase().execute({
userUuid: '1-2-3',
itemHashes: [itemHash],
@@ -202,6 +202,32 @@ describe('SyncItems', () => {
})
})
it('should sync items and not return items keys on top for first sync that is a shared vault exclusive sync', async () => {
const result = await createUseCase().execute({
userUuid: '1-2-3',
itemHashes: [itemHash],
computeIntegrityHash: false,
limit: 10,
readOnlyAccess: false,
sessionUuid: '2-3-4',
contentType: 'Note',
apiVersion: ApiVersion.v20200115,
snjsVersion: '1.2.3',
sharedVaultUuids: ['00000000-0000-0000-0000-000000000000'],
})
expect(result.getValue()).toEqual({
conflicts: [],
cursorToken: 'asdzxc',
retrievedItems: [item1],
savedItems: [item2],
syncToken: 'qwerty',
sharedVaults: [],
sharedVaultInvites: [],
notifications: [],
messages: [],
})
})
it('should sync items and return filtered out sync conflicts for consecutive sync operations', async () => {
getItemsUseCase.execute = jest.fn().mockReturnValue(
Result.ok({

View File

@@ -50,7 +50,8 @@ export class SyncItems implements UseCaseInterface<SyncItemsResponse> {
const saveItemsResult = saveItemsResultOrError.getValue()
let retrievedItems = this.filterOutSyncConflictsForConsecutiveSyncs(getItemsResult.items, saveItemsResult.conflicts)
if (this.isFirstSync(dto)) {
const isSharedVaultExclusiveSync = dto.sharedVaultUuids && dto.sharedVaultUuids.length > 0
if (this.isFirstSync(dto) && !isSharedVaultExclusiveSync) {
retrievedItems = await this.frontLoadKeysItemsToTop(dto.userUuid, retrievedItems)
}

View File

@@ -350,12 +350,14 @@ describe('UpdateExistingItem', () => {
shared_vault_uuid: '00000000-0000-0000-0000-000000000000',
}).getValue()
item1.props.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()
item1.setSharedVaultAssociation(
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(),
)
const idBefore = item1.props.sharedVaultAssociation?.id.toString()
const result = await useCase.execute({
@@ -368,7 +370,7 @@ describe('UpdateExistingItem', () => {
expect(result.isFailed()).toBeFalsy()
expect(item1.props.sharedVaultAssociation).not.toBeUndefined()
expect(item1.props.sharedVaultAssociation.id.toString()).toEqual(idBefore)
expect((item1.props.sharedVaultAssociation as SharedVaultAssociation).id.toString()).toEqual(idBefore)
})
it('should return error if shared vault association could not be created', async () => {
@@ -528,11 +530,13 @@ describe('UpdateExistingItem', () => {
key_system_identifier: '00000000-0000-0000-0000-000000000000',
}).getValue()
item1.props.keySystemAssociation = KeySystemAssociation.create({
itemUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
keySystemIdentifier: '00000000-0000-0000-0000-000000000000',
timestamps: Timestamps.create(123, 123).getValue(),
}).getValue()
item1.setKeySystemAssociation(
KeySystemAssociation.create({
itemUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
keySystemIdentifier: '00000000-0000-0000-0000-000000000000',
timestamps: Timestamps.create(123, 123).getValue(),
}).getValue(),
)
const idBefore = item1.props.keySystemAssociation?.id.toString()
const result = await useCase.execute({
@@ -545,7 +549,7 @@ describe('UpdateExistingItem', () => {
expect(result.isFailed()).toBeFalsy()
expect(item1.props.keySystemAssociation).not.toBeUndefined()
expect(item1.props.keySystemAssociation.id.toString()).toEqual(idBefore)
expect((item1.props.keySystemAssociation as KeySystemAssociation).id.toString()).toEqual(idBefore)
})
it('should return error if key system identifier is invalid', async () => {

View File

@@ -147,7 +147,7 @@ export class UpdateExistingItem implements UseCaseInterface<Item> {
return Result.fail(sharedVaultAssociationOrError.getError())
}
dto.existingItem.props.sharedVaultAssociation = sharedVaultAssociationOrError.getValue()
dto.existingItem.setSharedVaultAssociation(sharedVaultAssociationOrError.getValue())
const sharedVaultOperationOrError = await this.determineSharedVaultOperationOnItem.execute({
existingItem: dto.existingItem,
@@ -158,31 +158,39 @@ export class UpdateExistingItem implements UseCaseInterface<Item> {
return Result.fail(sharedVaultOperationOrError.getError())
}
sharedVaultOperation = sharedVaultOperationOrError.getValue()
} else {
dto.existingItem.unsetSharedVaultAssociation()
}
if (
dto.itemHash.hasDedicatedKeySystemAssociation() &&
!dto.existingItem.isAssociatedWithKeySystem(dto.itemHash.props.key_system_identifier as string)
) {
if (dto.itemHash.hasDedicatedKeySystemAssociation()) {
const keySystemIdentifiedValidationResult = Validator.isNotEmptyString(dto.itemHash.props.key_system_identifier)
if (keySystemIdentifiedValidationResult.isFailed()) {
return Result.fail(keySystemIdentifiedValidationResult.getError())
}
const keySystemIdentifier = dto.itemHash.props.key_system_identifier as string
const keySystemAssociationOrError = KeySystemAssociation.create({
itemUuid: Uuid.create(dto.existingItem.id.toString()).getValue(),
timestamps: Timestamps.create(
this.timer.getTimestampInMicroseconds(),
this.timer.getTimestampInMicroseconds(),
).getValue(),
keySystemIdentifier,
})
const keySystemAssociationOrError = KeySystemAssociation.create(
{
itemUuid: Uuid.create(dto.existingItem.id.toString()).getValue(),
timestamps: Timestamps.create(
this.timer.getTimestampInMicroseconds(),
this.timer.getTimestampInMicroseconds(),
).getValue(),
keySystemIdentifier,
},
new UniqueEntityId(
dto.existingItem.props.keySystemAssociation
? dto.existingItem.props.keySystemAssociation.id.toString()
: undefined,
),
)
if (keySystemAssociationOrError.isFailed()) {
return Result.fail(keySystemAssociationOrError.getError())
}
dto.existingItem.props.keySystemAssociation = keySystemAssociationOrError.getValue()
dto.existingItem.setKeySystemAssociation(keySystemAssociationOrError.getValue())
} else {
dto.existingItem.unsetKeySystemAssociation()
}
if (dto.itemHash.props.deleted === true) {

View File

@@ -1,6 +1,6 @@
import { ReadStream } from 'fs'
import { Repository, SelectQueryBuilder } from 'typeorm'
import { MapperInterface, Uuid } from '@standardnotes/domain-core'
import { Change, MapperInterface, Uuid } from '@standardnotes/domain-core'
import { Item } from '../../Domain/Item/Item'
import { ItemQuery } from '../../Domain/Item/ItemQuery'
@@ -10,6 +10,8 @@ import { TypeORMItem } from './TypeORMItem'
import { KeySystemAssociationRepositoryInterface } from '../../Domain/KeySystem/KeySystemAssociationRepositoryInterface'
import { SharedVaultAssociationRepositoryInterface } from '../../Domain/SharedVault/SharedVaultAssociationRepositoryInterface'
import { TypeORMSharedVaultAssociation } from './TypeORMSharedVaultAssociation'
import { SharedVaultAssociation } from '../../Domain/SharedVault/SharedVaultAssociation'
import { KeySystemAssociation } from '../../Domain/KeySystem/KeySystemAssociation'
export class TypeORMItemRepository implements ItemRepositoryInterface {
constructor(
@@ -24,13 +26,7 @@ export class TypeORMItemRepository implements ItemRepositoryInterface {
await this.ormRepository.save(persistence)
if (item.props.sharedVaultAssociation) {
await this.sharedVaultAssociationRepository.save(item.props.sharedVaultAssociation)
}
if (item.props.keySystemAssociation) {
await this.keySystemAssociationRepository.save(item.props.keySystemAssociation)
}
await this.persistAssociationChanges(item)
}
async remove(item: Item): Promise<void> {
@@ -273,4 +269,27 @@ export class TypeORMItemRepository implements ItemRepositoryInterface {
item.props.sharedVaultAssociation = sharedVaultAssociation
}
}
private async persistAssociationChanges(item: Item): Promise<void> {
for (const change of item.getChanges()) {
if (change.props.changeData instanceof SharedVaultAssociation) {
if ([Change.TYPES.Add, Change.TYPES.Modify].includes(change.props.changeType)) {
await this.sharedVaultAssociationRepository.save(change.props.changeData)
}
if (change.props.changeType === Change.TYPES.Remove) {
await this.sharedVaultAssociationRepository.remove(change.props.changeData)
}
}
if (change.props.changeData instanceof KeySystemAssociation) {
if ([Change.TYPES.Add, Change.TYPES.Modify].includes(change.props.changeType)) {
await this.keySystemAssociationRepository.save(change.props.changeData)
}
if (change.props.changeType === Change.TYPES.Remove) {
await this.keySystemAssociationRepository.remove(change.props.changeData)
}
}
}
item.flushChanges()
}
}

View File

@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.10.6](https://github.com/standardnotes/server/compare/@standardnotes/websockets-server@1.10.5...@standardnotes/websockets-server@1.10.6) (2023-07-26)
**Note:** Version bump only for package @standardnotes/websockets-server
## [1.10.5](https://github.com/standardnotes/server/compare/@standardnotes/websockets-server@1.10.4...@standardnotes/websockets-server@1.10.5) (2023-07-21)
**Note:** Version bump only for package @standardnotes/websockets-server

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/websockets-server",
"version": "1.10.5",
"version": "1.10.6",
"engines": {
"node": ">=18.0.0 <21.0.0"
},