mirror of
https://github.com/standardnotes/server
synced 2026-04-28 18:01:24 -04:00
Compare commits
85 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b1665779b5 | |||
| a82192db42 | |||
| 589b740f49 | |||
| 3c10de3e5d | |||
| 41a04062c9 | |||
| db9d10c302 | |||
| 5596d04040 | |||
| 341f69e301 | |||
| ef0464690b | |||
| 199ebeb4ea | |||
| c949670d4c | |||
| 9dcd583b58 | |||
| 097e320490 | |||
| c9bfda91f4 | |||
| 2d6a3ebf45 | |||
| d0d4bd23fb | |||
| edb0a768d0 | |||
| 4cc647ac07 | |||
| bcd1d830e6 | |||
| 2597324876 | |||
| 69b404f5d4 | |||
| e94b0d0b02 | |||
| ed1bf37287 | |||
| 3946f56261 | |||
| fc53dab007 | |||
| e836abdef7 | |||
| 826482b1f0 | |||
| 45bd00919c | |||
| 4e1bae6daf | |||
| 8f23c8ab3f | |||
| 4d32f26631 | |||
| c11abe1bd3 | |||
| 4d12566b0d | |||
| 2200dca69d | |||
| d41dd3bdda | |||
| c5c24b3ac9 | |||
| 462ade2145 | |||
| bfef16ce37 | |||
| aa4351c8e9 | |||
| 2dff6a2ed3 | |||
| 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 | |||
| e6d8e5c5f2 | |||
| c24353cc24 | |||
| 4855e1d5f5 | |||
| 5d3fb9a537 | |||
| b55d80a7cd | |||
| 16f92bdc99 | |||
| 4c5738416a | |||
| 45d4920e0f | |||
| 94e738532a | |||
| c4ae12d53f | |||
| 4ff78452f9 | |||
| 9465f2ecd8 | |||
| 93c2f1f12f | |||
| ca8a3fc77d |
@@ -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:
|
||||
|
||||
@@ -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,14 @@ jobs:
|
||||
env:
|
||||
DB_TYPE: mysql
|
||||
CACHE_TYPE: redis
|
||||
SERVICE_PROXY_TYPE: ${{ matrix.service_proxy_type }}
|
||||
|
||||
- name: Output Server Logs to File
|
||||
run: docker compose -f docker-compose.ci.yml logs -f > logs/docker-compose.log 2>&1 &
|
||||
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
|
||||
|
||||
@@ -4,6 +4,9 @@ on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
|
||||
permissions:
|
||||
id-token: write
|
||||
|
||||
jobs:
|
||||
build:
|
||||
if: contains(github.event.head_commit.message, 'chore(release)') == false
|
||||
|
||||
@@ -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"],\
|
||||
@@ -6961,7 +6976,6 @@ const RAW_RUNTIME_STATE =
|
||||
["@types/uuid", "npm:9.0.3"],\
|
||||
["@typescript-eslint/eslint-plugin", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:6.5.0"],\
|
||||
["@typescript-eslint/parser", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:6.5.0"],\
|
||||
["axios", "npm:1.4.0"],\
|
||||
["cors", "npm:2.8.5"],\
|
||||
["dotenv", "npm:16.1.3"],\
|
||||
["eslint", "npm:8.41.0"],\
|
||||
@@ -7031,20 +7045,19 @@ const RAW_RUNTIME_STATE =
|
||||
["@standardnotes/websockets-server", "workspace:packages/websockets"],\
|
||||
["@aws-sdk/client-apigatewaymanagementapi", "npm:3.427.0"],\
|
||||
["@aws-sdk/client-sqs", "npm:3.427.0"],\
|
||||
["@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/responses", "npm:1.13.27"],\
|
||||
["@standardnotes/security", "workspace:packages/security"],\
|
||||
["@standardnotes/time", "workspace:packages/time"],\
|
||||
["@types/cors", "npm:2.8.13"],\
|
||||
["@types/express", "npm:4.17.17"],\
|
||||
["@types/ioredis", "npm:5.0.0"],\
|
||||
["@types/jest", "npm:29.5.2"],\
|
||||
["@typescript-eslint/eslint-plugin", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:6.5.0"],\
|
||||
["@typescript-eslint/parser", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:6.5.0"],\
|
||||
["axios", "npm:1.4.0"],\
|
||||
["cors", "npm:2.8.5"],\
|
||||
["dotenv", "npm:16.1.3"],\
|
||||
["eslint", "npm:8.41.0"],\
|
||||
@@ -7322,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/",\
|
||||
@@ -7477,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/",\
|
||||
@@ -8196,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", [\
|
||||
@@ -8498,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"]\
|
||||
],\
|
||||
@@ -10906,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]\
|
||||
],\
|
||||
@@ -11336,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/",\
|
||||
@@ -11354,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/",\
|
||||
@@ -17991,14 +18048,16 @@ const Filename = {
|
||||
const npath = Object.create(path__default.default);
|
||||
const ppath = Object.create(path__default.default.posix);
|
||||
npath.cwd = () => process.cwd();
|
||||
ppath.cwd = () => toPortablePath(process.cwd());
|
||||
ppath.resolve = (...segments) => {
|
||||
if (segments.length > 0 && ppath.isAbsolute(segments[0])) {
|
||||
return path__default.default.posix.resolve(...segments);
|
||||
} else {
|
||||
return path__default.default.posix.resolve(ppath.cwd(), ...segments);
|
||||
}
|
||||
};
|
||||
ppath.cwd = process.platform === `win32` ? () => toPortablePath(process.cwd()) : process.cwd;
|
||||
if (process.platform === `win32`) {
|
||||
ppath.resolve = (...segments) => {
|
||||
if (segments.length > 0 && ppath.isAbsolute(segments[0])) {
|
||||
return path__default.default.posix.resolve(...segments);
|
||||
} else {
|
||||
return path__default.default.posix.resolve(ppath.cwd(), ...segments);
|
||||
}
|
||||
};
|
||||
}
|
||||
const contains = function(pathUtils, from, to) {
|
||||
from = pathUtils.normalize(from);
|
||||
to = pathUtils.normalize(to);
|
||||
@@ -18012,17 +18071,13 @@ const contains = function(pathUtils, from, to) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
npath.fromPortablePath = fromPortablePath;
|
||||
npath.toPortablePath = toPortablePath;
|
||||
npath.contains = (from, to) => contains(npath, from, to);
|
||||
ppath.contains = (from, to) => contains(ppath, from, to);
|
||||
const WINDOWS_PATH_REGEXP = /^([a-zA-Z]:.*)$/;
|
||||
const UNC_WINDOWS_PATH_REGEXP = /^\/\/(\.\/)?(.*)$/;
|
||||
const PORTABLE_PATH_REGEXP = /^\/([a-zA-Z]:.*)$/;
|
||||
const UNC_PORTABLE_PATH_REGEXP = /^\/unc\/(\.dot\/)?(.*)$/;
|
||||
function fromPortablePath(p) {
|
||||
if (process.platform !== `win32`)
|
||||
return p;
|
||||
function fromPortablePathWin32(p) {
|
||||
let portablePathMatch, uncPortablePathMatch;
|
||||
if (portablePathMatch = p.match(PORTABLE_PATH_REGEXP))
|
||||
p = portablePathMatch[1];
|
||||
@@ -18032,9 +18087,7 @@ function fromPortablePath(p) {
|
||||
return p;
|
||||
return p.replace(/\//g, `\\`);
|
||||
}
|
||||
function toPortablePath(p) {
|
||||
if (process.platform !== `win32`)
|
||||
return p;
|
||||
function toPortablePathWin32(p) {
|
||||
p = p.replace(/\\/g, `/`);
|
||||
let windowsPathMatch, uncWindowsPathMatch;
|
||||
if (windowsPathMatch = p.match(WINDOWS_PATH_REGEXP))
|
||||
@@ -18043,6 +18096,10 @@ function toPortablePath(p) {
|
||||
p = `/unc/${uncWindowsPathMatch[1] ? `.dot/` : ``}${uncWindowsPathMatch[2]}`;
|
||||
return p;
|
||||
}
|
||||
const toPortablePath = process.platform === `win32` ? toPortablePathWin32 : (p) => p;
|
||||
const fromPortablePath = process.platform === `win32` ? fromPortablePathWin32 : (p) => p;
|
||||
npath.fromPortablePath = fromPortablePath;
|
||||
npath.toPortablePath = toPortablePath;
|
||||
function convertPath(targetPathUtils, sourcePath) {
|
||||
return targetPathUtils === npath ? fromPortablePath(sourcePath) : toPortablePath(sourcePath);
|
||||
}
|
||||
@@ -19087,6 +19144,12 @@ class ProxiedFS extends FakeFS {
|
||||
}
|
||||
}
|
||||
|
||||
function direntToPortable(dirent) {
|
||||
const portableDirent = dirent;
|
||||
if (typeof dirent.path === `string`)
|
||||
portableDirent.path = npath.toPortablePath(dirent.path);
|
||||
return portableDirent;
|
||||
}
|
||||
class NodeFS extends BasePortableFakeFS {
|
||||
constructor(realFs = fs__default.default) {
|
||||
super();
|
||||
@@ -19413,15 +19476,31 @@ class NodeFS extends BasePortableFakeFS {
|
||||
async readdirPromise(p, opts) {
|
||||
return await new Promise((resolve, reject) => {
|
||||
if (opts) {
|
||||
this.realFs.readdir(npath.fromPortablePath(p), opts, this.makeCallback(resolve, reject));
|
||||
if (opts.recursive && process.platform === `win32`) {
|
||||
if (opts.withFileTypes) {
|
||||
this.realFs.readdir(npath.fromPortablePath(p), opts, this.makeCallback((results) => resolve(results.map(direntToPortable)), reject));
|
||||
} else {
|
||||
this.realFs.readdir(npath.fromPortablePath(p), opts, this.makeCallback((results) => resolve(results.map(npath.toPortablePath)), reject));
|
||||
}
|
||||
} else {
|
||||
this.realFs.readdir(npath.fromPortablePath(p), opts, this.makeCallback(resolve, reject));
|
||||
}
|
||||
} else {
|
||||
this.realFs.readdir(npath.fromPortablePath(p), this.makeCallback((value) => resolve(value), reject));
|
||||
this.realFs.readdir(npath.fromPortablePath(p), this.makeCallback(resolve, reject));
|
||||
}
|
||||
});
|
||||
}
|
||||
readdirSync(p, opts) {
|
||||
if (opts) {
|
||||
return this.realFs.readdirSync(npath.fromPortablePath(p), opts);
|
||||
if (opts.recursive && process.platform === `win32`) {
|
||||
if (opts.withFileTypes) {
|
||||
return this.realFs.readdirSync(npath.fromPortablePath(p), opts).map(direntToPortable);
|
||||
} else {
|
||||
return this.realFs.readdirSync(npath.fromPortablePath(p), opts).map(npath.toPortablePath);
|
||||
}
|
||||
} else {
|
||||
return this.realFs.readdirSync(npath.fromPortablePath(p), opts);
|
||||
}
|
||||
} else {
|
||||
return this.realFs.readdirSync(npath.fromPortablePath(p));
|
||||
}
|
||||
@@ -22975,8 +23054,6 @@ function getPathForDisplay(p) {
|
||||
const [major, minor] = process.versions.node.split(`.`).map((value) => parseInt(value, 10));
|
||||
const WATCH_MODE_MESSAGE_USES_ARRAYS = major > 19 || major === 19 && minor >= 2 || major === 18 && minor >= 13;
|
||||
|
||||
const builtinModules = new Set(require$$0.Module.builtinModules || Object.keys(process.binding(`natives`)));
|
||||
const isBuiltinModule = (request) => request.startsWith(`node:`) || builtinModules.has(request);
|
||||
function readPackageScope(checkPath) {
|
||||
const rootSeparatorIndex = checkPath.indexOf(npath.sep);
|
||||
let separatorIndex;
|
||||
@@ -23085,7 +23162,7 @@ function applyPatch(pnpapi, opts) {
|
||||
const pathRegExp = /^(?![a-zA-Z]:[\\/]|\\\\|\.{0,2}(?:\/|$))((?:@[^/]+\/)?[^/]+)\/*(.*|)$/;
|
||||
const originalModuleResolveFilename = require$$0.Module._resolveFilename;
|
||||
require$$0.Module._resolveFilename = function(request, parent, isMain, options) {
|
||||
if (isBuiltinModule(request))
|
||||
if (require$$0.isBuiltin(request))
|
||||
return request;
|
||||
if (!enableNativeHooks)
|
||||
return originalModuleResolveFilename.call(require$$0.Module, request, parent, isMain, options);
|
||||
@@ -24447,7 +24524,7 @@ function makeApi(runtimeState, opts) {
|
||||
throw new Error(`resolveToUnqualified can not handle private import mappings`);
|
||||
if (request === `pnpapi`)
|
||||
return npath.toPortablePath(opts.pnpapiResolution);
|
||||
if (considerBuiltins && isBuiltinModule(request))
|
||||
if (considerBuiltins && require$$0.isBuiltin(request))
|
||||
return null;
|
||||
const requestForDisplay = getPathForDisplay(request);
|
||||
const issuerForDisplay = issuer && getPathForDisplay(issuer);
|
||||
@@ -24585,7 +24662,7 @@ ${brokenAncestors.map((ancestorLocator) => `Ancestor breaking the chain: ${ances
|
||||
}
|
||||
}
|
||||
} else if (dependencyReference === void 0) {
|
||||
if (!considerBuiltins && isBuiltinModule(request)) {
|
||||
if (!considerBuiltins && require$$0.isBuiltin(request)) {
|
||||
if (isDependencyTreeRoot(issuerLocator)) {
|
||||
error = makeError(
|
||||
ErrorCode.UNDECLARED_DEPENDENCY,
|
||||
@@ -24752,7 +24829,7 @@ ${candidates.map((candidate) => `Not found: ${getPathForDisplay(candidate)}
|
||||
if (unqualifiedPath === null)
|
||||
return null;
|
||||
const isIssuerIgnored = () => issuer !== null ? isPathIgnored(issuer) : false;
|
||||
const remappedPath = (!considerBuiltins || !isBuiltinModule(request)) && !isIssuerIgnored() ? resolveUnqualifiedExport(request, unqualifiedPath, conditions, issuer) : unqualifiedPath;
|
||||
const remappedPath = (!considerBuiltins || !require$$0.isBuiltin(request)) && !isIssuerIgnored() ? resolveUnqualifiedExport(request, unqualifiedPath, conditions, issuer) : unqualifiedPath;
|
||||
return resolveUnqualified(remappedPath, { extensions });
|
||||
} catch (error) {
|
||||
if (Object.hasOwn(error, `pnpCode`))
|
||||
|
||||
Generated
+81
-46
@@ -1,9 +1,9 @@
|
||||
import fs from 'fs';
|
||||
import { URL as URL$1, fileURLToPath, pathToFileURL } from 'url';
|
||||
import path from 'path';
|
||||
import moduleExports, { Module } from 'module';
|
||||
import { createHash } from 'crypto';
|
||||
import { EOL } from 'os';
|
||||
import moduleExports, { isBuiltin } from 'module';
|
||||
import assert from 'assert';
|
||||
|
||||
const SAFE_TIME = 456789e3;
|
||||
@@ -16,14 +16,16 @@ const PortablePath = {
|
||||
const npath = Object.create(path);
|
||||
const ppath = Object.create(path.posix);
|
||||
npath.cwd = () => process.cwd();
|
||||
ppath.cwd = () => toPortablePath(process.cwd());
|
||||
ppath.resolve = (...segments) => {
|
||||
if (segments.length > 0 && ppath.isAbsolute(segments[0])) {
|
||||
return path.posix.resolve(...segments);
|
||||
} else {
|
||||
return path.posix.resolve(ppath.cwd(), ...segments);
|
||||
}
|
||||
};
|
||||
ppath.cwd = process.platform === `win32` ? () => toPortablePath(process.cwd()) : process.cwd;
|
||||
if (process.platform === `win32`) {
|
||||
ppath.resolve = (...segments) => {
|
||||
if (segments.length > 0 && ppath.isAbsolute(segments[0])) {
|
||||
return path.posix.resolve(...segments);
|
||||
} else {
|
||||
return path.posix.resolve(ppath.cwd(), ...segments);
|
||||
}
|
||||
};
|
||||
}
|
||||
const contains = function(pathUtils, from, to) {
|
||||
from = pathUtils.normalize(from);
|
||||
to = pathUtils.normalize(to);
|
||||
@@ -37,17 +39,13 @@ const contains = function(pathUtils, from, to) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
npath.fromPortablePath = fromPortablePath;
|
||||
npath.toPortablePath = toPortablePath;
|
||||
npath.contains = (from, to) => contains(npath, from, to);
|
||||
ppath.contains = (from, to) => contains(ppath, from, to);
|
||||
const WINDOWS_PATH_REGEXP = /^([a-zA-Z]:.*)$/;
|
||||
const UNC_WINDOWS_PATH_REGEXP = /^\/\/(\.\/)?(.*)$/;
|
||||
const PORTABLE_PATH_REGEXP = /^\/([a-zA-Z]:.*)$/;
|
||||
const UNC_PORTABLE_PATH_REGEXP = /^\/unc\/(\.dot\/)?(.*)$/;
|
||||
function fromPortablePath(p) {
|
||||
if (process.platform !== `win32`)
|
||||
return p;
|
||||
function fromPortablePathWin32(p) {
|
||||
let portablePathMatch, uncPortablePathMatch;
|
||||
if (portablePathMatch = p.match(PORTABLE_PATH_REGEXP))
|
||||
p = portablePathMatch[1];
|
||||
@@ -57,9 +55,7 @@ function fromPortablePath(p) {
|
||||
return p;
|
||||
return p.replace(/\//g, `\\`);
|
||||
}
|
||||
function toPortablePath(p) {
|
||||
if (process.platform !== `win32`)
|
||||
return p;
|
||||
function toPortablePathWin32(p) {
|
||||
p = p.replace(/\\/g, `/`);
|
||||
let windowsPathMatch, uncWindowsPathMatch;
|
||||
if (windowsPathMatch = p.match(WINDOWS_PATH_REGEXP))
|
||||
@@ -68,6 +64,10 @@ function toPortablePath(p) {
|
||||
p = `/unc/${uncWindowsPathMatch[1] ? `.dot/` : ``}${uncWindowsPathMatch[2]}`;
|
||||
return p;
|
||||
}
|
||||
const toPortablePath = process.platform === `win32` ? toPortablePathWin32 : (p) => p;
|
||||
const fromPortablePath = process.platform === `win32` ? fromPortablePathWin32 : (p) => p;
|
||||
npath.fromPortablePath = fromPortablePath;
|
||||
npath.toPortablePath = toPortablePath;
|
||||
function convertPath(targetPathUtils, sourcePath) {
|
||||
return targetPathUtils === npath ? fromPortablePath(sourcePath) : toPortablePath(sourcePath);
|
||||
}
|
||||
@@ -902,6 +902,12 @@ class ProxiedFS extends FakeFS {
|
||||
}
|
||||
}
|
||||
|
||||
function direntToPortable(dirent) {
|
||||
const portableDirent = dirent;
|
||||
if (typeof dirent.path === `string`)
|
||||
portableDirent.path = npath.toPortablePath(dirent.path);
|
||||
return portableDirent;
|
||||
}
|
||||
class NodeFS extends BasePortableFakeFS {
|
||||
constructor(realFs = fs) {
|
||||
super();
|
||||
@@ -1228,15 +1234,31 @@ class NodeFS extends BasePortableFakeFS {
|
||||
async readdirPromise(p, opts) {
|
||||
return await new Promise((resolve, reject) => {
|
||||
if (opts) {
|
||||
this.realFs.readdir(npath.fromPortablePath(p), opts, this.makeCallback(resolve, reject));
|
||||
if (opts.recursive && process.platform === `win32`) {
|
||||
if (opts.withFileTypes) {
|
||||
this.realFs.readdir(npath.fromPortablePath(p), opts, this.makeCallback((results) => resolve(results.map(direntToPortable)), reject));
|
||||
} else {
|
||||
this.realFs.readdir(npath.fromPortablePath(p), opts, this.makeCallback((results) => resolve(results.map(npath.toPortablePath)), reject));
|
||||
}
|
||||
} else {
|
||||
this.realFs.readdir(npath.fromPortablePath(p), opts, this.makeCallback(resolve, reject));
|
||||
}
|
||||
} else {
|
||||
this.realFs.readdir(npath.fromPortablePath(p), this.makeCallback((value) => resolve(value), reject));
|
||||
this.realFs.readdir(npath.fromPortablePath(p), this.makeCallback(resolve, reject));
|
||||
}
|
||||
});
|
||||
}
|
||||
readdirSync(p, opts) {
|
||||
if (opts) {
|
||||
return this.realFs.readdirSync(npath.fromPortablePath(p), opts);
|
||||
if (opts.recursive && process.platform === `win32`) {
|
||||
if (opts.withFileTypes) {
|
||||
return this.realFs.readdirSync(npath.fromPortablePath(p), opts).map(direntToPortable);
|
||||
} else {
|
||||
return this.realFs.readdirSync(npath.fromPortablePath(p), opts).map(npath.toPortablePath);
|
||||
}
|
||||
} else {
|
||||
return this.realFs.readdirSync(npath.fromPortablePath(p), opts);
|
||||
}
|
||||
} else {
|
||||
return this.realFs.readdirSync(npath.fromPortablePath(p));
|
||||
}
|
||||
@@ -1372,10 +1394,8 @@ class VirtualFS extends ProxiedFS {
|
||||
|
||||
const [major, minor] = process.versions.node.split(`.`).map((value) => parseInt(value, 10));
|
||||
const WATCH_MODE_MESSAGE_USES_ARRAYS = major > 19 || major === 19 && minor >= 2 || major === 18 && minor >= 13;
|
||||
const HAS_LAZY_LOADED_TRANSLATORS = major > 19 || major === 19 && minor >= 3;
|
||||
const HAS_LAZY_LOADED_TRANSLATORS = major === 20 && minor < 6 || major === 19 && minor >= 3;
|
||||
|
||||
const builtinModules = new Set(Module.builtinModules || Object.keys(process.binding(`natives`)));
|
||||
const isBuiltinModule = (request) => request.startsWith(`node:`) || builtinModules.has(request);
|
||||
function readPackageScope(checkPath) {
|
||||
const rootSeparatorIndex = checkPath.indexOf(npath.sep);
|
||||
let separatorIndex;
|
||||
@@ -1963,7 +1983,7 @@ async function resolvePrivateRequest(specifier, issuer, context, nextResolve) {
|
||||
}
|
||||
async function resolve$1(originalSpecifier, context, nextResolve) {
|
||||
const { findPnpApi } = moduleExports;
|
||||
if (!findPnpApi || isBuiltinModule(originalSpecifier))
|
||||
if (!findPnpApi || isBuiltin(originalSpecifier))
|
||||
return nextResolve(originalSpecifier, context, nextResolve);
|
||||
let specifier = originalSpecifier;
|
||||
const url = tryParseURL(specifier, isRelativeRegexp.test(specifier) ? context.parentURL : void 0);
|
||||
@@ -2022,31 +2042,46 @@ async function resolve$1(originalSpecifier, context, nextResolve) {
|
||||
|
||||
if (!HAS_LAZY_LOADED_TRANSLATORS) {
|
||||
const binding = process.binding(`fs`);
|
||||
const originalfstat = binding.fstat;
|
||||
const ZIP_MASK = 4278190080;
|
||||
const ZIP_MAGIC = 704643072;
|
||||
binding.fstat = function(...args) {
|
||||
const [fd, useBigint, req] = args;
|
||||
if ((fd & ZIP_MASK) === ZIP_MAGIC && useBigint === false && req === void 0) {
|
||||
const originalReadFile = binding.readFileUtf8 || binding.readFileSync;
|
||||
if (originalReadFile) {
|
||||
binding[originalReadFile.name] = function(...args) {
|
||||
try {
|
||||
const stats = fs.fstatSync(fd);
|
||||
return new Float64Array([
|
||||
stats.dev,
|
||||
stats.mode,
|
||||
stats.nlink,
|
||||
stats.uid,
|
||||
stats.gid,
|
||||
stats.rdev,
|
||||
stats.blksize,
|
||||
stats.ino,
|
||||
stats.size,
|
||||
stats.blocks
|
||||
]);
|
||||
return fs.readFileSync(args[0], {
|
||||
encoding: `utf8`,
|
||||
flag: args[1]
|
||||
});
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
return originalfstat.apply(this, args);
|
||||
};
|
||||
return originalReadFile.apply(this, args);
|
||||
};
|
||||
} else {
|
||||
const binding2 = process.binding(`fs`);
|
||||
const originalfstat = binding2.fstat;
|
||||
const ZIP_MASK = 4278190080;
|
||||
const ZIP_MAGIC = 704643072;
|
||||
binding2.fstat = function(...args) {
|
||||
const [fd, useBigint, req] = args;
|
||||
if ((fd & ZIP_MASK) === ZIP_MAGIC && useBigint === false && req === void 0) {
|
||||
try {
|
||||
const stats = fs.fstatSync(fd);
|
||||
return new Float64Array([
|
||||
stats.dev,
|
||||
stats.mode,
|
||||
stats.nlink,
|
||||
stats.uid,
|
||||
stats.gid,
|
||||
stats.rdev,
|
||||
stats.blksize,
|
||||
stats.ino,
|
||||
stats.size,
|
||||
stats.blocks
|
||||
]);
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
return originalfstat.apply(this, args);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const resolve = resolve$1;
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Vendored
-891
File diff suppressed because one or more lines are too long
+893
File diff suppressed because one or more lines are too long
+1
-1
@@ -2,4 +2,4 @@ compressionLevel: mixed
|
||||
|
||||
enableGlobalCache: false
|
||||
|
||||
yarnPath: .yarn/releases/yarn-4.0.0-rc.51.cjs
|
||||
yarnPath: .yarn/releases/yarn-4.0.2.cjs
|
||||
|
||||
@@ -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
|
||||
@@ -32,7 +33,7 @@ services:
|
||||
- standardnotes_self_hosted
|
||||
|
||||
localstack:
|
||||
image: localstack/localstack:1.4
|
||||
image: localstack/localstack:3.0
|
||||
container_name: localstack-ci
|
||||
expose:
|
||||
- 4566
|
||||
|
||||
@@ -14,7 +14,7 @@ services:
|
||||
- standardnotes_self_hosted
|
||||
|
||||
localstack:
|
||||
image: localstack/localstack:1.3
|
||||
image: localstack/localstack:3.0
|
||||
container_name: localstack_self_hosted
|
||||
expose:
|
||||
- 4566
|
||||
|
||||
@@ -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
|
||||
|
||||
+9
-1
@@ -39,5 +39,13 @@
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^5.0.4"
|
||||
},
|
||||
"packageManager": "yarn@4.0.0-rc.51"
|
||||
"packageManager": "yarn@4.0.2",
|
||||
"dependenciesMeta": {
|
||||
"grpc-tools@1.12.4": {
|
||||
"unplugged": true
|
||||
},
|
||||
"grpc_tools_node_protoc_ts@5.3.3": {
|
||||
"unplugged": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,48 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [2.34.3](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.34.2...@standardnotes/analytics@2.34.3) (2023-11-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/analytics
|
||||
|
||||
## [2.34.2](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.34.1...@standardnotes/analytics@2.34.2) (2023-11-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/analytics
|
||||
|
||||
## [2.34.1](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.34.0...@standardnotes/analytics@2.34.1) (2023-11-27)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* repository config in package.json files ([ed1bf37](https://github.com/standardnotes/server/commit/ed1bf37287af23a25b8388ada95f0acdec8f71ea))
|
||||
|
||||
# [2.34.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.33.4...@standardnotes/analytics@2.34.0) (2023-11-27)
|
||||
|
||||
### Features
|
||||
|
||||
* add npm provenance to published packages ([e836abd](https://github.com/standardnotes/server/commit/e836abdef73d246940d8fffd9e65e17c64cd35c8))
|
||||
|
||||
## [2.33.4](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.33.3...@standardnotes/analytics@2.33.4) (2023-11-23)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/analytics
|
||||
|
||||
## [2.33.3](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.33.2...@standardnotes/analytics@2.33.3) (2023-11-22)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/analytics
|
||||
|
||||
## [2.33.2](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.33.1...@standardnotes/analytics@2.33.2) (2023-11-13)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/analytics
|
||||
|
||||
## [2.33.1](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.33.0...@standardnotes/analytics@2.33.1) (2023-11-13)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/analytics
|
||||
|
||||
# [2.33.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.32.6...@standardnotes/analytics@2.33.0) (2023-11-10)
|
||||
|
||||
### Features
|
||||
|
||||
* add graceful shutdown procedures upon SIGTERM ([#923](https://github.com/standardnotes/server/issues/923)) ([c24353c](https://github.com/standardnotes/server/commit/c24353cc24ebf4b40ff9a2cec8e37cfdef109e37))
|
||||
|
||||
## [2.32.6](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.32.5...@standardnotes/analytics@2.32.6) (2023-11-07)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/analytics
|
||||
|
||||
@@ -22,5 +22,11 @@ void container.load().then((container) => {
|
||||
|
||||
const subscriber = container.get<DomainEventSubscriberInterface>(TYPES.DomainEventSubscriber)
|
||||
|
||||
process.on('SIGTERM', () => {
|
||||
logger.info('SIGTERM received. Stopping worker...')
|
||||
subscriber.stop()
|
||||
logger.info('Worker stopped.')
|
||||
})
|
||||
|
||||
subscriber.start()
|
||||
})
|
||||
|
||||
@@ -6,12 +6,12 @@ COMMAND=$1 && shift 1
|
||||
case "$COMMAND" in
|
||||
'start-worker' )
|
||||
echo "[Docker] Starting Worker..."
|
||||
node docker/entrypoint-worker.js
|
||||
exec node docker/entrypoint-worker.js
|
||||
;;
|
||||
|
||||
'report' )
|
||||
echo "[Docker] Starting Usage Report Generation..."
|
||||
node docker/entrypoint-report.js
|
||||
exec node docker/entrypoint-report.js
|
||||
;;
|
||||
|
||||
* )
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/analytics",
|
||||
"version": "2.32.6",
|
||||
"version": "2.34.3",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
@@ -10,7 +10,13 @@
|
||||
"author": "Standard Notes",
|
||||
"types": "dist/src/index.d.ts",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
"access": "public",
|
||||
"provenance": true
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git@github.com:standardnotes/server.git",
|
||||
"directory": "packages/analytics"
|
||||
},
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"scripts": {
|
||||
|
||||
@@ -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,170 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.87.4](https://github.com/standardnotes/server/compare/@standardnotes/api-gateway@1.87.3...@standardnotes/api-gateway@1.87.4) (2023-12-01)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **api-gateway:** home server home page ([#949](https://github.com/standardnotes/server/issues/949)) ([a82192d](https://github.com/standardnotes/server/commit/a82192db42dfbb3eea4ac6af40ef2b3d6126e5a3))
|
||||
|
||||
## [1.87.3](https://github.com/standardnotes/server/compare/@standardnotes/api-gateway@1.87.2...@standardnotes/api-gateway@1.87.3) (2023-11-28)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **api-gateway:** add session to response locals from web socket middleware ([4cc647a](https://github.com/standardnotes/server/commit/4cc647ac07b2471d6616a913bcdca431c506fd0e))
|
||||
|
||||
## [1.87.2](https://github.com/standardnotes/server/compare/@standardnotes/api-gateway@1.87.1...@standardnotes/api-gateway@1.87.2) (2023-11-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||
|
||||
## [1.87.1](https://github.com/standardnotes/server/compare/@standardnotes/api-gateway@1.87.0...@standardnotes/api-gateway@1.87.1) (2023-11-27)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* repository config in package.json files ([ed1bf37](https://github.com/standardnotes/server/commit/ed1bf37287af23a25b8388ada95f0acdec8f71ea))
|
||||
|
||||
# [1.87.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.86.6...@standardnotes/api-gateway@1.87.0) (2023-11-27)
|
||||
|
||||
### Features
|
||||
|
||||
* add npm provenance to published packages ([e836abd](https://github.com/standardnotes/api-gateway/commit/e836abdef73d246940d8fffd9e65e17c64cd35c8))
|
||||
|
||||
## [1.86.6](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.86.5...@standardnotes/api-gateway@1.86.6) (2023-11-23)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||
|
||||
## [1.86.5](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.86.4...@standardnotes/api-gateway@1.86.5) (2023-11-22)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* error handling on gRPC ([#937](https://github.com/standardnotes/api-gateway/issues/937)) ([8f23c8a](https://github.com/standardnotes/api-gateway/commit/8f23c8ab3f03e9c23adfb31a33c5805492bc2f5b))
|
||||
|
||||
## [1.86.4](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.86.3...@standardnotes/api-gateway@1.86.4) (2023-11-22)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||
|
||||
## [1.86.3](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.86.2...@standardnotes/api-gateway@1.86.3) (2023-11-21)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **api-gateway:** add meta field to grpc sync calls ([#934](https://github.com/standardnotes/api-gateway/issues/934)) ([c5c24b3](https://github.com/standardnotes/api-gateway/commit/c5c24b3ac9dbd559d96adc56270d724a3156ebd4))
|
||||
|
||||
## [1.86.2](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.86.1...@standardnotes/api-gateway@1.86.2) (2023-11-20)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* define grpc max message size ([bfef16c](https://github.com/standardnotes/api-gateway/commit/bfef16ce3757b57ea1cb0cb7417d6bc935a52321))
|
||||
|
||||
## [1.86.1](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.86.0...@standardnotes/api-gateway@1.86.1) (2023-11-20)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* setting gzip as default compression on grpc calls ([#933](https://github.com/standardnotes/api-gateway/issues/933)) ([2dff6a2](https://github.com/standardnotes/api-gateway/commit/2dff6a2ed3d105ca65996d47321a811e22e25099))
|
||||
|
||||
# [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
|
||||
|
||||
* add graceful shutdown procedures upon SIGTERM ([#923](https://github.com/standardnotes/api-gateway/issues/923)) ([c24353c](https://github.com/standardnotes/api-gateway/commit/c24353cc24ebf4b40ff9a2cec8e37cfdef109e37))
|
||||
|
||||
## [1.81.14](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.81.13...@standardnotes/api-gateway@1.81.14) (2023-11-10)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **api-gateway:** add logs about calling web sockets with minimal format ([5d3fb9a](https://github.com/standardnotes/api-gateway/commit/5d3fb9a537f6971cfe8ae3c5ea449806cc4de8a0))
|
||||
|
||||
## [1.81.13](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.81.12...@standardnotes/api-gateway@1.81.13) (2023-11-09)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **api-gateway:** add possibility to configure keep-alive timeout ([#920](https://github.com/standardnotes/api-gateway/issues/920)) ([16f92bd](https://github.com/standardnotes/api-gateway/commit/16f92bdc990ded5c3f1fe5af1e6e4a113a9954de))
|
||||
|
||||
## [1.81.12](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.81.11...@standardnotes/api-gateway@1.81.12) (2023-11-09)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* reduce websockets api communication data ([#919](https://github.com/standardnotes/api-gateway/issues/919)) ([c4ae12d](https://github.com/standardnotes/api-gateway/commit/c4ae12d53fc166879f90a4c5dbad1ab1cb4797e2))
|
||||
|
||||
## [1.81.11](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.81.10...@standardnotes/api-gateway@1.81.11) (2023-11-07)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -90,8 +90,15 @@ void container.load().then((container) => {
|
||||
const logger: winston.Logger = container.get(TYPES.ApiGateway_Logger)
|
||||
|
||||
server.setErrorConfig((app) => {
|
||||
app.use((error: Record<string, unknown>, _request: Request, response: Response, _next: NextFunction) => {
|
||||
logger.error(error.stack)
|
||||
app.use((error: Record<string, unknown>, request: Request, response: Response, _next: NextFunction) => {
|
||||
logger.error(
|
||||
`[URL: |${request.method}| ${request.url}][SNJS: ${request.headers['x-snjs-version']}][Application: ${request.headers['x-application-version']}] Error thrown: ${error.stack}`,
|
||||
)
|
||||
logger.debug(
|
||||
`[URL: |${request.method}| ${request.url}][SNJS: ${request.headers['x-snjs-version']}][Application: ${
|
||||
request.headers['x-application-version']
|
||||
}] Request body: ${JSON.stringify(request.body)}`,
|
||||
)
|
||||
|
||||
response.status(500).send({
|
||||
error: {
|
||||
@@ -102,9 +109,18 @@ void container.load().then((container) => {
|
||||
})
|
||||
})
|
||||
|
||||
const serverInstance = server.build()
|
||||
const serverInstance = server.build().listen(env.get('PORT'))
|
||||
|
||||
serverInstance.listen(env.get('PORT'))
|
||||
const keepAliveTimeout = env.get('HTTP_KEEP_ALIVE_TIMEOUT', true) ? +env.get('HTTP_KEEP_ALIVE_TIMEOUT', true) : 5000
|
||||
|
||||
serverInstance.keepAliveTimeout = keepAliveTimeout
|
||||
|
||||
process.on('SIGTERM', () => {
|
||||
logger.info('SIGTERM signal received: closing HTTP server')
|
||||
serverInstance.close(() => {
|
||||
logger.info('HTTP server closed')
|
||||
})
|
||||
})
|
||||
|
||||
logger.info(`Server started on port ${process.env.PORT}`)
|
||||
})
|
||||
|
||||
@@ -6,7 +6,7 @@ COMMAND=$1 && shift 1
|
||||
case "$COMMAND" in
|
||||
'start-web' )
|
||||
echo "Starting Web..."
|
||||
node docker/entrypoint-server.js
|
||||
exec node docker/entrypoint-server.js
|
||||
;;
|
||||
|
||||
* )
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/api-gateway",
|
||||
"version": "1.81.11",
|
||||
"version": "1.87.4",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
@@ -11,11 +11,16 @@
|
||||
"dist/src/**/*.js",
|
||||
"dist/src/**/*.d.ts"
|
||||
],
|
||||
"repository": "git@github.com:standardnotes/api-gateway.git",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git@github.com:standardnotes/server.git",
|
||||
"directory": "packages/api-gateway"
|
||||
},
|
||||
"author": "Karol Sójko <karol@standardnotes.com>",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
"access": "public",
|
||||
"provenance": true
|
||||
},
|
||||
"scripts": {
|
||||
"clean": "rm -fr dist",
|
||||
@@ -26,12 +31,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,100 @@ 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
|
||||
|
||||
const grpcMaxMessageSize = env.get('GRPC_MAX_MESSAGE_SIZE', true)
|
||||
? +env.get('GRPC_MAX_MESSAGE_SIZE', true)
|
||||
: 1024 * 1024 * 50
|
||||
|
||||
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,
|
||||
'grpc.default_compression_algorithm': grpc.compressionAlgorithms.gzip,
|
||||
'grpc.default_compression_level': 2,
|
||||
'grpc.max_receive_message_length': grpcMaxMessageSize,
|
||||
'grpc.max_send_message_length': grpcMaxMessageSize,
|
||||
},
|
||||
),
|
||||
)
|
||||
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,
|
||||
'grpc.default_compression_algorithm': grpc.compressionAlgorithms.gzip,
|
||||
'grpc.default_compression_level': 2,
|
||||
'grpc.max_receive_message_length': grpcMaxMessageSize,
|
||||
'grpc.max_send_message_length': grpcMaxMessageSize,
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -1,9 +1,18 @@
|
||||
import { Request, Response } from 'express'
|
||||
import { BaseHttpController, all, controller, results } from 'inversify-express-utils'
|
||||
|
||||
@controller('')
|
||||
export class FallbackController extends BaseHttpController {
|
||||
@all('*')
|
||||
public async fallback(): Promise<results.NotFoundResult> {
|
||||
public async fallback(request: Request, response: Response): Promise<void | results.NotFoundResult> {
|
||||
if (request.path === '/' && request.method === 'GET') {
|
||||
response.send(
|
||||
'<!DOCTYPE html><html lang="en"><head><meta name="robots" content="noindex"></head><body>Your home server is up and running! Enter the URL of this page into Standard Notes when registering or signing in to begin using your home server.</body></html>',
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
return this.notFound()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -60,6 +60,7 @@ export class WebSocketAuthMiddleware extends BaseMiddleware {
|
||||
const decodedToken = <CrossServiceTokenData>verify(crossServiceToken, this.jwtSecret, { algorithms: ['HS256'] })
|
||||
|
||||
response.locals.user = decodedToken.user
|
||||
response.locals.session = decodedToken.session
|
||||
response.locals.roles = decodedToken.roles
|
||||
} catch (error) {
|
||||
const errorMessage = (error as AxiosError).isAxiosError
|
||||
|
||||
@@ -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[]
|
||||
}
|
||||
+12
-16
@@ -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) {
|
||||
@@ -143,13 +137,18 @@ export class HttpServiceProxy implements ServiceProxyInterface {
|
||||
return
|
||||
}
|
||||
|
||||
await this.callServer(this.webSocketServerUrl, request, response, endpointOrMethodIdentifier, payload)
|
||||
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,
|
||||
endpointOrMethodIdentifier: string,
|
||||
endpoint: string,
|
||||
payload?: Record<string, unknown> | string,
|
||||
): Promise<void | Response<unknown, Record<string, unknown>>> {
|
||||
if (!this.paymentsServerUrl) {
|
||||
@@ -158,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> {
|
||||
@@ -201,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,
|
||||
@@ -226,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
|
||||
@@ -240,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
|
||||
@@ -261,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)}`)
|
||||
@@ -293,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
|
||||
@@ -337,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
|
||||
|
||||
@@ -0,0 +1,414 @@
|
||||
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> {
|
||||
const requestIsUsingLatestApiVersions =
|
||||
payload !== undefined && typeof payload !== 'string' && 'api' in payload && payload.api === '20200115'
|
||||
|
||||
if (requestIsUsingLatestApiVersions && endpoint === 'items/sync') {
|
||||
const result = await this.gRPCSyncingServerServiceProxy.sync(request, response, payload)
|
||||
|
||||
response.status(result.status).send({
|
||||
meta: {
|
||||
auth: {
|
||||
userUuid: response.locals.user?.uuid,
|
||||
roles: response.locals.roles,
|
||||
},
|
||||
server: {
|
||||
filesServerUrl: this.filesServerUrl,
|
||||
},
|
||||
},
|
||||
data: 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -6,4 +6,4 @@ sh supervisor/wait-for.sh localhost $AUTH_SERVER_PORT
|
||||
sh supervisor/wait-for.sh localhost $FILES_SERVER_PORT
|
||||
sh supervisor/wait-for.sh localhost $REVISIONS_SERVER_PORT
|
||||
sh supervisor/wait-for.sh localhost $SYNCING_SERVER_PORT
|
||||
node docker/entrypoint-server.js
|
||||
exec node docker/entrypoint-server.js
|
||||
|
||||
@@ -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,107 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.174.3](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.174.2...@standardnotes/auth-server@1.174.3) (2023-11-28)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** pass session uuid to web sockets controller ([edb0a76](https://github.com/standardnotes/server/commit/edb0a768d0b8c31298b31372e6cec16d003fd28d))
|
||||
* pass session uuid to websockets token ([bcd1d83](https://github.com/standardnotes/server/commit/bcd1d830e6125fc5f8cc1312e581284221aaac8f))
|
||||
|
||||
## [1.174.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.174.1...@standardnotes/auth-server@1.174.2) (2023-11-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/auth-server
|
||||
|
||||
## [1.174.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.174.0...@standardnotes/auth-server@1.174.1) (2023-11-27)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* repository config in package.json files ([ed1bf37](https://github.com/standardnotes/server/commit/ed1bf37287af23a25b8388ada95f0acdec8f71ea))
|
||||
|
||||
# [1.174.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.173.2...@standardnotes/auth-server@1.174.0) (2023-11-27)
|
||||
|
||||
### Features
|
||||
|
||||
* add npm provenance to published packages ([e836abd](https://github.com/standardnotes/server/commit/e836abdef73d246940d8fffd9e65e17c64cd35c8))
|
||||
|
||||
## [1.173.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.173.1...@standardnotes/auth-server@1.173.2) (2023-11-23)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/auth-server
|
||||
|
||||
## [1.173.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.173.0...@standardnotes/auth-server@1.173.1) (2023-11-22)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* error handling on gRPC ([#937](https://github.com/standardnotes/server/issues/937)) ([8f23c8a](https://github.com/standardnotes/server/commit/8f23c8ab3f03e9c23adfb31a33c5805492bc2f5b))
|
||||
|
||||
# [1.173.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.172.2...@standardnotes/auth-server@1.173.0) (2023-11-22)
|
||||
|
||||
### Features
|
||||
|
||||
* add verifiying if user has no items before mass deleting spam accounts ([#936](https://github.com/standardnotes/server/issues/936)) ([c11abe1](https://github.com/standardnotes/server/commit/c11abe1bd36de7c0fb9850c20a8157c066fa9379))
|
||||
|
||||
## [1.172.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.172.1...@standardnotes/auth-server@1.172.2) (2023-11-20)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* define grpc max message size ([bfef16c](https://github.com/standardnotes/server/commit/bfef16ce3757b57ea1cb0cb7417d6bc935a52321))
|
||||
|
||||
## [1.172.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.172.0...@standardnotes/auth-server@1.172.1) (2023-11-20)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* setting gzip as default compression on grpc calls ([#933](https://github.com/standardnotes/server/issues/933)) ([2dff6a2](https://github.com/standardnotes/server/commit/2dff6a2ed3d105ca65996d47321a811e22e25099))
|
||||
|
||||
# [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
|
||||
|
||||
* add graceful shutdown procedures upon SIGTERM ([#923](https://github.com/standardnotes/server/issues/923)) ([c24353c](https://github.com/standardnotes/server/commit/c24353cc24ebf4b40ff9a2cec8e37cfdef109e37))
|
||||
|
||||
## [1.167.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.167.1...@standardnotes/auth-server@1.167.2) (2023-11-08)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add logs about sending websocket events to clients ([9465f2e](https://github.com/standardnotes/server/commit/9465f2ecd8e8f0bf3ebeeb3976227b1b105aded0))
|
||||
|
||||
## [1.167.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.167.0...@standardnotes/auth-server@1.167.1) (2023-11-08)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** path to delete accounts script ([ca8a3fc](https://github.com/standardnotes/server/commit/ca8a3fc77d91410f0dee8c3ddef29c09947c9cf5))
|
||||
|
||||
# [1.167.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.166.0...@standardnotes/auth-server@1.167.0) (2023-11-08)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -20,6 +20,7 @@ import '../src/Infra/InversifyExpressUtils/AnnotatedHealthCheckController'
|
||||
import '../src/Infra/InversifyExpressUtils/AnnotatedFeaturesController'
|
||||
|
||||
import * as cors from 'cors'
|
||||
import * as grpc from '@grpc/grpc-js'
|
||||
import { urlencoded, json, Request, Response, NextFunction } from 'express'
|
||||
import * as winston from 'winston'
|
||||
import * as dayjs from 'dayjs'
|
||||
@@ -29,6 +30,10 @@ import { InversifyExpressServer } from 'inversify-express-utils'
|
||||
import { ContainerConfigLoader } from '../src/Bootstrap/Container'
|
||||
import TYPES from '../src/Bootstrap/Types'
|
||||
import { Env } from '../src/Bootstrap/Env'
|
||||
import { SessionsServer } from '../src/Infra/gRPC/SessionsServer'
|
||||
import { SessionsService } from '@standardnotes/grpc'
|
||||
import { AuthenticateRequest } from '../src/Domain/UseCase/AuthenticateRequest'
|
||||
import { CreateCrossServiceToken } from '../src/Domain/UseCase/CreateCrossServiceToken/CreateCrossServiceToken'
|
||||
|
||||
const container = new ContainerConfigLoader()
|
||||
void container.load().then((container) => {
|
||||
@@ -64,9 +69,68 @@ void container.load().then((container) => {
|
||||
})
|
||||
})
|
||||
|
||||
const serverInstance = server.build()
|
||||
const serverInstance = server.build().listen(env.get('PORT'))
|
||||
|
||||
serverInstance.listen(env.get('PORT'))
|
||||
const httpKeepAliveTimeout = env.get('HTTP_KEEP_ALIVE_TIMEOUT', true)
|
||||
? +env.get('HTTP_KEEP_ALIVE_TIMEOUT', true)
|
||||
: 10_000
|
||||
|
||||
serverInstance.keepAliveTimeout = httpKeepAliveTimeout
|
||||
|
||||
const grpcKeepAliveTimeout = env.get('GRPC_KEEP_ALIVE_TIMEOUT', true)
|
||||
? +env.get('GRPC_KEEP_ALIVE_TIMEOUT', true)
|
||||
: 10_000
|
||||
|
||||
const grpcMaxMessageSize = env.get('GRPC_MAX_MESSAGE_SIZE', true)
|
||||
? +env.get('GRPC_MAX_MESSAGE_SIZE', true)
|
||||
: 1024 * 1024 * 50
|
||||
|
||||
const grpcServer = new grpc.Server({
|
||||
'grpc.keepalive_time_ms': grpcKeepAliveTimeout * 2,
|
||||
'grpc.keepalive_timeout_ms': grpcKeepAliveTimeout,
|
||||
'grpc.default_compression_algorithm': grpc.compressionAlgorithms.gzip,
|
||||
'grpc.max_receive_message_length': grpcMaxMessageSize,
|
||||
'grpc.max_send_message_length': grpcMaxMessageSize,
|
||||
})
|
||||
|
||||
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}`)
|
||||
})
|
||||
|
||||
@@ -22,5 +22,11 @@ void container.load().then((container) => {
|
||||
|
||||
const subscriber = container.get<DomainEventSubscriberInterface>(TYPES.Auth_DomainEventSubscriber)
|
||||
|
||||
process.on('SIGTERM', () => {
|
||||
logger.info('SIGTERM received. Stopping worker...')
|
||||
subscriber.stop()
|
||||
logger.info('Worker stopped.')
|
||||
})
|
||||
|
||||
subscriber.start()
|
||||
})
|
||||
|
||||
@@ -4,7 +4,7 @@ const path = require('path')
|
||||
|
||||
const pnp = require(path.normalize(path.resolve(__dirname, '../../..', '.pnp.cjs'))).setup()
|
||||
|
||||
const index = require(path.normalize(path.resolve(__dirname, '../dist/bin/delete-accounts.js')))
|
||||
const index = require(path.normalize(path.resolve(__dirname, '../dist/bin/delete_accounts.js')))
|
||||
|
||||
Object.defineProperty(exports, '__esModule', { value: true })
|
||||
|
||||
|
||||
@@ -6,45 +6,45 @@ COMMAND=$1 && shift 1
|
||||
case "$COMMAND" in
|
||||
'start-web' )
|
||||
echo "[Docker] Starting Web..."
|
||||
node docker/entrypoint-server.js
|
||||
exec node docker/entrypoint-server.js
|
||||
;;
|
||||
|
||||
'start-worker' )
|
||||
echo "[Docker] Starting Worker..."
|
||||
node docker/entrypoint-worker.js
|
||||
exec node docker/entrypoint-worker.js
|
||||
;;
|
||||
|
||||
'cleanup' )
|
||||
echo "[Docker] Starting Cleanup..."
|
||||
node docker/entrypoint-cleanup.js
|
||||
exec node docker/entrypoint-cleanup.js
|
||||
;;
|
||||
|
||||
'stats' )
|
||||
echo "[Docker] Starting Persisting Stats..."
|
||||
node docker/entrypoint-stats.js
|
||||
exec node docker/entrypoint-stats.js
|
||||
;;
|
||||
|
||||
'email-daily-backup' )
|
||||
echo "[Docker] Starting Email Daily Backup..."
|
||||
node docker/entrypoint-backup.js daily
|
||||
exec node docker/entrypoint-backup.js daily
|
||||
;;
|
||||
|
||||
'email-weekly-backup' )
|
||||
echo "[Docker] Starting Email Weekly Backup..."
|
||||
node docker/entrypoint-backup.js weekly
|
||||
exec node docker/entrypoint-backup.js weekly
|
||||
;;
|
||||
|
||||
'email-backup' )
|
||||
echo "[Docker] Starting Email Backup For Single User..."
|
||||
EMAIL=$1 && shift 1
|
||||
node docker/entrypoint-user-email-backup.js $EMAIL
|
||||
exec node docker/entrypoint-user-email-backup.js $EMAIL
|
||||
;;
|
||||
|
||||
'delete-accounts' )
|
||||
echo "[Docker] Starting Accounts Deleting from CSV..."
|
||||
FILE_NAME=$1 && shift 1
|
||||
MODE=$1 && shift 1
|
||||
node docker/entrypoint-delete-accounts.js $FILE_NAME $MODE
|
||||
exec node docker/entrypoint-delete-accounts.js $FILE_NAME $MODE
|
||||
;;
|
||||
|
||||
* )
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/auth-server",
|
||||
"version": "1.167.0",
|
||||
"version": "1.174.3",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
@@ -10,7 +10,13 @@
|
||||
"author": "Karol Sójko <karol@standardnotes.com>",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
"access": "public",
|
||||
"provenance": true
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git@github.com:standardnotes/server.git",
|
||||
"directory": "packages/auth"
|
||||
},
|
||||
"scripts": {
|
||||
"clean": "rm -fr dist",
|
||||
@@ -37,6 +43,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 +52,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:*",
|
||||
|
||||
@@ -280,6 +280,7 @@ import { TriggerEmailBackupForAllUsers } from '../Domain/UseCase/TriggerEmailBac
|
||||
import { CSVFileReaderInterface } from '../Domain/CSV/CSVFileReaderInterface'
|
||||
import { S3CsvFileReader } from '../Infra/S3/S3CsvFileReader'
|
||||
import { DeleteAccountsFromCSVFile } from '../Domain/UseCase/DeleteAccountsFromCSVFile/DeleteAccountsFromCSVFile'
|
||||
import { AccountDeletionVerificationPassedEventHandler } from '../Domain/Handler/AccountDeletionVerificationPassedEventHandler'
|
||||
|
||||
export class ContainerConfigLoader {
|
||||
constructor(private mode: 'server' | 'worker' = 'server') {}
|
||||
@@ -1194,6 +1195,7 @@ export class ContainerConfigLoader {
|
||||
container.get<GetRegularSubscriptionForUser>(TYPES.Auth_GetRegularSubscriptionForUser),
|
||||
container.get<GetSubscriptionSetting>(TYPES.Auth_GetSubscriptionSetting),
|
||||
container.get<SharedVaultUserRepositoryInterface>(TYPES.Auth_SharedVaultUserRepository),
|
||||
container.get<GetActiveSessionsForUser>(TYPES.Auth_GetActiveSessionsForUser),
|
||||
),
|
||||
)
|
||||
container.bind<ProcessUserRequest>(TYPES.Auth_ProcessUserRequest).to(ProcessUserRequest)
|
||||
@@ -1274,7 +1276,9 @@ export class ContainerConfigLoader {
|
||||
.toConstantValue(
|
||||
new DeleteAccountsFromCSVFile(
|
||||
container.get<CSVFileReaderInterface>(TYPES.Auth_CSVFileReader),
|
||||
container.get<DeleteAccount>(TYPES.Auth_DeleteAccount),
|
||||
container.get<DomainEventPublisherInterface>(TYPES.Auth_DomainEventPublisher),
|
||||
container.get<DomainEventFactoryInterface>(TYPES.Auth_DomainEventFactory),
|
||||
container.get<UserRepositoryInterface>(TYPES.Auth_UserRepository),
|
||||
container.get<winston.Logger>(TYPES.Auth_Logger),
|
||||
),
|
||||
)
|
||||
@@ -1328,6 +1332,14 @@ export class ContainerConfigLoader {
|
||||
container.get<winston.Logger>(TYPES.Auth_Logger),
|
||||
),
|
||||
)
|
||||
container
|
||||
.bind<AccountDeletionVerificationPassedEventHandler>(TYPES.Auth_AccountDeletionVerificationPassedEventHandler)
|
||||
.toConstantValue(
|
||||
new AccountDeletionVerificationPassedEventHandler(
|
||||
container.get<DeleteAccount>(TYPES.Auth_DeleteAccount),
|
||||
container.get<winston.Logger>(TYPES.Auth_Logger),
|
||||
),
|
||||
)
|
||||
container
|
||||
.bind<SubscriptionPurchasedEventHandler>(TYPES.Auth_SubscriptionPurchasedEventHandler)
|
||||
.toConstantValue(
|
||||
@@ -1516,6 +1528,7 @@ export class ContainerConfigLoader {
|
||||
|
||||
const eventHandlers: Map<string, DomainEventHandlerInterface> = new Map([
|
||||
['ACCOUNT_DELETION_REQUESTED', container.get(TYPES.Auth_AccountDeletionRequestedEventHandler)],
|
||||
['ACCOUNT_DELETION_VERIFICATION_PASSED', container.get(TYPES.Auth_AccountDeletionVerificationPassedEventHandler)],
|
||||
['SUBSCRIPTION_PURCHASED', container.get(TYPES.Auth_SubscriptionPurchasedEventHandler)],
|
||||
['SUBSCRIPTION_CANCELLED', container.get(TYPES.Auth_SubscriptionCancelledEventHandler)],
|
||||
['SUBSCRIPTION_RENEWED', container.get(TYPES.Auth_SubscriptionRenewedEventHandler)],
|
||||
|
||||
@@ -171,6 +171,7 @@ const TYPES = {
|
||||
Auth_DeleteAccountsFromCSVFile: Symbol.for('Auth_DeleteAccountsFromCSVFile'),
|
||||
// Handlers
|
||||
Auth_AccountDeletionRequestedEventHandler: Symbol.for('Auth_AccountDeletionRequestedEventHandler'),
|
||||
Auth_AccountDeletionVerificationPassedEventHandler: Symbol.for('Auth_AccountDeletionVerificationPassedEventHandler'),
|
||||
Auth_SubscriptionPurchasedEventHandler: Symbol.for('Auth_SubscriptionPurchasedEventHandler'),
|
||||
Auth_SubscriptionCancelledEventHandler: Symbol.for('Auth_SubscriptionCancelledEventHandler'),
|
||||
Auth_SubscriptionReassignedEventHandler: Symbol.for('Auth_SubscriptionReassignedEventHandler'),
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
StatisticPersistenceRequestedEvent,
|
||||
SessionCreatedEvent,
|
||||
SessionRefreshedEvent,
|
||||
AccountDeletionVerificationRequestedEvent,
|
||||
} from '@standardnotes/domain-events'
|
||||
import { Predicate, PredicateVerificationResult } from '@standardnotes/predicates'
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
@@ -33,6 +34,24 @@ import { KeyParamsData } from '@standardnotes/responses'
|
||||
export class DomainEventFactory implements DomainEventFactoryInterface {
|
||||
constructor(@inject(TYPES.Auth_Timer) private timer: TimerInterface) {}
|
||||
|
||||
createAccountDeletionVerificationRequestedEvent(dto: {
|
||||
userUuid: string
|
||||
email: string
|
||||
}): AccountDeletionVerificationRequestedEvent {
|
||||
return {
|
||||
type: 'ACCOUNT_DELETION_VERIFICATION_REQUESTED',
|
||||
createdAt: this.timer.getUTCDate(),
|
||||
meta: {
|
||||
correlation: {
|
||||
userIdentifier: dto.userUuid,
|
||||
userIdentifierType: 'uuid',
|
||||
},
|
||||
origin: DomainEventService.Auth,
|
||||
},
|
||||
payload: dto,
|
||||
}
|
||||
}
|
||||
|
||||
createSessionCreatedEvent(dto: { userUuid: string }): SessionCreatedEvent {
|
||||
return {
|
||||
type: 'SESSION_CREATED',
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
StatisticPersistenceRequestedEvent,
|
||||
SessionCreatedEvent,
|
||||
SessionRefreshedEvent,
|
||||
AccountDeletionVerificationRequestedEvent,
|
||||
} from '@standardnotes/domain-events'
|
||||
import { InviteeIdentifierType } from '../SharedSubscription/InviteeIdentifierType'
|
||||
import { KeyParamsData } from '@standardnotes/responses'
|
||||
@@ -56,6 +57,10 @@ export interface DomainEventFactoryInterface {
|
||||
ownerUuid: string
|
||||
}
|
||||
}): AccountDeletionRequestedEvent
|
||||
createAccountDeletionVerificationRequestedEvent(dto: {
|
||||
userUuid: string
|
||||
email: string
|
||||
}): AccountDeletionVerificationRequestedEvent
|
||||
createUserRolesChangedEvent(userUuid: string, email: string, currentRoles: string[]): UserRolesChangedEvent
|
||||
createUserEmailChangedEvent(userUuid: string, fromEmail: string, toEmail: string): UserEmailChangedEvent
|
||||
createUserDisabledSessionUserAgentLoggingEvent(dto: {
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import { AccountDeletionVerificationPassedEvent, DomainEventHandlerInterface } from '@standardnotes/domain-events'
|
||||
import { Logger } from 'winston'
|
||||
|
||||
import { DeleteAccount } from '../UseCase/DeleteAccount/DeleteAccount'
|
||||
|
||||
export class AccountDeletionVerificationPassedEventHandler implements DomainEventHandlerInterface {
|
||||
constructor(
|
||||
private deleteAccount: DeleteAccount,
|
||||
private logger: Logger,
|
||||
) {}
|
||||
|
||||
async handle(event: AccountDeletionVerificationPassedEvent): Promise<void> {
|
||||
const result = await this.deleteAccount.execute({
|
||||
userUuid: event.payload.userUuid,
|
||||
})
|
||||
|
||||
if (result.isFailed()) {
|
||||
this.logger.error(`AccountDeletionVerificationPassedEventHandler failed: ${result.getError()}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
+69
@@ -22,6 +22,7 @@ import { GetRegularSubscriptionForUser } from '../GetRegularSubscriptionForUser/
|
||||
import { UserSubscription } from '../../Subscription/UserSubscription'
|
||||
import { SubscriptionSetting } from '../../Setting/SubscriptionSetting'
|
||||
import { EncryptionVersion } from '../../Encryption/EncryptionVersion'
|
||||
import { GetActiveSessionsForUser } from '../GetActiveSessionsForUser'
|
||||
|
||||
describe('CreateCrossServiceToken', () => {
|
||||
let userProjector: ProjectorInterface<User>
|
||||
@@ -32,6 +33,7 @@ describe('CreateCrossServiceToken', () => {
|
||||
let getRegularSubscription: GetRegularSubscriptionForUser
|
||||
let getSubscriptionSetting: GetSubscriptionSetting
|
||||
let sharedVaultUserRepository: SharedVaultUserRepositoryInterface
|
||||
let getActiveSessionsForUser: GetActiveSessionsForUser
|
||||
const jwtTTL = 60
|
||||
|
||||
let session: Session
|
||||
@@ -49,11 +51,15 @@ describe('CreateCrossServiceToken', () => {
|
||||
getRegularSubscription,
|
||||
getSubscriptionSetting,
|
||||
sharedVaultUserRepository,
|
||||
getActiveSessionsForUser,
|
||||
)
|
||||
|
||||
beforeEach(() => {
|
||||
session = {} as jest.Mocked<Session>
|
||||
|
||||
getActiveSessionsForUser = {} as jest.Mocked<GetActiveSessionsForUser>
|
||||
getActiveSessionsForUser.execute = jest.fn().mockReturnValue({ sessions: [session] })
|
||||
|
||||
user = {
|
||||
uuid: '00000000-0000-0000-0000-000000000000',
|
||||
email: 'test@test.te',
|
||||
@@ -195,6 +201,69 @@ describe('CreateCrossServiceToken', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('should create a cross service token for a user and a specific session', async () => {
|
||||
await createUseCase().execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
sessionUuid: '00000000-0000-0000-0000-000000000000',
|
||||
})
|
||||
|
||||
expect(tokenEncoder.encodeExpirableToken).toHaveBeenCalledWith(
|
||||
{
|
||||
roles: [
|
||||
{
|
||||
name: 'role1',
|
||||
uuid: '1-3-4',
|
||||
},
|
||||
],
|
||||
belongs_to_shared_vaults: [
|
||||
{
|
||||
shared_vault_uuid: '00000000-0000-0000-0000-000000000000',
|
||||
permission: 'read',
|
||||
},
|
||||
],
|
||||
session: {
|
||||
test: 'test',
|
||||
},
|
||||
user: {
|
||||
email: 'test@test.te',
|
||||
uuid: '00000000-0000-0000-0000-000000000000',
|
||||
},
|
||||
},
|
||||
60,
|
||||
)
|
||||
})
|
||||
|
||||
it('should create a cross service token for a user and specific session if the session is missing', async () => {
|
||||
getActiveSessionsForUser.execute = jest.fn().mockReturnValue({ sessions: [] })
|
||||
|
||||
await createUseCase().execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
sessionUuid: '00000000-0000-0000-0000-000000000000',
|
||||
})
|
||||
|
||||
expect(tokenEncoder.encodeExpirableToken).toHaveBeenCalledWith(
|
||||
{
|
||||
roles: [
|
||||
{
|
||||
name: 'role1',
|
||||
uuid: '1-3-4',
|
||||
},
|
||||
],
|
||||
belongs_to_shared_vaults: [
|
||||
{
|
||||
shared_vault_uuid: '00000000-0000-0000-0000-000000000000',
|
||||
permission: 'read',
|
||||
},
|
||||
],
|
||||
user: {
|
||||
email: 'test@test.te',
|
||||
uuid: '00000000-0000-0000-0000-000000000000',
|
||||
},
|
||||
},
|
||||
60,
|
||||
)
|
||||
})
|
||||
|
||||
it('should throw an error if user does not exist', async () => {
|
||||
userRepository.findOneByUuid = jest.fn().mockReturnValue(null)
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import { CreateCrossServiceTokenDTO } from './CreateCrossServiceTokenDTO'
|
||||
import { SharedVaultUserRepositoryInterface } from '../../SharedVault/SharedVaultUserRepositoryInterface'
|
||||
import { GetSubscriptionSetting } from '../GetSubscriptionSetting/GetSubscriptionSetting'
|
||||
import { GetRegularSubscriptionForUser } from '../GetRegularSubscriptionForUser/GetRegularSubscriptionForUser'
|
||||
import { GetActiveSessionsForUser } from '../GetActiveSessionsForUser'
|
||||
|
||||
export class CreateCrossServiceToken implements UseCaseInterface<string> {
|
||||
constructor(
|
||||
@@ -23,6 +24,7 @@ export class CreateCrossServiceToken implements UseCaseInterface<string> {
|
||||
private getRegularSubscription: GetRegularSubscriptionForUser,
|
||||
private getSubscriptionSettingUseCase: GetSubscriptionSetting,
|
||||
private sharedVaultUserRepository: SharedVaultUserRepositoryInterface,
|
||||
private getActiveSessions: GetActiveSessionsForUser,
|
||||
) {}
|
||||
|
||||
async execute(dto: CreateCrossServiceTokenDTO): Promise<Result<string>> {
|
||||
@@ -84,6 +86,14 @@ export class CreateCrossServiceToken implements UseCaseInterface<string> {
|
||||
|
||||
if (dto.session !== undefined) {
|
||||
authTokenData.session = this.projectSession(dto.session)
|
||||
} else if (dto.sessionUuid !== undefined) {
|
||||
const activeSessionsResponse = await this.getActiveSessions.execute({
|
||||
userUuid: user.uuid,
|
||||
sessionUuid: dto.sessionUuid,
|
||||
})
|
||||
if (activeSessionsResponse.sessions.length) {
|
||||
authTokenData.session = this.projectSession(activeSessionsResponse.sessions[0])
|
||||
}
|
||||
}
|
||||
|
||||
return Result.ok(this.tokenEncoder.encodeExpirableToken(authTokenData, this.jwtTTL))
|
||||
|
||||
@@ -10,5 +10,6 @@ export type CreateCrossServiceTokenDTO = Either<
|
||||
},
|
||||
{
|
||||
userUuid: string
|
||||
sessionUuid?: string
|
||||
}
|
||||
>
|
||||
|
||||
+38
-9
@@ -1,33 +1,51 @@
|
||||
import { Logger } from 'winston'
|
||||
import { Result } from '@standardnotes/domain-core'
|
||||
import { AccountDeletionVerificationRequestedEvent, DomainEventPublisherInterface } from '@standardnotes/domain-events'
|
||||
|
||||
import { CSVFileReaderInterface } from '../../CSV/CSVFileReaderInterface'
|
||||
import { DeleteAccount } from '../DeleteAccount/DeleteAccount'
|
||||
import { DeleteAccountsFromCSVFile } from './DeleteAccountsFromCSVFile'
|
||||
import { UserRepositoryInterface } from '../../User/UserRepositoryInterface'
|
||||
import { DomainEventFactoryInterface } from '../../Event/DomainEventFactoryInterface'
|
||||
import { User } from '../../User/User'
|
||||
|
||||
describe('DeleteAccountsFromCSVFile', () => {
|
||||
let csvFileReader: CSVFileReaderInterface
|
||||
let deleteAccount: DeleteAccount
|
||||
let userRepository: UserRepositoryInterface
|
||||
let domainEventPublisher: DomainEventPublisherInterface
|
||||
let domainEventFactory: DomainEventFactoryInterface
|
||||
let logger: Logger
|
||||
|
||||
const createUseCase = () => new DeleteAccountsFromCSVFile(csvFileReader, deleteAccount, logger)
|
||||
const createUseCase = () =>
|
||||
new DeleteAccountsFromCSVFile(csvFileReader, domainEventPublisher, domainEventFactory, userRepository, logger)
|
||||
|
||||
beforeEach(() => {
|
||||
const user = {} as jest.Mocked<User>
|
||||
|
||||
csvFileReader = {} as jest.Mocked<CSVFileReaderInterface>
|
||||
csvFileReader.getValues = jest.fn().mockResolvedValue(Result.ok(['email1']))
|
||||
|
||||
deleteAccount = {} as jest.Mocked<DeleteAccount>
|
||||
deleteAccount.execute = jest.fn().mockResolvedValue(Result.ok(''))
|
||||
userRepository = {} as jest.Mocked<UserRepositoryInterface>
|
||||
userRepository.findAllByUsernameOrEmail = jest.fn().mockResolvedValue([user])
|
||||
|
||||
domainEventPublisher = {} as jest.Mocked<DomainEventPublisherInterface>
|
||||
domainEventPublisher.publish = jest.fn()
|
||||
|
||||
domainEventFactory = {} as jest.Mocked<DomainEventFactoryInterface>
|
||||
domainEventFactory.createAccountDeletionVerificationRequestedEvent = jest
|
||||
.fn()
|
||||
.mockReturnValue({} as jest.Mocked<AccountDeletionVerificationRequestedEvent>)
|
||||
|
||||
logger = {} as jest.Mocked<Logger>
|
||||
logger.info = jest.fn()
|
||||
})
|
||||
|
||||
it('should delete accounts', async () => {
|
||||
it('should request account deletion verification', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({ fileName: 'test.csv', dryRun: false })
|
||||
|
||||
expect(domainEventPublisher.publish).toHaveBeenCalled()
|
||||
|
||||
expect(result.isFailed()).toBeFalsy()
|
||||
})
|
||||
|
||||
@@ -56,12 +74,12 @@ describe('DeleteAccountsFromCSVFile', () => {
|
||||
|
||||
const result = await useCase.execute({ fileName: 'test.csv', dryRun: true })
|
||||
|
||||
expect(deleteAccount.execute).not.toHaveBeenCalled()
|
||||
expect(domainEventPublisher.publish).not.toHaveBeenCalled()
|
||||
expect(result.isFailed()).toBeFalsy()
|
||||
})
|
||||
|
||||
it('should return error if delete account fails', async () => {
|
||||
deleteAccount.execute = jest.fn().mockResolvedValue(Result.fail('Oops'))
|
||||
it('should return error username is invalid', async () => {
|
||||
csvFileReader.getValues = jest.fn().mockResolvedValue(Result.ok(['']))
|
||||
|
||||
const useCase = createUseCase()
|
||||
|
||||
@@ -69,4 +87,15 @@ describe('DeleteAccountsFromCSVFile', () => {
|
||||
|
||||
expect(result.isFailed()).toBeTruthy()
|
||||
})
|
||||
|
||||
it('should do nothing if users could not be found', async () => {
|
||||
userRepository.findAllByUsernameOrEmail = jest.fn().mockResolvedValue([])
|
||||
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({ fileName: 'test.csv', dryRun: false })
|
||||
|
||||
expect(domainEventPublisher.publish).not.toHaveBeenCalled()
|
||||
expect(result.isFailed()).toBeFalsy()
|
||||
})
|
||||
})
|
||||
|
||||
+20
-8
@@ -1,14 +1,18 @@
|
||||
import { Result, UseCaseInterface } from '@standardnotes/domain-core'
|
||||
import { Result, UseCaseInterface, Username } from '@standardnotes/domain-core'
|
||||
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
|
||||
import { Logger } from 'winston'
|
||||
|
||||
import { DeleteAccount } from '../DeleteAccount/DeleteAccount'
|
||||
import { CSVFileReaderInterface } from '../../CSV/CSVFileReaderInterface'
|
||||
import { DeleteAccountsFromCSVFileDTO } from './DeleteAccountsFromCSVFileDTO'
|
||||
import { DomainEventFactoryInterface } from '../../Event/DomainEventFactoryInterface'
|
||||
import { UserRepositoryInterface } from '../../User/UserRepositoryInterface'
|
||||
|
||||
export class DeleteAccountsFromCSVFile implements UseCaseInterface<void> {
|
||||
constructor(
|
||||
private csvFileReader: CSVFileReaderInterface,
|
||||
private deleteAccount: DeleteAccount,
|
||||
private domainEventPublisher: DomainEventPublisherInterface,
|
||||
private domainEventFactory: DomainEventFactoryInterface,
|
||||
private userRepository: UserRepositoryInterface,
|
||||
private logger: Logger,
|
||||
) {}
|
||||
|
||||
@@ -33,12 +37,20 @@ export class DeleteAccountsFromCSVFile implements UseCaseInterface<void> {
|
||||
}
|
||||
|
||||
for (const email of emails) {
|
||||
const deleteAccountOrError = await this.deleteAccount.execute({
|
||||
username: email,
|
||||
})
|
||||
const usernameOrError = Username.create(email)
|
||||
if (usernameOrError.isFailed()) {
|
||||
return Result.fail(usernameOrError.getError())
|
||||
}
|
||||
const username = usernameOrError.getValue()
|
||||
|
||||
if (deleteAccountOrError.isFailed()) {
|
||||
return Result.fail(deleteAccountOrError.getError())
|
||||
const users = await this.userRepository.findAllByUsernameOrEmail(username)
|
||||
for (const user of users) {
|
||||
await this.domainEventPublisher.publish(
|
||||
this.domainEventFactory.createAccountDeletionVerificationRequestedEvent({
|
||||
userUuid: user.uuid,
|
||||
email: user.email,
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -65,4 +65,10 @@ describe('GetActiveSessionsForUser', () => {
|
||||
|
||||
expect(sessionRepository.findAllByRefreshExpirationAndUserUuid).toHaveBeenCalledWith('1-2-3')
|
||||
})
|
||||
|
||||
it('should get a single session for a user', async () => {
|
||||
expect(await createUseCase().execute({ userUuid: '1-2-3', sessionUuid: '2-3-4' })).toEqual({
|
||||
sessions: [session2],
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -5,6 +5,7 @@ import { SessionRepositoryInterface } from '../Session/SessionRepositoryInterfac
|
||||
import { GetActiveSessionsForUserDTO } from './GetActiveSessionsForUserDTO'
|
||||
import { GetActiveSessionsForUserResponse } from './GetActiveSessionsForUserResponse'
|
||||
import { UseCaseInterface } from './UseCaseInterface'
|
||||
import { Session } from '../Session/Session'
|
||||
|
||||
@injectable()
|
||||
export class GetActiveSessionsForUser implements UseCaseInterface {
|
||||
@@ -18,13 +19,26 @@ export class GetActiveSessionsForUser implements UseCaseInterface {
|
||||
const ephemeralSessions = await this.ephemeralSessionRepository.findAllByUserUuid(dto.userUuid)
|
||||
const sessions = await this.sessionRepository.findAllByRefreshExpirationAndUserUuid(dto.userUuid)
|
||||
|
||||
return {
|
||||
sessions: sessions.concat(ephemeralSessions).sort((a, b) => {
|
||||
const dateA = a.refreshExpiration instanceof Date ? a.refreshExpiration : new Date(a.refreshExpiration)
|
||||
const dateB = b.refreshExpiration instanceof Date ? b.refreshExpiration : new Date(b.refreshExpiration)
|
||||
const activeSessions = sessions.concat(ephemeralSessions).sort((a, b) => {
|
||||
const dateA = a.refreshExpiration instanceof Date ? a.refreshExpiration : new Date(a.refreshExpiration)
|
||||
const dateB = b.refreshExpiration instanceof Date ? b.refreshExpiration : new Date(b.refreshExpiration)
|
||||
|
||||
return dateB.getTime() - dateA.getTime()
|
||||
}),
|
||||
return dateB.getTime() - dateA.getTime()
|
||||
})
|
||||
|
||||
if (dto.sessionUuid) {
|
||||
let sessions: Session[] = []
|
||||
const session = activeSessions.find((session) => session.uuid === dto.sessionUuid)
|
||||
if (session) {
|
||||
sessions = [session]
|
||||
}
|
||||
return {
|
||||
sessions,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
sessions: activeSessions,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export type GetActiveSessionsForUserDTO = {
|
||||
userUuid: string
|
||||
sessionUuid?: string
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ export interface UserRepositoryInterface {
|
||||
streamTeam(memberEmail?: Email): Promise<ReadStream>
|
||||
findOneByUuid(uuid: Uuid): Promise<User | null>
|
||||
findOneByUsernameOrEmail(usernameOrEmail: Email | Username): Promise<User | null>
|
||||
findAllByUsernameOrEmail(usernameOrEmail: Email | Username): Promise<User[]>
|
||||
findAllCreatedBetween(dto: { start: Date; end: Date; offset: number; limit: number }): Promise<User[]>
|
||||
countAllCreatedBetween(start: Date, end: Date): Promise<number>
|
||||
save(user: User): Promise<User>
|
||||
|
||||
@@ -48,6 +48,7 @@ export class BaseWebSocketsController extends BaseHttpController {
|
||||
|
||||
const resultOrError = await this.createCrossServiceToken.execute({
|
||||
userUuid: token.userUuid,
|
||||
sessionUuid: token.sessionUuid,
|
||||
})
|
||||
if (resultOrError.isFailed()) {
|
||||
return this.json(
|
||||
|
||||
@@ -69,7 +69,13 @@ export class TypeORMUserRepository implements UserRepositoryInterface {
|
||||
return this.ormRepository
|
||||
.createQueryBuilder('user')
|
||||
.where('user.email = :email', { email: usernameOrEmail.value })
|
||||
.cache(`user_email_${usernameOrEmail.value}`, 60000)
|
||||
.getOne()
|
||||
}
|
||||
|
||||
async findAllByUsernameOrEmail(usernameOrEmail: Email | Username): Promise<User[]> {
|
||||
return this.ormRepository
|
||||
.createQueryBuilder('user')
|
||||
.where('user.email = :email', { email: usernameOrEmail.value })
|
||||
.getMany()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,12 +5,14 @@ import { DomainEventFactoryInterface } from '../../Domain/Event/DomainEventFacto
|
||||
import { User } from '../../Domain/User/User'
|
||||
import { ClientServiceInterface } from '../../Domain/Client/ClientServiceInterface'
|
||||
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
|
||||
import { Logger } from 'winston'
|
||||
|
||||
@injectable()
|
||||
export class WebSocketsClientService implements ClientServiceInterface {
|
||||
constructor(
|
||||
@inject(TYPES.Auth_DomainEventFactory) private domainEventFactory: DomainEventFactoryInterface,
|
||||
@inject(TYPES.Auth_DomainEventPublisher) private domainEventPublisher: DomainEventPublisherInterface,
|
||||
@inject(TYPES.Auth_Logger) private logger: Logger,
|
||||
) {}
|
||||
|
||||
async sendUserRolesChangedEvent(user: User): Promise<void> {
|
||||
@@ -20,6 +22,8 @@ export class WebSocketsClientService implements ClientServiceInterface {
|
||||
(await user.roles).map((role) => role.name),
|
||||
)
|
||||
|
||||
this.logger.info(`[WebSockets] Requesting message ${event.type} to user ${user.uuid}`)
|
||||
|
||||
await this.domainEventPublisher.publish(
|
||||
this.domainEventFactory.createWebSocketMessageRequestedEvent({
|
||||
userUuid: user.uuid,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user