feat: add api-gateway package

This commit is contained in:
Karol Sójko 2022-06-23 11:33:14 +02:00
parent b25f2e8c54
commit 57c3b9c29e
No known key found for this signature in database
GPG key ID: A50543BF560BDEB0
53 changed files with 2537 additions and 195 deletions

View file

@ -0,0 +1,125 @@
name: Api Gateway Dev
concurrency:
group: api_gateway_dev_environment
cancel-in-progress: true
on:
push:
tags:
- '@standardnotes/api-gateway@[0-9]*.[0-9]*.[0-9]*-alpha.[0-9]*'
- '@standardnotes/api-gateway@[0-9]*.[0-9]*.[0-9]*-beta.[0-9]*'
workflow_dispatch:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v1
with:
node-version: '16.x'
- run: yarn lint:api-gateway
publish-aws-ecr:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v1
- name: Build, tag, and push image to Amazon ECR
id: build-image
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
ECR_REPOSITORY: api-gateway
IMAGE_TAG: ${{ github.sha }}
run: |
yarn docker build @standardnotes/api-gateway -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
docker tag $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG $ECR_REGISTRY/$ECR_REPOSITORY:dev
docker push $ECR_REGISTRY/$ECR_REPOSITORY:dev
publish-docker-hub:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Publish to Registry
uses: elgohr/Publish-Docker-Github-Action@master
with:
name: standardnotes/api-gateway
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
tags: "dev,${{ github.sha }}"
deploy-web:
needs: publish-aws-ecr
runs-on: ubuntu-latest
steps:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: Download task definition
run: |
aws ecs describe-task-definition --task-definition api-gateway-dev --query taskDefinition > task-definition.json
- name: Fill in the new version in the Amazon ECS task definition
run: |
jq '(.containerDefinitions[] | select(.name=="api-gateway-dev") | .environment[] | select(.name=="VERSION")).value = "${{ github.sha }}"' task-definition.json > tmp.json && mv tmp.json task-definition.json
- name: Fill in the new image ID in the Amazon ECS task definition
id: task-def
uses: aws-actions/amazon-ecs-render-task-definition@v1
with:
task-definition: task-definition.json
container-name: api-gateway-dev
image: ${{ secrets.AWS_ECR_REGISTRY }}/api-gateway:${{ github.sha }}
- name: Deploy Amazon ECS task definition
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
with:
task-definition: ${{ steps.task-def.outputs.task-definition }}
service: api-gateway-dev
cluster: dev
wait-for-service-stability: true
newrelic:
needs: deploy-web
runs-on: ubuntu-latest
steps:
- name: Create New Relic deployment marker for Web
uses: newrelic/deployment-marker-action@v1
with:
accountId: ${{ secrets.NEW_RELIC_ACCOUNT_ID }}
apiKey: ${{ secrets.NEW_RELIC_API_KEY }}
applicationId: ${{ secrets.NEW_RELIC_APPLICATION_ID_API_GATEWAY_WEB_DEV }}
revision: "${{ github.sha }}"
description: "Automated Deployment via Github Actions"
user: "${{ github.actor }}"
notify_discord:
needs: deploy-web
runs-on: ubuntu-latest
steps:
- name: Run Discord Webhook
uses: johnnyhuy/actions-discord-git-webhook@main
with:
webhook_url: ${{ secrets.DISCORD_WEBHOOK_URL }}

View file

@ -46,7 +46,7 @@ jobs:
ECR_REPOSITORY: files
IMAGE_TAG: ${{ github.sha }}
run: |
yarn docker build @standardnotes/files -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
yarn docker build @standardnotes/files-server -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
docker tag $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG $ECR_REGISTRY/$ECR_REPOSITORY:dev
docker push $ECR_REGISTRY/$ECR_REPOSITORY:dev

482
.pnp.cjs generated
View file

