mirror of
https://github.com/standardnotes/server
synced 2026-02-11 08:01:12 -05:00
Compare commits
19 Commits
@standardn
...
@standardn
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fddd17e531 | ||
|
|
f99750169f | ||
|
|
daad76d0dd | ||
|
|
b3542e2fab | ||
|
|
a9b1543e20 | ||
|
|
e6d8e5c5f2 | ||
|
|
c24353cc24 | ||
|
|
4855e1d5f5 | ||
|
|
5d3fb9a537 | ||
|
|
b55d80a7cd | ||
|
|
16f92bdc99 | ||
|
|
4c5738416a | ||
|
|
45d4920e0f | ||
|
|
94e738532a | ||
|
|
c4ae12d53f | ||
|
|
4ff78452f9 | ||
|
|
9465f2ecd8 | ||
|
|
93c2f1f12f | ||
|
|
ca8a3fc77d |
27
.pnp.cjs
generated
27
.pnp.cjs
generated
@@ -6390,7 +6390,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"],\
|
||||
@@ -6961,7 +6962,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 +7044,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"],\
|
||||
@@ -8196,6 +8195,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 +8505,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 +10913,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]\
|
||||
],\
|
||||
|
||||
BIN
.yarn/cache/agentkeepalive-npm-4.5.0-f237b580b2-dd210ba2a2.zip
vendored
Normal file
BIN
.yarn/cache/agentkeepalive-npm-4.5.0-f237b580b2-dd210ba2a2.zip
vendored
Normal file
Binary file not shown.
Binary file not shown.
BIN
.yarn/cache/axios-npm-1.6.1-ffaff76449-fb091af3ad.zip
vendored
Normal file
BIN
.yarn/cache/axios-npm-1.6.1-ffaff76449-fb091af3ad.zip
vendored
Normal file
Binary file not shown.
@@ -3,6 +3,12 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [2.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
|
||||
|
||||
@@ -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()
|
||||
})
|
||||
|
||||
@@ -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
|
||||
;;
|
||||
|
||||
* )
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/analytics",
|
||||
"version": "2.32.6",
|
||||
"version": "2.33.0",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,46 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [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
|
||||
|
||||
@@ -90,8 +90,8 @@ 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.url}][SNJS: ${request.headers['x-snjs-version']}] Error thrown: ${error.stack}`)
|
||||
|
||||
response.status(500).send({
|
||||
error: {
|
||||
@@ -102,9 +102,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}`)
|
||||
})
|
||||
|
||||
@@ -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
|
||||
;;
|
||||
|
||||
* )
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/api-gateway",
|
||||
"version": "1.81.11",
|
||||
"version": "1.83.0",
|
||||
"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",
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -3,6 +3,30 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [1.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
|
||||
|
||||
* add logs about sending websocket events to clients ([9465f2e](https://github.com/standardnotes/server/commit/9465f2ecd8e8f0bf3ebeeb3976227b1b105aded0))
|
||||
|
||||
## [1.167.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.167.0...@standardnotes/auth-server@1.167.1) (2023-11-08)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** path to delete accounts script ([ca8a3fc](https://github.com/standardnotes/server/commit/ca8a3fc77d91410f0dee8c3ddef29c09947c9cf5))
|
||||
|
||||
# [1.167.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.166.0...@standardnotes/auth-server@1.167.0) (2023-11-08)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -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}`)
|
||||
})
|
||||
|
||||
@@ -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()
|
||||
})
|
||||
|
||||
@@ -4,7 +4,7 @@ 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/delete-accounts.js')))
|
||||
const index = require(path.normalize(path.resolve(__dirname, '../dist/bin/delete_accounts.js')))
|
||||
|
||||
Object.defineProperty(exports, '__esModule', { value: true })
|
||||
|
||||
|
||||
@@ -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
|
||||
;;
|
||||
|
||||
* )
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/auth-server",
|
||||
"version": "1.167.0",
|
||||
"version": "1.169.0",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -5,12 +5,14 @@ import { DomainEventFactoryInterface } from '../../Domain/Event/DomainEventFacto
|
||||
import { User } from '../../Domain/User/User'
|
||||
import { ClientServiceInterface } from '../../Domain/Client/ClientServiceInterface'
|
||||
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
|
||||
import { Logger } from 'winston'
|
||||
|
||||
@injectable()
|
||||
export class WebSocketsClientService implements ClientServiceInterface {
|
||||
constructor(
|
||||
@inject(TYPES.Auth_DomainEventFactory) private domainEventFactory: DomainEventFactoryInterface,
|
||||
@inject(TYPES.Auth_DomainEventPublisher) private domainEventPublisher: DomainEventPublisherInterface,
|
||||
@inject(TYPES.Auth_Logger) private logger: Logger,
|
||||
) {}
|
||||
|
||||
async sendUserRolesChangedEvent(user: User): Promise<void> {
|
||||
@@ -20,6 +22,8 @@ export class WebSocketsClientService implements ClientServiceInterface {
|
||||
(await user.roles).map((role) => role.name),
|
||||
)
|
||||
|
||||
this.logger.info(`[WebSockets] Requesting message ${event.type} to user ${user.uuid}`)
|
||||
|
||||
await this.domainEventPublisher.publish(
|
||||
this.domainEventFactory.createWebSocketMessageRequestedEvent({
|
||||
userUuid: user.uuid,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -3,6 +3,12 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [1.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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/domain-events-infra",
|
||||
"version": "1.20.4",
|
||||
"version": "1.21.0",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -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')
|
||||
})
|
||||
})
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
})
|
||||
})
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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`)
|
||||
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -3,6 +3,12 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [2.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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/domain-events",
|
||||
"version": "2.133.1",
|
||||
"version": "2.134.0",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export interface DomainEventSubscriberInterface {
|
||||
start(): void
|
||||
stop(): void
|
||||
}
|
||||
|
||||
@@ -3,6 +3,12 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [1.14.0](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.13.19...@standardnotes/event-store@1.14.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.13.19](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.13.18...@standardnotes/event-store@1.13.19) (2023-11-07)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/event-store
|
||||
|
||||
@@ -6,7 +6,7 @@ COMMAND=$1 && shift 1
|
||||
case "$COMMAND" in
|
||||
'start-worker' )
|
||||
echo "Starting Worker..."
|
||||
node docker/entrypoint-worker.js
|
||||
exec node docker/entrypoint-worker.js
|
||||
;;
|
||||
|
||||
* )
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/event-store",
|
||||
"version": "1.13.19",
|
||||
"version": "1.14.0",
|
||||
"description": "Event Store Service",
|
||||
"private": true,
|
||||
"main": "dist/src/index.js",
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [1.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
|
||||
|
||||
@@ -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}`)
|
||||
})
|
||||
|
||||
@@ -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()
|
||||
})
|
||||
|
||||
@@ -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
|
||||
;;
|
||||
|
||||
* )
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/files-server",
|
||||
"version": "1.32.5",
|
||||
"version": "1.34.0",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -3,6 +3,46 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [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
|
||||
|
||||
## [1.18.27](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.18.26...@standardnotes/home-server@1.18.27) (2023-11-08)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.18.26](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.18.25...@standardnotes/home-server@1.18.26) (2023-11-08)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/home-server",
|
||||
"version": "1.18.26",
|
||||
"version": "1.20.0",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -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)}.`)
|
||||
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [1.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
|
||||
|
||||
@@ -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}`)
|
||||
})
|
||||
|
||||
@@ -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()
|
||||
})
|
||||
|
||||
@@ -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
|
||||
;;
|
||||
|
||||
* )
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/revisions-server",
|
||||
"version": "1.47.5",
|
||||
"version": "1.49.0",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -3,6 +3,12 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [1.27.0](https://github.com/standardnotes/server/compare/@standardnotes/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
|
||||
|
||||
@@ -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()
|
||||
})
|
||||
|
||||
@@ -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
|
||||
;;
|
||||
|
||||
* )
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/scheduler-server",
|
||||
"version": "1.26.6",
|
||||
"version": "1.27.0",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,30 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [1.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
|
||||
|
||||
* add logs about sending websocket events to clients ([9465f2e](https://github.com/standardnotes/syncing-server-js/commit/9465f2ecd8e8f0bf3ebeeb3976227b1b105aded0))
|
||||
|
||||
## [1.120.3](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.120.2...@standardnotes/syncing-server@1.120.3) (2023-11-07)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/syncing-server
|
||||
|
||||
@@ -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}`)
|
||||
})
|
||||
|
||||
@@ -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()
|
||||
})
|
||||
|
||||
@@ -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
|
||||
;;
|
||||
|
||||
* )
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/syncing-server",
|
||||
"version": "1.120.3",
|
||||
"version": "1.122.0",
|
||||
"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",
|
||||
|
||||
@@ -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'
|
||||
@@ -554,6 +551,7 @@ export class ContainerConfigLoader {
|
||||
new SendEventToClient(
|
||||
container.get<DomainEventFactoryInterface>(TYPES.Sync_DomainEventFactory),
|
||||
container.get<DomainEventPublisherInterface>(TYPES.Sync_DomainEventPublisher),
|
||||
container.get<Logger>(TYPES.Sync_Logger),
|
||||
),
|
||||
)
|
||||
container
|
||||
@@ -948,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)],
|
||||
|
||||
@@ -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'),
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
export enum ExtensionName {
|
||||
Dropbox = 'Dropbox',
|
||||
GoogleDrive = 'Google Drive',
|
||||
OneDrive = 'OneDrive',
|
||||
}
|
||||
@@ -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()
|
||||
})
|
||||
})
|
||||
@@ -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')
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
}
|
||||
@@ -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>
|
||||
}
|
||||
@@ -5,14 +5,19 @@ import {
|
||||
} from '@standardnotes/domain-events'
|
||||
import { DomainEventFactoryInterface } from '../../../Event/DomainEventFactoryInterface'
|
||||
import { SendEventToClient } from './SendEventToClient'
|
||||
import { Logger } from 'winston'
|
||||
|
||||
describe('SendEventToClient', () => {
|
||||
let domainEventFactory: DomainEventFactoryInterface
|
||||
let domainEventPublisher: DomainEventPublisherInterface
|
||||
let logger: Logger
|
||||
|
||||
const createUseCase = () => new SendEventToClient(domainEventFactory, domainEventPublisher)
|
||||
const createUseCase = () => new SendEventToClient(domainEventFactory, domainEventPublisher, logger)
|
||||
|
||||
beforeEach(() => {
|
||||
logger = {} as jest.Mocked<Logger>
|
||||
logger.info = jest.fn()
|
||||
|
||||
domainEventFactory = {} as jest.Mocked<DomainEventFactoryInterface>
|
||||
domainEventFactory.createWebSocketMessageRequestedEvent = jest
|
||||
.fn()
|
||||
|
||||
@@ -3,11 +3,13 @@ import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
|
||||
|
||||
import { SendEventToClientDTO } from './SendEventToClientDTO'
|
||||
import { DomainEventFactoryInterface } from '../../../Event/DomainEventFactoryInterface'
|
||||
import { Logger } from 'winston'
|
||||
|
||||
export class SendEventToClient implements UseCaseInterface<void> {
|
||||
constructor(
|
||||
private domainEventFactory: DomainEventFactoryInterface,
|
||||
private domainEventPublisher: DomainEventPublisherInterface,
|
||||
private logger: Logger,
|
||||
) {}
|
||||
|
||||
async execute(dto: SendEventToClientDTO): Promise<Result<void>> {
|
||||
@@ -17,6 +19,8 @@ export class SendEventToClient implements UseCaseInterface<void> {
|
||||
}
|
||||
const userUuid = userUuidOrError.getValue()
|
||||
|
||||
this.logger.info(`[WebSockets] Requesting message ${dto.event.type} to user ${dto.userUuid}`)
|
||||
|
||||
const event = this.domainEventFactory.createWebSocketMessageRequestedEvent({
|
||||
userUuid: userUuid.value,
|
||||
message: JSON.stringify(dto.event),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -3,6 +3,30 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [1.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
|
||||
|
||||
@@ -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}`)
|
||||
})
|
||||
|
||||
@@ -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()
|
||||
})
|
||||
|
||||
@@ -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
|
||||
;;
|
||||
|
||||
* )
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/websockets-server",
|
||||
"version": "1.17.6",
|
||||
"version": "1.19.0",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
@@ -31,7 +31,6 @@
|
||||
"@standardnotes/domain-events-infra": "workspace:^",
|
||||
"@standardnotes/responses": "^1.13.27",
|
||||
"@standardnotes/security": "workspace:^",
|
||||
"axios": "^1.1.3",
|
||||
"cors": "2.8.5",
|
||||
"dotenv": "^16.0.1",
|
||||
"express": "^4.18.2",
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
import * as winston from 'winston'
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const axios = require('axios')
|
||||
import { AxiosInstance } from 'axios'
|
||||
import Redis from 'ioredis'
|
||||
import { SQSClient, SQSClientConfig } from '@aws-sdk/client-sqs'
|
||||
import { ApiGatewayManagementApiClient } from '@aws-sdk/client-apigatewaymanagementapi'
|
||||
@@ -123,7 +120,6 @@ export class ContainerConfigLoader {
|
||||
.to(WebSocketMessageRequestedEventHandler)
|
||||
|
||||
// Services
|
||||
container.bind<AxiosInstance>(TYPES.HTTPClient).toConstantValue(axios.create())
|
||||
container
|
||||
.bind<TokenDecoderInterface<CrossServiceTokenData>>(TYPES.CrossServiceTokenDecoder)
|
||||
.toConstantValue(new TokenDecoder<CrossServiceTokenData>(container.get(TYPES.AUTH_JWT_SECRET)))
|
||||
|
||||
@@ -29,7 +29,6 @@ const TYPES = {
|
||||
WebSocketConnectionTokenEncoder: Symbol.for('WebSocketConnectionTokenEncoder'),
|
||||
DomainEventSubscriber: Symbol.for('DomainEventSubscriber'),
|
||||
DomainEventMessageHandler: Symbol.for('DomainEventMessageHandler'),
|
||||
HTTPClient: Symbol.for('HTTPClient'),
|
||||
WebSocketsClientMessenger: Symbol.for('WebSocketsClientMessenger'),
|
||||
}
|
||||
|
||||
|
||||
@@ -16,11 +16,23 @@ describe('AddWebSocketsConnection', () => {
|
||||
|
||||
logger = {} as jest.Mocked<Logger>
|
||||
logger.debug = jest.fn()
|
||||
logger.error = jest.fn()
|
||||
})
|
||||
|
||||
it('should save a web sockets connection for a user for further communication', async () => {
|
||||
await createUseCase().execute({ userUuid: '1-2-3', connectionId: '2-3-4' })
|
||||
const result = await createUseCase().execute({ userUuid: '1-2-3', connectionId: '2-3-4' })
|
||||
|
||||
expect(webSocketsConnectionRepository.saveConnection).toHaveBeenCalledWith('1-2-3', '2-3-4')
|
||||
expect(result.isFailed()).toBe(false)
|
||||
})
|
||||
|
||||
it('should return a failure if the web sockets connection could not be saved', async () => {
|
||||
webSocketsConnectionRepository.saveConnection = jest
|
||||
.fn()
|
||||
.mockRejectedValueOnce(new Error('Could not save connection'))
|
||||
|
||||
const result = await createUseCase().execute({ userUuid: '1-2-3', connectionId: '2-3-4' })
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,26 +1,32 @@
|
||||
import { inject, injectable } from 'inversify'
|
||||
import { Logger } from 'winston'
|
||||
import { Result, UseCaseInterface } from '@standardnotes/domain-core'
|
||||
|
||||
import TYPES from '../../../Bootstrap/Types'
|
||||
import { WebSocketsConnectionRepositoryInterface } from '../../WebSockets/WebSocketsConnectionRepositoryInterface'
|
||||
import { UseCaseInterface } from '../UseCaseInterface'
|
||||
import { AddWebSocketsConnectionDTO } from './AddWebSocketsConnectionDTO'
|
||||
import { AddWebSocketsConnectionResponse } from './AddWebSocketsConnectionResponse'
|
||||
|
||||
@injectable()
|
||||
export class AddWebSocketsConnection implements UseCaseInterface {
|
||||
export class AddWebSocketsConnection implements UseCaseInterface<void> {
|
||||
constructor(
|
||||
@inject(TYPES.WebSocketsConnectionRepository)
|
||||
private webSocketsConnectionRepository: WebSocketsConnectionRepositoryInterface,
|
||||
@inject(TYPES.Logger) private logger: Logger,
|
||||
) {}
|
||||
|
||||
async execute(dto: AddWebSocketsConnectionDTO): Promise<AddWebSocketsConnectionResponse> {
|
||||
this.logger.debug(`Persisting connection ${dto.connectionId} for user ${dto.userUuid}`)
|
||||
async execute(dto: AddWebSocketsConnectionDTO): Promise<Result<void>> {
|
||||
try {
|
||||
this.logger.debug(`Persisting connection ${dto.connectionId} for user ${dto.userUuid}`)
|
||||
|
||||
await this.webSocketsConnectionRepository.saveConnection(dto.userUuid, dto.connectionId)
|
||||
await this.webSocketsConnectionRepository.saveConnection(dto.userUuid, dto.connectionId)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
return Result.ok()
|
||||
} catch (error) {
|
||||
this.logger.error(
|
||||
`Error persisting connection ${dto.connectionId} for user ${dto.userUuid}: ${(error as Error).message}`,
|
||||
)
|
||||
|
||||
return Result.fail((error as Error).message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
export type AddWebSocketsConnectionResponse = {
|
||||
success: boolean
|
||||
}
|
||||
@@ -16,11 +16,23 @@ describe('RemoveWebSocketsConnection', () => {
|
||||
|
||||
logger = {} as jest.Mocked<Logger>
|
||||
logger.debug = jest.fn()
|
||||
logger.error = jest.fn()
|
||||
})
|
||||
|
||||
it('should remove a web sockets connection', async () => {
|
||||
await createUseCase().execute({ connectionId: '2-3-4' })
|
||||
const result = await createUseCase().execute({ connectionId: '2-3-4' })
|
||||
|
||||
expect(webSocketsConnectionRepository.removeConnection).toHaveBeenCalledWith('2-3-4')
|
||||
expect(result.isFailed()).toBe(false)
|
||||
})
|
||||
|
||||
it('should return a failure if the web sockets connection could not be removed', async () => {
|
||||
webSocketsConnectionRepository.removeConnection = jest
|
||||
.fn()
|
||||
.mockRejectedValueOnce(new Error('Could not remove connection'))
|
||||
|
||||
const result = await createUseCase().execute({ connectionId: '2-3-4' })
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,26 +1,30 @@
|
||||
import { inject, injectable } from 'inversify'
|
||||
import { Logger } from 'winston'
|
||||
import { Result, UseCaseInterface } from '@standardnotes/domain-core'
|
||||
|
||||
import TYPES from '../../../Bootstrap/Types'
|
||||
import { WebSocketsConnectionRepositoryInterface } from '../../WebSockets/WebSocketsConnectionRepositoryInterface'
|
||||
import { UseCaseInterface } from '../UseCaseInterface'
|
||||
import { RemoveWebSocketsConnectionDTO } from './RemoveWebSocketsConnectionDTO'
|
||||
import { RemoveWebSocketsConnectionResponse } from './RemoveWebSocketsConnectionResponse'
|
||||
|
||||
@injectable()
|
||||
export class RemoveWebSocketsConnection implements UseCaseInterface {
|
||||
export class RemoveWebSocketsConnection implements UseCaseInterface<void> {
|
||||
constructor(
|
||||
@inject(TYPES.WebSocketsConnectionRepository)
|
||||
private webSocketsConnectionRepository: WebSocketsConnectionRepositoryInterface,
|
||||
@inject(TYPES.Logger) private logger: Logger,
|
||||
) {}
|
||||
|
||||
async execute(dto: RemoveWebSocketsConnectionDTO): Promise<RemoveWebSocketsConnectionResponse> {
|
||||
this.logger.debug(`Removing connection ${dto.connectionId}`)
|
||||
async execute(dto: RemoveWebSocketsConnectionDTO): Promise<Result<void>> {
|
||||
try {
|
||||
this.logger.debug(`Removing connection ${dto.connectionId}`)
|
||||
|
||||
await this.webSocketsConnectionRepository.removeConnection(dto.connectionId)
|
||||
await this.webSocketsConnectionRepository.removeConnection(dto.connectionId)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
return Result.ok()
|
||||
} catch (error) {
|
||||
this.logger.error(`Error removing connection ${dto.connectionId}: ${(error as Error).message}`)
|
||||
|
||||
return Result.fail((error as Error).message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
export type RemoveWebSocketsConnectionResponse = {
|
||||
success: boolean
|
||||
}
|
||||
@@ -36,21 +36,27 @@ export class AnnotatedWebSocketsController extends BaseHttpController {
|
||||
async storeWebSocketsConnection(
|
||||
request: Request,
|
||||
response: Response,
|
||||
): Promise<results.JsonResult | results.BadRequestErrorMessageResult> {
|
||||
await this.addWebSocketsConnection.execute({
|
||||
): Promise<results.OkResult | results.BadRequestResult> {
|
||||
const result = await this.addWebSocketsConnection.execute({
|
||||
userUuid: response.locals.user.uuid,
|
||||
connectionId: request.params.connectionId,
|
||||
})
|
||||
|
||||
return this.json({ success: true })
|
||||
if (result.isFailed()) {
|
||||
return this.badRequest()
|
||||
}
|
||||
|
||||
return this.ok()
|
||||
}
|
||||
|
||||
@httpDelete('/connections/:connectionId')
|
||||
async deleteWebSocketsConnection(
|
||||
request: Request,
|
||||
): Promise<results.JsonResult | results.BadRequestErrorMessageResult> {
|
||||
await this.removeWebSocketsConnection.execute({ connectionId: request.params.connectionId })
|
||||
async deleteWebSocketsConnection(request: Request): Promise<results.OkResult | results.BadRequestResult> {
|
||||
const result = await this.removeWebSocketsConnection.execute({ connectionId: request.params.connectionId })
|
||||
|
||||
return this.json({ success: true })
|
||||
if (result.isFailed()) {
|
||||
return this.badRequest()
|
||||
}
|
||||
|
||||
return this.ok()
|
||||
}
|
||||
}
|
||||
|
||||
22
yarn.lock
22
yarn.lock
@@ -5249,7 +5249,8 @@ __metadata:
|
||||
"@types/prettyjson": "npm:^0.0.30"
|
||||
"@typescript-eslint/eslint-plugin": "npm:^6.5.0"
|
||||
"@typescript-eslint/parser": "npm:^6.5.0"
|
||||
axios: "npm:^1.1.3"
|
||||
agentkeepalive: "npm:^4.5.0"
|
||||
axios: "npm:^1.6.1"
|
||||
cors: "npm:2.8.5"
|
||||
dotenv: "npm:^16.0.1"
|
||||
eslint: "npm:^8.39.0"
|
||||
@@ -5799,7 +5800,6 @@ __metadata:
|
||||
"@types/uuid": "npm:^9.0.3"
|
||||
"@typescript-eslint/eslint-plugin": "npm:^6.5.0"
|
||||
"@typescript-eslint/parser": "npm:^6.5.0"
|
||||
axios: "npm:^1.1.3"
|
||||
cors: "npm:2.8.5"
|
||||
dotenv: "npm:^16.0.1"
|
||||
eslint: "npm:^8.39.0"
|
||||
@@ -5877,7 +5877,6 @@ __metadata:
|
||||
"@types/jest": "npm:^29.5.1"
|
||||
"@typescript-eslint/eslint-plugin": "npm:^6.5.0"
|
||||
"@typescript-eslint/parser": "npm:^6.5.0"
|
||||
axios: "npm:^1.1.3"
|
||||
cors: "npm:2.8.5"
|
||||
dotenv: "npm:^16.0.1"
|
||||
eslint: "npm:^8.39.0"
|
||||
@@ -6791,6 +6790,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"agentkeepalive@npm:^4.5.0":
|
||||
version: 4.5.0
|
||||
resolution: "agentkeepalive@npm:4.5.0"
|
||||
dependencies:
|
||||
humanize-ms: "npm:^1.2.1"
|
||||
checksum: dd210ba2a2e2482028f027b1156789744aadbfd773a6c9dd8e4e8001930d5af82382abe19a69240307b1d8003222ce6b0542935038313434b900e351914fc15f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"aggregate-error@npm:^3.0.0":
|
||||
version: 3.1.0
|
||||
resolution: "aggregate-error@npm:3.1.0"
|
||||
@@ -7047,14 +7055,14 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"axios@npm:^1.1.3":
|
||||
version: 1.4.0
|
||||
resolution: "axios@npm:1.4.0"
|
||||
"axios@npm:^1.6.1":
|
||||
version: 1.6.1
|
||||
resolution: "axios@npm:1.6.1"
|
||||
dependencies:
|
||||
follow-redirects: "npm:^1.15.0"
|
||||
form-data: "npm:^4.0.0"
|
||||
proxy-from-env: "npm:^1.1.0"
|
||||
checksum: b987e4259e5cfc93e95ee306c267a44380bbc045789a91b716e8434a75e22987344605eb4e133482fe285dd3a2e0b7e791ba26999965f04a5ecdde25f56930cb
|
||||
checksum: fb091af3ad47d70fdcba5e71654b9e3c56947d93d8b2375dd0b4db63de9982adab6235aebc514488aa289c7faf103b09e8911280e6f6b1112d1604fe5f111f71
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
||||
Reference in New Issue
Block a user