mirror of
https://github.com/standardnotes/server
synced 2026-04-19 17:02:25 -04:00
Compare commits
105 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9313e6b568 | |||
| 8033177f48 | |||
| 11011fa15d | |||
| c2e9f3e72b | |||
| f0fb7fd1cd | |||
| 15e342fd51 | |||
| dfa7e06f87 | |||
| a9aef5521b | |||
| a628bdc44e | |||
| db6f966045 | |||
| 9b602ed405 | |||
| db15457ce4 | |||
| 719d8558a3 | |||
| c207c3fc84 | |||
| 4bde4758c3 | |||
| 5eb957c82a | |||
| 0b38617acf | |||
| 377d32c449 | |||
| cdfb0c2603 | |||
| d85152429c | |||
| 422e596fc7 | |||
| 89334c9022 | |||
| f5a0e88ab9 | |||
| a59ba08339 | |||
| 2641056c51 | |||
| 5d812befc4 | |||
| 1c592d6f96 | |||
| 531f13fe1f | |||
| 4757cc8dae | |||
| ecdfe9ecc0 | |||
| d19cb08e9c | |||
| f45320e5ed | |||
| 93ded34de9 | |||
| dd13e2eaf7 | |||
| 1405c6f260 | |||
| 0dab31f993 | |||
| 8070c70152 | |||
| c3ebb321cf | |||
| e54deb594a | |||
| 432d071ec8 | |||
| b9c06f1f5d | |||
| 52cc6462a6 | |||
| 35c2afef67 | |||
| 339c86fca0 | |||
| 0afd3de977 | |||
| e699569d46 | |||
| ced852d9db | |||
| a63612613e | |||
| c9ec7b492a | |||
| bf8ffc07ee | |||
| 73e1ea7f93 | |||
| 5979b99398 | |||
| 50ddb918cc | |||
| 6b19eb8876 | |||
| 47be0841fc | |||
| 99c7bb70fc | |||
| f139bb0036 | |||
| 23f592ca24 | |||
| fe4821d4f7 | |||
| c338d4fec5 | |||
| d7e6758089 | |||
| 0ad62636b9 | |||
| f872c7dfe9 | |||
| c5fdd59eb1 | |||
| 7132dc3ac0 | |||
| 956d5be959 | |||
| 936591d40b | |||
| 686e4f8ddf | |||
| b61825235e | |||
| 8157f324a0 | |||
| 132b617aaa | |||
| 25b1f3e9ea | |||
| f94c8fc26e | |||
| d149f46cf6 | |||
| 6a24ba5d56 | |||
| db8333a858 | |||
| 3af254d7c7 | |||
| 8151bb108a | |||
| 3b18769c2d | |||
| 2883cac6d4 | |||
| d7ae2f0625 | |||
| 318f6d0986 | |||
| 2ca430f40c | |||
| fd65060a8e | |||
| cb81f819ba | |||
| 61c7040e4b | |||
| fa10827443 | |||
| bcee779e74 | |||
| 34315c91d7 | |||
| 8d3bf6c4a5 | |||
| 0c176b70f8 | |||
| 87a5854357 | |||
| 9c2d51d718 | |||
| e618f046ea | |||
| a36cb925ff | |||
| 9e2aea2793 | |||
| ef1e2bb5ed | |||
| 6a457281ea | |||
| 41c512798d | |||
| ffa0f51305 | |||
| e0cec9e24a | |||
| f6b359a772 | |||
| 648eb89c7c | |||
| ba22e085b8 | |||
| 35373db1d3 |
@@ -2484,16 +2484,17 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
||||
}]\
|
||||
]],\
|
||||
["@standardnotes/api", [\
|
||||
["npm:1.1.19", {\
|
||||
"packageLocation": "./.yarn/cache/@standardnotes-api-npm-1.1.19-6a6d650ec9-cca168245a.zip/node_modules/@standardnotes/api/",\
|
||||
["npm:1.8.1", {\
|
||||
"packageLocation": "./.yarn/cache/@standardnotes-api-npm-1.8.1-15c2e051d4-76c5d1a2d2.zip/node_modules/@standardnotes/api/",\
|
||||
"packageDependencies": [\
|
||||
["@standardnotes/api", "npm:1.1.19"],\
|
||||
["@standardnotes/auth", "npm:3.19.4"],\
|
||||
["@standardnotes/api", "npm:1.8.1"],\
|
||||
["@standardnotes/common", "workspace:packages/common"],\
|
||||
["@standardnotes/encryption", "npm:1.12.0"],\
|
||||
["@standardnotes/responses", "npm:1.6.39"],\
|
||||
["@standardnotes/services", "npm:1.15.0"],\
|
||||
["@standardnotes/utils", "npm:1.6.12"]\
|
||||
["@standardnotes/encryption", "npm:1.15.3"],\
|
||||
["@standardnotes/models", "npm:1.18.3"],\
|
||||
["@standardnotes/responses", "npm:1.10.2"],\
|
||||
["@standardnotes/security", "workspace:packages/security"],\
|
||||
["@standardnotes/utils", "npm:1.9.0"],\
|
||||
["reflect-metadata", "npm:0.1.13"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
@@ -2506,6 +2507,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
||||
["@newrelic/winston-enricher", "virtual:04783e12400851b8a3d76e71495851cc94959db6e62f04cb0a31190080629440b182d8c8eb4d7f2b04e281912f2783a5fd4d2c3c6ab68d38b7097246c93f4c19#npm:4.0.0"],\
|
||||
["@sentry/node", "npm:7.5.0"],\
|
||||
["@standardnotes/analytics", "workspace:packages/analytics"],\
|
||||
["@standardnotes/common", "workspace:packages/common"],\
|
||||
["@standardnotes/domain-events", "workspace:packages/domain-events"],\
|
||||
["@standardnotes/domain-events-infra", "workspace:packages/domain-events-infra"],\
|
||||
["@standardnotes/security", "workspace:packages/security"],\
|
||||
@@ -2561,7 +2563,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
||||
["@newrelic/winston-enricher", "virtual:04783e12400851b8a3d76e71495851cc94959db6e62f04cb0a31190080629440b182d8c8eb4d7f2b04e281912f2783a5fd4d2c3c6ab68d38b7097246c93f4c19#npm:4.0.0"],\
|
||||
["@sentry/node", "npm:7.5.0"],\
|
||||
["@standardnotes/analytics", "workspace:packages/analytics"],\
|
||||
["@standardnotes/api", "npm:1.1.19"],\
|
||||
["@standardnotes/api", "npm:1.8.1"],\
|
||||
["@standardnotes/common", "workspace:packages/common"],\
|
||||
["@standardnotes/domain-events", "workspace:packages/domain-events"],\
|
||||
["@standardnotes/domain-events-infra", "workspace:packages/domain-events-infra"],\
|
||||
@@ -2607,7 +2609,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"\
|
||||
@@ -2686,16 +2688,15 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
||||
}]\
|
||||
]],\
|
||||
["@standardnotes/encryption", [\
|
||||
["npm:1.12.0", {\
|
||||
"packageLocation": "./.yarn/cache/@standardnotes-encryption-npm-1.12.0-eb2342c675-1a28653b1e.zip/node_modules/@standardnotes/encryption/",\
|
||||
["npm:1.15.3", {\
|
||||
"packageLocation": "./.yarn/cache/@standardnotes-encryption-npm-1.15.3-3580c52c1f-1a7863299f.zip/node_modules/@standardnotes/encryption/",\
|
||||
"packageDependencies": [\
|
||||
["@standardnotes/encryption", "npm:1.12.0"],\
|
||||
["@standardnotes/encryption", "npm:1.15.3"],\
|
||||
["@standardnotes/common", "workspace:packages/common"],\
|
||||
["@standardnotes/models", "npm:1.14.0"],\
|
||||
["@standardnotes/responses", "npm:1.6.39"],\
|
||||
["@standardnotes/services", "npm:1.15.0"],\
|
||||
["@standardnotes/sncrypto-common", "npm:1.9.0"],\
|
||||
["@standardnotes/utils", "npm:1.6.12"],\
|
||||
["@standardnotes/models", "npm:1.18.3"],\
|
||||
["@standardnotes/responses", "npm:1.10.2"],\
|
||||
["@standardnotes/sncrypto-common", "npm:1.11.1"],\
|
||||
["@standardnotes/utils", "npm:1.9.0"],\
|
||||
["reflect-metadata", "npm:0.1.13"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
@@ -2741,6 +2742,17 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
||||
["reflect-metadata", "npm:0.1.13"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}],\
|
||||
["npm:1.52.0", {\
|
||||
"packageLocation": "./.yarn/cache/@standardnotes-features-npm-1.52.0-8c1adf7881-3e6014272f.zip/node_modules/@standardnotes/features/",\
|
||||
"packageDependencies": [\
|
||||
["@standardnotes/features", "npm:1.52.0"],\
|
||||
["@standardnotes/auth", "npm:3.19.4"],\
|
||||
["@standardnotes/common", "workspace:packages/common"],\
|
||||
["@standardnotes/security", "workspace:packages/security"],\
|
||||
["reflect-metadata", "npm:0.1.13"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["@standardnotes/files-server", [\
|
||||
@@ -2789,21 +2801,21 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
||||
["reflect-metadata", "npm:0.1.13"],\
|
||||
["ts-jest", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:28.0.5"],\
|
||||
["ts-node", "virtual:b442cf0427cc365d1c137f7340f9b81f9b204561afe791a8564ae9590c3a7fc4b5f793aaf8817b946f75a3cb64d03ef8790eb847f8b576b41e700da7b00c240c#npm:10.9.1"],\
|
||||
["uuid", "npm:8.3.2"],\
|
||||
["uuid", "npm:9.0.0"],\
|
||||
["winston", "npm:3.8.1"]\
|
||||
],\
|
||||
"linkType": "SOFT"\
|
||||
}]\
|
||||
]],\
|
||||
["@standardnotes/models", [\
|
||||
["npm:1.14.0", {\
|
||||
"packageLocation": "./.yarn/cache/@standardnotes-models-npm-1.14.0-6f064d99e7-bfb9d517b6.zip/node_modules/@standardnotes/models/",\
|
||||
["npm:1.18.3", {\
|
||||
"packageLocation": "./.yarn/cache/@standardnotes-models-npm-1.18.3-6c65a62f30-21830c805f.zip/node_modules/@standardnotes/models/",\
|
||||
"packageDependencies": [\
|
||||
["@standardnotes/models", "npm:1.14.0"],\
|
||||
["@standardnotes/models", "npm:1.18.3"],\
|
||||
["@standardnotes/common", "workspace:packages/common"],\
|
||||
["@standardnotes/features", "npm:1.50.0"],\
|
||||
["@standardnotes/responses", "npm:1.6.39"],\
|
||||
["@standardnotes/utils", "npm:1.6.12"],\
|
||||
["@standardnotes/features", "npm:1.52.0"],\
|
||||
["@standardnotes/responses", "npm:1.10.2"],\
|
||||
["@standardnotes/utils", "npm:1.9.0"],\
|
||||
["lodash", "npm:4.17.21"],\
|
||||
["reflect-metadata", "npm:0.1.13"]\
|
||||
],\
|
||||
@@ -2839,6 +2851,17 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
||||
}]\
|
||||
]],\
|
||||
["@standardnotes/responses", [\
|
||||
["npm:1.10.2", {\
|
||||
"packageLocation": "./.yarn/cache/@standardnotes-responses-npm-1.10.2-39d2d1f9b5-364724b5c7.zip/node_modules/@standardnotes/responses/",\
|
||||
"packageDependencies": [\
|
||||
["@standardnotes/responses", "npm:1.10.2"],\
|
||||
["@standardnotes/common", "workspace:packages/common"],\
|
||||
["@standardnotes/features", "npm:1.52.0"],\
|
||||
["@standardnotes/security", "workspace:packages/security"],\
|
||||
["reflect-metadata", "npm:0.1.13"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}],\
|
||||
["npm:1.6.39", {\
|
||||
"packageLocation": "./.yarn/cache/@standardnotes-responses-npm-1.6.39-395f4c2d65-0ea1d4d5b8.zip/node_modules/@standardnotes/responses/",\
|
||||
"packageDependencies": [\
|
||||
@@ -2866,11 +2889,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"],\
|
||||
@@ -2930,21 +2954,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
||||
"linkType": "SOFT"\
|
||||
}]\
|
||||
]],\
|
||||
["@standardnotes/services", [\
|
||||
["npm:1.15.0", {\
|
||||
"packageLocation": "./.yarn/cache/@standardnotes-services-npm-1.15.0-acab3bc6a3-1028a5b4c1.zip/node_modules/@standardnotes/services/",\
|
||||
"packageDependencies": [\
|
||||
["@standardnotes/services", "npm:1.15.0"],\
|
||||
["@standardnotes/auth", "npm:3.19.4"],\
|
||||
["@standardnotes/common", "workspace:packages/common"],\
|
||||
["@standardnotes/models", "npm:1.14.0"],\
|
||||
["@standardnotes/responses", "npm:1.6.39"],\
|
||||
["@standardnotes/utils", "npm:1.6.12"],\
|
||||
["reflect-metadata", "npm:0.1.13"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["@standardnotes/settings", [\
|
||||
["workspace:packages/settings", {\
|
||||
"packageLocation": "./packages/settings/",\
|
||||
@@ -2958,6 +2967,14 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
||||
}]\
|
||||
]],\
|
||||
["@standardnotes/sncrypto-common", [\
|
||||
["npm:1.11.1", {\
|
||||
"packageLocation": "./.yarn/cache/@standardnotes-sncrypto-common-npm-1.11.1-58d12d6912-69d698abb7.zip/node_modules/@standardnotes/sncrypto-common/",\
|
||||
"packageDependencies": [\
|
||||
["@standardnotes/sncrypto-common", "npm:1.11.1"],\
|
||||
["reflect-metadata", "npm:0.1.13"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}],\
|
||||
["npm:1.9.0", {\
|
||||
"packageLocation": "./.yarn/cache/@standardnotes-sncrypto-common-npm-1.9.0-48773f745a-42252d7198.zip/node_modules/@standardnotes/sncrypto-common/",\
|
||||
"packageDependencies": [\
|
||||
@@ -3035,7 +3052,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"\
|
||||
@@ -3069,6 +3086,17 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
||||
["lodash", "npm:4.17.21"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}],\
|
||||
["npm:1.9.0", {\
|
||||
"packageLocation": "./.yarn/cache/@standardnotes-utils-npm-1.9.0-da939553f6-4591aff48d.zip/node_modules/@standardnotes/utils/",\
|
||||
"packageDependencies": [\
|
||||
["@standardnotes/utils", "npm:1.9.0"],\
|
||||
["@standardnotes/common", "workspace:packages/common"],\
|
||||
["dompurify", "npm:2.4.0"],\
|
||||
["lodash", "npm:4.17.21"],\
|
||||
["reflect-metadata", "npm:0.1.13"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["@szmarczak/http-timer", [\
|
||||
@@ -5842,6 +5870,13 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
||||
["dompurify", "npm:2.3.8"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}],\
|
||||
["npm:2.4.0", {\
|
||||
"packageLocation": "./.yarn/cache/dompurify-npm-2.4.0-0ffecf22ef-c93ea73cf8.zip/node_modules/dompurify/",\
|
||||
"packageDependencies": [\
|
||||
["dompurify", "npm:2.4.0"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["dot-prop", [\
|
||||
@@ -12778,6 +12813,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", [\
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -3,6 +3,84 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.29.1](https://github.com/standardnotes/server/compare/@standardnotes/analytics@1.29.0...@standardnotes/analytics@1.29.1) (2022-09-16)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** change remaining subscription time stats to percentage ([5eb957c](https://github.com/standardnotes/server/commit/5eb957c82a8cc5fdcb6815e2cd30e49cd2b1e8ac))
|
||||
|
||||
# [1.29.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@1.28.0...@standardnotes/analytics@1.29.0) (2022-09-15)
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** add remaining subscription time stats ([a59ba08](https://github.com/standardnotes/server/commit/a59ba083397c75960af0e8a102b617bf5baa287f))
|
||||
|
||||
# [1.28.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@1.27.0...@standardnotes/analytics@1.28.0) (2022-09-09)
|
||||
|
||||
### Features
|
||||
|
||||
* **syncing-server:** add tracking files count in stats ([52cc646](https://github.com/standardnotes/server/commit/52cc6462a66dae3bd6c05f551d4ba661c8a9b8c8))
|
||||
|
||||
# [1.27.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@1.26.0...@standardnotes/analytics@1.27.0) (2022-09-09)
|
||||
|
||||
### Features
|
||||
|
||||
* **api-gateway:** add tracking general activity for free and paid users breakdown ([0afd3de](https://github.com/standardnotes/server/commit/0afd3de9779e2abe10deede24626a3cbe6b15e6c))
|
||||
|
||||
# [1.26.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@1.25.0...@standardnotes/analytics@1.26.0) (2022-09-09)
|
||||
|
||||
### Features
|
||||
|
||||
* **syncing-server:** add statistics for notes count for free and paid users ([c9ec7b4](https://github.com/standardnotes/server/commit/c9ec7b492aea1911e441ed8ad9a155f871be2ef7))
|
||||
|
||||
# [1.25.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@1.24.0...@standardnotes/analytics@1.25.0) (2022-09-07)
|
||||
|
||||
### Features
|
||||
|
||||
* **analytics:** add discrete period key generation for last 7 days ([f872c7d](https://github.com/standardnotes/server/commit/f872c7dfe9f120f40dd0c28a9e0f5749eb251643))
|
||||
|
||||
# [1.24.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@1.23.0...@standardnotes/analytics@1.24.0) (2022-09-07)
|
||||
|
||||
### Features
|
||||
|
||||
* **analytics:** add calculation retention for two activities ([7132dc3](https://github.com/standardnotes/server/commit/7132dc3ac0cf878d2c326243747343e8a6746e2f))
|
||||
|
||||
# [1.23.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@1.22.0...@standardnotes/analytics@1.23.0) (2022-09-07)
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** add measuring registration to subscription time statistics ([b618252](https://github.com/standardnotes/server/commit/b61825235eebaf5eddb55cbda173176ca43c0099))
|
||||
|
||||
# [1.22.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@1.21.1...@standardnotes/analytics@1.22.0) (2022-09-06)
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** add statistics for refunds and account deletions ([d7ae2f0](https://github.com/standardnotes/server/commit/d7ae2f06255b19eb5d3403a4989610390064754e))
|
||||
|
||||
## [1.21.1](https://github.com/standardnotes/server/compare/@standardnotes/analytics@1.21.0...@standardnotes/analytics@1.21.1) (2022-09-06)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **analytics:** increment by float instead of integer on measures ([cb81f81](https://github.com/standardnotes/server/commit/cb81f819ba30a45f27ec344480b5ef22e5a0a50d))
|
||||
|
||||
# [1.21.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@1.20.0...@standardnotes/analytics@1.21.0) (2022-09-05)
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** add measuring subscription length ([fa10827](https://github.com/standardnotes/server/commit/fa108274430d8dff1016ddcba5bbcb2778eb781b))
|
||||
|
||||
# [1.20.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@1.19.0...@standardnotes/analytics@1.20.0) (2022-09-05)
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** add keeping stats on payments ([0c176b7](https://github.com/standardnotes/server/commit/0c176b70f8281e1e490224b9c7ab85f272a3d4e9))
|
||||
|
||||
# [1.19.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@1.18.1...@standardnotes/analytics@1.19.0) (2022-09-05)
|
||||
|
||||
### Features
|
||||
|
||||
* **analytics:** add statistics measurements tracking ([a36cb92](https://github.com/standardnotes/server/commit/a36cb925ff3bd8396a53f58c3e954549e904d694))
|
||||
|
||||
## [1.18.1](https://github.com/standardnotes/server/compare/@standardnotes/analytics@1.18.0...@standardnotes/analytics@1.18.1) (2022-08-15)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/analytics",
|
||||
"version": "1.18.1",
|
||||
"version": "1.29.1",
|
||||
"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",
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
export enum AnalyticsActivity {
|
||||
GeneralActivity = 'general-activity',
|
||||
GeneralActivityFreeUsers = 'general-activity-free-users',
|
||||
GeneralActivityPaidUsers = 'general-activity-paid-users',
|
||||
EditingItems = 'editing-items',
|
||||
CheckingIntegrity = 'checking-integrity',
|
||||
Login = 'login',
|
||||
Register = 'register',
|
||||
DeleteAccount = 'DeleteAccount',
|
||||
|
||||
@@ -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,11 @@
|
||||
export enum StatisticsMeasure {
|
||||
Income = 'income',
|
||||
SubscriptionLength = 'subscription-length',
|
||||
RegistrationLength = 'registration-length',
|
||||
RegistrationToSubscriptionTime = 'registration-to-subscription-time',
|
||||
RemainingSubscriptionTimePercentage = 'remaining-subscription-time-percentage',
|
||||
Refunds = 'refunds',
|
||||
NotesCountFreeUsers = 'notes-count-free-users',
|
||||
NotesCountPaidUsers = 'notes-count-paid-users',
|
||||
FilesCount = 'files-count',
|
||||
}
|
||||
@@ -1,3 +1,6 @@
|
||||
import { Period } from '../Time/Period'
|
||||
import { StatisticsMeasure } from './StatisticsMeasure'
|
||||
|
||||
export interface StatisticsStoreInterface {
|
||||
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,6 +8,7 @@ export enum Period {
|
||||
ThisMonth,
|
||||
LastMonth,
|
||||
Last30Days,
|
||||
Last7Days,
|
||||
Q1ThisYear,
|
||||
Q2ThisYear,
|
||||
Q3ThisYear,
|
||||
|
||||
@@ -48,6 +48,18 @@ describe('PeriodKeyGenerator', () => {
|
||||
])
|
||||
})
|
||||
|
||||
it('should generate period keys for last 7 days', () => {
|
||||
expect(createGenerator().getDiscretePeriodKeys(Period.Last7Days)).toEqual([
|
||||
'2022-5-17',
|
||||
'2022-5-18',
|
||||
'2022-5-19',
|
||||
'2022-5-20',
|
||||
'2022-5-21',
|
||||
'2022-5-22',
|
||||
'2022-5-23',
|
||||
])
|
||||
})
|
||||
|
||||
it('should generate period keys for Q1', () => {
|
||||
expect(createGenerator().getDiscretePeriodKeys(Period.Q1ThisYear)).toEqual(['2022-1', '2022-2', '2022-3'])
|
||||
})
|
||||
|
||||
@@ -11,6 +11,12 @@ export class PeriodKeyGenerator implements PeriodKeyGeneratorInterface {
|
||||
periodKeys.unshift(this.getDailyKey(this.getDateNDaysBefore(i)))
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -125,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',
|
||||
)
|
||||
|
||||
@@ -95,21 +95,19 @@ export class RedisAnalyticsStore implements AnalyticsStoreInterface {
|
||||
return bitValue === 1
|
||||
}
|
||||
|
||||
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}`
|
||||
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:${activity}:timespan:${initialPeriodKey}`,
|
||||
`bitmap:action:${activity}:timespan:${subsequentPeriodKey}`,
|
||||
`bitmap:action:${parameters.firstActivity}:timespan:${parameters.firstActivityPeriodKey}`,
|
||||
`bitmap:action:${parameters.secondActivity}:timespan:${parameters.secondActivityPeriodKey}`,
|
||||
)
|
||||
|
||||
await this.redisClient.expire(diffKey, 3600)
|
||||
@@ -117,12 +115,25 @@ export class RedisAnalyticsStore implements AnalyticsStoreInterface {
|
||||
const retainedTotalInActivity = await this.redisClient.bitcount(diffKey)
|
||||
|
||||
const initialTotalInActivity = await this.redisClient.bitcount(
|
||||
`bitmap:action:${activity}:timespan:${initialPeriodKey}`,
|
||||
`bitmap:action:${parameters.firstActivity}:timespan:${parameters.firstActivityPeriodKey}`,
|
||||
)
|
||||
|
||||
return Math.ceil((retainedTotalInActivity * 100) / initialTotalInActivity)
|
||||
}
|
||||
|
||||
async calculateActivityRetention(
|
||||
activity: AnalyticsActivity,
|
||||
firstPeriod: Period,
|
||||
secondPeriod: Period,
|
||||
): Promise<number> {
|
||||
return this.calculateActivitiesRetention({
|
||||
firstActivity: activity,
|
||||
firstActivityPeriodKey: this.periodKeyGenerator.getPeriodKey(firstPeriod),
|
||||
secondActivity: activity,
|
||||
secondActivityPeriodKey: this.periodKeyGenerator.getPeriodKey(secondPeriod),
|
||||
})
|
||||
}
|
||||
|
||||
async calculateActivityTotalCount(activity: AnalyticsActivity, period: Period): Promise<number> {
|
||||
return this.redisClient.bitcount(
|
||||
`bitmap:action:${activity}:timespan:${this.periodKeyGenerator.getPeriodKey(period)}`,
|
||||
|
||||
@@ -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,159 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [1.20.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.19.6...@standardnotes/api-gateway@1.20.0) (2022-09-21)
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** add creating web socket connection tokens ([8033177](https://github.com/standardnotes/api-gateway/commit/8033177f48dc961194f24fb7daa1073b8b697b74))
|
||||
|
||||
## [1.19.6](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.19.5...@standardnotes/api-gateway@1.19.6) (2022-09-19)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||
|
||||
## [1.19.5](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.19.4...@standardnotes/api-gateway@1.19.5) (2022-09-16)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** change remaining subscription time stats to percentage ([5eb957c](https://github.com/standardnotes/api-gateway/commit/5eb957c82a8cc5fdcb6815e2cd30e49cd2b1e8ac))
|
||||
|
||||
## [1.19.4](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.19.3...@standardnotes/api-gateway@1.19.4) (2022-09-16)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||
|
||||
## [1.19.3](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.19.2...@standardnotes/api-gateway@1.19.3) (2022-09-15)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **api-gateway:** add remaining subscription time to stats ([89334c9](https://github.com/standardnotes/api-gateway/commit/89334c90221045308d83fce9e97c146185d21389))
|
||||
|
||||
## [1.19.2](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.19.1...@standardnotes/api-gateway@1.19.2) (2022-09-15)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||
|
||||
## [1.19.1](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.19.0...@standardnotes/api-gateway@1.19.1) (2022-09-09)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||
|
||||
# [1.19.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.18.0...@standardnotes/api-gateway@1.19.0) (2022-09-09)
|
||||
|
||||
### Features
|
||||
|
||||
* **syncing-server:** add tracking files count in stats ([52cc646](https://github.com/standardnotes/api-gateway/commit/52cc6462a66dae3bd6c05f551d4ba661c8a9b8c8))
|
||||
|
||||
# [1.18.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.17.4...@standardnotes/api-gateway@1.18.0) (2022-09-09)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **api-gateway:** add general activity breakdown to yesterdays report stats ([339c86f](https://github.com/standardnotes/api-gateway/commit/339c86fca073b02054260417b7519c08874e1e4e))
|
||||
|
||||
### Features
|
||||
|
||||
* **api-gateway:** add tracking general activity for free and paid users breakdown ([0afd3de](https://github.com/standardnotes/api-gateway/commit/0afd3de9779e2abe10deede24626a3cbe6b15e6c))
|
||||
|
||||
## [1.17.4](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.17.3...@standardnotes/api-gateway@1.17.4) (2022-09-09)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **api-gateway:** add notes count statistics to report ([ced852d](https://github.com/standardnotes/api-gateway/commit/ced852d9dbf8cab4c235b94a834968a5fc5e7d36))
|
||||
|
||||
## [1.17.3](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.17.2...@standardnotes/api-gateway@1.17.3) (2022-09-09)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||
|
||||
## [1.17.2](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.17.1...@standardnotes/api-gateway@1.17.2) (2022-09-08)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **api-gateway:** retention data structure to include both period keys ([50ddb91](https://github.com/standardnotes/api-gateway/commit/50ddb918ccc52bee4caad82504cb899bc5936150))
|
||||
|
||||
## [1.17.1](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.17.0...@standardnotes/api-gateway@1.17.1) (2022-09-08)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **api-gateway:** retention data structure ([47be084](https://github.com/standardnotes/api-gateway/commit/47be0841fc6d5fa00892e775bb3a40f404a6382b))
|
||||
|
||||
# [1.17.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.16.8...@standardnotes/api-gateway@1.17.0) (2022-09-08)
|
||||
|
||||
### Features
|
||||
|
||||
* **api-gateway:** add registration-to-activity retention analytics to report ([f139bb0](https://github.com/standardnotes/api-gateway/commit/f139bb003669bb41f98ad4bb59a036c489f43606))
|
||||
|
||||
## [1.16.8](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.16.7...@standardnotes/api-gateway@1.16.8) (2022-09-08)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||
|
||||
## [1.16.7](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.16.6...@standardnotes/api-gateway@1.16.7) (2022-09-08)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||
|
||||
## [1.16.6](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.16.5...@standardnotes/api-gateway@1.16.6) (2022-09-07)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||
|
||||
## [1.16.5](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.16.4...@standardnotes/api-gateway@1.16.5) (2022-09-07)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||
|
||||
## [1.16.4](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.16.3...@standardnotes/api-gateway@1.16.4) (2022-09-07)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **api-gateway:** add registration-to-subscription time to analytics report ([936591d](https://github.com/standardnotes/api-gateway/commit/936591d40b5f5beb5c0a824c92cdfa20fff51c97))
|
||||
|
||||
## [1.16.3](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.16.2...@standardnotes/api-gateway@1.16.3) (2022-09-07)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||
|
||||
## [1.16.2](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.16.1...@standardnotes/api-gateway@1.16.2) (2022-09-06)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **api-gateway:** include period key in statistics measures ([d149f46](https://github.com/standardnotes/api-gateway/commit/d149f46cf6456201dd8690977f64ed32a75f3459))
|
||||
* **api-gateway:** period types on analytics report ([f94c8fc](https://github.com/standardnotes/api-gateway/commit/f94c8fc26e684a07101cc5282ebb9cda3c8c6961))
|
||||
|
||||
## [1.16.1](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.16.0...@standardnotes/api-gateway@1.16.1) (2022-09-06)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||
|
||||
# [1.16.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.15.12...@standardnotes/api-gateway@1.16.0) (2022-09-06)
|
||||
|
||||
### Features
|
||||
|
||||
* **api-gateway:** add statistics measures to report generation ([8151bb1](https://github.com/standardnotes/api-gateway/commit/8151bb108affb2b5cfa1ab365f99a9f0170a7795))
|
||||
|
||||
## [1.15.12](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.15.11...@standardnotes/api-gateway@1.15.12) (2022-09-06)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||
|
||||
## [1.15.11](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.15.10...@standardnotes/api-gateway@1.15.11) (2022-09-06)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||
|
||||
## [1.15.10](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.15.9...@standardnotes/api-gateway@1.15.10) (2022-09-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||
|
||||
## [1.15.9](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.15.8...@standardnotes/api-gateway@1.15.9) (2022-09-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||
|
||||
## [1.15.8](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.15.7...@standardnotes/api-gateway@1.15.8) (2022-09-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||
|
||||
## [1.15.7](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.15.6...@standardnotes/api-gateway@1.15.7) (2022-09-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||
|
||||
## [1.15.6](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.15.5...@standardnotes/api-gateway@1.15.6) (2022-09-01)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||
|
||||
## [1.15.5](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.15.4...@standardnotes/api-gateway@1.15.5) (2022-09-01)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||
|
||||
## [1.15.4](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.15.3...@standardnotes/api-gateway@1.15.4) (2022-08-30)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||
|
||||
@@ -12,12 +12,20 @@ import {
|
||||
DailyAnalyticsReportGeneratedEvent,
|
||||
DomainEventService,
|
||||
} from '@standardnotes/domain-events'
|
||||
import { AnalyticsActivity, AnalyticsStoreInterface, Period, StatisticsStoreInterface } from '@standardnotes/analytics'
|
||||
import {
|
||||
AnalyticsActivity,
|
||||
AnalyticsStoreInterface,
|
||||
Period,
|
||||
PeriodKeyGeneratorInterface,
|
||||
StatisticsMeasure,
|
||||
StatisticsStoreInterface,
|
||||
} from '@standardnotes/analytics'
|
||||
|
||||
const requestReport = async (
|
||||
analyticsStore: AnalyticsStoreInterface,
|
||||
statisticsStore: StatisticsStoreInterface,
|
||||
domainEventPublisher: DomainEventPublisherInterface,
|
||||
periodKeyGenerator: PeriodKeyGeneratorInterface,
|
||||
): Promise<void> => {
|
||||
const analyticsOverTime = []
|
||||
|
||||
@@ -60,9 +68,10 @@ const requestReport = async (
|
||||
|
||||
const yesterdayActivityStatistics = []
|
||||
const yesterdayActivityNames = [
|
||||
AnalyticsActivity.EditingItems,
|
||||
AnalyticsActivity.LimitedDiscountOfferPurchased,
|
||||
AnalyticsActivity.GeneralActivity,
|
||||
AnalyticsActivity.GeneralActivityFreeUsers,
|
||||
AnalyticsActivity.GeneralActivityPaidUsers,
|
||||
AnalyticsActivity.PaymentFailed,
|
||||
AnalyticsActivity.PaymentSuccess,
|
||||
]
|
||||
@@ -79,6 +88,48 @@ const requestReport = async (
|
||||
})
|
||||
}
|
||||
|
||||
const statisticMeasureNames = [
|
||||
StatisticsMeasure.Income,
|
||||
StatisticsMeasure.Refunds,
|
||||
StatisticsMeasure.RegistrationLength,
|
||||
StatisticsMeasure.SubscriptionLength,
|
||||
StatisticsMeasure.RegistrationToSubscriptionTime,
|
||||
StatisticsMeasure.RemainingSubscriptionTimePercentage,
|
||||
StatisticsMeasure.NotesCountFreeUsers,
|
||||
StatisticsMeasure.NotesCountPaidUsers,
|
||||
StatisticsMeasure.FilesCount,
|
||||
]
|
||||
const statisticMeasures = []
|
||||
for (const statisticMeasureName of statisticMeasureNames) {
|
||||
for (const period of [Period.Yesterday, Period.ThisMonth]) {
|
||||
statisticMeasures.push({
|
||||
name: statisticMeasureName,
|
||||
period,
|
||||
totalValue: await statisticsStore.getMeasureTotal(statisticMeasureName, period),
|
||||
average: await statisticsStore.getMeasureAverage(statisticMeasureName, period),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const periodKeys = periodKeyGenerator.getDiscretePeriodKeys(Period.Last7Days)
|
||||
const retentionOverDays = []
|
||||
for (let i = 0; i < periodKeys.length; i++) {
|
||||
for (let j = 0; j < periodKeys.length - i; j++) {
|
||||
const dailyRetention = await analyticsStore.calculateActivitiesRetention({
|
||||
firstActivity: AnalyticsActivity.Register,
|
||||
firstActivityPeriodKey: periodKeys[i],
|
||||
secondActivity: AnalyticsActivity.GeneralActivity,
|
||||
secondActivityPeriodKey: periodKeys[i + j],
|
||||
})
|
||||
|
||||
retentionOverDays.push({
|
||||
firstPeriodKey: periodKeys[i],
|
||||
secondPeriodKey: periodKeys[i + j],
|
||||
value: dailyRetention,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const event: DailyAnalyticsReportGeneratedEvent = {
|
||||
type: 'DAILY_ANALYTICS_REPORT_GENERATED',
|
||||
createdAt: new Date(),
|
||||
@@ -95,6 +146,17 @@ const requestReport = async (
|
||||
outOfSyncIncidents: await statisticsStore.getYesterdayOutOfSyncIncidents(),
|
||||
activityStatistics: yesterdayActivityStatistics,
|
||||
activityStatisticsOverTime: analyticsOverTime,
|
||||
statisticMeasures,
|
||||
retentionStatistics: [
|
||||
{
|
||||
firstActivity: AnalyticsActivity.Register,
|
||||
secondActivity: AnalyticsActivity.GeneralActivity,
|
||||
retention: {
|
||||
periodKeys,
|
||||
values: retentionOverDays,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
@@ -113,8 +175,9 @@ void container.load().then((container) => {
|
||||
const analyticsStore: AnalyticsStoreInterface = container.get(TYPES.AnalyticsStore)
|
||||
const statisticsStore: StatisticsStoreInterface = container.get(TYPES.StatisticsStore)
|
||||
const domainEventPublisher: DomainEventPublisherInterface = container.get(TYPES.DomainEventPublisher)
|
||||
const periodKeyGenerator: PeriodKeyGeneratorInterface = container.get(TYPES.PeriodKeyGenerator)
|
||||
|
||||
Promise.resolve(requestReport(analyticsStore, statisticsStore, domainEventPublisher))
|
||||
Promise.resolve(requestReport(analyticsStore, statisticsStore, domainEventPublisher, periodKeyGenerator))
|
||||
.then(() => {
|
||||
logger.info('Usage report generation complete')
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/api-gateway",
|
||||
"version": "1.15.4",
|
||||
"version": "1.20.0",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
@@ -25,6 +25,7 @@
|
||||
"@newrelic/winston-enricher": "^4.0.0",
|
||||
"@sentry/node": "^7.3.0",
|
||||
"@standardnotes/analytics": "workspace:*",
|
||||
"@standardnotes/common": "workspace:^",
|
||||
"@standardnotes/domain-events": "workspace:*",
|
||||
"@standardnotes/domain-events-infra": "workspace:*",
|
||||
"@standardnotes/security": "workspace:*",
|
||||
|
||||
@@ -6,6 +6,7 @@ import * as AWS from 'aws-sdk'
|
||||
import {
|
||||
AnalyticsStoreInterface,
|
||||
PeriodKeyGenerator,
|
||||
PeriodKeyGeneratorInterface,
|
||||
RedisAnalyticsStore,
|
||||
RedisStatisticsStore,
|
||||
StatisticsStoreInterface,
|
||||
@@ -91,13 +92,13 @@ export class ContainerConfigLoader {
|
||||
|
||||
// Services
|
||||
container.bind<HttpServiceInterface>(TYPES.HTTPService).to(HttpService)
|
||||
const periodKeyGenerator = new PeriodKeyGenerator()
|
||||
container.bind<PeriodKeyGeneratorInterface>(TYPES.PeriodKeyGenerator).toConstantValue(new PeriodKeyGenerator())
|
||||
container
|
||||
.bind<AnalyticsStoreInterface>(TYPES.AnalyticsStore)
|
||||
.toConstantValue(new RedisAnalyticsStore(periodKeyGenerator, container.get(TYPES.Redis)))
|
||||
.toConstantValue(new RedisAnalyticsStore(container.get(TYPES.PeriodKeyGenerator), container.get(TYPES.Redis)))
|
||||
container
|
||||
.bind<StatisticsStoreInterface>(TYPES.StatisticsStore)
|
||||
.toConstantValue(new RedisStatisticsStore(periodKeyGenerator, container.get(TYPES.Redis)))
|
||||
.toConstantValue(new RedisStatisticsStore(container.get(TYPES.PeriodKeyGenerator), container.get(TYPES.Redis)))
|
||||
container.bind<CrossServiceTokenCacheInterface>(TYPES.CrossServiceTokenCache).to(RedisCrossServiceTokenCache)
|
||||
container.bind<TimerInterface>(TYPES.Timer).toConstantValue(new Timer())
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ const TYPES = {
|
||||
StatisticsStore: Symbol.for('StatisticsStore'),
|
||||
DomainEventPublisher: Symbol.for('DomainEventPublisher'),
|
||||
Timer: Symbol.for('Timer'),
|
||||
PeriodKeyGenerator: Symbol.for('PeriodKeyGenerator'),
|
||||
}
|
||||
|
||||
export default TYPES
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { CrossServiceTokenData } from '@standardnotes/security'
|
||||
import { RoleName } from '@standardnotes/common'
|
||||
import { AnalyticsActivity, AnalyticsStoreInterface, Period } from '@standardnotes/analytics'
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
import { NextFunction, Request, Response } from 'express'
|
||||
@@ -75,9 +76,20 @@ export class AuthMiddleware extends BaseMiddleware {
|
||||
|
||||
const decodedToken = <CrossServiceTokenData>verify(crossServiceToken, this.jwtSecret, { algorithms: ['HS256'] })
|
||||
|
||||
await this.analyticsStore.markActivity([AnalyticsActivity.GeneralActivity], decodedToken.analyticsId as number, [
|
||||
Period.Today,
|
||||
])
|
||||
response.locals.freeUser =
|
||||
decodedToken.roles.length === 1 &&
|
||||
decodedToken.roles.find((role) => role.name === RoleName.CoreUser) !== undefined
|
||||
|
||||
await this.analyticsStore.markActivity(
|
||||
[
|
||||
AnalyticsActivity.GeneralActivity,
|
||||
response.locals.freeUser
|
||||
? AnalyticsActivity.GeneralActivityFreeUsers
|
||||
: AnalyticsActivity.GeneralActivityPaidUsers,
|
||||
],
|
||||
decodedToken.analyticsId as number,
|
||||
[Period.Today],
|
||||
)
|
||||
|
||||
if (this.crossServiceTokenCacheTTL && !crossServiceTokenFetchedFromCache) {
|
||||
await this.crossServiceTokenCache.set({
|
||||
|
||||
@@ -15,6 +15,11 @@ export class WebSocketsController extends BaseHttpController {
|
||||
super()
|
||||
}
|
||||
|
||||
@httpPost('/tokens', TYPES.AuthMiddleware)
|
||||
async createWebSocketConnectionToken(request: Request, response: Response): Promise<void> {
|
||||
await this.httpService.callAuthServer(request, response, 'sockets/tokens', request.body)
|
||||
}
|
||||
|
||||
@httpPost('/', TYPES.AuthMiddleware)
|
||||
async createWebSocketConnection(request: Request, response: Response): Promise<void> {
|
||||
if (!request.headers.connectionid) {
|
||||
|
||||
@@ -66,5 +66,8 @@ SENTRY_ENVIRONMENT=
|
||||
VALET_TOKEN_SECRET=
|
||||
VALET_TOKEN_TTL=
|
||||
|
||||
WEB_SOCKET_CONNECTION_TOKEN_SECRET=
|
||||
WEB_SOCKET_CONNECTION_TOKEN_TTL=
|
||||
|
||||
# (Optional) Analytics
|
||||
ANALYTICS_ENABLED=false
|
||||
|
||||
@@ -3,6 +3,202 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [1.30.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.29.1...@standardnotes/auth-server@1.30.0) (2022-09-21)
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** add creating web socket connection tokens ([8033177](https://github.com/standardnotes/server/commit/8033177f48dc961194f24fb7daa1073b8b697b74))
|
||||
|
||||
## [1.29.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.29.0...@standardnotes/auth-server@1.29.1) (2022-09-19)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** uuid validator binding ([db6f966](https://github.com/standardnotes/server/commit/db6f966045d51e59555740c9e009bf66b629673c))
|
||||
|
||||
# [1.29.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.28.4...@standardnotes/auth-server@1.29.0) (2022-09-19)
|
||||
|
||||
### Features
|
||||
|
||||
* **files:** add validating remote identifiers ([db15457](https://github.com/standardnotes/server/commit/db15457ce4eb533ec822cf93c3ed83eafe9e64d5))
|
||||
|
||||
## [1.28.4](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.28.3...@standardnotes/auth-server@1.28.4) (2022-09-16)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** feature service spec ([c207c3f](https://github.com/standardnotes/server/commit/c207c3fc8442eec9b8c3150f09ecccfdd6a5ed50))
|
||||
|
||||
## [1.28.3](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.28.2...@standardnotes/auth-server@1.28.3) (2022-09-16)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** change remaining subscription time stats to percentage ([5eb957c](https://github.com/standardnotes/server/commit/5eb957c82a8cc5fdcb6815e2cd30e49cd2b1e8ac))
|
||||
|
||||
## [1.28.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.28.1...@standardnotes/auth-server@1.28.2) (2022-09-16)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **files:** add verifying permitted operation on valet token ([377d32c](https://github.com/standardnotes/server/commit/377d32c4498305f0f59ff59e7357f0d2f10ce3a2))
|
||||
|
||||
## [1.28.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.28.0...@standardnotes/auth-server@1.28.1) (2022-09-15)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** missing injectable annotation ([d851524](https://github.com/standardnotes/server/commit/d85152429ca379d3d0314a9864cc46ebee541958))
|
||||
|
||||
# [1.28.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.27.0...@standardnotes/auth-server@1.28.0) (2022-09-15)
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** add remaining subscription time stats ([a59ba08](https://github.com/standardnotes/server/commit/a59ba083397c75960af0e8a102b617bf5baa287f))
|
||||
|
||||
# [1.27.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.26.1...@standardnotes/auth-server@1.27.0) (2022-09-15)
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** implement subscription server interface on server side ([5d812be](https://github.com/standardnotes/server/commit/5d812befc4733954919eef0d3718ae6f8eb81654))
|
||||
|
||||
## [1.26.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.26.0...@standardnotes/auth-server@1.26.1) (2022-09-15)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** disallow duplicating subscription invites ([531f13f](https://github.com/standardnotes/server/commit/531f13fe1f4bdfb8d27f5e3c07ec0b15d36ad413))
|
||||
|
||||
# [1.26.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.25.13...@standardnotes/auth-server@1.26.0) (2022-09-13)
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** add subscription sharing permission ([f45320e](https://github.com/standardnotes/server/commit/f45320e5ed8948a432029586c05284f4d640de5b))
|
||||
|
||||
## [1.25.13](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.25.12...@standardnotes/auth-server@1.25.13) (2022-09-12)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** add debug logs for canceling shared subscription invitations ([dd13e2e](https://github.com/standardnotes/server/commit/dd13e2eaf74de56a3c8c30c236c32c6dc0c560f2))
|
||||
|
||||
## [1.25.12](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.25.11...@standardnotes/auth-server@1.25.12) (2022-09-12)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** allow canceling shared subscription invitation before it was accepted ([0dab31f](https://github.com/standardnotes/server/commit/0dab31f9936bfd5081a87eef9701a268b8dec88c))
|
||||
|
||||
## [1.25.11](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.25.10...@standardnotes/auth-server@1.25.11) (2022-09-09)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/auth-server
|
||||
|
||||
## [1.25.10](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.25.9...@standardnotes/auth-server@1.25.10) (2022-09-09)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/auth-server
|
||||
|
||||
## [1.25.9](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.25.8...@standardnotes/auth-server@1.25.9) (2022-09-09)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/auth-server
|
||||
|
||||
## [1.25.8](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.25.7...@standardnotes/auth-server@1.25.8) (2022-09-09)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/auth-server
|
||||
|
||||
## [1.25.7](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.25.6...@standardnotes/auth-server@1.25.7) (2022-09-08)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/auth-server
|
||||
|
||||
## [1.25.6](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.25.5...@standardnotes/auth-server@1.25.6) (2022-09-08)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/auth-server
|
||||
|
||||
## [1.25.5](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.25.4...@standardnotes/auth-server@1.25.5) (2022-09-08)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/auth-server
|
||||
|
||||
## [1.25.4](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.25.3...@standardnotes/auth-server@1.25.4) (2022-09-08)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/auth-server
|
||||
|
||||
## [1.25.3](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.25.2...@standardnotes/auth-server@1.25.3) (2022-09-08)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/auth-server
|
||||
|
||||
## [1.25.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.25.1...@standardnotes/auth-server@1.25.2) (2022-09-07)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/auth-server
|
||||
|
||||
## [1.25.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.25.0...@standardnotes/auth-server@1.25.1) (2022-09-07)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/auth-server
|
||||
|
||||
# [1.25.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.24.4...@standardnotes/auth-server@1.25.0) (2022-09-07)
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** add measuring registration to subscription time statistics ([b618252](https://github.com/standardnotes/server/commit/b61825235eebaf5eddb55cbda173176ca43c0099))
|
||||
|
||||
## [1.24.4](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.24.3...@standardnotes/auth-server@1.24.4) (2022-09-07)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** forbid users on shared subscription to send out invitations ([132b617](https://github.com/standardnotes/server/commit/132b617aaa8a703877fd7e8d23711fb1ec234524))
|
||||
|
||||
## [1.24.3](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.24.2...@standardnotes/auth-server@1.24.3) (2022-09-06)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/auth-server
|
||||
|
||||
## [1.24.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.24.1...@standardnotes/auth-server@1.24.2) (2022-09-06)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/auth-server
|
||||
|
||||
## [1.24.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.24.0...@standardnotes/auth-server@1.24.1) (2022-09-06)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/auth-server
|
||||
|
||||
# [1.24.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.23.2...@standardnotes/auth-server@1.24.0) (2022-09-06)
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** add statistics for refunds and account deletions ([d7ae2f0](https://github.com/standardnotes/server/commit/d7ae2f06255b19eb5d3403a4989610390064754e))
|
||||
|
||||
## [1.23.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.23.1...@standardnotes/auth-server@1.23.2) (2022-09-06)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** add debug logs for subscription canceling ([2ca430f](https://github.com/standardnotes/server/commit/2ca430f40ce6a8d56aafa27e9c2d0b0dd561c650))
|
||||
|
||||
## [1.23.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.23.0...@standardnotes/auth-server@1.23.1) (2022-09-06)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/auth-server
|
||||
|
||||
# [1.23.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.22.1...@standardnotes/auth-server@1.23.0) (2022-09-05)
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** add measuring subscription length ([fa10827](https://github.com/standardnotes/server/commit/fa108274430d8dff1016ddcba5bbcb2778eb781b))
|
||||
|
||||
## [1.22.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.22.0...@standardnotes/auth-server@1.22.1) (2022-09-05)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** StatisticsStore binding ([34315c9](https://github.com/standardnotes/server/commit/34315c91d7428bbe8297e50972aa7823e2a983b2))
|
||||
|
||||
# [1.22.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.21.5...@standardnotes/auth-server@1.22.0) (2022-09-05)
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** add keeping stats on payments ([0c176b7](https://github.com/standardnotes/server/commit/0c176b70f8281e1e490224b9c7ab85f272a3d4e9))
|
||||
|
||||
## [1.21.5](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.21.4...@standardnotes/auth-server@1.21.5) (2022-09-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/auth-server
|
||||
|
||||
## [1.21.4](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.21.3...@standardnotes/auth-server@1.21.4) (2022-09-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/auth-server
|
||||
|
||||
## [1.21.3](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.21.2...@standardnotes/auth-server@1.21.3) (2022-09-01)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/auth-server
|
||||
|
||||
## [1.21.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.21.1...@standardnotes/auth-server@1.21.2) (2022-09-01)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/auth-server
|
||||
|
||||
## [1.21.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.21.0...@standardnotes/auth-server@1.21.1) (2022-08-30)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/auth-server
|
||||
|
||||
@@ -10,17 +10,17 @@ import '../src/Controller/SessionsController'
|
||||
import '../src/Controller/UsersController'
|
||||
import '../src/Controller/SettingsController'
|
||||
import '../src/Controller/FeaturesController'
|
||||
import '../src/Controller/WebSocketsController'
|
||||
import '../src/Controller/AdminController'
|
||||
import '../src/Controller/InternalController'
|
||||
import '../src/Controller/SubscriptionTokensController'
|
||||
import '../src/Controller/OfflineController'
|
||||
import '../src/Controller/ValetTokenController'
|
||||
import '../src/Controller/ListedController'
|
||||
import '../src/Controller/SubscriptionInvitesController'
|
||||
import '../src/Controller/SubscriptionSettingsController'
|
||||
|
||||
import '../src/Infra/InversifyExpressUtils/InversifyExpressAuthController'
|
||||
import '../src/Infra/InversifyExpressUtils/InversifyExpressSubscriptionInvitesController'
|
||||
import '../src/Infra/InversifyExpressUtils/InversifyExpressWebSocketsController'
|
||||
|
||||
import * as cors from 'cors'
|
||||
import { urlencoded, json, Request, Response, NextFunction, RequestHandler, ErrorRequestHandler } from 'express'
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm'
|
||||
|
||||
export class addSubscriptionSharingPermission1663073954000 implements MigrationInterface {
|
||||
name = 'addSubscriptionSharingPermission1663073954000'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
'INSERT INTO `permissions` (uuid, name) VALUES ("3aeaf12e-380f-4f21-97b9-d862d63874f6", "server:subscription-sharing")',
|
||||
)
|
||||
|
||||
// Pro User Permissions
|
||||
await queryRunner.query(
|
||||
'INSERT INTO `role_permissions` (role_uuid, permission_uuid) VALUES \
|
||||
("8047edbb-a10a-4ff8-8d53-c2cae600a8e8", "3aeaf12e-380f-4f21-97b9-d862d63874f6") \
|
||||
',
|
||||
)
|
||||
}
|
||||
|
||||
public async down(): Promise<void> {
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm'
|
||||
|
||||
export class addRenewedAtColumn1663321030000 implements MigrationInterface {
|
||||
name = 'addRenewedAtColumn1663321030000'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query('ALTER TABLE `user_subscriptions` ADD `renewed_at` bigint NULL')
|
||||
}
|
||||
|
||||
public async down(): Promise<void> {
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/auth-server",
|
||||
"version": "1.21.1",
|
||||
"version": "1.30.0",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
@@ -34,7 +34,7 @@
|
||||
"@newrelic/winston-enricher": "^4.0.0",
|
||||
"@sentry/node": "^7.3.0",
|
||||
"@standardnotes/analytics": "workspace:*",
|
||||
"@standardnotes/api": "^1.1.19",
|
||||
"@standardnotes/api": "^1.8.1",
|
||||
"@standardnotes/common": "workspace:*",
|
||||
"@standardnotes/domain-events": "workspace:*",
|
||||
"@standardnotes/domain-events-infra": "workspace:*",
|
||||
@@ -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'
|
||||
@@ -124,7 +130,14 @@ import { RedisOfflineSubscriptionTokenRepository } from '../Infra/Redis/RedisOff
|
||||
import { CreateOfflineSubscriptionToken } from '../Domain/UseCase/CreateOfflineSubscriptionToken/CreateOfflineSubscriptionToken'
|
||||
import { AuthenticateOfflineSubscriptionToken } from '../Domain/UseCase/AuthenticateOfflineSubscriptionToken/AuthenticateOfflineSubscriptionToken'
|
||||
import { SubscriptionCancelledEventHandler } from '../Domain/Handler/SubscriptionCancelledEventHandler'
|
||||
import { ContentDecoder, ContentDecoderInterface, ProtocolVersion } from '@standardnotes/common'
|
||||
import {
|
||||
ContentDecoder,
|
||||
ContentDecoderInterface,
|
||||
ProtocolVersion,
|
||||
Uuid,
|
||||
UuidValidator,
|
||||
ValidatorInterface,
|
||||
} from '@standardnotes/common'
|
||||
import { GetUserOfflineSubscription } from '../Domain/UseCase/GetUserOfflineSubscription/GetUserOfflineSubscription'
|
||||
import { ApiGatewayOfflineAuthMiddleware } from '../Controller/ApiGatewayOfflineAuthMiddleware'
|
||||
import { UserEmailChangedEventHandler } from '../Domain/Handler/UserEmailChangedEventHandler'
|
||||
@@ -143,6 +156,7 @@ import {
|
||||
TokenEncoder,
|
||||
TokenEncoderInterface,
|
||||
ValetTokenData,
|
||||
WebSocketConnectionTokenData,
|
||||
} from '@standardnotes/security'
|
||||
import { FileUploadedEventHandler } from '../Domain/Handler/FileUploadedEventHandler'
|
||||
import { CreateValetToken } from '../Domain/UseCase/CreateValetToken/CreateValetToken'
|
||||
@@ -193,6 +207,11 @@ import { PredicateVerificationRequestedEventHandler } from '../Domain/Handler/Pr
|
||||
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'
|
||||
import { SubscriptionInvitesController } from '../Controller/SubscriptionInvitesController'
|
||||
import { CreateWebSocketConnectionToken } from '../Domain/UseCase/CreateWebSocketConnectionToken/CreateWebSocketConnectionToken'
|
||||
import { WebSocketsController } from '../Controller/WebSocketsController'
|
||||
import { WebSocketServerInterface } from '@standardnotes/api'
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const newrelicFormatter = require('@newrelic/winston-enricher')
|
||||
@@ -255,6 +274,8 @@ export class ContainerConfigLoader {
|
||||
|
||||
// Controller
|
||||
container.bind<AuthController>(TYPES.AuthController).to(AuthController)
|
||||
container.bind<SubscriptionInvitesController>(TYPES.SubscriptionInvitesController).to(SubscriptionInvitesController)
|
||||
container.bind<WebSocketServerInterface>(TYPES.WebSocketsController).to(WebSocketsController)
|
||||
|
||||
// Repositories
|
||||
container.bind<SessionRepositoryInterface>(TYPES.SessionRepository).to(MySQLSessionRepository)
|
||||
@@ -353,6 +374,12 @@ export class ContainerConfigLoader {
|
||||
container.bind(TYPES.AUTH_JWT_TTL).toConstantValue(+env.get('AUTH_JWT_TTL'))
|
||||
container.bind(TYPES.VALET_TOKEN_SECRET).toConstantValue(env.get('VALET_TOKEN_SECRET', true))
|
||||
container.bind(TYPES.VALET_TOKEN_TTL).toConstantValue(+env.get('VALET_TOKEN_TTL', true))
|
||||
container
|
||||
.bind(TYPES.WEB_SOCKET_CONNECTION_TOKEN_SECRET)
|
||||
.toConstantValue(env.get('WEB_SOCKET_CONNECTION_TOKEN_SECRET', true))
|
||||
container
|
||||
.bind(TYPES.WEB_SOCKET_CONNECTION_TOKEN_TTL)
|
||||
.toConstantValue(+env.get('WEB_SOCKET_CONNECTION_TOKEN_TTL', true))
|
||||
container.bind(TYPES.ENCRYPTION_SERVER_KEY).toConstantValue(env.get('ENCRYPTION_SERVER_KEY'))
|
||||
container.bind(TYPES.ACCESS_TOKEN_AGE).toConstantValue(env.get('ACCESS_TOKEN_AGE'))
|
||||
container.bind(TYPES.REFRESH_TOKEN_AGE).toConstantValue(env.get('REFRESH_TOKEN_AGE'))
|
||||
@@ -432,6 +459,9 @@ export class ContainerConfigLoader {
|
||||
container.bind<GetSubscriptionSetting>(TYPES.GetSubscriptionSetting).to(GetSubscriptionSetting)
|
||||
container.bind<GetUserAnalyticsId>(TYPES.GetUserAnalyticsId).to(GetUserAnalyticsId)
|
||||
container.bind<VerifyPredicate>(TYPES.VerifyPredicate).to(VerifyPredicate)
|
||||
container
|
||||
.bind<CreateWebSocketConnectionToken>(TYPES.CreateWebSocketConnectionToken)
|
||||
.to(CreateWebSocketConnectionToken)
|
||||
|
||||
// Handlers
|
||||
container.bind<UserRegisteredEventHandler>(TYPES.UserRegisteredEventHandler).to(UserRegisteredEventHandler)
|
||||
@@ -482,6 +512,7 @@ export class ContainerConfigLoader {
|
||||
.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())
|
||||
@@ -515,6 +546,11 @@ export class ContainerConfigLoader {
|
||||
container
|
||||
.bind<TokenEncoderInterface<ValetTokenData>>(TYPES.ValetTokenEncoder)
|
||||
.toConstantValue(new TokenEncoder<ValetTokenData>(container.get(TYPES.VALET_TOKEN_SECRET)))
|
||||
container
|
||||
.bind<TokenEncoderInterface<WebSocketConnectionTokenData>>(TYPES.WebSocketConnectionTokenEncoder)
|
||||
.toConstantValue(
|
||||
new TokenEncoder<WebSocketConnectionTokenData>(container.get(TYPES.WEB_SOCKET_CONNECTION_TOKEN_SECRET)),
|
||||
)
|
||||
container.bind<AuthenticationMethodResolver>(TYPES.AuthenticationMethodResolver).to(AuthenticationMethodResolver)
|
||||
container.bind<DomainEventFactory>(TYPES.DomainEventFactory).to(DomainEventFactory)
|
||||
container.bind<AxiosInstance>(TYPES.HTTPClient).toConstantValue(axios.create())
|
||||
@@ -542,9 +578,14 @@ 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)))
|
||||
container.bind<ValidatorInterface<Uuid>>(TYPES.UuidValidator).toConstantValue(new UuidValidator())
|
||||
|
||||
if (env.get('SNS_TOPIC_ARN', true)) {
|
||||
container
|
||||
@@ -582,6 +623,7 @@ export class ContainerConfigLoader {
|
||||
['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)) {
|
||||
|
||||
@@ -5,6 +5,8 @@ const TYPES = {
|
||||
SQS: Symbol.for('SQS'),
|
||||
// Controller
|
||||
AuthController: Symbol.for('AuthController'),
|
||||
SubscriptionInvitesController: Symbol.for('SubscriptionInvitesController'),
|
||||
WebSocketsController: Symbol.for('WebSocketsController'),
|
||||
// Repositories
|
||||
UserRepository: Symbol.for('UserRepository'),
|
||||
SessionRepository: Symbol.for('SessionRepository'),
|
||||
@@ -59,6 +61,8 @@ const TYPES = {
|
||||
AUTH_JWT_TTL: Symbol.for('AUTH_JWT_TTL'),
|
||||
VALET_TOKEN_SECRET: Symbol.for('VALET_TOKEN_SECRET'),
|
||||
VALET_TOKEN_TTL: Symbol.for('VALET_TOKEN_TTL'),
|
||||
WEB_SOCKET_CONNECTION_TOKEN_SECRET: Symbol.for('WEB_SOCKET_CONNECTION_TOKEN_SECRET'),
|
||||
WEB_SOCKET_CONNECTION_TOKEN_TTL: Symbol.for('WEB_SOCKET_CONNECTION_TOKEN_TTL'),
|
||||
ENCRYPTION_SERVER_KEY: Symbol.for('ENCRYPTION_SERVER_KEY'),
|
||||
ACCESS_TOKEN_AGE: Symbol.for('ACCESS_TOKEN_AGE'),
|
||||
REFRESH_TOKEN_AGE: Symbol.for('REFRESH_TOKEN_AGE'),
|
||||
@@ -124,6 +128,7 @@ const TYPES = {
|
||||
GetSubscriptionSetting: Symbol.for('GetSubscriptionSetting'),
|
||||
GetUserAnalyticsId: Symbol.for('GetUserAnalyticsId'),
|
||||
VerifyPredicate: Symbol.for('VerifyPredicate'),
|
||||
CreateWebSocketConnectionToken: Symbol.for('CreateWebSocketConnectionToken'),
|
||||
// Handlers
|
||||
UserRegisteredEventHandler: Symbol.for('UserRegisteredEventHandler'),
|
||||
AccountDeletionRequestedEventHandler: Symbol.for('AccountDeletionRequestedEventHandler'),
|
||||
@@ -145,6 +150,7 @@ const TYPES = {
|
||||
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'),
|
||||
@@ -164,6 +170,7 @@ const TYPES = {
|
||||
CrossServiceTokenEncoder: Symbol.for('CrossServiceTokenEncoder'),
|
||||
SessionTokenEncoder: Symbol.for('SessionTokenEncoder'),
|
||||
ValetTokenEncoder: Symbol.for('ValetTokenEncoder'),
|
||||
WebSocketConnectionTokenEncoder: Symbol.for('WebSocketConnectionTokenEncoder'),
|
||||
AuthenticationMethodResolver: Symbol.for('AuthenticationMethodResolver'),
|
||||
DomainEventPublisher: Symbol.for('DomainEventPublisher'),
|
||||
DomainEventSubscriberFactory: Symbol.for('DomainEventSubscriberFactory'),
|
||||
@@ -186,6 +193,8 @@ const TYPES = {
|
||||
BooleanSelector: Symbol.for('BooleanSelector'),
|
||||
UserSubscriptionService: Symbol.for('UserSubscriptionService'),
|
||||
AnalyticsStore: Symbol.for('AnalyticsStore'),
|
||||
StatisticsStore: Symbol.for('StatisticsStore'),
|
||||
UuidValidator: Symbol.for('UuidValidator'),
|
||||
}
|
||||
|
||||
export default TYPES
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
import 'reflect-metadata'
|
||||
|
||||
import * as express from 'express'
|
||||
|
||||
import { SubscriptionInvitesController } from './SubscriptionInvitesController'
|
||||
import { results } from 'inversify-express-utils'
|
||||
import { User } from '../Domain/User/User'
|
||||
import { InviteToSharedSubscription } from '../Domain/UseCase/InviteToSharedSubscription/InviteToSharedSubscription'
|
||||
import { AcceptSharedSubscriptionInvitation } from '../Domain/UseCase/AcceptSharedSubscriptionInvitation/AcceptSharedSubscriptionInvitation'
|
||||
import { DeclineSharedSubscriptionInvitation } from '../Domain/UseCase/DeclineSharedSubscriptionInvitation/DeclineSharedSubscriptionInvitation'
|
||||
import { CancelSharedSubscriptionInvitation } from '../Domain/UseCase/CancelSharedSubscriptionInvitation/CancelSharedSubscriptionInvitation'
|
||||
import { RoleName } from '@standardnotes/common'
|
||||
import { ListSharedSubscriptionInvitations } from '../Domain/UseCase/ListSharedSubscriptionInvitations/ListSharedSubscriptionInvitations'
|
||||
import { ApiVersion } from '@standardnotes/api'
|
||||
|
||||
describe('SubscriptionInvitesController', () => {
|
||||
let inviteToSharedSubscription: InviteToSharedSubscription
|
||||
@@ -19,8 +16,6 @@ describe('SubscriptionInvitesController', () => {
|
||||
let cancelSharedSubscriptionInvitation: CancelSharedSubscriptionInvitation
|
||||
let listSharedSubscriptionInvitations: ListSharedSubscriptionInvitations
|
||||
|
||||
let request: express.Request
|
||||
let response: express.Response
|
||||
let user: User
|
||||
|
||||
const createController = () =>
|
||||
@@ -51,25 +46,6 @@ describe('SubscriptionInvitesController', () => {
|
||||
|
||||
listSharedSubscriptionInvitations = {} as jest.Mocked<ListSharedSubscriptionInvitations>
|
||||
listSharedSubscriptionInvitations.execute = jest.fn()
|
||||
|
||||
request = {
|
||||
headers: {},
|
||||
body: {},
|
||||
params: {},
|
||||
} as jest.Mocked<express.Request>
|
||||
|
||||
response = {
|
||||
locals: {},
|
||||
} as jest.Mocked<express.Response>
|
||||
response.locals.user = {
|
||||
email: 'test@test.te',
|
||||
}
|
||||
response.locals.roles = [
|
||||
{
|
||||
uuid: '1-2-3',
|
||||
name: RoleName.CoreUser,
|
||||
},
|
||||
]
|
||||
})
|
||||
|
||||
it('should get invitations to subscription sharing', async () => {
|
||||
@@ -77,128 +53,127 @@ describe('SubscriptionInvitesController', () => {
|
||||
invitations: [],
|
||||
})
|
||||
|
||||
const httpResponse = <results.JsonResult>await createController().listInvites(request, response)
|
||||
const result = await httpResponse.executeAsync()
|
||||
const result = await createController().listInvites({ api: ApiVersion.v0, inviterEmail: 'test@test.te' })
|
||||
|
||||
expect(listSharedSubscriptionInvitations.execute).toHaveBeenCalledWith({
|
||||
inviterEmail: 'test@test.te',
|
||||
})
|
||||
|
||||
expect(result.statusCode).toEqual(200)
|
||||
expect(result.status).toEqual(200)
|
||||
})
|
||||
|
||||
it('should cancel invitation to subscription sharing', async () => {
|
||||
request.params.inviteUuid = '1-2-3'
|
||||
|
||||
cancelSharedSubscriptionInvitation.execute = jest.fn().mockReturnValue({
|
||||
success: true,
|
||||
})
|
||||
|
||||
const httpResponse = <results.JsonResult>await createController().cancelSubscriptionSharing(request, response)
|
||||
const result = await httpResponse.executeAsync()
|
||||
const result = await createController().cancelInvite({
|
||||
api: ApiVersion.v0,
|
||||
inviteUuid: '1-2-3',
|
||||
inviterEmail: 'test@test.te',
|
||||
})
|
||||
|
||||
expect(cancelSharedSubscriptionInvitation.execute).toHaveBeenCalledWith({
|
||||
sharedSubscriptionInvitationUuid: '1-2-3',
|
||||
inviterEmail: 'test@test.te',
|
||||
})
|
||||
|
||||
expect(result.statusCode).toEqual(200)
|
||||
expect(result.status).toEqual(200)
|
||||
})
|
||||
|
||||
it('should not cancel invitation to subscription sharing if the workflow fails', async () => {
|
||||
request.params.inviteUuid = '1-2-3'
|
||||
|
||||
cancelSharedSubscriptionInvitation.execute = jest.fn().mockReturnValue({
|
||||
success: false,
|
||||
})
|
||||
|
||||
const httpResponse = <results.JsonResult>await createController().cancelSubscriptionSharing(request, response)
|
||||
const result = await httpResponse.executeAsync()
|
||||
const result = await createController().cancelInvite({
|
||||
api: ApiVersion.v0,
|
||||
inviteUuid: '1-2-3',
|
||||
})
|
||||
|
||||
expect(result.statusCode).toEqual(400)
|
||||
expect(result.status).toEqual(400)
|
||||
})
|
||||
|
||||
it('should decline invitation to subscription sharing', async () => {
|
||||
request.params.inviteUuid = '1-2-3'
|
||||
|
||||
declineSharedSubscriptionInvitation.execute = jest.fn().mockReturnValue({
|
||||
success: true,
|
||||
})
|
||||
|
||||
const httpResponse = <results.JsonResult>await createController().declineInvite(request)
|
||||
const result = await httpResponse.executeAsync()
|
||||
const result = await createController().declineInvite({
|
||||
api: ApiVersion.v0,
|
||||
inviteUuid: '1-2-3',
|
||||
})
|
||||
|
||||
expect(declineSharedSubscriptionInvitation.execute).toHaveBeenCalledWith({
|
||||
sharedSubscriptionInvitationUuid: '1-2-3',
|
||||
})
|
||||
|
||||
expect(result.statusCode).toEqual(200)
|
||||
expect(result.status).toEqual(200)
|
||||
})
|
||||
|
||||
it('should not decline invitation to subscription sharing if the workflow fails', async () => {
|
||||
request.params.inviteUuid = '1-2-3'
|
||||
|
||||
declineSharedSubscriptionInvitation.execute = jest.fn().mockReturnValue({
|
||||
success: false,
|
||||
})
|
||||
|
||||
const httpResponse = <results.JsonResult>await createController().declineInvite(request)
|
||||
const result = await httpResponse.executeAsync()
|
||||
const result = await createController().declineInvite({
|
||||
api: ApiVersion.v0,
|
||||
inviteUuid: '1-2-3',
|
||||
})
|
||||
|
||||
expect(declineSharedSubscriptionInvitation.execute).toHaveBeenCalledWith({
|
||||
sharedSubscriptionInvitationUuid: '1-2-3',
|
||||
})
|
||||
|
||||
expect(result.statusCode).toEqual(400)
|
||||
expect(result.status).toEqual(400)
|
||||
})
|
||||
|
||||
it('should accept invitation to subscription sharing', async () => {
|
||||
request.params.inviteUuid = '1-2-3'
|
||||
|
||||
acceptSharedSubscriptionInvitation.execute = jest.fn().mockReturnValue({
|
||||
success: true,
|
||||
})
|
||||
|
||||
const httpResponse = <results.JsonResult>await createController().acceptInvite(request)
|
||||
const result = await httpResponse.executeAsync()
|
||||
const result = await createController().acceptInvite({
|
||||
api: ApiVersion.v0,
|
||||
inviteUuid: '1-2-3',
|
||||
})
|
||||
|
||||
expect(acceptSharedSubscriptionInvitation.execute).toHaveBeenCalledWith({
|
||||
sharedSubscriptionInvitationUuid: '1-2-3',
|
||||
})
|
||||
|
||||
expect(result.statusCode).toEqual(200)
|
||||
expect(result.status).toEqual(200)
|
||||
})
|
||||
|
||||
it('should not accept invitation to subscription sharing if the workflow fails', async () => {
|
||||
request.params.inviteUuid = '1-2-3'
|
||||
|
||||
acceptSharedSubscriptionInvitation.execute = jest.fn().mockReturnValue({
|
||||
success: false,
|
||||
})
|
||||
|
||||
const httpResponse = <results.JsonResult>await createController().acceptInvite(request)
|
||||
const result = await httpResponse.executeAsync()
|
||||
const result = await createController().acceptInvite({
|
||||
api: ApiVersion.v0,
|
||||
inviteUuid: '1-2-3',
|
||||
})
|
||||
|
||||
expect(acceptSharedSubscriptionInvitation.execute).toHaveBeenCalledWith({
|
||||
sharedSubscriptionInvitationUuid: '1-2-3',
|
||||
})
|
||||
|
||||
expect(result.statusCode).toEqual(400)
|
||||
expect(result.status).toEqual(400)
|
||||
})
|
||||
|
||||
it('should invite to user subscription', async () => {
|
||||
request.body.identifier = 'invitee@test.te'
|
||||
response.locals.user = {
|
||||
uuid: '1-2-3',
|
||||
email: 'test@test.te',
|
||||
}
|
||||
|
||||
inviteToSharedSubscription.execute = jest.fn().mockReturnValue({
|
||||
success: true,
|
||||
})
|
||||
|
||||
const httpResponse = <results.JsonResult>await createController().inviteToSubscriptionSharing(request, response)
|
||||
const result = await httpResponse.executeAsync()
|
||||
const result = await createController().invite({
|
||||
api: ApiVersion.v0,
|
||||
identifier: 'invitee@test.te',
|
||||
inviterUuid: '1-2-3',
|
||||
inviterEmail: 'test@test.te',
|
||||
inviterRoles: ['CORE_USER'],
|
||||
})
|
||||
|
||||
expect(inviteToSharedSubscription.execute).toHaveBeenCalledWith({
|
||||
inviterEmail: 'test@test.te',
|
||||
@@ -207,37 +182,36 @@ describe('SubscriptionInvitesController', () => {
|
||||
inviterRoles: ['CORE_USER'],
|
||||
})
|
||||
|
||||
expect(result.statusCode).toEqual(200)
|
||||
expect(result.status).toEqual(200)
|
||||
})
|
||||
|
||||
it('should not invite to user subscription if the identifier is missing in request', async () => {
|
||||
response.locals.user = {
|
||||
uuid: '1-2-3',
|
||||
email: 'test@test.te',
|
||||
}
|
||||
|
||||
const httpResponse = <results.JsonResult>await createController().inviteToSubscriptionSharing(request, response)
|
||||
const result = await httpResponse.executeAsync()
|
||||
const result = await createController().invite({
|
||||
api: ApiVersion.v0,
|
||||
identifier: '',
|
||||
inviterUuid: '1-2-3',
|
||||
inviterEmail: 'test@test.te',
|
||||
inviterRoles: ['CORE_USER'],
|
||||
})
|
||||
|
||||
expect(inviteToSharedSubscription.execute).not.toHaveBeenCalled()
|
||||
|
||||
expect(result.statusCode).toEqual(400)
|
||||
expect(result.status).toEqual(400)
|
||||
})
|
||||
|
||||
it('should not invite to user subscription if the workflow does not run', async () => {
|
||||
request.body.identifier = 'invitee@test.te'
|
||||
response.locals.user = {
|
||||
uuid: '1-2-3',
|
||||
email: 'test@test.te',
|
||||
}
|
||||
|
||||
inviteToSharedSubscription.execute = jest.fn().mockReturnValue({
|
||||
success: false,
|
||||
})
|
||||
|
||||
const httpResponse = <results.JsonResult>await createController().inviteToSubscriptionSharing(request, response)
|
||||
const result = await httpResponse.executeAsync()
|
||||
const result = await createController().invite({
|
||||
api: ApiVersion.v0,
|
||||
identifier: 'invitee@test.te',
|
||||
inviterUuid: '1-2-3',
|
||||
inviterEmail: 'test@test.te',
|
||||
inviterRoles: ['CORE_USER'],
|
||||
})
|
||||
|
||||
expect(result.statusCode).toEqual(400)
|
||||
expect(result.status).toEqual(400)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
import { Role } from '@standardnotes/security'
|
||||
import { Request, Response } from 'express'
|
||||
import { inject } from 'inversify'
|
||||
import {
|
||||
BaseHttpController,
|
||||
controller,
|
||||
httpDelete,
|
||||
httpGet,
|
||||
httpPost,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
results,
|
||||
} from 'inversify-express-utils'
|
||||
HttpStatusCode,
|
||||
SubscriptionInviteAcceptRequestParams,
|
||||
SubscriptionInviteAcceptResponse,
|
||||
SubscriptionInviteCancelRequestParams,
|
||||
SubscriptionInviteCancelResponse,
|
||||
SubscriptionInviteDeclineRequestParams,
|
||||
SubscriptionInviteDeclineResponse,
|
||||
SubscriptionInviteListRequestParams,
|
||||
SubscriptionInviteListResponse,
|
||||
SubscriptionInviteRequestParams,
|
||||
SubscriptionInviteResponse,
|
||||
SubscriptionServerInterface,
|
||||
} from '@standardnotes/api'
|
||||
import { RoleName } from '@standardnotes/common'
|
||||
import { inject, injectable } from 'inversify'
|
||||
|
||||
import TYPES from '../Bootstrap/Types'
|
||||
import { AcceptSharedSubscriptionInvitation } from '../Domain/UseCase/AcceptSharedSubscriptionInvitation/AcceptSharedSubscriptionInvitation'
|
||||
@@ -18,8 +22,8 @@ import { DeclineSharedSubscriptionInvitation } from '../Domain/UseCase/DeclineSh
|
||||
import { InviteToSharedSubscription } from '../Domain/UseCase/InviteToSharedSubscription/InviteToSharedSubscription'
|
||||
import { ListSharedSubscriptionInvitations } from '../Domain/UseCase/ListSharedSubscriptionInvitations/ListSharedSubscriptionInvitations'
|
||||
|
||||
@controller('/subscription-invites')
|
||||
export class SubscriptionInvitesController extends BaseHttpController {
|
||||
@injectable()
|
||||
export class SubscriptionInvitesController implements SubscriptionServerInterface {
|
||||
constructor(
|
||||
@inject(TYPES.InviteToSharedSubscription) private inviteToSharedSubscription: InviteToSharedSubscription,
|
||||
@inject(TYPES.AcceptSharedSubscriptionInvitation)
|
||||
@@ -30,75 +34,103 @@ export class SubscriptionInvitesController extends BaseHttpController {
|
||||
private cancelSharedSubscriptionInvitation: CancelSharedSubscriptionInvitation,
|
||||
@inject(TYPES.ListSharedSubscriptionInvitations)
|
||||
private listSharedSubscriptionInvitations: ListSharedSubscriptionInvitations,
|
||||
) {
|
||||
super()
|
||||
}
|
||||
) {}
|
||||
|
||||
@httpGet('/:inviteUuid/accept')
|
||||
async acceptInvite(request: Request): Promise<results.JsonResult> {
|
||||
async acceptInvite(params: SubscriptionInviteAcceptRequestParams): Promise<SubscriptionInviteAcceptResponse> {
|
||||
const result = await this.acceptSharedSubscriptionInvitation.execute({
|
||||
sharedSubscriptionInvitationUuid: request.params.inviteUuid,
|
||||
sharedSubscriptionInvitationUuid: params.inviteUuid,
|
||||
})
|
||||
|
||||
if (result.success) {
|
||||
return this.json(result)
|
||||
return {
|
||||
status: HttpStatusCode.Success,
|
||||
data: result,
|
||||
}
|
||||
}
|
||||
|
||||
return this.json(result, 400)
|
||||
return {
|
||||
status: HttpStatusCode.BadRequest,
|
||||
data: result,
|
||||
}
|
||||
}
|
||||
|
||||
@httpGet('/:inviteUuid/decline')
|
||||
async declineInvite(request: Request): Promise<results.JsonResult> {
|
||||
async declineInvite(params: SubscriptionInviteDeclineRequestParams): Promise<SubscriptionInviteDeclineResponse> {
|
||||
const result = await this.declineSharedSubscriptionInvitation.execute({
|
||||
sharedSubscriptionInvitationUuid: request.params.inviteUuid,
|
||||
sharedSubscriptionInvitationUuid: params.inviteUuid,
|
||||
})
|
||||
|
||||
if (result.success) {
|
||||
return this.json(result)
|
||||
return {
|
||||
status: HttpStatusCode.Success,
|
||||
data: result,
|
||||
}
|
||||
}
|
||||
|
||||
return this.json(result, 400)
|
||||
return {
|
||||
status: HttpStatusCode.BadRequest,
|
||||
data: result,
|
||||
}
|
||||
}
|
||||
|
||||
@httpPost('/', TYPES.ApiGatewayAuthMiddleware)
|
||||
async inviteToSubscriptionSharing(request: Request, response: Response): Promise<results.JsonResult> {
|
||||
if (!request.body.identifier) {
|
||||
return this.json({ error: { message: 'Missing invitee identifier' } }, 400)
|
||||
async invite(params: SubscriptionInviteRequestParams): Promise<SubscriptionInviteResponse> {
|
||||
if (!params.identifier) {
|
||||
return {
|
||||
status: HttpStatusCode.BadRequest,
|
||||
data: {
|
||||
error: {
|
||||
message: 'Missing invitee identifier',
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const result = await this.inviteToSharedSubscription.execute({
|
||||
inviterEmail: response.locals.user.email,
|
||||
inviterUuid: response.locals.user.uuid,
|
||||
inviteeIdentifier: request.body.identifier,
|
||||
inviterRoles: response.locals.roles.map((role: Role) => role.name),
|
||||
inviterEmail: params.inviterEmail as string,
|
||||
inviterUuid: params.inviterUuid as string,
|
||||
inviteeIdentifier: params.identifier,
|
||||
inviterRoles: params.inviterRoles as RoleName[],
|
||||
})
|
||||
|
||||
if (result.success) {
|
||||
return this.json(result)
|
||||
return {
|
||||
status: HttpStatusCode.Success,
|
||||
data: result,
|
||||
}
|
||||
}
|
||||
|
||||
return this.json(result, 400)
|
||||
return {
|
||||
status: HttpStatusCode.BadRequest,
|
||||
data: result,
|
||||
}
|
||||
}
|
||||
|
||||
@httpDelete('/:inviteUuid', TYPES.ApiGatewayAuthMiddleware)
|
||||
async cancelSubscriptionSharing(request: Request, response: Response): Promise<results.JsonResult> {
|
||||
async cancelInvite(params: SubscriptionInviteCancelRequestParams): Promise<SubscriptionInviteCancelResponse> {
|
||||
const result = await this.cancelSharedSubscriptionInvitation.execute({
|
||||
sharedSubscriptionInvitationUuid: request.params.inviteUuid,
|
||||
inviterEmail: response.locals.user.email,
|
||||
sharedSubscriptionInvitationUuid: params.inviteUuid,
|
||||
inviterEmail: params.inviterEmail as string,
|
||||
})
|
||||
|
||||
if (result.success) {
|
||||
return this.json(result)
|
||||
return {
|
||||
status: HttpStatusCode.Success,
|
||||
data: result,
|
||||
}
|
||||
}
|
||||
|
||||
return this.json(result, 400)
|
||||
return {
|
||||
status: HttpStatusCode.BadRequest,
|
||||
data: result,
|
||||
}
|
||||
}
|
||||
|
||||
@httpGet('/', TYPES.ApiGatewayAuthMiddleware)
|
||||
async listInvites(_request: Request, response: Response): Promise<results.JsonResult> {
|
||||
async listInvites(params: SubscriptionInviteListRequestParams): Promise<SubscriptionInviteListResponse> {
|
||||
const result = await this.listSharedSubscriptionInvitations.execute({
|
||||
inviterEmail: response.locals.user.email,
|
||||
inviterEmail: params.inviterEmail as string,
|
||||
})
|
||||
|
||||
return this.json(result)
|
||||
return {
|
||||
status: HttpStatusCode.Success,
|
||||
data: result,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,18 +4,23 @@ import { Request, Response } from 'express'
|
||||
import { results } from 'inversify-express-utils'
|
||||
import { ValetTokenController } from './ValetTokenController'
|
||||
import { CreateValetToken } from '../Domain/UseCase/CreateValetToken/CreateValetToken'
|
||||
import { Uuid, ValidatorInterface } from '@standardnotes/common'
|
||||
|
||||
describe('ValetTokenController', () => {
|
||||
let createValetToken: CreateValetToken
|
||||
let uuidValidator: ValidatorInterface<Uuid>
|
||||
let request: Request
|
||||
let response: Response
|
||||
|
||||
const createController = () => new ValetTokenController(createValetToken)
|
||||
const createController = () => new ValetTokenController(createValetToken, uuidValidator)
|
||||
|
||||
beforeEach(() => {
|
||||
createValetToken = {} as jest.Mocked<CreateValetToken>
|
||||
createValetToken.execute = jest.fn().mockReturnValue({ success: true, valetToken: 'foobar' })
|
||||
|
||||
uuidValidator = {} as jest.Mocked<ValidatorInterface<Uuid>>
|
||||
uuidValidator.validate = jest.fn().mockReturnValue(true)
|
||||
|
||||
request = {
|
||||
body: {
|
||||
operation: 'write',
|
||||
@@ -42,6 +47,17 @@ describe('ValetTokenController', () => {
|
||||
expect(await result.content.readAsStringAsync()).toEqual('{"success":true,"valetToken":"foobar"}')
|
||||
})
|
||||
|
||||
it('should not create a valet token if the remote resource identifier is not a valid uuid', async () => {
|
||||
uuidValidator.validate = jest.fn().mockReturnValue(false)
|
||||
|
||||
const httpResponse = <results.JsonResult>await createController().create(request, response)
|
||||
const result = await httpResponse.executeAsync()
|
||||
|
||||
expect(createValetToken.execute).not.toHaveBeenCalled()
|
||||
|
||||
expect(result.statusCode).toEqual(400)
|
||||
})
|
||||
|
||||
it('should create a read valet token for read only access session', async () => {
|
||||
response.locals.readOnlyAccess = true
|
||||
request.body.operation = 'read'
|
||||
|
||||
@@ -11,11 +11,15 @@ import { CreateValetTokenPayload } from '@standardnotes/responses'
|
||||
|
||||
import TYPES from '../Bootstrap/Types'
|
||||
import { CreateValetToken } from '../Domain/UseCase/CreateValetToken/CreateValetToken'
|
||||
import { ErrorTag } from '@standardnotes/common'
|
||||
import { ErrorTag, Uuid, ValidatorInterface } from '@standardnotes/common'
|
||||
import { ValetTokenOperation } from '@standardnotes/security'
|
||||
|
||||
@controller('/valet-tokens', TYPES.ApiGatewayAuthMiddleware)
|
||||
export class ValetTokenController extends BaseHttpController {
|
||||
constructor(@inject(TYPES.CreateValetToken) private createValetKey: CreateValetToken) {
|
||||
constructor(
|
||||
@inject(TYPES.CreateValetToken) private createValetKey: CreateValetToken,
|
||||
@inject(TYPES.UuidValidator) private uuidValitor: ValidatorInterface<Uuid>,
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
||||
@@ -35,9 +39,23 @@ export class ValetTokenController extends BaseHttpController {
|
||||
)
|
||||
}
|
||||
|
||||
for (const resource of payload.resources) {
|
||||
if (!this.uuidValitor.validate(resource.remoteIdentifier)) {
|
||||
return this.json(
|
||||
{
|
||||
error: {
|
||||
tag: ErrorTag.ParametersInvalid,
|
||||
message: 'Invalid remote resource identifier.',
|
||||
},
|
||||
},
|
||||
400,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const createValetKeyResponse = await this.createValetKey.execute({
|
||||
userUuid: response.locals.user.uuid,
|
||||
operation: payload.operation,
|
||||
operation: payload.operation as ValetTokenOperation,
|
||||
resources: payload.resources,
|
||||
})
|
||||
|
||||
|
||||
@@ -1,65 +1,28 @@
|
||||
import 'reflect-metadata'
|
||||
|
||||
import * as express from 'express'
|
||||
import { results } from 'inversify-express-utils'
|
||||
|
||||
import { AddWebSocketsConnection } from '../Domain/UseCase/AddWebSocketsConnection/AddWebSocketsConnection'
|
||||
|
||||
import { WebSocketsController } from './WebSocketsController'
|
||||
import { RemoveWebSocketsConnection } from '../Domain/UseCase/RemoveWebSocketsConnection/RemoveWebSocketsConnection'
|
||||
import { CreateWebSocketConnectionToken } from '../Domain/UseCase/CreateWebSocketConnectionToken/CreateWebSocketConnectionToken'
|
||||
|
||||
describe('WebSocketsController', () => {
|
||||
let addWebSocketsConnection: AddWebSocketsConnection
|
||||
let removeWebSocketsConnection: RemoveWebSocketsConnection
|
||||
let request: express.Request
|
||||
let response: express.Response
|
||||
let createWebSocketConnectionToken: CreateWebSocketConnectionToken
|
||||
|
||||
const createController = () => new WebSocketsController(addWebSocketsConnection, removeWebSocketsConnection)
|
||||
const createController = () => new WebSocketsController(createWebSocketConnectionToken)
|
||||
|
||||
beforeEach(() => {
|
||||
addWebSocketsConnection = {} as jest.Mocked<AddWebSocketsConnection>
|
||||
addWebSocketsConnection.execute = jest.fn()
|
||||
|
||||
removeWebSocketsConnection = {} as jest.Mocked<RemoveWebSocketsConnection>
|
||||
removeWebSocketsConnection.execute = jest.fn()
|
||||
|
||||
request = {
|
||||
body: {
|
||||
userUuid: '1-2-3',
|
||||
},
|
||||
params: {},
|
||||
headers: {},
|
||||
} as jest.Mocked<express.Request>
|
||||
request.params.connectionId = '2-3-4'
|
||||
|
||||
response = {
|
||||
locals: {},
|
||||
} as jest.Mocked<express.Response>
|
||||
response.locals.user = {
|
||||
uuid: '1-2-3',
|
||||
}
|
||||
createWebSocketConnectionToken = {} as jest.Mocked<CreateWebSocketConnectionToken>
|
||||
createWebSocketConnectionToken.execute = jest.fn().mockReturnValue({ token: 'foobar' })
|
||||
})
|
||||
|
||||
it('should persist an established web sockets connection', async () => {
|
||||
const httpResponse = await createController().storeWebSocketsConnection(request, response)
|
||||
it('should create a web sockets connection token', async () => {
|
||||
const response = await createController().createConnectionToken({ userUuid: '1-2-3' })
|
||||
|
||||
expect(httpResponse).toBeInstanceOf(results.JsonResult)
|
||||
expect((<results.JsonResult>httpResponse).statusCode).toEqual(200)
|
||||
|
||||
expect(addWebSocketsConnection.execute).toHaveBeenCalledWith({
|
||||
userUuid: '1-2-3',
|
||||
connectionId: '2-3-4',
|
||||
expect(response).toEqual({
|
||||
status: 200,
|
||||
data: { token: 'foobar' },
|
||||
})
|
||||
})
|
||||
|
||||
it('should remove a disconnected web sockets connection', async () => {
|
||||
const httpResponse = await createController().deleteWebSocketsConnection(request)
|
||||
|
||||
expect(httpResponse).toBeInstanceOf(results.JsonResult)
|
||||
expect((<results.JsonResult>httpResponse).statusCode).toEqual(200)
|
||||
|
||||
expect(removeWebSocketsConnection.execute).toHaveBeenCalledWith({
|
||||
connectionId: '2-3-4',
|
||||
expect(createWebSocketConnectionToken.execute).toHaveBeenCalledWith({
|
||||
userUuid: '1-2-3',
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,45 +1,29 @@
|
||||
import { Request, Response } from 'express'
|
||||
import { inject } from 'inversify'
|
||||
import {
|
||||
BaseHttpController,
|
||||
controller,
|
||||
httpDelete,
|
||||
httpPost,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
results,
|
||||
} from 'inversify-express-utils'
|
||||
HttpStatusCode,
|
||||
WebSocketConnectionTokenRequestParams,
|
||||
WebSocketConnectionTokenResponse,
|
||||
WebSocketServerInterface,
|
||||
} from '@standardnotes/api'
|
||||
import { inject, injectable } from 'inversify'
|
||||
|
||||
import TYPES from '../Bootstrap/Types'
|
||||
import { AddWebSocketsConnection } from '../Domain/UseCase/AddWebSocketsConnection/AddWebSocketsConnection'
|
||||
import { RemoveWebSocketsConnection } from '../Domain/UseCase/RemoveWebSocketsConnection/RemoveWebSocketsConnection'
|
||||
import { CreateWebSocketConnectionToken } from '../Domain/UseCase/CreateWebSocketConnectionToken/CreateWebSocketConnectionToken'
|
||||
|
||||
@controller('/sockets')
|
||||
export class WebSocketsController extends BaseHttpController {
|
||||
@injectable()
|
||||
export class WebSocketsController implements WebSocketServerInterface {
|
||||
constructor(
|
||||
@inject(TYPES.AddWebSocketsConnection) private addWebSocketsConnection: AddWebSocketsConnection,
|
||||
@inject(TYPES.RemoveWebSocketsConnection) private removeWebSocketsConnection: RemoveWebSocketsConnection,
|
||||
) {
|
||||
super()
|
||||
}
|
||||
@inject(TYPES.CreateWebSocketConnectionToken)
|
||||
private createWebSocketConnectionToken: CreateWebSocketConnectionToken,
|
||||
) {}
|
||||
|
||||
@httpPost('/:connectionId', TYPES.ApiGatewayAuthMiddleware)
|
||||
async storeWebSocketsConnection(
|
||||
request: Request,
|
||||
response: Response,
|
||||
): Promise<results.JsonResult | results.BadRequestErrorMessageResult> {
|
||||
await this.addWebSocketsConnection.execute({
|
||||
userUuid: response.locals.user.uuid,
|
||||
connectionId: request.params.connectionId,
|
||||
})
|
||||
async createConnectionToken(
|
||||
params: WebSocketConnectionTokenRequestParams,
|
||||
): Promise<WebSocketConnectionTokenResponse> {
|
||||
const result = await this.createWebSocketConnectionToken.execute({ userUuid: params.userUuid as string })
|
||||
|
||||
return this.json({ success: true })
|
||||
}
|
||||
|
||||
@httpDelete('/:connectionId')
|
||||
async deleteWebSocketsConnection(
|
||||
request: Request,
|
||||
): Promise<results.JsonResult | results.BadRequestErrorMessageResult> {
|
||||
await this.removeWebSocketsConnection.execute({ connectionId: request.params.connectionId })
|
||||
|
||||
return this.json({ success: true })
|
||||
return {
|
||||
status: HttpStatusCode.Success,
|
||||
data: result,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,6 +82,7 @@ describe('FeatureService', () => {
|
||||
uuid: 'subscription-1-1-1',
|
||||
createdAt: 111,
|
||||
updatedAt: 222,
|
||||
renewedAt: null,
|
||||
planName: SubscriptionName.PlusPlan,
|
||||
endsAt: 555,
|
||||
user: Promise.resolve(user),
|
||||
@@ -95,6 +96,7 @@ describe('FeatureService', () => {
|
||||
uuid: 'subscription-2-2-2',
|
||||
createdAt: 222,
|
||||
updatedAt: 333,
|
||||
renewedAt: null,
|
||||
planName: SubscriptionName.ProPlan,
|
||||
endsAt: 777,
|
||||
user: Promise.resolve(user),
|
||||
@@ -108,6 +110,7 @@ describe('FeatureService', () => {
|
||||
uuid: 'subscription-3-3-3-canceled',
|
||||
createdAt: 111,
|
||||
updatedAt: 222,
|
||||
renewedAt: null,
|
||||
planName: SubscriptionName.PlusPlan,
|
||||
endsAt: 333,
|
||||
user: Promise.resolve(user),
|
||||
@@ -121,6 +124,7 @@ describe('FeatureService', () => {
|
||||
uuid: 'subscription-4-4-4-canceled',
|
||||
createdAt: 111,
|
||||
updatedAt: 222,
|
||||
renewedAt: null,
|
||||
planName: SubscriptionName.PlusPlan,
|
||||
endsAt: 333,
|
||||
user: Promise.resolve(user),
|
||||
@@ -240,6 +244,7 @@ describe('FeatureService', () => {
|
||||
uuid: 'subscription-1-1-1',
|
||||
createdAt: 111,
|
||||
updatedAt: 222,
|
||||
renewedAt: null,
|
||||
planName: 'non existing plan name' as SubscriptionName,
|
||||
endsAt: 555,
|
||||
user: Promise.resolve(user),
|
||||
|
||||
@@ -12,7 +12,8 @@ import { User } from '../User/User'
|
||||
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
|
||||
import { AccountDeletionRequestedEventHandler } from './AccountDeletionRequestedEventHandler'
|
||||
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
|
||||
import { AnalyticsStoreInterface } from '@standardnotes/analytics'
|
||||
import { AnalyticsStoreInterface, StatisticsStoreInterface } from '@standardnotes/analytics'
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
|
||||
describe('AccountDeletionRequestedEventHandler', () => {
|
||||
let userRepository: UserRepositoryInterface
|
||||
@@ -27,6 +28,8 @@ describe('AccountDeletionRequestedEventHandler', () => {
|
||||
let event: AccountDeletionRequestedEvent
|
||||
let getUserAnalyticsId: GetUserAnalyticsId
|
||||
let analyticsStore: AnalyticsStoreInterface
|
||||
let statisticsStore: StatisticsStoreInterface
|
||||
let timer: TimerInterface
|
||||
|
||||
const createHandler = () =>
|
||||
new AccountDeletionRequestedEventHandler(
|
||||
@@ -36,6 +39,8 @@ describe('AccountDeletionRequestedEventHandler', () => {
|
||||
revokedSessionRepository,
|
||||
getUserAnalyticsId,
|
||||
analyticsStore,
|
||||
statisticsStore,
|
||||
timer,
|
||||
logger,
|
||||
)
|
||||
|
||||
@@ -87,6 +92,13 @@ describe('AccountDeletionRequestedEventHandler', () => {
|
||||
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,5 +1,12 @@
|
||||
import { AnalyticsActivity, AnalyticsStoreInterface, Period } from '@standardnotes/analytics'
|
||||
import {
|
||||
AnalyticsActivity,
|
||||
AnalyticsStoreInterface,
|
||||
Period,
|
||||
StatisticsMeasure,
|
||||
StatisticsStoreInterface,
|
||||
} from '@standardnotes/analytics'
|
||||
import { AccountDeletionRequestedEvent, DomainEventHandlerInterface } from '@standardnotes/domain-events'
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
import { inject, injectable } from 'inversify'
|
||||
import { Logger } from 'winston'
|
||||
import TYPES from '../../Bootstrap/Types'
|
||||
@@ -18,6 +25,8 @@ export class AccountDeletionRequestedEventHandler implements DomainEventHandlerI
|
||||
@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,
|
||||
) {}
|
||||
|
||||
@@ -39,6 +48,14 @@ export class AccountDeletionRequestedEventHandler implements DomainEventHandlerI
|
||||
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}`)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'reflect-metadata'
|
||||
|
||||
import { PaymentSuccessEvent } from '@standardnotes/domain-events'
|
||||
import { AnalyticsStoreInterface } from '@standardnotes/analytics'
|
||||
import { AnalyticsStoreInterface, StatisticsStoreInterface } from '@standardnotes/analytics'
|
||||
|
||||
import { PaymentSuccessEventHandler } from './PaymentSuccessEventHandler'
|
||||
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
|
||||
@@ -14,8 +14,10 @@ describe('PaymentSuccessEventHandler', () => {
|
||||
let user: User
|
||||
let getUserAnalyticsId: GetUserAnalyticsId
|
||||
let analyticsStore: AnalyticsStoreInterface
|
||||
let statisticsStore: StatisticsStoreInterface
|
||||
|
||||
const createHandler = () => new PaymentSuccessEventHandler(userRepository, getUserAnalyticsId, analyticsStore)
|
||||
const createHandler = () =>
|
||||
new PaymentSuccessEventHandler(userRepository, getUserAnalyticsId, analyticsStore, statisticsStore)
|
||||
|
||||
beforeEach(() => {
|
||||
user = {} as jest.Mocked<User>
|
||||
@@ -29,9 +31,13 @@ describe('PaymentSuccessEventHandler', () => {
|
||||
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,
|
||||
}
|
||||
})
|
||||
|
||||
@@ -39,6 +45,7 @@ describe('PaymentSuccessEventHandler', () => {
|
||||
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 () => {
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
import { AnalyticsActivity, AnalyticsStoreInterface, Period } from '@standardnotes/analytics'
|
||||
import {
|
||||
AnalyticsActivity,
|
||||
AnalyticsStoreInterface,
|
||||
Period,
|
||||
StatisticsMeasure,
|
||||
StatisticsStoreInterface,
|
||||
} from '@standardnotes/analytics'
|
||||
import { DomainEventHandlerInterface, PaymentSuccessEvent } from '@standardnotes/domain-events'
|
||||
import { inject, injectable } from 'inversify'
|
||||
|
||||
@@ -12,6 +18,7 @@ export class PaymentSuccessEventHandler implements DomainEventHandlerInterface {
|
||||
@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> {
|
||||
@@ -26,5 +33,11 @@ export class PaymentSuccessEventHandler implements DomainEventHandlerInterface {
|
||||
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,10 +8,11 @@ import * as dayjs from 'dayjs'
|
||||
import { SubscriptionCancelledEventHandler } from './SubscriptionCancelledEventHandler'
|
||||
import { UserSubscriptionRepositoryInterface } from '../Subscription/UserSubscriptionRepositoryInterface'
|
||||
import { OfflineUserSubscriptionRepositoryInterface } from '../Subscription/OfflineUserSubscriptionRepositoryInterface'
|
||||
import { AnalyticsStoreInterface } from '@standardnotes/analytics'
|
||||
import { AnalyticsStoreInterface, Period, StatisticsMeasure, StatisticsStoreInterface } from '@standardnotes/analytics'
|
||||
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
|
||||
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
|
||||
import { User } from '../User/User'
|
||||
import { UserSubscription } from '../Subscription/UserSubscription'
|
||||
|
||||
describe('SubscriptionCancelledEventHandler', () => {
|
||||
let userSubscriptionRepository: UserSubscriptionRepositoryInterface
|
||||
@@ -20,6 +21,7 @@ describe('SubscriptionCancelledEventHandler', () => {
|
||||
let userRepository: UserRepositoryInterface
|
||||
let getUserAnalyticsId: GetUserAnalyticsId
|
||||
let analyticsStore: AnalyticsStoreInterface
|
||||
let statisticsStore: StatisticsStoreInterface
|
||||
let timestamp: number
|
||||
|
||||
const createHandler = () =>
|
||||
@@ -29,6 +31,7 @@ describe('SubscriptionCancelledEventHandler', () => {
|
||||
userRepository,
|
||||
getUserAnalyticsId,
|
||||
analyticsStore,
|
||||
statisticsStore,
|
||||
)
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -43,8 +46,16 @@ describe('SubscriptionCancelledEventHandler', () => {
|
||||
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()
|
||||
@@ -59,14 +70,21 @@ describe('SubscriptionCancelledEventHandler', () => {
|
||||
subscriptionName: SubscriptionName.ProPlan,
|
||||
timestamp,
|
||||
offline: false,
|
||||
replaced: false,
|
||||
}
|
||||
})
|
||||
|
||||
it('should update subscription cancelled', async () => {
|
||||
event.payload.timestamp = 1642395451516000
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(userSubscriptionRepository.updateCancelled).toHaveBeenCalledWith(1, true, timestamp)
|
||||
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 () => {
|
||||
|
||||
@@ -1,12 +1,19 @@
|
||||
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 { AnalyticsActivity, AnalyticsStoreInterface, Period } from '@standardnotes/analytics'
|
||||
import { UserSubscription } from '../Subscription/UserSubscription'
|
||||
|
||||
@injectable()
|
||||
export class SubscriptionCancelledEventHandler implements DomainEventHandlerInterface {
|
||||
@@ -17,16 +24,9 @@ export class SubscriptionCancelledEventHandler implements DomainEventHandlerInte
|
||||
@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: SubscriptionCancelledEvent): Promise<void> {
|
||||
if (event.payload.offline) {
|
||||
await this.updateOfflineSubscriptionCancelled(event.payload.subscriptionId, event.payload.timestamp)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
await this.updateSubscriptionCancelled(event.payload.subscriptionId, event.payload.timestamp)
|
||||
|
||||
const user = await this.userRepository.findOneByEmail(event.payload.userEmail)
|
||||
if (user !== null) {
|
||||
const { analyticsId } = await this.getUserAnalyticsId.execute({ userUuid: user.uuid })
|
||||
@@ -35,7 +35,38 @@ export class SubscriptionCancelledEventHandler implements DomainEventHandlerInte
|
||||
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
|
||||
await this.statisticsStore.incrementMeasure(StatisticsMeasure.SubscriptionLength, subscriptionLength, [
|
||||
Period.Today,
|
||||
Period.ThisWeek,
|
||||
Period.ThisMonth,
|
||||
])
|
||||
|
||||
const lastPurchaseTime = lastSubscription.renewedAt ?? lastSubscription.updatedAt
|
||||
const remainingSubscriptionTime = lastSubscription.endsAt - event.payload.timestamp
|
||||
const totalSubscriptionTime = lastSubscription.endsAt - lastPurchaseTime
|
||||
|
||||
const remainingSubscriptionPercentage = Math.floor((remainingSubscriptionTime / totalSubscriptionTime) * 100)
|
||||
|
||||
await this.statisticsStore.incrementMeasure(
|
||||
StatisticsMeasure.RemainingSubscriptionTimePercentage,
|
||||
remainingSubscriptionPercentage,
|
||||
[Period.Today, Period.ThisWeek, Period.ThisMonth],
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (event.payload.offline) {
|
||||
await this.updateOfflineSubscriptionCancelled(event.payload.subscriptionId, event.payload.timestamp)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
await this.updateSubscriptionCancelled(event.payload.subscriptionId, event.payload.timestamp)
|
||||
}
|
||||
|
||||
private async updateSubscriptionCancelled(subscriptionId: number, timestamp: number): Promise<void> {
|
||||
|
||||
@@ -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,
|
||||
@@ -80,6 +91,14 @@ export class SubscriptionPurchasedEventHandler implements DomainEventHandlerInte
|
||||
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> {
|
||||
|
||||
+4
@@ -7,5 +7,9 @@ export interface SharedSubscriptionInvitationRepositoryInterface {
|
||||
findOneByUuidAndStatus(uuid: Uuid, status: InvitationStatus): Promise<SharedSubscriptionInvitation | null>
|
||||
findOneByUuid(uuid: Uuid): Promise<SharedSubscriptionInvitation | null>
|
||||
findByInviterEmail(inviterEmail: string): Promise<SharedSubscriptionInvitation[]>
|
||||
findOneByInviteeAndInviterEmail(
|
||||
inviteeEmail: string,
|
||||
inviterEmail: string,
|
||||
): Promise<SharedSubscriptionInvitation | null>
|
||||
countByInviterEmailAndStatus(inviterEmail: Uuid, statuses: InvitationStatus[]): Promise<number>
|
||||
}
|
||||
|
||||
@@ -34,6 +34,13 @@ export class UserSubscription {
|
||||
@Index('updated_at')
|
||||
declare updatedAt: number
|
||||
|
||||
@Column({
|
||||
name: 'renewed_at',
|
||||
type: 'bigint',
|
||||
nullable: true,
|
||||
})
|
||||
declare renewedAt: number | null
|
||||
|
||||
@Column({
|
||||
type: 'tinyint',
|
||||
width: 1,
|
||||
|
||||
@@ -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[]>
|
||||
|
||||
+20
-13
@@ -16,6 +16,7 @@ import { DomainEventPublisherInterface, SharedSubscriptionInvitationCanceledEven
|
||||
import { DomainEventFactoryInterface } from '../../Event/DomainEventFactoryInterface'
|
||||
import { InviterIdentifierType } from '../../SharedSubscription/InviterIdentifierType'
|
||||
import { InviteeIdentifierType } from '../../SharedSubscription/InviteeIdentifierType'
|
||||
import { Logger } from 'winston'
|
||||
|
||||
describe('CancelSharedSubscriptionInvitation', () => {
|
||||
let sharedSubscriptionInvitationRepository: SharedSubscriptionInvitationRepositoryInterface
|
||||
@@ -28,6 +29,7 @@ describe('CancelSharedSubscriptionInvitation', () => {
|
||||
let invitation: SharedSubscriptionInvitation
|
||||
let domainEventPublisher: DomainEventPublisherInterface
|
||||
let domainEventFactory: DomainEventFactoryInterface
|
||||
let logger: Logger
|
||||
|
||||
const createUseCase = () =>
|
||||
new CancelSharedSubscriptionInvitation(
|
||||
@@ -38,6 +40,7 @@ describe('CancelSharedSubscriptionInvitation', () => {
|
||||
domainEventPublisher,
|
||||
domainEventFactory,
|
||||
timer,
|
||||
logger,
|
||||
)
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -60,6 +63,9 @@ describe('CancelSharedSubscriptionInvitation', () => {
|
||||
inviteeIdentifierType: InviteeIdentifierType.Email,
|
||||
} as jest.Mocked<SharedSubscriptionInvitation>
|
||||
|
||||
logger = {} as jest.Mocked<Logger>
|
||||
logger.debug = jest.fn()
|
||||
|
||||
sharedSubscriptionInvitationRepository = {} as jest.Mocked<SharedSubscriptionInvitationRepositoryInterface>
|
||||
sharedSubscriptionInvitationRepository.findOneByUuid = jest.fn().mockReturnValue(invitation)
|
||||
sharedSubscriptionInvitationRepository.save = jest.fn()
|
||||
@@ -126,7 +132,7 @@ describe('CancelSharedSubscriptionInvitation', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('should cancel a shared subscription invitation without subscription removal is subscription is not found', async () => {
|
||||
it('should cancel a shared subscription invitation without subscription removal if subscription is not found', async () => {
|
||||
userSubscriptionRepository.findOneByUserUuidAndSubscriptionId = jest.fn().mockReturnValue(null)
|
||||
|
||||
expect(
|
||||
@@ -175,7 +181,7 @@ describe('CancelSharedSubscriptionInvitation', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('should not cancel a shared subscription invitation if invitee is not found', async () => {
|
||||
it('should cancel a shared subscription invitation without subscription removal if invitee is not found', async () => {
|
||||
userRepository.findOneByEmail = jest.fn().mockReturnValue(null)
|
||||
expect(
|
||||
await createUseCase().execute({
|
||||
@@ -183,20 +189,21 @@ describe('CancelSharedSubscriptionInvitation', () => {
|
||||
inviterEmail: 'test@test.te',
|
||||
}),
|
||||
).toEqual({
|
||||
success: false,
|
||||
success: true,
|
||||
})
|
||||
})
|
||||
|
||||
it('should not cancel a shared subscription invitation if invitee is not found', async () => {
|
||||
userRepository.findOneByEmail = jest.fn().mockReturnValue(null)
|
||||
expect(
|
||||
await createUseCase().execute({
|
||||
sharedSubscriptionInvitationUuid: '1-2-3',
|
||||
inviterEmail: 'test@test.te',
|
||||
}),
|
||||
).toEqual({
|
||||
success: false,
|
||||
expect(sharedSubscriptionInvitationRepository.save).toHaveBeenCalledWith({
|
||||
status: 'canceled',
|
||||
subscriptionId: 3,
|
||||
updatedAt: 1,
|
||||
inviterIdentifier: 'test@test.te',
|
||||
uuid: '1-2-3',
|
||||
inviterIdentifierType: 'email',
|
||||
inviteeIdentifier: 'invitee@test.te',
|
||||
inviteeIdentifierType: 'email',
|
||||
})
|
||||
expect(userSubscriptionRepository.save).not.toHaveBeenCalled()
|
||||
expect(roleService.removeUserRole).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should not cancel a shared subscription invitation if inviter subscription is not found', async () => {
|
||||
|
||||
+27
-18
@@ -2,6 +2,7 @@ import { SubscriptionName } from '@standardnotes/common'
|
||||
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
import { inject, injectable } from 'inversify'
|
||||
import { Logger } from 'winston'
|
||||
|
||||
import TYPES from '../../../Bootstrap/Types'
|
||||
import { DomainEventFactoryInterface } from '../../Event/DomainEventFactoryInterface'
|
||||
@@ -29,6 +30,7 @@ export class CancelSharedSubscriptionInvitation implements UseCaseInterface {
|
||||
@inject(TYPES.DomainEventPublisher) private domainEventPublisher: DomainEventPublisherInterface,
|
||||
@inject(TYPES.DomainEventFactory) private domainEventFactory: DomainEventFactoryInterface,
|
||||
@inject(TYPES.Timer) private timer: TimerInterface,
|
||||
@inject(TYPES.Logger) private logger: Logger,
|
||||
) {}
|
||||
|
||||
async execute(dto: CancelSharedSubscriptionInvitationDTO): Promise<CancelSharedSubscriptionInvitationResponse> {
|
||||
@@ -36,29 +38,34 @@ export class CancelSharedSubscriptionInvitation implements UseCaseInterface {
|
||||
dto.sharedSubscriptionInvitationUuid,
|
||||
)
|
||||
if (sharedSubscriptionInvitation === null) {
|
||||
this.logger.debug(
|
||||
`Could not find a shared subscription invitation with uuid ${dto.sharedSubscriptionInvitationUuid}`,
|
||||
)
|
||||
|
||||
return {
|
||||
success: false,
|
||||
}
|
||||
}
|
||||
|
||||
if (dto.inviterEmail !== sharedSubscriptionInvitation.inviterIdentifier) {
|
||||
this.logger.debug(
|
||||
`Subscription belongs to a different inviter (${sharedSubscriptionInvitation.inviterIdentifier}). Modifier: ${dto.inviterEmail}`,
|
||||
)
|
||||
|
||||
return {
|
||||
success: false,
|
||||
}
|
||||
}
|
||||
|
||||
const invitee = await this.userRepository.findOneByEmail(sharedSubscriptionInvitation.inviteeIdentifier)
|
||||
if (invitee === null) {
|
||||
return {
|
||||
success: false,
|
||||
}
|
||||
}
|
||||
|
||||
const inviterUserSubscriptions = await this.userSubscriptionRepository.findBySubscriptionIdAndType(
|
||||
sharedSubscriptionInvitation.subscriptionId,
|
||||
UserSubscriptionType.Regular,
|
||||
)
|
||||
if (inviterUserSubscriptions.length !== 1) {
|
||||
if (inviterUserSubscriptions.length === 0) {
|
||||
this.logger.debug(`Could not find a regular subscription with id ${sharedSubscriptionInvitation.subscriptionId}`)
|
||||
|
||||
return {
|
||||
success: false,
|
||||
}
|
||||
@@ -70,20 +77,22 @@ export class CancelSharedSubscriptionInvitation implements UseCaseInterface {
|
||||
|
||||
await this.sharedSubscriptionInvitationRepository.save(sharedSubscriptionInvitation)
|
||||
|
||||
await this.removeSharedSubscription(sharedSubscriptionInvitation.subscriptionId, invitee)
|
||||
if (invitee !== null) {
|
||||
await this.removeSharedSubscription(sharedSubscriptionInvitation.subscriptionId, invitee)
|
||||
|
||||
await this.roleService.removeUserRole(invitee, inviterUserSubscription.planName as SubscriptionName)
|
||||
await this.roleService.removeUserRole(invitee, inviterUserSubscription.planName as SubscriptionName)
|
||||
|
||||
await this.domainEventPublisher.publish(
|
||||
this.domainEventFactory.createSharedSubscriptionInvitationCanceledEvent({
|
||||
inviteeIdentifier: invitee.uuid,
|
||||
inviteeIdentifierType: InviteeIdentifierType.Uuid,
|
||||
inviterEmail: sharedSubscriptionInvitation.inviterIdentifier,
|
||||
inviterSubscriptionId: sharedSubscriptionInvitation.subscriptionId,
|
||||
inviterSubscriptionUuid: inviterUserSubscription.uuid,
|
||||
sharedSubscriptionInvitationUuid: sharedSubscriptionInvitation.uuid,
|
||||
}),
|
||||
)
|
||||
await this.domainEventPublisher.publish(
|
||||
this.domainEventFactory.createSharedSubscriptionInvitationCanceledEvent({
|
||||
inviteeIdentifier: invitee.uuid,
|
||||
inviteeIdentifierType: InviteeIdentifierType.Uuid,
|
||||
inviterEmail: sharedSubscriptionInvitation.inviterIdentifier,
|
||||
inviterSubscriptionId: sharedSubscriptionInvitation.subscriptionId,
|
||||
inviterSubscriptionUuid: inviterUserSubscription.uuid,
|
||||
sharedSubscriptionInvitationUuid: sharedSubscriptionInvitation.uuid,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'reflect-metadata'
|
||||
|
||||
import { TokenEncoderInterface, ValetTokenData } from '@standardnotes/security'
|
||||
import { TokenEncoderInterface, ValetTokenData, ValetTokenOperation } from '@standardnotes/security'
|
||||
import { CreateValetToken } from './CreateValetToken'
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
import { UserSubscription } from '../../Subscription/UserSubscription'
|
||||
@@ -70,7 +70,7 @@ describe('CreateValetToken', () => {
|
||||
|
||||
it('should create a read valet token', async () => {
|
||||
const response = await createUseCase().execute({
|
||||
operation: 'read',
|
||||
operation: ValetTokenOperation.Read,
|
||||
userUuid: '1-2-3',
|
||||
resources: [
|
||||
{
|
||||
@@ -92,7 +92,7 @@ describe('CreateValetToken', () => {
|
||||
.mockReturnValue({ regularSubscription: null, sharedSubscription: null })
|
||||
|
||||
const response = await createUseCase().execute({
|
||||
operation: 'read',
|
||||
operation: ValetTokenOperation.Read,
|
||||
userUuid: '1-2-3',
|
||||
resources: [
|
||||
{
|
||||
@@ -117,7 +117,7 @@ describe('CreateValetToken', () => {
|
||||
timer.getTimestampInMicroseconds = jest.fn().mockReturnValue(150)
|
||||
|
||||
const response = await createUseCase().execute({
|
||||
operation: 'read',
|
||||
operation: ValetTokenOperation.Read,
|
||||
userUuid: '1-2-3',
|
||||
resources: [
|
||||
{
|
||||
@@ -135,7 +135,7 @@ describe('CreateValetToken', () => {
|
||||
|
||||
it('should not create a write valet token if unencrypted file size has not been provided for a resource', async () => {
|
||||
const response = await createUseCase().execute({
|
||||
operation: 'write',
|
||||
operation: ValetTokenOperation.Write,
|
||||
resources: [
|
||||
{
|
||||
remoteIdentifier: '2-3-4',
|
||||
@@ -152,7 +152,7 @@ describe('CreateValetToken', () => {
|
||||
|
||||
it('should create a write valet token', async () => {
|
||||
const response = await createUseCase().execute({
|
||||
operation: 'write',
|
||||
operation: ValetTokenOperation.Write,
|
||||
resources: [
|
||||
{
|
||||
remoteIdentifier: '2-3-4',
|
||||
@@ -192,7 +192,7 @@ describe('CreateValetToken', () => {
|
||||
.mockReturnValue({ regularSubscription, sharedSubscription })
|
||||
|
||||
const response = await createUseCase().execute({
|
||||
operation: 'write',
|
||||
operation: ValetTokenOperation.Write,
|
||||
resources: [
|
||||
{
|
||||
remoteIdentifier: '2-3-4',
|
||||
@@ -232,7 +232,7 @@ describe('CreateValetToken', () => {
|
||||
.mockReturnValue({ regularSubscription: null, sharedSubscription })
|
||||
|
||||
const response = await createUseCase().execute({
|
||||
operation: 'write',
|
||||
operation: ValetTokenOperation.Write,
|
||||
resources: [
|
||||
{
|
||||
remoteIdentifier: '2-3-4',
|
||||
@@ -252,7 +252,7 @@ describe('CreateValetToken', () => {
|
||||
subscriptionSettingService.findSubscriptionSettingWithDecryptedValue = jest.fn().mockReturnValue(null)
|
||||
|
||||
const response = await createUseCase().execute({
|
||||
operation: 'write',
|
||||
operation: ValetTokenOperation.Write,
|
||||
userUuid: '1-2-3',
|
||||
resources: [
|
||||
{
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import { CreateValetTokenPayload } from '@standardnotes/responses'
|
||||
import { ValetTokenOperation } from '@standardnotes/security'
|
||||
|
||||
export type CreateValetTokenDTO = CreateValetTokenPayload & {
|
||||
export type CreateValetTokenDTO = {
|
||||
operation: ValetTokenOperation
|
||||
resources: Array<{
|
||||
remoteIdentifier: string
|
||||
unencryptedFileSize?: number
|
||||
}>
|
||||
userUuid: string
|
||||
}
|
||||
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
import 'reflect-metadata'
|
||||
|
||||
import { TokenEncoderInterface, WebSocketConnectionTokenData } from '@standardnotes/security'
|
||||
|
||||
import { CreateWebSocketConnectionToken } from './CreateWebSocketConnectionToken'
|
||||
|
||||
describe('CreateWebSocketConnection', () => {
|
||||
let tokenEncoder: TokenEncoderInterface<WebSocketConnectionTokenData>
|
||||
const tokenTTL = 30
|
||||
|
||||
const createUseCase = () => new CreateWebSocketConnectionToken(tokenEncoder, tokenTTL)
|
||||
|
||||
beforeEach(() => {
|
||||
tokenEncoder = {} as jest.Mocked<TokenEncoderInterface<WebSocketConnectionTokenData>>
|
||||
tokenEncoder.encodeExpirableToken = jest.fn().mockReturnValue('foobar')
|
||||
})
|
||||
|
||||
it('should create a web socket connection token', async () => {
|
||||
const result = await createUseCase().execute({ userUuid: '1-2-3' })
|
||||
|
||||
expect(result.token).toEqual('foobar')
|
||||
|
||||
expect(tokenEncoder.encodeExpirableToken).toHaveBeenCalledWith({ userUuid: '1-2-3' }, 30)
|
||||
})
|
||||
})
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
export type CreateWebSocketConnectionDTO = {
|
||||
userUuid: string
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
export type CreateWebSocketConnectionResponse = {
|
||||
token: string
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
import { TokenEncoderInterface, WebSocketConnectionTokenData } from '@standardnotes/security'
|
||||
import { inject } from 'inversify'
|
||||
|
||||
import TYPES from '../../../Bootstrap/Types'
|
||||
import { UseCaseInterface } from '../UseCaseInterface'
|
||||
import { CreateWebSocketConnectionDTO } from './CreateWebSocketConnectionDTO'
|
||||
import { CreateWebSocketConnectionResponse } from './CreateWebSocketConnectionResponse'
|
||||
|
||||
export class CreateWebSocketConnectionToken implements UseCaseInterface {
|
||||
constructor(
|
||||
@inject(TYPES.WebSocketConnectionTokenEncoder)
|
||||
private tokenEncoder: TokenEncoderInterface<WebSocketConnectionTokenData>,
|
||||
@inject(TYPES.WEB_SOCKET_CONNECTION_TOKEN_TTL) private tokenTTL: number,
|
||||
) {}
|
||||
|
||||
async execute(dto: CreateWebSocketConnectionDTO): Promise<CreateWebSocketConnectionResponse> {
|
||||
const data: WebSocketConnectionTokenData = {
|
||||
userUuid: dto.userUuid,
|
||||
}
|
||||
|
||||
return {
|
||||
token: this.tokenEncoder.encodeExpirableToken(data, this.tokenTTL),
|
||||
}
|
||||
}
|
||||
}
|
||||
+49
-3
@@ -9,6 +9,8 @@ import { InviteToSharedSubscription } from './InviteToSharedSubscription'
|
||||
import { UserSubscriptionRepositoryInterface } from '../../Subscription/UserSubscriptionRepositoryInterface'
|
||||
import { UserSubscription } from '../../Subscription/UserSubscription'
|
||||
import { RoleName } from '@standardnotes/common'
|
||||
import { UserSubscriptionType } from '../../Subscription/UserSubscriptionType'
|
||||
import { SharedSubscriptionInvitation } from '../../SharedSubscription/SharedSubscriptionInvitation'
|
||||
|
||||
describe('InviteToSharedSubscription', () => {
|
||||
let userSubscriptionRepository: UserSubscriptionRepositoryInterface
|
||||
@@ -28,9 +30,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)
|
||||
@@ -38,6 +41,7 @@ describe('InviteToSharedSubscription', () => {
|
||||
sharedSubscriptionInvitationRepository = {} as jest.Mocked<SharedSubscriptionInvitationRepositoryInterface>
|
||||
sharedSubscriptionInvitationRepository.save = jest.fn().mockImplementation((same) => ({ ...same, uuid: '1-2-3' }))
|
||||
sharedSubscriptionInvitationRepository.countByInviterEmailAndStatus = jest.fn().mockReturnValue(2)
|
||||
sharedSubscriptionInvitationRepository.findOneByInviteeAndInviterEmail = jest.fn().mockReturnValue(null)
|
||||
|
||||
domainEventPublisher = {} as jest.Mocked<DomainEventPublisherInterface>
|
||||
domainEventPublisher.publish = jest.fn()
|
||||
@@ -159,4 +163,46 @@ 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()
|
||||
})
|
||||
|
||||
it('should not create an invitation if it already exists', async () => {
|
||||
sharedSubscriptionInvitationRepository.findOneByInviteeAndInviterEmail = jest
|
||||
.fn()
|
||||
.mockReturnValue({} as jest.Mocked<SharedSubscriptionInvitation>)
|
||||
|
||||
expect(
|
||||
await createUseCase().execute({
|
||||
inviteeIdentifier: 'invitee@test.te',
|
||||
inviterUuid: '1-2-3',
|
||||
inviterEmail: 'inviter@test.te',
|
||||
inviterRoles: [RoleName.ProUser],
|
||||
}),
|
||||
).toEqual({
|
||||
success: false,
|
||||
})
|
||||
|
||||
expect(sharedSubscriptionInvitationRepository.save).not.toHaveBeenCalled()
|
||||
|
||||
expect(domainEventFactory.createSharedSubscriptionInvitationCreatedEvent).not.toHaveBeenCalled()
|
||||
expect(domainEventPublisher.publish).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
+13
-2
@@ -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,6 +36,13 @@ export class InviteToSharedSubscription implements UseCaseInterface {
|
||||
}
|
||||
}
|
||||
|
||||
const inviterUserSubscription = await this.userSubscriptionRepository.findOneByUserUuid(dto.inviterUuid)
|
||||
if (inviterUserSubscription === null || inviterUserSubscription.subscriptionType === UserSubscriptionType.Shared) {
|
||||
return {
|
||||
success: false,
|
||||
}
|
||||
}
|
||||
|
||||
const numberOfUsedInvites = await this.sharedSubscriptionInvitationRepository.countByInviterEmailAndStatus(
|
||||
dto.inviterEmail,
|
||||
[InvitationStatus.Sent, InvitationStatus.Accepted],
|
||||
@@ -45,8 +53,11 @@ export class InviteToSharedSubscription implements UseCaseInterface {
|
||||
}
|
||||
}
|
||||
|
||||
const inviterUserSubscription = await this.userSubscriptionRepository.findOneByUserUuid(dto.inviterUuid)
|
||||
if (inviterUserSubscription === null) {
|
||||
const existingInvitation = await this.sharedSubscriptionInvitationRepository.findOneByInviteeAndInviterEmail(
|
||||
dto.inviteeIdentifier,
|
||||
dto.inviterEmail,
|
||||
)
|
||||
if (existingInvitation !== null) {
|
||||
return {
|
||||
success: false,
|
||||
}
|
||||
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
import { ApiVersion } from '@standardnotes/api'
|
||||
import { Role } from '@standardnotes/security'
|
||||
import { Request, Response } from 'express'
|
||||
import { inject } from 'inversify'
|
||||
import {
|
||||
BaseHttpController,
|
||||
controller,
|
||||
httpDelete,
|
||||
httpGet,
|
||||
httpPost,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
results,
|
||||
} from 'inversify-express-utils'
|
||||
import TYPES from '../../Bootstrap/Types'
|
||||
import { SubscriptionInvitesController } from '../../Controller/SubscriptionInvitesController'
|
||||
|
||||
@controller('/subscription-invites')
|
||||
export class InversifyExpressSubscriptionInvitesController extends BaseHttpController {
|
||||
constructor(
|
||||
@inject(TYPES.SubscriptionInvitesController) private subscriptionInvitesController: SubscriptionInvitesController,
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
||||
@httpGet('/:inviteUuid/accept')
|
||||
async acceptInvite(request: Request): Promise<results.JsonResult> {
|
||||
const response = await this.subscriptionInvitesController.acceptInvite({
|
||||
api: request.query.api as ApiVersion,
|
||||
inviteUuid: request.params.inviteUuid,
|
||||
})
|
||||
|
||||
return this.json(response.data, response.status)
|
||||
}
|
||||
|
||||
@httpGet('/:inviteUuid/decline')
|
||||
async declineInvite(request: Request): Promise<results.JsonResult> {
|
||||
const response = await this.subscriptionInvitesController.declineInvite({
|
||||
api: request.query.api as ApiVersion,
|
||||
inviteUuid: request.params.inviteUuid,
|
||||
})
|
||||
|
||||
return this.json(response.data, response.status)
|
||||
}
|
||||
|
||||
@httpPost('/', TYPES.ApiGatewayAuthMiddleware)
|
||||
async inviteToSubscriptionSharing(request: Request, response: Response): Promise<results.JsonResult> {
|
||||
const result = await this.subscriptionInvitesController.invite({
|
||||
...request.body,
|
||||
inviterEmail: response.locals.user.email,
|
||||
inviterUuid: response.locals.user.uuid,
|
||||
inviterRoles: response.locals.roles.map((role: Role) => role.name),
|
||||
})
|
||||
|
||||
return this.json(result.data, result.status)
|
||||
}
|
||||
|
||||
@httpDelete('/:inviteUuid', TYPES.ApiGatewayAuthMiddleware)
|
||||
async cancelSubscriptionSharing(request: Request, response: Response): Promise<results.JsonResult> {
|
||||
const result = await this.subscriptionInvitesController.cancelInvite({
|
||||
...request.body,
|
||||
inviteUuid: request.params.inviteUuid,
|
||||
inviterEmail: response.locals.user.email,
|
||||
})
|
||||
|
||||
return this.json(result.data, result.status)
|
||||
}
|
||||
|
||||
@httpGet('/', TYPES.ApiGatewayAuthMiddleware)
|
||||
async listInvites(request: Request, response: Response): Promise<results.JsonResult> {
|
||||
const result = await this.subscriptionInvitesController.listInvites({
|
||||
...request.body,
|
||||
inviterEmail: response.locals.user.email,
|
||||
})
|
||||
|
||||
return this.json(result.data, result.status)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import { WebSocketServerInterface } from '@standardnotes/api'
|
||||
import { Request, Response } from 'express'
|
||||
import { inject } from 'inversify'
|
||||
import {
|
||||
BaseHttpController,
|
||||
controller,
|
||||
httpDelete,
|
||||
httpPost,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
results,
|
||||
} from 'inversify-express-utils'
|
||||
import TYPES from '../../Bootstrap/Types'
|
||||
import { AddWebSocketsConnection } from '../../Domain/UseCase/AddWebSocketsConnection/AddWebSocketsConnection'
|
||||
import { RemoveWebSocketsConnection } from '../../Domain/UseCase/RemoveWebSocketsConnection/RemoveWebSocketsConnection'
|
||||
|
||||
@controller('/sockets')
|
||||
export class InversifyExpressWebSocketsController extends BaseHttpController {
|
||||
constructor(
|
||||
@inject(TYPES.AddWebSocketsConnection) private addWebSocketsConnection: AddWebSocketsConnection,
|
||||
@inject(TYPES.RemoveWebSocketsConnection) private removeWebSocketsConnection: RemoveWebSocketsConnection,
|
||||
@inject(TYPES.WebSocketsController) private webSocketsController: WebSocketServerInterface,
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
||||
@httpPost('/:connectionId', TYPES.ApiGatewayAuthMiddleware)
|
||||
async storeWebSocketsConnection(
|
||||
request: Request,
|
||||
response: Response,
|
||||
): Promise<results.JsonResult | results.BadRequestErrorMessageResult> {
|
||||
await this.addWebSocketsConnection.execute({
|
||||
userUuid: response.locals.user.uuid,
|
||||
connectionId: request.params.connectionId,
|
||||
})
|
||||
|
||||
return this.json({ success: true })
|
||||
}
|
||||
|
||||
@httpDelete('/:connectionId')
|
||||
async deleteWebSocketsConnection(
|
||||
request: Request,
|
||||
): Promise<results.JsonResult | results.BadRequestErrorMessageResult> {
|
||||
await this.removeWebSocketsConnection.execute({ connectionId: request.params.connectionId })
|
||||
|
||||
return this.json({ success: true })
|
||||
}
|
||||
|
||||
@httpPost('/tokens', TYPES.ApiGatewayAuthMiddleware)
|
||||
async createConnectionToken(_request: Request, response: Response): Promise<results.JsonResult> {
|
||||
const result = await this.webSocketsController.createConnectionToken({
|
||||
userUuid: response.locals.user.uuid,
|
||||
})
|
||||
|
||||
return this.json(result)
|
||||
}
|
||||
}
|
||||
@@ -70,6 +70,23 @@ describe('MySQLSharedSubscriptionInvitationRepository', () => {
|
||||
expect(result).toEqual(invitation)
|
||||
})
|
||||
|
||||
it('should find one invitation by invitee and inviter email', async () => {
|
||||
queryBuilder.where = jest.fn().mockReturnThis()
|
||||
queryBuilder.getOne = jest.fn().mockReturnValue(invitation)
|
||||
|
||||
const result = await createRepository().findOneByInviteeAndInviterEmail('invitee@test.te', 'inviter@test.te')
|
||||
|
||||
expect(queryBuilder.where).toHaveBeenCalledWith(
|
||||
'invitation.inviter_identifier = :inviterEmail AND invitation.invitee_identifier = :inviteeEmail',
|
||||
{
|
||||
inviterEmail: 'inviter@test.te',
|
||||
inviteeEmail: 'invitee@test.te',
|
||||
},
|
||||
)
|
||||
|
||||
expect(result).toEqual(invitation)
|
||||
})
|
||||
|
||||
it('should find one invitation by uuid', async () => {
|
||||
queryBuilder.where = jest.fn().mockReturnThis()
|
||||
queryBuilder.getOne = jest.fn().mockReturnValue(invitation)
|
||||
|
||||
@@ -13,6 +13,19 @@ export class MySQLSharedSubscriptionInvitationRepository implements SharedSubscr
|
||||
private ormRepository: Repository<SharedSubscriptionInvitation>,
|
||||
) {}
|
||||
|
||||
async findOneByInviteeAndInviterEmail(
|
||||
inviteeEmail: string,
|
||||
inviterEmail: string,
|
||||
): Promise<SharedSubscriptionInvitation | null> {
|
||||
return this.ormRepository
|
||||
.createQueryBuilder('invitation')
|
||||
.where('invitation.inviter_identifier = :inviterEmail AND invitation.invitee_identifier = :inviteeEmail', {
|
||||
inviterEmail,
|
||||
inviteeEmail,
|
||||
})
|
||||
.getOne()
|
||||
}
|
||||
|
||||
async save(sharedSubscriptionInvitation: SharedSubscriptionInvitation): Promise<SharedSubscriptionInvitation> {
|
||||
return this.ormRepository.save(sharedSubscriptionInvitation)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -123,7 +138,8 @@ describe('MySQLUserSubscriptionRepository', () => {
|
||||
|
||||
expect(updateQueryBuilder.update).toHaveBeenCalled()
|
||||
expect(updateQueryBuilder.set).toHaveBeenCalledWith({
|
||||
updatedAt: expect.any(Number),
|
||||
updatedAt: 1000,
|
||||
renewedAt: 1000,
|
||||
endsAt: 1000,
|
||||
})
|
||||
expect(updateQueryBuilder.where).toHaveBeenCalledWith('subscription_id = :subscriptionId', {
|
||||
@@ -157,6 +173,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 +181,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])
|
||||
})
|
||||
@@ -172,6 +190,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().findBySubscriptionIdAndType(123, UserSubscriptionType.Regular)
|
||||
@@ -183,6 +202,7 @@ describe('MySQLUserSubscriptionRepository', () => {
|
||||
type: 'regular',
|
||||
},
|
||||
)
|
||||
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)
|
||||
}
|
||||
@@ -35,6 +44,7 @@ export class MySQLUserSubscriptionRepository implements UserSubscriptionReposito
|
||||
subscriptionId,
|
||||
type,
|
||||
})
|
||||
.orderBy('created_at', 'DESC')
|
||||
.getMany()
|
||||
}
|
||||
|
||||
@@ -44,6 +54,7 @@ export class MySQLUserSubscriptionRepository implements UserSubscriptionReposito
|
||||
.where('subscription_id = :subscriptionId', {
|
||||
subscriptionId,
|
||||
})
|
||||
.orderBy('created_at', 'DESC')
|
||||
.getMany()
|
||||
}
|
||||
|
||||
@@ -77,13 +88,14 @@ export class MySQLUserSubscriptionRepository implements UserSubscriptionReposito
|
||||
return null
|
||||
}
|
||||
|
||||
async updateEndsAt(subscriptionId: number, endsAt: number, updatedAt: number): Promise<void> {
|
||||
async updateEndsAt(subscriptionId: number, endsAt: number, timestamp: number): Promise<void> {
|
||||
await this.ormRepository
|
||||
.createQueryBuilder()
|
||||
.update()
|
||||
.set({
|
||||
endsAt,
|
||||
updatedAt,
|
||||
updatedAt: timestamp,
|
||||
renewedAt: timestamp,
|
||||
})
|
||||
.where('subscription_id = :subscriptionId', {
|
||||
subscriptionId,
|
||||
|
||||
@@ -3,6 +3,24 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [1.33.0](https://github.com/standardnotes/server/compare/@standardnotes/common@1.32.0...@standardnotes/common@1.33.0) (2022-09-19)
|
||||
|
||||
### Features
|
||||
|
||||
* **files:** add validating remote identifiers ([db15457](https://github.com/standardnotes/server/commit/db15457ce4eb533ec822cf93c3ed83eafe9e64d5))
|
||||
|
||||
# [1.32.0](https://github.com/standardnotes/server/compare/@standardnotes/common@1.31.0...@standardnotes/common@1.32.0) (2022-09-09)
|
||||
|
||||
### Features
|
||||
|
||||
* **common:** add either and only types ([c3ebb32](https://github.com/standardnotes/server/commit/c3ebb321cfacd20769ebfd99413e283859b6e260))
|
||||
|
||||
# [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.33.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",
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
import { Only } from './Only'
|
||||
|
||||
export type Either<T, U> = Only<T, U> | Only<U, T>
|
||||
@@ -0,0 +1,5 @@
|
||||
export type Only<T, U> = {
|
||||
[P in keyof T]: T[P]
|
||||
} & {
|
||||
[P in keyof U]?: never
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import { UuidValidator } from './UuidValidator'
|
||||
|
||||
describe('UuidValidator', () => {
|
||||
const createValidator = () => new UuidValidator()
|
||||
|
||||
const validUuids = [
|
||||
'2221101c-1da9-4d2b-9b32-b8be2a8d1c82',
|
||||
'c08f2f29-a74b-42b4-aefd-98af9832391c',
|
||||
'b453fa64-1493-443b-b5bb-bca7b9c696c7',
|
||||
]
|
||||
|
||||
const invalidUuids = [
|
||||
123,
|
||||
'someone@127.0.0.1',
|
||||
'',
|
||||
null,
|
||||
'b453fa64-1493-443b-b5bb-ca7b9c696c7',
|
||||
'c08f*f29-a74b-42b4-aefd-98af9832391c',
|
||||
'c08f*f29-a74b-42b4-aefd-98af9832391c',
|
||||
'../../escaped.sh',
|
||||
]
|
||||
|
||||
it('should validate proper uuids', () => {
|
||||
for (const validUuid of validUuids) {
|
||||
expect(createValidator().validate(validUuid)).toBeTruthy()
|
||||
}
|
||||
})
|
||||
|
||||
it('should not validate invalid uuids', () => {
|
||||
for (const invalidUuid of invalidUuids) {
|
||||
expect(createValidator().validate(invalidUuid as string)).toBeFalsy()
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,10 @@
|
||||
import { Uuid } from '../DataType/Uuid'
|
||||
import { ValidatorInterface } from './ValidatorInterface'
|
||||
|
||||
export class UuidValidator implements ValidatorInterface<Uuid> {
|
||||
private readonly UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-5][0-9a-f]{3}-[089ab][0-9a-f]{3}-[0-9a-f]{12}$/i
|
||||
|
||||
validate(data: Uuid): boolean {
|
||||
return String(data).toLowerCase().match(this.UUID_REGEX) !== null
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export interface ValidatorInterface<T> {
|
||||
validate(data: T): boolean
|
||||
}
|
||||
@@ -18,3 +18,7 @@ export * from './Protocol/ProtocolVersion'
|
||||
export * from './Role/PaidRoles'
|
||||
export * from './Role/RoleName'
|
||||
export * from './Subscription/SubscriptionName'
|
||||
export * from './Type/Either'
|
||||
export * from './Type/Only'
|
||||
export * from './Validator/UuidValidator'
|
||||
export * from './Validator/ValidatorInterface'
|
||||
|
||||
@@ -3,6 +3,72 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.8.12](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.8.11...@standardnotes/domain-events-infra@1.8.12) (2022-09-21)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/domain-events-infra
|
||||
|
||||
## [1.8.11](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.8.10...@standardnotes/domain-events-infra@1.8.11) (2022-09-19)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/domain-events-infra
|
||||
|
||||
## [1.8.10](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.8.9...@standardnotes/domain-events-infra@1.8.10) (2022-09-16)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/domain-events-infra
|
||||
|
||||
## [1.8.9](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.8.8...@standardnotes/domain-events-infra@1.8.9) (2022-09-09)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/domain-events-infra
|
||||
|
||||
## [1.8.8](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.8.7...@standardnotes/domain-events-infra@1.8.8) (2022-09-08)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/domain-events-infra
|
||||
|
||||
## [1.8.7](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.8.6...@standardnotes/domain-events-infra@1.8.7) (2022-09-08)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/domain-events-infra
|
||||
|
||||
## [1.8.6](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.8.5...@standardnotes/domain-events-infra@1.8.6) (2022-09-08)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/domain-events-infra
|
||||
|
||||
## [1.8.5](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.8.4...@standardnotes/domain-events-infra@1.8.5) (2022-09-08)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/domain-events-infra
|
||||
|
||||
## [1.8.4](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.8.3...@standardnotes/domain-events-infra@1.8.4) (2022-09-08)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/domain-events-infra
|
||||
|
||||
## [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,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/domain-events-infra",
|
||||
"version": "1.7.34",
|
||||
"version": "1.8.12",
|
||||
"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,95 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [2.60.6](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.60.5...@standardnotes/domain-events@2.60.6) (2022-09-21)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/domain-events
|
||||
|
||||
## [2.60.5](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.60.4...@standardnotes/domain-events@2.60.5) (2022-09-19)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/domain-events
|
||||
|
||||
## [2.60.4](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.60.3...@standardnotes/domain-events@2.60.4) (2022-09-16)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/domain-events
|
||||
|
||||
## [2.60.3](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.60.2...@standardnotes/domain-events@2.60.3) (2022-09-09)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/domain-events
|
||||
|
||||
## [2.60.2](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.60.1...@standardnotes/domain-events@2.60.2) (2022-09-08)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **api-gateway:** retention data structure to include both period keys ([50ddb91](https://github.com/standardnotes/server/commit/50ddb918ccc52bee4caad82504cb899bc5936150))
|
||||
|
||||
## [2.60.1](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.60.0...@standardnotes/domain-events@2.60.1) (2022-09-08)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **api-gateway:** retention data structure ([47be084](https://github.com/standardnotes/server/commit/47be0841fc6d5fa00892e775bb3a40f404a6382b))
|
||||
|
||||
# [2.60.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.59.3...@standardnotes/domain-events@2.60.0) (2022-09-08)
|
||||
|
||||
### Features
|
||||
|
||||
* **api-gateway:** add registration-to-activity retention analytics to report ([f139bb0](https://github.com/standardnotes/server/commit/f139bb003669bb41f98ad4bb59a036c489f43606))
|
||||
|
||||
## [2.59.3](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.59.2...@standardnotes/domain-events@2.59.3) (2022-09-08)
|
||||
|
||||
### Reverts
|
||||
|
||||
* Revert "fix(domain-events): add boolean for sending emails on refund processed" ([fe4821d](https://github.com/standardnotes/server/commit/fe4821d4f7df38297cb92314b9cd3fde3d2c58b6))
|
||||
|
||||
## [2.59.2](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.59.1...@standardnotes/domain-events@2.59.2) (2022-09-08)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **domain-events:** add boolean for sending emails on refund processed ([d7e6758](https://github.com/standardnotes/server/commit/d7e6758089c5e9485fda345949ec8d58732afa90))
|
||||
|
||||
## [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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/domain-events",
|
||||
"version": "2.54.2",
|
||||
"version": "2.60.6",
|
||||
"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
|
||||
@@ -22,4 +28,16 @@ export interface DailyAnalyticsReportGeneratedEventPayload {
|
||||
totalCount: number
|
||||
}>
|
||||
outOfSyncIncidents: number
|
||||
retentionStatistics: Array<{
|
||||
firstActivity: string
|
||||
secondActivity: string
|
||||
retention: {
|
||||
periodKeys: Array<string>
|
||||
values: Array<{
|
||||
firstPeriodKey: string
|
||||
secondPeriodKey: string
|
||||
value: number
|
||||
}>
|
||||
}
|
||||
}>
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export enum DomainEventService {
|
||||
AdminPanel = 'admin-panel',
|
||||
Auth = 'auth',
|
||||
SyncingServer = 'syncing-server',
|
||||
Payments = 'payments',
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export interface PaymentSuccessEventPayload {
|
||||
userEmail: string
|
||||
amount: number
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export interface RefundProcessedEventPayload {
|
||||
userEmail: string
|
||||
amount: number
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -86,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,92 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.3.17](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.3.16...@standardnotes/event-store@1.3.17) (2022-09-21)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/event-store
|
||||
|
||||
## [1.3.16](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.3.15...@standardnotes/event-store@1.3.16) (2022-09-19)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/event-store
|
||||
|
||||
## [1.3.15](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.3.14...@standardnotes/event-store@1.3.15) (2022-09-16)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/event-store
|
||||
|
||||
## [1.3.14](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.3.13...@standardnotes/event-store@1.3.14) (2022-09-09)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/event-store
|
||||
|
||||
## [1.3.13](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.3.12...@standardnotes/event-store@1.3.13) (2022-09-09)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **event-store:** add missing event subscriptions ([432d071](https://github.com/standardnotes/server/commit/432d071ec88a49f90513be6c55a06005a471b174))
|
||||
|
||||
## [1.3.12](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.3.11...@standardnotes/event-store@1.3.12) (2022-09-08)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **event-store:** add listening to refund processed event ([73e1ea7](https://github.com/standardnotes/server/commit/73e1ea7f93b7d7956dd4a82298098e81ff9c85b1))
|
||||
|
||||
## [1.3.11](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.3.10...@standardnotes/event-store@1.3.11) (2022-09-08)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/event-store
|
||||
|
||||
## [1.3.10](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.3.9...@standardnotes/event-store@1.3.10) (2022-09-08)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/event-store
|
||||
|
||||
## [1.3.9](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.3.8...@standardnotes/event-store@1.3.9) (2022-09-08)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/event-store
|
||||
|
||||
## [1.3.8](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.3.7...@standardnotes/event-store@1.3.8) (2022-09-08)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/event-store
|
||||
|
||||
## [1.3.7](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.3.6...@standardnotes/event-store@1.3.7) (2022-09-08)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/event-store
|
||||
|
||||
## [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,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/event-store",
|
||||
"version": "1.2.1",
|
||||
"version": "1.3.17",
|
||||
"description": "Event Store Service",
|
||||
"private": true,
|
||||
"main": "dist/src/index.js",
|
||||
|
||||
@@ -79,6 +79,13 @@ export class ContainerConfigLoader {
|
||||
['PAYMENT_FAILED', container.get(TYPES.EventHandler)],
|
||||
['PAYMENT_SUCCESS', container.get(TYPES.EventHandler)],
|
||||
['ACCOUNT_CLAIM_REQUESTED', container.get(TYPES.EventHandler)],
|
||||
['SUBSCRIPTION_REVERT_REQUESTED', container.get(TYPES.EventHandler)],
|
||||
['REFUND_PROCESSED', container.get(TYPES.EventHandler)],
|
||||
['ACCOUNT_RESET_REQUESTED', container.get(TYPES.EventHandler)],
|
||||
['DISCOUNT_APPLIED', container.get(TYPES.EventHandler)],
|
||||
['SUBSCRIPTION_RATE_ADJUSTED', container.get(TYPES.EventHandler)],
|
||||
['REFUND_REQUESTED', container.get(TYPES.EventHandler)],
|
||||
['INVOICE_GENERATED', container.get(TYPES.EventHandler)],
|
||||
])
|
||||
|
||||
container
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user