@ -20,6 +20,10 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
"name": "@standardnotes/server-monorepo",\
"reference": "workspace:."\
},\
{\
"name": "@standardnotes/api-gateway",\
"reference": "workspace:packages/api-gateway"\
},\
{\
"name": "@standardnotes/auth-server",\
"reference": "workspace:packages/auth"\
@ -40,6 +44,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
"enableTopLevelFallback": true,\
"ignorePatternData": "(^(?:\\\\.yarn\\\\/sdks(?:\\\\/(?!\\\\.{1,2}(?:\\\\/|$))(?:(?:(?!(?:^|\\\\/)\\\\.{1,2}(?:\\\\/|$)).)*?)|$))$)",\
"fallbackExclusionList": [\
["@standardnotes/api-gateway", ["workspace:packages/api-gateway"]],\
["@standardnotes/auth-server", ["workspace:packages/auth"]],\
["@standardnotes/files-server", ["workspace:packages/files"]],\
["@standardnotes/scheduler-server", ["workspace:packages/scheduler"]],\
@ -307,10 +312,10 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
],\
"linkType": "SOFT"\
}],\
["virtual:511f18ec4797ab42da93993c808942012470709446ef400bc600a66a60e5e382e8081da234db4fe66d41d38d9212735169fb3c604c828d1ff8dc854506ed7b48#npm:7.8.4", {\
"packageLocation": "./.yarn/__virtual__/@babel-plugin-syntax-async-generators-virtual-1c91e87c25/0/cache/@babel-plugin-syntax-async-generators-npm-7.8.4-d10cf993c9-7ed1c1d9b9.zip/node_modules/@babel/plugin-syntax-async-generators/",\
["virtual:c373874e1baa9d76ffb6ce2109646b13db6b2b433728eac72e69f1da2ca3718a077c7549e49925b0fa06952690278e6a36ceca4ebe492de934ac9dea98343dd7#npm:7.8.4", {\
"packageLocation": "./.yarn/__virtual__/@babel-plugin-syntax-async-generators-virtual-20dc503383/0/cache/@babel-plugin-syntax-async-generators-npm-7.8.4-d10cf993c9-7ed1c1d9b9.zip/node_modules/@babel/plugin-syntax-async-generators/",\
"packageDependencies": [\
["@babel/plugin-syntax-async-generators", "virtual:511f18ec4797ab42da93993c808942012470709446ef400bc600a66a60e5e382e8081da234db4fe66d41d38d9212735169fb3c604c828d1ff8dc854506ed7b48#npm:7.8.4"],\
["@babel/plugin-syntax-async-generators", "virtual:c373874e1baa9d76ffb6ce2109646b13db6b2b433728eac72e69f1da2ca3718a077c7549e49925b0fa06952690278e6a36ceca4ebe492de934ac9dea98343dd7#npm:7.8.4"],\
["@babel/core", "npm:7.18.5"],\
["@babel/helper-plugin-utils", "npm:7.17.12"],\
["@types/babel__core", "npm:7.1.19"]\
@ -344,10 +349,10 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
],\
"linkType": "SOFT"\
}],\
["virtual:511f18ec4797ab42da93993c808942012470709446ef400bc600a66a60e5e382e8081da234db4fe66d41d38d9212735169fb3c604c828d1ff8dc854506ed7b48#npm:7.8.3", {\
"packageLocation": "./.yarn/__virtual__/@babel-plugin-syntax-bigint-virtual-62067a9335/0/cache/@babel-plugin-syntax-bigint-npm-7.8.3-b05d971e6c-3a10849d83.zip/node_modules/@babel/plugin-syntax-bigint/",\
["virtual:c373874e1baa9d76ffb6ce2109646b13db6b2b433728eac72e69f1da2ca3718a077c7549e49925b0fa06952690278e6a36ceca4ebe492de934ac9dea98343dd7#npm:7.8.3", {\
"packageLocation": "./.yarn/__virtual__/@babel-plugin-syntax-bigint-virtual-f9f6a1f31a/0/cache/@babel-plugin-syntax-bigint-npm-7.8.3-b05d971e6c-3a10849d83.zip/node_modules/@babel/plugin-syntax-bigint/",\
"packageDependencies": [\
["@babel/plugin-syntax-bigint", "virtual:511f18ec4797ab42da93993c808942012470709446ef400bc600a66a60e5e382e8081da234db4fe66d41d38d9212735169fb3c604c828d1ff8dc854506ed7b48#npm:7.8.3"],\
["@babel/plugin-syntax-bigint", "virtual:c373874e1baa9d76ffb6ce2109646b13db6b2b433728eac72e69f1da2ca3718a077c7549e49925b0fa06952690278e6a36ceca4ebe492de934ac9dea98343dd7#npm:7.8.3"],\
["@babel/core", "npm:7.18.5"],\
["@babel/helper-plugin-utils", "npm:7.17.12"],\
["@types/babel__core", "npm:7.1.19"]\
@ -381,10 +386,10 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
],\
"linkType": "SOFT"\
}],\
["virtual:511f18ec4797ab42da93993c808942012470709446ef400bc600a66a60e5e382e8081da234db4fe66d41d38d9212735169fb3c604c828d1ff8dc854506ed7b48#npm:7.12.13", {\
"packageLocation": "./.yarn/__virtual__/@babel-plugin-syntax-class-properties-virtual-c73477fb62/0/cache/@babel-plugin-syntax-class-properties-npm-7.12.13-002ee9d930-24f34b196d.zip/node_modules/@babel/plugin-syntax-class-properties/",\
["virtual:c373874e1baa9d76ffb6ce2109646b13db6b2b433728eac72e69f1da2ca3718a077c7549e49925b0fa06952690278e6a36ceca4ebe492de934ac9dea98343dd7#npm:7.12.13", {\
"packageLocation": "./.yarn/__virtual__/@babel-plugin-syntax-class-properties-virtual-d36699afc2/0/cache/@babel-plugin-syntax-class-properties-npm-7.12.13-002ee9d930-24f34b196d.zip/node_modules/@babel/plugin-syntax-class-properties/",\
"packageDependencies": [\
["@babel/plugin-syntax-class-properties", "virtual:511f18ec4797ab42da93993c808942012470709446ef400bc600a66a60e5e382e8081da234db4fe66d41d38d9212735169fb3c604c828d1ff8dc854506ed7b48#npm:7.12.13"],\
["@babel/plugin-syntax-class-properties", "virtual:c373874e1baa9d76ffb6ce2109646b13db6b2b433728eac72e69f1da2ca3718a077c7549e49925b0fa06952690278e6a36ceca4ebe492de934ac9dea98343dd7#npm:7.12.13"],\
["@babel/core", "npm:7.18.5"],\
["@babel/helper-plugin-utils", "npm:7.17.12"],\
["@types/babel__core", "npm:7.1.19"]\
@ -418,10 +423,10 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
],\
"linkType": "SOFT"\
}],\
["virtual:511f18ec4797ab42da93993c808942012470709446ef400bc600a66a60e5e382e8081da234db4fe66d41d38d9212735169fb3c604c828d1ff8dc854506ed7b48#npm:7.10.4", {\
"packageLocation": "./.yarn/__virtual__/@babel-plugin-syntax-import-meta-virtual-f94e1adec9/0/cache/@babel-plugin-syntax-import-meta-npm-7.10.4-4a0a0158bc-166ac1125d.zip/node_modules/@babel/plugin-syntax-import-meta/",\
["virtual:c373874e1baa9d76ffb6ce2109646b13db6b2b433728eac72e69f1da2ca3718a077c7549e49925b0fa06952690278e6a36ceca4ebe492de934ac9dea98343dd7#npm:7.10.4", {\
"packageLocation": "./.yarn/__virtual__/@babel-plugin-syntax-import-meta-virtual-f872deb0c7/0/cache/@babel-plugin-syntax-import-meta-npm-7.10.4-4a0a0158bc-166ac1125d.zip/node_modules/@babel/plugin-syntax-import-meta/",\
"packageDependencies": [\
["@babel/plugin-syntax-import-meta", "virtual:511f18ec4797ab42da93993c808942012470709446ef400bc600a66a60e5e382e8081da234db4fe66d41d38d9212735169fb3c604c828d1ff8dc854506ed7b48#npm:7.10.4"],\
["@babel/plugin-syntax-import-meta", "virtual:c373874e1baa9d76ffb6ce2109646b13db6b2b433728eac72e69f1da2ca3718a077c7549e49925b0fa06952690278e6a36ceca4ebe492de934ac9dea98343dd7#npm:7.10.4"],\
["@babel/core", "npm:7.18.5"],\
["@babel/helper-plugin-utils", "npm:7.17.12"],\
["@types/babel__core", "npm:7.1.19"]\
@ -455,10 +460,10 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
],\
"linkType": "SOFT"\
}],\
["virtual:511f18ec4797ab42da93993c808942012470709446ef400bc600a66a60e5e382e8081da234db4fe66d41d38d9212735169fb3c604c828d1ff8dc854506ed7b48#npm:7.8.3", {\
"packageLocation": "./.yarn/__virtual__/@babel-plugin-syntax-json-strings-virtual-b788fe66a9/0/cache/@babel-plugin-syntax-json-strings-npm-7.8.3-6dc7848179-bf5aea1f31.zip/node_modules/@babel/plugin-syntax-json-strings/",\
["virtual:c373874e1baa9d76ffb6ce2109646b13db6b2b433728eac72e69f1da2ca3718a077c7549e49925b0fa06952690278e6a36ceca4ebe492de934ac9dea98343dd7#npm:7.8.3", {\
"packageLocation": "./.yarn/__virtual__/@babel-plugin-syntax-json-strings-virtual-b38277fec2/0/cache/@babel-plugin-syntax-json-strings-npm-7.8.3-6dc7848179-bf5aea1f31.zip/node_modules/@babel/plugin-syntax-json-strings/",\
"packageDependencies": [\
["@babel/plugin-syntax-json-strings", "virtual:511f18ec4797ab42da93993c808942012470709446ef400bc600a66a60e5e382e8081da234db4fe66d41d38d9212735169fb3c604c828d1ff8dc854506ed7b48#npm:7.8.3"],\
["@babel/plugin-syntax-json-strings", "virtual:c373874e1baa9d76ffb6ce2109646b13db6b2b433728eac72e69f1da2ca3718a077c7549e49925b0fa06952690278e6a36ceca4ebe492de934ac9dea98343dd7#npm:7.8.3"],\
["@babel/core", "npm:7.18.5"],\
["@babel/helper-plugin-utils", "npm:7.17.12"],\
["@types/babel__core", "npm:7.1.19"]\
@ -492,10 +497,10 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
],\
"linkType": "SOFT"\
}],\
["virtual:511f18ec4797ab42da93993c808942012470709446ef400bc600a66a60e5e382e8081da234db4fe66d41d38d9212735169fb3c604c828d1ff8dc854506ed7b48#npm:7.10.4", {\
"packageLocation": "./.yarn/__virtual__/@babel-plugin-syntax-logical-assignment-operators-virtual-00106656c0/0/cache/@babel-plugin-syntax-logical-assignment-operators-npm-7.10.4-72ae00fdf6-aff3357703.zip/node_modules/@babel/plugin-syntax-logical-assignment-operators/",\
["virtual:c373874e1baa9d76ffb6ce2109646b13db6b2b433728eac72e69f1da2ca3718a077c7549e49925b0fa06952690278e6a36ceca4ebe492de934ac9dea98343dd7#npm:7.10.4", {\
"packageLocation": "./.yarn/__virtual__/@babel-plugin-syntax-logical-assignment-operators-virtual-f4f5745024/0/cache/@babel-plugin-syntax-logical-assignment-operators-npm-7.10.4-72ae00fdf6-aff3357703.zip/node_modules/@babel/plugin-syntax-logical-assignment-operators/",\
"packageDependencies": [\
["@babel/plugin-syntax-logical-assignment-operators", "virtual:511f18ec4797ab42da93993c808942012470709446ef400bc600a66a60e5e382e8081da234db4fe66d41d38d9212735169fb3c604c828d1ff8dc854506ed7b48#npm:7.10.4"],\
["@babel/plugin-syntax-logical-assignment-operators", "virtual:c373874e1baa9d76ffb6ce2109646b13db6b2b433728eac72e69f1da2ca3718a077c7549e49925b0fa06952690278e6a36ceca4ebe492de934ac9dea98343dd7#npm:7.10.4"],\
["@babel/core", "npm:7.18.5"],\
["@babel/helper-plugin-utils", "npm:7.17.12"],\
["@types/babel__core", "npm:7.1.19"]\
@ -529,10 +534,10 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
],\
"linkType": "SOFT"\
}],\
["virtual:511f18ec4797ab42da93993c808942012470709446ef400bc600a66a60e5e382e8081da234db4fe66d41d38d9212735169fb3c604c828d1ff8dc854506ed7b48#npm:7.8.3", {\
"packageLocation": "./.yarn/__virtual__/@babel-plugin-syntax-nullish-coalescing-operator-virtual-0e94aeb633/0/cache/@babel-plugin-syntax-nullish-coalescing-operator-npm-7.8.3-8a723173b5-87aca49189.zip/node_modules/@babel/plugin-syntax-nullish-coalescing-operator/",\
["virtual:c373874e1baa9d76ffb6ce2109646b13db6b2b433728eac72e69f1da2ca3718a077c7549e49925b0fa06952690278e6a36ceca4ebe492de934ac9dea98343dd7#npm:7.8.3", {\
"packageLocation": "./.yarn/__virtual__/@babel-plugin-syntax-nullish-coalescing-operator-virtual-7cd00d383d/0/cache/@babel-plugin-syntax-nullish-coalescing-operator-npm-7.8.3-8a723173b5-87aca49189.zip/node_modules/@babel/plugin-syntax-nullish-coalescing-operator/",\
"packageDependencies": [\
["@babel/plugin-syntax-nullish-coalescing-operator", "virtual:511f18ec4797ab42da93993c808942012470709446ef400bc600a66a60e5e382e8081da234db4fe66d41d38d9212735169fb3c604c828d1ff8dc854506ed7b48#npm:7.8.3"],\
["@babel/plugin-syntax-nullish-coalescing-operator", "virtual:c373874e1baa9d76ffb6ce2109646b13db6b2b433728eac72e69f1da2ca3718a077c7549e49925b0fa06952690278e6a36ceca4ebe492de934ac9dea98343dd7#npm:7.8.3"],\
["@babel/core", "npm:7.18.5"],\
["@babel/helper-plugin-utils", "npm:7.17.12"],\
["@types/babel__core", "npm:7.1.19"]\
@ -566,10 +571,10 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
],\
"linkType": "SOFT"\
}],\
["virtual:511f18ec4797ab42da93993c808942012470709446ef400bc600a66a60e5e382e8081da234db4fe66d41d38d9212735169fb3c604c828d1ff8dc854506ed7b48#npm:7.10.4", {\
"packageLocation": "./.yarn/__virtual__/@babel-plugin-syntax-numeric-separator-virtual-ae7862c24d/0/cache/@babel-plugin-syntax-numeric-separator-npm-7.10.4-81444be605-01ec5547bd.zip/node_modules/@babel/plugin-syntax-numeric-separator/",\
["virtual:c373874e1baa9d76ffb6ce2109646b13db6b2b433728eac72e69f1da2ca3718a077c7549e49925b0fa06952690278e6a36ceca4ebe492de934ac9dea98343dd7#npm:7.10.4", {\
"packageLocation": "./.yarn/__virtual__/@babel-plugin-syntax-numeric-separator-virtual-1f39443676/0/cache/@babel-plugin-syntax-numeric-separator-npm-7.10.4-81444be605-01ec5547bd.zip/node_modules/@babel/plugin-syntax-numeric-separator/",\
"packageDependencies": [\
["@babel/plugin-syntax-numeric-separator", "virtual:511f18ec4797ab42da93993c808942012470709446ef400bc600a66a60e5e382e8081da234db4fe66d41d38d9212735169fb3c604c828d1ff8dc854506ed7b48#npm:7.10.4"],\
["@babel/plugin-syntax-numeric-separator", "virtual:c373874e1baa9d76ffb6ce2109646b13db6b2b433728eac72e69f1da2ca3718a077c7549e49925b0fa06952690278e6a36ceca4ebe492de934ac9dea98343dd7#npm:7.10.4"],\
["@babel/core", "npm:7.18.5"],\
["@babel/helper-plugin-utils", "npm:7.17.12"],\
["@types/babel__core", "npm:7.1.19"]\
@ -603,10 +608,10 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
],\
"linkType": "SOFT"\
}],\
["virtual:511f18ec4797ab42da93993c808942012470709446ef400bc600a66a60e5e382e8081da234db4fe66d41d38d9212735169fb3c604c828d1ff8dc854506ed7b48#npm:7.8.3", {\
"packageLocation": "./.yarn/__virtual__/@babel-plugin-syntax-object-rest-spread-virtual-70d6d17cdf/0/cache/@babel-plugin-syntax-object-rest-spread-npm-7.8.3-60bd05b6ae-fddcf581a5.zip/node_modules/@babel/plugin-syntax-object-rest-spread/",\
["virtual:c373874e1baa9d76ffb6ce2109646b13db6b2b433728eac72e69f1da2ca3718a077c7549e49925b0fa06952690278e6a36ceca4ebe492de934ac9dea98343dd7#npm:7.8.3", {\
"packageLocation": "./.yarn/__virtual__/@babel-plugin-syntax-object-rest-spread-virtual-e635fc53df/0/cache/@babel-plugin-syntax-object-rest-spread-npm-7.8.3-60bd05b6ae-fddcf581a5.zip/node_modules/@babel/plugin-syntax-object-rest-spread/",\
"packageDependencies": [\
["@babel/plugin-syntax-object-rest-spread", "virtual:511f18ec4797ab42da93993c808942012470709446ef400bc600a66a60e5e382e8081da234db4fe66d41d38d9212735169fb3c604c828d1ff8dc854506ed7b48#npm:7.8.3"],\
["@babel/plugin-syntax-object-rest-spread", "virtual:c373874e1baa9d76ffb6ce2109646b13db6b2b433728eac72e69f1da2ca3718a077c7549e49925b0fa06952690278e6a36ceca4ebe492de934ac9dea98343dd7#npm:7.8.3"],\
["@babel/core", "npm:7.18.5"],\
["@babel/helper-plugin-utils", "npm:7.17.12"],\
["@types/babel__core", "npm:7.1.19"]\
@ -640,10 +645,10 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
],\
"linkType": "SOFT"\
}],\
["virtual:511f18ec4797ab42da93993c808942012470709446ef400bc600a66a60e5e382e8081da234db4fe66d41d38d9212735169fb3c604c828d1ff8dc854506ed7b48#npm:7.8.3", {\
"packageLocation": "./.yarn/__virtual__/@babel-plugin-syntax-optional-catch-binding-virtual-19cae39d4e/0/cache/@babel-plugin-syntax-optional-catch-binding-npm-7.8.3-ce337427d8-910d90e72b.zip/node_modules/@babel/plugin-syntax-optional-catch-binding/",\
["virtual:c373874e1baa9d76ffb6ce2109646b13db6b2b433728eac72e69f1da2ca3718a077c7549e49925b0fa06952690278e6a36ceca4ebe492de934ac9dea98343dd7#npm:7.8.3", {\
"packageLocation": "./.yarn/__virtual__/@babel-plugin-syntax-optional-catch-binding-virtual-7faf06b837/0/cache/@babel-plugin-syntax-optional-catch-binding-npm-7.8.3-ce337427d8-910d90e72b.zip/node_modules/@babel/plugin-syntax-optional-catch-binding/",\
"packageDependencies": [\
["@babel/plugin-syntax-optional-catch-binding", "virtual:511f18ec4797ab42da93993c808942012470709446ef400bc600a66a60e5e382e8081da234db4fe66d41d38d9212735169fb3c604c828d1ff8dc854506ed7b48#npm:7.8.3"],\
["@babel/plugin-syntax-optional-catch-binding", "virtual:c373874e1baa9d76ffb6ce2109646b13db6b2b433728eac72e69f1da2ca3718a077c7549e49925b0fa06952690278e6a36ceca4ebe492de934ac9dea98343dd7#npm:7.8.3"],\
["@babel/core", "npm:7.18.5"],\
["@babel/helper-plugin-utils", "npm:7.17.12"],\
["@types/babel__core", "npm:7.1.19"]\
@ -677,10 +682,10 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
],\
"linkType": "SOFT"\
}],\
["virtual:511f18ec4797ab42da93993c808942012470709446ef400bc600a66a60e5e382e8081da234db4fe66d41d38d9212735169fb3c604c828d1ff8dc854506ed7b48#npm:7.8.3", {\
"packageLocation": "./.yarn/__virtual__/@babel-plugin-syntax-optional-chaining-virtual-0faf2c8e15/0/cache/@babel-plugin-syntax-optional-chaining-npm-7.8.3-f3f3c79579-eef94d53a1.zip/node_modules/@babel/plugin-syntax-optional-chaining/",\
["virtual:c373874e1baa9d76ffb6ce2109646b13db6b2b433728eac72e69f1da2ca3718a077c7549e49925b0fa06952690278e6a36ceca4ebe492de934ac9dea98343dd7#npm:7.8.3", {\
"packageLocation": "./.yarn/__virtual__/@babel-plugin-syntax-optional-chaining-virtual-cd0a3a9619/0/cache/@babel-plugin-syntax-optional-chaining-npm-7.8.3-f3f3c79579-eef94d53a1.zip/node_modules/@babel/plugin-syntax-optional-chaining/",\
"packageDependencies": [\
["@babel/plugin-syntax-optional-chaining", "virtual:511f18ec4797ab42da93993c808942012470709446ef400bc600a66a60e5e382e8081da234db4fe66d41d38d9212735169fb3c604c828d1ff8dc854506ed7b48#npm:7.8.3"],\
["@babel/plugin-syntax-optional-chaining", "virtual:c373874e1baa9d76ffb6ce2109646b13db6b2b433728eac72e69f1da2ca3718a077c7549e49925b0fa06952690278e6a36ceca4ebe492de934ac9dea98343dd7#npm:7.8.3"],\
["@babel/core", "npm:7.18.5"],\
["@babel/helper-plugin-utils", "npm:7.17.12"],\
["@types/babel__core", "npm:7.1.19"]\
@ -714,10 +719,10 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
],\
"linkType": "SOFT"\
}],\
["virtual:511f18ec4797ab42da93993c808942012470709446ef400bc600a66a60e5e382e8081da234db4fe66d41d38d9212735169fb3c604c828d1ff8dc854506ed7b48#npm:7.14.5", {\
"packageLocation": "./.yarn/__virtual__/@babel-plugin-syntax-top-level-await-virtual-4f23029d05/0/cache/@babel-plugin-syntax-top-level-await-npm-7.14.5-60a0a2e83b-bbd1a56b09.zip/node_modules/@babel/plugin-syntax-top-level-await/",\
["virtual:c373874e1baa9d76ffb6ce2109646b13db6b2b433728eac72e69f1da2ca3718a077c7549e49925b0fa06952690278e6a36ceca4ebe492de934ac9dea98343dd7#npm:7.14.5", {\
"packageLocation": "./.yarn/__virtual__/@babel-plugin-syntax-top-level-await-virtual-ea96b657a7/0/cache/@babel-plugin-syntax-top-level-await-npm-7.14.5-60a0a2e83b-bbd1a56b09.zip/node_modules/@babel/plugin-syntax-top-level-await/",\
"packageDependencies": [\
["@babel/plugin-syntax-top-level-await", "virtual:511f18ec4797ab42da93993c808942012470709446ef400bc600a66a60e5e382e8081da234db4fe66d41d38d9212735169fb3c604c828d1ff8dc854506ed7b48#npm:7.14.5"],\
["@babel/plugin-syntax-top-level-await", "virtual:c373874e1baa9d76ffb6ce2109646b13db6b2b433728eac72e69f1da2ca3718a077c7549e49925b0fa06952690278e6a36ceca4ebe492de934ac9dea98343dd7#npm:7.14.5"],\
["@babel/core", "npm:7.18.5"],\
["@babel/helper-plugin-utils", "npm:7.17.12"],\
["@types/babel__core", "npm:7.1.19"]\
@ -1201,12 +1206,12 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
],\
"linkType": "SOFT"\
}],\
["virtual:6e20fd2eaaa940ccda315da6252a82baa2918f5ea3c40e2d7cb4d97f01b503d35a5076b4b63a33762fb1174e73a3313072cadf65e4a26d1b33660f964eda7880#npm:28.1.1", {\
"packageLocation": "./.yarn/__virtual__/@jest-core-virtual-4b45c3242e/0/cache/@jest-core-npm-28.1.1-fb910fbf90-fd4361f77b.zip/node_modules/@jest/core/",\
["virtual:c6df9164d8eb22b719e1cd2b32bc24b485c129a3b28db796c6f3a6c22ad9410bc922a7f59cbbfe78d6492c9ee9f75de8ed7c2ec2f694d58e500097e2a2b74028#npm:28.1.1", {\
"packageLocation": "./.yarn/__virtual__/@jest-core-virtual-c650136efc/0/cache/@jest-core-npm-28.1.1-fb910fbf90-fd4361f77b.zip/node_modules/@jest/core/",\
"packageDependencies": [\
["@jest/core", "virtual:6e20fd2eaaa940ccda315da6252a82baa2918f5ea3c40e2d7cb4d97f01b503d35a5076b4b63a33762fb1174e73a3313072cadf65e4a26d1b33660f964eda7880#npm:28.1.1"],\
["@jest/core", "virtual:c6df9164d8eb22b719e1cd2b32bc24b485c129a3b28db796c6f3a6c22ad9410bc922a7f59cbbfe78d6492c9ee9f75de8ed7c2ec2f694d58e500097e2a2b74028#npm:28.1.1"],\
["@jest/console", "npm:28.1.1"],\
["@jest/reporters", "virtual:4b45c3242ed36b84511b3946081e5d3b347e0463d6e39ebfdee2ad8392eb4bd7a5761a69e4fccf0d230c488b171720ddcf381e7c249fe8f4fcdf9d4afc493b87#npm:28.1.1"],\
["@jest/reporters", "virtual:c650136efccbbb479cf2d5b8062777752af30a07867b5d5c0bde3cbc6a5c6f054afdfcc433e9ded24592d8fb0664ba4983e4f355a06678d9210b9c9e34f577b9#npm:28.1.1"],\
["@jest/test-result", "npm:28.1.1"],\
["@jest/transform", "npm:28.1.1"],\
["@jest/types", "npm:28.1.1"],\
@ -1218,7 +1223,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
["exit", "npm:0.1.2"],\
["graceful-fs", "npm:4.2.10"],\
["jest-changed-files", "npm:28.0.2"],\
["jest-config", "virtual:4b45c3242ed36b84511b3946081e5d3b347e0463d6e39ebfdee2ad8392eb4bd7a5761a69e4fccf0d230c488b171720ddcf381e7c249fe8f4fcdf9d4afc493b87#npm:28.1.1"],\
["jest-config", "virtual:c650136efccbbb479cf2d5b8062777752af30a07867b5d5c0bde3cbc6a5c6f054afdfcc433e9ded24592d8fb0664ba4983e4f355a06678d9210b9c9e34f577b9#npm:28.1.1"],\
["jest-haste-map", "npm:28.1.1"],\
["jest-message-util", "npm:28.1.1"],\
["jest-regex-util", "npm:28.0.2"],\
@ -1313,10 +1318,10 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
],\
"linkType": "SOFT"\
}],\
["virtual:4b45c3242ed36b84511b3946081e5d3b347e0463d6e39ebfdee2ad8392eb4bd7a5761a69e4fccf0d230c488b171720ddcf381e7c249fe8f4fcdf9d4afc493b87#npm:28.1.1", {\
"packageLocation": "./.yarn/__virtual__/@jest-reporters-virtual-ea52091ed4/0/cache/@jest-reporters-npm-28.1.1-21fe131d02-8ad68d4a93.zip/node_modules/@jest/reporters/",\
["virtual:c650136efccbbb479cf2d5b8062777752af30a07867b5d5c0bde3cbc6a5c6f054afdfcc433e9ded24592d8fb0664ba4983e4f355a06678d9210b9c9e34f577b9#npm:28.1.1", {\
"packageLocation": "./.yarn/__virtual__/@jest-reporters-virtual-4b439ab9a1/0/cache/@jest-reporters-npm-28.1.1-21fe131d02-8ad68d4a93.zip/node_modules/@jest/reporters/",\
"packageDependencies": [\
["@jest/reporters", "virtual:4b45c3242ed36b84511b3946081e5d3b347e0463d6e39ebfdee2ad8392eb4bd7a5761a69e4fccf0d230c488b171720ddcf381e7c249fe8f4fcdf9d4afc493b87#npm:28.1.1"],\
["@jest/reporters", "virtual:c650136efccbbb479cf2d5b8062777752af30a07867b5d5c0bde3cbc6a5c6f054afdfcc433e9ded24592d8fb0664ba4983e4f355a06678d9210b9c9e34f577b9#npm:28.1.1"],\
["@bcoe/v8-coverage", "npm:0.2.3"],\
["@jest/console", "npm:28.1.1"],\
["@jest/test-result", "npm:28.1.1"],\
@ -1944,10 +1949,10 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
],\
"linkType": "SOFT"\
}],\
["virtual:31b5a94a105c89c9294c3d524a7f8929fe63ee5a2efadf21951ca4c0cfd2ecf02e8f4ef5a066bbda091f1e3a56e57c6749069a080618c96b22e51131a330fc4a#npm:2.1.2", {\
"packageLocation": "./.yarn/__virtual__/@newrelic-winston-enricher-virtual-30a09b12b4/0/cache/@newrelic-winston-enricher-npm-2.1.2-732878a1b2-d001c13166.zip/node_modules/@newrelic/winston-enricher/",\
["virtual:04783e12400851b8a3d76e71495851cc94959db6e62f04cb0a31190080629440b182d8c8eb4d7f2b04e281912f2783a5fd4d2c3c6ab68d38b7097246c93f4c19#npm:2.1.2", {\
"packageLocation": "./.yarn/__virtual__/@newrelic-winston-enricher-virtual-193127fbcd/0/cache/@newrelic-winston-enricher-npm-2.1.2-732878a1b2-d001c13166.zip/node_modules/@newrelic/winston-enricher/",\
"packageDependencies": [\
["@newrelic/winston-enricher", "virtual:31b5a94a105c89c9294c3d524a7f8929fe63ee5a2efadf21951ca4c0cfd2ecf02e8f4ef5a066bbda091f1e3a56e57c6749069a080618c96b22e51131a330fc4a#npm:2.1.2"],\
["@newrelic/winston-enricher", "virtual:04783e12400851b8a3d76e71495851cc94959db6e62f04cb0a31190080629440b182d8c8eb4d7f2b04e281912f2783a5fd4d2c3c6ab68d38b7097246c93f4c19#npm:2.1.2"],\
["@types/newrelic", "npm:7.0.3"],\
["@types/winston", null],\
["newrelic", "npm:8.6.0"],\
@ -2584,7 +2589,60 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
"linkType": "HARD"\
}]\
]],\
["@standardnotes/api-gateway", [\
["workspace:packages/api-gateway", {\
"packageLocation": "./packages/api-gateway/",\
"packageDependencies": [\
["@standardnotes/api-gateway", "workspace:packages/api-gateway"],\
["@newrelic/native-metrics", "npm:7.0.2"],\
["@newrelic/winston-enricher", "virtual:04783e12400851b8a3d76e71495851cc94959db6e62f04cb0a31190080629440b182d8c8eb4d7f2b04e281912f2783a5fd4d2c3c6ab68d38b7097246c93f4c19#npm:2.1.2"],\
["@sentry/node", "npm:6.19.7"],\
["@standardnotes/analytics", "npm:1.6.0"],\
["@standardnotes/auth", "npm:3.19.2"],\
["@standardnotes/domain-events", "npm:2.29.0"],\
["@standardnotes/domain-events-infra", "npm:1.4.127"],\
["@standardnotes/time", "npm:1.7.0"],\
["@types/cors", "npm:2.8.12"],\
["@types/express", "npm:4.17.13"],\
["@types/ioredis", "npm:4.28.10"],\
["@types/jest", "npm:28.1.3"],\
["@types/jsonwebtoken", "npm:8.5.8"],\
["@types/newrelic", "npm:7.0.3"],\
["@types/prettyjson", "npm:0.0.29"],\
["@typescript-eslint/eslint-plugin", "virtual:04783e12400851b8a3d76e71495851cc94959db6e62f04cb0a31190080629440b182d8c8eb4d7f2b04e281912f2783a5fd4d2c3c6ab68d38b7097246c93f4c19#npm:5.29.0"],\
["aws-sdk", "npm:2.1160.0"],\
["axios", "npm:0.24.0"],\
["cors", "npm:2.8.5"],\
["dotenv", "npm:8.2.0"],\
["eslint", "npm:8.18.0"],\
["eslint-plugin-prettier", "virtual:04783e12400851b8a3d76e71495851cc94959db6e62f04cb0a31190080629440b182d8c8eb4d7f2b04e281912f2783a5fd4d2c3c6ab68d38b7097246c93f4c19#npm:4.0.0"],\
["express", "npm:4.17.1"],\
["helmet", "npm:4.4.1"],\
["inversify", "npm:6.0.1"],\
["inversify-express-utils", "npm:6.4.3"],\
["ioredis", "npm:5.0.6"],\
["jest", "virtual:04783e12400851b8a3d76e71495851cc94959db6e62f04cb0a31190080629440b182d8c8eb4d7f2b04e281912f2783a5fd4d2c3c6ab68d38b7097246c93f4c19#npm:28.1.1"],\
["jsonwebtoken", "npm:8.5.1"],\
["newrelic", "npm:8.6.0"],\
["nodemon", "npm:2.0.16"],\
["prettyjson", "npm:1.2.1"],\
["reflect-metadata", "npm:0.1.13"],\
["ts-jest", "virtual:04783e12400851b8a3d76e71495851cc94959db6e62f04cb0a31190080629440b182d8c8eb4d7f2b04e281912f2783a5fd4d2c3c6ab68d38b7097246c93f4c19#npm:28.0.5"],\
["winston", "npm:3.3.3"]\
],\
"linkType": "SOFT"\
}]\
]],\
["@standardnotes/auth", [\
["npm:3.19.2", {\
"packageLocation": "./.yarn/cache/@standardnotes-auth-npm-3.19.2-5289525e60-2e4b37b303.zip/node_modules/@standardnotes/auth/",\
"packageDependencies": [\
["@standardnotes/auth", "npm:3.19.2"],\
["@standardnotes/common", "npm:1.23.0"],\
["jsonwebtoken", "npm:8.5.1"]\
],\
"linkType": "HARD"\
}],\
["npm:3.19.3", {\
"packageLocation": "./.yarn/cache/@standardnotes-auth-npm-3.19.3-c77ec60e52-7e421b5eaf.zip/node_modules/@standardnotes/auth/",\
"packageDependencies": [\
@ -2601,7 +2659,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
"packageDependencies": [\
["@standardnotes/auth-server", "workspace:packages/auth"],\
["@newrelic/native-metrics", "npm:7.0.2"],\
["@newrelic/winston-enricher", "virtual:31b5a94a105c89c9294c3d524a7f8929fe63ee5a2efadf21951ca4c0cfd2ecf02e8f4ef5a066bbda091f1e3a56e57c6749069a080618c96b22e51131a330fc4a#npm:2.1.2"],\
["@newrelic/winston-enricher", "virtual:04783e12400851b8a3d76e71495851cc94959db6e62f04cb0a31190080629440b182d8c8eb4d7f2b04e281912f2783a5fd4d2c3c6ab68d38b7097246c93f4c19#npm:2.1.2"],\
["@sentry/node", "npm:6.19.7"],\
["@standardnotes/analytics", "npm:1.6.0"],\
["@standardnotes/api", "npm:1.1.13"],\
@ -2626,7 +2684,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
["@types/prettyjson", "npm:0.0.29"],\
["@types/ua-parser-js", "npm:0.7.36"],\
["@types/uuid", "npm:8.3.4"],\
["@typescript-eslint/eslint-plugin", "virtual:31b5a94a105c89c9294c3d524a7f8929fe63ee5a2efadf21951ca4c0cfd2ecf02e8f4ef5a066bbda091f1e3a56e57c6749069a080618c96b22e51131a330fc4a#npm:5.29.0"],\
["@typescript-eslint/eslint-plugin", "virtual:04783e12400851b8a3d76e71495851cc94959db6e62f04cb0a31190080629440b182d8c8eb4d7f2b04e281912f2783a5fd4d2c3c6ab68d38b7097246c93f4c19#npm:5.29.0"],\
["aws-sdk", "npm:2.1159.0"],\
["axios", "npm:0.24.0"],\
["bcryptjs", "npm:2.4.3"],\
@ -2635,19 +2693,19 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
["dayjs", "npm:1.11.3"],\
["dotenv", "npm:8.2.0"],\
["eslint", "npm:8.18.0"],\
["eslint-plugin-prettier", "virtual:31b5a94a105c89c9294c3d524a7f8929fe63ee5a2efadf21951ca4c0cfd2ecf02e8f4ef5a066bbda091f1e3a56e57c6749069a080618c96b22e51131a330fc4a#npm:4.0.0"],\
["eslint-plugin-prettier", "virtual:04783e12400851b8a3d76e71495851cc94959db6e62f04cb0a31190080629440b182d8c8eb4d7f2b04e281912f2783a5fd4d2c3c6ab68d38b7097246c93f4c19#npm:4.0.0"],\
["express", "npm:4.17.1"],\
["inversify", "npm:6.0.1"],\
["inversify-express-utils", "npm:6.4.3"],\
["ioredis", "npm:5.0.6"],\
["jest", "virtual:31b5a94a105c89c9294c3d524a7f8929fe63ee5a2efadf21951ca4c0cfd2ecf02e8f4ef5a066bbda091f1e3a56e57c6749069a080618c96b22e51131a330fc4a#npm:28.1.1"],\
["jest", "virtual:04783e12400851b8a3d76e71495851cc94959db6e62f04cb0a31190080629440b182d8c8eb4d7f2b04e281912f2783a5fd4d2c3c6ab68d38b7097246c93f4c19#npm:28.1.1"],\
["mysql2", "npm:2.3.3"],\
["newrelic", "npm:8.6.0"],\
["nodemon", "npm:2.0.16"],\
["otplib", "npm:12.0.1"],\
["prettyjson", "npm:1.2.1"],\
["reflect-metadata", "npm:0.1.13"],\
["ts-jest", "virtual:31b5a94a105c89c9294c3d524a7f8929fe63ee5a2efadf21951ca4c0cfd2ecf02e8f4ef5a066bbda091f1e3a56e57c6749069a080618c96b22e51131a330fc4a#npm:28.0.5"],\
["ts-jest", "virtual:04783e12400851b8a3d76e71495851cc94959db6e62f04cb0a31190080629440b182d8c8eb4d7f2b04e281912f2783a5fd4d2c3c6ab68d38b7097246c93f4c19#npm:28.0.5"],\
["typeorm", "virtual:31b5a94a105c89c9294c3d524a7f8929fe63ee5a2efadf21951ca4c0cfd2ecf02e8f4ef5a066bbda091f1e3a56e57c6749069a080618c96b22e51131a330fc4a#npm:0.3.6"],\
["ua-parser-js", "npm:1.0.2"],\
["uuid", "npm:8.3.2"],\
@ -2677,6 +2735,15 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
}]\
]],\
["@standardnotes/domain-events", [\
["npm:2.29.0", {\
"packageLocation": "./.yarn/cache/@standardnotes-domain-events-npm-2.29.0-13bec3d9a7-1b68999e2a.zip/node_modules/@standardnotes/domain-events/",\
"packageDependencies": [\
["@standardnotes/domain-events", "npm:2.29.0"],\
["@standardnotes/auth", "npm:3.19.3"],\
["@standardnotes/features", "npm:1.45.5"]\
],\
"linkType": "HARD"\
}],\
["npm:2.32.2", {\
"packageLocation": "./.yarn/cache/@standardnotes-domain-events-npm-2.32.2-73adf7a999-54da5fc885.zip/node_modules/@standardnotes/domain-events/",\
"packageDependencies": [\
@ -2689,6 +2756,20 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
}]\
]],\
["@standardnotes/domain-events-infra", [\
["npm:1.4.127", {\
"packageLocation": "./.yarn/cache/@standardnotes-domain-events-infra-npm-1.4.127-18a82f2f72-54e37c296f.zip/node_modules/@standardnotes/domain-events-infra/",\
"packageDependencies": [\
["@standardnotes/domain-events-infra", "npm:1.4.127"],\
["@standardnotes/domain-events", "npm:2.32.2"],\
["aws-sdk", "npm:2.1157.0"],\
["ioredis", "npm:4.28.5"],\
["newrelic", "npm:8.14.1"],\
["reflect-metadata", "npm:0.1.13"],\
["sqs-consumer", "virtual:18a82f2f722cf47811304317f79c07fee6ecd8a887f3af2b42c72227f3b982ee05bf525aa8a7d2d20252ed23a1ed39ca99b430ed432b264a8bad79965c0cae5e#npm:5.7.0"],\
["winston", "npm:3.7.2"]\
],\
"linkType": "HARD"\
}],\
["npm:1.5.2", {\
"packageLocation": "./.yarn/cache/@standardnotes-domain-events-infra-npm-1.5.2-948d77a715-b027dfd329.zip/node_modules/@standardnotes/domain-events-infra/",\
"packageDependencies": [\
@ -2698,7 +2779,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
["ioredis", "npm:4.28.5"],\
["newrelic", "npm:8.14.1"],\
["reflect-metadata", "npm:0.1.13"],\
["sqs-consumer", "virtual:948d77a715c2182e00f2764d231af7afb02bf7f3164cf05b0248dc116c0a54912c77c570591f08b5b0dbb55984329e93f5f5704931076ba8f07fc46d4a69d072#npm:5.7.0"],\
["sqs-consumer", "virtual:18a82f2f722cf47811304317f79c07fee6ecd8a887f3af2b42c72227f3b982ee05bf525aa8a7d2d20252ed23a1ed39ca99b430ed432b264a8bad79965c0cae5e#npm:5.7.0"],\
["winston", "npm:3.7.2"]\
],\
"linkType": "HARD"\
@ -2751,27 +2832,27 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
["@types/newrelic", "npm:7.0.3"],\
["@types/prettyjson", "npm:0.0.29"],\
["@types/uuid", "npm:8.3.4"],\
["@typescript-eslint/eslint-plugin", "virtual:31b5a94a105c89c9294c3d524a7f8929fe63ee5a2efadf21951ca4c0cfd2ecf02e8f4ef5a066bbda091f1e3a56e57c6749069a080618c96b22e51131a330fc4a#npm:5.29.0"],\
["@typescript-eslint/eslint-plugin", "virtual:04783e12400851b8a3d76e71495851cc94959db6e62f04cb0a31190080629440b182d8c8eb4d7f2b04e281912f2783a5fd4d2c3c6ab68d38b7097246c93f4c19#npm:5.29.0"],\
["aws-sdk", "npm:2.1158.0"],\
["connect-busboy", "npm:1.0.0"],\
["cors", "npm:2.8.5"],\
["dayjs", "npm:1.11.3"],\
["dotenv", "npm:8.6.0"],\
["eslint", "npm:8.18.0"],\
["eslint-plugin-prettier", "virtual:31b5a94a105c89c9294c3d524a7f8929fe63ee5a2efadf21951ca4c0cfd2ecf02e8f4ef5a066bbda091f1e3a56e57c6749069a080618c96b22e51131a330fc4a#npm:4.0.0"],\
["eslint-plugin-prettier", "virtual:04783e12400851b8a3d76e71495851cc94959db6e62f04cb0a31190080629440b182d8c8eb4d7f2b04e281912f2783a5fd4d2c3c6ab68d38b7097246c93f4c19#npm:4.0.0"],\
["express", "npm:4.18.1"],\
["express-winston", "virtual:b442cf0427cc365d1c137f7340f9b81f9b204561afe791a8564ae9590c3a7fc4b5f793aaf8817b946f75a3cb64d03ef8790eb847f8b576b41e700da7b00c240c#npm:4.2.0"],\
["helmet", "npm:4.6.0"],\
["inversify", "npm:6.0.1"],\
["inversify-express-utils", "npm:6.4.3"],\
["ioredis", "npm:5.0.6"],\
["jest", "virtual:31b5a94a105c89c9294c3d524a7f8929fe63ee5a2efadf21951ca4c0cfd2ecf02e8f4ef5a066bbda091f1e3a56e57c6749069a080618c96b22e51131a330fc4a#npm:28.1.1"],\
["jest", "virtual:04783e12400851b8a3d76e71495851cc94959db6e62f04cb0a31190080629440b182d8c8eb4d7f2b04e281912f2783a5fd4d2c3c6ab68d38b7097246c93f4c19#npm:28.1.1"],\
["jsonwebtoken", "npm:8.5.1"],\
["newrelic", "npm:7.5.2"],\
["nodemon", "npm:2.0.16"],\
["prettyjson", "npm:1.2.5"],\
["reflect-metadata", "npm:0.1.13"],\
["ts-jest", "virtual:31b5a94a105c89c9294c3d524a7f8929fe63ee5a2efadf21951ca4c0cfd2ecf02e8f4ef5a066bbda091f1e3a56e57c6749069a080618c96b22e51131a330fc4a#npm:28.0.5"],\
["ts-jest", "virtual:04783e12400851b8a3d76e71495851cc94959db6e62f04cb0a31190080629440b182d8c8eb4d7f2b04e281912f2783a5fd4d2c3c6ab68d38b7097246c93f4c19#npm:28.0.5"],\
["ts-node", "virtual:b442cf0427cc365d1c137f7340f9b81f9b204561afe791a8564ae9590c3a7fc4b5f793aaf8817b946f75a3cb64d03ef8790eb847f8b576b41e700da7b00c240c#npm:10.8.1"],\
["uuid", "npm:8.3.2"],\
["winston", "npm:3.7.2"]\
@ -2831,7 +2912,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
"packageDependencies": [\
["@standardnotes/scheduler-server", "workspace:packages/scheduler"],\
["@newrelic/native-metrics", "npm:7.0.2"],\
["@newrelic/winston-enricher", "virtual:31b5a94a105c89c9294c3d524a7f8929fe63ee5a2efadf21951ca4c0cfd2ecf02e8f4ef5a066bbda091f1e3a56e57c6749069a080618c96b22e51131a330fc4a#npm:2.1.2"],\
["@newrelic/winston-enricher", "virtual:04783e12400851b8a3d76e71495851cc94959db6e62f04cb0a31190080629440b182d8c8eb4d7f2b04e281912f2783a5fd4d2c3c6ab68d38b7097246c93f4c19#npm:2.1.2"],\
["@standardnotes/common", "npm:1.23.0"],\
["@standardnotes/domain-events", "npm:2.32.2"],\
["@standardnotes/domain-events-infra", "npm:1.5.2"],\
@ -2848,7 +2929,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
["eslint-plugin-prettier", "virtual:16bfd8597041deb71e4581ea0755edd4dcd1b09b8ab14bfbbf5e4d5ca6b5d47ed7fbe2a25cdf57fcbb8e092c30b6beb93d2e7533f9e31c5dc62f7f0e487d1e4b#npm:4.0.0"],\
["inversify", "npm:5.0.5"],\
["ioredis", "npm:5.0.6"],\
["jest", "virtual:31b5a94a105c89c9294c3d524a7f8929fe63ee5a2efadf21951ca4c0cfd2ecf02e8f4ef5a066bbda091f1e3a56e57c6749069a080618c96b22e51131a330fc4a#npm:28.1.1"],\
["jest", "virtual:04783e12400851b8a3d76e71495851cc94959db6e62f04cb0a31190080629440b182d8c8eb4d7f2b04e281912f2783a5fd4d2c3c6ab68d38b7097246c93f4c19#npm:28.1.1"],\
["mysql2", "npm:2.3.3"],\
["newrelic", "npm:8.6.0"],\
["reflect-metadata", "npm:0.1.13"],\
@ -2928,7 +3009,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
"packageDependencies": [\
["@standardnotes/syncing-server", "workspace:packages/syncing-server"],\
["@newrelic/native-metrics", "npm:7.0.2"],\
["@newrelic/winston-enricher", "virtual:31b5a94a105c89c9294c3d524a7f8929fe63ee5a2efadf21951ca4c0cfd2ecf02e8f4ef5a066bbda091f1e3a56e57c6749069a080618c96b22e51131a330fc4a#npm:2.1.2"],\
["@newrelic/winston-enricher", "virtual:04783e12400851b8a3d76e71495851cc94959db6e62f04cb0a31190080629440b182d8c8eb4d7f2b04e281912f2783a5fd4d2c3c6ab68d38b7097246c93f4c19#npm:2.1.2"],\
["@sentry/node", "npm:6.19.7"],\
["@standardnotes/analytics", "npm:1.6.0"],\
["@standardnotes/auth", "npm:3.19.3"],\
@ -2950,26 +3031,26 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
["@types/prettyjson", "npm:0.0.29"],\
["@types/ua-parser-js", "npm:0.7.36"],\
["@types/uuid", "npm:8.3.4"],\
["@typescript-eslint/eslint-plugin", "virtual:31b5a94a105c89c9294c3d524a7f8929fe63ee5a2efadf21951ca4c0cfd2ecf02e8f4ef5a066bbda091f1e3a56e57c6749069a080618c96b22e51131a330fc4a#npm:5.29.0"],\
["@typescript-eslint/eslint-plugin", "virtual:04783e12400851b8a3d76e71495851cc94959db6e62f04cb0a31190080629440b182d8c8eb4d7f2b04e281912f2783a5fd4d2c3c6ab68d38b7097246c93f4c19#npm:5.29.0"],\
["aws-sdk", "npm:2.1159.0"],\
["axios", "npm:0.24.0"],\
["cors", "npm:2.8.5"],\
["dotenv", "npm:8.2.0"],\
["eslint", "npm:8.18.0"],\
["eslint-plugin-prettier", "virtual:31b5a94a105c89c9294c3d524a7f8929fe63ee5a2efadf21951ca4c0cfd2ecf02e8f4ef5a066bbda091f1e3a56e57c6749069a080618c96b22e51131a330fc4a#npm:4.0.0"],\
["eslint-plugin-prettier", "virtual:04783e12400851b8a3d76e71495851cc94959db6e62f04cb0a31190080629440b182d8c8eb4d7f2b04e281912f2783a5fd4d2c3c6ab68d38b7097246c93f4c19#npm:4.0.0"],\
["express", "npm:4.17.1"],\
["helmet", "npm:4.3.1"],\
["inversify", "npm:6.0.1"],\
["inversify-express-utils", "npm:6.4.3"],\
["ioredis", "npm:5.0.6"],\
["jest", "virtual:31b5a94a105c89c9294c3d524a7f8929fe63ee5a2efadf21951ca4c0cfd2ecf02e8f4ef5a066bbda091f1e3a56e57c6749069a080618c96b22e51131a330fc4a#npm:28.1.1"],\
["jest", "virtual:04783e12400851b8a3d76e71495851cc94959db6e62f04cb0a31190080629440b182d8c8eb4d7f2b04e281912f2783a5fd4d2c3c6ab68d38b7097246c93f4c19#npm:28.1.1"],\
["jsonwebtoken", "npm:8.5.1"],\
["mysql2", "npm:2.3.3"],\
["newrelic", "npm:8.6.0"],\
["nodemon", "npm:2.0.7"],\
["prettyjson", "npm:1.2.1"],\
["reflect-metadata", "npm:0.1.13"],\
["ts-jest", "virtual:31b5a94a105c89c9294c3d524a7f8929fe63ee5a2efadf21951ca4c0cfd2ecf02e8f4ef5a066bbda091f1e3a56e57c6749069a080618c96b22e51131a330fc4a#npm:28.0.5"],\
["ts-jest", "virtual:04783e12400851b8a3d76e71495851cc94959db6e62f04cb0a31190080629440b182d8c8eb4d7f2b04e281912f2783a5fd4d2c3c6ab68d38b7097246c93f4c19#npm:28.0.5"],\
["typeorm", "virtual:31b5a94a105c89c9294c3d524a7f8929fe63ee5a2efadf21951ca4c0cfd2ecf02e8f4ef5a066bbda091f1e3a56e57c6749069a080618c96b22e51131a330fc4a#npm:0.3.6"],\
["ua-parser-js", "npm:1.0.2"],\
["uuid", "npm:8.3.2"],\
@ -3513,6 +3594,36 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
],\
"linkType": "SOFT"\
}],\
["virtual:04783e12400851b8a3d76e71495851cc94959db6e62f04cb0a31190080629440b182d8c8eb4d7f2b04e281912f2783a5fd4d2c3c6ab68d38b7097246c93f4c19#npm:5.29.0", {\
"packageLocation": "./.yarn/__virtual__/@typescript-eslint-eslint-plugin-virtual-d2330b8caf/0/cache/@typescript-eslint-eslint-plugin-npm-5.29.0-d7e482bb3e-b1022a640f.zip/node_modules/@typescript-eslint/eslint-plugin/",\
"packageDependencies": [\
["@typescript-eslint/eslint-plugin", "virtual:04783e12400851b8a3d76e71495851cc94959db6e62f04cb0a31190080629440b182d8c8eb4d7f2b04e281912f2783a5fd4d2c3c6ab68d38b7097246c93f4c19#npm:5.29.0"],\
["@types/eslint", null],\
["@types/typescript", null],\
["@types/typescript-eslint__parser", null],\
["@typescript-eslint/parser", null],\
["@typescript-eslint/scope-manager", "npm:5.29.0"],\
["@typescript-eslint/type-utils", "virtual:d2330b8caf9d1ed905d5d037b08afe5d2dcb2220d795f2d96b3dd514824e8aefae721c334dbd0cd8d53c639ff2c9ad893d3d83b1092b2db05593aa9dc8e59994#npm:5.29.0"],\
["@typescript-eslint/utils", "virtual:d2330b8caf9d1ed905d5d037b08afe5d2dcb2220d795f2d96b3dd514824e8aefae721c334dbd0cd8d53c639ff2c9ad893d3d83b1092b2db05593aa9dc8e59994#npm:5.29.0"],\
["debug", "virtual:b86a9fb34323a98c6519528ed55faa0d9b44ca8879307c0b29aa384bde47ff59a7d0c9051b31246f14521dfb71ba3c5d6d0b35c29fffc17bf875aa6ad977d9e8#npm:4.3.4"],\
["eslint", "npm:8.18.0"],\
["functional-red-black-tree", "npm:1.0.1"],\
["ignore", "npm:5.2.0"],\
["regexpp", "npm:3.2.0"],\
["semver", "npm:7.3.7"],\
["tsutils", "virtual:d2330b8caf9d1ed905d5d037b08afe5d2dcb2220d795f2d96b3dd514824e8aefae721c334dbd0cd8d53c639ff2c9ad893d3d83b1092b2db05593aa9dc8e59994#npm:3.21.0"],\
["typescript", null]\
],\
"packagePeers": [\
"@types/eslint",\
"@types/typescript-eslint__parser",\
"@types/typescript",\
"@typescript-eslint/parser",\
"eslint",\
"typescript"\
],\
"linkType": "HARD"\
}],\
["virtual:16bfd8597041deb71e4581ea0755edd4dcd1b09b8ab14bfbbf5e4d5ca6b5d47ed7fbe2a25cdf57fcbb8e092c30b6beb93d2e7533f9e31c5dc62f7f0e487d1e4b#npm:5.29.0", {\
"packageLocation": "./.yarn/__virtual__/@typescript-eslint-eslint-plugin-virtual-f8d5e1f46d/0/cache/@typescript-eslint-eslint-plugin-npm-5.29.0-d7e482bb3e-b1022a640f.zip/node_modules/@typescript-eslint/eslint-plugin/",\
"packageDependencies": [\
@ -3530,37 +3641,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
["ignore", "npm:5.2.0"],\
["regexpp", "npm:3.2.0"],\
["semver", "npm:7.3.7"],\
["tsutils", "virtual:e64d2841693653abb2dee666d19406912f5e913a8081a709c081d9877d2f39987ff853b7cd736901a2df59af98328f7249f3db0da01abf060cf1d858d4d4e43b#npm:3.21.0"],\
["typescript", null]\
],\
"packagePeers": [\
"@types/eslint",\
"@types/typescript-eslint__parser",\
"@types/typescript",\
"@typescript-eslint/parser",\
"eslint",\
"typescript"\
],\
"linkType": "HARD"\
}],\
["virtual:31b5a94a105c89c9294c3d524a7f8929fe63ee5a2efadf21951ca4c0cfd2ecf02e8f4ef5a066bbda091f1e3a56e57c6749069a080618c96b22e51131a330fc4a#npm:5.29.0", {\
"packageLocation": "./.yarn/__virtual__/@typescript-eslint-eslint-plugin-virtual-e64d284169/0/cache/@typescript-eslint-eslint-plugin-npm-5.29.0-d7e482bb3e-b1022a640f.zip/node_modules/@typescript-eslint/eslint-plugin/",\
"packageDependencies": [\
["@typescript-eslint/eslint-plugin", "virtual:31b5a94a105c89c9294c3d524a7f8929fe63ee5a2efadf21951ca4c0cfd2ecf02e8f4ef5a066bbda091f1e3a56e57c6749069a080618c96b22e51131a330fc4a#npm:5.29.0"],\
["@types/eslint", null],\
["@types/typescript", null],\
["@types/typescript-eslint__parser", null],\
["@typescript-eslint/parser", null],\
["@typescript-eslint/scope-manager", "npm:5.29.0"],\
["@typescript-eslint/type-utils", "virtual:e64d2841693653abb2dee666d19406912f5e913a8081a709c081d9877d2f39987ff853b7cd736901a2df59af98328f7249f3db0da01abf060cf1d858d4d4e43b#npm:5.29.0"],\
["@typescript-eslint/utils", "virtual:e64d2841693653abb2dee666d19406912f5e913a8081a709c081d9877d2f39987ff853b7cd736901a2df59af98328f7249f3db0da01abf060cf1d858d4d4e43b#npm:5.29.0"],\
["debug", "virtual:b86a9fb34323a98c6519528ed55faa0d9b44ca8879307c0b29aa384bde47ff59a7d0c9051b31246f14521dfb71ba3c5d6d0b35c29fffc17bf875aa6ad977d9e8#npm:4.3.4"],\
["eslint", "npm:8.18.0"],\
["functional-red-black-tree", "npm:1.0.1"],\
["ignore", "npm:5.2.0"],\
["regexpp", "npm:3.2.0"],\
["semver", "npm:7.3.7"],\
["tsutils", "virtual:e64d2841693653abb2dee666d19406912f5e913a8081a709c081d9877d2f39987ff853b7cd736901a2df59af98328f7249f3db0da01abf060cf1d858d4d4e43b#npm:3.21.0"],\
["tsutils", "virtual:d2330b8caf9d1ed905d5d037b08afe5d2dcb2220d795f2d96b3dd514824e8aefae721c334dbd0cd8d53c639ff2c9ad893d3d83b1092b2db05593aa9dc8e59994#npm:3.21.0"],\
["typescript", null]\
],\
"packagePeers": [\
@ -3589,7 +3670,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
["ignore", "npm:5.2.0"],\
["regexpp", "npm:3.2.0"],\
["semver", "npm:7.3.7"],\
["tsutils", "virtual:e64d2841693653abb2dee666d19406912f5e913a8081a709c081d9877d2f39987ff853b7cd736901a2df59af98328f7249f3db0da01abf060cf1d858d4d4e43b#npm:3.21.0"],\
["tsutils", "virtual:d2330b8caf9d1ed905d5d037b08afe5d2dcb2220d795f2d96b3dd514824e8aefae721c334dbd0cd8d53c639ff2c9ad893d3d83b1092b2db05593aa9dc8e59994#npm:3.21.0"],\
["typescript", null]\
],\
"packagePeers": [\
@ -3717,16 +3798,16 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
],\
"linkType": "SOFT"\
}],\
["virtual:e64d2841693653abb2dee666d19406912f5e913a8081a709c081d9877d2f39987ff853b7cd736901a2df59af98328f7249f3db0da01abf060cf1d858d4d4e43b#npm:5.29.0", {\
"packageLocation": "./.yarn/__virtual__/@typescript-eslint-type-utils-virtual-96dd1bc160/0/cache/@typescript-eslint-type-utils-npm-5.29.0-063d15676f-686b8ff05a.zip/node_modules/@typescript-eslint/type-utils/",\
["virtual:d2330b8caf9d1ed905d5d037b08afe5d2dcb2220d795f2d96b3dd514824e8aefae721c334dbd0cd8d53c639ff2c9ad893d3d83b1092b2db05593aa9dc8e59994#npm:5.29.0", {\
"packageLocation": "./.yarn/__virtual__/@typescript-eslint-type-utils-virtual-2a27889b50/0/cache/@typescript-eslint-type-utils-npm-5.29.0-063d15676f-686b8ff05a.zip/node_modules/@typescript-eslint/type-utils/",\
"packageDependencies": [\
["@typescript-eslint/type-utils", "virtual:e64d2841693653abb2dee666d19406912f5e913a8081a709c081d9877d2f39987ff853b7cd736901a2df59af98328f7249f3db0da01abf060cf1d858d4d4e43b#npm:5.29.0"],\
["@typescript-eslint/type-utils", "virtual:d2330b8caf9d1ed905d5d037b08afe5d2dcb2220d795f2d96b3dd514824e8aefae721c334dbd0cd8d53c639ff2c9ad893d3d83b1092b2db05593aa9dc8e59994#npm:5.29.0"],\
["@types/eslint", null],\
["@types/typescript", null],\
["@typescript-eslint/utils", "virtual:e64d2841693653abb2dee666d19406912f5e913a8081a709c081d9877d2f39987ff853b7cd736901a2df59af98328f7249f3db0da01abf060cf1d858d4d4e43b#npm:5.29.0"],\
["@typescript-eslint/utils", "virtual:d2330b8caf9d1ed905d5d037b08afe5d2dcb2220d795f2d96b3dd514824e8aefae721c334dbd0cd8d53c639ff2c9ad893d3d83b1092b2db05593aa9dc8e59994#npm:5.29.0"],\
["debug", "virtual:b86a9fb34323a98c6519528ed55faa0d9b44ca8879307c0b29aa384bde47ff59a7d0c9051b31246f14521dfb71ba3c5d6d0b35c29fffc17bf875aa6ad977d9e8#npm:4.3.4"],\
["eslint", "npm:8.18.0"],\
["tsutils", "virtual:e64d2841693653abb2dee666d19406912f5e913a8081a709c081d9877d2f39987ff853b7cd736901a2df59af98328f7249f3db0da01abf060cf1d858d4d4e43b#npm:3.21.0"],\
["tsutils", "virtual:d2330b8caf9d1ed905d5d037b08afe5d2dcb2220d795f2d96b3dd514824e8aefae721c334dbd0cd8d53c639ff2c9ad893d3d83b1092b2db05593aa9dc8e59994#npm:3.21.0"],\
["typescript", null]\
],\
"packagePeers": [\
@ -3746,7 +3827,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
["@typescript-eslint/utils", "virtual:f8d5e1f46dbb2f1fb352e8d15f1237589f03161f87569a6446ffa325c842024c20e3b7f196872650fbbdc62125c711d99dd1c2ba271f15e9b316292a2dec51bc#npm:5.29.0"],\
["debug", "virtual:b86a9fb34323a98c6519528ed55faa0d9b44ca8879307c0b29aa384bde47ff59a7d0c9051b31246f14521dfb71ba3c5d6d0b35c29fffc17bf875aa6ad977d9e8#npm:4.3.4"],\
["eslint", null],\
["tsutils", "virtual:e64d2841693653abb2dee666d19406912f5e913a8081a709c081d9877d2f39987ff853b7cd736901a2df59af98328f7249f3db0da01abf060cf1d858d4d4e43b#npm:3.21.0"],\
["tsutils", "virtual:d2330b8caf9d1ed905d5d037b08afe5d2dcb2220d795f2d96b3dd514824e8aefae721c334dbd0cd8d53c639ff2c9ad893d3d83b1092b2db05593aa9dc8e59994#npm:3.21.0"],\
["typescript", null]\
],\
"packagePeers": [\
@ -3800,7 +3881,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
["globby", "npm:11.1.0"],\
["is-glob", "npm:4.0.3"],\
["semver", "npm:7.3.7"],\
["tsutils", "virtual:e64d2841693653abb2dee666d19406912f5e913a8081a709c081d9877d2f39987ff853b7cd736901a2df59af98328f7249f3db0da01abf060cf1d858d4d4e43b#npm:3.21.0"],\
["tsutils", "virtual:d2330b8caf9d1ed905d5d037b08afe5d2dcb2220d795f2d96b3dd514824e8aefae721c334dbd0cd8d53c639ff2c9ad893d3d83b1092b2db05593aa9dc8e59994#npm:3.21.0"],\
["typescript", null]\
],\
"packagePeers": [\
@ -3829,10 +3910,10 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
],\
"linkType": "HARD"\
}],\
["virtual:4ec458b53cfcb38d153394fe4d0300908a12ce721ae6026f1e2d7bbe8409ed98079b29d9688a9eb93463ace5dbaac7d454b12c4582b1cd0b1d8210588cf0cb1c#npm:5.29.0", {\
"packageLocation": "./.yarn/__virtual__/@typescript-eslint-typescript-estree-virtual-8de7b9cb0d/0/cache/@typescript-eslint-typescript-estree-npm-5.29.0-f23de2ab5c-b91107a9fc.zip/node_modules/@typescript-eslint/typescript-estree/",\
["virtual:5355269a141f9a806d08de8974384aa45476d01ec51fc7301c8384e52bbf36be0eab945f0602a4df4c7737fa267f81331777eaeb3d1de352f83549caee258ae1#npm:5.29.0", {\
"packageLocation": "./.yarn/__virtual__/@typescript-eslint-typescript-estree-virtual-76f89dbc29/0/cache/@typescript-eslint-typescript-estree-npm-5.29.0-f23de2ab5c-b91107a9fc.zip/node_modules/@typescript-eslint/typescript-estree/",\
"packageDependencies": [\
["@typescript-eslint/typescript-estree", "virtual:4ec458b53cfcb38d153394fe4d0300908a12ce721ae6026f1e2d7bbe8409ed98079b29d9688a9eb93463ace5dbaac7d454b12c4582b1cd0b1d8210588cf0cb1c#npm:5.29.0"],\
["@typescript-eslint/typescript-estree", "virtual:5355269a141f9a806d08de8974384aa45476d01ec51fc7301c8384e52bbf36be0eab945f0602a4df4c7737fa267f81331777eaeb3d1de352f83549caee258ae1#npm:5.29.0"],\
["@types/typescript", null],\
["@typescript-eslint/types", "npm:5.29.0"],\
["@typescript-eslint/visitor-keys", "npm:5.29.0"],\
@ -3840,7 +3921,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
["globby", "npm:11.1.0"],\
["is-glob", "npm:4.0.3"],\
["semver", "npm:7.3.7"],\
["tsutils", "virtual:e64d2841693653abb2dee666d19406912f5e913a8081a709c081d9877d2f39987ff853b7cd736901a2df59af98328f7249f3db0da01abf060cf1d858d4d4e43b#npm:3.21.0"],\
["tsutils", "virtual:d2330b8caf9d1ed905d5d037b08afe5d2dcb2220d795f2d96b3dd514824e8aefae721c334dbd0cd8d53c639ff2c9ad893d3d83b1092b2db05593aa9dc8e59994#npm:3.21.0"],\
["typescript", null]\
],\
"packagePeers": [\
@ -3858,15 +3939,15 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
],\
"linkType": "SOFT"\
}],\
["virtual:e64d2841693653abb2dee666d19406912f5e913a8081a709c081d9877d2f39987ff853b7cd736901a2df59af98328f7249f3db0da01abf060cf1d858d4d4e43b#npm:5.29.0", {\
"packageLocation": "./.yarn/__virtual__/@typescript-eslint-utils-virtual-4ec458b53c/0/cache/@typescript-eslint-utils-npm-5.29.0-f3bfc5f3f3-216f51fb9c.zip/node_modules/@typescript-eslint/utils/",\
["virtual:d2330b8caf9d1ed905d5d037b08afe5d2dcb2220d795f2d96b3dd514824e8aefae721c334dbd0cd8d53c639ff2c9ad893d3d83b1092b2db05593aa9dc8e59994#npm:5.29.0", {\
"packageLocation": "./.yarn/__virtual__/@typescript-eslint-utils-virtual-5355269a14/0/cache/@typescript-eslint-utils-npm-5.29.0-f3bfc5f3f3-216f51fb9c.zip/node_modules/@typescript-eslint/utils/",\
"packageDependencies": [\
["@typescript-eslint/utils", "virtual:e64d2841693653abb2dee666d19406912f5e913a8081a709c081d9877d2f39987ff853b7cd736901a2df59af98328f7249f3db0da01abf060cf1d858d4d4e43b#npm:5.29.0"],\
["@typescript-eslint/utils", "virtual:d2330b8caf9d1ed905d5d037b08afe5d2dcb2220d795f2d96b3dd514824e8aefae721c334dbd0cd8d53c639ff2c9ad893d3d83b1092b2db05593aa9dc8e59994#npm:5.29.0"],\
["@types/eslint", null],\
["@types/json-schema", "npm:7.0.11"],\
["@typescript-eslint/scope-manager", "npm:5.29.0"],\
["@typescript-eslint/types", "npm:5.29.0"],\
["@typescript-eslint/typescript-estree", "virtual:4ec458b53cfcb38d153394fe4d0300908a12ce721ae6026f1e2d7bbe8409ed98079b29d9688a9eb93463ace5dbaac7d454b12c4582b1cd0b1d8210588cf0cb1c#npm:5.29.0"],\
["@typescript-eslint/typescript-estree", "virtual:5355269a141f9a806d08de8974384aa45476d01ec51fc7301c8384e52bbf36be0eab945f0602a4df4c7737fa267f81331777eaeb3d1de352f83549caee258ae1#npm:5.29.0"],\
["eslint", "npm:8.18.0"],\
["eslint-scope", "npm:5.1.1"],\
["eslint-utils", "virtual:3b3bfb190f25ed01591b1d51c8e6a15e818ab97d9cabea5c63912afc819a8f6e3ad395aaf338cd170314411b04e35eec5c8cff33dfa644476d292dcf2c5354d1#npm:3.0.0"]\
@ -3885,7 +3966,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
["@types/json-schema", "npm:7.0.11"],\
["@typescript-eslint/scope-manager", "npm:5.29.0"],\
["@typescript-eslint/types", "npm:5.29.0"],\
["@typescript-eslint/typescript-estree", "virtual:4ec458b53cfcb38d153394fe4d0300908a12ce721ae6026f1e2d7bbe8409ed98079b29d9688a9eb93463ace5dbaac7d454b12c4582b1cd0b1d8210588cf0cb1c#npm:5.29.0"],\
["@typescript-eslint/typescript-estree", "virtual:5355269a141f9a806d08de8974384aa45476d01ec51fc7301c8384e52bbf36be0eab945f0602a4df4c7737fa267f81331777eaeb3d1de352f83549caee258ae1#npm:5.29.0"],\
["eslint", null],\
["eslint-scope", "npm:5.1.1"],\
["eslint-utils", "virtual:3b1d487b65ac14c3c2f5d6292c3e4b93bf25216a88a2d253428f98942e01532ac4933ee30564874cec0a0bb5aea3ee613d7494705e42eed4a2106f8ac0a03f97#npm:3.0.0"]\
@ -4297,6 +4378,22 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
["xml2js", "npm:0.4.19"]\
],\
"linkType": "HARD"\
}],\
["npm:2.1160.0", {\
"packageLocation": "./.yarn/cache/aws-sdk-npm-2.1160.0-1a3db600b7-b95647d4de.zip/node_modules/aws-sdk/",\
"packageDependencies": [\
["aws-sdk", "npm:2.1160.0"],\
["buffer", "npm:4.9.2"],\
["events", "npm:1.1.1"],\
["ieee754", "npm:1.1.13"],\
["jmespath", "npm:0.16.0"],\
["querystring", "npm:0.2.0"],\
["sax", "npm:1.2.1"],\
["url", "npm:0.10.3"],\
["uuid", "npm:8.0.0"],\
["xml2js", "npm:0.4.19"]\
],\
"linkType": "HARD"\
}]\
]],\
["axios", [\
@ -4317,15 +4414,15 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
],\
"linkType": "SOFT"\
}],\
["virtual:805c813b6f046618cef5c7d6c026d202467ce267579e0c7a252be4f063439bc6f090ab5b924f50d7ae022b220d8e89e00ef15869e26244774ec68ef480e4e54d#npm:28.1.1", {\
"packageLocation": "./.yarn/__virtual__/babel-jest-virtual-419439314f/0/cache/babel-jest-npm-28.1.1-a0706ab037-9c7c7f6006.zip/node_modules/babel-jest/",\
["virtual:32b5269fd2e54a6fa7239e0777f3768bd33c5a094884dd5492350113f2bec8947944fe0156c37c044965c85742064fae363a3a7138decb195966e011bb7f7d4f#npm:28.1.1", {\
"packageLocation": "./.yarn/__virtual__/babel-jest-virtual-08d3591870/0/cache/babel-jest-npm-28.1.1-a0706ab037-9c7c7f6006.zip/node_modules/babel-jest/",\
"packageDependencies": [\
["babel-jest", "virtual:805c813b6f046618cef5c7d6c026d202467ce267579e0c7a252be4f063439bc6f090ab5b924f50d7ae022b220d8e89e00ef15869e26244774ec68ef480e4e54d#npm:28.1.1"],\
["babel-jest", "virtual:32b5269fd2e54a6fa7239e0777f3768bd33c5a094884dd5492350113f2bec8947944fe0156c37c044965c85742064fae363a3a7138decb195966e011bb7f7d4f#npm:28.1.1"],\
["@babel/core", "npm:7.18.5"],\
["@jest/transform", "npm:28.1.1"],\
["@types/babel__core", "npm:7.1.19"],\
["babel-plugin-istanbul", "npm:6.1.1"],\
["babel-preset-jest", "virtual:419439314f6ac7e6aeb104f74d9bd1fb754b552a3112b86e2807390519b56dbd6a88f32cff6e239d59a2670b389ec32d1afc7812be06ebe4cb1eeb9c2c58cf9e#npm:28.1.1"],\
["babel-preset-jest", "virtual:08d35918703ec0bc2fb72b89407cc8430c63eb749ffe2bff641418cce72be4aab0afe750c610e41ee52e9bd2c8d7e721847c3791d36317060f0620130926424e#npm:28.1.1"],\
["chalk", "npm:4.1.2"],\
["graceful-fs", "npm:4.2.10"],\
["slash", "npm:3.0.0"]\
@ -4371,6 +4468,31 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
],\
"linkType": "SOFT"\
}],\
["virtual:081cd5e09087a97f33e6c2157b2f04624b270420cbd295805f8d59379d1fdc60eb969f610d8b11a66179e64b76fb08cb87d36cb6f67d17466aa40dc8ab796225#npm:1.0.1", {\
"packageLocation": "./.yarn/__virtual__/babel-preset-current-node-syntax-virtual-c373874e1b/0/cache/babel-preset-current-node-syntax-npm-1.0.1-849ec71e32-d118c27424.zip/node_modules/babel-preset-current-node-syntax/",\
"packageDependencies": [\
["babel-preset-current-node-syntax", "virtual:081cd5e09087a97f33e6c2157b2f04624b270420cbd295805f8d59379d1fdc60eb969f610d8b11a66179e64b76fb08cb87d36cb6f67d17466aa40dc8ab796225#npm:1.0.1"],\
["@babel/core", "npm:7.18.5"],\
["@babel/plugin-syntax-async-generators", "virtual:c373874e1baa9d76ffb6ce2109646b13db6b2b433728eac72e69f1da2ca3718a077c7549e49925b0fa06952690278e6a36ceca4ebe492de934ac9dea98343dd7#npm:7.8.4"],\
["@babel/plugin-syntax-bigint", "virtual:c373874e1baa9d76ffb6ce2109646b13db6b2b433728eac72e69f1da2ca3718a077c7549e49925b0fa06952690278e6a36ceca4ebe492de934ac9dea98343dd7#npm:7.8.3"],\
["@babel/plugin-syntax-class-properties", "virtual:c373874e1baa9d76ffb6ce2109646b13db6b2b433728eac72e69f1da2ca3718a077c7549e49925b0fa06952690278e6a36ceca4ebe492de934ac9dea98343dd7#npm:7.12.13"],\
["@babel/plugin-syntax-import-meta", "virtual:c373874e1baa9d76ffb6ce2109646b13db6b2b433728eac72e69f1da2ca3718a077c7549e49925b0fa06952690278e6a36ceca4ebe492de934ac9dea98343dd7#npm:7.10.4"],\
["@babel/plugin-syntax-json-strings", "virtual:c373874e1baa9d76ffb6ce2109646b13db6b2b433728eac72e69f1da2ca3718a077c7549e49925b0fa06952690278e6a36ceca4ebe492de934ac9dea98343dd7#npm:7.8.3"],\
["@babel/plugin-syntax-logical-assignment-operators", "virtual:c373874e1baa9d76ffb6ce2109646b13db6b2b433728eac72e69f1da2ca3718a077c7549e49925b0fa06952690278e6a36ceca4ebe492de934ac9dea98343dd7#npm:7.10.4"],\
["@babel/plugin-syntax-nullish-coalescing-operator", "virtual:c373874e1baa9d76ffb6ce2109646b13db6b2b433728eac72e69f1da2ca3718a077c7549e49925b0fa06952690278e6a36ceca4ebe492de934ac9dea98343dd7#npm:7.8.3"],\
["@babel/plugin-syntax-numeric-separator", "virtual:c373874e1baa9d76ffb6ce2109646b13db6b2b433728eac72e69f1da2ca3718a077c7549e49925b0fa06952690278e6a36ceca4ebe492de934ac9dea98343dd7#npm:7.10.4"],\
["@babel/plugin-syntax-object-rest-spread", "virtual:c373874e1baa9d76ffb6ce2109646b13db6b2b433728eac72e69f1da2ca3718a077c7549e49925b0fa06952690278e6a36ceca4ebe492de934ac9dea98343dd7#npm:7.8.3"],\
["@babel/plugin-syntax-optional-catch-binding", "virtual:c373874e1baa9d76ffb6ce2109646b13db6b2b433728eac72e69f1da2ca3718a077c7549e49925b0fa06952690278e6a36ceca4ebe492de934ac9dea98343dd7#npm:7.8.3"],\
["@babel/plugin-syntax-optional-chaining", "virtual:c373874e1baa9d76ffb6ce2109646b13db6b2b433728eac72e69f1da2ca3718a077c7549e49925b0fa06952690278e6a36ceca4ebe492de934ac9dea98343dd7#npm:7.8.3"],\
["@babel/plugin-syntax-top-level-await", "virtual:c373874e1baa9d76ffb6ce2109646b13db6b2b433728eac72e69f1da2ca3718a077c7549e49925b0fa06952690278e6a36ceca4ebe492de934ac9dea98343dd7#npm:7.14.5"],\
["@types/babel__core", "npm:7.1.19"]\
],\
"packagePeers": [\
"@babel/core",\
"@types/babel__core"\
],\
"linkType": "HARD"\
}],\
["virtual:7ff9a9a22630d18bc8c20eec37b86b4c191fbcee5349c62dbf8ba14d95b3502ae4cb63cce8e26089a0dd1b269b70fad4ce808ff97d3255679417f5177f7bef0e#npm:1.0.1", {\
"packageLocation": "./.yarn/__virtual__/babel-preset-current-node-syntax-virtual-fcabcac42b/0/cache/babel-preset-current-node-syntax-npm-1.0.1-849ec71e32-d118c27424.zip/node_modules/babel-preset-current-node-syntax/",\
"packageDependencies": [\
@ -4395,31 +4517,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
"@types/babel__core"\
],\
"linkType": "HARD"\
}],\
["virtual:a21946f32fecc6d2a4f39c804bc6851b8c98cf267db2bb0a25b0f443fe3cf1ff67012036ab014b3ec1309cc1f0a5678c35acb443e7d8c8a0d3c29071288e53d7#npm:1.0.1", {\
"packageLocation": "./.yarn/__virtual__/babel-preset-current-node-syntax-virtual-511f18ec47/0/cache/babel-preset-current-node-syntax-npm-1.0.1-849ec71e32-d118c27424.zip/node_modules/babel-preset-current-node-syntax/",\
"packageDependencies": [\
["babel-preset-current-node-syntax", "virtual:a21946f32fecc6d2a4f39c804bc6851b8c98cf267db2bb0a25b0f443fe3cf1ff67012036ab014b3ec1309cc1f0a5678c35acb443e7d8c8a0d3c29071288e53d7#npm:1.0.1"],\
["@babel/core", "npm:7.18.5"],\
["@babel/plugin-syntax-async-generators", "virtual:511f18ec4797ab42da93993c808942012470709446ef400bc600a66a60e5e382e8081da234db4fe66d41d38d9212735169fb3c604c828d1ff8dc854506ed7b48#npm:7.8.4"],\
["@babel/plugin-syntax-bigint", "virtual:511f18ec4797ab42da93993c808942012470709446ef400bc600a66a60e5e382e8081da234db4fe66d41d38d9212735169fb3c604c828d1ff8dc854506ed7b48#npm:7.8.3"],\
["@babel/plugin-syntax-class-properties", "virtual:511f18ec4797ab42da93993c808942012470709446ef400bc600a66a60e5e382e8081da234db4fe66d41d38d9212735169fb3c604c828d1ff8dc854506ed7b48#npm:7.12.13"],\
["@babel/plugin-syntax-import-meta", "virtual:511f18ec4797ab42da93993c808942012470709446ef400bc600a66a60e5e382e8081da234db4fe66d41d38d9212735169fb3c604c828d1ff8dc854506ed7b48#npm:7.10.4"],\
["@babel/plugin-syntax-json-strings", "virtual:511f18ec4797ab42da93993c808942012470709446ef400bc600a66a60e5e382e8081da234db4fe66d41d38d9212735169fb3c604c828d1ff8dc854506ed7b48#npm:7.8.3"],\
["@babel/plugin-syntax-logical-assignment-operators", "virtual:511f18ec4797ab42da93993c808942012470709446ef400bc600a66a60e5e382e8081da234db4fe66d41d38d9212735169fb3c604c828d1ff8dc854506ed7b48#npm:7.10.4"],\
["@babel/plugin-syntax-nullish-coalescing-operator", "virtual:511f18ec4797ab42da93993c808942012470709446ef400bc600a66a60e5e382e8081da234db4fe66d41d38d9212735169fb3c604c828d1ff8dc854506ed7b48#npm:7.8.3"],\
["@babel/plugin-syntax-numeric-separator", "virtual:511f18ec4797ab42da93993c808942012470709446ef400bc600a66a60e5e382e8081da234db4fe66d41d38d9212735169fb3c604c828d1ff8dc854506ed7b48#npm:7.10.4"],\
["@babel/plugin-syntax-object-rest-spread", "virtual:511f18ec4797ab42da93993c808942012470709446ef400bc600a66a60e5e382e8081da234db4fe66d41d38d9212735169fb3c604c828d1ff8dc854506ed7b48#npm:7.8.3"],\
["@babel/plugin-syntax-optional-catch-binding", "virtual:511f18ec4797ab42da93993c808942012470709446ef400bc600a66a60e5e382e8081da234db4fe66d41d38d9212735169fb3c604c828d1ff8dc854506ed7b48#npm:7.8.3"],\
["@babel/plugin-syntax-optional-chaining", "virtual:511f18ec4797ab42da93993c808942012470709446ef400bc600a66a60e5e382e8081da234db4fe66d41d38d9212735169fb3c604c828d1ff8dc854506ed7b48#npm:7.8.3"],\
["@babel/plugin-syntax-top-level-await", "virtual:511f18ec4797ab42da93993c808942012470709446ef400bc600a66a60e5e382e8081da234db4fe66d41d38d9212735169fb3c604c828d1ff8dc854506ed7b48#npm:7.14.5"],\
["@types/babel__core", "npm:7.1.19"]\
],\
"packagePeers": [\
"@babel/core",\
"@types/babel__core"\
],\
"linkType": "HARD"\
}]\
]],\
["babel-preset-jest", [\
@ -4430,14 +4527,14 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
],\
"linkType": "SOFT"\
}],\
["virtual:419439314f6ac7e6aeb104f74d9bd1fb754b552a3112b86e2807390519b56dbd6a88f32cff6e239d59a2670b389ec32d1afc7812be06ebe4cb1eeb9c2c58cf9e#npm:28.1.1", {\
"packageLocation": "./.yarn/__virtual__/babel-preset-jest-virtual-a21946f32f/0/cache/babel-preset-jest-npm-28.1.1-05a1e38dd1-c581a81967.zip/node_modules/babel-preset-jest/",\
["virtual:08d35918703ec0bc2fb72b89407cc8430c63eb749ffe2bff641418cce72be4aab0afe750c610e41ee52e9bd2c8d7e721847c3791d36317060f0620130926424e#npm:28.1.1", {\
"packageLocation": "./.yarn/__virtual__/babel-preset-jest-virtual-081cd5e090/0/cache/babel-preset-jest-npm-28.1.1-05a1e38dd1-c581a81967.zip/node_modules/babel-preset-jest/",\
"packageDependencies": [\
["babel-preset-jest", "virtual:419439314f6ac7e6aeb104f74d9bd1fb754b552a3112b86e2807390519b56dbd6a88f32cff6e239d59a2670b389ec32d1afc7812be06ebe4cb1eeb9c2c58cf9e#npm:28.1.1"],\
["babel-preset-jest", "virtual:08d35918703ec0bc2fb72b89407cc8430c63eb749ffe2bff641418cce72be4aab0afe750c610e41ee52e9bd2c8d7e721847c3791d36317060f0620130926424e#npm:28.1.1"],\
["@babel/core", "npm:7.18.5"],\
["@types/babel__core", "npm:7.1.19"],\
["babel-plugin-jest-hoist", "npm:28.1.1"],\
["babel-preset-current-node-syntax", "virtual:a21946f32fecc6d2a4f39c804bc6851b8c98cf267db2bb0a25b0f443fe3cf1ff67012036ab014b3ec1309cc1f0a5678c35acb443e7d8c8a0d3c29071288e53d7#npm:1.0.1"]\
["babel-preset-current-node-syntax", "virtual:081cd5e09087a97f33e6c2157b2f04624b270420cbd295805f8d59379d1fdc60eb969f610d8b11a66179e64b76fb08cb87d36cb6f67d17466aa40dc8ab796225#npm:1.0.1"]\
],\
"packagePeers": [\
"@babel/core",\
@ -6173,14 +6270,14 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
],\
"linkType": "SOFT"\
}],\
["virtual:16bfd8597041deb71e4581ea0755edd4dcd1b09b8ab14bfbbf5e4d5ca6b5d47ed7fbe2a25cdf57fcbb8e092c30b6beb93d2e7533f9e31c5dc62f7f0e487d1e4b#npm:4.0.0", {\
"packageLocation": "./.yarn/__virtual__/eslint-plugin-prettier-virtual-7e331f4408/0/cache/eslint-plugin-prettier-npm-4.0.0-e632552861-03d69177a3.zip/node_modules/eslint-plugin-prettier/",\
["virtual:04783e12400851b8a3d76e71495851cc94959db6e62f04cb0a31190080629440b182d8c8eb4d7f2b04e281912f2783a5fd4d2c3c6ab68d38b7097246c93f4c19#npm:4.0.0", {\
"packageLocation": "./.yarn/__virtual__/eslint-plugin-prettier-virtual-eaf1b54b4e/0/cache/eslint-plugin-prettier-npm-4.0.0-e632552861-03d69177a3.zip/node_modules/eslint-plugin-prettier/",\
"packageDependencies": [\
["eslint-plugin-prettier", "virtual:16bfd8597041deb71e4581ea0755edd4dcd1b09b8ab14bfbbf5e4d5ca6b5d47ed7fbe2a25cdf57fcbb8e092c30b6beb93d2e7533f9e31c5dc62f7f0e487d1e4b#npm:4.0.0"],\
["eslint-plugin-prettier", "virtual:04783e12400851b8a3d76e71495851cc94959db6e62f04cb0a31190080629440b182d8c8eb4d7f2b04e281912f2783a5fd4d2c3c6ab68d38b7097246c93f4c19#npm:4.0.0"],\
["@types/eslint", null],\
["@types/eslint-config-prettier", null],\
["@types/prettier", null],\
["eslint", null],\
["eslint", "npm:8.18.0"],\
["eslint-config-prettier", null],\
["prettier", null],\
["prettier-linter-helpers", "npm:1.0.0"]\
@ -6195,14 +6292,14 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
],\
"linkType": "HARD"\
}],\
["virtual:31b5a94a105c89c9294c3d524a7f8929fe63ee5a2efadf21951ca4c0cfd2ecf02e8f4ef5a066bbda091f1e3a56e57c6749069a080618c96b22e51131a330fc4a#npm:4.0.0", {\
"packageLocation": "./.yarn/__virtual__/eslint-plugin-prettier-virtual-5c96b2a218/0/cache/eslint-plugin-prettier-npm-4.0.0-e632552861-03d69177a3.zip/node_modules/eslint-plugin-prettier/",\
["virtual:16bfd8597041deb71e4581ea0755edd4dcd1b09b8ab14bfbbf5e4d5ca6b5d47ed7fbe2a25cdf57fcbb8e092c30b6beb93d2e7533f9e31c5dc62f7f0e487d1e4b#npm:4.0.0", {\
"packageLocation": "./.yarn/__virtual__/eslint-plugin-prettier-virtual-7e331f4408/0/cache/eslint-plugin-prettier-npm-4.0.0-e632552861-03d69177a3.zip/node_modules/eslint-plugin-prettier/",\
"packageDependencies": [\
["eslint-plugin-prettier", "virtual:31b5a94a105c89c9294c3d524a7f8929fe63ee5a2efadf21951ca4c0cfd2ecf02e8f4ef5a066bbda091f1e3a56e57c6749069a080618c96b22e51131a330fc4a#npm:4.0.0"],\
["eslint-plugin-prettier", "virtual:16bfd8597041deb71e4581ea0755edd4dcd1b09b8ab14bfbbf5e4d5ca6b5d47ed7fbe2a25cdf57fcbb8e092c30b6beb93d2e7533f9e31c5dc62f7f0e487d1e4b#npm:4.0.0"],\
["@types/eslint", null],\
["@types/eslint-config-prettier", null],\
["@types/prettier", null],\
["eslint", "npm:8.18.0"],\
["eslint", null],\
["eslint-config-prettier", null],\
["prettier", null],\
["prettier-linter-helpers", "npm:1.0.0"]\
@ -7231,6 +7328,13 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
],\
"linkType": "HARD"\
}],\
["npm:4.4.1", {\
"packageLocation": "./.yarn/cache/helmet-npm-4.4.1-286ac392ee-cfe385e185.zip/node_modules/helmet/",\
"packageDependencies": [\
["helmet", "npm:4.4.1"]\
],\
"linkType": "HARD"\
}],\
["npm:4.6.0", {\
"packageLocation": "./.yarn/cache/helmet-npm-4.6.0-f244fd965c-139ad678d1.zip/node_modules/helmet/",\
"packageDependencies": [\
@ -8038,15 +8142,15 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
],\
"linkType": "SOFT"\
}],\
["virtual:31b5a94a105c89c9294c3d524a7f8929fe63ee5a2efadf21951ca4c0cfd2ecf02e8f4ef5a066bbda091f1e3a56e57c6749069a080618c96b22e51131a330fc4a#npm:28.1.1", {\
"packageLocation": "./.yarn/__virtual__/jest-virtual-6e20fd2eaa/0/cache/jest-npm-28.1.1-a4158efd82-398a143d9e.zip/node_modules/jest/",\
["virtual:04783e12400851b8a3d76e71495851cc94959db6e62f04cb0a31190080629440b182d8c8eb4d7f2b04e281912f2783a5fd4d2c3c6ab68d38b7097246c93f4c19#npm:28.1.1", {\
"packageLocation": "./.yarn/__virtual__/jest-virtual-c6df9164d8/0/cache/jest-npm-28.1.1-a4158efd82-398a143d9e.zip/node_modules/jest/",\
"packageDependencies": [\
["jest", "virtual:31b5a94a105c89c9294c3d524a7f8929fe63ee5a2efadf21951ca4c0cfd2ecf02e8f4ef5a066bbda091f1e3a56e57c6749069a080618c96b22e51131a330fc4a#npm:28.1.1"],\
["@jest/core", "virtual:6e20fd2eaaa940ccda315da6252a82baa2918f5ea3c40e2d7cb4d97f01b503d35a5076b4b63a33762fb1174e73a3313072cadf65e4a26d1b33660f964eda7880#npm:28.1.1"],\
["jest", "virtual:04783e12400851b8a3d76e71495851cc94959db6e62f04cb0a31190080629440b182d8c8eb4d7f2b04e281912f2783a5fd4d2c3c6ab68d38b7097246c93f4c19#npm:28.1.1"],\
["@jest/core", "virtual:c6df9164d8eb22b719e1cd2b32bc24b485c129a3b28db796c6f3a6c22ad9410bc922a7f59cbbfe78d6492c9ee9f75de8ed7c2ec2f694d58e500097e2a2b74028#npm:28.1.1"],\
["@jest/types", "npm:28.1.1"],\
["@types/node-notifier", null],\
["import-local", "npm:3.1.0"],\
["jest-cli", "virtual:6e20fd2eaaa940ccda315da6252a82baa2918f5ea3c40e2d7cb4d97f01b503d35a5076b4b63a33762fb1174e73a3313072cadf65e4a26d1b33660f964eda7880#npm:28.1.1"],\
["jest-cli", "virtual:c6df9164d8eb22b719e1cd2b32bc24b485c129a3b28db796c6f3a6c22ad9410bc922a7f59cbbfe78d6492c9ee9f75de8ed7c2ec2f694d58e500097e2a2b74028#npm:28.1.1"],\
["node-notifier", null]\
],\
"packagePeers": [\
@ -8103,11 +8207,11 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
],\
"linkType": "SOFT"\
}],\
["virtual:6e20fd2eaaa940ccda315da6252a82baa2918f5ea3c40e2d7cb4d97f01b503d35a5076b4b63a33762fb1174e73a3313072cadf65e4a26d1b33660f964eda7880#npm:28.1.1", {\
"packageLocation": "./.yarn/__virtual__/jest-cli-virtual-18fea92b00/0/cache/jest-cli-npm-28.1.1-7fb5826ae7-fce96f2f0c.zip/node_modules/jest-cli/",\
["virtual:c6df9164d8eb22b719e1cd2b32bc24b485c129a3b28db796c6f3a6c22ad9410bc922a7f59cbbfe78d6492c9ee9f75de8ed7c2ec2f694d58e500097e2a2b74028#npm:28.1.1", {\
"packageLocation": "./.yarn/__virtual__/jest-cli-virtual-ffe7948780/0/cache/jest-cli-npm-28.1.1-7fb5826ae7-fce96f2f0c.zip/node_modules/jest-cli/",\
"packageDependencies": [\
["jest-cli", "virtual:6e20fd2eaaa940ccda315da6252a82baa2918f5ea3c40e2d7cb4d97f01b503d35a5076b4b63a33762fb1174e73a3313072cadf65e4a26d1b33660f964eda7880#npm:28.1.1"],\
["@jest/core", "virtual:6e20fd2eaaa940ccda315da6252a82baa2918f5ea3c40e2d7cb4d97f01b503d35a5076b4b63a33762fb1174e73a3313072cadf65e4a26d1b33660f964eda7880#npm:28.1.1"],\
["jest-cli", "virtual:c6df9164d8eb22b719e1cd2b32bc24b485c129a3b28db796c6f3a6c22ad9410bc922a7f59cbbfe78d6492c9ee9f75de8ed7c2ec2f694d58e500097e2a2b74028#npm:28.1.1"],\
["@jest/core", "virtual:c6df9164d8eb22b719e1cd2b32bc24b485c129a3b28db796c6f3a6c22ad9410bc922a7f59cbbfe78d6492c9ee9f75de8ed7c2ec2f694d58e500097e2a2b74028#npm:28.1.1"],\
["@jest/test-result", "npm:28.1.1"],\
["@jest/types", "npm:28.1.1"],\
["@types/node-notifier", null],\
@ -8115,7 +8219,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
["exit", "npm:0.1.2"],\
["graceful-fs", "npm:4.2.10"],\
["import-local", "npm:3.1.0"],\
["jest-config", "virtual:18fea92b00a9a17809e3136cba934f07b76b6365d781cde6e4e8ad39518603c42b210c61bd96fbdefd3c18ea76e15da85ec1d250fdb153b485bb120f2884a87d#npm:28.1.1"],\
["jest-config", "virtual:ffe7948780650e103d113b366bd00aa5fddf83f2645b1950ace5412f68838bb229734eeb6f43c5cf8e80750f2112707820eb53dac9ef0db7f3ad9a040066aa3b#npm:28.1.1"],\
["jest-util", "npm:28.1.1"],\
["jest-validate", "npm:28.1.1"],\
["node-notifier", null],\
@ -8137,16 +8241,16 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
],\
"linkType": "SOFT"\
}],\
["virtual:18fea92b00a9a17809e3136cba934f07b76b6365d781cde6e4e8ad39518603c42b210c61bd96fbdefd3c18ea76e15da85ec1d250fdb153b485bb120f2884a87d#npm:28.1.1", {\
"packageLocation": "./.yarn/__virtual__/jest-config-virtual-56145f3e40/0/cache/jest-config-npm-28.1.1-8c4e855059-8ce9f6b8f6.zip/node_modules/jest-config/",\
["virtual:c650136efccbbb479cf2d5b8062777752af30a07867b5d5c0bde3cbc6a5c6f054afdfcc433e9ded24592d8fb0664ba4983e4f355a06678d9210b9c9e34f577b9#npm:28.1.1", {\
"packageLocation": "./.yarn/__virtual__/jest-config-virtual-32b5269fd2/0/cache/jest-config-npm-28.1.1-8c4e855059-8ce9f6b8f6.zip/node_modules/jest-config/",\
"packageDependencies": [\
["jest-config", "virtual:18fea92b00a9a17809e3136cba934f07b76b6365d781cde6e4e8ad39518603c42b210c61bd96fbdefd3c18ea76e15da85ec1d250fdb153b485bb120f2884a87d#npm:28.1.1"],\
["jest-config", "virtual:c650136efccbbb479cf2d5b8062777752af30a07867b5d5c0bde3cbc6a5c6f054afdfcc433e9ded24592d8fb0664ba4983e4f355a06678d9210b9c9e34f577b9#npm:28.1.1"],\
["@babel/core", "npm:7.18.5"],\
["@jest/test-sequencer", "npm:28.1.1"],\
["@jest/types", "npm:28.1.1"],\
["@types/node", null],\
["@types/node", "npm:18.0.0"],\
["@types/ts-node", null],\
["babel-jest", "virtual:805c813b6f046618cef5c7d6c026d202467ce267579e0c7a252be4f063439bc6f090ab5b924f50d7ae022b220d8e89e00ef15869e26244774ec68ef480e4e54d#npm:28.1.1"],\
["babel-jest", "virtual:32b5269fd2e54a6fa7239e0777f3768bd33c5a094884dd5492350113f2bec8947944fe0156c37c044965c85742064fae363a3a7138decb195966e011bb7f7d4f#npm:28.1.1"],\
["chalk", "npm:4.1.2"],\
["ci-info", "npm:3.3.2"],\
["deepmerge", "npm:4.2.2"],\
@ -8174,16 +8278,16 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
],\
"linkType": "HARD"\
}],\
["virtual:4b45c3242ed36b84511b3946081e5d3b347e0463d6e39ebfdee2ad8392eb4bd7a5761a69e4fccf0d230c488b171720ddcf381e7c249fe8f4fcdf9d4afc493b87#npm:28.1.1", {\
"packageLocation": "./.yarn/__virtual__/jest-config-virtual-805c813b6f/0/cache/jest-config-npm-28.1.1-8c4e855059-8ce9f6b8f6.zip/node_modules/jest-config/",\
["virtual:ffe7948780650e103d113b366bd00aa5fddf83f2645b1950ace5412f68838bb229734eeb6f43c5cf8e80750f2112707820eb53dac9ef0db7f3ad9a040066aa3b#npm:28.1.1", {\
"packageLocation": "./.yarn/__virtual__/jest-config-virtual-588b409452/0/cache/jest-config-npm-28.1.1-8c4e855059-8ce9f6b8f6.zip/node_modules/jest-config/",\
"packageDependencies": [\
["jest-config", "virtual:4b45c3242ed36b84511b3946081e5d3b347e0463d6e39ebfdee2ad8392eb4bd7a5761a69e4fccf0d230c488b171720ddcf381e7c249fe8f4fcdf9d4afc493b87#npm:28.1.1"],\
["jest-config", "virtual:ffe7948780650e103d113b366bd00aa5fddf83f2645b1950ace5412f68838bb229734eeb6f43c5cf8e80750f2112707820eb53dac9ef0db7f3ad9a040066aa3b#npm:28.1.1"],\
["@babel/core", "npm:7.18.5"],\
["@jest/test-sequencer", "npm:28.1.1"],\
["@jest/types", "npm:28.1.1"],\
["@types/node", "npm:18.0.0"],\
["@types/node", null],\
["@types/ts-node", null],\
["babel-jest", "virtual:805c813b6f046618cef5c7d6c026d202467ce267579e0c7a252be4f063439bc6f090ab5b924f50d7ae022b220d8e89e00ef15869e26244774ec68ef480e4e54d#npm:28.1.1"],\
["babel-jest", "virtual:32b5269fd2e54a6fa7239e0777f3768bd33c5a094884dd5492350113f2bec8947944fe0156c37c044965c85742064fae363a3a7138decb195966e011bb7f7d4f#npm:28.1.1"],\
["chalk", "npm:4.1.2"],\
["ci-info", "npm:3.3.2"],\
["deepmerge", "npm:4.2.2"],\
@ -11713,10 +11817,10 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
],\
"linkType": "SOFT"\
}],\
["virtual:948d77a715c2182e00f2764d231af7afb02bf7f3164cf05b0248dc116c0a54912c77c570591f08b5b0dbb55984329e93f5f5704931076ba8f07fc46d4a69d072#npm:5.7.0", {\
"packageLocation": "./.yarn/__virtual__/sqs-consumer-virtual-b5ed97f086/0/cache/sqs-consumer-npm-5.7.0-09231a3791-d1eb00cbc5.zip/node_modules/sqs-consumer/",\
["virtual:18a82f2f722cf47811304317f79c07fee6ecd8a887f3af2b42c72227f3b982ee05bf525aa8a7d2d20252ed23a1ed39ca99b430ed432b264a8bad79965c0cae5e#npm:5.7.0", {\
"packageLocation": "./.yarn/__virtual__/sqs-consumer-virtual-cbacaabf93/0/cache/sqs-consumer-npm-5.7.0-09231a3791-d1eb00cbc5.zip/node_modules/sqs-consumer/",\
"packageDependencies": [\
["sqs-consumer", "virtual:948d77a715c2182e00f2764d231af7afb02bf7f3164cf05b0248dc116c0a54912c77c570591f08b5b0dbb55984329e93f5f5704931076ba8f07fc46d4a69d072#npm:5.7.0"],\
["sqs-consumer", "virtual:18a82f2f722cf47811304317f79c07fee6ecd8a887f3af2b42c72227f3b982ee05bf525aa8a7d2d20252ed23a1ed39ca99b430ed432b264a8bad79965c0cae5e#npm:5.7.0"],\
["@types/aws-sdk", null],\
["aws-sdk", "npm:2.1157.0"],\
["debug", "virtual:b86a9fb34323a98c6519528ed55faa0d9b44ca8879307c0b29aa384bde47ff59a7d0c9051b31246f14521dfb71ba3c5d6d0b35c29fffc17bf875aa6ad977d9e8#npm:4.3.4"]\
@ -12233,21 +12337,21 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
],\
"linkType": "SOFT"\
}],\
["virtual:16bfd8597041deb71e4581ea0755edd4dcd1b09b8ab14bfbbf5e4d5ca6b5d47ed7fbe2a25cdf57fcbb8e092c30b6beb93d2e7533f9e31c5dc62f7f0e487d1e4b#npm:28.0.5", {\
"packageLocation": "./.yarn/__virtual__/ts-jest-virtual-c9b832d80c/0/cache/ts-jest-npm-28.0.5-8c44d8b86f-53e05db5b7.zip/node_modules/ts-jest/",\
["virtual:04783e12400851b8a3d76e71495851cc94959db6e62f04cb0a31190080629440b182d8c8eb4d7f2b04e281912f2783a5fd4d2c3c6ab68d38b7097246c93f4c19#npm:28.0.5", {\
"packageLocation": "./.yarn/__virtual__/ts-jest-virtual-45e1f3673c/0/cache/ts-jest-npm-28.0.5-8c44d8b86f-53e05db5b7.zip/node_modules/ts-jest/",\
"packageDependencies": [\
["ts-jest", "virtual:16bfd8597041deb71e4581ea0755edd4dcd1b09b8ab14bfbbf5e4d5ca6b5d47ed7fbe2a25cdf57fcbb8e092c30b6beb93d2e7533f9e31c5dc62f7f0e487d1e4b#npm:28.0.5"],\
["ts-jest", "virtual:04783e12400851b8a3d76e71495851cc94959db6e62f04cb0a31190080629440b182d8c8eb4d7f2b04e281912f2783a5fd4d2c3c6ab68d38b7097246c93f4c19#npm:28.0.5"],\
["@babel/core", null],\
["@types/babel-jest", null],\
["@types/babel__core", null],\
["@types/esbuild", null],\
["@types/jest", "npm:28.1.2"],\
["@types/jest", "npm:28.1.3"],\
["@types/typescript", null],\
["babel-jest", null],\
["bs-logger", "npm:0.2.6"],\
["esbuild", null],\
["fast-json-stable-stringify", "npm:2.1.0"],\
["jest", "virtual:31b5a94a105c89c9294c3d524a7f8929fe63ee5a2efadf21951ca4c0cfd2ecf02e8f4ef5a066bbda091f1e3a56e57c6749069a080618c96b22e51131a330fc4a#npm:28.1.1"],\
["jest", "virtual:04783e12400851b8a3d76e71495851cc94959db6e62f04cb0a31190080629440b182d8c8eb4d7f2b04e281912f2783a5fd4d2c3c6ab68d38b7097246c93f4c19#npm:28.1.1"],\
["jest-util", "npm:28.1.1"],\
["json5", "npm:2.2.1"],\
["lodash.memoize", "npm:4.1.2"],\
@ -12270,21 +12374,21 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
],\
"linkType": "HARD"\
}],\
["virtual:31b5a94a105c89c9294c3d524a7f8929fe63ee5a2efadf21951ca4c0cfd2ecf02e8f4ef5a066bbda091f1e3a56e57c6749069a080618c96b22e51131a330fc4a#npm:28.0.5", {\
"packageLocation": "./.yarn/__virtual__/ts-jest-virtual-21163ab03c/0/cache/ts-jest-npm-28.0.5-8c44d8b86f-53e05db5b7.zip/node_modules/ts-jest/",\
["virtual:16bfd8597041deb71e4581ea0755edd4dcd1b09b8ab14bfbbf5e4d5ca6b5d47ed7fbe2a25cdf57fcbb8e092c30b6beb93d2e7533f9e31c5dc62f7f0e487d1e4b#npm:28.0.5", {\
"packageLocation": "./.yarn/__virtual__/ts-jest-virtual-c9b832d80c/0/cache/ts-jest-npm-28.0.5-8c44d8b86f-53e05db5b7.zip/node_modules/ts-jest/",\
"packageDependencies": [\
["ts-jest", "virtual:31b5a94a105c89c9294c3d524a7f8929fe63ee5a2efadf21951ca4c0cfd2ecf02e8f4ef5a066bbda091f1e3a56e57c6749069a080618c96b22e51131a330fc4a#npm:28.0.5"],\
["ts-jest", "virtual:16bfd8597041deb71e4581ea0755edd4dcd1b09b8ab14bfbbf5e4d5ca6b5d47ed7fbe2a25cdf57fcbb8e092c30b6beb93d2e7533f9e31c5dc62f7f0e487d1e4b#npm:28.0.5"],\
["@babel/core", null],\
["@types/babel-jest", null],\
["@types/babel__core", null],\
["@types/esbuild", null],\
["@types/jest", "npm:28.1.3"],\
["@types/jest", "npm:28.1.2"],\
["@types/typescript", null],\
["babel-jest", null],\
["bs-logger", "npm:0.2.6"],\
["esbuild", null],\
["fast-json-stable-stringify", "npm:2.1.0"],\
["jest", "virtual:31b5a94a105c89c9294c3d524a7f8929fe63ee5a2efadf21951ca4c0cfd2ecf02e8f4ef5a066bbda091f1e3a56e57c6749069a080618c96b22e51131a330fc4a#npm:28.1.1"],\
["jest", "virtual:04783e12400851b8a3d76e71495851cc94959db6e62f04cb0a31190080629440b182d8c8eb4d7f2b04e281912f2783a5fd4d2c3c6ab68d38b7097246c93f4c19#npm:28.1.1"],\
["jest-util", "npm:28.1.1"],\
["json5", "npm:2.2.1"],\
["lodash.memoize", "npm:4.1.2"],\
@ -12463,10 +12567,10 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
],\
"linkType": "HARD"\
}],\
["virtual:e64d2841693653abb2dee666d19406912f5e913a8081a709c081d9877d2f39987ff853b7cd736901a2df59af98328f7249f3db0da01abf060cf1d858d4d4e43b#npm:3.21.0", {\
"packageLocation": "./.yarn/__virtual__/tsutils-virtual-cd74663c37/0/cache/tsutils-npm-3.21.0-347e6636c5-1843f4c1b2.zip/node_modules/tsutils/",\
["virtual:d2330b8caf9d1ed905d5d037b08afe5d2dcb2220d795f2d96b3dd514824e8aefae721c334dbd0cd8d53c639ff2c9ad893d3d83b1092b2db05593aa9dc8e59994#npm:3.21.0", {\
"packageLocation": "./.yarn/__virtual__/tsutils-virtual-f8ae8a51d8/0/cache/tsutils-npm-3.21.0-347e6636c5-1843f4c1b2.zip/node_modules/tsutils/",\
"packageDependencies": [\
["tsutils", "virtual:e64d2841693653abb2dee666d19406912f5e913a8081a709c081d9877d2f39987ff853b7cd736901a2df59af98328f7249f3db0da01abf060cf1d858d4d4e43b#npm:3.21.0"],\
["tsutils", "virtual:d2330b8caf9d1ed905d5d037b08afe5d2dcb2220d795f2d96b3dd514824e8aefae721c334dbd0cd8d53c639ff2c9ad893d3d83b1092b2db05593aa9dc8e59994#npm:3.21.0"],\
["@types/typescript", null],\
["tslib", "npm:1.14.1"],\
["typescript", null]\

Binary file not shown.

Binary file not shown.

View file

@ -16,6 +16,7 @@
"lint:scheduler": "yarn workspace @standardnotes/scheduler-server lint",
"lint:syncing-server": "yarn workspace @standardnotes/syncing-server lint",
"lint:files": "yarn workspace @standardnotes/files-server lint",
"lint:api-gateway": "yarn workspace @standardnotes/api-gateway lint",
"test": "yarn workspaces foreach -p -j 10 --verbose run test",
"test:auth": "yarn workspace @standardnotes/auth-server test",
"test:scheduler": "yarn workspace @standardnotes/scheduler-server test",
@ -28,6 +29,7 @@
"build:scheduler": "yarn workspace @standardnotes/scheduler-server build",
"build:syncing-server": "yarn workspace @standardnotes/syncing-server build",
"build:files": "yarn workspace @standardnotes/files-server build",
"build:api-gateway": "yarn workspace @standardnotes/api-gateway build",
"start:auth": "yarn workspace @standardnotes/auth-server start",
"start:auth-worker": "yarn workspace @standardnotes/auth-server worker",
"start:scheduler": "yarn workspace @standardnotes/scheduler-server worker",
@ -35,6 +37,7 @@
"start:syncing-server-worker": "yarn workspace @standardnotes/syncing-server worker",
"start:files": "yarn workspace @standardnotes/files-server start",
"start:files-worker": "yarn workspace @standardnotes/files-server worker",
"start:api-gateway": "yarn workspace @standardnotes/api-gateway start",
"release:beta": "lerna version --conventional-prerelease --conventional-commits --yes -m \"chore(release): publish\""
},
"devDependencies": {

View file

@ -0,0 +1,33 @@
LOG_LEVEL=debug
NODE_ENV=development
VERSION=development
PORT=3000
SYNCING_SERVER_JS_URL=http://syncing_server_js:3000
AUTH_SERVER_URL=http://auth:3000
PAYMENTS_SERVER_URL=http://payments:3000
FILES_SERVER_URL=http://files:3000
HTTP_CALL_TIMEOUT=60000
AUTH_JWT_SECRET=auth_jwt_secret
# (Optional) New Relic Setup
NEW_RELIC_ENABLED=false
NEW_RELIC_APP_NAME=API Gateway
NEW_RELIC_LICENSE_KEY=
NEW_RELIC_NO_CONFIG_FILE=true
NEW_RELIC_DISTRIBUTED_TRACING_ENABLED=false
NEW_RELIC_LOG_ENABLED=false
NEW_RELIC_LOG_LEVEL=info
REDIS_URL=redis://cache
REDIS_EVENTS_CHANNEL=events
# (Optional) SNS Setup
SNS_TOPIC_ARN=
SNS_AWS_REGION=
# (Optional) Caching Cross Service Tokens
CROSS_SERVICE_TOKEN_CACHE_TTL=

View file

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

View file

@ -0,0 +1,6 @@
{
"extends": "../../.eslintrc",
"parserOptions": {
"project": "./linter.tsconfig.json"
}
}

View file

@ -0,0 +1,25 @@
FROM node:16.15.1-alpine AS builder
# Install dependencies for building native libraries
RUN apk add --update git openssh-client python3 alpine-sdk
WORKDIR /workspace
# docker-build plugin copies everything needed for `yarn install` to `manifests` folder.
COPY manifests ./
RUN yarn install --immutable
FROM node:16.15.1-alpine
WORKDIR /workspace
# Copy the installed dependencies from the previous stage.
COPY --from=builder /workspace ./
# docker-build plugin runs `yarn pack` in all workspace dependencies and copies them to `packs` folder.
COPY packs ./
ENTRYPOINT [ "/workspace/packages/api-gateway/docker/entrypoint.sh" ]
CMD [ "start-web" ]

View file

@ -0,0 +1,75 @@
import 'reflect-metadata'
import 'newrelic'
import { Logger } from 'winston'
import { ContainerConfigLoader } from '../src/Bootstrap/Container'
import TYPES from '../src/Bootstrap/Types'
import { Env } from '../src/Bootstrap/Env'
import { DomainEventPublisherInterface, DailyAnalyticsReportGeneratedEvent } from '@standardnotes/domain-events'
import { AnalyticsActivity, AnalyticsStoreInterface, Period, StatisticsStoreInterface } from '@standardnotes/analytics'
const requestReport = async (
analyticsStore: AnalyticsStoreInterface,
statisticsStore: StatisticsStoreInterface,
domainEventPublisher: DomainEventPublisherInterface,
): Promise<void> => {
const event: DailyAnalyticsReportGeneratedEvent = {
type: 'DAILY_ANALYTICS_REPORT_GENERATED',
createdAt: new Date(),
meta: {
correlation: {
userIdentifier: '',
userIdentifierType: 'uuid',
},
},
payload: {
applicationStatistics: await statisticsStore.getYesterdayApplicationUsage(),
snjsStatistics: await statisticsStore.getYesterdaySNJSUsage(),
outOfSyncIncidents: await statisticsStore.getYesterdayOutOfSyncIncidents(),
activityStatistics: [
{
name: AnalyticsActivity.EditingItems,
retention: await analyticsStore.calculateActivityRetention(
AnalyticsActivity.EditingItems,
Period.DayBeforeYesterday,
Period.Yesterday,
),
totalCount: await analyticsStore.calculateActivityTotalCount(
AnalyticsActivity.EditingItems,
Period.Yesterday,
),
},
],
},
}
await domainEventPublisher.publish(event)
}
const container = new ContainerConfigLoader()
void container.load().then((container) => {
const env: Env = new Env()
env.load()
const logger: Logger = container.get(TYPES.Logger)
logger.info('Starting usage report generation...')
const analyticsStore: AnalyticsStoreInterface = container.get(TYPES.AnalyticsStore)
const statisticsStore: StatisticsStoreInterface = container.get(TYPES.StatisticsStore)
const domainEventPublisher: DomainEventPublisherInterface = container.get(TYPES.DomainEventPublisher)
Promise.resolve(requestReport(analyticsStore, statisticsStore, domainEventPublisher))
.then(() => {
logger.info('Usage report generation complete')
process.exit(0)
})
.catch((error) => {
logger.error(`Could not finish usage report generation: ${error.message}`)
process.exit(1)
})
})

View file

@ -0,0 +1,113 @@
import 'reflect-metadata'
import 'newrelic'
import * as Sentry from '@sentry/node'
import '../src/Controller/LegacyController'
import '../src/Controller/HealthCheckController'
import '../src/Controller/v1/SessionsController'
import '../src/Controller/v1/UsersController'
import '../src/Controller/v1/ActionsController'
import '../src/Controller/v1/InvoicesController'
import '../src/Controller/v1/RevisionsController'
import '../src/Controller/v1/ItemsController'
import '../src/Controller/v1/PaymentsController'
import '../src/Controller/v1/WebSocketsController'
import '../src/Controller/v1/TokensController'
import '../src/Controller/v1/OfflineController'
import '../src/Controller/v1/FilesController'
import '../src/Controller/v1/SubscriptionInvitesController'
import '../src/Controller/v2/PaymentsControllerV2'
import '../src/Controller/v2/ActionsControllerV2'
import * as helmet from 'helmet'
import * as cors from 'cors'
import { text, json, Request, Response, NextFunction, RequestHandler, ErrorRequestHandler } from 'express'
import * as winston from 'winston'
import { InversifyExpressServer } from 'inversify-express-utils'
import { ContainerConfigLoader } from '../src/Bootstrap/Container'
import TYPES from '../src/Bootstrap/Types'
import { Env } from '../src/Bootstrap/Env'
const container = new ContainerConfigLoader()
void container.load().then((container) => {
const env: Env = new Env()
env.load()
const server = new InversifyExpressServer(container)
server.setConfig((app) => {
app.use((_request: Request, response: Response, next: NextFunction) => {
response.setHeader('X-API-Gateway-Version', container.get(TYPES.VERSION))
next()
})
/* eslint-disable */
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["https: 'self'"],
baseUri: ["'self'"],
childSrc: ["*", "blob:"],
connectSrc: ["*"],
fontSrc: ["*", "'self'"],
formAction: ["'self'"],
frameAncestors: ["*", "*.standardnotes.org", "*.standardnotes.com"],
frameSrc: ["*", "blob:"],
imgSrc: ["'self'", "*", "data:"],
manifestSrc: ["'self'"],
mediaSrc: ["'self'"],
objectSrc: ["'self'"],
scriptSrc: ["'self'"],
styleSrc: ["'self'"]
}
}
}))
/* eslint-enable */
app.use(json({ limit: '50mb' }))
app.use(
text({
type: ['text/plain', 'application/x-www-form-urlencoded', 'application/x-www-form-urlencoded; charset=utf-8'],
}),
)
app.use(cors())
if (env.get('SENTRY_DSN', true)) {
Sentry.init({
dsn: env.get('SENTRY_DSN'),
integrations: [new Sentry.Integrations.Http({ tracing: false, breadcrumbs: true })],
tracesSampleRate: 0,
})
app.use(Sentry.Handlers.requestHandler() as RequestHandler)
}
})
const logger: winston.Logger = container.get(TYPES.Logger)
server.setErrorConfig((app) => {
if (env.get('SENTRY_DSN', true)) {
app.use(Sentry.Handlers.errorHandler() as ErrorRequestHandler)
}
app.use((error: Record<string, unknown>, _request: Request, response: Response, _next: NextFunction) => {
logger.error(error.stack)
response.status(500).send({
error: {
message:
"Unfortunately, we couldn't handle your request. Please try again or contact our support if the error persists.",
},
})
})
})
const serverInstance = server.build()
serverInstance.listen(env.get('PORT'))
logger.info(`Server started on port ${process.env.PORT}`)
})

View file

@ -0,0 +1,29 @@
#!/bin/sh
set -e
COMMAND=$1 && shift 1
case "$COMMAND" in
'start-local' )
echo "Building the project..."
yarn workspace @standardnotes/api-gateway build
echo "Starting Web..."
yarn workspace @standardnotes/api-gateway start
;;
'start-web' )
echo "Starting Web..."
yarn workspace @standardnotes/api-gateway start
;;
'report' )
echo "Starting Usage Report Generation..."
yarn workspace @standardnotes/api-gateway report
;;
* )
echo "Unknown command"
;;
esac
exec "$@"

View file

@ -0,0 +1,18 @@
// eslint-disable-next-line @typescript-eslint/no-var-requires
const base = require('../../jest.config');
module.exports = {
...base,
globals: {
'ts-jest': {
tsconfig: 'tsconfig.json',
},
},
coveragePathIgnorePatterns: [
'/Bootstrap/',
'HealthCheckController'
],
setupFilesAfterEnv: [
'./test-setup.ts'
]
};

View file

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

View file

@ -0,0 +1,60 @@
{
"name": "@standardnotes/api-gateway",
"version": "1.0.0",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
"description": "API Gateway For Standard Notes Services",
"main": "dist/src/index.js",
"typings": "dist/src/index.d.ts",
"repository": "git@github.com:standardnotes/api-gateway.git",
"author": "Karol Sójko <karolsojko@standardnotes.com>",
"license": "AGPL-3.0-or-later",
"scripts": {
"clean": "rm -fr dist",
"prebuild": "yarn clean",
"build": "tsc --rootDir ./",
"lint": "eslint . --ext .ts",
"start": "yarn node dist/bin/server.js",
"report": "yarn node dist/bin/report.js"
},
"dependencies": {
"@newrelic/native-metrics": "7.0.2",
"@newrelic/winston-enricher": "^2.1.0",
"@sentry/node": "^6.16.1",
"@standardnotes/analytics": "^1.4.0",
"@standardnotes/auth": "3.19.2",
"@standardnotes/domain-events": "2.29.0",
"@standardnotes/domain-events-infra": "1.4.127",
"@standardnotes/time": "^1.7.0",
"aws-sdk": "^2.1160.0",
"axios": "0.24.0",
"cors": "2.8.5",
"dotenv": "8.2.0",
"express": "4.17.1",
"helmet": "4.4.1",
"inversify": "^6.0.1",
"inversify-express-utils": "^6.4.3",
"ioredis": "^5.0.6",
"jsonwebtoken": "8.5.1",
"newrelic": "8.6.0",
"prettyjson": "1.2.1",
"reflect-metadata": "0.1.13",
"winston": "3.3.3"
},
"devDependencies": {
"@types/cors": "^2.8.9",
"@types/express": "^4.17.11",
"@types/ioredis": "^4.28.10",
"@types/jest": "^28.1.3",
"@types/jsonwebtoken": "^8.5.0",
"@types/newrelic": "^7.0.1",
"@types/prettyjson": "^0.0.29",
"@typescript-eslint/eslint-plugin": "^5.29.0",
"eslint": "^8.14.0",
"eslint-plugin-prettier": "^4.0.0",
"jest": "^28.1.1",
"nodemon": "^2.0.16",
"ts-jest": "^28.0.1"
}
}

View file

@ -0,0 +1,117 @@
import * as winston from 'winston'
import axios, { AxiosInstance } from 'axios'
import Redis from 'ioredis'
import { Container } from 'inversify'
import * as AWS from 'aws-sdk'
import {
AnalyticsStoreInterface,
PeriodKeyGenerator,
RedisAnalyticsStore,
RedisStatisticsStore,
StatisticsStoreInterface,
} from '@standardnotes/analytics'
import { RedisDomainEventPublisher, SNSDomainEventPublisher } from '@standardnotes/domain-events-infra'
import { Timer, TimerInterface } from '@standardnotes/time'
import { Env } from './Env'
import TYPES from './Types'
import { AuthMiddleware } from '../Controller/AuthMiddleware'
import { HttpServiceInterface } from '../Service/Http/HttpServiceInterface'
import { HttpService } from '../Service/Http/HttpService'
import { SubscriptionTokenAuthMiddleware } from '../Controller/SubscriptionTokenAuthMiddleware'
import { StatisticsMiddleware } from '../Controller/StatisticsMiddleware'
import { CrossServiceTokenCacheInterface } from '../Service/Cache/CrossServiceTokenCacheInterface'
import { RedisCrossServiceTokenCache } from '../Infra/Redis/RedisCrossServiceTokenCache'
// eslint-disable-next-line @typescript-eslint/no-var-requires
const newrelicWinstonEnricher = require('@newrelic/winston-enricher')
export class ContainerConfigLoader {
async load(): Promise<Container> {
const env: Env = new Env()
env.load()
const container = new Container()
const winstonFormatters = [winston.format.splat(), winston.format.json()]
if (env.get('NEW_RELIC_ENABLED', true) === 'true') {
winstonFormatters.push(newrelicWinstonEnricher())
}
const logger = winston.createLogger({
level: env.get('LOG_LEVEL') || 'info',
format: winston.format.combine(...winstonFormatters),
transports: [new winston.transports.Console({ level: env.get('LOG_LEVEL') || 'info' })],
})
container.bind<winston.Logger>(TYPES.Logger).toConstantValue(logger)
const redisUrl = env.get('REDIS_URL')
const isRedisInClusterMode = redisUrl.indexOf(',') > 0
let redis
if (isRedisInClusterMode) {
redis = new Redis.Cluster(redisUrl.split(','))
} else {
redis = new Redis(redisUrl)
}
container.bind(TYPES.Redis).toConstantValue(redis)
if (env.get('SNS_AWS_REGION', true)) {
container.bind<AWS.SNS>(TYPES.SNS).toConstantValue(
new AWS.SNS({
apiVersion: 'latest',
region: env.get('SNS_AWS_REGION', true),
}),
)
}
container.bind<AxiosInstance>(TYPES.HTTPClient).toConstantValue(axios.create())
// env vars
container.bind(TYPES.SYNCING_SERVER_JS_URL).toConstantValue(env.get('SYNCING_SERVER_JS_URL'))
container.bind(TYPES.AUTH_SERVER_URL).toConstantValue(env.get('AUTH_SERVER_URL'))
container.bind(TYPES.PAYMENTS_SERVER_URL).toConstantValue(env.get('PAYMENTS_SERVER_URL', true))
container.bind(TYPES.FILES_SERVER_URL).toConstantValue(env.get('FILES_SERVER_URL', true))
container.bind(TYPES.AUTH_JWT_SECRET).toConstantValue(env.get('AUTH_JWT_SECRET'))
container
.bind(TYPES.HTTP_CALL_TIMEOUT)
.toConstantValue(env.get('HTTP_CALL_TIMEOUT', true) ? +env.get('HTTP_CALL_TIMEOUT', true) : 60_000)
container.bind(TYPES.VERSION).toConstantValue(env.get('VERSION'))
container.bind(TYPES.SNS_TOPIC_ARN).toConstantValue(env.get('SNS_TOPIC_ARN', true))
container.bind(TYPES.SNS_AWS_REGION).toConstantValue(env.get('SNS_AWS_REGION', true))
container.bind(TYPES.REDIS_EVENTS_CHANNEL).toConstantValue(env.get('REDIS_EVENTS_CHANNEL'))
container.bind(TYPES.CROSS_SERVICE_TOKEN_CACHE_TTL).toConstantValue(+env.get('CROSS_SERVICE_TOKEN_CACHE_TTL', true))
// Middleware
container.bind<AuthMiddleware>(TYPES.AuthMiddleware).to(AuthMiddleware)
container
.bind<SubscriptionTokenAuthMiddleware>(TYPES.SubscriptionTokenAuthMiddleware)
.to(SubscriptionTokenAuthMiddleware)
container.bind<StatisticsMiddleware>(TYPES.StatisticsMiddleware).to(StatisticsMiddleware)
// Services
container.bind<HttpServiceInterface>(TYPES.HTTPService).to(HttpService)
const periodKeyGenerator = new PeriodKeyGenerator()
container
.bind<AnalyticsStoreInterface>(TYPES.AnalyticsStore)
.toConstantValue(new RedisAnalyticsStore(periodKeyGenerator, container.get(TYPES.Redis)))
container
.bind<StatisticsStoreInterface>(TYPES.StatisticsStore)
.toConstantValue(new RedisStatisticsStore(periodKeyGenerator, container.get(TYPES.Redis)))
container.bind<CrossServiceTokenCacheInterface>(TYPES.CrossServiceTokenCache).to(RedisCrossServiceTokenCache)
container.bind<TimerInterface>(TYPES.Timer).toConstantValue(new Timer())
if (env.get('SNS_TOPIC_ARN', true)) {
container
.bind<SNSDomainEventPublisher>(TYPES.DomainEventPublisher)
.toConstantValue(new SNSDomainEventPublisher(container.get(TYPES.SNS), container.get(TYPES.SNS_TOPIC_ARN)))
} else {
container
.bind<RedisDomainEventPublisher>(TYPES.DomainEventPublisher)
.toConstantValue(
new RedisDomainEventPublisher(container.get(TYPES.Redis), container.get(TYPES.REDIS_EVENTS_CHANNEL)),
)
}
return container
}
}

View file

@ -0,0 +1,24 @@
import { config, DotenvParseOutput } from 'dotenv'
import { injectable } from 'inversify'
@injectable()
export class Env {
private env?: DotenvParseOutput
public load(): void {
const output = config()
this.env = <DotenvParseOutput>output.parsed
}
public get(key: string, optional = false): string {
if (!this.env) {
this.load()
}
if (!process.env[key] && !optional) {
throw new Error(`Environment variable ${key} not set`)
}
return <string>process.env[key]
}
}

View file

@ -0,0 +1,31 @@
const TYPES = {
Logger: Symbol.for('Logger'),
Redis: Symbol.for('Redis'),
HTTPClient: Symbol.for('HTTPClient'),
SNS: Symbol.for('SNS'),
// env vars
SYNCING_SERVER_JS_URL: Symbol.for('SYNCING_SERVER_JS_URL'),
AUTH_SERVER_URL: Symbol.for('AUTH_SERVER_URL'),
PAYMENTS_SERVER_URL: Symbol.for('PAYMENTS_SERVER_URL'),
FILES_SERVER_URL: Symbol.for('FILES_SERVER_URL'),
AUTH_JWT_SECRET: Symbol.for('AUTH_JWT_SECRET'),
HTTP_CALL_TIMEOUT: Symbol.for('HTTP_CALL_TIMEOUT'),
VERSION: Symbol.for('VERSION'),
SNS_TOPIC_ARN: Symbol.for('SNS_TOPIC_ARN'),
SNS_AWS_REGION: Symbol.for('SNS_AWS_REGION'),
REDIS_EVENTS_CHANNEL: Symbol.for('REDIS_EVENTS_CHANNEL'),
CROSS_SERVICE_TOKEN_CACHE_TTL: Symbol.for('CROSS_SERVICE_TOKEN_CACHE_TTL'),
// Middleware
StatisticsMiddleware: Symbol.for('StatisticsMiddleware'),
AuthMiddleware: Symbol.for('AuthMiddleware'),
SubscriptionTokenAuthMiddleware: Symbol.for('SubscriptionTokenAuthMiddleware'),
// Services
HTTPService: Symbol.for('HTTPService'),
CrossServiceTokenCache: Symbol.for('CrossServiceTokenCache'),
AnalyticsStore: Symbol.for('AnalyticsStore'),
StatisticsStore: Symbol.for('StatisticsStore'),
DomainEventPublisher: Symbol.for('DomainEventPublisher'),
Timer: Symbol.for('Timer'),
}
export default TYPES

View file

@ -0,0 +1,124 @@
import { CrossServiceTokenData } from '@standardnotes/auth'
import { TimerInterface } from '@standardnotes/time'
import { NextFunction, Request, Response } from 'express'
import { inject, injectable } from 'inversify'
import { BaseMiddleware } from 'inversify-express-utils'
import { verify } from 'jsonwebtoken'
import { AxiosError, AxiosInstance } from 'axios'
import { Logger } from 'winston'
import TYPES from '../Bootstrap/Types'
import { CrossServiceTokenCacheInterface } from '../Service/Cache/CrossServiceTokenCacheInterface'
@injectable()
export class AuthMiddleware extends BaseMiddleware {
constructor(
@inject(TYPES.HTTPClient) private httpClient: AxiosInstance,
@inject(TYPES.AUTH_SERVER_URL) private authServerUrl: string,
@inject(TYPES.AUTH_JWT_SECRET) private jwtSecret: string,
@inject(TYPES.CROSS_SERVICE_TOKEN_CACHE_TTL) private crossServiceTokenCacheTTL: number,
@inject(TYPES.CrossServiceTokenCache) private crossServiceTokenCache: CrossServiceTokenCacheInterface,
@inject(TYPES.Timer) private timer: TimerInterface,
@inject(TYPES.Logger) private logger: Logger,
) {
super()
}
async handler(request: Request, response: Response, next: NextFunction): Promise<void> {
const authHeaderValue = request.headers.authorization as string
if (!authHeaderValue) {
response.status(401).send({
error: {
tag: 'invalid-auth',
message: 'Invalid login credentials.',
},
})
return
}
try {
let crossServiceTokenFetchedFromCache = true
let crossServiceToken = null
if (this.crossServiceTokenCacheTTL) {
crossServiceToken = await this.crossServiceTokenCache.get(authHeaderValue)
}
if (crossServiceToken === null) {
const authResponse = await this.httpClient.request({
method: 'POST',
headers: {
Authorization: authHeaderValue,
Accept: 'application/json',
},
validateStatus: (status: number) => {
return status >= 200 && status < 500
},
url: `${this.authServerUrl}/sessions/validate`,
})
if (authResponse.status > 200) {
response.setHeader('content-type', authResponse.headers['content-type'])
response.status(authResponse.status).send(authResponse.data)
return
}
crossServiceToken = authResponse.data.authToken
crossServiceTokenFetchedFromCache = false
}
response.locals.authToken = crossServiceToken
const decodedToken = <CrossServiceTokenData>verify(crossServiceToken, this.jwtSecret, { algorithms: ['HS256'] })
if (this.crossServiceTokenCacheTTL && !crossServiceTokenFetchedFromCache) {
await this.crossServiceTokenCache.set({
authorizationHeaderValue: authHeaderValue,
encodedCrossServiceToken: crossServiceToken,
expiresAtInSeconds: this.getCrossServiceTokenCacheExpireTimestamp(decodedToken),
userUuid: decodedToken.user.uuid,
})
}
response.locals.userUuid = decodedToken.user.uuid
response.locals.roles = decodedToken.roles
} catch (error) {
const errorMessage = (error as AxiosError).isAxiosError
? JSON.stringify((error as AxiosError).response?.data)
: (error as Error).message
this.logger.error(
`Could not pass the request to ${this.authServerUrl}/sessions/validate on underlying service: ${errorMessage}`,
)
this.logger.debug('Response error: %O', (error as AxiosError).response ?? 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 ? +((error as AxiosError).code as string) : 500
response.status(errorCode).send(errorMessage)
return
}
return next()
}
private getCrossServiceTokenCacheExpireTimestamp(token: CrossServiceTokenData): number {
const crossServiceTokenDefaultCacheExpiration = this.timer.getTimestampInSeconds() + this.crossServiceTokenCacheTTL
if (token.session === undefined) {
return crossServiceTokenDefaultCacheExpiration
}
const sessionAccessExpiration = this.timer.convertStringDateToSeconds(token.session.access_expiration)
const sessionRefreshExpiration = this.timer.convertStringDateToSeconds(token.session.refresh_expiration)
return Math.min(crossServiceTokenDefaultCacheExpiration, sessionAccessExpiration, sessionRefreshExpiration)
}
}

View file

@ -0,0 +1,9 @@
import { controller, httpGet } from 'inversify-express-utils'
@controller('/healthcheck')
export class HealthCheckController {
@httpGet('/')
public async get(): Promise<string> {
return 'OK'
}
}

View file

@ -0,0 +1,124 @@
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 { HttpServiceInterface } from '../Service/Http/HttpServiceInterface'
@controller('', TYPES.StatisticsMiddleware)
export class LegacyController extends BaseHttpController {
private AUTH_ROUTES: Map<string, string>
private PARAMETRIZED_AUTH_ROUTES: Map<string, string>
constructor(@inject(TYPES.HTTPService) private httpService: HttpServiceInterface) {
super()
this.AUTH_ROUTES = new Map([
['POST:/auth', 'POST:auth'],
['POST:/auth/sign_out', 'POST:auth/sign_out'],
['POST:/auth/change_pw', 'PUT:/users/legacy-endpoint-user/attributes/credentials'],
['GET:/sessions', 'GET:sessions'],
['DELETE:/session', 'DELETE:session'],
['DELETE:/session/all', 'DELETE:session/all'],
['POST:/session/refresh', 'POST:session/refresh'],
['POST:/auth/sign_in', 'POST:auth/sign_in'],
['GET:/auth/params', 'GET:auth/params'],
])
this.PARAMETRIZED_AUTH_ROUTES = new Map([
['PATCH:/users/([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})', 'users/{uuid}'],
])
}
@httpPost('/items/sync', TYPES.AuthMiddleware)
async legacyItemsSync(request: Request, response: Response): Promise<void> {
await this.httpService.callLegacySyncingServer(request, response, request.path.substring(1), request.body)
}
@httpGet('/items/:item_id/revisions', TYPES.AuthMiddleware)
async legacyGetRevisions(request: Request, response: Response): Promise<void> {
await this.httpService.callLegacySyncingServer(request, response, request.path.substring(1), request.body)
}
@httpGet('/items/:item_id/revisions/:id', TYPES.AuthMiddleware)
async legacyGetRevision(request: Request, response: Response): Promise<void> {
await this.httpService.callLegacySyncingServer(request, response, request.path.substring(1), request.body)
}
@httpGet('/items/mfa/:userUuid')
async blockedMFARequest(): Promise<results.StatusCodeResult> {
return this.statusCode(401)
}
@httpDelete('/items/mfa/:userUuid')
async blockedMFARemoveRequest(): Promise<results.StatusCodeResult> {
return this.statusCode(401)
}
@all('*')
async legacyProxyToSyncingServer(request: Request, response: Response): Promise<void> {
if (request.path === '/') {
response.send('Welcome to the Standard Notes server infrastructure. Learn more at https://docs.standardnotes.com')
return
}
if (this.shouldBeRedirectedToAuthService(request)) {
const methodAndPath = this.getMethodAndPath(request)
request.method = methodAndPath.method
await this.httpService.callAuthServerWithLegacyFormat(request, response, methodAndPath.path, request.body)
return
}
await this.httpService.callLegacySyncingServer(request, response, request.path.substring(1), request.body)
}
private getMethodAndPath(request: Request): { method: string; path: string } {
const requestKey = `${request.method}:${request.path}`
if (this.AUTH_ROUTES.has(requestKey)) {
const legacyRoute = this.AUTH_ROUTES.get(requestKey) as string
const legacyRouteMethodAndPath = legacyRoute.split(':')
return {
method: legacyRouteMethodAndPath[0],
path: legacyRouteMethodAndPath[1],
}
}
for (const key of this.AUTH_ROUTES.keys()) {
const regExp = new RegExp(key)
const matches = regExp.exec(requestKey)
if (matches !== null) {
const legacyRoute = (this.AUTH_ROUTES.get(key) as string).replace('{uuid}', matches[1])
const legacyRouteMethodAndPath = legacyRoute.split(':')
return {
method: legacyRouteMethodAndPath[0],
path: legacyRouteMethodAndPath[1],
}
}
}
throw Error('could not find path for key')
}
private shouldBeRedirectedToAuthService(request: Request): boolean {
const requestKey = `${request.method}:${request.path}`
if (this.AUTH_ROUTES.has(requestKey)) {
return true
}
for (const key of this.PARAMETRIZED_AUTH_ROUTES.keys()) {
const regExp = new RegExp(key)
const matches = regExp.test(requestKey)
if (matches) {
return true
}
}
return false
}
}

View file

@ -0,0 +1,31 @@
import { NextFunction, Request, Response } from 'express'
import { inject, injectable } from 'inversify'
import { BaseMiddleware } from 'inversify-express-utils'
import { Logger } from 'winston'
import { StatisticsStoreInterface } from '@standardnotes/analytics'
import TYPES from '../Bootstrap/Types'
@injectable()
export class StatisticsMiddleware extends BaseMiddleware {
constructor(
@inject(TYPES.StatisticsStore) private statisticsStore: StatisticsStoreInterface,
@inject(TYPES.Logger) private logger: Logger,
) {
super()
}
async handler(request: Request, _response: Response, next: NextFunction): Promise<void> {
try {
const snjsVersion = request.headers['x-snjs-version'] ?? 'unknown'
await this.statisticsStore.incrementSNJSVersionUsage(snjsVersion as string)
const applicationVersion = request.headers['x-application-version'] ?? 'unknown'
await this.statisticsStore.incrementApplicationVersionUsage(applicationVersion as string)
} catch (error) {
this.logger.error(`Could not store analytics data: ${(error as Error).message}`)
}
return next()
}
}

View file

@ -0,0 +1,120 @@
import { OfflineUserTokenData, CrossServiceTokenData } from '@standardnotes/auth'
import { NextFunction, Request, Response } from 'express'
import { inject, injectable } from 'inversify'
import { BaseMiddleware } from 'inversify-express-utils'
import { verify } from 'jsonwebtoken'
import { AxiosError, AxiosInstance, AxiosResponse } from 'axios'
import { Logger } from 'winston'
import TYPES from '../Bootstrap/Types'
import { TokenAuthenticationMethod } from './TokenAuthenticationMethod'
@injectable()
export class SubscriptionTokenAuthMiddleware extends BaseMiddleware {
constructor(
@inject(TYPES.HTTPClient) private httpClient: AxiosInstance,
@inject(TYPES.AUTH_SERVER_URL) private authServerUrl: string,
@inject(TYPES.AUTH_JWT_SECRET) private jwtSecret: string,
@inject(TYPES.Logger) private logger: Logger,
) {
super()
}
async handler(request: Request, response: Response, next: NextFunction): Promise<void> {
const subscriptionToken = request.query.subscription_token
const email = request.headers['x-offline-email']
if (!subscriptionToken) {
response.status(401).send({
error: {
tag: 'invalid-auth',
message: 'Invalid login credentials.',
},
})
return
}
response.locals.tokenAuthenticationMethod = email
? TokenAuthenticationMethod.OfflineSubscriptionToken
: TokenAuthenticationMethod.SubscriptionToken
try {
const url =
response.locals.tokenAuthenticationMethod == TokenAuthenticationMethod.OfflineSubscriptionToken
? `${this.authServerUrl}/offline/subscription-tokens/${subscriptionToken}/validate`
: `${this.authServerUrl}/subscription-tokens/${subscriptionToken}/validate`
const authResponse = await this.httpClient.request({
method: 'POST',
headers: {
Accept: 'application/json',
},
data: {
email,
},
validateStatus: (status: number) => {
return status >= 200 && status < 500
},
url,
})
if (authResponse.status > 200) {
response.setHeader('content-type', authResponse.headers['content-type'])
response.status(authResponse.status).send(authResponse.data)
return
}
if (response.locals.tokenAuthenticationMethod == TokenAuthenticationMethod.OfflineSubscriptionToken) {
this.handleOfflineAuthTokenValidationResponse(response, authResponse)
return next()
}
this.handleAuthTokenValidationResponse(response, authResponse)
return next()
} catch (error) {
const errorMessage = (error as AxiosError).isAxiosError
? JSON.stringify((error as AxiosError).response?.data)
: (error as Error).message
this.logger.error(
`Could not pass the request to ${this.authServerUrl}/subscription-tokens/${subscriptionToken}/validate on underlying service: ${errorMessage}`,
)
this.logger.debug('Response error: %O', (error as AxiosError).response ?? 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 ? +((error as AxiosError).code as string) : 500
response.status(errorCode).send(errorMessage)
return
}
}
private handleOfflineAuthTokenValidationResponse(response: Response, authResponse: AxiosResponse) {
response.locals.offlineAuthToken = authResponse.data.authToken
const decodedToken = <OfflineUserTokenData>(
verify(authResponse.data.authToken, this.jwtSecret, { algorithms: ['HS256'] })
)
response.locals.offlineUserEmail = decodedToken.userEmail
response.locals.offlineFeaturesToken = decodedToken.featuresToken
}
private handleAuthTokenValidationResponse(response: Response, authResponse: AxiosResponse) {
response.locals.authToken = authResponse.data.authToken
const decodedToken = <CrossServiceTokenData>(
verify(authResponse.data.authToken, this.jwtSecret, { algorithms: ['HS256'] })
)
response.locals.userUuid = decodedToken.user.uuid
response.locals.roles = decodedToken.roles
}
}

View file

@ -0,0 +1,4 @@
export enum TokenAuthenticationMethod {
OfflineSubscriptionToken = 'OfflineSubscriptionToken',
SubscriptionToken = 'SubscriptionToken',
}

View file

@ -0,0 +1,52 @@
import { Request, Response } from 'express'
import { inject } from 'inversify'
import { BaseHttpController, controller, httpGet, httpPost } from 'inversify-express-utils'
import TYPES from '../../Bootstrap/Types'
import { HttpServiceInterface } from '../../Service/Http/HttpServiceInterface'
@controller('/v1', TYPES.StatisticsMiddleware)
export class ActionsController extends BaseHttpController {
constructor(@inject(TYPES.HTTPService) private httpService: HttpServiceInterface) {
super()
}
@httpPost('/login')
async login(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(request, response, 'auth/sign_in', request.body)
}
@httpGet('/login-params')
async loginParams(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(request, response, 'auth/params', request.body)
}
@httpPost('/logout')
async logout(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(request, response, 'auth/sign_out', request.body)
}
@httpGet('/auth/methods')
async methods(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(request, response, 'auth/methods', request.body)
}
@httpGet('/failed-backups-emails/mute/:settingUuid')
async muteFailedBackupsEmails(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(
request,
response,
`internal/settings/email_backup/${request.params.settingUuid}/mute`,
request.body,
)
}
@httpGet('/sign-in-emails/mute/:settingUuid')
async muteSignInEmails(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(
request,
response,
`internal/settings/sign_in/${request.params.settingUuid}/mute`,
request.body,
)
}
}

View file

@ -0,0 +1,18 @@
import { Request, Response } from 'express'
import { inject } from 'inversify'
import { BaseHttpController, controller, httpPost } from 'inversify-express-utils'
import TYPES from '../../Bootstrap/Types'
import { HttpServiceInterface } from '../../Service/Http/HttpServiceInterface'
@controller('/v1/files', TYPES.StatisticsMiddleware)
export class FilesController extends BaseHttpController {
constructor(@inject(TYPES.HTTPService) private httpService: HttpServiceInterface) {
super()
}
@httpPost('/valet-tokens', TYPES.AuthMiddleware)
async createToken(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(request, response, 'valet-tokens', request.body)
}
}

View file

@ -0,0 +1,17 @@
import { Request, Response } from 'express'
import { BaseHttpController, controller, httpPost } from 'inversify-express-utils'
import { inject } from 'inversify'
import TYPES from '../../Bootstrap/Types'
import { HttpServiceInterface } from '../../Service/Http/HttpServiceInterface'
@controller('/v1', TYPES.StatisticsMiddleware)
export class InvoicesController extends BaseHttpController {
constructor(@inject(TYPES.HTTPService) private httpService: HttpServiceInterface) {
super()
}
@httpPost('/invoices/send-latest', TYPES.SubscriptionTokenAuthMiddleware)
async sendLatestInvoice(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(request, response, 'api/pro_users/send-invoice', request.body)
}
}

View file

@ -0,0 +1,27 @@
import { Request, Response } from 'express'
import { inject } from 'inversify'
import { BaseHttpController, controller, httpGet, httpPost } from 'inversify-express-utils'
import TYPES from '../../Bootstrap/Types'
import { HttpServiceInterface } from '../../Service/Http/HttpServiceInterface'
@controller('/v1/items', TYPES.StatisticsMiddleware, TYPES.AuthMiddleware)
export class ItemsController extends BaseHttpController {
constructor(@inject(TYPES.HTTPService) private httpService: HttpServiceInterface) {
super()
}
@httpPost('/')
async sync(request: Request, response: Response): Promise<void> {
await this.httpService.callSyncingServer(request, response, 'items/sync', request.body)
}
@httpPost('/check-integrity')
async checkIntegrity(request: Request, response: Response): Promise<void> {
await this.httpService.callSyncingServer(request, response, 'items/check-integrity', request.body)
}
@httpGet('/:uuid')
async getItem(request: Request, response: Response): Promise<void> {
await this.httpService.callSyncingServer(request, response, `items/${request.params.uuid}`, request.body)
}
}

View file

@ -0,0 +1,33 @@
import { Request, Response } from 'express'
import { inject } from 'inversify'
import { BaseHttpController, controller, httpGet, httpPost } from 'inversify-express-utils'
import TYPES from '../../Bootstrap/Types'
import { HttpServiceInterface } from '../../Service/Http/HttpServiceInterface'
@controller('/v1/offline', TYPES.StatisticsMiddleware)
export class OfflineController extends BaseHttpController {
constructor(@inject(TYPES.HTTPService) private httpService: HttpServiceInterface) {
super()
}
@httpGet('/features')
async getOfflineFeatures(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(request, response, 'offline/features', request.body)
}
@httpPost('/subscription-tokens')
async createOfflineSubscriptionToken(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(request, response, 'offline/subscription-tokens', request.body)
}
@httpPost('/payments/stripe-setup-intent')
async createStripeSetupIntent(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(
request,
response,
'api/pro_users/stripe-setup-intent/offline',
request.body,
)
}
}

View file

@ -0,0 +1,162 @@
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 { HttpServiceInterface } from '../../Service/Http/HttpServiceInterface'
@controller('/v1', TYPES.StatisticsMiddleware)
export class PaymentsController extends BaseHttpController {
constructor(@inject(TYPES.HTTPService) private httpService: HttpServiceInterface) {
super()
}
@httpGet('/downloads')
async downloads(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(request, response, 'api/downloads', request.body)
}
@httpGet('/downloads/download-info')
async downloadInfo(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(request, response, 'api/downloads/download-info', request.body)
}
@httpGet('/downloads/platforms')
async platformDownloads(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(request, response, 'api/downloads/platforms', request.body)
}
@httpGet('/help/categories')
async categoriesHelp(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(request, response, 'api/help/categories', request.body)
}
@httpGet('/knowledge/categories')
async categoriesKnowledge(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(request, response, 'api/knowledge/categories', request.body)
}
@httpGet('/extensions')
async extensions(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(request, response, 'api/extensions', request.body)
}
@httpPost('/subscriptions/tiered', TYPES.SubscriptionTokenAuthMiddleware)
async createTieredSubscription(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(request, response, 'api/subscriptions/tiered', request.body)
}
@all('/subscriptions(/*)?')
async subscriptions(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(request, response, request.path.replace('v1', 'api'), request.body)
}
@httpGet('/reset/validate')
async validateReset(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(request, response, 'api/reset/validate', request.body)
}
@httpDelete('/reset')
async reset(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(request, response, 'api/reset', request.body)
}
@httpPost('/reset')
async resetRequest(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(request, response, 'api/reset', request.body)
}
@httpPost('/user-registration')
async userRegistration(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(request, response, 'admin/events/registration', request.body)
}
@httpPost('/admin/graphql')
async adminGraphql(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(request, response, 'admin/graphql', request.body)
}
@httpPost('/admin/auth/login')
async adminLogin(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(request, response, 'admin/auth/login', request.body)
}
@httpPost('/admin/auth/logout')
async adminLogout(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(request, response, 'admin/auth/logout', request.body)
}
@httpPost('/students')
async students(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(request, response, 'api/students', request.body)
}
@httpPost('/students/:token/approve')
async studentsApprove(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(
request,
response,
`api/students/${request.params.token}/approve`,
request.body,
)
}
@httpPost('/email_subscriptions/:token/less')
async subscriptionsLess(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(
request,
response,
`api/email_subscriptions/${request.params.token}/less`,
request.body,
)
}
@httpPost('/email_subscriptions/:token/more')
async subscriptionsMore(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(
request,
response,
`api/email_subscriptions/${request.params.token}/more`,
request.body,
)
}
@httpPost('/email_subscriptions/:token/mute/:campaignId')
async subscriptionsMute(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(
request,
response,
`api/email_subscriptions/${request.params.token}/mute/${request.params.campaignId}`,
request.body,
)
}
@httpPost('/email_subscriptions/:token/unsubscribe')
async subscriptionsUnsubscribe(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(
request,
response,
`api/email_subscriptions/${request.params.token}/unsubscribe`,
request.body,
)
}
@httpPost('/payments/stripe-setup-intent', TYPES.SubscriptionTokenAuthMiddleware)
async createStripeSetupIntent(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(request, response, 'api/pro_users/stripe-setup-intent', request.body)
}
@httpGet('/pro_users/cp-prepayment-info', TYPES.SubscriptionTokenAuthMiddleware)
async coinpaymentsPrepaymentInfo(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(request, response, 'api/pro_users/cp-prepayment-info', request.body)
}
@all('/pro_users(/*)?')
async proUsers(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(request, response, request.path.replace('v1', 'api'), request.body)
}
@all('/refunds')
async refunds(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(request, response, 'api/refunds', request.body)
}
}

View file

@ -0,0 +1,35 @@
import { Request, Response } from 'express'
import { inject } from 'inversify'
import { BaseHttpController, controller, httpDelete, httpGet } from 'inversify-express-utils'
import TYPES from '../../Bootstrap/Types'
import { HttpServiceInterface } from '../../Service/Http/HttpServiceInterface'
@controller('/v1/items/:item_id/revisions', TYPES.StatisticsMiddleware, TYPES.AuthMiddleware)
export class RevisionsController extends BaseHttpController {
constructor(@inject(TYPES.HTTPService) private httpService: HttpServiceInterface) {
super()
}
@httpGet('/')
async getRevisions(request: Request, response: Response): Promise<void> {
await this.httpService.callSyncingServer(request, response, `items/${request.params.item_id}/revisions`)
}
@httpGet('/:id')
async getRevision(request: Request, response: Response): Promise<void> {
await this.httpService.callSyncingServer(
request,
response,
`items/${request.params.item_id}/revisions/${request.params.id}`,
)
}
@httpDelete('/:id')
async deleteRevision(request: Request, response: Response): Promise<void> {
await this.httpService.callSyncingServer(
request,
response,
`items/${request.params.item_id}/revisions/${request.params.id}`,
)
}
}

View file

@ -0,0 +1,34 @@
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 { HttpServiceInterface } from '../../Service/Http/HttpServiceInterface'
@controller('/v1/sessions', TYPES.StatisticsMiddleware)
export class SessionsController extends BaseHttpController {
constructor(@inject(TYPES.HTTPService) private httpService: HttpServiceInterface) {
super()
}
@httpGet('/', TYPES.AuthMiddleware)
async getSessions(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(request, response, 'sessions')
}
@httpDelete('/:uuid', TYPES.AuthMiddleware)
async deleteSession(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(request, response, 'session', {
uuid: request.params.uuid,
})
}
@httpDelete('/', TYPES.AuthMiddleware)
async deleteSessions(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(request, response, 'session/all')
}
@httpPost('/refresh')
async refreshSession(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(request, response, 'session/refresh', request.body)
}
}

View file

@ -0,0 +1,42 @@
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 { HttpServiceInterface } from '../../Service/Http/HttpServiceInterface'
@controller('/v1/subscription-invites', TYPES.StatisticsMiddleware)
export class SubscriptionInvitesController extends BaseHttpController {
constructor(@inject(TYPES.HTTPService) private httpService: HttpServiceInterface) {
super()
}
@httpPost('/', TYPES.AuthMiddleware)
async inviteToSubscriptionSharing(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(request, response, 'subscription-invites', request.body)
}
@httpGet('/', TYPES.AuthMiddleware)
async listInvites(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(request, response, 'subscription-invites', request.body)
}
@httpDelete('/:inviteUuid', TYPES.AuthMiddleware)
async cancelSubscriptionSharing(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(request, response, `subscription-invites/${request.params.inviteUuid}`)
}
@httpGet('/:inviteUuid/accept')
async acceptInvite(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(request, response, `subscription-invites/${request.params.inviteUuid}/accept`)
}
@httpGet('/:inviteUuid/decline')
async declineInvite(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(
request,
response,
`subscription-invites/${request.params.inviteUuid}/decline`,
)
}
}

View file

@ -0,0 +1,18 @@
import { Request, Response } from 'express'
import { inject } from 'inversify'
import { BaseHttpController, controller, httpPost } from 'inversify-express-utils'
import TYPES from '../../Bootstrap/Types'
import { HttpServiceInterface } from '../../Service/Http/HttpServiceInterface'
@controller('/v1/subscription-tokens', TYPES.StatisticsMiddleware)
export class TokensController extends BaseHttpController {
constructor(@inject(TYPES.HTTPService) private httpService: HttpServiceInterface) {
super()
}
@httpPost('/', TYPES.AuthMiddleware)
async createToken(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(request, response, 'subscription-tokens', request.body)
}
}

View file

@ -0,0 +1,150 @@
import { Request, Response } from 'express'
import { inject } from 'inversify'
import {
all,
BaseHttpController,
controller,
httpDelete,
httpGet,
httpPatch,
httpPost,
httpPut,
results,
} from 'inversify-express-utils'
import { Logger } from 'winston'
import TYPES from '../../Bootstrap/Types'
import { HttpServiceInterface } from '../../Service/Http/HttpServiceInterface'
import { TokenAuthenticationMethod } from '../TokenAuthenticationMethod'
@controller('/v1/users', TYPES.StatisticsMiddleware)
export class UsersController extends BaseHttpController {
constructor(
@inject(TYPES.HTTPService) private httpService: HttpServiceInterface,
@inject(TYPES.Logger) private logger: Logger,
) {
super()
}
@httpPost('/claim-account')
async claimAccount(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(request, response, 'api/pro_users/claim-account', request.body)
}
@httpPost('/send-activation-code', TYPES.SubscriptionTokenAuthMiddleware)
async sendActivationCode(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(request, response, 'api/pro_users/send-activation-code', request.body)
}
@httpPatch('/:userId', TYPES.AuthMiddleware)
async updateUser(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(request, response, `users/${request.params.userId}`, request.body)
}
@httpPut('/:userUuid/password', TYPES.AuthMiddleware)
async changePassword(request: Request, response: Response): Promise<void> {
this.logger.debug(
'[DEPRECATED] use endpoint /v1/users/:userUuid/attributes/credentials instead of /v1/users/:userUuid/password',
)
await this.httpService.callAuthServer(
request,
response,
`users/${request.params.userUuid}/attributes/credentials`,
request.body,
)
}
@httpPut('/:userUuid/attributes/credentials', TYPES.AuthMiddleware)
async changeCredentials(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(
request,
response,
`users/${request.params.userUuid}/attributes/credentials`,
request.body,
)
}
@httpGet('/:userId/params', TYPES.AuthMiddleware)
async getKeyParams(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(request, response, 'auth/params')
}
@all('/:userId/mfa', TYPES.AuthMiddleware)
async blockMFA(): Promise<results.StatusCodeResult> {
return this.statusCode(401)
}
@httpPost('/:userUuid/integrations/listed', TYPES.AuthMiddleware)
async createListedAccount(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(request, response, 'listed', request.body)
}
@httpPost('/')
async register(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(request, response, 'auth', request.body)
}
@httpGet('/:userUuid/settings', TYPES.AuthMiddleware)
async listSettings(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(request, response, `users/${request.params.userUuid}/settings`)
}
@httpPut('/:userUuid/settings', TYPES.AuthMiddleware)
async putSetting(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(request, response, `users/${request.params.userUuid}/settings`, request.body)
}
@httpGet('/:userUuid/settings/:settingName', TYPES.AuthMiddleware)
async getSetting(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(
request,
response,
`users/${request.params.userUuid}/settings/${request.params.settingName}`,
)
}
@httpDelete('/:userUuid/settings/:settingName', TYPES.AuthMiddleware)
async deleteSetting(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(
request,
response,
`users/${request.params.userUuid}/settings/${request.params.settingName}`,
request.body,
)
}
@httpGet('/:userUuid/subscription-settings/:subscriptionSettingName', TYPES.AuthMiddleware)
async getSubscriptionSetting(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(
request,
response,
`users/${request.params.userUuid}/subscription-settings/${request.params.subscriptionSettingName}`,
)
}
@httpGet('/:userUuid/features', TYPES.AuthMiddleware)
async getFeatures(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(request, response, `users/${request.params.userUuid}/features`)
}
@httpGet('/:userUuid/subscription', TYPES.AuthMiddleware)
async getSubscription(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(request, response, `users/${request.params.userUuid}/subscription`)
}
@httpGet('/subscription', TYPES.SubscriptionTokenAuthMiddleware)
async getSubscriptionBySubscriptionToken(request: Request, response: Response): Promise<void> {
if (response.locals.tokenAuthenticationMethod === TokenAuthenticationMethod.OfflineSubscriptionToken) {
await this.httpService.callAuthServer(request, response, 'offline/users/subscription')
return
}
await this.httpService.callAuthServer(request, response, `users/${response.locals.userUuid}/subscription`)
}
@httpDelete('/:userUuid', TYPES.AuthMiddleware)
async deleteUser(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(request, response, 'api/account', request.body)
}
}

View file

@ -0,0 +1,34 @@
import { Request, Response } from 'express'
import { inject } from 'inversify'
import { BaseHttpController, controller, httpDelete, httpPost } from 'inversify-express-utils'
import TYPES from '../../Bootstrap/Types'
import { HttpServiceInterface } from '../../Service/Http/HttpServiceInterface'
@controller('/v1/sockets')
export class WebSocketsController extends BaseHttpController {
constructor(@inject(TYPES.HTTPService) private httpService: HttpServiceInterface) {
super()
}
@httpPost('/', TYPES.AuthMiddleware)
async createWebSocketConnection(request: Request, response: Response): Promise<void> {
if (!request.headers.connectionid) {
response.status(400).send('Missing connection id in the request')
return
}
await this.httpService.callAuthServer(request, response, `sockets/${request.headers.connectionid}`, request.body)
}
@httpDelete('/')
async deleteWebSocketConnection(request: Request, response: Response): Promise<void> {
if (!request.headers.connectionid) {
response.status(400).send('Missing connection id in the request')
return
}
await this.httpService.callAuthServer(request, response, `sockets/${request.headers.connectionid}`, request.body)
}
}

View file

@ -0,0 +1,23 @@
import { Request, Response } from 'express'
import { inject } from 'inversify'
import { BaseHttpController, controller, httpPost } from 'inversify-express-utils'
import TYPES from '../../Bootstrap/Types'
import { HttpServiceInterface } from '../../Service/Http/HttpServiceInterface'
@controller('/v2', TYPES.StatisticsMiddleware)
export class ActionsControllerV2 extends BaseHttpController {
constructor(@inject(TYPES.HTTPService) private httpService: HttpServiceInterface) {
super()
}
@httpPost('/login')
async login(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(request, response, 'auth/pkce_sign_in', request.body)
}
@httpPost('/login-params')
async loginParams(request: Request, response: Response): Promise<void> {
await this.httpService.callAuthServer(request, response, 'auth/pkce_params', request.body)
}
}

View file

@ -0,0 +1,62 @@
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 { HttpServiceInterface } from '../../Service/Http/HttpServiceInterface'
@controller('/v2', TYPES.StatisticsMiddleware)
export class PaymentsControllerV2 extends BaseHttpController {
constructor(@inject(TYPES.HTTPService) private httpService: HttpServiceInterface) {
super()
}
@httpGet('/subscriptions')
async getSubscriptionsWithFeatures(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(request, response, 'api/subscriptions/features', request.body)
}
@httpGet('/subscriptions/tailored', TYPES.SubscriptionTokenAuthMiddleware)
async getTailoredSubscriptionsWithFeatures(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(request, response, 'api/subscriptions/features', request.body)
}
@httpGet('/subscriptions/deltas', TYPES.SubscriptionTokenAuthMiddleware)
async getSubscriptionDeltasForChangingPlan(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(request, response, 'api/subscriptions/deltas', request.body)
}
@httpPost('/subscriptions/deltas/apply', TYPES.SubscriptionTokenAuthMiddleware)
async applySubscriptionDelta(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(request, response, 'api/subscriptions/deltas/apply', request.body)
}
@httpGet('/subscriptions/:subscriptionId', TYPES.SubscriptionTokenAuthMiddleware)
async getSubscription(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(
request,
response,
`api/subscriptions/${request.params.subscriptionId}`,
request.body,
)
}
@httpDelete('/subscriptions/:subscriptionId', TYPES.SubscriptionTokenAuthMiddleware)
async cancelSubscription(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(
request,
response,
`api/subscriptions/${request.params.subscriptionId}`,
request.body,
)
}
@httpPatch('/subscriptions/:subscriptionId', TYPES.SubscriptionTokenAuthMiddleware)
async updateSubscription(request: Request, response: Response): Promise<void> {
await this.httpService.callPaymentsServer(
request,
response,
`api/subscriptions/${request.params.subscriptionId}`,
request.body,
)
}
}

View file

@ -0,0 +1,46 @@
import { inject, injectable } from 'inversify'
import * as IORedis from 'ioredis'
import TYPES from '../../Bootstrap/Types'
import { CrossServiceTokenCacheInterface } from '../../Service/Cache/CrossServiceTokenCacheInterface'
@injectable()
export class RedisCrossServiceTokenCache implements CrossServiceTokenCacheInterface {
private readonly PREFIX = 'cst'
private readonly USER_CST_PREFIX = 'user-cst'
constructor(@inject(TYPES.Redis) private redisClient: IORedis.Redis) {}
async set(dto: {
authorizationHeaderValue: string
encodedCrossServiceToken: string
expiresAtInSeconds: number
userUuid: string
}): Promise<void> {
const pipeline = this.redisClient.pipeline()
pipeline.sadd(`${this.USER_CST_PREFIX}:${dto.userUuid}`, dto.authorizationHeaderValue)
pipeline.expireat(`${this.USER_CST_PREFIX}:${dto.userUuid}`, dto.expiresAtInSeconds)
pipeline.set(`${this.PREFIX}:${dto.authorizationHeaderValue}`, dto.encodedCrossServiceToken)
pipeline.expireat(`${this.PREFIX}:${dto.authorizationHeaderValue}`, dto.expiresAtInSeconds)
await pipeline.exec()
}
async get(authorizationHeaderValue: string): Promise<string | null> {
return this.redisClient.get(`${this.PREFIX}:${authorizationHeaderValue}`)
}
async invalidate(userUuid: string): Promise<void> {
const userAuthorizationHeaderValues = await this.redisClient.smembers(`${this.USER_CST_PREFIX}:${userUuid}`)
const pipeline = this.redisClient.pipeline()
for (const authorizationHeaderValue of userAuthorizationHeaderValues) {
pipeline.del(`${this.PREFIX}:${authorizationHeaderValue}`)
}
pipeline.del(`${this.USER_CST_PREFIX}:${userUuid}`)
await pipeline.exec()
}
}

View file

@ -0,0 +1,10 @@
export interface CrossServiceTokenCacheInterface {
set(dto: {
authorizationHeaderValue: string
encodedCrossServiceToken: string
expiresAtInSeconds: number
userUuid: string
}): Promise<void>
get(authorizationHeaderValue: string): Promise<string | null>
invalidate(userUuid: string): Promise<void>
}

View file

@ -0,0 +1,231 @@
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import { AxiosInstance, AxiosError, AxiosResponse, Method } from 'axios'
import { Request, Response } from 'express'
import { inject, injectable } from 'inversify'
import { Logger } from 'winston'
import TYPES from '../../Bootstrap/Types'
import { CrossServiceTokenCacheInterface } from '../Cache/CrossServiceTokenCacheInterface'
import { HttpServiceInterface } from './HttpServiceInterface'
@injectable()
export class HttpService implements HttpServiceInterface {
constructor(
@inject(TYPES.HTTPClient) private httpClient: AxiosInstance,
@inject(TYPES.AUTH_SERVER_URL) private authServerUrl: string,
@inject(TYPES.SYNCING_SERVER_JS_URL) private syncingServerJsUrl: string,
@inject(TYPES.PAYMENTS_SERVER_URL) private paymentsServerUrl: string,
@inject(TYPES.FILES_SERVER_URL) private filesServerUrl: string,
@inject(TYPES.HTTP_CALL_TIMEOUT) private httpCallTimeout: number,
@inject(TYPES.CrossServiceTokenCache) private crossServiceTokenCache: CrossServiceTokenCacheInterface,
@inject(TYPES.Logger) private logger: Logger,
) {}
async callSyncingServer(
request: Request,
response: Response,
endpoint: string,
payload?: Record<string, unknown> | string,
): Promise<void> {
await this.callServer(this.syncingServerJsUrl, 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 callPaymentsServer(
request: Request,
response: Response,
endpoint: string,
payload?: Record<string, unknown> | string,
): Promise<void> {
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,
): Promise<AxiosResponse | undefined> {
try {
const headers: Record<string, string> = {}
for (const headerName of Object.keys(request.headers)) {
headers[headerName] = request.headers[headerName] as string
}
delete headers.host
delete headers['content-length']
if (response.locals.authToken) {
headers['X-Auth-Token'] = response.locals.authToken
}
if (response.locals.offlineAuthToken) {
headers['X-Auth-Offline-Token'] = response.locals.offlineAuthToken
}
this.logger.debug(`Calling [${request.method}] ${serverUrl}/${endpoint},
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}/${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)
}
return serviceResponse
} catch (error) {
const errorMessage = (error as AxiosError).isAxiosError
? JSON.stringify((error as AxiosError).response?.data)
: (error as Error).message
this.logger.error(`Could not pass the request to ${serverUrl}/${endpoint} on underlying service: ${errorMessage}`)
this.logger.debug('Response error: %O', (error as AxiosError).response ?? 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 ? +((error as AxiosError).code as string) : 500
response.status(errorCode).send(errorMessage)
}
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)
this.logger.debug(`Response from underlying server: ${JSON.stringify(serviceResponse?.data)},
headers: ${JSON.stringify(serviceResponse?.headers)}`)
if (!serviceResponse) {
return
}
this.applyResponseHeaders(serviceResponse, response)
response.status(serviceResponse.status).send({
meta: {
auth: {
userUuid: response.locals.userUuid,
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> {
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).redirect(serviceResponse.request.res.responseUrl)
} else {
response.status(serviceResponse.status).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 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)
}
})
}
}

View file

@ -0,0 +1,34 @@
import { Request, Response } from 'express'
export interface HttpServiceInterface {
callAuthServer(
request: Request,
response: Response,
endpoint: string,
payload?: Record<string, unknown> | string,
): Promise<void>
callAuthServerWithLegacyFormat(
request: Request,
response: Response,
endpoint: string,
payload?: Record<string, unknown> | string,
): Promise<void>
callSyncingServer(
request: Request,
response: Response,
endpoint: string,
payload?: Record<string, unknown> | string,
): Promise<void>
callLegacySyncingServer(
request: Request,
response: Response,
endpoint: string,
payload?: Record<string, unknown> | string,
): Promise<void>
callPaymentsServer(
request: Request,
response: Response,
endpoint: string,
payload?: Record<string, unknown> | string,
): Promise<void>
}

View file

View file

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

View file

@ -0,0 +1,17 @@
#!/bin/sh
set -e
host="$1"
shift
port="$1"
shift
cmd="$@"
while ! nc -vz $host $port; do
>&2 echo "$host:$port is unavailable yet - waiting for it to start"
sleep 10
done
>&2 echo "$host:$port is up - executing command"
exec $cmd

View file

@ -20,6 +20,6 @@ COPY --from=builder /workspace ./
# docker-build plugin runs `yarn pack` in all workspace dependencies and copies them to `packs` folder.
COPY packs ./
ENTRYPOINT [ "/workspace/packages/auth/docker/entrypoint.sh" ]
ENTRYPOINT [ "/workspace/packages/files/docker/entrypoint.sh" ]
CMD [ "start-web" ]

View file

@ -20,6 +20,6 @@ COPY --from=builder /workspace ./
# docker-build plugin runs `yarn pack` in all workspace dependencies and copies them to `packs` folder.
COPY packs ./
ENTRYPOINT [ "/workspace/packages/auth/docker/entrypoint.sh" ]
ENTRYPOINT [ "/workspace/packages/syncing-server/docker/entrypoint.sh" ]
CMD [ "start-web" ]

View file

@ -33,6 +33,9 @@
},
{
"path": "./packages/files"
},
{
"path": "./packages/api-gateway"
}
]
}

107
yarn.lock
View file

@ -1836,13 +1836,55 @@ __metadata:
languageName: node
linkType: hard
"@standardnotes/analytics@npm:^1.6.0":
"@standardnotes/analytics@npm:^1.4.0, @standardnotes/analytics@npm:^1.6.0":
version: 1.6.0
resolution: "@standardnotes/analytics@npm:1.6.0"
checksum: 6a5e86152673ce9ddce43c52b5f699a1b3ba5141e58a944bda8eaa88fc2f4169df27239f82633226752bf3f10f9804f426721b9c919d10fbfbb51f952430eb1f
languageName: node
linkType: hard
"@standardnotes/api-gateway@workspace:packages/api-gateway":
version: 0.0.0-use.local
resolution: "@standardnotes/api-gateway@workspace:packages/api-gateway"
dependencies:
"@newrelic/native-metrics": 7.0.2
"@newrelic/winston-enricher": ^2.1.0
"@sentry/node": ^6.16.1
"@standardnotes/analytics": ^1.4.0
"@standardnotes/auth": 3.19.2
"@standardnotes/domain-events": 2.29.0
"@standardnotes/domain-events-infra": 1.4.127
"@standardnotes/time": ^1.7.0
"@types/cors": ^2.8.9
"@types/express": ^4.17.11
"@types/ioredis": ^4.28.10
"@types/jest": ^28.1.3
"@types/jsonwebtoken": ^8.5.0
"@types/newrelic": ^7.0.1
"@types/prettyjson": ^0.0.29
"@typescript-eslint/eslint-plugin": ^5.29.0
aws-sdk: ^2.1160.0
axios: 0.24.0
cors: 2.8.5
dotenv: 8.2.0
eslint: ^8.14.0
eslint-plugin-prettier: ^4.0.0
express: 4.17.1
helmet: 4.4.1
inversify: ^6.0.1
inversify-express-utils: ^6.4.3
ioredis: ^5.0.6
jest: ^28.1.1
jsonwebtoken: 8.5.1
newrelic: 8.6.0
nodemon: ^2.0.16
prettyjson: 1.2.1
reflect-metadata: 0.1.13
ts-jest: ^28.0.1
winston: 3.3.3
languageName: unknown
linkType: soft
"@standardnotes/api@npm:^1.1.13":
version: 1.1.13
resolution: "@standardnotes/api@npm:1.1.13"
@ -1916,6 +1958,16 @@ __metadata:
languageName: unknown
linkType: soft
"@standardnotes/auth@npm:3.19.2":
version: 3.19.2
resolution: "@standardnotes/auth@npm:3.19.2"
dependencies:
"@standardnotes/common": ^1.22.0
jsonwebtoken: ^8.5.1
checksum: 2e4b37b3034ca561e42525313c5ddf66249d27da4c423738241fb3f59f8ea990eafb60b113e648fa6904c171e3c86e93abffb7e72a52bb17b0eb6c9025427678
languageName: node
linkType: hard
"@standardnotes/auth@npm:^3.18.9, @standardnotes/auth@npm:^3.19.2, @standardnotes/auth@npm:^3.19.3":
version: 3.19.3
resolution: "@standardnotes/auth@npm:3.19.3"
@ -1943,6 +1995,21 @@ __metadata:
languageName: node
linkType: hard
"@standardnotes/domain-events-infra@npm:1.4.127":
version: 1.4.127
resolution: "@standardnotes/domain-events-infra@npm:1.4.127"
dependencies:
"@standardnotes/domain-events": ^2.29.0
aws-sdk: ^2.1082.0
ioredis: ^4.28.5
newrelic: ^8.8.0
reflect-metadata: ^0.1.13
sqs-consumer: ^5.6.0
winston: ^3.6.0
checksum: 54e37c296ff3b44adc8d425f89fd56eb42d435dded6b04a952ff77ebb867022585bb472635f488863c9f9b6493a04592593f7a0f2bfa1d86bb9354ba341d350f
languageName: node
linkType: hard
"@standardnotes/domain-events-infra@npm:^1.4.135, @standardnotes/domain-events-infra@npm:^1.4.93, @standardnotes/domain-events-infra@npm:^1.5.0, @standardnotes/domain-events-infra@npm:^1.5.2":
version: 1.5.2
resolution: "@standardnotes/domain-events-infra@npm:1.5.2"
@ -1958,7 +2025,17 @@ __metadata:
languageName: node
linkType: hard
"@standardnotes/domain-events@npm:^2.27.6, @standardnotes/domain-events@npm:^2.31.1, @standardnotes/domain-events@npm:^2.32.0, @standardnotes/domain-events@npm:^2.32.2":
"@standardnotes/domain-events@npm:2.29.0":
version: 2.29.0
resolution: "@standardnotes/domain-events@npm:2.29.0"
dependencies:
"@standardnotes/auth": ^3.19.2
"@standardnotes/features": ^1.44.6
checksum: 1b68999e2a7a6a26a9ecd27638cbb878bd4abb987a1a3b254136af0bec5619d4f5f99ede1b27cf93561fd6d6453f645310b5decfe468c8dd644363caed48aeb9
languageName: node
linkType: hard
"@standardnotes/domain-events@npm:^2.27.6, @standardnotes/domain-events@npm:^2.29.0, @standardnotes/domain-events@npm:^2.31.1, @standardnotes/domain-events@npm:^2.32.0, @standardnotes/domain-events@npm:^2.32.2":
version: 2.32.2
resolution: "@standardnotes/domain-events@npm:2.32.2"
dependencies:
@ -1980,7 +2057,7 @@ __metadata:
languageName: node
linkType: hard
"@standardnotes/features@npm:^1.36.3, @standardnotes/features@npm:^1.45.2, @standardnotes/features@npm:^1.45.5":
"@standardnotes/features@npm:^1.36.3, @standardnotes/features@npm:^1.44.6, @standardnotes/features@npm:^1.45.2, @standardnotes/features@npm:^1.45.5":
version: 1.45.5
resolution: "@standardnotes/features@npm:1.45.5"
dependencies:
@ -3216,6 +3293,23 @@ __metadata:
languageName: node
linkType: hard
"aws-sdk@npm:^2.1160.0":
version: 2.1160.0
resolution: "aws-sdk@npm:2.1160.0"
dependencies:
buffer: 4.9.2
events: 1.1.1
ieee754: 1.1.13
jmespath: 0.16.0
querystring: 0.2.0
sax: 1.2.1
url: 0.10.3
uuid: 8.0.0
xml2js: 0.4.19
checksum: b95647d4de6d07fc7b62ef409d1dd4a915f3711163a4da4057a3451d62b56818f4ea3d7970c2735af376c44a97d603190f2d85c2a11911eb6f97334ad0ace4a7
languageName: node
linkType: hard
"axios@npm:0.24.0":
version: 0.24.0
resolution: "axios@npm:0.24.0"
@ -5696,6 +5790,13 @@ __metadata:
languageName: node
linkType: hard
"helmet@npm:4.4.1":
version: 4.4.1
resolution: "helmet@npm:4.4.1"
checksum: cfe385e185e1ef6e4cd2ade4c54e160b05dd0454f270a663c528a8666402cbcad14e0ff0df09567fa62b0b4ac3371bbd1c8a253f6e7af37656a22339fe98c869
languageName: node
linkType: hard
"helmet@npm:^4.3.1":
version: 4.6.0
resolution: "helmet@npm:4.6.0"