mirror of
https://github.com/standardnotes/server
synced 2026-05-10 00:57:19 -04:00
Compare commits
66 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2d9b3578b6 | |||
| 3d5e747590 | |||
| 94467747ac | |||
| cebab59a02 | |||
| 09e1a892ca | |||
| 7b1eec21e5 | |||
| a58262d584 | |||
| a8f03e157b | |||
| a401962bcd | |||
| 9759814f63 | |||
| c7cf53722c | |||
| 8cb33dc906 | |||
| 1d73e4f072 | |||
| 0a0f82ea3d | |||
| f9b1f40ddf | |||
| 0562b0a621 | |||
| 15ed1fd789 | |||
| 5001496c7b | |||
| 0a1080ce2a | |||
| 4802d7e876 | |||
| bcd95cdbe9 | |||
| d50c4440c2 | |||
| 921c30f641 | |||
| 22540ee834 | |||
| 4f4443a882 | |||
| 80dbacf933 | |||
| dc77ff3e45 | |||
| 6515dcf487 | |||
| d0fd6b98df | |||
| 345efacb44 | |||
| d0dba1b66d | |||
| da119af8b2 | |||
| a5da42bddd | |||
| 12767237d2 | |||
| 230c96dcf1 | |||
| e2696fcd1a | |||
| a621cf1e3b | |||
| db35b9fcab | |||
| 880db1038a | |||
| 414b090efb | |||
| 41e2136bc0 | |||
| 378ecedfcc | |||
| 06d4200909 | |||
| 22a8cc90f1 | |||
| 5cf84e31b0 | |||
| 31e7aaf253 | |||
| 8ec3d37c18 | |||
| 857c6af946 | |||
| de081fe786 | |||
| 0aeeb2d1cf | |||
| e589029722 | |||
| b265a39b63 | |||
| ed5cfd86db | |||
| a1a3e9f586 | |||
| a40b17b141 | |||
| 18181ed9df | |||
| 0ae028db73 | |||
| 79971be672 | |||
| e4fcd738c3 | |||
| 6827e5e218 | |||
| b91cd7e232 | |||
| 735d89cdaa | |||
| e8db412bc2 | |||
| bf855bb26e | |||
| 41cd377145 | |||
| d798864caf |
@@ -33,17 +33,12 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
python-version: '3.11'
|
python-version: '3.11'
|
||||||
|
|
||||||
- name: Create Bundle Dir
|
|
||||||
id: bundle-dir
|
|
||||||
run: echo "temp_dir=$(mktemp -d -t ${{ inputs.service_name }}-${{ github.sha }}-XXXXXXX)" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Cache build
|
- name: Cache build
|
||||||
id: cache-build
|
id: cache-build
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v3
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
packages/**/dist
|
packages/**/dist
|
||||||
${{ steps.bundle-dir.outputs.temp_dir }}
|
|
||||||
key: ${{ runner.os }}-${{ inputs.service_name }}-build-${{ github.sha }}
|
key: ${{ runner.os }}-${{ inputs.service_name }}-build-${{ github.sha }}
|
||||||
|
|
||||||
- name: Set up Node
|
- name: Set up Node
|
||||||
@@ -57,11 +52,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
if: steps.cache-build.outputs.cache-hit != 'true'
|
if: steps.cache-build.outputs.cache-hit != 'true'
|
||||||
run: yarn build ${{ inputs.package_path }}
|
run: yarn build
|
||||||
|
|
||||||
- name: Bundle
|
|
||||||
if: steps.cache-build.outputs.cache-hit != 'true'
|
|
||||||
run: yarn workspace ${{ inputs.workspace_name }} bundle --no-compress --output-directory ${{ steps.bundle-dir.outputs.temp_dir }}
|
|
||||||
|
|
||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
uses: docker/login-action@v2
|
uses: docker/login-action@v2
|
||||||
@@ -93,8 +84,8 @@ jobs:
|
|||||||
uses: docker/build-push-action@v4
|
uses: docker/build-push-action@v4
|
||||||
with:
|
with:
|
||||||
builder: ${{ steps.buildx.outputs.name }}
|
builder: ${{ steps.buildx.outputs.name }}
|
||||||
context: ${{ steps.bundle-dir.outputs.temp_dir }}
|
context: .
|
||||||
file: ${{ steps.bundle-dir.outputs.temp_dir }}/${{ inputs.package_path }}/Dockerfile
|
file: ${{ inputs.package_path }}/Dockerfile
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
push: true
|
push: true
|
||||||
tags: |
|
tags: |
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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],\
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|
||||||
#########
|
#########
|
||||||
|
|||||||
@@ -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
-2
@@ -20,8 +20,8 @@
|
|||||||
"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 build packages/home-server && PORT=3123 yarn workspace @standardnotes/home-server start",
|
"e2e": "yarn workspace @standardnotes/home-server run build && PORT=3123 yarn workspace @standardnotes/home-server start",
|
||||||
"start": "yarn build packages/home-server && yarn workspace @standardnotes/home-server start"
|
"start": "yarn workspace @standardnotes/home-server run build && yarn workspace @standardnotes/home-server start"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@commitlint/cli": "^17.0.2",
|
"@commitlint/cli": "^17.0.2",
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|
||||||
|
## [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)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.26.19](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.26.18...@standardnotes/analytics@2.26.19) (2023-09-20)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.26.18](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.26.17...@standardnotes/analytics@2.26.18) (2023-09-20)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.26.17](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.26.16...@standardnotes/analytics@2.26.17) (2023-09-19)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
## [2.26.16](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.26.15...@standardnotes/analytics@2.26.16) (2023-09-18)
|
## [2.26.16](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.26.15...@standardnotes/analytics@2.26.16) (2023-09-18)
|
||||||
|
|
||||||
**Note:** Version bump only for package @standardnotes/analytics
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/analytics",
|
"name": "@standardnotes/analytics",
|
||||||
"version": "2.26.16",
|
"version": "2.26.23",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0 <21.0.0"
|
"node": ">=18.0.0 <21.0.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,6 +3,36 @@
|
|||||||
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.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)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add designating a survivor in shared vault ([#841](https://github.com/standardnotes/api-gateway/issues/841)) ([230c96d](https://github.com/standardnotes/api-gateway/commit/230c96dcf1d8faed9ce8fe288549226da70317e6))
|
||||||
|
|
||||||
|
## [1.74.17](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.74.16...@standardnotes/api-gateway@1.74.17) (2023-09-20)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||||
|
|
||||||
|
## [1.74.16](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.74.15...@standardnotes/api-gateway@1.74.16) (2023-09-20)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||||
|
|
||||||
|
## [1.74.15](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.74.14...@standardnotes/api-gateway@1.74.15) (2023-09-19)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||||
|
|
||||||
## [1.74.14](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.74.13...@standardnotes/api-gateway@1.74.14) (2023-09-18)
|
## [1.74.14](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.74.13...@standardnotes/api-gateway@1.74.14) (2023-09-18)
|
||||||
|
|
||||||
**Note:** Version bump only for package @standardnotes/api-gateway
|
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/api-gateway",
|
"name": "@standardnotes/api-gateway",
|
||||||
"version": "1.74.14",
|
"version": "1.75.3",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0 <21.0.0"
|
"node": ">=18.0.0 <21.0.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Request, Response } from 'express'
|
import { Request, Response } from 'express'
|
||||||
import { inject } from 'inversify'
|
import { inject } from 'inversify'
|
||||||
import { BaseHttpController, controller, httpDelete, httpGet } from 'inversify-express-utils'
|
import { BaseHttpController, controller, httpDelete, httpGet, httpPost } from 'inversify-express-utils'
|
||||||
import { TYPES } from '../../Bootstrap/Types'
|
import { TYPES } from '../../Bootstrap/Types'
|
||||||
import { ServiceProxyInterface } from '../../Service/Http/ServiceProxyInterface'
|
import { ServiceProxyInterface } from '../../Service/Http/ServiceProxyInterface'
|
||||||
import { EndpointResolverInterface } from '../../Service/Resolver/EndpointResolverInterface'
|
import { EndpointResolverInterface } from '../../Service/Resolver/EndpointResolverInterface'
|
||||||
@@ -42,4 +42,19 @@ export class SharedVaultUsersController extends BaseHttpController {
|
|||||||
request.body,
|
request.body,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@httpPost('/:userUuid/designate-survivor')
|
||||||
|
async designateSurvivor(request: Request, response: Response): Promise<void> {
|
||||||
|
await this.httpService.callSyncingServer(
|
||||||
|
request,
|
||||||
|
response,
|
||||||
|
this.endpointResolver.resolveEndpointOrMethodIdentifier(
|
||||||
|
'POST',
|
||||||
|
'shared-vaults/:sharedVaultUuid/users/:userUuid/designate-survivor',
|
||||||
|
request.params.sharedVaultUuid,
|
||||||
|
request.params.userUuid,
|
||||||
|
),
|
||||||
|
request.body,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -89,6 +89,10 @@ export class EndpointResolver implements EndpointResolverInterface {
|
|||||||
// Shared Vault Users Controller
|
// Shared Vault Users Controller
|
||||||
['[GET]:shared-vaults/:sharedVaultUuid/users', 'sync.shared-vault-users.get-users'],
|
['[GET]:shared-vaults/:sharedVaultUuid/users', 'sync.shared-vault-users.get-users'],
|
||||||
['[DELETE]:shared-vaults/:sharedVaultUuid/users/:userUuid', 'sync.shared-vault-users.remove-user'],
|
['[DELETE]:shared-vaults/:sharedVaultUuid/users/:userUuid', 'sync.shared-vault-users.remove-user'],
|
||||||
|
[
|
||||||
|
'[POST]:shared-vaults/:sharedVaultUuid/users/:userUuid/designate-survivor',
|
||||||
|
'sync.shared-vault-users.designate-survivor',
|
||||||
|
],
|
||||||
])
|
])
|
||||||
|
|
||||||
resolveEndpointOrMethodIdentifier(method: string, endpoint: string, ...params: string[]): string {
|
resolveEndpointOrMethodIdentifier(method: string, endpoint: string, ...params: string[]): string {
|
||||||
|
|||||||
@@ -3,6 +3,64 @@
|
|||||||
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.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)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* remove user from all shared vaults upon account deletion ([#843](https://github.com/standardnotes/server/issues/843)) ([dc77ff3](https://github.com/standardnotes/server/commit/dc77ff3e45983d231bc9c132802428e77b4be431))
|
||||||
|
|
||||||
|
# [1.144.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.143.9...@standardnotes/auth-server@1.144.0) (2023-09-21)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add designating a survivor in shared vault ([#841](https://github.com/standardnotes/server/issues/841)) ([230c96d](https://github.com/standardnotes/server/commit/230c96dcf1d8faed9ce8fe288549226da70317e6))
|
||||||
|
|
||||||
|
## [1.143.9](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.143.8...@standardnotes/auth-server@1.143.9) (2023-09-20)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/auth-server
|
||||||
|
|
||||||
|
## [1.143.8](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.143.7...@standardnotes/auth-server@1.143.8) (2023-09-20)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/auth-server
|
||||||
|
|
||||||
|
## [1.143.7](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.143.6...@standardnotes/auth-server@1.143.7) (2023-09-19)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/auth-server
|
||||||
|
|
||||||
|
## [1.143.6](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.143.4...@standardnotes/auth-server@1.143.6) (2023-09-19)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **auth:** bump version ([b91cd7e](https://github.com/standardnotes/server/commit/b91cd7e2329d68026665f61d9bd17ba918c57563))
|
||||||
|
|
||||||
|
## [1.143.4](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.143.3...@standardnotes/auth-server@1.143.4) (2023-09-19)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **auth:** add debug logs to transition ([bf855bb](https://github.com/standardnotes/server/commit/bf855bb26e8a3618113bd2f5801c260782566815))
|
||||||
|
|
||||||
## [1.143.3](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.143.2...@standardnotes/auth-server@1.143.3) (2023-09-18)
|
## [1.143.3](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.143.2...@standardnotes/auth-server@1.143.3) (2023-09-18)
|
||||||
|
|
||||||
**Note:** Version bump only for package @standardnotes/auth-server
|
**Note:** Version bump only for package @standardnotes/auth-server
|
||||||
|
|||||||
@@ -53,7 +53,10 @@ const requestTransition = async (
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let wasTransitionRequested = false
|
||||||
|
|
||||||
if (itemsTransitionStatus?.value !== TransitionStatus.STATUSES.Verified) {
|
if (itemsTransitionStatus?.value !== TransitionStatus.STATUSES.Verified) {
|
||||||
|
wasTransitionRequested = true
|
||||||
await transitionStatusRepository.remove(user.uuid, 'items')
|
await transitionStatusRepository.remove(user.uuid, 'items')
|
||||||
|
|
||||||
await domainEventPublisher.publish(
|
await domainEventPublisher.publish(
|
||||||
@@ -66,6 +69,7 @@ const requestTransition = async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (revisionsTransitionStatus?.value !== TransitionStatus.STATUSES.Verified) {
|
if (revisionsTransitionStatus?.value !== TransitionStatus.STATUSES.Verified) {
|
||||||
|
wasTransitionRequested = true
|
||||||
await transitionStatusRepository.remove(user.uuid, 'revisions')
|
await transitionStatusRepository.remove(user.uuid, 'revisions')
|
||||||
|
|
||||||
await domainEventPublisher.publish(
|
await domainEventPublisher.publish(
|
||||||
@@ -78,6 +82,12 @@ const requestTransition = async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
usersTriggered += 1
|
usersTriggered += 1
|
||||||
|
|
||||||
|
if (wasTransitionRequested) {
|
||||||
|
logger.info(
|
||||||
|
`[TRANSITION ${timestamp}] Transition requested for user ${user.uuid} - items status: ${itemsTransitionStatus?.value}, revisions status: ${revisionsTransitionStatus?.value}, has transition role: ${userHasTransitionRole}`,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
|
|||||||
Binary file not shown.
@@ -0,0 +1,15 @@
|
|||||||
|
import { MigrationInterface, QueryRunner } from 'typeorm'
|
||||||
|
|
||||||
|
export class AddDesignatedSurvivor1695283870612 implements MigrationInterface {
|
||||||
|
name = 'AddDesignatedSurvivor1695283870612'
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(
|
||||||
|
'ALTER TABLE `auth_shared_vault_users` ADD `is_designated_survivor` tinyint NOT NULL DEFAULT 0',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query('ALTER TABLE `auth_shared_vault_users` DROP COLUMN `is_designated_survivor`')
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import { MigrationInterface, QueryRunner } from 'typeorm'
|
||||||
|
|
||||||
|
export class AddDesignatedSurvivor1695283961201 implements MigrationInterface {
|
||||||
|
name = 'AddDesignatedSurvivor1695283961201'
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query('DROP INDEX "user_uuid_on_auth_shared_vault_users"')
|
||||||
|
await queryRunner.query('DROP INDEX "shared_vault_uuid_on_auth_shared_vault_users"')
|
||||||
|
await queryRunner.query(
|
||||||
|
'CREATE TABLE "temporary_auth_shared_vault_users" ("uuid" varchar PRIMARY KEY NOT NULL, "shared_vault_uuid" varchar(36) NOT NULL, "user_uuid" varchar(36) NOT NULL, "permission" varchar(24) NOT NULL, "created_at_timestamp" bigint NOT NULL, "updated_at_timestamp" bigint NOT NULL, "is_designated_survivor" boolean NOT NULL DEFAULT (0))',
|
||||||
|
)
|
||||||
|
await queryRunner.query(
|
||||||
|
'INSERT INTO "temporary_auth_shared_vault_users"("uuid", "shared_vault_uuid", "user_uuid", "permission", "created_at_timestamp", "updated_at_timestamp") SELECT "uuid", "shared_vault_uuid", "user_uuid", "permission", "created_at_timestamp", "updated_at_timestamp" FROM "auth_shared_vault_users"',
|
||||||
|
)
|
||||||
|
await queryRunner.query('DROP TABLE "auth_shared_vault_users"')
|
||||||
|
await queryRunner.query('ALTER TABLE "temporary_auth_shared_vault_users" RENAME TO "auth_shared_vault_users"')
|
||||||
|
await queryRunner.query(
|
||||||
|
'CREATE INDEX "user_uuid_on_auth_shared_vault_users" ON "auth_shared_vault_users" ("user_uuid") ',
|
||||||
|
)
|
||||||
|
await queryRunner.query(
|
||||||
|
'CREATE INDEX "shared_vault_uuid_on_auth_shared_vault_users" ON "auth_shared_vault_users" ("shared_vault_uuid") ',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query('DROP INDEX "shared_vault_uuid_on_auth_shared_vault_users"')
|
||||||
|
await queryRunner.query('DROP INDEX "user_uuid_on_auth_shared_vault_users"')
|
||||||
|
await queryRunner.query('ALTER TABLE "auth_shared_vault_users" RENAME TO "temporary_auth_shared_vault_users"')
|
||||||
|
await queryRunner.query(
|
||||||
|
'CREATE TABLE "auth_shared_vault_users" ("uuid" varchar PRIMARY KEY NOT NULL, "shared_vault_uuid" varchar(36) NOT NULL, "user_uuid" varchar(36) NOT NULL, "permission" varchar(24) NOT NULL, "created_at_timestamp" bigint NOT NULL, "updated_at_timestamp" bigint NOT NULL)',
|
||||||
|
)
|
||||||
|
await queryRunner.query(
|
||||||
|
'INSERT INTO "auth_shared_vault_users"("uuid", "shared_vault_uuid", "user_uuid", "permission", "created_at_timestamp", "updated_at_timestamp") SELECT "uuid", "shared_vault_uuid", "user_uuid", "permission", "created_at_timestamp", "updated_at_timestamp" FROM "temporary_auth_shared_vault_users"',
|
||||||
|
)
|
||||||
|
await queryRunner.query('DROP TABLE "temporary_auth_shared_vault_users"')
|
||||||
|
await queryRunner.query(
|
||||||
|
'CREATE INDEX "shared_vault_uuid_on_auth_shared_vault_users" ON "auth_shared_vault_users" ("shared_vault_uuid") ',
|
||||||
|
)
|
||||||
|
await queryRunner.query(
|
||||||
|
'CREATE INDEX "user_uuid_on_auth_shared_vault_users" ON "auth_shared_vault_users" ("user_uuid") ',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/auth-server",
|
"name": "@standardnotes/auth-server",
|
||||||
"version": "1.143.3",
|
"version": "1.146.3",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0 <21.0.0"
|
"node": ">=18.0.0 <21.0.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -271,6 +271,8 @@ import { AddSharedVaultUser } from '../Domain/UseCase/AddSharedVaultUser/AddShar
|
|||||||
import { RemoveSharedVaultUser } from '../Domain/UseCase/RemoveSharedVaultUser/RemoveSharedVaultUser'
|
import { RemoveSharedVaultUser } from '../Domain/UseCase/RemoveSharedVaultUser/RemoveSharedVaultUser'
|
||||||
import { UserAddedToSharedVaultEventHandler } from '../Domain/Handler/UserAddedToSharedVaultEventHandler'
|
import { UserAddedToSharedVaultEventHandler } from '../Domain/Handler/UserAddedToSharedVaultEventHandler'
|
||||||
import { UserRemovedFromSharedVaultEventHandler } from '../Domain/Handler/UserRemovedFromSharedVaultEventHandler'
|
import { UserRemovedFromSharedVaultEventHandler } from '../Domain/Handler/UserRemovedFromSharedVaultEventHandler'
|
||||||
|
import { DesignateSurvivor } from '../Domain/UseCase/DesignateSurvivor/DesignateSurvivor'
|
||||||
|
import { UserDesignatedAsSurvivorInSharedVaultEventHandler } from '../Domain/Handler/UserDesignatedAsSurvivorInSharedVaultEventHandler'
|
||||||
|
|
||||||
export class ContainerConfigLoader {
|
export class ContainerConfigLoader {
|
||||||
constructor(private mode: 'server' | 'worker' = 'server') {}
|
constructor(private mode: 'server' | 'worker' = 'server') {}
|
||||||
@@ -593,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
|
||||||
@@ -957,6 +956,14 @@ export class ContainerConfigLoader {
|
|||||||
container.get<SharedVaultUserRepositoryInterface>(TYPES.Auth_SharedVaultUserRepository),
|
container.get<SharedVaultUserRepositoryInterface>(TYPES.Auth_SharedVaultUserRepository),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
container
|
||||||
|
.bind<DesignateSurvivor>(TYPES.Auth_DesignateSurvivor)
|
||||||
|
.toConstantValue(
|
||||||
|
new DesignateSurvivor(
|
||||||
|
container.get<SharedVaultUserRepositoryInterface>(TYPES.Auth_SharedVaultUserRepository),
|
||||||
|
container.get<TimerInterface>(TYPES.Auth_Timer),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
// Controller
|
// Controller
|
||||||
container
|
container
|
||||||
@@ -998,7 +1005,16 @@ export class ContainerConfigLoader {
|
|||||||
container.bind<UserRegisteredEventHandler>(TYPES.Auth_UserRegisteredEventHandler).to(UserRegisteredEventHandler)
|
container.bind<UserRegisteredEventHandler>(TYPES.Auth_UserRegisteredEventHandler).to(UserRegisteredEventHandler)
|
||||||
container
|
container
|
||||||
.bind<AccountDeletionRequestedEventHandler>(TYPES.Auth_AccountDeletionRequestedEventHandler)
|
.bind<AccountDeletionRequestedEventHandler>(TYPES.Auth_AccountDeletionRequestedEventHandler)
|
||||||
.to(AccountDeletionRequestedEventHandler)
|
.toConstantValue(
|
||||||
|
new AccountDeletionRequestedEventHandler(
|
||||||
|
container.get<UserRepositoryInterface>(TYPES.Auth_UserRepository),
|
||||||
|
container.get<SessionRepositoryInterface>(TYPES.Auth_SessionRepository),
|
||||||
|
container.get<EphemeralSessionRepositoryInterface>(TYPES.Auth_EphemeralSessionRepository),
|
||||||
|
container.get<RevokedSessionRepositoryInterface>(TYPES.Auth_RevokedSessionRepository),
|
||||||
|
container.get<RemoveSharedVaultUser>(TYPES.Auth_RemoveSharedVaultUser),
|
||||||
|
container.get<winston.Logger>(TYPES.Auth_Logger),
|
||||||
|
),
|
||||||
|
)
|
||||||
container
|
container
|
||||||
.bind<SubscriptionPurchasedEventHandler>(TYPES.Auth_SubscriptionPurchasedEventHandler)
|
.bind<SubscriptionPurchasedEventHandler>(TYPES.Auth_SubscriptionPurchasedEventHandler)
|
||||||
.to(SubscriptionPurchasedEventHandler)
|
.to(SubscriptionPurchasedEventHandler)
|
||||||
@@ -1122,6 +1138,16 @@ export class ContainerConfigLoader {
|
|||||||
container.get<winston.Logger>(TYPES.Auth_Logger),
|
container.get<winston.Logger>(TYPES.Auth_Logger),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
container
|
||||||
|
.bind<UserDesignatedAsSurvivorInSharedVaultEventHandler>(
|
||||||
|
TYPES.Auth_UserDesignatedAsSurvivorInSharedVaultEventHandler,
|
||||||
|
)
|
||||||
|
.toConstantValue(
|
||||||
|
new UserDesignatedAsSurvivorInSharedVaultEventHandler(
|
||||||
|
container.get<DesignateSurvivor>(TYPES.Auth_DesignateSurvivor),
|
||||||
|
container.get<winston.Logger>(TYPES.Auth_Logger),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
const eventHandlers: Map<string, DomainEventHandlerInterface> = new Map([
|
const eventHandlers: Map<string, DomainEventHandlerInterface> = new Map([
|
||||||
['USER_REGISTERED', container.get(TYPES.Auth_UserRegisteredEventHandler)],
|
['USER_REGISTERED', container.get(TYPES.Auth_UserRegisteredEventHandler)],
|
||||||
@@ -1156,6 +1182,10 @@ export class ContainerConfigLoader {
|
|||||||
['TRANSITION_STATUS_UPDATED', container.get(TYPES.Auth_TransitionStatusUpdatedEventHandler)],
|
['TRANSITION_STATUS_UPDATED', container.get(TYPES.Auth_TransitionStatusUpdatedEventHandler)],
|
||||||
['USER_ADDED_TO_SHARED_VAULT', container.get(TYPES.Auth_UserAddedToSharedVaultEventHandler)],
|
['USER_ADDED_TO_SHARED_VAULT', container.get(TYPES.Auth_UserAddedToSharedVaultEventHandler)],
|
||||||
['USER_REMOVED_FROM_SHARED_VAULT', container.get(TYPES.Auth_UserRemovedFromSharedVaultEventHandler)],
|
['USER_REMOVED_FROM_SHARED_VAULT', container.get(TYPES.Auth_UserRemovedFromSharedVaultEventHandler)],
|
||||||
|
[
|
||||||
|
'USER_DESIGNATED_AS_SURVIVOR_IN_SHARED_VAULT',
|
||||||
|
container.get(TYPES.Auth_UserDesignatedAsSurvivorInSharedVaultEventHandler),
|
||||||
|
],
|
||||||
])
|
])
|
||||||
|
|
||||||
if (isConfiguredForHomeServer) {
|
if (isConfiguredForHomeServer) {
|
||||||
|
|||||||
@@ -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'),
|
||||||
@@ -161,6 +160,7 @@ const TYPES = {
|
|||||||
Auth_UpdateTransitionStatus: Symbol.for('Auth_UpdateTransitionStatus'),
|
Auth_UpdateTransitionStatus: Symbol.for('Auth_UpdateTransitionStatus'),
|
||||||
Auth_AddSharedVaultUser: Symbol.for('Auth_AddSharedVaultUser'),
|
Auth_AddSharedVaultUser: Symbol.for('Auth_AddSharedVaultUser'),
|
||||||
Auth_RemoveSharedVaultUser: Symbol.for('Auth_RemoveSharedVaultUser'),
|
Auth_RemoveSharedVaultUser: Symbol.for('Auth_RemoveSharedVaultUser'),
|
||||||
|
Auth_DesignateSurvivor: Symbol.for('Auth_DesignateSurvivor'),
|
||||||
// Handlers
|
// Handlers
|
||||||
Auth_UserRegisteredEventHandler: Symbol.for('Auth_UserRegisteredEventHandler'),
|
Auth_UserRegisteredEventHandler: Symbol.for('Auth_UserRegisteredEventHandler'),
|
||||||
Auth_AccountDeletionRequestedEventHandler: Symbol.for('Auth_AccountDeletionRequestedEventHandler'),
|
Auth_AccountDeletionRequestedEventHandler: Symbol.for('Auth_AccountDeletionRequestedEventHandler'),
|
||||||
@@ -192,6 +192,9 @@ const TYPES = {
|
|||||||
Auth_TransitionStatusUpdatedEventHandler: Symbol.for('Auth_TransitionStatusUpdatedEventHandler'),
|
Auth_TransitionStatusUpdatedEventHandler: Symbol.for('Auth_TransitionStatusUpdatedEventHandler'),
|
||||||
Auth_UserAddedToSharedVaultEventHandler: Symbol.for('Auth_UserAddedToSharedVaultEventHandler'),
|
Auth_UserAddedToSharedVaultEventHandler: Symbol.for('Auth_UserAddedToSharedVaultEventHandler'),
|
||||||
Auth_UserRemovedFromSharedVaultEventHandler: Symbol.for('Auth_UserRemovedFromSharedVaultEventHandler'),
|
Auth_UserRemovedFromSharedVaultEventHandler: Symbol.for('Auth_UserRemovedFromSharedVaultEventHandler'),
|
||||||
|
Auth_UserDesignatedAsSurvivorInSharedVaultEventHandler: Symbol.for(
|
||||||
|
'Auth_UserDesignatedAsSurvivorInSharedVaultEventHandler',
|
||||||
|
),
|
||||||
// Services
|
// Services
|
||||||
Auth_DeviceDetector: Symbol.for('Auth_DeviceDetector'),
|
Auth_DeviceDetector: Symbol.for('Auth_DeviceDetector'),
|
||||||
Auth_SessionService: Symbol.for('Auth_SessionService'),
|
Auth_SessionService: Symbol.for('Auth_SessionService'),
|
||||||
|
|||||||
@@ -1,114 +0,0 @@
|
|||||||
import 'reflect-metadata'
|
|
||||||
|
|
||||||
import { AccountDeletionRequestedEvent } from '@standardnotes/domain-events'
|
|
||||||
import { Logger } from 'winston'
|
|
||||||
import { EphemeralSession } from '../Session/EphemeralSession'
|
|
||||||
import { EphemeralSessionRepositoryInterface } from '../Session/EphemeralSessionRepositoryInterface'
|
|
||||||
import { RevokedSession } from '../Session/RevokedSession'
|
|
||||||
import { RevokedSessionRepositoryInterface } from '../Session/RevokedSessionRepositoryInterface'
|
|
||||||
import { Session } from '../Session/Session'
|
|
||||||
import { SessionRepositoryInterface } from '../Session/SessionRepositoryInterface'
|
|
||||||
import { User } from '../User/User'
|
|
||||||
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
|
|
||||||
import { AccountDeletionRequestedEventHandler } from './AccountDeletionRequestedEventHandler'
|
|
||||||
|
|
||||||
describe('AccountDeletionRequestedEventHandler', () => {
|
|
||||||
let userRepository: UserRepositoryInterface
|
|
||||||
let sessionRepository: SessionRepositoryInterface
|
|
||||||
let ephemeralSessionRepository: EphemeralSessionRepositoryInterface
|
|
||||||
let revokedSessionRepository: RevokedSessionRepositoryInterface
|
|
||||||
let logger: Logger
|
|
||||||
let session: Session
|
|
||||||
let ephemeralSession: EphemeralSession
|
|
||||||
let revokedSession: RevokedSession
|
|
||||||
let user: User
|
|
||||||
let event: AccountDeletionRequestedEvent
|
|
||||||
|
|
||||||
const createHandler = () =>
|
|
||||||
new AccountDeletionRequestedEventHandler(
|
|
||||||
userRepository,
|
|
||||||
sessionRepository,
|
|
||||||
ephemeralSessionRepository,
|
|
||||||
revokedSessionRepository,
|
|
||||||
logger,
|
|
||||||
)
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
user = {} as jest.Mocked<User>
|
|
||||||
|
|
||||||
userRepository = {} as jest.Mocked<UserRepositoryInterface>
|
|
||||||
userRepository.findOneByUuid = jest.fn().mockReturnValue(user)
|
|
||||||
userRepository.remove = jest.fn()
|
|
||||||
|
|
||||||
session = {
|
|
||||||
uuid: '1-2-3',
|
|
||||||
} as jest.Mocked<Session>
|
|
||||||
|
|
||||||
sessionRepository = {} as jest.Mocked<SessionRepositoryInterface>
|
|
||||||
sessionRepository.findAllByUserUuid = jest.fn().mockReturnValue([session])
|
|
||||||
sessionRepository.remove = jest.fn()
|
|
||||||
|
|
||||||
ephemeralSession = {
|
|
||||||
uuid: '2-3-4',
|
|
||||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
|
||||||
} as jest.Mocked<EphemeralSession>
|
|
||||||
|
|
||||||
ephemeralSessionRepository = {} as jest.Mocked<EphemeralSessionRepositoryInterface>
|
|
||||||
ephemeralSessionRepository.findAllByUserUuid = jest.fn().mockReturnValue([ephemeralSession])
|
|
||||||
ephemeralSessionRepository.deleteOne = jest.fn()
|
|
||||||
|
|
||||||
revokedSession = {
|
|
||||||
uuid: '3-4-5',
|
|
||||||
} as jest.Mocked<RevokedSession>
|
|
||||||
|
|
||||||
revokedSessionRepository = {} as jest.Mocked<RevokedSessionRepositoryInterface>
|
|
||||||
revokedSessionRepository.findAllByUserUuid = jest.fn().mockReturnValue([revokedSession])
|
|
||||||
revokedSessionRepository.remove = jest.fn()
|
|
||||||
|
|
||||||
event = {} as jest.Mocked<AccountDeletionRequestedEvent>
|
|
||||||
event.createdAt = new Date(1)
|
|
||||||
event.payload = {
|
|
||||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
|
||||||
userCreatedAtTimestamp: 1,
|
|
||||||
regularSubscriptionUuid: '2-3-4',
|
|
||||||
roleNames: ['CORE_USER'],
|
|
||||||
}
|
|
||||||
|
|
||||||
logger = {} as jest.Mocked<Logger>
|
|
||||||
logger.info = jest.fn()
|
|
||||||
logger.warn = jest.fn()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should remove a user', async () => {
|
|
||||||
await createHandler().handle(event)
|
|
||||||
|
|
||||||
expect(userRepository.remove).toHaveBeenCalledWith(user)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should not remove a user with invalid uuid', async () => {
|
|
||||||
event.payload.userUuid = 'invalid'
|
|
||||||
|
|
||||||
await createHandler().handle(event)
|
|
||||||
|
|
||||||
expect(userRepository.remove).not.toHaveBeenCalled()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should not remove a user if one does not exist', async () => {
|
|
||||||
userRepository.findOneByUuid = jest.fn().mockReturnValue(null)
|
|
||||||
|
|
||||||
await createHandler().handle(event)
|
|
||||||
|
|
||||||
expect(userRepository.remove).not.toHaveBeenCalled()
|
|
||||||
expect(sessionRepository.remove).not.toHaveBeenCalled()
|
|
||||||
expect(revokedSessionRepository.remove).not.toHaveBeenCalled()
|
|
||||||
expect(ephemeralSessionRepository.deleteOne).not.toHaveBeenCalled()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should remove all user sessions', async () => {
|
|
||||||
await createHandler().handle(event)
|
|
||||||
|
|
||||||
expect(sessionRepository.remove).toHaveBeenCalledWith(session)
|
|
||||||
expect(revokedSessionRepository.remove).toHaveBeenCalledWith(revokedSession)
|
|
||||||
expect(ephemeralSessionRepository.deleteOne).toHaveBeenCalledWith('2-3-4', '00000000-0000-0000-0000-000000000000')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@@ -1,22 +1,21 @@
|
|||||||
import { AccountDeletionRequestedEvent, DomainEventHandlerInterface } from '@standardnotes/domain-events'
|
import { AccountDeletionRequestedEvent, DomainEventHandlerInterface } from '@standardnotes/domain-events'
|
||||||
import { inject, injectable } from 'inversify'
|
import { Uuid } from '@standardnotes/domain-core'
|
||||||
import { Logger } from 'winston'
|
import { Logger } from 'winston'
|
||||||
import TYPES from '../../Bootstrap/Types'
|
|
||||||
import { EphemeralSessionRepositoryInterface } from '../Session/EphemeralSessionRepositoryInterface'
|
import { EphemeralSessionRepositoryInterface } from '../Session/EphemeralSessionRepositoryInterface'
|
||||||
import { RevokedSessionRepositoryInterface } from '../Session/RevokedSessionRepositoryInterface'
|
import { RevokedSessionRepositoryInterface } from '../Session/RevokedSessionRepositoryInterface'
|
||||||
import { SessionRepositoryInterface } from '../Session/SessionRepositoryInterface'
|
import { SessionRepositoryInterface } from '../Session/SessionRepositoryInterface'
|
||||||
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
|
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
|
||||||
import { Uuid } from '@standardnotes/domain-core'
|
import { RemoveSharedVaultUser } from '../UseCase/RemoveSharedVaultUser/RemoveSharedVaultUser'
|
||||||
|
|
||||||
@injectable()
|
|
||||||
export class AccountDeletionRequestedEventHandler implements DomainEventHandlerInterface {
|
export class AccountDeletionRequestedEventHandler implements DomainEventHandlerInterface {
|
||||||
constructor(
|
constructor(
|
||||||
@inject(TYPES.Auth_UserRepository) private userRepository: UserRepositoryInterface,
|
private userRepository: UserRepositoryInterface,
|
||||||
@inject(TYPES.Auth_SessionRepository) private sessionRepository: SessionRepositoryInterface,
|
private sessionRepository: SessionRepositoryInterface,
|
||||||
@inject(TYPES.Auth_EphemeralSessionRepository)
|
|
||||||
private ephemeralSessionRepository: EphemeralSessionRepositoryInterface,
|
private ephemeralSessionRepository: EphemeralSessionRepositoryInterface,
|
||||||
@inject(TYPES.Auth_RevokedSessionRepository) private revokedSessionRepository: RevokedSessionRepositoryInterface,
|
private revokedSessionRepository: RevokedSessionRepositoryInterface,
|
||||||
@inject(TYPES.Auth_Logger) private logger: Logger,
|
private removeSharedVaultUser: RemoveSharedVaultUser,
|
||||||
|
private logger: Logger,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async handle(event: AccountDeletionRequestedEvent): Promise<void> {
|
async handle(event: AccountDeletionRequestedEvent): Promise<void> {
|
||||||
@@ -38,6 +37,13 @@ export class AccountDeletionRequestedEventHandler implements DomainEventHandlerI
|
|||||||
|
|
||||||
await this.removeSessions(userUuid.value)
|
await this.removeSessions(userUuid.value)
|
||||||
|
|
||||||
|
const result = await this.removeSharedVaultUser.execute({
|
||||||
|
userUuid: userUuid.value,
|
||||||
|
})
|
||||||
|
if (result.isFailed()) {
|
||||||
|
this.logger.error(`Could not remove shared vault user: ${result.getError()}`)
|
||||||
|
}
|
||||||
|
|
||||||
await this.userRepository.remove(user)
|
await this.userRepository.remove(user)
|
||||||
|
|
||||||
this.logger.info(`Finished account cleanup for user: ${userUuid.value}`)
|
this.logger.info(`Finished account cleanup for user: ${userUuid.value}`)
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { DomainEventHandlerInterface, UserDesignatedAsSurvivorInSharedVaultEvent } from '@standardnotes/domain-events'
|
||||||
|
import { Logger } from 'winston'
|
||||||
|
import { DesignateSurvivor } from '../UseCase/DesignateSurvivor/DesignateSurvivor'
|
||||||
|
|
||||||
|
export class UserDesignatedAsSurvivorInSharedVaultEventHandler implements DomainEventHandlerInterface {
|
||||||
|
constructor(
|
||||||
|
private designateSurvivorUseCase: DesignateSurvivor,
|
||||||
|
private logger: Logger,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async handle(event: UserDesignatedAsSurvivorInSharedVaultEvent): Promise<void> {
|
||||||
|
const result = await this.designateSurvivorUseCase.execute({
|
||||||
|
sharedVaultUuid: event.payload.sharedVaultUuid,
|
||||||
|
userUuid: event.payload.userUuid,
|
||||||
|
timestamp: event.payload.timestamp,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (result.isFailed()) {
|
||||||
|
this.logger.error(
|
||||||
|
`Failed designate survivor for user ${event.payload.userUuid} and shared vault ${
|
||||||
|
event.payload.sharedVaultUuid
|
||||||
|
}: ${result.getError()}`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,6 +10,12 @@ export class UserRemovedFromSharedVaultEventHandler implements DomainEventHandle
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
async handle(event: UserRemovedFromSharedVaultEvent): Promise<void> {
|
async handle(event: UserRemovedFromSharedVaultEvent): Promise<void> {
|
||||||
|
if (!event.payload.sharedVaultUuid) {
|
||||||
|
this.logger.error(`Shared vault uuid is missing from event: ${JSON.stringify(event)}`)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const result = await this.removeSharedVaultUser.execute({
|
const result = await this.removeSharedVaultUser.execute({
|
||||||
userUuid: event.payload.userUuid,
|
userUuid: event.payload.userUuid,
|
||||||
sharedVaultUuid: event.payload.sharedVaultUuid,
|
sharedVaultUuid: event.payload.sharedVaultUuid,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { SharedVaultUser, Uuid } from '@standardnotes/domain-core'
|
|||||||
export interface SharedVaultUserRepositoryInterface {
|
export interface SharedVaultUserRepositoryInterface {
|
||||||
findByUserUuidAndSharedVaultUuid(dto: { userUuid: Uuid; sharedVaultUuid: Uuid }): Promise<SharedVaultUser | null>
|
findByUserUuidAndSharedVaultUuid(dto: { userUuid: Uuid; sharedVaultUuid: Uuid }): Promise<SharedVaultUser | null>
|
||||||
findByUserUuid(userUuid: Uuid): Promise<SharedVaultUser[]>
|
findByUserUuid(userUuid: Uuid): Promise<SharedVaultUser[]>
|
||||||
|
findDesignatedSurvivorBySharedVaultUuid(sharedVaultUuid: Uuid): Promise<SharedVaultUser | null>
|
||||||
save(sharedVaultUser: SharedVaultUser): Promise<void>
|
save(sharedVaultUser: SharedVaultUser): Promise<void>
|
||||||
remove(sharedVault: SharedVaultUser): Promise<void>
|
remove(sharedVault: SharedVaultUser): Promise<void>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ export class AddSharedVaultUser implements UseCaseInterface<void> {
|
|||||||
sharedVaultUuid,
|
sharedVaultUuid,
|
||||||
permission,
|
permission,
|
||||||
timestamps,
|
timestamps,
|
||||||
|
isDesignatedSurvivor: false,
|
||||||
})
|
})
|
||||||
if (sharedVaultUserOrError.isFailed()) {
|
if (sharedVaultUserOrError.isFailed()) {
|
||||||
return Result.fail(sharedVaultUserOrError.getError())
|
return Result.fail(sharedVaultUserOrError.getError())
|
||||||
|
|||||||
+1
@@ -90,6 +90,7 @@ describe('CreateCrossServiceToken', () => {
|
|||||||
sharedVaultUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
sharedVaultUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||||
timestamps: Timestamps.create(123456789, 123456789).getValue(),
|
timestamps: Timestamps.create(123456789, 123456789).getValue(),
|
||||||
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||||
|
isDesignatedSurvivor: false,
|
||||||
}).getValue(),
|
}).getValue(),
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -0,0 +1,156 @@
|
|||||||
|
import { SharedVaultUser, SharedVaultUserPermission, Timestamps, Uuid } from '@standardnotes/domain-core'
|
||||||
|
|
||||||
|
import { DesignateSurvivor } from './DesignateSurvivor'
|
||||||
|
import { TimerInterface } from '@standardnotes/time'
|
||||||
|
import { SharedVaultUserRepositoryInterface } from '../../SharedVault/SharedVaultUserRepositoryInterface'
|
||||||
|
|
||||||
|
describe('DesignateSurvivor', () => {
|
||||||
|
let sharedVaultUserRepository: SharedVaultUserRepositoryInterface
|
||||||
|
let sharedVaultUser: SharedVaultUser
|
||||||
|
let timer: TimerInterface
|
||||||
|
|
||||||
|
const createUseCase = () => new DesignateSurvivor(sharedVaultUserRepository, timer)
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
timer = {} as jest.Mocked<TimerInterface>
|
||||||
|
timer.getTimestampInMicroseconds = jest.fn().mockReturnValue(123)
|
||||||
|
|
||||||
|
sharedVaultUser = SharedVaultUser.create({
|
||||||
|
permission: SharedVaultUserPermission.create(SharedVaultUserPermission.PERMISSIONS.Write).getValue(),
|
||||||
|
sharedVaultUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||||
|
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||||
|
timestamps: Timestamps.create(123, 123).getValue(),
|
||||||
|
isDesignatedSurvivor: false,
|
||||||
|
}).getValue()
|
||||||
|
|
||||||
|
sharedVaultUserRepository = {} as jest.Mocked<SharedVaultUserRepositoryInterface>
|
||||||
|
sharedVaultUserRepository.findDesignatedSurvivorBySharedVaultUuid = jest.fn().mockReturnValue(null)
|
||||||
|
sharedVaultUserRepository.findByUserUuidAndSharedVaultUuid = jest.fn().mockReturnValue(sharedVaultUser)
|
||||||
|
sharedVaultUserRepository.save = jest.fn()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should fail if shared vault uuid is invalid', async () => {
|
||||||
|
const useCase = createUseCase()
|
||||||
|
|
||||||
|
const result = await useCase.execute({
|
||||||
|
sharedVaultUuid: 'invalid',
|
||||||
|
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
|
timestamp: 123,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result.isFailed()).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should fail if user uuid is invalid', async () => {
|
||||||
|
const useCase = createUseCase()
|
||||||
|
|
||||||
|
const result = await useCase.execute({
|
||||||
|
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
|
userUuid: 'invalid',
|
||||||
|
timestamp: 123,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result.isFailed()).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should fail if shared vault user is not found', async () => {
|
||||||
|
sharedVaultUserRepository.findByUserUuidAndSharedVaultUuid = jest.fn().mockReturnValue(null)
|
||||||
|
|
||||||
|
const useCase = createUseCase()
|
||||||
|
|
||||||
|
const result = await useCase.execute({
|
||||||
|
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
|
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
|
timestamp: 123,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result.isFailed()).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should designate a survivor if the user is a member', async () => {
|
||||||
|
sharedVaultUserRepository.findByUserUuidAndSharedVaultUuid = jest.fn().mockReturnValue(sharedVaultUser)
|
||||||
|
|
||||||
|
const useCase = createUseCase()
|
||||||
|
|
||||||
|
const result = await useCase.execute({
|
||||||
|
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
|
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
|
timestamp: 123,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result.isFailed()).toBe(false)
|
||||||
|
expect(sharedVaultUser.props.isDesignatedSurvivor).toBe(true)
|
||||||
|
expect(sharedVaultUserRepository.save).toBeCalledTimes(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should designate a survivor if the user is a member and there is already a survivor', async () => {
|
||||||
|
sharedVaultUserRepository.findDesignatedSurvivorBySharedVaultUuid = jest.fn().mockReturnValue(
|
||||||
|
SharedVaultUser.create({
|
||||||
|
permission: SharedVaultUserPermission.create(SharedVaultUserPermission.PERMISSIONS.Write).getValue(),
|
||||||
|
sharedVaultUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||||
|
userUuid: Uuid.create('00000000-0000-0000-0000-000000000001').getValue(),
|
||||||
|
timestamps: Timestamps.create(123, 123).getValue(),
|
||||||
|
isDesignatedSurvivor: true,
|
||||||
|
}).getValue(),
|
||||||
|
)
|
||||||
|
sharedVaultUserRepository.findByUserUuidAndSharedVaultUuid = jest.fn().mockReturnValue(sharedVaultUser)
|
||||||
|
|
||||||
|
const useCase = createUseCase()
|
||||||
|
|
||||||
|
const result = await useCase.execute({
|
||||||
|
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
|
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
|
timestamp: 123,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result.isFailed()).toBe(false)
|
||||||
|
expect(sharedVaultUser.props.isDesignatedSurvivor).toBe(true)
|
||||||
|
expect(sharedVaultUserRepository.save).toBeCalledTimes(2)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should fail if the timestamp is older than the existing survivor', async () => {
|
||||||
|
sharedVaultUserRepository.findDesignatedSurvivorBySharedVaultUuid = jest.fn().mockReturnValue(
|
||||||
|
SharedVaultUser.create({
|
||||||
|
permission: SharedVaultUserPermission.create(SharedVaultUserPermission.PERMISSIONS.Write).getValue(),
|
||||||
|
sharedVaultUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||||
|
userUuid: Uuid.create('00000000-0000-0000-0000-000000000001').getValue(),
|
||||||
|
timestamps: Timestamps.create(123, 123).getValue(),
|
||||||
|
isDesignatedSurvivor: true,
|
||||||
|
}).getValue(),
|
||||||
|
)
|
||||||
|
sharedVaultUserRepository.findByUserUuidAndSharedVaultUuid = jest.fn().mockReturnValue(sharedVaultUser)
|
||||||
|
|
||||||
|
const useCase = createUseCase()
|
||||||
|
|
||||||
|
const result = await useCase.execute({
|
||||||
|
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
|
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
|
timestamp: 122,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result.isFailed()).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should do nothing if the user is already a survivor', async () => {
|
||||||
|
sharedVaultUserRepository.findDesignatedSurvivorBySharedVaultUuid = jest.fn().mockReturnValue(
|
||||||
|
SharedVaultUser.create({
|
||||||
|
permission: SharedVaultUserPermission.create(SharedVaultUserPermission.PERMISSIONS.Write).getValue(),
|
||||||
|
sharedVaultUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||||
|
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||||
|
timestamps: Timestamps.create(123, 123).getValue(),
|
||||||
|
isDesignatedSurvivor: true,
|
||||||
|
}).getValue(),
|
||||||
|
)
|
||||||
|
sharedVaultUserRepository.findByUserUuidAndSharedVaultUuid = jest.fn().mockReturnValue(sharedVaultUser)
|
||||||
|
|
||||||
|
const useCase = createUseCase()
|
||||||
|
|
||||||
|
const result = await useCase.execute({
|
||||||
|
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
|
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
|
timestamp: 200,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result.isFailed()).toBe(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
import { TimerInterface } from '@standardnotes/time'
|
||||||
|
import { Result, Timestamps, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
|
||||||
|
|
||||||
|
import { DesignateSurvivorDTO } from './DesignateSurvivorDTO'
|
||||||
|
import { SharedVaultUserRepositoryInterface } from '../../SharedVault/SharedVaultUserRepositoryInterface'
|
||||||
|
|
||||||
|
export class DesignateSurvivor implements UseCaseInterface<void> {
|
||||||
|
constructor(
|
||||||
|
private sharedVaultUserRepository: SharedVaultUserRepositoryInterface,
|
||||||
|
private timer: TimerInterface,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async execute(dto: DesignateSurvivorDTO): Promise<Result<void>> {
|
||||||
|
const sharedVaultUuidOrError = Uuid.create(dto.sharedVaultUuid)
|
||||||
|
if (sharedVaultUuidOrError.isFailed()) {
|
||||||
|
return Result.fail(sharedVaultUuidOrError.getError())
|
||||||
|
}
|
||||||
|
const sharedVaultUuid = sharedVaultUuidOrError.getValue()
|
||||||
|
|
||||||
|
const userUuidOrError = Uuid.create(dto.userUuid)
|
||||||
|
if (userUuidOrError.isFailed()) {
|
||||||
|
return Result.fail(userUuidOrError.getError())
|
||||||
|
}
|
||||||
|
const userUuid = userUuidOrError.getValue()
|
||||||
|
|
||||||
|
const existingSurvivor =
|
||||||
|
await this.sharedVaultUserRepository.findDesignatedSurvivorBySharedVaultUuid(sharedVaultUuid)
|
||||||
|
|
||||||
|
if (existingSurvivor) {
|
||||||
|
if (existingSurvivor.props.timestamps.updatedAt > dto.timestamp) {
|
||||||
|
return Result.fail(
|
||||||
|
'Cannot designate survivor to a previous version of the shared vault. Most probably a race condition.',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (existingSurvivor.props.userUuid.value === userUuid.value) {
|
||||||
|
return Result.ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
existingSurvivor.props.isDesignatedSurvivor = false
|
||||||
|
existingSurvivor.props.timestamps = Timestamps.create(
|
||||||
|
existingSurvivor.props.timestamps.createdAt,
|
||||||
|
this.timer.getTimestampInMicroseconds(),
|
||||||
|
).getValue()
|
||||||
|
|
||||||
|
await this.sharedVaultUserRepository.save(existingSurvivor)
|
||||||
|
}
|
||||||
|
|
||||||
|
const toBeDesignatedAsASurvivor = await this.sharedVaultUserRepository.findByUserUuidAndSharedVaultUuid({
|
||||||
|
userUuid,
|
||||||
|
sharedVaultUuid,
|
||||||
|
})
|
||||||
|
if (!toBeDesignatedAsASurvivor) {
|
||||||
|
return Result.fail('User is not a member of the shared vault')
|
||||||
|
}
|
||||||
|
|
||||||
|
toBeDesignatedAsASurvivor.props.isDesignatedSurvivor = true
|
||||||
|
toBeDesignatedAsASurvivor.props.timestamps = Timestamps.create(
|
||||||
|
toBeDesignatedAsASurvivor.props.timestamps.createdAt,
|
||||||
|
this.timer.getTimestampInMicroseconds(),
|
||||||
|
).getValue()
|
||||||
|
|
||||||
|
await this.sharedVaultUserRepository.save(toBeDesignatedAsASurvivor)
|
||||||
|
|
||||||
|
return Result.ok()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
export interface DesignateSurvivorDTO {
|
||||||
|
sharedVaultUuid: string
|
||||||
|
userUuid: string
|
||||||
|
timestamp: number
|
||||||
|
}
|
||||||
@@ -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',
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ describe('RemoveSharedVaultUser', () => {
|
|||||||
sharedVaultUserRepository.findByUserUuidAndSharedVaultUuid = jest
|
sharedVaultUserRepository.findByUserUuidAndSharedVaultUuid = jest
|
||||||
.fn()
|
.fn()
|
||||||
.mockReturnValue({} as jest.Mocked<SharedVaultUser>)
|
.mockReturnValue({} as jest.Mocked<SharedVaultUser>)
|
||||||
|
sharedVaultUserRepository.findByUserUuid = jest.fn().mockReturnValue([{} as jest.Mocked<SharedVaultUser>])
|
||||||
sharedVaultUserRepository.remove = jest.fn()
|
sharedVaultUserRepository.remove = jest.fn()
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -28,6 +29,17 @@ describe('RemoveSharedVaultUser', () => {
|
|||||||
expect(sharedVaultUserRepository.remove).toHaveBeenCalled()
|
expect(sharedVaultUserRepository.remove).toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should remove all shared vault users', async () => {
|
||||||
|
const useCase = createUseCase()
|
||||||
|
|
||||||
|
const result = await useCase.execute({
|
||||||
|
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result.isFailed()).toBeFalsy()
|
||||||
|
expect(sharedVaultUserRepository.remove).toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
it('should fail when user uuid is invalid', async () => {
|
it('should fail when user uuid is invalid', async () => {
|
||||||
const useCase = createUseCase()
|
const useCase = createUseCase()
|
||||||
|
|
||||||
|
|||||||
@@ -13,21 +13,31 @@ export class RemoveSharedVaultUser implements UseCaseInterface<void> {
|
|||||||
}
|
}
|
||||||
const userUuid = userUuidOrError.getValue()
|
const userUuid = userUuidOrError.getValue()
|
||||||
|
|
||||||
const sharedVaultUuidOrError = Uuid.create(dto.sharedVaultUuid)
|
let sharedVaultUuid: Uuid | undefined
|
||||||
if (sharedVaultUuidOrError.isFailed()) {
|
if (dto.sharedVaultUuid !== undefined) {
|
||||||
return Result.fail(sharedVaultUuidOrError.getError())
|
const sharedVaultUuidOrError = Uuid.create(dto.sharedVaultUuid)
|
||||||
}
|
if (sharedVaultUuidOrError.isFailed()) {
|
||||||
const sharedVaultUuid = sharedVaultUuidOrError.getValue()
|
return Result.fail(sharedVaultUuidOrError.getError())
|
||||||
|
}
|
||||||
const sharedVaultUser = await this.sharedVaultUserRepository.findByUserUuidAndSharedVaultUuid({
|
sharedVaultUuid = sharedVaultUuidOrError.getValue()
|
||||||
userUuid,
|
|
||||||
sharedVaultUuid,
|
|
||||||
})
|
|
||||||
if (!sharedVaultUser) {
|
|
||||||
return Result.fail('Shared vault user not found')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.sharedVaultUserRepository.remove(sharedVaultUser)
|
if (sharedVaultUuid) {
|
||||||
|
const sharedVaultUser = await this.sharedVaultUserRepository.findByUserUuidAndSharedVaultUuid({
|
||||||
|
userUuid,
|
||||||
|
sharedVaultUuid,
|
||||||
|
})
|
||||||
|
if (!sharedVaultUser) {
|
||||||
|
return Result.fail('Shared vault user not found')
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.sharedVaultUserRepository.remove(sharedVaultUser)
|
||||||
|
} else {
|
||||||
|
const sharedVaultUsers = await this.sharedVaultUserRepository.findByUserUuid(userUuid)
|
||||||
|
for (const sharedVaultUser of sharedVaultUsers) {
|
||||||
|
await this.sharedVaultUserRepository.remove(sharedVaultUser)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return Result.ok()
|
return Result.ok()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
export interface RemoveSharedVaultUserDTO {
|
export interface RemoveSharedVaultUserDTO {
|
||||||
sharedVaultUuid: string
|
sharedVaultUuid?: string
|
||||||
userUuid: string
|
userUuid: string
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,13 @@ export class TypeORMSharedVaultUser {
|
|||||||
})
|
})
|
||||||
declare permission: string
|
declare permission: string
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
name: 'is_designated_survivor',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
})
|
||||||
|
declare isDesignatedSurvivor: boolean
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
name: 'created_at_timestamp',
|
name: 'created_at_timestamp',
|
||||||
type: 'bigint',
|
type: 'bigint',
|
||||||
|
|||||||
@@ -10,6 +10,24 @@ export class TypeORMSharedVaultUserRepository implements SharedVaultUserReposito
|
|||||||
private mapper: MapperInterface<SharedVaultUser, TypeORMSharedVaultUser>,
|
private mapper: MapperInterface<SharedVaultUser, TypeORMSharedVaultUser>,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
async findDesignatedSurvivorBySharedVaultUuid(sharedVaultUuid: Uuid): Promise<SharedVaultUser | null> {
|
||||||
|
const persistence = await this.ormRepository
|
||||||
|
.createQueryBuilder('shared_vault_user')
|
||||||
|
.where('shared_vault_user.shared_vault_uuid = :sharedVaultUuid', {
|
||||||
|
sharedVaultUuid: sharedVaultUuid.value,
|
||||||
|
})
|
||||||
|
.andWhere('shared_vault_user.is_designated_survivor = :isDesignatedSurvivor', {
|
||||||
|
isDesignatedSurvivor: true,
|
||||||
|
})
|
||||||
|
.getOne()
|
||||||
|
|
||||||
|
if (persistence === null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.mapper.toDomain(persistence)
|
||||||
|
}
|
||||||
|
|
||||||
async findByUserUuid(userUuid: Uuid): Promise<SharedVaultUser[]> {
|
async findByUserUuid(userUuid: Uuid): Promise<SharedVaultUser[]> {
|
||||||
const persistence = await this.ormRepository
|
const persistence = await this.ormRepository
|
||||||
.createQueryBuilder('shared_vault_user')
|
.createQueryBuilder('shared_vault_user')
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ export class SharedVaultUserPersistenceMapper implements MapperInterface<SharedV
|
|||||||
sharedVaultUuid,
|
sharedVaultUuid,
|
||||||
permission,
|
permission,
|
||||||
timestamps,
|
timestamps,
|
||||||
|
isDesignatedSurvivor: !!projection.isDesignatedSurvivor,
|
||||||
},
|
},
|
||||||
new UniqueEntityId(projection.uuid),
|
new UniqueEntityId(projection.uuid),
|
||||||
)
|
)
|
||||||
@@ -61,6 +62,7 @@ export class SharedVaultUserPersistenceMapper implements MapperInterface<SharedV
|
|||||||
typeorm.permission = domain.props.permission.value
|
typeorm.permission = domain.props.permission.value
|
||||||
typeorm.createdAtTimestamp = domain.props.timestamps.createdAt
|
typeorm.createdAtTimestamp = domain.props.timestamps.createdAt
|
||||||
typeorm.updatedAtTimestamp = domain.props.timestamps.updatedAt
|
typeorm.updatedAtTimestamp = domain.props.timestamps.updatedAt
|
||||||
|
typeorm.isDesignatedSurvivor = !!domain.props.isDesignatedSurvivor
|
||||||
|
|
||||||
return typeorm
|
return typeorm
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,36 @@
|
|||||||
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.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)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add designating a survivor in shared vault ([#841](https://github.com/standardnotes/server/issues/841)) ([230c96d](https://github.com/standardnotes/server/commit/230c96dcf1d8faed9ce8fe288549226da70317e6))
|
||||||
|
|
||||||
|
# [1.32.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-core@1.31.0...@standardnotes/domain-core@1.32.0) (2023-09-20)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **syncing-server:** distinct notifications upon user removal from shared vault ([#840](https://github.com/standardnotes/server/issues/840)) ([41e2136](https://github.com/standardnotes/server/commit/41e2136bc07312974701a70652528d304105e0f9))
|
||||||
|
|
||||||
|
# [1.31.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-core@1.30.1...@standardnotes/domain-core@1.31.0) (2023-09-20)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **syncing-server:** add notification for user upon declined shared vault invitation ([#837](https://github.com/standardnotes/server/issues/837)) ([31e7aaf](https://github.com/standardnotes/server/commit/31e7aaf253029a951d8b943d6cffd655cd5ca765))
|
||||||
|
|
||||||
|
## [1.30.1](https://github.com/standardnotes/server/compare/@standardnotes/domain-core@1.30.0...@standardnotes/domain-core@1.30.1) (2023-09-19)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **domain-core:** allow any version and variant of the UUID format ([de081fe](https://github.com/standardnotes/server/commit/de081fe78658bdd36c8c5d86b70a16a2880aa3d1))
|
||||||
|
|
||||||
# [1.30.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-core@1.29.0...@standardnotes/domain-core@1.30.0) (2023-09-18)
|
# [1.30.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-core@1.29.0...@standardnotes/domain-core@1.30.0) (2023-09-18)
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/domain-core",
|
"name": "@standardnotes/domain-core",
|
||||||
"version": "1.30.0",
|
"version": "1.33.1",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0 <21.0.0"
|
"node": ">=18.0.0 <21.0.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ describe('Validator', () => {
|
|||||||
'2221101c-1da9-4d2b-9b32-b8be2a8d1c82',
|
'2221101c-1da9-4d2b-9b32-b8be2a8d1c82',
|
||||||
'c08f2f29-a74b-42b4-aefd-98af9832391c',
|
'c08f2f29-a74b-42b4-aefd-98af9832391c',
|
||||||
'b453fa64-1493-443b-b5bb-bca7b9c696c7',
|
'b453fa64-1493-443b-b5bb-bca7b9c696c7',
|
||||||
|
'fa7350b3-77cf-8c0c-40b2-6046b13254fe',
|
||||||
]
|
]
|
||||||
|
|
||||||
const invalidUuids = [
|
const invalidUuids = [
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import { Result } from './Result'
|
import { Result } from './Result'
|
||||||
|
|
||||||
export class Validator {
|
export class Validator {
|
||||||
private static readonly UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-5][0-9a-f]{3}-[089ab][0-9a-f]{3}-[0-9a-f]{12}$/i
|
private static readonly UUID_ANY_VERSION_AND_VARIANT_REGEX =
|
||||||
|
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
|
||||||
private static readonly EMAIL_REGEX =
|
private static readonly EMAIL_REGEX =
|
||||||
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
|
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
|
||||||
|
|
||||||
static isValidUuid(value: string): Result<string> {
|
static isValidUuid(value: string): Result<string> {
|
||||||
const matchesUuidRegex = String(value).toLowerCase().match(Validator.UUID_REGEX) !== null
|
const matchesUuidRegex = String(value).toLowerCase().match(Validator.UUID_ANY_VERSION_AND_VARIANT_REGEX) !== null
|
||||||
if (matchesUuidRegex) {
|
if (matchesUuidRegex) {
|
||||||
return Result.ok()
|
return Result.ok()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
itemUuid: this.props.itemUuid ? this.props.itemUuid.value : undefined,
|
primaryIndentifierType: this.props.primaryIndentifierType,
|
||||||
|
secondaryIdentifier: this.props.secondaryIdentifier,
|
||||||
|
secondaryIdentifierType: this.props.secondaryIdentifierType,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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,27 @@
|
|||||||
|
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',
|
||||||
|
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
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,10 @@ import { NotificationTypeProps } from './NotificationTypeProps'
|
|||||||
export class NotificationType extends ValueObject<NotificationTypeProps> {
|
export class NotificationType extends ValueObject<NotificationTypeProps> {
|
||||||
static readonly TYPES = {
|
static readonly TYPES = {
|
||||||
SharedVaultItemRemoved: 'shared_vault_item_removed',
|
SharedVaultItemRemoved: 'shared_vault_item_removed',
|
||||||
RemovedFromSharedVault: 'removed_from_shared_vault',
|
SelfRemovedFromSharedVault: 'self_removed_from_shared_vault',
|
||||||
|
UserRemovedFromSharedVault: 'user_removed_from_shared_vault',
|
||||||
UserAddedToSharedVault: 'user_added_to_shared_vault',
|
UserAddedToSharedVault: 'user_added_to_shared_vault',
|
||||||
|
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',
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ describe('SharedVaultUser', () => {
|
|||||||
sharedVaultUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
sharedVaultUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||||
timestamps: Timestamps.create(123456789, 123456789).getValue(),
|
timestamps: Timestamps.create(123456789, 123456789).getValue(),
|
||||||
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||||
|
isDesignatedSurvivor: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(entityOrError.isFailed()).toBeFalsy()
|
expect(entityOrError.isFailed()).toBeFalsy()
|
||||||
|
|||||||
@@ -6,5 +6,6 @@ export interface SharedVaultUserProps {
|
|||||||
sharedVaultUuid: Uuid
|
sharedVaultUuid: Uuid
|
||||||
userUuid: Uuid
|
userUuid: Uuid
|
||||||
permission: SharedVaultUserPermission
|
permission: SharedVaultUserPermission
|
||||||
|
isDesignatedSurvivor: boolean
|
||||||
timestamps: Timestamps
|
timestamps: Timestamps
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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'
|
||||||
|
|||||||
@@ -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.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)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/domain-events-infra
|
||||||
|
|
||||||
|
## [1.12.31](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.12.30...@standardnotes/domain-events-infra@1.12.31) (2023-09-20)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/domain-events-infra
|
||||||
|
|
||||||
## [1.12.30](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.12.29...@standardnotes/domain-events-infra@1.12.30) (2023-09-15)
|
## [1.12.30](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.12.29...@standardnotes/domain-events-infra@1.12.30) (2023-09-15)
|
||||||
|
|
||||||
**Note:** Version bump only for package @standardnotes/domain-events-infra
|
**Note:** Version bump only for package @standardnotes/domain-events-infra
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/domain-events-infra",
|
"name": "@standardnotes/domain-events-infra",
|
||||||
"version": "1.12.30",
|
"version": "1.12.34",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0 <21.0.0"
|
"node": ">=18.0.0 <21.0.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|
||||||
|
# [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)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add designating a survivor in shared vault ([#841](https://github.com/standardnotes/server/issues/841)) ([230c96d](https://github.com/standardnotes/server/commit/230c96dcf1d8faed9ce8fe288549226da70317e6))
|
||||||
|
|
||||||
|
# [2.128.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.127.0...@standardnotes/domain-events@2.128.0) (2023-09-20)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add unassigning items and revisions upon shared vault removal ([#839](https://github.com/standardnotes/server/issues/839)) ([378eced](https://github.com/standardnotes/server/commit/378ecedfcc4fb23475c2329fb37479edb3b48a39))
|
||||||
|
|
||||||
# [2.127.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.126.0...@standardnotes/domain-events@2.127.0) (2023-09-15)
|
# [2.127.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.126.0...@standardnotes/domain-events@2.127.0) (2023-09-15)
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/domain-events",
|
"name": "@standardnotes/domain-events",
|
||||||
"version": "2.127.0",
|
"version": "2.130.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0 <21.0.0"
|
"node": ">=18.0.0 <21.0.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,5 +2,4 @@ export interface ItemRemovedFromSharedVaultEventPayload {
|
|||||||
userUuid: string
|
userUuid: string
|
||||||
itemUuid: string
|
itemUuid: string
|
||||||
sharedVaultUuid: string
|
sharedVaultUuid: string
|
||||||
roleNames: string[]
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { DomainEventInterface } from './DomainEventInterface'
|
||||||
|
import { SharedVaultRemovedEventPayload } from './SharedVaultRemovedEventPayload'
|
||||||
|
|
||||||
|
export interface SharedVaultRemovedEvent extends DomainEventInterface {
|
||||||
|
type: 'SHARED_VAULT_REMOVED'
|
||||||
|
payload: SharedVaultRemovedEventPayload
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export interface SharedVaultRemovedEventPayload {
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { DomainEventInterface } from './DomainEventInterface'
|
||||||
|
import { UserDesignatedAsSurvivorInSharedVaultEventPayload } from './UserDesignatedAsSurvivorInSharedVaultEventPayload'
|
||||||
|
|
||||||
|
export interface UserDesignatedAsSurvivorInSharedVaultEvent extends DomainEventInterface {
|
||||||
|
type: 'USER_DESIGNATED_AS_SURVIVOR_IN_SHARED_VAULT'
|
||||||
|
payload: UserDesignatedAsSurvivorInSharedVaultEventPayload
|
||||||
|
}
|
||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
export interface UserDesignatedAsSurvivorInSharedVaultEventPayload {
|
||||||
|
userUuid: string
|
||||||
|
sharedVaultUuid: string
|
||||||
|
timestamp: number
|
||||||
|
}
|
||||||
@@ -76,6 +76,8 @@ export * from './Event/SharedVaultFileRemovedEvent'
|
|||||||
export * from './Event/SharedVaultFileRemovedEventPayload'
|
export * from './Event/SharedVaultFileRemovedEventPayload'
|
||||||
export * from './Event/SharedVaultFileUploadedEvent'
|
export * from './Event/SharedVaultFileUploadedEvent'
|
||||||
export * from './Event/SharedVaultFileUploadedEventPayload'
|
export * from './Event/SharedVaultFileUploadedEventPayload'
|
||||||
|
export * from './Event/SharedVaultRemovedEvent'
|
||||||
|
export * from './Event/SharedVaultRemovedEventPayload'
|
||||||
export * from './Event/StatisticPersistenceRequestedEvent'
|
export * from './Event/StatisticPersistenceRequestedEvent'
|
||||||
export * from './Event/StatisticPersistenceRequestedEventPayload'
|
export * from './Event/StatisticPersistenceRequestedEventPayload'
|
||||||
export * from './Event/SubscriptionCancelledEvent'
|
export * from './Event/SubscriptionCancelledEvent'
|
||||||
@@ -102,6 +104,8 @@ export * from './Event/TransitionStatusUpdatedEvent'
|
|||||||
export * from './Event/TransitionStatusUpdatedEventPayload'
|
export * from './Event/TransitionStatusUpdatedEventPayload'
|
||||||
export * from './Event/UserAddedToSharedVaultEvent'
|
export * from './Event/UserAddedToSharedVaultEvent'
|
||||||
export * from './Event/UserAddedToSharedVaultEventPayload'
|
export * from './Event/UserAddedToSharedVaultEventPayload'
|
||||||
|
export * from './Event/UserDesignatedAsSurvivorInSharedVaultEvent'
|
||||||
|
export * from './Event/UserDesignatedAsSurvivorInSharedVaultEventPayload'
|
||||||
export * from './Event/UserDisabledSessionUserAgentLoggingEvent'
|
export * from './Event/UserDisabledSessionUserAgentLoggingEvent'
|
||||||
export * from './Event/UserDisabledSessionUserAgentLoggingEventPayload'
|
export * from './Event/UserDisabledSessionUserAgentLoggingEventPayload'
|
||||||
export * from './Event/UserEmailChangedEvent'
|
export * from './Event/UserEmailChangedEvent'
|
||||||
|
|||||||
@@ -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.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)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/event-store
|
||||||
|
|
||||||
|
## [1.11.47](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.11.46...@standardnotes/event-store@1.11.47) (2023-09-20)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/event-store
|
||||||
|
|
||||||
|
## [1.11.46](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.11.45...@standardnotes/event-store@1.11.46) (2023-09-20)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/event-store
|
||||||
|
|
||||||
|
## [1.11.45](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.11.44...@standardnotes/event-store@1.11.45) (2023-09-19)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/event-store
|
||||||
|
|
||||||
## [1.11.44](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.11.43...@standardnotes/event-store@1.11.44) (2023-09-18)
|
## [1.11.44](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.11.43...@standardnotes/event-store@1.11.44) (2023-09-18)
|
||||||
|
|
||||||
**Note:** Version bump only for package @standardnotes/event-store
|
**Note:** Version bump only for package @standardnotes/event-store
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/event-store",
|
"name": "@standardnotes/event-store",
|
||||||
"version": "1.11.44",
|
"version": "1.11.51",
|
||||||
"description": "Event Store Service",
|
"description": "Event Store Service",
|
||||||
"private": true,
|
"private": true,
|
||||||
"main": "dist/src/index.js",
|
"main": "dist/src/index.js",
|
||||||
|
|||||||
@@ -3,6 +3,36 @@
|
|||||||
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.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)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/files-server
|
||||||
|
|
||||||
|
## [1.22.26](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.22.25...@standardnotes/files-server@1.22.26) (2023-09-20)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/files-server
|
||||||
|
|
||||||
|
## [1.22.25](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.22.24...@standardnotes/files-server@1.22.25) (2023-09-20)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/files-server
|
||||||
|
|
||||||
|
## [1.22.24](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.22.23...@standardnotes/files-server@1.22.24) (2023-09-19)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/files-server
|
||||||
|
|
||||||
## [1.22.23](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.22.22...@standardnotes/files-server@1.22.23) (2023-09-18)
|
## [1.22.23](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.22.22...@standardnotes/files-server@1.22.23) (2023-09-18)
|
||||||
|
|
||||||
**Note:** Version bump only for package @standardnotes/files-server
|
**Note:** Version bump only for package @standardnotes/files-server
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/files-server",
|
"name": "@standardnotes/files-server",
|
||||||
"version": "1.22.23",
|
"version": "1.23.1",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0 <21.0.0"
|
"node": ">=18.0.0 <21.0.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
-73
@@ -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()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
+5
-3
@@ -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',
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
|
||||||
|
|||||||
@@ -3,6 +3,108 @@
|
|||||||
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.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)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/home-server
|
||||||
|
|
||||||
|
## [1.15.77](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.76...@standardnotes/home-server@1.15.77) (2023-09-22)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/home-server
|
||||||
|
|
||||||
|
## [1.15.76](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.75...@standardnotes/home-server@1.15.76) (2023-09-21)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/home-server
|
||||||
|
|
||||||
|
## [1.15.75](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.74...@standardnotes/home-server@1.15.75) (2023-09-21)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/home-server
|
||||||
|
|
||||||
|
## [1.15.74](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.73...@standardnotes/home-server@1.15.74) (2023-09-21)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/home-server
|
||||||
|
|
||||||
|
## [1.15.73](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.72...@standardnotes/home-server@1.15.73) (2023-09-21)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/home-server
|
||||||
|
|
||||||
|
## [1.15.72](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.71...@standardnotes/home-server@1.15.72) (2023-09-21)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/home-server
|
||||||
|
|
||||||
|
## [1.15.71](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.70...@standardnotes/home-server@1.15.71) (2023-09-20)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/home-server
|
||||||
|
|
||||||
|
## [1.15.70](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.69...@standardnotes/home-server@1.15.70) (2023-09-20)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/home-server
|
||||||
|
|
||||||
|
## [1.15.69](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.68...@standardnotes/home-server@1.15.69) (2023-09-20)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/home-server
|
||||||
|
|
||||||
|
## [1.15.68](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.67...@standardnotes/home-server@1.15.68) (2023-09-19)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/home-server
|
||||||
|
|
||||||
|
## [1.15.67](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.66...@standardnotes/home-server@1.15.67) (2023-09-19)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/home-server
|
||||||
|
|
||||||
|
## [1.15.66](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.65...@standardnotes/home-server@1.15.66) (2023-09-19)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/home-server
|
||||||
|
|
||||||
|
## [1.15.65](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.64...@standardnotes/home-server@1.15.65) (2023-09-19)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/home-server
|
||||||
|
|
||||||
|
## [1.15.64](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.63...@standardnotes/home-server@1.15.64) (2023-09-19)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/home-server
|
||||||
|
|
||||||
|
## [1.15.63](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.62...@standardnotes/home-server@1.15.63) (2023-09-19)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/home-server
|
||||||
|
|
||||||
|
## [1.15.62](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.61...@standardnotes/home-server@1.15.62) (2023-09-19)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/home-server
|
||||||
|
|
||||||
## [1.15.61](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.60...@standardnotes/home-server@1.15.61) (2023-09-18)
|
## [1.15.61](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.60...@standardnotes/home-server@1.15.61) (2023-09-18)
|
||||||
|
|
||||||
**Note:** Version bump only for package @standardnotes/home-server
|
**Note:** Version bump only for package @standardnotes/home-server
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/home-server",
|
"name": "@standardnotes/home-server",
|
||||||
"version": "1.15.61",
|
"version": "1.16.5",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0 <21.0.0"
|
"node": ">=18.0.0 <21.0.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,6 +3,105 @@
|
|||||||
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.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)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **revisions:** add log info about skipping already existing revision ([d0dba1b](https://github.com/standardnotes/server/commit/d0dba1b66df0fb4ab64ede8f0d4e1c4e2a23ad3c))
|
||||||
|
|
||||||
|
## [1.36.3](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.36.2...@standardnotes/revisions-server@1.36.3) (2023-09-21)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/revisions-server
|
||||||
|
|
||||||
|
## [1.36.2](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.36.1...@standardnotes/revisions-server@1.36.2) (2023-09-21)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **revisions:** rename revisions table to all users stuck mid-migration process - fixes [#836](https://github.com/standardnotes/server/issues/836) ([#842](https://github.com/standardnotes/server/issues/842)) ([a621cf1](https://github.com/standardnotes/server/commit/a621cf1e3b891c450272e9762e4a71a199ea2932))
|
||||||
|
|
||||||
|
## [1.36.1](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.36.0...@standardnotes/revisions-server@1.36.1) (2023-09-21)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* secondary database catch up time ([880db10](https://github.com/standardnotes/server/commit/880db1038a39d4610a2593489a18e207487347a2))
|
||||||
|
|
||||||
|
# [1.36.0](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.35.8...@standardnotes/revisions-server@1.36.0) (2023-09-20)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add unassigning items and revisions upon shared vault removal ([#839](https://github.com/standardnotes/server/issues/839)) ([378eced](https://github.com/standardnotes/server/commit/378ecedfcc4fb23475c2329fb37479edb3b48a39))
|
||||||
|
|
||||||
|
## [1.35.8](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.35.7...@standardnotes/revisions-server@1.35.8) (2023-09-20)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/revisions-server
|
||||||
|
|
||||||
|
## [1.35.7](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.35.6...@standardnotes/revisions-server@1.35.7) (2023-09-19)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* skip removing already existing content in secondary to pick up where the transition left of ([857c6af](https://github.com/standardnotes/server/commit/857c6af9468ec829ff4dce9a96ba7bf9c14d55a5))
|
||||||
|
|
||||||
|
## [1.35.6](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.35.5...@standardnotes/revisions-server@1.35.6) (2023-09-19)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* increase timeout for secondary database to catch up for indexes to be rebuilt ([b265a39](https://github.com/standardnotes/server/commit/b265a39b635373c36ee8c3d8e09f0631159b3574))
|
||||||
|
* logs verbosity during transitions ([e589029](https://github.com/standardnotes/server/commit/e589029722ab9f4debc8aa6cc78913f877eda2e3))
|
||||||
|
|
||||||
|
## [1.35.5](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.35.4...@standardnotes/revisions-server@1.35.5) (2023-09-19)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* add checking for secondary items logs ([a1a3e9f](https://github.com/standardnotes/server/commit/a1a3e9f586358b943b1b490a1382e42f081f7d06))
|
||||||
|
* logs for removing already existing content and paging through diff of the content ([a40b17b](https://github.com/standardnotes/server/commit/a40b17b141f1d5954e1a45b969d5a941386c68d0))
|
||||||
|
|
||||||
|
## [1.35.4](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.35.3...@standardnotes/revisions-server@1.35.4) (2023-09-19)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* logs formatting during transition for better readability ([0ae028d](https://github.com/standardnotes/server/commit/0ae028db739decec8c50321b18b0af515e00bd23))
|
||||||
|
|
||||||
|
## [1.35.3](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.35.2...@standardnotes/revisions-server@1.35.3) (2023-09-19)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **syncing-server:** paging through already existing items ([e4fcd73](https://github.com/standardnotes/server/commit/e4fcd738c35a4dc96e57db6ca08383a5647d61ad))
|
||||||
|
|
||||||
## [1.35.2](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.35.1...@standardnotes/revisions-server@1.35.2) (2023-09-18)
|
## [1.35.2](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.35.1...@standardnotes/revisions-server@1.35.2) (2023-09-18)
|
||||||
|
|
||||||
**Note:** Version bump only for package @standardnotes/revisions-server
|
**Note:** Version bump only for package @standardnotes/revisions-server
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ export class removeDateIndexes1669636497932 implements MigrationInterface {
|
|||||||
name = 'removeDateIndexes1669636497932'
|
name = 'removeDateIndexes1669636497932'
|
||||||
|
|
||||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await this.renameRevisionsTable(queryRunner)
|
||||||
|
|
||||||
const indexRevisionsOnCreatedAt = await queryRunner.manager.query(
|
const indexRevisionsOnCreatedAt = await queryRunner.manager.query(
|
||||||
'SHOW INDEX FROM `revisions_revisions` where `key_name` = "created_at"',
|
'SHOW INDEX FROM `revisions_revisions` where `key_name` = "created_at"',
|
||||||
)
|
)
|
||||||
@@ -25,4 +27,14 @@ export class removeDateIndexes1669636497932 implements MigrationInterface {
|
|||||||
await queryRunner.query('CREATE INDEX `creation_date` ON `revisions_revisions` (`creation_date`)')
|
await queryRunner.query('CREATE INDEX `creation_date` ON `revisions_revisions` (`creation_date`)')
|
||||||
await queryRunner.query('CREATE INDEX `created_at` ON `revisions_revisions` (`created_at`)')
|
await queryRunner.query('CREATE INDEX `created_at` ON `revisions_revisions` (`created_at`)')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async renameRevisionsTable(queryRunner: QueryRunner) {
|
||||||
|
const revisionsTableExistsQueryResult = await queryRunner.manager.query(
|
||||||
|
'SELECT COUNT(*) as count FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = "revisions"',
|
||||||
|
)
|
||||||
|
const revisionsTableExists = revisionsTableExistsQueryResult[0].count === 1
|
||||||
|
if (revisionsTableExists) {
|
||||||
|
await queryRunner.query('RENAME TABLE `revisions` TO `revisions_revisions`')
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,10 +4,22 @@ export class makeUserUuidNullable1669735585016 implements MigrationInterface {
|
|||||||
name = 'makeUserUuidNullable1669735585016'
|
name = 'makeUserUuidNullable1669735585016'
|
||||||
|
|
||||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await this.renameRevisionsTable(queryRunner)
|
||||||
|
|
||||||
await queryRunner.query('ALTER TABLE `revisions_revisions` CHANGE `user_uuid` `user_uuid` varchar(36) NULL')
|
await queryRunner.query('ALTER TABLE `revisions_revisions` CHANGE `user_uuid` `user_uuid` varchar(36) NULL')
|
||||||
}
|
}
|
||||||
|
|
||||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
await queryRunner.query('ALTER TABLE `revisions_revisions` CHANGE `user_uuid` `user_uuid` varchar(36) NOT NULL')
|
await queryRunner.query('ALTER TABLE `revisions_revisions` CHANGE `user_uuid` `user_uuid` varchar(36) NOT NULL')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async renameRevisionsTable(queryRunner: QueryRunner) {
|
||||||
|
const revisionsTableExistsQueryResult = await queryRunner.manager.query(
|
||||||
|
'SELECT COUNT(*) as count FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = "revisions"',
|
||||||
|
)
|
||||||
|
const revisionsTableExists = revisionsTableExistsQueryResult[0].count === 1
|
||||||
|
if (revisionsTableExists) {
|
||||||
|
await queryRunner.query('RENAME TABLE `revisions` TO `revisions_revisions`')
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import { MigrationInterface, QueryRunner } from 'typeorm'
|
|||||||
|
|
||||||
export class AddSharedVaultInformation1693915383950 implements MigrationInterface {
|
export class AddSharedVaultInformation1693915383950 implements MigrationInterface {
|
||||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await this.renameRevisionsTable(queryRunner)
|
||||||
|
|
||||||
await queryRunner.query('ALTER TABLE `revisions_revisions` ADD `edited_by` varchar(36) NULL')
|
await queryRunner.query('ALTER TABLE `revisions_revisions` ADD `edited_by` varchar(36) NULL')
|
||||||
await queryRunner.query('ALTER TABLE `revisions_revisions` ADD `shared_vault_uuid` varchar(36) NULL')
|
await queryRunner.query('ALTER TABLE `revisions_revisions` ADD `shared_vault_uuid` varchar(36) NULL')
|
||||||
await queryRunner.query('ALTER TABLE `revisions_revisions` ADD `key_system_identifier` varchar(36) NULL')
|
await queryRunner.query('ALTER TABLE `revisions_revisions` ADD `key_system_identifier` varchar(36) NULL')
|
||||||
@@ -16,4 +18,14 @@ export class AddSharedVaultInformation1693915383950 implements MigrationInterfac
|
|||||||
await queryRunner.query('ALTER TABLE `revisions_revisions` DROP COLUMN `shared_vault_uuid`')
|
await queryRunner.query('ALTER TABLE `revisions_revisions` DROP COLUMN `shared_vault_uuid`')
|
||||||
await queryRunner.query('ALTER TABLE `revisions_revisions` DROP COLUMN `last_edited_by`')
|
await queryRunner.query('ALTER TABLE `revisions_revisions` DROP COLUMN `last_edited_by`')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async renameRevisionsTable(queryRunner: QueryRunner) {
|
||||||
|
const revisionsTableExistsQueryResult = await queryRunner.manager.query(
|
||||||
|
'SELECT COUNT(*) as count FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = "revisions"',
|
||||||
|
)
|
||||||
|
const revisionsTableExists = revisionsTableExistsQueryResult[0].count === 1
|
||||||
|
if (revisionsTableExists) {
|
||||||
|
await queryRunner.query('RENAME TABLE `revisions` TO `revisions_revisions`')
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/revisions-server",
|
"name": "@standardnotes/revisions-server",
|
||||||
"version": "1.35.2",
|
"version": "1.37.2",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0 <21.0.0"
|
"node": ">=18.0.0 <21.0.0"
|
||||||
},
|
},
|
||||||
@@ -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",
|
||||||
|
|||||||
@@ -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'
|
||||||
@@ -67,6 +68,9 @@ import { SQLRevisionPersistenceMapper } from '../Mapping/Persistence/SQL/SQLRevi
|
|||||||
import { RemoveRevisionsFromSharedVault } from '../Domain/UseCase/RemoveRevisionsFromSharedVault/RemoveRevisionsFromSharedVault'
|
import { RemoveRevisionsFromSharedVault } from '../Domain/UseCase/RemoveRevisionsFromSharedVault/RemoveRevisionsFromSharedVault'
|
||||||
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 { TransitionRepositoryInterface } from '../Domain/Transition/TransitionRepositoryInterface'
|
||||||
|
import { RedisTransitionRepository } from '../Infra/Redis/RedisTransitionRepository'
|
||||||
|
|
||||||
export class ContainerConfigLoader {
|
export class ContainerConfigLoader {
|
||||||
constructor(private mode: 'server' | 'worker' = 'server') {}
|
constructor(private mode: 'server' | 'worker' = 'server') {}
|
||||||
@@ -87,11 +91,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
|
||||||
@@ -347,6 +368,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,
|
||||||
@@ -356,7 +380,9 @@ export class ContainerConfigLoader {
|
|||||||
.bind<RemoveRevisionsFromSharedVault>(TYPES.Revisions_RemoveRevisionsFromSharedVault)
|
.bind<RemoveRevisionsFromSharedVault>(TYPES.Revisions_RemoveRevisionsFromSharedVault)
|
||||||
.toConstantValue(
|
.toConstantValue(
|
||||||
new RemoveRevisionsFromSharedVault(
|
new RemoveRevisionsFromSharedVault(
|
||||||
container.get<RevisionRepositoryResolverInterface>(TYPES.Revisions_RevisionRepositoryResolver),
|
isSecondaryDatabaseEnabled
|
||||||
|
? container.get<RevisionRepositoryInterface>(TYPES.Revisions_MongoDBRevisionRepository)
|
||||||
|
: container.get<RevisionRepositoryInterface>(TYPES.Revisions_SQLRevisionRepository),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -448,6 +474,14 @@ export class ContainerConfigLoader {
|
|||||||
container.get<winston.Logger>(TYPES.Revisions_Logger),
|
container.get<winston.Logger>(TYPES.Revisions_Logger),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
container
|
||||||
|
.bind<SharedVaultRemovedEventHandler>(TYPES.Revisions_SharedVaultRemovedEventHandler)
|
||||||
|
.toConstantValue(
|
||||||
|
new SharedVaultRemovedEventHandler(
|
||||||
|
container.get<RemoveRevisionsFromSharedVault>(TYPES.Revisions_RemoveRevisionsFromSharedVault),
|
||||||
|
container.get<winston.Logger>(TYPES.Revisions_Logger),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
const eventHandlers: Map<string, DomainEventHandlerInterface> = new Map([
|
const eventHandlers: Map<string, DomainEventHandlerInterface> = new Map([
|
||||||
['ITEM_DUMPED', container.get(TYPES.Revisions_ItemDumpedEventHandler)],
|
['ITEM_DUMPED', container.get(TYPES.Revisions_ItemDumpedEventHandler)],
|
||||||
@@ -455,6 +489,7 @@ export class ContainerConfigLoader {
|
|||||||
['REVISIONS_COPY_REQUESTED', container.get(TYPES.Revisions_RevisionsCopyRequestedEventHandler)],
|
['REVISIONS_COPY_REQUESTED', container.get(TYPES.Revisions_RevisionsCopyRequestedEventHandler)],
|
||||||
['ITEM_REMOVED_FROM_SHARED_VAULT', container.get(TYPES.Revisions_ItemRemovedFromSharedVaultEventHandler)],
|
['ITEM_REMOVED_FROM_SHARED_VAULT', container.get(TYPES.Revisions_ItemRemovedFromSharedVaultEventHandler)],
|
||||||
['TRANSITION_REQUESTED', container.get(TYPES.Revisions_TransitionRequestedEventHandler)],
|
['TRANSITION_REQUESTED', container.get(TYPES.Revisions_TransitionRequestedEventHandler)],
|
||||||
|
['SHARED_VAULT_REMOVED', container.get(TYPES.Revisions_SharedVaultRemovedEventHandler)],
|
||||||
])
|
])
|
||||||
|
|
||||||
if (isConfiguredForHomeServer) {
|
if (isConfiguredForHomeServer) {
|
||||||
|
|||||||
@@ -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'),
|
||||||
@@ -57,6 +59,7 @@ const TYPES = {
|
|||||||
Revisions_RevisionsCopyRequestedEventHandler: Symbol.for('Revisions_RevisionsCopyRequestedEventHandler'),
|
Revisions_RevisionsCopyRequestedEventHandler: Symbol.for('Revisions_RevisionsCopyRequestedEventHandler'),
|
||||||
Revisions_ItemRemovedFromSharedVaultEventHandler: Symbol.for('Revisions_ItemRemovedFromSharedVaultEventHandler'),
|
Revisions_ItemRemovedFromSharedVaultEventHandler: Symbol.for('Revisions_ItemRemovedFromSharedVaultEventHandler'),
|
||||||
Revisions_TransitionRequestedEventHandler: Symbol.for('Revisions_TransitionRequestedEventHandler'),
|
Revisions_TransitionRequestedEventHandler: Symbol.for('Revisions_TransitionRequestedEventHandler'),
|
||||||
|
Revisions_SharedVaultRemovedEventHandler: Symbol.for('Revisions_SharedVaultRemovedEventHandler'),
|
||||||
// Services
|
// Services
|
||||||
Revisions_CrossServiceTokenDecoder: Symbol.for('Revisions_CrossServiceTokenDecoder'),
|
Revisions_CrossServiceTokenDecoder: Symbol.for('Revisions_CrossServiceTokenDecoder'),
|
||||||
Revisions_DomainEventSubscriberFactory: Symbol.for('Revisions_DomainEventSubscriberFactory'),
|
Revisions_DomainEventSubscriberFactory: Symbol.for('Revisions_DomainEventSubscriberFactory'),
|
||||||
|
|||||||
@@ -9,10 +9,15 @@ export class ItemRemovedFromSharedVaultEventHandler implements DomainEventHandle
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
async handle(event: ItemRemovedFromSharedVaultEvent): Promise<void> {
|
async handle(event: ItemRemovedFromSharedVaultEvent): Promise<void> {
|
||||||
|
if (!event.payload.itemUuid) {
|
||||||
|
this.logger.error('ItemRemovedFromSharedVaultEvent is missing itemUuid')
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const result = await this.removeRevisionsFromSharedVault.execute({
|
const result = await this.removeRevisionsFromSharedVault.execute({
|
||||||
sharedVaultUuid: event.payload.sharedVaultUuid,
|
sharedVaultUuid: event.payload.sharedVaultUuid,
|
||||||
itemUuid: event.payload.itemUuid,
|
itemUuid: event.payload.itemUuid,
|
||||||
roleNames: event.payload.roleNames,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
if (result.isFailed()) {
|
if (result.isFailed()) {
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { DomainEventHandlerInterface, SharedVaultRemovedEvent } from '@standardnotes/domain-events'
|
||||||
|
import { Logger } from 'winston'
|
||||||
|
|
||||||
|
import { RemoveRevisionsFromSharedVault } from '../UseCase/RemoveRevisionsFromSharedVault/RemoveRevisionsFromSharedVault'
|
||||||
|
|
||||||
|
export class SharedVaultRemovedEventHandler implements DomainEventHandlerInterface {
|
||||||
|
constructor(
|
||||||
|
private removeRevisionsFromSharedVault: RemoveRevisionsFromSharedVault,
|
||||||
|
private logger: Logger,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async handle(event: SharedVaultRemovedEvent): Promise<void> {
|
||||||
|
const result = await this.removeRevisionsFromSharedVault.execute({
|
||||||
|
sharedVaultUuid: event.payload.sharedVaultUuid,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (result.isFailed()) {
|
||||||
|
this.logger.error(`Failed to remove revisions from shared vault: ${result.getError()}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -29,7 +29,7 @@ export class TransitionRequestedEventHandler implements DomainEventHandlerInterf
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (await this.isAlreadyMigrated(userUuid)) {
|
if (await this.isAlreadyMigrated(userUuid)) {
|
||||||
this.logger.info(`User ${event.payload.userUuid} already migrated.`)
|
this.logger.info(`[${event.payload.userUuid}] User already migrated.`)
|
||||||
|
|
||||||
await this.domainEventPublisher.publish(
|
await this.domainEventPublisher.publish(
|
||||||
this.domainEventFactory.createTransitionStatusUpdatedEvent({
|
this.domainEventFactory.createTransitionStatusUpdatedEvent({
|
||||||
@@ -43,7 +43,7 @@ export class TransitionRequestedEventHandler implements DomainEventHandlerInterf
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
this.logger.info(`Handling transition requested event for user ${event.payload.userUuid}`)
|
this.logger.info(`[${event.payload.userUuid}] Handling transition requested event`)
|
||||||
|
|
||||||
await this.domainEventPublisher.publish(
|
await this.domainEventPublisher.publish(
|
||||||
this.domainEventFactory.createTransitionStatusUpdatedEvent({
|
this.domainEventFactory.createTransitionStatusUpdatedEvent({
|
||||||
@@ -59,7 +59,7 @@ export class TransitionRequestedEventHandler implements DomainEventHandlerInterf
|
|||||||
})
|
})
|
||||||
|
|
||||||
if (result.isFailed()) {
|
if (result.isFailed()) {
|
||||||
this.logger.error(`Failed to transition for user ${event.payload.userUuid}`)
|
this.logger.error(`[${event.payload.userUuid}] Failed to transition: ${result.getError()}`)
|
||||||
|
|
||||||
await this.domainEventPublisher.publish(
|
await this.domainEventPublisher.publish(
|
||||||
this.domainEventFactory.createTransitionStatusUpdatedEvent({
|
this.domainEventFactory.createTransitionStatusUpdatedEvent({
|
||||||
@@ -88,7 +88,7 @@ export class TransitionRequestedEventHandler implements DomainEventHandlerInterf
|
|||||||
|
|
||||||
if (totalRevisionsCountForUserInPrimary > 0) {
|
if (totalRevisionsCountForUserInPrimary > 0) {
|
||||||
this.logger.info(
|
this.logger.info(
|
||||||
`User ${userUuid.value} has ${totalRevisionsCountForUserInPrimary} revisions in primary database.`,
|
`[${userUuid.value}] User has ${totalRevisionsCountForUserInPrimary} revisions in primary database.`,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,9 +98,7 @@ export class TransitionRequestedEventHandler implements DomainEventHandlerInterf
|
|||||||
private async getUserUuidFromEvent(event: TransitionRequestedEvent): Promise<Uuid | null> {
|
private async getUserUuidFromEvent(event: TransitionRequestedEvent): Promise<Uuid | null> {
|
||||||
const userUuidOrError = Uuid.create(event.payload.userUuid)
|
const userUuidOrError = Uuid.create(event.payload.userUuid)
|
||||||
if (userUuidOrError.isFailed()) {
|
if (userUuidOrError.isFailed()) {
|
||||||
this.logger.error(
|
this.logger.error(`[${event.payload.userUuid}] Failed to transition revisions: ${userUuidOrError.getError()}`)
|
||||||
`Failed to transition revisions for user ${event.payload.userUuid}: ${userUuidOrError.getError()}`,
|
|
||||||
)
|
|
||||||
await this.domainEventPublisher.publish(
|
await this.domainEventPublisher.publish(
|
||||||
this.domainEventFactory.createTransitionStatusUpdatedEvent({
|
this.domainEventFactory.createTransitionStatusUpdatedEvent({
|
||||||
userUuid: event.payload.userUuid,
|
userUuid: event.payload.userUuid,
|
||||||
|
|||||||
@@ -13,5 +13,5 @@ export interface RevisionRepositoryInterface {
|
|||||||
updateUserUuid(itemUuid: Uuid, userUuid: Uuid): Promise<void>
|
updateUserUuid(itemUuid: Uuid, userUuid: Uuid): Promise<void>
|
||||||
findByUserUuid(dto: { userUuid: Uuid; offset?: number; limit?: number }): Promise<Array<Revision>>
|
findByUserUuid(dto: { userUuid: Uuid; offset?: number; limit?: number }): Promise<Array<Revision>>
|
||||||
insert(revision: Revision): Promise<boolean>
|
insert(revision: Revision): Promise<boolean>
|
||||||
clearSharedVaultAndKeySystemAssociations(itemUuid: Uuid, sharedVaultUuid: Uuid): Promise<void>
|
clearSharedVaultAndKeySystemAssociations(dto: { itemUuid?: Uuid; sharedVaultUuid: Uuid }): Promise<void>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export interface TransitionRepositoryInterface {
|
||||||
|
getPagingProgress(userUuid: string): Promise<number>
|
||||||
|
setPagingProgress(userUuid: string, progress: number): Promise<void>
|
||||||
|
}
|
||||||
+11
-21
@@ -1,19 +1,14 @@
|
|||||||
import { RevisionRepositoryInterface } from '../../Revision/RevisionRepositoryInterface'
|
import { RevisionRepositoryInterface } from '../../Revision/RevisionRepositoryInterface'
|
||||||
import { RevisionRepositoryResolverInterface } from '../../Revision/RevisionRepositoryResolverInterface'
|
|
||||||
import { RemoveRevisionsFromSharedVault } from './RemoveRevisionsFromSharedVault'
|
import { RemoveRevisionsFromSharedVault } from './RemoveRevisionsFromSharedVault'
|
||||||
|
|
||||||
describe('RemoveRevisionsFromSharedVault', () => {
|
describe('RemoveRevisionsFromSharedVault', () => {
|
||||||
let revisionRepositoryResolver: RevisionRepositoryResolverInterface
|
|
||||||
let revisionRepository: RevisionRepositoryInterface
|
let revisionRepository: RevisionRepositoryInterface
|
||||||
|
|
||||||
const createUseCase = () => new RemoveRevisionsFromSharedVault(revisionRepositoryResolver)
|
const createUseCase = () => new RemoveRevisionsFromSharedVault(revisionRepository)
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
revisionRepository = {} as jest.Mocked<RevisionRepositoryInterface>
|
revisionRepository = {} as jest.Mocked<RevisionRepositoryInterface>
|
||||||
revisionRepository.clearSharedVaultAndKeySystemAssociations = jest.fn()
|
revisionRepository.clearSharedVaultAndKeySystemAssociations = jest.fn()
|
||||||
|
|
||||||
revisionRepositoryResolver = {} as jest.Mocked<RevisionRepositoryResolverInterface>
|
|
||||||
revisionRepositoryResolver.resolve = jest.fn().mockReturnValue(revisionRepository)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should clear shared vault and key system associations', async () => {
|
it('should clear shared vault and key system associations', async () => {
|
||||||
@@ -22,7 +17,16 @@ describe('RemoveRevisionsFromSharedVault', () => {
|
|||||||
await useCase.execute({
|
await useCase.execute({
|
||||||
itemUuid: '00000000-0000-0000-0000-000000000000',
|
itemUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
sharedVaultUuid: '00000000-0000-0000-0000-000000000001',
|
sharedVaultUuid: '00000000-0000-0000-0000-000000000001',
|
||||||
roleNames: ['CORE_USER'],
|
})
|
||||||
|
|
||||||
|
expect(revisionRepository.clearSharedVaultAndKeySystemAssociations).toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should clear shared vault and key system associations for all items in a vault when item uuid is not provided', async () => {
|
||||||
|
const useCase = createUseCase()
|
||||||
|
|
||||||
|
await useCase.execute({
|
||||||
|
sharedVaultUuid: '00000000-0000-0000-0000-000000000001',
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(revisionRepository.clearSharedVaultAndKeySystemAssociations).toHaveBeenCalled()
|
expect(revisionRepository.clearSharedVaultAndKeySystemAssociations).toHaveBeenCalled()
|
||||||
@@ -34,7 +38,6 @@ describe('RemoveRevisionsFromSharedVault', () => {
|
|||||||
const result = await useCase.execute({
|
const result = await useCase.execute({
|
||||||
itemUuid: '00000000-0000-0000-0000-000000000000',
|
itemUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
sharedVaultUuid: 'invalid',
|
sharedVaultUuid: 'invalid',
|
||||||
roleNames: ['CORE_USER'],
|
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(result.isFailed()).toBe(true)
|
expect(result.isFailed()).toBe(true)
|
||||||
@@ -46,19 +49,6 @@ describe('RemoveRevisionsFromSharedVault', () => {
|
|||||||
const result = await useCase.execute({
|
const result = await useCase.execute({
|
||||||
itemUuid: 'invalid',
|
itemUuid: 'invalid',
|
||||||
sharedVaultUuid: '00000000-0000-0000-0000-000000000001',
|
sharedVaultUuid: '00000000-0000-0000-0000-000000000001',
|
||||||
roleNames: ['CORE_USER'],
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(result.isFailed()).toBe(true)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should return error when role names are invalid', async () => {
|
|
||||||
const useCase = createUseCase()
|
|
||||||
|
|
||||||
const result = await useCase.execute({
|
|
||||||
itemUuid: '00000000-0000-0000-0000-000000000000',
|
|
||||||
sharedVaultUuid: '00000000-0000-0000-0000-000000000001',
|
|
||||||
roleNames: ['invalid'],
|
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(result.isFailed()).toBe(true)
|
expect(result.isFailed()).toBe(true)
|
||||||
|
|||||||
+15
-16
@@ -1,9 +1,10 @@
|
|||||||
import { Result, RoleNameCollection, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
|
import { Result, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
|
||||||
import { RevisionRepositoryResolverInterface } from '../../Revision/RevisionRepositoryResolverInterface'
|
|
||||||
import { RemoveRevisionsFromSharedVaultDTO } from './RemoveRevisionsFromSharedVaultDTO'
|
import { RemoveRevisionsFromSharedVaultDTO } from './RemoveRevisionsFromSharedVaultDTO'
|
||||||
|
import { RevisionRepositoryInterface } from '../../Revision/RevisionRepositoryInterface'
|
||||||
|
|
||||||
export class RemoveRevisionsFromSharedVault implements UseCaseInterface<void> {
|
export class RemoveRevisionsFromSharedVault implements UseCaseInterface<void> {
|
||||||
constructor(private revisionRepositoryResolver: RevisionRepositoryResolverInterface) {}
|
constructor(private revisionRepository: RevisionRepositoryInterface) {}
|
||||||
|
|
||||||
async execute(dto: RemoveRevisionsFromSharedVaultDTO): Promise<Result<void>> {
|
async execute(dto: RemoveRevisionsFromSharedVaultDTO): Promise<Result<void>> {
|
||||||
const sharedVaultUuidOrError = Uuid.create(dto.sharedVaultUuid)
|
const sharedVaultUuidOrError = Uuid.create(dto.sharedVaultUuid)
|
||||||
@@ -12,21 +13,19 @@ export class RemoveRevisionsFromSharedVault implements UseCaseInterface<void> {
|
|||||||
}
|
}
|
||||||
const sharedVaultUuid = sharedVaultUuidOrError.getValue()
|
const sharedVaultUuid = sharedVaultUuidOrError.getValue()
|
||||||
|
|
||||||
const itemUuidOrError = Uuid.create(dto.itemUuid)
|
let itemUuid: Uuid | undefined
|
||||||
if (itemUuidOrError.isFailed()) {
|
if (dto.itemUuid !== undefined) {
|
||||||
return Result.fail(itemUuidOrError.getError())
|
const itemUuidOrError = Uuid.create(dto.itemUuid)
|
||||||
|
if (itemUuidOrError.isFailed()) {
|
||||||
|
return Result.fail(itemUuidOrError.getError())
|
||||||
|
}
|
||||||
|
itemUuid = itemUuidOrError.getValue()
|
||||||
}
|
}
|
||||||
const itemUuid = itemUuidOrError.getValue()
|
|
||||||
|
|
||||||
const roleNamesOrError = RoleNameCollection.create(dto.roleNames)
|
await this.revisionRepository.clearSharedVaultAndKeySystemAssociations({
|
||||||
if (roleNamesOrError.isFailed()) {
|
itemUuid,
|
||||||
return Result.fail(roleNamesOrError.getError())
|
sharedVaultUuid,
|
||||||
}
|
})
|
||||||
const roleNames = roleNamesOrError.getValue()
|
|
||||||
|
|
||||||
const revisionRepository = this.revisionRepositoryResolver.resolve(roleNames)
|
|
||||||
|
|
||||||
await revisionRepository.clearSharedVaultAndKeySystemAssociations(itemUuid, sharedVaultUuid)
|
|
||||||
|
|
||||||
return Result.ok()
|
return Result.ok()
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-2
@@ -1,5 +1,4 @@
|
|||||||
export interface RemoveRevisionsFromSharedVaultDTO {
|
export interface RemoveRevisionsFromSharedVaultDTO {
|
||||||
itemUuid: string
|
itemUuid?: string
|
||||||
sharedVaultUuid: string
|
sharedVaultUuid: string
|
||||||
roleNames: string[]
|
|
||||||
}
|
}
|
||||||
|
|||||||
+72
-200
@@ -5,108 +5,62 @@ 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,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async execute(dto: TransitionRevisionsFromPrimaryToSecondaryDatabaseForUserDTO): Promise<Result<void>> {
|
async execute(dto: TransitionRevisionsFromPrimaryToSecondaryDatabaseForUserDTO): Promise<Result<void>> {
|
||||||
this.logger.info(`Transitioning revisions for user ${dto.userUuid}`)
|
this.logger.info(`[${dto.userUuid}] Transitioning revisions for user`)
|
||||||
|
|
||||||
if (this.secondRevisionsRepository === null) {
|
if (this.secondRevisionsRepository === null) {
|
||||||
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: Revision[] = []
|
|
||||||
if (await this.hasAlreadyDataInSecondaryDatabase(userUuid)) {
|
|
||||||
const { alreadyExistingInPrimary, newRevisionsInSecondary, updatedInSecondary } =
|
|
||||||
await this.getNewRevisionsCreatedInSecondaryDatabase(userUuid)
|
|
||||||
|
|
||||||
for (const existingRevision of alreadyExistingInPrimary) {
|
|
||||||
this.logger.info(`Removing revision ${existingRevision.id.toString()} from secondary database`)
|
|
||||||
await (this.secondRevisionsRepository as RevisionRepositoryInterface).removeOneByUuid(
|
|
||||||
Uuid.create(existingRevision.id.toString()).getValue(),
|
|
||||||
userUuid,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newRevisionsInSecondary.length > 0) {
|
|
||||||
this.logger.info(
|
|
||||||
`Found ${newRevisionsInSecondary.length} new revisions in secondary database for user ${userUuid.value}`,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
newRevisionsInSecondaryCount = newRevisionsInSecondary.length
|
|
||||||
|
|
||||||
if (updatedInSecondary.length > 0) {
|
|
||||||
this.logger.info(
|
|
||||||
`Found ${updatedInSecondary.length} updated revisions in secondary database for user ${userUuid.value}`,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
updatedRevisionsInSecondary = updatedInSecondary
|
|
||||||
}
|
|
||||||
|
|
||||||
const updatedRevisionsInSecondaryCount = updatedRevisionsInSecondary.length
|
|
||||||
|
|
||||||
await this.allowForSecondaryDatabaseToCatchUp()
|
|
||||||
|
|
||||||
const migrationTimeStart = this.timer.getTimestampInMicroseconds()
|
const migrationTimeStart = this.timer.getTimestampInMicroseconds()
|
||||||
|
|
||||||
this.logger.debug(`Transitioning revisions for user ${userUuid.value}`)
|
this.logger.info(`[${dto.userUuid}] Migrating revisions`)
|
||||||
|
|
||||||
const migrationResult = await this.migrateRevisionsForUser(userUuid, updatedRevisionsInSecondary)
|
const migrationResult = await this.migrateRevisionsForUser(userUuid)
|
||||||
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(
|
|
||||||
`Failed to clean up secondary database revisions for user ${userUuid.value}: ${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,
|
|
||||||
)
|
)
|
||||||
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(
|
|
||||||
`Failed to clean up secondary database revisions for user ${userUuid.value}: ${cleanupResult.getError()}`,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Result.fail(integrityCheckResult.getError())
|
return Result.fail(integrityCheckResult.getError())
|
||||||
}
|
}
|
||||||
|
|
||||||
const cleanupResult = await this.deleteRevisionsForUser(userUuid, this.primaryRevisionsRepository)
|
const cleanupResult = await this.deleteRevisionsForUser(userUuid, this.primaryRevisionsRepository)
|
||||||
if (cleanupResult.isFailed()) {
|
if (cleanupResult.isFailed()) {
|
||||||
this.logger.error(
|
this.logger.error(`[${dto.userUuid}] Failed to clean up primary database revisions: ${cleanupResult.getError()}`)
|
||||||
`Failed to clean up primary database revisions for user ${userUuid.value}: ${cleanupResult.getError()}`,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const migrationTimeEnd = this.timer.getTimestampInMicroseconds()
|
const migrationTimeEnd = this.timer.getTimestampInMicroseconds()
|
||||||
@@ -115,21 +69,29 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
|
|||||||
const migrationDurationTimeStructure = this.timer.convertMicrosecondsToTimeStructure(migrationDuration)
|
const migrationDurationTimeStructure = this.timer.convertMicrosecondsToTimeStructure(migrationDuration)
|
||||||
|
|
||||||
this.logger.info(
|
this.logger.info(
|
||||||
`Transitioned revisions for user ${userUuid.value} in ${migrationDurationTimeStructure.hours}h ${migrationDurationTimeStructure.minutes}m ${migrationDurationTimeStructure.seconds}s ${migrationDurationTimeStructure.milliseconds}ms`,
|
`[${dto.userUuid}] Transitioned revisions in ${migrationDurationTimeStructure.hours}h ${migrationDurationTimeStructure.minutes}m ${migrationDurationTimeStructure.seconds}s ${migrationDurationTimeStructure.milliseconds}ms`,
|
||||||
)
|
)
|
||||||
|
|
||||||
return Result.ok()
|
return Result.ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
private async migrateRevisionsForUser(
|
private async migrateRevisionsForUser(userUuid: Uuid): Promise<Result<string[]>> {
|
||||||
userUuid: Uuid,
|
|
||||||
updatedRevisionsInSecondary: Revision[],
|
|
||||||
): 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)
|
||||||
let totalRevisionsCountTransitionedToSecondary = 0
|
|
||||||
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,
|
||||||
@@ -137,34 +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(
|
this.secondRevisionsRepository as RevisionRepositoryInterface
|
||||||
(updatedRevision) => updatedRevision.id.toString() === revision.id.toString(),
|
).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(
|
||||||
`Skipping saving revision ${revision.id.toString()} as it was updated in secondary database`,
|
`[${
|
||||||
|
userUuid.value
|
||||||
|
}] 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,
|
||||||
|
)
|
||||||
|
await this.allowForSecondaryDatabaseToCatchUp()
|
||||||
}
|
}
|
||||||
|
|
||||||
this.logger.debug(
|
|
||||||
`Transitioning revision #${
|
|
||||||
totalRevisionsCountTransitionedToSecondary + 1
|
|
||||||
}: ${revision.id.toString()} to secondary database`,
|
|
||||||
)
|
|
||||||
|
|
||||||
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`)
|
||||||
}
|
}
|
||||||
totalRevisionsCountTransitionedToSecondary++
|
|
||||||
} 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
|
||||||
}`,
|
}`,
|
||||||
@@ -173,9 +145,7 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.logger.debug(`Transitioned ${totalRevisionsCountTransitionedToSecondary} revisions to secondary database`)
|
return Result.ok(revisionsToSkipInIntegrityCheck)
|
||||||
|
|
||||||
return Result.ok()
|
|
||||||
} 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}`)
|
||||||
}
|
}
|
||||||
@@ -186,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()
|
||||||
@@ -199,101 +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(
|
|
||||||
`User ${userUuid.value} has already ${totalRevisionsCountForUserInSecondary} revisions in secondary database`,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return hasAlreadyDataInSecondaryDatabase
|
|
||||||
}
|
|
||||||
|
|
||||||
private async getNewRevisionsCreatedInSecondaryDatabase(userUuid: Uuid): Promise<{
|
|
||||||
alreadyExistingInPrimary: Revision[]
|
|
||||||
newRevisionsInSecondary: Revision[]
|
|
||||||
updatedInSecondary: Revision[]
|
|
||||||
}> {
|
|
||||||
const revisions = await (this.secondRevisionsRepository as RevisionRepositoryInterface).findByUserUuid({
|
|
||||||
userUuid: userUuid,
|
|
||||||
})
|
|
||||||
|
|
||||||
const alreadyExistingInPrimary: Revision[] = []
|
|
||||||
const newRevisionsInSecondary: Revision[] = []
|
|
||||||
const updatedInSecondary: Revision[] = []
|
|
||||||
|
|
||||||
for (const revision of revisions) {
|
|
||||||
const { revisionInPrimary, newerRevisionInSecondary } =
|
|
||||||
await this.checkIfRevisionExistsInPrimaryDatabase(revision)
|
|
||||||
if (revisionInPrimary !== null) {
|
|
||||||
alreadyExistingInPrimary.push(revision)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if (newerRevisionInSecondary !== null) {
|
|
||||||
updatedInSecondary.push(newerRevisionInSecondary)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if (revisionInPrimary === null && newerRevisionInSecondary === null) {
|
|
||||||
newRevisionsInSecondary.push(revision)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
alreadyExistingInPrimary: alreadyExistingInPrimary,
|
|
||||||
newRevisionsInSecondary: newRevisionsInSecondary,
|
|
||||||
updatedInSecondary: updatedInSecondary,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async checkIfRevisionExistsInPrimaryDatabase(
|
|
||||||
revision: Revision,
|
|
||||||
): Promise<{ revisionInPrimary: 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 {
|
|
||||||
revisionInPrimary: null,
|
|
||||||
newerRevisionInSecondary: null,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!revision.isIdenticalTo(revisionInPrimary)) {
|
|
||||||
this.logger.error(
|
|
||||||
`Revision ${revision.id.toString()} is not identical in primary and secondary database. Revision in secondary database: ${JSON.stringify(
|
|
||||||
revision,
|
|
||||||
)}, revision in primary database: ${JSON.stringify(revisionInPrimary)}`,
|
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
|
||||||
revisionInPrimary: null,
|
|
||||||
newerRevisionInSecondary:
|
|
||||||
revision.props.dates.updatedAt > revisionInPrimary.props.dates.updatedAt ? revision : null,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
revisionInPrimary: revisionInPrimary,
|
|
||||||
newerRevisionInSecondary: null,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async checkIntegrityBetweenPrimaryAndSecondaryDatabase(
|
private async checkIntegrityBetweenPrimaryAndSecondaryDatabase(
|
||||||
userUuid: Uuid,
|
userUuid: Uuid,
|
||||||
newRevisionsInSecondaryCount: number,
|
revisionsToSkipInIntegrityCheck: string[],
|
||||||
updatedRevisionsInSecondary: Revision[],
|
|
||||||
): 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++) {
|
||||||
@@ -320,14 +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(
|
|
||||||
(updatedRevision) => updatedRevision.id.toString() === revision.id.toString(),
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
this.logger.info(
|
|
||||||
`Skipping integrity check for revision ${revision.id.toString()} as it was updated in secondary database`,
|
|
||||||
)
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -341,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(
|
||||||
|
|||||||
@@ -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,5 +1,5 @@
|
|||||||
import { MapperInterface, Uuid } from '@standardnotes/domain-core'
|
import { MapperInterface, Uuid } from '@standardnotes/domain-core'
|
||||||
import { MongoRepository } from 'typeorm'
|
import { MongoRepository, ObjectLiteral } from 'typeorm'
|
||||||
import { BSON } from 'mongodb'
|
import { BSON } from 'mongodb'
|
||||||
import { Logger } from 'winston'
|
import { Logger } from 'winston'
|
||||||
|
|
||||||
@@ -16,19 +16,25 @@ export class MongoDBRevisionRepository implements RevisionRepositoryInterface {
|
|||||||
private logger: Logger,
|
private logger: Logger,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async clearSharedVaultAndKeySystemAssociations(itemUuid: Uuid, sharedVaultUuid: Uuid): Promise<void> {
|
async clearSharedVaultAndKeySystemAssociations(dto: { itemUuid?: Uuid; sharedVaultUuid: Uuid }): Promise<void> {
|
||||||
await this.mongoRepository.updateMany(
|
let query: ObjectLiteral
|
||||||
{
|
if (dto.itemUuid !== undefined) {
|
||||||
itemUuid: { $eq: itemUuid.value },
|
query = {
|
||||||
sharedVaultUuid: { $eq: sharedVaultUuid.value },
|
itemUuid: { $eq: dto.itemUuid.value },
|
||||||
|
sharedVaultUuid: { $eq: dto.sharedVaultUuid.value },
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
query = {
|
||||||
|
sharedVaultUuid: { $eq: dto.sharedVaultUuid.value },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.mongoRepository.updateMany(query, {
|
||||||
|
$set: {
|
||||||
|
sharedVaultUuid: null,
|
||||||
|
keySystemIdentifier: null,
|
||||||
},
|
},
|
||||||
{
|
})
|
||||||
$set: {
|
|
||||||
sharedVaultUuid: null,
|
|
||||||
keySystemIdentifier: null,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async countByUserUuid(userUuid: Uuid): Promise<number> {
|
async countByUserUuid(userUuid: Uuid): Promise<number> {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export class SQLLegacyRevisionRepository implements RevisionRepositoryInterface
|
|||||||
protected logger: Logger,
|
protected logger: Logger,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async clearSharedVaultAndKeySystemAssociations(_itemUuid: Uuid, _sharedVaultUuid: Uuid): Promise<void> {
|
async clearSharedVaultAndKeySystemAssociations(_dto: { itemUuid?: Uuid; sharedVaultUuid: Uuid }): Promise<void> {
|
||||||
this.logger.error('Method clearSharedVaultAndKeySystemAssociations not implemented.')
|
this.logger.error('Method clearSharedVaultAndKeySystemAssociations not implemented.')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -66,19 +66,27 @@ export class SQLRevisionRepository extends SQLLegacyRevisionRepository {
|
|||||||
return this.revisionMapper.toDomain(sqlRevision)
|
return this.revisionMapper.toDomain(sqlRevision)
|
||||||
}
|
}
|
||||||
|
|
||||||
override async clearSharedVaultAndKeySystemAssociations(itemUuid: Uuid, sharedVaultUuid: Uuid): Promise<void> {
|
override async clearSharedVaultAndKeySystemAssociations(dto: {
|
||||||
await this.ormRepository
|
itemUuid?: Uuid
|
||||||
.createQueryBuilder()
|
sharedVaultUuid: Uuid
|
||||||
.update()
|
}): Promise<void> {
|
||||||
.set({
|
const queryBuilder = this.ormRepository.createQueryBuilder().update().set({
|
||||||
sharedVaultUuid: null,
|
sharedVaultUuid: null,
|
||||||
keySystemIdentifier: null,
|
keySystemIdentifier: null,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (dto.itemUuid !== undefined) {
|
||||||
|
queryBuilder.where('item_uuid = :itemUuid AND shared_vault_uuid = :sharedVaultUuid', {
|
||||||
|
itemUuid: dto.itemUuid.value,
|
||||||
|
sharedVaultUuid: dto.sharedVaultUuid.value,
|
||||||
})
|
})
|
||||||
.where('item_uuid = :itemUuid AND shared_vault_uuid = :sharedVaultUuid', {
|
} else {
|
||||||
itemUuid: itemUuid.value,
|
queryBuilder.where('shared_vault_uuid = :sharedVaultUuid', {
|
||||||
sharedVaultUuid: sharedVaultUuid.value,
|
sharedVaultUuid: dto.sharedVaultUuid.value,
|
||||||
})
|
})
|
||||||
.execute()
|
}
|
||||||
|
|
||||||
|
await queryBuilder.execute()
|
||||||
}
|
}
|
||||||
|
|
||||||
override async findMetadataByItemId(
|
override async findMetadataByItemId(
|
||||||
|
|||||||
@@ -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.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)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/scheduler-server
|
||||||
|
|
||||||
|
## [1.20.51](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.20.50...@standardnotes/scheduler-server@1.20.51) (2023-09-20)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/scheduler-server
|
||||||
|
|
||||||
|
## [1.20.50](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.20.49...@standardnotes/scheduler-server@1.20.50) (2023-09-20)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/scheduler-server
|
||||||
|
|
||||||
|
## [1.20.49](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.20.48...@standardnotes/scheduler-server@1.20.49) (2023-09-19)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/scheduler-server
|
||||||
|
|
||||||
## [1.20.48](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.20.47...@standardnotes/scheduler-server@1.20.48) (2023-09-18)
|
## [1.20.48](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.20.47...@standardnotes/scheduler-server@1.20.48) (2023-09-18)
|
||||||
|
|
||||||
**Note:** Version bump only for package @standardnotes/scheduler-server
|
**Note:** Version bump only for package @standardnotes/scheduler-server
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/scheduler-server",
|
"name": "@standardnotes/scheduler-server",
|
||||||
"version": "1.20.48",
|
"version": "1.20.55",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0 <21.0.0"
|
"node": ">=18.0.0 <21.0.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,6 +3,26 @@
|
|||||||
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.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)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/settings
|
||||||
|
|
||||||
|
## [1.21.36](https://github.com/standardnotes/server/compare/@standardnotes/settings@1.21.35...@standardnotes/settings@1.21.36) (2023-09-20)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/settings
|
||||||
|
|
||||||
|
## [1.21.35](https://github.com/standardnotes/server/compare/@standardnotes/settings@1.21.34...@standardnotes/settings@1.21.35) (2023-09-20)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/settings
|
||||||
|
|
||||||
|
## [1.21.34](https://github.com/standardnotes/server/compare/@standardnotes/settings@1.21.33...@standardnotes/settings@1.21.34) (2023-09-19)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/settings
|
||||||
|
|
||||||
## [1.21.33](https://github.com/standardnotes/server/compare/@standardnotes/settings@1.21.32...@standardnotes/settings@1.21.33) (2023-09-18)
|
## [1.21.33](https://github.com/standardnotes/server/compare/@standardnotes/settings@1.21.32...@standardnotes/settings@1.21.33) (2023-09-18)
|
||||||
|
|
||||||
**Note:** Version bump only for package @standardnotes/settings
|
**Note:** Version bump only for package @standardnotes/settings
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/settings",
|
"name": "@standardnotes/settings",
|
||||||
"version": "1.21.33",
|
"version": "1.21.38",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0 <21.0.0"
|
"node": ">=18.0.0 <21.0.0"
|
||||||
},
|
},
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user