Compare commits

..

55 Commits

Author SHA1 Message Date
standardci
c5d0d63ddd chore(release): publish new version
- @standardnotes/analytics@2.26.14
 - @standardnotes/api-gateway@1.74.12
 - @standardnotes/auth-server@1.143.0
 - @standardnotes/domain-core@1.29.0
 - @standardnotes/domain-events-infra@1.12.30
 - @standardnotes/domain-events@2.127.0
 - @standardnotes/event-store@1.11.42
 - @standardnotes/files-server@1.22.21
 - @standardnotes/home-server@1.15.58
 - @standardnotes/revisions-server@1.35.0
 - @standardnotes/scheduler-server@1.20.46
 - @standardnotes/settings@1.21.32
 - @standardnotes/syncing-server@1.97.0
 - @standardnotes/websockets-server@1.10.43
2023-09-15 15:13:07 +00:00
Karol Sójko
36f07c691a feat: refactor transition to minimize status changes (#828) 2023-09-15 16:56:08 +02:00
standardci
ac0390e7c3 chore(release): publish new version
- @standardnotes/auth-server@1.142.1
 - @standardnotes/home-server@1.15.57
 - @standardnotes/revisions-server@1.34.1
 - @standardnotes/syncing-server@1.96.1
2023-09-15 11:15:32 +00:00
Karol Sójko
0477507a6a fix: add debug logs for updating transition status on auth 2023-09-15 12:57:43 +02:00
Karol Sójko
3e7856c895 fix: add debug logs for transition status updates 2023-09-15 12:55:04 +02:00
standardci
6778a80f21 chore(release): publish new version
- @standardnotes/analytics@2.26.13
 - @standardnotes/api-gateway@1.74.11
 - @standardnotes/auth-server@1.142.0
 - @standardnotes/domain-events-infra@1.12.29
 - @standardnotes/domain-events@2.126.0
 - @standardnotes/event-store@1.11.41
 - @standardnotes/files-server@1.22.20
 - @standardnotes/home-server@1.15.56
 - @standardnotes/revisions-server@1.34.0
 - @standardnotes/scheduler-server@1.20.45
 - @standardnotes/syncing-server@1.96.0
 - @standardnotes/websockets-server@1.10.42
2023-09-15 09:49:09 +00:00
Karol Sójko
d4d49454a6 feat: add skipping verified transitions (#827)
* fix(syncing-server): remove transitioning individual users

* feat: add skipping verified transitions

* fix(auth): remove unused use case
2023-09-15 11:31:52 +02:00
standardci
04b52e6773 chore(release): publish new version
- @standardnotes/auth-server@1.141.14
 - @standardnotes/home-server@1.15.55
 - @standardnotes/syncing-server@1.95.19
2023-09-15 08:53:53 +00:00
Karol Sójko
2a1859e4be fix(auth): remove extensive logs from updating transitions 2023-09-15 10:35:50 +02:00
Karol Sójko
dd9a9c68cb fix(auth): upgrade simplewebauthn dependency (#826) 2023-09-15 10:35:14 +02:00
Karol Sójko
9147ff5d49 fix(syncing-server): remove unused index in mongodb 2023-09-14 14:18:20 +02:00
standardci
503b84531b chore(release): publish new version
- @standardnotes/auth-server@1.141.13
 - @standardnotes/home-server@1.15.54
 - @standardnotes/revisions-server@1.33.21
 - @standardnotes/syncing-server@1.95.18
2023-09-14 10:16:54 +00:00
Karol Sójko
fe8ca828fb fix(auth): set ttl for started and not picked up transitions to 10h 2023-09-14 11:57:21 +02:00
Karol Sójko
03a4a3f2ab fix: skip already updated items and revisions in integrity check (#825) 2023-09-14 11:54:30 +02:00
Karol Sójko
3a8607d146 fix(syncing-server): updating with missing creation date (#824) 2023-09-14 11:45:24 +02:00
standardci
93b6e65554 chore(release): publish new version
- @standardnotes/auth-server@1.141.12
 - @standardnotes/home-server@1.15.53
2023-09-13 10:22:30 +00:00
Karol Sójko
5984e4c3e7 fix(auth): remove re-triggering revisions transition 2023-09-13 11:51:34 +02:00
standardci
b4257c10ea chore(release): publish new version
- @standardnotes/auth-server@1.141.11
 - @standardnotes/home-server@1.15.52
2023-09-13 09:41:28 +00:00
Karol Sójko
c164bde847 fix(auth): passing transition timestamp 2023-09-13 11:03:32 +02:00
standardci
883df939dd chore(release): publish new version
- @standardnotes/api-gateway@1.74.10
 - @standardnotes/auth-server@1.141.10
 - @standardnotes/home-server@1.15.51
 - @standardnotes/revisions-server@1.33.20
 - @standardnotes/syncing-server@1.95.17
2023-09-13 08:40:17 +00:00
Karol Sójko
c7807d0f9e fix: adjust transition timestamps to be universal 2023-09-13 10:25:02 +02:00
standardci
fc90343aaa chore(release): publish new version
- @standardnotes/home-server@1.15.50
 - @standardnotes/revisions-server@1.33.19
 - @standardnotes/syncing-server@1.95.16
2023-09-13 08:17:45 +00:00
Karol Sójko
fbcb45c3a2 fix: include handling updated items in revisions in secondary 2023-09-13 10:00:02 +02:00
standardci
179d8eaaa1 chore(release): publish new version
- @standardnotes/analytics@2.26.12
 - @standardnotes/api-gateway@1.74.9
 - @standardnotes/auth-server@1.141.9
 - @standardnotes/domain-events-infra@1.12.28
 - @standardnotes/domain-events@2.125.4
 - @standardnotes/event-store@1.11.40
 - @standardnotes/files-server@1.22.19
 - @standardnotes/home-server@1.15.49
 - @standardnotes/revisions-server@1.33.18
 - @standardnotes/scheduler-server@1.20.44
 - @standardnotes/syncing-server@1.95.15
 - @standardnotes/websockets-server@1.10.41
2023-09-13 07:47:36 +00:00
Karol Sójko
38685c1861 fix: display transition progress in logs 2023-09-13 09:31:07 +02:00
standardci
cdf42fbe2d chore(release): publish new version
- @standardnotes/home-server@1.15.48
 - @standardnotes/revisions-server@1.33.17
 - @standardnotes/syncing-server@1.95.14
2023-09-13 06:39:14 +00:00
Karol Sójko
9be4c002b7 fix: setting status for already migrated users 2023-09-13 08:21:21 +02:00
standardci
a16c5307a0 chore(release): publish new version
- @standardnotes/home-server@1.15.47
 - @standardnotes/revisions-server@1.33.16
 - @standardnotes/syncing-server@1.95.13
2023-09-13 04:50:15 +00:00
Karol Sójko
d5536f5430 fix(syncing-server): case insensitive integrity check 2023-09-13 06:33:31 +02:00
Karol Sójko
b1d88b15be fix: cleanup only for 0 new items 2023-09-12 22:51:39 +02:00
Karol Sójko
ff78285e43 fix(syncing-server): add catch up timeout for secondary db 2023-09-12 22:24:48 +02:00
standardci
1a26221385 chore(release): publish new version
- @standardnotes/auth-server@1.141.8
 - @standardnotes/home-server@1.15.46
 - @standardnotes/revisions-server@1.33.15
 - @standardnotes/syncing-server@1.95.12
2023-09-12 20:05:02 +00:00
Karol Sójko
54113abe2a fix: imports 2023-09-12 21:31:58 +02:00
Karol Sójko
afe385aed4 fix(auth): remove the transition role constraint 2023-09-12 21:28:48 +02:00
Karol Sójko
f055e52e06 fix(auth): add transition role only if the items transition has completed 2023-09-12 21:26:40 +02:00
Karol Sójko
fab5d18064 fix: sync between primary and secondary database with diff 2023-09-12 21:00:01 +02:00
standardci
a1e654a0d0 chore(release): publish new version
- @standardnotes/home-server@1.15.45
 - @standardnotes/revisions-server@1.33.14
2023-09-12 16:21:14 +00:00
Karol Sójko
aa835268ea fix(revisions): handle transitions with already existing data in secondary 2023-09-12 17:42:11 +02:00
standardci
74b4312928 chore(release): publish new version
- @standardnotes/home-server@1.15.44
 - @standardnotes/syncing-server@1.95.11
2023-09-12 15:17:12 +00:00
Karol Sójko
e91a832152 fix(syncing-server): binding 2023-09-12 16:41:39 +02:00
standardci
4f95bbee3f chore(release): publish new version
- @standardnotes/analytics@2.26.11
 - @standardnotes/api-gateway@1.74.8
 - @standardnotes/auth-server@1.141.7
 - @standardnotes/domain-events-infra@1.12.27
 - @standardnotes/domain-events@2.125.3
 - @standardnotes/event-store@1.11.39
 - @standardnotes/files-server@1.22.18
 - @standardnotes/home-server@1.15.43
 - @standardnotes/revisions-server@1.33.13
 - @standardnotes/scheduler-server@1.20.43
 - @standardnotes/syncing-server@1.95.10
 - @standardnotes/websockets-server@1.10.40
2023-09-12 13:37:37 +00:00
Karol Sójko
b9c9f74d0c fix(syncing-server): log syncing errors 2023-09-12 15:03:11 +02:00
Karol Sójko
e535cd504c fix: retry failed revision transitions 2023-09-12 14:45:17 +02:00
standardci
db0360860a chore(release): publish new version
- @standardnotes/home-server@1.15.42
 - @standardnotes/revisions-server@1.33.12
 - @standardnotes/syncing-server@1.95.9
2023-09-12 10:57:29 +00:00
Karol Sójko
aa2b5f3b74 chore: add logs for failed item dumps 2023-09-12 12:13:55 +02:00
standardci
6241661e27 chore(release): publish new version
- @standardnotes/home-server@1.15.41
 - @standardnotes/syncing-server@1.95.8
2023-09-12 09:38:19 +00:00
Karol Sójko
25047bf46d fix(syncing-server): allow fetching shared vault users for members (#821) 2023-09-12 11:00:55 +02:00
standardci
a1820ed212 chore(release): publish new version
- @standardnotes/analytics@2.26.10
 - @standardnotes/api-gateway@1.74.7
 - @standardnotes/auth-server@1.141.6
 - @standardnotes/domain-core@1.28.1
 - @standardnotes/event-store@1.11.38
 - @standardnotes/files-server@1.22.17
 - @standardnotes/home-server@1.15.40
 - @standardnotes/revisions-server@1.33.11
 - @standardnotes/scheduler-server@1.20.42
 - @standardnotes/settings@1.21.31
 - @standardnotes/syncing-server@1.95.7
 - @standardnotes/websockets-server@1.10.39
2023-09-12 08:55:11 +00:00
Karol Sójko
0a1d1624e8 fix: comparing uuids 2023-09-12 10:21:42 +02:00
standardci
7367de6832 chore(release): publish new version
- @standardnotes/analytics@2.26.9
 - @standardnotes/api-gateway@1.74.6
 - @standardnotes/auth-server@1.141.5
 - @standardnotes/domain-events-infra@1.12.26
 - @standardnotes/domain-events@2.125.2
 - @standardnotes/event-store@1.11.37
 - @standardnotes/files-server@1.22.16
 - @standardnotes/home-server@1.15.39
 - @standardnotes/revisions-server@1.33.10
 - @standardnotes/scheduler-server@1.20.41
 - @standardnotes/syncing-server@1.95.6
 - @standardnotes/websockets-server@1.10.38
2023-09-12 07:40:20 +00:00
Karol Sójko
f0abfe89fc fix: domain event values 2023-09-12 08:59:28 +02:00
standardci
d1244d165a chore(release): publish new version
- @standardnotes/analytics@2.26.8
 - @standardnotes/api-gateway@1.74.5
 - @standardnotes/auth-server@1.141.4
 - @standardnotes/domain-events-infra@1.12.25
 - @standardnotes/domain-events@2.125.1
 - @standardnotes/event-store@1.11.36
 - @standardnotes/files-server@1.22.15
 - @standardnotes/home-server@1.15.38
 - @standardnotes/revisions-server@1.33.9
 - @standardnotes/scheduler-server@1.20.40
 - @standardnotes/security@1.13.1
 - @standardnotes/syncing-server@1.95.5
 - @standardnotes/websockets-server@1.10.37
2023-09-12 06:58:11 +00:00
Karol Sójko
106d8f9192 fix: adjust transitions to not create revisions during ongoing revisions transition 2023-09-12 08:22:50 +02:00
standardci
1d86ba8fcb chore(release): publish new version
- @standardnotes/auth-server@1.141.3
 - @standardnotes/home-server@1.15.37
 - @standardnotes/revisions-server@1.33.8
 - @standardnotes/syncing-server@1.95.4
2023-09-12 05:31:57 +00:00
Karol Sójko
f20a947f8a fix: transition adjustments 2023-09-12 06:52:34 +02:00
132 changed files with 2000 additions and 1916 deletions

106
.pnp.cjs generated
View File

@@ -3326,10 +3326,10 @@ const RAW_RUNTIME_STATE =
}]\
]],\
["@hexagon/base64", [\
["npm:1.1.26", {\
"packageLocation": "./.yarn/cache/@hexagon-base64-npm-1.1.26-dbfda05df8-e42582ed12.zip/node_modules/@hexagon/base64/",\
["npm:1.1.27", {\
"packageLocation": "./.yarn/cache/@hexagon-base64-npm-1.1.27-df6f264962-899fffaf54.zip/node_modules/@hexagon/base64/",\
"packageDependencies": [\
["@hexagon/base64", "npm:1.1.26"]\
["@hexagon/base64", "npm:1.1.27"]\
],\
"linkType": "HARD"\
}]\
@@ -5067,44 +5067,29 @@ const RAW_RUNTIME_STATE =
"linkType": "HARD"\
}]\
]],\
["@simplewebauthn/iso-webcrypto", [\
["npm:7.2.0", {\
"packageLocation": "./.yarn/cache/@simplewebauthn-iso-webcrypto-npm-7.2.0-db7b12b859-b57899d0ad.zip/node_modules/@simplewebauthn/iso-webcrypto/",\
"packageDependencies": [\
["@simplewebauthn/iso-webcrypto", "npm:7.2.0"],\
["@simplewebauthn/typescript-types", "npm:7.0.0"],\
["@types/node", "npm:18.16.16"]\
],\
"linkType": "HARD"\
}]\
]],\
["@simplewebauthn/server", [\
["npm:7.2.0", {\
"packageLocation": "./.yarn/cache/@simplewebauthn-server-npm-7.2.0-f1ed5fde8a-2e37c87edd.zip/node_modules/@simplewebauthn/server/",\
["npm:8.1.1", {\
"packageLocation": "./.yarn/cache/@simplewebauthn-server-npm-8.1.1-106d3bd108-a07c2a067b.zip/node_modules/@simplewebauthn/server/",\
"packageDependencies": [\
["@simplewebauthn/server", "npm:7.2.0"],\
["@hexagon/base64", "npm:1.1.26"],\
["@simplewebauthn/server", "npm:8.1.1"],\
["@hexagon/base64", "npm:1.1.27"],\
["@peculiar/asn1-android", "npm:2.3.6"],\
["@peculiar/asn1-ecc", "npm:2.3.6"],\
["@peculiar/asn1-rsa", "npm:2.3.6"],\
["@peculiar/asn1-schema", "npm:2.3.6"],\
["@peculiar/asn1-x509", "npm:2.3.6"],\
["@simplewebauthn/iso-webcrypto", "npm:7.2.0"],\
["@simplewebauthn/typescript-types", "npm:7.0.0"],\
["@types/debug", "npm:4.1.8"],\
["@types/node", "npm:18.16.16"],\
["cbor-x", "npm:1.5.3"],\
["cross-fetch", "npm:3.1.6"],\
["debug", "virtual:ac3d8e680759ce54399273724d44e041d6c9b73454d191d411a8c44bb27e22f02aaf6ed9d3ad0ac1c298eac4833cff369c9c7b84c573016112c4f84be2cd8543#npm:4.3.4"]\
["@simplewebauthn/typescript-types", "npm:8.0.0"],\
["cbor-x", "npm:1.5.4"],\
["cross-fetch", "npm:4.0.0"]\
],\
"linkType": "HARD"\
}]\
]],\
["@simplewebauthn/typescript-types", [\
["npm:7.0.0", {\
"packageLocation": "./.yarn/cache/@simplewebauthn-typescript-types-npm-7.0.0-cc6ca20415-124238ea18.zip/node_modules/@simplewebauthn/typescript-types/",\
["npm:8.0.0", {\
"packageLocation": "./.yarn/cache/@simplewebauthn-typescript-types-npm-8.0.0-f3b313c27b-21e0b13268.zip/node_modules/@simplewebauthn/typescript-types/",\
"packageDependencies": [\
["@simplewebauthn/typescript-types", "npm:7.0.0"]\
["@simplewebauthn/typescript-types", "npm:8.0.0"]\
],\
"linkType": "HARD"\
}]\
@@ -5864,8 +5849,8 @@ const RAW_RUNTIME_STATE =
["@cbor-extract/cbor-extract-linux-arm64", "npm:2.1.1"],\
["@cbor-extract/cbor-extract-linux-x64", "npm:2.1.1"],\
["@newrelic/winston-enricher", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:4.0.1"],\
["@simplewebauthn/server", "npm:7.2.0"],\
["@simplewebauthn/typescript-types", "npm:7.0.0"],\
["@simplewebauthn/server", "npm:8.1.1"],\
["@simplewebauthn/typescript-types", "npm:8.0.0"],\
["@standardnotes/api", "npm:1.26.26"],\
["@standardnotes/common", "workspace:packages/common"],\
["@standardnotes/domain-core", "workspace:packages/domain-core"],\
@@ -6720,16 +6705,6 @@ const RAW_RUNTIME_STATE =
"linkType": "HARD"\
}]\
]],\
["@types/debug", [\
["npm:4.1.8", {\
"packageLocation": "./.yarn/cache/@types-debug-npm-4.1.8-a04e2ca136-9c190e8129.zip/node_modules/@types/debug/",\
"packageDependencies": [\
["@types/debug", "npm:4.1.8"],\
["@types/ms", "npm:0.7.31"]\
],\
"linkType": "HARD"\
}]\
]],\
["@types/dotenv", [\
["npm:8.2.0", {\
"packageLocation": "./.yarn/cache/@types-dotenv-npm-8.2.0-f4d0e3d65b-13f90a36f7.zip/node_modules/@types/dotenv/",\
@@ -6956,15 +6931,6 @@ const RAW_RUNTIME_STATE =
"linkType": "HARD"\
}]\
]],\
["@types/ms", [\
["npm:0.7.31", {\
"packageLocation": "./.yarn/cache/@types-ms-npm-0.7.31-ea3b89342b-cccb52777b.zip/node_modules/@types/ms/",\
"packageDependencies": [\
["@types/ms", "npm:0.7.31"]\
],\
"linkType": "HARD"\
}]\
]],\
["@types/newrelic", [\
["npm:9.14.0", {\
"packageLocation": "./.yarn/cache/@types-newrelic-npm-9.14.0-4668da51a1-2ec951bd8f.zip/node_modules/@types/newrelic/",\
@@ -6982,13 +6948,6 @@ const RAW_RUNTIME_STATE =
],\
"linkType": "HARD"\
}],\
["npm:18.16.16", {\
"packageLocation": "./.yarn/cache/@types-node-npm-18.16.16-8a41330dc3-946bd4d8e6.zip/node_modules/@types/node/",\
"packageDependencies": [\
["@types/node", "npm:18.16.16"]\
],\
"linkType": "HARD"\
}],\
["npm:20.2.5", {\
"packageLocation": "./.yarn/cache/@types-node-npm-20.2.5-0014d2d9ce-55e4f8d08e.zip/node_modules/@types/node/",\
"packageDependencies": [\
@@ -8706,10 +8665,10 @@ const RAW_RUNTIME_STATE =
}]\
]],\
["cbor-x", [\
["npm:1.5.3", {\
"packageLocation": "./.yarn/cache/cbor-x-npm-1.5.3-1d452dd267-d4df85b339.zip/node_modules/cbor-x/",\
["npm:1.5.4", {\
"packageLocation": "./.yarn/cache/cbor-x-npm-1.5.4-2d5a649a4b-742aea498a.zip/node_modules/cbor-x/",\
"packageDependencies": [\
["cbor-x", "npm:1.5.3"],\
["cbor-x", "npm:1.5.4"],\
["cbor-extract", "npm:2.1.1"]\
],\
"linkType": "HARD"\
@@ -9434,11 +9393,11 @@ const RAW_RUNTIME_STATE =
}]\
]],\
["cross-fetch", [\
["npm:3.1.6", {\
"packageLocation": "./.yarn/cache/cross-fetch-npm-3.1.6-cdb982d446-a8989fca82.zip/node_modules/cross-fetch/",\
["npm:4.0.0", {\
"packageLocation": "./.yarn/cache/cross-fetch-npm-4.0.0-9c67668db4-30e86b703a.zip/node_modules/cross-fetch/",\
"packageDependencies": [\
["cross-fetch", "npm:3.1.6"],\
["node-fetch", "virtual:0f92dfe7f9dc4fd492639d4a5b7805c2b27442bf599fd4f370b22a7966ba078f5d4525e2a8e8af29369f20e1833ed084bd52be59679efaa6c1c6c10cdbcd8baa#npm:2.6.11"]\
["cross-fetch", "npm:4.0.0"],\
["node-fetch", "virtual:9c67668db478e95ba4d6a763bc55027eeff0d22eaf59478017ea07386fc33a3c7b7b625af78aa86a33991a9a500a7aa216e28632de568f02adefd662ef53a42d#npm:2.7.0"]\
],\
"linkType": "HARD"\
}]\
@@ -14174,6 +14133,13 @@ const RAW_RUNTIME_STATE =
],\
"linkType": "SOFT"\
}],\
["npm:2.7.0", {\
"packageLocation": "./.yarn/cache/node-fetch-npm-2.7.0-587d57004e-a3ad788903.zip/node_modules/node-fetch/",\
"packageDependencies": [\
["node-fetch", "npm:2.7.0"]\
],\
"linkType": "SOFT"\
}],\
["npm:3.3.1", {\
"packageLocation": "./.yarn/cache/node-fetch-npm-3.3.1-576511fc5a-1d0c635bdf.zip/node_modules/node-fetch/",\
"packageDependencies": [\
@@ -14197,6 +14163,20 @@ const RAW_RUNTIME_STATE =
"encoding"\
],\
"linkType": "HARD"\
}],\
["virtual:9c67668db478e95ba4d6a763bc55027eeff0d22eaf59478017ea07386fc33a3c7b7b625af78aa86a33991a9a500a7aa216e28632de568f02adefd662ef53a42d#npm:2.7.0", {\
"packageLocation": "./.yarn/__virtual__/node-fetch-virtual-0ec1497d1c/0/cache/node-fetch-npm-2.7.0-587d57004e-a3ad788903.zip/node_modules/node-fetch/",\
"packageDependencies": [\
["node-fetch", "virtual:9c67668db478e95ba4d6a763bc55027eeff0d22eaf59478017ea07386fc33a3c7b7b625af78aa86a33991a9a500a7aa216e28632de568f02adefd662ef53a42d#npm:2.7.0"],\
["@types/encoding", null],\
["encoding", null],\
["whatwg-url", "npm:5.0.0"]\
],\
"packagePeers": [\
"@types/encoding",\
"encoding"\
],\
"linkType": "HARD"\
}]\
]],\
["node-gyp", [\

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -53,7 +53,7 @@ services:
image: mysql:8
container_name: db-ci
env_file: .github/ci.env
expose:
ports:
- 3306
restart: unless-stopped
command: --default-authentication-plugin=mysql_native_password --character-set-server=utf8mb4 --collation-server=utf8mb4_general_ci
@@ -66,7 +66,7 @@ services:
secondary_db:
image: mongo:5.0
container_name: secondary_db-ci
expose:
ports:
- 27017
restart: unless-stopped
volumes:
@@ -83,7 +83,7 @@ services:
container_name: cache-ci
volumes:
- ./data/redis/:/data
expose:
ports:
- 6379
restart: unless-stopped
networks:

View File

@@ -3,6 +3,34 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [2.26.14](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.26.13...@standardnotes/analytics@2.26.14) (2023-09-15)
**Note:** Version bump only for package @standardnotes/analytics
## [2.26.13](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.26.12...@standardnotes/analytics@2.26.13) (2023-09-15)
**Note:** Version bump only for package @standardnotes/analytics
## [2.26.12](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.26.11...@standardnotes/analytics@2.26.12) (2023-09-13)
**Note:** Version bump only for package @standardnotes/analytics
## [2.26.11](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.26.10...@standardnotes/analytics@2.26.11) (2023-09-12)
**Note:** Version bump only for package @standardnotes/analytics
## [2.26.10](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.26.9...@standardnotes/analytics@2.26.10) (2023-09-12)
**Note:** Version bump only for package @standardnotes/analytics
## [2.26.9](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.26.8...@standardnotes/analytics@2.26.9) (2023-09-12)
**Note:** Version bump only for package @standardnotes/analytics
## [2.26.8](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.26.7...@standardnotes/analytics@2.26.8) (2023-09-12)
**Note:** Version bump only for package @standardnotes/analytics
## [2.26.7](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.26.6...@standardnotes/analytics@2.26.7) (2023-09-08)
**Note:** Version bump only for package @standardnotes/analytics

View File

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

View File

@@ -3,6 +3,40 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.74.12](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.74.11...@standardnotes/api-gateway@1.74.12) (2023-09-15)
**Note:** Version bump only for package @standardnotes/api-gateway
## [1.74.11](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.74.10...@standardnotes/api-gateway@1.74.11) (2023-09-15)
**Note:** Version bump only for package @standardnotes/api-gateway
## [1.74.10](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.74.9...@standardnotes/api-gateway@1.74.10) (2023-09-13)
### Bug Fixes
* adjust transition timestamps to be universal ([c7807d0](https://github.com/standardnotes/api-gateway/commit/c7807d0f9e69ce572c4c03ff606375d706f24d9f))
## [1.74.9](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.74.8...@standardnotes/api-gateway@1.74.9) (2023-09-13)
**Note:** Version bump only for package @standardnotes/api-gateway
## [1.74.8](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.74.7...@standardnotes/api-gateway@1.74.8) (2023-09-12)
**Note:** Version bump only for package @standardnotes/api-gateway
## [1.74.7](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.74.6...@standardnotes/api-gateway@1.74.7) (2023-09-12)
**Note:** Version bump only for package @standardnotes/api-gateway
## [1.74.6](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.74.5...@standardnotes/api-gateway@1.74.6) (2023-09-12)
**Note:** Version bump only for package @standardnotes/api-gateway
## [1.74.5](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.74.4...@standardnotes/api-gateway@1.74.5) (2023-09-12)
**Note:** Version bump only for package @standardnotes/api-gateway
## [1.74.4](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.74.3...@standardnotes/api-gateway@1.74.4) (2023-09-11)
### Bug Fixes

View File

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

View File

@@ -80,15 +80,6 @@ export class UsersController extends BaseHttpController {
)
}
@httpGet('/transition-status', TYPES.ApiGateway_RequiredCrossServiceTokenMiddleware)
async getTransitionStatus(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(
request,
response,
this.endpointResolver.resolveEndpointOrMethodIdentifier('GET', 'users/transition-status'),
)
}
@httpGet('/:userId/params', TYPES.ApiGateway_RequiredCrossServiceTokenMiddleware)
async getKeyParams(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(

View File

@@ -43,7 +43,6 @@ export class EndpointResolver implements EndpointResolverInterface {
['[PATCH]:users/:userId', 'auth.users.update'],
['[PUT]:users/:userUuid/attributes/credentials', 'auth.users.updateCredentials'],
['[GET]:users/params', 'auth.users.getKeyParams'],
['[GET]:users/transition-status', 'auth.users.transition-status'],
['[DELETE]:users/:userUuid', 'auth.users.delete'],
['[POST]:listed', 'auth.users.createListedAccount'],
['[POST]:auth', 'auth.users.register'],
@@ -59,13 +58,11 @@ export class EndpointResolver implements EndpointResolverInterface {
// Syncing Server
['[POST]:items/sync', 'sync.items.sync'],
['[POST]:items/check-integrity', 'sync.items.check_integrity'],
['[POST]:items/transition', 'sync.items.transition'],
['[GET]:items/:uuid', 'sync.items.get_item'],
// Revisions Controller V2
['[GET]:items/:itemUuid/revisions', 'revisions.revisions.getRevisions'],
['[GET]:items/:itemUuid/revisions/:id', 'revisions.revisions.getRevision'],
['[DELETE]:items/:itemUuid/revisions/:id', 'revisions.revisions.deleteRevision'],
['[POST]:revisions/transition', 'revisions.revisions.transition'],
// Messages Controller
['[GET]:messages/', 'sync.messages.get-received'],
['[GET]:messages/outbound', 'sync.messages.get-sent'],

View File

@@ -3,6 +3,97 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.143.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.142.1...@standardnotes/auth-server@1.143.0) (2023-09-15)
### Features
* refactor transition to minimize status changes ([#828](https://github.com/standardnotes/server/issues/828)) ([36f07c6](https://github.com/standardnotes/server/commit/36f07c691afc213ecf817d6e98f885ddb19a6ed6))
## [1.142.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.142.0...@standardnotes/auth-server@1.142.1) (2023-09-15)
### Bug Fixes
* add debug logs for updating transition status on auth ([0477507](https://github.com/standardnotes/server/commit/0477507a6a951dc2bb904711c870e2c40a0664bd))
# [1.142.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.141.14...@standardnotes/auth-server@1.142.0) (2023-09-15)
### Features
* add skipping verified transitions ([#827](https://github.com/standardnotes/server/issues/827)) ([d4d4945](https://github.com/standardnotes/server/commit/d4d49454a68de0acdf440dc202fa14b9743905f6))
## [1.141.14](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.141.13...@standardnotes/auth-server@1.141.14) (2023-09-15)
### Bug Fixes
* **auth:** remove extensive logs from updating transitions ([2a1859e](https://github.com/standardnotes/server/commit/2a1859e4beff4cc7c4348ebbff8357a8e061bf5e))
* **auth:** upgrade simplewebauthn dependency ([#826](https://github.com/standardnotes/server/issues/826)) ([dd9a9c6](https://github.com/standardnotes/server/commit/dd9a9c68cb431a61700a8e5d8247eb1b505a9d62))
## [1.141.13](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.141.12...@standardnotes/auth-server@1.141.13) (2023-09-14)
### Bug Fixes
* **auth:** set ttl for started and not picked up transitions to 10h ([fe8ca82](https://github.com/standardnotes/server/commit/fe8ca828fb37306e0e5056627e67366885e86861))
## [1.141.12](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.141.11...@standardnotes/auth-server@1.141.12) (2023-09-13)
### Bug Fixes
* **auth:** remove re-triggering revisions transition ([5984e4c](https://github.com/standardnotes/server/commit/5984e4c3e7e550e5ed53805bde1e6dabcbe54da8))
## [1.141.11](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.141.10...@standardnotes/auth-server@1.141.11) (2023-09-13)
### Bug Fixes
* **auth:** passing transition timestamp ([c164bde](https://github.com/standardnotes/server/commit/c164bde847b5974e74fd439f0d439526ad439443))
## [1.141.10](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.141.9...@standardnotes/auth-server@1.141.10) (2023-09-13)
### Bug Fixes
* adjust transition timestamps to be universal ([c7807d0](https://github.com/standardnotes/server/commit/c7807d0f9e69ce572c4c03ff606375d706f24d9f))
## [1.141.9](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.141.8...@standardnotes/auth-server@1.141.9) (2023-09-13)
### Bug Fixes
* display transition progress in logs ([38685c1](https://github.com/standardnotes/server/commit/38685c1861b13e398dd96aa39f2cf1aece2090fb))
## [1.141.8](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.141.7...@standardnotes/auth-server@1.141.8) (2023-09-12)
### Bug Fixes
* **auth:** add transition role only if the items transition has completed ([f055e52](https://github.com/standardnotes/server/commit/f055e52e06b6e93501abd340dfce214d5363bc30))
* **auth:** remove the transition role constraint ([afe385a](https://github.com/standardnotes/server/commit/afe385aed4ba5ca53d8ef429ae4154f4ccf81419))
* imports ([54113ab](https://github.com/standardnotes/server/commit/54113abe2a961720a3561e5ff3a0069046ea8d25))
## [1.141.7](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.141.6...@standardnotes/auth-server@1.141.7) (2023-09-12)
### Bug Fixes
* retry failed revision transitions ([e535cd5](https://github.com/standardnotes/server/commit/e535cd504cf1929539ff7faf13e9c1fdd2b7bfd1))
## [1.141.6](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.141.5...@standardnotes/auth-server@1.141.6) (2023-09-12)
**Note:** Version bump only for package @standardnotes/auth-server
## [1.141.5](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.141.4...@standardnotes/auth-server@1.141.5) (2023-09-12)
### Bug Fixes
* domain event values ([f0abfe8](https://github.com/standardnotes/server/commit/f0abfe89fca0049c47131389683efe2f5aff23f8))
## [1.141.4](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.141.3...@standardnotes/auth-server@1.141.4) (2023-09-12)
### Bug Fixes
* adjust transitions to not create revisions during ongoing revisions transition ([106d8f9](https://github.com/standardnotes/server/commit/106d8f9192f630794ca4ddc2c4503f2c6cd196e7))
## [1.141.3](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.141.2...@standardnotes/auth-server@1.141.3) (2023-09-12)
### Bug Fixes
* transition adjustments ([f20a947](https://github.com/standardnotes/server/commit/f20a947f8a555c074d8dc1543c7a8bf61d1d887e))
## [1.141.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.141.1...@standardnotes/auth-server@1.141.2) (2023-09-11)
### Bug Fixes

View File

@@ -10,29 +10,79 @@ import { Env } from '../src/Bootstrap/Env'
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
import { DomainEventFactoryInterface } from '../src/Domain/Event/DomainEventFactoryInterface'
import { UserRepositoryInterface } from '../src/Domain/User/UserRepositoryInterface'
import { TimerInterface } from '@standardnotes/time'
import { TransitionStatusRepositoryInterface } from '../src/Domain/Transition/TransitionStatusRepositoryInterface'
import { RoleName, TransitionStatus } from '@standardnotes/domain-core'
const inputArgs = process.argv.slice(2)
const startDateString = inputArgs[0]
const endDateString = inputArgs[1]
const requestTransition = async (
transitionStatusRepository: TransitionStatusRepositoryInterface,
userRepository: UserRepositoryInterface,
logger: Logger,
domainEventFactory: DomainEventFactoryInterface,
domainEventPublisher: DomainEventPublisherInterface,
timer: TimerInterface,
): Promise<void> => {
const startDate = new Date(startDateString)
const endDate = new Date(endDateString)
const users = await userRepository.findAllCreatedBetween(startDate, endDate)
logger.info(`Found ${users.length} users created between ${startDateString} and ${endDateString}`)
const timestamp = timer.getTimestampInMicroseconds()
logger.info(
`[TRANSITION ${timestamp}] Found ${users.length} users created between ${startDateString} and ${endDateString}`,
)
let usersTriggered = 0
for (const user of users) {
const transitionRequestedEvent = domainEventFactory.createTransitionRequestedEvent({ userUuid: user.uuid })
const itemsTransitionStatus = await transitionStatusRepository.getStatus(user.uuid, 'items')
const revisionsTransitionStatus = await transitionStatusRepository.getStatus(user.uuid, 'revisions')
await domainEventPublisher.publish(transitionRequestedEvent)
const userRoles = await user.roles
const userHasTransitionRole = userRoles.some((role) => role.name === RoleName.NAMES.TransitionUser)
const bothTransitionStatusesAreVerified =
itemsTransitionStatus?.value === TransitionStatus.STATUSES.Verified &&
revisionsTransitionStatus?.value === TransitionStatus.STATUSES.Verified
if (userHasTransitionRole && bothTransitionStatusesAreVerified) {
continue
}
if (itemsTransitionStatus?.value !== TransitionStatus.STATUSES.Verified) {
await transitionStatusRepository.remove(user.uuid, 'items')
await domainEventPublisher.publish(
domainEventFactory.createTransitionRequestedEvent({
userUuid: user.uuid,
type: 'items',
timestamp,
}),
)
}
if (revisionsTransitionStatus?.value !== TransitionStatus.STATUSES.Verified) {
await transitionStatusRepository.remove(user.uuid, 'revisions')
await domainEventPublisher.publish(
domainEventFactory.createTransitionRequestedEvent({
userUuid: user.uuid,
type: 'revisions',
timestamp,
}),
)
}
usersTriggered += 1
}
logger.info(
`[TRANSITION ${timestamp}] Triggered transition for ${usersTriggered} users created between ${startDateString} and ${endDateString}`,
)
}
const container = new ContainerConfigLoader('worker')
@@ -49,8 +99,21 @@ void container.load().then((container) => {
const userRepository: UserRepositoryInterface = container.get(TYPES.Auth_UserRepository)
const domainEventFactory: DomainEventFactoryInterface = container.get(TYPES.Auth_DomainEventFactory)
const domainEventPublisher: DomainEventPublisherInterface = container.get(TYPES.Auth_DomainEventPublisher)
const timer = container.get<TimerInterface>(TYPES.Auth_Timer)
const transitionStatusRepository = container.get<TransitionStatusRepositoryInterface>(
TYPES.Auth_TransitionStatusRepository,
)
Promise.resolve(requestTransition(userRepository, logger, domainEventFactory, domainEventPublisher))
Promise.resolve(
requestTransition(
transitionStatusRepository,
userRepository,
logger,
domainEventFactory,
domainEventPublisher,
timer,
),
)
.then(() => {
logger.info(`Finished transition request for users created between ${startDateString} and ${endDateString}`)

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/auth-server",
"version": "1.141.2",
"version": "1.143.0",
"engines": {
"node": ">=18.0.0 <21.0.0"
},
@@ -40,8 +40,8 @@
"@aws-sdk/client-sqs": "^3.332.0",
"@cbor-extract/cbor-extract-linux-arm64": "^2.1.1",
"@cbor-extract/cbor-extract-linux-x64": "^2.1.1",
"@simplewebauthn/server": "^7.2.0",
"@simplewebauthn/typescript-types": "^7.0.0",
"@simplewebauthn/server": "^8.1.1",
"@simplewebauthn/typescript-types": "^8.0.0",
"@standardnotes/api": "^1.26.26",
"@standardnotes/common": "workspace:*",
"@standardnotes/domain-core": "workspace:^",

View File

@@ -263,7 +263,6 @@ import { RedisTransitionStatusRepository } from '../Infra/Redis/RedisTransitionS
import { InMemoryTransitionStatusRepository } from '../Infra/InMemory/InMemoryTransitionStatusRepository'
import { TransitionStatusUpdatedEventHandler } from '../Domain/Handler/TransitionStatusUpdatedEventHandler'
import { UpdateTransitionStatus } from '../Domain/UseCase/UpdateTransitionStatus/UpdateTransitionStatus'
import { GetTransitionStatus } from '../Domain/UseCase/GetTransitionStatus/GetTransitionStatus'
import { TypeORMSharedVaultUser } from '../Infra/TypeORM/TypeORMSharedVaultUser'
import { SharedVaultUserPersistenceMapper } from '../Mapping/SharedVaultUserPersistenceMapper'
import { SharedVaultUserRepositoryInterface } from '../Domain/SharedVault/SharedVaultUserRepositoryInterface'
@@ -943,14 +942,7 @@ export class ContainerConfigLoader {
new UpdateTransitionStatus(
container.get<TransitionStatusRepositoryInterface>(TYPES.Auth_TransitionStatusRepository),
container.get<RoleServiceInterface>(TYPES.Auth_RoleService),
),
)
container
.bind<GetTransitionStatus>(TYPES.Auth_GetTransitionStatus)
.toConstantValue(
new GetTransitionStatus(
container.get<TransitionStatusRepositoryInterface>(TYPES.Auth_TransitionStatusRepository),
container.get<UserRepositoryInterface>(TYPES.Auth_UserRepository),
container.get<winston.Logger>(TYPES.Auth_Logger),
),
)
container
@@ -1275,7 +1267,6 @@ export class ContainerConfigLoader {
container.get<ClearLoginAttempts>(TYPES.Auth_ClearLoginAttempts),
container.get<IncreaseLoginAttempts>(TYPES.Auth_IncreaseLoginAttempts),
container.get<ChangeCredentials>(TYPES.Auth_ChangeCredentials),
container.get<GetTransitionStatus>(TYPES.Auth_GetTransitionStatus),
container.get<ControllerContainerInterface>(TYPES.Auth_ControllerContainer),
),
)

View File

@@ -159,7 +159,6 @@ const TYPES = {
Auth_GetUserKeyParamsRecovery: Symbol.for('Auth_GetUserKeyParamsRecovery'),
Auth_UpdateStorageQuotaUsedForUser: Symbol.for('Auth_UpdateStorageQuotaUsedForUser'),
Auth_UpdateTransitionStatus: Symbol.for('Auth_UpdateTransitionStatus'),
Auth_GetTransitionStatus: Symbol.for('Auth_GetTransitionStatus'),
Auth_AddSharedVaultUser: Symbol.for('Auth_AddSharedVaultUser'),
Auth_RemoveSharedVaultUser: Symbol.for('Auth_RemoveSharedVaultUser'),
// Handlers

View File

@@ -33,7 +33,11 @@ import { DomainEventFactoryInterface } from './DomainEventFactoryInterface'
export class DomainEventFactory implements DomainEventFactoryInterface {
constructor(@inject(TYPES.Auth_Timer) private timer: TimerInterface) {}
createTransitionRequestedEvent(dto: { userUuid: string }): TransitionRequestedEvent {
createTransitionRequestedEvent(dto: {
userUuid: string
type: 'items' | 'revisions'
timestamp: number
}): TransitionRequestedEvent {
return {
type: 'TRANSITION_REQUESTED',
createdAt: this.timer.getUTCDate(),

View File

@@ -90,5 +90,9 @@ export interface DomainEventFactoryInterface {
}): StatisticPersistenceRequestedEvent
createSessionCreatedEvent(dto: { userUuid: string }): SessionCreatedEvent
createSessionRefreshedEvent(dto: { userUuid: string }): SessionRefreshedEvent
createTransitionRequestedEvent(dto: { userUuid: string }): TransitionRequestedEvent
createTransitionRequestedEvent(dto: {
userUuid: string
type: 'items' | 'revisions'
timestamp: number
}): TransitionRequestedEvent
}

View File

@@ -13,6 +13,7 @@ export class TransitionStatusUpdatedEventHandler implements DomainEventHandlerIn
status: event.payload.status,
userUuid: event.payload.userUuid,
transitionType: event.payload.transitionType,
transitionTimestamp: event.payload.transitionTimestamp,
})
if (result.isFailed()) {

View File

@@ -1,5 +1,7 @@
import { TransitionStatus } from '@standardnotes/domain-core'
export interface TransitionStatusRepositoryInterface {
updateStatus(userUuid: string, transitionType: 'items' | 'revisions', status: 'STARTED' | 'FAILED'): Promise<void>
removeStatus(userUuid: string, transitionType: 'items' | 'revisions'): Promise<void>
getStatus(userUuid: string, transitionType: 'items' | 'revisions'): Promise<'STARTED' | 'FAILED' | null>
updateStatus(userUuid: string, transitionType: 'items' | 'revisions', status: TransitionStatus): Promise<void>
getStatus(userUuid: string, transitionType: 'items' | 'revisions'): Promise<TransitionStatus | null>
remove(userUuid: string, transitionType: 'items' | 'revisions'): Promise<void>
}

View File

@@ -9,7 +9,14 @@ import { UserRepositoryInterface } from '../../User/UserRepositoryInterface'
import { CreateCrossServiceToken } from './CreateCrossServiceToken'
import { GetSetting } from '../GetSetting/GetSetting'
import { Result, SharedVaultUser, SharedVaultUserPermission, Timestamps, Uuid } from '@standardnotes/domain-core'
import {
Result,
SharedVaultUser,
SharedVaultUserPermission,
Timestamps,
TransitionStatus,
Uuid,
} from '@standardnotes/domain-core'
import { TransitionStatusRepositoryInterface } from '../../Transition/TransitionStatusRepositoryInterface'
import { SharedVaultUserRepositoryInterface } from '../../SharedVault/SharedVaultUserRepositoryInterface'
@@ -72,7 +79,9 @@ describe('CreateCrossServiceToken', () => {
getSettingUseCase.execute = jest.fn().mockReturnValue(Result.ok({ setting: { value: '100' } }))
transitionStatusRepository = {} as jest.Mocked<TransitionStatusRepositoryInterface>
transitionStatusRepository.getStatus = jest.fn().mockReturnValue('TO-DO')
transitionStatusRepository.getStatus = jest
.fn()
.mockReturnValue(TransitionStatus.create(TransitionStatus.STATUSES.Verified).getValue())
sharedVaultUserRepository = {} as jest.Mocked<SharedVaultUserRepositoryInterface>
sharedVaultUserRepository.findByUserUuid = jest.fn().mockReturnValue([
@@ -113,13 +122,16 @@ describe('CreateCrossServiceToken', () => {
uuid: '00000000-0000-0000-0000-000000000000',
},
ongoing_transition: false,
ongoing_revisions_transition: false,
},
60,
)
})
it('should create a cross service token for user that has an ongoing transaction', async () => {
transitionStatusRepository.getStatus = jest.fn().mockReturnValue('STARTED')
transitionStatusRepository.getStatus = jest
.fn()
.mockReturnValue(TransitionStatus.create(TransitionStatus.STATUSES.InProgress).getValue())
await createUseCase().execute({
user,
@@ -148,6 +160,7 @@ describe('CreateCrossServiceToken', () => {
uuid: '00000000-0000-0000-0000-000000000000',
},
ongoing_transition: true,
ongoing_revisions_transition: true,
},
60,
)
@@ -177,6 +190,7 @@ describe('CreateCrossServiceToken', () => {
uuid: '00000000-0000-0000-0000-000000000000',
},
ongoing_transition: false,
ongoing_revisions_transition: false,
},
60,
)
@@ -206,6 +220,7 @@ describe('CreateCrossServiceToken', () => {
uuid: '00000000-0000-0000-0000-000000000000',
},
ongoing_transition: false,
ongoing_revisions_transition: false,
},
60,
)
@@ -261,6 +276,7 @@ describe('CreateCrossServiceToken', () => {
email: 'test@test.te',
uuid: '00000000-0000-0000-0000-000000000000',
},
ongoing_revisions_transition: false,
ongoing_transition: false,
},
60,

View File

@@ -1,6 +1,6 @@
import { TokenEncoderInterface, CrossServiceTokenData } from '@standardnotes/security'
import { inject, injectable } from 'inversify'
import { Result, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
import { Result, TransitionStatus, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
import TYPES from '../../../Bootstrap/Types'
import { ProjectorInterface } from '../../../Projection/ProjectorInterface'
@@ -48,6 +48,7 @@ export class CreateCrossServiceToken implements UseCaseInterface<string> {
}
const transitionStatus = await this.transitionStatusRepository.getStatus(user.uuid, 'items')
const revisionsTransitionStatus = await this.transitionStatusRepository.getStatus(user.uuid, 'revisions')
const roles = await user.roles
@@ -59,7 +60,8 @@ export class CreateCrossServiceToken implements UseCaseInterface<string> {
user: this.projectUser(user),
roles: this.projectRoles(roles),
shared_vault_owner_context: undefined,
ongoing_transition: transitionStatus === 'STARTED',
ongoing_transition: transitionStatus?.value === TransitionStatus.STATUSES.InProgress,
ongoing_revisions_transition: revisionsTransitionStatus?.value === TransitionStatus.STATUSES.InProgress,
belongs_to_shared_vaults: sharedVaultAssociations.map((association) => ({
shared_vault_uuid: association.props.sharedVaultUuid.value,
permission: association.props.permission.value,

View File

@@ -35,7 +35,7 @@ export class GenerateAuthenticatorAuthenticationOptions
.update(`u2f-selector-${dto.username}${this.pseudoKeyParamsKey}`)
.digest('base64url')
const options = generateAuthenticationOptions({
const options = await generateAuthenticationOptions({
allowCredentials: [
{
id: Buffer.from(credentialIdHash),
@@ -56,7 +56,7 @@ export class GenerateAuthenticatorAuthenticationOptions
const userUuid = userUuidOrError.getValue()
const authenticators = await this.authenticatorRepository.findByUserUuid(userUuid)
const options = generateAuthenticationOptions({
const options = await generateAuthenticationOptions({
allowCredentials: authenticators.map((authenticator) => ({
id: authenticator.props.credentialId,
type: 'public-key',

View File

@@ -52,7 +52,7 @@ export class GenerateAuthenticatorRegistrationOptions
}
const authenticators = await this.authenticatorRepository.findByUserUuid(userUuid)
const options = generateRegistrationOptions({
const options = await generateRegistrationOptions({
rpID: this.relyingPartyId,
rpName: this.relyingPartyName,
userID: userUuid.value,

View File

@@ -1,114 +0,0 @@
import { RoleName } from '@standardnotes/domain-core'
import { Role } from '../../Role/Role'
import { TransitionStatusRepositoryInterface } from '../../Transition/TransitionStatusRepositoryInterface'
import { User } from '../../User/User'
import { UserRepositoryInterface } from '../../User/UserRepositoryInterface'
import { GetTransitionStatus } from './GetTransitionStatus'
describe('GetTransitionStatus', () => {
let transitionStatusRepository: TransitionStatusRepositoryInterface
let userRepository: UserRepositoryInterface
let user: User
let role: Role
const createUseCase = () => new GetTransitionStatus(transitionStatusRepository, userRepository)
beforeEach(() => {
transitionStatusRepository = {} as jest.Mocked<TransitionStatusRepositoryInterface>
transitionStatusRepository.getStatus = jest.fn().mockReturnValue(null)
role = {} as jest.Mocked<Role>
role.name = RoleName.NAMES.CoreUser
user = {
uuid: '00000000-0000-0000-0000-000000000000',
email: 'test@test.te',
} as jest.Mocked<User>
user.roles = Promise.resolve([role])
userRepository = {} as jest.Mocked<UserRepositoryInterface>
userRepository.findOneByUuid = jest.fn().mockReturnValue(user)
})
it('returns transition status FINISHED', async () => {
role.name = RoleName.NAMES.TransitionUser
user.roles = Promise.resolve([role])
userRepository.findOneByUuid = jest.fn().mockReturnValue(user)
const useCase = createUseCase()
const result = await useCase.execute({
userUuid: '00000000-0000-0000-0000-000000000000',
transitionType: 'items',
})
expect(result.isFailed()).toBeFalsy()
expect(result.getValue()).toEqual('FINISHED')
})
it('returns transition status STARTED', async () => {
const useCase = createUseCase()
transitionStatusRepository.getStatus = jest.fn().mockReturnValue('STARTED')
const result = await useCase.execute({
userUuid: '00000000-0000-0000-0000-000000000000',
transitionType: 'items',
})
expect(result.isFailed()).toBeFalsy()
expect(result.getValue()).toEqual('STARTED')
})
it('returns transition status TO-DO', async () => {
const useCase = createUseCase()
const result = await useCase.execute({
userUuid: '00000000-0000-0000-0000-000000000000',
transitionType: 'items',
})
expect(result.isFailed()).toBeFalsy()
expect(result.getValue()).toEqual('TO-DO')
})
it('returns transition status FAILED', async () => {
const useCase = createUseCase()
transitionStatusRepository.getStatus = jest.fn().mockReturnValue('FAILED')
const result = await useCase.execute({
userUuid: '00000000-0000-0000-0000-000000000000',
transitionType: 'items',
})
expect(result.isFailed()).toBeFalsy()
expect(result.getValue()).toEqual('FAILED')
})
it('return error if user uuid is invalid', async () => {
const useCase = createUseCase()
const result = await useCase.execute({
userUuid: 'invalid',
transitionType: 'items',
})
expect(result.isFailed()).toBeTruthy()
expect(result.getError()).toEqual('Given value is not a valid uuid: invalid')
})
it('return error if user not found', async () => {
const useCase = createUseCase()
userRepository.findOneByUuid = jest.fn().mockReturnValue(null)
const result = await useCase.execute({
userUuid: '00000000-0000-0000-0000-000000000000',
transitionType: 'items',
})
expect(result.isFailed()).toBeTruthy()
expect(result.getError()).toEqual('User not found.')
})
})

View File

@@ -1,39 +0,0 @@
import { Result, RoleName, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
import { GetTransitionStatusDTO } from './GetTransitionStatusDTO'
import { UserRepositoryInterface } from '../../User/UserRepositoryInterface'
import { TransitionStatusRepositoryInterface } from '../../Transition/TransitionStatusRepositoryInterface'
export class GetTransitionStatus implements UseCaseInterface<'TO-DO' | 'STARTED' | 'FINISHED' | 'FAILED'> {
constructor(
private transitionStatusRepository: TransitionStatusRepositoryInterface,
private userRepository: UserRepositoryInterface,
) {}
async execute(dto: GetTransitionStatusDTO): Promise<Result<'TO-DO' | 'STARTED' | 'FINISHED' | 'FAILED'>> {
const userUuidOrError = Uuid.create(dto.userUuid)
if (userUuidOrError.isFailed()) {
return Result.fail(userUuidOrError.getError())
}
const userUuid = userUuidOrError.getValue()
const user = await this.userRepository.findOneByUuid(userUuid)
if (user === null) {
return Result.fail('User not found.')
}
const roles = await user.roles
for (const role of roles) {
if (role.name === RoleName.NAMES.TransitionUser) {
return Result.ok('FINISHED')
}
}
const transitionStatus = await this.transitionStatusRepository.getStatus(userUuid.value, dto.transitionType)
if (transitionStatus === null) {
return Result.ok('TO-DO')
}
return Result.ok(transitionStatus)
}
}

View File

@@ -1,4 +0,0 @@
export interface GetTransitionStatusDTO {
userUuid: string
transitionType: 'items' | 'revisions'
}

View File

@@ -1,38 +1,40 @@
import { RoleName, Uuid } from '@standardnotes/domain-core'
import { RoleName, TransitionStatus, Uuid } from '@standardnotes/domain-core'
import { RoleServiceInterface } from '../../Role/RoleServiceInterface'
import { TransitionStatusRepositoryInterface } from '../../Transition/TransitionStatusRepositoryInterface'
import { UpdateTransitionStatus } from './UpdateTransitionStatus'
import { Logger } from 'winston'
describe('UpdateTransitionStatus', () => {
let transitionStatusRepository: TransitionStatusRepositoryInterface
let roleService: RoleServiceInterface
let logger: Logger
const createUseCase = () => new UpdateTransitionStatus(transitionStatusRepository, roleService)
const createUseCase = () => new UpdateTransitionStatus(transitionStatusRepository, roleService, logger)
beforeEach(() => {
logger = {} as jest.Mocked<Logger>
logger.info = jest.fn()
transitionStatusRepository = {} as jest.Mocked<TransitionStatusRepositoryInterface>
transitionStatusRepository.removeStatus = jest.fn()
transitionStatusRepository.updateStatus = jest.fn()
transitionStatusRepository.getStatus = jest.fn().mockResolvedValue(null)
roleService = {} as jest.Mocked<RoleServiceInterface>
roleService.addRoleToUser = jest.fn()
})
it('should remove transition status and add TransitionUser role', async () => {
it('should add TRANSITION_USER role', async () => {
const useCase = createUseCase()
const result = await useCase.execute({
userUuid: '00000000-0000-0000-0000-000000000000',
status: 'FINISHED',
status: 'VERIFIED',
transitionType: 'items',
transitionTimestamp: 123,
})
expect(result.isFailed()).toBeFalsy()
expect(transitionStatusRepository.removeStatus).toHaveBeenCalledWith(
'00000000-0000-0000-0000-000000000000',
'items',
)
expect(roleService.addRoleToUser).toHaveBeenCalledWith(
Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
RoleName.create(RoleName.NAMES.TransitionUser).getValue(),
@@ -44,16 +46,13 @@ describe('UpdateTransitionStatus', () => {
const result = await useCase.execute({
userUuid: '00000000-0000-0000-0000-000000000000',
status: 'STARTED',
status: TransitionStatus.STATUSES.InProgress,
transitionType: 'items',
transitionTimestamp: 123,
})
expect(result.isFailed()).toBeFalsy()
expect(transitionStatusRepository.updateStatus).toHaveBeenCalledWith(
'00000000-0000-0000-0000-000000000000',
'items',
'STARTED',
)
expect(transitionStatusRepository.updateStatus).toHaveBeenCalled()
})
it('should return error when user uuid is invalid', async () => {
@@ -63,9 +62,46 @@ describe('UpdateTransitionStatus', () => {
userUuid: 'invalid',
status: 'STARTED',
transitionType: 'items',
transitionTimestamp: 123,
})
expect(result.isFailed()).toBeTruthy()
expect(result.getError()).toEqual('Given value is not a valid uuid: invalid')
})
it('should not update status if transition is already verified', async () => {
transitionStatusRepository.getStatus = jest
.fn()
.mockResolvedValue(TransitionStatus.create(TransitionStatus.STATUSES.Verified).getValue())
const useCase = createUseCase()
const result = await useCase.execute({
userUuid: '00000000-0000-0000-0000-000000000000',
status: TransitionStatus.STATUSES.InProgress,
transitionType: 'items',
transitionTimestamp: 123,
})
expect(result.isFailed()).toBeFalsy()
expect(transitionStatusRepository.updateStatus).not.toHaveBeenCalled()
})
it('should not update status if transition is already failed', async () => {
transitionStatusRepository.getStatus = jest
.fn()
.mockResolvedValue(TransitionStatus.create(TransitionStatus.STATUSES.Failed).getValue())
const useCase = createUseCase()
const result = await useCase.execute({
userUuid: '00000000-0000-0000-0000-000000000000',
status: TransitionStatus.STATUSES.InProgress,
transitionType: 'items',
transitionTimestamp: 123,
})
expect(result.isFailed()).toBeFalsy()
expect(transitionStatusRepository.updateStatus).not.toHaveBeenCalled()
})
})

View File

@@ -1,12 +1,14 @@
import { Result, RoleName, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
import { Result, RoleName, TransitionStatus, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
import { TransitionStatusRepositoryInterface } from '../../Transition/TransitionStatusRepositoryInterface'
import { UpdateTransitionStatusDTO } from './UpdateTransitionStatusDTO'
import { RoleServiceInterface } from '../../Role/RoleServiceInterface'
import { Logger } from 'winston'
export class UpdateTransitionStatus implements UseCaseInterface<void> {
constructor(
private transitionStatusRepository: TransitionStatusRepositoryInterface,
private roleService: RoleServiceInterface,
private logger: Logger,
) {}
async execute(dto: UpdateTransitionStatusDTO): Promise<Result<void>> {
@@ -16,15 +18,22 @@ export class UpdateTransitionStatus implements UseCaseInterface<void> {
}
const userUuid = userUuidOrError.getValue()
if (dto.status === 'FINISHED') {
await this.transitionStatusRepository.removeStatus(dto.userUuid, dto.transitionType)
await this.roleService.addRoleToUser(userUuid, RoleName.create(RoleName.NAMES.TransitionUser).getValue())
const currentStatus = await this.transitionStatusRepository.getStatus(dto.userUuid, dto.transitionType)
if (
[TransitionStatus.STATUSES.Verified, TransitionStatus.STATUSES.Failed].includes(currentStatus?.value as string)
) {
this.logger.info(`User ${dto.userUuid} transition already finished.`)
return Result.ok()
}
await this.transitionStatusRepository.updateStatus(dto.userUuid, dto.transitionType, dto.status)
const transitionStatus = TransitionStatus.create(dto.status).getValue()
await this.transitionStatusRepository.updateStatus(dto.userUuid, dto.transitionType, transitionStatus)
if (dto.transitionType === 'items' && transitionStatus.value === TransitionStatus.STATUSES.Verified) {
await this.roleService.addRoleToUser(userUuid, RoleName.create(RoleName.NAMES.TransitionUser).getValue())
}
return Result.ok()
}

View File

@@ -1,5 +1,6 @@
export interface UpdateTransitionStatusDTO {
userUuid: string
transitionType: 'items' | 'revisions'
status: 'STARTED' | 'FINISHED' | 'FAILED'
transitionTimestamp: number
status: string
}

View File

@@ -1,22 +1,19 @@
import { TransitionStatus } from '@standardnotes/domain-core'
import { TransitionStatusRepositoryInterface } from '../../Domain/Transition/TransitionStatusRepositoryInterface'
export class InMemoryTransitionStatusRepository implements TransitionStatusRepositoryInterface {
private itemStatuses: Map<string, 'STARTED' | 'FAILED'> = new Map()
private revisionStatuses: Map<string, 'STARTED' | 'FAILED'> = new Map()
private itemStatuses: Map<string, string> = new Map()
private revisionStatuses: Map<string, string> = new Map()
async updateStatus(
userUuid: string,
transitionType: 'items' | 'revisions',
status: 'STARTED' | 'FAILED',
): Promise<void> {
async updateStatus(userUuid: string, transitionType: 'items' | 'revisions', status: TransitionStatus): Promise<void> {
if (transitionType === 'items') {
this.itemStatuses.set(userUuid, status)
this.itemStatuses.set(userUuid, status.value)
} else {
this.revisionStatuses.set(userUuid, status)
this.revisionStatuses.set(userUuid, status.value)
}
}
async removeStatus(userUuid: string, transitionType: 'items' | 'revisions'): Promise<void> {
async remove(userUuid: string, transitionType: 'items' | 'revisions'): Promise<void> {
if (transitionType === 'items') {
this.itemStatuses.delete(userUuid)
} else {
@@ -24,8 +21,8 @@ export class InMemoryTransitionStatusRepository implements TransitionStatusRepos
}
}
async getStatus(userUuid: string, transitionType: 'items' | 'revisions'): Promise<'STARTED' | 'FAILED' | null> {
let status: 'STARTED' | 'FAILED' | null = null
async getStatus(userUuid: string, transitionType: 'items' | 'revisions'): Promise<TransitionStatus | null> {
let status: string | null
if (transitionType === 'items') {
status = this.itemStatuses.get(userUuid) ?? null
@@ -33,6 +30,10 @@ export class InMemoryTransitionStatusRepository implements TransitionStatusRepos
status = this.revisionStatuses.get(userUuid) ?? null
}
return status
if (status === null) {
return null
}
return TransitionStatus.create(status).getValue()
}
}

View File

@@ -14,7 +14,6 @@ import { IncreaseLoginAttempts } from '../../Domain/UseCase/IncreaseLoginAttempt
import { InviteToSharedSubscription } from '../../Domain/UseCase/InviteToSharedSubscription/InviteToSharedSubscription'
import { UpdateUser } from '../../Domain/UseCase/UpdateUser'
import { User } from '../../Domain/User/User'
import { GetTransitionStatus } from '../../Domain/UseCase/GetTransitionStatus/GetTransitionStatus'
describe('AnnotatedUsersController', () => {
let updateUser: UpdateUser
@@ -25,7 +24,6 @@ describe('AnnotatedUsersController', () => {
let increaseLoginAttempts: IncreaseLoginAttempts
let changeCredentials: ChangeCredentials
let inviteToSharedSubscription: InviteToSharedSubscription
let getTransitionStatus: GetTransitionStatus
let request: express.Request
let response: express.Response
@@ -40,7 +38,6 @@ describe('AnnotatedUsersController', () => {
clearLoginAttempts,
increaseLoginAttempts,
changeCredentials,
getTransitionStatus,
)
beforeEach(() => {
@@ -72,9 +69,6 @@ describe('AnnotatedUsersController', () => {
inviteToSharedSubscription = {} as jest.Mocked<InviteToSharedSubscription>
inviteToSharedSubscription.execute = jest.fn()
getTransitionStatus = {} as jest.Mocked<GetTransitionStatus>
getTransitionStatus.execute = jest.fn()
request = {
headers: {},
body: {},

View File

@@ -18,7 +18,6 @@ import { ClearLoginAttempts } from '../../Domain/UseCase/ClearLoginAttempts'
import { IncreaseLoginAttempts } from '../../Domain/UseCase/IncreaseLoginAttempts'
import { ChangeCredentials } from '../../Domain/UseCase/ChangeCredentials/ChangeCredentials'
import { BaseUsersController } from './Base/BaseUsersController'
import { GetTransitionStatus } from '../../Domain/UseCase/GetTransitionStatus/GetTransitionStatus'
@controller('/users')
export class AnnotatedUsersController extends BaseUsersController {
@@ -30,7 +29,6 @@ export class AnnotatedUsersController extends BaseUsersController {
@inject(TYPES.Auth_ClearLoginAttempts) override clearLoginAttempts: ClearLoginAttempts,
@inject(TYPES.Auth_IncreaseLoginAttempts) override increaseLoginAttempts: IncreaseLoginAttempts,
@inject(TYPES.Auth_ChangeCredentials) override changeCredentialsUseCase: ChangeCredentials,
@inject(TYPES.Auth_GetTransitionStatus) override getTransitionStatusUseCase: GetTransitionStatus,
) {
super(
updateUser,
@@ -40,7 +38,6 @@ export class AnnotatedUsersController extends BaseUsersController {
clearLoginAttempts,
increaseLoginAttempts,
changeCredentialsUseCase,
getTransitionStatusUseCase,
)
}
@@ -54,11 +51,6 @@ export class AnnotatedUsersController extends BaseUsersController {
return super.keyParams(request)
}
@httpGet('/transition-status', TYPES.Auth_RequiredCrossServiceTokenMiddleware)
override async transitionStatus(request: Request, response: Response): Promise<results.JsonResult> {
return super.transitionStatus(request, response)
}
@httpDelete('/:userUuid', TYPES.Auth_RequiredCrossServiceTokenMiddleware)
override async deleteAccount(request: Request, response: Response): Promise<results.JsonResult> {
return super.deleteAccount(request, response)

View File

@@ -10,7 +10,6 @@ import { GetUserSubscription } from '../../../Domain/UseCase/GetUserSubscription
import { IncreaseLoginAttempts } from '../../../Domain/UseCase/IncreaseLoginAttempts'
import { UpdateUser } from '../../../Domain/UseCase/UpdateUser'
import { ErrorTag } from '@standardnotes/responses'
import { GetTransitionStatus } from '../../../Domain/UseCase/GetTransitionStatus/GetTransitionStatus'
export class BaseUsersController extends BaseHttpController {
constructor(
@@ -21,7 +20,6 @@ export class BaseUsersController extends BaseHttpController {
protected clearLoginAttempts: ClearLoginAttempts,
protected increaseLoginAttempts: IncreaseLoginAttempts,
protected changeCredentialsUseCase: ChangeCredentials,
protected getTransitionStatusUseCase: GetTransitionStatus,
private controllerContainer?: ControllerContainerInterface,
) {
super()
@@ -32,7 +30,6 @@ export class BaseUsersController extends BaseHttpController {
this.controllerContainer.register('auth.users.getSubscription', this.getSubscription.bind(this))
this.controllerContainer.register('auth.users.updateCredentials', this.changeCredentials.bind(this))
this.controllerContainer.register('auth.users.delete', this.deleteAccount.bind(this))
this.controllerContainer.register('auth.users.transition-status', this.transitionStatus.bind(this))
}
}
@@ -106,30 +103,6 @@ export class BaseUsersController extends BaseHttpController {
return this.json(result.keyParams)
}
async transitionStatus(_request: Request, response: Response): Promise<results.JsonResult> {
const result = await this.getTransitionStatusUseCase.execute({
userUuid: response.locals.user.uuid,
transitionType: 'items',
})
if (result.isFailed()) {
return this.json(
{
error: {
message: result.getError(),
},
},
400,
)
}
response.setHeader('x-invalidate-cache', response.locals.user.uuid)
return this.json({
status: result.getValue(),
})
}
async deleteAccount(request: Request, response: Response): Promise<results.JsonResult> {
if (request.params.userUuid !== response.locals.user.uuid) {
return this.json(

View File

@@ -1,30 +1,38 @@
import * as IORedis from 'ioredis'
import { TransitionStatusRepositoryInterface } from '../../Domain/Transition/TransitionStatusRepositoryInterface'
import { TransitionStatus } from '@standardnotes/domain-core'
export class RedisTransitionStatusRepository implements TransitionStatusRepositoryInterface {
private readonly PREFIX = 'transition'
constructor(private redisClient: IORedis.Redis) {}
async updateStatus(
userUuid: string,
transitionType: 'items' | 'revisions',
status: 'STARTED' | 'FAILED',
): Promise<void> {
await this.redisClient.set(`${this.PREFIX}:${transitionType}:${userUuid}`, status)
}
async removeStatus(userUuid: string, transitionType: 'items' | 'revisions'): Promise<void> {
async remove(userUuid: string, transitionType: 'items' | 'revisions'): Promise<void> {
await this.redisClient.del(`${this.PREFIX}:${transitionType}:${userUuid}`)
}
async getStatus(userUuid: string, transitionType: 'items' | 'revisions'): Promise<'STARTED' | 'FAILED' | null> {
const status = (await this.redisClient.get(`${this.PREFIX}:${transitionType}:${userUuid}`)) as
| 'STARTED'
| 'FAILED'
| null
async updateStatus(userUuid: string, transitionType: 'items' | 'revisions', status: TransitionStatus): Promise<void> {
switch (status.value) {
case TransitionStatus.STATUSES.Failed:
case TransitionStatus.STATUSES.Verified:
await this.redisClient.set(`${this.PREFIX}:${transitionType}:${userUuid}`, status.value)
break
case TransitionStatus.STATUSES.InProgress: {
const ttl2Hourse = 7_200
await this.redisClient.setex(`${this.PREFIX}:${transitionType}:${userUuid}`, ttl2Hourse, status.value)
break
}
}
}
return status
async getStatus(userUuid: string, transitionType: 'items' | 'revisions'): Promise<TransitionStatus | null> {
const status = await this.redisClient.get(`${this.PREFIX}:${transitionType}:${userUuid}`)
if (status === null) {
return null
}
return TransitionStatus.create(status).getValue()
}
}

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.
# [1.29.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-core@1.28.1...@standardnotes/domain-core@1.29.0) (2023-09-15)
### Features
* refactor transition to minimize status changes ([#828](https://github.com/standardnotes/server/issues/828)) ([36f07c6](https://github.com/standardnotes/server/commit/36f07c691afc213ecf817d6e98f885ddb19a6ed6))
## [1.28.1](https://github.com/standardnotes/server/compare/@standardnotes/domain-core@1.28.0...@standardnotes/domain-core@1.28.1) (2023-09-12)
### Bug Fixes
* comparing uuids ([0a1d162](https://github.com/standardnotes/server/commit/0a1d1624e818000f2e951f29323a88e6e233c755))
# [1.28.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-core@1.27.0...@standardnotes/domain-core@1.28.0) (2023-09-07)
### Features

View File

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

View File

@@ -8,6 +8,13 @@ describe('Uuid', () => {
expect(valueOrError.getValue().value).toEqual('84c0f8e8-544a-4c7e-9adf-26209303bc1d')
})
it('should create a value object on upper case', () => {
const valueOrError = Uuid.create('00B57455-B563-4B50-A2AA-B19762102219')
expect(valueOrError.isFailed()).toBeFalsy()
expect(valueOrError.getValue().value).toEqual('00B57455-B563-4B50-A2AA-B19762102219')
})
it('should not create an invalid value object', () => {
const valueOrError = Uuid.create('1-2-3')

View File

@@ -0,0 +1,28 @@
import { Result } from '../Core/Result'
import { ValueObject } from '../Core/ValueObject'
import { TransitionStatusProps } from './TransitionStatusProps'
export class TransitionStatus extends ValueObject<TransitionStatusProps> {
static readonly STATUSES = {
InProgress: 'IN_PROGRESS',
Failed: 'FAILED',
Verified: 'VERIFIED',
}
get value(): string {
return this.props.value
}
private constructor(props: TransitionStatusProps) {
super(props)
}
static create(name: string): Result<TransitionStatus> {
const isValidName = Object.values(this.STATUSES).includes(name)
if (!isValidName) {
return Result.fail<TransitionStatus>('Invalid transition status name.')
} else {
return Result.ok<TransitionStatus>(new TransitionStatus({ value: name }))
}
}
}

View File

@@ -0,0 +1,3 @@
export interface TransitionStatusProps {
value: string
}

View File

@@ -67,5 +67,8 @@ export * from './SharedVault/SharedVaultUserProps'
export * from './Subscription/SubscriptionPlanName'
export * from './Subscription/SubscriptionPlanNameProps'
export * from './Transition/TransitionStatus'
export * from './Transition/TransitionStatusProps'
export * from './UseCase/SyncUseCaseInterface'
export * from './UseCase/UseCaseInterface'

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.12.30](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.12.29...@standardnotes/domain-events-infra@1.12.30) (2023-09-15)
**Note:** Version bump only for package @standardnotes/domain-events-infra
## [1.12.29](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.12.28...@standardnotes/domain-events-infra@1.12.29) (2023-09-15)
**Note:** Version bump only for package @standardnotes/domain-events-infra
## [1.12.28](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.12.27...@standardnotes/domain-events-infra@1.12.28) (2023-09-13)
**Note:** Version bump only for package @standardnotes/domain-events-infra
## [1.12.27](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.12.26...@standardnotes/domain-events-infra@1.12.27) (2023-09-12)
**Note:** Version bump only for package @standardnotes/domain-events-infra
## [1.12.26](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.12.25...@standardnotes/domain-events-infra@1.12.26) (2023-09-12)
**Note:** Version bump only for package @standardnotes/domain-events-infra
## [1.12.25](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.12.24...@standardnotes/domain-events-infra@1.12.25) (2023-09-12)
**Note:** Version bump only for package @standardnotes/domain-events-infra
## [1.12.24](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.12.23...@standardnotes/domain-events-infra@1.12.24) (2023-09-08)
**Note:** Version bump only for package @standardnotes/domain-events-infra

View File

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

View File

@@ -3,6 +3,40 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [2.127.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.126.0...@standardnotes/domain-events@2.127.0) (2023-09-15)
### Features
* refactor transition to minimize status changes ([#828](https://github.com/standardnotes/server/issues/828)) ([36f07c6](https://github.com/standardnotes/server/commit/36f07c691afc213ecf817d6e98f885ddb19a6ed6))
# [2.126.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.125.4...@standardnotes/domain-events@2.126.0) (2023-09-15)
### Features
* add skipping verified transitions ([#827](https://github.com/standardnotes/server/issues/827)) ([d4d4945](https://github.com/standardnotes/server/commit/d4d49454a68de0acdf440dc202fa14b9743905f6))
## [2.125.4](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.125.3...@standardnotes/domain-events@2.125.4) (2023-09-13)
### Bug Fixes
* display transition progress in logs ([38685c1](https://github.com/standardnotes/server/commit/38685c1861b13e398dd96aa39f2cf1aece2090fb))
## [2.125.3](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.125.2...@standardnotes/domain-events@2.125.3) (2023-09-12)
### Bug Fixes
* retry failed revision transitions ([e535cd5](https://github.com/standardnotes/server/commit/e535cd504cf1929539ff7faf13e9c1fdd2b7bfd1))
## [2.125.2](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.125.1...@standardnotes/domain-events@2.125.2) (2023-09-12)
### Bug Fixes
* domain event values ([f0abfe8](https://github.com/standardnotes/server/commit/f0abfe89fca0049c47131389683efe2f5aff23f8))
## [2.125.1](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.125.0...@standardnotes/domain-events@2.125.1) (2023-09-12)
**Note:** Version bump only for package @standardnotes/domain-events
# [2.125.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.124.0...@standardnotes/domain-events@2.125.0) (2023-09-08)
### Features

View File

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

View File

@@ -1,3 +1,5 @@
export interface TransitionRequestedEventPayload {
userUuid: string
type: 'items' | 'revisions'
timestamp: number
}

View File

@@ -1,5 +1,6 @@
export interface TransitionStatusUpdatedEventPayload {
userUuid: string
transitionType: 'items' | 'revisions'
status: 'STARTED' | 'FINISHED' | 'FAILED'
transitionTimestamp: number
status: string
}

View File

@@ -3,6 +3,34 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.11.42](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.11.41...@standardnotes/event-store@1.11.42) (2023-09-15)
**Note:** Version bump only for package @standardnotes/event-store
## [1.11.41](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.11.40...@standardnotes/event-store@1.11.41) (2023-09-15)
**Note:** Version bump only for package @standardnotes/event-store
## [1.11.40](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.11.39...@standardnotes/event-store@1.11.40) (2023-09-13)
**Note:** Version bump only for package @standardnotes/event-store
## [1.11.39](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.11.38...@standardnotes/event-store@1.11.39) (2023-09-12)
**Note:** Version bump only for package @standardnotes/event-store
## [1.11.38](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.11.37...@standardnotes/event-store@1.11.38) (2023-09-12)
**Note:** Version bump only for package @standardnotes/event-store
## [1.11.37](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.11.36...@standardnotes/event-store@1.11.37) (2023-09-12)
**Note:** Version bump only for package @standardnotes/event-store
## [1.11.36](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.11.35...@standardnotes/event-store@1.11.36) (2023-09-12)
**Note:** Version bump only for package @standardnotes/event-store
## [1.11.35](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.11.34...@standardnotes/event-store@1.11.35) (2023-09-08)
**Note:** Version bump only for package @standardnotes/event-store

View File

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

View File

@@ -3,6 +3,34 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.22.21](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.22.20...@standardnotes/files-server@1.22.21) (2023-09-15)
**Note:** Version bump only for package @standardnotes/files-server
## [1.22.20](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.22.19...@standardnotes/files-server@1.22.20) (2023-09-15)
**Note:** Version bump only for package @standardnotes/files-server
## [1.22.19](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.22.18...@standardnotes/files-server@1.22.19) (2023-09-13)
**Note:** Version bump only for package @standardnotes/files-server
## [1.22.18](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.22.17...@standardnotes/files-server@1.22.18) (2023-09-12)
**Note:** Version bump only for package @standardnotes/files-server
## [1.22.17](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.22.16...@standardnotes/files-server@1.22.17) (2023-09-12)
**Note:** Version bump only for package @standardnotes/files-server
## [1.22.16](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.22.15...@standardnotes/files-server@1.22.16) (2023-09-12)
**Note:** Version bump only for package @standardnotes/files-server
## [1.22.15](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.22.14...@standardnotes/files-server@1.22.15) (2023-09-12)
**Note:** Version bump only for package @standardnotes/files-server
## [1.22.14](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.22.13...@standardnotes/files-server@1.22.14) (2023-09-08)
**Note:** Version bump only for package @standardnotes/files-server

View File

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

View File

@@ -3,6 +3,94 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.15.58](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.57...@standardnotes/home-server@1.15.58) (2023-09-15)
**Note:** Version bump only for package @standardnotes/home-server
## [1.15.57](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.56...@standardnotes/home-server@1.15.57) (2023-09-15)
**Note:** Version bump only for package @standardnotes/home-server
## [1.15.56](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.55...@standardnotes/home-server@1.15.56) (2023-09-15)
**Note:** Version bump only for package @standardnotes/home-server
## [1.15.55](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.54...@standardnotes/home-server@1.15.55) (2023-09-15)
**Note:** Version bump only for package @standardnotes/home-server
## [1.15.54](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.53...@standardnotes/home-server@1.15.54) (2023-09-14)
**Note:** Version bump only for package @standardnotes/home-server
## [1.15.53](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.52...@standardnotes/home-server@1.15.53) (2023-09-13)
**Note:** Version bump only for package @standardnotes/home-server
## [1.15.52](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.51...@standardnotes/home-server@1.15.52) (2023-09-13)
**Note:** Version bump only for package @standardnotes/home-server
## [1.15.51](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.50...@standardnotes/home-server@1.15.51) (2023-09-13)
**Note:** Version bump only for package @standardnotes/home-server
## [1.15.50](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.49...@standardnotes/home-server@1.15.50) (2023-09-13)
**Note:** Version bump only for package @standardnotes/home-server
## [1.15.49](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.48...@standardnotes/home-server@1.15.49) (2023-09-13)
**Note:** Version bump only for package @standardnotes/home-server
## [1.15.48](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.47...@standardnotes/home-server@1.15.48) (2023-09-13)
**Note:** Version bump only for package @standardnotes/home-server
## [1.15.47](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.46...@standardnotes/home-server@1.15.47) (2023-09-13)
**Note:** Version bump only for package @standardnotes/home-server
## [1.15.46](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.45...@standardnotes/home-server@1.15.46) (2023-09-12)
**Note:** Version bump only for package @standardnotes/home-server
## [1.15.45](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.44...@standardnotes/home-server@1.15.45) (2023-09-12)
**Note:** Version bump only for package @standardnotes/home-server
## [1.15.44](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.43...@standardnotes/home-server@1.15.44) (2023-09-12)
**Note:** Version bump only for package @standardnotes/home-server
## [1.15.43](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.42...@standardnotes/home-server@1.15.43) (2023-09-12)
**Note:** Version bump only for package @standardnotes/home-server
## [1.15.42](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.41...@standardnotes/home-server@1.15.42) (2023-09-12)
**Note:** Version bump only for package @standardnotes/home-server
## [1.15.41](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.40...@standardnotes/home-server@1.15.41) (2023-09-12)
**Note:** Version bump only for package @standardnotes/home-server
## [1.15.40](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.39...@standardnotes/home-server@1.15.40) (2023-09-12)
**Note:** Version bump only for package @standardnotes/home-server
## [1.15.39](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.38...@standardnotes/home-server@1.15.39) (2023-09-12)
**Note:** Version bump only for package @standardnotes/home-server
## [1.15.38](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.37...@standardnotes/home-server@1.15.38) (2023-09-12)
**Note:** Version bump only for package @standardnotes/home-server
## [1.15.37](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.36...@standardnotes/home-server@1.15.37) (2023-09-12)
**Note:** Version bump only for package @standardnotes/home-server
## [1.15.36](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.35...@standardnotes/home-server@1.15.36) (2023-09-11)
**Note:** Version bump only for package @standardnotes/home-server

View File

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

View File

@@ -3,6 +3,104 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.35.0](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.34.1...@standardnotes/revisions-server@1.35.0) (2023-09-15)
### Features
* refactor transition to minimize status changes ([#828](https://github.com/standardnotes/server/issues/828)) ([36f07c6](https://github.com/standardnotes/server/commit/36f07c691afc213ecf817d6e98f885ddb19a6ed6))
## [1.34.1](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.34.0...@standardnotes/revisions-server@1.34.1) (2023-09-15)
### Bug Fixes
* add debug logs for transition status updates ([3e7856c](https://github.com/standardnotes/server/commit/3e7856c895e73b775c8977c6c6e86dffd5755c00))
# [1.34.0](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.33.21...@standardnotes/revisions-server@1.34.0) (2023-09-15)
### Features
* add skipping verified transitions ([#827](https://github.com/standardnotes/server/issues/827)) ([d4d4945](https://github.com/standardnotes/server/commit/d4d49454a68de0acdf440dc202fa14b9743905f6))
## [1.33.21](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.33.20...@standardnotes/revisions-server@1.33.21) (2023-09-14)
### Bug Fixes
* skip already updated items and revisions in integrity check ([#825](https://github.com/standardnotes/server/issues/825)) ([03a4a3f](https://github.com/standardnotes/server/commit/03a4a3f2abc0b4e09942ba39dbd227524068dfb6))
## [1.33.20](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.33.19...@standardnotes/revisions-server@1.33.20) (2023-09-13)
### Bug Fixes
* adjust transition timestamps to be universal ([c7807d0](https://github.com/standardnotes/server/commit/c7807d0f9e69ce572c4c03ff606375d706f24d9f))
## [1.33.19](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.33.18...@standardnotes/revisions-server@1.33.19) (2023-09-13)
### Bug Fixes
* include handling updated items in revisions in secondary ([fbcb45c](https://github.com/standardnotes/server/commit/fbcb45c3a23fde09702fae7bfcb409bdbb610191))
## [1.33.18](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.33.17...@standardnotes/revisions-server@1.33.18) (2023-09-13)
### Bug Fixes
* display transition progress in logs ([38685c1](https://github.com/standardnotes/server/commit/38685c1861b13e398dd96aa39f2cf1aece2090fb))
## [1.33.17](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.33.16...@standardnotes/revisions-server@1.33.17) (2023-09-13)
### Bug Fixes
* setting status for already migrated users ([9be4c00](https://github.com/standardnotes/server/commit/9be4c002b755fea057489b6077b297162223aefe))
## [1.33.16](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.33.15...@standardnotes/revisions-server@1.33.16) (2023-09-13)
### Bug Fixes
* cleanup only for 0 new items ([b1d88b1](https://github.com/standardnotes/server/commit/b1d88b15be78a48224963e337a222fb675ed2692))
## [1.33.15](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.33.14...@standardnotes/revisions-server@1.33.15) (2023-09-12)
### Bug Fixes
* sync between primary and secondary database with diff ([fab5d18](https://github.com/standardnotes/server/commit/fab5d180645e0a6fa0c9c67205d44f27c8a65c8b))
## [1.33.14](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.33.13...@standardnotes/revisions-server@1.33.14) (2023-09-12)
### Bug Fixes
* **revisions:** handle transitions with already existing data in secondary ([aa83526](https://github.com/standardnotes/server/commit/aa835268ea80e3aa74907e449d189e8b2774a859))
## [1.33.13](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.33.12...@standardnotes/revisions-server@1.33.13) (2023-09-12)
### Bug Fixes
* retry failed revision transitions ([e535cd5](https://github.com/standardnotes/server/commit/e535cd504cf1929539ff7faf13e9c1fdd2b7bfd1))
## [1.33.12](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.33.11...@standardnotes/revisions-server@1.33.12) (2023-09-12)
**Note:** Version bump only for package @standardnotes/revisions-server
## [1.33.11](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.33.10...@standardnotes/revisions-server@1.33.11) (2023-09-12)
### Bug Fixes
* comparing uuids ([0a1d162](https://github.com/standardnotes/server/commit/0a1d1624e818000f2e951f29323a88e6e233c755))
## [1.33.10](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.33.9...@standardnotes/revisions-server@1.33.10) (2023-09-12)
**Note:** Version bump only for package @standardnotes/revisions-server
## [1.33.9](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.33.8...@standardnotes/revisions-server@1.33.9) (2023-09-12)
### Bug Fixes
* adjust transitions to not create revisions during ongoing revisions transition ([106d8f9](https://github.com/standardnotes/server/commit/106d8f9192f630794ca4ddc2c4503f2c6cd196e7))
## [1.33.8](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.33.7...@standardnotes/revisions-server@1.33.8) (2023-09-12)
### Bug Fixes
* transition adjustments ([f20a947](https://github.com/standardnotes/server/commit/f20a947f8a555c074d8dc1543c7a8bf61d1d887e))
## [1.33.7](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.33.6...@standardnotes/revisions-server@1.33.7) (2023-09-11)
### Bug Fixes

View File

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

View File

@@ -60,14 +60,13 @@ import { RevisionHttpRepresentation } from '../Mapping/Http/RevisionHttpRepresen
import { TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser } from '../Domain/UseCase/Transition/TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser/TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser'
import { DomainEventFactoryInterface } from '../Domain/Event/DomainEventFactoryInterface'
import { DomainEventFactory } from '../Domain/Event/DomainEventFactory'
import { TransitionStatusUpdatedEventHandler } from '../Domain/Handler/TransitionStatusUpdatedEventHandler'
import { TriggerTransitionFromPrimaryToSecondaryDatabaseForUser } from '../Domain/UseCase/Transition/TriggerTransitionFromPrimaryToSecondaryDatabaseForUser/TriggerTransitionFromPrimaryToSecondaryDatabaseForUser'
import { SQLRevision } from '../Infra/TypeORM/SQL/SQLRevision'
import { SQLRevisionRepository } from '../Infra/TypeORM/SQL/SQLRevisionRepository'
import { SQLRevisionMetadataPersistenceMapper } from '../Mapping/Persistence/SQL/SQLRevisionMetadataPersistenceMapper'
import { SQLRevisionPersistenceMapper } from '../Mapping/Persistence/SQL/SQLRevisionPersistenceMapper'
import { RemoveRevisionsFromSharedVault } from '../Domain/UseCase/RemoveRevisionsFromSharedVault/RemoveRevisionsFromSharedVault'
import { ItemRemovedFromSharedVaultEventHandler } from '../Domain/Handler/ItemRemovedFromSharedVaultEventHandler'
import { TransitionRequestedEventHandler } from '../Domain/Handler/TransitionRequestedEventHandler'
export class ContainerConfigLoader {
constructor(private mode: 'server' | 'worker' = 'server') {}
@@ -353,16 +352,6 @@ export class ContainerConfigLoader {
env.get('MIGRATION_BATCH_SIZE', true) ? +env.get('MIGRATION_BATCH_SIZE', true) : 100,
),
)
container
.bind<TriggerTransitionFromPrimaryToSecondaryDatabaseForUser>(
TYPES.Revisions_TriggerTransitionFromPrimaryToSecondaryDatabaseForUser,
)
.toConstantValue(
new TriggerTransitionFromPrimaryToSecondaryDatabaseForUser(
container.get<DomainEventPublisherInterface>(TYPES.Revisions_DomainEventPublisher),
container.get<DomainEventFactoryInterface>(TYPES.Revisions_DomainEventFactory),
),
)
container
.bind<RemoveRevisionsFromSharedVault>(TYPES.Revisions_RemoveRevisionsFromSharedVault)
.toConstantValue(
@@ -419,6 +408,7 @@ export class ContainerConfigLoader {
new ItemDumpedEventHandler(
container.get<DumpRepositoryInterface>(TYPES.Revisions_DumpRepository),
container.get<RevisionRepositoryResolverInterface>(TYPES.Revisions_RevisionRepositoryResolver),
container.get<winston.Logger>(TYPES.Revisions_Logger),
),
)
container
@@ -437,18 +427,6 @@ export class ContainerConfigLoader {
container.get<winston.Logger>(TYPES.Revisions_Logger),
),
)
container
.bind<TransitionStatusUpdatedEventHandler>(TYPES.Revisions_TransitionStatusUpdatedEventHandler)
.toConstantValue(
new TransitionStatusUpdatedEventHandler(
container.get<TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser>(
TYPES.Revisions_TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser,
),
container.get<DomainEventPublisherInterface>(TYPES.Revisions_DomainEventPublisher),
container.get<DomainEventFactoryInterface>(TYPES.Revisions_DomainEventFactory),
container.get<winston.Logger>(TYPES.Revisions_Logger),
),
)
container
.bind<ItemRemovedFromSharedVaultEventHandler>(TYPES.Revisions_ItemRemovedFromSharedVaultEventHandler)
.toConstantValue(
@@ -457,13 +435,26 @@ export class ContainerConfigLoader {
container.get<winston.Logger>(TYPES.Revisions_Logger),
),
)
container
.bind<TransitionRequestedEventHandler>(TYPES.Revisions_TransitionRequestedEventHandler)
.toConstantValue(
new TransitionRequestedEventHandler(
container.get<TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser>(
TYPES.Revisions_TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser,
),
container.get<RevisionRepositoryInterface>(TYPES.Revisions_SQLRevisionRepository),
container.get<DomainEventPublisherInterface>(TYPES.Revisions_DomainEventPublisher),
container.get<DomainEventFactoryInterface>(TYPES.Revisions_DomainEventFactory),
container.get<winston.Logger>(TYPES.Revisions_Logger),
),
)
const eventHandlers: Map<string, DomainEventHandlerInterface> = new Map([
['ITEM_DUMPED', container.get(TYPES.Revisions_ItemDumpedEventHandler)],
['ACCOUNT_DELETION_REQUESTED', container.get(TYPES.Revisions_AccountDeletionRequestedEventHandler)],
['REVISIONS_COPY_REQUESTED', container.get(TYPES.Revisions_RevisionsCopyRequestedEventHandler)],
['TRANSITION_STATUS_UPDATED', container.get(TYPES.Revisions_TransitionStatusUpdatedEventHandler)],
['ITEM_REMOVED_FROM_SHARED_VAULT', container.get(TYPES.Revisions_ItemRemovedFromSharedVaultEventHandler)],
['TRANSITION_REQUESTED', container.get(TYPES.Revisions_TransitionRequestedEventHandler)],
])
if (isConfiguredForHomeServer) {
@@ -506,9 +497,6 @@ export class ContainerConfigLoader {
container.get<DeleteRevision>(TYPES.Revisions_DeleteRevision),
container.get<RevisionHttpMapper>(TYPES.Revisions_RevisionHttpMapper),
container.get<RevisionMetadataHttpMapper>(TYPES.Revisions_RevisionMetadataHttpMapper),
container.get<TriggerTransitionFromPrimaryToSecondaryDatabaseForUser>(
TYPES.Revisions_TriggerTransitionFromPrimaryToSecondaryDatabaseForUser,
),
container.get<ControllerContainerInterface>(TYPES.Revisions_ControllerContainer),
),
)

View File

@@ -46,9 +46,6 @@ const TYPES = {
Revisions_TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser: Symbol.for(
'Revisions_TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser',
),
Revisions_TriggerTransitionFromPrimaryToSecondaryDatabaseForUser: Symbol.for(
'Revisions_TriggerTransitionFromPrimaryToSecondaryDatabaseForUser',
),
Revisions_RemoveRevisionsFromSharedVault: Symbol.for('Revisions_RemoveRevisionsFromSharedVault'),
// Controller
Revisions_ControllerContainer: Symbol.for('Revisions_ControllerContainer'),
@@ -58,8 +55,8 @@ const TYPES = {
Revisions_ItemDumpedEventHandler: Symbol.for('Revisions_ItemDumpedEventHandler'),
Revisions_AccountDeletionRequestedEventHandler: Symbol.for('Revisions_AccountDeletionRequestedEventHandler'),
Revisions_RevisionsCopyRequestedEventHandler: Symbol.for('Revisions_RevisionsCopyRequestedEventHandler'),
Revisions_TransitionStatusUpdatedEventHandler: Symbol.for('Revisions_TransitionStatusUpdatedEventHandler'),
Revisions_ItemRemovedFromSharedVaultEventHandler: Symbol.for('Revisions_ItemRemovedFromSharedVaultEventHandler'),
Revisions_TransitionRequestedEventHandler: Symbol.for('Revisions_TransitionRequestedEventHandler'),
// Services
Revisions_CrossServiceTokenDecoder: Symbol.for('Revisions_CrossServiceTokenDecoder'),
Revisions_DomainEventSubscriberFactory: Symbol.for('Revisions_DomainEventSubscriberFactory'),

View File

@@ -9,7 +9,8 @@ export class DomainEventFactory implements DomainEventFactoryInterface {
createTransitionStatusUpdatedEvent(dto: {
userUuid: string
transitionType: 'items' | 'revisions'
status: 'STARTED' | 'FAILED' | 'FINISHED'
transitionTimestamp: number
status: string
}): TransitionStatusUpdatedEvent {
return {
type: 'TRANSITION_STATUS_UPDATED',
@@ -19,7 +20,7 @@ export class DomainEventFactory implements DomainEventFactoryInterface {
userIdentifier: dto.userUuid,
userIdentifierType: 'uuid',
},
origin: DomainEventService.SyncingServer,
origin: DomainEventService.Revisions,
},
payload: dto,
}

View File

@@ -4,6 +4,7 @@ export interface DomainEventFactoryInterface {
createTransitionStatusUpdatedEvent(dto: {
userUuid: string
transitionType: 'items' | 'revisions'
status: 'STARTED' | 'FAILED' | 'FINISHED'
transitionTimestamp: number
status: string
}): TransitionStatusUpdatedEvent
}

View File

@@ -1,4 +1,7 @@
import { ItemDumpedEvent } from '@standardnotes/domain-events'
import { Logger } from 'winston'
import { Uuid, ContentType, Dates } from '@standardnotes/domain-core'
import { DumpRepositoryInterface } from '../Dump/DumpRepositoryInterface'
import { Revision } from '../Revision/Revision'
import { RevisionRepositoryInterface } from '../Revision/RevisionRepositoryInterface'
@@ -11,11 +14,22 @@ describe('ItemDumpedEventHandler', () => {
let revisionRepositoryResolver: RevisionRepositoryResolverInterface
let revision: Revision
let event: ItemDumpedEvent
let logger: Logger
const createHandler = () => new ItemDumpedEventHandler(dumpRepository, revisionRepositoryResolver)
const createHandler = () => new ItemDumpedEventHandler(dumpRepository, revisionRepositoryResolver, logger)
beforeEach(() => {
revision = {} as jest.Mocked<Revision>
revision = Revision.create({
itemUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
userUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
content: 'test',
contentType: ContentType.create('Note').getValue(),
itemsKeyId: 'test',
encItemKey: 'test',
authHash: 'test',
creationDate: new Date(1),
dates: Dates.create(new Date(1), new Date(2)).getValue(),
}).getValue()
dumpRepository = {} as jest.Mocked<DumpRepositoryInterface>
dumpRepository.getRevisionFromDumpPath = jest.fn().mockReturnValue(revision)
@@ -32,6 +46,10 @@ describe('ItemDumpedEventHandler', () => {
fileDumpPath: 'foobar',
roleNames: ['CORE_USER'],
}
logger = {} as jest.Mocked<Logger>
logger.debug = jest.fn()
logger.error = jest.fn()
})
it('should save a revision from file dump', async () => {

View File

@@ -3,16 +3,20 @@ import { DomainEventHandlerInterface, ItemDumpedEvent } from '@standardnotes/dom
import { DumpRepositoryInterface } from '../Dump/DumpRepositoryInterface'
import { RevisionRepositoryResolverInterface } from '../Revision/RevisionRepositoryResolverInterface'
import { RoleNameCollection } from '@standardnotes/domain-core'
import { Logger } from 'winston'
export class ItemDumpedEventHandler implements DomainEventHandlerInterface {
constructor(
private dumpRepository: DumpRepositoryInterface,
private revisionRepositoryResolver: RevisionRepositoryResolverInterface,
private logger: Logger,
) {}
async handle(event: ItemDumpedEvent): Promise<void> {
const revision = await this.dumpRepository.getRevisionFromDumpPath(event.payload.fileDumpPath)
if (revision === null) {
this.logger.error(`Revision not found for dump path ${event.payload.fileDumpPath}`)
await this.dumpRepository.removeDump(event.payload.fileDumpPath)
return
@@ -20,6 +24,8 @@ export class ItemDumpedEventHandler implements DomainEventHandlerInterface {
const roleNamesOrError = RoleNameCollection.create(event.payload.roleNames)
if (roleNamesOrError.isFailed()) {
this.logger.error(`Invalid role names ${event.payload.roleNames}`)
await this.dumpRepository.removeDump(event.payload.fileDumpPath)
return
@@ -28,7 +34,10 @@ export class ItemDumpedEventHandler implements DomainEventHandlerInterface {
const revisionRepository = this.revisionRepositoryResolver.resolve(roleNames)
await revisionRepository.insert(revision)
const successfullyInserted = await revisionRepository.insert(revision)
if (!successfullyInserted) {
this.logger.error(`Could not insert revision ${revision.id.toString()}`)
}
await this.dumpRepository.removeDump(event.payload.fileDumpPath)
}

View File

@@ -0,0 +1,118 @@
import {
DomainEventHandlerInterface,
DomainEventPublisherInterface,
TransitionRequestedEvent,
} from '@standardnotes/domain-events'
import { Logger } from 'winston'
import { TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser } from '../UseCase/Transition/TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser/TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser'
import { DomainEventFactoryInterface } from '../Event/DomainEventFactoryInterface'
import { RevisionRepositoryInterface } from '../Revision/RevisionRepositoryInterface'
import { TransitionStatus, Uuid } from '@standardnotes/domain-core'
export class TransitionRequestedEventHandler implements DomainEventHandlerInterface {
constructor(
private transitionRevisionsFromPrimaryToSecondaryDatabaseForUser: TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser,
private primaryRevisionsRepository: RevisionRepositoryInterface,
private domainEventPublisher: DomainEventPublisherInterface,
private domainEventFactory: DomainEventFactoryInterface,
private logger: Logger,
) {}
async handle(event: TransitionRequestedEvent): Promise<void> {
if (event.payload.type !== 'revisions') {
return
}
const userUuid = await this.getUserUuidFromEvent(event)
if (!userUuid) {
return
}
if (await this.isAlreadyMigrated(userUuid)) {
this.logger.info(`User ${event.payload.userUuid} already migrated.`)
await this.domainEventPublisher.publish(
this.domainEventFactory.createTransitionStatusUpdatedEvent({
userUuid: event.payload.userUuid,
status: TransitionStatus.STATUSES.Verified,
transitionType: 'revisions',
transitionTimestamp: event.payload.timestamp,
}),
)
return
}
this.logger.info(`Handling transition requested event for user ${event.payload.userUuid}`)
await this.domainEventPublisher.publish(
this.domainEventFactory.createTransitionStatusUpdatedEvent({
userUuid: event.payload.userUuid,
status: TransitionStatus.STATUSES.InProgress,
transitionType: 'revisions',
transitionTimestamp: event.payload.timestamp,
}),
)
const result = await this.transitionRevisionsFromPrimaryToSecondaryDatabaseForUser.execute({
userUuid: event.payload.userUuid,
})
if (result.isFailed()) {
this.logger.error(`Failed to transition for user ${event.payload.userUuid}`)
await this.domainEventPublisher.publish(
this.domainEventFactory.createTransitionStatusUpdatedEvent({
userUuid: event.payload.userUuid,
status: TransitionStatus.STATUSES.Failed,
transitionType: 'revisions',
transitionTimestamp: event.payload.timestamp,
}),
)
return
}
await this.domainEventPublisher.publish(
this.domainEventFactory.createTransitionStatusUpdatedEvent({
userUuid: event.payload.userUuid,
status: TransitionStatus.STATUSES.Verified,
transitionType: 'revisions',
transitionTimestamp: event.payload.timestamp,
}),
)
}
private async isAlreadyMigrated(userUuid: Uuid): Promise<boolean> {
const totalRevisionsCountForUserInPrimary = await this.primaryRevisionsRepository.countByUserUuid(userUuid)
if (totalRevisionsCountForUserInPrimary > 0) {
this.logger.info(
`User ${userUuid.value} has ${totalRevisionsCountForUserInPrimary} revisions in primary database.`,
)
}
return totalRevisionsCountForUserInPrimary === 0
}
private async getUserUuidFromEvent(event: TransitionRequestedEvent): Promise<Uuid | null> {
const userUuidOrError = Uuid.create(event.payload.userUuid)
if (userUuidOrError.isFailed()) {
this.logger.error(
`Failed to transition revisions for user ${event.payload.userUuid}: ${userUuidOrError.getError()}`,
)
await this.domainEventPublisher.publish(
this.domainEventFactory.createTransitionStatusUpdatedEvent({
userUuid: event.payload.userUuid,
status: TransitionStatus.STATUSES.Failed,
transitionType: 'revisions',
transitionTimestamp: event.payload.timestamp,
}),
)
return null
}
return userUuidOrError.getValue()
}
}

View File

@@ -1,61 +0,0 @@
import {
DomainEventHandlerInterface,
DomainEventPublisherInterface,
TransitionStatusUpdatedEvent,
} from '@standardnotes/domain-events'
import { Logger } from 'winston'
import { DomainEventFactoryInterface } from '../Event/DomainEventFactoryInterface'
import { TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser } from '../UseCase/Transition/TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser/TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser'
export class TransitionStatusUpdatedEventHandler implements DomainEventHandlerInterface {
constructor(
private transitionRevisionsFromPrimaryToSecondaryDatabaseForUser: TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser,
private domainEventPublisher: DomainEventPublisherInterface,
private domainEventFactory: DomainEventFactoryInterface,
private logger: Logger,
) {}
async handle(event: TransitionStatusUpdatedEvent): Promise<void> {
if (event.payload.status === 'STARTED' && event.payload.transitionType === 'items') {
await this.domainEventPublisher.publish(
this.domainEventFactory.createTransitionStatusUpdatedEvent({
userUuid: event.payload.userUuid,
status: 'STARTED',
transitionType: 'revisions',
}),
)
return
}
if (event.payload.status === 'STARTED' && event.payload.transitionType === 'revisions') {
const result = await this.transitionRevisionsFromPrimaryToSecondaryDatabaseForUser.execute({
userUuid: event.payload.userUuid,
})
if (result.isFailed()) {
this.logger.error(`Failed to transition revisions for user ${event.payload.userUuid}: ${result.getError()}`)
await this.domainEventPublisher.publish(
this.domainEventFactory.createTransitionStatusUpdatedEvent({
userUuid: event.payload.userUuid,
status: 'FAILED',
transitionType: 'revisions',
}),
)
return
}
await this.domainEventPublisher.publish(
this.domainEventFactory.createTransitionStatusUpdatedEvent({
userUuid: event.payload.userUuid,
status: 'FINISHED',
transitionType: 'revisions',
}),
)
return
}
}
}

View File

@@ -12,7 +12,7 @@ export class Revision extends Entity<RevisionProps> {
}
isIdenticalTo(revision: Revision): boolean {
if (this._id.toString() !== revision._id.toString()) {
if (this._id.toString().toLowerCase() !== revision._id.toString().toLowerCase()) {
return false
}

View File

@@ -1,438 +0,0 @@
import { Logger } from 'winston'
import { RevisionRepositoryInterface } from '../../../Revision/RevisionRepositoryInterface'
import { TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser } from './TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser'
import { Revision } from '../../../Revision/Revision'
import { ContentType, Dates, UniqueEntityId, Uuid } from '@standardnotes/domain-core'
import { TimerInterface } from '@standardnotes/time'
describe('TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser', () => {
let primaryRevisionRepository: RevisionRepositoryInterface
let secondaryRevisionRepository: RevisionRepositoryInterface | null
let logger: Logger
let primaryRevision1: Revision
let primaryRevision2: Revision
let secondaryRevision1: Revision
let secondaryRevision2: Revision
let timer: TimerInterface
const createUseCase = () =>
new TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser(
primaryRevisionRepository,
secondaryRevisionRepository,
timer,
logger,
1,
)
beforeEach(() => {
primaryRevision1 = Revision.create(
{
itemUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
userUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
content: 'test',
contentType: ContentType.create('Note').getValue(),
itemsKeyId: 'test',
encItemKey: 'test',
authHash: 'test',
creationDate: new Date(1),
dates: Dates.create(new Date(1), new Date(2)).getValue(),
},
new UniqueEntityId('00000000-0000-0000-0000-000000000000'),
).getValue()
primaryRevision2 = Revision.create(
{
itemUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc2d').getValue(),
userUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
content: 'test',
contentType: ContentType.create('Note').getValue(),
itemsKeyId: 'test',
encItemKey: 'test',
authHash: 'test',
creationDate: new Date(1),
dates: Dates.create(new Date(1), new Date(2)).getValue(),
},
new UniqueEntityId('00000000-0000-0000-0000-000000000001'),
).getValue()
secondaryRevision1 = Revision.create(
{
itemUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
userUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
content: 'test',
contentType: ContentType.create('Note').getValue(),
itemsKeyId: 'test',
encItemKey: 'test',
authHash: 'test',
creationDate: new Date(1),
dates: Dates.create(new Date(1), new Date(2)).getValue(),
},
new UniqueEntityId('00000000-0000-0000-0000-000000000000'),
).getValue()
secondaryRevision2 = Revision.create(
{
itemUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc2d').getValue(),
userUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
content: 'test',
contentType: ContentType.create('Note').getValue(),
itemsKeyId: 'test',
encItemKey: 'test',
authHash: 'test',
creationDate: new Date(1),
dates: Dates.create(new Date(1), new Date(2)).getValue(),
},
new UniqueEntityId('00000000-0000-0000-0000-000000000001'),
).getValue()
primaryRevisionRepository = {} as jest.Mocked<RevisionRepositoryInterface>
primaryRevisionRepository.countByUserUuid = jest.fn().mockResolvedValue(2)
primaryRevisionRepository.findByUserUuid = jest
.fn()
.mockResolvedValueOnce([primaryRevision1])
.mockResolvedValueOnce([primaryRevision2])
.mockResolvedValueOnce([primaryRevision1])
.mockResolvedValueOnce([primaryRevision2])
primaryRevisionRepository.removeByUserUuid = jest.fn().mockResolvedValue(undefined)
secondaryRevisionRepository = {} as jest.Mocked<RevisionRepositoryInterface>
secondaryRevisionRepository.insert = jest.fn().mockResolvedValue(true)
secondaryRevisionRepository.removeByUserUuid = jest.fn().mockResolvedValue(undefined)
secondaryRevisionRepository.countByUserUuid = jest.fn().mockResolvedValue(2)
secondaryRevisionRepository.findOneByUuid = jest
.fn()
.mockResolvedValueOnce(secondaryRevision1)
.mockResolvedValueOnce(secondaryRevision2)
logger = {} as jest.Mocked<Logger>
logger.error = jest.fn()
logger.info = jest.fn()
logger.debug = jest.fn()
timer = {} as jest.Mocked<TimerInterface>
timer.sleep = jest.fn()
timer.getTimestampInMicroseconds = jest.fn().mockReturnValue(123)
timer.convertMicrosecondsToTimeStructure = jest.fn().mockReturnValue({
days: 0,
hours: 0,
minutes: 0,
seconds: 0,
milliseconds: 0,
})
})
describe('successfull transition', () => {
it('should transition Revisions from primary to secondary database', async () => {
const useCase = createUseCase()
const result = await useCase.execute({
userUuid: '00000000-0000-0000-0000-000000000000',
})
expect(result.isFailed()).toBeFalsy()
expect(primaryRevisionRepository.countByUserUuid).toHaveBeenCalledTimes(3)
expect(primaryRevisionRepository.countByUserUuid).toHaveBeenCalledWith(
Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
)
expect(primaryRevisionRepository.findByUserUuid).toHaveBeenCalledTimes(4)
expect(primaryRevisionRepository.findByUserUuid).toHaveBeenNthCalledWith(1, {
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
limit: 1,
offset: 0,
})
expect(primaryRevisionRepository.findByUserUuid).toHaveBeenNthCalledWith(2, {
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
limit: 1,
offset: 1,
})
expect(primaryRevisionRepository.findByUserUuid).toHaveBeenNthCalledWith(3, {
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
limit: 1,
offset: 0,
})
expect(primaryRevisionRepository.findByUserUuid).toHaveBeenNthCalledWith(4, {
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
limit: 1,
offset: 1,
})
expect((secondaryRevisionRepository as RevisionRepositoryInterface).insert).toHaveBeenCalledTimes(2)
expect((secondaryRevisionRepository as RevisionRepositoryInterface).insert).toHaveBeenCalledWith(primaryRevision1)
expect((secondaryRevisionRepository as RevisionRepositoryInterface).insert).toHaveBeenCalledWith(primaryRevision2)
expect((secondaryRevisionRepository as RevisionRepositoryInterface).removeByUserUuid).not.toHaveBeenCalled()
expect(primaryRevisionRepository.removeByUserUuid).toHaveBeenCalledTimes(1)
})
it('should log an error if deleting Revisions from primary database fails', async () => {
primaryRevisionRepository.removeByUserUuid = jest.fn().mockRejectedValue(new Error('error'))
const useCase = createUseCase()
const result = await useCase.execute({
userUuid: '00000000-0000-0000-0000-000000000000',
})
expect(result.isFailed()).toBeFalsy()
expect(logger.error).toHaveBeenCalledTimes(1)
expect(logger.error).toHaveBeenCalledWith(
'Failed to clean up primary database revisions for user 00000000-0000-0000-0000-000000000000: Errored when deleting revisions for user 00000000-0000-0000-0000-000000000000: error',
)
})
})
describe('failed transition', () => {
it('should remove Revisions from secondary database if integrity check fails', async () => {
const secondaryRevision2WithDifferentContent = Revision.create({
...secondaryRevision2.props,
content: 'different-content',
}).getValue()
;(secondaryRevisionRepository as RevisionRepositoryInterface).findOneByUuid = jest
.fn()
.mockResolvedValueOnce(secondaryRevision1)
.mockResolvedValueOnce(secondaryRevision2WithDifferentContent)
const useCase = createUseCase()
const result = await useCase.execute({
userUuid: '00000000-0000-0000-0000-000000000000',
})
expect(result.isFailed()).toBeTruthy()
expect(result.getError()).toEqual(
'Revision 00000000-0000-0000-0000-000000000001 is not identical in primary and secondary database',
)
expect((secondaryRevisionRepository as RevisionRepositoryInterface).removeByUserUuid).toHaveBeenCalledTimes(1)
expect(primaryRevisionRepository.removeByUserUuid).not.toHaveBeenCalled()
})
it('should remove Revisions from secondary database if migrating Revisions fails', async () => {
primaryRevisionRepository.findByUserUuid = jest
.fn()
.mockResolvedValueOnce([primaryRevision1])
.mockRejectedValueOnce(new Error('error'))
const useCase = createUseCase()
const result = await useCase.execute({
userUuid: '00000000-0000-0000-0000-000000000000',
})
expect(result.isFailed()).toBeTruthy()
expect(result.getError()).toEqual(
'Errored when migrating revisions for user 00000000-0000-0000-0000-000000000000: error',
)
expect((secondaryRevisionRepository as RevisionRepositoryInterface).removeByUserUuid).toHaveBeenCalledTimes(1)
expect(primaryRevisionRepository.removeByUserUuid).not.toHaveBeenCalled()
})
it('should return an error for a specific revision if it errors when saving to secondary database', async () => {
;(secondaryRevisionRepository as RevisionRepositoryInterface).insert = jest
.fn()
.mockResolvedValueOnce(true)
.mockRejectedValueOnce(new Error('error'))
const useCase = createUseCase()
const result = await useCase.execute({
userUuid: '84c0f8e8-544a-4c7e-9adf-26209303bc1d',
})
expect(result.isFailed()).toBeTruthy()
expect(result.getError()).toEqual(
'Errored when saving revision 00000000-0000-0000-0000-000000000001 to secondary database: error',
)
})
it('should log an error if deleting Revisions from secondary database fails upon migration failure', async () => {
primaryRevisionRepository.findByUserUuid = jest
.fn()
.mockResolvedValueOnce([primaryRevision1])
.mockRejectedValueOnce(new Error('error'))
;(secondaryRevisionRepository as RevisionRepositoryInterface).removeByUserUuid = jest
.fn()
.mockRejectedValue(new Error('error'))
const useCase = createUseCase()
const result = await useCase.execute({
userUuid: '00000000-0000-0000-0000-000000000000',
})
expect(result.isFailed()).toBeTruthy()
expect(logger.error).toHaveBeenCalledTimes(1)
expect(logger.error).toHaveBeenCalledWith(
'Failed to clean up secondary database revisions for user 00000000-0000-0000-0000-000000000000: Errored when deleting revisions for user 00000000-0000-0000-0000-000000000000: error',
)
})
it('should log an error if deleting Revisions from secondary database fails upon integrity check failure', async () => {
const secondaryRevision2WithDifferentContent = Revision.create({
...secondaryRevision2.props,
content: 'different-content',
}).getValue()
;(secondaryRevisionRepository as RevisionRepositoryInterface).findOneByUuid = jest
.fn()
.mockResolvedValueOnce(secondaryRevision1)
.mockResolvedValueOnce(secondaryRevision2WithDifferentContent)
;(secondaryRevisionRepository as RevisionRepositoryInterface).removeByUserUuid = jest
.fn()
.mockRejectedValue(new Error('error'))
const useCase = createUseCase()
const result = await useCase.execute({
userUuid: '00000000-0000-0000-0000-000000000000',
})
expect(result.isFailed()).toBeTruthy()
expect(logger.error).toHaveBeenCalledTimes(1)
expect(logger.error).toHaveBeenCalledWith(
'Failed to clean up secondary database revisions for user 00000000-0000-0000-0000-000000000000: Errored when deleting revisions for user 00000000-0000-0000-0000-000000000000: error',
)
})
it('should not perform the transition if secondary Revision repository is not set', async () => {
secondaryRevisionRepository = null
const useCase = createUseCase()
const result = await useCase.execute({
userUuid: '00000000-0000-0000-0000-000000000000',
})
expect(result.isFailed()).toBeTruthy()
expect(result.getError()).toEqual('Secondary revision repository is not set')
expect(primaryRevisionRepository.countByUserUuid).not.toHaveBeenCalled()
expect(primaryRevisionRepository.findByUserUuid).not.toHaveBeenCalled()
expect(primaryRevisionRepository.removeByUserUuid).not.toHaveBeenCalled()
})
it('should not perform the transition if the user uuid is invalid', async () => {
const useCase = createUseCase()
const result = await useCase.execute({
userUuid: 'invalid-uuid',
})
expect(result.isFailed()).toBeTruthy()
expect(result.getError()).toEqual('Given value is not a valid uuid: invalid-uuid')
expect(primaryRevisionRepository.countByUserUuid).not.toHaveBeenCalled()
expect(primaryRevisionRepository.findByUserUuid).not.toHaveBeenCalled()
expect(primaryRevisionRepository.removeByUserUuid).not.toHaveBeenCalled()
})
it('should fail integrity check if the Revision count is not the same in both databases', async () => {
;(secondaryRevisionRepository as RevisionRepositoryInterface).countByUserUuid = jest.fn().mockResolvedValue(1)
const useCase = createUseCase()
const result = await useCase.execute({
userUuid: '00000000-0000-0000-0000-000000000000',
})
expect(result.isFailed()).toBeTruthy()
expect(result.getError()).toEqual(
'Total revisions count for user 00000000-0000-0000-0000-000000000000 in primary database (2) does not match total revisions count in secondary database (1)',
)
expect(primaryRevisionRepository.countByUserUuid).toHaveBeenCalledTimes(3)
expect(primaryRevisionRepository.countByUserUuid).toHaveBeenCalledWith(
Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
)
expect((secondaryRevisionRepository as RevisionRepositoryInterface).countByUserUuid).toHaveBeenCalledTimes(1)
expect(primaryRevisionRepository.removeByUserUuid).not.toHaveBeenCalled()
expect((secondaryRevisionRepository as RevisionRepositoryInterface).removeByUserUuid).toHaveBeenCalledTimes(1)
})
it('should fail if one Revision is not found in the secondary database', async () => {
;(secondaryRevisionRepository as RevisionRepositoryInterface).findOneByUuid = jest
.fn()
.mockResolvedValueOnce(secondaryRevision1)
.mockResolvedValueOnce(null)
const useCase = createUseCase()
const result = await useCase.execute({
userUuid: '00000000-0000-0000-0000-000000000000',
})
expect(result.isFailed()).toBeTruthy()
expect(result.getError()).toEqual('Revision 00000000-0000-0000-0000-000000000001 not found in secondary database')
expect(primaryRevisionRepository.countByUserUuid).toHaveBeenCalledTimes(3)
expect(primaryRevisionRepository.countByUserUuid).toHaveBeenCalledWith(
Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
)
expect((secondaryRevisionRepository as RevisionRepositoryInterface).countByUserUuid).not.toHaveBeenCalled()
expect(primaryRevisionRepository.removeByUserUuid).not.toHaveBeenCalled()
expect((secondaryRevisionRepository as RevisionRepositoryInterface).removeByUserUuid).toHaveBeenCalledTimes(1)
})
it('should fail if an error is thrown during integrity check between primary and secondary database', async () => {
;(secondaryRevisionRepository as RevisionRepositoryInterface).countByUserUuid = jest
.fn()
.mockRejectedValue(new Error('error'))
const useCase = createUseCase()
const result = await useCase.execute({
userUuid: '00000000-0000-0000-0000-000000000000',
})
expect(result.isFailed()).toBeTruthy()
expect(result.getError()).toEqual('Errored when checking integrity between primary and secondary database: error')
expect(primaryRevisionRepository.removeByUserUuid).not.toHaveBeenCalled()
expect((secondaryRevisionRepository as RevisionRepositoryInterface).removeByUserUuid).toHaveBeenCalledTimes(1)
})
it('should fail if a revisions did not save in the secondary database', async () => {
;(secondaryRevisionRepository as RevisionRepositoryInterface).insert = jest.fn().mockResolvedValue(false)
const useCase = createUseCase()
const result = await useCase.execute({
userUuid: '00000000-0000-0000-0000-000000000000',
})
expect(result.isFailed()).toBeTruthy()
expect(result.getError()).toEqual(
'Failed to save revision 00000000-0000-0000-0000-000000000000 to secondary database',
)
expect(primaryRevisionRepository.removeByUserUuid).not.toHaveBeenCalled()
expect((secondaryRevisionRepository as RevisionRepositoryInterface).removeByUserUuid).toHaveBeenCalledTimes(1)
})
})
it('should not migrate revisions if there are no revisions in the primary database', async () => {
primaryRevisionRepository.countByUserUuid = jest.fn().mockResolvedValue(0)
const useCase = createUseCase()
const result = await useCase.execute({
userUuid: '00000000-0000-0000-0000-000000000000',
})
expect(result.isFailed()).toBeFalsy()
expect(primaryRevisionRepository.countByUserUuid).toHaveBeenCalledTimes(1)
expect(primaryRevisionRepository.countByUserUuid).toHaveBeenCalledWith(
Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
)
expect(primaryRevisionRepository.findByUserUuid).not.toHaveBeenCalled()
expect((secondaryRevisionRepository as RevisionRepositoryInterface).insert).not.toHaveBeenCalled()
expect(primaryRevisionRepository.removeByUserUuid).not.toHaveBeenCalled()
expect((secondaryRevisionRepository as RevisionRepositoryInterface).removeByUserUuid).not.toHaveBeenCalled()
})
})

View File

@@ -1,9 +1,11 @@
/* istanbul ignore file */
import { Result, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
import { TimerInterface } from '@standardnotes/time'
import { Logger } from 'winston'
import { TransitionRevisionsFromPrimaryToSecondaryDatabaseForUserDTO } from './TransitionRevisionsFromPrimaryToSecondaryDatabaseForUserDTO'
import { RevisionRepositoryInterface } from '../../../Revision/RevisionRepositoryInterface'
import { Revision } from '../../../Revision/Revision'
export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements UseCaseInterface<void> {
constructor(
@@ -15,6 +17,8 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
) {}
async execute(dto: TransitionRevisionsFromPrimaryToSecondaryDatabaseForUserDTO): Promise<Result<void>> {
this.logger.info(`Transitioning revisions for user ${dto.userUuid}`)
if (this.secondRevisionsRepository === null) {
return Result.fail('Secondary revision repository is not set')
}
@@ -25,23 +29,54 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
}
const userUuid = userUuidOrError.getValue()
if (await this.isAlreadyMigrated(userUuid)) {
this.logger.info(`Revisions for user ${userUuid.value} are already migrated`)
let newRevisionsInSecondaryCount = 0
let updatedRevisionsInSecondary: Revision[] = []
if (await this.hasAlreadyDataInSecondaryDatabase(userUuid)) {
const { alreadyExistingInPrimary, newRevisionsInSecondary, updatedInSecondary } =
await this.getNewRevisionsCreatedInSecondaryDatabase(userUuid)
return Result.ok()
for (const existingRevision of alreadyExistingInPrimary) {
this.logger.info(`Removing revision ${existingRevision.id.toString()} from secondary database`)
await (this.secondRevisionsRepository as RevisionRepositoryInterface).removeOneByUuid(
Uuid.create(existingRevision.id.toString()).getValue(),
userUuid,
)
}
if (newRevisionsInSecondary.length > 0) {
this.logger.info(
`Found ${newRevisionsInSecondary.length} new revisions in secondary database for user ${userUuid.value}`,
)
}
newRevisionsInSecondaryCount = newRevisionsInSecondary.length
if (updatedInSecondary.length > 0) {
this.logger.info(
`Found ${updatedInSecondary.length} updated revisions in secondary database for user ${userUuid.value}`,
)
}
updatedRevisionsInSecondary = updatedInSecondary
}
const updatedRevisionsInSecondaryCount = updatedRevisionsInSecondary.length
await this.allowForSecondaryDatabaseToCatchUp()
const migrationTimeStart = this.timer.getTimestampInMicroseconds()
this.logger.debug(`Transitioning revisions for user ${userUuid.value}`)
const migrationResult = await this.migrateRevisionsForUser(userUuid)
const migrationResult = await this.migrateRevisionsForUser(userUuid, updatedRevisionsInSecondary)
if (migrationResult.isFailed()) {
const cleanupResult = await this.deleteRevisionsForUser(userUuid, this.secondRevisionsRepository)
if (cleanupResult.isFailed()) {
this.logger.error(
`Failed to clean up secondary database revisions for user ${userUuid.value}: ${cleanupResult.getError()}`,
)
if (newRevisionsInSecondaryCount === 0 && updatedRevisionsInSecondaryCount === 0) {
const cleanupResult = await this.deleteRevisionsForUser(userUuid, this.secondRevisionsRepository)
if (cleanupResult.isFailed()) {
this.logger.error(
`Failed to clean up secondary database revisions for user ${userUuid.value}: ${cleanupResult.getError()}`,
)
}
}
return Result.fail(migrationResult.getError())
@@ -49,13 +84,19 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
await this.allowForSecondaryDatabaseToCatchUp()
const integrityCheckResult = await this.checkIntegrityBetweenPrimaryAndSecondaryDatabase(userUuid)
const integrityCheckResult = await this.checkIntegrityBetweenPrimaryAndSecondaryDatabase(
userUuid,
newRevisionsInSecondaryCount,
updatedRevisionsInSecondary,
)
if (integrityCheckResult.isFailed()) {
const cleanupResult = await this.deleteRevisionsForUser(userUuid, this.secondRevisionsRepository)
if (cleanupResult.isFailed()) {
this.logger.error(
`Failed to clean up secondary database revisions for user ${userUuid.value}: ${cleanupResult.getError()}`,
)
if (newRevisionsInSecondaryCount === 0 && updatedRevisionsInSecondaryCount === 0) {
const cleanupResult = await this.deleteRevisionsForUser(userUuid, this.secondRevisionsRepository)
if (cleanupResult.isFailed()) {
this.logger.error(
`Failed to clean up secondary database revisions for user ${userUuid.value}: ${cleanupResult.getError()}`,
)
}
}
return Result.fail(integrityCheckResult.getError())
@@ -80,7 +121,10 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
return Result.ok()
}
private async migrateRevisionsForUser(userUuid: Uuid): Promise<Result<void>> {
private async migrateRevisionsForUser(
userUuid: Uuid,
updatedRevisionsInSecondary: Revision[],
): Promise<Result<void>> {
try {
const totalRevisionsCountForUser = await this.primaryRevisionsRepository.countByUserUuid(userUuid)
let totalRevisionsCountTransitionedToSecondary = 0
@@ -96,6 +140,18 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
for (const revision of revisions) {
try {
if (
updatedRevisionsInSecondary.find(
(updatedRevision) => updatedRevision.id.toString() === revision.id.toString(),
)
) {
this.logger.info(
`Skipping saving revision ${revision.id.toString()} as it was updated in secondary database`,
)
continue
}
this.logger.debug(
`Transitioning revision #${
totalRevisionsCountTransitionedToSecondary + 1
@@ -143,13 +199,99 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
await this.timer.sleep(twoSecondsInMilliseconds)
}
private async isAlreadyMigrated(userUuid: Uuid): Promise<boolean> {
const totalRevisionsCountForUserInPrimary = await this.primaryRevisionsRepository.countByUserUuid(userUuid)
private async hasAlreadyDataInSecondaryDatabase(userUuid: Uuid): Promise<boolean> {
const totalRevisionsCountForUserInSecondary = await (
this.secondRevisionsRepository as RevisionRepositoryInterface
).countByUserUuid(userUuid)
return totalRevisionsCountForUserInPrimary === 0
const hasAlreadyDataInSecondaryDatabase = totalRevisionsCountForUserInSecondary > 0
if (hasAlreadyDataInSecondaryDatabase) {
this.logger.info(
`User ${userUuid.value} has already ${totalRevisionsCountForUserInSecondary} revisions in secondary database`,
)
}
return hasAlreadyDataInSecondaryDatabase
}
private async checkIntegrityBetweenPrimaryAndSecondaryDatabase(userUuid: Uuid): Promise<Result<boolean>> {
private async getNewRevisionsCreatedInSecondaryDatabase(userUuid: Uuid): Promise<{
alreadyExistingInPrimary: Revision[]
newRevisionsInSecondary: Revision[]
updatedInSecondary: Revision[]
}> {
const revisions = await (this.secondRevisionsRepository as RevisionRepositoryInterface).findByUserUuid({
userUuid: userUuid,
})
const alreadyExistingInPrimary: Revision[] = []
const newRevisionsInSecondary: Revision[] = []
const updatedInSecondary: Revision[] = []
for (const revision of revisions) {
const { revisionInPrimary, newerRevisionInSecondary } =
await this.checkIfRevisionExistsInPrimaryDatabase(revision)
if (revisionInPrimary !== null) {
alreadyExistingInPrimary.push(revision)
continue
}
if (newerRevisionInSecondary !== null) {
updatedInSecondary.push(newerRevisionInSecondary)
continue
}
if (revisionInPrimary === null && newerRevisionInSecondary === null) {
newRevisionsInSecondary.push(revision)
continue
}
}
return {
alreadyExistingInPrimary: alreadyExistingInPrimary,
newRevisionsInSecondary: newRevisionsInSecondary,
updatedInSecondary: updatedInSecondary,
}
}
private async checkIfRevisionExistsInPrimaryDatabase(
revision: Revision,
): Promise<{ revisionInPrimary: Revision | null; newerRevisionInSecondary: Revision | null }> {
const revisionInPrimary = await this.primaryRevisionsRepository.findOneByUuid(
Uuid.create(revision.id.toString()).getValue(),
revision.props.userUuid as Uuid,
[],
)
if (revisionInPrimary === null) {
return {
revisionInPrimary: null,
newerRevisionInSecondary: null,
}
}
if (!revision.isIdenticalTo(revisionInPrimary)) {
this.logger.error(
`Revision ${revision.id.toString()} is not identical in primary and secondary database. Revision in secondary database: ${JSON.stringify(
revision,
)}, revision in primary database: ${JSON.stringify(revisionInPrimary)}`,
)
return {
revisionInPrimary: null,
newerRevisionInSecondary:
revision.props.dates.updatedAt > revisionInPrimary.props.dates.updatedAt ? revision : null,
}
}
return {
revisionInPrimary: revisionInPrimary,
newerRevisionInSecondary: null,
}
}
private async checkIntegrityBetweenPrimaryAndSecondaryDatabase(
userUuid: Uuid,
newRevisionsInSecondaryCount: number,
updatedRevisionsInSecondary: Revision[],
): Promise<Result<boolean>> {
try {
const totalRevisionsCountForUserInPrimary = await this.primaryRevisionsRepository.countByUserUuid(userUuid)
@@ -178,8 +320,23 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
return Result.fail(`Revision ${revision.id.toString()} not found in secondary database`)
}
if (
updatedRevisionsInSecondary.find(
(updatedRevision) => updatedRevision.id.toString() === revision.id.toString(),
)
) {
this.logger.info(
`Skipping integrity check for revision ${revision.id.toString()} as it was updated in secondary database`,
)
continue
}
if (!revision.isIdenticalTo(revisionInSecondary)) {
return Result.fail(`Revision ${revision.id.toString()} is not identical in primary and secondary database`)
return Result.fail(
`Revision ${revision.id.toString()} is not identical in primary and secondary database. Revision in primary database: ${JSON.stringify(
revision,
)}, revision in secondary database: ${JSON.stringify(revisionInSecondary)}`,
)
}
}
}
@@ -188,9 +345,12 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
this.secondRevisionsRepository as RevisionRepositoryInterface
).countByUserUuid(userUuid)
if (totalRevisionsCountForUserInPrimary !== totalRevisionsCountForUserInSecondary) {
if (
totalRevisionsCountForUserInPrimary + newRevisionsInSecondaryCount !==
totalRevisionsCountForUserInSecondary
) {
return Result.fail(
`Total revisions count for user ${userUuid.value} in primary database (${totalRevisionsCountForUserInPrimary}) does not match total revisions count in secondary database (${totalRevisionsCountForUserInSecondary})`,
`Total revisions count for user ${userUuid.value} in primary database (${totalRevisionsCountForUserInPrimary} + ${newRevisionsInSecondaryCount}) does not match total revisions count in secondary database (${totalRevisionsCountForUserInSecondary})`,
)
}

View File

@@ -1,30 +0,0 @@
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
import { TriggerTransitionFromPrimaryToSecondaryDatabaseForUser } from './TriggerTransitionFromPrimaryToSecondaryDatabaseForUser'
import { DomainEventFactoryInterface } from '../../../Event/DomainEventFactoryInterface'
describe('TriggerTransitionFromPrimaryToSecondaryDatabaseForUser', () => {
let domainEventPubliser: DomainEventPublisherInterface
let domainEventFactory: DomainEventFactoryInterface
const createUseCase = () =>
new TriggerTransitionFromPrimaryToSecondaryDatabaseForUser(domainEventPubliser, domainEventFactory)
beforeEach(() => {
domainEventPubliser = {} as jest.Mocked<DomainEventPublisherInterface>
domainEventPubliser.publish = jest.fn()
domainEventFactory = {} as jest.Mocked<DomainEventFactoryInterface>
domainEventFactory.createTransitionStatusUpdatedEvent = jest.fn()
})
it('should publish transition status updated event', async () => {
const useCase = createUseCase()
await useCase.execute({
userUuid: '00000000-0000-0000-0000-000000000000',
})
expect(domainEventPubliser.publish).toHaveBeenCalled()
})
})

View File

@@ -1,24 +0,0 @@
import { Result, UseCaseInterface } from '@standardnotes/domain-core'
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
import { TriggerTransitionFromPrimaryToSecondaryDatabaseForUserDTO } from './TriggerTransitionFromPrimaryToSecondaryDatabaseForUserDTO'
import { DomainEventFactoryInterface } from '../../../Event/DomainEventFactoryInterface'
export class TriggerTransitionFromPrimaryToSecondaryDatabaseForUser implements UseCaseInterface<void> {
constructor(
private domainEventPubliser: DomainEventPublisherInterface,
private domainEventFactory: DomainEventFactoryInterface,
) {}
async execute(dto: TriggerTransitionFromPrimaryToSecondaryDatabaseForUserDTO): Promise<Result<void>> {
const event = this.domainEventFactory.createTransitionStatusUpdatedEvent({
userUuid: dto.userUuid,
status: 'STARTED',
transitionType: 'revisions',
})
await this.domainEventPubliser.publish(event)
return Result.ok()
}
}

View File

@@ -1,3 +0,0 @@
export interface TriggerTransitionFromPrimaryToSecondaryDatabaseForUserDTO {
userUuid: string
}

View File

@@ -1,5 +1,5 @@
import { Request, Response } from 'express'
import { controller, httpDelete, httpGet, httpPost, results } from 'inversify-express-utils'
import { controller, httpDelete, httpGet, results } from 'inversify-express-utils'
import { inject } from 'inversify'
import TYPES from '../../Bootstrap/Types'
@@ -12,7 +12,6 @@ import { Revision } from '../../Domain/Revision/Revision'
import { RevisionMetadata } from '../../Domain/Revision/RevisionMetadata'
import { RevisionHttpRepresentation } from '../../Mapping/Http/RevisionHttpRepresentation'
import { RevisionMetadataHttpRepresentation } from '../../Mapping/Http/RevisionMetadataHttpRepresentation'
import { TriggerTransitionFromPrimaryToSecondaryDatabaseForUser } from '../../Domain/UseCase/Transition/TriggerTransitionFromPrimaryToSecondaryDatabaseForUser/TriggerTransitionFromPrimaryToSecondaryDatabaseForUser'
@controller('', TYPES.Revisions_ApiGatewayAuthMiddleware)
export class AnnotatedRevisionsController extends BaseRevisionsController {
@@ -24,17 +23,8 @@ export class AnnotatedRevisionsController extends BaseRevisionsController {
override revisionHttpMapper: MapperInterface<Revision, RevisionHttpRepresentation>,
@inject(TYPES.Revisions_RevisionMetadataHttpMapper)
override revisionMetadataHttpMapper: MapperInterface<RevisionMetadata, RevisionMetadataHttpRepresentation>,
@inject(TYPES.Revisions_TriggerTransitionFromPrimaryToSecondaryDatabaseForUser)
override triggerTransitionFromPrimaryToSecondaryDatabaseForUser: TriggerTransitionFromPrimaryToSecondaryDatabaseForUser,
) {
super(
getRevisionsMetadata,
doGetRevision,
doDeleteRevision,
revisionHttpMapper,
revisionMetadataHttpMapper,
triggerTransitionFromPrimaryToSecondaryDatabaseForUser,
)
super(getRevisionsMetadata, doGetRevision, doDeleteRevision, revisionHttpMapper, revisionMetadataHttpMapper)
}
@httpGet('/items/:itemUuid/revisions')
@@ -51,9 +41,4 @@ export class AnnotatedRevisionsController extends BaseRevisionsController {
override async deleteRevision(request: Request, response: Response): Promise<results.JsonResult> {
return super.deleteRevision(request, response)
}
@httpPost('/revisions/transition')
override async transition(request: Request, response: Response): Promise<results.JsonResult> {
return super.transition(request, response)
}
}

View File

@@ -11,7 +11,6 @@ import { GetRevision } from '../../../Domain/UseCase/GetRevision/GetRevision'
import { GetRevisionsMetada } from '../../../Domain/UseCase/GetRevisionsMetada/GetRevisionsMetada'
import { RevisionHttpRepresentation } from '../../../Mapping/Http/RevisionHttpRepresentation'
import { RevisionMetadataHttpRepresentation } from '../../../Mapping/Http/RevisionMetadataHttpRepresentation'
import { TriggerTransitionFromPrimaryToSecondaryDatabaseForUser } from '../../../Domain/UseCase/Transition/TriggerTransitionFromPrimaryToSecondaryDatabaseForUser/TriggerTransitionFromPrimaryToSecondaryDatabaseForUser'
export class BaseRevisionsController extends BaseHttpController {
constructor(
@@ -20,7 +19,6 @@ export class BaseRevisionsController extends BaseHttpController {
protected doDeleteRevision: DeleteRevision,
protected revisionHttpMapper: MapperInterface<Revision, RevisionHttpRepresentation>,
protected revisionMetadataHttpMapper: MapperInterface<RevisionMetadata, RevisionMetadataHttpRepresentation>,
protected triggerTransitionFromPrimaryToSecondaryDatabaseForUser: TriggerTransitionFromPrimaryToSecondaryDatabaseForUser,
private controllerContainer?: ControllerContainerInterface,
) {
super()
@@ -29,7 +27,6 @@ export class BaseRevisionsController extends BaseHttpController {
this.controllerContainer.register('revisions.revisions.getRevisions', this.getRevisions.bind(this))
this.controllerContainer.register('revisions.revisions.getRevision', this.getRevision.bind(this))
this.controllerContainer.register('revisions.revisions.deleteRevision', this.deleteRevision.bind(this))
this.controllerContainer.register('revisions.revisions.transition', this.transition.bind(this))
}
}
@@ -108,23 +105,4 @@ export class BaseRevisionsController extends BaseHttpController {
message: revisionOrError.getValue(),
})
}
async transition(_request: Request, response: Response): Promise<results.JsonResult> {
const result = await this.triggerTransitionFromPrimaryToSecondaryDatabaseForUser.execute({
userUuid: response.locals.user.uuid,
})
if (result.isFailed()) {
return this.json(
{
error: { message: result.getError() },
},
400,
)
}
response.setHeader('x-invalidate-cache', response.locals.user.uuid)
return this.json({ success: true })
}
}

View File

@@ -3,6 +3,34 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.20.46](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.20.45...@standardnotes/scheduler-server@1.20.46) (2023-09-15)
**Note:** Version bump only for package @standardnotes/scheduler-server
## [1.20.45](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.20.44...@standardnotes/scheduler-server@1.20.45) (2023-09-15)
**Note:** Version bump only for package @standardnotes/scheduler-server
## [1.20.44](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.20.43...@standardnotes/scheduler-server@1.20.44) (2023-09-13)
**Note:** Version bump only for package @standardnotes/scheduler-server
## [1.20.43](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.20.42...@standardnotes/scheduler-server@1.20.43) (2023-09-12)
**Note:** Version bump only for package @standardnotes/scheduler-server
## [1.20.42](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.20.41...@standardnotes/scheduler-server@1.20.42) (2023-09-12)
**Note:** Version bump only for package @standardnotes/scheduler-server
## [1.20.41](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.20.40...@standardnotes/scheduler-server@1.20.41) (2023-09-12)
**Note:** Version bump only for package @standardnotes/scheduler-server
## [1.20.40](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.20.39...@standardnotes/scheduler-server@1.20.40) (2023-09-12)
**Note:** Version bump only for package @standardnotes/scheduler-server
## [1.20.39](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.20.38...@standardnotes/scheduler-server@1.20.39) (2023-09-08)
**Note:** Version bump only for package @standardnotes/scheduler-server

View File

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

View File

@@ -3,6 +3,12 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.13.1](https://github.com/standardnotes/server/compare/@standardnotes/security@1.13.0...@standardnotes/security@1.13.1) (2023-09-12)
### Bug Fixes
* adjust transitions to not create revisions during ongoing revisions transition ([106d8f9](https://github.com/standardnotes/server/commit/106d8f9192f630794ca4ddc2c4503f2c6cd196e7))
# [1.13.0](https://github.com/standardnotes/server/compare/@standardnotes/security@1.12.2...@standardnotes/security@1.13.0) (2023-09-06)
### Features

View File

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

View File

@@ -25,4 +25,5 @@ export type CrossServiceTokenData = {
}
extensionKey?: string
ongoing_transition?: boolean
ongoing_revisions_transition?: boolean
}

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.21.32](https://github.com/standardnotes/server/compare/@standardnotes/settings@1.21.31...@standardnotes/settings@1.21.32) (2023-09-15)
**Note:** Version bump only for package @standardnotes/settings
## [1.21.31](https://github.com/standardnotes/server/compare/@standardnotes/settings@1.21.30...@standardnotes/settings@1.21.31) (2023-09-12)
**Note:** Version bump only for package @standardnotes/settings
## [1.21.30](https://github.com/standardnotes/server/compare/@standardnotes/settings@1.21.29...@standardnotes/settings@1.21.30) (2023-09-07)
**Note:** Version bump only for package @standardnotes/settings

View File

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

View File

@@ -3,6 +3,120 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.97.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.96.1...@standardnotes/syncing-server@1.97.0) (2023-09-15)
### Features
* refactor transition to minimize status changes ([#828](https://github.com/standardnotes/syncing-server-js/issues/828)) ([36f07c6](https://github.com/standardnotes/syncing-server-js/commit/36f07c691afc213ecf817d6e98f885ddb19a6ed6))
## [1.96.1](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.96.0...@standardnotes/syncing-server@1.96.1) (2023-09-15)
### Bug Fixes
* add debug logs for transition status updates ([3e7856c](https://github.com/standardnotes/syncing-server-js/commit/3e7856c895e73b775c8977c6c6e86dffd5755c00))
# [1.96.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.95.19...@standardnotes/syncing-server@1.96.0) (2023-09-15)
### Features
* add skipping verified transitions ([#827](https://github.com/standardnotes/syncing-server-js/issues/827)) ([d4d4945](https://github.com/standardnotes/syncing-server-js/commit/d4d49454a68de0acdf440dc202fa14b9743905f6))
## [1.95.19](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.95.18...@standardnotes/syncing-server@1.95.19) (2023-09-15)
### Bug Fixes
* **syncing-server:** remove unused index in mongodb ([9147ff5](https://github.com/standardnotes/syncing-server-js/commit/9147ff5d49c507d943f4f8c6775f7c1fff878b0f))
## [1.95.18](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.95.17...@standardnotes/syncing-server@1.95.18) (2023-09-14)
### Bug Fixes
* skip already updated items and revisions in integrity check ([#825](https://github.com/standardnotes/syncing-server-js/issues/825)) ([03a4a3f](https://github.com/standardnotes/syncing-server-js/commit/03a4a3f2abc0b4e09942ba39dbd227524068dfb6))
* **syncing-server:** updating with missing creation date ([#824](https://github.com/standardnotes/syncing-server-js/issues/824)) ([3a8607d](https://github.com/standardnotes/syncing-server-js/commit/3a8607d1465cabedad68b84c753e407342e60d20))
## [1.95.17](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.95.16...@standardnotes/syncing-server@1.95.17) (2023-09-13)
### Bug Fixes
* adjust transition timestamps to be universal ([c7807d0](https://github.com/standardnotes/syncing-server-js/commit/c7807d0f9e69ce572c4c03ff606375d706f24d9f))
## [1.95.16](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.95.15...@standardnotes/syncing-server@1.95.16) (2023-09-13)
### Bug Fixes
* include handling updated items in revisions in secondary ([fbcb45c](https://github.com/standardnotes/syncing-server-js/commit/fbcb45c3a23fde09702fae7bfcb409bdbb610191))
## [1.95.15](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.95.14...@standardnotes/syncing-server@1.95.15) (2023-09-13)
### Bug Fixes
* display transition progress in logs ([38685c1](https://github.com/standardnotes/syncing-server-js/commit/38685c1861b13e398dd96aa39f2cf1aece2090fb))
## [1.95.14](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.95.13...@standardnotes/syncing-server@1.95.14) (2023-09-13)
### Bug Fixes
* setting status for already migrated users ([9be4c00](https://github.com/standardnotes/syncing-server-js/commit/9be4c002b755fea057489b6077b297162223aefe))
## [1.95.13](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.95.12...@standardnotes/syncing-server@1.95.13) (2023-09-13)
### Bug Fixes
* cleanup only for 0 new items ([b1d88b1](https://github.com/standardnotes/syncing-server-js/commit/b1d88b15be78a48224963e337a222fb675ed2692))
* **syncing-server:** add catch up timeout for secondary db ([ff78285](https://github.com/standardnotes/syncing-server-js/commit/ff78285e43db849bdc44caa36f602150562b4d81))
* **syncing-server:** case insensitive integrity check ([d5536f5](https://github.com/standardnotes/syncing-server-js/commit/d5536f54304e2aecd59dbece7650254f7c2101bb))
## [1.95.12](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.95.11...@standardnotes/syncing-server@1.95.12) (2023-09-12)
### Bug Fixes
* sync between primary and secondary database with diff ([fab5d18](https://github.com/standardnotes/syncing-server-js/commit/fab5d180645e0a6fa0c9c67205d44f27c8a65c8b))
## [1.95.11](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.95.10...@standardnotes/syncing-server@1.95.11) (2023-09-12)
### Bug Fixes
* **syncing-server:** binding ([e91a832](https://github.com/standardnotes/syncing-server-js/commit/e91a8321527ac269ba9822ce270184db5bc57099))
## [1.95.10](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.95.9...@standardnotes/syncing-server@1.95.10) (2023-09-12)
### Bug Fixes
* retry failed revision transitions ([e535cd5](https://github.com/standardnotes/syncing-server-js/commit/e535cd504cf1929539ff7faf13e9c1fdd2b7bfd1))
* **syncing-server:** log syncing errors ([b9c9f74](https://github.com/standardnotes/syncing-server-js/commit/b9c9f74d0c699cf72ea6090627bd5716ac8360d7))
## [1.95.9](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.95.8...@standardnotes/syncing-server@1.95.9) (2023-09-12)
**Note:** Version bump only for package @standardnotes/syncing-server
## [1.95.8](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.95.7...@standardnotes/syncing-server@1.95.8) (2023-09-12)
### Bug Fixes
* **syncing-server:** allow fetching shared vault users for members ([#821](https://github.com/standardnotes/syncing-server-js/issues/821)) ([25047bf](https://github.com/standardnotes/syncing-server-js/commit/25047bf46dfabba7b12eafb59519de3f08822e45))
## [1.95.7](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.95.6...@standardnotes/syncing-server@1.95.7) (2023-09-12)
### Bug Fixes
* comparing uuids ([0a1d162](https://github.com/standardnotes/syncing-server-js/commit/0a1d1624e818000f2e951f29323a88e6e233c755))
## [1.95.6](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.95.5...@standardnotes/syncing-server@1.95.6) (2023-09-12)
**Note:** Version bump only for package @standardnotes/syncing-server
## [1.95.5](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.95.4...@standardnotes/syncing-server@1.95.5) (2023-09-12)
### Bug Fixes
* adjust transitions to not create revisions during ongoing revisions transition ([106d8f9](https://github.com/standardnotes/syncing-server-js/commit/106d8f9192f630794ca4ddc2c4503f2c6cd196e7))
## [1.95.4](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.95.3...@standardnotes/syncing-server@1.95.4) (2023-09-12)
### Bug Fixes
* transition adjustments ([f20a947](https://github.com/standardnotes/syncing-server-js/commit/f20a947f8a555c074d8dc1543c7a8bf61d1d887e))
## [1.95.3](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.95.2...@standardnotes/syncing-server@1.95.3) (2023-09-11)
### Bug Fixes

View File

@@ -1,50 +0,0 @@
import 'reflect-metadata'
import { Logger } from 'winston'
import { ContainerConfigLoader } from '../src/Bootstrap/Container'
import TYPES from '../src/Bootstrap/Types'
import { Env } from '../src/Bootstrap/Env'
import { TriggerTransitionFromPrimaryToSecondaryDatabaseForUser } from '../src/Domain/UseCase/Transition/TriggerTransitionFromPrimaryToSecondaryDatabaseForUser/TriggerTransitionFromPrimaryToSecondaryDatabaseForUser'
const inputArgs = process.argv.slice(2)
const userUuid = inputArgs[0]
const requestTransition = async (
triggerTransitionFromPrimaryToSecondaryDatabaseForUser: TriggerTransitionFromPrimaryToSecondaryDatabaseForUser,
logger: Logger,
): Promise<void> => {
const result = await triggerTransitionFromPrimaryToSecondaryDatabaseForUser.execute({
userUuid,
})
if (result.isFailed()) {
logger.error(`Could not trigger transition for user ${userUuid}: ${result.getError()}`)
}
return
}
const container = new ContainerConfigLoader('worker')
void container.load().then((container) => {
const env: Env = new Env()
env.load()
const logger: Logger = container.get(TYPES.Sync_Logger)
logger.info(`Starting transitiong for user ${userUuid} ...`)
const triggerTransitionFromPrimaryToSecondaryDatabaseForUser: TriggerTransitionFromPrimaryToSecondaryDatabaseForUser =
container.get(TYPES.Sync_TriggerTransitionFromPrimaryToSecondaryDatabaseForUser)
Promise.resolve(requestTransition(triggerTransitionFromPrimaryToSecondaryDatabaseForUser, logger))
.then(() => {
logger.info(`Transition triggered for user ${userUuid}`)
process.exit(0)
})
.catch((error) => {
logger.error(`Could not trigger transition for user ${userUuid}: ${error.message}`)
process.exit(1)
})
})

View File

@@ -1,11 +0,0 @@
'use strict'
const path = require('path')
const pnp = require(path.normalize(path.resolve(__dirname, '../../..', '.pnp.cjs'))).setup()
const index = require(path.normalize(path.resolve(__dirname, '../dist/bin/transition.js')))
Object.defineProperty(exports, '__esModule', { value: true })
exports.default = index

View File

@@ -14,12 +14,6 @@ case "$COMMAND" in
node docker/entrypoint-worker.js
;;
'transition' )
echo "[Docker] Starting transition Single User..."
USER_UUID=$1 && shift 1
node docker/entrypoint-transition.js $USER_UUID
;;
* )
echo "[Docker] Unknown command"
;;

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/syncing-server",
"version": "1.95.3",
"version": "1.97.0",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -160,8 +160,6 @@ import { ItemRepositoryResolverInterface } from '../Domain/Item/ItemRepositoryRe
import { TypeORMItemRepositoryResolver } from '../Infra/TypeORM/TypeORMItemRepositoryResolver'
import { TransitionItemsFromPrimaryToSecondaryDatabaseForUser } from '../Domain/UseCase/Transition/TransitionItemsFromPrimaryToSecondaryDatabaseForUser/TransitionItemsFromPrimaryToSecondaryDatabaseForUser'
import { SharedVaultFileMovedEventHandler } from '../Domain/Handler/SharedVaultFileMovedEventHandler'
import { TransitionStatusUpdatedEventHandler } from '../Domain/Handler/TransitionStatusUpdatedEventHandler'
import { TriggerTransitionFromPrimaryToSecondaryDatabaseForUser } from '../Domain/UseCase/Transition/TriggerTransitionFromPrimaryToSecondaryDatabaseForUser/TriggerTransitionFromPrimaryToSecondaryDatabaseForUser'
import { SQLItem } from '../Infra/TypeORM/SQLItem'
import { SQLItemPersistenceMapper } from '../Mapping/Persistence/SQLItemPersistenceMapper'
import { SQLItemRepository } from '../Infra/TypeORM/SQLItemRepository'
@@ -666,13 +664,14 @@ export class ContainerConfigLoader {
.bind<SyncItems>(TYPES.Sync_SyncItems)
.toConstantValue(
new SyncItems(
container.get(TYPES.Sync_ItemRepositoryResolver),
container.get(TYPES.Sync_GetItems),
container.get(TYPES.Sync_SaveItems),
container.get(TYPES.Sync_GetSharedVaults),
container.get(TYPES.Sync_GetSharedVaultInvitesSentToUser),
container.get(TYPES.Sync_GetMessagesSentToUser),
container.get(TYPES.Sync_GetUserNotifications),
container.get<ItemRepositoryResolverInterface>(TYPES.Sync_ItemRepositoryResolver),
container.get<GetItems>(TYPES.Sync_GetItems),
container.get<SaveItems>(TYPES.Sync_SaveItems),
container.get<GetSharedVaults>(TYPES.Sync_GetSharedVaults),
container.get<GetSharedVaultInvitesSentToUser>(TYPES.Sync_GetSharedVaultInvitesSentToUser),
container.get<GetMessagesSentToUser>(TYPES.Sync_GetMessagesSentToUser),
container.get<GetUserNotifications>(TYPES.Sync_GetUserNotifications),
container.get<Logger>(TYPES.Sync_Logger),
),
)
container.bind<CheckIntegrity>(TYPES.Sync_CheckIntegrity).toDynamicValue((context: interfaces.Context) => {
@@ -836,16 +835,6 @@ export class ContainerConfigLoader {
env.get('MIGRATION_BATCH_SIZE', true) ? +env.get('MIGRATION_BATCH_SIZE', true) : 100,
),
)
container
.bind<TriggerTransitionFromPrimaryToSecondaryDatabaseForUser>(
TYPES.Sync_TriggerTransitionFromPrimaryToSecondaryDatabaseForUser,
)
.toConstantValue(
new TriggerTransitionFromPrimaryToSecondaryDatabaseForUser(
container.get<DomainEventPublisherInterface>(TYPES.Sync_DomainEventPublisher),
container.get<DomainEventFactoryInterface>(TYPES.Sync_DomainEventFactory),
),
)
// Services
container
@@ -948,9 +937,10 @@ export class ContainerConfigLoader {
),
)
container
.bind<TransitionStatusUpdatedEventHandler>(TYPES.Sync_TransitionStatusUpdatedEventHandler)
.bind<TransitionRequestedEventHandler>(TYPES.Sync_TransitionRequestedEventHandler)
.toConstantValue(
new TransitionStatusUpdatedEventHandler(
new TransitionRequestedEventHandler(
container.get<ItemRepositoryInterface>(TYPES.Sync_SQLItemRepository),
container.get<TransitionItemsFromPrimaryToSecondaryDatabaseForUser>(
TYPES.Sync_TransitionItemsFromPrimaryToSecondaryDatabaseForUser,
),
@@ -959,16 +949,6 @@ export class ContainerConfigLoader {
container.get<Logger>(TYPES.Sync_Logger),
),
)
container
.bind<TransitionRequestedEventHandler>(TYPES.Sync_TransitionRequestedEventHandler)
.toConstantValue(
new TransitionRequestedEventHandler(
container.get<TriggerTransitionFromPrimaryToSecondaryDatabaseForUser>(
TYPES.Sync_TriggerTransitionFromPrimaryToSecondaryDatabaseForUser,
),
container.get<Logger>(TYPES.Sync_Logger),
),
)
// Services
container.bind<ContentDecoder>(TYPES.Sync_ContentDecoder).toDynamicValue(() => new ContentDecoder())
@@ -1003,10 +983,6 @@ export class ContainerConfigLoader {
'SHARED_VAULT_FILE_MOVED',
container.get<SharedVaultFileMovedEventHandler>(TYPES.Sync_SharedVaultFileMovedEventHandler),
],
[
'TRANSITION_STATUS_UPDATED',
container.get<TransitionStatusUpdatedEventHandler>(TYPES.Sync_TransitionStatusUpdatedEventHandler),
],
[
'TRANSITION_REQUESTED',
container.get<TransitionRequestedEventHandler>(TYPES.Sync_TransitionRequestedEventHandler),
@@ -1088,9 +1064,6 @@ export class ContainerConfigLoader {
container.get<SyncItems>(TYPES.Sync_SyncItems),
container.get<CheckIntegrity>(TYPES.Sync_CheckIntegrity),
container.get<GetItem>(TYPES.Sync_GetItem),
container.get<TriggerTransitionFromPrimaryToSecondaryDatabaseForUser>(
TYPES.Sync_TriggerTransitionFromPrimaryToSecondaryDatabaseForUser,
),
container.get<MapperInterface<Item, ItemHttpRepresentation>>(TYPES.Sync_ItemHttpMapper),
container.get<SyncResponseFactoryResolverInterface>(TYPES.Sync_SyncResponseFactoryResolver),
container.get<ControllerContainerInterface>(TYPES.Sync_ControllerContainer),

View File

@@ -84,9 +84,6 @@ const TYPES = {
Sync_TransitionItemsFromPrimaryToSecondaryDatabaseForUser: Symbol.for(
'Sync_TransitionItemsFromPrimaryToSecondaryDatabaseForUser',
),
Sync_TriggerTransitionFromPrimaryToSecondaryDatabaseForUser: Symbol.for(
'Sync_TriggerTransitionFromPrimaryToSecondaryDatabaseForUser',
),
Sync_SendEventToClient: Symbol.for('Sync_SendEventToClient'),
// Handlers
Sync_AccountDeletionRequestedEventHandler: Symbol.for('Sync_AccountDeletionRequestedEventHandler'),
@@ -96,7 +93,6 @@ const TYPES = {
Sync_SharedVaultFileRemovedEventHandler: Symbol.for('Sync_SharedVaultFileRemovedEventHandler'),
Sync_SharedVaultFileUploadedEventHandler: Symbol.for('Sync_SharedVaultFileUploadedEventHandler'),
Sync_SharedVaultFileMovedEventHandler: Symbol.for('Sync_SharedVaultFileMovedEventHandler'),
Sync_TransitionStatusUpdatedEventHandler: Symbol.for('Sync_TransitionStatusUpdatedEventHandler'),
Sync_TransitionRequestedEventHandler: Symbol.for('Sync_TransitionRequestedEventHandler'),
// Services
Sync_ContentDecoder: Symbol.for('Sync_ContentDecoder'),

View File

@@ -172,7 +172,8 @@ export class DomainEventFactory implements DomainEventFactoryInterface {
createTransitionStatusUpdatedEvent(dto: {
userUuid: string
transitionType: 'items' | 'revisions'
status: 'STARTED' | 'FAILED' | 'FINISHED'
transitionTimestamp: number
status: string
}): TransitionStatusUpdatedEvent {
return {
type: 'TRANSITION_STATUS_UPDATED',

View File

@@ -52,7 +52,8 @@ export interface DomainEventFactoryInterface {
createTransitionStatusUpdatedEvent(dto: {
userUuid: string
transitionType: 'items' | 'revisions'
status: 'STARTED' | 'FAILED' | 'FINISHED'
transitionTimestamp: number
status: string
}): TransitionStatusUpdatedEvent
createEmailRequestedEvent(dto: {
userEmail: string

View File

@@ -1,23 +1,118 @@
import { DomainEventHandlerInterface, TransitionRequestedEvent } from '@standardnotes/domain-events'
import {
DomainEventHandlerInterface,
DomainEventPublisherInterface,
TransitionRequestedEvent,
} from '@standardnotes/domain-events'
import { Logger } from 'winston'
import { TransitionStatus, Uuid } from '@standardnotes/domain-core'
import { TriggerTransitionFromPrimaryToSecondaryDatabaseForUser } from '../UseCase/Transition/TriggerTransitionFromPrimaryToSecondaryDatabaseForUser/TriggerTransitionFromPrimaryToSecondaryDatabaseForUser'
import { TransitionItemsFromPrimaryToSecondaryDatabaseForUser } from '../UseCase/Transition/TransitionItemsFromPrimaryToSecondaryDatabaseForUser/TransitionItemsFromPrimaryToSecondaryDatabaseForUser'
import { ItemRepositoryInterface } from '../Item/ItemRepositoryInterface'
import { DomainEventFactoryInterface } from '../Event/DomainEventFactoryInterface'
export class TransitionRequestedEventHandler implements DomainEventHandlerInterface {
constructor(
private triggerTransitionFromPrimaryToSecondaryDatabaseForUser: TriggerTransitionFromPrimaryToSecondaryDatabaseForUser,
private primaryItemRepository: ItemRepositoryInterface,
private transitionItemsFromPrimaryToSecondaryDatabaseForUser: TransitionItemsFromPrimaryToSecondaryDatabaseForUser,
private domainEventPublisher: DomainEventPublisherInterface,
private domainEventFactory: DomainEventFactoryInterface,
private logger: Logger,
) {}
async handle(event: TransitionRequestedEvent): Promise<void> {
if (event.payload.type !== 'items') {
return
}
const userUuid = await this.getUserUuidFromEvent(event)
if (!userUuid) {
return
}
if (await this.isAlreadyMigrated(userUuid)) {
this.logger.info(`User ${event.payload.userUuid} already migrated.`)
await this.domainEventPublisher.publish(
this.domainEventFactory.createTransitionStatusUpdatedEvent({
userUuid: event.payload.userUuid,
status: TransitionStatus.STATUSES.Verified,
transitionType: 'items',
transitionTimestamp: event.payload.timestamp,
}),
)
return
}
this.logger.info(`Handling transition requested event for user ${event.payload.userUuid}`)
const result = await this.triggerTransitionFromPrimaryToSecondaryDatabaseForUser.execute({
await this.domainEventPublisher.publish(
this.domainEventFactory.createTransitionStatusUpdatedEvent({
userUuid: event.payload.userUuid,
status: TransitionStatus.STATUSES.InProgress,
transitionType: 'items',
transitionTimestamp: event.payload.timestamp,
}),
)
const result = await this.transitionItemsFromPrimaryToSecondaryDatabaseForUser.execute({
userUuid: event.payload.userUuid,
})
if (result.isFailed()) {
this.logger.error(`Failed to trigger transition for user ${event.payload.userUuid}`)
await this.domainEventPublisher.publish(
this.domainEventFactory.createTransitionStatusUpdatedEvent({
userUuid: event.payload.userUuid,
status: TransitionStatus.STATUSES.Failed,
transitionType: 'items',
transitionTimestamp: event.payload.timestamp,
}),
)
return
}
await this.domainEventPublisher.publish(
this.domainEventFactory.createTransitionStatusUpdatedEvent({
userUuid: event.payload.userUuid,
status: TransitionStatus.STATUSES.Verified,
transitionType: 'items',
transitionTimestamp: event.payload.timestamp,
}),
)
}
private async isAlreadyMigrated(userUuid: Uuid): Promise<boolean> {
const totalItemsCountForUserInPrimary = await this.primaryItemRepository.countAll({
userUuid: userUuid.value,
})
if (totalItemsCountForUserInPrimary > 0) {
this.logger.info(`User ${userUuid.value} has ${totalItemsCountForUserInPrimary} items in primary database.`)
}
return totalItemsCountForUserInPrimary === 0
}
private async getUserUuidFromEvent(event: TransitionRequestedEvent): Promise<Uuid | null> {
const userUuidOrError = Uuid.create(event.payload.userUuid)
if (userUuidOrError.isFailed()) {
this.logger.error(`Failed to transition items for user ${event.payload.userUuid}: ${userUuidOrError.getError()}`)
await this.domainEventPublisher.publish(
this.domainEventFactory.createTransitionStatusUpdatedEvent({
userUuid: event.payload.userUuid,
status: TransitionStatus.STATUSES.Failed,
transitionType: 'items',
transitionTimestamp: event.payload.timestamp,
}),
)
return null
}
return userUuidOrError.getValue()
}
}

View File

@@ -1,47 +0,0 @@
import {
DomainEventHandlerInterface,
DomainEventPublisherInterface,
TransitionStatusUpdatedEvent,
} from '@standardnotes/domain-events'
import { Logger } from 'winston'
import { TransitionItemsFromPrimaryToSecondaryDatabaseForUser } from '../UseCase/Transition/TransitionItemsFromPrimaryToSecondaryDatabaseForUser/TransitionItemsFromPrimaryToSecondaryDatabaseForUser'
import { DomainEventFactoryInterface } from '../Event/DomainEventFactoryInterface'
export class TransitionStatusUpdatedEventHandler implements DomainEventHandlerInterface {
constructor(
private transitionItemsFromPrimaryToSecondaryDatabaseForUser: TransitionItemsFromPrimaryToSecondaryDatabaseForUser,
private domainEventPublisher: DomainEventPublisherInterface,
private domainEventFactory: DomainEventFactoryInterface,
private logger: Logger,
) {}
async handle(event: TransitionStatusUpdatedEvent): Promise<void> {
if (event.payload.status === 'STARTED' && event.payload.transitionType === 'items') {
const result = await this.transitionItemsFromPrimaryToSecondaryDatabaseForUser.execute({
userUuid: event.payload.userUuid,
})
if (result.isFailed()) {
this.logger.error(`Failed to transition items for user ${event.payload.userUuid}: ${result.getError()}`)
await this.domainEventPublisher.publish(
this.domainEventFactory.createTransitionStatusUpdatedEvent({
userUuid: event.payload.userUuid,
status: 'FAILED',
transitionType: 'items',
}),
)
return
}
await this.domainEventPublisher.publish(
this.domainEventFactory.createTransitionStatusUpdatedEvent({
userUuid: event.payload.userUuid,
status: 'FINISHED',
transitionType: 'items',
}),
)
}
}
}

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