Compare commits

..

29 Commits

Author SHA1 Message Date
standardci 4418c38878 chore(release): publish new version
- @standardnotes/api-gateway@1.85.0
 - @standardnotes/auth-server@1.171.0
 - @standardnotes/home-server@1.21.2
2023-11-16 11:31:52 +00:00
Karol Sójko 6391a01b57 feat: add debug logs for grpc communication 2023-11-16 12:10:51 +01:00
standardci 9dbcec198d chore(release): publish new version
- @standardnotes/api-gateway@1.84.1
 - @standardnotes/home-server@1.21.1
2023-11-16 10:25:54 +00:00
Karol Sójko 78fbeb595f fix(api-gateway): bindings 2023-11-16 11:04:44 +01:00
standardci d894a87e87 chore(release): publish new version
- @standardnotes/api-gateway@1.84.0
 - @standardnotes/auth-server@1.170.0
 - @standardnotes/files-server@1.35.0
 - @standardnotes/grpc@1.1.0
 - @standardnotes/home-server@1.21.0
 - @standardnotes/revisions-server@1.50.0
 - @standardnotes/syncing-server@1.123.0
 - @standardnotes/websockets-server@1.20.0
2023-11-16 09:31:37 +00:00
Karol Sójko 4f62cac213 feat: add grpc sessions validation server (#928)
* feat: add grpc sessions validation server

* feat: add client implementation on api gateway

* fix: response codes

* fix: errored response

* feat: add configuring grpc as optional service proxy

* fix env vars

* fix linter issue
2023-11-16 10:10:42 +01:00
standardci ce081274da chore(release): publish new version
- @standardnotes/api-gateway@1.83.5
 - @standardnotes/home-server@1.20.5
2023-11-14 14:18:46 +00:00
Karol Sójko fd997f4849 fix(api-gateway): remove unused imports 2023-11-14 14:57:41 +01:00
Karol Sójko 3ddd671c47 fix(api-gateway): remove the verify body function 2023-11-14 14:55:55 +01:00
standardci c19de13cac chore(release): publish new version
- @standardnotes/api-gateway@1.83.4
 - @standardnotes/home-server@1.20.4
2023-11-14 13:30:24 +00:00
Karol Sójko f65809ef30 fix(api-gateway): checking for buffer length 2023-11-14 14:09:10 +01:00
Karol Sójko 2823ed8612 fix(api-gateway): buffer encoding 2023-11-14 14:07:55 +01:00
Karol Sójko 420bf9ec54 fix(api-gateway): add verification if json is valid on request 2023-11-14 14:04:58 +01:00
standardci 5f67e5efda chore(release): publish new version
- @standardnotes/analytics@2.33.2
 - @standardnotes/api-gateway@1.83.3
 - @standardnotes/auth-server@1.169.2
 - @standardnotes/domain-events-infra@1.21.2
 - @standardnotes/domain-events@2.134.2
 - @standardnotes/files-server@1.34.2
 - @standardnotes/home-server@1.20.3
 - @standardnotes/revisions-server@1.49.2
 - @standardnotes/scheduler-server@1.27.2
 - @standardnotes/syncing-server@1.122.2
 - @standardnotes/websockets-server@1.19.2
2023-11-13 10:51:57 +00:00
Karol Sójko daed1a77a0 fix(api-gateway): add application version to the error logs 2023-11-13 11:23:18 +01:00
Karol Sójko b39eb09d91 fix(api-gateway): add request method to the debug logs 2023-11-13 11:15:02 +01:00
Karol Sójko f6ec8626e5 fix(domain-events): remove unused event 2023-11-13 11:02:11 +01:00
standardci 97b12f2131 chore(release): publish new version
- @standardnotes/analytics@2.33.1
 - @standardnotes/api-gateway@1.83.2
 - @standardnotes/auth-server@1.169.1
 - @standardnotes/domain-events-infra@1.21.1
 - @standardnotes/domain-events@2.134.1
 - @standardnotes/files-server@1.34.1
 - @standardnotes/home-server@1.20.2
 - @standardnotes/revisions-server@1.49.1
 - @standardnotes/scheduler-server@1.27.1
 - @standardnotes/syncing-server@1.122.1
 - @standardnotes/websockets-server@1.19.1
2023-11-13 10:00:24 +00:00
Karol Sójko 8e4e36513a fix: remove unused even-store from code base (#925)
* fix: remove unused even-store from code base

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

* fix: defaults
2023-11-10 14:52:44 +01:00
standardci b3542e2fab chore(release): publish new version
- @standardnotes/api-gateway@1.82.1
 - @standardnotes/home-server@1.19.1
2023-11-10 12:21:41 +00:00
Karol Sójko a9b1543e20 fix(api-gateway): websockets calls logs severity 2023-11-10 12:59:42 +01:00
standardci e6d8e5c5f2 chore(release): publish new version
- @standardnotes/analytics@2.33.0
 - @standardnotes/api-gateway@1.82.0
 - @standardnotes/auth-server@1.168.0
 - @standardnotes/domain-events-infra@1.21.0
 - @standardnotes/domain-events@2.134.0
 - @standardnotes/event-store@1.14.0
 - @standardnotes/files-server@1.33.0
 - @standardnotes/home-server@1.19.0
 - @standardnotes/revisions-server@1.48.0
 - @standardnotes/scheduler-server@1.27.0
 - @standardnotes/syncing-server@1.121.0
 - @standardnotes/websockets-server@1.18.0
2023-11-10 11:46:24 +00:00
Karol Sójko c24353cc24 feat: add graceful shutdown procedures upon SIGTERM (#923) 2023-11-10 12:20:21 +01:00
148 changed files with 1989 additions and 2016 deletions
-7
View File
@@ -61,13 +61,6 @@ updates:
allow:
- dependency-type: "direct"
- package-ecosystem: "npm"
directory: "/packages/event-store"
schedule:
interval: "daily"
allow:
- dependency-type: "direct"
- package-ecosystem: "npm"
directory: "/packages/files"
schedule:
+3
View File
@@ -17,6 +17,8 @@ jobs:
name: (Self Hosting) E2E Test Suite
strategy:
fail-fast: false
matrix:
service_proxy_type: [http, grpc]
runs-on: ubuntu-latest
services:
@@ -42,6 +44,7 @@ jobs:
env:
DB_TYPE: mysql
CACHE_TYPE: redis
SERVICE_PROXY_TYPE: ${{ matrix.service_proxy_type }}
- name: Wait for server to start
run: docker/is-available.sh http://localhost:3123 $(pwd)/logs
Generated
+112 -55
View File
@@ -41,14 +41,14 @@ const RAW_RUNTIME_STATE =
"name": "@standardnotes/domain-events-infra",\
"reference": "workspace:packages/domain-events-infra"\
},\
{\
"name": "@standardnotes/event-store",\
"reference": "workspace:packages/event-store"\
},\
{\
"name": "@standardnotes/files-server",\
"reference": "workspace:packages/files"\
},\
{\
"name": "@standardnotes/grpc",\
"reference": "workspace:packages/grpc"\
},\
{\
"name": "@standardnotes/home-server",\
"reference": "workspace:packages/home-server"\
@@ -100,8 +100,8 @@ const RAW_RUNTIME_STATE =
["@standardnotes/domain-core", ["workspace:packages/domain-core"]],\
["@standardnotes/domain-events", ["workspace:packages/domain-events"]],\
["@standardnotes/domain-events-infra", ["workspace:packages/domain-events-infra"]],\
["@standardnotes/event-store", ["workspace:packages/event-store"]],\
["@standardnotes/files-server", ["workspace:packages/files"]],\
["@standardnotes/grpc", ["workspace:packages/grpc"]],\
["@standardnotes/home-server", ["workspace:packages/home-server"]],\
["@standardnotes/predicates", ["workspace:packages/predicates"]],\
["@standardnotes/revisions-server", ["workspace:packages/revisions"]],\
@@ -2674,6 +2674,15 @@ const RAW_RUNTIME_STATE =
}]\
]],\
["@grpc/grpc-js", [\
["npm:1.9.10", {\
"packageLocation": "./.yarn/cache/@grpc-grpc-js-npm-1.9.10-28317a9d2d-243cf994e6.zip/node_modules/@grpc/grpc-js/",\
"packageDependencies": [\
["@grpc/grpc-js", "npm:1.9.10"],\
["@grpc/proto-loader", "npm:0.7.10"],\
["@types/node", "npm:20.2.5"]\
],\
"linkType": "HARD"\
}],\
["npm:1.9.5", {\
"packageLocation": "./.yarn/cache/@grpc-grpc-js-npm-1.9.5-9b0cd6b5ed-5499d964d2.zip/node_modules/@grpc/grpc-js/",\
"packageDependencies": [\
@@ -3575,6 +3584,22 @@ const RAW_RUNTIME_STATE =
["tar", "npm:6.1.15"]\
],\
"linkType": "HARD"\
}],\
["npm:1.0.11", {\
"packageLocation": "./.yarn/cache/@mapbox-node-pre-gyp-npm-1.0.11-5547f15a2b-59529a2444.zip/node_modules/@mapbox/node-pre-gyp/",\
"packageDependencies": [\
["@mapbox/node-pre-gyp", "npm:1.0.11"],\
["detect-libc", "npm:2.0.1"],\
["https-proxy-agent", "npm:5.0.1"],\
["make-dir", "npm:3.1.0"],\
["node-fetch", "virtual:0f92dfe7f9dc4fd492639d4a5b7805c2b27442bf599fd4f370b22a7966ba078f5d4525e2a8e8af29369f20e1833ed084bd52be59679efaa6c1c6c10cdbcd8baa#npm:2.6.11"],\
["nopt", "npm:5.0.0"],\
["npmlog", "npm:5.0.1"],\
["rimraf", "npm:3.0.2"],\
["semver", "npm:7.5.1"],\
["tar", "npm:6.1.15"]\
],\
"linkType": "HARD"\
}]\
]],\
["@nodelib/fs.scandir", [\
@@ -6376,9 +6401,11 @@ const RAW_RUNTIME_STATE =
"packageLocation": "./packages/api-gateway/",\
"packageDependencies": [\
["@standardnotes/api-gateway", "workspace:packages/api-gateway"],\
["@grpc/grpc-js", "npm:1.9.10"],\
["@standardnotes/domain-core", "workspace:packages/domain-core"],\
["@standardnotes/domain-events", "workspace:packages/domain-events"],\
["@standardnotes/domain-events-infra", "workspace:packages/domain-events-infra"],\
["@standardnotes/grpc", "workspace:packages/grpc"],\
["@standardnotes/security", "workspace:packages/security"],\
["@standardnotes/time", "workspace:packages/time"],\
["@types/cors", "npm:2.8.13"],\
@@ -6390,7 +6417,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"],\
@@ -6423,6 +6451,7 @@ const RAW_RUNTIME_STATE =
["@aws-sdk/client-sqs", "npm:3.427.0"],\
["@cbor-extract/cbor-extract-linux-arm64", "npm:2.1.1"],\
["@cbor-extract/cbor-extract-linux-x64", "npm:2.1.1"],\
["@grpc/grpc-js", "npm:1.9.10"],\
["@simplewebauthn/server", "npm:8.1.1"],\
["@simplewebauthn/typescript-types", "npm:8.0.0"],\
["@standardnotes/api", "npm:1.26.26"],\
@@ -6431,6 +6460,7 @@ const RAW_RUNTIME_STATE =
["@standardnotes/domain-events", "workspace:packages/domain-events"],\
["@standardnotes/domain-events-infra", "workspace:packages/domain-events-infra"],\
["@standardnotes/features", "npm:1.59.7"],\
["@standardnotes/grpc", "workspace:packages/grpc"],\
["@standardnotes/predicates", "workspace:packages/predicates"],\
["@standardnotes/responses", "npm:1.13.27"],\
["@standardnotes/security", "workspace:packages/security"],\
@@ -6578,38 +6608,6 @@ const RAW_RUNTIME_STATE =
"linkType": "SOFT"\
}]\
]],\
["@standardnotes/event-store", [\
["workspace:packages/event-store", {\
"packageLocation": "./packages/event-store/",\
"packageDependencies": [\
["@standardnotes/event-store", "workspace:packages/event-store"],\
["@aws-sdk/client-sqs", "npm:3.427.0"],\
["@standardnotes/domain-core", "workspace:packages/domain-core"],\
["@standardnotes/domain-events", "workspace:packages/domain-events"],\
["@standardnotes/domain-events-infra", "workspace:packages/domain-events-infra"],\
["@standardnotes/time", "workspace:packages/time"],\
["@types/ioredis", "npm:5.0.0"],\
["@types/jest", "npm:29.5.2"],\
["@types/nodemailer", "npm:6.4.8"],\
["@typescript-eslint/eslint-plugin", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:6.5.0"],\
["@typescript-eslint/parser", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:6.5.0"],\
["dotenv", "npm:16.1.3"],\
["eslint", "npm:8.41.0"],\
["eslint-plugin-prettier", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:5.0.0"],\
["inversify", "npm:6.0.1"],\
["ioredis", "npm:5.3.2"],\
["jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.5.0"],\
["mysql2", "npm:3.3.3"],\
["prettier", "npm:3.0.3"],\
["reflect-metadata", "npm:0.1.13"],\
["ts-jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.1.0"],\
["typeorm", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:0.3.17"],\
["typescript", "patch:typescript@npm%3A5.0.4#optional!builtin<compat/typescript>::version=5.0.4&hash=b5f058"],\
["winston", "npm:3.9.0"]\
],\
"linkType": "SOFT"\
}]\
]],\
["@standardnotes/features", [\
["npm:1.59.7", {\
"packageLocation": "./.yarn/cache/@standardnotes-features-npm-1.59.7-27c3e5296e-421af62d1e.zip/node_modules/@standardnotes/features/",\
@@ -6675,6 +6673,21 @@ const RAW_RUNTIME_STATE =
"linkType": "SOFT"\
}]\
]],\
["@standardnotes/grpc", [\
["workspace:packages/grpc", {\
"packageLocation": "./packages/grpc/",\
"packageDependencies": [\
["@standardnotes/grpc", "workspace:packages/grpc"],\
["@grpc/grpc-js", "npm:1.9.10"],\
["@types/google-protobuf", "npm:3.15.10"],\
["google-protobuf", "npm:3.21.2"],\
["grpc-tools", "npm:1.12.4"],\
["grpc_tools_node_protoc_ts", "npm:5.3.3"],\
["typescript", "patch:typescript@npm%3A5.0.4#optional!builtin<compat/typescript>::version=5.0.4&hash=b5f058"]\
],\
"linkType": "SOFT"\
}]\
]],\
["@standardnotes/home-server", [\
["workspace:packages/home-server", {\
"packageLocation": "./packages/home-server/",\
@@ -7320,6 +7333,15 @@ const RAW_RUNTIME_STATE =
"linkType": "HARD"\
}]\
]],\
["@types/google-protobuf", [\
["npm:3.15.10", {\
"packageLocation": "./.yarn/cache/@types-google-protobuf-npm-3.15.10-cbaa6c3e6c-29efde966f.zip/node_modules/@types/google-protobuf/",\
"packageDependencies": [\
["@types/google-protobuf", "npm:3.15.10"]\
],\
"linkType": "HARD"\
}]\
]],\
["@types/graceful-fs", [\
["npm:4.1.6", {\
"packageLocation": "./.yarn/cache/@types-graceful-fs-npm-4.1.6-1eadcf742d-c3070ccdc9.zip/node_modules/@types/graceful-fs/",\
@@ -7475,16 +7497,6 @@ const RAW_RUNTIME_STATE =
"linkType": "HARD"\
}]\
]],\
["@types/nodemailer", [\
["npm:6.4.8", {\
"packageLocation": "./.yarn/cache/@types-nodemailer-npm-6.4.8-04975b93f9-d5afdd77ef.zip/node_modules/@types/nodemailer/",\
"packageDependencies": [\
["@types/nodemailer", "npm:6.4.8"],\
["@types/node", "npm:20.2.5"]\
],\
"linkType": "HARD"\
}]\
]],\
["@types/normalize-package-data", [\
["npm:2.4.1", {\
"packageLocation": "./.yarn/cache/@types-normalize-package-data-npm-2.4.1-c31c56ae6a-e87bccbf11.zip/node_modules/@types/normalize-package-data/",\
@@ -8194,6 +8206,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", [\
@@ -8496,11 +8516,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"]\
],\
@@ -10904,10 +10924,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]\
],\
@@ -11334,6 +11354,22 @@ const RAW_RUNTIME_STATE =
"linkType": "HARD"\
}]\
]],\
["google-protobuf", [\
["npm:3.15.8", {\
"packageLocation": "./.yarn/cache/google-protobuf-npm-3.15.8-75df975b6c-0b1ea24a55.zip/node_modules/google-protobuf/",\
"packageDependencies": [\
["google-protobuf", "npm:3.15.8"]\
],\
"linkType": "HARD"\
}],\
["npm:3.21.2", {\
"packageLocation": "./.yarn/cache/google-protobuf-npm-3.21.2-7c82de39ab-b376c2e47f.zip/node_modules/google-protobuf/",\
"packageDependencies": [\
["google-protobuf", "npm:3.21.2"]\
],\
"linkType": "HARD"\
}]\
]],\
["graceful-fs", [\
["npm:4.2.11", {\
"packageLocation": "./.yarn/cache/graceful-fs-npm-4.2.11-24bb648a68-bf152d0ed1.zip/node_modules/graceful-fs/",\
@@ -11352,6 +11388,27 @@ const RAW_RUNTIME_STATE =
"linkType": "HARD"\
}]\
]],\
["grpc-tools", [\
["npm:1.12.4", {\
"packageLocation": "./.yarn/unplugged/grpc-tools-npm-1.12.4-956df6794d/node_modules/grpc-tools/",\
"packageDependencies": [\
["grpc-tools", "npm:1.12.4"],\
["@mapbox/node-pre-gyp", "npm:1.0.11"]\
],\
"linkType": "HARD"\
}]\
]],\
["grpc_tools_node_protoc_ts", [\
["npm:5.3.3", {\
"packageLocation": "./.yarn/unplugged/grpc_tools_node_protoc_ts-npm-5.3.3-297a345c26/node_modules/grpc_tools_node_protoc_ts/",\
"packageDependencies": [\
["grpc_tools_node_protoc_ts", "npm:5.3.3"],\
["google-protobuf", "npm:3.15.8"],\
["handlebars", "npm:4.7.7"]\
],\
"linkType": "HARD"\
}]\
]],\
["handlebars", [\
["npm:4.7.7", {\
"packageLocation": "./.yarn/cache/handlebars-npm-4.7.7-a9ccfabf80-617b1e689b.zip/node_modules/handlebars/",\
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1
View File
@@ -26,6 +26,7 @@ services:
ports:
- 3123:3000
- 3125:3104
- 50051:50051
volumes:
- ./logs:/var/lib/server/logs
networks:
+5
View File
@@ -18,6 +18,10 @@ if [ -z "$AUTH_SERVER_PORT" ]; then
export AUTH_SERVER_PORT=3103
fi
if [ -z "$AUTH_SERVER_GRPC_PORT" ]; then
export AUTH_SERVER_GRPC_PORT=50051
fi
export FILES_SERVER_PORT=3104
if [ -z "$REVISIONS_SERVER_PORT" ]; then
@@ -353,6 +357,7 @@ export API_GATEWAY_VERSION=local
export API_GATEWAY_SYNCING_SERVER_JS_URL=http://localhost:$SYNCING_SERVER_PORT
export API_GATEWAY_AUTH_SERVER_URL=http://localhost:$AUTH_SERVER_PORT
export API_GATEWAY_AUTH_SERVER_GRPC_URL=0.0.0.0:$AUTH_SERVER_GRPC_PORT
export API_GATEWAY_REVISIONS_SERVER_URL=http://localhost:$REVISIONS_SERVER_PORT
if [ -z "$PUBLIC_FILES_SERVER_URL" ]; then
export PUBLIC_FILES_SERVER_URL=http://localhost:3125
+9 -1
View File
@@ -39,5 +39,13 @@
"ts-node": "^10.9.1",
"typescript": "^5.0.4"
},
"packageManager": "yarn@4.0.0-rc.51"
"packageManager": "yarn@4.0.0-rc.51",
"dependenciesMeta": {
"grpc-tools@1.12.4": {
"unplugged": true
},
"grpc_tools_node_protoc_ts@5.3.3": {
"unplugged": true
}
}
}
+14
View File
@@ -3,6 +3,20 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [2.33.2](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.33.1...@standardnotes/analytics@2.33.2) (2023-11-13)
**Note:** Version bump only for package @standardnotes/analytics
## [2.33.1](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.33.0...@standardnotes/analytics@2.33.1) (2023-11-13)
**Note:** Version bump only for package @standardnotes/analytics
# [2.33.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.32.6...@standardnotes/analytics@2.33.0) (2023-11-10)
### Features
* add graceful shutdown procedures upon SIGTERM ([#923](https://github.com/standardnotes/server/issues/923)) ([c24353c](https://github.com/standardnotes/server/commit/c24353cc24ebf4b40ff9a2cec8e37cfdef109e37))
## [2.32.6](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.32.5...@standardnotes/analytics@2.32.6) (2023-11-07)
**Note:** Version bump only for package @standardnotes/analytics
+6
View File
@@ -22,5 +22,11 @@ void container.load().then((container) => {
const subscriber = container.get<DomainEventSubscriberInterface>(TYPES.DomainEventSubscriber)
process.on('SIGTERM', () => {
logger.info('SIGTERM received. Stopping worker...')
subscriber.stop()
logger.info('Worker stopped.')
})
subscriber.start()
})
+2 -2
View File
@@ -6,12 +6,12 @@ COMMAND=$1 && shift 1
case "$COMMAND" in
'start-worker' )
echo "[Docker] Starting Worker..."
node docker/entrypoint-worker.js
exec node docker/entrypoint-worker.js
;;
'report' )
echo "[Docker] Starting Usage Report Generation..."
node docker/entrypoint-report.js
exec node docker/entrypoint-report.js
;;
* )
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/analytics",
"version": "2.32.6",
"version": "2.33.2",
"engines": {
"node": ">=18.0.0 <21.0.0"
},
+1
View File
@@ -7,6 +7,7 @@ PORT=3000
SYNCING_SERVER_JS_URL=http://syncing_server_js:3000
AUTH_SERVER_URL=http://auth:3000
AUTH_SERVER_GRPC_URL=http://auth:50051
WEB_SOCKET_SERVER_URL=http://websockets:3000
PAYMENTS_SERVER_URL=http://payments:3000
FILES_SERVER_URL=http://files:3000
+74
View File
@@ -3,6 +3,80 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.85.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.84.1...@standardnotes/api-gateway@1.85.0) (2023-11-16)
### Features
* add debug logs for grpc communication ([6391a01](https://github.com/standardnotes/api-gateway/commit/6391a01b5703db23b566710d0520c1197c46144b))
## [1.84.1](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.84.0...@standardnotes/api-gateway@1.84.1) (2023-11-16)
### Bug Fixes
* **api-gateway:** bindings ([78fbeb5](https://github.com/standardnotes/api-gateway/commit/78fbeb595f9e213688bcb2a031fba2aa3974cc6a))
# [1.84.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.83.5...@standardnotes/api-gateway@1.84.0) (2023-11-16)
### Features
* add grpc sessions validation server ([#928](https://github.com/standardnotes/api-gateway/issues/928)) ([4f62cac](https://github.com/standardnotes/api-gateway/commit/4f62cac213a6b5f503040ef6319e5198967974ce))
## [1.83.5](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.83.4...@standardnotes/api-gateway@1.83.5) (2023-11-14)
### Bug Fixes
* **api-gateway:** remove the verify body function ([3ddd671](https://github.com/standardnotes/api-gateway/commit/3ddd671c4797482a396844e804b4b45b82dbff2d))
* **api-gateway:** remove unused imports ([fd997f4](https://github.com/standardnotes/api-gateway/commit/fd997f4849ed01ef3ae4baf52b5895012fa711d4))
## [1.83.4](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.83.3...@standardnotes/api-gateway@1.83.4) (2023-11-14)
### Bug Fixes
* **api-gateway:** add verification if json is valid on request ([420bf9e](https://github.com/standardnotes/api-gateway/commit/420bf9ec5460a9693cc382e9164b4bdbb9b769a1))
* **api-gateway:** buffer encoding ([2823ed8](https://github.com/standardnotes/api-gateway/commit/2823ed8612cb9797d43e847edac5e2bdc0fe7426))
* **api-gateway:** checking for buffer length ([f65809e](https://github.com/standardnotes/api-gateway/commit/f65809ef3052d05df2e3f012a9b6340d18a6deae))
## [1.83.3](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.83.2...@standardnotes/api-gateway@1.83.3) (2023-11-13)
### Bug Fixes
* **api-gateway:** add application version to the error logs ([daed1a7](https://github.com/standardnotes/api-gateway/commit/daed1a77a02559a8487896b6fb8299befe8a2d96))
* **api-gateway:** add request method to the debug logs ([b39eb09](https://github.com/standardnotes/api-gateway/commit/b39eb09d91f0ea9482d27578faecdf57ed2ea48e))
## [1.83.2](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.83.1...@standardnotes/api-gateway@1.83.2) (2023-11-13)
### Bug Fixes
* **api-gateway:** debug log on error thrown body representation ([c8bf4ab](https://github.com/standardnotes/api-gateway/commit/c8bf4ab3a0ab757092077fc594e3ca7e090116b4))
## [1.83.1](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.83.0...@standardnotes/api-gateway@1.83.1) (2023-11-13)
### Bug Fixes
* **api-gateway:** add debug logs for errors on parsing ([60686dc](https://github.com/standardnotes/api-gateway/commit/60686dcdbd59c0d99cd1857a82ad62baed088b25))
# [1.83.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.82.1...@standardnotes/api-gateway@1.83.0) (2023-11-10)
### Bug Fixes
* **api-gateway:** add more info on error logs ([f997501](https://github.com/standardnotes/api-gateway/commit/f99750169f4d24cdc7530184af2230c687f3e166))
### Features
* add keep-alive connections to subservices ([#924](https://github.com/standardnotes/api-gateway/issues/924)) ([daad76d](https://github.com/standardnotes/api-gateway/commit/daad76d0ddae34c59dce45eedc4a055c4a11456d))
## [1.82.1](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.82.0...@standardnotes/api-gateway@1.82.1) (2023-11-10)
### Bug Fixes
* **api-gateway:** websockets calls logs severity ([a9b1543](https://github.com/standardnotes/api-gateway/commit/a9b1543e204afeab1fa2e008327c39cf306a247c))
# [1.82.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.81.14...@standardnotes/api-gateway@1.82.0) (2023-11-10)
### Features
* add graceful shutdown procedures upon SIGTERM ([#923](https://github.com/standardnotes/api-gateway/issues/923)) ([c24353c](https://github.com/standardnotes/api-gateway/commit/c24353cc24ebf4b40ff9a2cec8e37cfdef109e37))
## [1.81.14](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.81.13...@standardnotes/api-gateway@1.81.14) (2023-11-10)
### Bug Fixes
+17 -3
View File
@@ -90,8 +90,15 @@ void container.load().then((container) => {
const logger: winston.Logger = container.get(TYPES.ApiGateway_Logger)
server.setErrorConfig((app) => {
app.use((error: Record<string, unknown>, _request: Request, response: Response, _next: NextFunction) => {
logger.error(error.stack)
app.use((error: Record<string, unknown>, request: Request, response: Response, _next: NextFunction) => {
logger.error(
`[URL: |${request.method}| ${request.url}][SNJS: ${request.headers['x-snjs-version']}][Application: ${request.headers['x-application-version']}] Error thrown: ${error.stack}`,
)
logger.debug(
`[URL: |${request.method}| ${request.url}][SNJS: ${request.headers['x-snjs-version']}][Application: ${
request.headers['x-application-version']
}] Request body: ${JSON.stringify(request.body)}`,
)
response.status(500).send({
error: {
@@ -104,9 +111,16 @@ void container.load().then((container) => {
const serverInstance = server.build().listen(env.get('PORT'))
const keepAliveTimeout = env.get('KEEP_ALIVE_TIMEOUT', true) ? +env.get('KEEP_ALIVE_TIMEOUT', true) : 5000
const keepAliveTimeout = env.get('HTTP_KEEP_ALIVE_TIMEOUT', true) ? +env.get('HTTP_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}`)
})
+1 -1
View File
@@ -6,7 +6,7 @@ COMMAND=$1 && shift 1
case "$COMMAND" in
'start-web' )
echo "Starting Web..."
node docker/entrypoint-server.js
exec node docker/entrypoint-server.js
;;
* )
+5 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/api-gateway",
"version": "1.81.14",
"version": "1.85.0",
"engines": {
"node": ">=18.0.0 <21.0.0"
},
@@ -26,12 +26,15 @@
"start": "yarn node dist/bin/server.js"
},
"dependencies": {
"@grpc/grpc-js": "^1.9.10",
"@standardnotes/domain-core": "workspace:^",
"@standardnotes/domain-events": "workspace:*",
"@standardnotes/domain-events-infra": "workspace:*",
"@standardnotes/grpc": "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",
+70 -19
View File
@@ -1,27 +1,29 @@
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 * as grpc from '@grpc/grpc-js'
import axios, { AxiosInstance } from 'axios'
import Redis from 'ioredis'
import { Container } from 'inversify'
import { Timer, TimerInterface } from '@standardnotes/time'
import { Env } from './Env'
import { TYPES } from './Types'
import { ServiceProxyInterface } from '../Service/Http/ServiceProxyInterface'
import { ServiceProxyInterface } from '../Service/Proxy/ServiceProxyInterface'
import { HttpServiceProxy } from '../Service/Http/HttpServiceProxy'
import { SubscriptionTokenAuthMiddleware } from '../Controller/SubscriptionTokenAuthMiddleware'
import { CrossServiceTokenCacheInterface } from '../Service/Cache/CrossServiceTokenCacheInterface'
import { RedisCrossServiceTokenCache } from '../Infra/Redis/RedisCrossServiceTokenCache'
import { WebSocketAuthMiddleware } from '../Controller/WebSocketAuthMiddleware'
import { InMemoryCrossServiceTokenCache } from '../Infra/InMemory/InMemoryCrossServiceTokenCache'
import { DirectCallServiceProxy } from '../Service/Proxy/DirectCallServiceProxy'
import { DirectCallServiceProxy } from '../Service/DirectCall/DirectCallServiceProxy'
import { ServiceContainerInterface } from '@standardnotes/domain-core'
import { EndpointResolverInterface } from '../Service/Resolver/EndpointResolverInterface'
import { EndpointResolver } from '../Service/Resolver/EndpointResolver'
import { RequiredCrossServiceTokenMiddleware } from '../Controller/RequiredCrossServiceTokenMiddleware'
import { OptionalCrossServiceTokenMiddleware } from '../Controller/OptionalCrossServiceTokenMiddleware'
import { Transform } from 'stream'
import { ISessionsClient, SessionsClient } from '@standardnotes/grpc'
import { GRPCServiceProxy } from '../Service/gRPC/GRPCServiceProxy'
export class ContainerConfigLoader {
async load(configuration?: {
@@ -70,7 +72,19 @@ export class ContainerConfigLoader {
container.bind(TYPES.ApiGateway_Redis).toConstantValue(redis)
}
container.bind<AxiosInstance>(TYPES.ApiGateway_HTTPClient).toConstantValue(axios.create())
const httpAgentKeepAliveTimeout = env.get('HTTP_AGENT_KEEP_ALIVE_TIMEOUT', true)
? +env.get('HTTP_AGENT_KEEP_ALIVE_TIMEOUT', true)
: 4_000
container.bind<AxiosInstance>(TYPES.ApiGateway_HTTPClient).toConstantValue(
axios.create({
httpAgent: new AgentKeepAlive({
keepAlive: true,
timeout: 2 * httpAgentKeepAliveTimeout,
freeSocketTimeout: httpAgentKeepAliveTimeout,
}),
}),
)
// env vars
container.bind(TYPES.ApiGateway_SYNCING_SERVER_JS_URL).toConstantValue(env.get('SYNCING_SERVER_JS_URL', true))
@@ -105,19 +119,6 @@ export class ContainerConfigLoader {
// Services
container.bind<TimerInterface>(TYPES.ApiGateway_Timer).toConstantValue(new Timer())
if (isConfiguredForHomeServer) {
if (!configuration?.serviceContainer) {
throw new Error('Service container is required when configured for home server')
}
container
.bind<ServiceProxyInterface>(TYPES.ApiGateway_ServiceProxy)
.toConstantValue(
new DirectCallServiceProxy(configuration.serviceContainer, container.get(TYPES.ApiGateway_FILES_SERVER_URL)),
)
} else {
container.bind<ServiceProxyInterface>(TYPES.ApiGateway_ServiceProxy).to(HttpServiceProxy)
}
if (isConfiguredForHomeServer) {
container
.bind<CrossServiceTokenCacheInterface>(TYPES.ApiGateway_CrossServiceTokenCache)
@@ -131,6 +132,56 @@ export class ContainerConfigLoader {
.bind<EndpointResolverInterface>(TYPES.ApiGateway_EndpointResolver)
.toConstantValue(new EndpointResolver(isConfiguredForHomeServer))
if (isConfiguredForHomeServer) {
if (!configuration?.serviceContainer) {
throw new Error('Service container is required when configured for home server')
}
container
.bind<ServiceProxyInterface>(TYPES.ApiGateway_ServiceProxy)
.toConstantValue(
new DirectCallServiceProxy(configuration.serviceContainer, container.get(TYPES.ApiGateway_FILES_SERVER_URL)),
)
} else {
const isConfiguredForGRPCProxy = env.get('SERVICE_PROXY_TYPE', true) === 'grpc'
if (isConfiguredForGRPCProxy) {
container.bind(TYPES.ApiGateway_AUTH_SERVER_GRPC_URL).toConstantValue(env.get('AUTH_SERVER_GRPC_URL'))
const grpcAgentKeepAliveTimeout = env.get('GRPC_AGENT_KEEP_ALIVE_TIMEOUT', true)
? +env.get('GRPC_AGENT_KEEP_ALIVE_TIMEOUT', true)
: 8_000
container.bind<ISessionsClient>(TYPES.ApiGateway_GRPCSessionsClient).toConstantValue(
new SessionsClient(
container.get<string>(TYPES.ApiGateway_AUTH_SERVER_GRPC_URL),
grpc.credentials.createInsecure(),
{
'grpc.keepalive_time_ms': grpcAgentKeepAliveTimeout * 2,
'grpc.keepalive_timeout_ms': grpcAgentKeepAliveTimeout,
},
),
)
container
.bind<ServiceProxyInterface>(TYPES.ApiGateway_ServiceProxy)
.toConstantValue(
new GRPCServiceProxy(
container.get<AxiosInstance>(TYPES.ApiGateway_HTTPClient),
container.get<string>(TYPES.ApiGateway_AUTH_SERVER_URL),
container.get<string>(TYPES.ApiGateway_SYNCING_SERVER_JS_URL),
container.get<string>(TYPES.ApiGateway_PAYMENTS_SERVER_URL),
container.get<string>(TYPES.ApiGateway_FILES_SERVER_URL),
container.get<string>(TYPES.ApiGateway_WEB_SOCKET_SERVER_URL),
container.get<string>(TYPES.ApiGateway_REVISIONS_SERVER_URL),
container.get<string>(TYPES.ApiGateway_EMAIL_SERVER_URL),
container.get<number>(TYPES.ApiGateway_HTTP_CALL_TIMEOUT),
container.get<CrossServiceTokenCacheInterface>(TYPES.ApiGateway_CrossServiceTokenCache),
container.get<winston.Logger>(TYPES.ApiGateway_Logger),
container.get<TimerInterface>(TYPES.ApiGateway_Timer),
container.get<ISessionsClient>(TYPES.ApiGateway_GRPCSessionsClient),
),
)
} else {
container.bind<ServiceProxyInterface>(TYPES.ApiGateway_ServiceProxy).to(HttpServiceProxy)
}
}
logger.debug('Configuration complete')
return container
@@ -5,6 +5,7 @@ export const TYPES = {
// env vars
ApiGateway_SYNCING_SERVER_JS_URL: Symbol.for('ApiGateway_SYNCING_SERVER_JS_URL'),
ApiGateway_AUTH_SERVER_URL: Symbol.for('ApiGateway_AUTH_SERVER_URL'),
ApiGateway_AUTH_SERVER_GRPC_URL: Symbol.for('ApiGateway_AUTH_SERVER_GRPC_URL'),
ApiGateway_PAYMENTS_SERVER_URL: Symbol.for('ApiGateway_PAYMENTS_SERVER_URL'),
ApiGateway_FILES_SERVER_URL: Symbol.for('ApiGateway_FILES_SERVER_URL'),
ApiGateway_REVISIONS_SERVER_URL: Symbol.for('ApiGateway_REVISIONS_SERVER_URL'),
@@ -28,4 +29,5 @@ export const TYPES = {
ApiGateway_CrossServiceTokenCache: Symbol.for('ApiGateway_CrossServiceTokenCache'),
ApiGateway_Timer: Symbol.for('ApiGateway_Timer'),
ApiGateway_EndpointResolver: Symbol.for('ApiGateway_EndpointResolver'),
ApiGateway_GRPCSessionsClient: Symbol.for('ApiGateway_GRPCSessionsClient'),
}
@@ -7,7 +7,7 @@ import { AxiosError } from 'axios'
import { Logger } from 'winston'
import { CrossServiceTokenCacheInterface } from '../Service/Cache/CrossServiceTokenCacheInterface'
import { ServiceProxyInterface } from '../Service/Http/ServiceProxyInterface'
import { ServiceProxyInterface } from '../Service/Proxy/ServiceProxyInterface'
export abstract class AuthMiddleware extends BaseMiddleware {
constructor(
@@ -49,6 +49,8 @@ export abstract class AuthMiddleware extends BaseMiddleware {
return
}
this.logger.debug('[AuthMiddleware] Fetched cross-service token from underlying service')
crossServiceToken = (authResponse.data as { authToken: string }).authToken
crossServiceTokenFetchedFromCache = false
}
@@ -2,7 +2,7 @@ import { Request, Response } from 'express'
import { inject } from 'inversify'
import { controller, all, BaseHttpController, httpPost, httpGet, results, httpDelete } from 'inversify-express-utils'
import { TYPES } from '../Bootstrap/Types'
import { ServiceProxyInterface } from '../Service/Http/ServiceProxyInterface'
import { ServiceProxyInterface } from '../Service/Proxy/ServiceProxyInterface'
@controller('')
export class LegacyController extends BaseHttpController {
@@ -5,7 +5,7 @@ import { Logger } from 'winston'
import { TYPES } from '../Bootstrap/Types'
import { CrossServiceTokenCacheInterface } from '../Service/Cache/CrossServiceTokenCacheInterface'
import { ServiceProxyInterface } from '../Service/Http/ServiceProxyInterface'
import { ServiceProxyInterface } from '../Service/Proxy/ServiceProxyInterface'
import { AuthMiddleware } from './AuthMiddleware'
@injectable()
@@ -5,7 +5,7 @@ import { Logger } from 'winston'
import { TYPES } from '../Bootstrap/Types'
import { CrossServiceTokenCacheInterface } from '../Service/Cache/CrossServiceTokenCacheInterface'
import { ServiceProxyInterface } from '../Service/Http/ServiceProxyInterface'
import { ServiceProxyInterface } from '../Service/Proxy/ServiceProxyInterface'
import { AuthMiddleware } from './AuthMiddleware'
@injectable()
@@ -2,7 +2,7 @@ import { Request, Response } from 'express'
import { inject } from 'inversify'
import { BaseHttpController, controller, httpGet, httpPost } from 'inversify-express-utils'
import { TYPES } from '../../Bootstrap/Types'
import { ServiceProxyInterface } from '../../Service/Http/ServiceProxyInterface'
import { ServiceProxyInterface } from '../../Service/Proxy/ServiceProxyInterface'
import { EndpointResolverInterface } from '../../Service/Resolver/EndpointResolverInterface'
@controller('/v1')
@@ -3,7 +3,7 @@ import { Request, Response } from 'express'
import { controller, BaseHttpController, httpPost, httpGet, httpDelete } from 'inversify-express-utils'
import { TYPES } from '../../Bootstrap/Types'
import { ServiceProxyInterface } from '../../Service/Http/ServiceProxyInterface'
import { ServiceProxyInterface } from '../../Service/Proxy/ServiceProxyInterface'
import { EndpointResolverInterface } from '../../Service/Resolver/EndpointResolverInterface'
@controller('/v1/authenticators')
@@ -3,7 +3,7 @@ import { inject } from 'inversify'
import { BaseHttpController, controller, httpPost } from 'inversify-express-utils'
import { TYPES } from '../../Bootstrap/Types'
import { ServiceProxyInterface } from '../../Service/Http/ServiceProxyInterface'
import { ServiceProxyInterface } from '../../Service/Proxy/ServiceProxyInterface'
import { EndpointResolverInterface } from '../../Service/Resolver/EndpointResolverInterface'
@controller('/v1/files')
@@ -2,7 +2,7 @@ import { Request, Response } from 'express'
import { BaseHttpController, controller, httpPost } from 'inversify-express-utils'
import { inject } from 'inversify'
import { TYPES } from '../../Bootstrap/Types'
import { ServiceProxyInterface } from '../../Service/Http/ServiceProxyInterface'
import { ServiceProxyInterface } from '../../Service/Proxy/ServiceProxyInterface'
@controller('/v1')
export class InvoicesController extends BaseHttpController {
@@ -2,7 +2,7 @@ import { Request, Response } from 'express'
import { inject } from 'inversify'
import { BaseHttpController, controller, httpGet, httpPost } from 'inversify-express-utils'
import { TYPES } from '../../Bootstrap/Types'
import { ServiceProxyInterface } from '../../Service/Http/ServiceProxyInterface'
import { ServiceProxyInterface } from '../../Service/Proxy/ServiceProxyInterface'
import { EndpointResolverInterface } from '../../Service/Resolver/EndpointResolverInterface'
@controller('/v1/items', TYPES.ApiGateway_RequiredCrossServiceTokenMiddleware)
@@ -2,7 +2,7 @@ import { Request, Response } from 'express'
import { inject } from 'inversify'
import { BaseHttpController, controller, httpDelete, httpGet, httpPost } from 'inversify-express-utils'
import { TYPES } from '../../Bootstrap/Types'
import { ServiceProxyInterface } from '../../Service/Http/ServiceProxyInterface'
import { ServiceProxyInterface } from '../../Service/Proxy/ServiceProxyInterface'
import { EndpointResolverInterface } from '../../Service/Resolver/EndpointResolverInterface'
@controller('/v1/messages', TYPES.ApiGateway_RequiredCrossServiceTokenMiddleware)
@@ -3,7 +3,7 @@ import { inject } from 'inversify'
import { BaseHttpController, controller, httpGet, httpPost } from 'inversify-express-utils'
import { TYPES } from '../../Bootstrap/Types'
import { ServiceProxyInterface } from '../../Service/Http/ServiceProxyInterface'
import { ServiceProxyInterface } from '../../Service/Proxy/ServiceProxyInterface'
import { EndpointResolverInterface } from '../../Service/Resolver/EndpointResolverInterface'
@controller('/v1/offline')
@@ -2,7 +2,7 @@ import { Request, Response } from 'express'
import { inject } from 'inversify'
import { all, BaseHttpController, controller, httpDelete, httpGet, httpPost } from 'inversify-express-utils'
import { TYPES } from '../../Bootstrap/Types'
import { ServiceProxyInterface } from '../../Service/Http/ServiceProxyInterface'
import { ServiceProxyInterface } from '../../Service/Proxy/ServiceProxyInterface'
@controller('/v1')
export class PaymentsController extends BaseHttpController {
@@ -2,7 +2,7 @@ import { Request, Response } from 'express'
import { inject } from 'inversify'
import { BaseHttpController, controller, httpDelete, httpGet, httpPost } from 'inversify-express-utils'
import { TYPES } from '../../Bootstrap/Types'
import { ServiceProxyInterface } from '../../Service/Http/ServiceProxyInterface'
import { ServiceProxyInterface } from '../../Service/Proxy/ServiceProxyInterface'
import { EndpointResolverInterface } from '../../Service/Resolver/EndpointResolverInterface'
@controller('/v1/sessions')
@@ -2,7 +2,7 @@ import { Request, Response } from 'express'
import { inject } from 'inversify'
import { BaseHttpController, controller, httpDelete, httpGet, httpPatch, httpPost } from 'inversify-express-utils'
import { TYPES } from '../../Bootstrap/Types'
import { ServiceProxyInterface } from '../../Service/Http/ServiceProxyInterface'
import { ServiceProxyInterface } from '../../Service/Proxy/ServiceProxyInterface'
import { EndpointResolverInterface } from '../../Service/Resolver/EndpointResolverInterface'
@controller('/v1/shared-vaults', TYPES.ApiGateway_RequiredCrossServiceTokenMiddleware)
@@ -2,7 +2,7 @@ import { Request, Response } from 'express'
import { inject } from 'inversify'
import { BaseHttpController, controller, httpDelete, httpGet, httpPost } from 'inversify-express-utils'
import { TYPES } from '../../Bootstrap/Types'
import { ServiceProxyInterface } from '../../Service/Http/ServiceProxyInterface'
import { ServiceProxyInterface } from '../../Service/Proxy/ServiceProxyInterface'
import { EndpointResolverInterface } from '../../Service/Resolver/EndpointResolverInterface'
@controller('/v1/shared-vaults/:sharedVaultUuid/users', TYPES.ApiGateway_RequiredCrossServiceTokenMiddleware)
@@ -2,7 +2,7 @@ import { Request, Response } from 'express'
import { inject } from 'inversify'
import { BaseHttpController, controller, httpDelete, httpGet, httpPost } from 'inversify-express-utils'
import { TYPES } from '../../Bootstrap/Types'
import { ServiceProxyInterface } from '../../Service/Http/ServiceProxyInterface'
import { ServiceProxyInterface } from '../../Service/Proxy/ServiceProxyInterface'
import { EndpointResolverInterface } from '../../Service/Resolver/EndpointResolverInterface'
@controller('/v1/shared-vaults', TYPES.ApiGateway_RequiredCrossServiceTokenMiddleware)
@@ -3,7 +3,7 @@ import { inject } from 'inversify'
import { BaseHttpController, controller, httpDelete, httpGet, httpPost } from 'inversify-express-utils'
import { TYPES } from '../../Bootstrap/Types'
import { ServiceProxyInterface } from '../../Service/Http/ServiceProxyInterface'
import { ServiceProxyInterface } from '../../Service/Proxy/ServiceProxyInterface'
import { EndpointResolverInterface } from '../../Service/Resolver/EndpointResolverInterface'
@controller('/v1/subscription-invites')
@@ -3,7 +3,7 @@ import { inject } from 'inversify'
import { BaseHttpController, controller, httpPost } from 'inversify-express-utils'
import { TYPES } from '../../Bootstrap/Types'
import { ServiceProxyInterface } from '../../Service/Http/ServiceProxyInterface'
import { ServiceProxyInterface } from '../../Service/Proxy/ServiceProxyInterface'
import { EndpointResolverInterface } from '../../Service/Resolver/EndpointResolverInterface'
@controller('/v1/subscription-tokens')
@@ -13,7 +13,7 @@ import {
} from 'inversify-express-utils'
import { Logger } from 'winston'
import { TYPES } from '../../Bootstrap/Types'
import { ServiceProxyInterface } from '../../Service/Http/ServiceProxyInterface'
import { ServiceProxyInterface } from '../../Service/Proxy/ServiceProxyInterface'
import { TokenAuthenticationMethod } from '../TokenAuthenticationMethod'
import { EndpointResolverInterface } from '../../Service/Resolver/EndpointResolverInterface'
@@ -4,7 +4,7 @@ import { BaseHttpController, controller, httpDelete, httpPost } from 'inversify-
import { Logger } from 'winston'
import { TYPES } from '../../Bootstrap/Types'
import { ServiceProxyInterface } from '../../Service/Http/ServiceProxyInterface'
import { ServiceProxyInterface } from '../../Service/Proxy/ServiceProxyInterface'
import { EndpointResolverInterface } from '../../Service/Resolver/EndpointResolverInterface'
@controller('/v1/sockets')
@@ -3,7 +3,7 @@ import { inject } from 'inversify'
import { BaseHttpController, controller, httpPost } from 'inversify-express-utils'
import { TYPES } from '../../Bootstrap/Types'
import { ServiceProxyInterface } from '../../Service/Http/ServiceProxyInterface'
import { ServiceProxyInterface } from '../../Service/Proxy/ServiceProxyInterface'
import { EndpointResolverInterface } from '../../Service/Resolver/EndpointResolverInterface'
@controller('/v2')
@@ -2,7 +2,7 @@ import { Request, Response } from 'express'
import { BaseHttpController, controller, httpDelete, httpGet, httpPatch, httpPost } from 'inversify-express-utils'
import { inject } from 'inversify'
import { TYPES } from '../../Bootstrap/Types'
import { ServiceProxyInterface } from '../../Service/Http/ServiceProxyInterface'
import { ServiceProxyInterface } from '../../Service/Proxy/ServiceProxyInterface'
@controller('/v2')
export class PaymentsControllerV2 extends BaseHttpController {
@@ -3,7 +3,7 @@ import { inject } from 'inversify'
import { BaseHttpController, controller, httpDelete, httpGet } from 'inversify-express-utils'
import { TYPES } from '../../Bootstrap/Types'
import { ServiceProxyInterface } from '../../Service/Http/ServiceProxyInterface'
import { ServiceProxyInterface } from '../../Service/Proxy/ServiceProxyInterface'
import { EndpointResolverInterface } from '../../Service/Resolver/EndpointResolverInterface'
@controller('/v2', TYPES.ApiGateway_RequiredCrossServiceTokenMiddleware)
@@ -1,7 +1,7 @@
import { Request, Response } from 'express'
import { ServiceContainerInterface, ServiceIdentifier } from '@standardnotes/domain-core'
import { ServiceProxyInterface } from '../Http/ServiceProxyInterface'
import { ServiceProxyInterface } from '../Proxy/ServiceProxyInterface'
export class DirectCallServiceProxy implements ServiceProxyInterface {
constructor(
@@ -6,7 +6,7 @@ import { Logger } from 'winston'
import { TYPES } from '../../Bootstrap/Types'
import { CrossServiceTokenCacheInterface } from '../Cache/CrossServiceTokenCacheInterface'
import { ServiceProxyInterface } from './ServiceProxyInterface'
import { ServiceProxyInterface } from '../Proxy/ServiceProxyInterface'
import { TimerInterface } from '@standardnotes/time'
@injectable()
@@ -144,7 +144,7 @@ export class HttpServiceProxy implements ServiceProxyInterface {
}
const isARequestComingFromApiGatewayAndShouldBeKeptInMinimalFormat = request.headers.connectionid !== undefined
this.logger.info(
this.logger.debug(
`Calling websockets service: ${endpointOrMethodIdentifier}. Format is minimal: ${isARequestComingFromApiGatewayAndShouldBeKeptInMinimalFormat}`,
)
if (isARequestComingFromApiGatewayAndShouldBeKeptInMinimalFormat) {
@@ -0,0 +1,442 @@
import { AxiosInstance, AxiosError, AxiosResponse, Method } from 'axios'
import { Request, Response } from 'express'
import { Logger } from 'winston'
import { TimerInterface } from '@standardnotes/time'
import { ISessionsClient, AuthorizationHeader, SessionValidationResponse } from '@standardnotes/grpc'
import * as grpc from '@grpc/grpc-js'
import { CrossServiceTokenCacheInterface } from '../Cache/CrossServiceTokenCacheInterface'
import { ServiceProxyInterface } from '../Proxy/ServiceProxyInterface'
export class GRPCServiceProxy implements ServiceProxyInterface {
constructor(
private httpClient: AxiosInstance,
private authServerUrl: string,
private syncingServerJsUrl: string,
private paymentsServerUrl: string,
private filesServerUrl: string,
private webSocketServerUrl: string,
private revisionsServerUrl: string,
private emailServerUrl: string,
private httpCallTimeout: number,
private crossServiceTokenCache: CrossServiceTokenCacheInterface,
private logger: Logger,
private timer: TimerInterface,
private sessionsClient: ISessionsClient,
) {}
async validateSession(headers: {
authorization: string
sharedVaultOwnerContext?: string
}): Promise<{ status: number; data: unknown; headers: { contentType: string } }> {
return new Promise((resolve, reject) => {
try {
const request = new AuthorizationHeader()
request.setBearerToken(headers.authorization)
const metadata = new grpc.Metadata()
metadata.set('x-shared-vault-owner-context', headers.sharedVaultOwnerContext ?? '')
this.logger.debug('[GRPCServiceProxy] Validating session via gRPC')
this.sessionsClient.validate(
request,
metadata,
(error: grpc.ServiceError | null, response: SessionValidationResponse) => {
if (error) {
const responseCode = error.metadata.get('x-auth-error-response-code').pop()
if (responseCode) {
return resolve({
status: +responseCode,
data: {
error: {
message: error.metadata.get('x-auth-error-message').pop(),
tag: error.metadata.get('x-auth-error-tag').pop(),
},
},
headers: {
contentType: 'application/json',
},
})
}
return reject(error)
}
return resolve({
status: 200,
data: {
authToken: response.getCrossServiceToken(),
},
headers: {
contentType: 'application/json',
},
})
},
)
} catch (error) {
return reject(error)
}
})
}
async callSyncingServer(
request: Request,
response: Response,
endpointOrMethodIdentifier: string,
payload?: Record<string, unknown> | string,
): Promise<void> {
await this.callServer(this.syncingServerJsUrl, request, response, endpointOrMethodIdentifier, payload)
}
async callRevisionsServer(
request: Request,
response: Response,
endpointOrMethodIdentifier: string,
payload?: Record<string, unknown> | string,
): Promise<void> {
if (!this.revisionsServerUrl) {
response.status(400).send({ message: 'Revisions Server not configured' })
return
}
await this.callServer(this.revisionsServerUrl, request, response, endpointOrMethodIdentifier, payload)
}
async callLegacySyncingServer(
request: Request,
response: Response,
endpointOrMethodIdentifier: string,
payload?: Record<string, unknown> | string,
): Promise<void> {
await this.callServerWithLegacyFormat(
this.syncingServerJsUrl,
request,
response,
endpointOrMethodIdentifier,
payload,
)
}
async callAuthServer(
request: Request,
response: Response,
endpointOrMethodIdentifier: string,
payload?: Record<string, unknown> | string,
): Promise<void> {
await this.callServer(this.authServerUrl, request, response, endpointOrMethodIdentifier, payload)
}
async callEmailServer(
request: Request,
response: Response,
endpointOrMethodIdentifier: string,
payload?: Record<string, unknown> | string,
): Promise<void> {
if (!this.emailServerUrl) {
response.status(400).send({ message: 'Email Server not configured' })
return
}
await this.callServer(this.emailServerUrl, request, response, endpointOrMethodIdentifier, payload)
}
async callWebSocketServer(
request: Request,
response: Response,
endpointOrMethodIdentifier: string,
payload?: Record<string, unknown> | string,
): Promise<void> {
if (!this.webSocketServerUrl) {
this.logger.debug('Websockets Server URL not defined. Skipped request to WebSockets API.')
return
}
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(
request: Request,
response: Response,
endpointOrMethodIdentifier: string,
payload?: Record<string, unknown> | string,
): Promise<void | Response<unknown, Record<string, unknown>>> {
if (!this.paymentsServerUrl) {
this.logger.debug('Payments Server URL not defined. Skipped request to Payments API.')
return
}
await this.callServerWithLegacyFormat(
this.paymentsServerUrl,
request,
response,
endpointOrMethodIdentifier,
payload,
)
}
async callAuthServerWithLegacyFormat(
request: Request,
response: Response,
endpointOrMethodIdentifier: string,
payload?: Record<string, unknown> | string,
): Promise<void> {
await this.callServerWithLegacyFormat(this.authServerUrl, request, response, endpointOrMethodIdentifier, payload)
}
private async getServerResponse(
serverUrl: string,
request: Request,
response: Response,
endpointOrMethodIdentifier: string,
payload?: Record<string, unknown> | string,
retryAttempt?: number,
): Promise<AxiosResponse | undefined> {
try {
const headers: Record<string, string> = {}
for (const headerName of Object.keys(request.headers)) {
headers[headerName] = request.headers[headerName] as string
}
delete headers.host
delete headers['content-length']
if (response.locals.authToken) {
headers['X-Auth-Token'] = response.locals.authToken
}
if (response.locals.offlineAuthToken) {
headers['X-Auth-Offline-Token'] = response.locals.offlineAuthToken
}
this.logger.debug(`Calling [${request.method}] ${serverUrl}/${endpointOrMethodIdentifier},
headers: ${JSON.stringify(headers)},
query: ${JSON.stringify(request.query)},
payload: ${JSON.stringify(payload)}`)
const serviceResponse = await this.httpClient.request({
method: request.method as Method,
headers,
url: `${serverUrl}/${endpointOrMethodIdentifier}`,
data: this.getRequestData(payload),
maxContentLength: Infinity,
maxBodyLength: Infinity,
params: request.query,
timeout: this.httpCallTimeout,
validateStatus: (status: number) => {
return status >= 200 && status < 500
},
})
if (serviceResponse.headers['x-invalidate-cache']) {
const userUuid = serviceResponse.headers['x-invalidate-cache']
await this.crossServiceTokenCache.invalidate(userUuid)
}
if (retryAttempt) {
this.logger.debug(
`Request to ${serverUrl}/${endpointOrMethodIdentifier} succeeded after ${retryAttempt} retries`,
)
}
return serviceResponse
} catch (error) {
const requestDidNotMakeIt = this.requestTimedOutOrDidNotReachDestination(error as Record<string, unknown>)
const tooManyRetryAttempts = retryAttempt && retryAttempt > 2
if (!tooManyRetryAttempts && requestDidNotMakeIt) {
await this.timer.sleep(50)
const nextRetryAttempt = retryAttempt ? retryAttempt + 1 : 1
this.logger.debug(
`Retrying request to ${serverUrl}/${endpointOrMethodIdentifier} for the ${nextRetryAttempt} time`,
)
return this.getServerResponse(
serverUrl,
request,
response,
endpointOrMethodIdentifier,
payload,
nextRetryAttempt,
)
}
let detailedErrorMessage = (error as Error).message
if (error instanceof AxiosError) {
detailedErrorMessage = `Status: ${error.status}, code: ${error.code}, message: ${error.message}`
}
this.logger.error(
tooManyRetryAttempts
? `Request to ${serverUrl}/${endpointOrMethodIdentifier} timed out after ${retryAttempt} retries`
: `Could not pass the request to ${serverUrl}/${endpointOrMethodIdentifier} on underlying service: ${detailedErrorMessage}`,
)
this.logger.debug(`Response error: ${JSON.stringify(error)}`)
if ((error as AxiosError).response?.headers['content-type']) {
response.setHeader('content-type', (error as AxiosError).response?.headers['content-type'] as string)
}
const errorCode =
(error as AxiosError).isAxiosError && !isNaN(+((error as AxiosError).code as string))
? +((error as AxiosError).code as string)
: 500
const responseErrorMessage = (error as AxiosError).response?.data
response
.status(errorCode)
.send(
responseErrorMessage ??
"Unfortunately, we couldn't handle your request. Please try again or contact our support if the error persists.",
)
}
return
}
private async callServer(
serverUrl: string,
request: Request,
response: Response,
endpointOrMethodIdentifier: string,
payload?: Record<string, unknown> | string,
): Promise<void> {
const serviceResponse = await this.getServerResponse(
serverUrl,
request,
response,
endpointOrMethodIdentifier,
payload,
)
this.logger.debug(`Response from underlying server: ${JSON.stringify(serviceResponse?.data)},
headers: ${JSON.stringify(serviceResponse?.headers)}`)
if (!serviceResponse) {
return
}
this.applyResponseHeaders(serviceResponse, response)
if (this.responseShouldNotBeDecorated(serviceResponse)) {
response.status(serviceResponse.status).send(serviceResponse.data)
return
}
response.status(serviceResponse.status).send({
meta: {
auth: {
userUuid: response.locals.user?.uuid,
roles: response.locals.roles,
},
server: {
filesServerUrl: this.filesServerUrl,
},
},
data: serviceResponse.data,
})
}
private async callServerWithLegacyFormat(
serverUrl: string,
request: Request,
response: Response,
endpointOrMethodIdentifier: string,
payload?: Record<string, unknown> | string,
): Promise<void | Response<unknown, Record<string, unknown>>> {
const serviceResponse = await this.getServerResponse(
serverUrl,
request,
response,
endpointOrMethodIdentifier,
payload,
)
if (!serviceResponse) {
return
}
this.applyResponseHeaders(serviceResponse, response)
if (serviceResponse.request._redirectable._redirectCount > 0) {
response.status(302)
response.redirect(serviceResponse.request.res.responseUrl)
} else {
response.status(serviceResponse.status)
response.send(serviceResponse.data)
}
}
private getRequestData(
payload: Record<string, unknown> | string | undefined,
): Record<string, unknown> | string | undefined {
if (
payload === '' ||
payload === null ||
payload === undefined ||
(typeof payload === 'object' && Object.keys(payload).length === 0)
) {
return undefined
}
return payload
}
private responseShouldNotBeDecorated(serviceResponse: AxiosResponse): boolean {
return (
serviceResponse.headers['content-type'] !== undefined &&
serviceResponse.headers['content-type'].toLowerCase().includes('text/html')
)
}
private applyResponseHeaders(serviceResponse: AxiosResponse, response: Response): void {
const returnedHeadersFromUnderlyingService = [
'access-control-allow-methods',
'access-control-allow-origin',
'access-control-expose-headers',
'authorization',
'content-type',
'x-ssjs-version',
'x-auth-version',
]
returnedHeadersFromUnderlyingService.map((headerName) => {
const headerValue = serviceResponse.headers[headerName]
if (headerValue) {
response.setHeader(headerName, headerValue)
}
})
}
private requestTimedOutOrDidNotReachDestination(error: Record<string, unknown>): boolean {
return (
('code' in error && error.code === 'ETIMEDOUT') ||
('response' in error &&
'status' in (error.response as Record<string, unknown>) &&
[503, 504].includes((error.response as Record<string, unknown>).status as number))
)
}
}
@@ -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
+1
View File
@@ -13,6 +13,7 @@ AUTH_JWT_TTL=60000
ENCRYPTION_SERVER_KEY=change-me-!
PORT=3000
GRPC_PORT=50051
DB_HOST=127.0.0.1
DB_REPLICA_HOST=127.0.0.1
+32
View File
@@ -3,6 +3,38 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.171.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.170.0...@standardnotes/auth-server@1.171.0) (2023-11-16)
### Features
* add debug logs for grpc communication ([6391a01](https://github.com/standardnotes/server/commit/6391a01b5703db23b566710d0520c1197c46144b))
# [1.170.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.169.2...@standardnotes/auth-server@1.170.0) (2023-11-16)
### Features
* add grpc sessions validation server ([#928](https://github.com/standardnotes/server/issues/928)) ([4f62cac](https://github.com/standardnotes/server/commit/4f62cac213a6b5f503040ef6319e5198967974ce))
## [1.169.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.169.1...@standardnotes/auth-server@1.169.2) (2023-11-13)
**Note:** Version bump only for package @standardnotes/auth-server
## [1.169.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.169.0...@standardnotes/auth-server@1.169.1) (2023-11-13)
**Note:** Version bump only for package @standardnotes/auth-server
# [1.169.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.168.0...@standardnotes/auth-server@1.169.0) (2023-11-10)
### Features
* add keep-alive connections to subservices ([#924](https://github.com/standardnotes/server/issues/924)) ([daad76d](https://github.com/standardnotes/server/commit/daad76d0ddae34c59dce45eedc4a055c4a11456d))
# [1.168.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.167.2...@standardnotes/auth-server@1.168.0) (2023-11-10)
### Features
* add graceful shutdown procedures upon SIGTERM ([#923](https://github.com/standardnotes/server/issues/923)) ([c24353c](https://github.com/standardnotes/server/commit/c24353cc24ebf4b40ff9a2cec8e37cfdef109e37))
## [1.167.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.167.1...@standardnotes/auth-server@1.167.2) (2023-11-08)
### Bug Fixes
+50 -2
View File
@@ -20,6 +20,7 @@ import '../src/Infra/InversifyExpressUtils/AnnotatedHealthCheckController'
import '../src/Infra/InversifyExpressUtils/AnnotatedFeaturesController'
import * as cors from 'cors'
import * as grpc from '@grpc/grpc-js'
import { urlencoded, json, Request, Response, NextFunction } from 'express'
import * as winston from 'winston'
import * as dayjs from 'dayjs'
@@ -29,6 +30,10 @@ import { InversifyExpressServer } from 'inversify-express-utils'
import { ContainerConfigLoader } from '../src/Bootstrap/Container'
import TYPES from '../src/Bootstrap/Types'
import { Env } from '../src/Bootstrap/Env'
import { SessionsServer } from '../src/Infra/gRPC/SessionsServer'
import { SessionsService } from '@standardnotes/grpc'
import { AuthenticateRequest } from '../src/Domain/UseCase/AuthenticateRequest'
import { CreateCrossServiceToken } from '../src/Domain/UseCase/CreateCrossServiceToken/CreateCrossServiceToken'
const container = new ContainerConfigLoader()
void container.load().then((container) => {
@@ -64,9 +69,52 @@ void container.load().then((container) => {
})
})
const serverInstance = server.build()
const serverInstance = server.build().listen(env.get('PORT'))
serverInstance.listen(env.get('PORT'))
const httpKeepAliveTimeout = env.get('HTTP_KEEP_ALIVE_TIMEOUT', true)
? +env.get('HTTP_KEEP_ALIVE_TIMEOUT', true)
: 10_000
serverInstance.keepAliveTimeout = httpKeepAliveTimeout
const grpcKeepAliveTimeout = env.get('GRPC_KEEP_ALIVE_TIMEOUT', true)
? +env.get('GRPC_KEEP_ALIVE_TIMEOUT', true)
: 10_000
const grpcServer = new grpc.Server({
'grpc.keepalive_time_ms': grpcKeepAliveTimeout * 2,
'grpc.keepalive_timeout_ms': grpcKeepAliveTimeout,
})
const gRPCPort = env.get('GRPC_PORT', true) ? +env.get('GRPC_PORT', true) : 50051
const sessionsServer = new SessionsServer(
container.get<AuthenticateRequest>(TYPES.Auth_AuthenticateRequest),
container.get<CreateCrossServiceToken>(TYPES.Auth_CreateCrossServiceToken),
container.get<winston.Logger>(TYPES.Auth_Logger),
)
grpcServer.addService(SessionsService, {
validate: sessionsServer.validate.bind(sessionsServer),
})
grpcServer.bindAsync(`0.0.0.0:${gRPCPort}`, grpc.ServerCredentials.createInsecure(), (error, port) => {
if (error) {
logger.error(`Failed to bind gRPC server: ${error.message}`)
}
logger.info(`gRPC server bound on port ${port}`)
grpcServer.start()
logger.info('gRPC server started')
})
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
View File
@@ -22,5 +22,11 @@ void container.load().then((container) => {
const subscriber = container.get<DomainEventSubscriberInterface>(TYPES.Auth_DomainEventSubscriber)
process.on('SIGTERM', () => {
logger.info('SIGTERM received. Stopping worker...')
subscriber.stop()
logger.info('Worker stopped.')
})
subscriber.start()
})
+8 -8
View File
@@ -6,45 +6,45 @@ COMMAND=$1 && shift 1
case "$COMMAND" in
'start-web' )
echo "[Docker] Starting Web..."
node docker/entrypoint-server.js
exec node docker/entrypoint-server.js
;;
'start-worker' )
echo "[Docker] Starting Worker..."
node docker/entrypoint-worker.js
exec node docker/entrypoint-worker.js
;;
'cleanup' )
echo "[Docker] Starting Cleanup..."
node docker/entrypoint-cleanup.js
exec node docker/entrypoint-cleanup.js
;;
'stats' )
echo "[Docker] Starting Persisting Stats..."
node docker/entrypoint-stats.js
exec node docker/entrypoint-stats.js
;;
'email-daily-backup' )
echo "[Docker] Starting Email Daily Backup..."
node docker/entrypoint-backup.js daily
exec node docker/entrypoint-backup.js daily
;;
'email-weekly-backup' )
echo "[Docker] Starting Email Weekly Backup..."
node docker/entrypoint-backup.js weekly
exec node docker/entrypoint-backup.js weekly
;;
'email-backup' )
echo "[Docker] Starting Email Backup For Single User..."
EMAIL=$1 && shift 1
node docker/entrypoint-user-email-backup.js $EMAIL
exec node docker/entrypoint-user-email-backup.js $EMAIL
;;
'delete-accounts' )
echo "[Docker] Starting Accounts Deleting from CSV..."
FILE_NAME=$1 && shift 1
MODE=$1 && shift 1
node docker/entrypoint-delete-accounts.js $FILE_NAME $MODE
exec node docker/entrypoint-delete-accounts.js $FILE_NAME $MODE
;;
* )
+4 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/auth-server",
"version": "1.167.2",
"version": "1.171.0",
"engines": {
"node": ">=18.0.0 <21.0.0"
},
@@ -20,6 +20,7 @@
"lint:fix": "eslint . --fix --ext .ts",
"pretest": "yarn lint && yarn build",
"test": "jest --coverage --no-cache --config=./jest.config.js --maxWorkers=50%",
"grpc": "yarn node dist/bin/grpc.js",
"start": "yarn node dist/bin/server.js",
"worker": "yarn node dist/bin/worker.js",
"cleanup": "yarn node dist/bin/cleanup.js",
@@ -37,6 +38,7 @@
"@aws-sdk/client-sqs": "^3.427.0",
"@cbor-extract/cbor-extract-linux-arm64": "^2.1.1",
"@cbor-extract/cbor-extract-linux-x64": "^2.1.1",
"@grpc/grpc-js": "^1.9.10",
"@simplewebauthn/server": "^8.1.1",
"@simplewebauthn/typescript-types": "^8.0.0",
"@standardnotes/api": "^1.26.26",
@@ -45,6 +47,7 @@
"@standardnotes/domain-events": "workspace:*",
"@standardnotes/domain-events-infra": "workspace:*",
"@standardnotes/features": "^1.59.7",
"@standardnotes/grpc": "workspace:^",
"@standardnotes/predicates": "workspace:*",
"@standardnotes/responses": "^1.13.27",
"@standardnotes/security": "workspace:*",
@@ -0,0 +1,80 @@
import * as grpc from '@grpc/grpc-js'
import { Status } from '@grpc/grpc-js/build/src/constants'
import { AuthorizationHeader, ISessionsServer, SessionValidationResponse } from '@standardnotes/grpc'
import { AuthenticateRequest } from '../../Domain/UseCase/AuthenticateRequest'
import { User } from '../../Domain/User/User'
import { CreateCrossServiceToken } from '../../Domain/UseCase/CreateCrossServiceToken/CreateCrossServiceToken'
import { Logger } from 'winston'
export class SessionsServer implements ISessionsServer {
constructor(
private authenticateRequest: AuthenticateRequest,
private createCrossServiceToken: CreateCrossServiceToken,
private logger: Logger,
) {}
async validate(
call: grpc.ServerUnaryCall<AuthorizationHeader, SessionValidationResponse>,
callback: grpc.sendUnaryData<SessionValidationResponse>,
): Promise<void> {
this.logger.debug('[SessionsServer] Validating session via gRPC')
const authenticateRequestResponse = await this.authenticateRequest.execute({
authorizationHeader: call.request.getBearerToken(),
})
if (!authenticateRequestResponse.success) {
const metadata = new grpc.Metadata()
metadata.set('x-auth-error-message', authenticateRequestResponse.errorMessage as string)
metadata.set('x-auth-error-tag', authenticateRequestResponse.errorTag as string)
metadata.set('x-auth-error-response-code', authenticateRequestResponse.responseCode.toString())
return callback(
{
code: Status.PERMISSION_DENIED,
message: authenticateRequestResponse.errorMessage,
name: authenticateRequestResponse.errorTag,
metadata,
},
null,
)
}
const user = authenticateRequestResponse.user as User
const sharedVaultOwnerMetadata = call.metadata.get('x-shared-vault-owner-context')
let sharedVaultOwnerContext = undefined
if (sharedVaultOwnerMetadata.length > 0 && sharedVaultOwnerMetadata[0].length > 0) {
sharedVaultOwnerContext = sharedVaultOwnerMetadata[0].toString()
}
const resultOrError = await this.createCrossServiceToken.execute({
user,
session: authenticateRequestResponse.session,
sharedVaultOwnerContext,
})
if (resultOrError.isFailed()) {
const metadata = new grpc.Metadata()
metadata.set('x-auth-error-message', resultOrError.getError())
metadata.set('x-auth-error-response-code', '400')
return callback(
{
code: Status.INVALID_ARGUMENT,
message: resultOrError.getError(),
name: 'INVALID_ARGUMENT',
metadata,
},
null,
)
}
const response = new SessionValidationResponse()
response.setCrossServiceToken(resultOrError.getValue())
this.logger.debug('[SessionsServer] Session validated via gRPC')
callback(null, response)
}
}
@@ -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
+16
View File
@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.21.2](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.21.1...@standardnotes/domain-events-infra@1.21.2) (2023-11-13)
**Note:** Version bump only for package @standardnotes/domain-events-infra
## [1.21.1](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.21.0...@standardnotes/domain-events-infra@1.21.1) (2023-11-13)
### Bug Fixes
* remove unused even-store from code base ([#925](https://github.com/standardnotes/server/issues/925)) ([8e4e365](https://github.com/standardnotes/server/commit/8e4e36513aa6e3c4f98197adfa75e014920b3572))
# [1.21.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.20.4...@standardnotes/domain-events-infra@1.21.0) (2023-11-10)
### Features
* add graceful shutdown procedures upon SIGTERM ([#923](https://github.com/standardnotes/server/issues/923)) ([c24353c](https://github.com/standardnotes/server/commit/c24353cc24ebf4b40ff9a2cec8e37cfdef109e37))
## [1.20.4](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.20.3...@standardnotes/domain-events-infra@1.20.4) (2023-11-07)
**Note:** Version bump only for package @standardnotes/domain-events-infra
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/domain-events-infra",
"version": "1.20.4",
"version": "1.21.2",
"engines": {
"node": ">=18.0.0 <21.0.0"
},
@@ -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)
}
@@ -1,32 +0,0 @@
import { Consumer } from 'sqs-consumer'
import { Message, SQSClient } from '@aws-sdk/client-sqs'
import {
DomainEventMessageHandlerInterface,
DomainEventSubscriberFactoryInterface,
DomainEventSubscriberInterface,
} from '@standardnotes/domain-events'
export class SQSDomainEventSubscriberFactory implements DomainEventSubscriberFactoryInterface {
constructor(
private sqs: SQSClient,
private queueUrl: string,
private domainEventMessageHandler: DomainEventMessageHandlerInterface,
) {}
create(): DomainEventSubscriberInterface {
const sqsConsumer = Consumer.create({
attributeNames: ['All'],
messageAttributeNames: ['All'],
queueUrl: this.queueUrl,
sqs: this.sqs,
handleMessage:
/* istanbul ignore next */
async (message: Message) => await this.domainEventMessageHandler.handleMessage(<string>message.Body),
})
sqsConsumer.on('error', this.domainEventMessageHandler.handleError.bind(this.domainEventMessageHandler))
sqsConsumer.on('processing_error', this.domainEventMessageHandler.handleError.bind(this.domainEventMessageHandler))
return sqsConsumer
}
}
@@ -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'
@@ -16,6 +14,5 @@ export * from './SNS/SNSOpenTelemetryDomainEventPublisher'
export * from './SQS/SQSBounceNotificiationHandler'
export * from './SQS/SQSDomainEventSubscriber'
export * from './SQS/SQSDomainEventSubscriberFactory'
export * from './SQS/SQSEventMessageHandler'
export * from './SQS/SQSOpenTelemetryDomainEventSubscriber'
+18
View File
@@ -3,6 +3,24 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [2.134.2](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.134.1...@standardnotes/domain-events@2.134.2) (2023-11-13)
### Bug Fixes
* **domain-events:** remove unused event ([f6ec862](https://github.com/standardnotes/server/commit/f6ec8626e57f1d93210aaf9fae4c15ae032aca8e))
## [2.134.1](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.134.0...@standardnotes/domain-events@2.134.1) (2023-11-13)
### Bug Fixes
* remove unused even-store from code base ([#925](https://github.com/standardnotes/server/issues/925)) ([8e4e365](https://github.com/standardnotes/server/commit/8e4e36513aa6e3c4f98197adfa75e014920b3572))
# [2.134.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.133.1...@standardnotes/domain-events@2.134.0) (2023-11-10)
### Features
* add graceful shutdown procedures upon SIGTERM ([#923](https://github.com/standardnotes/server/issues/923)) ([c24353c](https://github.com/standardnotes/server/commit/c24353cc24ebf4b40ff9a2cec8e37cfdef109e37))
## [2.133.1](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.133.0...@standardnotes/domain-events@2.133.1) (2023-11-07)
### Bug Fixes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/domain-events",
"version": "2.133.1",
"version": "2.134.2",
"engines": {
"node": ">=18.0.0 <21.0.0"
},
@@ -1,7 +0,0 @@
import { DomainEventInterface } from './DomainEventInterface'
import { EmailSentEventPayload } from './EmailSentEventPayload'
export interface EmailSentEvent extends DomainEventInterface {
type: 'EMAIL_SENT'
payload: EmailSentEventPayload
}
@@ -1,15 +0,0 @@
export interface EmailSentEventPayload {
userEmail: string
messageIdentifier: string
level: string
subject: string
body: string
sender?: string
additionalStyles?: string
attachments?: Array<{
filePath: string
fileName: string
attachmentFileName: string
attachmentContentType: string
}>
}
@@ -1,5 +0,0 @@
import { DomainEventSubscriberInterface } from './DomainEventSubscriberInterface'
export interface DomainEventSubscriberFactoryInterface {
create(): DomainEventSubscriberInterface
}
@@ -1,3 +1,4 @@
export interface DomainEventSubscriberInterface {
start(): void
stop(): void
}
@@ -14,8 +14,6 @@ export * from './Event/EmailBouncedEvent'
export * from './Event/EmailBouncedEventPayload'
export * from './Event/EmailRequestedEvent'
export * from './Event/EmailRequestedEventPayload'
export * from './Event/EmailSentEvent'
export * from './Event/EmailSentEventPayload'
export * from './Event/EmailSubscriptionUnsubscribedEvent'
export * from './Event/EmailSubscriptionUnsubscribedEventPayload'
export * from './Event/ExitDiscountAppliedEvent'
@@ -122,5 +120,4 @@ export * from './Handler/DomainEventMessageHandlerInterface'
export * from './Publisher/DomainEventPublisherInterface'
export * from './Subscriber/DomainEventSubscriberFactoryInterface'
export * from './Subscriber/DomainEventSubscriberInterface'
-15
View File
@@ -1,15 +0,0 @@
LOG_LEVEL=debug
NODE_ENV=development
VERSION=development
DB_HOST=127.0.0.1
DB_REPLICA_HOST=127.0.0.1
DB_PORT=3306
DB_USERNAME=store
DB_PASSWORD=changeme123
DB_DATABASE=store
DB_DEBUG_LEVEL=all # "all" | "query" | "schema" | "error" | "warn" | "info" | "log" | "migration"
DB_MIGRATIONS_PATH=dist/migrations/*.js
SQS_QUEUE_URL=
SQS_AWS_REGION=
-2
View File
@@ -1,2 +0,0 @@
dist
test-setup.ts
-7
View File
@@ -1,7 +0,0 @@
{
"extends": "../../.eslintrc",
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": "./linter.tsconfig.json"
}
}
File diff suppressed because it is too large Load Diff
-17
View File
@@ -1,17 +0,0 @@
FROM node:20.6.1-alpine
RUN apk add --update \
curl \
&& rm -rf /var/cache/apk/*
ENV NODE_ENV production
RUN corepack enable
COPY ./ /workspace
WORKDIR /workspace/packages/event-store
ENTRYPOINT [ "/workspace/packages/event-store/docker/entrypoint.sh" ]
CMD [ "start-worker" ]
-21
View File
@@ -1,21 +0,0 @@
import 'reflect-metadata'
import { Logger } from 'winston'
import { ContainerConfigLoader } from '../src/Bootstrap/Container'
import TYPES from '../src/Bootstrap/Types'
import { Env } from '../src/Bootstrap/Env'
import { DomainEventSubscriberFactoryInterface } from '@standardnotes/domain-events'
const container = new ContainerConfigLoader()
void container.load().then((container) => {
const env: Env = new Env()
env.load()
const logger: Logger = container.get(TYPES.Logger)
logger.info('Starting worker...')
const subscriberFactory: DomainEventSubscriberFactoryInterface = container.get(TYPES.DomainEventSubscriberFactory)
subscriberFactory.create().start()
})
@@ -1,11 +0,0 @@
'use strict'
const path = require('path')
const pnp = require(path.normalize(path.resolve(__dirname, '../../..', '.pnp.cjs'))).setup()
const index = require(path.normalize(path.resolve(__dirname, '../dist/bin/worker.js')))
Object.defineProperty(exports, '__esModule', { value: true })
exports.default = index
-17
View File
@@ -1,17 +0,0 @@
#!/bin/sh
set -e
COMMAND=$1 && shift 1
case "$COMMAND" in
'start-worker' )
echo "Starting Worker..."
node docker/entrypoint-worker.js
;;
* )
echo "Unknown command"
;;
esac
exec "$@"
-12
View File
@@ -1,12 +0,0 @@
// eslint-disable-next-line @typescript-eslint/no-var-requires
const base = require('../../jest.config')
const { defaults: tsjPreset } = require('ts-jest/presets')
module.exports = {
...base,
transform: {
...tsjPreset.transform,
},
coveragePathIgnorePatterns: ['/Bootstrap/'],
setupFilesAfterEnv: ['./test-setup.ts'],
}
@@ -1,4 +0,0 @@
{
"extends": "./tsconfig.json",
"exclude": ["dist", "test-setup.ts"]
}
@@ -1,16 +0,0 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class initDatabase1639394147420 implements MigrationInterface {
name = 'initDatabase1639394147420'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
'CREATE TABLE `events` (`uuid` varchar(36) NOT NULL, `user_identifier` varchar(255) NOT NULL, `user_identifier_type` varchar(255) NOT NULL, `event_type` varchar(255) NOT NULL, `event_payload` text NOT NULL, `timestamp` bigint NOT NULL, INDEX `index_events_on_user_identifier` (`user_identifier`), PRIMARY KEY (`uuid`)) ENGINE=InnoDB',
)
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('DROP INDEX `index_events_on_user_identifier` ON `events`')
await queryRunner.query('DROP TABLE `events`')
}
}
-48
View File
@@ -1,48 +0,0 @@
{
"name": "@standardnotes/event-store",
"version": "1.13.19",
"description": "Event Store Service",
"private": true,
"main": "dist/src/index.js",
"typings": "dist/src/index.d.ts",
"engines": {
"node": ">=18.0.0 <21.0.0"
},
"scripts": {
"clean": "rm -fr dist",
"build": "tsc --build",
"lint": "eslint . --ext .ts",
"pretest": "yarn lint && yarn build",
"test": "jest --coverage --no-cache --config=./jest.config.js --maxWorkers=50%",
"worker": "yarn node dist/bin/worker.js"
},
"author": "Karol Sójko <karol@standardnotes.com>",
"license": "AGPL-3.0-or-later",
"devDependencies": {
"@types/ioredis": "^5.0.0",
"@types/jest": "^29.5.1",
"@types/nodemailer": "^6.4.1",
"@typescript-eslint/eslint-plugin": "^6.5.0",
"@typescript-eslint/parser": "^6.5.0",
"eslint": "^8.39.0",
"eslint-plugin-prettier": "^5.0.0",
"jest": "^29.5.0",
"prettier": "^3.0.3",
"ts-jest": "^29.1.0",
"typescript": "^5.0.4"
},
"dependencies": {
"@aws-sdk/client-sqs": "^3.427.0",
"@standardnotes/domain-core": "workspace:^",
"@standardnotes/domain-events": "workspace:*",
"@standardnotes/domain-events-infra": "workspace:*",
"@standardnotes/time": "workspace:*",
"dotenv": "^16.0.1",
"inversify": "^6.0.1",
"ioredis": "^5.2.4",
"mysql2": "^3.0.1",
"reflect-metadata": "0.1.13",
"typeorm": "^0.3.17",
"winston": "^3.8.1"
}
}
@@ -1,110 +0,0 @@
import * as winston from 'winston'
import { SQSClient, SQSClientConfig } from '@aws-sdk/client-sqs'
import { Container } from 'inversify'
import { Event } from '../Domain/Event/Event'
import { Env } from './Env'
import TYPES from './Types'
import {
DomainEventHandlerInterface,
DomainEventMessageHandlerInterface,
DomainEventSubscriberFactoryInterface,
} from '@standardnotes/domain-events'
import { SQSDomainEventSubscriberFactory, SQSEventMessageHandler } from '@standardnotes/domain-events-infra'
import { Timer, TimerInterface } from '@standardnotes/time'
import { EventHandler } from '../Domain/Handler/EventHandler'
import { AppDataSource } from './DataSource'
import { Repository } from 'typeorm'
export class ContainerConfigLoader {
async load(): Promise<Container> {
const env: Env = new Env()
env.load()
const container = new Container()
await AppDataSource.initialize()
if (env.get('SQS_QUEUE_URL', true)) {
const sqsConfig: SQSClientConfig = {
region: env.get('SQS_AWS_REGION', true),
}
if (env.get('SQS_ENDPOINT', true)) {
sqsConfig.endpoint = env.get('SQS_ENDPOINT', true)
}
if (env.get('SQS_ACCESS_KEY_ID', true) && env.get('SQS_SECRET_ACCESS_KEY', true)) {
sqsConfig.credentials = {
accessKeyId: env.get('SQS_ACCESS_KEY_ID', true),
secretAccessKey: env.get('SQS_SECRET_ACCESS_KEY', true),
}
}
container.bind<SQSClient>(TYPES.SQS).toConstantValue(new SQSClient(sqsConfig))
}
const logger = winston.createLogger({
level: env.get('LOG_LEVEL', true) || 'info',
format: winston.format.combine(winston.format.splat(), winston.format.json()),
transports: [new winston.transports.Console({ level: env.get('LOG_LEVEL', true) || 'info' })],
})
container.bind<winston.Logger>(TYPES.Logger).toConstantValue(logger)
container.bind<TimerInterface>(TYPES.Timer).toConstantValue(new Timer())
// env vars
container.bind(TYPES.SQS_AWS_REGION).toConstantValue(env.get('SQS_AWS_REGION'))
container.bind(TYPES.SQS_QUEUE_URL).toConstantValue(env.get('SQS_QUEUE_URL'))
// ORM
container.bind<Repository<Event>>(TYPES.ORMEventRepository).toConstantValue(AppDataSource.getRepository(Event))
// Handlers
container.bind<EventHandler>(TYPES.EventHandler).to(EventHandler)
const eventHandlers: Map<string, DomainEventHandlerInterface> = new Map([
['USER_REGISTERED', container.get(TYPES.EventHandler)],
['ACCOUNT_DELETION_REQUESTED', container.get(TYPES.EventHandler)],
['SUBSCRIPTION_PURCHASED', container.get(TYPES.EventHandler)],
['SUBSCRIPTION_CANCELLED', container.get(TYPES.EventHandler)],
['SUBSCRIPTION_RENEWED', container.get(TYPES.EventHandler)],
['SUBSCRIPTION_REFUNDED', container.get(TYPES.EventHandler)],
['SUBSCRIPTION_SYNC_REQUESTED', container.get(TYPES.EventHandler)],
['SUBSCRIPTION_EXPIRED', container.get(TYPES.EventHandler)],
['EXTENSION_KEY_GRANTED', container.get(TYPES.EventHandler)],
['SUBSCRIPTION_REASSIGNED', container.get(TYPES.EventHandler)],
['USER_EMAIL_CHANGED', container.get(TYPES.EventHandler)],
['FILE_UPLOADED', container.get(TYPES.EventHandler)],
['FILE_REMOVED', container.get(TYPES.EventHandler)],
['LISTED_ACCOUNT_REQUESTED', container.get(TYPES.EventHandler)],
['LISTED_ACCOUNT_CREATED', container.get(TYPES.EventHandler)],
['LISTED_ACCOUNT_DELETED', container.get(TYPES.EventHandler)],
['EMAIL_REQUESTED', container.get(TYPES.EventHandler)],
['EMAIL_SENT', container.get(TYPES.EventHandler)],
['SHARED_SUBSCRIPTION_INVITATION_CREATED', container.get(TYPES.EventHandler)],
['EMAIL_BACKUP_REQUESTED', container.get(TYPES.EventHandler)],
['PAYMENT_FAILED', container.get(TYPES.EventHandler)],
['PAYMENT_SUCCESS', container.get(TYPES.EventHandler)],
['SUBSCRIPTION_REVERT_REQUESTED', container.get(TYPES.EventHandler)],
['REFUND_PROCESSED', container.get(TYPES.EventHandler)],
['DISCOUNT_APPLY_REQUESTED', container.get(TYPES.EventHandler)],
['DISCOUNT_WITHDRAW_REQUESTED', container.get(TYPES.EventHandler)],
['SUBSCRIPTION_REACTIVATED', container.get(TYPES.EventHandler)],
['EXIT_DISCOUNT_APPLY_REQUESTED', container.get(TYPES.EventHandler)],
['EXIT_DISCOUNT_APPLIED', container.get(TYPES.EventHandler)],
['EXIT_DISCOUNT_WITHDRAW_REQUESTED', container.get(TYPES.EventHandler)],
])
container
.bind<DomainEventMessageHandlerInterface>(TYPES.DomainEventMessageHandler)
.toConstantValue(new SQSEventMessageHandler(eventHandlers, container.get(TYPES.Logger)))
container
.bind<DomainEventSubscriberFactoryInterface>(TYPES.DomainEventSubscriberFactory)
.toConstantValue(
new SQSDomainEventSubscriberFactory(
container.get(TYPES.SQS),
container.get(TYPES.SQS_QUEUE_URL),
container.get(TYPES.DomainEventMessageHandler),
),
)
return container
}
}
@@ -1,51 +0,0 @@
import { DataSource, LoggerOptions } from 'typeorm'
import { Event } from '../Domain/Event/Event'
import { Env } from './Env'
const env: Env = new Env()
env.load()
const maxQueryExecutionTime = env.get('DB_MAX_QUERY_EXECUTION_TIME', true)
? +env.get('DB_MAX_QUERY_EXECUTION_TIME', true)
: 45_000
const inReplicaMode = env.get('DB_REPLICA_HOST', true) ? true : false
const replicationConfig = {
master: {
host: env.get('DB_HOST'),
port: parseInt(env.get('DB_PORT')),
username: env.get('DB_USERNAME'),
password: env.get('DB_PASSWORD'),
database: env.get('DB_DATABASE'),
},
slaves: [
{
host: env.get('DB_REPLICA_HOST', true),
port: parseInt(env.get('DB_PORT')),
username: env.get('DB_USERNAME'),
password: env.get('DB_PASSWORD'),
database: env.get('DB_DATABASE'),
},
],
removeNodeErrorCount: 10,
restoreNodeTimeout: 5,
}
export const AppDataSource = new DataSource({
type: 'mysql',
charset: 'utf8mb4',
supportBigNumbers: true,
bigNumberStrings: false,
maxQueryExecutionTime,
replication: inReplicaMode ? replicationConfig : undefined,
host: inReplicaMode ? undefined : env.get('DB_HOST'),
port: inReplicaMode ? undefined : parseInt(env.get('DB_PORT')),
username: inReplicaMode ? undefined : env.get('DB_USERNAME'),
password: inReplicaMode ? undefined : env.get('DB_PASSWORD'),
database: inReplicaMode ? undefined : env.get('DB_DATABASE'),
entities: [Event],
migrations: [env.get('DB_MIGRATIONS_PATH', true) ?? 'dist/migrations/*.js'],
migrationsRun: true,
logging: <LoggerOptions>env.get('DB_DEBUG_LEVEL'),
})
@@ -1,9 +0,0 @@
import { AbstractEnv } from '@standardnotes/domain-core'
import { config, DotenvParseOutput } from 'dotenv'
export class Env extends AbstractEnv {
load(): void {
const output = config()
this.env = <DotenvParseOutput>output.parsed
}
}
@@ -1,17 +0,0 @@
const TYPES = {
Logger: Symbol.for('Logger'),
SQS: Symbol.for('SQS'),
// env vars
SQS_QUEUE_URL: Symbol.for('SQS_QUEUE_URL'),
SQS_AWS_REGION: Symbol.for('SQS_AWS_REGION'),
// Handlers
DomainEventSubscriberFactory: Symbol.for('DomainEventSubscriberFactory'),
DomainEventMessageHandler: Symbol.for('DomainEventMessageHandler'),
EventHandler: Symbol.for('EventHandler'),
// ORM
ORMEventRepository: Symbol.for('ORMEventRepository'),
// Services
Timer: Symbol.for('Timer'),
}
export default TYPES
@@ -1,38 +0,0 @@
import { Column, Entity, Index, PrimaryGeneratedColumn } from 'typeorm'
@Entity({ name: 'events' })
export class Event {
@PrimaryGeneratedColumn('uuid')
declare uuid: string
@Column({
name: 'user_identifier',
length: 255,
})
@Index('index_events_on_user_identifier')
declare userIdentifier: string
@Column({
name: 'user_identifier_type',
length: 255,
})
declare userIdentifierType: string
@Column({
name: 'event_type',
length: 255,
})
declare eventType: string
@Column({
name: 'event_payload',
type: 'text',
})
declare eventPayload: string
@Column({
name: 'timestamp',
type: 'bigint',
})
declare timestamp: number
}
@@ -1,76 +0,0 @@
import 'reflect-metadata'
import { TimerInterface } from '@standardnotes/time'
import { Repository } from 'typeorm'
import { EventHandler } from './EventHandler'
import { Event } from '../Event/Event'
import { Logger } from 'winston'
import { DomainEventInterface } from '@standardnotes/domain-events'
describe('EventHandler', () => {
let timer: TimerInterface
let repository: Repository<Event>
let logger: Logger
const createHandler = () => new EventHandler(timer, repository, logger)
beforeEach(() => {
timer = {} as jest.Mocked<TimerInterface>
timer.convertStringDateToMicroseconds = jest.fn().mockReturnValue(1)
repository = {} as jest.Mocked<Repository<Event>>
repository.save = jest.fn()
logger = {} as jest.Mocked<Logger>
logger.debug = jest.fn()
logger.error = jest.fn()
})
it('should persist as event in the store', async () => {
const event = {
type: 'test',
createdAt: new Date(2),
meta: {
correlation: {
userIdentifier: '1-2-3',
userIdentifierType: 'uuid',
},
},
payload: {
foo: 'bar',
},
} as jest.Mocked<DomainEventInterface>
await createHandler().handle(event)
expect(repository.save).toHaveBeenCalledWith({
eventType: 'test',
timestamp: 1,
userIdentifier: '1-2-3',
userIdentifierType: 'uuid',
eventPayload: '{"foo":"bar"}',
})
})
it('should inform about failure to saven the event in the store', async () => {
const event = {
type: 'test',
createdAt: new Date(2),
meta: {
correlation: {
userIdentifier: '1-2-3',
userIdentifierType: 'uuid',
},
},
payload: {
foo: 'bar',
},
} as jest.Mocked<DomainEventInterface>
repository.save = jest.fn().mockImplementation(() => {
throw new Error('Ooops')
})
await createHandler().handle(event)
expect(logger.error).toHaveBeenCalledWith('Could not store event %O in the event store: %s', event, 'Ooops')
})
})

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