Compare commits

...

15 Commits

Author SHA1 Message Date
standardci
4715e019a2 chore(release): publish new version
- @standardnotes/analytics@2.26.4
 - @standardnotes/api-gateway@1.74.0
 - @standardnotes/auth-server@1.138.0
 - @standardnotes/domain-core@1.27.0
 - @standardnotes/domain-events-infra@1.12.22
 - @standardnotes/domain-events@2.123.0
 - @standardnotes/event-store@1.11.32
 - @standardnotes/files-server@1.22.11
 - @standardnotes/home-server@1.15.25
 - @standardnotes/revisions-server@1.31.0
 - @standardnotes/scheduler-server@1.20.36
 - @standardnotes/security@1.13.0
 - @standardnotes/settings@1.21.29
 - @standardnotes/syncing-server@1.92.0
 - @standardnotes/websockets-server@1.10.33
2023-09-06 14:12:13 +00:00
Karol Sójko
794cd8734a feat: should be able to access shared item revisions as third party user (#807) 2023-09-06 15:36:23 +02:00
standardci
14d42b26bb chore(release): publish new version
- @standardnotes/websockets-server@1.10.32
2023-09-05 12:29:23 +00:00
Karol Sójko
6bb44afd91 fix(websockets): add missing region parameter in api gateway client 2023-09-05 13:50:30 +02:00
standardci
c82345aeeb chore(release): publish new version
- @standardnotes/websockets-server@1.10.31
2023-09-05 06:49:48 +00:00
Karol Sójko
72ab08a0d0 fix(websockets): issue with sending messages to active websocket connections (#806) 2023-09-05 08:13:42 +02:00
standardci
f2d1b47e40 chore(release): publish new version
- @standardnotes/home-server@1.15.24
 - @standardnotes/revisions-server@1.30.14
 - @standardnotes/syncing-server@1.91.3
2023-09-04 12:48:16 +00:00
Karol Sójko
d9ee2c5be2 fix: prevent doubling transitions 2023-09-04 14:10:37 +02:00
standardci
eb59902cf7 chore(release): publish new version
- @standardnotes/home-server@1.15.23
 - @standardnotes/revisions-server@1.30.13
 - @standardnotes/websockets-server@1.10.30
2023-09-04 11:11:04 +00:00
Karol Sójko
002074e4d1 fix(websockets): stringify response from the AWS Api Gateway 2023-09-04 12:32:47 +02:00
Karol Sójko
45b55068f9 fix(revisions): change logs severity in the transition process 2023-09-04 12:19:03 +02:00
Karol Sójko
157eee5d93 chore: increase mocha headless chrome global timeout 2023-09-04 12:06:49 +02:00
standardci
d5f2b4f6eb chore(release): publish new version
- @standardnotes/home-server@1.15.22
 - @standardnotes/revisions-server@1.30.12
 - @standardnotes/websockets-server@1.10.29
2023-09-04 10:05:10 +00:00
Karol Sójko
a7a93497e8 fix(revisions): change order field for transition of revisions 2023-09-04 10:56:14 +02:00
Karol Sójko
8f96f0ed7a fix(websockets): add response debug on websockets sending 2023-09-04 10:43:58 +02:00
149 changed files with 2655 additions and 328 deletions

View File

@@ -57,7 +57,7 @@ jobs:
run: docker/is-available.sh http://localhost:3123 $(pwd)/logs
- name: Run E2E Test Suite
run: yarn dlx mocha-headless-chrome --timeout 1800000 -f http://localhost:9001/mocha/test.html
run: yarn dlx mocha-headless-chrome --timeout 3600000 -f http://localhost:9001/mocha/test.html
- name: Show logs on failure
if: ${{ failure() }}
@@ -162,7 +162,7 @@ jobs:
run: for i in {1..30}; do curl -s http://localhost:3123/healthcheck && break || sleep 1; done
- name: Run E2E Test Suite
run: yarn dlx mocha-headless-chrome --timeout 1800000 -f http://localhost:9001/mocha/test.html
run: yarn dlx mocha-headless-chrome --timeout 3600000 -f http://localhost:9001/mocha/test.html
- name: Show logs on failure
if: ${{ failure() }}

322
.pnp.cjs generated
View File

@@ -275,6 +275,51 @@ const RAW_RUNTIME_STATE =
"linkType": "HARD"\
}]\
]],\
["@aws-sdk/client-apigatewaymanagementapi", [\
["npm:3.405.0", {\
"packageLocation": "./.yarn/cache/@aws-sdk-client-apigatewaymanagementapi-npm-3.405.0-e4e17d811f-d7103d0b37.zip/node_modules/@aws-sdk/client-apigatewaymanagementapi/",\
"packageDependencies": [\
["@aws-sdk/client-apigatewaymanagementapi", "npm:3.405.0"],\
["@aws-crypto/sha256-browser", "npm:3.0.0"],\
["@aws-crypto/sha256-js", "npm:3.0.0"],\
["@aws-sdk/client-sts", "npm:3.405.0"],\
["@aws-sdk/credential-provider-node", "npm:3.405.0"],\
["@aws-sdk/middleware-host-header", "npm:3.398.0"],\
["@aws-sdk/middleware-logger", "npm:3.398.0"],\
["@aws-sdk/middleware-recursion-detection", "npm:3.398.0"],\
["@aws-sdk/middleware-signing", "npm:3.398.0"],\
["@aws-sdk/middleware-user-agent", "npm:3.398.0"],\
["@aws-sdk/types", "npm:3.398.0"],\
["@aws-sdk/util-endpoints", "npm:3.398.0"],\
["@aws-sdk/util-user-agent-browser", "npm:3.398.0"],\
["@aws-sdk/util-user-agent-node", "virtual:0b227682399940b7e75175c6ae399b938ec07d3986be60e73f8026d2677e9fe96d05ac258f3f223cf1b6bdd7fd2a24f55f0efa40a8406ba949dc1b7af3a6a968#npm:3.405.0"],\
["@smithy/config-resolver", "npm:2.0.5"],\
["@smithy/fetch-http-handler", "npm:2.0.5"],\
["@smithy/hash-node", "npm:2.0.5"],\
["@smithy/invalid-dependency", "npm:2.0.5"],\
["@smithy/middleware-content-length", "npm:2.0.5"],\
["@smithy/middleware-endpoint", "npm:2.0.5"],\
["@smithy/middleware-retry", "npm:2.0.5"],\
["@smithy/middleware-serde", "npm:2.0.5"],\
["@smithy/middleware-stack", "npm:2.0.0"],\
["@smithy/node-config-provider", "npm:2.0.7"],\
["@smithy/node-http-handler", "npm:2.0.5"],\
["@smithy/protocol-http", "npm:2.0.5"],\
["@smithy/smithy-client", "npm:2.0.5"],\
["@smithy/types", "npm:2.2.2"],\
["@smithy/url-parser", "npm:2.0.5"],\
["@smithy/util-base64", "npm:2.0.0"],\
["@smithy/util-body-length-browser", "npm:2.0.0"],\
["@smithy/util-body-length-node", "npm:2.1.0"],\
["@smithy/util-defaults-mode-browser", "npm:2.0.6"],\
["@smithy/util-defaults-mode-node", "npm:2.0.7"],\
["@smithy/util-retry", "npm:2.0.0"],\
["@smithy/util-utf8", "npm:2.0.0"],\
["tslib", "npm:2.5.2"]\
],\
"linkType": "HARD"\
}]\
]],\
["@aws-sdk/client-lambda", [\
["npm:3.398.0", {\
"packageLocation": "./.yarn/cache/@aws-sdk-client-lambda-npm-3.398.0-fa4aacfc7b-85ef0fe18d.zip/node_modules/@aws-sdk/client-lambda/",\
@@ -563,6 +608,46 @@ const RAW_RUNTIME_STATE =
["tslib", "npm:2.5.2"]\
],\
"linkType": "HARD"\
}],\
["npm:3.405.0", {\
"packageLocation": "./.yarn/cache/@aws-sdk-client-sso-npm-3.405.0-0b22768239-323f99e024.zip/node_modules/@aws-sdk/client-sso/",\
"packageDependencies": [\
["@aws-sdk/client-sso", "npm:3.405.0"],\
["@aws-crypto/sha256-browser", "npm:3.0.0"],\
["@aws-crypto/sha256-js", "npm:3.0.0"],\
["@aws-sdk/middleware-host-header", "npm:3.398.0"],\
["@aws-sdk/middleware-logger", "npm:3.398.0"],\
["@aws-sdk/middleware-recursion-detection", "npm:3.398.0"],\
["@aws-sdk/middleware-user-agent", "npm:3.398.0"],\
["@aws-sdk/types", "npm:3.398.0"],\
["@aws-sdk/util-endpoints", "npm:3.398.0"],\
["@aws-sdk/util-user-agent-browser", "npm:3.398.0"],\
["@aws-sdk/util-user-agent-node", "virtual:0b227682399940b7e75175c6ae399b938ec07d3986be60e73f8026d2677e9fe96d05ac258f3f223cf1b6bdd7fd2a24f55f0efa40a8406ba949dc1b7af3a6a968#npm:3.405.0"],\
["@smithy/config-resolver", "npm:2.0.5"],\
["@smithy/fetch-http-handler", "npm:2.0.5"],\
["@smithy/hash-node", "npm:2.0.5"],\
["@smithy/invalid-dependency", "npm:2.0.5"],\
["@smithy/middleware-content-length", "npm:2.0.5"],\
["@smithy/middleware-endpoint", "npm:2.0.5"],\
["@smithy/middleware-retry", "npm:2.0.5"],\
["@smithy/middleware-serde", "npm:2.0.5"],\
["@smithy/middleware-stack", "npm:2.0.0"],\
["@smithy/node-config-provider", "npm:2.0.7"],\
["@smithy/node-http-handler", "npm:2.0.5"],\
["@smithy/protocol-http", "npm:2.0.5"],\
["@smithy/smithy-client", "npm:2.0.5"],\
["@smithy/types", "npm:2.2.2"],\
["@smithy/url-parser", "npm:2.0.5"],\
["@smithy/util-base64", "npm:2.0.0"],\
["@smithy/util-body-length-browser", "npm:2.0.0"],\
["@smithy/util-body-length-node", "npm:2.1.0"],\
["@smithy/util-defaults-mode-browser", "npm:2.0.6"],\
["@smithy/util-defaults-mode-node", "npm:2.0.7"],\
["@smithy/util-retry", "npm:2.0.0"],\
["@smithy/util-utf8", "npm:2.0.0"],\
["tslib", "npm:2.5.2"]\
],\
"linkType": "HARD"\
}]\
]],\
["@aws-sdk/client-sso-oidc", [\
@@ -695,6 +780,50 @@ const RAW_RUNTIME_STATE =
["tslib", "npm:2.5.2"]\
],\
"linkType": "HARD"\
}],\
["npm:3.405.0", {\
"packageLocation": "./.yarn/cache/@aws-sdk-client-sts-npm-3.405.0-b83c3faf19-01ea2a8695.zip/node_modules/@aws-sdk/client-sts/",\
"packageDependencies": [\
["@aws-sdk/client-sts", "npm:3.405.0"],\
["@aws-crypto/sha256-browser", "npm:3.0.0"],\
["@aws-crypto/sha256-js", "npm:3.0.0"],\
["@aws-sdk/credential-provider-node", "npm:3.405.0"],\
["@aws-sdk/middleware-host-header", "npm:3.398.0"],\
["@aws-sdk/middleware-logger", "npm:3.398.0"],\
["@aws-sdk/middleware-recursion-detection", "npm:3.398.0"],\
["@aws-sdk/middleware-sdk-sts", "npm:3.398.0"],\
["@aws-sdk/middleware-signing", "npm:3.398.0"],\
["@aws-sdk/middleware-user-agent", "npm:3.398.0"],\
["@aws-sdk/types", "npm:3.398.0"],\
["@aws-sdk/util-endpoints", "npm:3.398.0"],\
["@aws-sdk/util-user-agent-browser", "npm:3.398.0"],\
["@aws-sdk/util-user-agent-node", "virtual:0b227682399940b7e75175c6ae399b938ec07d3986be60e73f8026d2677e9fe96d05ac258f3f223cf1b6bdd7fd2a24f55f0efa40a8406ba949dc1b7af3a6a968#npm:3.405.0"],\
["@smithy/config-resolver", "npm:2.0.5"],\
["@smithy/fetch-http-handler", "npm:2.0.5"],\
["@smithy/hash-node", "npm:2.0.5"],\
["@smithy/invalid-dependency", "npm:2.0.5"],\
["@smithy/middleware-content-length", "npm:2.0.5"],\
["@smithy/middleware-endpoint", "npm:2.0.5"],\
["@smithy/middleware-retry", "npm:2.0.5"],\
["@smithy/middleware-serde", "npm:2.0.5"],\
["@smithy/middleware-stack", "npm:2.0.0"],\
["@smithy/node-config-provider", "npm:2.0.7"],\
["@smithy/node-http-handler", "npm:2.0.5"],\
["@smithy/protocol-http", "npm:2.0.5"],\
["@smithy/smithy-client", "npm:2.0.5"],\
["@smithy/types", "npm:2.2.2"],\
["@smithy/url-parser", "npm:2.0.5"],\
["@smithy/util-base64", "npm:2.0.0"],\
["@smithy/util-body-length-browser", "npm:2.0.0"],\
["@smithy/util-body-length-node", "npm:2.1.0"],\
["@smithy/util-defaults-mode-browser", "npm:2.0.6"],\
["@smithy/util-defaults-mode-node", "npm:2.0.7"],\
["@smithy/util-retry", "npm:2.0.0"],\
["@smithy/util-utf8", "npm:2.0.0"],\
["fast-xml-parser", "npm:4.2.5"],\
["tslib", "npm:2.5.2"]\
],\
"linkType": "HARD"\
}]\
]],\
["@aws-sdk/config-resolver", [\
@@ -780,6 +909,23 @@ const RAW_RUNTIME_STATE =
["tslib", "npm:2.5.2"]\
],\
"linkType": "HARD"\
}],\
["npm:3.405.0", {\
"packageLocation": "./.yarn/cache/@aws-sdk-credential-provider-ini-npm-3.405.0-759c2d9674-0d2694b969.zip/node_modules/@aws-sdk/credential-provider-ini/",\
"packageDependencies": [\
["@aws-sdk/credential-provider-ini", "npm:3.405.0"],\
["@aws-sdk/credential-provider-env", "npm:3.398.0"],\
["@aws-sdk/credential-provider-process", "npm:3.405.0"],\
["@aws-sdk/credential-provider-sso", "npm:3.405.0"],\
["@aws-sdk/credential-provider-web-identity", "npm:3.398.0"],\
["@aws-sdk/types", "npm:3.398.0"],\
["@smithy/credential-provider-imds", "npm:2.0.5"],\
["@smithy/property-provider", "npm:2.0.5"],\
["@smithy/shared-ini-file-loader", "npm:2.0.6"],\
["@smithy/types", "npm:2.2.2"],\
["tslib", "npm:2.5.2"]\
],\
"linkType": "HARD"\
}]\
]],\
["@aws-sdk/credential-provider-node", [\
@@ -817,6 +963,24 @@ const RAW_RUNTIME_STATE =
["tslib", "npm:2.5.2"]\
],\
"linkType": "HARD"\
}],\
["npm:3.405.0", {\
"packageLocation": "./.yarn/cache/@aws-sdk-credential-provider-node-npm-3.405.0-33a4e3c01f-58cf90600d.zip/node_modules/@aws-sdk/credential-provider-node/",\
"packageDependencies": [\
["@aws-sdk/credential-provider-node", "npm:3.405.0"],\
["@aws-sdk/credential-provider-env", "npm:3.398.0"],\
["@aws-sdk/credential-provider-ini", "npm:3.405.0"],\
["@aws-sdk/credential-provider-process", "npm:3.405.0"],\
["@aws-sdk/credential-provider-sso", "npm:3.405.0"],\
["@aws-sdk/credential-provider-web-identity", "npm:3.398.0"],\
["@aws-sdk/types", "npm:3.398.0"],\
["@smithy/credential-provider-imds", "npm:2.0.5"],\
["@smithy/property-provider", "npm:2.0.5"],\
["@smithy/shared-ini-file-loader", "npm:2.0.6"],\
["@smithy/types", "npm:2.2.2"],\
["tslib", "npm:2.5.2"]\
],\
"linkType": "HARD"\
}]\
]],\
["@aws-sdk/credential-provider-process", [\
@@ -842,6 +1006,18 @@ const RAW_RUNTIME_STATE =
["tslib", "npm:2.5.2"]\
],\
"linkType": "HARD"\
}],\
["npm:3.405.0", {\
"packageLocation": "./.yarn/cache/@aws-sdk-credential-provider-process-npm-3.405.0-ed6dc867ed-bd23e267bd.zip/node_modules/@aws-sdk/credential-provider-process/",\
"packageDependencies": [\
["@aws-sdk/credential-provider-process", "npm:3.405.0"],\
["@aws-sdk/types", "npm:3.398.0"],\
["@smithy/property-provider", "npm:2.0.5"],\
["@smithy/shared-ini-file-loader", "npm:2.0.6"],\
["@smithy/types", "npm:2.2.2"],\
["tslib", "npm:2.5.2"]\
],\
"linkType": "HARD"\
}]\
]],\
["@aws-sdk/credential-provider-sso", [\
@@ -871,6 +1047,20 @@ const RAW_RUNTIME_STATE =
["tslib", "npm:2.5.2"]\
],\
"linkType": "HARD"\
}],\
["npm:3.405.0", {\
"packageLocation": "./.yarn/cache/@aws-sdk-credential-provider-sso-npm-3.405.0-24b76ee82f-754f796b2a.zip/node_modules/@aws-sdk/credential-provider-sso/",\
"packageDependencies": [\
["@aws-sdk/credential-provider-sso", "npm:3.405.0"],\
["@aws-sdk/client-sso", "npm:3.405.0"],\
["@aws-sdk/token-providers", "npm:3.405.0"],\
["@aws-sdk/types", "npm:3.398.0"],\
["@smithy/property-provider", "npm:2.0.5"],\
["@smithy/shared-ini-file-loader", "npm:2.0.6"],\
["@smithy/types", "npm:2.2.2"],\
["tslib", "npm:2.5.2"]\
],\
"linkType": "HARD"\
}]\
]],\
["@aws-sdk/credential-provider-web-identity", [\
@@ -1538,6 +1728,48 @@ const RAW_RUNTIME_STATE =
["tslib", "npm:2.5.2"]\
],\
"linkType": "HARD"\
}],\
["npm:3.405.0", {\
"packageLocation": "./.yarn/cache/@aws-sdk-token-providers-npm-3.405.0-29e68d4065-08e30dbc7b.zip/node_modules/@aws-sdk/token-providers/",\
"packageDependencies": [\
["@aws-sdk/token-providers", "npm:3.405.0"],\
["@aws-crypto/sha256-browser", "npm:3.0.0"],\
["@aws-crypto/sha256-js", "npm:3.0.0"],\
["@aws-sdk/middleware-host-header", "npm:3.398.0"],\
["@aws-sdk/middleware-logger", "npm:3.398.0"],\
["@aws-sdk/middleware-recursion-detection", "npm:3.398.0"],\
["@aws-sdk/middleware-user-agent", "npm:3.398.0"],\
["@aws-sdk/types", "npm:3.398.0"],\
["@aws-sdk/util-endpoints", "npm:3.398.0"],\
["@aws-sdk/util-user-agent-browser", "npm:3.398.0"],\
["@aws-sdk/util-user-agent-node", "virtual:0b227682399940b7e75175c6ae399b938ec07d3986be60e73f8026d2677e9fe96d05ac258f3f223cf1b6bdd7fd2a24f55f0efa40a8406ba949dc1b7af3a6a968#npm:3.405.0"],\
["@smithy/config-resolver", "npm:2.0.5"],\
["@smithy/fetch-http-handler", "npm:2.0.5"],\
["@smithy/hash-node", "npm:2.0.5"],\
["@smithy/invalid-dependency", "npm:2.0.5"],\
["@smithy/middleware-content-length", "npm:2.0.5"],\
["@smithy/middleware-endpoint", "npm:2.0.5"],\
["@smithy/middleware-retry", "npm:2.0.5"],\
["@smithy/middleware-serde", "npm:2.0.5"],\
["@smithy/middleware-stack", "npm:2.0.0"],\
["@smithy/node-config-provider", "npm:2.0.7"],\
["@smithy/node-http-handler", "npm:2.0.5"],\
["@smithy/property-provider", "npm:2.0.5"],\
["@smithy/protocol-http", "npm:2.0.5"],\
["@smithy/shared-ini-file-loader", "npm:2.0.6"],\
["@smithy/smithy-client", "npm:2.0.5"],\
["@smithy/types", "npm:2.2.2"],\
["@smithy/url-parser", "npm:2.0.5"],\
["@smithy/util-base64", "npm:2.0.0"],\
["@smithy/util-body-length-browser", "npm:2.0.0"],\
["@smithy/util-body-length-node", "npm:2.1.0"],\
["@smithy/util-defaults-mode-browser", "npm:2.0.6"],\
["@smithy/util-defaults-mode-node", "npm:2.0.7"],\
["@smithy/util-retry", "npm:2.0.0"],\
["@smithy/util-utf8", "npm:2.0.0"],\
["tslib", "npm:2.5.2"]\
],\
"linkType": "HARD"\
}]\
]],\
["@aws-sdk/types", [\
@@ -1798,6 +2030,30 @@ const RAW_RUNTIME_STATE =
],\
"linkType": "SOFT"\
}],\
["npm:3.405.0", {\
"packageLocation": "./.yarn/cache/@aws-sdk-util-user-agent-node-npm-3.405.0-160b854f92-6422874d9e.zip/node_modules/@aws-sdk/util-user-agent-node/",\
"packageDependencies": [\
["@aws-sdk/util-user-agent-node", "npm:3.405.0"]\
],\
"linkType": "SOFT"\
}],\
["virtual:0b227682399940b7e75175c6ae399b938ec07d3986be60e73f8026d2677e9fe96d05ac258f3f223cf1b6bdd7fd2a24f55f0efa40a8406ba949dc1b7af3a6a968#npm:3.405.0", {\
"packageLocation": "./.yarn/__virtual__/@aws-sdk-util-user-agent-node-virtual-f09536a53a/0/cache/@aws-sdk-util-user-agent-node-npm-3.405.0-160b854f92-6422874d9e.zip/node_modules/@aws-sdk/util-user-agent-node/",\
"packageDependencies": [\
["@aws-sdk/util-user-agent-node", "virtual:0b227682399940b7e75175c6ae399b938ec07d3986be60e73f8026d2677e9fe96d05ac258f3f223cf1b6bdd7fd2a24f55f0efa40a8406ba949dc1b7af3a6a968#npm:3.405.0"],\
["@aws-sdk/types", "npm:3.398.0"],\
["@smithy/node-config-provider", "npm:2.0.7"],\
["@smithy/types", "npm:2.2.2"],\
["@types/aws-crt", null],\
["aws-crt", null],\
["tslib", "npm:2.5.2"]\
],\
"packagePeers": [\
"@types/aws-crt",\
"aws-crt"\
],\
"linkType": "HARD"\
}],\
["virtual:bc215baaece010534480a5bbeef12e01b493e2e880132aa6bad8e946a74eb78bacf426e63b2a17a031e79d1b4089081937de686f0c09183e86cf908c3bd861cc#npm:3.398.0", {\
"packageLocation": "./.yarn/__virtual__/@aws-sdk-util-user-agent-node-virtual-cb6c764817/0/cache/@aws-sdk-util-user-agent-node-npm-3.398.0-e1c632b733-6d5dae585a.zip/node_modules/@aws-sdk/util-user-agent-node/",\
"packageDependencies": [\
@@ -4927,6 +5183,18 @@ const RAW_RUNTIME_STATE =
["tslib", "npm:2.5.2"]\
],\
"linkType": "HARD"\
}],\
["npm:2.0.7", {\
"packageLocation": "./.yarn/cache/@smithy-credential-provider-imds-npm-2.0.7-46bd1e8858-61c59aea7e.zip/node_modules/@smithy/credential-provider-imds/",\
"packageDependencies": [\
["@smithy/credential-provider-imds", "npm:2.0.7"],\
["@smithy/node-config-provider", "npm:2.0.7"],\
["@smithy/property-provider", "npm:2.0.6"],\
["@smithy/types", "npm:2.2.2"],\
["@smithy/url-parser", "npm:2.0.5"],\
["tslib", "npm:2.5.2"]\
],\
"linkType": "HARD"\
}]\
]],\
["@smithy/eventstream-codec", [\
@@ -5111,6 +5379,17 @@ const RAW_RUNTIME_STATE =
["tslib", "npm:2.5.2"]\
],\
"linkType": "HARD"\
}],\
["npm:2.0.7", {\
"packageLocation": "./.yarn/cache/@smithy-node-config-provider-npm-2.0.7-806b68f393-d4b58ee69f.zip/node_modules/@smithy/node-config-provider/",\
"packageDependencies": [\
["@smithy/node-config-provider", "npm:2.0.7"],\
["@smithy/property-provider", "npm:2.0.6"],\
["@smithy/shared-ini-file-loader", "npm:2.0.6"],\
["@smithy/types", "npm:2.2.2"],\
["tslib", "npm:2.5.2"]\
],\
"linkType": "HARD"\
}]\
]],\
["@smithy/node-http-handler", [\
@@ -5136,6 +5415,15 @@ const RAW_RUNTIME_STATE =
["tslib", "npm:2.5.2"]\
],\
"linkType": "HARD"\
}],\
["npm:2.0.6", {\
"packageLocation": "./.yarn/cache/@smithy-property-provider-npm-2.0.6-4f294049d1-b9a4aff1f0.zip/node_modules/@smithy/property-provider/",\
"packageDependencies": [\
["@smithy/property-provider", "npm:2.0.6"],\
["@smithy/types", "npm:2.2.2"],\
["tslib", "npm:2.5.2"]\
],\
"linkType": "HARD"\
}]\
]],\
["@smithy/protocol-http", [\
@@ -5199,6 +5487,15 @@ const RAW_RUNTIME_STATE =
["tslib", "npm:2.5.2"]\
],\
"linkType": "HARD"\
}],\
["npm:2.0.6", {\
"packageLocation": "./.yarn/cache/@smithy-shared-ini-file-loader-npm-2.0.6-ebbee54019-4b538ef59a.zip/node_modules/@smithy/shared-ini-file-loader/",\
"packageDependencies": [\
["@smithy/shared-ini-file-loader", "npm:2.0.6"],\
["@smithy/types", "npm:2.2.2"],\
["tslib", "npm:2.5.2"]\
],\
"linkType": "HARD"\
}]\
]],\
["@smithy/signature-v4", [\
@@ -5324,6 +5621,17 @@ const RAW_RUNTIME_STATE =
["tslib", "npm:2.5.2"]\
],\
"linkType": "HARD"\
}],\
["npm:2.0.6", {\
"packageLocation": "./.yarn/cache/@smithy-util-defaults-mode-browser-npm-2.0.6-d40f165a01-286295e6e9.zip/node_modules/@smithy/util-defaults-mode-browser/",\
"packageDependencies": [\
["@smithy/util-defaults-mode-browser", "npm:2.0.6"],\
["@smithy/property-provider", "npm:2.0.6"],\
["@smithy/types", "npm:2.2.2"],\
["bowser", "npm:2.11.0"],\
["tslib", "npm:2.5.2"]\
],\
"linkType": "HARD"\
}]\
]],\
["@smithy/util-defaults-mode-node", [\
@@ -5339,6 +5647,19 @@ const RAW_RUNTIME_STATE =
["tslib", "npm:2.5.2"]\
],\
"linkType": "HARD"\
}],\
["npm:2.0.7", {\
"packageLocation": "./.yarn/cache/@smithy-util-defaults-mode-node-npm-2.0.7-8a9d03e11c-b1c74a3b41.zip/node_modules/@smithy/util-defaults-mode-node/",\
"packageDependencies": [\
["@smithy/util-defaults-mode-node", "npm:2.0.7"],\
["@smithy/config-resolver", "npm:2.0.5"],\
["@smithy/credential-provider-imds", "npm:2.0.7"],\
["@smithy/node-config-provider", "npm:2.0.7"],\
["@smithy/property-provider", "npm:2.0.6"],\
["@smithy/types", "npm:2.2.2"],\
["tslib", "npm:2.5.2"]\
],\
"linkType": "HARD"\
}]\
]],\
["@smithy/util-hex-encoding", [\
@@ -6161,6 +6482,7 @@ const RAW_RUNTIME_STATE =
"packageLocation": "./packages/websockets/",\
"packageDependencies": [\
["@standardnotes/websockets-server", "workspace:packages/websockets"],\
["@aws-sdk/client-apigatewaymanagementapi", "npm:3.405.0"],\
["@aws-sdk/client-sqs", "npm:3.342.0"],\
["@newrelic/winston-enricher", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:4.0.1"],\
["@standardnotes/api", "npm:1.26.26"],\

View File

@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [2.26.4](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.26.3...@standardnotes/analytics@2.26.4) (2023-09-06)
**Note:** Version bump only for package @standardnotes/analytics
## [2.26.3](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.26.2...@standardnotes/analytics@2.26.3) (2023-09-04)
**Note:** Version bump only for package @standardnotes/analytics

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/analytics",
"version": "2.26.3",
"version": "2.26.4",
"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.74.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.73.7...@standardnotes/api-gateway@1.74.0) (2023-09-06)
### Features
* should be able to access shared item revisions as third party user ([#807](https://github.com/standardnotes/api-gateway/issues/807)) ([794cd87](https://github.com/standardnotes/api-gateway/commit/794cd8734acf89fb29f09dfb169a3f08f252bb6a))
## [1.73.7](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.73.6...@standardnotes/api-gateway@1.73.7) (2023-09-04)
**Note:** Version bump only for package @standardnotes/api-gateway

View File

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

View File

@@ -72,6 +72,7 @@ export abstract class AuthMiddleware extends BaseMiddleware {
response.locals.session = decodedToken.session
response.locals.roles = decodedToken.roles
response.locals.sharedVaultOwnerContext = decodedToken.shared_vault_owner_context
response.locals.belongsToSharedVaults = decodedToken.belongs_to_shared_vaults ?? []
} catch (error) {
const errorMessage = (error as AxiosError).isAxiosError
? JSON.stringify((error as AxiosError).response?.data)

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.138.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.137.6...@standardnotes/auth-server@1.138.0) (2023-09-06)
### Features
* should be able to access shared item revisions as third party user ([#807](https://github.com/standardnotes/server/issues/807)) ([794cd87](https://github.com/standardnotes/server/commit/794cd8734acf89fb29f09dfb169a3f08f252bb6a))
## [1.137.6](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.137.5...@standardnotes/auth-server@1.137.6) (2023-09-04)
**Note:** Version bump only for package @standardnotes/auth-server

View File

@@ -0,0 +1,15 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class AddSharedVaultUsers1694000575425 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
'CREATE TABLE `auth_shared_vault_users` (`uuid` varchar(36) NOT NULL, `shared_vault_uuid` varchar(36) NOT NULL, `user_uuid` varchar(36) NOT NULL, `permission` varchar(24) NOT NULL, `created_at_timestamp` bigint NOT NULL, `updated_at_timestamp` bigint NOT NULL, INDEX `shared_vault_uuid_on_auth_shared_vault_users` (`shared_vault_uuid`), INDEX `user_uuid_on_auth_shared_vault_users` (`user_uuid`), PRIMARY KEY (`uuid`)) ENGINE=InnoDB',
)
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('DROP INDEX `user_uuid_on_auth_shared_vault_users` ON `auth_shared_vault_users`')
await queryRunner.query('DROP INDEX `shared_vault_uuid_on_auth_shared_vault_users` ON `auth_shared_vault_users`')
await queryRunner.query('DROP TABLE `auth_shared_vault_users`')
}
}

View File

@@ -0,0 +1,21 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class AddSharedVaultUsers1694000640645 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
'CREATE TABLE "auth_shared_vault_users" ("uuid" varchar PRIMARY KEY NOT NULL, "shared_vault_uuid" varchar(36) NOT NULL, "user_uuid" varchar(36) NOT NULL, "permission" varchar(24) NOT NULL, "created_at_timestamp" bigint NOT NULL, "updated_at_timestamp" bigint NOT NULL)',
)
await queryRunner.query(
'CREATE INDEX "shared_vault_uuid_on_auth_shared_vault_users" ON "auth_shared_vault_users" ("shared_vault_uuid") ',
)
await queryRunner.query(
'CREATE INDEX "user_uuid_on_auth_shared_vault_users" ON "auth_shared_vault_users" ("user_uuid") ',
)
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('DROP INDEX "user_uuid_on_auth_shared_vault_users"')
await queryRunner.query('DROP INDEX "shared_vault_uuid_on_auth_shared_vault_users"')
await queryRunner.query('DROP TABLE "auth_shared_vault_users"')
}
}

View File

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

View File

@@ -189,6 +189,7 @@ import {
ControllerContainer,
ControllerContainerInterface,
MapperInterface,
SharedVaultUser,
} from '@standardnotes/domain-core'
import { SessionTracePersistenceMapper } from '../Mapping/SessionTracePersistenceMapper'
import { SessionTrace } from '../Domain/Session/SessionTrace'
@@ -263,6 +264,14 @@ import { InMemoryTransitionStatusRepository } from '../Infra/InMemory/InMemoryTr
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'
import { TypeORMSharedVaultUserRepository } from '../Infra/TypeORM/TypeORMSharedVaultUserRepository'
import { AddSharedVaultUser } from '../Domain/UseCase/AddSharedVaultUser/AddSharedVaultUser'
import { RemoveSharedVaultUser } from '../Domain/UseCase/RemoveSharedVaultUser/RemoveSharedVaultUser'
import { UserAddedToSharedVaultEventHandler } from '../Domain/Handler/UserAddedToSharedVaultEventHandler'
import { UserRemovedFromSharedVaultEventHandler } from '../Domain/Handler/UserRemovedFromSharedVaultEventHandler'
export class ContainerConfigLoader {
async load(configuration?: {
@@ -372,6 +381,9 @@ export class ContainerConfigLoader {
container
.bind<MapperInterface<CacheEntry, TypeORMCacheEntry>>(TYPES.Auth_CacheEntryPersistenceMapper)
.toConstantValue(new CacheEntryPersistenceMapper())
container
.bind<MapperInterface<SharedVaultUser, TypeORMSharedVaultUser>>(TYPES.Auth_SharedVaultUserPersistenceMapper)
.toConstantValue(new SharedVaultUserPersistenceMapper())
// ORM
container
@@ -412,6 +424,9 @@ export class ContainerConfigLoader {
container
.bind<Repository<TypeORMCacheEntry>>(TYPES.Auth_ORMCacheEntryRepository)
.toConstantValue(appDataSource.getRepository(TypeORMCacheEntry))
container
.bind<Repository<TypeORMSharedVaultUser>>(TYPES.Auth_ORMSharedVaultUserRepository)
.toConstantValue(appDataSource.getRepository(TypeORMSharedVaultUser))
// Repositories
container.bind<SessionRepositoryInterface>(TYPES.Auth_SessionRepository).to(TypeORMSessionRepository)
@@ -468,6 +483,16 @@ export class ContainerConfigLoader {
container.get(TYPES.Auth_CacheEntryPersistenceMapper),
),
)
container
.bind<SharedVaultUserRepositoryInterface>(TYPES.Auth_SharedVaultUserRepository)
.toConstantValue(
new TypeORMSharedVaultUserRepository(
container.get<Repository<TypeORMSharedVaultUser>>(TYPES.Auth_ORMSharedVaultUserRepository),
container.get<MapperInterface<SharedVaultUser, TypeORMSharedVaultUser>>(
TYPES.Auth_SharedVaultUserPersistenceMapper,
),
),
)
// Middleware
container.bind<SessionMiddleware>(TYPES.Auth_SessionMiddleware).to(SessionMiddleware)
@@ -926,6 +951,18 @@ export class ContainerConfigLoader {
container.get<UserRepositoryInterface>(TYPES.Auth_UserRepository),
),
)
container
.bind<AddSharedVaultUser>(TYPES.Auth_AddSharedVaultUser)
.toConstantValue(
new AddSharedVaultUser(container.get<SharedVaultUserRepositoryInterface>(TYPES.Auth_SharedVaultUserRepository)),
)
container
.bind<RemoveSharedVaultUser>(TYPES.Auth_RemoveSharedVaultUser)
.toConstantValue(
new RemoveSharedVaultUser(
container.get<SharedVaultUserRepositoryInterface>(TYPES.Auth_SharedVaultUserRepository),
),
)
// Controller
container
@@ -1075,6 +1112,22 @@ export class ContainerConfigLoader {
container.get<winston.Logger>(TYPES.Auth_Logger),
),
)
container
.bind<UserAddedToSharedVaultEventHandler>(TYPES.Auth_UserAddedToSharedVaultEventHandler)
.toConstantValue(
new UserAddedToSharedVaultEventHandler(
container.get<AddSharedVaultUser>(TYPES.Auth_AddSharedVaultUser),
container.get<winston.Logger>(TYPES.Auth_Logger),
),
)
container
.bind<UserRemovedFromSharedVaultEventHandler>(TYPES.Auth_UserRemovedFromSharedVaultEventHandler)
.toConstantValue(
new UserRemovedFromSharedVaultEventHandler(
container.get<RemoveSharedVaultUser>(TYPES.Auth_RemoveSharedVaultUser),
container.get<winston.Logger>(TYPES.Auth_Logger),
),
)
const eventHandlers: Map<string, DomainEventHandlerInterface> = new Map([
['USER_REGISTERED', container.get(TYPES.Auth_UserRegisteredEventHandler)],
@@ -1107,6 +1160,8 @@ export class ContainerConfigLoader {
['EMAIL_SUBSCRIPTION_UNSUBSCRIBED', container.get(TYPES.Auth_EmailSubscriptionUnsubscribedEventHandler)],
['PAYMENTS_ACCOUNT_DELETED', container.get(TYPES.Auth_PaymentsAccountDeletedEventHandler)],
['TRANSITION_STATUS_UPDATED', container.get(TYPES.Auth_TransitionStatusUpdatedEventHandler)],
['USER_ADDED_TO_SHARED_VAULT', container.get(TYPES.Auth_UserAddedToSharedVaultEventHandler)],
['USER_REMOVED_FROM_SHARED_VAULT', container.get(TYPES.Auth_UserRemovedFromSharedVaultEventHandler)],
])
if (isConfiguredForHomeServer) {

View File

@@ -18,6 +18,7 @@ import { TypeORMEmergencyAccessInvitation } from '../Infra/TypeORM/TypeORMEmerge
import { TypeORMSessionTrace } from '../Infra/TypeORM/TypeORMSessionTrace'
import { Env } from './Env'
import { SqliteConnectionOptions } from 'typeorm/driver/sqlite/SqliteConnectionOptions'
import { TypeORMSharedVaultUser } from '../Infra/TypeORM/TypeORMSharedVaultUser'
export class AppDataSource {
private _dataSource: DataSource | undefined
@@ -64,6 +65,7 @@ export class AppDataSource {
TypeORMAuthenticatorChallenge,
TypeORMEmergencyAccessInvitation,
TypeORMCacheEntry,
TypeORMSharedVaultUser,
],
migrations: [`${__dirname}/../../migrations/${isConfiguredForMySQL ? 'mysql' : 'sqlite'}/*.js`],
migrationsRun: true,

View File

@@ -9,6 +9,7 @@ const TYPES = {
Auth_AuthenticatorPersistenceMapper: Symbol.for('Auth_AuthenticatorPersistenceMapper'),
Auth_AuthenticatorHttpMapper: Symbol.for('Auth_AuthenticatorHttpMapper'),
Auth_CacheEntryPersistenceMapper: Symbol.for('Auth_CacheEntryPersistenceMapper'),
Auth_SharedVaultUserPersistenceMapper: Symbol.for('Auth_SharedVaultUserPersistenceMapper'),
// Controller
Auth_ControllerContainer: Symbol.for('Auth_ControllerContainer'),
Auth_AuthController: Symbol.for('Auth_AuthController'),
@@ -36,6 +37,7 @@ const TYPES = {
Auth_AuthenticatorChallengeRepository: Symbol.for('Auth_AuthenticatorChallengeRepository'),
Auth_CacheEntryRepository: Symbol.for('Auth_CacheEntryRepository'),
Auth_TransitionStatusRepository: Symbol.for('Auth_TransitionStatusRepository'),
Auth_SharedVaultUserRepository: Symbol.for('Auth_SharedVaultUserRepository'),
// ORM
Auth_ORMOfflineSettingRepository: Symbol.for('Auth_ORMOfflineSettingRepository'),
Auth_ORMOfflineUserSubscriptionRepository: Symbol.for('Auth_ORMOfflineUserSubscriptionRepository'),
@@ -51,6 +53,7 @@ const TYPES = {
Auth_ORMAuthenticatorRepository: Symbol.for('Auth_ORMAuthenticatorRepository'),
Auth_ORMAuthenticatorChallengeRepository: Symbol.for('Auth_ORMAuthenticatorChallengeRepository'),
Auth_ORMCacheEntryRepository: Symbol.for('Auth_ORMCacheEntryRepository'),
Auth_ORMSharedVaultUserRepository: Symbol.for('Auth_ORMSharedVaultUserRepository'),
// Middleware
Auth_RequiredCrossServiceTokenMiddleware: Symbol.for('Auth_RequiredCrossServiceTokenMiddleware'),
Auth_OptionalCrossServiceTokenMiddleware: Symbol.for('Auth_OptionalCrossServiceTokenMiddleware'),
@@ -157,6 +160,8 @@ const TYPES = {
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
Auth_UserRegisteredEventHandler: Symbol.for('Auth_UserRegisteredEventHandler'),
Auth_AccountDeletionRequestedEventHandler: Symbol.for('Auth_AccountDeletionRequestedEventHandler'),
@@ -186,6 +191,8 @@ const TYPES = {
Auth_EmailSubscriptionUnsubscribedEventHandler: Symbol.for('Auth_EmailSubscriptionUnsubscribedEventHandler'),
Auth_PaymentsAccountDeletedEventHandler: Symbol.for('Auth_PaymentsAccountDeletedEventHandler'),
Auth_TransitionStatusUpdatedEventHandler: Symbol.for('Auth_TransitionStatusUpdatedEventHandler'),
Auth_UserAddedToSharedVaultEventHandler: Symbol.for('Auth_UserAddedToSharedVaultEventHandler'),
Auth_UserRemovedFromSharedVaultEventHandler: Symbol.for('Auth_UserRemovedFromSharedVaultEventHandler'),
// Services
Auth_DeviceDetector: Symbol.for('Auth_DeviceDetector'),
Auth_SessionService: Symbol.for('Auth_SessionService'),

View File

@@ -0,0 +1,24 @@
import { DomainEventHandlerInterface, UserAddedToSharedVaultEvent } from '@standardnotes/domain-events'
import { Logger } from 'winston'
import { AddSharedVaultUser } from '../UseCase/AddSharedVaultUser/AddSharedVaultUser'
export class UserAddedToSharedVaultEventHandler implements DomainEventHandlerInterface {
constructor(
private addSharedVaultUser: AddSharedVaultUser,
private logger: Logger,
) {}
async handle(event: UserAddedToSharedVaultEvent): Promise<void> {
const result = await this.addSharedVaultUser.execute({
userUuid: event.payload.userUuid,
sharedVaultUuid: event.payload.sharedVaultUuid,
permission: event.payload.permission,
createdAt: event.payload.createdAt,
updatedAt: event.payload.updatedAt,
})
if (result.isFailed()) {
this.logger.error(`Failed to add user to shared vault: ${result.getError()}`)
}
}
}

View File

@@ -0,0 +1,22 @@
import { DomainEventHandlerInterface, UserRemovedFromSharedVaultEvent } from '@standardnotes/domain-events'
import { Logger } from 'winston'
import { RemoveSharedVaultUser } from '../UseCase/RemoveSharedVaultUser/RemoveSharedVaultUser'
export class UserRemovedFromSharedVaultEventHandler implements DomainEventHandlerInterface {
constructor(
private removeSharedVaultUser: RemoveSharedVaultUser,
private logger: Logger,
) {}
async handle(event: UserRemovedFromSharedVaultEvent): Promise<void> {
const result = await this.removeSharedVaultUser.execute({
userUuid: event.payload.userUuid,
sharedVaultUuid: event.payload.sharedVaultUuid,
})
if (result.isFailed()) {
this.logger.error(`Failed to remove user from shared vault: ${result.getError()}`)
}
}
}

View File

@@ -0,0 +1,8 @@
import { SharedVaultUser, Uuid } from '@standardnotes/domain-core'
export interface SharedVaultUserRepositoryInterface {
findByUserUuidAndSharedVaultUuid(dto: { userUuid: Uuid; sharedVaultUuid: Uuid }): Promise<SharedVaultUser | null>
findByUserUuid(userUuid: Uuid): Promise<SharedVaultUser[]>
save(sharedVaultUser: SharedVaultUser): Promise<void>
remove(sharedVault: SharedVaultUser): Promise<void>
}

View File

@@ -0,0 +1,106 @@
import { Result, SharedVaultUser } from '@standardnotes/domain-core'
import { SharedVaultUserRepositoryInterface } from '../../SharedVault/SharedVaultUserRepositoryInterface'
import { AddSharedVaultUser } from './AddSharedVaultUser'
describe('AddSharedVaultUser', () => {
let sharedVaultUserRepository: SharedVaultUserRepositoryInterface
const createUseCase = () => new AddSharedVaultUser(sharedVaultUserRepository)
beforeEach(() => {
sharedVaultUserRepository = {} as jest.Mocked<SharedVaultUserRepositoryInterface>
sharedVaultUserRepository.save = jest.fn()
})
it('should save shared vault user', async () => {
const useCase = createUseCase()
const result = await useCase.execute({
userUuid: '00000000-0000-0000-0000-000000000000',
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
permission: 'read',
createdAt: 1,
updatedAt: 2,
})
expect(result.isFailed()).toBeFalsy()
expect(sharedVaultUserRepository.save).toHaveBeenCalled()
})
it('should fail when user uuid is invalid', async () => {
const useCase = createUseCase()
const result = await useCase.execute({
userUuid: 'invalid',
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
permission: 'read',
createdAt: 1,
updatedAt: 2,
})
expect(result.isFailed()).toBeTruthy()
expect(sharedVaultUserRepository.save).not.toHaveBeenCalled()
})
it('should fail when shared vault uuid is invalid', async () => {
const useCase = createUseCase()
const result = await useCase.execute({
userUuid: '00000000-0000-0000-0000-000000000000',
sharedVaultUuid: 'invalid',
permission: 'read',
createdAt: 1,
updatedAt: 2,
})
expect(result.isFailed()).toBeTruthy()
expect(sharedVaultUserRepository.save).not.toHaveBeenCalled()
})
it('should fail when permission is invalid', async () => {
const useCase = createUseCase()
const result = await useCase.execute({
userUuid: '00000000-0000-0000-0000-000000000000',
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
permission: 'invalid',
createdAt: 1,
updatedAt: 2,
})
expect(result.isFailed()).toBeTruthy()
expect(sharedVaultUserRepository.save).not.toHaveBeenCalled()
})
it('should fail when timestamps are invalid', async () => {
const useCase = createUseCase()
const result = await useCase.execute({
userUuid: '00000000-0000-0000-0000-000000000000',
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
permission: 'read',
createdAt: 'invalid' as unknown as number,
updatedAt: 1,
})
expect(result.isFailed()).toBeTruthy()
expect(sharedVaultUserRepository.save).not.toHaveBeenCalled()
})
it('should fail when shared vault user is invalid', async () => {
const mock = jest.spyOn(SharedVaultUser, 'create')
mock.mockReturnValue(Result.fail('Oops'))
const result = await createUseCase().execute({
userUuid: '00000000-0000-0000-0000-000000000000',
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
permission: 'read',
createdAt: 2,
updatedAt: 1,
})
expect(result.isFailed()).toBeTruthy()
mock.mockRestore()
})
})

View File

@@ -0,0 +1,56 @@
import {
Result,
SharedVaultUser,
SharedVaultUserPermission,
Timestamps,
UseCaseInterface,
Uuid,
} from '@standardnotes/domain-core'
import { SharedVaultUserRepositoryInterface } from '../../SharedVault/SharedVaultUserRepositoryInterface'
import { AddSharedVaultUserDTO } from './AddSharedVaultUserDTO'
export class AddSharedVaultUser implements UseCaseInterface<void> {
constructor(private sharedVaultUserRepository: SharedVaultUserRepositoryInterface) {}
async execute(dto: AddSharedVaultUserDTO): Promise<Result<void>> {
const userUuidOrError = Uuid.create(dto.userUuid)
if (userUuidOrError.isFailed()) {
return Result.fail(userUuidOrError.getError())
}
const userUuid = userUuidOrError.getValue()
const sharedVaultUuidOrError = Uuid.create(dto.sharedVaultUuid)
if (sharedVaultUuidOrError.isFailed()) {
return Result.fail(sharedVaultUuidOrError.getError())
}
const sharedVaultUuid = sharedVaultUuidOrError.getValue()
const permissionOrError = SharedVaultUserPermission.create(dto.permission)
if (permissionOrError.isFailed()) {
return Result.fail(permissionOrError.getError())
}
const permission = permissionOrError.getValue()
const timestampsOrError = Timestamps.create(dto.createdAt, dto.updatedAt)
if (timestampsOrError.isFailed()) {
return Result.fail(timestampsOrError.getError())
}
const timestamps = timestampsOrError.getValue()
const sharedVaultUserOrError = SharedVaultUser.create({
userUuid,
sharedVaultUuid,
permission,
timestamps,
})
if (sharedVaultUserOrError.isFailed()) {
return Result.fail(sharedVaultUserOrError.getError())
}
const sharedVaultUser = sharedVaultUserOrError.getValue()
await this.sharedVaultUserRepository.save(sharedVaultUser)
return Result.ok()
}
}

View File

@@ -0,0 +1,7 @@
export interface AddSharedVaultUserDTO {
sharedVaultUuid: string
userUuid: string
permission: string
createdAt: number
updatedAt: number
}

View File

@@ -9,8 +9,9 @@ import { UserRepositoryInterface } from '../../User/UserRepositoryInterface'
import { CreateCrossServiceToken } from './CreateCrossServiceToken'
import { GetSetting } from '../GetSetting/GetSetting'
import { Result } from '@standardnotes/domain-core'
import { Result, SharedVaultUser, SharedVaultUserPermission, Timestamps, Uuid } from '@standardnotes/domain-core'
import { TransitionStatusRepositoryInterface } from '../../Transition/TransitionStatusRepositoryInterface'
import { SharedVaultUserRepositoryInterface } from '../../SharedVault/SharedVaultUserRepositoryInterface'
describe('CreateCrossServiceToken', () => {
let userProjector: ProjectorInterface<User>
@@ -20,6 +21,7 @@ describe('CreateCrossServiceToken', () => {
let userRepository: UserRepositoryInterface
let getSettingUseCase: GetSetting
let transitionStatusRepository: TransitionStatusRepositoryInterface
let sharedVaultUserRepository: SharedVaultUserRepositoryInterface
const jwtTTL = 60
let session: Session
@@ -36,6 +38,7 @@ describe('CreateCrossServiceToken', () => {
jwtTTL,
getSettingUseCase,
transitionStatusRepository,
sharedVaultUserRepository,
)
beforeEach(() => {
@@ -70,6 +73,16 @@ describe('CreateCrossServiceToken', () => {
transitionStatusRepository = {} as jest.Mocked<TransitionStatusRepositoryInterface>
transitionStatusRepository.getStatus = jest.fn().mockReturnValue('TO-DO')
sharedVaultUserRepository = {} as jest.Mocked<SharedVaultUserRepositoryInterface>
sharedVaultUserRepository.findByUserUuid = jest.fn().mockReturnValue([
SharedVaultUser.create({
permission: SharedVaultUserPermission.create('read').getValue(),
sharedVaultUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
timestamps: Timestamps.create(123456789, 123456789).getValue(),
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
}).getValue(),
])
})
it('should create a cross service token for user', async () => {
@@ -86,6 +99,12 @@ describe('CreateCrossServiceToken', () => {
uuid: '1-3-4',
},
],
belongs_to_shared_vaults: [
{
shared_vault_uuid: '00000000-0000-0000-0000-000000000000',
permission: 'read',
},
],
session: {
test: 'test',
},
@@ -115,6 +134,12 @@ describe('CreateCrossServiceToken', () => {
uuid: '1-3-4',
},
],
belongs_to_shared_vaults: [
{
shared_vault_uuid: '00000000-0000-0000-0000-000000000000',
permission: 'read',
},
],
session: {
test: 'test',
},
@@ -141,6 +166,12 @@ describe('CreateCrossServiceToken', () => {
uuid: '1-3-4',
},
],
belongs_to_shared_vaults: [
{
shared_vault_uuid: '00000000-0000-0000-0000-000000000000',
permission: 'read',
},
],
user: {
email: 'test@test.te',
uuid: '00000000-0000-0000-0000-000000000000',
@@ -164,6 +195,12 @@ describe('CreateCrossServiceToken', () => {
uuid: '1-3-4',
},
],
belongs_to_shared_vaults: [
{
shared_vault_uuid: '00000000-0000-0000-0000-000000000000',
permission: 'read',
},
],
user: {
email: 'test@test.te',
uuid: '00000000-0000-0000-0000-000000000000',
@@ -208,6 +245,12 @@ describe('CreateCrossServiceToken', () => {
uuid: '1-3-4',
},
],
belongs_to_shared_vaults: [
{
shared_vault_uuid: '00000000-0000-0000-0000-000000000000',
permission: 'read',
},
],
session: {
test: 'test',
},

View File

@@ -13,6 +13,7 @@ import { CreateCrossServiceTokenDTO } from './CreateCrossServiceTokenDTO'
import { GetSetting } from '../GetSetting/GetSetting'
import { SettingName } from '@standardnotes/settings'
import { TransitionStatusRepositoryInterface } from '../../Transition/TransitionStatusRepositoryInterface'
import { SharedVaultUserRepositoryInterface } from '../../SharedVault/SharedVaultUserRepositoryInterface'
@injectable()
export class CreateCrossServiceToken implements UseCaseInterface<string> {
@@ -27,6 +28,7 @@ export class CreateCrossServiceToken implements UseCaseInterface<string> {
private getSettingUseCase: GetSetting,
@inject(TYPES.Auth_TransitionStatusRepository)
private transitionStatusRepository: TransitionStatusRepositoryInterface,
@inject(TYPES.Auth_SharedVaultUserRepository) private sharedVaultUserRepository: SharedVaultUserRepositoryInterface,
) {}
async execute(dto: CreateCrossServiceTokenDTO): Promise<Result<string>> {
@@ -49,11 +51,19 @@ export class CreateCrossServiceToken implements UseCaseInterface<string> {
const roles = await user.roles
const sharedVaultAssociations = await this.sharedVaultUserRepository.findByUserUuid(
Uuid.create(user.uuid).getValue(),
)
const authTokenData: CrossServiceTokenData = {
user: this.projectUser(user),
roles: this.projectRoles(roles),
shared_vault_owner_context: undefined,
ongoing_transition: transitionStatus === 'STARTED',
belongs_to_shared_vaults: sharedVaultAssociations.map((association) => ({
shared_vault_uuid: association.props.sharedVaultUuid.value,
permission: association.props.permission.value,
})),
}
if (dto.sharedVaultOwnerContext !== undefined) {

View File

@@ -0,0 +1,68 @@
import { SharedVaultUser } from '@standardnotes/domain-core'
import { SharedVaultUserRepositoryInterface } from '../../SharedVault/SharedVaultUserRepositoryInterface'
import { RemoveSharedVaultUser } from './RemoveSharedVaultUser'
describe('RemoveSharedVaultUser', () => {
let sharedVaultUserRepository: SharedVaultUserRepositoryInterface
const createUseCase = () => new RemoveSharedVaultUser(sharedVaultUserRepository)
beforeEach(() => {
sharedVaultUserRepository = {} as jest.Mocked<SharedVaultUserRepositoryInterface>
sharedVaultUserRepository.findByUserUuidAndSharedVaultUuid = jest
.fn()
.mockReturnValue({} as jest.Mocked<SharedVaultUser>)
sharedVaultUserRepository.remove = jest.fn()
})
it('should remove shared vault user', async () => {
const useCase = createUseCase()
const result = await useCase.execute({
userUuid: '00000000-0000-0000-0000-000000000000',
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
})
expect(result.isFailed()).toBeFalsy()
expect(sharedVaultUserRepository.remove).toHaveBeenCalled()
})
it('should fail when user uuid is invalid', async () => {
const useCase = createUseCase()
const result = await useCase.execute({
userUuid: 'invalid',
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
})
expect(result.isFailed()).toBeTruthy()
expect(sharedVaultUserRepository.remove).not.toHaveBeenCalled()
})
it('should fail when shared vault uuid is invalid', async () => {
const useCase = createUseCase()
const result = await useCase.execute({
userUuid: '00000000-0000-0000-0000-000000000000',
sharedVaultUuid: 'invalid',
})
expect(result.isFailed()).toBeTruthy()
expect(sharedVaultUserRepository.remove).not.toHaveBeenCalled()
})
it('should fail when shared vault user is not found', async () => {
sharedVaultUserRepository.findByUserUuidAndSharedVaultUuid = jest.fn().mockReturnValue(null)
const useCase = createUseCase()
const result = await useCase.execute({
userUuid: '00000000-0000-0000-0000-000000000000',
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
})
expect(result.isFailed()).toBeTruthy()
expect(sharedVaultUserRepository.remove).not.toHaveBeenCalled()
})
})

View File

@@ -0,0 +1,34 @@
import { Result, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
import { SharedVaultUserRepositoryInterface } from '../../SharedVault/SharedVaultUserRepositoryInterface'
import { RemoveSharedVaultUserDTO } from './RemoveSharedVaultUserDTO'
export class RemoveSharedVaultUser implements UseCaseInterface<void> {
constructor(private sharedVaultUserRepository: SharedVaultUserRepositoryInterface) {}
async execute(dto: RemoveSharedVaultUserDTO): Promise<Result<void>> {
const userUuidOrError = Uuid.create(dto.userUuid)
if (userUuidOrError.isFailed()) {
return Result.fail(userUuidOrError.getError())
}
const userUuid = userUuidOrError.getValue()
const sharedVaultUuidOrError = Uuid.create(dto.sharedVaultUuid)
if (sharedVaultUuidOrError.isFailed()) {
return Result.fail(sharedVaultUuidOrError.getError())
}
const sharedVaultUuid = sharedVaultUuidOrError.getValue()
const sharedVaultUser = await this.sharedVaultUserRepository.findByUserUuidAndSharedVaultUuid({
userUuid,
sharedVaultUuid,
})
if (!sharedVaultUser) {
return Result.fail('Shared vault user not found')
}
await this.sharedVaultUserRepository.remove(sharedVaultUser)
return Result.ok()
}
}

View File

@@ -0,0 +1,4 @@
export interface RemoveSharedVaultUserDTO {
sharedVaultUuid: string
userUuid: string
}

View File

@@ -0,0 +1,40 @@
import { Column, Entity, Index, PrimaryGeneratedColumn } from 'typeorm'
@Entity({ name: 'auth_shared_vault_users' })
export class TypeORMSharedVaultUser {
@PrimaryGeneratedColumn('uuid')
declare uuid: string
@Column({
name: 'shared_vault_uuid',
length: 36,
})
@Index('shared_vault_uuid_on_auth_shared_vault_users')
declare sharedVaultUuid: string
@Column({
name: 'user_uuid',
length: 36,
})
@Index('user_uuid_on_auth_shared_vault_users')
declare userUuid: string
@Column({
name: 'permission',
type: 'varchar',
length: 24,
})
declare permission: string
@Column({
name: 'created_at_timestamp',
type: 'bigint',
})
declare createdAtTimestamp: number
@Column({
name: 'updated_at_timestamp',
type: 'bigint',
})
declare updatedAtTimestamp: number
}

View File

@@ -0,0 +1,54 @@
import { Repository } from 'typeorm'
import { MapperInterface, SharedVaultUser, Uuid } from '@standardnotes/domain-core'
import { SharedVaultUserRepositoryInterface } from '../../Domain/SharedVault/SharedVaultUserRepositoryInterface'
import { TypeORMSharedVaultUser } from './TypeORMSharedVaultUser'
export class TypeORMSharedVaultUserRepository implements SharedVaultUserRepositoryInterface {
constructor(
private ormRepository: Repository<TypeORMSharedVaultUser>,
private mapper: MapperInterface<SharedVaultUser, TypeORMSharedVaultUser>,
) {}
async findByUserUuid(userUuid: Uuid): Promise<SharedVaultUser[]> {
const persistence = await this.ormRepository
.createQueryBuilder('shared_vault_user')
.where('shared_vault_user.user_uuid = :userUuid', {
userUuid: userUuid.value,
})
.getMany()
return persistence.map((p) => this.mapper.toDomain(p))
}
async findByUserUuidAndSharedVaultUuid(dto: {
userUuid: Uuid
sharedVaultUuid: Uuid
}): Promise<SharedVaultUser | null> {
const persistence = await this.ormRepository
.createQueryBuilder('shared_vault_user')
.where('shared_vault_user.user_uuid = :userUuid', {
userUuid: dto.userUuid.value,
})
.andWhere('shared_vault_user.shared_vault_uuid = :sharedVaultUuid', {
sharedVaultUuid: dto.sharedVaultUuid.value,
})
.getOne()
if (persistence === null) {
return null
}
return this.mapper.toDomain(persistence)
}
async save(sharedVaultUser: SharedVaultUser): Promise<void> {
const persistence = this.mapper.toProjection(sharedVaultUser)
await this.ormRepository.save(persistence)
}
async remove(sharedVaultUser: SharedVaultUser): Promise<void> {
await this.ormRepository.remove(this.mapper.toProjection(sharedVaultUser))
}
}

View File

@@ -0,0 +1,67 @@
import {
Timestamps,
MapperInterface,
UniqueEntityId,
Uuid,
SharedVaultUserPermission,
SharedVaultUser,
} from '@standardnotes/domain-core'
import { TypeORMSharedVaultUser } from '../Infra/TypeORM/TypeORMSharedVaultUser'
export class SharedVaultUserPersistenceMapper implements MapperInterface<SharedVaultUser, TypeORMSharedVaultUser> {
toDomain(projection: TypeORMSharedVaultUser): SharedVaultUser {
const userUuidOrError = Uuid.create(projection.userUuid)
if (userUuidOrError.isFailed()) {
throw new Error(`Failed to create shared vault user from projection: ${userUuidOrError.getError()}`)
}
const userUuid = userUuidOrError.getValue()
const sharedVaultUuidOrError = Uuid.create(projection.sharedVaultUuid)
if (sharedVaultUuidOrError.isFailed()) {
throw new Error(`Failed to create shared vault user from projection: ${sharedVaultUuidOrError.getError()}`)
}
const sharedVaultUuid = sharedVaultUuidOrError.getValue()
const timestampsOrError = Timestamps.create(projection.createdAtTimestamp, projection.updatedAtTimestamp)
if (timestampsOrError.isFailed()) {
throw new Error(`Failed to create shared vault user from projection: ${timestampsOrError.getError()}`)
}
const timestamps = timestampsOrError.getValue()
const permissionOrError = SharedVaultUserPermission.create(projection.permission)
if (permissionOrError.isFailed()) {
throw new Error(`Failed to create shared vault user from projection: ${permissionOrError.getError()}`)
}
const permission = permissionOrError.getValue()
const sharedVaultUserOrError = SharedVaultUser.create(
{
userUuid,
sharedVaultUuid,
permission,
timestamps,
},
new UniqueEntityId(projection.uuid),
)
if (sharedVaultUserOrError.isFailed()) {
throw new Error(`Failed to create shared vault user from projection: ${sharedVaultUserOrError.getError()}`)
}
const sharedVaultUser = sharedVaultUserOrError.getValue()
return sharedVaultUser
}
toProjection(domain: SharedVaultUser): TypeORMSharedVaultUser {
const typeorm = new TypeORMSharedVaultUser()
typeorm.uuid = domain.id.toString()
typeorm.sharedVaultUuid = domain.props.sharedVaultUuid.value
typeorm.userUuid = domain.props.userUuid.value
typeorm.permission = domain.props.permission.value
typeorm.createdAtTimestamp = domain.props.timestamps.createdAt
typeorm.updatedAtTimestamp = domain.props.timestamps.updatedAt
return typeorm
}
}

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.27.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-core@1.26.3...@standardnotes/domain-core@1.27.0) (2023-09-06)
### Features
* should be able to access shared item revisions as third party user ([#807](https://github.com/standardnotes/server/issues/807)) ([794cd87](https://github.com/standardnotes/server/commit/794cd8734acf89fb29f09dfb169a3f08f252bb6a))
## [1.26.3](https://github.com/standardnotes/server/compare/@standardnotes/domain-core@1.26.2...@standardnotes/domain-core@1.26.3) (2023-09-04)
**Note:** Version bump only for package @standardnotes/domain-core

View File

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

View File

@@ -0,0 +1,23 @@
import { Timestamps } from './Timestamps'
describe('Timestamps', () => {
it('should create a value object', () => {
const valueOrError = Timestamps.create(123, 234)
expect(valueOrError.isFailed()).toBeFalsy()
expect(valueOrError.getValue().createdAt).toEqual(123)
expect(valueOrError.getValue().updatedAt).toEqual(234)
})
it('should not create an invalid value object', () => {
const valueOrError = Timestamps.create('' as unknown as number, 123)
expect(valueOrError.isFailed()).toBeTruthy()
})
it('should not create an invalid value object', () => {
const valueOrError = Timestamps.create(123, '' as unknown as number)
expect(valueOrError.isFailed()).toBeTruthy()
})
})

View File

@@ -16,12 +16,12 @@ export class Timestamps extends ValueObject<TimestampsProps> {
}
static create(createdAt: number, updatedAt: number): Result<Timestamps> {
if (isNaN(createdAt)) {
if (isNaN(createdAt) || typeof createdAt !== 'number') {
return Result.fail<Timestamps>(
`Could not create Timestamps. Creation date should be a number, given: ${createdAt}`,
)
}
if (isNaN(updatedAt)) {
if (isNaN(updatedAt) || typeof updatedAt !== 'number') {
return Result.fail<Timestamps>(`Could not create Timestamps. Update date should be a number, given: ${updatedAt}`)
}

View File

@@ -1,6 +1,7 @@
import { SharedVaultUserPermission, Timestamps, Uuid } from '@standardnotes/domain-core'
import { SharedVaultUserPermission } from './SharedVaultUserPermission'
import { SharedVaultUser } from './SharedVaultUser'
import { Uuid } from '../Common/Uuid'
import { Timestamps } from '../Common/Timestamps'
describe('SharedVaultUser', () => {
it('should create an entity', () => {

View File

@@ -1,5 +1,6 @@
import { Entity, Result, UniqueEntityId } from '@standardnotes/domain-core'
import { Entity } from '../Core/Entity'
import { Result } from '../Core/Result'
import { UniqueEntityId } from '../Core/UniqueEntityId'
import { SharedVaultUserProps } from './SharedVaultUserProps'
export class SharedVaultUser extends Entity<SharedVaultUserProps> {

View File

@@ -0,0 +1,10 @@
import { Timestamps } from '../Common/Timestamps'
import { Uuid } from '../Common/Uuid'
import { SharedVaultUserPermission } from './SharedVaultUserPermission'
export interface SharedVaultUserProps {
sharedVaultUuid: Uuid
userUuid: Uuid
permission: SharedVaultUserPermission
timestamps: Timestamps
}

View File

@@ -59,8 +59,10 @@ export * from './Service/ServiceIdentifier'
export * from './Service/ServiceIdentifierProps'
export * from './Service/ServiceInterface'
export * from './SharedVault/SharedVaultUser'
export * from './SharedVault/SharedVaultUserPermission'
export * from './SharedVault/SharedVaultUserPermissionProps'
export * from './SharedVault/SharedVaultUserProps'
export * from './Subscription/SubscriptionPlanName'
export * from './Subscription/SubscriptionPlanNameProps'

View File

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

View File

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

View File

@@ -3,6 +3,12 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [2.123.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.122.2...@standardnotes/domain-events@2.123.0) (2023-09-06)
### Features
* should be able to access shared item revisions as third party user ([#807](https://github.com/standardnotes/server/issues/807)) ([794cd87](https://github.com/standardnotes/server/commit/794cd8734acf89fb29f09dfb169a3f08f252bb6a))
## [2.122.2](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.122.1...@standardnotes/domain-events@2.122.2) (2023-09-04)
**Note:** Version bump only for package @standardnotes/domain-events

View File

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

View File

@@ -0,0 +1,7 @@
import { DomainEventInterface } from './DomainEventInterface'
import { UserAddedToSharedVaultEventPayload } from './UserAddedToSharedVaultEventPayload'
export interface UserAddedToSharedVaultEvent extends DomainEventInterface {
type: 'USER_ADDED_TO_SHARED_VAULT'
payload: UserAddedToSharedVaultEventPayload
}

View File

@@ -0,0 +1,7 @@
export interface UserAddedToSharedVaultEventPayload {
sharedVaultUuid: string
userUuid: string
permission: string
createdAt: number
updatedAt: number
}

View File

@@ -0,0 +1,7 @@
import { DomainEventInterface } from './DomainEventInterface'
import { UserRemovedFromSharedVaultEventPayload } from './UserRemovedFromSharedVaultEventPayload'
export interface UserRemovedFromSharedVaultEvent extends DomainEventInterface {
type: 'USER_REMOVED_FROM_SHARED_VAULT'
payload: UserRemovedFromSharedVaultEventPayload
}

View File

@@ -0,0 +1,4 @@
export interface UserRemovedFromSharedVaultEventPayload {
sharedVaultUuid: string
userUuid: string
}

View File

@@ -96,6 +96,8 @@ export * from './Event/SubscriptionSyncRequestedEvent'
export * from './Event/SubscriptionSyncRequestedEventPayload'
export * from './Event/TransitionStatusUpdatedEvent'
export * from './Event/TransitionStatusUpdatedEventPayload'
export * from './Event/UserAddedToSharedVaultEvent'
export * from './Event/UserAddedToSharedVaultEventPayload'
export * from './Event/UserDisabledSessionUserAgentLoggingEvent'
export * from './Event/UserDisabledSessionUserAgentLoggingEventPayload'
export * from './Event/UserEmailChangedEvent'
@@ -104,6 +106,8 @@ export * from './Event/UserInvitedToSharedVaultEvent'
export * from './Event/UserInvitedToSharedVaultEventPayload'
export * from './Event/UserRegisteredEvent'
export * from './Event/UserRegisteredEventPayload'
export * from './Event/UserRemovedFromSharedVaultEvent'
export * from './Event/UserRemovedFromSharedVaultEventPayload'
export * from './Event/UserRolesChangedEvent'
export * from './Event/UserRolesChangedEventPayload'
export * from './Event/WebSocketMessageRequestedEvent'

View File

@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.11.32](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.11.31...@standardnotes/event-store@1.11.32) (2023-09-06)
**Note:** Version bump only for package @standardnotes/event-store
## [1.11.31](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.11.30...@standardnotes/event-store@1.11.31) (2023-09-04)
**Note:** Version bump only for package @standardnotes/event-store

View File

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

View File

@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.22.11](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.22.10...@standardnotes/files-server@1.22.11) (2023-09-06)
**Note:** Version bump only for package @standardnotes/files-server
## [1.22.10](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.22.9...@standardnotes/files-server@1.22.10) (2023-09-04)
**Note:** Version bump only for package @standardnotes/files-server

View File

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

View File

@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.15.25](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.24...@standardnotes/home-server@1.15.25) (2023-09-06)
**Note:** Version bump only for package @standardnotes/home-server
## [1.15.24](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.23...@standardnotes/home-server@1.15.24) (2023-09-04)
**Note:** Version bump only for package @standardnotes/home-server
## [1.15.23](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.22...@standardnotes/home-server@1.15.23) (2023-09-04)
**Note:** Version bump only for package @standardnotes/home-server
## [1.15.22](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.21...@standardnotes/home-server@1.15.22) (2023-09-04)
**Note:** Version bump only for package @standardnotes/home-server
## [1.15.21](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.20...@standardnotes/home-server@1.15.21) (2023-09-04)
**Note:** Version bump only for package @standardnotes/home-server

View File

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

View File

@@ -3,6 +3,30 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.31.0](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.30.14...@standardnotes/revisions-server@1.31.0) (2023-09-06)
### Features
* should be able to access shared item revisions as third party user ([#807](https://github.com/standardnotes/server/issues/807)) ([794cd87](https://github.com/standardnotes/server/commit/794cd8734acf89fb29f09dfb169a3f08f252bb6a))
## [1.30.14](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.30.13...@standardnotes/revisions-server@1.30.14) (2023-09-04)
### Bug Fixes
* prevent doubling transitions ([d9ee2c5](https://github.com/standardnotes/server/commit/d9ee2c5be2d81c729c829e6078846df624d500bd))
## [1.30.13](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.30.12...@standardnotes/revisions-server@1.30.13) (2023-09-04)
### Bug Fixes
* **revisions:** change logs severity in the transition process ([45b5506](https://github.com/standardnotes/server/commit/45b55068f9b60447e92f0fe0667f884d1690be9b))
## [1.30.12](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.30.11...@standardnotes/revisions-server@1.30.12) (2023-09-04)
### Bug Fixes
* **revisions:** change order field for transition of revisions ([a7a9349](https://github.com/standardnotes/server/commit/a7a93497e87679fdc910d3621a8d1c374e1fa796))
## [1.30.11](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.30.10...@standardnotes/revisions-server@1.30.11) (2023-09-04)
### Bug Fixes

View File

@@ -0,0 +1,41 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class init1669113322388 implements MigrationInterface {
name = 'init1669113322388'
public async up(queryRunner: QueryRunner): Promise<void> {
await this.syncSchemaBetweenLegacyRevisions(queryRunner)
await queryRunner.query(
'CREATE TABLE IF NOT EXISTS `revisions` (`uuid` varchar(36) NOT NULL, `item_uuid` varchar(36) NOT NULL, `user_uuid` varchar(36) NOT NULL, `content` mediumtext NULL, `content_type` varchar(255) NULL, `items_key_id` varchar(255) NULL, `enc_item_key` text NULL, `auth_hash` varchar(255) NULL, `creation_date` date NULL, `created_at` datetime(6) NULL, `updated_at` datetime(6) NULL, INDEX `item_uuid` (`item_uuid`), INDEX `user_uuid` (`user_uuid`), INDEX `creation_date` (`creation_date`), INDEX `created_at` (`created_at`), PRIMARY KEY (`uuid`)) ENGINE=InnoDB',
)
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('DROP INDEX `created_at` ON `revisions`')
await queryRunner.query('DROP INDEX `creation_date` ON `revisions`')
await queryRunner.query('DROP INDEX `user_uuid` ON `revisions`')
await queryRunner.query('DROP INDEX `item_uuid` ON `revisions`')
await queryRunner.query('DROP TABLE `revisions`')
}
private async syncSchemaBetweenLegacyRevisions(queryRunner: QueryRunner): Promise<void> {
const revisionsTableExistsQueryResult = await queryRunner.manager.query(
'SELECT COUNT(*) as count FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = "revisions"',
)
const revisionsTableExists = revisionsTableExistsQueryResult[0].count === 1
if (!revisionsTableExists) {
return
}
const revisionsTableHasUserUuidColumnQueryResult = await queryRunner.manager.query(
'SELECT COUNT(*) as count FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = "revisions" AND column_name = "user_uuid"',
)
const revisionsTableHasUserUuidColumn = revisionsTableHasUserUuidColumnQueryResult[0].count === 1
if (revisionsTableHasUserUuidColumn) {
return
}
await queryRunner.query('ALTER TABLE `revisions` ADD COLUMN `user_uuid` varchar(36) NULL')
}
}

View File

@@ -0,0 +1,28 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class removeDateIndexes1669636497932 implements MigrationInterface {
name = 'removeDateIndexes1669636497932'
public async up(queryRunner: QueryRunner): Promise<void> {
const indexRevisionsOnCreatedAt = await queryRunner.manager.query(
'SHOW INDEX FROM `revisions` where `key_name` = "created_at"',
)
const indexRevisionsOnCreatedAtExist = indexRevisionsOnCreatedAt && indexRevisionsOnCreatedAt.length > 0
if (indexRevisionsOnCreatedAtExist) {
await queryRunner.query('DROP INDEX `created_at` ON `revisions`')
}
const indexRevisionsOnCreationDate = await queryRunner.manager.query(
'SHOW INDEX FROM `revisions` where `key_name` = "creation_date"',
)
const indexRevisionsOnCreationDateAtExist = indexRevisionsOnCreationDate && indexRevisionsOnCreationDate.length > 0
if (indexRevisionsOnCreationDateAtExist) {
await queryRunner.query('DROP INDEX `creation_date` ON `revisions`')
}
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('CREATE INDEX `creation_date` ON `revisions` (`creation_date`)')
await queryRunner.query('CREATE INDEX `created_at` ON `revisions` (`created_at`)')
}
}

View File

@@ -0,0 +1,13 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class makeUserUuidNullable1669735585016 implements MigrationInterface {
name = 'makeUserUuidNullable1669735585016'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('ALTER TABLE `revisions` CHANGE `user_uuid` `user_uuid` varchar(36) NULL')
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('ALTER TABLE `revisions` CHANGE `user_uuid` `user_uuid` varchar(36) NOT NULL')
}
}

View File

@@ -0,0 +1,17 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class AddSharedVaultInformation1693915383950 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('ALTER TABLE `revisions` ADD `edited_by` varchar(36) NULL')
await queryRunner.query('ALTER TABLE `revisions` ADD `shared_vault_uuid` varchar(36) NULL')
await queryRunner.query('ALTER TABLE `revisions` ADD `key_system_identifier` varchar(36) NULL')
await queryRunner.query('CREATE INDEX `index_revisions_on_shared_vault_uuid` ON `revisions` (`shared_vault_uuid`)')
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('DROP INDEX `index_revisions_on_shared_vault_uuid` ON `revisions`')
await queryRunner.query('ALTER TABLE `revisions` DROP COLUMN `key_system_identifier`')
await queryRunner.query('ALTER TABLE `revisions` DROP COLUMN `shared_vault_uuid`')
await queryRunner.query('ALTER TABLE `revisions` DROP COLUMN `last_edited_by`')
}
}

View File

@@ -0,0 +1,37 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class AddSharedVaultInformation1693915775491 implements MigrationInterface {
name = 'AddSharedVaultInformation1693915775491'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('DROP INDEX "user_uuid"')
await queryRunner.query('DROP INDEX "item_uuid"')
await queryRunner.query(
'CREATE TABLE "temporary_revisions" ("uuid" varchar PRIMARY KEY NOT NULL, "item_uuid" varchar(36) NOT NULL, "user_uuid" varchar(36), "content" text, "content_type" varchar(255), "items_key_id" varchar(255), "enc_item_key" text, "auth_hash" varchar(255), "creation_date" date, "created_at" datetime(6), "updated_at" datetime(6), "edited_by" varchar(36), "shared_vault_uuid" varchar(36), "key_system_identifier" varchar(36))',
)
await queryRunner.query(
'INSERT INTO "temporary_revisions"("uuid", "item_uuid", "user_uuid", "content", "content_type", "items_key_id", "enc_item_key", "auth_hash", "creation_date", "created_at", "updated_at") SELECT "uuid", "item_uuid", "user_uuid", "content", "content_type", "items_key_id", "enc_item_key", "auth_hash", "creation_date", "created_at", "updated_at" FROM "revisions"',
)
await queryRunner.query('DROP TABLE "revisions"')
await queryRunner.query('ALTER TABLE "temporary_revisions" RENAME TO "revisions"')
await queryRunner.query('CREATE INDEX "user_uuid" ON "revisions" ("user_uuid") ')
await queryRunner.query('CREATE INDEX "item_uuid" ON "revisions" ("item_uuid") ')
await queryRunner.query('CREATE INDEX "index_revisions_on_shared_vault_uuid" ON "revisions" ("shared_vault_uuid") ')
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('DROP INDEX "index_revisions_on_shared_vault_uuid"')
await queryRunner.query('DROP INDEX "item_uuid"')
await queryRunner.query('DROP INDEX "user_uuid"')
await queryRunner.query('ALTER TABLE "revisions" RENAME TO "temporary_revisions"')
await queryRunner.query(
'CREATE TABLE "revisions" ("uuid" varchar PRIMARY KEY NOT NULL, "item_uuid" varchar(36) NOT NULL, "user_uuid" varchar(36), "content" text, "content_type" varchar(255), "items_key_id" varchar(255), "enc_item_key" text, "auth_hash" varchar(255), "creation_date" date, "created_at" datetime(6), "updated_at" datetime(6))',
)
await queryRunner.query(
'INSERT INTO "revisions"("uuid", "item_uuid", "user_uuid", "content", "content_type", "items_key_id", "enc_item_key", "auth_hash", "creation_date", "created_at", "updated_at") SELECT "uuid", "item_uuid", "user_uuid", "content", "content_type", "items_key_id", "enc_item_key", "auth_hash", "creation_date", "created_at", "updated_at" FROM "temporary_revisions"',
)
await queryRunner.query('DROP TABLE "temporary_revisions"')
await queryRunner.query('CREATE INDEX "item_uuid" ON "revisions" ("item_uuid") ')
await queryRunner.query('CREATE INDEX "user_uuid" ON "revisions" ("user_uuid") ')
}
}

View File

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

View File

@@ -7,8 +7,8 @@ import { SNSClient, SNSClientConfig } from '@aws-sdk/client-sns'
import { Revision } from '../Domain/Revision/Revision'
import { RevisionMetadata } from '../Domain/Revision/RevisionMetadata'
import { RevisionRepositoryInterface } from '../Domain/Revision/RevisionRepositoryInterface'
import { SQLRevisionRepository } from '../Infra/TypeORM/SQL/SQLRevisionRepository'
import { SQLRevision } from '../Infra/TypeORM/SQL/SQLRevision'
import { SQLLegacyRevisionRepository } from '../Infra/TypeORM/SQL/SQLLegacyRevisionRepository'
import { SQLLegacyRevision } from '../Infra/TypeORM/SQL/SQLLegacyRevision'
import { AppDataSource } from './DataSource'
import { Env } from './Env'
import TYPES from './Types'
@@ -48,8 +48,8 @@ import { BaseRevisionsController } from '../Infra/InversifyExpress/Base/BaseRevi
import { Transform } from 'stream'
import { MongoDBRevision } from '../Infra/TypeORM/MongoDB/MongoDBRevision'
import { MongoDBRevisionRepository } from '../Infra/TypeORM/MongoDB/MongoDBRevisionRepository'
import { SQLRevisionMetadataPersistenceMapper } from '../Mapping/Persistence/SQL/SQLRevisionMetadataPersistenceMapper'
import { SQLRevisionPersistenceMapper } from '../Mapping/Persistence/SQL/SQLRevisionPersistenceMapper'
import { SQLLegacyRevisionMetadataPersistenceMapper } from '../Mapping/Persistence/SQL/SQLLegacyRevisionMetadataPersistenceMapper'
import { SQLLegacyRevisionPersistenceMapper } from '../Mapping/Persistence/SQL/SQLLegacyRevisionPersistenceMapper'
import { MongoDBRevisionMetadataPersistenceMapper } from '../Mapping/Persistence/MongoDB/MongoDBRevisionMetadataPersistenceMapper'
import { MongoDBRevisionPersistenceMapper } from '../Mapping/Persistence/MongoDB/MongoDBRevisionPersistenceMapper'
import { RevisionHttpMapper } from '../Mapping/Http/RevisionHttpMapper'
@@ -62,6 +62,10 @@ import { DomainEventFactoryInterface } from '../Domain/Event/DomainEventFactoryI
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'
export class ContainerConfigLoader {
async load(configuration?: {
@@ -77,6 +81,8 @@ export class ContainerConfigLoader {
env.load()
const isConfiguredForHomeServer = env.get('MODE', true) === 'home-server'
const isConfiguredForSelfHosting = env.get('MODE', true) === 'self-hosted'
const isConfiguredForHomeServerOrSelfHosting = isConfiguredForHomeServer || isConfiguredForSelfHosting
const isSecondaryDatabaseEnabled = env.get('SECONDARY_DB_ENABLED', true) === 'true'
const container = new Container({
@@ -197,9 +203,17 @@ export class ContainerConfigLoader {
.toConstantValue(new DomainEventFactory(container.get(TYPES.Revisions_Timer)))
// Map
container
.bind<MapperInterface<RevisionMetadata, SQLLegacyRevision>>(
TYPES.Revisions_SQLLegacyRevisionMetadataPersistenceMapper,
)
.toConstantValue(new SQLLegacyRevisionMetadataPersistenceMapper())
container
.bind<MapperInterface<RevisionMetadata, SQLRevision>>(TYPES.Revisions_SQLRevisionMetadataPersistenceMapper)
.toConstantValue(new SQLRevisionMetadataPersistenceMapper())
container
.bind<MapperInterface<Revision, SQLLegacyRevision>>(TYPES.Revisions_SQLLegacyRevisionPersistenceMapper)
.toConstantValue(new SQLLegacyRevisionPersistenceMapper())
container
.bind<MapperInterface<Revision, SQLRevision>>(TYPES.Revisions_SQLRevisionPersistenceMapper)
.toConstantValue(new SQLRevisionPersistenceMapper())
@@ -213,22 +227,36 @@ export class ContainerConfigLoader {
.toConstantValue(new MongoDBRevisionPersistenceMapper())
// ORM
container
.bind<Repository<SQLLegacyRevision>>(TYPES.Revisions_ORMLegacyRevisionRepository)
.toDynamicValue(() => appDataSource.getRepository(SQLLegacyRevision))
container
.bind<Repository<SQLRevision>>(TYPES.Revisions_ORMRevisionRepository)
.toDynamicValue(() => appDataSource.getRepository(SQLRevision))
.toConstantValue(appDataSource.getRepository(SQLRevision))
// Repositories
container
.bind<RevisionRepositoryInterface>(TYPES.Revisions_SQLRevisionRepository)
.toConstantValue(
new SQLRevisionRepository(
container.get<Repository<SQLRevision>>(TYPES.Revisions_ORMRevisionRepository),
container.get<MapperInterface<RevisionMetadata, SQLRevision>>(
TYPES.Revisions_SQLRevisionMetadataPersistenceMapper,
),
container.get<MapperInterface<Revision, SQLRevision>>(TYPES.Revisions_SQLRevisionPersistenceMapper),
container.get<winston.Logger>(TYPES.Revisions_Logger),
),
isConfiguredForHomeServerOrSelfHosting
? new SQLRevisionRepository(
container.get<Repository<SQLRevision>>(TYPES.Revisions_ORMRevisionRepository),
container.get<MapperInterface<RevisionMetadata, SQLRevision>>(
TYPES.Revisions_SQLRevisionMetadataPersistenceMapper,
),
container.get<MapperInterface<Revision, SQLRevision>>(TYPES.Revisions_SQLRevisionPersistenceMapper),
container.get<winston.Logger>(TYPES.Revisions_Logger),
)
: new SQLLegacyRevisionRepository(
container.get<Repository<SQLLegacyRevision>>(TYPES.Revisions_ORMLegacyRevisionRepository),
container.get<MapperInterface<RevisionMetadata, SQLLegacyRevision>>(
TYPES.Revisions_SQLLegacyRevisionMetadataPersistenceMapper,
),
container.get<MapperInterface<Revision, SQLLegacyRevision>>(
TYPES.Revisions_SQLLegacyRevisionPersistenceMapper,
),
container.get<winston.Logger>(TYPES.Revisions_Logger),
),
)
if (isSecondaryDatabaseEnabled) {

View File

@@ -1,11 +1,12 @@
import { DataSource, EntityTarget, LoggerOptions, MongoRepository, ObjectLiteral, Repository } from 'typeorm'
import { MysqlConnectionOptions } from 'typeorm/driver/mysql/MysqlConnectionOptions'
import { SQLRevision } from '../Infra/TypeORM/SQL/SQLRevision'
import { SQLLegacyRevision } from '../Infra/TypeORM/SQL/SQLLegacyRevision'
import { Env } from './Env'
import { SqliteConnectionOptions } from 'typeorm/driver/sqlite/SqliteConnectionOptions'
import { MongoDBRevision } from '../Infra/TypeORM/MongoDB/MongoDBRevision'
import { SQLRevision } from '../Infra/TypeORM/SQL/SQLRevision'
export class AppDataSource {
private _dataSource: DataSource | undefined
@@ -65,14 +66,23 @@ export class AppDataSource {
const isConfiguredForMySQL = this.env.get('DB_TYPE') === 'mysql'
const isConfiguredForHomeServerOrSelfHosting =
this.env.get('MODE', true) === 'home-server' || this.env.get('MODE', true) === 'self-hosted'
const maxQueryExecutionTime = this.env.get('DB_MAX_QUERY_EXECUTION_TIME', true)
? +this.env.get('DB_MAX_QUERY_EXECUTION_TIME', true)
: 45_000
const migrationsSourceDirectoryName = isConfiguredForMySQL
? isConfiguredForHomeServerOrSelfHosting
? 'mysql'
: 'mysql-legacy'
: 'sqlite'
const commonDataSourceOptions = {
maxQueryExecutionTime,
entities: [SQLRevision],
migrations: [`${__dirname}/../../migrations/${isConfiguredForMySQL ? 'mysql' : 'sqlite'}/*.js`],
entities: [isConfiguredForHomeServerOrSelfHosting ? SQLRevision : SQLLegacyRevision],
migrations: [`${__dirname}/../../migrations/${migrationsSourceDirectoryName}/*.js`],
migrationsRun: true,
logging: <LoggerOptions>this.env.get('DB_DEBUG_LEVEL', true) ?? 'info',
}

View File

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

View File

@@ -6,7 +6,11 @@ const TYPES = {
Revisions_S3: Symbol.for('Revisions_S3'),
Revisions_Env: Symbol.for('Revisions_Env'),
// Map
Revisions_SQLLegacyRevisionMetadataPersistenceMapper: Symbol.for(
'Revisions_SQLLegacyRevisionMetadataPersistenceMapper',
),
Revisions_SQLRevisionMetadataPersistenceMapper: Symbol.for('Revisions_SQLRevisionMetadataPersistenceMapper'),
Revisions_SQLLegacyRevisionPersistenceMapper: Symbol.for('Revisions_SQLLegacyRevisionPersistenceMapper'),
Revisions_SQLRevisionPersistenceMapper: Symbol.for('Revisions_SQLRevisionPersistenceMapper'),
Revisions_MongoDBRevisionMetadataPersistenceMapper: Symbol.for('Revisions_MongoDBRevisionMetadataPersistenceMapper'),
Revisions_MongoDBRevisionPersistenceMapper: Symbol.for('Revisions_MongoDBRevisionPersistenceMapper'),
@@ -15,6 +19,7 @@ const TYPES = {
Revisions_RevisionMetadataHttpMapper: Symbol.for('Revisions_RevisionMetadataHttpMapper'),
// ORM
Revisions_ORMRevisionRepository: Symbol.for('Revisions_ORMRevisionRepository'),
Revisions_ORMLegacyRevisionRepository: Symbol.for('Revisions_ORMLegacyRevisionRepository'),
// Mongo
Revisions_ORMMongoRevisionRepository: Symbol.for('Revisions_ORMMongoRevisionRepository'),
// Repositories

View File

@@ -0,0 +1,3 @@
export interface KeySystemAssociationProps {
keySystemIdentifier: string
}

View File

@@ -0,0 +1,15 @@
import { KeySystemAssociation } from './KeySystemAssociation'
describe('KeySystemAssociation', () => {
it('should create a value object', () => {
const entityOrError = KeySystemAssociation.create('00000000-0000-0000-0000-000000000000')
expect(entityOrError.isFailed()).toBeFalsy()
})
it('should fail to create a value object with an empty key system identifier', () => {
const entityOrError = KeySystemAssociation.create('')
expect(entityOrError.isFailed()).toBeTruthy()
})
})

View File

@@ -0,0 +1,18 @@
import { Result, Validator, ValueObject } from '@standardnotes/domain-core'
import { KeySystemAssociationProps } from './KeySystemAssocationProps'
export class KeySystemAssociation extends ValueObject<KeySystemAssociationProps> {
private constructor(props: KeySystemAssociationProps) {
super(props)
}
static create(keySystemIdentifier: string): Result<KeySystemAssociation> {
const validationResult = Validator.isNotEmptyString(keySystemIdentifier)
if (validationResult.isFailed()) {
return Result.fail<KeySystemAssociation>(validationResult.getError())
}
return Result.ok<KeySystemAssociation>(new KeySystemAssociation({ keySystemIdentifier }))
}
}

View File

@@ -1,5 +1,8 @@
import { ContentType, Dates, Uuid } from '@standardnotes/domain-core'
import { SharedVaultAssociation } from '../SharedVault/SharedVaultAssociation'
import { KeySystemAssociation } from '../KeySystem/KeySystemAssociation'
export interface RevisionProps {
itemUuid: Uuid
userUuid: Uuid | null
@@ -10,4 +13,6 @@ export interface RevisionProps {
authHash: string | null
creationDate: Date
dates: Dates
sharedVaultAssociation?: SharedVaultAssociation
keySystemAssociation?: KeySystemAssociation
}

View File

@@ -9,7 +9,7 @@ export interface RevisionRepositoryInterface {
removeOneByUuid(revisionUuid: Uuid, userUuid: Uuid): Promise<void>
findOneByUuid(revisionUuid: Uuid, userUuid: Uuid): Promise<Revision | null>
findByItemUuid(itemUuid: Uuid): Promise<Array<Revision>>
findMetadataByItemId(itemUuid: Uuid, userUuid: Uuid): Promise<Array<RevisionMetadata>>
findMetadataByItemId(itemUuid: Uuid, userUuid: Uuid, sharedVaultUuids: Uuid[]): Promise<Array<RevisionMetadata>>
updateUserUuid(itemUuid: Uuid, userUuid: Uuid): Promise<void>
findByUserUuid(dto: { userUuid: Uuid; offset?: number; limit?: number }): Promise<Array<Revision>>
insert(revision: Revision): Promise<boolean>

View File

@@ -0,0 +1,14 @@
import { Uuid } from '@standardnotes/domain-core'
import { SharedVaultAssociation } from './SharedVaultAssociation'
describe('SharedVaultAssociation', () => {
it('should create a value object', () => {
const entityOrError = SharedVaultAssociation.create({
editedBy: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
sharedVaultUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
})
expect(entityOrError.isFailed()).toBeFalsy()
})
})

View File

@@ -0,0 +1,13 @@
import { Result, ValueObject } from '@standardnotes/domain-core'
import { SharedVaultAssociationProps } from './SharedVaultAssociationProps'
export class SharedVaultAssociation extends ValueObject<SharedVaultAssociationProps> {
private constructor(props: SharedVaultAssociationProps) {
super(props)
}
static create(props: SharedVaultAssociationProps): Result<SharedVaultAssociation> {
return Result.ok<SharedVaultAssociation>(new SharedVaultAssociation(props))
}
}

View File

@@ -0,0 +1,6 @@
import { Uuid } from '@standardnotes/domain-core'
export interface SharedVaultAssociationProps {
editedBy: Uuid
sharedVaultUuid: Uuid
}

View File

@@ -22,17 +22,30 @@ describe('GetRevisionsMetada', () => {
itemUuid: '84c0f8e8-544a-4c7e-9adf-26209303bc1d',
userUuid: '84c0f8e8-544a-4c7e-9adf-26209303bc1d',
roleNames: ['CORE_USER'],
sharedVaultUuids: ['84c0f8e8-544a-4c7e-9adf-26209303bc1d'],
})
expect(result.isFailed()).toBeFalsy()
expect(result.getValue().length).toEqual(1)
})
it('should not return revisions metadata for an invalid shared vault uuid', async () => {
const result = await createUseCase().execute({
itemUuid: '84c0f8e8-544a-4c7e-9adf-26209303bc1d',
userUuid: '84c0f8e8-544a-4c7e-9adf-26209303bc1d',
roleNames: ['CORE_USER'],
sharedVaultUuids: ['1-2-3'],
})
expect(result.isFailed()).toBeTruthy()
})
it('should not return revisions metadata for a an invalid item uuid', async () => {
const result = await createUseCase().execute({
itemUuid: '1-2-3',
userUuid: '84c0f8e8-544a-4c7e-9adf-26209303bc1d',
roleNames: ['CORE_USER'],
sharedVaultUuids: [],
})
expect(result.isFailed()).toBeTruthy()
@@ -43,6 +56,7 @@ describe('GetRevisionsMetada', () => {
userUuid: '1-2-3',
itemUuid: '84c0f8e8-544a-4c7e-9adf-26209303bc1d',
roleNames: ['CORE_USER'],
sharedVaultUuids: [],
})
expect(result.isFailed()).toBeTruthy()
@@ -53,6 +67,7 @@ describe('GetRevisionsMetada', () => {
itemUuid: '84c0f8e8-544a-4c7e-9adf-26209303bc1d',
userUuid: '84c0f8e8-544a-4c7e-9adf-26209303bc1d',
roleNames: ['INVALID_ROLE_NAME'],
sharedVaultUuids: [],
})
expect(result.isFailed()).toBeTruthy()

View File

@@ -19,6 +19,15 @@ export class GetRevisionsMetada implements UseCaseInterface<RevisionMetadata[]>
return Result.fail<RevisionMetadata[]>(`Could not get revisions: ${userUuidOrError.getError()}`)
}
const sharedVaultUuids = []
for (const sharedVaultUuid of dto.sharedVaultUuids) {
const sharedVaultUuidOrError = Uuid.create(sharedVaultUuid)
if (sharedVaultUuidOrError.isFailed()) {
return Result.fail<RevisionMetadata[]>(`Could not get revisions: ${sharedVaultUuidOrError.getError()}`)
}
sharedVaultUuids.push(sharedVaultUuidOrError.getValue())
}
const roleNamesOrError = RoleNameCollection.create(dto.roleNames)
if (roleNamesOrError.isFailed()) {
return Result.fail(roleNamesOrError.getError())
@@ -30,6 +39,7 @@ export class GetRevisionsMetada implements UseCaseInterface<RevisionMetadata[]>
const revisionsMetdata = await revisionRepository.findMetadataByItemId(
itemUuidOrError.getValue(),
userUuidOrError.getValue(),
sharedVaultUuids,
)
return Result.ok<RevisionMetadata[]>(revisionsMetdata)

View File

@@ -1,5 +1,6 @@
export interface GetRevisionsMetadaDTO {
itemUuid: string
sharedVaultUuids: string[]
userUuid: string
roleNames: string[]
}

View File

@@ -107,6 +107,7 @@ describe('TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser', () => {
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()
@@ -130,7 +131,7 @@ describe('TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser', () => {
expect(result.isFailed()).toBeFalsy()
expect(primaryRevisionRepository.countByUserUuid).toHaveBeenCalledTimes(2)
expect(primaryRevisionRepository.countByUserUuid).toHaveBeenCalledTimes(3)
expect(primaryRevisionRepository.countByUserUuid).toHaveBeenCalledWith(
Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
)
@@ -343,7 +344,7 @@ describe('TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser', () => {
'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(2)
expect(primaryRevisionRepository.countByUserUuid).toHaveBeenCalledTimes(3)
expect(primaryRevisionRepository.countByUserUuid).toHaveBeenCalledWith(
Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
)
@@ -367,7 +368,7 @@ describe('TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser', () => {
expect(result.isFailed()).toBeTruthy()
expect(result.getError()).toEqual('Revision 00000000-0000-0000-0000-000000000001 not found in secondary database')
expect(primaryRevisionRepository.countByUserUuid).toHaveBeenCalledTimes(2)
expect(primaryRevisionRepository.countByUserUuid).toHaveBeenCalledTimes(3)
expect(primaryRevisionRepository.countByUserUuid).toHaveBeenCalledWith(
Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
)
@@ -412,4 +413,25 @@ describe('TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser', () => {
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

@@ -24,9 +24,15 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
}
const userUuid = userUuidOrError.getValue()
if (await this.isAlreadyMigrated(userUuid)) {
this.logger.info(`Revisions for user ${userUuid.value} are already migrated`)
return Result.ok()
}
const migrationTimeStart = this.timer.getTimestampInMicroseconds()
this.logger.info(`Transitioning revisions for user ${userUuid.value}`)
this.logger.debug(`Transitioning revisions for user ${userUuid.value}`)
const migrationResult = await this.migrateRevisionsForUser(userUuid)
if (migrationResult.isFailed()) {
@@ -90,7 +96,7 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
for (const revision of revisions) {
try {
this.logger.info(
this.logger.debug(
`Transitioning revision #${
totalRevisionsCountTransitionedToSecondary + 1
}: ${revision.id.toString()} to secondary database`,
@@ -111,7 +117,7 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
}
}
this.logger.info(`Transitioned ${totalRevisionsCountTransitionedToSecondary} revisions to secondary database`)
this.logger.debug(`Transitioned ${totalRevisionsCountTransitionedToSecondary} revisions to secondary database`)
return Result.ok()
} catch (error) {
@@ -137,6 +143,12 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
await this.timer.sleep(twoSecondsInMilliseconds)
}
private async isAlreadyMigrated(userUuid: Uuid): Promise<boolean> {
const totalRevisionsCountForUserInPrimary = await this.primaryRevisionsRepository.countByUserUuid(userUuid)
return totalRevisionsCountForUserInPrimary === 0
}
private async checkIntegrityBetweenPrimaryAndSecondaryDatabase(userUuid: Uuid): Promise<Result<boolean>> {
try {
const totalRevisionsCountForUserInPrimary = await this.primaryRevisionsRepository.countByUserUuid(userUuid)

View File

@@ -38,6 +38,9 @@ export class BaseRevisionsController extends BaseHttpController {
itemUuid: request.params.itemUuid,
userUuid: response.locals.user.uuid,
roleNames: response.locals.roles.map((role: Role) => role.name),
sharedVaultUuids: response.locals.belongsToSharedVaults.map(
(association: { shared_vault_uuid: string; permission: string }) => association.shared_vault_uuid,
),
})
if (revisionMetadataOrError.isFailed()) {

View File

@@ -47,6 +47,7 @@ export class ApiGatewayAuthMiddleware extends BaseMiddleware {
response.locals.roles = token.roles
response.locals.session = token.session
response.locals.readOnlyAccess = token.session?.readonly_access ?? false
response.locals.belongsToSharedVaults = token.belongs_to_shared_vaults ?? []
return next()
} catch (error) {

View File

@@ -37,4 +37,14 @@ export class MongoDBRevision {
@Column()
declare updatedAt: Date
@Column()
declare editedBy: string | null
@Column()
@Index('index_revisions_on_shared_vault_uuid')
declare sharedVaultUuid: string | null
@Column()
declare keySystemIdentifier: string | null
}

View File

@@ -86,16 +86,36 @@ export class MongoDBRevisionRepository implements RevisionRepositoryInterface {
return revisions
}
async findMetadataByItemId(itemUuid: Uuid, userUuid: Uuid): Promise<RevisionMetadata[]> {
const persistence = await this.mongoRepository.find({
select: ['_id', 'contentType', 'createdAt', 'updatedAt'],
where: {
$and: [{ itemUuid: { $eq: itemUuid.value } }, { userUuid: { $eq: userUuid.value } }],
},
order: {
createdAt: 'DESC',
},
})
async findMetadataByItemId(
itemUuid: Uuid,
userUuid: Uuid,
sharedVaultUuids: Uuid[],
): Promise<Array<RevisionMetadata>> {
let persistence = []
if (sharedVaultUuids.length > 0) {
persistence = await this.mongoRepository.find({
select: ['_id', 'contentType', 'createdAt', 'updatedAt'],
where: {
$and: [
{ itemUuid: { $eq: itemUuid.value } },
{ sharedVaultUuid: { $in: sharedVaultUuids.map((uuid) => uuid.value) } },
],
},
order: {
createdAt: 'DESC',
},
})
} else {
persistence = await this.mongoRepository.find({
select: ['_id', 'contentType', 'createdAt', 'updatedAt'],
where: {
$and: [{ itemUuid: { $eq: itemUuid.value } }, { userUuid: { $eq: userUuid.value } }],
},
order: {
createdAt: 'DESC',
},
})
}
const revisions: RevisionMetadata[] = []

View File

@@ -0,0 +1,83 @@
import { Column, Entity, Index, PrimaryGeneratedColumn } from 'typeorm'
@Entity({ name: 'revisions' })
export class SQLLegacyRevision {
@PrimaryGeneratedColumn('uuid')
declare uuid: string
@Column({
name: 'item_uuid',
length: 36,
})
@Index('item_uuid')
declare itemUuid: string
@Column({
name: 'user_uuid',
length: 36,
type: 'varchar',
nullable: true,
})
@Index('user_uuid')
declare userUuid: string | null
@Column({
type: 'text',
nullable: true,
})
declare content: string | null
@Column({
name: 'content_type',
type: 'varchar',
length: 255,
nullable: true,
})
declare contentType: string | null
@Column({
type: 'varchar',
name: 'items_key_id',
length: 255,
nullable: true,
})
declare itemsKeyId: string | null
@Column({
name: 'enc_item_key',
type: 'text',
nullable: true,
})
declare encItemKey: string | null
@Column({
name: 'auth_hash',
type: 'varchar',
length: 255,
nullable: true,
})
declare authHash: string | null
@Column({
name: 'creation_date',
type: 'date',
nullable: true,
})
declare creationDate: Date
@Column({
name: 'created_at',
type: 'datetime',
precision: 6,
nullable: true,
})
declare createdAt: Date
@Column({
name: 'updated_at',
type: 'datetime',
precision: 6,
nullable: true,
})
declare updatedAt: Date
}

View File

@@ -0,0 +1,146 @@
import { MapperInterface, Uuid } from '@standardnotes/domain-core'
import { Repository } from 'typeorm'
import { Logger } from 'winston'
import { Revision } from '../../../Domain/Revision/Revision'
import { RevisionMetadata } from '../../../Domain/Revision/RevisionMetadata'
import { RevisionRepositoryInterface } from '../../../Domain/Revision/RevisionRepositoryInterface'
import { SQLLegacyRevision } from './SQLLegacyRevision'
export class SQLLegacyRevisionRepository implements RevisionRepositoryInterface {
constructor(
protected ormRepository: Repository<SQLLegacyRevision>,
protected revisionMetadataMapper: MapperInterface<RevisionMetadata, SQLLegacyRevision>,
protected revisionMapper: MapperInterface<Revision, SQLLegacyRevision>,
protected logger: Logger,
) {}
async countByUserUuid(userUuid: Uuid): Promise<number> {
return this.ormRepository
.createQueryBuilder()
.where('user_uuid = :userUuid', { userUuid: userUuid.value })
.getCount()
}
async findByUserUuid(dto: { userUuid: Uuid; offset?: number; limit?: number }): Promise<Revision[]> {
const queryBuilder = this.ormRepository
.createQueryBuilder('revision')
.where('revision.user_uuid = :userUuid', { userUuid: dto.userUuid.value })
.orderBy('revision.uuid', 'ASC')
if (dto.offset !== undefined) {
queryBuilder.skip(dto.offset)
}
if (dto.limit !== undefined) {
queryBuilder.take(dto.limit)
}
const sqlRevisions = await queryBuilder.getMany()
const revisions = []
for (const sqlRevision of sqlRevisions) {
revisions.push(this.revisionMapper.toDomain(sqlRevision))
}
return revisions
}
async updateUserUuid(itemUuid: Uuid, userUuid: Uuid): Promise<void> {
await this.ormRepository
.createQueryBuilder()
.update()
.set({
userUuid: userUuid.value,
})
.where('item_uuid = :itemUuid', { itemUuid: itemUuid.value })
.execute()
}
async findByItemUuid(itemUuid: Uuid): Promise<Revision[]> {
const SQLLegacyRevisions = await this.ormRepository
.createQueryBuilder()
.where('item_uuid = :itemUuid', { itemUuid: itemUuid.value })
.getMany()
const revisions = []
for (const revision of SQLLegacyRevisions) {
revisions.push(this.revisionMapper.toDomain(revision))
}
return revisions
}
async removeByUserUuid(userUuid: Uuid): Promise<void> {
await this.ormRepository
.createQueryBuilder()
.delete()
.from('revisions')
.where('user_uuid = :userUuid', { userUuid: userUuid.value })
.execute()
}
async removeOneByUuid(revisionUuid: Uuid, userUuid: Uuid): Promise<void> {
await this.ormRepository
.createQueryBuilder()
.delete()
.from('revisions')
.where('uuid = :revisionUuid AND user_uuid = :userUuid', {
userUuid: userUuid.value,
revisionUuid: revisionUuid.value,
})
.execute()
}
async findOneByUuid(revisionUuid: Uuid, userUuid: Uuid): Promise<Revision | null> {
const SQLLegacyRevision = await this.ormRepository
.createQueryBuilder()
.where('uuid = :revisionUuid', { revisionUuid: revisionUuid.value })
.andWhere('user_uuid = :userUuid', { userUuid: userUuid.value })
.getOne()
if (SQLLegacyRevision === null) {
return null
}
return this.revisionMapper.toDomain(SQLLegacyRevision)
}
async insert(revision: Revision): Promise<boolean> {
const SQLLegacyRevision = this.revisionMapper.toProjection(revision)
await this.ormRepository.insert(SQLLegacyRevision)
return true
}
async findMetadataByItemId(
itemUuid: Uuid,
userUuid: Uuid,
_sharedVaultUuids: Uuid[],
): Promise<Array<RevisionMetadata>> {
const queryBuilder = this.ormRepository
.createQueryBuilder()
.select('uuid', 'uuid')
.addSelect('content_type', 'contentType')
.addSelect('created_at', 'createdAt')
.addSelect('updated_at', 'updatedAt')
.where('item_uuid = :itemUuid', { itemUuid: itemUuid.value })
.andWhere('user_uuid = :userUuid', { userUuid: userUuid.value })
.orderBy('created_at', 'DESC')
const simplifiedRevisions = await queryBuilder.getRawMany()
this.logger.debug(
`Found ${simplifiedRevisions.length} revisions entries for item ${itemUuid.value}`,
simplifiedRevisions,
)
const metadata = []
for (const simplifiedRevision of simplifiedRevisions) {
metadata.push(this.revisionMetadataMapper.toDomain(simplifiedRevision))
}
return metadata
}
}

View File

@@ -1,83 +1,31 @@
import { Column, Entity, Index, PrimaryGeneratedColumn } from 'typeorm'
import { Column, Entity, Index } from 'typeorm'
import { SQLLegacyRevision } from './SQLLegacyRevision'
@Entity({ name: 'revisions' })
export class SQLRevision {
@PrimaryGeneratedColumn('uuid')
declare uuid: string
export class SQLRevision extends SQLLegacyRevision {
@Column({
name: 'item_uuid',
type: 'varchar',
name: 'edited_by',
length: 36,
nullable: true,
})
@Index('item_uuid')
declare itemUuid: string
declare editedBy: string | null
@Column({
name: 'user_uuid',
type: 'varchar',
name: 'shared_vault_uuid',
length: 36,
type: 'varchar',
nullable: true,
})
@Index('user_uuid')
declare userUuid: string | null
@Column({
type: 'text',
nullable: true,
})
declare content: string | null
@Column({
name: 'content_type',
type: 'varchar',
length: 255,
nullable: true,
})
declare contentType: string | null
@Index('index_revisions_on_shared_vault_uuid')
declare sharedVaultUuid: string | null
@Column({
type: 'varchar',
name: 'items_key_id',
length: 255,
name: 'key_system_identifier',
length: 36,
nullable: true,
})
declare itemsKeyId: string | null
@Column({
name: 'enc_item_key',
type: 'text',
nullable: true,
})
declare encItemKey: string | null
@Column({
name: 'auth_hash',
type: 'varchar',
length: 255,
nullable: true,
})
declare authHash: string | null
@Column({
name: 'creation_date',
type: 'date',
nullable: true,
})
declare creationDate: Date
@Column({
name: 'created_at',
type: 'datetime',
precision: 6,
nullable: true,
})
declare createdAt: Date
@Column({
name: 'updated_at',
type: 'datetime',
precision: 6,
nullable: true,
})
declare updatedAt: Date
declare keySystemIdentifier: string | null
}

View File

@@ -4,127 +4,40 @@ import { Logger } from 'winston'
import { Revision } from '../../../Domain/Revision/Revision'
import { RevisionMetadata } from '../../../Domain/Revision/RevisionMetadata'
import { RevisionRepositoryInterface } from '../../../Domain/Revision/RevisionRepositoryInterface'
import { SQLLegacyRevisionRepository } from './SQLLegacyRevisionRepository'
import { SQLRevision } from './SQLRevision'
export class SQLRevisionRepository implements RevisionRepositoryInterface {
export class SQLRevisionRepository extends SQLLegacyRevisionRepository {
constructor(
private ormRepository: Repository<SQLRevision>,
private revisionMetadataMapper: MapperInterface<RevisionMetadata, SQLRevision>,
private revisionMapper: MapperInterface<Revision, SQLRevision>,
private logger: Logger,
) {}
async countByUserUuid(userUuid: Uuid): Promise<number> {
return this.ormRepository
.createQueryBuilder()
.where('user_uuid = :userUuid', { userUuid: userUuid.value })
.getCount()
protected override ormRepository: Repository<SQLRevision>,
protected override revisionMetadataMapper: MapperInterface<RevisionMetadata, SQLRevision>,
protected override revisionMapper: MapperInterface<Revision, SQLRevision>,
protected override logger: Logger,
) {
super(ormRepository, revisionMetadataMapper, revisionMapper, logger)
}
async findByUserUuid(dto: { userUuid: Uuid; offset?: number; limit?: number }): Promise<Revision[]> {
const queryBuilder = this.ormRepository
.createQueryBuilder('revision')
.where('revision.user_uuid = :userUuid', { userUuid: dto.userUuid.value })
.orderBy('revision.created_at', 'ASC')
if (dto.offset !== undefined) {
queryBuilder.skip(dto.offset)
}
if (dto.limit !== undefined) {
queryBuilder.take(dto.limit)
}
const sqlRevisions = await queryBuilder.getMany()
const revisions = []
for (const sqlRevision of sqlRevisions) {
revisions.push(this.revisionMapper.toDomain(sqlRevision))
}
return revisions
}
async updateUserUuid(itemUuid: Uuid, userUuid: Uuid): Promise<void> {
await this.ormRepository
.createQueryBuilder()
.update()
.set({
userUuid: userUuid.value,
})
.where('item_uuid = :itemUuid', { itemUuid: itemUuid.value })
.execute()
}
async findByItemUuid(itemUuid: Uuid): Promise<Revision[]> {
const SQLRevisions = await this.ormRepository
.createQueryBuilder()
.where('item_uuid = :itemUuid', { itemUuid: itemUuid.value })
.getMany()
const revisions = []
for (const revision of SQLRevisions) {
revisions.push(this.revisionMapper.toDomain(revision))
}
return revisions
}
async removeByUserUuid(userUuid: Uuid): Promise<void> {
await this.ormRepository
.createQueryBuilder()
.delete()
.from('revisions')
.where('user_uuid = :userUuid', { userUuid: userUuid.value })
.execute()
}
async removeOneByUuid(revisionUuid: Uuid, userUuid: Uuid): Promise<void> {
await this.ormRepository
.createQueryBuilder()
.delete()
.from('revisions')
.where('uuid = :revisionUuid AND user_uuid = :userUuid', {
userUuid: userUuid.value,
revisionUuid: revisionUuid.value,
})
.execute()
}
async findOneByUuid(revisionUuid: Uuid, userUuid: Uuid): Promise<Revision | null> {
const SQLRevision = await this.ormRepository
.createQueryBuilder()
.where('uuid = :revisionUuid', { revisionUuid: revisionUuid.value })
.andWhere('user_uuid = :userUuid', { userUuid: userUuid.value })
.getOne()
if (SQLRevision === null) {
return null
}
return this.revisionMapper.toDomain(SQLRevision)
}
async insert(revision: Revision): Promise<boolean> {
const SQLRevision = this.revisionMapper.toProjection(revision)
await this.ormRepository.insert(SQLRevision)
return true
}
async findMetadataByItemId(itemUuid: Uuid, userUuid: Uuid): Promise<Array<RevisionMetadata>> {
override async findMetadataByItemId(
itemUuid: Uuid,
userUuid: Uuid,
sharedVaultUuids: Uuid[],
): Promise<Array<RevisionMetadata>> {
const queryBuilder = this.ormRepository
.createQueryBuilder()
.select('uuid', 'uuid')
.addSelect('content_type', 'contentType')
.addSelect('created_at', 'createdAt')
.addSelect('updated_at', 'updatedAt')
.where('item_uuid = :itemUuid', { itemUuid: itemUuid.value })
.andWhere('user_uuid = :userUuid', { userUuid: userUuid.value })
.where('item_uuid = :itemUuid AND user_uuid = :userUuid', { itemUuid: itemUuid.value, userUuid: userUuid.value })
.orderBy('created_at', 'DESC')
if (sharedVaultUuids.length > 0) {
queryBuilder.orWhere('item_uuid = :itemUuid AND shared_vault_uuid IN (:...sharedVaultUuids)', {
itemUuid: itemUuid.value,
sharedVaultUuids: sharedVaultUuids.map((uuid) => uuid.value),
})
}
const simplifiedRevisions = await queryBuilder.getRawMany()
this.logger.debug(

View File

@@ -1,6 +1,8 @@
import { MapperInterface, Dates, Uuid, ContentType } from '@standardnotes/domain-core'
import { Revision } from '../../Domain/Revision/Revision'
import { SharedVaultAssociation } from '../../Domain/SharedVault/SharedVaultAssociation'
import { KeySystemAssociation } from '../../Domain/KeySystem/KeySystemAssociation'
export class RevisionItemStringMapper implements MapperInterface<Revision, string> {
toDomain(projection: string): Revision {
@@ -24,6 +26,39 @@ export class RevisionItemStringMapper implements MapperInterface<Revision, strin
}
const userUuid = userUuidOrError.getValue()
let sharedVaultAssociation: SharedVaultAssociation | undefined = undefined
if (item.shared_vault_uuid && item.last_edited_by) {
const sharedVaultUuidOrError = Uuid.create(item.shared_vault_uuid)
if (sharedVaultUuidOrError.isFailed()) {
throw new Error(`Failed to create revision from projection: ${sharedVaultUuidOrError.getError()}`)
}
const sharedVaultUuid = sharedVaultUuidOrError.getValue()
const lastEditedByOrError = Uuid.create(item.last_edited_by)
if (lastEditedByOrError.isFailed()) {
throw new Error(`Failed to create revision from projection: ${lastEditedByOrError.getError()}`)
}
const lastEditedBy = lastEditedByOrError.getValue()
const sharedVaultAssociationOrError = SharedVaultAssociation.create({
sharedVaultUuid,
editedBy: lastEditedBy,
})
if (sharedVaultAssociationOrError.isFailed()) {
throw new Error(`Failed to create revision from projection: ${sharedVaultAssociationOrError.getError()}`)
}
sharedVaultAssociation = sharedVaultAssociationOrError.getValue()
}
let keySystemAssociation: KeySystemAssociation | undefined = undefined
if (item.key_system_identifier) {
const keySystemAssociationOrError = KeySystemAssociation.create(item.key_system_identifier)
if (keySystemAssociationOrError.isFailed()) {
throw new Error(`Failed to create revision from projection: ${keySystemAssociationOrError.getError()}`)
}
keySystemAssociation = keySystemAssociationOrError.getValue()
}
const revisionOrError = Revision.create({
itemUuid,
userUuid,
@@ -34,6 +69,8 @@ export class RevisionItemStringMapper implements MapperInterface<Revision, strin
encItemKey: item.enc_item_key,
creationDate: new Date(),
dates: Dates.create(new Date(), new Date()).getValue(),
sharedVaultAssociation,
keySystemAssociation,
})
if (revisionOrError.isFailed()) {

View File

@@ -0,0 +1,43 @@
import { MapperInterface, Dates, UniqueEntityId, ContentType } from '@standardnotes/domain-core'
import { RevisionMetadata } from '../../../Domain/Revision/RevisionMetadata'
import { SQLLegacyRevision } from '../../../Infra/TypeORM/SQL/SQLLegacyRevision'
export class SQLLegacyRevisionMetadataPersistenceMapper
implements MapperInterface<RevisionMetadata, SQLLegacyRevision>
{
toDomain(projection: SQLLegacyRevision): RevisionMetadata {
const contentTypeOrError = ContentType.create(projection.contentType)
if (contentTypeOrError.isFailed()) {
throw new Error(`Could not create content type: ${contentTypeOrError.getError()}`)
}
const contentType = contentTypeOrError.getValue()
const createdAt = projection.createdAt instanceof Date ? projection.createdAt : new Date(projection.createdAt)
const updatedAt = projection.updatedAt instanceof Date ? projection.updatedAt : new Date(projection.updatedAt)
const datesOrError = Dates.create(createdAt, updatedAt)
if (datesOrError.isFailed()) {
throw new Error(`Could not create dates: ${datesOrError.getError()}`)
}
const dates = datesOrError.getValue()
const revisionMetadataOrError = RevisionMetadata.create(
{
contentType,
dates,
},
new UniqueEntityId(projection.uuid),
)
if (revisionMetadataOrError.isFailed()) {
throw new Error(`Could not create revision metdata: ${revisionMetadataOrError.getError()}`)
}
return revisionMetadataOrError.getValue()
}
toProjection(_domain: RevisionMetadata): SQLLegacyRevision {
throw new Error('Method not implemented.')
}
}

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