mirror of
https://github.com/standardnotes/server
synced 2026-01-28 20:01:14 -05:00
Compare commits
27 Commits
@standardn
...
@standardn
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
414b090efb | ||
|
|
41e2136bc0 | ||
|
|
378ecedfcc | ||
|
|
06d4200909 | ||
|
|
22a8cc90f1 | ||
|
|
5cf84e31b0 | ||
|
|
31e7aaf253 | ||
|
|
8ec3d37c18 | ||
|
|
857c6af946 | ||
|
|
de081fe786 | ||
|
|
0aeeb2d1cf | ||
|
|
e589029722 | ||
|
|
b265a39b63 | ||
|
|
ed5cfd86db | ||
|
|
a1a3e9f586 | ||
|
|
a40b17b141 | ||
|
|
18181ed9df | ||
|
|
0ae028db73 | ||
|
|
79971be672 | ||
|
|
e4fcd738c3 | ||
|
|
6827e5e218 | ||
|
|
b91cd7e232 | ||
|
|
735d89cdaa | ||
|
|
e8db412bc2 | ||
|
|
bf855bb26e | ||
|
|
41cd377145 | ||
|
|
d798864caf |
15
.github/workflows/common-docker-image.yml
vendored
15
.github/workflows/common-docker-image.yml
vendored
@@ -33,17 +33,12 @@ jobs:
|
||||
with:
|
||||
python-version: '3.11'
|
||||
|
||||
- name: Create Bundle Dir
|
||||
id: bundle-dir
|
||||
run: echo "temp_dir=$(mktemp -d -t ${{ inputs.service_name }}-${{ github.sha }}-XXXXXXX)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Cache build
|
||||
id: cache-build
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
packages/**/dist
|
||||
${{ steps.bundle-dir.outputs.temp_dir }}
|
||||
key: ${{ runner.os }}-${{ inputs.service_name }}-build-${{ github.sha }}
|
||||
|
||||
- name: Set up Node
|
||||
@@ -57,11 +52,7 @@ jobs:
|
||||
|
||||
- name: Build
|
||||
if: steps.cache-build.outputs.cache-hit != 'true'
|
||||
run: yarn build ${{ inputs.package_path }}
|
||||
|
||||
- name: Bundle
|
||||
if: steps.cache-build.outputs.cache-hit != 'true'
|
||||
run: yarn workspace ${{ inputs.workspace_name }} bundle --no-compress --output-directory ${{ steps.bundle-dir.outputs.temp_dir }}
|
||||
run: yarn build
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
@@ -93,8 +84,8 @@ jobs:
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
builder: ${{ steps.buildx.outputs.name }}
|
||||
context: ${{ steps.bundle-dir.outputs.temp_dir }}
|
||||
file: ${{ steps.bundle-dir.outputs.temp_dir }}/${{ inputs.package_path }}/Dockerfile
|
||||
context: .
|
||||
file: ${{ inputs.package_path }}/Dockerfile
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: |
|
||||
|
||||
@@ -20,8 +20,8 @@
|
||||
"release": "lerna version --conventional-graduate --conventional-commits --yes -m \"chore(release): publish new version\"",
|
||||
"publish": "lerna publish from-git --yes --no-verify-access --loglevel verbose",
|
||||
"postversion": "./scripts/push-tags-one-by-one.sh",
|
||||
"e2e": "yarn build packages/home-server && PORT=3123 yarn workspace @standardnotes/home-server start",
|
||||
"start": "yarn build packages/home-server && yarn workspace @standardnotes/home-server start"
|
||||
"e2e": "yarn workspace @standardnotes/home-server run build && PORT=3123 yarn workspace @standardnotes/home-server start",
|
||||
"start": "yarn workspace @standardnotes/home-server run build && yarn workspace @standardnotes/home-server start"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "^17.0.2",
|
||||
|
||||
@@ -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.
|
||||
|
||||
## [2.26.19](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.26.18...@standardnotes/analytics@2.26.19) (2023-09-20)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/analytics
|
||||
|
||||
## [2.26.18](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.26.17...@standardnotes/analytics@2.26.18) (2023-09-20)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/analytics
|
||||
|
||||
## [2.26.17](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.26.16...@standardnotes/analytics@2.26.17) (2023-09-19)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/analytics
|
||||
|
||||
## [2.26.16](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.26.15...@standardnotes/analytics@2.26.16) (2023-09-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/analytics
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/analytics",
|
||||
"version": "2.26.16",
|
||||
"version": "2.26.19",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -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.74.17](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.74.16...@standardnotes/api-gateway@1.74.17) (2023-09-20)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||
|
||||
## [1.74.16](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.74.15...@standardnotes/api-gateway@1.74.16) (2023-09-20)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||
|
||||
## [1.74.15](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.74.14...@standardnotes/api-gateway@1.74.15) (2023-09-19)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||
|
||||
## [1.74.14](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.74.13...@standardnotes/api-gateway@1.74.14) (2023-09-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/api-gateway",
|
||||
"version": "1.74.14",
|
||||
"version": "1.74.17",
|
||||
"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.143.9](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.143.8...@standardnotes/auth-server@1.143.9) (2023-09-20)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/auth-server
|
||||
|
||||
## [1.143.8](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.143.7...@standardnotes/auth-server@1.143.8) (2023-09-20)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/auth-server
|
||||
|
||||
## [1.143.7](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.143.6...@standardnotes/auth-server@1.143.7) (2023-09-19)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/auth-server
|
||||
|
||||
## [1.143.6](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.143.4...@standardnotes/auth-server@1.143.6) (2023-09-19)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** bump version ([b91cd7e](https://github.com/standardnotes/server/commit/b91cd7e2329d68026665f61d9bd17ba918c57563))
|
||||
|
||||
## [1.143.4](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.143.3...@standardnotes/auth-server@1.143.4) (2023-09-19)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** add debug logs to transition ([bf855bb](https://github.com/standardnotes/server/commit/bf855bb26e8a3618113bd2f5801c260782566815))
|
||||
|
||||
## [1.143.3](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.143.2...@standardnotes/auth-server@1.143.3) (2023-09-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/auth-server
|
||||
|
||||
@@ -53,7 +53,10 @@ const requestTransition = async (
|
||||
continue
|
||||
}
|
||||
|
||||
let wasTransitionRequested = false
|
||||
|
||||
if (itemsTransitionStatus?.value !== TransitionStatus.STATUSES.Verified) {
|
||||
wasTransitionRequested = true
|
||||
await transitionStatusRepository.remove(user.uuid, 'items')
|
||||
|
||||
await domainEventPublisher.publish(
|
||||
@@ -66,6 +69,7 @@ const requestTransition = async (
|
||||
}
|
||||
|
||||
if (revisionsTransitionStatus?.value !== TransitionStatus.STATUSES.Verified) {
|
||||
wasTransitionRequested = true
|
||||
await transitionStatusRepository.remove(user.uuid, 'revisions')
|
||||
|
||||
await domainEventPublisher.publish(
|
||||
@@ -78,6 +82,12 @@ const requestTransition = async (
|
||||
}
|
||||
|
||||
usersTriggered += 1
|
||||
|
||||
if (wasTransitionRequested) {
|
||||
logger.info(
|
||||
`[TRANSITION ${timestamp}] Transition requested for user ${user.uuid} - items status: ${itemsTransitionStatus?.value}, revisions status: ${revisionsTransitionStatus?.value}, has transition role: ${userHasTransitionRole}`,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
logger.info(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/auth-server",
|
||||
"version": "1.143.3",
|
||||
"version": "1.143.9",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,24 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [1.32.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-core@1.31.0...@standardnotes/domain-core@1.32.0) (2023-09-20)
|
||||
|
||||
### Features
|
||||
|
||||
* **syncing-server:** distinct notifications upon user removal from shared vault ([#840](https://github.com/standardnotes/server/issues/840)) ([41e2136](https://github.com/standardnotes/server/commit/41e2136bc07312974701a70652528d304105e0f9))
|
||||
|
||||
# [1.31.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-core@1.30.1...@standardnotes/domain-core@1.31.0) (2023-09-20)
|
||||
|
||||
### Features
|
||||
|
||||
* **syncing-server:** add notification for user upon declined shared vault invitation ([#837](https://github.com/standardnotes/server/issues/837)) ([31e7aaf](https://github.com/standardnotes/server/commit/31e7aaf253029a951d8b943d6cffd655cd5ca765))
|
||||
|
||||
## [1.30.1](https://github.com/standardnotes/server/compare/@standardnotes/domain-core@1.30.0...@standardnotes/domain-core@1.30.1) (2023-09-19)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **domain-core:** allow any version and variant of the UUID format ([de081fe](https://github.com/standardnotes/server/commit/de081fe78658bdd36c8c5d86b70a16a2880aa3d1))
|
||||
|
||||
# [1.30.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-core@1.29.0...@standardnotes/domain-core@1.30.0) (2023-09-18)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/domain-core",
|
||||
"version": "1.30.0",
|
||||
"version": "1.32.0",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -5,6 +5,7 @@ describe('Validator', () => {
|
||||
'2221101c-1da9-4d2b-9b32-b8be2a8d1c82',
|
||||
'c08f2f29-a74b-42b4-aefd-98af9832391c',
|
||||
'b453fa64-1493-443b-b5bb-bca7b9c696c7',
|
||||
'fa7350b3-77cf-8c0c-40b2-6046b13254fe',
|
||||
]
|
||||
|
||||
const invalidUuids = [
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { Result } from './Result'
|
||||
|
||||
export class Validator {
|
||||
private static readonly UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-5][0-9a-f]{3}-[089ab][0-9a-f]{3}-[0-9a-f]{12}$/i
|
||||
private static readonly UUID_ANY_VERSION_AND_VARIANT_REGEX =
|
||||
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
|
||||
private static readonly EMAIL_REGEX =
|
||||
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
|
||||
|
||||
static isValidUuid(value: string): Result<string> {
|
||||
const matchesUuidRegex = String(value).toLowerCase().match(Validator.UUID_REGEX) !== null
|
||||
const matchesUuidRegex = String(value).toLowerCase().match(Validator.UUID_ANY_VERSION_AND_VARIANT_REGEX) !== null
|
||||
if (matchesUuidRegex) {
|
||||
return Result.ok()
|
||||
}
|
||||
|
||||
@@ -5,8 +5,10 @@ import { NotificationTypeProps } from './NotificationTypeProps'
|
||||
export class NotificationType extends ValueObject<NotificationTypeProps> {
|
||||
static readonly TYPES = {
|
||||
SharedVaultItemRemoved: 'shared_vault_item_removed',
|
||||
RemovedFromSharedVault: 'removed_from_shared_vault',
|
||||
SelfRemovedFromSharedVault: 'self_removed_from_shared_vault',
|
||||
UserRemovedFromSharedVault: 'user_removed_from_shared_vault',
|
||||
UserAddedToSharedVault: 'user_added_to_shared_vault',
|
||||
SharedVaultInviteDeclined: 'shared_vault_invite_declined',
|
||||
SharedVaultFileUploaded: 'shared_vault_file_uploaded',
|
||||
SharedVaultFileRemoved: 'shared_vault_file_removed',
|
||||
}
|
||||
|
||||
@@ -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.31](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.12.30...@standardnotes/domain-events-infra@1.12.31) (2023-09-20)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/domain-events-infra
|
||||
|
||||
## [1.12.30](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.12.29...@standardnotes/domain-events-infra@1.12.30) (2023-09-15)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/domain-events-infra
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/domain-events-infra",
|
||||
"version": "1.12.30",
|
||||
"version": "1.12.31",
|
||||
"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.128.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.127.0...@standardnotes/domain-events@2.128.0) (2023-09-20)
|
||||
|
||||
### Features
|
||||
|
||||
* add unassigning items and revisions upon shared vault removal ([#839](https://github.com/standardnotes/server/issues/839)) ([378eced](https://github.com/standardnotes/server/commit/378ecedfcc4fb23475c2329fb37479edb3b48a39))
|
||||
|
||||
# [2.127.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.126.0...@standardnotes/domain-events@2.127.0) (2023-09-15)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/domain-events",
|
||||
"version": "2.127.0",
|
||||
"version": "2.128.0",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -2,5 +2,4 @@ export interface ItemRemovedFromSharedVaultEventPayload {
|
||||
userUuid: string
|
||||
itemUuid: string
|
||||
sharedVaultUuid: string
|
||||
roleNames: string[]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import { DomainEventInterface } from './DomainEventInterface'
|
||||
import { SharedVaultRemovedEventPayload } from './SharedVaultRemovedEventPayload'
|
||||
|
||||
export interface SharedVaultRemovedEvent extends DomainEventInterface {
|
||||
type: 'SHARED_VAULT_REMOVED'
|
||||
payload: SharedVaultRemovedEventPayload
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export interface SharedVaultRemovedEventPayload {
|
||||
sharedVaultUuid: string
|
||||
}
|
||||
@@ -76,6 +76,8 @@ export * from './Event/SharedVaultFileRemovedEvent'
|
||||
export * from './Event/SharedVaultFileRemovedEventPayload'
|
||||
export * from './Event/SharedVaultFileUploadedEvent'
|
||||
export * from './Event/SharedVaultFileUploadedEventPayload'
|
||||
export * from './Event/SharedVaultRemovedEvent'
|
||||
export * from './Event/SharedVaultRemovedEventPayload'
|
||||
export * from './Event/StatisticPersistenceRequestedEvent'
|
||||
export * from './Event/StatisticPersistenceRequestedEventPayload'
|
||||
export * from './Event/SubscriptionCancelledEvent'
|
||||
|
||||
@@ -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.11.47](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.11.46...@standardnotes/event-store@1.11.47) (2023-09-20)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/event-store
|
||||
|
||||
## [1.11.46](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.11.45...@standardnotes/event-store@1.11.46) (2023-09-20)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/event-store
|
||||
|
||||
## [1.11.45](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.11.44...@standardnotes/event-store@1.11.45) (2023-09-19)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/event-store
|
||||
|
||||
## [1.11.44](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.11.43...@standardnotes/event-store@1.11.44) (2023-09-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/event-store
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/event-store",
|
||||
"version": "1.11.44",
|
||||
"version": "1.11.47",
|
||||
"description": "Event Store Service",
|
||||
"private": true,
|
||||
"main": "dist/src/index.js",
|
||||
|
||||
@@ -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.22.26](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.22.25...@standardnotes/files-server@1.22.26) (2023-09-20)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files-server
|
||||
|
||||
## [1.22.25](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.22.24...@standardnotes/files-server@1.22.25) (2023-09-20)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files-server
|
||||
|
||||
## [1.22.24](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.22.23...@standardnotes/files-server@1.22.24) (2023-09-19)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files-server
|
||||
|
||||
## [1.22.23](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.22.22...@standardnotes/files-server@1.22.23) (2023-09-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files-server
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/files-server",
|
||||
"version": "1.22.23",
|
||||
"version": "1.22.26",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,46 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.15.71](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.70...@standardnotes/home-server@1.15.71) (2023-09-20)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.15.70](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.69...@standardnotes/home-server@1.15.70) (2023-09-20)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.15.69](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.68...@standardnotes/home-server@1.15.69) (2023-09-20)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.15.68](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.67...@standardnotes/home-server@1.15.68) (2023-09-19)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.15.67](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.66...@standardnotes/home-server@1.15.67) (2023-09-19)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.15.66](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.65...@standardnotes/home-server@1.15.66) (2023-09-19)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.15.65](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.64...@standardnotes/home-server@1.15.65) (2023-09-19)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.15.64](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.63...@standardnotes/home-server@1.15.64) (2023-09-19)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.15.63](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.62...@standardnotes/home-server@1.15.63) (2023-09-19)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.15.62](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.61...@standardnotes/home-server@1.15.62) (2023-09-19)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.15.61](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.60...@standardnotes/home-server@1.15.61) (2023-09-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/home-server",
|
||||
"version": "1.15.61",
|
||||
"version": "1.15.71",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,48 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [1.36.0](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.35.8...@standardnotes/revisions-server@1.36.0) (2023-09-20)
|
||||
|
||||
### Features
|
||||
|
||||
* add unassigning items and revisions upon shared vault removal ([#839](https://github.com/standardnotes/server/issues/839)) ([378eced](https://github.com/standardnotes/server/commit/378ecedfcc4fb23475c2329fb37479edb3b48a39))
|
||||
|
||||
## [1.35.8](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.35.7...@standardnotes/revisions-server@1.35.8) (2023-09-20)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/revisions-server
|
||||
|
||||
## [1.35.7](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.35.6...@standardnotes/revisions-server@1.35.7) (2023-09-19)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* skip removing already existing content in secondary to pick up where the transition left of ([857c6af](https://github.com/standardnotes/server/commit/857c6af9468ec829ff4dce9a96ba7bf9c14d55a5))
|
||||
|
||||
## [1.35.6](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.35.5...@standardnotes/revisions-server@1.35.6) (2023-09-19)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* increase timeout for secondary database to catch up for indexes to be rebuilt ([b265a39](https://github.com/standardnotes/server/commit/b265a39b635373c36ee8c3d8e09f0631159b3574))
|
||||
* logs verbosity during transitions ([e589029](https://github.com/standardnotes/server/commit/e589029722ab9f4debc8aa6cc78913f877eda2e3))
|
||||
|
||||
## [1.35.5](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.35.4...@standardnotes/revisions-server@1.35.5) (2023-09-19)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add checking for secondary items logs ([a1a3e9f](https://github.com/standardnotes/server/commit/a1a3e9f586358b943b1b490a1382e42f081f7d06))
|
||||
* logs for removing already existing content and paging through diff of the content ([a40b17b](https://github.com/standardnotes/server/commit/a40b17b141f1d5954e1a45b969d5a941386c68d0))
|
||||
|
||||
## [1.35.4](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.35.3...@standardnotes/revisions-server@1.35.4) (2023-09-19)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* logs formatting during transition for better readability ([0ae028d](https://github.com/standardnotes/server/commit/0ae028db739decec8c50321b18b0af515e00bd23))
|
||||
|
||||
## [1.35.3](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.35.2...@standardnotes/revisions-server@1.35.3) (2023-09-19)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **syncing-server:** paging through already existing items ([e4fcd73](https://github.com/standardnotes/server/commit/e4fcd738c35a4dc96e57db6ca08383a5647d61ad))
|
||||
|
||||
## [1.35.2](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.35.1...@standardnotes/revisions-server@1.35.2) (2023-09-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/revisions-server
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/revisions-server",
|
||||
"version": "1.35.2",
|
||||
"version": "1.36.0",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -67,6 +67,7 @@ import { SQLRevisionPersistenceMapper } from '../Mapping/Persistence/SQL/SQLRevi
|
||||
import { RemoveRevisionsFromSharedVault } from '../Domain/UseCase/RemoveRevisionsFromSharedVault/RemoveRevisionsFromSharedVault'
|
||||
import { ItemRemovedFromSharedVaultEventHandler } from '../Domain/Handler/ItemRemovedFromSharedVaultEventHandler'
|
||||
import { TransitionRequestedEventHandler } from '../Domain/Handler/TransitionRequestedEventHandler'
|
||||
import { SharedVaultRemovedEventHandler } from '../Domain/Handler/SharedVaultRemovedEventHandler'
|
||||
|
||||
export class ContainerConfigLoader {
|
||||
constructor(private mode: 'server' | 'worker' = 'server') {}
|
||||
@@ -356,7 +357,9 @@ export class ContainerConfigLoader {
|
||||
.bind<RemoveRevisionsFromSharedVault>(TYPES.Revisions_RemoveRevisionsFromSharedVault)
|
||||
.toConstantValue(
|
||||
new RemoveRevisionsFromSharedVault(
|
||||
container.get<RevisionRepositoryResolverInterface>(TYPES.Revisions_RevisionRepositoryResolver),
|
||||
isSecondaryDatabaseEnabled
|
||||
? container.get<RevisionRepositoryInterface>(TYPES.Revisions_MongoDBRevisionRepository)
|
||||
: container.get<RevisionRepositoryInterface>(TYPES.Revisions_SQLRevisionRepository),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -448,6 +451,14 @@ export class ContainerConfigLoader {
|
||||
container.get<winston.Logger>(TYPES.Revisions_Logger),
|
||||
),
|
||||
)
|
||||
container
|
||||
.bind<SharedVaultRemovedEventHandler>(TYPES.Revisions_SharedVaultRemovedEventHandler)
|
||||
.toConstantValue(
|
||||
new SharedVaultRemovedEventHandler(
|
||||
container.get<RemoveRevisionsFromSharedVault>(TYPES.Revisions_RemoveRevisionsFromSharedVault),
|
||||
container.get<winston.Logger>(TYPES.Revisions_Logger),
|
||||
),
|
||||
)
|
||||
|
||||
const eventHandlers: Map<string, DomainEventHandlerInterface> = new Map([
|
||||
['ITEM_DUMPED', container.get(TYPES.Revisions_ItemDumpedEventHandler)],
|
||||
@@ -455,6 +466,7 @@ export class ContainerConfigLoader {
|
||||
['REVISIONS_COPY_REQUESTED', container.get(TYPES.Revisions_RevisionsCopyRequestedEventHandler)],
|
||||
['ITEM_REMOVED_FROM_SHARED_VAULT', container.get(TYPES.Revisions_ItemRemovedFromSharedVaultEventHandler)],
|
||||
['TRANSITION_REQUESTED', container.get(TYPES.Revisions_TransitionRequestedEventHandler)],
|
||||
['SHARED_VAULT_REMOVED', container.get(TYPES.Revisions_SharedVaultRemovedEventHandler)],
|
||||
])
|
||||
|
||||
if (isConfiguredForHomeServer) {
|
||||
|
||||
@@ -57,6 +57,7 @@ const TYPES = {
|
||||
Revisions_RevisionsCopyRequestedEventHandler: Symbol.for('Revisions_RevisionsCopyRequestedEventHandler'),
|
||||
Revisions_ItemRemovedFromSharedVaultEventHandler: Symbol.for('Revisions_ItemRemovedFromSharedVaultEventHandler'),
|
||||
Revisions_TransitionRequestedEventHandler: Symbol.for('Revisions_TransitionRequestedEventHandler'),
|
||||
Revisions_SharedVaultRemovedEventHandler: Symbol.for('Revisions_SharedVaultRemovedEventHandler'),
|
||||
// Services
|
||||
Revisions_CrossServiceTokenDecoder: Symbol.for('Revisions_CrossServiceTokenDecoder'),
|
||||
Revisions_DomainEventSubscriberFactory: Symbol.for('Revisions_DomainEventSubscriberFactory'),
|
||||
|
||||
@@ -9,10 +9,15 @@ export class ItemRemovedFromSharedVaultEventHandler implements DomainEventHandle
|
||||
) {}
|
||||
|
||||
async handle(event: ItemRemovedFromSharedVaultEvent): Promise<void> {
|
||||
if (!event.payload.itemUuid) {
|
||||
this.logger.error('ItemRemovedFromSharedVaultEvent is missing itemUuid')
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
const result = await this.removeRevisionsFromSharedVault.execute({
|
||||
sharedVaultUuid: event.payload.sharedVaultUuid,
|
||||
itemUuid: event.payload.itemUuid,
|
||||
roleNames: event.payload.roleNames,
|
||||
})
|
||||
|
||||
if (result.isFailed()) {
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import { DomainEventHandlerInterface, SharedVaultRemovedEvent } from '@standardnotes/domain-events'
|
||||
import { Logger } from 'winston'
|
||||
|
||||
import { RemoveRevisionsFromSharedVault } from '../UseCase/RemoveRevisionsFromSharedVault/RemoveRevisionsFromSharedVault'
|
||||
|
||||
export class SharedVaultRemovedEventHandler implements DomainEventHandlerInterface {
|
||||
constructor(
|
||||
private removeRevisionsFromSharedVault: RemoveRevisionsFromSharedVault,
|
||||
private logger: Logger,
|
||||
) {}
|
||||
|
||||
async handle(event: SharedVaultRemovedEvent): Promise<void> {
|
||||
const result = await this.removeRevisionsFromSharedVault.execute({
|
||||
sharedVaultUuid: event.payload.sharedVaultUuid,
|
||||
})
|
||||
|
||||
if (result.isFailed()) {
|
||||
this.logger.error(`Failed to remove revisions from shared vault: ${result.getError()}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,7 @@ export class TransitionRequestedEventHandler implements DomainEventHandlerInterf
|
||||
}
|
||||
|
||||
if (await this.isAlreadyMigrated(userUuid)) {
|
||||
this.logger.info(`User ${event.payload.userUuid} already migrated.`)
|
||||
this.logger.info(`[${event.payload.userUuid}] User already migrated.`)
|
||||
|
||||
await this.domainEventPublisher.publish(
|
||||
this.domainEventFactory.createTransitionStatusUpdatedEvent({
|
||||
@@ -43,7 +43,7 @@ export class TransitionRequestedEventHandler implements DomainEventHandlerInterf
|
||||
return
|
||||
}
|
||||
|
||||
this.logger.info(`Handling transition requested event for user ${event.payload.userUuid}`)
|
||||
this.logger.info(`[${event.payload.userUuid}] Handling transition requested event`)
|
||||
|
||||
await this.domainEventPublisher.publish(
|
||||
this.domainEventFactory.createTransitionStatusUpdatedEvent({
|
||||
@@ -59,7 +59,7 @@ export class TransitionRequestedEventHandler implements DomainEventHandlerInterf
|
||||
})
|
||||
|
||||
if (result.isFailed()) {
|
||||
this.logger.error(`Failed to transition for user ${event.payload.userUuid}`)
|
||||
this.logger.error(`[${event.payload.userUuid}] Failed to transition: ${result.getError()}`)
|
||||
|
||||
await this.domainEventPublisher.publish(
|
||||
this.domainEventFactory.createTransitionStatusUpdatedEvent({
|
||||
@@ -88,7 +88,7 @@ export class TransitionRequestedEventHandler implements DomainEventHandlerInterf
|
||||
|
||||
if (totalRevisionsCountForUserInPrimary > 0) {
|
||||
this.logger.info(
|
||||
`User ${userUuid.value} has ${totalRevisionsCountForUserInPrimary} revisions in primary database.`,
|
||||
`[${userUuid.value}] User has ${totalRevisionsCountForUserInPrimary} revisions in primary database.`,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -98,9 +98,7 @@ export class TransitionRequestedEventHandler implements DomainEventHandlerInterf
|
||||
private async getUserUuidFromEvent(event: TransitionRequestedEvent): Promise<Uuid | null> {
|
||||
const userUuidOrError = Uuid.create(event.payload.userUuid)
|
||||
if (userUuidOrError.isFailed()) {
|
||||
this.logger.error(
|
||||
`Failed to transition revisions for user ${event.payload.userUuid}: ${userUuidOrError.getError()}`,
|
||||
)
|
||||
this.logger.error(`[${event.payload.userUuid}] Failed to transition revisions: ${userUuidOrError.getError()}`)
|
||||
await this.domainEventPublisher.publish(
|
||||
this.domainEventFactory.createTransitionStatusUpdatedEvent({
|
||||
userUuid: event.payload.userUuid,
|
||||
|
||||
@@ -13,5 +13,5 @@ export interface RevisionRepositoryInterface {
|
||||
updateUserUuid(itemUuid: Uuid, userUuid: Uuid): Promise<void>
|
||||
findByUserUuid(dto: { userUuid: Uuid; offset?: number; limit?: number }): Promise<Array<Revision>>
|
||||
insert(revision: Revision): Promise<boolean>
|
||||
clearSharedVaultAndKeySystemAssociations(itemUuid: Uuid, sharedVaultUuid: Uuid): Promise<void>
|
||||
clearSharedVaultAndKeySystemAssociations(dto: { itemUuid?: Uuid; sharedVaultUuid: Uuid }): Promise<void>
|
||||
}
|
||||
|
||||
@@ -1,19 +1,14 @@
|
||||
import { RevisionRepositoryInterface } from '../../Revision/RevisionRepositoryInterface'
|
||||
import { RevisionRepositoryResolverInterface } from '../../Revision/RevisionRepositoryResolverInterface'
|
||||
import { RemoveRevisionsFromSharedVault } from './RemoveRevisionsFromSharedVault'
|
||||
|
||||
describe('RemoveRevisionsFromSharedVault', () => {
|
||||
let revisionRepositoryResolver: RevisionRepositoryResolverInterface
|
||||
let revisionRepository: RevisionRepositoryInterface
|
||||
|
||||
const createUseCase = () => new RemoveRevisionsFromSharedVault(revisionRepositoryResolver)
|
||||
const createUseCase = () => new RemoveRevisionsFromSharedVault(revisionRepository)
|
||||
|
||||
beforeEach(() => {
|
||||
revisionRepository = {} as jest.Mocked<RevisionRepositoryInterface>
|
||||
revisionRepository.clearSharedVaultAndKeySystemAssociations = jest.fn()
|
||||
|
||||
revisionRepositoryResolver = {} as jest.Mocked<RevisionRepositoryResolverInterface>
|
||||
revisionRepositoryResolver.resolve = jest.fn().mockReturnValue(revisionRepository)
|
||||
})
|
||||
|
||||
it('should clear shared vault and key system associations', async () => {
|
||||
@@ -22,7 +17,16 @@ describe('RemoveRevisionsFromSharedVault', () => {
|
||||
await useCase.execute({
|
||||
itemUuid: '00000000-0000-0000-0000-000000000000',
|
||||
sharedVaultUuid: '00000000-0000-0000-0000-000000000001',
|
||||
roleNames: ['CORE_USER'],
|
||||
})
|
||||
|
||||
expect(revisionRepository.clearSharedVaultAndKeySystemAssociations).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should clear shared vault and key system associations for all items in a vault when item uuid is not provided', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
await useCase.execute({
|
||||
sharedVaultUuid: '00000000-0000-0000-0000-000000000001',
|
||||
})
|
||||
|
||||
expect(revisionRepository.clearSharedVaultAndKeySystemAssociations).toHaveBeenCalled()
|
||||
@@ -34,7 +38,6 @@ describe('RemoveRevisionsFromSharedVault', () => {
|
||||
const result = await useCase.execute({
|
||||
itemUuid: '00000000-0000-0000-0000-000000000000',
|
||||
sharedVaultUuid: 'invalid',
|
||||
roleNames: ['CORE_USER'],
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
@@ -46,19 +49,6 @@ describe('RemoveRevisionsFromSharedVault', () => {
|
||||
const result = await useCase.execute({
|
||||
itemUuid: 'invalid',
|
||||
sharedVaultUuid: '00000000-0000-0000-0000-000000000001',
|
||||
roleNames: ['CORE_USER'],
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
})
|
||||
|
||||
it('should return error when role names are invalid', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
itemUuid: '00000000-0000-0000-0000-000000000000',
|
||||
sharedVaultUuid: '00000000-0000-0000-0000-000000000001',
|
||||
roleNames: ['invalid'],
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { Result, RoleNameCollection, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
|
||||
import { RevisionRepositoryResolverInterface } from '../../Revision/RevisionRepositoryResolverInterface'
|
||||
import { Result, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
|
||||
|
||||
import { RemoveRevisionsFromSharedVaultDTO } from './RemoveRevisionsFromSharedVaultDTO'
|
||||
import { RevisionRepositoryInterface } from '../../Revision/RevisionRepositoryInterface'
|
||||
|
||||
export class RemoveRevisionsFromSharedVault implements UseCaseInterface<void> {
|
||||
constructor(private revisionRepositoryResolver: RevisionRepositoryResolverInterface) {}
|
||||
constructor(private revisionRepository: RevisionRepositoryInterface) {}
|
||||
|
||||
async execute(dto: RemoveRevisionsFromSharedVaultDTO): Promise<Result<void>> {
|
||||
const sharedVaultUuidOrError = Uuid.create(dto.sharedVaultUuid)
|
||||
@@ -12,21 +13,19 @@ export class RemoveRevisionsFromSharedVault implements UseCaseInterface<void> {
|
||||
}
|
||||
const sharedVaultUuid = sharedVaultUuidOrError.getValue()
|
||||
|
||||
const itemUuidOrError = Uuid.create(dto.itemUuid)
|
||||
if (itemUuidOrError.isFailed()) {
|
||||
return Result.fail(itemUuidOrError.getError())
|
||||
let itemUuid: Uuid | undefined
|
||||
if (dto.itemUuid !== undefined) {
|
||||
const itemUuidOrError = Uuid.create(dto.itemUuid)
|
||||
if (itemUuidOrError.isFailed()) {
|
||||
return Result.fail(itemUuidOrError.getError())
|
||||
}
|
||||
itemUuid = itemUuidOrError.getValue()
|
||||
}
|
||||
const itemUuid = itemUuidOrError.getValue()
|
||||
|
||||
const roleNamesOrError = RoleNameCollection.create(dto.roleNames)
|
||||
if (roleNamesOrError.isFailed()) {
|
||||
return Result.fail(roleNamesOrError.getError())
|
||||
}
|
||||
const roleNames = roleNamesOrError.getValue()
|
||||
|
||||
const revisionRepository = this.revisionRepositoryResolver.resolve(roleNames)
|
||||
|
||||
await revisionRepository.clearSharedVaultAndKeySystemAssociations(itemUuid, sharedVaultUuid)
|
||||
await this.revisionRepository.clearSharedVaultAndKeySystemAssociations({
|
||||
itemUuid,
|
||||
sharedVaultUuid,
|
||||
})
|
||||
|
||||
return Result.ok()
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
export interface RemoveRevisionsFromSharedVaultDTO {
|
||||
itemUuid: string
|
||||
itemUuid?: string
|
||||
sharedVaultUuid: string
|
||||
roleNames: string[]
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
|
||||
) {}
|
||||
|
||||
async execute(dto: TransitionRevisionsFromPrimaryToSecondaryDatabaseForUserDTO): Promise<Result<void>> {
|
||||
this.logger.info(`Transitioning revisions for user ${dto.userUuid}`)
|
||||
this.logger.info(`[${dto.userUuid}] Transitioning revisions for user`)
|
||||
|
||||
if (this.secondRevisionsRepository === null) {
|
||||
return Result.fail('Secondary revision repository is not set')
|
||||
@@ -30,31 +30,28 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
|
||||
const userUuid = userUuidOrError.getValue()
|
||||
|
||||
let newRevisionsInSecondaryCount = 0
|
||||
let updatedRevisionsInSecondary: Revision[] = []
|
||||
let updatedRevisionsInSecondary: string[] = []
|
||||
let alreadyIdenticalInSecondaryAndPrimary: string[] = []
|
||||
if (await this.hasAlreadyDataInSecondaryDatabase(userUuid)) {
|
||||
const { alreadyExistingInPrimary, newRevisionsInSecondary, updatedInSecondary } =
|
||||
const { alreadyExistingInSecondaryAndPrimary, newRevisionsInSecondary, updatedInSecondary } =
|
||||
await this.getNewRevisionsCreatedInSecondaryDatabase(userUuid)
|
||||
|
||||
for (const existingRevision of alreadyExistingInPrimary) {
|
||||
this.logger.info(`Removing revision ${existingRevision.id.toString()} from secondary database`)
|
||||
await (this.secondRevisionsRepository as RevisionRepositoryInterface).removeOneByUuid(
|
||||
Uuid.create(existingRevision.id.toString()).getValue(),
|
||||
userUuid,
|
||||
)
|
||||
}
|
||||
this.logger.info(
|
||||
`[${dto.userUuid}] ${alreadyExistingInSecondaryAndPrimary.length} already existing identical revisions in primary and secondary.`,
|
||||
)
|
||||
|
||||
alreadyIdenticalInSecondaryAndPrimary = alreadyExistingInSecondaryAndPrimary
|
||||
|
||||
if (newRevisionsInSecondary.length > 0) {
|
||||
this.logger.info(
|
||||
`Found ${newRevisionsInSecondary.length} new revisions in secondary database for user ${userUuid.value}`,
|
||||
`[${dto.userUuid}] Found ${newRevisionsInSecondary.length} new revisions in secondary database`,
|
||||
)
|
||||
}
|
||||
|
||||
newRevisionsInSecondaryCount = newRevisionsInSecondary.length
|
||||
|
||||
if (updatedInSecondary.length > 0) {
|
||||
this.logger.info(
|
||||
`Found ${updatedInSecondary.length} updated revisions in secondary database for user ${userUuid.value}`,
|
||||
)
|
||||
this.logger.info(`[${dto.userUuid}] Found ${updatedInSecondary.length} updated revisions in secondary database`)
|
||||
}
|
||||
|
||||
updatedRevisionsInSecondary = updatedInSecondary
|
||||
@@ -66,15 +63,19 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
|
||||
|
||||
const migrationTimeStart = this.timer.getTimestampInMicroseconds()
|
||||
|
||||
this.logger.debug(`Transitioning revisions for user ${userUuid.value}`)
|
||||
this.logger.info(`[${dto.userUuid}] Migrating revisions`)
|
||||
|
||||
const migrationResult = await this.migrateRevisionsForUser(userUuid, updatedRevisionsInSecondary)
|
||||
const migrationResult = await this.migrateRevisionsForUser(
|
||||
userUuid,
|
||||
updatedRevisionsInSecondary,
|
||||
alreadyIdenticalInSecondaryAndPrimary,
|
||||
)
|
||||
if (migrationResult.isFailed()) {
|
||||
if (newRevisionsInSecondaryCount === 0 && updatedRevisionsInSecondaryCount === 0) {
|
||||
const cleanupResult = await this.deleteRevisionsForUser(userUuid, this.secondRevisionsRepository)
|
||||
if (cleanupResult.isFailed()) {
|
||||
this.logger.error(
|
||||
`Failed to clean up secondary database revisions for user ${userUuid.value}: ${cleanupResult.getError()}`,
|
||||
`[${dto.userUuid}] Failed to clean up secondary database revisions: ${cleanupResult.getError()}`,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -88,13 +89,14 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
|
||||
userUuid,
|
||||
newRevisionsInSecondaryCount,
|
||||
updatedRevisionsInSecondary,
|
||||
alreadyIdenticalInSecondaryAndPrimary,
|
||||
)
|
||||
if (integrityCheckResult.isFailed()) {
|
||||
if (newRevisionsInSecondaryCount === 0 && updatedRevisionsInSecondaryCount === 0) {
|
||||
const cleanupResult = await this.deleteRevisionsForUser(userUuid, this.secondRevisionsRepository)
|
||||
if (cleanupResult.isFailed()) {
|
||||
this.logger.error(
|
||||
`Failed to clean up secondary database revisions for user ${userUuid.value}: ${cleanupResult.getError()}`,
|
||||
`[${dto.userUuid}] Failed to clean up secondary database revisions: ${cleanupResult.getError()}`,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -104,9 +106,7 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
|
||||
|
||||
const cleanupResult = await this.deleteRevisionsForUser(userUuid, this.primaryRevisionsRepository)
|
||||
if (cleanupResult.isFailed()) {
|
||||
this.logger.error(
|
||||
`Failed to clean up primary database revisions for user ${userUuid.value}: ${cleanupResult.getError()}`,
|
||||
)
|
||||
this.logger.error(`[${dto.userUuid}] Failed to clean up primary database revisions: ${cleanupResult.getError()}`)
|
||||
}
|
||||
|
||||
const migrationTimeEnd = this.timer.getTimestampInMicroseconds()
|
||||
@@ -115,7 +115,7 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
|
||||
const migrationDurationTimeStructure = this.timer.convertMicrosecondsToTimeStructure(migrationDuration)
|
||||
|
||||
this.logger.info(
|
||||
`Transitioned revisions for user ${userUuid.value} in ${migrationDurationTimeStructure.hours}h ${migrationDurationTimeStructure.minutes}m ${migrationDurationTimeStructure.seconds}s ${migrationDurationTimeStructure.milliseconds}ms`,
|
||||
`[${dto.userUuid}] Transitioned revisions in ${migrationDurationTimeStructure.hours}h ${migrationDurationTimeStructure.minutes}m ${migrationDurationTimeStructure.seconds}s ${migrationDurationTimeStructure.milliseconds}ms`,
|
||||
)
|
||||
|
||||
return Result.ok()
|
||||
@@ -123,11 +123,11 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
|
||||
|
||||
private async migrateRevisionsForUser(
|
||||
userUuid: Uuid,
|
||||
updatedRevisionsInSecondary: Revision[],
|
||||
updatedRevisionsInSecondary: string[],
|
||||
alreadyExistingInSecondaryAndPrimary: string[],
|
||||
): Promise<Result<void>> {
|
||||
try {
|
||||
const totalRevisionsCountForUser = await this.primaryRevisionsRepository.countByUserUuid(userUuid)
|
||||
let totalRevisionsCountTransitionedToSecondary = 0
|
||||
const totalPages = Math.ceil(totalRevisionsCountForUser / this.pageSize)
|
||||
for (let currentPage = 1; currentPage <= totalPages; currentPage++) {
|
||||
const query = {
|
||||
@@ -141,28 +141,29 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
|
||||
for (const revision of revisions) {
|
||||
try {
|
||||
if (
|
||||
updatedRevisionsInSecondary.find(
|
||||
(updatedRevision) => updatedRevision.id.toString() === revision.id.toString(),
|
||||
)
|
||||
updatedRevisionsInSecondary.find((updatedRevisionUuid) => updatedRevisionUuid === revision.id.toString())
|
||||
) {
|
||||
this.logger.info(
|
||||
`Skipping saving revision ${revision.id.toString()} as it was updated in secondary database`,
|
||||
`[${
|
||||
userUuid.value
|
||||
}] Skipping saving revision ${revision.id.toString()} as it was updated in secondary database`,
|
||||
)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
this.logger.debug(
|
||||
`Transitioning revision #${
|
||||
totalRevisionsCountTransitionedToSecondary + 1
|
||||
}: ${revision.id.toString()} to secondary database`,
|
||||
)
|
||||
if (
|
||||
alreadyExistingInSecondaryAndPrimary.find(
|
||||
(alreadyExistingRevisionUuid) => alreadyExistingRevisionUuid === revision.id.toString(),
|
||||
)
|
||||
) {
|
||||
continue
|
||||
}
|
||||
|
||||
const didSave = await (this.secondRevisionsRepository as RevisionRepositoryInterface).insert(revision)
|
||||
if (!didSave) {
|
||||
return Result.fail(`Failed to save revision ${revision.id.toString()} to secondary database`)
|
||||
}
|
||||
totalRevisionsCountTransitionedToSecondary++
|
||||
} catch (error) {
|
||||
return Result.fail(
|
||||
`Errored when saving revision ${revision.id.toString()} to secondary database: ${
|
||||
@@ -173,8 +174,6 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
|
||||
}
|
||||
}
|
||||
|
||||
this.logger.debug(`Transitioned ${totalRevisionsCountTransitionedToSecondary} revisions to secondary database`)
|
||||
|
||||
return Result.ok()
|
||||
} catch (error) {
|
||||
return Result.fail(`Errored when migrating revisions for user ${userUuid.value}: ${(error as Error).message}`)
|
||||
@@ -195,8 +194,8 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
|
||||
}
|
||||
|
||||
private async allowForSecondaryDatabaseToCatchUp(): Promise<void> {
|
||||
const twoSecondsInMilliseconds = 2_000
|
||||
await this.timer.sleep(twoSecondsInMilliseconds)
|
||||
const tenSecondsInMillisecondsToRebuildIndexes = 10_000
|
||||
await this.timer.sleep(tenSecondsInMillisecondsToRebuildIndexes)
|
||||
}
|
||||
|
||||
private async hasAlreadyDataInSecondaryDatabase(userUuid: Uuid): Promise<boolean> {
|
||||
@@ -207,7 +206,7 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
|
||||
const hasAlreadyDataInSecondaryDatabase = totalRevisionsCountForUserInSecondary > 0
|
||||
if (hasAlreadyDataInSecondaryDatabase) {
|
||||
this.logger.info(
|
||||
`User ${userUuid.value} has already ${totalRevisionsCountForUserInSecondary} revisions in secondary database`,
|
||||
`[${userUuid.value}] User has already ${totalRevisionsCountForUserInSecondary} revisions in secondary database`,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -215,45 +214,57 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
|
||||
}
|
||||
|
||||
private async getNewRevisionsCreatedInSecondaryDatabase(userUuid: Uuid): Promise<{
|
||||
alreadyExistingInPrimary: Revision[]
|
||||
newRevisionsInSecondary: Revision[]
|
||||
updatedInSecondary: Revision[]
|
||||
alreadyExistingInSecondaryAndPrimary: string[]
|
||||
newRevisionsInSecondary: string[]
|
||||
updatedInSecondary: string[]
|
||||
}> {
|
||||
const revisions = await (this.secondRevisionsRepository as RevisionRepositoryInterface).findByUserUuid({
|
||||
userUuid: userUuid,
|
||||
})
|
||||
this.logger.info(`[${userUuid.value}] Checking for new revisions created in secondary database`)
|
||||
|
||||
const alreadyExistingInPrimary: Revision[] = []
|
||||
const newRevisionsInSecondary: Revision[] = []
|
||||
const updatedInSecondary: Revision[] = []
|
||||
const totalRevisionsCountForUser = await (
|
||||
this.secondRevisionsRepository as RevisionRepositoryInterface
|
||||
).countByUserUuid(userUuid)
|
||||
const totalPages = Math.ceil(totalRevisionsCountForUser / this.pageSize)
|
||||
|
||||
for (const revision of revisions) {
|
||||
const { revisionInPrimary, newerRevisionInSecondary } =
|
||||
await this.checkIfRevisionExistsInPrimaryDatabase(revision)
|
||||
if (revisionInPrimary !== null) {
|
||||
alreadyExistingInPrimary.push(revision)
|
||||
continue
|
||||
const alreadyExistingInSecondaryAndPrimary: string[] = []
|
||||
const newRevisionsInSecondary: string[] = []
|
||||
const updatedInSecondary: string[] = []
|
||||
|
||||
for (let currentPage = 1; currentPage <= totalPages; currentPage++) {
|
||||
const query = {
|
||||
userUuid: userUuid,
|
||||
offset: (currentPage - 1) * this.pageSize,
|
||||
limit: this.pageSize,
|
||||
}
|
||||
if (newerRevisionInSecondary !== null) {
|
||||
updatedInSecondary.push(newerRevisionInSecondary)
|
||||
continue
|
||||
}
|
||||
if (revisionInPrimary === null && newerRevisionInSecondary === null) {
|
||||
newRevisionsInSecondary.push(revision)
|
||||
continue
|
||||
|
||||
const revisions = await (this.secondRevisionsRepository as RevisionRepositoryInterface).findByUserUuid(query)
|
||||
for (const revision of revisions) {
|
||||
const { identicalRevisionInPrimary, newerRevisionInSecondary } =
|
||||
await this.checkIfRevisionExistsInPrimaryDatabase(revision)
|
||||
if (identicalRevisionInPrimary !== null) {
|
||||
alreadyExistingInSecondaryAndPrimary.push(revision.id.toString())
|
||||
continue
|
||||
}
|
||||
if (newerRevisionInSecondary !== null) {
|
||||
updatedInSecondary.push(newerRevisionInSecondary.id.toString())
|
||||
continue
|
||||
}
|
||||
if (identicalRevisionInPrimary === null && newerRevisionInSecondary === null) {
|
||||
newRevisionsInSecondary.push(revision.id.toString())
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
alreadyExistingInPrimary: alreadyExistingInPrimary,
|
||||
newRevisionsInSecondary: newRevisionsInSecondary,
|
||||
updatedInSecondary: updatedInSecondary,
|
||||
alreadyExistingInSecondaryAndPrimary,
|
||||
newRevisionsInSecondary,
|
||||
updatedInSecondary,
|
||||
}
|
||||
}
|
||||
|
||||
private async checkIfRevisionExistsInPrimaryDatabase(
|
||||
revision: Revision,
|
||||
): Promise<{ revisionInPrimary: Revision | null; newerRevisionInSecondary: Revision | null }> {
|
||||
): Promise<{ identicalRevisionInPrimary: Revision | null; newerRevisionInSecondary: Revision | null }> {
|
||||
const revisionInPrimary = await this.primaryRevisionsRepository.findOneByUuid(
|
||||
Uuid.create(revision.id.toString()).getValue(),
|
||||
revision.props.userUuid as Uuid,
|
||||
@@ -262,27 +273,28 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
|
||||
|
||||
if (revisionInPrimary === null) {
|
||||
return {
|
||||
revisionInPrimary: null,
|
||||
identicalRevisionInPrimary: null,
|
||||
newerRevisionInSecondary: null,
|
||||
}
|
||||
}
|
||||
|
||||
if (!revision.isIdenticalTo(revisionInPrimary)) {
|
||||
this.logger.error(
|
||||
`Revision ${revision.id.toString()} is not identical in primary and secondary database. Revision in secondary database: ${JSON.stringify(
|
||||
`[${revision.props.userUuid
|
||||
?.value}] Revision ${revision.id.toString()} is not identical in primary and secondary database. Revision in secondary database: ${JSON.stringify(
|
||||
revision,
|
||||
)}, revision in primary database: ${JSON.stringify(revisionInPrimary)}`,
|
||||
)
|
||||
|
||||
return {
|
||||
revisionInPrimary: null,
|
||||
identicalRevisionInPrimary: null,
|
||||
newerRevisionInSecondary:
|
||||
revision.props.dates.updatedAt > revisionInPrimary.props.dates.updatedAt ? revision : null,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
revisionInPrimary: revisionInPrimary,
|
||||
identicalRevisionInPrimary: revisionInPrimary,
|
||||
newerRevisionInSecondary: null,
|
||||
}
|
||||
}
|
||||
@@ -290,7 +302,8 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
|
||||
private async checkIntegrityBetweenPrimaryAndSecondaryDatabase(
|
||||
userUuid: Uuid,
|
||||
newRevisionsInSecondaryCount: number,
|
||||
updatedRevisionsInSecondary: Revision[],
|
||||
updatedRevisionsInSecondary: string[],
|
||||
alreadyExistingInSecondaryAndPrimary: string[],
|
||||
): Promise<Result<boolean>> {
|
||||
try {
|
||||
const totalRevisionsCountForUserInPrimary = await this.primaryRevisionsRepository.countByUserUuid(userUuid)
|
||||
@@ -321,16 +334,24 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
|
||||
}
|
||||
|
||||
if (
|
||||
updatedRevisionsInSecondary.find(
|
||||
(updatedRevision) => updatedRevision.id.toString() === revision.id.toString(),
|
||||
)
|
||||
updatedRevisionsInSecondary.find((updatedRevisionUuid) => updatedRevisionUuid === revision.id.toString())
|
||||
) {
|
||||
this.logger.info(
|
||||
`Skipping integrity check for revision ${revision.id.toString()} as it was updated in secondary database`,
|
||||
`[${
|
||||
userUuid.value
|
||||
}] Skipping integrity check for revision ${revision.id.toString()} as it was updated in secondary database`,
|
||||
)
|
||||
continue
|
||||
}
|
||||
|
||||
if (
|
||||
alreadyExistingInSecondaryAndPrimary.find(
|
||||
(alreadyExistingRevisionUuid) => alreadyExistingRevisionUuid === revision.id.toString(),
|
||||
)
|
||||
) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (!revision.isIdenticalTo(revisionInSecondary)) {
|
||||
return Result.fail(
|
||||
`Revision ${revision.id.toString()} is not identical in primary and secondary database. Revision in primary database: ${JSON.stringify(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { MapperInterface, Uuid } from '@standardnotes/domain-core'
|
||||
import { MongoRepository } from 'typeorm'
|
||||
import { MongoRepository, ObjectLiteral } from 'typeorm'
|
||||
import { BSON } from 'mongodb'
|
||||
import { Logger } from 'winston'
|
||||
|
||||
@@ -16,19 +16,25 @@ export class MongoDBRevisionRepository implements RevisionRepositoryInterface {
|
||||
private logger: Logger,
|
||||
) {}
|
||||
|
||||
async clearSharedVaultAndKeySystemAssociations(itemUuid: Uuid, sharedVaultUuid: Uuid): Promise<void> {
|
||||
await this.mongoRepository.updateMany(
|
||||
{
|
||||
itemUuid: { $eq: itemUuid.value },
|
||||
sharedVaultUuid: { $eq: sharedVaultUuid.value },
|
||||
async clearSharedVaultAndKeySystemAssociations(dto: { itemUuid?: Uuid; sharedVaultUuid: Uuid }): Promise<void> {
|
||||
let query: ObjectLiteral
|
||||
if (dto.itemUuid !== undefined) {
|
||||
query = {
|
||||
itemUuid: { $eq: dto.itemUuid.value },
|
||||
sharedVaultUuid: { $eq: dto.sharedVaultUuid.value },
|
||||
}
|
||||
} else {
|
||||
query = {
|
||||
sharedVaultUuid: { $eq: dto.sharedVaultUuid.value },
|
||||
}
|
||||
}
|
||||
|
||||
await this.mongoRepository.updateMany(query, {
|
||||
$set: {
|
||||
sharedVaultUuid: null,
|
||||
keySystemIdentifier: null,
|
||||
},
|
||||
{
|
||||
$set: {
|
||||
sharedVaultUuid: null,
|
||||
keySystemIdentifier: null,
|
||||
},
|
||||
},
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
async countByUserUuid(userUuid: Uuid): Promise<number> {
|
||||
|
||||
@@ -15,7 +15,7 @@ export class SQLLegacyRevisionRepository implements RevisionRepositoryInterface
|
||||
protected logger: Logger,
|
||||
) {}
|
||||
|
||||
async clearSharedVaultAndKeySystemAssociations(_itemUuid: Uuid, _sharedVaultUuid: Uuid): Promise<void> {
|
||||
async clearSharedVaultAndKeySystemAssociations(_dto: { itemUuid?: Uuid; sharedVaultUuid: Uuid }): Promise<void> {
|
||||
this.logger.error('Method clearSharedVaultAndKeySystemAssociations not implemented.')
|
||||
}
|
||||
|
||||
|
||||
@@ -66,19 +66,27 @@ export class SQLRevisionRepository extends SQLLegacyRevisionRepository {
|
||||
return this.revisionMapper.toDomain(sqlRevision)
|
||||
}
|
||||
|
||||
override async clearSharedVaultAndKeySystemAssociations(itemUuid: Uuid, sharedVaultUuid: Uuid): Promise<void> {
|
||||
await this.ormRepository
|
||||
.createQueryBuilder()
|
||||
.update()
|
||||
.set({
|
||||
sharedVaultUuid: null,
|
||||
keySystemIdentifier: null,
|
||||
override async clearSharedVaultAndKeySystemAssociations(dto: {
|
||||
itemUuid?: Uuid
|
||||
sharedVaultUuid: Uuid
|
||||
}): Promise<void> {
|
||||
const queryBuilder = this.ormRepository.createQueryBuilder().update().set({
|
||||
sharedVaultUuid: null,
|
||||
keySystemIdentifier: null,
|
||||
})
|
||||
|
||||
if (dto.itemUuid !== undefined) {
|
||||
queryBuilder.where('item_uuid = :itemUuid AND shared_vault_uuid = :sharedVaultUuid', {
|
||||
itemUuid: dto.itemUuid.value,
|
||||
sharedVaultUuid: dto.sharedVaultUuid.value,
|
||||
})
|
||||
.where('item_uuid = :itemUuid AND shared_vault_uuid = :sharedVaultUuid', {
|
||||
itemUuid: itemUuid.value,
|
||||
sharedVaultUuid: sharedVaultUuid.value,
|
||||
} else {
|
||||
queryBuilder.where('shared_vault_uuid = :sharedVaultUuid', {
|
||||
sharedVaultUuid: dto.sharedVaultUuid.value,
|
||||
})
|
||||
.execute()
|
||||
}
|
||||
|
||||
await queryBuilder.execute()
|
||||
}
|
||||
|
||||
override async findMetadataByItemId(
|
||||
|
||||
@@ -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.20.51](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.20.50...@standardnotes/scheduler-server@1.20.51) (2023-09-20)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/scheduler-server
|
||||
|
||||
## [1.20.50](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.20.49...@standardnotes/scheduler-server@1.20.50) (2023-09-20)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/scheduler-server
|
||||
|
||||
## [1.20.49](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.20.48...@standardnotes/scheduler-server@1.20.49) (2023-09-19)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/scheduler-server
|
||||
|
||||
## [1.20.48](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.20.47...@standardnotes/scheduler-server@1.20.48) (2023-09-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/scheduler-server
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/scheduler-server",
|
||||
"version": "1.20.48",
|
||||
"version": "1.20.51",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -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.21.36](https://github.com/standardnotes/server/compare/@standardnotes/settings@1.21.35...@standardnotes/settings@1.21.36) (2023-09-20)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/settings
|
||||
|
||||
## [1.21.35](https://github.com/standardnotes/server/compare/@standardnotes/settings@1.21.34...@standardnotes/settings@1.21.35) (2023-09-20)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/settings
|
||||
|
||||
## [1.21.34](https://github.com/standardnotes/server/compare/@standardnotes/settings@1.21.33...@standardnotes/settings@1.21.34) (2023-09-19)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/settings
|
||||
|
||||
## [1.21.33](https://github.com/standardnotes/server/compare/@standardnotes/settings@1.21.32...@standardnotes/settings@1.21.33) (2023-09-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/settings
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/settings",
|
||||
"version": "1.21.33",
|
||||
"version": "1.21.36",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,57 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [1.101.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.100.0...@standardnotes/syncing-server@1.101.0) (2023-09-20)
|
||||
|
||||
### Features
|
||||
|
||||
* add unassigning items and revisions upon shared vault removal ([#839](https://github.com/standardnotes/syncing-server-js/issues/839)) ([378eced](https://github.com/standardnotes/syncing-server-js/commit/378ecedfcc4fb23475c2329fb37479edb3b48a39))
|
||||
* **syncing-server:** distinct notifications upon user removal from shared vault ([#840](https://github.com/standardnotes/syncing-server-js/issues/840)) ([41e2136](https://github.com/standardnotes/syncing-server-js/commit/41e2136bc07312974701a70652528d304105e0f9))
|
||||
|
||||
# [1.100.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.99.0...@standardnotes/syncing-server@1.100.0) (2023-09-20)
|
||||
|
||||
### Features
|
||||
|
||||
* **syncing-server:** remove owned shared vaults upon account deletion ([#838](https://github.com/standardnotes/syncing-server-js/issues/838)) ([22a8cc9](https://github.com/standardnotes/syncing-server-js/commit/22a8cc90f1232fd5f5646f613c80bd7c60186670))
|
||||
|
||||
# [1.99.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.98.6...@standardnotes/syncing-server@1.99.0) (2023-09-20)
|
||||
|
||||
### Features
|
||||
|
||||
* **syncing-server:** add notification for user upon declined shared vault invitation ([#837](https://github.com/standardnotes/syncing-server-js/issues/837)) ([31e7aaf](https://github.com/standardnotes/syncing-server-js/commit/31e7aaf253029a951d8b943d6cffd655cd5ca765))
|
||||
|
||||
## [1.98.6](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.98.5...@standardnotes/syncing-server@1.98.6) (2023-09-19)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* skip removing already existing content in secondary to pick up where the transition left of ([857c6af](https://github.com/standardnotes/syncing-server-js/commit/857c6af9468ec829ff4dce9a96ba7bf9c14d55a5))
|
||||
|
||||
## [1.98.5](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.98.4...@standardnotes/syncing-server@1.98.5) (2023-09-19)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* increase timeout for secondary database to catch up for indexes to be rebuilt ([b265a39](https://github.com/standardnotes/syncing-server-js/commit/b265a39b635373c36ee8c3d8e09f0631159b3574))
|
||||
* logs verbosity during transitions ([e589029](https://github.com/standardnotes/syncing-server-js/commit/e589029722ab9f4debc8aa6cc78913f877eda2e3))
|
||||
|
||||
## [1.98.4](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.98.3...@standardnotes/syncing-server@1.98.4) (2023-09-19)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add checking for secondary items logs ([a1a3e9f](https://github.com/standardnotes/syncing-server-js/commit/a1a3e9f586358b943b1b490a1382e42f081f7d06))
|
||||
* logs for removing already existing content and paging through diff of the content ([a40b17b](https://github.com/standardnotes/syncing-server-js/commit/a40b17b141f1d5954e1a45b969d5a941386c68d0))
|
||||
|
||||
## [1.98.3](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.98.2...@standardnotes/syncing-server@1.98.3) (2023-09-19)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* logs formatting during transition for better readability ([0ae028d](https://github.com/standardnotes/syncing-server-js/commit/0ae028db739decec8c50321b18b0af515e00bd23))
|
||||
|
||||
## [1.98.2](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.98.1...@standardnotes/syncing-server@1.98.2) (2023-09-19)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **syncing-server:** paging through already existing items ([e4fcd73](https://github.com/standardnotes/syncing-server-js/commit/e4fcd738c35a4dc96e57db6ca08383a5647d61ad))
|
||||
|
||||
## [1.98.1](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.98.0...@standardnotes/syncing-server@1.98.1) (2023-09-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/syncing-server
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/syncing-server",
|
||||
"version": "1.98.1",
|
||||
"version": "1.101.0",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -165,6 +165,9 @@ import { SQLItemPersistenceMapper } from '../Mapping/Persistence/SQLItemPersiste
|
||||
import { SQLItemRepository } from '../Infra/TypeORM/SQLItemRepository'
|
||||
import { SendEventToClient } from '../Domain/UseCase/Syncing/SendEventToClient/SendEventToClient'
|
||||
import { TransitionRequestedEventHandler } from '../Domain/Handler/TransitionRequestedEventHandler'
|
||||
import { DeleteSharedVaults } from '../Domain/UseCase/SharedVaults/DeleteSharedVaults/DeleteSharedVaults'
|
||||
import { RemoveItemsFromSharedVault } from '../Domain/UseCase/SharedVaults/RemoveItemsFromSharedVault/RemoveItemsFromSharedVault'
|
||||
import { SharedVaultRemovedEventHandler } from '../Domain/Handler/SharedVaultRemovedEventHandler'
|
||||
|
||||
export class ContainerConfigLoader {
|
||||
private readonly DEFAULT_CONTENT_SIZE_TRANSFER_LIMIT = 10_000_000
|
||||
@@ -723,7 +726,12 @@ export class ContainerConfigLoader {
|
||||
)
|
||||
container
|
||||
.bind<DeclineInviteToSharedVault>(TYPES.Sync_DeclineInviteToSharedVault)
|
||||
.toConstantValue(new DeclineInviteToSharedVault(container.get(TYPES.Sync_SharedVaultInviteRepository)))
|
||||
.toConstantValue(
|
||||
new DeclineInviteToSharedVault(
|
||||
container.get<SharedVaultInviteRepositoryInterface>(TYPES.Sync_SharedVaultInviteRepository),
|
||||
container.get<AddNotificationForUser>(TYPES.Sync_AddNotificationForUser),
|
||||
),
|
||||
)
|
||||
container
|
||||
.bind<DeleteSharedVaultInvitesToUser>(TYPES.Sync_DeleteSharedVaultInvitesToUser)
|
||||
.toConstantValue(
|
||||
@@ -758,6 +766,7 @@ export class ContainerConfigLoader {
|
||||
container.get<SharedVaultUserRepositoryInterface>(TYPES.Sync_SharedVaultUserRepository),
|
||||
container.get<SharedVaultRepositoryInterface>(TYPES.Sync_SharedVaultRepository),
|
||||
container.get<AddNotificationsForUsers>(TYPES.Sync_AddNotificationsForUsers),
|
||||
container.get<AddNotificationForUser>(TYPES.Sync_AddNotificationForUser),
|
||||
container.get<DomainEventFactoryInterface>(TYPES.Sync_DomainEventFactory),
|
||||
container.get<DomainEventPublisherInterface>(TYPES.Sync_DomainEventPublisher),
|
||||
),
|
||||
@@ -775,10 +784,21 @@ export class ContainerConfigLoader {
|
||||
.bind<DeleteSharedVault>(TYPES.Sync_DeleteSharedVault)
|
||||
.toConstantValue(
|
||||
new DeleteSharedVault(
|
||||
container.get(TYPES.Sync_SharedVaultRepository),
|
||||
container.get(TYPES.Sync_SharedVaultUserRepository),
|
||||
container.get(TYPES.Sync_SharedVaultInviteRepository),
|
||||
container.get(TYPES.Sync_RemoveSharedVaultUser),
|
||||
container.get<SharedVaultRepositoryInterface>(TYPES.Sync_SharedVaultRepository),
|
||||
container.get<SharedVaultUserRepositoryInterface>(TYPES.Sync_SharedVaultUserRepository),
|
||||
container.get<SharedVaultInviteRepositoryInterface>(TYPES.Sync_SharedVaultInviteRepository),
|
||||
container.get<RemoveUserFromSharedVault>(TYPES.Sync_RemoveSharedVaultUser),
|
||||
container.get<DeclineInviteToSharedVault>(TYPES.Sync_DeclineInviteToSharedVault),
|
||||
container.get<DomainEventFactoryInterface>(TYPES.Sync_DomainEventFactory),
|
||||
container.get<DomainEventPublisherInterface>(TYPES.Sync_DomainEventPublisher),
|
||||
),
|
||||
)
|
||||
container
|
||||
.bind<DeleteSharedVaults>(TYPES.Sync_DeleteSharedVaults)
|
||||
.toConstantValue(
|
||||
new DeleteSharedVaults(
|
||||
container.get<SharedVaultRepositoryInterface>(TYPES.Sync_SharedVaultRepository),
|
||||
container.get<DeleteSharedVault>(TYPES.Sync_DeleteSharedVault),
|
||||
),
|
||||
)
|
||||
container
|
||||
@@ -836,6 +856,15 @@ export class ContainerConfigLoader {
|
||||
env.get('MIGRATION_BATCH_SIZE', true) ? +env.get('MIGRATION_BATCH_SIZE', true) : 100,
|
||||
),
|
||||
)
|
||||
container
|
||||
.bind<RemoveItemsFromSharedVault>(TYPES.Sync_RemoveItemsFromSharedVault)
|
||||
.toConstantValue(
|
||||
new RemoveItemsFromSharedVault(
|
||||
isSecondaryDatabaseEnabled
|
||||
? container.get<ItemRepositoryInterface>(TYPES.Sync_MongoDBItemRepository)
|
||||
: container.get<ItemRepositoryInterface>(TYPES.Sync_SQLItemRepository),
|
||||
),
|
||||
)
|
||||
|
||||
// Services
|
||||
container
|
||||
@@ -897,6 +926,7 @@ export class ContainerConfigLoader {
|
||||
.toConstantValue(
|
||||
new AccountDeletionRequestedEventHandler(
|
||||
container.get<ItemRepositoryResolverInterface>(TYPES.Sync_ItemRepositoryResolver),
|
||||
container.get<DeleteSharedVaults>(TYPES.Sync_DeleteSharedVaults),
|
||||
container.get<Logger>(TYPES.Sync_Logger),
|
||||
),
|
||||
)
|
||||
@@ -950,6 +980,14 @@ export class ContainerConfigLoader {
|
||||
container.get<Logger>(TYPES.Sync_Logger),
|
||||
),
|
||||
)
|
||||
container
|
||||
.bind<SharedVaultRemovedEventHandler>(TYPES.Sync_SharedVaultRemovedEventHandler)
|
||||
.toConstantValue(
|
||||
new SharedVaultRemovedEventHandler(
|
||||
container.get<RemoveItemsFromSharedVault>(TYPES.Sync_RemoveItemsFromSharedVault),
|
||||
container.get<Logger>(TYPES.Sync_Logger),
|
||||
),
|
||||
)
|
||||
|
||||
// Services
|
||||
container.bind<ContentDecoder>(TYPES.Sync_ContentDecoder).toDynamicValue(() => new ContentDecoder())
|
||||
@@ -988,6 +1026,10 @@ export class ContainerConfigLoader {
|
||||
'TRANSITION_REQUESTED',
|
||||
container.get<TransitionRequestedEventHandler>(TYPES.Sync_TransitionRequestedEventHandler),
|
||||
],
|
||||
[
|
||||
'SHARED_VAULT_REMOVED',
|
||||
container.get<SharedVaultRemovedEventHandler>(TYPES.Sync_SharedVaultRemovedEventHandler),
|
||||
],
|
||||
])
|
||||
if (!isConfiguredForHomeServer) {
|
||||
container.bind(TYPES.Sync_AUTH_SERVER_URL).toConstantValue(env.get('AUTH_SERVER_URL'))
|
||||
|
||||
@@ -54,6 +54,7 @@ const TYPES = {
|
||||
Sync_GetSharedVaults: Symbol.for('Sync_GetSharedVaults'),
|
||||
Sync_CreateSharedVault: Symbol.for('Sync_CreateSharedVault'),
|
||||
Sync_DeleteSharedVault: Symbol.for('Sync_DeleteSharedVault'),
|
||||
Sync_DeleteSharedVaults: Symbol.for('Sync_DeleteSharedVaults'),
|
||||
Sync_CreateSharedVaultFileValetToken: Symbol.for('Sync_CreateSharedVaultFileValetToken'),
|
||||
Sync_GetSharedVaultUsers: Symbol.for('Sync_GetSharedVaultUsers'),
|
||||
Sync_AddUserToSharedVault: Symbol.for('Sync_AddUserToSharedVault'),
|
||||
@@ -85,6 +86,7 @@ const TYPES = {
|
||||
'Sync_TransitionItemsFromPrimaryToSecondaryDatabaseForUser',
|
||||
),
|
||||
Sync_SendEventToClient: Symbol.for('Sync_SendEventToClient'),
|
||||
Sync_RemoveItemsFromSharedVault: Symbol.for('Sync_RemoveItemsFromSharedVault'),
|
||||
// Handlers
|
||||
Sync_AccountDeletionRequestedEventHandler: Symbol.for('Sync_AccountDeletionRequestedEventHandler'),
|
||||
Sync_DuplicateItemSyncedEventHandler: Symbol.for('Sync_DuplicateItemSyncedEventHandler'),
|
||||
@@ -94,6 +96,7 @@ const TYPES = {
|
||||
Sync_SharedVaultFileUploadedEventHandler: Symbol.for('Sync_SharedVaultFileUploadedEventHandler'),
|
||||
Sync_SharedVaultFileMovedEventHandler: Symbol.for('Sync_SharedVaultFileMovedEventHandler'),
|
||||
Sync_TransitionRequestedEventHandler: Symbol.for('Sync_TransitionRequestedEventHandler'),
|
||||
Sync_SharedVaultRemovedEventHandler: Symbol.for('Sync_SharedVaultRemovedEventHandler'),
|
||||
// Services
|
||||
Sync_ContentDecoder: Symbol.for('Sync_ContentDecoder'),
|
||||
Sync_DomainEventPublisher: Symbol.for('Sync_DomainEventPublisher'),
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
MessageSentToUserEvent,
|
||||
NotificationAddedForUserEvent,
|
||||
RevisionsCopyRequestedEvent,
|
||||
SharedVaultRemovedEvent,
|
||||
TransitionStatusUpdatedEvent,
|
||||
UserAddedToSharedVaultEvent,
|
||||
UserInvitedToSharedVaultEvent,
|
||||
@@ -21,11 +22,25 @@ import { DomainEventFactoryInterface } from './DomainEventFactoryInterface'
|
||||
export class DomainEventFactory implements DomainEventFactoryInterface {
|
||||
constructor(private timer: TimerInterface) {}
|
||||
|
||||
createSharedVaultRemovedEvent(dto: { sharedVaultUuid: string }): SharedVaultRemovedEvent {
|
||||
return {
|
||||
type: 'SHARED_VAULT_REMOVED',
|
||||
createdAt: this.timer.getUTCDate(),
|
||||
meta: {
|
||||
correlation: {
|
||||
userIdentifier: dto.sharedVaultUuid,
|
||||
userIdentifierType: 'shared-vault-uuid',
|
||||
},
|
||||
origin: DomainEventService.SyncingServer,
|
||||
},
|
||||
payload: dto,
|
||||
}
|
||||
}
|
||||
|
||||
createItemRemovedFromSharedVaultEvent(dto: {
|
||||
sharedVaultUuid: string
|
||||
itemUuid: string
|
||||
userUuid: string
|
||||
roleNames: string[]
|
||||
}): ItemRemovedFromSharedVaultEvent {
|
||||
return {
|
||||
type: 'ITEM_REMOVED_FROM_SHARED_VAULT',
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
MessageSentToUserEvent,
|
||||
NotificationAddedForUserEvent,
|
||||
RevisionsCopyRequestedEvent,
|
||||
SharedVaultRemovedEvent,
|
||||
TransitionStatusUpdatedEvent,
|
||||
UserAddedToSharedVaultEvent,
|
||||
UserInvitedToSharedVaultEvent,
|
||||
@@ -99,6 +100,6 @@ export interface DomainEventFactoryInterface {
|
||||
sharedVaultUuid: string
|
||||
itemUuid: string
|
||||
userUuid: string
|
||||
roleNames: string[]
|
||||
}): ItemRemovedFromSharedVaultEvent
|
||||
createSharedVaultRemovedEvent(dto: { sharedVaultUuid: string }): SharedVaultRemovedEvent
|
||||
}
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
import 'reflect-metadata'
|
||||
|
||||
import { AccountDeletionRequestedEvent } from '@standardnotes/domain-events'
|
||||
import { Logger } from 'winston'
|
||||
import { Item } from '../Item/Item'
|
||||
import { ItemRepositoryInterface } from '../Item/ItemRepositoryInterface'
|
||||
import { AccountDeletionRequestedEventHandler } from './AccountDeletionRequestedEventHandler'
|
||||
import { Uuid, ContentType, Dates, Timestamps, UniqueEntityId } from '@standardnotes/domain-core'
|
||||
import { ItemRepositoryResolverInterface } from '../Item/ItemRepositoryResolverInterface'
|
||||
|
||||
describe('AccountDeletionRequestedEventHandler', () => {
|
||||
let itemRepositoryResolver: ItemRepositoryResolverInterface
|
||||
let itemRepository: ItemRepositoryInterface
|
||||
let logger: Logger
|
||||
let event: AccountDeletionRequestedEvent
|
||||
let item: Item
|
||||
|
||||
const createHandler = () => new AccountDeletionRequestedEventHandler(itemRepositoryResolver, logger)
|
||||
|
||||
beforeEach(() => {
|
||||
item = 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()
|
||||
|
||||
itemRepository = {} as jest.Mocked<ItemRepositoryInterface>
|
||||
itemRepository.findAll = jest.fn().mockReturnValue([item])
|
||||
itemRepository.deleteByUserUuid = jest.fn()
|
||||
|
||||
itemRepositoryResolver = {} as jest.Mocked<ItemRepositoryResolverInterface>
|
||||
itemRepositoryResolver.resolve = jest.fn().mockReturnValue(itemRepository)
|
||||
|
||||
logger = {} as jest.Mocked<Logger>
|
||||
logger.info = jest.fn()
|
||||
|
||||
event = {} as jest.Mocked<AccountDeletionRequestedEvent>
|
||||
event.createdAt = new Date(1)
|
||||
event.payload = {
|
||||
userUuid: '2-3-4',
|
||||
userCreatedAtTimestamp: 1,
|
||||
regularSubscriptionUuid: '1-2-3',
|
||||
roleNames: ['CORE_USER'],
|
||||
}
|
||||
})
|
||||
|
||||
it('should remove all items for a user', async () => {
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(itemRepository.deleteByUserUuid).toHaveBeenCalledWith('2-3-4')
|
||||
})
|
||||
|
||||
it('should do nothing if role names are not valid', async () => {
|
||||
event.payload.roleNames = ['INVALID_ROLE_NAME']
|
||||
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(itemRepository.deleteByUserUuid).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
@@ -3,10 +3,12 @@ import { RoleNameCollection } from '@standardnotes/domain-core'
|
||||
import { Logger } from 'winston'
|
||||
|
||||
import { ItemRepositoryResolverInterface } from '../Item/ItemRepositoryResolverInterface'
|
||||
import { DeleteSharedVaults } from '../UseCase/SharedVaults/DeleteSharedVaults/DeleteSharedVaults'
|
||||
|
||||
export class AccountDeletionRequestedEventHandler implements DomainEventHandlerInterface {
|
||||
constructor(
|
||||
private itemRepositoryResolver: ItemRepositoryResolverInterface,
|
||||
private deleteSharedVaults: DeleteSharedVaults,
|
||||
private logger: Logger,
|
||||
) {}
|
||||
|
||||
@@ -21,6 +23,15 @@ export class AccountDeletionRequestedEventHandler implements DomainEventHandlerI
|
||||
|
||||
await itemRepository.deleteByUserUuid(event.payload.userUuid)
|
||||
|
||||
const result = await this.deleteSharedVaults.execute({
|
||||
ownerUuid: event.payload.userUuid,
|
||||
})
|
||||
if (result.isFailed()) {
|
||||
this.logger.error(`Failed to delete shared vaults for user: ${event.payload.userUuid}: ${result.getError()}`)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
this.logger.info(`Finished account cleanup for user: ${event.payload.userUuid}`)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import { DomainEventHandlerInterface, SharedVaultRemovedEvent } from '@standardnotes/domain-events'
|
||||
import { RemoveItemsFromSharedVault } from '../UseCase/SharedVaults/RemoveItemsFromSharedVault/RemoveItemsFromSharedVault'
|
||||
import { Logger } from 'winston'
|
||||
|
||||
export class SharedVaultRemovedEventHandler implements DomainEventHandlerInterface {
|
||||
constructor(
|
||||
private removeItemsFromSharedVault: RemoveItemsFromSharedVault,
|
||||
private logger: Logger,
|
||||
) {}
|
||||
|
||||
async handle(event: SharedVaultRemovedEvent): Promise<void> {
|
||||
const result = await this.removeItemsFromSharedVault.execute({
|
||||
sharedVaultUuid: event.payload.sharedVaultUuid,
|
||||
})
|
||||
|
||||
if (result.isFailed()) {
|
||||
this.logger.error(
|
||||
`Failed to remove items from shared vault ${event.payload.sharedVaultUuid}: ${result.getError()}`,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,7 @@ export class TransitionRequestedEventHandler implements DomainEventHandlerInterf
|
||||
}
|
||||
|
||||
if (await this.isAlreadyMigrated(userUuid)) {
|
||||
this.logger.info(`User ${event.payload.userUuid} already migrated.`)
|
||||
this.logger.info(`[${event.payload.userUuid}] User already migrated.`)
|
||||
|
||||
await this.domainEventPublisher.publish(
|
||||
this.domainEventFactory.createTransitionStatusUpdatedEvent({
|
||||
@@ -44,7 +44,7 @@ export class TransitionRequestedEventHandler implements DomainEventHandlerInterf
|
||||
return
|
||||
}
|
||||
|
||||
this.logger.info(`Handling transition requested event for user ${event.payload.userUuid}`)
|
||||
this.logger.info(`[${event.payload.userUuid}] Handling transition requested event`)
|
||||
|
||||
await this.domainEventPublisher.publish(
|
||||
this.domainEventFactory.createTransitionStatusUpdatedEvent({
|
||||
@@ -60,7 +60,7 @@ export class TransitionRequestedEventHandler implements DomainEventHandlerInterf
|
||||
})
|
||||
|
||||
if (result.isFailed()) {
|
||||
this.logger.error(`Failed to trigger transition for user ${event.payload.userUuid}`)
|
||||
this.logger.error(`[${event.payload.userUuid}] Failed to trigger transition: ${result.getError()}`)
|
||||
|
||||
await this.domainEventPublisher.publish(
|
||||
this.domainEventFactory.createTransitionStatusUpdatedEvent({
|
||||
@@ -90,7 +90,7 @@ export class TransitionRequestedEventHandler implements DomainEventHandlerInterf
|
||||
})
|
||||
|
||||
if (totalItemsCountForUserInPrimary > 0) {
|
||||
this.logger.info(`User ${userUuid.value} has ${totalItemsCountForUserInPrimary} items in primary database.`)
|
||||
this.logger.info(`[${userUuid.value}] User has ${totalItemsCountForUserInPrimary} items in primary database.`)
|
||||
}
|
||||
|
||||
return totalItemsCountForUserInPrimary === 0
|
||||
@@ -99,7 +99,7 @@ export class TransitionRequestedEventHandler implements DomainEventHandlerInterf
|
||||
private async getUserUuidFromEvent(event: TransitionRequestedEvent): Promise<Uuid | null> {
|
||||
const userUuidOrError = Uuid.create(event.payload.userUuid)
|
||||
if (userUuidOrError.isFailed()) {
|
||||
this.logger.error(`Failed to transition items for user ${event.payload.userUuid}: ${userUuidOrError.getError()}`)
|
||||
this.logger.error(`[${event.payload.userUuid}] Failed to transition items: ${userUuidOrError.getError()}`)
|
||||
|
||||
await this.domainEventPublisher.publish(
|
||||
this.domainEventFactory.createTransitionStatusUpdatedEvent({
|
||||
|
||||
@@ -15,7 +15,9 @@ export interface ItemRepositoryInterface {
|
||||
findByUuidAndUserUuid(uuid: string, userUuid: string): Promise<Item | null>
|
||||
findByUuid(uuid: Uuid): Promise<Item | null>
|
||||
remove(item: Item): Promise<void>
|
||||
removeByUuid(uuid: Uuid): Promise<void>
|
||||
save(item: Item): Promise<void>
|
||||
markItemsAsDeleted(itemUuids: Array<string>, updatedAtTimestamp: number): Promise<void>
|
||||
updateContentSize(itemUuid: string, contentSize: number): Promise<void>
|
||||
unassignFromSharedVault(sharedVaultUuid: Uuid): Promise<void>
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ describe('Notification', () => {
|
||||
it('should create an entity', () => {
|
||||
const payload = NotificationPayload.create({
|
||||
sharedVaultUuid: Uuid.create('0e8c3c7e-3f1a-4f7a-9b5a-5b2b0a7d4b1e').getValue(),
|
||||
type: NotificationType.create(NotificationType.TYPES.RemovedFromSharedVault).getValue(),
|
||||
type: NotificationType.create(NotificationType.TYPES.SelfRemovedFromSharedVault).getValue(),
|
||||
version: '1.0',
|
||||
}).getValue()
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import { SharedVault } from './SharedVault'
|
||||
|
||||
export interface SharedVaultRepositoryInterface {
|
||||
findByUuid(uuid: Uuid): Promise<SharedVault | null>
|
||||
findByUserUuid(userUuid: Uuid): Promise<SharedVault[]>
|
||||
countByUserUuid(userUuid: Uuid): Promise<number>
|
||||
findByUuids(uuids: Uuid[], lastSyncTime?: number): Promise<SharedVault[]>
|
||||
save(sharedVault: SharedVault): Promise<void>
|
||||
|
||||
@@ -6,7 +6,7 @@ export interface SharedVaultInviteRepositoryInterface {
|
||||
findByUuid(sharedVaultInviteUuid: Uuid): Promise<SharedVaultInvite | null>
|
||||
save(sharedVaultInvite: SharedVaultInvite): Promise<void>
|
||||
remove(sharedVaultInvite: SharedVaultInvite): Promise<void>
|
||||
removeBySharedVaultUuid(sharedVaultUuid: Uuid): Promise<void>
|
||||
findBySharedVaultUuid(sharedVaultUuid: Uuid): Promise<SharedVaultInvite[]>
|
||||
findByUserUuid(userUuid: Uuid): Promise<SharedVaultInvite[]>
|
||||
findByUserUuidUpdatedAfter(userUuid: Uuid, updatedAtTimestamp: number): Promise<SharedVaultInvite[]>
|
||||
findBySenderUuid(senderUuid: Uuid): Promise<SharedVaultInvite[]>
|
||||
|
||||
@@ -29,7 +29,7 @@ describe('AddNotificationForUser', () => {
|
||||
|
||||
payload = NotificationPayload.create({
|
||||
sharedVaultUuid: Uuid.create('0e8c3c7e-3f1a-4f7a-9b5a-5b2b0a7d4b1e').getValue(),
|
||||
type: NotificationType.create(NotificationType.TYPES.RemovedFromSharedVault).getValue(),
|
||||
type: NotificationType.create(NotificationType.TYPES.SelfRemovedFromSharedVault).getValue(),
|
||||
version: '1.0',
|
||||
}).getValue()
|
||||
|
||||
@@ -50,7 +50,7 @@ describe('AddNotificationForUser', () => {
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: '0e8c3c7e-3f1a-4f7a-9b5a-5b2b0a7d4b1e',
|
||||
type: NotificationType.TYPES.RemovedFromSharedVault,
|
||||
type: NotificationType.TYPES.SelfRemovedFromSharedVault,
|
||||
payload,
|
||||
version: '1.0',
|
||||
})
|
||||
@@ -63,7 +63,7 @@ describe('AddNotificationForUser', () => {
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: 'invalid',
|
||||
type: NotificationType.TYPES.RemovedFromSharedVault,
|
||||
type: NotificationType.TYPES.SelfRemovedFromSharedVault,
|
||||
payload,
|
||||
version: '1.0',
|
||||
})
|
||||
@@ -94,7 +94,7 @@ describe('AddNotificationForUser', () => {
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: '0e8c3c7e-3f1a-4f7a-9b5a-5b2b0a7d4b1e',
|
||||
type: NotificationType.TYPES.RemovedFromSharedVault,
|
||||
type: NotificationType.TYPES.SelfRemovedFromSharedVault,
|
||||
payload,
|
||||
version: '1.0',
|
||||
})
|
||||
@@ -111,7 +111,7 @@ describe('AddNotificationForUser', () => {
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: '0e8c3c7e-3f1a-4f7a-9b5a-5b2b0a7d4b1e',
|
||||
type: NotificationType.TYPES.RemovedFromSharedVault,
|
||||
type: NotificationType.TYPES.SelfRemovedFromSharedVault,
|
||||
payload,
|
||||
version: '1.0',
|
||||
})
|
||||
|
||||
@@ -54,6 +54,35 @@ describe('AddNotificationsForUsers', () => {
|
||||
expect(addNotificationForUser.execute).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('should not add notification for exceptUserUuid', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
exceptUserUuid: '00000000-0000-0000-0000-000000000000',
|
||||
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
|
||||
type: 'test',
|
||||
payload,
|
||||
version: '1.0',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBeFalsy()
|
||||
expect(addNotificationForUser.execute).toHaveBeenCalledTimes(0)
|
||||
})
|
||||
|
||||
it('should return error if exceptUserUuid is invalid', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
exceptUserUuid: 'invalid',
|
||||
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
|
||||
type: 'test',
|
||||
payload,
|
||||
version: '1.0',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBeTruthy()
|
||||
})
|
||||
|
||||
it('should return error if shared vault uuid is invalid', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
|
||||
@@ -17,8 +17,21 @@ export class AddNotificationsForUsers implements UseCaseInterface<void> {
|
||||
}
|
||||
const sharedVaultUuid = sharedVaultUuidOrError.getValue()
|
||||
|
||||
let exceptUserUuid: Uuid | undefined
|
||||
if (dto.exceptUserUuid) {
|
||||
const exceptUserUuidOrError = Uuid.create(dto.exceptUserUuid)
|
||||
if (exceptUserUuidOrError.isFailed()) {
|
||||
return Result.fail(exceptUserUuidOrError.getError())
|
||||
}
|
||||
exceptUserUuid = exceptUserUuidOrError.getValue()
|
||||
}
|
||||
|
||||
const sharedVaultUsers = await this.sharedVaultUserRepository.findBySharedVaultUuid(sharedVaultUuid)
|
||||
for (const sharedVaultUser of sharedVaultUsers) {
|
||||
if (exceptUserUuid && sharedVaultUser.props.userUuid.equals(exceptUserUuid)) {
|
||||
continue
|
||||
}
|
||||
|
||||
const result = await this.addNotificationForUser.execute({
|
||||
userUuid: sharedVaultUser.props.userUuid.value,
|
||||
type: dto.type,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { NotificationPayload } from '@standardnotes/domain-core'
|
||||
|
||||
export interface AddNotificationsForUsersDTO {
|
||||
exceptUserUuid?: string
|
||||
sharedVaultUuid: string
|
||||
version: string
|
||||
type: string
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import { SharedVaultUserPermission, Timestamps, Uuid } from '@standardnotes/domain-core'
|
||||
import { NotificationPayload, Result, SharedVaultUserPermission, Timestamps, Uuid } from '@standardnotes/domain-core'
|
||||
import { SharedVaultInviteRepositoryInterface } from '../../../SharedVault/User/Invite/SharedVaultInviteRepositoryInterface'
|
||||
import { DeclineInviteToSharedVault } from './DeclineInviteToSharedVault'
|
||||
import { SharedVaultInvite } from '../../../SharedVault/User/Invite/SharedVaultInvite'
|
||||
import { AddNotificationForUser } from '../../Messaging/AddNotificationForUser/AddNotificationForUser'
|
||||
|
||||
describe('DeclineInviteToSharedVault', () => {
|
||||
let sharedVaultInviteRepository: SharedVaultInviteRepositoryInterface
|
||||
let invite: SharedVaultInvite
|
||||
let addNotificationForUser: AddNotificationForUser
|
||||
|
||||
const createUseCase = () => new DeclineInviteToSharedVault(sharedVaultInviteRepository)
|
||||
const createUseCase = () => new DeclineInviteToSharedVault(sharedVaultInviteRepository, addNotificationForUser)
|
||||
|
||||
beforeEach(() => {
|
||||
invite = SharedVaultInvite.create({
|
||||
@@ -22,6 +24,9 @@ describe('DeclineInviteToSharedVault', () => {
|
||||
sharedVaultInviteRepository = {} as jest.Mocked<SharedVaultInviteRepositoryInterface>
|
||||
sharedVaultInviteRepository.findByUuid = jest.fn().mockResolvedValue(invite)
|
||||
sharedVaultInviteRepository.remove = jest.fn()
|
||||
|
||||
addNotificationForUser = {} as jest.Mocked<AddNotificationForUser>
|
||||
addNotificationForUser.execute = jest.fn().mockReturnValue(Result.ok())
|
||||
})
|
||||
|
||||
it('should fail if invite uuid is invalid', async () => {
|
||||
@@ -36,6 +41,37 @@ describe('DeclineInviteToSharedVault', () => {
|
||||
expect(result.getError()).toBe('Given value is not a valid uuid: invalid')
|
||||
})
|
||||
|
||||
it('should fail if adding a notification for user fails', async () => {
|
||||
addNotificationForUser.execute = jest.fn().mockReturnValue(Result.fail('Error'))
|
||||
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
inviteUuid: '00000000-0000-0000-0000-000000000000',
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
expect(result.getError()).toBe('Error')
|
||||
})
|
||||
|
||||
it('should return error if notification payload could not be created', async () => {
|
||||
const mock = jest.spyOn(NotificationPayload, 'create')
|
||||
mock.mockReturnValue(Result.fail('Oops'))
|
||||
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
inviteUuid: '00000000-0000-0000-0000-000000000000',
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
expect(result.getError()).toBe('Oops')
|
||||
|
||||
mock.mockRestore()
|
||||
})
|
||||
|
||||
it('should fail if originator uuid is invalid', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
import { Result, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
|
||||
import { NotificationPayload, NotificationType, Result, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
|
||||
|
||||
import { DeclineInviteToSharedVaultDTO } from './DeclineInviteToSharedVaultDTO'
|
||||
import { SharedVaultInviteRepositoryInterface } from '../../../SharedVault/User/Invite/SharedVaultInviteRepositoryInterface'
|
||||
import { AddNotificationForUser } from '../../Messaging/AddNotificationForUser/AddNotificationForUser'
|
||||
|
||||
export class DeclineInviteToSharedVault implements UseCaseInterface<void> {
|
||||
constructor(private sharedVaultInviteRepository: SharedVaultInviteRepositoryInterface) {}
|
||||
constructor(
|
||||
private sharedVaultInviteRepository: SharedVaultInviteRepositoryInterface,
|
||||
private addNotificationForUser: AddNotificationForUser,
|
||||
) {}
|
||||
|
||||
async execute(dto: DeclineInviteToSharedVaultDTO): Promise<Result<void>> {
|
||||
const inviteUuidOrError = Uuid.create(dto.inviteUuid)
|
||||
@@ -29,6 +34,26 @@ export class DeclineInviteToSharedVault implements UseCaseInterface<void> {
|
||||
|
||||
await this.sharedVaultInviteRepository.remove(invite)
|
||||
|
||||
const notificationPayloadOrError = NotificationPayload.create({
|
||||
sharedVaultUuid: invite.props.sharedVaultUuid,
|
||||
type: NotificationType.create(NotificationType.TYPES.SharedVaultInviteDeclined).getValue(),
|
||||
version: '1.0',
|
||||
})
|
||||
if (notificationPayloadOrError.isFailed()) {
|
||||
return Result.fail(notificationPayloadOrError.getError())
|
||||
}
|
||||
const notificationPayload = notificationPayloadOrError.getValue()
|
||||
|
||||
const result = await this.addNotificationForUser.execute({
|
||||
userUuid: invite.props.userUuid.value,
|
||||
type: NotificationType.TYPES.SharedVaultInviteDeclined,
|
||||
payload: notificationPayload,
|
||||
version: '1.0',
|
||||
})
|
||||
if (result.isFailed()) {
|
||||
return Result.fail(result.getError())
|
||||
}
|
||||
|
||||
return Result.ok()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Uuid, Timestamps, Result, SharedVaultUserPermission, SharedVaultUser } from '@standardnotes/domain-core'
|
||||
import { DomainEventInterface, DomainEventPublisherInterface } from '@standardnotes/domain-events'
|
||||
|
||||
import { SharedVaultRepositoryInterface } from '../../../SharedVault/SharedVaultRepositoryInterface'
|
||||
import { SharedVaultInviteRepositoryInterface } from '../../../SharedVault/User/Invite/SharedVaultInviteRepositoryInterface'
|
||||
@@ -6,14 +7,21 @@ import { SharedVaultUserRepositoryInterface } from '../../../SharedVault/User/Sh
|
||||
import { DeleteSharedVault } from './DeleteSharedVault'
|
||||
import { SharedVault } from '../../../SharedVault/SharedVault'
|
||||
import { RemoveUserFromSharedVault } from '../RemoveUserFromSharedVault/RemoveUserFromSharedVault'
|
||||
import { DeclineInviteToSharedVault } from '../DeclineInviteToSharedVault/DeclineInviteToSharedVault'
|
||||
import { SharedVaultInvite } from '../../../SharedVault/User/Invite/SharedVaultInvite'
|
||||
import { DomainEventFactoryInterface } from '../../../Event/DomainEventFactoryInterface'
|
||||
|
||||
describe('DeleteSharedVault', () => {
|
||||
let sharedVaultRepository: SharedVaultRepositoryInterface
|
||||
let sharedVaultUserRepository: SharedVaultUserRepositoryInterface
|
||||
let sharedVaultInviteRepository: SharedVaultInviteRepositoryInterface
|
||||
let removeUserFromSharedVault: RemoveUserFromSharedVault
|
||||
let declineInviteToSharedVault: DeclineInviteToSharedVault
|
||||
let sharedVault: SharedVault
|
||||
let sharedVaultUser: SharedVaultUser
|
||||
let sharedVaultInvite: SharedVaultInvite
|
||||
let domainEventFactory: DomainEventFactoryInterface
|
||||
let domainEventPublisher: DomainEventPublisherInterface
|
||||
|
||||
const createUseCase = () =>
|
||||
new DeleteSharedVault(
|
||||
@@ -21,6 +29,9 @@ describe('DeleteSharedVault', () => {
|
||||
sharedVaultUserRepository,
|
||||
sharedVaultInviteRepository,
|
||||
removeUserFromSharedVault,
|
||||
declineInviteToSharedVault,
|
||||
domainEventFactory,
|
||||
domainEventPublisher,
|
||||
)
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -42,11 +53,33 @@ describe('DeleteSharedVault', () => {
|
||||
sharedVaultUserRepository = {} as jest.Mocked<SharedVaultUserRepositoryInterface>
|
||||
sharedVaultUserRepository.findBySharedVaultUuid = jest.fn().mockResolvedValue([sharedVaultUser])
|
||||
|
||||
sharedVaultInvite = SharedVaultInvite.create({
|
||||
encryptedMessage: 'test',
|
||||
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
sharedVaultUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
senderUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
permission: SharedVaultUserPermission.create(SharedVaultUserPermission.PERMISSIONS.Read).getValue(),
|
||||
timestamps: Timestamps.create(123, 123).getValue(),
|
||||
}).getValue()
|
||||
sharedVaultInviteRepository = {} as jest.Mocked<SharedVaultInviteRepositoryInterface>
|
||||
sharedVaultInviteRepository.removeBySharedVaultUuid = jest.fn()
|
||||
sharedVaultInviteRepository.findBySharedVaultUuid = jest.fn().mockReturnValue([sharedVaultInvite])
|
||||
|
||||
declineInviteToSharedVault = {} as jest.Mocked<DeclineInviteToSharedVault>
|
||||
declineInviteToSharedVault.execute = jest.fn().mockReturnValue(Result.ok())
|
||||
|
||||
removeUserFromSharedVault = {} as jest.Mocked<RemoveUserFromSharedVault>
|
||||
removeUserFromSharedVault.execute = jest.fn().mockReturnValue(Result.ok())
|
||||
|
||||
domainEventFactory = {} as jest.Mocked<DomainEventFactoryInterface>
|
||||
domainEventFactory.createUserRemovedFromSharedVaultEvent = jest
|
||||
.fn()
|
||||
.mockReturnValue({} as jest.Mocked<DomainEventInterface>)
|
||||
domainEventFactory.createSharedVaultRemovedEvent = jest
|
||||
.fn()
|
||||
.mockReturnValue({} as jest.Mocked<DomainEventInterface>)
|
||||
|
||||
domainEventPublisher = {} as jest.Mocked<DomainEventPublisherInterface>
|
||||
domainEventPublisher.publish = jest.fn()
|
||||
})
|
||||
|
||||
it('should remove shared vault', async () => {
|
||||
@@ -59,7 +92,7 @@ describe('DeleteSharedVault', () => {
|
||||
|
||||
expect(result.isFailed()).toBeFalsy()
|
||||
expect(sharedVaultRepository.remove).toHaveBeenCalled()
|
||||
expect(sharedVaultInviteRepository.removeBySharedVaultUuid).toHaveBeenCalled()
|
||||
expect(declineInviteToSharedVault.execute).toHaveBeenCalled()
|
||||
expect(removeUserFromSharedVault.execute).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
@@ -74,7 +107,7 @@ describe('DeleteSharedVault', () => {
|
||||
|
||||
expect(result.isFailed()).toBeTruthy()
|
||||
expect(sharedVaultRepository.remove).not.toHaveBeenCalled()
|
||||
expect(sharedVaultInviteRepository.removeBySharedVaultUuid).not.toHaveBeenCalled()
|
||||
expect(declineInviteToSharedVault.execute).not.toHaveBeenCalled()
|
||||
expect(removeUserFromSharedVault.execute).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
@@ -88,7 +121,7 @@ describe('DeleteSharedVault', () => {
|
||||
|
||||
expect(result.isFailed()).toBeTruthy()
|
||||
expect(sharedVaultRepository.remove).not.toHaveBeenCalled()
|
||||
expect(sharedVaultInviteRepository.removeBySharedVaultUuid).not.toHaveBeenCalled()
|
||||
expect(declineInviteToSharedVault.execute).not.toHaveBeenCalled()
|
||||
expect(removeUserFromSharedVault.execute).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
@@ -102,7 +135,7 @@ describe('DeleteSharedVault', () => {
|
||||
|
||||
expect(result.isFailed()).toBeTruthy()
|
||||
expect(sharedVaultRepository.remove).not.toHaveBeenCalled()
|
||||
expect(sharedVaultInviteRepository.removeBySharedVaultUuid).not.toHaveBeenCalled()
|
||||
expect(declineInviteToSharedVault.execute).not.toHaveBeenCalled()
|
||||
expect(removeUserFromSharedVault.execute).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
@@ -122,7 +155,7 @@ describe('DeleteSharedVault', () => {
|
||||
|
||||
expect(result.isFailed()).toBeTruthy()
|
||||
expect(sharedVaultRepository.remove).not.toHaveBeenCalled()
|
||||
expect(sharedVaultInviteRepository.removeBySharedVaultUuid).not.toHaveBeenCalled()
|
||||
expect(declineInviteToSharedVault.execute).not.toHaveBeenCalled()
|
||||
expect(removeUserFromSharedVault.execute).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
@@ -137,7 +170,22 @@ describe('DeleteSharedVault', () => {
|
||||
|
||||
expect(result.isFailed()).toBeTruthy()
|
||||
expect(sharedVaultRepository.remove).not.toHaveBeenCalled()
|
||||
expect(sharedVaultInviteRepository.removeBySharedVaultUuid).not.toHaveBeenCalled()
|
||||
expect(declineInviteToSharedVault.execute).not.toHaveBeenCalled()
|
||||
expect(removeUserFromSharedVault.execute).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should return error if declining invite to shared vault fails', async () => {
|
||||
declineInviteToSharedVault.execute = jest.fn().mockReturnValue(Result.fail('failed'))
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
|
||||
originatorUuid: '00000000-0000-0000-0000-000000000000',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBeTruthy()
|
||||
expect(sharedVaultRepository.remove).not.toHaveBeenCalled()
|
||||
expect(declineInviteToSharedVault.execute).toHaveBeenCalled()
|
||||
expect(removeUserFromSharedVault.execute).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import { Result, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
|
||||
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
|
||||
|
||||
import { DeleteSharedVaultDTO } from './DeleteSharedVaultDTO'
|
||||
import { SharedVaultRepositoryInterface } from '../../../SharedVault/SharedVaultRepositoryInterface'
|
||||
import { SharedVaultUserRepositoryInterface } from '../../../SharedVault/User/SharedVaultUserRepositoryInterface'
|
||||
import { SharedVaultInviteRepositoryInterface } from '../../../SharedVault/User/Invite/SharedVaultInviteRepositoryInterface'
|
||||
import { RemoveUserFromSharedVault } from '../RemoveUserFromSharedVault/RemoveUserFromSharedVault'
|
||||
import { DeclineInviteToSharedVault } from '../DeclineInviteToSharedVault/DeclineInviteToSharedVault'
|
||||
import { DomainEventFactoryInterface } from '../../../Event/DomainEventFactoryInterface'
|
||||
|
||||
export class DeleteSharedVault implements UseCaseInterface<void> {
|
||||
constructor(
|
||||
@@ -12,6 +15,9 @@ export class DeleteSharedVault implements UseCaseInterface<void> {
|
||||
private sharedVaultUserRepository: SharedVaultUserRepositoryInterface,
|
||||
private sharedVaultInviteRepository: SharedVaultInviteRepositoryInterface,
|
||||
private removeUserFromSharedVault: RemoveUserFromSharedVault,
|
||||
private declineInviteToSharedVault: DeclineInviteToSharedVault,
|
||||
private domainEventFactory: DomainEventFactoryInterface,
|
||||
private domainEventPublisher: DomainEventPublisherInterface,
|
||||
) {}
|
||||
|
||||
async execute(dto: DeleteSharedVaultDTO): Promise<Result<void>> {
|
||||
@@ -50,10 +56,26 @@ export class DeleteSharedVault implements UseCaseInterface<void> {
|
||||
}
|
||||
}
|
||||
|
||||
await this.sharedVaultInviteRepository.removeBySharedVaultUuid(sharedVaultUuid)
|
||||
const sharedVaultInvites = await this.sharedVaultInviteRepository.findBySharedVaultUuid(sharedVaultUuid)
|
||||
for (const sharedVaultInvite of sharedVaultInvites) {
|
||||
const result = await this.declineInviteToSharedVault.execute({
|
||||
inviteUuid: sharedVaultInvite.id.toString(),
|
||||
userUuid: sharedVaultInvite.props.userUuid.value,
|
||||
})
|
||||
|
||||
if (result.isFailed()) {
|
||||
return Result.fail(result.getError())
|
||||
}
|
||||
}
|
||||
|
||||
await this.sharedVaultRepository.remove(sharedVault)
|
||||
|
||||
await this.domainEventPublisher.publish(
|
||||
this.domainEventFactory.createSharedVaultRemovedEvent({
|
||||
sharedVaultUuid: sharedVaultUuid.value,
|
||||
}),
|
||||
)
|
||||
|
||||
return Result.ok()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
import { Result, Timestamps, UniqueEntityId, Uuid } from '@standardnotes/domain-core'
|
||||
import { SharedVault } from '../../../SharedVault/SharedVault'
|
||||
import { SharedVaultRepositoryInterface } from '../../../SharedVault/SharedVaultRepositoryInterface'
|
||||
import { DeleteSharedVault } from '../DeleteSharedVault/DeleteSharedVault'
|
||||
import { DeleteSharedVaults } from './DeleteSharedVaults'
|
||||
|
||||
describe('DeleteSharedVaults', () => {
|
||||
let sharedVaultRepository: SharedVaultRepositoryInterface
|
||||
let deleteSharedVaultUseCase: DeleteSharedVault
|
||||
let sharedVault: SharedVault
|
||||
|
||||
const createUseCase = () => new DeleteSharedVaults(sharedVaultRepository, deleteSharedVaultUseCase)
|
||||
|
||||
beforeEach(() => {
|
||||
sharedVault = SharedVault.create(
|
||||
{
|
||||
fileUploadBytesUsed: 2,
|
||||
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
timestamps: Timestamps.create(123, 123).getValue(),
|
||||
},
|
||||
new UniqueEntityId('00000000-0000-0000-0000-000000000000'),
|
||||
).getValue()
|
||||
sharedVaultRepository = {} as jest.Mocked<SharedVaultRepositoryInterface>
|
||||
sharedVaultRepository.findByUserUuid = jest.fn().mockResolvedValue([sharedVault])
|
||||
|
||||
deleteSharedVaultUseCase = {} as jest.Mocked<DeleteSharedVault>
|
||||
deleteSharedVaultUseCase.execute = jest.fn().mockResolvedValue(Result.ok())
|
||||
})
|
||||
|
||||
it('should delete all shared vaults for a user', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
ownerUuid: '00000000-0000-0000-0000-000000000000',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(false)
|
||||
expect(sharedVaultRepository.findByUserUuid).toHaveBeenCalled()
|
||||
expect(deleteSharedVaultUseCase.execute).toHaveBeenCalledWith({
|
||||
originatorUuid: '00000000-0000-0000-0000-000000000000',
|
||||
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
|
||||
})
|
||||
})
|
||||
|
||||
it('should return error if delete shared vault fails', async () => {
|
||||
deleteSharedVaultUseCase.execute = jest.fn().mockResolvedValue(Result.fail('error'))
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
ownerUuid: '00000000-0000-0000-0000-000000000000',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
expect(sharedVaultRepository.findByUserUuid).toHaveBeenCalled()
|
||||
expect(deleteSharedVaultUseCase.execute).toHaveBeenCalledWith({
|
||||
originatorUuid: '00000000-0000-0000-0000-000000000000',
|
||||
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
|
||||
})
|
||||
})
|
||||
|
||||
it('should return error if owner uuid is invalid', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
ownerUuid: 'invalid',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBeTruthy()
|
||||
expect(sharedVaultRepository.findByUserUuid).not.toHaveBeenCalled()
|
||||
expect(deleteSharedVaultUseCase.execute).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,34 @@
|
||||
import { Result, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
|
||||
|
||||
import { DeleteSharedVaultsDTO } from './DeleteSharedVaultsDTO'
|
||||
import { DeleteSharedVault } from '../DeleteSharedVault/DeleteSharedVault'
|
||||
import { SharedVaultRepositoryInterface } from '../../../SharedVault/SharedVaultRepositoryInterface'
|
||||
|
||||
export class DeleteSharedVaults implements UseCaseInterface<void> {
|
||||
constructor(
|
||||
private sharedVaultRepository: SharedVaultRepositoryInterface,
|
||||
private deleteSharedVaultUseCase: DeleteSharedVault,
|
||||
) {}
|
||||
|
||||
async execute(dto: DeleteSharedVaultsDTO): Promise<Result<void>> {
|
||||
const ownerUuidOrError = Uuid.create(dto.ownerUuid)
|
||||
if (ownerUuidOrError.isFailed()) {
|
||||
return Result.fail(ownerUuidOrError.getError())
|
||||
}
|
||||
const ownerUuid = ownerUuidOrError.getValue()
|
||||
|
||||
const sharedVaults = await this.sharedVaultRepository.findByUserUuid(ownerUuid)
|
||||
|
||||
for (const sharedVault of sharedVaults) {
|
||||
const result = await this.deleteSharedVaultUseCase.execute({
|
||||
originatorUuid: ownerUuid.value,
|
||||
sharedVaultUuid: sharedVault.id.toString(),
|
||||
})
|
||||
if (result.isFailed()) {
|
||||
return Result.fail(result.getError())
|
||||
}
|
||||
}
|
||||
|
||||
return Result.ok()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export interface DeleteSharedVaultsDTO {
|
||||
ownerUuid: string
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import { ItemRepositoryInterface } from '../../../Item/ItemRepositoryInterface'
|
||||
import { RemoveItemsFromSharedVault } from './RemoveItemsFromSharedVault'
|
||||
|
||||
describe('RemoveItemsFromSharedVault', () => {
|
||||
let itemRepository: ItemRepositoryInterface
|
||||
|
||||
const createUseCase = () => new RemoveItemsFromSharedVault(itemRepository)
|
||||
|
||||
beforeEach(() => {
|
||||
itemRepository = {} as jest.Mocked<ItemRepositoryInterface>
|
||||
itemRepository.unassignFromSharedVault = jest.fn()
|
||||
})
|
||||
|
||||
it('should unassign items from shared vault', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
await useCase.execute({
|
||||
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
|
||||
})
|
||||
|
||||
expect(itemRepository.unassignFromSharedVault).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should return error when shared vault uuid is invalid', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
sharedVaultUuid: 'invalid-uuid',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,20 @@
|
||||
import { Result, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
|
||||
|
||||
import { ItemRepositoryInterface } from '../../../Item/ItemRepositoryInterface'
|
||||
import { RemoveItemsFromSharedVaultDTO } from './RemoveItemsFromSharedVaultDTO'
|
||||
|
||||
export class RemoveItemsFromSharedVault implements UseCaseInterface<void> {
|
||||
constructor(private itemRepository: ItemRepositoryInterface) {}
|
||||
|
||||
async execute(dto: RemoveItemsFromSharedVaultDTO): Promise<Result<void>> {
|
||||
const sharedVaultUuidOrError = Uuid.create(dto.sharedVaultUuid)
|
||||
if (sharedVaultUuidOrError.isFailed()) {
|
||||
return Result.fail(sharedVaultUuidOrError.getError())
|
||||
}
|
||||
const sharedVaultUuid = sharedVaultUuidOrError.getValue()
|
||||
|
||||
await this.itemRepository.unassignFromSharedVault(sharedVaultUuid)
|
||||
|
||||
return Result.ok()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export interface RemoveItemsFromSharedVaultDTO {
|
||||
sharedVaultUuid: string
|
||||
}
|
||||
@@ -14,11 +14,13 @@ import { RemoveUserFromSharedVault } from './RemoveUserFromSharedVault'
|
||||
import { DomainEventFactoryInterface } from '../../../Event/DomainEventFactoryInterface'
|
||||
import { DomainEventInterface, DomainEventPublisherInterface } from '@standardnotes/domain-events'
|
||||
import { AddNotificationsForUsers } from '../../Messaging/AddNotificationsForUsers/AddNotificationsForUsers'
|
||||
import { AddNotificationForUser } from '../../Messaging/AddNotificationForUser/AddNotificationForUser'
|
||||
|
||||
describe('RemoveUserFromSharedVault', () => {
|
||||
let sharedVaultRepository: SharedVaultRepositoryInterface
|
||||
let sharedVaultUserRepository: SharedVaultUserRepositoryInterface
|
||||
let addNotificationsForUsers: AddNotificationsForUsers
|
||||
let addNotificationForUser: AddNotificationForUser
|
||||
let sharedVault: SharedVault
|
||||
let sharedVaultUser: SharedVaultUser
|
||||
let domainEventFactory: DomainEventFactoryInterface
|
||||
@@ -29,6 +31,7 @@ describe('RemoveUserFromSharedVault', () => {
|
||||
sharedVaultUserRepository,
|
||||
sharedVaultRepository,
|
||||
addNotificationsForUsers,
|
||||
addNotificationForUser,
|
||||
domainEventFactory,
|
||||
domainEventPublisher,
|
||||
)
|
||||
@@ -56,6 +59,9 @@ describe('RemoveUserFromSharedVault', () => {
|
||||
addNotificationsForUsers = {} as jest.Mocked<AddNotificationsForUsers>
|
||||
addNotificationsForUsers.execute = jest.fn().mockReturnValue(Result.ok())
|
||||
|
||||
addNotificationForUser = {} as jest.Mocked<AddNotificationForUser>
|
||||
addNotificationForUser.execute = jest.fn().mockReturnValue(Result.ok())
|
||||
|
||||
domainEventFactory = {} as jest.Mocked<DomainEventFactoryInterface>
|
||||
domainEventFactory.createUserRemovedFromSharedVaultEvent = jest
|
||||
.fn()
|
||||
@@ -215,6 +221,19 @@ describe('RemoveUserFromSharedVault', () => {
|
||||
expect(result.isFailed()).toBe(true)
|
||||
})
|
||||
|
||||
it('should return error if notification could not be added for the user removed', async () => {
|
||||
addNotificationForUser.execute = jest.fn().mockResolvedValue(Result.fail('Could not add notification'))
|
||||
|
||||
const useCase = createUseCase()
|
||||
const result = await useCase.execute({
|
||||
originatorUuid: '00000000-0000-0000-0000-000000000000',
|
||||
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
|
||||
userUuid: '00000000-0000-0000-0000-000000000001',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
})
|
||||
|
||||
it('should return error if notification payload could not be created', async () => {
|
||||
const mock = jest.spyOn(NotificationPayload, 'create')
|
||||
mock.mockReturnValue(Result.fail('Oops'))
|
||||
@@ -231,4 +250,21 @@ describe('RemoveUserFromSharedVault', () => {
|
||||
|
||||
mock.mockRestore()
|
||||
})
|
||||
|
||||
it('should return error if self notification payload could not be created', async () => {
|
||||
const mock = jest.spyOn(NotificationPayload, 'create')
|
||||
mock.mockReturnValueOnce(Result.ok()).mockReturnValueOnce(Result.fail('Oops'))
|
||||
|
||||
const useCase = createUseCase()
|
||||
const result = await useCase.execute({
|
||||
originatorUuid: '00000000-0000-0000-0000-000000000000',
|
||||
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
|
||||
userUuid: '00000000-0000-0000-0000-000000000001',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
expect(result.getError()).toBe('Oops')
|
||||
|
||||
mock.mockRestore()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -6,12 +6,14 @@ import { SharedVaultRepositoryInterface } from '../../../SharedVault/SharedVault
|
||||
import { SharedVaultUserRepositoryInterface } from '../../../SharedVault/User/SharedVaultUserRepositoryInterface'
|
||||
import { DomainEventFactoryInterface } from '../../../Event/DomainEventFactoryInterface'
|
||||
import { AddNotificationsForUsers } from '../../Messaging/AddNotificationsForUsers/AddNotificationsForUsers'
|
||||
import { AddNotificationForUser } from '../../Messaging/AddNotificationForUser/AddNotificationForUser'
|
||||
|
||||
export class RemoveUserFromSharedVault implements UseCaseInterface<void> {
|
||||
constructor(
|
||||
private sharedVaultUsersRepository: SharedVaultUserRepositoryInterface,
|
||||
private sharedVaultRepository: SharedVaultRepositoryInterface,
|
||||
private addNotificationForUsers: AddNotificationsForUsers,
|
||||
private addNotificationsForUsers: AddNotificationsForUsers,
|
||||
private addNotificationForUser: AddNotificationForUser,
|
||||
private domainEventFactory: DomainEventFactoryInterface,
|
||||
private domainEventPublisher: DomainEventPublisherInterface,
|
||||
) {}
|
||||
@@ -63,7 +65,7 @@ export class RemoveUserFromSharedVault implements UseCaseInterface<void> {
|
||||
|
||||
const notificationPayloadOrError = NotificationPayload.create({
|
||||
sharedVaultUuid: sharedVault.uuid,
|
||||
type: NotificationType.create(NotificationType.TYPES.RemovedFromSharedVault).getValue(),
|
||||
type: NotificationType.create(NotificationType.TYPES.UserRemovedFromSharedVault).getValue(),
|
||||
version: '1.0',
|
||||
})
|
||||
if (notificationPayloadOrError.isFailed()) {
|
||||
@@ -71,9 +73,10 @@ export class RemoveUserFromSharedVault implements UseCaseInterface<void> {
|
||||
}
|
||||
const notificationPayload = notificationPayloadOrError.getValue()
|
||||
|
||||
const result = await this.addNotificationForUsers.execute({
|
||||
const result = await this.addNotificationsForUsers.execute({
|
||||
sharedVaultUuid: sharedVault.id.toString(),
|
||||
type: NotificationType.TYPES.RemovedFromSharedVault,
|
||||
exceptUserUuid: userUuid.value,
|
||||
type: NotificationType.TYPES.UserRemovedFromSharedVault,
|
||||
payload: notificationPayload,
|
||||
version: '1.0',
|
||||
})
|
||||
@@ -81,6 +84,26 @@ export class RemoveUserFromSharedVault implements UseCaseInterface<void> {
|
||||
return Result.fail(result.getError())
|
||||
}
|
||||
|
||||
const selfNotificationPayloadOrError = NotificationPayload.create({
|
||||
sharedVaultUuid: sharedVault.uuid,
|
||||
type: NotificationType.create(NotificationType.TYPES.SelfRemovedFromSharedVault).getValue(),
|
||||
version: '1.0',
|
||||
})
|
||||
if (selfNotificationPayloadOrError.isFailed()) {
|
||||
return Result.fail(selfNotificationPayloadOrError.getError())
|
||||
}
|
||||
const selfNotificationPayload = selfNotificationPayloadOrError.getValue()
|
||||
|
||||
const selfResult = await this.addNotificationForUser.execute({
|
||||
userUuid: userUuid.value,
|
||||
type: NotificationType.TYPES.SelfRemovedFromSharedVault,
|
||||
payload: selfNotificationPayload,
|
||||
version: '1.0',
|
||||
})
|
||||
if (selfResult.isFailed()) {
|
||||
return Result.fail(selfResult.getError())
|
||||
}
|
||||
|
||||
await this.domainEventPublisher.publish(
|
||||
this.domainEventFactory.createUserRemovedFromSharedVaultEvent({
|
||||
sharedVaultUuid: dto.sharedVaultUuid,
|
||||
|
||||
@@ -270,7 +270,6 @@ export class UpdateExistingItem implements UseCaseInterface<Item> {
|
||||
sharedVaultUuid: sharedVaultOperation.props.sharedVaultUuid.value,
|
||||
itemUuid: dto.existingItem.uuid.value,
|
||||
userUuid: userUuid.value,
|
||||
roleNames: dto.roleNames,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ export class TransitionItemsFromPrimaryToSecondaryDatabaseForUser implements Use
|
||||
) {}
|
||||
|
||||
async execute(dto: TransitionItemsFromPrimaryToSecondaryDatabaseForUserDTO): Promise<Result<void>> {
|
||||
this.logger.info(`Transitioning items for user ${dto.userUuid}`)
|
||||
this.logger.info(`[${dto.userUuid}] Transitioning items`)
|
||||
|
||||
if (this.secondaryItemRepository === null) {
|
||||
return Result.fail('Secondary item repository is not set')
|
||||
@@ -31,28 +31,26 @@ export class TransitionItemsFromPrimaryToSecondaryDatabaseForUser implements Use
|
||||
const userUuid = userUuidOrError.getValue()
|
||||
|
||||
let newItemsInSecondaryCount = 0
|
||||
let updatedItemsInSecondary: Item[] = []
|
||||
let updatedItemsInSecondary: string[] = []
|
||||
let alreadyIdenticalInSecondaryAndPrimary: string[] = []
|
||||
if (await this.hasAlreadyDataInSecondaryDatabase(userUuid)) {
|
||||
const { alreadyExistingInPrimary, newItemsInSecondary, updatedInSecondary } =
|
||||
const { alreadyExistingInSecondaryAndPrimary, newItemsInSecondary, updatedInSecondary } =
|
||||
await this.getNewItemsCreatedInSecondaryDatabase(userUuid)
|
||||
|
||||
for (const existingItem of alreadyExistingInPrimary) {
|
||||
this.logger.info(`Removing item ${existingItem.uuid.value} from secondary database`)
|
||||
await (this.secondaryItemRepository as ItemRepositoryInterface).remove(existingItem)
|
||||
}
|
||||
this.logger.info(
|
||||
`[${dto.userUuid}] ${alreadyExistingInSecondaryAndPrimary.length} already existing identical items in primary and secondary.`,
|
||||
)
|
||||
|
||||
alreadyIdenticalInSecondaryAndPrimary = alreadyExistingInSecondaryAndPrimary
|
||||
|
||||
if (newItemsInSecondary.length > 0) {
|
||||
this.logger.info(
|
||||
`Found ${newItemsInSecondary.length} new items in secondary database for user ${userUuid.value}`,
|
||||
)
|
||||
this.logger.info(`[${dto.userUuid}] Found ${newItemsInSecondary.length} new items in secondary database.`)
|
||||
}
|
||||
|
||||
newItemsInSecondaryCount = newItemsInSecondary.length
|
||||
|
||||
if (updatedInSecondary.length > 0) {
|
||||
this.logger.info(
|
||||
`Found ${updatedInSecondary.length} updated items in secondary database for user ${userUuid.value}`,
|
||||
)
|
||||
this.logger.info(`[${dto.userUuid}] Found ${updatedInSecondary.length} updated items in secondary database.`)
|
||||
}
|
||||
|
||||
updatedItemsInSecondary = updatedInSecondary
|
||||
@@ -63,13 +61,19 @@ export class TransitionItemsFromPrimaryToSecondaryDatabaseForUser implements Use
|
||||
|
||||
const migrationTimeStart = this.timer.getTimestampInMicroseconds()
|
||||
|
||||
const migrationResult = await this.migrateItemsForUser(userUuid, updatedItemsInSecondary)
|
||||
this.logger.info(`[${dto.userUuid}] Migrating items`)
|
||||
|
||||
const migrationResult = await this.migrateItemsForUser(
|
||||
userUuid,
|
||||
updatedItemsInSecondary,
|
||||
alreadyIdenticalInSecondaryAndPrimary,
|
||||
)
|
||||
if (migrationResult.isFailed()) {
|
||||
if (newItemsInSecondaryCount === 0 && updatedItemsInSecondaryCount === 0) {
|
||||
const cleanupResult = await this.deleteItemsForUser(userUuid, this.secondaryItemRepository)
|
||||
if (cleanupResult.isFailed()) {
|
||||
this.logger.error(
|
||||
`Failed to clean up secondary database items for user ${userUuid.value}: ${cleanupResult.getError()}`,
|
||||
`[${dto.userUuid}] Failed to clean up secondary database items: ${cleanupResult.getError()}`,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -83,13 +87,14 @@ export class TransitionItemsFromPrimaryToSecondaryDatabaseForUser implements Use
|
||||
userUuid,
|
||||
newItemsInSecondaryCount,
|
||||
updatedItemsInSecondary,
|
||||
alreadyIdenticalInSecondaryAndPrimary,
|
||||
)
|
||||
if (integrityCheckResult.isFailed()) {
|
||||
if (newItemsInSecondaryCount === 0 && updatedItemsInSecondaryCount === 0) {
|
||||
const cleanupResult = await this.deleteItemsForUser(userUuid, this.secondaryItemRepository)
|
||||
if (cleanupResult.isFailed()) {
|
||||
this.logger.error(
|
||||
`Failed to clean up secondary database items for user ${userUuid.value}: ${cleanupResult.getError()}`,
|
||||
`[${dto.userUuid}] Failed to clean up secondary database items: ${cleanupResult.getError()}`,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -99,9 +104,7 @@ export class TransitionItemsFromPrimaryToSecondaryDatabaseForUser implements Use
|
||||
|
||||
const cleanupResult = await this.deleteItemsForUser(userUuid, this.primaryItemRepository)
|
||||
if (cleanupResult.isFailed()) {
|
||||
this.logger.error(
|
||||
`Failed to clean up primary database items for user ${userUuid.value}: ${cleanupResult.getError()}`,
|
||||
)
|
||||
this.logger.error(`[${dto.userUuid}] Failed to clean up primary database items: ${cleanupResult.getError()}`)
|
||||
}
|
||||
|
||||
const migrationTimeEnd = this.timer.getTimestampInMicroseconds()
|
||||
@@ -110,7 +113,7 @@ export class TransitionItemsFromPrimaryToSecondaryDatabaseForUser implements Use
|
||||
const migrationDurationTimeStructure = this.timer.convertMicrosecondsToTimeStructure(migrationDuration)
|
||||
|
||||
this.logger.info(
|
||||
`Transitioned items for user ${userUuid.value} in ${migrationDurationTimeStructure.hours}h ${migrationDurationTimeStructure.minutes}m ${migrationDurationTimeStructure.seconds}s ${migrationDurationTimeStructure.milliseconds}ms`,
|
||||
`[${dto.userUuid}] Transitioned items in ${migrationDurationTimeStructure.hours}h ${migrationDurationTimeStructure.minutes}m ${migrationDurationTimeStructure.seconds}s ${migrationDurationTimeStructure.milliseconds}ms`,
|
||||
)
|
||||
|
||||
return Result.ok()
|
||||
@@ -123,48 +126,61 @@ export class TransitionItemsFromPrimaryToSecondaryDatabaseForUser implements Use
|
||||
|
||||
const hasAlreadyDataInSecondaryDatabase = totalItemsCountForUser > 0
|
||||
if (hasAlreadyDataInSecondaryDatabase) {
|
||||
this.logger.info(`User ${userUuid.value} has already ${totalItemsCountForUser} items in secondary database`)
|
||||
this.logger.info(`[${userUuid.value}] User has already ${totalItemsCountForUser} items in secondary database`)
|
||||
}
|
||||
|
||||
return hasAlreadyDataInSecondaryDatabase
|
||||
}
|
||||
|
||||
private async allowForSecondaryDatabaseToCatchUp(): Promise<void> {
|
||||
const twoSecondsInMilliseconds = 2_000
|
||||
await this.timer.sleep(twoSecondsInMilliseconds)
|
||||
const tenSecondsInMillisecondsToRebuildIndexes = 10_000
|
||||
await this.timer.sleep(tenSecondsInMillisecondsToRebuildIndexes)
|
||||
}
|
||||
|
||||
private async getNewItemsCreatedInSecondaryDatabase(userUuid: Uuid): Promise<{
|
||||
alreadyExistingInPrimary: Item[]
|
||||
newItemsInSecondary: Item[]
|
||||
updatedInSecondary: Item[]
|
||||
alreadyExistingInSecondaryAndPrimary: string[]
|
||||
newItemsInSecondary: string[]
|
||||
updatedInSecondary: string[]
|
||||
}> {
|
||||
const items = await (this.secondaryItemRepository as ItemRepositoryInterface).findAll({
|
||||
this.logger.info(`[${userUuid.value}] Checking for new items in secondary database`)
|
||||
|
||||
const alreadyExistingInSecondaryAndPrimary: string[] = []
|
||||
const updatedInSecondary: string[] = []
|
||||
const newItemsInSecondary: string[] = []
|
||||
|
||||
const totalItemsCountForUser = await (this.secondaryItemRepository as ItemRepositoryInterface).countAll({
|
||||
userUuid: userUuid.value,
|
||||
})
|
||||
|
||||
const alreadyExistingInPrimary: Item[] = []
|
||||
const updatedInSecondary: Item[] = []
|
||||
const newItemsInSecondary: Item[] = []
|
||||
|
||||
for (const item of items) {
|
||||
const { itemInPrimary, newerItemInSecondary } = await this.checkIfItemExistsInPrimaryDatabase(item)
|
||||
if (itemInPrimary !== null) {
|
||||
alreadyExistingInPrimary.push(item)
|
||||
continue
|
||||
const totalPages = Math.ceil(totalItemsCountForUser / this.pageSize)
|
||||
for (let currentPage = 1; currentPage <= totalPages; currentPage++) {
|
||||
const query: ItemQuery = {
|
||||
userUuid: userUuid.value,
|
||||
offset: (currentPage - 1) * this.pageSize,
|
||||
limit: this.pageSize,
|
||||
sortOrder: 'ASC',
|
||||
sortBy: 'uuid',
|
||||
}
|
||||
if (newerItemInSecondary !== null) {
|
||||
updatedInSecondary.push(newerItemInSecondary)
|
||||
continue
|
||||
}
|
||||
if (itemInPrimary === null && newerItemInSecondary === null) {
|
||||
newItemsInSecondary.push(item)
|
||||
continue
|
||||
|
||||
const items = await (this.secondaryItemRepository as ItemRepositoryInterface).findAll(query)
|
||||
for (const item of items) {
|
||||
const { identicalItemInPrimary, newerItemInSecondary } = await this.checkIfItemExistsInPrimaryDatabase(item)
|
||||
if (identicalItemInPrimary !== null) {
|
||||
alreadyExistingInSecondaryAndPrimary.push(item.id.toString())
|
||||
continue
|
||||
}
|
||||
if (newerItemInSecondary !== null) {
|
||||
updatedInSecondary.push(newerItemInSecondary.id.toString())
|
||||
continue
|
||||
}
|
||||
if (identicalItemInPrimary === null && newerItemInSecondary === null) {
|
||||
newItemsInSecondary.push(item.id.toString())
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
alreadyExistingInPrimary,
|
||||
alreadyExistingInSecondaryAndPrimary,
|
||||
newItemsInSecondary,
|
||||
updatedInSecondary,
|
||||
}
|
||||
@@ -172,30 +188,36 @@ export class TransitionItemsFromPrimaryToSecondaryDatabaseForUser implements Use
|
||||
|
||||
private async checkIfItemExistsInPrimaryDatabase(
|
||||
item: Item,
|
||||
): Promise<{ itemInPrimary: Item | null; newerItemInSecondary: Item | null }> {
|
||||
): Promise<{ identicalItemInPrimary: Item | null; newerItemInSecondary: Item | null }> {
|
||||
const itemInPrimary = await this.primaryItemRepository.findByUuid(item.uuid)
|
||||
|
||||
if (itemInPrimary === null) {
|
||||
return { itemInPrimary: null, newerItemInSecondary: null }
|
||||
return { identicalItemInPrimary: null, newerItemInSecondary: null }
|
||||
}
|
||||
|
||||
if (!item.isIdenticalTo(itemInPrimary)) {
|
||||
this.logger.error(
|
||||
`Revision ${item.id.toString()} is not identical in primary and secondary database. Revision in secondary database: ${JSON.stringify(
|
||||
`[${
|
||||
item.props.userUuid.value
|
||||
}] Item ${item.id.toString()} is not identical in primary and secondary database. Item in secondary database: ${JSON.stringify(
|
||||
item,
|
||||
)}, revision in primary database: ${JSON.stringify(itemInPrimary)}`,
|
||||
)}, item in primary database: ${JSON.stringify(itemInPrimary)}`,
|
||||
)
|
||||
|
||||
return {
|
||||
itemInPrimary: null,
|
||||
identicalItemInPrimary: null,
|
||||
newerItemInSecondary: item.props.timestamps.updatedAt > itemInPrimary.props.timestamps.updatedAt ? item : null,
|
||||
}
|
||||
}
|
||||
|
||||
return { itemInPrimary: itemInPrimary, newerItemInSecondary: null }
|
||||
return { identicalItemInPrimary: itemInPrimary, newerItemInSecondary: null }
|
||||
}
|
||||
|
||||
private async migrateItemsForUser(userUuid: Uuid, updatedItemsInSecondary: Item[]): Promise<Result<void>> {
|
||||
private async migrateItemsForUser(
|
||||
userUuid: Uuid,
|
||||
updatedItemsInSecondary: string[],
|
||||
alreadyExistingInSecondaryAndPrimary: string[],
|
||||
): Promise<Result<void>> {
|
||||
try {
|
||||
const totalItemsCountForUser = await this.primaryItemRepository.countAll({ userUuid: userUuid.value })
|
||||
const totalPages = Math.ceil(totalItemsCountForUser / this.pageSize)
|
||||
@@ -204,16 +226,24 @@ export class TransitionItemsFromPrimaryToSecondaryDatabaseForUser implements Use
|
||||
userUuid: userUuid.value,
|
||||
offset: (currentPage - 1) * this.pageSize,
|
||||
limit: this.pageSize,
|
||||
sortBy: 'uuid',
|
||||
sortOrder: 'ASC',
|
||||
}
|
||||
|
||||
const items = await this.primaryItemRepository.findAll(query)
|
||||
|
||||
for (const item of items) {
|
||||
if (updatedItemsInSecondary.find((updatedItem) => updatedItem.uuid.equals(item.uuid))) {
|
||||
this.logger.info(`Skipping saving item ${item.uuid.value} as it was updated in secondary database`)
|
||||
if (updatedItemsInSecondary.find((updatedItemUuid) => item.uuid.value === updatedItemUuid)) {
|
||||
this.logger.info(
|
||||
`[${userUuid.value}] Skipping saving item ${item.uuid.value} as it was updated in secondary database`,
|
||||
)
|
||||
|
||||
continue
|
||||
}
|
||||
if (alreadyExistingInSecondaryAndPrimary.find((itemUuid) => item.uuid.value === itemUuid)) {
|
||||
continue
|
||||
}
|
||||
|
||||
await (this.secondaryItemRepository as ItemRepositoryInterface).save(item)
|
||||
}
|
||||
}
|
||||
@@ -237,7 +267,8 @@ export class TransitionItemsFromPrimaryToSecondaryDatabaseForUser implements Use
|
||||
private async checkIntegrityBetweenPrimaryAndSecondaryDatabase(
|
||||
userUuid: Uuid,
|
||||
newItemsInSecondaryCount: number,
|
||||
updatedItemsInSecondary: Item[],
|
||||
updatedItemsInSecondary: string[],
|
||||
alreadyExistingInSecondaryAndPrimary: string[],
|
||||
): Promise<Result<boolean>> {
|
||||
try {
|
||||
const totalItemsCountForUserInPrimary = await this.primaryItemRepository.countAll({ userUuid: userUuid.value })
|
||||
@@ -259,6 +290,8 @@ export class TransitionItemsFromPrimaryToSecondaryDatabaseForUser implements Use
|
||||
userUuid: userUuid.value,
|
||||
offset: (currentPage - 1) * this.pageSize,
|
||||
limit: this.pageSize,
|
||||
sortBy: 'uuid',
|
||||
sortOrder: 'ASC',
|
||||
}
|
||||
|
||||
const items = await this.primaryItemRepository.findAll(query)
|
||||
@@ -269,13 +302,17 @@ export class TransitionItemsFromPrimaryToSecondaryDatabaseForUser implements Use
|
||||
return Result.fail(`Item ${item.uuid.value} not found in secondary database`)
|
||||
}
|
||||
|
||||
if (updatedItemsInSecondary.find((updatedItem) => updatedItem.uuid.equals(item.uuid))) {
|
||||
if (updatedItemsInSecondary.find((updatedItemUuid) => item.uuid.value === updatedItemUuid)) {
|
||||
this.logger.info(
|
||||
`Skipping integrity check for item ${item.uuid.value} as it was updated in secondary database`,
|
||||
`[${userUuid.value}] Skipping integrity check for item ${item.uuid.value} as it was updated in secondary database`,
|
||||
)
|
||||
continue
|
||||
}
|
||||
|
||||
if (alreadyExistingInSecondaryAndPrimary.find((itemUuid) => item.uuid.value === itemUuid)) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (!item.isIdenticalTo(itemInSecondary)) {
|
||||
return Result.fail(
|
||||
`Item ${
|
||||
|
||||
@@ -17,6 +17,17 @@ export class MongoDBItemRepository implements ItemRepositoryInterface {
|
||||
private logger: Logger,
|
||||
) {}
|
||||
|
||||
async unassignFromSharedVault(sharedVaultUuid: Uuid): Promise<void> {
|
||||
await this.mongoRepository.updateMany(
|
||||
{ sharedVaultUuid: { $eq: sharedVaultUuid.value } },
|
||||
{ $set: { sharedVaultUuid: null } },
|
||||
)
|
||||
}
|
||||
|
||||
async removeByUuid(uuid: Uuid): Promise<void> {
|
||||
await this.mongoRepository.deleteOne({ _id: { $eq: BSON.UUID.createFromHexString(uuid.value) } })
|
||||
}
|
||||
|
||||
async deleteByUserUuid(userUuid: string): Promise<void> {
|
||||
await this.mongoRepository.deleteMany({ userUuid })
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Repository, SelectQueryBuilder } from 'typeorm'
|
||||
import { MapperInterface } from '@standardnotes/domain-core'
|
||||
import { MapperInterface, Uuid } from '@standardnotes/domain-core'
|
||||
import { Logger } from 'winston'
|
||||
|
||||
import { Item } from '../../Domain/Item/Item'
|
||||
@@ -16,6 +16,19 @@ export class SQLItemRepository extends SQLLegacyItemRepository {
|
||||
super(ormRepository, mapper, logger)
|
||||
}
|
||||
|
||||
override async unassignFromSharedVault(sharedVaultUuid: Uuid): Promise<void> {
|
||||
await this.ormRepository
|
||||
.createQueryBuilder('item')
|
||||
.update()
|
||||
.set({
|
||||
sharedVaultUuid: null,
|
||||
})
|
||||
.where('shared_vault_uuid = :sharedVaultUuid', {
|
||||
sharedVaultUuid: sharedVaultUuid.value,
|
||||
})
|
||||
.execute()
|
||||
}
|
||||
|
||||
protected override createFindAllQueryBuilder(query: ItemQuery): SelectQueryBuilder<SQLItem> {
|
||||
const queryBuilder = this.ormRepository.createQueryBuilder('item')
|
||||
|
||||
|
||||
@@ -16,6 +16,19 @@ export class SQLLegacyItemRepository implements ItemRepositoryInterface {
|
||||
protected logger: Logger,
|
||||
) {}
|
||||
|
||||
async unassignFromSharedVault(_sharedVaultUuid: Uuid): Promise<void> {
|
||||
this.logger.error('Method unassignFromSharedVault not supported.')
|
||||
}
|
||||
|
||||
async removeByUuid(uuid: Uuid): Promise<void> {
|
||||
await this.ormRepository
|
||||
.createQueryBuilder('item')
|
||||
.delete()
|
||||
.from('items')
|
||||
.where('uuid = :uuid', { uuid: uuid.value })
|
||||
.execute()
|
||||
}
|
||||
|
||||
async save(item: Item): Promise<void> {
|
||||
const persistence = this.mapper.toProjection(item)
|
||||
|
||||
|
||||
@@ -64,13 +64,13 @@ export class TypeORMSharedVaultInviteRepository implements SharedVaultInviteRepo
|
||||
return persistence.map((p) => this.mapper.toDomain(p))
|
||||
}
|
||||
|
||||
async removeBySharedVaultUuid(sharedVaultUuid: Uuid): Promise<void> {
|
||||
await this.ormRepository
|
||||
async findBySharedVaultUuid(sharedVaultUuid: Uuid): Promise<SharedVaultInvite[]> {
|
||||
const persistence = await this.ormRepository
|
||||
.createQueryBuilder('shared_vault_invite')
|
||||
.delete()
|
||||
.from('shared_vault_invites')
|
||||
.where('shared_vault_uuid = :sharedVaultUuid', { sharedVaultUuid: sharedVaultUuid.value })
|
||||
.execute()
|
||||
.where('shared_vault_invite.shared_vault_uuid = :sharedVaultUuid', { sharedVaultUuid: sharedVaultUuid.value })
|
||||
.getMany()
|
||||
|
||||
return persistence.map((p) => this.mapper.toDomain(p))
|
||||
}
|
||||
|
||||
async findByUserUuidAndSharedVaultUuid(dto: {
|
||||
|
||||
@@ -11,6 +11,17 @@ export class TypeORMSharedVaultRepository implements SharedVaultRepositoryInterf
|
||||
private mapper: MapperInterface<SharedVault, TypeORMSharedVault>,
|
||||
) {}
|
||||
|
||||
async findByUserUuid(userUuid: Uuid): Promise<SharedVault[]> {
|
||||
const persistence = await this.ormRepository
|
||||
.createQueryBuilder('shared_vault')
|
||||
.where('shared_vault.user_uuid = :userUuid', {
|
||||
userUuid: userUuid.value,
|
||||
})
|
||||
.getMany()
|
||||
|
||||
return persistence.map((p) => this.mapper.toDomain(p))
|
||||
}
|
||||
|
||||
async countByUserUuid(userUuid: Uuid): Promise<number> {
|
||||
const count = await this.ormRepository
|
||||
.createQueryBuilder('shared_vault')
|
||||
|
||||
@@ -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.10.48](https://github.com/standardnotes/server/compare/@standardnotes/websockets-server@1.10.47...@standardnotes/websockets-server@1.10.48) (2023-09-20)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/websockets-server
|
||||
|
||||
## [1.10.47](https://github.com/standardnotes/server/compare/@standardnotes/websockets-server@1.10.46...@standardnotes/websockets-server@1.10.47) (2023-09-20)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/websockets-server
|
||||
|
||||
## [1.10.46](https://github.com/standardnotes/server/compare/@standardnotes/websockets-server@1.10.45...@standardnotes/websockets-server@1.10.46) (2023-09-19)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/websockets-server
|
||||
|
||||
## [1.10.45](https://github.com/standardnotes/server/compare/@standardnotes/websockets-server@1.10.44...@standardnotes/websockets-server@1.10.45) (2023-09-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/websockets-server
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/websockets-server",
|
||||
"version": "1.10.45",
|
||||
"version": "1.10.48",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user