mirror of
https://github.com/standardnotes/server
synced 2026-01-21 08:04:27 -05:00
Compare commits
88 Commits
@standardn
...
@standardn
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
38de2d6b30 | ||
|
|
4b3de264ef | ||
|
|
4bb785c7f0 | ||
|
|
2fb904d2cb | ||
|
|
ee79347e27 | ||
|
|
3477c81d37 | ||
|
|
930789316c | ||
|
|
01a08eae58 | ||
|
|
d73c9833ab | ||
|
|
1841597405 | ||
|
|
8003e5ce43 | ||
|
|
d0023a6c92 | ||
|
|
a9293f6ce1 | ||
|
|
58c5b586a9 | ||
|
|
21d224da22 | ||
|
|
43d957c8d3 | ||
|
|
917fad510a | ||
|
|
269eef7ef3 | ||
|
|
b811f4527b | ||
|
|
67378e4535 | ||
|
|
dad9033482 | ||
|
|
32c8333564 | ||
|
|
4d074e7f9a | ||
|
|
c61b615da6 | ||
|
|
fba6cfd62c | ||
|
|
1ba5ba5ff6 | ||
|
|
31b6988f17 | ||
|
|
16076382ba | ||
|
|
666c919b70 | ||
|
|
dea5fd717d |
51
.github/dependabot.yml
vendored
51
.github/dependabot.yml
vendored
@@ -9,134 +9,83 @@ updates:
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
reviewers:
|
||||
- "moughxyz"
|
||||
- "karolsojko"
|
||||
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/packages/analytics"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
reviewers:
|
||||
- "moughxyz"
|
||||
- "karolsojko"
|
||||
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/packages/api-gateway"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
reviewers:
|
||||
- "moughxyz"
|
||||
- "karolsojko"
|
||||
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/packages/auth"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
reviewers:
|
||||
- "moughxyz"
|
||||
- "karolsojko"
|
||||
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/packages/common"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
reviewers:
|
||||
- "moughxyz"
|
||||
- "karolsojko"
|
||||
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/packages/domain-events"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
reviewers:
|
||||
- "moughxyz"
|
||||
- "karolsojko"
|
||||
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/packages/domain-events-infra"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
reviewers:
|
||||
- "moughxyz"
|
||||
- "karolsojko"
|
||||
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/packages/event-store"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
reviewers:
|
||||
- "moughxyz"
|
||||
- "karolsojko"
|
||||
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/packages/files"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
reviewers:
|
||||
- "moughxyz"
|
||||
- "karolsojko"
|
||||
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/packages/predicates"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
reviewers:
|
||||
- "moughxyz"
|
||||
- "karolsojko"
|
||||
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/packages/scheduler"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
reviewers:
|
||||
- "moughxyz"
|
||||
- "karolsojko"
|
||||
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/packages/security"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
reviewers:
|
||||
- "moughxyz"
|
||||
- "karolsojko"
|
||||
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/packages/settings"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
reviewers:
|
||||
- "moughxyz"
|
||||
- "karolsojko"
|
||||
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/packages/sncrypto-node"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
reviewers:
|
||||
- "moughxyz"
|
||||
- "karolsojko"
|
||||
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/packages/syncing-server"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
reviewers:
|
||||
- "moughxyz"
|
||||
- "karolsojko"
|
||||
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/packages/time"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
reviewers:
|
||||
- "moughxyz"
|
||||
- "karolsojko"
|
||||
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
reviewers:
|
||||
- "moughxyz"
|
||||
- "karolsojko"
|
||||
|
||||
10
.github/workflows/api-gateway.release.yml
vendored
10
.github/workflows/api-gateway.release.yml
vendored
@@ -60,6 +60,11 @@ jobs:
|
||||
|
||||
steps:
|
||||
- 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
|
||||
run: yarn build
|
||||
- name: Configure AWS credentials
|
||||
@@ -90,6 +95,11 @@ jobs:
|
||||
|
||||
steps:
|
||||
- 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
|
||||
run: yarn build
|
||||
- name: Login to Docker Hub
|
||||
|
||||
10
.github/workflows/auth.release.yml
vendored
10
.github/workflows/auth.release.yml
vendored
@@ -63,6 +63,11 @@ jobs:
|
||||
|
||||
steps:
|
||||
- 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
|
||||
run: yarn build
|
||||
- name: Configure AWS credentials
|
||||
@@ -93,6 +98,11 @@ jobs:
|
||||
|
||||
steps:
|
||||
- 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
|
||||
run: yarn build
|
||||
- name: Login to Docker Hub
|
||||
|
||||
10
.github/workflows/event-store.release.yml
vendored
10
.github/workflows/event-store.release.yml
vendored
@@ -32,6 +32,11 @@ jobs:
|
||||
|
||||
steps:
|
||||
- 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
|
||||
run: yarn build
|
||||
- name: Configure AWS credentials
|
||||
@@ -62,6 +67,11 @@ jobs:
|
||||
|
||||
steps:
|
||||
- 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
|
||||
run: yarn build
|
||||
- name: Login to Docker Hub
|
||||
|
||||
10
.github/workflows/files.release.yml
vendored
10
.github/workflows/files.release.yml
vendored
@@ -63,6 +63,11 @@ jobs:
|
||||
|
||||
steps:
|
||||
- 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
|
||||
run: yarn build
|
||||
- name: Configure AWS credentials
|
||||
@@ -93,6 +98,11 @@ jobs:
|
||||
|
||||
steps:
|
||||
- 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
|
||||
run: yarn build
|
||||
- name: Login to Docker Hub
|
||||
|
||||
5
.github/workflows/pr.yml
vendored
5
.github/workflows/pr.yml
vendored
@@ -11,6 +11,11 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout code
|
||||
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
|
||||
run: yarn lint
|
||||
- name: Build
|
||||
|
||||
10
.github/workflows/scheduler.release.yml
vendored
10
.github/workflows/scheduler.release.yml
vendored
@@ -32,6 +32,11 @@ jobs:
|
||||
|
||||
steps:
|
||||
- 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
|
||||
run: yarn build
|
||||
- name: Configure AWS credentials
|
||||
@@ -62,6 +67,11 @@ jobs:
|
||||
|
||||
steps:
|
||||
- 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
|
||||
run: yarn build
|
||||
- name: Login to Docker Hub
|
||||
|
||||
6
.github/workflows/snjs.upgrade.event.yml
vendored
6
.github/workflows/snjs.upgrade.event.yml
vendored
@@ -14,9 +14,11 @@ jobs:
|
||||
with:
|
||||
ref: main
|
||||
token: ${{ secrets.CI_PAT_TOKEN }}
|
||||
- uses: actions/setup-node@v3
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
node-version-file: '.nvmrc'
|
||||
- name: Setup git config
|
||||
run: |
|
||||
git config --global user.name "standardci"
|
||||
@@ -43,4 +45,4 @@ jobs:
|
||||
commit-message: "${{ 'chore(deps): upgrade snjs' }}"
|
||||
delete-branch: true
|
||||
committer: standardci <ci@standardnotes.com>
|
||||
author: standardci <ci@standardnotes.com>
|
||||
author: standardci <ci@standardnotes.com>
|
||||
|
||||
10
.github/workflows/syncing-server.release.yml
vendored
10
.github/workflows/syncing-server.release.yml
vendored
@@ -63,6 +63,11 @@ jobs:
|
||||
|
||||
steps:
|
||||
- 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
|
||||
run: yarn build
|
||||
- name: Configure AWS credentials
|
||||
@@ -93,6 +98,11 @@ jobs:
|
||||
|
||||
steps:
|
||||
- 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
|
||||
run: yarn build
|
||||
- name: Login to Docker Hub
|
||||
|
||||
73
.pnp.cjs
generated
73
.pnp.cjs
generated
@@ -124,7 +124,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
||||
["newrelic", "npm:9.0.0"],\
|
||||
["npm-check-updates", "npm:16.0.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"]\
|
||||
],\
|
||||
"linkType": "SOFT"\
|
||||
@@ -2607,7 +2607,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
||||
["ts-jest", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:28.0.5"],\
|
||||
["typeorm", "virtual:31b5a94a105c89c9294c3d524a7f8929fe63ee5a2efadf21951ca4c0cfd2ecf02e8f4ef5a066bbda091f1e3a56e57c6749069a080618c96b22e51131a330fc4a#npm:0.3.7"],\
|
||||
["ua-parser-js", "npm:1.0.2"],\
|
||||
["uuid", "npm:8.3.2"],\
|
||||
["uuid", "npm:9.0.0"],\
|
||||
["winston", "npm:3.8.1"]\
|
||||
],\
|
||||
"linkType": "SOFT"\
|
||||
@@ -2788,8 +2788,8 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
||||
["prettyjson", "npm:1.2.5"],\
|
||||
["reflect-metadata", "npm:0.1.13"],\
|
||||
["ts-jest", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:28.0.5"],\
|
||||
["ts-node", "virtual:b442cf0427cc365d1c137f7340f9b81f9b204561afe791a8564ae9590c3a7fc4b5f793aaf8817b946f75a3cb64d03ef8790eb847f8b576b41e700da7b00c240c#npm:10.8.2"],\
|
||||
["uuid", "npm:8.3.2"],\
|
||||
["ts-node", "virtual:b442cf0427cc365d1c137f7340f9b81f9b204561afe791a8564ae9590c3a7fc4b5f793aaf8817b946f75a3cb64d03ef8790eb847f8b576b41e700da7b00c240c#npm:10.9.1"],\
|
||||
["uuid", "npm:9.0.0"],\
|
||||
["winston", "npm:3.8.1"]\
|
||||
],\
|
||||
"linkType": "SOFT"\
|
||||
@@ -2866,11 +2866,12 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
||||
["@types/jest", "npm:28.1.4"],\
|
||||
["@types/newrelic", "npm:7.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"],\
|
||||
["dayjs", "npm:1.11.3"],\
|
||||
["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"],\
|
||||
["ioredis", "npm:5.2.0"],\
|
||||
["jest", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:28.1.2"],\
|
||||
@@ -2924,7 +2925,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
||||
["newrelic", "npm:9.0.0"],\
|
||||
["npm-check-updates", "npm:16.0.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"]\
|
||||
],\
|
||||
"linkType": "SOFT"\
|
||||
@@ -3035,7 +3036,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
||||
["ts-jest", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:28.0.5"],\
|
||||
["typeorm", "virtual:31b5a94a105c89c9294c3d524a7f8929fe63ee5a2efadf21951ca4c0cfd2ecf02e8f4ef5a066bbda091f1e3a56e57c6749069a080618c96b22e51131a330fc4a#npm:0.3.7"],\
|
||||
["ua-parser-js", "npm:1.0.2"],\
|
||||
["uuid", "npm:8.3.2"],\
|
||||
["uuid", "npm:9.0.0"],\
|
||||
["winston", "npm:3.8.1"]\
|
||||
],\
|
||||
"linkType": "SOFT"\
|
||||
@@ -12187,10 +12188,53 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
||||
],\
|
||||
"linkType": "SOFT"\
|
||||
}],\
|
||||
["virtual:b442cf0427cc365d1c137f7340f9b81f9b204561afe791a8564ae9590c3a7fc4b5f793aaf8817b946f75a3cb64d03ef8790eb847f8b576b41e700da7b00c240c#npm:10.8.2", {\
|
||||
"packageLocation": "./.yarn/__virtual__/ts-node-virtual-8a01a45377/0/cache/ts-node-npm-10.8.2-f3c0c9eaee-1eede939be.zip/node_modules/ts-node/",\
|
||||
["npm:10.9.1", {\
|
||||
"packageLocation": "./.yarn/cache/ts-node-npm-10.9.1-6c268be7f4-090adff130.zip/node_modules/ts-node/",\
|
||||
"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"],\
|
||||
["@swc/core", null],\
|
||||
["@swc/wasm", null],\
|
||||
@@ -12735,6 +12779,13 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
||||
["uuid", "npm:8.3.2"]\
|
||||
],\
|
||||
"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", [\
|
||||
|
||||
BIN
.yarn/cache/ts-node-npm-10.9.1-6c268be7f4-090adff130.zip
vendored
Normal file
BIN
.yarn/cache/ts-node-npm-10.9.1-6c268be7f4-090adff130.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/uuid-npm-9.0.0-46c41e3e43-8dd2c83c43.zip
vendored
Normal file
BIN
.yarn/cache/uuid-npm-9.0.0-46c41e3e43-8dd2c83c43.zip
vendored
Normal file
Binary file not shown.
@@ -327,7 +327,7 @@ endif
|
||||
|
||||
quiet_cmd_regen_makefile = ACTION Regenerating $@
|
||||
cmd_regen_makefile = cd $(srcdir); /Users/karolsojko/workspace/server/.yarn/unplugged/node-gyp-npm-9.0.0-0eccfca4d1/node_modules/node-gyp/gyp/gyp_main.py -fmake --ignore-environment "-Dlibrary=shared_library" "-Dvisibility=default" "-Dnode_root_dir=/Users/karolsojko/Library/Caches/node-gyp/16.15.1" "-Dnode_gyp_dir=/Users/karolsojko/workspace/server/.yarn/unplugged/node-gyp-npm-9.0.0-0eccfca4d1/node_modules/node-gyp" "-Dnode_lib_file=/Users/karolsojko/Library/Caches/node-gyp/16.15.1/<(target_arch)/node.lib" "-Dmodule_root_dir=/Users/karolsojko/workspace/server/.yarn/unplugged/@newrelic-native-metrics-npm-9.0.0-590d2e713a/node_modules/@newrelic/native-metrics" "-Dnode_engine=v8" "--depth=." "-Goutput_dir=." "--generator-output=build" -I/Users/karolsojko/workspace/server/.yarn/unplugged/@newrelic-native-metrics-npm-9.0.0-590d2e713a/node_modules/@newrelic/native-metrics/build/config.gypi -I/Users/karolsojko/workspace/server/.yarn/unplugged/node-gyp-npm-9.0.0-0eccfca4d1/node_modules/node-gyp/addon.gypi -I/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/common.gypi "--toplevel-dir=." binding.gyp
|
||||
Makefile: $(srcdir)/binding.gyp $(srcdir)/../../../../../../../../Library/Caches/node-gyp/16.15.1/include/node/common.gypi $(srcdir)/build/config.gypi $(srcdir)/../../../../node-gyp-npm-9.0.0-0eccfca4d1/node_modules/node-gyp/addon.gypi
|
||||
Makefile: $(srcdir)/binding.gyp $(srcdir)/../../../../node-gyp-npm-9.0.0-0eccfca4d1/node_modules/node-gyp/addon.gypi $(srcdir)/../../../../../../../../Library/Caches/node-gyp/16.15.1/include/node/common.gypi $(srcdir)/build/config.gypi
|
||||
$(call do_cmd,regen_makefile)
|
||||
|
||||
# "all" is a concatenation of the "all" targets from all the included
|
||||
|
||||
Binary file not shown.
BIN
.yarn/unplugged/node-gyp-npm-9.0.0-0eccfca4d1/node_modules/node-gyp/gyp/pylib/gyp/__pycache__/MSVSUtil.cpython-37.pyc
generated
vendored
Normal file
BIN
.yarn/unplugged/node-gyp-npm-9.0.0-0eccfca4d1/node_modules/node-gyp/gyp/pylib/gyp/__pycache__/MSVSUtil.cpython-37.pyc
generated
vendored
Normal file
Binary file not shown.
BIN
.yarn/unplugged/node-gyp-npm-9.0.0-0eccfca4d1/node_modules/node-gyp/gyp/pylib/gyp/__pycache__/MSVSVersion.cpython-37.pyc
generated
vendored
Normal file
BIN
.yarn/unplugged/node-gyp-npm-9.0.0-0eccfca4d1/node_modules/node-gyp/gyp/pylib/gyp/__pycache__/MSVSVersion.cpython-37.pyc
generated
vendored
Normal file
Binary file not shown.
BIN
.yarn/unplugged/node-gyp-npm-9.0.0-0eccfca4d1/node_modules/node-gyp/gyp/pylib/gyp/__pycache__/__init__.cpython-37.pyc
generated
vendored
Normal file
BIN
.yarn/unplugged/node-gyp-npm-9.0.0-0eccfca4d1/node_modules/node-gyp/gyp/pylib/gyp/__pycache__/__init__.cpython-37.pyc
generated
vendored
Normal file
Binary file not shown.
BIN
.yarn/unplugged/node-gyp-npm-9.0.0-0eccfca4d1/node_modules/node-gyp/gyp/pylib/gyp/__pycache__/common.cpython-37.pyc
generated
vendored
Normal file
BIN
.yarn/unplugged/node-gyp-npm-9.0.0-0eccfca4d1/node_modules/node-gyp/gyp/pylib/gyp/__pycache__/common.cpython-37.pyc
generated
vendored
Normal file
Binary file not shown.
BIN
.yarn/unplugged/node-gyp-npm-9.0.0-0eccfca4d1/node_modules/node-gyp/gyp/pylib/gyp/__pycache__/input.cpython-37.pyc
generated
vendored
Normal file
BIN
.yarn/unplugged/node-gyp-npm-9.0.0-0eccfca4d1/node_modules/node-gyp/gyp/pylib/gyp/__pycache__/input.cpython-37.pyc
generated
vendored
Normal file
Binary file not shown.
BIN
.yarn/unplugged/node-gyp-npm-9.0.0-0eccfca4d1/node_modules/node-gyp/gyp/pylib/gyp/__pycache__/msvs_emulation.cpython-37.pyc
generated
vendored
Normal file
BIN
.yarn/unplugged/node-gyp-npm-9.0.0-0eccfca4d1/node_modules/node-gyp/gyp/pylib/gyp/__pycache__/msvs_emulation.cpython-37.pyc
generated
vendored
Normal file
Binary file not shown.
BIN
.yarn/unplugged/node-gyp-npm-9.0.0-0eccfca4d1/node_modules/node-gyp/gyp/pylib/gyp/__pycache__/ninja_syntax.cpython-37.pyc
generated
vendored
Normal file
BIN
.yarn/unplugged/node-gyp-npm-9.0.0-0eccfca4d1/node_modules/node-gyp/gyp/pylib/gyp/__pycache__/ninja_syntax.cpython-37.pyc
generated
vendored
Normal file
Binary file not shown.
BIN
.yarn/unplugged/node-gyp-npm-9.0.0-0eccfca4d1/node_modules/node-gyp/gyp/pylib/gyp/__pycache__/simple_copy.cpython-37.pyc
generated
vendored
Normal file
BIN
.yarn/unplugged/node-gyp-npm-9.0.0-0eccfca4d1/node_modules/node-gyp/gyp/pylib/gyp/__pycache__/simple_copy.cpython-37.pyc
generated
vendored
Normal file
Binary file not shown.
BIN
.yarn/unplugged/node-gyp-npm-9.0.0-0eccfca4d1/node_modules/node-gyp/gyp/pylib/gyp/__pycache__/xcode_emulation.cpython-37.pyc
generated
vendored
Normal file
BIN
.yarn/unplugged/node-gyp-npm-9.0.0-0eccfca4d1/node_modules/node-gyp/gyp/pylib/gyp/__pycache__/xcode_emulation.cpython-37.pyc
generated
vendored
Normal file
Binary file not shown.
BIN
.yarn/unplugged/node-gyp-npm-9.0.0-0eccfca4d1/node_modules/node-gyp/gyp/pylib/gyp/__pycache__/xcode_ninja.cpython-37.pyc
generated
vendored
Normal file
BIN
.yarn/unplugged/node-gyp-npm-9.0.0-0eccfca4d1/node_modules/node-gyp/gyp/pylib/gyp/__pycache__/xcode_ninja.cpython-37.pyc
generated
vendored
Normal file
Binary file not shown.
BIN
.yarn/unplugged/node-gyp-npm-9.0.0-0eccfca4d1/node_modules/node-gyp/gyp/pylib/gyp/__pycache__/xcodeproj_file.cpython-37.pyc
generated
vendored
Normal file
BIN
.yarn/unplugged/node-gyp-npm-9.0.0-0eccfca4d1/node_modules/node-gyp/gyp/pylib/gyp/__pycache__/xcodeproj_file.cpython-37.pyc
generated
vendored
Normal file
Binary file not shown.
BIN
.yarn/unplugged/node-gyp-npm-9.0.0-0eccfca4d1/node_modules/node-gyp/gyp/pylib/gyp/generator/__pycache__/__init__.cpython-37.pyc
generated
vendored
Normal file
BIN
.yarn/unplugged/node-gyp-npm-9.0.0-0eccfca4d1/node_modules/node-gyp/gyp/pylib/gyp/generator/__pycache__/__init__.cpython-37.pyc
generated
vendored
Normal file
Binary file not shown.
BIN
.yarn/unplugged/node-gyp-npm-9.0.0-0eccfca4d1/node_modules/node-gyp/gyp/pylib/gyp/generator/__pycache__/make.cpython-37.pyc
generated
vendored
Normal file
BIN
.yarn/unplugged/node-gyp-npm-9.0.0-0eccfca4d1/node_modules/node-gyp/gyp/pylib/gyp/generator/__pycache__/make.cpython-37.pyc
generated
vendored
Normal file
Binary file not shown.
BIN
.yarn/unplugged/node-gyp-npm-9.0.0-0eccfca4d1/node_modules/node-gyp/gyp/pylib/gyp/generator/__pycache__/ninja.cpython-37.pyc
generated
vendored
Normal file
BIN
.yarn/unplugged/node-gyp-npm-9.0.0-0eccfca4d1/node_modules/node-gyp/gyp/pylib/gyp/generator/__pycache__/ninja.cpython-37.pyc
generated
vendored
Normal file
Binary file not shown.
BIN
.yarn/unplugged/node-gyp-npm-9.0.0-0eccfca4d1/node_modules/node-gyp/gyp/pylib/gyp/generator/__pycache__/xcode.cpython-37.pyc
generated
vendored
Normal file
BIN
.yarn/unplugged/node-gyp-npm-9.0.0-0eccfca4d1/node_modules/node-gyp/gyp/pylib/gyp/generator/__pycache__/xcode.cpython-37.pyc
generated
vendored
Normal file
Binary file not shown.
@@ -60,7 +60,7 @@
|
||||
"ini": "^3.0.0",
|
||||
"npm-check-updates": "^16.0.1",
|
||||
"prettier": "^2.7.1",
|
||||
"ts-node": "^10.8.1",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^4.7.4"
|
||||
},
|
||||
"packageManager": "yarn@3.2.1",
|
||||
|
||||
@@ -3,6 +3,96 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [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)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **analytics:** add payment success activity ([9307893](https://github.com/standardnotes/server/commit/930789316c2eec8227f26e75d4917796168f2d08))
|
||||
|
||||
# [1.18.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@1.17.2...@standardnotes/analytics@1.18.0) (2022-08-15)
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** add payment failed event handler ([58c5b58](https://github.com/standardnotes/server/commit/58c5b586a904cf1fd179cc28783a6ae7da688063))
|
||||
|
||||
## [1.17.2](https://github.com/standardnotes/server/compare/@standardnotes/analytics@1.17.1...@standardnotes/analytics@1.17.2) (2022-08-15)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **analytics:** quarterly calculations over time ([43d957c](https://github.com/standardnotes/server/commit/43d957c8d382b501e8101b51e30b33f18a4dd871))
|
||||
|
||||
## [1.17.1](https://github.com/standardnotes/server/compare/@standardnotes/analytics@1.17.0...@standardnotes/analytics@1.17.1) (2022-08-15)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **analytics:** expire bitop keys ([269eef7](https://github.com/standardnotes/server/commit/269eef7ef31343390c6909350bf1bfede94c24b3))
|
||||
|
||||
# [1.17.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@1.16.0...@standardnotes/analytics@1.17.0) (2022-08-15)
|
||||
|
||||
### Features
|
||||
|
||||
* **api-gateway:** add quarterly analytics ([67378e4](https://github.com/standardnotes/server/commit/67378e4535ef2760cfe3fe27256ffe117ee11a71))
|
||||
|
||||
# [1.16.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@1.15.0...@standardnotes/analytics@1.16.0) (2022-08-15)
|
||||
|
||||
### Features
|
||||
|
||||
* **analytics:** add calculating quarterly stats ([32c8333](https://github.com/standardnotes/server/commit/32c8333564dea742b28ccc6f09e5fa33dd1f7af2))
|
||||
|
||||
# [1.15.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@1.14.0...@standardnotes/analytics@1.15.0) (2022-08-11)
|
||||
|
||||
### Features
|
||||
|
||||
* add analytics for subscription cancelling, refunding and account deletion ([1607638](https://github.com/standardnotes/server/commit/16076382bae74552a35901bb5474e2c2c2d96f43))
|
||||
|
||||
# [1.14.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@1.13.0...@standardnotes/analytics@1.14.0) (2022-08-09)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/analytics",
|
||||
"version": "1.14.0",
|
||||
"version": "1.25.0",
|
||||
"engines": {
|
||||
"node": ">=14.0.0 <17.0.0"
|
||||
},
|
||||
@@ -23,7 +23,7 @@
|
||||
"prebuild": "yarn clean",
|
||||
"build": "tsc -p tsconfig.json",
|
||||
"lint": "eslint . --ext .ts",
|
||||
"test:unit": "jest spec --coverage"
|
||||
"test": "jest spec --coverage"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/ioredis": "^4.28.10",
|
||||
|
||||
@@ -3,9 +3,14 @@ export enum AnalyticsActivity {
|
||||
EditingItems = 'editing-items',
|
||||
Login = 'login',
|
||||
Register = 'register',
|
||||
DeleteAccount = 'DeleteAccount',
|
||||
SubscriptionPurchased = 'subscription-purchased',
|
||||
SubscriptionRenewed = 'subscription-renewed',
|
||||
SubscriptionRefunded = 'subscription-refunded',
|
||||
SubscriptionCancelled = 'subscription-cancelled',
|
||||
EmailUnbackedUpData = 'email-unbacked-up-data',
|
||||
EmailBackup = 'email-backup',
|
||||
LimitedDiscountOfferPurchased = 'limited-discount-offer-purchased',
|
||||
PaymentFailed = 'payment-failed',
|
||||
PaymentSuccess = 'payment-success',
|
||||
}
|
||||
|
||||
@@ -6,6 +6,12 @@ export interface AnalyticsStoreInterface {
|
||||
markActivity(activities: AnalyticsActivity[], analyticsId: number, periods: Period[]): Promise<void>
|
||||
wasActivityDone(activity: AnalyticsActivity, analyticsId: number, period: Period): Promise<boolean>
|
||||
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>
|
||||
calculateActivityChangesTotalCount(
|
||||
activity: AnalyticsActivity,
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
export enum StatisticsMeasure {
|
||||
Income = 'income',
|
||||
SubscriptionLength = 'subscription-length',
|
||||
RegistrationLength = 'registration-length',
|
||||
RegistrationToSubscriptionTime = 'registration-to-subscription-time',
|
||||
Refunds = 'refunds',
|
||||
}
|
||||
@@ -1,3 +1,6 @@
|
||||
import { Period } from '../Time/Period'
|
||||
import { StatisticsMeasure } from './StatisticsMeasure'
|
||||
|
||||
export interface StatisticsStoreInterface {
|
||||
incrementSNJSVersionUsage(snjsVersion: string): Promise<void>
|
||||
incrementApplicationVersionUsage(applicationVersion: string): Promise<void>
|
||||
@@ -5,4 +8,7 @@ export interface StatisticsStoreInterface {
|
||||
getYesterdaySNJSUsage(): Promise<Array<{ version: string; count: number }>>
|
||||
getYesterdayApplicationUsage(): Promise<Array<{ version: string; count: 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,4 +8,9 @@ export enum Period {
|
||||
ThisMonth,
|
||||
LastMonth,
|
||||
Last30Days,
|
||||
Last7Days,
|
||||
Q1ThisYear,
|
||||
Q2ThisYear,
|
||||
Q3ThisYear,
|
||||
Q4ThisYear,
|
||||
}
|
||||
|
||||
@@ -48,6 +48,34 @@ 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', () => {
|
||||
expect(createGenerator().getDiscretePeriodKeys(Period.Q1ThisYear)).toEqual(['2022-1', '2022-2', '2022-3'])
|
||||
})
|
||||
|
||||
it('should generate period keys for Q2', () => {
|
||||
expect(createGenerator().getDiscretePeriodKeys(Period.Q2ThisYear)).toEqual(['2022-4', '2022-5', '2022-6'])
|
||||
})
|
||||
|
||||
it('should generate period keys for Q3', () => {
|
||||
expect(createGenerator().getDiscretePeriodKeys(Period.Q3ThisYear)).toEqual(['2022-7', '2022-8', '2022-9'])
|
||||
})
|
||||
|
||||
it('should generate period keys for Q4', () => {
|
||||
expect(createGenerator().getDiscretePeriodKeys(Period.Q4ThisYear)).toEqual(['2022-10', '2022-11', '2022-12'])
|
||||
})
|
||||
|
||||
it('should generate a period key for today', () => {
|
||||
expect(createGenerator().getPeriodKey(Period.Today)).toEqual('2022-5-24')
|
||||
})
|
||||
|
||||
@@ -12,6 +12,20 @@ export class PeriodKeyGenerator implements PeriodKeyGeneratorInterface {
|
||||
}
|
||||
|
||||
return periodKeys
|
||||
case Period.Last7Days:
|
||||
for (let i = 1; i <= 7; i++) {
|
||||
periodKeys.unshift(this.getDailyKey(this.getDateNDaysBefore(i)))
|
||||
}
|
||||
|
||||
return periodKeys
|
||||
case Period.Q1ThisYear:
|
||||
return this.generateMonthlyKeysRange(0, 3)
|
||||
case Period.Q2ThisYear:
|
||||
return this.generateMonthlyKeysRange(3, 6)
|
||||
case Period.Q3ThisYear:
|
||||
return this.generateMonthlyKeysRange(6, 9)
|
||||
case Period.Q4ThisYear:
|
||||
return this.generateMonthlyKeysRange(9, 12)
|
||||
default:
|
||||
throw new Error(`Unsuporrted period: ${period}`)
|
||||
}
|
||||
@@ -115,4 +129,16 @@ export class PeriodKeyGenerator implements PeriodKeyGeneratorInterface {
|
||||
|
||||
return yesterday
|
||||
}
|
||||
|
||||
private generateMonthlyKeysRange(startingMonthIndex: number, endingMonthIndex: number): string[] {
|
||||
const today = new Date()
|
||||
const keys = []
|
||||
for (let i = startingMonthIndex; i < endingMonthIndex; i++) {
|
||||
today.setMonth(i)
|
||||
today.setDate(1)
|
||||
keys.push(this.getMonthlyKey(today))
|
||||
}
|
||||
|
||||
return keys
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export * from './Analytics/AnalyticsActivity'
|
||||
export * from './Analytics/AnalyticsStoreInterface'
|
||||
export * from './Statistics/StatisticsMeasure'
|
||||
export * from './Statistics/StatisticsStoreInterface'
|
||||
export * from './Time/Period'
|
||||
export * from './Time/PeriodKeyGenerator'
|
||||
|
||||
@@ -24,6 +24,7 @@ describe('RedisAnalyticsStore', () => {
|
||||
redisClient.setbit = jest.fn()
|
||||
redisClient.getbit = jest.fn().mockReturnValue(1)
|
||||
redisClient.bitop = jest.fn()
|
||||
redisClient.expire = jest.fn()
|
||||
|
||||
periodKeyGenerator = {} as jest.Mocked<PeriodKeyGeneratorInterface>
|
||||
periodKeyGenerator.getPeriodKey = jest.fn().mockReturnValue('period-key')
|
||||
@@ -48,6 +49,17 @@ describe('RedisAnalyticsStore', () => {
|
||||
expect(redisClient.bitcount).toHaveBeenCalledWith('bitmap:action:editing-items:timespan:2022-4-24-2022-4-26')
|
||||
})
|
||||
|
||||
it('should not calculate total count over time of activities if period is unsupported', async () => {
|
||||
let caughtError = null
|
||||
try {
|
||||
await createStore().calculateActivityTotalCountOverTime(AnalyticsActivity.EditingItems, Period.LastWeek)
|
||||
} catch (error) {
|
||||
caughtError = error
|
||||
}
|
||||
|
||||
expect(caughtError).not.toBeNull()
|
||||
})
|
||||
|
||||
it('should calculate total count changes of activities', async () => {
|
||||
periodKeyGenerator.getDiscretePeriodKeys = jest.fn().mockReturnValue(['2022-4-24', '2022-4-25', '2022-4-26'])
|
||||
|
||||
@@ -113,7 +125,7 @@ describe('RedisAnalyticsStore', () => {
|
||||
|
||||
expect(redisClient.bitop).toHaveBeenCalledWith(
|
||||
'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',
|
||||
)
|
||||
|
||||
@@ -9,17 +9,24 @@ export class RedisAnalyticsStore implements AnalyticsStoreInterface {
|
||||
constructor(private periodKeyGenerator: PeriodKeyGeneratorInterface, private redisClient: IORedis.Redis) {}
|
||||
|
||||
async calculateActivityTotalCountOverTime(activity: AnalyticsActivity, period: Period): Promise<number> {
|
||||
if (period !== Period.Last30Days) {
|
||||
if (
|
||||
![Period.Last30Days, Period.Q1ThisYear, Period.Q2ThisYear, Period.Q3ThisYear, Period.Q4ThisYear].includes(period)
|
||||
) {
|
||||
throw new Error(`Unsuporrted period: ${period}`)
|
||||
}
|
||||
|
||||
const periodKeys = this.periodKeyGenerator.getDiscretePeriodKeys(Period.Last30Days)
|
||||
const periodKeys = this.periodKeyGenerator.getDiscretePeriodKeys(period)
|
||||
await this.redisClient.bitop(
|
||||
'OR',
|
||||
`bitmap:action:${activity}:timespan:${periodKeys[0]}-${periodKeys[periodKeys.length - 1]}`,
|
||||
...periodKeys.map((p) => `bitmap:action:${activity}:timespan:${p}`),
|
||||
)
|
||||
|
||||
await this.redisClient.expire(
|
||||
`bitmap:action:${activity}:timespan:${periodKeys[0]}-${periodKeys[periodKeys.length - 1]}`,
|
||||
3600,
|
||||
)
|
||||
|
||||
return this.redisClient.bitcount(
|
||||
`bitmap:action:${activity}:timespan:${periodKeys[0]}-${periodKeys[periodKeys.length - 1]}`,
|
||||
)
|
||||
@@ -29,11 +36,13 @@ export class RedisAnalyticsStore implements AnalyticsStoreInterface {
|
||||
activity: AnalyticsActivity,
|
||||
period: Period,
|
||||
): Promise<Array<{ periodKey: string; totalCount: number }>> {
|
||||
if (period !== Period.Last30Days) {
|
||||
if (
|
||||
![Period.Last30Days, Period.Q1ThisYear, Period.Q2ThisYear, Period.Q3ThisYear, Period.Q4ThisYear].includes(period)
|
||||
) {
|
||||
throw new Error(`Unsuporrted period: ${period}`)
|
||||
}
|
||||
|
||||
const periodKeys = this.periodKeyGenerator.getDiscretePeriodKeys(Period.Last30Days)
|
||||
const periodKeys = this.periodKeyGenerator.getDiscretePeriodKeys(period)
|
||||
const counts = []
|
||||
for (const periodKey of periodKeys) {
|
||||
counts.push({
|
||||
@@ -86,30 +95,43 @@ export class RedisAnalyticsStore implements AnalyticsStoreInterface {
|
||||
return bitValue === 1
|
||||
}
|
||||
|
||||
async calculateActivitiesRetention(parameters: {
|
||||
firstActivity: AnalyticsActivity
|
||||
firstActivityPeriodKey: string
|
||||
secondActivity: AnalyticsActivity
|
||||
secondActivityPeriodKey: string
|
||||
}): Promise<number> {
|
||||
const diffKey = `bitmap:action:${parameters.firstActivity}-${parameters.secondActivity}:timespan:${parameters.secondActivityPeriodKey}`
|
||||
|
||||
await this.redisClient.bitop(
|
||||
'AND',
|
||||
diffKey,
|
||||
`bitmap:action:${parameters.firstActivity}:timespan:${parameters.firstActivityPeriodKey}`,
|
||||
`bitmap:action:${parameters.secondActivity}:timespan:${parameters.secondActivityPeriodKey}`,
|
||||
)
|
||||
|
||||
await this.redisClient.expire(diffKey, 3600)
|
||||
|
||||
const retainedTotalInActivity = await this.redisClient.bitcount(diffKey)
|
||||
|
||||
const initialTotalInActivity = await this.redisClient.bitcount(
|
||||
`bitmap:action:${parameters.firstActivity}:timespan:${parameters.firstActivityPeriodKey}`,
|
||||
)
|
||||
|
||||
return Math.ceil((retainedTotalInActivity * 100) / initialTotalInActivity)
|
||||
}
|
||||
|
||||
async calculateActivityRetention(
|
||||
activity: AnalyticsActivity,
|
||||
firstPeriod: Period,
|
||||
secondPeriod: Period,
|
||||
): Promise<number> {
|
||||
const initialPeriodKey = this.periodKeyGenerator.getPeriodKey(firstPeriod)
|
||||
const subsequentPeriodKey = this.periodKeyGenerator.getPeriodKey(secondPeriod)
|
||||
|
||||
const diffKey = `bitmap:action:${activity}:timespan:${initialPeriodKey}-${subsequentPeriodKey}`
|
||||
|
||||
await this.redisClient.bitop(
|
||||
'AND',
|
||||
diffKey,
|
||||
`bitmap:action:${activity}:timespan:${initialPeriodKey}`,
|
||||
`bitmap:action:${activity}:timespan:${subsequentPeriodKey}`,
|
||||
)
|
||||
|
||||
const retainedTotalInActivity = await this.redisClient.bitcount(diffKey)
|
||||
|
||||
const initialTotalInActivity = await this.redisClient.bitcount(
|
||||
`bitmap:action:${activity}:timespan:${initialPeriodKey}`,
|
||||
)
|
||||
|
||||
return Math.ceil((retainedTotalInActivity * 100) / initialTotalInActivity)
|
||||
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> {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import * as IORedis from 'ioredis'
|
||||
import { PeriodKeyGeneratorInterface } from '../../Domain'
|
||||
|
||||
import { Period, PeriodKeyGeneratorInterface } from '../../Domain'
|
||||
import { StatisticsMeasure } from '../../Domain/Statistics/StatisticsMeasure'
|
||||
|
||||
import { RedisStatisticsStore } from './RedisStatisticsStore'
|
||||
|
||||
@@ -13,6 +15,7 @@ describe('RedisStatisticsStore', () => {
|
||||
beforeEach(() => {
|
||||
pipeline = {} as jest.Mocked<IORedis.Pipeline>
|
||||
pipeline.incr = jest.fn()
|
||||
pipeline.incrbyfloat = jest.fn()
|
||||
pipeline.setbit = jest.fn()
|
||||
pipeline.exec = jest.fn()
|
||||
|
||||
@@ -88,4 +91,30 @@ describe('RedisStatisticsStore', () => {
|
||||
expect(pipeline.incr).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 { Period, PeriodKeyGeneratorInterface } from '../../Domain'
|
||||
import { StatisticsMeasure } from '../../Domain/Statistics/StatisticsMeasure'
|
||||
|
||||
import { StatisticsStoreInterface } from '../../Domain/Statistics/StatisticsStoreInterface'
|
||||
|
||||
export class RedisStatisticsStore implements StatisticsStoreInterface {
|
||||
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> {
|
||||
const count = await this.redisClient.get(
|
||||
`count:action:out-of-sync:timespan:${this.periodKeyGenerator.getPeriodKey(Period.Yesterday)}`,
|
||||
|
||||
@@ -3,6 +3,133 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [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)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **api-gateway:** add payment success events to report ([ee79347](https://github.com/standardnotes/api-gateway/commit/ee79347e27f5887def2cda57091a7c0a40570d33))
|
||||
|
||||
## [1.15.1](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.15.0...@standardnotes/api-gateway@1.15.1) (2022-08-15)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||
|
||||
# [1.15.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.14.3...@standardnotes/api-gateway@1.15.0) (2022-08-15)
|
||||
|
||||
### Features
|
||||
|
||||
* **api-gateway:** add gathering analytics for failed payments ([d0023a6](https://github.com/standardnotes/api-gateway/commit/d0023a6c92756c81b8daa9089d38141b6cd4fe48))
|
||||
|
||||
## [1.14.3](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.14.2...@standardnotes/api-gateway@1.14.3) (2022-08-15)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||
|
||||
## [1.14.2](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.14.1...@standardnotes/api-gateway@1.14.2) (2022-08-15)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||
|
||||
## [1.14.1](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.14.0...@standardnotes/api-gateway@1.14.1) (2022-08-15)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||
|
||||
# [1.14.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.13.1...@standardnotes/api-gateway@1.14.0) (2022-08-15)
|
||||
|
||||
### Features
|
||||
|
||||
* **api-gateway:** add quarterly analytics ([67378e4](https://github.com/standardnotes/api-gateway/commit/67378e4535ef2760cfe3fe27256ffe117ee11a71))
|
||||
|
||||
## [1.13.1](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.13.0...@standardnotes/api-gateway@1.13.1) (2022-08-15)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||
|
||||
# [1.13.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.12.0...@standardnotes/api-gateway@1.13.0) (2022-08-11)
|
||||
|
||||
### Features
|
||||
|
||||
* add analytics for subscription cancelling, refunding and account deletion ([1607638](https://github.com/standardnotes/api-gateway/commit/16076382bae74552a35901bb5474e2c2c2d96f43))
|
||||
|
||||
# [1.12.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.11.5...@standardnotes/api-gateway@1.12.0) (2022-08-10)
|
||||
|
||||
### Features
|
||||
|
||||
* **api-gateway:** add publishing subscription purchased, renewed and registration analytics ([dea5fd7](https://github.com/standardnotes/api-gateway/commit/dea5fd717d222d96bcbbd16a8d84a84ed20144a8))
|
||||
|
||||
## [1.11.5](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.11.4...@standardnotes/api-gateway@1.11.5) (2022-08-10)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||
|
||||
@@ -12,13 +12,97 @@ import {
|
||||
DailyAnalyticsReportGeneratedEvent,
|
||||
DomainEventService,
|
||||
} from '@standardnotes/domain-events'
|
||||
import { AnalyticsActivity, AnalyticsStoreInterface, Period, StatisticsStoreInterface } from '@standardnotes/analytics'
|
||||
import {
|
||||
AnalyticsActivity,
|
||||
AnalyticsStoreInterface,
|
||||
Period,
|
||||
StatisticsMeasure,
|
||||
StatisticsStoreInterface,
|
||||
} from '@standardnotes/analytics'
|
||||
|
||||
const requestReport = async (
|
||||
analyticsStore: AnalyticsStoreInterface,
|
||||
statisticsStore: StatisticsStoreInterface,
|
||||
domainEventPublisher: DomainEventPublisherInterface,
|
||||
): Promise<void> => {
|
||||
const analyticsOverTime = []
|
||||
|
||||
const thirtyDaysAnalyticsNames = [
|
||||
AnalyticsActivity.GeneralActivity,
|
||||
AnalyticsActivity.EditingItems,
|
||||
AnalyticsActivity.SubscriptionPurchased,
|
||||
AnalyticsActivity.Register,
|
||||
AnalyticsActivity.SubscriptionRenewed,
|
||||
AnalyticsActivity.DeleteAccount,
|
||||
AnalyticsActivity.SubscriptionCancelled,
|
||||
AnalyticsActivity.SubscriptionRefunded,
|
||||
]
|
||||
|
||||
for (const analyticsName of thirtyDaysAnalyticsNames) {
|
||||
analyticsOverTime.push({
|
||||
name: analyticsName,
|
||||
period: Period.Last30Days,
|
||||
counts: await analyticsStore.calculateActivityChangesTotalCount(analyticsName, Period.Last30Days),
|
||||
totalCount: await analyticsStore.calculateActivityTotalCountOverTime(analyticsName, Period.Last30Days),
|
||||
})
|
||||
}
|
||||
|
||||
const quarterlyAnalyticsNames = [
|
||||
AnalyticsActivity.Register,
|
||||
AnalyticsActivity.SubscriptionPurchased,
|
||||
AnalyticsActivity.SubscriptionRenewed,
|
||||
]
|
||||
|
||||
for (const analyticsName of quarterlyAnalyticsNames) {
|
||||
for (const period of [Period.Q1ThisYear, Period.Q2ThisYear, Period.Q3ThisYear, Period.Q4ThisYear]) {
|
||||
analyticsOverTime.push({
|
||||
name: analyticsName,
|
||||
period: period,
|
||||
counts: await analyticsStore.calculateActivityChangesTotalCount(analyticsName, period),
|
||||
totalCount: await analyticsStore.calculateActivityTotalCountOverTime(analyticsName, period),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const yesterdayActivityStatistics = []
|
||||
const yesterdayActivityNames = [
|
||||
AnalyticsActivity.LimitedDiscountOfferPurchased,
|
||||
AnalyticsActivity.GeneralActivity,
|
||||
AnalyticsActivity.PaymentFailed,
|
||||
AnalyticsActivity.PaymentSuccess,
|
||||
]
|
||||
|
||||
for (const activityName of yesterdayActivityNames) {
|
||||
yesterdayActivityStatistics.push({
|
||||
name: activityName,
|
||||
retention: await analyticsStore.calculateActivityRetention(
|
||||
activityName,
|
||||
Period.DayBeforeYesterday,
|
||||
Period.Yesterday,
|
||||
),
|
||||
totalCount: await analyticsStore.calculateActivityTotalCount(activityName, Period.Yesterday),
|
||||
})
|
||||
}
|
||||
|
||||
const statisticMeasureNames = [
|
||||
StatisticsMeasure.Income,
|
||||
StatisticsMeasure.Refunds,
|
||||
StatisticsMeasure.RegistrationLength,
|
||||
StatisticsMeasure.SubscriptionLength,
|
||||
StatisticsMeasure.RegistrationToSubscriptionTime,
|
||||
]
|
||||
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 event: DailyAnalyticsReportGeneratedEvent = {
|
||||
type: 'DAILY_ANALYTICS_REPORT_GENERATED',
|
||||
createdAt: new Date(),
|
||||
@@ -33,66 +117,9 @@ const requestReport = async (
|
||||
applicationStatistics: await statisticsStore.getYesterdayApplicationUsage(),
|
||||
snjsStatistics: await statisticsStore.getYesterdaySNJSUsage(),
|
||||
outOfSyncIncidents: await statisticsStore.getYesterdayOutOfSyncIncidents(),
|
||||
activityStatistics: [
|
||||
{
|
||||
name: AnalyticsActivity.EditingItems,
|
||||
retention: await analyticsStore.calculateActivityRetention(
|
||||
AnalyticsActivity.EditingItems,
|
||||
Period.DayBeforeYesterday,
|
||||
Period.Yesterday,
|
||||
),
|
||||
totalCount: await analyticsStore.calculateActivityTotalCount(
|
||||
AnalyticsActivity.EditingItems,
|
||||
Period.Yesterday,
|
||||
),
|
||||
},
|
||||
{
|
||||
name: AnalyticsActivity.LimitedDiscountOfferPurchased,
|
||||
retention: 0,
|
||||
totalCount: await analyticsStore.calculateActivityTotalCount(
|
||||
AnalyticsActivity.LimitedDiscountOfferPurchased,
|
||||
Period.Yesterday,
|
||||
),
|
||||
},
|
||||
{
|
||||
name: AnalyticsActivity.GeneralActivity,
|
||||
retention: await analyticsStore.calculateActivityRetention(
|
||||
AnalyticsActivity.GeneralActivity,
|
||||
Period.DayBeforeYesterday,
|
||||
Period.Yesterday,
|
||||
),
|
||||
totalCount: await analyticsStore.calculateActivityTotalCount(
|
||||
AnalyticsActivity.GeneralActivity,
|
||||
Period.Yesterday,
|
||||
),
|
||||
},
|
||||
],
|
||||
activityStatisticsOverTime: [
|
||||
{
|
||||
name: AnalyticsActivity.GeneralActivity,
|
||||
period: Period.Last30Days,
|
||||
counts: await analyticsStore.calculateActivityChangesTotalCount(
|
||||
AnalyticsActivity.GeneralActivity,
|
||||
Period.Last30Days,
|
||||
),
|
||||
totalCount: await analyticsStore.calculateActivityTotalCountOverTime(
|
||||
AnalyticsActivity.GeneralActivity,
|
||||
Period.Last30Days,
|
||||
),
|
||||
},
|
||||
{
|
||||
name: AnalyticsActivity.EditingItems,
|
||||
period: Period.Last30Days,
|
||||
counts: await analyticsStore.calculateActivityChangesTotalCount(
|
||||
AnalyticsActivity.EditingItems,
|
||||
Period.Last30Days,
|
||||
),
|
||||
totalCount: await analyticsStore.calculateActivityTotalCountOverTime(
|
||||
AnalyticsActivity.EditingItems,
|
||||
Period.Last30Days,
|
||||
),
|
||||
},
|
||||
],
|
||||
activityStatistics: yesterdayActivityStatistics,
|
||||
activityStatisticsOverTime: analyticsOverTime,
|
||||
statisticMeasures,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/api-gateway",
|
||||
"version": "1.11.5",
|
||||
"version": "1.16.6",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -1,18 +1,25 @@
|
||||
import { Request, Response } from 'express'
|
||||
import { inject } from 'inversify'
|
||||
import { BaseHttpController, controller, httpDelete, httpPost } from 'inversify-express-utils'
|
||||
import { Logger } from 'winston'
|
||||
|
||||
import TYPES from '../../Bootstrap/Types'
|
||||
import { HttpServiceInterface } from '../../Service/Http/HttpServiceInterface'
|
||||
|
||||
@controller('/v1/sockets')
|
||||
export class WebSocketsController extends BaseHttpController {
|
||||
constructor(@inject(TYPES.HTTPService) private httpService: HttpServiceInterface) {
|
||||
constructor(
|
||||
@inject(TYPES.HTTPService) private httpService: HttpServiceInterface,
|
||||
@inject(TYPES.Logger) private logger: Logger,
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
||||
@httpPost('/', TYPES.AuthMiddleware)
|
||||
async createWebSocketConnection(request: Request, response: Response): Promise<void> {
|
||||
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')
|
||||
|
||||
return
|
||||
@@ -24,6 +31,8 @@ export class WebSocketsController extends BaseHttpController {
|
||||
@httpDelete('/')
|
||||
async deleteWebSocketConnection(request: Request, response: Response): Promise<void> {
|
||||
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')
|
||||
|
||||
return
|
||||
|
||||
@@ -3,6 +3,148 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [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)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **scheduler:** change discount from 10% to 20% on a limited time offer ([4b3de26](https://github.com/standardnotes/server/commit/4b3de264efc4fffb2603181c158cddb25c4ed4a9))
|
||||
|
||||
# [1.20.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.19.1...@standardnotes/auth-server@1.20.0) (2022-08-15)
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** add payment success event handler ([01a08ea](https://github.com/standardnotes/server/commit/01a08eae582e070ec844f5e05f34260447b7d4c6))
|
||||
|
||||
## [1.19.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.19.0...@standardnotes/auth-server@1.19.1) (2022-08-15)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/auth-server
|
||||
|
||||
# [1.19.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.18.4...@standardnotes/auth-server@1.19.0) (2022-08-15)
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** add payment failed event handler ([58c5b58](https://github.com/standardnotes/server/commit/58c5b586a904cf1fd179cc28783a6ae7da688063))
|
||||
|
||||
## [1.18.4](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.18.3...@standardnotes/auth-server@1.18.4) (2022-08-15)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/auth-server
|
||||
|
||||
## [1.18.3](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.18.2...@standardnotes/auth-server@1.18.3) (2022-08-15)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/auth-server
|
||||
|
||||
## [1.18.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.18.1...@standardnotes/auth-server@1.18.2) (2022-08-15)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/auth-server
|
||||
|
||||
## [1.18.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.18.0...@standardnotes/auth-server@1.18.1) (2022-08-15)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/auth-server
|
||||
|
||||
# [1.18.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.17.0...@standardnotes/auth-server@1.18.0) (2022-08-12)
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** add endpoint for generating offline subscription tokens for admin panel ([c61b615](https://github.com/standardnotes/server/commit/c61b615da6e1d4556e60e73d7414a80e8b331fca))
|
||||
|
||||
# [1.17.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.16.2...@standardnotes/auth-server@1.17.0) (2022-08-11)
|
||||
|
||||
### Features
|
||||
|
||||
* add analytics for subscription cancelling, refunding and account deletion ([1607638](https://github.com/standardnotes/server/commit/16076382bae74552a35901bb5474e2c2c2d96f43))
|
||||
|
||||
## [1.16.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.16.1...@standardnotes/auth-server@1.16.2) (2022-08-10)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/auth-server
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/auth-server",
|
||||
"version": "1.16.2",
|
||||
"version": "1.25.2",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
@@ -63,7 +63,7 @@
|
||||
"reflect-metadata": "0.1.13",
|
||||
"typeorm": "^0.3.6",
|
||||
"ua-parser-js": "1.0.2",
|
||||
"uuid": "8.3.2",
|
||||
"uuid": "^9.0.0",
|
||||
"winston": "^3.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -9,7 +9,13 @@ import {
|
||||
} from '@standardnotes/domain-events'
|
||||
import { TimerInterface, Timer } from '@standardnotes/time'
|
||||
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 TYPES from './Types'
|
||||
@@ -191,6 +197,9 @@ import { AuthController } from '../Controller/AuthController'
|
||||
import { VerifyPredicate } from '../Domain/UseCase/VerifyPredicate/VerifyPredicate'
|
||||
import { PredicateVerificationRequestedEventHandler } from '../Domain/Handler/PredicateVerificationRequestedEventHandler'
|
||||
import { MuteMarketingEmails } from '../Domain/UseCase/MuteMarketingEmails/MuteMarketingEmails'
|
||||
import { PaymentFailedEventHandler } from '../Domain/Handler/PaymentFailedEventHandler'
|
||||
import { PaymentSuccessEventHandler } from '../Domain/Handler/PaymentSuccessEventHandler'
|
||||
import { RefundProcessedEventHandler } from '../Domain/Handler/RefundProcessedEventHandler'
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const newrelicFormatter = require('@newrelic/winston-enricher')
|
||||
@@ -478,6 +487,9 @@ export class ContainerConfigLoader {
|
||||
container
|
||||
.bind<PredicateVerificationRequestedEventHandler>(TYPES.PredicateVerificationRequestedEventHandler)
|
||||
.to(PredicateVerificationRequestedEventHandler)
|
||||
container.bind<PaymentFailedEventHandler>(TYPES.PaymentFailedEventHandler).to(PaymentFailedEventHandler)
|
||||
container.bind<PaymentSuccessEventHandler>(TYPES.PaymentSuccessEventHandler).to(PaymentSuccessEventHandler)
|
||||
container.bind<RefundProcessedEventHandler>(TYPES.RefundProcessedEventHandler).to(RefundProcessedEventHandler)
|
||||
|
||||
// Services
|
||||
container.bind<UAParser>(TYPES.DeviceDetector).toConstantValue(new UAParser())
|
||||
@@ -538,9 +550,13 @@ export class ContainerConfigLoader {
|
||||
.bind<SelectorInterface<boolean>>(TYPES.BooleanSelector)
|
||||
.toConstantValue(new DeterministicSelector<boolean>())
|
||||
container.bind<UserSubscriptionServiceInterface>(TYPES.UserSubscriptionService).to(UserSubscriptionService)
|
||||
const periodKeyGenerator = new PeriodKeyGenerator()
|
||||
container
|
||||
.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)))
|
||||
|
||||
if (env.get('SNS_TOPIC_ARN', true)) {
|
||||
container
|
||||
@@ -576,6 +592,9 @@ export class ContainerConfigLoader {
|
||||
],
|
||||
['SHARED_SUBSCRIPTION_INVITATION_CREATED', container.get(TYPES.SharedSubscriptionInvitationCreatedEventHandler)],
|
||||
['PREDICATE_VERIFICATION_REQUESTED', container.get(TYPES.PredicateVerificationRequestedEventHandler)],
|
||||
['PAYMENT_FAILED', container.get(TYPES.PaymentFailedEventHandler)],
|
||||
['PAYMENT_SUCCESS', container.get(TYPES.PaymentSuccessEventHandler)],
|
||||
['REFUND_PROCESSED', container.get(TYPES.RefundProcessedEventHandler)],
|
||||
])
|
||||
|
||||
if (env.get('SQS_QUEUE_URL', true)) {
|
||||
|
||||
@@ -143,6 +143,9 @@ const TYPES = {
|
||||
UserDisabledSessionUserAgentLoggingEventHandler: Symbol.for('UserDisabledSessionUserAgentLoggingEventHandler'),
|
||||
SharedSubscriptionInvitationCreatedEventHandler: Symbol.for('SharedSubscriptionInvitationCreatedEventHandler'),
|
||||
PredicateVerificationRequestedEventHandler: Symbol.for('PredicateVerificationRequestedEventHandler'),
|
||||
PaymentFailedEventHandler: Symbol.for('PaymentFailedEventHandler'),
|
||||
PaymentSuccessEventHandler: Symbol.for('PaymentSuccessEventHandler'),
|
||||
RefundProcessedEventHandler: Symbol.for('RefundProcessedEventHandler'),
|
||||
// Services
|
||||
DeviceDetector: Symbol.for('DeviceDetector'),
|
||||
SessionService: Symbol.for('SessionService'),
|
||||
@@ -184,6 +187,7 @@ const TYPES = {
|
||||
BooleanSelector: Symbol.for('BooleanSelector'),
|
||||
UserSubscriptionService: Symbol.for('UserSubscriptionService'),
|
||||
AnalyticsStore: Symbol.for('AnalyticsStore'),
|
||||
StatisticsStore: Symbol.for('StatisticsStore'),
|
||||
}
|
||||
|
||||
export default TYPES
|
||||
|
||||
@@ -7,15 +7,18 @@ import { UserRepositoryInterface } from '../Domain/User/UserRepositoryInterface'
|
||||
import * as express from 'express'
|
||||
import { DeleteSetting } from '../Domain/UseCase/DeleteSetting/DeleteSetting'
|
||||
import { CreateSubscriptionToken } from '../Domain/UseCase/CreateSubscriptionToken/CreateSubscriptionToken'
|
||||
import { CreateOfflineSubscriptionToken } from '../Domain/UseCase/CreateOfflineSubscriptionToken/CreateOfflineSubscriptionToken'
|
||||
|
||||
describe('AdminController', () => {
|
||||
let deleteSetting: DeleteSetting
|
||||
let userRepository: UserRepositoryInterface
|
||||
let createSubscriptionToken: CreateSubscriptionToken
|
||||
let createOfflineSubscriptionToken: CreateOfflineSubscriptionToken
|
||||
let request: express.Request
|
||||
let user: User
|
||||
|
||||
const createController = () => new AdminController(deleteSetting, userRepository, createSubscriptionToken)
|
||||
const createController = () =>
|
||||
new AdminController(deleteSetting, userRepository, createSubscriptionToken, createOfflineSubscriptionToken)
|
||||
|
||||
beforeEach(() => {
|
||||
user = {} as jest.Mocked<User>
|
||||
@@ -34,6 +37,14 @@ describe('AdminController', () => {
|
||||
},
|
||||
})
|
||||
|
||||
createOfflineSubscriptionToken = {} as jest.Mocked<CreateOfflineSubscriptionToken>
|
||||
createOfflineSubscriptionToken.execute = jest.fn().mockReturnValue({
|
||||
success: true,
|
||||
offlineSubscriptionToken: {
|
||||
token: '123-sub-token',
|
||||
},
|
||||
})
|
||||
|
||||
request = {
|
||||
headers: {},
|
||||
body: {},
|
||||
@@ -125,6 +136,31 @@ describe('AdminController', () => {
|
||||
expect(await result.content.readAsStringAsync()).toEqual('{"token":"123-sub-token"}')
|
||||
})
|
||||
|
||||
it("should return a new offline subscription token for the user's email", async () => {
|
||||
request.params.email = 'test@test.te'
|
||||
|
||||
const httpResponse = await createController().createOfflineToken(request)
|
||||
const result = await httpResponse.executeAsync()
|
||||
|
||||
expect(httpResponse).toBeInstanceOf(results.JsonResult)
|
||||
|
||||
expect(result.statusCode).toBe(200)
|
||||
expect(await result.content.readAsStringAsync()).toEqual('{"token":"123-sub-token"}')
|
||||
})
|
||||
|
||||
it('should not return a new offline subscription token if the workflow fails', async () => {
|
||||
request.params.email = 'test@test.te'
|
||||
|
||||
createOfflineSubscriptionToken.execute = jest.fn().mockReturnValue({ success: false })
|
||||
|
||||
const httpResponse = await createController().createOfflineToken(request)
|
||||
const result = await httpResponse.executeAsync()
|
||||
|
||||
expect(httpResponse).toBeInstanceOf(results.BadRequestResult)
|
||||
|
||||
expect(result.statusCode).toBe(400)
|
||||
})
|
||||
|
||||
it('should not delete email backup setting if value is null', async () => {
|
||||
request.body = {}
|
||||
request.params = {
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
results,
|
||||
} from 'inversify-express-utils'
|
||||
import TYPES from '../Bootstrap/Types'
|
||||
import { CreateOfflineSubscriptionToken } from '../Domain/UseCase/CreateOfflineSubscriptionToken/CreateOfflineSubscriptionToken'
|
||||
import { CreateSubscriptionToken } from '../Domain/UseCase/CreateSubscriptionToken/CreateSubscriptionToken'
|
||||
import { DeleteSetting } from '../Domain/UseCase/DeleteSetting/DeleteSetting'
|
||||
import { UserRepositoryInterface } from '../Domain/User/UserRepositoryInterface'
|
||||
@@ -21,6 +22,8 @@ export class AdminController extends BaseHttpController {
|
||||
@inject(TYPES.DeleteSetting) private doDeleteSetting: DeleteSetting,
|
||||
@inject(TYPES.UserRepository) private userRepository: UserRepositoryInterface,
|
||||
@inject(TYPES.CreateSubscriptionToken) private createSubscriptionToken: CreateSubscriptionToken,
|
||||
@inject(TYPES.CreateOfflineSubscriptionToken)
|
||||
private createOfflineSubscriptionToken: CreateOfflineSubscriptionToken,
|
||||
) {
|
||||
super()
|
||||
}
|
||||
@@ -90,6 +93,22 @@ export class AdminController extends BaseHttpController {
|
||||
})
|
||||
}
|
||||
|
||||
@httpPost('/users/:email/offline-subscription-token')
|
||||
async createOfflineToken(request: Request): Promise<results.JsonResult | results.BadRequestResult> {
|
||||
const { email } = request.params
|
||||
const result = await this.createOfflineSubscriptionToken.execute({
|
||||
userEmail: email,
|
||||
})
|
||||
|
||||
if (!result.success) {
|
||||
return this.badRequest()
|
||||
}
|
||||
|
||||
return this.json({
|
||||
token: result.offlineSubscriptionToken.token,
|
||||
})
|
||||
}
|
||||
|
||||
@httpPost('/users/:userUuid/email-backups')
|
||||
async disableEmailBackups(request: Request): Promise<results.BadRequestErrorMessageResult | results.OkResult> {
|
||||
const { userUuid } = request.params
|
||||
|
||||
@@ -11,6 +11,9 @@ import { SessionRepositoryInterface } from '../Session/SessionRepositoryInterfac
|
||||
import { User } from '../User/User'
|
||||
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
|
||||
import { AccountDeletionRequestedEventHandler } from './AccountDeletionRequestedEventHandler'
|
||||
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
|
||||
import { AnalyticsStoreInterface, StatisticsStoreInterface } from '@standardnotes/analytics'
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
|
||||
describe('AccountDeletionRequestedEventHandler', () => {
|
||||
let userRepository: UserRepositoryInterface
|
||||
@@ -23,6 +26,10 @@ describe('AccountDeletionRequestedEventHandler', () => {
|
||||
let revokedSession: RevokedSession
|
||||
let user: User
|
||||
let event: AccountDeletionRequestedEvent
|
||||
let getUserAnalyticsId: GetUserAnalyticsId
|
||||
let analyticsStore: AnalyticsStoreInterface
|
||||
let statisticsStore: StatisticsStoreInterface
|
||||
let timer: TimerInterface
|
||||
|
||||
const createHandler = () =>
|
||||
new AccountDeletionRequestedEventHandler(
|
||||
@@ -30,6 +37,10 @@ describe('AccountDeletionRequestedEventHandler', () => {
|
||||
sessionRepository,
|
||||
ephemeralSessionRepository,
|
||||
revokedSessionRepository,
|
||||
getUserAnalyticsId,
|
||||
analyticsStore,
|
||||
statisticsStore,
|
||||
timer,
|
||||
logger,
|
||||
)
|
||||
|
||||
@@ -72,9 +83,22 @@ describe('AccountDeletionRequestedEventHandler', () => {
|
||||
regularSubscriptionUuid: '2-3-4',
|
||||
}
|
||||
|
||||
getUserAnalyticsId = {} as jest.Mocked<GetUserAnalyticsId>
|
||||
getUserAnalyticsId.execute = jest.fn().mockReturnValue({ analyticsId: 3 })
|
||||
|
||||
analyticsStore = {} as jest.Mocked<AnalyticsStoreInterface>
|
||||
analyticsStore.markActivity = jest.fn()
|
||||
|
||||
logger = {} as jest.Mocked<Logger>
|
||||
logger.info = 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 () => {
|
||||
|
||||
@@ -1,10 +1,19 @@
|
||||
import {
|
||||
AnalyticsActivity,
|
||||
AnalyticsStoreInterface,
|
||||
Period,
|
||||
StatisticsMeasure,
|
||||
StatisticsStoreInterface,
|
||||
} from '@standardnotes/analytics'
|
||||
import { AccountDeletionRequestedEvent, DomainEventHandlerInterface } from '@standardnotes/domain-events'
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
import { inject, injectable } from 'inversify'
|
||||
import { Logger } from 'winston'
|
||||
import TYPES from '../../Bootstrap/Types'
|
||||
import { EphemeralSessionRepositoryInterface } from '../Session/EphemeralSessionRepositoryInterface'
|
||||
import { RevokedSessionRepositoryInterface } from '../Session/RevokedSessionRepositoryInterface'
|
||||
import { SessionRepositoryInterface } from '../Session/SessionRepositoryInterface'
|
||||
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
|
||||
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
|
||||
|
||||
@injectable()
|
||||
@@ -14,6 +23,10 @@ export class AccountDeletionRequestedEventHandler implements DomainEventHandlerI
|
||||
@inject(TYPES.SessionRepository) private sessionRepository: SessionRepositoryInterface,
|
||||
@inject(TYPES.EphemeralSessionRepository) private ephemeralSessionRepository: EphemeralSessionRepositoryInterface,
|
||||
@inject(TYPES.RevokedSessionRepository) private revokedSessionRepository: RevokedSessionRepositoryInterface,
|
||||
@inject(TYPES.GetUserAnalyticsId) private getUserAnalyticsId: GetUserAnalyticsId,
|
||||
@inject(TYPES.AnalyticsStore) private analyticsStore: AnalyticsStoreInterface,
|
||||
@inject(TYPES.StatisticsStore) private statisticsStore: StatisticsStoreInterface,
|
||||
@inject(TYPES.Timer) private timer: TimerInterface,
|
||||
@inject(TYPES.Logger) private logger: Logger,
|
||||
) {}
|
||||
|
||||
@@ -28,6 +41,21 @@ export class AccountDeletionRequestedEventHandler implements DomainEventHandlerI
|
||||
|
||||
await this.removeSessions(event.payload.userUuid)
|
||||
|
||||
const { analyticsId } = await this.getUserAnalyticsId.execute({ userUuid: user.uuid })
|
||||
await this.analyticsStore.markActivity([AnalyticsActivity.DeleteAccount], analyticsId, [
|
||||
Period.Today,
|
||||
Period.ThisWeek,
|
||||
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)
|
||||
|
||||
this.logger.info(`Finished account cleanup for user: ${event.payload.userUuid}`)
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import 'reflect-metadata'
|
||||
|
||||
import { PaymentFailedEvent } from '@standardnotes/domain-events'
|
||||
import { AnalyticsStoreInterface } from '@standardnotes/analytics'
|
||||
|
||||
import { PaymentFailedEventHandler } from './PaymentFailedEventHandler'
|
||||
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
|
||||
import { User } from '../User/User'
|
||||
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
|
||||
|
||||
describe('PaymentFailedEventHandler', () => {
|
||||
let userRepository: UserRepositoryInterface
|
||||
let event: PaymentFailedEvent
|
||||
let user: User
|
||||
let getUserAnalyticsId: GetUserAnalyticsId
|
||||
let analyticsStore: AnalyticsStoreInterface
|
||||
|
||||
const createHandler = () => new PaymentFailedEventHandler(userRepository, getUserAnalyticsId, analyticsStore)
|
||||
|
||||
beforeEach(() => {
|
||||
user = {} as jest.Mocked<User>
|
||||
|
||||
userRepository = {} as jest.Mocked<UserRepositoryInterface>
|
||||
userRepository.findOneByEmail = jest.fn().mockReturnValue(user)
|
||||
|
||||
getUserAnalyticsId = {} as jest.Mocked<GetUserAnalyticsId>
|
||||
getUserAnalyticsId.execute = jest.fn().mockReturnValue({ analyticsId: 3 })
|
||||
|
||||
analyticsStore = {} as jest.Mocked<AnalyticsStoreInterface>
|
||||
analyticsStore.markActivity = jest.fn()
|
||||
|
||||
event = {} as jest.Mocked<PaymentFailedEvent>
|
||||
event.payload = {
|
||||
userEmail: 'test@test.com',
|
||||
}
|
||||
})
|
||||
|
||||
it('should mark payment failed for analytics', async () => {
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(analyticsStore.markActivity).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should not mark payment failed for analytics if user is not found', async () => {
|
||||
userRepository.findOneByEmail = jest.fn().mockReturnValue(null)
|
||||
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(analyticsStore.markActivity).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,30 @@
|
||||
import { AnalyticsActivity, AnalyticsStoreInterface, Period } from '@standardnotes/analytics'
|
||||
import { DomainEventHandlerInterface, PaymentFailedEvent } from '@standardnotes/domain-events'
|
||||
import { inject, injectable } from 'inversify'
|
||||
|
||||
import TYPES from '../../Bootstrap/Types'
|
||||
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
|
||||
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
|
||||
|
||||
@injectable()
|
||||
export class PaymentFailedEventHandler implements DomainEventHandlerInterface {
|
||||
constructor(
|
||||
@inject(TYPES.UserRepository) private userRepository: UserRepositoryInterface,
|
||||
@inject(TYPES.GetUserAnalyticsId) private getUserAnalyticsId: GetUserAnalyticsId,
|
||||
@inject(TYPES.AnalyticsStore) private analyticsStore: AnalyticsStoreInterface,
|
||||
) {}
|
||||
|
||||
async handle(event: PaymentFailedEvent): Promise<void> {
|
||||
const user = await this.userRepository.findOneByEmail(event.payload.userEmail)
|
||||
if (user === null) {
|
||||
return
|
||||
}
|
||||
|
||||
const { analyticsId } = await this.getUserAnalyticsId.execute({ userUuid: user.uuid })
|
||||
await this.analyticsStore.markActivity([AnalyticsActivity.PaymentFailed], analyticsId, [
|
||||
Period.Today,
|
||||
Period.ThisWeek,
|
||||
Period.ThisMonth,
|
||||
])
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
import 'reflect-metadata'
|
||||
|
||||
import { PaymentSuccessEvent } from '@standardnotes/domain-events'
|
||||
import { AnalyticsStoreInterface, StatisticsStoreInterface } from '@standardnotes/analytics'
|
||||
|
||||
import { PaymentSuccessEventHandler } from './PaymentSuccessEventHandler'
|
||||
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
|
||||
import { User } from '../User/User'
|
||||
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
|
||||
|
||||
describe('PaymentSuccessEventHandler', () => {
|
||||
let userRepository: UserRepositoryInterface
|
||||
let event: PaymentSuccessEvent
|
||||
let user: User
|
||||
let getUserAnalyticsId: GetUserAnalyticsId
|
||||
let analyticsStore: AnalyticsStoreInterface
|
||||
let statisticsStore: StatisticsStoreInterface
|
||||
|
||||
const createHandler = () =>
|
||||
new PaymentSuccessEventHandler(userRepository, getUserAnalyticsId, analyticsStore, statisticsStore)
|
||||
|
||||
beforeEach(() => {
|
||||
user = {} as jest.Mocked<User>
|
||||
|
||||
userRepository = {} as jest.Mocked<UserRepositoryInterface>
|
||||
userRepository.findOneByEmail = jest.fn().mockReturnValue(user)
|
||||
|
||||
getUserAnalyticsId = {} as jest.Mocked<GetUserAnalyticsId>
|
||||
getUserAnalyticsId.execute = jest.fn().mockReturnValue({ analyticsId: 3 })
|
||||
|
||||
analyticsStore = {} as jest.Mocked<AnalyticsStoreInterface>
|
||||
analyticsStore.markActivity = jest.fn()
|
||||
|
||||
statisticsStore = {} as jest.Mocked<StatisticsStoreInterface>
|
||||
statisticsStore.incrementMeasure = jest.fn()
|
||||
|
||||
event = {} as jest.Mocked<PaymentSuccessEvent>
|
||||
event.payload = {
|
||||
userEmail: 'test@test.com',
|
||||
amount: 12.45,
|
||||
}
|
||||
})
|
||||
|
||||
it('should mark payment failed for analytics', async () => {
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(analyticsStore.markActivity).toHaveBeenCalled()
|
||||
expect(statisticsStore.incrementMeasure).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should not mark payment failed for analytics if user is not found', async () => {
|
||||
userRepository.findOneByEmail = jest.fn().mockReturnValue(null)
|
||||
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(analyticsStore.markActivity).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,43 @@
|
||||
import {
|
||||
AnalyticsActivity,
|
||||
AnalyticsStoreInterface,
|
||||
Period,
|
||||
StatisticsMeasure,
|
||||
StatisticsStoreInterface,
|
||||
} from '@standardnotes/analytics'
|
||||
import { DomainEventHandlerInterface, PaymentSuccessEvent } from '@standardnotes/domain-events'
|
||||
import { inject, injectable } from 'inversify'
|
||||
|
||||
import TYPES from '../../Bootstrap/Types'
|
||||
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
|
||||
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
|
||||
|
||||
@injectable()
|
||||
export class PaymentSuccessEventHandler implements DomainEventHandlerInterface {
|
||||
constructor(
|
||||
@inject(TYPES.UserRepository) private userRepository: UserRepositoryInterface,
|
||||
@inject(TYPES.GetUserAnalyticsId) private getUserAnalyticsId: GetUserAnalyticsId,
|
||||
@inject(TYPES.AnalyticsStore) private analyticsStore: AnalyticsStoreInterface,
|
||||
@inject(TYPES.StatisticsStore) private statisticsStore: StatisticsStoreInterface,
|
||||
) {}
|
||||
|
||||
async handle(event: PaymentSuccessEvent): Promise<void> {
|
||||
const user = await this.userRepository.findOneByEmail(event.payload.userEmail)
|
||||
if (user === null) {
|
||||
return
|
||||
}
|
||||
|
||||
const { analyticsId } = await this.getUserAnalyticsId.execute({ userUuid: user.uuid })
|
||||
await this.analyticsStore.markActivity([AnalyticsActivity.PaymentSuccess], analyticsId, [
|
||||
Period.Today,
|
||||
Period.ThisWeek,
|
||||
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,19 +8,57 @@ import * as dayjs from 'dayjs'
|
||||
import { SubscriptionCancelledEventHandler } from './SubscriptionCancelledEventHandler'
|
||||
import { UserSubscriptionRepositoryInterface } from '../Subscription/UserSubscriptionRepositoryInterface'
|
||||
import { OfflineUserSubscriptionRepositoryInterface } from '../Subscription/OfflineUserSubscriptionRepositoryInterface'
|
||||
import { AnalyticsStoreInterface, Period, StatisticsMeasure, StatisticsStoreInterface } from '@standardnotes/analytics'
|
||||
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
|
||||
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
|
||||
import { User } from '../User/User'
|
||||
import { UserSubscription } from '../Subscription/UserSubscription'
|
||||
import { Logger } from 'winston'
|
||||
|
||||
describe('SubscriptionCancelledEventHandler', () => {
|
||||
let userSubscriptionRepository: UserSubscriptionRepositoryInterface
|
||||
let offlineUserSubscriptionRepository: OfflineUserSubscriptionRepositoryInterface
|
||||
let event: SubscriptionCancelledEvent
|
||||
let userRepository: UserRepositoryInterface
|
||||
let getUserAnalyticsId: GetUserAnalyticsId
|
||||
let analyticsStore: AnalyticsStoreInterface
|
||||
let statisticsStore: StatisticsStoreInterface
|
||||
let timestamp: number
|
||||
let logger: Logger
|
||||
|
||||
const createHandler = () =>
|
||||
new SubscriptionCancelledEventHandler(userSubscriptionRepository, offlineUserSubscriptionRepository)
|
||||
new SubscriptionCancelledEventHandler(
|
||||
userSubscriptionRepository,
|
||||
offlineUserSubscriptionRepository,
|
||||
userRepository,
|
||||
getUserAnalyticsId,
|
||||
analyticsStore,
|
||||
statisticsStore,
|
||||
logger,
|
||||
)
|
||||
|
||||
beforeEach(() => {
|
||||
const user = { uuid: '1-2-3' } as jest.Mocked<User>
|
||||
|
||||
userRepository = {} as jest.Mocked<UserRepositoryInterface>
|
||||
userRepository.findOneByEmail = jest.fn().mockReturnValue(user)
|
||||
|
||||
getUserAnalyticsId = {} as jest.Mocked<GetUserAnalyticsId>
|
||||
getUserAnalyticsId.execute = jest.fn().mockReturnValue({ analyticsId: 3 })
|
||||
|
||||
analyticsStore = {} as jest.Mocked<AnalyticsStoreInterface>
|
||||
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.updateCancelled = jest.fn()
|
||||
userSubscriptionRepository.findBySubscriptionId = jest.fn().mockReturnValue([userSubscription])
|
||||
|
||||
offlineUserSubscriptionRepository = {} as jest.Mocked<OfflineUserSubscriptionRepositoryInterface>
|
||||
offlineUserSubscriptionRepository.updateCancelled = jest.fn()
|
||||
@@ -35,13 +73,33 @@ describe('SubscriptionCancelledEventHandler', () => {
|
||||
subscriptionName: SubscriptionName.ProPlan,
|
||||
timestamp,
|
||||
offline: false,
|
||||
replaced: false,
|
||||
}
|
||||
|
||||
logger = {} as jest.Mocked<Logger>
|
||||
logger.info = jest.fn()
|
||||
})
|
||||
|
||||
it('should update subscription cancelled', async () => {
|
||||
event.payload.timestamp = 1642395451516000
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(userSubscriptionRepository.updateCancelled).toHaveBeenCalledWith(1, true, 1642395451516000)
|
||||
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 () => {
|
||||
userRepository.findOneByEmail = jest.fn().mockReturnValue(null)
|
||||
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(userSubscriptionRepository.updateCancelled).toHaveBeenCalledWith(1, true, timestamp)
|
||||
expect(analyticsStore.markActivity).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should update offline subscription cancelled', async () => {
|
||||
|
||||
@@ -1,9 +1,20 @@
|
||||
import { DomainEventHandlerInterface, SubscriptionCancelledEvent } from '@standardnotes/domain-events'
|
||||
import { inject, injectable } from 'inversify'
|
||||
import {
|
||||
AnalyticsActivity,
|
||||
AnalyticsStoreInterface,
|
||||
Period,
|
||||
StatisticsMeasure,
|
||||
StatisticsStoreInterface,
|
||||
} from '@standardnotes/analytics'
|
||||
|
||||
import TYPES from '../../Bootstrap/Types'
|
||||
import { UserSubscriptionRepositoryInterface } from '../Subscription/UserSubscriptionRepositoryInterface'
|
||||
import { OfflineUserSubscriptionRepositoryInterface } from '../Subscription/OfflineUserSubscriptionRepositoryInterface'
|
||||
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
|
||||
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
|
||||
import { UserSubscription } from '../Subscription/UserSubscription'
|
||||
import { Logger } from 'winston'
|
||||
|
||||
@injectable()
|
||||
export class SubscriptionCancelledEventHandler implements DomainEventHandlerInterface {
|
||||
@@ -11,6 +22,11 @@ export class SubscriptionCancelledEventHandler implements DomainEventHandlerInte
|
||||
@inject(TYPES.UserSubscriptionRepository) private userSubscriptionRepository: UserSubscriptionRepositoryInterface,
|
||||
@inject(TYPES.OfflineUserSubscriptionRepository)
|
||||
private offlineUserSubscriptionRepository: OfflineUserSubscriptionRepositoryInterface,
|
||||
@inject(TYPES.UserRepository) private userRepository: UserRepositoryInterface,
|
||||
@inject(TYPES.GetUserAnalyticsId) private getUserAnalyticsId: GetUserAnalyticsId,
|
||||
@inject(TYPES.AnalyticsStore) private analyticsStore: AnalyticsStoreInterface,
|
||||
@inject(TYPES.StatisticsStore) private statisticsStore: StatisticsStoreInterface,
|
||||
@inject(TYPES.Logger) private logger: Logger,
|
||||
) {}
|
||||
async handle(event: SubscriptionCancelledEvent): Promise<void> {
|
||||
if (event.payload.offline) {
|
||||
@@ -20,6 +36,30 @@ export class SubscriptionCancelledEventHandler implements DomainEventHandlerInte
|
||||
}
|
||||
|
||||
await this.updateSubscriptionCancelled(event.payload.subscriptionId, event.payload.timestamp)
|
||||
|
||||
const user = await this.userRepository.findOneByEmail(event.payload.userEmail)
|
||||
if (user !== null) {
|
||||
const { analyticsId } = await this.getUserAnalyticsId.execute({ userUuid: user.uuid })
|
||||
await this.analyticsStore.markActivity([AnalyticsActivity.SubscriptionCancelled], analyticsId, [
|
||||
Period.Today,
|
||||
Period.ThisWeek,
|
||||
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
|
||||
this.logger.info(
|
||||
`Canceling subscription ${lastSubscription.uuid} - lasted for ${subscriptionLength} microseconds`,
|
||||
)
|
||||
await this.statisticsStore.incrementMeasure(StatisticsMeasure.SubscriptionLength, subscriptionLength, [
|
||||
Period.Today,
|
||||
Period.ThisWeek,
|
||||
Period.ThisMonth,
|
||||
])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async updateSubscriptionCancelled(subscriptionId: number, timestamp: number): Promise<void> {
|
||||
|
||||
@@ -16,9 +16,10 @@ import { OfflineUserSubscriptionRepositoryInterface } from '../Subscription/Offl
|
||||
import { OfflineUserSubscription } from '../Subscription/OfflineUserSubscription'
|
||||
import { SubscriptionSettingServiceInterface } from '../Setting/SubscriptionSettingServiceInterface'
|
||||
import { UserSubscriptionType } from '../Subscription/UserSubscriptionType'
|
||||
import { AnalyticsStoreInterface, Period } from '@standardnotes/analytics'
|
||||
import { AnalyticsStoreInterface, Period, StatisticsStoreInterface } from '@standardnotes/analytics'
|
||||
import { AnalyticsEntity } from '../Analytics/AnalyticsEntity'
|
||||
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
|
||||
describe('SubscriptionPurchasedEventHandler', () => {
|
||||
let userRepository: UserRepositoryInterface
|
||||
@@ -35,6 +36,8 @@ describe('SubscriptionPurchasedEventHandler', () => {
|
||||
let getUserAnalyticsId: GetUserAnalyticsId
|
||||
let analyticsStore: AnalyticsStoreInterface
|
||||
let timestamp: number
|
||||
let statisticsStore: StatisticsStoreInterface
|
||||
let timer: TimerInterface
|
||||
|
||||
const createHandler = () =>
|
||||
new SubscriptionPurchasedEventHandler(
|
||||
@@ -45,6 +48,8 @@ describe('SubscriptionPurchasedEventHandler', () => {
|
||||
subscriptionSettingService,
|
||||
getUserAnalyticsId,
|
||||
analyticsStore,
|
||||
statisticsStore,
|
||||
timer,
|
||||
logger,
|
||||
)
|
||||
|
||||
@@ -66,7 +71,14 @@ describe('SubscriptionPurchasedEventHandler', () => {
|
||||
userRepository.findOneByEmail = 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.countByUserUuid = jest.fn().mockReturnValue(0)
|
||||
userSubscriptionRepository.save = jest.fn().mockReturnValue(subscription)
|
||||
|
||||
offlineUserSubscription = {} as jest.Mocked<OfflineUserSubscription>
|
||||
@@ -146,6 +158,15 @@ describe('SubscriptionPurchasedEventHandler', () => {
|
||||
updatedAt: expect.any(Number),
|
||||
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 () => {
|
||||
|
||||
@@ -13,8 +13,15 @@ import { OfflineUserSubscription } from '../Subscription/OfflineUserSubscription
|
||||
import { OfflineUserSubscriptionRepositoryInterface } from '../Subscription/OfflineUserSubscriptionRepositoryInterface'
|
||||
import { UserSubscriptionType } from '../Subscription/UserSubscriptionType'
|
||||
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 { TimerInterface } from '@standardnotes/time'
|
||||
|
||||
@injectable()
|
||||
export class SubscriptionPurchasedEventHandler implements DomainEventHandlerInterface {
|
||||
@@ -27,6 +34,8 @@ export class SubscriptionPurchasedEventHandler implements DomainEventHandlerInte
|
||||
@inject(TYPES.SubscriptionSettingService) private subscriptionSettingService: SubscriptionSettingServiceInterface,
|
||||
@inject(TYPES.GetUserAnalyticsId) private getUserAnalyticsId: GetUserAnalyticsId,
|
||||
@inject(TYPES.AnalyticsStore) private analyticsStore: AnalyticsStoreInterface,
|
||||
@inject(TYPES.StatisticsStore) private statisticsStore: StatisticsStoreInterface,
|
||||
@inject(TYPES.Timer) private timer: TimerInterface,
|
||||
@inject(TYPES.Logger) private logger: Logger,
|
||||
) {}
|
||||
|
||||
@@ -52,6 +61,8 @@ export class SubscriptionPurchasedEventHandler implements DomainEventHandlerInte
|
||||
return
|
||||
}
|
||||
|
||||
const previousSubscriptionCount = await this.userSubscriptionRepository.countByUserUuid(user.uuid)
|
||||
|
||||
const userSubscription = await this.createSubscription(
|
||||
event.payload.subscriptionId,
|
||||
event.payload.subscriptionName,
|
||||
@@ -68,14 +79,26 @@ export class SubscriptionPurchasedEventHandler implements DomainEventHandlerInte
|
||||
)
|
||||
|
||||
const { analyticsId } = await this.getUserAnalyticsId.execute({ userUuid: user.uuid })
|
||||
await this.analyticsStore.markActivity([AnalyticsActivity.SubscriptionPurchased], analyticsId, [Period.Today])
|
||||
await this.analyticsStore.markActivity([AnalyticsActivity.SubscriptionPurchased], analyticsId, [
|
||||
Period.Today,
|
||||
Period.ThisWeek,
|
||||
Period.ThisMonth,
|
||||
])
|
||||
|
||||
const limitedDiscountPurchased = event.payload.discountCode === 'limited-10'
|
||||
const limitedDiscountPurchased = ['limited-10', 'limited-20'].includes(event.payload.discountCode as string)
|
||||
if (limitedDiscountPurchased) {
|
||||
await this.analyticsStore.markActivity([AnalyticsActivity.LimitedDiscountOfferPurchased], analyticsId, [
|
||||
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> {
|
||||
|
||||
@@ -13,6 +13,8 @@ import { UserSubscriptionRepositoryInterface } from '../Subscription/UserSubscri
|
||||
import { RoleServiceInterface } from '../Role/RoleServiceInterface'
|
||||
import { OfflineUserSubscriptionRepositoryInterface } from '../Subscription/OfflineUserSubscriptionRepositoryInterface'
|
||||
import { UserSubscription } from '../Subscription/UserSubscription'
|
||||
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
|
||||
import { AnalyticsStoreInterface } from '@standardnotes/analytics'
|
||||
|
||||
describe('SubscriptionRefundedEventHandler', () => {
|
||||
let userRepository: UserRepositoryInterface
|
||||
@@ -23,6 +25,8 @@ describe('SubscriptionRefundedEventHandler', () => {
|
||||
let user: User
|
||||
let event: SubscriptionRefundedEvent
|
||||
let timestamp: number
|
||||
let getUserAnalyticsId: GetUserAnalyticsId
|
||||
let analyticsStore: AnalyticsStoreInterface
|
||||
|
||||
const createHandler = () =>
|
||||
new SubscriptionRefundedEventHandler(
|
||||
@@ -30,6 +34,8 @@ describe('SubscriptionRefundedEventHandler', () => {
|
||||
userSubscriptionRepository,
|
||||
offlineUserSubscriptionRepository,
|
||||
roleService,
|
||||
getUserAnalyticsId,
|
||||
analyticsStore,
|
||||
logger,
|
||||
)
|
||||
|
||||
@@ -72,6 +78,12 @@ describe('SubscriptionRefundedEventHandler', () => {
|
||||
offline: false,
|
||||
}
|
||||
|
||||
getUserAnalyticsId = {} as jest.Mocked<GetUserAnalyticsId>
|
||||
getUserAnalyticsId.execute = jest.fn().mockReturnValue({ analyticsId: 3 })
|
||||
|
||||
analyticsStore = {} as jest.Mocked<AnalyticsStoreInterface>
|
||||
analyticsStore.markActivity = jest.fn()
|
||||
|
||||
logger = {} as jest.Mocked<Logger>
|
||||
logger.info = jest.fn()
|
||||
logger.warn = jest.fn()
|
||||
|
||||
@@ -8,6 +8,8 @@ import { RoleServiceInterface } from '../Role/RoleServiceInterface'
|
||||
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
|
||||
import { UserSubscriptionRepositoryInterface } from '../Subscription/UserSubscriptionRepositoryInterface'
|
||||
import { OfflineUserSubscriptionRepositoryInterface } from '../Subscription/OfflineUserSubscriptionRepositoryInterface'
|
||||
import { AnalyticsActivity, AnalyticsStoreInterface, Period } from '@standardnotes/analytics'
|
||||
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
|
||||
|
||||
@injectable()
|
||||
export class SubscriptionRefundedEventHandler implements DomainEventHandlerInterface {
|
||||
@@ -17,6 +19,8 @@ export class SubscriptionRefundedEventHandler implements DomainEventHandlerInter
|
||||
@inject(TYPES.OfflineUserSubscriptionRepository)
|
||||
private offlineUserSubscriptionRepository: OfflineUserSubscriptionRepositoryInterface,
|
||||
@inject(TYPES.RoleService) private roleService: RoleServiceInterface,
|
||||
@inject(TYPES.GetUserAnalyticsId) private getUserAnalyticsId: GetUserAnalyticsId,
|
||||
@inject(TYPES.AnalyticsStore) private analyticsStore: AnalyticsStoreInterface,
|
||||
@inject(TYPES.Logger) private logger: Logger,
|
||||
) {}
|
||||
|
||||
@@ -36,6 +40,13 @@ export class SubscriptionRefundedEventHandler implements DomainEventHandlerInter
|
||||
|
||||
await this.updateSubscriptionEndsAt(event.payload.subscriptionId, event.payload.timestamp)
|
||||
await this.removeRoleFromSubscriptionUsers(event.payload.subscriptionId, event.payload.subscriptionName)
|
||||
|
||||
const { analyticsId } = await this.getUserAnalyticsId.execute({ userUuid: user.uuid })
|
||||
await this.analyticsStore.markActivity([AnalyticsActivity.SubscriptionRefunded], analyticsId, [
|
||||
Period.Today,
|
||||
Period.ThisWeek,
|
||||
Period.ThisMonth,
|
||||
])
|
||||
}
|
||||
|
||||
private async removeRoleFromSubscriptionUsers(
|
||||
|
||||
@@ -61,7 +61,11 @@ export class SubscriptionRenewedEventHandler implements DomainEventHandlerInterf
|
||||
await this.addRoleToSubscriptionUsers(event.payload.subscriptionId, event.payload.subscriptionName)
|
||||
|
||||
const { analyticsId } = await this.getUserAnalyticsId.execute({ userUuid: user.uuid })
|
||||
await this.analyticsStore.markActivity([AnalyticsActivity.SubscriptionRenewed], analyticsId, [Period.Today])
|
||||
await this.analyticsStore.markActivity([AnalyticsActivity.SubscriptionRenewed], analyticsId, [
|
||||
Period.Today,
|
||||
Period.ThisWeek,
|
||||
Period.ThisMonth,
|
||||
])
|
||||
}
|
||||
|
||||
private async addRoleToSubscriptionUsers(subscriptionId: number, subscriptionName: SubscriptionName): Promise<void> {
|
||||
|
||||
@@ -4,17 +4,23 @@ import { UserDisabledSessionUserAgentLoggingEvent } from '@standardnotes/domain-
|
||||
import { SessionRepositoryInterface } from '../Session/SessionRepositoryInterface'
|
||||
|
||||
import { UserDisabledSessionUserAgentLoggingEventHandler } from './UserDisabledSessionUserAgentLoggingEventHandler'
|
||||
import { RevokedSessionRepositoryInterface } from '../Session/RevokedSessionRepositoryInterface'
|
||||
|
||||
describe('UserDisabledSessionUserAgentLoggingEventHandler', () => {
|
||||
let sessionRepository: SessionRepositoryInterface
|
||||
let revokedSessionRepository: RevokedSessionRepositoryInterface
|
||||
let event: UserDisabledSessionUserAgentLoggingEvent
|
||||
|
||||
const createHandler = () => new UserDisabledSessionUserAgentLoggingEventHandler(sessionRepository)
|
||||
const createHandler = () =>
|
||||
new UserDisabledSessionUserAgentLoggingEventHandler(sessionRepository, revokedSessionRepository)
|
||||
|
||||
beforeEach(() => {
|
||||
sessionRepository = {} as jest.Mocked<SessionRepositoryInterface>
|
||||
sessionRepository.clearUserAgentByUserUuid = jest.fn()
|
||||
|
||||
revokedSessionRepository = {} as jest.Mocked<RevokedSessionRepositoryInterface>
|
||||
revokedSessionRepository.clearUserAgentByUserUuid = jest.fn()
|
||||
|
||||
event = {} as jest.Mocked<UserDisabledSessionUserAgentLoggingEvent>
|
||||
event.payload = {
|
||||
userUuid: '1-2-3',
|
||||
@@ -26,5 +32,6 @@ describe('UserDisabledSessionUserAgentLoggingEventHandler', () => {
|
||||
await createHandler().handle(event)
|
||||
|
||||
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 TYPES from '../../Bootstrap/Types'
|
||||
import { RevokedSessionRepositoryInterface } from '../Session/RevokedSessionRepositoryInterface'
|
||||
import { SessionRepositoryInterface } from '../Session/SessionRepositoryInterface'
|
||||
|
||||
@injectable()
|
||||
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> {
|
||||
await this.sessionRepository.clearUserAgentByUserUuid(event.payload.userUuid)
|
||||
await this.revokedSessionRepository.clearUserAgentByUserUuid(event.payload.userUuid)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,11 @@ export class UserRegisteredEventHandler implements DomainEventHandlerInterface {
|
||||
}
|
||||
|
||||
const { analyticsId } = await this.getUserAnalyticsId.execute({ userUuid: event.payload.userUuid })
|
||||
await this.analyticsStore.markActivity([AnalyticsActivity.Register], analyticsId, [Period.Today])
|
||||
await this.analyticsStore.markActivity([AnalyticsActivity.Register], analyticsId, [
|
||||
Period.Today,
|
||||
Period.ThisWeek,
|
||||
Period.ThisMonth,
|
||||
])
|
||||
|
||||
await this.httpClient.request({
|
||||
method: 'POST',
|
||||
|
||||
@@ -36,4 +36,25 @@ export class RevokedSession {
|
||||
)
|
||||
@JoinColumn({ name: 'user_uuid', referencedColumnName: 'uuid' })
|
||||
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'
|
||||
|
||||
export interface RevokedSessionRepositoryInterface {
|
||||
@@ -5,4 +6,5 @@ export interface RevokedSessionRepositoryInterface {
|
||||
findAllByUserUuid(userUuid: string): Promise<Array<RevokedSession>>
|
||||
save(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 { TimerInterface } from '@standardnotes/time'
|
||||
|
||||
import { ApiVersion } from '../Api/ApiVersion'
|
||||
import { Session } from './Session'
|
||||
import { SessionRepositoryInterface } from './SessionRepositoryInterface'
|
||||
import { SessionService } from './SessionService'
|
||||
@@ -47,6 +48,7 @@ describe('SessionService', () => {
|
||||
session.uuid = '2e1e43'
|
||||
session.userUuid = '1-2-3'
|
||||
session.userAgent = 'Chrome'
|
||||
session.apiVersion = ApiVersion.v20200115
|
||||
session.hashedAccessToken = '4e07408562bedb8b60ce05c1decfe3ad16b72230967de01f640b7e4729b49fce'
|
||||
session.hashedRefreshToken = '4e07408562bedb8b60ce05c1decfe3ad16b72230967de01f640b7e4729b49fce'
|
||||
|
||||
@@ -80,6 +82,7 @@ describe('SessionService', () => {
|
||||
|
||||
timer = {} as jest.Mocked<TimerInterface>
|
||||
timer.convertStringDateToMilliseconds = jest.fn().mockReturnValue(123)
|
||||
timer.getUTCDate = jest.fn().mockReturnValue(new Date(1))
|
||||
|
||||
deviceDetector = {} as jest.Mocked<UAParser>
|
||||
deviceDetector.setUA = jest.fn().mockReturnThis()
|
||||
@@ -111,6 +114,7 @@ describe('SessionService', () => {
|
||||
expect(revokedSessionRepository.save).toHaveBeenCalledWith({
|
||||
uuid: '2e1e43',
|
||||
received: true,
|
||||
receivedAt: new Date(1),
|
||||
})
|
||||
})
|
||||
|
||||
@@ -476,6 +480,8 @@ describe('SessionService', () => {
|
||||
expect(revokedSessionRepository.save).toHaveBeenCalledWith({
|
||||
uuid: '2e1e43',
|
||||
userUuid: '1-2-3',
|
||||
userAgent: 'Chrome',
|
||||
apiVersion: '20200115',
|
||||
createdAt: expect.any(Date),
|
||||
})
|
||||
})
|
||||
|
||||
@@ -203,6 +203,7 @@ export class SessionService implements SessionServiceInterface {
|
||||
|
||||
async markRevokedSessionAsReceived(revokedSession: RevokedSession): Promise<RevokedSession> {
|
||||
revokedSession.received = true
|
||||
revokedSession.receivedAt = this.timer.getUTCDate()
|
||||
|
||||
return this.revokedSessionRepository.save(revokedSession)
|
||||
}
|
||||
@@ -224,7 +225,9 @@ export class SessionService implements SessionServiceInterface {
|
||||
const revokedSession = new RevokedSession()
|
||||
revokedSession.uuid = session.uuid
|
||||
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)
|
||||
}
|
||||
@@ -246,8 +249,8 @@ export class SessionService implements SessionServiceInterface {
|
||||
}
|
||||
session.userUuid = dto.user.uuid
|
||||
session.apiVersion = dto.apiVersion
|
||||
session.createdAt = dayjs.utc().toDate()
|
||||
session.updatedAt = dayjs.utc().toDate()
|
||||
session.createdAt = this.timer.getUTCDate()
|
||||
session.updatedAt = this.timer.getUTCDate()
|
||||
session.readonlyAccess = dto.readonlyAccess
|
||||
|
||||
return session
|
||||
|
||||
@@ -4,6 +4,7 @@ import { UserSubscriptionType } from './UserSubscriptionType'
|
||||
|
||||
export interface UserSubscriptionRepositoryInterface {
|
||||
findOneByUuid(uuid: Uuid): Promise<UserSubscription | null>
|
||||
countByUserUuid(userUuid: Uuid): Promise<number>
|
||||
findOneByUserUuid(userUuid: Uuid): Promise<UserSubscription | null>
|
||||
findOneByUserUuidAndSubscriptionId(userUuid: Uuid, subscriptionId: number): Promise<UserSubscription | null>
|
||||
findBySubscriptionIdAndType(subscriptionId: number, type: UserSubscriptionType): Promise<UserSubscription[]>
|
||||
|
||||
@@ -9,6 +9,7 @@ import { InviteToSharedSubscription } from './InviteToSharedSubscription'
|
||||
import { UserSubscriptionRepositoryInterface } from '../../Subscription/UserSubscriptionRepositoryInterface'
|
||||
import { UserSubscription } from '../../Subscription/UserSubscription'
|
||||
import { RoleName } from '@standardnotes/common'
|
||||
import { UserSubscriptionType } from '../../Subscription/UserSubscriptionType'
|
||||
|
||||
describe('InviteToSharedSubscription', () => {
|
||||
let userSubscriptionRepository: UserSubscriptionRepositoryInterface
|
||||
@@ -28,9 +29,10 @@ describe('InviteToSharedSubscription', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
userSubscriptionRepository = {} as jest.Mocked<UserSubscriptionRepositoryInterface>
|
||||
userSubscriptionRepository.findOneByUserUuid = jest
|
||||
.fn()
|
||||
.mockReturnValue({ subscriptionId: 2 } as jest.Mocked<UserSubscription>)
|
||||
userSubscriptionRepository.findOneByUserUuid = jest.fn().mockReturnValue({
|
||||
subscriptionId: 2,
|
||||
subscriptionType: UserSubscriptionType.Regular,
|
||||
} as jest.Mocked<UserSubscription>)
|
||||
|
||||
timer = {} as jest.Mocked<TimerInterface>
|
||||
timer.getTimestampInMicroseconds = jest.fn().mockReturnValue(1)
|
||||
@@ -159,4 +161,24 @@ describe('InviteToSharedSubscription', () => {
|
||||
})
|
||||
expect(domainEventPublisher.publish).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should not create an inivitation for sharing the subscription if the inviter is on a shared subscription', async () => {
|
||||
userSubscriptionRepository.findOneByUserUuid = jest.fn().mockReturnValue({
|
||||
subscriptionId: 2,
|
||||
subscriptionType: UserSubscriptionType.Shared,
|
||||
} as jest.Mocked<UserSubscription>)
|
||||
|
||||
await createUseCase().execute({
|
||||
inviteeIdentifier: 'invitee@test.te',
|
||||
inviterUuid: '1-2-3',
|
||||
inviterEmail: 'inviter@test.te',
|
||||
inviterRoles: [RoleName.ProUser],
|
||||
})
|
||||
|
||||
expect(sharedSubscriptionInvitationRepository.save).not.toHaveBeenCalled()
|
||||
|
||||
expect(domainEventFactory.createSharedSubscriptionInvitationCreatedEvent).not.toHaveBeenCalled()
|
||||
|
||||
expect(domainEventPublisher.publish).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -11,6 +11,7 @@ import { InviterIdentifierType } from '../../SharedSubscription/InviterIdentifie
|
||||
import { SharedSubscriptionInvitation } from '../../SharedSubscription/SharedSubscriptionInvitation'
|
||||
import { SharedSubscriptionInvitationRepositoryInterface } from '../../SharedSubscription/SharedSubscriptionInvitationRepositoryInterface'
|
||||
import { UserSubscriptionRepositoryInterface } from '../../Subscription/UserSubscriptionRepositoryInterface'
|
||||
import { UserSubscriptionType } from '../../Subscription/UserSubscriptionType'
|
||||
import { UseCaseInterface } from '../UseCaseInterface'
|
||||
|
||||
import { InviteToSharedSubscriptionDTO } from './InviteToSharedSubscriptionDTO'
|
||||
@@ -35,18 +36,18 @@ export class InviteToSharedSubscription implements UseCaseInterface {
|
||||
}
|
||||
}
|
||||
|
||||
const numberOfUsedInvites = await this.sharedSubscriptionInvitationRepository.countByInviterEmailAndStatus(
|
||||
dto.inviterEmail,
|
||||
[InvitationStatus.Sent, InvitationStatus.Accepted],
|
||||
)
|
||||
if (numberOfUsedInvites >= this.MAX_NUMBER_OF_INVITES) {
|
||||
const inviterUserSubscription = await this.userSubscriptionRepository.findOneByUserUuid(dto.inviterUuid)
|
||||
if (inviterUserSubscription === null || inviterUserSubscription.subscriptionType === UserSubscriptionType.Shared) {
|
||||
return {
|
||||
success: false,
|
||||
}
|
||||
}
|
||||
|
||||
const inviterUserSubscription = await this.userSubscriptionRepository.findOneByUserUuid(dto.inviterUuid)
|
||||
if (inviterUserSubscription === null) {
|
||||
const numberOfUsedInvites = await this.sharedSubscriptionInvitationRepository.countByInviterEmailAndStatus(
|
||||
dto.inviterEmail,
|
||||
[InvitationStatus.Sent, InvitationStatus.Accepted],
|
||||
)
|
||||
if (numberOfUsedInvites >= this.MAX_NUMBER_OF_INVITES) {
|
||||
return {
|
||||
success: false,
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'reflect-metadata'
|
||||
|
||||
import { Repository, SelectQueryBuilder } from 'typeorm'
|
||||
import { Repository, SelectQueryBuilder, UpdateQueryBuilder } from 'typeorm'
|
||||
|
||||
import { RevokedSession } from '../../Domain/Session/RevokedSession'
|
||||
|
||||
@@ -9,12 +9,14 @@ import { MySQLRevokedSessionRepository } from './MySQLRevokedSessionRepository'
|
||||
describe('MySQLRevokedSessionRepository', () => {
|
||||
let ormRepository: Repository<RevokedSession>
|
||||
let queryBuilder: SelectQueryBuilder<RevokedSession>
|
||||
let updateQueryBuilder: UpdateQueryBuilder<RevokedSession>
|
||||
let session: RevokedSession
|
||||
|
||||
const createRepository = () => new MySQLRevokedSessionRepository(ormRepository)
|
||||
|
||||
beforeEach(() => {
|
||||
queryBuilder = {} as jest.Mocked<SelectQueryBuilder<RevokedSession>>
|
||||
updateQueryBuilder = {} as jest.Mocked<UpdateQueryBuilder<RevokedSession>>
|
||||
|
||||
session = {} as jest.Mocked<RevokedSession>
|
||||
|
||||
@@ -36,6 +38,24 @@ describe('MySQLRevokedSessionRepository', () => {
|
||||
expect(ormRepository.remove).toHaveBeenCalledWith(session)
|
||||
})
|
||||
|
||||
it('should clear user agent data on all user sessions', async () => {
|
||||
ormRepository.createQueryBuilder = jest.fn().mockImplementation(() => updateQueryBuilder)
|
||||
|
||||
updateQueryBuilder.update = jest.fn().mockReturnThis()
|
||||
updateQueryBuilder.set = jest.fn().mockReturnThis()
|
||||
updateQueryBuilder.where = jest.fn().mockReturnThis()
|
||||
updateQueryBuilder.execute = jest.fn()
|
||||
|
||||
await createRepository().clearUserAgentByUserUuid('1-2-3')
|
||||
|
||||
expect(updateQueryBuilder.update).toHaveBeenCalled()
|
||||
expect(updateQueryBuilder.set).toHaveBeenCalledWith({
|
||||
userAgent: null,
|
||||
})
|
||||
expect(updateQueryBuilder.where).toHaveBeenCalledWith('user_uuid = :userUuid', { userUuid: '1-2-3' })
|
||||
expect(updateQueryBuilder.execute).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should find one session by id', async () => {
|
||||
queryBuilder.where = jest.fn().mockReturnThis()
|
||||
queryBuilder.getOne = jest.fn().mockReturnValue(session)
|
||||
|
||||
@@ -20,6 +20,17 @@ export class MySQLRevokedSessionRepository implements RevokedSessionRepositoryIn
|
||||
return this.ormRepository.remove(revokedSession)
|
||||
}
|
||||
|
||||
async clearUserAgentByUserUuid(userUuid: string): Promise<void> {
|
||||
await this.ormRepository
|
||||
.createQueryBuilder('revoked_session')
|
||||
.update()
|
||||
.set({
|
||||
userAgent: null,
|
||||
})
|
||||
.where('user_uuid = :userUuid', { userUuid })
|
||||
.execute()
|
||||
}
|
||||
|
||||
async findAllByUserUuid(userUuid: string): Promise<RevokedSession[]> {
|
||||
return this.ormRepository
|
||||
.createQueryBuilder('revoked_session')
|
||||
|
||||
@@ -75,6 +75,21 @@ describe('MySQLUserSubscriptionRepository', () => {
|
||||
expect(result).toEqual(subscription)
|
||||
})
|
||||
|
||||
it('should count by user uuid', async () => {
|
||||
ormRepository.createQueryBuilder = jest.fn().mockImplementation(() => selectQueryBuilder)
|
||||
|
||||
selectQueryBuilder.where = jest.fn().mockReturnThis()
|
||||
selectQueryBuilder.getCount = jest.fn().mockReturnValue(2)
|
||||
|
||||
const result = await createRepository().countByUserUuid('123')
|
||||
|
||||
expect(selectQueryBuilder.where).toHaveBeenCalledWith('user_uuid = :user_uuid', {
|
||||
user_uuid: '123',
|
||||
})
|
||||
expect(selectQueryBuilder.getCount).toHaveBeenCalled()
|
||||
expect(result).toEqual(2)
|
||||
})
|
||||
|
||||
it('should find one, longest lasting subscription by user uuid if there are no ucanceled ones', async () => {
|
||||
subscription.cancelled = true
|
||||
|
||||
@@ -157,6 +172,7 @@ describe('MySQLUserSubscriptionRepository', () => {
|
||||
ormRepository.createQueryBuilder = jest.fn().mockImplementation(() => selectQueryBuilder)
|
||||
|
||||
selectQueryBuilder.where = jest.fn().mockReturnThis()
|
||||
selectQueryBuilder.orderBy = jest.fn().mockReturnThis()
|
||||
selectQueryBuilder.getMany = jest.fn().mockReturnValue([subscription])
|
||||
|
||||
const result = await createRepository().findBySubscriptionId(123)
|
||||
@@ -164,6 +180,7 @@ describe('MySQLUserSubscriptionRepository', () => {
|
||||
expect(selectQueryBuilder.where).toHaveBeenCalledWith('subscription_id = :subscriptionId', {
|
||||
subscriptionId: 123,
|
||||
})
|
||||
expect(selectQueryBuilder.orderBy).toHaveBeenCalledWith('created_at', 'DESC')
|
||||
expect(selectQueryBuilder.getMany).toHaveBeenCalled()
|
||||
expect(result).toEqual([subscription])
|
||||
})
|
||||
|
||||
@@ -14,6 +14,15 @@ export class MySQLUserSubscriptionRepository implements UserSubscriptionReposito
|
||||
private ormRepository: Repository<UserSubscription>,
|
||||
) {}
|
||||
|
||||
async countByUserUuid(userUuid: Uuid): Promise<number> {
|
||||
return await this.ormRepository
|
||||
.createQueryBuilder()
|
||||
.where('user_uuid = :user_uuid', {
|
||||
user_uuid: userUuid,
|
||||
})
|
||||
.getCount()
|
||||
}
|
||||
|
||||
async save(subscription: UserSubscription): Promise<UserSubscription> {
|
||||
return this.ormRepository.save(subscription)
|
||||
}
|
||||
@@ -44,6 +53,7 @@ export class MySQLUserSubscriptionRepository implements UserSubscriptionReposito
|
||||
.where('subscription_id = :subscriptionId', {
|
||||
subscriptionId,
|
||||
})
|
||||
.orderBy('created_at', 'DESC')
|
||||
.getMany()
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,12 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [1.31.0](https://github.com/standardnotes/server/compare/@standardnotes/common@1.30.0...@standardnotes/common@1.31.0) (2022-09-05)
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** add keeping stats on payments ([0c176b7](https://github.com/standardnotes/server/commit/0c176b70f8281e1e490224b9c7ab85f272a3d4e9))
|
||||
|
||||
# [1.30.0](https://github.com/standardnotes/server/compare/@standardnotes/common@1.29.0...@standardnotes/common@1.30.0) (2022-07-14)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/common",
|
||||
"version": "1.30.0",
|
||||
"version": "1.31.0",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
@@ -23,7 +23,7 @@
|
||||
"prebuild": "yarn clean",
|
||||
"build": "tsc -p tsconfig.json",
|
||||
"lint": "eslint . --ext .ts",
|
||||
"test:unit": "jest spec --coverage"
|
||||
"test": "jest spec --coverage"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^28.1.4",
|
||||
|
||||
@@ -3,6 +3,48 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.8.3](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.8.2...@standardnotes/domain-events-infra@1.8.3) (2022-09-06)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/domain-events-infra
|
||||
|
||||
## [1.8.2](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.8.1...@standardnotes/domain-events-infra@1.8.2) (2022-09-06)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/domain-events-infra
|
||||
|
||||
## [1.8.1](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.8.0...@standardnotes/domain-events-infra@1.8.1) (2022-09-06)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/domain-events-infra
|
||||
|
||||
# [1.8.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.7.37...@standardnotes/domain-events-infra@1.8.0) (2022-09-05)
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** add keeping stats on payments ([0c176b7](https://github.com/standardnotes/server/commit/0c176b70f8281e1e490224b9c7ab85f272a3d4e9))
|
||||
|
||||
## [1.7.37](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.7.36...@standardnotes/domain-events-infra@1.7.37) (2022-09-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/domain-events-infra
|
||||
|
||||
## [1.7.36](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.7.35...@standardnotes/domain-events-infra@1.7.36) (2022-09-01)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/domain-events-infra
|
||||
|
||||
## [1.7.35](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.7.34...@standardnotes/domain-events-infra@1.7.35) (2022-09-01)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/domain-events-infra
|
||||
|
||||
## [1.7.34](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.7.33...@standardnotes/domain-events-infra@1.7.34) (2022-08-30)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/domain-events-infra
|
||||
|
||||
## [1.7.33](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.7.32...@standardnotes/domain-events-infra@1.7.33) (2022-08-15)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/domain-events-infra
|
||||
|
||||
## [1.7.32](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.7.31...@standardnotes/domain-events-infra@1.7.32) (2022-08-15)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/domain-events-infra
|
||||
|
||||
## [1.7.31](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.7.30...@standardnotes/domain-events-infra@1.7.31) (2022-08-10)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/domain-events-infra
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/domain-events-infra",
|
||||
"version": "1.7.31",
|
||||
"version": "1.8.3",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
@@ -21,7 +21,7 @@
|
||||
"prebuild": "yarn clean",
|
||||
"build": "tsc -p tsconfig.json",
|
||||
"lint": "eslint . --ext .ts",
|
||||
"test:unit": "jest spec --coverage"
|
||||
"test": "jest spec --coverage"
|
||||
},
|
||||
"dependencies": {
|
||||
"@standardnotes/domain-events": "workspace:*",
|
||||
|
||||
@@ -3,6 +3,67 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [2.59.1](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.59.0...@standardnotes/domain-events@2.59.1) (2022-09-06)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **api-gateway:** include period key in statistics measures ([d149f46](https://github.com/standardnotes/server/commit/d149f46cf6456201dd8690977f64ed32a75f3459))
|
||||
* **api-gateway:** period types on analytics report ([f94c8fc](https://github.com/standardnotes/server/commit/f94c8fc26e684a07101cc5282ebb9cda3c8c6961))
|
||||
|
||||
# [2.59.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.58.0...@standardnotes/domain-events@2.59.0) (2022-09-06)
|
||||
|
||||
### Features
|
||||
|
||||
* **api-gateway:** add statistics measures to report generation ([8151bb1](https://github.com/standardnotes/server/commit/8151bb108affb2b5cfa1ab365f99a9f0170a7795))
|
||||
|
||||
# [2.58.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.57.0...@standardnotes/domain-events@2.58.0) (2022-09-06)
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** add statistics for refunds and account deletions ([d7ae2f0](https://github.com/standardnotes/server/commit/d7ae2f06255b19eb5d3403a4989610390064754e))
|
||||
|
||||
# [2.57.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.56.0...@standardnotes/domain-events@2.57.0) (2022-09-05)
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** add keeping stats on payments ([0c176b7](https://github.com/standardnotes/server/commit/0c176b70f8281e1e490224b9c7ab85f272a3d4e9))
|
||||
|
||||
# [2.56.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.55.1...@standardnotes/domain-events@2.56.0) (2022-09-05)
|
||||
|
||||
### Features
|
||||
|
||||
* **domain-events:** add amount of dollars to payment success event ([9c2d51d](https://github.com/standardnotes/server/commit/9c2d51d718516b550c23637b00a3edead0749425))
|
||||
|
||||
## [2.55.1](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.55.0...@standardnotes/domain-events@2.55.1) (2022-09-01)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **domain-events:** add admin-panel as event source option ([41c5127](https://github.com/standardnotes/server/commit/41c512798d932859b5d46c6e62fccb89fa288891))
|
||||
|
||||
# [2.55.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.54.2...@standardnotes/domain-events@2.55.0) (2022-09-01)
|
||||
|
||||
### Features
|
||||
|
||||
* **domain-events:** add subscription revert requested event ([e0cec9e](https://github.com/standardnotes/server/commit/e0cec9e24ab9954868fb428062c9a82d0f0f85d5))
|
||||
|
||||
## [2.54.2](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.54.1...@standardnotes/domain-events@2.54.2) (2022-08-30)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **domain-events:** add replaced boolean to subscription canceled event ([932ef93](https://github.com/standardnotes/server/commit/932ef933fce71aabdddca33da4eec6ce8fe686ef))
|
||||
|
||||
## [2.54.1](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.54.0...@standardnotes/domain-events@2.54.1) (2022-08-15)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **domain-events:** missing exports ([2fb904d](https://github.com/standardnotes/server/commit/2fb904d2cbff040ba9a6b2b323eab1591309b174))
|
||||
|
||||
# [2.54.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.53.0...@standardnotes/domain-events@2.54.0) (2022-08-15)
|
||||
|
||||
### Features
|
||||
|
||||
* **domain-events:** add payment success event ([1841597](https://github.com/standardnotes/server/commit/1841597405461981c7b6eb4ee8ada079c77a8fc5))
|
||||
|
||||
# [2.53.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.52.0...@standardnotes/domain-events@2.53.0) (2022-08-09)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/domain-events",
|
||||
"version": "2.53.0",
|
||||
"version": "2.59.1",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
@@ -21,7 +21,7 @@
|
||||
"prebuild": "yarn clean",
|
||||
"build": "tsc -p tsconfig.json",
|
||||
"lint": "eslint . --ext .ts",
|
||||
"test:unit": "jest spec --coverage --passWithNoTests"
|
||||
"test": "jest spec --coverage --passWithNoTests"
|
||||
},
|
||||
"dependencies": {
|
||||
"@standardnotes/common": "workspace:*",
|
||||
|
||||
@@ -12,6 +12,12 @@ export interface DailyAnalyticsReportGeneratedEventPayload {
|
||||
retention: number
|
||||
totalCount: number
|
||||
}>
|
||||
statisticMeasures: Array<{
|
||||
name: string
|
||||
totalValue: number
|
||||
average: number
|
||||
period: number
|
||||
}>
|
||||
activityStatisticsOverTime: Array<{
|
||||
name: string
|
||||
period: number
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export enum DomainEventService {
|
||||
AdminPanel = 'admin-panel',
|
||||
Auth = 'auth',
|
||||
SyncingServer = 'syncing-server',
|
||||
Payments = 'payments',
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import { DomainEventInterface } from './DomainEventInterface'
|
||||
|
||||
import { PaymentSuccessEventPayload } from './PaymentSuccessEventPayload'
|
||||
|
||||
export interface PaymentSuccessEvent extends DomainEventInterface {
|
||||
type: 'PAYMENT_SUCCESS'
|
||||
payload: PaymentSuccessEventPayload
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export interface PaymentSuccessEventPayload {
|
||||
userEmail: string
|
||||
amount: number
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
export interface RefundProcessedEventPayload {
|
||||
userEmail: string
|
||||
amount: number
|
||||
}
|
||||
|
||||
@@ -6,4 +6,5 @@ export interface SubscriptionCancelledEventPayload {
|
||||
subscriptionName: SubscriptionName
|
||||
timestamp: number
|
||||
offline: boolean
|
||||
replaced: boolean
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import { DomainEventInterface } from './DomainEventInterface'
|
||||
|
||||
import { SubscriptionRevertRequestedEventPayload } from './SubscriptionRevertRequestedEventPayload'
|
||||
|
||||
export interface SubscriptionRevertRequestedEvent extends DomainEventInterface {
|
||||
type: 'SUBSCRIPTION_REVERT_REQUESTED'
|
||||
payload: SubscriptionRevertRequestedEventPayload
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export interface SubscriptionRevertRequestedEventPayload {
|
||||
userEmail: string
|
||||
}
|
||||
@@ -54,6 +54,8 @@ export * from './Event/OneDriveBackupFailedEvent'
|
||||
export * from './Event/OneDriveBackupFailedEventPayload'
|
||||
export * from './Event/PaymentFailedEvent'
|
||||
export * from './Event/PaymentFailedEventPayload'
|
||||
export * from './Event/PaymentSuccessEvent'
|
||||
export * from './Event/PaymentSuccessEventPayload'
|
||||
export * from './Event/PredicateVerificationRequestedEvent'
|
||||
export * from './Event/PredicateVerificationRequestedEventPayload'
|
||||
export * from './Event/PredicateVerifiedEvent'
|
||||
@@ -84,6 +86,8 @@ export * from './Event/SubscriptionRenewedEvent'
|
||||
export * from './Event/SubscriptionRenewedEventPayload'
|
||||
export * from './Event/SubscriptionExpiredEvent'
|
||||
export * from './Event/SubscriptionExpiredEventPayload'
|
||||
export * from './Event/SubscriptionRevertRequestedEvent'
|
||||
export * from './Event/SubscriptionRevertRequestedEventPayload'
|
||||
export * from './Event/SubscriptionSyncRequestedEvent'
|
||||
export * from './Event/SubscriptionSyncRequestedEventPayload'
|
||||
export * from './Event/UserDisabledSessionUserAgentLoggingEvent'
|
||||
|
||||
@@ -3,6 +3,70 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.3.6](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.3.5...@standardnotes/event-store@1.3.6) (2022-09-06)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/event-store
|
||||
|
||||
## [1.3.5](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.3.4...@standardnotes/event-store@1.3.5) (2022-09-06)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/event-store
|
||||
|
||||
## [1.3.4](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.3.3...@standardnotes/event-store@1.3.4) (2022-09-06)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/event-store
|
||||
|
||||
## [1.3.3](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.3.2...@standardnotes/event-store@1.3.3) (2022-09-06)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/event-store
|
||||
|
||||
## [1.3.2](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.3.1...@standardnotes/event-store@1.3.2) (2022-09-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/event-store
|
||||
|
||||
## [1.3.1](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.3.0...@standardnotes/event-store@1.3.1) (2022-09-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/event-store
|
||||
|
||||
# [1.3.0](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.2.3...@standardnotes/event-store@1.3.0) (2022-09-02)
|
||||
|
||||
### Features
|
||||
|
||||
* **event-store:** add listening to subscription reverts ([ef1e2bb](https://github.com/standardnotes/server/commit/ef1e2bb5edb6df191d22a676e365aed3511b3960))
|
||||
|
||||
## [1.2.3](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.2.2...@standardnotes/event-store@1.2.3) (2022-09-01)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/event-store
|
||||
|
||||
## [1.2.2](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.2.1...@standardnotes/event-store@1.2.2) (2022-09-01)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/event-store
|
||||
|
||||
## [1.2.1](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.2.0...@standardnotes/event-store@1.2.1) (2022-08-30)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/event-store
|
||||
|
||||
# [1.2.0](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.1.33...@standardnotes/event-store@1.2.0) (2022-08-30)
|
||||
|
||||
### Features
|
||||
|
||||
* **event-store:** add account claim events subscription ([dd6bec8](https://github.com/standardnotes/server/commit/dd6bec8a0c957b87ccf4d1fb0636fa3f56a4525e))
|
||||
|
||||
## [1.1.33](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.1.32...@standardnotes/event-store@1.1.33) (2022-08-15)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **event-store:** add payment events handling ([3477c81](https://github.com/standardnotes/server/commit/3477c81d37e6cb92e76d59b1128daac702a4a7ae))
|
||||
|
||||
## [1.1.32](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.1.31...@standardnotes/event-store@1.1.32) (2022-08-15)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/event-store
|
||||
|
||||
## [1.1.31](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.1.30...@standardnotes/event-store@1.1.31) (2022-08-12)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **event-store:** add listening to offline subscription token created events ([1ba5ba5](https://github.com/standardnotes/server/commit/1ba5ba5ff6f7afe5c8e2e929555042a01f2465ac))
|
||||
|
||||
## [1.1.30](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.1.29...@standardnotes/event-store@1.1.30) (2022-08-10)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/event-store
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user