Compare commits

..

45 Commits

Author SHA1 Message Date
standardci
2396053bc1 chore(release): publish new version
- @standardnotes/auth-server@1.123.2
 - @standardnotes/files-server@1.19.7
 - @standardnotes/home-server@1.11.41
 - @standardnotes/revisions-server@1.24.1
 - @standardnotes/sncrypto-node@1.15.3
 - @standardnotes/syncing-server@1.63.1
 - @standardnotes/websockets-server@1.10.1
2023-07-12 12:42:08 +00:00
Karol Sójko
17fd12305e chore(deps): upgrade @standardnotes deps 2023-07-12 14:25:23 +02:00
standardci
425ea4374d chore(release): publish new version
- @standardnotes/auth-server@1.123.1
 - @standardnotes/home-server@1.11.40
2023-07-12 09:20:55 +00:00
Karol Sójko
c076c3c74a chore(deps): upgrade @standardnotes/features 2023-07-12 11:05:44 +02:00
standardci
547cdfd8ec chore(release): publish new version
- @standardnotes/analytics@2.24.9
 - @standardnotes/api-gateway@1.65.6
 - @standardnotes/auth-server@1.123.0
 - @standardnotes/common@1.50.0
 - @standardnotes/domain-core@1.22.0
 - @standardnotes/event-store@1.11.6
 - @standardnotes/files-server@1.19.6
 - @standardnotes/home-server@1.11.39
 - @standardnotes/revisions-server@1.24.0
 - @standardnotes/scheduler-server@1.20.8
 - @standardnotes/settings@1.21.13
 - @standardnotes/syncing-server@1.63.0
 - @standardnotes/websockets-server@1.10.0
