mirror of
https://github.com/standardnotes/server
synced 2026-02-02 08:01:14 -05:00
Compare commits
21 Commits
@standardn
...
@standardn
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ac0390e7c3 | ||
|
|
0477507a6a | ||
|
|
3e7856c895 | ||
|
|
6778a80f21 | ||
|
|
d4d49454a6 | ||
|
|
04b52e6773 | ||
|
|
2a1859e4be | ||
|
|
dd9a9c68cb | ||
|
|
9147ff5d49 | ||
|
|
503b84531b | ||
|
|
fe8ca828fb | ||
|
|
03a4a3f2ab | ||
|
|
3a8607d146 | ||
|
|
93b6e65554 | ||
|
|
5984e4c3e7 | ||
|
|
b4257c10ea | ||
|
|
c164bde847 | ||
|
|
883df939dd | ||
|
|
c7807d0f9e | ||
|
|
fc90343aaa | ||
|
|
fbcb45c3a2 |
106
.pnp.cjs
generated
106
.pnp.cjs
generated
@@ -3326,10 +3326,10 @@ const RAW_RUNTIME_STATE =
|
||||
}]\
|
||||
]],\
|
||||
["@hexagon/base64", [\
|
||||
["npm:1.1.26", {\
|
||||
"packageLocation": "./.yarn/cache/@hexagon-base64-npm-1.1.26-dbfda05df8-e42582ed12.zip/node_modules/@hexagon/base64/",\
|
||||
["npm:1.1.27", {\
|
||||
"packageLocation": "./.yarn/cache/@hexagon-base64-npm-1.1.27-df6f264962-899fffaf54.zip/node_modules/@hexagon/base64/",\
|
||||
"packageDependencies": [\
|
||||
["@hexagon/base64", "npm:1.1.26"]\
|
||||
["@hexagon/base64", "npm:1.1.27"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
@@ -5067,44 +5067,29 @@ const RAW_RUNTIME_STATE =
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["@simplewebauthn/iso-webcrypto", [\
|
||||
["npm:7.2.0", {\
|
||||
"packageLocation": "./.yarn/cache/@simplewebauthn-iso-webcrypto-npm-7.2.0-db7b12b859-b57899d0ad.zip/node_modules/@simplewebauthn/iso-webcrypto/",\
|
||||
"packageDependencies": [\
|
||||
["@simplewebauthn/iso-webcrypto", "npm:7.2.0"],\
|
||||
["@simplewebauthn/typescript-types", "npm:7.0.0"],\
|
||||
["@types/node", "npm:18.16.16"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["@simplewebauthn/server", [\
|
||||
["npm:7.2.0", {\
|
||||
"packageLocation": "./.yarn/cache/@simplewebauthn-server-npm-7.2.0-f1ed5fde8a-2e37c87edd.zip/node_modules/@simplewebauthn/server/",\
|
||||
["npm:8.1.1", {\
|
||||
"packageLocation": "./.yarn/cache/@simplewebauthn-server-npm-8.1.1-106d3bd108-a07c2a067b.zip/node_modules/@simplewebauthn/server/",\
|
||||
"packageDependencies": [\
|
||||
["@simplewebauthn/server", "npm:7.2.0"],\
|
||||
["@hexagon/base64", "npm:1.1.26"],\
|
||||
["@simplewebauthn/server", "npm:8.1.1"],\
|
||||
["@hexagon/base64", "npm:1.1.27"],\
|
||||
["@peculiar/asn1-android", "npm:2.3.6"],\
|
||||
["@peculiar/asn1-ecc", "npm:2.3.6"],\
|
||||
["@peculiar/asn1-rsa", "npm:2.3.6"],\
|
||||
["@peculiar/asn1-schema", "npm:2.3.6"],\
|
||||
["@peculiar/asn1-x509", "npm:2.3.6"],\
|
||||
["@simplewebauthn/iso-webcrypto", "npm:7.2.0"],\
|
||||
["@simplewebauthn/typescript-types", "npm:7.0.0"],\
|
||||
["@types/debug", "npm:4.1.8"],\
|
||||
["@types/node", "npm:18.16.16"],\
|
||||
["cbor-x", "npm:1.5.3"],\
|
||||
["cross-fetch", "npm:3.1.6"],\
|
||||
["debug", "virtual:ac3d8e680759ce54399273724d44e041d6c9b73454d191d411a8c44bb27e22f02aaf6ed9d3ad0ac1c298eac4833cff369c9c7b84c573016112c4f84be2cd8543#npm:4.3.4"]\
|
||||
["@simplewebauthn/typescript-types", "npm:8.0.0"],\
|
||||
["cbor-x", "npm:1.5.4"],\
|
||||
["cross-fetch", "npm:4.0.0"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["@simplewebauthn/typescript-types", [\
|
||||
["npm:7.0.0", {\
|
||||
"packageLocation": "./.yarn/cache/@simplewebauthn-typescript-types-npm-7.0.0-cc6ca20415-124238ea18.zip/node_modules/@simplewebauthn/typescript-types/",\
|
||||
["npm:8.0.0", {\
|
||||
"packageLocation": "./.yarn/cache/@simplewebauthn-typescript-types-npm-8.0.0-f3b313c27b-21e0b13268.zip/node_modules/@simplewebauthn/typescript-types/",\
|
||||
"packageDependencies": [\
|
||||
["@simplewebauthn/typescript-types", "npm:7.0.0"]\
|
||||
["@simplewebauthn/typescript-types", "npm:8.0.0"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
@@ -5864,8 +5849,8 @@ const RAW_RUNTIME_STATE =
|
||||
["@cbor-extract/cbor-extract-linux-arm64", "npm:2.1.1"],\
|
||||
["@cbor-extract/cbor-extract-linux-x64", "npm:2.1.1"],\
|
||||
["@newrelic/winston-enricher", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:4.0.1"],\
|
||||
["@simplewebauthn/server", "npm:7.2.0"],\
|
||||
["@simplewebauthn/typescript-types", "npm:7.0.0"],\
|
||||
["@simplewebauthn/server", "npm:8.1.1"],\
|
||||
["@simplewebauthn/typescript-types", "npm:8.0.0"],\
|
||||
["@standardnotes/api", "npm:1.26.26"],\
|
||||
["@standardnotes/common", "workspace:packages/common"],\
|
||||
["@standardnotes/domain-core", "workspace:packages/domain-core"],\
|
||||
@@ -6720,16 +6705,6 @@ const RAW_RUNTIME_STATE =
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["@types/debug", [\
|
||||
["npm:4.1.8", {\
|
||||
"packageLocation": "./.yarn/cache/@types-debug-npm-4.1.8-a04e2ca136-9c190e8129.zip/node_modules/@types/debug/",\
|
||||
"packageDependencies": [\
|
||||
["@types/debug", "npm:4.1.8"],\
|
||||
["@types/ms", "npm:0.7.31"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["@types/dotenv", [\
|
||||
["npm:8.2.0", {\
|
||||
"packageLocation": "./.yarn/cache/@types-dotenv-npm-8.2.0-f4d0e3d65b-13f90a36f7.zip/node_modules/@types/dotenv/",\
|
||||
@@ -6956,15 +6931,6 @@ const RAW_RUNTIME_STATE =
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["@types/ms", [\
|
||||
["npm:0.7.31", {\
|
||||
"packageLocation": "./.yarn/cache/@types-ms-npm-0.7.31-ea3b89342b-cccb52777b.zip/node_modules/@types/ms/",\
|
||||
"packageDependencies": [\
|
||||
["@types/ms", "npm:0.7.31"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["@types/newrelic", [\
|
||||
["npm:9.14.0", {\
|
||||
"packageLocation": "./.yarn/cache/@types-newrelic-npm-9.14.0-4668da51a1-2ec951bd8f.zip/node_modules/@types/newrelic/",\
|
||||
@@ -6982,13 +6948,6 @@ const RAW_RUNTIME_STATE =
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}],\
|
||||
["npm:18.16.16", {\
|
||||
"packageLocation": "./.yarn/cache/@types-node-npm-18.16.16-8a41330dc3-946bd4d8e6.zip/node_modules/@types/node/",\
|
||||
"packageDependencies": [\
|
||||
["@types/node", "npm:18.16.16"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}],\
|
||||
["npm:20.2.5", {\
|
||||
"packageLocation": "./.yarn/cache/@types-node-npm-20.2.5-0014d2d9ce-55e4f8d08e.zip/node_modules/@types/node/",\
|
||||
"packageDependencies": [\
|
||||
@@ -8706,10 +8665,10 @@ const RAW_RUNTIME_STATE =
|
||||
}]\
|
||||
]],\
|
||||
["cbor-x", [\
|
||||
["npm:1.5.3", {\
|
||||
"packageLocation": "./.yarn/cache/cbor-x-npm-1.5.3-1d452dd267-d4df85b339.zip/node_modules/cbor-x/",\
|
||||
["npm:1.5.4", {\
|
||||
"packageLocation": "./.yarn/cache/cbor-x-npm-1.5.4-2d5a649a4b-742aea498a.zip/node_modules/cbor-x/",\
|
||||
"packageDependencies": [\
|
||||
["cbor-x", "npm:1.5.3"],\
|
||||
["cbor-x", "npm:1.5.4"],\
|
||||
["cbor-extract", "npm:2.1.1"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
@@ -9434,11 +9393,11 @@ const RAW_RUNTIME_STATE =
|
||||
}]\
|
||||
]],\
|
||||
["cross-fetch", [\
|
||||
["npm:3.1.6", {\
|
||||
"packageLocation": "./.yarn/cache/cross-fetch-npm-3.1.6-cdb982d446-a8989fca82.zip/node_modules/cross-fetch/",\
|
||||
["npm:4.0.0", {\
|
||||
"packageLocation": "./.yarn/cache/cross-fetch-npm-4.0.0-9c67668db4-30e86b703a.zip/node_modules/cross-fetch/",\
|
||||
"packageDependencies": [\
|
||||
["cross-fetch", "npm:3.1.6"],\
|
||||
["node-fetch", "virtual:0f92dfe7f9dc4fd492639d4a5b7805c2b27442bf599fd4f370b22a7966ba078f5d4525e2a8e8af29369f20e1833ed084bd52be59679efaa6c1c6c10cdbcd8baa#npm:2.6.11"]\
|
||||
["cross-fetch", "npm:4.0.0"],\
|
||||
["node-fetch", "virtual:9c67668db478e95ba4d6a763bc55027eeff0d22eaf59478017ea07386fc33a3c7b7b625af78aa86a33991a9a500a7aa216e28632de568f02adefd662ef53a42d#npm:2.7.0"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
@@ -14174,6 +14133,13 @@ const RAW_RUNTIME_STATE =
|
||||
],\
|
||||
"linkType": "SOFT"\
|
||||
}],\
|
||||
["npm:2.7.0", {\
|
||||
"packageLocation": "./.yarn/cache/node-fetch-npm-2.7.0-587d57004e-a3ad788903.zip/node_modules/node-fetch/",\
|
||||
"packageDependencies": [\
|
||||
["node-fetch", "npm:2.7.0"]\
|
||||
],\
|
||||
"linkType": "SOFT"\
|
||||
}],\
|
||||
["npm:3.3.1", {\
|
||||
"packageLocation": "./.yarn/cache/node-fetch-npm-3.3.1-576511fc5a-1d0c635bdf.zip/node_modules/node-fetch/",\
|
||||
"packageDependencies": [\
|
||||
@@ -14197,6 +14163,20 @@ const RAW_RUNTIME_STATE =
|
||||
"encoding"\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}],\
|
||||
["virtual:9c67668db478e95ba4d6a763bc55027eeff0d22eaf59478017ea07386fc33a3c7b7b625af78aa86a33991a9a500a7aa216e28632de568f02adefd662ef53a42d#npm:2.7.0", {\
|
||||
"packageLocation": "./.yarn/__virtual__/node-fetch-virtual-0ec1497d1c/0/cache/node-fetch-npm-2.7.0-587d57004e-a3ad788903.zip/node_modules/node-fetch/",\
|
||||
"packageDependencies": [\
|
||||
["node-fetch", "virtual:9c67668db478e95ba4d6a763bc55027eeff0d22eaf59478017ea07386fc33a3c7b7b625af78aa86a33991a9a500a7aa216e28632de568f02adefd662ef53a42d#npm:2.7.0"],\
|
||||
["@types/encoding", null],\
|
||||
["encoding", null],\
|
||||
["whatwg-url", "npm:5.0.0"]\
|
||||
],\
|
||||
"packagePeers": [\
|
||||
"@types/encoding",\
|
||||
"encoding"\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["node-gyp", [\
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
.yarn/cache/@simplewebauthn-server-npm-8.1.1-106d3bd108-a07c2a067b.zip
vendored
Normal file
BIN
.yarn/cache/@simplewebauthn-server-npm-8.1.1-106d3bd108-a07c2a067b.zip
vendored
Normal file
Binary file not shown.
Binary file not shown.
BIN
.yarn/cache/@simplewebauthn-typescript-types-npm-8.0.0-f3b313c27b-21e0b13268.zip
vendored
Normal file
BIN
.yarn/cache/@simplewebauthn-typescript-types-npm-8.0.0-f3b313c27b-21e0b13268.zip
vendored
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
.yarn/cache/cbor-x-npm-1.5.4-2d5a649a4b-742aea498a.zip
vendored
Normal file
BIN
.yarn/cache/cbor-x-npm-1.5.4-2d5a649a4b-742aea498a.zip
vendored
Normal file
Binary file not shown.
Binary file not shown.
BIN
.yarn/cache/cross-fetch-npm-4.0.0-9c67668db4-30e86b703a.zip
vendored
Normal file
BIN
.yarn/cache/cross-fetch-npm-4.0.0-9c67668db4-30e86b703a.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/node-fetch-npm-2.7.0-587d57004e-a3ad788903.zip
vendored
Normal file
BIN
.yarn/cache/node-fetch-npm-2.7.0-587d57004e-a3ad788903.zip
vendored
Normal file
Binary file not shown.
@@ -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.13](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.26.12...@standardnotes/analytics@2.26.13) (2023-09-15)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/analytics
|
||||
|
||||
## [2.26.12](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.26.11...@standardnotes/analytics@2.26.12) (2023-09-13)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/analytics
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/analytics",
|
||||
"version": "2.26.12",
|
||||
"version": "2.26.13",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,16 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.74.11](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.74.10...@standardnotes/api-gateway@1.74.11) (2023-09-15)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||
|
||||
## [1.74.10](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.74.9...@standardnotes/api-gateway@1.74.10) (2023-09-13)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* adjust transition timestamps to be universal ([c7807d0](https://github.com/standardnotes/api-gateway/commit/c7807d0f9e69ce572c4c03ff606375d706f24d9f))
|
||||
|
||||
## [1.74.9](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.74.8...@standardnotes/api-gateway@1.74.9) (2023-09-13)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/api-gateway",
|
||||
"version": "1.74.9",
|
||||
"version": "1.74.11",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -80,15 +80,6 @@ export class UsersController extends BaseHttpController {
|
||||
)
|
||||
}
|
||||
|
||||
@httpGet('/transition-status', TYPES.ApiGateway_RequiredCrossServiceTokenMiddleware)
|
||||
async getTransitionStatus(request: Request, response: Response): Promise<void> {
|
||||
await this.httpService.callAuthServer(
|
||||
request,
|
||||
response,
|
||||
this.endpointResolver.resolveEndpointOrMethodIdentifier('GET', 'users/transition-status'),
|
||||
)
|
||||
}
|
||||
|
||||
@httpGet('/:userId/params', TYPES.ApiGateway_RequiredCrossServiceTokenMiddleware)
|
||||
async getKeyParams(request: Request, response: Response): Promise<void> {
|
||||
await this.httpService.callAuthServer(
|
||||
|
||||
@@ -43,7 +43,6 @@ export class EndpointResolver implements EndpointResolverInterface {
|
||||
['[PATCH]:users/:userId', 'auth.users.update'],
|
||||
['[PUT]:users/:userUuid/attributes/credentials', 'auth.users.updateCredentials'],
|
||||
['[GET]:users/params', 'auth.users.getKeyParams'],
|
||||
['[GET]:users/transition-status', 'auth.users.transition-status'],
|
||||
['[DELETE]:users/:userUuid', 'auth.users.delete'],
|
||||
['[POST]:listed', 'auth.users.createListedAccount'],
|
||||
['[POST]:auth', 'auth.users.register'],
|
||||
@@ -59,13 +58,11 @@ export class EndpointResolver implements EndpointResolverInterface {
|
||||
// Syncing Server
|
||||
['[POST]:items/sync', 'sync.items.sync'],
|
||||
['[POST]:items/check-integrity', 'sync.items.check_integrity'],
|
||||
['[POST]:items/transition', 'sync.items.transition'],
|
||||
['[GET]:items/:uuid', 'sync.items.get_item'],
|
||||
// Revisions Controller V2
|
||||
['[GET]:items/:itemUuid/revisions', 'revisions.revisions.getRevisions'],
|
||||
['[GET]:items/:itemUuid/revisions/:id', 'revisions.revisions.getRevision'],
|
||||
['[DELETE]:items/:itemUuid/revisions/:id', 'revisions.revisions.deleteRevision'],
|
||||
['[POST]:revisions/transition', 'revisions.revisions.transition'],
|
||||
// Messages Controller
|
||||
['[GET]:messages/', 'sync.messages.get-received'],
|
||||
['[GET]:messages/outbound', 'sync.messages.get-sent'],
|
||||
|
||||
@@ -3,6 +3,49 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.142.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.142.0...@standardnotes/auth-server@1.142.1) (2023-09-15)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add debug logs for updating transition status on auth ([0477507](https://github.com/standardnotes/server/commit/0477507a6a951dc2bb904711c870e2c40a0664bd))
|
||||
|
||||
# [1.142.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.141.14...@standardnotes/auth-server@1.142.0) (2023-09-15)
|
||||
|
||||
### Features
|
||||
|
||||
* add skipping verified transitions ([#827](https://github.com/standardnotes/server/issues/827)) ([d4d4945](https://github.com/standardnotes/server/commit/d4d49454a68de0acdf440dc202fa14b9743905f6))
|
||||
|
||||
## [1.141.14](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.141.13...@standardnotes/auth-server@1.141.14) (2023-09-15)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** remove extensive logs from updating transitions ([2a1859e](https://github.com/standardnotes/server/commit/2a1859e4beff4cc7c4348ebbff8357a8e061bf5e))
|
||||
* **auth:** upgrade simplewebauthn dependency ([#826](https://github.com/standardnotes/server/issues/826)) ([dd9a9c6](https://github.com/standardnotes/server/commit/dd9a9c68cb431a61700a8e5d8247eb1b505a9d62))
|
||||
|
||||
## [1.141.13](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.141.12...@standardnotes/auth-server@1.141.13) (2023-09-14)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** set ttl for started and not picked up transitions to 10h ([fe8ca82](https://github.com/standardnotes/server/commit/fe8ca828fb37306e0e5056627e67366885e86861))
|
||||
|
||||
## [1.141.12](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.141.11...@standardnotes/auth-server@1.141.12) (2023-09-13)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** remove re-triggering revisions transition ([5984e4c](https://github.com/standardnotes/server/commit/5984e4c3e7e550e5ed53805bde1e6dabcbe54da8))
|
||||
|
||||
## [1.141.11](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.141.10...@standardnotes/auth-server@1.141.11) (2023-09-13)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** passing transition timestamp ([c164bde](https://github.com/standardnotes/server/commit/c164bde847b5974e74fd439f0d439526ad439443))
|
||||
|
||||
## [1.141.10](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.141.9...@standardnotes/auth-server@1.141.10) (2023-09-13)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* adjust transition timestamps to be universal ([c7807d0](https://github.com/standardnotes/server/commit/c7807d0f9e69ce572c4c03ff606375d706f24d9f))
|
||||
|
||||
## [1.141.9](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.141.8...@standardnotes/auth-server@1.141.9) (2023-09-13)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -10,31 +10,52 @@ import { Env } from '../src/Bootstrap/Env'
|
||||
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
|
||||
import { DomainEventFactoryInterface } from '../src/Domain/Event/DomainEventFactoryInterface'
|
||||
import { UserRepositoryInterface } from '../src/Domain/User/UserRepositoryInterface'
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
import { TransitionStatusRepositoryInterface } from '../src/Domain/Transition/TransitionStatusRepositoryInterface'
|
||||
import { RoleName } from '@standardnotes/domain-core'
|
||||
|
||||
const inputArgs = process.argv.slice(2)
|
||||
const startDateString = inputArgs[0]
|
||||
const endDateString = inputArgs[1]
|
||||
|
||||
const requestTransition = async (
|
||||
userRepository: UserRepositoryInterface,
|
||||
transitionStatusRepository: TransitionStatusRepositoryInterface,
|
||||
userRepository: UserRepositoryInterface,
|
||||
logger: Logger,
|
||||
domainEventFactory: DomainEventFactoryInterface,
|
||||
domainEventPublisher: DomainEventPublisherInterface,
|
||||
timer: TimerInterface,
|
||||
): Promise<void> => {
|
||||
const startDate = new Date(startDateString)
|
||||
const endDate = new Date(endDateString)
|
||||
|
||||
const users = await userRepository.findAllCreatedBetween(startDate, endDate)
|
||||
|
||||
logger.info(`Found ${users.length} users created between ${startDateString} and ${endDateString}`)
|
||||
const timestamp = timer.getTimestampInMicroseconds()
|
||||
|
||||
logger.info(
|
||||
`[TRANSITION ${timestamp}] Found ${users.length} users created between ${startDateString} and ${endDateString}`,
|
||||
)
|
||||
|
||||
let usersTriggered = 0
|
||||
for (const user of users) {
|
||||
const itemsTransitionStatus = await transitionStatusRepository.getStatus(user.uuid, 'items')
|
||||
const revisionsTransitionStatus = await transitionStatusRepository.getStatus(user.uuid, 'revisions')
|
||||
|
||||
const userRoles = await user.roles
|
||||
|
||||
const userHasTransitionRole = userRoles.some((role) => role.name === RoleName.NAMES.TransitionUser)
|
||||
const bothTransitionStatusesAreVerified =
|
||||
itemsTransitionStatus === 'VERIFIED' && revisionsTransitionStatus === 'VERIFIED'
|
||||
|
||||
if (userHasTransitionRole && bothTransitionStatusesAreVerified) {
|
||||
continue
|
||||
}
|
||||
|
||||
const transitionRequestedEvent = domainEventFactory.createTransitionRequestedEvent({
|
||||
userUuid: user.uuid,
|
||||
type: 'items',
|
||||
timestamp,
|
||||
})
|
||||
|
||||
usersTriggered += 1
|
||||
@@ -43,22 +64,8 @@ const requestTransition = async (
|
||||
}
|
||||
|
||||
logger.info(
|
||||
`Triggered transition for ${usersTriggered} users created between ${startDateString} and ${endDateString}`,
|
||||
`[TRANSITION ${timestamp}] Triggered transition for ${usersTriggered} users created between ${startDateString} and ${endDateString}`,
|
||||
)
|
||||
|
||||
const revisionStatuses = await transitionStatusRepository.getStatuses('revisions')
|
||||
const failedStatuses = revisionStatuses.filter((status) => status.status === 'FAILED')
|
||||
|
||||
logger.info(`Found ${failedStatuses.length} failed revision transitions`)
|
||||
|
||||
for (const status of failedStatuses) {
|
||||
const transitionRequestedEvent = domainEventFactory.createTransitionRequestedEvent({
|
||||
userUuid: status.userUuid,
|
||||
type: 'revisions',
|
||||
})
|
||||
|
||||
await domainEventPublisher.publish(transitionRequestedEvent)
|
||||
}
|
||||
}
|
||||
|
||||
const container = new ContainerConfigLoader('worker')
|
||||
@@ -75,12 +82,20 @@ void container.load().then((container) => {
|
||||
const userRepository: UserRepositoryInterface = container.get(TYPES.Auth_UserRepository)
|
||||
const domainEventFactory: DomainEventFactoryInterface = container.get(TYPES.Auth_DomainEventFactory)
|
||||
const domainEventPublisher: DomainEventPublisherInterface = container.get(TYPES.Auth_DomainEventPublisher)
|
||||
const transitionStatusRepository: TransitionStatusRepositoryInterface = container.get(
|
||||
const timer = container.get<TimerInterface>(TYPES.Auth_Timer)
|
||||
const transitionStatusRepository = container.get<TransitionStatusRepositoryInterface>(
|
||||
TYPES.Auth_TransitionStatusRepository,
|
||||
)
|
||||
|
||||
Promise.resolve(
|
||||
requestTransition(userRepository, transitionStatusRepository, logger, domainEventFactory, domainEventPublisher),
|
||||
requestTransition(
|
||||
transitionStatusRepository,
|
||||
userRepository,
|
||||
logger,
|
||||
domainEventFactory,
|
||||
domainEventPublisher,
|
||||
timer,
|
||||
),
|
||||
)
|
||||
.then(() => {
|
||||
logger.info(`Finished transition request for users created between ${startDateString} and ${endDateString}`)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/auth-server",
|
||||
"version": "1.141.9",
|
||||
"version": "1.142.1",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
@@ -40,8 +40,8 @@
|
||||
"@aws-sdk/client-sqs": "^3.332.0",
|
||||
"@cbor-extract/cbor-extract-linux-arm64": "^2.1.1",
|
||||
"@cbor-extract/cbor-extract-linux-x64": "^2.1.1",
|
||||
"@simplewebauthn/server": "^7.2.0",
|
||||
"@simplewebauthn/typescript-types": "^7.0.0",
|
||||
"@simplewebauthn/server": "^8.1.1",
|
||||
"@simplewebauthn/typescript-types": "^8.0.0",
|
||||
"@standardnotes/api": "^1.26.26",
|
||||
"@standardnotes/common": "workspace:*",
|
||||
"@standardnotes/domain-core": "workspace:^",
|
||||
|
||||
@@ -263,7 +263,6 @@ import { RedisTransitionStatusRepository } from '../Infra/Redis/RedisTransitionS
|
||||
import { InMemoryTransitionStatusRepository } from '../Infra/InMemory/InMemoryTransitionStatusRepository'
|
||||
import { TransitionStatusUpdatedEventHandler } from '../Domain/Handler/TransitionStatusUpdatedEventHandler'
|
||||
import { UpdateTransitionStatus } from '../Domain/UseCase/UpdateTransitionStatus/UpdateTransitionStatus'
|
||||
import { GetTransitionStatus } from '../Domain/UseCase/GetTransitionStatus/GetTransitionStatus'
|
||||
import { TypeORMSharedVaultUser } from '../Infra/TypeORM/TypeORMSharedVaultUser'
|
||||
import { SharedVaultUserPersistenceMapper } from '../Mapping/SharedVaultUserPersistenceMapper'
|
||||
import { SharedVaultUserRepositoryInterface } from '../Domain/SharedVault/SharedVaultUserRepositoryInterface'
|
||||
@@ -946,14 +945,6 @@ export class ContainerConfigLoader {
|
||||
container.get<winston.Logger>(TYPES.Auth_Logger),
|
||||
),
|
||||
)
|
||||
container
|
||||
.bind<GetTransitionStatus>(TYPES.Auth_GetTransitionStatus)
|
||||
.toConstantValue(
|
||||
new GetTransitionStatus(
|
||||
container.get<TransitionStatusRepositoryInterface>(TYPES.Auth_TransitionStatusRepository),
|
||||
container.get<UserRepositoryInterface>(TYPES.Auth_UserRepository),
|
||||
),
|
||||
)
|
||||
container
|
||||
.bind<AddSharedVaultUser>(TYPES.Auth_AddSharedVaultUser)
|
||||
.toConstantValue(
|
||||
@@ -1276,7 +1267,6 @@ export class ContainerConfigLoader {
|
||||
container.get<ClearLoginAttempts>(TYPES.Auth_ClearLoginAttempts),
|
||||
container.get<IncreaseLoginAttempts>(TYPES.Auth_IncreaseLoginAttempts),
|
||||
container.get<ChangeCredentials>(TYPES.Auth_ChangeCredentials),
|
||||
container.get<GetTransitionStatus>(TYPES.Auth_GetTransitionStatus),
|
||||
container.get<ControllerContainerInterface>(TYPES.Auth_ControllerContainer),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -159,7 +159,6 @@ const TYPES = {
|
||||
Auth_GetUserKeyParamsRecovery: Symbol.for('Auth_GetUserKeyParamsRecovery'),
|
||||
Auth_UpdateStorageQuotaUsedForUser: Symbol.for('Auth_UpdateStorageQuotaUsedForUser'),
|
||||
Auth_UpdateTransitionStatus: Symbol.for('Auth_UpdateTransitionStatus'),
|
||||
Auth_GetTransitionStatus: Symbol.for('Auth_GetTransitionStatus'),
|
||||
Auth_AddSharedVaultUser: Symbol.for('Auth_AddSharedVaultUser'),
|
||||
Auth_RemoveSharedVaultUser: Symbol.for('Auth_RemoveSharedVaultUser'),
|
||||
// Handlers
|
||||
|
||||
@@ -33,7 +33,11 @@ import { DomainEventFactoryInterface } from './DomainEventFactoryInterface'
|
||||
export class DomainEventFactory implements DomainEventFactoryInterface {
|
||||
constructor(@inject(TYPES.Auth_Timer) private timer: TimerInterface) {}
|
||||
|
||||
createTransitionRequestedEvent(dto: { userUuid: string; type: 'items' | 'revisions' }): TransitionRequestedEvent {
|
||||
createTransitionRequestedEvent(dto: {
|
||||
userUuid: string
|
||||
type: 'items' | 'revisions'
|
||||
timestamp: number
|
||||
}): TransitionRequestedEvent {
|
||||
return {
|
||||
type: 'TRANSITION_REQUESTED',
|
||||
createdAt: this.timer.getUTCDate(),
|
||||
@@ -44,10 +48,7 @@ export class DomainEventFactory implements DomainEventFactoryInterface {
|
||||
},
|
||||
origin: DomainEventService.Auth,
|
||||
},
|
||||
payload: {
|
||||
timestamp: this.timer.getTimestampInMicroseconds(),
|
||||
...dto,
|
||||
},
|
||||
payload: dto,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -90,5 +90,9 @@ export interface DomainEventFactoryInterface {
|
||||
}): StatisticPersistenceRequestedEvent
|
||||
createSessionCreatedEvent(dto: { userUuid: string }): SessionCreatedEvent
|
||||
createSessionRefreshedEvent(dto: { userUuid: string }): SessionRefreshedEvent
|
||||
createTransitionRequestedEvent(dto: { userUuid: string; type: 'items' | 'revisions' }): TransitionRequestedEvent
|
||||
createTransitionRequestedEvent(dto: {
|
||||
userUuid: string
|
||||
type: 'items' | 'revisions'
|
||||
timestamp: number
|
||||
}): TransitionRequestedEvent
|
||||
}
|
||||
|
||||
@@ -2,14 +2,10 @@ export interface TransitionStatusRepositoryInterface {
|
||||
updateStatus(
|
||||
userUuid: string,
|
||||
transitionType: 'items' | 'revisions',
|
||||
status: 'STARTED' | 'IN_PROGRESS' | 'FAILED',
|
||||
status: 'STARTED' | 'IN_PROGRESS' | 'FINISHED' | 'FAILED' | 'VERIFIED',
|
||||
): Promise<void>
|
||||
removeStatus(userUuid: string, transitionType: 'items' | 'revisions'): Promise<void>
|
||||
getStatus(
|
||||
userUuid: string,
|
||||
transitionType: 'items' | 'revisions',
|
||||
): Promise<'STARTED' | 'IN_PROGRESS' | 'FAILED' | null>
|
||||
getStatuses(
|
||||
transitionType: 'items' | 'revisions',
|
||||
): Promise<Array<{ userUuid: string; status: 'STARTED' | 'IN_PROGRESS' | 'FAILED' }>>
|
||||
): Promise<'STARTED' | 'IN_PROGRESS' | 'FINISHED' | 'FAILED' | 'VERIFIED' | null>
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ export class GenerateAuthenticatorAuthenticationOptions
|
||||
.update(`u2f-selector-${dto.username}${this.pseudoKeyParamsKey}`)
|
||||
.digest('base64url')
|
||||
|
||||
const options = generateAuthenticationOptions({
|
||||
const options = await generateAuthenticationOptions({
|
||||
allowCredentials: [
|
||||
{
|
||||
id: Buffer.from(credentialIdHash),
|
||||
@@ -56,7 +56,7 @@ export class GenerateAuthenticatorAuthenticationOptions
|
||||
const userUuid = userUuidOrError.getValue()
|
||||
|
||||
const authenticators = await this.authenticatorRepository.findByUserUuid(userUuid)
|
||||
const options = generateAuthenticationOptions({
|
||||
const options = await generateAuthenticationOptions({
|
||||
allowCredentials: authenticators.map((authenticator) => ({
|
||||
id: authenticator.props.credentialId,
|
||||
type: 'public-key',
|
||||
|
||||
@@ -52,7 +52,7 @@ export class GenerateAuthenticatorRegistrationOptions
|
||||
}
|
||||
|
||||
const authenticators = await this.authenticatorRepository.findByUserUuid(userUuid)
|
||||
const options = generateRegistrationOptions({
|
||||
const options = await generateRegistrationOptions({
|
||||
rpID: this.relyingPartyId,
|
||||
rpName: this.relyingPartyName,
|
||||
userID: userUuid.value,
|
||||
|
||||
@@ -1,114 +0,0 @@
|
||||
import { RoleName } from '@standardnotes/domain-core'
|
||||
import { Role } from '../../Role/Role'
|
||||
import { TransitionStatusRepositoryInterface } from '../../Transition/TransitionStatusRepositoryInterface'
|
||||
import { User } from '../../User/User'
|
||||
import { UserRepositoryInterface } from '../../User/UserRepositoryInterface'
|
||||
import { GetTransitionStatus } from './GetTransitionStatus'
|
||||
|
||||
describe('GetTransitionStatus', () => {
|
||||
let transitionStatusRepository: TransitionStatusRepositoryInterface
|
||||
let userRepository: UserRepositoryInterface
|
||||
let user: User
|
||||
let role: Role
|
||||
|
||||
const createUseCase = () => new GetTransitionStatus(transitionStatusRepository, userRepository)
|
||||
|
||||
beforeEach(() => {
|
||||
transitionStatusRepository = {} as jest.Mocked<TransitionStatusRepositoryInterface>
|
||||
transitionStatusRepository.getStatus = jest.fn().mockReturnValue(null)
|
||||
|
||||
role = {} as jest.Mocked<Role>
|
||||
role.name = RoleName.NAMES.CoreUser
|
||||
|
||||
user = {
|
||||
uuid: '00000000-0000-0000-0000-000000000000',
|
||||
email: 'test@test.te',
|
||||
} as jest.Mocked<User>
|
||||
user.roles = Promise.resolve([role])
|
||||
|
||||
userRepository = {} as jest.Mocked<UserRepositoryInterface>
|
||||
userRepository.findOneByUuid = jest.fn().mockReturnValue(user)
|
||||
})
|
||||
|
||||
it('returns transition status FINISHED', async () => {
|
||||
role.name = RoleName.NAMES.TransitionUser
|
||||
user.roles = Promise.resolve([role])
|
||||
userRepository.findOneByUuid = jest.fn().mockReturnValue(user)
|
||||
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
transitionType: 'items',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBeFalsy()
|
||||
expect(result.getValue()).toEqual('FINISHED')
|
||||
})
|
||||
|
||||
it('returns transition status STARTED', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
transitionStatusRepository.getStatus = jest.fn().mockReturnValue('STARTED')
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
transitionType: 'items',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBeFalsy()
|
||||
expect(result.getValue()).toEqual('STARTED')
|
||||
})
|
||||
|
||||
it('returns transition status TO-DO', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
transitionType: 'items',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBeFalsy()
|
||||
expect(result.getValue()).toEqual('TO-DO')
|
||||
})
|
||||
|
||||
it('returns transition status FAILED', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
transitionStatusRepository.getStatus = jest.fn().mockReturnValue('FAILED')
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
transitionType: 'items',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBeFalsy()
|
||||
expect(result.getValue()).toEqual('FAILED')
|
||||
})
|
||||
|
||||
it('return error if user uuid is invalid', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: 'invalid',
|
||||
transitionType: 'items',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBeTruthy()
|
||||
expect(result.getError()).toEqual('Given value is not a valid uuid: invalid')
|
||||
})
|
||||
|
||||
it('return error if user not found', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
userRepository.findOneByUuid = jest.fn().mockReturnValue(null)
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
transitionType: 'items',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBeTruthy()
|
||||
expect(result.getError()).toEqual('User not found.')
|
||||
})
|
||||
})
|
||||
@@ -1,43 +0,0 @@
|
||||
import { Result, RoleName, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
|
||||
|
||||
import { GetTransitionStatusDTO } from './GetTransitionStatusDTO'
|
||||
import { UserRepositoryInterface } from '../../User/UserRepositoryInterface'
|
||||
import { TransitionStatusRepositoryInterface } from '../../Transition/TransitionStatusRepositoryInterface'
|
||||
|
||||
export class GetTransitionStatus
|
||||
implements UseCaseInterface<'TO-DO' | 'STARTED' | 'IN_PROGRESS' | 'FINISHED' | 'FAILED'>
|
||||
{
|
||||
constructor(
|
||||
private transitionStatusRepository: TransitionStatusRepositoryInterface,
|
||||
private userRepository: UserRepositoryInterface,
|
||||
) {}
|
||||
|
||||
async execute(
|
||||
dto: GetTransitionStatusDTO,
|
||||
): Promise<Result<'TO-DO' | 'STARTED' | 'IN_PROGRESS' | 'FINISHED' | 'FAILED'>> {
|
||||
const userUuidOrError = Uuid.create(dto.userUuid)
|
||||
if (userUuidOrError.isFailed()) {
|
||||
return Result.fail(userUuidOrError.getError())
|
||||
}
|
||||
const userUuid = userUuidOrError.getValue()
|
||||
|
||||
const user = await this.userRepository.findOneByUuid(userUuid)
|
||||
if (user === null) {
|
||||
return Result.fail('User not found.')
|
||||
}
|
||||
|
||||
const roles = await user.roles
|
||||
for (const role of roles) {
|
||||
if (role.name === RoleName.NAMES.TransitionUser) {
|
||||
return Result.ok('FINISHED')
|
||||
}
|
||||
}
|
||||
|
||||
const transitionStatus = await this.transitionStatusRepository.getStatus(userUuid.value, dto.transitionType)
|
||||
if (transitionStatus === null) {
|
||||
return Result.ok('TO-DO')
|
||||
}
|
||||
|
||||
return Result.ok(transitionStatus)
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
export interface GetTransitionStatusDTO {
|
||||
userUuid: string
|
||||
transitionType: 'items' | 'revisions'
|
||||
}
|
||||
@@ -17,66 +17,29 @@ describe('UpdateTransitionStatus', () => {
|
||||
logger.info = jest.fn()
|
||||
|
||||
transitionStatusRepository = {} as jest.Mocked<TransitionStatusRepositoryInterface>
|
||||
transitionStatusRepository.removeStatus = jest.fn()
|
||||
transitionStatusRepository.updateStatus = jest.fn()
|
||||
transitionStatusRepository.getStatuses = jest.fn().mockResolvedValue([
|
||||
{
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
status: 'STARTED',
|
||||
},
|
||||
{
|
||||
userUuid: '00000000-0000-0000-0000-000000000001',
|
||||
status: 'IN_PROGRESS',
|
||||
},
|
||||
{
|
||||
userUuid: '00000000-0000-0000-0000-000000000002',
|
||||
status: 'FAILED',
|
||||
},
|
||||
])
|
||||
|
||||
roleService = {} as jest.Mocked<RoleServiceInterface>
|
||||
roleService.addRoleToUser = jest.fn()
|
||||
})
|
||||
|
||||
it('should remove transition status and add TransitionUser role', async () => {
|
||||
it('should add TRANSITION_USER role', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
status: 'FINISHED',
|
||||
status: 'VERIFIED',
|
||||
transitionType: 'items',
|
||||
transitionTimestamp: 123,
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBeFalsy()
|
||||
expect(transitionStatusRepository.removeStatus).toHaveBeenCalledWith(
|
||||
'00000000-0000-0000-0000-000000000000',
|
||||
'items',
|
||||
)
|
||||
expect(roleService.addRoleToUser).toHaveBeenCalledWith(
|
||||
Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
RoleName.create(RoleName.NAMES.TransitionUser).getValue(),
|
||||
)
|
||||
})
|
||||
|
||||
it('should remove transition status', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
status: 'FINISHED',
|
||||
transitionType: 'revisions',
|
||||
transitionTimestamp: 123,
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBeFalsy()
|
||||
expect(transitionStatusRepository.removeStatus).toHaveBeenCalledWith(
|
||||
'00000000-0000-0000-0000-000000000000',
|
||||
'revisions',
|
||||
)
|
||||
expect(roleService.addRoleToUser).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should update transition status', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
|
||||
@@ -18,36 +18,14 @@ export class UpdateTransitionStatus implements UseCaseInterface<void> {
|
||||
}
|
||||
const userUuid = userUuidOrError.getValue()
|
||||
|
||||
if (dto.status !== 'FINISHED') {
|
||||
await this.transitionStatusRepository.updateStatus(dto.userUuid, dto.transitionType, dto.status)
|
||||
this.logger.info(`Received transition status updated event to ${dto.status} for user ${dto.userUuid}`)
|
||||
|
||||
return Result.ok()
|
||||
}
|
||||
await this.transitionStatusRepository.updateStatus(dto.userUuid, dto.transitionType, dto.status)
|
||||
|
||||
await this.transitionStatusRepository.removeStatus(dto.userUuid, dto.transitionType)
|
||||
|
||||
if (dto.transitionType === 'items') {
|
||||
if (dto.transitionType === 'items' && dto.status === 'VERIFIED') {
|
||||
await this.roleService.addRoleToUser(userUuid, RoleName.create(RoleName.NAMES.TransitionUser).getValue())
|
||||
}
|
||||
|
||||
const itemStatuses = await this.transitionStatusRepository.getStatuses('items')
|
||||
const itemsStartedStatusesCount = itemStatuses.filter((status) => status.status === 'STARTED').length
|
||||
const itemsInProgressStatusesCount = itemStatuses.filter((status) => status.status === 'IN_PROGRESS').length
|
||||
const itemsFailedStatusesCount = itemStatuses.filter((status) => status.status === 'FAILED').length
|
||||
|
||||
this.logger.info(
|
||||
`[TRANSITION ${dto.transitionTimestamp}] Items transition statuses: ${itemsStartedStatusesCount} started, ${itemsInProgressStatusesCount} in progress, ${itemsFailedStatusesCount} failed`,
|
||||
)
|
||||
|
||||
const revisionStatuses = await this.transitionStatusRepository.getStatuses('revisions')
|
||||
const revisionsStartedStatusesCount = revisionStatuses.filter((status) => status.status === 'STARTED').length
|
||||
const revisionsInProgressStatusesCount = revisionStatuses.filter((status) => status.status === 'IN_PROGRESS').length
|
||||
const revisionsFailedStatusesCount = revisionStatuses.filter((status) => status.status === 'FAILED').length
|
||||
|
||||
this.logger.info(
|
||||
`[TRANSITION ${dto.transitionTimestamp}] Revisions transition statuses: ${revisionsStartedStatusesCount} started, ${revisionsInProgressStatusesCount} in progress, ${revisionsFailedStatusesCount} failed`,
|
||||
)
|
||||
|
||||
return Result.ok()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,5 +2,5 @@ export interface UpdateTransitionStatusDTO {
|
||||
userUuid: string
|
||||
transitionType: 'items' | 'revisions'
|
||||
transitionTimestamp: number
|
||||
status: 'STARTED' | 'IN_PROGRESS' | 'FINISHED' | 'FAILED'
|
||||
status: 'STARTED' | 'IN_PROGRESS' | 'FINISHED' | 'FAILED' | 'VERIFIED'
|
||||
}
|
||||
|
||||
@@ -4,24 +4,6 @@ export class InMemoryTransitionStatusRepository implements TransitionStatusRepos
|
||||
private itemStatuses: Map<string, 'STARTED' | 'FAILED'> = new Map()
|
||||
private revisionStatuses: Map<string, 'STARTED' | 'FAILED'> = new Map()
|
||||
|
||||
async getStatuses(
|
||||
transitionType: 'items' | 'revisions',
|
||||
): Promise<{ userUuid: string; status: 'STARTED' | 'FAILED' | 'IN_PROGRESS' }[]> {
|
||||
const statuses: { userUuid: string; status: 'STARTED' | 'FAILED' | 'IN_PROGRESS' }[] = []
|
||||
|
||||
if (transitionType === 'items') {
|
||||
for (const [userUuid, status] of this.itemStatuses) {
|
||||
statuses.push({ userUuid, status })
|
||||
}
|
||||
} else {
|
||||
for (const [userUuid, status] of this.revisionStatuses) {
|
||||
statuses.push({ userUuid, status })
|
||||
}
|
||||
}
|
||||
|
||||
return statuses
|
||||
}
|
||||
|
||||
async updateStatus(
|
||||
userUuid: string,
|
||||
transitionType: 'items' | 'revisions',
|
||||
|
||||
@@ -14,7 +14,6 @@ import { IncreaseLoginAttempts } from '../../Domain/UseCase/IncreaseLoginAttempt
|
||||
import { InviteToSharedSubscription } from '../../Domain/UseCase/InviteToSharedSubscription/InviteToSharedSubscription'
|
||||
import { UpdateUser } from '../../Domain/UseCase/UpdateUser'
|
||||
import { User } from '../../Domain/User/User'
|
||||
import { GetTransitionStatus } from '../../Domain/UseCase/GetTransitionStatus/GetTransitionStatus'
|
||||
|
||||
describe('AnnotatedUsersController', () => {
|
||||
let updateUser: UpdateUser
|
||||
@@ -25,7 +24,6 @@ describe('AnnotatedUsersController', () => {
|
||||
let increaseLoginAttempts: IncreaseLoginAttempts
|
||||
let changeCredentials: ChangeCredentials
|
||||
let inviteToSharedSubscription: InviteToSharedSubscription
|
||||
let getTransitionStatus: GetTransitionStatus
|
||||
|
||||
let request: express.Request
|
||||
let response: express.Response
|
||||
@@ -40,7 +38,6 @@ describe('AnnotatedUsersController', () => {
|
||||
clearLoginAttempts,
|
||||
increaseLoginAttempts,
|
||||
changeCredentials,
|
||||
getTransitionStatus,
|
||||
)
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -72,9 +69,6 @@ describe('AnnotatedUsersController', () => {
|
||||
inviteToSharedSubscription = {} as jest.Mocked<InviteToSharedSubscription>
|
||||
inviteToSharedSubscription.execute = jest.fn()
|
||||
|
||||
getTransitionStatus = {} as jest.Mocked<GetTransitionStatus>
|
||||
getTransitionStatus.execute = jest.fn()
|
||||
|
||||
request = {
|
||||
headers: {},
|
||||
body: {},
|
||||
|
||||
@@ -18,7 +18,6 @@ import { ClearLoginAttempts } from '../../Domain/UseCase/ClearLoginAttempts'
|
||||
import { IncreaseLoginAttempts } from '../../Domain/UseCase/IncreaseLoginAttempts'
|
||||
import { ChangeCredentials } from '../../Domain/UseCase/ChangeCredentials/ChangeCredentials'
|
||||
import { BaseUsersController } from './Base/BaseUsersController'
|
||||
import { GetTransitionStatus } from '../../Domain/UseCase/GetTransitionStatus/GetTransitionStatus'
|
||||
|
||||
@controller('/users')
|
||||
export class AnnotatedUsersController extends BaseUsersController {
|
||||
@@ -30,7 +29,6 @@ export class AnnotatedUsersController extends BaseUsersController {
|
||||
@inject(TYPES.Auth_ClearLoginAttempts) override clearLoginAttempts: ClearLoginAttempts,
|
||||
@inject(TYPES.Auth_IncreaseLoginAttempts) override increaseLoginAttempts: IncreaseLoginAttempts,
|
||||
@inject(TYPES.Auth_ChangeCredentials) override changeCredentialsUseCase: ChangeCredentials,
|
||||
@inject(TYPES.Auth_GetTransitionStatus) override getTransitionStatusUseCase: GetTransitionStatus,
|
||||
) {
|
||||
super(
|
||||
updateUser,
|
||||
@@ -40,7 +38,6 @@ export class AnnotatedUsersController extends BaseUsersController {
|
||||
clearLoginAttempts,
|
||||
increaseLoginAttempts,
|
||||
changeCredentialsUseCase,
|
||||
getTransitionStatusUseCase,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -54,11 +51,6 @@ export class AnnotatedUsersController extends BaseUsersController {
|
||||
return super.keyParams(request)
|
||||
}
|
||||
|
||||
@httpGet('/transition-status', TYPES.Auth_RequiredCrossServiceTokenMiddleware)
|
||||
override async transitionStatus(request: Request, response: Response): Promise<results.JsonResult> {
|
||||
return super.transitionStatus(request, response)
|
||||
}
|
||||
|
||||
@httpDelete('/:userUuid', TYPES.Auth_RequiredCrossServiceTokenMiddleware)
|
||||
override async deleteAccount(request: Request, response: Response): Promise<results.JsonResult> {
|
||||
return super.deleteAccount(request, response)
|
||||
|
||||
@@ -10,7 +10,6 @@ import { GetUserSubscription } from '../../../Domain/UseCase/GetUserSubscription
|
||||
import { IncreaseLoginAttempts } from '../../../Domain/UseCase/IncreaseLoginAttempts'
|
||||
import { UpdateUser } from '../../../Domain/UseCase/UpdateUser'
|
||||
import { ErrorTag } from '@standardnotes/responses'
|
||||
import { GetTransitionStatus } from '../../../Domain/UseCase/GetTransitionStatus/GetTransitionStatus'
|
||||
|
||||
export class BaseUsersController extends BaseHttpController {
|
||||
constructor(
|
||||
@@ -21,7 +20,6 @@ export class BaseUsersController extends BaseHttpController {
|
||||
protected clearLoginAttempts: ClearLoginAttempts,
|
||||
protected increaseLoginAttempts: IncreaseLoginAttempts,
|
||||
protected changeCredentialsUseCase: ChangeCredentials,
|
||||
protected getTransitionStatusUseCase: GetTransitionStatus,
|
||||
private controllerContainer?: ControllerContainerInterface,
|
||||
) {
|
||||
super()
|
||||
@@ -32,7 +30,6 @@ export class BaseUsersController extends BaseHttpController {
|
||||
this.controllerContainer.register('auth.users.getSubscription', this.getSubscription.bind(this))
|
||||
this.controllerContainer.register('auth.users.updateCredentials', this.changeCredentials.bind(this))
|
||||
this.controllerContainer.register('auth.users.delete', this.deleteAccount.bind(this))
|
||||
this.controllerContainer.register('auth.users.transition-status', this.transitionStatus.bind(this))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,30 +103,6 @@ export class BaseUsersController extends BaseHttpController {
|
||||
return this.json(result.keyParams)
|
||||
}
|
||||
|
||||
async transitionStatus(_request: Request, response: Response): Promise<results.JsonResult> {
|
||||
const result = await this.getTransitionStatusUseCase.execute({
|
||||
userUuid: response.locals.user.uuid,
|
||||
transitionType: 'items',
|
||||
})
|
||||
|
||||
if (result.isFailed()) {
|
||||
return this.json(
|
||||
{
|
||||
error: {
|
||||
message: result.getError(),
|
||||
},
|
||||
},
|
||||
400,
|
||||
)
|
||||
}
|
||||
|
||||
response.setHeader('x-invalidate-cache', response.locals.user.uuid)
|
||||
|
||||
return this.json({
|
||||
status: result.getValue(),
|
||||
})
|
||||
}
|
||||
|
||||
async deleteAccount(request: Request, response: Response): Promise<results.JsonResult> {
|
||||
if (request.params.userUuid !== response.locals.user.uuid) {
|
||||
return this.json(
|
||||
|
||||
@@ -7,47 +7,35 @@ export class RedisTransitionStatusRepository implements TransitionStatusReposito
|
||||
|
||||
constructor(private redisClient: IORedis.Redis) {}
|
||||
|
||||
async getStatuses(
|
||||
transitionType: 'items' | 'revisions',
|
||||
): Promise<{ userUuid: string; status: 'STARTED' | 'IN_PROGRESS' | 'FAILED' }[]> {
|
||||
const keys = await this.redisClient.keys(`${this.PREFIX}:${transitionType}:*`)
|
||||
const statuses = await Promise.all(
|
||||
keys.map(async (key) => {
|
||||
const userUuid = key.split(':')[2]
|
||||
const status = (await this.redisClient.get(key)) as 'STARTED' | 'IN_PROGRESS' | 'FAILED'
|
||||
return { userUuid, status }
|
||||
}),
|
||||
)
|
||||
|
||||
return statuses
|
||||
}
|
||||
|
||||
async updateStatus(
|
||||
userUuid: string,
|
||||
transitionType: 'items' | 'revisions',
|
||||
status: 'STARTED' | 'IN_PROGRESS' | 'FAILED',
|
||||
status: 'STARTED' | 'IN_PROGRESS' | 'FINISHED' | 'FAILED' | 'VERIFIED',
|
||||
): Promise<void> {
|
||||
if (status === 'IN_PROGRESS') {
|
||||
await this.redisClient.setex(`${this.PREFIX}:${transitionType}:${userUuid}`, 7200, status)
|
||||
} else {
|
||||
await this.redisClient.set(`${this.PREFIX}:${transitionType}:${userUuid}`, status)
|
||||
switch (status) {
|
||||
case 'FAILED':
|
||||
case 'VERIFIED':
|
||||
await this.redisClient.set(`${this.PREFIX}:${transitionType}:${userUuid}`, status)
|
||||
break
|
||||
case 'IN_PROGRESS': {
|
||||
const ttl2Hourse = 7_200
|
||||
await this.redisClient.setex(`${this.PREFIX}:${transitionType}:${userUuid}`, ttl2Hourse, status)
|
||||
break
|
||||
}
|
||||
default: {
|
||||
const ttl10Hours = 36_000
|
||||
await this.redisClient.setex(`${this.PREFIX}:${transitionType}:${userUuid}`, ttl10Hours, status)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async removeStatus(userUuid: string, transitionType: 'items' | 'revisions'): Promise<void> {
|
||||
await this.redisClient.del(`${this.PREFIX}:${transitionType}:${userUuid}`)
|
||||
}
|
||||
|
||||
async getStatus(
|
||||
userUuid: string,
|
||||
transitionType: 'items' | 'revisions',
|
||||
): Promise<'STARTED' | 'IN_PROGRESS' | 'FAILED' | null> {
|
||||
const status = (await this.redisClient.get(`${this.PREFIX}:${transitionType}:${userUuid}`)) as
|
||||
| 'STARTED'
|
||||
| 'IN_PROGRESS'
|
||||
| 'FAILED'
|
||||
| null
|
||||
): Promise<'STARTED' | 'IN_PROGRESS' | 'FINISHED' | 'FAILED' | 'VERIFIED' | null> {
|
||||
const status = await this.redisClient.get(`${this.PREFIX}:${transitionType}:${userUuid}`)
|
||||
|
||||
return status
|
||||
return status as 'STARTED' | 'IN_PROGRESS' | 'FINISHED' | 'FAILED' | 'VERIFIED' | null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.29](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.12.28...@standardnotes/domain-events-infra@1.12.29) (2023-09-15)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/domain-events-infra
|
||||
|
||||
## [1.12.28](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.12.27...@standardnotes/domain-events-infra@1.12.28) (2023-09-13)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/domain-events-infra
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/domain-events-infra",
|
||||
"version": "1.12.28",
|
||||
"version": "1.12.29",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -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.126.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.125.4...@standardnotes/domain-events@2.126.0) (2023-09-15)
|
||||
|
||||
### Features
|
||||
|
||||
* add skipping verified transitions ([#827](https://github.com/standardnotes/server/issues/827)) ([d4d4945](https://github.com/standardnotes/server/commit/d4d49454a68de0acdf440dc202fa14b9743905f6))
|
||||
|
||||
## [2.125.4](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.125.3...@standardnotes/domain-events@2.125.4) (2023-09-13)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/domain-events",
|
||||
"version": "2.125.4",
|
||||
"version": "2.126.0",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -2,5 +2,5 @@ export interface TransitionStatusUpdatedEventPayload {
|
||||
userUuid: string
|
||||
transitionType: 'items' | 'revisions'
|
||||
transitionTimestamp: number
|
||||
status: 'STARTED' | 'IN_PROGRESS' | 'FINISHED' | 'FAILED'
|
||||
status: 'STARTED' | 'IN_PROGRESS' | 'FINISHED' | 'FAILED' | 'VERIFIED'
|
||||
}
|
||||
|
||||
@@ -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.41](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.11.40...@standardnotes/event-store@1.11.41) (2023-09-15)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/event-store
|
||||
|
||||
## [1.11.40](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.11.39...@standardnotes/event-store@1.11.40) (2023-09-13)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/event-store
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/event-store",
|
||||
"version": "1.11.40",
|
||||
"version": "1.11.41",
|
||||
"description": "Event Store Service",
|
||||
"private": true,
|
||||
"main": "dist/src/index.js",
|
||||
|
||||
@@ -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.20](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.22.19...@standardnotes/files-server@1.22.20) (2023-09-15)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files-server
|
||||
|
||||
## [1.22.19](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.22.18...@standardnotes/files-server@1.22.19) (2023-09-13)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files-server
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/files-server",
|
||||
"version": "1.22.19",
|
||||
"version": "1.22.20",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,38 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.15.57](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.56...@standardnotes/home-server@1.15.57) (2023-09-15)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.15.56](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.55...@standardnotes/home-server@1.15.56) (2023-09-15)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.15.55](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.54...@standardnotes/home-server@1.15.55) (2023-09-15)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.15.54](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.53...@standardnotes/home-server@1.15.54) (2023-09-14)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.15.53](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.52...@standardnotes/home-server@1.15.53) (2023-09-13)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.15.52](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.51...@standardnotes/home-server@1.15.52) (2023-09-13)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.15.51](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.50...@standardnotes/home-server@1.15.51) (2023-09-13)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.15.50](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.49...@standardnotes/home-server@1.15.50) (2023-09-13)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.15.49](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.48...@standardnotes/home-server@1.15.49) (2023-09-13)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/home-server",
|
||||
"version": "1.15.49",
|
||||
"version": "1.15.57",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,36 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.34.1](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.34.0...@standardnotes/revisions-server@1.34.1) (2023-09-15)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add debug logs for transition status updates ([3e7856c](https://github.com/standardnotes/server/commit/3e7856c895e73b775c8977c6c6e86dffd5755c00))
|
||||
|
||||
# [1.34.0](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.33.21...@standardnotes/revisions-server@1.34.0) (2023-09-15)
|
||||
|
||||
### Features
|
||||
|
||||
* add skipping verified transitions ([#827](https://github.com/standardnotes/server/issues/827)) ([d4d4945](https://github.com/standardnotes/server/commit/d4d49454a68de0acdf440dc202fa14b9743905f6))
|
||||
|
||||
## [1.33.21](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.33.20...@standardnotes/revisions-server@1.33.21) (2023-09-14)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* skip already updated items and revisions in integrity check ([#825](https://github.com/standardnotes/server/issues/825)) ([03a4a3f](https://github.com/standardnotes/server/commit/03a4a3f2abc0b4e09942ba39dbd227524068dfb6))
|
||||
|
||||
## [1.33.20](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.33.19...@standardnotes/revisions-server@1.33.20) (2023-09-13)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* adjust transition timestamps to be universal ([c7807d0](https://github.com/standardnotes/server/commit/c7807d0f9e69ce572c4c03ff606375d706f24d9f))
|
||||
|
||||
## [1.33.19](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.33.18...@standardnotes/revisions-server@1.33.19) (2023-09-13)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* include handling updated items in revisions in secondary ([fbcb45c](https://github.com/standardnotes/server/commit/fbcb45c3a23fde09702fae7bfcb409bdbb610191))
|
||||
|
||||
## [1.33.18](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.33.17...@standardnotes/revisions-server@1.33.18) (2023-09-13)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/revisions-server",
|
||||
"version": "1.33.18",
|
||||
"version": "1.34.1",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -520,9 +520,6 @@ export class ContainerConfigLoader {
|
||||
container.get<DeleteRevision>(TYPES.Revisions_DeleteRevision),
|
||||
container.get<RevisionHttpMapper>(TYPES.Revisions_RevisionHttpMapper),
|
||||
container.get<RevisionMetadataHttpMapper>(TYPES.Revisions_RevisionMetadataHttpMapper),
|
||||
container.get<TriggerTransitionFromPrimaryToSecondaryDatabaseForUser>(
|
||||
TYPES.Revisions_TriggerTransitionFromPrimaryToSecondaryDatabaseForUser,
|
||||
),
|
||||
container.get<ControllerContainerInterface>(TYPES.Revisions_ControllerContainer),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -9,7 +9,8 @@ export class DomainEventFactory implements DomainEventFactoryInterface {
|
||||
createTransitionStatusUpdatedEvent(dto: {
|
||||
userUuid: string
|
||||
transitionType: 'items' | 'revisions'
|
||||
status: 'STARTED' | 'FAILED' | 'FINISHED'
|
||||
transitionTimestamp: number
|
||||
status: 'STARTED' | 'IN_PROGRESS' | 'FAILED' | 'FINISHED' | 'VERIFIED'
|
||||
}): TransitionStatusUpdatedEvent {
|
||||
return {
|
||||
type: 'TRANSITION_STATUS_UPDATED',
|
||||
@@ -19,12 +20,9 @@ export class DomainEventFactory implements DomainEventFactoryInterface {
|
||||
userIdentifier: dto.userUuid,
|
||||
userIdentifierType: 'uuid',
|
||||
},
|
||||
origin: DomainEventService.SyncingServer,
|
||||
},
|
||||
payload: {
|
||||
transitionTimestamp: this.timer.getTimestampInMicroseconds(),
|
||||
...dto,
|
||||
origin: DomainEventService.Revisions,
|
||||
},
|
||||
payload: dto,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ export interface DomainEventFactoryInterface {
|
||||
createTransitionStatusUpdatedEvent(dto: {
|
||||
userUuid: string
|
||||
transitionType: 'items' | 'revisions'
|
||||
status: 'STARTED' | 'IN_PROGRESS' | 'FAILED' | 'FINISHED'
|
||||
transitionTimestamp: number
|
||||
status: 'STARTED' | 'IN_PROGRESS' | 'FAILED' | 'FINISHED' | 'VERIFIED'
|
||||
}): TransitionStatusUpdatedEvent
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ export class TransitionRequestedEventHandler implements DomainEventHandlerInterf
|
||||
|
||||
const result = await this.triggerTransitionFromPrimaryToSecondaryDatabaseForUser.execute({
|
||||
userUuid: event.payload.userUuid,
|
||||
transitionTimestamp: event.payload.timestamp,
|
||||
})
|
||||
|
||||
if (result.isFailed()) {
|
||||
|
||||
@@ -25,35 +25,30 @@ export class TransitionStatusUpdatedEventHandler implements DomainEventHandlerIn
|
||||
userUuid: event.payload.userUuid,
|
||||
status: 'STARTED',
|
||||
transitionType: 'revisions',
|
||||
transitionTimestamp: event.payload.transitionTimestamp,
|
||||
}),
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (event.payload.status === 'STARTED' && event.payload.transitionType === 'revisions') {
|
||||
const userUuidOrError = Uuid.create(event.payload.userUuid)
|
||||
if (userUuidOrError.isFailed()) {
|
||||
this.logger.error(
|
||||
`Failed to transition revisions for user ${event.payload.userUuid}: ${userUuidOrError.getError()}`,
|
||||
)
|
||||
await this.domainEventPublisher.publish(
|
||||
this.domainEventFactory.createTransitionStatusUpdatedEvent({
|
||||
userUuid: event.payload.userUuid,
|
||||
status: 'FAILED',
|
||||
transitionType: 'revisions',
|
||||
}),
|
||||
)
|
||||
this.logger.info(
|
||||
`Received transition status updated event to ${event.payload.status} for user ${event.payload.userUuid}`,
|
||||
)
|
||||
|
||||
if (event.payload.status === 'STARTED' && event.payload.transitionType === 'revisions') {
|
||||
const userUuid = await this.getUserUuidFromEvent(event)
|
||||
if (userUuid === null) {
|
||||
return
|
||||
}
|
||||
|
||||
if (await this.isAlreadyMigrated(userUuidOrError.getValue())) {
|
||||
if (await this.isAlreadyMigrated(userUuid)) {
|
||||
await this.domainEventPublisher.publish(
|
||||
this.domainEventFactory.createTransitionStatusUpdatedEvent({
|
||||
userUuid: event.payload.userUuid,
|
||||
status: 'FINISHED',
|
||||
transitionType: 'revisions',
|
||||
transitionTimestamp: event.payload.transitionTimestamp,
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -65,6 +60,7 @@ export class TransitionStatusUpdatedEventHandler implements DomainEventHandlerIn
|
||||
userUuid: event.payload.userUuid,
|
||||
status: 'IN_PROGRESS',
|
||||
transitionType: 'revisions',
|
||||
transitionTimestamp: event.payload.transitionTimestamp,
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -80,6 +76,7 @@ export class TransitionStatusUpdatedEventHandler implements DomainEventHandlerIn
|
||||
userUuid: event.payload.userUuid,
|
||||
status: 'FAILED',
|
||||
transitionType: 'revisions',
|
||||
transitionTimestamp: event.payload.transitionTimestamp,
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -91,11 +88,28 @@ export class TransitionStatusUpdatedEventHandler implements DomainEventHandlerIn
|
||||
userUuid: event.payload.userUuid,
|
||||
status: 'FINISHED',
|
||||
transitionType: 'revisions',
|
||||
transitionTimestamp: event.payload.transitionTimestamp,
|
||||
}),
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (event.payload.status === 'FINISHED' && event.payload.transitionType === 'revisions') {
|
||||
const userUuid = await this.getUserUuidFromEvent(event)
|
||||
if (userUuid === null) {
|
||||
return
|
||||
}
|
||||
|
||||
if (await this.isAlreadyMigrated(userUuid)) {
|
||||
this.domainEventFactory.createTransitionStatusUpdatedEvent({
|
||||
userUuid: event.payload.userUuid,
|
||||
status: 'VERIFIED',
|
||||
transitionType: 'revisions',
|
||||
transitionTimestamp: event.payload.transitionTimestamp,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async isAlreadyMigrated(userUuid: Uuid): Promise<boolean> {
|
||||
@@ -109,4 +123,25 @@ export class TransitionStatusUpdatedEventHandler implements DomainEventHandlerIn
|
||||
|
||||
return totalRevisionsCountForUserInPrimary === 0
|
||||
}
|
||||
|
||||
private async getUserUuidFromEvent(event: TransitionStatusUpdatedEvent): Promise<Uuid | null> {
|
||||
const userUuidOrError = Uuid.create(event.payload.userUuid)
|
||||
if (userUuidOrError.isFailed()) {
|
||||
this.logger.error(
|
||||
`Failed to transition revisions for user ${event.payload.userUuid}: ${userUuidOrError.getError()}`,
|
||||
)
|
||||
await this.domainEventPublisher.publish(
|
||||
this.domainEventFactory.createTransitionStatusUpdatedEvent({
|
||||
userUuid: event.payload.userUuid,
|
||||
status: 'FAILED',
|
||||
transitionType: 'revisions',
|
||||
transitionTimestamp: event.payload.transitionTimestamp,
|
||||
}),
|
||||
)
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
return userUuidOrError.getValue()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,9 +30,12 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
|
||||
const userUuid = userUuidOrError.getValue()
|
||||
|
||||
let newRevisionsInSecondaryCount = 0
|
||||
let updatedRevisionsInSecondary: Revision[] = []
|
||||
if (await this.hasAlreadyDataInSecondaryDatabase(userUuid)) {
|
||||
const newRevisions = await this.getNewRevisionsCreatedInSecondaryDatabase(userUuid)
|
||||
for (const existingRevision of newRevisions.alreadyExistingInPrimary) {
|
||||
const { alreadyExistingInPrimary, newRevisionsInSecondary, updatedInSecondary } =
|
||||
await this.getNewRevisionsCreatedInSecondaryDatabase(userUuid)
|
||||
|
||||
for (const existingRevision of alreadyExistingInPrimary) {
|
||||
this.logger.info(`Removing revision ${existingRevision.id.toString()} from secondary database`)
|
||||
await (this.secondRevisionsRepository as RevisionRepositoryInterface).removeOneByUuid(
|
||||
Uuid.create(existingRevision.id.toString()).getValue(),
|
||||
@@ -40,24 +43,34 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
|
||||
)
|
||||
}
|
||||
|
||||
if (newRevisions.newRevisionsInSecondary.length > 0) {
|
||||
if (newRevisionsInSecondary.length > 0) {
|
||||
this.logger.info(
|
||||
`Found ${newRevisions.newRevisionsInSecondary.length} new revisions in secondary database for user ${userUuid.value}`,
|
||||
`Found ${newRevisionsInSecondary.length} new revisions in secondary database for user ${userUuid.value}`,
|
||||
)
|
||||
}
|
||||
|
||||
newRevisionsInSecondaryCount = newRevisions.newRevisionsInSecondary.length
|
||||
newRevisionsInSecondaryCount = newRevisionsInSecondary.length
|
||||
|
||||
if (updatedInSecondary.length > 0) {
|
||||
this.logger.info(
|
||||
`Found ${updatedInSecondary.length} updated revisions in secondary database for user ${userUuid.value}`,
|
||||
)
|
||||
}
|
||||
|
||||
updatedRevisionsInSecondary = updatedInSecondary
|
||||
}
|
||||
|
||||
const updatedRevisionsInSecondaryCount = updatedRevisionsInSecondary.length
|
||||
|
||||
await this.allowForSecondaryDatabaseToCatchUp()
|
||||
|
||||
const migrationTimeStart = this.timer.getTimestampInMicroseconds()
|
||||
|
||||
this.logger.debug(`Transitioning revisions for user ${userUuid.value}`)
|
||||
|
||||
const migrationResult = await this.migrateRevisionsForUser(userUuid)
|
||||
const migrationResult = await this.migrateRevisionsForUser(userUuid, updatedRevisionsInSecondary)
|
||||
if (migrationResult.isFailed()) {
|
||||
if (newRevisionsInSecondaryCount === 0) {
|
||||
if (newRevisionsInSecondaryCount === 0 && updatedRevisionsInSecondaryCount === 0) {
|
||||
const cleanupResult = await this.deleteRevisionsForUser(userUuid, this.secondRevisionsRepository)
|
||||
if (cleanupResult.isFailed()) {
|
||||
this.logger.error(
|
||||
@@ -74,9 +87,10 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
|
||||
const integrityCheckResult = await this.checkIntegrityBetweenPrimaryAndSecondaryDatabase(
|
||||
userUuid,
|
||||
newRevisionsInSecondaryCount,
|
||||
updatedRevisionsInSecondary,
|
||||
)
|
||||
if (integrityCheckResult.isFailed()) {
|
||||
if (newRevisionsInSecondaryCount === 0) {
|
||||
if (newRevisionsInSecondaryCount === 0 && updatedRevisionsInSecondaryCount === 0) {
|
||||
const cleanupResult = await this.deleteRevisionsForUser(userUuid, this.secondRevisionsRepository)
|
||||
if (cleanupResult.isFailed()) {
|
||||
this.logger.error(
|
||||
@@ -107,7 +121,10 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
|
||||
return Result.ok()
|
||||
}
|
||||
|
||||
private async migrateRevisionsForUser(userUuid: Uuid): Promise<Result<void>> {
|
||||
private async migrateRevisionsForUser(
|
||||
userUuid: Uuid,
|
||||
updatedRevisionsInSecondary: Revision[],
|
||||
): Promise<Result<void>> {
|
||||
try {
|
||||
const totalRevisionsCountForUser = await this.primaryRevisionsRepository.countByUserUuid(userUuid)
|
||||
let totalRevisionsCountTransitionedToSecondary = 0
|
||||
@@ -123,6 +140,18 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
|
||||
|
||||
for (const revision of revisions) {
|
||||
try {
|
||||
if (
|
||||
updatedRevisionsInSecondary.find(
|
||||
(updatedRevision) => updatedRevision.id.toString() === revision.id.toString(),
|
||||
)
|
||||
) {
|
||||
this.logger.info(
|
||||
`Skipping saving revision ${revision.id.toString()} as it was updated in secondary database`,
|
||||
)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
this.logger.debug(
|
||||
`Transitioning revision #${
|
||||
totalRevisionsCountTransitionedToSecondary + 1
|
||||
@@ -188,6 +217,7 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
|
||||
private async getNewRevisionsCreatedInSecondaryDatabase(userUuid: Uuid): Promise<{
|
||||
alreadyExistingInPrimary: Revision[]
|
||||
newRevisionsInSecondary: Revision[]
|
||||
updatedInSecondary: Revision[]
|
||||
}> {
|
||||
const revisions = await (this.secondRevisionsRepository as RevisionRepositoryInterface).findByUserUuid({
|
||||
userUuid: userUuid,
|
||||
@@ -195,23 +225,35 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
|
||||
|
||||
const alreadyExistingInPrimary: Revision[] = []
|
||||
const newRevisionsInSecondary: Revision[] = []
|
||||
const updatedInSecondary: Revision[] = []
|
||||
|
||||
for (const revision of revisions) {
|
||||
const revisionExistsInPrimary = await this.checkIfRevisionExistsInPrimaryDatabase(revision)
|
||||
if (revisionExistsInPrimary) {
|
||||
const { revisionInPrimary, newerRevisionInSecondary } =
|
||||
await this.checkIfRevisionExistsInPrimaryDatabase(revision)
|
||||
if (revisionInPrimary !== null) {
|
||||
alreadyExistingInPrimary.push(revision)
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
if (newerRevisionInSecondary !== null) {
|
||||
updatedInSecondary.push(newerRevisionInSecondary)
|
||||
continue
|
||||
}
|
||||
if (revisionInPrimary === null && newerRevisionInSecondary === null) {
|
||||
newRevisionsInSecondary.push(revision)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
alreadyExistingInPrimary: alreadyExistingInPrimary,
|
||||
newRevisionsInSecondary: newRevisionsInSecondary,
|
||||
updatedInSecondary: updatedInSecondary,
|
||||
}
|
||||
}
|
||||
|
||||
private async checkIfRevisionExistsInPrimaryDatabase(revision: Revision): Promise<boolean> {
|
||||
private async checkIfRevisionExistsInPrimaryDatabase(
|
||||
revision: Revision,
|
||||
): Promise<{ revisionInPrimary: Revision | null; newerRevisionInSecondary: Revision | null }> {
|
||||
const revisionInPrimary = await this.primaryRevisionsRepository.findOneByUuid(
|
||||
Uuid.create(revision.id.toString()).getValue(),
|
||||
revision.props.userUuid as Uuid,
|
||||
@@ -219,7 +261,10 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
|
||||
)
|
||||
|
||||
if (revisionInPrimary === null) {
|
||||
return false
|
||||
return {
|
||||
revisionInPrimary: null,
|
||||
newerRevisionInSecondary: null,
|
||||
}
|
||||
}
|
||||
|
||||
if (!revision.isIdenticalTo(revisionInPrimary)) {
|
||||
@@ -229,15 +274,23 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
|
||||
)}, revision in primary database: ${JSON.stringify(revisionInPrimary)}`,
|
||||
)
|
||||
|
||||
return false
|
||||
return {
|
||||
revisionInPrimary: null,
|
||||
newerRevisionInSecondary:
|
||||
revision.props.dates.updatedAt > revisionInPrimary.props.dates.updatedAt ? revision : null,
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
return {
|
||||
revisionInPrimary: revisionInPrimary,
|
||||
newerRevisionInSecondary: null,
|
||||
}
|
||||
}
|
||||
|
||||
private async checkIntegrityBetweenPrimaryAndSecondaryDatabase(
|
||||
userUuid: Uuid,
|
||||
newRevisionsInSecondaryCount: number,
|
||||
updatedRevisionsInSecondary: Revision[],
|
||||
): Promise<Result<boolean>> {
|
||||
try {
|
||||
const totalRevisionsCountForUserInPrimary = await this.primaryRevisionsRepository.countByUserUuid(userUuid)
|
||||
@@ -267,6 +320,17 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
|
||||
return Result.fail(`Revision ${revision.id.toString()} not found in secondary database`)
|
||||
}
|
||||
|
||||
if (
|
||||
updatedRevisionsInSecondary.find(
|
||||
(updatedRevision) => updatedRevision.id.toString() === revision.id.toString(),
|
||||
)
|
||||
) {
|
||||
this.logger.info(
|
||||
`Skipping integrity check for revision ${revision.id.toString()} as it was updated in secondary database`,
|
||||
)
|
||||
continue
|
||||
}
|
||||
|
||||
if (!revision.isIdenticalTo(revisionInSecondary)) {
|
||||
return Result.fail(
|
||||
`Revision ${revision.id.toString()} is not identical in primary and secondary database. Revision in primary database: ${JSON.stringify(
|
||||
|
||||
@@ -23,6 +23,7 @@ describe('TriggerTransitionFromPrimaryToSecondaryDatabaseForUser', () => {
|
||||
|
||||
await useCase.execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
transitionTimestamp: 123,
|
||||
})
|
||||
|
||||
expect(domainEventPubliser.publish).toHaveBeenCalled()
|
||||
|
||||
@@ -15,6 +15,7 @@ export class TriggerTransitionFromPrimaryToSecondaryDatabaseForUser implements U
|
||||
userUuid: dto.userUuid,
|
||||
status: 'STARTED',
|
||||
transitionType: 'revisions',
|
||||
transitionTimestamp: dto.transitionTimestamp,
|
||||
})
|
||||
|
||||
await this.domainEventPubliser.publish(event)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export interface TriggerTransitionFromPrimaryToSecondaryDatabaseForUserDTO {
|
||||
userUuid: string
|
||||
transitionTimestamp: number
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Request, Response } from 'express'
|
||||
import { controller, httpDelete, httpGet, httpPost, results } from 'inversify-express-utils'
|
||||
import { controller, httpDelete, httpGet, results } from 'inversify-express-utils'
|
||||
import { inject } from 'inversify'
|
||||
|
||||
import TYPES from '../../Bootstrap/Types'
|
||||
@@ -12,7 +12,6 @@ import { Revision } from '../../Domain/Revision/Revision'
|
||||
import { RevisionMetadata } from '../../Domain/Revision/RevisionMetadata'
|
||||
import { RevisionHttpRepresentation } from '../../Mapping/Http/RevisionHttpRepresentation'
|
||||
import { RevisionMetadataHttpRepresentation } from '../../Mapping/Http/RevisionMetadataHttpRepresentation'
|
||||
import { TriggerTransitionFromPrimaryToSecondaryDatabaseForUser } from '../../Domain/UseCase/Transition/TriggerTransitionFromPrimaryToSecondaryDatabaseForUser/TriggerTransitionFromPrimaryToSecondaryDatabaseForUser'
|
||||
|
||||
@controller('', TYPES.Revisions_ApiGatewayAuthMiddleware)
|
||||
export class AnnotatedRevisionsController extends BaseRevisionsController {
|
||||
@@ -24,17 +23,8 @@ export class AnnotatedRevisionsController extends BaseRevisionsController {
|
||||
override revisionHttpMapper: MapperInterface<Revision, RevisionHttpRepresentation>,
|
||||
@inject(TYPES.Revisions_RevisionMetadataHttpMapper)
|
||||
override revisionMetadataHttpMapper: MapperInterface<RevisionMetadata, RevisionMetadataHttpRepresentation>,
|
||||
@inject(TYPES.Revisions_TriggerTransitionFromPrimaryToSecondaryDatabaseForUser)
|
||||
override triggerTransitionFromPrimaryToSecondaryDatabaseForUser: TriggerTransitionFromPrimaryToSecondaryDatabaseForUser,
|
||||
) {
|
||||
super(
|
||||
getRevisionsMetadata,
|
||||
doGetRevision,
|
||||
doDeleteRevision,
|
||||
revisionHttpMapper,
|
||||
revisionMetadataHttpMapper,
|
||||
triggerTransitionFromPrimaryToSecondaryDatabaseForUser,
|
||||
)
|
||||
super(getRevisionsMetadata, doGetRevision, doDeleteRevision, revisionHttpMapper, revisionMetadataHttpMapper)
|
||||
}
|
||||
|
||||
@httpGet('/items/:itemUuid/revisions')
|
||||
@@ -51,9 +41,4 @@ export class AnnotatedRevisionsController extends BaseRevisionsController {
|
||||
override async deleteRevision(request: Request, response: Response): Promise<results.JsonResult> {
|
||||
return super.deleteRevision(request, response)
|
||||
}
|
||||
|
||||
@httpPost('/revisions/transition')
|
||||
override async transition(request: Request, response: Response): Promise<results.JsonResult> {
|
||||
return super.transition(request, response)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ import { GetRevision } from '../../../Domain/UseCase/GetRevision/GetRevision'
|
||||
import { GetRevisionsMetada } from '../../../Domain/UseCase/GetRevisionsMetada/GetRevisionsMetada'
|
||||
import { RevisionHttpRepresentation } from '../../../Mapping/Http/RevisionHttpRepresentation'
|
||||
import { RevisionMetadataHttpRepresentation } from '../../../Mapping/Http/RevisionMetadataHttpRepresentation'
|
||||
import { TriggerTransitionFromPrimaryToSecondaryDatabaseForUser } from '../../../Domain/UseCase/Transition/TriggerTransitionFromPrimaryToSecondaryDatabaseForUser/TriggerTransitionFromPrimaryToSecondaryDatabaseForUser'
|
||||
|
||||
export class BaseRevisionsController extends BaseHttpController {
|
||||
constructor(
|
||||
@@ -20,7 +19,6 @@ export class BaseRevisionsController extends BaseHttpController {
|
||||
protected doDeleteRevision: DeleteRevision,
|
||||
protected revisionHttpMapper: MapperInterface<Revision, RevisionHttpRepresentation>,
|
||||
protected revisionMetadataHttpMapper: MapperInterface<RevisionMetadata, RevisionMetadataHttpRepresentation>,
|
||||
protected triggerTransitionFromPrimaryToSecondaryDatabaseForUser: TriggerTransitionFromPrimaryToSecondaryDatabaseForUser,
|
||||
private controllerContainer?: ControllerContainerInterface,
|
||||
) {
|
||||
super()
|
||||
@@ -29,7 +27,6 @@ export class BaseRevisionsController extends BaseHttpController {
|
||||
this.controllerContainer.register('revisions.revisions.getRevisions', this.getRevisions.bind(this))
|
||||
this.controllerContainer.register('revisions.revisions.getRevision', this.getRevision.bind(this))
|
||||
this.controllerContainer.register('revisions.revisions.deleteRevision', this.deleteRevision.bind(this))
|
||||
this.controllerContainer.register('revisions.revisions.transition', this.transition.bind(this))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,23 +105,4 @@ export class BaseRevisionsController extends BaseHttpController {
|
||||
message: revisionOrError.getValue(),
|
||||
})
|
||||
}
|
||||
|
||||
async transition(_request: Request, response: Response): Promise<results.JsonResult> {
|
||||
const result = await this.triggerTransitionFromPrimaryToSecondaryDatabaseForUser.execute({
|
||||
userUuid: response.locals.user.uuid,
|
||||
})
|
||||
|
||||
if (result.isFailed()) {
|
||||
return this.json(
|
||||
{
|
||||
error: { message: result.getError() },
|
||||
},
|
||||
400,
|
||||
)
|
||||
}
|
||||
|
||||
response.setHeader('x-invalidate-cache', response.locals.user.uuid)
|
||||
|
||||
return this.json({ success: true })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.20.45](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.20.44...@standardnotes/scheduler-server@1.20.45) (2023-09-15)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/scheduler-server
|
||||
|
||||
## [1.20.44](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.20.43...@standardnotes/scheduler-server@1.20.44) (2023-09-13)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/scheduler-server
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/scheduler-server",
|
||||
"version": "1.20.44",
|
||||
"version": "1.20.45",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,43 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.96.1](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.96.0...@standardnotes/syncing-server@1.96.1) (2023-09-15)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add debug logs for transition status updates ([3e7856c](https://github.com/standardnotes/syncing-server-js/commit/3e7856c895e73b775c8977c6c6e86dffd5755c00))
|
||||
|
||||
# [1.96.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.95.19...@standardnotes/syncing-server@1.96.0) (2023-09-15)
|
||||
|
||||
### Features
|
||||
|
||||
* add skipping verified transitions ([#827](https://github.com/standardnotes/syncing-server-js/issues/827)) ([d4d4945](https://github.com/standardnotes/syncing-server-js/commit/d4d49454a68de0acdf440dc202fa14b9743905f6))
|
||||
|
||||
## [1.95.19](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.95.18...@standardnotes/syncing-server@1.95.19) (2023-09-15)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **syncing-server:** remove unused index in mongodb ([9147ff5](https://github.com/standardnotes/syncing-server-js/commit/9147ff5d49c507d943f4f8c6775f7c1fff878b0f))
|
||||
|
||||
## [1.95.18](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.95.17...@standardnotes/syncing-server@1.95.18) (2023-09-14)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* skip already updated items and revisions in integrity check ([#825](https://github.com/standardnotes/syncing-server-js/issues/825)) ([03a4a3f](https://github.com/standardnotes/syncing-server-js/commit/03a4a3f2abc0b4e09942ba39dbd227524068dfb6))
|
||||
* **syncing-server:** updating with missing creation date ([#824](https://github.com/standardnotes/syncing-server-js/issues/824)) ([3a8607d](https://github.com/standardnotes/syncing-server-js/commit/3a8607d1465cabedad68b84c753e407342e60d20))
|
||||
|
||||
## [1.95.17](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.95.16...@standardnotes/syncing-server@1.95.17) (2023-09-13)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* adjust transition timestamps to be universal ([c7807d0](https://github.com/standardnotes/syncing-server-js/commit/c7807d0f9e69ce572c4c03ff606375d706f24d9f))
|
||||
|
||||
## [1.95.16](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.95.15...@standardnotes/syncing-server@1.95.16) (2023-09-13)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* include handling updated items in revisions in secondary ([fbcb45c](https://github.com/standardnotes/syncing-server-js/commit/fbcb45c3a23fde09702fae7bfcb409bdbb610191))
|
||||
|
||||
## [1.95.15](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.95.14...@standardnotes/syncing-server@1.95.15) (2023-09-13)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
import 'reflect-metadata'
|
||||
|
||||
import { Logger } from 'winston'
|
||||
|
||||
import { ContainerConfigLoader } from '../src/Bootstrap/Container'
|
||||
import TYPES from '../src/Bootstrap/Types'
|
||||
import { Env } from '../src/Bootstrap/Env'
|
||||
import { TriggerTransitionFromPrimaryToSecondaryDatabaseForUser } from '../src/Domain/UseCase/Transition/TriggerTransitionFromPrimaryToSecondaryDatabaseForUser/TriggerTransitionFromPrimaryToSecondaryDatabaseForUser'
|
||||
|
||||
const inputArgs = process.argv.slice(2)
|
||||
const userUuid = inputArgs[0]
|
||||
|
||||
const requestTransition = async (
|
||||
triggerTransitionFromPrimaryToSecondaryDatabaseForUser: TriggerTransitionFromPrimaryToSecondaryDatabaseForUser,
|
||||
logger: Logger,
|
||||
): Promise<void> => {
|
||||
const result = await triggerTransitionFromPrimaryToSecondaryDatabaseForUser.execute({
|
||||
userUuid,
|
||||
})
|
||||
if (result.isFailed()) {
|
||||
logger.error(`Could not trigger transition for user ${userUuid}: ${result.getError()}`)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
const container = new ContainerConfigLoader('worker')
|
||||
void container.load().then((container) => {
|
||||
const env: Env = new Env()
|
||||
env.load()
|
||||
|
||||
const logger: Logger = container.get(TYPES.Sync_Logger)
|
||||
|
||||
logger.info(`Starting transitiong for user ${userUuid} ...`)
|
||||
|
||||
const triggerTransitionFromPrimaryToSecondaryDatabaseForUser: TriggerTransitionFromPrimaryToSecondaryDatabaseForUser =
|
||||
container.get(TYPES.Sync_TriggerTransitionFromPrimaryToSecondaryDatabaseForUser)
|
||||
|
||||
Promise.resolve(requestTransition(triggerTransitionFromPrimaryToSecondaryDatabaseForUser, logger))
|
||||
.then(() => {
|
||||
logger.info(`Transition triggered for user ${userUuid}`)
|
||||
|
||||
process.exit(0)
|
||||
})
|
||||
.catch((error) => {
|
||||
logger.error(`Could not trigger transition for user ${userUuid}: ${error.message}`)
|
||||
|
||||
process.exit(1)
|
||||
})
|
||||
})
|
||||
@@ -1,11 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
const path = require('path')
|
||||
|
||||
const pnp = require(path.normalize(path.resolve(__dirname, '../../..', '.pnp.cjs'))).setup()
|
||||
|
||||
const index = require(path.normalize(path.resolve(__dirname, '../dist/bin/transition.js')))
|
||||
|
||||
Object.defineProperty(exports, '__esModule', { value: true })
|
||||
|
||||
exports.default = index
|
||||
@@ -14,12 +14,6 @@ case "$COMMAND" in
|
||||
node docker/entrypoint-worker.js
|
||||
;;
|
||||
|
||||
'transition' )
|
||||
echo "[Docker] Starting transition Single User..."
|
||||
USER_UUID=$1 && shift 1
|
||||
node docker/entrypoint-transition.js $USER_UUID
|
||||
;;
|
||||
|
||||
* )
|
||||
echo "[Docker] Unknown command"
|
||||
;;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/syncing-server",
|
||||
"version": "1.95.15",
|
||||
"version": "1.96.1",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -1090,9 +1090,6 @@ export class ContainerConfigLoader {
|
||||
container.get<SyncItems>(TYPES.Sync_SyncItems),
|
||||
container.get<CheckIntegrity>(TYPES.Sync_CheckIntegrity),
|
||||
container.get<GetItem>(TYPES.Sync_GetItem),
|
||||
container.get<TriggerTransitionFromPrimaryToSecondaryDatabaseForUser>(
|
||||
TYPES.Sync_TriggerTransitionFromPrimaryToSecondaryDatabaseForUser,
|
||||
),
|
||||
container.get<MapperInterface<Item, ItemHttpRepresentation>>(TYPES.Sync_ItemHttpMapper),
|
||||
container.get<SyncResponseFactoryResolverInterface>(TYPES.Sync_SyncResponseFactoryResolver),
|
||||
container.get<ControllerContainerInterface>(TYPES.Sync_ControllerContainer),
|
||||
|
||||
@@ -172,7 +172,8 @@ export class DomainEventFactory implements DomainEventFactoryInterface {
|
||||
createTransitionStatusUpdatedEvent(dto: {
|
||||
userUuid: string
|
||||
transitionType: 'items' | 'revisions'
|
||||
status: 'STARTED' | 'FAILED' | 'FINISHED'
|
||||
transitionTimestamp: number
|
||||
status: 'STARTED' | 'IN_PROGRESS' | 'FAILED' | 'FINISHED' | 'VERIFIED'
|
||||
}): TransitionStatusUpdatedEvent {
|
||||
return {
|
||||
type: 'TRANSITION_STATUS_UPDATED',
|
||||
@@ -184,10 +185,7 @@ export class DomainEventFactory implements DomainEventFactoryInterface {
|
||||
},
|
||||
origin: DomainEventService.SyncingServer,
|
||||
},
|
||||
payload: {
|
||||
transitionTimestamp: this.timer.getTimestampInMicroseconds(),
|
||||
...dto,
|
||||
},
|
||||
payload: dto,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -52,7 +52,8 @@ export interface DomainEventFactoryInterface {
|
||||
createTransitionStatusUpdatedEvent(dto: {
|
||||
userUuid: string
|
||||
transitionType: 'items' | 'revisions'
|
||||
status: 'STARTED' | 'IN_PROGRESS' | 'FAILED' | 'FINISHED'
|
||||
transitionTimestamp: number
|
||||
status: 'STARTED' | 'IN_PROGRESS' | 'FAILED' | 'FINISHED' | 'VERIFIED'
|
||||
}): TransitionStatusUpdatedEvent
|
||||
createEmailRequestedEvent(dto: {
|
||||
userEmail: string
|
||||
|
||||
@@ -18,6 +18,7 @@ export class TransitionRequestedEventHandler implements DomainEventHandlerInterf
|
||||
|
||||
const result = await this.triggerTransitionFromPrimaryToSecondaryDatabaseForUser.execute({
|
||||
userUuid: event.payload.userUuid,
|
||||
transitionTimestamp: event.payload.timestamp,
|
||||
})
|
||||
|
||||
if (result.isFailed()) {
|
||||
|
||||
@@ -18,13 +18,22 @@ export class TransitionStatusUpdatedEventHandler implements DomainEventHandlerIn
|
||||
) {}
|
||||
|
||||
async handle(event: TransitionStatusUpdatedEvent): Promise<void> {
|
||||
if (event.payload.status === 'STARTED' && event.payload.transitionType === 'items') {
|
||||
if (event.payload.transitionType !== 'items') {
|
||||
return
|
||||
}
|
||||
|
||||
this.logger.info(
|
||||
`Received transition status updated event to ${event.payload.status} for user ${event.payload.userUuid}`,
|
||||
)
|
||||
|
||||
if (event.payload.status === 'STARTED') {
|
||||
if (await this.isAlreadyMigrated(event.payload.userUuid)) {
|
||||
await this.domainEventPublisher.publish(
|
||||
this.domainEventFactory.createTransitionStatusUpdatedEvent({
|
||||
userUuid: event.payload.userUuid,
|
||||
status: 'FINISHED',
|
||||
transitionType: 'items',
|
||||
transitionTimestamp: event.payload.transitionTimestamp,
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -36,6 +45,7 @@ export class TransitionStatusUpdatedEventHandler implements DomainEventHandlerIn
|
||||
userUuid: event.payload.userUuid,
|
||||
status: 'IN_PROGRESS',
|
||||
transitionType: 'items',
|
||||
transitionTimestamp: event.payload.transitionTimestamp,
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -51,6 +61,7 @@ export class TransitionStatusUpdatedEventHandler implements DomainEventHandlerIn
|
||||
userUuid: event.payload.userUuid,
|
||||
status: 'FAILED',
|
||||
transitionType: 'items',
|
||||
transitionTimestamp: event.payload.transitionTimestamp,
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -62,8 +73,18 @@ export class TransitionStatusUpdatedEventHandler implements DomainEventHandlerIn
|
||||
userUuid: event.payload.userUuid,
|
||||
status: 'FINISHED',
|
||||
transitionType: 'items',
|
||||
transitionTimestamp: event.payload.transitionTimestamp,
|
||||
}),
|
||||
)
|
||||
} else if (event.payload.status === 'FINISHED') {
|
||||
if (await this.isAlreadyMigrated(event.payload.userUuid)) {
|
||||
this.domainEventFactory.createTransitionStatusUpdatedEvent({
|
||||
userUuid: event.payload.userUuid,
|
||||
status: 'VERIFIED',
|
||||
transitionType: 'items',
|
||||
transitionTimestamp: event.payload.transitionTimestamp,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -298,7 +298,7 @@ describe('UpdateExistingItem', () => {
|
||||
expect(itemRepository.save).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should return error if created at time is not give in any form', async () => {
|
||||
it('should fallback to updated at timestamp if created at time is not give in any form', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
@@ -308,13 +308,59 @@ describe('UpdateExistingItem', () => {
|
||||
...itemHash1.props,
|
||||
created_at: undefined,
|
||||
created_at_timestamp: undefined,
|
||||
updated_at_timestamp: 123,
|
||||
}).getValue(),
|
||||
sessionUuid: '00000000-0000-0000-0000-000000000000',
|
||||
performingUserUuid: '00000000-0000-0000-0000-000000000000',
|
||||
roleNames: [RoleName.NAMES.CoreUser],
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBeTruthy()
|
||||
expect(result.isFailed()).toBeFalsy()
|
||||
expect(itemRepository.save).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should fallback to updated at date if created at time is not give in any form', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
existingItem: item1,
|
||||
onGoingRevisionsTransition: false,
|
||||
itemHash: ItemHash.create({
|
||||
...itemHash1.props,
|
||||
created_at: undefined,
|
||||
created_at_timestamp: undefined,
|
||||
updated_at_timestamp: undefined,
|
||||
updated_at: '2020-01-01T00:00:00.000Z',
|
||||
}).getValue(),
|
||||
sessionUuid: '00000000-0000-0000-0000-000000000000',
|
||||
performingUserUuid: '00000000-0000-0000-0000-000000000000',
|
||||
roleNames: [RoleName.NAMES.CoreUser],
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBeFalsy()
|
||||
expect(itemRepository.save).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should fallback to 0 if created at and update at time is not give in any form', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
existingItem: item1,
|
||||
onGoingRevisionsTransition: false,
|
||||
itemHash: ItemHash.create({
|
||||
...itemHash1.props,
|
||||
created_at: undefined,
|
||||
created_at_timestamp: undefined,
|
||||
updated_at_timestamp: undefined,
|
||||
updated_at: undefined,
|
||||
}).getValue(),
|
||||
sessionUuid: '00000000-0000-0000-0000-000000000000',
|
||||
performingUserUuid: '00000000-0000-0000-0000-000000000000',
|
||||
roleNames: [RoleName.NAMES.CoreUser],
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBeFalsy()
|
||||
expect(itemRepository.save).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should return error if dates could not be created from timestamps', async () => {
|
||||
|
||||
@@ -23,6 +23,7 @@ import { SharedVaultOperationOnItem } from '../../../SharedVault/SharedVaultOper
|
||||
import { AddNotificationForUser } from '../../Messaging/AddNotificationForUser/AddNotificationForUser'
|
||||
import { RemoveNotificationsForUser } from '../../Messaging/RemoveNotificationsForUser/RemoveNotificationsForUser'
|
||||
import { ItemRepositoryResolverInterface } from '../../../Item/ItemRepositoryResolverInterface'
|
||||
import { ItemHash } from '../../../Item/ItemHash'
|
||||
|
||||
export class UpdateExistingItem implements UseCaseInterface<Item> {
|
||||
constructor(
|
||||
@@ -115,17 +116,7 @@ export class UpdateExistingItem implements UseCaseInterface<Item> {
|
||||
)
|
||||
const updatedAtDate = this.timer.convertMicrosecondsToDate(updatedAtTimestamp)
|
||||
|
||||
let createdAtTimestamp: number
|
||||
let createdAtDate: Date
|
||||
if (dto.itemHash.props.created_at_timestamp) {
|
||||
createdAtTimestamp = dto.itemHash.props.created_at_timestamp
|
||||
createdAtDate = this.timer.convertMicrosecondsToDate(createdAtTimestamp)
|
||||
} else if (dto.itemHash.props.created_at) {
|
||||
createdAtTimestamp = this.timer.convertStringDateToMicroseconds(dto.itemHash.props.created_at)
|
||||
createdAtDate = this.timer.convertStringDateToDate(dto.itemHash.props.created_at)
|
||||
} else {
|
||||
return Result.fail('Created at timestamp is required.')
|
||||
}
|
||||
const { createdAtDate, createdAtTimestamp } = this.determineCreatedAt(dto.itemHash)
|
||||
|
||||
const datesOrError = Dates.create(createdAtDate, updatedAtDate)
|
||||
if (datesOrError.isFailed()) {
|
||||
@@ -221,6 +212,29 @@ export class UpdateExistingItem implements UseCaseInterface<Item> {
|
||||
return Result.ok(dto.existingItem)
|
||||
}
|
||||
|
||||
private determineCreatedAt(itemHash: ItemHash): { createdAtDate: Date; createdAtTimestamp: number } {
|
||||
let createdAtTimestamp: number
|
||||
let createdAtDate: Date
|
||||
if (itemHash.props.created_at_timestamp) {
|
||||
createdAtTimestamp = itemHash.props.created_at_timestamp
|
||||
createdAtDate = this.timer.convertMicrosecondsToDate(createdAtTimestamp)
|
||||
} else if (itemHash.props.created_at) {
|
||||
createdAtTimestamp = this.timer.convertStringDateToMicroseconds(itemHash.props.created_at)
|
||||
createdAtDate = this.timer.convertStringDateToDate(itemHash.props.created_at)
|
||||
} else if (itemHash.props.updated_at_timestamp) {
|
||||
createdAtTimestamp = itemHash.props.updated_at_timestamp
|
||||
createdAtDate = this.timer.convertMicrosecondsToDate(itemHash.props.updated_at_timestamp)
|
||||
} else if (itemHash.props.updated_at) {
|
||||
createdAtTimestamp = this.timer.convertStringDateToMicroseconds(itemHash.props.updated_at)
|
||||
createdAtDate = this.timer.convertStringDateToDate(itemHash.props.updated_at)
|
||||
} else {
|
||||
createdAtTimestamp = 0
|
||||
createdAtDate = new Date(0)
|
||||
}
|
||||
|
||||
return { createdAtDate, createdAtTimestamp }
|
||||
}
|
||||
|
||||
private async addNotificationsAndPublishEvents(
|
||||
userUuid: Uuid,
|
||||
sharedVaultOperation: SharedVaultOperationOnItem | null,
|
||||
|
||||
@@ -31,29 +31,41 @@ export class TransitionItemsFromPrimaryToSecondaryDatabaseForUser implements Use
|
||||
const userUuid = userUuidOrError.getValue()
|
||||
|
||||
let newItemsInSecondaryCount = 0
|
||||
let updatedItemsInSecondary: Item[] = []
|
||||
if (await this.hasAlreadyDataInSecondaryDatabase(userUuid)) {
|
||||
const newItems = await this.getNewItemsCreatedInSecondaryDatabase(userUuid)
|
||||
for (const existingItem of newItems.alreadyExistingInPrimary) {
|
||||
const { alreadyExistingInPrimary, newItemsInSecondary, updatedInSecondary } =
|
||||
await this.getNewItemsCreatedInSecondaryDatabase(userUuid)
|
||||
|
||||
for (const existingItem of alreadyExistingInPrimary) {
|
||||
this.logger.info(`Removing item ${existingItem.uuid.value} from secondary database`)
|
||||
await (this.secondaryItemRepository as ItemRepositoryInterface).remove(existingItem)
|
||||
}
|
||||
|
||||
if (newItems.newItemsInSecondary.length > 0) {
|
||||
if (newItemsInSecondary.length > 0) {
|
||||
this.logger.info(
|
||||
`Found ${newItems.newItemsInSecondary.length} new items in secondary database for user ${userUuid.value}`,
|
||||
`Found ${newItemsInSecondary.length} new items in secondary database for user ${userUuid.value}`,
|
||||
)
|
||||
}
|
||||
|
||||
newItemsInSecondaryCount = newItems.newItemsInSecondary.length
|
||||
newItemsInSecondaryCount = newItemsInSecondary.length
|
||||
|
||||
if (updatedInSecondary.length > 0) {
|
||||
this.logger.info(
|
||||
`Found ${updatedInSecondary.length} updated items in secondary database for user ${userUuid.value}`,
|
||||
)
|
||||
}
|
||||
|
||||
updatedItemsInSecondary = updatedInSecondary
|
||||
}
|
||||
const updatedItemsInSecondaryCount = updatedItemsInSecondary.length
|
||||
|
||||
await this.allowForSecondaryDatabaseToCatchUp()
|
||||
|
||||
const migrationTimeStart = this.timer.getTimestampInMicroseconds()
|
||||
|
||||
const migrationResult = await this.migrateItemsForUser(userUuid)
|
||||
const migrationResult = await this.migrateItemsForUser(userUuid, updatedItemsInSecondary)
|
||||
if (migrationResult.isFailed()) {
|
||||
if (newItemsInSecondaryCount === 0) {
|
||||
if (newItemsInSecondaryCount === 0 && updatedItemsInSecondaryCount === 0) {
|
||||
const cleanupResult = await this.deleteItemsForUser(userUuid, this.secondaryItemRepository)
|
||||
if (cleanupResult.isFailed()) {
|
||||
this.logger.error(
|
||||
@@ -70,9 +82,10 @@ export class TransitionItemsFromPrimaryToSecondaryDatabaseForUser implements Use
|
||||
const integrityCheckResult = await this.checkIntegrityBetweenPrimaryAndSecondaryDatabase(
|
||||
userUuid,
|
||||
newItemsInSecondaryCount,
|
||||
updatedItemsInSecondary,
|
||||
)
|
||||
if (integrityCheckResult.isFailed()) {
|
||||
if (newItemsInSecondaryCount === 0) {
|
||||
if (newItemsInSecondaryCount === 0 && updatedItemsInSecondaryCount === 0) {
|
||||
const cleanupResult = await this.deleteItemsForUser(userUuid, this.secondaryItemRepository)
|
||||
if (cleanupResult.isFailed()) {
|
||||
this.logger.error(
|
||||
@@ -124,34 +137,46 @@ export class TransitionItemsFromPrimaryToSecondaryDatabaseForUser implements Use
|
||||
private async getNewItemsCreatedInSecondaryDatabase(userUuid: Uuid): Promise<{
|
||||
alreadyExistingInPrimary: Item[]
|
||||
newItemsInSecondary: Item[]
|
||||
updatedInSecondary: Item[]
|
||||
}> {
|
||||
const items = await (this.secondaryItemRepository as ItemRepositoryInterface).findAll({
|
||||
userUuid: userUuid.value,
|
||||
})
|
||||
|
||||
const alreadyExistingInPrimary: Item[] = []
|
||||
const updatedInSecondary: Item[] = []
|
||||
const newItemsInSecondary: Item[] = []
|
||||
|
||||
for (const item of items) {
|
||||
const itemExistsInPrimary = await this.checkIfItemExistsInPrimaryDatabase(item)
|
||||
if (itemExistsInPrimary) {
|
||||
const { itemInPrimary, newerItemInSecondary } = await this.checkIfItemExistsInPrimaryDatabase(item)
|
||||
if (itemInPrimary !== null) {
|
||||
alreadyExistingInPrimary.push(item)
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
if (newerItemInSecondary !== null) {
|
||||
updatedInSecondary.push(newerItemInSecondary)
|
||||
continue
|
||||
}
|
||||
if (itemInPrimary === null && newerItemInSecondary === null) {
|
||||
newItemsInSecondary.push(item)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
alreadyExistingInPrimary: alreadyExistingInPrimary,
|
||||
newItemsInSecondary: newItemsInSecondary,
|
||||
alreadyExistingInPrimary,
|
||||
newItemsInSecondary,
|
||||
updatedInSecondary,
|
||||
}
|
||||
}
|
||||
|
||||
private async checkIfItemExistsInPrimaryDatabase(item: Item): Promise<boolean> {
|
||||
private async checkIfItemExistsInPrimaryDatabase(
|
||||
item: Item,
|
||||
): Promise<{ itemInPrimary: Item | null; newerItemInSecondary: Item | null }> {
|
||||
const itemInPrimary = await this.primaryItemRepository.findByUuid(item.uuid)
|
||||
|
||||
if (itemInPrimary === null) {
|
||||
return false
|
||||
return { itemInPrimary: null, newerItemInSecondary: null }
|
||||
}
|
||||
|
||||
if (!item.isIdenticalTo(itemInPrimary)) {
|
||||
@@ -161,13 +186,16 @@ export class TransitionItemsFromPrimaryToSecondaryDatabaseForUser implements Use
|
||||
)}, revision in primary database: ${JSON.stringify(itemInPrimary)}`,
|
||||
)
|
||||
|
||||
return false
|
||||
return {
|
||||
itemInPrimary: null,
|
||||
newerItemInSecondary: item.props.timestamps.updatedAt > itemInPrimary.props.timestamps.updatedAt ? item : null,
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
return { itemInPrimary: itemInPrimary, newerItemInSecondary: null }
|
||||
}
|
||||
|
||||
private async migrateItemsForUser(userUuid: Uuid): Promise<Result<void>> {
|
||||
private async migrateItemsForUser(userUuid: Uuid, updatedItemsInSecondary: Item[]): Promise<Result<void>> {
|
||||
try {
|
||||
const totalItemsCountForUser = await this.primaryItemRepository.countAll({ userUuid: userUuid.value })
|
||||
const totalPages = Math.ceil(totalItemsCountForUser / this.pageSize)
|
||||
@@ -181,6 +209,11 @@ export class TransitionItemsFromPrimaryToSecondaryDatabaseForUser implements Use
|
||||
const items = await this.primaryItemRepository.findAll(query)
|
||||
|
||||
for (const item of items) {
|
||||
if (updatedItemsInSecondary.find((updatedItem) => updatedItem.uuid.equals(item.uuid))) {
|
||||
this.logger.info(`Skipping saving item ${item.uuid.value} as it was updated in secondary database`)
|
||||
|
||||
continue
|
||||
}
|
||||
await (this.secondaryItemRepository as ItemRepositoryInterface).save(item)
|
||||
}
|
||||
}
|
||||
@@ -204,6 +237,7 @@ export class TransitionItemsFromPrimaryToSecondaryDatabaseForUser implements Use
|
||||
private async checkIntegrityBetweenPrimaryAndSecondaryDatabase(
|
||||
userUuid: Uuid,
|
||||
newItemsInSecondaryCount: number,
|
||||
updatedItemsInSecondary: Item[],
|
||||
): Promise<Result<boolean>> {
|
||||
try {
|
||||
const totalItemsCountForUserInPrimary = await this.primaryItemRepository.countAll({ userUuid: userUuid.value })
|
||||
@@ -235,6 +269,13 @@ export class TransitionItemsFromPrimaryToSecondaryDatabaseForUser implements Use
|
||||
return Result.fail(`Item ${item.uuid.value} not found in secondary database`)
|
||||
}
|
||||
|
||||
if (updatedItemsInSecondary.find((updatedItem) => updatedItem.uuid.equals(item.uuid))) {
|
||||
this.logger.info(
|
||||
`Skipping integrity check for item ${item.uuid.value} as it was updated in secondary database`,
|
||||
)
|
||||
continue
|
||||
}
|
||||
|
||||
if (!item.isIdenticalTo(itemInSecondary)) {
|
||||
return Result.fail(
|
||||
`Item ${
|
||||
|
||||
@@ -23,6 +23,7 @@ describe('TriggerTransitionFromPrimaryToSecondaryDatabaseForUser', () => {
|
||||
|
||||
await useCase.execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
transitionTimestamp: 123,
|
||||
})
|
||||
|
||||
expect(domainEventPubliser.publish).toHaveBeenCalled()
|
||||
|
||||
@@ -15,6 +15,7 @@ export class TriggerTransitionFromPrimaryToSecondaryDatabaseForUser implements U
|
||||
userUuid: dto.userUuid,
|
||||
status: 'STARTED',
|
||||
transitionType: 'items',
|
||||
transitionTimestamp: dto.transitionTimestamp,
|
||||
})
|
||||
|
||||
await this.domainEventPubliser.publish(event)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export interface TriggerTransitionFromPrimaryToSecondaryDatabaseForUserDTO {
|
||||
userUuid: string
|
||||
transitionTimestamp: number
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ import { SyncItems } from '../../Domain/UseCase/Syncing/SyncItems/SyncItems'
|
||||
import { BaseItemsController } from './Base/BaseItemsController'
|
||||
import { MapperInterface } from '@standardnotes/domain-core'
|
||||
import { ItemHttpRepresentation } from '../../Mapping/Http/ItemHttpRepresentation'
|
||||
import { TriggerTransitionFromPrimaryToSecondaryDatabaseForUser } from '../../Domain/UseCase/Transition/TriggerTransitionFromPrimaryToSecondaryDatabaseForUser/TriggerTransitionFromPrimaryToSecondaryDatabaseForUser'
|
||||
|
||||
@controller('/items', TYPES.Sync_AuthMiddleware)
|
||||
export class AnnotatedItemsController extends BaseItemsController {
|
||||
@@ -19,20 +18,11 @@ export class AnnotatedItemsController extends BaseItemsController {
|
||||
@inject(TYPES.Sync_SyncItems) override syncItems: SyncItems,
|
||||
@inject(TYPES.Sync_CheckIntegrity) override checkIntegrity: CheckIntegrity,
|
||||
@inject(TYPES.Sync_GetItem) override getItem: GetItem,
|
||||
@inject(TYPES.Sync_TriggerTransitionFromPrimaryToSecondaryDatabaseForUser)
|
||||
override triggerTransitionFromPrimaryToSecondaryDatabaseForUser: TriggerTransitionFromPrimaryToSecondaryDatabaseForUser,
|
||||
@inject(TYPES.Sync_ItemHttpMapper) override itemHttpMapper: MapperInterface<Item, ItemHttpRepresentation>,
|
||||
@inject(TYPES.Sync_SyncResponseFactoryResolver)
|
||||
override syncResponseFactoryResolver: SyncResponseFactoryResolverInterface,
|
||||
) {
|
||||
super(
|
||||
syncItems,
|
||||
checkIntegrity,
|
||||
getItem,
|
||||
triggerTransitionFromPrimaryToSecondaryDatabaseForUser,
|
||||
itemHttpMapper,
|
||||
syncResponseFactoryResolver,
|
||||
)
|
||||
super(syncItems, checkIntegrity, getItem, itemHttpMapper, syncResponseFactoryResolver)
|
||||
}
|
||||
|
||||
@httpPost('/sync')
|
||||
@@ -45,11 +35,6 @@ export class AnnotatedItemsController extends BaseItemsController {
|
||||
return super.checkItemsIntegrity(request, response)
|
||||
}
|
||||
|
||||
@httpPost('/transition')
|
||||
override async transition(request: Request, response: Response): Promise<results.JsonResult> {
|
||||
return super.transition(request, response)
|
||||
}
|
||||
|
||||
@httpGet('/:uuid')
|
||||
override async getSingleItem(request: Request, response: Response): Promise<results.JsonResult> {
|
||||
return super.getSingleItem(request, response)
|
||||
|
||||
@@ -12,14 +12,12 @@ import { ApiVersion } from '../../../Domain/Api/ApiVersion'
|
||||
import { SyncItems } from '../../../Domain/UseCase/Syncing/SyncItems/SyncItems'
|
||||
import { ItemHttpRepresentation } from '../../../Mapping/Http/ItemHttpRepresentation'
|
||||
import { ItemHash } from '../../../Domain/Item/ItemHash'
|
||||
import { TriggerTransitionFromPrimaryToSecondaryDatabaseForUser } from '../../../Domain/UseCase/Transition/TriggerTransitionFromPrimaryToSecondaryDatabaseForUser/TriggerTransitionFromPrimaryToSecondaryDatabaseForUser'
|
||||
|
||||
export class BaseItemsController extends BaseHttpController {
|
||||
constructor(
|
||||
protected syncItems: SyncItems,
|
||||
protected checkIntegrity: CheckIntegrity,
|
||||
protected getItem: GetItem,
|
||||
protected triggerTransitionFromPrimaryToSecondaryDatabaseForUser: TriggerTransitionFromPrimaryToSecondaryDatabaseForUser,
|
||||
protected itemHttpMapper: MapperInterface<Item, ItemHttpRepresentation>,
|
||||
protected syncResponseFactoryResolver: SyncResponseFactoryResolverInterface,
|
||||
private controllerContainer?: ControllerContainerInterface,
|
||||
@@ -30,7 +28,6 @@ export class BaseItemsController extends BaseHttpController {
|
||||
this.controllerContainer.register('sync.items.sync', this.sync.bind(this))
|
||||
this.controllerContainer.register('sync.items.check_integrity', this.checkItemsIntegrity.bind(this))
|
||||
this.controllerContainer.register('sync.items.get_item', this.getSingleItem.bind(this))
|
||||
this.controllerContainer.register('sync.items.transition', this.transition.bind(this))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,25 +110,6 @@ export class BaseItemsController extends BaseHttpController {
|
||||
})
|
||||
}
|
||||
|
||||
async transition(_request: Request, response: Response): Promise<results.JsonResult> {
|
||||
const result = await this.triggerTransitionFromPrimaryToSecondaryDatabaseForUser.execute({
|
||||
userUuid: response.locals.user.uuid,
|
||||
})
|
||||
|
||||
if (result.isFailed()) {
|
||||
return this.json(
|
||||
{
|
||||
error: { message: result.getError() },
|
||||
},
|
||||
400,
|
||||
)
|
||||
}
|
||||
|
||||
response.setHeader('x-invalidate-cache', response.locals.user.uuid)
|
||||
|
||||
return this.json({ success: true })
|
||||
}
|
||||
|
||||
async getSingleItem(request: Request, response: Response): Promise<results.JsonResult> {
|
||||
const result = await this.getItem.execute({
|
||||
userUuid: response.locals.user.uuid,
|
||||
|
||||
@@ -18,7 +18,6 @@ export class MongoDBItem {
|
||||
declare content: string | null
|
||||
|
||||
@Column()
|
||||
@Index('index_items_on_content_type')
|
||||
declare contentType: string | null
|
||||
|
||||
@Column()
|
||||
|
||||
@@ -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.10.42](https://github.com/standardnotes/server/compare/@standardnotes/websockets-server@1.10.41...@standardnotes/websockets-server@1.10.42) (2023-09-15)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/websockets-server
|
||||
|
||||
## [1.10.41](https://github.com/standardnotes/server/compare/@standardnotes/websockets-server@1.10.40...@standardnotes/websockets-server@1.10.41) (2023-09-13)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/websockets-server
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/websockets-server",
|
||||
"version": "1.10.41",
|
||||
"version": "1.10.42",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
127
yarn.lock
127
yarn.lock
@@ -2659,10 +2659,10 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@hexagon/base64@npm:^1.1.25":
|
||||
version: 1.1.26
|
||||
resolution: "@hexagon/base64@npm:1.1.26"
|
||||
checksum: e42582ed12465bffaf96307c9d5b7dfd36166ec4dc41a1838b9a560c90c9d136d006099a205e1684fb0dc18002cc5af51a49dd7b81a7c4b86798372d6ee26af3
|
||||
"@hexagon/base64@npm:^1.1.27":
|
||||
version: 1.1.27
|
||||
resolution: "@hexagon/base64@npm:1.1.27"
|
||||
checksum: 899fffaf54b291e1df997bf33dbf6e068fcfbd83155adc114e14bcb9c1e36c5f820dfaaee3d0c2409f7e84efa4352f51655eac8bec4c2432fca443bf179bce8d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -3793,7 +3793,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@peculiar/asn1-android@npm:^2.3.3":
|
||||
"@peculiar/asn1-android@npm:^2.3.6":
|
||||
version: 2.3.6
|
||||
resolution: "@peculiar/asn1-android@npm:2.3.6"
|
||||
dependencies:
|
||||
@@ -3804,7 +3804,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@peculiar/asn1-ecc@npm:^2.3.4":
|
||||
"@peculiar/asn1-ecc@npm:^2.3.6":
|
||||
version: 2.3.6
|
||||
resolution: "@peculiar/asn1-ecc@npm:2.3.6"
|
||||
dependencies:
|
||||
@@ -3816,7 +3816,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@peculiar/asn1-rsa@npm:^2.3.4":
|
||||
"@peculiar/asn1-rsa@npm:^2.3.6":
|
||||
version: 2.3.6
|
||||
resolution: "@peculiar/asn1-rsa@npm:2.3.6"
|
||||
dependencies:
|
||||
@@ -3828,7 +3828,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@peculiar/asn1-schema@npm:^2.3.3, @peculiar/asn1-schema@npm:^2.3.6":
|
||||
"@peculiar/asn1-schema@npm:^2.3.6":
|
||||
version: 2.3.6
|
||||
resolution: "@peculiar/asn1-schema@npm:2.3.6"
|
||||
dependencies:
|
||||
@@ -3839,7 +3839,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@peculiar/asn1-x509@npm:^2.3.4, @peculiar/asn1-x509@npm:^2.3.6":
|
||||
"@peculiar/asn1-x509@npm:^2.3.6":
|
||||
version: 2.3.6
|
||||
resolution: "@peculiar/asn1-x509@npm:2.3.6"
|
||||
dependencies:
|
||||
@@ -3987,41 +3987,27 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@simplewebauthn/iso-webcrypto@npm:^7.2.0":
|
||||
version: 7.2.0
|
||||
resolution: "@simplewebauthn/iso-webcrypto@npm:7.2.0"
|
||||
"@simplewebauthn/server@npm:^8.1.1":
|
||||
version: 8.1.1
|
||||
resolution: "@simplewebauthn/server@npm:8.1.1"
|
||||
dependencies:
|
||||
"@simplewebauthn/typescript-types": "npm:*"
|
||||
"@types/node": "npm:^18.11.9"
|
||||
checksum: b57899d0ada391507ce8f4601328ed62df5d09f75f6e91b018278631270a96f37ceab95f9e824c9555dd05820e5a99ac386ed067db7902d37bdb6d995fbd7eaf
|
||||
"@hexagon/base64": "npm:^1.1.27"
|
||||
"@peculiar/asn1-android": "npm:^2.3.6"
|
||||
"@peculiar/asn1-ecc": "npm:^2.3.6"
|
||||
"@peculiar/asn1-rsa": "npm:^2.3.6"
|
||||
"@peculiar/asn1-schema": "npm:^2.3.6"
|
||||
"@peculiar/asn1-x509": "npm:^2.3.6"
|
||||
"@simplewebauthn/typescript-types": "npm:^8.0.0"
|
||||
cbor-x: "npm:^1.5.2"
|
||||
cross-fetch: "npm:^4.0.0"
|
||||
checksum: a07c2a067b25b7f4afe215dcf81e0280aa5c382f5eb3cb1ef9327c6dc84c9618ed5774fc61bbc5717b8633dd5b8b4c41c7800cb32e83c6a4b1d2d1e1e50f5250
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@simplewebauthn/server@npm:^7.2.0":
|
||||
version: 7.2.0
|
||||
resolution: "@simplewebauthn/server@npm:7.2.0"
|
||||
dependencies:
|
||||
"@hexagon/base64": "npm:^1.1.25"
|
||||
"@peculiar/asn1-android": "npm:^2.3.3"
|
||||
"@peculiar/asn1-ecc": "npm:^2.3.4"
|
||||
"@peculiar/asn1-rsa": "npm:^2.3.4"
|
||||
"@peculiar/asn1-schema": "npm:^2.3.3"
|
||||
"@peculiar/asn1-x509": "npm:^2.3.4"
|
||||
"@simplewebauthn/iso-webcrypto": "npm:^7.2.0"
|
||||
"@simplewebauthn/typescript-types": "npm:*"
|
||||
"@types/debug": "npm:^4.1.7"
|
||||
"@types/node": "npm:^18.11.9"
|
||||
cbor-x: "npm:^1.4.1"
|
||||
cross-fetch: "npm:^3.1.5"
|
||||
debug: "npm:^4.3.2"
|
||||
checksum: 2e37c87edd05abace8ba8c5b1f4e2cb4adb9ec4dcf0b237d25f375f35538d25e31cc0ae196029006cf5124c983216a3cf69127732942863d2960bd72ed5783c4
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@simplewebauthn/typescript-types@npm:*, @simplewebauthn/typescript-types@npm:^7.0.0":
|
||||
version: 7.0.0
|
||||
resolution: "@simplewebauthn/typescript-types@npm:7.0.0"
|
||||
checksum: 124238ea1859c80761c4cdbf19107e2e8e96fdefa64affb55fb4fc67d1ac5e3354c3098c908729d2de439a633115d98da77ded7289286fe576559306fa933815
|
||||
"@simplewebauthn/typescript-types@npm:^8.0.0":
|
||||
version: 8.0.0
|
||||
resolution: "@simplewebauthn/typescript-types@npm:8.0.0"
|
||||
checksum: 21e0b13268f237d7cd6ecdc6cdceb884ddcc85e18a3554b65d10da4f502056aefa810db94f504beaad96b4ac0c7861a022954dcc6aa8721d4215571c2c3dcdf5
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -4744,8 +4730,8 @@ __metadata:
|
||||
"@cbor-extract/cbor-extract-linux-arm64": "npm:^2.1.1"
|
||||
"@cbor-extract/cbor-extract-linux-x64": "npm:^2.1.1"
|
||||
"@newrelic/winston-enricher": "npm:^4.0.1"
|
||||
"@simplewebauthn/server": "npm:^7.2.0"
|
||||
"@simplewebauthn/typescript-types": "npm:^7.0.0"
|
||||
"@simplewebauthn/server": "npm:^8.1.1"
|
||||
"@simplewebauthn/typescript-types": "npm:^8.0.0"
|
||||
"@standardnotes/api": "npm:^1.26.26"
|
||||
"@standardnotes/common": "workspace:*"
|
||||
"@standardnotes/domain-core": "workspace:^"
|
||||
@@ -5589,15 +5575,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/debug@npm:^4.1.7":
|
||||
version: 4.1.8
|
||||
resolution: "@types/debug@npm:4.1.8"
|
||||
dependencies:
|
||||
"@types/ms": "npm:*"
|
||||
checksum: 9c190e812984e0f6e02dfdfb0c7a3081a55cf3fc712a4e059336bd9f8329db70211eb851ce409311520876549cff2c4785ce48dd4c9fef8e48549c87bec29ded
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/dotenv@npm:^8.2.0":
|
||||
version: 8.2.0
|
||||
resolution: "@types/dotenv@npm:8.2.0"
|
||||
@@ -5792,13 +5769,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/ms@npm:*":
|
||||
version: 0.7.31
|
||||
resolution: "@types/ms@npm:0.7.31"
|
||||
checksum: cccb52777bb683c65ac5bab61351cd3910c9ce3512b1d903a591fc9694bb83afad6e48bf0beee5b47b6a8b620a05f5d82f8febfd55de05e7d9eb93586cc196c8
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/newrelic@npm:^9.14.0":
|
||||
version: 9.14.0
|
||||
resolution: "@types/newrelic@npm:9.14.0"
|
||||
@@ -5820,13 +5790,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/node@npm:^18.11.9":
|
||||
version: 18.16.16
|
||||
resolution: "@types/node@npm:18.16.16"
|
||||
checksum: 946bd4d8e6fa54220e4193bc594de8a2e138e6afebb6efb7d862d98e30ced25a19476a6f47c81e690b9ac77f616f64217e0bcf4811916ccd9b5935e5bea0e4a0
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/node@npm:^20.5.7":
|
||||
version: 20.5.7
|
||||
resolution: "@types/node@npm:20.5.7"
|
||||
@@ -7208,15 +7171,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"cbor-x@npm:^1.4.1":
|
||||
version: 1.5.3
|
||||
resolution: "cbor-x@npm:1.5.3"
|
||||
"cbor-x@npm:^1.5.2":
|
||||
version: 1.5.4
|
||||
resolution: "cbor-x@npm:1.5.4"
|
||||
dependencies:
|
||||
cbor-extract: "npm:^2.1.1"
|
||||
dependenciesMeta:
|
||||
cbor-extract:
|
||||
optional: true
|
||||
checksum: d4df85b33969826f4c96a4b4a8fbe03132fb0817fba876f16d41ad6d1a7d2668ec04c923f313220506029cc2b5ab212901ba24b4594d0115e0f527ef31506fbf
|
||||
checksum: 742aea498abfe004a7ff4db2a1c0e00d9e9c1d89db4ad9aa94a9b886cd2ce10a133f20e32788c83696eef368e18c2b5bc82e4b1480c5af91937816a5630989d6
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -7848,12 +7811,12 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"cross-fetch@npm:^3.1.5":
|
||||
version: 3.1.6
|
||||
resolution: "cross-fetch@npm:3.1.6"
|
||||
"cross-fetch@npm:^4.0.0":
|
||||
version: 4.0.0
|
||||
resolution: "cross-fetch@npm:4.0.0"
|
||||
dependencies:
|
||||
node-fetch: "npm:^2.6.11"
|
||||
checksum: a8989fca821cae97520976d00f85ce7c3ab8af7e00cc06c94fd94c49ada6847f4cdeabca8e0ebd4aa6c7343f70bea7e0c64d5910b846aab218136a450585aa61
|
||||
node-fetch: "npm:^2.6.12"
|
||||
checksum: 30e86b703a455baca17b7f2088fdd88b71193b39e7cb61f3385511dc6064b7741c816329c0abff8a74d306969455c8797131d056518a981fd4d2424ecd4ab451
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -12007,7 +11970,21 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"node-fetch@npm:^2.6.11, node-fetch@npm:^2.6.7":
|
||||
"node-fetch@npm:^2.6.12":
|
||||
version: 2.7.0
|
||||
resolution: "node-fetch@npm:2.7.0"
|
||||
dependencies:
|
||||
whatwg-url: "npm:^5.0.0"
|
||||
peerDependencies:
|
||||
encoding: ^0.1.0
|
||||
peerDependenciesMeta:
|
||||
encoding:
|
||||
optional: true
|
||||
checksum: a3ad7889038bf6c49046272515d4f0e3167088b40fd37e1cc6eeea745f5a68cec798d55ac3210e2bc51891cb745e3dc30a734cc5f4b4df764f45886881b198b1
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"node-fetch@npm:^2.6.7":
|
||||
version: 2.6.11
|
||||
resolution: "node-fetch@npm:2.6.11"
|
||||
dependencies:
|
||||
|
||||
Reference in New Issue
Block a user