mirror of
https://github.com/standardnotes/server
synced 2026-04-24 15:01:42 -04:00
Compare commits
62 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3d6559921b | |||
| 15a7f0e71a | |||
| 3e56243d6f | |||
| 032fcb938d | |||
| e98393452b | |||
| 302b624504 | |||
| e00d9d2ca0 | |||
| 9ab4601c8d | |||
| 19e43bdb1a | |||
| 49832e7944 | |||
| 916e98936a | |||
| 31d1eef7f7 | |||
| 2648d9a813 | |||
| b24b576209 | |||
| faee38bffd | |||
| 65f3503fe8 | |||
| 054023b791 | |||
| 383c3a68fa | |||
| 7d22b1c15c | |||
| c71e7cd926 | |||
| 83ad069c5d | |||
| 081108d9ba | |||
| 8f3df56a2b | |||
| d02124f4e5 | |||
| 09e351fedb | |||
| ad4b85b095 | |||
| 0bf7d8beae | |||
| 1ae7cca394 | |||
| bc1c7a8ae1 | |||
| c22c5e4584 | |||
| ac3646836c | |||
| 7a31ab75d6 | |||
| c49dc35ab5 | |||
| 06cedd11d8 | |||
| f496376fb3 | |||
| 091e2a57e8 | |||
| 0d40ef6796 | |||
| 1be33ba4c3 | |||
| aaeb311928 | |||
| a7a38c07ac | |||
| 56f49752b4 | |||
| 892d8b6fe2 | |||
| cec2005436 | |||
| 0eb86c0096 | |||
| b8e39d76c1 | |||
| 1c3ff526b7 | |||
| 373767248c | |||
| d7965b2748 | |||
| cbcd2ec87a | |||
| c74d37fc48 | |||
| 66f9352a06 | |||
| e5eef3aba0 | |||
| d261c81cd0 | |||
| 634e3bbb67 | |||
| f8c9e67063 | |||
| 18eddea6f8 | |||
| c6d655c5f5 | |||
| 46867c1a4d | |||
| d29903bab6 | |||
| 3415cae093 | |||
| 408fd5a0c6 | |||
| 0a16ee64fe |
+7
-1
@@ -10,7 +10,7 @@ REDIS_HOST=cache
|
||||
AUTH_SERVER_ACCESS_TOKEN_AGE=4
|
||||
AUTH_SERVER_REFRESH_TOKEN_AGE=10
|
||||
AUTH_SERVER_EPHEMERAL_SESSION_AGE=300
|
||||
SYNCING_SERVER_REVISIONS_FREQUENCY=5
|
||||
SYNCING_SERVER_REVISIONS_FREQUENCY=2
|
||||
AUTH_SERVER_LOG_LEVEL=debug
|
||||
SYNCING_SERVER_LOG_LEVEL=debug
|
||||
FILES_SERVER_LOG_LEVEL=debug
|
||||
@@ -22,6 +22,12 @@ MYSQL_USER=std_notes_user
|
||||
MYSQL_PASSWORD=changeme123
|
||||
MYSQL_ROOT_PASSWORD=changeme123
|
||||
|
||||
MONGO_HOST=secondary_db
|
||||
MONGO_PORT=27017
|
||||
MONGO_USERNAME=standardnotes
|
||||
MONGO_PASSWORD=standardnotes
|
||||
MONGO_DATABASE=standardnotes
|
||||
|
||||
AUTH_JWT_SECRET=f95259c5e441f5a4646d76422cfb3df4c4488842901aa50b6c51b8be2e0040e9
|
||||
AUTH_SERVER_ENCRYPTION_SERVER_KEY=1087415dfde3093797f9a7ca93a49e7d7aa1861735eb0d32aae9c303b8c3d060
|
||||
VALET_TOKEN_SECRET=4b886819ebe1e908077c6cae96311b48a8416bd60cc91c03060e15bdf6b30d1f
|
||||
|
||||
@@ -20,6 +20,11 @@ on:
|
||||
jobs:
|
||||
e2e:
|
||||
name: (Docker) E2E Test Suite
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
secondary_db_enabled: [true, false]
|
||||
transition_mode_enabled: [true, false]
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
services:
|
||||
@@ -45,12 +50,23 @@ jobs:
|
||||
env:
|
||||
DB_TYPE: mysql
|
||||
CACHE_TYPE: redis
|
||||
SECONDARY_DB_ENABLED: ${{ matrix.secondary_db_enabled }}
|
||||
TRANSITION_MODE_ENABLED: ${{ matrix.transition_mode_enabled }}
|
||||
|
||||
- name: Wait for server to start
|
||||
run: docker/is-available.sh http://localhost:3123 $(pwd)/logs
|
||||
|
||||
- name: Define if vault tests are enabled
|
||||
id: vaults
|
||||
run: |
|
||||
if [ "${{ matrix.secondary_db_enabled }}" = "true" ] && [ "${{ matrix.transition_mode_enabled }}" = "true" ]; then
|
||||
echo "vault-tests=enabled" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "vault-tests=disabled" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Run E2E Test Suite
|
||||
run: yarn dlx mocha-headless-chrome --timeout 1800000 -f http://localhost:9001/mocha/test.html
|
||||
run: yarn dlx mocha-headless-chrome --timeout 1800000 -f http://localhost:9001/mocha/test.html?vaults=${{ steps.vaults.outputs.vault-tests }}
|
||||
|
||||
- name: Show logs on failure
|
||||
if: ${{ failure() }}
|
||||
@@ -67,13 +83,8 @@ jobs:
|
||||
matrix:
|
||||
db_type: [mysql, sqlite]
|
||||
cache_type: [redis, memory]
|
||||
include:
|
||||
- cache_type: redis
|
||||
db_type: mysql
|
||||
redis_port: 6380
|
||||
- cache_type: redis
|
||||
db_type: sqlite
|
||||
redis_port: 6381
|
||||
secondary_db_enabled: [true, false]
|
||||
transition_mode_enabled: [true, false]
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -85,16 +96,24 @@ jobs:
|
||||
cache:
|
||||
image: redis
|
||||
ports:
|
||||
- ${{ matrix.redis_port }}:6379
|
||||
- 6379:6379
|
||||
db:
|
||||
image: mysql
|
||||
ports:
|
||||
- 3307:3306
|
||||
- 3306:3306
|
||||
env:
|
||||
MYSQL_ROOT_PASSWORD: root
|
||||
MYSQL_DATABASE: standardnotes_${{ matrix.cache_type }}
|
||||
MYSQL_DATABASE: standardnotes
|
||||
MYSQL_USER: standardnotes
|
||||
MYSQL_PASSWORD: standardnotes
|
||||
secondary_db:
|
||||
image: mongo:5.0
|
||||
ports:
|
||||
- 27017:27017
|
||||
env:
|
||||
MONGO_INITDB_ROOT_USERNAME: standardnotes
|
||||
MONGO_INITDB_ROOT_PASSWORD: standardnotes
|
||||
MONGO_INITDB_DATABASE: standardnotes
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
@@ -123,16 +142,23 @@ jobs:
|
||||
sed -i "s/VALET_TOKEN_SECRET=/VALET_TOKEN_SECRET=$(openssl rand -hex 32)/g" packages/home-server/.env
|
||||
echo "ACCESS_TOKEN_AGE=4" >> packages/home-server/.env
|
||||
echo "REFRESH_TOKEN_AGE=10" >> packages/home-server/.env
|
||||
echo "REVISIONS_FREQUENCY=5" >> packages/home-server/.env
|
||||
echo "REVISIONS_FREQUENCY=2" >> packages/home-server/.env
|
||||
echo "DB_HOST=localhost" >> packages/home-server/.env
|
||||
echo "DB_PORT=3307" >> packages/home-server/.env
|
||||
echo "DB_DATABASE=standardnotes_${{ matrix.cache_type }}" >> packages/home-server/.env
|
||||
echo "DB_SQLITE_DATABASE_PATH=sqlite_${{ matrix.cache_type }}.db" >> packages/home-server/.env
|
||||
echo "DB_PORT=3306" >> packages/home-server/.env
|
||||
echo "DB_DATABASE=standardnotes" >> packages/home-server/.env
|
||||
echo "DB_SQLITE_DATABASE_PATH=homeserver.db" >> packages/home-server/.env
|
||||
echo "DB_USERNAME=standardnotes" >> packages/home-server/.env
|
||||
echo "DB_PASSWORD=standardnotes" >> packages/home-server/.env
|
||||
echo "DB_TYPE=${{ matrix.db_type }}" >> packages/home-server/.env
|
||||
echo "REDIS_URL=redis://localhost:${{ matrix.redis_port }}" >> 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 "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_PORT=27017" >> packages/home-server/.env
|
||||
echo "MONGO_DATABASE=standardnotes" >> packages/home-server/.env
|
||||
echo "MONGO_USERNAME=standardnotes" >> packages/home-server/.env
|
||||
echo "MONGO_PASSWORD=standardnotes" >> packages/home-server/.env
|
||||
echo "FILES_SERVER_URL=http://localhost:3123" >> packages/home-server/.env
|
||||
echo "E2E_TESTING=true" >> packages/home-server/.env
|
||||
|
||||
@@ -144,8 +170,17 @@ jobs:
|
||||
- name: Wait for server to start
|
||||
run: for i in {1..30}; do curl -s http://localhost:3123/healthcheck && break || sleep 1; done
|
||||
|
||||
- name: Define if vault tests are enabled
|
||||
id: vaults
|
||||
run: |
|
||||
if [ "${{ matrix.secondary_db_enabled }}" = "true" ] && [ "${{ matrix.transition_mode_enabled }}" = "true" ]; then
|
||||
echo "vault-tests=enabled" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "vault-tests=disabled" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Run E2E Test Suite
|
||||
run: yarn dlx mocha-headless-chrome --timeout 1800000 -f http://localhost:9001/mocha/test.html
|
||||
run: yarn dlx mocha-headless-chrome --timeout 1800000 -f http://localhost:9001/mocha/test.html?vaults=${{ steps.vaults.outputs.vault-tests }}
|
||||
|
||||
- name: Show logs on failure
|
||||
if: ${{ failure() }}
|
||||
|
||||
@@ -5191,6 +5191,7 @@ const RAW_RUNTIME_STATE =
|
||||
["inversify-express-utils", "npm:6.4.3"],\
|
||||
["jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.5.0"],\
|
||||
["jsonwebtoken", "npm:9.0.0"],\
|
||||
["mongodb", "virtual:67ad3a1ca34e24ce4821cc48979e98af0c3e5dd7aabc7ad0b5d22d1d977d6f943f81c9f141a420105ebdc61ef777e508a96c7946081decd98f8c30543d468b33#npm:5.7.0"],\
|
||||
["mysql2", "npm:3.3.3"],\
|
||||
["newrelic", "npm:10.1.2"],\
|
||||
["nodemon", "npm:2.0.22"],\
|
||||
@@ -5201,7 +5202,7 @@ const RAW_RUNTIME_STATE =
|
||||
["semver", "npm:7.5.1"],\
|
||||
["sqlite3", "virtual:31b5a94a105c89c9294c3d524a7f8929fe63ee5a2efadf21951ca4c0cfd2ecf02e8f4ef5a066bbda091f1e3a56e57c6749069a080618c96b22e51131a330fc4a#npm:5.1.6"],\
|
||||
["ts-jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.1.0"],\
|
||||
["typeorm", "virtual:365b8c88cdf194291829ee28b79556e2328175d26a621363e703848100bea0042e9500db2a1206c9bbc3a4a76a1d169639ef774b2ea3a1a98584a9936b58c6be#npm:0.3.16"],\
|
||||
["typeorm", "virtual:67ad3a1ca34e24ce4821cc48979e98af0c3e5dd7aabc7ad0b5d22d1d977d6f943f81c9f141a420105ebdc61ef777e508a96c7946081decd98f8c30543d468b33#npm:0.3.16"],\
|
||||
["typescript", "patch:typescript@npm%3A5.0.4#optional!builtin<compat/typescript>::version=5.0.4&hash=b5f058"],\
|
||||
["ua-parser-js", "npm:1.0.35"],\
|
||||
["uuid", "npm:9.0.0"],\
|
||||
@@ -5869,6 +5870,26 @@ const RAW_RUNTIME_STATE =
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["@types/webidl-conversions", [\
|
||||
["npm:7.0.0", {\
|
||||
"packageLocation": "./.yarn/cache/@types-webidl-conversions-npm-7.0.0-0903313151-86c337dc1e.zip/node_modules/@types/webidl-conversions/",\
|
||||
"packageDependencies": [\
|
||||
["@types/webidl-conversions", "npm:7.0.0"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["@types/whatwg-url", [\
|
||||
["npm:8.2.2", {\
|
||||
"packageLocation": "./.yarn/cache/@types-whatwg-url-npm-8.2.2-54c5c24e6c-25f20f5649.zip/node_modules/@types/whatwg-url/",\
|
||||
"packageDependencies": [\
|
||||
["@types/whatwg-url", "npm:8.2.2"],\
|
||||
["@types/node", "npm:20.2.5"],\
|
||||
["@types/webidl-conversions", "npm:7.0.0"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["@types/yargs", [\
|
||||
["npm:17.0.24", {\
|
||||
"packageLocation": "./.yarn/cache/@types-yargs-npm-17.0.24-b034cf1d8b-f7811cc0b9.zip/node_modules/@types/yargs/",\
|
||||
@@ -7074,6 +7095,15 @@ const RAW_RUNTIME_STATE =
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["bson", [\
|
||||
["npm:5.4.0", {\
|
||||
"packageLocation": "./.yarn/cache/bson-npm-5.4.0-2f854c8216-2c913a45c0.zip/node_modules/bson/",\
|
||||
"packageDependencies": [\
|
||||
["bson", "npm:5.4.0"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["buffer", [\
|
||||
["npm:5.7.1", {\
|
||||
"packageLocation": "./.yarn/cache/buffer-npm-5.7.1-513ef8259e-8e611bed4d.zip/node_modules/buffer/",\
|
||||
@@ -11932,6 +11962,15 @@ const RAW_RUNTIME_STATE =
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["memory-pager", [\
|
||||
["npm:1.5.0", {\
|
||||
"packageLocation": "./.yarn/cache/memory-pager-npm-1.5.0-46e20e6c81-6b00ff499b.zip/node_modules/memory-pager/",\
|
||||
"packageDependencies": [\
|
||||
["memory-pager", "npm:1.5.0"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["meow", [\
|
||||
["npm:8.1.2", {\
|
||||
"packageLocation": "./.yarn/cache/meow-npm-8.1.2-bcfe48d4f3-e36c879078.zip/node_modules/meow/",\
|
||||
@@ -12290,6 +12329,59 @@ const RAW_RUNTIME_STATE =
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["mongodb", [\
|
||||
["npm:5.7.0", {\
|
||||
"packageLocation": "./.yarn/cache/mongodb-npm-5.7.0-c5e415a2e7-23a291ffe7.zip/node_modules/mongodb/",\
|
||||
"packageDependencies": [\
|
||||
["mongodb", "npm:5.7.0"]\
|
||||
],\
|
||||
"linkType": "SOFT"\
|
||||
}],\
|
||||
["virtual:67ad3a1ca34e24ce4821cc48979e98af0c3e5dd7aabc7ad0b5d22d1d977d6f943f81c9f141a420105ebdc61ef777e508a96c7946081decd98f8c30543d468b33#npm:5.7.0", {\
|
||||
"packageLocation": "./.yarn/__virtual__/mongodb-virtual-eb0cd47e23/0/cache/mongodb-npm-5.7.0-c5e415a2e7-23a291ffe7.zip/node_modules/mongodb/",\
|
||||
"packageDependencies": [\
|
||||
["mongodb", "virtual:67ad3a1ca34e24ce4821cc48979e98af0c3e5dd7aabc7ad0b5d22d1d977d6f943f81c9f141a420105ebdc61ef777e508a96c7946081decd98f8c30543d468b33#npm:5.7.0"],\
|
||||
["@aws-sdk/credential-providers", null],\
|
||||
["@mongodb-js/zstd", null],\
|
||||
["@types/aws-sdk__credential-providers", null],\
|
||||
["@types/kerberos", null],\
|
||||
["@types/mongodb-client-encryption", null],\
|
||||
["@types/mongodb-js__zstd", null],\
|
||||
["@types/snappy", null],\
|
||||
["bson", "npm:5.4.0"],\
|
||||
["kerberos", null],\
|
||||
["mongodb-client-encryption", null],\
|
||||
["mongodb-connection-string-url", "npm:2.6.0"],\
|
||||
["saslprep", "npm:1.0.3"],\
|
||||
["snappy", null],\
|
||||
["socks", "npm:2.7.1"]\
|
||||
],\
|
||||
"packagePeers": [\
|
||||
"@aws-sdk/credential-providers",\
|
||||
"@mongodb-js/zstd",\
|
||||
"@types/aws-sdk__credential-providers",\
|
||||
"@types/kerberos",\
|
||||
"@types/mongodb-client-encryption",\
|
||||
"@types/mongodb-js__zstd",\
|
||||
"@types/snappy",\
|
||||
"kerberos",\
|
||||
"mongodb-client-encryption",\
|
||||
"snappy"\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["mongodb-connection-string-url", [\
|
||||
["npm:2.6.0", {\
|
||||
"packageLocation": "./.yarn/cache/mongodb-connection-string-url-npm-2.6.0-af011ba17f-8a9186dd1b.zip/node_modules/mongodb-connection-string-url/",\
|
||||
"packageDependencies": [\
|
||||
["mongodb-connection-string-url", "npm:2.6.0"],\
|
||||
["@types/whatwg-url", "npm:8.2.2"],\
|
||||
["whatwg-url", "npm:11.0.0"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["ms", [\
|
||||
["npm:2.0.0", {\
|
||||
"packageLocation": "./.yarn/cache/ms-npm-2.0.0-9e1101a471-de027828fc.zip/node_modules/ms/",\
|
||||
@@ -14249,6 +14341,16 @@ const RAW_RUNTIME_STATE =
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["saslprep", [\
|
||||
["npm:1.0.3", {\
|
||||
"packageLocation": "./.yarn/cache/saslprep-npm-1.0.3-8db649c346-23ebcda091.zip/node_modules/saslprep/",\
|
||||
"packageDependencies": [\
|
||||
["saslprep", "npm:1.0.3"],\
|
||||
["sparse-bitfield", "npm:3.0.3"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["schema-utils", [\
|
||||
["npm:3.1.2", {\
|
||||
"packageLocation": "./.yarn/cache/schema-utils-npm-3.1.2-d97c6dc247-11d35f997e.zip/node_modules/schema-utils/",\
|
||||
@@ -14604,6 +14706,16 @@ const RAW_RUNTIME_STATE =
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["sparse-bitfield", [\
|
||||
["npm:3.0.3", {\
|
||||
"packageLocation": "./.yarn/cache/sparse-bitfield-npm-3.0.3-cb80d0c89f-625ecdf6f4.zip/node_modules/sparse-bitfield/",\
|
||||
"packageDependencies": [\
|
||||
["sparse-bitfield", "npm:3.0.3"],\
|
||||
["memory-pager", "npm:1.5.0"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["spawn-please", [\
|
||||
["npm:2.0.1", {\
|
||||
"packageLocation": "./.yarn/cache/spawn-please-npm-2.0.1-265b6b5432-fe19a7ceb5.zip/node_modules/spawn-please/",\
|
||||
@@ -15246,6 +15358,14 @@ const RAW_RUNTIME_STATE =
|
||||
["tr46", "npm:0.0.3"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}],\
|
||||
["npm:3.0.0", {\
|
||||
"packageLocation": "./.yarn/cache/tr46-npm-3.0.0-e1ae1ea7c9-3a481676bf.zip/node_modules/tr46/",\
|
||||
"packageDependencies": [\
|
||||
["tr46", "npm:3.0.0"],\
|
||||
["punycode", "npm:2.3.0"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["treeverse", [\
|
||||
@@ -15757,6 +15877,98 @@ const RAW_RUNTIME_STATE =
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}],\
|
||||
["virtual:67ad3a1ca34e24ce4821cc48979e98af0c3e5dd7aabc7ad0b5d22d1d977d6f943f81c9f141a420105ebdc61ef777e508a96c7946081decd98f8c30543d468b33#npm:0.3.16", {\
|
||||
"packageLocation": "./.yarn/__virtual__/typeorm-virtual-13b6364fde/0/cache/typeorm-npm-0.3.16-5ac12a7afc-19803f935e.zip/node_modules/typeorm/",\
|
||||
"packageDependencies": [\
|
||||
["typeorm", "virtual:67ad3a1ca34e24ce4821cc48979e98af0c3e5dd7aabc7ad0b5d22d1d977d6f943f81c9f141a420105ebdc61ef777e508a96c7946081decd98f8c30543d468b33#npm:0.3.16"],\
|
||||
["@google-cloud/spanner", null],\
|
||||
["@sap/hana-client", null],\
|
||||
["@sqltools/formatter", "npm:1.2.5"],\
|
||||
["@types/better-sqlite3", null],\
|
||||
["@types/google-cloud__spanner", null],\
|
||||
["@types/hdb-pool", null],\
|
||||
["@types/ioredis", null],\
|
||||
["@types/mongodb", null],\
|
||||
["@types/mssql", null],\
|
||||
["@types/mysql2", null],\
|
||||
["@types/oracledb", null],\
|
||||
["@types/pg", null],\
|
||||
["@types/pg-native", null],\
|
||||
["@types/pg-query-stream", null],\
|
||||
["@types/redis", null],\
|
||||
["@types/sap__hana-client", null],\
|
||||
["@types/sql.js", null],\
|
||||
["@types/sqlite3", null],\
|
||||
["@types/ts-node", null],\
|
||||
["@types/typeorm-aurora-data-api-driver", null],\
|
||||
["app-root-path", "npm:3.1.0"],\
|
||||
["better-sqlite3", null],\
|
||||
["buffer", "npm:6.0.3"],\
|
||||
["chalk", "npm:4.1.2"],\
|
||||
["cli-highlight", "npm:2.1.11"],\
|
||||
["date-fns", "npm:2.30.0"],\
|
||||
["debug", "virtual:ac3d8e680759ce54399273724d44e041d6c9b73454d191d411a8c44bb27e22f02aaf6ed9d3ad0ac1c298eac4833cff369c9c7b84c573016112c4f84be2cd8543#npm:4.3.4"],\
|
||||
["dotenv", "npm:16.1.3"],\
|
||||
["glob", "npm:8.1.0"],\
|
||||
["hdb-pool", null],\
|
||||
["ioredis", null],\
|
||||
["mkdirp", "npm:2.1.6"],\
|
||||
["mongodb", "virtual:67ad3a1ca34e24ce4821cc48979e98af0c3e5dd7aabc7ad0b5d22d1d977d6f943f81c9f141a420105ebdc61ef777e508a96c7946081decd98f8c30543d468b33#npm:5.7.0"],\
|
||||
["mssql", null],\
|
||||
["mysql2", "npm:3.3.3"],\
|
||||
["oracledb", null],\
|
||||
["pg", null],\
|
||||
["pg-native", null],\
|
||||
["pg-query-stream", null],\
|
||||
["redis", null],\
|
||||
["reflect-metadata", "npm:0.1.13"],\
|
||||
["sha.js", "npm:2.4.11"],\
|
||||
["sql.js", null],\
|
||||
["sqlite3", "virtual:31b5a94a105c89c9294c3d524a7f8929fe63ee5a2efadf21951ca4c0cfd2ecf02e8f4ef5a066bbda091f1e3a56e57c6749069a080618c96b22e51131a330fc4a#npm:5.1.6"],\
|
||||
["ts-node", null],\
|
||||
["tslib", "npm:2.5.2"],\
|
||||
["typeorm-aurora-data-api-driver", null],\
|
||||
["uuid", "npm:9.0.0"],\
|
||||
["yargs", "npm:17.7.2"]\
|
||||
],\
|
||||
"packagePeers": [\
|
||||
"@google-cloud/spanner",\
|
||||
"@sap/hana-client",\
|
||||
"@types/better-sqlite3",\
|
||||
"@types/google-cloud__spanner",\
|
||||
"@types/hdb-pool",\
|
||||
"@types/ioredis",\
|
||||
"@types/mongodb",\
|
||||
"@types/mssql",\
|
||||
"@types/mysql2",\
|
||||
"@types/oracledb",\
|
||||
"@types/pg-native",\
|
||||
"@types/pg-query-stream",\
|
||||
"@types/pg",\
|
||||
"@types/redis",\
|
||||
"@types/sap__hana-client",\
|
||||
"@types/sql.js",\
|
||||
"@types/sqlite3",\
|
||||
"@types/ts-node",\
|
||||
"@types/typeorm-aurora-data-api-driver",\
|
||||
"better-sqlite3",\
|
||||
"hdb-pool",\
|
||||
"ioredis",\
|
||||
"mongodb",\
|
||||
"mssql",\
|
||||
"mysql2",\
|
||||
"oracledb",\
|
||||
"pg-native",\
|
||||
"pg-query-stream",\
|
||||
"pg",\
|
||||
"redis",\
|
||||
"sql.js",\
|
||||
"sqlite3",\
|
||||
"ts-node",\
|
||||
"typeorm-aurora-data-api-driver"\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}],\
|
||||
["virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:0.3.16", {\
|
||||
"packageLocation": "./.yarn/__virtual__/typeorm-virtual-fc9b7b780b/0/cache/typeorm-npm-0.3.16-5ac12a7afc-19803f935e.zip/node_modules/typeorm/",\
|
||||
"packageDependencies": [\
|
||||
@@ -16191,6 +16403,13 @@ const RAW_RUNTIME_STATE =
|
||||
["webidl-conversions", "npm:3.0.1"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}],\
|
||||
["npm:7.0.0", {\
|
||||
"packageLocation": "./.yarn/cache/webidl-conversions-npm-7.0.0-e8c8e30c68-bdbe11c68c.zip/node_modules/webidl-conversions/",\
|
||||
"packageDependencies": [\
|
||||
["webidl-conversions", "npm:7.0.0"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["webpack", [\
|
||||
@@ -16249,6 +16468,15 @@ const RAW_RUNTIME_STATE =
|
||||
}]\
|
||||
]],\
|
||||
["whatwg-url", [\
|
||||
["npm:11.0.0", {\
|
||||
"packageLocation": "./.yarn/cache/whatwg-url-npm-11.0.0-073529d93a-ee3a532bfb.zip/node_modules/whatwg-url/",\
|
||||
"packageDependencies": [\
|
||||
["whatwg-url", "npm:11.0.0"],\
|
||||
["tr46", "npm:3.0.0"],\
|
||||
["webidl-conversions", "npm:7.0.0"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}],\
|
||||
["npm:5.0.0", {\
|
||||
"packageLocation": "./.yarn/cache/whatwg-url-npm-5.0.0-374fb45e60-bd0cc6b75b.zip/node_modules/whatwg-url/",\
|
||||
"packageDependencies": [\
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -23,6 +23,8 @@ services:
|
||||
environment:
|
||||
DB_TYPE: "${DB_TYPE}"
|
||||
CACHE_TYPE: "${CACHE_TYPE}"
|
||||
SECONDARY_DB_ENABLED: "${SECONDARY_DB_ENABLED}"
|
||||
TRANSITION_MODE_ENABLED: "${TRANSITION_MODE_ENABLED}"
|
||||
container_name: server-ci
|
||||
ports:
|
||||
- 3123:3000
|
||||
@@ -61,6 +63,21 @@ services:
|
||||
networks:
|
||||
- standardnotes_self_hosted
|
||||
|
||||
secondary_db:
|
||||
image: mongo:5.0
|
||||
container_name: secondary_db-ci
|
||||
expose:
|
||||
- 27017
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- ./data/mongo:/data/db
|
||||
environment:
|
||||
MONGO_INITDB_ROOT_USERNAME: standardnotes
|
||||
MONGO_INITDB_ROOT_PASSWORD: standardnotes
|
||||
MONGO_INITDB_DATABASE: standardnotes
|
||||
networks:
|
||||
- standardnotes_self_hosted
|
||||
|
||||
cache:
|
||||
image: redis:6.0-alpine
|
||||
container_name: cache-ci
|
||||
|
||||
@@ -63,6 +63,12 @@ fi
|
||||
if [ -z "$CACHE_TYPE" ]; then
|
||||
export CACHE_TYPE="redis"
|
||||
fi
|
||||
if [ -z "$SECONDARY_DB_ENABLED" ]; then
|
||||
export SECONDARY_DB_ENABLED=false
|
||||
fi
|
||||
if [ -z "$TRANSITION_MODE_ENABLED" ]; then
|
||||
export TRANSITION_MODE_ENABLED=false
|
||||
fi
|
||||
export DB_MIGRATIONS_PATH="dist/migrations/*.js"
|
||||
|
||||
#########
|
||||
|
||||
@@ -3,6 +3,26 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [2.25.14](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.25.13...@standardnotes/analytics@2.25.14) (2023-08-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/analytics
|
||||
|
||||
## [2.25.13](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.25.12...@standardnotes/analytics@2.25.13) (2023-08-11)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/analytics
|
||||
|
||||
## [2.25.12](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.25.11...@standardnotes/analytics@2.25.12) (2023-08-09)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/analytics
|
||||
|
||||
## [2.25.11](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.25.10...@standardnotes/analytics@2.25.11) (2023-08-09)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/analytics
|
||||
|
||||
## [2.25.10](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.25.9...@standardnotes/analytics@2.25.10) (2023-08-09)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/analytics
|
||||
|
||||
## [2.25.9](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.25.8...@standardnotes/analytics@2.25.9) (2023-08-08)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/analytics
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/analytics",
|
||||
"version": "2.25.9",
|
||||
"version": "2.25.14",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,22 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.70.5](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.70.4...@standardnotes/api-gateway@1.70.5) (2023-08-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||
|
||||
## [1.70.4](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.70.3...@standardnotes/api-gateway@1.70.4) (2023-08-09)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||
|
||||
## [1.70.3](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.70.2...@standardnotes/api-gateway@1.70.3) (2023-08-09)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||
|
||||
## [1.70.2](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.70.1...@standardnotes/api-gateway@1.70.2) (2023-08-09)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||
|
||||
## [1.70.1](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.70.0...@standardnotes/api-gateway@1.70.1) (2023-08-08)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/api-gateway",
|
||||
"version": "1.70.1",
|
||||
"version": "1.70.5",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,34 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [1.132.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.131.5...@standardnotes/auth-server@1.132.0) (2023-08-18)
|
||||
|
||||
### Features
|
||||
|
||||
* add mechanism for determining if a user should use the primary or secondary items database ([#700](https://github.com/standardnotes/server/issues/700)) ([302b624](https://github.com/standardnotes/server/commit/302b624504f4c87fd7c3ddfee77cbdc14a61018b))
|
||||
|
||||
## [1.131.5](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.131.4...@standardnotes/auth-server@1.131.5) (2023-08-15)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** passing the invalidate cache header ([#697](https://github.com/standardnotes/server/issues/697)) ([83ad069](https://github.com/standardnotes/server/commit/83ad069c5dd9afa3a6db881f0d8a55a58d0642aa))
|
||||
|
||||
## [1.131.4](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.131.3...@standardnotes/auth-server@1.131.4) (2023-08-11)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/auth-server
|
||||
|
||||
## [1.131.3](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.131.2...@standardnotes/auth-server@1.131.3) (2023-08-09)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/auth-server
|
||||
|
||||
## [1.131.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.131.1...@standardnotes/auth-server@1.131.2) (2023-08-09)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/auth-server
|
||||
|
||||
## [1.131.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.131.0...@standardnotes/auth-server@1.131.1) (2023-08-09)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/auth-server
|
||||
|
||||
# [1.131.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.130.1...@standardnotes/auth-server@1.131.0) (2023-08-08)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm'
|
||||
|
||||
export class AddTransitionRole1692348191367 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
'INSERT INTO `roles` (uuid, name, version) VALUES ("e7381dc5-3d67-49e9-b7bd-f2407b2f726e", "TRANSITION_USER", 1)',
|
||||
)
|
||||
}
|
||||
|
||||
public async down(): Promise<void> {
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm'
|
||||
|
||||
export class AddTransitionRole1692348280258 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
'INSERT INTO `roles` (uuid, name, version) VALUES ("e7381dc5-3d67-49e9-b7bd-f2407b2f726e", "TRANSITION_USER", 1)',
|
||||
)
|
||||
}
|
||||
|
||||
public async down(): Promise<void> {
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/auth-server",
|
||||
"version": "1.131.0",
|
||||
"version": "1.132.0",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -560,6 +560,9 @@ export class ContainerConfigLoader {
|
||||
container
|
||||
.bind(TYPES.Auth_READONLY_USERS)
|
||||
.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) {
|
||||
container
|
||||
|
||||
@@ -101,6 +101,7 @@ const TYPES = {
|
||||
Auth_U2F_EXPECTED_ORIGIN: Symbol.for('Auth_U2F_EXPECTED_ORIGIN'),
|
||||
Auth_U2F_REQUIRE_USER_VERIFICATION: Symbol.for('Auth_U2F_REQUIRE_USER_VERIFICATION'),
|
||||
Auth_READONLY_USERS: Symbol.for('Auth_READONLY_USERS'),
|
||||
Auth_TRANSITION_MODE_ENABLED: Symbol.for('Auth_TRANSITION_MODE_ENABLED'),
|
||||
// use cases
|
||||
Auth_AuthenticateUser: Symbol.for('Auth_AuthenticateUser'),
|
||||
Auth_AuthenticateRequest: Symbol.for('Auth_AuthenticateRequest'),
|
||||
|
||||
@@ -11,6 +11,7 @@ import { Register } from './Register'
|
||||
import { SettingServiceInterface } from '../Setting/SettingServiceInterface'
|
||||
import { AuthResponseFactory20200115 } from '../Auth/AuthResponseFactory20200115'
|
||||
import { Session } from '../Session/Session'
|
||||
import { RoleName } from '@standardnotes/domain-core'
|
||||
|
||||
describe('Register', () => {
|
||||
let userRepository: UserRepositoryInterface
|
||||
@@ -20,9 +21,19 @@ describe('Register', () => {
|
||||
let user: User
|
||||
let crypter: CrypterInterface
|
||||
let timer: TimerInterface
|
||||
let transitionModeEnabled = false
|
||||
|
||||
const createUseCase = () =>
|
||||
new Register(userRepository, roleRepository, authResponseFactory, crypter, false, settingService, timer)
|
||||
new Register(
|
||||
userRepository,
|
||||
roleRepository,
|
||||
authResponseFactory,
|
||||
crypter,
|
||||
false,
|
||||
settingService,
|
||||
timer,
|
||||
transitionModeEnabled,
|
||||
)
|
||||
|
||||
beforeEach(() => {
|
||||
userRepository = {} as jest.Mocked<UserRepositoryInterface>
|
||||
@@ -75,6 +86,7 @@ describe('Register', () => {
|
||||
updatedWithUserAgent: 'Mozilla',
|
||||
uuid: expect.any(String),
|
||||
version: '004',
|
||||
roles: Promise.resolve([]),
|
||||
createdAt: new Date(1),
|
||||
updatedAt: new Date(1),
|
||||
})
|
||||
@@ -118,6 +130,48 @@ describe('Register', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('should register a new user with default role and transition role', async () => {
|
||||
transitionModeEnabled = true
|
||||
|
||||
const role = new Role()
|
||||
role.name = RoleName.NAMES.CoreUser
|
||||
|
||||
const transitionRole = new Role()
|
||||
transitionRole.name = RoleName.NAMES.TransitionUser
|
||||
|
||||
roleRepository.findOneByName = jest.fn().mockReturnValueOnce(role).mockReturnValueOnce(transitionRole)
|
||||
|
||||
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, transitionRole]),
|
||||
})
|
||||
})
|
||||
|
||||
it('should fail to register if username is invalid', async () => {
|
||||
expect(
|
||||
await createUseCase().execute({
|
||||
@@ -195,6 +249,7 @@ describe('Register', () => {
|
||||
true,
|
||||
settingService,
|
||||
timer,
|
||||
transitionModeEnabled,
|
||||
).execute({
|
||||
email: 'test@test.te',
|
||||
password: 'asdzxc',
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import * as bcrypt from 'bcryptjs'
|
||||
import { RoleName, Username } from '@standardnotes/domain-core'
|
||||
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { inject, injectable } from 'inversify'
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
|
||||
import TYPES from '../../Bootstrap/Types'
|
||||
import { User } from '../User/User'
|
||||
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
|
||||
@@ -11,7 +12,6 @@ import { RegisterResponse } from './RegisterResponse'
|
||||
import { UseCaseInterface } from './UseCaseInterface'
|
||||
import { RoleRepositoryInterface } from '../Role/RoleRepositoryInterface'
|
||||
import { CrypterInterface } from '../Encryption/CrypterInterface'
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
import { SettingServiceInterface } from '../Setting/SettingServiceInterface'
|
||||
import { AuthResponseFactory20200115 } from '../Auth/AuthResponseFactory20200115'
|
||||
import { AuthResponse20200115 } from '../Auth/AuthResponse20200115'
|
||||
@@ -27,6 +27,7 @@ export class Register implements UseCaseInterface {
|
||||
@inject(TYPES.Auth_DISABLE_USER_REGISTRATION) private disableUserRegistration: boolean,
|
||||
@inject(TYPES.Auth_SettingService) private settingService: SettingServiceInterface,
|
||||
@inject(TYPES.Auth_Timer) private timer: TimerInterface,
|
||||
@inject(TYPES.Auth_TRANSITION_MODE_ENABLED) private transitionModeEnabled: boolean,
|
||||
) {}
|
||||
|
||||
async execute(dto: RegisterDTO): Promise<RegisterResponse> {
|
||||
@@ -72,10 +73,18 @@ export class Register implements UseCaseInterface {
|
||||
user.encryptedServerKey = await this.crypter.generateEncryptedUserServerKey()
|
||||
user.serverEncryptionVersion = User.DEFAULT_ENCRYPTION_VERSION
|
||||
|
||||
const roles = []
|
||||
const defaultRole = await this.roleRepository.findOneByName(RoleName.NAMES.CoreUser)
|
||||
if (defaultRole) {
|
||||
user.roles = Promise.resolve([defaultRole])
|
||||
roles.push(defaultRole)
|
||||
}
|
||||
if (this.transitionModeEnabled) {
|
||||
const transitionRole = await this.roleRepository.findOneByName(RoleName.NAMES.TransitionUser)
|
||||
if (transitionRole) {
|
||||
roles.push(transitionRole)
|
||||
}
|
||||
}
|
||||
user.roles = Promise.resolve(roles)
|
||||
|
||||
Object.assign(user, registrationFields)
|
||||
|
||||
|
||||
@@ -285,6 +285,10 @@ export class BaseAuthController extends BaseHttpController {
|
||||
authorizationHeader: <string>request.headers.authorization,
|
||||
})
|
||||
|
||||
if (result.headers?.has('x-invalidate-cache')) {
|
||||
response.setHeader('x-invalidate-cache', result.headers.get('x-invalidate-cache') as string)
|
||||
}
|
||||
|
||||
return this.json(result.data, result.status)
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.50.1](https://github.com/standardnotes/server/compare/@standardnotes/common@1.50.0...@standardnotes/common@1.50.1) (2023-08-11)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/common
|
||||
|
||||
# [1.50.0](https://github.com/standardnotes/server/compare/@standardnotes/common@1.49.0...@standardnotes/common@1.50.0) (2023-07-12)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/common",
|
||||
"version": "1.50.0",
|
||||
"version": "1.50.1",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export type ApplicationIdentifier = string
|
||||
@@ -5,43 +5,12 @@ export enum ProtocolVersion {
|
||||
V004 = '004',
|
||||
}
|
||||
|
||||
export const ProtocolVersionLatest = ProtocolVersion.V004
|
||||
|
||||
/** The last protocol version to not use root-key based items keys */
|
||||
export const ProtocolVersionLastNonrootItemsKey = ProtocolVersion.V003
|
||||
|
||||
export const ProtocolExpirationDates: Partial<Record<ProtocolVersion, number>> = Object.freeze({
|
||||
[ProtocolVersion.V001]: Date.parse('2018-01-01'),
|
||||
[ProtocolVersion.V002]: Date.parse('2020-01-01'),
|
||||
})
|
||||
|
||||
export function isProtocolVersionExpired(version: ProtocolVersion) {
|
||||
const expireDate = ProtocolExpirationDates[version]
|
||||
if (!expireDate) {
|
||||
return false
|
||||
}
|
||||
|
||||
const expired = new Date().getTime() > expireDate
|
||||
return expired
|
||||
}
|
||||
|
||||
export const ProtocolVersionLength = 3
|
||||
|
||||
export function protocolVersionFromEncryptedString(string: string): ProtocolVersion {
|
||||
const version = string.substring(0, ProtocolVersionLength) as ProtocolVersion
|
||||
if (Object.values(ProtocolVersion).includes(version)) {
|
||||
return version
|
||||
}
|
||||
|
||||
throw Error(`Unrecognized protocol version ${version}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* -1 if a < b
|
||||
* 0 if a == b
|
||||
* 1 if a > b
|
||||
*/
|
||||
export function compareVersions(a: ProtocolVersion, b: ProtocolVersion): number {
|
||||
function compareVersions(a: ProtocolVersion, b: ProtocolVersion): number {
|
||||
const aNum = Number(a)
|
||||
const bNum = Number(b)
|
||||
return aNum - bNum
|
||||
@@ -50,7 +19,3 @@ export function compareVersions(a: ProtocolVersion, b: ProtocolVersion): number
|
||||
export function leftVersionGreaterThanOrEqualToRight(a: ProtocolVersion, b: ProtocolVersion): boolean {
|
||||
return compareVersions(a, b) >= 0
|
||||
}
|
||||
|
||||
export function isVersionLessThanOrEqualTo(input: ProtocolVersion, compareTo: ProtocolVersion): boolean {
|
||||
return compareVersions(input, compareTo) <= 0
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ export * from './Content/ContentDecoderInterface'
|
||||
export * from './DataType/AnyRecord'
|
||||
export * from './DataType/JSONString'
|
||||
export * from './DataType/MicrosecondsTimestamp'
|
||||
export * from './DataType/ApplicationIdentifier'
|
||||
export * from './Email/EmailMessageIdentifier'
|
||||
export * from './KeyParams/AnyKeyParamsContent'
|
||||
export * from './KeyParams/BaseKeyParams'
|
||||
|
||||
@@ -3,6 +3,30 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [1.26.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-core@1.25.2...@standardnotes/domain-core@1.26.0) (2023-08-18)
|
||||
|
||||
### Features
|
||||
|
||||
* add mechanism for determining if a user should use the primary or secondary items database ([#700](https://github.com/standardnotes/server/issues/700)) ([302b624](https://github.com/standardnotes/server/commit/302b624504f4c87fd7c3ddfee77cbdc14a61018b))
|
||||
|
||||
## [1.25.2](https://github.com/standardnotes/server/compare/@standardnotes/domain-core@1.25.1...@standardnotes/domain-core@1.25.2) (2023-08-09)
|
||||
|
||||
### Reverts
|
||||
|
||||
* Revert "Revert "feat(syncing-server): notify shared vault users upon file uploads or removals (#692)"" ([1c3ff52](https://github.com/standardnotes/server/commit/1c3ff526b7c4885f71f019f6c01142f522a6f8ad)), closes [#692](https://github.com/standardnotes/server/issues/692)
|
||||
|
||||
## [1.25.1](https://github.com/standardnotes/server/compare/@standardnotes/domain-core@1.25.0...@standardnotes/domain-core@1.25.1) (2023-08-09)
|
||||
|
||||
### Reverts
|
||||
|
||||
* Revert "feat(syncing-server): notify shared vault users upon file uploads or removals (#692)" ([d261c81](https://github.com/standardnotes/server/commit/d261c81cd0bdbb9001c8589224f007ed2d338903)), closes [#692](https://github.com/standardnotes/server/issues/692)
|
||||
|
||||
# [1.25.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-core@1.24.2...@standardnotes/domain-core@1.25.0) (2023-08-09)
|
||||
|
||||
### Features
|
||||
|
||||
* **syncing-server:** notify shared vault users upon file uploads or removals ([#692](https://github.com/standardnotes/server/issues/692)) ([46867c1](https://github.com/standardnotes/server/commit/46867c1a4dd310c1971ff37e1bdf380c10e478fd))
|
||||
|
||||
## [1.24.2](https://github.com/standardnotes/server/compare/@standardnotes/domain-core@1.24.1...@standardnotes/domain-core@1.24.2) (2023-08-02)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/domain-core",
|
||||
"version": "1.24.2",
|
||||
"version": "1.26.0",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -21,25 +21,36 @@ describe('RoleName', () => {
|
||||
const plusUserRole = RoleName.create(RoleName.NAMES.PlusUser).getValue()
|
||||
const coreUser = RoleName.create(RoleName.NAMES.CoreUser).getValue()
|
||||
const internalTeamUser = RoleName.create(RoleName.NAMES.InternalTeamUser).getValue()
|
||||
const transitionUser = RoleName.create(RoleName.NAMES.TransitionUser).getValue()
|
||||
|
||||
expect(internalTeamUser.hasMoreOrEqualPowerTo(proUserRole)).toBeTruthy()
|
||||
expect(internalTeamUser.hasMoreOrEqualPowerTo(proUserRole)).toBeTruthy()
|
||||
expect(internalTeamUser.hasMoreOrEqualPowerTo(plusUserRole)).toBeTruthy()
|
||||
expect(internalTeamUser.hasMoreOrEqualPowerTo(coreUser)).toBeTruthy()
|
||||
expect(internalTeamUser.hasMoreOrEqualPowerTo(transitionUser)).toBeTruthy()
|
||||
|
||||
expect(proUserRole.hasMoreOrEqualPowerTo(internalTeamUser)).toBeFalsy()
|
||||
expect(proUserRole.hasMoreOrEqualPowerTo(proUserRole)).toBeTruthy()
|
||||
expect(proUserRole.hasMoreOrEqualPowerTo(plusUserRole)).toBeTruthy()
|
||||
expect(proUserRole.hasMoreOrEqualPowerTo(coreUser)).toBeTruthy()
|
||||
expect(proUserRole.hasMoreOrEqualPowerTo(transitionUser)).toBeTruthy()
|
||||
|
||||
expect(plusUserRole.hasMoreOrEqualPowerTo(internalTeamUser)).toBeFalsy()
|
||||
expect(plusUserRole.hasMoreOrEqualPowerTo(proUserRole)).toBeFalsy()
|
||||
expect(plusUserRole.hasMoreOrEqualPowerTo(plusUserRole)).toBeTruthy()
|
||||
expect(plusUserRole.hasMoreOrEqualPowerTo(coreUser)).toBeTruthy()
|
||||
expect(plusUserRole.hasMoreOrEqualPowerTo(transitionUser)).toBeTruthy()
|
||||
|
||||
expect(coreUser.hasMoreOrEqualPowerTo(internalTeamUser)).toBeFalsy()
|
||||
expect(coreUser.hasMoreOrEqualPowerTo(proUserRole)).toBeFalsy()
|
||||
expect(coreUser.hasMoreOrEqualPowerTo(plusUserRole)).toBeFalsy()
|
||||
expect(coreUser.hasMoreOrEqualPowerTo(coreUser)).toBeTruthy()
|
||||
expect(coreUser.hasMoreOrEqualPowerTo(transitionUser)).toBeTruthy()
|
||||
|
||||
expect(transitionUser.hasMoreOrEqualPowerTo(internalTeamUser)).toBeFalsy()
|
||||
expect(transitionUser.hasMoreOrEqualPowerTo(proUserRole)).toBeFalsy()
|
||||
expect(transitionUser.hasMoreOrEqualPowerTo(plusUserRole)).toBeFalsy()
|
||||
expect(transitionUser.hasMoreOrEqualPowerTo(coreUser)).toBeTruthy()
|
||||
expect(transitionUser.hasMoreOrEqualPowerTo(transitionUser)).toBeTruthy()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -8,6 +8,7 @@ export class RoleName extends ValueObject<RoleNameProps> {
|
||||
PlusUser: 'PLUS_USER',
|
||||
ProUser: 'PRO_USER',
|
||||
InternalTeamUser: 'INTERNAL_TEAM_USER',
|
||||
TransitionUser: 'TRANSITION_USER',
|
||||
}
|
||||
|
||||
get value(): string {
|
||||
@@ -19,11 +20,19 @@ export class RoleName extends ValueObject<RoleNameProps> {
|
||||
case RoleName.NAMES.InternalTeamUser:
|
||||
return true
|
||||
case RoleName.NAMES.ProUser:
|
||||
return [RoleName.NAMES.CoreUser, RoleName.NAMES.PlusUser, RoleName.NAMES.ProUser].includes(roleName.value)
|
||||
return [
|
||||
RoleName.NAMES.CoreUser,
|
||||
RoleName.NAMES.PlusUser,
|
||||
RoleName.NAMES.ProUser,
|
||||
RoleName.NAMES.TransitionUser,
|
||||
].includes(roleName.value)
|
||||
case RoleName.NAMES.PlusUser:
|
||||
return [RoleName.NAMES.CoreUser, RoleName.NAMES.PlusUser].includes(roleName.value)
|
||||
return [RoleName.NAMES.CoreUser, RoleName.NAMES.PlusUser, RoleName.NAMES.TransitionUser].includes(
|
||||
roleName.value,
|
||||
)
|
||||
case RoleName.NAMES.CoreUser:
|
||||
return [RoleName.NAMES.CoreUser].includes(roleName.value)
|
||||
case RoleName.NAMES.TransitionUser:
|
||||
return [RoleName.NAMES.CoreUser, RoleName.NAMES.TransitionUser].includes(roleName.value)
|
||||
/*istanbul ignore next*/
|
||||
default:
|
||||
throw new Error(`Invalid role name: ${this.value}`)
|
||||
|
||||
@@ -3,32 +3,24 @@ import { RoleNameCollection } from './RoleNameCollection'
|
||||
|
||||
describe('RoleNameCollection', () => {
|
||||
it('should create a value object', () => {
|
||||
const role1 = RoleName.create(RoleName.NAMES.ProUser).getValue()
|
||||
|
||||
const valueOrError = RoleNameCollection.create([role1])
|
||||
const valueOrError = RoleNameCollection.create([RoleName.NAMES.ProUser])
|
||||
|
||||
expect(valueOrError.isFailed()).toBeFalsy()
|
||||
expect(valueOrError.getValue().value).toEqual([role1])
|
||||
expect(valueOrError.getValue().value[0].value).toEqual('PRO_USER')
|
||||
})
|
||||
|
||||
it('should tell if collections are not equal', () => {
|
||||
const roles1 = [
|
||||
RoleName.create(RoleName.NAMES.ProUser).getValue(),
|
||||
RoleName.create(RoleName.NAMES.PlusUser).getValue(),
|
||||
]
|
||||
const roles1 = [RoleName.NAMES.ProUser, RoleName.NAMES.PlusUser]
|
||||
|
||||
let roles2 = RoleNameCollection.create([
|
||||
RoleName.create(RoleName.NAMES.ProUser).getValue(),
|
||||
RoleName.create(RoleName.NAMES.CoreUser).getValue(),
|
||||
]).getValue()
|
||||
let roles2 = RoleNameCollection.create([RoleName.NAMES.ProUser, RoleName.NAMES.CoreUser]).getValue()
|
||||
|
||||
let valueOrError = RoleNameCollection.create(roles1)
|
||||
expect(valueOrError.getValue().equals(roles2)).toBeFalsy()
|
||||
|
||||
roles2 = RoleNameCollection.create([
|
||||
RoleName.create(RoleName.NAMES.ProUser).getValue(),
|
||||
RoleName.create(RoleName.NAMES.PlusUser).getValue(),
|
||||
RoleName.create(RoleName.NAMES.CoreUser).getValue(),
|
||||
RoleName.NAMES.ProUser,
|
||||
RoleName.NAMES.PlusUser,
|
||||
RoleName.NAMES.CoreUser,
|
||||
]).getValue()
|
||||
|
||||
valueOrError = RoleNameCollection.create(roles1)
|
||||
@@ -36,42 +28,30 @@ describe('RoleNameCollection', () => {
|
||||
})
|
||||
|
||||
it('should tell if collections are equal', () => {
|
||||
const roles1 = [
|
||||
RoleName.create(RoleName.NAMES.ProUser).getValue(),
|
||||
RoleName.create(RoleName.NAMES.PlusUser).getValue(),
|
||||
]
|
||||
const roles1 = [RoleName.NAMES.ProUser, RoleName.NAMES.PlusUser]
|
||||
|
||||
const roles2 = RoleNameCollection.create([
|
||||
RoleName.create(RoleName.NAMES.ProUser).getValue(),
|
||||
RoleName.create(RoleName.NAMES.PlusUser).getValue(),
|
||||
]).getValue()
|
||||
const roles2 = RoleNameCollection.create([RoleName.NAMES.ProUser, RoleName.NAMES.PlusUser]).getValue()
|
||||
|
||||
const valueOrError = RoleNameCollection.create(roles1)
|
||||
expect(valueOrError.getValue().equals(roles2)).toBeTruthy()
|
||||
})
|
||||
|
||||
it('should tell if collection includes element', () => {
|
||||
const roles1 = [
|
||||
RoleName.create(RoleName.NAMES.ProUser).getValue(),
|
||||
RoleName.create(RoleName.NAMES.PlusUser).getValue(),
|
||||
]
|
||||
const roles1 = [RoleName.NAMES.ProUser, RoleName.NAMES.PlusUser]
|
||||
|
||||
const valueOrError = RoleNameCollection.create(roles1)
|
||||
expect(valueOrError.getValue().includes(RoleName.create(RoleName.NAMES.ProUser).getValue())).toBeTruthy()
|
||||
})
|
||||
|
||||
it('should tell if collection does not includes element', () => {
|
||||
const roles1 = [
|
||||
RoleName.create(RoleName.NAMES.ProUser).getValue(),
|
||||
RoleName.create(RoleName.NAMES.PlusUser).getValue(),
|
||||
]
|
||||
const roles1 = [RoleName.NAMES.ProUser, RoleName.NAMES.PlusUser]
|
||||
|
||||
const valueOrError = RoleNameCollection.create(roles1)
|
||||
expect(valueOrError.getValue().includes(RoleName.create(RoleName.NAMES.CoreUser).getValue())).toBeFalsy()
|
||||
})
|
||||
|
||||
it('should tell if collection has a role with more or equal power to', () => {
|
||||
let roles = [RoleName.create(RoleName.NAMES.CoreUser).getValue()]
|
||||
let roles = [RoleName.NAMES.CoreUser]
|
||||
let valueOrError = RoleNameCollection.create(roles)
|
||||
let roleNames = valueOrError.getValue()
|
||||
|
||||
@@ -83,7 +63,7 @@ describe('RoleNameCollection', () => {
|
||||
roleNames.hasARoleNameWithMoreOrEqualPowerTo(RoleName.create(RoleName.NAMES.CoreUser).getValue()),
|
||||
).toBeTruthy()
|
||||
|
||||
roles = [RoleName.create(RoleName.NAMES.CoreUser).getValue(), RoleName.create(RoleName.NAMES.PlusUser).getValue()]
|
||||
roles = [RoleName.NAMES.CoreUser, RoleName.NAMES.PlusUser]
|
||||
valueOrError = RoleNameCollection.create(roles)
|
||||
roleNames = valueOrError.getValue()
|
||||
|
||||
@@ -95,7 +75,7 @@ describe('RoleNameCollection', () => {
|
||||
roleNames.hasARoleNameWithMoreOrEqualPowerTo(RoleName.create(RoleName.NAMES.CoreUser).getValue()),
|
||||
).toBeTruthy()
|
||||
|
||||
roles = [RoleName.create(RoleName.NAMES.ProUser).getValue(), RoleName.create(RoleName.NAMES.PlusUser).getValue()]
|
||||
roles = [RoleName.NAMES.ProUser, RoleName.NAMES.PlusUser]
|
||||
valueOrError = RoleNameCollection.create(roles)
|
||||
roleNames = valueOrError.getValue()
|
||||
|
||||
@@ -109,4 +89,11 @@ describe('RoleNameCollection', () => {
|
||||
roleNames.hasARoleNameWithMoreOrEqualPowerTo(RoleName.create(RoleName.NAMES.CoreUser).getValue()),
|
||||
).toBeTruthy()
|
||||
})
|
||||
|
||||
it('should fail to create a collection if a role name is invalid', () => {
|
||||
const valueOrError = RoleNameCollection.create(['invalid-role-name'])
|
||||
|
||||
expect(valueOrError.isFailed()).toBeTruthy()
|
||||
expect(valueOrError.getError()).toEqual('Invalid role name: invalid-role-name')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -46,7 +46,16 @@ export class RoleNameCollection extends ValueObject<RoleNameCollectionProps> {
|
||||
super(props)
|
||||
}
|
||||
|
||||
static create(roleName: RoleName[]): Result<RoleNameCollection> {
|
||||
return Result.ok<RoleNameCollection>(new RoleNameCollection({ value: roleName }))
|
||||
static create(roleNameStrings: string[]): Result<RoleNameCollection> {
|
||||
const roleNames: RoleName[] = []
|
||||
for (const roleNameString of roleNameStrings) {
|
||||
const roleNameOrError = RoleName.create(roleNameString)
|
||||
if (roleNameOrError.isFailed()) {
|
||||
return Result.fail<RoleNameCollection>(roleNameOrError.getError())
|
||||
}
|
||||
roleNames.push(roleNameOrError.getValue())
|
||||
}
|
||||
|
||||
return Result.ok<RoleNameCollection>(new RoleNameCollection({ value: roleNames }))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ export class NotificationType extends ValueObject<NotificationTypeProps> {
|
||||
static readonly TYPES = {
|
||||
SharedVaultItemRemoved: 'shared_vault_item_removed',
|
||||
RemovedFromSharedVault: 'removed_from_shared_vault',
|
||||
SharedVaultFileUploaded: 'shared_vault_file_uploaded',
|
||||
SharedVaultFileRemoved: 'shared_vault_file_removed',
|
||||
}
|
||||
|
||||
get value(): string {
|
||||
|
||||
@@ -3,6 +3,22 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.11.20](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.11.19...@standardnotes/event-store@1.11.20) (2023-08-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/event-store
|
||||
|
||||
## [1.11.19](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.11.18...@standardnotes/event-store@1.11.19) (2023-08-09)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/event-store
|
||||
|
||||
## [1.11.18](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.11.17...@standardnotes/event-store@1.11.18) (2023-08-09)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/event-store
|
||||
|
||||
## [1.11.17](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.11.16...@standardnotes/event-store@1.11.17) (2023-08-09)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/event-store
|
||||
|
||||
## [1.11.16](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.11.15...@standardnotes/event-store@1.11.16) (2023-08-08)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/event-store
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/event-store",
|
||||
"version": "1.11.16",
|
||||
"version": "1.11.20",
|
||||
"description": "Event Store Service",
|
||||
"private": true,
|
||||
"main": "dist/src/index.js",
|
||||
|
||||
@@ -3,6 +3,22 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.20.4](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.20.3...@standardnotes/files-server@1.20.4) (2023-08-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files-server
|
||||
|
||||
## [1.20.3](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.20.2...@standardnotes/files-server@1.20.3) (2023-08-09)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files-server
|
||||
|
||||
## [1.20.2](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.20.1...@standardnotes/files-server@1.20.2) (2023-08-09)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files-server
|
||||
|
||||
## [1.20.1](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.20.0...@standardnotes/files-server@1.20.1) (2023-08-09)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files-server
|
||||
|
||||
# [1.20.0](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.19.18...@standardnotes/files-server@1.20.0) (2023-08-08)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/files-server",
|
||||
"version": "1.20.0",
|
||||
"version": "1.20.4",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -9,3 +9,12 @@ PSEUDO_KEY_PARAMS_KEY=
|
||||
VALET_TOKEN_SECRET=
|
||||
|
||||
FILES_SERVER_URL=
|
||||
|
||||
SECONDARY_DB_ENABLED=false
|
||||
MONGO_HOST=localhost
|
||||
MONGO_PORT=27017
|
||||
MONGO_USERNAME=standardnotes
|
||||
MONGO_PASSWORD=standardnotes
|
||||
MONGO_DATABASE=standardnotes
|
||||
|
||||
TRANSITION_MODE_ENABLED=false
|
||||
|
||||
@@ -3,6 +3,86 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.14.1](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.14.0...@standardnotes/home-server@1.14.1) (2023-08-21)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
# [1.14.0](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.13.51...@standardnotes/home-server@1.14.0) (2023-08-18)
|
||||
|
||||
### Features
|
||||
|
||||
* add mechanism for determining if a user should use the primary or secondary items database ([#700](https://github.com/standardnotes/server/issues/700)) ([302b624](https://github.com/standardnotes/server/commit/302b624504f4c87fd7c3ddfee77cbdc14a61018b))
|
||||
|
||||
## [1.13.51](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.13.50...@standardnotes/home-server@1.13.51) (2023-08-17)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **home-server:** add default env values for secondary database ([916e989](https://github.com/standardnotes/server/commit/916e98936a276a3960d949c5b70803214c945686))
|
||||
|
||||
## [1.13.50](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.13.49...@standardnotes/home-server@1.13.50) (2023-08-16)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.13.49](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.13.48...@standardnotes/home-server@1.13.49) (2023-08-15)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.13.48](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.13.47...@standardnotes/home-server@1.13.48) (2023-08-11)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.13.47](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.13.46...@standardnotes/home-server@1.13.47) (2023-08-11)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.13.46](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.13.45...@standardnotes/home-server@1.13.46) (2023-08-11)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.13.45](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.13.44...@standardnotes/home-server@1.13.45) (2023-08-11)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.13.44](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.13.43...@standardnotes/home-server@1.13.44) (2023-08-11)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.13.43](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.13.42...@standardnotes/home-server@1.13.43) (2023-08-10)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.13.42](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.13.41...@standardnotes/home-server@1.13.42) (2023-08-09)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.13.41](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.13.40...@standardnotes/home-server@1.13.41) (2023-08-09)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.13.40](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.13.39...@standardnotes/home-server@1.13.40) (2023-08-09)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.13.39](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.13.38...@standardnotes/home-server@1.13.39) (2023-08-09)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.13.38](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.13.37...@standardnotes/home-server@1.13.38) (2023-08-09)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.13.37](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.13.36...@standardnotes/home-server@1.13.37) (2023-08-09)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.13.36](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.13.35...@standardnotes/home-server@1.13.36) (2023-08-09)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.13.35](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.13.34...@standardnotes/home-server@1.13.35) (2023-08-08)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.13.34](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.13.33...@standardnotes/home-server@1.13.34) (2023-08-08)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/home-server",
|
||||
"version": "1.13.34",
|
||||
"version": "1.14.1",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,26 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.26.8](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.26.7...@standardnotes/revisions-server@1.26.8) (2023-08-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/revisions-server
|
||||
|
||||
## [1.26.7](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.26.6...@standardnotes/revisions-server@1.26.7) (2023-08-11)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/revisions-server
|
||||
|
||||
## [1.26.6](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.26.5...@standardnotes/revisions-server@1.26.6) (2023-08-09)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/revisions-server
|
||||
|
||||
## [1.26.5](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.26.4...@standardnotes/revisions-server@1.26.5) (2023-08-09)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/revisions-server
|
||||
|
||||
## [1.26.4](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.26.3...@standardnotes/revisions-server@1.26.4) (2023-08-09)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/revisions-server
|
||||
|
||||
## [1.26.3](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.26.2...@standardnotes/revisions-server@1.26.3) (2023-08-08)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/revisions-server
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/revisions-server",
|
||||
"version": "1.26.3",
|
||||
"version": "1.26.8",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,34 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.20.24](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.20.23...@standardnotes/scheduler-server@1.20.24) (2023-08-21)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **scheduler:** remove exit interview form link ([#702](https://github.com/standardnotes/server/issues/702)) ([3e56243](https://github.com/standardnotes/server/commit/3e56243d6f72f5ac86d4fb2191349ec3a589bc83))
|
||||
|
||||
## [1.20.23](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.20.22...@standardnotes/scheduler-server@1.20.23) (2023-08-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/scheduler-server
|
||||
|
||||
## [1.20.22](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.20.21...@standardnotes/scheduler-server@1.20.22) (2023-08-11)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **scheduler:** adjust email backups encouraging email schedule ([#695](https://github.com/standardnotes/server/issues/695)) ([091e2a5](https://github.com/standardnotes/server/commit/091e2a57e81bb335286807bba4b0fcfc04a086bb))
|
||||
|
||||
## [1.20.21](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.20.20...@standardnotes/scheduler-server@1.20.21) (2023-08-09)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/scheduler-server
|
||||
|
||||
## [1.20.20](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.20.19...@standardnotes/scheduler-server@1.20.20) (2023-08-09)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/scheduler-server
|
||||
|
||||
## [1.20.19](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.20.18...@standardnotes/scheduler-server@1.20.19) (2023-08-09)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/scheduler-server
|
||||
|
||||
## [1.20.18](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.20.17...@standardnotes/scheduler-server@1.20.18) (2023-08-08)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/scheduler-server
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/scheduler-server",
|
||||
"version": "1.20.18",
|
||||
"version": "1.20.24",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -15,8 +15,6 @@ export const html = `<div>
|
||||
are willing to pay for a product is most crucial for us as we continue to evolve and iterate on Standard
|
||||
Notes.
|
||||
</p>
|
||||
<p>If you have a minute, please fill out this brief exit interview: </p>
|
||||
<a href="https://standardnotes.typeform.com/to/dX5lzPtm">Short Exit Interview →</a>
|
||||
<p>
|
||||
Our team reads every single response, and your feedback will be shared with the relevant department within our
|
||||
team.
|
||||
|
||||
@@ -33,7 +33,7 @@ export class UserRegisteredEventHandler implements DomainEventHandlerInterface {
|
||||
private async scheduleEncourageEmailBackupsJob(event: UserRegisteredEvent): Promise<void> {
|
||||
const job = new Job()
|
||||
job.name = JobName.ENCOURAGE_EMAIL_BACKUPS
|
||||
job.scheduledAt = this.timer.convertDateToMicroseconds(this.timer.getUTCDateNDaysAhead(7))
|
||||
job.scheduledAt = this.timer.convertDateToMicroseconds(this.timer.getUTCDateNDaysAhead(2))
|
||||
job.createdAt = this.timer.getTimestampInMicroseconds()
|
||||
job.status = JobStatus.Pending
|
||||
job.userIdentifier = event.payload.email
|
||||
|
||||
@@ -3,6 +3,22 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.21.25](https://github.com/standardnotes/server/compare/@standardnotes/settings@1.21.24...@standardnotes/settings@1.21.25) (2023-08-18)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/settings
|
||||
|
||||
## [1.21.24](https://github.com/standardnotes/server/compare/@standardnotes/settings@1.21.23...@standardnotes/settings@1.21.24) (2023-08-09)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/settings
|
||||
|
||||
## [1.21.23](https://github.com/standardnotes/server/compare/@standardnotes/settings@1.21.22...@standardnotes/settings@1.21.23) (2023-08-09)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/settings
|
||||
|
||||
## [1.21.22](https://github.com/standardnotes/server/compare/@standardnotes/settings@1.21.21...@standardnotes/settings@1.21.22) (2023-08-09)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/settings
|
||||
|
||||
## [1.21.21](https://github.com/standardnotes/server/compare/@standardnotes/settings@1.21.20...@standardnotes/settings@1.21.21) (2023-08-02)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/settings
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/settings",
|
||||
"version": "1.21.21",
|
||||
"version": "1.21.25",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -52,3 +52,11 @@ FILE_UPLOAD_PATH=
|
||||
|
||||
VALET_TOKEN_SECRET=change-me-!
|
||||
VALET_TOKEN_TTL=7200
|
||||
|
||||
# (Optional) Mongo Setup
|
||||
SECONDARY_DB_ENABLED=false
|
||||
MONGO_HOST=
|
||||
MONGO_PORT=
|
||||
MONGO_USERNAME=
|
||||
MONGO_PASSWORD=
|
||||
MONGO_DATABASE=
|
||||
|
||||
@@ -3,6 +3,115 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [1.81.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.80.0...@standardnotes/syncing-server@1.81.0) (2023-08-21)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **syncing-server:** DocumentDB retry writes support ([#703](https://github.com/standardnotes/syncing-server-js/issues/703)) ([15a7f0e](https://github.com/standardnotes/syncing-server-js/commit/15a7f0e71ac2f6c355fb73208559a8fd822773aa))
|
||||
|
||||
### Features
|
||||
|
||||
* **syncing-server:** add use case for migrating items from one database to another ([#701](https://github.com/standardnotes/syncing-server-js/issues/701)) ([032fcb9](https://github.com/standardnotes/syncing-server-js/commit/032fcb938d9f81381dd9879af4bda9254ee8c499))
|
||||
|
||||
# [1.80.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.79.1...@standardnotes/syncing-server@1.80.0) (2023-08-18)
|
||||
|
||||
### Features
|
||||
|
||||
* add mechanism for determining if a user should use the primary or secondary items database ([#700](https://github.com/standardnotes/syncing-server-js/issues/700)) ([302b624](https://github.com/standardnotes/syncing-server-js/commit/302b624504f4c87fd7c3ddfee77cbdc14a61018b))
|
||||
|
||||
## [1.79.1](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.79.0...@standardnotes/syncing-server@1.79.1) (2023-08-17)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **syncing-server:** refactor shared vault and key system associations ([#698](https://github.com/standardnotes/syncing-server-js/issues/698)) ([31d1eef](https://github.com/standardnotes/syncing-server-js/commit/31d1eef7f74310b176085311fc04c2efc4a7059f))
|
||||
|
||||
# [1.79.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.78.11...@standardnotes/syncing-server@1.79.0) (2023-08-16)
|
||||
|
||||
### Features
|
||||
|
||||
* add mongodb initial support ([#696](https://github.com/standardnotes/syncing-server-js/issues/696)) ([b24b576](https://github.com/standardnotes/syncing-server-js/commit/b24b5762093c0f48a28dfb879339c1b9927c9333))
|
||||
|
||||
## [1.78.11](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.78.10...@standardnotes/syncing-server@1.78.11) (2023-08-11)
|
||||
|
||||
### Reverts
|
||||
|
||||
* Revert "tmp: disable shared vaults" ([d02124f](https://github.com/standardnotes/syncing-server-js/commit/d02124f4e505e4f7e7510637c461fdd0552c381a))
|
||||
* Revert "tmp: disable decorating with associations on revisions" ([ad4b85b](https://github.com/standardnotes/syncing-server-js/commit/ad4b85b095a8539955f7b47d51643121d33eed6a))
|
||||
* Revert "tmp: disable decorating items completely" ([0bf7d8b](https://github.com/standardnotes/syncing-server-js/commit/0bf7d8beae2de1b86d95711a4e15e84b8bf5e9c0))
|
||||
|
||||
## [1.78.10](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.78.9...@standardnotes/syncing-server@1.78.10) (2023-08-11)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/syncing-server
|
||||
|
||||
## [1.78.9](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.78.8...@standardnotes/syncing-server@1.78.9) (2023-08-11)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/syncing-server
|
||||
|
||||
## [1.78.8](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.78.7...@standardnotes/syncing-server@1.78.8) (2023-08-11)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/syncing-server
|
||||
|
||||
## [1.78.7](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.78.6...@standardnotes/syncing-server@1.78.7) (2023-08-11)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/syncing-server
|
||||
|
||||
## [1.78.6](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.78.5...@standardnotes/syncing-server@1.78.6) (2023-08-10)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **syncing-server:** setting user uuid in notifications ([56f4975](https://github.com/standardnotes/syncing-server-js/commit/56f49752b43fa1d3dff4f1e3a8f07cd7739516a9))
|
||||
|
||||
## [1.78.5](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.78.4...@standardnotes/syncing-server@1.78.5) (2023-08-09)
|
||||
|
||||
### Reverts
|
||||
|
||||
* Revert "tmp: disable fetching shared vault items" ([0eb86c0](https://github.com/standardnotes/syncing-server-js/commit/0eb86c009678a468bf9a7d0079dac58eff48f4d7))
|
||||
* Revert "Revert "feat(syncing-server): notify shared vault users upon file uploads or removals (#692)"" ([1c3ff52](https://github.com/standardnotes/syncing-server-js/commit/1c3ff526b7c4885f71f019f6c01142f522a6f8ad)), closes [#692](https://github.com/standardnotes/syncing-server-js/issues/692)
|
||||
|
||||
## [1.78.4](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.78.3...@standardnotes/syncing-server@1.78.4) (2023-08-09)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **syncing-server:** casting handlers ([d7965b2](https://github.com/standardnotes/syncing-server-js/commit/d7965b2748ad59b1bff0cd6c0bf691303d9a6a76))
|
||||
|
||||
### Reverts
|
||||
|
||||
* Revert "Revert "fix(syncing-server): update storage quota used in a shared vault (#691)"" ([cbcd2ec](https://github.com/standardnotes/syncing-server-js/commit/cbcd2ec87ac5b94e06608da0426d7c27e5e56146)), closes [#691](https://github.com/standardnotes/syncing-server-js/issues/691)
|
||||
|
||||
## [1.78.3](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.78.2...@standardnotes/syncing-server@1.78.3) (2023-08-09)
|
||||
|
||||
### Reverts
|
||||
|
||||
* Revert "fix(syncing-server): update storage quota used in a shared vault (#691)" ([66f9352](https://github.com/standardnotes/syncing-server-js/commit/66f9352a062f45b5c66e7aae9681a56ca3ec6084)), closes [#691](https://github.com/standardnotes/syncing-server-js/issues/691)
|
||||
|
||||
## [1.78.2](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.78.1...@standardnotes/syncing-server@1.78.2) (2023-08-09)
|
||||
|
||||
### Reverts
|
||||
|
||||
* Revert "feat(syncing-server): notify shared vault users upon file uploads or removals (#692)" ([d261c81](https://github.com/standardnotes/syncing-server-js/commit/d261c81cd0bdbb9001c8589224f007ed2d338903)), closes [#692](https://github.com/standardnotes/syncing-server-js/issues/692)
|
||||
|
||||
## [1.78.1](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.78.0...@standardnotes/syncing-server@1.78.1) (2023-08-09)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/syncing-server
|
||||
|
||||
# [1.78.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.77.2...@standardnotes/syncing-server@1.78.0) (2023-08-09)
|
||||
|
||||
### Features
|
||||
|
||||
* **syncing-server:** notify shared vault users upon file uploads or removals ([#692](https://github.com/standardnotes/syncing-server-js/issues/692)) ([46867c1](https://github.com/standardnotes/syncing-server-js/commit/46867c1a4dd310c1971ff37e1bdf380c10e478fd))
|
||||
|
||||
## [1.77.2](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.77.1...@standardnotes/syncing-server@1.77.2) (2023-08-09)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **syncing-server:** update storage quota used in a shared vault ([#691](https://github.com/standardnotes/syncing-server-js/issues/691)) ([3415cae](https://github.com/standardnotes/syncing-server-js/commit/3415cae093ecd3631b924e722d4bd1d5015dd37a))
|
||||
|
||||
## [1.77.1](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.77.0...@standardnotes/syncing-server@1.77.1) (2023-08-08)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **syncing-server:** inviting already existing members to shared vault ([#690](https://github.com/standardnotes/syncing-server-js/issues/690)) ([0a16ee6](https://github.com/standardnotes/syncing-server-js/commit/0a16ee64fecc8d61d4a77fcf8c2c239691616000))
|
||||
|
||||
# [1.77.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.76.1...@standardnotes/syncing-server@1.77.0) (2023-08-08)
|
||||
|
||||
### Features
|
||||
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm'
|
||||
|
||||
export class RemoveRevisionsForeignKey1692176803410 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
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) {
|
||||
try {
|
||||
await queryRunner.query('ALTER TABLE `revisions` DROP FOREIGN KEY `FK_ab3b92e54701fe3010022a31d90`')
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('Error dropping foreign key: ', (error as Error).message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async down(): Promise<void> {
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm'
|
||||
|
||||
export class RemoveAssociations1692264556858 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
'DROP INDEX `key_system_identifier_on_key_system_associations` ON `key_system_associations`',
|
||||
)
|
||||
await queryRunner.query('DROP INDEX `item_uuid_on_key_system_associations` ON `key_system_associations`')
|
||||
await queryRunner.query('DROP TABLE `key_system_associations`')
|
||||
|
||||
await queryRunner.query('DROP INDEX `item_uuid_on_shared_vault_associations` ON `shared_vault_associations`')
|
||||
await queryRunner.query(
|
||||
'DROP INDEX `shared_vault_uuid_on_shared_vault_associations` ON `shared_vault_associations`',
|
||||
)
|
||||
await queryRunner.query('DROP TABLE `shared_vault_associations`')
|
||||
}
|
||||
|
||||
public async down(): Promise<void> {
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm'
|
||||
|
||||
export class RemoveAssociations1692264735730 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query('DROP INDEX "key_system_identifier_on_key_system_associations"')
|
||||
await queryRunner.query('DROP INDEX "item_uuid_on_key_system_associations"')
|
||||
await queryRunner.query('DROP TABLE "key_system_associations"')
|
||||
|
||||
await queryRunner.query('DROP INDEX "item_uuid_on_shared_vault_associations"')
|
||||
await queryRunner.query('DROP INDEX "shared_vault_uuid_on_shared_vault_associations"')
|
||||
await queryRunner.query('DROP TABLE "shared_vault_associations"')
|
||||
}
|
||||
|
||||
public async down(): Promise<void> {
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/syncing-server",
|
||||
"version": "1.77.0",
|
||||
"version": "1.81.0",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
@@ -49,6 +49,7 @@
|
||||
"inversify": "^6.0.1",
|
||||
"inversify-express-utils": "^6.4.3",
|
||||
"jsonwebtoken": "^9.0.0",
|
||||
"mongodb": "^5.7.0",
|
||||
"mysql2": "^3.0.1",
|
||||
"nodemon": "^2.0.19",
|
||||
"prettyjson": "^1.2.5",
|
||||
|
||||
@@ -7,7 +7,7 @@ import { AppDataSource } from './DataSource'
|
||||
import { SNSClient, SNSClientConfig } from '@aws-sdk/client-sns'
|
||||
import { ItemRepositoryInterface } from '../Domain/Item/ItemRepositoryInterface'
|
||||
import { TypeORMItemRepository } from '../Infra/TypeORM/TypeORMItemRepository'
|
||||
import { Repository } from 'typeorm'
|
||||
import { MongoRepository, Repository } from 'typeorm'
|
||||
import { Item } from '../Domain/Item/Item'
|
||||
import {
|
||||
DirectCallDomainEventPublisher,
|
||||
@@ -39,7 +39,7 @@ import { SyncItems } from '../Domain/UseCase/Syncing/SyncItems/SyncItems'
|
||||
import { InversifyExpressAuthMiddleware } from '../Infra/InversifyExpressUtils/Middleware/InversifyExpressAuthMiddleware'
|
||||
import { S3Client } from '@aws-sdk/client-s3'
|
||||
import { SQSClient, SQSClientConfig } from '@aws-sdk/client-sqs'
|
||||
import { ContentDecoder } from '@standardnotes/common'
|
||||
import { ContentDecoder, ContentDecoderInterface } from '@standardnotes/common'
|
||||
import {
|
||||
DomainEventMessageHandlerInterface,
|
||||
DomainEventHandlerInterface,
|
||||
@@ -79,15 +79,7 @@ import { ItemHashHttpMapper } from '../Mapping/Http/ItemHashHttpMapper'
|
||||
import { ItemHash } from '../Domain/Item/ItemHash'
|
||||
import { ItemHashHttpRepresentation } from '../Mapping/Http/ItemHashHttpRepresentation'
|
||||
import { TypeORMKeySystemAssociation } from '../Infra/TypeORM/TypeORMKeySystemAssociation'
|
||||
import { SharedVaultAssociation } from '../Domain/SharedVault/SharedVaultAssociation'
|
||||
import { TypeORMSharedVaultAssociation } from '../Infra/TypeORM/TypeORMSharedVaultAssociation'
|
||||
import { SharedVaultAssociationPersistenceMapper } from '../Mapping/Persistence/SharedVaultAssociationPersistenceMapper'
|
||||
import { TypeORMKeySystemAssociationRepository } from '../Infra/TypeORM/TypeORMKeySystemAssociationRepository'
|
||||
import { SharedVaultAssociationRepositoryInterface } from '../Domain/SharedVault/SharedVaultAssociationRepositoryInterface'
|
||||
import { TypeORMSharedVaultAssociationRepository } from '../Infra/TypeORM/TypeORMSharedVaultAssociationRepository'
|
||||
import { KeySystemAssociation } from '../Domain/KeySystem/KeySystemAssociation'
|
||||
import { KeySystemAssociationRepositoryInterface } from '../Domain/KeySystem/KeySystemAssociationRepositoryInterface'
|
||||
import { KeySystemAssociationPersistenceMapper } from '../Mapping/Persistence/KeySystemAssociationPersistenceMapper'
|
||||
import { BaseSharedVaultInvitesController } from '../Infra/InversifyExpressUtils/Base/BaseSharedVaultInvitesController'
|
||||
import { InviteUserToSharedVault } from '../Domain/UseCase/SharedVaults/InviteUserToSharedVault/InviteUserToSharedVault'
|
||||
import { TypeORMSharedVaultRepository } from '../Infra/TypeORM/TypeORMSharedVaultRepository'
|
||||
@@ -154,6 +146,17 @@ import { DetermineSharedVaultOperationOnItem } from '../Domain/UseCase/SharedVau
|
||||
import { SharedVaultFilter } from '../Domain/Item/SaveRule/SharedVaultFilter'
|
||||
import { RemoveNotificationsForUser } from '../Domain/UseCase/Messaging/RemoveNotificationsForUser/RemoveNotificationsForUser'
|
||||
import { SharedVaultSnjsFilter } from '../Domain/Item/SaveRule/SharedVaultSnjsFilter'
|
||||
import { UpdateStorageQuotaUsedInSharedVault } from '../Domain/UseCase/SharedVaults/UpdateStorageQuotaUsedInSharedVault/UpdateStorageQuotaUsedInSharedVault'
|
||||
import { SharedVaultFileUploadedEventHandler } from '../Domain/Handler/SharedVaultFileUploadedEventHandler'
|
||||
import { SharedVaultFileRemovedEventHandler } from '../Domain/Handler/SharedVaultFileRemovedEventHandler'
|
||||
import { AddNotificationsForUsers } from '../Domain/UseCase/Messaging/AddNotificationsForUsers/AddNotificationsForUsers'
|
||||
import { MongoDBItem } from '../Infra/TypeORM/MongoDBItem'
|
||||
import { MongoDBItemRepository } from '../Infra/TypeORM/MongoDBItemRepository'
|
||||
import { MongoDBItemPersistenceMapper } from '../Mapping/Persistence/MongoDB/MongoDBItemPersistenceMapper'
|
||||
import { Logger } from 'winston'
|
||||
import { ItemRepositoryResolverInterface } from '../Domain/Item/ItemRepositoryResolverInterface'
|
||||
import { TypeORMItemRepositoryResolver } from '../Infra/TypeORM/TypeORMItemRepositoryResolver'
|
||||
import { TransitionItemsFromPrimaryToSecondaryDatabaseForUser } from '../Domain/UseCase/Transition/TransitionItemsFromPrimaryToSecondaryDatabaseForUser/TransitionItemsFromPrimaryToSecondaryDatabaseForUser'
|
||||
|
||||
export class ContainerConfigLoader {
|
||||
private readonly DEFAULT_CONTENT_SIZE_TRANSFER_LIMIT = 10_000_000
|
||||
@@ -206,6 +209,7 @@ export class ContainerConfigLoader {
|
||||
container.bind<TimerInterface>(TYPES.Sync_Timer).toConstantValue(new Timer())
|
||||
|
||||
const isConfiguredForHomeServer = env.get('MODE', true) === 'home-server'
|
||||
const isSecondaryDatabaseEnabled = env.get('SECONDARY_DB_ENABLED', true) === 'true'
|
||||
|
||||
container.bind<Env>(TYPES.Sync_Env).toConstantValue(env)
|
||||
|
||||
@@ -284,6 +288,18 @@ export class ContainerConfigLoader {
|
||||
})
|
||||
}
|
||||
|
||||
container
|
||||
.bind(TYPES.Sync_EMAIL_ATTACHMENT_MAX_BYTE_SIZE)
|
||||
.toConstantValue(
|
||||
env.get('EMAIL_ATTACHMENT_MAX_BYTE_SIZE', true) ? +env.get('EMAIL_ATTACHMENT_MAX_BYTE_SIZE', true) : 10485760,
|
||||
)
|
||||
container.bind(TYPES.Sync_NEW_RELIC_ENABLED).toConstantValue(env.get('NEW_RELIC_ENABLED', true))
|
||||
container
|
||||
.bind(TYPES.Sync_FILE_UPLOAD_PATH)
|
||||
.toConstantValue(
|
||||
env.get('FILE_UPLOAD_PATH', true) ? env.get('FILE_UPLOAD_PATH', true) : this.DEFAULT_FILE_UPLOAD_PATH,
|
||||
)
|
||||
|
||||
// Mapping
|
||||
container
|
||||
.bind<MapperInterface<Item, TypeORMItem>>(TYPES.Sync_ItemPersistenceMapper)
|
||||
@@ -308,16 +324,6 @@ export class ContainerConfigLoader {
|
||||
container
|
||||
.bind<MapperInterface<Item, ItemBackupRepresentation>>(TYPES.Sync_ItemBackupMapper)
|
||||
.toConstantValue(new ItemBackupMapper(container.get(TYPES.Sync_Timer)))
|
||||
container
|
||||
.bind<MapperInterface<KeySystemAssociation, TypeORMKeySystemAssociation>>(
|
||||
TYPES.Sync_KeySystemAssociationPersistenceMapper,
|
||||
)
|
||||
.toConstantValue(new KeySystemAssociationPersistenceMapper())
|
||||
container
|
||||
.bind<MapperInterface<SharedVaultAssociation, TypeORMSharedVaultAssociation>>(
|
||||
TYPES.Sync_SharedVaultAssociationPersistenceMapper,
|
||||
)
|
||||
.toConstantValue(new SharedVaultAssociationPersistenceMapper())
|
||||
container
|
||||
.bind<MapperInterface<SharedVault, TypeORMSharedVault>>(TYPES.Sync_SharedVaultPersistenceMapper)
|
||||
.toConstantValue(new SharedVaultPersistenceMapper())
|
||||
@@ -377,32 +383,43 @@ export class ContainerConfigLoader {
|
||||
.bind<Repository<TypeORMMessage>>(TYPES.Sync_ORMMessageRepository)
|
||||
.toConstantValue(appDataSource.getRepository(TypeORMMessage))
|
||||
|
||||
// Mongo
|
||||
if (isSecondaryDatabaseEnabled) {
|
||||
container
|
||||
.bind<MapperInterface<Item, MongoDBItem>>(TYPES.Sync_MongoDBItemPersistenceMapper)
|
||||
.toConstantValue(new MongoDBItemPersistenceMapper())
|
||||
|
||||
container
|
||||
.bind<MongoRepository<MongoDBItem>>(TYPES.Sync_ORMMongoItemRepository)
|
||||
.toConstantValue(appDataSource.getMongoRepository(MongoDBItem))
|
||||
|
||||
container
|
||||
.bind<ItemRepositoryInterface>(TYPES.Sync_MongoDBItemRepository)
|
||||
.toConstantValue(
|
||||
new MongoDBItemRepository(
|
||||
container.get<MongoRepository<MongoDBItem>>(TYPES.Sync_ORMMongoItemRepository),
|
||||
container.get<MapperInterface<Item, MongoDBItem>>(TYPES.Sync_MongoDBItemPersistenceMapper),
|
||||
container.get<Logger>(TYPES.Sync_Logger),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
// Repositories
|
||||
container
|
||||
.bind<KeySystemAssociationRepositoryInterface>(TYPES.Sync_KeySystemAssociationRepository)
|
||||
.toConstantValue(
|
||||
new TypeORMKeySystemAssociationRepository(
|
||||
container.get(TYPES.Sync_ORMKeySystemAssociationRepository),
|
||||
container.get(TYPES.Sync_KeySystemAssociationPersistenceMapper),
|
||||
),
|
||||
)
|
||||
container
|
||||
.bind<SharedVaultAssociationRepositoryInterface>(TYPES.Sync_SharedVaultAssociationRepository)
|
||||
.toConstantValue(
|
||||
new TypeORMSharedVaultAssociationRepository(
|
||||
container.get(TYPES.Sync_ORMSharedVaultAssociationRepository),
|
||||
container.get(TYPES.Sync_SharedVaultAssociationPersistenceMapper),
|
||||
),
|
||||
)
|
||||
container
|
||||
.bind<ItemRepositoryInterface>(TYPES.Sync_ItemRepository)
|
||||
.bind<ItemRepositoryInterface>(TYPES.Sync_MySQLItemRepository)
|
||||
.toConstantValue(
|
||||
new TypeORMItemRepository(
|
||||
container.get(TYPES.Sync_ORMItemRepository),
|
||||
container.get(TYPES.Sync_ItemPersistenceMapper),
|
||||
container.get(TYPES.Sync_KeySystemAssociationRepository),
|
||||
container.get(TYPES.Sync_SharedVaultAssociationRepository),
|
||||
container.get(TYPES.Sync_Logger),
|
||||
container.get<Repository<TypeORMItem>>(TYPES.Sync_ORMItemRepository),
|
||||
container.get<MapperInterface<Item, TypeORMItem>>(TYPES.Sync_ItemPersistenceMapper),
|
||||
container.get<Logger>(TYPES.Sync_Logger),
|
||||
),
|
||||
)
|
||||
container
|
||||
.bind<ItemRepositoryResolverInterface>(TYPES.Sync_ItemRepositoryResolver)
|
||||
.toConstantValue(
|
||||
new TypeORMItemRepositoryResolver(
|
||||
container.get<ItemRepositoryInterface>(TYPES.Sync_MySQLItemRepository),
|
||||
isSecondaryDatabaseEnabled ? container.get<ItemRepositoryInterface>(TYPES.Sync_MongoDBItemRepository) : null,
|
||||
),
|
||||
)
|
||||
container
|
||||
@@ -455,10 +472,7 @@ export class ContainerConfigLoader {
|
||||
container
|
||||
.bind<ItemTransferCalculatorInterface>(TYPES.Sync_ItemTransferCalculator)
|
||||
.toDynamicValue((context: interfaces.Context) => {
|
||||
return new ItemTransferCalculator(
|
||||
context.container.get(TYPES.Sync_ItemRepository),
|
||||
context.container.get(TYPES.Sync_Logger),
|
||||
)
|
||||
return new ItemTransferCalculator(context.container.get<Logger>(TYPES.Sync_Logger))
|
||||
})
|
||||
|
||||
// Middleware
|
||||
@@ -536,7 +550,7 @@ export class ContainerConfigLoader {
|
||||
.bind<GetItems>(TYPES.Sync_GetItems)
|
||||
.toConstantValue(
|
||||
new GetItems(
|
||||
container.get(TYPES.Sync_ItemRepository),
|
||||
container.get(TYPES.Sync_ItemRepositoryResolver),
|
||||
container.get(TYPES.Sync_SharedVaultUserRepository),
|
||||
container.get(TYPES.Sync_CONTENT_SIZE_TRANSFER_LIMIT),
|
||||
container.get(TYPES.Sync_ItemTransferCalculator),
|
||||
@@ -548,7 +562,7 @@ export class ContainerConfigLoader {
|
||||
.bind<SaveNewItem>(TYPES.Sync_SaveNewItem)
|
||||
.toConstantValue(
|
||||
new SaveNewItem(
|
||||
container.get(TYPES.Sync_ItemRepository),
|
||||
container.get(TYPES.Sync_ItemRepositoryResolver),
|
||||
container.get(TYPES.Sync_Timer),
|
||||
container.get(TYPES.Sync_DomainEventPublisher),
|
||||
container.get(TYPES.Sync_DomainEventFactory),
|
||||
@@ -559,6 +573,14 @@ export class ContainerConfigLoader {
|
||||
.toConstantValue(
|
||||
new AddNotificationForUser(container.get(TYPES.Sync_NotificationRepository), container.get(TYPES.Sync_Timer)),
|
||||
)
|
||||
container
|
||||
.bind<AddNotificationsForUsers>(TYPES.Sync_AddNotificationsForUsers)
|
||||
.toConstantValue(
|
||||
new AddNotificationsForUsers(
|
||||
container.get<SharedVaultUserRepositoryInterface>(TYPES.Sync_SharedVaultUserRepository),
|
||||
container.get<AddNotificationForUser>(TYPES.Sync_AddNotificationForUser),
|
||||
),
|
||||
)
|
||||
container
|
||||
.bind<RemoveNotificationsForUser>(TYPES.Sync_RemoveNotificationsForUser)
|
||||
.toConstantValue(new RemoveNotificationsForUser(container.get(TYPES.Sync_NotificationRepository)))
|
||||
@@ -566,7 +588,7 @@ export class ContainerConfigLoader {
|
||||
.bind<UpdateExistingItem>(TYPES.Sync_UpdateExistingItem)
|
||||
.toConstantValue(
|
||||
new UpdateExistingItem(
|
||||
container.get(TYPES.Sync_ItemRepository),
|
||||
container.get(TYPES.Sync_ItemRepositoryResolver),
|
||||
container.get(TYPES.Sync_Timer),
|
||||
container.get(TYPES.Sync_DomainEventPublisher),
|
||||
container.get(TYPES.Sync_DomainEventFactory),
|
||||
@@ -581,7 +603,7 @@ export class ContainerConfigLoader {
|
||||
.toConstantValue(
|
||||
new SaveItems(
|
||||
container.get(TYPES.Sync_ItemSaveValidator),
|
||||
container.get(TYPES.Sync_ItemRepository),
|
||||
container.get(TYPES.Sync_ItemRepositoryResolver),
|
||||
container.get(TYPES.Sync_Timer),
|
||||
container.get(TYPES.Sync_SaveNewItem),
|
||||
container.get(TYPES.Sync_UpdateExistingItem),
|
||||
@@ -610,7 +632,7 @@ export class ContainerConfigLoader {
|
||||
.bind<SyncItems>(TYPES.Sync_SyncItems)
|
||||
.toConstantValue(
|
||||
new SyncItems(
|
||||
container.get(TYPES.Sync_ItemRepository),
|
||||
container.get(TYPES.Sync_ItemRepositoryResolver),
|
||||
container.get(TYPES.Sync_GetItems),
|
||||
container.get(TYPES.Sync_SaveItems),
|
||||
container.get(TYPES.Sync_GetSharedVaults),
|
||||
@@ -620,10 +642,10 @@ export class ContainerConfigLoader {
|
||||
),
|
||||
)
|
||||
container.bind<CheckIntegrity>(TYPES.Sync_CheckIntegrity).toDynamicValue((context: interfaces.Context) => {
|
||||
return new CheckIntegrity(context.container.get(TYPES.Sync_ItemRepository))
|
||||
return new CheckIntegrity(context.container.get(TYPES.Sync_ItemRepositoryResolver))
|
||||
})
|
||||
container.bind<GetItem>(TYPES.Sync_GetItem).toDynamicValue((context: interfaces.Context) => {
|
||||
return new GetItem(context.container.get(TYPES.Sync_ItemRepository))
|
||||
return new GetItem(context.container.get(TYPES.Sync_ItemRepositoryResolver))
|
||||
})
|
||||
container
|
||||
.bind<InviteUserToSharedVault>(TYPES.Sync_InviteUserToSharedVault)
|
||||
@@ -631,6 +653,7 @@ export class ContainerConfigLoader {
|
||||
new InviteUserToSharedVault(
|
||||
container.get(TYPES.Sync_SharedVaultRepository),
|
||||
container.get(TYPES.Sync_SharedVaultInviteRepository),
|
||||
container.get(TYPES.Sync_SharedVaultUserRepository),
|
||||
container.get(TYPES.Sync_Timer),
|
||||
),
|
||||
)
|
||||
@@ -746,6 +769,22 @@ export class ContainerConfigLoader {
|
||||
container.get(TYPES.Sync_DeleteMessage),
|
||||
),
|
||||
)
|
||||
container
|
||||
.bind<UpdateStorageQuotaUsedInSharedVault>(TYPES.Sync_UpdateStorageQuotaUsedInSharedVault)
|
||||
.toConstantValue(
|
||||
new UpdateStorageQuotaUsedInSharedVault(
|
||||
container.get<SharedVaultRepositoryInterface>(TYPES.Sync_SharedVaultRepository),
|
||||
),
|
||||
)
|
||||
container
|
||||
.bind(TransitionItemsFromPrimaryToSecondaryDatabaseForUser)
|
||||
.toConstantValue(
|
||||
new TransitionItemsFromPrimaryToSecondaryDatabaseForUser(
|
||||
container.get<ItemRepositoryInterface>(TYPES.Sync_MySQLItemRepository),
|
||||
isSecondaryDatabaseEnabled ? container.get<ItemRepositoryInterface>(TYPES.Sync_MongoDBItemRepository) : null,
|
||||
container.get<Logger>(TYPES.Sync_Logger),
|
||||
),
|
||||
)
|
||||
|
||||
// Services
|
||||
container
|
||||
@@ -773,91 +812,104 @@ export class ContainerConfigLoader {
|
||||
)
|
||||
})
|
||||
|
||||
// env vars
|
||||
container
|
||||
.bind(TYPES.Sync_EMAIL_ATTACHMENT_MAX_BYTE_SIZE)
|
||||
.bind<ItemBackupServiceInterface>(TYPES.Sync_ItemBackupService)
|
||||
.toConstantValue(
|
||||
env.get('EMAIL_ATTACHMENT_MAX_BYTE_SIZE', true) ? +env.get('EMAIL_ATTACHMENT_MAX_BYTE_SIZE', true) : 10485760,
|
||||
)
|
||||
container.bind(TYPES.Sync_NEW_RELIC_ENABLED).toConstantValue(env.get('NEW_RELIC_ENABLED', true))
|
||||
container
|
||||
.bind(TYPES.Sync_FILE_UPLOAD_PATH)
|
||||
.toConstantValue(
|
||||
env.get('FILE_UPLOAD_PATH', true) ? env.get('FILE_UPLOAD_PATH', true) : this.DEFAULT_FILE_UPLOAD_PATH,
|
||||
env.get('S3_AWS_REGION', true)
|
||||
? new S3ItemBackupService(
|
||||
container.get(TYPES.Sync_S3_BACKUP_BUCKET_NAME),
|
||||
container.get(TYPES.Sync_ItemBackupMapper),
|
||||
container.get(TYPES.Sync_ItemHttpMapper),
|
||||
container.get(TYPES.Sync_Logger),
|
||||
container.get(TYPES.Sync_S3),
|
||||
)
|
||||
: new FSItemBackupService(
|
||||
container.get(TYPES.Sync_FILE_UPLOAD_PATH),
|
||||
container.get(TYPES.Sync_ItemBackupMapper),
|
||||
container.get(TYPES.Sync_Logger),
|
||||
),
|
||||
)
|
||||
|
||||
// Handlers
|
||||
container
|
||||
.bind<DuplicateItemSyncedEventHandler>(TYPES.Sync_DuplicateItemSyncedEventHandler)
|
||||
.toDynamicValue((context: interfaces.Context) => {
|
||||
return new DuplicateItemSyncedEventHandler(
|
||||
context.container.get(TYPES.Sync_ItemRepository),
|
||||
context.container.get(TYPES.Sync_DomainEventFactory),
|
||||
context.container.get(TYPES.Sync_DomainEventPublisher),
|
||||
context.container.get(TYPES.Sync_Logger),
|
||||
)
|
||||
})
|
||||
.toConstantValue(
|
||||
new DuplicateItemSyncedEventHandler(
|
||||
container.get<ItemRepositoryInterface>(TYPES.Sync_MySQLItemRepository),
|
||||
isSecondaryDatabaseEnabled ? container.get<ItemRepositoryInterface>(TYPES.Sync_MongoDBItemRepository) : null,
|
||||
container.get<DomainEventFactoryInterface>(TYPES.Sync_DomainEventFactory),
|
||||
container.get<DomainEventPublisherInterface>(TYPES.Sync_DomainEventPublisher),
|
||||
container.get<Logger>(TYPES.Sync_Logger),
|
||||
),
|
||||
)
|
||||
container
|
||||
.bind<AccountDeletionRequestedEventHandler>(TYPES.Sync_AccountDeletionRequestedEventHandler)
|
||||
.toDynamicValue((context: interfaces.Context) => {
|
||||
return new AccountDeletionRequestedEventHandler(
|
||||
context.container.get(TYPES.Sync_ItemRepository),
|
||||
context.container.get(TYPES.Sync_Logger),
|
||||
)
|
||||
})
|
||||
.toConstantValue(
|
||||
new AccountDeletionRequestedEventHandler(
|
||||
container.get<ItemRepositoryInterface>(TYPES.Sync_MySQLItemRepository),
|
||||
isSecondaryDatabaseEnabled ? container.get<ItemRepositoryInterface>(TYPES.Sync_MongoDBItemRepository) : null,
|
||||
container.get<Logger>(TYPES.Sync_Logger),
|
||||
),
|
||||
)
|
||||
container
|
||||
.bind<ItemRevisionCreationRequestedEventHandler>(TYPES.Sync_ItemRevisionCreationRequestedEventHandler)
|
||||
.toDynamicValue((context: interfaces.Context) => {
|
||||
return new ItemRevisionCreationRequestedEventHandler(
|
||||
context.container.get(TYPES.Sync_ItemRepository),
|
||||
context.container.get(TYPES.Sync_ItemBackupService),
|
||||
context.container.get(TYPES.Sync_DomainEventFactory),
|
||||
context.container.get(TYPES.Sync_DomainEventPublisher),
|
||||
)
|
||||
})
|
||||
.toConstantValue(
|
||||
new ItemRevisionCreationRequestedEventHandler(
|
||||
container.get<ItemRepositoryInterface>(TYPES.Sync_MySQLItemRepository),
|
||||
isSecondaryDatabaseEnabled ? container.get<ItemRepositoryInterface>(TYPES.Sync_MongoDBItemRepository) : null,
|
||||
container.get<ItemBackupServiceInterface>(TYPES.Sync_ItemBackupService),
|
||||
container.get<DomainEventFactoryInterface>(TYPES.Sync_DomainEventFactory),
|
||||
container.get<DomainEventPublisherInterface>(TYPES.Sync_DomainEventPublisher),
|
||||
),
|
||||
)
|
||||
container
|
||||
.bind<SharedVaultFileUploadedEventHandler>(TYPES.Sync_SharedVaultFileUploadedEventHandler)
|
||||
.toConstantValue(
|
||||
new SharedVaultFileUploadedEventHandler(
|
||||
container.get<UpdateStorageQuotaUsedInSharedVault>(TYPES.Sync_UpdateStorageQuotaUsedInSharedVault),
|
||||
container.get<AddNotificationsForUsers>(TYPES.Sync_AddNotificationsForUsers),
|
||||
container.get<winston.Logger>(TYPES.Sync_Logger),
|
||||
),
|
||||
)
|
||||
container
|
||||
.bind<SharedVaultFileRemovedEventHandler>(TYPES.Sync_SharedVaultFileRemovedEventHandler)
|
||||
.toConstantValue(
|
||||
new SharedVaultFileRemovedEventHandler(
|
||||
container.get<UpdateStorageQuotaUsedInSharedVault>(TYPES.Sync_UpdateStorageQuotaUsedInSharedVault),
|
||||
container.get<AddNotificationsForUsers>(TYPES.Sync_AddNotificationsForUsers),
|
||||
container.get<winston.Logger>(TYPES.Sync_Logger),
|
||||
),
|
||||
)
|
||||
|
||||
// Services
|
||||
container.bind<ContentDecoder>(TYPES.Sync_ContentDecoder).toDynamicValue(() => new ContentDecoder())
|
||||
container.bind<AxiosInstance>(TYPES.Sync_HTTPClient).toDynamicValue(() => axios.create())
|
||||
container
|
||||
.bind<ExtensionsHttpServiceInterface>(TYPES.Sync_ExtensionsHttpService)
|
||||
.toDynamicValue((context: interfaces.Context) => {
|
||||
return new ExtensionsHttpService(
|
||||
context.container.get(TYPES.Sync_HTTPClient),
|
||||
context.container.get(TYPES.Sync_ItemRepository),
|
||||
context.container.get(TYPES.Sync_ContentDecoder),
|
||||
context.container.get(TYPES.Sync_DomainEventPublisher),
|
||||
context.container.get(TYPES.Sync_DomainEventFactory),
|
||||
context.container.get(TYPES.Sync_Logger),
|
||||
)
|
||||
})
|
||||
|
||||
container
|
||||
.bind<ItemBackupServiceInterface>(TYPES.Sync_ItemBackupService)
|
||||
.toDynamicValue((context: interfaces.Context) => {
|
||||
const env: Env = context.container.get(TYPES.Sync_Env)
|
||||
|
||||
if (env.get('S3_AWS_REGION', true)) {
|
||||
return new S3ItemBackupService(
|
||||
context.container.get(TYPES.Sync_S3_BACKUP_BUCKET_NAME),
|
||||
context.container.get(TYPES.Sync_ItemBackupMapper),
|
||||
context.container.get(TYPES.Sync_ItemHttpMapper),
|
||||
context.container.get(TYPES.Sync_Logger),
|
||||
context.container.get(TYPES.Sync_S3),
|
||||
)
|
||||
} else {
|
||||
return new FSItemBackupService(
|
||||
context.container.get(TYPES.Sync_FILE_UPLOAD_PATH),
|
||||
context.container.get(TYPES.Sync_ItemBackupMapper),
|
||||
context.container.get(TYPES.Sync_Logger),
|
||||
)
|
||||
}
|
||||
})
|
||||
.toConstantValue(
|
||||
new ExtensionsHttpService(
|
||||
container.get<AxiosInstance>(TYPES.Sync_HTTPClient),
|
||||
container.get<ItemRepositoryInterface>(TYPES.Sync_MySQLItemRepository),
|
||||
isSecondaryDatabaseEnabled ? container.get<ItemRepositoryInterface>(TYPES.Sync_MongoDBItemRepository) : null,
|
||||
container.get<ContentDecoderInterface>(TYPES.Sync_ContentDecoder),
|
||||
container.get<DomainEventPublisherInterface>(TYPES.Sync_DomainEventPublisher),
|
||||
container.get<DomainEventFactoryInterface>(TYPES.Sync_DomainEventFactory),
|
||||
container.get<Logger>(TYPES.Sync_Logger),
|
||||
),
|
||||
)
|
||||
|
||||
const eventHandlers: Map<string, DomainEventHandlerInterface> = new Map([
|
||||
['DUPLICATE_ITEM_SYNCED', container.get(TYPES.Sync_DuplicateItemSyncedEventHandler)],
|
||||
['ACCOUNT_DELETION_REQUESTED', container.get(TYPES.Sync_AccountDeletionRequestedEventHandler)],
|
||||
['ITEM_REVISION_CREATION_REQUESTED', container.get(TYPES.Sync_ItemRevisionCreationRequestedEventHandler)],
|
||||
[
|
||||
'SHARED_VAULT_FILE_UPLOADED',
|
||||
container.get<SharedVaultFileUploadedEventHandler>(TYPES.Sync_SharedVaultFileUploadedEventHandler),
|
||||
],
|
||||
[
|
||||
'SHARED_VAULT_FILE_REMOVED',
|
||||
container.get<SharedVaultFileRemovedEventHandler>(TYPES.Sync_SharedVaultFileRemovedEventHandler),
|
||||
],
|
||||
])
|
||||
if (!isConfiguredForHomeServer) {
|
||||
container.bind(TYPES.Sync_AUTH_SERVER_URL).toConstantValue(env.get('AUTH_SERVER_URL'))
|
||||
@@ -873,19 +925,22 @@ export class ContainerConfigLoader {
|
||||
|
||||
container
|
||||
.bind<EmailBackupRequestedEventHandler>(TYPES.Sync_EmailBackupRequestedEventHandler)
|
||||
.toDynamicValue((context: interfaces.Context) => {
|
||||
return new EmailBackupRequestedEventHandler(
|
||||
context.container.get(TYPES.Sync_ItemRepository),
|
||||
context.container.get(TYPES.Sync_AuthHttpService),
|
||||
context.container.get(TYPES.Sync_ItemBackupService),
|
||||
context.container.get(TYPES.Sync_DomainEventPublisher),
|
||||
context.container.get(TYPES.Sync_DomainEventFactory),
|
||||
context.container.get(TYPES.Sync_EMAIL_ATTACHMENT_MAX_BYTE_SIZE),
|
||||
context.container.get(TYPES.Sync_ItemTransferCalculator),
|
||||
context.container.get(TYPES.Sync_S3_BACKUP_BUCKET_NAME),
|
||||
context.container.get(TYPES.Sync_Logger),
|
||||
)
|
||||
})
|
||||
.toConstantValue(
|
||||
new EmailBackupRequestedEventHandler(
|
||||
container.get<ItemRepositoryInterface>(TYPES.Sync_MySQLItemRepository),
|
||||
isSecondaryDatabaseEnabled
|
||||
? container.get<ItemRepositoryInterface>(TYPES.Sync_MongoDBItemRepository)
|
||||
: null,
|
||||
container.get<AuthHttpServiceInterface>(TYPES.Sync_AuthHttpService),
|
||||
container.get<ItemBackupServiceInterface>(TYPES.Sync_ItemBackupService),
|
||||
container.get<DomainEventPublisherInterface>(TYPES.Sync_DomainEventPublisher),
|
||||
container.get<DomainEventFactoryInterface>(TYPES.Sync_DomainEventFactory),
|
||||
container.get<number>(TYPES.Sync_EMAIL_ATTACHMENT_MAX_BYTE_SIZE),
|
||||
container.get<ItemTransferCalculatorInterface>(TYPES.Sync_ItemTransferCalculator),
|
||||
container.get<string>(TYPES.Sync_S3_BACKUP_BUCKET_NAME),
|
||||
container.get<Logger>(TYPES.Sync_Logger),
|
||||
),
|
||||
)
|
||||
|
||||
eventHandlers.set('EMAIL_BACKUP_REQUESTED', container.get(TYPES.Sync_EmailBackupRequestedEventHandler))
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { DataSource, EntityTarget, LoggerOptions, ObjectLiteral, Repository } from 'typeorm'
|
||||
import { DataSource, EntityTarget, LoggerOptions, MongoRepository, ObjectLiteral, Repository } from 'typeorm'
|
||||
import { MysqlConnectionOptions } from 'typeorm/driver/mysql/MysqlConnectionOptions'
|
||||
import { Env } from './Env'
|
||||
import { SqliteConnectionOptions } from 'typeorm/driver/sqlite/SqliteConnectionOptions'
|
||||
@@ -10,9 +10,11 @@ import { TypeORMSharedVault } from '../Infra/TypeORM/TypeORMSharedVault'
|
||||
import { TypeORMSharedVaultUser } from '../Infra/TypeORM/TypeORMSharedVaultUser'
|
||||
import { TypeORMSharedVaultInvite } from '../Infra/TypeORM/TypeORMSharedVaultInvite'
|
||||
import { TypeORMMessage } from '../Infra/TypeORM/TypeORMMessage'
|
||||
import { MongoDBItem } from '../Infra/TypeORM/MongoDBItem'
|
||||
|
||||
export class AppDataSource {
|
||||
private _dataSource: DataSource | undefined
|
||||
private _secondaryDataSource: DataSource | undefined
|
||||
|
||||
constructor(private env: Env) {}
|
||||
|
||||
@@ -24,8 +26,43 @@ export class AppDataSource {
|
||||
return this._dataSource.getRepository(target)
|
||||
}
|
||||
|
||||
getMongoRepository<Entity extends ObjectLiteral>(target: EntityTarget<Entity>): MongoRepository<Entity> {
|
||||
if (!this._secondaryDataSource) {
|
||||
throw new Error('Secondary DataSource not initialized')
|
||||
}
|
||||
|
||||
return this._secondaryDataSource.getMongoRepository(target)
|
||||
}
|
||||
|
||||
async initialize(): Promise<void> {
|
||||
await this.dataSource.initialize()
|
||||
const secondaryDataSource = this.secondaryDataSource
|
||||
if (secondaryDataSource) {
|
||||
await secondaryDataSource.initialize()
|
||||
}
|
||||
}
|
||||
|
||||
get secondaryDataSource(): DataSource | undefined {
|
||||
this.env.load()
|
||||
|
||||
if (this.env.get('SECONDARY_DB_ENABLED', true) !== 'true') {
|
||||
return undefined
|
||||
}
|
||||
|
||||
this._secondaryDataSource = new DataSource({
|
||||
type: 'mongodb',
|
||||
host: this.env.get('MONGO_HOST'),
|
||||
authSource: 'admin',
|
||||
port: parseInt(this.env.get('MONGO_PORT')),
|
||||
username: this.env.get('MONGO_USERNAME'),
|
||||
password: this.env.get('MONGO_PASSWORD', true),
|
||||
database: this.env.get('MONGO_DATABASE'),
|
||||
entities: [MongoDBItem],
|
||||
retryWrites: false,
|
||||
synchronize: true,
|
||||
})
|
||||
|
||||
return this._secondaryDataSource
|
||||
}
|
||||
|
||||
get dataSource(): DataSource {
|
||||
|
||||
@@ -7,9 +7,9 @@ const TYPES = {
|
||||
Sync_S3: Symbol.for('Sync_S3'),
|
||||
Sync_Env: Symbol.for('Sync_Env'),
|
||||
// Repositories
|
||||
Sync_ItemRepository: Symbol.for('Sync_ItemRepository'),
|
||||
Sync_KeySystemAssociationRepository: Symbol.for('Sync_KeySystemAssociationRepository'),
|
||||
Sync_SharedVaultAssociationRepository: Symbol.for('Sync_SharedVaultAssociationRepository'),
|
||||
Sync_ItemRepositoryResolver: Symbol.for('Sync_ItemRepositoryResolver'),
|
||||
Sync_MySQLItemRepository: Symbol.for('Sync_MySQLItemRepository'),
|
||||
Sync_MongoDBItemRepository: Symbol.for('Sync_MongoDBItemRepository'),
|
||||
Sync_SharedVaultRepository: Symbol.for('Sync_SharedVaultRepository'),
|
||||
Sync_SharedVaultInviteRepository: Symbol.for('Sync_SharedVaultInviteRepository'),
|
||||
Sync_SharedVaultUserRepository: Symbol.for('Sync_SharedVaultUserRepository'),
|
||||
@@ -24,6 +24,8 @@ const TYPES = {
|
||||
Sync_ORMSharedVaultUserRepository: Symbol.for('Sync_ORMSharedVaultUserRepository'),
|
||||
Sync_ORMNotificationRepository: Symbol.for('Sync_ORMNotificationRepository'),
|
||||
Sync_ORMMessageRepository: Symbol.for('Sync_ORMMessageRepository'),
|
||||
// Mongo
|
||||
Sync_ORMMongoItemRepository: Symbol.for('Sync_ORMMongoItemRepository'),
|
||||
// Middleware
|
||||
Sync_AuthMiddleware: Symbol.for('Sync_AuthMiddleware'),
|
||||
// env vars
|
||||
@@ -78,11 +80,18 @@ const TYPES = {
|
||||
Sync_SaveItems: Symbol.for('Sync_SaveItems'),
|
||||
Sync_GetUserNotifications: Symbol.for('Sync_GetUserNotifications'),
|
||||
Sync_DetermineSharedVaultOperationOnItem: Symbol.for('Sync_DetermineSharedVaultOperationOnItem'),
|
||||
Sync_UpdateStorageQuotaUsedInSharedVault: Symbol.for('Sync_UpdateStorageQuotaUsedInSharedVault'),
|
||||
Sync_AddNotificationsForUsers: Symbol.for('Sync_AddNotificationsForUsers'),
|
||||
Sync_TransitionItemsFromPrimaryToSecondaryDatabaseForUser: Symbol.for(
|
||||
'Sync_TransitionItemsFromPrimaryToSecondaryDatabaseForUser',
|
||||
),
|
||||
// Handlers
|
||||
Sync_AccountDeletionRequestedEventHandler: Symbol.for('Sync_AccountDeletionRequestedEventHandler'),
|
||||
Sync_DuplicateItemSyncedEventHandler: Symbol.for('Sync_DuplicateItemSyncedEventHandler'),
|
||||
Sync_EmailBackupRequestedEventHandler: Symbol.for('Sync_EmailBackupRequestedEventHandler'),
|
||||
Sync_ItemRevisionCreationRequestedEventHandler: Symbol.for('Sync_ItemRevisionCreationRequestedEventHandler'),
|
||||
Sync_SharedVaultFileRemovedEventHandler: Symbol.for('Sync_SharedVaultFileRemovedEventHandler'),
|
||||
Sync_SharedVaultFileUploadedEventHandler: Symbol.for('Sync_SharedVaultFileUploadedEventHandler'),
|
||||
// Services
|
||||
Sync_ContentDecoder: Symbol.for('Sync_ContentDecoder'),
|
||||
Sync_DomainEventPublisher: Symbol.for('Sync_DomainEventPublisher'),
|
||||
@@ -120,13 +129,12 @@ const TYPES = {
|
||||
Sync_MessageHttpMapper: Symbol.for('Sync_MessageHttpMapper'),
|
||||
Sync_NotificationHttpMapper: Symbol.for('Sync_NotificationHttpMapper'),
|
||||
Sync_ItemPersistenceMapper: Symbol.for('Sync_ItemPersistenceMapper'),
|
||||
Sync_MongoDBItemPersistenceMapper: Symbol.for('Sync_MongoDBItemPersistenceMapper'),
|
||||
Sync_ItemHttpMapper: Symbol.for('Sync_ItemHttpMapper'),
|
||||
Sync_ItemHashHttpMapper: Symbol.for('Sync_ItemHashHttpMapper'),
|
||||
Sync_SavedItemHttpMapper: Symbol.for('Sync_SavedItemHttpMapper'),
|
||||
Sync_ItemConflictHttpMapper: Symbol.for('Sync_ItemConflictHttpMapper'),
|
||||
Sync_ItemBackupMapper: Symbol.for('Sync_ItemBackupMapper'),
|
||||
Sync_KeySystemAssociationPersistenceMapper: Symbol.for('Sync_KeySystemAssociationPersistenceMapper'),
|
||||
Sync_SharedVaultAssociationPersistenceMapper: Symbol.for('Sync_SharedVaultAssociationPersistenceMapper'),
|
||||
Sync_SharedVaultPersistenceMapper: Symbol.for('Sync_SharedVaultPersistenceMapper'),
|
||||
Sync_SharedVaultUserPersistenceMapper: Symbol.for('Sync_SharedVaultUserPersistenceMapper'),
|
||||
Sync_SharedVaultInvitePersistenceMapper: Symbol.for('Sync_SharedVaultInvitePersistenceMapper'),
|
||||
|
||||
@@ -2,7 +2,7 @@ export const html = (email: string) => `
|
||||
<p>
|
||||
Your encrypted data backup is attached for ${email}. You can import this file using
|
||||
the Standard Notes web or desktop app, or by using the offline decryption script available at
|
||||
<a style="text-decoration:none !important; text-decoration:none;">standardnotes.org/offline</a>.
|
||||
<a style="text-decoration:none !important; text-decoration:none;">standardnotes.com/offline</a>.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
|
||||
@@ -13,7 +13,8 @@ import { Uuid, ContentType, Dates, Timestamps, UniqueEntityId } from '@standardn
|
||||
|
||||
describe('ExtensionsHttpService', () => {
|
||||
let httpClient: AxiosInstance
|
||||
let itemRepository: ItemRepositoryInterface
|
||||
let primaryItemRepository: ItemRepositoryInterface
|
||||
let secondaryItemRepository: ItemRepositoryInterface | null
|
||||
let contentDecoder: ContentDecoderInterface
|
||||
let domainEventPublisher: DomainEventPublisherInterface
|
||||
let domainEventFactory: DomainEventFactoryInterface
|
||||
@@ -24,7 +25,8 @@ describe('ExtensionsHttpService', () => {
|
||||
const createService = () =>
|
||||
new ExtensionsHttpService(
|
||||
httpClient,
|
||||
itemRepository,
|
||||
primaryItemRepository,
|
||||
secondaryItemRepository,
|
||||
contentDecoder,
|
||||
domainEventPublisher,
|
||||
domainEventFactory,
|
||||
@@ -54,8 +56,8 @@ describe('ExtensionsHttpService', () => {
|
||||
|
||||
authParams = {} as jest.Mocked<KeyParamsData>
|
||||
|
||||
itemRepository = {} as jest.Mocked<ItemRepositoryInterface>
|
||||
itemRepository.findByUuidAndUserUuid = jest.fn().mockReturnValue(item)
|
||||
primaryItemRepository = {} as jest.Mocked<ItemRepositoryInterface>
|
||||
primaryItemRepository.findByUuidAndUserUuid = jest.fn().mockReturnValue(item)
|
||||
|
||||
logger = {} as jest.Mocked<Logger>
|
||||
logger.error = jest.fn()
|
||||
@@ -191,6 +193,31 @@ describe('ExtensionsHttpService', () => {
|
||||
expect(domainEventFactory.createEmailRequestedEvent).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should publish a failed backup event if the extension is in the secondary repository', async () => {
|
||||
primaryItemRepository.findByUuidAndUserUuid = jest.fn().mockReturnValue(null)
|
||||
secondaryItemRepository = {} as jest.Mocked<ItemRepositoryInterface>
|
||||
secondaryItemRepository.findByUuidAndUserUuid = jest.fn().mockReturnValue(item)
|
||||
|
||||
httpClient.request = jest.fn().mockImplementation(() => {
|
||||
throw new Error('Could not reach the extensions server')
|
||||
})
|
||||
|
||||
await createService().sendItemsToExtensionsServer({
|
||||
userUuid: '1-2-3',
|
||||
extensionId: '2-3-4',
|
||||
extensionsServerUrl: '',
|
||||
forceMute: false,
|
||||
items: [item],
|
||||
backupFilename: 'backup-file',
|
||||
authParams,
|
||||
})
|
||||
|
||||
expect(domainEventPublisher.publish).toHaveBeenCalled()
|
||||
expect(domainEventFactory.createEmailRequestedEvent).toHaveBeenCalled()
|
||||
|
||||
secondaryItemRepository = null
|
||||
})
|
||||
|
||||
it('should publish a failed Dropbox backup event if request was sent and extensions server responded not ok', async () => {
|
||||
contentDecoder.decode = jest.fn().mockReturnValue({ name: 'Dropbox' })
|
||||
|
||||
@@ -273,7 +300,7 @@ describe('ExtensionsHttpService', () => {
|
||||
})
|
||||
|
||||
it('should throw an error if the extension to post to is not found', async () => {
|
||||
itemRepository.findByUuidAndUserUuid = jest.fn().mockReturnValue(null)
|
||||
primaryItemRepository.findByUuidAndUserUuid = jest.fn().mockReturnValue(null)
|
||||
|
||||
httpClient.request = jest.fn().mockImplementation(() => {
|
||||
throw new Error('Could not reach the extensions server')
|
||||
@@ -299,7 +326,7 @@ describe('ExtensionsHttpService', () => {
|
||||
|
||||
it('should throw an error if the extension to post to has no content', async () => {
|
||||
item = {} as jest.Mocked<Item>
|
||||
itemRepository.findByUuidAndUserUuid = jest.fn().mockReturnValue(item)
|
||||
primaryItemRepository.findByUuidAndUserUuid = jest.fn().mockReturnValue(item)
|
||||
|
||||
httpClient.request = jest.fn().mockImplementation(() => {
|
||||
throw new Error('Could not reach the extensions server')
|
||||
|
||||
@@ -17,7 +17,8 @@ import { getBody as oneDriveBody, getSubject as oneDriveSubject } from '../Email
|
||||
export class ExtensionsHttpService implements ExtensionsHttpServiceInterface {
|
||||
constructor(
|
||||
private httpClient: AxiosInstance,
|
||||
private itemRepository: ItemRepositoryInterface,
|
||||
private primaryItemRepository: ItemRepositoryInterface,
|
||||
private secondaryItemRepository: ItemRepositoryInterface | null,
|
||||
private contentDecoder: ContentDecoderInterface,
|
||||
private domainEventPublisher: DomainEventPublisherInterface,
|
||||
private domainEventFactory: DomainEventFactoryInterface,
|
||||
@@ -139,9 +140,14 @@ export class ExtensionsHttpService implements ExtensionsHttpServiceInterface {
|
||||
userUuid: string,
|
||||
email: string,
|
||||
): Promise<DomainEventInterface> {
|
||||
const extension = await this.itemRepository.findByUuidAndUserUuid(extensionId, userUuid)
|
||||
let extension = await this.primaryItemRepository.findByUuidAndUserUuid(extensionId, userUuid)
|
||||
if (extension === null || !extension.props.content) {
|
||||
throw Error(`Could not find extensions with id ${extensionId}`)
|
||||
if (this.secondaryItemRepository) {
|
||||
extension = await this.secondaryItemRepository.findByUuidAndUserUuid(extensionId, userUuid)
|
||||
}
|
||||
if (extension === null || !extension.props.content) {
|
||||
throw Error(`Could not find extensions with id ${extensionId}`)
|
||||
}
|
||||
}
|
||||
|
||||
const content = this.contentDecoder.decode(extension.props.content)
|
||||
|
||||
+19
-6
@@ -8,12 +8,14 @@ import { AccountDeletionRequestedEventHandler } from './AccountDeletionRequested
|
||||
import { Uuid, ContentType, Dates, Timestamps, UniqueEntityId } from '@standardnotes/domain-core'
|
||||
|
||||
describe('AccountDeletionRequestedEventHandler', () => {
|
||||
let itemRepository: ItemRepositoryInterface
|
||||
let primaryItemRepository: ItemRepositoryInterface
|
||||
let secondaryItemRepository: ItemRepositoryInterface | null
|
||||
let logger: Logger
|
||||
let event: AccountDeletionRequestedEvent
|
||||
let item: Item
|
||||
|
||||
const createHandler = () => new AccountDeletionRequestedEventHandler(itemRepository, logger)
|
||||
const createHandler = () =>
|
||||
new AccountDeletionRequestedEventHandler(primaryItemRepository, secondaryItemRepository, logger)
|
||||
|
||||
beforeEach(() => {
|
||||
item = Item.create(
|
||||
@@ -33,9 +35,9 @@ describe('AccountDeletionRequestedEventHandler', () => {
|
||||
new UniqueEntityId('00000000-0000-0000-0000-000000000000'),
|
||||
).getValue()
|
||||
|
||||
itemRepository = {} as jest.Mocked<ItemRepositoryInterface>
|
||||
itemRepository.findAll = jest.fn().mockReturnValue([item])
|
||||
itemRepository.deleteByUserUuid = jest.fn()
|
||||
primaryItemRepository = {} as jest.Mocked<ItemRepositoryInterface>
|
||||
primaryItemRepository.findAll = jest.fn().mockReturnValue([item])
|
||||
primaryItemRepository.deleteByUserUuid = jest.fn()
|
||||
|
||||
logger = {} as jest.Mocked<Logger>
|
||||
logger.info = jest.fn()
|
||||
@@ -52,6 +54,17 @@ describe('AccountDeletionRequestedEventHandler', () => {
|
||||
it('should remove all items for a user', async () => {
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(itemRepository.deleteByUserUuid).toHaveBeenCalledWith('2-3-4')
|
||||
expect(primaryItemRepository.deleteByUserUuid).toHaveBeenCalledWith('2-3-4')
|
||||
})
|
||||
|
||||
it('should remove all items for a user from secondary repository', async () => {
|
||||
secondaryItemRepository = {} as jest.Mocked<ItemRepositoryInterface>
|
||||
secondaryItemRepository.deleteByUserUuid = jest.fn()
|
||||
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(secondaryItemRepository.deleteByUserUuid).toHaveBeenCalledWith('2-3-4')
|
||||
|
||||
secondaryItemRepository = null
|
||||
})
|
||||
})
|
||||
|
||||
@@ -3,10 +3,17 @@ import { Logger } from 'winston'
|
||||
import { ItemRepositoryInterface } from '../Item/ItemRepositoryInterface'
|
||||
|
||||
export class AccountDeletionRequestedEventHandler implements DomainEventHandlerInterface {
|
||||
constructor(private itemRepository: ItemRepositoryInterface, private logger: Logger) {}
|
||||
constructor(
|
||||
private primaryItemRepository: ItemRepositoryInterface,
|
||||
private secondaryItemRepository: ItemRepositoryInterface | null,
|
||||
private logger: Logger,
|
||||
) {}
|
||||
|
||||
async handle(event: AccountDeletionRequestedEvent): Promise<void> {
|
||||
await this.itemRepository.deleteByUserUuid(event.payload.userUuid)
|
||||
await this.primaryItemRepository.deleteByUserUuid(event.payload.userUuid)
|
||||
if (this.secondaryItemRepository) {
|
||||
await this.secondaryItemRepository.deleteByUserUuid(event.payload.userUuid)
|
||||
}
|
||||
|
||||
this.logger.info(`Finished account cleanup for user: ${event.payload.userUuid}`)
|
||||
}
|
||||
|
||||
@@ -13,7 +13,8 @@ import { DomainEventFactoryInterface } from '../Event/DomainEventFactoryInterfac
|
||||
import { Uuid, ContentType, Dates, Timestamps, UniqueEntityId } from '@standardnotes/domain-core'
|
||||
|
||||
describe('DuplicateItemSyncedEventHandler', () => {
|
||||
let itemRepository: ItemRepositoryInterface
|
||||
let primaryItemRepository: ItemRepositoryInterface
|
||||
let secondaryItemRepository: ItemRepositoryInterface | null
|
||||
let logger: Logger
|
||||
let duplicateItem: Item
|
||||
let originalItem: Item
|
||||
@@ -22,7 +23,13 @@ describe('DuplicateItemSyncedEventHandler', () => {
|
||||
let domainEventPublisher: DomainEventPublisherInterface
|
||||
|
||||
const createHandler = () =>
|
||||
new DuplicateItemSyncedEventHandler(itemRepository, domainEventFactory, domainEventPublisher, logger)
|
||||
new DuplicateItemSyncedEventHandler(
|
||||
primaryItemRepository,
|
||||
secondaryItemRepository,
|
||||
domainEventFactory,
|
||||
domainEventPublisher,
|
||||
logger,
|
||||
)
|
||||
|
||||
beforeEach(() => {
|
||||
originalItem = Item.create(
|
||||
@@ -59,8 +66,8 @@ describe('DuplicateItemSyncedEventHandler', () => {
|
||||
new UniqueEntityId('00000000-0000-0000-0000-000000000001'),
|
||||
).getValue()
|
||||
|
||||
itemRepository = {} as jest.Mocked<ItemRepositoryInterface>
|
||||
itemRepository.findByUuidAndUserUuid = jest
|
||||
primaryItemRepository = {} as jest.Mocked<ItemRepositoryInterface>
|
||||
primaryItemRepository.findByUuidAndUserUuid = jest
|
||||
.fn()
|
||||
.mockReturnValueOnce(duplicateItem)
|
||||
.mockReturnValueOnce(originalItem)
|
||||
@@ -90,8 +97,22 @@ describe('DuplicateItemSyncedEventHandler', () => {
|
||||
expect(domainEventPublisher.publish).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should copy revisions from original item to the duplicate item in the secondary repository', async () => {
|
||||
secondaryItemRepository = {} as jest.Mocked<ItemRepositoryInterface>
|
||||
secondaryItemRepository.findByUuidAndUserUuid = jest
|
||||
.fn()
|
||||
.mockReturnValueOnce(duplicateItem)
|
||||
.mockReturnValueOnce(originalItem)
|
||||
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(domainEventPublisher.publish).toHaveBeenCalledTimes(2)
|
||||
|
||||
secondaryItemRepository = null
|
||||
})
|
||||
|
||||
it('should not copy revisions if original item does not exist', async () => {
|
||||
itemRepository.findByUuidAndUserUuid = jest.fn().mockReturnValueOnce(duplicateItem).mockReturnValueOnce(null)
|
||||
primaryItemRepository.findByUuidAndUserUuid = jest.fn().mockReturnValueOnce(duplicateItem).mockReturnValueOnce(null)
|
||||
|
||||
await createHandler().handle(event)
|
||||
|
||||
@@ -99,7 +120,7 @@ describe('DuplicateItemSyncedEventHandler', () => {
|
||||
})
|
||||
|
||||
it('should not copy revisions if duplicate item does not exist', async () => {
|
||||
itemRepository.findByUuidAndUserUuid = jest.fn().mockReturnValueOnce(null).mockReturnValueOnce(originalItem)
|
||||
primaryItemRepository.findByUuidAndUserUuid = jest.fn().mockReturnValueOnce(null).mockReturnValueOnce(originalItem)
|
||||
|
||||
await createHandler().handle(event)
|
||||
|
||||
|
||||
@@ -9,14 +9,26 @@ import { ItemRepositoryInterface } from '../Item/ItemRepositoryInterface'
|
||||
|
||||
export class DuplicateItemSyncedEventHandler implements DomainEventHandlerInterface {
|
||||
constructor(
|
||||
private itemRepository: ItemRepositoryInterface,
|
||||
private primaryItemRepository: ItemRepositoryInterface,
|
||||
private secondaryItemRepository: ItemRepositoryInterface | null,
|
||||
private domainEventFactory: DomainEventFactoryInterface,
|
||||
private domainEventPublisher: DomainEventPublisherInterface,
|
||||
private logger: Logger,
|
||||
) {}
|
||||
|
||||
async handle(event: DuplicateItemSyncedEvent): Promise<void> {
|
||||
const item = await this.itemRepository.findByUuidAndUserUuid(event.payload.itemUuid, event.payload.userUuid)
|
||||
await this.requestRevisionsCopy(event, this.primaryItemRepository)
|
||||
|
||||
if (this.secondaryItemRepository) {
|
||||
await this.requestRevisionsCopy(event, this.secondaryItemRepository)
|
||||
}
|
||||
}
|
||||
|
||||
private async requestRevisionsCopy(
|
||||
event: DuplicateItemSyncedEvent,
|
||||
itemRepository: ItemRepositoryInterface,
|
||||
): Promise<void> {
|
||||
const item = await itemRepository.findByUuidAndUserUuid(event.payload.itemUuid, event.payload.userUuid)
|
||||
|
||||
if (item === null) {
|
||||
this.logger.warn(`Could not find item with uuid ${event.payload.itemUuid}`)
|
||||
@@ -30,7 +42,7 @@ export class DuplicateItemSyncedEventHandler implements DomainEventHandlerInterf
|
||||
return
|
||||
}
|
||||
|
||||
const existingOriginalItem = await this.itemRepository.findByUuidAndUserUuid(
|
||||
const existingOriginalItem = await itemRepository.findByUuidAndUserUuid(
|
||||
item.props.duplicateOf.value,
|
||||
event.payload.userUuid,
|
||||
)
|
||||
|
||||
+25
-4
@@ -13,9 +13,11 @@ import { ItemBackupServiceInterface } from '../Item/ItemBackupServiceInterface'
|
||||
import { ItemRepositoryInterface } from '../Item/ItemRepositoryInterface'
|
||||
import { EmailBackupRequestedEventHandler } from './EmailBackupRequestedEventHandler'
|
||||
import { ItemTransferCalculatorInterface } from '../Item/ItemTransferCalculatorInterface'
|
||||
import { ItemContentSizeDescriptor } from '../Item/ItemContentSizeDescriptor'
|
||||
|
||||
describe('EmailBackupRequestedEventHandler', () => {
|
||||
let itemRepository: ItemRepositoryInterface
|
||||
let primaryItemRepository: ItemRepositoryInterface
|
||||
let secondaryItemRepository: ItemRepositoryInterface | null
|
||||
let authHttpService: AuthHttpServiceInterface
|
||||
let itemBackupService: ItemBackupServiceInterface
|
||||
let domainEventPublisher: DomainEventPublisherInterface
|
||||
@@ -28,7 +30,8 @@ describe('EmailBackupRequestedEventHandler', () => {
|
||||
|
||||
const createHandler = () =>
|
||||
new EmailBackupRequestedEventHandler(
|
||||
itemRepository,
|
||||
primaryItemRepository,
|
||||
secondaryItemRepository,
|
||||
authHttpService,
|
||||
itemBackupService,
|
||||
domainEventPublisher,
|
||||
@@ -42,8 +45,11 @@ describe('EmailBackupRequestedEventHandler', () => {
|
||||
beforeEach(() => {
|
||||
item = {} as jest.Mocked<Item>
|
||||
|
||||
itemRepository = {} as jest.Mocked<ItemRepositoryInterface>
|
||||
itemRepository.findAll = jest.fn().mockReturnValue([item])
|
||||
primaryItemRepository = {} as jest.Mocked<ItemRepositoryInterface>
|
||||
primaryItemRepository.findAll = jest.fn().mockReturnValue([item])
|
||||
primaryItemRepository.findContentSizeForComputingTransferLimit = jest
|
||||
.fn()
|
||||
.mockResolvedValue([ItemContentSizeDescriptor.create('00000000-0000-0000-0000-000000000000', 20).getValue()])
|
||||
|
||||
authHttpService = {} as jest.Mocked<AuthHttpServiceInterface>
|
||||
authHttpService.getUserKeyParams = jest.fn().mockReturnValue({ identifier: 'test@test.com' })
|
||||
@@ -81,6 +87,21 @@ describe('EmailBackupRequestedEventHandler', () => {
|
||||
expect(domainEventFactory.createEmailRequestedEvent).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should inform that backup attachment for email was created in the secondary repository', async () => {
|
||||
secondaryItemRepository = {} as jest.Mocked<ItemRepositoryInterface>
|
||||
secondaryItemRepository.findAll = jest.fn().mockReturnValue([item])
|
||||
secondaryItemRepository.findContentSizeForComputingTransferLimit = jest
|
||||
.fn()
|
||||
.mockResolvedValue([ItemContentSizeDescriptor.create('00000000-0000-0000-0000-000000000000', 20).getValue()])
|
||||
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(domainEventPublisher.publish).toHaveBeenCalledTimes(2)
|
||||
expect(domainEventFactory.createEmailRequestedEvent).toHaveBeenCalledTimes(2)
|
||||
|
||||
secondaryItemRepository = null
|
||||
})
|
||||
|
||||
it('should inform that multipart backup attachment for email was created', async () => {
|
||||
itemBackupService.backup = jest
|
||||
.fn()
|
||||
|
||||
@@ -16,7 +16,8 @@ import { getBody, getSubject } from '../Email/EmailBackupAttachmentCreated'
|
||||
|
||||
export class EmailBackupRequestedEventHandler implements DomainEventHandlerInterface {
|
||||
constructor(
|
||||
private itemRepository: ItemRepositoryInterface,
|
||||
private primaryItemRepository: ItemRepositoryInterface,
|
||||
private secondaryItemRepository: ItemRepositoryInterface | null,
|
||||
private authHttpService: AuthHttpServiceInterface,
|
||||
private itemBackupService: ItemBackupServiceInterface,
|
||||
private domainEventPublisher: DomainEventPublisherInterface,
|
||||
@@ -28,6 +29,17 @@ export class EmailBackupRequestedEventHandler implements DomainEventHandlerInter
|
||||
) {}
|
||||
|
||||
async handle(event: EmailBackupRequestedEvent): Promise<void> {
|
||||
await this.requestEmailWithBackupFile(event, this.primaryItemRepository)
|
||||
|
||||
if (this.secondaryItemRepository) {
|
||||
await this.requestEmailWithBackupFile(event, this.secondaryItemRepository)
|
||||
}
|
||||
}
|
||||
|
||||
private async requestEmailWithBackupFile(
|
||||
event: EmailBackupRequestedEvent,
|
||||
itemRepository: ItemRepositoryInterface,
|
||||
): Promise<void> {
|
||||
let authParams: KeyParamsData
|
||||
try {
|
||||
authParams = await this.authHttpService.getUserKeyParams({
|
||||
@@ -46,14 +58,15 @@ export class EmailBackupRequestedEventHandler implements DomainEventHandlerInter
|
||||
sortOrder: 'ASC',
|
||||
deleted: false,
|
||||
}
|
||||
const itemContentSizeDescriptors = await itemRepository.findContentSizeForComputingTransferLimit(itemQuery)
|
||||
const itemUuidBundles = await this.itemTransferCalculator.computeItemUuidBundlesToFetch(
|
||||
itemQuery,
|
||||
itemContentSizeDescriptors,
|
||||
this.emailAttachmentMaxByteSize,
|
||||
)
|
||||
|
||||
const backupFileNames: string[] = []
|
||||
for (const itemUuidBundle of itemUuidBundles) {
|
||||
const items = await this.itemRepository.findAll({
|
||||
const items = await itemRepository.findAll({
|
||||
uuids: itemUuidBundle,
|
||||
sortBy: 'updated_at_timestamp',
|
||||
sortOrder: 'ASC',
|
||||
|
||||
+19
-5
@@ -14,7 +14,8 @@ import { DomainEventFactoryInterface } from '../Event/DomainEventFactoryInterfac
|
||||
import { Uuid, ContentType, Dates, Timestamps, UniqueEntityId } from '@standardnotes/domain-core'
|
||||
|
||||
describe('ItemRevisionCreationRequestedEventHandler', () => {
|
||||
let itemRepository: ItemRepositoryInterface
|
||||
let primaryItemRepository: ItemRepositoryInterface
|
||||
let secondaryItemRepository: ItemRepositoryInterface | null
|
||||
let event: ItemRevisionCreationRequestedEvent
|
||||
let item: Item
|
||||
let itemBackupService: ItemBackupServiceInterface
|
||||
@@ -23,7 +24,8 @@ describe('ItemRevisionCreationRequestedEventHandler', () => {
|
||||
|
||||
const createHandler = () =>
|
||||
new ItemRevisionCreationRequestedEventHandler(
|
||||
itemRepository,
|
||||
primaryItemRepository,
|
||||
secondaryItemRepository,
|
||||
itemBackupService,
|
||||
domainEventFactory,
|
||||
domainEventPublisher,
|
||||
@@ -47,8 +49,8 @@ describe('ItemRevisionCreationRequestedEventHandler', () => {
|
||||
new UniqueEntityId('00000000-0000-0000-0000-000000000000'),
|
||||
).getValue()
|
||||
|
||||
itemRepository = {} as jest.Mocked<ItemRepositoryInterface>
|
||||
itemRepository.findByUuid = jest.fn().mockReturnValue(item)
|
||||
primaryItemRepository = {} as jest.Mocked<ItemRepositoryInterface>
|
||||
primaryItemRepository.findByUuid = jest.fn().mockReturnValue(item)
|
||||
|
||||
event = {} as jest.Mocked<ItemRevisionCreationRequestedEvent>
|
||||
event.createdAt = new Date(1)
|
||||
@@ -80,8 +82,20 @@ describe('ItemRevisionCreationRequestedEventHandler', () => {
|
||||
expect(domainEventFactory.createItemDumpedEvent).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should create a revision for an item in the secondary repository', async () => {
|
||||
secondaryItemRepository = {} as jest.Mocked<ItemRepositoryInterface>
|
||||
secondaryItemRepository.findByUuid = jest.fn().mockReturnValue(item)
|
||||
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(domainEventPublisher.publish).toHaveBeenCalled()
|
||||
expect(domainEventFactory.createItemDumpedEvent).toHaveBeenCalled()
|
||||
|
||||
secondaryItemRepository = null
|
||||
})
|
||||
|
||||
it('should not create a revision for an item that does not exist', async () => {
|
||||
itemRepository.findByUuid = jest.fn().mockReturnValue(null)
|
||||
primaryItemRepository.findByUuid = jest.fn().mockReturnValue(null)
|
||||
|
||||
await createHandler().handle(event)
|
||||
|
||||
|
||||
+14
-2
@@ -11,20 +11,32 @@ import { ItemRepositoryInterface } from '../Item/ItemRepositoryInterface'
|
||||
|
||||
export class ItemRevisionCreationRequestedEventHandler implements DomainEventHandlerInterface {
|
||||
constructor(
|
||||
private itemRepository: ItemRepositoryInterface,
|
||||
private primaryItemRepository: ItemRepositoryInterface,
|
||||
private secondaryItemRepository: ItemRepositoryInterface | null,
|
||||
private itemBackupService: ItemBackupServiceInterface,
|
||||
private domainEventFactory: DomainEventFactoryInterface,
|
||||
private domainEventPublisher: DomainEventPublisherInterface,
|
||||
) {}
|
||||
|
||||
async handle(event: ItemRevisionCreationRequestedEvent): Promise<void> {
|
||||
await this.createItemDump(event, this.primaryItemRepository)
|
||||
|
||||
if (this.secondaryItemRepository) {
|
||||
await this.createItemDump(event, this.secondaryItemRepository)
|
||||
}
|
||||
}
|
||||
|
||||
private async createItemDump(
|
||||
event: ItemRevisionCreationRequestedEvent,
|
||||
itemRepository: ItemRepositoryInterface,
|
||||
): Promise<void> {
|
||||
const itemUuidOrError = Uuid.create(event.payload.itemUuid)
|
||||
if (itemUuidOrError.isFailed()) {
|
||||
return
|
||||
}
|
||||
const itemUuid = itemUuidOrError.getValue()
|
||||
|
||||
const item = await this.itemRepository.findByUuid(itemUuid)
|
||||
const item = await itemRepository.findByUuid(itemUuid)
|
||||
if (item === null) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import { DomainEventHandlerInterface, SharedVaultFileRemovedEvent } from '@standardnotes/domain-events'
|
||||
import { NotificationPayload, NotificationType, Uuid } from '@standardnotes/domain-core'
|
||||
import { Logger } from 'winston'
|
||||
|
||||
import { UpdateStorageQuotaUsedInSharedVault } from '../UseCase/SharedVaults/UpdateStorageQuotaUsedInSharedVault/UpdateStorageQuotaUsedInSharedVault'
|
||||
import { AddNotificationsForUsers } from '../UseCase/Messaging/AddNotificationsForUsers/AddNotificationsForUsers'
|
||||
|
||||
export class SharedVaultFileRemovedEventHandler implements DomainEventHandlerInterface {
|
||||
constructor(
|
||||
private updateStorageQuotaUsedInSharedVaultUseCase: UpdateStorageQuotaUsedInSharedVault,
|
||||
private addNotificationsForUsers: AddNotificationsForUsers,
|
||||
private logger: Logger,
|
||||
) {}
|
||||
|
||||
async handle(event: SharedVaultFileRemovedEvent): Promise<void> {
|
||||
const sharedVaultUuidOrError = Uuid.create(event.payload.sharedVaultUuid)
|
||||
if (sharedVaultUuidOrError.isFailed()) {
|
||||
this.logger.error(sharedVaultUuidOrError.getError())
|
||||
|
||||
return
|
||||
}
|
||||
const sharedVaultUuid = sharedVaultUuidOrError.getValue()
|
||||
|
||||
const result = await this.updateStorageQuotaUsedInSharedVaultUseCase.execute({
|
||||
sharedVaultUuid: event.payload.sharedVaultUuid,
|
||||
bytesUsed: -event.payload.fileByteSize,
|
||||
})
|
||||
|
||||
if (result.isFailed()) {
|
||||
this.logger.error(`Failed to update storage quota used in shared vault: ${result.getError()}`)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
const notificationPayload = NotificationPayload.create({
|
||||
sharedVaultUuid,
|
||||
type: NotificationType.create(NotificationType.TYPES.SharedVaultFileRemoved).getValue(),
|
||||
version: '1.0',
|
||||
}).getValue()
|
||||
|
||||
const notificationResult = await this.addNotificationsForUsers.execute({
|
||||
sharedVaultUuid: event.payload.sharedVaultUuid,
|
||||
type: NotificationType.TYPES.SharedVaultFileRemoved,
|
||||
payload: notificationPayload,
|
||||
version: '1.0',
|
||||
})
|
||||
if (notificationResult.isFailed()) {
|
||||
this.logger.error(`Failed to add notification for users: ${notificationResult.getError()}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import { DomainEventHandlerInterface, SharedVaultFileUploadedEvent } from '@standardnotes/domain-events'
|
||||
import { NotificationPayload, NotificationType, Uuid } from '@standardnotes/domain-core'
|
||||
import { Logger } from 'winston'
|
||||
|
||||
import { UpdateStorageQuotaUsedInSharedVault } from '../UseCase/SharedVaults/UpdateStorageQuotaUsedInSharedVault/UpdateStorageQuotaUsedInSharedVault'
|
||||
import { AddNotificationsForUsers } from '../UseCase/Messaging/AddNotificationsForUsers/AddNotificationsForUsers'
|
||||
|
||||
export class SharedVaultFileUploadedEventHandler implements DomainEventHandlerInterface {
|
||||
constructor(
|
||||
private updateStorageQuotaUsedInSharedVaultUseCase: UpdateStorageQuotaUsedInSharedVault,
|
||||
private addNotificationsForUsers: AddNotificationsForUsers,
|
||||
private logger: Logger,
|
||||
) {}
|
||||
|
||||
async handle(event: SharedVaultFileUploadedEvent): Promise<void> {
|
||||
const sharedVaultUuidOrError = Uuid.create(event.payload.sharedVaultUuid)
|
||||
if (sharedVaultUuidOrError.isFailed()) {
|
||||
this.logger.error(sharedVaultUuidOrError.getError())
|
||||
|
||||
return
|
||||
}
|
||||
const sharedVaultUuid = sharedVaultUuidOrError.getValue()
|
||||
|
||||
const result = await this.updateStorageQuotaUsedInSharedVaultUseCase.execute({
|
||||
sharedVaultUuid: event.payload.sharedVaultUuid,
|
||||
bytesUsed: event.payload.fileByteSize,
|
||||
})
|
||||
|
||||
if (result.isFailed()) {
|
||||
this.logger.error(`Failed to update storage quota used in shared vault: ${result.getError()}`)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
const notificationPayload = NotificationPayload.create({
|
||||
sharedVaultUuid,
|
||||
type: NotificationType.create(NotificationType.TYPES.SharedVaultFileUploaded).getValue(),
|
||||
version: '1.0',
|
||||
}).getValue()
|
||||
|
||||
const notificationResult = await this.addNotificationsForUsers.execute({
|
||||
sharedVaultUuid: event.payload.sharedVaultUuid,
|
||||
type: NotificationType.TYPES.SharedVaultFileUploaded,
|
||||
payload: notificationPayload,
|
||||
version: '1.0',
|
||||
})
|
||||
if (notificationResult.isFailed()) {
|
||||
this.logger.error(`Failed to add notification for users: ${notificationResult.getError()}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -61,10 +61,8 @@ describe('Item', () => {
|
||||
dates: Dates.create(new Date(123), new Date(123)).getValue(),
|
||||
timestamps: Timestamps.create(123, 123).getValue(),
|
||||
sharedVaultAssociation: SharedVaultAssociation.create({
|
||||
itemUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
sharedVaultUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
lastEditedBy: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
timestamps: Timestamps.create(123, 123).getValue(),
|
||||
}).getValue(),
|
||||
})
|
||||
|
||||
@@ -113,9 +111,7 @@ describe('Item', () => {
|
||||
dates: Dates.create(new Date(123), new Date(123)).getValue(),
|
||||
timestamps: Timestamps.create(123, 123).getValue(),
|
||||
keySystemAssociation: KeySystemAssociation.create({
|
||||
itemUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
keySystemIdentifier: 'key-system-identifier',
|
||||
timestamps: Timestamps.create(123, 123).getValue(),
|
||||
}).getValue(),
|
||||
})
|
||||
|
||||
@@ -144,10 +140,8 @@ describe('Item', () => {
|
||||
|
||||
it('should set shared vault association', () => {
|
||||
const sharedVaultAssociation = SharedVaultAssociation.create({
|
||||
itemUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
sharedVaultUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
lastEditedBy: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
timestamps: Timestamps.create(123, 123).getValue(),
|
||||
}).getValue()
|
||||
|
||||
const entity = Item.create({
|
||||
@@ -184,10 +178,8 @@ describe('Item', () => {
|
||||
dates: Dates.create(new Date(123), new Date(123)).getValue(),
|
||||
timestamps: Timestamps.create(123, 123).getValue(),
|
||||
sharedVaultAssociation: SharedVaultAssociation.create({
|
||||
itemUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
sharedVaultUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
lastEditedBy: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
timestamps: Timestamps.create(123, 123).getValue(),
|
||||
}).getValue(),
|
||||
}).getValue()
|
||||
|
||||
@@ -199,9 +191,7 @@ describe('Item', () => {
|
||||
|
||||
it('should set key system association', () => {
|
||||
const keySystemAssociation = KeySystemAssociation.create({
|
||||
itemUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
keySystemIdentifier: 'key-system-identifier',
|
||||
timestamps: Timestamps.create(123, 123).getValue(),
|
||||
}).getValue()
|
||||
|
||||
const entity = Item.create({
|
||||
@@ -238,9 +228,7 @@ describe('Item', () => {
|
||||
dates: Dates.create(new Date(123), new Date(123)).getValue(),
|
||||
timestamps: Timestamps.create(123, 123).getValue(),
|
||||
keySystemAssociation: KeySystemAssociation.create({
|
||||
itemUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
keySystemIdentifier: 'key-system-identifier',
|
||||
timestamps: Timestamps.create(123, 123).getValue(),
|
||||
}).getValue(),
|
||||
}).getValue()
|
||||
|
||||
@@ -249,4 +237,118 @@ describe('Item', () => {
|
||||
expect(entity.props.keySystemAssociation).toBeUndefined()
|
||||
expect(entity.getChanges()).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('should tell if an item is identical to another item', () => {
|
||||
const entity = Item.create(
|
||||
{
|
||||
duplicateOf: null,
|
||||
itemsKeyId: 'items-key-id',
|
||||
content: 'content',
|
||||
contentType: ContentType.create(ContentType.TYPES.Note).getValue(),
|
||||
encItemKey: 'enc-item-key',
|
||||
authHash: 'auth-hash',
|
||||
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
deleted: false,
|
||||
updatedWithSession: null,
|
||||
dates: Dates.create(new Date(123), new Date(123)).getValue(),
|
||||
timestamps: Timestamps.create(123, 123).getValue(),
|
||||
},
|
||||
new UniqueEntityId('00000000-0000-0000-0000-000000000000'),
|
||||
).getValue()
|
||||
|
||||
const otherEntity = Item.create(
|
||||
{
|
||||
duplicateOf: null,
|
||||
itemsKeyId: 'items-key-id',
|
||||
content: 'content',
|
||||
contentType: ContentType.create(ContentType.TYPES.Note).getValue(),
|
||||
encItemKey: 'enc-item-key',
|
||||
authHash: 'auth-hash',
|
||||
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
deleted: false,
|
||||
updatedWithSession: null,
|
||||
dates: Dates.create(new Date(123), new Date(123)).getValue(),
|
||||
timestamps: Timestamps.create(123, 123).getValue(),
|
||||
},
|
||||
new UniqueEntityId('00000000-0000-0000-0000-000000000000'),
|
||||
).getValue()
|
||||
|
||||
expect(entity.isIdenticalTo(otherEntity)).toBeTruthy()
|
||||
})
|
||||
|
||||
it('should tell that an item is not identical to another item', () => {
|
||||
const entity = Item.create(
|
||||
{
|
||||
duplicateOf: null,
|
||||
itemsKeyId: 'items-key-id',
|
||||
content: 'content',
|
||||
contentType: ContentType.create(ContentType.TYPES.Note).getValue(),
|
||||
encItemKey: 'enc-item-key',
|
||||
authHash: 'auth-hash',
|
||||
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
deleted: false,
|
||||
updatedWithSession: null,
|
||||
dates: Dates.create(new Date(123), new Date(123)).getValue(),
|
||||
timestamps: Timestamps.create(123, 123).getValue(),
|
||||
},
|
||||
new UniqueEntityId('00000000-0000-0000-0000-000000000000'),
|
||||
).getValue()
|
||||
|
||||
const otherEntity = Item.create(
|
||||
{
|
||||
duplicateOf: null,
|
||||
itemsKeyId: 'items-key-id',
|
||||
content: 'content',
|
||||
contentType: ContentType.create(ContentType.TYPES.Note).getValue(),
|
||||
encItemKey: 'enc-item-key',
|
||||
authHash: 'auth-hash',
|
||||
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
deleted: false,
|
||||
updatedWithSession: null,
|
||||
dates: Dates.create(new Date(123), new Date(124)).getValue(),
|
||||
timestamps: Timestamps.create(123, 123).getValue(),
|
||||
},
|
||||
new UniqueEntityId('00000000-0000-0000-0000-000000000000'),
|
||||
).getValue()
|
||||
|
||||
expect(entity.isIdenticalTo(otherEntity)).toBeFalsy()
|
||||
})
|
||||
|
||||
it('should tell that an item is not identical to another item if their ids are different', () => {
|
||||
const entity = Item.create(
|
||||
{
|
||||
duplicateOf: null,
|
||||
itemsKeyId: 'items-key-id',
|
||||
content: 'content',
|
||||
contentType: ContentType.create(ContentType.TYPES.Note).getValue(),
|
||||
encItemKey: 'enc-item-key',
|
||||
authHash: 'auth-hash',
|
||||
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
deleted: false,
|
||||
updatedWithSession: null,
|
||||
dates: Dates.create(new Date(123), new Date(123)).getValue(),
|
||||
timestamps: Timestamps.create(123, 123).getValue(),
|
||||
},
|
||||
new UniqueEntityId('00000000-0000-0000-0000-000000000000'),
|
||||
).getValue()
|
||||
|
||||
const otherEntity = Item.create(
|
||||
{
|
||||
duplicateOf: null,
|
||||
itemsKeyId: 'items-key-id',
|
||||
content: 'content',
|
||||
contentType: ContentType.create(ContentType.TYPES.Note).getValue(),
|
||||
encItemKey: 'enc-item-key',
|
||||
authHash: 'auth-hash',
|
||||
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
deleted: false,
|
||||
updatedWithSession: null,
|
||||
dates: Dates.create(new Date(123), new Date(124)).getValue(),
|
||||
timestamps: Timestamps.create(123, 123).getValue(),
|
||||
},
|
||||
new UniqueEntityId('00000000-0000-0000-0000-000000000001'),
|
||||
).getValue()
|
||||
|
||||
expect(entity.isIdenticalTo(otherEntity)).toBeFalsy()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -108,4 +108,18 @@ export class Item extends Aggregate<ItemProps> {
|
||||
)
|
||||
this.props.keySystemAssociation = undefined
|
||||
}
|
||||
|
||||
isIdenticalTo(item: Item): boolean {
|
||||
if (this._id.toString() !== item._id.toString()) {
|
||||
return false
|
||||
}
|
||||
|
||||
const stringifiedThis = JSON.stringify(this.props)
|
||||
const stringifiedItem = JSON.stringify(item.props)
|
||||
|
||||
const base64This = Buffer.from(stringifiedThis).toString('base64')
|
||||
const base64Item = Buffer.from(stringifiedItem).toString('base64')
|
||||
|
||||
return base64This === base64Item
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import { ItemContentSizeDescriptor } from './ItemContentSizeDescriptor'
|
||||
|
||||
describe('ItemContentSizeDescriptor', () => {
|
||||
it('should create a value object', () => {
|
||||
const valueOrError = ItemContentSizeDescriptor.create('00000000-0000-0000-0000-000000000000', 20)
|
||||
|
||||
expect(valueOrError.isFailed()).toBeFalsy()
|
||||
})
|
||||
|
||||
it('should return error if shared vault uuid is not valid', () => {
|
||||
const valueOrError = ItemContentSizeDescriptor.create('invalid', 20)
|
||||
|
||||
expect(valueOrError.isFailed()).toBeTruthy()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,24 @@
|
||||
import { Result, Uuid, ValueObject } from '@standardnotes/domain-core'
|
||||
|
||||
import { ItemContentSizeDescriptorProps } from './ItemContentSizeDescriptorProps'
|
||||
|
||||
export class ItemContentSizeDescriptor extends ValueObject<ItemContentSizeDescriptorProps> {
|
||||
private constructor(props: ItemContentSizeDescriptorProps) {
|
||||
super(props)
|
||||
}
|
||||
|
||||
static create(itemUuidString: string, contentSize: number | null): Result<ItemContentSizeDescriptor> {
|
||||
const uuidOrError = Uuid.create(itemUuidString)
|
||||
if (uuidOrError.isFailed()) {
|
||||
return Result.fail<ItemContentSizeDescriptor>(uuidOrError.getError())
|
||||
}
|
||||
const uuid = uuidOrError.getValue()
|
||||
|
||||
return Result.ok<ItemContentSizeDescriptor>(
|
||||
new ItemContentSizeDescriptor({
|
||||
uuid,
|
||||
contentSize,
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { Uuid } from '@standardnotes/domain-core'
|
||||
|
||||
export interface ItemContentSizeDescriptorProps {
|
||||
uuid: Uuid
|
||||
contentSize: number | null
|
||||
}
|
||||
@@ -10,7 +10,6 @@ export type ItemQuery = {
|
||||
offset?: number
|
||||
limit?: number
|
||||
createdBetween?: Date[]
|
||||
selectString?: string
|
||||
includeSharedVaultUuids?: string[]
|
||||
exclusiveSharedVaultUuids?: string[]
|
||||
}
|
||||
|
||||
@@ -1,19 +1,15 @@
|
||||
import { Uuid } from '@standardnotes/domain-core'
|
||||
import { ReadStream } from 'fs'
|
||||
|
||||
import { Item } from './Item'
|
||||
import { ItemQuery } from './ItemQuery'
|
||||
import { ExtendedIntegrityPayload } from './ExtendedIntegrityPayload'
|
||||
import { ItemContentSizeDescriptor } from './ItemContentSizeDescriptor'
|
||||
|
||||
export interface ItemRepositoryInterface {
|
||||
deleteByUserUuid(userUuid: string): Promise<void>
|
||||
findAll(query: ItemQuery): Promise<Item[]>
|
||||
findAllRaw<T>(query: ItemQuery): Promise<T[]>
|
||||
streamAll(query: ItemQuery): Promise<ReadStream>
|
||||
countAll(query: ItemQuery): Promise<number>
|
||||
findContentSizeForComputingTransferLimit(
|
||||
query: ItemQuery,
|
||||
): Promise<Array<{ uuid: string; contentSize: number | null }>>
|
||||
findContentSizeForComputingTransferLimit(query: ItemQuery): Promise<Array<ItemContentSizeDescriptor>>
|
||||
findDatesForComputingIntegrityHash(userUuid: string): Promise<Array<{ updated_at_timestamp: number }>>
|
||||
findItemsForComputingIntegrityPayloads(userUuid: string): Promise<ExtendedIntegrityPayload[]>
|
||||
findByUuidAndUserUuid(uuid: string, userUuid: string): Promise<Item | null>
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import { RoleNameCollection } from '@standardnotes/domain-core'
|
||||
|
||||
import { ItemRepositoryInterface } from './ItemRepositoryInterface'
|
||||
|
||||
export interface ItemRepositoryResolverInterface {
|
||||
resolve(roleNames: RoleNameCollection): ItemRepositoryInterface
|
||||
}
|
||||
@@ -1,201 +1,143 @@
|
||||
import 'reflect-metadata'
|
||||
|
||||
import { Logger } from 'winston'
|
||||
import { ItemQuery } from './ItemQuery'
|
||||
|
||||
import { ItemRepositoryInterface } from './ItemRepositoryInterface'
|
||||
|
||||
import { ItemTransferCalculator } from './ItemTransferCalculator'
|
||||
import { ItemContentSizeDescriptor } from './ItemContentSizeDescriptor'
|
||||
|
||||
describe('ItemTransferCalculator', () => {
|
||||
let itemRepository: ItemRepositoryInterface
|
||||
let logger: Logger
|
||||
|
||||
const createCalculator = () => new ItemTransferCalculator(itemRepository, logger)
|
||||
const createCalculator = () => new ItemTransferCalculator(logger)
|
||||
|
||||
beforeEach(() => {
|
||||
itemRepository = {} as jest.Mocked<ItemRepositoryInterface>
|
||||
itemRepository.findContentSizeForComputingTransferLimit = jest.fn().mockReturnValue([])
|
||||
|
||||
logger = {} as jest.Mocked<Logger>
|
||||
logger.warn = jest.fn()
|
||||
})
|
||||
|
||||
describe('fetching uuids', () => {
|
||||
it('should compute uuids to fetch based on transfer limit - one item overlaping limit', async () => {
|
||||
const query = {} as jest.Mocked<ItemQuery>
|
||||
itemRepository.findContentSizeForComputingTransferLimit = jest.fn().mockReturnValue([
|
||||
{
|
||||
uuid: '1-2-3',
|
||||
contentSize: 20,
|
||||
},
|
||||
{
|
||||
uuid: '2-3-4',
|
||||
contentSize: 20,
|
||||
},
|
||||
{
|
||||
uuid: '3-4-5',
|
||||
contentSize: 20,
|
||||
},
|
||||
const itemContentSizeDescriptors = [
|
||||
ItemContentSizeDescriptor.create('00000000-0000-0000-0000-000000000000', 20).getValue(),
|
||||
ItemContentSizeDescriptor.create('00000000-0000-0000-0000-000000000001', 20).getValue(),
|
||||
ItemContentSizeDescriptor.create('00000000-0000-0000-0000-000000000002', 20).getValue(),
|
||||
]
|
||||
|
||||
const result = await createCalculator().computeItemUuidsToFetch(itemContentSizeDescriptors, 50)
|
||||
|
||||
expect(result).toEqual([
|
||||
'00000000-0000-0000-0000-000000000000',
|
||||
'00000000-0000-0000-0000-000000000001',
|
||||
'00000000-0000-0000-0000-000000000002',
|
||||
])
|
||||
|
||||
const result = await createCalculator().computeItemUuidsToFetch(query, 50)
|
||||
|
||||
expect(result).toEqual(['1-2-3', '2-3-4', '3-4-5'])
|
||||
})
|
||||
|
||||
it('should compute uuids to fetch based on transfer limit - exact limit fit', async () => {
|
||||
const query = {} as jest.Mocked<ItemQuery>
|
||||
itemRepository.findContentSizeForComputingTransferLimit = jest.fn().mockReturnValue([
|
||||
{
|
||||
uuid: '1-2-3',
|
||||
contentSize: 20,
|
||||
},
|
||||
{
|
||||
uuid: '2-3-4',
|
||||
contentSize: 20,
|
||||
},
|
||||
{
|
||||
uuid: '3-4-5',
|
||||
contentSize: 20,
|
||||
},
|
||||
])
|
||||
const itemContentSizeDescriptors = [
|
||||
ItemContentSizeDescriptor.create('00000000-0000-0000-0000-000000000000', 20).getValue(),
|
||||
ItemContentSizeDescriptor.create('00000000-0000-0000-0000-000000000001', 20).getValue(),
|
||||
ItemContentSizeDescriptor.create('00000000-0000-0000-0000-000000000002', 20).getValue(),
|
||||
]
|
||||
|
||||
const result = await createCalculator().computeItemUuidsToFetch(query, 40)
|
||||
const result = await createCalculator().computeItemUuidsToFetch(itemContentSizeDescriptors, 40)
|
||||
|
||||
expect(result).toEqual(['1-2-3', '2-3-4'])
|
||||
expect(result).toEqual(['00000000-0000-0000-0000-000000000000', '00000000-0000-0000-0000-000000000001'])
|
||||
})
|
||||
|
||||
it('should compute uuids to fetch based on transfer limit - content size not defined on an item', async () => {
|
||||
const query = {} as jest.Mocked<ItemQuery>
|
||||
itemRepository.findContentSizeForComputingTransferLimit = jest.fn().mockReturnValue([
|
||||
{
|
||||
uuid: '1-2-3',
|
||||
contentSize: 20,
|
||||
},
|
||||
{
|
||||
uuid: '2-3-4',
|
||||
contentSize: 20,
|
||||
},
|
||||
{
|
||||
uuid: '3-4-5',
|
||||
},
|
||||
const itemContentSizeDescriptors = [
|
||||
ItemContentSizeDescriptor.create('00000000-0000-0000-0000-000000000000', 20).getValue(),
|
||||
ItemContentSizeDescriptor.create('00000000-0000-0000-0000-000000000001', 20).getValue(),
|
||||
ItemContentSizeDescriptor.create('00000000-0000-0000-0000-000000000002', null).getValue(),
|
||||
]
|
||||
|
||||
const result = await createCalculator().computeItemUuidsToFetch(itemContentSizeDescriptors, 50)
|
||||
|
||||
expect(result).toEqual([
|
||||
'00000000-0000-0000-0000-000000000000',
|
||||
'00000000-0000-0000-0000-000000000001',
|
||||
'00000000-0000-0000-0000-000000000002',
|
||||
])
|
||||
|
||||
const result = await createCalculator().computeItemUuidsToFetch(query, 50)
|
||||
|
||||
expect(result).toEqual(['1-2-3', '2-3-4', '3-4-5'])
|
||||
})
|
||||
|
||||
it('should compute uuids to fetch based on transfer limit - first item over the limit', async () => {
|
||||
const query = {} as jest.Mocked<ItemQuery>
|
||||
itemRepository.findContentSizeForComputingTransferLimit = jest.fn().mockReturnValue([
|
||||
{
|
||||
uuid: '1-2-3',
|
||||
contentSize: 50,
|
||||
},
|
||||
{
|
||||
uuid: '2-3-4',
|
||||
contentSize: 20,
|
||||
},
|
||||
{
|
||||
uuid: '3-4-5',
|
||||
contentSize: 20,
|
||||
},
|
||||
])
|
||||
const itemContentSizeDescriptors = [
|
||||
ItemContentSizeDescriptor.create('00000000-0000-0000-0000-000000000000', 50).getValue(),
|
||||
ItemContentSizeDescriptor.create('00000000-0000-0000-0000-000000000001', 20).getValue(),
|
||||
ItemContentSizeDescriptor.create('00000000-0000-0000-0000-000000000002', 20).getValue(),
|
||||
]
|
||||
|
||||
const result = await createCalculator().computeItemUuidsToFetch(query, 40)
|
||||
const result = await createCalculator().computeItemUuidsToFetch(itemContentSizeDescriptors, 40)
|
||||
|
||||
expect(result).toEqual(['1-2-3', '2-3-4'])
|
||||
expect(result).toEqual(['00000000-0000-0000-0000-000000000000', '00000000-0000-0000-0000-000000000001'])
|
||||
})
|
||||
})
|
||||
|
||||
describe('fetching bundles', () => {
|
||||
it('should compute uuid bundles to fetch based on transfer limit - one item overlaping limit', async () => {
|
||||
const query = {} as jest.Mocked<ItemQuery>
|
||||
itemRepository.findContentSizeForComputingTransferLimit = jest.fn().mockReturnValue([
|
||||
{
|
||||
uuid: '1-2-3',
|
||||
contentSize: 20,
|
||||
},
|
||||
{
|
||||
uuid: '2-3-4',
|
||||
contentSize: 20,
|
||||
},
|
||||
{
|
||||
uuid: '3-4-5',
|
||||
contentSize: 20,
|
||||
},
|
||||
const itemContentSizeDescriptors = [
|
||||
ItemContentSizeDescriptor.create('00000000-0000-0000-0000-000000000000', 20).getValue(),
|
||||
ItemContentSizeDescriptor.create('00000000-0000-0000-0000-000000000001', 20).getValue(),
|
||||
ItemContentSizeDescriptor.create('00000000-0000-0000-0000-000000000002', 20).getValue(),
|
||||
]
|
||||
|
||||
const result = await createCalculator().computeItemUuidBundlesToFetch(itemContentSizeDescriptors, 50)
|
||||
|
||||
expect(result).toEqual([
|
||||
[
|
||||
'00000000-0000-0000-0000-000000000000',
|
||||
'00000000-0000-0000-0000-000000000001',
|
||||
'00000000-0000-0000-0000-000000000002',
|
||||
],
|
||||
])
|
||||
|
||||
const result = await createCalculator().computeItemUuidBundlesToFetch(query, 50)
|
||||
|
||||
expect(result).toEqual([['1-2-3', '2-3-4', '3-4-5']])
|
||||
})
|
||||
|
||||
it('should compute uuid bundles to fetch based on transfer limit - exact limit fit', async () => {
|
||||
const query = {} as jest.Mocked<ItemQuery>
|
||||
itemRepository.findContentSizeForComputingTransferLimit = jest.fn().mockReturnValue([
|
||||
{
|
||||
uuid: '1-2-3',
|
||||
contentSize: 20,
|
||||
},
|
||||
{
|
||||
uuid: '2-3-4',
|
||||
contentSize: 20,
|
||||
},
|
||||
{
|
||||
uuid: '3-4-5',
|
||||
contentSize: 20,
|
||||
},
|
||||
const itemContentSizeDescriptors = [
|
||||
ItemContentSizeDescriptor.create('00000000-0000-0000-0000-000000000000', 20).getValue(),
|
||||
ItemContentSizeDescriptor.create('00000000-0000-0000-0000-000000000001', 20).getValue(),
|
||||
ItemContentSizeDescriptor.create('00000000-0000-0000-0000-000000000002', 20).getValue(),
|
||||
]
|
||||
|
||||
const result = await createCalculator().computeItemUuidBundlesToFetch(itemContentSizeDescriptors, 40)
|
||||
|
||||
expect(result).toEqual([
|
||||
['00000000-0000-0000-0000-000000000000', '00000000-0000-0000-0000-000000000001'],
|
||||
['00000000-0000-0000-0000-000000000002'],
|
||||
])
|
||||
|
||||
const result = await createCalculator().computeItemUuidBundlesToFetch(query, 40)
|
||||
|
||||
expect(result).toEqual([['1-2-3', '2-3-4'], ['3-4-5']])
|
||||
})
|
||||
|
||||
it('should compute uuid bundles to fetch based on transfer limit - content size not defined on an item', async () => {
|
||||
const query = {} as jest.Mocked<ItemQuery>
|
||||
itemRepository.findContentSizeForComputingTransferLimit = jest.fn().mockReturnValue([
|
||||
{
|
||||
uuid: '1-2-3',
|
||||
contentSize: 20,
|
||||
},
|
||||
{
|
||||
uuid: '2-3-4',
|
||||
contentSize: 20,
|
||||
},
|
||||
{
|
||||
uuid: '3-4-5',
|
||||
},
|
||||
const itemContentSizeDescriptors = [
|
||||
ItemContentSizeDescriptor.create('00000000-0000-0000-0000-000000000000', 20).getValue(),
|
||||
ItemContentSizeDescriptor.create('00000000-0000-0000-0000-000000000001', 20).getValue(),
|
||||
ItemContentSizeDescriptor.create('00000000-0000-0000-0000-000000000002', null).getValue(),
|
||||
]
|
||||
|
||||
const result = await createCalculator().computeItemUuidBundlesToFetch(itemContentSizeDescriptors, 50)
|
||||
|
||||
expect(result).toEqual([
|
||||
[
|
||||
'00000000-0000-0000-0000-000000000000',
|
||||
'00000000-0000-0000-0000-000000000001',
|
||||
'00000000-0000-0000-0000-000000000002',
|
||||
],
|
||||
])
|
||||
|
||||
const result = await createCalculator().computeItemUuidBundlesToFetch(query, 50)
|
||||
|
||||
expect(result).toEqual([['1-2-3', '2-3-4', '3-4-5']])
|
||||
})
|
||||
|
||||
it('should compute uuid bundles to fetch based on transfer limit - first item over the limit', async () => {
|
||||
const query = {} as jest.Mocked<ItemQuery>
|
||||
itemRepository.findContentSizeForComputingTransferLimit = jest.fn().mockReturnValue([
|
||||
{
|
||||
uuid: '1-2-3',
|
||||
contentSize: 50,
|
||||
},
|
||||
{
|
||||
uuid: '2-3-4',
|
||||
contentSize: 20,
|
||||
},
|
||||
{
|
||||
uuid: '3-4-5',
|
||||
contentSize: 20,
|
||||
},
|
||||
const itemContentSizeDescriptors = [
|
||||
ItemContentSizeDescriptor.create('00000000-0000-0000-0000-000000000000', 50).getValue(),
|
||||
ItemContentSizeDescriptor.create('00000000-0000-0000-0000-000000000001', 20).getValue(),
|
||||
ItemContentSizeDescriptor.create('00000000-0000-0000-0000-000000000002', 20).getValue(),
|
||||
]
|
||||
|
||||
const result = await createCalculator().computeItemUuidBundlesToFetch(itemContentSizeDescriptors, 40)
|
||||
|
||||
expect(result).toEqual([
|
||||
['00000000-0000-0000-0000-000000000000', '00000000-0000-0000-0000-000000000001'],
|
||||
['00000000-0000-0000-0000-000000000002'],
|
||||
])
|
||||
|
||||
const result = await createCalculator().computeItemUuidBundlesToFetch(query, 40)
|
||||
|
||||
expect(result).toEqual([['1-2-3', '2-3-4'], ['3-4-5']])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,27 +1,28 @@
|
||||
import { Logger } from 'winston'
|
||||
|
||||
import { ItemTransferCalculatorInterface } from './ItemTransferCalculatorInterface'
|
||||
import { ItemQuery } from './ItemQuery'
|
||||
import { ItemRepositoryInterface } from './ItemRepositoryInterface'
|
||||
import { ItemContentSizeDescriptor } from './ItemContentSizeDescriptor'
|
||||
|
||||
export class ItemTransferCalculator implements ItemTransferCalculatorInterface {
|
||||
constructor(private itemRepository: ItemRepositoryInterface, private logger: Logger) {}
|
||||
constructor(private logger: Logger) {}
|
||||
|
||||
async computeItemUuidsToFetch(itemQuery: ItemQuery, bytesTransferLimit: number): Promise<Array<string>> {
|
||||
async computeItemUuidsToFetch(
|
||||
itemContentSizeDescriptors: ItemContentSizeDescriptor[],
|
||||
bytesTransferLimit: number,
|
||||
): Promise<Array<string>> {
|
||||
const itemUuidsToFetch = []
|
||||
const itemContentSizes = await this.itemRepository.findContentSizeForComputingTransferLimit(itemQuery)
|
||||
let totalContentSizeInBytes = 0
|
||||
for (const itemContentSize of itemContentSizes) {
|
||||
const contentSize = itemContentSize.contentSize ?? 0
|
||||
for (const itemContentSize of itemContentSizeDescriptors) {
|
||||
const contentSize = itemContentSize.props.contentSize ?? 0
|
||||
|
||||
itemUuidsToFetch.push(itemContentSize.uuid)
|
||||
itemUuidsToFetch.push(itemContentSize.props.uuid.value)
|
||||
totalContentSizeInBytes += contentSize
|
||||
|
||||
const transferLimitBreached = this.isTransferLimitBreached({
|
||||
totalContentSizeInBytes,
|
||||
bytesTransferLimit,
|
||||
itemUuidsToFetch,
|
||||
itemContentSizes,
|
||||
itemContentSizeDescriptors,
|
||||
})
|
||||
|
||||
if (transferLimitBreached) {
|
||||
@@ -32,22 +33,24 @@ export class ItemTransferCalculator implements ItemTransferCalculatorInterface {
|
||||
return itemUuidsToFetch
|
||||
}
|
||||
|
||||
async computeItemUuidBundlesToFetch(itemQuery: ItemQuery, bytesTransferLimit: number): Promise<Array<Array<string>>> {
|
||||
async computeItemUuidBundlesToFetch(
|
||||
itemContentSizeDescriptors: ItemContentSizeDescriptor[],
|
||||
bytesTransferLimit: number,
|
||||
): Promise<Array<Array<string>>> {
|
||||
let itemUuidsToFetch = []
|
||||
const itemContentSizes = await this.itemRepository.findContentSizeForComputingTransferLimit(itemQuery)
|
||||
let totalContentSizeInBytes = 0
|
||||
const bundles = []
|
||||
for (const itemContentSize of itemContentSizes) {
|
||||
const contentSize = itemContentSize.contentSize ?? 0
|
||||
for (const itemContentSize of itemContentSizeDescriptors) {
|
||||
const contentSize = itemContentSize.props.contentSize ?? 0
|
||||
|
||||
itemUuidsToFetch.push(itemContentSize.uuid)
|
||||
itemUuidsToFetch.push(itemContentSize.props.uuid.value)
|
||||
totalContentSizeInBytes += contentSize
|
||||
|
||||
const transferLimitBreached = this.isTransferLimitBreached({
|
||||
totalContentSizeInBytes,
|
||||
bytesTransferLimit,
|
||||
itemUuidsToFetch,
|
||||
itemContentSizes,
|
||||
itemContentSizeDescriptors,
|
||||
})
|
||||
|
||||
if (transferLimitBreached) {
|
||||
@@ -68,11 +71,11 @@ export class ItemTransferCalculator implements ItemTransferCalculatorInterface {
|
||||
totalContentSizeInBytes: number
|
||||
bytesTransferLimit: number
|
||||
itemUuidsToFetch: Array<string>
|
||||
itemContentSizes: Array<{ uuid: string; contentSize: number | null }>
|
||||
itemContentSizeDescriptors: ItemContentSizeDescriptor[]
|
||||
}): boolean {
|
||||
const transferLimitBreached = dto.totalContentSizeInBytes >= dto.bytesTransferLimit
|
||||
const transferLimitBreachedAtFirstItem =
|
||||
transferLimitBreached && dto.itemUuidsToFetch.length === 1 && dto.itemContentSizes.length > 1
|
||||
transferLimitBreached && dto.itemUuidsToFetch.length === 1 && dto.itemContentSizeDescriptors.length > 1
|
||||
|
||||
if (transferLimitBreachedAtFirstItem) {
|
||||
this.logger.warn(
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
import { ItemQuery } from './ItemQuery'
|
||||
import { ItemContentSizeDescriptor } from './ItemContentSizeDescriptor'
|
||||
|
||||
export interface ItemTransferCalculatorInterface {
|
||||
computeItemUuidsToFetch(itemQuery: ItemQuery, bytesTransferLimit: number): Promise<Array<string>>
|
||||
computeItemUuidBundlesToFetch(itemQuery: ItemQuery, bytesTransferLimit: number): Promise<Array<Array<string>>>
|
||||
computeItemUuidsToFetch(
|
||||
itemContentSizeDescriptors: ItemContentSizeDescriptor[],
|
||||
bytesTransferLimit: number,
|
||||
): Promise<Array<string>>
|
||||
computeItemUuidBundlesToFetch(
|
||||
itemContentSizeDescriptors: ItemContentSizeDescriptor[],
|
||||
bytesTransferLimit: number,
|
||||
): Promise<Array<Array<string>>>
|
||||
}
|
||||
|
||||
@@ -40,10 +40,8 @@ describe('SharedVaultFilter', () => {
|
||||
dates: Dates.create(new Date(1616164633241311), new Date(1616164633241311)).getValue(),
|
||||
timestamps: Timestamps.create(1616164633241311, 1616164633241311).getValue(),
|
||||
sharedVaultAssociation: SharedVaultAssociation.create({
|
||||
itemUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
lastEditedBy: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
sharedVaultUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
timestamps: Timestamps.create(123, 123).getValue(),
|
||||
}).getValue(),
|
||||
},
|
||||
new UniqueEntityId('00000000-0000-0000-0000-000000000000'),
|
||||
@@ -254,10 +252,8 @@ describe('SharedVaultFilter', () => {
|
||||
dates: Dates.create(new Date(1616164633241311), new Date(1616164633241311)).getValue(),
|
||||
timestamps: Timestamps.create(1616164633241311, 1616164633241311).getValue(),
|
||||
sharedVaultAssociation: SharedVaultAssociation.create({
|
||||
itemUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
lastEditedBy: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
sharedVaultUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
timestamps: Timestamps.create(123, 123).getValue(),
|
||||
}).getValue(),
|
||||
},
|
||||
new UniqueEntityId('00000000-0000-0000-0000-000000000000'),
|
||||
@@ -427,10 +423,8 @@ describe('SharedVaultFilter', () => {
|
||||
dates: Dates.create(new Date(1616164633241311), new Date(1616164633241311)).getValue(),
|
||||
timestamps: Timestamps.create(1616164633241311, 1616164633241311).getValue(),
|
||||
sharedVaultAssociation: SharedVaultAssociation.create({
|
||||
itemUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
lastEditedBy: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
sharedVaultUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
timestamps: Timestamps.create(123, 123).getValue(),
|
||||
}).getValue(),
|
||||
},
|
||||
new UniqueEntityId('00000000-0000-0000-0000-000000000000'),
|
||||
@@ -589,10 +583,8 @@ describe('SharedVaultFilter', () => {
|
||||
dates: Dates.create(new Date(1616164633241311), new Date(1616164633241311)).getValue(),
|
||||
timestamps: Timestamps.create(1616164633241311, 1616164633241311).getValue(),
|
||||
sharedVaultAssociation: SharedVaultAssociation.create({
|
||||
itemUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
lastEditedBy: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
sharedVaultUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
timestamps: Timestamps.create(123, 123).getValue(),
|
||||
}).getValue(),
|
||||
},
|
||||
new UniqueEntityId('00000000-0000-0000-0000-000000000000'),
|
||||
|
||||
@@ -26,10 +26,8 @@ describe('SharedVaultSnjsFilter', () => {
|
||||
dates: Dates.create(new Date(1616164633241311), new Date(1616164633241311)).getValue(),
|
||||
timestamps: Timestamps.create(1616164633241311, 1616164633241311).getValue(),
|
||||
sharedVaultAssociation: SharedVaultAssociation.create({
|
||||
itemUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
lastEditedBy: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
sharedVaultUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
timestamps: Timestamps.create(123, 123).getValue(),
|
||||
}).getValue(),
|
||||
},
|
||||
new UniqueEntityId('00000000-0000-0000-0000-000000000000'),
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
import { Timestamps, Uuid } from '@standardnotes/domain-core'
|
||||
|
||||
export interface KeySystemAssociationProps {
|
||||
itemUuid: Uuid
|
||||
keySystemIdentifier: string
|
||||
timestamps: Timestamps
|
||||
}
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
import { Timestamps, Uuid } from '@standardnotes/domain-core'
|
||||
|
||||
import { KeySystemAssociation } from './KeySystemAssociation'
|
||||
|
||||
describe('KeySystemAssociation', () => {
|
||||
it('should create an entity', () => {
|
||||
const entityOrError = KeySystemAssociation.create({
|
||||
timestamps: Timestamps.create(123456789, 123456789).getValue(),
|
||||
itemUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
keySystemIdentifier: '00000000-0000-0000-0000-000000000000',
|
||||
})
|
||||
|
||||
|
||||
-9
@@ -1,9 +0,0 @@
|
||||
import { Uuid } from '@standardnotes/domain-core'
|
||||
|
||||
import { KeySystemAssociation } from './KeySystemAssociation'
|
||||
|
||||
export interface KeySystemAssociationRepositoryInterface {
|
||||
save(keySystem: KeySystemAssociation): Promise<void>
|
||||
remove(keySystem: KeySystemAssociation): Promise<void>
|
||||
findByItemUuid(itemUuid: Uuid): Promise<KeySystemAssociation | null>
|
||||
}
|
||||
@@ -1,12 +1,10 @@
|
||||
import { Timestamps, Uuid } from '@standardnotes/domain-core'
|
||||
import { Uuid } from '@standardnotes/domain-core'
|
||||
|
||||
import { SharedVaultAssociation } from './SharedVaultAssociation'
|
||||
|
||||
describe('SharedVaultAssociation', () => {
|
||||
it('should create an entity', () => {
|
||||
const entityOrError = SharedVaultAssociation.create({
|
||||
timestamps: Timestamps.create(123456789, 123456789).getValue(),
|
||||
itemUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
lastEditedBy: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
sharedVaultUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
})
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { Timestamps, Uuid } from '@standardnotes/domain-core'
|
||||
import { Uuid } from '@standardnotes/domain-core'
|
||||
|
||||
export interface SharedVaultAssociationProps {
|
||||
lastEditedBy: Uuid
|
||||
sharedVaultUuid: Uuid
|
||||
itemUuid: Uuid
|
||||
timestamps: Timestamps
|
||||
}
|
||||
|
||||
-9
@@ -1,9 +0,0 @@
|
||||
import { Uuid } from '@standardnotes/domain-core'
|
||||
|
||||
import { SharedVaultAssociation } from './SharedVaultAssociation'
|
||||
|
||||
export interface SharedVaultAssociationRepositoryInterface {
|
||||
save(sharedVaultAssociation: SharedVaultAssociation): Promise<void>
|
||||
remove(sharedVaultAssociation: SharedVaultAssociation): Promise<void>
|
||||
findByItemUuid(itemUuid: Uuid): Promise<SharedVaultAssociation | null>
|
||||
}
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
import {
|
||||
SharedVaultUserPermission,
|
||||
Uuid,
|
||||
Timestamps,
|
||||
Result,
|
||||
NotificationPayload,
|
||||
NotificationType,
|
||||
} from '@standardnotes/domain-core'
|
||||
import { SharedVaultUser } from '../../../SharedVault/User/SharedVaultUser'
|
||||
import { SharedVaultUserRepositoryInterface } from '../../../SharedVault/User/SharedVaultUserRepositoryInterface'
|
||||
import { AddNotificationForUser } from '../AddNotificationForUser/AddNotificationForUser'
|
||||
import { AddNotificationsForUsers } from './AddNotificationsForUsers'
|
||||
|
||||
describe('AddNotificationsForUsers', () => {
|
||||
let sharedVaultUserRepository: SharedVaultUserRepositoryInterface
|
||||
let addNotificationForUser: AddNotificationForUser
|
||||
let sharedVaultUser: SharedVaultUser
|
||||
let payload: NotificationPayload
|
||||
|
||||
const createUseCase = () => new AddNotificationsForUsers(sharedVaultUserRepository, addNotificationForUser)
|
||||
|
||||
beforeEach(() => {
|
||||
sharedVaultUser = SharedVaultUser.create({
|
||||
permission: SharedVaultUserPermission.create(SharedVaultUserPermission.PERMISSIONS.Read).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(),
|
||||
}).getValue()
|
||||
|
||||
sharedVaultUserRepository = {} as jest.Mocked<SharedVaultUserRepositoryInterface>
|
||||
sharedVaultUserRepository.findBySharedVaultUuid = jest.fn().mockResolvedValue([sharedVaultUser])
|
||||
|
||||
addNotificationForUser = {} as jest.Mocked<AddNotificationForUser>
|
||||
addNotificationForUser.execute = jest.fn().mockResolvedValue(Result.ok())
|
||||
|
||||
payload = NotificationPayload.create({
|
||||
sharedVaultUuid: Uuid.create('0e8c3c7e-3f1a-4f7a-9b5a-5b2b0a7d4b1e').getValue(),
|
||||
type: NotificationType.create(NotificationType.TYPES.SharedVaultFileUploaded).getValue(),
|
||||
version: '1.0',
|
||||
}).getValue()
|
||||
})
|
||||
|
||||
it('should add notifications for all users in a shared vault', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
|
||||
type: 'test',
|
||||
payload,
|
||||
version: '1.0',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBeFalsy()
|
||||
expect(addNotificationForUser.execute).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('should return error if shared vault uuid is invalid', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
sharedVaultUuid: 'invalid',
|
||||
type: 'test',
|
||||
payload,
|
||||
version: '1.0',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBeTruthy()
|
||||
})
|
||||
|
||||
it('should return error if adding notification fails', async () => {
|
||||
const useCase = createUseCase()
|
||||
addNotificationForUser.execute = jest.fn().mockResolvedValue(Result.fail('test'))
|
||||
|
||||
const result = await useCase.execute({
|
||||
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
|
||||
type: 'test',
|
||||
payload,
|
||||
version: '1.0',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBeTruthy()
|
||||
})
|
||||
})
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
import { Result, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
|
||||
|
||||
import { AddNotificationsForUsersDTO } from './AddNotificationsForUsersDTO'
|
||||
import { SharedVaultUserRepositoryInterface } from '../../../SharedVault/User/SharedVaultUserRepositoryInterface'
|
||||
import { AddNotificationForUser } from '../AddNotificationForUser/AddNotificationForUser'
|
||||
|
||||
export class AddNotificationsForUsers implements UseCaseInterface<void> {
|
||||
constructor(
|
||||
private sharedVaultUserRepository: SharedVaultUserRepositoryInterface,
|
||||
private addNotificationForUser: AddNotificationForUser,
|
||||
) {}
|
||||
|
||||
async execute(dto: AddNotificationsForUsersDTO): Promise<Result<void>> {
|
||||
const sharedVaultUuidOrError = Uuid.create(dto.sharedVaultUuid)
|
||||
if (sharedVaultUuidOrError.isFailed()) {
|
||||
return Result.fail(sharedVaultUuidOrError.getError())
|
||||
}
|
||||
const sharedVaultUuid = sharedVaultUuidOrError.getValue()
|
||||
|
||||
const sharedVaultUsers = await this.sharedVaultUserRepository.findBySharedVaultUuid(sharedVaultUuid)
|
||||
for (const sharedVaultUser of sharedVaultUsers) {
|
||||
const result = await this.addNotificationForUser.execute({
|
||||
userUuid: sharedVaultUser.props.userUuid.value,
|
||||
type: dto.type,
|
||||
payload: dto.payload,
|
||||
version: dto.version,
|
||||
})
|
||||
if (result.isFailed()) {
|
||||
return Result.fail(result.getError())
|
||||
}
|
||||
}
|
||||
|
||||
return Result.ok()
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
import { NotificationPayload } from '@standardnotes/domain-core'
|
||||
|
||||
export interface AddNotificationsForUsersDTO {
|
||||
sharedVaultUuid: string
|
||||
version: string
|
||||
type: string
|
||||
payload: NotificationPayload
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user