2023-07-12 08:34:32 +00:00
Karol Sójko
a0af8f0025 feat: domain items (#655)
* feat: content type as a value object

* feat: turn items into domain entities

* fix: update @standardnotes/api

* fix(syncing-server): bindings order
2023-07-12 10:19:22 +02:00
standardci
c970b1ea68 chore(release): publish new version
- @standardnotes/home-server@1.11.38
 - @standardnotes/syncing-server@1.62.1
2023-07-11 15:22:11 +00:00
Karol Sójko
4d1e2dec26 fix: unify use case usage (#654) 2023-07-11 17:05:45 +02:00
standardci
108408a944 chore(release): publish new version
- @standardnotes/home-server@1.11.37
 - @standardnotes/syncing-server@1.62.0
2023-07-10 12:37:48 +00:00
Karol Sójko
18d07d431f feat: messages controller. (#653)
Co-authored-by: Mo <mo@standardnotes.com>
2023-07-10 14:21:59 +02:00
standardci
cbc024f67a chore(release): publish new version
- @standardnotes/home-server@1.11.36
 - @standardnotes/syncing-server@1.61.0
2023-07-10 11:54:54 +00:00
Karol Sójko
55ec5970da feat: message operations use cases. (#652)
Co-authored-by: Mo <mo@standardnotes.com>
2023-07-10 13:38:07 +02:00
standardci
58bdca6659 chore(release): publish new version
- @standardnotes/home-server@1.11.35
 - @standardnotes/syncing-server@1.60.0
2023-07-10 11:25:21 +00:00
Karol Sójko
ef49b0d3f8 feat: sending messages. (#651)
* feat: sending messages.

Co-authored-by: Mo <mo@standardnotes.com>

* fix: messages repository.

Co-authored-by: Mo <mo@standardnotes.com>

---------

Co-authored-by: Mo <mo@standardnotes.com>
2023-07-10 13:10:31 +02:00
standardci
9cb691e5ad chore(release): publish new version
- @standardnotes/home-server@1.11.34
 - @standardnotes/syncing-server@1.59.1
2023-07-10 10:58:38 +00:00
Karol Sójko
04d09582d4 fix: restructure use cases (#650) 2023-07-10 12:43:20 +02:00
standardci
8f90dc172b chore(release): publish new version
- @standardnotes/home-server@1.11.33
 - @standardnotes/syncing-server@1.59.0
2023-07-10 10:27:47 +00:00
Karol Sójko
f759261919 feat: user to user message model. (#649)
Co-authored-by: Mo <mo@standardnotes.com>
2023-07-10 12:10:25 +02:00
standardci
2606f6d929 chore(release): publish new version
- @standardnotes/analytics@2.24.8
 - @standardnotes/api-gateway@1.65.5
 - @standardnotes/auth-server@1.122.2
 - @standardnotes/domain-core@1.21.1
 - @standardnotes/domain-events-infra@1.12.9
 - @standardnotes/domain-events@2.113.1
 - @standardnotes/event-store@1.11.5
 - @standardnotes/files-server@1.19.5
 - @standardnotes/home-server@1.11.32
 - @standardnotes/revisions-server@1.23.9
 - @standardnotes/scheduler-server@1.20.7
 - @standardnotes/settings@1.21.12
 - @standardnotes/syncing-server@1.58.1
 - @standardnotes/websockets-server@1.9.8
2023-07-07 13:30:56 +00:00
Karol Sójko
c288e5d8dc fix: transfer notifications from auth to syncing-server. (#648)
* fix: transfer notifications from auth to syncing-server.

Co-authored-by: Mo <mo@standardnotes.com>

* fix: add notification to data source init

---------

Co-authored-by: Mo <mo@standardnotes.com>
2023-07-07 15:12:27 +02:00
standardci
4b76d4b71e chore(release): publish new version
- @standardnotes/home-server@1.11.31
 - @standardnotes/syncing-server@1.58.0
2023-07-07 11:53:44 +00:00
Karol Sójko
72310130d2 feat: shared vault invites controller and use cases (#647)
* feat: get shared vault invites sent by user.

Co-authored-by: Mo <mo@standardnotes.com>

* feat: shared vault invites controller.

Co-authored-by: Mo <mo@standardnotes.com>

---------

Co-authored-by: Mo <mo@standardnotes.com>
2023-07-07 13:39:43 +02:00
standardci
f9e51ef06e chore(release): publish new version
- @standardnotes/home-server@1.11.30
 - @standardnotes/syncing-server@1.57.0
2023-07-06 12:09:05 +00:00
Karol Sójko
92a5eb0d98 feat: remove inbound shared vault invites. (#646)
Co-authored-by: Mo <mo@standardnotes.com>
2023-07-06 13:53:52 +02:00
standardci
77d2ea1a1f chore(release): publish new version
- @standardnotes/home-server@1.11.29
 - @standardnotes/syncing-server@1.56.0
2023-07-06 11:06:03 +00:00
Karol Sójko
92f96ddb84 feat: accept and decline shared vault invites (#645)
* feat: accept shared vault invite.

Co-authored-by: Mo <mo@standardnotes.com>

* feat: decline shared vault invite.

Co-authored-by: Mo <mo@standardnotes.com>

---------

Co-authored-by: Mo <mo@standardnotes.com>
2023-07-06 12:47:48 +02:00
standardci
15a914e25e chore(release): publish new version
- @standardnotes/home-server@1.11.28
 - @standardnotes/syncing-server@1.55.0
2023-07-06 10:12:49 +00:00
Karol Sójko
912a29d091 feat: update shared vault invite. (#644)
Co-authored-by: Mo <mo@standardnotes.com>
2023-07-06 11:58:45 +02:00
Karol Sójko
b2c32ce70e feat: shared vault users controller. (#643)
Co-authored-by: Mo <mo@standardnotes.com>
2023-07-06 11:41:36 +02:00
standardci
ed1a708c40 chore(release): publish new version
- @standardnotes/analytics@2.24.7
 - @standardnotes/api-gateway@1.65.4
 - @standardnotes/auth-server@1.122.1
 - @standardnotes/domain-core@1.21.0
 - @standardnotes/event-store@1.11.4
 - @standardnotes/files-server@1.19.4
 - @standardnotes/home-server@1.11.27
 - @standardnotes/revisions-server@1.23.8
 - @standardnotes/scheduler-server@1.20.6
 - @standardnotes/settings@1.21.11
 - @standardnotes/syncing-server@1.54.0
 - @standardnotes/websockets-server@1.9.7
2023-07-06 09:34:50 +00:00
Karol Sójko
e905128d45 feat: getting shared vault users and removing shared vault user (#642)
* feat: getting shared vault users.

Co-authored-by: Mo <mo@standardnotes.com>

* feat: removing shared vault user.

Co-authored-by: Mo <mo@standardnotes.com>

---------

Co-authored-by: Mo <mo@standardnotes.com>
2023-07-06 11:18:06 +02:00
standardci
fd598f372a chore(release): publish new version
- @standardnotes/home-server@1.11.26
 - @standardnotes/syncing-server@1.53.0
2023-07-05 13:33:44 +00:00
Karol Sójko
7a3946a9e2 feat: http controllers for shared vaults. (#641)
Co-authored-by: Mo <mo@standardnotes.com>
2023-07-05 15:17:43 +02:00
standardci
cbdd2584d0 chore(release): publish new version
- @standardnotes/analytics@2.24.6
 - @standardnotes/api-gateway@1.65.3
 - @standardnotes/auth-server@1.122.0
 - @standardnotes/domain-core@1.20.0
 - @standardnotes/domain-events-infra@1.12.8
 - @standardnotes/domain-events@2.113.0
 - @standardnotes/event-store@1.11.3
 - @standardnotes/files-server@1.19.3
 - @standardnotes/home-server@1.11.25
 - @standardnotes/revisions-server@1.23.7
 - @standardnotes/scheduler-server@1.20.5
 - @standardnotes/settings@1.21.10
 - @standardnotes/syncing-server@1.52.0
 - @standardnotes/websockets-server@1.9.6
2023-07-05 11:59:55 +00:00
Karol Sójko
f3161c2712 feat: deleting shared vaults. (#640)
Co-authored-by: Mo <mo@standardnotes.com>
2023-07-05 13:45:49 +02:00
standardci
148542dd5a chore(release): publish new version
- @standardnotes/home-server@1.11.24
 - @standardnotes/syncing-server@1.51.0
2023-07-05 09:17:53 +00:00
Karol Sójko
d2b2c339f2 feat: add getting shared vaults for a user (#639) 2023-07-05 11:01:21 +02:00
standardci
d2578c48f0 chore(release): publish new version
- @standardnotes/auth-server@1.121.0
 - @standardnotes/home-server@1.11.23
2023-07-05 08:21:19 +00:00
Karol Sójko
fecfd54728 feat(auth): add notifications model (#638) 2023-07-05 10:03:01 +02:00
Karol Sójko
17e4162d3e Revert "fix: unplug node-gyp based deps (#637)"
This reverts commit 742209d773.
2023-07-04 12:56:10 +02:00
Karol Sójko
742209d773 fix: unplug node-gyp based deps (#637) 2023-07-04 10:42:44 +02:00
standardci
1fa4b7cf27 chore(release): publish new version
- @standardnotes/home-server@1.11.22
 - @standardnotes/syncing-server@1.50.0
2023-07-03 17:55:21 +00:00
Karol Sójko
5dc5507039 feat: add invite users to a shared vault. (#636)
Co-authored-by: Mo <mo@standardnotes.com>
2023-07-03 19:40:36 +02:00
standardci
3035a20b9f chore(release): publish new version
- @standardnotes/home-server@1.11.21
 - @standardnotes/syncing-server@1.49.0
2023-07-03 16:58:01 +00:00
Karol Sójko
04b3bb034f feat: add creating shared vault file valet tokens. (#635)
Co-authored-by: Mo <mo@standardnotes.com>
2023-07-03 18:43:32 +02:00
268 changed files with 7949 additions and 2426 deletions

94
.pnp.cjs generated
View File

@@ -4560,17 +4560,16 @@ const RAW_RUNTIME_STATE =
}]\
]],\
["@standardnotes/api", [\
["npm:1.26.10", {\
"packageLocation": "./.yarn/cache/@standardnotes-api-npm-1.26.10-f6165cafd3-3c3561aec8.zip/node_modules/@standardnotes/api/",\
["npm:1.26.26", {\
"packageLocation": "./.yarn/cache/@standardnotes-api-npm-1.26.26-4338a5fe92-db41aedfa3.zip/node_modules/@standardnotes/api/",\
"packageDependencies": [\
["@standardnotes/api", "npm:1.26.10"],\
["@standardnotes/api", "npm:1.26.26"],\
["@standardnotes/common", "workspace:packages/common"],\
["@standardnotes/domain-core", "workspace:packages/domain-core"],\
["@standardnotes/encryption", "npm:1.21.38"],\
["@standardnotes/models", "npm:1.45.5"],\
["@standardnotes/responses", "npm:1.13.24"],\
["@standardnotes/models", "npm:1.46.8"],\
["@standardnotes/responses", "npm:1.13.27"],\
["@standardnotes/security", "workspace:packages/security"],\
["@standardnotes/utils", "npm:1.16.5"],\
["@standardnotes/utils", "npm:1.17.5"],\
["reflect-metadata", "npm:0.1.13"]\
],\
"linkType": "HARD"\
@@ -4635,17 +4634,17 @@ const RAW_RUNTIME_STATE =
["@newrelic/winston-enricher", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:4.0.1"],\
["@simplewebauthn/server", "npm:7.2.0"],\
["@simplewebauthn/typescript-types", "npm:7.0.0"],\
["@standardnotes/api", "npm:1.26.10"],\
["@standardnotes/api", "npm:1.26.26"],\
["@standardnotes/common", "workspace:packages/common"],\
["@standardnotes/domain-core", "workspace:packages/domain-core"],\
["@standardnotes/domain-events", "workspace:packages/domain-events"],\
["@standardnotes/domain-events-infra", "workspace:packages/domain-events-infra"],\
["@standardnotes/features", "npm:1.59.5"],\
["@standardnotes/features", "npm:1.59.7"],\
["@standardnotes/predicates", "workspace:packages/predicates"],\
["@standardnotes/responses", "npm:1.13.24"],\
["@standardnotes/responses", "npm:1.13.27"],\
["@standardnotes/security", "workspace:packages/security"],\
["@standardnotes/settings", "workspace:packages/settings"],\
["@standardnotes/sncrypto-common", "npm:1.13.3"],\
["@standardnotes/sncrypto-common", "npm:1.13.4"],\
["@standardnotes/sncrypto-node", "workspace:packages/sncrypto-node"],\
["@standardnotes/time", "workspace:packages/time"],\
["@types/bcryptjs", "npm:2.4.2"],\
@@ -4781,21 +4780,6 @@ const RAW_RUNTIME_STATE =
"linkType": "SOFT"\
}]\
]],\
["@standardnotes/encryption", [\
["npm:1.21.38", {\
"packageLocation": "./.yarn/cache/@standardnotes-encryption-npm-1.21.38-d08c3d4766-1393840523.zip/node_modules/@standardnotes/encryption/",\
"packageDependencies": [\
["@standardnotes/encryption", "npm:1.21.38"],\
["@standardnotes/common", "workspace:packages/common"],\
["@standardnotes/models", "npm:1.45.5"],\
["@standardnotes/responses", "npm:1.13.24"],\
["@standardnotes/sncrypto-common", "npm:1.13.3"],\
["@standardnotes/utils", "npm:1.16.5"],\
["reflect-metadata", "npm:0.1.13"]\
],\
"linkType": "HARD"\
}]\
]],\
["@standardnotes/event-store", [\
["workspace:packages/event-store", {\
"packageLocation": "./packages/event-store/",\
@@ -4831,10 +4815,10 @@ const RAW_RUNTIME_STATE =
}]\
]],\
["@standardnotes/features", [\
["npm:1.59.5", {\
"packageLocation": "./.yarn/cache/@standardnotes-features-npm-1.59.5-83c83acde9-173b1f5d52.zip/node_modules/@standardnotes/features/",\
["npm:1.59.7", {\
"packageLocation": "./.yarn/cache/@standardnotes-features-npm-1.59.7-27c3e5296e-1632d64cc1.zip/node_modules/@standardnotes/features/",\
"packageDependencies": [\
["@standardnotes/features", "npm:1.59.5"],\
["@standardnotes/features", "npm:1.59.7"],\
["@standardnotes/common", "workspace:packages/common"],\
["@standardnotes/domain-core", "workspace:packages/domain-core"],\
["@standardnotes/security", "workspace:packages/security"],\
@@ -4855,7 +4839,7 @@ const RAW_RUNTIME_STATE =
["@standardnotes/domain-events", "workspace:packages/domain-events"],\
["@standardnotes/domain-events-infra", "workspace:packages/domain-events-infra"],\
["@standardnotes/security", "workspace:packages/security"],\
["@standardnotes/sncrypto-common", "npm:1.13.3"],\
["@standardnotes/sncrypto-common", "npm:1.13.4"],\
["@standardnotes/sncrypto-node", "workspace:packages/sncrypto-node"],\
["@standardnotes/time", "workspace:packages/time"],\
["@types/connect-busboy", "npm:1.0.0"],\
@@ -4935,14 +4919,16 @@ const RAW_RUNTIME_STATE =
}]\
]],\
["@standardnotes/models", [\
["npm:1.45.5", {\
"packageLocation": "./.yarn/cache/@standardnotes-models-npm-1.45.5-29326e959c-15f26c11b2.zip/node_modules/@standardnotes/models/",\
["npm:1.46.8", {\
"packageLocation": "./.yarn/cache/@standardnotes-models-npm-1.46.8-bc0390832e-8404340f27.zip/node_modules/@standardnotes/models/",\
"packageDependencies": [\
["@standardnotes/models", "npm:1.45.5"],\
["@standardnotes/models", "npm:1.46.8"],\
["@standardnotes/common", "workspace:packages/common"],\
["@standardnotes/features", "npm:1.59.5"],\
["@standardnotes/responses", "npm:1.13.24"],\
["@standardnotes/utils", "npm:1.16.5"],\
["@standardnotes/domain-core", "workspace:packages/domain-core"],\
["@standardnotes/features", "npm:1.59.7"],\
["@standardnotes/responses", "npm:1.13.27"],\
["@standardnotes/sncrypto-common", "npm:1.13.4"],\
["@standardnotes/utils", "npm:1.17.5"],\
["lodash", "npm:4.17.21"]\
],\
"linkType": "HARD"\
@@ -4967,12 +4953,12 @@ const RAW_RUNTIME_STATE =
}]\
]],\
["@standardnotes/responses", [\
["npm:1.13.24", {\
"packageLocation": "./.yarn/cache/@standardnotes-responses-npm-1.13.24-3b4167c7ea-3bcfee90f0.zip/node_modules/@standardnotes/responses/",\
["npm:1.13.27", {\
"packageLocation": "./.yarn/cache/@standardnotes-responses-npm-1.13.27-829dec3e6e-9bf55e5f02.zip/node_modules/@standardnotes/responses/",\
"packageDependencies": [\
["@standardnotes/responses", "npm:1.13.24"],\
["@standardnotes/responses", "npm:1.13.27"],\
["@standardnotes/common", "workspace:packages/common"],\
["@standardnotes/features", "npm:1.59.5"],\
["@standardnotes/features", "npm:1.59.7"],\
["@standardnotes/security", "workspace:packages/security"],\
["reflect-metadata", "npm:0.1.13"]\
],\
@@ -4987,12 +4973,12 @@ const RAW_RUNTIME_STATE =
["@aws-sdk/client-s3", "npm:3.342.0"],\
["@aws-sdk/client-sqs", "npm:3.342.0"],\
["@newrelic/winston-enricher", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:4.0.1"],\
["@standardnotes/api", "npm:1.26.10"],\
["@standardnotes/api", "npm:1.26.26"],\
["@standardnotes/common", "workspace:packages/common"],\
["@standardnotes/domain-core", "workspace:packages/domain-core"],\
["@standardnotes/domain-events", "workspace:packages/domain-events"],\
["@standardnotes/domain-events-infra", "workspace:packages/domain-events-infra"],\
["@standardnotes/responses", "npm:1.13.24"],\
["@standardnotes/responses", "npm:1.13.27"],\
["@standardnotes/security", "workspace:packages/security"],\
["@standardnotes/time", "workspace:packages/time"],\
["@types/cors", "npm:2.8.13"],\
@@ -5129,10 +5115,10 @@ const RAW_RUNTIME_STATE =
}]\
]],\
["@standardnotes/sncrypto-common", [\
["npm:1.13.3", {\
"packageLocation": "./.yarn/cache/@standardnotes-sncrypto-common-npm-1.13.3-97ef3850ce-a73af90962.zip/node_modules/@standardnotes/sncrypto-common/",\
["npm:1.13.4", {\
"packageLocation": "./.yarn/cache/@standardnotes-sncrypto-common-npm-1.13.4-3186513fa6-48e0e207f2.zip/node_modules/@standardnotes/sncrypto-common/",\
"packageDependencies": [\
["@standardnotes/sncrypto-common", "npm:1.13.3"],\
["@standardnotes/sncrypto-common", "npm:1.13.4"],\
["reflect-metadata", "npm:0.1.13"]\
],\
"linkType": "HARD"\
@@ -5143,7 +5129,7 @@ const RAW_RUNTIME_STATE =
"packageLocation": "./packages/sncrypto-node/",\
"packageDependencies": [\
["@standardnotes/sncrypto-node", "workspace:packages/sncrypto-node"],\
["@standardnotes/sncrypto-common", "npm:1.13.3"],\
["@standardnotes/sncrypto-common", "npm:1.13.4"],\
["@types/jest", "npm:29.5.2"],\
["@types/node", "npm:20.2.5"],\
["@typescript-eslint/eslint-plugin", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:5.59.8"],\
@@ -5171,12 +5157,12 @@ const RAW_RUNTIME_STATE =
["@aws-sdk/client-sns", "npm:3.342.0"],\
["@aws-sdk/client-sqs", "npm:3.342.0"],\
["@newrelic/winston-enricher", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:4.0.1"],\
["@standardnotes/api", "npm:1.26.10"],\
["@standardnotes/api", "npm:1.26.26"],\
["@standardnotes/common", "workspace:packages/common"],\
["@standardnotes/domain-core", "workspace:packages/domain-core"],\
["@standardnotes/domain-events", "workspace:packages/domain-events"],\
["@standardnotes/domain-events-infra", "workspace:packages/domain-events-infra"],\
["@standardnotes/responses", "npm:1.13.24"],\
["@standardnotes/responses", "npm:1.13.27"],\
["@standardnotes/security", "workspace:packages/security"],\
["@standardnotes/settings", "workspace:packages/settings"],\
["@standardnotes/sncrypto-node", "workspace:packages/sncrypto-node"],\
@@ -5247,10 +5233,10 @@ const RAW_RUNTIME_STATE =
}]\
]],\
["@standardnotes/utils", [\
["npm:1.16.5", {\
"packageLocation": "./.yarn/cache/@standardnotes-utils-npm-1.16.5-47f537f49f-d5caa7181f.zip/node_modules/@standardnotes/utils/",\
["npm:1.17.5", {\
"packageLocation": "./.yarn/cache/@standardnotes-utils-npm-1.17.5-210b60222d-47e8520174.zip/node_modules/@standardnotes/utils/",\
"packageDependencies": [\
["@standardnotes/utils", "npm:1.16.5"],\
["@standardnotes/utils", "npm:1.17.5"],\
["@standardnotes/common", "workspace:packages/common"],\
["dompurify", "npm:2.4.5"],\
["lodash", "npm:4.17.21"],\
@@ -5266,14 +5252,14 @@ const RAW_RUNTIME_STATE =
["@standardnotes/websockets-server", "workspace:packages/websockets"],\
["@aws-sdk/client-sqs", "npm:3.342.0"],\
["@newrelic/winston-enricher", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:4.0.1"],\
["@standardnotes/api", "npm:1.26.10"],\
["@standardnotes/api", "npm:1.26.26"],\
["@standardnotes/common", "workspace:packages/common"],\
["@standardnotes/domain-core", "workspace:packages/domain-core"],\
["@standardnotes/domain-events", "workspace:packages/domain-events"],\
["@standardnotes/domain-events-infra", "workspace:packages/domain-events-infra"],\
["@standardnotes/responses", "npm:1.13.24"],\
["@standardnotes/responses", "npm:1.13.27"],\
["@standardnotes/security", "workspace:packages/security"],\
["@standardnotes/utils", "npm:1.16.5"],\
["@standardnotes/utils", "npm:1.17.5"],\
["@types/cors", "npm:2.8.13"],\
["@types/express", "npm:4.17.17"],\
["@types/ioredis", "npm:5.0.0"],\

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.
## [2.24.9](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.24.8...@standardnotes/analytics@2.24.9) (2023-07-12)
**Note:** Version bump only for package @standardnotes/analytics
## [2.24.8](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.24.7...@standardnotes/analytics@2.24.8) (2023-07-07)
**Note:** Version bump only for package @standardnotes/analytics
## [2.24.7](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.24.6...@standardnotes/analytics@2.24.7) (2023-07-06)
**Note:** Version bump only for package @standardnotes/analytics
## [2.24.6](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.24.5...@standardnotes/analytics@2.24.6) (2023-07-05)
**Note:** Version bump only for package @standardnotes/analytics
## [2.24.5](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.24.4...@standardnotes/analytics@2.24.5) (2023-06-30)
**Note:** Version bump only for package @standardnotes/analytics

View File

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

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.65.6](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.65.5...@standardnotes/api-gateway@1.65.6) (2023-07-12)
**Note:** Version bump only for package @standardnotes/api-gateway
## [1.65.5](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.65.4...@standardnotes/api-gateway@1.65.5) (2023-07-07)
**Note:** Version bump only for package @standardnotes/api-gateway
## [1.65.4](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.65.3...@standardnotes/api-gateway@1.65.4) (2023-07-06)
**Note:** Version bump only for package @standardnotes/api-gateway
## [1.65.3](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.65.2...@standardnotes/api-gateway@1.65.3) (2023-07-05)
**Note:** Version bump only for package @standardnotes/api-gateway
## [1.65.2](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.65.1...@standardnotes/api-gateway@1.65.2) (2023-06-30)
**Note:** Version bump only for package @standardnotes/api-gateway

View File

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

View File

@@ -3,6 +3,42 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.123.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.123.1...@standardnotes/auth-server@1.123.2) (2023-07-12)
**Note:** Version bump only for package @standardnotes/auth-server
## [1.123.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.123.0...@standardnotes/auth-server@1.123.1) (2023-07-12)
**Note:** Version bump only for package @standardnotes/auth-server
# [1.123.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.122.2...@standardnotes/auth-server@1.123.0) (2023-07-12)
### Features
* domain items ([#655](https://github.com/standardnotes/server/issues/655)) ([a0af8f0](https://github.com/standardnotes/server/commit/a0af8f00252e1219e58cb7e066c11a8e71692e9d))
## [1.122.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.122.1...@standardnotes/auth-server@1.122.2) (2023-07-07)
### Bug Fixes
* transfer notifications from auth to syncing-server. ([#648](https://github.com/standardnotes/server/issues/648)) ([c288e5d](https://github.com/standardnotes/server/commit/c288e5d8dc54778a96a9fc33e3c9cae00583fade))
## [1.122.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.122.0...@standardnotes/auth-server@1.122.1) (2023-07-06)
**Note:** Version bump only for package @standardnotes/auth-server
# [1.122.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.121.0...@standardnotes/auth-server@1.122.0) (2023-07-05)
### Features
* deleting shared vaults. ([#640](https://github.com/standardnotes/server/issues/640)) ([f3161c2](https://github.com/standardnotes/server/commit/f3161c271296159331639814b2dbb2e566cc54c9))
# [1.121.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.120.2...@standardnotes/auth-server@1.121.0) (2023-07-05)
### Features
* **auth:** add notifications model ([#638](https://github.com/standardnotes/server/issues/638)) ([fecfd54](https://github.com/standardnotes/server/commit/fecfd5472824b5adae708db95d351e4ad65ee87b))
## [1.120.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.120.1...@standardnotes/auth-server@1.120.2) (2023-06-30)
**Note:** Version bump only for package @standardnotes/auth-server

Binary file not shown.

View File

@@ -0,0 +1,16 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class AddNotifications1688540448427 implements MigrationInterface {
name = 'AddNotifications1688540448427'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
'CREATE TABLE `notifications` (`uuid` varchar(36) NOT NULL, `user_uuid` varchar(36) NOT NULL, `type` varchar(36) NOT NULL, `payload` text NOT NULL, `created_at_timestamp` bigint NOT NULL, `updated_at_timestamp` bigint NOT NULL, INDEX `index_notifications_on_user_uuid` (`user_uuid`), PRIMARY KEY (`uuid`)) ENGINE=InnoDB',
)
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('DROP INDEX `index_notifications_on_user_uuid` ON `notifications`')
await queryRunner.query('DROP TABLE `notifications`')
}
}

View File

@@ -0,0 +1,16 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class RemoveNotifications1688540448428 implements MigrationInterface {
name = 'RemoveNotifications1688540448428'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('DROP INDEX `index_notifications_on_user_uuid` ON `notifications`')
await queryRunner.query('DROP TABLE `notifications`')
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
'CREATE TABLE `notifications` (`uuid` varchar(36) NOT NULL, `user_uuid` varchar(36) NOT NULL, `type` varchar(36) NOT NULL, `payload` text NOT NULL, `created_at_timestamp` bigint NOT NULL, `updated_at_timestamp` bigint NOT NULL, INDEX `index_notifications_on_user_uuid` (`user_uuid`), PRIMARY KEY (`uuid`)) ENGINE=InnoDB',
)
}
}

View File

@@ -0,0 +1,17 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class AddNotifications1688540623272 implements MigrationInterface {
name = 'AddNotifications1688540623272'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
'CREATE TABLE "notifications" ("uuid" varchar PRIMARY KEY NOT NULL, "user_uuid" varchar(36) NOT NULL, "type" varchar(36) NOT NULL, "payload" text NOT NULL, "created_at_timestamp" bigint NOT NULL, "updated_at_timestamp" bigint NOT NULL)',
)
await queryRunner.query('CREATE INDEX "index_notifications_on_user_uuid" ON "notifications" ("user_uuid") ')
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('DROP INDEX "index_notifications_on_user_uuid"')
await queryRunner.query('DROP TABLE "notifications"')
}
}

View File

@@ -0,0 +1,17 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class RemoveNotifications1688540623273 implements MigrationInterface {
name = 'RemoveNotifications1688540623273'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('DROP INDEX "index_notifications_on_user_uuid"')
await queryRunner.query('DROP TABLE "notifications"')
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
'CREATE TABLE "notifications" ("uuid" varchar PRIMARY KEY NOT NULL, "user_uuid" varchar(36) NOT NULL, "type" varchar(36) NOT NULL, "payload" text NOT NULL, "created_at_timestamp" bigint NOT NULL, "updated_at_timestamp" bigint NOT NULL)',
)
await queryRunner.query('CREATE INDEX "index_notifications_on_user_uuid" ON "notifications" ("user_uuid") ')
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/auth-server",
"version": "1.120.2",
"version": "1.123.2",
"engines": {
"node": ">=18.0.0 <21.0.0"
},
@@ -42,17 +42,17 @@
"@cbor-extract/cbor-extract-linux-x64": "^2.1.1",
"@simplewebauthn/server": "^7.2.0",
"@simplewebauthn/typescript-types": "^7.0.0",
"@standardnotes/api": "^1.25.3",
"@standardnotes/api": "^1.26.26",
"@standardnotes/common": "workspace:*",
"@standardnotes/domain-core": "workspace:^",
"@standardnotes/domain-events": "workspace:*",
"@standardnotes/domain-events-infra": "workspace:*",
"@standardnotes/features": "^1.58.12",
"@standardnotes/features": "^1.59.7",
"@standardnotes/predicates": "workspace:*",
"@standardnotes/responses": "^1.13.9",
"@standardnotes/responses": "^1.13.27",
"@standardnotes/security": "workspace:*",
"@standardnotes/settings": "workspace:*",
"@standardnotes/sncrypto-common": "^1.9.0",
"@standardnotes/sncrypto-common": "^1.13.4",
"@standardnotes/sncrypto-node": "workspace:*",
"@standardnotes/time": "workspace:*",
"axios": "^1.1.3",

View File

@@ -20,19 +20,23 @@ import { Env } from './Env'
import { SqliteConnectionOptions } from 'typeorm/driver/sqlite/SqliteConnectionOptions'
export class AppDataSource {
private dataSource: DataSource | undefined
private _dataSource: DataSource | undefined
constructor(private env: Env) {}
getRepository<Entity extends ObjectLiteral>(target: EntityTarget<Entity>): Repository<Entity> {
if (!this.dataSource) {
if (!this._dataSource) {
throw new Error('DataSource not initialized')
}
return this.dataSource.getRepository(target)
return this._dataSource.getRepository(target)
}
async initialize(): Promise<void> {
await this.dataSource.initialize()
}
get dataSource(): DataSource {
this.env.load()
const isConfiguredForMySQL = this.env.get('DB_TYPE') === 'mysql'
@@ -104,7 +108,7 @@ export class AppDataSource {
database: inReplicaMode ? undefined : this.env.get('DB_DATABASE'),
}
this.dataSource = new DataSource(mySQLDataSourceOptions)
this._dataSource = new DataSource(mySQLDataSourceOptions)
} else {
const sqliteDataSourceOptions: SqliteConnectionOptions = {
...commonDataSourceOptions,
@@ -112,9 +116,9 @@ export class AppDataSource {
database: this.env.get('DB_SQLITE_DATABASE_PATH'),
}
this.dataSource = new DataSource(sqliteDataSourceOptions)
this._dataSource = new DataSource(sqliteDataSourceOptions)
}
await this.dataSource.initialize()
return this._dataSource
}
}

View File

@@ -0,0 +1,7 @@
import { AppDataSource } from './DataSource'
import { Env } from './Env'
const env: Env = new Env()
env.load()
export const MigrationsDataSource = new AppDataSource(env).dataSource

View File

@@ -8,12 +8,12 @@ import { User } from '../Domain/User/User'
import { Register } from '../Domain/UseCase/Register'
import { DomainEventFactoryInterface } from '../Domain/Event/DomainEventFactoryInterface'
import { KeyParamsOrigination, ProtocolVersion } from '@standardnotes/common'
import { ApiVersion } from '@standardnotes/api'
import { SignInWithRecoveryCodes } from '../Domain/UseCase/SignInWithRecoveryCodes/SignInWithRecoveryCodes'
import { GetUserKeyParamsRecovery } from '../Domain/UseCase/GetUserKeyParamsRecovery/GetUserKeyParamsRecovery'
import { GenerateRecoveryCodes } from '../Domain/UseCase/GenerateRecoveryCodes/GenerateRecoveryCodes'
import { Logger } from 'winston'
import { SessionServiceInterface } from '../Domain/Session/SessionServiceInterface'
import { ApiVersion } from '../Domain/Api/ApiVersion'
describe('AuthController', () => {
let clearLoginAttempts: ClearLoginAttempts
@@ -73,7 +73,7 @@ describe('AuthController', () => {
email: 'test@test.te',
password: 'asdzxc',
version: ProtocolVersion.V004,
api: ApiVersion.v0,
api: ApiVersion.v20200115,
origination: KeyParamsOrigination.Registration,
userAgent: 'Google Chrome',
identifier: 'test@test.te',
@@ -103,7 +103,7 @@ describe('AuthController', () => {
email: 'test@test.te',
password: '',
version: ProtocolVersion.V004,
api: ApiVersion.v0,
api: ApiVersion.v20200115,
origination: KeyParamsOrigination.Registration,
userAgent: 'Google Chrome',
identifier: 'test@test.te',
@@ -123,7 +123,7 @@ describe('AuthController', () => {
email: 'test@test.te',
password: 'test',
version: ProtocolVersion.V004,
api: ApiVersion.v0,
api: ApiVersion.v20200115,
origination: KeyParamsOrigination.Registration,
userAgent: 'Google Chrome',
identifier: 'test@test.te',

View File

@@ -1,10 +1,10 @@
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
import {
ApiVersion,
UserRegistrationRequestParams,
UserServerInterface,
UserDeletionResponseBody,
UserRegistrationResponseBody,
UserUpdateRequestParams,
} from '@standardnotes/api'
import { ErrorTag, HttpResponse, HttpStatusCode } from '@standardnotes/responses'
import { ProtocolVersion } from '@standardnotes/common'
@@ -23,6 +23,8 @@ import { GenerateRecoveryCodes } from '../Domain/UseCase/GenerateRecoveryCodes/G
import { GenerateRecoveryCodesRequestParams } from '../Infra/Http/Request/GenerateRecoveryCodesRequestParams'
import { Logger } from 'winston'
import { SessionServiceInterface } from '../Domain/Session/SessionServiceInterface'
import { ApiVersion } from '../Domain/Api/ApiVersion'
import { UserUpdateResponse } from '@standardnotes/api/dist/Domain/Response/User/UserUpdateResponse'
export class AuthController implements UserServerInterface {
constructor(
@@ -37,6 +39,10 @@ export class AuthController implements UserServerInterface {
private sessionService: SessionServiceInterface,
) {}
async update(_params: UserUpdateRequestParams): Promise<HttpResponse<UserUpdateResponse>> {
throw new Error('Method not implemented.')
}
async deleteAccount(_params: never): Promise<HttpResponse<UserDeletionResponseBody>> {
throw new Error('This method is implemented on the payments server.')
}
@@ -121,7 +127,7 @@ export class AuthController implements UserServerInterface {
async signInWithRecoveryCodes(
params: SignInWithRecoveryCodesRequestParams,
): Promise<HttpResponse<SignInWithRecoveryCodesResponseBody>> {
if (params.apiVersion !== ApiVersion.v0) {
if (params.apiVersion !== ApiVersion.v20200115) {
return {
status: HttpStatusCode.BadRequest,
data: {
@@ -162,7 +168,7 @@ export class AuthController implements UserServerInterface {
async recoveryKeyParams(
params: RecoveryKeyParamsRequestParams,
): Promise<HttpResponse<RecoveryKeyParamsResponseBody>> {
if (params.apiVersion !== ApiVersion.v0) {
if (params.apiVersion !== ApiVersion.v20200115) {
return {
status: HttpStatusCode.BadRequest,
data: {

View File

@@ -7,7 +7,7 @@ import { AcceptSharedSubscriptionInvitation } from '../Domain/UseCase/AcceptShar
import { DeclineSharedSubscriptionInvitation } from '../Domain/UseCase/DeclineSharedSubscriptionInvitation/DeclineSharedSubscriptionInvitation'
import { CancelSharedSubscriptionInvitation } from '../Domain/UseCase/CancelSharedSubscriptionInvitation/CancelSharedSubscriptionInvitation'
import { ListSharedSubscriptionInvitations } from '../Domain/UseCase/ListSharedSubscriptionInvitations/ListSharedSubscriptionInvitations'
import { ApiVersion } from '@standardnotes/api'
import { ApiVersion } from '../Domain/Api/ApiVersion'
describe('SubscriptionInvitesController', () => {
let inviteToSharedSubscription: InviteToSharedSubscription
@@ -53,7 +53,7 @@ describe('SubscriptionInvitesController', () => {
invitations: [],
})
const result = await createController().listInvites({ api: ApiVersion.v0, inviterEmail: 'test@test.te' })
const result = await createController().listInvites({ api: ApiVersion.v20200115, inviterEmail: 'test@test.te' })
expect(listSharedSubscriptionInvitations.execute).toHaveBeenCalledWith({
inviterEmail: 'test@test.te',
@@ -68,7 +68,7 @@ describe('SubscriptionInvitesController', () => {
})
const result = await createController().cancelInvite({
api: ApiVersion.v0,
api: ApiVersion.v20200115,
inviteUuid: '1-2-3',
inviterEmail: 'test@test.te',
})
@@ -87,7 +87,7 @@ describe('SubscriptionInvitesController', () => {
})
const result = await createController().cancelInvite({
api: ApiVersion.v0,
api: ApiVersion.v20200115,
inviteUuid: '1-2-3',
})
@@ -100,7 +100,7 @@ describe('SubscriptionInvitesController', () => {
})
const result = await createController().declineInvite({
api: ApiVersion.v0,
api: ApiVersion.v20200115,
inviteUuid: '1-2-3',
})
@@ -117,7 +117,7 @@ describe('SubscriptionInvitesController', () => {
})
const result = await createController().declineInvite({
api: ApiVersion.v0,
api: ApiVersion.v20200115,
inviteUuid: '1-2-3',
})
@@ -134,7 +134,7 @@ describe('SubscriptionInvitesController', () => {
})
const result = await createController().acceptInvite({
api: ApiVersion.v0,
api: ApiVersion.v20200115,
inviteUuid: '1-2-3',
})
@@ -151,7 +151,7 @@ describe('SubscriptionInvitesController', () => {
})
const result = await createController().acceptInvite({
api: ApiVersion.v0,
api: ApiVersion.v20200115,
inviteUuid: '1-2-3',
})
@@ -168,7 +168,7 @@ describe('SubscriptionInvitesController', () => {
})
const result = await createController().invite({
api: ApiVersion.v0,
api: ApiVersion.v20200115,
identifier: 'invitee@test.te',
inviterUuid: '1-2-3',
inviterEmail: 'test@test.te',
@@ -187,7 +187,7 @@ describe('SubscriptionInvitesController', () => {
it('should not invite to user subscription if the identifier is missing in request', async () => {
const result = await createController().invite({
api: ApiVersion.v0,
api: ApiVersion.v20200115,
identifier: '',
inviterUuid: '1-2-3',
inviterEmail: 'test@test.te',
@@ -205,7 +205,7 @@ describe('SubscriptionInvitesController', () => {
})
const result = await createController().invite({
api: ApiVersion.v0,
api: ApiVersion.v20200115,
identifier: 'invitee@test.te',
inviterUuid: '1-2-3',
inviterEmail: 'test@test.te',

View File

@@ -1,6 +1,5 @@
import * as bcrypt from 'bcryptjs'
import { RoleName, Username } from '@standardnotes/domain-core'
import { ApiVersion } from '@standardnotes/api'
import { v4 as uuidv4 } from 'uuid'
import { inject, injectable } from 'inversify'
@@ -16,6 +15,7 @@ import { TimerInterface } from '@standardnotes/time'
import { SettingServiceInterface } from '../Setting/SettingServiceInterface'
import { AuthResponseFactory20200115 } from '../Auth/AuthResponseFactory20200115'
import { AuthResponse20200115 } from '../Auth/AuthResponse20200115'
import { ApiVersion } from '../Api/ApiVersion'
@injectable()
export class Register implements UseCaseInterface {
@@ -39,7 +39,7 @@ export class Register implements UseCaseInterface {
const { email, password, apiVersion, ephemeralSession, ...registrationFields } = dto
if (apiVersion !== ApiVersion.v0) {
if (apiVersion !== ApiVersion.v20200115) {
return {
success: false,
errorMessage: `Unsupported api version: ${apiVersion}`,

View File

@@ -1,7 +1,6 @@
import * as bcrypt from 'bcryptjs'
import { Result, UseCaseInterface, Username, Uuid, Validator } from '@standardnotes/domain-core'
import { SettingName } from '@standardnotes/settings'
import { ApiVersion } from '@standardnotes/api'
import { AuthResponse20200115 } from '../../Auth/AuthResponse20200115'
import { SettingServiceInterface } from '../../Setting/SettingServiceInterface'
@@ -16,6 +15,7 @@ import { IncreaseLoginAttempts } from '../IncreaseLoginAttempts'
import { ClearLoginAttempts } from '../ClearLoginAttempts'
import { DeleteSetting } from '../DeleteSetting/DeleteSetting'
import { AuthenticatorRepositoryInterface } from '../../Authenticator/AuthenticatorRepositoryInterface'
import { ApiVersion } from '../../Api/ApiVersion'
export class SignInWithRecoveryCodes implements UseCaseInterface<AuthResponse20200115> {
constructor(
@@ -100,7 +100,7 @@ export class SignInWithRecoveryCodes implements UseCaseInterface<AuthResponse202
const authResponse = await this.authResponseFactory.createResponse({
user,
apiVersion: ApiVersion.v0,
apiVersion: ApiVersion.v20200115,
userAgent: dto.userAgent,
ephemeralSession: false,
readonlyAccess: false,

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.50.0](https://github.com/standardnotes/server/compare/@standardnotes/common@1.49.0...@standardnotes/common@1.50.0) (2023-07-12)
### Features
* domain items ([#655](https://github.com/standardnotes/server/issues/655)) ([a0af8f0](https://github.com/standardnotes/server/commit/a0af8f00252e1219e58cb7e066c11a8e71692e9d))
# [1.49.0](https://github.com/standardnotes/server/compare/@standardnotes/common@1.48.3...@standardnotes/common@1.49.0) (2023-06-30)
### Features

View File

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

View File

@@ -1,48 +0,0 @@
/* istanbul ignore file */
export enum ContentType {
Any = '*',
Item = 'SF|Item',
KeySystemItemsKey = 'SN|KeySystemItemsKey',
KeySystemRootKey = 'SN|KeySystemRootKey',
TrustedContact = 'SN|TrustedContact',
VaultListing = 'SN|VaultListing',
RootKey = 'SN|RootKey|NoSync',
ItemsKey = 'SN|ItemsKey',
EncryptedStorage = 'SN|EncryptedStorage',
Privileges = 'SN|Privileges',
Note = 'Note',
Tag = 'Tag',
SmartView = 'SN|SmartTag',
Component = 'SN|Component',
Editor = 'SN|Editor',
ActionsExtension = 'Extension',
UserPrefs = 'SN|UserPreferences',
HistorySession = 'SN|HistorySession',
Theme = 'SN|Theme',
File = 'SN|File',
FilesafeCredentials = 'SN|FileSafe|Credentials',
FilesafeFileMetadata = 'SN|FileSafe|FileMetadata',
FilesafeIntegration = 'SN|FileSafe|Integration',
ExtensionRepo = 'SN|ExtensionRepo',
Unknown = 'Unknown',
}
export function DisplayStringForContentType(contentType: ContentType): string | undefined {
const map: Partial<Record<ContentType, string>> = {
[ContentType.ActionsExtension]: 'action-based extension',
[ContentType.Component]: 'component',
[ContentType.Editor]: 'editor',
[ContentType.File]: 'file',
[ContentType.FilesafeCredentials]: 'FileSafe credential',
[ContentType.FilesafeFileMetadata]: 'FileSafe file',
[ContentType.FilesafeIntegration]: 'FileSafe integration',
[ContentType.ItemsKey]: 'encryption key',
[ContentType.Note]: 'note',
[ContentType.SmartView]: 'smart view',
[ContentType.Tag]: 'tag',
[ContentType.Theme]: 'theme',
[ContentType.UserPrefs]: 'user preferences',
}
return map[contentType]
}

View File

@@ -1,4 +1,3 @@
export * from './Content/ContentType'
export * from './Content/ContentDecoder'
export * from './Content/ContentDecoderInterface'
export * from './DataType/AnyRecord'

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.22.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-core@1.21.1...@standardnotes/domain-core@1.22.0) (2023-07-12)
### Features
* domain items ([#655](https://github.com/standardnotes/server/issues/655)) ([a0af8f0](https://github.com/standardnotes/server/commit/a0af8f00252e1219e58cb7e066c11a8e71692e9d))
## [1.21.1](https://github.com/standardnotes/server/compare/@standardnotes/domain-core@1.21.0...@standardnotes/domain-core@1.21.1) (2023-07-07)
### Bug Fixes
* transfer notifications from auth to syncing-server. ([#648](https://github.com/standardnotes/server/issues/648)) ([c288e5d](https://github.com/standardnotes/server/commit/c288e5d8dc54778a96a9fc33e3c9cae00583fade))
# [1.21.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-core@1.20.0...@standardnotes/domain-core@1.21.0) (2023-07-06)
### Features
* getting shared vault users and removing shared vault user ([#642](https://github.com/standardnotes/server/issues/642)) ([e905128](https://github.com/standardnotes/server/commit/e905128d45eaadb34d3465d4480dfb3a2c5f3f79))
# [1.20.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-core@1.19.0...@standardnotes/domain-core@1.20.0) (2023-07-05)
### Features
* deleting shared vaults. ([#640](https://github.com/standardnotes/server/issues/640)) ([f3161c2](https://github.com/standardnotes/server/commit/f3161c271296159331639814b2dbb2e566cc54c9))
# [1.19.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-core@1.18.0...@standardnotes/domain-core@1.19.0) (2023-06-30)
### Features

View File

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

View File

@@ -0,0 +1,39 @@
import { ContentType } from './ContentType'
describe('ContentType', () => {
it('should create a value object', () => {
const valueOrError = ContentType.create(ContentType.TYPES.Component)
expect(valueOrError.isFailed()).toBeFalsy()
expect(valueOrError.getValue().value).toEqual('SN|Component')
})
it('should not create an invalid value object', () => {
for (const value of ['', undefined, 0, 'FOOBAR']) {
const valueOrError = ContentType.create(value as string)
expect(valueOrError.isFailed()).toBeTruthy()
}
})
it('should return a display name', () => {
const valueOrError = ContentType.create(ContentType.TYPES.FilesafeFileMetadata)
expect(valueOrError.isFailed()).toBeFalsy()
expect(valueOrError.getValue().getDisplayName()).toEqual('FileSafe file')
})
it('should return null for a display name if the value is null', () => {
const valueOrError = ContentType.create(null)
expect(valueOrError.isFailed()).toBeFalsy()
expect(valueOrError.getValue().getDisplayName()).toBeNull()
})
it('should fallback to the value if the display name is not found', () => {
const valueOrError = ContentType.create(ContentType.TYPES.Unknown)
expect(valueOrError.isFailed()).toBeFalsy()
expect(valueOrError.getValue().getDisplayName()).toEqual('Unknown')
})
})

View File

@@ -0,0 +1,79 @@
import { Result } from '../Core/Result'
import { ValueObject } from '../Core/ValueObject'
import { ContentTypeProps } from './ContentTypeProps'
export class ContentType extends ValueObject<ContentTypeProps> {
static readonly TYPES = {
Any: '*',
Item: 'SF|Item',
KeySystemItemsKey: 'SN|KeySystemItemsKey',
KeySystemRootKey: 'SN|KeySystemRootKey',
TrustedContact: 'SN|TrustedContact',
VaultListing: 'SN|VaultListing',
RootKey: 'SN|RootKey|NoSync',
ItemsKey: 'SN|ItemsKey',
EncryptedStorage: 'SN|EncryptedStorage',
Privileges: 'SN|Privileges',
Note: 'Note',
Tag: 'Tag',
SmartView: 'SN|SmartTag',
Component: 'SN|Component',
Editor: 'SN|Editor',
ActionsExtension: 'Extension',
UserPrefs: 'SN|UserPreferences',
HistorySession: 'SN|HistorySession',
Theme: 'SN|Theme',
File: 'SN|File',
FilesafeCredentials: 'SN|FileSafe|Credentials',
FilesafeFileMetadata: 'SN|FileSafe|FileMetadata',
FilesafeIntegration: 'SN|FileSafe|Integration',
ExtensionRepo: 'SN|ExtensionRepo',
Unknown: 'Unknown',
}
private readonly displayNamesMap: Partial<Record<string, string>> = {
[ContentType.TYPES.ActionsExtension]: 'action-based extension',
[ContentType.TYPES.Component]: 'component',
[ContentType.TYPES.Editor]: 'editor',
[ContentType.TYPES.File]: 'file',
[ContentType.TYPES.FilesafeCredentials]: 'FileSafe credential',
[ContentType.TYPES.FilesafeFileMetadata]: 'FileSafe file',
[ContentType.TYPES.FilesafeIntegration]: 'FileSafe integration',
[ContentType.TYPES.ItemsKey]: 'encryption key',
[ContentType.TYPES.Note]: 'note',
[ContentType.TYPES.SmartView]: 'smart view',
[ContentType.TYPES.Tag]: 'tag',
[ContentType.TYPES.Theme]: 'theme',
[ContentType.TYPES.UserPrefs]: 'user preferences',
}
get value(): string | null {
return this.props.value
}
private constructor(props: ContentTypeProps) {
super(props)
}
static create(type: string | null): Result<ContentType> {
if (type === null) {
return Result.ok<ContentType>(new ContentType({ value: null }))
}
const isValidType = Object.values(this.TYPES).includes(type)
if (!isValidType) {
return Result.fail<ContentType>(`Invalid content type: ${type}`)
} else {
return Result.ok<ContentType>(new ContentType({ value: type }))
}
}
getDisplayName(): string | null {
if (!this.value) {
return null
}
return this.displayNamesMap[this.value] || this.value
}
}

View File

@@ -28,7 +28,7 @@ export class RoleNameCollection extends ValueObject<RoleNameCollectionProps> {
return false
}
equals(roleNameCollection: RoleNameCollection): boolean {
override equals(roleNameCollection: RoleNameCollection): boolean {
if (this.props.value.length !== roleNameCollection.value.length) {
return false
}

View File

@@ -13,4 +13,25 @@ describe('Uuid', () => {
expect(valueOrError.isFailed()).toBeTruthy()
})
it('should check equality between two value objects', () => {
const uuid1 = Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue()
const uuid2 = Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue()
expect(uuid1.equals(uuid2)).toBeTruthy()
})
it('should check inequality between two value objects', () => {
const uuid1 = Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue()
const uuid2 = Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1e').getValue()
expect(uuid1.equals(uuid2)).toBeFalsy()
})
it('should check inequality between two value objects of different types', () => {
const uuid1 = Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue()
expect(uuid1.equals(null as unknown as Uuid)).toBeFalsy()
expect(uuid1.equals(undefined as unknown as Uuid)).toBeFalsy()
})
})

View File

@@ -7,4 +7,12 @@ export abstract class ValueObject<T extends ValueObjectProps> {
constructor(props: T) {
this.props = Object.freeze(props)
}
public equals(vo?: ValueObject<T>): boolean {
if (vo === null || vo === undefined) {
return false
}
return JSON.stringify(this.props) === JSON.stringify(vo.props)
}
}

View File

@@ -9,6 +9,8 @@ export * from './Cache/CacheEntry'
export * from './Cache/CacheEntryProps'
export * from './Cache/CacheEntryRepositoryInterface'
export * from './Common/ContentType'
export * from './Common/ContentTypeProps'
export * from './Common/Dates'
export * from './Common/DatesProps'
export * from './Common/Email'

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.12.9](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.12.8...@standardnotes/domain-events-infra@1.12.9) (2023-07-07)
**Note:** Version bump only for package @standardnotes/domain-events-infra
## [1.12.8](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.12.7...@standardnotes/domain-events-infra@1.12.8) (2023-07-05)
**Note:** Version bump only for package @standardnotes/domain-events-infra
## [1.12.7](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.12.6...@standardnotes/domain-events-infra@1.12.7) (2023-06-30)
**Note:** Version bump only for package @standardnotes/domain-events-infra

View File

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

View File

@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [2.113.1](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.113.0...@standardnotes/domain-events@2.113.1) (2023-07-07)
### Bug Fixes
* transfer notifications from auth to syncing-server. ([#648](https://github.com/standardnotes/server/issues/648)) ([c288e5d](https://github.com/standardnotes/server/commit/c288e5d8dc54778a96a9fc33e3c9cae00583fade))
# [2.113.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.112.1...@standardnotes/domain-events@2.113.0) (2023-07-05)
### Features
* deleting shared vaults. ([#640](https://github.com/standardnotes/server/issues/640)) ([f3161c2](https://github.com/standardnotes/server/commit/f3161c271296159331639814b2dbb2e566cc54c9))
## [2.112.1](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.112.0...@standardnotes/domain-events@2.112.1) (2023-06-30)
**Note:** Version bump only for package @standardnotes/domain-events

View File

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

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.11.6](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.11.5...@standardnotes/event-store@1.11.6) (2023-07-12)
**Note:** Version bump only for package @standardnotes/event-store
## [1.11.5](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.11.4...@standardnotes/event-store@1.11.5) (2023-07-07)
**Note:** Version bump only for package @standardnotes/event-store
## [1.11.4](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.11.3...@standardnotes/event-store@1.11.4) (2023-07-06)
**Note:** Version bump only for package @standardnotes/event-store
## [1.11.3](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.11.2...@standardnotes/event-store@1.11.3) (2023-07-05)
**Note:** Version bump only for package @standardnotes/event-store
## [1.11.2](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.11.1...@standardnotes/event-store@1.11.2) (2023-06-30)
**Note:** Version bump only for package @standardnotes/event-store

View File

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

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.19.7](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.19.6...@standardnotes/files-server@1.19.7) (2023-07-12)
**Note:** Version bump only for package @standardnotes/files-server
## [1.19.6](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.19.5...@standardnotes/files-server@1.19.6) (2023-07-12)
**Note:** Version bump only for package @standardnotes/files-server
## [1.19.5](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.19.4...@standardnotes/files-server@1.19.5) (2023-07-07)
**Note:** Version bump only for package @standardnotes/files-server
## [1.19.4](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.19.3...@standardnotes/files-server@1.19.4) (2023-07-06)
**Note:** Version bump only for package @standardnotes/files-server
## [1.19.3](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.19.2...@standardnotes/files-server@1.19.3) (2023-07-05)
**Note:** Version bump only for package @standardnotes/files-server
## [1.19.2](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.19.1...@standardnotes/files-server@1.19.2) (2023-06-30)
**Note:** Version bump only for package @standardnotes/files-server

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/files-server",
"version": "1.19.2",
"version": "1.19.7",
"engines": {
"node": ">=18.0.0 <21.0.0"
},
@@ -35,7 +35,7 @@
"@standardnotes/domain-events": "workspace:*",
"@standardnotes/domain-events-infra": "workspace:*",
"@standardnotes/security": "workspace:*",
"@standardnotes/sncrypto-common": "^1.9.0",
"@standardnotes/sncrypto-common": "^1.13.4",
"@standardnotes/sncrypto-node": "workspace:*",
"@standardnotes/time": "workspace:*",
"connect-busboy": "^1.0.0",

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.11.41](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.11.40...@standardnotes/home-server@1.11.41) (2023-07-12)
**Note:** Version bump only for package @standardnotes/home-server
## [1.11.40](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.11.39...@standardnotes/home-server@1.11.40) (2023-07-12)
**Note:** Version bump only for package @standardnotes/home-server
## [1.11.39](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.11.38...@standardnotes/home-server@1.11.39) (2023-07-12)
**Note:** Version bump only for package @standardnotes/home-server
## [1.11.38](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.11.37...@standardnotes/home-server@1.11.38) (2023-07-11)
**Note:** Version bump only for package @standardnotes/home-server
## [1.11.37](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.11.36...@standardnotes/home-server@1.11.37) (2023-07-10)
**Note:** Version bump only for package @standardnotes/home-server
## [1.11.36](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.11.35...@standardnotes/home-server@1.11.36) (2023-07-10)
**Note:** Version bump only for package @standardnotes/home-server
## [1.11.35](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.11.34...@standardnotes/home-server@1.11.35) (2023-07-10)
**Note:** Version bump only for package @standardnotes/home-server
## [1.11.34](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.11.33...@standardnotes/home-server@1.11.34) (2023-07-10)
**Note:** Version bump only for package @standardnotes/home-server
## [1.11.33](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.11.32...@standardnotes/home-server@1.11.33) (2023-07-10)
**Note:** Version bump only for package @standardnotes/home-server
## [1.11.32](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.11.31...@standardnotes/home-server@1.11.32) (2023-07-07)
**Note:** Version bump only for package @standardnotes/home-server
## [1.11.31](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.11.30...@standardnotes/home-server@1.11.31) (2023-07-07)
**Note:** Version bump only for package @standardnotes/home-server
## [1.11.30](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.11.29...@standardnotes/home-server@1.11.30) (2023-07-06)
**Note:** Version bump only for package @standardnotes/home-server
## [1.11.29](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.11.28...@standardnotes/home-server@1.11.29) (2023-07-06)
**Note:** Version bump only for package @standardnotes/home-server
## [1.11.28](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.11.27...@standardnotes/home-server@1.11.28) (2023-07-06)
**Note:** Version bump only for package @standardnotes/home-server
## [1.11.27](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.11.26...@standardnotes/home-server@1.11.27) (2023-07-06)
**Note:** Version bump only for package @standardnotes/home-server
## [1.11.26](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.11.25...@standardnotes/home-server@1.11.26) (2023-07-05)
**Note:** Version bump only for package @standardnotes/home-server
## [1.11.25](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.11.24...@standardnotes/home-server@1.11.25) (2023-07-05)
**Note:** Version bump only for package @standardnotes/home-server
## [1.11.24](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.11.23...@standardnotes/home-server@1.11.24) (2023-07-05)
**Note:** Version bump only for package @standardnotes/home-server
## [1.11.23](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.11.22...@standardnotes/home-server@1.11.23) (2023-07-05)
**Note:** Version bump only for package @standardnotes/home-server
## [1.11.22](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.11.21...@standardnotes/home-server@1.11.22) (2023-07-03)
**Note:** Version bump only for package @standardnotes/home-server
## [1.11.21](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.11.20...@standardnotes/home-server@1.11.21) (2023-07-03)
**Note:** Version bump only for package @standardnotes/home-server
## [1.11.20](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.11.19...@standardnotes/home-server@1.11.20) (2023-07-03)
**Note:** Version bump only for package @standardnotes/home-server

View File

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

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.24.1](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.24.0...@standardnotes/revisions-server@1.24.1) (2023-07-12)
**Note:** Version bump only for package @standardnotes/revisions-server
# [1.24.0](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.23.9...@standardnotes/revisions-server@1.24.0) (2023-07-12)
### Features
* domain items ([#655](https://github.com/standardnotes/server/issues/655)) ([a0af8f0](https://github.com/standardnotes/server/commit/a0af8f00252e1219e58cb7e066c11a8e71692e9d))
## [1.23.9](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.23.8...@standardnotes/revisions-server@1.23.9) (2023-07-07)
**Note:** Version bump only for package @standardnotes/revisions-server
## [1.23.8](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.23.7...@standardnotes/revisions-server@1.23.8) (2023-07-06)
**Note:** Version bump only for package @standardnotes/revisions-server
## [1.23.7](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.23.6...@standardnotes/revisions-server@1.23.7) (2023-07-05)
**Note:** Version bump only for package @standardnotes/revisions-server
## [1.23.6](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.23.5...@standardnotes/revisions-server@1.23.6) (2023-06-30)
**Note:** Version bump only for package @standardnotes/revisions-server

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/revisions-server",
"version": "1.23.6",
"version": "1.24.1",
"engines": {
"node": ">=18.0.0 <21.0.0"
},
@@ -27,12 +27,12 @@
"dependencies": {
"@aws-sdk/client-s3": "^3.332.0",
"@aws-sdk/client-sqs": "^3.332.0",
"@standardnotes/api": "^1.25.3",
"@standardnotes/api": "^1.26.26",
"@standardnotes/common": "workspace:^",
"@standardnotes/domain-core": "workspace:^",
"@standardnotes/domain-events": "workspace:*",
"@standardnotes/domain-events-infra": "workspace:*",
"@standardnotes/responses": "^1.13.9",
"@standardnotes/responses": "^1.13.27",
"@standardnotes/security": "workspace:^",
"@standardnotes/time": "workspace:^",
"cors": "2.8.5",

View File

@@ -1,16 +0,0 @@
import { ContentType } from './ContentType'
describe('ContentType', () => {
it('should create a value obejct', () => {
const valueOrError = ContentType.create('Note')
expect(valueOrError.isFailed()).toBeFalsy()
expect(valueOrError.getValue().value).not.toBeNull()
})
it('should fail to create a value obejct', () => {
const valueOrError = ContentType.create('test')
expect(valueOrError.isFailed()).toBeTruthy()
})
})

View File

@@ -1,22 +0,0 @@
import { ContentType as ContentTypeValues } from '@standardnotes/common'
import { Result, ValueObject } from '@standardnotes/domain-core'
import { ContentTypeProps } from './ContentTypeProps'
export class ContentType extends ValueObject<ContentTypeProps> {
get value(): string | null {
return this.props.value
}
private constructor(props: ContentTypeProps) {
super(props)
}
static create(contentType: string | null): Result<ContentType> {
if (contentType !== null && !Object.values(ContentTypeValues).includes(contentType as ContentTypeValues)) {
return Result.fail<ContentType>(`Value is not a valid content type: ${contentType}`)
} else {
return Result.ok<ContentType>(new ContentType({ value: contentType }))
}
}
}

View File

@@ -1,5 +1,5 @@
import { Dates, Uuid } from '@standardnotes/domain-core'
import { ContentType } from './ContentType'
import { ContentType, Dates, Uuid } from '@standardnotes/domain-core'
import { Revision } from './Revision'
describe('Revision', () => {

View File

@@ -1,6 +1,4 @@
import { Dates } from '@standardnotes/domain-core'
import { ContentType } from './ContentType'
import { ContentType, Dates } from '@standardnotes/domain-core'
export interface RevisionMetadataProps {
contentType: ContentType

View File

@@ -1,6 +1,4 @@
import { Dates, Uuid } from '@standardnotes/domain-core'
import { ContentType } from './ContentType'
import { ContentType, Dates, Uuid } from '@standardnotes/domain-core'
export interface RevisionProps {
itemUuid: Uuid

View File

@@ -1,6 +1,5 @@
import { MapperInterface, Dates, Uuid } from '@standardnotes/domain-core'
import { MapperInterface, Dates, Uuid, ContentType } from '@standardnotes/domain-core'
import { ContentType } from '../Domain/Revision/ContentType'
import { Revision } from '../Domain/Revision/Revision'
export class RevisionItemStringMapper implements MapperInterface<Revision, string> {

View File

@@ -1,6 +1,5 @@
import { MapperInterface, Dates, UniqueEntityId } from '@standardnotes/domain-core'
import { MapperInterface, Dates, UniqueEntityId, ContentType } from '@standardnotes/domain-core'
import { ContentType } from '../Domain/Revision/ContentType'
import { RevisionMetadata } from '../Domain/Revision/RevisionMetadata'
import { TypeORMRevision } from '../Infra/TypeORM/TypeORMRevision'

View File

@@ -1,5 +1,4 @@
import { MapperInterface, Dates, UniqueEntityId, Uuid } from '@standardnotes/domain-core'
import { ContentType } from '../Domain/Revision/ContentType'
import { MapperInterface, Dates, UniqueEntityId, Uuid, ContentType } from '@standardnotes/domain-core'
import { Revision } from '../Domain/Revision/Revision'
import { TypeORMRevision } from '../Infra/TypeORM/TypeORMRevision'

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.20.8](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.20.7...@standardnotes/scheduler-server@1.20.8) (2023-07-12)
**Note:** Version bump only for package @standardnotes/scheduler-server
## [1.20.7](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.20.6...@standardnotes/scheduler-server@1.20.7) (2023-07-07)
**Note:** Version bump only for package @standardnotes/scheduler-server
## [1.20.6](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.20.5...@standardnotes/scheduler-server@1.20.6) (2023-07-06)
**Note:** Version bump only for package @standardnotes/scheduler-server
## [1.20.5](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.20.4...@standardnotes/scheduler-server@1.20.5) (2023-07-05)
**Note:** Version bump only for package @standardnotes/scheduler-server
## [1.20.4](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.20.3...@standardnotes/scheduler-server@1.20.4) (2023-06-30)
**Note:** Version bump only for package @standardnotes/scheduler-server

View File

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

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.13](https://github.com/standardnotes/server/compare/@standardnotes/settings@1.21.12...@standardnotes/settings@1.21.13) (2023-07-12)
**Note:** Version bump only for package @standardnotes/settings
## [1.21.12](https://github.com/standardnotes/server/compare/@standardnotes/settings@1.21.11...@standardnotes/settings@1.21.12) (2023-07-07)
**Note:** Version bump only for package @standardnotes/settings
## [1.21.11](https://github.com/standardnotes/server/compare/@standardnotes/settings@1.21.10...@standardnotes/settings@1.21.11) (2023-07-06)
**Note:** Version bump only for package @standardnotes/settings
## [1.21.10](https://github.com/standardnotes/server/compare/@standardnotes/settings@1.21.9...@standardnotes/settings@1.21.10) (2023-07-05)
**Note:** Version bump only for package @standardnotes/settings
## [1.21.9](https://github.com/standardnotes/server/compare/@standardnotes/settings@1.21.8...@standardnotes/settings@1.21.9) (2023-06-30)
**Note:** Version bump only for package @standardnotes/settings

View File

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

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.15.3](https://github.com/standardnotes/server/compare/@standardnotes/sncrypto-node@1.15.2...@standardnotes/sncrypto-node@1.15.3) (2023-07-12)
**Note:** Version bump only for package @standardnotes/sncrypto-node
## [1.15.2](https://github.com/standardnotes/server/compare/@standardnotes/sncrypto-node@1.15.0...@standardnotes/sncrypto-node@1.15.2) (2023-06-01)
### Bug Fixes

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/sncrypto-node",
"version": "1.15.2",
"version": "1.15.3",
"engines": {
"node": ">=18.0.0 <21.0.0"
},
@@ -23,7 +23,7 @@
"test": "jest spec"
},
"dependencies": {
"@standardnotes/sncrypto-common": "^1.9.0",
"@standardnotes/sncrypto-common": "^1.13.4",
"reflect-metadata": "^0.1.13"
},
"devDependencies": {

View File

@@ -3,6 +3,119 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.63.1](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.63.0...@standardnotes/syncing-server@1.63.1) (2023-07-12)
**Note:** Version bump only for package @standardnotes/syncing-server
# [1.63.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.62.1...@standardnotes/syncing-server@1.63.0) (2023-07-12)
### Features
* domain items ([#655](https://github.com/standardnotes/syncing-server-js/issues/655)) ([a0af8f0](https://github.com/standardnotes/syncing-server-js/commit/a0af8f00252e1219e58cb7e066c11a8e71692e9d))
## [1.62.1](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.62.0...@standardnotes/syncing-server@1.62.1) (2023-07-11)
### Bug Fixes
* unify use case usage ([#654](https://github.com/standardnotes/syncing-server-js/issues/654)) ([4d1e2de](https://github.com/standardnotes/syncing-server-js/commit/4d1e2dec264b156a4cfb4980ca3b486433ce64b7))
# [1.62.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.61.0...@standardnotes/syncing-server@1.62.0) (2023-07-10)
### Features
* messages controller. ([#653](https://github.com/standardnotes/syncing-server-js/issues/653)) ([18d07d4](https://github.com/standardnotes/syncing-server-js/commit/18d07d431f08dc17a276f84c0724935d9014a4fd))
# [1.61.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.60.0...@standardnotes/syncing-server@1.61.0) (2023-07-10)
### Features
* message operations use cases. ([#652](https://github.com/standardnotes/syncing-server-js/issues/652)) ([55ec597](https://github.com/standardnotes/syncing-server-js/commit/55ec5970daff9ef51f59e23eca17b312d392542a))
# [1.60.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.59.1...@standardnotes/syncing-server@1.60.0) (2023-07-10)
### Features
* sending messages. ([#651](https://github.com/standardnotes/syncing-server-js/issues/651)) ([ef49b0d](https://github.com/standardnotes/syncing-server-js/commit/ef49b0d3f8ab76dfa63a4c691feb9f35ad65c46f))
## [1.59.1](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.59.0...@standardnotes/syncing-server@1.59.1) (2023-07-10)
### Bug Fixes
* restructure use cases ([#650](https://github.com/standardnotes/syncing-server-js/issues/650)) ([04d0958](https://github.com/standardnotes/syncing-server-js/commit/04d09582d4c90706a7c7a4601ce011edf6cbc9c2))
# [1.59.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.58.1...@standardnotes/syncing-server@1.59.0) (2023-07-10)
### Features
* user to user message model. ([#649](https://github.com/standardnotes/syncing-server-js/issues/649)) ([f759261](https://github.com/standardnotes/syncing-server-js/commit/f7592619199596f7bef5787dde25efee017c8e60))
## [1.58.1](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.58.0...@standardnotes/syncing-server@1.58.1) (2023-07-07)
### Bug Fixes
* transfer notifications from auth to syncing-server. ([#648](https://github.com/standardnotes/syncing-server-js/issues/648)) ([c288e5d](https://github.com/standardnotes/syncing-server-js/commit/c288e5d8dc54778a96a9fc33e3c9cae00583fade))
# [1.58.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.57.0...@standardnotes/syncing-server@1.58.0) (2023-07-07)
### Features
* shared vault invites controller and use cases ([#647](https://github.com/standardnotes/syncing-server-js/issues/647)) ([7231013](https://github.com/standardnotes/syncing-server-js/commit/72310130d215047a8097a0c42a7b7dddeb4e3827))
# [1.57.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.56.0...@standardnotes/syncing-server@1.57.0) (2023-07-06)
### Features
* remove inbound shared vault invites. ([#646](https://github.com/standardnotes/syncing-server-js/issues/646)) ([92a5eb0](https://github.com/standardnotes/syncing-server-js/commit/92a5eb0d98486f25b761f37bc5710c45bd95d965))
# [1.56.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.55.0...@standardnotes/syncing-server@1.56.0) (2023-07-06)
### Features
* accept and decline shared vault invites ([#645](https://github.com/standardnotes/syncing-server-js/issues/645)) ([92f96dd](https://github.com/standardnotes/syncing-server-js/commit/92f96ddb84b9b7662899ef187ac38ad2a5769640))
# [1.55.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.54.0...@standardnotes/syncing-server@1.55.0) (2023-07-06)
### Features
* shared vault users controller. ([#643](https://github.com/standardnotes/syncing-server-js/issues/643)) ([b2c32ce](https://github.com/standardnotes/syncing-server-js/commit/b2c32ce70e9020b8d755a65432cb286b624a009c))
* update shared vault invite. ([#644](https://github.com/standardnotes/syncing-server-js/issues/644)) ([912a29d](https://github.com/standardnotes/syncing-server-js/commit/912a29d091ed1ca0af1712cbd09986a1c173a960))
# [1.54.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.53.0...@standardnotes/syncing-server@1.54.0) (2023-07-06)
### Features
* getting shared vault users and removing shared vault user ([#642](https://github.com/standardnotes/syncing-server-js/issues/642)) ([e905128](https://github.com/standardnotes/syncing-server-js/commit/e905128d45eaadb34d3465d4480dfb3a2c5f3f79))
# [1.53.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.52.0...@standardnotes/syncing-server@1.53.0) (2023-07-05)
### Features
* http controllers for shared vaults. ([#641](https://github.com/standardnotes/syncing-server-js/issues/641)) ([7a3946a](https://github.com/standardnotes/syncing-server-js/commit/7a3946a9e2d4168a1d286df321d9972588252b5d))
# [1.52.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.51.0...@standardnotes/syncing-server@1.52.0) (2023-07-05)
### Features
* deleting shared vaults. ([#640](https://github.com/standardnotes/syncing-server-js/issues/640)) ([f3161c2](https://github.com/standardnotes/syncing-server-js/commit/f3161c271296159331639814b2dbb2e566cc54c9))
# [1.51.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.50.0...@standardnotes/syncing-server@1.51.0) (2023-07-05)
### Features
* add getting shared vaults for a user ([#639](https://github.com/standardnotes/syncing-server-js/issues/639)) ([d2b2c33](https://github.com/standardnotes/syncing-server-js/commit/d2b2c339f2089ea5d538ee40118af083983be5ef))
# [1.50.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.49.0...@standardnotes/syncing-server@1.50.0) (2023-07-03)
### Features
* add invite users to a shared vault. ([#636](https://github.com/standardnotes/syncing-server-js/issues/636)) ([5dc5507](https://github.com/standardnotes/syncing-server-js/commit/5dc5507039c0dfb9df82a85377846651fef73c57))
# [1.49.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.48.0...@standardnotes/syncing-server@1.49.0) (2023-07-03)
### Features
* add creating shared vault file valet tokens. ([#635](https://github.com/standardnotes/syncing-server-js/issues/635)) ([04b3bb0](https://github.com/standardnotes/syncing-server-js/commit/04b3bb034fb5bf6f9d00d5b2e8a1abc4832c5417))
# [1.48.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.47.0...@standardnotes/syncing-server@1.48.0) (2023-07-03)
### Features

View File

@@ -0,0 +1,16 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class AddNotifications1688540448427 implements MigrationInterface {
name = 'AddNotifications1688540448427'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
'CREATE TABLE `notifications` (`uuid` varchar(36) NOT NULL, `user_uuid` varchar(36) NOT NULL, `type` varchar(36) NOT NULL, `payload` text NOT NULL, `created_at_timestamp` bigint NOT NULL, `updated_at_timestamp` bigint NOT NULL, INDEX `index_notifications_on_user_uuid` (`user_uuid`), PRIMARY KEY (`uuid`)) ENGINE=InnoDB',
)
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('DROP INDEX `index_notifications_on_user_uuid` ON `notifications`')
await queryRunner.query('DROP TABLE `notifications`')
}
}

View File

@@ -0,0 +1,17 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class AddNotifications1688540623272 implements MigrationInterface {
name = 'AddNotifications1688540623272'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
'CREATE TABLE "notifications" ("uuid" varchar PRIMARY KEY NOT NULL, "user_uuid" varchar(36) NOT NULL, "type" varchar(36) NOT NULL, "payload" text NOT NULL, "created_at_timestamp" bigint NOT NULL, "updated_at_timestamp" bigint NOT NULL)',
)
await queryRunner.query('CREATE INDEX "index_notifications_on_user_uuid" ON "notifications" ("user_uuid") ')
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('DROP INDEX "index_notifications_on_user_uuid"')
await queryRunner.query('DROP TABLE "notifications"')
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/syncing-server",
"version": "1.48.0",
"version": "1.63.1",
"engines": {
"node": ">=18.0.0 <21.0.0"
},
@@ -31,12 +31,12 @@
"@aws-sdk/client-s3": "^3.332.0",
"@aws-sdk/client-sns": "^3.332.0",
"@aws-sdk/client-sqs": "^3.332.0",
"@standardnotes/api": "^1.25.3",
"@standardnotes/api": "^1.26.26",
"@standardnotes/common": "workspace:*",
"@standardnotes/domain-core": "workspace:^",
"@standardnotes/domain-events": "workspace:*",
"@standardnotes/domain-events-infra": "workspace:*",
"@standardnotes/responses": "^1.13.9",
"@standardnotes/responses": "^1.13.27",
"@standardnotes/security": "workspace:*",
"@standardnotes/settings": "workspace:*",
"@standardnotes/sncrypto-node": "workspace:*",

View File

@@ -9,9 +9,6 @@ import { ItemRepositoryInterface } from '../Domain/Item/ItemRepositoryInterface'
import { TypeORMItemRepository } from '../Infra/TypeORM/TypeORMItemRepository'
import { Repository } from 'typeorm'
import { Item } from '../Domain/Item/Item'
import { ItemProjection } from '../Projection/ItemProjection'
import { ProjectorInterface } from '../Projection/ProjectorInterface'
import { ItemProjector } from '../Projection/ItemProjector'
import {
DirectCallDomainEventPublisher,
DirectCallEventMessageHandler,
@@ -26,29 +23,22 @@ import { Timer, TimerInterface } from '@standardnotes/time'
import { ItemTransferCalculatorInterface } from '../Domain/Item/ItemTransferCalculatorInterface'
import { ItemTransferCalculator } from '../Domain/Item/ItemTransferCalculator'
import { ItemConflict } from '../Domain/Item/ItemConflict'
import { ItemFactory } from '../Domain/Item/ItemFactory'
import { ItemFactoryInterface } from '../Domain/Item/ItemFactoryInterface'
import { ItemService } from '../Domain/Item/ItemService'
import { ItemServiceInterface } from '../Domain/Item/ItemServiceInterface'
import { ContentFilter } from '../Domain/Item/SaveRule/ContentFilter'
import { ContentTypeFilter } from '../Domain/Item/SaveRule/ContentTypeFilter'
import { OwnershipFilter } from '../Domain/Item/SaveRule/OwnershipFilter'
import { TimeDifferenceFilter } from '../Domain/Item/SaveRule/TimeDifferenceFilter'
import { UuidFilter } from '../Domain/Item/SaveRule/UuidFilter'
import { ItemSaveValidator } from '../Domain/Item/SaveValidator/ItemSaveValidator'
import { ItemSaveValidatorInterface } from '../Domain/Item/SaveValidator/ItemSaveValidatorInterface'
import { SyncResponseFactory20161215 } from '../Domain/Item/SyncResponse/SyncResponseFactory20161215'
import { SyncResponseFactory20200115 } from '../Domain/Item/SyncResponse/SyncResponseFactory20200115'
import { SyncResponseFactoryResolver } from '../Domain/Item/SyncResponse/SyncResponseFactoryResolver'
import { SyncResponseFactoryResolverInterface } from '../Domain/Item/SyncResponse/SyncResponseFactoryResolverInterface'
import { CheckIntegrity } from '../Domain/UseCase/CheckIntegrity/CheckIntegrity'
import { GetItem } from '../Domain/UseCase/GetItem/GetItem'
import { SyncItems } from '../Domain/UseCase/SyncItems'
import { CheckIntegrity } from '../Domain/UseCase/Syncing/CheckIntegrity/CheckIntegrity'
import { GetItem } from '../Domain/UseCase/Syncing/GetItem/GetItem'
import { SyncItems } from '../Domain/UseCase/Syncing/SyncItems/SyncItems'
import { InversifyExpressAuthMiddleware } from '../Infra/InversifyExpressUtils/Middleware/InversifyExpressAuthMiddleware'
import { ItemConflictProjection } from '../Projection/ItemConflictProjection'
import { ItemConflictProjector } from '../Projection/ItemConflictProjector'
import { SavedItemProjection } from '../Projection/SavedItemProjection'
import { SavedItemProjector } from '../Projection/SavedItemProjector'
import { S3Client } from '@aws-sdk/client-s3'
import { SQSClient, SQSClientConfig } from '@aws-sdk/client-sqs'
import { ContentDecoder } from '@standardnotes/common'
@@ -70,9 +60,21 @@ import { ItemBackupServiceInterface } from '../Domain/Item/ItemBackupServiceInte
import { FSItemBackupService } from '../Infra/FS/FSItemBackupService'
import { AuthHttpService } from '../Infra/HTTP/AuthHttpService'
import { S3ItemBackupService } from '../Infra/S3/S3ItemBackupService'
import { ControllerContainer, ControllerContainerInterface } from '@standardnotes/domain-core'
import { ControllerContainer, ControllerContainerInterface, MapperInterface } from '@standardnotes/domain-core'
import { HomeServerItemsController } from '../Infra/InversifyExpressUtils/HomeServer/HomeServerItemsController'
import { Transform } from 'stream'
import { TypeORMItem } from '../Infra/TypeORM/TypeORMItem'
import { ItemPersistenceMapper } from '../Mapping/Persistence/ItemPersistenceMapper'
import { ItemHttpRepresentation } from '../Mapping/Http/ItemHttpRepresentation'
import { ItemHttpMapper } from '../Mapping/Http/ItemHttpMapper'
import { SavedItemHttpRepresentation } from '../Mapping/Http/SavedItemHttpRepresentation'
import { SavedItemHttpMapper } from '../Mapping/Http/SavedItemHttpMapper'
import { ItemConflictHttpRepresentation } from '../Mapping/Http/ItemConflictHttpRepresentation'
import { ItemConflictHttpMapper } from '../Mapping/Http/ItemConflictHttpMapper'
import { ItemBackupRepresentation } from '../Mapping/Backup/ItemBackupRepresentation'
import { ItemBackupMapper } from '../Mapping/Backup/ItemBackupMapper'
import { SaveNewItem } from '../Domain/UseCase/Syncing/SaveNewItem/SaveNewItem'
import { UpdateExistingItem } from '../Domain/UseCase/Syncing/UpdateExistingItem/UpdateExistingItem'
export class ContainerConfigLoader {
private readonly DEFAULT_CONTENT_SIZE_TRANSFER_LIMIT = 10_000_000
@@ -122,6 +124,8 @@ export class ContainerConfigLoader {
logger.debug('Database initialized')
container.bind<TimerInterface>(TYPES.Sync_Timer).toConstantValue(new Timer())
const isConfiguredForHomeServer = env.get('MODE', true) === 'home-server'
container.bind<Env>(TYPES.Sync_Env).toConstantValue(env)
@@ -201,24 +205,37 @@ export class ContainerConfigLoader {
})
}
// Repositories
container.bind<ItemRepositoryInterface>(TYPES.Sync_ItemRepository).toDynamicValue((context: interfaces.Context) => {
return new TypeORMItemRepository(context.container.get(TYPES.Sync_ORMItemRepository))
})
// Mapping
container
.bind<MapperInterface<Item, TypeORMItem>>(TYPES.Sync_ItemPersistenceMapper)
.toConstantValue(new ItemPersistenceMapper())
container
.bind<MapperInterface<Item, ItemHttpRepresentation>>(TYPES.Sync_ItemHttpMapper)
.toConstantValue(new ItemHttpMapper(container.get(TYPES.Sync_Timer)))
container
.bind<MapperInterface<Item, SavedItemHttpRepresentation>>(TYPES.Sync_SavedItemHttpMapper)
.toConstantValue(new SavedItemHttpMapper(container.get(TYPES.Sync_Timer)))
container
.bind<MapperInterface<ItemConflict, ItemConflictHttpRepresentation>>(TYPES.Sync_ItemConflictHttpMapper)
.toConstantValue(new ItemConflictHttpMapper(container.get(TYPES.Sync_ItemHttpMapper)))
container
.bind<MapperInterface<Item, ItemBackupRepresentation>>(TYPES.Sync_ItemBackupMapper)
.toConstantValue(new ItemBackupMapper(container.get(TYPES.Sync_Timer)))
// ORM
container
.bind<Repository<Item>>(TYPES.Sync_ORMItemRepository)
.toDynamicValue(() => appDataSource.getRepository(Item))
.bind<Repository<TypeORMItem>>(TYPES.Sync_ORMItemRepository)
.toDynamicValue(() => appDataSource.getRepository(TypeORMItem))
// Projectors
// Repositories
container
.bind<ProjectorInterface<Item, ItemProjection>>(TYPES.Sync_ItemProjector)
.toDynamicValue((context: interfaces.Context) => {
return new ItemProjector(context.container.get(TYPES.Sync_Timer))
})
container.bind<TimerInterface>(TYPES.Sync_Timer).toDynamicValue(() => new Timer())
.bind<ItemRepositoryInterface>(TYPES.Sync_ItemRepository)
.toConstantValue(
new TypeORMItemRepository(
container.get(TYPES.Sync_ORMItemRepository),
container.get(TYPES.Sync_ItemPersistenceMapper),
),
)
container
.bind<DomainEventFactoryInterface>(TYPES.Sync_DomainEventFactory)
@@ -245,18 +262,6 @@ export class ContainerConfigLoader {
)
})
// Projectors
container
.bind<ProjectorInterface<Item, SavedItemProjection>>(TYPES.Sync_SavedItemProjector)
.toDynamicValue((context: interfaces.Context) => {
return new SavedItemProjector(context.container.get(TYPES.Sync_Timer))
})
container
.bind<ProjectorInterface<ItemConflict, ItemConflictProjection>>(TYPES.Sync_ItemConflictProjector)
.toDynamicValue((context: interfaces.Context) => {
return new ItemConflictProjector(context.container.get(TYPES.Sync_ItemProjector))
})
// env vars
container.bind(TYPES.Sync_AUTH_JWT_SECRET).toConstantValue(env.get('AUTH_JWT_SECRET'))
container
@@ -287,60 +292,35 @@ export class ContainerConfigLoader {
container.bind<GetItem>(TYPES.Sync_GetItem).toDynamicValue((context: interfaces.Context) => {
return new GetItem(context.container.get(TYPES.Sync_ItemRepository))
})
container
.bind<SaveNewItem>(TYPES.Sync_SaveNewItem)
.toConstantValue(
new SaveNewItem(
container.get(TYPES.Sync_ItemRepository),
container.get(TYPES.Sync_Timer),
container.get(TYPES.Sync_DomainEventPublisher),
container.get(TYPES.Sync_DomainEventFactory),
),
)
container
.bind<UpdateExistingItem>(TYPES.Sync_UpdateExistingItem)
.toConstantValue(
new UpdateExistingItem(
container.get(TYPES.Sync_ItemRepository),
container.get(TYPES.Sync_Timer),
container.get(TYPES.Sync_DomainEventPublisher),
container.get(TYPES.Sync_DomainEventFactory),
container.get(TYPES.Sync_REVISIONS_FREQUENCY),
),
)
// Services
container.bind<ItemServiceInterface>(TYPES.Sync_ItemService).toDynamicValue((context: interfaces.Context) => {
return new ItemService(
context.container.get(TYPES.Sync_ItemSaveValidator),
context.container.get(TYPES.Sync_ItemFactory),
context.container.get(TYPES.Sync_ItemRepository),
context.container.get(TYPES.Sync_DomainEventPublisher),
context.container.get(TYPES.Sync_DomainEventFactory),
context.container.get(TYPES.Sync_REVISIONS_FREQUENCY),
context.container.get(TYPES.Sync_CONTENT_SIZE_TRANSFER_LIMIT),
context.container.get(TYPES.Sync_ItemTransferCalculator),
context.container.get(TYPES.Sync_Timer),
context.container.get(TYPES.Sync_ItemProjector),
context.container.get(TYPES.Sync_MAX_ITEMS_LIMIT),
context.container.get(TYPES.Sync_Logger),
)
})
container
.bind<SyncResponseFactory20161215>(TYPES.Sync_SyncResponseFactory20161215)
.toDynamicValue((context: interfaces.Context) => {
return new SyncResponseFactory20161215(context.container.get(TYPES.Sync_ItemProjector))
})
container
.bind<SyncResponseFactory20200115>(TYPES.Sync_SyncResponseFactory20200115)
.toDynamicValue((context: interfaces.Context) => {
return new SyncResponseFactory20200115(
context.container.get(TYPES.Sync_ItemProjector),
context.container.get(TYPES.Sync_ItemConflictProjector),
context.container.get(TYPES.Sync_SavedItemProjector),
)
})
container
.bind<SyncResponseFactoryResolverInterface>(TYPES.Sync_SyncResponseFactoryResolver)
.toDynamicValue((context: interfaces.Context) => {
return new SyncResponseFactoryResolver(
context.container.get(TYPES.Sync_SyncResponseFactory20161215),
context.container.get(TYPES.Sync_SyncResponseFactory20200115),
)
})
container.bind<ItemFactoryInterface>(TYPES.Sync_ItemFactory).toDynamicValue((context: interfaces.Context) => {
return new ItemFactory(context.container.get(TYPES.Sync_Timer), context.container.get(TYPES.Sync_ItemProjector))
})
container.bind<OwnershipFilter>(TYPES.Sync_OwnershipFilter).toDynamicValue(() => new OwnershipFilter())
container.bind<OwnershipFilter>(TYPES.Sync_OwnershipFilter).toConstantValue(new OwnershipFilter())
container
.bind<TimeDifferenceFilter>(TYPES.Sync_TimeDifferenceFilter)
.toDynamicValue(
(context: interfaces.Context) => new TimeDifferenceFilter(context.container.get(TYPES.Sync_Timer)),
)
container.bind<UuidFilter>(TYPES.Sync_UuidFilter).toDynamicValue(() => new UuidFilter())
container.bind<ContentTypeFilter>(TYPES.Sync_ContentTypeFilter).toDynamicValue(() => new ContentTypeFilter())
container.bind<ContentFilter>(TYPES.Sync_ContentFilter).toDynamicValue(() => new ContentFilter())
.toConstantValue(new TimeDifferenceFilter(container.get(TYPES.Sync_Timer)))
container.bind<ContentTypeFilter>(TYPES.Sync_ContentTypeFilter).toConstantValue(new ContentTypeFilter())
container.bind<ContentFilter>(TYPES.Sync_ContentFilter).toConstantValue(new ContentFilter())
container
.bind<ItemSaveValidatorInterface>(TYPES.Sync_ItemSaveValidator)
@@ -348,12 +328,47 @@ export class ContainerConfigLoader {
return new ItemSaveValidator([
context.container.get(TYPES.Sync_OwnershipFilter),
context.container.get(TYPES.Sync_TimeDifferenceFilter),
context.container.get(TYPES.Sync_UuidFilter),
context.container.get(TYPES.Sync_ContentTypeFilter),
context.container.get(TYPES.Sync_ContentFilter),
])
})
container
.bind<ItemServiceInterface>(TYPES.Sync_ItemService)
.toConstantValue(
new ItemService(
container.get(TYPES.Sync_ItemSaveValidator),
container.get(TYPES.Sync_ItemRepository),
container.get(TYPES.Sync_CONTENT_SIZE_TRANSFER_LIMIT),
container.get(TYPES.Sync_ItemTransferCalculator),
container.get(TYPES.Sync_Timer),
container.get(TYPES.Sync_MAX_ITEMS_LIMIT),
container.get(TYPES.Sync_SaveNewItem),
container.get(TYPES.Sync_UpdateExistingItem),
container.get(TYPES.Sync_Logger),
),
)
container
.bind<SyncResponseFactory20161215>(TYPES.Sync_SyncResponseFactory20161215)
.toConstantValue(new SyncResponseFactory20161215(container.get(TYPES.Sync_ItemHttpMapper)))
container
.bind<SyncResponseFactory20200115>(TYPES.Sync_SyncResponseFactory20200115)
.toConstantValue(
new SyncResponseFactory20200115(
container.get(TYPES.Sync_ItemHttpMapper),
container.get(TYPES.Sync_ItemConflictHttpMapper),
container.get(TYPES.Sync_SavedItemHttpMapper),
),
)
container
.bind<SyncResponseFactoryResolverInterface>(TYPES.Sync_SyncResponseFactoryResolver)
.toDynamicValue((context: interfaces.Context) => {
return new SyncResponseFactoryResolver(
context.container.get(TYPES.Sync_SyncResponseFactory20161215),
context.container.get(TYPES.Sync_SyncResponseFactory20200115),
)
})
// env vars
container
.bind(TYPES.Sync_EMAIL_ATTACHMENT_MAX_BYTE_SIZE)
@@ -421,14 +436,15 @@ export class ContainerConfigLoader {
if (env.get('S3_AWS_REGION', true)) {
return new S3ItemBackupService(
context.container.get(TYPES.Sync_S3_BACKUP_BUCKET_NAME),
context.container.get(TYPES.Sync_ItemProjector),
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_ItemProjector),
context.container.get(TYPES.Sync_ItemBackupMapper),
context.container.get(TYPES.Sync_Logger),
)
}
@@ -512,7 +528,7 @@ export class ContainerConfigLoader {
container.get(TYPES.Sync_SyncItems),
container.get(TYPES.Sync_CheckIntegrity),
container.get(TYPES.Sync_GetItem),
container.get(TYPES.Sync_ItemProjector),
container.get(TYPES.Sync_ItemHttpMapper),
container.get(TYPES.Sync_SyncResponseFactoryResolver),
container.get(TYPES.Sync_ControllerContainer),
),

View File

@@ -1,23 +1,28 @@
import { DataSource, EntityTarget, LoggerOptions, ObjectLiteral, Repository } from 'typeorm'
import { MysqlConnectionOptions } from 'typeorm/driver/mysql/MysqlConnectionOptions'
import { Item } from '../Domain/Item/Item'
import { Env } from './Env'
import { SqliteConnectionOptions } from 'typeorm/driver/sqlite/SqliteConnectionOptions'
import { TypeORMItem } from '../Infra/TypeORM/TypeORMItem'
import { TypeORMNotification } from '../Infra/TypeORM/TypeORMNotification'
export class AppDataSource {
private dataSource: DataSource | undefined
private _dataSource: DataSource | undefined
constructor(private env: Env) {}
getRepository<Entity extends ObjectLiteral>(target: EntityTarget<Entity>): Repository<Entity> {
if (!this.dataSource) {
if (!this._dataSource) {
throw new Error('DataSource not initialized')
}
return this.dataSource.getRepository(target)
return this._dataSource.getRepository(target)
}
async initialize(): Promise<void> {
await this.dataSource.initialize()
}
get dataSource(): DataSource {
this.env.load()
const isConfiguredForMySQL = this.env.get('DB_TYPE') === 'mysql'
@@ -28,7 +33,7 @@ export class AppDataSource {
const commonDataSourceOptions = {
maxQueryExecutionTime,
entities: [Item],
entities: [TypeORMItem, TypeORMNotification],
migrations: [`${__dirname}/../../migrations/${isConfiguredForMySQL ? 'mysql' : 'sqlite'}/*.js`],
migrationsRun: true,
logging: <LoggerOptions>this.env.get('DB_DEBUG_LEVEL', true) ?? 'info',
@@ -72,7 +77,7 @@ export class AppDataSource {
database: inReplicaMode ? undefined : this.env.get('DB_DATABASE'),
}
this.dataSource = new DataSource(mySQLDataSourceOptions)
this._dataSource = new DataSource(mySQLDataSourceOptions)
} else {
const sqliteDataSourceOptions: SqliteConnectionOptions = {
...commonDataSourceOptions,
@@ -80,9 +85,9 @@ export class AppDataSource {
database: this.env.get('DB_SQLITE_DATABASE_PATH'),
}
this.dataSource = new DataSource(sqliteDataSourceOptions)
this._dataSource = new DataSource(sqliteDataSourceOptions)
}
await this.dataSource.initialize()
return this._dataSource
}
}

View File

@@ -0,0 +1,7 @@
import { AppDataSource } from './DataSource'
import { Env } from './Env'
const env: Env = new Env()
env.load()
export const MigrationsDataSource = new AppDataSource(env).dataSource

View File

@@ -12,10 +12,6 @@ const TYPES = {
Sync_ORMItemRepository: Symbol.for('Sync_ORMItemRepository'),
// Middleware
Sync_AuthMiddleware: Symbol.for('Sync_AuthMiddleware'),
// Projectors
Sync_ItemProjector: Symbol.for('Sync_ItemProjector'),
Sync_SavedItemProjector: Symbol.for('Sync_SavedItemProjector'),
Sync_ItemConflictProjector: Symbol.for('Sync_ItemConflictProjector'),
// env vars
Sync_REDIS_URL: Symbol.for('Sync_REDIS_URL'),
Sync_SNS_TOPIC_ARN: Symbol.for('Sync_SNS_TOPIC_ARN'),
@@ -38,6 +34,27 @@ const TYPES = {
Sync_SyncItems: Symbol.for('Sync_SyncItems'),
Sync_CheckIntegrity: Symbol.for('Sync_CheckIntegrity'),
Sync_GetItem: Symbol.for('Sync_GetItem'),
Sync_GetSharedVaults: Symbol.for('Sync_GetSharedVaults'),
Sync_CreateSharedVault: Symbol.for('Sync_CreateSharedVault'),
Sync_DeleteSharedVault: Symbol.for('Sync_DeleteSharedVault'),
Sync_CreateSharedVaultFileValetToken: Symbol.for('Sync_CreateSharedVaultFileValetToken'),
Sync_GetSharedVaultUsers: Symbol.for('Sync_GetSharedVaultUsers'),
Sync_RemoveSharedVaultUser: Symbol.for('Sync_RemoveSharedVaultUser'),
Sync_InviteUserToSharedVault: Symbol.for('Sync_InviteUserToSharedVault'),
Sync_UpdateSharedVaultInvite: Symbol.for('Sync_UpdateSharedVaultInvite'),
Sync_AcceptInviteToSharedVault: Symbol.for('Sync_AcceptInviteToSharedVault'),
Sync_DeclineInviteToSharedVault: Symbol.for('Sync_DeclineInviteToSharedVault'),
Sync_DeleteSharedVaultInvitesToUser: Symbol.for('Sync_DeleteSharedVaultInvitesToUser'),
Sync_DeleteSharedVaultInvitesSentByUser: Symbol.for('Sync_DeleteSharedVaultInvitesSentByUser'),
Sync_GetSharedVaultInvitesSentByUser: Symbol.for('Sync_GetSharedVaultInvitesSentByUser'),
Sync_GetSharedVaultInvitesSentToUser: Symbol.for('Sync_GetSharedVaultInvitesSentToUser'),
Sync_GetMessagesSentToUser: Symbol.for('Sync_GetMessagesSentToUser'),
Sync_GetMessagesSentByUser: Symbol.for('Sync_GetMessagesSentByUser'),
Sync_SendMessageToUser: Symbol.for('Sync_SendMessageToUser'),
Sync_DeleteAllMessagesSentToUser: Symbol.for('Sync_DeleteAllMessagesSentToUser'),
Sync_DeleteMessage: Symbol.for('Sync_DeleteMessage'),
Sync_SaveNewItem: Symbol.for('Sync_SaveNewItem'),
Sync_UpdateExistingItem: Symbol.for('Sync_UpdateExistingItem'),
// Handlers
Sync_AccountDeletionRequestedEventHandler: Symbol.for('Sync_AccountDeletionRequestedEventHandler'),
Sync_DuplicateItemSyncedEventHandler: Symbol.for('Sync_DuplicateItemSyncedEventHandler'),
@@ -61,13 +78,21 @@ const TYPES = {
Sync_ItemSaveValidator: Symbol.for('Sync_ItemSaveValidator'),
Sync_OwnershipFilter: Symbol.for('Sync_OwnershipFilter'),
Sync_TimeDifferenceFilter: Symbol.for('Sync_TimeDifferenceFilter'),
Sync_UuidFilter: Symbol.for('Sync_UuidFilter'),
Sync_ContentTypeFilter: Symbol.for('Sync_ContentTypeFilter'),
Sync_ContentFilter: Symbol.for('Sync_ContentFilter'),
Sync_ItemFactory: Symbol.for('Sync_ItemFactory'),
Sync_ItemTransferCalculator: Symbol.for('Sync_ItemTransferCalculator'),
Sync_ControllerContainer: Symbol.for('Sync_ControllerContainer'),
Sync_HomeServerItemsController: Symbol.for('Sync_HomeServerItemsController'),
// Mapping
Sync_SharedVaultHttpMapper: Symbol.for('Sync_SharedVaultHttpMapper'),
Sync_SharedVaultUserHttpMapper: Symbol.for('Sync_SharedVaultUserHttpMapper'),
Sync_SharedVaultInviteHttpMapper: Symbol.for('Sync_SharedVaultInviteHttpMapper'),
Sync_MessageHttpMapper: Symbol.for('Sync_MessageHttpMapper'),
Sync_ItemPersistenceMapper: Symbol.for('Sync_ItemPersistenceMapper'),
Sync_ItemHttpMapper: Symbol.for('Sync_ItemHttpMapper'),
Sync_SavedItemHttpMapper: Symbol.for('Sync_SavedItemHttpMapper'),
Sync_ItemConflictHttpMapper: Symbol.for('Sync_ItemConflictHttpMapper'),
Sync_ItemBackupMapper: Symbol.for('Sync_ItemBackupMapper'),
}
export default TYPES

View File

@@ -9,6 +9,7 @@ import { ItemRepositoryInterface } from '../Item/ItemRepositoryInterface'
import { ExtensionsHttpService } from './ExtensionsHttpService'
import { DomainEventFactoryInterface } from '../Event/DomainEventFactoryInterface'
import { AxiosInstance } from 'axios'
import { Uuid, ContentType, Dates, Timestamps, UniqueEntityId } from '@standardnotes/domain-core'
describe('ExtensionsHttpService', () => {
let httpClient: AxiosInstance
@@ -34,9 +35,22 @@ describe('ExtensionsHttpService', () => {
httpClient = {} as jest.Mocked<AxiosInstance>
httpClient.request = jest.fn().mockReturnValue({ status: 200, data: { foo: 'bar' } })
item = {
content: 'test',
} as jest.Mocked<Item>
item = Item.create(
{
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
updatedWithSession: null,
content: 'foobar',
contentType: ContentType.create(ContentType.TYPES.Note).getValue(),
encItemKey: null,
authHash: null,
itemsKeyId: null,
duplicateOf: null,
deleted: false,
dates: Dates.create(new Date(1616164633241311), new Date(1616164633241311)).getValue(),
timestamps: Timestamps.create(1616164633241311, 1616164633241311).getValue(),
},
new UniqueEntityId('00000000-0000-0000-0000-000000000000'),
).getValue()
authParams = {} as jest.Mocked<KeyParamsData>

View File

@@ -140,11 +140,11 @@ export class ExtensionsHttpService implements ExtensionsHttpServiceInterface {
email: string,
): Promise<DomainEventInterface> {
const extension = await this.itemRepository.findByUuidAndUserUuid(extensionId, userUuid)
if (extension === null || !extension.content) {
if (extension === null || !extension.props.content) {
throw Error(`Could not find extensions with id ${extensionId}`)
}
const content = this.contentDecoder.decode(extension.content)
const content = this.contentDecoder.decode(extension.props.content)
switch (this.getExtensionName(content)) {
case ExtensionName.Dropbox:
return this.createCloudBackupFailedEventBasedOnProvider('DROPBOX', email)

View File

@@ -5,6 +5,7 @@ import { Logger } from 'winston'
import { Item } from '../Item/Item'
import { ItemRepositoryInterface } from '../Item/ItemRepositoryInterface'
import { AccountDeletionRequestedEventHandler } from './AccountDeletionRequestedEventHandler'
import { Uuid, ContentType, Dates, Timestamps, UniqueEntityId } from '@standardnotes/domain-core'
describe('AccountDeletionRequestedEventHandler', () => {
let itemRepository: ItemRepositoryInterface
@@ -15,10 +16,22 @@ describe('AccountDeletionRequestedEventHandler', () => {
const createHandler = () => new AccountDeletionRequestedEventHandler(itemRepository, logger)
beforeEach(() => {
item = {
uuid: '1-2-3',
content: 'test',
} as jest.Mocked<Item>
item = Item.create(
{
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
updatedWithSession: null,
content: 'foobar',
contentType: ContentType.create(ContentType.TYPES.Note).getValue(),
encItemKey: null,
authHash: null,
itemsKeyId: null,
duplicateOf: null,
deleted: false,
dates: Dates.create(new Date(1616164633241311), new Date(1616164633241311)).getValue(),
timestamps: Timestamps.create(1616164633241311, 1616164633241311).getValue(),
},
new UniqueEntityId('00000000-0000-0000-0000-000000000000'),
).getValue()
itemRepository = {} as jest.Mocked<ItemRepositoryInterface>
itemRepository.findAll = jest.fn().mockReturnValue([item])

View File

@@ -10,6 +10,7 @@ import { Item } from '../Item/Item'
import { ItemRepositoryInterface } from '../Item/ItemRepositoryInterface'
import { DuplicateItemSyncedEventHandler } from './DuplicateItemSyncedEventHandler'
import { DomainEventFactoryInterface } from '../Event/DomainEventFactoryInterface'
import { Uuid, ContentType, Dates, Timestamps, UniqueEntityId } from '@standardnotes/domain-core'
describe('DuplicateItemSyncedEventHandler', () => {
let itemRepository: ItemRepositoryInterface
@@ -24,14 +25,39 @@ describe('DuplicateItemSyncedEventHandler', () => {
new DuplicateItemSyncedEventHandler(itemRepository, domainEventFactory, domainEventPublisher, logger)
beforeEach(() => {
originalItem = {
uuid: '1-2-3',
} as jest.Mocked<Item>
originalItem = Item.create(
{
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
updatedWithSession: null,
content: 'foobar',
contentType: ContentType.create(ContentType.TYPES.Note).getValue(),
encItemKey: null,
authHash: null,
itemsKeyId: null,
duplicateOf: null,
deleted: false,
dates: Dates.create(new Date(1616164633241311), new Date(1616164633241311)).getValue(),
timestamps: Timestamps.create(1616164633241311, 1616164633241311).getValue(),
},
new UniqueEntityId('00000000-0000-0000-0000-000000000000'),
).getValue()
duplicateItem = {
uuid: '2-3-4',
duplicateOf: '1-2-3',
} as jest.Mocked<Item>
duplicateItem = Item.create(
{
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
updatedWithSession: null,
content: 'foobar',
contentType: ContentType.create(ContentType.TYPES.Note).getValue(),
encItemKey: null,
authHash: null,
itemsKeyId: null,
duplicateOf: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
deleted: false,
dates: Dates.create(new Date(1616164633241311), new Date(1616164633241311)).getValue(),
timestamps: Timestamps.create(1616164633241311, 1616164633241311).getValue(),
},
new UniqueEntityId('00000000-0000-0000-0000-000000000001'),
).getValue()
itemRepository = {} as jest.Mocked<ItemRepositoryInterface>
itemRepository.findByUuidAndUserUuid = jest
@@ -81,7 +107,7 @@ describe('DuplicateItemSyncedEventHandler', () => {
})
it('should not copy revisions if duplicate item is not pointing to duplicate anything', async () => {
duplicateItem.duplicateOf = null
duplicateItem.props.duplicateOf = null
await createHandler().handle(event)
expect(domainEventPublisher.publish).not.toHaveBeenCalled()

View File

@@ -24,22 +24,22 @@ export class DuplicateItemSyncedEventHandler implements DomainEventHandlerInterf
return
}
if (!item.duplicateOf) {
if (!item.props.duplicateOf) {
this.logger.warn(`Item ${event.payload.itemUuid} does not point to any duplicate`)
return
}
const existingOriginalItem = await this.itemRepository.findByUuidAndUserUuid(
item.duplicateOf,
item.props.duplicateOf.value,
event.payload.userUuid,
)
if (existingOriginalItem !== null) {
await this.domainEventPublisher.publish(
this.domainEventFactory.createRevisionsCopyRequestedEvent(event.payload.userUuid, {
originalItemUuid: existingOriginalItem.uuid,
newItemUuid: item.uuid,
originalItemUuid: existingOriginalItem.id.toString(),
newItemUuid: item.id.toString(),
}),
)
}

View File

@@ -11,6 +11,7 @@ import { ItemRepositoryInterface } from '../Item/ItemRepositoryInterface'
import { ItemRevisionCreationRequestedEventHandler } from './ItemRevisionCreationRequestedEventHandler'
import { ItemBackupServiceInterface } from '../Item/ItemBackupServiceInterface'
import { DomainEventFactoryInterface } from '../Event/DomainEventFactoryInterface'
import { Uuid, ContentType, Dates, Timestamps, UniqueEntityId } from '@standardnotes/domain-core'
describe('ItemRevisionCreationRequestedEventHandler', () => {
let itemRepository: ItemRepositoryInterface
@@ -29,10 +30,22 @@ describe('ItemRevisionCreationRequestedEventHandler', () => {
)
beforeEach(() => {
item = {
uuid: '1-2-3',
content: 'test',
} as jest.Mocked<Item>
item = Item.create(
{
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
updatedWithSession: null,
content: 'foobar1',
contentType: ContentType.create(ContentType.TYPES.Note).getValue(),
encItemKey: null,
authHash: null,
itemsKeyId: null,
duplicateOf: null,
deleted: false,
dates: Dates.create(new Date(1616164633241311), new Date(1616164633241311)).getValue(),
timestamps: Timestamps.create(1616164633241311, 1616164633241311).getValue(),
},
new UniqueEntityId('00000000-0000-0000-0000-000000000000'),
).getValue()
itemRepository = {} as jest.Mocked<ItemRepositoryInterface>
itemRepository.findByUuid = jest.fn().mockReturnValue(item)

View File

@@ -1,6 +1,5 @@
import { ContentType } from '@standardnotes/common'
import { IntegrityPayload } from '@standardnotes/responses'
export type ExtendedIntegrityPayload = IntegrityPayload & {
content_type: ContentType
content_type: string | null
}

View File

@@ -1,119 +1,22 @@
import { ContentType } from '@standardnotes/common'
import { Column, Entity, Index, PrimaryGeneratedColumn } from 'typeorm'
import { Entity, Result, UniqueEntityId } from '@standardnotes/domain-core'
@Entity({ name: 'items' })
@Index('index_items_on_user_uuid_and_content_type', ['userUuid', 'contentType'])
@Index('user_uuid_and_updated_at_timestamp_and_created_at_timestamp', [
'userUuid',
'updatedAtTimestamp',
'createdAtTimestamp',
])
@Index('user_uuid_and_deleted', ['userUuid', 'deleted'])
export class Item {
@PrimaryGeneratedColumn('uuid')
declare uuid: string
import { ItemProps } from './ItemProps'
@Column({
type: 'varchar',
name: 'duplicate_of',
length: 36,
nullable: true,
})
declare duplicateOf: string | null
export class Item extends Entity<ItemProps> {
get id(): UniqueEntityId {
return this._id
}
@Column({
type: 'varchar',
name: 'items_key_id',
length: 255,
nullable: true,
})
declare itemsKeyId: string | null
private constructor(props: ItemProps, id?: UniqueEntityId) {
super(props, id)
}
@Column({
type: 'text',
nullable: true,
})
declare content: string | null
static create(props: ItemProps, id?: UniqueEntityId): Result<Item> {
if (!props.contentSize) {
const contentSize = Buffer.byteLength(JSON.stringify(props))
props.contentSize = contentSize
}
@Column({
name: 'content_type',
type: 'varchar',
length: 255,
nullable: true,
})
@Index('index_items_on_content_type')
declare contentType: ContentType | null
@Column({
name: 'content_size',
type: 'int',
nullable: true,
})
declare contentSize: number | null
@Column({
name: 'enc_item_key',
type: 'text',
nullable: true,
})
declare encItemKey: string | null
@Column({
name: 'auth_hash',
type: 'varchar',
length: 255,
nullable: true,
})
declare authHash: string | null
@Column({
name: 'user_uuid',
length: 36,
})
@Index('index_items_on_user_uuid')
declare userUuid: string
@Column({
type: 'tinyint',
precision: 1,
nullable: true,
default: 0,
})
@Index('index_items_on_deleted')
declare deleted: boolean
@Column({
name: 'created_at',
type: 'datetime',
precision: 6,
})
declare createdAt: Date
@Column({
name: 'updated_at',
type: 'datetime',
precision: 6,
})
declare updatedAt: Date
@Column({
name: 'created_at_timestamp',
type: 'bigint',
})
declare createdAtTimestamp: number
@Column({
name: 'updated_at_timestamp',
type: 'bigint',
})
@Index('updated_at_timestamp')
declare updatedAtTimestamp: number
@Column({
name: 'updated_with_session',
type: 'varchar',
length: 36,
nullable: true,
})
declare updatedWithSession: string | null
return Result.ok<Item>(new Item(props, id))
}
}

View File

@@ -1,198 +0,0 @@
import 'reflect-metadata'
import { Timer, TimerInterface } from '@standardnotes/time'
import { ContentType } from '@standardnotes/common'
import { ItemFactory } from './ItemFactory'
import { ItemHash } from './ItemHash'
import { ProjectorInterface } from '../../Projection/ProjectorInterface'
import { ItemProjection } from '../../Projection/ItemProjection'
import { Item } from './Item'
describe('ItemFactory', () => {
let timer: TimerInterface
let itemProjector: ProjectorInterface<Item, ItemProjection>
let timeHelper: Timer
const createFactory = () => new ItemFactory(timer, itemProjector)
beforeEach(() => {
timeHelper = new Timer()
timer = {} as jest.Mocked<TimerInterface>
timer.getTimestampInMicroseconds = jest.fn().mockReturnValue(1616164633241568)
timer.convertMicrosecondsToDate = jest
.fn()
.mockImplementation((microseconds: number) => timeHelper.convertMicrosecondsToDate(microseconds))
timer.convertStringDateToMicroseconds = jest
.fn()
.mockImplementation((date: string) => timeHelper.convertStringDateToMicroseconds(date))
timer.convertStringDateToDate = jest
.fn()
.mockImplementation((date: string) => timeHelper.convertStringDateToDate(date))
itemProjector = {} as jest.Mocked<ProjectorInterface<Item, ItemProjection>>
itemProjector.projectFull = jest.fn().mockReturnValue({
uuid: '1-2-3',
items_key_id: 'foobar',
duplicate_of: null,
enc_item_key: 'foobar',
content: 'foobar',
content_type: ContentType.Note,
auth_hash: 'foobar',
deleted: false,
created_at: '2022-09-01 10:00:00',
created_at_timestamp: 123123123123123,
updated_at: '2022-09-01 10:00:00',
updated_at_timestamp: 123123123123123,
updated_with_session: '2-4-5',
})
})
it('should create an item based on item hash', () => {
const itemHash = {
uuid: '1-2-3',
} as jest.Mocked<ItemHash>
const item = createFactory().create({ userUuid: 'a-b-c', itemHash, sessionUuid: '1-2-3' })
expect(item).toEqual({
createdAtTimestamp: 1616164633241568,
createdAt: expect.any(Date),
updatedWithSession: '1-2-3',
updatedAt: expect.any(Date),
updatedAtTimestamp: 1616164633241568,
userUuid: 'a-b-c',
uuid: '1-2-3',
contentSize: 341,
})
})
it('should create a stub item based on item hash with update_at date and timestamps overwritten', () => {
const itemHash = {
uuid: '1-2-3',
updated_at: '2021-03-25T09:37:37.943Z',
} as jest.Mocked<ItemHash>
const item = createFactory().createStub({ userUuid: 'a-b-c', itemHash, sessionUuid: '1-2-3' })
expect(item).toEqual({
createdAtTimestamp: 1616164633241568,
createdAt: expect.any(Date),
updatedWithSession: '1-2-3',
updatedAt: new Date('2021-03-25T09:37:37.943Z'),
updatedAtTimestamp: 1616665057943000,
userUuid: 'a-b-c',
uuid: '1-2-3',
content: null,
contentSize: 341,
})
})
it('should create a stub item based on item hash with update_at_timestamp date and timestamps overwritten', () => {
const itemHash = {
uuid: '1-2-3',
updated_at_timestamp: 1616164633241568,
content: 'foobar',
} as jest.Mocked<ItemHash>
const item = createFactory().createStub({ userUuid: 'a-b-c', itemHash, sessionUuid: '1-2-3' })
expect(item).toEqual({
createdAtTimestamp: 1616164633241568,
createdAt: expect.any(Date),
updatedWithSession: '1-2-3',
updatedAt: new Date('2021-03-19T14:37:13.241Z'),
updatedAtTimestamp: 1616164633241568,
userUuid: 'a-b-c',
uuid: '1-2-3',
content: 'foobar',
contentSize: 341,
})
})
it('should create a stub item based on item hash without updated timestamps', () => {
const itemHash = {
uuid: '1-2-3',
} as jest.Mocked<ItemHash>
const item = createFactory().createStub({ userUuid: 'a-b-c', itemHash, sessionUuid: '1-2-3' })
expect(item).toEqual({
createdAtTimestamp: 1616164633241568,
createdAt: expect.any(Date),
updatedWithSession: '1-2-3',
updatedAt: expect.any(Date),
updatedAtTimestamp: 1616164633241568,
userUuid: 'a-b-c',
uuid: '1-2-3',
content: null,
contentSize: 341,
})
})
it('should create an item based on item hash with all fields filled', () => {
const itemHash = {
uuid: '1-2-3',
content: 'asdqwe1',
content_type: ContentType.Note,
duplicate_of: '222',
auth_hash: 'aaa',
deleted: true,
enc_item_key: 'qweqwe1',
items_key_id: 'asdasd1',
created_at: timeHelper.formatDate(new Date(1616164633241), 'YYYY-MM-DDTHH:mm:ss.SSS[Z]'),
updated_at: timeHelper.formatDate(new Date(1616164633242), 'YYYY-MM-DDTHH:mm:ss.SSS[Z]'),
} as jest.Mocked<ItemHash>
const item = createFactory().create({ userUuid: 'a-b-c', itemHash, sessionUuid: '1-2-3' })
expect(item).toEqual({
content: 'asdqwe1',
contentSize: 341,
contentType: 'Note',
createdAt: expect.any(Date),
updatedWithSession: '1-2-3',
createdAtTimestamp: 1616164633241000,
encItemKey: 'qweqwe1',
itemsKeyId: 'asdasd1',
authHash: 'aaa',
deleted: true,
duplicateOf: '222',
updatedAt: expect.any(Date),
updatedAtTimestamp: 1616164633241568,
userUuid: 'a-b-c',
uuid: '1-2-3',
})
})
it('should create an item based on item hash with created at timestamp', () => {
const itemHash = {
uuid: '1-2-3',
content: 'asdqwe1',
content_type: ContentType.Note,
duplicate_of: null,
enc_item_key: 'qweqwe1',
items_key_id: 'asdasd1',
created_at_timestamp: 1616164633241312,
updated_at: timeHelper.formatDate(new Date(1616164633242), 'YYYY-MM-DDTHH:mm:ss.SSS[Z]'),
} as jest.Mocked<ItemHash>
const item = createFactory().create({ userUuid: 'a-b-c', itemHash, sessionUuid: '1-2-3' })
expect(item).toEqual({
content: 'asdqwe1',
contentSize: 341,
contentType: 'Note',
createdAt: expect.any(Date),
updatedWithSession: '1-2-3',
createdAtTimestamp: 1616164633241312,
encItemKey: 'qweqwe1',
itemsKeyId: 'asdasd1',
updatedAt: expect.any(Date),
updatedAtTimestamp: 1616164633241568,
userUuid: 'a-b-c',
uuid: '1-2-3',
})
})
})

View File

@@ -1,79 +0,0 @@
import { TimerInterface } from '@standardnotes/time'
import { ItemProjection } from '../../Projection/ItemProjection'
import { ProjectorInterface } from '../../Projection/ProjectorInterface'
import { Item } from './Item'
import { ItemFactoryInterface } from './ItemFactoryInterface'
import { ItemHash } from './ItemHash'
export class ItemFactory implements ItemFactoryInterface {
constructor(private timer: TimerInterface, private itemProjector: ProjectorInterface<Item, ItemProjection>) {}
createStub(dto: { userUuid: string; itemHash: ItemHash; sessionUuid: string | null }): Item {
const item = this.create(dto)
if (dto.itemHash.content === undefined) {
item.content = null
}
if (dto.itemHash.updated_at_timestamp) {
item.updatedAtTimestamp = dto.itemHash.updated_at_timestamp
item.updatedAt = this.timer.convertMicrosecondsToDate(dto.itemHash.updated_at_timestamp)
} else if (dto.itemHash.updated_at) {
item.updatedAtTimestamp = this.timer.convertStringDateToMicroseconds(dto.itemHash.updated_at)
item.updatedAt = this.timer.convertStringDateToDate(dto.itemHash.updated_at)
}
return item
}
create(dto: { userUuid: string; itemHash: ItemHash; sessionUuid: string | null }): Item {
const newItem = new Item()
newItem.uuid = dto.itemHash.uuid
newItem.updatedWithSession = dto.sessionUuid
newItem.contentSize = 0
if (dto.itemHash.content) {
newItem.content = dto.itemHash.content
}
newItem.userUuid = dto.userUuid
if (dto.itemHash.content_type) {
newItem.contentType = dto.itemHash.content_type
}
if (dto.itemHash.enc_item_key) {
newItem.encItemKey = dto.itemHash.enc_item_key
}
if (dto.itemHash.items_key_id) {
newItem.itemsKeyId = dto.itemHash.items_key_id
}
if (dto.itemHash.duplicate_of) {
newItem.duplicateOf = dto.itemHash.duplicate_of
}
if (dto.itemHash.deleted !== undefined) {
newItem.deleted = dto.itemHash.deleted
}
if (dto.itemHash.auth_hash) {
newItem.authHash = dto.itemHash.auth_hash
}
const now = this.timer.getTimestampInMicroseconds()
const nowDate = this.timer.convertMicrosecondsToDate(now)
newItem.updatedAtTimestamp = now
newItem.updatedAt = nowDate
newItem.createdAtTimestamp = now
newItem.createdAt = nowDate
if (dto.itemHash.created_at_timestamp) {
newItem.createdAtTimestamp = dto.itemHash.created_at_timestamp
newItem.createdAt = this.timer.convertMicrosecondsToDate(dto.itemHash.created_at_timestamp)
} else if (dto.itemHash.created_at) {
newItem.createdAtTimestamp = this.timer.convertStringDateToMicroseconds(dto.itemHash.created_at)
newItem.createdAt = this.timer.convertStringDateToDate(dto.itemHash.created_at)
}
newItem.contentSize = Buffer.byteLength(JSON.stringify(this.itemProjector.projectFull(newItem)))
return newItem
}
}

View File

@@ -1,7 +0,0 @@
import { Item } from './Item'
import { ItemHash } from './ItemHash'
export interface ItemFactoryInterface {
create(dto: { userUuid: string; itemHash: ItemHash; sessionUuid: string | null }): Item
createStub(dto: { userUuid: string; itemHash: ItemHash; sessionUuid: string | null }): Item
}

View File

@@ -1,9 +1,7 @@
import { ContentType } from '@standardnotes/common'
export type ItemHash = {
uuid: string
content?: string
content_type: ContentType
content_type: string | null
deleted?: boolean
duplicate_of?: string | null
auth_hash?: string

View File

@@ -0,0 +1,16 @@
import { ContentType, Dates, Timestamps, Uuid } from '@standardnotes/domain-core'
export interface ItemProps {
duplicateOf: Uuid | null
itemsKeyId: string | null
content: string | null
contentType: ContentType
encItemKey: string | null
authHash: string | null
userUuid: Uuid
deleted: boolean
updatedWithSession: Uuid | null
dates: Dates
timestamps: Timestamps
contentSize?: number
}

View File

@@ -16,8 +16,8 @@ export interface ItemRepositoryInterface {
findItemsForComputingIntegrityPayloads(userUuid: string): Promise<ExtendedIntegrityPayload[]>
findByUuidAndUserUuid(uuid: string, userUuid: string): Promise<Item | null>
findByUuid(uuid: string): Promise<Item | null>
remove(item: Item): Promise<Item>
save(item: Item): Promise<Item>
remove(item: Item): Promise<void>
save(item: Item): Promise<void>
markItemsAsDeleted(itemUuids: Array<string>, updatedAtTimestamp: number): Promise<void>
updateContentSize(itemUuid: string, contentSize: number): Promise<void>
}

View File

@@ -1,94 +1,101 @@
import 'reflect-metadata'
import { ContentType } from '@standardnotes/common'
import { Timer, TimerInterface } from '@standardnotes/time'
import { Logger } from 'winston'
import { Item } from './Item'
import { ItemHash } from './ItemHash'
import { ItemRepositoryInterface } from './ItemRepositoryInterface'
import { ItemService } from './ItemService'
import { ApiVersion } from '../Api/ApiVersion'
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
import { DomainEventFactoryInterface } from '../Event/DomainEventFactoryInterface'
import { Logger } from 'winston'
import { Timer, TimerInterface } from '@standardnotes/time'
import { ItemSaveValidatorInterface } from './SaveValidator/ItemSaveValidatorInterface'
import { ItemFactoryInterface } from './ItemFactoryInterface'
import { ItemConflict } from './ItemConflict'
import { ItemTransferCalculatorInterface } from './ItemTransferCalculatorInterface'
import { ProjectorInterface } from '../../Projection/ProjectorInterface'
import { ItemProjection } from '../../Projection/ItemProjection'
import { SaveNewItem } from '../UseCase/Syncing/SaveNewItem/SaveNewItem'
import { UpdateExistingItem } from '../UseCase/Syncing/UpdateExistingItem/UpdateExistingItem'
import { Uuid, ContentType, Dates, Timestamps, UniqueEntityId, Result } from '@standardnotes/domain-core'
describe('ItemService', () => {
let itemRepository: ItemRepositoryInterface
let domainEventPublisher: DomainEventPublisherInterface
let domainEventFactory: DomainEventFactoryInterface
const revisionFrequency = 300
const contentSizeTransferLimit = 100
let timer: TimerInterface
let item1: Item
let item2: Item
let itemHash1: ItemHash
let itemHash2: ItemHash
let emptyHash: ItemHash
let syncToken: string
let logger: Logger
let itemSaveValidator: ItemSaveValidatorInterface
let newItem: Item
let itemFactory: ItemFactoryInterface
let timeHelper: Timer
let itemTransferCalculator: ItemTransferCalculatorInterface
let itemProjector: ProjectorInterface<Item, ItemProjection>
let saveNewItemUseCase: SaveNewItem
let updateExistingItemUseCase: UpdateExistingItem
const maxItemsSyncLimit = 300
const createService = () =>
new ItemService(
itemSaveValidator,
itemFactory,
itemRepository,
domainEventPublisher,
domainEventFactory,
revisionFrequency,
contentSizeTransferLimit,
itemTransferCalculator,
timer,
itemProjector,
maxItemsSyncLimit,
saveNewItemUseCase,
updateExistingItemUseCase,
logger,
)
beforeEach(() => {
timeHelper = new Timer()
item1 = {
uuid: '1-2-3',
userUuid: '1-2-3',
createdAt: new Date(1616164633241311),
createdAtTimestamp: 1616164633241311,
updatedAt: new Date(1616164633241311),
updatedAtTimestamp: 1616164633241311,
} as jest.Mocked<Item>
item2 = {
uuid: '2-3-4',
userUuid: '1-2-3',
createdAt: new Date(1616164633241312),
createdAtTimestamp: 1616164633241312,
updatedAt: new Date(1616164633241312),
updatedAtTimestamp: 1616164633241312,
} as jest.Mocked<Item>
item1 = Item.create(
{
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
updatedWithSession: null,
content: 'foobar1',
contentType: ContentType.create(ContentType.TYPES.Note).getValue(),
encItemKey: null,
authHash: null,
itemsKeyId: null,
duplicateOf: null,
deleted: false,
dates: Dates.create(new Date(1616164633241311), new Date(1616164633241311)).getValue(),
timestamps: Timestamps.create(1616164633241311, 1616164633241311).getValue(),
},
new UniqueEntityId('00000000-0000-0000-0000-000000000000'),
).getValue()
item2 = Item.create(
{
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
updatedWithSession: null,
content: 'foobar2',
contentType: ContentType.create(ContentType.TYPES.Note).getValue(),
encItemKey: null,
authHash: null,
itemsKeyId: null,
duplicateOf: null,
deleted: false,
dates: Dates.create(new Date(1616164633241312), new Date(1616164633241312)).getValue(),
timestamps: Timestamps.create(1616164633241312, 1616164633241312).getValue(),
},
new UniqueEntityId('00000000-0000-0000-0000-000000000001'),
).getValue()
itemHash1 = {
uuid: '1-2-3',
content: 'asdqwe1',
content_type: ContentType.Note,
content_type: ContentType.TYPES.Note,
duplicate_of: null,
enc_item_key: 'qweqwe1',
items_key_id: 'asdasd1',
created_at: timeHelper.formatDate(
timeHelper.convertMicrosecondsToDate(item1.createdAtTimestamp),
timeHelper.convertMicrosecondsToDate(item1.props.timestamps.createdAt),
'YYYY-MM-DDTHH:mm:ss.SSS[Z]',
),
updated_at: timeHelper.formatDate(
new Date(timeHelper.convertMicrosecondsToMilliseconds(item1.updatedAtTimestamp) + 1),
new Date(timeHelper.convertMicrosecondsToMilliseconds(item1.props.timestamps.updatedAt) + 1),
'YYYY-MM-DDTHH:mm:ss.SSS[Z]',
),
} as jest.Mocked<ItemHash>
@@ -96,31 +103,28 @@ describe('ItemService', () => {
itemHash2 = {
uuid: '2-3-4',
content: 'asdqwe2',
content_type: ContentType.Note,
content_type: ContentType.TYPES.Note,
duplicate_of: null,
enc_item_key: 'qweqwe2',
items_key_id: 'asdasd2',
created_at: timeHelper.formatDate(
timeHelper.convertMicrosecondsToDate(item2.createdAtTimestamp),
timeHelper.convertMicrosecondsToDate(item2.props.timestamps.createdAt),
'YYYY-MM-DDTHH:mm:ss.SSS[Z]',
),
updated_at: timeHelper.formatDate(
new Date(timeHelper.convertMicrosecondsToMilliseconds(item2.updatedAtTimestamp) + 1),
new Date(timeHelper.convertMicrosecondsToMilliseconds(item2.props.timestamps.updatedAt) + 1),
'YYYY-MM-DDTHH:mm:ss.SSS[Z]',
),
} as jest.Mocked<ItemHash>
emptyHash = {
uuid: '2-3-4',
} as jest.Mocked<ItemHash>
itemTransferCalculator = {} as jest.Mocked<ItemTransferCalculatorInterface>
itemTransferCalculator.computeItemUuidsToFetch = jest.fn().mockReturnValue([item1.uuid, item2.uuid])
itemTransferCalculator.computeItemUuidsToFetch = jest
.fn()
.mockReturnValue([item1.id.toString(), item2.id.toString()])
itemRepository = {} as jest.Mocked<ItemRepositoryInterface>
itemRepository.findAll = jest.fn().mockReturnValue([item1, item2])
itemRepository.countAll = jest.fn().mockReturnValue(2)
itemRepository.save = jest.fn().mockImplementation((item: Item) => item)
timer = {} as jest.Mocked<TimerInterface>
timer.getTimestampInMicroseconds = jest.fn().mockReturnValue(1616164633241568)
@@ -136,13 +140,6 @@ describe('ItemService', () => {
.fn()
.mockImplementation((microseconds: number) => timeHelper.convertMicrosecondsToDate(microseconds))
domainEventPublisher = {} as jest.Mocked<DomainEventPublisherInterface>
domainEventPublisher.publish = jest.fn()
domainEventFactory = {} as jest.Mocked<DomainEventFactoryInterface>
domainEventFactory.createDuplicateItemSyncedEvent = jest.fn()
domainEventFactory.createItemRevisionCreationRequested = jest.fn()
logger = {} as jest.Mocked<Logger>
logger.error = jest.fn()
logger.warn = jest.fn()
@@ -152,31 +149,28 @@ describe('ItemService', () => {
itemSaveValidator = {} as jest.Mocked<ItemSaveValidatorInterface>
itemSaveValidator.validate = jest.fn().mockReturnValue({ passed: true })
newItem = {
contentType: ContentType.Note,
} as jest.Mocked<Item>
newItem = Item.create(
{
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
updatedWithSession: null,
content: 'foobar2',
contentType: ContentType.create(ContentType.TYPES.Note).getValue(),
encItemKey: null,
authHash: null,
itemsKeyId: null,
duplicateOf: null,
deleted: false,
dates: Dates.create(new Date(1616164633241313), new Date(1616164633241313)).getValue(),
timestamps: Timestamps.create(1616164633241313, 1616164633241313).getValue(),
},
new UniqueEntityId('00000000-0000-0000-0000-000000000002'),
).getValue()
itemFactory = {} as jest.Mocked<ItemFactoryInterface>
itemFactory.create = jest.fn().mockReturnValue(newItem)
itemFactory.createStub = jest.fn().mockReturnValue(newItem)
saveNewItemUseCase = {} as jest.Mocked<SaveNewItem>
saveNewItemUseCase.execute = jest.fn().mockReturnValue(Result.ok(newItem))
itemProjector = {} as jest.Mocked<ProjectorInterface<Item, ItemProjection>>
itemProjector.projectFull = jest.fn().mockReturnValue({
uuid: '1-2-3',
items_key_id: 'foobar',
duplicate_of: null,
enc_item_key: 'foobar',
content:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Sed viverra tellus in hac habitasse. Tortor posuere ac ut consequat semper. Ut diam quam nulla porttitor. Sapien pellentesque habitant morbi tristique senectus et netus et malesuada. Dapibus ultrices in iaculis nunc. Pellentesque habitant morbi tristique senectus et netus et malesuada fames. Faucibus et molestie ac feugiat sed lectus vestibulum mattis. Eu consequat ac felis donec. Eget velit aliquet sagittis id. Nullam eget felis eget nunc. Turpis in eu mi bibendum neque egestas congue.',
content_type: ContentType.Note,
auth_hash: 'foobar',
deleted: false,
created_at: '2022-09-01 10:00:00',
created_at_timestamp: 123123123123123,
updated_at: '2022-09-01 10:00:00',
updated_at_timestamp: 123123123123123,
updated_with_session: '2-4-5',
})
updateExistingItemUseCase = {} as jest.Mocked<UpdateExistingItem>
updateExistingItemUseCase.execute = jest.fn().mockReturnValue(Result.ok(item1))
})
it('should retrieve all items for a user from last sync with sync token version 1', async () => {
@@ -186,7 +180,7 @@ describe('ItemService', () => {
await createService().getItems({
userUuid: '1-2-3',
syncToken,
contentType: ContentType.Note,
contentType: ContentType.TYPES.Note,
}),
).toEqual({
items: [item1, item2],
@@ -202,7 +196,7 @@ describe('ItemService', () => {
limit: 150,
})
expect(itemRepository.findAll).toHaveBeenCalledWith({
uuids: ['1-2-3', '2-3-4'],
uuids: ['00000000-0000-0000-0000-000000000000', '00000000-0000-0000-0000-000000000001'],
sortOrder: 'ASC',
sortBy: 'updated_at_timestamp',
})
@@ -213,7 +207,7 @@ describe('ItemService', () => {
await createService().getItems({
userUuid: '1-2-3',
syncToken,
contentType: ContentType.Note,
contentType: ContentType.TYPES.Note,
}),
).toEqual({
items: [item1, item2],
@@ -229,7 +223,7 @@ describe('ItemService', () => {
limit: 150,
})
expect(itemRepository.findAll).toHaveBeenCalledWith({
uuids: ['1-2-3', '2-3-4'],
uuids: ['00000000-0000-0000-0000-000000000000', '00000000-0000-0000-0000-000000000001'],
sortBy: 'updated_at_timestamp',
sortOrder: 'ASC',
})
@@ -240,7 +234,7 @@ describe('ItemService', () => {
await createService().getItems({
userUuid: '1-2-3',
syncToken,
contentType: ContentType.Note,
contentType: ContentType.TYPES.Note,
limit: 1000,
}),
).toEqual({
@@ -257,7 +251,7 @@ describe('ItemService', () => {
limit: 300,
})
expect(itemRepository.findAll).toHaveBeenCalledWith({
uuids: ['1-2-3', '2-3-4'],
uuids: ['00000000-0000-0000-0000-000000000000', '00000000-0000-0000-0000-000000000001'],
sortBy: 'updated_at_timestamp',
sortOrder: 'ASC',
})
@@ -270,7 +264,7 @@ describe('ItemService', () => {
await createService().getItems({
userUuid: '1-2-3',
syncToken,
contentType: ContentType.Note,
contentType: ContentType.TYPES.Note,
}),
).toEqual({
items: [],
@@ -295,7 +289,7 @@ describe('ItemService', () => {
userUuid: '1-2-3',
syncToken,
limit: 1,
contentType: ContentType.Note,
contentType: ContentType.TYPES.Note,
})
expect(itemsResponse).toEqual({
@@ -315,7 +309,7 @@ describe('ItemService', () => {
limit: 1,
})
expect(itemRepository.findAll).toHaveBeenCalledWith({
uuids: ['1-2-3', '2-3-4'],
uuids: ['00000000-0000-0000-0000-000000000000', '00000000-0000-0000-0000-000000000001'],
sortBy: 'updated_at_timestamp',
sortOrder: 'ASC',
})
@@ -329,7 +323,7 @@ describe('ItemService', () => {
userUuid: '1-2-3',
syncToken,
cursorToken,
contentType: ContentType.Note,
contentType: ContentType.TYPES.Note,
}),
).toEqual({
items: [item1, item2],
@@ -345,7 +339,7 @@ describe('ItemService', () => {
limit: 150,
})
expect(itemRepository.findAll).toHaveBeenCalledWith({
uuids: ['1-2-3', '2-3-4'],
uuids: ['00000000-0000-0000-0000-000000000000', '00000000-0000-0000-0000-000000000001'],
sortBy: 'updated_at_timestamp',
sortOrder: 'ASC',
})
@@ -355,7 +349,7 @@ describe('ItemService', () => {
expect(
await createService().getItems({
userUuid: '1-2-3',
contentType: ContentType.Note,
contentType: ContentType.TYPES.Note,
}),
).toEqual({
items: [item1, item2],
@@ -371,7 +365,7 @@ describe('ItemService', () => {
limit: 150,
})
expect(itemRepository.findAll).toHaveBeenCalledWith({
uuids: ['1-2-3', '2-3-4'],
uuids: ['00000000-0000-0000-0000-000000000000', '00000000-0000-0000-0000-000000000001'],
sortBy: 'updated_at_timestamp',
sortOrder: 'ASC',
})
@@ -381,7 +375,7 @@ describe('ItemService', () => {
await createService().getItems({
userUuid: '1-2-3',
syncToken,
contentType: ContentType.Note,
contentType: ContentType.TYPES.Note,
})
expect(itemRepository.countAll).toHaveBeenCalledWith({
@@ -394,7 +388,7 @@ describe('ItemService', () => {
limit: 150,
})
expect(itemRepository.findAll).toHaveBeenCalledWith({
uuids: ['1-2-3', '2-3-4'],
uuids: ['00000000-0000-0000-0000-000000000000', '00000000-0000-0000-0000-000000000001'],
sortOrder: 'ASC',
sortBy: 'updated_at_timestamp',
})
@@ -405,7 +399,7 @@ describe('ItemService', () => {
userUuid: '1-2-3',
syncToken,
limit: 0,
contentType: ContentType.Note,
contentType: ContentType.TYPES.Note,
})
expect(itemRepository.countAll).toHaveBeenCalledWith({
@@ -418,7 +412,7 @@ describe('ItemService', () => {
limit: 150,
})
expect(itemRepository.findAll).toHaveBeenCalledWith({
uuids: ['1-2-3', '2-3-4'],
uuids: ['00000000-0000-0000-0000-000000000000', '00000000-0000-0000-0000-000000000001'],
sortBy: 'updated_at_timestamp',
sortOrder: 'ASC',
})
@@ -432,7 +426,7 @@ describe('ItemService', () => {
userUuid: '1-2-3',
syncToken: '2:',
limit: 0,
contentType: ContentType.Note,
contentType: ContentType.TYPES.Note,
})
} catch (e) {
error = e
@@ -449,7 +443,7 @@ describe('ItemService', () => {
userUuid: '1-2-3',
syncToken: '1234567890',
limit: 0,
contentType: ContentType.Note,
contentType: ContentType.TYPES.Note,
})
} catch (e) {
error = e
@@ -459,12 +453,38 @@ describe('ItemService', () => {
})
it('should front load keys items to top of the collection for better client performance', async () => {
const item3 = {
uuid: '1-2-3',
} as jest.Mocked<Item>
const item4 = {
uuid: '4-5-6',
} as jest.Mocked<Item>
const item3 = Item.create(
{
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
updatedWithSession: null,
content: 'foobar1',
contentType: ContentType.create(ContentType.TYPES.Note).getValue(),
encItemKey: null,
authHash: null,
itemsKeyId: null,
duplicateOf: null,
deleted: false,
dates: Dates.create(new Date(1616164633241311), new Date(1616164633241311)).getValue(),
timestamps: Timestamps.create(1616164633241311, 1616164633241311).getValue(),
},
new UniqueEntityId('00000000-0000-0000-0000-000000000003'),
).getValue()
const item4 = Item.create(
{
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
updatedWithSession: null,
content: 'foobar2',
contentType: ContentType.create(ContentType.TYPES.Note).getValue(),
encItemKey: null,
authHash: null,
itemsKeyId: null,
duplicateOf: null,
deleted: false,
dates: Dates.create(new Date(1616164633241312), new Date(1616164633241312)).getValue(),
timestamps: Timestamps.create(1616164633241312, 1616164633241312).getValue(),
},
new UniqueEntityId('00000000-0000-0000-0000-000000000004'),
).getValue()
itemRepository.findAll = jest.fn().mockReturnValue([item3, item4])
@@ -485,11 +505,10 @@ describe('ItemService', () => {
expect(result).toEqual({
conflicts: [],
savedItems: [newItem],
syncToken: 'MjpOYU4=',
syncToken: 'MjoxNjE2MTY0NjMzLjI0MTMxNA==',
})
expect(domainEventFactory.createItemRevisionCreationRequested).toHaveBeenCalledTimes(1)
expect(domainEventPublisher.publish).toHaveBeenCalledTimes(1)
expect(saveNewItemUseCase.execute).toHaveBeenCalled()
})
it('should not save new items in read only access mode', async () => {
@@ -513,16 +532,29 @@ describe('ItemService', () => {
savedItems: [],
syncToken: 'MjoxNjE2MTY0NjMzLjI0MTU2OQ==',
})
expect(saveNewItemUseCase.execute).not.toHaveBeenCalled()
})
it('should save new items that are duplicates', async () => {
itemRepository.findByUuid = jest.fn().mockReturnValue(null)
const duplicateItem = {
updatedAtTimestamp: 1616164633241570,
duplicateOf: '1-2-3',
contentType: ContentType.Note,
} as jest.Mocked<Item>
itemFactory.create = jest.fn().mockReturnValueOnce(duplicateItem)
const duplicateItem = Item.create(
{
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
updatedWithSession: null,
content: 'foobar1',
contentType: ContentType.create(ContentType.TYPES.Note).getValue(),
encItemKey: null,
authHash: null,
itemsKeyId: null,
duplicateOf: Uuid.create('00000000-0000-0000-0000-000000000001').getValue(),
deleted: false,
dates: Dates.create(new Date(1616164633241570), new Date(1616164633241570)).getValue(),
timestamps: Timestamps.create(1616164633241570, 1616164633241570).getValue(),
},
new UniqueEntityId('00000000-0000-0000-0000-000000000005'),
).getValue()
saveNewItemUseCase.execute = jest.fn().mockReturnValue(Result.ok(duplicateItem))
const result = await createService().saveItems({
itemHashes: [itemHash1],
@@ -537,10 +569,6 @@ describe('ItemService', () => {
savedItems: [duplicateItem],
syncToken: 'MjoxNjE2MTY0NjMzLjI0MTU3MQ==',
})
expect(domainEventFactory.createItemRevisionCreationRequested).toHaveBeenCalledTimes(1)
expect(domainEventPublisher.publish).toHaveBeenCalledTimes(2)
expect(domainEventFactory.createDuplicateItemSyncedEvent).toHaveBeenCalledTimes(1)
})
it('should skip items that are conflicting on validation', async () => {
@@ -568,7 +596,7 @@ describe('ItemService', () => {
it('should mark items as saved that are skipped on validation', async () => {
itemRepository.findByUuid = jest.fn().mockReturnValue(null)
const skipped = {} as jest.Mocked<Item>
const skipped = item1
const validationResult = { passed: false, skipped }
itemSaveValidator.validate = jest.fn().mockReturnValue(validationResult)
@@ -583,7 +611,7 @@ describe('ItemService', () => {
expect(result).toEqual({
conflicts: [],
savedItems: [skipped],
syncToken: 'MjpOYU4=',
syncToken: 'MjoxNjE2MTY0NjMzLjI0MTMxMg==',
})
})
@@ -593,7 +621,7 @@ describe('ItemService', () => {
const itemHash3 = {
uuid: '3-4-5',
content: 'asdqwe3',
content_type: ContentType.Note,
content_type: ContentType.TYPES.Note,
duplicate_of: null,
enc_item_key: 'qweqwe3',
items_key_id: 'asdasd3',
@@ -607,11 +635,41 @@ describe('ItemService', () => {
const item3Timestamp = 1616164633241569
timer.getTimestampInMicroseconds = jest.fn().mockReturnValueOnce(saveProcedureStartTimestamp)
itemFactory.create = jest
saveNewItemUseCase.execute = jest
.fn()
.mockReturnValueOnce({ updatedAtTimestamp: item1Timestamp, duplicateOf: null } as jest.Mocked<Item>)
.mockReturnValueOnce({ updatedAtTimestamp: item2Timestamp, duplicateOf: null } as jest.Mocked<Item>)
.mockReturnValueOnce({ updatedAtTimestamp: item3Timestamp, duplicateOf: null } as jest.Mocked<Item>)
.mockReturnValueOnce(
Result.ok(
Item.create(
{
...item1.props,
timestamps: Timestamps.create(item1Timestamp, item1Timestamp).getValue(),
},
new UniqueEntityId('00000000-0000-0000-0000-000000000001'),
).getValue(),
),
)
.mockReturnValueOnce(
Result.ok(
Item.create(
{
...item2.props,
timestamps: Timestamps.create(item2Timestamp, item2Timestamp).getValue(),
},
new UniqueEntityId('00000000-0000-0000-0000-000000000002'),
).getValue(),
),
)
.mockReturnValueOnce(
Result.ok(
Item.create(
{
...item2.props,
timestamps: Timestamps.create(item3Timestamp, item3Timestamp).getValue(),
},
new UniqueEntityId('00000000-0000-0000-0000-000000000003'),
).getValue(),
),
)
const result = await createService().saveItems({
itemHashes: [itemHash1, itemHash3, itemHash2],
@@ -638,66 +696,14 @@ describe('ItemService', () => {
expect(result).toEqual({
conflicts: [],
savedItems: [
{
content: 'asdqwe1',
contentSize: 950,
contentType: 'Note',
createdAtTimestamp: expect.any(Number),
createdAt: expect.any(Date),
encItemKey: 'qweqwe1',
itemsKeyId: 'asdasd1',
userUuid: '1-2-3',
updatedAtTimestamp: expect.any(Number),
updatedAt: expect.any(Date),
updatedWithSession: '2-3-4',
uuid: '1-2-3',
},
],
syncToken: 'MjoxNjE2MTY0NjMzLjI0MTU2OQ==',
savedItems: [item1],
syncToken: 'MjoxNjE2MTY0NjMzLjI0MTMxMg==',
})
})
it('should update existing items from legacy clients', async () => {
itemRepository.findByUuid = jest.fn().mockReturnValue(item1)
delete itemHash1.updated_at
delete itemHash1.updated_at_timestamp
const result = await createService().saveItems({
itemHashes: [itemHash1],
userUuid: '1-2-3',
apiVersion: ApiVersion.v20161215,
readOnlyAccess: false,
sessionUuid: '2-3-4',
})
expect(result).toEqual({
conflicts: [],
savedItems: [
{
content: 'asdqwe1',
contentSize: 950,
contentType: 'Note',
createdAtTimestamp: expect.any(Number),
createdAt: expect.any(Date),
encItemKey: 'qweqwe1',
itemsKeyId: 'asdasd1',
userUuid: '1-2-3',
updatedAtTimestamp: expect.any(Number),
updatedAt: expect.any(Date),
updatedWithSession: '2-3-4',
uuid: '1-2-3',
},
],
syncToken: 'MjoxNjE2MTY0NjMzLjI0MTU2OQ==',
})
})
it('should update existing items with created_at_timestamp', async () => {
itemHash1.created_at_timestamp = 123
itemHash1.updated_at_timestamp = item1.updatedAtTimestamp
it('should mark as skipped existing items that failed to update', async () => {
itemRepository.findByUuid = jest.fn().mockReturnValue(item1)
updateExistingItemUseCase.execute = jest.fn().mockReturnValue(Result.fail('Oops'))
const result = await createService().saveItems({
itemHashes: [itemHash1],
@@ -708,240 +714,49 @@ describe('ItemService', () => {
})
expect(result).toEqual({
conflicts: [],
savedItems: [
conflicts: [
{
content: 'asdqwe1',
contentSize: 950,
contentType: 'Note',
createdAtTimestamp: 123,
createdAt: expect.any(Date),
encItemKey: 'qweqwe1',
itemsKeyId: 'asdasd1',
userUuid: '1-2-3',
updatedAtTimestamp: expect.any(Number),
updatedAt: expect.any(Date),
updatedWithSession: '2-3-4',
uuid: '1-2-3',
type: 'uuid_conflict',
unsavedItem: itemHash1,
},
],
savedItems: [],
syncToken: 'MjoxNjE2MTY0NjMzLjI0MTU2OQ==',
})
})
it('should update existing empty hashes', async () => {
itemRepository.findByUuid = jest.fn().mockReturnValue(item2)
emptyHash.updated_at = timeHelper.formatDate(
new Date(timeHelper.convertMicrosecondsToMilliseconds(item2.updatedAtTimestamp) + 1),
'YYYY-MM-DDTHH:mm:ss.SSS[Z]',
)
const result = await createService().saveItems({
itemHashes: [emptyHash],
userUuid: '1-2-3',
apiVersion: ApiVersion.v20200115,
readOnlyAccess: false,
sessionUuid: '2-3-4',
})
expect(result).toEqual({
conflicts: [],
savedItems: [
{
contentSize: 950,
createdAtTimestamp: expect.any(Number),
createdAt: expect.any(Date),
userUuid: '1-2-3',
updatedAtTimestamp: expect.any(Number),
updatedAt: expect.any(Date),
updatedWithSession: '2-3-4',
uuid: '2-3-4',
},
],
syncToken: 'MjoxNjE2MTY0NjMzLjI0MTU2OQ==',
})
})
it('should create a revision for existing item if revisions frequency is matched', async () => {
timer.convertMicrosecondsToSeconds = itemRepository.findByUuid = jest.fn().mockReturnValue(item1)
const result = await createService().saveItems({
itemHashes: [itemHash1],
userUuid: '1-2-3',
apiVersion: ApiVersion.v20200115,
readOnlyAccess: false,
sessionUuid: '2-3-4',
})
expect(result).toEqual({
conflicts: [],
savedItems: [
{
content: 'asdqwe1',
contentSize: 950,
contentType: 'Note',
createdAtTimestamp: expect.any(Number),
createdAt: expect.any(Date),
encItemKey: 'qweqwe1',
itemsKeyId: 'asdasd1',
userUuid: '1-2-3',
updatedAtTimestamp: expect.any(Number),
updatedAt: expect.any(Date),
updatedWithSession: '2-3-4',
uuid: '1-2-3',
},
],
syncToken: 'MjoxNjE2MTY0NjMzLjI0MTU2OQ==',
})
})
it('should update existing items with empty user-agent', async () => {
itemRepository.findByUuid = jest.fn().mockReturnValue(item1)
const result = await createService().saveItems({
itemHashes: [itemHash1],
userUuid: '1-2-3',
apiVersion: ApiVersion.v20200115,
readOnlyAccess: false,
sessionUuid: '2-3-4',
})
expect(result).toEqual({
conflicts: [],
savedItems: [
{
content: 'asdqwe1',
contentSize: 950,
contentType: 'Note',
createdAtTimestamp: expect.any(Number),
createdAt: expect.any(Date),
encItemKey: 'qweqwe1',
itemsKeyId: 'asdasd1',
userUuid: '1-2-3',
updatedAtTimestamp: expect.any(Number),
updatedAt: expect.any(Date),
updatedWithSession: '2-3-4',
uuid: '1-2-3',
},
],
syncToken: 'MjoxNjE2MTY0NjMzLjI0MTU2OQ==',
})
})
it('should update existing items with auth hash', async () => {
itemRepository.findByUuid = jest.fn().mockReturnValue(item1)
itemHash1.auth_hash = 'test'
const result = await createService().saveItems({
itemHashes: [itemHash1],
userUuid: '1-2-3',
apiVersion: ApiVersion.v20200115,
readOnlyAccess: false,
sessionUuid: '2-3-4',
})
expect(result).toEqual({
conflicts: [],
savedItems: [
{
content: 'asdqwe1',
contentSize: 950,
contentType: 'Note',
createdAtTimestamp: expect.any(Number),
createdAt: expect.any(Date),
encItemKey: 'qweqwe1',
itemsKeyId: 'asdasd1',
authHash: 'test',
userUuid: '1-2-3',
updatedAtTimestamp: expect.any(Number),
updatedAt: expect.any(Date),
updatedWithSession: '2-3-4',
uuid: '1-2-3',
},
],
syncToken: 'MjoxNjE2MTY0NjMzLjI0MTU2OQ==',
})
})
it('should mark existing item as deleted', async () => {
itemRepository.findByUuid = jest.fn().mockReturnValue(item1)
itemHash1.deleted = true
const result = await createService().saveItems({
itemHashes: [itemHash1],
userUuid: '1-2-3',
apiVersion: ApiVersion.v20200115,
readOnlyAccess: false,
sessionUuid: '2-3-4',
})
expect(result).toEqual({
conflicts: [],
savedItems: [
{
content: null,
contentSize: 0,
authHash: null,
contentType: 'Note',
createdAtTimestamp: expect.any(Number),
createdAt: expect.any(Date),
encItemKey: null,
deleted: true,
itemsKeyId: null,
userUuid: '1-2-3',
updatedAtTimestamp: expect.any(Number),
updatedAt: expect.any(Date),
updatedWithSession: '2-3-4',
uuid: '1-2-3',
},
],
syncToken: 'MjoxNjE2MTY0NjMzLjI0MTU2OQ==',
})
})
it('should mark existing item as duplicate', async () => {
itemRepository.findByUuid = jest.fn().mockReturnValue(item1)
itemHash1.duplicate_of = '1-2-3'
const result = await createService().saveItems({
itemHashes: [itemHash1],
userUuid: '1-2-3',
apiVersion: ApiVersion.v20200115,
readOnlyAccess: false,
sessionUuid: '2-3-4',
})
expect(result).toEqual({
conflicts: [],
savedItems: [
{
content: 'asdqwe1',
contentSize: 950,
contentType: 'Note',
createdAtTimestamp: expect.any(Number),
createdAt: expect.any(Date),
encItemKey: 'qweqwe1',
duplicateOf: '1-2-3',
itemsKeyId: 'asdasd1',
userUuid: '1-2-3',
updatedAtTimestamp: expect.any(Number),
updatedAt: expect.any(Date),
updatedWithSession: '2-3-4',
uuid: '1-2-3',
},
],
syncToken: 'MjoxNjE2MTY0NjMzLjI0MTU2OQ==',
})
expect(domainEventPublisher.publish).toHaveBeenCalledTimes(2)
expect(domainEventFactory.createDuplicateItemSyncedEvent).toHaveBeenCalledTimes(1)
expect(domainEventFactory.createItemRevisionCreationRequested).toHaveBeenCalledTimes(1)
})
it('should skip saving conflicting items and mark them as sync conflicts when saving to database fails', async () => {
it('should skip saving conflicting items and mark them as sync conflicts when saving fails', async () => {
itemRepository.findByUuid = jest.fn().mockReturnValue(null)
itemRepository.save = jest.fn().mockImplementation(() => {
throw new Error('Something bad happened')
saveNewItemUseCase.execute = jest.fn().mockReturnValue(Result.fail('Oops'))
const result = await createService().saveItems({
itemHashes: [itemHash1, itemHash2],
userUuid: '1-2-3',
apiVersion: ApiVersion.v20200115,
readOnlyAccess: false,
sessionUuid: '2-3-4',
})
expect(result).toEqual({
conflicts: [
{
type: 'uuid_conflict',
unsavedItem: itemHash1,
},
{
type: 'uuid_conflict',
unsavedItem: itemHash2,
},
],
savedItems: [],
syncToken: 'MjoxNjE2MTY0NjMzLjI0MTU2OQ==',
})
})
it('should skip saving conflicting items and mark them as sync conflicts when saving throws an error', async () => {
itemRepository.findByUuid = jest.fn().mockReturnValue(null)
saveNewItemUseCase.execute = jest.fn().mockImplementation(() => {
throw new Error('Oops')
})
const result = await createService().saveItems({

View File

@@ -1,15 +1,10 @@
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
import { Time, TimerInterface } from '@standardnotes/time'
import { ContentType } from '@standardnotes/common'
import { Logger } from 'winston'
import { DomainEventFactoryInterface } from '../Event/DomainEventFactoryInterface'
import { GetItemsDTO } from './GetItemsDTO'
import { GetItemsResult } from './GetItemsResult'
import { Item } from './Item'
import { ItemConflict } from './ItemConflict'
import { ItemFactoryInterface } from './ItemFactoryInterface'
import { ItemHash } from './ItemHash'
import { ItemQuery } from './ItemQuery'
import { ItemRepositoryInterface } from './ItemRepositoryInterface'
import { ItemServiceInterface } from './ItemServiceInterface'
@@ -18,8 +13,9 @@ import { SaveItemsResult } from './SaveItemsResult'
import { ItemSaveValidatorInterface } from './SaveValidator/ItemSaveValidatorInterface'
import { ConflictType } from '@standardnotes/responses'
import { ItemTransferCalculatorInterface } from './ItemTransferCalculatorInterface'
import { ProjectorInterface } from '../../Projection/ProjectorInterface'
import { ItemProjection } from '../../Projection/ItemProjection'
import { SaveNewItem } from '../UseCase/Syncing/SaveNewItem/SaveNewItem'
import { ContentType } from '@standardnotes/domain-core'
import { UpdateExistingItem } from '../UseCase/Syncing/UpdateExistingItem/UpdateExistingItem'
export class ItemService implements ItemServiceInterface {
private readonly DEFAULT_ITEMS_LIMIT = 150
@@ -27,16 +23,13 @@ export class ItemService implements ItemServiceInterface {
constructor(
private itemSaveValidator: ItemSaveValidatorInterface,
private itemFactory: ItemFactoryInterface,
private itemRepository: ItemRepositoryInterface,
private domainEventPublisher: DomainEventPublisherInterface,
private domainEventFactory: DomainEventFactoryInterface,
private revisionFrequency: number,
private contentSizeTransferLimit: number,
private itemTransferCalculator: ItemTransferCalculatorInterface,
private timer: TimerInterface,
private itemProjector: ProjectorInterface<Item, ItemProjection>,
private maxItemsSyncLimit: number,
private saveNewItem: SaveNewItem,
private updateExistingItem: UpdateExistingItem,
private logger: Logger,
) {}
@@ -73,7 +66,7 @@ export class ItemService implements ItemServiceInterface {
let cursorToken = undefined
if (totalItemsCount > upperBoundLimit) {
const lastSyncTime = items[items.length - 1].updatedAtTimestamp / Time.MicrosecondsInASecond
const lastSyncTime = items[items.length - 1].props.timestamps.updatedAt / Time.MicrosecondsInASecond
cursorToken = Buffer.from(`${this.SYNC_TOKEN_VERSION}:${lastSyncTime}`, 'utf-8').toString('base64')
}
@@ -118,15 +111,47 @@ export class ItemService implements ItemServiceInterface {
}
if (existingItem) {
const updatedItem = await this.updateExistingItem({
const udpatedItemOrError = await this.updateExistingItem.execute({
existingItem,
itemHash,
sessionUuid: dto.sessionUuid,
})
if (udpatedItemOrError.isFailed()) {
this.logger.error(
`[${dto.userUuid}] Updating item ${itemHash.uuid} failed. Error: ${udpatedItemOrError.getError()}`,
)
conflicts.push({
unsavedItem: itemHash,
type: ConflictType.UuidConflict,
})
continue
}
const updatedItem = udpatedItemOrError.getValue()
savedItems.push(updatedItem)
} else {
try {
const newItem = await this.saveNewItem({ userUuid: dto.userUuid, itemHash, sessionUuid: dto.sessionUuid })
const newItemOrError = await this.saveNewItem.execute({
userUuid: dto.userUuid,
itemHash,
sessionUuid: dto.sessionUuid,
})
if (newItemOrError.isFailed()) {
this.logger.error(
`[${dto.userUuid}] Saving item ${itemHash.uuid} failed. Error: ${newItemOrError.getError()}`,
)
conflicts.push({
unsavedItem: itemHash,
type: ConflictType.UuidConflict,
})
continue
}
const newItem = newItemOrError.getValue()
savedItems.push(newItem)
} catch (error) {
this.logger.error(`[${dto.userUuid}] Saving item ${itemHash.uuid} failed. Error: ${(error as Error).message}`)
@@ -153,15 +178,15 @@ export class ItemService implements ItemServiceInterface {
async frontLoadKeysItemsToTop(userUuid: string, retrievedItems: Array<Item>): Promise<Array<Item>> {
const itemsKeys = await this.itemRepository.findAll({
userUuid,
contentType: ContentType.ItemsKey,
contentType: ContentType.TYPES.ItemsKey,
sortBy: 'updated_at_timestamp',
sortOrder: 'ASC',
})
const retrievedItemsIds: Array<string> = retrievedItems.map((item: Item) => item.uuid)
const retrievedItemsIds: Array<string> = retrievedItems.map((item: Item) => item.id.toString())
itemsKeys.forEach((itemKey: Item) => {
if (retrievedItemsIds.indexOf(itemKey.uuid) === -1) {
if (retrievedItemsIds.indexOf(itemKey.id.toString()) === -1) {
retrievedItems.unshift(itemKey)
}
})
@@ -172,9 +197,9 @@ export class ItemService implements ItemServiceInterface {
private calculateSyncToken(lastUpdatedTimestamp: number, savedItems: Array<Item>): string {
if (savedItems.length) {
const sortedItems = savedItems.sort((itemA: Item, itemB: Item) => {
return itemA.updatedAtTimestamp > itemB.updatedAtTimestamp ? 1 : -1
return itemA.props.timestamps.updatedAt > itemB.props.timestamps.updatedAt ? 1 : -1
})
lastUpdatedTimestamp = sortedItems[sortedItems.length - 1].updatedAtTimestamp
lastUpdatedTimestamp = sortedItems[sortedItems.length - 1].props.timestamps.updatedAt
}
const lastUpdatedTimestampWithMicrosecondPreventingSyncDoubles = lastUpdatedTimestamp + 1
@@ -187,103 +212,6 @@ export class ItemService implements ItemServiceInterface {
).toString('base64')
}
private async updateExistingItem(dto: {
existingItem: Item
itemHash: ItemHash
sessionUuid: string | null
}): Promise<Item> {
dto.existingItem.updatedWithSession = dto.sessionUuid
dto.existingItem.contentSize = 0
if (dto.itemHash.content) {
dto.existingItem.content = dto.itemHash.content
}
if (dto.itemHash.content_type) {
dto.existingItem.contentType = dto.itemHash.content_type
}
if (dto.itemHash.deleted !== undefined) {
dto.existingItem.deleted = dto.itemHash.deleted
}
let wasMarkedAsDuplicate = false
if (dto.itemHash.duplicate_of) {
wasMarkedAsDuplicate = !dto.existingItem.duplicateOf
dto.existingItem.duplicateOf = dto.itemHash.duplicate_of
}
if (dto.itemHash.auth_hash) {
dto.existingItem.authHash = dto.itemHash.auth_hash
}
if (dto.itemHash.enc_item_key) {
dto.existingItem.encItemKey = dto.itemHash.enc_item_key
}
if (dto.itemHash.items_key_id) {
dto.existingItem.itemsKeyId = dto.itemHash.items_key_id
}
const updatedAt = this.timer.getTimestampInMicroseconds()
const secondsFromLastUpdate = this.timer.convertMicrosecondsToSeconds(
updatedAt - dto.existingItem.updatedAtTimestamp,
)
if (dto.itemHash.created_at_timestamp) {
dto.existingItem.createdAtTimestamp = dto.itemHash.created_at_timestamp
dto.existingItem.createdAt = this.timer.convertMicrosecondsToDate(dto.itemHash.created_at_timestamp)
} else if (dto.itemHash.created_at) {
dto.existingItem.createdAtTimestamp = this.timer.convertStringDateToMicroseconds(dto.itemHash.created_at)
dto.existingItem.createdAt = this.timer.convertStringDateToDate(dto.itemHash.created_at)
}
dto.existingItem.updatedAtTimestamp = updatedAt
dto.existingItem.updatedAt = this.timer.convertMicrosecondsToDate(updatedAt)
dto.existingItem.contentSize = Buffer.byteLength(JSON.stringify(this.itemProjector.projectFull(dto.existingItem)))
if (dto.itemHash.deleted === true) {
dto.existingItem.deleted = true
dto.existingItem.content = null
dto.existingItem.contentSize = 0
dto.existingItem.encItemKey = null
dto.existingItem.authHash = null
dto.existingItem.itemsKeyId = null
}
const savedItem = await this.itemRepository.save(dto.existingItem)
if (secondsFromLastUpdate >= this.revisionFrequency) {
if ([ContentType.Note, ContentType.File].includes(savedItem.contentType as ContentType)) {
await this.domainEventPublisher.publish(
this.domainEventFactory.createItemRevisionCreationRequested(savedItem.uuid, savedItem.userUuid),
)
}
}
if (wasMarkedAsDuplicate) {
await this.domainEventPublisher.publish(
this.domainEventFactory.createDuplicateItemSyncedEvent(savedItem.uuid, savedItem.userUuid),
)
}
return savedItem
}
private async saveNewItem(dto: { userUuid: string; itemHash: ItemHash; sessionUuid: string | null }): Promise<Item> {
const newItem = this.itemFactory.create(dto)
const savedItem = await this.itemRepository.save(newItem)
if ([ContentType.Note, ContentType.File].includes(savedItem.contentType as ContentType)) {
await this.domainEventPublisher.publish(
this.domainEventFactory.createItemRevisionCreationRequested(savedItem.uuid, savedItem.userUuid),
)
}
if (savedItem.duplicateOf) {
await this.domainEventPublisher.publish(
this.domainEventFactory.createDuplicateItemSyncedEvent(savedItem.uuid, savedItem.userUuid),
)
}
return savedItem
}
private getLastSyncTime(dto: GetItemsDTO): number | undefined {
let token = dto.syncToken
if (dto.cursorToken !== undefined && dto.cursorToken !== null) {

View File

@@ -1,11 +1,10 @@
import 'reflect-metadata'
import { ContentType } from '@standardnotes/common'
import { ApiVersion } from '../../Api/ApiVersion'
import { Item } from '../Item'
import { ContentFilter } from './ContentFilter'
import { ContentType } from '@standardnotes/domain-core'
describe('ContentFilter', () => {
let existingItem: Item
@@ -21,7 +20,7 @@ describe('ContentFilter', () => {
itemHash: {
uuid: '123e4567-e89b-12d3-a456-426655440000',
content: invalidContent as unknown as string,
content_type: ContentType.Note,
content_type: ContentType.TYPES.Note,
},
existingItem: null,
})
@@ -32,7 +31,7 @@ describe('ContentFilter', () => {
unsavedItem: {
uuid: '123e4567-e89b-12d3-a456-426655440000',
content: invalidContent,
content_type: ContentType.Note,
content_type: ContentType.TYPES.Note,
},
type: 'content_error',
},
@@ -50,7 +49,7 @@ describe('ContentFilter', () => {
itemHash: {
uuid: '123e4567-e89b-12d3-a456-426655440000',
content: validContent as unknown as string,
content_type: ContentType.Note,
content_type: ContentType.TYPES.Note,
},
existingItem,
})

View File

@@ -1,5 +1,5 @@
import { ContentType } from '@standardnotes/common'
import 'reflect-metadata'
import { ApiVersion } from '../../Api/ApiVersion'
import { Item } from '../Item'
@@ -27,7 +27,7 @@ describe('ContentTypeFilter', () => {
apiVersion: ApiVersion.v20200115,
itemHash: {
uuid: '123e4567-e89b-12d3-a456-426655440000',
content_type: invalidContentType as ContentType,
content_type: invalidContentType,
},
existingItem: null,
})
@@ -54,7 +54,7 @@ describe('ContentTypeFilter', () => {
apiVersion: ApiVersion.v20200115,
itemHash: {
uuid: '123e4567-e89b-12d3-a456-426655440000',
content_type: validContentType as ContentType,
content_type: validContentType,
},
existingItem,
})

View File

@@ -1,15 +1,14 @@
import { ContentType } from '@standardnotes/common'
import { ConflictType } from '@standardnotes/responses'
import { ContentType } from '@standardnotes/domain-core'
import { ItemSaveValidationDTO } from '../SaveValidator/ItemSaveValidationDTO'
import { ItemSaveRuleResult } from './ItemSaveRuleResult'
import { ItemSaveRuleInterface } from './ItemSaveRuleInterface'
import { ConflictType } from '@standardnotes/responses'
export class ContentTypeFilter implements ItemSaveRuleInterface {
async check(dto: ItemSaveValidationDTO): Promise<ItemSaveRuleResult> {
const validContentType = Object.values(ContentType).includes(dto.itemHash.content_type as ContentType)
if (!validContentType) {
const contentTypeOrError = ContentType.create(dto.itemHash.content_type)
if (contentTypeOrError.isFailed()) {
return {
passed: false,
conflict: {

View File

@@ -1,28 +1,41 @@
import 'reflect-metadata'
import { ContentType } from '@standardnotes/common'
import { ApiVersion } from '../../Api/ApiVersion'
import { Item } from '../Item'
import { OwnershipFilter } from './OwnershipFilter'
import { Uuid, ContentType, Dates, Timestamps, UniqueEntityId } from '@standardnotes/domain-core'
describe('OwnershipFilter', () => {
let existingItem: Item
const createFilter = () => new OwnershipFilter()
beforeEach(() => {
existingItem = {} as jest.Mocked<Item>
existingItem.userUuid = '2-3-4'
existingItem = Item.create(
{
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
updatedWithSession: null,
content: 'foobar',
contentType: ContentType.create(ContentType.TYPES.Note).getValue(),
encItemKey: null,
authHash: null,
itemsKeyId: null,
duplicateOf: null,
deleted: false,
dates: Dates.create(new Date(1616164633241311), new Date(1616164633241311)).getValue(),
timestamps: Timestamps.create(1616164633241311, 1616164633241311).getValue(),
},
new UniqueEntityId('00000000-0000-0000-0000-000000000000'),
).getValue()
})
it('should filter out items belonging to a different user', async () => {
const result = await createFilter().check({
userUuid: '1-2-3',
userUuid: '00000000-0000-0000-0000-000000000001',
apiVersion: ApiVersion.v20200115,
itemHash: {
uuid: '2-3-4',
content_type: ContentType.Note,
content_type: ContentType.TYPES.Note,
},
existingItem,
})
@@ -32,7 +45,7 @@ describe('OwnershipFilter', () => {
conflict: {
unsavedItem: {
uuid: '2-3-4',
content_type: ContentType.Note,
content_type: ContentType.TYPES.Note,
},
type: 'uuid_conflict',
},
@@ -40,14 +53,12 @@ describe('OwnershipFilter', () => {
})
it('should leave items belonging to the same user', async () => {
existingItem.userUuid = '1-2-3'
const result = await createFilter().check({
userUuid: '1-2-3',
userUuid: '00000000-0000-0000-0000-000000000000',
apiVersion: ApiVersion.v20200115,
itemHash: {
uuid: '2-3-4',
content_type: ContentType.Note,
content_type: ContentType.TYPES.Note,
},
existingItem,
})
@@ -59,11 +70,11 @@ describe('OwnershipFilter', () => {
it('should leave non existing items', async () => {
const result = await createFilter().check({
userUuid: '1-2-3',
userUuid: '00000000-0000-0000-0000-000000000000',
apiVersion: ApiVersion.v20200115,
itemHash: {
uuid: '2-3-4',
content_type: ContentType.Note,
content_type: ContentType.TYPES.Note,
},
existingItem: null,
})
@@ -72,4 +83,27 @@ describe('OwnershipFilter', () => {
passed: true,
})
})
it('should return an error if the user uuid is invalid', async () => {
const result = await createFilter().check({
userUuid: 'invalid',
apiVersion: ApiVersion.v20200115,
itemHash: {
uuid: '2-3-4',
content_type: ContentType.TYPES.Note,
},
existingItem,
})
expect(result).toEqual({
passed: false,
conflict: {
unsavedItem: {
uuid: '2-3-4',
content_type: ContentType.TYPES.Note,
},
type: 'uuid_error',
},
})
})
})

View File

@@ -2,10 +2,23 @@ import { ItemSaveValidationDTO } from '../SaveValidator/ItemSaveValidationDTO'
import { ItemSaveRuleResult } from './ItemSaveRuleResult'
import { ItemSaveRuleInterface } from './ItemSaveRuleInterface'
import { ConflictType } from '@standardnotes/responses'
import { Uuid } from '@standardnotes/domain-core'
export class OwnershipFilter implements ItemSaveRuleInterface {
async check(dto: ItemSaveValidationDTO): Promise<ItemSaveRuleResult> {
const itemBelongsToADifferentUser = dto.existingItem !== null && dto.existingItem.userUuid !== dto.userUuid
const userUuidOrError = Uuid.create(dto.userUuid)
if (userUuidOrError.isFailed()) {
return {
passed: false,
conflict: {
unsavedItem: dto.itemHash,
type: ConflictType.UuidError,
},
}
}
const userUuid = userUuidOrError.getValue()
const itemBelongsToADifferentUser = dto.existingItem !== null && !dto.existingItem.props.userUuid.equals(userUuid)
if (itemBelongsToADifferentUser) {
return {
passed: false,

View File

@@ -1,8 +1,7 @@
import 'reflect-metadata'
import { ContentType } from '@standardnotes/common'
import { Time, Timer, TimerInterface } from '@standardnotes/time'
import { ContentType, Dates, Timestamps, UniqueEntityId, Uuid } from '@standardnotes/domain-core'
import { ApiVersion } from '../../Api/ApiVersion'
@@ -26,28 +25,36 @@ describe('TimeDifferenceFilter', () => {
.fn()
.mockImplementation((date: string) => timeHelper.convertStringDateToMicroseconds(date))
existingItem = {
uuid: '1-2-3',
userUuid: '1-2-3',
createdAt: new Date(1616164633241311),
createdAtTimestamp: 1616164633241311,
updatedAt: new Date(1616164633241311),
updatedAtTimestamp: 1616164633241311,
} as jest.Mocked<Item>
existingItem = Item.create(
{
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
updatedWithSession: null,
content: 'foobar',
contentType: ContentType.create(ContentType.TYPES.Note).getValue(),
encItemKey: null,
authHash: null,
itemsKeyId: null,
duplicateOf: null,
deleted: false,
dates: Dates.create(new Date(1616164633241311), new Date(1616164633241311)).getValue(),
timestamps: Timestamps.create(1616164633241311, 1616164633241311).getValue(),
},
new UniqueEntityId('00000000-0000-0000-0000-000000000000'),
).getValue()
itemHash = {
uuid: '1-2-3',
content: 'asdqwe1',
content_type: ContentType.Note,
content_type: ContentType.TYPES.Note,
duplicate_of: null,
enc_item_key: 'qweqwe1',
items_key_id: 'asdasd1',
created_at: timeHelper.formatDate(
timeHelper.convertMicrosecondsToDate(existingItem.createdAtTimestamp),
timeHelper.convertMicrosecondsToDate(existingItem.props.timestamps.createdAt),
'YYYY-MM-DDTHH:mm:ss.SSS[Z]',
),
updated_at: timeHelper.formatDate(
timeHelper.convertMicrosecondsToDate(existingItem.updatedAtTimestamp + 1),
timeHelper.convertMicrosecondsToDate(existingItem.props.timestamps.updatedAt + 1),
'YYYY-MM-DDTHH:mm:ss.SSS[Z]',
),
} as jest.Mocked<ItemHash>
@@ -83,7 +90,7 @@ describe('TimeDifferenceFilter', () => {
})
it('should filter out items having update at timestamp different in microseconds precision', async () => {
itemHash.updated_at_timestamp = existingItem.updatedAtTimestamp + 1
itemHash.updated_at_timestamp = existingItem.props.timestamps.updatedAt + 1
const result = await createFilter().check({
userUuid: '1-2-3',
@@ -102,7 +109,7 @@ describe('TimeDifferenceFilter', () => {
})
it('should leave items having update at timestamp same in microseconds precision', async () => {
itemHash.updated_at_timestamp = existingItem.updatedAtTimestamp
itemHash.updated_at_timestamp = existingItem.props.timestamps.updatedAt
const result = await createFilter().check({
userUuid: '1-2-3',
@@ -119,7 +126,9 @@ describe('TimeDifferenceFilter', () => {
it('should filter out items having update at timestamp different by a second for legacy clients', async () => {
itemHash.updated_at = timeHelper.formatDate(
new Date(
timeHelper.convertMicrosecondsToMilliseconds(existingItem.updatedAtTimestamp) + Time.MicrosecondsInASecond + 1,
timeHelper.convertMicrosecondsToMilliseconds(existingItem.props.timestamps.updatedAt) +
Time.MicrosecondsInASecond +
1,
),
'YYYY-MM-DDTHH:mm:ss.SSS[Z]',
)
@@ -142,7 +151,7 @@ describe('TimeDifferenceFilter', () => {
it('should leave items having update at timestamp different by less then a second for legacy clients', async () => {
itemHash.updated_at = timeHelper.formatDate(
timeHelper.convertMicrosecondsToDate(existingItem.updatedAtTimestamp),
timeHelper.convertMicrosecondsToDate(existingItem.props.timestamps.updatedAt),
'YYYY-MM-DDTHH:mm:ss.SSS[Z]',
)
@@ -161,7 +170,7 @@ describe('TimeDifferenceFilter', () => {
it('should filter out items having update at timestamp different by a millisecond', async () => {
itemHash.updated_at = timeHelper.formatDate(
new Date(
timeHelper.convertMicrosecondsToMilliseconds(existingItem.updatedAtTimestamp) +
timeHelper.convertMicrosecondsToMilliseconds(existingItem.props.timestamps.updatedAt) +
Time.MicrosecondsInAMillisecond +
1,
),
@@ -186,7 +195,7 @@ describe('TimeDifferenceFilter', () => {
it('should leave items having update at timestamp different by less than a millisecond', async () => {
itemHash.updated_at = timeHelper.formatDate(
timeHelper.convertMicrosecondsToDate(existingItem.updatedAtTimestamp),
timeHelper.convertMicrosecondsToDate(existingItem.props.timestamps.updatedAt),
'YYYY-MM-DDTHH:mm:ss.SSS[Z]',
)

View File

@@ -31,7 +31,7 @@ export class TimeDifferenceFilter implements ItemSaveRuleInterface {
}
}
const ourUpdatedAtTimestamp = dto.existingItem.updatedAtTimestamp
const ourUpdatedAtTimestamp = dto.existingItem.props.timestamps.updatedAt
const difference = incomingUpdatedAtTimestamp - ourUpdatedAtTimestamp
if (this.itemHashHasMicrosecondsPrecision(dto.itemHash)) {

View File

@@ -1,72 +0,0 @@
import 'reflect-metadata'
import { ContentType } from '@standardnotes/common'
import { ApiVersion } from '../../Api/ApiVersion'
import { Item } from '../Item'
import { UuidFilter } from './UuidFilter'
describe('UuidFilter', () => {
const createFilter = () => new UuidFilter()
it('should filter out items with invalid uuid', async () => {
const invalidUuids = [
'c73bcdcc-2669-4bf6-81d3-e4an73fb11fd',
'c73bcdcc26694bf681d3e4ae73fb11fd',
'definitely-not-a-uuid',
'1-2-3',
'test',
"(select load_file('\\\\\\\\iugt7mazsk477",
'/etc/passwd',
"eval(compile('for x in range(1):\\n i",
]
for (const invalidUuid of invalidUuids) {
const result = await createFilter().check({
userUuid: '1-2-3',
apiVersion: ApiVersion.v20200115,
itemHash: {
uuid: invalidUuid,
content_type: ContentType.Note,
},
existingItem: null,
})
expect(result).toEqual({
passed: false,
conflict: {
unsavedItem: {
uuid: invalidUuid,
content_type: ContentType.Note,
},
type: 'uuid_error',
},
})
}
})
it('should leave items with valid uuid', async () => {
const validUuids = [
'123e4567-e89b-12d3-a456-426655440000',
'c73bcdcc-2669-4bf6-81d3-e4ae73fb11fd',
'C73BCDCC-2669-4Bf6-81d3-E4AE73FB11FD',
]
for (const validUuid of validUuids) {
const result = await createFilter().check({
userUuid: '1-2-3',
apiVersion: ApiVersion.v20200115,
itemHash: {
uuid: validUuid,
content_type: ContentType.Note,
},
existingItem: {} as jest.Mocked<Item>,
})
expect(result).toEqual({
passed: true,
})
}
})
})

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