Compare commits

..

31 Commits

Author SHA1 Message Date
standardci 51cd0a4dad chore(release): publish new version
- @standardnotes/analytics@2.27.1
 - @standardnotes/api-gateway@1.75.6
 - @standardnotes/auth-server@1.147.1
 - @standardnotes/domain-core@1.34.1
 - @standardnotes/event-store@1.12.1
 - @standardnotes/files-server@1.24.1
 - @standardnotes/home-server@1.16.8
 - @standardnotes/revisions-server@1.38.1
 - @standardnotes/scheduler-server@1.21.1
 - @standardnotes/settings@1.21.41
 - @standardnotes/syncing-server@1.109.1
 - @standardnotes/websockets-server@1.11.1
2023-09-27 09:45:40 +00:00
Karol Sójko 1d06ffe9d5 fix: removing items in a vault and notifying about designated survivor (#855)
* fix: removing items in a vault and notifying about designated survivor

* fix deleting shared vault items
2023-09-27 11:10:41 +02:00
standardci dbf532f55e chore(release): publish new version
- @standardnotes/analytics@2.27.0
 - @standardnotes/api-gateway@1.75.5
 - @standardnotes/auth-server@1.147.0
 - @standardnotes/common@1.51.0
 - @standardnotes/domain-core@1.34.0
 - @standardnotes/domain-events-infra@1.13.0
 - @standardnotes/domain-events@2.131.0
 - @standardnotes/event-store@1.12.0
 - @standardnotes/files-server@1.24.0
 - @standardnotes/home-server@1.16.7
 - @standardnotes/predicates@1.7.0
 - @standardnotes/revisions-server@1.38.0
 - @standardnotes/scheduler-server@1.21.0
 - @standardnotes/security@1.14.0
 - @standardnotes/settings@1.21.40
 - @standardnotes/syncing-server@1.109.0
 - @standardnotes/time@1.16.0
 - @standardnotes/websockets-server@1.11.0
2023-09-26 10:05:20 +00:00
Karol Sójko ca6dbc0053 feat: refactor handling revision creation from dump (#854)
* feat: refactor handling revision creation from dump

* fix: dump repository handling
2023-09-26 11:47:41 +02:00
standardci 1bb5980b45 chore(release): publish new version
- @standardnotes/analytics@2.26.24
 - @standardnotes/api-gateway@1.75.4
 - @standardnotes/auth-server@1.146.4
 - @standardnotes/domain-core@1.33.2
 - @standardnotes/event-store@1.11.52
 - @standardnotes/files-server@1.23.2
 - @standardnotes/home-server@1.16.6
 - @standardnotes/revisions-server@1.37.3
 - @standardnotes/scheduler-server@1.20.56
 - @standardnotes/settings@1.21.39
 - @standardnotes/syncing-server@1.108.2
 - @standardnotes/websockets-server@1.10.53
2023-09-25 16:42:43 +00:00
Karol Sójko a02a28774b fix(domain-core): notification paylod to string casting 2023-09-25 18:24:38 +02:00
standardci 2d9b3578b6 chore(release): publish new version
- @standardnotes/analytics@2.26.23
 - @standardnotes/api-gateway@1.75.3
 - @standardnotes/auth-server@1.146.3
 - @standardnotes/domain-core@1.33.1
 - @standardnotes/event-store@1.11.51
 - @standardnotes/files-server@1.23.1
 - @standardnotes/home-server@1.16.5
 - @standardnotes/revisions-server@1.37.2
 - @standardnotes/scheduler-server@1.20.55
 - @standardnotes/settings@1.21.38
 - @standardnotes/syncing-server@1.108.1
 - @standardnotes/websockets-server@1.10.52
2023-09-25 15:28:21 +00:00
Karol Sójko 3d5e747590 fix(syncing-server): another spec 2023-09-25 17:10:30 +02:00
Karol Sójko 94467747ac fix(syncing-server): specs 2023-09-25 15:52:35 +02:00
Karol Sójko cebab59a02 fix: refactor the structure of notifications (#853) 2023-09-25 15:40:51 +02:00
standardci 09e1a892ca chore(release): publish new version
- @standardnotes/analytics@2.26.22
 - @standardnotes/api-gateway@1.75.2
 - @standardnotes/auth-server@1.146.2
 - @standardnotes/domain-events-infra@1.12.34
 - @standardnotes/domain-events@2.130.0
 - @standardnotes/event-store@1.11.50
 - @standardnotes/files-server@1.23.0
 - @standardnotes/home-server@1.16.4
 - @standardnotes/revisions-server@1.37.1
 - @standardnotes/scheduler-server@1.20.54
 - @standardnotes/syncing-server@1.108.0
 - @standardnotes/websockets-server@1.10.51
2023-09-25 11:53:52 +00:00
Karol Sójko 7b1eec21e5 feat: remove shared vault files upon shared vault removal (#852)
* feat: remove shared vault files upon shared vault removal

* fix: link files queue with syncing-server-js topic
2023-09-25 12:56:31 +02:00
standardci a58262d584 chore(release): publish new version
- @standardnotes/home-server@1.16.3
 - @standardnotes/syncing-server@1.107.0
2023-09-25 09:43:23 +00:00
Karol Sójko a8f03e157b feat(syncing-server): transfer shared vault items (#851) 2023-09-25 11:09:33 +02:00
standardci a401962bcd chore(release): publish new version
- @standardnotes/home-server@1.16.2
 - @standardnotes/revisions-server@1.37.0
 - @standardnotes/syncing-server@1.106.0
2023-09-25 08:15:14 +00:00
Karol Sójko 9759814f63 feat: add storing paging progress in redis 2023-09-25 09:40:49 +02:00
standardci c7cf53722c chore(release): publish new version
- @standardnotes/analytics@2.26.21
 - @standardnotes/api-gateway@1.75.1
 - @standardnotes/auth-server@1.146.1
 - @standardnotes/domain-events-infra@1.12.33
 - @standardnotes/domain-events@2.129.1
 - @standardnotes/event-store@1.11.49
 - @standardnotes/files-server@1.22.28
 - @standardnotes/home-server@1.16.1
 - @standardnotes/revisions-server@1.36.7
 - @standardnotes/scheduler-server@1.20.53
 - @standardnotes/syncing-server@1.105.1
 - @standardnotes/websockets-server@1.10.50
2023-09-25 07:30:06 +00:00
Karol Sójko 8cb33dc906 fix: add paging progress log 2023-09-25 08:51:33 +02:00
Karol Sójko 1d73e4f072 fix: remember paging progress on transitioning 2023-09-25 08:50:59 +02:00
standardci 0a0f82ea3d chore(release): publish new version
- @standardnotes/auth-server@1.146.0
 - @standardnotes/home-server@1.16.0
 - @standardnotes/revisions-server@1.36.6
 - @standardnotes/syncing-server@1.105.0
2023-09-22 12:43:13 +00:00
Karol Sójko f9b1f40ddf fix(auth): register specs 2023-09-22 14:23:21 +02:00
Karol Sójko 0562b0a621 fix: add more logs to transition process 2023-09-22 14:19:14 +02:00
Karol Sójko 15ed1fd789 fix: remove excessive logs 2023-09-22 14:16:51 +02:00
Karol Sójko 5001496c7b feat: remove transition mode from code 2023-09-22 14:13:32 +02:00
Karol Sójko 0a1080ce2a feat(syncing-server): transfer shared vault ownership to designated survivor upon account deletion (#845) 2023-09-22 14:11:01 +02:00
standardci 4802d7e876 chore(release): publish new version
- @standardnotes/home-server@1.15.80
 - @standardnotes/syncing-server@1.104.0
2023-09-22 11:33:45 +00:00
Karol Sójko bcd95cdbe9 feat(syncing-server): add designated survivors in fetching shared vaults response (#844) 2023-09-22 13:15:11 +02:00
standardci d50c4440c2 chore(release): publish new version
- @standardnotes/home-server@1.15.79
 - @standardnotes/revisions-server@1.36.5
 - @standardnotes/syncing-server@1.103.1
2023-09-22 09:57:54 +00:00
Karol Sójko 921c30f641 fix: integrity check during transition 2023-09-22 11:27:54 +02:00
Karol Sójko 22540ee834 fix: processing migration optimization 2023-09-22 11:17:03 +02:00
Karol Sójko 4f4443a882 fix: disable cleaning secondary database on transition 2023-09-22 11:17:02 +02:00
140 changed files with 2413 additions and 1211 deletions
-4
View File
@@ -24,7 +24,6 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
secondary_db_enabled: [true, false] secondary_db_enabled: [true, false]
transition_mode_enabled: [true, false]
runs-on: ubuntu-latest runs-on: ubuntu-latest
services: services:
@@ -51,7 +50,6 @@ jobs:
DB_TYPE: mysql DB_TYPE: mysql
CACHE_TYPE: redis CACHE_TYPE: redis
SECONDARY_DB_ENABLED: ${{ matrix.secondary_db_enabled }} SECONDARY_DB_ENABLED: ${{ matrix.secondary_db_enabled }}
TRANSITION_MODE_ENABLED: ${{ matrix.transition_mode_enabled }}
- name: Wait for server to start - name: Wait for server to start
run: docker/is-available.sh http://localhost:3123 $(pwd)/logs run: docker/is-available.sh http://localhost:3123 $(pwd)/logs
@@ -75,7 +73,6 @@ jobs:
db_type: [mysql, sqlite] db_type: [mysql, sqlite]
cache_type: [redis, memory] cache_type: [redis, memory]
secondary_db_enabled: [true, false] secondary_db_enabled: [true, false]
transition_mode_enabled: [true, false]
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -145,7 +142,6 @@ jobs:
echo "REDIS_URL=redis://localhost:6379" >> packages/home-server/.env echo "REDIS_URL=redis://localhost:6379" >> packages/home-server/.env
echo "CACHE_TYPE=${{ matrix.cache_type }}" >> packages/home-server/.env echo "CACHE_TYPE=${{ matrix.cache_type }}" >> packages/home-server/.env
echo "SECONDARY_DB_ENABLED=${{ matrix.secondary_db_enabled }}" >> packages/home-server/.env echo "SECONDARY_DB_ENABLED=${{ matrix.secondary_db_enabled }}" >> packages/home-server/.env
echo "TRANSITION_MODE_ENABLED=${{ matrix.transition_mode_enabled }}" >> packages/home-server/.env
echo "MONGO_HOST=localhost" >> packages/home-server/.env echo "MONGO_HOST=localhost" >> packages/home-server/.env
echo "MONGO_PORT=27017" >> packages/home-server/.env echo "MONGO_PORT=27017" >> packages/home-server/.env
echo "MONGO_DATABASE=standardnotes" >> packages/home-server/.env echo "MONGO_DATABASE=standardnotes" >> packages/home-server/.env
Generated
+6 -2
View File
@@ -6156,6 +6156,7 @@ const RAW_RUNTIME_STATE =
["@types/cors", "npm:2.8.13"],\ ["@types/cors", "npm:2.8.13"],\
["@types/dotenv", "npm:8.2.0"],\ ["@types/dotenv", "npm:8.2.0"],\
["@types/express", "npm:4.17.17"],\ ["@types/express", "npm:4.17.17"],\
["@types/ioredis", "npm:5.0.0"],\
["@types/jest", "npm:29.5.2"],\ ["@types/jest", "npm:29.5.2"],\
["@types/newrelic", "npm:9.14.0"],\ ["@types/newrelic", "npm:9.14.0"],\
["@types/node", "npm:20.5.7"],\ ["@types/node", "npm:20.5.7"],\
@@ -6168,6 +6169,7 @@ const RAW_RUNTIME_STATE =
["express", "npm:4.18.2"],\ ["express", "npm:4.18.2"],\
["inversify", "npm:6.0.1"],\ ["inversify", "npm:6.0.1"],\
["inversify-express-utils", "npm:6.4.3"],\ ["inversify-express-utils", "npm:6.4.3"],\
["ioredis", "npm:5.3.2"],\
["jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.5.0"],\ ["jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.5.0"],\
["mongodb", "virtual:365b8c88cdf194291829ee28b79556e2328175d26a621363e703848100bea0042e9500db2a1206c9bbc3a4a76a1d169639ef774b2ea3a1a98584a9936b58c6be#npm:6.0.0"],\ ["mongodb", "virtual:365b8c88cdf194291829ee28b79556e2328175d26a621363e703848100bea0042e9500db2a1206c9bbc3a4a76a1d169639ef774b2ea3a1a98584a9936b58c6be#npm:6.0.0"],\
["mysql2", "npm:3.3.3"],\ ["mysql2", "npm:3.3.3"],\
@@ -6340,6 +6342,7 @@ const RAW_RUNTIME_STATE =
["@types/cors", "npm:2.8.13"],\ ["@types/cors", "npm:2.8.13"],\
["@types/dotenv", "npm:8.2.0"],\ ["@types/dotenv", "npm:8.2.0"],\
["@types/express", "npm:4.17.17"],\ ["@types/express", "npm:4.17.17"],\
["@types/ioredis", "npm:5.0.0"],\
["@types/jest", "npm:29.5.2"],\ ["@types/jest", "npm:29.5.2"],\
["@types/jsonwebtoken", "npm:9.0.2"],\ ["@types/jsonwebtoken", "npm:9.0.2"],\
["@types/newrelic", "npm:9.14.0"],\ ["@types/newrelic", "npm:9.14.0"],\
@@ -6359,6 +6362,7 @@ const RAW_RUNTIME_STATE =
["helmet", "npm:7.0.0"],\ ["helmet", "npm:7.0.0"],\
["inversify", "npm:6.0.1"],\ ["inversify", "npm:6.0.1"],\
["inversify-express-utils", "npm:6.4.3"],\ ["inversify-express-utils", "npm:6.4.3"],\
["ioredis", "npm:5.3.2"],\
["jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.5.0"],\ ["jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.5.0"],\
["jsonwebtoken", "npm:9.0.0"],\ ["jsonwebtoken", "npm:9.0.0"],\
["mongodb", "virtual:365b8c88cdf194291829ee28b79556e2328175d26a621363e703848100bea0042e9500db2a1206c9bbc3a4a76a1d169639ef774b2ea3a1a98584a9936b58c6be#npm:6.0.0"],\ ["mongodb", "virtual:365b8c88cdf194291829ee28b79556e2328175d26a621363e703848100bea0042e9500db2a1206c9bbc3a4a76a1d169639ef774b2ea3a1a98584a9936b58c6be#npm:6.0.0"],\
@@ -16772,7 +16776,7 @@ const RAW_RUNTIME_STATE =
["@types/better-sqlite3", null],\ ["@types/better-sqlite3", null],\
["@types/google-cloud__spanner", null],\ ["@types/google-cloud__spanner", null],\
["@types/hdb-pool", null],\ ["@types/hdb-pool", null],\
["@types/ioredis", null],\ ["@types/ioredis", "npm:5.0.0"],\
["@types/mongodb", null],\ ["@types/mongodb", null],\
["@types/mssql", null],\ ["@types/mssql", null],\
["@types/mysql2", null],\ ["@types/mysql2", null],\
@@ -16796,7 +16800,7 @@ const RAW_RUNTIME_STATE =
["dotenv", "npm:16.1.3"],\ ["dotenv", "npm:16.1.3"],\
["glob", "npm:8.1.0"],\ ["glob", "npm:8.1.0"],\
["hdb-pool", null],\ ["hdb-pool", null],\
["ioredis", null],\ ["ioredis", "npm:5.3.2"],\
["mkdirp", "npm:2.1.6"],\ ["mkdirp", "npm:2.1.6"],\
["mongodb", "virtual:365b8c88cdf194291829ee28b79556e2328175d26a621363e703848100bea0042e9500db2a1206c9bbc3a4a76a1d169639ef774b2ea3a1a98584a9936b58c6be#npm:6.0.0"],\ ["mongodb", "virtual:365b8c88cdf194291829ee28b79556e2328175d26a621363e703848100bea0042e9500db2a1206c9bbc3a4a76a1d169639ef774b2ea3a1a98584a9936b58c6be#npm:6.0.0"],\
["mssql", null],\ ["mssql", null],\
-1
View File
@@ -24,7 +24,6 @@ services:
DB_TYPE: "${DB_TYPE}" DB_TYPE: "${DB_TYPE}"
CACHE_TYPE: "${CACHE_TYPE}" CACHE_TYPE: "${CACHE_TYPE}"
SECONDARY_DB_ENABLED: "${SECONDARY_DB_ENABLED}" SECONDARY_DB_ENABLED: "${SECONDARY_DB_ENABLED}"
TRANSITION_MODE_ENABLED: "${TRANSITION_MODE_ENABLED}"
container_name: server-ci container_name: server-ci
ports: ports:
- 3123:3000 - 3123:3000
-3
View File
@@ -68,9 +68,6 @@ fi
if [ -z "$SECONDARY_DB_ENABLED" ]; then if [ -z "$SECONDARY_DB_ENABLED" ]; then
export SECONDARY_DB_ENABLED=false export SECONDARY_DB_ENABLED=false
fi fi
if [ -z "$TRANSITION_MODE_ENABLED" ]; then
export TRANSITION_MODE_ENABLED=false
fi
export DB_MIGRATIONS_PATH="dist/migrations/*.js" export DB_MIGRATIONS_PATH="dist/migrations/*.js"
######### #########
+5
View File
@@ -139,6 +139,11 @@ LINKING_RESULT=$(link_queue_and_topic $AUTH_TOPIC_ARN $FILES_QUEUE_ARN)
echo "linking done:" echo "linking done:"
echo "$LINKING_RESULT" echo "$LINKING_RESULT"
echo "linking topic $SYNCING_SERVER_TOPIC_ARN to queue $FILES_QUEUE_ARN"
LINKING_RESULT=$(link_queue_and_topic $SYNCING_SERVER_TOPIC_ARN $FILES_QUEUE_ARN)
echo "linking done:"
echo "$LINKING_RESULT"
QUEUE_NAME="syncing-server-local-queue" QUEUE_NAME="syncing-server-local-queue"
echo "creating queue $QUEUE_NAME" echo "creating queue $QUEUE_NAME"
-2
View File
@@ -3,8 +3,6 @@ module.exports = {
testEnvironment: 'node', testEnvironment: 'node',
testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.ts$', testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.ts$',
testTimeout: 20000, testTimeout: 20000,
coverageReporters: ['text'],
reporters: ['summary'],
coverageThreshold: { coverageThreshold: {
global: { global: {
branches: 100, branches: 100,
+1 -1
View File
@@ -20,7 +20,7 @@
"release": "lerna version --conventional-graduate --conventional-commits --yes -m \"chore(release): publish new version\"", "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", "publish": "lerna publish from-git --yes --no-verify-access --loglevel verbose",
"postversion": "./scripts/push-tags-one-by-one.sh", "postversion": "./scripts/push-tags-one-by-one.sh",
"e2e": "yarn workspace @standardnotes/home-server run build && PORT=3123 yarn workspace @standardnotes/home-server start", "e2e": "yarn build && PORT=3123 yarn workspace @standardnotes/home-server start",
"start": "yarn workspace @standardnotes/home-server run build && yarn workspace @standardnotes/home-server start" "start": "yarn workspace @standardnotes/home-server run build && yarn workspace @standardnotes/home-server start"
}, },
"devDependencies": { "devDependencies": {
+26
View File
@@ -3,6 +3,32 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [2.27.1](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.27.0...@standardnotes/analytics@2.27.1) (2023-09-27)
**Note:** Version bump only for package @standardnotes/analytics
# [2.27.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.26.24...@standardnotes/analytics@2.27.0) (2023-09-26)
### Features
* refactor handling revision creation from dump ([#854](https://github.com/standardnotes/server/issues/854)) ([ca6dbc0](https://github.com/standardnotes/server/commit/ca6dbc00537bb20f508f9310b1a838421f53a643))
## [2.26.24](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.26.23...@standardnotes/analytics@2.26.24) (2023-09-25)
**Note:** Version bump only for package @standardnotes/analytics
## [2.26.23](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.26.22...@standardnotes/analytics@2.26.23) (2023-09-25)
**Note:** Version bump only for package @standardnotes/analytics
## [2.26.22](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.26.21...@standardnotes/analytics@2.26.22) (2023-09-25)
**Note:** Version bump only for package @standardnotes/analytics
## [2.26.21](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.26.20...@standardnotes/analytics@2.26.21) (2023-09-25)
**Note:** Version bump only for package @standardnotes/analytics
## [2.26.20](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.26.19...@standardnotes/analytics@2.26.20) (2023-09-21) ## [2.26.20](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.26.19...@standardnotes/analytics@2.26.20) (2023-09-21)
**Note:** Version bump only for package @standardnotes/analytics **Note:** Version bump only for package @standardnotes/analytics
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@standardnotes/analytics", "name": "@standardnotes/analytics",
"version": "2.26.20", "version": "2.27.1",
"engines": { "engines": {
"node": ">=18.0.0 <21.0.0" "node": ">=18.0.0 <21.0.0"
}, },
@@ -18,7 +18,7 @@
"build": "tsc --build", "build": "tsc --build",
"lint": "eslint . --ext .ts", "lint": "eslint . --ext .ts",
"lint:fix": "eslint . --ext .ts --fix", "lint:fix": "eslint . --ext .ts --fix",
"test": "jest --coverage --config=./jest.config.js --maxWorkers=50%", "test": "jest --coverage --no-cache --config=./jest.config.js --maxWorkers=50%",
"worker": "yarn node dist/bin/worker.js", "worker": "yarn node dist/bin/worker.js",
"report": "yarn node dist/bin/report.js", "report": "yarn node dist/bin/report.js",
"setup:env": "cp .env.sample .env", "setup:env": "cp .env.sample .env",
+24
View File
@@ -3,6 +3,30 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.75.6](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.75.5...@standardnotes/api-gateway@1.75.6) (2023-09-27)
**Note:** Version bump only for package @standardnotes/api-gateway
## [1.75.5](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.75.4...@standardnotes/api-gateway@1.75.5) (2023-09-26)
**Note:** Version bump only for package @standardnotes/api-gateway
## [1.75.4](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.75.3...@standardnotes/api-gateway@1.75.4) (2023-09-25)
**Note:** Version bump only for package @standardnotes/api-gateway
## [1.75.3](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.75.2...@standardnotes/api-gateway@1.75.3) (2023-09-25)
**Note:** Version bump only for package @standardnotes/api-gateway
## [1.75.2](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.75.1...@standardnotes/api-gateway@1.75.2) (2023-09-25)
**Note:** Version bump only for package @standardnotes/api-gateway
## [1.75.1](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.75.0...@standardnotes/api-gateway@1.75.1) (2023-09-25)
**Note:** Version bump only for package @standardnotes/api-gateway
# [1.75.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.74.17...@standardnotes/api-gateway@1.75.0) (2023-09-21) # [1.75.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.74.17...@standardnotes/api-gateway@1.75.0) (2023-09-21)
### Features ### Features
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@standardnotes/api-gateway", "name": "@standardnotes/api-gateway",
"version": "1.75.0", "version": "1.75.6",
"engines": { "engines": {
"node": ">=18.0.0 <21.0.0" "node": ">=18.0.0 <21.0.0"
}, },
+36
View File
@@ -3,6 +3,42 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.147.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.147.0...@standardnotes/auth-server@1.147.1) (2023-09-27)
**Note:** Version bump only for package @standardnotes/auth-server
# [1.147.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.146.4...@standardnotes/auth-server@1.147.0) (2023-09-26)
### Features
* refactor handling revision creation from dump ([#854](https://github.com/standardnotes/server/issues/854)) ([ca6dbc0](https://github.com/standardnotes/server/commit/ca6dbc00537bb20f508f9310b1a838421f53a643))
## [1.146.4](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.146.3...@standardnotes/auth-server@1.146.4) (2023-09-25)
**Note:** Version bump only for package @standardnotes/auth-server
## [1.146.3](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.146.2...@standardnotes/auth-server@1.146.3) (2023-09-25)
**Note:** Version bump only for package @standardnotes/auth-server
## [1.146.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.146.1...@standardnotes/auth-server@1.146.2) (2023-09-25)
**Note:** Version bump only for package @standardnotes/auth-server
## [1.146.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.146.0...@standardnotes/auth-server@1.146.1) (2023-09-25)
**Note:** Version bump only for package @standardnotes/auth-server
# [1.146.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.145.0...@standardnotes/auth-server@1.146.0) (2023-09-22)
### Bug Fixes
* **auth:** register specs ([f9b1f40](https://github.com/standardnotes/server/commit/f9b1f40ddf2d733d106ea64b9a7c4b38c5ec43ce))
### Features
* remove transition mode from code ([5001496](https://github.com/standardnotes/server/commit/5001496c7bc1df9e20c2d88ebf52ed77f868110c))
# [1.145.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.144.0...@standardnotes/auth-server@1.145.0) (2023-09-22) # [1.145.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.144.0...@standardnotes/auth-server@1.145.0) (2023-09-22)
### Features ### Features
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@standardnotes/auth-server", "name": "@standardnotes/auth-server",
"version": "1.145.0", "version": "1.147.1",
"engines": { "engines": {
"node": ">=18.0.0 <21.0.0" "node": ">=18.0.0 <21.0.0"
}, },
@@ -19,7 +19,7 @@
"lint": "eslint . --ext .ts", "lint": "eslint . --ext .ts",
"lint:fix": "eslint . --fix --ext .ts", "lint:fix": "eslint . --fix --ext .ts",
"pretest": "yarn lint && yarn build", "pretest": "yarn lint && yarn build",
"test": "jest --coverage --config=./jest.config.js --maxWorkers=50%", "test": "jest --coverage --no-cache --config=./jest.config.js --maxWorkers=50%",
"start": "yarn node dist/bin/server.js", "start": "yarn node dist/bin/server.js",
"worker": "yarn node dist/bin/worker.js", "worker": "yarn node dist/bin/worker.js",
"cleanup": "yarn node dist/bin/cleanup.js", "cleanup": "yarn node dist/bin/cleanup.js",
-3
View File
@@ -595,9 +595,6 @@ export class ContainerConfigLoader {
container container
.bind(TYPES.Auth_READONLY_USERS) .bind(TYPES.Auth_READONLY_USERS)
.toConstantValue(env.get('READONLY_USERS', true) ? env.get('READONLY_USERS', true).split(',') : []) .toConstantValue(env.get('READONLY_USERS', true) ? env.get('READONLY_USERS', true).split(',') : [])
container
.bind(TYPES.Auth_TRANSITION_MODE_ENABLED)
.toConstantValue(env.get('TRANSITION_MODE_ENABLED', true) === 'true')
if (isConfiguredForInMemoryCache) { if (isConfiguredForInMemoryCache) {
container container
-1
View File
@@ -105,7 +105,6 @@ const TYPES = {
Auth_U2F_EXPECTED_ORIGIN: Symbol.for('Auth_U2F_EXPECTED_ORIGIN'), Auth_U2F_EXPECTED_ORIGIN: Symbol.for('Auth_U2F_EXPECTED_ORIGIN'),
Auth_U2F_REQUIRE_USER_VERIFICATION: Symbol.for('Auth_U2F_REQUIRE_USER_VERIFICATION'), Auth_U2F_REQUIRE_USER_VERIFICATION: Symbol.for('Auth_U2F_REQUIRE_USER_VERIFICATION'),
Auth_READONLY_USERS: Symbol.for('Auth_READONLY_USERS'), Auth_READONLY_USERS: Symbol.for('Auth_READONLY_USERS'),
Auth_TRANSITION_MODE_ENABLED: Symbol.for('Auth_TRANSITION_MODE_ENABLED'),
// use cases // use cases
Auth_AuthenticateUser: Symbol.for('Auth_AuthenticateUser'), Auth_AuthenticateUser: Symbol.for('Auth_AuthenticateUser'),
Auth_AuthenticateRequest: Symbol.for('Auth_AuthenticateRequest'), Auth_AuthenticateRequest: Symbol.for('Auth_AuthenticateRequest'),
@@ -21,19 +21,9 @@ describe('Register', () => {
let user: User let user: User
let crypter: CrypterInterface let crypter: CrypterInterface
let timer: TimerInterface let timer: TimerInterface
let transitionModeEnabled = false
const createUseCase = () => const createUseCase = () =>
new Register( new Register(userRepository, roleRepository, authResponseFactory, crypter, false, settingService, timer)
userRepository,
roleRepository,
authResponseFactory,
crypter,
false,
settingService,
timer,
transitionModeEnabled,
)
beforeEach(() => { beforeEach(() => {
userRepository = {} as jest.Mocked<UserRepositoryInterface> userRepository = {} as jest.Mocked<UserRepositoryInterface>
@@ -94,45 +84,7 @@ describe('Register', () => {
expect(settingService.applyDefaultSettingsUponRegistration).toHaveBeenCalled() expect(settingService.applyDefaultSettingsUponRegistration).toHaveBeenCalled()
}) })
it('should register a new user with default role', async () => {
const role = new Role()
role.name = 'role1'
roleRepository.findOneByName = jest.fn().mockReturnValue(role)
expect(
await createUseCase().execute({
email: 'test@test.te',
password: 'asdzxc',
updatedWithUserAgent: 'Mozilla',
apiVersion: '20200115',
ephemeralSession: false,
version: '004',
pwCost: 11,
pwSalt: 'qweqwe',
pwNonce: undefined,
}),
).toEqual({ success: true, authResponse: { foo: 'bar' } })
expect(userRepository.save).toHaveBeenCalledWith({
email: 'test@test.te',
encryptedPassword: expect.any(String),
encryptedServerKey: 'test',
serverEncryptionVersion: 1,
pwCost: 11,
pwNonce: undefined,
pwSalt: 'qweqwe',
updatedWithUserAgent: 'Mozilla',
uuid: expect.any(String),
version: '004',
createdAt: new Date(1),
updatedAt: new Date(1),
roles: Promise.resolve([role]),
})
})
it('should register a new user with default role and transition role', async () => { it('should register a new user with default role and transition role', async () => {
transitionModeEnabled = true
const role = new Role() const role = new Role()
role.name = RoleName.NAMES.CoreUser role.name = RoleName.NAMES.CoreUser
@@ -249,7 +201,6 @@ describe('Register', () => {
true, true,
settingService, settingService,
timer, timer,
transitionModeEnabled,
).execute({ ).execute({
email: 'test@test.te', email: 'test@test.te',
password: 'asdzxc', password: 'asdzxc',
+3 -6
View File
@@ -27,7 +27,6 @@ export class Register implements UseCaseInterface {
@inject(TYPES.Auth_DISABLE_USER_REGISTRATION) private disableUserRegistration: boolean, @inject(TYPES.Auth_DISABLE_USER_REGISTRATION) private disableUserRegistration: boolean,
@inject(TYPES.Auth_SettingService) private settingService: SettingServiceInterface, @inject(TYPES.Auth_SettingService) private settingService: SettingServiceInterface,
@inject(TYPES.Auth_Timer) private timer: TimerInterface, @inject(TYPES.Auth_Timer) private timer: TimerInterface,
@inject(TYPES.Auth_TRANSITION_MODE_ENABLED) private transitionModeEnabled: boolean,
) {} ) {}
async execute(dto: RegisterDTO): Promise<RegisterResponse> { async execute(dto: RegisterDTO): Promise<RegisterResponse> {
@@ -78,11 +77,9 @@ export class Register implements UseCaseInterface {
if (defaultRole) { if (defaultRole) {
roles.push(defaultRole) roles.push(defaultRole)
} }
if (this.transitionModeEnabled) { const transitionRole = await this.roleRepository.findOneByName(RoleName.NAMES.TransitionUser)
const transitionRole = await this.roleRepository.findOneByName(RoleName.NAMES.TransitionUser) if (transitionRole) {
if (transitionRole) { roles.push(transitionRole)
roles.push(transitionRole)
}
} }
user.roles = Promise.resolve(roles) user.roles = Promise.resolve(roles)
+6
View File
@@ -3,6 +3,12 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.51.0](https://github.com/standardnotes/server/compare/@standardnotes/common@1.50.4...@standardnotes/common@1.51.0) (2023-09-26)
### Features
* refactor handling revision creation from dump ([#854](https://github.com/standardnotes/server/issues/854)) ([ca6dbc0](https://github.com/standardnotes/server/commit/ca6dbc00537bb20f508f9310b1a838421f53a643))
## [1.50.4](https://github.com/standardnotes/server/compare/@standardnotes/common@1.50.3...@standardnotes/common@1.50.4) (2023-09-04) ## [1.50.4](https://github.com/standardnotes/server/compare/@standardnotes/common@1.50.3...@standardnotes/common@1.50.4) (2023-09-04)
**Note:** Version bump only for package @standardnotes/common **Note:** Version bump only for package @standardnotes/common
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@standardnotes/common", "name": "@standardnotes/common",
"version": "1.50.4", "version": "1.51.0",
"engines": { "engines": {
"node": ">=18.0.0 <21.0.0" "node": ">=18.0.0 <21.0.0"
}, },
@@ -20,7 +20,7 @@
"clean": "rm -fr dist", "clean": "rm -fr dist",
"build": "tsc --build", "build": "tsc --build",
"lint": "eslint . --ext .ts", "lint": "eslint . --ext .ts",
"test": "jest spec --coverage" "test": "jest --coverage --no-cache"
}, },
"devDependencies": { "devDependencies": {
"@types/jest": "^29.5.1", "@types/jest": "^29.5.1",
+24
View File
@@ -3,6 +3,30 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.34.1](https://github.com/standardnotes/server/compare/@standardnotes/domain-core@1.34.0...@standardnotes/domain-core@1.34.1) (2023-09-27)
### Bug Fixes
* removing items in a vault and notifying about designated survivor ([#855](https://github.com/standardnotes/server/issues/855)) ([1d06ffe](https://github.com/standardnotes/server/commit/1d06ffe9d51722ada7baa040e1d5ed351fc28f39))
# [1.34.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-core@1.33.2...@standardnotes/domain-core@1.34.0) (2023-09-26)
### Features
* refactor handling revision creation from dump ([#854](https://github.com/standardnotes/server/issues/854)) ([ca6dbc0](https://github.com/standardnotes/server/commit/ca6dbc00537bb20f508f9310b1a838421f53a643))
## [1.33.2](https://github.com/standardnotes/server/compare/@standardnotes/domain-core@1.33.1...@standardnotes/domain-core@1.33.2) (2023-09-25)
### Bug Fixes
* **domain-core:** notification paylod to string casting ([a02a287](https://github.com/standardnotes/server/commit/a02a28774b6d500200043faefb9ebac3719e7661))
## [1.33.1](https://github.com/standardnotes/server/compare/@standardnotes/domain-core@1.33.0...@standardnotes/domain-core@1.33.1) (2023-09-25)
### Bug Fixes
* refactor the structure of notifications ([#853](https://github.com/standardnotes/server/issues/853)) ([cebab59](https://github.com/standardnotes/server/commit/cebab59a026c6868886e0945787a8ddb0442fbc3))
# [1.33.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-core@1.32.0...@standardnotes/domain-core@1.33.0) (2023-09-21) # [1.33.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-core@1.32.0...@standardnotes/domain-core@1.33.0) (2023-09-21)
### Features ### Features
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@standardnotes/domain-core", "name": "@standardnotes/domain-core",
"version": "1.33.0", "version": "1.34.1",
"engines": { "engines": {
"node": ">=18.0.0 <21.0.0" "node": ">=18.0.0 <21.0.0"
}, },
@@ -21,7 +21,7 @@
"build": "tsc --build", "build": "tsc --build",
"lint": "eslint . --ext .ts", "lint": "eslint . --ext .ts",
"lint:fix": "eslint . --ext .ts --fix", "lint:fix": "eslint . --ext .ts --fix",
"test": "jest spec --coverage --passWithNoTests" "test": "jest --coverage --no-cache --passWithNoTests"
}, },
"dependencies": { "dependencies": {
"uuid": "^9.0.0" "uuid": "^9.0.0"
@@ -4,6 +4,7 @@ import { Result } from '../Core/Result'
import { NotificationPayloadProps } from './NotificationPayloadProps' import { NotificationPayloadProps } from './NotificationPayloadProps'
import { NotificationType } from './NotificationType' import { NotificationType } from './NotificationType'
import { Uuid } from '../Common/Uuid' import { Uuid } from '../Common/Uuid'
import { NotificationPayloadIdentifierType } from './NotificationPayloadIdentifierType'
export class NotificationPayload extends ValueObject<NotificationPayloadProps> { export class NotificationPayload extends ValueObject<NotificationPayloadProps> {
private constructor(props: NotificationPayloadProps) { private constructor(props: NotificationPayloadProps) {
@@ -14,8 +15,10 @@ export class NotificationPayload extends ValueObject<NotificationPayloadProps> {
return JSON.stringify({ return JSON.stringify({
version: this.props.version, version: this.props.version,
type: this.props.type.value, type: this.props.type.value,
sharedVaultUuid: this.props.sharedVaultUuid.value, primaryIdentifier: this.props.primaryIdentifier.value,
itemUuid: this.props.itemUuid ? this.props.itemUuid.value : undefined, primaryIndentifierType: this.props.primaryIndentifierType.value,
secondaryIdentifier: this.props.secondaryIdentifier?.value,
secondaryIdentifierType: this.props.secondaryIdentifierType?.value,
}) })
} }
@@ -29,26 +32,43 @@ export class NotificationPayload extends ValueObject<NotificationPayloadProps> {
} }
const type = typeOrError.getValue() const type = typeOrError.getValue()
const sharedVaultUuidOrError = Uuid.create(props.sharedVaultUuid) const primaryIdentifierOrError = Uuid.create(props.primaryIdentifier)
if (sharedVaultUuidOrError.isFailed()) { if (primaryIdentifierOrError.isFailed()) {
return Result.fail<NotificationPayload>(sharedVaultUuidOrError.getError()) return Result.fail<NotificationPayload>(primaryIdentifierOrError.getError())
} }
const sharedVaultUuid = sharedVaultUuidOrError.getValue() const primaryIdentifier = primaryIdentifierOrError.getValue()
let itemUuid: Uuid | undefined = undefined const primaryIndentifierTypeOrError = NotificationPayloadIdentifierType.create(props.primaryIndentifierType)
if (props.itemUuid) { if (primaryIndentifierTypeOrError.isFailed()) {
const itemUuidOrError = Uuid.create(props.itemUuid) return Result.fail<NotificationPayload>(primaryIndentifierTypeOrError.getError())
if (itemUuidOrError.isFailed()) { }
return Result.fail<NotificationPayload>(itemUuidOrError.getError()) const primaryIndentifierType = primaryIndentifierTypeOrError.getValue()
let secondaryIdentifier: Uuid | undefined
if (props.secondaryIdentifier) {
const secondaryIdentifierOrError = Uuid.create(props.secondaryIdentifier)
if (secondaryIdentifierOrError.isFailed()) {
return Result.fail<NotificationPayload>(secondaryIdentifierOrError.getError())
} }
itemUuid = itemUuidOrError.getValue() secondaryIdentifier = secondaryIdentifierOrError.getValue()
}
let secondaryIdentifierType: NotificationPayloadIdentifierType | undefined
if (props.secondaryIdentifierType) {
const secondaryIdentifierTypeOrError = NotificationPayloadIdentifierType.create(props.secondaryIdentifierType)
if (secondaryIdentifierTypeOrError.isFailed()) {
return Result.fail<NotificationPayload>(secondaryIdentifierTypeOrError.getError())
}
secondaryIdentifierType = secondaryIdentifierTypeOrError.getValue()
} }
return NotificationPayload.create({ return NotificationPayload.create({
version: props.version, version: props.version,
type, type,
sharedVaultUuid, primaryIdentifier,
itemUuid, primaryIndentifierType,
secondaryIdentifier,
secondaryIdentifierType,
}) })
} catch (error) { } catch (error) {
return Result.fail<NotificationPayload>((error as Error).message) return Result.fail<NotificationPayload>((error as Error).message)
@@ -57,7 +77,7 @@ export class NotificationPayload extends ValueObject<NotificationPayloadProps> {
static create(props: NotificationPayloadProps): Result<NotificationPayload> { static create(props: NotificationPayloadProps): Result<NotificationPayload> {
if ( if (
props.itemUuid === undefined && props.secondaryIdentifier === undefined &&
props.type.equals(NotificationType.create(NotificationType.TYPES.SharedVaultItemRemoved).getValue()) props.type.equals(NotificationType.create(NotificationType.TYPES.SharedVaultItemRemoved).getValue())
) { ) {
return Result.fail<NotificationPayload>( return Result.fail<NotificationPayload>(
@@ -0,0 +1,28 @@
import { Result } from '../Core/Result'
import { ValueObject } from '../Core/ValueObject'
import { NotificationPayloadIdentifierTypeProps } from './NotificationPayloadIdentifierTypeProps'
export class NotificationPayloadIdentifierType extends ValueObject<NotificationPayloadIdentifierTypeProps> {
static readonly TYPES = {
SharedVaultUuid: 'shared_vault_uuid',
UserUuid: 'user_uuid',
SharedVaultInviteUuid: 'shared_vault_invite_uuid',
ItemUuid: 'item_uuid',
}
private constructor(props: NotificationPayloadIdentifierTypeProps) {
super(props)
}
get value(): string {
return this.props.value
}
static create(type: string): Result<NotificationPayloadIdentifierType> {
if (!Object.values(this.TYPES).includes(type)) {
return Result.fail<NotificationPayloadIdentifierType>(`Invalid notification payload identifier type: ${type}`)
}
return Result.ok<NotificationPayloadIdentifierType>(new NotificationPayloadIdentifierType({ value: type }))
}
}
@@ -0,0 +1,3 @@
export interface NotificationPayloadIdentifierTypeProps {
value: string
}
@@ -1,9 +1,12 @@
import { Uuid } from '../Common/Uuid' import { Uuid } from '../Common/Uuid'
import { NotificationPayloadIdentifierType } from './NotificationPayloadIdentifierType'
import { NotificationType } from './NotificationType' import { NotificationType } from './NotificationType'
export interface NotificationPayloadProps { export interface NotificationPayloadProps {
type: NotificationType type: NotificationType
sharedVaultUuid: Uuid primaryIdentifier: Uuid
primaryIndentifierType: NotificationPayloadIdentifierType
secondaryIdentifier?: Uuid
secondaryIdentifierType?: NotificationPayloadIdentifierType
version: string version: string
itemUuid?: Uuid
} }
@@ -7,8 +7,9 @@ export class NotificationType extends ValueObject<NotificationTypeProps> {
SharedVaultItemRemoved: 'shared_vault_item_removed', SharedVaultItemRemoved: 'shared_vault_item_removed',
SelfRemovedFromSharedVault: 'self_removed_from_shared_vault', SelfRemovedFromSharedVault: 'self_removed_from_shared_vault',
UserRemovedFromSharedVault: 'user_removed_from_shared_vault', UserRemovedFromSharedVault: 'user_removed_from_shared_vault',
UserDesignatedAsSurvivor: 'user_designated_as_survivor',
UserAddedToSharedVault: 'user_added_to_shared_vault', UserAddedToSharedVault: 'user_added_to_shared_vault',
SharedVaultInviteDeclined: 'shared_vault_invite_declined', SharedVaultInviteCanceled: 'shared_vault_invite_canceled',
SharedVaultFileUploaded: 'shared_vault_file_uploaded', SharedVaultFileUploaded: 'shared_vault_file_uploaded',
SharedVaultFileRemoved: 'shared_vault_file_removed', SharedVaultFileRemoved: 'shared_vault_file_removed',
} }
+2
View File
@@ -48,6 +48,8 @@ export * from './Env/AbstractEnv'
export * from './Mapping/MapperInterface' export * from './Mapping/MapperInterface'
export * from './Notification/NotificationPayload' export * from './Notification/NotificationPayload'
export * from './Notification/NotificationPayloadIdentifierType'
export * from './Notification/NotificationPayloadIdentifierTypeProps'
export * from './Notification/NotificationPayloadProps' export * from './Notification/NotificationPayloadProps'
export * from './Notification/NotificationType' export * from './Notification/NotificationType'
export * from './Notification/NotificationTypeProps' export * from './Notification/NotificationTypeProps'
+14
View File
@@ -3,6 +3,20 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.13.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.12.34...@standardnotes/domain-events-infra@1.13.0) (2023-09-26)
### Features
* refactor handling revision creation from dump ([#854](https://github.com/standardnotes/server/issues/854)) ([ca6dbc0](https://github.com/standardnotes/server/commit/ca6dbc00537bb20f508f9310b1a838421f53a643))
## [1.12.34](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.12.33...@standardnotes/domain-events-infra@1.12.34) (2023-09-25)
**Note:** Version bump only for package @standardnotes/domain-events-infra
## [1.12.33](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.12.32...@standardnotes/domain-events-infra@1.12.33) (2023-09-25)
**Note:** Version bump only for package @standardnotes/domain-events-infra
## [1.12.32](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.12.31...@standardnotes/domain-events-infra@1.12.32) (2023-09-21) ## [1.12.32](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.12.31...@standardnotes/domain-events-infra@1.12.32) (2023-09-21)
**Note:** Version bump only for package @standardnotes/domain-events-infra **Note:** Version bump only for package @standardnotes/domain-events-infra
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@standardnotes/domain-events-infra", "name": "@standardnotes/domain-events-infra",
"version": "1.12.32", "version": "1.13.0",
"engines": { "engines": {
"node": ">=18.0.0 <21.0.0" "node": ">=18.0.0 <21.0.0"
}, },
@@ -21,7 +21,7 @@
"build": "tsc --build", "build": "tsc --build",
"lint": "eslint . --ext .ts", "lint": "eslint . --ext .ts",
"lint:fix": "eslint . --ext .ts --fix", "lint:fix": "eslint . --ext .ts --fix",
"test": "jest spec --coverage" "test": "jest --coverage --no-cache"
}, },
"dependencies": { "dependencies": {
"@aws-sdk/client-sns": "^3.332.0", "@aws-sdk/client-sns": "^3.332.0",
+18
View File
@@ -3,6 +3,24 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [2.131.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.130.0...@standardnotes/domain-events@2.131.0) (2023-09-26)
### Features
* refactor handling revision creation from dump ([#854](https://github.com/standardnotes/server/issues/854)) ([ca6dbc0](https://github.com/standardnotes/server/commit/ca6dbc00537bb20f508f9310b1a838421f53a643))
# [2.130.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.129.1...@standardnotes/domain-events@2.130.0) (2023-09-25)
### Features
* remove shared vault files upon shared vault removal ([#852](https://github.com/standardnotes/server/issues/852)) ([7b1eec2](https://github.com/standardnotes/server/commit/7b1eec21e54330bebbeebb80cec3ba4284112aab))
## [2.129.1](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.129.0...@standardnotes/domain-events@2.129.1) (2023-09-25)
### Bug Fixes
* remember paging progress on transitioning ([1d73e4f](https://github.com/standardnotes/server/commit/1d73e4f0720d41029af4d4b2b7a10d101add6c82))
# [2.129.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.128.0...@standardnotes/domain-events@2.129.0) (2023-09-21) # [2.129.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.128.0...@standardnotes/domain-events@2.129.0) (2023-09-21)
### Features ### Features
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@standardnotes/domain-events", "name": "@standardnotes/domain-events",
"version": "2.129.0", "version": "2.131.0",
"engines": { "engines": {
"node": ">=18.0.0 <21.0.0" "node": ">=18.0.0 <21.0.0"
}, },
@@ -20,7 +20,7 @@
"clean": "rm -fr dist", "clean": "rm -fr dist",
"build": "tsc --build", "build": "tsc --build",
"lint": "eslint . --ext .ts", "lint": "eslint . --ext .ts",
"test": "jest spec --coverage --passWithNoTests" "test": "jest --coverage --no-cache --passWithNoTests"
}, },
"dependencies": { "dependencies": {
"@standardnotes/predicates": "workspace:*", "@standardnotes/predicates": "workspace:*",
@@ -1,3 +1,4 @@
export interface SharedVaultRemovedEventPayload { export interface SharedVaultRemovedEventPayload {
sharedVaultUuid: string sharedVaultUuid: string
vaultOwnerUuid: string
} }
@@ -3,4 +3,5 @@ export interface TransitionStatusUpdatedEventPayload {
transitionType: 'items' | 'revisions' transitionType: 'items' | 'revisions'
transitionTimestamp: number transitionTimestamp: number
status: string status: string
page?: number
} }
+26
View File
@@ -3,6 +3,32 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.12.1](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.12.0...@standardnotes/event-store@1.12.1) (2023-09-27)
**Note:** Version bump only for package @standardnotes/event-store
# [1.12.0](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.11.52...@standardnotes/event-store@1.12.0) (2023-09-26)
### Features
* refactor handling revision creation from dump ([#854](https://github.com/standardnotes/server/issues/854)) ([ca6dbc0](https://github.com/standardnotes/server/commit/ca6dbc00537bb20f508f9310b1a838421f53a643))
## [1.11.52](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.11.51...@standardnotes/event-store@1.11.52) (2023-09-25)
**Note:** Version bump only for package @standardnotes/event-store
## [1.11.51](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.11.50...@standardnotes/event-store@1.11.51) (2023-09-25)
**Note:** Version bump only for package @standardnotes/event-store
## [1.11.50](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.11.49...@standardnotes/event-store@1.11.50) (2023-09-25)
**Note:** Version bump only for package @standardnotes/event-store
## [1.11.49](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.11.48...@standardnotes/event-store@1.11.49) (2023-09-25)
**Note:** Version bump only for package @standardnotes/event-store
## [1.11.48](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.11.47...@standardnotes/event-store@1.11.48) (2023-09-21) ## [1.11.48](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.11.47...@standardnotes/event-store@1.11.48) (2023-09-21)
**Note:** Version bump only for package @standardnotes/event-store **Note:** Version bump only for package @standardnotes/event-store
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@standardnotes/event-store", "name": "@standardnotes/event-store",
"version": "1.11.48", "version": "1.12.1",
"description": "Event Store Service", "description": "Event Store Service",
"private": true, "private": true,
"main": "dist/src/index.js", "main": "dist/src/index.js",
@@ -13,7 +13,7 @@
"build": "tsc --build", "build": "tsc --build",
"lint": "eslint . --ext .ts", "lint": "eslint . --ext .ts",
"pretest": "yarn lint && yarn build", "pretest": "yarn lint && yarn build",
"test": "jest --coverage --config=./jest.config.js --maxWorkers=50%", "test": "jest --coverage --no-cache --config=./jest.config.js --maxWorkers=50%",
"worker": "yarn node dist/bin/worker.js" "worker": "yarn node dist/bin/worker.js"
}, },
"author": "Karol Sójko <karol@standardnotes.com>", "author": "Karol Sójko <karol@standardnotes.com>",
+28
View File
@@ -3,6 +3,34 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.24.1](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.24.0...@standardnotes/files-server@1.24.1) (2023-09-27)
**Note:** Version bump only for package @standardnotes/files-server
# [1.24.0](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.23.2...@standardnotes/files-server@1.24.0) (2023-09-26)
### Features
* refactor handling revision creation from dump ([#854](https://github.com/standardnotes/files/issues/854)) ([ca6dbc0](https://github.com/standardnotes/files/commit/ca6dbc00537bb20f508f9310b1a838421f53a643))
## [1.23.2](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.23.1...@standardnotes/files-server@1.23.2) (2023-09-25)
**Note:** Version bump only for package @standardnotes/files-server
## [1.23.1](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.23.0...@standardnotes/files-server@1.23.1) (2023-09-25)
**Note:** Version bump only for package @standardnotes/files-server
# [1.23.0](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.22.28...@standardnotes/files-server@1.23.0) (2023-09-25)
### Features
* remove shared vault files upon shared vault removal ([#852](https://github.com/standardnotes/files/issues/852)) ([7b1eec2](https://github.com/standardnotes/files/commit/7b1eec21e54330bebbeebb80cec3ba4284112aab))
## [1.22.28](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.22.27...@standardnotes/files-server@1.22.28) (2023-09-25)
**Note:** Version bump only for package @standardnotes/files-server
## [1.22.27](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.22.26...@standardnotes/files-server@1.22.27) (2023-09-21) ## [1.22.27](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.22.26...@standardnotes/files-server@1.22.27) (2023-09-21)
**Note:** Version bump only for package @standardnotes/files-server **Note:** Version bump only for package @standardnotes/files-server
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@standardnotes/files-server", "name": "@standardnotes/files-server",
"version": "1.22.27", "version": "1.24.1",
"engines": { "engines": {
"node": ">=18.0.0 <21.0.0" "node": ">=18.0.0 <21.0.0"
}, },
@@ -22,7 +22,7 @@
"lint": "eslint . --ext .ts", "lint": "eslint . --ext .ts",
"lint:fix": "eslint . --fix --ext .ts", "lint:fix": "eslint . --fix --ext .ts",
"pretest": "yarn lint && yarn build", "pretest": "yarn lint && yarn build",
"test": "jest --coverage --config=./jest.config.js --maxWorkers=50%", "test": "jest --coverage --no-cache --config=./jest.config.js --maxWorkers=50%",
"start": "yarn node dist/bin/server.js", "start": "yarn node dist/bin/server.js",
"worker": "yarn node dist/bin/worker.js" "worker": "yarn node dist/bin/worker.js"
}, },
@@ -1,73 +0,0 @@
import 'reflect-metadata'
import {
AccountDeletionRequestedEvent,
AccountDeletionRequestedEventPayload,
DomainEventPublisherInterface,
FileRemovedEvent,
} from '@standardnotes/domain-events'
import { MarkFilesToBeRemoved } from '../UseCase/MarkFilesToBeRemoved/MarkFilesToBeRemoved'
import { AccountDeletionRequestedEventHandler } from './AccountDeletionRequestedEventHandler'
import { DomainEventFactoryInterface } from '../Event/DomainEventFactoryInterface'
import { RemovedFileDescription } from '../File/RemovedFileDescription'
describe('AccountDeletionRequestedEventHandler', () => {
let markFilesToBeRemoved: MarkFilesToBeRemoved
let event: AccountDeletionRequestedEvent
let domainEventPublisher: DomainEventPublisherInterface
let domainEventFactory: DomainEventFactoryInterface
const createHandler = () =>
new AccountDeletionRequestedEventHandler(markFilesToBeRemoved, domainEventPublisher, domainEventFactory)
beforeEach(() => {
markFilesToBeRemoved = {} as jest.Mocked<MarkFilesToBeRemoved>
markFilesToBeRemoved.execute = jest.fn().mockReturnValue({
success: true,
filesRemoved: [{} as jest.Mocked<RemovedFileDescription>],
})
event = {} as jest.Mocked<AccountDeletionRequestedEvent>
event.payload = {
userUuid: '1-2-3',
regularSubscriptionUuid: '1-2-3',
} as jest.Mocked<AccountDeletionRequestedEventPayload>
domainEventPublisher = {} as jest.Mocked<DomainEventPublisherInterface>
domainEventPublisher.publish = jest.fn()
domainEventFactory = {} as jest.Mocked<DomainEventFactoryInterface>
domainEventFactory.createFileRemovedEvent = jest.fn().mockReturnValue({} as jest.Mocked<FileRemovedEvent>)
})
it('should mark files to be remove for user', async () => {
await createHandler().handle(event)
expect(markFilesToBeRemoved.execute).toHaveBeenCalledWith({ ownerUuid: '1-2-3' })
expect(domainEventPublisher.publish).toHaveBeenCalled()
})
it('should not mark files to be remove for user if user has no regular subscription', async () => {
event.payload.regularSubscriptionUuid = undefined
await createHandler().handle(event)
expect(markFilesToBeRemoved.execute).not.toHaveBeenCalled()
expect(domainEventPublisher.publish).not.toHaveBeenCalled()
})
it('should not publish events if failed to mark files to be removed', async () => {
markFilesToBeRemoved.execute = jest.fn().mockReturnValue({
success: false,
})
await createHandler().handle(event)
expect(markFilesToBeRemoved.execute).toHaveBeenCalledWith({ ownerUuid: '1-2-3' })
expect(domainEventPublisher.publish).not.toHaveBeenCalled()
})
})
@@ -22,15 +22,17 @@ export class AccountDeletionRequestedEventHandler implements DomainEventHandlerI
return return
} }
const response = await this.markFilesToBeRemoved.execute({ const result = await this.markFilesToBeRemoved.execute({
ownerUuid: event.payload.userUuid, ownerUuid: event.payload.userUuid,
}) })
if (!response.success) { if (result.isFailed()) {
return return
} }
for (const fileRemoved of response.filesRemoved) { const filesRemoved = result.getValue()
for (const fileRemoved of filesRemoved) {
await this.domainEventPublisher.publish( await this.domainEventPublisher.publish(
this.domainEventFactory.createFileRemovedEvent({ this.domainEventFactory.createFileRemovedEvent({
regularSubscriptionUuid: event.payload.regularSubscriptionUuid, regularSubscriptionUuid: event.payload.regularSubscriptionUuid,
@@ -1,73 +0,0 @@
import 'reflect-metadata'
import {
SharedSubscriptionInvitationCanceledEvent,
SharedSubscriptionInvitationCanceledEventPayload,
DomainEventPublisherInterface,
FileRemovedEvent,
} from '@standardnotes/domain-events'
import { MarkFilesToBeRemoved } from '../UseCase/MarkFilesToBeRemoved/MarkFilesToBeRemoved'
import { SharedSubscriptionInvitationCanceledEventHandler } from './SharedSubscriptionInvitationCanceledEventHandler'
import { DomainEventFactoryInterface } from '../Event/DomainEventFactoryInterface'
import { RemovedFileDescription } from '../File/RemovedFileDescription'
describe('SharedSubscriptionInvitationCanceledEventHandler', () => {
let markFilesToBeRemoved: MarkFilesToBeRemoved
let event: SharedSubscriptionInvitationCanceledEvent
let domainEventPublisher: DomainEventPublisherInterface
let domainEventFactory: DomainEventFactoryInterface
const createHandler = () =>
new SharedSubscriptionInvitationCanceledEventHandler(markFilesToBeRemoved, domainEventPublisher, domainEventFactory)
beforeEach(() => {
markFilesToBeRemoved = {} as jest.Mocked<MarkFilesToBeRemoved>
markFilesToBeRemoved.execute = jest.fn().mockReturnValue({
success: true,
filesRemoved: [{} as jest.Mocked<RemovedFileDescription>],
})
event = {} as jest.Mocked<SharedSubscriptionInvitationCanceledEvent>
event.payload = {
inviteeIdentifier: '1-2-3',
inviteeIdentifierType: 'uuid',
} as jest.Mocked<SharedSubscriptionInvitationCanceledEventPayload>
domainEventPublisher = {} as jest.Mocked<DomainEventPublisherInterface>
domainEventPublisher.publish = jest.fn()
domainEventFactory = {} as jest.Mocked<DomainEventFactoryInterface>
domainEventFactory.createFileRemovedEvent = jest.fn().mockReturnValue({} as jest.Mocked<FileRemovedEvent>)
})
it('should mark files to be remove for user', async () => {
await createHandler().handle(event)
expect(markFilesToBeRemoved.execute).toHaveBeenCalledWith({ ownerUuid: '1-2-3' })
expect(domainEventPublisher.publish).toHaveBeenCalled()
})
it('should not mark files to be remove for user if identifier is not of uuid type', async () => {
event.payload.inviteeIdentifierType = 'email'
await createHandler().handle(event)
expect(markFilesToBeRemoved.execute).not.toHaveBeenCalled()
expect(domainEventPublisher.publish).not.toHaveBeenCalled()
})
it('should not publish events if failed to mark files to be removed', async () => {
markFilesToBeRemoved.execute = jest.fn().mockReturnValue({
success: false,
})
await createHandler().handle(event)
expect(markFilesToBeRemoved.execute).toHaveBeenCalledWith({ ownerUuid: '1-2-3' })
expect(domainEventPublisher.publish).not.toHaveBeenCalled()
})
})
@@ -22,15 +22,17 @@ export class SharedSubscriptionInvitationCanceledEventHandler implements DomainE
return return
} }
const response = await this.markFilesToBeRemoved.execute({ const result = await this.markFilesToBeRemoved.execute({
ownerUuid: event.payload.inviteeIdentifier, ownerUuid: event.payload.inviteeIdentifier,
}) })
if (!response.success) { if (result.isFailed()) {
return return
} }
for (const fileRemoved of response.filesRemoved) { const filesRemoved = result.getValue()
for (const fileRemoved of filesRemoved) {
await this.domainEventPublisher.publish( await this.domainEventPublisher.publish(
this.domainEventFactory.createFileRemovedEvent({ this.domainEventFactory.createFileRemovedEvent({
regularSubscriptionUuid: event.payload.inviterSubscriptionUuid, regularSubscriptionUuid: event.payload.inviterSubscriptionUuid,
@@ -0,0 +1,44 @@
import {
DomainEventHandlerInterface,
DomainEventPublisherInterface,
SharedVaultRemovedEvent,
} from '@standardnotes/domain-events'
import { Logger } from 'winston'
import { MarkFilesToBeRemoved } from '../UseCase/MarkFilesToBeRemoved/MarkFilesToBeRemoved'
import { DomainEventFactoryInterface } from '../Event/DomainEventFactoryInterface'
export class SharedVaultRemovedEventHandler implements DomainEventHandlerInterface {
constructor(
private markFilesToBeRemoved: MarkFilesToBeRemoved,
private domainEventPublisher: DomainEventPublisherInterface,
private domainEventFactory: DomainEventFactoryInterface,
private logger: Logger,
) {}
async handle(event: SharedVaultRemovedEvent): Promise<void> {
const result = await this.markFilesToBeRemoved.execute({
ownerUuid: event.payload.sharedVaultUuid,
})
if (result.isFailed()) {
this.logger.error(
`Could not mark files to be removed for shared vault: ${event.payload.sharedVaultUuid}: ${result.getError()}`,
)
}
const filesRemoved = result.getValue()
for (const fileRemoved of filesRemoved) {
await this.domainEventPublisher.publish(
this.domainEventFactory.createSharedVaultFileRemovedEvent({
fileByteSize: fileRemoved.fileByteSize,
fileName: fileRemoved.fileName,
filePath: fileRemoved.filePath,
sharedVaultUuid: event.payload.sharedVaultUuid,
vaultOwnerUuid: event.payload.vaultOwnerUuid,
}),
)
}
}
}
@@ -21,7 +21,9 @@ describe('MarkFilesToBeRemoved', () => {
}) })
it('should mark files for being removed', async () => { it('should mark files for being removed', async () => {
expect(await createUseCase().execute({ ownerUuid: '1-2-3' })).toEqual({ success: true }) const result = await createUseCase().execute({ ownerUuid: '1-2-3' })
expect(result.isFailed()).toEqual(false)
expect(fileRemover.markFilesToBeRemoved).toHaveBeenCalledWith('1-2-3') expect(fileRemover.markFilesToBeRemoved).toHaveBeenCalledWith('1-2-3')
}) })
@@ -31,9 +33,7 @@ describe('MarkFilesToBeRemoved', () => {
throw new Error('Oops') throw new Error('Oops')
}) })
expect(await createUseCase().execute({ ownerUuid: '1-2-3' })).toEqual({ const result = await createUseCase().execute({ ownerUuid: '1-2-3' })
success: false, expect(result.isFailed()).toEqual(true)
message: 'Could not mark resources for removal',
})
}) })
}) })
@@ -1,36 +1,30 @@
import { inject, injectable } from 'inversify' import { inject, injectable } from 'inversify'
import { Logger } from 'winston' import { Logger } from 'winston'
import { Result, UseCaseInterface } from '@standardnotes/domain-core'
import TYPES from '../../../Bootstrap/Types' import TYPES from '../../../Bootstrap/Types'
import { FileRemoverInterface } from '../../Services/FileRemoverInterface' import { FileRemoverInterface } from '../../Services/FileRemoverInterface'
import { UseCaseInterface } from '../UseCaseInterface'
import { MarkFilesToBeRemovedDTO } from './MarkFilesToBeRemovedDTO' import { MarkFilesToBeRemovedDTO } from './MarkFilesToBeRemovedDTO'
import { MarkFilesToBeRemovedResponse } from './MarkFilesToBeRemovedResponse' import { RemovedFileDescription } from '../../File/RemovedFileDescription'
@injectable() @injectable()
export class MarkFilesToBeRemoved implements UseCaseInterface { export class MarkFilesToBeRemoved implements UseCaseInterface<RemovedFileDescription[]> {
constructor( constructor(
@inject(TYPES.Files_FileRemover) private fileRemover: FileRemoverInterface, @inject(TYPES.Files_FileRemover) private fileRemover: FileRemoverInterface,
@inject(TYPES.Files_Logger) private logger: Logger, @inject(TYPES.Files_Logger) private logger: Logger,
) {} ) {}
async execute(dto: MarkFilesToBeRemovedDTO): Promise<MarkFilesToBeRemovedResponse> { async execute(dto: MarkFilesToBeRemovedDTO): Promise<Result<RemovedFileDescription[]>> {
try { try {
this.logger.debug(`Marking files for later removal for user: ${dto.ownerUuid}`) this.logger.debug(`Marking files for later removal for user: ${dto.ownerUuid}`)
const filesRemoved = await this.fileRemover.markFilesToBeRemoved(dto.ownerUuid) const filesRemoved = await this.fileRemover.markFilesToBeRemoved(dto.ownerUuid)
return { return Result.ok(filesRemoved)
success: true,
filesRemoved,
}
} catch (error) { } catch (error) {
this.logger.error(`Could not mark resources for removal: ${dto.ownerUuid} - ${(error as Error).message}`) this.logger.error(`Could not mark resources for removal: ${dto.ownerUuid} - ${(error as Error).message}`)
return { return Result.fail('Could not mark resources for removal')
success: false,
message: 'Could not mark resources for removal',
}
} }
} }
} }
-2
View File
@@ -16,5 +16,3 @@ MONGO_PORT=27017
MONGO_USERNAME=standardnotes MONGO_USERNAME=standardnotes
MONGO_PASSWORD=standardnotes MONGO_PASSWORD=standardnotes
MONGO_DATABASE=standardnotes MONGO_DATABASE=standardnotes
TRANSITION_MODE_ENABLED=false
+46
View File
@@ -3,6 +3,52 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.16.8](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.16.7...@standardnotes/home-server@1.16.8) (2023-09-27)
**Note:** Version bump only for package @standardnotes/home-server
## [1.16.7](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.16.6...@standardnotes/home-server@1.16.7) (2023-09-26)
**Note:** Version bump only for package @standardnotes/home-server
## [1.16.6](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.16.5...@standardnotes/home-server@1.16.6) (2023-09-25)
**Note:** Version bump only for package @standardnotes/home-server
## [1.16.5](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.16.4...@standardnotes/home-server@1.16.5) (2023-09-25)
**Note:** Version bump only for package @standardnotes/home-server
## [1.16.4](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.16.3...@standardnotes/home-server@1.16.4) (2023-09-25)
**Note:** Version bump only for package @standardnotes/home-server
## [1.16.3](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.16.2...@standardnotes/home-server@1.16.3) (2023-09-25)
**Note:** Version bump only for package @standardnotes/home-server
## [1.16.2](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.16.1...@standardnotes/home-server@1.16.2) (2023-09-25)
**Note:** Version bump only for package @standardnotes/home-server
## [1.16.1](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.16.0...@standardnotes/home-server@1.16.1) (2023-09-25)
**Note:** Version bump only for package @standardnotes/home-server
# [1.16.0](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.80...@standardnotes/home-server@1.16.0) (2023-09-22)
### Features
* remove transition mode from code ([5001496](https://github.com/standardnotes/server/commit/5001496c7bc1df9e20c2d88ebf52ed77f868110c))
## [1.15.80](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.79...@standardnotes/home-server@1.15.80) (2023-09-22)
**Note:** Version bump only for package @standardnotes/home-server
## [1.15.79](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.78...@standardnotes/home-server@1.15.79) (2023-09-22)
**Note:** Version bump only for package @standardnotes/home-server
## [1.15.78](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.77...@standardnotes/home-server@1.15.78) (2023-09-22) ## [1.15.78](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.77...@standardnotes/home-server@1.15.78) (2023-09-22)
**Note:** Version bump only for package @standardnotes/home-server **Note:** Version bump only for package @standardnotes/home-server
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@standardnotes/home-server", "name": "@standardnotes/home-server",
"version": "1.15.78", "version": "1.16.8",
"engines": { "engines": {
"node": ">=18.0.0 <21.0.0" "node": ">=18.0.0 <21.0.0"
}, },
+6
View File
@@ -3,6 +3,12 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.7.0](https://github.com/standardnotes/server/compare/@standardnotes/predicates@1.6.11...@standardnotes/predicates@1.7.0) (2023-09-26)
### Features
* refactor handling revision creation from dump ([#854](https://github.com/standardnotes/server/issues/854)) ([ca6dbc0](https://github.com/standardnotes/server/commit/ca6dbc00537bb20f508f9310b1a838421f53a643))
## [1.6.11](https://github.com/standardnotes/server/compare/@standardnotes/predicates@1.6.10...@standardnotes/predicates@1.6.11) (2023-09-04) ## [1.6.11](https://github.com/standardnotes/server/compare/@standardnotes/predicates@1.6.10...@standardnotes/predicates@1.6.11) (2023-09-04)
**Note:** Version bump only for package @standardnotes/predicates **Note:** Version bump only for package @standardnotes/predicates
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@standardnotes/predicates", "name": "@standardnotes/predicates",
"version": "1.6.11", "version": "1.7.0",
"engines": { "engines": {
"node": ">=18.0.0 <21.0.0" "node": ">=18.0.0 <21.0.0"
}, },
@@ -22,7 +22,7 @@
"start": "tsc -p tsconfig.json --watch", "start": "tsc -p tsconfig.json --watch",
"build": "tsc --build", "build": "tsc --build",
"lint": "eslint . --ext .ts", "lint": "eslint . --ext .ts",
"test": "jest spec --coverage --passWithNoTests" "test": "jest --coverage --no-cache --passWithNoTests"
}, },
"devDependencies": { "devDependencies": {
"@types/jest": "^29.5.1", "@types/jest": "^29.5.1",
+49
View File
@@ -3,6 +3,55 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.38.1](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.38.0...@standardnotes/revisions-server@1.38.1) (2023-09-27)
**Note:** Version bump only for package @standardnotes/revisions-server
# [1.38.0](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.37.3...@standardnotes/revisions-server@1.38.0) (2023-09-26)
### Features
* refactor handling revision creation from dump ([#854](https://github.com/standardnotes/server/issues/854)) ([ca6dbc0](https://github.com/standardnotes/server/commit/ca6dbc00537bb20f508f9310b1a838421f53a643))
## [1.37.3](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.37.2...@standardnotes/revisions-server@1.37.3) (2023-09-25)
**Note:** Version bump only for package @standardnotes/revisions-server
## [1.37.2](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.37.1...@standardnotes/revisions-server@1.37.2) (2023-09-25)
**Note:** Version bump only for package @standardnotes/revisions-server
## [1.37.1](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.37.0...@standardnotes/revisions-server@1.37.1) (2023-09-25)
**Note:** Version bump only for package @standardnotes/revisions-server
# [1.37.0](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.36.7...@standardnotes/revisions-server@1.37.0) (2023-09-25)
### Features
* add storing paging progress in redis ([9759814](https://github.com/standardnotes/server/commit/9759814f637b8ae25b325e35bc7f5159747980b6))
## [1.36.7](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.36.6...@standardnotes/revisions-server@1.36.7) (2023-09-25)
### Bug Fixes
* remember paging progress on transitioning ([1d73e4f](https://github.com/standardnotes/server/commit/1d73e4f0720d41029af4d4b2b7a10d101add6c82))
## [1.36.6](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.36.5...@standardnotes/revisions-server@1.36.6) (2023-09-22)
### Bug Fixes
* add more logs to transition process ([0562b0a](https://github.com/standardnotes/server/commit/0562b0a621eb878026fbdc0346b6170e815b64bf))
* remove excessive logs ([15ed1fd](https://github.com/standardnotes/server/commit/15ed1fd789aba306cbec6a23e88d5c1f837dabc0))
## [1.36.5](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.36.4...@standardnotes/revisions-server@1.36.5) (2023-09-22)
### Bug Fixes
* disable cleaning secondary database on transition ([4f4443a](https://github.com/standardnotes/server/commit/4f4443a882f69c2e76ef831ef36347c9c54f31cd))
* integrity check during transition ([921c30f](https://github.com/standardnotes/server/commit/921c30f6415ef122a7d1af83ffa3f6840a42edba))
* processing migration optimization ([22540ee](https://github.com/standardnotes/server/commit/22540ee83436b986949127a6923285a702162706))
## [1.36.4](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.36.3...@standardnotes/revisions-server@1.36.4) (2023-09-21) ## [1.36.4](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.36.3...@standardnotes/revisions-server@1.36.4) (2023-09-21)
### Bug Fixes ### Bug Fixes
+4 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@standardnotes/revisions-server", "name": "@standardnotes/revisions-server",
"version": "1.36.4", "version": "1.38.1",
"engines": { "engines": {
"node": ">=18.0.0 <21.0.0" "node": ">=18.0.0 <21.0.0"
}, },
@@ -20,7 +20,7 @@
"lint": "eslint . --ext .ts", "lint": "eslint . --ext .ts",
"lint:fix": "eslint . --ext .ts --fix", "lint:fix": "eslint . --ext .ts --fix",
"pretest": "yarn lint && yarn build", "pretest": "yarn lint && yarn build",
"test": "jest --coverage --config=./jest.config.js --maxWorkers=50%", "test": "jest --coverage --no-cache --config=./jest.config.js --maxWorkers=50%",
"start": "yarn node dist/bin/server.js", "start": "yarn node dist/bin/server.js",
"worker": "yarn node dist/bin/worker.js" "worker": "yarn node dist/bin/worker.js"
}, },
@@ -41,6 +41,7 @@
"express": "^4.18.2", "express": "^4.18.2",
"inversify": "^6.0.1", "inversify": "^6.0.1",
"inversify-express-utils": "^6.4.3", "inversify-express-utils": "^6.4.3",
"ioredis": "^5.3.2",
"mongodb": "^6.0.0", "mongodb": "^6.0.0",
"mysql2": "^3.0.1", "mysql2": "^3.0.1",
"reflect-metadata": "0.1.13", "reflect-metadata": "0.1.13",
@@ -52,6 +53,7 @@
"@types/cors": "^2.8.9", "@types/cors": "^2.8.9",
"@types/dotenv": "^8.2.0", "@types/dotenv": "^8.2.0",
"@types/express": "^4.17.14", "@types/express": "^4.17.14",
"@types/ioredis": "^5.0.0",
"@types/jest": "^29.5.1", "@types/jest": "^29.5.1",
"@types/node": "^20.5.7", "@types/node": "^20.5.7",
"@typescript-eslint/eslint-plugin": "^6.5.0", "@typescript-eslint/eslint-plugin": "^6.5.0",
+48 -20
View File
@@ -1,4 +1,5 @@
import { ControllerContainer, ControllerContainerInterface, MapperInterface } from '@standardnotes/domain-core' import { ControllerContainer, ControllerContainerInterface, MapperInterface } from '@standardnotes/domain-core'
import Redis from 'ioredis'
import { Container, interfaces } from 'inversify' import { Container, interfaces } from 'inversify'
import { MongoRepository, Repository } from 'typeorm' import { MongoRepository, Repository } from 'typeorm'
import * as winston from 'winston' import * as winston from 'winston'
@@ -68,6 +69,9 @@ import { RemoveRevisionsFromSharedVault } from '../Domain/UseCase/RemoveRevision
import { ItemRemovedFromSharedVaultEventHandler } from '../Domain/Handler/ItemRemovedFromSharedVaultEventHandler' import { ItemRemovedFromSharedVaultEventHandler } from '../Domain/Handler/ItemRemovedFromSharedVaultEventHandler'
import { TransitionRequestedEventHandler } from '../Domain/Handler/TransitionRequestedEventHandler' import { TransitionRequestedEventHandler } from '../Domain/Handler/TransitionRequestedEventHandler'
import { SharedVaultRemovedEventHandler } from '../Domain/Handler/SharedVaultRemovedEventHandler' import { SharedVaultRemovedEventHandler } from '../Domain/Handler/SharedVaultRemovedEventHandler'
import { TransitionRepositoryInterface } from '../Domain/Transition/TransitionRepositoryInterface'
import { RedisTransitionRepository } from '../Infra/Redis/RedisTransitionRepository'
import { CreateRevisionFromDump } from '../Domain/UseCase/CreateRevisionFromDump/CreateRevisionFromDump'
export class ContainerConfigLoader { export class ContainerConfigLoader {
constructor(private mode: 'server' | 'worker' = 'server') {} constructor(private mode: 'server' | 'worker' = 'server') {}
@@ -88,11 +92,28 @@ export class ContainerConfigLoader {
const isConfiguredForSelfHosting = env.get('MODE', true) === 'self-hosted' const isConfiguredForSelfHosting = env.get('MODE', true) === 'self-hosted'
const isConfiguredForHomeServerOrSelfHosting = isConfiguredForHomeServer || isConfiguredForSelfHosting const isConfiguredForHomeServerOrSelfHosting = isConfiguredForHomeServer || isConfiguredForSelfHosting
const isSecondaryDatabaseEnabled = env.get('SECONDARY_DB_ENABLED', true) === 'true' const isSecondaryDatabaseEnabled = env.get('SECONDARY_DB_ENABLED', true) === 'true'
const isConfiguredForInMemoryCache = env.get('CACHE_TYPE', true) === 'memory'
const container = new Container({ const container = new Container({
defaultScope: 'Singleton', defaultScope: 'Singleton',
}) })
if (!isConfiguredForInMemoryCache) {
const redisUrl = env.get('REDIS_URL')
const isRedisInClusterMode = redisUrl.indexOf(',') > 0
let redis
if (isRedisInClusterMode) {
redis = new Redis.Cluster(redisUrl.split(','))
} else {
redis = new Redis(redisUrl)
}
container.bind(TYPES.Revisions_Redis).toConstantValue(redis)
container
.bind<TransitionRepositoryInterface>(TYPES.Revisions_TransitionStatusRepository)
.toConstantValue(new RedisTransitionRepository(container.get<Redis>(TYPES.Revisions_Redis)))
}
let logger: winston.Logger let logger: winston.Logger
if (configuration?.logger) { if (configuration?.logger) {
logger = configuration.logger as winston.Logger logger = configuration.logger as winston.Logger
@@ -310,6 +331,21 @@ export class ContainerConfigLoader {
.toDynamicValue((context: interfaces.Context) => { .toDynamicValue((context: interfaces.Context) => {
return new RevisionMetadataHttpMapper(context.container.get(TYPES.Revisions_GetRequiredRoleToViewRevision)) return new RevisionMetadataHttpMapper(context.container.get(TYPES.Revisions_GetRequiredRoleToViewRevision))
}) })
container
.bind<MapperInterface<Revision, string>>(TYPES.Revisions_RevisionItemStringMapper)
.toDynamicValue(() => new RevisionItemStringMapper())
container
.bind<DumpRepositoryInterface>(TYPES.Revisions_DumpRepository)
.toConstantValue(
env.get('S3_AWS_REGION', true)
? new S3DumpRepository(
container.get(TYPES.Revisions_S3_BACKUP_BUCKET_NAME),
container.get(TYPES.Revisions_S3),
container.get(TYPES.Revisions_RevisionItemStringMapper),
)
: new FSDumpRepository(container.get(TYPES.Revisions_RevisionItemStringMapper)),
)
// use cases // use cases
container container
@@ -348,6 +384,9 @@ export class ContainerConfigLoader {
isSecondaryDatabaseEnabled isSecondaryDatabaseEnabled
? container.get<RevisionRepositoryInterface>(TYPES.Revisions_MongoDBRevisionRepository) ? container.get<RevisionRepositoryInterface>(TYPES.Revisions_MongoDBRevisionRepository)
: null, : null,
isConfiguredForInMemoryCache
? null
: container.get<TransitionRepositoryInterface>(TYPES.Revisions_TransitionStatusRepository),
container.get<TimerInterface>(TYPES.Revisions_Timer), container.get<TimerInterface>(TYPES.Revisions_Timer),
container.get<winston.Logger>(TYPES.Revisions_Logger), container.get<winston.Logger>(TYPES.Revisions_Logger),
env.get('MIGRATION_BATCH_SIZE', true) ? +env.get('MIGRATION_BATCH_SIZE', true) : 100, env.get('MIGRATION_BATCH_SIZE', true) ? +env.get('MIGRATION_BATCH_SIZE', true) : 100,
@@ -362,6 +401,14 @@ export class ContainerConfigLoader {
: container.get<RevisionRepositoryInterface>(TYPES.Revisions_SQLRevisionRepository), : container.get<RevisionRepositoryInterface>(TYPES.Revisions_SQLRevisionRepository),
), ),
) )
container
.bind<CreateRevisionFromDump>(TYPES.Revisions_CreateRevisionFromDump)
.toConstantValue(
new CreateRevisionFromDump(
container.get<DumpRepositoryInterface>(TYPES.Revisions_DumpRepository),
container.get<RevisionRepositoryResolverInterface>(TYPES.Revisions_RevisionRepositoryResolver),
),
)
// env vars // env vars
container.bind(TYPES.Revisions_AUTH_JWT_SECRET).toConstantValue(env.get('AUTH_JWT_SECRET')) container.bind(TYPES.Revisions_AUTH_JWT_SECRET).toConstantValue(env.get('AUTH_JWT_SECRET'))
@@ -386,31 +433,12 @@ export class ContainerConfigLoader {
) )
}) })
// Map
container
.bind<MapperInterface<Revision, string>>(TYPES.Revisions_RevisionItemStringMapper)
.toDynamicValue(() => new RevisionItemStringMapper())
container
.bind<DumpRepositoryInterface>(TYPES.Revisions_DumpRepository)
.toConstantValue(
env.get('S3_AWS_REGION', true)
? new S3DumpRepository(
container.get(TYPES.Revisions_S3_BACKUP_BUCKET_NAME),
container.get(TYPES.Revisions_S3),
container.get(TYPES.Revisions_RevisionItemStringMapper),
container.get(TYPES.Revisions_Logger),
)
: new FSDumpRepository(container.get(TYPES.Revisions_RevisionItemStringMapper)),
)
// Handlers // Handlers
container container
.bind<ItemDumpedEventHandler>(TYPES.Revisions_ItemDumpedEventHandler) .bind<ItemDumpedEventHandler>(TYPES.Revisions_ItemDumpedEventHandler)
.toConstantValue( .toConstantValue(
new ItemDumpedEventHandler( new ItemDumpedEventHandler(
container.get<DumpRepositoryInterface>(TYPES.Revisions_DumpRepository), container.get<CreateRevisionFromDump>(TYPES.Revisions_CreateRevisionFromDump),
container.get<RevisionRepositoryResolverInterface>(TYPES.Revisions_RevisionRepositoryResolver),
container.get<winston.Logger>(TYPES.Revisions_Logger), container.get<winston.Logger>(TYPES.Revisions_Logger),
), ),
) )
@@ -1,6 +1,7 @@
const TYPES = { const TYPES = {
Revisions_DBConnection: Symbol.for('Revisions_DBConnection'), Revisions_DBConnection: Symbol.for('Revisions_DBConnection'),
Revisions_Logger: Symbol.for('Revisions_Logger'), Revisions_Logger: Symbol.for('Revisions_Logger'),
Revisions_Redis: Symbol.for('Revisions_Redis'),
Revisions_SQS: Symbol.for('Revisions_SQS'), Revisions_SQS: Symbol.for('Revisions_SQS'),
Revisions_SNS: Symbol.for('Revisions_SNS'), Revisions_SNS: Symbol.for('Revisions_SNS'),
Revisions_S3: Symbol.for('Revisions_S3'), Revisions_S3: Symbol.for('Revisions_S3'),
@@ -27,6 +28,7 @@ const TYPES = {
Revisions_MongoDBRevisionRepository: Symbol.for('Revisions_MongoDBRevisionRepository'), Revisions_MongoDBRevisionRepository: Symbol.for('Revisions_MongoDBRevisionRepository'),
Revisions_DumpRepository: Symbol.for('Revisions_DumpRepository'), Revisions_DumpRepository: Symbol.for('Revisions_DumpRepository'),
Revisions_RevisionRepositoryResolver: Symbol.for('Revisions_RevisionRepositoryResolver'), Revisions_RevisionRepositoryResolver: Symbol.for('Revisions_RevisionRepositoryResolver'),
Revisions_TransitionStatusRepository: Symbol.for('Revisions_TransitionStatusRepository'),
// env vars // env vars
Revisions_AUTH_JWT_SECRET: Symbol.for('Revisions_AUTH_JWT_SECRET'), Revisions_AUTH_JWT_SECRET: Symbol.for('Revisions_AUTH_JWT_SECRET'),
Revisions_SQS_QUEUE_URL: Symbol.for('Revisions_SQS_QUEUE_URL'), Revisions_SQS_QUEUE_URL: Symbol.for('Revisions_SQS_QUEUE_URL'),
@@ -47,6 +49,7 @@ const TYPES = {
'Revisions_TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser', 'Revisions_TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser',
), ),
Revisions_RemoveRevisionsFromSharedVault: Symbol.for('Revisions_RemoveRevisionsFromSharedVault'), Revisions_RemoveRevisionsFromSharedVault: Symbol.for('Revisions_RemoveRevisionsFromSharedVault'),
Revisions_CreateRevisionFromDump: Symbol.for('Revisions_CreateRevisionFromDump'),
// Controller // Controller
Revisions_ControllerContainer: Symbol.for('Revisions_ControllerContainer'), Revisions_ControllerContainer: Symbol.for('Revisions_ControllerContainer'),
Revisions_RevisionsController: Symbol.for('Revisions_RevisionsController'), Revisions_RevisionsController: Symbol.for('Revisions_RevisionsController'),
@@ -1,6 +1,8 @@
import { Result } from '@standardnotes/domain-core'
import { Revision } from '../Revision/Revision' import { Revision } from '../Revision/Revision'
export interface DumpRepositoryInterface { export interface DumpRepositoryInterface {
getRevisionFromDumpPath(path: string): Promise<Revision | null> getRevisionFromDumpPath(path: string): Promise<Result<Revision>>
removeDump(path: string): Promise<void> removeDump(path: string): Promise<void>
} }
@@ -1,79 +0,0 @@
import { ItemDumpedEvent } from '@standardnotes/domain-events'
import { Logger } from 'winston'
import { Uuid, ContentType, Dates } from '@standardnotes/domain-core'
import { DumpRepositoryInterface } from '../Dump/DumpRepositoryInterface'
import { Revision } from '../Revision/Revision'
import { RevisionRepositoryInterface } from '../Revision/RevisionRepositoryInterface'
import { ItemDumpedEventHandler } from './ItemDumpedEventHandler'
import { RevisionRepositoryResolverInterface } from '../Revision/RevisionRepositoryResolverInterface'
describe('ItemDumpedEventHandler', () => {
let dumpRepository: DumpRepositoryInterface
let revisionRepository: RevisionRepositoryInterface
let revisionRepositoryResolver: RevisionRepositoryResolverInterface
let revision: Revision
let event: ItemDumpedEvent
let logger: Logger
const createHandler = () => new ItemDumpedEventHandler(dumpRepository, revisionRepositoryResolver, logger)
beforeEach(() => {
revision = Revision.create({
itemUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
userUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
content: 'test',
contentType: ContentType.create('Note').getValue(),
itemsKeyId: 'test',
encItemKey: 'test',
authHash: 'test',
creationDate: new Date(1),
dates: Dates.create(new Date(1), new Date(2)).getValue(),
}).getValue()
dumpRepository = {} as jest.Mocked<DumpRepositoryInterface>
dumpRepository.getRevisionFromDumpPath = jest.fn().mockReturnValue(revision)
dumpRepository.removeDump = jest.fn()
revisionRepository = {} as jest.Mocked<RevisionRepositoryInterface>
revisionRepository.insert = jest.fn()
revisionRepositoryResolver = {} as jest.Mocked<RevisionRepositoryResolverInterface>
revisionRepositoryResolver.resolve = jest.fn().mockReturnValue(revisionRepository)
event = {} as jest.Mocked<ItemDumpedEvent>
event.payload = {
fileDumpPath: 'foobar',
roleNames: ['CORE_USER'],
}
logger = {} as jest.Mocked<Logger>
logger.debug = jest.fn()
logger.error = jest.fn()
})
it('should save a revision from file dump', async () => {
await createHandler().handle(event)
expect(revisionRepository.insert).toHaveBeenCalled()
expect(dumpRepository.removeDump).toHaveBeenCalled()
})
it('should do nothing if role names are not valid', async () => {
event.payload.roleNames = ['INVALID_ROLE_NAME']
await createHandler().handle(event)
expect(revisionRepository.insert).not.toHaveBeenCalled()
expect(dumpRepository.removeDump).toHaveBeenCalled()
})
it('should not save a revision if it could not be created from dump', async () => {
dumpRepository.getRevisionFromDumpPath = jest.fn().mockReturnValue(null)
await createHandler().handle(event)
expect(revisionRepository.insert).not.toHaveBeenCalled()
expect(dumpRepository.removeDump).toHaveBeenCalled()
})
})
@@ -1,44 +1,22 @@
import { DomainEventHandlerInterface, ItemDumpedEvent } from '@standardnotes/domain-events' import { DomainEventHandlerInterface, ItemDumpedEvent } from '@standardnotes/domain-events'
import { DumpRepositoryInterface } from '../Dump/DumpRepositoryInterface'
import { RevisionRepositoryResolverInterface } from '../Revision/RevisionRepositoryResolverInterface'
import { RoleNameCollection } from '@standardnotes/domain-core'
import { Logger } from 'winston' import { Logger } from 'winston'
import { CreateRevisionFromDump } from '../UseCase/CreateRevisionFromDump/CreateRevisionFromDump'
export class ItemDumpedEventHandler implements DomainEventHandlerInterface { export class ItemDumpedEventHandler implements DomainEventHandlerInterface {
constructor( constructor(
private dumpRepository: DumpRepositoryInterface, private createRevisionFromDump: CreateRevisionFromDump,
private revisionRepositoryResolver: RevisionRepositoryResolverInterface,
private logger: Logger, private logger: Logger,
) {} ) {}
async handle(event: ItemDumpedEvent): Promise<void> { async handle(event: ItemDumpedEvent): Promise<void> {
const revision = await this.dumpRepository.getRevisionFromDumpPath(event.payload.fileDumpPath) const result = await this.createRevisionFromDump.execute({
if (revision === null) { filePath: event.payload.fileDumpPath,
this.logger.error(`Revision not found for dump path ${event.payload.fileDumpPath}`) roleNames: event.payload.roleNames,
})
await this.dumpRepository.removeDump(event.payload.fileDumpPath) if (result.isFailed()) {
this.logger.error(`Item dumped event handler failed: ${result.getError()}`)
return
} }
const roleNamesOrError = RoleNameCollection.create(event.payload.roleNames)
if (roleNamesOrError.isFailed()) {
this.logger.error(`Invalid role names ${event.payload.roleNames}`)
await this.dumpRepository.removeDump(event.payload.fileDumpPath)
return
}
const roleNames = roleNamesOrError.getValue()
const revisionRepository = this.revisionRepositoryResolver.resolve(roleNames)
const successfullyInserted = await revisionRepository.insert(revision)
if (!successfullyInserted) {
this.logger.error(`Could not insert revision ${revision.id.toString()}`)
}
await this.dumpRepository.removeDump(event.payload.fileDumpPath)
} }
} }
@@ -0,0 +1,4 @@
export interface TransitionRepositoryInterface {
getPagingProgress(userUuid: string): Promise<number>
setPagingProgress(userUuid: string, progress: number): Promise<void>
}
@@ -0,0 +1,99 @@
import { Uuid, ContentType, Dates, Result } from '@standardnotes/domain-core'
import { DumpRepositoryInterface } from '../../Dump/DumpRepositoryInterface'
import { Revision } from '../../Revision/Revision'
import { RevisionRepositoryInterface } from '../../Revision/RevisionRepositoryInterface'
import { RevisionRepositoryResolverInterface } from '../../Revision/RevisionRepositoryResolverInterface'
import { CreateRevisionFromDump } from './CreateRevisionFromDump'
describe('CreateRevisionFromDump', () => {
let revisionRepository: RevisionRepositoryInterface
let revision: Revision
let dumpRepository: DumpRepositoryInterface
let revisionRepositoryResolver: RevisionRepositoryResolverInterface
const createUseCase = () => new CreateRevisionFromDump(dumpRepository, revisionRepositoryResolver)
beforeEach(() => {
revision = Revision.create({
itemUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
userUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
content: 'test',
contentType: ContentType.create('Note').getValue(),
itemsKeyId: 'test',
encItemKey: 'test',
authHash: 'test',
creationDate: new Date(1),
dates: Dates.create(new Date(1), new Date(2)).getValue(),
}).getValue()
dumpRepository = {} as jest.Mocked<DumpRepositoryInterface>
dumpRepository.getRevisionFromDumpPath = jest.fn().mockReturnValue(Result.ok(revision))
dumpRepository.removeDump = jest.fn()
revisionRepository = {} as jest.Mocked<RevisionRepositoryInterface>
revisionRepository.insert = jest.fn().mockReturnValue(true)
revisionRepositoryResolver = {} as jest.Mocked<RevisionRepositoryResolverInterface>
revisionRepositoryResolver.resolve = jest.fn().mockReturnValue(revisionRepository)
})
it('should create a revision from file dump', async () => {
const result = await createUseCase().execute({
filePath: 'foobar',
roleNames: ['CORE_USER'],
})
expect(result.isFailed()).toBeFalsy()
expect(revisionRepository.insert).toHaveBeenCalled()
expect(dumpRepository.removeDump).toHaveBeenCalled()
})
it('should fail if file path is empty', async () => {
const result = await createUseCase().execute({
filePath: '',
roleNames: ['CORE_USER'],
})
expect(result.isFailed()).toBeTruthy()
expect(revisionRepository.insert).not.toHaveBeenCalled()
expect(dumpRepository.removeDump).not.toHaveBeenCalled()
})
it('should fail if role name is invalid', async () => {
const result = await createUseCase().execute({
filePath: 'foobar',
roleNames: ['INVALID_ROLE_NAME'],
})
expect(result.isFailed()).toBeTruthy()
expect(revisionRepository.insert).not.toHaveBeenCalled()
expect(dumpRepository.removeDump).toHaveBeenCalled()
})
it('should fail if revision cannot be found', async () => {
dumpRepository.getRevisionFromDumpPath = jest.fn().mockReturnValue(Result.fail('Oops'))
const result = await createUseCase().execute({
filePath: 'foobar',
roleNames: ['CORE_USER'],
})
expect(result.isFailed()).toBeTruthy()
expect(revisionRepository.insert).not.toHaveBeenCalled()
expect(dumpRepository.removeDump).toHaveBeenCalled()
})
it('should fail if revision cannot be inserted', async () => {
revisionRepository.insert = jest.fn().mockReturnValue(false)
const result = await createUseCase().execute({
filePath: 'foobar',
roleNames: ['CORE_USER'],
})
expect(result.isFailed()).toBeTruthy()
expect(revisionRepository.insert).toHaveBeenCalled()
expect(dumpRepository.removeDump).toHaveBeenCalled()
})
})
@@ -0,0 +1,47 @@
import { Result, RoleNameCollection, UseCaseInterface, Validator } from '@standardnotes/domain-core'
import { DumpRepositoryInterface } from '../../Dump/DumpRepositoryInterface'
import { RevisionRepositoryResolverInterface } from '../../Revision/RevisionRepositoryResolverInterface'
import { CreateRevisionFromDumpDTO } from './CreateRevisionFromDumpDTO'
export class CreateRevisionFromDump implements UseCaseInterface<void> {
constructor(
private dumpRepository: DumpRepositoryInterface,
private revisionRepositoryResolver: RevisionRepositoryResolverInterface,
) {}
async execute(dto: CreateRevisionFromDumpDTO): Promise<Result<void>> {
const filePathValidationResult = Validator.isNotEmptyString(dto.filePath)
if (filePathValidationResult.isFailed()) {
return Result.fail(`Could not create revision from dump: ${filePathValidationResult.getError()}`)
}
const revisionOrError = await this.dumpRepository.getRevisionFromDumpPath(dto.filePath)
if (revisionOrError.isFailed()) {
await this.dumpRepository.removeDump(dto.filePath)
return Result.fail(`Could not create revision from dump: ${revisionOrError.getError()}`)
}
const revision = revisionOrError.getValue()
const roleNamesOrError = RoleNameCollection.create(dto.roleNames)
if (roleNamesOrError.isFailed()) {
await this.dumpRepository.removeDump(dto.filePath)
return Result.fail(`Could not create revision from dump: ${roleNamesOrError.getError()}`)
}
const roleNames = roleNamesOrError.getValue()
const revisionRepository = this.revisionRepositoryResolver.resolve(roleNames)
const successfullyInserted = await revisionRepository.insert(revision)
if (!successfullyInserted) {
await this.dumpRepository.removeDump(dto.filePath)
return Result.fail(`Could not insert revision from dump: ${revision.id.toString()}`)
}
await this.dumpRepository.removeDump(dto.filePath)
return Result.ok()
}
}
@@ -0,0 +1,4 @@
export interface CreateRevisionFromDumpDTO {
filePath: string
roleNames: string[]
}
@@ -5,12 +5,13 @@ import { Logger } from 'winston'
import { TransitionRevisionsFromPrimaryToSecondaryDatabaseForUserDTO } from './TransitionRevisionsFromPrimaryToSecondaryDatabaseForUserDTO' import { TransitionRevisionsFromPrimaryToSecondaryDatabaseForUserDTO } from './TransitionRevisionsFromPrimaryToSecondaryDatabaseForUserDTO'
import { RevisionRepositoryInterface } from '../../../Revision/RevisionRepositoryInterface' import { RevisionRepositoryInterface } from '../../../Revision/RevisionRepositoryInterface'
import { Revision } from '../../../Revision/Revision' import { TransitionRepositoryInterface } from '../../../Transition/TransitionRepositoryInterface'
export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements UseCaseInterface<void> { export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements UseCaseInterface<void> {
constructor( constructor(
private primaryRevisionsRepository: RevisionRepositoryInterface, private primaryRevisionsRepository: RevisionRepositoryInterface,
private secondRevisionsRepository: RevisionRepositoryInterface | null, private secondRevisionsRepository: RevisionRepositoryInterface | null,
private transitionStatusRepository: TransitionRepositoryInterface | null,
private timer: TimerInterface, private timer: TimerInterface,
private logger: Logger, private logger: Logger,
private pageSize: number, private pageSize: number,
@@ -23,82 +24,37 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
return Result.fail('Secondary revision repository is not set') return Result.fail('Secondary revision repository is not set')
} }
if (this.transitionStatusRepository === null) {
return Result.fail('Transition status repository is not set')
}
const userUuidOrError = Uuid.create(dto.userUuid) const userUuidOrError = Uuid.create(dto.userUuid)
if (userUuidOrError.isFailed()) { if (userUuidOrError.isFailed()) {
return Result.fail(userUuidOrError.getError()) return Result.fail(userUuidOrError.getError())
} }
const userUuid = userUuidOrError.getValue() const userUuid = userUuidOrError.getValue()
let newRevisionsInSecondaryCount = 0
let updatedRevisionsInSecondary: string[] = []
let alreadyIdenticalInSecondaryAndPrimary: string[] = []
if (await this.hasAlreadyDataInSecondaryDatabase(userUuid)) {
const { alreadyExistingInSecondaryAndPrimary, newRevisionsInSecondary, updatedInSecondary } =
await this.getNewRevisionsCreatedInSecondaryDatabase(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(
`[${dto.userUuid}] Found ${newRevisionsInSecondary.length} new revisions in secondary database`,
)
}
newRevisionsInSecondaryCount = newRevisionsInSecondary.length
if (updatedInSecondary.length > 0) {
this.logger.info(`[${dto.userUuid}] Found ${updatedInSecondary.length} updated revisions in secondary database`)
}
updatedRevisionsInSecondary = updatedInSecondary
}
const updatedRevisionsInSecondaryCount = updatedRevisionsInSecondary.length
const migrationTimeStart = this.timer.getTimestampInMicroseconds() const migrationTimeStart = this.timer.getTimestampInMicroseconds()
this.logger.info(`[${dto.userUuid}] Migrating revisions`) this.logger.info(`[${dto.userUuid}] Migrating revisions`)
const migrationResult = await this.migrateRevisionsForUser( const migrationResult = await this.migrateRevisionsForUser(userUuid)
userUuid,
updatedRevisionsInSecondary,
alreadyIdenticalInSecondaryAndPrimary,
)
if (migrationResult.isFailed()) { if (migrationResult.isFailed()) {
if (newRevisionsInSecondaryCount === 0 && updatedRevisionsInSecondaryCount === 0) {
const cleanupResult = await this.deleteRevisionsForUser(userUuid, this.secondRevisionsRepository)
if (cleanupResult.isFailed()) {
this.logger.error(
`[${dto.userUuid}] Failed to clean up secondary database revisions: ${cleanupResult.getError()}`,
)
}
}
return Result.fail(migrationResult.getError()) return Result.fail(migrationResult.getError())
} }
const revisionsToSkipInIntegrityCheck = migrationResult.getValue()
this.logger.info(`[${dto.userUuid}] Revisions migrated`)
await this.allowForSecondaryDatabaseToCatchUp() await this.allowForSecondaryDatabaseToCatchUp()
this.logger.info(`[${dto.userUuid}] Checking integrity between primary and secondary database`)
const integrityCheckResult = await this.checkIntegrityBetweenPrimaryAndSecondaryDatabase( const integrityCheckResult = await this.checkIntegrityBetweenPrimaryAndSecondaryDatabase(
userUuid, userUuid,
newRevisionsInSecondaryCount, revisionsToSkipInIntegrityCheck,
updatedRevisionsInSecondary,
alreadyIdenticalInSecondaryAndPrimary,
) )
if (integrityCheckResult.isFailed()) { if (integrityCheckResult.isFailed()) {
if (newRevisionsInSecondaryCount === 0 && updatedRevisionsInSecondaryCount === 0) {
const cleanupResult = await this.deleteRevisionsForUser(userUuid, this.secondRevisionsRepository)
if (cleanupResult.isFailed()) {
this.logger.error(
`[${dto.userUuid}] Failed to clean up secondary database revisions: ${cleanupResult.getError()}`,
)
}
}
return Result.fail(integrityCheckResult.getError()) return Result.fail(integrityCheckResult.getError())
} }
@@ -119,15 +75,23 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
return Result.ok() return Result.ok()
} }
private async migrateRevisionsForUser( private async migrateRevisionsForUser(userUuid: Uuid): Promise<Result<string[]>> {
userUuid: Uuid,
updatedRevisionsInSecondary: string[],
alreadyExistingInSecondaryAndPrimary: string[],
): Promise<Result<void>> {
try { try {
const initialPage = await (this.transitionStatusRepository as TransitionRepositoryInterface).getPagingProgress(
userUuid.value,
)
this.logger.info(`[${userUuid.value}] Migrating from page ${initialPage}`)
const totalRevisionsCountForUser = await this.primaryRevisionsRepository.countByUserUuid(userUuid) const totalRevisionsCountForUser = await this.primaryRevisionsRepository.countByUserUuid(userUuid)
const totalPages = Math.ceil(totalRevisionsCountForUser / this.pageSize) const totalPages = Math.ceil(totalRevisionsCountForUser / this.pageSize)
for (let currentPage = 1; currentPage <= totalPages; currentPage++) { const revisionsToSkipInIntegrityCheck = []
for (let currentPage = initialPage; currentPage <= totalPages; currentPage++) {
await (this.transitionStatusRepository as TransitionRepositoryInterface).setPagingProgress(
userUuid.value,
currentPage,
)
const query = { const query = {
userUuid: userUuid, userUuid: userUuid,
offset: (currentPage - 1) * this.pageSize, offset: (currentPage - 1) * this.pageSize,
@@ -135,41 +99,44 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
} }
const revisions = await this.primaryRevisionsRepository.findByUserUuid(query) const revisions = await this.primaryRevisionsRepository.findByUserUuid(query)
for (const revision of revisions) { for (const revision of revisions) {
try { try {
if ( const revisionInSecondary = await (
updatedRevisionsInSecondary.find((updatedRevisionUuid) => updatedRevisionUuid === revision.id.toString()) this.secondRevisionsRepository as RevisionRepositoryInterface
) { ).findOneByUuid(Uuid.create(revision.id.toString()).getValue(), revision.props.userUuid as Uuid, [])
if (revisionInSecondary !== null) {
if (revisionInSecondary.isIdenticalTo(revision)) {
continue
}
if (revisionInSecondary.props.dates.updatedAt > revision.props.dates.updatedAt) {
this.logger.info(
`[${userUuid.value}] Revision ${revision.id.toString()} is older than revision in secondary database`,
)
revisionsToSkipInIntegrityCheck.push(revision.id.toString())
continue
}
this.logger.info( this.logger.info(
`[${ `[${
userUuid.value userUuid.value
}] Skipping saving revision ${revision.id.toString()} as it was updated in secondary database`, }] Removing revision ${revision.id.toString()} in secondary database as it is not identical to revision in primary database`,
) )
continue await (this.secondRevisionsRepository as RevisionRepositoryInterface).removeOneByUuid(
} Uuid.create(revisionInSecondary.id.toString()).getValue(),
revisionInSecondary.props.userUuid as Uuid,
if (
alreadyExistingInSecondaryAndPrimary.find(
(alreadyExistingRevisionUuid) => alreadyExistingRevisionUuid === revision.id.toString(),
) )
) { await this.allowForSecondaryDatabaseToCatchUp()
this.logger.info(
`[${
userUuid.value
}] Skipping saving revision ${revision.id.toString()} as it is already existing in primary and secondary database`,
)
continue
} }
const didSave = await (this.secondRevisionsRepository as RevisionRepositoryInterface).insert(revision) const didSave = await (this.secondRevisionsRepository as RevisionRepositoryInterface).insert(revision)
if (!didSave) { if (!didSave) {
return Result.fail(`Failed to save revision ${revision.id.toString()} to secondary database`) this.logger.error(`Failed to save revision ${revision.id.toString()} to secondary database`)
} }
} catch (error) { } catch (error) {
return Result.fail( this.logger.error(
`Errored when saving revision ${revision.id.toString()} to secondary database: ${ `Errored when saving revision ${revision.id.toString()} to secondary database: ${
(error as Error).message (error as Error).message
}`, }`,
@@ -178,7 +145,7 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
} }
} }
return Result.ok() return Result.ok(revisionsToSkipInIntegrityCheck)
} catch (error) { } catch (error) {
return Result.fail(`Errored when migrating revisions for user ${userUuid.value}: ${(error as Error).message}`) return Result.fail(`Errored when migrating revisions for user ${userUuid.value}: ${(error as Error).message}`)
} }
@@ -189,6 +156,8 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
revisionRepository: RevisionRepositoryInterface, revisionRepository: RevisionRepositoryInterface,
): Promise<Result<void>> { ): Promise<Result<void>> {
try { try {
this.logger.info(`[${userUuid.value}] Deleting all revisions from primary database`)
await revisionRepository.removeByUserUuid(userUuid) await revisionRepository.removeByUserUuid(userUuid)
return Result.ok() return Result.ok()
@@ -202,115 +171,21 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
await this.timer.sleep(twoSecondsInMilliseconds) await this.timer.sleep(twoSecondsInMilliseconds)
} }
private async hasAlreadyDataInSecondaryDatabase(userUuid: Uuid): Promise<boolean> {
const totalRevisionsCountForUserInSecondary = await (
this.secondRevisionsRepository as RevisionRepositoryInterface
).countByUserUuid(userUuid)
const hasAlreadyDataInSecondaryDatabase = totalRevisionsCountForUserInSecondary > 0
if (hasAlreadyDataInSecondaryDatabase) {
this.logger.info(
`[${userUuid.value}] User has already ${totalRevisionsCountForUserInSecondary} revisions in secondary database`,
)
}
return hasAlreadyDataInSecondaryDatabase
}
private async getNewRevisionsCreatedInSecondaryDatabase(userUuid: Uuid): Promise<{
alreadyExistingInSecondaryAndPrimary: string[]
newRevisionsInSecondary: string[]
updatedInSecondary: string[]
}> {
this.logger.info(`[${userUuid.value}] Checking for new revisions created in secondary database`)
const totalRevisionsCountForUser = await (
this.secondRevisionsRepository as RevisionRepositoryInterface
).countByUserUuid(userUuid)
const totalPages = Math.ceil(totalRevisionsCountForUser / this.pageSize)
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,
}
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 {
alreadyExistingInSecondaryAndPrimary,
newRevisionsInSecondary,
updatedInSecondary,
}
}
private async checkIfRevisionExistsInPrimaryDatabase(
revision: Revision,
): Promise<{ identicalRevisionInPrimary: Revision | null; newerRevisionInSecondary: Revision | null }> {
const revisionInPrimary = await this.primaryRevisionsRepository.findOneByUuid(
Uuid.create(revision.id.toString()).getValue(),
revision.props.userUuid as Uuid,
[],
)
if (revisionInPrimary === null) {
return {
identicalRevisionInPrimary: null,
newerRevisionInSecondary: null,
}
}
if (!revision.isIdenticalTo(revisionInPrimary)) {
this.logger.error(
`[${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 {
identicalRevisionInPrimary: null,
newerRevisionInSecondary:
revision.props.dates.updatedAt > revisionInPrimary.props.dates.updatedAt ? revision : null,
}
}
return {
identicalRevisionInPrimary: revisionInPrimary,
newerRevisionInSecondary: null,
}
}
private async checkIntegrityBetweenPrimaryAndSecondaryDatabase( private async checkIntegrityBetweenPrimaryAndSecondaryDatabase(
userUuid: Uuid, userUuid: Uuid,
newRevisionsInSecondaryCount: number, revisionsToSkipInIntegrityCheck: string[],
updatedRevisionsInSecondary: string[],
alreadyExistingInSecondaryAndPrimary: string[],
): Promise<Result<boolean>> { ): Promise<Result<boolean>> {
try { try {
const totalRevisionsCountForUserInPrimary = await this.primaryRevisionsRepository.countByUserUuid(userUuid) const totalRevisionsCountForUserInPrimary = await this.primaryRevisionsRepository.countByUserUuid(userUuid)
const totalRevisionsCountForUserInSecondary = await (
this.secondRevisionsRepository as RevisionRepositoryInterface
).countByUserUuid(userUuid)
if (totalRevisionsCountForUserInPrimary > totalRevisionsCountForUserInSecondary) {
return Result.fail(
`Total revisions count for user ${userUuid.value} in primary database (${totalRevisionsCountForUserInPrimary}) does not match total revisions count in secondary database (${totalRevisionsCountForUserInSecondary})`,
)
}
const totalPages = Math.ceil(totalRevisionsCountForUserInPrimary / this.pageSize) const totalPages = Math.ceil(totalRevisionsCountForUserInPrimary / this.pageSize)
for (let currentPage = 1; currentPage <= totalPages; currentPage++) { for (let currentPage = 1; currentPage <= totalPages; currentPage++) {
@@ -337,22 +212,7 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
return Result.fail(`Revision ${revision.id.toString()} not found in secondary database`) return Result.fail(`Revision ${revision.id.toString()} not found in secondary database`)
} }
if ( if (revisionsToSkipInIntegrityCheck.includes(revision.id.toString())) {
updatedRevisionsInSecondary.find((updatedRevisionUuid) => updatedRevisionUuid === revision.id.toString())
) {
this.logger.info(
`[${
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 continue
} }
@@ -366,19 +226,6 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
} }
} }
const totalRevisionsCountForUserInSecondary = await (
this.secondRevisionsRepository as RevisionRepositoryInterface
).countByUserUuid(userUuid)
if (
totalRevisionsCountForUserInPrimary + newRevisionsInSecondaryCount !==
totalRevisionsCountForUserInSecondary
) {
return Result.fail(
`Total revisions count for user ${userUuid.value} in primary database (${totalRevisionsCountForUserInPrimary} + ${newRevisionsInSecondaryCount}) does not match total revisions count in secondary database (${totalRevisionsCountForUserInSecondary})`,
)
}
return Result.ok() return Result.ok()
} catch (error) { } catch (error) {
return Result.fail( return Result.fail(
@@ -1,4 +1,4 @@
import { MapperInterface } from '@standardnotes/domain-core' import { MapperInterface, Result } from '@standardnotes/domain-core'
import { promises } from 'fs' import { promises } from 'fs'
import { DumpRepositoryInterface } from '../../Domain/Dump/DumpRepositoryInterface' import { DumpRepositoryInterface } from '../../Domain/Dump/DumpRepositoryInterface'
@@ -7,12 +7,16 @@ import { Revision } from '../../Domain/Revision/Revision'
export class FSDumpRepository implements DumpRepositoryInterface { export class FSDumpRepository implements DumpRepositoryInterface {
constructor(private revisionStringItemMapper: MapperInterface<Revision, string>) {} constructor(private revisionStringItemMapper: MapperInterface<Revision, string>) {}
async getRevisionFromDumpPath(path: string): Promise<Revision | null> { async getRevisionFromDumpPath(path: string): Promise<Result<Revision>> {
const contents = (await promises.readFile(path)).toString() try {
const contents = (await promises.readFile(path)).toString()
const revision = this.revisionStringItemMapper.toDomain(contents) const revision = this.revisionStringItemMapper.toDomain(contents)
return revision return Result.ok(revision)
} catch (error) {
return Result.fail(`Failed to read dump file: ${(error as Error).message}`)
}
} }
async removeDump(path: string): Promise<void> { async removeDump(path: string): Promise<void> {
@@ -0,0 +1,23 @@
import * as IORedis from 'ioredis'
import { TransitionRepositoryInterface } from '../../Domain/Transition/TransitionRepositoryInterface'
export class RedisTransitionRepository implements TransitionRepositoryInterface {
private readonly PREFIX = 'transition-revisions-paging-progress'
constructor(private redisClient: IORedis.Redis) {}
async getPagingProgress(userUuid: string): Promise<number> {
const progress = await this.redisClient.get(`${this.PREFIX}:${userUuid}`)
if (progress === null) {
return 1
}
return parseInt(progress)
}
async setPagingProgress(userUuid: string, progress: number): Promise<void> {
await this.redisClient.setex(`${this.PREFIX}:${userUuid}`, 172_800, progress.toString())
}
}
@@ -1,6 +1,5 @@
import { DeleteObjectCommand, GetObjectCommand, S3Client } from '@aws-sdk/client-s3' import { DeleteObjectCommand, GetObjectCommand, S3Client } from '@aws-sdk/client-s3'
import { MapperInterface } from '@standardnotes/domain-core' import { MapperInterface, Result } from '@standardnotes/domain-core'
import { Logger } from 'winston'
import { DumpRepositoryInterface } from '../../Domain/Dump/DumpRepositoryInterface' import { DumpRepositoryInterface } from '../../Domain/Dump/DumpRepositoryInterface'
import { Revision } from '../../Domain/Revision/Revision' import { Revision } from '../../Domain/Revision/Revision'
@@ -10,26 +9,27 @@ export class S3DumpRepository implements DumpRepositoryInterface {
private dumpBucketName: string, private dumpBucketName: string,
private s3Client: S3Client, private s3Client: S3Client,
private revisionStringItemMapper: MapperInterface<Revision, string>, private revisionStringItemMapper: MapperInterface<Revision, string>,
private logger: Logger,
) {} ) {}
async getRevisionFromDumpPath(path: string): Promise<Revision | null> { async getRevisionFromDumpPath(path: string): Promise<Result<Revision>> {
const s3Object = await this.s3Client.send( try {
new GetObjectCommand({ const s3Object = await this.s3Client.send(
Bucket: this.dumpBucketName, new GetObjectCommand({
Key: path, Bucket: this.dumpBucketName,
}), Key: path,
) }),
)
if (s3Object.Body === undefined) { if (s3Object.Body === undefined) {
this.logger.warn(`Could not find revision dump at path: ${path}`) return Result.fail(`Could not find revision dump at path: ${path}`)
}
return null const revision = this.revisionStringItemMapper.toDomain(await s3Object.Body.transformToString())
return Result.ok(revision)
} catch (error) {
return Result.fail(`Failed to read dump file: ${(error as Error).message}`)
} }
const revision = this.revisionStringItemMapper.toDomain(await s3Object.Body.transformToString())
return revision
} }
async removeDump(path: string): Promise<void> { async removeDump(path: string): Promise<void> {
+26
View File
@@ -3,6 +3,32 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.21.1](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.21.0...@standardnotes/scheduler-server@1.21.1) (2023-09-27)
**Note:** Version bump only for package @standardnotes/scheduler-server
# [1.21.0](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.20.56...@standardnotes/scheduler-server@1.21.0) (2023-09-26)
### Features
* refactor handling revision creation from dump ([#854](https://github.com/standardnotes/server/issues/854)) ([ca6dbc0](https://github.com/standardnotes/server/commit/ca6dbc00537bb20f508f9310b1a838421f53a643))
## [1.20.56](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.20.55...@standardnotes/scheduler-server@1.20.56) (2023-09-25)
**Note:** Version bump only for package @standardnotes/scheduler-server
## [1.20.55](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.20.54...@standardnotes/scheduler-server@1.20.55) (2023-09-25)
**Note:** Version bump only for package @standardnotes/scheduler-server
## [1.20.54](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.20.53...@standardnotes/scheduler-server@1.20.54) (2023-09-25)
**Note:** Version bump only for package @standardnotes/scheduler-server
## [1.20.53](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.20.52...@standardnotes/scheduler-server@1.20.53) (2023-09-25)
**Note:** Version bump only for package @standardnotes/scheduler-server
## [1.20.52](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.20.51...@standardnotes/scheduler-server@1.20.52) (2023-09-21) ## [1.20.52](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.20.51...@standardnotes/scheduler-server@1.20.52) (2023-09-21)
**Note:** Version bump only for package @standardnotes/scheduler-server **Note:** Version bump only for package @standardnotes/scheduler-server
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@standardnotes/scheduler-server", "name": "@standardnotes/scheduler-server",
"version": "1.20.52", "version": "1.21.1",
"engines": { "engines": {
"node": ">=18.0.0 <21.0.0" "node": ">=18.0.0 <21.0.0"
}, },
@@ -16,7 +16,7 @@
"lint": "eslint . --ext .ts", "lint": "eslint . --ext .ts",
"lint:fix": "eslint . --ext .ts --fix", "lint:fix": "eslint . --ext .ts --fix",
"pretest": "yarn lint && yarn build", "pretest": "yarn lint && yarn build",
"test": "jest --coverage --config=./jest.config.js --maxWorkers=50%", "test": "jest --coverage --no-cache --config=./jest.config.js --maxWorkers=50%",
"worker": "yarn node dist/bin/worker.js", "worker": "yarn node dist/bin/worker.js",
"verify:jobs": "yarn node dist/bin/verify.js", "verify:jobs": "yarn node dist/bin/verify.js",
"setup:env": "cp .env.sample .env", "setup:env": "cp .env.sample .env",
+6
View File
@@ -3,6 +3,12 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.14.0](https://github.com/standardnotes/server/compare/@standardnotes/security@1.13.1...@standardnotes/security@1.14.0) (2023-09-26)
### Features
* refactor handling revision creation from dump ([#854](https://github.com/standardnotes/server/issues/854)) ([ca6dbc0](https://github.com/standardnotes/server/commit/ca6dbc00537bb20f508f9310b1a838421f53a643))
## [1.13.1](https://github.com/standardnotes/server/compare/@standardnotes/security@1.13.0...@standardnotes/security@1.13.1) (2023-09-12) ## [1.13.1](https://github.com/standardnotes/server/compare/@standardnotes/security@1.13.0...@standardnotes/security@1.13.1) (2023-09-12)
### Bug Fixes ### Bug Fixes
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@standardnotes/security", "name": "@standardnotes/security",
"version": "1.13.1", "version": "1.14.0",
"engines": { "engines": {
"node": ">=18.0.0 <21.0.0" "node": ">=18.0.0 <21.0.0"
}, },
@@ -22,7 +22,7 @@
"start": "tsc -p tsconfig.json --watch", "start": "tsc -p tsconfig.json --watch",
"build": "tsc --build", "build": "tsc --build",
"lint": "eslint . --ext .ts", "lint": "eslint . --ext .ts",
"test": "jest spec --coverage" "test": "jest --coverage --no-cache"
}, },
"dependencies": { "dependencies": {
"jsonwebtoken": "^9.0.0", "jsonwebtoken": "^9.0.0",
+16
View File
@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.21.41](https://github.com/standardnotes/server/compare/@standardnotes/settings@1.21.40...@standardnotes/settings@1.21.41) (2023-09-27)
**Note:** Version bump only for package @standardnotes/settings
## [1.21.40](https://github.com/standardnotes/server/compare/@standardnotes/settings@1.21.39...@standardnotes/settings@1.21.40) (2023-09-26)
**Note:** Version bump only for package @standardnotes/settings
## [1.21.39](https://github.com/standardnotes/server/compare/@standardnotes/settings@1.21.38...@standardnotes/settings@1.21.39) (2023-09-25)
**Note:** Version bump only for package @standardnotes/settings
## [1.21.38](https://github.com/standardnotes/server/compare/@standardnotes/settings@1.21.37...@standardnotes/settings@1.21.38) (2023-09-25)
**Note:** Version bump only for package @standardnotes/settings
## [1.21.37](https://github.com/standardnotes/server/compare/@standardnotes/settings@1.21.36...@standardnotes/settings@1.21.37) (2023-09-21) ## [1.21.37](https://github.com/standardnotes/server/compare/@standardnotes/settings@1.21.36...@standardnotes/settings@1.21.37) (2023-09-21)
**Note:** Version bump only for package @standardnotes/settings **Note:** Version bump only for package @standardnotes/settings
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@standardnotes/settings", "name": "@standardnotes/settings",
"version": "1.21.37", "version": "1.21.41",
"engines": { "engines": {
"node": ">=18.0.0 <21.0.0" "node": ">=18.0.0 <21.0.0"
}, },
+74
View File
@@ -3,6 +3,80 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.109.1](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.109.0...@standardnotes/syncing-server@1.109.1) (2023-09-27)
### Bug Fixes
* removing items in a vault and notifying about designated survivor ([#855](https://github.com/standardnotes/syncing-server-js/issues/855)) ([1d06ffe](https://github.com/standardnotes/syncing-server-js/commit/1d06ffe9d51722ada7baa040e1d5ed351fc28f39))
# [1.109.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.108.2...@standardnotes/syncing-server@1.109.0) (2023-09-26)
### Features
* refactor handling revision creation from dump ([#854](https://github.com/standardnotes/syncing-server-js/issues/854)) ([ca6dbc0](https://github.com/standardnotes/syncing-server-js/commit/ca6dbc00537bb20f508f9310b1a838421f53a643))
## [1.108.2](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.108.1...@standardnotes/syncing-server@1.108.2) (2023-09-25)
**Note:** Version bump only for package @standardnotes/syncing-server
## [1.108.1](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.108.0...@standardnotes/syncing-server@1.108.1) (2023-09-25)
### Bug Fixes
* refactor the structure of notifications ([#853](https://github.com/standardnotes/syncing-server-js/issues/853)) ([cebab59](https://github.com/standardnotes/syncing-server-js/commit/cebab59a026c6868886e0945787a8ddb0442fbc3))
* **syncing-server:** another spec ([3d5e747](https://github.com/standardnotes/syncing-server-js/commit/3d5e7475901c5eb7741f461a35febdb996bcfd1d))
* **syncing-server:** specs ([9446774](https://github.com/standardnotes/syncing-server-js/commit/94467747acca83b954129702111f903c3d1ceab8))
# [1.108.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.107.0...@standardnotes/syncing-server@1.108.0) (2023-09-25)
### Features
* remove shared vault files upon shared vault removal ([#852](https://github.com/standardnotes/syncing-server-js/issues/852)) ([7b1eec2](https://github.com/standardnotes/syncing-server-js/commit/7b1eec21e54330bebbeebb80cec3ba4284112aab))
# [1.107.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.106.0...@standardnotes/syncing-server@1.107.0) (2023-09-25)
### Features
* **syncing-server:** transfer shared vault items ([#851](https://github.com/standardnotes/syncing-server-js/issues/851)) ([a8f03e1](https://github.com/standardnotes/syncing-server-js/commit/a8f03e157be3d277e60d2756dd25c953775b1ba4))
# [1.106.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.105.1...@standardnotes/syncing-server@1.106.0) (2023-09-25)
### Features
* add storing paging progress in redis ([9759814](https://github.com/standardnotes/syncing-server-js/commit/9759814f637b8ae25b325e35bc7f5159747980b6))
## [1.105.1](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.105.0...@standardnotes/syncing-server@1.105.1) (2023-09-25)
### Bug Fixes
* add paging progress log ([8cb33dc](https://github.com/standardnotes/syncing-server-js/commit/8cb33dc906391ee8b1ebd333937045c328e4fc06))
* remember paging progress on transitioning ([1d73e4f](https://github.com/standardnotes/syncing-server-js/commit/1d73e4f0720d41029af4d4b2b7a10d101add6c82))
# [1.105.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.104.0...@standardnotes/syncing-server@1.105.0) (2023-09-22)
### Bug Fixes
* add more logs to transition process ([0562b0a](https://github.com/standardnotes/syncing-server-js/commit/0562b0a621eb878026fbdc0346b6170e815b64bf))
* remove excessive logs ([15ed1fd](https://github.com/standardnotes/syncing-server-js/commit/15ed1fd789aba306cbec6a23e88d5c1f837dabc0))
### Features
* **syncing-server:** transfer shared vault ownership to designated survivor upon account deletion ([#845](https://github.com/standardnotes/syncing-server-js/issues/845)) ([0a1080c](https://github.com/standardnotes/syncing-server-js/commit/0a1080ce2a0fb021309a960de2c40193acab46eb))
# [1.104.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.103.1...@standardnotes/syncing-server@1.104.0) (2023-09-22)
### Features
* **syncing-server:** add designated survivors in fetching shared vaults response ([#844](https://github.com/standardnotes/syncing-server-js/issues/844)) ([bcd95cd](https://github.com/standardnotes/syncing-server-js/commit/bcd95cdbe9054d4ca39d5dc0486b6a0c0b6f52da))
## [1.103.1](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.103.0...@standardnotes/syncing-server@1.103.1) (2023-09-22)
### Bug Fixes
* disable cleaning secondary database on transition ([4f4443a](https://github.com/standardnotes/syncing-server-js/commit/4f4443a882f69c2e76ef831ef36347c9c54f31cd))
* integrity check during transition ([921c30f](https://github.com/standardnotes/syncing-server-js/commit/921c30f6415ef122a7d1af83ffa3f6840a42edba))
* processing migration optimization ([22540ee](https://github.com/standardnotes/syncing-server-js/commit/22540ee83436b986949127a6923285a702162706))
# [1.103.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.102.2...@standardnotes/syncing-server@1.103.0) (2023-09-22) # [1.103.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.102.2...@standardnotes/syncing-server@1.103.0) (2023-09-22)
### Features ### Features
@@ -0,0 +1,11 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class RemoveNotifications1695643525793 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('DELETE FROM `notifications`')
}
public async down(): Promise<void> {
return
}
}
@@ -0,0 +1,11 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class RemoveNotifications1695643525793 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('DELETE FROM `notifications`')
}
public async down(): Promise<void> {
return
}
}
@@ -0,0 +1,11 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class RemoveNotifications1695643525793 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('DELETE FROM `notifications`')
}
public async down(): Promise<void> {
return
}
}
+4 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@standardnotes/syncing-server", "name": "@standardnotes/syncing-server",
"version": "1.103.0", "version": "1.109.1",
"engines": { "engines": {
"node": ">=18.0.0 <21.0.0" "node": ">=18.0.0 <21.0.0"
}, },
@@ -20,7 +20,7 @@
"lint": "eslint . --ext .ts", "lint": "eslint . --ext .ts",
"lint:fix": "eslint . --ext .ts --fix", "lint:fix": "eslint . --ext .ts --fix",
"pretest": "yarn lint && yarn build", "pretest": "yarn lint && yarn build",
"test": "jest --coverage --config=./jest.config.js --maxWorkers=50%", "test": "jest --coverage --no-cache --config=./jest.config.js --maxWorkers=50%",
"start": "yarn node dist/bin/server.js", "start": "yarn node dist/bin/server.js",
"worker": "yarn node dist/bin/worker.js", "worker": "yarn node dist/bin/worker.js",
"content-size": "yarn node dist/bin/content.js", "content-size": "yarn node dist/bin/content.js",
@@ -47,6 +47,7 @@
"helmet": "^7.0.0", "helmet": "^7.0.0",
"inversify": "^6.0.1", "inversify": "^6.0.1",
"inversify-express-utils": "^6.4.3", "inversify-express-utils": "^6.4.3",
"ioredis": "^5.3.2",
"jsonwebtoken": "^9.0.0", "jsonwebtoken": "^9.0.0",
"mongodb": "^6.0.0", "mongodb": "^6.0.0",
"mysql2": "^3.0.1", "mysql2": "^3.0.1",
@@ -63,6 +64,7 @@
"@types/cors": "^2.8.9", "@types/cors": "^2.8.9",
"@types/dotenv": "^8.2.0", "@types/dotenv": "^8.2.0",
"@types/express": "^4.17.14", "@types/express": "^4.17.14",
"@types/ioredis": "^5.0.0",
"@types/jest": "^29.5.1", "@types/jest": "^29.5.1",
"@types/jsonwebtoken": "^9.0.1", "@types/jsonwebtoken": "^9.0.1",
"@types/node": "^20.5.7", "@types/node": "^20.5.7",
@@ -1,4 +1,5 @@
import * as winston from 'winston' import * as winston from 'winston'
import Redis from 'ioredis'
import { Container, interfaces } from 'inversify' import { Container, interfaces } from 'inversify'
import { Env } from './Env' import { Env } from './Env'
@@ -102,7 +103,7 @@ import { TypeORMSharedVaultInviteRepository } from '../Infra/TypeORM/TypeORMShar
import { UpdateSharedVaultInvite } from '../Domain/UseCase/SharedVaults/UpdateSharedVaultInvite/UpdateSharedVaultInvite' import { UpdateSharedVaultInvite } from '../Domain/UseCase/SharedVaults/UpdateSharedVaultInvite/UpdateSharedVaultInvite'
import { AcceptInviteToSharedVault } from '../Domain/UseCase/SharedVaults/AcceptInviteToSharedVault/AcceptInviteToSharedVault' import { AcceptInviteToSharedVault } from '../Domain/UseCase/SharedVaults/AcceptInviteToSharedVault/AcceptInviteToSharedVault'
import { AddUserToSharedVault } from '../Domain/UseCase/SharedVaults/AddUserToSharedVault/AddUserToSharedVault' import { AddUserToSharedVault } from '../Domain/UseCase/SharedVaults/AddUserToSharedVault/AddUserToSharedVault'
import { DeclineInviteToSharedVault } from '../Domain/UseCase/SharedVaults/DeclineInviteToSharedVault/DeclineInviteToSharedVault' import { CancelInviteToSharedVault } from '../Domain/UseCase/SharedVaults/CancelInviteToSharedVault/CancelInviteToSharedVault'
import { DeleteSharedVaultInvitesToUser } from '../Domain/UseCase/SharedVaults/DeleteSharedVaultInvitesToUser/DeleteSharedVaultInvitesToUser' import { DeleteSharedVaultInvitesToUser } from '../Domain/UseCase/SharedVaults/DeleteSharedVaultInvitesToUser/DeleteSharedVaultInvitesToUser'
import { DeleteSharedVaultInvitesSentByUser } from '../Domain/UseCase/SharedVaults/DeleteSharedVaultInvitesSentByUser/DeleteSharedVaultInvitesSentByUser' import { DeleteSharedVaultInvitesSentByUser } from '../Domain/UseCase/SharedVaults/DeleteSharedVaultInvitesSentByUser/DeleteSharedVaultInvitesSentByUser'
import { GetSharedVaultInvitesSentByUser } from '../Domain/UseCase/SharedVaults/GetSharedVaultInvitesSentByUser/GetSharedVaultInvitesSentByUser' import { GetSharedVaultInvitesSentByUser } from '../Domain/UseCase/SharedVaults/GetSharedVaultInvitesSentByUser/GetSharedVaultInvitesSentByUser'
@@ -170,6 +171,11 @@ import { RemoveItemsFromSharedVault } from '../Domain/UseCase/SharedVaults/Remov
import { SharedVaultRemovedEventHandler } from '../Domain/Handler/SharedVaultRemovedEventHandler' import { SharedVaultRemovedEventHandler } from '../Domain/Handler/SharedVaultRemovedEventHandler'
import { DesignateSurvivor } from '../Domain/UseCase/SharedVaults/DesignateSurvivor/DesignateSurvivor' import { DesignateSurvivor } from '../Domain/UseCase/SharedVaults/DesignateSurvivor/DesignateSurvivor'
import { RemoveUserFromSharedVaults } from '../Domain/UseCase/SharedVaults/RemoveUserFromSharedVaults/RemoveUserFromSharedVaults' import { RemoveUserFromSharedVaults } from '../Domain/UseCase/SharedVaults/RemoveUserFromSharedVaults/RemoveUserFromSharedVaults'
import { TransferSharedVault } from '../Domain/UseCase/SharedVaults/TransferSharedVault/TransferSharedVault'
import { TransitionRepositoryInterface } from '../Domain/Transition/TransitionRepositoryInterface'
import { RedisTransitionRepository } from '../Infra/Redis/RedisTransitionRepository'
import { TransferSharedVaultItems } from '../Domain/UseCase/SharedVaults/TransferSharedVaultItems/TransferSharedVaultItems'
import { DumpItem } from '../Domain/UseCase/Syncing/DumpItem/DumpItem'
export class ContainerConfigLoader { export class ContainerConfigLoader {
private readonly DEFAULT_CONTENT_SIZE_TRANSFER_LIMIT = 10_000_000 private readonly DEFAULT_CONTENT_SIZE_TRANSFER_LIMIT = 10_000_000
@@ -227,6 +233,23 @@ export class ContainerConfigLoader {
const isConfiguredForSelfHosting = env.get('MODE', true) === 'self-hosted' const isConfiguredForSelfHosting = env.get('MODE', true) === 'self-hosted'
const isConfiguredForHomeServerOrSelfHosting = isConfiguredForHomeServer || isConfiguredForSelfHosting const isConfiguredForHomeServerOrSelfHosting = isConfiguredForHomeServer || isConfiguredForSelfHosting
const isSecondaryDatabaseEnabled = env.get('SECONDARY_DB_ENABLED', true) === 'true' const isSecondaryDatabaseEnabled = env.get('SECONDARY_DB_ENABLED', true) === 'true'
const isConfiguredForInMemoryCache = env.get('CACHE_TYPE', true) === 'memory'
if (!isConfiguredForInMemoryCache) {
const redisUrl = env.get('REDIS_URL')
const isRedisInClusterMode = redisUrl.indexOf(',') > 0
let redis
if (isRedisInClusterMode) {
redis = new Redis.Cluster(redisUrl.split(','))
} else {
redis = new Redis(redisUrl)
}
container.bind(TYPES.Sync_Redis).toConstantValue(redis)
container
.bind<TransitionRepositoryInterface>(TYPES.Sync_TransitionStatusRepository)
.toConstantValue(new RedisTransitionRepository(container.get<Redis>(TYPES.Sync_Redis)))
}
container.bind<Env>(TYPES.Sync_Env).toConstantValue(env) container.bind<Env>(TYPES.Sync_Env).toConstantValue(env)
@@ -568,6 +591,24 @@ export class ContainerConfigLoader {
]), ]),
) )
container
.bind<ItemBackupServiceInterface>(TYPES.Sync_ItemBackupService)
.toConstantValue(
env.get('S3_AWS_REGION', true)
? new S3ItemBackupService(
container.get(TYPES.Sync_S3_BACKUP_BUCKET_NAME),
container.get(TYPES.Sync_ItemBackupMapper),
container.get(TYPES.Sync_ItemHttpMapper),
container.get(TYPES.Sync_Logger),
container.get(TYPES.Sync_S3),
)
: new FSItemBackupService(
container.get(TYPES.Sync_FILE_UPLOAD_PATH),
container.get(TYPES.Sync_ItemBackupMapper),
container.get(TYPES.Sync_Logger),
),
)
// use cases // use cases
container container
.bind<GetItems>(TYPES.Sync_GetItems) .bind<GetItems>(TYPES.Sync_GetItems)
@@ -727,9 +768,9 @@ export class ContainerConfigLoader {
), ),
) )
container container
.bind<DeclineInviteToSharedVault>(TYPES.Sync_DeclineInviteToSharedVault) .bind<CancelInviteToSharedVault>(TYPES.Sync_DeclineInviteToSharedVault)
.toConstantValue( .toConstantValue(
new DeclineInviteToSharedVault( new CancelInviteToSharedVault(
container.get<SharedVaultInviteRepositoryInterface>(TYPES.Sync_SharedVaultInviteRepository), container.get<SharedVaultInviteRepositoryInterface>(TYPES.Sync_SharedVaultInviteRepository),
container.get<AddNotificationForUser>(TYPES.Sync_AddNotificationForUser), container.get<AddNotificationForUser>(TYPES.Sync_AddNotificationForUser),
), ),
@@ -782,27 +823,6 @@ export class ContainerConfigLoader {
container.get(TYPES.Sync_Timer), container.get(TYPES.Sync_Timer),
), ),
) )
container
.bind<DeleteSharedVault>(TYPES.Sync_DeleteSharedVault)
.toConstantValue(
new DeleteSharedVault(
container.get<SharedVaultRepositoryInterface>(TYPES.Sync_SharedVaultRepository),
container.get<SharedVaultUserRepositoryInterface>(TYPES.Sync_SharedVaultUserRepository),
container.get<SharedVaultInviteRepositoryInterface>(TYPES.Sync_SharedVaultInviteRepository),
container.get<RemoveUserFromSharedVault>(TYPES.Sync_RemoveSharedVaultUser),
container.get<DeclineInviteToSharedVault>(TYPES.Sync_DeclineInviteToSharedVault),
container.get<DomainEventFactoryInterface>(TYPES.Sync_DomainEventFactory),
container.get<DomainEventPublisherInterface>(TYPES.Sync_DomainEventPublisher),
),
)
container
.bind<DeleteSharedVaults>(TYPES.Sync_DeleteSharedVaults)
.toConstantValue(
new DeleteSharedVaults(
container.get<SharedVaultRepositoryInterface>(TYPES.Sync_SharedVaultRepository),
container.get<DeleteSharedVault>(TYPES.Sync_DeleteSharedVault),
),
)
container container
.bind<CreateSharedVaultFileValetToken>(TYPES.Sync_CreateSharedVaultFileValetToken) .bind<CreateSharedVaultFileValetToken>(TYPES.Sync_CreateSharedVaultFileValetToken)
.toConstantValue( .toConstantValue(
@@ -853,6 +873,9 @@ export class ContainerConfigLoader {
new TransitionItemsFromPrimaryToSecondaryDatabaseForUser( new TransitionItemsFromPrimaryToSecondaryDatabaseForUser(
container.get<ItemRepositoryInterface>(TYPES.Sync_SQLItemRepository), container.get<ItemRepositoryInterface>(TYPES.Sync_SQLItemRepository),
isSecondaryDatabaseEnabled ? container.get<ItemRepositoryInterface>(TYPES.Sync_MongoDBItemRepository) : null, isSecondaryDatabaseEnabled ? container.get<ItemRepositoryInterface>(TYPES.Sync_MongoDBItemRepository) : null,
isConfiguredForInMemoryCache
? null
: container.get<TransitionRepositoryInterface>(TYPES.Sync_TransitionStatusRepository),
container.get<TimerInterface>(TYPES.Sync_Timer), container.get<TimerInterface>(TYPES.Sync_Timer),
container.get<Logger>(TYPES.Sync_Logger), container.get<Logger>(TYPES.Sync_Logger),
env.get('MIGRATION_BATCH_SIZE', true) ? +env.get('MIGRATION_BATCH_SIZE', true) : 100, env.get('MIGRATION_BATCH_SIZE', true) ? +env.get('MIGRATION_BATCH_SIZE', true) : 100,
@@ -871,10 +894,12 @@ export class ContainerConfigLoader {
.bind<DesignateSurvivor>(TYPES.Sync_DesignateSurvivor) .bind<DesignateSurvivor>(TYPES.Sync_DesignateSurvivor)
.toConstantValue( .toConstantValue(
new DesignateSurvivor( new DesignateSurvivor(
container.get<SharedVaultRepositoryInterface>(TYPES.Sync_SharedVaultRepository),
container.get<SharedVaultUserRepositoryInterface>(TYPES.Sync_SharedVaultUserRepository), container.get<SharedVaultUserRepositoryInterface>(TYPES.Sync_SharedVaultUserRepository),
container.get<TimerInterface>(TYPES.Sync_Timer), container.get<TimerInterface>(TYPES.Sync_Timer),
container.get<DomainEventFactoryInterface>(TYPES.Sync_DomainEventFactory), container.get<DomainEventFactoryInterface>(TYPES.Sync_DomainEventFactory),
container.get<DomainEventPublisherInterface>(TYPES.Sync_DomainEventPublisher), container.get<DomainEventPublisherInterface>(TYPES.Sync_DomainEventPublisher),
container.get<AddNotificationForUser>(TYPES.Sync_AddNotificationForUser),
), ),
) )
container container
@@ -886,6 +911,57 @@ export class ContainerConfigLoader {
container.get<Logger>(TYPES.Sync_Logger), container.get<Logger>(TYPES.Sync_Logger),
), ),
) )
container
.bind<TransferSharedVaultItems>(TYPES.Sync_TransferSharedVaultItems)
.toConstantValue(
new TransferSharedVaultItems(
isSecondaryDatabaseEnabled
? container.get<ItemRepositoryInterface>(TYPES.Sync_MongoDBItemRepository)
: container.get<ItemRepositoryInterface>(TYPES.Sync_SQLItemRepository),
),
)
container
.bind<TransferSharedVault>(TYPES.Sync_TransferSharedVault)
.toConstantValue(
new TransferSharedVault(
container.get<SharedVaultRepositoryInterface>(TYPES.Sync_SharedVaultRepository),
container.get<SharedVaultUserRepositoryInterface>(TYPES.Sync_SharedVaultUserRepository),
container.get<TransferSharedVaultItems>(TYPES.Sync_TransferSharedVaultItems),
container.get<TimerInterface>(TYPES.Sync_Timer),
),
)
container
.bind<DeleteSharedVault>(TYPES.Sync_DeleteSharedVault)
.toConstantValue(
new DeleteSharedVault(
container.get<SharedVaultRepositoryInterface>(TYPES.Sync_SharedVaultRepository),
container.get<SharedVaultUserRepositoryInterface>(TYPES.Sync_SharedVaultUserRepository),
container.get<SharedVaultInviteRepositoryInterface>(TYPES.Sync_SharedVaultInviteRepository),
container.get<RemoveUserFromSharedVault>(TYPES.Sync_RemoveSharedVaultUser),
container.get<CancelInviteToSharedVault>(TYPES.Sync_DeclineInviteToSharedVault),
container.get<DomainEventFactoryInterface>(TYPES.Sync_DomainEventFactory),
container.get<DomainEventPublisherInterface>(TYPES.Sync_DomainEventPublisher),
container.get<TransferSharedVault>(TYPES.Sync_TransferSharedVault),
),
)
container
.bind<DeleteSharedVaults>(TYPES.Sync_DeleteSharedVaults)
.toConstantValue(
new DeleteSharedVaults(
container.get<SharedVaultRepositoryInterface>(TYPES.Sync_SharedVaultRepository),
container.get<DeleteSharedVault>(TYPES.Sync_DeleteSharedVault),
),
)
container
.bind<DumpItem>(TYPES.Sync_DumpItem)
.toConstantValue(
new DumpItem(
container.get<ItemRepositoryResolverInterface>(TYPES.Sync_ItemRepositoryResolver),
container.get<ItemBackupServiceInterface>(TYPES.Sync_ItemBackupService),
container.get<DomainEventFactoryInterface>(TYPES.Sync_DomainEventFactory),
container.get<DomainEventPublisherInterface>(TYPES.Sync_DomainEventPublisher),
),
)
// Services // Services
container container
@@ -913,24 +989,6 @@ export class ContainerConfigLoader {
) )
}) })
container
.bind<ItemBackupServiceInterface>(TYPES.Sync_ItemBackupService)
.toConstantValue(
env.get('S3_AWS_REGION', true)
? new S3ItemBackupService(
container.get(TYPES.Sync_S3_BACKUP_BUCKET_NAME),
container.get(TYPES.Sync_ItemBackupMapper),
container.get(TYPES.Sync_ItemHttpMapper),
container.get(TYPES.Sync_Logger),
container.get(TYPES.Sync_S3),
)
: new FSItemBackupService(
container.get(TYPES.Sync_FILE_UPLOAD_PATH),
container.get(TYPES.Sync_ItemBackupMapper),
container.get(TYPES.Sync_Logger),
),
)
// Handlers // Handlers
container container
.bind<DuplicateItemSyncedEventHandler>(TYPES.Sync_DuplicateItemSyncedEventHandler) .bind<DuplicateItemSyncedEventHandler>(TYPES.Sync_DuplicateItemSyncedEventHandler)
@@ -956,10 +1014,8 @@ export class ContainerConfigLoader {
.bind<ItemRevisionCreationRequestedEventHandler>(TYPES.Sync_ItemRevisionCreationRequestedEventHandler) .bind<ItemRevisionCreationRequestedEventHandler>(TYPES.Sync_ItemRevisionCreationRequestedEventHandler)
.toConstantValue( .toConstantValue(
new ItemRevisionCreationRequestedEventHandler( new ItemRevisionCreationRequestedEventHandler(
container.get<ItemRepositoryResolverInterface>(TYPES.Sync_ItemRepositoryResolver), container.get<DumpItem>(TYPES.Sync_DumpItem),
container.get<ItemBackupServiceInterface>(TYPES.Sync_ItemBackupService), container.get<Logger>(TYPES.Sync_Logger),
container.get<DomainEventFactoryInterface>(TYPES.Sync_DomainEventFactory),
container.get<DomainEventPublisherInterface>(TYPES.Sync_DomainEventPublisher),
), ),
) )
container container
@@ -15,6 +15,7 @@ const TYPES = {
Sync_SharedVaultUserRepository: Symbol.for('Sync_SharedVaultUserRepository'), Sync_SharedVaultUserRepository: Symbol.for('Sync_SharedVaultUserRepository'),
Sync_NotificationRepository: Symbol.for('Sync_NotificationRepository'), Sync_NotificationRepository: Symbol.for('Sync_NotificationRepository'),
Sync_MessageRepository: Symbol.for('Sync_MessageRepository'), Sync_MessageRepository: Symbol.for('Sync_MessageRepository'),
Sync_TransitionStatusRepository: Symbol.for('Sync_TransitionStatusRepository'),
// ORM // ORM
Sync_ORMItemRepository: Symbol.for('Sync_ORMItemRepository'), Sync_ORMItemRepository: Symbol.for('Sync_ORMItemRepository'),
Sync_ORMLegacyItemRepository: Symbol.for('Sync_ORMLegacyItemRepository'), Sync_ORMLegacyItemRepository: Symbol.for('Sync_ORMLegacyItemRepository'),
@@ -89,6 +90,9 @@ const TYPES = {
Sync_RemoveItemsFromSharedVault: Symbol.for('Sync_RemoveItemsFromSharedVault'), Sync_RemoveItemsFromSharedVault: Symbol.for('Sync_RemoveItemsFromSharedVault'),
Sync_DesignateSurvivor: Symbol.for('Sync_DesignateSurvivor'), Sync_DesignateSurvivor: Symbol.for('Sync_DesignateSurvivor'),
Sync_RemoveUserFromSharedVaults: Symbol.for('Sync_RemoveUserFromSharedVaults'), Sync_RemoveUserFromSharedVaults: Symbol.for('Sync_RemoveUserFromSharedVaults'),
Sync_TransferSharedVault: Symbol.for('Sync_TransferSharedVault'),
Sync_TransferSharedVaultItems: Symbol.for('Sync_TransferSharedVaultItems'),
Sync_DumpItem: Symbol.for('Sync_DumpItem'),
// Handlers // Handlers
Sync_AccountDeletionRequestedEventHandler: Symbol.for('Sync_AccountDeletionRequestedEventHandler'), Sync_AccountDeletionRequestedEventHandler: Symbol.for('Sync_AccountDeletionRequestedEventHandler'),
Sync_DuplicateItemSyncedEventHandler: Symbol.for('Sync_DuplicateItemSyncedEventHandler'), Sync_DuplicateItemSyncedEventHandler: Symbol.for('Sync_DuplicateItemSyncedEventHandler'),
@@ -42,7 +42,7 @@ export class DomainEventFactory implements DomainEventFactoryInterface {
} }
} }
createSharedVaultRemovedEvent(dto: { sharedVaultUuid: string }): SharedVaultRemovedEvent { createSharedVaultRemovedEvent(dto: { sharedVaultUuid: string; vaultOwnerUuid: string }): SharedVaultRemovedEvent {
return { return {
type: 'SHARED_VAULT_REMOVED', type: 'SHARED_VAULT_REMOVED',
createdAt: this.timer.getUTCDate(), createdAt: this.timer.getUTCDate(),
@@ -102,7 +102,7 @@ export interface DomainEventFactoryInterface {
itemUuid: string itemUuid: string
userUuid: string userUuid: string
}): ItemRemovedFromSharedVaultEvent }): ItemRemovedFromSharedVaultEvent
createSharedVaultRemovedEvent(dto: { sharedVaultUuid: string }): SharedVaultRemovedEvent createSharedVaultRemovedEvent(dto: { sharedVaultUuid: string; vaultOwnerUuid: string }): SharedVaultRemovedEvent
createUserDesignatedAsSurvivorInSharedVaultEvent(dto: { createUserDesignatedAsSurvivorInSharedVaultEvent(dto: {
sharedVaultUuid: string sharedVaultUuid: string
userUuid: string userUuid: string
@@ -1,5 +1,5 @@
import { AccountDeletionRequestedEvent, DomainEventHandlerInterface } from '@standardnotes/domain-events' import { AccountDeletionRequestedEvent, DomainEventHandlerInterface } from '@standardnotes/domain-events'
import { RoleNameCollection } from '@standardnotes/domain-core' import { RoleNameCollection, Uuid } from '@standardnotes/domain-core'
import { Logger } from 'winston' import { Logger } from 'winston'
import { ItemRepositoryResolverInterface } from '../Item/ItemRepositoryResolverInterface' import { ItemRepositoryResolverInterface } from '../Item/ItemRepositoryResolverInterface'
@@ -15,15 +15,25 @@ export class AccountDeletionRequestedEventHandler implements DomainEventHandlerI
) {} ) {}
async handle(event: AccountDeletionRequestedEvent): Promise<void> { async handle(event: AccountDeletionRequestedEvent): Promise<void> {
const userUuidOrError = Uuid.create(event.payload.userUuid)
if (userUuidOrError.isFailed()) {
this.logger.error(`AccountDeletionRequestedEventHandler failed: ${userUuidOrError.getError()}`)
return
}
const userUuid = userUuidOrError.getValue()
const roleNamesOrError = RoleNameCollection.create(event.payload.roleNames) const roleNamesOrError = RoleNameCollection.create(event.payload.roleNames)
if (roleNamesOrError.isFailed()) { if (roleNamesOrError.isFailed()) {
this.logger.error(`AccountDeletionRequestedEventHandler failed: ${roleNamesOrError.getError()}`)
return return
} }
const roleNames = roleNamesOrError.getValue() const roleNames = roleNamesOrError.getValue()
const itemRepository = this.itemRepositoryResolver.resolve(roleNames) const itemRepository = this.itemRepositoryResolver.resolve(roleNames)
await itemRepository.deleteByUserUuid(event.payload.userUuid) await itemRepository.deleteByUserUuidAndNotInSharedVault(userUuid)
const deletingVaultsResult = await this.deleteSharedVaults.execute({ const deletingVaultsResult = await this.deleteSharedVaults.execute({
ownerUuid: event.payload.userUuid, ownerUuid: event.payload.userUuid,
@@ -34,6 +44,16 @@ export class AccountDeletionRequestedEventHandler implements DomainEventHandlerI
) )
} }
const deletedSharedVaultUuids = Array.from(deletingVaultsResult.getValue().keys())
this.logger.debug(
`Deleting items from shared vaults: ${deletedSharedVaultUuids.map((uuid) => uuid.value).join(', ')}`,
)
if (deletedSharedVaultUuids.length !== 0) {
await itemRepository.deleteByUserUuidInSharedVaults(userUuid, deletedSharedVaultUuids)
}
const deletingUserFromOtherVaultsResult = await this.removeUserFromSharedVaults.execute({ const deletingUserFromOtherVaultsResult = await this.removeUserFromSharedVaults.execute({
userUuid: event.payload.userUuid, userUuid: event.payload.userUuid,
}) })
@@ -1,125 +0,0 @@
import 'reflect-metadata'
import {
DomainEventPublisherInterface,
DomainEventService,
ItemRevisionCreationRequestedEvent,
} from '@standardnotes/domain-events'
import { Item } from '../Item/Item'
import { ItemRepositoryInterface } from '../Item/ItemRepositoryInterface'
import { ItemRevisionCreationRequestedEventHandler } from './ItemRevisionCreationRequestedEventHandler'
import { ItemBackupServiceInterface } from '../Item/ItemBackupServiceInterface'
import { DomainEventFactoryInterface } from '../Event/DomainEventFactoryInterface'
import { Uuid, ContentType, Dates, Timestamps, UniqueEntityId } from '@standardnotes/domain-core'
import { ItemRepositoryResolverInterface } from '../Item/ItemRepositoryResolverInterface'
describe('ItemRevisionCreationRequestedEventHandler', () => {
let itemRepositoryResolver: ItemRepositoryResolverInterface
let itemRepository: ItemRepositoryInterface
let event: ItemRevisionCreationRequestedEvent
let item: Item
let itemBackupService: ItemBackupServiceInterface
let domainEventFactory: DomainEventFactoryInterface
let domainEventPublisher: DomainEventPublisherInterface
const createHandler = () =>
new ItemRevisionCreationRequestedEventHandler(
itemRepositoryResolver,
itemBackupService,
domainEventFactory,
domainEventPublisher,
)
beforeEach(() => {
item = Item.create(
{
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
updatedWithSession: null,
content: 'foobar1',
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.findByUuid = jest.fn().mockReturnValue(item)
itemRepositoryResolver = {} as jest.Mocked<ItemRepositoryResolverInterface>
itemRepositoryResolver.resolve = jest.fn().mockReturnValue(itemRepository)
event = {} as jest.Mocked<ItemRevisionCreationRequestedEvent>
event.createdAt = new Date(1)
event.payload = {
itemUuid: '00000000-0000-0000-0000-000000000000',
roleNames: ['CORE_USER'],
}
event.meta = {
correlation: {
userIdentifier: '1-2-3',
userIdentifierType: 'uuid',
},
origin: DomainEventService.SyncingServer,
}
itemBackupService = {} as jest.Mocked<ItemBackupServiceInterface>
itemBackupService.dump = jest.fn().mockReturnValue('foo://bar')
domainEventFactory = {} as jest.Mocked<DomainEventFactoryInterface>
domainEventFactory.createItemDumpedEvent = jest.fn()
domainEventPublisher = {} as jest.Mocked<DomainEventPublisherInterface>
domainEventPublisher.publish = jest.fn()
})
it('should create a revision for an item', async () => {
await createHandler().handle(event)
expect(domainEventPublisher.publish).toHaveBeenCalled()
expect(domainEventFactory.createItemDumpedEvent).toHaveBeenCalled()
})
it('should do nothing if roles names are not valid', async () => {
event.payload.roleNames = ['INVALID_ROLE_NAME']
await createHandler().handle(event)
expect(domainEventPublisher.publish).not.toHaveBeenCalled()
expect(domainEventFactory.createItemDumpedEvent).not.toHaveBeenCalled()
})
it('should not create a revision for an item that does not exist', async () => {
itemRepository.findByUuid = jest.fn().mockReturnValue(null)
itemRepositoryResolver.resolve = jest.fn().mockReturnValue(itemRepository)
await createHandler().handle(event)
expect(domainEventPublisher.publish).not.toHaveBeenCalled()
})
it('should not create a revision for an item if the dump was not created', async () => {
itemBackupService.dump = jest.fn().mockReturnValue('')
await createHandler().handle(event)
expect(domainEventPublisher.publish).not.toHaveBeenCalled()
expect(domainEventFactory.createItemDumpedEvent).not.toHaveBeenCalled()
})
it('should not create a revision if the item uuid is invalid', async () => {
event.payload.itemUuid = 'invalid-uuid'
await createHandler().handle(event)
expect(domainEventPublisher.publish).not.toHaveBeenCalled()
expect(domainEventFactory.createItemDumpedEvent).not.toHaveBeenCalled()
})
})
@@ -1,59 +1,22 @@
import { import { ItemRevisionCreationRequestedEvent, DomainEventHandlerInterface } from '@standardnotes/domain-events'
ItemRevisionCreationRequestedEvent,
DomainEventHandlerInterface,
DomainEventPublisherInterface,
} from '@standardnotes/domain-events'
import { RoleNameCollection, Uuid } from '@standardnotes/domain-core'
import { DomainEventFactoryInterface } from '../Event/DomainEventFactoryInterface' import { DumpItem } from '../UseCase/Syncing/DumpItem/DumpItem'
import { ItemBackupServiceInterface } from '../Item/ItemBackupServiceInterface' import { Logger } from 'winston'
import { ItemRepositoryInterface } from '../Item/ItemRepositoryInterface'
import { ItemRepositoryResolverInterface } from '../Item/ItemRepositoryResolverInterface'
export class ItemRevisionCreationRequestedEventHandler implements DomainEventHandlerInterface { export class ItemRevisionCreationRequestedEventHandler implements DomainEventHandlerInterface {
constructor( constructor(
private itemRepositoryResolver: ItemRepositoryResolverInterface, private dumpItem: DumpItem,
private itemBackupService: ItemBackupServiceInterface, private logger: Logger,
private domainEventFactory: DomainEventFactoryInterface,
private domainEventPublisher: DomainEventPublisherInterface,
) {} ) {}
async handle(event: ItemRevisionCreationRequestedEvent): Promise<void> { async handle(event: ItemRevisionCreationRequestedEvent): Promise<void> {
const roleNamesOrError = RoleNameCollection.create(event.payload.roleNames) const result = await this.dumpItem.execute({
if (roleNamesOrError.isFailed()) { itemUuid: event.payload.itemUuid,
return roleNames: event.payload.roleNames,
} })
const roleNames = roleNamesOrError.getValue()
const itemRepository = this.itemRepositoryResolver.resolve(roleNames) if (result.isFailed()) {
this.logger.error(`Item revision requested handler failed: ${result.getError()}`)
await this.createItemDump(event, itemRepository)
}
private async createItemDump(
event: ItemRevisionCreationRequestedEvent,
itemRepository: ItemRepositoryInterface,
): Promise<void> {
const itemUuidOrError = Uuid.create(event.payload.itemUuid)
if (itemUuidOrError.isFailed()) {
return
}
const itemUuid = itemUuidOrError.getValue()
const item = await itemRepository.findByUuid(itemUuid)
if (item === null) {
return
}
const fileDumpPath = await this.itemBackupService.dump(item)
if (fileDumpPath) {
await this.domainEventPublisher.publish(
this.domainEventFactory.createItemDumpedEvent({
fileDumpPath,
userUuid: event.meta.correlation.userIdentifier,
roleNames: event.payload.roleNames,
}),
)
} }
} }
} }
@@ -1,5 +1,10 @@
import { DomainEventHandlerInterface, SharedVaultFileMovedEvent } from '@standardnotes/domain-events' import { DomainEventHandlerInterface, SharedVaultFileMovedEvent } from '@standardnotes/domain-events'
import { NotificationPayload, NotificationType, Uuid } from '@standardnotes/domain-core' import {
NotificationPayload,
NotificationPayloadIdentifierType,
NotificationType,
Uuid,
} from '@standardnotes/domain-core'
import { Logger } from 'winston' import { Logger } from 'winston'
import { UpdateStorageQuotaUsedInSharedVault } from '../UseCase/SharedVaults/UpdateStorageQuotaUsedInSharedVault/UpdateStorageQuotaUsedInSharedVault' import { UpdateStorageQuotaUsedInSharedVault } from '../UseCase/SharedVaults/UpdateStorageQuotaUsedInSharedVault/UpdateStorageQuotaUsedInSharedVault'
@@ -34,7 +39,10 @@ export class SharedVaultFileMovedEventHandler implements DomainEventHandlerInter
} }
const notificationPayload = NotificationPayload.create({ const notificationPayload = NotificationPayload.create({
sharedVaultUuid: sharedVaultUuid, primaryIdentifier: sharedVaultUuid,
primaryIndentifierType: NotificationPayloadIdentifierType.create(
NotificationPayloadIdentifierType.TYPES.SharedVaultUuid,
).getValue(),
type: NotificationType.create(NotificationType.TYPES.SharedVaultFileRemoved).getValue(), type: NotificationType.create(NotificationType.TYPES.SharedVaultFileRemoved).getValue(),
version: '1.0', version: '1.0',
}).getValue() }).getValue()
@@ -71,7 +79,10 @@ export class SharedVaultFileMovedEventHandler implements DomainEventHandlerInter
} }
const notificationPayload = NotificationPayload.create({ const notificationPayload = NotificationPayload.create({
sharedVaultUuid: sharedVaultUuid, primaryIdentifier: sharedVaultUuid,
primaryIndentifierType: NotificationPayloadIdentifierType.create(
NotificationPayloadIdentifierType.TYPES.SharedVaultUuid,
).getValue(),
type: NotificationType.create(NotificationType.TYPES.SharedVaultFileUploaded).getValue(), type: NotificationType.create(NotificationType.TYPES.SharedVaultFileUploaded).getValue(),
version: '1.0', version: '1.0',
}).getValue() }).getValue()
@@ -1,5 +1,10 @@
import { DomainEventHandlerInterface, SharedVaultFileRemovedEvent } from '@standardnotes/domain-events' import { DomainEventHandlerInterface, SharedVaultFileRemovedEvent } from '@standardnotes/domain-events'
import { NotificationPayload, NotificationType, Uuid } from '@standardnotes/domain-core' import {
NotificationPayload,
NotificationPayloadIdentifierType,
NotificationType,
Uuid,
} from '@standardnotes/domain-core'
import { Logger } from 'winston' import { Logger } from 'winston'
import { UpdateStorageQuotaUsedInSharedVault } from '../UseCase/SharedVaults/UpdateStorageQuotaUsedInSharedVault/UpdateStorageQuotaUsedInSharedVault' import { UpdateStorageQuotaUsedInSharedVault } from '../UseCase/SharedVaults/UpdateStorageQuotaUsedInSharedVault/UpdateStorageQuotaUsedInSharedVault'
@@ -33,7 +38,10 @@ export class SharedVaultFileRemovedEventHandler implements DomainEventHandlerInt
} }
const notificationPayload = NotificationPayload.create({ const notificationPayload = NotificationPayload.create({
sharedVaultUuid, primaryIdentifier: sharedVaultUuid,
primaryIndentifierType: NotificationPayloadIdentifierType.create(
NotificationPayloadIdentifierType.TYPES.SharedVaultUuid,
).getValue(),
type: NotificationType.create(NotificationType.TYPES.SharedVaultFileRemoved).getValue(), type: NotificationType.create(NotificationType.TYPES.SharedVaultFileRemoved).getValue(),
version: '1.0', version: '1.0',
}).getValue() }).getValue()
@@ -1,5 +1,10 @@
import { DomainEventHandlerInterface, SharedVaultFileUploadedEvent } from '@standardnotes/domain-events' import { DomainEventHandlerInterface, SharedVaultFileUploadedEvent } from '@standardnotes/domain-events'
import { NotificationPayload, NotificationType, Uuid } from '@standardnotes/domain-core' import {
NotificationPayload,
NotificationPayloadIdentifierType,
NotificationType,
Uuid,
} from '@standardnotes/domain-core'
import { Logger } from 'winston' import { Logger } from 'winston'
import { UpdateStorageQuotaUsedInSharedVault } from '../UseCase/SharedVaults/UpdateStorageQuotaUsedInSharedVault/UpdateStorageQuotaUsedInSharedVault' import { UpdateStorageQuotaUsedInSharedVault } from '../UseCase/SharedVaults/UpdateStorageQuotaUsedInSharedVault/UpdateStorageQuotaUsedInSharedVault'
@@ -33,7 +38,10 @@ export class SharedVaultFileUploadedEventHandler implements DomainEventHandlerIn
} }
const notificationPayload = NotificationPayload.create({ const notificationPayload = NotificationPayload.create({
sharedVaultUuid, primaryIdentifier: sharedVaultUuid,
primaryIndentifierType: NotificationPayloadIdentifierType.create(
NotificationPayloadIdentifierType.TYPES.SharedVaultUuid,
).getValue(),
type: NotificationType.create(NotificationType.TYPES.SharedVaultFileUploaded).getValue(), type: NotificationType.create(NotificationType.TYPES.SharedVaultFileUploaded).getValue(),
version: '1.0', version: '1.0',
}).getValue() }).getValue()
@@ -1,7 +1,9 @@
import { KeyParamsData } from '@standardnotes/responses' import { KeyParamsData } from '@standardnotes/responses'
import { Result } from '@standardnotes/domain-core'
import { Item } from './Item' import { Item } from './Item'
export interface ItemBackupServiceInterface { export interface ItemBackupServiceInterface {
backup(items: Array<Item>, authParams: KeyParamsData, contentSizeLimit?: number): Promise<string[]> backup(items: Array<Item>, authParams: KeyParamsData, contentSizeLimit?: number): Promise<string[]>
dump(item: Item): Promise<string> dump(item: Item): Promise<Result<string>>
} }
@@ -6,7 +6,8 @@ import { ExtendedIntegrityPayload } from './ExtendedIntegrityPayload'
import { ItemContentSizeDescriptor } from './ItemContentSizeDescriptor' import { ItemContentSizeDescriptor } from './ItemContentSizeDescriptor'
export interface ItemRepositoryInterface { export interface ItemRepositoryInterface {
deleteByUserUuid(userUuid: string): Promise<void> deleteByUserUuidAndNotInSharedVault(userUuid: Uuid): Promise<void>
deleteByUserUuidInSharedVaults(userUuid: Uuid, sharedVaultUuids: Uuid[]): Promise<void>
findAll(query: ItemQuery): Promise<Item[]> findAll(query: ItemQuery): Promise<Item[]>
countAll(query: ItemQuery): Promise<number> countAll(query: ItemQuery): Promise<number>
findContentSizeForComputingTransferLimit(query: ItemQuery): Promise<Array<ItemContentSizeDescriptor>> findContentSizeForComputingTransferLimit(query: ItemQuery): Promise<Array<ItemContentSizeDescriptor>>
@@ -20,4 +21,5 @@ export interface ItemRepositoryInterface {
markItemsAsDeleted(itemUuids: Array<string>, updatedAtTimestamp: number): Promise<void> markItemsAsDeleted(itemUuids: Array<string>, updatedAtTimestamp: number): Promise<void>
updateContentSize(itemUuid: string, contentSize: number): Promise<void> updateContentSize(itemUuid: string, contentSize: number): Promise<void>
unassignFromSharedVault(sharedVaultUuid: Uuid): Promise<void> unassignFromSharedVault(sharedVaultUuid: Uuid): Promise<void>
updateSharedVaultOwner(dto: { sharedVaultUuid: Uuid; fromOwnerUuid: Uuid; toOwnerUuid: Uuid }): Promise<void>
} }
@@ -1,11 +1,20 @@
import { NotificationPayload, NotificationType, Timestamps, Uuid } from '@standardnotes/domain-core' import {
NotificationPayload,
NotificationPayloadIdentifierType,
NotificationType,
Timestamps,
Uuid,
} from '@standardnotes/domain-core'
import { Notification } from './Notification' import { Notification } from './Notification'
describe('Notification', () => { describe('Notification', () => {
it('should create an entity', () => { it('should create an entity', () => {
const payload = NotificationPayload.create({ const payload = NotificationPayload.create({
sharedVaultUuid: Uuid.create('0e8c3c7e-3f1a-4f7a-9b5a-5b2b0a7d4b1e').getValue(), primaryIdentifier: Uuid.create('0e8c3c7e-3f1a-4f7a-9b5a-5b2b0a7d4b1e').getValue(),
primaryIndentifierType: NotificationPayloadIdentifierType.create(
NotificationPayloadIdentifierType.TYPES.SharedVaultUuid,
).getValue(),
type: NotificationType.create(NotificationType.TYPES.SelfRemovedFromSharedVault).getValue(), type: NotificationType.create(NotificationType.TYPES.SelfRemovedFromSharedVault).getValue(),
version: '1.0', version: '1.0',
}).getValue() }).getValue()
@@ -8,4 +8,5 @@ export interface SharedVaultUserRepositoryInterface {
remove(sharedVault: SharedVaultUser): Promise<void> remove(sharedVault: SharedVaultUser): Promise<void>
removeBySharedVaultUuid(sharedVaultUuid: Uuid): Promise<void> removeBySharedVaultUuid(sharedVaultUuid: Uuid): Promise<void>
findByUserUuidAndSharedVaultUuid(dto: { userUuid: Uuid; sharedVaultUuid: Uuid }): Promise<SharedVaultUser | null> findByUserUuidAndSharedVaultUuid(dto: { userUuid: Uuid; sharedVaultUuid: Uuid }): Promise<SharedVaultUser | null>
findDesignatedSurvivorBySharedVaultUuid(sharedVaultUuid: Uuid): Promise<SharedVaultUser | null>
} }
@@ -0,0 +1,4 @@
export interface TransitionRepositoryInterface {
getPagingProgress(userUuid: string): Promise<number>
setPagingProgress(userUuid: string, progress: number): Promise<void>
}
@@ -1,5 +1,11 @@
import { TimerInterface } from '@standardnotes/time' import { TimerInterface } from '@standardnotes/time'
import { NotificationPayload, NotificationType, Result, Uuid } from '@standardnotes/domain-core' import {
NotificationPayload,
NotificationPayloadIdentifierType,
NotificationType,
Result,
Uuid,
} from '@standardnotes/domain-core'
import { NotificationRepositoryInterface } from '../../../Notifications/NotificationRepositoryInterface' import { NotificationRepositoryInterface } from '../../../Notifications/NotificationRepositoryInterface'
import { Notification } from '../../../Notifications/Notification' import { Notification } from '../../../Notifications/Notification'
@@ -28,7 +34,10 @@ describe('AddNotificationForUser', () => {
timer.getTimestampInMicroseconds = jest.fn().mockReturnValue(123456789) timer.getTimestampInMicroseconds = jest.fn().mockReturnValue(123456789)
payload = NotificationPayload.create({ payload = NotificationPayload.create({
sharedVaultUuid: Uuid.create('0e8c3c7e-3f1a-4f7a-9b5a-5b2b0a7d4b1e').getValue(), primaryIdentifier: Uuid.create('0e8c3c7e-3f1a-4f7a-9b5a-5b2b0a7d4b1e').getValue(),
primaryIndentifierType: NotificationPayloadIdentifierType.create(
NotificationPayloadIdentifierType.TYPES.SharedVaultUuid,
).getValue(),
type: NotificationType.create(NotificationType.TYPES.SelfRemovedFromSharedVault).getValue(), type: NotificationType.create(NotificationType.TYPES.SelfRemovedFromSharedVault).getValue(),
version: '1.0', version: '1.0',
}).getValue() }).getValue()
@@ -6,6 +6,7 @@ import {
NotificationPayload, NotificationPayload,
NotificationType, NotificationType,
SharedVaultUser, SharedVaultUser,
NotificationPayloadIdentifierType,
} from '@standardnotes/domain-core' } from '@standardnotes/domain-core'
import { SharedVaultUserRepositoryInterface } from '../../../SharedVault/User/SharedVaultUserRepositoryInterface' import { SharedVaultUserRepositoryInterface } from '../../../SharedVault/User/SharedVaultUserRepositoryInterface'
import { AddNotificationForUser } from '../AddNotificationForUser/AddNotificationForUser' import { AddNotificationForUser } from '../AddNotificationForUser/AddNotificationForUser'
@@ -35,7 +36,10 @@ describe('AddNotificationsForUsers', () => {
addNotificationForUser.execute = jest.fn().mockResolvedValue(Result.ok()) addNotificationForUser.execute = jest.fn().mockResolvedValue(Result.ok())
payload = NotificationPayload.create({ payload = NotificationPayload.create({
sharedVaultUuid: Uuid.create('0e8c3c7e-3f1a-4f7a-9b5a-5b2b0a7d4b1e').getValue(), primaryIdentifier: Uuid.create('0e8c3c7e-3f1a-4f7a-9b5a-5b2b0a7d4b1e').getValue(),
primaryIndentifierType: NotificationPayloadIdentifierType.create(
NotificationPayloadIdentifierType.TYPES.SharedVaultUuid,
).getValue(),
type: NotificationType.create(NotificationType.TYPES.SharedVaultFileUploaded).getValue(), type: NotificationType.create(NotificationType.TYPES.SharedVaultFileUploaded).getValue(),
version: '1.0', version: '1.0',
}).getValue() }).getValue()
@@ -1,4 +1,10 @@
import { NotificationPayload, NotificationType, Timestamps, Uuid } from '@standardnotes/domain-core' import {
NotificationPayload,
NotificationPayloadIdentifierType,
NotificationType,
Timestamps,
Uuid,
} from '@standardnotes/domain-core'
import { NotificationRepositoryInterface } from '../../../Notifications/NotificationRepositoryInterface' import { NotificationRepositoryInterface } from '../../../Notifications/NotificationRepositoryInterface'
import { RemoveNotificationsForUser } from './RemoveNotificationsForUser' import { RemoveNotificationsForUser } from './RemoveNotificationsForUser'
@@ -15,8 +21,14 @@ describe('RemoveNotificationsForUser', () => {
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(), userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
type: NotificationType.create(NotificationType.TYPES.SharedVaultItemRemoved).getValue(), type: NotificationType.create(NotificationType.TYPES.SharedVaultItemRemoved).getValue(),
payload: NotificationPayload.create({ payload: NotificationPayload.create({
itemUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(), primaryIdentifier: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
sharedVaultUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(), primaryIndentifierType: NotificationPayloadIdentifierType.create(
NotificationPayloadIdentifierType.TYPES.SharedVaultUuid,
).getValue(),
secondaryIdentifier: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
secondaryIdentifierType: NotificationPayloadIdentifierType.create(
NotificationPayloadIdentifierType.TYPES.ItemUuid,
).getValue(),
type: NotificationType.create(NotificationType.TYPES.SharedVaultItemRemoved).getValue(), type: NotificationType.create(NotificationType.TYPES.SharedVaultItemRemoved).getValue(),
version: '1.0', version: '1.0',
}).getValue(), }).getValue(),
@@ -1,5 +1,6 @@
import { import {
NotificationPayload, NotificationPayload,
NotificationPayloadIdentifierType,
NotificationType, NotificationType,
Result, Result,
SharedVaultUser, SharedVaultUser,
@@ -73,7 +74,10 @@ export class AddUserToSharedVault implements UseCaseInterface<SharedVaultUser> {
await this.sharedVaultUserRepository.save(sharedVaultUser) await this.sharedVaultUserRepository.save(sharedVaultUser)
const notificationPayloadOrError = NotificationPayload.create({ const notificationPayloadOrError = NotificationPayload.create({
sharedVaultUuid: sharedVaultUuid, primaryIdentifier: sharedVaultUuid,
primaryIndentifierType: NotificationPayloadIdentifierType.create(
NotificationPayloadIdentifierType.TYPES.SharedVaultUuid,
).getValue(),
type: NotificationType.create(NotificationType.TYPES.UserAddedToSharedVault).getValue(), type: NotificationType.create(NotificationType.TYPES.UserAddedToSharedVault).getValue(),
version: '1.0', version: '1.0',
}) })
@@ -1,15 +1,15 @@
import { NotificationPayload, Result, 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 { SharedVaultInviteRepositoryInterface } from '../../../SharedVault/User/Invite/SharedVaultInviteRepositoryInterface'
import { DeclineInviteToSharedVault } from './DeclineInviteToSharedVault' import { CancelInviteToSharedVault } from './CancelInviteToSharedVault'
import { SharedVaultInvite } from '../../../SharedVault/User/Invite/SharedVaultInvite' import { SharedVaultInvite } from '../../../SharedVault/User/Invite/SharedVaultInvite'
import { AddNotificationForUser } from '../../Messaging/AddNotificationForUser/AddNotificationForUser' import { AddNotificationForUser } from '../../Messaging/AddNotificationForUser/AddNotificationForUser'
describe('DeclineInviteToSharedVault', () => { describe('CancelInviteToSharedVault', () => {
let sharedVaultInviteRepository: SharedVaultInviteRepositoryInterface let sharedVaultInviteRepository: SharedVaultInviteRepositoryInterface
let invite: SharedVaultInvite let invite: SharedVaultInvite
let addNotificationForUser: AddNotificationForUser let addNotificationForUser: AddNotificationForUser
const createUseCase = () => new DeclineInviteToSharedVault(sharedVaultInviteRepository, addNotificationForUser) const createUseCase = () => new CancelInviteToSharedVault(sharedVaultInviteRepository, addNotificationForUser)
beforeEach(() => { beforeEach(() => {
invite = SharedVaultInvite.create({ invite = SharedVaultInvite.create({
@@ -1,16 +1,23 @@
import { NotificationPayload, NotificationType, Result, UseCaseInterface, Uuid } from '@standardnotes/domain-core' import {
NotificationPayload,
NotificationPayloadIdentifierType,
NotificationType,
Result,
UseCaseInterface,
Uuid,
} from '@standardnotes/domain-core'
import { DeclineInviteToSharedVaultDTO } from './DeclineInviteToSharedVaultDTO' import { CancelInviteToSharedVaultDTO } from './CancelInviteToSharedVaultDTO'
import { SharedVaultInviteRepositoryInterface } from '../../../SharedVault/User/Invite/SharedVaultInviteRepositoryInterface' import { SharedVaultInviteRepositoryInterface } from '../../../SharedVault/User/Invite/SharedVaultInviteRepositoryInterface'
import { AddNotificationForUser } from '../../Messaging/AddNotificationForUser/AddNotificationForUser' import { AddNotificationForUser } from '../../Messaging/AddNotificationForUser/AddNotificationForUser'
export class DeclineInviteToSharedVault implements UseCaseInterface<void> { export class CancelInviteToSharedVault implements UseCaseInterface<void> {
constructor( constructor(
private sharedVaultInviteRepository: SharedVaultInviteRepositoryInterface, private sharedVaultInviteRepository: SharedVaultInviteRepositoryInterface,
private addNotificationForUser: AddNotificationForUser, private addNotificationForUser: AddNotificationForUser,
) {} ) {}
async execute(dto: DeclineInviteToSharedVaultDTO): Promise<Result<void>> { async execute(dto: CancelInviteToSharedVaultDTO): Promise<Result<void>> {
const inviteUuidOrError = Uuid.create(dto.inviteUuid) const inviteUuidOrError = Uuid.create(dto.inviteUuid)
if (inviteUuidOrError.isFailed()) { if (inviteUuidOrError.isFailed()) {
return Result.fail(inviteUuidOrError.getError()) return Result.fail(inviteUuidOrError.getError())
@@ -35,8 +42,15 @@ export class DeclineInviteToSharedVault implements UseCaseInterface<void> {
await this.sharedVaultInviteRepository.remove(invite) await this.sharedVaultInviteRepository.remove(invite)
const notificationPayloadOrError = NotificationPayload.create({ const notificationPayloadOrError = NotificationPayload.create({
sharedVaultUuid: invite.props.sharedVaultUuid, primaryIdentifier: Uuid.create(invite.id.toString()).getValue(),
type: NotificationType.create(NotificationType.TYPES.SharedVaultInviteDeclined).getValue(), primaryIndentifierType: NotificationPayloadIdentifierType.create(
NotificationPayloadIdentifierType.TYPES.SharedVaultInviteUuid,
).getValue(),
secondaryIdentifier: invite.props.sharedVaultUuid,
secondaryIdentifierType: NotificationPayloadIdentifierType.create(
NotificationPayloadIdentifierType.TYPES.SharedVaultUuid,
).getValue(),
type: NotificationType.create(NotificationType.TYPES.SharedVaultInviteCanceled).getValue(),
version: '1.0', version: '1.0',
}) })
if (notificationPayloadOrError.isFailed()) { if (notificationPayloadOrError.isFailed()) {
@@ -46,7 +60,7 @@ export class DeclineInviteToSharedVault implements UseCaseInterface<void> {
const result = await this.addNotificationForUser.execute({ const result = await this.addNotificationForUser.execute({
userUuid: invite.props.userUuid.value, userUuid: invite.props.userUuid.value,
type: NotificationType.TYPES.SharedVaultInviteDeclined, type: NotificationType.TYPES.SharedVaultInviteCanceled,
payload: notificationPayload, payload: notificationPayload,
version: '1.0', version: '1.0',
}) })
@@ -0,0 +1,4 @@
export interface CancelInviteToSharedVaultDTO {
inviteUuid: string
userUuid: string
}
@@ -1,4 +0,0 @@
export interface DeclineInviteToSharedVaultDTO {
inviteUuid: string
userUuid: string
}
@@ -7,21 +7,23 @@ import { SharedVaultUserRepositoryInterface } from '../../../SharedVault/User/Sh
import { DeleteSharedVault } from './DeleteSharedVault' import { DeleteSharedVault } from './DeleteSharedVault'
import { SharedVault } from '../../../SharedVault/SharedVault' import { SharedVault } from '../../../SharedVault/SharedVault'
import { RemoveUserFromSharedVault } from '../RemoveUserFromSharedVault/RemoveUserFromSharedVault' import { RemoveUserFromSharedVault } from '../RemoveUserFromSharedVault/RemoveUserFromSharedVault'
import { DeclineInviteToSharedVault } from '../DeclineInviteToSharedVault/DeclineInviteToSharedVault' import { CancelInviteToSharedVault } from '../CancelInviteToSharedVault/CancelInviteToSharedVault'
import { SharedVaultInvite } from '../../../SharedVault/User/Invite/SharedVaultInvite' import { SharedVaultInvite } from '../../../SharedVault/User/Invite/SharedVaultInvite'
import { DomainEventFactoryInterface } from '../../../Event/DomainEventFactoryInterface' import { DomainEventFactoryInterface } from '../../../Event/DomainEventFactoryInterface'
import { TransferSharedVault } from '../TransferSharedVault/TransferSharedVault'
describe('DeleteSharedVault', () => { describe('DeleteSharedVault', () => {
let sharedVaultRepository: SharedVaultRepositoryInterface let sharedVaultRepository: SharedVaultRepositoryInterface
let sharedVaultUserRepository: SharedVaultUserRepositoryInterface let sharedVaultUserRepository: SharedVaultUserRepositoryInterface
let sharedVaultInviteRepository: SharedVaultInviteRepositoryInterface let sharedVaultInviteRepository: SharedVaultInviteRepositoryInterface
let removeUserFromSharedVault: RemoveUserFromSharedVault let removeUserFromSharedVault: RemoveUserFromSharedVault
let declineInviteToSharedVault: DeclineInviteToSharedVault let cancelInviteToSharedVault: CancelInviteToSharedVault
let sharedVault: SharedVault let sharedVault: SharedVault
let sharedVaultUser: SharedVaultUser let sharedVaultUser: SharedVaultUser
let sharedVaultInvite: SharedVaultInvite let sharedVaultInvite: SharedVaultInvite
let domainEventFactory: DomainEventFactoryInterface let domainEventFactory: DomainEventFactoryInterface
let domainEventPublisher: DomainEventPublisherInterface let domainEventPublisher: DomainEventPublisherInterface
let transferSharedVault: TransferSharedVault
const createUseCase = () => const createUseCase = () =>
new DeleteSharedVault( new DeleteSharedVault(
@@ -29,12 +31,16 @@ describe('DeleteSharedVault', () => {
sharedVaultUserRepository, sharedVaultUserRepository,
sharedVaultInviteRepository, sharedVaultInviteRepository,
removeUserFromSharedVault, removeUserFromSharedVault,
declineInviteToSharedVault, cancelInviteToSharedVault,
domainEventFactory, domainEventFactory,
domainEventPublisher, domainEventPublisher,
transferSharedVault,
) )
beforeEach(() => { beforeEach(() => {
transferSharedVault = {} as jest.Mocked<TransferSharedVault>
transferSharedVault.execute = jest.fn().mockReturnValue(Result.ok())
sharedVault = SharedVault.create({ sharedVault = SharedVault.create({
fileUploadBytesUsed: 2, fileUploadBytesUsed: 2,
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(), userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
@@ -53,6 +59,7 @@ describe('DeleteSharedVault', () => {
}).getValue() }).getValue()
sharedVaultUserRepository = {} as jest.Mocked<SharedVaultUserRepositoryInterface> sharedVaultUserRepository = {} as jest.Mocked<SharedVaultUserRepositoryInterface>
sharedVaultUserRepository.findBySharedVaultUuid = jest.fn().mockResolvedValue([sharedVaultUser]) sharedVaultUserRepository.findBySharedVaultUuid = jest.fn().mockResolvedValue([sharedVaultUser])
sharedVaultUserRepository.findDesignatedSurvivorBySharedVaultUuid = jest.fn().mockResolvedValue(null)
sharedVaultInvite = SharedVaultInvite.create({ sharedVaultInvite = SharedVaultInvite.create({
encryptedMessage: 'test', encryptedMessage: 'test',
@@ -65,8 +72,8 @@ describe('DeleteSharedVault', () => {
sharedVaultInviteRepository = {} as jest.Mocked<SharedVaultInviteRepositoryInterface> sharedVaultInviteRepository = {} as jest.Mocked<SharedVaultInviteRepositoryInterface>
sharedVaultInviteRepository.findBySharedVaultUuid = jest.fn().mockReturnValue([sharedVaultInvite]) sharedVaultInviteRepository.findBySharedVaultUuid = jest.fn().mockReturnValue([sharedVaultInvite])
declineInviteToSharedVault = {} as jest.Mocked<DeclineInviteToSharedVault> cancelInviteToSharedVault = {} as jest.Mocked<CancelInviteToSharedVault>
declineInviteToSharedVault.execute = jest.fn().mockReturnValue(Result.ok()) cancelInviteToSharedVault.execute = jest.fn().mockReturnValue(Result.ok())
removeUserFromSharedVault = {} as jest.Mocked<RemoveUserFromSharedVault> removeUserFromSharedVault = {} as jest.Mocked<RemoveUserFromSharedVault>
removeUserFromSharedVault.execute = jest.fn().mockReturnValue(Result.ok()) removeUserFromSharedVault.execute = jest.fn().mockReturnValue(Result.ok())
@@ -93,7 +100,7 @@ describe('DeleteSharedVault', () => {
expect(result.isFailed()).toBeFalsy() expect(result.isFailed()).toBeFalsy()
expect(sharedVaultRepository.remove).toHaveBeenCalled() expect(sharedVaultRepository.remove).toHaveBeenCalled()
expect(declineInviteToSharedVault.execute).toHaveBeenCalled() expect(cancelInviteToSharedVault.execute).toHaveBeenCalled()
expect(removeUserFromSharedVault.execute).toHaveBeenCalled() expect(removeUserFromSharedVault.execute).toHaveBeenCalled()
}) })
@@ -108,7 +115,7 @@ describe('DeleteSharedVault', () => {
expect(result.isFailed()).toBeTruthy() expect(result.isFailed()).toBeTruthy()
expect(sharedVaultRepository.remove).not.toHaveBeenCalled() expect(sharedVaultRepository.remove).not.toHaveBeenCalled()
expect(declineInviteToSharedVault.execute).not.toHaveBeenCalled() expect(cancelInviteToSharedVault.execute).not.toHaveBeenCalled()
expect(removeUserFromSharedVault.execute).not.toHaveBeenCalled() expect(removeUserFromSharedVault.execute).not.toHaveBeenCalled()
}) })
@@ -122,7 +129,7 @@ describe('DeleteSharedVault', () => {
expect(result.isFailed()).toBeTruthy() expect(result.isFailed()).toBeTruthy()
expect(sharedVaultRepository.remove).not.toHaveBeenCalled() expect(sharedVaultRepository.remove).not.toHaveBeenCalled()
expect(declineInviteToSharedVault.execute).not.toHaveBeenCalled() expect(cancelInviteToSharedVault.execute).not.toHaveBeenCalled()
expect(removeUserFromSharedVault.execute).not.toHaveBeenCalled() expect(removeUserFromSharedVault.execute).not.toHaveBeenCalled()
}) })
@@ -136,7 +143,7 @@ describe('DeleteSharedVault', () => {
expect(result.isFailed()).toBeTruthy() expect(result.isFailed()).toBeTruthy()
expect(sharedVaultRepository.remove).not.toHaveBeenCalled() expect(sharedVaultRepository.remove).not.toHaveBeenCalled()
expect(declineInviteToSharedVault.execute).not.toHaveBeenCalled() expect(cancelInviteToSharedVault.execute).not.toHaveBeenCalled()
expect(removeUserFromSharedVault.execute).not.toHaveBeenCalled() expect(removeUserFromSharedVault.execute).not.toHaveBeenCalled()
}) })
@@ -156,7 +163,7 @@ describe('DeleteSharedVault', () => {
expect(result.isFailed()).toBeTruthy() expect(result.isFailed()).toBeTruthy()
expect(sharedVaultRepository.remove).not.toHaveBeenCalled() expect(sharedVaultRepository.remove).not.toHaveBeenCalled()
expect(declineInviteToSharedVault.execute).not.toHaveBeenCalled() expect(cancelInviteToSharedVault.execute).not.toHaveBeenCalled()
expect(removeUserFromSharedVault.execute).not.toHaveBeenCalled() expect(removeUserFromSharedVault.execute).not.toHaveBeenCalled()
}) })
@@ -171,12 +178,11 @@ describe('DeleteSharedVault', () => {
expect(result.isFailed()).toBeTruthy() expect(result.isFailed()).toBeTruthy()
expect(sharedVaultRepository.remove).not.toHaveBeenCalled() expect(sharedVaultRepository.remove).not.toHaveBeenCalled()
expect(declineInviteToSharedVault.execute).not.toHaveBeenCalled()
expect(removeUserFromSharedVault.execute).toHaveBeenCalled() expect(removeUserFromSharedVault.execute).toHaveBeenCalled()
}) })
it('should return error if declining invite to shared vault fails', async () => { it('should return error if declining invite to shared vault fails', async () => {
declineInviteToSharedVault.execute = jest.fn().mockReturnValue(Result.fail('failed')) cancelInviteToSharedVault.execute = jest.fn().mockReturnValue(Result.fail('failed'))
const useCase = createUseCase() const useCase = createUseCase()
const result = await useCase.execute({ const result = await useCase.execute({
@@ -186,7 +192,60 @@ describe('DeleteSharedVault', () => {
expect(result.isFailed()).toBeTruthy() expect(result.isFailed()).toBeTruthy()
expect(sharedVaultRepository.remove).not.toHaveBeenCalled() expect(sharedVaultRepository.remove).not.toHaveBeenCalled()
expect(declineInviteToSharedVault.execute).toHaveBeenCalled() expect(cancelInviteToSharedVault.execute).toHaveBeenCalled()
expect(removeUserFromSharedVault.execute).toHaveBeenCalled() expect(removeUserFromSharedVault.execute).not.toHaveBeenCalled()
})
describe('when shared vault has designated survivor', () => {
beforeEach(() => {
sharedVaultUserRepository.findDesignatedSurvivorBySharedVaultUuid = jest.fn().mockResolvedValue(sharedVaultUser)
})
it('should transfer shared vault to designated survivor', async () => {
const useCase = createUseCase()
const result = await useCase.execute({
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
originatorUuid: '00000000-0000-0000-0000-000000000000',
})
expect(result.isFailed()).toBeFalsy()
expect(sharedVaultRepository.remove).not.toHaveBeenCalled()
expect(cancelInviteToSharedVault.execute).toHaveBeenCalled()
expect(removeUserFromSharedVault.execute).toHaveBeenCalled()
expect(transferSharedVault.execute).toHaveBeenCalled()
})
it('should fail if transfering shared vault to designated survivor fails', async () => {
transferSharedVault.execute = jest.fn().mockReturnValue(Result.fail('failed'))
const useCase = createUseCase()
const result = await useCase.execute({
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
originatorUuid: '00000000-0000-0000-0000-000000000000',
})
expect(result.isFailed()).toBeTruthy()
expect(sharedVaultRepository.remove).not.toHaveBeenCalled()
expect(cancelInviteToSharedVault.execute).toHaveBeenCalled()
expect(removeUserFromSharedVault.execute).not.toHaveBeenCalled()
expect(transferSharedVault.execute).toHaveBeenCalled()
})
it('should fail if removing owner from shared vault fails', async () => {
removeUserFromSharedVault.execute = jest.fn().mockReturnValue(Result.fail('failed'))
const useCase = createUseCase()
const result = await useCase.execute({
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
originatorUuid: '00000000-0000-0000-0000-000000000000',
})
expect(result.isFailed()).toBeTruthy()
expect(sharedVaultRepository.remove).not.toHaveBeenCalled()
expect(cancelInviteToSharedVault.execute).toHaveBeenCalled()
expect(removeUserFromSharedVault.execute).toHaveBeenCalled()
expect(transferSharedVault.execute).toHaveBeenCalled()
})
}) })
}) })
@@ -6,21 +6,23 @@ import { SharedVaultRepositoryInterface } from '../../../SharedVault/SharedVault
import { SharedVaultUserRepositoryInterface } from '../../../SharedVault/User/SharedVaultUserRepositoryInterface' import { SharedVaultUserRepositoryInterface } from '../../../SharedVault/User/SharedVaultUserRepositoryInterface'
import { SharedVaultInviteRepositoryInterface } from '../../../SharedVault/User/Invite/SharedVaultInviteRepositoryInterface' import { SharedVaultInviteRepositoryInterface } from '../../../SharedVault/User/Invite/SharedVaultInviteRepositoryInterface'
import { RemoveUserFromSharedVault } from '../RemoveUserFromSharedVault/RemoveUserFromSharedVault' import { RemoveUserFromSharedVault } from '../RemoveUserFromSharedVault/RemoveUserFromSharedVault'
import { DeclineInviteToSharedVault } from '../DeclineInviteToSharedVault/DeclineInviteToSharedVault' import { CancelInviteToSharedVault } from '../CancelInviteToSharedVault/CancelInviteToSharedVault'
import { DomainEventFactoryInterface } from '../../../Event/DomainEventFactoryInterface' import { DomainEventFactoryInterface } from '../../../Event/DomainEventFactoryInterface'
import { TransferSharedVault } from '../TransferSharedVault/TransferSharedVault'
export class DeleteSharedVault implements UseCaseInterface<void> { export class DeleteSharedVault implements UseCaseInterface<{ status: 'deleted' | 'transitioned' }> {
constructor( constructor(
private sharedVaultRepository: SharedVaultRepositoryInterface, private sharedVaultRepository: SharedVaultRepositoryInterface,
private sharedVaultUserRepository: SharedVaultUserRepositoryInterface, private sharedVaultUserRepository: SharedVaultUserRepositoryInterface,
private sharedVaultInviteRepository: SharedVaultInviteRepositoryInterface, private sharedVaultInviteRepository: SharedVaultInviteRepositoryInterface,
private removeUserFromSharedVault: RemoveUserFromSharedVault, private removeUserFromSharedVault: RemoveUserFromSharedVault,
private declineInviteToSharedVault: DeclineInviteToSharedVault, private cancelInviteToSharedVault: CancelInviteToSharedVault,
private domainEventFactory: DomainEventFactoryInterface, private domainEventFactory: DomainEventFactoryInterface,
private domainEventPublisher: DomainEventPublisherInterface, private domainEventPublisher: DomainEventPublisherInterface,
private transferSharedVault: TransferSharedVault,
) {} ) {}
async execute(dto: DeleteSharedVaultDTO): Promise<Result<void>> { async execute(dto: DeleteSharedVaultDTO): Promise<Result<{ status: 'deleted' | 'transitioned' }>> {
const originatorUuidOrError = Uuid.create(dto.originatorUuid) const originatorUuidOrError = Uuid.create(dto.originatorUuid)
if (originatorUuidOrError.isFailed()) { if (originatorUuidOrError.isFailed()) {
return Result.fail(originatorUuidOrError.getError()) return Result.fail(originatorUuidOrError.getError())
@@ -42,6 +44,44 @@ export class DeleteSharedVault implements UseCaseInterface<void> {
return Result.fail('Shared vault does not belong to the user') return Result.fail('Shared vault does not belong to the user')
} }
const sharedVaultInvites = await this.sharedVaultInviteRepository.findBySharedVaultUuid(sharedVaultUuid)
for (const sharedVaultInvite of sharedVaultInvites) {
const result = await this.cancelInviteToSharedVault.execute({
inviteUuid: sharedVaultInvite.id.toString(),
userUuid: sharedVaultInvite.props.userUuid.value,
})
if (result.isFailed()) {
return Result.fail(result.getError())
}
}
const sharedVaultDesignatedSurvivor =
await this.sharedVaultUserRepository.findDesignatedSurvivorBySharedVaultUuid(sharedVaultUuid)
if (sharedVaultDesignatedSurvivor) {
const result = await this.transferSharedVault.execute({
sharedVaultUid: sharedVaultUuid.value,
fromUserUuid: originatorUuid.value,
toUserUuid: sharedVaultDesignatedSurvivor.props.userUuid.value,
})
if (result.isFailed()) {
return Result.fail(result.getError())
}
const removingOwnerFromSharedVaultResult = await this.removeUserFromSharedVault.execute({
originatorUuid: originatorUuid.value,
sharedVaultUuid: sharedVaultUuid.value,
userUuid: originatorUuid.value,
forceRemoveOwner: true,
})
if (removingOwnerFromSharedVaultResult.isFailed()) {
return Result.fail(removingOwnerFromSharedVaultResult.getError())
}
return Result.ok({ status: 'transitioned' })
}
const sharedVaultUsers = await this.sharedVaultUserRepository.findBySharedVaultUuid(sharedVaultUuid) const sharedVaultUsers = await this.sharedVaultUserRepository.findBySharedVaultUuid(sharedVaultUuid)
for (const sharedVaultUser of sharedVaultUsers) { for (const sharedVaultUser of sharedVaultUsers) {
const result = await this.removeUserFromSharedVault.execute({ const result = await this.removeUserFromSharedVault.execute({
@@ -56,26 +96,15 @@ export class DeleteSharedVault implements UseCaseInterface<void> {
} }
} }
const sharedVaultInvites = await this.sharedVaultInviteRepository.findBySharedVaultUuid(sharedVaultUuid)
for (const sharedVaultInvite of sharedVaultInvites) {
const result = await this.declineInviteToSharedVault.execute({
inviteUuid: sharedVaultInvite.id.toString(),
userUuid: sharedVaultInvite.props.userUuid.value,
})
if (result.isFailed()) {
return Result.fail(result.getError())
}
}
await this.sharedVaultRepository.remove(sharedVault) await this.sharedVaultRepository.remove(sharedVault)
await this.domainEventPublisher.publish( await this.domainEventPublisher.publish(
this.domainEventFactory.createSharedVaultRemovedEvent({ this.domainEventFactory.createSharedVaultRemovedEvent({
sharedVaultUuid: sharedVaultUuid.value, sharedVaultUuid: sharedVaultUuid.value,
vaultOwnerUuid: sharedVault.props.userUuid.value,
}), }),
) )
return Result.ok() return Result.ok({ status: 'deleted' })
} }
} }

Some files were not shown because too many files have changed in this diff Show More