mirror of
https://github.com/standardnotes/server
synced 2026-02-11 17:01:14 -05:00
Compare commits
26 Commits
@standardn
...
@standardn
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1f7e4dd184 | ||
|
|
8b04216998 | ||
|
|
27ff25b70e | ||
|
|
dc3a41e4bb | ||
|
|
94448bb5d8 | ||
|
|
9a568b0f73 | ||
|
|
a1ee491dc5 | ||
|
|
e5c118c262 | ||
|
|
1bef1279e6 | ||
|
|
c511f259c7 | ||
|
|
f77ed8ef94 | ||
|
|
a4929af2ee | ||
|
|
095811dda9 | ||
|
|
480d5879ba | ||
|
|
c0722b173b | ||
|
|
f07c8e4bd4 | ||
|
|
baf4b2c1d2 | ||
|
|
a6039bd99a | ||
|
|
7c0010c902 | ||
|
|
596a0f1a02 | ||
|
|
efda3df09b | ||
|
|
ec35f46d45 | ||
|
|
c64fa2f47c | ||
|
|
6ce42a0101 | ||
|
|
d40c74c072 | ||
|
|
d722206916 |
4
.github/workflows/common-e2e.yml
vendored
4
.github/workflows/common-e2e.yml
vendored
@@ -21,7 +21,7 @@ jobs:
|
||||
e2e:
|
||||
name: (Self Hosting) E2E Test Suite
|
||||
strategy:
|
||||
fail-fast: false
|
||||
fail-fast: true
|
||||
matrix:
|
||||
secondary_db_enabled: [true, false]
|
||||
transition_mode_enabled: [true, false]
|
||||
@@ -70,7 +70,7 @@ jobs:
|
||||
e2e-home-server:
|
||||
name: (Home Server) E2E Test Suite
|
||||
strategy:
|
||||
fail-fast: false
|
||||
fail-fast: true
|
||||
matrix:
|
||||
db_type: [mysql, sqlite]
|
||||
cache_type: [redis, memory]
|
||||
|
||||
38
.pnp.cjs
generated
38
.pnp.cjs
generated
@@ -5442,7 +5442,7 @@ const RAW_RUNTIME_STATE =
|
||||
["prettier", "npm:2.8.8"],\
|
||||
["reflect-metadata", "npm:0.1.13"],\
|
||||
["ts-jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.1.0"],\
|
||||
["typeorm", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:0.3.16"],\
|
||||
["typeorm", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:0.3.17"],\
|
||||
["typescript", "patch:typescript@npm%3A5.0.4#optional!builtin<compat/typescript>::version=5.0.4&hash=b5f058"],\
|
||||
["winston", "npm:3.9.0"]\
|
||||
],\
|
||||
@@ -5572,7 +5572,7 @@ const RAW_RUNTIME_STATE =
|
||||
["reflect-metadata", "npm:0.1.13"],\
|
||||
["sqlite3", "virtual:31b5a94a105c89c9294c3d524a7f8929fe63ee5a2efadf21951ca4c0cfd2ecf02e8f4ef5a066bbda091f1e3a56e57c6749069a080618c96b22e51131a330fc4a#npm:5.1.6"],\
|
||||
["ts-jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.1.0"],\
|
||||
["typeorm", "virtual:31b5a94a105c89c9294c3d524a7f8929fe63ee5a2efadf21951ca4c0cfd2ecf02e8f4ef5a066bbda091f1e3a56e57c6749069a080618c96b22e51131a330fc4a#npm:0.3.16"],\
|
||||
["typeorm", "virtual:31b5a94a105c89c9294c3d524a7f8929fe63ee5a2efadf21951ca4c0cfd2ecf02e8f4ef5a066bbda091f1e3a56e57c6749069a080618c96b22e51131a330fc4a#npm:0.3.17"],\
|
||||
["typescript", "patch:typescript@npm%3A5.0.4#optional!builtin<compat/typescript>::version=5.0.4&hash=b5f058"],\
|
||||
["ua-parser-js", "npm:1.0.35"],\
|
||||
["uuid", "npm:9.0.0"],\
|
||||
@@ -5697,7 +5697,7 @@ const RAW_RUNTIME_STATE =
|
||||
["prettier", "npm:2.8.8"],\
|
||||
["reflect-metadata", "npm:0.1.13"],\
|
||||
["ts-jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.1.0"],\
|
||||
["typeorm", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:0.3.16"],\
|
||||
["typeorm", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:0.3.17"],\
|
||||
["typescript", "patch:typescript@npm%3A5.0.4#optional!builtin<compat/typescript>::version=5.0.4&hash=b5f058"],\
|
||||
["winston", "npm:3.9.0"]\
|
||||
],\
|
||||
@@ -5896,7 +5896,7 @@ const RAW_RUNTIME_STATE =
|
||||
["reflect-metadata", "npm:0.1.13"],\
|
||||
["sqlite3", "virtual:31b5a94a105c89c9294c3d524a7f8929fe63ee5a2efadf21951ca4c0cfd2ecf02e8f4ef5a066bbda091f1e3a56e57c6749069a080618c96b22e51131a330fc4a#npm:5.1.6"],\
|
||||
["ts-jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.1.0"],\
|
||||
["typeorm", "virtual:365b8c88cdf194291829ee28b79556e2328175d26a621363e703848100bea0042e9500db2a1206c9bbc3a4a76a1d169639ef774b2ea3a1a98584a9936b58c6be#npm:0.3.16"],\
|
||||
["typeorm", "virtual:365b8c88cdf194291829ee28b79556e2328175d26a621363e703848100bea0042e9500db2a1206c9bbc3a4a76a1d169639ef774b2ea3a1a98584a9936b58c6be#npm:0.3.17"],\
|
||||
["typescript", "patch:typescript@npm%3A5.0.4#optional!builtin<compat/typescript>::version=5.0.4&hash=b5f058"],\
|
||||
["winston", "npm:3.9.0"]\
|
||||
],\
|
||||
@@ -5935,7 +5935,7 @@ const RAW_RUNTIME_STATE =
|
||||
["prettier", "npm:2.8.8"],\
|
||||
["reflect-metadata", "npm:0.1.13"],\
|
||||
["ts-jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.1.0"],\
|
||||
["typeorm", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:0.3.16"],\
|
||||
["typeorm", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:0.3.17"],\
|
||||
["typescript", "patch:typescript@npm%3A5.0.4#optional!builtin<compat/typescript>::version=5.0.4&hash=b5f058"],\
|
||||
["winston", "npm:3.9.0"]\
|
||||
],\
|
||||
@@ -6094,7 +6094,7 @@ const RAW_RUNTIME_STATE =
|
||||
["semver", "npm:7.5.1"],\
|
||||
["sqlite3", "virtual:31b5a94a105c89c9294c3d524a7f8929fe63ee5a2efadf21951ca4c0cfd2ecf02e8f4ef5a066bbda091f1e3a56e57c6749069a080618c96b22e51131a330fc4a#npm:5.1.6"],\
|
||||
["ts-jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.1.0"],\
|
||||
["typeorm", "virtual:365b8c88cdf194291829ee28b79556e2328175d26a621363e703848100bea0042e9500db2a1206c9bbc3a4a76a1d169639ef774b2ea3a1a98584a9936b58c6be#npm:0.3.16"],\
|
||||
["typeorm", "virtual:365b8c88cdf194291829ee28b79556e2328175d26a621363e703848100bea0042e9500db2a1206c9bbc3a4a76a1d169639ef774b2ea3a1a98584a9936b58c6be#npm:0.3.17"],\
|
||||
["typescript", "patch:typescript@npm%3A5.0.4#optional!builtin<compat/typescript>::version=5.0.4&hash=b5f058"],\
|
||||
["ua-parser-js", "npm:1.0.35"],\
|
||||
["uuid", "npm:9.0.0"],\
|
||||
@@ -6174,7 +6174,7 @@ const RAW_RUNTIME_STATE =
|
||||
["prettier", "npm:2.8.8"],\
|
||||
["reflect-metadata", "npm:0.1.13"],\
|
||||
["ts-jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.1.0"],\
|
||||
["typeorm", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:0.3.16"],\
|
||||
["typeorm", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:0.3.17"],\
|
||||
["typescript", "patch:typescript@npm%3A5.0.4#optional!builtin<compat/typescript>::version=5.0.4&hash=b5f058"],\
|
||||
["winston", "npm:3.9.0"]\
|
||||
],\
|
||||
@@ -17059,17 +17059,17 @@ const RAW_RUNTIME_STATE =
|
||||
}]\
|
||||
]],\
|
||||
["typeorm", [\
|
||||
["npm:0.3.16", {\
|
||||
"packageLocation": "./.yarn/cache/typeorm-npm-0.3.16-5ac12a7afc-19803f935e.zip/node_modules/typeorm/",\
|
||||
["npm:0.3.17", {\
|
||||
"packageLocation": "./.yarn/cache/typeorm-npm-0.3.17-f8c2578e7f-bae071e097.zip/node_modules/typeorm/",\
|
||||
"packageDependencies": [\
|
||||
["typeorm", "npm:0.3.16"]\
|
||||
["typeorm", "npm:0.3.17"]\
|
||||
],\
|
||||
"linkType": "SOFT"\
|
||||
}],\
|
||||
["virtual:31b5a94a105c89c9294c3d524a7f8929fe63ee5a2efadf21951ca4c0cfd2ecf02e8f4ef5a066bbda091f1e3a56e57c6749069a080618c96b22e51131a330fc4a#npm:0.3.16", {\
|
||||
"packageLocation": "./.yarn/__virtual__/typeorm-virtual-3f6407120b/0/cache/typeorm-npm-0.3.16-5ac12a7afc-19803f935e.zip/node_modules/typeorm/",\
|
||||
["virtual:31b5a94a105c89c9294c3d524a7f8929fe63ee5a2efadf21951ca4c0cfd2ecf02e8f4ef5a066bbda091f1e3a56e57c6749069a080618c96b22e51131a330fc4a#npm:0.3.17", {\
|
||||
"packageLocation": "./.yarn/__virtual__/typeorm-virtual-39f2df0bb4/0/cache/typeorm-npm-0.3.17-f8c2578e7f-bae071e097.zip/node_modules/typeorm/",\
|
||||
"packageDependencies": [\
|
||||
["typeorm", "virtual:31b5a94a105c89c9294c3d524a7f8929fe63ee5a2efadf21951ca4c0cfd2ecf02e8f4ef5a066bbda091f1e3a56e57c6749069a080618c96b22e51131a330fc4a#npm:0.3.16"],\
|
||||
["typeorm", "virtual:31b5a94a105c89c9294c3d524a7f8929fe63ee5a2efadf21951ca4c0cfd2ecf02e8f4ef5a066bbda091f1e3a56e57c6749069a080618c96b22e51131a330fc4a#npm:0.3.17"],\
|
||||
["@google-cloud/spanner", null],\
|
||||
["@sap/hana-client", null],\
|
||||
["@sqltools/formatter", "npm:1.2.5"],\
|
||||
@@ -17158,10 +17158,10 @@ const RAW_RUNTIME_STATE =
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}],\
|
||||
["virtual:365b8c88cdf194291829ee28b79556e2328175d26a621363e703848100bea0042e9500db2a1206c9bbc3a4a76a1d169639ef774b2ea3a1a98584a9936b58c6be#npm:0.3.16", {\
|
||||
"packageLocation": "./.yarn/__virtual__/typeorm-virtual-6adc0d3ce7/0/cache/typeorm-npm-0.3.16-5ac12a7afc-19803f935e.zip/node_modules/typeorm/",\
|
||||
["virtual:365b8c88cdf194291829ee28b79556e2328175d26a621363e703848100bea0042e9500db2a1206c9bbc3a4a76a1d169639ef774b2ea3a1a98584a9936b58c6be#npm:0.3.17", {\
|
||||
"packageLocation": "./.yarn/__virtual__/typeorm-virtual-bfb7ebf128/0/cache/typeorm-npm-0.3.17-f8c2578e7f-bae071e097.zip/node_modules/typeorm/",\
|
||||
"packageDependencies": [\
|
||||
["typeorm", "virtual:365b8c88cdf194291829ee28b79556e2328175d26a621363e703848100bea0042e9500db2a1206c9bbc3a4a76a1d169639ef774b2ea3a1a98584a9936b58c6be#npm:0.3.16"],\
|
||||
["typeorm", "virtual:365b8c88cdf194291829ee28b79556e2328175d26a621363e703848100bea0042e9500db2a1206c9bbc3a4a76a1d169639ef774b2ea3a1a98584a9936b58c6be#npm:0.3.17"],\
|
||||
["@google-cloud/spanner", null],\
|
||||
["@sap/hana-client", null],\
|
||||
["@sqltools/formatter", "npm:1.2.5"],\
|
||||
@@ -17250,10 +17250,10 @@ const RAW_RUNTIME_STATE =
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}],\
|
||||
["virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:0.3.16", {\
|
||||
"packageLocation": "./.yarn/__virtual__/typeorm-virtual-fc9b7b780b/0/cache/typeorm-npm-0.3.16-5ac12a7afc-19803f935e.zip/node_modules/typeorm/",\
|
||||
["virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:0.3.17", {\
|
||||
"packageLocation": "./.yarn/__virtual__/typeorm-virtual-bfa664706d/0/cache/typeorm-npm-0.3.17-f8c2578e7f-bae071e097.zip/node_modules/typeorm/",\
|
||||
"packageDependencies": [\
|
||||
["typeorm", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:0.3.16"],\
|
||||
["typeorm", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:0.3.17"],\
|
||||
["@google-cloud/spanner", null],\
|
||||
["@sap/hana-client", null],\
|
||||
["@sqltools/formatter", "npm:1.2.5"],\
|
||||
|
||||
Binary file not shown.
@@ -3,6 +3,31 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [2.26.1](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.26.0...@standardnotes/analytics@2.26.1) (2023-09-01)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* inserting revisions instead of upsert ([#803](https://github.com/standardnotes/server/issues/803)) ([27ff25b](https://github.com/standardnotes/server/commit/27ff25b70e6b65dfe89aa35582422dce682a4105))
|
||||
|
||||
# [2.26.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.25.21...@standardnotes/analytics@2.26.0) (2023-09-01)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **analytics:** throwing errors on unexisting users ([c511f25](https://github.com/standardnotes/server/commit/c511f259c765fe5cb5b022213d2a59d67390a3c4))
|
||||
* remove the alive and kicking info logs on workers ([1bef127](https://github.com/standardnotes/server/commit/1bef1279e6dbf3cbdfa87e44aa9108ed6dbb3b0f))
|
||||
|
||||
### Features
|
||||
|
||||
* send websocket event to user when a message is sent ([#802](https://github.com/standardnotes/server/issues/802)) ([9a568b0](https://github.com/standardnotes/server/commit/9a568b0f73078ab74d4771bac469903a124e67da))
|
||||
|
||||
## [2.25.21](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.25.20...@standardnotes/analytics@2.25.21) (2023-08-31)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/analytics
|
||||
|
||||
## [2.25.20](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.25.19...@standardnotes/analytics@2.25.20) (2023-08-31)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/analytics
|
||||
|
||||
## [2.25.19](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.25.18...@standardnotes/analytics@2.25.19) (2023-08-30)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/analytics
|
||||
|
||||
@@ -22,6 +22,4 @@ void container.load().then((container) => {
|
||||
|
||||
const subscriberFactory: DomainEventSubscriberFactoryInterface = container.get(TYPES.DomainEventSubscriberFactory)
|
||||
subscriberFactory.create().start()
|
||||
|
||||
setInterval(() => logger.info('Alive and kicking!'), 20 * 60 * 1000)
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/analytics",
|
||||
"version": "2.25.19",
|
||||
"version": "2.26.1",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
@@ -53,7 +53,7 @@
|
||||
"mixpanel": "^0.17.0",
|
||||
"mysql2": "^3.0.1",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"typeorm": "^0.3.15",
|
||||
"typeorm": "^0.3.17",
|
||||
"winston": "^3.8.1"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
|
||||
@@ -41,13 +41,13 @@ export class AccountDeletionRequestedEventHandler implements DomainEventHandlerI
|
||||
Period.ThisMonth,
|
||||
])
|
||||
|
||||
await this.analyticsEntityRepository.remove(analyticsEntity)
|
||||
|
||||
if (this.mixpanelClient !== null) {
|
||||
this.mixpanelClient.track(event.type, {
|
||||
distinct_id: analyticsEntity.id.toString(),
|
||||
user_created_at: this.timer.convertMicrosecondsToDate(event.payload.userCreatedAtTimestamp),
|
||||
})
|
||||
}
|
||||
|
||||
await this.analyticsEntityRepository.remove(analyticsEntity)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,11 @@ export class PaymentFailedEventHandler implements DomainEventHandlerInterface {
|
||||
) {}
|
||||
|
||||
async handle(event: PaymentFailedEvent): Promise<void> {
|
||||
const { analyticsId } = await this.getUserAnalyticsId.execute({ userEmail: event.payload.userEmail })
|
||||
const analyticsMetadataOrError = await this.getUserAnalyticsId.execute({ userEmail: event.payload.userEmail })
|
||||
if (analyticsMetadataOrError.isFailed()) {
|
||||
return
|
||||
}
|
||||
const { analyticsId } = analyticsMetadataOrError.getValue()
|
||||
await this.analyticsStore.markActivity([AnalyticsActivity.PaymentFailed], analyticsId, [
|
||||
Period.Today,
|
||||
Period.ThisWeek,
|
||||
|
||||
@@ -88,7 +88,11 @@ export class PaymentSuccessEventHandler implements DomainEventHandlerInterface {
|
||||
) {}
|
||||
|
||||
async handle(event: PaymentSuccessEvent): Promise<void> {
|
||||
const { analyticsId } = await this.getUserAnalyticsId.execute({ userEmail: event.payload.userEmail })
|
||||
const analyticsMetadataOrError = await this.getUserAnalyticsId.execute({ userEmail: event.payload.userEmail })
|
||||
if (analyticsMetadataOrError.isFailed()) {
|
||||
return
|
||||
}
|
||||
const { analyticsId } = analyticsMetadataOrError.getValue()
|
||||
await this.analyticsStore.markActivity([AnalyticsActivity.PaymentSuccess], analyticsId, [
|
||||
Period.Today,
|
||||
Period.ThisWeek,
|
||||
|
||||
@@ -17,8 +17,11 @@ export class RefundProcessedEventHandler implements DomainEventHandlerInterface
|
||||
) {}
|
||||
|
||||
async handle(event: RefundProcessedEvent): Promise<void> {
|
||||
const { analyticsId } = await this.getUserAnalyticsId.execute({ userEmail: event.payload.userEmail })
|
||||
|
||||
const analyticsMetadataOrError = await this.getUserAnalyticsId.execute({ userEmail: event.payload.userEmail })
|
||||
if (analyticsMetadataOrError.isFailed()) {
|
||||
return
|
||||
}
|
||||
const { analyticsId } = analyticsMetadataOrError.getValue()
|
||||
await this.statisticsStore.incrementMeasure(StatisticMeasureName.NAMES.Refunds, event.payload.amount, [
|
||||
Period.Today,
|
||||
Period.ThisWeek,
|
||||
|
||||
@@ -13,7 +13,11 @@ export class SessionCreatedEventHandler implements DomainEventHandlerInterface {
|
||||
) {}
|
||||
|
||||
async handle(event: SessionCreatedEvent): Promise<void> {
|
||||
const { analyticsId } = await this.getUserAnalyticsId.execute({ userUuid: event.payload.userUuid })
|
||||
const analyticsMetadataOrError = await this.getUserAnalyticsId.execute({ userUuid: event.payload.userUuid })
|
||||
if (analyticsMetadataOrError.isFailed()) {
|
||||
return
|
||||
}
|
||||
const { analyticsId } = analyticsMetadataOrError.getValue()
|
||||
|
||||
if (this.mixpanelClient !== null) {
|
||||
this.mixpanelClient.track(event.type, {
|
||||
|
||||
@@ -13,7 +13,11 @@ export class SessionRefreshedEventHandler implements DomainEventHandlerInterface
|
||||
) {}
|
||||
|
||||
async handle(event: SessionRefreshedEvent): Promise<void> {
|
||||
const { analyticsId } = await this.getUserAnalyticsId.execute({ userUuid: event.payload.userUuid })
|
||||
const analyticsMetadataOrError = await this.getUserAnalyticsId.execute({ userUuid: event.payload.userUuid })
|
||||
if (analyticsMetadataOrError.isFailed()) {
|
||||
return
|
||||
}
|
||||
const { analyticsId } = analyticsMetadataOrError.getValue()
|
||||
|
||||
if (this.mixpanelClient !== null) {
|
||||
this.mixpanelClient.track(event.type, {
|
||||
|
||||
@@ -29,7 +29,11 @@ export class SubscriptionCancelledEventHandler implements DomainEventHandlerInte
|
||||
) {}
|
||||
|
||||
async handle(event: SubscriptionCancelledEvent): Promise<void> {
|
||||
const { analyticsId, userUuid } = await this.getUserAnalyticsId.execute({ userEmail: event.payload.userEmail })
|
||||
const analyticsMetadataOrError = await this.getUserAnalyticsId.execute({ userEmail: event.payload.userEmail })
|
||||
if (analyticsMetadataOrError.isFailed()) {
|
||||
return
|
||||
}
|
||||
const { analyticsId, userUuid } = analyticsMetadataOrError.getValue()
|
||||
await this.analyticsStore.markActivity([AnalyticsActivity.SubscriptionCancelled], analyticsId, [
|
||||
Period.Today,
|
||||
Period.ThisWeek,
|
||||
|
||||
@@ -27,7 +27,11 @@ export class SubscriptionExpiredEventHandler implements DomainEventHandlerInterf
|
||||
) {}
|
||||
|
||||
async handle(event: SubscriptionExpiredEvent): Promise<void> {
|
||||
const { analyticsId, userUuid } = await this.getUserAnalyticsId.execute({ userEmail: event.payload.userEmail })
|
||||
const analyticsMetadataOrError = await this.getUserAnalyticsId.execute({ userEmail: event.payload.userEmail })
|
||||
if (analyticsMetadataOrError.isFailed()) {
|
||||
return
|
||||
}
|
||||
const { analyticsId, userUuid } = analyticsMetadataOrError.getValue()
|
||||
await this.analyticsStore.markActivity(
|
||||
[AnalyticsActivity.SubscriptionExpired, AnalyticsActivity.ExistingCustomersChurn],
|
||||
analyticsId,
|
||||
|
||||
@@ -29,7 +29,11 @@ export class SubscriptionPurchasedEventHandler implements DomainEventHandlerInte
|
||||
) {}
|
||||
|
||||
async handle(event: SubscriptionPurchasedEvent): Promise<void> {
|
||||
const { analyticsId, userUuid } = await this.getUserAnalyticsId.execute({ userEmail: event.payload.userEmail })
|
||||
const analyticsMetadataOrError = await this.getUserAnalyticsId.execute({ userEmail: event.payload.userEmail })
|
||||
if (analyticsMetadataOrError.isFailed()) {
|
||||
return
|
||||
}
|
||||
const { analyticsId, userUuid } = analyticsMetadataOrError.getValue()
|
||||
await this.analyticsStore.markActivity([AnalyticsActivity.SubscriptionPurchased], analyticsId, [
|
||||
Period.Today,
|
||||
Period.ThisWeek,
|
||||
|
||||
@@ -19,7 +19,11 @@ export class SubscriptionReactivatedEventHandler implements DomainEventHandlerIn
|
||||
) {}
|
||||
|
||||
async handle(event: SubscriptionReactivatedEvent): Promise<void> {
|
||||
const { analyticsId } = await this.getUserAnalyticsId.execute({ userEmail: event.payload.userEmail })
|
||||
const analyticsMetadataOrError = await this.getUserAnalyticsId.execute({ userEmail: event.payload.userEmail })
|
||||
if (analyticsMetadataOrError.isFailed()) {
|
||||
return
|
||||
}
|
||||
const { analyticsId } = analyticsMetadataOrError.getValue()
|
||||
await this.analyticsStore.markActivity([AnalyticsActivity.SubscriptionReactivated], analyticsId, [
|
||||
Period.Today,
|
||||
Period.ThisWeek,
|
||||
|
||||
@@ -27,7 +27,11 @@ export class SubscriptionRefundedEventHandler implements DomainEventHandlerInter
|
||||
) {}
|
||||
|
||||
async handle(event: SubscriptionRefundedEvent): Promise<void> {
|
||||
const { analyticsId, userUuid } = await this.getUserAnalyticsId.execute({ userEmail: event.payload.userEmail })
|
||||
const analyticsMetadataOrError = await this.getUserAnalyticsId.execute({ userEmail: event.payload.userEmail })
|
||||
if (analyticsMetadataOrError.isFailed()) {
|
||||
return
|
||||
}
|
||||
const { analyticsId, userUuid } = analyticsMetadataOrError.getValue()
|
||||
await this.analyticsStore.markActivity([AnalyticsActivity.SubscriptionRefunded], analyticsId, [
|
||||
Period.Today,
|
||||
Period.ThisWeek,
|
||||
|
||||
@@ -26,7 +26,11 @@ export class SubscriptionRenewedEventHandler implements DomainEventHandlerInterf
|
||||
) {}
|
||||
|
||||
async handle(event: SubscriptionRenewedEvent): Promise<void> {
|
||||
const { analyticsId, userUuid } = await this.getUserAnalyticsId.execute({ userEmail: event.payload.userEmail })
|
||||
const analyticsMetadataOrError = await this.getUserAnalyticsId.execute({ userEmail: event.payload.userEmail })
|
||||
if (analyticsMetadataOrError.isFailed()) {
|
||||
return
|
||||
}
|
||||
const { analyticsId, userUuid } = analyticsMetadataOrError.getValue()
|
||||
await this.analyticsStore.markActivity([AnalyticsActivity.SubscriptionRenewed], analyticsId, [
|
||||
Period.Today,
|
||||
Period.ThisWeek,
|
||||
|
||||
@@ -24,23 +24,18 @@ describe('GetUserAnalyticsId', () => {
|
||||
})
|
||||
|
||||
it('should return analytics id for a user by uuid', async () => {
|
||||
expect((await createUseCase().execute({ userUuid: '1-2-3' })).analyticsId).toEqual(123)
|
||||
expect((await createUseCase().execute({ userUuid: '1-2-3' })).getValue().analyticsId).toEqual(123)
|
||||
})
|
||||
|
||||
it('should return analytics id for a user by email', async () => {
|
||||
expect((await createUseCase().execute({ userEmail: 'test@test.te' })).analyticsId).toEqual(123)
|
||||
expect((await createUseCase().execute({ userEmail: 'test@test.te' })).getValue().analyticsId).toEqual(123)
|
||||
})
|
||||
|
||||
it('should throw error if user is missing analytics entity', async () => {
|
||||
analyticsEntityRepository.findOneByUserUuid = jest.fn().mockReturnValue(null)
|
||||
let error = null
|
||||
|
||||
try {
|
||||
await createUseCase().execute({ userUuid: '1-2-3' })
|
||||
} catch (caughtError) {
|
||||
error = caughtError
|
||||
}
|
||||
const result = await createUseCase().execute({ userUuid: '1-2-3' })
|
||||
|
||||
expect(error).not.toBeNull()
|
||||
expect(result.isFailed()).toEqual(true)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
import { inject, injectable } from 'inversify'
|
||||
import { Username, Uuid } from '@standardnotes/domain-core'
|
||||
import { Result, UseCaseInterface, Username, Uuid } from '@standardnotes/domain-core'
|
||||
|
||||
import TYPES from '../../../Bootstrap/Types'
|
||||
import { AnalyticsEntityRepositoryInterface } from '../../Entity/AnalyticsEntityRepositoryInterface'
|
||||
import { UseCaseInterface } from '../UseCaseInterface'
|
||||
import { GetUserAnalyticsIdDTO } from './GetUserAnalyticsIdDTO'
|
||||
import { GetUserAnalyticsIdResponse } from './GetUserAnalyticsIdResponse'
|
||||
|
||||
@injectable()
|
||||
export class GetUserAnalyticsId implements UseCaseInterface {
|
||||
export class GetUserAnalyticsId implements UseCaseInterface<GetUserAnalyticsIdResponse> {
|
||||
constructor(
|
||||
@inject(TYPES.AnalyticsEntityRepository) private analyticsEntityRepository: AnalyticsEntityRepositoryInterface,
|
||||
) {}
|
||||
|
||||
async execute(dto: GetUserAnalyticsIdDTO): Promise<GetUserAnalyticsIdResponse> {
|
||||
async execute(dto: GetUserAnalyticsIdDTO): Promise<Result<GetUserAnalyticsIdResponse>> {
|
||||
let analyticsEntity = null
|
||||
if (dto.userUuid) {
|
||||
analyticsEntity = await this.analyticsEntityRepository.findOneByUserUuid(dto.userUuid)
|
||||
@@ -22,13 +21,13 @@ export class GetUserAnalyticsId implements UseCaseInterface {
|
||||
}
|
||||
|
||||
if (analyticsEntity === null) {
|
||||
throw new Error(`Could not find analytics entity for user ${dto.userUuid}`)
|
||||
return Result.fail(`Could not find analytics entity ${dto.userUuid}`)
|
||||
}
|
||||
|
||||
return {
|
||||
return Result.ok({
|
||||
analyticsId: analyticsEntity.id,
|
||||
userUuid: Uuid.create(analyticsEntity.userUuid).getValue(),
|
||||
username: Username.create(analyticsEntity.username).getValue(),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,22 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.73.5](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.73.4...@standardnotes/api-gateway@1.73.5) (2023-09-01)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||
|
||||
## [1.73.4](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.73.3...@standardnotes/api-gateway@1.73.4) (2023-09-01)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||
|
||||
## [1.73.3](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.73.2...@standardnotes/api-gateway@1.73.3) (2023-08-31)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||
|
||||
## [1.73.2](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.73.1...@standardnotes/api-gateway@1.73.2) (2023-08-31)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||
|
||||
## [1.73.1](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.73.0...@standardnotes/api-gateway@1.73.1) (2023-08-30)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/api-gateway",
|
||||
"version": "1.73.1",
|
||||
"version": "1.73.5",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,26 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.137.4](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.137.3...@standardnotes/auth-server@1.137.4) (2023-09-01)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* inserting revisions instead of upsert ([#803](https://github.com/standardnotes/server/issues/803)) ([27ff25b](https://github.com/standardnotes/server/commit/27ff25b70e6b65dfe89aa35582422dce682a4105))
|
||||
|
||||
## [1.137.3](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.137.2...@standardnotes/auth-server@1.137.3) (2023-09-01)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* remove the alive and kicking info logs on workers ([1bef127](https://github.com/standardnotes/server/commit/1bef1279e6dbf3cbdfa87e44aa9108ed6dbb3b0f))
|
||||
|
||||
## [1.137.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.137.1...@standardnotes/auth-server@1.137.2) (2023-08-31)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/auth-server
|
||||
|
||||
## [1.137.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.137.0...@standardnotes/auth-server@1.137.1) (2023-08-31)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/auth-server
|
||||
|
||||
# [1.137.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.136.0...@standardnotes/auth-server@1.137.0) (2023-08-30)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -24,6 +24,4 @@ void container.load().then((container) => {
|
||||
TYPES.Auth_DomainEventSubscriberFactory,
|
||||
)
|
||||
subscriberFactory.create().start()
|
||||
|
||||
setInterval(() => logger.info('Alive and kicking!'), 20 * 60 * 1000)
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/auth-server",
|
||||
"version": "1.137.0",
|
||||
"version": "1.137.4",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
@@ -69,7 +69,7 @@
|
||||
"prettyjson": "^1.2.5",
|
||||
"reflect-metadata": "0.1.13",
|
||||
"sqlite3": "^5.1.6",
|
||||
"typeorm": "^0.3.15",
|
||||
"typeorm": "^0.3.17",
|
||||
"ua-parser-js": "^1.0.35",
|
||||
"uuid": "^9.0.0",
|
||||
"winston": "^3.8.1"
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.12.19](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.12.18...@standardnotes/domain-events-infra@1.12.19) (2023-09-01)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/domain-events-infra
|
||||
|
||||
## [1.12.18](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.12.17...@standardnotes/domain-events-infra@1.12.18) (2023-09-01)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/domain-events-infra
|
||||
|
||||
## [1.12.17](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.12.16...@standardnotes/domain-events-infra@1.12.17) (2023-08-31)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/domain-events-infra
|
||||
|
||||
## [1.12.16](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.12.15...@standardnotes/domain-events-infra@1.12.16) (2023-08-30)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/domain-events-infra
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/domain-events-infra",
|
||||
"version": "1.12.16",
|
||||
"version": "1.12.19",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,24 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [2.122.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.121.0...@standardnotes/domain-events@2.122.0) (2023-09-01)
|
||||
|
||||
### Features
|
||||
|
||||
* **syncing-server:** add sending invites via websockets ([#804](https://github.com/standardnotes/server/issues/804)) ([dc3a41e](https://github.com/standardnotes/server/commit/dc3a41e4bb3f3541f812b938fd42a6192e3e20f8))
|
||||
|
||||
# [2.121.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.120.0...@standardnotes/domain-events@2.121.0) (2023-09-01)
|
||||
|
||||
### Features
|
||||
|
||||
* send websocket event to user when a message is sent ([#802](https://github.com/standardnotes/server/issues/802)) ([9a568b0](https://github.com/standardnotes/server/commit/9a568b0f73078ab74d4771bac469903a124e67da))
|
||||
|
||||
# [2.120.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.119.0...@standardnotes/domain-events@2.120.0) (2023-08-31)
|
||||
|
||||
### Features
|
||||
|
||||
* add sending notifications to user via websockets ([#799](https://github.com/standardnotes/server/issues/799)) ([c0722b1](https://github.com/standardnotes/server/commit/c0722b173b71d696568d8e8c5095a22fd219bef6))
|
||||
|
||||
# [2.119.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.118.0...@standardnotes/domain-events@2.119.0) (2023-08-30)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/domain-events",
|
||||
"version": "2.119.0",
|
||||
"version": "2.122.0",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import { DomainEventInterface } from './DomainEventInterface'
|
||||
|
||||
import { MessageSentToUserEventPayload } from './MessageSentToUserEventPayload'
|
||||
|
||||
export interface MessageSentToUserEvent extends DomainEventInterface {
|
||||
type: 'MESSAGE_SENT_TO_USER'
|
||||
payload: MessageSentToUserEventPayload
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
export interface MessageSentToUserEventPayload {
|
||||
message: {
|
||||
uuid: string
|
||||
recipient_uuid: string
|
||||
sender_uuid: string
|
||||
encrypted_message: string
|
||||
replaceability_identifier: string | null
|
||||
created_at_timestamp: number
|
||||
updated_at_timestamp: number
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { DomainEventInterface } from './DomainEventInterface'
|
||||
|
||||
import { NotificationAddedForUserEventPayload } from './NotificationAddedForUserEventPayload'
|
||||
|
||||
export interface NotificationAddedForUserEvent extends DomainEventInterface {
|
||||
type: 'NOTIFICATION_ADDED_FOR_USER'
|
||||
payload: NotificationAddedForUserEventPayload
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
export interface NotificationAddedForUserEventPayload {
|
||||
notification: {
|
||||
uuid: string
|
||||
user_uuid: string
|
||||
type: string
|
||||
payload: string
|
||||
created_at_timestamp: number
|
||||
updated_at_timestamp: number
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { DomainEventInterface } from './DomainEventInterface'
|
||||
|
||||
import { UserInvitedToSharedVaultEventPayload } from './UserInvitedToSharedVaultEventPayload'
|
||||
|
||||
export interface UserInvitedToSharedVaultEvent extends DomainEventInterface {
|
||||
type: 'USER_INVITED_TO_SHARED_VAULT'
|
||||
payload: UserInvitedToSharedVaultEventPayload
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
export interface UserInvitedToSharedVaultEventPayload {
|
||||
invite: {
|
||||
uuid: string
|
||||
shared_vault_uuid: string
|
||||
user_uuid: string
|
||||
sender_uuid: string
|
||||
encrypted_message: string
|
||||
permission: string
|
||||
created_at_timestamp: number
|
||||
updated_at_timestamp: number
|
||||
}
|
||||
}
|
||||
@@ -40,8 +40,12 @@ export * from './Event/ListedAccountDeletedEvent'
|
||||
export * from './Event/ListedAccountDeletedEventPayload'
|
||||
export * from './Event/ListedAccountRequestedEvent'
|
||||
export * from './Event/ListedAccountRequestedEventPayload'
|
||||
export * from './Event/MessageSentToUserEvent'
|
||||
export * from './Event/MessageSentToUserEventPayload'
|
||||
export * from './Event/MuteEmailsSettingChangedEvent'
|
||||
export * from './Event/MuteEmailsSettingChangedEventPayload'
|
||||
export * from './Event/NotificationAddedForUserEvent'
|
||||
export * from './Event/NotificationAddedForUserEventPayload'
|
||||
export * from './Event/PaymentFailedEvent'
|
||||
export * from './Event/PaymentFailedEventPayload'
|
||||
export * from './Event/PaymentsAccountDeletedEvent'
|
||||
@@ -96,6 +100,8 @@ export * from './Event/UserDisabledSessionUserAgentLoggingEvent'
|
||||
export * from './Event/UserDisabledSessionUserAgentLoggingEventPayload'
|
||||
export * from './Event/UserEmailChangedEvent'
|
||||
export * from './Event/UserEmailChangedEventPayload'
|
||||
export * from './Event/UserInvitedToSharedVaultEvent'
|
||||
export * from './Event/UserInvitedToSharedVaultEventPayload'
|
||||
export * from './Event/UserRegisteredEvent'
|
||||
export * from './Event/UserRegisteredEventPayload'
|
||||
export * from './Event/UserRolesChangedEvent'
|
||||
|
||||
@@ -3,6 +3,26 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.11.29](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.11.28...@standardnotes/event-store@1.11.29) (2023-09-01)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* inserting revisions instead of upsert ([#803](https://github.com/standardnotes/server/issues/803)) ([27ff25b](https://github.com/standardnotes/server/commit/27ff25b70e6b65dfe89aa35582422dce682a4105))
|
||||
|
||||
## [1.11.28](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.11.27...@standardnotes/event-store@1.11.28) (2023-09-01)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* remove the alive and kicking info logs on workers ([1bef127](https://github.com/standardnotes/server/commit/1bef1279e6dbf3cbdfa87e44aa9108ed6dbb3b0f))
|
||||
|
||||
## [1.11.27](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.11.26...@standardnotes/event-store@1.11.27) (2023-08-31)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/event-store
|
||||
|
||||
## [1.11.26](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.11.25...@standardnotes/event-store@1.11.26) (2023-08-31)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/event-store
|
||||
|
||||
## [1.11.25](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.11.24...@standardnotes/event-store@1.11.25) (2023-08-30)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/event-store
|
||||
|
||||
@@ -18,6 +18,4 @@ void container.load().then((container) => {
|
||||
|
||||
const subscriberFactory: DomainEventSubscriberFactoryInterface = container.get(TYPES.DomainEventSubscriberFactory)
|
||||
subscriberFactory.create().start()
|
||||
|
||||
setInterval(() => logger.info('Alive and kicking!'), 20 * 60 * 1000)
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/event-store",
|
||||
"version": "1.11.25",
|
||||
"version": "1.11.29",
|
||||
"description": "Event Store Service",
|
||||
"private": true,
|
||||
"main": "dist/src/index.js",
|
||||
@@ -42,7 +42,7 @@
|
||||
"ioredis": "^5.2.4",
|
||||
"mysql2": "^3.0.1",
|
||||
"reflect-metadata": "0.1.13",
|
||||
"typeorm": "^0.3.15",
|
||||
"typeorm": "^0.3.17",
|
||||
"winston": "^3.8.1"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
|
||||
@@ -3,6 +3,24 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.22.8](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.22.7...@standardnotes/files-server@1.22.8) (2023-09-01)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files-server
|
||||
|
||||
## [1.22.7](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.22.6...@standardnotes/files-server@1.22.7) (2023-09-01)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* remove the alive and kicking info logs on workers ([1bef127](https://github.com/standardnotes/files/commit/1bef1279e6dbf3cbdfa87e44aa9108ed6dbb3b0f))
|
||||
|
||||
## [1.22.6](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.22.5...@standardnotes/files-server@1.22.6) (2023-08-31)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files-server
|
||||
|
||||
## [1.22.5](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.22.4...@standardnotes/files-server@1.22.5) (2023-08-31)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files-server
|
||||
|
||||
## [1.22.4](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.22.3...@standardnotes/files-server@1.22.4) (2023-08-30)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files-server
|
||||
|
||||
@@ -24,6 +24,4 @@ void container.load().then((container) => {
|
||||
TYPES.Files_DomainEventSubscriberFactory,
|
||||
)
|
||||
subscriberFactory.create().start()
|
||||
|
||||
setInterval(() => logger.info('Alive and kicking!'), 20 * 60 * 1000)
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/files-server",
|
||||
"version": "1.22.4",
|
||||
"version": "1.22.8",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,42 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.15.19](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.18...@standardnotes/home-server@1.15.19) (2023-09-01)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.15.18](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.17...@standardnotes/home-server@1.15.18) (2023-09-01)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.15.17](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.16...@standardnotes/home-server@1.15.17) (2023-08-31)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.15.16](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.15...@standardnotes/home-server@1.15.16) (2023-08-31)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.15.15](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.14...@standardnotes/home-server@1.15.15) (2023-08-31)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.15.14](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.13...@standardnotes/home-server@1.15.14) (2023-08-31)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.15.13](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.12...@standardnotes/home-server@1.15.13) (2023-08-30)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.15.12](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.11...@standardnotes/home-server@1.15.12) (2023-08-30)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.15.11](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.10...@standardnotes/home-server@1.15.11) (2023-08-30)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.15.10](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.9...@standardnotes/home-server@1.15.10) (2023-08-30)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/home-server",
|
||||
"version": "1.15.10",
|
||||
"version": "1.15.19",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,60 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.30.9](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.30.8...@standardnotes/revisions-server@1.30.9) (2023-09-01)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* inserting revisions instead of upsert ([#803](https://github.com/standardnotes/server/issues/803)) ([27ff25b](https://github.com/standardnotes/server/commit/27ff25b70e6b65dfe89aa35582422dce682a4105))
|
||||
|
||||
## [1.30.8](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.30.7...@standardnotes/revisions-server@1.30.8) (2023-09-01)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **revisions:** add transition start info ([a1ee491](https://github.com/standardnotes/server/commit/a1ee491dc5835bfe9521b34f449085d2f13d5c68))
|
||||
* **revisions:** info logs on total revisions transitioned count ([e5c118c](https://github.com/standardnotes/server/commit/e5c118c262535971b42177db2a5a70d959b1c5d7))
|
||||
|
||||
## [1.30.7](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.30.6...@standardnotes/revisions-server@1.30.7) (2023-08-31)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **revisions:** add more verbose messages about failures in revision transitioning ([a4929af](https://github.com/standardnotes/server/commit/a4929af2ee3df4db9f57c2b0fb250b6828095421))
|
||||
* **revisions:** revisions transition check for total count at the end ([095811d](https://github.com/standardnotes/server/commit/095811dda929e6947af36e096a659f18f1b5f8b8))
|
||||
|
||||
## [1.30.6](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.30.5...@standardnotes/revisions-server@1.30.6) (2023-08-31)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/revisions-server
|
||||
|
||||
## [1.30.5](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.30.4...@standardnotes/revisions-server@1.30.5) (2023-08-31)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **revisions:** indiciate if revision did not save ([a6039bd](https://github.com/standardnotes/server/commit/a6039bd99ac37cc5ee4487336fa42412722c7815))
|
||||
|
||||
## [1.30.4](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.30.3...@standardnotes/revisions-server@1.30.4) (2023-08-31)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* transitionining revisions ([#801](https://github.com/standardnotes/server/issues/801)) ([596a0f1](https://github.com/standardnotes/server/commit/596a0f1a0221ab0636c4c04d17a28c57fe74b620))
|
||||
|
||||
## [1.30.3](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.30.2...@standardnotes/revisions-server@1.30.3) (2023-08-30)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* mongo delete queries ([ec35f46](https://github.com/standardnotes/server/commit/ec35f46d457ec5a5125dc1d0f1a14fb262012caa))
|
||||
|
||||
## [1.30.2](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.30.1...@standardnotes/revisions-server@1.30.2) (2023-08-30)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **revisions:** mongo queries ([6ce42a0](https://github.com/standardnotes/server/commit/6ce42a0101169dd316624651b47a34f87ca35299))
|
||||
|
||||
## [1.30.1](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.30.0...@standardnotes/revisions-server@1.30.1) (2023-08-30)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **revisions:** message in logs for trasitions status updated ([d722206](https://github.com/standardnotes/server/commit/d722206916d358e757f1cad7efeaf9881a1c6246))
|
||||
|
||||
# [1.30.0](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.29.0...@standardnotes/revisions-server@1.30.0) (2023-08-30)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/revisions-server",
|
||||
"version": "1.30.0",
|
||||
"version": "1.30.9",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
@@ -45,7 +45,7 @@
|
||||
"mysql2": "^3.0.1",
|
||||
"reflect-metadata": "0.1.13",
|
||||
"sqlite3": "^5.1.6",
|
||||
"typeorm": "^0.3.15",
|
||||
"typeorm": "^0.3.17",
|
||||
"winston": "^3.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -22,7 +22,7 @@ describe('ItemDumpedEventHandler', () => {
|
||||
dumpRepository.removeDump = jest.fn()
|
||||
|
||||
revisionRepository = {} as jest.Mocked<RevisionRepositoryInterface>
|
||||
revisionRepository.save = jest.fn()
|
||||
revisionRepository.insert = jest.fn()
|
||||
|
||||
revisionRepositoryResolver = {} as jest.Mocked<RevisionRepositoryResolverInterface>
|
||||
revisionRepositoryResolver.resolve = jest.fn().mockReturnValue(revisionRepository)
|
||||
@@ -37,7 +37,7 @@ describe('ItemDumpedEventHandler', () => {
|
||||
it('should save a revision from file dump', async () => {
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(revisionRepository.save).toHaveBeenCalled()
|
||||
expect(revisionRepository.insert).toHaveBeenCalled()
|
||||
expect(dumpRepository.removeDump).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
@@ -46,7 +46,7 @@ describe('ItemDumpedEventHandler', () => {
|
||||
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(revisionRepository.save).not.toHaveBeenCalled()
|
||||
expect(revisionRepository.insert).not.toHaveBeenCalled()
|
||||
expect(dumpRepository.removeDump).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
@@ -55,7 +55,7 @@ describe('ItemDumpedEventHandler', () => {
|
||||
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(revisionRepository.save).not.toHaveBeenCalled()
|
||||
expect(revisionRepository.insert).not.toHaveBeenCalled()
|
||||
expect(dumpRepository.removeDump).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -28,7 +28,7 @@ export class ItemDumpedEventHandler implements DomainEventHandlerInterface {
|
||||
|
||||
const revisionRepository = this.revisionRepositoryResolver.resolve(roleNames)
|
||||
|
||||
await revisionRepository.save(revision)
|
||||
await revisionRepository.insert(revision)
|
||||
|
||||
await this.dumpRepository.removeDump(event.payload.fileDumpPath)
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ export class TransitionStatusUpdatedEventHandler implements DomainEventHandlerIn
|
||||
})
|
||||
|
||||
if (result.isFailed()) {
|
||||
this.logger.error(`Failed to transition items for user ${event.payload.userUuid}: ${result.getError()}`)
|
||||
this.logger.error(`Failed to transition revisions for user ${event.payload.userUuid}: ${result.getError()}`)
|
||||
|
||||
await this.domainEventPublisher.publish(
|
||||
this.domainEventFactory.createTransitionStatusUpdatedEvent({
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ContentType, Dates, Uuid } from '@standardnotes/domain-core'
|
||||
import { ContentType, Dates, UniqueEntityId, Uuid } from '@standardnotes/domain-core'
|
||||
|
||||
import { Revision } from './Revision'
|
||||
|
||||
@@ -19,4 +19,106 @@ describe('Revision', () => {
|
||||
expect(entityOrError.isFailed()).toBeFalsy()
|
||||
expect(entityOrError.getValue().id).not.toBeNull()
|
||||
})
|
||||
|
||||
it('should tell if a revision is identical to another revision', () => {
|
||||
const entity1 = Revision.create(
|
||||
{
|
||||
itemUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
|
||||
userUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
|
||||
content: 'test',
|
||||
contentType: ContentType.create('Note').getValue(),
|
||||
itemsKeyId: 'test',
|
||||
encItemKey: 'test',
|
||||
authHash: 'test',
|
||||
creationDate: new Date(1),
|
||||
dates: Dates.create(new Date(1), new Date(2)).getValue(),
|
||||
},
|
||||
new UniqueEntityId('00000000-0000-0000-0000-000000000000'),
|
||||
).getValue()
|
||||
|
||||
const entity2 = Revision.create(
|
||||
{
|
||||
itemUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
|
||||
userUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
|
||||
content: 'test',
|
||||
contentType: ContentType.create('Note').getValue(),
|
||||
itemsKeyId: 'test',
|
||||
encItemKey: 'test',
|
||||
authHash: 'test',
|
||||
creationDate: new Date(1),
|
||||
dates: Dates.create(new Date(1), new Date(2)).getValue(),
|
||||
},
|
||||
new UniqueEntityId('00000000-0000-0000-0000-000000000000'),
|
||||
).getValue()
|
||||
|
||||
expect(entity1.isIdenticalTo(entity2)).toBeTruthy()
|
||||
})
|
||||
|
||||
it('should tell if a revision is not identical to another revision', () => {
|
||||
const entity1 = Revision.create(
|
||||
{
|
||||
itemUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
|
||||
userUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
|
||||
content: 'test',
|
||||
contentType: ContentType.create('Note').getValue(),
|
||||
itemsKeyId: 'test',
|
||||
encItemKey: 'test',
|
||||
authHash: 'test',
|
||||
creationDate: new Date(1),
|
||||
dates: Dates.create(new Date(1), new Date(2)).getValue(),
|
||||
},
|
||||
new UniqueEntityId('00000000-0000-0000-0000-000000000000'),
|
||||
).getValue()
|
||||
|
||||
const entity2 = Revision.create(
|
||||
{
|
||||
itemUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
|
||||
userUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
|
||||
content: 'test2',
|
||||
contentType: ContentType.create('Note').getValue(),
|
||||
itemsKeyId: 'test',
|
||||
encItemKey: 'test',
|
||||
authHash: 'test',
|
||||
creationDate: new Date(1),
|
||||
dates: Dates.create(new Date(1), new Date(2)).getValue(),
|
||||
},
|
||||
new UniqueEntityId('00000000-0000-0000-0000-000000000000'),
|
||||
).getValue()
|
||||
|
||||
expect(entity1.isIdenticalTo(entity2)).toBeFalsy()
|
||||
})
|
||||
|
||||
it('should tell if a revision is not identical to another revision id ids do not match', () => {
|
||||
const entity1 = Revision.create(
|
||||
{
|
||||
itemUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
|
||||
userUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
|
||||
content: 'test',
|
||||
contentType: ContentType.create('Note').getValue(),
|
||||
itemsKeyId: 'test',
|
||||
encItemKey: 'test',
|
||||
authHash: 'test',
|
||||
creationDate: new Date(1),
|
||||
dates: Dates.create(new Date(1), new Date(2)).getValue(),
|
||||
},
|
||||
new UniqueEntityId('00000000-0000-0000-0000-000000000000'),
|
||||
).getValue()
|
||||
|
||||
const entity2 = Revision.create(
|
||||
{
|
||||
itemUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
|
||||
userUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
|
||||
content: 'test',
|
||||
contentType: ContentType.create('Note').getValue(),
|
||||
itemsKeyId: 'test',
|
||||
encItemKey: 'test',
|
||||
authHash: 'test',
|
||||
creationDate: new Date(1),
|
||||
dates: Dates.create(new Date(1), new Date(2)).getValue(),
|
||||
},
|
||||
new UniqueEntityId('00000000-0000-0000-0000-000000000001'),
|
||||
).getValue()
|
||||
|
||||
expect(entity1.isIdenticalTo(entity2)).toBeFalsy()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -12,5 +12,5 @@ export interface RevisionRepositoryInterface {
|
||||
findMetadataByItemId(itemUuid: Uuid, userUuid: Uuid): Promise<Array<RevisionMetadata>>
|
||||
updateUserUuid(itemUuid: Uuid, userUuid: Uuid): Promise<void>
|
||||
findByUserUuid(dto: { userUuid: Uuid; offset?: number; limit?: number }): Promise<Array<Revision>>
|
||||
save(revision: Revision): Promise<Revision>
|
||||
insert(revision: Revision): Promise<boolean>
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ describe('CopyRevisions', () => {
|
||||
beforeEach(() => {
|
||||
revisionRepository = {} as jest.Mocked<RevisionRepositoryInterface>
|
||||
revisionRepository.findByItemUuid = jest.fn().mockReturnValue([{} as jest.Mocked<Revision>])
|
||||
revisionRepository.save = jest.fn()
|
||||
revisionRepository.insert = jest.fn()
|
||||
|
||||
revisionRepositoryResolver = {} as jest.Mocked<RevisionRepositoryResolverInterface>
|
||||
revisionRepositoryResolver.resolve = jest.fn().mockReturnValue(revisionRepository)
|
||||
@@ -52,7 +52,7 @@ describe('CopyRevisions', () => {
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBeFalsy()
|
||||
expect(revisionRepository.save).toHaveBeenCalled()
|
||||
expect(revisionRepository.insert).toHaveBeenCalled()
|
||||
expect(result.getValue()).toEqual('Revisions copied')
|
||||
})
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ export class CopyRevisions implements UseCaseInterface<string> {
|
||||
|
||||
const revisionCopy = revisionCopyOrError.getValue()
|
||||
|
||||
await revisionRepository.save(revisionCopy)
|
||||
await revisionRepository.insert(revisionCopy)
|
||||
}
|
||||
|
||||
return Result.ok<string>('Revisions copied')
|
||||
|
||||
@@ -96,7 +96,7 @@ describe('TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser', () => {
|
||||
primaryRevisionRepository.removeByUserUuid = jest.fn().mockResolvedValue(undefined)
|
||||
|
||||
secondaryRevisionRepository = {} as jest.Mocked<RevisionRepositoryInterface>
|
||||
secondaryRevisionRepository.save = jest.fn().mockResolvedValue(undefined)
|
||||
secondaryRevisionRepository.insert = jest.fn().mockResolvedValue(true)
|
||||
secondaryRevisionRepository.removeByUserUuid = jest.fn().mockResolvedValue(undefined)
|
||||
secondaryRevisionRepository.countByUserUuid = jest.fn().mockResolvedValue(2)
|
||||
secondaryRevisionRepository.findOneByUuid = jest
|
||||
@@ -109,6 +109,7 @@ describe('TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser', () => {
|
||||
logger.info = jest.fn()
|
||||
|
||||
timer = {} as jest.Mocked<TimerInterface>
|
||||
timer.sleep = jest.fn()
|
||||
timer.getTimestampInMicroseconds = jest.fn().mockReturnValue(123)
|
||||
timer.convertMicrosecondsToTimeStructure = jest.fn().mockReturnValue({
|
||||
days: 0,
|
||||
@@ -154,9 +155,9 @@ describe('TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser', () => {
|
||||
limit: 1,
|
||||
offset: 1,
|
||||
})
|
||||
expect((secondaryRevisionRepository as RevisionRepositoryInterface).save).toHaveBeenCalledTimes(2)
|
||||
expect((secondaryRevisionRepository as RevisionRepositoryInterface).save).toHaveBeenCalledWith(primaryRevision1)
|
||||
expect((secondaryRevisionRepository as RevisionRepositoryInterface).save).toHaveBeenCalledWith(primaryRevision2)
|
||||
expect((secondaryRevisionRepository as RevisionRepositoryInterface).insert).toHaveBeenCalledTimes(2)
|
||||
expect((secondaryRevisionRepository as RevisionRepositoryInterface).insert).toHaveBeenCalledWith(primaryRevision1)
|
||||
expect((secondaryRevisionRepository as RevisionRepositoryInterface).insert).toHaveBeenCalledWith(primaryRevision2)
|
||||
expect((secondaryRevisionRepository as RevisionRepositoryInterface).removeByUserUuid).not.toHaveBeenCalled()
|
||||
expect(primaryRevisionRepository.removeByUserUuid).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
@@ -174,7 +175,7 @@ describe('TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser', () => {
|
||||
|
||||
expect(logger.error).toHaveBeenCalledTimes(1)
|
||||
expect(logger.error).toHaveBeenCalledWith(
|
||||
'Failed to clean up primary database revisions for user 00000000-0000-0000-0000-000000000000: error',
|
||||
'Failed to clean up primary database revisions for user 00000000-0000-0000-0000-000000000000: Errored when deleting revisions for user 00000000-0000-0000-0000-000000000000: error',
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -219,12 +220,32 @@ describe('TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser', () => {
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBeTruthy()
|
||||
expect(result.getError()).toEqual('error')
|
||||
expect(result.getError()).toEqual(
|
||||
'Errored when migrating revisions for user 00000000-0000-0000-0000-000000000000: error',
|
||||
)
|
||||
|
||||
expect((secondaryRevisionRepository as RevisionRepositoryInterface).removeByUserUuid).toHaveBeenCalledTimes(1)
|
||||
expect(primaryRevisionRepository.removeByUserUuid).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should return an error for a specific revision if it errors when saving to secondary database', async () => {
|
||||
;(secondaryRevisionRepository as RevisionRepositoryInterface).insert = jest
|
||||
.fn()
|
||||
.mockResolvedValueOnce(true)
|
||||
.mockRejectedValueOnce(new Error('error'))
|
||||
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: '84c0f8e8-544a-4c7e-9adf-26209303bc1d',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBeTruthy()
|
||||
expect(result.getError()).toEqual(
|
||||
'Errored when saving revision 00000000-0000-0000-0000-000000000001 to secondary database: error',
|
||||
)
|
||||
})
|
||||
|
||||
it('should log an error if deleting Revisions from secondary database fails upon migration failure', async () => {
|
||||
primaryRevisionRepository.findByUserUuid = jest
|
||||
.fn()
|
||||
@@ -244,7 +265,7 @@ describe('TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser', () => {
|
||||
|
||||
expect(logger.error).toHaveBeenCalledTimes(1)
|
||||
expect(logger.error).toHaveBeenCalledWith(
|
||||
'Failed to clean up secondary database revisions for user 00000000-0000-0000-0000-000000000000: error',
|
||||
'Failed to clean up secondary database revisions for user 00000000-0000-0000-0000-000000000000: Errored when deleting revisions for user 00000000-0000-0000-0000-000000000000: error',
|
||||
)
|
||||
})
|
||||
|
||||
@@ -272,7 +293,7 @@ describe('TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser', () => {
|
||||
|
||||
expect(logger.error).toHaveBeenCalledTimes(1)
|
||||
expect(logger.error).toHaveBeenCalledWith(
|
||||
'Failed to clean up secondary database revisions for user 00000000-0000-0000-0000-000000000000: error',
|
||||
'Failed to clean up secondary database revisions for user 00000000-0000-0000-0000-000000000000: Errored when deleting revisions for user 00000000-0000-0000-0000-000000000000: error',
|
||||
)
|
||||
})
|
||||
|
||||
@@ -350,7 +371,7 @@ describe('TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser', () => {
|
||||
expect(primaryRevisionRepository.countByUserUuid).toHaveBeenCalledWith(
|
||||
Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
)
|
||||
expect((secondaryRevisionRepository as RevisionRepositoryInterface).countByUserUuid).toHaveBeenCalledTimes(1)
|
||||
expect((secondaryRevisionRepository as RevisionRepositoryInterface).countByUserUuid).not.toHaveBeenCalled()
|
||||
expect(primaryRevisionRepository.removeByUserUuid).not.toHaveBeenCalled()
|
||||
expect((secondaryRevisionRepository as RevisionRepositoryInterface).removeByUserUuid).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
@@ -367,7 +388,25 @@ describe('TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser', () => {
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBeTruthy()
|
||||
expect(result.getError()).toEqual('error')
|
||||
expect(result.getError()).toEqual('Errored when checking integrity between primary and secondary database: error')
|
||||
|
||||
expect(primaryRevisionRepository.removeByUserUuid).not.toHaveBeenCalled()
|
||||
expect((secondaryRevisionRepository as RevisionRepositoryInterface).removeByUserUuid).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('should fail if a revisions did not save in the secondary database', async () => {
|
||||
;(secondaryRevisionRepository as RevisionRepositoryInterface).insert = jest.fn().mockResolvedValue(false)
|
||||
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBeTruthy()
|
||||
expect(result.getError()).toEqual(
|
||||
'Failed to save revision 00000000-0000-0000-0000-000000000000 to secondary database',
|
||||
)
|
||||
|
||||
expect(primaryRevisionRepository.removeByUserUuid).not.toHaveBeenCalled()
|
||||
expect((secondaryRevisionRepository as RevisionRepositoryInterface).removeByUserUuid).toHaveBeenCalledTimes(1)
|
||||
|
||||
@@ -26,6 +26,8 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
|
||||
|
||||
const migrationTimeStart = this.timer.getTimestampInMicroseconds()
|
||||
|
||||
this.logger.info(`Transitioning revisions for user ${userUuid.value}`)
|
||||
|
||||
const migrationResult = await this.migrateRevisionsForUser(userUuid)
|
||||
if (migrationResult.isFailed()) {
|
||||
const cleanupResult = await this.deleteRevisionsForUser(userUuid, this.secondRevisionsRepository)
|
||||
@@ -38,6 +40,8 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
|
||||
return Result.fail(migrationResult.getError())
|
||||
}
|
||||
|
||||
await this.allowForSecondaryDatabaseToCatchUp()
|
||||
|
||||
const integrityCheckResult = await this.checkIntegrityBetweenPrimaryAndSecondaryDatabase(userUuid)
|
||||
if (integrityCheckResult.isFailed()) {
|
||||
const cleanupResult = await this.deleteRevisionsForUser(userUuid, this.secondRevisionsRepository)
|
||||
@@ -72,6 +76,7 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
|
||||
private async migrateRevisionsForUser(userUuid: Uuid): Promise<Result<void>> {
|
||||
try {
|
||||
const totalRevisionsCountForUser = await this.primaryRevisionsRepository.countByUserUuid(userUuid)
|
||||
let totalRevisionsCountTransitionedToSecondary = 0
|
||||
const pageSize = 1
|
||||
const totalPages = Math.ceil(totalRevisionsCountForUser / pageSize)
|
||||
for (let currentPage = 1; currentPage <= totalPages; currentPage++) {
|
||||
@@ -84,13 +89,27 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
|
||||
const revisions = await this.primaryRevisionsRepository.findByUserUuid(query)
|
||||
|
||||
for (const revision of revisions) {
|
||||
await (this.secondRevisionsRepository as RevisionRepositoryInterface).save(revision)
|
||||
try {
|
||||
const didSave = await (this.secondRevisionsRepository as RevisionRepositoryInterface).insert(revision)
|
||||
if (!didSave) {
|
||||
return Result.fail(`Failed to save revision ${revision.id.toString()} to secondary database`)
|
||||
}
|
||||
totalRevisionsCountTransitionedToSecondary++
|
||||
} catch (error) {
|
||||
return Result.fail(
|
||||
`Errored when saving revision ${revision.id.toString()} to secondary database: ${
|
||||
(error as Error).message
|
||||
}`,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.logger.info(`Transitioned ${totalRevisionsCountTransitionedToSecondary} revisions to secondary database`)
|
||||
|
||||
return Result.ok()
|
||||
} catch (error) {
|
||||
return Result.fail((error as Error).message)
|
||||
return Result.fail(`Errored when migrating revisions for user ${userUuid.value}: ${(error as Error).message}`)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,22 +122,18 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
|
||||
|
||||
return Result.ok()
|
||||
} catch (error) {
|
||||
return Result.fail((error as Error).message)
|
||||
return Result.fail(`Errored when deleting revisions for user ${userUuid.value}: ${(error as Error).message}`)
|
||||
}
|
||||
}
|
||||
|
||||
private async allowForSecondaryDatabaseToCatchUp(): Promise<void> {
|
||||
const twoSecondsInMilliseconds = 2_000
|
||||
await this.timer.sleep(twoSecondsInMilliseconds)
|
||||
}
|
||||
|
||||
private async checkIntegrityBetweenPrimaryAndSecondaryDatabase(userUuid: Uuid): Promise<Result<boolean>> {
|
||||
try {
|
||||
const totalRevisionsCountForUserInPrimary = await this.primaryRevisionsRepository.countByUserUuid(userUuid)
|
||||
const totalRevisionsCountForUserInSecondary = await (
|
||||
this.secondRevisionsRepository as RevisionRepositoryInterface
|
||||
).countByUserUuid(userUuid)
|
||||
|
||||
if (totalRevisionsCountForUserInPrimary !== totalRevisionsCountForUserInSecondary) {
|
||||
return Result.fail(
|
||||
`Total revisions count for user ${userUuid.value} in primary database (${totalRevisionsCountForUserInPrimary}) does not match total revisions count in secondary database (${totalRevisionsCountForUserInSecondary})`,
|
||||
)
|
||||
}
|
||||
|
||||
const pageSize = 1
|
||||
const totalPages = Math.ceil(totalRevisionsCountForUserInPrimary / pageSize)
|
||||
@@ -152,9 +167,21 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
|
||||
}
|
||||
}
|
||||
|
||||
const totalRevisionsCountForUserInSecondary = await (
|
||||
this.secondRevisionsRepository as RevisionRepositoryInterface
|
||||
).countByUserUuid(userUuid)
|
||||
|
||||
if (totalRevisionsCountForUserInPrimary !== totalRevisionsCountForUserInSecondary) {
|
||||
return Result.fail(
|
||||
`Total revisions count for user ${userUuid.value} in primary database (${totalRevisionsCountForUserInPrimary}) does not match total revisions count in secondary database (${totalRevisionsCountForUserInSecondary})`,
|
||||
)
|
||||
}
|
||||
|
||||
return Result.ok()
|
||||
} catch (error) {
|
||||
return Result.fail((error as Error).message)
|
||||
return Result.fail(
|
||||
`Errored when checking integrity between primary and secondary database: ${(error as Error).message}`,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ export class MongoDBRevisionRepository implements RevisionRepositoryInterface {
|
||||
) {}
|
||||
|
||||
async countByUserUuid(userUuid: Uuid): Promise<number> {
|
||||
return this.mongoRepository.count({ where: { userUuid: { $eq: userUuid.value } } })
|
||||
return this.mongoRepository.count({ userUuid: { $eq: userUuid.value } })
|
||||
}
|
||||
|
||||
async findByUserUuid(dto: { userUuid: Uuid; offset?: number; limit?: number }): Promise<Revision[]> {
|
||||
@@ -39,17 +39,13 @@ export class MongoDBRevisionRepository implements RevisionRepositoryInterface {
|
||||
}
|
||||
|
||||
async removeByUserUuid(userUuid: Uuid): Promise<void> {
|
||||
await this.mongoRepository.deleteMany({ where: { userUuid: { $eq: userUuid.value } } })
|
||||
await this.mongoRepository.deleteMany({ userUuid: userUuid.value })
|
||||
}
|
||||
|
||||
async removeOneByUuid(revisionUuid: Uuid, userUuid: Uuid): Promise<void> {
|
||||
await this.mongoRepository.deleteOne({
|
||||
where: {
|
||||
$and: [
|
||||
{ _id: { $eq: BSON.UUID.createFromHexString(revisionUuid.value) } },
|
||||
{ userUuid: { $eq: userUuid.value } },
|
||||
],
|
||||
},
|
||||
_id: { $eq: BSON.UUID.createFromHexString(revisionUuid.value) },
|
||||
userUuid: { $eq: userUuid.value },
|
||||
})
|
||||
}
|
||||
|
||||
@@ -127,19 +123,11 @@ export class MongoDBRevisionRepository implements RevisionRepositoryInterface {
|
||||
)
|
||||
}
|
||||
|
||||
async save(revision: Revision): Promise<Revision> {
|
||||
async insert(revision: Revision): Promise<boolean> {
|
||||
const persistence = this.revisionMapper.toProjection(revision)
|
||||
|
||||
const { _id, ...rest } = persistence
|
||||
const insertResult = await this.mongoRepository.insertOne(persistence)
|
||||
|
||||
await this.mongoRepository.updateOne(
|
||||
{ _id: { $eq: _id } },
|
||||
{
|
||||
$set: rest,
|
||||
},
|
||||
{ upsert: true },
|
||||
)
|
||||
|
||||
return revision
|
||||
return insertResult.acknowledged
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,12 +106,12 @@ export class SQLRevisionRepository implements RevisionRepositoryInterface {
|
||||
return this.revisionMapper.toDomain(SQLRevision)
|
||||
}
|
||||
|
||||
async save(revision: Revision): Promise<Revision> {
|
||||
async insert(revision: Revision): Promise<boolean> {
|
||||
const SQLRevision = this.revisionMapper.toProjection(revision)
|
||||
|
||||
await this.ormRepository.save(SQLRevision)
|
||||
await this.ormRepository.insert(SQLRevision)
|
||||
|
||||
return revision
|
||||
return true
|
||||
}
|
||||
|
||||
async findMetadataByItemId(itemUuid: Uuid, userUuid: Uuid): Promise<Array<RevisionMetadata>> {
|
||||
|
||||
@@ -3,6 +3,26 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.20.33](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.20.32...@standardnotes/scheduler-server@1.20.33) (2023-09-01)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* inserting revisions instead of upsert ([#803](https://github.com/standardnotes/server/issues/803)) ([27ff25b](https://github.com/standardnotes/server/commit/27ff25b70e6b65dfe89aa35582422dce682a4105))
|
||||
|
||||
## [1.20.32](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.20.31...@standardnotes/scheduler-server@1.20.32) (2023-09-01)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* remove the alive and kicking info logs on workers ([1bef127](https://github.com/standardnotes/server/commit/1bef1279e6dbf3cbdfa87e44aa9108ed6dbb3b0f))
|
||||
|
||||
## [1.20.31](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.20.30...@standardnotes/scheduler-server@1.20.31) (2023-08-31)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/scheduler-server
|
||||
|
||||
## [1.20.30](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.20.29...@standardnotes/scheduler-server@1.20.30) (2023-08-31)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/scheduler-server
|
||||
|
||||
## [1.20.29](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.20.28...@standardnotes/scheduler-server@1.20.29) (2023-08-30)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/scheduler-server
|
||||
|
||||
@@ -22,6 +22,4 @@ void container.load().then((container) => {
|
||||
|
||||
const subscriberFactory: DomainEventSubscriberFactoryInterface = container.get(TYPES.DomainEventSubscriberFactory)
|
||||
subscriberFactory.create().start()
|
||||
|
||||
setInterval(() => logger.info('Alive and kicking!'), 20 * 60 * 1000)
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/scheduler-server",
|
||||
"version": "1.20.29",
|
||||
"version": "1.20.33",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
@@ -37,7 +37,7 @@
|
||||
"ioredis": "^5.2.4",
|
||||
"mysql2": "^3.0.1",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"typeorm": "^0.3.15",
|
||||
"typeorm": "^0.3.17",
|
||||
"winston": "^3.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -3,6 +3,50 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [1.91.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.90.0...@standardnotes/syncing-server@1.91.0) (2023-09-01)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* inserting revisions instead of upsert ([#803](https://github.com/standardnotes/syncing-server-js/issues/803)) ([27ff25b](https://github.com/standardnotes/syncing-server-js/commit/27ff25b70e6b65dfe89aa35582422dce682a4105))
|
||||
|
||||
### Features
|
||||
|
||||
* **syncing-server:** add sending invites via websockets ([#804](https://github.com/standardnotes/syncing-server-js/issues/804)) ([dc3a41e](https://github.com/standardnotes/syncing-server-js/commit/dc3a41e4bb3f3541f812b938fd42a6192e3e20f8))
|
||||
|
||||
# [1.90.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.89.0...@standardnotes/syncing-server@1.90.0) (2023-09-01)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* remove the alive and kicking info logs on workers ([1bef127](https://github.com/standardnotes/syncing-server-js/commit/1bef1279e6dbf3cbdfa87e44aa9108ed6dbb3b0f))
|
||||
|
||||
### Features
|
||||
|
||||
* send websocket event to user when a message is sent ([#802](https://github.com/standardnotes/syncing-server-js/issues/802)) ([9a568b0](https://github.com/standardnotes/syncing-server-js/commit/9a568b0f73078ab74d4771bac469903a124e67da))
|
||||
|
||||
# [1.89.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.88.3...@standardnotes/syncing-server@1.89.0) (2023-08-31)
|
||||
|
||||
### Features
|
||||
|
||||
* add sending notifications to user via websockets ([#799](https://github.com/standardnotes/syncing-server-js/issues/799)) ([c0722b1](https://github.com/standardnotes/syncing-server-js/commit/c0722b173b71d696568d8e8c5095a22fd219bef6))
|
||||
|
||||
## [1.88.3](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.88.2...@standardnotes/syncing-server@1.88.3) (2023-08-31)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **syncing-server:** persistence mapping for deleted field ([baf4b2c](https://github.com/standardnotes/syncing-server-js/commit/baf4b2c1d205929be8c330450dca16c18ad5cdd6))
|
||||
|
||||
## [1.88.2](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.88.1...@standardnotes/syncing-server@1.88.2) (2023-08-31)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* transitionining revisions ([#801](https://github.com/standardnotes/syncing-server-js/issues/801)) ([596a0f1](https://github.com/standardnotes/syncing-server-js/commit/596a0f1a0221ab0636c4c04d17a28c57fe74b620))
|
||||
|
||||
## [1.88.1](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.88.0...@standardnotes/syncing-server@1.88.1) (2023-08-30)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* mongo delete queries ([ec35f46](https://github.com/standardnotes/syncing-server-js/commit/ec35f46d457ec5a5125dc1d0f1a14fb262012caa))
|
||||
|
||||
# [1.88.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.87.0...@standardnotes/syncing-server@1.88.0) (2023-08-30)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -20,6 +20,4 @@ void container.load().then((container) => {
|
||||
TYPES.Sync_DomainEventSubscriberFactory,
|
||||
)
|
||||
subscriberFactory.create().start()
|
||||
|
||||
setInterval(() => logger.info('Alive and kicking!'), 20 * 60 * 1000)
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/syncing-server",
|
||||
"version": "1.88.0",
|
||||
"version": "1.91.0",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
@@ -56,7 +56,7 @@
|
||||
"reflect-metadata": "0.1.13",
|
||||
"semver": "^7.5.1",
|
||||
"sqlite3": "^5.1.6",
|
||||
"typeorm": "^0.3.15",
|
||||
"typeorm": "^0.3.17",
|
||||
"ua-parser-js": "^1.0.35",
|
||||
"uuid": "^9.0.0",
|
||||
"winston": "^3.8.1"
|
||||
|
||||
@@ -161,6 +161,7 @@ import { TriggerTransitionFromPrimaryToSecondaryDatabaseForUser } from '../Domai
|
||||
import { SQLItem } from '../Infra/TypeORM/SQLItem'
|
||||
import { SQLItemPersistenceMapper } from '../Mapping/Persistence/SQLItemPersistenceMapper'
|
||||
import { SQLItemRepository } from '../Infra/TypeORM/SQLItemRepository'
|
||||
import { SendEventToClient } from '../Domain/UseCase/Syncing/SendEventToClient/SendEventToClient'
|
||||
|
||||
export class ContainerConfigLoader {
|
||||
private readonly DEFAULT_CONTENT_SIZE_TRANSFER_LIMIT = 10_000_000
|
||||
@@ -580,10 +581,24 @@ export class ContainerConfigLoader {
|
||||
container.get(TYPES.Sync_DomainEventFactory),
|
||||
),
|
||||
)
|
||||
container
|
||||
.bind<SendEventToClient>(TYPES.Sync_SendEventToClient)
|
||||
.toConstantValue(
|
||||
new SendEventToClient(
|
||||
container.get<DomainEventFactoryInterface>(TYPES.Sync_DomainEventFactory),
|
||||
container.get<DomainEventPublisherInterface>(TYPES.Sync_DomainEventPublisher),
|
||||
),
|
||||
)
|
||||
container
|
||||
.bind<AddNotificationForUser>(TYPES.Sync_AddNotificationForUser)
|
||||
.toConstantValue(
|
||||
new AddNotificationForUser(container.get(TYPES.Sync_NotificationRepository), container.get(TYPES.Sync_Timer)),
|
||||
new AddNotificationForUser(
|
||||
container.get<NotificationRepositoryInterface>(TYPES.Sync_NotificationRepository),
|
||||
container.get<TimerInterface>(TYPES.Sync_Timer),
|
||||
container.get<DomainEventFactoryInterface>(TYPES.Sync_DomainEventFactory),
|
||||
container.get<SendEventToClient>(TYPES.Sync_SendEventToClient),
|
||||
container.get<Logger>(TYPES.Sync_Logger),
|
||||
),
|
||||
)
|
||||
container
|
||||
.bind<AddNotificationsForUsers>(TYPES.Sync_AddNotificationsForUsers)
|
||||
@@ -663,10 +678,13 @@ export class ContainerConfigLoader {
|
||||
.bind<InviteUserToSharedVault>(TYPES.Sync_InviteUserToSharedVault)
|
||||
.toConstantValue(
|
||||
new InviteUserToSharedVault(
|
||||
container.get(TYPES.Sync_SharedVaultRepository),
|
||||
container.get(TYPES.Sync_SharedVaultInviteRepository),
|
||||
container.get(TYPES.Sync_SharedVaultUserRepository),
|
||||
container.get(TYPES.Sync_Timer),
|
||||
container.get<SharedVaultRepositoryInterface>(TYPES.Sync_SharedVaultRepository),
|
||||
container.get<SharedVaultInviteRepositoryInterface>(TYPES.Sync_SharedVaultInviteRepository),
|
||||
container.get<SharedVaultUserRepositoryInterface>(TYPES.Sync_SharedVaultUserRepository),
|
||||
container.get<TimerInterface>(TYPES.Sync_Timer),
|
||||
container.get<DomainEventFactoryInterface>(TYPES.Sync_DomainEventFactory),
|
||||
container.get<SendEventToClient>(TYPES.Sync_SendEventToClient),
|
||||
container.get<Logger>(TYPES.Sync_Logger),
|
||||
),
|
||||
)
|
||||
container
|
||||
@@ -768,7 +786,13 @@ export class ContainerConfigLoader {
|
||||
container
|
||||
.bind<SendMessageToUser>(TYPES.Sync_SendMessageToUser)
|
||||
.toConstantValue(
|
||||
new SendMessageToUser(container.get(TYPES.Sync_MessageRepository), container.get(TYPES.Sync_Timer)),
|
||||
new SendMessageToUser(
|
||||
container.get<MessageRepositoryInterface>(TYPES.Sync_MessageRepository),
|
||||
container.get<TimerInterface>(TYPES.Sync_Timer),
|
||||
container.get<DomainEventFactoryInterface>(TYPES.Sync_DomainEventFactory),
|
||||
container.get<SendEventToClient>(TYPES.Sync_SendEventToClient),
|
||||
container.get<Logger>(TYPES.Sync_Logger),
|
||||
),
|
||||
)
|
||||
container
|
||||
.bind<DeleteMessage>(TYPES.Sync_DeleteMessage)
|
||||
|
||||
@@ -87,6 +87,7 @@ const TYPES = {
|
||||
Sync_TriggerTransitionFromPrimaryToSecondaryDatabaseForUser: Symbol.for(
|
||||
'Sync_TriggerTransitionFromPrimaryToSecondaryDatabaseForUser',
|
||||
),
|
||||
Sync_SendEventToClient: Symbol.for('Sync_SendEventToClient'),
|
||||
// Handlers
|
||||
Sync_AccountDeletionRequestedEventHandler: Symbol.for('Sync_AccountDeletionRequestedEventHandler'),
|
||||
Sync_DuplicateItemSyncedEventHandler: Symbol.for('Sync_DuplicateItemSyncedEventHandler'),
|
||||
|
||||
@@ -5,8 +5,12 @@ import {
|
||||
EmailRequestedEvent,
|
||||
ItemDumpedEvent,
|
||||
ItemRevisionCreationRequestedEvent,
|
||||
MessageSentToUserEvent,
|
||||
NotificationAddedForUserEvent,
|
||||
RevisionsCopyRequestedEvent,
|
||||
TransitionStatusUpdatedEvent,
|
||||
UserInvitedToSharedVaultEvent,
|
||||
WebSocketMessageRequestedEvent,
|
||||
} from '@standardnotes/domain-events'
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
import { DomainEventFactoryInterface } from './DomainEventFactoryInterface'
|
||||
@@ -14,6 +18,96 @@ import { DomainEventFactoryInterface } from './DomainEventFactoryInterface'
|
||||
export class DomainEventFactory implements DomainEventFactoryInterface {
|
||||
constructor(private timer: TimerInterface) {}
|
||||
|
||||
createUserInvitedToSharedVaultEvent(dto: {
|
||||
invite: {
|
||||
uuid: string
|
||||
shared_vault_uuid: string
|
||||
user_uuid: string
|
||||
sender_uuid: string
|
||||
encrypted_message: string
|
||||
permission: string
|
||||
created_at_timestamp: number
|
||||
updated_at_timestamp: number
|
||||
}
|
||||
}): UserInvitedToSharedVaultEvent {
|
||||
return {
|
||||
type: 'USER_INVITED_TO_SHARED_VAULT',
|
||||
createdAt: this.timer.getUTCDate(),
|
||||
meta: {
|
||||
correlation: {
|
||||
userIdentifier: dto.invite.user_uuid,
|
||||
userIdentifierType: 'uuid',
|
||||
},
|
||||
origin: DomainEventService.SyncingServer,
|
||||
},
|
||||
payload: dto,
|
||||
}
|
||||
}
|
||||
|
||||
createMessageSentToUserEvent(dto: {
|
||||
message: {
|
||||
uuid: string
|
||||
recipient_uuid: string
|
||||
sender_uuid: string
|
||||
encrypted_message: string
|
||||
replaceability_identifier: string | null
|
||||
created_at_timestamp: number
|
||||
updated_at_timestamp: number
|
||||
}
|
||||
}): MessageSentToUserEvent {
|
||||
return {
|
||||
type: 'MESSAGE_SENT_TO_USER',
|
||||
createdAt: this.timer.getUTCDate(),
|
||||
meta: {
|
||||
correlation: {
|
||||
userIdentifier: dto.message.recipient_uuid,
|
||||
userIdentifierType: 'uuid',
|
||||
},
|
||||
origin: DomainEventService.SyncingServer,
|
||||
},
|
||||
payload: dto,
|
||||
}
|
||||
}
|
||||
|
||||
createNotificationAddedForUserEvent(dto: {
|
||||
notification: {
|
||||
uuid: string
|
||||
user_uuid: string
|
||||
type: string
|
||||
payload: string
|
||||
created_at_timestamp: number
|
||||
updated_at_timestamp: number
|
||||
}
|
||||
}): NotificationAddedForUserEvent {
|
||||
return {
|
||||
type: 'NOTIFICATION_ADDED_FOR_USER',
|
||||
createdAt: this.timer.getUTCDate(),
|
||||
meta: {
|
||||
correlation: {
|
||||
userIdentifier: dto.notification.user_uuid,
|
||||
userIdentifierType: 'uuid',
|
||||
},
|
||||
origin: DomainEventService.SyncingServer,
|
||||
},
|
||||
payload: dto,
|
||||
}
|
||||
}
|
||||
|
||||
createWebSocketMessageRequestedEvent(dto: { userUuid: string; message: string }): WebSocketMessageRequestedEvent {
|
||||
return {
|
||||
type: 'WEB_SOCKET_MESSAGE_REQUESTED',
|
||||
createdAt: this.timer.getUTCDate(),
|
||||
meta: {
|
||||
correlation: {
|
||||
userIdentifier: dto.userUuid,
|
||||
userIdentifierType: 'uuid',
|
||||
},
|
||||
origin: DomainEventService.SyncingServer,
|
||||
},
|
||||
payload: dto,
|
||||
}
|
||||
}
|
||||
|
||||
createTransitionStatusUpdatedEvent(dto: {
|
||||
userUuid: string
|
||||
transitionType: 'items' | 'revisions'
|
||||
|
||||
@@ -3,11 +3,49 @@ import {
|
||||
EmailRequestedEvent,
|
||||
ItemDumpedEvent,
|
||||
ItemRevisionCreationRequestedEvent,
|
||||
MessageSentToUserEvent,
|
||||
NotificationAddedForUserEvent,
|
||||
RevisionsCopyRequestedEvent,
|
||||
TransitionStatusUpdatedEvent,
|
||||
UserInvitedToSharedVaultEvent,
|
||||
WebSocketMessageRequestedEvent,
|
||||
} from '@standardnotes/domain-events'
|
||||
|
||||
export interface DomainEventFactoryInterface {
|
||||
createWebSocketMessageRequestedEvent(dto: { userUuid: string; message: string }): WebSocketMessageRequestedEvent
|
||||
createUserInvitedToSharedVaultEvent(dto: {
|
||||
invite: {
|
||||
uuid: string
|
||||
shared_vault_uuid: string
|
||||
user_uuid: string
|
||||
sender_uuid: string
|
||||
encrypted_message: string
|
||||
permission: string
|
||||
created_at_timestamp: number
|
||||
updated_at_timestamp: number
|
||||
}
|
||||
}): UserInvitedToSharedVaultEvent
|
||||
createMessageSentToUserEvent(dto: {
|
||||
message: {
|
||||
uuid: string
|
||||
recipient_uuid: string
|
||||
sender_uuid: string
|
||||
encrypted_message: string
|
||||
replaceability_identifier: string | null
|
||||
created_at_timestamp: number
|
||||
updated_at_timestamp: number
|
||||
}
|
||||
}): MessageSentToUserEvent
|
||||
createNotificationAddedForUserEvent(dto: {
|
||||
notification: {
|
||||
uuid: string
|
||||
user_uuid: string
|
||||
type: string
|
||||
payload: string
|
||||
created_at_timestamp: number
|
||||
updated_at_timestamp: number
|
||||
}
|
||||
}): NotificationAddedForUserEvent
|
||||
createTransitionStatusUpdatedEvent(dto: {
|
||||
userUuid: string
|
||||
transitionType: 'items' | 'revisions'
|
||||
|
||||
@@ -4,13 +4,21 @@ import { NotificationPayload, NotificationType, Result, Uuid } from '@standardno
|
||||
import { NotificationRepositoryInterface } from '../../../Notifications/NotificationRepositoryInterface'
|
||||
import { Notification } from '../../../Notifications/Notification'
|
||||
import { AddNotificationForUser } from './AddNotificationForUser'
|
||||
import { DomainEventFactoryInterface } from '../../../Event/DomainEventFactoryInterface'
|
||||
import { SendEventToClient } from '../../Syncing/SendEventToClient/SendEventToClient'
|
||||
import { NotificationAddedForUserEvent } from '@standardnotes/domain-events'
|
||||
import { Logger } from 'winston'
|
||||
|
||||
describe('AddNotificationForUser', () => {
|
||||
let notificationRepository: NotificationRepositoryInterface
|
||||
let timer: TimerInterface
|
||||
let payload: NotificationPayload
|
||||
let domainEventFactory: DomainEventFactoryInterface
|
||||
let sendEventToClientUseCase: SendEventToClient
|
||||
let logger: Logger
|
||||
|
||||
const createUseCase = () => new AddNotificationForUser(notificationRepository, timer)
|
||||
const createUseCase = () =>
|
||||
new AddNotificationForUser(notificationRepository, timer, domainEventFactory, sendEventToClientUseCase, logger)
|
||||
|
||||
beforeEach(() => {
|
||||
notificationRepository = {} as jest.Mocked<NotificationRepositoryInterface>
|
||||
@@ -24,6 +32,17 @@ describe('AddNotificationForUser', () => {
|
||||
type: NotificationType.create(NotificationType.TYPES.RemovedFromSharedVault).getValue(),
|
||||
version: '1.0',
|
||||
}).getValue()
|
||||
|
||||
domainEventFactory = {} as jest.Mocked<DomainEventFactoryInterface>
|
||||
domainEventFactory.createNotificationAddedForUserEvent = jest.fn().mockReturnValue({
|
||||
type: 'NOTIFICATION_ADDED_FOR_USER',
|
||||
} as jest.Mocked<NotificationAddedForUserEvent>)
|
||||
|
||||
sendEventToClientUseCase = {} as jest.Mocked<SendEventToClient>
|
||||
sendEventToClientUseCase.execute = jest.fn().mockReturnValue(Result.ok())
|
||||
|
||||
logger = {} as jest.Mocked<Logger>
|
||||
logger.error = jest.fn()
|
||||
})
|
||||
|
||||
it('should save notification', async () => {
|
||||
@@ -84,4 +103,20 @@ describe('AddNotificationForUser', () => {
|
||||
|
||||
mock.mockRestore()
|
||||
})
|
||||
|
||||
it('should log error if event could not be sent to client', async () => {
|
||||
sendEventToClientUseCase.execute = jest.fn().mockReturnValue(Result.fail('Oops'))
|
||||
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: '0e8c3c7e-3f1a-4f7a-9b5a-5b2b0a7d4b1e',
|
||||
type: NotificationType.TYPES.RemovedFromSharedVault,
|
||||
payload,
|
||||
version: '1.0',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBeFalsy()
|
||||
expect(logger.error).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -4,9 +4,18 @@ import { TimerInterface } from '@standardnotes/time'
|
||||
import { AddNotificationForUserDTO } from './AddNotificationForUserDTO'
|
||||
import { NotificationRepositoryInterface } from '../../../Notifications/NotificationRepositoryInterface'
|
||||
import { Notification } from '../../../Notifications/Notification'
|
||||
import { SendEventToClient } from '../../Syncing/SendEventToClient/SendEventToClient'
|
||||
import { DomainEventFactoryInterface } from '../../../Event/DomainEventFactoryInterface'
|
||||
import { Logger } from 'winston'
|
||||
|
||||
export class AddNotificationForUser implements UseCaseInterface<Notification> {
|
||||
constructor(private notificationRepository: NotificationRepositoryInterface, private timer: TimerInterface) {}
|
||||
constructor(
|
||||
private notificationRepository: NotificationRepositoryInterface,
|
||||
private timer: TimerInterface,
|
||||
private domainEventFactory: DomainEventFactoryInterface,
|
||||
private sendEventToClientUseCase: SendEventToClient,
|
||||
private logger: Logger,
|
||||
) {}
|
||||
|
||||
async execute(dto: AddNotificationForUserDTO): Promise<Result<Notification>> {
|
||||
const userUuidOrError = Uuid.create(dto.userUuid)
|
||||
@@ -37,6 +46,27 @@ export class AddNotificationForUser implements UseCaseInterface<Notification> {
|
||||
|
||||
await this.notificationRepository.save(notification)
|
||||
|
||||
const event = this.domainEventFactory.createNotificationAddedForUserEvent({
|
||||
notification: {
|
||||
uuid: notification.id.toString(),
|
||||
user_uuid: notification.props.userUuid.value,
|
||||
type: notification.props.type.value,
|
||||
payload: notification.props.payload.toString(),
|
||||
created_at_timestamp: notification.props.timestamps.createdAt,
|
||||
updated_at_timestamp: notification.props.timestamps.updatedAt,
|
||||
},
|
||||
})
|
||||
|
||||
const result = await this.sendEventToClientUseCase.execute({
|
||||
userUuid: userUuid.value,
|
||||
event,
|
||||
})
|
||||
if (result.isFailed()) {
|
||||
this.logger.error(
|
||||
`Failed to send notification added event to client for user ${userUuid.value}: ${result.getError()}`,
|
||||
)
|
||||
}
|
||||
|
||||
return Result.ok(notification)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,13 +3,21 @@ import { MessageRepositoryInterface } from '../../../Message/MessageRepositoryIn
|
||||
import { SendMessageToUser } from './SendMessageToUser'
|
||||
import { Message } from '../../../Message/Message'
|
||||
import { Result } from '@standardnotes/domain-core'
|
||||
import { Logger } from 'winston'
|
||||
import { DomainEventFactoryInterface } from '../../../Event/DomainEventFactoryInterface'
|
||||
import { SendEventToClient } from '../../Syncing/SendEventToClient/SendEventToClient'
|
||||
import { MessageSentToUserEvent } from '@standardnotes/domain-events'
|
||||
|
||||
describe('SendMessageToUser', () => {
|
||||
let messageRepository: MessageRepositoryInterface
|
||||
let timer: TimerInterface
|
||||
let existingMessage: Message
|
||||
let domainEventFactory: DomainEventFactoryInterface
|
||||
let sendEventToClientUseCase: SendEventToClient
|
||||
let logger: Logger
|
||||
|
||||
const createUseCase = () => new SendMessageToUser(messageRepository, timer)
|
||||
const createUseCase = () =>
|
||||
new SendMessageToUser(messageRepository, timer, domainEventFactory, sendEventToClientUseCase, logger)
|
||||
|
||||
beforeEach(() => {
|
||||
existingMessage = {} as jest.Mocked<Message>
|
||||
@@ -21,6 +29,17 @@ describe('SendMessageToUser', () => {
|
||||
|
||||
timer = {} as jest.Mocked<TimerInterface>
|
||||
timer.getTimestampInMicroseconds = jest.fn().mockReturnValue(123456789)
|
||||
|
||||
domainEventFactory = {} as jest.Mocked<DomainEventFactoryInterface>
|
||||
domainEventFactory.createMessageSentToUserEvent = jest.fn().mockReturnValue({
|
||||
type: 'MESSAGE_SENT_TO_USER',
|
||||
} as jest.Mocked<MessageSentToUserEvent>)
|
||||
|
||||
sendEventToClientUseCase = {} as jest.Mocked<SendEventToClient>
|
||||
sendEventToClientUseCase.execute = jest.fn().mockReturnValue(Result.ok())
|
||||
|
||||
logger = {} as jest.Mocked<Logger>
|
||||
logger.error = jest.fn()
|
||||
})
|
||||
|
||||
it('saves a new message', async () => {
|
||||
@@ -104,4 +123,19 @@ describe('SendMessageToUser', () => {
|
||||
|
||||
mock.mockRestore()
|
||||
})
|
||||
|
||||
it('should log error if event could not be sent to user', async () => {
|
||||
sendEventToClientUseCase.execute = jest.fn().mockReturnValue(Result.fail('Oops'))
|
||||
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
recipientUuid: '00000000-0000-0000-0000-000000000000',
|
||||
senderUuid: '00000000-0000-0000-0000-000000000000',
|
||||
encryptedMessage: 'encrypted-message',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBeFalsy()
|
||||
expect(logger.error).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -4,9 +4,18 @@ import { TimerInterface } from '@standardnotes/time'
|
||||
import { SendMessageToUserDTO } from './SendMessageToUserDTO'
|
||||
import { MessageRepositoryInterface } from '../../../Message/MessageRepositoryInterface'
|
||||
import { Message } from '../../../Message/Message'
|
||||
import { DomainEventFactoryInterface } from '../../../Event/DomainEventFactoryInterface'
|
||||
import { SendEventToClient } from '../../Syncing/SendEventToClient/SendEventToClient'
|
||||
import { Logger } from 'winston'
|
||||
|
||||
export class SendMessageToUser implements UseCaseInterface<Message> {
|
||||
constructor(private messageRepository: MessageRepositoryInterface, private timer: TimerInterface) {}
|
||||
constructor(
|
||||
private messageRepository: MessageRepositoryInterface,
|
||||
private timer: TimerInterface,
|
||||
private domainEventFactory: DomainEventFactoryInterface,
|
||||
private sendEventToClientUseCase: SendEventToClient,
|
||||
private logger: Logger,
|
||||
) {}
|
||||
|
||||
async execute(dto: SendMessageToUserDTO): Promise<Result<Message>> {
|
||||
const recipientUuidOrError = Uuid.create(dto.recipientUuid)
|
||||
@@ -54,6 +63,30 @@ export class SendMessageToUser implements UseCaseInterface<Message> {
|
||||
|
||||
await this.messageRepository.save(message)
|
||||
|
||||
const event = this.domainEventFactory.createMessageSentToUserEvent({
|
||||
message: {
|
||||
uuid: message.id.toString(),
|
||||
recipient_uuid: message.props.recipientUuid.value,
|
||||
sender_uuid: message.props.senderUuid.value,
|
||||
encrypted_message: message.props.encryptedMessage,
|
||||
replaceability_identifier: message.props.replaceabilityIdentifier,
|
||||
created_at_timestamp: message.props.timestamps.createdAt,
|
||||
updated_at_timestamp: message.props.timestamps.updatedAt,
|
||||
},
|
||||
})
|
||||
|
||||
const result = await this.sendEventToClientUseCase.execute({
|
||||
userUuid: message.props.recipientUuid.value,
|
||||
event,
|
||||
})
|
||||
if (result.isFailed()) {
|
||||
this.logger.error(
|
||||
`Failed to send message sent event to client for user ${
|
||||
message.props.recipientUuid.value
|
||||
}: ${result.getError()}`,
|
||||
)
|
||||
}
|
||||
|
||||
return Result.ok(message)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
import { Uuid, Timestamps, Result, SharedVaultUserPermission } from '@standardnotes/domain-core'
|
||||
import { Logger } from 'winston'
|
||||
|
||||
import { SharedVaultRepositoryInterface } from '../../../SharedVault/SharedVaultRepositoryInterface'
|
||||
import { SharedVaultInviteRepositoryInterface } from '../../../SharedVault/User/Invite/SharedVaultInviteRepositoryInterface'
|
||||
@@ -8,6 +9,9 @@ import { SharedVault } from '../../../SharedVault/SharedVault'
|
||||
import { SharedVaultInvite } from '../../../SharedVault/User/Invite/SharedVaultInvite'
|
||||
import { SharedVaultUserRepositoryInterface } from '../../../SharedVault/User/SharedVaultUserRepositoryInterface'
|
||||
import { SharedVaultUser } from '../../../SharedVault/User/SharedVaultUser'
|
||||
import { DomainEventFactoryInterface } from '../../../Event/DomainEventFactoryInterface'
|
||||
import { SendEventToClient } from '../../Syncing/SendEventToClient/SendEventToClient'
|
||||
import { UserInvitedToSharedVaultEvent } from '@standardnotes/domain-events'
|
||||
|
||||
describe('InviteUserToSharedVault', () => {
|
||||
let sharedVaultRepository: SharedVaultRepositoryInterface
|
||||
@@ -16,9 +20,20 @@ describe('InviteUserToSharedVault', () => {
|
||||
let timer: TimerInterface
|
||||
let sharedVault: SharedVault
|
||||
let sharedVaultUser: SharedVaultUser
|
||||
let domainEventFactory: DomainEventFactoryInterface
|
||||
let sendEventToClientUseCase: SendEventToClient
|
||||
let logger: Logger
|
||||
|
||||
const createUseCase = () =>
|
||||
new InviteUserToSharedVault(sharedVaultRepository, sharedVaultInviteRepository, sharedVaultUserRepository, timer)
|
||||
new InviteUserToSharedVault(
|
||||
sharedVaultRepository,
|
||||
sharedVaultInviteRepository,
|
||||
sharedVaultUserRepository,
|
||||
timer,
|
||||
domainEventFactory,
|
||||
sendEventToClientUseCase,
|
||||
logger,
|
||||
)
|
||||
|
||||
beforeEach(() => {
|
||||
sharedVault = SharedVault.create({
|
||||
@@ -46,6 +61,17 @@ describe('InviteUserToSharedVault', () => {
|
||||
|
||||
timer = {} as jest.Mocked<TimerInterface>
|
||||
timer.getTimestampInMicroseconds = jest.fn().mockReturnValue(123)
|
||||
|
||||
domainEventFactory = {} as jest.Mocked<DomainEventFactoryInterface>
|
||||
domainEventFactory.createUserInvitedToSharedVaultEvent = jest.fn().mockReturnValue({
|
||||
type: 'USER_INVITED_TO_SHARED_VAULT',
|
||||
} as jest.Mocked<UserInvitedToSharedVaultEvent>)
|
||||
|
||||
sendEventToClientUseCase = {} as jest.Mocked<SendEventToClient>
|
||||
sendEventToClientUseCase.execute = jest.fn().mockReturnValue(Result.ok())
|
||||
|
||||
logger = {} as jest.Mocked<Logger>
|
||||
logger.error = jest.fn()
|
||||
})
|
||||
|
||||
it('should return a failure result if the shared vault uuid is invalid', async () => {
|
||||
@@ -217,4 +243,21 @@ describe('InviteUserToSharedVault', () => {
|
||||
|
||||
mockSharedVaultInvite.mockRestore()
|
||||
})
|
||||
|
||||
it('should log error if event could not be sent to user', async () => {
|
||||
sendEventToClientUseCase.execute = jest.fn().mockReturnValue(Result.fail('Oops'))
|
||||
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
|
||||
senderUuid: '00000000-0000-0000-0000-000000000000',
|
||||
recipientUuid: '00000000-0000-0000-0000-000000000000',
|
||||
permission: SharedVaultUserPermission.PERMISSIONS.Read,
|
||||
encryptedMessage: 'encryptedMessage',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(false)
|
||||
expect(logger.error).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -6,6 +6,9 @@ import { SharedVaultRepositoryInterface } from '../../../SharedVault/SharedVault
|
||||
import { InviteUserToSharedVaultDTO } from './InviteUserToSharedVaultDTO'
|
||||
import { SharedVaultInviteRepositoryInterface } from '../../../SharedVault/User/Invite/SharedVaultInviteRepositoryInterface'
|
||||
import { SharedVaultUserRepositoryInterface } from '../../../SharedVault/User/SharedVaultUserRepositoryInterface'
|
||||
import { Logger } from 'winston'
|
||||
import { DomainEventFactoryInterface } from '../../../Event/DomainEventFactoryInterface'
|
||||
import { SendEventToClient } from '../../Syncing/SendEventToClient/SendEventToClient'
|
||||
|
||||
export class InviteUserToSharedVault implements UseCaseInterface<SharedVaultInvite> {
|
||||
constructor(
|
||||
@@ -13,6 +16,9 @@ export class InviteUserToSharedVault implements UseCaseInterface<SharedVaultInvi
|
||||
private sharedVaultInviteRepository: SharedVaultInviteRepositoryInterface,
|
||||
private sharedVaultUserRepository: SharedVaultUserRepositoryInterface,
|
||||
private timer: TimerInterface,
|
||||
private domainEventFactory: DomainEventFactoryInterface,
|
||||
private sendEventToClientUseCase: SendEventToClient,
|
||||
private logger: Logger,
|
||||
) {}
|
||||
async execute(dto: InviteUserToSharedVaultDTO): Promise<Result<SharedVaultInvite>> {
|
||||
const sharedVaultUuidOrError = Uuid.create(dto.sharedVaultUuid)
|
||||
@@ -82,6 +88,31 @@ export class InviteUserToSharedVault implements UseCaseInterface<SharedVaultInvi
|
||||
|
||||
await this.sharedVaultInviteRepository.save(sharedVaultInvite)
|
||||
|
||||
const event = this.domainEventFactory.createUserInvitedToSharedVaultEvent({
|
||||
invite: {
|
||||
uuid: sharedVaultInvite.id.toString(),
|
||||
shared_vault_uuid: sharedVaultInvite.props.sharedVaultUuid.value,
|
||||
user_uuid: sharedVaultInvite.props.userUuid.value,
|
||||
sender_uuid: sharedVaultInvite.props.senderUuid.value,
|
||||
encrypted_message: sharedVaultInvite.props.encryptedMessage,
|
||||
permission: sharedVaultInvite.props.permission.value,
|
||||
created_at_timestamp: sharedVaultInvite.props.timestamps.createdAt,
|
||||
updated_at_timestamp: sharedVaultInvite.props.timestamps.updatedAt,
|
||||
},
|
||||
})
|
||||
|
||||
const result = await this.sendEventToClientUseCase.execute({
|
||||
userUuid: sharedVaultInvite.props.userUuid.value,
|
||||
event,
|
||||
})
|
||||
if (result.isFailed()) {
|
||||
this.logger.error(
|
||||
`Failed to send user invited to shared vault event to client for user ${
|
||||
sharedVaultInvite.props.userUuid.value
|
||||
}: ${result.getError()}`,
|
||||
)
|
||||
}
|
||||
|
||||
return Result.ok(sharedVaultInvite)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import {
|
||||
DomainEventInterface,
|
||||
DomainEventPublisherInterface,
|
||||
WebSocketMessageRequestedEvent,
|
||||
} from '@standardnotes/domain-events'
|
||||
import { DomainEventFactoryInterface } from '../../../Event/DomainEventFactoryInterface'
|
||||
import { SendEventToClient } from './SendEventToClient'
|
||||
|
||||
describe('SendEventToClient', () => {
|
||||
let domainEventFactory: DomainEventFactoryInterface
|
||||
let domainEventPublisher: DomainEventPublisherInterface
|
||||
|
||||
const createUseCase = () => new SendEventToClient(domainEventFactory, domainEventPublisher)
|
||||
|
||||
beforeEach(() => {
|
||||
domainEventFactory = {} as jest.Mocked<DomainEventFactoryInterface>
|
||||
domainEventFactory.createWebSocketMessageRequestedEvent = jest
|
||||
.fn()
|
||||
.mockReturnValue({} as jest.Mocked<WebSocketMessageRequestedEvent>)
|
||||
|
||||
domainEventPublisher = {} as jest.Mocked<DomainEventPublisherInterface>
|
||||
domainEventPublisher.publish = jest.fn()
|
||||
})
|
||||
|
||||
it('should publish a WebSocketMessageRequestedEvent', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
await useCase.execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
event: {
|
||||
type: 'test',
|
||||
} as jest.Mocked<DomainEventInterface>,
|
||||
})
|
||||
|
||||
expect(domainEventFactory.createWebSocketMessageRequestedEvent).toHaveBeenCalledWith({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
message: JSON.stringify({
|
||||
type: 'test',
|
||||
}),
|
||||
})
|
||||
expect(domainEventPublisher.publish).toHaveBeenCalledWith({} as jest.Mocked<WebSocketMessageRequestedEvent>)
|
||||
})
|
||||
|
||||
it('should return a failed result if user uuid is invalid', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: 'invalid',
|
||||
event: {
|
||||
type: 'test',
|
||||
} as jest.Mocked<DomainEventInterface>,
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,29 @@
|
||||
import { Result, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
|
||||
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
|
||||
|
||||
import { SendEventToClientDTO } from './SendEventToClientDTO'
|
||||
import { DomainEventFactoryInterface } from '../../../Event/DomainEventFactoryInterface'
|
||||
|
||||
export class SendEventToClient implements UseCaseInterface<void> {
|
||||
constructor(
|
||||
private domainEventFactory: DomainEventFactoryInterface,
|
||||
private domainEventPublisher: DomainEventPublisherInterface,
|
||||
) {}
|
||||
|
||||
async execute(dto: SendEventToClientDTO): Promise<Result<void>> {
|
||||
const userUuidOrError = Uuid.create(dto.userUuid)
|
||||
if (userUuidOrError.isFailed()) {
|
||||
return Result.fail(userUuidOrError.getError())
|
||||
}
|
||||
const userUuid = userUuidOrError.getValue()
|
||||
|
||||
const event = this.domainEventFactory.createWebSocketMessageRequestedEvent({
|
||||
userUuid: userUuid.value,
|
||||
message: JSON.stringify(dto.event),
|
||||
})
|
||||
|
||||
await this.domainEventPublisher.publish(event)
|
||||
|
||||
return Result.ok()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { DomainEventInterface } from '@standardnotes/domain-events'
|
||||
|
||||
export interface SendEventToClientDTO {
|
||||
userUuid: string
|
||||
event: DomainEventInterface
|
||||
}
|
||||
@@ -117,6 +117,7 @@ describe('TransitionItemsFromPrimaryToSecondaryDatabaseForUser', () => {
|
||||
logger.info = jest.fn()
|
||||
|
||||
timer = {} as jest.Mocked<TimerInterface>
|
||||
timer.sleep = jest.fn()
|
||||
timer.getTimestampInMicroseconds = jest.fn().mockReturnValue(123)
|
||||
timer.convertMicrosecondsToTimeStructure = jest.fn().mockReturnValue({
|
||||
days: 0,
|
||||
|
||||
@@ -39,6 +39,8 @@ export class TransitionItemsFromPrimaryToSecondaryDatabaseForUser implements Use
|
||||
return Result.fail(migrationResult.getError())
|
||||
}
|
||||
|
||||
await this.allowForSecondaryDatabaseToCatchUp()
|
||||
|
||||
const integrityCheckResult = await this.checkIntegrityBetweenPrimaryAndSecondaryDatabase(userUuid)
|
||||
if (integrityCheckResult.isFailed()) {
|
||||
const cleanupResult = await this.deleteItemsForUser(userUuid, this.secondaryItemRepository)
|
||||
@@ -70,6 +72,11 @@ export class TransitionItemsFromPrimaryToSecondaryDatabaseForUser implements Use
|
||||
return Result.ok()
|
||||
}
|
||||
|
||||
private async allowForSecondaryDatabaseToCatchUp(): Promise<void> {
|
||||
const twoSecondsInMilliseconds = 2_000
|
||||
await this.timer.sleep(twoSecondsInMilliseconds)
|
||||
}
|
||||
|
||||
private async migrateItemsForUser(userUuid: Uuid): Promise<Result<void>> {
|
||||
try {
|
||||
const totalItemsCountForUser = await this.primaryItemRepository.countAll({ userUuid: userUuid.value })
|
||||
|
||||
@@ -18,7 +18,7 @@ export class MongoDBItemRepository implements ItemRepositoryInterface {
|
||||
) {}
|
||||
|
||||
async deleteByUserUuid(userUuid: string): Promise<void> {
|
||||
await this.mongoRepository.deleteMany({ where: { userUuid } })
|
||||
await this.mongoRepository.deleteMany({ userUuid })
|
||||
}
|
||||
|
||||
async findAll(query: ItemQuery): Promise<Item[]> {
|
||||
@@ -136,7 +136,7 @@ export class MongoDBItemRepository implements ItemRepositoryInterface {
|
||||
}
|
||||
|
||||
async remove(item: Item): Promise<void> {
|
||||
await this.mongoRepository.deleteOne({ where: { _id: { $eq: BSON.UUID.createFromHexString(item.uuid.value) } } })
|
||||
await this.mongoRepository.deleteOne({ _id: { $eq: BSON.UUID.createFromHexString(item.uuid.value) } })
|
||||
}
|
||||
|
||||
async save(item: Item): Promise<void> {
|
||||
@@ -189,9 +189,7 @@ export class MongoDBItemRepository implements ItemRepositoryInterface {
|
||||
}
|
||||
}
|
||||
if (query.deleted !== undefined) {
|
||||
const deletedMixedValues = query.deleted === true ? [true, 1] : [false, 0]
|
||||
|
||||
options.where = { ...options.where, deleted: { $in: deletedMixedValues } }
|
||||
options.where = { ...options.where, deleted: { $eq: query.deleted } }
|
||||
}
|
||||
if (query.contentType) {
|
||||
if (Array.isArray(query.contentType)) {
|
||||
|
||||
@@ -99,7 +99,7 @@ export class SQLItemPersistenceMapper implements MapperInterface<Item, SQLItem>
|
||||
encItemKey: projection.encItemKey,
|
||||
authHash: projection.authHash,
|
||||
userUuid,
|
||||
deleted: projection.deleted,
|
||||
deleted: !!projection.deleted,
|
||||
dates,
|
||||
timestamps,
|
||||
updatedWithSession,
|
||||
@@ -127,7 +127,7 @@ export class SQLItemPersistenceMapper implements MapperInterface<Item, SQLItem>
|
||||
typeorm.encItemKey = domain.props.encItemKey
|
||||
typeorm.authHash = domain.props.authHash
|
||||
typeorm.userUuid = domain.props.userUuid.value
|
||||
typeorm.deleted = domain.props.deleted
|
||||
typeorm.deleted = !!domain.props.deleted
|
||||
typeorm.createdAt = domain.props.dates.createdAt
|
||||
typeorm.updatedAt = domain.props.dates.updatedAt
|
||||
typeorm.createdAtTimestamp = domain.props.timestamps.createdAt
|
||||
|
||||
@@ -64,7 +64,7 @@ export class SQLLegacyItemPersistenceMapper implements MapperInterface<Item, SQL
|
||||
encItemKey: projection.encItemKey,
|
||||
authHash: projection.authHash,
|
||||
userUuid,
|
||||
deleted: projection.deleted,
|
||||
deleted: !!projection.deleted,
|
||||
dates,
|
||||
timestamps,
|
||||
updatedWithSession,
|
||||
@@ -90,7 +90,7 @@ export class SQLLegacyItemPersistenceMapper implements MapperInterface<Item, SQL
|
||||
typeorm.encItemKey = domain.props.encItemKey
|
||||
typeorm.authHash = domain.props.authHash
|
||||
typeorm.userUuid = domain.props.userUuid.value
|
||||
typeorm.deleted = domain.props.deleted
|
||||
typeorm.deleted = !!domain.props.deleted
|
||||
typeorm.createdAt = domain.props.dates.createdAt
|
||||
typeorm.updatedAt = domain.props.dates.updatedAt
|
||||
typeorm.createdAtTimestamp = domain.props.timestamps.createdAt
|
||||
|
||||
@@ -3,6 +3,12 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.15.1](https://github.com/standardnotes/server/compare/@standardnotes/time@1.15.0...@standardnotes/time@1.15.1) (2023-08-31)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* transitionining revisions ([#801](https://github.com/standardnotes/server/issues/801)) ([596a0f1](https://github.com/standardnotes/server/commit/596a0f1a0221ab0636c4c04d17a28c57fe74b620))
|
||||
|
||||
# [1.15.0](https://github.com/standardnotes/server/compare/@standardnotes/time@1.14.6...@standardnotes/time@1.15.0) (2023-08-24)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/time",
|
||||
"version": "1.15.0",
|
||||
"version": "1.15.1",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -10,6 +10,11 @@ export class Timer implements TimerInterface {
|
||||
dayjs.extend(utc)
|
||||
}
|
||||
|
||||
/* istanbul ignore next */
|
||||
async sleep(milliseconds: number): Promise<void> {
|
||||
return new Promise((resolve) => setTimeout(resolve, milliseconds))
|
||||
}
|
||||
|
||||
getUTCDateNSecondsAhead(n: number): Date {
|
||||
return dayjs.utc().add(n, 'second').toDate()
|
||||
}
|
||||
|
||||
@@ -23,4 +23,5 @@ export interface TimerInterface {
|
||||
convertMicrosecondsToTimeStructure(microseconds: number): TimeStructure
|
||||
formatDate(date: Date, format: string): string
|
||||
dateWasNDaysAgo(date: Date): number
|
||||
sleep(milliseconds: number): Promise<void>
|
||||
}
|
||||
|
||||
@@ -3,6 +3,22 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.10.26](https://github.com/standardnotes/server/compare/@standardnotes/websockets-server@1.10.25...@standardnotes/websockets-server@1.10.26) (2023-09-01)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* inserting revisions instead of upsert ([#803](https://github.com/standardnotes/server/issues/803)) ([27ff25b](https://github.com/standardnotes/server/commit/27ff25b70e6b65dfe89aa35582422dce682a4105))
|
||||
|
||||
## [1.10.25](https://github.com/standardnotes/server/compare/@standardnotes/websockets-server@1.10.24...@standardnotes/websockets-server@1.10.25) (2023-09-01)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* remove the alive and kicking info logs on workers ([1bef127](https://github.com/standardnotes/server/commit/1bef1279e6dbf3cbdfa87e44aa9108ed6dbb3b0f))
|
||||
|
||||
## [1.10.24](https://github.com/standardnotes/server/compare/@standardnotes/websockets-server@1.10.23...@standardnotes/websockets-server@1.10.24) (2023-08-31)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/websockets-server
|
||||
|
||||
## [1.10.23](https://github.com/standardnotes/server/compare/@standardnotes/websockets-server@1.10.22...@standardnotes/websockets-server@1.10.23) (2023-08-30)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/websockets-server
|
||||
|
||||
@@ -18,6 +18,4 @@ void container.load().then((container) => {
|
||||
|
||||
const subscriberFactory: DomainEventSubscriberFactoryInterface = container.get(TYPES.DomainEventSubscriberFactory)
|
||||
subscriberFactory.create().start()
|
||||
|
||||
setInterval(() => logger.info('Alive and kicking!'), 20 * 60 * 1000)
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/websockets-server",
|
||||
"version": "1.10.23",
|
||||
"version": "1.10.26",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
@@ -39,7 +39,7 @@
|
||||
"ioredis": "^5.2.4",
|
||||
"mysql2": "^3.0.1",
|
||||
"reflect-metadata": "0.1.13",
|
||||
"typeorm": "^0.3.15",
|
||||
"typeorm": "^0.3.17",
|
||||
"winston": "^3.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
22
yarn.lock
22
yarn.lock
@@ -4309,7 +4309,7 @@ __metadata:
|
||||
prettier: "npm:^2.8.8"
|
||||
reflect-metadata: "npm:^0.1.13"
|
||||
ts-jest: "npm:^29.1.0"
|
||||
typeorm: "npm:^0.3.15"
|
||||
typeorm: "npm:^0.3.17"
|
||||
typescript: "npm:^5.0.4"
|
||||
winston: "npm:^3.8.1"
|
||||
dependenciesMeta:
|
||||
@@ -4448,7 +4448,7 @@ __metadata:
|
||||
reflect-metadata: "npm:0.1.13"
|
||||
sqlite3: "npm:^5.1.6"
|
||||
ts-jest: "npm:^29.1.0"
|
||||
typeorm: "npm:^0.3.15"
|
||||
typeorm: "npm:^0.3.17"
|
||||
typescript: "npm:^5.0.4"
|
||||
ua-parser-js: "npm:^1.0.35"
|
||||
uuid: "npm:^9.0.0"
|
||||
@@ -4575,7 +4575,7 @@ __metadata:
|
||||
prettier: "npm:^2.8.8"
|
||||
reflect-metadata: "npm:0.1.13"
|
||||
ts-jest: "npm:^29.1.0"
|
||||
typeorm: "npm:^0.3.15"
|
||||
typeorm: "npm:^0.3.17"
|
||||
typescript: "npm:^5.0.4"
|
||||
winston: "npm:^3.8.1"
|
||||
dependenciesMeta:
|
||||
@@ -4773,7 +4773,7 @@ __metadata:
|
||||
reflect-metadata: "npm:0.1.13"
|
||||
sqlite3: "npm:^5.1.6"
|
||||
ts-jest: "npm:^29.1.0"
|
||||
typeorm: "npm:^0.3.15"
|
||||
typeorm: "npm:^0.3.17"
|
||||
typescript: "npm:^5.0.4"
|
||||
winston: "npm:^3.8.1"
|
||||
dependenciesMeta:
|
||||
@@ -4817,7 +4817,7 @@ __metadata:
|
||||
prettier: "npm:^2.8.8"
|
||||
reflect-metadata: "npm:^0.1.13"
|
||||
ts-jest: "npm:^29.1.0"
|
||||
typeorm: "npm:^0.3.15"
|
||||
typeorm: "npm:^0.3.17"
|
||||
typescript: "npm:^5.0.4"
|
||||
winston: "npm:^3.8.1"
|
||||
dependenciesMeta:
|
||||
@@ -4977,7 +4977,7 @@ __metadata:
|
||||
semver: "npm:^7.5.1"
|
||||
sqlite3: "npm:^5.1.6"
|
||||
ts-jest: "npm:^29.1.0"
|
||||
typeorm: "npm:^0.3.15"
|
||||
typeorm: "npm:^0.3.17"
|
||||
typescript: "npm:^5.0.4"
|
||||
ua-parser-js: "npm:^1.0.35"
|
||||
uuid: "npm:^9.0.0"
|
||||
@@ -5059,7 +5059,7 @@ __metadata:
|
||||
prettier: "npm:^2.8.8"
|
||||
reflect-metadata: "npm:0.1.13"
|
||||
ts-jest: "npm:^29.1.0"
|
||||
typeorm: "npm:^0.3.15"
|
||||
typeorm: "npm:^0.3.17"
|
||||
typescript: "npm:^5.0.4"
|
||||
winston: "npm:^3.8.1"
|
||||
dependenciesMeta:
|
||||
@@ -14508,9 +14508,9 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"typeorm@npm:^0.3.15":
|
||||
version: 0.3.16
|
||||
resolution: "typeorm@npm:0.3.16"
|
||||
"typeorm@npm:^0.3.17":
|
||||
version: 0.3.17
|
||||
resolution: "typeorm@npm:0.3.17"
|
||||
dependencies:
|
||||
"@sqltools/formatter": "npm:^1.2.5"
|
||||
app-root-path: "npm:^3.1.0"
|
||||
@@ -14584,7 +14584,7 @@ __metadata:
|
||||
typeorm: cli.js
|
||||
typeorm-ts-node-commonjs: cli-ts-node-commonjs.js
|
||||
typeorm-ts-node-esm: cli-ts-node-esm.js
|
||||
checksum: 19803f935e2733982d04d505226092aa6aa109d233b5616f7c87d2acc578647590514c1580ff611499846bdfb90d901f388fd1700717bf0d4d36482fcdfa4304
|
||||
checksum: bae071e0974f5befaec4238be2056f6c27858ac68713118be242759cc3321f469da55b544f0b16306fe48cc4d178ce4ba86628853dfb5403fb23c37b8a935817
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
||||
Reference in New Issue
Block a user