mirror of
https://github.com/standardnotes/server
synced 2026-02-11 17:01:14 -05:00
Compare commits
16 Commits
@standardn
...
@standardn
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f77ed8ef94 | ||
|
|
a4929af2ee | ||
|
|
095811dda9 | ||
|
|
480d5879ba | ||
|
|
c0722b173b | ||
|
|
f07c8e4bd4 | ||
|
|
baf4b2c1d2 | ||
|
|
a6039bd99a | ||
|
|
7c0010c902 | ||
|
|
596a0f1a02 | ||
|
|
efda3df09b | ||
|
|
ec35f46d45 | ||
|
|
c64fa2f47c | ||
|
|
6ce42a0101 | ||
|
|
d40c74c072 | ||
|
|
d722206916 |
@@ -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.
|
||||
|
||||
## [2.25.21](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.25.20...@standardnotes/analytics@2.25.21) (2023-08-31)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/analytics
|
||||
|
||||
## [2.25.20](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.25.19...@standardnotes/analytics@2.25.20) (2023-08-31)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/analytics
|
||||
|
||||
## [2.25.19](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.25.18...@standardnotes/analytics@2.25.19) (2023-08-30)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/analytics
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/analytics",
|
||||
"version": "2.25.19",
|
||||
"version": "2.25.21",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -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.73.3](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.73.2...@standardnotes/api-gateway@1.73.3) (2023-08-31)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||
|
||||
## [1.73.2](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.73.1...@standardnotes/api-gateway@1.73.2) (2023-08-31)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||
|
||||
## [1.73.1](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.73.0...@standardnotes/api-gateway@1.73.1) (2023-08-30)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/api-gateway",
|
||||
"version": "1.73.1",
|
||||
"version": "1.73.3",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -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.137.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.137.1...@standardnotes/auth-server@1.137.2) (2023-08-31)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/auth-server
|
||||
|
||||
## [1.137.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.137.0...@standardnotes/auth-server@1.137.1) (2023-08-31)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/auth-server
|
||||
|
||||
# [1.137.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.136.0...@standardnotes/auth-server@1.137.0) (2023-08-30)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/auth-server",
|
||||
"version": "1.137.0",
|
||||
"version": "1.137.2",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -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.12.17](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.12.16...@standardnotes/domain-events-infra@1.12.17) (2023-08-31)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/domain-events-infra
|
||||
|
||||
## [1.12.16](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.12.15...@standardnotes/domain-events-infra@1.12.16) (2023-08-30)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/domain-events-infra
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/domain-events-infra",
|
||||
"version": "1.12.16",
|
||||
"version": "1.12.17",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -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.
|
||||
|
||||
# [2.120.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.119.0...@standardnotes/domain-events@2.120.0) (2023-08-31)
|
||||
|
||||
### Features
|
||||
|
||||
* add sending notifications to user via websockets ([#799](https://github.com/standardnotes/server/issues/799)) ([c0722b1](https://github.com/standardnotes/server/commit/c0722b173b71d696568d8e8c5095a22fd219bef6))
|
||||
|
||||
# [2.119.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.118.0...@standardnotes/domain-events@2.119.0) (2023-08-30)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/domain-events",
|
||||
"version": "2.119.0",
|
||||
"version": "2.120.0",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import { DomainEventInterface } from './DomainEventInterface'
|
||||
|
||||
import { NotificationAddedForUserEventPayload } from './NotificationAddedForUserEventPayload'
|
||||
|
||||
export interface NotificationAddedForUserEvent extends DomainEventInterface {
|
||||
type: 'NOTIFICATION_ADDED_FOR_USER'
|
||||
payload: NotificationAddedForUserEventPayload
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
export interface NotificationAddedForUserEventPayload {
|
||||
notification: {
|
||||
uuid: string
|
||||
user_uuid: string
|
||||
type: string
|
||||
payload: string
|
||||
created_at_timestamp: number
|
||||
updated_at_timestamp: number
|
||||
}
|
||||
}
|
||||
@@ -42,6 +42,8 @@ export * from './Event/ListedAccountRequestedEvent'
|
||||
export * from './Event/ListedAccountRequestedEventPayload'
|
||||
export * from './Event/MuteEmailsSettingChangedEvent'
|
||||
export * from './Event/MuteEmailsSettingChangedEventPayload'
|
||||
export * from './Event/NotificationAddedForUserEvent'
|
||||
export * from './Event/NotificationAddedForUserEventPayload'
|
||||
export * from './Event/PaymentFailedEvent'
|
||||
export * from './Event/PaymentFailedEventPayload'
|
||||
export * from './Event/PaymentsAccountDeletedEvent'
|
||||
|
||||
@@ -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.11.27](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.11.26...@standardnotes/event-store@1.11.27) (2023-08-31)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/event-store
|
||||
|
||||
## [1.11.26](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.11.25...@standardnotes/event-store@1.11.26) (2023-08-31)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/event-store
|
||||
|
||||
## [1.11.25](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.11.24...@standardnotes/event-store@1.11.25) (2023-08-30)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/event-store
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/event-store",
|
||||
"version": "1.11.25",
|
||||
"version": "1.11.27",
|
||||
"description": "Event Store Service",
|
||||
"private": true,
|
||||
"main": "dist/src/index.js",
|
||||
|
||||
@@ -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.22.6](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.22.5...@standardnotes/files-server@1.22.6) (2023-08-31)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files-server
|
||||
|
||||
## [1.22.5](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.22.4...@standardnotes/files-server@1.22.5) (2023-08-31)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files-server
|
||||
|
||||
## [1.22.4](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.22.3...@standardnotes/files-server@1.22.4) (2023-08-30)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files-server
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/files-server",
|
||||
"version": "1.22.4",
|
||||
"version": "1.22.6",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,34 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.15.17](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.16...@standardnotes/home-server@1.15.17) (2023-08-31)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.15.16](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.15...@standardnotes/home-server@1.15.16) (2023-08-31)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.15.15](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.14...@standardnotes/home-server@1.15.15) (2023-08-31)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.15.14](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.13...@standardnotes/home-server@1.15.14) (2023-08-31)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.15.13](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.12...@standardnotes/home-server@1.15.13) (2023-08-30)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.15.12](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.11...@standardnotes/home-server@1.15.12) (2023-08-30)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.15.11](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.10...@standardnotes/home-server@1.15.11) (2023-08-30)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.15.10](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.9...@standardnotes/home-server@1.15.10) (2023-08-30)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/home-server",
|
||||
"version": "1.15.10",
|
||||
"version": "1.15.17",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,47 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.30.7](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.30.6...@standardnotes/revisions-server@1.30.7) (2023-08-31)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **revisions:** add more verbose messages about failures in revision transitioning ([a4929af](https://github.com/standardnotes/server/commit/a4929af2ee3df4db9f57c2b0fb250b6828095421))
|
||||
* **revisions:** revisions transition check for total count at the end ([095811d](https://github.com/standardnotes/server/commit/095811dda929e6947af36e096a659f18f1b5f8b8))
|
||||
|
||||
## [1.30.6](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.30.5...@standardnotes/revisions-server@1.30.6) (2023-08-31)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/revisions-server
|
||||
|
||||
## [1.30.5](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.30.4...@standardnotes/revisions-server@1.30.5) (2023-08-31)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **revisions:** indiciate if revision did not save ([a6039bd](https://github.com/standardnotes/server/commit/a6039bd99ac37cc5ee4487336fa42412722c7815))
|
||||
|
||||
## [1.30.4](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.30.3...@standardnotes/revisions-server@1.30.4) (2023-08-31)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* transitionining revisions ([#801](https://github.com/standardnotes/server/issues/801)) ([596a0f1](https://github.com/standardnotes/server/commit/596a0f1a0221ab0636c4c04d17a28c57fe74b620))
|
||||
|
||||
## [1.30.3](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.30.2...@standardnotes/revisions-server@1.30.3) (2023-08-30)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* mongo delete queries ([ec35f46](https://github.com/standardnotes/server/commit/ec35f46d457ec5a5125dc1d0f1a14fb262012caa))
|
||||
|
||||
## [1.30.2](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.30.1...@standardnotes/revisions-server@1.30.2) (2023-08-30)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **revisions:** mongo queries ([6ce42a0](https://github.com/standardnotes/server/commit/6ce42a0101169dd316624651b47a34f87ca35299))
|
||||
|
||||
## [1.30.1](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.30.0...@standardnotes/revisions-server@1.30.1) (2023-08-30)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **revisions:** message in logs for trasitions status updated ([d722206](https://github.com/standardnotes/server/commit/d722206916d358e757f1cad7efeaf9881a1c6246))
|
||||
|
||||
# [1.30.0](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.29.0...@standardnotes/revisions-server@1.30.0) (2023-08-30)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/revisions-server",
|
||||
"version": "1.30.0",
|
||||
"version": "1.30.7",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -34,7 +34,7 @@ export class TransitionStatusUpdatedEventHandler implements DomainEventHandlerIn
|
||||
})
|
||||
|
||||
if (result.isFailed()) {
|
||||
this.logger.error(`Failed to transition items for user ${event.payload.userUuid}: ${result.getError()}`)
|
||||
this.logger.error(`Failed to transition revisions for user ${event.payload.userUuid}: ${result.getError()}`)
|
||||
|
||||
await this.domainEventPublisher.publish(
|
||||
this.domainEventFactory.createTransitionStatusUpdatedEvent({
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ContentType, Dates, Uuid } from '@standardnotes/domain-core'
|
||||
import { ContentType, Dates, UniqueEntityId, Uuid } from '@standardnotes/domain-core'
|
||||
|
||||
import { Revision } from './Revision'
|
||||
|
||||
@@ -19,4 +19,106 @@ describe('Revision', () => {
|
||||
expect(entityOrError.isFailed()).toBeFalsy()
|
||||
expect(entityOrError.getValue().id).not.toBeNull()
|
||||
})
|
||||
|
||||
it('should tell if a revision is identical to another revision', () => {
|
||||
const entity1 = Revision.create(
|
||||
{
|
||||
itemUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
|
||||
userUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
|
||||
content: 'test',
|
||||
contentType: ContentType.create('Note').getValue(),
|
||||
itemsKeyId: 'test',
|
||||
encItemKey: 'test',
|
||||
authHash: 'test',
|
||||
creationDate: new Date(1),
|
||||
dates: Dates.create(new Date(1), new Date(2)).getValue(),
|
||||
},
|
||||
new UniqueEntityId('00000000-0000-0000-0000-000000000000'),
|
||||
).getValue()
|
||||
|
||||
const entity2 = Revision.create(
|
||||
{
|
||||
itemUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
|
||||
userUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
|
||||
content: 'test',
|
||||
contentType: ContentType.create('Note').getValue(),
|
||||
itemsKeyId: 'test',
|
||||
encItemKey: 'test',
|
||||
authHash: 'test',
|
||||
creationDate: new Date(1),
|
||||
dates: Dates.create(new Date(1), new Date(2)).getValue(),
|
||||
},
|
||||
new UniqueEntityId('00000000-0000-0000-0000-000000000000'),
|
||||
).getValue()
|
||||
|
||||
expect(entity1.isIdenticalTo(entity2)).toBeTruthy()
|
||||
})
|
||||
|
||||
it('should tell if a revision is not identical to another revision', () => {
|
||||
const entity1 = Revision.create(
|
||||
{
|
||||
itemUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
|
||||
userUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
|
||||
content: 'test',
|
||||
contentType: ContentType.create('Note').getValue(),
|
||||
itemsKeyId: 'test',
|
||||
encItemKey: 'test',
|
||||
authHash: 'test',
|
||||
creationDate: new Date(1),
|
||||
dates: Dates.create(new Date(1), new Date(2)).getValue(),
|
||||
},
|
||||
new UniqueEntityId('00000000-0000-0000-0000-000000000000'),
|
||||
).getValue()
|
||||
|
||||
const entity2 = Revision.create(
|
||||
{
|
||||
itemUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
|
||||
userUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
|
||||
content: 'test2',
|
||||
contentType: ContentType.create('Note').getValue(),
|
||||
itemsKeyId: 'test',
|
||||
encItemKey: 'test',
|
||||
authHash: 'test',
|
||||
creationDate: new Date(1),
|
||||
dates: Dates.create(new Date(1), new Date(2)).getValue(),
|
||||
},
|
||||
new UniqueEntityId('00000000-0000-0000-0000-000000000000'),
|
||||
).getValue()
|
||||
|
||||
expect(entity1.isIdenticalTo(entity2)).toBeFalsy()
|
||||
})
|
||||
|
||||
it('should tell if a revision is not identical to another revision id ids do not match', () => {
|
||||
const entity1 = Revision.create(
|
||||
{
|
||||
itemUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
|
||||
userUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
|
||||
content: 'test',
|
||||
contentType: ContentType.create('Note').getValue(),
|
||||
itemsKeyId: 'test',
|
||||
encItemKey: 'test',
|
||||
authHash: 'test',
|
||||
creationDate: new Date(1),
|
||||
dates: Dates.create(new Date(1), new Date(2)).getValue(),
|
||||
},
|
||||
new UniqueEntityId('00000000-0000-0000-0000-000000000000'),
|
||||
).getValue()
|
||||
|
||||
const entity2 = Revision.create(
|
||||
{
|
||||
itemUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
|
||||
userUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
|
||||
content: 'test',
|
||||
contentType: ContentType.create('Note').getValue(),
|
||||
itemsKeyId: 'test',
|
||||
encItemKey: 'test',
|
||||
authHash: 'test',
|
||||
creationDate: new Date(1),
|
||||
dates: Dates.create(new Date(1), new Date(2)).getValue(),
|
||||
},
|
||||
new UniqueEntityId('00000000-0000-0000-0000-000000000001'),
|
||||
).getValue()
|
||||
|
||||
expect(entity1.isIdenticalTo(entity2)).toBeFalsy()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -12,5 +12,5 @@ export interface RevisionRepositoryInterface {
|
||||
findMetadataByItemId(itemUuid: Uuid, userUuid: Uuid): Promise<Array<RevisionMetadata>>
|
||||
updateUserUuid(itemUuid: Uuid, userUuid: Uuid): Promise<void>
|
||||
findByUserUuid(dto: { userUuid: Uuid; offset?: number; limit?: number }): Promise<Array<Revision>>
|
||||
save(revision: Revision): Promise<Revision>
|
||||
save(revision: Revision): Promise<boolean>
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ describe('TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser', () => {
|
||||
primaryRevisionRepository.removeByUserUuid = jest.fn().mockResolvedValue(undefined)
|
||||
|
||||
secondaryRevisionRepository = {} as jest.Mocked<RevisionRepositoryInterface>
|
||||
secondaryRevisionRepository.save = jest.fn().mockResolvedValue(undefined)
|
||||
secondaryRevisionRepository.save = jest.fn().mockResolvedValue(true)
|
||||
secondaryRevisionRepository.removeByUserUuid = jest.fn().mockResolvedValue(undefined)
|
||||
secondaryRevisionRepository.countByUserUuid = jest.fn().mockResolvedValue(2)
|
||||
secondaryRevisionRepository.findOneByUuid = jest
|
||||
@@ -109,6 +109,7 @@ describe('TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser', () => {
|
||||
logger.info = jest.fn()
|
||||
|
||||
timer = {} as jest.Mocked<TimerInterface>
|
||||
timer.sleep = jest.fn()
|
||||
timer.getTimestampInMicroseconds = jest.fn().mockReturnValue(123)
|
||||
timer.convertMicrosecondsToTimeStructure = jest.fn().mockReturnValue({
|
||||
days: 0,
|
||||
@@ -174,7 +175,7 @@ describe('TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser', () => {
|
||||
|
||||
expect(logger.error).toHaveBeenCalledTimes(1)
|
||||
expect(logger.error).toHaveBeenCalledWith(
|
||||
'Failed to clean up primary database revisions for user 00000000-0000-0000-0000-000000000000: error',
|
||||
'Failed to clean up primary database revisions for user 00000000-0000-0000-0000-000000000000: Errored when deleting revisions for user 00000000-0000-0000-0000-000000000000: error',
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -219,12 +220,32 @@ describe('TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser', () => {
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBeTruthy()
|
||||
expect(result.getError()).toEqual('error')
|
||||
expect(result.getError()).toEqual(
|
||||
'Errored when migrating revisions for user 00000000-0000-0000-0000-000000000000: error',
|
||||
)
|
||||
|
||||
expect((secondaryRevisionRepository as RevisionRepositoryInterface).removeByUserUuid).toHaveBeenCalledTimes(1)
|
||||
expect(primaryRevisionRepository.removeByUserUuid).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should return an error for a specific revision if it errors when saving to secondary database', async () => {
|
||||
;(secondaryRevisionRepository as RevisionRepositoryInterface).save = jest
|
||||
.fn()
|
||||
.mockResolvedValueOnce(true)
|
||||
.mockRejectedValueOnce(new Error('error'))
|
||||
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: '84c0f8e8-544a-4c7e-9adf-26209303bc1d',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBeTruthy()
|
||||
expect(result.getError()).toEqual(
|
||||
'Errored when saving revision 00000000-0000-0000-0000-000000000001 to secondary database: error',
|
||||
)
|
||||
})
|
||||
|
||||
it('should log an error if deleting Revisions from secondary database fails upon migration failure', async () => {
|
||||
primaryRevisionRepository.findByUserUuid = jest
|
||||
.fn()
|
||||
@@ -244,7 +265,7 @@ describe('TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser', () => {
|
||||
|
||||
expect(logger.error).toHaveBeenCalledTimes(1)
|
||||
expect(logger.error).toHaveBeenCalledWith(
|
||||
'Failed to clean up secondary database revisions for user 00000000-0000-0000-0000-000000000000: error',
|
||||
'Failed to clean up secondary database revisions for user 00000000-0000-0000-0000-000000000000: Errored when deleting revisions for user 00000000-0000-0000-0000-000000000000: error',
|
||||
)
|
||||
})
|
||||
|
||||
@@ -272,7 +293,7 @@ describe('TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser', () => {
|
||||
|
||||
expect(logger.error).toHaveBeenCalledTimes(1)
|
||||
expect(logger.error).toHaveBeenCalledWith(
|
||||
'Failed to clean up secondary database revisions for user 00000000-0000-0000-0000-000000000000: error',
|
||||
'Failed to clean up secondary database revisions for user 00000000-0000-0000-0000-000000000000: Errored when deleting revisions for user 00000000-0000-0000-0000-000000000000: error',
|
||||
)
|
||||
})
|
||||
|
||||
@@ -350,7 +371,7 @@ describe('TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser', () => {
|
||||
expect(primaryRevisionRepository.countByUserUuid).toHaveBeenCalledWith(
|
||||
Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
)
|
||||
expect((secondaryRevisionRepository as RevisionRepositoryInterface).countByUserUuid).toHaveBeenCalledTimes(1)
|
||||
expect((secondaryRevisionRepository as RevisionRepositoryInterface).countByUserUuid).not.toHaveBeenCalled()
|
||||
expect(primaryRevisionRepository.removeByUserUuid).not.toHaveBeenCalled()
|
||||
expect((secondaryRevisionRepository as RevisionRepositoryInterface).removeByUserUuid).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
@@ -367,7 +388,25 @@ describe('TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser', () => {
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBeTruthy()
|
||||
expect(result.getError()).toEqual('error')
|
||||
expect(result.getError()).toEqual('Errored when checking integrity between primary and secondary database: error')
|
||||
|
||||
expect(primaryRevisionRepository.removeByUserUuid).not.toHaveBeenCalled()
|
||||
expect((secondaryRevisionRepository as RevisionRepositoryInterface).removeByUserUuid).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('should fail if a revisions did not save in the secondary database', async () => {
|
||||
;(secondaryRevisionRepository as RevisionRepositoryInterface).save = jest.fn().mockResolvedValue(false)
|
||||
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBeTruthy()
|
||||
expect(result.getError()).toEqual(
|
||||
'Failed to save revision 00000000-0000-0000-0000-000000000000 to secondary database',
|
||||
)
|
||||
|
||||
expect(primaryRevisionRepository.removeByUserUuid).not.toHaveBeenCalled()
|
||||
expect((secondaryRevisionRepository as RevisionRepositoryInterface).removeByUserUuid).toHaveBeenCalledTimes(1)
|
||||
|
||||
@@ -38,6 +38,8 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
|
||||
return Result.fail(migrationResult.getError())
|
||||
}
|
||||
|
||||
await this.allowForSecondaryDatabaseToCatchUp()
|
||||
|
||||
const integrityCheckResult = await this.checkIntegrityBetweenPrimaryAndSecondaryDatabase(userUuid)
|
||||
if (integrityCheckResult.isFailed()) {
|
||||
const cleanupResult = await this.deleteRevisionsForUser(userUuid, this.secondRevisionsRepository)
|
||||
@@ -84,13 +86,24 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
|
||||
const revisions = await this.primaryRevisionsRepository.findByUserUuid(query)
|
||||
|
||||
for (const revision of revisions) {
|
||||
await (this.secondRevisionsRepository as RevisionRepositoryInterface).save(revision)
|
||||
try {
|
||||
const didSave = await (this.secondRevisionsRepository as RevisionRepositoryInterface).save(revision)
|
||||
if (!didSave) {
|
||||
return Result.fail(`Failed to save revision ${revision.id.toString()} to secondary database`)
|
||||
}
|
||||
} catch (error) {
|
||||
return Result.fail(
|
||||
`Errored when saving revision ${revision.id.toString()} to secondary database: ${
|
||||
(error as Error).message
|
||||
}`,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Result.ok()
|
||||
} catch (error) {
|
||||
return Result.fail((error as Error).message)
|
||||
return Result.fail(`Errored when migrating revisions for user ${userUuid.value}: ${(error as Error).message}`)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,22 +116,18 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
|
||||
|
||||
return Result.ok()
|
||||
} catch (error) {
|
||||
return Result.fail((error as Error).message)
|
||||
return Result.fail(`Errored when deleting revisions for user ${userUuid.value}: ${(error as Error).message}`)
|
||||
}
|
||||
}
|
||||
|
||||
private async allowForSecondaryDatabaseToCatchUp(): Promise<void> {
|
||||
const twoSecondsInMilliseconds = 2_000
|
||||
await this.timer.sleep(twoSecondsInMilliseconds)
|
||||
}
|
||||
|
||||
private async checkIntegrityBetweenPrimaryAndSecondaryDatabase(userUuid: Uuid): Promise<Result<boolean>> {
|
||||
try {
|
||||
const totalRevisionsCountForUserInPrimary = await this.primaryRevisionsRepository.countByUserUuid(userUuid)
|
||||
const totalRevisionsCountForUserInSecondary = await (
|
||||
this.secondRevisionsRepository as RevisionRepositoryInterface
|
||||
).countByUserUuid(userUuid)
|
||||
|
||||
if (totalRevisionsCountForUserInPrimary !== totalRevisionsCountForUserInSecondary) {
|
||||
return Result.fail(
|
||||
`Total revisions count for user ${userUuid.value} in primary database (${totalRevisionsCountForUserInPrimary}) does not match total revisions count in secondary database (${totalRevisionsCountForUserInSecondary})`,
|
||||
)
|
||||
}
|
||||
|
||||
const pageSize = 1
|
||||
const totalPages = Math.ceil(totalRevisionsCountForUserInPrimary / pageSize)
|
||||
@@ -152,9 +161,21 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
|
||||
}
|
||||
}
|
||||
|
||||
const totalRevisionsCountForUserInSecondary = await (
|
||||
this.secondRevisionsRepository as RevisionRepositoryInterface
|
||||
).countByUserUuid(userUuid)
|
||||
|
||||
if (totalRevisionsCountForUserInPrimary !== totalRevisionsCountForUserInSecondary) {
|
||||
return Result.fail(
|
||||
`Total revisions count for user ${userUuid.value} in primary database (${totalRevisionsCountForUserInPrimary}) does not match total revisions count in secondary database (${totalRevisionsCountForUserInSecondary})`,
|
||||
)
|
||||
}
|
||||
|
||||
return Result.ok()
|
||||
} catch (error) {
|
||||
return Result.fail((error as Error).message)
|
||||
return Result.fail(
|
||||
`Errored when checking integrity between primary and secondary database: ${(error as Error).message}`,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ export class MongoDBRevisionRepository implements RevisionRepositoryInterface {
|
||||
) {}
|
||||
|
||||
async countByUserUuid(userUuid: Uuid): Promise<number> {
|
||||
return this.mongoRepository.count({ where: { userUuid: { $eq: userUuid.value } } })
|
||||
return this.mongoRepository.count({ userUuid: { $eq: userUuid.value } })
|
||||
}
|
||||
|
||||
async findByUserUuid(dto: { userUuid: Uuid; offset?: number; limit?: number }): Promise<Revision[]> {
|
||||
@@ -39,17 +39,13 @@ export class MongoDBRevisionRepository implements RevisionRepositoryInterface {
|
||||
}
|
||||
|
||||
async removeByUserUuid(userUuid: Uuid): Promise<void> {
|
||||
await this.mongoRepository.deleteMany({ where: { userUuid: { $eq: userUuid.value } } })
|
||||
await this.mongoRepository.deleteMany({ userUuid: userUuid.value })
|
||||
}
|
||||
|
||||
async removeOneByUuid(revisionUuid: Uuid, userUuid: Uuid): Promise<void> {
|
||||
await this.mongoRepository.deleteOne({
|
||||
where: {
|
||||
$and: [
|
||||
{ _id: { $eq: BSON.UUID.createFromHexString(revisionUuid.value) } },
|
||||
{ userUuid: { $eq: userUuid.value } },
|
||||
],
|
||||
},
|
||||
_id: { $eq: BSON.UUID.createFromHexString(revisionUuid.value) },
|
||||
userUuid: { $eq: userUuid.value },
|
||||
})
|
||||
}
|
||||
|
||||
@@ -127,12 +123,12 @@ export class MongoDBRevisionRepository implements RevisionRepositoryInterface {
|
||||
)
|
||||
}
|
||||
|
||||
async save(revision: Revision): Promise<Revision> {
|
||||
async save(revision: Revision): Promise<boolean> {
|
||||
const persistence = this.revisionMapper.toProjection(revision)
|
||||
|
||||
const { _id, ...rest } = persistence
|
||||
|
||||
await this.mongoRepository.updateOne(
|
||||
const updateResult = await this.mongoRepository.updateOne(
|
||||
{ _id: { $eq: _id } },
|
||||
{
|
||||
$set: rest,
|
||||
@@ -140,6 +136,6 @@ export class MongoDBRevisionRepository implements RevisionRepositoryInterface {
|
||||
{ upsert: true },
|
||||
)
|
||||
|
||||
return revision
|
||||
return updateResult.acknowledged
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,12 +106,12 @@ export class SQLRevisionRepository implements RevisionRepositoryInterface {
|
||||
return this.revisionMapper.toDomain(SQLRevision)
|
||||
}
|
||||
|
||||
async save(revision: Revision): Promise<Revision> {
|
||||
async save(revision: Revision): Promise<boolean> {
|
||||
const SQLRevision = this.revisionMapper.toProjection(revision)
|
||||
|
||||
await this.ormRepository.save(SQLRevision)
|
||||
|
||||
return revision
|
||||
return true
|
||||
}
|
||||
|
||||
async findMetadataByItemId(itemUuid: Uuid, userUuid: Uuid): Promise<Array<RevisionMetadata>> {
|
||||
|
||||
@@ -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.20.31](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.20.30...@standardnotes/scheduler-server@1.20.31) (2023-08-31)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/scheduler-server
|
||||
|
||||
## [1.20.30](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.20.29...@standardnotes/scheduler-server@1.20.30) (2023-08-31)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/scheduler-server
|
||||
|
||||
## [1.20.29](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.20.28...@standardnotes/scheduler-server@1.20.29) (2023-08-30)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/scheduler-server
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/scheduler-server",
|
||||
"version": "1.20.29",
|
||||
"version": "1.20.31",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,30 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [1.89.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.88.3...@standardnotes/syncing-server@1.89.0) (2023-08-31)
|
||||
|
||||
### Features
|
||||
|
||||
* add sending notifications to user via websockets ([#799](https://github.com/standardnotes/syncing-server-js/issues/799)) ([c0722b1](https://github.com/standardnotes/syncing-server-js/commit/c0722b173b71d696568d8e8c5095a22fd219bef6))
|
||||
|
||||
## [1.88.3](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.88.2...@standardnotes/syncing-server@1.88.3) (2023-08-31)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **syncing-server:** persistence mapping for deleted field ([baf4b2c](https://github.com/standardnotes/syncing-server-js/commit/baf4b2c1d205929be8c330450dca16c18ad5cdd6))
|
||||
|
||||
## [1.88.2](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.88.1...@standardnotes/syncing-server@1.88.2) (2023-08-31)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* transitionining revisions ([#801](https://github.com/standardnotes/syncing-server-js/issues/801)) ([596a0f1](https://github.com/standardnotes/syncing-server-js/commit/596a0f1a0221ab0636c4c04d17a28c57fe74b620))
|
||||
|
||||
## [1.88.1](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.88.0...@standardnotes/syncing-server@1.88.1) (2023-08-30)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* mongo delete queries ([ec35f46](https://github.com/standardnotes/syncing-server-js/commit/ec35f46d457ec5a5125dc1d0f1a14fb262012caa))
|
||||
|
||||
# [1.88.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.87.0...@standardnotes/syncing-server@1.88.0) (2023-08-30)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/syncing-server",
|
||||
"version": "1.88.0",
|
||||
"version": "1.89.0",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -161,6 +161,7 @@ import { TriggerTransitionFromPrimaryToSecondaryDatabaseForUser } from '../Domai
|
||||
import { SQLItem } from '../Infra/TypeORM/SQLItem'
|
||||
import { SQLItemPersistenceMapper } from '../Mapping/Persistence/SQLItemPersistenceMapper'
|
||||
import { SQLItemRepository } from '../Infra/TypeORM/SQLItemRepository'
|
||||
import { SendEventToClient } from '../Domain/UseCase/Syncing/SendEventToClient/SendEventToClient'
|
||||
|
||||
export class ContainerConfigLoader {
|
||||
private readonly DEFAULT_CONTENT_SIZE_TRANSFER_LIMIT = 10_000_000
|
||||
@@ -580,10 +581,24 @@ export class ContainerConfigLoader {
|
||||
container.get(TYPES.Sync_DomainEventFactory),
|
||||
),
|
||||
)
|
||||
container
|
||||
.bind<SendEventToClient>(TYPES.Sync_SendEventToClient)
|
||||
.toConstantValue(
|
||||
new SendEventToClient(
|
||||
container.get<DomainEventFactoryInterface>(TYPES.Sync_DomainEventFactory),
|
||||
container.get<DomainEventPublisherInterface>(TYPES.Sync_DomainEventPublisher),
|
||||
),
|
||||
)
|
||||
container
|
||||
.bind<AddNotificationForUser>(TYPES.Sync_AddNotificationForUser)
|
||||
.toConstantValue(
|
||||
new AddNotificationForUser(container.get(TYPES.Sync_NotificationRepository), container.get(TYPES.Sync_Timer)),
|
||||
new AddNotificationForUser(
|
||||
container.get<NotificationRepositoryInterface>(TYPES.Sync_NotificationRepository),
|
||||
container.get<TimerInterface>(TYPES.Sync_Timer),
|
||||
container.get<DomainEventFactoryInterface>(TYPES.Sync_DomainEventFactory),
|
||||
container.get<SendEventToClient>(TYPES.Sync_SendEventToClient),
|
||||
container.get<Logger>(TYPES.Sync_Logger),
|
||||
),
|
||||
)
|
||||
container
|
||||
.bind<AddNotificationsForUsers>(TYPES.Sync_AddNotificationsForUsers)
|
||||
|
||||
@@ -87,6 +87,7 @@ const TYPES = {
|
||||
Sync_TriggerTransitionFromPrimaryToSecondaryDatabaseForUser: Symbol.for(
|
||||
'Sync_TriggerTransitionFromPrimaryToSecondaryDatabaseForUser',
|
||||
),
|
||||
Sync_SendEventToClient: Symbol.for('Sync_SendEventToClient'),
|
||||
// Handlers
|
||||
Sync_AccountDeletionRequestedEventHandler: Symbol.for('Sync_AccountDeletionRequestedEventHandler'),
|
||||
Sync_DuplicateItemSyncedEventHandler: Symbol.for('Sync_DuplicateItemSyncedEventHandler'),
|
||||
|
||||
@@ -5,8 +5,10 @@ import {
|
||||
EmailRequestedEvent,
|
||||
ItemDumpedEvent,
|
||||
ItemRevisionCreationRequestedEvent,
|
||||
NotificationAddedForUserEvent,
|
||||
RevisionsCopyRequestedEvent,
|
||||
TransitionStatusUpdatedEvent,
|
||||
WebSocketMessageRequestedEvent,
|
||||
} from '@standardnotes/domain-events'
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
import { DomainEventFactoryInterface } from './DomainEventFactoryInterface'
|
||||
@@ -14,6 +16,45 @@ import { DomainEventFactoryInterface } from './DomainEventFactoryInterface'
|
||||
export class DomainEventFactory implements DomainEventFactoryInterface {
|
||||
constructor(private timer: TimerInterface) {}
|
||||
|
||||
createNotificationAddedForUserEvent(dto: {
|
||||
notification: {
|
||||
uuid: string
|
||||
user_uuid: string
|
||||
type: string
|
||||
payload: string
|
||||
created_at_timestamp: number
|
||||
updated_at_timestamp: number
|
||||
}
|
||||
}): NotificationAddedForUserEvent {
|
||||
return {
|
||||
type: 'NOTIFICATION_ADDED_FOR_USER',
|
||||
createdAt: this.timer.getUTCDate(),
|
||||
meta: {
|
||||
correlation: {
|
||||
userIdentifier: dto.notification.user_uuid,
|
||||
userIdentifierType: 'uuid',
|
||||
},
|
||||
origin: DomainEventService.SyncingServer,
|
||||
},
|
||||
payload: dto,
|
||||
}
|
||||
}
|
||||
|
||||
createWebSocketMessageRequestedEvent(dto: { userUuid: string; message: string }): WebSocketMessageRequestedEvent {
|
||||
return {
|
||||
type: 'WEB_SOCKET_MESSAGE_REQUESTED',
|
||||
createdAt: this.timer.getUTCDate(),
|
||||
meta: {
|
||||
correlation: {
|
||||
userIdentifier: dto.userUuid,
|
||||
userIdentifierType: 'uuid',
|
||||
},
|
||||
origin: DomainEventService.SyncingServer,
|
||||
},
|
||||
payload: dto,
|
||||
}
|
||||
}
|
||||
|
||||
createTransitionStatusUpdatedEvent(dto: {
|
||||
userUuid: string
|
||||
transitionType: 'items' | 'revisions'
|
||||
|
||||
@@ -3,11 +3,24 @@ import {
|
||||
EmailRequestedEvent,
|
||||
ItemDumpedEvent,
|
||||
ItemRevisionCreationRequestedEvent,
|
||||
NotificationAddedForUserEvent,
|
||||
RevisionsCopyRequestedEvent,
|
||||
TransitionStatusUpdatedEvent,
|
||||
WebSocketMessageRequestedEvent,
|
||||
} from '@standardnotes/domain-events'
|
||||
|
||||
export interface DomainEventFactoryInterface {
|
||||
createWebSocketMessageRequestedEvent(dto: { userUuid: string; message: string }): WebSocketMessageRequestedEvent
|
||||
createNotificationAddedForUserEvent(dto: {
|
||||
notification: {
|
||||
uuid: string
|
||||
user_uuid: string
|
||||
type: string
|
||||
payload: string
|
||||
created_at_timestamp: number
|
||||
updated_at_timestamp: number
|
||||
}
|
||||
}): NotificationAddedForUserEvent
|
||||
createTransitionStatusUpdatedEvent(dto: {
|
||||
userUuid: string
|
||||
transitionType: 'items' | 'revisions'
|
||||
|
||||
@@ -4,13 +4,21 @@ import { NotificationPayload, NotificationType, Result, Uuid } from '@standardno
|
||||
import { NotificationRepositoryInterface } from '../../../Notifications/NotificationRepositoryInterface'
|
||||
import { Notification } from '../../../Notifications/Notification'
|
||||
import { AddNotificationForUser } from './AddNotificationForUser'
|
||||
import { DomainEventFactoryInterface } from '../../../Event/DomainEventFactoryInterface'
|
||||
import { SendEventToClient } from '../../Syncing/SendEventToClient/SendEventToClient'
|
||||
import { NotificationAddedForUserEvent } from '@standardnotes/domain-events'
|
||||
import { Logger } from 'winston'
|
||||
|
||||
describe('AddNotificationForUser', () => {
|
||||
let notificationRepository: NotificationRepositoryInterface
|
||||
let timer: TimerInterface
|
||||
let payload: NotificationPayload
|
||||
let domainEventFactory: DomainEventFactoryInterface
|
||||
let sendEventToClientUseCase: SendEventToClient
|
||||
let logger: Logger
|
||||
|
||||
const createUseCase = () => new AddNotificationForUser(notificationRepository, timer)
|
||||
const createUseCase = () =>
|
||||
new AddNotificationForUser(notificationRepository, timer, domainEventFactory, sendEventToClientUseCase, logger)
|
||||
|
||||
beforeEach(() => {
|
||||
notificationRepository = {} as jest.Mocked<NotificationRepositoryInterface>
|
||||
@@ -24,6 +32,17 @@ describe('AddNotificationForUser', () => {
|
||||
type: NotificationType.create(NotificationType.TYPES.RemovedFromSharedVault).getValue(),
|
||||
version: '1.0',
|
||||
}).getValue()
|
||||
|
||||
domainEventFactory = {} as jest.Mocked<DomainEventFactoryInterface>
|
||||
domainEventFactory.createNotificationAddedForUserEvent = jest.fn().mockReturnValue({
|
||||
type: 'NOTIFICATION_ADDED_FOR_USER',
|
||||
} as jest.Mocked<NotificationAddedForUserEvent>)
|
||||
|
||||
sendEventToClientUseCase = {} as jest.Mocked<SendEventToClient>
|
||||
sendEventToClientUseCase.execute = jest.fn().mockReturnValue(Result.ok())
|
||||
|
||||
logger = {} as jest.Mocked<Logger>
|
||||
logger.error = jest.fn()
|
||||
})
|
||||
|
||||
it('should save notification', async () => {
|
||||
@@ -84,4 +103,20 @@ describe('AddNotificationForUser', () => {
|
||||
|
||||
mock.mockRestore()
|
||||
})
|
||||
|
||||
it('should log error if event could not be sent to client', async () => {
|
||||
sendEventToClientUseCase.execute = jest.fn().mockReturnValue(Result.fail('Oops'))
|
||||
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: '0e8c3c7e-3f1a-4f7a-9b5a-5b2b0a7d4b1e',
|
||||
type: NotificationType.TYPES.RemovedFromSharedVault,
|
||||
payload,
|
||||
version: '1.0',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBeFalsy()
|
||||
expect(logger.error).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -4,9 +4,18 @@ import { TimerInterface } from '@standardnotes/time'
|
||||
import { AddNotificationForUserDTO } from './AddNotificationForUserDTO'
|
||||
import { NotificationRepositoryInterface } from '../../../Notifications/NotificationRepositoryInterface'
|
||||
import { Notification } from '../../../Notifications/Notification'
|
||||
import { SendEventToClient } from '../../Syncing/SendEventToClient/SendEventToClient'
|
||||
import { DomainEventFactoryInterface } from '../../../Event/DomainEventFactoryInterface'
|
||||
import { Logger } from 'winston'
|
||||
|
||||
export class AddNotificationForUser implements UseCaseInterface<Notification> {
|
||||
constructor(private notificationRepository: NotificationRepositoryInterface, private timer: TimerInterface) {}
|
||||
constructor(
|
||||
private notificationRepository: NotificationRepositoryInterface,
|
||||
private timer: TimerInterface,
|
||||
private domainEventFactory: DomainEventFactoryInterface,
|
||||
private sendEventToClientUseCase: SendEventToClient,
|
||||
private logger: Logger,
|
||||
) {}
|
||||
|
||||
async execute(dto: AddNotificationForUserDTO): Promise<Result<Notification>> {
|
||||
const userUuidOrError = Uuid.create(dto.userUuid)
|
||||
@@ -37,6 +46,27 @@ export class AddNotificationForUser implements UseCaseInterface<Notification> {
|
||||
|
||||
await this.notificationRepository.save(notification)
|
||||
|
||||
const event = this.domainEventFactory.createNotificationAddedForUserEvent({
|
||||
notification: {
|
||||
uuid: notification.id.toString(),
|
||||
user_uuid: notification.props.userUuid.value,
|
||||
type: notification.props.type.value,
|
||||
payload: notification.props.payload.toString(),
|
||||
created_at_timestamp: notification.props.timestamps.createdAt,
|
||||
updated_at_timestamp: notification.props.timestamps.updatedAt,
|
||||
},
|
||||
})
|
||||
|
||||
const result = await this.sendEventToClientUseCase.execute({
|
||||
userUuid: userUuid.value,
|
||||
event,
|
||||
})
|
||||
if (result.isFailed()) {
|
||||
this.logger.error(
|
||||
`Failed to send notification added event to client for user ${userUuid.value}: ${result.getError()}`,
|
||||
)
|
||||
}
|
||||
|
||||
return Result.ok(notification)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import {
|
||||
DomainEventInterface,
|
||||
DomainEventPublisherInterface,
|
||||
WebSocketMessageRequestedEvent,
|
||||
} from '@standardnotes/domain-events'
|
||||
import { DomainEventFactoryInterface } from '../../../Event/DomainEventFactoryInterface'
|
||||
import { SendEventToClient } from './SendEventToClient'
|
||||
|
||||
describe('SendEventToClient', () => {
|
||||
let domainEventFactory: DomainEventFactoryInterface
|
||||
let domainEventPublisher: DomainEventPublisherInterface
|
||||
|
||||
const createUseCase = () => new SendEventToClient(domainEventFactory, domainEventPublisher)
|
||||
|
||||
beforeEach(() => {
|
||||
domainEventFactory = {} as jest.Mocked<DomainEventFactoryInterface>
|
||||
domainEventFactory.createWebSocketMessageRequestedEvent = jest
|
||||
.fn()
|
||||
.mockReturnValue({} as jest.Mocked<WebSocketMessageRequestedEvent>)
|
||||
|
||||
domainEventPublisher = {} as jest.Mocked<DomainEventPublisherInterface>
|
||||
domainEventPublisher.publish = jest.fn()
|
||||
})
|
||||
|
||||
it('should publish a WebSocketMessageRequestedEvent', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
await useCase.execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
event: {
|
||||
type: 'test',
|
||||
} as jest.Mocked<DomainEventInterface>,
|
||||
})
|
||||
|
||||
expect(domainEventFactory.createWebSocketMessageRequestedEvent).toHaveBeenCalledWith({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
message: JSON.stringify({
|
||||
type: 'test',
|
||||
}),
|
||||
})
|
||||
expect(domainEventPublisher.publish).toHaveBeenCalledWith({} as jest.Mocked<WebSocketMessageRequestedEvent>)
|
||||
})
|
||||
|
||||
it('should return a failed result if user uuid is invalid', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: 'invalid',
|
||||
event: {
|
||||
type: 'test',
|
||||
} as jest.Mocked<DomainEventInterface>,
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,29 @@
|
||||
import { Result, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
|
||||
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
|
||||
|
||||
import { SendEventToClientDTO } from './SendEventToClientDTO'
|
||||
import { DomainEventFactoryInterface } from '../../../Event/DomainEventFactoryInterface'
|
||||
|
||||
export class SendEventToClient implements UseCaseInterface<void> {
|
||||
constructor(
|
||||
private domainEventFactory: DomainEventFactoryInterface,
|
||||
private domainEventPublisher: DomainEventPublisherInterface,
|
||||
) {}
|
||||
|
||||
async execute(dto: SendEventToClientDTO): Promise<Result<void>> {
|
||||
const userUuidOrError = Uuid.create(dto.userUuid)
|
||||
if (userUuidOrError.isFailed()) {
|
||||
return Result.fail(userUuidOrError.getError())
|
||||
}
|
||||
const userUuid = userUuidOrError.getValue()
|
||||
|
||||
const event = this.domainEventFactory.createWebSocketMessageRequestedEvent({
|
||||
userUuid: userUuid.value,
|
||||
message: JSON.stringify(dto.event),
|
||||
})
|
||||
|
||||
await this.domainEventPublisher.publish(event)
|
||||
|
||||
return Result.ok()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { DomainEventInterface } from '@standardnotes/domain-events'
|
||||
|
||||
export interface SendEventToClientDTO {
|
||||
userUuid: string
|
||||
event: DomainEventInterface
|
||||
}
|
||||
@@ -117,6 +117,7 @@ describe('TransitionItemsFromPrimaryToSecondaryDatabaseForUser', () => {
|
||||
logger.info = jest.fn()
|
||||
|
||||
timer = {} as jest.Mocked<TimerInterface>
|
||||
timer.sleep = jest.fn()
|
||||
timer.getTimestampInMicroseconds = jest.fn().mockReturnValue(123)
|
||||
timer.convertMicrosecondsToTimeStructure = jest.fn().mockReturnValue({
|
||||
days: 0,
|
||||
|
||||
@@ -39,6 +39,8 @@ export class TransitionItemsFromPrimaryToSecondaryDatabaseForUser implements Use
|
||||
return Result.fail(migrationResult.getError())
|
||||
}
|
||||
|
||||
await this.allowForSecondaryDatabaseToCatchUp()
|
||||
|
||||
const integrityCheckResult = await this.checkIntegrityBetweenPrimaryAndSecondaryDatabase(userUuid)
|
||||
if (integrityCheckResult.isFailed()) {
|
||||
const cleanupResult = await this.deleteItemsForUser(userUuid, this.secondaryItemRepository)
|
||||
@@ -70,6 +72,11 @@ export class TransitionItemsFromPrimaryToSecondaryDatabaseForUser implements Use
|
||||
return Result.ok()
|
||||
}
|
||||
|
||||
private async allowForSecondaryDatabaseToCatchUp(): Promise<void> {
|
||||
const twoSecondsInMilliseconds = 2_000
|
||||
await this.timer.sleep(twoSecondsInMilliseconds)
|
||||
}
|
||||
|
||||
private async migrateItemsForUser(userUuid: Uuid): Promise<Result<void>> {
|
||||
try {
|
||||
const totalItemsCountForUser = await this.primaryItemRepository.countAll({ userUuid: userUuid.value })
|
||||
|
||||
@@ -18,7 +18,7 @@ export class MongoDBItemRepository implements ItemRepositoryInterface {
|
||||
) {}
|
||||
|
||||
async deleteByUserUuid(userUuid: string): Promise<void> {
|
||||
await this.mongoRepository.deleteMany({ where: { userUuid } })
|
||||
await this.mongoRepository.deleteMany({ userUuid })
|
||||
}
|
||||
|
||||
async findAll(query: ItemQuery): Promise<Item[]> {
|
||||
@@ -136,7 +136,7 @@ export class MongoDBItemRepository implements ItemRepositoryInterface {
|
||||
}
|
||||
|
||||
async remove(item: Item): Promise<void> {
|
||||
await this.mongoRepository.deleteOne({ where: { _id: { $eq: BSON.UUID.createFromHexString(item.uuid.value) } } })
|
||||
await this.mongoRepository.deleteOne({ _id: { $eq: BSON.UUID.createFromHexString(item.uuid.value) } })
|
||||
}
|
||||
|
||||
async save(item: Item): Promise<void> {
|
||||
@@ -189,9 +189,7 @@ export class MongoDBItemRepository implements ItemRepositoryInterface {
|
||||
}
|
||||
}
|
||||
if (query.deleted !== undefined) {
|
||||
const deletedMixedValues = query.deleted === true ? [true, 1] : [false, 0]
|
||||
|
||||
options.where = { ...options.where, deleted: { $in: deletedMixedValues } }
|
||||
options.where = { ...options.where, deleted: { $eq: query.deleted } }
|
||||
}
|
||||
if (query.contentType) {
|
||||
if (Array.isArray(query.contentType)) {
|
||||
|
||||
@@ -99,7 +99,7 @@ export class SQLItemPersistenceMapper implements MapperInterface<Item, SQLItem>
|
||||
encItemKey: projection.encItemKey,
|
||||
authHash: projection.authHash,
|
||||
userUuid,
|
||||
deleted: projection.deleted,
|
||||
deleted: !!projection.deleted,
|
||||
dates,
|
||||
timestamps,
|
||||
updatedWithSession,
|
||||
@@ -127,7 +127,7 @@ export class SQLItemPersistenceMapper implements MapperInterface<Item, SQLItem>
|
||||
typeorm.encItemKey = domain.props.encItemKey
|
||||
typeorm.authHash = domain.props.authHash
|
||||
typeorm.userUuid = domain.props.userUuid.value
|
||||
typeorm.deleted = domain.props.deleted
|
||||
typeorm.deleted = !!domain.props.deleted
|
||||
typeorm.createdAt = domain.props.dates.createdAt
|
||||
typeorm.updatedAt = domain.props.dates.updatedAt
|
||||
typeorm.createdAtTimestamp = domain.props.timestamps.createdAt
|
||||
|
||||
@@ -64,7 +64,7 @@ export class SQLLegacyItemPersistenceMapper implements MapperInterface<Item, SQL
|
||||
encItemKey: projection.encItemKey,
|
||||
authHash: projection.authHash,
|
||||
userUuid,
|
||||
deleted: projection.deleted,
|
||||
deleted: !!projection.deleted,
|
||||
dates,
|
||||
timestamps,
|
||||
updatedWithSession,
|
||||
@@ -90,7 +90,7 @@ export class SQLLegacyItemPersistenceMapper implements MapperInterface<Item, SQL
|
||||
typeorm.encItemKey = domain.props.encItemKey
|
||||
typeorm.authHash = domain.props.authHash
|
||||
typeorm.userUuid = domain.props.userUuid.value
|
||||
typeorm.deleted = domain.props.deleted
|
||||
typeorm.deleted = !!domain.props.deleted
|
||||
typeorm.createdAt = domain.props.dates.createdAt
|
||||
typeorm.updatedAt = domain.props.dates.updatedAt
|
||||
typeorm.createdAtTimestamp = domain.props.timestamps.createdAt
|
||||
|
||||
@@ -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.15.1](https://github.com/standardnotes/server/compare/@standardnotes/time@1.15.0...@standardnotes/time@1.15.1) (2023-08-31)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* transitionining revisions ([#801](https://github.com/standardnotes/server/issues/801)) ([596a0f1](https://github.com/standardnotes/server/commit/596a0f1a0221ab0636c4c04d17a28c57fe74b620))
|
||||
|
||||
# [1.15.0](https://github.com/standardnotes/server/compare/@standardnotes/time@1.14.6...@standardnotes/time@1.15.0) (2023-08-24)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/time",
|
||||
"version": "1.15.0",
|
||||
"version": "1.15.1",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -10,6 +10,11 @@ export class Timer implements TimerInterface {
|
||||
dayjs.extend(utc)
|
||||
}
|
||||
|
||||
/* istanbul ignore next */
|
||||
async sleep(milliseconds: number): Promise<void> {
|
||||
return new Promise((resolve) => setTimeout(resolve, milliseconds))
|
||||
}
|
||||
|
||||
getUTCDateNSecondsAhead(n: number): Date {
|
||||
return dayjs.utc().add(n, 'second').toDate()
|
||||
}
|
||||
|
||||
@@ -23,4 +23,5 @@ export interface TimerInterface {
|
||||
convertMicrosecondsToTimeStructure(microseconds: number): TimeStructure
|
||||
formatDate(date: Date, format: string): string
|
||||
dateWasNDaysAgo(date: Date): number
|
||||
sleep(milliseconds: number): Promise<void>
|
||||
}
|
||||
|
||||
@@ -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.24](https://github.com/standardnotes/server/compare/@standardnotes/websockets-server@1.10.23...@standardnotes/websockets-server@1.10.24) (2023-08-31)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/websockets-server
|
||||
|
||||
## [1.10.23](https://github.com/standardnotes/server/compare/@standardnotes/websockets-server@1.10.22...@standardnotes/websockets-server@1.10.23) (2023-08-30)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/websockets-server
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/websockets-server",
|
||||
"version": "1.10.23",
|
||||
"version": "1.10.24",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user