Compare commits

...

24 Commits

Author SHA1 Message Date
standardci
5f67e5efda chore(release): publish new version
- @standardnotes/analytics@2.33.2
 - @standardnotes/api-gateway@1.83.3
 - @standardnotes/auth-server@1.169.2
 - @standardnotes/domain-events-infra@1.21.2
 - @standardnotes/domain-events@2.134.2
 - @standardnotes/files-server@1.34.2
 - @standardnotes/home-server@1.20.3
 - @standardnotes/revisions-server@1.49.2
 - @standardnotes/scheduler-server@1.27.2
 - @standardnotes/syncing-server@1.122.2
 - @standardnotes/websockets-server@1.19.2
2023-11-13 10:51:57 +00:00
Karol Sójko
daed1a77a0 fix(api-gateway): add application version to the error logs 2023-11-13 11:23:18 +01:00
Karol Sójko
b39eb09d91 fix(api-gateway): add request method to the debug logs 2023-11-13 11:15:02 +01:00
Karol Sójko
f6ec8626e5 fix(domain-events): remove unused event 2023-11-13 11:02:11 +01:00
standardci
97b12f2131 chore(release): publish new version
- @standardnotes/analytics@2.33.1
 - @standardnotes/api-gateway@1.83.2
 - @standardnotes/auth-server@1.169.1
 - @standardnotes/domain-events-infra@1.21.1
 - @standardnotes/domain-events@2.134.1
 - @standardnotes/files-server@1.34.1
 - @standardnotes/home-server@1.20.2
 - @standardnotes/revisions-server@1.49.1
 - @standardnotes/scheduler-server@1.27.1
 - @standardnotes/syncing-server@1.122.1
 - @standardnotes/websockets-server@1.19.1
