mirror of
https://github.com/standardnotes/server
synced 2026-06-25 04:01:17 -04:00
Compare commits
157 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 55587f6207 | |||
| 0d6b45c795 | |||
| 95f64d9952 | |||
| 54da5def4b | |||
| d2fc1e057d | |||
| 0a90d98c71 | |||
| cc269e3b35 | |||
| b19093179b | |||
| e2cc0bc003 | |||
| 644c52ae36 | |||
| 2554273a3f | |||
| a8ee149d7a | |||
| dcf92d58f9 | |||
| 053092031c | |||
| c12e3eb3ec | |||
| 07def20f6b | |||
| 6c2cca66bd | |||
| 6efd336f34 | |||
| 81eb4be200 | |||
| 76cee6dbad | |||
| dcc35a5738 | |||
| 5628de6445 | |||
| 53bea47727 | |||
| d6cf8d400a | |||
| b58cc335f2 | |||
| 03d1bc611c | |||
| a48b09cefe | |||
| d3f36c05df | |||
| 488ade25ab | |||
| 413a276d20 | |||
| 65675a21d6 | |||
| d35de38289 | |||
| 83e1baa978 | |||
| 875edce5b1 | |||
| 1baa504728 | |||
| 965ae79414 | |||
| 7a8448c116 | |||
| d935157ee8 | |||
| 9313e6b568 | |||
| 8033177f48 | |||
| 11011fa15d | |||
| c2e9f3e72b | |||
| f0fb7fd1cd | |||
| 15e342fd51 | |||
| dfa7e06f87 | |||
| a9aef5521b | |||
| a628bdc44e | |||
| db6f966045 | |||
| 9b602ed405 | |||
| db15457ce4 | |||
| 719d8558a3 | |||
| c207c3fc84 | |||
| 4bde4758c3 | |||
| 5eb957c82a | |||
| 0b38617acf | |||
| 377d32c449 | |||
| cdfb0c2603 | |||
| d85152429c | |||
| 422e596fc7 | |||
| 89334c9022 | |||
| f5a0e88ab9 | |||
| a59ba08339 | |||
| 2641056c51 | |||
| 5d812befc4 | |||
| 1c592d6f96 | |||
| 531f13fe1f | |||
| 4757cc8dae | |||
| ecdfe9ecc0 | |||
| d19cb08e9c | |||
| f45320e5ed | |||
| 93ded34de9 | |||
| dd13e2eaf7 | |||
| 1405c6f260 | |||
| 0dab31f993 | |||
| 8070c70152 | |||
| c3ebb321cf | |||
| e54deb594a | |||
| 432d071ec8 | |||
| b9c06f1f5d | |||
| 52cc6462a6 | |||
| 35c2afef67 | |||
| 339c86fca0 | |||
| 0afd3de977 | |||
| e699569d46 | |||
| ced852d9db | |||
| a63612613e | |||
| c9ec7b492a | |||
| bf8ffc07ee | |||
| 73e1ea7f93 | |||
| 5979b99398 | |||
| 50ddb918cc | |||
| 6b19eb8876 | |||
| 47be0841fc | |||
| 99c7bb70fc | |||
| f139bb0036 | |||
| 23f592ca24 | |||
| fe4821d4f7 | |||
| c338d4fec5 | |||
| d7e6758089 | |||
| 0ad62636b9 | |||
| f872c7dfe9 | |||
| c5fdd59eb1 | |||
| 7132dc3ac0 | |||
| 956d5be959 | |||
| 936591d40b | |||
| 686e4f8ddf | |||
| b61825235e | |||
| 8157f324a0 | |||
| 132b617aaa | |||
| 25b1f3e9ea | |||
| f94c8fc26e | |||
| d149f46cf6 | |||
| 6a24ba5d56 | |||
| db8333a858 | |||
| 3af254d7c7 | |||
| 8151bb108a | |||
| 3b18769c2d | |||
| 2883cac6d4 | |||
| d7ae2f0625 | |||
| 318f6d0986 | |||
| 2ca430f40c | |||
| fd65060a8e | |||
| cb81f819ba | |||
| 61c7040e4b | |||
| fa10827443 | |||
| bcee779e74 | |||
| 34315c91d7 | |||
| 8d3bf6c4a5 | |||
| 0c176b70f8 | |||
| 87a5854357 | |||
| 9c2d51d718 | |||
| e618f046ea | |||
| a36cb925ff | |||
| 9e2aea2793 | |||
| ef1e2bb5ed | |||
| 6a457281ea | |||
| 41c512798d | |||
| ffa0f51305 | |||
| e0cec9e24a | |||
| f6b359a772 | |||
| 648eb89c7c | |||
| ba22e085b8 | |||
| 35373db1d3 | |||
| 932cfa7200 | |||
| 932ef933fc | |||
| 4f1293525c | |||
| dd6bec8a0c | |||
| 1abca64765 | |||
| dbe55d89ec | |||
| dcb3ad661c | |||
| 1e1f6cb4a3 | |||
| 83d96fd71d | |||
| 7dc4670028 | |||
| dc88e2413b | |||
| b7f7c3f164 | |||
| f7def38e20 | |||
| cf49e1ff74 |
@@ -9,134 +9,83 @@ updates:
|
|||||||
directory: "/"
|
directory: "/"
|
||||||
schedule:
|
schedule:
|
||||||
interval: "daily"
|
interval: "daily"
|
||||||
reviewers:
|
|
||||||
- "moughxyz"
|
|
||||||
- "karolsojko"
|
|
||||||
|
|
||||||
- package-ecosystem: "npm"
|
- package-ecosystem: "npm"
|
||||||
directory: "/packages/analytics"
|
directory: "/packages/analytics"
|
||||||
schedule:
|
schedule:
|
||||||
interval: "daily"
|
interval: "daily"
|
||||||
reviewers:
|
|
||||||
- "moughxyz"
|
|
||||||
- "karolsojko"
|
|
||||||
|
|
||||||
- package-ecosystem: "npm"
|
- package-ecosystem: "npm"
|
||||||
directory: "/packages/api-gateway"
|
directory: "/packages/api-gateway"
|
||||||
schedule:
|
schedule:
|
||||||
interval: "daily"
|
interval: "daily"
|
||||||
reviewers:
|
|
||||||
- "moughxyz"
|
|
||||||
- "karolsojko"
|
|
||||||
|
|
||||||
- package-ecosystem: "npm"
|
- package-ecosystem: "npm"
|
||||||
directory: "/packages/auth"
|
directory: "/packages/auth"
|
||||||
schedule:
|
schedule:
|
||||||
interval: "daily"
|
interval: "daily"
|
||||||
reviewers:
|
|
||||||
- "moughxyz"
|
|
||||||
- "karolsojko"
|
|
||||||
|
|
||||||
- package-ecosystem: "npm"
|
- package-ecosystem: "npm"
|
||||||
directory: "/packages/common"
|
directory: "/packages/common"
|
||||||
schedule:
|
schedule:
|
||||||
interval: "daily"
|
interval: "daily"
|
||||||
reviewers:
|
|
||||||
- "moughxyz"
|
|
||||||
- "karolsojko"
|
|
||||||
|
|
||||||
- package-ecosystem: "npm"
|
- package-ecosystem: "npm"
|
||||||
directory: "/packages/domain-events"
|
directory: "/packages/domain-events"
|
||||||
schedule:
|
schedule:
|
||||||
interval: "daily"
|
interval: "daily"
|
||||||
reviewers:
|
|
||||||
- "moughxyz"
|
|
||||||
- "karolsojko"
|
|
||||||
|
|
||||||
- package-ecosystem: "npm"
|
- package-ecosystem: "npm"
|
||||||
directory: "/packages/domain-events-infra"
|
directory: "/packages/domain-events-infra"
|
||||||
schedule:
|
schedule:
|
||||||
interval: "daily"
|
interval: "daily"
|
||||||
reviewers:
|
|
||||||
- "moughxyz"
|
|
||||||
- "karolsojko"
|
|
||||||
|
|
||||||
- package-ecosystem: "npm"
|
- package-ecosystem: "npm"
|
||||||
directory: "/packages/event-store"
|
directory: "/packages/event-store"
|
||||||
schedule:
|
schedule:
|
||||||
interval: "daily"
|
interval: "daily"
|
||||||
reviewers:
|
|
||||||
- "moughxyz"
|
|
||||||
- "karolsojko"
|
|
||||||
|
|
||||||
- package-ecosystem: "npm"
|
- package-ecosystem: "npm"
|
||||||
directory: "/packages/files"
|
directory: "/packages/files"
|
||||||
schedule:
|
schedule:
|
||||||
interval: "daily"
|
interval: "daily"
|
||||||
reviewers:
|
|
||||||
- "moughxyz"
|
|
||||||
- "karolsojko"
|
|
||||||
|
|
||||||
- package-ecosystem: "npm"
|
- package-ecosystem: "npm"
|
||||||
directory: "/packages/predicates"
|
directory: "/packages/predicates"
|
||||||
schedule:
|
schedule:
|
||||||
interval: "daily"
|
interval: "daily"
|
||||||
reviewers:
|
|
||||||
- "moughxyz"
|
|
||||||
- "karolsojko"
|
|
||||||
|
|
||||||
- package-ecosystem: "npm"
|
- package-ecosystem: "npm"
|
||||||
directory: "/packages/scheduler"
|
directory: "/packages/scheduler"
|
||||||
schedule:
|
schedule:
|
||||||
interval: "daily"
|
interval: "daily"
|
||||||
reviewers:
|
|
||||||
- "moughxyz"
|
|
||||||
- "karolsojko"
|
|
||||||
|
|
||||||
- package-ecosystem: "npm"
|
- package-ecosystem: "npm"
|
||||||
directory: "/packages/security"
|
directory: "/packages/security"
|
||||||
schedule:
|
schedule:
|
||||||
interval: "daily"
|
interval: "daily"
|
||||||
reviewers:
|
|
||||||
- "moughxyz"
|
|
||||||
- "karolsojko"
|
|
||||||
|
|
||||||
- package-ecosystem: "npm"
|
- package-ecosystem: "npm"
|
||||||
directory: "/packages/settings"
|
directory: "/packages/settings"
|
||||||
schedule:
|
schedule:
|
||||||
interval: "daily"
|
interval: "daily"
|
||||||
reviewers:
|
|
||||||
- "moughxyz"
|
|
||||||
- "karolsojko"
|
|
||||||
|
|
||||||
- package-ecosystem: "npm"
|
- package-ecosystem: "npm"
|
||||||
directory: "/packages/sncrypto-node"
|
directory: "/packages/sncrypto-node"
|
||||||
schedule:
|
schedule:
|
||||||
interval: "daily"
|
interval: "daily"
|
||||||
reviewers:
|
|
||||||
- "moughxyz"
|
|
||||||
- "karolsojko"
|
|
||||||
|
|
||||||
- package-ecosystem: "npm"
|
- package-ecosystem: "npm"
|
||||||
directory: "/packages/syncing-server"
|
directory: "/packages/syncing-server"
|
||||||
schedule:
|
schedule:
|
||||||
interval: "daily"
|
interval: "daily"
|
||||||
reviewers:
|
|
||||||
- "moughxyz"
|
|
||||||
- "karolsojko"
|
|
||||||
|
|
||||||
- package-ecosystem: "npm"
|
- package-ecosystem: "npm"
|
||||||
directory: "/packages/time"
|
directory: "/packages/time"
|
||||||
schedule:
|
schedule:
|
||||||
interval: "daily"
|
interval: "daily"
|
||||||
reviewers:
|
|
||||||
- "moughxyz"
|
|
||||||
- "karolsojko"
|
|
||||||
|
|
||||||
- package-ecosystem: "github-actions"
|
- package-ecosystem: "github-actions"
|
||||||
directory: "/"
|
directory: "/"
|
||||||
schedule:
|
schedule:
|
||||||
interval: "weekly"
|
interval: "weekly"
|
||||||
reviewers:
|
|
||||||
- "moughxyz"
|
|
||||||
- "karolsojko"
|
|
||||||
|
|||||||
@@ -60,6 +60,11 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
- name: Set up Node
|
||||||
|
uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
registry-url: 'https://registry.npmjs.org'
|
||||||
|
node-version-file: '.nvmrc'
|
||||||
- name: Build locally
|
- name: Build locally
|
||||||
run: yarn build
|
run: yarn build
|
||||||
- name: Configure AWS credentials
|
- name: Configure AWS credentials
|
||||||
@@ -90,6 +95,11 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
- name: Set up Node
|
||||||
|
uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
registry-url: 'https://registry.npmjs.org'
|
||||||
|
node-version-file: '.nvmrc'
|
||||||
- name: Build locally
|
- name: Build locally
|
||||||
run: yarn build
|
run: yarn build
|
||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
|
|||||||
@@ -63,6 +63,11 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
- name: Set up Node
|
||||||
|
uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
registry-url: 'https://registry.npmjs.org'
|
||||||
|
node-version-file: '.nvmrc'
|
||||||
- name: Build locally
|
- name: Build locally
|
||||||
run: yarn build
|
run: yarn build
|
||||||
- name: Configure AWS credentials
|
- name: Configure AWS credentials
|
||||||
@@ -93,6 +98,11 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
- name: Set up Node
|
||||||
|
uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
registry-url: 'https://registry.npmjs.org'
|
||||||
|
node-version-file: '.nvmrc'
|
||||||
- name: Build locally
|
- name: Build locally
|
||||||
run: yarn build
|
run: yarn build
|
||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
|
|||||||
@@ -32,6 +32,11 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
- name: Set up Node
|
||||||
|
uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
registry-url: 'https://registry.npmjs.org'
|
||||||
|
node-version-file: '.nvmrc'
|
||||||
- name: Build locally
|
- name: Build locally
|
||||||
run: yarn build
|
run: yarn build
|
||||||
- name: Configure AWS credentials
|
- name: Configure AWS credentials
|
||||||
@@ -62,6 +67,11 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
- name: Set up Node
|
||||||
|
uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
registry-url: 'https://registry.npmjs.org'
|
||||||
|
node-version-file: '.nvmrc'
|
||||||
- name: Build locally
|
- name: Build locally
|
||||||
run: yarn build
|
run: yarn build
|
||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
|
|||||||
@@ -63,6 +63,11 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
- name: Set up Node
|
||||||
|
uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
registry-url: 'https://registry.npmjs.org'
|
||||||
|
node-version-file: '.nvmrc'
|
||||||
- name: Build locally
|
- name: Build locally
|
||||||
run: yarn build
|
run: yarn build
|
||||||
- name: Configure AWS credentials
|
- name: Configure AWS credentials
|
||||||
@@ -93,6 +98,11 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
- name: Set up Node
|
||||||
|
uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
registry-url: 'https://registry.npmjs.org'
|
||||||
|
node-version-file: '.nvmrc'
|
||||||
- name: Build locally
|
- name: Build locally
|
||||||
run: yarn build
|
run: yarn build
|
||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
|
|||||||
@@ -11,6 +11,11 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
|
- name: Set up Node
|
||||||
|
uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
registry-url: 'https://registry.npmjs.org'
|
||||||
|
node-version-file: '.nvmrc'
|
||||||
- name: ESLint
|
- name: ESLint
|
||||||
run: yarn lint
|
run: yarn lint
|
||||||
- name: Build
|
- name: Build
|
||||||
|
|||||||
@@ -32,6 +32,11 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
- name: Set up Node
|
||||||
|
uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
registry-url: 'https://registry.npmjs.org'
|
||||||
|
node-version-file: '.nvmrc'
|
||||||
- name: Build locally
|
- name: Build locally
|
||||||
run: yarn build
|
run: yarn build
|
||||||
- name: Configure AWS credentials
|
- name: Configure AWS credentials
|
||||||
@@ -62,6 +67,11 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
- name: Set up Node
|
||||||
|
uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
registry-url: 'https://registry.npmjs.org'
|
||||||
|
node-version-file: '.nvmrc'
|
||||||
- name: Build locally
|
- name: Build locally
|
||||||
run: yarn build
|
run: yarn build
|
||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
|
|||||||
@@ -14,9 +14,11 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
ref: main
|
ref: main
|
||||||
token: ${{ secrets.CI_PAT_TOKEN }}
|
token: ${{ secrets.CI_PAT_TOKEN }}
|
||||||
- uses: actions/setup-node@v3
|
- name: Set up Node
|
||||||
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
registry-url: 'https://registry.npmjs.org'
|
registry-url: 'https://registry.npmjs.org'
|
||||||
|
node-version-file: '.nvmrc'
|
||||||
- name: Setup git config
|
- name: Setup git config
|
||||||
run: |
|
run: |
|
||||||
git config --global user.name "standardci"
|
git config --global user.name "standardci"
|
||||||
@@ -43,4 +45,4 @@ jobs:
|
|||||||
commit-message: "${{ 'chore(deps): upgrade snjs' }}"
|
commit-message: "${{ 'chore(deps): upgrade snjs' }}"
|
||||||
delete-branch: true
|
delete-branch: true
|
||||||
committer: standardci <ci@standardnotes.com>
|
committer: standardci <ci@standardnotes.com>
|
||||||
author: standardci <ci@standardnotes.com>
|
author: standardci <ci@standardnotes.com>
|
||||||
|
|||||||
@@ -63,6 +63,11 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
- name: Set up Node
|
||||||
|
uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
registry-url: 'https://registry.npmjs.org'
|
||||||
|
node-version-file: '.nvmrc'
|
||||||
- name: Build locally
|
- name: Build locally
|
||||||
run: yarn build
|
run: yarn build
|
||||||
- name: Configure AWS credentials
|
- name: Configure AWS credentials
|
||||||
@@ -93,6 +98,11 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
- name: Set up Node
|
||||||
|
uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
registry-url: 'https://registry.npmjs.org'
|
||||||
|
node-version-file: '.nvmrc'
|
||||||
- name: Build locally
|
- name: Build locally
|
||||||
run: yarn build
|
run: yarn build
|
||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
|||||||
["newrelic", "npm:9.0.0"],\
|
["newrelic", "npm:9.0.0"],\
|
||||||
["npm-check-updates", "npm:16.0.1"],\
|
["npm-check-updates", "npm:16.0.1"],\
|
||||||
["prettier", "npm:2.7.1"],\
|
["prettier", "npm:2.7.1"],\
|
||||||
["ts-node", "virtual:c0eab07e71af57f5501e97e7ca7a2a4f4965035bd2455ad124a8b09fa55780657c55fe3df41019fa6c2c44487c897668c842a0939e380b3c1f13b3756d128543#npm:10.8.2"],\
|
["ts-node", "virtual:8859b278716fedf3e7458b5628625f7e35678c418626878559a0b816445001b7e24c55546f4677ba4c20b521aa0cf52cc33ac07deff171e383ada6eeab69933f#npm:10.9.1"],\
|
||||||
["typescript", "patch:typescript@npm%3A4.7.4#~builtin<compat/typescript>::version=4.7.4&hash=7ad353"]\
|
["typescript", "patch:typescript@npm%3A4.7.4#~builtin<compat/typescript>::version=4.7.4&hash=7ad353"]\
|
||||||
],\
|
],\
|
||||||
"linkType": "SOFT"\
|
"linkType": "SOFT"\
|
||||||
@@ -2484,16 +2484,17 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
|||||||
}]\
|
}]\
|
||||||
]],\
|
]],\
|
||||||
["@standardnotes/api", [\
|
["@standardnotes/api", [\
|
||||||
["npm:1.1.19", {\
|
["npm:1.8.1", {\
|
||||||
"packageLocation": "./.yarn/cache/@standardnotes-api-npm-1.1.19-6a6d650ec9-cca168245a.zip/node_modules/@standardnotes/api/",\
|
"packageLocation": "./.yarn/cache/@standardnotes-api-npm-1.8.1-15c2e051d4-76c5d1a2d2.zip/node_modules/@standardnotes/api/",\
|
||||||
"packageDependencies": [\
|
"packageDependencies": [\
|
||||||
["@standardnotes/api", "npm:1.1.19"],\
|
["@standardnotes/api", "npm:1.8.1"],\
|
||||||
["@standardnotes/auth", "npm:3.19.4"],\
|
|
||||||
["@standardnotes/common", "workspace:packages/common"],\
|
["@standardnotes/common", "workspace:packages/common"],\
|
||||||
["@standardnotes/encryption", "npm:1.12.0"],\
|
["@standardnotes/encryption", "npm:1.15.3"],\
|
||||||
["@standardnotes/responses", "npm:1.6.39"],\
|
["@standardnotes/models", "npm:1.18.3"],\
|
||||||
["@standardnotes/services", "npm:1.15.0"],\
|
["@standardnotes/responses", "npm:1.10.2"],\
|
||||||
["@standardnotes/utils", "npm:1.6.12"]\
|
["@standardnotes/security", "workspace:packages/security"],\
|
||||||
|
["@standardnotes/utils", "npm:1.9.0"],\
|
||||||
|
["reflect-metadata", "npm:0.1.13"]\
|
||||||
],\
|
],\
|
||||||
"linkType": "HARD"\
|
"linkType": "HARD"\
|
||||||
}]\
|
}]\
|
||||||
@@ -2506,6 +2507,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
|||||||
["@newrelic/winston-enricher", "virtual:04783e12400851b8a3d76e71495851cc94959db6e62f04cb0a31190080629440b182d8c8eb4d7f2b04e281912f2783a5fd4d2c3c6ab68d38b7097246c93f4c19#npm:4.0.0"],\
|
["@newrelic/winston-enricher", "virtual:04783e12400851b8a3d76e71495851cc94959db6e62f04cb0a31190080629440b182d8c8eb4d7f2b04e281912f2783a5fd4d2c3c6ab68d38b7097246c93f4c19#npm:4.0.0"],\
|
||||||
["@sentry/node", "npm:7.5.0"],\
|
["@sentry/node", "npm:7.5.0"],\
|
||||||
["@standardnotes/analytics", "workspace:packages/analytics"],\
|
["@standardnotes/analytics", "workspace:packages/analytics"],\
|
||||||
|
["@standardnotes/common", "workspace:packages/common"],\
|
||||||
["@standardnotes/domain-events", "workspace:packages/domain-events"],\
|
["@standardnotes/domain-events", "workspace:packages/domain-events"],\
|
||||||
["@standardnotes/domain-events-infra", "workspace:packages/domain-events-infra"],\
|
["@standardnotes/domain-events-infra", "workspace:packages/domain-events-infra"],\
|
||||||
["@standardnotes/security", "workspace:packages/security"],\
|
["@standardnotes/security", "workspace:packages/security"],\
|
||||||
@@ -2561,11 +2563,11 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
|||||||
["@newrelic/winston-enricher", "virtual:04783e12400851b8a3d76e71495851cc94959db6e62f04cb0a31190080629440b182d8c8eb4d7f2b04e281912f2783a5fd4d2c3c6ab68d38b7097246c93f4c19#npm:4.0.0"],\
|
["@newrelic/winston-enricher", "virtual:04783e12400851b8a3d76e71495851cc94959db6e62f04cb0a31190080629440b182d8c8eb4d7f2b04e281912f2783a5fd4d2c3c6ab68d38b7097246c93f4c19#npm:4.0.0"],\
|
||||||
["@sentry/node", "npm:7.5.0"],\
|
["@sentry/node", "npm:7.5.0"],\
|
||||||
["@standardnotes/analytics", "workspace:packages/analytics"],\
|
["@standardnotes/analytics", "workspace:packages/analytics"],\
|
||||||
["@standardnotes/api", "npm:1.1.19"],\
|
["@standardnotes/api", "npm:1.8.1"],\
|
||||||
["@standardnotes/common", "workspace:packages/common"],\
|
["@standardnotes/common", "workspace:packages/common"],\
|
||||||
["@standardnotes/domain-events", "workspace:packages/domain-events"],\
|
["@standardnotes/domain-events", "workspace:packages/domain-events"],\
|
||||||
["@standardnotes/domain-events-infra", "workspace:packages/domain-events-infra"],\
|
["@standardnotes/domain-events-infra", "workspace:packages/domain-events-infra"],\
|
||||||
["@standardnotes/features", "npm:1.50.0"],\
|
["@standardnotes/features", "npm:1.52.1"],\
|
||||||
["@standardnotes/predicates", "workspace:packages/predicates"],\
|
["@standardnotes/predicates", "workspace:packages/predicates"],\
|
||||||
["@standardnotes/responses", "npm:1.6.39"],\
|
["@standardnotes/responses", "npm:1.6.39"],\
|
||||||
["@standardnotes/security", "workspace:packages/security"],\
|
["@standardnotes/security", "workspace:packages/security"],\
|
||||||
@@ -2607,7 +2609,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
|||||||
["ts-jest", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:28.0.5"],\
|
["ts-jest", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:28.0.5"],\
|
||||||
["typeorm", "virtual:31b5a94a105c89c9294c3d524a7f8929fe63ee5a2efadf21951ca4c0cfd2ecf02e8f4ef5a066bbda091f1e3a56e57c6749069a080618c96b22e51131a330fc4a#npm:0.3.7"],\
|
["typeorm", "virtual:31b5a94a105c89c9294c3d524a7f8929fe63ee5a2efadf21951ca4c0cfd2ecf02e8f4ef5a066bbda091f1e3a56e57c6749069a080618c96b22e51131a330fc4a#npm:0.3.7"],\
|
||||||
["ua-parser-js", "npm:1.0.2"],\
|
["ua-parser-js", "npm:1.0.2"],\
|
||||||
["uuid", "npm:8.3.2"],\
|
["uuid", "npm:9.0.0"],\
|
||||||
["winston", "npm:3.8.1"]\
|
["winston", "npm:3.8.1"]\
|
||||||
],\
|
],\
|
||||||
"linkType": "SOFT"\
|
"linkType": "SOFT"\
|
||||||
@@ -2649,7 +2651,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
|||||||
"packageDependencies": [\
|
"packageDependencies": [\
|
||||||
["@standardnotes/domain-events", "workspace:packages/domain-events"],\
|
["@standardnotes/domain-events", "workspace:packages/domain-events"],\
|
||||||
["@standardnotes/common", "workspace:packages/common"],\
|
["@standardnotes/common", "workspace:packages/common"],\
|
||||||
["@standardnotes/features", "npm:1.50.0"],\
|
["@standardnotes/features", "npm:1.52.1"],\
|
||||||
["@standardnotes/predicates", "workspace:packages/predicates"],\
|
["@standardnotes/predicates", "workspace:packages/predicates"],\
|
||||||
["@standardnotes/security", "workspace:packages/security"],\
|
["@standardnotes/security", "workspace:packages/security"],\
|
||||||
["@types/jest", "npm:28.1.4"],\
|
["@types/jest", "npm:28.1.4"],\
|
||||||
@@ -2686,16 +2688,15 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
|||||||
}]\
|
}]\
|
||||||
]],\
|
]],\
|
||||||
["@standardnotes/encryption", [\
|
["@standardnotes/encryption", [\
|
||||||
["npm:1.12.0", {\
|
["npm:1.15.3", {\
|
||||||
"packageLocation": "./.yarn/cache/@standardnotes-encryption-npm-1.12.0-eb2342c675-1a28653b1e.zip/node_modules/@standardnotes/encryption/",\
|
"packageLocation": "./.yarn/cache/@standardnotes-encryption-npm-1.15.3-3580c52c1f-1a7863299f.zip/node_modules/@standardnotes/encryption/",\
|
||||||
"packageDependencies": [\
|
"packageDependencies": [\
|
||||||
["@standardnotes/encryption", "npm:1.12.0"],\
|
["@standardnotes/encryption", "npm:1.15.3"],\
|
||||||
["@standardnotes/common", "workspace:packages/common"],\
|
["@standardnotes/common", "workspace:packages/common"],\
|
||||||
["@standardnotes/models", "npm:1.14.0"],\
|
["@standardnotes/models", "npm:1.18.3"],\
|
||||||
["@standardnotes/responses", "npm:1.6.39"],\
|
["@standardnotes/responses", "npm:1.10.2"],\
|
||||||
["@standardnotes/services", "npm:1.15.0"],\
|
["@standardnotes/sncrypto-common", "npm:1.11.1"],\
|
||||||
["@standardnotes/sncrypto-common", "npm:1.9.0"],\
|
["@standardnotes/utils", "npm:1.9.0"],\
|
||||||
["@standardnotes/utils", "npm:1.6.12"],\
|
|
||||||
["reflect-metadata", "npm:0.1.13"]\
|
["reflect-metadata", "npm:0.1.13"]\
|
||||||
],\
|
],\
|
||||||
"linkType": "HARD"\
|
"linkType": "HARD"\
|
||||||
@@ -2741,6 +2742,28 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
|||||||
["reflect-metadata", "npm:0.1.13"]\
|
["reflect-metadata", "npm:0.1.13"]\
|
||||||
],\
|
],\
|
||||||
"linkType": "HARD"\
|
"linkType": "HARD"\
|
||||||
|
}],\
|
||||||
|
["npm:1.52.0", {\
|
||||||
|
"packageLocation": "./.yarn/cache/@standardnotes-features-npm-1.52.0-8c1adf7881-3e6014272f.zip/node_modules/@standardnotes/features/",\
|
||||||
|
"packageDependencies": [\
|
||||||
|
["@standardnotes/features", "npm:1.52.0"],\
|
||||||
|
["@standardnotes/auth", "npm:3.19.4"],\
|
||||||
|
["@standardnotes/common", "workspace:packages/common"],\
|
||||||
|
["@standardnotes/security", "workspace:packages/security"],\
|
||||||
|
["reflect-metadata", "npm:0.1.13"]\
|
||||||
|
],\
|
||||||
|
"linkType": "HARD"\
|
||||||
|
}],\
|
||||||
|
["npm:1.52.1", {\
|
||||||
|
"packageLocation": "./.yarn/cache/@standardnotes-features-npm-1.52.1-1fee85cf4e-ff3684399e.zip/node_modules/@standardnotes/features/",\
|
||||||
|
"packageDependencies": [\
|
||||||
|
["@standardnotes/features", "npm:1.52.1"],\
|
||||||
|
["@standardnotes/auth", "npm:3.19.4"],\
|
||||||
|
["@standardnotes/common", "workspace:packages/common"],\
|
||||||
|
["@standardnotes/security", "workspace:packages/security"],\
|
||||||
|
["reflect-metadata", "npm:0.1.13"]\
|
||||||
|
],\
|
||||||
|
"linkType": "HARD"\
|
||||||
}]\
|
}]\
|
||||||
]],\
|
]],\
|
||||||
["@standardnotes/files-server", [\
|
["@standardnotes/files-server", [\
|
||||||
@@ -2788,22 +2811,22 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
|||||||
["prettyjson", "npm:1.2.5"],\
|
["prettyjson", "npm:1.2.5"],\
|
||||||
["reflect-metadata", "npm:0.1.13"],\
|
["reflect-metadata", "npm:0.1.13"],\
|
||||||
["ts-jest", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:28.0.5"],\
|
["ts-jest", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:28.0.5"],\
|
||||||
["ts-node", "virtual:b442cf0427cc365d1c137f7340f9b81f9b204561afe791a8564ae9590c3a7fc4b5f793aaf8817b946f75a3cb64d03ef8790eb847f8b576b41e700da7b00c240c#npm:10.8.2"],\
|
["ts-node", "virtual:b442cf0427cc365d1c137f7340f9b81f9b204561afe791a8564ae9590c3a7fc4b5f793aaf8817b946f75a3cb64d03ef8790eb847f8b576b41e700da7b00c240c#npm:10.9.1"],\
|
||||||
["uuid", "npm:8.3.2"],\
|
["uuid", "npm:9.0.0"],\
|
||||||
["winston", "npm:3.8.1"]\
|
["winston", "npm:3.8.1"]\
|
||||||
],\
|
],\
|
||||||
"linkType": "SOFT"\
|
"linkType": "SOFT"\
|
||||||
}]\
|
}]\
|
||||||
]],\
|
]],\
|
||||||
["@standardnotes/models", [\
|
["@standardnotes/models", [\
|
||||||
["npm:1.14.0", {\
|
["npm:1.18.3", {\
|
||||||
"packageLocation": "./.yarn/cache/@standardnotes-models-npm-1.14.0-6f064d99e7-bfb9d517b6.zip/node_modules/@standardnotes/models/",\
|
"packageLocation": "./.yarn/cache/@standardnotes-models-npm-1.18.3-6c65a62f30-21830c805f.zip/node_modules/@standardnotes/models/",\
|
||||||
"packageDependencies": [\
|
"packageDependencies": [\
|
||||||
["@standardnotes/models", "npm:1.14.0"],\
|
["@standardnotes/models", "npm:1.18.3"],\
|
||||||
["@standardnotes/common", "workspace:packages/common"],\
|
["@standardnotes/common", "workspace:packages/common"],\
|
||||||
["@standardnotes/features", "npm:1.50.0"],\
|
["@standardnotes/features", "npm:1.52.0"],\
|
||||||
["@standardnotes/responses", "npm:1.6.39"],\
|
["@standardnotes/responses", "npm:1.10.2"],\
|
||||||
["@standardnotes/utils", "npm:1.6.12"],\
|
["@standardnotes/utils", "npm:1.9.0"],\
|
||||||
["lodash", "npm:4.17.21"],\
|
["lodash", "npm:4.17.21"],\
|
||||||
["reflect-metadata", "npm:0.1.13"]\
|
["reflect-metadata", "npm:0.1.13"]\
|
||||||
],\
|
],\
|
||||||
@@ -2839,6 +2862,17 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
|||||||
}]\
|
}]\
|
||||||
]],\
|
]],\
|
||||||
["@standardnotes/responses", [\
|
["@standardnotes/responses", [\
|
||||||
|
["npm:1.10.2", {\
|
||||||
|
"packageLocation": "./.yarn/cache/@standardnotes-responses-npm-1.10.2-39d2d1f9b5-364724b5c7.zip/node_modules/@standardnotes/responses/",\
|
||||||
|
"packageDependencies": [\
|
||||||
|
["@standardnotes/responses", "npm:1.10.2"],\
|
||||||
|
["@standardnotes/common", "workspace:packages/common"],\
|
||||||
|
["@standardnotes/features", "npm:1.52.0"],\
|
||||||
|
["@standardnotes/security", "workspace:packages/security"],\
|
||||||
|
["reflect-metadata", "npm:0.1.13"]\
|
||||||
|
],\
|
||||||
|
"linkType": "HARD"\
|
||||||
|
}],\
|
||||||
["npm:1.6.39", {\
|
["npm:1.6.39", {\
|
||||||
"packageLocation": "./.yarn/cache/@standardnotes-responses-npm-1.6.39-395f4c2d65-0ea1d4d5b8.zip/node_modules/@standardnotes/responses/",\
|
"packageLocation": "./.yarn/cache/@standardnotes-responses-npm-1.6.39-395f4c2d65-0ea1d4d5b8.zip/node_modules/@standardnotes/responses/",\
|
||||||
"packageDependencies": [\
|
"packageDependencies": [\
|
||||||
@@ -2866,11 +2900,12 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
|||||||
["@types/jest", "npm:28.1.4"],\
|
["@types/jest", "npm:28.1.4"],\
|
||||||
["@types/newrelic", "npm:7.0.3"],\
|
["@types/newrelic", "npm:7.0.3"],\
|
||||||
["@types/node", "npm:18.0.3"],\
|
["@types/node", "npm:18.0.3"],\
|
||||||
["@typescript-eslint/eslint-plugin", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:5.30.5"],\
|
["@typescript-eslint/eslint-plugin", "virtual:04783e12400851b8a3d76e71495851cc94959db6e62f04cb0a31190080629440b182d8c8eb4d7f2b04e281912f2783a5fd4d2c3c6ab68d38b7097246c93f4c19#npm:5.30.5"],\
|
||||||
["aws-sdk", "npm:2.1168.0"],\
|
["aws-sdk", "npm:2.1168.0"],\
|
||||||
["dayjs", "npm:1.11.3"],\
|
["dayjs", "npm:1.11.3"],\
|
||||||
["dotenv", "npm:16.0.1"],\
|
["dotenv", "npm:16.0.1"],\
|
||||||
["eslint-plugin-prettier", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:4.2.1"],\
|
["eslint", "npm:8.19.0"],\
|
||||||
|
["eslint-plugin-prettier", "virtual:04783e12400851b8a3d76e71495851cc94959db6e62f04cb0a31190080629440b182d8c8eb4d7f2b04e281912f2783a5fd4d2c3c6ab68d38b7097246c93f4c19#npm:4.2.1"],\
|
||||||
["inversify", "npm:6.0.1"],\
|
["inversify", "npm:6.0.1"],\
|
||||||
["ioredis", "npm:5.2.0"],\
|
["ioredis", "npm:5.2.0"],\
|
||||||
["jest", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:28.1.2"],\
|
["jest", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:28.1.2"],\
|
||||||
@@ -2924,27 +2959,12 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
|||||||
["newrelic", "npm:9.0.0"],\
|
["newrelic", "npm:9.0.0"],\
|
||||||
["npm-check-updates", "npm:16.0.1"],\
|
["npm-check-updates", "npm:16.0.1"],\
|
||||||
["prettier", "npm:2.7.1"],\
|
["prettier", "npm:2.7.1"],\
|
||||||
["ts-node", "virtual:c0eab07e71af57f5501e97e7ca7a2a4f4965035bd2455ad124a8b09fa55780657c55fe3df41019fa6c2c44487c897668c842a0939e380b3c1f13b3756d128543#npm:10.8.2"],\
|
["ts-node", "virtual:8859b278716fedf3e7458b5628625f7e35678c418626878559a0b816445001b7e24c55546f4677ba4c20b521aa0cf52cc33ac07deff171e383ada6eeab69933f#npm:10.9.1"],\
|
||||||
["typescript", "patch:typescript@npm%3A4.7.4#~builtin<compat/typescript>::version=4.7.4&hash=7ad353"]\
|
["typescript", "patch:typescript@npm%3A4.7.4#~builtin<compat/typescript>::version=4.7.4&hash=7ad353"]\
|
||||||
],\
|
],\
|
||||||
"linkType": "SOFT"\
|
"linkType": "SOFT"\
|
||||||
}]\
|
}]\
|
||||||
]],\
|
]],\
|
||||||
["@standardnotes/services", [\
|
|
||||||
["npm:1.15.0", {\
|
|
||||||
"packageLocation": "./.yarn/cache/@standardnotes-services-npm-1.15.0-acab3bc6a3-1028a5b4c1.zip/node_modules/@standardnotes/services/",\
|
|
||||||
"packageDependencies": [\
|
|
||||||
["@standardnotes/services", "npm:1.15.0"],\
|
|
||||||
["@standardnotes/auth", "npm:3.19.4"],\
|
|
||||||
["@standardnotes/common", "workspace:packages/common"],\
|
|
||||||
["@standardnotes/models", "npm:1.14.0"],\
|
|
||||||
["@standardnotes/responses", "npm:1.6.39"],\
|
|
||||||
["@standardnotes/utils", "npm:1.6.12"],\
|
|
||||||
["reflect-metadata", "npm:0.1.13"]\
|
|
||||||
],\
|
|
||||||
"linkType": "HARD"\
|
|
||||||
}]\
|
|
||||||
]],\
|
|
||||||
["@standardnotes/settings", [\
|
["@standardnotes/settings", [\
|
||||||
["workspace:packages/settings", {\
|
["workspace:packages/settings", {\
|
||||||
"packageLocation": "./packages/settings/",\
|
"packageLocation": "./packages/settings/",\
|
||||||
@@ -2958,6 +2978,14 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
|||||||
}]\
|
}]\
|
||||||
]],\
|
]],\
|
||||||
["@standardnotes/sncrypto-common", [\
|
["@standardnotes/sncrypto-common", [\
|
||||||
|
["npm:1.11.1", {\
|
||||||
|
"packageLocation": "./.yarn/cache/@standardnotes-sncrypto-common-npm-1.11.1-58d12d6912-69d698abb7.zip/node_modules/@standardnotes/sncrypto-common/",\
|
||||||
|
"packageDependencies": [\
|
||||||
|
["@standardnotes/sncrypto-common", "npm:1.11.1"],\
|
||||||
|
["reflect-metadata", "npm:0.1.13"]\
|
||||||
|
],\
|
||||||
|
"linkType": "HARD"\
|
||||||
|
}],\
|
||||||
["npm:1.9.0", {\
|
["npm:1.9.0", {\
|
||||||
"packageLocation": "./.yarn/cache/@standardnotes-sncrypto-common-npm-1.9.0-48773f745a-42252d7198.zip/node_modules/@standardnotes/sncrypto-common/",\
|
"packageLocation": "./.yarn/cache/@standardnotes-sncrypto-common-npm-1.9.0-48773f745a-42252d7198.zip/node_modules/@standardnotes/sncrypto-common/",\
|
||||||
"packageDependencies": [\
|
"packageDependencies": [\
|
||||||
@@ -3035,7 +3063,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
|||||||
["ts-jest", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:28.0.5"],\
|
["ts-jest", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:28.0.5"],\
|
||||||
["typeorm", "virtual:31b5a94a105c89c9294c3d524a7f8929fe63ee5a2efadf21951ca4c0cfd2ecf02e8f4ef5a066bbda091f1e3a56e57c6749069a080618c96b22e51131a330fc4a#npm:0.3.7"],\
|
["typeorm", "virtual:31b5a94a105c89c9294c3d524a7f8929fe63ee5a2efadf21951ca4c0cfd2ecf02e8f4ef5a066bbda091f1e3a56e57c6749069a080618c96b22e51131a330fc4a#npm:0.3.7"],\
|
||||||
["ua-parser-js", "npm:1.0.2"],\
|
["ua-parser-js", "npm:1.0.2"],\
|
||||||
["uuid", "npm:8.3.2"],\
|
["uuid", "npm:9.0.0"],\
|
||||||
["winston", "npm:3.8.1"]\
|
["winston", "npm:3.8.1"]\
|
||||||
],\
|
],\
|
||||||
"linkType": "SOFT"\
|
"linkType": "SOFT"\
|
||||||
@@ -3069,6 +3097,17 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
|||||||
["lodash", "npm:4.17.21"]\
|
["lodash", "npm:4.17.21"]\
|
||||||
],\
|
],\
|
||||||
"linkType": "HARD"\
|
"linkType": "HARD"\
|
||||||
|
}],\
|
||||||
|
["npm:1.9.0", {\
|
||||||
|
"packageLocation": "./.yarn/cache/@standardnotes-utils-npm-1.9.0-da939553f6-4591aff48d.zip/node_modules/@standardnotes/utils/",\
|
||||||
|
"packageDependencies": [\
|
||||||
|
["@standardnotes/utils", "npm:1.9.0"],\
|
||||||
|
["@standardnotes/common", "workspace:packages/common"],\
|
||||||
|
["dompurify", "npm:2.4.0"],\
|
||||||
|
["lodash", "npm:4.17.21"],\
|
||||||
|
["reflect-metadata", "npm:0.1.13"]\
|
||||||
|
],\
|
||||||
|
"linkType": "HARD"\
|
||||||
}]\
|
}]\
|
||||||
]],\
|
]],\
|
||||||
["@szmarczak/http-timer", [\
|
["@szmarczak/http-timer", [\
|
||||||
@@ -5842,6 +5881,13 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
|||||||
["dompurify", "npm:2.3.8"]\
|
["dompurify", "npm:2.3.8"]\
|
||||||
],\
|
],\
|
||||||
"linkType": "HARD"\
|
"linkType": "HARD"\
|
||||||
|
}],\
|
||||||
|
["npm:2.4.0", {\
|
||||||
|
"packageLocation": "./.yarn/cache/dompurify-npm-2.4.0-0ffecf22ef-c93ea73cf8.zip/node_modules/dompurify/",\
|
||||||
|
"packageDependencies": [\
|
||||||
|
["dompurify", "npm:2.4.0"]\
|
||||||
|
],\
|
||||||
|
"linkType": "HARD"\
|
||||||
}]\
|
}]\
|
||||||
]],\
|
]],\
|
||||||
["dot-prop", [\
|
["dot-prop", [\
|
||||||
@@ -12187,10 +12233,53 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
|||||||
],\
|
],\
|
||||||
"linkType": "SOFT"\
|
"linkType": "SOFT"\
|
||||||
}],\
|
}],\
|
||||||
["virtual:b442cf0427cc365d1c137f7340f9b81f9b204561afe791a8564ae9590c3a7fc4b5f793aaf8817b946f75a3cb64d03ef8790eb847f8b576b41e700da7b00c240c#npm:10.8.2", {\
|
["npm:10.9.1", {\
|
||||||
"packageLocation": "./.yarn/__virtual__/ts-node-virtual-8a01a45377/0/cache/ts-node-npm-10.8.2-f3c0c9eaee-1eede939be.zip/node_modules/ts-node/",\
|
"packageLocation": "./.yarn/cache/ts-node-npm-10.9.1-6c268be7f4-090adff130.zip/node_modules/ts-node/",\
|
||||||
"packageDependencies": [\
|
"packageDependencies": [\
|
||||||
["ts-node", "virtual:b442cf0427cc365d1c137f7340f9b81f9b204561afe791a8564ae9590c3a7fc4b5f793aaf8817b946f75a3cb64d03ef8790eb847f8b576b41e700da7b00c240c#npm:10.8.2"],\
|
["ts-node", "npm:10.9.1"]\
|
||||||
|
],\
|
||||||
|
"linkType": "SOFT"\
|
||||||
|
}],\
|
||||||
|
["virtual:8859b278716fedf3e7458b5628625f7e35678c418626878559a0b816445001b7e24c55546f4677ba4c20b521aa0cf52cc33ac07deff171e383ada6eeab69933f#npm:10.9.1", {\
|
||||||
|
"packageLocation": "./.yarn/__virtual__/ts-node-virtual-ac01688ebc/0/cache/ts-node-npm-10.9.1-6c268be7f4-090adff130.zip/node_modules/ts-node/",\
|
||||||
|
"packageDependencies": [\
|
||||||
|
["ts-node", "virtual:8859b278716fedf3e7458b5628625f7e35678c418626878559a0b816445001b7e24c55546f4677ba4c20b521aa0cf52cc33ac07deff171e383ada6eeab69933f#npm:10.9.1"],\
|
||||||
|
["@cspotcode/source-map-support", "npm:0.8.1"],\
|
||||||
|
["@swc/core", null],\
|
||||||
|
["@swc/wasm", null],\
|
||||||
|
["@tsconfig/node10", "npm:1.0.9"],\
|
||||||
|
["@tsconfig/node12", "npm:1.0.11"],\
|
||||||
|
["@tsconfig/node14", "npm:1.0.3"],\
|
||||||
|
["@tsconfig/node16", "npm:1.0.3"],\
|
||||||
|
["@types/node", "npm:18.0.3"],\
|
||||||
|
["@types/swc__core", null],\
|
||||||
|
["@types/swc__wasm", null],\
|
||||||
|
["@types/typescript", null],\
|
||||||
|
["acorn", "npm:8.7.1"],\
|
||||||
|
["acorn-walk", "npm:8.2.0"],\
|
||||||
|
["arg", "npm:4.1.3"],\
|
||||||
|
["create-require", "npm:1.1.1"],\
|
||||||
|
["diff", "npm:4.0.2"],\
|
||||||
|
["make-error", "npm:1.3.6"],\
|
||||||
|
["typescript", "patch:typescript@npm%3A4.7.4#~builtin<compat/typescript>::version=4.7.4&hash=7ad353"],\
|
||||||
|
["v8-compile-cache-lib", "npm:3.0.1"],\
|
||||||
|
["yn", "npm:3.1.1"]\
|
||||||
|
],\
|
||||||
|
"packagePeers": [\
|
||||||
|
"@swc/core",\
|
||||||
|
"@swc/wasm",\
|
||||||
|
"@types/node",\
|
||||||
|
"@types/swc__core",\
|
||||||
|
"@types/swc__wasm",\
|
||||||
|
"@types/typescript",\
|
||||||
|
"typescript"\
|
||||||
|
],\
|
||||||
|
"linkType": "HARD"\
|
||||||
|
}],\
|
||||||
|
["virtual:b442cf0427cc365d1c137f7340f9b81f9b204561afe791a8564ae9590c3a7fc4b5f793aaf8817b946f75a3cb64d03ef8790eb847f8b576b41e700da7b00c240c#npm:10.9.1", {\
|
||||||
|
"packageLocation": "./.yarn/__virtual__/ts-node-virtual-c4e9951caa/0/cache/ts-node-npm-10.9.1-6c268be7f4-090adff130.zip/node_modules/ts-node/",\
|
||||||
|
"packageDependencies": [\
|
||||||
|
["ts-node", "virtual:b442cf0427cc365d1c137f7340f9b81f9b204561afe791a8564ae9590c3a7fc4b5f793aaf8817b946f75a3cb64d03ef8790eb847f8b576b41e700da7b00c240c#npm:10.9.1"],\
|
||||||
["@cspotcode/source-map-support", "npm:0.8.1"],\
|
["@cspotcode/source-map-support", "npm:0.8.1"],\
|
||||||
["@swc/core", null],\
|
["@swc/core", null],\
|
||||||
["@swc/wasm", null],\
|
["@swc/wasm", null],\
|
||||||
@@ -12735,6 +12824,13 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
|||||||
["uuid", "npm:8.3.2"]\
|
["uuid", "npm:8.3.2"]\
|
||||||
],\
|
],\
|
||||||
"linkType": "HARD"\
|
"linkType": "HARD"\
|
||||||
|
}],\
|
||||||
|
["npm:9.0.0", {\
|
||||||
|
"packageLocation": "./.yarn/cache/uuid-npm-9.0.0-46c41e3e43-8dd2c83c43.zip/node_modules/uuid/",\
|
||||||
|
"packageDependencies": [\
|
||||||
|
["uuid", "npm:9.0.0"]\
|
||||||
|
],\
|
||||||
|
"linkType": "HARD"\
|
||||||
}]\
|
}]\
|
||||||
]],\
|
]],\
|
||||||
["v8-compile-cache", [\
|
["v8-compile-cache", [\
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1
-1
@@ -60,7 +60,7 @@
|
|||||||
"ini": "^3.0.0",
|
"ini": "^3.0.0",
|
||||||
"npm-check-updates": "^16.0.1",
|
"npm-check-updates": "^16.0.1",
|
||||||
"prettier": "^2.7.1",
|
"prettier": "^2.7.1",
|
||||||
"ts-node": "^10.8.1",
|
"ts-node": "^10.9.1",
|
||||||
"typescript": "^4.7.4"
|
"typescript": "^4.7.4"
|
||||||
},
|
},
|
||||||
"packageManager": "yarn@3.2.1",
|
"packageManager": "yarn@3.2.1",
|
||||||
|
|||||||
@@ -3,6 +3,84 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [1.29.1](https://github.com/standardnotes/server/compare/@standardnotes/analytics@1.29.0...@standardnotes/analytics@1.29.1) (2022-09-16)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **auth:** change remaining subscription time stats to percentage ([5eb957c](https://github.com/standardnotes/server/commit/5eb957c82a8cc5fdcb6815e2cd30e49cd2b1e8ac))
|
||||||
|
|
||||||
|
# [1.29.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@1.28.0...@standardnotes/analytics@1.29.0) (2022-09-15)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **auth:** add remaining subscription time stats ([a59ba08](https://github.com/standardnotes/server/commit/a59ba083397c75960af0e8a102b617bf5baa287f))
|
||||||
|
|
||||||
|
# [1.28.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@1.27.0...@standardnotes/analytics@1.28.0) (2022-09-09)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **syncing-server:** add tracking files count in stats ([52cc646](https://github.com/standardnotes/server/commit/52cc6462a66dae3bd6c05f551d4ba661c8a9b8c8))
|
||||||
|
|
||||||
|
# [1.27.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@1.26.0...@standardnotes/analytics@1.27.0) (2022-09-09)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **api-gateway:** add tracking general activity for free and paid users breakdown ([0afd3de](https://github.com/standardnotes/server/commit/0afd3de9779e2abe10deede24626a3cbe6b15e6c))
|
||||||
|
|
||||||
|
# [1.26.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@1.25.0...@standardnotes/analytics@1.26.0) (2022-09-09)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **syncing-server:** add statistics for notes count for free and paid users ([c9ec7b4](https://github.com/standardnotes/server/commit/c9ec7b492aea1911e441ed8ad9a155f871be2ef7))
|
||||||
|
|
||||||
|
# [1.25.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@1.24.0...@standardnotes/analytics@1.25.0) (2022-09-07)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **analytics:** add discrete period key generation for last 7 days ([f872c7d](https://github.com/standardnotes/server/commit/f872c7dfe9f120f40dd0c28a9e0f5749eb251643))
|
||||||
|
|
||||||
|
# [1.24.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@1.23.0...@standardnotes/analytics@1.24.0) (2022-09-07)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **analytics:** add calculation retention for two activities ([7132dc3](https://github.com/standardnotes/server/commit/7132dc3ac0cf878d2c326243747343e8a6746e2f))
|
||||||
|
|
||||||
|
# [1.23.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@1.22.0...@standardnotes/analytics@1.23.0) (2022-09-07)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **auth:** add measuring registration to subscription time statistics ([b618252](https://github.com/standardnotes/server/commit/b61825235eebaf5eddb55cbda173176ca43c0099))
|
||||||
|
|
||||||
|
# [1.22.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@1.21.1...@standardnotes/analytics@1.22.0) (2022-09-06)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **auth:** add statistics for refunds and account deletions ([d7ae2f0](https://github.com/standardnotes/server/commit/d7ae2f06255b19eb5d3403a4989610390064754e))
|
||||||
|
|
||||||
|
## [1.21.1](https://github.com/standardnotes/server/compare/@standardnotes/analytics@1.21.0...@standardnotes/analytics@1.21.1) (2022-09-06)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **analytics:** increment by float instead of integer on measures ([cb81f81](https://github.com/standardnotes/server/commit/cb81f819ba30a45f27ec344480b5ef22e5a0a50d))
|
||||||
|
|
||||||
|
# [1.21.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@1.20.0...@standardnotes/analytics@1.21.0) (2022-09-05)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **auth:** add measuring subscription length ([fa10827](https://github.com/standardnotes/server/commit/fa108274430d8dff1016ddcba5bbcb2778eb781b))
|
||||||
|
|
||||||
|
# [1.20.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@1.19.0...@standardnotes/analytics@1.20.0) (2022-09-05)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **auth:** add keeping stats on payments ([0c176b7](https://github.com/standardnotes/server/commit/0c176b70f8281e1e490224b9c7ab85f272a3d4e9))
|
||||||
|
|
||||||
|
# [1.19.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@1.18.1...@standardnotes/analytics@1.19.0) (2022-09-05)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **analytics:** add statistics measurements tracking ([a36cb92](https://github.com/standardnotes/server/commit/a36cb925ff3bd8396a53f58c3e954549e904d694))
|
||||||
|
|
||||||
## [1.18.1](https://github.com/standardnotes/server/compare/@standardnotes/analytics@1.18.0...@standardnotes/analytics@1.18.1) (2022-08-15)
|
## [1.18.1](https://github.com/standardnotes/server/compare/@standardnotes/analytics@1.18.0...@standardnotes/analytics@1.18.1) (2022-08-15)
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/analytics",
|
"name": "@standardnotes/analytics",
|
||||||
"version": "1.18.1",
|
"version": "1.29.1",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=14.0.0 <17.0.0"
|
"node": ">=14.0.0 <17.0.0"
|
||||||
},
|
},
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
"prebuild": "yarn clean",
|
"prebuild": "yarn clean",
|
||||||
"build": "tsc -p tsconfig.json",
|
"build": "tsc -p tsconfig.json",
|
||||||
"lint": "eslint . --ext .ts",
|
"lint": "eslint . --ext .ts",
|
||||||
"test:unit": "jest spec --coverage"
|
"test": "jest spec --coverage"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/ioredis": "^4.28.10",
|
"@types/ioredis": "^4.28.10",
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
export enum AnalyticsActivity {
|
export enum AnalyticsActivity {
|
||||||
GeneralActivity = 'general-activity',
|
GeneralActivity = 'general-activity',
|
||||||
|
GeneralActivityFreeUsers = 'general-activity-free-users',
|
||||||
|
GeneralActivityPaidUsers = 'general-activity-paid-users',
|
||||||
EditingItems = 'editing-items',
|
EditingItems = 'editing-items',
|
||||||
|
CheckingIntegrity = 'checking-integrity',
|
||||||
Login = 'login',
|
Login = 'login',
|
||||||
Register = 'register',
|
Register = 'register',
|
||||||
DeleteAccount = 'DeleteAccount',
|
DeleteAccount = 'DeleteAccount',
|
||||||
|
|||||||
@@ -6,6 +6,12 @@ export interface AnalyticsStoreInterface {
|
|||||||
markActivity(activities: AnalyticsActivity[], analyticsId: number, periods: Period[]): Promise<void>
|
markActivity(activities: AnalyticsActivity[], analyticsId: number, periods: Period[]): Promise<void>
|
||||||
wasActivityDone(activity: AnalyticsActivity, analyticsId: number, period: Period): Promise<boolean>
|
wasActivityDone(activity: AnalyticsActivity, analyticsId: number, period: Period): Promise<boolean>
|
||||||
calculateActivityRetention(activity: AnalyticsActivity, firstPeriod: Period, secondPeriod: Period): Promise<number>
|
calculateActivityRetention(activity: AnalyticsActivity, firstPeriod: Period, secondPeriod: Period): Promise<number>
|
||||||
|
calculateActivitiesRetention(parameters: {
|
||||||
|
firstActivity: AnalyticsActivity
|
||||||
|
firstActivityPeriodKey: string
|
||||||
|
secondActivity: AnalyticsActivity
|
||||||
|
secondActivityPeriodKey: string
|
||||||
|
}): Promise<number>
|
||||||
calculateActivityTotalCount(activity: AnalyticsActivity, period: Period): Promise<number>
|
calculateActivityTotalCount(activity: AnalyticsActivity, period: Period): Promise<number>
|
||||||
calculateActivityChangesTotalCount(
|
calculateActivityChangesTotalCount(
|
||||||
activity: AnalyticsActivity,
|
activity: AnalyticsActivity,
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
export enum StatisticsMeasure {
|
||||||
|
Income = 'income',
|
||||||
|
SubscriptionLength = 'subscription-length',
|
||||||
|
RegistrationLength = 'registration-length',
|
||||||
|
RegistrationToSubscriptionTime = 'registration-to-subscription-time',
|
||||||
|
RemainingSubscriptionTimePercentage = 'remaining-subscription-time-percentage',
|
||||||
|
Refunds = 'refunds',
|
||||||
|
NotesCountFreeUsers = 'notes-count-free-users',
|
||||||
|
NotesCountPaidUsers = 'notes-count-paid-users',
|
||||||
|
FilesCount = 'files-count',
|
||||||
|
}
|
||||||
@@ -1,3 +1,6 @@
|
|||||||
|
import { Period } from '../Time/Period'
|
||||||
|
import { StatisticsMeasure } from './StatisticsMeasure'
|
||||||
|
|
||||||
export interface StatisticsStoreInterface {
|
export interface StatisticsStoreInterface {
|
||||||
incrementSNJSVersionUsage(snjsVersion: string): Promise<void>
|
incrementSNJSVersionUsage(snjsVersion: string): Promise<void>
|
||||||
incrementApplicationVersionUsage(applicationVersion: string): Promise<void>
|
incrementApplicationVersionUsage(applicationVersion: string): Promise<void>
|
||||||
@@ -5,4 +8,7 @@ export interface StatisticsStoreInterface {
|
|||||||
getYesterdaySNJSUsage(): Promise<Array<{ version: string; count: number }>>
|
getYesterdaySNJSUsage(): Promise<Array<{ version: string; count: number }>>
|
||||||
getYesterdayApplicationUsage(): Promise<Array<{ version: string; count: number }>>
|
getYesterdayApplicationUsage(): Promise<Array<{ version: string; count: number }>>
|
||||||
getYesterdayOutOfSyncIncidents(): Promise<number>
|
getYesterdayOutOfSyncIncidents(): Promise<number>
|
||||||
|
incrementMeasure(measure: StatisticsMeasure, value: number, periods: Period[]): Promise<void>
|
||||||
|
getMeasureAverage(measure: StatisticsMeasure, period: Period): Promise<number>
|
||||||
|
getMeasureTotal(measure: StatisticsMeasure, period: Period): Promise<number>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ export enum Period {
|
|||||||
ThisMonth,
|
ThisMonth,
|
||||||
LastMonth,
|
LastMonth,
|
||||||
Last30Days,
|
Last30Days,
|
||||||
|
Last7Days,
|
||||||
Q1ThisYear,
|
Q1ThisYear,
|
||||||
Q2ThisYear,
|
Q2ThisYear,
|
||||||
Q3ThisYear,
|
Q3ThisYear,
|
||||||
|
|||||||
@@ -48,6 +48,18 @@ describe('PeriodKeyGenerator', () => {
|
|||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should generate period keys for last 7 days', () => {
|
||||||
|
expect(createGenerator().getDiscretePeriodKeys(Period.Last7Days)).toEqual([
|
||||||
|
'2022-5-17',
|
||||||
|
'2022-5-18',
|
||||||
|
'2022-5-19',
|
||||||
|
'2022-5-20',
|
||||||
|
'2022-5-21',
|
||||||
|
'2022-5-22',
|
||||||
|
'2022-5-23',
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
it('should generate period keys for Q1', () => {
|
it('should generate period keys for Q1', () => {
|
||||||
expect(createGenerator().getDiscretePeriodKeys(Period.Q1ThisYear)).toEqual(['2022-1', '2022-2', '2022-3'])
|
expect(createGenerator().getDiscretePeriodKeys(Period.Q1ThisYear)).toEqual(['2022-1', '2022-2', '2022-3'])
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -11,6 +11,12 @@ export class PeriodKeyGenerator implements PeriodKeyGeneratorInterface {
|
|||||||
periodKeys.unshift(this.getDailyKey(this.getDateNDaysBefore(i)))
|
periodKeys.unshift(this.getDailyKey(this.getDateNDaysBefore(i)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return periodKeys
|
||||||
|
case Period.Last7Days:
|
||||||
|
for (let i = 1; i <= 7; i++) {
|
||||||
|
periodKeys.unshift(this.getDailyKey(this.getDateNDaysBefore(i)))
|
||||||
|
}
|
||||||
|
|
||||||
return periodKeys
|
return periodKeys
|
||||||
case Period.Q1ThisYear:
|
case Period.Q1ThisYear:
|
||||||
return this.generateMonthlyKeysRange(0, 3)
|
return this.generateMonthlyKeysRange(0, 3)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
export * from './Analytics/AnalyticsActivity'
|
export * from './Analytics/AnalyticsActivity'
|
||||||
export * from './Analytics/AnalyticsStoreInterface'
|
export * from './Analytics/AnalyticsStoreInterface'
|
||||||
|
export * from './Statistics/StatisticsMeasure'
|
||||||
export * from './Statistics/StatisticsStoreInterface'
|
export * from './Statistics/StatisticsStoreInterface'
|
||||||
export * from './Time/Period'
|
export * from './Time/Period'
|
||||||
export * from './Time/PeriodKeyGenerator'
|
export * from './Time/PeriodKeyGenerator'
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ describe('RedisAnalyticsStore', () => {
|
|||||||
|
|
||||||
expect(redisClient.bitop).toHaveBeenCalledWith(
|
expect(redisClient.bitop).toHaveBeenCalledWith(
|
||||||
'AND',
|
'AND',
|
||||||
'bitmap:action:editing-items:timespan:period-key-period-key',
|
'bitmap:action:editing-items-editing-items:timespan:period-key',
|
||||||
'bitmap:action:editing-items:timespan:period-key',
|
'bitmap:action:editing-items:timespan:period-key',
|
||||||
'bitmap:action:editing-items:timespan:period-key',
|
'bitmap:action:editing-items:timespan:period-key',
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -95,21 +95,19 @@ export class RedisAnalyticsStore implements AnalyticsStoreInterface {
|
|||||||
return bitValue === 1
|
return bitValue === 1
|
||||||
}
|
}
|
||||||
|
|
||||||
async calculateActivityRetention(
|
async calculateActivitiesRetention(parameters: {
|
||||||
activity: AnalyticsActivity,
|
firstActivity: AnalyticsActivity
|
||||||
firstPeriod: Period,
|
firstActivityPeriodKey: string
|
||||||
secondPeriod: Period,
|
secondActivity: AnalyticsActivity
|
||||||
): Promise<number> {
|
secondActivityPeriodKey: string
|
||||||
const initialPeriodKey = this.periodKeyGenerator.getPeriodKey(firstPeriod)
|
}): Promise<number> {
|
||||||
const subsequentPeriodKey = this.periodKeyGenerator.getPeriodKey(secondPeriod)
|
const diffKey = `bitmap:action:${parameters.firstActivity}-${parameters.secondActivity}:timespan:${parameters.secondActivityPeriodKey}`
|
||||||
|
|
||||||
const diffKey = `bitmap:action:${activity}:timespan:${initialPeriodKey}-${subsequentPeriodKey}`
|
|
||||||
|
|
||||||
await this.redisClient.bitop(
|
await this.redisClient.bitop(
|
||||||
'AND',
|
'AND',
|
||||||
diffKey,
|
diffKey,
|
||||||
`bitmap:action:${activity}:timespan:${initialPeriodKey}`,
|
`bitmap:action:${parameters.firstActivity}:timespan:${parameters.firstActivityPeriodKey}`,
|
||||||
`bitmap:action:${activity}:timespan:${subsequentPeriodKey}`,
|
`bitmap:action:${parameters.secondActivity}:timespan:${parameters.secondActivityPeriodKey}`,
|
||||||
)
|
)
|
||||||
|
|
||||||
await this.redisClient.expire(diffKey, 3600)
|
await this.redisClient.expire(diffKey, 3600)
|
||||||
@@ -117,12 +115,25 @@ export class RedisAnalyticsStore implements AnalyticsStoreInterface {
|
|||||||
const retainedTotalInActivity = await this.redisClient.bitcount(diffKey)
|
const retainedTotalInActivity = await this.redisClient.bitcount(diffKey)
|
||||||
|
|
||||||
const initialTotalInActivity = await this.redisClient.bitcount(
|
const initialTotalInActivity = await this.redisClient.bitcount(
|
||||||
`bitmap:action:${activity}:timespan:${initialPeriodKey}`,
|
`bitmap:action:${parameters.firstActivity}:timespan:${parameters.firstActivityPeriodKey}`,
|
||||||
)
|
)
|
||||||
|
|
||||||
return Math.ceil((retainedTotalInActivity * 100) / initialTotalInActivity)
|
return Math.ceil((retainedTotalInActivity * 100) / initialTotalInActivity)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async calculateActivityRetention(
|
||||||
|
activity: AnalyticsActivity,
|
||||||
|
firstPeriod: Period,
|
||||||
|
secondPeriod: Period,
|
||||||
|
): Promise<number> {
|
||||||
|
return this.calculateActivitiesRetention({
|
||||||
|
firstActivity: activity,
|
||||||
|
firstActivityPeriodKey: this.periodKeyGenerator.getPeriodKey(firstPeriod),
|
||||||
|
secondActivity: activity,
|
||||||
|
secondActivityPeriodKey: this.periodKeyGenerator.getPeriodKey(secondPeriod),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
async calculateActivityTotalCount(activity: AnalyticsActivity, period: Period): Promise<number> {
|
async calculateActivityTotalCount(activity: AnalyticsActivity, period: Period): Promise<number> {
|
||||||
return this.redisClient.bitcount(
|
return this.redisClient.bitcount(
|
||||||
`bitmap:action:${activity}:timespan:${this.periodKeyGenerator.getPeriodKey(period)}`,
|
`bitmap:action:${activity}:timespan:${this.periodKeyGenerator.getPeriodKey(period)}`,
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import * as IORedis from 'ioredis'
|
import * as IORedis from 'ioredis'
|
||||||
import { PeriodKeyGeneratorInterface } from '../../Domain'
|
|
||||||
|
import { Period, PeriodKeyGeneratorInterface } from '../../Domain'
|
||||||
|
import { StatisticsMeasure } from '../../Domain/Statistics/StatisticsMeasure'
|
||||||
|
|
||||||
import { RedisStatisticsStore } from './RedisStatisticsStore'
|
import { RedisStatisticsStore } from './RedisStatisticsStore'
|
||||||
|
|
||||||
@@ -13,6 +15,7 @@ describe('RedisStatisticsStore', () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
pipeline = {} as jest.Mocked<IORedis.Pipeline>
|
pipeline = {} as jest.Mocked<IORedis.Pipeline>
|
||||||
pipeline.incr = jest.fn()
|
pipeline.incr = jest.fn()
|
||||||
|
pipeline.incrbyfloat = jest.fn()
|
||||||
pipeline.setbit = jest.fn()
|
pipeline.setbit = jest.fn()
|
||||||
pipeline.exec = jest.fn()
|
pipeline.exec = jest.fn()
|
||||||
|
|
||||||
@@ -88,4 +91,30 @@ describe('RedisStatisticsStore', () => {
|
|||||||
expect(pipeline.incr).toHaveBeenCalled()
|
expect(pipeline.incr).toHaveBeenCalled()
|
||||||
expect(pipeline.exec).toHaveBeenCalled()
|
expect(pipeline.exec).toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should increment measure by a value', async () => {
|
||||||
|
await createStore().incrementMeasure(StatisticsMeasure.Income, 2, [Period.Today, Period.ThisMonth])
|
||||||
|
|
||||||
|
expect(pipeline.incr).toHaveBeenCalledTimes(2)
|
||||||
|
expect(pipeline.incrbyfloat).toHaveBeenCalledTimes(2)
|
||||||
|
expect(pipeline.exec).toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should count a measurement average', async () => {
|
||||||
|
redisClient.get = jest.fn().mockReturnValueOnce('5').mockReturnValueOnce('2')
|
||||||
|
|
||||||
|
expect(await createStore().getMeasureAverage(StatisticsMeasure.Income, Period.Today)).toEqual(2 / 5)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should count a measurement average - 0 increments', async () => {
|
||||||
|
redisClient.get = jest.fn().mockReturnValueOnce(null).mockReturnValueOnce(null)
|
||||||
|
|
||||||
|
expect(await createStore().getMeasureAverage(StatisticsMeasure.Income, Period.Today)).toEqual(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should count a measurement average - 0 total value', async () => {
|
||||||
|
redisClient.get = jest.fn().mockReturnValueOnce(5).mockReturnValueOnce(null)
|
||||||
|
|
||||||
|
expect(await createStore().getMeasureAverage(StatisticsMeasure.Income, Period.Today)).toEqual(0)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,12 +1,49 @@
|
|||||||
import * as IORedis from 'ioredis'
|
import * as IORedis from 'ioredis'
|
||||||
|
|
||||||
import { Period, PeriodKeyGeneratorInterface } from '../../Domain'
|
import { Period, PeriodKeyGeneratorInterface } from '../../Domain'
|
||||||
|
import { StatisticsMeasure } from '../../Domain/Statistics/StatisticsMeasure'
|
||||||
|
|
||||||
import { StatisticsStoreInterface } from '../../Domain/Statistics/StatisticsStoreInterface'
|
import { StatisticsStoreInterface } from '../../Domain/Statistics/StatisticsStoreInterface'
|
||||||
|
|
||||||
export class RedisStatisticsStore implements StatisticsStoreInterface {
|
export class RedisStatisticsStore implements StatisticsStoreInterface {
|
||||||
constructor(private periodKeyGenerator: PeriodKeyGeneratorInterface, private redisClient: IORedis.Redis) {}
|
constructor(private periodKeyGenerator: PeriodKeyGeneratorInterface, private redisClient: IORedis.Redis) {}
|
||||||
|
|
||||||
|
async getMeasureTotal(measure: StatisticsMeasure, period: Period): Promise<number> {
|
||||||
|
const totalValue = await this.redisClient.get(
|
||||||
|
`count:measure:${measure}:timespan:${this.periodKeyGenerator.getPeriodKey(period)}`,
|
||||||
|
)
|
||||||
|
|
||||||
|
if (totalValue === null) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return +totalValue
|
||||||
|
}
|
||||||
|
|
||||||
|
async incrementMeasure(measure: StatisticsMeasure, value: number, periods: Period[]): Promise<void> {
|
||||||
|
const pipeline = this.redisClient.pipeline()
|
||||||
|
|
||||||
|
for (const period of periods) {
|
||||||
|
pipeline.incrbyfloat(`count:measure:${measure}:timespan:${this.periodKeyGenerator.getPeriodKey(period)}`, value)
|
||||||
|
pipeline.incr(`count:increments:${measure}:timespan:${this.periodKeyGenerator.getPeriodKey(period)}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
await pipeline.exec()
|
||||||
|
}
|
||||||
|
|
||||||
|
async getMeasureAverage(measure: StatisticsMeasure, period: Period): Promise<number> {
|
||||||
|
const increments = await this.redisClient.get(
|
||||||
|
`count:increments:${measure}:timespan:${this.periodKeyGenerator.getPeriodKey(period)}`,
|
||||||
|
)
|
||||||
|
if (increments === null) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
const totalValue = await this.getMeasureTotal(measure, period)
|
||||||
|
|
||||||
|
return totalValue / +increments
|
||||||
|
}
|
||||||
|
|
||||||
async getYesterdayOutOfSyncIncidents(): Promise<number> {
|
async getYesterdayOutOfSyncIncidents(): Promise<number> {
|
||||||
const count = await this.redisClient.get(
|
const count = await this.redisClient.get(
|
||||||
`count:action:out-of-sync:timespan:${this.periodKeyGenerator.getPeriodKey(Period.Yesterday)}`,
|
`count:action:out-of-sync:timespan:${this.periodKeyGenerator.getPeriodKey(Period.Yesterday)}`,
|
||||||
|
|||||||
@@ -3,6 +3,197 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [1.22.2](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.22.1...@standardnotes/api-gateway@1.22.2) (2022-09-28)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||||
|
|
||||||
|
## [1.22.1](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.22.0...@standardnotes/api-gateway@1.22.1) (2022-09-27)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **api-gateway:** remove admin graphql endpoint from being publicly available ([0a90d98](https://github.com/standardnotes/api-gateway/commit/0a90d98c71c6023b700f852c91aedfe1ad23af55))
|
||||||
|
|
||||||
|
# [1.22.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.21.1...@standardnotes/api-gateway@1.22.0) (2022-09-22)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **auth:** remove muting emails by use case in favor of updating user settings ([d3f36c0](https://github.com/standardnotes/api-gateway/commit/d3f36c05dfc114098a6c231d81149ebd1a959b74))
|
||||||
|
|
||||||
|
## [1.21.1](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.21.0...@standardnotes/api-gateway@1.21.1) (2022-09-21)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **api-gateway:** web socket connection routing ([d35de38](https://github.com/standardnotes/api-gateway/commit/d35de38289e70d707d57a859b8bf39833fa825dd))
|
||||||
|
|
||||||
|
# [1.21.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.20.0...@standardnotes/api-gateway@1.21.0) (2022-09-21)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **auth:** add creating cross service token in exchange for web socket connection token ([965ae79](https://github.com/standardnotes/api-gateway/commit/965ae79414e25d0959f67e16dcbb054229013e1c))
|
||||||
|
|
||||||
|
# [1.20.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.19.6...@standardnotes/api-gateway@1.20.0) (2022-09-21)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **auth:** add creating web socket connection tokens ([8033177](https://github.com/standardnotes/api-gateway/commit/8033177f48dc961194f24fb7daa1073b8b697b74))
|
||||||
|
|
||||||
|
## [1.19.6](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.19.5...@standardnotes/api-gateway@1.19.6) (2022-09-19)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||||
|
|
||||||
|
## [1.19.5](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.19.4...@standardnotes/api-gateway@1.19.5) (2022-09-16)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **auth:** change remaining subscription time stats to percentage ([5eb957c](https://github.com/standardnotes/api-gateway/commit/5eb957c82a8cc5fdcb6815e2cd30e49cd2b1e8ac))
|
||||||
|
|
||||||
|
## [1.19.4](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.19.3...@standardnotes/api-gateway@1.19.4) (2022-09-16)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||||
|
|
||||||
|
## [1.19.3](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.19.2...@standardnotes/api-gateway@1.19.3) (2022-09-15)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **api-gateway:** add remaining subscription time to stats ([89334c9](https://github.com/standardnotes/api-gateway/commit/89334c90221045308d83fce9e97c146185d21389))
|
||||||
|
|
||||||
|
## [1.19.2](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.19.1...@standardnotes/api-gateway@1.19.2) (2022-09-15)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||||
|
|
||||||
|
## [1.19.1](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.19.0...@standardnotes/api-gateway@1.19.1) (2022-09-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||||
|
|
||||||
|
# [1.19.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.18.0...@standardnotes/api-gateway@1.19.0) (2022-09-09)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **syncing-server:** add tracking files count in stats ([52cc646](https://github.com/standardnotes/api-gateway/commit/52cc6462a66dae3bd6c05f551d4ba661c8a9b8c8))
|
||||||
|
|
||||||
|
# [1.18.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.17.4...@standardnotes/api-gateway@1.18.0) (2022-09-09)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **api-gateway:** add general activity breakdown to yesterdays report stats ([339c86f](https://github.com/standardnotes/api-gateway/commit/339c86fca073b02054260417b7519c08874e1e4e))
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **api-gateway:** add tracking general activity for free and paid users breakdown ([0afd3de](https://github.com/standardnotes/api-gateway/commit/0afd3de9779e2abe10deede24626a3cbe6b15e6c))
|
||||||
|
|
||||||
|
## [1.17.4](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.17.3...@standardnotes/api-gateway@1.17.4) (2022-09-09)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **api-gateway:** add notes count statistics to report ([ced852d](https://github.com/standardnotes/api-gateway/commit/ced852d9dbf8cab4c235b94a834968a5fc5e7d36))
|
||||||
|
|
||||||
|
## [1.17.3](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.17.2...@standardnotes/api-gateway@1.17.3) (2022-09-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||||
|
|
||||||
|
## [1.17.2](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.17.1...@standardnotes/api-gateway@1.17.2) (2022-09-08)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **api-gateway:** retention data structure to include both period keys ([50ddb91](https://github.com/standardnotes/api-gateway/commit/50ddb918ccc52bee4caad82504cb899bc5936150))
|
||||||
|
|
||||||
|
## [1.17.1](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.17.0...@standardnotes/api-gateway@1.17.1) (2022-09-08)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **api-gateway:** retention data structure ([47be084](https://github.com/standardnotes/api-gateway/commit/47be0841fc6d5fa00892e775bb3a40f404a6382b))
|
||||||
|
|
||||||
|
# [1.17.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.16.8...@standardnotes/api-gateway@1.17.0) (2022-09-08)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **api-gateway:** add registration-to-activity retention analytics to report ([f139bb0](https://github.com/standardnotes/api-gateway/commit/f139bb003669bb41f98ad4bb59a036c489f43606))
|
||||||
|
|
||||||
|
## [1.16.8](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.16.7...@standardnotes/api-gateway@1.16.8) (2022-09-08)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||||
|
|
||||||
|
## [1.16.7](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.16.6...@standardnotes/api-gateway@1.16.7) (2022-09-08)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||||
|
|
||||||
|
## [1.16.6](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.16.5...@standardnotes/api-gateway@1.16.6) (2022-09-07)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||||
|
|
||||||
|
## [1.16.5](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.16.4...@standardnotes/api-gateway@1.16.5) (2022-09-07)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||||
|
|
||||||
|
## [1.16.4](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.16.3...@standardnotes/api-gateway@1.16.4) (2022-09-07)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **api-gateway:** add registration-to-subscription time to analytics report ([936591d](https://github.com/standardnotes/api-gateway/commit/936591d40b5f5beb5c0a824c92cdfa20fff51c97))
|
||||||
|
|
||||||
|
## [1.16.3](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.16.2...@standardnotes/api-gateway@1.16.3) (2022-09-07)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||||
|
|
||||||
|
## [1.16.2](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.16.1...@standardnotes/api-gateway@1.16.2) (2022-09-06)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **api-gateway:** include period key in statistics measures ([d149f46](https://github.com/standardnotes/api-gateway/commit/d149f46cf6456201dd8690977f64ed32a75f3459))
|
||||||
|
* **api-gateway:** period types on analytics report ([f94c8fc](https://github.com/standardnotes/api-gateway/commit/f94c8fc26e684a07101cc5282ebb9cda3c8c6961))
|
||||||
|
|
||||||
|
## [1.16.1](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.16.0...@standardnotes/api-gateway@1.16.1) (2022-09-06)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||||
|
|
||||||
|
# [1.16.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.15.12...@standardnotes/api-gateway@1.16.0) (2022-09-06)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **api-gateway:** add statistics measures to report generation ([8151bb1](https://github.com/standardnotes/api-gateway/commit/8151bb108affb2b5cfa1ab365f99a9f0170a7795))
|
||||||
|
|
||||||
|
## [1.15.12](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.15.11...@standardnotes/api-gateway@1.15.12) (2022-09-06)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||||
|
|
||||||
|
## [1.15.11](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.15.10...@standardnotes/api-gateway@1.15.11) (2022-09-06)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||||
|
|
||||||
|
## [1.15.10](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.15.9...@standardnotes/api-gateway@1.15.10) (2022-09-05)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||||
|
|
||||||
|
## [1.15.9](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.15.8...@standardnotes/api-gateway@1.15.9) (2022-09-05)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||||
|
|
||||||
|
## [1.15.8](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.15.7...@standardnotes/api-gateway@1.15.8) (2022-09-05)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||||
|
|
||||||
|
## [1.15.7](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.15.6...@standardnotes/api-gateway@1.15.7) (2022-09-05)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||||
|
|
||||||
|
## [1.15.6](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.15.5...@standardnotes/api-gateway@1.15.6) (2022-09-01)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||||
|
|
||||||
|
## [1.15.5](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.15.4...@standardnotes/api-gateway@1.15.5) (2022-09-01)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||||
|
|
||||||
|
## [1.15.4](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.15.3...@standardnotes/api-gateway@1.15.4) (2022-08-30)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||||
|
|
||||||
|
## [1.15.3](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.15.2...@standardnotes/api-gateway@1.15.3) (2022-08-22)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **api-gateway:** add error logs on missing connection id for websockets ([f7def38](https://github.com/standardnotes/api-gateway/commit/f7def38e20f87ae24ebc736a41bc7cac53b0c61f))
|
||||||
|
|
||||||
## [1.15.2](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.15.1...@standardnotes/api-gateway@1.15.2) (2022-08-15)
|
## [1.15.2](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.15.1...@standardnotes/api-gateway@1.15.2) (2022-08-15)
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|||||||
@@ -12,12 +12,20 @@ import {
|
|||||||
DailyAnalyticsReportGeneratedEvent,
|
DailyAnalyticsReportGeneratedEvent,
|
||||||
DomainEventService,
|
DomainEventService,
|
||||||
} from '@standardnotes/domain-events'
|
} from '@standardnotes/domain-events'
|
||||||
import { AnalyticsActivity, AnalyticsStoreInterface, Period, StatisticsStoreInterface } from '@standardnotes/analytics'
|
import {
|
||||||
|
AnalyticsActivity,
|
||||||
|
AnalyticsStoreInterface,
|
||||||
|
Period,
|
||||||
|
PeriodKeyGeneratorInterface,
|
||||||
|
StatisticsMeasure,
|
||||||
|
StatisticsStoreInterface,
|
||||||
|
} from '@standardnotes/analytics'
|
||||||
|
|
||||||
const requestReport = async (
|
const requestReport = async (
|
||||||
analyticsStore: AnalyticsStoreInterface,
|
analyticsStore: AnalyticsStoreInterface,
|
||||||
statisticsStore: StatisticsStoreInterface,
|
statisticsStore: StatisticsStoreInterface,
|
||||||
domainEventPublisher: DomainEventPublisherInterface,
|
domainEventPublisher: DomainEventPublisherInterface,
|
||||||
|
periodKeyGenerator: PeriodKeyGeneratorInterface,
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
const analyticsOverTime = []
|
const analyticsOverTime = []
|
||||||
|
|
||||||
@@ -60,9 +68,10 @@ const requestReport = async (
|
|||||||
|
|
||||||
const yesterdayActivityStatistics = []
|
const yesterdayActivityStatistics = []
|
||||||
const yesterdayActivityNames = [
|
const yesterdayActivityNames = [
|
||||||
AnalyticsActivity.EditingItems,
|
|
||||||
AnalyticsActivity.LimitedDiscountOfferPurchased,
|
AnalyticsActivity.LimitedDiscountOfferPurchased,
|
||||||
AnalyticsActivity.GeneralActivity,
|
AnalyticsActivity.GeneralActivity,
|
||||||
|
AnalyticsActivity.GeneralActivityFreeUsers,
|
||||||
|
AnalyticsActivity.GeneralActivityPaidUsers,
|
||||||
AnalyticsActivity.PaymentFailed,
|
AnalyticsActivity.PaymentFailed,
|
||||||
AnalyticsActivity.PaymentSuccess,
|
AnalyticsActivity.PaymentSuccess,
|
||||||
]
|
]
|
||||||
@@ -79,6 +88,48 @@ const requestReport = async (
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const statisticMeasureNames = [
|
||||||
|
StatisticsMeasure.Income,
|
||||||
|
StatisticsMeasure.Refunds,
|
||||||
|
StatisticsMeasure.RegistrationLength,
|
||||||
|
StatisticsMeasure.SubscriptionLength,
|
||||||
|
StatisticsMeasure.RegistrationToSubscriptionTime,
|
||||||
|
StatisticsMeasure.RemainingSubscriptionTimePercentage,
|
||||||
|
StatisticsMeasure.NotesCountFreeUsers,
|
||||||
|
StatisticsMeasure.NotesCountPaidUsers,
|
||||||
|
StatisticsMeasure.FilesCount,
|
||||||
|
]
|
||||||
|
const statisticMeasures = []
|
||||||
|
for (const statisticMeasureName of statisticMeasureNames) {
|
||||||
|
for (const period of [Period.Yesterday, Period.ThisMonth]) {
|
||||||
|
statisticMeasures.push({
|
||||||
|
name: statisticMeasureName,
|
||||||
|
period,
|
||||||
|
totalValue: await statisticsStore.getMeasureTotal(statisticMeasureName, period),
|
||||||
|
average: await statisticsStore.getMeasureAverage(statisticMeasureName, period),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const periodKeys = periodKeyGenerator.getDiscretePeriodKeys(Period.Last7Days)
|
||||||
|
const retentionOverDays = []
|
||||||
|
for (let i = 0; i < periodKeys.length; i++) {
|
||||||
|
for (let j = 0; j < periodKeys.length - i; j++) {
|
||||||
|
const dailyRetention = await analyticsStore.calculateActivitiesRetention({
|
||||||
|
firstActivity: AnalyticsActivity.Register,
|
||||||
|
firstActivityPeriodKey: periodKeys[i],
|
||||||
|
secondActivity: AnalyticsActivity.GeneralActivity,
|
||||||
|
secondActivityPeriodKey: periodKeys[i + j],
|
||||||
|
})
|
||||||
|
|
||||||
|
retentionOverDays.push({
|
||||||
|
firstPeriodKey: periodKeys[i],
|
||||||
|
secondPeriodKey: periodKeys[i + j],
|
||||||
|
value: dailyRetention,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const event: DailyAnalyticsReportGeneratedEvent = {
|
const event: DailyAnalyticsReportGeneratedEvent = {
|
||||||
type: 'DAILY_ANALYTICS_REPORT_GENERATED',
|
type: 'DAILY_ANALYTICS_REPORT_GENERATED',
|
||||||
createdAt: new Date(),
|
createdAt: new Date(),
|
||||||
@@ -95,6 +146,17 @@ const requestReport = async (
|
|||||||
outOfSyncIncidents: await statisticsStore.getYesterdayOutOfSyncIncidents(),
|
outOfSyncIncidents: await statisticsStore.getYesterdayOutOfSyncIncidents(),
|
||||||
activityStatistics: yesterdayActivityStatistics,
|
activityStatistics: yesterdayActivityStatistics,
|
||||||
activityStatisticsOverTime: analyticsOverTime,
|
activityStatisticsOverTime: analyticsOverTime,
|
||||||
|
statisticMeasures,
|
||||||
|
retentionStatistics: [
|
||||||
|
{
|
||||||
|
firstActivity: AnalyticsActivity.Register,
|
||||||
|
secondActivity: AnalyticsActivity.GeneralActivity,
|
||||||
|
retention: {
|
||||||
|
periodKeys,
|
||||||
|
values: retentionOverDays,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,8 +175,9 @@ void container.load().then((container) => {
|
|||||||
const analyticsStore: AnalyticsStoreInterface = container.get(TYPES.AnalyticsStore)
|
const analyticsStore: AnalyticsStoreInterface = container.get(TYPES.AnalyticsStore)
|
||||||
const statisticsStore: StatisticsStoreInterface = container.get(TYPES.StatisticsStore)
|
const statisticsStore: StatisticsStoreInterface = container.get(TYPES.StatisticsStore)
|
||||||
const domainEventPublisher: DomainEventPublisherInterface = container.get(TYPES.DomainEventPublisher)
|
const domainEventPublisher: DomainEventPublisherInterface = container.get(TYPES.DomainEventPublisher)
|
||||||
|
const periodKeyGenerator: PeriodKeyGeneratorInterface = container.get(TYPES.PeriodKeyGenerator)
|
||||||
|
|
||||||
Promise.resolve(requestReport(analyticsStore, statisticsStore, domainEventPublisher))
|
Promise.resolve(requestReport(analyticsStore, statisticsStore, domainEventPublisher, periodKeyGenerator))
|
||||||
.then(() => {
|
.then(() => {
|
||||||
logger.info('Usage report generation complete')
|
logger.info('Usage report generation complete')
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/api-gateway",
|
"name": "@standardnotes/api-gateway",
|
||||||
"version": "1.15.2",
|
"version": "1.22.2",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16.0.0 <17.0.0"
|
"node": ">=16.0.0 <17.0.0"
|
||||||
},
|
},
|
||||||
@@ -25,6 +25,7 @@
|
|||||||
"@newrelic/winston-enricher": "^4.0.0",
|
"@newrelic/winston-enricher": "^4.0.0",
|
||||||
"@sentry/node": "^7.3.0",
|
"@sentry/node": "^7.3.0",
|
||||||
"@standardnotes/analytics": "workspace:*",
|
"@standardnotes/analytics": "workspace:*",
|
||||||
|
"@standardnotes/common": "workspace:^",
|
||||||
"@standardnotes/domain-events": "workspace:*",
|
"@standardnotes/domain-events": "workspace:*",
|
||||||
"@standardnotes/domain-events-infra": "workspace:*",
|
"@standardnotes/domain-events-infra": "workspace:*",
|
||||||
"@standardnotes/security": "workspace:*",
|
"@standardnotes/security": "workspace:*",
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import * as AWS from 'aws-sdk'
|
|||||||
import {
|
import {
|
||||||
AnalyticsStoreInterface,
|
AnalyticsStoreInterface,
|
||||||
PeriodKeyGenerator,
|
PeriodKeyGenerator,
|
||||||
|
PeriodKeyGeneratorInterface,
|
||||||
RedisAnalyticsStore,
|
RedisAnalyticsStore,
|
||||||
RedisStatisticsStore,
|
RedisStatisticsStore,
|
||||||
StatisticsStoreInterface,
|
StatisticsStoreInterface,
|
||||||
@@ -22,6 +23,7 @@ import { SubscriptionTokenAuthMiddleware } from '../Controller/SubscriptionToken
|
|||||||
import { StatisticsMiddleware } from '../Controller/StatisticsMiddleware'
|
import { StatisticsMiddleware } from '../Controller/StatisticsMiddleware'
|
||||||
import { CrossServiceTokenCacheInterface } from '../Service/Cache/CrossServiceTokenCacheInterface'
|
import { CrossServiceTokenCacheInterface } from '../Service/Cache/CrossServiceTokenCacheInterface'
|
||||||
import { RedisCrossServiceTokenCache } from '../Infra/Redis/RedisCrossServiceTokenCache'
|
import { RedisCrossServiceTokenCache } from '../Infra/Redis/RedisCrossServiceTokenCache'
|
||||||
|
import { WebSocketAuthMiddleware } from '../Controller/WebSocketAuthMiddleware'
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
const newrelicFormatter = require('@newrelic/winston-enricher')
|
const newrelicFormatter = require('@newrelic/winston-enricher')
|
||||||
@@ -84,6 +86,7 @@ export class ContainerConfigLoader {
|
|||||||
|
|
||||||
// Middleware
|
// Middleware
|
||||||
container.bind<AuthMiddleware>(TYPES.AuthMiddleware).to(AuthMiddleware)
|
container.bind<AuthMiddleware>(TYPES.AuthMiddleware).to(AuthMiddleware)
|
||||||
|
container.bind<WebSocketAuthMiddleware>(TYPES.WebSocketAuthMiddleware).to(WebSocketAuthMiddleware)
|
||||||
container
|
container
|
||||||
.bind<SubscriptionTokenAuthMiddleware>(TYPES.SubscriptionTokenAuthMiddleware)
|
.bind<SubscriptionTokenAuthMiddleware>(TYPES.SubscriptionTokenAuthMiddleware)
|
||||||
.to(SubscriptionTokenAuthMiddleware)
|
.to(SubscriptionTokenAuthMiddleware)
|
||||||
@@ -91,13 +94,13 @@ export class ContainerConfigLoader {
|
|||||||
|
|
||||||
// Services
|
// Services
|
||||||
container.bind<HttpServiceInterface>(TYPES.HTTPService).to(HttpService)
|
container.bind<HttpServiceInterface>(TYPES.HTTPService).to(HttpService)
|
||||||
const periodKeyGenerator = new PeriodKeyGenerator()
|
container.bind<PeriodKeyGeneratorInterface>(TYPES.PeriodKeyGenerator).toConstantValue(new PeriodKeyGenerator())
|
||||||
container
|
container
|
||||||
.bind<AnalyticsStoreInterface>(TYPES.AnalyticsStore)
|
.bind<AnalyticsStoreInterface>(TYPES.AnalyticsStore)
|
||||||
.toConstantValue(new RedisAnalyticsStore(periodKeyGenerator, container.get(TYPES.Redis)))
|
.toConstantValue(new RedisAnalyticsStore(container.get(TYPES.PeriodKeyGenerator), container.get(TYPES.Redis)))
|
||||||
container
|
container
|
||||||
.bind<StatisticsStoreInterface>(TYPES.StatisticsStore)
|
.bind<StatisticsStoreInterface>(TYPES.StatisticsStore)
|
||||||
.toConstantValue(new RedisStatisticsStore(periodKeyGenerator, container.get(TYPES.Redis)))
|
.toConstantValue(new RedisStatisticsStore(container.get(TYPES.PeriodKeyGenerator), container.get(TYPES.Redis)))
|
||||||
container.bind<CrossServiceTokenCacheInterface>(TYPES.CrossServiceTokenCache).to(RedisCrossServiceTokenCache)
|
container.bind<CrossServiceTokenCacheInterface>(TYPES.CrossServiceTokenCache).to(RedisCrossServiceTokenCache)
|
||||||
container.bind<TimerInterface>(TYPES.Timer).toConstantValue(new Timer())
|
container.bind<TimerInterface>(TYPES.Timer).toConstantValue(new Timer())
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ const TYPES = {
|
|||||||
// Middleware
|
// Middleware
|
||||||
StatisticsMiddleware: Symbol.for('StatisticsMiddleware'),
|
StatisticsMiddleware: Symbol.for('StatisticsMiddleware'),
|
||||||
AuthMiddleware: Symbol.for('AuthMiddleware'),
|
AuthMiddleware: Symbol.for('AuthMiddleware'),
|
||||||
|
WebSocketAuthMiddleware: Symbol.for('WebSocketAuthMiddleware'),
|
||||||
SubscriptionTokenAuthMiddleware: Symbol.for('SubscriptionTokenAuthMiddleware'),
|
SubscriptionTokenAuthMiddleware: Symbol.for('SubscriptionTokenAuthMiddleware'),
|
||||||
// Services
|
// Services
|
||||||
HTTPService: Symbol.for('HTTPService'),
|
HTTPService: Symbol.for('HTTPService'),
|
||||||
@@ -26,6 +27,7 @@ const TYPES = {
|
|||||||
StatisticsStore: Symbol.for('StatisticsStore'),
|
StatisticsStore: Symbol.for('StatisticsStore'),
|
||||||
DomainEventPublisher: Symbol.for('DomainEventPublisher'),
|
DomainEventPublisher: Symbol.for('DomainEventPublisher'),
|
||||||
Timer: Symbol.for('Timer'),
|
Timer: Symbol.for('Timer'),
|
||||||
|
PeriodKeyGenerator: Symbol.for('PeriodKeyGenerator'),
|
||||||
}
|
}
|
||||||
|
|
||||||
export default TYPES
|
export default TYPES
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { CrossServiceTokenData } from '@standardnotes/security'
|
import { CrossServiceTokenData } from '@standardnotes/security'
|
||||||
|
import { RoleName } from '@standardnotes/common'
|
||||||
import { AnalyticsActivity, AnalyticsStoreInterface, Period } from '@standardnotes/analytics'
|
import { AnalyticsActivity, AnalyticsStoreInterface, Period } from '@standardnotes/analytics'
|
||||||
import { TimerInterface } from '@standardnotes/time'
|
import { TimerInterface } from '@standardnotes/time'
|
||||||
import { NextFunction, Request, Response } from 'express'
|
import { NextFunction, Request, Response } from 'express'
|
||||||
@@ -75,9 +76,20 @@ export class AuthMiddleware extends BaseMiddleware {
|
|||||||
|
|
||||||
const decodedToken = <CrossServiceTokenData>verify(crossServiceToken, this.jwtSecret, { algorithms: ['HS256'] })
|
const decodedToken = <CrossServiceTokenData>verify(crossServiceToken, this.jwtSecret, { algorithms: ['HS256'] })
|
||||||
|
|
||||||
await this.analyticsStore.markActivity([AnalyticsActivity.GeneralActivity], decodedToken.analyticsId as number, [
|
response.locals.freeUser =
|
||||||
Period.Today,
|
decodedToken.roles.length === 1 &&
|
||||||
])
|
decodedToken.roles.find((role) => role.name === RoleName.CoreUser) !== undefined
|
||||||
|
|
||||||
|
await this.analyticsStore.markActivity(
|
||||||
|
[
|
||||||
|
AnalyticsActivity.GeneralActivity,
|
||||||
|
response.locals.freeUser
|
||||||
|
? AnalyticsActivity.GeneralActivityFreeUsers
|
||||||
|
: AnalyticsActivity.GeneralActivityPaidUsers,
|
||||||
|
],
|
||||||
|
decodedToken.analyticsId as number,
|
||||||
|
[Period.Today],
|
||||||
|
)
|
||||||
|
|
||||||
if (this.crossServiceTokenCacheTTL && !crossServiceTokenFetchedFromCache) {
|
if (this.crossServiceTokenCacheTTL && !crossServiceTokenFetchedFromCache) {
|
||||||
await this.crossServiceTokenCache.set({
|
await this.crossServiceTokenCache.set({
|
||||||
|
|||||||
@@ -0,0 +1,95 @@
|
|||||||
|
import { CrossServiceTokenData } from '@standardnotes/security'
|
||||||
|
import { RoleName } from '@standardnotes/common'
|
||||||
|
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'
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class WebSocketAuthMiddleware 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 authHeaderValue = request.headers.authorization as string
|
||||||
|
|
||||||
|
if (!authHeaderValue) {
|
||||||
|
response.status(401).send({
|
||||||
|
error: {
|
||||||
|
tag: 'invalid-auth',
|
||||||
|
message: 'Invalid login credentials.',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
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}/sockets/tokens/validate`,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (authResponse.status > 200) {
|
||||||
|
response.setHeader('content-type', authResponse.headers['content-type'])
|
||||||
|
response.status(authResponse.status).send(authResponse.data)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const crossServiceToken = authResponse.data.authToken
|
||||||
|
|
||||||
|
response.locals.authToken = crossServiceToken
|
||||||
|
|
||||||
|
const decodedToken = <CrossServiceTokenData>verify(crossServiceToken, this.jwtSecret, { algorithms: ['HS256'] })
|
||||||
|
|
||||||
|
response.locals.freeUser =
|
||||||
|
decodedToken.roles.length === 1 &&
|
||||||
|
decodedToken.roles.find((role) => role.name === RoleName.CoreUser) !== undefined
|
||||||
|
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}/sockets/tokens/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 && !isNaN(+((error as AxiosError).code as string))
|
||||||
|
? +((error as AxiosError).code as string)
|
||||||
|
: 500
|
||||||
|
|
||||||
|
response.status(errorCode).send(errorMessage)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return next()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -29,34 +29,4 @@ export class ActionsController extends BaseHttpController {
|
|||||||
async methods(request: Request, response: Response): Promise<void> {
|
async methods(request: Request, response: Response): Promise<void> {
|
||||||
await this.httpService.callAuthServer(request, response, 'auth/methods', request.body)
|
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,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@httpGet('/marketing-emails/mute/:settingUuid')
|
|
||||||
async muteMarketingEmails(request: Request, response: Response): Promise<void> {
|
|
||||||
await this.httpService.callAuthServer(
|
|
||||||
request,
|
|
||||||
response,
|
|
||||||
`internal/settings/marketing-emails/${request.params.settingUuid}/mute`,
|
|
||||||
request.body,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,11 +70,6 @@ export class PaymentsController extends BaseHttpController {
|
|||||||
await this.httpService.callPaymentsServer(request, response, 'admin/events/registration', request.body)
|
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')
|
@httpPost('/admin/auth/login')
|
||||||
async adminLogin(request: Request, response: Response): Promise<void> {
|
async adminLogin(request: Request, response: Response): Promise<void> {
|
||||||
await this.httpService.callPaymentsServer(request, response, 'admin/auth/login', request.body)
|
await this.httpService.callPaymentsServer(request, response, 'admin/auth/login', request.body)
|
||||||
|
|||||||
@@ -1,34 +1,58 @@
|
|||||||
import { Request, Response } from 'express'
|
import { Request, Response } from 'express'
|
||||||
import { inject } from 'inversify'
|
import { inject } from 'inversify'
|
||||||
import { BaseHttpController, controller, httpDelete, httpPost } from 'inversify-express-utils'
|
import { BaseHttpController, controller, httpDelete, httpPost } from 'inversify-express-utils'
|
||||||
|
import { Logger } from 'winston'
|
||||||
|
|
||||||
import TYPES from '../../Bootstrap/Types'
|
import TYPES from '../../Bootstrap/Types'
|
||||||
import { HttpServiceInterface } from '../../Service/Http/HttpServiceInterface'
|
import { HttpServiceInterface } from '../../Service/Http/HttpServiceInterface'
|
||||||
|
|
||||||
@controller('/v1/sockets')
|
@controller('/v1/sockets')
|
||||||
export class WebSocketsController extends BaseHttpController {
|
export class WebSocketsController extends BaseHttpController {
|
||||||
constructor(@inject(TYPES.HTTPService) private httpService: HttpServiceInterface) {
|
constructor(
|
||||||
|
@inject(TYPES.HTTPService) private httpService: HttpServiceInterface,
|
||||||
|
@inject(TYPES.Logger) private logger: Logger,
|
||||||
|
) {
|
||||||
super()
|
super()
|
||||||
}
|
}
|
||||||
|
|
||||||
@httpPost('/', TYPES.AuthMiddleware)
|
@httpPost('/tokens', TYPES.AuthMiddleware)
|
||||||
|
async createWebSocketConnectionToken(request: Request, response: Response): Promise<void> {
|
||||||
|
await this.httpService.callAuthServer(request, response, 'sockets/tokens', request.body)
|
||||||
|
}
|
||||||
|
|
||||||
|
@httpPost('/connections', TYPES.WebSocketAuthMiddleware)
|
||||||
async createWebSocketConnection(request: Request, response: Response): Promise<void> {
|
async createWebSocketConnection(request: Request, response: Response): Promise<void> {
|
||||||
if (!request.headers.connectionid) {
|
if (!request.headers.connectionid) {
|
||||||
|
this.logger.error('Could not create a websocket connection. Missing connection id header.')
|
||||||
|
|
||||||
response.status(400).send('Missing connection id in the request')
|
response.status(400).send('Missing connection id in the request')
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.httpService.callAuthServer(request, response, `sockets/${request.headers.connectionid}`, request.body)
|
await this.httpService.callAuthServer(
|
||||||
|
request,
|
||||||
|
response,
|
||||||
|
`sockets/connections/${request.headers.connectionid}`,
|
||||||
|
request.body,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@httpDelete('/')
|
@httpDelete('/connections')
|
||||||
async deleteWebSocketConnection(request: Request, response: Response): Promise<void> {
|
async deleteWebSocketConnection(request: Request, response: Response): Promise<void> {
|
||||||
if (!request.headers.connectionid) {
|
if (!request.headers.connectionid) {
|
||||||
|
this.logger.error('Could not delete a websocket connection. Missing connection id header.')
|
||||||
|
|
||||||
response.status(400).send('Missing connection id in the request')
|
response.status(400).send('Missing connection id in the request')
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.httpService.callAuthServer(request, response, `sockets/${request.headers.connectionid}`, request.body)
|
await this.httpService.callAuthServer(
|
||||||
|
request,
|
||||||
|
response,
|
||||||
|
`sockets/connections/${request.headers.connectionid}`,
|
||||||
|
request.body,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,5 +66,8 @@ SENTRY_ENVIRONMENT=
|
|||||||
VALET_TOKEN_SECRET=
|
VALET_TOKEN_SECRET=
|
||||||
VALET_TOKEN_TTL=
|
VALET_TOKEN_TTL=
|
||||||
|
|
||||||
|
WEB_SOCKET_CONNECTION_TOKEN_SECRET=
|
||||||
|
WEB_SOCKET_CONNECTION_TOKEN_TTL=
|
||||||
|
|
||||||
# (Optional) Analytics
|
# (Optional) Analytics
|
||||||
ANALYTICS_ENABLED=false
|
ANALYTICS_ENABLED=false
|
||||||
|
|||||||
@@ -3,6 +3,294 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [1.32.9](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.32.8...@standardnotes/auth-server@1.32.9) (2022-09-28)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/auth-server
|
||||||
|
|
||||||
|
## [1.32.8](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.32.7...@standardnotes/auth-server@1.32.8) (2022-09-27)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **auth:** ttl for lock counter on login lockout ([54da5de](https://github.com/standardnotes/server/commit/54da5def4bbfbb4f74cbf02ae23e45103d250dd9))
|
||||||
|
|
||||||
|
## [1.32.7](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.32.6...@standardnotes/auth-server@1.32.7) (2022-09-27)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **auth:** allow resending canceled subscription invites ([b190931](https://github.com/standardnotes/server/commit/b19093179baaa1fb8cdf3f9d9bee20e625ed0b9b))
|
||||||
|
|
||||||
|
## [1.32.6](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.32.5...@standardnotes/auth-server@1.32.6) (2022-09-22)
|
||||||
|
|
||||||
|
### Reverts
|
||||||
|
|
||||||
|
* Revert "fix(auth): subscription token ttl" ([644c52a](https://github.com/standardnotes/server/commit/644c52ae36d3720dee0712e2cb826c7e617ab7b7))
|
||||||
|
* Revert "fix(auth): increase subscription token ttl" ([2554273](https://github.com/standardnotes/server/commit/2554273a3f85a968fed4286d109bed5413ef9908))
|
||||||
|
* Revert "tmp(auth): disable expiring of subscription tokens" ([a8ee149](https://github.com/standardnotes/server/commit/a8ee149d7ac78775bf447ab924458b116414a15e))
|
||||||
|
|
||||||
|
## [1.32.5](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.32.4...@standardnotes/auth-server@1.32.5) (2022-09-22)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/auth-server
|
||||||
|
|
||||||
|
## [1.32.4](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.32.3...@standardnotes/auth-server@1.32.4) (2022-09-22)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **auth:** increase subscription token ttl ([07def20](https://github.com/standardnotes/server/commit/07def20f6b47f9d1c678cfe5206b924dd5e6014a))
|
||||||
|
|
||||||
|
## [1.32.3](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.32.2...@standardnotes/auth-server@1.32.3) (2022-09-22)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **auth:** subscription token ttl ([6efd336](https://github.com/standardnotes/server/commit/6efd336f3407e7204a0c5d385ea9df5c02c7e5f5))
|
||||||
|
|
||||||
|
## [1.32.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.32.1...@standardnotes/auth-server@1.32.2) (2022-09-22)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **auth:** add throwing an error if the subscription token was not persisted ([76cee6d](https://github.com/standardnotes/server/commit/76cee6dbad9bff041d8d5a1d4435046509c14f71))
|
||||||
|
|
||||||
|
## [1.32.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.32.0...@standardnotes/auth-server@1.32.1) (2022-09-22)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **auth:** settings and subscription settings projection ([d6cf8d4](https://github.com/standardnotes/server/commit/d6cf8d400a0177ee9030a171cf2ca47ade293fd9))
|
||||||
|
|
||||||
|
# [1.32.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.31.2...@standardnotes/auth-server@1.32.0) (2022-09-22)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **auth:** remove muting emails by use case in favor of updating user settings ([d3f36c0](https://github.com/standardnotes/server/commit/d3f36c05dfc114098a6c231d81149ebd1a959b74))
|
||||||
|
|
||||||
|
## [1.31.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.31.1...@standardnotes/auth-server@1.31.2) (2022-09-21)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **auth:** response wrapping on web socket connection token creation ([413a276](https://github.com/standardnotes/server/commit/413a276d205d53c316f7d0af8aed422001a6c1ab))
|
||||||
|
|
||||||
|
## [1.31.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.31.0...@standardnotes/auth-server@1.31.1) (2022-09-21)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **auth:** web sockets routes ([875edce](https://github.com/standardnotes/server/commit/875edce5b1dc134b4e22702354b29303fab3c910))
|
||||||
|
|
||||||
|
# [1.31.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.30.1...@standardnotes/auth-server@1.31.0) (2022-09-21)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **auth:** add creating cross service token in exchange for web socket connection token ([965ae79](https://github.com/standardnotes/server/commit/965ae79414e25d0959f67e16dcbb054229013e1c))
|
||||||
|
|
||||||
|
## [1.30.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.30.0...@standardnotes/auth-server@1.30.1) (2022-09-21)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **auth:** missing injectable annotation ([d935157](https://github.com/standardnotes/server/commit/d935157ee8425d427fa52465e766d18e29332b5b))
|
||||||
|
|
||||||
|
# [1.30.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.29.1...@standardnotes/auth-server@1.30.0) (2022-09-21)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **auth:** add creating web socket connection tokens ([8033177](https://github.com/standardnotes/server/commit/8033177f48dc961194f24fb7daa1073b8b697b74))
|
||||||
|
|
||||||
|
## [1.29.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.29.0...@standardnotes/auth-server@1.29.1) (2022-09-19)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **auth:** uuid validator binding ([db6f966](https://github.com/standardnotes/server/commit/db6f966045d51e59555740c9e009bf66b629673c))
|
||||||
|
|
||||||
|
# [1.29.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.28.4...@standardnotes/auth-server@1.29.0) (2022-09-19)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **files:** add validating remote identifiers ([db15457](https://github.com/standardnotes/server/commit/db15457ce4eb533ec822cf93c3ed83eafe9e64d5))
|
||||||
|
|
||||||
|
## [1.28.4](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.28.3...@standardnotes/auth-server@1.28.4) (2022-09-16)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **auth:** feature service spec ([c207c3f](https://github.com/standardnotes/server/commit/c207c3fc8442eec9b8c3150f09ecccfdd6a5ed50))
|
||||||
|
|
||||||
|
## [1.28.3](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.28.2...@standardnotes/auth-server@1.28.3) (2022-09-16)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **auth:** change remaining subscription time stats to percentage ([5eb957c](https://github.com/standardnotes/server/commit/5eb957c82a8cc5fdcb6815e2cd30e49cd2b1e8ac))
|
||||||
|
|
||||||
|
## [1.28.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.28.1...@standardnotes/auth-server@1.28.2) (2022-09-16)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **files:** add verifying permitted operation on valet token ([377d32c](https://github.com/standardnotes/server/commit/377d32c4498305f0f59ff59e7357f0d2f10ce3a2))
|
||||||
|
|
||||||
|
## [1.28.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.28.0...@standardnotes/auth-server@1.28.1) (2022-09-15)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **auth:** missing injectable annotation ([d851524](https://github.com/standardnotes/server/commit/d85152429ca379d3d0314a9864cc46ebee541958))
|
||||||
|
|
||||||
|
# [1.28.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.27.0...@standardnotes/auth-server@1.28.0) (2022-09-15)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **auth:** add remaining subscription time stats ([a59ba08](https://github.com/standardnotes/server/commit/a59ba083397c75960af0e8a102b617bf5baa287f))
|
||||||
|
|
||||||
|
# [1.27.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.26.1...@standardnotes/auth-server@1.27.0) (2022-09-15)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **auth:** implement subscription server interface on server side ([5d812be](https://github.com/standardnotes/server/commit/5d812befc4733954919eef0d3718ae6f8eb81654))
|
||||||
|
|
||||||
|
## [1.26.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.26.0...@standardnotes/auth-server@1.26.1) (2022-09-15)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **auth:** disallow duplicating subscription invites ([531f13f](https://github.com/standardnotes/server/commit/531f13fe1f4bdfb8d27f5e3c07ec0b15d36ad413))
|
||||||
|
|
||||||
|
# [1.26.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.25.13...@standardnotes/auth-server@1.26.0) (2022-09-13)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **auth:** add subscription sharing permission ([f45320e](https://github.com/standardnotes/server/commit/f45320e5ed8948a432029586c05284f4d640de5b))
|
||||||
|
|
||||||
|
## [1.25.13](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.25.12...@standardnotes/auth-server@1.25.13) (2022-09-12)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **auth:** add debug logs for canceling shared subscription invitations ([dd13e2e](https://github.com/standardnotes/server/commit/dd13e2eaf74de56a3c8c30c236c32c6dc0c560f2))
|
||||||
|
|
||||||
|
## [1.25.12](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.25.11...@standardnotes/auth-server@1.25.12) (2022-09-12)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **auth:** allow canceling shared subscription invitation before it was accepted ([0dab31f](https://github.com/standardnotes/server/commit/0dab31f9936bfd5081a87eef9701a268b8dec88c))
|
||||||
|
|
||||||
|
## [1.25.11](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.25.10...@standardnotes/auth-server@1.25.11) (2022-09-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/auth-server
|
||||||
|
|
||||||
|
## [1.25.10](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.25.9...@standardnotes/auth-server@1.25.10) (2022-09-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/auth-server
|
||||||
|
|
||||||
|
## [1.25.9](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.25.8...@standardnotes/auth-server@1.25.9) (2022-09-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/auth-server
|
||||||
|
|
||||||
|
## [1.25.8](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.25.7...@standardnotes/auth-server@1.25.8) (2022-09-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/auth-server
|
||||||
|
|
||||||
|
## [1.25.7](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.25.6...@standardnotes/auth-server@1.25.7) (2022-09-08)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/auth-server
|
||||||
|
|
||||||
|
## [1.25.6](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.25.5...@standardnotes/auth-server@1.25.6) (2022-09-08)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/auth-server
|
||||||
|
|
||||||
|
## [1.25.5](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.25.4...@standardnotes/auth-server@1.25.5) (2022-09-08)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/auth-server
|
||||||
|
|
||||||
|
## [1.25.4](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.25.3...@standardnotes/auth-server@1.25.4) (2022-09-08)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/auth-server
|
||||||
|
|
||||||
|
## [1.25.3](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.25.2...@standardnotes/auth-server@1.25.3) (2022-09-08)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/auth-server
|
||||||
|
|
||||||
|
## [1.25.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.25.1...@standardnotes/auth-server@1.25.2) (2022-09-07)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/auth-server
|
||||||
|
|
||||||
|
## [1.25.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.25.0...@standardnotes/auth-server@1.25.1) (2022-09-07)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/auth-server
|
||||||
|
|
||||||
|
# [1.25.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.24.4...@standardnotes/auth-server@1.25.0) (2022-09-07)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **auth:** add measuring registration to subscription time statistics ([b618252](https://github.com/standardnotes/server/commit/b61825235eebaf5eddb55cbda173176ca43c0099))
|
||||||
|
|
||||||
|
## [1.24.4](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.24.3...@standardnotes/auth-server@1.24.4) (2022-09-07)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **auth:** forbid users on shared subscription to send out invitations ([132b617](https://github.com/standardnotes/server/commit/132b617aaa8a703877fd7e8d23711fb1ec234524))
|
||||||
|
|
||||||
|
## [1.24.3](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.24.2...@standardnotes/auth-server@1.24.3) (2022-09-06)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/auth-server
|
||||||
|
|
||||||
|
## [1.24.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.24.1...@standardnotes/auth-server@1.24.2) (2022-09-06)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/auth-server
|
||||||
|
|
||||||
|
## [1.24.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.24.0...@standardnotes/auth-server@1.24.1) (2022-09-06)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/auth-server
|
||||||
|
|
||||||
|
# [1.24.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.23.2...@standardnotes/auth-server@1.24.0) (2022-09-06)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **auth:** add statistics for refunds and account deletions ([d7ae2f0](https://github.com/standardnotes/server/commit/d7ae2f06255b19eb5d3403a4989610390064754e))
|
||||||
|
|
||||||
|
## [1.23.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.23.1...@standardnotes/auth-server@1.23.2) (2022-09-06)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **auth:** add debug logs for subscription canceling ([2ca430f](https://github.com/standardnotes/server/commit/2ca430f40ce6a8d56aafa27e9c2d0b0dd561c650))
|
||||||
|
|
||||||
|
## [1.23.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.23.0...@standardnotes/auth-server@1.23.1) (2022-09-06)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/auth-server
|
||||||
|
|
||||||
|
# [1.23.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.22.1...@standardnotes/auth-server@1.23.0) (2022-09-05)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **auth:** add measuring subscription length ([fa10827](https://github.com/standardnotes/server/commit/fa108274430d8dff1016ddcba5bbcb2778eb781b))
|
||||||
|
|
||||||
|
## [1.22.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.22.0...@standardnotes/auth-server@1.22.1) (2022-09-05)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **auth:** StatisticsStore binding ([34315c9](https://github.com/standardnotes/server/commit/34315c91d7428bbe8297e50972aa7823e2a983b2))
|
||||||
|
|
||||||
|
# [1.22.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.21.5...@standardnotes/auth-server@1.22.0) (2022-09-05)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **auth:** add keeping stats on payments ([0c176b7](https://github.com/standardnotes/server/commit/0c176b70f8281e1e490224b9c7ab85f272a3d4e9))
|
||||||
|
|
||||||
|
## [1.21.5](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.21.4...@standardnotes/auth-server@1.21.5) (2022-09-05)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/auth-server
|
||||||
|
|
||||||
|
## [1.21.4](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.21.3...@standardnotes/auth-server@1.21.4) (2022-09-05)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/auth-server
|
||||||
|
|
||||||
|
## [1.21.3](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.21.2...@standardnotes/auth-server@1.21.3) (2022-09-01)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/auth-server
|
||||||
|
|
||||||
|
## [1.21.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.21.1...@standardnotes/auth-server@1.21.2) (2022-09-01)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/auth-server
|
||||||
|
|
||||||
|
## [1.21.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.21.0...@standardnotes/auth-server@1.21.1) (2022-08-30)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/auth-server
|
||||||
|
|
||||||
|
# [1.21.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.20.1...@standardnotes/auth-server@1.21.0) (2022-08-29)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **auth:** retain user agent, api version and received at on revoked sessions ([dc88e24](https://github.com/standardnotes/server/commit/dc88e2413b3be254b265d2874174eaac39d628ee))
|
||||||
|
|
||||||
## [1.20.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.20.0...@standardnotes/auth-server@1.20.1) (2022-08-17)
|
## [1.20.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.20.0...@standardnotes/auth-server@1.20.1) (2022-08-17)
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|||||||
@@ -10,17 +10,17 @@ import '../src/Controller/SessionsController'
|
|||||||
import '../src/Controller/UsersController'
|
import '../src/Controller/UsersController'
|
||||||
import '../src/Controller/SettingsController'
|
import '../src/Controller/SettingsController'
|
||||||
import '../src/Controller/FeaturesController'
|
import '../src/Controller/FeaturesController'
|
||||||
import '../src/Controller/WebSocketsController'
|
|
||||||
import '../src/Controller/AdminController'
|
import '../src/Controller/AdminController'
|
||||||
import '../src/Controller/InternalController'
|
import '../src/Controller/InternalController'
|
||||||
import '../src/Controller/SubscriptionTokensController'
|
import '../src/Controller/SubscriptionTokensController'
|
||||||
import '../src/Controller/OfflineController'
|
import '../src/Controller/OfflineController'
|
||||||
import '../src/Controller/ValetTokenController'
|
import '../src/Controller/ValetTokenController'
|
||||||
import '../src/Controller/ListedController'
|
import '../src/Controller/ListedController'
|
||||||
import '../src/Controller/SubscriptionInvitesController'
|
|
||||||
import '../src/Controller/SubscriptionSettingsController'
|
import '../src/Controller/SubscriptionSettingsController'
|
||||||
|
|
||||||
import '../src/Infra/InversifyExpressUtils/InversifyExpressAuthController'
|
import '../src/Infra/InversifyExpressUtils/InversifyExpressAuthController'
|
||||||
|
import '../src/Infra/InversifyExpressUtils/InversifyExpressSubscriptionInvitesController'
|
||||||
|
import '../src/Infra/InversifyExpressUtils/InversifyExpressWebSocketsController'
|
||||||
|
|
||||||
import * as cors from 'cors'
|
import * as cors from 'cors'
|
||||||
import { urlencoded, json, Request, Response, NextFunction, RequestHandler, ErrorRequestHandler } from 'express'
|
import { urlencoded, json, Request, Response, NextFunction, RequestHandler, ErrorRequestHandler } from 'express'
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { MigrationInterface, QueryRunner } from 'typeorm'
|
||||||
|
|
||||||
|
export class revokedSessionData1661771230400 implements MigrationInterface {
|
||||||
|
name = 'revokedSessionData1661771230400'
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query('ALTER TABLE `revoked_sessions` ADD `received_at` datetime NULL')
|
||||||
|
await queryRunner.query('ALTER TABLE `revoked_sessions` ADD `user_agent` text NULL')
|
||||||
|
await queryRunner.query('ALTER TABLE `revoked_sessions` ADD `api_version` varchar(255) NULL')
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(): Promise<void> {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { MigrationInterface, QueryRunner } from 'typeorm'
|
||||||
|
|
||||||
|
export class addSubscriptionSharingPermission1663073954000 implements MigrationInterface {
|
||||||
|
name = 'addSubscriptionSharingPermission1663073954000'
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(
|
||||||
|
'INSERT INTO `permissions` (uuid, name) VALUES ("3aeaf12e-380f-4f21-97b9-d862d63874f6", "server:subscription-sharing")',
|
||||||
|
)
|
||||||
|
|
||||||
|
// Pro User Permissions
|
||||||
|
await queryRunner.query(
|
||||||
|
'INSERT INTO `role_permissions` (role_uuid, permission_uuid) VALUES \
|
||||||
|
("8047edbb-a10a-4ff8-8d53-c2cae600a8e8", "3aeaf12e-380f-4f21-97b9-d862d63874f6") \
|
||||||
|
',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(): Promise<void> {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { MigrationInterface, QueryRunner } from 'typeorm'
|
||||||
|
|
||||||
|
export class addRenewedAtColumn1663321030000 implements MigrationInterface {
|
||||||
|
name = 'addRenewedAtColumn1663321030000'
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query('ALTER TABLE `user_subscriptions` ADD `renewed_at` bigint NULL')
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(): Promise<void> {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/auth-server",
|
"name": "@standardnotes/auth-server",
|
||||||
"version": "1.20.1",
|
"version": "1.32.9",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16.0.0 <17.0.0"
|
"node": ">=16.0.0 <17.0.0"
|
||||||
},
|
},
|
||||||
@@ -34,11 +34,11 @@
|
|||||||
"@newrelic/winston-enricher": "^4.0.0",
|
"@newrelic/winston-enricher": "^4.0.0",
|
||||||
"@sentry/node": "^7.3.0",
|
"@sentry/node": "^7.3.0",
|
||||||
"@standardnotes/analytics": "workspace:*",
|
"@standardnotes/analytics": "workspace:*",
|
||||||
"@standardnotes/api": "^1.1.19",
|
"@standardnotes/api": "^1.8.1",
|
||||||
"@standardnotes/common": "workspace:*",
|
"@standardnotes/common": "workspace:*",
|
||||||
"@standardnotes/domain-events": "workspace:*",
|
"@standardnotes/domain-events": "workspace:*",
|
||||||
"@standardnotes/domain-events-infra": "workspace:*",
|
"@standardnotes/domain-events-infra": "workspace:*",
|
||||||
"@standardnotes/features": "^1.47.0",
|
"@standardnotes/features": "^1.52.1",
|
||||||
"@standardnotes/predicates": "workspace:*",
|
"@standardnotes/predicates": "workspace:*",
|
||||||
"@standardnotes/responses": "^1.6.39",
|
"@standardnotes/responses": "^1.6.39",
|
||||||
"@standardnotes/security": "workspace:*",
|
"@standardnotes/security": "workspace:*",
|
||||||
@@ -63,7 +63,7 @@
|
|||||||
"reflect-metadata": "0.1.13",
|
"reflect-metadata": "0.1.13",
|
||||||
"typeorm": "^0.3.6",
|
"typeorm": "^0.3.6",
|
||||||
"ua-parser-js": "1.0.2",
|
"ua-parser-js": "1.0.2",
|
||||||
"uuid": "8.3.2",
|
"uuid": "^9.0.0",
|
||||||
"winston": "^3.8.1"
|
"winston": "^3.8.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -9,7 +9,13 @@ import {
|
|||||||
} from '@standardnotes/domain-events'
|
} from '@standardnotes/domain-events'
|
||||||
import { TimerInterface, Timer } from '@standardnotes/time'
|
import { TimerInterface, Timer } from '@standardnotes/time'
|
||||||
import { UAParser } from 'ua-parser-js'
|
import { UAParser } from 'ua-parser-js'
|
||||||
import { AnalyticsStoreInterface, PeriodKeyGenerator, RedisAnalyticsStore } from '@standardnotes/analytics'
|
import {
|
||||||
|
AnalyticsStoreInterface,
|
||||||
|
PeriodKeyGenerator,
|
||||||
|
RedisAnalyticsStore,
|
||||||
|
RedisStatisticsStore,
|
||||||
|
StatisticsStoreInterface,
|
||||||
|
} from '@standardnotes/analytics'
|
||||||
|
|
||||||
import { Env } from './Env'
|
import { Env } from './Env'
|
||||||
import TYPES from './Types'
|
import TYPES from './Types'
|
||||||
@@ -124,13 +130,19 @@ import { RedisOfflineSubscriptionTokenRepository } from '../Infra/Redis/RedisOff
|
|||||||
import { CreateOfflineSubscriptionToken } from '../Domain/UseCase/CreateOfflineSubscriptionToken/CreateOfflineSubscriptionToken'
|
import { CreateOfflineSubscriptionToken } from '../Domain/UseCase/CreateOfflineSubscriptionToken/CreateOfflineSubscriptionToken'
|
||||||
import { AuthenticateOfflineSubscriptionToken } from '../Domain/UseCase/AuthenticateOfflineSubscriptionToken/AuthenticateOfflineSubscriptionToken'
|
import { AuthenticateOfflineSubscriptionToken } from '../Domain/UseCase/AuthenticateOfflineSubscriptionToken/AuthenticateOfflineSubscriptionToken'
|
||||||
import { SubscriptionCancelledEventHandler } from '../Domain/Handler/SubscriptionCancelledEventHandler'
|
import { SubscriptionCancelledEventHandler } from '../Domain/Handler/SubscriptionCancelledEventHandler'
|
||||||
import { ContentDecoder, ContentDecoderInterface, ProtocolVersion } from '@standardnotes/common'
|
import {
|
||||||
|
ContentDecoder,
|
||||||
|
ContentDecoderInterface,
|
||||||
|
ProtocolVersion,
|
||||||
|
Uuid,
|
||||||
|
UuidValidator,
|
||||||
|
ValidatorInterface,
|
||||||
|
} from '@standardnotes/common'
|
||||||
import { GetUserOfflineSubscription } from '../Domain/UseCase/GetUserOfflineSubscription/GetUserOfflineSubscription'
|
import { GetUserOfflineSubscription } from '../Domain/UseCase/GetUserOfflineSubscription/GetUserOfflineSubscription'
|
||||||
import { ApiGatewayOfflineAuthMiddleware } from '../Controller/ApiGatewayOfflineAuthMiddleware'
|
import { ApiGatewayOfflineAuthMiddleware } from '../Controller/ApiGatewayOfflineAuthMiddleware'
|
||||||
import { UserEmailChangedEventHandler } from '../Domain/Handler/UserEmailChangedEventHandler'
|
import { UserEmailChangedEventHandler } from '../Domain/Handler/UserEmailChangedEventHandler'
|
||||||
import { SettingsAssociationServiceInterface } from '../Domain/Setting/SettingsAssociationServiceInterface'
|
import { SettingsAssociationServiceInterface } from '../Domain/Setting/SettingsAssociationServiceInterface'
|
||||||
import { SettingsAssociationService } from '../Domain/Setting/SettingsAssociationService'
|
import { SettingsAssociationService } from '../Domain/Setting/SettingsAssociationService'
|
||||||
import { MuteFailedBackupsEmails } from '../Domain/UseCase/MuteFailedBackupsEmails/MuteFailedBackupsEmails'
|
|
||||||
import { SubscriptionSyncRequestedEventHandler } from '../Domain/Handler/SubscriptionSyncRequestedEventHandler'
|
import { SubscriptionSyncRequestedEventHandler } from '../Domain/Handler/SubscriptionSyncRequestedEventHandler'
|
||||||
import {
|
import {
|
||||||
CrossServiceTokenData,
|
CrossServiceTokenData,
|
||||||
@@ -143,13 +155,13 @@ import {
|
|||||||
TokenEncoder,
|
TokenEncoder,
|
||||||
TokenEncoderInterface,
|
TokenEncoderInterface,
|
||||||
ValetTokenData,
|
ValetTokenData,
|
||||||
|
WebSocketConnectionTokenData,
|
||||||
} from '@standardnotes/security'
|
} from '@standardnotes/security'
|
||||||
import { FileUploadedEventHandler } from '../Domain/Handler/FileUploadedEventHandler'
|
import { FileUploadedEventHandler } from '../Domain/Handler/FileUploadedEventHandler'
|
||||||
import { CreateValetToken } from '../Domain/UseCase/CreateValetToken/CreateValetToken'
|
import { CreateValetToken } from '../Domain/UseCase/CreateValetToken/CreateValetToken'
|
||||||
import { CreateListedAccount } from '../Domain/UseCase/CreateListedAccount/CreateListedAccount'
|
import { CreateListedAccount } from '../Domain/UseCase/CreateListedAccount/CreateListedAccount'
|
||||||
import { ListedAccountCreatedEventHandler } from '../Domain/Handler/ListedAccountCreatedEventHandler'
|
import { ListedAccountCreatedEventHandler } from '../Domain/Handler/ListedAccountCreatedEventHandler'
|
||||||
import { ListedAccountDeletedEventHandler } from '../Domain/Handler/ListedAccountDeletedEventHandler'
|
import { ListedAccountDeletedEventHandler } from '../Domain/Handler/ListedAccountDeletedEventHandler'
|
||||||
import { MuteSignInEmails } from '../Domain/UseCase/MuteSignInEmails/MuteSignInEmails'
|
|
||||||
import { FileRemovedEventHandler } from '../Domain/Handler/FileRemovedEventHandler'
|
import { FileRemovedEventHandler } from '../Domain/Handler/FileRemovedEventHandler'
|
||||||
import { UserDisabledSessionUserAgentLoggingEventHandler } from '../Domain/Handler/UserDisabledSessionUserAgentLoggingEventHandler'
|
import { UserDisabledSessionUserAgentLoggingEventHandler } from '../Domain/Handler/UserDisabledSessionUserAgentLoggingEventHandler'
|
||||||
import { SettingInterpreterInterface } from '../Domain/Setting/SettingInterpreterInterface'
|
import { SettingInterpreterInterface } from '../Domain/Setting/SettingInterpreterInterface'
|
||||||
@@ -190,9 +202,14 @@ import { GetUserAnalyticsId } from '../Domain/UseCase/GetUserAnalyticsId/GetUser
|
|||||||
import { AuthController } from '../Controller/AuthController'
|
import { AuthController } from '../Controller/AuthController'
|
||||||
import { VerifyPredicate } from '../Domain/UseCase/VerifyPredicate/VerifyPredicate'
|
import { VerifyPredicate } from '../Domain/UseCase/VerifyPredicate/VerifyPredicate'
|
||||||
import { PredicateVerificationRequestedEventHandler } from '../Domain/Handler/PredicateVerificationRequestedEventHandler'
|
import { PredicateVerificationRequestedEventHandler } from '../Domain/Handler/PredicateVerificationRequestedEventHandler'
|
||||||
import { MuteMarketingEmails } from '../Domain/UseCase/MuteMarketingEmails/MuteMarketingEmails'
|
|
||||||
import { PaymentFailedEventHandler } from '../Domain/Handler/PaymentFailedEventHandler'
|
import { PaymentFailedEventHandler } from '../Domain/Handler/PaymentFailedEventHandler'
|
||||||
import { PaymentSuccessEventHandler } from '../Domain/Handler/PaymentSuccessEventHandler'
|
import { PaymentSuccessEventHandler } from '../Domain/Handler/PaymentSuccessEventHandler'
|
||||||
|
import { RefundProcessedEventHandler } from '../Domain/Handler/RefundProcessedEventHandler'
|
||||||
|
import { SubscriptionInvitesController } from '../Controller/SubscriptionInvitesController'
|
||||||
|
import { CreateWebSocketConnectionToken } from '../Domain/UseCase/CreateWebSocketConnectionToken/CreateWebSocketConnectionToken'
|
||||||
|
import { WebSocketsController } from '../Controller/WebSocketsController'
|
||||||
|
import { WebSocketServerInterface } from '@standardnotes/api'
|
||||||
|
import { CreateCrossServiceToken } from '../Domain/UseCase/CreateCrossServiceToken/CreateCrossServiceToken'
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
const newrelicFormatter = require('@newrelic/winston-enricher')
|
const newrelicFormatter = require('@newrelic/winston-enricher')
|
||||||
@@ -255,6 +272,8 @@ export class ContainerConfigLoader {
|
|||||||
|
|
||||||
// Controller
|
// Controller
|
||||||
container.bind<AuthController>(TYPES.AuthController).to(AuthController)
|
container.bind<AuthController>(TYPES.AuthController).to(AuthController)
|
||||||
|
container.bind<SubscriptionInvitesController>(TYPES.SubscriptionInvitesController).to(SubscriptionInvitesController)
|
||||||
|
container.bind<WebSocketServerInterface>(TYPES.WebSocketsController).to(WebSocketsController)
|
||||||
|
|
||||||
// Repositories
|
// Repositories
|
||||||
container.bind<SessionRepositoryInterface>(TYPES.SessionRepository).to(MySQLSessionRepository)
|
container.bind<SessionRepositoryInterface>(TYPES.SessionRepository).to(MySQLSessionRepository)
|
||||||
@@ -353,6 +372,12 @@ export class ContainerConfigLoader {
|
|||||||
container.bind(TYPES.AUTH_JWT_TTL).toConstantValue(+env.get('AUTH_JWT_TTL'))
|
container.bind(TYPES.AUTH_JWT_TTL).toConstantValue(+env.get('AUTH_JWT_TTL'))
|
||||||
container.bind(TYPES.VALET_TOKEN_SECRET).toConstantValue(env.get('VALET_TOKEN_SECRET', true))
|
container.bind(TYPES.VALET_TOKEN_SECRET).toConstantValue(env.get('VALET_TOKEN_SECRET', true))
|
||||||
container.bind(TYPES.VALET_TOKEN_TTL).toConstantValue(+env.get('VALET_TOKEN_TTL', true))
|
container.bind(TYPES.VALET_TOKEN_TTL).toConstantValue(+env.get('VALET_TOKEN_TTL', true))
|
||||||
|
container
|
||||||
|
.bind(TYPES.WEB_SOCKET_CONNECTION_TOKEN_SECRET)
|
||||||
|
.toConstantValue(env.get('WEB_SOCKET_CONNECTION_TOKEN_SECRET', true))
|
||||||
|
container
|
||||||
|
.bind(TYPES.WEB_SOCKET_CONNECTION_TOKEN_TTL)
|
||||||
|
.toConstantValue(+env.get('WEB_SOCKET_CONNECTION_TOKEN_TTL', true))
|
||||||
container.bind(TYPES.ENCRYPTION_SERVER_KEY).toConstantValue(env.get('ENCRYPTION_SERVER_KEY'))
|
container.bind(TYPES.ENCRYPTION_SERVER_KEY).toConstantValue(env.get('ENCRYPTION_SERVER_KEY'))
|
||||||
container.bind(TYPES.ACCESS_TOKEN_AGE).toConstantValue(env.get('ACCESS_TOKEN_AGE'))
|
container.bind(TYPES.ACCESS_TOKEN_AGE).toConstantValue(env.get('ACCESS_TOKEN_AGE'))
|
||||||
container.bind(TYPES.REFRESH_TOKEN_AGE).toConstantValue(env.get('REFRESH_TOKEN_AGE'))
|
container.bind(TYPES.REFRESH_TOKEN_AGE).toConstantValue(env.get('REFRESH_TOKEN_AGE'))
|
||||||
@@ -411,9 +436,6 @@ export class ContainerConfigLoader {
|
|||||||
container
|
container
|
||||||
.bind<CreateOfflineSubscriptionToken>(TYPES.CreateOfflineSubscriptionToken)
|
.bind<CreateOfflineSubscriptionToken>(TYPES.CreateOfflineSubscriptionToken)
|
||||||
.to(CreateOfflineSubscriptionToken)
|
.to(CreateOfflineSubscriptionToken)
|
||||||
container.bind<MuteFailedBackupsEmails>(TYPES.MuteFailedBackupsEmails).to(MuteFailedBackupsEmails)
|
|
||||||
container.bind<MuteSignInEmails>(TYPES.MuteSignInEmails).to(MuteSignInEmails)
|
|
||||||
container.bind<MuteMarketingEmails>(TYPES.MuteMarketingEmails).to(MuteMarketingEmails)
|
|
||||||
container.bind<CreateValetToken>(TYPES.CreateValetToken).to(CreateValetToken)
|
container.bind<CreateValetToken>(TYPES.CreateValetToken).to(CreateValetToken)
|
||||||
container.bind<CreateListedAccount>(TYPES.CreateListedAccount).to(CreateListedAccount)
|
container.bind<CreateListedAccount>(TYPES.CreateListedAccount).to(CreateListedAccount)
|
||||||
container.bind<InviteToSharedSubscription>(TYPES.InviteToSharedSubscription).to(InviteToSharedSubscription)
|
container.bind<InviteToSharedSubscription>(TYPES.InviteToSharedSubscription).to(InviteToSharedSubscription)
|
||||||
@@ -432,6 +454,10 @@ export class ContainerConfigLoader {
|
|||||||
container.bind<GetSubscriptionSetting>(TYPES.GetSubscriptionSetting).to(GetSubscriptionSetting)
|
container.bind<GetSubscriptionSetting>(TYPES.GetSubscriptionSetting).to(GetSubscriptionSetting)
|
||||||
container.bind<GetUserAnalyticsId>(TYPES.GetUserAnalyticsId).to(GetUserAnalyticsId)
|
container.bind<GetUserAnalyticsId>(TYPES.GetUserAnalyticsId).to(GetUserAnalyticsId)
|
||||||
container.bind<VerifyPredicate>(TYPES.VerifyPredicate).to(VerifyPredicate)
|
container.bind<VerifyPredicate>(TYPES.VerifyPredicate).to(VerifyPredicate)
|
||||||
|
container
|
||||||
|
.bind<CreateWebSocketConnectionToken>(TYPES.CreateWebSocketConnectionToken)
|
||||||
|
.to(CreateWebSocketConnectionToken)
|
||||||
|
container.bind<CreateCrossServiceToken>(TYPES.CreateCrossServiceToken).to(CreateCrossServiceToken)
|
||||||
|
|
||||||
// Handlers
|
// Handlers
|
||||||
container.bind<UserRegisteredEventHandler>(TYPES.UserRegisteredEventHandler).to(UserRegisteredEventHandler)
|
container.bind<UserRegisteredEventHandler>(TYPES.UserRegisteredEventHandler).to(UserRegisteredEventHandler)
|
||||||
@@ -482,6 +508,7 @@ export class ContainerConfigLoader {
|
|||||||
.to(PredicateVerificationRequestedEventHandler)
|
.to(PredicateVerificationRequestedEventHandler)
|
||||||
container.bind<PaymentFailedEventHandler>(TYPES.PaymentFailedEventHandler).to(PaymentFailedEventHandler)
|
container.bind<PaymentFailedEventHandler>(TYPES.PaymentFailedEventHandler).to(PaymentFailedEventHandler)
|
||||||
container.bind<PaymentSuccessEventHandler>(TYPES.PaymentSuccessEventHandler).to(PaymentSuccessEventHandler)
|
container.bind<PaymentSuccessEventHandler>(TYPES.PaymentSuccessEventHandler).to(PaymentSuccessEventHandler)
|
||||||
|
container.bind<RefundProcessedEventHandler>(TYPES.RefundProcessedEventHandler).to(RefundProcessedEventHandler)
|
||||||
|
|
||||||
// Services
|
// Services
|
||||||
container.bind<UAParser>(TYPES.DeviceDetector).toConstantValue(new UAParser())
|
container.bind<UAParser>(TYPES.DeviceDetector).toConstantValue(new UAParser())
|
||||||
@@ -503,6 +530,11 @@ export class ContainerConfigLoader {
|
|||||||
container
|
container
|
||||||
.bind<TokenDecoderInterface<OfflineUserTokenData>>(TYPES.OfflineUserTokenDecoder)
|
.bind<TokenDecoderInterface<OfflineUserTokenData>>(TYPES.OfflineUserTokenDecoder)
|
||||||
.toConstantValue(new TokenDecoder<OfflineUserTokenData>(container.get(TYPES.AUTH_JWT_SECRET)))
|
.toConstantValue(new TokenDecoder<OfflineUserTokenData>(container.get(TYPES.AUTH_JWT_SECRET)))
|
||||||
|
container
|
||||||
|
.bind<TokenDecoderInterface<WebSocketConnectionTokenData>>(TYPES.WebSocketConnectionTokenDecoder)
|
||||||
|
.toConstantValue(
|
||||||
|
new TokenDecoder<WebSocketConnectionTokenData>(container.get(TYPES.WEB_SOCKET_CONNECTION_TOKEN_SECRET)),
|
||||||
|
)
|
||||||
container
|
container
|
||||||
.bind<TokenEncoderInterface<OfflineUserTokenData>>(TYPES.OfflineUserTokenEncoder)
|
.bind<TokenEncoderInterface<OfflineUserTokenData>>(TYPES.OfflineUserTokenEncoder)
|
||||||
.toConstantValue(new TokenEncoder<OfflineUserTokenData>(container.get(TYPES.AUTH_JWT_SECRET)))
|
.toConstantValue(new TokenEncoder<OfflineUserTokenData>(container.get(TYPES.AUTH_JWT_SECRET)))
|
||||||
@@ -515,6 +547,11 @@ export class ContainerConfigLoader {
|
|||||||
container
|
container
|
||||||
.bind<TokenEncoderInterface<ValetTokenData>>(TYPES.ValetTokenEncoder)
|
.bind<TokenEncoderInterface<ValetTokenData>>(TYPES.ValetTokenEncoder)
|
||||||
.toConstantValue(new TokenEncoder<ValetTokenData>(container.get(TYPES.VALET_TOKEN_SECRET)))
|
.toConstantValue(new TokenEncoder<ValetTokenData>(container.get(TYPES.VALET_TOKEN_SECRET)))
|
||||||
|
container
|
||||||
|
.bind<TokenEncoderInterface<WebSocketConnectionTokenData>>(TYPES.WebSocketConnectionTokenEncoder)
|
||||||
|
.toConstantValue(
|
||||||
|
new TokenEncoder<WebSocketConnectionTokenData>(container.get(TYPES.WEB_SOCKET_CONNECTION_TOKEN_SECRET)),
|
||||||
|
)
|
||||||
container.bind<AuthenticationMethodResolver>(TYPES.AuthenticationMethodResolver).to(AuthenticationMethodResolver)
|
container.bind<AuthenticationMethodResolver>(TYPES.AuthenticationMethodResolver).to(AuthenticationMethodResolver)
|
||||||
container.bind<DomainEventFactory>(TYPES.DomainEventFactory).to(DomainEventFactory)
|
container.bind<DomainEventFactory>(TYPES.DomainEventFactory).to(DomainEventFactory)
|
||||||
container.bind<AxiosInstance>(TYPES.HTTPClient).toConstantValue(axios.create())
|
container.bind<AxiosInstance>(TYPES.HTTPClient).toConstantValue(axios.create())
|
||||||
@@ -542,9 +579,14 @@ export class ContainerConfigLoader {
|
|||||||
.bind<SelectorInterface<boolean>>(TYPES.BooleanSelector)
|
.bind<SelectorInterface<boolean>>(TYPES.BooleanSelector)
|
||||||
.toConstantValue(new DeterministicSelector<boolean>())
|
.toConstantValue(new DeterministicSelector<boolean>())
|
||||||
container.bind<UserSubscriptionServiceInterface>(TYPES.UserSubscriptionService).to(UserSubscriptionService)
|
container.bind<UserSubscriptionServiceInterface>(TYPES.UserSubscriptionService).to(UserSubscriptionService)
|
||||||
|
const periodKeyGenerator = new PeriodKeyGenerator()
|
||||||
container
|
container
|
||||||
.bind<AnalyticsStoreInterface>(TYPES.AnalyticsStore)
|
.bind<AnalyticsStoreInterface>(TYPES.AnalyticsStore)
|
||||||
.toConstantValue(new RedisAnalyticsStore(new PeriodKeyGenerator(), container.get(TYPES.Redis)))
|
.toConstantValue(new RedisAnalyticsStore(periodKeyGenerator, container.get(TYPES.Redis)))
|
||||||
|
container
|
||||||
|
.bind<StatisticsStoreInterface>(TYPES.StatisticsStore)
|
||||||
|
.toConstantValue(new RedisStatisticsStore(periodKeyGenerator, container.get(TYPES.Redis)))
|
||||||
|
container.bind<ValidatorInterface<Uuid>>(TYPES.UuidValidator).toConstantValue(new UuidValidator())
|
||||||
|
|
||||||
if (env.get('SNS_TOPIC_ARN', true)) {
|
if (env.get('SNS_TOPIC_ARN', true)) {
|
||||||
container
|
container
|
||||||
@@ -582,6 +624,7 @@ export class ContainerConfigLoader {
|
|||||||
['PREDICATE_VERIFICATION_REQUESTED', container.get(TYPES.PredicateVerificationRequestedEventHandler)],
|
['PREDICATE_VERIFICATION_REQUESTED', container.get(TYPES.PredicateVerificationRequestedEventHandler)],
|
||||||
['PAYMENT_FAILED', container.get(TYPES.PaymentFailedEventHandler)],
|
['PAYMENT_FAILED', container.get(TYPES.PaymentFailedEventHandler)],
|
||||||
['PAYMENT_SUCCESS', container.get(TYPES.PaymentSuccessEventHandler)],
|
['PAYMENT_SUCCESS', container.get(TYPES.PaymentSuccessEventHandler)],
|
||||||
|
['REFUND_PROCESSED', container.get(TYPES.RefundProcessedEventHandler)],
|
||||||
])
|
])
|
||||||
|
|
||||||
if (env.get('SQS_QUEUE_URL', true)) {
|
if (env.get('SQS_QUEUE_URL', true)) {
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ const TYPES = {
|
|||||||
SQS: Symbol.for('SQS'),
|
SQS: Symbol.for('SQS'),
|
||||||
// Controller
|
// Controller
|
||||||
AuthController: Symbol.for('AuthController'),
|
AuthController: Symbol.for('AuthController'),
|
||||||
|
SubscriptionInvitesController: Symbol.for('SubscriptionInvitesController'),
|
||||||
|
WebSocketsController: Symbol.for('WebSocketsController'),
|
||||||
// Repositories
|
// Repositories
|
||||||
UserRepository: Symbol.for('UserRepository'),
|
UserRepository: Symbol.for('UserRepository'),
|
||||||
SessionRepository: Symbol.for('SessionRepository'),
|
SessionRepository: Symbol.for('SessionRepository'),
|
||||||
@@ -59,6 +61,8 @@ const TYPES = {
|
|||||||
AUTH_JWT_TTL: Symbol.for('AUTH_JWT_TTL'),
|
AUTH_JWT_TTL: Symbol.for('AUTH_JWT_TTL'),
|
||||||
VALET_TOKEN_SECRET: Symbol.for('VALET_TOKEN_SECRET'),
|
VALET_TOKEN_SECRET: Symbol.for('VALET_TOKEN_SECRET'),
|
||||||
VALET_TOKEN_TTL: Symbol.for('VALET_TOKEN_TTL'),
|
VALET_TOKEN_TTL: Symbol.for('VALET_TOKEN_TTL'),
|
||||||
|
WEB_SOCKET_CONNECTION_TOKEN_SECRET: Symbol.for('WEB_SOCKET_CONNECTION_TOKEN_SECRET'),
|
||||||
|
WEB_SOCKET_CONNECTION_TOKEN_TTL: Symbol.for('WEB_SOCKET_CONNECTION_TOKEN_TTL'),
|
||||||
ENCRYPTION_SERVER_KEY: Symbol.for('ENCRYPTION_SERVER_KEY'),
|
ENCRYPTION_SERVER_KEY: Symbol.for('ENCRYPTION_SERVER_KEY'),
|
||||||
ACCESS_TOKEN_AGE: Symbol.for('ACCESS_TOKEN_AGE'),
|
ACCESS_TOKEN_AGE: Symbol.for('ACCESS_TOKEN_AGE'),
|
||||||
REFRESH_TOKEN_AGE: Symbol.for('REFRESH_TOKEN_AGE'),
|
REFRESH_TOKEN_AGE: Symbol.for('REFRESH_TOKEN_AGE'),
|
||||||
@@ -111,9 +115,6 @@ const TYPES = {
|
|||||||
AuthenticateSubscriptionToken: Symbol.for('AuthenticateSubscriptionToken'),
|
AuthenticateSubscriptionToken: Symbol.for('AuthenticateSubscriptionToken'),
|
||||||
CreateOfflineSubscriptionToken: Symbol.for('CreateOfflineSubscriptionToken'),
|
CreateOfflineSubscriptionToken: Symbol.for('CreateOfflineSubscriptionToken'),
|
||||||
AuthenticateOfflineSubscriptionToken: Symbol.for('AuthenticateOfflineSubscriptionToken'),
|
AuthenticateOfflineSubscriptionToken: Symbol.for('AuthenticateOfflineSubscriptionToken'),
|
||||||
MuteFailedBackupsEmails: Symbol.for('MuteFailedBackupsEmails'),
|
|
||||||
MuteSignInEmails: Symbol.for('MuteSignInEmails'),
|
|
||||||
MuteMarketingEmails: Symbol.for('MuteMarketingEmails'),
|
|
||||||
CreateValetToken: Symbol.for('CreateValetToken'),
|
CreateValetToken: Symbol.for('CreateValetToken'),
|
||||||
CreateListedAccount: Symbol.for('CreateListedAccount'),
|
CreateListedAccount: Symbol.for('CreateListedAccount'),
|
||||||
InviteToSharedSubscription: Symbol.for('InviteToSharedSubscription'),
|
InviteToSharedSubscription: Symbol.for('InviteToSharedSubscription'),
|
||||||
@@ -124,6 +125,8 @@ const TYPES = {
|
|||||||
GetSubscriptionSetting: Symbol.for('GetSubscriptionSetting'),
|
GetSubscriptionSetting: Symbol.for('GetSubscriptionSetting'),
|
||||||
GetUserAnalyticsId: Symbol.for('GetUserAnalyticsId'),
|
GetUserAnalyticsId: Symbol.for('GetUserAnalyticsId'),
|
||||||
VerifyPredicate: Symbol.for('VerifyPredicate'),
|
VerifyPredicate: Symbol.for('VerifyPredicate'),
|
||||||
|
CreateWebSocketConnectionToken: Symbol.for('CreateWebSocketConnectionToken'),
|
||||||
|
CreateCrossServiceToken: Symbol.for('CreateCrossServiceToken'),
|
||||||
// Handlers
|
// Handlers
|
||||||
UserRegisteredEventHandler: Symbol.for('UserRegisteredEventHandler'),
|
UserRegisteredEventHandler: Symbol.for('UserRegisteredEventHandler'),
|
||||||
AccountDeletionRequestedEventHandler: Symbol.for('AccountDeletionRequestedEventHandler'),
|
AccountDeletionRequestedEventHandler: Symbol.for('AccountDeletionRequestedEventHandler'),
|
||||||
@@ -145,6 +148,7 @@ const TYPES = {
|
|||||||
PredicateVerificationRequestedEventHandler: Symbol.for('PredicateVerificationRequestedEventHandler'),
|
PredicateVerificationRequestedEventHandler: Symbol.for('PredicateVerificationRequestedEventHandler'),
|
||||||
PaymentFailedEventHandler: Symbol.for('PaymentFailedEventHandler'),
|
PaymentFailedEventHandler: Symbol.for('PaymentFailedEventHandler'),
|
||||||
PaymentSuccessEventHandler: Symbol.for('PaymentSuccessEventHandler'),
|
PaymentSuccessEventHandler: Symbol.for('PaymentSuccessEventHandler'),
|
||||||
|
RefundProcessedEventHandler: Symbol.for('RefundProcessedEventHandler'),
|
||||||
// Services
|
// Services
|
||||||
DeviceDetector: Symbol.for('DeviceDetector'),
|
DeviceDetector: Symbol.for('DeviceDetector'),
|
||||||
SessionService: Symbol.for('SessionService'),
|
SessionService: Symbol.for('SessionService'),
|
||||||
@@ -164,6 +168,8 @@ const TYPES = {
|
|||||||
CrossServiceTokenEncoder: Symbol.for('CrossServiceTokenEncoder'),
|
CrossServiceTokenEncoder: Symbol.for('CrossServiceTokenEncoder'),
|
||||||
SessionTokenEncoder: Symbol.for('SessionTokenEncoder'),
|
SessionTokenEncoder: Symbol.for('SessionTokenEncoder'),
|
||||||
ValetTokenEncoder: Symbol.for('ValetTokenEncoder'),
|
ValetTokenEncoder: Symbol.for('ValetTokenEncoder'),
|
||||||
|
WebSocketConnectionTokenEncoder: Symbol.for('WebSocketConnectionTokenEncoder'),
|
||||||
|
WebSocketConnectionTokenDecoder: Symbol.for('WebSocketConnectionTokenDecoder'),
|
||||||
AuthenticationMethodResolver: Symbol.for('AuthenticationMethodResolver'),
|
AuthenticationMethodResolver: Symbol.for('AuthenticationMethodResolver'),
|
||||||
DomainEventPublisher: Symbol.for('DomainEventPublisher'),
|
DomainEventPublisher: Symbol.for('DomainEventPublisher'),
|
||||||
DomainEventSubscriberFactory: Symbol.for('DomainEventSubscriberFactory'),
|
DomainEventSubscriberFactory: Symbol.for('DomainEventSubscriberFactory'),
|
||||||
@@ -186,6 +192,8 @@ const TYPES = {
|
|||||||
BooleanSelector: Symbol.for('BooleanSelector'),
|
BooleanSelector: Symbol.for('BooleanSelector'),
|
||||||
UserSubscriptionService: Symbol.for('UserSubscriptionService'),
|
UserSubscriptionService: Symbol.for('UserSubscriptionService'),
|
||||||
AnalyticsStore: Symbol.for('AnalyticsStore'),
|
AnalyticsStore: Symbol.for('AnalyticsStore'),
|
||||||
|
StatisticsStore: Symbol.for('StatisticsStore'),
|
||||||
|
UuidValidator: Symbol.for('UuidValidator'),
|
||||||
}
|
}
|
||||||
|
|
||||||
export default TYPES
|
export default TYPES
|
||||||
|
|||||||
@@ -7,23 +7,16 @@ import { results } from 'inversify-express-utils'
|
|||||||
import { User } from '../Domain/User/User'
|
import { User } from '../Domain/User/User'
|
||||||
import { GetUserFeatures } from '../Domain/UseCase/GetUserFeatures/GetUserFeatures'
|
import { GetUserFeatures } from '../Domain/UseCase/GetUserFeatures/GetUserFeatures'
|
||||||
import { GetSetting } from '../Domain/UseCase/GetSetting/GetSetting'
|
import { GetSetting } from '../Domain/UseCase/GetSetting/GetSetting'
|
||||||
import { MuteFailedBackupsEmails } from '../Domain/UseCase/MuteFailedBackupsEmails/MuteFailedBackupsEmails'
|
|
||||||
import { MuteSignInEmails } from '../Domain/UseCase/MuteSignInEmails/MuteSignInEmails'
|
|
||||||
import { MuteMarketingEmails } from '../Domain/UseCase/MuteMarketingEmails/MuteMarketingEmails'
|
|
||||||
|
|
||||||
describe('InternalController', () => {
|
describe('InternalController', () => {
|
||||||
let getUserFeatures: GetUserFeatures
|
let getUserFeatures: GetUserFeatures
|
||||||
let getSetting: GetSetting
|
let getSetting: GetSetting
|
||||||
let muteFailedBackupsEmails: MuteFailedBackupsEmails
|
|
||||||
let muteSignInEmails: MuteSignInEmails
|
|
||||||
let muteMarketingEmails: MuteMarketingEmails
|
|
||||||
|
|
||||||
let request: express.Request
|
let request: express.Request
|
||||||
let response: express.Response
|
let response: express.Response
|
||||||
let user: User
|
let user: User
|
||||||
|
|
||||||
const createController = () =>
|
const createController = () => new InternalController(getUserFeatures, getSetting)
|
||||||
new InternalController(getUserFeatures, getSetting, muteFailedBackupsEmails, muteSignInEmails, muteMarketingEmails)
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
user = {} as jest.Mocked<User>
|
user = {} as jest.Mocked<User>
|
||||||
@@ -35,15 +28,6 @@ describe('InternalController', () => {
|
|||||||
getSetting = {} as jest.Mocked<GetSetting>
|
getSetting = {} as jest.Mocked<GetSetting>
|
||||||
getSetting.execute = jest.fn()
|
getSetting.execute = jest.fn()
|
||||||
|
|
||||||
muteFailedBackupsEmails = {} as jest.Mocked<MuteFailedBackupsEmails>
|
|
||||||
muteFailedBackupsEmails.execute = jest.fn()
|
|
||||||
|
|
||||||
muteSignInEmails = {} as jest.Mocked<MuteSignInEmails>
|
|
||||||
muteSignInEmails.execute = jest.fn()
|
|
||||||
|
|
||||||
muteMarketingEmails = {} as jest.Mocked<MuteMarketingEmails>
|
|
||||||
muteMarketingEmails.execute = jest.fn()
|
|
||||||
|
|
||||||
request = {
|
request = {
|
||||||
headers: {},
|
headers: {},
|
||||||
body: {},
|
body: {},
|
||||||
@@ -120,83 +104,4 @@ describe('InternalController', () => {
|
|||||||
|
|
||||||
expect(result.statusCode).toEqual(400)
|
expect(result.statusCode).toEqual(400)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should mute failed backup emails user setting', async () => {
|
|
||||||
request.params.settingUuid = '1-2-3'
|
|
||||||
|
|
||||||
muteFailedBackupsEmails.execute = jest.fn().mockReturnValue({ success: true })
|
|
||||||
|
|
||||||
const httpResponse = <results.JsonResult>await createController().muteFailedBackupsEmails(request)
|
|
||||||
const result = await httpResponse.executeAsync()
|
|
||||||
|
|
||||||
expect(muteFailedBackupsEmails.execute).toHaveBeenCalledWith({ settingUuid: '1-2-3' })
|
|
||||||
|
|
||||||
expect(result.statusCode).toEqual(200)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should not mute failed backup emails user setting if it does not exist', async () => {
|
|
||||||
request.params.settingUuid = '1-2-3'
|
|
||||||
|
|
||||||
muteFailedBackupsEmails.execute = jest.fn().mockReturnValue({ success: false })
|
|
||||||
|
|
||||||
const httpResponse = <results.JsonResult>await createController().muteFailedBackupsEmails(request)
|
|
||||||
const result = await httpResponse.executeAsync()
|
|
||||||
|
|
||||||
expect(muteFailedBackupsEmails.execute).toHaveBeenCalledWith({ settingUuid: '1-2-3' })
|
|
||||||
|
|
||||||
expect(result.statusCode).toEqual(404)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should mute sign in emails user setting', async () => {
|
|
||||||
request.params.settingUuid = '1-2-3'
|
|
||||||
|
|
||||||
muteSignInEmails.execute = jest.fn().mockReturnValue({ success: true })
|
|
||||||
|
|
||||||
const httpResponse = <results.JsonResult>await createController().muteSignInEmails(request)
|
|
||||||
const result = await httpResponse.executeAsync()
|
|
||||||
|
|
||||||
expect(muteSignInEmails.execute).toHaveBeenCalledWith({ settingUuid: '1-2-3' })
|
|
||||||
|
|
||||||
expect(result.statusCode).toEqual(200)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should not mute sign in emails user setting if it does not exist', async () => {
|
|
||||||
request.params.settingUuid = '1-2-3'
|
|
||||||
|
|
||||||
muteSignInEmails.execute = jest.fn().mockReturnValue({ success: false })
|
|
||||||
|
|
||||||
const httpResponse = <results.JsonResult>await createController().muteSignInEmails(request)
|
|
||||||
const result = await httpResponse.executeAsync()
|
|
||||||
|
|
||||||
expect(muteSignInEmails.execute).toHaveBeenCalledWith({ settingUuid: '1-2-3' })
|
|
||||||
|
|
||||||
expect(result.statusCode).toEqual(404)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should mute marketing emails user setting', async () => {
|
|
||||||
request.params.settingUuid = '1-2-3'
|
|
||||||
|
|
||||||
muteMarketingEmails.execute = jest.fn().mockReturnValue({ success: true, message: 'foobar' })
|
|
||||||
|
|
||||||
await createController().muteMarketingEmails(request, response)
|
|
||||||
|
|
||||||
expect(muteMarketingEmails.execute).toHaveBeenCalledWith({ settingUuid: '1-2-3' })
|
|
||||||
|
|
||||||
expect(response.setHeader).toHaveBeenCalledWith('content-type', 'text/html')
|
|
||||||
expect(response.send).toHaveBeenCalledWith('foobar')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should not mute marketing emails user setting if it does not exist', async () => {
|
|
||||||
request.params.settingUuid = '1-2-3'
|
|
||||||
|
|
||||||
muteMarketingEmails.execute = jest.fn().mockReturnValue({ success: false, message: 'foobar' })
|
|
||||||
|
|
||||||
await createController().muteMarketingEmails(request, response)
|
|
||||||
|
|
||||||
expect(muteMarketingEmails.execute).toHaveBeenCalledWith({ settingUuid: '1-2-3' })
|
|
||||||
|
|
||||||
expect(response.setHeader).toHaveBeenCalledWith('content-type', 'text/html')
|
|
||||||
expect(response.status).toHaveBeenCalledWith(404)
|
|
||||||
expect(response.send).toHaveBeenCalledWith('foobar')
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Request, Response } from 'express'
|
import { Request } from 'express'
|
||||||
import { inject } from 'inversify'
|
import { inject } from 'inversify'
|
||||||
import {
|
import {
|
||||||
BaseHttpController,
|
BaseHttpController,
|
||||||
@@ -10,18 +10,12 @@ import {
|
|||||||
import TYPES from '../Bootstrap/Types'
|
import TYPES from '../Bootstrap/Types'
|
||||||
import { GetSetting } from '../Domain/UseCase/GetSetting/GetSetting'
|
import { GetSetting } from '../Domain/UseCase/GetSetting/GetSetting'
|
||||||
import { GetUserFeatures } from '../Domain/UseCase/GetUserFeatures/GetUserFeatures'
|
import { GetUserFeatures } from '../Domain/UseCase/GetUserFeatures/GetUserFeatures'
|
||||||
import { MuteFailedBackupsEmails } from '../Domain/UseCase/MuteFailedBackupsEmails/MuteFailedBackupsEmails'
|
|
||||||
import { MuteMarketingEmails } from '../Domain/UseCase/MuteMarketingEmails/MuteMarketingEmails'
|
|
||||||
import { MuteSignInEmails } from '../Domain/UseCase/MuteSignInEmails/MuteSignInEmails'
|
|
||||||
|
|
||||||
@controller('/internal')
|
@controller('/internal')
|
||||||
export class InternalController extends BaseHttpController {
|
export class InternalController extends BaseHttpController {
|
||||||
constructor(
|
constructor(
|
||||||
@inject(TYPES.GetUserFeatures) private doGetUserFeatures: GetUserFeatures,
|
@inject(TYPES.GetUserFeatures) private doGetUserFeatures: GetUserFeatures,
|
||||||
@inject(TYPES.GetSetting) private doGetSetting: GetSetting,
|
@inject(TYPES.GetSetting) private doGetSetting: GetSetting,
|
||||||
@inject(TYPES.MuteFailedBackupsEmails) private doMuteFailedBackupsEmails: MuteFailedBackupsEmails,
|
|
||||||
@inject(TYPES.MuteSignInEmails) private doMuteSignInEmails: MuteSignInEmails,
|
|
||||||
@inject(TYPES.MuteMarketingEmails) private doMuteMarketingEmails: MuteMarketingEmails,
|
|
||||||
) {
|
) {
|
||||||
super()
|
super()
|
||||||
}
|
}
|
||||||
@@ -54,50 +48,4 @@ export class InternalController extends BaseHttpController {
|
|||||||
|
|
||||||
return this.json(result, 400)
|
return this.json(result, 400)
|
||||||
}
|
}
|
||||||
|
|
||||||
@httpGet('/settings/email_backup/:settingUuid/mute')
|
|
||||||
async muteFailedBackupsEmails(request: Request): Promise<results.JsonResult> {
|
|
||||||
const { settingUuid } = request.params
|
|
||||||
const result = await this.doMuteFailedBackupsEmails.execute({
|
|
||||||
settingUuid,
|
|
||||||
})
|
|
||||||
|
|
||||||
if (result.success) {
|
|
||||||
return this.json({ message: result.message })
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.json({ message: result.message }, 404)
|
|
||||||
}
|
|
||||||
|
|
||||||
@httpGet('/settings/sign_in/:settingUuid/mute')
|
|
||||||
async muteSignInEmails(request: Request): Promise<results.JsonResult> {
|
|
||||||
const { settingUuid } = request.params
|
|
||||||
const result = await this.doMuteSignInEmails.execute({
|
|
||||||
settingUuid,
|
|
||||||
})
|
|
||||||
|
|
||||||
if (result.success) {
|
|
||||||
return this.json({ message: result.message })
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.json({ message: result.message }, 404)
|
|
||||||
}
|
|
||||||
|
|
||||||
@httpGet('/settings/marketing-emails/:settingUuid/mute')
|
|
||||||
async muteMarketingEmails(request: Request, response: Response): Promise<void> {
|
|
||||||
const { settingUuid } = request.params
|
|
||||||
const result = await this.doMuteMarketingEmails.execute({
|
|
||||||
settingUuid,
|
|
||||||
})
|
|
||||||
|
|
||||||
response.setHeader('content-type', 'text/html')
|
|
||||||
|
|
||||||
if (result.success) {
|
|
||||||
response.send(result.message)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
response.status(404).send(result.message)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,43 +9,25 @@ import { ProjectorInterface } from '../Projection/ProjectorInterface'
|
|||||||
import { GetActiveSessionsForUser } from '../Domain/UseCase/GetActiveSessionsForUser'
|
import { GetActiveSessionsForUser } from '../Domain/UseCase/GetActiveSessionsForUser'
|
||||||
import { AuthenticateRequest } from '../Domain/UseCase/AuthenticateRequest'
|
import { AuthenticateRequest } from '../Domain/UseCase/AuthenticateRequest'
|
||||||
import { User } from '../Domain/User/User'
|
import { User } from '../Domain/User/User'
|
||||||
import { Role } from '../Domain/Role/Role'
|
import { CreateCrossServiceToken } from '../Domain/UseCase/CreateCrossServiceToken/CreateCrossServiceToken'
|
||||||
import { CrossServiceTokenData, TokenEncoderInterface } from '@standardnotes/security'
|
|
||||||
import { GetUserAnalyticsId } from '../Domain/UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
|
|
||||||
|
|
||||||
describe('SessionsController', () => {
|
describe('SessionsController', () => {
|
||||||
let getActiveSessionsForUser: GetActiveSessionsForUser
|
let getActiveSessionsForUser: GetActiveSessionsForUser
|
||||||
let authenticateRequest: AuthenticateRequest
|
let authenticateRequest: AuthenticateRequest
|
||||||
let userProjector: ProjectorInterface<User>
|
|
||||||
let tokenEncoder: TokenEncoderInterface<CrossServiceTokenData>
|
|
||||||
const jwtTTL = 60
|
|
||||||
let sessionProjector: ProjectorInterface<Session>
|
let sessionProjector: ProjectorInterface<Session>
|
||||||
let roleProjector: ProjectorInterface<Role>
|
|
||||||
let session: Session
|
let session: Session
|
||||||
let request: express.Request
|
let request: express.Request
|
||||||
let response: express.Response
|
let response: express.Response
|
||||||
let user: User
|
let user: User
|
||||||
let role: Role
|
let createCrossServiceToken: CreateCrossServiceToken
|
||||||
let getUserAnalyticsId: GetUserAnalyticsId
|
|
||||||
|
|
||||||
const createController = () =>
|
const createController = () =>
|
||||||
new SessionsController(
|
new SessionsController(getActiveSessionsForUser, authenticateRequest, sessionProjector, createCrossServiceToken)
|
||||||
getActiveSessionsForUser,
|
|
||||||
authenticateRequest,
|
|
||||||
userProjector,
|
|
||||||
sessionProjector,
|
|
||||||
roleProjector,
|
|
||||||
tokenEncoder,
|
|
||||||
getUserAnalyticsId,
|
|
||||||
true,
|
|
||||||
jwtTTL,
|
|
||||||
)
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
session = {} as jest.Mocked<Session>
|
session = {} as jest.Mocked<Session>
|
||||||
|
|
||||||
user = {} as jest.Mocked<User>
|
user = {} as jest.Mocked<User>
|
||||||
user.roles = Promise.resolve([role])
|
|
||||||
|
|
||||||
getActiveSessionsForUser = {} as jest.Mocked<GetActiveSessionsForUser>
|
getActiveSessionsForUser = {} as jest.Mocked<GetActiveSessionsForUser>
|
||||||
getActiveSessionsForUser.execute = jest.fn().mockReturnValue({ sessions: [session] })
|
getActiveSessionsForUser.execute = jest.fn().mockReturnValue({ sessions: [session] })
|
||||||
@@ -53,21 +35,11 @@ describe('SessionsController', () => {
|
|||||||
authenticateRequest = {} as jest.Mocked<AuthenticateRequest>
|
authenticateRequest = {} as jest.Mocked<AuthenticateRequest>
|
||||||
authenticateRequest.execute = jest.fn()
|
authenticateRequest.execute = jest.fn()
|
||||||
|
|
||||||
userProjector = {} as jest.Mocked<ProjectorInterface<User>>
|
|
||||||
userProjector.projectSimple = jest.fn().mockReturnValue({ bar: 'baz' })
|
|
||||||
|
|
||||||
roleProjector = {} as jest.Mocked<ProjectorInterface<Role>>
|
|
||||||
roleProjector.projectSimple = jest.fn().mockReturnValue({ name: 'role1', uuid: '1-3-4' })
|
|
||||||
|
|
||||||
sessionProjector = {} as jest.Mocked<ProjectorInterface<Session>>
|
sessionProjector = {} as jest.Mocked<ProjectorInterface<Session>>
|
||||||
sessionProjector.projectCustom = jest.fn().mockReturnValue({ foo: 'bar' })
|
sessionProjector.projectCustom = jest.fn().mockReturnValue({ foo: 'bar' })
|
||||||
sessionProjector.projectSimple = jest.fn().mockReturnValue({ test: 'test' })
|
|
||||||
|
|
||||||
tokenEncoder = {} as jest.Mocked<TokenEncoderInterface<CrossServiceTokenData>>
|
createCrossServiceToken = {} as jest.Mocked<CreateCrossServiceToken>
|
||||||
tokenEncoder.encodeExpirableToken = jest.fn().mockReturnValue('foobar')
|
createCrossServiceToken.execute = jest.fn().mockReturnValue({ token: 'foobar' })
|
||||||
|
|
||||||
getUserAnalyticsId = {} as jest.Mocked<GetUserAnalyticsId>
|
|
||||||
getUserAnalyticsId.execute = jest.fn().mockReturnValue({ analyticsId: 123 })
|
|
||||||
|
|
||||||
request = {
|
request = {
|
||||||
params: {},
|
params: {},
|
||||||
@@ -114,75 +86,6 @@ describe('SessionsController', () => {
|
|||||||
const httpResponseContent = await result.content.readAsStringAsync()
|
const httpResponseContent = await result.content.readAsStringAsync()
|
||||||
const httpResponseJSON = JSON.parse(httpResponseContent)
|
const httpResponseJSON = JSON.parse(httpResponseContent)
|
||||||
|
|
||||||
expect(tokenEncoder.encodeExpirableToken).toHaveBeenCalledWith(
|
|
||||||
{
|
|
||||||
analyticsId: 123,
|
|
||||||
roles: [
|
|
||||||
{
|
|
||||||
name: 'role1',
|
|
||||||
uuid: '1-3-4',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
session: {
|
|
||||||
test: 'test',
|
|
||||||
},
|
|
||||||
user: {
|
|
||||||
bar: 'baz',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
60,
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(httpResponseJSON.authToken).toEqual('foobar')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should validate a session from an incoming request - disabled analytics', async () => {
|
|
||||||
authenticateRequest.execute = jest.fn().mockReturnValue({
|
|
||||||
success: true,
|
|
||||||
user,
|
|
||||||
session,
|
|
||||||
})
|
|
||||||
|
|
||||||
request.headers.authorization = 'test'
|
|
||||||
|
|
||||||
const controller = new SessionsController(
|
|
||||||
getActiveSessionsForUser,
|
|
||||||
authenticateRequest,
|
|
||||||
userProjector,
|
|
||||||
sessionProjector,
|
|
||||||
roleProjector,
|
|
||||||
tokenEncoder,
|
|
||||||
getUserAnalyticsId,
|
|
||||||
false,
|
|
||||||
jwtTTL,
|
|
||||||
)
|
|
||||||
|
|
||||||
const httpResponse = await controller.validate(request)
|
|
||||||
|
|
||||||
expect(httpResponse).toBeInstanceOf(results.JsonResult)
|
|
||||||
|
|
||||||
const result = await httpResponse.executeAsync()
|
|
||||||
const httpResponseContent = await result.content.readAsStringAsync()
|
|
||||||
const httpResponseJSON = JSON.parse(httpResponseContent)
|
|
||||||
|
|
||||||
expect(tokenEncoder.encodeExpirableToken).toHaveBeenCalledWith(
|
|
||||||
{
|
|
||||||
roles: [
|
|
||||||
{
|
|
||||||
name: 'role1',
|
|
||||||
uuid: '1-3-4',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
session: {
|
|
||||||
test: 'test',
|
|
||||||
},
|
|
||||||
user: {
|
|
||||||
bar: 'baz',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
60,
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(httpResponseJSON.authToken).toEqual('foobar')
|
expect(httpResponseJSON.authToken).toEqual('foobar')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -12,26 +12,18 @@ import TYPES from '../Bootstrap/Types'
|
|||||||
import { Session } from '../Domain/Session/Session'
|
import { Session } from '../Domain/Session/Session'
|
||||||
import { AuthenticateRequest } from '../Domain/UseCase/AuthenticateRequest'
|
import { AuthenticateRequest } from '../Domain/UseCase/AuthenticateRequest'
|
||||||
import { GetActiveSessionsForUser } from '../Domain/UseCase/GetActiveSessionsForUser'
|
import { GetActiveSessionsForUser } from '../Domain/UseCase/GetActiveSessionsForUser'
|
||||||
import { Role } from '../Domain/Role/Role'
|
|
||||||
import { User } from '../Domain/User/User'
|
import { User } from '../Domain/User/User'
|
||||||
import { ProjectorInterface } from '../Projection/ProjectorInterface'
|
import { ProjectorInterface } from '../Projection/ProjectorInterface'
|
||||||
import { SessionProjector } from '../Projection/SessionProjector'
|
import { SessionProjector } from '../Projection/SessionProjector'
|
||||||
import { CrossServiceTokenData, TokenEncoderInterface } from '@standardnotes/security'
|
import { CreateCrossServiceToken } from '../Domain/UseCase/CreateCrossServiceToken/CreateCrossServiceToken'
|
||||||
import { RoleName } from '@standardnotes/common'
|
|
||||||
import { GetUserAnalyticsId } from '../Domain/UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
|
|
||||||
|
|
||||||
@controller('/sessions')
|
@controller('/sessions')
|
||||||
export class SessionsController extends BaseHttpController {
|
export class SessionsController extends BaseHttpController {
|
||||||
constructor(
|
constructor(
|
||||||
@inject(TYPES.GetActiveSessionsForUser) private getActiveSessionsForUser: GetActiveSessionsForUser,
|
@inject(TYPES.GetActiveSessionsForUser) private getActiveSessionsForUser: GetActiveSessionsForUser,
|
||||||
@inject(TYPES.AuthenticateRequest) private authenticateRequest: AuthenticateRequest,
|
@inject(TYPES.AuthenticateRequest) private authenticateRequest: AuthenticateRequest,
|
||||||
@inject(TYPES.UserProjector) private userProjector: ProjectorInterface<User>,
|
|
||||||
@inject(TYPES.SessionProjector) private sessionProjector: ProjectorInterface<Session>,
|
@inject(TYPES.SessionProjector) private sessionProjector: ProjectorInterface<Session>,
|
||||||
@inject(TYPES.RoleProjector) private roleProjector: ProjectorInterface<Role>,
|
@inject(TYPES.CreateCrossServiceToken) private createCrossServiceToken: CreateCrossServiceToken,
|
||||||
@inject(TYPES.CrossServiceTokenEncoder) private tokenEncoder: TokenEncoderInterface<CrossServiceTokenData>,
|
|
||||||
@inject(TYPES.GetUserAnalyticsId) private getUserAnalyticsId: GetUserAnalyticsId,
|
|
||||||
@inject(TYPES.ANALYTICS_ENABLED) private analyticsEnabled: boolean,
|
|
||||||
@inject(TYPES.AUTH_JWT_TTL) private jwtTTL: number,
|
|
||||||
) {
|
) {
|
||||||
super()
|
super()
|
||||||
}
|
}
|
||||||
@@ -56,25 +48,12 @@ export class SessionsController extends BaseHttpController {
|
|||||||
|
|
||||||
const user = authenticateRequestResponse.user as User
|
const user = authenticateRequestResponse.user as User
|
||||||
|
|
||||||
const roles = await user.roles
|
const result = await this.createCrossServiceToken.execute({
|
||||||
|
user,
|
||||||
|
session: authenticateRequestResponse.session,
|
||||||
|
})
|
||||||
|
|
||||||
const authTokenData: CrossServiceTokenData = {
|
return this.json({ authToken: result.token })
|
||||||
user: this.projectUser(user),
|
|
||||||
roles: this.projectRoles(roles),
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.analyticsEnabled) {
|
|
||||||
const { analyticsId } = await this.getUserAnalyticsId.execute({ userUuid: user.uuid })
|
|
||||||
authTokenData.analyticsId = analyticsId
|
|
||||||
}
|
|
||||||
|
|
||||||
if (authenticateRequestResponse.session !== undefined) {
|
|
||||||
authTokenData.session = this.projectSession(authenticateRequestResponse.session)
|
|
||||||
}
|
|
||||||
|
|
||||||
const authToken = this.tokenEncoder.encodeExpirableToken(authTokenData, this.jwtTTL)
|
|
||||||
|
|
||||||
return this.json({ authToken })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@httpGet('/', TYPES.AuthMiddleware, TYPES.SessionMiddleware)
|
@httpGet('/', TYPES.AuthMiddleware, TYPES.SessionMiddleware)
|
||||||
@@ -93,36 +72,4 @@ export class SessionsController extends BaseHttpController {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private projectUser(user: User): { uuid: string; email: string } {
|
|
||||||
return <{ uuid: string; email: string }>this.userProjector.projectSimple(user)
|
|
||||||
}
|
|
||||||
|
|
||||||
private projectSession(session: Session): {
|
|
||||||
uuid: string
|
|
||||||
api_version: string
|
|
||||||
created_at: string
|
|
||||||
updated_at: string
|
|
||||||
device_info: string
|
|
||||||
readonly_access: boolean
|
|
||||||
access_expiration: string
|
|
||||||
refresh_expiration: string
|
|
||||||
} {
|
|
||||||
return <
|
|
||||||
{
|
|
||||||
uuid: string
|
|
||||||
api_version: string
|
|
||||||
created_at: string
|
|
||||||
updated_at: string
|
|
||||||
device_info: string
|
|
||||||
readonly_access: boolean
|
|
||||||
access_expiration: string
|
|
||||||
refresh_expiration: string
|
|
||||||
}
|
|
||||||
>this.sessionProjector.projectSimple(session)
|
|
||||||
}
|
|
||||||
|
|
||||||
private projectRoles(roles: Array<Role>): Array<{ uuid: string; name: RoleName }> {
|
|
||||||
return roles.map((role) => <{ uuid: string; name: RoleName }>this.roleProjector.projectSimple(role))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,13 @@
|
|||||||
import 'reflect-metadata'
|
import 'reflect-metadata'
|
||||||
|
|
||||||
import * as express from 'express'
|
|
||||||
|
|
||||||
import { SubscriptionInvitesController } from './SubscriptionInvitesController'
|
import { SubscriptionInvitesController } from './SubscriptionInvitesController'
|
||||||
import { results } from 'inversify-express-utils'
|
|
||||||
import { User } from '../Domain/User/User'
|
import { User } from '../Domain/User/User'
|
||||||
import { InviteToSharedSubscription } from '../Domain/UseCase/InviteToSharedSubscription/InviteToSharedSubscription'
|
import { InviteToSharedSubscription } from '../Domain/UseCase/InviteToSharedSubscription/InviteToSharedSubscription'
|
||||||
import { AcceptSharedSubscriptionInvitation } from '../Domain/UseCase/AcceptSharedSubscriptionInvitation/AcceptSharedSubscriptionInvitation'
|
import { AcceptSharedSubscriptionInvitation } from '../Domain/UseCase/AcceptSharedSubscriptionInvitation/AcceptSharedSubscriptionInvitation'
|
||||||
import { DeclineSharedSubscriptionInvitation } from '../Domain/UseCase/DeclineSharedSubscriptionInvitation/DeclineSharedSubscriptionInvitation'
|
import { DeclineSharedSubscriptionInvitation } from '../Domain/UseCase/DeclineSharedSubscriptionInvitation/DeclineSharedSubscriptionInvitation'
|
||||||
import { CancelSharedSubscriptionInvitation } from '../Domain/UseCase/CancelSharedSubscriptionInvitation/CancelSharedSubscriptionInvitation'
|
import { CancelSharedSubscriptionInvitation } from '../Domain/UseCase/CancelSharedSubscriptionInvitation/CancelSharedSubscriptionInvitation'
|
||||||
import { RoleName } from '@standardnotes/common'
|
|
||||||
import { ListSharedSubscriptionInvitations } from '../Domain/UseCase/ListSharedSubscriptionInvitations/ListSharedSubscriptionInvitations'
|
import { ListSharedSubscriptionInvitations } from '../Domain/UseCase/ListSharedSubscriptionInvitations/ListSharedSubscriptionInvitations'
|
||||||
|
import { ApiVersion } from '@standardnotes/api'
|
||||||
|
|
||||||
describe('SubscriptionInvitesController', () => {
|
describe('SubscriptionInvitesController', () => {
|
||||||
let inviteToSharedSubscription: InviteToSharedSubscription
|
let inviteToSharedSubscription: InviteToSharedSubscription
|
||||||
@@ -19,8 +16,6 @@ describe('SubscriptionInvitesController', () => {
|
|||||||
let cancelSharedSubscriptionInvitation: CancelSharedSubscriptionInvitation
|
let cancelSharedSubscriptionInvitation: CancelSharedSubscriptionInvitation
|
||||||
let listSharedSubscriptionInvitations: ListSharedSubscriptionInvitations
|
let listSharedSubscriptionInvitations: ListSharedSubscriptionInvitations
|
||||||
|
|
||||||
let request: express.Request
|
|
||||||
let response: express.Response
|
|
||||||
let user: User
|
let user: User
|
||||||
|
|
||||||
const createController = () =>
|
const createController = () =>
|
||||||
@@ -51,25 +46,6 @@ describe('SubscriptionInvitesController', () => {
|
|||||||
|
|
||||||
listSharedSubscriptionInvitations = {} as jest.Mocked<ListSharedSubscriptionInvitations>
|
listSharedSubscriptionInvitations = {} as jest.Mocked<ListSharedSubscriptionInvitations>
|
||||||
listSharedSubscriptionInvitations.execute = jest.fn()
|
listSharedSubscriptionInvitations.execute = jest.fn()
|
||||||
|
|
||||||
request = {
|
|
||||||
headers: {},
|
|
||||||
body: {},
|
|
||||||
params: {},
|
|
||||||
} as jest.Mocked<express.Request>
|
|
||||||
|
|
||||||
response = {
|
|
||||||
locals: {},
|
|
||||||
} as jest.Mocked<express.Response>
|
|
||||||
response.locals.user = {
|
|
||||||
email: 'test@test.te',
|
|
||||||
}
|
|
||||||
response.locals.roles = [
|
|
||||||
{
|
|
||||||
uuid: '1-2-3',
|
|
||||||
name: RoleName.CoreUser,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should get invitations to subscription sharing', async () => {
|
it('should get invitations to subscription sharing', async () => {
|
||||||
@@ -77,128 +53,127 @@ describe('SubscriptionInvitesController', () => {
|
|||||||
invitations: [],
|
invitations: [],
|
||||||
})
|
})
|
||||||
|
|
||||||
const httpResponse = <results.JsonResult>await createController().listInvites(request, response)
|
const result = await createController().listInvites({ api: ApiVersion.v0, inviterEmail: 'test@test.te' })
|
||||||
const result = await httpResponse.executeAsync()
|
|
||||||
|
|
||||||
expect(listSharedSubscriptionInvitations.execute).toHaveBeenCalledWith({
|
expect(listSharedSubscriptionInvitations.execute).toHaveBeenCalledWith({
|
||||||
inviterEmail: 'test@test.te',
|
inviterEmail: 'test@test.te',
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(result.statusCode).toEqual(200)
|
expect(result.status).toEqual(200)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should cancel invitation to subscription sharing', async () => {
|
it('should cancel invitation to subscription sharing', async () => {
|
||||||
request.params.inviteUuid = '1-2-3'
|
|
||||||
|
|
||||||
cancelSharedSubscriptionInvitation.execute = jest.fn().mockReturnValue({
|
cancelSharedSubscriptionInvitation.execute = jest.fn().mockReturnValue({
|
||||||
success: true,
|
success: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
const httpResponse = <results.JsonResult>await createController().cancelSubscriptionSharing(request, response)
|
const result = await createController().cancelInvite({
|
||||||
const result = await httpResponse.executeAsync()
|
api: ApiVersion.v0,
|
||||||
|
inviteUuid: '1-2-3',
|
||||||
|
inviterEmail: 'test@test.te',
|
||||||
|
})
|
||||||
|
|
||||||
expect(cancelSharedSubscriptionInvitation.execute).toHaveBeenCalledWith({
|
expect(cancelSharedSubscriptionInvitation.execute).toHaveBeenCalledWith({
|
||||||
sharedSubscriptionInvitationUuid: '1-2-3',
|
sharedSubscriptionInvitationUuid: '1-2-3',
|
||||||
inviterEmail: 'test@test.te',
|
inviterEmail: 'test@test.te',
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(result.statusCode).toEqual(200)
|
expect(result.status).toEqual(200)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should not cancel invitation to subscription sharing if the workflow fails', async () => {
|
it('should not cancel invitation to subscription sharing if the workflow fails', async () => {
|
||||||
request.params.inviteUuid = '1-2-3'
|
|
||||||
|
|
||||||
cancelSharedSubscriptionInvitation.execute = jest.fn().mockReturnValue({
|
cancelSharedSubscriptionInvitation.execute = jest.fn().mockReturnValue({
|
||||||
success: false,
|
success: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
const httpResponse = <results.JsonResult>await createController().cancelSubscriptionSharing(request, response)
|
const result = await createController().cancelInvite({
|
||||||
const result = await httpResponse.executeAsync()
|
api: ApiVersion.v0,
|
||||||
|
inviteUuid: '1-2-3',
|
||||||
|
})
|
||||||
|
|
||||||
expect(result.statusCode).toEqual(400)
|
expect(result.status).toEqual(400)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should decline invitation to subscription sharing', async () => {
|
it('should decline invitation to subscription sharing', async () => {
|
||||||
request.params.inviteUuid = '1-2-3'
|
|
||||||
|
|
||||||
declineSharedSubscriptionInvitation.execute = jest.fn().mockReturnValue({
|
declineSharedSubscriptionInvitation.execute = jest.fn().mockReturnValue({
|
||||||
success: true,
|
success: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
const httpResponse = <results.JsonResult>await createController().declineInvite(request)
|
const result = await createController().declineInvite({
|
||||||
const result = await httpResponse.executeAsync()
|
api: ApiVersion.v0,
|
||||||
|
inviteUuid: '1-2-3',
|
||||||
|
})
|
||||||
|
|
||||||
expect(declineSharedSubscriptionInvitation.execute).toHaveBeenCalledWith({
|
expect(declineSharedSubscriptionInvitation.execute).toHaveBeenCalledWith({
|
||||||
sharedSubscriptionInvitationUuid: '1-2-3',
|
sharedSubscriptionInvitationUuid: '1-2-3',
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(result.statusCode).toEqual(200)
|
expect(result.status).toEqual(200)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should not decline invitation to subscription sharing if the workflow fails', async () => {
|
it('should not decline invitation to subscription sharing if the workflow fails', async () => {
|
||||||
request.params.inviteUuid = '1-2-3'
|
|
||||||
|
|
||||||
declineSharedSubscriptionInvitation.execute = jest.fn().mockReturnValue({
|
declineSharedSubscriptionInvitation.execute = jest.fn().mockReturnValue({
|
||||||
success: false,
|
success: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
const httpResponse = <results.JsonResult>await createController().declineInvite(request)
|
const result = await createController().declineInvite({
|
||||||
const result = await httpResponse.executeAsync()
|
api: ApiVersion.v0,
|
||||||
|
inviteUuid: '1-2-3',
|
||||||
|
})
|
||||||
|
|
||||||
expect(declineSharedSubscriptionInvitation.execute).toHaveBeenCalledWith({
|
expect(declineSharedSubscriptionInvitation.execute).toHaveBeenCalledWith({
|
||||||
sharedSubscriptionInvitationUuid: '1-2-3',
|
sharedSubscriptionInvitationUuid: '1-2-3',
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(result.statusCode).toEqual(400)
|
expect(result.status).toEqual(400)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should accept invitation to subscription sharing', async () => {
|
it('should accept invitation to subscription sharing', async () => {
|
||||||
request.params.inviteUuid = '1-2-3'
|
|
||||||
|
|
||||||
acceptSharedSubscriptionInvitation.execute = jest.fn().mockReturnValue({
|
acceptSharedSubscriptionInvitation.execute = jest.fn().mockReturnValue({
|
||||||
success: true,
|
success: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
const httpResponse = <results.JsonResult>await createController().acceptInvite(request)
|
const result = await createController().acceptInvite({
|
||||||
const result = await httpResponse.executeAsync()
|
api: ApiVersion.v0,
|
||||||
|
inviteUuid: '1-2-3',
|
||||||
|
})
|
||||||
|
|
||||||
expect(acceptSharedSubscriptionInvitation.execute).toHaveBeenCalledWith({
|
expect(acceptSharedSubscriptionInvitation.execute).toHaveBeenCalledWith({
|
||||||
sharedSubscriptionInvitationUuid: '1-2-3',
|
sharedSubscriptionInvitationUuid: '1-2-3',
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(result.statusCode).toEqual(200)
|
expect(result.status).toEqual(200)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should not accept invitation to subscription sharing if the workflow fails', async () => {
|
it('should not accept invitation to subscription sharing if the workflow fails', async () => {
|
||||||
request.params.inviteUuid = '1-2-3'
|
|
||||||
|
|
||||||
acceptSharedSubscriptionInvitation.execute = jest.fn().mockReturnValue({
|
acceptSharedSubscriptionInvitation.execute = jest.fn().mockReturnValue({
|
||||||
success: false,
|
success: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
const httpResponse = <results.JsonResult>await createController().acceptInvite(request)
|
const result = await createController().acceptInvite({
|
||||||
const result = await httpResponse.executeAsync()
|
api: ApiVersion.v0,
|
||||||
|
inviteUuid: '1-2-3',
|
||||||
|
})
|
||||||
|
|
||||||
expect(acceptSharedSubscriptionInvitation.execute).toHaveBeenCalledWith({
|
expect(acceptSharedSubscriptionInvitation.execute).toHaveBeenCalledWith({
|
||||||
sharedSubscriptionInvitationUuid: '1-2-3',
|
sharedSubscriptionInvitationUuid: '1-2-3',
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(result.statusCode).toEqual(400)
|
expect(result.status).toEqual(400)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should invite to user subscription', async () => {
|
it('should invite to user subscription', async () => {
|
||||||
request.body.identifier = 'invitee@test.te'
|
|
||||||
response.locals.user = {
|
|
||||||
uuid: '1-2-3',
|
|
||||||
email: 'test@test.te',
|
|
||||||
}
|
|
||||||
|
|
||||||
inviteToSharedSubscription.execute = jest.fn().mockReturnValue({
|
inviteToSharedSubscription.execute = jest.fn().mockReturnValue({
|
||||||
success: true,
|
success: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
const httpResponse = <results.JsonResult>await createController().inviteToSubscriptionSharing(request, response)
|
const result = await createController().invite({
|
||||||
const result = await httpResponse.executeAsync()
|
api: ApiVersion.v0,
|
||||||
|
identifier: 'invitee@test.te',
|
||||||
|
inviterUuid: '1-2-3',
|
||||||
|
inviterEmail: 'test@test.te',
|
||||||
|
inviterRoles: ['CORE_USER'],
|
||||||
|
})
|
||||||
|
|
||||||
expect(inviteToSharedSubscription.execute).toHaveBeenCalledWith({
|
expect(inviteToSharedSubscription.execute).toHaveBeenCalledWith({
|
||||||
inviterEmail: 'test@test.te',
|
inviterEmail: 'test@test.te',
|
||||||
@@ -207,37 +182,36 @@ describe('SubscriptionInvitesController', () => {
|
|||||||
inviterRoles: ['CORE_USER'],
|
inviterRoles: ['CORE_USER'],
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(result.statusCode).toEqual(200)
|
expect(result.status).toEqual(200)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should not invite to user subscription if the identifier is missing in request', async () => {
|
it('should not invite to user subscription if the identifier is missing in request', async () => {
|
||||||
response.locals.user = {
|
const result = await createController().invite({
|
||||||
uuid: '1-2-3',
|
api: ApiVersion.v0,
|
||||||
email: 'test@test.te',
|
identifier: '',
|
||||||
}
|
inviterUuid: '1-2-3',
|
||||||
|
inviterEmail: 'test@test.te',
|
||||||
const httpResponse = <results.JsonResult>await createController().inviteToSubscriptionSharing(request, response)
|
inviterRoles: ['CORE_USER'],
|
||||||
const result = await httpResponse.executeAsync()
|
})
|
||||||
|
|
||||||
expect(inviteToSharedSubscription.execute).not.toHaveBeenCalled()
|
expect(inviteToSharedSubscription.execute).not.toHaveBeenCalled()
|
||||||
|
|
||||||
expect(result.statusCode).toEqual(400)
|
expect(result.status).toEqual(400)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should not invite to user subscription if the workflow does not run', async () => {
|
it('should not invite to user subscription if the workflow does not run', async () => {
|
||||||
request.body.identifier = 'invitee@test.te'
|
|
||||||
response.locals.user = {
|
|
||||||
uuid: '1-2-3',
|
|
||||||
email: 'test@test.te',
|
|
||||||
}
|
|
||||||
|
|
||||||
inviteToSharedSubscription.execute = jest.fn().mockReturnValue({
|
inviteToSharedSubscription.execute = jest.fn().mockReturnValue({
|
||||||
success: false,
|
success: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
const httpResponse = <results.JsonResult>await createController().inviteToSubscriptionSharing(request, response)
|
const result = await createController().invite({
|
||||||
const result = await httpResponse.executeAsync()
|
api: ApiVersion.v0,
|
||||||
|
identifier: 'invitee@test.te',
|
||||||
|
inviterUuid: '1-2-3',
|
||||||
|
inviterEmail: 'test@test.te',
|
||||||
|
inviterRoles: ['CORE_USER'],
|
||||||
|
})
|
||||||
|
|
||||||
expect(result.statusCode).toEqual(400)
|
expect(result.status).toEqual(400)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,15 +1,19 @@
|
|||||||
import { Role } from '@standardnotes/security'
|
|
||||||
import { Request, Response } from 'express'
|
|
||||||
import { inject } from 'inversify'
|
|
||||||
import {
|
import {
|
||||||
BaseHttpController,
|
HttpStatusCode,
|
||||||
controller,
|
SubscriptionInviteAcceptRequestParams,
|
||||||
httpDelete,
|
SubscriptionInviteAcceptResponse,
|
||||||
httpGet,
|
SubscriptionInviteCancelRequestParams,
|
||||||
httpPost,
|
SubscriptionInviteCancelResponse,
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
SubscriptionInviteDeclineRequestParams,
|
||||||
results,
|
SubscriptionInviteDeclineResponse,
|
||||||
} from 'inversify-express-utils'
|
SubscriptionInviteListRequestParams,
|
||||||
|
SubscriptionInviteListResponse,
|
||||||
|
SubscriptionInviteRequestParams,
|
||||||
|
SubscriptionInviteResponse,
|
||||||
|
SubscriptionServerInterface,
|
||||||
|
} from '@standardnotes/api'
|
||||||
|
import { RoleName } from '@standardnotes/common'
|
||||||
|
import { inject, injectable } from 'inversify'
|
||||||
|
|
||||||
import TYPES from '../Bootstrap/Types'
|
import TYPES from '../Bootstrap/Types'
|
||||||
import { AcceptSharedSubscriptionInvitation } from '../Domain/UseCase/AcceptSharedSubscriptionInvitation/AcceptSharedSubscriptionInvitation'
|
import { AcceptSharedSubscriptionInvitation } from '../Domain/UseCase/AcceptSharedSubscriptionInvitation/AcceptSharedSubscriptionInvitation'
|
||||||
@@ -18,8 +22,8 @@ import { DeclineSharedSubscriptionInvitation } from '../Domain/UseCase/DeclineSh
|
|||||||
import { InviteToSharedSubscription } from '../Domain/UseCase/InviteToSharedSubscription/InviteToSharedSubscription'
|
import { InviteToSharedSubscription } from '../Domain/UseCase/InviteToSharedSubscription/InviteToSharedSubscription'
|
||||||
import { ListSharedSubscriptionInvitations } from '../Domain/UseCase/ListSharedSubscriptionInvitations/ListSharedSubscriptionInvitations'
|
import { ListSharedSubscriptionInvitations } from '../Domain/UseCase/ListSharedSubscriptionInvitations/ListSharedSubscriptionInvitations'
|
||||||
|
|
||||||
@controller('/subscription-invites')
|
@injectable()
|
||||||
export class SubscriptionInvitesController extends BaseHttpController {
|
export class SubscriptionInvitesController implements SubscriptionServerInterface {
|
||||||
constructor(
|
constructor(
|
||||||
@inject(TYPES.InviteToSharedSubscription) private inviteToSharedSubscription: InviteToSharedSubscription,
|
@inject(TYPES.InviteToSharedSubscription) private inviteToSharedSubscription: InviteToSharedSubscription,
|
||||||
@inject(TYPES.AcceptSharedSubscriptionInvitation)
|
@inject(TYPES.AcceptSharedSubscriptionInvitation)
|
||||||
@@ -30,75 +34,103 @@ export class SubscriptionInvitesController extends BaseHttpController {
|
|||||||
private cancelSharedSubscriptionInvitation: CancelSharedSubscriptionInvitation,
|
private cancelSharedSubscriptionInvitation: CancelSharedSubscriptionInvitation,
|
||||||
@inject(TYPES.ListSharedSubscriptionInvitations)
|
@inject(TYPES.ListSharedSubscriptionInvitations)
|
||||||
private listSharedSubscriptionInvitations: ListSharedSubscriptionInvitations,
|
private listSharedSubscriptionInvitations: ListSharedSubscriptionInvitations,
|
||||||
) {
|
) {}
|
||||||
super()
|
|
||||||
}
|
|
||||||
|
|
||||||
@httpGet('/:inviteUuid/accept')
|
async acceptInvite(params: SubscriptionInviteAcceptRequestParams): Promise<SubscriptionInviteAcceptResponse> {
|
||||||
async acceptInvite(request: Request): Promise<results.JsonResult> {
|
|
||||||
const result = await this.acceptSharedSubscriptionInvitation.execute({
|
const result = await this.acceptSharedSubscriptionInvitation.execute({
|
||||||
sharedSubscriptionInvitationUuid: request.params.inviteUuid,
|
sharedSubscriptionInvitationUuid: params.inviteUuid,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
return this.json(result)
|
return {
|
||||||
|
status: HttpStatusCode.Success,
|
||||||
|
data: result,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.json(result, 400)
|
return {
|
||||||
|
status: HttpStatusCode.BadRequest,
|
||||||
|
data: result,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@httpGet('/:inviteUuid/decline')
|
async declineInvite(params: SubscriptionInviteDeclineRequestParams): Promise<SubscriptionInviteDeclineResponse> {
|
||||||
async declineInvite(request: Request): Promise<results.JsonResult> {
|
|
||||||
const result = await this.declineSharedSubscriptionInvitation.execute({
|
const result = await this.declineSharedSubscriptionInvitation.execute({
|
||||||
sharedSubscriptionInvitationUuid: request.params.inviteUuid,
|
sharedSubscriptionInvitationUuid: params.inviteUuid,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
return this.json(result)
|
return {
|
||||||
|
status: HttpStatusCode.Success,
|
||||||
|
data: result,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.json(result, 400)
|
return {
|
||||||
|
status: HttpStatusCode.BadRequest,
|
||||||
|
data: result,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@httpPost('/', TYPES.ApiGatewayAuthMiddleware)
|
async invite(params: SubscriptionInviteRequestParams): Promise<SubscriptionInviteResponse> {
|
||||||
async inviteToSubscriptionSharing(request: Request, response: Response): Promise<results.JsonResult> {
|
if (!params.identifier) {
|
||||||
if (!request.body.identifier) {
|
return {
|
||||||
return this.json({ error: { message: 'Missing invitee identifier' } }, 400)
|
status: HttpStatusCode.BadRequest,
|
||||||
|
data: {
|
||||||
|
error: {
|
||||||
|
message: 'Missing invitee identifier',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await this.inviteToSharedSubscription.execute({
|
const result = await this.inviteToSharedSubscription.execute({
|
||||||
inviterEmail: response.locals.user.email,
|
inviterEmail: params.inviterEmail as string,
|
||||||
inviterUuid: response.locals.user.uuid,
|
inviterUuid: params.inviterUuid as string,
|
||||||
inviteeIdentifier: request.body.identifier,
|
inviteeIdentifier: params.identifier,
|
||||||
inviterRoles: response.locals.roles.map((role: Role) => role.name),
|
inviterRoles: params.inviterRoles as RoleName[],
|
||||||
})
|
})
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
return this.json(result)
|
return {
|
||||||
|
status: HttpStatusCode.Success,
|
||||||
|
data: result,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.json(result, 400)
|
return {
|
||||||
|
status: HttpStatusCode.BadRequest,
|
||||||
|
data: result,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@httpDelete('/:inviteUuid', TYPES.ApiGatewayAuthMiddleware)
|
async cancelInvite(params: SubscriptionInviteCancelRequestParams): Promise<SubscriptionInviteCancelResponse> {
|
||||||
async cancelSubscriptionSharing(request: Request, response: Response): Promise<results.JsonResult> {
|
|
||||||
const result = await this.cancelSharedSubscriptionInvitation.execute({
|
const result = await this.cancelSharedSubscriptionInvitation.execute({
|
||||||
sharedSubscriptionInvitationUuid: request.params.inviteUuid,
|
sharedSubscriptionInvitationUuid: params.inviteUuid,
|
||||||
inviterEmail: response.locals.user.email,
|
inviterEmail: params.inviterEmail as string,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
return this.json(result)
|
return {
|
||||||
|
status: HttpStatusCode.Success,
|
||||||
|
data: result,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.json(result, 400)
|
return {
|
||||||
|
status: HttpStatusCode.BadRequest,
|
||||||
|
data: result,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@httpGet('/', TYPES.ApiGatewayAuthMiddleware)
|
async listInvites(params: SubscriptionInviteListRequestParams): Promise<SubscriptionInviteListResponse> {
|
||||||
async listInvites(_request: Request, response: Response): Promise<results.JsonResult> {
|
|
||||||
const result = await this.listSharedSubscriptionInvitations.execute({
|
const result = await this.listSharedSubscriptionInvitations.execute({
|
||||||
inviterEmail: response.locals.user.email,
|
inviterEmail: params.inviterEmail as string,
|
||||||
})
|
})
|
||||||
|
|
||||||
return this.json(result)
|
return {
|
||||||
|
status: HttpStatusCode.Success,
|
||||||
|
data: result,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,18 +4,23 @@ import { Request, Response } from 'express'
|
|||||||
import { results } from 'inversify-express-utils'
|
import { results } from 'inversify-express-utils'
|
||||||
import { ValetTokenController } from './ValetTokenController'
|
import { ValetTokenController } from './ValetTokenController'
|
||||||
import { CreateValetToken } from '../Domain/UseCase/CreateValetToken/CreateValetToken'
|
import { CreateValetToken } from '../Domain/UseCase/CreateValetToken/CreateValetToken'
|
||||||
|
import { Uuid, ValidatorInterface } from '@standardnotes/common'
|
||||||
|
|
||||||
describe('ValetTokenController', () => {
|
describe('ValetTokenController', () => {
|
||||||
let createValetToken: CreateValetToken
|
let createValetToken: CreateValetToken
|
||||||
|
let uuidValidator: ValidatorInterface<Uuid>
|
||||||
let request: Request
|
let request: Request
|
||||||
let response: Response
|
let response: Response
|
||||||
|
|
||||||
const createController = () => new ValetTokenController(createValetToken)
|
const createController = () => new ValetTokenController(createValetToken, uuidValidator)
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
createValetToken = {} as jest.Mocked<CreateValetToken>
|
createValetToken = {} as jest.Mocked<CreateValetToken>
|
||||||
createValetToken.execute = jest.fn().mockReturnValue({ success: true, valetToken: 'foobar' })
|
createValetToken.execute = jest.fn().mockReturnValue({ success: true, valetToken: 'foobar' })
|
||||||
|
|
||||||
|
uuidValidator = {} as jest.Mocked<ValidatorInterface<Uuid>>
|
||||||
|
uuidValidator.validate = jest.fn().mockReturnValue(true)
|
||||||
|
|
||||||
request = {
|
request = {
|
||||||
body: {
|
body: {
|
||||||
operation: 'write',
|
operation: 'write',
|
||||||
@@ -42,6 +47,17 @@ describe('ValetTokenController', () => {
|
|||||||
expect(await result.content.readAsStringAsync()).toEqual('{"success":true,"valetToken":"foobar"}')
|
expect(await result.content.readAsStringAsync()).toEqual('{"success":true,"valetToken":"foobar"}')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should not create a valet token if the remote resource identifier is not a valid uuid', async () => {
|
||||||
|
uuidValidator.validate = jest.fn().mockReturnValue(false)
|
||||||
|
|
||||||
|
const httpResponse = <results.JsonResult>await createController().create(request, response)
|
||||||
|
const result = await httpResponse.executeAsync()
|
||||||
|
|
||||||
|
expect(createValetToken.execute).not.toHaveBeenCalled()
|
||||||
|
|
||||||
|
expect(result.statusCode).toEqual(400)
|
||||||
|
})
|
||||||
|
|
||||||
it('should create a read valet token for read only access session', async () => {
|
it('should create a read valet token for read only access session', async () => {
|
||||||
response.locals.readOnlyAccess = true
|
response.locals.readOnlyAccess = true
|
||||||
request.body.operation = 'read'
|
request.body.operation = 'read'
|
||||||
|
|||||||
@@ -11,11 +11,15 @@ import { CreateValetTokenPayload } from '@standardnotes/responses'
|
|||||||
|
|
||||||
import TYPES from '../Bootstrap/Types'
|
import TYPES from '../Bootstrap/Types'
|
||||||
import { CreateValetToken } from '../Domain/UseCase/CreateValetToken/CreateValetToken'
|
import { CreateValetToken } from '../Domain/UseCase/CreateValetToken/CreateValetToken'
|
||||||
import { ErrorTag } from '@standardnotes/common'
|
import { ErrorTag, Uuid, ValidatorInterface } from '@standardnotes/common'
|
||||||
|
import { ValetTokenOperation } from '@standardnotes/security'
|
||||||
|
|
||||||
@controller('/valet-tokens', TYPES.ApiGatewayAuthMiddleware)
|
@controller('/valet-tokens', TYPES.ApiGatewayAuthMiddleware)
|
||||||
export class ValetTokenController extends BaseHttpController {
|
export class ValetTokenController extends BaseHttpController {
|
||||||
constructor(@inject(TYPES.CreateValetToken) private createValetKey: CreateValetToken) {
|
constructor(
|
||||||
|
@inject(TYPES.CreateValetToken) private createValetKey: CreateValetToken,
|
||||||
|
@inject(TYPES.UuidValidator) private uuidValitor: ValidatorInterface<Uuid>,
|
||||||
|
) {
|
||||||
super()
|
super()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,9 +39,23 @@ export class ValetTokenController extends BaseHttpController {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const resource of payload.resources) {
|
||||||
|
if (!this.uuidValitor.validate(resource.remoteIdentifier)) {
|
||||||
|
return this.json(
|
||||||
|
{
|
||||||
|
error: {
|
||||||
|
tag: ErrorTag.ParametersInvalid,
|
||||||
|
message: 'Invalid remote resource identifier.',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
400,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const createValetKeyResponse = await this.createValetKey.execute({
|
const createValetKeyResponse = await this.createValetKey.execute({
|
||||||
userUuid: response.locals.user.uuid,
|
userUuid: response.locals.user.uuid,
|
||||||
operation: payload.operation,
|
operation: payload.operation as ValetTokenOperation,
|
||||||
resources: payload.resources,
|
resources: payload.resources,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1,65 +1,28 @@
|
|||||||
import 'reflect-metadata'
|
import 'reflect-metadata'
|
||||||
|
|
||||||
import * as express from 'express'
|
|
||||||
import { results } from 'inversify-express-utils'
|
|
||||||
|
|
||||||
import { AddWebSocketsConnection } from '../Domain/UseCase/AddWebSocketsConnection/AddWebSocketsConnection'
|
|
||||||
|
|
||||||
import { WebSocketsController } from './WebSocketsController'
|
import { WebSocketsController } from './WebSocketsController'
|
||||||
import { RemoveWebSocketsConnection } from '../Domain/UseCase/RemoveWebSocketsConnection/RemoveWebSocketsConnection'
|
import { CreateWebSocketConnectionToken } from '../Domain/UseCase/CreateWebSocketConnectionToken/CreateWebSocketConnectionToken'
|
||||||
|
|
||||||
describe('WebSocketsController', () => {
|
describe('WebSocketsController', () => {
|
||||||
let addWebSocketsConnection: AddWebSocketsConnection
|
let createWebSocketConnectionToken: CreateWebSocketConnectionToken
|
||||||
let removeWebSocketsConnection: RemoveWebSocketsConnection
|
|
||||||
let request: express.Request
|
|
||||||
let response: express.Response
|
|
||||||
|
|
||||||
const createController = () => new WebSocketsController(addWebSocketsConnection, removeWebSocketsConnection)
|
const createController = () => new WebSocketsController(createWebSocketConnectionToken)
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
addWebSocketsConnection = {} as jest.Mocked<AddWebSocketsConnection>
|
createWebSocketConnectionToken = {} as jest.Mocked<CreateWebSocketConnectionToken>
|
||||||
addWebSocketsConnection.execute = jest.fn()
|
createWebSocketConnectionToken.execute = jest.fn().mockReturnValue({ token: 'foobar' })
|
||||||
|
|
||||||
removeWebSocketsConnection = {} as jest.Mocked<RemoveWebSocketsConnection>
|
|
||||||
removeWebSocketsConnection.execute = jest.fn()
|
|
||||||
|
|
||||||
request = {
|
|
||||||
body: {
|
|
||||||
userUuid: '1-2-3',
|
|
||||||
},
|
|
||||||
params: {},
|
|
||||||
headers: {},
|
|
||||||
} as jest.Mocked<express.Request>
|
|
||||||
request.params.connectionId = '2-3-4'
|
|
||||||
|
|
||||||
response = {
|
|
||||||
locals: {},
|
|
||||||
} as jest.Mocked<express.Response>
|
|
||||||
response.locals.user = {
|
|
||||||
uuid: '1-2-3',
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should persist an established web sockets connection', async () => {
|
it('should create a web sockets connection token', async () => {
|
||||||
const httpResponse = await createController().storeWebSocketsConnection(request, response)
|
const response = await createController().createConnectionToken({ userUuid: '1-2-3' })
|
||||||
|
|
||||||
expect(httpResponse).toBeInstanceOf(results.JsonResult)
|
expect(response).toEqual({
|
||||||
expect((<results.JsonResult>httpResponse).statusCode).toEqual(200)
|
status: 200,
|
||||||
|
data: { token: 'foobar' },
|
||||||
expect(addWebSocketsConnection.execute).toHaveBeenCalledWith({
|
|
||||||
userUuid: '1-2-3',
|
|
||||||
connectionId: '2-3-4',
|
|
||||||
})
|
})
|
||||||
})
|
|
||||||
|
|
||||||
it('should remove a disconnected web sockets connection', async () => {
|
expect(createWebSocketConnectionToken.execute).toHaveBeenCalledWith({
|
||||||
const httpResponse = await createController().deleteWebSocketsConnection(request)
|
userUuid: '1-2-3',
|
||||||
|
|
||||||
expect(httpResponse).toBeInstanceOf(results.JsonResult)
|
|
||||||
expect((<results.JsonResult>httpResponse).statusCode).toEqual(200)
|
|
||||||
|
|
||||||
expect(removeWebSocketsConnection.execute).toHaveBeenCalledWith({
|
|
||||||
connectionId: '2-3-4',
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,45 +1,29 @@
|
|||||||
import { Request, Response } from 'express'
|
|
||||||
import { inject } from 'inversify'
|
|
||||||
import {
|
import {
|
||||||
BaseHttpController,
|
HttpStatusCode,
|
||||||
controller,
|
WebSocketConnectionTokenRequestParams,
|
||||||
httpDelete,
|
WebSocketConnectionTokenResponse,
|
||||||
httpPost,
|
WebSocketServerInterface,
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
} from '@standardnotes/api'
|
||||||
results,
|
import { inject, injectable } from 'inversify'
|
||||||
} from 'inversify-express-utils'
|
|
||||||
import TYPES from '../Bootstrap/Types'
|
import TYPES from '../Bootstrap/Types'
|
||||||
import { AddWebSocketsConnection } from '../Domain/UseCase/AddWebSocketsConnection/AddWebSocketsConnection'
|
import { CreateWebSocketConnectionToken } from '../Domain/UseCase/CreateWebSocketConnectionToken/CreateWebSocketConnectionToken'
|
||||||
import { RemoveWebSocketsConnection } from '../Domain/UseCase/RemoveWebSocketsConnection/RemoveWebSocketsConnection'
|
|
||||||
|
|
||||||
@controller('/sockets')
|
@injectable()
|
||||||
export class WebSocketsController extends BaseHttpController {
|
export class WebSocketsController implements WebSocketServerInterface {
|
||||||
constructor(
|
constructor(
|
||||||
@inject(TYPES.AddWebSocketsConnection) private addWebSocketsConnection: AddWebSocketsConnection,
|
@inject(TYPES.CreateWebSocketConnectionToken)
|
||||||
@inject(TYPES.RemoveWebSocketsConnection) private removeWebSocketsConnection: RemoveWebSocketsConnection,
|
private createWebSocketConnectionToken: CreateWebSocketConnectionToken,
|
||||||
) {
|
) {}
|
||||||
super()
|
|
||||||
}
|
|
||||||
|
|
||||||
@httpPost('/:connectionId', TYPES.ApiGatewayAuthMiddleware)
|
async createConnectionToken(
|
||||||
async storeWebSocketsConnection(
|
params: WebSocketConnectionTokenRequestParams,
|
||||||
request: Request,
|
): Promise<WebSocketConnectionTokenResponse> {
|
||||||
response: Response,
|
const result = await this.createWebSocketConnectionToken.execute({ userUuid: params.userUuid as string })
|
||||||
): Promise<results.JsonResult | results.BadRequestErrorMessageResult> {
|
|
||||||
await this.addWebSocketsConnection.execute({
|
|
||||||
userUuid: response.locals.user.uuid,
|
|
||||||
connectionId: request.params.connectionId,
|
|
||||||
})
|
|
||||||
|
|
||||||
return this.json({ success: true })
|
return {
|
||||||
}
|
status: HttpStatusCode.Success,
|
||||||
|
data: result,
|
||||||
@httpDelete('/:connectionId')
|
}
|
||||||
async deleteWebSocketsConnection(
|
|
||||||
request: Request,
|
|
||||||
): Promise<results.JsonResult | results.BadRequestErrorMessageResult> {
|
|
||||||
await this.removeWebSocketsConnection.execute({ connectionId: request.params.connectionId })
|
|
||||||
|
|
||||||
return this.json({ success: true })
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ describe('FeatureService', () => {
|
|||||||
uuid: 'subscription-1-1-1',
|
uuid: 'subscription-1-1-1',
|
||||||
createdAt: 111,
|
createdAt: 111,
|
||||||
updatedAt: 222,
|
updatedAt: 222,
|
||||||
|
renewedAt: null,
|
||||||
planName: SubscriptionName.PlusPlan,
|
planName: SubscriptionName.PlusPlan,
|
||||||
endsAt: 555,
|
endsAt: 555,
|
||||||
user: Promise.resolve(user),
|
user: Promise.resolve(user),
|
||||||
@@ -95,6 +96,7 @@ describe('FeatureService', () => {
|
|||||||
uuid: 'subscription-2-2-2',
|
uuid: 'subscription-2-2-2',
|
||||||
createdAt: 222,
|
createdAt: 222,
|
||||||
updatedAt: 333,
|
updatedAt: 333,
|
||||||
|
renewedAt: null,
|
||||||
planName: SubscriptionName.ProPlan,
|
planName: SubscriptionName.ProPlan,
|
||||||
endsAt: 777,
|
endsAt: 777,
|
||||||
user: Promise.resolve(user),
|
user: Promise.resolve(user),
|
||||||
@@ -108,6 +110,7 @@ describe('FeatureService', () => {
|
|||||||
uuid: 'subscription-3-3-3-canceled',
|
uuid: 'subscription-3-3-3-canceled',
|
||||||
createdAt: 111,
|
createdAt: 111,
|
||||||
updatedAt: 222,
|
updatedAt: 222,
|
||||||
|
renewedAt: null,
|
||||||
planName: SubscriptionName.PlusPlan,
|
planName: SubscriptionName.PlusPlan,
|
||||||
endsAt: 333,
|
endsAt: 333,
|
||||||
user: Promise.resolve(user),
|
user: Promise.resolve(user),
|
||||||
@@ -121,6 +124,7 @@ describe('FeatureService', () => {
|
|||||||
uuid: 'subscription-4-4-4-canceled',
|
uuid: 'subscription-4-4-4-canceled',
|
||||||
createdAt: 111,
|
createdAt: 111,
|
||||||
updatedAt: 222,
|
updatedAt: 222,
|
||||||
|
renewedAt: null,
|
||||||
planName: SubscriptionName.PlusPlan,
|
planName: SubscriptionName.PlusPlan,
|
||||||
endsAt: 333,
|
endsAt: 333,
|
||||||
user: Promise.resolve(user),
|
user: Promise.resolve(user),
|
||||||
@@ -240,6 +244,7 @@ describe('FeatureService', () => {
|
|||||||
uuid: 'subscription-1-1-1',
|
uuid: 'subscription-1-1-1',
|
||||||
createdAt: 111,
|
createdAt: 111,
|
||||||
updatedAt: 222,
|
updatedAt: 222,
|
||||||
|
renewedAt: null,
|
||||||
planName: 'non existing plan name' as SubscriptionName,
|
planName: 'non existing plan name' as SubscriptionName,
|
||||||
endsAt: 555,
|
endsAt: 555,
|
||||||
user: Promise.resolve(user),
|
user: Promise.resolve(user),
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ import { User } from '../User/User'
|
|||||||
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
|
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
|
||||||
import { AccountDeletionRequestedEventHandler } from './AccountDeletionRequestedEventHandler'
|
import { AccountDeletionRequestedEventHandler } from './AccountDeletionRequestedEventHandler'
|
||||||
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
|
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
|
||||||
import { AnalyticsStoreInterface } from '@standardnotes/analytics'
|
import { AnalyticsStoreInterface, StatisticsStoreInterface } from '@standardnotes/analytics'
|
||||||
|
import { TimerInterface } from '@standardnotes/time'
|
||||||
|
|
||||||
describe('AccountDeletionRequestedEventHandler', () => {
|
describe('AccountDeletionRequestedEventHandler', () => {
|
||||||
let userRepository: UserRepositoryInterface
|
let userRepository: UserRepositoryInterface
|
||||||
@@ -27,6 +28,8 @@ describe('AccountDeletionRequestedEventHandler', () => {
|
|||||||
let event: AccountDeletionRequestedEvent
|
let event: AccountDeletionRequestedEvent
|
||||||
let getUserAnalyticsId: GetUserAnalyticsId
|
let getUserAnalyticsId: GetUserAnalyticsId
|
||||||
let analyticsStore: AnalyticsStoreInterface
|
let analyticsStore: AnalyticsStoreInterface
|
||||||
|
let statisticsStore: StatisticsStoreInterface
|
||||||
|
let timer: TimerInterface
|
||||||
|
|
||||||
const createHandler = () =>
|
const createHandler = () =>
|
||||||
new AccountDeletionRequestedEventHandler(
|
new AccountDeletionRequestedEventHandler(
|
||||||
@@ -36,6 +39,8 @@ describe('AccountDeletionRequestedEventHandler', () => {
|
|||||||
revokedSessionRepository,
|
revokedSessionRepository,
|
||||||
getUserAnalyticsId,
|
getUserAnalyticsId,
|
||||||
analyticsStore,
|
analyticsStore,
|
||||||
|
statisticsStore,
|
||||||
|
timer,
|
||||||
logger,
|
logger,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -87,6 +92,13 @@ describe('AccountDeletionRequestedEventHandler', () => {
|
|||||||
logger = {} as jest.Mocked<Logger>
|
logger = {} as jest.Mocked<Logger>
|
||||||
logger.info = jest.fn()
|
logger.info = jest.fn()
|
||||||
logger.warn = jest.fn()
|
logger.warn = jest.fn()
|
||||||
|
|
||||||
|
statisticsStore = {} as jest.Mocked<StatisticsStoreInterface>
|
||||||
|
statisticsStore.incrementMeasure = jest.fn()
|
||||||
|
|
||||||
|
timer = {} as jest.Mocked<TimerInterface>
|
||||||
|
timer.getTimestampInMicroseconds = jest.fn().mockReturnValue(123)
|
||||||
|
timer.convertDateToMicroseconds = jest.fn().mockReturnValue(100)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should remove a user', async () => {
|
it('should remove a user', async () => {
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
import { AnalyticsActivity, AnalyticsStoreInterface, Period } from '@standardnotes/analytics'
|
import {
|
||||||
|
AnalyticsActivity,
|
||||||
|
AnalyticsStoreInterface,
|
||||||
|
Period,
|
||||||
|
StatisticsMeasure,
|
||||||
|
StatisticsStoreInterface,
|
||||||
|
} from '@standardnotes/analytics'
|
||||||
import { AccountDeletionRequestedEvent, DomainEventHandlerInterface } from '@standardnotes/domain-events'
|
import { AccountDeletionRequestedEvent, DomainEventHandlerInterface } from '@standardnotes/domain-events'
|
||||||
|
import { TimerInterface } from '@standardnotes/time'
|
||||||
import { inject, injectable } from 'inversify'
|
import { inject, injectable } from 'inversify'
|
||||||
import { Logger } from 'winston'
|
import { Logger } from 'winston'
|
||||||
import TYPES from '../../Bootstrap/Types'
|
import TYPES from '../../Bootstrap/Types'
|
||||||
@@ -18,6 +25,8 @@ export class AccountDeletionRequestedEventHandler implements DomainEventHandlerI
|
|||||||
@inject(TYPES.RevokedSessionRepository) private revokedSessionRepository: RevokedSessionRepositoryInterface,
|
@inject(TYPES.RevokedSessionRepository) private revokedSessionRepository: RevokedSessionRepositoryInterface,
|
||||||
@inject(TYPES.GetUserAnalyticsId) private getUserAnalyticsId: GetUserAnalyticsId,
|
@inject(TYPES.GetUserAnalyticsId) private getUserAnalyticsId: GetUserAnalyticsId,
|
||||||
@inject(TYPES.AnalyticsStore) private analyticsStore: AnalyticsStoreInterface,
|
@inject(TYPES.AnalyticsStore) private analyticsStore: AnalyticsStoreInterface,
|
||||||
|
@inject(TYPES.StatisticsStore) private statisticsStore: StatisticsStoreInterface,
|
||||||
|
@inject(TYPES.Timer) private timer: TimerInterface,
|
||||||
@inject(TYPES.Logger) private logger: Logger,
|
@inject(TYPES.Logger) private logger: Logger,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@@ -39,6 +48,14 @@ export class AccountDeletionRequestedEventHandler implements DomainEventHandlerI
|
|||||||
Period.ThisMonth,
|
Period.ThisMonth,
|
||||||
])
|
])
|
||||||
|
|
||||||
|
const registrationLength =
|
||||||
|
this.timer.getTimestampInMicroseconds() - this.timer.convertDateToMicroseconds(user.createdAt)
|
||||||
|
await this.statisticsStore.incrementMeasure(StatisticsMeasure.RegistrationLength, registrationLength, [
|
||||||
|
Period.Today,
|
||||||
|
Period.ThisWeek,
|
||||||
|
Period.ThisMonth,
|
||||||
|
])
|
||||||
|
|
||||||
await this.userRepository.remove(user)
|
await this.userRepository.remove(user)
|
||||||
|
|
||||||
this.logger.info(`Finished account cleanup for user: ${event.payload.userUuid}`)
|
this.logger.info(`Finished account cleanup for user: ${event.payload.userUuid}`)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import 'reflect-metadata'
|
import 'reflect-metadata'
|
||||||
|
|
||||||
import { PaymentSuccessEvent } from '@standardnotes/domain-events'
|
import { PaymentSuccessEvent } from '@standardnotes/domain-events'
|
||||||
import { AnalyticsStoreInterface } from '@standardnotes/analytics'
|
import { AnalyticsStoreInterface, StatisticsStoreInterface } from '@standardnotes/analytics'
|
||||||
|
|
||||||
import { PaymentSuccessEventHandler } from './PaymentSuccessEventHandler'
|
import { PaymentSuccessEventHandler } from './PaymentSuccessEventHandler'
|
||||||
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
|
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
|
||||||
@@ -14,8 +14,10 @@ describe('PaymentSuccessEventHandler', () => {
|
|||||||
let user: User
|
let user: User
|
||||||
let getUserAnalyticsId: GetUserAnalyticsId
|
let getUserAnalyticsId: GetUserAnalyticsId
|
||||||
let analyticsStore: AnalyticsStoreInterface
|
let analyticsStore: AnalyticsStoreInterface
|
||||||
|
let statisticsStore: StatisticsStoreInterface
|
||||||
|
|
||||||
const createHandler = () => new PaymentSuccessEventHandler(userRepository, getUserAnalyticsId, analyticsStore)
|
const createHandler = () =>
|
||||||
|
new PaymentSuccessEventHandler(userRepository, getUserAnalyticsId, analyticsStore, statisticsStore)
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
user = {} as jest.Mocked<User>
|
user = {} as jest.Mocked<User>
|
||||||
@@ -29,9 +31,13 @@ describe('PaymentSuccessEventHandler', () => {
|
|||||||
analyticsStore = {} as jest.Mocked<AnalyticsStoreInterface>
|
analyticsStore = {} as jest.Mocked<AnalyticsStoreInterface>
|
||||||
analyticsStore.markActivity = jest.fn()
|
analyticsStore.markActivity = jest.fn()
|
||||||
|
|
||||||
|
statisticsStore = {} as jest.Mocked<StatisticsStoreInterface>
|
||||||
|
statisticsStore.incrementMeasure = jest.fn()
|
||||||
|
|
||||||
event = {} as jest.Mocked<PaymentSuccessEvent>
|
event = {} as jest.Mocked<PaymentSuccessEvent>
|
||||||
event.payload = {
|
event.payload = {
|
||||||
userEmail: 'test@test.com',
|
userEmail: 'test@test.com',
|
||||||
|
amount: 12.45,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -39,6 +45,7 @@ describe('PaymentSuccessEventHandler', () => {
|
|||||||
await createHandler().handle(event)
|
await createHandler().handle(event)
|
||||||
|
|
||||||
expect(analyticsStore.markActivity).toHaveBeenCalled()
|
expect(analyticsStore.markActivity).toHaveBeenCalled()
|
||||||
|
expect(statisticsStore.incrementMeasure).toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should not mark payment failed for analytics if user is not found', async () => {
|
it('should not mark payment failed for analytics if user is not found', async () => {
|
||||||
|
|||||||
@@ -1,4 +1,10 @@
|
|||||||
import { AnalyticsActivity, AnalyticsStoreInterface, Period } from '@standardnotes/analytics'
|
import {
|
||||||
|
AnalyticsActivity,
|
||||||
|
AnalyticsStoreInterface,
|
||||||
|
Period,
|
||||||
|
StatisticsMeasure,
|
||||||
|
StatisticsStoreInterface,
|
||||||
|
} from '@standardnotes/analytics'
|
||||||
import { DomainEventHandlerInterface, PaymentSuccessEvent } from '@standardnotes/domain-events'
|
import { DomainEventHandlerInterface, PaymentSuccessEvent } from '@standardnotes/domain-events'
|
||||||
import { inject, injectable } from 'inversify'
|
import { inject, injectable } from 'inversify'
|
||||||
|
|
||||||
@@ -12,6 +18,7 @@ export class PaymentSuccessEventHandler implements DomainEventHandlerInterface {
|
|||||||
@inject(TYPES.UserRepository) private userRepository: UserRepositoryInterface,
|
@inject(TYPES.UserRepository) private userRepository: UserRepositoryInterface,
|
||||||
@inject(TYPES.GetUserAnalyticsId) private getUserAnalyticsId: GetUserAnalyticsId,
|
@inject(TYPES.GetUserAnalyticsId) private getUserAnalyticsId: GetUserAnalyticsId,
|
||||||
@inject(TYPES.AnalyticsStore) private analyticsStore: AnalyticsStoreInterface,
|
@inject(TYPES.AnalyticsStore) private analyticsStore: AnalyticsStoreInterface,
|
||||||
|
@inject(TYPES.StatisticsStore) private statisticsStore: StatisticsStoreInterface,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async handle(event: PaymentSuccessEvent): Promise<void> {
|
async handle(event: PaymentSuccessEvent): Promise<void> {
|
||||||
@@ -26,5 +33,11 @@ export class PaymentSuccessEventHandler implements DomainEventHandlerInterface {
|
|||||||
Period.ThisWeek,
|
Period.ThisWeek,
|
||||||
Period.ThisMonth,
|
Period.ThisMonth,
|
||||||
])
|
])
|
||||||
|
|
||||||
|
await this.statisticsStore.incrementMeasure(StatisticsMeasure.Income, event.payload.amount, [
|
||||||
|
Period.Today,
|
||||||
|
Period.ThisWeek,
|
||||||
|
Period.ThisMonth,
|
||||||
|
])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import 'reflect-metadata'
|
||||||
|
|
||||||
|
import { RefundProcessedEvent } from '@standardnotes/domain-events'
|
||||||
|
import { Period, StatisticsMeasure, StatisticsStoreInterface } from '@standardnotes/analytics'
|
||||||
|
|
||||||
|
import { RefundProcessedEventHandler } from './RefundProcessedEventHandler'
|
||||||
|
|
||||||
|
describe('RefundProcessedEventHandler', () => {
|
||||||
|
let event: RefundProcessedEvent
|
||||||
|
let statisticsStore: StatisticsStoreInterface
|
||||||
|
|
||||||
|
const createHandler = () => new RefundProcessedEventHandler(statisticsStore)
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
statisticsStore = {} as jest.Mocked<StatisticsStoreInterface>
|
||||||
|
statisticsStore.incrementMeasure = jest.fn()
|
||||||
|
|
||||||
|
event = {} as jest.Mocked<RefundProcessedEvent>
|
||||||
|
event.payload = {
|
||||||
|
userEmail: 'test@test.com',
|
||||||
|
amount: 12.45,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should mark refunds for statistics', async () => {
|
||||||
|
await createHandler().handle(event)
|
||||||
|
|
||||||
|
expect(statisticsStore.incrementMeasure).toHaveBeenCalledWith(StatisticsMeasure.Refunds, 12.45, [
|
||||||
|
Period.Today,
|
||||||
|
Period.ThisWeek,
|
||||||
|
Period.ThisMonth,
|
||||||
|
])
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { Period, StatisticsMeasure, StatisticsStoreInterface } from '@standardnotes/analytics'
|
||||||
|
import { DomainEventHandlerInterface, RefundProcessedEvent } from '@standardnotes/domain-events'
|
||||||
|
import { inject, injectable } from 'inversify'
|
||||||
|
|
||||||
|
import TYPES from '../../Bootstrap/Types'
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class RefundProcessedEventHandler implements DomainEventHandlerInterface {
|
||||||
|
constructor(@inject(TYPES.StatisticsStore) private statisticsStore: StatisticsStoreInterface) {}
|
||||||
|
|
||||||
|
async handle(event: RefundProcessedEvent): Promise<void> {
|
||||||
|
await this.statisticsStore.incrementMeasure(StatisticsMeasure.Refunds, event.payload.amount, [
|
||||||
|
Period.Today,
|
||||||
|
Period.ThisWeek,
|
||||||
|
Period.ThisMonth,
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,10 +8,11 @@ import * as dayjs from 'dayjs'
|
|||||||
import { SubscriptionCancelledEventHandler } from './SubscriptionCancelledEventHandler'
|
import { SubscriptionCancelledEventHandler } from './SubscriptionCancelledEventHandler'
|
||||||
import { UserSubscriptionRepositoryInterface } from '../Subscription/UserSubscriptionRepositoryInterface'
|
import { UserSubscriptionRepositoryInterface } from '../Subscription/UserSubscriptionRepositoryInterface'
|
||||||
import { OfflineUserSubscriptionRepositoryInterface } from '../Subscription/OfflineUserSubscriptionRepositoryInterface'
|
import { OfflineUserSubscriptionRepositoryInterface } from '../Subscription/OfflineUserSubscriptionRepositoryInterface'
|
||||||
import { AnalyticsStoreInterface } from '@standardnotes/analytics'
|
import { AnalyticsStoreInterface, Period, StatisticsMeasure, StatisticsStoreInterface } from '@standardnotes/analytics'
|
||||||
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
|
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
|
||||||
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
|
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
|
||||||
import { User } from '../User/User'
|
import { User } from '../User/User'
|
||||||
|
import { UserSubscription } from '../Subscription/UserSubscription'
|
||||||
|
|
||||||
describe('SubscriptionCancelledEventHandler', () => {
|
describe('SubscriptionCancelledEventHandler', () => {
|
||||||
let userSubscriptionRepository: UserSubscriptionRepositoryInterface
|
let userSubscriptionRepository: UserSubscriptionRepositoryInterface
|
||||||
@@ -20,6 +21,7 @@ describe('SubscriptionCancelledEventHandler', () => {
|
|||||||
let userRepository: UserRepositoryInterface
|
let userRepository: UserRepositoryInterface
|
||||||
let getUserAnalyticsId: GetUserAnalyticsId
|
let getUserAnalyticsId: GetUserAnalyticsId
|
||||||
let analyticsStore: AnalyticsStoreInterface
|
let analyticsStore: AnalyticsStoreInterface
|
||||||
|
let statisticsStore: StatisticsStoreInterface
|
||||||
let timestamp: number
|
let timestamp: number
|
||||||
|
|
||||||
const createHandler = () =>
|
const createHandler = () =>
|
||||||
@@ -29,6 +31,7 @@ describe('SubscriptionCancelledEventHandler', () => {
|
|||||||
userRepository,
|
userRepository,
|
||||||
getUserAnalyticsId,
|
getUserAnalyticsId,
|
||||||
analyticsStore,
|
analyticsStore,
|
||||||
|
statisticsStore,
|
||||||
)
|
)
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -43,8 +46,16 @@ describe('SubscriptionCancelledEventHandler', () => {
|
|||||||
analyticsStore = {} as jest.Mocked<AnalyticsStoreInterface>
|
analyticsStore = {} as jest.Mocked<AnalyticsStoreInterface>
|
||||||
analyticsStore.markActivity = jest.fn()
|
analyticsStore.markActivity = jest.fn()
|
||||||
|
|
||||||
|
statisticsStore = {} as jest.Mocked<StatisticsStoreInterface>
|
||||||
|
statisticsStore.incrementMeasure = jest.fn()
|
||||||
|
|
||||||
|
const userSubscription = {
|
||||||
|
createdAt: 1642395451515000,
|
||||||
|
} as jest.Mocked<UserSubscription>
|
||||||
|
|
||||||
userSubscriptionRepository = {} as jest.Mocked<UserSubscriptionRepositoryInterface>
|
userSubscriptionRepository = {} as jest.Mocked<UserSubscriptionRepositoryInterface>
|
||||||
userSubscriptionRepository.updateCancelled = jest.fn()
|
userSubscriptionRepository.updateCancelled = jest.fn()
|
||||||
|
userSubscriptionRepository.findBySubscriptionId = jest.fn().mockReturnValue([userSubscription])
|
||||||
|
|
||||||
offlineUserSubscriptionRepository = {} as jest.Mocked<OfflineUserSubscriptionRepositoryInterface>
|
offlineUserSubscriptionRepository = {} as jest.Mocked<OfflineUserSubscriptionRepositoryInterface>
|
||||||
offlineUserSubscriptionRepository.updateCancelled = jest.fn()
|
offlineUserSubscriptionRepository.updateCancelled = jest.fn()
|
||||||
@@ -59,14 +70,21 @@ describe('SubscriptionCancelledEventHandler', () => {
|
|||||||
subscriptionName: SubscriptionName.ProPlan,
|
subscriptionName: SubscriptionName.ProPlan,
|
||||||
timestamp,
|
timestamp,
|
||||||
offline: false,
|
offline: false,
|
||||||
|
replaced: false,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should update subscription cancelled', async () => {
|
it('should update subscription cancelled', async () => {
|
||||||
|
event.payload.timestamp = 1642395451516000
|
||||||
await createHandler().handle(event)
|
await createHandler().handle(event)
|
||||||
|
|
||||||
expect(userSubscriptionRepository.updateCancelled).toHaveBeenCalledWith(1, true, timestamp)
|
expect(userSubscriptionRepository.updateCancelled).toHaveBeenCalledWith(1, true, 1642395451516000)
|
||||||
expect(analyticsStore.markActivity).toHaveBeenCalled()
|
expect(analyticsStore.markActivity).toHaveBeenCalled()
|
||||||
|
expect(statisticsStore.incrementMeasure).toHaveBeenCalledWith(StatisticsMeasure.SubscriptionLength, 1000, [
|
||||||
|
Period.Today,
|
||||||
|
Period.ThisWeek,
|
||||||
|
Period.ThisMonth,
|
||||||
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should update subscription cancelled - user not found', async () => {
|
it('should update subscription cancelled - user not found', async () => {
|
||||||
|
|||||||
@@ -1,12 +1,19 @@
|
|||||||
import { DomainEventHandlerInterface, SubscriptionCancelledEvent } from '@standardnotes/domain-events'
|
import { DomainEventHandlerInterface, SubscriptionCancelledEvent } from '@standardnotes/domain-events'
|
||||||
import { inject, injectable } from 'inversify'
|
import { inject, injectable } from 'inversify'
|
||||||
|
import {
|
||||||
|
AnalyticsActivity,
|
||||||
|
AnalyticsStoreInterface,
|
||||||
|
Period,
|
||||||
|
StatisticsMeasure,
|
||||||
|
StatisticsStoreInterface,
|
||||||
|
} from '@standardnotes/analytics'
|
||||||
|
|
||||||
import TYPES from '../../Bootstrap/Types'
|
import TYPES from '../../Bootstrap/Types'
|
||||||
import { UserSubscriptionRepositoryInterface } from '../Subscription/UserSubscriptionRepositoryInterface'
|
import { UserSubscriptionRepositoryInterface } from '../Subscription/UserSubscriptionRepositoryInterface'
|
||||||
import { OfflineUserSubscriptionRepositoryInterface } from '../Subscription/OfflineUserSubscriptionRepositoryInterface'
|
import { OfflineUserSubscriptionRepositoryInterface } from '../Subscription/OfflineUserSubscriptionRepositoryInterface'
|
||||||
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
|
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
|
||||||
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
|
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
|
||||||
import { AnalyticsActivity, AnalyticsStoreInterface, Period } from '@standardnotes/analytics'
|
import { UserSubscription } from '../Subscription/UserSubscription'
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class SubscriptionCancelledEventHandler implements DomainEventHandlerInterface {
|
export class SubscriptionCancelledEventHandler implements DomainEventHandlerInterface {
|
||||||
@@ -17,16 +24,9 @@ export class SubscriptionCancelledEventHandler implements DomainEventHandlerInte
|
|||||||
@inject(TYPES.UserRepository) private userRepository: UserRepositoryInterface,
|
@inject(TYPES.UserRepository) private userRepository: UserRepositoryInterface,
|
||||||
@inject(TYPES.GetUserAnalyticsId) private getUserAnalyticsId: GetUserAnalyticsId,
|
@inject(TYPES.GetUserAnalyticsId) private getUserAnalyticsId: GetUserAnalyticsId,
|
||||||
@inject(TYPES.AnalyticsStore) private analyticsStore: AnalyticsStoreInterface,
|
@inject(TYPES.AnalyticsStore) private analyticsStore: AnalyticsStoreInterface,
|
||||||
|
@inject(TYPES.StatisticsStore) private statisticsStore: StatisticsStoreInterface,
|
||||||
) {}
|
) {}
|
||||||
async handle(event: SubscriptionCancelledEvent): Promise<void> {
|
async handle(event: SubscriptionCancelledEvent): Promise<void> {
|
||||||
if (event.payload.offline) {
|
|
||||||
await this.updateOfflineSubscriptionCancelled(event.payload.subscriptionId, event.payload.timestamp)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.updateSubscriptionCancelled(event.payload.subscriptionId, event.payload.timestamp)
|
|
||||||
|
|
||||||
const user = await this.userRepository.findOneByEmail(event.payload.userEmail)
|
const user = await this.userRepository.findOneByEmail(event.payload.userEmail)
|
||||||
if (user !== null) {
|
if (user !== null) {
|
||||||
const { analyticsId } = await this.getUserAnalyticsId.execute({ userUuid: user.uuid })
|
const { analyticsId } = await this.getUserAnalyticsId.execute({ userUuid: user.uuid })
|
||||||
@@ -35,7 +35,38 @@ export class SubscriptionCancelledEventHandler implements DomainEventHandlerInte
|
|||||||
Period.ThisWeek,
|
Period.ThisWeek,
|
||||||
Period.ThisMonth,
|
Period.ThisMonth,
|
||||||
])
|
])
|
||||||
|
|
||||||
|
const subscriptions = await this.userSubscriptionRepository.findBySubscriptionId(event.payload.subscriptionId)
|
||||||
|
if (subscriptions.length !== 0) {
|
||||||
|
const lastSubscription = subscriptions.shift() as UserSubscription
|
||||||
|
const subscriptionLength = event.payload.timestamp - lastSubscription.createdAt
|
||||||
|
await this.statisticsStore.incrementMeasure(StatisticsMeasure.SubscriptionLength, subscriptionLength, [
|
||||||
|
Period.Today,
|
||||||
|
Period.ThisWeek,
|
||||||
|
Period.ThisMonth,
|
||||||
|
])
|
||||||
|
|
||||||
|
const lastPurchaseTime = lastSubscription.renewedAt ?? lastSubscription.updatedAt
|
||||||
|
const remainingSubscriptionTime = lastSubscription.endsAt - event.payload.timestamp
|
||||||
|
const totalSubscriptionTime = lastSubscription.endsAt - lastPurchaseTime
|
||||||
|
|
||||||
|
const remainingSubscriptionPercentage = Math.floor((remainingSubscriptionTime / totalSubscriptionTime) * 100)
|
||||||
|
|
||||||
|
await this.statisticsStore.incrementMeasure(
|
||||||
|
StatisticsMeasure.RemainingSubscriptionTimePercentage,
|
||||||
|
remainingSubscriptionPercentage,
|
||||||
|
[Period.Today, Period.ThisWeek, Period.ThisMonth],
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (event.payload.offline) {
|
||||||
|
await this.updateOfflineSubscriptionCancelled(event.payload.subscriptionId, event.payload.timestamp)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.updateSubscriptionCancelled(event.payload.subscriptionId, event.payload.timestamp)
|
||||||
}
|
}
|
||||||
|
|
||||||
private async updateSubscriptionCancelled(subscriptionId: number, timestamp: number): Promise<void> {
|
private async updateSubscriptionCancelled(subscriptionId: number, timestamp: number): Promise<void> {
|
||||||
|
|||||||
@@ -16,9 +16,10 @@ import { OfflineUserSubscriptionRepositoryInterface } from '../Subscription/Offl
|
|||||||
import { OfflineUserSubscription } from '../Subscription/OfflineUserSubscription'
|
import { OfflineUserSubscription } from '../Subscription/OfflineUserSubscription'
|
||||||
import { SubscriptionSettingServiceInterface } from '../Setting/SubscriptionSettingServiceInterface'
|
import { SubscriptionSettingServiceInterface } from '../Setting/SubscriptionSettingServiceInterface'
|
||||||
import { UserSubscriptionType } from '../Subscription/UserSubscriptionType'
|
import { UserSubscriptionType } from '../Subscription/UserSubscriptionType'
|
||||||
import { AnalyticsStoreInterface, Period } from '@standardnotes/analytics'
|
import { AnalyticsStoreInterface, Period, StatisticsStoreInterface } from '@standardnotes/analytics'
|
||||||
import { AnalyticsEntity } from '../Analytics/AnalyticsEntity'
|
import { AnalyticsEntity } from '../Analytics/AnalyticsEntity'
|
||||||
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
|
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
|
||||||
|
import { TimerInterface } from '@standardnotes/time'
|
||||||
|
|
||||||
describe('SubscriptionPurchasedEventHandler', () => {
|
describe('SubscriptionPurchasedEventHandler', () => {
|
||||||
let userRepository: UserRepositoryInterface
|
let userRepository: UserRepositoryInterface
|
||||||
@@ -35,6 +36,8 @@ describe('SubscriptionPurchasedEventHandler', () => {
|
|||||||
let getUserAnalyticsId: GetUserAnalyticsId
|
let getUserAnalyticsId: GetUserAnalyticsId
|
||||||
let analyticsStore: AnalyticsStoreInterface
|
let analyticsStore: AnalyticsStoreInterface
|
||||||
let timestamp: number
|
let timestamp: number
|
||||||
|
let statisticsStore: StatisticsStoreInterface
|
||||||
|
let timer: TimerInterface
|
||||||
|
|
||||||
const createHandler = () =>
|
const createHandler = () =>
|
||||||
new SubscriptionPurchasedEventHandler(
|
new SubscriptionPurchasedEventHandler(
|
||||||
@@ -45,6 +48,8 @@ describe('SubscriptionPurchasedEventHandler', () => {
|
|||||||
subscriptionSettingService,
|
subscriptionSettingService,
|
||||||
getUserAnalyticsId,
|
getUserAnalyticsId,
|
||||||
analyticsStore,
|
analyticsStore,
|
||||||
|
statisticsStore,
|
||||||
|
timer,
|
||||||
logger,
|
logger,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -66,7 +71,14 @@ describe('SubscriptionPurchasedEventHandler', () => {
|
|||||||
userRepository.findOneByEmail = jest.fn().mockReturnValue(user)
|
userRepository.findOneByEmail = jest.fn().mockReturnValue(user)
|
||||||
userRepository.save = jest.fn().mockReturnValue(user)
|
userRepository.save = jest.fn().mockReturnValue(user)
|
||||||
|
|
||||||
|
statisticsStore = {} as jest.Mocked<StatisticsStoreInterface>
|
||||||
|
statisticsStore.incrementMeasure = jest.fn()
|
||||||
|
|
||||||
|
timer = {} as jest.Mocked<TimerInterface>
|
||||||
|
timer.convertDateToMicroseconds = jest.fn().mockReturnValue(1)
|
||||||
|
|
||||||
userSubscriptionRepository = {} as jest.Mocked<UserSubscriptionRepositoryInterface>
|
userSubscriptionRepository = {} as jest.Mocked<UserSubscriptionRepositoryInterface>
|
||||||
|
userSubscriptionRepository.countByUserUuid = jest.fn().mockReturnValue(0)
|
||||||
userSubscriptionRepository.save = jest.fn().mockReturnValue(subscription)
|
userSubscriptionRepository.save = jest.fn().mockReturnValue(subscription)
|
||||||
|
|
||||||
offlineUserSubscription = {} as jest.Mocked<OfflineUserSubscription>
|
offlineUserSubscription = {} as jest.Mocked<OfflineUserSubscription>
|
||||||
@@ -146,6 +158,15 @@ describe('SubscriptionPurchasedEventHandler', () => {
|
|||||||
updatedAt: expect.any(Number),
|
updatedAt: expect.any(Number),
|
||||||
cancelled: false,
|
cancelled: false,
|
||||||
})
|
})
|
||||||
|
expect(statisticsStore.incrementMeasure).toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should not measure registration to subscription time if this is not user's first subscription", async () => {
|
||||||
|
userSubscriptionRepository.countByUserUuid = jest.fn().mockReturnValue(1)
|
||||||
|
|
||||||
|
await createHandler().handle(event)
|
||||||
|
|
||||||
|
expect(statisticsStore.incrementMeasure).not.toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should update analytics on limited discount offer purchasing', async () => {
|
it('should update analytics on limited discount offer purchasing', async () => {
|
||||||
|
|||||||
@@ -13,8 +13,15 @@ import { OfflineUserSubscription } from '../Subscription/OfflineUserSubscription
|
|||||||
import { OfflineUserSubscriptionRepositoryInterface } from '../Subscription/OfflineUserSubscriptionRepositoryInterface'
|
import { OfflineUserSubscriptionRepositoryInterface } from '../Subscription/OfflineUserSubscriptionRepositoryInterface'
|
||||||
import { UserSubscriptionType } from '../Subscription/UserSubscriptionType'
|
import { UserSubscriptionType } from '../Subscription/UserSubscriptionType'
|
||||||
import { SubscriptionSettingServiceInterface } from '../Setting/SubscriptionSettingServiceInterface'
|
import { SubscriptionSettingServiceInterface } from '../Setting/SubscriptionSettingServiceInterface'
|
||||||
import { AnalyticsActivity, AnalyticsStoreInterface, Period } from '@standardnotes/analytics'
|
import {
|
||||||
|
AnalyticsActivity,
|
||||||
|
AnalyticsStoreInterface,
|
||||||
|
Period,
|
||||||
|
StatisticsMeasure,
|
||||||
|
StatisticsStoreInterface,
|
||||||
|
} from '@standardnotes/analytics'
|
||||||
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
|
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
|
||||||
|
import { TimerInterface } from '@standardnotes/time'
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class SubscriptionPurchasedEventHandler implements DomainEventHandlerInterface {
|
export class SubscriptionPurchasedEventHandler implements DomainEventHandlerInterface {
|
||||||
@@ -27,6 +34,8 @@ export class SubscriptionPurchasedEventHandler implements DomainEventHandlerInte
|
|||||||
@inject(TYPES.SubscriptionSettingService) private subscriptionSettingService: SubscriptionSettingServiceInterface,
|
@inject(TYPES.SubscriptionSettingService) private subscriptionSettingService: SubscriptionSettingServiceInterface,
|
||||||
@inject(TYPES.GetUserAnalyticsId) private getUserAnalyticsId: GetUserAnalyticsId,
|
@inject(TYPES.GetUserAnalyticsId) private getUserAnalyticsId: GetUserAnalyticsId,
|
||||||
@inject(TYPES.AnalyticsStore) private analyticsStore: AnalyticsStoreInterface,
|
@inject(TYPES.AnalyticsStore) private analyticsStore: AnalyticsStoreInterface,
|
||||||
|
@inject(TYPES.StatisticsStore) private statisticsStore: StatisticsStoreInterface,
|
||||||
|
@inject(TYPES.Timer) private timer: TimerInterface,
|
||||||
@inject(TYPES.Logger) private logger: Logger,
|
@inject(TYPES.Logger) private logger: Logger,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@@ -52,6 +61,8 @@ export class SubscriptionPurchasedEventHandler implements DomainEventHandlerInte
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const previousSubscriptionCount = await this.userSubscriptionRepository.countByUserUuid(user.uuid)
|
||||||
|
|
||||||
const userSubscription = await this.createSubscription(
|
const userSubscription = await this.createSubscription(
|
||||||
event.payload.subscriptionId,
|
event.payload.subscriptionId,
|
||||||
event.payload.subscriptionName,
|
event.payload.subscriptionName,
|
||||||
@@ -80,6 +91,14 @@ export class SubscriptionPurchasedEventHandler implements DomainEventHandlerInte
|
|||||||
Period.Today,
|
Period.Today,
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (previousSubscriptionCount === 0) {
|
||||||
|
await this.statisticsStore.incrementMeasure(
|
||||||
|
StatisticsMeasure.RegistrationToSubscriptionTime,
|
||||||
|
event.payload.timestamp - this.timer.convertDateToMicroseconds(user.createdAt),
|
||||||
|
[Period.Today, Period.ThisWeek, Period.ThisMonth],
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async addUserRole(user: User, subscriptionName: SubscriptionName): Promise<void> {
|
private async addUserRole(user: User, subscriptionName: SubscriptionName): Promise<void> {
|
||||||
|
|||||||
+8
-1
@@ -4,17 +4,23 @@ import { UserDisabledSessionUserAgentLoggingEvent } from '@standardnotes/domain-
|
|||||||
import { SessionRepositoryInterface } from '../Session/SessionRepositoryInterface'
|
import { SessionRepositoryInterface } from '../Session/SessionRepositoryInterface'
|
||||||
|
|
||||||
import { UserDisabledSessionUserAgentLoggingEventHandler } from './UserDisabledSessionUserAgentLoggingEventHandler'
|
import { UserDisabledSessionUserAgentLoggingEventHandler } from './UserDisabledSessionUserAgentLoggingEventHandler'
|
||||||
|
import { RevokedSessionRepositoryInterface } from '../Session/RevokedSessionRepositoryInterface'
|
||||||
|
|
||||||
describe('UserDisabledSessionUserAgentLoggingEventHandler', () => {
|
describe('UserDisabledSessionUserAgentLoggingEventHandler', () => {
|
||||||
let sessionRepository: SessionRepositoryInterface
|
let sessionRepository: SessionRepositoryInterface
|
||||||
|
let revokedSessionRepository: RevokedSessionRepositoryInterface
|
||||||
let event: UserDisabledSessionUserAgentLoggingEvent
|
let event: UserDisabledSessionUserAgentLoggingEvent
|
||||||
|
|
||||||
const createHandler = () => new UserDisabledSessionUserAgentLoggingEventHandler(sessionRepository)
|
const createHandler = () =>
|
||||||
|
new UserDisabledSessionUserAgentLoggingEventHandler(sessionRepository, revokedSessionRepository)
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
sessionRepository = {} as jest.Mocked<SessionRepositoryInterface>
|
sessionRepository = {} as jest.Mocked<SessionRepositoryInterface>
|
||||||
sessionRepository.clearUserAgentByUserUuid = jest.fn()
|
sessionRepository.clearUserAgentByUserUuid = jest.fn()
|
||||||
|
|
||||||
|
revokedSessionRepository = {} as jest.Mocked<RevokedSessionRepositoryInterface>
|
||||||
|
revokedSessionRepository.clearUserAgentByUserUuid = jest.fn()
|
||||||
|
|
||||||
event = {} as jest.Mocked<UserDisabledSessionUserAgentLoggingEvent>
|
event = {} as jest.Mocked<UserDisabledSessionUserAgentLoggingEvent>
|
||||||
event.payload = {
|
event.payload = {
|
||||||
userUuid: '1-2-3',
|
userUuid: '1-2-3',
|
||||||
@@ -26,5 +32,6 @@ describe('UserDisabledSessionUserAgentLoggingEventHandler', () => {
|
|||||||
await createHandler().handle(event)
|
await createHandler().handle(event)
|
||||||
|
|
||||||
expect(sessionRepository.clearUserAgentByUserUuid).toHaveBeenCalledWith('1-2-3')
|
expect(sessionRepository.clearUserAgentByUserUuid).toHaveBeenCalledWith('1-2-3')
|
||||||
|
expect(revokedSessionRepository.clearUserAgentByUserUuid).toHaveBeenCalledWith('1-2-3')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2,13 +2,18 @@ import { DomainEventHandlerInterface, UserDisabledSessionUserAgentLoggingEvent }
|
|||||||
import { inject, injectable } from 'inversify'
|
import { inject, injectable } from 'inversify'
|
||||||
|
|
||||||
import TYPES from '../../Bootstrap/Types'
|
import TYPES from '../../Bootstrap/Types'
|
||||||
|
import { RevokedSessionRepositoryInterface } from '../Session/RevokedSessionRepositoryInterface'
|
||||||
import { SessionRepositoryInterface } from '../Session/SessionRepositoryInterface'
|
import { SessionRepositoryInterface } from '../Session/SessionRepositoryInterface'
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class UserDisabledSessionUserAgentLoggingEventHandler implements DomainEventHandlerInterface {
|
export class UserDisabledSessionUserAgentLoggingEventHandler implements DomainEventHandlerInterface {
|
||||||
constructor(@inject(TYPES.SessionRepository) private sessionRepository: SessionRepositoryInterface) {}
|
constructor(
|
||||||
|
@inject(TYPES.SessionRepository) private sessionRepository: SessionRepositoryInterface,
|
||||||
|
@inject(TYPES.SessionRepository) private revokedSessionRepository: RevokedSessionRepositoryInterface,
|
||||||
|
) {}
|
||||||
|
|
||||||
async handle(event: UserDisabledSessionUserAgentLoggingEvent): Promise<void> {
|
async handle(event: UserDisabledSessionUserAgentLoggingEvent): Promise<void> {
|
||||||
await this.sessionRepository.clearUserAgentByUserUuid(event.payload.userUuid)
|
await this.sessionRepository.clearUserAgentByUserUuid(event.payload.userUuid)
|
||||||
|
await this.revokedSessionRepository.clearUserAgentByUserUuid(event.payload.userUuid)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,4 +36,25 @@ export class RevokedSession {
|
|||||||
)
|
)
|
||||||
@JoinColumn({ name: 'user_uuid', referencedColumnName: 'uuid' })
|
@JoinColumn({ name: 'user_uuid', referencedColumnName: 'uuid' })
|
||||||
declare user: Promise<User>
|
declare user: Promise<User>
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
name: 'received_at',
|
||||||
|
type: 'datetime',
|
||||||
|
nullable: true,
|
||||||
|
})
|
||||||
|
declare receivedAt: Date
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
name: 'user_agent',
|
||||||
|
type: 'text',
|
||||||
|
nullable: true,
|
||||||
|
})
|
||||||
|
declare userAgent: string | null
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
name: 'api_version',
|
||||||
|
length: 255,
|
||||||
|
nullable: true,
|
||||||
|
})
|
||||||
|
declare apiVersion: string
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { Uuid } from '@standardnotes/common'
|
||||||
import { RevokedSession } from './RevokedSession'
|
import { RevokedSession } from './RevokedSession'
|
||||||
|
|
||||||
export interface RevokedSessionRepositoryInterface {
|
export interface RevokedSessionRepositoryInterface {
|
||||||
@@ -5,4 +6,5 @@ export interface RevokedSessionRepositoryInterface {
|
|||||||
findAllByUserUuid(userUuid: string): Promise<Array<RevokedSession>>
|
findAllByUserUuid(userUuid: string): Promise<Array<RevokedSession>>
|
||||||
save(revokedSession: RevokedSession): Promise<RevokedSession>
|
save(revokedSession: RevokedSession): Promise<RevokedSession>
|
||||||
remove(revokedSession: RevokedSession): Promise<RevokedSession>
|
remove(revokedSession: RevokedSession): Promise<RevokedSession>
|
||||||
|
clearUserAgentByUserUuid(userUuid: Uuid): Promise<void>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import 'reflect-metadata'
|
|||||||
import * as winston from 'winston'
|
import * as winston from 'winston'
|
||||||
import { TimerInterface } from '@standardnotes/time'
|
import { TimerInterface } from '@standardnotes/time'
|
||||||
|
|
||||||
|
import { ApiVersion } from '../Api/ApiVersion'
|
||||||
import { Session } from './Session'
|
import { Session } from './Session'
|
||||||
import { SessionRepositoryInterface } from './SessionRepositoryInterface'
|
import { SessionRepositoryInterface } from './SessionRepositoryInterface'
|
||||||
import { SessionService } from './SessionService'
|
import { SessionService } from './SessionService'
|
||||||
@@ -47,6 +48,7 @@ describe('SessionService', () => {
|
|||||||
session.uuid = '2e1e43'
|
session.uuid = '2e1e43'
|
||||||
session.userUuid = '1-2-3'
|
session.userUuid = '1-2-3'
|
||||||
session.userAgent = 'Chrome'
|
session.userAgent = 'Chrome'
|
||||||
|
session.apiVersion = ApiVersion.v20200115
|
||||||
session.hashedAccessToken = '4e07408562bedb8b60ce05c1decfe3ad16b72230967de01f640b7e4729b49fce'
|
session.hashedAccessToken = '4e07408562bedb8b60ce05c1decfe3ad16b72230967de01f640b7e4729b49fce'
|
||||||
session.hashedRefreshToken = '4e07408562bedb8b60ce05c1decfe3ad16b72230967de01f640b7e4729b49fce'
|
session.hashedRefreshToken = '4e07408562bedb8b60ce05c1decfe3ad16b72230967de01f640b7e4729b49fce'
|
||||||
|
|
||||||
@@ -80,6 +82,7 @@ describe('SessionService', () => {
|
|||||||
|
|
||||||
timer = {} as jest.Mocked<TimerInterface>
|
timer = {} as jest.Mocked<TimerInterface>
|
||||||
timer.convertStringDateToMilliseconds = jest.fn().mockReturnValue(123)
|
timer.convertStringDateToMilliseconds = jest.fn().mockReturnValue(123)
|
||||||
|
timer.getUTCDate = jest.fn().mockReturnValue(new Date(1))
|
||||||
|
|
||||||
deviceDetector = {} as jest.Mocked<UAParser>
|
deviceDetector = {} as jest.Mocked<UAParser>
|
||||||
deviceDetector.setUA = jest.fn().mockReturnThis()
|
deviceDetector.setUA = jest.fn().mockReturnThis()
|
||||||
@@ -111,6 +114,7 @@ describe('SessionService', () => {
|
|||||||
expect(revokedSessionRepository.save).toHaveBeenCalledWith({
|
expect(revokedSessionRepository.save).toHaveBeenCalledWith({
|
||||||
uuid: '2e1e43',
|
uuid: '2e1e43',
|
||||||
received: true,
|
received: true,
|
||||||
|
receivedAt: new Date(1),
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -476,6 +480,8 @@ describe('SessionService', () => {
|
|||||||
expect(revokedSessionRepository.save).toHaveBeenCalledWith({
|
expect(revokedSessionRepository.save).toHaveBeenCalledWith({
|
||||||
uuid: '2e1e43',
|
uuid: '2e1e43',
|
||||||
userUuid: '1-2-3',
|
userUuid: '1-2-3',
|
||||||
|
userAgent: 'Chrome',
|
||||||
|
apiVersion: '20200115',
|
||||||
createdAt: expect.any(Date),
|
createdAt: expect.any(Date),
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -203,6 +203,7 @@ export class SessionService implements SessionServiceInterface {
|
|||||||
|
|
||||||
async markRevokedSessionAsReceived(revokedSession: RevokedSession): Promise<RevokedSession> {
|
async markRevokedSessionAsReceived(revokedSession: RevokedSession): Promise<RevokedSession> {
|
||||||
revokedSession.received = true
|
revokedSession.received = true
|
||||||
|
revokedSession.receivedAt = this.timer.getUTCDate()
|
||||||
|
|
||||||
return this.revokedSessionRepository.save(revokedSession)
|
return this.revokedSessionRepository.save(revokedSession)
|
||||||
}
|
}
|
||||||
@@ -224,7 +225,9 @@ export class SessionService implements SessionServiceInterface {
|
|||||||
const revokedSession = new RevokedSession()
|
const revokedSession = new RevokedSession()
|
||||||
revokedSession.uuid = session.uuid
|
revokedSession.uuid = session.uuid
|
||||||
revokedSession.userUuid = session.userUuid
|
revokedSession.userUuid = session.userUuid
|
||||||
revokedSession.createdAt = dayjs.utc().toDate()
|
revokedSession.createdAt = this.timer.getUTCDate()
|
||||||
|
revokedSession.apiVersion = session.apiVersion
|
||||||
|
revokedSession.userAgent = session.userAgent
|
||||||
|
|
||||||
return this.revokedSessionRepository.save(revokedSession)
|
return this.revokedSessionRepository.save(revokedSession)
|
||||||
}
|
}
|
||||||
@@ -246,8 +249,8 @@ export class SessionService implements SessionServiceInterface {
|
|||||||
}
|
}
|
||||||
session.userUuid = dto.user.uuid
|
session.userUuid = dto.user.uuid
|
||||||
session.apiVersion = dto.apiVersion
|
session.apiVersion = dto.apiVersion
|
||||||
session.createdAt = dayjs.utc().toDate()
|
session.createdAt = this.timer.getUTCDate()
|
||||||
session.updatedAt = dayjs.utc().toDate()
|
session.updatedAt = this.timer.getUTCDate()
|
||||||
session.readonlyAccess = dto.readonlyAccess
|
session.readonlyAccess = dto.readonlyAccess
|
||||||
|
|
||||||
return session
|
return session
|
||||||
|
|||||||
+4
@@ -7,5 +7,9 @@ export interface SharedSubscriptionInvitationRepositoryInterface {
|
|||||||
findOneByUuidAndStatus(uuid: Uuid, status: InvitationStatus): Promise<SharedSubscriptionInvitation | null>
|
findOneByUuidAndStatus(uuid: Uuid, status: InvitationStatus): Promise<SharedSubscriptionInvitation | null>
|
||||||
findOneByUuid(uuid: Uuid): Promise<SharedSubscriptionInvitation | null>
|
findOneByUuid(uuid: Uuid): Promise<SharedSubscriptionInvitation | null>
|
||||||
findByInviterEmail(inviterEmail: string): Promise<SharedSubscriptionInvitation[]>
|
findByInviterEmail(inviterEmail: string): Promise<SharedSubscriptionInvitation[]>
|
||||||
|
findOneByInviteeAndInviterEmail(
|
||||||
|
inviteeEmail: string,
|
||||||
|
inviterEmail: string,
|
||||||
|
): Promise<SharedSubscriptionInvitation | null>
|
||||||
countByInviterEmailAndStatus(inviterEmail: Uuid, statuses: InvitationStatus[]): Promise<number>
|
countByInviterEmailAndStatus(inviterEmail: Uuid, statuses: InvitationStatus[]): Promise<number>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,6 @@ import { Uuid } from '@standardnotes/common'
|
|||||||
import { SubscriptionToken } from './SubscriptionToken'
|
import { SubscriptionToken } from './SubscriptionToken'
|
||||||
|
|
||||||
export interface SubscriptionTokenRepositoryInterface {
|
export interface SubscriptionTokenRepositoryInterface {
|
||||||
save(subscriptionToken: SubscriptionToken): Promise<void>
|
save(subscriptionToken: SubscriptionToken): Promise<boolean>
|
||||||
getUserUuidByToken(token: string): Promise<Uuid | undefined>
|
getUserUuidByToken(token: string): Promise<Uuid | undefined>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,13 @@ export class UserSubscription {
|
|||||||
@Index('updated_at')
|
@Index('updated_at')
|
||||||
declare updatedAt: number
|
declare updatedAt: number
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
name: 'renewed_at',
|
||||||
|
type: 'bigint',
|
||||||
|
nullable: true,
|
||||||
|
})
|
||||||
|
declare renewedAt: number | null
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
type: 'tinyint',
|
type: 'tinyint',
|
||||||
width: 1,
|
width: 1,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { UserSubscriptionType } from './UserSubscriptionType'
|
|||||||
|
|
||||||
export interface UserSubscriptionRepositoryInterface {
|
export interface UserSubscriptionRepositoryInterface {
|
||||||
findOneByUuid(uuid: Uuid): Promise<UserSubscription | null>
|
findOneByUuid(uuid: Uuid): Promise<UserSubscription | null>
|
||||||
|
countByUserUuid(userUuid: Uuid): Promise<number>
|
||||||
findOneByUserUuid(userUuid: Uuid): Promise<UserSubscription | null>
|
findOneByUserUuid(userUuid: Uuid): Promise<UserSubscription | null>
|
||||||
findOneByUserUuidAndSubscriptionId(userUuid: Uuid, subscriptionId: number): Promise<UserSubscription | null>
|
findOneByUserUuidAndSubscriptionId(userUuid: Uuid, subscriptionId: number): Promise<UserSubscription | null>
|
||||||
findBySubscriptionIdAndType(subscriptionId: number, type: UserSubscriptionType): Promise<UserSubscription[]>
|
findBySubscriptionIdAndType(subscriptionId: number, type: UserSubscriptionType): Promise<UserSubscription[]>
|
||||||
|
|||||||
+20
-13
@@ -16,6 +16,7 @@ import { DomainEventPublisherInterface, SharedSubscriptionInvitationCanceledEven
|
|||||||
import { DomainEventFactoryInterface } from '../../Event/DomainEventFactoryInterface'
|
import { DomainEventFactoryInterface } from '../../Event/DomainEventFactoryInterface'
|
||||||
import { InviterIdentifierType } from '../../SharedSubscription/InviterIdentifierType'
|
import { InviterIdentifierType } from '../../SharedSubscription/InviterIdentifierType'
|
||||||
import { InviteeIdentifierType } from '../../SharedSubscription/InviteeIdentifierType'
|
import { InviteeIdentifierType } from '../../SharedSubscription/InviteeIdentifierType'
|
||||||
|
import { Logger } from 'winston'
|
||||||
|
|
||||||
describe('CancelSharedSubscriptionInvitation', () => {
|
describe('CancelSharedSubscriptionInvitation', () => {
|
||||||
let sharedSubscriptionInvitationRepository: SharedSubscriptionInvitationRepositoryInterface
|
let sharedSubscriptionInvitationRepository: SharedSubscriptionInvitationRepositoryInterface
|
||||||
@@ -28,6 +29,7 @@ describe('CancelSharedSubscriptionInvitation', () => {
|
|||||||
let invitation: SharedSubscriptionInvitation
|
let invitation: SharedSubscriptionInvitation
|
||||||
let domainEventPublisher: DomainEventPublisherInterface
|
let domainEventPublisher: DomainEventPublisherInterface
|
||||||
let domainEventFactory: DomainEventFactoryInterface
|
let domainEventFactory: DomainEventFactoryInterface
|
||||||
|
let logger: Logger
|
||||||
|
|
||||||
const createUseCase = () =>
|
const createUseCase = () =>
|
||||||
new CancelSharedSubscriptionInvitation(
|
new CancelSharedSubscriptionInvitation(
|
||||||
@@ -38,6 +40,7 @@ describe('CancelSharedSubscriptionInvitation', () => {
|
|||||||
domainEventPublisher,
|
domainEventPublisher,
|
||||||
domainEventFactory,
|
domainEventFactory,
|
||||||
timer,
|
timer,
|
||||||
|
logger,
|
||||||
)
|
)
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -60,6 +63,9 @@ describe('CancelSharedSubscriptionInvitation', () => {
|
|||||||
inviteeIdentifierType: InviteeIdentifierType.Email,
|
inviteeIdentifierType: InviteeIdentifierType.Email,
|
||||||
} as jest.Mocked<SharedSubscriptionInvitation>
|
} as jest.Mocked<SharedSubscriptionInvitation>
|
||||||
|
|
||||||
|
logger = {} as jest.Mocked<Logger>
|
||||||
|
logger.debug = jest.fn()
|
||||||
|
|
||||||
sharedSubscriptionInvitationRepository = {} as jest.Mocked<SharedSubscriptionInvitationRepositoryInterface>
|
sharedSubscriptionInvitationRepository = {} as jest.Mocked<SharedSubscriptionInvitationRepositoryInterface>
|
||||||
sharedSubscriptionInvitationRepository.findOneByUuid = jest.fn().mockReturnValue(invitation)
|
sharedSubscriptionInvitationRepository.findOneByUuid = jest.fn().mockReturnValue(invitation)
|
||||||
sharedSubscriptionInvitationRepository.save = jest.fn()
|
sharedSubscriptionInvitationRepository.save = jest.fn()
|
||||||
@@ -126,7 +132,7 @@ describe('CancelSharedSubscriptionInvitation', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should cancel a shared subscription invitation without subscription removal is subscription is not found', async () => {
|
it('should cancel a shared subscription invitation without subscription removal if subscription is not found', async () => {
|
||||||
userSubscriptionRepository.findOneByUserUuidAndSubscriptionId = jest.fn().mockReturnValue(null)
|
userSubscriptionRepository.findOneByUserUuidAndSubscriptionId = jest.fn().mockReturnValue(null)
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
@@ -175,7 +181,7 @@ describe('CancelSharedSubscriptionInvitation', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should not cancel a shared subscription invitation if invitee is not found', async () => {
|
it('should cancel a shared subscription invitation without subscription removal if invitee is not found', async () => {
|
||||||
userRepository.findOneByEmail = jest.fn().mockReturnValue(null)
|
userRepository.findOneByEmail = jest.fn().mockReturnValue(null)
|
||||||
expect(
|
expect(
|
||||||
await createUseCase().execute({
|
await createUseCase().execute({
|
||||||
@@ -183,20 +189,21 @@ describe('CancelSharedSubscriptionInvitation', () => {
|
|||||||
inviterEmail: 'test@test.te',
|
inviterEmail: 'test@test.te',
|
||||||
}),
|
}),
|
||||||
).toEqual({
|
).toEqual({
|
||||||
success: false,
|
success: true,
|
||||||
})
|
})
|
||||||
})
|
|
||||||
|
|
||||||
it('should not cancel a shared subscription invitation if invitee is not found', async () => {
|
expect(sharedSubscriptionInvitationRepository.save).toHaveBeenCalledWith({
|
||||||
userRepository.findOneByEmail = jest.fn().mockReturnValue(null)
|
status: 'canceled',
|
||||||
expect(
|
subscriptionId: 3,
|
||||||
await createUseCase().execute({
|
updatedAt: 1,
|
||||||
sharedSubscriptionInvitationUuid: '1-2-3',
|
inviterIdentifier: 'test@test.te',
|
||||||
inviterEmail: 'test@test.te',
|
uuid: '1-2-3',
|
||||||
}),
|
inviterIdentifierType: 'email',
|
||||||
).toEqual({
|
inviteeIdentifier: 'invitee@test.te',
|
||||||
success: false,
|
inviteeIdentifierType: 'email',
|
||||||
})
|
})
|
||||||
|
expect(userSubscriptionRepository.save).not.toHaveBeenCalled()
|
||||||
|
expect(roleService.removeUserRole).not.toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should not cancel a shared subscription invitation if inviter subscription is not found', async () => {
|
it('should not cancel a shared subscription invitation if inviter subscription is not found', async () => {
|
||||||
|
|||||||
+27
-18
@@ -2,6 +2,7 @@ import { SubscriptionName } from '@standardnotes/common'
|
|||||||
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
|
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
|
||||||
import { TimerInterface } from '@standardnotes/time'
|
import { TimerInterface } from '@standardnotes/time'
|
||||||
import { inject, injectable } from 'inversify'
|
import { inject, injectable } from 'inversify'
|
||||||
|
import { Logger } from 'winston'
|
||||||
|
|
||||||
import TYPES from '../../../Bootstrap/Types'
|
import TYPES from '../../../Bootstrap/Types'
|
||||||
import { DomainEventFactoryInterface } from '../../Event/DomainEventFactoryInterface'
|
import { DomainEventFactoryInterface } from '../../Event/DomainEventFactoryInterface'
|
||||||
@@ -29,6 +30,7 @@ export class CancelSharedSubscriptionInvitation implements UseCaseInterface {
|
|||||||
@inject(TYPES.DomainEventPublisher) private domainEventPublisher: DomainEventPublisherInterface,
|
@inject(TYPES.DomainEventPublisher) private domainEventPublisher: DomainEventPublisherInterface,
|
||||||
@inject(TYPES.DomainEventFactory) private domainEventFactory: DomainEventFactoryInterface,
|
@inject(TYPES.DomainEventFactory) private domainEventFactory: DomainEventFactoryInterface,
|
||||||
@inject(TYPES.Timer) private timer: TimerInterface,
|
@inject(TYPES.Timer) private timer: TimerInterface,
|
||||||
|
@inject(TYPES.Logger) private logger: Logger,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async execute(dto: CancelSharedSubscriptionInvitationDTO): Promise<CancelSharedSubscriptionInvitationResponse> {
|
async execute(dto: CancelSharedSubscriptionInvitationDTO): Promise<CancelSharedSubscriptionInvitationResponse> {
|
||||||
@@ -36,29 +38,34 @@ export class CancelSharedSubscriptionInvitation implements UseCaseInterface {
|
|||||||
dto.sharedSubscriptionInvitationUuid,
|
dto.sharedSubscriptionInvitationUuid,
|
||||||
)
|
)
|
||||||
if (sharedSubscriptionInvitation === null) {
|
if (sharedSubscriptionInvitation === null) {
|
||||||
|
this.logger.debug(
|
||||||
|
`Could not find a shared subscription invitation with uuid ${dto.sharedSubscriptionInvitationUuid}`,
|
||||||
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dto.inviterEmail !== sharedSubscriptionInvitation.inviterIdentifier) {
|
if (dto.inviterEmail !== sharedSubscriptionInvitation.inviterIdentifier) {
|
||||||
|
this.logger.debug(
|
||||||
|
`Subscription belongs to a different inviter (${sharedSubscriptionInvitation.inviterIdentifier}). Modifier: ${dto.inviterEmail}`,
|
||||||
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const invitee = await this.userRepository.findOneByEmail(sharedSubscriptionInvitation.inviteeIdentifier)
|
const invitee = await this.userRepository.findOneByEmail(sharedSubscriptionInvitation.inviteeIdentifier)
|
||||||
if (invitee === null) {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const inviterUserSubscriptions = await this.userSubscriptionRepository.findBySubscriptionIdAndType(
|
const inviterUserSubscriptions = await this.userSubscriptionRepository.findBySubscriptionIdAndType(
|
||||||
sharedSubscriptionInvitation.subscriptionId,
|
sharedSubscriptionInvitation.subscriptionId,
|
||||||
UserSubscriptionType.Regular,
|
UserSubscriptionType.Regular,
|
||||||
)
|
)
|
||||||
if (inviterUserSubscriptions.length !== 1) {
|
if (inviterUserSubscriptions.length === 0) {
|
||||||
|
this.logger.debug(`Could not find a regular subscription with id ${sharedSubscriptionInvitation.subscriptionId}`)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
}
|
}
|
||||||
@@ -70,20 +77,22 @@ export class CancelSharedSubscriptionInvitation implements UseCaseInterface {
|
|||||||
|
|
||||||
await this.sharedSubscriptionInvitationRepository.save(sharedSubscriptionInvitation)
|
await this.sharedSubscriptionInvitationRepository.save(sharedSubscriptionInvitation)
|
||||||
|
|
||||||
await this.removeSharedSubscription(sharedSubscriptionInvitation.subscriptionId, invitee)
|
if (invitee !== null) {
|
||||||
|
await this.removeSharedSubscription(sharedSubscriptionInvitation.subscriptionId, invitee)
|
||||||
|
|
||||||
await this.roleService.removeUserRole(invitee, inviterUserSubscription.planName as SubscriptionName)
|
await this.roleService.removeUserRole(invitee, inviterUserSubscription.planName as SubscriptionName)
|
||||||
|
|
||||||
await this.domainEventPublisher.publish(
|
await this.domainEventPublisher.publish(
|
||||||
this.domainEventFactory.createSharedSubscriptionInvitationCanceledEvent({
|
this.domainEventFactory.createSharedSubscriptionInvitationCanceledEvent({
|
||||||
inviteeIdentifier: invitee.uuid,
|
inviteeIdentifier: invitee.uuid,
|
||||||
inviteeIdentifierType: InviteeIdentifierType.Uuid,
|
inviteeIdentifierType: InviteeIdentifierType.Uuid,
|
||||||
inviterEmail: sharedSubscriptionInvitation.inviterIdentifier,
|
inviterEmail: sharedSubscriptionInvitation.inviterIdentifier,
|
||||||
inviterSubscriptionId: sharedSubscriptionInvitation.subscriptionId,
|
inviterSubscriptionId: sharedSubscriptionInvitation.subscriptionId,
|
||||||
inviterSubscriptionUuid: inviterUserSubscription.uuid,
|
inviterSubscriptionUuid: inviterUserSubscription.uuid,
|
||||||
sharedSubscriptionInvitationUuid: sharedSubscriptionInvitation.uuid,
|
sharedSubscriptionInvitationUuid: sharedSubscriptionInvitation.uuid,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
|
|||||||
+173
@@ -0,0 +1,173 @@
|
|||||||
|
import 'reflect-metadata'
|
||||||
|
|
||||||
|
import { TokenEncoderInterface, CrossServiceTokenData } from '@standardnotes/security'
|
||||||
|
import { ProjectorInterface } from '../../../Projection/ProjectorInterface'
|
||||||
|
import { Session } from '../../Session/Session'
|
||||||
|
import { User } from '../../User/User'
|
||||||
|
import { Role } from '../../Role/Role'
|
||||||
|
import { UserRepositoryInterface } from '../../User/UserRepositoryInterface'
|
||||||
|
import { GetUserAnalyticsId } from '../GetUserAnalyticsId/GetUserAnalyticsId'
|
||||||
|
|
||||||
|
import { CreateCrossServiceToken } from './CreateCrossServiceToken'
|
||||||
|
|
||||||
|
describe('CreateCrossServiceToken', () => {
|
||||||
|
let userProjector: ProjectorInterface<User>
|
||||||
|
let sessionProjector: ProjectorInterface<Session>
|
||||||
|
let roleProjector: ProjectorInterface<Role>
|
||||||
|
let tokenEncoder: TokenEncoderInterface<CrossServiceTokenData>
|
||||||
|
let getUserAnalyticsId: GetUserAnalyticsId
|
||||||
|
let userRepository: UserRepositoryInterface
|
||||||
|
const jwtTTL = 60
|
||||||
|
|
||||||
|
let session: Session
|
||||||
|
let user: User
|
||||||
|
let role: Role
|
||||||
|
|
||||||
|
const createUseCase = (analyticsEnabled = true) =>
|
||||||
|
new CreateCrossServiceToken(
|
||||||
|
userProjector,
|
||||||
|
sessionProjector,
|
||||||
|
roleProjector,
|
||||||
|
tokenEncoder,
|
||||||
|
getUserAnalyticsId,
|
||||||
|
userRepository,
|
||||||
|
analyticsEnabled,
|
||||||
|
jwtTTL,
|
||||||
|
)
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
session = {} as jest.Mocked<Session>
|
||||||
|
|
||||||
|
user = {} as jest.Mocked<User>
|
||||||
|
user.roles = Promise.resolve([role])
|
||||||
|
|
||||||
|
userProjector = {} as jest.Mocked<ProjectorInterface<User>>
|
||||||
|
userProjector.projectSimple = jest.fn().mockReturnValue({ bar: 'baz' })
|
||||||
|
|
||||||
|
roleProjector = {} as jest.Mocked<ProjectorInterface<Role>>
|
||||||
|
roleProjector.projectSimple = jest.fn().mockReturnValue({ name: 'role1', uuid: '1-3-4' })
|
||||||
|
|
||||||
|
sessionProjector = {} as jest.Mocked<ProjectorInterface<Session>>
|
||||||
|
sessionProjector.projectCustom = jest.fn().mockReturnValue({ foo: 'bar' })
|
||||||
|
sessionProjector.projectSimple = jest.fn().mockReturnValue({ test: 'test' })
|
||||||
|
|
||||||
|
tokenEncoder = {} as jest.Mocked<TokenEncoderInterface<CrossServiceTokenData>>
|
||||||
|
tokenEncoder.encodeExpirableToken = jest.fn().mockReturnValue('foobar')
|
||||||
|
|
||||||
|
getUserAnalyticsId = {} as jest.Mocked<GetUserAnalyticsId>
|
||||||
|
getUserAnalyticsId.execute = jest.fn().mockReturnValue({ analyticsId: 123 })
|
||||||
|
|
||||||
|
userRepository = {} as jest.Mocked<UserRepositoryInterface>
|
||||||
|
userRepository.findOneByUuid = jest.fn().mockReturnValue(user)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should create a cross service token for user', async () => {
|
||||||
|
await createUseCase().execute({
|
||||||
|
user,
|
||||||
|
session,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(tokenEncoder.encodeExpirableToken).toHaveBeenCalledWith(
|
||||||
|
{
|
||||||
|
analyticsId: 123,
|
||||||
|
roles: [
|
||||||
|
{
|
||||||
|
name: 'role1',
|
||||||
|
uuid: '1-3-4',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
session: {
|
||||||
|
test: 'test',
|
||||||
|
},
|
||||||
|
user: {
|
||||||
|
bar: 'baz',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
60,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should create a cross service token for user - analytics disabled', async () => {
|
||||||
|
await createUseCase(false).execute({
|
||||||
|
user,
|
||||||
|
session,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(tokenEncoder.encodeExpirableToken).toHaveBeenCalledWith(
|
||||||
|
{
|
||||||
|
roles: [
|
||||||
|
{
|
||||||
|
name: 'role1',
|
||||||
|
uuid: '1-3-4',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
session: {
|
||||||
|
test: 'test',
|
||||||
|
},
|
||||||
|
user: {
|
||||||
|
bar: 'baz',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
60,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should create a cross service token for user without a session', async () => {
|
||||||
|
await createUseCase().execute({
|
||||||
|
user,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(tokenEncoder.encodeExpirableToken).toHaveBeenCalledWith(
|
||||||
|
{
|
||||||
|
analyticsId: 123,
|
||||||
|
roles: [
|
||||||
|
{
|
||||||
|
name: 'role1',
|
||||||
|
uuid: '1-3-4',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
user: {
|
||||||
|
bar: 'baz',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
60,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should create a cross service token for user by user uuid', async () => {
|
||||||
|
await createUseCase().execute({
|
||||||
|
userUuid: '1-2-3',
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(tokenEncoder.encodeExpirableToken).toHaveBeenCalledWith(
|
||||||
|
{
|
||||||
|
analyticsId: 123,
|
||||||
|
roles: [
|
||||||
|
{
|
||||||
|
name: 'role1',
|
||||||
|
uuid: '1-3-4',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
user: {
|
||||||
|
bar: 'baz',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
60,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should throw an error if user does not exist', async () => {
|
||||||
|
userRepository.findOneByUuid = jest.fn().mockReturnValue(null)
|
||||||
|
|
||||||
|
let caughtError = null
|
||||||
|
try {
|
||||||
|
await createUseCase().execute({
|
||||||
|
userUuid: '1-2-3',
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
caughtError = error
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(caughtError).not.toBeNull()
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
import { RoleName } from '@standardnotes/common'
|
||||||
|
import { TokenEncoderInterface, CrossServiceTokenData } from '@standardnotes/security'
|
||||||
|
|
||||||
|
import { inject, injectable } from 'inversify'
|
||||||
|
import TYPES from '../../../Bootstrap/Types'
|
||||||
|
import { ProjectorInterface } from '../../../Projection/ProjectorInterface'
|
||||||
|
import { Role } from '../../Role/Role'
|
||||||
|
import { Session } from '../../Session/Session'
|
||||||
|
import { User } from '../../User/User'
|
||||||
|
import { UserRepositoryInterface } from '../../User/UserRepositoryInterface'
|
||||||
|
import { GetUserAnalyticsId } from '../GetUserAnalyticsId/GetUserAnalyticsId'
|
||||||
|
import { UseCaseInterface } from '../UseCaseInterface'
|
||||||
|
import { CreateCrossServiceTokenDTO } from './CreateCrossServiceTokenDTO'
|
||||||
|
import { CreateCrossServiceTokenResponse } from './CreateCrossServiceTokenResponse'
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class CreateCrossServiceToken implements UseCaseInterface {
|
||||||
|
constructor(
|
||||||
|
@inject(TYPES.UserProjector) private userProjector: ProjectorInterface<User>,
|
||||||
|
@inject(TYPES.SessionProjector) private sessionProjector: ProjectorInterface<Session>,
|
||||||
|
@inject(TYPES.RoleProjector) private roleProjector: ProjectorInterface<Role>,
|
||||||
|
@inject(TYPES.CrossServiceTokenEncoder) private tokenEncoder: TokenEncoderInterface<CrossServiceTokenData>,
|
||||||
|
@inject(TYPES.GetUserAnalyticsId) private getUserAnalyticsId: GetUserAnalyticsId,
|
||||||
|
@inject(TYPES.UserRepository) private userRepository: UserRepositoryInterface,
|
||||||
|
@inject(TYPES.ANALYTICS_ENABLED) private analyticsEnabled: boolean,
|
||||||
|
@inject(TYPES.AUTH_JWT_TTL) private jwtTTL: number,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async execute(dto: CreateCrossServiceTokenDTO): Promise<CreateCrossServiceTokenResponse> {
|
||||||
|
let user: User | undefined | null = dto.user
|
||||||
|
if (user === undefined && dto.userUuid !== undefined) {
|
||||||
|
user = await this.userRepository.findOneByUuid(dto.userUuid)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
throw new Error(`Could not find user with uuid ${dto.userUuid}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const roles = await user.roles
|
||||||
|
|
||||||
|
const authTokenData: CrossServiceTokenData = {
|
||||||
|
user: this.projectUser(user),
|
||||||
|
roles: this.projectRoles(roles),
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.analyticsEnabled) {
|
||||||
|
const { analyticsId } = await this.getUserAnalyticsId.execute({ userUuid: user.uuid })
|
||||||
|
authTokenData.analyticsId = analyticsId
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dto.session !== undefined) {
|
||||||
|
authTokenData.session = this.projectSession(dto.session)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
token: this.tokenEncoder.encodeExpirableToken(authTokenData, this.jwtTTL),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private projectUser(user: User): { uuid: string; email: string } {
|
||||||
|
return <{ uuid: string; email: string }>this.userProjector.projectSimple(user)
|
||||||
|
}
|
||||||
|
|
||||||
|
private projectSession(session: Session): {
|
||||||
|
uuid: string
|
||||||
|
api_version: string
|
||||||
|
created_at: string
|
||||||
|
updated_at: string
|
||||||
|
device_info: string
|
||||||
|
readonly_access: boolean
|
||||||
|
access_expiration: string
|
||||||
|
refresh_expiration: string
|
||||||
|
} {
|
||||||
|
return <
|
||||||
|
{
|
||||||
|
uuid: string
|
||||||
|
api_version: string
|
||||||
|
created_at: string
|
||||||
|
updated_at: string
|
||||||
|
device_info: string
|
||||||
|
readonly_access: boolean
|
||||||
|
access_expiration: string
|
||||||
|
refresh_expiration: string
|
||||||
|
}
|
||||||
|
>this.sessionProjector.projectSimple(session)
|
||||||
|
}
|
||||||
|
|
||||||
|
private projectRoles(roles: Array<Role>): Array<{ uuid: string; name: RoleName }> {
|
||||||
|
return roles.map((role) => <{ uuid: string; name: RoleName }>this.roleProjector.projectSimple(role))
|
||||||
|
}
|
||||||
|
}
|
||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
import { Either, Uuid } from '@standardnotes/common'
|
||||||
|
import { Session } from '../../Session/Session'
|
||||||
|
import { User } from '../../User/User'
|
||||||
|
|
||||||
|
export type CreateCrossServiceTokenDTO = Either<
|
||||||
|
{
|
||||||
|
user: User
|
||||||
|
session?: Session
|
||||||
|
},
|
||||||
|
{
|
||||||
|
userUuid: Uuid
|
||||||
|
}
|
||||||
|
>
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
export type CreateCrossServiceTokenResponse = {
|
||||||
|
token: string
|
||||||
|
}
|
||||||
+22
-2
@@ -5,17 +5,19 @@ import { TimerInterface } from '@standardnotes/time'
|
|||||||
import { SubscriptionTokenRepositoryInterface } from '../../Subscription/SubscriptionTokenRepositoryInterface'
|
import { SubscriptionTokenRepositoryInterface } from '../../Subscription/SubscriptionTokenRepositoryInterface'
|
||||||
|
|
||||||
import { CreateSubscriptionToken } from './CreateSubscriptionToken'
|
import { CreateSubscriptionToken } from './CreateSubscriptionToken'
|
||||||
|
import { Logger } from 'winston'
|
||||||
|
|
||||||
describe('CreateSubscriptionToken', () => {
|
describe('CreateSubscriptionToken', () => {
|
||||||
let subscriptionTokenRepository: SubscriptionTokenRepositoryInterface
|
let subscriptionTokenRepository: SubscriptionTokenRepositoryInterface
|
||||||
let cryptoNode: CryptoNode
|
let cryptoNode: CryptoNode
|
||||||
let timer: TimerInterface
|
let timer: TimerInterface
|
||||||
|
let logger: Logger
|
||||||
|
|
||||||
const createUseCase = () => new CreateSubscriptionToken(subscriptionTokenRepository, cryptoNode, timer)
|
const createUseCase = () => new CreateSubscriptionToken(subscriptionTokenRepository, cryptoNode, timer, logger)
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
subscriptionTokenRepository = {} as jest.Mocked<SubscriptionTokenRepositoryInterface>
|
subscriptionTokenRepository = {} as jest.Mocked<SubscriptionTokenRepositoryInterface>
|
||||||
subscriptionTokenRepository.save = jest.fn()
|
subscriptionTokenRepository.save = jest.fn().mockReturnValue(true)
|
||||||
|
|
||||||
cryptoNode = {} as jest.Mocked<CryptoNode>
|
cryptoNode = {} as jest.Mocked<CryptoNode>
|
||||||
cryptoNode.generateRandomKey = jest.fn().mockReturnValueOnce('random-string')
|
cryptoNode.generateRandomKey = jest.fn().mockReturnValueOnce('random-string')
|
||||||
@@ -23,6 +25,9 @@ describe('CreateSubscriptionToken', () => {
|
|||||||
timer = {} as jest.Mocked<TimerInterface>
|
timer = {} as jest.Mocked<TimerInterface>
|
||||||
timer.convertStringDateToMicroseconds = jest.fn().mockReturnValue(1)
|
timer.convertStringDateToMicroseconds = jest.fn().mockReturnValue(1)
|
||||||
timer.getUTCDateNHoursAhead = jest.fn().mockReturnValue(new Date(1))
|
timer.getUTCDateNHoursAhead = jest.fn().mockReturnValue(new Date(1))
|
||||||
|
|
||||||
|
logger = {} as jest.Mocked<Logger>
|
||||||
|
logger.error = jest.fn()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should create an subscription token and persist it', async () => {
|
it('should create an subscription token and persist it', async () => {
|
||||||
@@ -36,4 +41,19 @@ describe('CreateSubscriptionToken', () => {
|
|||||||
expiresAt: 1,
|
expiresAt: 1,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should throw an error if the subscription token was not created', async () => {
|
||||||
|
subscriptionTokenRepository.save = jest.fn().mockReturnValue(false)
|
||||||
|
|
||||||
|
let caughtError = null
|
||||||
|
try {
|
||||||
|
await createUseCase().execute({
|
||||||
|
userUuid: '1-2-3',
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
caughtError = error
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(caughtError).not.toBeNull()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { CryptoNode } from '@standardnotes/sncrypto-node'
|
import { CryptoNode } from '@standardnotes/sncrypto-node'
|
||||||
import { TimerInterface } from '@standardnotes/time'
|
import { TimerInterface } from '@standardnotes/time'
|
||||||
import { inject, injectable } from 'inversify'
|
import { inject, injectable } from 'inversify'
|
||||||
|
import { Logger } from 'winston'
|
||||||
|
|
||||||
import TYPES from '../../../Bootstrap/Types'
|
import TYPES from '../../../Bootstrap/Types'
|
||||||
import { SubscriptionTokenRepositoryInterface } from '../../Subscription/SubscriptionTokenRepositoryInterface'
|
import { SubscriptionTokenRepositoryInterface } from '../../Subscription/SubscriptionTokenRepositoryInterface'
|
||||||
@@ -15,6 +16,7 @@ export class CreateSubscriptionToken implements UseCaseInterface {
|
|||||||
private subscriptionTokenRepository: SubscriptionTokenRepositoryInterface,
|
private subscriptionTokenRepository: SubscriptionTokenRepositoryInterface,
|
||||||
@inject(TYPES.CryptoNode) private cryptoNode: CryptoNode,
|
@inject(TYPES.CryptoNode) private cryptoNode: CryptoNode,
|
||||||
@inject(TYPES.Timer) private timer: TimerInterface,
|
@inject(TYPES.Timer) private timer: TimerInterface,
|
||||||
|
@inject(TYPES.Logger) private logger: Logger,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async execute(dto: CreateSubscriptionTokenDTO): Promise<CreateSubscriptionTokenResponse> {
|
async execute(dto: CreateSubscriptionTokenDTO): Promise<CreateSubscriptionTokenResponse> {
|
||||||
@@ -26,7 +28,13 @@ export class CreateSubscriptionToken implements UseCaseInterface {
|
|||||||
expiresAt: this.timer.convertStringDateToMicroseconds(this.timer.getUTCDateNHoursAhead(3).toString()),
|
expiresAt: this.timer.convertStringDateToMicroseconds(this.timer.getUTCDateNHoursAhead(3).toString()),
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.subscriptionTokenRepository.save(subscriptionToken)
|
const subscriptionTokenWasSaved = await this.subscriptionTokenRepository.save(subscriptionToken)
|
||||||
|
|
||||||
|
if (!subscriptionTokenWasSaved) {
|
||||||
|
this.logger.error(`Could not create subscription token for user ${dto.userUuid}`)
|
||||||
|
|
||||||
|
throw new Error('Could not create subscription token')
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
subscriptionToken,
|
subscriptionToken,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import 'reflect-metadata'
|
import 'reflect-metadata'
|
||||||
|
|
||||||
import { TokenEncoderInterface, ValetTokenData } from '@standardnotes/security'
|
import { TokenEncoderInterface, ValetTokenData, ValetTokenOperation } from '@standardnotes/security'
|
||||||
import { CreateValetToken } from './CreateValetToken'
|
import { CreateValetToken } from './CreateValetToken'
|
||||||
import { TimerInterface } from '@standardnotes/time'
|
import { TimerInterface } from '@standardnotes/time'
|
||||||
import { UserSubscription } from '../../Subscription/UserSubscription'
|
import { UserSubscription } from '../../Subscription/UserSubscription'
|
||||||
@@ -70,7 +70,7 @@ describe('CreateValetToken', () => {
|
|||||||
|
|
||||||
it('should create a read valet token', async () => {
|
it('should create a read valet token', async () => {
|
||||||
const response = await createUseCase().execute({
|
const response = await createUseCase().execute({
|
||||||
operation: 'read',
|
operation: ValetTokenOperation.Read,
|
||||||
userUuid: '1-2-3',
|
userUuid: '1-2-3',
|
||||||
resources: [
|
resources: [
|
||||||
{
|
{
|
||||||
@@ -92,7 +92,7 @@ describe('CreateValetToken', () => {
|
|||||||
.mockReturnValue({ regularSubscription: null, sharedSubscription: null })
|
.mockReturnValue({ regularSubscription: null, sharedSubscription: null })
|
||||||
|
|
||||||
const response = await createUseCase().execute({
|
const response = await createUseCase().execute({
|
||||||
operation: 'read',
|
operation: ValetTokenOperation.Read,
|
||||||
userUuid: '1-2-3',
|
userUuid: '1-2-3',
|
||||||
resources: [
|
resources: [
|
||||||
{
|
{
|
||||||
@@ -117,7 +117,7 @@ describe('CreateValetToken', () => {
|
|||||||
timer.getTimestampInMicroseconds = jest.fn().mockReturnValue(150)
|
timer.getTimestampInMicroseconds = jest.fn().mockReturnValue(150)
|
||||||
|
|
||||||
const response = await createUseCase().execute({
|
const response = await createUseCase().execute({
|
||||||
operation: 'read',
|
operation: ValetTokenOperation.Read,
|
||||||
userUuid: '1-2-3',
|
userUuid: '1-2-3',
|
||||||
resources: [
|
resources: [
|
||||||
{
|
{
|
||||||
@@ -135,7 +135,7 @@ describe('CreateValetToken', () => {
|
|||||||
|
|
||||||
it('should not create a write valet token if unencrypted file size has not been provided for a resource', async () => {
|
it('should not create a write valet token if unencrypted file size has not been provided for a resource', async () => {
|
||||||
const response = await createUseCase().execute({
|
const response = await createUseCase().execute({
|
||||||
operation: 'write',
|
operation: ValetTokenOperation.Write,
|
||||||
resources: [
|
resources: [
|
||||||
{
|
{
|
||||||
remoteIdentifier: '2-3-4',
|
remoteIdentifier: '2-3-4',
|
||||||
@@ -152,7 +152,7 @@ describe('CreateValetToken', () => {
|
|||||||
|
|
||||||
it('should create a write valet token', async () => {
|
it('should create a write valet token', async () => {
|
||||||
const response = await createUseCase().execute({
|
const response = await createUseCase().execute({
|
||||||
operation: 'write',
|
operation: ValetTokenOperation.Write,
|
||||||
resources: [
|
resources: [
|
||||||
{
|
{
|
||||||
remoteIdentifier: '2-3-4',
|
remoteIdentifier: '2-3-4',
|
||||||
@@ -192,7 +192,7 @@ describe('CreateValetToken', () => {
|
|||||||
.mockReturnValue({ regularSubscription, sharedSubscription })
|
.mockReturnValue({ regularSubscription, sharedSubscription })
|
||||||
|
|
||||||
const response = await createUseCase().execute({
|
const response = await createUseCase().execute({
|
||||||
operation: 'write',
|
operation: ValetTokenOperation.Write,
|
||||||
resources: [
|
resources: [
|
||||||
{
|
{
|
||||||
remoteIdentifier: '2-3-4',
|
remoteIdentifier: '2-3-4',
|
||||||
@@ -232,7 +232,7 @@ describe('CreateValetToken', () => {
|
|||||||
.mockReturnValue({ regularSubscription: null, sharedSubscription })
|
.mockReturnValue({ regularSubscription: null, sharedSubscription })
|
||||||
|
|
||||||
const response = await createUseCase().execute({
|
const response = await createUseCase().execute({
|
||||||
operation: 'write',
|
operation: ValetTokenOperation.Write,
|
||||||
resources: [
|
resources: [
|
||||||
{
|
{
|
||||||
remoteIdentifier: '2-3-4',
|
remoteIdentifier: '2-3-4',
|
||||||
@@ -252,7 +252,7 @@ describe('CreateValetToken', () => {
|
|||||||
subscriptionSettingService.findSubscriptionSettingWithDecryptedValue = jest.fn().mockReturnValue(null)
|
subscriptionSettingService.findSubscriptionSettingWithDecryptedValue = jest.fn().mockReturnValue(null)
|
||||||
|
|
||||||
const response = await createUseCase().execute({
|
const response = await createUseCase().execute({
|
||||||
operation: 'write',
|
operation: ValetTokenOperation.Write,
|
||||||
userUuid: '1-2-3',
|
userUuid: '1-2-3',
|
||||||
resources: [
|
resources: [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
import { CreateValetTokenPayload } from '@standardnotes/responses'
|
import { ValetTokenOperation } from '@standardnotes/security'
|
||||||
|
|
||||||
export type CreateValetTokenDTO = CreateValetTokenPayload & {
|
export type CreateValetTokenDTO = {
|
||||||
|
operation: ValetTokenOperation
|
||||||
|
resources: Array<{
|
||||||
|
remoteIdentifier: string
|
||||||
|
unencryptedFileSize?: number
|
||||||
|
}>
|
||||||
userUuid: string
|
userUuid: string
|
||||||
}
|
}
|
||||||
|
|||||||
+25
@@ -0,0 +1,25 @@
|
|||||||
|
import 'reflect-metadata'
|
||||||
|
|
||||||
|
import { TokenEncoderInterface, WebSocketConnectionTokenData } from '@standardnotes/security'
|
||||||
|
|
||||||
|
import { CreateWebSocketConnectionToken } from './CreateWebSocketConnectionToken'
|
||||||
|
|
||||||
|
describe('CreateWebSocketConnection', () => {
|
||||||
|
let tokenEncoder: TokenEncoderInterface<WebSocketConnectionTokenData>
|
||||||
|
const tokenTTL = 30
|
||||||
|
|
||||||
|
const createUseCase = () => new CreateWebSocketConnectionToken(tokenEncoder, tokenTTL)
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
tokenEncoder = {} as jest.Mocked<TokenEncoderInterface<WebSocketConnectionTokenData>>
|
||||||
|
tokenEncoder.encodeExpirableToken = jest.fn().mockReturnValue('foobar')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should create a web socket connection token', async () => {
|
||||||
|
const result = await createUseCase().execute({ userUuid: '1-2-3' })
|
||||||
|
|
||||||
|
expect(result.token).toEqual('foobar')
|
||||||
|
|
||||||
|
expect(tokenEncoder.encodeExpirableToken).toHaveBeenCalledWith({ userUuid: '1-2-3' }, 30)
|
||||||
|
})
|
||||||
|
})
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user