mirror of
https://github.com/standardnotes/server
synced 2026-01-27 17:01:07 -05:00
Compare commits
31 Commits
@standardn
...
@standardn
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7808cc8ed2 | ||
|
|
5b84f078c6 | ||
|
|
cf5f44a4a5 | ||
|
|
ed05ea553f | ||
|
|
4418c38878 | ||
|
|
6391a01b57 | ||
|
|
9dbcec198d | ||
|
|
78fbeb595f | ||
|
|
d894a87e87 | ||
|
|
4f62cac213 | ||
|
|
ce081274da | ||
|
|
fd997f4849 | ||
|
|
3ddd671c47 | ||
|
|
c19de13cac | ||
|
|
f65809ef30 | ||
|
|
2823ed8612 | ||
|
|
420bf9ec54 | ||
|
|
5f67e5efda | ||
|
|
daed1a77a0 | ||
|
|
b39eb09d91 | ||
|
|
f6ec8626e5 | ||
|
|
97b12f2131 | ||
|
|
8e4e36513a | ||
|
|
c8bf4ab3a0 | ||
|
|
3fa01a328b | ||
|
|
60686dcdbd | ||
|
|
fddd17e531 | ||
|
|
f99750169f | ||
|
|
daad76d0dd | ||
|
|
b3542e2fab | ||
|
|
a9b1543e20 |
7
.github/dependabot.yml
vendored
7
.github/dependabot.yml
vendored
@@ -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
.github/workflows/e2e-self-hosted.yml
vendored
3
.github/workflows/e2e-self-hosted.yml
vendored
@@ -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
|
||||
|
||||
169
.pnp.cjs
generated
169
.pnp.cjs
generated
@@ -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.11", {\
|
||||
"packageLocation": "./.yarn/cache/@grpc-grpc-js-npm-1.9.11-5bb7febd65-71b8517b4f.zip/node_modules/@grpc/grpc-js/",\
|
||||
"packageDependencies": [\
|
||||
["@grpc/grpc-js", "npm:1.9.11"],\
|
||||
["@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.11"],\
|
||||
["@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.11"],\
|
||||
["@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.11"],\
|
||||
["@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/",\
|
||||
@@ -6938,11 +6951,13 @@ const RAW_RUNTIME_STATE =
|
||||
["@aws-sdk/client-s3", "npm:3.427.0"],\
|
||||
["@aws-sdk/client-sns", "npm:3.427.0"],\
|
||||
["@aws-sdk/client-sqs", "npm:3.427.0"],\
|
||||
["@grpc/grpc-js", "npm:1.9.11"],\
|
||||
["@standardnotes/api", "npm:1.26.26"],\
|
||||
["@standardnotes/common", "workspace:packages/common"],\
|
||||
["@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/responses", "npm:1.13.27"],\
|
||||
["@standardnotes/security", "workspace:packages/security"],\
|
||||
["@standardnotes/settings", "workspace:packages/settings"],\
|
||||
@@ -7320,6 +7335,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 +7499,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 +8208,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 +8518,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 +10926,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 +11356,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 +11390,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/",\
|
||||
|
||||
BIN
.yarn/cache/@grpc-grpc-js-npm-1.9.11-5bb7febd65-71b8517b4f.zip
vendored
Normal file
BIN
.yarn/cache/@grpc-grpc-js-npm-1.9.11-5bb7febd65-71b8517b4f.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@mapbox-node-pre-gyp-npm-1.0.11-5547f15a2b-59529a2444.zip
vendored
Normal file
BIN
.yarn/cache/@mapbox-node-pre-gyp-npm-1.0.11-5547f15a2b-59529a2444.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@types-google-protobuf-npm-3.15.10-cbaa6c3e6c-29efde966f.zip
vendored
Normal file
BIN
.yarn/cache/@types-google-protobuf-npm-3.15.10-cbaa6c3e6c-29efde966f.zip
vendored
Normal file
Binary file not shown.
Binary file not shown.
BIN
.yarn/cache/agentkeepalive-npm-4.5.0-f237b580b2-dd210ba2a2.zip
vendored
Normal file
BIN
.yarn/cache/agentkeepalive-npm-4.5.0-f237b580b2-dd210ba2a2.zip
vendored
Normal file
Binary file not shown.
Binary file not shown.
BIN
.yarn/cache/axios-npm-1.6.1-ffaff76449-fb091af3ad.zip
vendored
Normal file
BIN
.yarn/cache/axios-npm-1.6.1-ffaff76449-fb091af3ad.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/google-protobuf-npm-3.15.8-75df975b6c-0b1ea24a55.zip
vendored
Normal file
BIN
.yarn/cache/google-protobuf-npm-3.15.8-75df975b6c-0b1ea24a55.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/google-protobuf-npm-3.21.2-7c82de39ab-b376c2e47f.zip
vendored
Normal file
BIN
.yarn/cache/google-protobuf-npm-3.21.2-7c82de39ab-b376c2e47f.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/grpc-tools-npm-1.12.4-956df6794d-56852c756f.zip
vendored
Normal file
BIN
.yarn/cache/grpc-tools-npm-1.12.4-956df6794d-56852c756f.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/grpc_tools_node_protoc_ts-npm-5.3.3-297a345c26-96fe57b04a.zip
vendored
Normal file
BIN
.yarn/cache/grpc_tools_node_protoc_ts-npm-5.3.3-297a345c26-96fe57b04a.zip
vendored
Normal file
Binary file not shown.
@@ -22,6 +22,7 @@ services:
|
||||
environment:
|
||||
DB_TYPE: "${DB_TYPE}"
|
||||
CACHE_TYPE: "${CACHE_TYPE}"
|
||||
SERVICE_PROXY_TYPE: "${SERVICE_PROXY_TYPE}"
|
||||
container_name: server-ci
|
||||
ports:
|
||||
- 3123:3000
|
||||
|
||||
@@ -14,10 +14,18 @@ if [ -z "$SYNCING_SERVER_PORT" ]; then
|
||||
export SYNCING_SERVER_PORT=3101
|
||||
fi
|
||||
|
||||
if [ -z "$SYNCING_SERVER_GRPC_PORT" ]; then
|
||||
export SYNCING_SERVER_GRPC_PORT=50052
|
||||
fi
|
||||
|
||||
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
|
||||
@@ -352,7 +360,9 @@ export API_GATEWAY_NODE_ENV=production
|
||||
export API_GATEWAY_VERSION=local
|
||||
|
||||
export API_GATEWAY_SYNCING_SERVER_JS_URL=http://localhost:$SYNCING_SERVER_PORT
|
||||
export API_GATEWAY_SYNCING_SERVER_GRPC_URL=0.0.0.0:$SYNCING_SERVER_GRPC_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
|
||||
|
||||
10
package.json
10
package.json
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/analytics",
|
||||
"version": "2.33.0",
|
||||
"version": "2.33.2",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -6,7 +6,9 @@ VERSION=development
|
||||
PORT=3000
|
||||
|
||||
SYNCING_SERVER_JS_URL=http://syncing_server_js:3000
|
||||
SYNCING_SERVER_GRPC_URL=http://syncing_server_js:50052
|
||||
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
|
||||
|
||||
@@ -3,6 +3,86 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [1.86.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.85.1...@standardnotes/api-gateway@1.86.0) (2023-11-20)
|
||||
|
||||
### Features
|
||||
|
||||
* **grpc:** add syncing protocol buffers ([#930](https://github.com/standardnotes/api-gateway/issues/930)) ([5b84f07](https://github.com/standardnotes/api-gateway/commit/5b84f078c6ae6330706895f7c57b67ff8c8d18ae))
|
||||
|
||||
## [1.85.1](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.85.0...@standardnotes/api-gateway@1.85.1) (2023-11-16)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **api-gateway:** remove overly verbose debug messages ([ed05ea5](https://github.com/standardnotes/api-gateway/commit/ed05ea553f605234cd8803e633f3c07429877dbb))
|
||||
|
||||
# [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
|
||||
|
||||
@@ -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,7 +111,7 @@ 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
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/api-gateway",
|
||||
"version": "1.82.0",
|
||||
"version": "1.86.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.11",
|
||||
"@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",
|
||||
|
||||
@@ -1,27 +1,40 @@
|
||||
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 { ServiceContainerInterface } from '@standardnotes/domain-core'
|
||||
import { DirectCallServiceProxy } from '../Service/DirectCall/DirectCallServiceProxy'
|
||||
import { MapperInterface, 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,
|
||||
ISyncingClient,
|
||||
SessionsClient,
|
||||
SyncRequest,
|
||||
SyncResponse,
|
||||
SyncingClient,
|
||||
} from '@standardnotes/grpc'
|
||||
import { GRPCServiceProxy } from '../Service/gRPC/GRPCServiceProxy'
|
||||
import { GRPCSyncingServerServiceProxy } from '../Service/gRPC/GRPCSyncingServerServiceProxy'
|
||||
import { SyncResponseHttpRepresentation } from '../Mapping/Sync/Http/SyncResponseHttpRepresentation'
|
||||
import { SyncRequestGRPCMapper } from '../Mapping/Sync/GRPC/SyncRequestGRPCMapper'
|
||||
import { SyncResponseGRPCMapper } from '../Mapping/Sync/GRPC/SyncResponseGRPCMapper'
|
||||
|
||||
export class ContainerConfigLoader {
|
||||
async load(configuration?: {
|
||||
@@ -70,7 +83,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 +130,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 +143,87 @@ 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'))
|
||||
container.bind(TYPES.ApiGateway_SYNCING_SERVER_GRPC_URL).toConstantValue(env.get('SYNCING_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<ISyncingClient>(TYPES.ApiGateway_GRPCSyncingClient).toConstantValue(
|
||||
new SyncingClient(
|
||||
container.get<string>(TYPES.ApiGateway_SYNCING_SERVER_GRPC_URL),
|
||||
grpc.credentials.createInsecure(),
|
||||
{
|
||||
'grpc.keepalive_time_ms': grpcAgentKeepAliveTimeout * 2,
|
||||
'grpc.keepalive_timeout_ms': grpcAgentKeepAliveTimeout,
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
container
|
||||
.bind<MapperInterface<Record<string, unknown>, SyncRequest>>(TYPES.Mapper_SyncRequestGRPCMapper)
|
||||
.toConstantValue(new SyncRequestGRPCMapper())
|
||||
container
|
||||
.bind<MapperInterface<SyncResponse, SyncResponseHttpRepresentation>>(TYPES.Mapper_SyncResponseGRPCMapper)
|
||||
.toConstantValue(new SyncResponseGRPCMapper())
|
||||
|
||||
container
|
||||
.bind<GRPCSyncingServerServiceProxy>(TYPES.ApiGateway_GRPCSyncingServerServiceProxy)
|
||||
.toConstantValue(
|
||||
new GRPCSyncingServerServiceProxy(
|
||||
container.get<ISyncingClient>(TYPES.ApiGateway_GRPCSyncingClient),
|
||||
container.get<MapperInterface<Record<string, unknown>, SyncRequest>>(TYPES.Mapper_SyncRequestGRPCMapper),
|
||||
container.get<MapperInterface<SyncResponse, SyncResponseHttpRepresentation>>(
|
||||
TYPES.Mapper_SyncResponseGRPCMapper,
|
||||
),
|
||||
),
|
||||
)
|
||||
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),
|
||||
container.get<GRPCSyncingServerServiceProxy>(TYPES.ApiGateway_GRPCSyncingServerServiceProxy),
|
||||
),
|
||||
)
|
||||
} else {
|
||||
container.bind<ServiceProxyInterface>(TYPES.ApiGateway_ServiceProxy).to(HttpServiceProxy)
|
||||
}
|
||||
}
|
||||
|
||||
logger.debug('Configuration complete')
|
||||
|
||||
return container
|
||||
|
||||
@@ -5,6 +5,8 @@ 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_SYNCING_SERVER_GRPC_URL: Symbol.for('ApiGateway_SYNCING_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'),
|
||||
@@ -23,9 +25,15 @@ export const TYPES = {
|
||||
ApiGateway_OptionalCrossServiceTokenMiddleware: Symbol.for('ApiGateway_OptionalCrossServiceTokenMiddleware'),
|
||||
ApiGateway_WebSocketAuthMiddleware: Symbol.for('ApiGateway_WebSocketAuthMiddleware'),
|
||||
ApiGateway_SubscriptionTokenAuthMiddleware: Symbol.for('ApiGateway_SubscriptionTokenAuthMiddleware'),
|
||||
// Mapping
|
||||
Mapper_SyncRequestGRPCMapper: Symbol.for('Mapper_SyncRequestGRPCMapper'),
|
||||
Mapper_SyncResponseGRPCMapper: Symbol.for('Mapper_SyncResponseGRPCMapper'),
|
||||
// Services
|
||||
ApiGateway_GRPCSyncingServerServiceProxy: Symbol.for('ApiGateway_GRPCSyncingServerServiceProxy'),
|
||||
ApiGateway_ServiceProxy: Symbol.for('ApiGateway_ServiceProxy'),
|
||||
ApiGateway_CrossServiceTokenCache: Symbol.for('ApiGateway_CrossServiceTokenCache'),
|
||||
ApiGateway_Timer: Symbol.for('ApiGateway_Timer'),
|
||||
ApiGateway_EndpointResolver: Symbol.for('ApiGateway_EndpointResolver'),
|
||||
ApiGateway_GRPCSessionsClient: Symbol.for('ApiGateway_GRPCSessionsClient'),
|
||||
ApiGateway_GRPCSyncingClient: Symbol.for('ApiGateway_GRPCSyncingClient'),
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
import { MapperInterface, Validator } from '@standardnotes/domain-core'
|
||||
import { ItemHash, SyncRequest } from '@standardnotes/grpc'
|
||||
|
||||
export class SyncRequestGRPCMapper implements MapperInterface<Record<string, unknown>, SyncRequest> {
|
||||
toDomain(_projection: SyncRequest): Record<string, unknown> {
|
||||
throw new Error('Method not implemented.')
|
||||
}
|
||||
|
||||
toProjection(domain: Record<string, unknown>): SyncRequest {
|
||||
const syncRequest = new SyncRequest()
|
||||
if ('items' in domain) {
|
||||
syncRequest.setItemsList((domain.items as Record<string, unknown>[]).map((item) => this.createItemHash(item)))
|
||||
}
|
||||
|
||||
if ('shared_vault_uuids' in domain) {
|
||||
const sharedVaultUuidsValidation = Validator.isNotEmpty(domain.shared_vault_uuids)
|
||||
if (!sharedVaultUuidsValidation.isFailed()) {
|
||||
syncRequest.setSharedVaultUuidsList(domain.shared_vault_uuids as string[])
|
||||
}
|
||||
}
|
||||
|
||||
if ('compute_integrity' in domain) {
|
||||
syncRequest.setComputeIntegrity(!!domain.compute_integrity)
|
||||
}
|
||||
|
||||
if ('sync_token' in domain) {
|
||||
syncRequest.setSyncToken(domain.sync_token as string)
|
||||
}
|
||||
|
||||
if ('cursor_token' in domain) {
|
||||
syncRequest.setCursorToken(domain.cursor_token as string)
|
||||
}
|
||||
|
||||
if ('limit' in domain) {
|
||||
syncRequest.setLimit(domain.limit as number)
|
||||
}
|
||||
|
||||
if ('content_type' in domain) {
|
||||
syncRequest.setContentType(domain.content_type as string)
|
||||
}
|
||||
|
||||
if ('api' in domain) {
|
||||
syncRequest.setApiVersion(domain.api as string)
|
||||
}
|
||||
|
||||
return syncRequest
|
||||
}
|
||||
|
||||
private createItemHash(record: Record<string, unknown>): ItemHash {
|
||||
const itemHash = new ItemHash()
|
||||
itemHash.setUuid(record.uuid as string)
|
||||
if (record.content) {
|
||||
itemHash.setContent(record.content as string)
|
||||
}
|
||||
if (record.content_type) {
|
||||
itemHash.setContentType(record.content_type as string)
|
||||
}
|
||||
if (record.deleted !== undefined) {
|
||||
itemHash.setDeleted(!!record.deleted)
|
||||
}
|
||||
if (record.duplicate_of) {
|
||||
itemHash.setDuplicateOf(record.duplicate_of as string)
|
||||
}
|
||||
if (record.auth_hash) {
|
||||
itemHash.setAuthHash(record.auth_hash as string)
|
||||
}
|
||||
if (record.enc_item_key) {
|
||||
itemHash.setEncItemKey(record.enc_item_key as string)
|
||||
}
|
||||
if (record.items_key_id) {
|
||||
itemHash.setItemsKeyId(record.items_key_id as string)
|
||||
}
|
||||
if (record.key_system_identifier) {
|
||||
itemHash.setKeySystemIdentifier(record.key_system_identifier as string)
|
||||
}
|
||||
if (record.shared_vault_uuid) {
|
||||
itemHash.setSharedVaultUuid(record.shared_vault_uuid as string)
|
||||
}
|
||||
if (record.created_at) {
|
||||
itemHash.setCreatedAt(record.created_at as string)
|
||||
}
|
||||
if (record.created_at_timestamp) {
|
||||
itemHash.setCreatedAtTimestamp(record.created_at_timestamp as number)
|
||||
}
|
||||
if (record.updated_at) {
|
||||
itemHash.setUpdatedAt(record.updated_at as string)
|
||||
}
|
||||
if (record.updated_at_timestamp) {
|
||||
itemHash.setUpdatedAtTimestamp(record.updated_at_timestamp as number)
|
||||
}
|
||||
|
||||
return itemHash
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
import { MapperInterface } from '@standardnotes/domain-core'
|
||||
import {
|
||||
ItemConflictRepresentation,
|
||||
ItemHashRepresentation,
|
||||
ItemRepresentation,
|
||||
MessageRepresentation,
|
||||
NotificationRepresentation,
|
||||
SavedItemRepresentation,
|
||||
SharedVaultInviteRepresentation,
|
||||
SharedVaultRepresentation,
|
||||
SyncResponse,
|
||||
} from '@standardnotes/grpc'
|
||||
|
||||
import { ItemHttpRepresentation } from '../Http/ItemHttpRepresentation'
|
||||
import { SavedItemHttpRepresentation } from '../Http/SavedItemHttpRepresentation'
|
||||
import { ItemConflictHttpRepresentation } from '../Http/ItemConflictHttpRepresentation'
|
||||
import { ItemHashHttpRepresentation } from '../Http/ItemHashHttpRepresentation'
|
||||
import { MessageHttpRepresentation } from '../Http/MessageHttpRepresentation'
|
||||
import { SharedVaultHttpRepresentation } from '../Http/SharedVaultHttpRepresentation'
|
||||
import { SharedVaultInviteHttpRepresentation } from '../Http/SharedVaultInviteHttpRepresentation'
|
||||
import { NotificationHttpRepresentation } from '../Http/NotificationHttpRepresentation'
|
||||
import { SyncResponseHttpRepresentation } from '../Http/SyncResponseHttpRepresentation'
|
||||
|
||||
export class SyncResponseGRPCMapper implements MapperInterface<SyncResponse, SyncResponseHttpRepresentation> {
|
||||
toDomain(_projection: SyncResponseHttpRepresentation): SyncResponse {
|
||||
throw new Error('Method not implemented.')
|
||||
}
|
||||
|
||||
toProjection(domain: SyncResponse): SyncResponseHttpRepresentation {
|
||||
return {
|
||||
retrieved_items: domain.getRetrievedItemsList().map((item) => this.createItem(item)),
|
||||
saved_items: domain.getSavedItemsList().map((item) => this.createSavedItem(item)),
|
||||
conflicts: domain.getConflictsList().map((conflict) => this.createConflict(conflict)),
|
||||
sync_token: domain.getSyncToken(),
|
||||
cursor_token: domain.getCursorToken(),
|
||||
messages: domain.getMessagesList().map((message) => this.createMessage(message)),
|
||||
shared_vaults: domain.getSharedVaultsList().map((sharedVault) => this.createSharedVault(sharedVault)),
|
||||
shared_vault_invites: domain
|
||||
.getSharedVaultInvitesList()
|
||||
.map((sharedVaultInvite) => this.createSharedVaultInvite(sharedVaultInvite)),
|
||||
notifications: domain.getNotificationsList().map((notification) => this.createNotification(notification)),
|
||||
}
|
||||
}
|
||||
|
||||
private createNotification(notification: NotificationRepresentation): NotificationHttpRepresentation {
|
||||
return {
|
||||
uuid: notification.getUuid(),
|
||||
user_uuid: notification.getUserUuid(),
|
||||
type: notification.getType(),
|
||||
payload: notification.getPayload(),
|
||||
created_at_timestamp: notification.getCreatedAtTimestamp(),
|
||||
updated_at_timestamp: notification.getUpdatedAtTimestamp(),
|
||||
}
|
||||
}
|
||||
|
||||
private createSharedVaultInvite(
|
||||
sharedVaultInvite: SharedVaultInviteRepresentation,
|
||||
): SharedVaultInviteHttpRepresentation {
|
||||
return {
|
||||
uuid: sharedVaultInvite.getUuid(),
|
||||
shared_vault_uuid: sharedVaultInvite.getSharedVaultUuid(),
|
||||
user_uuid: sharedVaultInvite.getUserUuid(),
|
||||
sender_uuid: sharedVaultInvite.getSenderUuid(),
|
||||
encrypted_message: sharedVaultInvite.getEncryptedMessage(),
|
||||
permission: sharedVaultInvite.getPermission(),
|
||||
created_at_timestamp: sharedVaultInvite.getCreatedAtTimestamp(),
|
||||
updated_at_timestamp: sharedVaultInvite.getUpdatedAtTimestamp(),
|
||||
}
|
||||
}
|
||||
|
||||
private createSharedVault(sharedVault: SharedVaultRepresentation): SharedVaultHttpRepresentation {
|
||||
return {
|
||||
uuid: sharedVault.getUuid(),
|
||||
user_uuid: sharedVault.getUserUuid(),
|
||||
file_upload_bytes_used: sharedVault.getFileUploadBytesUsed(),
|
||||
created_at_timestamp: sharedVault.getCreatedAtTimestamp(),
|
||||
updated_at_timestamp: sharedVault.getUpdatedAtTimestamp(),
|
||||
}
|
||||
}
|
||||
|
||||
private createMessage(message: MessageRepresentation): MessageHttpRepresentation {
|
||||
return {
|
||||
uuid: message.getUuid(),
|
||||
recipient_uuid: message.getRecipientUuid(),
|
||||
sender_uuid: message.getSenderUuid(),
|
||||
encrypted_message: message.getEncryptedMessage(),
|
||||
replaceability_identifier: message.getReplaceabilityIdentifier() ?? null,
|
||||
created_at_timestamp: message.getCreatedAtTimestamp(),
|
||||
updated_at_timestamp: message.getUpdatedAtTimestamp(),
|
||||
}
|
||||
}
|
||||
|
||||
private createConflict(conflict: ItemConflictRepresentation): ItemConflictHttpRepresentation {
|
||||
return {
|
||||
server_item: conflict.hasServerItem()
|
||||
? this.createItem(conflict.getServerItem() as ItemRepresentation)
|
||||
: undefined,
|
||||
unsaved_item: conflict.hasUnsavedItem()
|
||||
? this.createItemHash(conflict.getUnsavedItem() as ItemHashRepresentation)
|
||||
: undefined,
|
||||
type: conflict.getType(),
|
||||
}
|
||||
}
|
||||
|
||||
private createItemHash(itemHash: ItemHashRepresentation): ItemHashHttpRepresentation {
|
||||
return {
|
||||
uuid: itemHash.getUuid(),
|
||||
user_uuid: itemHash.getUserUuid(),
|
||||
content: itemHash.hasContent() ? (itemHash.getContent() as string) : undefined,
|
||||
content_type: itemHash.hasContentType() ? (itemHash.getContentType() as string) : null,
|
||||
deleted: itemHash.hasDeleted() ? (itemHash.getDeleted() as boolean) : false,
|
||||
duplicate_of: itemHash.hasDuplicateOf() ? (itemHash.getDuplicateOf() as string) : null,
|
||||
auth_hash: itemHash.hasAuthHash() ? (itemHash.getAuthHash() as string) : undefined,
|
||||
enc_item_key: itemHash.hasEncItemKey() ? (itemHash.getEncItemKey() as string) : undefined,
|
||||
items_key_id: itemHash.hasItemsKeyId() ? (itemHash.getItemsKeyId() as string) : undefined,
|
||||
key_system_identifier: itemHash.hasKeySystemIdentifier() ? (itemHash.getKeySystemIdentifier() as string) : null,
|
||||
shared_vault_uuid: itemHash.hasSharedVaultUuid() ? (itemHash.getSharedVaultUuid() as string) : null,
|
||||
created_at: itemHash.hasCreatedAt() ? (itemHash.getCreatedAt() as string) : undefined,
|
||||
created_at_timestamp: itemHash.hasCreatedAtTimestamp() ? (itemHash.getCreatedAtTimestamp() as number) : undefined,
|
||||
updated_at: itemHash.hasUpdatedAt() ? (itemHash.getUpdatedAt() as string) : undefined,
|
||||
updated_at_timestamp: itemHash.hasUpdatedAtTimestamp() ? (itemHash.getUpdatedAtTimestamp() as number) : undefined,
|
||||
}
|
||||
}
|
||||
|
||||
private createSavedItem(item: SavedItemRepresentation): SavedItemHttpRepresentation {
|
||||
return {
|
||||
uuid: item.getUuid(),
|
||||
duplicate_of: item.hasDuplicateOf() ? (item.getDuplicateOf() as string) : null,
|
||||
content_type: item.getContentType(),
|
||||
auth_hash: item.hasAuthHash() ? (item.getAuthHash() as string) : null,
|
||||
deleted: item.getDeleted(),
|
||||
created_at: item.getCreatedAt(),
|
||||
created_at_timestamp: item.getCreatedAtTimestamp(),
|
||||
updated_at: item.getUpdatedAt(),
|
||||
updated_at_timestamp: item.getUpdatedAtTimestamp(),
|
||||
key_system_identifier: item.hasKeySystemIdentifier() ? (item.getKeySystemIdentifier() as string) : null,
|
||||
shared_vault_uuid: item.hasSharedVaultUuid() ? (item.getSharedVaultUuid() as string) : null,
|
||||
user_uuid: item.hasUserUuid() ? (item.getUserUuid() as string) : null,
|
||||
last_edited_by_uuid: item.hasLastEditedByUuid() ? (item.getLastEditedByUuid() as string) : null,
|
||||
}
|
||||
}
|
||||
|
||||
private createItem(item: ItemRepresentation): ItemHttpRepresentation {
|
||||
return {
|
||||
uuid: item.getUuid(),
|
||||
items_key_id: item.hasItemsKeyId() ? (item.getItemsKeyId() as string) : null,
|
||||
duplicate_of: item.hasDuplicateOf() ? (item.getDuplicateOf() as string) : null,
|
||||
enc_item_key: item.hasEncItemKey() ? (item.getEncItemKey() as string) : null,
|
||||
content: item.hasContent() ? (item.getContent() as string) : null,
|
||||
content_type: item.getContentType(),
|
||||
auth_hash: item.hasAuthHash() ? (item.getAuthHash() as string) : null,
|
||||
deleted: item.getDeleted(),
|
||||
created_at: item.getCreatedAt(),
|
||||
created_at_timestamp: item.getCreatedAtTimestamp(),
|
||||
updated_at: item.getUpdatedAt(),
|
||||
updated_at_timestamp: item.getUpdatedAtTimestamp(),
|
||||
updated_with_session: item.hasUpdatedWithSession() ? (item.getUpdatedWithSession() as string) : null,
|
||||
key_system_identifier: item.hasKeySystemIdentifier() ? (item.getKeySystemIdentifier() as string) : null,
|
||||
shared_vault_uuid: item.hasSharedVaultUuid() ? (item.getSharedVaultUuid() as string) : null,
|
||||
user_uuid: item.hasUserUuid() ? (item.getUserUuid() as string) : null,
|
||||
last_edited_by_uuid: item.hasLastEditedByUuid() ? (item.getLastEditedByUuid() as string) : null,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { ItemHttpRepresentation } from './ItemHttpRepresentation'
|
||||
import { ItemHashHttpRepresentation } from './ItemHashHttpRepresentation'
|
||||
|
||||
export interface ItemConflictHttpRepresentation {
|
||||
server_item?: ItemHttpRepresentation
|
||||
unsaved_item?: ItemHashHttpRepresentation
|
||||
type: string
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
export interface ItemHashHttpRepresentation {
|
||||
uuid: string
|
||||
user_uuid: string
|
||||
content?: string
|
||||
content_type: string | null
|
||||
deleted?: boolean
|
||||
duplicate_of?: string | null
|
||||
auth_hash?: string
|
||||
enc_item_key?: string
|
||||
items_key_id?: string
|
||||
key_system_identifier: string | null
|
||||
shared_vault_uuid: string | null
|
||||
created_at?: string
|
||||
created_at_timestamp?: number
|
||||
updated_at?: string
|
||||
updated_at_timestamp?: number
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
export interface ItemHttpRepresentation {
|
||||
uuid: string
|
||||
items_key_id: string | null
|
||||
duplicate_of: string | null
|
||||
enc_item_key: string | null
|
||||
content: string | null
|
||||
content_type: string
|
||||
auth_hash: string | null
|
||||
deleted: boolean
|
||||
created_at: string
|
||||
created_at_timestamp: number
|
||||
updated_at: string
|
||||
updated_at_timestamp: number
|
||||
updated_with_session: string | null
|
||||
key_system_identifier: string | null
|
||||
shared_vault_uuid: string | null
|
||||
user_uuid: string | null
|
||||
last_edited_by_uuid: string | null
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
export interface MessageHttpRepresentation {
|
||||
uuid: string
|
||||
recipient_uuid: string
|
||||
sender_uuid: string
|
||||
encrypted_message: string
|
||||
replaceability_identifier: string | null
|
||||
created_at_timestamp: number
|
||||
updated_at_timestamp: number
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
export interface NotificationHttpRepresentation {
|
||||
uuid: string
|
||||
user_uuid: string
|
||||
type: string
|
||||
payload: string
|
||||
created_at_timestamp: number
|
||||
updated_at_timestamp: number
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
export interface SavedItemHttpRepresentation {
|
||||
uuid: string
|
||||
duplicate_of: string | null
|
||||
content_type: string
|
||||
auth_hash: string | null
|
||||
deleted: boolean
|
||||
created_at: string
|
||||
created_at_timestamp: number
|
||||
updated_at: string
|
||||
updated_at_timestamp: number
|
||||
key_system_identifier: string | null
|
||||
shared_vault_uuid: string | null
|
||||
user_uuid: string | null
|
||||
last_edited_by_uuid: string | null
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
export interface SharedVaultHttpRepresentation {
|
||||
uuid: string
|
||||
user_uuid: string
|
||||
file_upload_bytes_used: number
|
||||
created_at_timestamp: number
|
||||
updated_at_timestamp: number
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
export interface SharedVaultInviteHttpRepresentation {
|
||||
uuid: string
|
||||
shared_vault_uuid: string
|
||||
user_uuid: string
|
||||
sender_uuid: string
|
||||
encrypted_message: string
|
||||
permission: string
|
||||
created_at_timestamp: number
|
||||
updated_at_timestamp: number
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { ItemConflictHttpRepresentation } from './ItemConflictHttpRepresentation'
|
||||
import { ItemHttpRepresentation } from './ItemHttpRepresentation'
|
||||
import { MessageHttpRepresentation } from './MessageHttpRepresentation'
|
||||
import { NotificationHttpRepresentation } from './NotificationHttpRepresentation'
|
||||
import { SavedItemHttpRepresentation } from './SavedItemHttpRepresentation'
|
||||
import { SharedVaultHttpRepresentation } from './SharedVaultHttpRepresentation'
|
||||
import { SharedVaultInviteHttpRepresentation } from './SharedVaultInviteHttpRepresentation'
|
||||
|
||||
export type SyncResponseHttpRepresentation = {
|
||||
retrieved_items: ItemHttpRepresentation[]
|
||||
saved_items: SavedItemHttpRepresentation[]
|
||||
conflicts: ItemConflictHttpRepresentation[]
|
||||
sync_token: string
|
||||
cursor_token?: string
|
||||
messages: MessageHttpRepresentation[]
|
||||
shared_vaults: SharedVaultHttpRepresentation[]
|
||||
shared_vault_invites: SharedVaultInviteHttpRepresentation[]
|
||||
notifications: NotificationHttpRepresentation[]
|
||||
}
|
||||
@@ -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(
|
||||
@@ -42,7 +42,7 @@ export class DirectCallServiceProxy implements ServiceProxyInterface {
|
||||
}
|
||||
}
|
||||
|
||||
async callEmailServer(_request: Request, response: Response, _endpointOrMethodIdentifier: string): Promise<void> {
|
||||
async callEmailServer(_request: Request, response: Response, _methodIdentifier: string): Promise<void> {
|
||||
response.status(400).send({
|
||||
error: {
|
||||
message: 'Email server is not available.',
|
||||
@@ -50,13 +50,13 @@ export class DirectCallServiceProxy implements ServiceProxyInterface {
|
||||
})
|
||||
}
|
||||
|
||||
async callAuthServer(request: never, response: never, endpointOrMethodIdentifier: string): Promise<void> {
|
||||
async callAuthServer(request: never, response: never, methodIdentifier: string): Promise<void> {
|
||||
const authService = this.serviceContainer.get(ServiceIdentifier.create(ServiceIdentifier.NAMES.Auth).getValue())
|
||||
if (!authService) {
|
||||
throw new Error('Auth service not found')
|
||||
}
|
||||
|
||||
const serviceResponse = (await authService.handleRequest(request, response, endpointOrMethodIdentifier)) as {
|
||||
const serviceResponse = (await authService.handleRequest(request, response, methodIdentifier)) as {
|
||||
statusCode: number
|
||||
json: Record<string, unknown>
|
||||
}
|
||||
@@ -67,7 +67,7 @@ export class DirectCallServiceProxy implements ServiceProxyInterface {
|
||||
async callAuthServerWithLegacyFormat(
|
||||
_request: Request,
|
||||
response: Response,
|
||||
_endpointOrMethodIdentifier: string,
|
||||
_methodIdentifier: string,
|
||||
): Promise<void> {
|
||||
response.status(400).send({
|
||||
error: {
|
||||
@@ -76,13 +76,13 @@ export class DirectCallServiceProxy implements ServiceProxyInterface {
|
||||
})
|
||||
}
|
||||
|
||||
async callRevisionsServer(request: never, response: never, endpointOrMethodIdentifier: string): Promise<void> {
|
||||
async callRevisionsServer(request: never, response: never, methodIdentifier: string): Promise<void> {
|
||||
const service = this.serviceContainer.get(ServiceIdentifier.create(ServiceIdentifier.NAMES.Revisions).getValue())
|
||||
if (!service) {
|
||||
throw new Error('Revisions service not found')
|
||||
}
|
||||
|
||||
const serviceResponse = (await service.handleRequest(request, response, endpointOrMethodIdentifier)) as {
|
||||
const serviceResponse = (await service.handleRequest(request, response, methodIdentifier)) as {
|
||||
statusCode: number
|
||||
json: Record<string, unknown>
|
||||
}
|
||||
@@ -90,7 +90,7 @@ export class DirectCallServiceProxy implements ServiceProxyInterface {
|
||||
this.sendDecoratedResponse(response, serviceResponse)
|
||||
}
|
||||
|
||||
async callSyncingServer(request: never, response: never, endpointOrMethodIdentifier: string): Promise<void> {
|
||||
async callSyncingServer(request: never, response: never, methodIdentifier: string): Promise<void> {
|
||||
const service = this.serviceContainer.get(
|
||||
ServiceIdentifier.create(ServiceIdentifier.NAMES.SyncingServer).getValue(),
|
||||
)
|
||||
@@ -98,7 +98,7 @@ export class DirectCallServiceProxy implements ServiceProxyInterface {
|
||||
throw new Error('Syncing service not found')
|
||||
}
|
||||
|
||||
const serviceResponse = (await service.handleRequest(request, response, endpointOrMethodIdentifier)) as {
|
||||
const serviceResponse = (await service.handleRequest(request, response, methodIdentifier)) as {
|
||||
statusCode: number
|
||||
json: Record<string, unknown>
|
||||
}
|
||||
@@ -106,11 +106,7 @@ export class DirectCallServiceProxy implements ServiceProxyInterface {
|
||||
this.sendDecoratedResponse(response, serviceResponse)
|
||||
}
|
||||
|
||||
async callLegacySyncingServer(
|
||||
_request: Request,
|
||||
response: Response,
|
||||
_endpointOrMethodIdentifier: string,
|
||||
): Promise<void> {
|
||||
async callLegacySyncingServer(_request: Request, response: Response, _methodIdentifier: string): Promise<void> {
|
||||
response.status(400).send({
|
||||
error: {
|
||||
message: 'Legacy syncing server endpoints are no longer available.',
|
||||
@@ -118,7 +114,7 @@ export class DirectCallServiceProxy implements ServiceProxyInterface {
|
||||
})
|
||||
}
|
||||
|
||||
async callPaymentsServer(_request: Request, response: Response, _endpointOrMethodIdentifier: string): Promise<void> {
|
||||
async callPaymentsServer(_request: Request, response: Response, _methodIdentifier: string): Promise<void> {
|
||||
response.status(400).send({
|
||||
error: {
|
||||
message: 'Payments server is not available.',
|
||||
@@ -126,7 +122,7 @@ export class DirectCallServiceProxy implements ServiceProxyInterface {
|
||||
})
|
||||
}
|
||||
|
||||
async callWebSocketServer(_request: Request, response: Response, _endpointOrMethodIdentifier: string): Promise<void> {
|
||||
async callWebSocketServer(_request: Request, response: Response, _methodIdentifier: string): Promise<void> {
|
||||
response.status(400).send({
|
||||
error: {
|
||||
message: 'Websockets server is not available.',
|
||||
@@ -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()
|
||||
@@ -72,16 +72,16 @@ export class HttpServiceProxy implements ServiceProxyInterface {
|
||||
async callSyncingServer(
|
||||
request: Request,
|
||||
response: Response,
|
||||
endpointOrMethodIdentifier: string,
|
||||
endpoint: string,
|
||||
payload?: Record<string, unknown> | string,
|
||||
): Promise<void> {
|
||||
await this.callServer(this.syncingServerJsUrl, request, response, endpointOrMethodIdentifier, payload)
|
||||
await this.callServer(this.syncingServerJsUrl, request, response, endpoint, payload)
|
||||
}
|
||||
|
||||
async callRevisionsServer(
|
||||
request: Request,
|
||||
response: Response,
|
||||
endpointOrMethodIdentifier: string,
|
||||
endpoint: string,
|
||||
payload?: Record<string, unknown> | string,
|
||||
): Promise<void> {
|
||||
if (!this.revisionsServerUrl) {
|
||||
@@ -89,37 +89,31 @@ export class HttpServiceProxy implements ServiceProxyInterface {
|
||||
|
||||
return
|
||||
}
|
||||
await this.callServer(this.revisionsServerUrl, request, response, endpointOrMethodIdentifier, payload)
|
||||
await this.callServer(this.revisionsServerUrl, request, response, endpoint, payload)
|
||||
}
|
||||
|
||||
async callLegacySyncingServer(
|
||||
request: Request,
|
||||
response: Response,
|
||||
endpointOrMethodIdentifier: string,
|
||||
endpoint: string,
|
||||
payload?: Record<string, unknown> | string,
|
||||
): Promise<void> {
|
||||
await this.callServerWithLegacyFormat(
|
||||
this.syncingServerJsUrl,
|
||||
request,
|
||||
response,
|
||||
endpointOrMethodIdentifier,
|
||||
payload,
|
||||
)
|
||||
await this.callServerWithLegacyFormat(this.syncingServerJsUrl, request, response, endpoint, payload)
|
||||
}
|
||||
|
||||
async callAuthServer(
|
||||
request: Request,
|
||||
response: Response,
|
||||
endpointOrMethodIdentifier: string,
|
||||
endpoint: string,
|
||||
payload?: Record<string, unknown> | string,
|
||||
): Promise<void> {
|
||||
await this.callServer(this.authServerUrl, request, response, endpointOrMethodIdentifier, payload)
|
||||
await this.callServer(this.authServerUrl, request, response, endpoint, payload)
|
||||
}
|
||||
|
||||
async callEmailServer(
|
||||
request: Request,
|
||||
response: Response,
|
||||
endpointOrMethodIdentifier: string,
|
||||
endpoint: string,
|
||||
payload?: Record<string, unknown> | string,
|
||||
): Promise<void> {
|
||||
if (!this.emailServerUrl) {
|
||||
@@ -128,13 +122,13 @@ export class HttpServiceProxy implements ServiceProxyInterface {
|
||||
return
|
||||
}
|
||||
|
||||
await this.callServer(this.emailServerUrl, request, response, endpointOrMethodIdentifier, payload)
|
||||
await this.callServer(this.emailServerUrl, request, response, endpoint, payload)
|
||||
}
|
||||
|
||||
async callWebSocketServer(
|
||||
request: Request,
|
||||
response: Response,
|
||||
endpointOrMethodIdentifier: string,
|
||||
endpoint: string,
|
||||
payload?: Record<string, unknown> | string,
|
||||
): Promise<void> {
|
||||
if (!this.webSocketServerUrl) {
|
||||
@@ -144,26 +138,17 @@ export class HttpServiceProxy implements ServiceProxyInterface {
|
||||
}
|
||||
|
||||
const isARequestComingFromApiGatewayAndShouldBeKeptInMinimalFormat = request.headers.connectionid !== undefined
|
||||
this.logger.info(
|
||||
`Calling websockets service: ${endpointOrMethodIdentifier}. Format is minimal: ${isARequestComingFromApiGatewayAndShouldBeKeptInMinimalFormat}`,
|
||||
)
|
||||
if (isARequestComingFromApiGatewayAndShouldBeKeptInMinimalFormat) {
|
||||
await this.callServerWithLegacyFormat(
|
||||
this.webSocketServerUrl,
|
||||
request,
|
||||
response,
|
||||
endpointOrMethodIdentifier,
|
||||
payload,
|
||||
)
|
||||
await this.callServerWithLegacyFormat(this.webSocketServerUrl, request, response, endpoint, payload)
|
||||
} else {
|
||||
await this.callServer(this.webSocketServerUrl, request, response, endpointOrMethodIdentifier, payload)
|
||||
await this.callServer(this.webSocketServerUrl, request, response, endpoint, payload)
|
||||
}
|
||||
}
|
||||
|
||||
async callPaymentsServer(
|
||||
request: Request,
|
||||
response: Response,
|
||||
endpointOrMethodIdentifier: string,
|
||||
endpoint: string,
|
||||
payload?: Record<string, unknown> | string,
|
||||
): Promise<void | Response<unknown, Record<string, unknown>>> {
|
||||
if (!this.paymentsServerUrl) {
|
||||
@@ -172,29 +157,23 @@ export class HttpServiceProxy implements ServiceProxyInterface {
|
||||
return
|
||||
}
|
||||
|
||||
await this.callServerWithLegacyFormat(
|
||||
this.paymentsServerUrl,
|
||||
request,
|
||||
response,
|
||||
endpointOrMethodIdentifier,
|
||||
payload,
|
||||
)
|
||||
await this.callServerWithLegacyFormat(this.paymentsServerUrl, request, response, endpoint, payload)
|
||||
}
|
||||
|
||||
async callAuthServerWithLegacyFormat(
|
||||
request: Request,
|
||||
response: Response,
|
||||
endpointOrMethodIdentifier: string,
|
||||
endpoint: string,
|
||||
payload?: Record<string, unknown> | string,
|
||||
): Promise<void> {
|
||||
await this.callServerWithLegacyFormat(this.authServerUrl, request, response, endpointOrMethodIdentifier, payload)
|
||||
await this.callServerWithLegacyFormat(this.authServerUrl, request, response, endpoint, payload)
|
||||
}
|
||||
|
||||
private async getServerResponse(
|
||||
serverUrl: string,
|
||||
request: Request,
|
||||
response: Response,
|
||||
endpointOrMethodIdentifier: string,
|
||||
endpoint: string,
|
||||
payload?: Record<string, unknown> | string,
|
||||
retryAttempt?: number,
|
||||
): Promise<AxiosResponse | undefined> {
|
||||
@@ -215,15 +194,10 @@ export class HttpServiceProxy implements ServiceProxyInterface {
|
||||
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}`,
|
||||
url: `${serverUrl}/${endpoint}`,
|
||||
data: this.getRequestData(payload),
|
||||
maxContentLength: Infinity,
|
||||
maxBodyLength: Infinity,
|
||||
@@ -240,9 +214,7 @@ export class HttpServiceProxy implements ServiceProxyInterface {
|
||||
}
|
||||
|
||||
if (retryAttempt) {
|
||||
this.logger.debug(
|
||||
`Request to ${serverUrl}/${endpointOrMethodIdentifier} succeeded after ${retryAttempt} retries`,
|
||||
)
|
||||
this.logger.debug(`Request to ${serverUrl}/${endpoint} succeeded after ${retryAttempt} retries`)
|
||||
}
|
||||
|
||||
return serviceResponse
|
||||
@@ -254,18 +226,9 @@ export class HttpServiceProxy implements ServiceProxyInterface {
|
||||
|
||||
const nextRetryAttempt = retryAttempt ? retryAttempt + 1 : 1
|
||||
|
||||
this.logger.debug(
|
||||
`Retrying request to ${serverUrl}/${endpointOrMethodIdentifier} for the ${nextRetryAttempt} time`,
|
||||
)
|
||||
this.logger.debug(`Retrying request to ${serverUrl}/${endpoint} for the ${nextRetryAttempt} time`)
|
||||
|
||||
return this.getServerResponse(
|
||||
serverUrl,
|
||||
request,
|
||||
response,
|
||||
endpointOrMethodIdentifier,
|
||||
payload,
|
||||
nextRetryAttempt,
|
||||
)
|
||||
return this.getServerResponse(serverUrl, request, response, endpoint, payload, nextRetryAttempt)
|
||||
}
|
||||
|
||||
let detailedErrorMessage = (error as Error).message
|
||||
@@ -275,8 +238,8 @@ export class HttpServiceProxy implements ServiceProxyInterface {
|
||||
|
||||
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}`,
|
||||
? `Request to ${serverUrl}/${endpoint} timed out after ${retryAttempt} retries`
|
||||
: `Could not pass the request to ${serverUrl}/${endpoint} on underlying service: ${detailedErrorMessage}`,
|
||||
)
|
||||
|
||||
this.logger.debug(`Response error: ${JSON.stringify(error)}`)
|
||||
@@ -307,19 +270,10 @@ export class HttpServiceProxy implements ServiceProxyInterface {
|
||||
serverUrl: string,
|
||||
request: Request,
|
||||
response: Response,
|
||||
endpointOrMethodIdentifier: string,
|
||||
endpoint: 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)}`)
|
||||
const serviceResponse = await this.getServerResponse(serverUrl, request, response, endpoint, payload)
|
||||
|
||||
if (!serviceResponse) {
|
||||
return
|
||||
@@ -351,16 +305,10 @@ export class HttpServiceProxy implements ServiceProxyInterface {
|
||||
serverUrl: string,
|
||||
request: Request,
|
||||
response: Response,
|
||||
endpointOrMethodIdentifier: string,
|
||||
endpoint: string,
|
||||
payload?: Record<string, unknown> | string,
|
||||
): Promise<void | Response<unknown, Record<string, unknown>>> {
|
||||
const serviceResponse = await this.getServerResponse(
|
||||
serverUrl,
|
||||
request,
|
||||
response,
|
||||
endpointOrMethodIdentifier,
|
||||
payload,
|
||||
)
|
||||
const serviceResponse = await this.getServerResponse(serverUrl, request, response, endpoint, payload)
|
||||
|
||||
if (!serviceResponse) {
|
||||
return
|
||||
|
||||
400
packages/api-gateway/src/Service/gRPC/GRPCServiceProxy.ts
Normal file
400
packages/api-gateway/src/Service/gRPC/GRPCServiceProxy.ts
Normal file
@@ -0,0 +1,400 @@
|
||||
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'
|
||||
import { GRPCSyncingServerServiceProxy } from './GRPCSyncingServerServiceProxy'
|
||||
|
||||
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,
|
||||
private gRPCSyncingServerServiceProxy: GRPCSyncingServerServiceProxy,
|
||||
) {}
|
||||
|
||||
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,
|
||||
endpoint: string,
|
||||
payload?: Record<string, unknown> | string,
|
||||
): Promise<void> {
|
||||
if (endpoint === 'items/sync') {
|
||||
const result = await this.gRPCSyncingServerServiceProxy.sync(request, response, payload)
|
||||
|
||||
response.status(result.status).send(result.data)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
await this.callServer(this.syncingServerJsUrl, request, response, endpoint, payload)
|
||||
}
|
||||
|
||||
async callRevisionsServer(
|
||||
request: Request,
|
||||
response: Response,
|
||||
endpoint: 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, endpoint, payload)
|
||||
}
|
||||
|
||||
async callLegacySyncingServer(
|
||||
request: Request,
|
||||
response: Response,
|
||||
endpoint: string,
|
||||
payload?: Record<string, unknown> | string,
|
||||
): Promise<void> {
|
||||
await this.callServerWithLegacyFormat(this.syncingServerJsUrl, request, response, endpoint, payload)
|
||||
}
|
||||
|
||||
async callAuthServer(
|
||||
request: Request,
|
||||
response: Response,
|
||||
endpoint: string,
|
||||
payload?: Record<string, unknown> | string,
|
||||
): Promise<void> {
|
||||
await this.callServer(this.authServerUrl, request, response, endpoint, payload)
|
||||
}
|
||||
|
||||
async callEmailServer(
|
||||
request: Request,
|
||||
response: Response,
|
||||
endpoint: 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, endpoint, payload)
|
||||
}
|
||||
|
||||
async callWebSocketServer(
|
||||
request: Request,
|
||||
response: Response,
|
||||
endpoint: 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
|
||||
if (isARequestComingFromApiGatewayAndShouldBeKeptInMinimalFormat) {
|
||||
await this.callServerWithLegacyFormat(this.webSocketServerUrl, request, response, endpoint, payload)
|
||||
} else {
|
||||
await this.callServer(this.webSocketServerUrl, request, response, endpoint, payload)
|
||||
}
|
||||
}
|
||||
|
||||
async callPaymentsServer(
|
||||
request: Request,
|
||||
response: Response,
|
||||
endpoint: 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, endpoint, payload)
|
||||
}
|
||||
|
||||
async callAuthServerWithLegacyFormat(
|
||||
request: Request,
|
||||
response: Response,
|
||||
endpoint: string,
|
||||
payload?: Record<string, unknown> | string,
|
||||
): Promise<void> {
|
||||
await this.callServerWithLegacyFormat(this.authServerUrl, request, response, endpoint, payload)
|
||||
}
|
||||
|
||||
private async getServerResponse(
|
||||
serverUrl: string,
|
||||
request: Request,
|
||||
response: Response,
|
||||
endpoint: 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
|
||||
}
|
||||
|
||||
const serviceResponse = await this.httpClient.request({
|
||||
method: request.method as Method,
|
||||
headers,
|
||||
url: `${serverUrl}/${endpoint}`,
|
||||
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}/${endpoint} 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}/${endpoint} for the ${nextRetryAttempt} time`)
|
||||
|
||||
return this.getServerResponse(serverUrl, request, response, endpoint, 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}/${endpoint} timed out after ${retryAttempt} retries`
|
||||
: `Could not pass the request to ${serverUrl}/${endpoint} 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,
|
||||
endpoint: string,
|
||||
payload?: Record<string, unknown> | string,
|
||||
): Promise<void> {
|
||||
const serviceResponse = await this.getServerResponse(serverUrl, request, response, endpoint, payload)
|
||||
|
||||
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,
|
||||
endpoint: string,
|
||||
payload?: Record<string, unknown> | string,
|
||||
): Promise<void | Response<unknown, Record<string, unknown>>> {
|
||||
const serviceResponse = await this.getServerResponse(serverUrl, request, response, endpoint, 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))
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import { Request, Response } from 'express'
|
||||
import { ISyncingClient, SyncRequest, SyncResponse } from '@standardnotes/grpc'
|
||||
import { MapperInterface } from '@standardnotes/domain-core'
|
||||
import { Metadata } from '@grpc/grpc-js'
|
||||
|
||||
import { SyncResponseHttpRepresentation } from '../../Mapping/Sync/Http/SyncResponseHttpRepresentation'
|
||||
|
||||
export class GRPCSyncingServerServiceProxy {
|
||||
constructor(
|
||||
private syncingClient: ISyncingClient,
|
||||
private syncRequestGRPCMapper: MapperInterface<Record<string, unknown>, SyncRequest>,
|
||||
private syncResponseGRPCMapper: MapperInterface<SyncResponse, SyncResponseHttpRepresentation>,
|
||||
) {}
|
||||
|
||||
async sync(
|
||||
request: Request,
|
||||
response: Response,
|
||||
payload?: Record<string, unknown> | string,
|
||||
): Promise<{ status: number; data: unknown }> {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
const syncRequest = this.syncRequestGRPCMapper.toProjection(payload as Record<string, unknown>)
|
||||
|
||||
const metadata = new Metadata()
|
||||
metadata.set('x-user-uuid', response.locals.user.uuid)
|
||||
metadata.set('x-snjs-version', request.headers['x-snjs-version'] as string)
|
||||
metadata.set('x-read-only-access', response.locals.readonlyAccess ? 'true' : 'false')
|
||||
if (response.locals.session) {
|
||||
metadata.set('x-session-uuid', response.locals.session.uuid)
|
||||
}
|
||||
|
||||
this.syncingClient.syncItems(syncRequest, metadata, (error, syncResponse) => {
|
||||
if (error) {
|
||||
const responseCode = error.metadata.get('x-sync-error-response-code').pop()
|
||||
if (responseCode) {
|
||||
return resolve({
|
||||
status: +responseCode,
|
||||
data: { error: { message: error.metadata.get('x-sync-error-message').pop() } },
|
||||
})
|
||||
}
|
||||
|
||||
return reject(error)
|
||||
}
|
||||
|
||||
return resolve({ status: 200, data: this.syncResponseGRPCMapper.toProjection(syncResponse) })
|
||||
})
|
||||
} catch (error) {
|
||||
reject(error)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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.172.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.171.0...@standardnotes/auth-server@1.172.0) (2023-11-20)
|
||||
|
||||
### Features
|
||||
|
||||
* **grpc:** add syncing protocol buffers ([#930](https://github.com/standardnotes/server/issues/930)) ([5b84f07](https://github.com/standardnotes/server/commit/5b84f078c6ae6330706895f7c57b67ff8c8d18ae))
|
||||
|
||||
# [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
|
||||
|
||||
@@ -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) => {
|
||||
@@ -66,11 +71,58 @@ void container.load().then((container) => {
|
||||
|
||||
const serverInstance = server.build().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}`)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
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')
|
||||
})
|
||||
grpcServer.tryShutdown((error?: Error) => {
|
||||
if (error) {
|
||||
logger.error(`Failed to shutdown gRPC server: ${error.message}`)
|
||||
} else {
|
||||
logger.info('gRPC server closed')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
logger.info(`Server started on port ${process.env.PORT}`)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/auth-server",
|
||||
"version": "1.168.0",
|
||||
"version": "1.172.0",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
@@ -37,6 +37,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.11",
|
||||
"@simplewebauthn/server": "^8.1.1",
|
||||
"@simplewebauthn/typescript-types": "^8.0.0",
|
||||
"@standardnotes/api": "^1.26.26",
|
||||
@@ -45,6 +46,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:*",
|
||||
|
||||
80
packages/auth/src/Infra/gRPC/SessionsServer.ts
Normal file
80
packages/auth/src/Infra/gRPC/SessionsServer.ts
Normal file
@@ -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,6 +3,16 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/domain-events-infra",
|
||||
"version": "1.21.0",
|
||||
"version": "1.21.2",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -14,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'
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/domain-events",
|
||||
"version": "2.134.0",
|
||||
"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
|
||||
}
|
||||
@@ -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'
|
||||
|
||||
@@ -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=
|
||||
@@ -1,2 +0,0 @@
|
||||
dist
|
||||
test-setup.ts
|
||||
@@ -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
@@ -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" ]
|
||||
@@ -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
|
||||
@@ -1,17 +0,0 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
COMMAND=$1 && shift 1
|
||||
|
||||
case "$COMMAND" in
|
||||
'start-worker' )
|
||||
echo "Starting Worker..."
|
||||
exec node docker/entrypoint-worker.js
|
||||
;;
|
||||
|
||||
* )
|
||||
echo "Unknown command"
|
||||
;;
|
||||
esac
|
||||
|
||||
exec "$@"
|
||||
@@ -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`')
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
{
|
||||
"name": "@standardnotes/event-store",
|
||||
"version": "1.14.0",
|
||||
"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')
|
||||
})
|
||||
})
|
||||
@@ -1,33 +0,0 @@
|
||||
import { DomainEventHandlerInterface, DomainEventInterface } from '@standardnotes/domain-events'
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
import { inject, injectable } from 'inversify'
|
||||
import { Repository } from 'typeorm'
|
||||
import { Logger } from 'winston'
|
||||
import TYPES from '../../Bootstrap/Types'
|
||||
import { Event } from '../Event/Event'
|
||||
|
||||
@injectable()
|
||||
export class EventHandler implements DomainEventHandlerInterface {
|
||||
constructor(
|
||||
@inject(TYPES.Timer) private timer: TimerInterface,
|
||||
@inject(TYPES.ORMEventRepository) private eventRepository: Repository<Event>,
|
||||
@inject(TYPES.Logger) private logger: Logger,
|
||||
) {}
|
||||
|
||||
async handle(event: DomainEventInterface): Promise<void> {
|
||||
this.logger.debug('Handling event: %O', event)
|
||||
|
||||
try {
|
||||
const storedEvent = new Event()
|
||||
storedEvent.eventType = event.type
|
||||
storedEvent.userIdentifier = event.meta.correlation.userIdentifier
|
||||
storedEvent.userIdentifierType = event.meta.correlation.userIdentifierType
|
||||
storedEvent.eventPayload = JSON.stringify(event.payload)
|
||||
storedEvent.timestamp = this.timer.convertStringDateToMicroseconds(event.createdAt.toString())
|
||||
|
||||
await this.eventRepository.save(storedEvent)
|
||||
} catch (error) {
|
||||
this.logger.error('Could not store event %O in the event store: %s', event, (error as Error).message)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,26 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [1.35.0](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.34.2...@standardnotes/files-server@1.35.0) (2023-11-16)
|
||||
|
||||
### Features
|
||||
|
||||
* add grpc sessions validation server ([#928](https://github.com/standardnotes/files/issues/928)) ([4f62cac](https://github.com/standardnotes/files/commit/4f62cac213a6b5f503040ef6319e5198967974ce))
|
||||
|
||||
## [1.34.2](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.34.1...@standardnotes/files-server@1.34.2) (2023-11-13)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files-server
|
||||
|
||||
## [1.34.1](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.34.0...@standardnotes/files-server@1.34.1) (2023-11-13)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files-server
|
||||
|
||||
# [1.34.0](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.33.0...@standardnotes/files-server@1.34.0) (2023-11-10)
|
||||
|
||||
### Features
|
||||
|
||||
* add keep-alive connections to subservices ([#924](https://github.com/standardnotes/files/issues/924)) ([daad76d](https://github.com/standardnotes/files/commit/daad76d0ddae34c59dce45eedc4a055c4a11456d))
|
||||
|
||||
# [1.33.0](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.32.5...@standardnotes/files-server@1.33.0) (2023-11-10)
|
||||
|
||||
### Features
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user