2023-11-13 10:00:24 +00:00
Karol Sójko
8e4e36513a fix: remove unused even-store from code base (#925)
* fix: remove unused even-store from code base

* fix lock
2023-11-13 10:37:42 +01:00
Karol Sójko
c8bf4ab3a0 fix(api-gateway): debug log on error thrown body representation 2023-11-13 09:56:54 +01:00
standardci
3fa01a328b chore(release): publish new version
- @standardnotes/api-gateway@1.83.1
 - @standardnotes/home-server@1.20.1
2023-11-13 08:35:23 +00:00
Karol Sójko
60686dcdbd fix(api-gateway): add debug logs for errors on parsing 2023-11-13 07:39:34 +01:00
standardci
fddd17e531 chore(release): publish new version
- @standardnotes/api-gateway@1.83.0
 - @standardnotes/auth-server@1.169.0
 - @standardnotes/files-server@1.34.0
 - @standardnotes/home-server@1.20.0
 - @standardnotes/revisions-server@1.49.0
 - @standardnotes/syncing-server@1.122.0
 - @standardnotes/websockets-server@1.19.0
2023-11-10 14:39:08 +00:00
Karol Sójko
f99750169f fix(api-gateway): add more info on error logs 2023-11-10 15:13:54 +01:00
Karol Sójko
daad76d0dd feat: add keep-alive connections to subservices (#924)
* feat: add keep-alive connections to subservices

* fix: defaults
2023-11-10 14:52:44 +01:00
standardci
b3542e2fab chore(release): publish new version
- @standardnotes/api-gateway@1.82.1
 - @standardnotes/home-server@1.19.1
2023-11-10 12:21:41 +00:00
Karol Sójko
a9b1543e20 fix(api-gateway): websockets calls logs severity 2023-11-10 12:59:42 +01:00
standardci
e6d8e5c5f2 chore(release): publish new version
- @standardnotes/analytics@2.33.0
 - @standardnotes/api-gateway@1.82.0
 - @standardnotes/auth-server@1.168.0
 - @standardnotes/domain-events-infra@1.21.0
 - @standardnotes/domain-events@2.134.0
 - @standardnotes/event-store@1.14.0
 - @standardnotes/files-server@1.33.0
 - @standardnotes/home-server@1.19.0
 - @standardnotes/revisions-server@1.48.0
 - @standardnotes/scheduler-server@1.27.0
 - @standardnotes/syncing-server@1.121.0
 - @standardnotes/websockets-server@1.18.0
2023-11-10 11:46:24 +00:00
Karol Sójko
c24353cc24 feat: add graceful shutdown procedures upon SIGTERM (#923) 2023-11-10 12:20:21 +01:00
standardci
4855e1d5f5 chore(release): publish new version
- @standardnotes/api-gateway@1.81.14
 - @standardnotes/home-server@1.18.32
2023-11-10 10:04:31 +00:00
Karol Sójko
5d3fb9a537 fix(api-gateway): add logs about calling web sockets with minimal format 2023-11-10 10:32:47 +01:00
standardci
b55d80a7cd chore(release): publish new version
- @standardnotes/api-gateway@1.81.13
 - @standardnotes/home-server@1.18.31
2023-11-09 14:29:28 +00:00
Karol Sójko
16f92bdc99 fix(api-gateway): add possibility to configure keep-alive timeout (#920) 2023-11-09 15:02:32 +01:00
standardci
4c5738416a chore(release): publish new version
- @standardnotes/home-server@1.18.30
 - @standardnotes/syncing-server@1.120.5
 - @standardnotes/websockets-server@1.17.8
2023-11-09 13:26:30 +00:00
Karol Sójko
45d4920e0f fix: remove unused axios dep in subservices 2023-11-09 14:03:44 +01:00
standardci
94e738532a chore(release): publish new version
- @standardnotes/api-gateway@1.81.12
 - @standardnotes/home-server@1.18.29
 - @standardnotes/websockets-server@1.17.7
2023-11-09 11:18:35 +00:00
Karol Sójko
c4ae12d53f fix: reduce websockets api communication data (#919) 2023-11-09 11:44:31 +01:00
113 changed files with 606 additions and 2692 deletions

View File

@@ -61,13 +61,6 @@ updates:
allow:
- dependency-type: "direct"
- package-ecosystem: "npm"
directory: "/packages/event-store"
schedule:
interval: "daily"
allow:
- dependency-type: "direct"
- package-ecosystem: "npm"
directory: "/packages/files"
schedule:

74
.pnp.cjs generated
View File

@@ -41,10 +41,6 @@ const RAW_RUNTIME_STATE =
"name": "@standardnotes/domain-events-infra",\
"reference": "workspace:packages/domain-events-infra"\
},\
{\
"name": "@standardnotes/event-store",\
"reference": "workspace:packages/event-store"\
},\
{\
"name": "@standardnotes/files-server",\
"reference": "workspace:packages/files"\
@@ -100,7 +96,6 @@ const RAW_RUNTIME_STATE =
["@standardnotes/domain-core", ["workspace:packages/domain-core"]],\
["@standardnotes/domain-events", ["workspace:packages/domain-events"]],\
["@standardnotes/domain-events-infra", ["workspace:packages/domain-events-infra"]],\
["@standardnotes/event-store", ["workspace:packages/event-store"]],\
["@standardnotes/files-server", ["workspace:packages/files"]],\
["@standardnotes/home-server", ["workspace:packages/home-server"]],\
["@standardnotes/predicates", ["workspace:packages/predicates"]],\
@@ -6390,7 +6385,8 @@ const RAW_RUNTIME_STATE =
["@types/prettyjson", "npm:0.0.30"],\
["@typescript-eslint/eslint-plugin", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:6.5.0"],\
["@typescript-eslint/parser", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:6.5.0"],\
["axios", "npm:1.4.0"],\
["agentkeepalive", "npm:4.5.0"],\
["axios", "npm:1.6.1"],\
["cors", "npm:2.8.5"],\
["dotenv", "npm:16.1.3"],\
["eslint", "npm:8.41.0"],\
@@ -6578,38 +6574,6 @@ const RAW_RUNTIME_STATE =
"linkType": "SOFT"\
}]\
]],\
["@standardnotes/event-store", [\
["workspace:packages/event-store", {\
"packageLocation": "./packages/event-store/",\
"packageDependencies": [\
["@standardnotes/event-store", "workspace:packages/event-store"],\
["@aws-sdk/client-sqs", "npm:3.427.0"],\
["@standardnotes/domain-core", "workspace:packages/domain-core"],\
["@standardnotes/domain-events", "workspace:packages/domain-events"],\
["@standardnotes/domain-events-infra", "workspace:packages/domain-events-infra"],\
["@standardnotes/time", "workspace:packages/time"],\
["@types/ioredis", "npm:5.0.0"],\
["@types/jest", "npm:29.5.2"],\
["@types/nodemailer", "npm:6.4.8"],\
["@typescript-eslint/eslint-plugin", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:6.5.0"],\
["@typescript-eslint/parser", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:6.5.0"],\
["dotenv", "npm:16.1.3"],\
["eslint", "npm:8.41.0"],\
["eslint-plugin-prettier", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:5.0.0"],\
["inversify", "npm:6.0.1"],\
["ioredis", "npm:5.3.2"],\
["jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.5.0"],\
["mysql2", "npm:3.3.3"],\
["prettier", "npm:3.0.3"],\
["reflect-metadata", "npm:0.1.13"],\
["ts-jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.1.0"],\
["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"]\
],\
"linkType": "SOFT"\
}]\
]],\
["@standardnotes/features", [\
["npm:1.59.7", {\
"packageLocation": "./.yarn/cache/@standardnotes-features-npm-1.59.7-27c3e5296e-421af62d1e.zip/node_modules/@standardnotes/features/",\
@@ -6961,7 +6925,6 @@ const RAW_RUNTIME_STATE =
["@types/uuid", "npm:9.0.3"],\
["@typescript-eslint/eslint-plugin", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:6.5.0"],\
["@typescript-eslint/parser", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:6.5.0"],\
["axios", "npm:1.4.0"],\
["cors", "npm:2.8.5"],\
["dotenv", "npm:16.1.3"],\
["eslint", "npm:8.41.0"],\
@@ -7044,7 +7007,6 @@ const RAW_RUNTIME_STATE =
["@types/jest", "npm:29.5.2"],\
["@typescript-eslint/eslint-plugin", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:6.5.0"],\
["@typescript-eslint/parser", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:6.5.0"],\
["axios", "npm:1.4.0"],\
["cors", "npm:2.8.5"],\
["dotenv", "npm:16.1.3"],\
["eslint", "npm:8.41.0"],\
@@ -7477,16 +7439,6 @@ const RAW_RUNTIME_STATE =
"linkType": "HARD"\
}]\
]],\
["@types/nodemailer", [\
["npm:6.4.8", {\
"packageLocation": "./.yarn/cache/@types-nodemailer-npm-6.4.8-04975b93f9-d5afdd77ef.zip/node_modules/@types/nodemailer/",\
"packageDependencies": [\
["@types/nodemailer", "npm:6.4.8"],\
["@types/node", "npm:20.2.5"]\
],\
"linkType": "HARD"\
}]\
]],\
["@types/normalize-package-data", [\
["npm:2.4.1", {\
"packageLocation": "./.yarn/cache/@types-normalize-package-data-npm-2.4.1-c31c56ae6a-e87bccbf11.zip/node_modules/@types/normalize-package-data/",\
@@ -8196,6 +8148,14 @@ const RAW_RUNTIME_STATE =
["humanize-ms", "npm:1.2.1"]\
],\
"linkType": "HARD"\
}],\
["npm:4.5.0", {\
"packageLocation": "./.yarn/cache/agentkeepalive-npm-4.5.0-f237b580b2-dd210ba2a2.zip/node_modules/agentkeepalive/",\
"packageDependencies": [\
["agentkeepalive", "npm:4.5.0"],\
["humanize-ms", "npm:1.2.1"]\
],\
"linkType": "HARD"\
}]\
]],\
["aggregate-error", [\
@@ -8498,11 +8458,11 @@ const RAW_RUNTIME_STATE =
}]\
]],\
["axios", [\
["npm:1.4.0", {\
"packageLocation": "./.yarn/cache/axios-npm-1.4.0-4d7ce8ca3e-b987e4259e.zip/node_modules/axios/",\
["npm:1.6.1", {\
"packageLocation": "./.yarn/cache/axios-npm-1.6.1-ffaff76449-fb091af3ad.zip/node_modules/axios/",\
"packageDependencies": [\
["axios", "npm:1.4.0"],\
["follow-redirects", "virtual:4d7ce8ca3e1e44d82523fba2ad95e1be18c4e9f8dec6d551377587540da3ed75bd8bd3e812280309a3b90cfdb0560f076f3552a20839f7f15665207a4fbd588a#npm:1.15.2"],\
["axios", "npm:1.6.1"],\
["follow-redirects", "virtual:ffaff76449f02e83712a7d24e03c564489516739c78ebeffb0fbcdb3893ad9a0e48504f9acfa70fe6f16debe9c8dabde3679d63bf648278ea98a5ff38cf77a9e#npm:1.15.2"],\
["form-data", "npm:4.0.0"],\
["proxy-from-env", "npm:1.1.0"]\
],\
@@ -10906,10 +10866,10 @@ const RAW_RUNTIME_STATE =
],\
"linkType": "SOFT"\
}],\
["virtual:4d7ce8ca3e1e44d82523fba2ad95e1be18c4e9f8dec6d551377587540da3ed75bd8bd3e812280309a3b90cfdb0560f076f3552a20839f7f15665207a4fbd588a#npm:1.15.2", {\
"packageLocation": "./.yarn/__virtual__/follow-redirects-virtual-359bc4c55c/0/cache/follow-redirects-npm-1.15.2-1ec1dd82be-8be0d39919.zip/node_modules/follow-redirects/",\
["virtual:ffaff76449f02e83712a7d24e03c564489516739c78ebeffb0fbcdb3893ad9a0e48504f9acfa70fe6f16debe9c8dabde3679d63bf648278ea98a5ff38cf77a9e#npm:1.15.2", {\
"packageLocation": "./.yarn/__virtual__/follow-redirects-virtual-c2d5794c26/0/cache/follow-redirects-npm-1.15.2-1ec1dd82be-8be0d39919.zip/node_modules/follow-redirects/",\
"packageDependencies": [\
["follow-redirects", "virtual:4d7ce8ca3e1e44d82523fba2ad95e1be18c4e9f8dec6d551377587540da3ed75bd8bd3e812280309a3b90cfdb0560f076f3552a20839f7f15665207a4fbd588a#npm:1.15.2"],\
["follow-redirects", "virtual:ffaff76449f02e83712a7d24e03c564489516739c78ebeffb0fbcdb3893ad9a0e48504f9acfa70fe6f16debe9c8dabde3679d63bf648278ea98a5ff38cf77a9e#npm:1.15.2"],\
["@types/debug", null],\
["debug", null]\
],\

Binary file not shown.

Binary file not shown.

View File

@@ -3,6 +3,20 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [2.33.2](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.33.1...@standardnotes/analytics@2.33.2) (2023-11-13)
**Note:** Version bump only for package @standardnotes/analytics
## [2.33.1](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.33.0...@standardnotes/analytics@2.33.1) (2023-11-13)
**Note:** Version bump only for package @standardnotes/analytics
# [2.33.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.32.6...@standardnotes/analytics@2.33.0) (2023-11-10)
### Features
* add graceful shutdown procedures upon SIGTERM ([#923](https://github.com/standardnotes/server/issues/923)) ([c24353c](https://github.com/standardnotes/server/commit/c24353cc24ebf4b40ff9a2cec8e37cfdef109e37))
## [2.32.6](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.32.5...@standardnotes/analytics@2.32.6) (2023-11-07)
**Note:** Version bump only for package @standardnotes/analytics

View File

@@ -22,5 +22,11 @@ void container.load().then((container) => {
const subscriber = container.get<DomainEventSubscriberInterface>(TYPES.DomainEventSubscriber)
process.on('SIGTERM', () => {
logger.info('SIGTERM received. Stopping worker...')
subscriber.stop()
logger.info('Worker stopped.')
})
subscriber.start()
})

View File

@@ -6,12 +6,12 @@ COMMAND=$1 && shift 1
case "$COMMAND" in
'start-worker' )
echo "[Docker] Starting Worker..."
node docker/entrypoint-worker.js
exec node docker/entrypoint-worker.js
;;
'report' )
echo "[Docker] Starting Usage Report Generation..."
node docker/entrypoint-report.js
exec node docker/entrypoint-report.js
;;
* )

View File

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

View File

@@ -3,6 +3,65 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.83.3](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.83.2...@standardnotes/api-gateway@1.83.3) (2023-11-13)
### Bug Fixes
* **api-gateway:** add application version to the error logs ([daed1a7](https://github.com/standardnotes/api-gateway/commit/daed1a77a02559a8487896b6fb8299befe8a2d96))
* **api-gateway:** add request method to the debug logs ([b39eb09](https://github.com/standardnotes/api-gateway/commit/b39eb09d91f0ea9482d27578faecdf57ed2ea48e))
## [1.83.2](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.83.1...@standardnotes/api-gateway@1.83.2) (2023-11-13)
### Bug Fixes
* **api-gateway:** debug log on error thrown body representation ([c8bf4ab](https://github.com/standardnotes/api-gateway/commit/c8bf4ab3a0ab757092077fc594e3ca7e090116b4))
## [1.83.1](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.83.0...@standardnotes/api-gateway@1.83.1) (2023-11-13)
### Bug Fixes
* **api-gateway:** add debug logs for errors on parsing ([60686dc](https://github.com/standardnotes/api-gateway/commit/60686dcdbd59c0d99cd1857a82ad62baed088b25))
# [1.83.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.82.1...@standardnotes/api-gateway@1.83.0) (2023-11-10)
### Bug Fixes
* **api-gateway:** add more info on error logs ([f997501](https://github.com/standardnotes/api-gateway/commit/f99750169f4d24cdc7530184af2230c687f3e166))
### Features
* add keep-alive connections to subservices ([#924](https://github.com/standardnotes/api-gateway/issues/924)) ([daad76d](https://github.com/standardnotes/api-gateway/commit/daad76d0ddae34c59dce45eedc4a055c4a11456d))
## [1.82.1](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.82.0...@standardnotes/api-gateway@1.82.1) (2023-11-10)
### Bug Fixes
* **api-gateway:** websockets calls logs severity ([a9b1543](https://github.com/standardnotes/api-gateway/commit/a9b1543e204afeab1fa2e008327c39cf306a247c))
# [1.82.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.81.14...@standardnotes/api-gateway@1.82.0) (2023-11-10)
### Features
* add graceful shutdown procedures upon SIGTERM ([#923](https://github.com/standardnotes/api-gateway/issues/923)) ([c24353c](https://github.com/standardnotes/api-gateway/commit/c24353cc24ebf4b40ff9a2cec8e37cfdef109e37))
## [1.81.14](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.81.13...@standardnotes/api-gateway@1.81.14) (2023-11-10)
### Bug Fixes
* **api-gateway:** add logs about calling web sockets with minimal format ([5d3fb9a](https://github.com/standardnotes/api-gateway/commit/5d3fb9a537f6971cfe8ae3c5ea449806cc4de8a0))
## [1.81.13](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.81.12...@standardnotes/api-gateway@1.81.13) (2023-11-09)
### Bug Fixes
* **api-gateway:** add possibility to configure keep-alive timeout ([#920](https://github.com/standardnotes/api-gateway/issues/920)) ([16f92bd](https://github.com/standardnotes/api-gateway/commit/16f92bdc990ded5c3f1fe5af1e6e4a113a9954de))
## [1.81.12](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.81.11...@standardnotes/api-gateway@1.81.12) (2023-11-09)
### Bug Fixes
* reduce websockets api communication data ([#919](https://github.com/standardnotes/api-gateway/issues/919)) ([c4ae12d](https://github.com/standardnotes/api-gateway/commit/c4ae12d53fc166879f90a4c5dbad1ab1cb4797e2))
## [1.81.11](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.81.10...@standardnotes/api-gateway@1.81.11) (2023-11-07)
### Bug Fixes

View File

@@ -90,8 +90,15 @@ void container.load().then((container) => {
const logger: winston.Logger = container.get(TYPES.ApiGateway_Logger)
server.setErrorConfig((app) => {
app.use((error: Record<string, unknown>, _request: Request, response: Response, _next: NextFunction) => {
logger.error(error.stack)
app.use((error: Record<string, unknown>, request: Request, response: Response, _next: NextFunction) => {
logger.error(
`[URL: |${request.method}| ${request.url}][SNJS: ${request.headers['x-snjs-version']}][Application: ${request.headers['x-application-version']}] Error thrown: ${error.stack}`,
)
logger.debug(
`[URL: |${request.method}| ${request.url}][SNJS: ${request.headers['x-snjs-version']}][Application: ${
request.headers['x-application-version']
}] Request body: ${JSON.stringify(request.body)}`,
)
response.status(500).send({
error: {
@@ -102,9 +109,18 @@ void container.load().then((container) => {
})
})
const serverInstance = server.build()
const serverInstance = server.build().listen(env.get('PORT'))
serverInstance.listen(env.get('PORT'))
const keepAliveTimeout = env.get('KEEP_ALIVE_TIMEOUT', true) ? +env.get('KEEP_ALIVE_TIMEOUT', true) : 5000
serverInstance.keepAliveTimeout = keepAliveTimeout
process.on('SIGTERM', () => {
logger.info('SIGTERM signal received: closing HTTP server')
serverInstance.close(() => {
logger.info('HTTP server closed')
})
})
logger.info(`Server started on port ${process.env.PORT}`)
})

View File

@@ -6,7 +6,7 @@ COMMAND=$1 && shift 1
case "$COMMAND" in
'start-web' )
echo "Starting Web..."
node docker/entrypoint-server.js
exec node docker/entrypoint-server.js
;;
* )

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/api-gateway",
"version": "1.81.11",
"version": "1.83.3",
"engines": {
"node": ">=18.0.0 <21.0.0"
},
@@ -31,7 +31,8 @@
"@standardnotes/domain-events-infra": "workspace:*",
"@standardnotes/security": "workspace:*",
"@standardnotes/time": "workspace:*",
"axios": "^1.1.3",
"agentkeepalive": "^4.5.0",
"axios": "^1.6.1",
"cors": "2.8.5",
"dotenv": "^16.0.1",
"express": "^4.18.2",

View File

@@ -1,7 +1,6 @@
import * as winston from 'winston'
// eslint-disable-next-line @typescript-eslint/no-var-requires
const axios = require('axios')
import { AxiosInstance } from 'axios'
import * as AgentKeepAlive from 'agentkeepalive'
import axios, { AxiosInstance } from 'axios'
import Redis from 'ioredis'
import { Container } from 'inversify'
import { Timer, TimerInterface } from '@standardnotes/time'
@@ -70,7 +69,17 @@ export class ContainerConfigLoader {
container.bind(TYPES.ApiGateway_Redis).toConstantValue(redis)
}
container.bind<AxiosInstance>(TYPES.ApiGateway_HTTPClient).toConstantValue(axios.create())
container.bind<AxiosInstance>(TYPES.ApiGateway_HTTPClient).toConstantValue(
axios.create({
httpAgent: new AgentKeepAlive({
keepAlive: true,
timeout: env.get('AGENT_KEEP_ALIVE_TIMEOUT', true) ? +env.get('AGENT_KEEP_ALIVE_TIMEOUT', true) : 8_000,
freeSocketTimeout: env.get('AGENT_KEEP_ALIVE_FREE_SOCKET_TIMEOUT', true)
? +env.get('AGENT_KEEP_ALIVE_FREE_SOCKET_TIMEOUT', true)
: 4_000,
}),
}),
)
// env vars
container.bind(TYPES.ApiGateway_SYNCING_SERVER_JS_URL).toConstantValue(env.get('SYNCING_SERVER_JS_URL', true))

View File

@@ -143,7 +143,21 @@ export class HttpServiceProxy implements ServiceProxyInterface {
return
}
await this.callServer(this.webSocketServerUrl, request, response, endpointOrMethodIdentifier, payload)
const isARequestComingFromApiGatewayAndShouldBeKeptInMinimalFormat = request.headers.connectionid !== undefined
this.logger.debug(
`Calling websockets service: ${endpointOrMethodIdentifier}. Format is minimal: ${isARequestComingFromApiGatewayAndShouldBeKeptInMinimalFormat}`,
)
if (isARequestComingFromApiGatewayAndShouldBeKeptInMinimalFormat) {
await this.callServerWithLegacyFormat(
this.webSocketServerUrl,
request,
response,
endpointOrMethodIdentifier,
payload,
)
} else {
await this.callServer(this.webSocketServerUrl, request, response, endpointOrMethodIdentifier, payload)
}
}
async callPaymentsServer(

View File

@@ -6,4 +6,4 @@ sh supervisor/wait-for.sh localhost $AUTH_SERVER_PORT
sh supervisor/wait-for.sh localhost $FILES_SERVER_PORT
sh supervisor/wait-for.sh localhost $REVISIONS_SERVER_PORT
sh supervisor/wait-for.sh localhost $SYNCING_SERVER_PORT
node docker/entrypoint-server.js
exec node docker/entrypoint-server.js

View File

@@ -3,6 +3,26 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.169.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.169.1...@standardnotes/auth-server@1.169.2) (2023-11-13)
**Note:** Version bump only for package @standardnotes/auth-server
## [1.169.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.169.0...@standardnotes/auth-server@1.169.1) (2023-11-13)
**Note:** Version bump only for package @standardnotes/auth-server
# [1.169.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.168.0...@standardnotes/auth-server@1.169.0) (2023-11-10)
### Features
* add keep-alive connections to subservices ([#924](https://github.com/standardnotes/server/issues/924)) ([daad76d](https://github.com/standardnotes/server/commit/daad76d0ddae34c59dce45eedc4a055c4a11456d))
# [1.168.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.167.2...@standardnotes/auth-server@1.168.0) (2023-11-10)
### Features
* add graceful shutdown procedures upon SIGTERM ([#923](https://github.com/standardnotes/server/issues/923)) ([c24353c](https://github.com/standardnotes/server/commit/c24353cc24ebf4b40ff9a2cec8e37cfdef109e37))
## [1.167.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.167.1...@standardnotes/auth-server@1.167.2) (2023-11-08)
### Bug Fixes

View File

@@ -64,9 +64,18 @@ void container.load().then((container) => {
})
})
const serverInstance = server.build()
const serverInstance = server.build().listen(env.get('PORT'))
serverInstance.listen(env.get('PORT'))
const keepAliveTimeout = env.get('KEEP_ALIVE_TIMEOUT', true) ? +env.get('KEEP_ALIVE_TIMEOUT', true) : 5000
serverInstance.keepAliveTimeout = keepAliveTimeout
process.on('SIGTERM', () => {
logger.info('SIGTERM signal received: closing HTTP server')
serverInstance.close(() => {
logger.info('HTTP server closed')
})
})
logger.info(`Server started on port ${process.env.PORT}`)
})

View File

@@ -22,5 +22,11 @@ void container.load().then((container) => {
const subscriber = container.get<DomainEventSubscriberInterface>(TYPES.Auth_DomainEventSubscriber)
process.on('SIGTERM', () => {
logger.info('SIGTERM received. Stopping worker...')
subscriber.stop()
logger.info('Worker stopped.')
})
subscriber.start()
})

View File

@@ -6,45 +6,45 @@ COMMAND=$1 && shift 1
case "$COMMAND" in
'start-web' )
echo "[Docker] Starting Web..."
node docker/entrypoint-server.js
exec node docker/entrypoint-server.js
;;
'start-worker' )
echo "[Docker] Starting Worker..."
node docker/entrypoint-worker.js
exec node docker/entrypoint-worker.js
;;
'cleanup' )
echo "[Docker] Starting Cleanup..."
node docker/entrypoint-cleanup.js
exec node docker/entrypoint-cleanup.js
;;
'stats' )
echo "[Docker] Starting Persisting Stats..."
node docker/entrypoint-stats.js
exec node docker/entrypoint-stats.js
;;
'email-daily-backup' )
echo "[Docker] Starting Email Daily Backup..."
node docker/entrypoint-backup.js daily
exec node docker/entrypoint-backup.js daily
;;
'email-weekly-backup' )
echo "[Docker] Starting Email Weekly Backup..."
node docker/entrypoint-backup.js weekly
exec node docker/entrypoint-backup.js weekly
;;
'email-backup' )
echo "[Docker] Starting Email Backup For Single User..."
EMAIL=$1 && shift 1
node docker/entrypoint-user-email-backup.js $EMAIL
exec node docker/entrypoint-user-email-backup.js $EMAIL
;;
'delete-accounts' )
echo "[Docker] Starting Accounts Deleting from CSV..."
FILE_NAME=$1 && shift 1
MODE=$1 && shift 1
node docker/entrypoint-delete-accounts.js $FILE_NAME $MODE
exec node docker/entrypoint-delete-accounts.js $FILE_NAME $MODE
;;
* )

View File

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

View File

@@ -3,4 +3,4 @@
set -euo pipefail
sh supervisor/wait-for.sh localhost $SYNCING_SERVER_PORT
node docker/entrypoint-server.js
exec node docker/entrypoint-server.js

View File

@@ -3,4 +3,4 @@
set -euo pipefail
sh supervisor/wait-for.sh localhost $AUTH_SERVER_PORT
node docker/entrypoint-worker.js
exec node docker/entrypoint-worker.js

View File

@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.21.2](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.21.1...@standardnotes/domain-events-infra@1.21.2) (2023-11-13)
**Note:** Version bump only for package @standardnotes/domain-events-infra
## [1.21.1](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.21.0...@standardnotes/domain-events-infra@1.21.1) (2023-11-13)
### Bug Fixes
* remove unused even-store from code base ([#925](https://github.com/standardnotes/server/issues/925)) ([8e4e365](https://github.com/standardnotes/server/commit/8e4e36513aa6e3c4f98197adfa75e014920b3572))
# [1.21.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.20.4...@standardnotes/domain-events-infra@1.21.0) (2023-11-10)
### Features
* add graceful shutdown procedures upon SIGTERM ([#923](https://github.com/standardnotes/server/issues/923)) ([c24353c](https://github.com/standardnotes/server/commit/c24353cc24ebf4b40ff9a2cec8e37cfdef109e37))
## [1.20.4](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.20.3...@standardnotes/domain-events-infra@1.20.4) (2023-11-07)
**Note:** Version bump only for package @standardnotes/domain-events-infra

View File

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

View File

@@ -1,23 +0,0 @@
import 'reflect-metadata'
import * as IORedis from 'ioredis'
import { RedisDomainEventSubscriber } from './RedisDomainEventSubscriber'
describe('RedisDomainEventSubscriber', () => {
let redisClient: IORedis.Redis
const eventChannel = 'test-channel'
const createSubscriber = () => new RedisDomainEventSubscriber(redisClient, eventChannel)
beforeEach(() => {
redisClient = {} as jest.Mocked<IORedis.Redis>
redisClient.subscribe = jest.fn()
})
it('should start the subscription', () => {
createSubscriber().start()
expect(redisClient.subscribe).toHaveBeenCalledWith('test-channel')
})
})

View File

@@ -1,14 +0,0 @@
import * as IORedis from 'ioredis'
import { DomainEventSubscriberInterface } from '@standardnotes/domain-events'
export class RedisDomainEventSubscriber implements DomainEventSubscriberInterface {
constructor(
private redisClient: IORedis.Redis,
private eventChannel: string,
) {}
start(): void {
void this.redisClient.subscribe(this.eventChannel)
}
}

View File

@@ -1,31 +0,0 @@
import 'reflect-metadata'
import * as IORedis from 'ioredis'
import { RedisDomainEventSubscriberFactory } from './RedisDomainEventSubscriberFactory'
import { DomainEventMessageHandlerInterface } from '@standardnotes/domain-events'
import { RedisDomainEventSubscriber } from './RedisDomainEventSubscriber'
describe('RedisDomainEventSubscriberFactory', () => {
let redisClient: IORedis.Redis
let domainEventMessageHandler: DomainEventMessageHandlerInterface
const eventChannel = 'events'
const createFactory = () =>
new RedisDomainEventSubscriberFactory(redisClient, domainEventMessageHandler, eventChannel)
beforeEach(() => {
redisClient = {} as jest.Mocked<IORedis.Redis>
redisClient.on = jest.fn()
domainEventMessageHandler = {} as jest.Mocked<DomainEventMessageHandlerInterface>
domainEventMessageHandler.handleMessage = jest.fn()
})
it('should create an event subscriber', () => {
const subscriber = createFactory().create()
expect(subscriber).toBeInstanceOf(RedisDomainEventSubscriber)
expect(redisClient.on).toHaveBeenCalledWith('message', expect.any(Function))
})
})

View File

@@ -1,29 +0,0 @@
import * as IORedis from 'ioredis'
import {
DomainEventSubscriberFactoryInterface,
DomainEventSubscriberInterface,
DomainEventMessageHandlerInterface,
} from '@standardnotes/domain-events'
import { RedisDomainEventSubscriber } from './RedisDomainEventSubscriber'
export class RedisDomainEventSubscriberFactory implements DomainEventSubscriberFactoryInterface {
constructor(
private redisClient: IORedis.Redis,
private domainEventMessageHandler: DomainEventMessageHandlerInterface,
private eventChannel: string,
) {}
create(): DomainEventSubscriberInterface {
const subscriber = new RedisDomainEventSubscriber(this.redisClient, this.eventChannel)
this.redisClient.on(
'message',
/* istanbul ignore next */
async (_channel: string, message: string) => await this.domainEventMessageHandler.handleMessage(message),
)
return subscriber
}
}

View File

@@ -4,6 +4,8 @@ import { DomainEventSubscriberInterface, DomainEventMessageHandlerInterface } fr
import { Logger } from 'winston'
export class SQSDomainEventSubscriber implements DomainEventSubscriberInterface {
private consumer: Consumer | undefined
constructor(
private sqs: SQSClient,
private queueUrl: string,
@@ -23,9 +25,18 @@ export class SQSDomainEventSubscriber implements DomainEventSubscriberInterface
sqsConsumer.on('error', this.handleError.bind(this))
sqsConsumer.on('processing_error', this.handleError.bind(this))
this.consumer = sqsConsumer
sqsConsumer.start()
}
stop(): void {
if (this.consumer && this.consumer.isRunning) {
this.logger.info('Stopping SQS consumer...')
this.consumer.stop()
}
}
async handleMessage(message: Message): Promise<void> {
await this.domainEventMessageHandler.handleMessage(<string>message.Body)
}

View File

@@ -1,32 +0,0 @@
import { Consumer } from 'sqs-consumer'
import { Message, SQSClient } from '@aws-sdk/client-sqs'
import {
DomainEventMessageHandlerInterface,
DomainEventSubscriberFactoryInterface,
DomainEventSubscriberInterface,
} from '@standardnotes/domain-events'
export class SQSDomainEventSubscriberFactory implements DomainEventSubscriberFactoryInterface {
constructor(
private sqs: SQSClient,
private queueUrl: string,
private domainEventMessageHandler: DomainEventMessageHandlerInterface,
) {}
create(): DomainEventSubscriberInterface {
const sqsConsumer = Consumer.create({
attributeNames: ['All'],
messageAttributeNames: ['All'],
queueUrl: this.queueUrl,
sqs: this.sqs,
handleMessage:
/* istanbul ignore next */
async (message: Message) => await this.domainEventMessageHandler.handleMessage(<string>message.Body),
})
sqsConsumer.on('error', this.domainEventMessageHandler.handleError.bind(this.domainEventMessageHandler))
sqsConsumer.on('processing_error', this.domainEventMessageHandler.handleError.bind(this.domainEventMessageHandler))
return sqsConsumer
}
}

View File

@@ -5,6 +5,7 @@ import { DomainEventSubscriberInterface, DomainEventMessageHandlerInterface } fr
import { Logger } from 'winston'
export class SQSOpenTelemetryDomainEventSubscriber implements DomainEventSubscriberInterface {
private consumer: Consumer | undefined
private currentSpan: OpenTelemetryApi.Span | undefined
constructor(
@@ -28,9 +29,18 @@ export class SQSOpenTelemetryDomainEventSubscriber implements DomainEventSubscri
sqsConsumer.on('error', this.handleError.bind(this))
sqsConsumer.on('processing_error', this.handleError.bind(this))
this.consumer = sqsConsumer
sqsConsumer.start()
}
stop(): void {
if (this.consumer && this.consumer.isRunning) {
this.logger.info('Stopping SQS consumer...')
this.consumer.stop()
}
}
async startParentSpan(): Promise<void> {
const tracer = OpenTelemetryApi.trace.getTracer(`${this.serviceName}-domain-event-subscriber`)

View File

@@ -7,8 +7,6 @@ export * from './OpenTelemetry/OpenTelemetryTracer'
export * from './OpenTelemetry/OpenTelemetryTracerInterface'
export * from './Redis/RedisDomainEventPublisher'
export * from './Redis/RedisDomainEventSubscriber'
export * from './Redis/RedisDomainEventSubscriberFactory'
export * from './Redis/RedisEventMessageHandler'
export * from './SNS/SNSDomainEventPublisher'
@@ -16,6 +14,5 @@ export * from './SNS/SNSOpenTelemetryDomainEventPublisher'
export * from './SQS/SQSBounceNotificiationHandler'
export * from './SQS/SQSDomainEventSubscriber'
export * from './SQS/SQSDomainEventSubscriberFactory'
export * from './SQS/SQSEventMessageHandler'
export * from './SQS/SQSOpenTelemetryDomainEventSubscriber'

View File

@@ -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.134.2](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.134.1...@standardnotes/domain-events@2.134.2) (2023-11-13)
### Bug Fixes
* **domain-events:** remove unused event ([f6ec862](https://github.com/standardnotes/server/commit/f6ec8626e57f1d93210aaf9fae4c15ae032aca8e))
## [2.134.1](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.134.0...@standardnotes/domain-events@2.134.1) (2023-11-13)
### Bug Fixes
* remove unused even-store from code base ([#925](https://github.com/standardnotes/server/issues/925)) ([8e4e365](https://github.com/standardnotes/server/commit/8e4e36513aa6e3c4f98197adfa75e014920b3572))
# [2.134.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.133.1...@standardnotes/domain-events@2.134.0) (2023-11-10)
### Features
* add graceful shutdown procedures upon SIGTERM ([#923](https://github.com/standardnotes/server/issues/923)) ([c24353c](https://github.com/standardnotes/server/commit/c24353cc24ebf4b40ff9a2cec8e37cfdef109e37))
## [2.133.1](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.133.0...@standardnotes/domain-events@2.133.1) (2023-11-07)
### Bug Fixes

View File

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

View File

@@ -1,7 +0,0 @@
import { DomainEventInterface } from './DomainEventInterface'
import { EmailSentEventPayload } from './EmailSentEventPayload'
export interface EmailSentEvent extends DomainEventInterface {
type: 'EMAIL_SENT'
payload: EmailSentEventPayload
}

View File

@@ -1,15 +0,0 @@
export interface EmailSentEventPayload {
userEmail: string
messageIdentifier: string
level: string
subject: string
body: string
sender?: string
additionalStyles?: string
attachments?: Array<{
filePath: string
fileName: string
attachmentFileName: string
attachmentContentType: string
}>
}

View File

@@ -1,5 +0,0 @@
import { DomainEventSubscriberInterface } from './DomainEventSubscriberInterface'
export interface DomainEventSubscriberFactoryInterface {
create(): DomainEventSubscriberInterface
}

View File

@@ -1,3 +1,4 @@
export interface DomainEventSubscriberInterface {
start(): void
stop(): void
}

View File

@@ -14,8 +14,6 @@ export * from './Event/EmailBouncedEvent'
export * from './Event/EmailBouncedEventPayload'
export * from './Event/EmailRequestedEvent'
export * from './Event/EmailRequestedEventPayload'
export * from './Event/EmailSentEvent'
export * from './Event/EmailSentEventPayload'
export * from './Event/EmailSubscriptionUnsubscribedEvent'
export * from './Event/EmailSubscriptionUnsubscribedEventPayload'
export * from './Event/ExitDiscountAppliedEvent'
@@ -122,5 +120,4 @@ export * from './Handler/DomainEventMessageHandlerInterface'
export * from './Publisher/DomainEventPublisherInterface'
export * from './Subscriber/DomainEventSubscriberFactoryInterface'
export * from './Subscriber/DomainEventSubscriberInterface'

View File

@@ -1,15 +0,0 @@
LOG_LEVEL=debug
NODE_ENV=development
VERSION=development
DB_HOST=127.0.0.1
DB_REPLICA_HOST=127.0.0.1
DB_PORT=3306
DB_USERNAME=store
DB_PASSWORD=changeme123
DB_DATABASE=store
DB_DEBUG_LEVEL=all # "all" | "query" | "schema" | "error" | "warn" | "info" | "log" | "migration"
DB_MIGRATIONS_PATH=dist/migrations/*.js
SQS_QUEUE_URL=
SQS_AWS_REGION=

View File

@@ -1,2 +0,0 @@
dist
test-setup.ts

View File

@@ -1,7 +0,0 @@
{
"extends": "../../.eslintrc",
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": "./linter.tsconfig.json"
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,17 +0,0 @@
FROM node:20.6.1-alpine
RUN apk add --update \
curl \
&& rm -rf /var/cache/apk/*
ENV NODE_ENV production
RUN corepack enable
COPY ./ /workspace
WORKDIR /workspace/packages/event-store
ENTRYPOINT [ "/workspace/packages/event-store/docker/entrypoint.sh" ]
CMD [ "start-worker" ]

View File

@@ -1,21 +0,0 @@
import 'reflect-metadata'
import { Logger } from 'winston'
import { ContainerConfigLoader } from '../src/Bootstrap/Container'
import TYPES from '../src/Bootstrap/Types'
import { Env } from '../src/Bootstrap/Env'
import { DomainEventSubscriberFactoryInterface } from '@standardnotes/domain-events'
const container = new ContainerConfigLoader()
void container.load().then((container) => {
const env: Env = new Env()
env.load()
const logger: Logger = container.get(TYPES.Logger)
logger.info('Starting worker...')
const subscriberFactory: DomainEventSubscriberFactoryInterface = container.get(TYPES.DomainEventSubscriberFactory)
subscriberFactory.create().start()
})

View File

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

View File

@@ -1,17 +0,0 @@
#!/bin/sh
set -e
COMMAND=$1 && shift 1
case "$COMMAND" in
'start-worker' )
echo "Starting Worker..."
node docker/entrypoint-worker.js
;;
* )
echo "Unknown command"
;;
esac
exec "$@"

View File

@@ -1,12 +0,0 @@
// eslint-disable-next-line @typescript-eslint/no-var-requires
const base = require('../../jest.config')
const { defaults: tsjPreset } = require('ts-jest/presets')
module.exports = {
...base,
transform: {
...tsjPreset.transform,
},
coveragePathIgnorePatterns: ['/Bootstrap/'],
setupFilesAfterEnv: ['./test-setup.ts'],
}

View File

@@ -1,4 +0,0 @@
{
"extends": "./tsconfig.json",
"exclude": ["dist", "test-setup.ts"]
}

View File

@@ -1,16 +0,0 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class initDatabase1639394147420 implements MigrationInterface {
name = 'initDatabase1639394147420'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
'CREATE TABLE `events` (`uuid` varchar(36) NOT NULL, `user_identifier` varchar(255) NOT NULL, `user_identifier_type` varchar(255) NOT NULL, `event_type` varchar(255) NOT NULL, `event_payload` text NOT NULL, `timestamp` bigint NOT NULL, INDEX `index_events_on_user_identifier` (`user_identifier`), PRIMARY KEY (`uuid`)) ENGINE=InnoDB',
)
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('DROP INDEX `index_events_on_user_identifier` ON `events`')
await queryRunner.query('DROP TABLE `events`')
}
}

View File

@@ -1,48 +0,0 @@
{
"name": "@standardnotes/event-store",
"version": "1.13.19",
"description": "Event Store Service",
"private": true,
"main": "dist/src/index.js",
"typings": "dist/src/index.d.ts",
"engines": {
"node": ">=18.0.0 <21.0.0"
},
"scripts": {
"clean": "rm -fr dist",
"build": "tsc --build",
"lint": "eslint . --ext .ts",
"pretest": "yarn lint && yarn build",
"test": "jest --coverage --no-cache --config=./jest.config.js --maxWorkers=50%",
"worker": "yarn node dist/bin/worker.js"
},
"author": "Karol Sójko <karol@standardnotes.com>",
"license": "AGPL-3.0-or-later",
"devDependencies": {
"@types/ioredis": "^5.0.0",
"@types/jest": "^29.5.1",
"@types/nodemailer": "^6.4.1",
"@typescript-eslint/eslint-plugin": "^6.5.0",
"@typescript-eslint/parser": "^6.5.0",
"eslint": "^8.39.0",
"eslint-plugin-prettier": "^5.0.0",
"jest": "^29.5.0",
"prettier": "^3.0.3",
"ts-jest": "^29.1.0",
"typescript": "^5.0.4"
},
"dependencies": {
"@aws-sdk/client-sqs": "^3.427.0",
"@standardnotes/domain-core": "workspace:^",
"@standardnotes/domain-events": "workspace:*",
"@standardnotes/domain-events-infra": "workspace:*",
"@standardnotes/time": "workspace:*",
"dotenv": "^16.0.1",
"inversify": "^6.0.1",
"ioredis": "^5.2.4",
"mysql2": "^3.0.1",
"reflect-metadata": "0.1.13",
"typeorm": "^0.3.17",
"winston": "^3.8.1"
}
}

View File

@@ -1,110 +0,0 @@
import * as winston from 'winston'
import { SQSClient, SQSClientConfig } from '@aws-sdk/client-sqs'
import { Container } from 'inversify'
import { Event } from '../Domain/Event/Event'
import { Env } from './Env'
import TYPES from './Types'
import {
DomainEventHandlerInterface,
DomainEventMessageHandlerInterface,
DomainEventSubscriberFactoryInterface,
} from '@standardnotes/domain-events'
import { SQSDomainEventSubscriberFactory, SQSEventMessageHandler } from '@standardnotes/domain-events-infra'
import { Timer, TimerInterface } from '@standardnotes/time'
import { EventHandler } from '../Domain/Handler/EventHandler'
import { AppDataSource } from './DataSource'
import { Repository } from 'typeorm'
export class ContainerConfigLoader {
async load(): Promise<Container> {
const env: Env = new Env()
env.load()
const container = new Container()
await AppDataSource.initialize()
if (env.get('SQS_QUEUE_URL', true)) {
const sqsConfig: SQSClientConfig = {
region: env.get('SQS_AWS_REGION', true),
}
if (env.get('SQS_ENDPOINT', true)) {
sqsConfig.endpoint = env.get('SQS_ENDPOINT', true)
}
if (env.get('SQS_ACCESS_KEY_ID', true) && env.get('SQS_SECRET_ACCESS_KEY', true)) {
sqsConfig.credentials = {
accessKeyId: env.get('SQS_ACCESS_KEY_ID', true),
secretAccessKey: env.get('SQS_SECRET_ACCESS_KEY', true),
}
}
container.bind<SQSClient>(TYPES.SQS).toConstantValue(new SQSClient(sqsConfig))
}
const logger = winston.createLogger({
level: env.get('LOG_LEVEL', true) || 'info',
format: winston.format.combine(winston.format.splat(), winston.format.json()),
transports: [new winston.transports.Console({ level: env.get('LOG_LEVEL', true) || 'info' })],
})
container.bind<winston.Logger>(TYPES.Logger).toConstantValue(logger)
container.bind<TimerInterface>(TYPES.Timer).toConstantValue(new Timer())
// env vars
container.bind(TYPES.SQS_AWS_REGION).toConstantValue(env.get('SQS_AWS_REGION'))
container.bind(TYPES.SQS_QUEUE_URL).toConstantValue(env.get('SQS_QUEUE_URL'))
// ORM
container.bind<Repository<Event>>(TYPES.ORMEventRepository).toConstantValue(AppDataSource.getRepository(Event))
// Handlers
container.bind<EventHandler>(TYPES.EventHandler).to(EventHandler)
const eventHandlers: Map<string, DomainEventHandlerInterface> = new Map([
['USER_REGISTERED', container.get(TYPES.EventHandler)],
['ACCOUNT_DELETION_REQUESTED', container.get(TYPES.EventHandler)],
['SUBSCRIPTION_PURCHASED', container.get(TYPES.EventHandler)],
['SUBSCRIPTION_CANCELLED', container.get(TYPES.EventHandler)],
['SUBSCRIPTION_RENEWED', container.get(TYPES.EventHandler)],
['SUBSCRIPTION_REFUNDED', container.get(TYPES.EventHandler)],
['SUBSCRIPTION_SYNC_REQUESTED', container.get(TYPES.EventHandler)],
['SUBSCRIPTION_EXPIRED', container.get(TYPES.EventHandler)],
['EXTENSION_KEY_GRANTED', container.get(TYPES.EventHandler)],
['SUBSCRIPTION_REASSIGNED', container.get(TYPES.EventHandler)],
['USER_EMAIL_CHANGED', container.get(TYPES.EventHandler)],
['FILE_UPLOADED', container.get(TYPES.EventHandler)],
['FILE_REMOVED', container.get(TYPES.EventHandler)],
['LISTED_ACCOUNT_REQUESTED', container.get(TYPES.EventHandler)],
['LISTED_ACCOUNT_CREATED', container.get(TYPES.EventHandler)],
['LISTED_ACCOUNT_DELETED', container.get(TYPES.EventHandler)],
['EMAIL_REQUESTED', container.get(TYPES.EventHandler)],
['EMAIL_SENT', container.get(TYPES.EventHandler)],
['SHARED_SUBSCRIPTION_INVITATION_CREATED', container.get(TYPES.EventHandler)],
['EMAIL_BACKUP_REQUESTED', container.get(TYPES.EventHandler)],
['PAYMENT_FAILED', container.get(TYPES.EventHandler)],
['PAYMENT_SUCCESS', container.get(TYPES.EventHandler)],
['SUBSCRIPTION_REVERT_REQUESTED', container.get(TYPES.EventHandler)],
['REFUND_PROCESSED', container.get(TYPES.EventHandler)],
['DISCOUNT_APPLY_REQUESTED', container.get(TYPES.EventHandler)],
['DISCOUNT_WITHDRAW_REQUESTED', container.get(TYPES.EventHandler)],
['SUBSCRIPTION_REACTIVATED', container.get(TYPES.EventHandler)],
['EXIT_DISCOUNT_APPLY_REQUESTED', container.get(TYPES.EventHandler)],
['EXIT_DISCOUNT_APPLIED', container.get(TYPES.EventHandler)],
['EXIT_DISCOUNT_WITHDRAW_REQUESTED', container.get(TYPES.EventHandler)],
])
container
.bind<DomainEventMessageHandlerInterface>(TYPES.DomainEventMessageHandler)
.toConstantValue(new SQSEventMessageHandler(eventHandlers, container.get(TYPES.Logger)))
container
.bind<DomainEventSubscriberFactoryInterface>(TYPES.DomainEventSubscriberFactory)
.toConstantValue(
new SQSDomainEventSubscriberFactory(
container.get(TYPES.SQS),
container.get(TYPES.SQS_QUEUE_URL),
container.get(TYPES.DomainEventMessageHandler),
),
)
return container
}
}

View File

@@ -1,51 +0,0 @@
import { DataSource, LoggerOptions } from 'typeorm'
import { Event } from '../Domain/Event/Event'
import { Env } from './Env'
const env: Env = new Env()
env.load()
const maxQueryExecutionTime = env.get('DB_MAX_QUERY_EXECUTION_TIME', true)
? +env.get('DB_MAX_QUERY_EXECUTION_TIME', true)
: 45_000
const inReplicaMode = env.get('DB_REPLICA_HOST', true) ? true : false
const replicationConfig = {
master: {
host: env.get('DB_HOST'),
port: parseInt(env.get('DB_PORT')),
username: env.get('DB_USERNAME'),
password: env.get('DB_PASSWORD'),
database: env.get('DB_DATABASE'),
},
slaves: [
{
host: env.get('DB_REPLICA_HOST', true),
port: parseInt(env.get('DB_PORT')),
username: env.get('DB_USERNAME'),
password: env.get('DB_PASSWORD'),
database: env.get('DB_DATABASE'),
},
],
removeNodeErrorCount: 10,
restoreNodeTimeout: 5,
}
export const AppDataSource = new DataSource({
type: 'mysql',
charset: 'utf8mb4',
supportBigNumbers: true,
bigNumberStrings: false,
maxQueryExecutionTime,
replication: inReplicaMode ? replicationConfig : undefined,
host: inReplicaMode ? undefined : env.get('DB_HOST'),
port: inReplicaMode ? undefined : parseInt(env.get('DB_PORT')),
username: inReplicaMode ? undefined : env.get('DB_USERNAME'),
password: inReplicaMode ? undefined : env.get('DB_PASSWORD'),
database: inReplicaMode ? undefined : env.get('DB_DATABASE'),
entities: [Event],
migrations: [env.get('DB_MIGRATIONS_PATH', true) ?? 'dist/migrations/*.js'],
migrationsRun: true,
logging: <LoggerOptions>env.get('DB_DEBUG_LEVEL'),
})

View File

@@ -1,9 +0,0 @@
import { AbstractEnv } from '@standardnotes/domain-core'
import { config, DotenvParseOutput } from 'dotenv'
export class Env extends AbstractEnv {
load(): void {
const output = config()
this.env = <DotenvParseOutput>output.parsed
}
}

View File

@@ -1,17 +0,0 @@
const TYPES = {
Logger: Symbol.for('Logger'),
SQS: Symbol.for('SQS'),
// env vars
SQS_QUEUE_URL: Symbol.for('SQS_QUEUE_URL'),
SQS_AWS_REGION: Symbol.for('SQS_AWS_REGION'),
// Handlers
DomainEventSubscriberFactory: Symbol.for('DomainEventSubscriberFactory'),
DomainEventMessageHandler: Symbol.for('DomainEventMessageHandler'),
EventHandler: Symbol.for('EventHandler'),
// ORM
ORMEventRepository: Symbol.for('ORMEventRepository'),
// Services
Timer: Symbol.for('Timer'),
}
export default TYPES

View File

@@ -1,38 +0,0 @@
import { Column, Entity, Index, PrimaryGeneratedColumn } from 'typeorm'
@Entity({ name: 'events' })
export class Event {
@PrimaryGeneratedColumn('uuid')
declare uuid: string
@Column({
name: 'user_identifier',
length: 255,
})
@Index('index_events_on_user_identifier')
declare userIdentifier: string
@Column({
name: 'user_identifier_type',
length: 255,
})
declare userIdentifierType: string
@Column({
name: 'event_type',
length: 255,
})
declare eventType: string
@Column({
name: 'event_payload',
type: 'text',
})
declare eventPayload: string
@Column({
name: 'timestamp',
type: 'bigint',
})
declare timestamp: number
}

View File

@@ -1,76 +0,0 @@
import 'reflect-metadata'
import { TimerInterface } from '@standardnotes/time'
import { Repository } from 'typeorm'
import { EventHandler } from './EventHandler'
import { Event } from '../Event/Event'
import { Logger } from 'winston'
import { DomainEventInterface } from '@standardnotes/domain-events'
describe('EventHandler', () => {
let timer: TimerInterface
let repository: Repository<Event>
let logger: Logger
const createHandler = () => new EventHandler(timer, repository, logger)
beforeEach(() => {
timer = {} as jest.Mocked<TimerInterface>
timer.convertStringDateToMicroseconds = jest.fn().mockReturnValue(1)
repository = {} as jest.Mocked<Repository<Event>>
repository.save = jest.fn()
logger = {} as jest.Mocked<Logger>
logger.debug = jest.fn()
logger.error = jest.fn()
})
it('should persist as event in the store', async () => {
const event = {
type: 'test',
createdAt: new Date(2),
meta: {
correlation: {
userIdentifier: '1-2-3',
userIdentifierType: 'uuid',
},
},
payload: {
foo: 'bar',
},
} as jest.Mocked<DomainEventInterface>
await createHandler().handle(event)
expect(repository.save).toHaveBeenCalledWith({
eventType: 'test',
timestamp: 1,
userIdentifier: '1-2-3',
userIdentifierType: 'uuid',
eventPayload: '{"foo":"bar"}',
})
})
it('should inform about failure to saven the event in the store', async () => {
const event = {
type: 'test',
createdAt: new Date(2),
meta: {
correlation: {
userIdentifier: '1-2-3',
userIdentifierType: 'uuid',
},
},
payload: {
foo: 'bar',
},
} as jest.Mocked<DomainEventInterface>
repository.save = jest.fn().mockImplementation(() => {
throw new Error('Ooops')
})
await createHandler().handle(event)
expect(logger.error).toHaveBeenCalledWith('Could not store event %O in the event store: %s', event, 'Ooops')
})
})

View File

@@ -1,33 +0,0 @@
import { DomainEventHandlerInterface, DomainEventInterface } from '@standardnotes/domain-events'
import { TimerInterface } from '@standardnotes/time'
import { inject, injectable } from 'inversify'
import { Repository } from 'typeorm'
import { Logger } from 'winston'
import TYPES from '../../Bootstrap/Types'
import { Event } from '../Event/Event'
@injectable()
export class EventHandler implements DomainEventHandlerInterface {
constructor(
@inject(TYPES.Timer) private timer: TimerInterface,
@inject(TYPES.ORMEventRepository) private eventRepository: Repository<Event>,
@inject(TYPES.Logger) private logger: Logger,
) {}
async handle(event: DomainEventInterface): Promise<void> {
this.logger.debug('Handling event: %O', event)
try {
const storedEvent = new Event()
storedEvent.eventType = event.type
storedEvent.userIdentifier = event.meta.correlation.userIdentifier
storedEvent.userIdentifierType = event.meta.correlation.userIdentifierType
storedEvent.eventPayload = JSON.stringify(event.payload)
storedEvent.timestamp = this.timer.convertStringDateToMicroseconds(event.createdAt.toString())
await this.eventRepository.save(storedEvent)
} catch (error) {
this.logger.error('Could not store event %O in the event store: %s', event, (error as Error).message)
}
}
}

View File

@@ -1,13 +0,0 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"composite": true,
"outDir": "./dist",
},
"include": [
"src/**/*",
"bin/**/*",
"migrations/**/*",
],
"references": []
}

View File

@@ -3,6 +3,26 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.34.2](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.34.1...@standardnotes/files-server@1.34.2) (2023-11-13)
**Note:** Version bump only for package @standardnotes/files-server
## [1.34.1](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.34.0...@standardnotes/files-server@1.34.1) (2023-11-13)
**Note:** Version bump only for package @standardnotes/files-server
# [1.34.0](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.33.0...@standardnotes/files-server@1.34.0) (2023-11-10)
### Features
* add keep-alive connections to subservices ([#924](https://github.com/standardnotes/files/issues/924)) ([daad76d](https://github.com/standardnotes/files/commit/daad76d0ddae34c59dce45eedc4a055c4a11456d))
# [1.33.0](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.32.5...@standardnotes/files-server@1.33.0) (2023-11-10)
### Features
* add graceful shutdown procedures upon SIGTERM ([#923](https://github.com/standardnotes/files/issues/923)) ([c24353c](https://github.com/standardnotes/files/commit/c24353cc24ebf4b40ff9a2cec8e37cfdef109e37))
## [1.32.5](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.32.4...@standardnotes/files-server@1.32.5) (2023-11-07)
### Bug Fixes

View File

@@ -89,9 +89,18 @@ void container.load().then((container) => {
})
})
const serverInstance = server.build()
const serverInstance = server.build().listen(env.get('PORT'))
serverInstance.listen(env.get('PORT'))
const keepAliveTimeout = env.get('KEEP_ALIVE_TIMEOUT', true) ? +env.get('KEEP_ALIVE_TIMEOUT', true) : 5000
serverInstance.keepAliveTimeout = keepAliveTimeout
process.on('SIGTERM', () => {
logger.info('SIGTERM signal received: closing HTTP server')
serverInstance.close(() => {
logger.info('HTTP server closed')
})
})
logger.info(`Server started on port ${process.env.PORT}`)
})

View File

@@ -22,5 +22,11 @@ void container.load().then((container) => {
const subscriber = container.get<DomainEventSubscriberInterface>(TYPES.Files_DomainEventSubscriber)
process.on('SIGTERM', () => {
logger.info('SIGTERM received. Stopping worker...')
subscriber.stop()
logger.info('Worker stopped.')
})
subscriber.start()
})

View File

@@ -6,12 +6,12 @@ COMMAND=$1 && shift 1
case "$COMMAND" in
'start-web' )
echo "Starting Web..."
node docker/entrypoint-server.js
exec node docker/entrypoint-server.js
;;
'start-worker' )
echo "Starting Worker..."
node docker/entrypoint-worker.js
exec node docker/entrypoint-worker.js
;;
* )

View File

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

View File

@@ -4,4 +4,4 @@ set -euo pipefail
sh supervisor/wait-for.sh $DB_HOST $DB_PORT
sh supervisor/wait-for.sh $REDIS_HOST $REDIS_PORT
node docker/entrypoint-server.js
exec node docker/entrypoint-server.js

View File

@@ -3,4 +3,4 @@
set -euo pipefail
sh supervisor/wait-for.sh localhost $SYNCING_SERVER_PORT
node docker/entrypoint-worker.js
exec node docker/entrypoint-worker.js

View File

@@ -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.20.3](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.20.2...@standardnotes/home-server@1.20.3) (2023-11-13)
**Note:** Version bump only for package @standardnotes/home-server
## [1.20.2](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.20.1...@standardnotes/home-server@1.20.2) (2023-11-13)
**Note:** Version bump only for package @standardnotes/home-server
## [1.20.1](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.20.0...@standardnotes/home-server@1.20.1) (2023-11-13)
**Note:** Version bump only for package @standardnotes/home-server
# [1.20.0](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.19.1...@standardnotes/home-server@1.20.0) (2023-11-10)
### Features
* add keep-alive connections to subservices ([#924](https://github.com/standardnotes/server/issues/924)) ([daad76d](https://github.com/standardnotes/server/commit/daad76d0ddae34c59dce45eedc4a055c4a11456d))
## [1.19.1](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.19.0...@standardnotes/home-server@1.19.1) (2023-11-10)
**Note:** Version bump only for package @standardnotes/home-server
# [1.19.0](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.18.32...@standardnotes/home-server@1.19.0) (2023-11-10)
### Features
* add graceful shutdown procedures upon SIGTERM ([#923](https://github.com/standardnotes/server/issues/923)) ([c24353c](https://github.com/standardnotes/server/commit/c24353cc24ebf4b40ff9a2cec8e37cfdef109e37))
## [1.18.32](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.18.31...@standardnotes/home-server@1.18.32) (2023-11-10)
**Note:** Version bump only for package @standardnotes/home-server
## [1.18.31](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.18.30...@standardnotes/home-server@1.18.31) (2023-11-09)
**Note:** Version bump only for package @standardnotes/home-server
## [1.18.30](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.18.29...@standardnotes/home-server@1.18.30) (2023-11-09)
**Note:** Version bump only for package @standardnotes/home-server
## [1.18.29](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.18.28...@standardnotes/home-server@1.18.29) (2023-11-09)
**Note:** Version bump only for package @standardnotes/home-server
## [1.18.28](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.18.27...@standardnotes/home-server@1.18.28) (2023-11-08)
**Note:** Version bump only for package @standardnotes/home-server

View File

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

View File

@@ -174,7 +174,20 @@ export class HomeServer implements HomeServerInterface {
const port = env.get('PORT', true) ? +env.get('PORT', true) : 3000
this.serverInstance = server.build().listen(port)
const serverInstance = server.build().listen(port)
const keepAliveTimeout = env.get('KEEP_ALIVE_TIMEOUT', true) ? +env.get('KEEP_ALIVE_TIMEOUT', true) : 5000
serverInstance.keepAliveTimeout = keepAliveTimeout
this.serverInstance = serverInstance
process.on('SIGTERM', () => {
logger.info('SIGTERM signal received: closing HTTP server')
serverInstance.close(() => {
logger.info('HTTP server closed')
})
})
logger.info(`Server started on port ${port}. Log level: ${env.get('LOG_LEVEL', true)}.`)

View File

@@ -3,6 +3,26 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.49.2](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.49.1...@standardnotes/revisions-server@1.49.2) (2023-11-13)
**Note:** Version bump only for package @standardnotes/revisions-server
## [1.49.1](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.49.0...@standardnotes/revisions-server@1.49.1) (2023-11-13)
**Note:** Version bump only for package @standardnotes/revisions-server
# [1.49.0](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.48.0...@standardnotes/revisions-server@1.49.0) (2023-11-10)
### Features
* add keep-alive connections to subservices ([#924](https://github.com/standardnotes/server/issues/924)) ([daad76d](https://github.com/standardnotes/server/commit/daad76d0ddae34c59dce45eedc4a055c4a11456d))
# [1.48.0](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.47.5...@standardnotes/revisions-server@1.48.0) (2023-11-10)
### Features
* add graceful shutdown procedures upon SIGTERM ([#923](https://github.com/standardnotes/server/issues/923)) ([c24353c](https://github.com/standardnotes/server/commit/c24353cc24ebf4b40ff9a2cec8e37cfdef109e37))
## [1.47.5](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.47.4...@standardnotes/revisions-server@1.47.5) (2023-11-07)
### Bug Fixes

View File

@@ -43,9 +43,18 @@ void container.load().then((container) => {
})
})
const serverInstance = server.build()
const serverInstance = server.build().listen(env.get('PORT'))
serverInstance.listen(env.get('PORT'))
const keepAliveTimeout = env.get('KEEP_ALIVE_TIMEOUT', true) ? +env.get('KEEP_ALIVE_TIMEOUT', true) : 5000
serverInstance.keepAliveTimeout = keepAliveTimeout
process.on('SIGTERM', () => {
logger.info('SIGTERM signal received: closing HTTP server')
serverInstance.close(() => {
logger.info('HTTP server closed')
})
})
logger.info(`Server started on port ${process.env.PORT}`)
})

View File

@@ -18,5 +18,11 @@ void container.load().then((container) => {
const subscriber = container.get<DomainEventSubscriberInterface>(TYPES.Revisions_DomainEventSubscriber)
process.on('SIGTERM', () => {
logger.info('SIGTERM received. Stopping worker...')
subscriber.stop()
logger.info('Worker stopped.')
})
subscriber.start()
})

View File

@@ -6,12 +6,12 @@ COMMAND=$1 && shift 1
case "$COMMAND" in
'start-web' )
echo "Starting Web..."
node docker/entrypoint-server.js
exec node docker/entrypoint-server.js
;;
'start-worker' )
echo "Starting Worker..."
node docker/entrypoint-worker.js
exec node docker/entrypoint-worker.js
;;
* )

View File

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

View File

@@ -3,4 +3,4 @@
set -euo pipefail
sh supervisor/wait-for.sh localhost $SYNCING_SERVER_PORT
node docker/entrypoint-server.js
exec node docker/entrypoint-server.js

View File

@@ -3,4 +3,4 @@
set -euo pipefail
sh supervisor/wait-for.sh localhost $SYNCING_SERVER_PORT
node docker/entrypoint-worker.js
exec node docker/entrypoint-worker.js

View File

@@ -3,6 +3,20 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.27.2](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.27.1...@standardnotes/scheduler-server@1.27.2) (2023-11-13)
**Note:** Version bump only for package @standardnotes/scheduler-server
## [1.27.1](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.27.0...@standardnotes/scheduler-server@1.27.1) (2023-11-13)
**Note:** Version bump only for package @standardnotes/scheduler-server
# [1.27.0](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.26.6...@standardnotes/scheduler-server@1.27.0) (2023-11-10)
### Features
* add graceful shutdown procedures upon SIGTERM ([#923](https://github.com/standardnotes/server/issues/923)) ([c24353c](https://github.com/standardnotes/server/commit/c24353cc24ebf4b40ff9a2cec8e37cfdef109e37))
## [1.26.6](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.26.5...@standardnotes/scheduler-server@1.26.6) (2023-11-07)
**Note:** Version bump only for package @standardnotes/scheduler-server

View File

@@ -22,5 +22,11 @@ void container.load().then((container) => {
const subscriber = container.get<DomainEventSubscriberInterface>(TYPES.DomainEventSubscriber)
process.on('SIGTERM', () => {
logger.info('SIGTERM received. Stopping worker...')
subscriber.stop()
logger.info('Worker stopped.')
})
subscriber.start()
})

View File

@@ -6,12 +6,12 @@ COMMAND=$1 && shift 1
case "$COMMAND" in
'start-worker' )
echo "Starting Worker..."
node docker/entrypoint-worker.js
exec node docker/entrypoint-worker.js
;;
'verify-jobs' )
echo "Starting jobs verification..."
node docker/entrypoint-verify.js
exec node docker/entrypoint-verify.js
;;
* )

View File

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

View File

@@ -3,6 +3,32 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.122.2](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.122.1...@standardnotes/syncing-server@1.122.2) (2023-11-13)
**Note:** Version bump only for package @standardnotes/syncing-server
## [1.122.1](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.122.0...@standardnotes/syncing-server@1.122.1) (2023-11-13)
**Note:** Version bump only for package @standardnotes/syncing-server
# [1.122.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.121.0...@standardnotes/syncing-server@1.122.0) (2023-11-10)
### Features
* add keep-alive connections to subservices ([#924](https://github.com/standardnotes/syncing-server-js/issues/924)) ([daad76d](https://github.com/standardnotes/syncing-server-js/commit/daad76d0ddae34c59dce45eedc4a055c4a11456d))
# [1.121.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.120.5...@standardnotes/syncing-server@1.121.0) (2023-11-10)
### Features
* add graceful shutdown procedures upon SIGTERM ([#923](https://github.com/standardnotes/syncing-server-js/issues/923)) ([c24353c](https://github.com/standardnotes/syncing-server-js/commit/c24353cc24ebf4b40ff9a2cec8e37cfdef109e37))
## [1.120.5](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.120.4...@standardnotes/syncing-server@1.120.5) (2023-11-09)
### Bug Fixes
* remove unused axios dep in subservices ([45d4920](https://github.com/standardnotes/syncing-server-js/commit/45d4920e0fc2848a28ce888d139201e68c4b416f))
## [1.120.4](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.120.3...@standardnotes/syncing-server@1.120.4) (2023-11-08)
### Bug Fixes

View File

@@ -72,9 +72,18 @@ void container.load().then((container) => {
})
})
const serverInstance = server.build()
const serverInstance = server.build().listen(env.get('PORT'))
serverInstance.listen(env.get('PORT'))
const keepAliveTimeout = env.get('KEEP_ALIVE_TIMEOUT', true) ? +env.get('KEEP_ALIVE_TIMEOUT', true) : 5000
serverInstance.keepAliveTimeout = keepAliveTimeout
process.on('SIGTERM', () => {
logger.info('SIGTERM signal received: closing HTTP server')
serverInstance.close(() => {
logger.info('HTTP server closed')
})
})
logger.info(`Server started on port ${process.env.PORT}`)
})

View File

@@ -18,5 +18,11 @@ void container.load().then((container) => {
const subscriber = container.get<DomainEventSubscriberInterface>(TYPES.Sync_DomainEventSubscriber)
process.on('SIGTERM', () => {
logger.info('SIGTERM received. Stopping worker...')
subscriber.stop()
logger.info('Worker stopped.')
})
subscriber.start()
})

View File

@@ -6,12 +6,12 @@ COMMAND=$1 && shift 1
case "$COMMAND" in
'start-web' )
echo "[Docker] Starting Web..."
node docker/entrypoint-server.js
exec node docker/entrypoint-server.js
;;
'start-worker' )
echo "[Docker] Starting Worker..."
node docker/entrypoint-worker.js
exec node docker/entrypoint-worker.js
;;
* )

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/syncing-server",
"version": "1.120.4",
"version": "1.122.2",
"engines": {
"node": ">=18.0.0 <21.0.0"
},
@@ -40,7 +40,6 @@
"@standardnotes/settings": "workspace:*",
"@standardnotes/sncrypto-node": "workspace:*",
"@standardnotes/time": "workspace:*",
"axios": "^1.1.3",
"cors": "2.8.5",
"dotenv": "^16.0.1",
"express": "^4.18.2",

View File

@@ -44,9 +44,6 @@ import {
DomainEventPublisherInterface,
DomainEventSubscriberInterface,
} from '@standardnotes/domain-events'
import axios, { AxiosInstance } from 'axios'
import { ExtensionsHttpService } from '../Domain/Extension/ExtensionsHttpService'
import { ExtensionsHttpServiceInterface } from '../Domain/Extension/ExtensionsHttpServiceInterface'
import { AccountDeletionRequestedEventHandler } from '../Domain/Handler/AccountDeletionRequestedEventHandler'
import { DuplicateItemSyncedEventHandler } from '../Domain/Handler/DuplicateItemSyncedEventHandler'
import { EmailBackupRequestedEventHandler } from '../Domain/Handler/EmailBackupRequestedEventHandler'
@@ -949,20 +946,7 @@ export class ContainerConfigLoader {
)
// Services
container.bind<ContentDecoder>(TYPES.Sync_ContentDecoder).toDynamicValue(() => new ContentDecoder())
container.bind<AxiosInstance>(TYPES.Sync_HTTPClient).toDynamicValue(() => axios.create())
container
.bind<ExtensionsHttpServiceInterface>(TYPES.Sync_ExtensionsHttpService)
.toConstantValue(
new ExtensionsHttpService(
container.get<AxiosInstance>(TYPES.Sync_HTTPClient),
container.get<ItemRepositoryInterface>(TYPES.Sync_SQLItemRepository),
container.get<ContentDecoderInterface>(TYPES.Sync_ContentDecoder),
container.get<DomainEventPublisherInterface>(TYPES.Sync_DomainEventPublisher),
container.get<DomainEventFactoryInterface>(TYPES.Sync_DomainEventFactory),
container.get<Logger>(TYPES.Sync_Logger),
),
)
container.bind<ContentDecoderInterface>(TYPES.Sync_ContentDecoder).toDynamicValue(() => new ContentDecoder())
const eventHandlers: Map<string, DomainEventHandlerInterface> = new Map([
['DUPLICATE_ITEM_SYNCED', container.get(TYPES.Sync_DuplicateItemSyncedEventHandler)],

View File

@@ -104,7 +104,6 @@ const TYPES = {
Sync_SyncResponseFactory20161215: Symbol.for('Sync_SyncResponseFactory20161215'),
Sync_SyncResponseFactory20200115: Symbol.for('Sync_SyncResponseFactory20200115'),
Sync_SyncResponseFactoryResolver: Symbol.for('Sync_SyncResponseFactoryResolver'),
Sync_ExtensionsHttpService: Symbol.for('Sync_ExtensionsHttpService'),
Sync_ItemBackupService: Symbol.for('Sync_ItemBackupService'),
Sync_ItemSaveValidator: Symbol.for('Sync_ItemSaveValidator'),
Sync_OwnershipFilter: Symbol.for('Sync_OwnershipFilter'),

View File

@@ -1,5 +0,0 @@
export enum ExtensionName {
Dropbox = 'Dropbox',
GoogleDrive = 'Google Drive',
OneDrive = 'OneDrive',
}

View File

@@ -1,438 +0,0 @@
import 'reflect-metadata'
import { KeyParamsData } from '@standardnotes/responses'
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
import { Logger } from 'winston'
import { ContentDecoderInterface } from '../Item/ContentDecoderInterface'
import { Item } from '../Item/Item'
import { ItemRepositoryInterface } from '../Item/ItemRepositoryInterface'
import { ExtensionsHttpService } from './ExtensionsHttpService'
import { DomainEventFactoryInterface } from '../Event/DomainEventFactoryInterface'
import { AxiosInstance } from 'axios'
import { Uuid, ContentType, Dates, Timestamps, UniqueEntityId } from '@standardnotes/domain-core'
describe('ExtensionsHttpService', () => {
let httpClient: AxiosInstance
let primaryItemRepository: ItemRepositoryInterface
let contentDecoder: ContentDecoderInterface
let domainEventPublisher: DomainEventPublisherInterface
let domainEventFactory: DomainEventFactoryInterface
let item: Item
let authParams: KeyParamsData
let logger: Logger
const createService = () =>
new ExtensionsHttpService(
httpClient,
primaryItemRepository,
contentDecoder,
domainEventPublisher,
domainEventFactory,
logger,
)
beforeEach(() => {
httpClient = {} as jest.Mocked<AxiosInstance>
httpClient.request = jest.fn().mockReturnValue({ status: 200, data: { foo: 'bar' } })
item = Item.create(
{
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
updatedWithSession: null,
content: 'foobar',
contentType: ContentType.create(ContentType.TYPES.Note).getValue(),
encItemKey: null,
authHash: null,
itemsKeyId: null,
duplicateOf: null,
deleted: false,
dates: Dates.create(new Date(1616164633241311), new Date(1616164633241311)).getValue(),
timestamps: Timestamps.create(1616164633241311, 1616164633241311).getValue(),
},
new UniqueEntityId('00000000-0000-0000-0000-000000000000'),
).getValue()
authParams = {} as jest.Mocked<KeyParamsData>
primaryItemRepository = {} as jest.Mocked<ItemRepositoryInterface>
primaryItemRepository.findByUuidAndUserUuid = jest.fn().mockReturnValue(item)
logger = {} as jest.Mocked<Logger>
logger.error = jest.fn()
domainEventPublisher = {} as jest.Mocked<DomainEventPublisherInterface>
domainEventPublisher.publish = jest.fn()
domainEventFactory = {} as jest.Mocked<DomainEventFactoryInterface>
domainEventFactory.createEmailRequestedEvent = jest.fn()
contentDecoder = {} as jest.Mocked<ContentDecoderInterface>
contentDecoder.decode = jest.fn().mockReturnValue({ name: 'Dropbox' })
})
it('should trigger cloud backup on extensions server', async () => {
await createService().triggerCloudBackupOnExtensionsServer({
userUuid: '1-2-3',
extensionsServerUrl: 'https://extensions-server/extension1',
forceMute: false,
backupFilename: 'test',
authParams,
cloudProvider: 'DROPBOX',
})
expect(httpClient.request).toHaveBeenCalledWith({
data: {
auth_params: authParams,
backup_filename: 'test',
silent: false,
user_uuid: '1-2-3',
},
headers: {
'Content-Type': 'application/json',
},
method: 'POST',
url: 'https://extensions-server/extension1',
validateStatus: expect.any(Function),
})
})
it('should publish a failed Dropbox backup event if request was not sent successfully', async () => {
contentDecoder.decode = jest.fn().mockReturnValue({ name: 'Dropbox' })
httpClient.request = jest.fn().mockImplementation(() => {
throw new Error('Could not reach the extensions server')
})
await createService().triggerCloudBackupOnExtensionsServer({
userUuid: '1-2-3',
extensionsServerUrl: 'https://extensions-server/extension1',
forceMute: false,
backupFilename: 'test',
authParams,
cloudProvider: 'DROPBOX',
})
expect(domainEventPublisher.publish).toHaveBeenCalled()
expect(domainEventFactory.createEmailRequestedEvent).toHaveBeenCalled()
})
it('should send items to extensions server', async () => {
await createService().sendItemsToExtensionsServer({
userUuid: '1-2-3',
extensionId: '2-3-4',
extensionsServerUrl: 'https://extensions-server/extension1',
forceMute: false,
items: [item],
backupFilename: '',
authParams,
})
expect(httpClient.request).toHaveBeenCalledWith({
data: {
auth_params: authParams,
backup_filename: '',
items: [item],
silent: false,
user_uuid: '1-2-3',
},
headers: {
'Content-Type': 'application/json',
},
method: 'POST',
url: 'https://extensions-server/extension1',
validateStatus: expect.any(Function),
})
})
it('should send items proxy backup file name only to extensions server', async () => {
await createService().sendItemsToExtensionsServer({
userUuid: '1-2-3',
extensionId: '2-3-4',
extensionsServerUrl: 'https://extensions-server/extension1',
forceMute: false,
backupFilename: 'backup-file',
authParams,
})
expect(httpClient.request).toHaveBeenCalledWith({
data: {
auth_params: authParams,
backup_filename: 'backup-file',
silent: false,
user_uuid: '1-2-3',
},
headers: {
'Content-Type': 'application/json',
},
method: 'POST',
url: 'https://extensions-server/extension1',
validateStatus: expect.any(Function),
})
})
it('should publish a failed Dropbox backup event if request was not sent successfully', async () => {
contentDecoder.decode = jest.fn().mockReturnValue({ name: 'Dropbox' })
httpClient.request = jest.fn().mockImplementation(() => {
throw new Error('Could not reach the extensions server')
})
await createService().sendItemsToExtensionsServer({
userUuid: '1-2-3',
extensionId: '2-3-4',
extensionsServerUrl: 'https://extensions-server/extension1',
forceMute: false,
items: [item],
backupFilename: 'backup-file',
authParams,
})
expect(domainEventPublisher.publish).toHaveBeenCalled()
expect(domainEventFactory.createEmailRequestedEvent).toHaveBeenCalled()
})
it('should publish a failed Dropbox backup event if request was sent and extensions server responded not ok', async () => {
contentDecoder.decode = jest.fn().mockReturnValue({ name: 'Dropbox' })
httpClient.request = jest.fn().mockReturnValue({ status: 400, data: { error: 'foo-bar' } })
await createService().sendItemsToExtensionsServer({
userUuid: '1-2-3',
extensionId: '2-3-4',
extensionsServerUrl: 'https://extensions-server/extension1',
forceMute: false,
items: [item],
backupFilename: 'backup-file',
authParams,
})
expect(domainEventPublisher.publish).toHaveBeenCalled()
expect(domainEventFactory.createEmailRequestedEvent).toHaveBeenCalled()
})
it('should publish a failed Google Drive backup event if request was not sent successfully', async () => {
contentDecoder.decode = jest.fn().mockReturnValue({ name: 'Google Drive' })
httpClient.request = jest.fn().mockImplementation(() => {
throw new Error('Could not reach the extensions server')
})
await createService().sendItemsToExtensionsServer({
userUuid: '1-2-3',
extensionId: '2-3-4',
extensionsServerUrl: 'https://extensions-server/extension1',
forceMute: false,
items: [item],
backupFilename: 'backup-file',
authParams,
})
expect(domainEventPublisher.publish).toHaveBeenCalled()
expect(domainEventFactory.createEmailRequestedEvent).toHaveBeenCalled()
})
it('should publish a failed One Drive backup event if request was not sent successfully', async () => {
contentDecoder.decode = jest.fn().mockReturnValue({ name: 'OneDrive' })
httpClient.request = jest.fn().mockImplementation(() => {
throw new Error('Could not reach the extensions server')
})
await createService().sendItemsToExtensionsServer({
userUuid: '1-2-3',
extensionId: '2-3-4',
extensionsServerUrl: 'https://extensions-server/extension1',
forceMute: false,
items: [item],
backupFilename: 'backup-file',
authParams,
})
expect(domainEventPublisher.publish).toHaveBeenCalled()
expect(domainEventFactory.createEmailRequestedEvent).toHaveBeenCalled()
})
it('should not publish a failed backup event if emailes are force muted', async () => {
contentDecoder.decode = jest.fn().mockReturnValue({ name: 'OneDrive' })
httpClient.request = jest.fn().mockImplementation(() => {
throw new Error('Could not reach the extensions server')
})
await createService().sendItemsToExtensionsServer({
userUuid: '1-2-3',
extensionId: '2-3-4',
extensionsServerUrl: 'https://extensions-server/extension1',
forceMute: true,
items: [item],
backupFilename: 'backup-file',
authParams,
})
expect(domainEventPublisher.publish).not.toHaveBeenCalled()
})
it('should throw an error if the extension to post to is not found', async () => {
primaryItemRepository.findByUuidAndUserUuid = jest.fn().mockReturnValue(null)
httpClient.request = jest.fn().mockImplementation(() => {
throw new Error('Could not reach the extensions server')
})
let error = null
try {
await createService().sendItemsToExtensionsServer({
userUuid: '1-2-3',
extensionId: '2-3-4',
extensionsServerUrl: 'https://extensions-server/extension1',
forceMute: false,
items: [item],
backupFilename: 'backup-file',
authParams,
})
} catch (e) {
error = e
}
expect(error).not.toBeNull()
})
it('should throw an error if the extension to post to has no content', async () => {
item = {} as jest.Mocked<Item>
primaryItemRepository.findByUuidAndUserUuid = jest.fn().mockReturnValue(item)
httpClient.request = jest.fn().mockImplementation(() => {
throw new Error('Could not reach the extensions server')
})
let error = null
try {
await createService().sendItemsToExtensionsServer({
userUuid: '1-2-3',
extensionId: '2-3-4',
extensionsServerUrl: 'https://extensions-server/extension1',
forceMute: false,
items: [item],
backupFilename: 'backup-file',
authParams,
})
} catch (e) {
error = e
}
expect(error).not.toBeNull()
})
it('should publish a failed Dropbox backup event judging by extension url if request was not sent successfully', async () => {
contentDecoder.decode = jest.fn().mockReturnValue({ url: 'https://dbt.com/...' })
httpClient.request = jest.fn().mockImplementation(() => {
throw new Error('Could not reach the extensions server')
})
await createService().sendItemsToExtensionsServer({
userUuid: '1-2-3',
extensionId: '2-3-4',
extensionsServerUrl: 'https://extensions-server/extension1',
forceMute: false,
items: [item],
backupFilename: 'backup-file',
authParams,
})
expect(domainEventPublisher.publish).toHaveBeenCalled()
expect(domainEventFactory.createEmailRequestedEvent).toHaveBeenCalled()
})
it('should publish a failed Google Drive backup event judging by extension url if request was not sent successfully', async () => {
contentDecoder.decode = jest.fn().mockReturnValue({ url: 'https://gdrive.com/...' })
httpClient.request = jest.fn().mockImplementation(() => {
throw new Error('Could not reach the extensions server')
})
await createService().sendItemsToExtensionsServer({
userUuid: '1-2-3',
extensionId: '2-3-4',
extensionsServerUrl: 'https://extensions-server/extension1',
forceMute: false,
items: [item],
backupFilename: 'backup-file',
authParams,
})
expect(domainEventPublisher.publish).toHaveBeenCalled()
expect(domainEventFactory.createEmailRequestedEvent).toHaveBeenCalled()
})
it('should publish a failed One Drive backup event judging by extension url if request was not sent successfully', async () => {
contentDecoder.decode = jest.fn().mockReturnValue({ url: 'https://onedrive.com/...' })
httpClient.request = jest.fn().mockImplementation(() => {
throw new Error('Could not reach the extensions server')
})
await createService().sendItemsToExtensionsServer({
userUuid: '1-2-3',
extensionId: '2-3-4',
extensionsServerUrl: 'https://extensions-server/extension1',
forceMute: false,
items: [item],
backupFilename: 'backup-file',
authParams,
})
expect(domainEventPublisher.publish).toHaveBeenCalled()
expect(domainEventFactory.createEmailRequestedEvent).toHaveBeenCalled()
})
it('should throw an error if cannot deduce extension by judging from the url', async () => {
contentDecoder.decode = jest.fn().mockReturnValue({ url: 'https://foobar.com/...' })
httpClient.request = jest.fn().mockImplementation(() => {
throw new Error('Could not reach the extensions server')
})
let error = null
try {
await createService().sendItemsToExtensionsServer({
userUuid: '1-2-3',
extensionId: '2-3-4',
extensionsServerUrl: 'https://extensions-server/extension1',
forceMute: false,
items: [item],
backupFilename: 'backup-file',
authParams,
})
} catch (e) {
error = e
}
expect(error).not.toBeNull()
})
it('should throw an error if there is no extension name or url', async () => {
contentDecoder.decode = jest.fn().mockReturnValue({})
httpClient.request = jest.fn().mockImplementation(() => {
throw new Error('Could not reach the extensions server')
})
let error = null
try {
await createService().sendItemsToExtensionsServer({
userUuid: '1-2-3',
extensionId: '2-3-4',
extensionsServerUrl: 'https://extensions-server/extension1',
forceMute: false,
items: [item],
backupFilename: 'backup-file',
authParams,
})
} catch (e) {
error = e
}
expect(error).not.toBeNull()
})
})

View File

@@ -1,177 +0,0 @@
import { KeyParamsData } from '@standardnotes/responses'
import { DomainEventInterface, DomainEventPublisherInterface } from '@standardnotes/domain-events'
import { EmailLevel } from '@standardnotes/domain-core'
import { AxiosInstance } from 'axios'
import { Logger } from 'winston'
import { DomainEventFactoryInterface } from '../Event/DomainEventFactoryInterface'
import { ContentDecoderInterface } from '../Item/ContentDecoderInterface'
import { ItemRepositoryInterface } from '../Item/ItemRepositoryInterface'
import { ExtensionName } from './ExtensionName'
import { ExtensionsHttpServiceInterface } from './ExtensionsHttpServiceInterface'
import { SendItemsToExtensionsServerDTO } from './SendItemsToExtensionsServerDTO'
import { getBody as googleDriveBody, getSubject as googleDriveSubject } from '../Email/GoogleDriveBackupFailed'
import { getBody as dropboxBody, getSubject as dropboxSubject } from '../Email/DropboxBackupFailed'
import { getBody as oneDriveBody, getSubject as oneDriveSubject } from '../Email/OneDriveBackupFailed'
export class ExtensionsHttpService implements ExtensionsHttpServiceInterface {
constructor(
private httpClient: AxiosInstance,
private primaryItemRepository: ItemRepositoryInterface,
private contentDecoder: ContentDecoderInterface,
private domainEventPublisher: DomainEventPublisherInterface,
private domainEventFactory: DomainEventFactoryInterface,
private logger: Logger,
) {}
async triggerCloudBackupOnExtensionsServer(dto: {
cloudProvider: 'DROPBOX' | 'GOOGLE_DRIVE' | 'ONE_DRIVE'
extensionsServerUrl: string
backupFilename: string
authParams: KeyParamsData
forceMute: boolean
userUuid: string
}): Promise<void> {
let sent = false
try {
const payload: Record<string, unknown> = {
backup_filename: dto.backupFilename,
auth_params: dto.authParams,
silent: dto.forceMute,
user_uuid: dto.userUuid,
}
const response = await this.httpClient.request({
method: 'POST',
url: dto.extensionsServerUrl,
headers: {
'Content-Type': 'application/json',
},
data: payload,
validateStatus:
/* istanbul ignore next */
(status: number) => status >= 200 && status < 500,
})
sent = response.status >= 200 && response.status < 300
} catch (error) {
this.logger.error(`[${dto.userUuid}] Failed to send a request to extensions server: ${(error as Error).message}`)
}
if (!sent && !dto.forceMute) {
await this.domainEventPublisher.publish(
this.createCloudBackupFailedEventBasedOnProvider(dto.cloudProvider, dto.authParams.identifier as string),
)
}
}
async sendItemsToExtensionsServer(dto: SendItemsToExtensionsServerDTO): Promise<void> {
let sent = false
try {
const payload: Record<string, unknown> = {
backup_filename: dto.backupFilename,
auth_params: dto.authParams,
silent: dto.forceMute,
user_uuid: dto.userUuid,
}
if (dto.items !== undefined) {
payload.items = dto.items
}
const response = await this.httpClient.request({
method: 'POST',
url: dto.extensionsServerUrl,
headers: {
'Content-Type': 'application/json',
},
data: payload,
validateStatus:
/* istanbul ignore next */
(status: number) => status >= 200 && status < 500,
})
sent = response.status >= 200 && response.status < 300
} catch (error) {
this.logger.error(`[${dto.userUuid}] Failed to send a request to extensions server: ${(error as Error).message}`)
}
if (!sent && !dto.forceMute) {
await this.domainEventPublisher.publish(
await this.getBackupFailedEvent(dto.extensionId, dto.userUuid, dto.authParams.identifier as string),
)
}
}
private createCloudBackupFailedEventBasedOnProvider(
cloudProvider: 'DROPBOX' | 'GOOGLE_DRIVE' | 'ONE_DRIVE',
email: string,
): DomainEventInterface {
switch (cloudProvider) {
case 'DROPBOX':
return this.domainEventFactory.createEmailRequestedEvent({
userEmail: email,
level: EmailLevel.LEVELS.FailedCloudBackup,
body: dropboxBody(),
messageIdentifier: 'FAILED_DROPBOX_BACKUP',
subject: dropboxSubject(),
})
case 'GOOGLE_DRIVE':
return this.domainEventFactory.createEmailRequestedEvent({
userEmail: email,
level: EmailLevel.LEVELS.FailedCloudBackup,
body: googleDriveBody(),
messageIdentifier: 'FAILED_GOOGLE_DRIVE_BACKUP',
subject: googleDriveSubject(),
})
case 'ONE_DRIVE':
return this.domainEventFactory.createEmailRequestedEvent({
userEmail: email,
level: EmailLevel.LEVELS.FailedCloudBackup,
body: oneDriveBody(),
messageIdentifier: 'FAILED_ONE_DRIVE_BACKUP',
subject: oneDriveSubject(),
})
}
}
private async getBackupFailedEvent(
extensionId: string,
userUuid: string,
email: string,
): Promise<DomainEventInterface> {
const extension = await this.primaryItemRepository.findByUuidAndUserUuid(extensionId, userUuid)
if (extension === null || !extension.props.content) {
throw Error(`Could not find extensions with id ${extensionId}`)
}
const content = this.contentDecoder.decode(extension.props.content)
switch (this.getExtensionName(content)) {
case ExtensionName.Dropbox:
return this.createCloudBackupFailedEventBasedOnProvider('DROPBOX', email)
case ExtensionName.GoogleDrive:
return this.createCloudBackupFailedEventBasedOnProvider('GOOGLE_DRIVE', email)
case ExtensionName.OneDrive:
return this.createCloudBackupFailedEventBasedOnProvider('ONE_DRIVE', email)
}
}
private getExtensionName(content: Record<string, unknown>): ExtensionName {
if ('name' in content) {
return <ExtensionName>content.name
}
const url = 'url' in content ? <string>content.url : undefined
if (url) {
if (url.indexOf('dbt') !== -1) {
return ExtensionName.Dropbox
} else if (url.indexOf('gdrive') !== -1) {
return ExtensionName.GoogleDrive
} else if (url.indexOf('onedrive') !== -1) {
return ExtensionName.OneDrive
}
}
throw Error('Could not deduce extension name from extension content')
}
}

View File

@@ -1,15 +0,0 @@
import { KeyParamsData } from '@standardnotes/responses'
import { SendItemsToExtensionsServerDTO } from './SendItemsToExtensionsServerDTO'
export interface ExtensionsHttpServiceInterface {
triggerCloudBackupOnExtensionsServer(dto: {
cloudProvider: 'DROPBOX' | 'GOOGLE_DRIVE' | 'ONE_DRIVE'
extensionsServerUrl: string
backupFilename: string
authParams: KeyParamsData
forceMute: boolean
userUuid: string
muteEmailsSettingUuid: string
}): Promise<void>
sendItemsToExtensionsServer(dto: SendItemsToExtensionsServerDTO): Promise<void>
}

View File

@@ -1,13 +0,0 @@
import { KeyParamsData } from '@standardnotes/responses'
import { Item } from '../Item/Item'
export type SendItemsToExtensionsServerDTO = {
extensionsServerUrl: string
extensionId: string
backupFilename: string
authParams: KeyParamsData
forceMute: boolean
userUuid: string
items?: Array<Item>
}

View File

@@ -4,4 +4,4 @@ set -euo pipefail
sh supervisor/wait-for.sh $DB_HOST $DB_PORT
sh supervisor/wait-for.sh $REDIS_HOST $REDIS_PORT
node docker/entrypoint-server.js
exec node docker/entrypoint-server.js

View File

@@ -3,4 +3,4 @@
set -euo pipefail
sh supervisor/wait-for.sh localhost $SYNCING_SERVER_PORT
node docker/entrypoint-worker.js
exec node docker/entrypoint-worker.js

View File

@@ -3,6 +3,38 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.19.2](https://github.com/standardnotes/server/compare/@standardnotes/websockets-server@1.19.1...@standardnotes/websockets-server@1.19.2) (2023-11-13)
**Note:** Version bump only for package @standardnotes/websockets-server
## [1.19.1](https://github.com/standardnotes/server/compare/@standardnotes/websockets-server@1.19.0...@standardnotes/websockets-server@1.19.1) (2023-11-13)
**Note:** Version bump only for package @standardnotes/websockets-server
# [1.19.0](https://github.com/standardnotes/server/compare/@standardnotes/websockets-server@1.18.0...@standardnotes/websockets-server@1.19.0) (2023-11-10)
### Features
* add keep-alive connections to subservices ([#924](https://github.com/standardnotes/server/issues/924)) ([daad76d](https://github.com/standardnotes/server/commit/daad76d0ddae34c59dce45eedc4a055c4a11456d))
# [1.18.0](https://github.com/standardnotes/server/compare/@standardnotes/websockets-server@1.17.8...@standardnotes/websockets-server@1.18.0) (2023-11-10)
### Features
* add graceful shutdown procedures upon SIGTERM ([#923](https://github.com/standardnotes/server/issues/923)) ([c24353c](https://github.com/standardnotes/server/commit/c24353cc24ebf4b40ff9a2cec8e37cfdef109e37))
## [1.17.8](https://github.com/standardnotes/server/compare/@standardnotes/websockets-server@1.17.7...@standardnotes/websockets-server@1.17.8) (2023-11-09)
### Bug Fixes
* remove unused axios dep in subservices ([45d4920](https://github.com/standardnotes/server/commit/45d4920e0fc2848a28ce888d139201e68c4b416f))
## [1.17.7](https://github.com/standardnotes/server/compare/@standardnotes/websockets-server@1.17.6...@standardnotes/websockets-server@1.17.7) (2023-11-09)
### Bug Fixes
* reduce websockets api communication data ([#919](https://github.com/standardnotes/server/issues/919)) ([c4ae12d](https://github.com/standardnotes/server/commit/c4ae12d53fc166879f90a4c5dbad1ab1cb4797e2))
## [1.17.6](https://github.com/standardnotes/server/compare/@standardnotes/websockets-server@1.17.5...@standardnotes/websockets-server@1.17.6) (2023-11-07)
**Note:** Version bump only for package @standardnotes/websockets-server

View File

@@ -44,9 +44,18 @@ void container.load().then((container) => {
})
})
const serverInstance = server.build()
const serverInstance = server.build().listen(env.get('PORT'))
serverInstance.listen(env.get('PORT'))
const keepAliveTimeout = env.get('KEEP_ALIVE_TIMEOUT', true) ? +env.get('KEEP_ALIVE_TIMEOUT', true) : 5000
serverInstance.keepAliveTimeout = keepAliveTimeout
process.on('SIGTERM', () => {
logger.info('SIGTERM signal received: closing HTTP server')
serverInstance.close(() => {
logger.info('HTTP server closed')
})
})
logger.info(`Server started on port ${process.env.PORT}`)
})

View File

@@ -18,5 +18,11 @@ void container.load().then((container) => {
const subscriber = container.get<DomainEventSubscriberInterface>(TYPES.DomainEventSubscriber)
process.on('SIGTERM', () => {
logger.info('SIGTERM received. Stopping worker...')
subscriber.stop()
logger.info('Worker stopped.')
})
subscriber.start()
})

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