Compare commits

..

62 Commits

Author SHA1 Message Date
standardci
e98393452b chore(release): publish new version
- @standardnotes/analytics@2.25.14
 - @standardnotes/api-gateway@1.70.5
 - @standardnotes/auth-server@1.132.0
 - @standardnotes/domain-core@1.26.0
 - @standardnotes/event-store@1.11.20
 - @standardnotes/files-server@1.20.4
 - @standardnotes/home-server@1.14.0
 - @standardnotes/revisions-server@1.26.8
 - @standardnotes/scheduler-server@1.20.23
 - @standardnotes/settings@1.21.25
 - @standardnotes/syncing-server@1.80.0
 - @standardnotes/websockets-server@1.10.18
2023-08-18 15:15:20 +00:00
Karol Sójko
302b624504 feat: add mechanism for determining if a user should use the primary or secondary items database (#700)
* feat(domain-core): introduce new role for users transitioning to new mechanisms

* feat: add mechanism for determining if a user should use the primary or secondary items database

* fix: add transition mode enabled switch in docker entrypoint

* fix(syncing-server): mapping roles from middleware

* fix: mongodb item repository binding

* fix: item backups service binding

* fix: passing transition mode enabled variable to docker setup
2023-08-18 16:45:10 +02:00
Karol Sójko
e00d9d2ca0 fix: e2e parameter for running vault tests 2023-08-18 11:11:54 +02:00
Karol Sójko
9ab4601c8d feat: add transition mode switch to e2e test suite 2023-08-18 11:00:36 +02:00
Karol Sójko
19e43bdb1a fix: run vault tests based on secondary db usage (#699) 2023-08-17 13:21:50 +02:00
standardci
49832e7944 chore(release): publish new version
- @standardnotes/home-server@1.13.51
 - @standardnotes/syncing-server@1.79.1
2023-08-17 10:15:43 +00:00
Karol Sójko
916e98936a fix(home-server): add default env values for secondary database 2023-08-17 11:56:56 +02:00
Karol Sójko
31d1eef7f7 fix(syncing-server): refactor shared vault and key system associations (#698)
* feat(syncing-server): refactor persistence of shared vault and key system associations

* fix(syncing-server): refactor shared vault and key system associations
2023-08-17 11:56:16 +02:00
standardci
2648d9a813 chore(release): publish new version
- @standardnotes/home-server@1.13.50
 - @standardnotes/syncing-server@1.79.0
2023-08-16 11:16:38 +00:00
Karol Sójko
b24b576209 feat: add mongodb initial support (#696)
* feat: add mongodb initial support

* fix: typeorm annotations for mongodb entity

* wip mongo repo

* feat: add mongodb queries

* fix(syncing-server): env sample

* fix(syncing-server): Mongo connection auth source

* fix(syncing-server): db switch env var name

* fix(syncing-server): persisting and querying by _id as UUID in MongoDB

* fix(syncing-server): items upserts on MongoDB

* fix: remove foreign key migration
2023-08-16 13:00:16 +02:00
Karol Sójko
faee38bffd fix: hosts for home-server e2e ci setup 2023-08-15 13:17:20 +02:00
Karol Sójko
65f3503fe8 fix: docker compose ci setup 2023-08-15 13:11:14 +02:00
Karol Sójko
054023b791 fix: host variables 2023-08-15 12:59:13 +02:00
Karol Sójko
383c3a68fa fix: default value for SECONDARY_DB_ENABLED 2023-08-15 12:56:55 +02:00
Karol Sójko
7d22b1c15c feat: run mongo db secondary database in e2e 2023-08-15 12:50:38 +02:00
standardci
c71e7cd926 chore(release): publish new version
- @standardnotes/auth-server@1.131.5
 - @standardnotes/home-server@1.13.49
2023-08-15 10:34:11 +00:00
Karol Sójko
83ad069c5d fix(auth): passing the invalidate cache header (#697) 2023-08-15 12:16:01 +02:00
standardci
081108d9ba chore(release): publish new version
- @standardnotes/home-server@1.13.48
 - @standardnotes/syncing-server@1.78.11
2023-08-11 11:52:27 +00:00
Karol Sójko
8f3df56a2b chore: fix revisions frequency 2023-08-11 13:22:11 +02:00
Karol Sójko
d02124f4e5 Revert "tmp: disable shared vaults"
This reverts commit c49dc35ab5.
2023-08-11 12:28:57 +02:00
Karol Sójko
09e351fedb Revert "tmp: ci"
This reverts commit 06cedd11d8.
2023-08-11 12:27:12 +02:00
Karol Sójko
ad4b85b095 Revert "tmp: disable decorating with associations on revisions"
This reverts commit ac3646836c.
2023-08-11 12:26:44 +02:00
Karol Sójko
0bf7d8beae Revert "tmp: disable decorating items completely"
This reverts commit bc1c7a8ae1.
2023-08-11 12:25:35 +02:00
standardci
1ae7cca394 chore(release): publish new version
- @standardnotes/home-server@1.13.47
 - @standardnotes/syncing-server@1.78.10
2023-08-11 09:00:00 +00:00
Karol Sójko
bc1c7a8ae1 tmp: disable decorating items completely 2023-08-11 10:54:12 +02:00
standardci
c22c5e4584 chore(release): publish new version
- @standardnotes/home-server@1.13.46
 - @standardnotes/syncing-server@1.78.9
2023-08-11 08:46:28 +00:00
Karol Sójko
ac3646836c tmp: disable decorating with associations on revisions 2023-08-11 10:40:03 +02:00
standardci
7a31ab75d6 chore(release): publish new version
- @standardnotes/home-server@1.13.45
 - @standardnotes/syncing-server@1.78.8
2023-08-11 08:23:28 +00:00
Karol Sójko
c49dc35ab5 tmp: disable shared vaults 2023-08-11 10:15:55 +02:00
Karol Sójko
06cedd11d8 tmp: ci 2023-08-11 10:15:55 +02:00
standardci
f496376fb3 chore(release): publish new version
- @standardnotes/scheduler-server@1.20.22
2023-08-11 08:14:41 +00:00
Karol Sójko
091e2a57e8 fix(scheduler): adjust email backups encouraging email schedule (#695) 2023-08-11 09:35:51 +02:00
standardci
0d40ef6796 chore(release): publish new version
- @standardnotes/analytics@2.25.13
 - @standardnotes/auth-server@1.131.4
 - @standardnotes/common@1.50.1
 - @standardnotes/home-server@1.13.44
 - @standardnotes/revisions-server@1.26.7
 - @standardnotes/syncing-server@1.78.7
 - @standardnotes/websockets-server@1.10.17
2023-08-11 07:35:15 +00:00
Mo
1be33ba4c3 refactor: remove unused functions (#694)
* refactor: remove unused functions

* refactor: remove unused functions
2023-08-11 08:58:39 +02:00
Mo
aaeb311928 chore: reduce ci revisions timeout 2023-08-10 13:09:49 -05:00
standardci
a7a38c07ac chore(release): publish new version
- @standardnotes/home-server@1.13.43
 - @standardnotes/syncing-server@1.78.6
2023-08-10 11:37:24 +00:00
Karol Sójko
56f49752b4 fix(syncing-server): setting user uuid in notifications 2023-08-10 13:04:51 +02:00
Mo
892d8b6fe2 chore: update email template 2023-08-10 05:49:30 -05:00
standardci
cec2005436 chore(release): publish new version
- @standardnotes/analytics@2.25.12
 - @standardnotes/api-gateway@1.70.4
 - @standardnotes/auth-server@1.131.3
 - @standardnotes/domain-core@1.25.2
 - @standardnotes/event-store@1.11.19
 - @standardnotes/files-server@1.20.3
 - @standardnotes/home-server@1.13.42
 - @standardnotes/revisions-server@1.26.6
 - @standardnotes/scheduler-server@1.20.21
 - @standardnotes/settings@1.21.24
 - @standardnotes/syncing-server@1.78.5
 - @standardnotes/websockets-server@1.10.16
2023-08-09 16:31:35 +00:00
Karol Sójko
0eb86c0096 Revert "tmp: disable fetching shared vault items"
This reverts commit 18eddea6f8.
2023-08-09 18:01:16 +02:00
Karol Sójko
b8e39d76c1 Revert "tmp: skip ci"
This reverts commit f8c9e67063.
2023-08-09 18:01:09 +02:00
Karol Sójko
1c3ff526b7 Revert "Revert "feat(syncing-server): notify shared vault users upon file uploads or removals (#692)""
This reverts commit d261c81cd0.
2023-08-09 18:00:49 +02:00
standardci
373767248c chore(release): publish new version
- @standardnotes/home-server@1.13.41
 - @standardnotes/syncing-server@1.78.4
2023-08-09 15:47:05 +00:00
Karol Sójko
d7965b2748 fix(syncing-server): casting handlers 2023-08-09 17:40:48 +02:00
Karol Sójko
cbcd2ec87a Revert "Revert "fix(syncing-server): update storage quota used in a shared vault (#691)""
This reverts commit 66f9352a06.
2023-08-09 17:36:59 +02:00
standardci
c74d37fc48 chore(release): publish new version
- @standardnotes/home-server@1.13.40
 - @standardnotes/syncing-server@1.78.3
2023-08-09 15:29:30 +00:00
Karol Sójko
66f9352a06 Revert "fix(syncing-server): update storage quota used in a shared vault (#691)"
This reverts commit 3415cae093.
2023-08-09 17:21:59 +02:00
standardci
e5eef3aba0 chore(release): publish new version
- @standardnotes/analytics@2.25.11
 - @standardnotes/api-gateway@1.70.3
 - @standardnotes/auth-server@1.131.2
 - @standardnotes/domain-core@1.25.1
 - @standardnotes/event-store@1.11.18
 - @standardnotes/files-server@1.20.2
 - @standardnotes/home-server@1.13.39
 - @standardnotes/revisions-server@1.26.5
 - @standardnotes/scheduler-server@1.20.20
 - @standardnotes/settings@1.21.23
 - @standardnotes/syncing-server@1.78.2
 - @standardnotes/websockets-server@1.10.15
2023-08-09 14:51:38 +00:00
Karol Sójko
d261c81cd0 Revert "feat(syncing-server): notify shared vault users upon file uploads or removals (#692)"
This reverts commit 46867c1a4d.
2023-08-09 16:43:33 +02:00
standardci
634e3bbb67 chore(release): publish new version
- @standardnotes/home-server@1.13.38
 - @standardnotes/syncing-server@1.78.1
2023-08-09 14:41:32 +00:00
Karol Sójko
f8c9e67063 tmp: skip ci 2023-08-09 16:33:59 +02:00
Karol Sójko
18eddea6f8 tmp: disable fetching shared vault items 2023-08-09 16:17:53 +02:00
standardci
c6d655c5f5 chore(release): publish new version
- @standardnotes/analytics@2.25.10
 - @standardnotes/api-gateway@1.70.2
 - @standardnotes/auth-server@1.131.1
 - @standardnotes/domain-core@1.25.0
 - @standardnotes/event-store@1.11.17
 - @standardnotes/files-server@1.20.1
 - @standardnotes/home-server@1.13.37
 - @standardnotes/revisions-server@1.26.4
 - @standardnotes/scheduler-server@1.20.19
 - @standardnotes/settings@1.21.22
 - @standardnotes/syncing-server@1.78.0
 - @standardnotes/websockets-server@1.10.14
2023-08-09 13:46:50 +00:00
Karol Sójko
46867c1a4d feat(syncing-server): notify shared vault users upon file uploads or removals (#692) 2023-08-09 15:08:17 +02:00
standardci
d29903bab6 chore(release): publish new version
- @standardnotes/home-server@1.13.36
 - @standardnotes/syncing-server@1.77.2
2023-08-09 08:37:21 +00:00
Karol Sójko
3415cae093 fix(syncing-server): update storage quota used in a shared vault (#691) 2023-08-09 10:05:48 +02:00
standardci
408fd5a0c6 chore(release): publish new version
- @standardnotes/home-server@1.13.35
 - @standardnotes/syncing-server@1.77.1
2023-08-08 13:05:40 +00:00
Karol Sójko
0a16ee64fe fix(syncing-server): inviting already existing members to shared vault (#690)
* fix(syncing-server): inviting already existing members to shared vault

* fix(syncing-server): finding method for existing members
2023-08-08 14:31:23 +02:00
standardci
22b00479b4 chore(release): publish new version
- @standardnotes/analytics@2.25.9
 - @standardnotes/api-gateway@1.70.1
 - @standardnotes/auth-server@1.131.0
 - @standardnotes/domain-events-infra@1.12.11
 - @standardnotes/domain-events@2.115.0
 - @standardnotes/event-store@1.11.16
 - @standardnotes/files-server@1.20.0
 - @standardnotes/home-server@1.13.34
 - @standardnotes/revisions-server@1.26.3
 - @standardnotes/scheduler-server@1.20.18
 - @standardnotes/security@1.9.0
 - @standardnotes/syncing-server@1.77.0
 - @standardnotes/websockets-server@1.10.13
2023-08-08 12:06:10 +00:00
Karol Sójko
5311e74266 feat: update storage quota used for user based on shared vault files (#689)
* feat: update storage quota used for user based on shared vault files

* fix: use case binding

* fix: increase file upload bytes limit for shared vaults
2023-08-08 13:36:35 +02:00
standardci
5be7db7788 chore(release): publish new version
- @standardnotes/home-server@1.13.33
 - @standardnotes/syncing-server@1.76.1
2023-08-08 09:29:54 +00:00
Karol Sójko
3bd1547ce3 fix(syncing-server): race condition when adding admin user to newly created shared vault (#688) 2023-08-08 11:02:10 +02:00
178 changed files with 3395 additions and 1496 deletions

8
.github/ci.env vendored
View File

@@ -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

View File

@@ -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() }}

230
.pnp.cjs generated
View File

@@ -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.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -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

View File

@@ -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"
#########

View File

@@ -19,7 +19,8 @@
"publish": "lerna publish from-git --yes --no-verify-access --loglevel verbose",
"postversion": "./scripts/push-tags-one-by-one.sh",
"upgrade:snjs": "yarn workspaces foreach --verbose run upgrade:snjs",
"e2e": "yarn build packages/home-server && PORT=3123 yarn workspace @standardnotes/home-server start"
"e2e": "yarn build packages/home-server && PORT=3123 yarn workspace @standardnotes/home-server start",
"start": "yarn build packages/home-server && yarn workspace @standardnotes/home-server start"
},
"devDependencies": {
"@commitlint/cli": "^17.0.2",

View File

@@ -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.
## [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
## [2.25.8](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.25.7...@standardnotes/analytics@2.25.8) (2023-08-03)
**Note:** Version bump only for package @standardnotes/analytics

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/analytics",
"version": "2.25.8",
"version": "2.25.14",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -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.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.70.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.69.3...@standardnotes/api-gateway@1.70.0) (2023-08-07)
### Features

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/api-gateway",
"version": "1.70.0",
"version": "1.70.5",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -3,6 +3,40 @@
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
* update storage quota used for user based on shared vault files ([#689](https://github.com/standardnotes/server/issues/689)) ([5311e74](https://github.com/standardnotes/server/commit/5311e7426617da6fc75593dd0fcbff589ca4fc22))
## [1.130.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.130.0...@standardnotes/auth-server@1.130.1) (2023-08-07)
### Bug Fixes

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/auth-server",
"version": "1.130.1",
"version": "1.132.0",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -253,6 +253,9 @@ import { BaseSessionsController } from '../Infra/InversifyExpressUtils/Base/Base
import { Transform } from 'stream'
import { ActivatePremiumFeatures } from '../Domain/UseCase/ActivatePremiumFeatures/ActivatePremiumFeatures'
import { PaymentsAccountDeletedEventHandler } from '../Domain/Handler/PaymentsAccountDeletedEventHandler'
import { UpdateStorageQuotaUsedForUser } from '../Domain/UseCase/UpdateStorageQuotaUsedForUser/UpdateStorageQuotaUsedForUser'
import { SharedVaultFileUploadedEventHandler } from '../Domain/Handler/SharedVaultFileUploadedEventHandler'
import { SharedVaultFileRemovedEventHandler } from '../Domain/Handler/SharedVaultFileRemovedEventHandler'
export class ContainerConfigLoader {
async load(configuration?: {
@@ -557,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
@@ -882,6 +888,15 @@ export class ContainerConfigLoader {
container.bind<VerifyPredicate>(TYPES.Auth_VerifyPredicate).to(VerifyPredicate)
container.bind<CreateCrossServiceToken>(TYPES.Auth_CreateCrossServiceToken).to(CreateCrossServiceToken)
container.bind<ProcessUserRequest>(TYPES.Auth_ProcessUserRequest).to(ProcessUserRequest)
container
.bind<UpdateStorageQuotaUsedForUser>(TYPES.Auth_UpdateStorageQuotaUsedForUser)
.toConstantValue(
new UpdateStorageQuotaUsedForUser(
container.get(TYPES.Auth_UserRepository),
container.get(TYPES.Auth_UserSubscriptionService),
container.get(TYPES.Auth_SubscriptionSettingService),
),
)
// Controller
container
@@ -951,8 +966,38 @@ export class ContainerConfigLoader {
container
.bind<UserEmailChangedEventHandler>(TYPES.Auth_UserEmailChangedEventHandler)
.to(UserEmailChangedEventHandler)
container.bind<FileUploadedEventHandler>(TYPES.Auth_FileUploadedEventHandler).to(FileUploadedEventHandler)
container.bind<FileRemovedEventHandler>(TYPES.Auth_FileRemovedEventHandler).to(FileRemovedEventHandler)
container
.bind<FileUploadedEventHandler>(TYPES.Auth_FileUploadedEventHandler)
.toConstantValue(
new FileUploadedEventHandler(
container.get(TYPES.Auth_UpdateStorageQuotaUsedForUser),
container.get(TYPES.Auth_Logger),
),
)
container
.bind<SharedVaultFileUploadedEventHandler>(TYPES.Auth_SharedVaultFileUploadedEventHandler)
.toConstantValue(
new SharedVaultFileUploadedEventHandler(
container.get(TYPES.Auth_UpdateStorageQuotaUsedForUser),
container.get(TYPES.Auth_Logger),
),
)
container
.bind<FileRemovedEventHandler>(TYPES.Auth_FileRemovedEventHandler)
.toConstantValue(
new FileRemovedEventHandler(
container.get(TYPES.Auth_UpdateStorageQuotaUsedForUser),
container.get(TYPES.Auth_Logger),
),
)
container
.bind<SharedVaultFileRemovedEventHandler>(TYPES.Auth_SharedVaultFileRemovedEventHandler)
.toConstantValue(
new SharedVaultFileRemovedEventHandler(
container.get(TYPES.Auth_UpdateStorageQuotaUsedForUser),
container.get(TYPES.Auth_Logger),
),
)
container
.bind<ListedAccountCreatedEventHandler>(TYPES.Auth_ListedAccountCreatedEventHandler)
.to(ListedAccountCreatedEventHandler)
@@ -999,7 +1044,9 @@ export class ContainerConfigLoader {
['SUBSCRIPTION_REASSIGNED', container.get(TYPES.Auth_SubscriptionReassignedEventHandler)],
['USER_EMAIL_CHANGED', container.get(TYPES.Auth_UserEmailChangedEventHandler)],
['FILE_UPLOADED', container.get(TYPES.Auth_FileUploadedEventHandler)],
['SHARED_VAULT_FILE_UPLOADED', container.get(TYPES.Auth_SharedVaultFileUploadedEventHandler)],
['FILE_REMOVED', container.get(TYPES.Auth_FileRemovedEventHandler)],
['SHARED_VAULT_FILE_REMOVED', container.get(TYPES.Auth_SharedVaultFileRemovedEventHandler)],
['LISTED_ACCOUNT_CREATED', container.get(TYPES.Auth_ListedAccountCreatedEventHandler)],
['LISTED_ACCOUNT_DELETED', container.get(TYPES.Auth_ListedAccountDeletedEventHandler)],
[

View File

@@ -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'),
@@ -152,6 +153,7 @@ const TYPES = {
Auth_ActivatePremiumFeatures: Symbol.for('Auth_ActivatePremiumFeatures'),
Auth_SignInWithRecoveryCodes: Symbol.for('Auth_SignInWithRecoveryCodes'),
Auth_GetUserKeyParamsRecovery: Symbol.for('Auth_GetUserKeyParamsRecovery'),
Auth_UpdateStorageQuotaUsedForUser: Symbol.for('Auth_UpdateStorageQuotaUsedForUser'),
// Handlers
Auth_UserRegisteredEventHandler: Symbol.for('Auth_UserRegisteredEventHandler'),
Auth_AccountDeletionRequestedEventHandler: Symbol.for('Auth_AccountDeletionRequestedEventHandler'),
@@ -165,7 +167,9 @@ const TYPES = {
Auth_ExtensionKeyGrantedEventHandler: Symbol.for('Auth_ExtensionKeyGrantedEventHandler'),
Auth_UserEmailChangedEventHandler: Symbol.for('Auth_UserEmailChangedEventHandler'),
Auth_FileUploadedEventHandler: Symbol.for('Auth_FileUploadedEventHandler'),
Auth_SharedVaultFileUploadedEventHandler: Symbol.for('Auth_SharedVaultFileUploadedEventHandler'),
Auth_FileRemovedEventHandler: Symbol.for('Auth_FileRemovedEventHandler'),
Auth_SharedVaultFileRemovedEventHandler: Symbol.for('Auth_SharedVaultFileRemovedEventHandler'),
Auth_ListedAccountCreatedEventHandler: Symbol.for('Auth_ListedAccountCreatedEventHandler'),
Auth_ListedAccountDeletedEventHandler: Symbol.for('Auth_ListedAccountDeletedEventHandler'),
Auth_UserDisabledSessionUserAgentLoggingEventHandler: Symbol.for(

View File

@@ -1,150 +0,0 @@
import 'reflect-metadata'
import { FileRemovedEvent } from '@standardnotes/domain-events'
import { Logger } from 'winston'
import { User } from '../User/User'
import { FileRemovedEventHandler } from './FileRemovedEventHandler'
import { SubscriptionSettingServiceInterface } from '../Setting/SubscriptionSettingServiceInterface'
import { UserSubscription } from '../Subscription/UserSubscription'
import { UserSubscriptionType } from '../Subscription/UserSubscriptionType'
import { UserSubscriptionServiceInterface } from '../Subscription/UserSubscriptionServiceInterface'
describe('FileRemovedEventHandler', () => {
let userSubscriptionService: UserSubscriptionServiceInterface
let logger: Logger
let regularUser: User
let sharedUser: User
let event: FileRemovedEvent
let subscriptionSettingService: SubscriptionSettingServiceInterface
let regularSubscription: UserSubscription
let sharedSubscription: UserSubscription
const createHandler = () => new FileRemovedEventHandler(userSubscriptionService, subscriptionSettingService, logger)
beforeEach(() => {
regularUser = {
uuid: '123',
} as jest.Mocked<User>
sharedUser = {
uuid: '234',
} as jest.Mocked<User>
regularSubscription = {
uuid: '1-2-3',
subscriptionType: UserSubscriptionType.Regular,
user: Promise.resolve(regularUser),
} as jest.Mocked<UserSubscription>
sharedSubscription = {
uuid: '2-3-4',
subscriptionType: UserSubscriptionType.Shared,
user: Promise.resolve(sharedUser),
} as jest.Mocked<UserSubscription>
userSubscriptionService = {} as jest.Mocked<UserSubscriptionServiceInterface>
userSubscriptionService.findRegularSubscriptionForUserUuid = jest
.fn()
.mockReturnValue({ regularSubscription, sharedSubscription: null })
subscriptionSettingService = {} as jest.Mocked<SubscriptionSettingServiceInterface>
subscriptionSettingService.findSubscriptionSettingWithDecryptedValue = jest.fn().mockReturnValue(null)
subscriptionSettingService.createOrReplace = jest.fn()
event = {} as jest.Mocked<FileRemovedEvent>
event.createdAt = new Date(1)
event.payload = {
userUuid: '1-2-3',
fileByteSize: 123,
filePath: '1-2-3/2-3-4',
fileName: '2-3-4',
regularSubscriptionUuid: '4-5-6',
}
logger = {} as jest.Mocked<Logger>
logger.warn = jest.fn()
})
it('should do nothing a bytes used setting does not exist', async () => {
await createHandler().handle(event)
expect(subscriptionSettingService.createOrReplace).not.toHaveBeenCalled()
})
it('should not do anything if a user subscription is not found', async () => {
subscriptionSettingService.findSubscriptionSettingWithDecryptedValue = jest.fn().mockReturnValue({
value: 345,
})
userSubscriptionService.findRegularSubscriptionForUserUuid = jest
.fn()
.mockReturnValue({ regularSubscription: null, sharedSubscription: null })
await createHandler().handle(event)
expect(subscriptionSettingService.createOrReplace).not.toHaveBeenCalled()
})
it('should update a bytes used setting', async () => {
subscriptionSettingService.findSubscriptionSettingWithDecryptedValue = jest.fn().mockReturnValue({
value: 345,
})
await createHandler().handle(event)
expect(subscriptionSettingService.createOrReplace).toHaveBeenCalledWith({
props: {
name: 'FILE_UPLOAD_BYTES_USED',
sensitive: false,
unencryptedValue: '222',
serverEncryptionVersion: 0,
},
user: regularUser,
userSubscription: {
uuid: '1-2-3',
subscriptionType: 'regular',
user: Promise.resolve(regularUser),
},
})
})
it('should update a bytes used setting on both shared and regular subscription', async () => {
userSubscriptionService.findRegularSubscriptionForUserUuid = jest
.fn()
.mockReturnValue({ regularSubscription, sharedSubscription })
subscriptionSettingService.findSubscriptionSettingWithDecryptedValue = jest.fn().mockReturnValue({
value: 345,
})
await createHandler().handle(event)
expect(subscriptionSettingService.createOrReplace).toHaveBeenNthCalledWith(1, {
props: {
name: 'FILE_UPLOAD_BYTES_USED',
sensitive: false,
unencryptedValue: '222',
serverEncryptionVersion: 0,
},
user: regularUser,
userSubscription: {
uuid: '1-2-3',
subscriptionType: 'regular',
user: Promise.resolve(regularUser),
},
})
expect(subscriptionSettingService.createOrReplace).toHaveBeenNthCalledWith(2, {
props: {
name: 'FILE_UPLOAD_BYTES_USED',
sensitive: false,
unencryptedValue: '222',
serverEncryptionVersion: 0,
},
user: sharedUser,
userSubscription: {
uuid: '2-3-4',
subscriptionType: 'shared',
user: Promise.resolve(sharedUser),
},
})
})
})

View File

@@ -1,63 +1,19 @@
import { DomainEventHandlerInterface, FileRemovedEvent } from '@standardnotes/domain-events'
import { SettingName } from '@standardnotes/settings'
import { inject, injectable } from 'inversify'
import { Logger } from 'winston'
import TYPES from '../../Bootstrap/Types'
import { EncryptionVersion } from '../Encryption/EncryptionVersion'
import { SubscriptionSettingServiceInterface } from '../Setting/SubscriptionSettingServiceInterface'
import { UserSubscription } from '../Subscription/UserSubscription'
import { UserSubscriptionServiceInterface } from '../Subscription/UserSubscriptionServiceInterface'
import { UpdateStorageQuotaUsedForUser } from '../UseCase/UpdateStorageQuotaUsedForUser/UpdateStorageQuotaUsedForUser'
@injectable()
export class FileRemovedEventHandler implements DomainEventHandlerInterface {
constructor(
@inject(TYPES.Auth_UserSubscriptionService) private userSubscriptionService: UserSubscriptionServiceInterface,
@inject(TYPES.Auth_SubscriptionSettingService)
private subscriptionSettingService: SubscriptionSettingServiceInterface,
@inject(TYPES.Auth_Logger) private logger: Logger,
) {}
constructor(private updateStorageQuotaUsedForUserUseCase: UpdateStorageQuotaUsedForUser, private logger: Logger) {}
async handle(event: FileRemovedEvent): Promise<void> {
const { regularSubscription, sharedSubscription } =
await this.userSubscriptionService.findRegularSubscriptionForUserUuid(event.payload.userUuid)
if (regularSubscription === null) {
this.logger.warn(`Could not find regular user subscription for user with uuid: ${event.payload.userUuid}`)
return
}
await this.updateUploadBytesUsedSetting(regularSubscription, event.payload.fileByteSize)
if (sharedSubscription !== null) {
await this.updateUploadBytesUsedSetting(sharedSubscription, event.payload.fileByteSize)
}
}
private async updateUploadBytesUsedSetting(subscription: UserSubscription, byteSize: number): Promise<void> {
const user = await subscription.user
const bytesUsedSetting = await this.subscriptionSettingService.findSubscriptionSettingWithDecryptedValue({
userUuid: user.uuid,
userSubscriptionUuid: subscription.uuid,
subscriptionSettingName: SettingName.create(SettingName.NAMES.FileUploadBytesUsed).getValue(),
const result = await this.updateStorageQuotaUsedForUserUseCase.execute({
userUuid: event.payload.userUuid,
bytesUsed: -event.payload.fileByteSize,
})
if (bytesUsedSetting === null) {
this.logger.warn(`Could not find bytes used setting for user with uuid: ${user.uuid}`)
return
if (result.isFailed()) {
this.logger.error(`Failed to update storage quota used for user: ${result.getError()}`)
}
const bytesUsed = bytesUsedSetting.value as string
await this.subscriptionSettingService.createOrReplace({
userSubscription: subscription,
user,
props: {
name: SettingName.NAMES.FileUploadBytesUsed,
unencryptedValue: (+bytesUsed - byteSize).toString(),
sensitive: false,
serverEncryptionVersion: EncryptionVersion.Unencrypted,
},
})
}
}

View File

@@ -1,82 +1,19 @@
import { DomainEventHandlerInterface, FileUploadedEvent } from '@standardnotes/domain-events'
import { SettingName } from '@standardnotes/settings'
import { inject, injectable } from 'inversify'
import { Logger } from 'winston'
import TYPES from '../../Bootstrap/Types'
import { EncryptionVersion } from '../Encryption/EncryptionVersion'
import { SubscriptionSettingServiceInterface } from '../Setting/SubscriptionSettingServiceInterface'
import { UserSubscription } from '../Subscription/UserSubscription'
import { UserSubscriptionServiceInterface } from '../Subscription/UserSubscriptionServiceInterface'
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
import { User } from '../User/User'
import { Uuid } from '@standardnotes/domain-core'
import { UpdateStorageQuotaUsedForUser } from '../UseCase/UpdateStorageQuotaUsedForUser/UpdateStorageQuotaUsedForUser'
@injectable()
export class FileUploadedEventHandler implements DomainEventHandlerInterface {
constructor(
@inject(TYPES.Auth_UserRepository) private userRepository: UserRepositoryInterface,
@inject(TYPES.Auth_UserSubscriptionService) private userSubscriptionService: UserSubscriptionServiceInterface,
@inject(TYPES.Auth_SubscriptionSettingService)
private subscriptionSettingService: SubscriptionSettingServiceInterface,
@inject(TYPES.Auth_Logger) private logger: Logger,
) {}
constructor(private updateStorageQuotaUsedForUserUseCase: UpdateStorageQuotaUsedForUser, private logger: Logger) {}
async handle(event: FileUploadedEvent): Promise<void> {
const userUuidOrError = Uuid.create(event.payload.userUuid)
if (userUuidOrError.isFailed()) {
this.logger.warn(userUuidOrError.getError())
return
}
const userUuid = userUuidOrError.getValue()
const user = await this.userRepository.findOneByUuid(userUuid)
if (user === null) {
this.logger.warn(`Could not find user with uuid: ${userUuid.value}`)
return
}
const { regularSubscription, sharedSubscription } =
await this.userSubscriptionService.findRegularSubscriptionForUserUuid(userUuid.value)
if (regularSubscription === null) {
this.logger.warn(`Could not find regular user subscription for user with uuid: ${userUuid.value}`)
return
}
await this.updateUploadBytesUsedSetting(regularSubscription, user, event.payload.fileByteSize)
if (sharedSubscription !== null) {
await this.updateUploadBytesUsedSetting(sharedSubscription, user, event.payload.fileByteSize)
}
}
private async updateUploadBytesUsedSetting(
subscription: UserSubscription,
user: User,
byteSize: number,
): Promise<void> {
let bytesUsed = '0'
const bytesUsedSetting = await this.subscriptionSettingService.findSubscriptionSettingWithDecryptedValue({
userUuid: (await subscription.user).uuid,
userSubscriptionUuid: subscription.uuid,
subscriptionSettingName: SettingName.create(SettingName.NAMES.FileUploadBytesUsed).getValue(),
const result = await this.updateStorageQuotaUsedForUserUseCase.execute({
userUuid: event.payload.userUuid,
bytesUsed: event.payload.fileByteSize,
})
if (bytesUsedSetting !== null) {
bytesUsed = bytesUsedSetting.value as string
}
await this.subscriptionSettingService.createOrReplace({
userSubscription: subscription,
user,
props: {
name: SettingName.NAMES.FileUploadBytesUsed,
unencryptedValue: (+bytesUsed + byteSize).toString(),
sensitive: false,
serverEncryptionVersion: EncryptionVersion.Unencrypted,
},
})
if (result.isFailed()) {
this.logger.error(`Failed to update storage quota used for user: ${result.getError()}`)
}
}
}

View File

@@ -0,0 +1,19 @@
import { DomainEventHandlerInterface, SharedVaultFileRemovedEvent } from '@standardnotes/domain-events'
import { Logger } from 'winston'
import { UpdateStorageQuotaUsedForUser } from '../UseCase/UpdateStorageQuotaUsedForUser/UpdateStorageQuotaUsedForUser'
export class SharedVaultFileRemovedEventHandler implements DomainEventHandlerInterface {
constructor(private updateStorageQuotaUsedForUserUseCase: UpdateStorageQuotaUsedForUser, private logger: Logger) {}
async handle(event: SharedVaultFileRemovedEvent): Promise<void> {
const result = await this.updateStorageQuotaUsedForUserUseCase.execute({
userUuid: event.payload.vaultOwnerUuid,
bytesUsed: -event.payload.fileByteSize,
})
if (result.isFailed()) {
this.logger.error(`Failed to update storage quota used for user: ${result.getError()}`)
}
}
}

View File

@@ -0,0 +1,19 @@
import { DomainEventHandlerInterface, SharedVaultFileUploadedEvent } from '@standardnotes/domain-events'
import { Logger } from 'winston'
import { UpdateStorageQuotaUsedForUser } from '../UseCase/UpdateStorageQuotaUsedForUser/UpdateStorageQuotaUsedForUser'
export class SharedVaultFileUploadedEventHandler implements DomainEventHandlerInterface {
constructor(private updateStorageQuotaUsedForUserUseCase: UpdateStorageQuotaUsedForUser, private logger: Logger) {}
async handle(event: SharedVaultFileUploadedEvent): Promise<void> {
const result = await this.updateStorageQuotaUsedForUserUseCase.execute({
userUuid: event.payload.vaultOwnerUuid,
bytesUsed: event.payload.fileByteSize,
})
if (result.isFailed()) {
this.logger.error(`Failed to update storage quota used for user: ${result.getError()}`)
}
}
}

View File

@@ -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',

View File

@@ -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)

View File

@@ -1,28 +1,22 @@
import 'reflect-metadata'
import { UpdateStorageQuotaUsedForUser } from './UpdateStorageQuotaUsedForUser'
import { FileUploadedEvent } from '@standardnotes/domain-events'
import { Logger } from 'winston'
import { SubscriptionSettingServiceInterface } from '../../Setting/SubscriptionSettingServiceInterface'
import { UserSubscription } from '../../Subscription/UserSubscription'
import { UserSubscriptionServiceInterface } from '../../Subscription/UserSubscriptionServiceInterface'
import { UserSubscriptionType } from '../../Subscription/UserSubscriptionType'
import { User } from '../../User/User'
import { UserRepositoryInterface } from '../../User/UserRepositoryInterface'
import { User } from '../User/User'
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
import { FileUploadedEventHandler } from './FileUploadedEventHandler'
import { SubscriptionSettingServiceInterface } from '../Setting/SubscriptionSettingServiceInterface'
import { UserSubscription } from '../Subscription/UserSubscription'
import { UserSubscriptionServiceInterface } from '../Subscription/UserSubscriptionServiceInterface'
import { UserSubscriptionType } from '../Subscription/UserSubscriptionType'
describe('FileUploadedEventHandler', () => {
describe('UpdateStorageQuotaUsedForUser', () => {
let userRepository: UserRepositoryInterface
let userSubscriptionService: UserSubscriptionServiceInterface
let logger: Logger
let user: User
let event: FileUploadedEvent
let subscriptionSettingService: SubscriptionSettingServiceInterface
let regularSubscription: UserSubscription
let sharedSubscription: UserSubscription
const createHandler = () =>
new FileUploadedEventHandler(userRepository, userSubscriptionService, subscriptionSettingService, logger)
const createUseCase = () =>
new UpdateStorageQuotaUsedForUser(userRepository, userSubscriptionService, subscriptionSettingService)
beforeEach(() => {
user = {
@@ -52,23 +46,15 @@ describe('FileUploadedEventHandler', () => {
subscriptionSettingService = {} as jest.Mocked<SubscriptionSettingServiceInterface>
subscriptionSettingService.findSubscriptionSettingWithDecryptedValue = jest.fn().mockReturnValue(null)
subscriptionSettingService.createOrReplace = jest.fn()
event = {} as jest.Mocked<FileUploadedEvent>
event.createdAt = new Date(1)
event.payload = {
userUuid: '00000000-0000-0000-0000-000000000000',
fileByteSize: 123,
filePath: '00000000-0000-0000-0000-000000000000/2-3-4',
fileName: '2-3-4',
}
logger = {} as jest.Mocked<Logger>
logger.warn = jest.fn()
})
it('should create a bytes used setting if one does not exist', async () => {
await createHandler().handle(event)
const result = await createUseCase().execute({
userUuid: '00000000-0000-0000-0000-000000000000',
bytesUsed: 123,
})
expect(result.isFailed()).toBeFalsy()
expect(subscriptionSettingService.createOrReplace).toHaveBeenCalledWith({
props: {
name: 'FILE_UPLOAD_BYTES_USED',
@@ -86,9 +72,11 @@ describe('FileUploadedEventHandler', () => {
})
it('should not do anything if a user uuid is invalid', async () => {
event.payload.userUuid = 'invalid'
await createHandler().handle(event)
const result = await createUseCase().execute({
userUuid: 'invalid',
bytesUsed: 123,
})
expect(result.isFailed()).toBeTruthy()
expect(subscriptionSettingService.createOrReplace).not.toHaveBeenCalled()
})
@@ -96,7 +84,11 @@ describe('FileUploadedEventHandler', () => {
it('should not do anything if a user is not found', async () => {
userRepository.findOneByUuid = jest.fn().mockReturnValue(null)
await createHandler().handle(event)
const result = await createUseCase().execute({
userUuid: '00000000-0000-0000-0000-000000000000',
bytesUsed: 123,
})
expect(result.isFailed()).toBeTruthy()
expect(subscriptionSettingService.createOrReplace).not.toHaveBeenCalled()
})
@@ -109,16 +101,24 @@ describe('FileUploadedEventHandler', () => {
.fn()
.mockReturnValue({ regularSubscription: null, sharedSubscription: null })
await createHandler().handle(event)
const result = await createUseCase().execute({
userUuid: '00000000-0000-0000-0000-000000000000',
bytesUsed: 123,
})
expect(result.isFailed()).toBeTruthy()
expect(subscriptionSettingService.createOrReplace).not.toHaveBeenCalled()
})
it('should update a bytes used setting if one does exist', async () => {
it('should add bytes used setting if one does exist', async () => {
subscriptionSettingService.findSubscriptionSettingWithDecryptedValue = jest.fn().mockReturnValue({
value: 345,
})
await createHandler().handle(event)
const result = await createUseCase().execute({
userUuid: '00000000-0000-0000-0000-000000000000',
bytesUsed: 123,
})
expect(result.isFailed()).toBeFalsy()
expect(subscriptionSettingService.createOrReplace).toHaveBeenCalledWith({
props: {
@@ -136,6 +136,32 @@ describe('FileUploadedEventHandler', () => {
})
})
it('should subtract bytes used setting if one does exist', async () => {
subscriptionSettingService.findSubscriptionSettingWithDecryptedValue = jest.fn().mockReturnValue({
value: 345,
})
const result = await createUseCase().execute({
userUuid: '00000000-0000-0000-0000-000000000000',
bytesUsed: -123,
})
expect(result.isFailed()).toBeFalsy()
expect(subscriptionSettingService.createOrReplace).toHaveBeenCalledWith({
props: {
name: 'FILE_UPLOAD_BYTES_USED',
sensitive: false,
unencryptedValue: '222',
serverEncryptionVersion: 0,
},
user,
userSubscription: {
uuid: '00000000-0000-0000-0000-000000000000',
subscriptionType: 'regular',
user: Promise.resolve(user),
},
})
})
it('should update a bytes used setting on both regular and shared subscription', async () => {
userSubscriptionService.findRegularSubscriptionForUserUuid = jest
.fn()
@@ -144,7 +170,11 @@ describe('FileUploadedEventHandler', () => {
subscriptionSettingService.findSubscriptionSettingWithDecryptedValue = jest.fn().mockReturnValue({
value: 345,
})
await createHandler().handle(event)
const result = await createUseCase().execute({
userUuid: '00000000-0000-0000-0000-000000000000',
bytesUsed: 123,
})
expect(result.isFailed()).toBeFalsy()
expect(subscriptionSettingService.createOrReplace).toHaveBeenCalledWith({
props: {

View File

@@ -0,0 +1,72 @@
import { Result, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
import { SettingName } from '@standardnotes/settings'
import { EncryptionVersion } from '../../Encryption/EncryptionVersion'
import { SubscriptionSettingServiceInterface } from '../../Setting/SubscriptionSettingServiceInterface'
import { UserSubscription } from '../../Subscription/UserSubscription'
import { UserSubscriptionServiceInterface } from '../../Subscription/UserSubscriptionServiceInterface'
import { UserRepositoryInterface } from '../../User/UserRepositoryInterface'
import { UpdateStorageQuotaUsedForUserDTO } from './UpdateStorageQuotaUsedForUserDTO'
import { User } from '../../User/User'
export class UpdateStorageQuotaUsedForUser implements UseCaseInterface<void> {
constructor(
private userRepository: UserRepositoryInterface,
private userSubscriptionService: UserSubscriptionServiceInterface,
private subscriptionSettingService: SubscriptionSettingServiceInterface,
) {}
async execute(dto: UpdateStorageQuotaUsedForUserDTO): Promise<Result<void>> {
const userUuidOrError = Uuid.create(dto.userUuid)
if (userUuidOrError.isFailed()) {
return Result.fail(userUuidOrError.getError())
}
const userUuid = userUuidOrError.getValue()
const user = await this.userRepository.findOneByUuid(userUuid)
if (user === null) {
return Result.fail(`Could not find user with uuid: ${userUuid.value}`)
}
const { regularSubscription, sharedSubscription } =
await this.userSubscriptionService.findRegularSubscriptionForUserUuid(userUuid.value)
if (regularSubscription === null) {
return Result.fail(`Could not find regular user subscription for user with uuid: ${userUuid.value}`)
}
await this.updateUploadBytesUsedSetting(regularSubscription, user, dto.bytesUsed)
if (sharedSubscription !== null) {
await this.updateUploadBytesUsedSetting(sharedSubscription, user, dto.bytesUsed)
}
return Result.ok()
}
private async updateUploadBytesUsedSetting(
subscription: UserSubscription,
user: User,
bytesUsed: number,
): Promise<void> {
let bytesAlreadyUsed = '0'
const bytesUsedSetting = await this.subscriptionSettingService.findSubscriptionSettingWithDecryptedValue({
userUuid: (await subscription.user).uuid,
userSubscriptionUuid: subscription.uuid,
subscriptionSettingName: SettingName.create(SettingName.NAMES.FileUploadBytesUsed).getValue(),
})
if (bytesUsedSetting !== null) {
bytesAlreadyUsed = bytesUsedSetting.value as string
}
await this.subscriptionSettingService.createOrReplace({
userSubscription: subscription,
user,
props: {
name: SettingName.NAMES.FileUploadBytesUsed,
unencryptedValue: (+bytesAlreadyUsed + bytesUsed).toString(),
sensitive: false,
serverEncryptionVersion: EncryptionVersion.Unencrypted,
},
})
}
}

View File

@@ -0,0 +1,4 @@
export interface UpdateStorageQuotaUsedForUserDTO {
userUuid: string
bytesUsed: number
}

View File

@@ -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)
}

View File

@@ -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

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/common",
"version": "1.50.0",
"version": "1.50.1",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -1 +0,0 @@
export type ApplicationIdentifier = string

View File

@@ -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
}

View File

@@ -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'

View File

@@ -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

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/domain-core",
"version": "1.24.2",
"version": "1.26.0",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -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()
})
})

View File

@@ -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}`)

View File

@@ -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')
})
})

View File

@@ -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 }))
}
}

View File

@@ -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 {

View File

@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.12.11](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.12.10...@standardnotes/domain-events-infra@1.12.11) (2023-08-08)
**Note:** Version bump only for package @standardnotes/domain-events-infra
## [1.12.10](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.12.9...@standardnotes/domain-events-infra@1.12.10) (2023-08-03)
**Note:** Version bump only for package @standardnotes/domain-events-infra

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/domain-events-infra",
"version": "1.12.10",
"version": "1.12.11",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -3,6 +3,12 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [2.115.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.114.0...@standardnotes/domain-events@2.115.0) (2023-08-08)
### Features
* update storage quota used for user based on shared vault files ([#689](https://github.com/standardnotes/server/issues/689)) ([5311e74](https://github.com/standardnotes/server/commit/5311e7426617da6fc75593dd0fcbff589ca4fc22))
# [2.114.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.113.1...@standardnotes/domain-events@2.114.0) (2023-08-03)
### Features

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/domain-events",
"version": "2.114.0",
"version": "2.115.0",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -1,5 +1,6 @@
export interface SharedVaultFileRemovedEventPayload {
sharedVaultUuid: string
vaultOwnerUuid: string
fileByteSize: number
filePath: string
fileName: string

View File

@@ -1,5 +1,6 @@
export interface SharedVaultFileUploadedEventPayload {
sharedVaultUuid: string
vaultOwnerUuid: string
fileByteSize: number
filePath: string
fileName: string

View File

@@ -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.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.11.15](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.11.14...@standardnotes/event-store@1.11.15) (2023-08-03)
**Note:** Version bump only for package @standardnotes/event-store

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/event-store",
"version": "1.11.15",
"version": "1.11.20",
"description": "Event Store Service",
"private": true,
"main": "dist/src/index.js",

View File

@@ -3,6 +3,28 @@
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
* update storage quota used for user based on shared vault files ([#689](https://github.com/standardnotes/files/issues/689)) ([5311e74](https://github.com/standardnotes/files/commit/5311e7426617da6fc75593dd0fcbff589ca4fc22))
## [1.19.18](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.19.17...@standardnotes/files-server@1.19.18) (2023-08-03)
**Note:** Version bump only for package @standardnotes/files-server

View File

@@ -7,6 +7,6 @@ module.exports = {
transform: {
...tsjPreset.transform,
},
coveragePathIgnorePatterns: ['/Bootstrap/', 'HealthCheckController', '/Infra/FS'],
coveragePathIgnorePatterns: ['/Bootstrap/', 'HealthCheckController', '/Infra/FS', '/Domain/Event/'],
setupFilesAfterEnv: ['./test-setup.ts'],
}

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/files-server",
"version": "1.19.18",
"version": "1.20.4",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -72,6 +72,16 @@ export class ContainerConfigLoader {
await import('newrelic')
}
// env vars
container.bind(TYPES.Files_VALET_TOKEN_SECRET).toConstantValue(env.get('VALET_TOKEN_SECRET'))
container
.bind(TYPES.Files_MAX_CHUNK_BYTES)
.toConstantValue(env.get('MAX_CHUNK_BYTES', true) ? +env.get('MAX_CHUNK_BYTES', true) : 100000000)
container.bind(TYPES.Files_VERSION).toConstantValue(env.get('VERSION', true) ?? 'development')
container
.bind(TYPES.Files_FILE_UPLOAD_PATH)
.toConstantValue(env.get('FILE_UPLOAD_PATH', true) ?? `${__dirname}/../../uploads`)
const isConfiguredForHomeServer = env.get('MODE', true) === 'home-server'
const isConfiguredForInMemoryCache = env.get('CACHE_TYPE', true) === 'memory'
@@ -85,6 +95,12 @@ export class ContainerConfigLoader {
container.bind<TimerInterface>(TYPES.Files_Timer).toConstantValue(new Timer())
// services
container
.bind<TokenDecoderInterface<ValetTokenData>>(TYPES.Files_ValetTokenDecoder)
.toConstantValue(new TokenDecoder<ValetTokenData>(container.get(TYPES.Files_VALET_TOKEN_SECRET)))
container.bind<DomainEventFactoryInterface>(TYPES.Files_DomainEventFactory).to(DomainEventFactory)
if (isConfiguredForInMemoryCache) {
container
.bind<UploadRepositoryInterface>(TYPES.Files_UploadRepository)
@@ -157,16 +173,6 @@ export class ContainerConfigLoader {
)
}
// env vars
container.bind(TYPES.Files_VALET_TOKEN_SECRET).toConstantValue(env.get('VALET_TOKEN_SECRET'))
container
.bind(TYPES.Files_MAX_CHUNK_BYTES)
.toConstantValue(env.get('MAX_CHUNK_BYTES', true) ? +env.get('MAX_CHUNK_BYTES', true) : 100000000)
container.bind(TYPES.Files_VERSION).toConstantValue(env.get('VERSION', true) ?? 'development')
container
.bind(TYPES.Files_FILE_UPLOAD_PATH)
.toConstantValue(env.get('FILE_UPLOAD_PATH', true) ?? `${__dirname}/../../uploads`)
if (!isConfiguredForHomeServer && (env.get('S3_AWS_REGION', true) || env.get('S3_ENDPOINT', true))) {
const s3Opts: S3ClientConfig = {
apiVersion: 'latest',
@@ -198,7 +204,16 @@ export class ContainerConfigLoader {
container.bind<UploadFileChunk>(TYPES.Files_UploadFileChunk).to(UploadFileChunk)
container.bind<StreamDownloadFile>(TYPES.Files_StreamDownloadFile).to(StreamDownloadFile)
container.bind<CreateUploadSession>(TYPES.Files_CreateUploadSession).to(CreateUploadSession)
container.bind<FinishUploadSession>(TYPES.Files_FinishUploadSession).to(FinishUploadSession)
container
.bind<FinishUploadSession>(TYPES.Files_FinishUploadSession)
.toConstantValue(
new FinishUploadSession(
container.get(TYPES.Files_FileUploader),
container.get(TYPES.Files_UploadRepository),
container.get(TYPES.Files_DomainEventPublisher),
container.get(TYPES.Files_DomainEventFactory),
),
)
container.bind<GetFileMetadata>(TYPES.Files_GetFileMetadata).to(GetFileMetadata)
container.bind<RemoveFile>(TYPES.Files_RemoveFile).to(RemoveFile)
container.bind<MoveFile>(TYPES.Files_MoveFile).to(MoveFile)
@@ -210,12 +225,6 @@ export class ContainerConfigLoader {
.bind<SharedVaultValetTokenAuthMiddleware>(TYPES.Files_SharedVaultValetTokenAuthMiddleware)
.to(SharedVaultValetTokenAuthMiddleware)
// services
container
.bind<TokenDecoderInterface<ValetTokenData>>(TYPES.Files_ValetTokenDecoder)
.toConstantValue(new TokenDecoder<ValetTokenData>(container.get(TYPES.Files_VALET_TOKEN_SECRET)))
container.bind<DomainEventFactoryInterface>(TYPES.Files_DomainEventFactory).to(DomainEventFactory)
// Handlers
container
.bind<AccountDeletionRequestedEventHandler>(TYPES.Files_AccountDeletionRequestedEventHandler)

View File

@@ -1,126 +0,0 @@
import 'reflect-metadata'
import { TimerInterface } from '@standardnotes/time'
import { DomainEventFactory } from './DomainEventFactory'
describe('DomainEventFactory', () => {
let timer: TimerInterface
const createFactory = () => new DomainEventFactory(timer)
beforeEach(() => {
timer = {} as jest.Mocked<TimerInterface>
timer.getUTCDate = jest.fn().mockReturnValue(new Date(1))
})
it('should create a SHARED_VAULT_FILE_UPLOADED event', () => {
expect(
createFactory().createSharedVaultFileUploadedEvent({
sharedVaultUuid: '1-2-3',
filePath: 'foo/bar',
fileName: 'baz',
fileByteSize: 123,
}),
).toEqual({
createdAt: new Date(1),
meta: {
correlation: {
userIdentifier: '1-2-3',
userIdentifierType: 'shared-vault-uuid',
},
origin: 'files',
},
payload: {
sharedVaultUuid: '1-2-3',
filePath: 'foo/bar',
fileName: 'baz',
fileByteSize: 123,
},
type: 'SHARED_VAULT_FILE_UPLOADED',
})
})
it('should create a SHARED_VAULT_FILE_REMOVED event', () => {
expect(
createFactory().createSharedVaultFileRemovedEvent({
sharedVaultUuid: '1-2-3',
filePath: 'foo/bar',
fileName: 'baz',
fileByteSize: 123,
}),
).toEqual({
createdAt: new Date(1),
meta: {
correlation: {
userIdentifier: '1-2-3',
userIdentifierType: 'shared-vault-uuid',
},
origin: 'files',
},
payload: {
sharedVaultUuid: '1-2-3',
filePath: 'foo/bar',
fileName: 'baz',
fileByteSize: 123,
},
type: 'SHARED_VAULT_FILE_REMOVED',
})
})
it('should create a FILE_UPLOADED event', () => {
expect(
createFactory().createFileUploadedEvent({
fileByteSize: 123,
fileName: '2-3-4',
filePath: '1-2-3/2-3-4',
userUuid: '1-2-3',
}),
).toEqual({
createdAt: new Date(1),
meta: {
correlation: {
userIdentifier: '1-2-3',
userIdentifierType: 'uuid',
},
origin: 'files',
},
payload: {
fileByteSize: 123,
fileName: '2-3-4',
filePath: '1-2-3/2-3-4',
userUuid: '1-2-3',
},
type: 'FILE_UPLOADED',
})
})
it('should create a FILE_REMOVED event', () => {
expect(
createFactory().createFileRemovedEvent({
fileByteSize: 123,
fileName: '2-3-4',
filePath: '1-2-3/2-3-4',
userUuid: '1-2-3',
regularSubscriptionUuid: '1-2-3',
}),
).toEqual({
createdAt: new Date(1),
meta: {
correlation: {
userIdentifier: '1-2-3',
userIdentifierType: 'uuid',
},
origin: 'files',
},
payload: {
fileByteSize: 123,
fileName: '2-3-4',
filePath: '1-2-3/2-3-4',
userUuid: '1-2-3',
regularSubscriptionUuid: '1-2-3',
},
type: 'FILE_REMOVED',
})
})
})

View File

@@ -58,6 +58,7 @@ export class DomainEventFactory implements DomainEventFactoryInterface {
createSharedVaultFileUploadedEvent(payload: {
sharedVaultUuid: string
vaultOwnerUuid: string
filePath: string
fileName: string
fileByteSize: number
@@ -78,6 +79,7 @@ export class DomainEventFactory implements DomainEventFactoryInterface {
createSharedVaultFileRemovedEvent(payload: {
sharedVaultUuid: string
vaultOwnerUuid: string
filePath: string
fileName: string
fileByteSize: number

View File

@@ -21,12 +21,14 @@ export interface DomainEventFactoryInterface {
}): FileRemovedEvent
createSharedVaultFileUploadedEvent(payload: {
sharedVaultUuid: string
vaultOwnerUuid: string
filePath: string
fileName: string
fileByteSize: number
}): SharedVaultFileUploadedEvent
createSharedVaultFileRemovedEvent(payload: {
sharedVaultUuid: string
vaultOwnerUuid: string
filePath: string
fileName: string
fileByteSize: number

View File

@@ -1,11 +1,9 @@
import 'reflect-metadata'
import {
DomainEventPublisherInterface,
FileUploadedEvent,
SharedVaultFileUploadedEvent,
} from '@standardnotes/domain-events'
import { Logger } from 'winston'
import { DomainEventFactoryInterface } from '../../Event/DomainEventFactoryInterface'
import { FileUploaderInterface } from '../../Services/FileUploaderInterface'
import { UploadRepositoryInterface } from '../../Upload/UploadRepositoryInterface'
@@ -17,10 +15,9 @@ describe('FinishUploadSession', () => {
let uploadRepository: UploadRepositoryInterface
let domainEventPublisher: DomainEventPublisherInterface
let domainEventFactory: DomainEventFactoryInterface
let logger: Logger
const createUseCase = () =>
new FinishUploadSession(fileUploader, uploadRepository, domainEventPublisher, domainEventFactory, logger)
new FinishUploadSession(fileUploader, uploadRepository, domainEventPublisher, domainEventFactory)
beforeEach(() => {
fileUploader = {} as jest.Mocked<FileUploaderInterface>
@@ -38,11 +35,6 @@ describe('FinishUploadSession', () => {
domainEventFactory.createSharedVaultFileUploadedEvent = jest
.fn()
.mockReturnValue({} as jest.Mocked<SharedVaultFileUploadedEvent>)
logger = {} as jest.Mocked<Logger>
logger.debug = jest.fn()
logger.error = jest.fn()
logger.warn = jest.fn()
})
it('should not finish an upload session if non existing', async () => {
@@ -50,8 +42,7 @@ describe('FinishUploadSession', () => {
await createUseCase().execute({
resourceRemoteIdentifier: '2-3-4',
ownerUuid: '1-2-3',
ownerType: 'user',
userUuid: '00000000-0000-0000-0000-000000000000',
uploadBytesLimit: 100,
uploadBytesUsed: 0,
})
@@ -60,24 +51,33 @@ describe('FinishUploadSession', () => {
expect(domainEventPublisher.publish).not.toHaveBeenCalled()
})
it('should not finish an upload session user uuid is invalid', async () => {
const result = await createUseCase().execute({
resourceRemoteIdentifier: '2-3-4',
userUuid: 'invalid',
uploadBytesLimit: 100,
uploadBytesUsed: 0,
})
expect(result.isFailed()).toBeTruthy()
expect(fileUploader.finishUploadSession).not.toHaveBeenCalled()
expect(domainEventPublisher.publish).not.toHaveBeenCalled()
})
it('should indicate of an error in finishing session fails', async () => {
uploadRepository.retrieveUploadSessionId = jest.fn().mockImplementation(() => {
throw new Error('oops')
})
expect(
await createUseCase().execute({
resourceRemoteIdentifier: '2-3-4',
ownerUuid: '1-2-3',
ownerType: 'user',
uploadBytesLimit: 100,
uploadBytesUsed: 0,
}),
).toEqual({
success: false,
message: 'Could not finish upload session',
const result = await createUseCase().execute({
resourceRemoteIdentifier: '2-3-4',
userUuid: '00000000-0000-0000-0000-000000000000',
uploadBytesLimit: 100,
uploadBytesUsed: 0,
})
expect(result.getError()).toEqual('Could not finish upload session')
expect(fileUploader.finishUploadSession).not.toHaveBeenCalled()
expect(domainEventPublisher.publish).not.toHaveBeenCalled()
})
@@ -85,13 +85,12 @@ describe('FinishUploadSession', () => {
it('should finish an upload session', async () => {
await createUseCase().execute({
resourceRemoteIdentifier: '2-3-4',
ownerUuid: '1-2-3',
ownerType: 'user',
userUuid: '00000000-0000-0000-0000-000000000000',
uploadBytesLimit: 100,
uploadBytesUsed: 0,
})
expect(fileUploader.finishUploadSession).toHaveBeenCalledWith('123', '1-2-3/2-3-4', [
expect(fileUploader.finishUploadSession).toHaveBeenCalledWith('123', '00000000-0000-0000-0000-000000000000/2-3-4', [
{ tag: '123', chunkId: 1, chunkSize: 1 },
])
expect(domainEventPublisher.publish).toHaveBeenCalled()
@@ -100,18 +99,32 @@ describe('FinishUploadSession', () => {
it('should finish an upload session for a vault shared file', async () => {
await createUseCase().execute({
resourceRemoteIdentifier: '2-3-4',
ownerUuid: '1-2-3',
ownerType: 'shared-vault',
userUuid: '00000000-0000-0000-0000-000000000000',
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
uploadBytesLimit: 100,
uploadBytesUsed: 0,
})
expect(fileUploader.finishUploadSession).toHaveBeenCalledWith('123', '1-2-3/2-3-4', [
expect(fileUploader.finishUploadSession).toHaveBeenCalledWith('123', '00000000-0000-0000-0000-000000000000/2-3-4', [
{ tag: '123', chunkId: 1, chunkSize: 1 },
])
expect(domainEventPublisher.publish).toHaveBeenCalled()
})
it('should not finish an upload session for a vault shared file if shared vault uuid is invalid', async () => {
const result = await createUseCase().execute({
resourceRemoteIdentifier: '2-3-4',
userUuid: '00000000-0000-0000-0000-000000000000',
sharedVaultUuid: 'invalid',
uploadBytesLimit: 100,
uploadBytesUsed: 0,
})
expect(result.isFailed()).toBeTruthy()
expect(fileUploader.finishUploadSession).not.toHaveBeenCalled()
expect(domainEventPublisher.publish).not.toHaveBeenCalled()
})
it('should not finish an upload session if the file size exceeds storage quota', async () => {
uploadRepository.retrieveUploadChunkResults = jest.fn().mockReturnValue([
{ tag: '123', chunkId: 1, chunkSize: 60 },
@@ -119,18 +132,13 @@ describe('FinishUploadSession', () => {
{ tag: '345', chunkId: 3, chunkSize: 20 },
])
expect(
await createUseCase().execute({
resourceRemoteIdentifier: '2-3-4',
ownerUuid: '1-2-3',
ownerType: 'user',
uploadBytesLimit: 100,
uploadBytesUsed: 20,
}),
).toEqual({
success: false,
message: 'Could not finish upload session. You are out of space.',
const result = await createUseCase().execute({
resourceRemoteIdentifier: '2-3-4',
userUuid: '00000000-0000-0000-0000-000000000000',
uploadBytesLimit: 100,
uploadBytesUsed: 20,
})
expect(result.getError()).toEqual('Could not finish upload session. You are out of space.')
expect(fileUploader.finishUploadSession).not.toHaveBeenCalled()
expect(domainEventPublisher.publish).not.toHaveBeenCalled()
@@ -143,17 +151,13 @@ describe('FinishUploadSession', () => {
{ tag: '345', chunkId: 3, chunkSize: 20 },
])
expect(
await createUseCase().execute({
resourceRemoteIdentifier: '2-3-4',
ownerUuid: '1-2-3',
ownerType: 'user',
uploadBytesLimit: -1,
uploadBytesUsed: 20,
}),
).toEqual({
success: true,
const result = await createUseCase().execute({
resourceRemoteIdentifier: '2-3-4',
userUuid: '00000000-0000-0000-0000-000000000000',
uploadBytesLimit: -1,
uploadBytesUsed: 20,
})
expect(result.isFailed()).toBeFalsy()
expect(fileUploader.finishUploadSession).toHaveBeenCalled()
expect(domainEventPublisher.publish).toHaveBeenCalled()

View File

@@ -1,39 +1,41 @@
import { inject, injectable } from 'inversify'
import { Logger } from 'winston'
import { Result, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
import TYPES from '../../../Bootstrap/Types'
import { UseCaseInterface } from '../UseCaseInterface'
import { FinishUploadSessionDTO } from './FinishUploadSessionDTO'
import { FinishUploadSessionResponse } from './FinishUploadSessionResponse'
import { FileUploaderInterface } from '../../Services/FileUploaderInterface'
import { UploadRepositoryInterface } from '../../Upload/UploadRepositoryInterface'
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
import { DomainEventFactoryInterface } from '../../Event/DomainEventFactoryInterface'
@injectable()
export class FinishUploadSession implements UseCaseInterface {
export class FinishUploadSession implements UseCaseInterface<void> {
constructor(
@inject(TYPES.Files_FileUploader) private fileUploader: FileUploaderInterface,
@inject(TYPES.Files_UploadRepository) private uploadRepository: UploadRepositoryInterface,
@inject(TYPES.Files_DomainEventPublisher) private domainEventPublisher: DomainEventPublisherInterface,
@inject(TYPES.Files_DomainEventFactory) private domainEventFactory: DomainEventFactoryInterface,
@inject(TYPES.Files_Logger) private logger: Logger,
private fileUploader: FileUploaderInterface,
private uploadRepository: UploadRepositoryInterface,
private domainEventPublisher: DomainEventPublisherInterface,
private domainEventFactory: DomainEventFactoryInterface,
) {}
async execute(dto: FinishUploadSessionDTO): Promise<FinishUploadSessionResponse> {
async execute(dto: FinishUploadSessionDTO): Promise<Result<void>> {
try {
this.logger.debug(`Finishing upload session for resource: ${dto.resourceRemoteIdentifier}`)
const userUuidOrError = Uuid.create(dto.userUuid)
if (userUuidOrError.isFailed()) {
return Result.fail(userUuidOrError.getError())
}
const userUuid = userUuidOrError.getValue()
const filePath = `${dto.ownerUuid}/${dto.resourceRemoteIdentifier}`
let sharedVaultUuid: Uuid | undefined
if (dto.sharedVaultUuid !== undefined) {
const sharedVaultUuidOrError = Uuid.create(dto.sharedVaultUuid)
if (sharedVaultUuidOrError.isFailed()) {
return Result.fail(sharedVaultUuidOrError.getError())
}
sharedVaultUuid = sharedVaultUuidOrError.getValue()
}
const filePath = `${sharedVaultUuid ? sharedVaultUuid.value : userUuid.value}/${dto.resourceRemoteIdentifier}`
const uploadId = await this.uploadRepository.retrieveUploadSessionId(filePath)
if (uploadId === undefined) {
this.logger.warn(`Could not find upload session for file path: ${filePath}`)
return {
success: false,
message: 'Could not finish upload session',
}
return Result.fail('Could not finish upload session')
}
const uploadChunkResults = await this.uploadRepository.retrieveUploadChunkResults(uploadId)
@@ -46,46 +48,35 @@ export class FinishUploadSession implements UseCaseInterface {
const userHasUnlimitedStorage = dto.uploadBytesLimit === -1
const remainingSpaceLeft = dto.uploadBytesLimit - dto.uploadBytesUsed
if (!userHasUnlimitedStorage && remainingSpaceLeft < totalFileSize) {
return {
success: false,
message: 'Could not finish upload session. You are out of space.',
}
return Result.fail('Could not finish upload session. You are out of space.')
}
await this.fileUploader.finishUploadSession(uploadId, filePath, uploadChunkResults)
if (dto.ownerType === 'user') {
if (sharedVaultUuid !== undefined) {
await this.domainEventPublisher.publish(
this.domainEventFactory.createFileUploadedEvent({
userUuid: dto.ownerUuid,
filePath: `${dto.ownerUuid}/${dto.resourceRemoteIdentifier}`,
this.domainEventFactory.createSharedVaultFileUploadedEvent({
sharedVaultUuid: sharedVaultUuid.value,
vaultOwnerUuid: userUuid.value,
filePath,
fileName: dto.resourceRemoteIdentifier,
fileByteSize: totalFileSize,
}),
)
} else {
await this.domainEventPublisher.publish(
this.domainEventFactory.createSharedVaultFileUploadedEvent({
sharedVaultUuid: dto.ownerUuid,
filePath: `${dto.ownerUuid}/${dto.resourceRemoteIdentifier}`,
this.domainEventFactory.createFileUploadedEvent({
userUuid: userUuid.value,
filePath,
fileName: dto.resourceRemoteIdentifier,
fileByteSize: totalFileSize,
}),
)
}
return {
success: true,
}
return Result.ok()
} catch (error) {
this.logger.error(
`Could not finish upload session for resource: ${dto.resourceRemoteIdentifier} - ${(error as Error).message}`,
)
return {
success: false,
message: 'Could not finish upload session',
}
return Result.fail('Could not finish upload session')
}
}
}

View File

@@ -1,6 +1,6 @@
export type FinishUploadSessionDTO = {
ownerUuid: string
ownerType: 'user' | 'shared-vault'
userUuid: string
sharedVaultUuid?: string
resourceRemoteIdentifier: string
uploadBytesUsed: number
uploadBytesLimit: number

View File

@@ -1,8 +0,0 @@
export type FinishUploadSessionResponse =
| {
success: true
}
| {
success: false
message: string
}

View File

@@ -80,6 +80,7 @@ describe('RemoveFile', () => {
vaultInput: {
resourceRemoteIdentifier: '2-3-4',
sharedVaultUuid: '1-2-3',
vaultOwnerUuid: '3-4-5',
},
})

View File

@@ -43,6 +43,7 @@ export class RemoveFile implements UseCaseInterface<boolean> {
await this.domainEventPublisher.publish(
this.domainEventFactory.createSharedVaultFileRemovedEvent({
sharedVaultUuid: dto.vaultInput.sharedVaultUuid,
vaultOwnerUuid: dto.vaultInput.vaultOwnerUuid,
filePath: `${dto.vaultInput.sharedVaultUuid}/${dto.vaultInput.resourceRemoteIdentifier}`,
fileName: dto.vaultInput.resourceRemoteIdentifier,
fileByteSize: removedFileSize,

View File

@@ -6,6 +6,7 @@ export interface RemoveFileDTO {
}
vaultInput?: {
sharedVaultUuid: string
vaultOwnerUuid: string
resourceRemoteIdentifier: string
}
}

View File

@@ -1,19 +1,20 @@
import 'reflect-metadata'
import { ValetTokenOperation } from '@standardnotes/security'
import { BadRequestErrorMessageResult } from 'inversify-express-utils/lib/results'
import { Result } from '@standardnotes/domain-core'
import { Logger } from 'winston'
import { Request, Response } from 'express'
import { Writable, Readable } from 'stream'
import { results } from 'inversify-express-utils'
import { CreateUploadSession } from '../../Domain/UseCase/CreateUploadSession/CreateUploadSession'
import { FinishUploadSession } from '../../Domain/UseCase/FinishUploadSession/FinishUploadSession'
import { StreamDownloadFile } from '../../Domain/UseCase/StreamDownloadFile/StreamDownloadFile'
import { UploadFileChunk } from '../../Domain/UseCase/UploadFileChunk/UploadFileChunk'
import { Request, Response } from 'express'
import { Writable, Readable } from 'stream'
import { AnnotatedFilesController } from './AnnotatedFilesController'
import { GetFileMetadata } from '../../Domain/UseCase/GetFileMetadata/GetFileMetadata'
import { results } from 'inversify-express-utils'
import { RemoveFile } from '../../Domain/UseCase/RemoveFile/RemoveFile'
import { ValetTokenOperation } from '@standardnotes/security'
import { BadRequestErrorMessageResult } from 'inversify-express-utils/lib/results'
import { Result } from '@standardnotes/domain-core'
describe('AnnotatedFilesController', () => {
let uploadFileChunk: UploadFileChunk
@@ -26,6 +27,7 @@ describe('AnnotatedFilesController', () => {
let response: Response
let readStream: Readable
const maxChunkBytes = 100_000
let logger: Logger
const createController = () =>
new AnnotatedFilesController(
@@ -36,9 +38,13 @@ describe('AnnotatedFilesController', () => {
getFileMetadata,
removeFile,
maxChunkBytes,
logger,
)
beforeEach(() => {
logger = {} as jest.Mocked<Logger>
logger.error = jest.fn()
readStream = {} as jest.Mocked<Readable>
readStream.pipe = jest.fn().mockReturnValue(new Writable())
@@ -52,7 +58,7 @@ describe('AnnotatedFilesController', () => {
createUploadSession.execute = jest.fn().mockReturnValue({ success: true, uploadId: '123' })
finishUploadSession = {} as jest.Mocked<FinishUploadSession>
finishUploadSession.execute = jest.fn().mockReturnValue({ success: true })
finishUploadSession.execute = jest.fn().mockReturnValue(Result.ok())
getFileMetadata = {} as jest.Mocked<GetFileMetadata>
getFileMetadata.execute = jest.fn().mockReturnValue({ success: true, size: 555_555 })
@@ -233,8 +239,7 @@ describe('AnnotatedFilesController', () => {
expect(finishUploadSession.execute).toHaveBeenCalledWith({
resourceRemoteIdentifier: '2-3-4',
ownerType: 'user',
ownerUuid: '1-2-3',
userUuid: '1-2-3',
})
})
@@ -249,7 +254,7 @@ describe('AnnotatedFilesController', () => {
it('should return bad request if upload session could not be finished', async () => {
response.locals.permittedOperation = ValetTokenOperation.Write
finishUploadSession.execute = jest.fn().mockReturnValue({ success: false })
finishUploadSession.execute = jest.fn().mockReturnValue(Result.fail('Oops'))
const httpResponse = await createController().finishUpload(request, response)
const result = await httpResponse.executeAsync()

View File

@@ -2,6 +2,9 @@ import { BaseHttpController, controller, httpDelete, httpGet, httpPost, results
import { Request, Response } from 'express'
import { inject } from 'inversify'
import { Writable } from 'stream'
import { ValetTokenOperation } from '@standardnotes/security'
import { Logger } from 'winston'
import TYPES from '../../Bootstrap/Types'
import { UploadFileChunk } from '../../Domain/UseCase/UploadFileChunk/UploadFileChunk'
import { StreamDownloadFile } from '../../Domain/UseCase/StreamDownloadFile/StreamDownloadFile'
@@ -9,7 +12,6 @@ import { CreateUploadSession } from '../../Domain/UseCase/CreateUploadSession/Cr
import { FinishUploadSession } from '../../Domain/UseCase/FinishUploadSession/FinishUploadSession'
import { GetFileMetadata } from '../../Domain/UseCase/GetFileMetadata/GetFileMetadata'
import { RemoveFile } from '../../Domain/UseCase/RemoveFile/RemoveFile'
import { ValetTokenOperation } from '@standardnotes/security'
@controller('/v1/files', TYPES.Files_ValetTokenAuthMiddleware)
export class AnnotatedFilesController extends BaseHttpController {
@@ -21,6 +23,7 @@ export class AnnotatedFilesController extends BaseHttpController {
@inject(TYPES.Files_GetFileMetadata) private getFileMetadata: GetFileMetadata,
@inject(TYPES.Files_RemoveFile) private removeFile: RemoveFile,
@inject(TYPES.Files_MAX_CHUNK_BYTES) private maxChunkBytes: number,
@inject(TYPES.Files_Logger) private logger: Logger,
) {
super()
}
@@ -85,15 +88,16 @@ export class AnnotatedFilesController extends BaseHttpController {
}
const result = await this.finishUploadSession.execute({
ownerUuid: response.locals.userUuid,
ownerType: 'user',
userUuid: response.locals.userUuid,
resourceRemoteIdentifier: response.locals.permittedResources[0].remoteIdentifier,
uploadBytesLimit: response.locals.uploadBytesLimit,
uploadBytesUsed: response.locals.uploadBytesUsed,
})
if (!result.success) {
return this.badRequest(result.message)
if (result.isFailed()) {
this.logger.error(result.getError())
return this.badRequest(result.getError())
}
return this.json({ success: true, message: 'File uploaded successfully' })

View File

@@ -3,6 +3,7 @@ import { Request, Response } from 'express'
import { inject } from 'inversify'
import { Writable } from 'stream'
import { SharedVaultValetTokenData, ValetTokenOperation } from '@standardnotes/security'
import { Logger } from 'winston'
import TYPES from '../../Bootstrap/Types'
import { CreateUploadSession } from '../../Domain/UseCase/CreateUploadSession/CreateUploadSession'
@@ -24,6 +25,7 @@ export class AnnotatedSharedVaultFilesController extends BaseHttpController {
@inject(TYPES.Files_RemoveFile) private removeFile: RemoveFile,
@inject(TYPES.Files_MoveFile) private moveFile: MoveFile,
@inject(TYPES.Files_MAX_CHUNK_BYTES) private maxChunkBytes: number,
@inject(TYPES.Files_Logger) private logger: Logger,
) {
super()
}
@@ -120,15 +122,17 @@ export class AnnotatedSharedVaultFilesController extends BaseHttpController {
}
const result = await this.finishUploadSession.execute({
ownerUuid: locals.sharedVaultUuid,
ownerType: 'shared-vault',
userUuid: locals.vaultOwnerUuid,
sharedVaultUuid: locals.sharedVaultUuid,
resourceRemoteIdentifier: locals.remoteIdentifier,
uploadBytesLimit: locals.uploadBytesLimit,
uploadBytesUsed: locals.uploadBytesUsed,
})
if (!result.success) {
return this.badRequest(result.message)
if (result.isFailed()) {
this.logger.error(result.getError())
return this.badRequest(result.getError())
}
return this.json({ success: true, message: 'File uploaded successfully' })
@@ -147,6 +151,7 @@ export class AnnotatedSharedVaultFilesController extends BaseHttpController {
const result = await this.removeFile.execute({
vaultInput: {
sharedVaultUuid: locals.sharedVaultUuid,
vaultOwnerUuid: locals.vaultOwnerUuid,
resourceRemoteIdentifier: locals.remoteIdentifier,
},
})

View File

@@ -63,6 +63,7 @@ export class SharedVaultValetTokenAuthMiddleware extends BaseMiddleware {
const whitelistedData: SharedVaultValetTokenData = {
sharedVaultUuid: valetTokenData.sharedVaultUuid,
vaultOwnerUuid: valetTokenData.vaultOwnerUuid,
remoteIdentifier: valetTokenData.remoteIdentifier,
permittedOperation: valetTokenData.permittedOperation,
uploadBytesUsed: valetTokenData.uploadBytesUsed,

View File

@@ -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

View File

@@ -3,6 +3,90 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [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.13.33](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.13.32...@standardnotes/home-server@1.13.33) (2023-08-08)
**Note:** Version bump only for package @standardnotes/home-server
## [1.13.32](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.13.31...@standardnotes/home-server@1.13.32) (2023-08-07)
**Note:** Version bump only for package @standardnotes/home-server

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/home-server",
"version": "1.13.32",
"version": "1.14.0",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -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.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.26.2](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.26.1...@standardnotes/revisions-server@1.26.2) (2023-08-03)
**Note:** Version bump only for package @standardnotes/revisions-server

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/revisions-server",
"version": "1.26.2",
"version": "1.26.8",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -3,6 +3,32 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [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.20.17](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.20.16...@standardnotes/scheduler-server@1.20.17) (2023-08-03)
**Note:** Version bump only for package @standardnotes/scheduler-server

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/scheduler-server",
"version": "1.20.17",
"version": "1.20.23",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -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

View File

@@ -3,6 +3,12 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.9.0](https://github.com/standardnotes/server/compare/@standardnotes/security@1.8.1...@standardnotes/security@1.9.0) (2023-08-08)
### Features
* update storage quota used for user based on shared vault files ([#689](https://github.com/standardnotes/server/issues/689)) ([5311e74](https://github.com/standardnotes/server/commit/5311e7426617da6fc75593dd0fcbff589ca4fc22))
## [1.8.1](https://github.com/standardnotes/server/compare/@standardnotes/security@1.8.0...@standardnotes/security@1.8.1) (2023-06-30)
**Note:** Version bump only for package @standardnotes/security

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/security",
"version": "1.8.1",
"version": "1.9.0",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -3,6 +3,7 @@ import { SharedVaultMoveType } from './SharedVaultMoveType'
export interface SharedVaultValetTokenData {
sharedVaultUuid: string
vaultOwnerUuid: string
permittedOperation: ValetTokenOperation
remoteIdentifier: string
unencryptedFileSize?: number

View File

@@ -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

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/settings",
"version": "1.21.21",
"version": "1.21.25",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -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=

View File

@@ -3,6 +3,117 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [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
* update storage quota used for user based on shared vault files ([#689](https://github.com/standardnotes/syncing-server-js/issues/689)) ([5311e74](https://github.com/standardnotes/syncing-server-js/commit/5311e7426617da6fc75593dd0fcbff589ca4fc22))
## [1.76.1](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.76.0...@standardnotes/syncing-server@1.76.1) (2023-08-08)
### Bug Fixes
* **syncing-server:** race condition when adding admin user to newly created shared vault ([#688](https://github.com/standardnotes/syncing-server-js/issues/688)) ([3bd1547](https://github.com/standardnotes/syncing-server-js/commit/3bd1547ce3f599306f3942ce0a46f98cebfd33a4))
# [1.76.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.75.4...@standardnotes/syncing-server@1.76.0) (2023-08-07)
### Features

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/syncing-server",
"version": "1.76.0",
"version": "1.80.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",

View File

@@ -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,16 @@ 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'
export class ContainerConfigLoader {
private readonly DEFAULT_CONTENT_SIZE_TRANSFER_LIMIT = 10_000_000
@@ -206,6 +208,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 +287,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 +323,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 +382,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 +471,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 +549,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 +561,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 +572,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 +587,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 +602,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 +631,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 +641,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 +652,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 +768,13 @@ export class ContainerConfigLoader {
container.get(TYPES.Sync_DeleteMessage),
),
)
container
.bind<UpdateStorageQuotaUsedInSharedVault>(TYPES.Sync_UpdateStorageQuotaUsedInSharedVault)
.toConstantValue(
new UpdateStorageQuotaUsedInSharedVault(
container.get<SharedVaultRepositoryInterface>(TYPES.Sync_SharedVaultRepository),
),
)
// Services
container
@@ -773,91 +802,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 +915,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))
}

View File

@@ -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,42 @@ 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],
synchronize: true,
})
return this._secondaryDataSource
}
get dataSource(): DataSource {

View File

@@ -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,15 @@ 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'),
// 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 +126,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'),

View File

@@ -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>

View File

@@ -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')

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