mirror of
https://github.com/standardnotes/server
synced 2026-03-23 12:01:14 -04:00
Compare commits
7 Commits
@standardn
...
settings_s
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
55f8f65c3f | ||
|
|
3953dbc6b4 | ||
|
|
0b205287d1 | ||
|
|
4f0bc57b1a | ||
|
|
7d43316597 | ||
|
|
65d31f011b | ||
|
|
80dd6efae3 |
@@ -190,9 +190,9 @@ jobs:
|
||||
uses: convictional/trigger-workflow-and-wait@master
|
||||
with:
|
||||
owner: standardnotes
|
||||
repo: self-hosted
|
||||
repo: e2e
|
||||
github_token: ${{ secrets.CI_PAT_TOKEN }}
|
||||
workflow_file_name: testing-with-updating-client-and-server.yml
|
||||
workflow_file_name: testing-with-stable-client.yml
|
||||
wait_interval: 30
|
||||
client_payload: '{"${{ inputs.e2e_tag_parameter_name }}": "${{ github.sha }}"}'
|
||||
propagate_failure: true
|
||||
|
||||
505
.pnp.cjs
generated
505
.pnp.cjs
generated
@@ -66,7 +66,7 @@ const RAW_RUNTIME_STATE =
|
||||
"reference": "workspace:packages/security"\
|
||||
},\
|
||||
{\
|
||||
"name": "@standardnotes/settings",\
|
||||
"name": "@standardnotes/settings-server",\
|
||||
"reference": "workspace:packages/settings"\
|
||||
},\
|
||||
{\
|
||||
@@ -107,7 +107,7 @@ const RAW_RUNTIME_STATE =
|
||||
["@standardnotes/scheduler-server", ["workspace:packages/scheduler"]],\
|
||||
["@standardnotes/security", ["workspace:packages/security"]],\
|
||||
["@standardnotes/server-monorepo", ["workspace:."]],\
|
||||
["@standardnotes/settings", ["workspace:packages/settings"]],\
|
||||
["@standardnotes/settings-server", ["workspace:packages/settings"]],\
|
||||
["@standardnotes/sncrypto-node", ["workspace:packages/sncrypto-node"]],\
|
||||
["@standardnotes/syncing-server", ["workspace:packages/syncing-server"]],\
|
||||
["@standardnotes/time", ["workspace:packages/time"]],\
|
||||
@@ -126,7 +126,7 @@ const RAW_RUNTIME_STATE =
|
||||
["@lerna-lite/cli", "npm:1.6.0"],\
|
||||
["@lerna-lite/list", "npm:1.6.0"],\
|
||||
["@lerna-lite/run", "npm:1.6.0"],\
|
||||
["@sentry/node", "npm:7.28.1"],\
|
||||
["@sentry/node", "npm:7.19.0"],\
|
||||
["@types/jest", "npm:29.1.1"],\
|
||||
["@types/newrelic", "npm:7.0.4"],\
|
||||
["@types/node", "npm:18.11.9"],\
|
||||
@@ -1968,15 +1968,6 @@ const RAW_RUNTIME_STATE =
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["@noble/ed25519", [\
|
||||
["npm:1.7.1", {\
|
||||
"packageLocation": "./.yarn/cache/@noble-ed25519-npm-1.7.1-177d9beb01-b1aa4b9264.zip/node_modules/@noble/ed25519/",\
|
||||
"packageDependencies": [\
|
||||
["@noble/ed25519", "npm:1.7.1"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["@nodelib/fs.scandir", [\
|
||||
["npm:2.1.5", {\
|
||||
"packageLocation": "./.yarn/cache/@nodelib-fs.scandir-npm-2.1.5-89c67370dd-5f309a3b37.zip/node_modules/@nodelib/fs.scandir/",\
|
||||
@@ -2333,44 +2324,6 @@ const RAW_RUNTIME_STATE =
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["@peculiar/asn1-android", [\
|
||||
["npm:2.3.3", {\
|
||||
"packageLocation": "./.yarn/cache/@peculiar-asn1-android-npm-2.3.3-28df67d7a3-0c7cad544e.zip/node_modules/@peculiar/asn1-android/",\
|
||||
"packageDependencies": [\
|
||||
["@peculiar/asn1-android", "npm:2.3.3"],\
|
||||
["@peculiar/asn1-schema", "npm:2.3.3"],\
|
||||
["asn1js", "npm:3.0.5"],\
|
||||
["tslib", "npm:2.4.1"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["@peculiar/asn1-schema", [\
|
||||
["npm:2.3.3", {\
|
||||
"packageLocation": "./.yarn/cache/@peculiar-asn1-schema-npm-2.3.3-7c2b9469c4-f584f79d5a.zip/node_modules/@peculiar/asn1-schema/",\
|
||||
"packageDependencies": [\
|
||||
["@peculiar/asn1-schema", "npm:2.3.3"],\
|
||||
["asn1js", "npm:3.0.5"],\
|
||||
["pvtsutils", "npm:1.3.2"],\
|
||||
["tslib", "npm:2.4.1"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["@peculiar/asn1-x509", [\
|
||||
["npm:2.3.4", {\
|
||||
"packageLocation": "./.yarn/cache/@peculiar-asn1-x509-npm-2.3.4-a579005836-10a8659980.zip/node_modules/@peculiar/asn1-x509/",\
|
||||
"packageDependencies": [\
|
||||
["@peculiar/asn1-x509", "npm:2.3.4"],\
|
||||
["@peculiar/asn1-schema", "npm:2.3.3"],\
|
||||
["asn1js", "npm:3.0.5"],\
|
||||
["ipaddr.js", "npm:2.0.1"],\
|
||||
["pvtsutils", "npm:1.3.2"],\
|
||||
["tslib", "npm:2.4.1"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["@pnpm/network.ca-file", [\
|
||||
["npm:1.0.1", {\
|
||||
"packageLocation": "./.yarn/cache/@pnpm-network.ca-file-npm-1.0.1-42bfe40bec-ed952a5574.zip/node_modules/@pnpm/network.ca-file/",\
|
||||
@@ -2485,25 +2438,25 @@ const RAW_RUNTIME_STATE =
|
||||
}]\
|
||||
]],\
|
||||
["@sentry/core", [\
|
||||
["npm:7.28.1", {\
|
||||
"packageLocation": "./.yarn/cache/@sentry-core-npm-7.28.1-a468033ea8-f29d747d3e.zip/node_modules/@sentry/core/",\
|
||||
["npm:7.19.0", {\
|
||||
"packageLocation": "./.yarn/cache/@sentry-core-npm-7.19.0-151e6173ac-cabd7852ff.zip/node_modules/@sentry/core/",\
|
||||
"packageDependencies": [\
|
||||
["@sentry/core", "npm:7.28.1"],\
|
||||
["@sentry/types", "npm:7.28.1"],\
|
||||
["@sentry/utils", "npm:7.28.1"],\
|
||||
["@sentry/core", "npm:7.19.0"],\
|
||||
["@sentry/types", "npm:7.19.0"],\
|
||||
["@sentry/utils", "npm:7.19.0"],\
|
||||
["tslib", "npm:1.14.1"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["@sentry/node", [\
|
||||
["npm:7.28.1", {\
|
||||
"packageLocation": "./.yarn/cache/@sentry-node-npm-7.28.1-b0e124fdfc-b4922d1f0a.zip/node_modules/@sentry/node/",\
|
||||
["npm:7.19.0", {\
|
||||
"packageLocation": "./.yarn/cache/@sentry-node-npm-7.19.0-fd3d8dbde1-3a69647d2e.zip/node_modules/@sentry/node/",\
|
||||
"packageDependencies": [\
|
||||
["@sentry/node", "npm:7.28.1"],\
|
||||
["@sentry/core", "npm:7.28.1"],\
|
||||
["@sentry/types", "npm:7.28.1"],\
|
||||
["@sentry/utils", "npm:7.28.1"],\
|
||||
["@sentry/node", "npm:7.19.0"],\
|
||||
["@sentry/core", "npm:7.19.0"],\
|
||||
["@sentry/types", "npm:7.19.0"],\
|
||||
["@sentry/utils", "npm:7.19.0"],\
|
||||
["cookie", "npm:0.4.2"],\
|
||||
["https-proxy-agent", "npm:5.0.1"],\
|
||||
["lru_map", "npm:0.3.3"],\
|
||||
@@ -2512,67 +2465,26 @@ const RAW_RUNTIME_STATE =
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["@sentry/tracing", [\
|
||||
["npm:7.28.1", {\
|
||||
"packageLocation": "./.yarn/cache/@sentry-tracing-npm-7.28.1-e15d453d8e-be501ca9d7.zip/node_modules/@sentry/tracing/",\
|
||||
"packageDependencies": [\
|
||||
["@sentry/tracing", "npm:7.28.1"],\
|
||||
["@sentry/core", "npm:7.28.1"],\
|
||||
["@sentry/types", "npm:7.28.1"],\
|
||||
["@sentry/utils", "npm:7.28.1"],\
|
||||
["tslib", "npm:1.14.1"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["@sentry/types", [\
|
||||
["npm:7.28.1", {\
|
||||
"packageLocation": "./.yarn/cache/@sentry-types-npm-7.28.1-42d9a8574c-7dc6639cb7.zip/node_modules/@sentry/types/",\
|
||||
["npm:7.19.0", {\
|
||||
"packageLocation": "./.yarn/cache/@sentry-types-npm-7.19.0-d6ed1960f2-541e1ef49a.zip/node_modules/@sentry/types/",\
|
||||
"packageDependencies": [\
|
||||
["@sentry/types", "npm:7.28.1"]\
|
||||
["@sentry/types", "npm:7.19.0"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["@sentry/utils", [\
|
||||
["npm:7.28.1", {\
|
||||
"packageLocation": "./.yarn/cache/@sentry-utils-npm-7.28.1-71eaeb767f-a4b5f73db0.zip/node_modules/@sentry/utils/",\
|
||||
["npm:7.19.0", {\
|
||||
"packageLocation": "./.yarn/cache/@sentry-utils-npm-7.19.0-79844d4d90-50e4f391fe.zip/node_modules/@sentry/utils/",\
|
||||
"packageDependencies": [\
|
||||
["@sentry/utils", "npm:7.28.1"],\
|
||||
["@sentry/types", "npm:7.28.1"],\
|
||||
["@sentry/utils", "npm:7.19.0"],\
|
||||
["@sentry/types", "npm:7.19.0"],\
|
||||
["tslib", "npm:1.14.1"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["@simplewebauthn/server", [\
|
||||
["npm:6.2.2", {\
|
||||
"packageLocation": "./.yarn/cache/@simplewebauthn-server-npm-6.2.2-ca870b05c2-5ffb9b1c15.zip/node_modules/@simplewebauthn/server/",\
|
||||
"packageDependencies": [\
|
||||
["@simplewebauthn/server", "npm:6.2.2"],\
|
||||
["@noble/ed25519", "npm:1.7.1"],\
|
||||
["@peculiar/asn1-android", "npm:2.3.3"],\
|
||||
["@peculiar/asn1-schema", "npm:2.3.3"],\
|
||||
["@peculiar/asn1-x509", "npm:2.3.4"],\
|
||||
["base64url", "npm:3.0.1"],\
|
||||
["cbor", "npm:5.2.0"],\
|
||||
["debug", "virtual:b86a9fb34323a98c6519528ed55faa0d9b44ca8879307c0b29aa384bde47ff59a7d0c9051b31246f14521dfb71ba3c5d6d0b35c29fffc17bf875aa6ad977d9e8#npm:4.3.4"],\
|
||||
["jsrsasign", "npm:10.6.1"],\
|
||||
["jwk-to-pem", "npm:2.0.5"],\
|
||||
["node-fetch", "virtual:25a5f5382d53dbf298bf7a1191760bc2e0a523a619eeb0e667b99a8649e8ad183f9e2e0b45f6fb831b92f4078b61622aa567cf79565f6aa5af9597e3c84864f6#npm:2.6.7"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["@simplewebauthn/typescript-types", [\
|
||||
["npm:6.3.0-alpha.1", {\
|
||||
"packageLocation": "./.yarn/cache/@simplewebauthn-typescript-types-npm-6.3.0-alpha.1-629da05c10-5667c214e9.zip/node_modules/@simplewebauthn/typescript-types/",\
|
||||
"packageDependencies": [\
|
||||
["@simplewebauthn/typescript-types", "npm:6.3.0-alpha.1"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["@sinclair/typebox", [\
|
||||
["npm:0.24.44", {\
|
||||
"packageLocation": "./.yarn/cache/@sinclair-typebox-npm-0.24.44-38506ddef6-f37b9d28bf.zip/node_modules/@sinclair/typebox/",\
|
||||
@@ -2626,7 +2538,7 @@ const RAW_RUNTIME_STATE =
|
||||
"packageDependencies": [\
|
||||
["@standardnotes/analytics", "workspace:packages/analytics"],\
|
||||
["@newrelic/winston-enricher", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:4.0.0"],\
|
||||
["@sentry/node", "npm:7.28.1"],\
|
||||
["@sentry/node", "npm:7.19.0"],\
|
||||
["@standardnotes/common", "workspace:packages/common"],\
|
||||
["@standardnotes/domain-core", "workspace:packages/domain-core"],\
|
||||
["@standardnotes/domain-events", "workspace:packages/domain-events"],\
|
||||
@@ -2634,7 +2546,6 @@ const RAW_RUNTIME_STATE =
|
||||
["@standardnotes/time", "workspace:packages/time"],\
|
||||
["@types/ioredis", "npm:5.0.0"],\
|
||||
["@types/jest", "npm:29.1.1"],\
|
||||
["@types/mixpanel", "npm:2.14.4"],\
|
||||
["@types/newrelic", "npm:7.0.4"],\
|
||||
["@types/node", "npm:18.11.9"],\
|
||||
["@typescript-eslint/eslint-plugin", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:5.30.5"],\
|
||||
@@ -2646,7 +2557,6 @@ const RAW_RUNTIME_STATE =
|
||||
["inversify", "npm:6.0.1"],\
|
||||
["ioredis", "npm:5.2.4"],\
|
||||
["jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.1.2"],\
|
||||
["mixpanel", "npm:0.17.0"],\
|
||||
["mysql2", "npm:2.3.3"],\
|
||||
["newrelic", "npm:9.6.0"],\
|
||||
["reflect-metadata", "npm:0.1.13"],\
|
||||
@@ -2672,6 +2582,20 @@ const RAW_RUNTIME_STATE =
|
||||
["reflect-metadata", "npm:0.1.13"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}],\
|
||||
["npm:1.20.13", {\
|
||||
"packageLocation": "./.yarn/cache/@standardnotes-api-npm-1.20.13-3efe52d749-67bdb982ec.zip/node_modules/@standardnotes/api/",\
|
||||
"packageDependencies": [\
|
||||
["@standardnotes/api", "npm:1.20.13"],\
|
||||
["@standardnotes/common", "workspace:packages/common"],\
|
||||
["@standardnotes/encryption", "npm:1.19.21"],\
|
||||
["@standardnotes/models", "npm:1.38.0"],\
|
||||
["@standardnotes/responses", "npm:1.12.9"],\
|
||||
["@standardnotes/security", "workspace:packages/security"],\
|
||||
["@standardnotes/utils", "npm:1.13.0"],\
|
||||
["reflect-metadata", "npm:0.1.13"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["@standardnotes/api-gateway", [\
|
||||
@@ -2680,7 +2604,7 @@ const RAW_RUNTIME_STATE =
|
||||
"packageDependencies": [\
|
||||
["@standardnotes/api-gateway", "workspace:packages/api-gateway"],\
|
||||
["@newrelic/winston-enricher", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:4.0.0"],\
|
||||
["@sentry/node", "npm:7.28.1"],\
|
||||
["@sentry/node", "npm:7.19.0"],\
|
||||
["@standardnotes/common", "workspace:packages/common"],\
|
||||
["@standardnotes/domain-events", "workspace:packages/domain-events"],\
|
||||
["@standardnotes/domain-events-infra", "workspace:packages/domain-events-infra"],\
|
||||
@@ -2736,10 +2660,7 @@ const RAW_RUNTIME_STATE =
|
||||
"packageDependencies": [\
|
||||
["@standardnotes/auth-server", "workspace:packages/auth"],\
|
||||
["@newrelic/winston-enricher", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:4.0.0"],\
|
||||
["@sentry/node", "npm:7.28.1"],\
|
||||
["@sentry/tracing", "npm:7.28.1"],\
|
||||
["@simplewebauthn/server", "npm:6.2.2"],\
|
||||
["@simplewebauthn/typescript-types", "npm:6.3.0-alpha.1"],\
|
||||
["@sentry/node", "npm:7.19.0"],\
|
||||
["@standardnotes/api", "npm:1.19.0"],\
|
||||
["@standardnotes/common", "workspace:packages/common"],\
|
||||
["@standardnotes/domain-core", "workspace:packages/domain-core"],\
|
||||
@@ -2749,7 +2670,6 @@ const RAW_RUNTIME_STATE =
|
||||
["@standardnotes/predicates", "workspace:packages/predicates"],\
|
||||
["@standardnotes/responses", "npm:1.11.1"],\
|
||||
["@standardnotes/security", "workspace:packages/security"],\
|
||||
["@standardnotes/settings", "workspace:packages/settings"],\
|
||||
["@standardnotes/sncrypto-common", "npm:1.13.0"],\
|
||||
["@standardnotes/sncrypto-node", "workspace:packages/sncrypto-node"],\
|
||||
["@standardnotes/time", "workspace:packages/time"],\
|
||||
@@ -2830,6 +2750,10 @@ const RAW_RUNTIME_STATE =
|
||||
"packageLocation": "./packages/domain-core/",\
|
||||
"packageDependencies": [\
|
||||
["@standardnotes/domain-core", "workspace:packages/domain-core"],\
|
||||
["@standardnotes/common", "workspace:packages/common"],\
|
||||
["@standardnotes/features", "npm:1.53.1"],\
|
||||
["@standardnotes/predicates", "workspace:packages/predicates"],\
|
||||
["@standardnotes/security", "workspace:packages/security"],\
|
||||
["@types/jest", "npm:29.1.1"],\
|
||||
["@types/uuid", "npm:8.3.4"],\
|
||||
["@typescript-eslint/eslint-plugin", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:5.30.5"],\
|
||||
@@ -2901,6 +2825,19 @@ const RAW_RUNTIME_STATE =
|
||||
["reflect-metadata", "npm:0.1.13"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}],\
|
||||
["npm:1.19.21", {\
|
||||
"packageLocation": "./.yarn/cache/@standardnotes-encryption-npm-1.19.21-dfa10f00e6-c8c2c27bfe.zip/node_modules/@standardnotes/encryption/",\
|
||||
"packageDependencies": [\
|
||||
["@standardnotes/encryption", "npm:1.19.21"],\
|
||||
["@standardnotes/common", "workspace:packages/common"],\
|
||||
["@standardnotes/models", "npm:1.38.0"],\
|
||||
["@standardnotes/responses", "npm:1.12.9"],\
|
||||
["@standardnotes/sncrypto-common", "npm:1.13.3"],\
|
||||
["@standardnotes/utils", "npm:1.13.0"],\
|
||||
["reflect-metadata", "npm:0.1.13"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["@standardnotes/event-store", [\
|
||||
@@ -2956,6 +2893,17 @@ const RAW_RUNTIME_STATE =
|
||||
["reflect-metadata", "npm:0.1.13"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}],\
|
||||
["npm:1.55.3", {\
|
||||
"packageLocation": "./.yarn/cache/@standardnotes-features-npm-1.55.3-c124505183-b39fe2d49b.zip/node_modules/@standardnotes/features/",\
|
||||
"packageDependencies": [\
|
||||
["@standardnotes/features", "npm:1.55.3"],\
|
||||
["@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", [\
|
||||
@@ -2963,7 +2911,7 @@ const RAW_RUNTIME_STATE =
|
||||
"packageLocation": "./packages/files/",\
|
||||
"packageDependencies": [\
|
||||
["@standardnotes/files-server", "workspace:packages/files"],\
|
||||
["@sentry/node", "npm:7.28.1"],\
|
||||
["@sentry/node", "npm:7.19.0"],\
|
||||
["@standardnotes/common", "workspace:packages/common"],\
|
||||
["@standardnotes/config", "npm:2.4.3"],\
|
||||
["@standardnotes/domain-events", "workspace:packages/domain-events"],\
|
||||
@@ -3037,6 +2985,13 @@ const RAW_RUNTIME_STATE =
|
||||
["reflect-metadata", "npm:0.1.13"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}],\
|
||||
["npm:1.38.0", {\
|
||||
"packageLocation": "./.yarn/cache/@standardnotes-models-npm-1.38.0-108f602f56-2dc2ac957e.zip/node_modules/@standardnotes/models/",\
|
||||
"packageDependencies": [\
|
||||
["@standardnotes/models", "npm:1.38.0"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["@standardnotes/payloads", [\
|
||||
@@ -3090,6 +3045,17 @@ const RAW_RUNTIME_STATE =
|
||||
["reflect-metadata", "npm:0.1.13"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}],\
|
||||
["npm:1.12.9", {\
|
||||
"packageLocation": "./.yarn/cache/@standardnotes-responses-npm-1.12.9-280dc75972-353fe1ca6d.zip/node_modules/@standardnotes/responses/",\
|
||||
"packageDependencies": [\
|
||||
["@standardnotes/responses", "npm:1.12.9"],\
|
||||
["@standardnotes/common", "workspace:packages/common"],\
|
||||
["@standardnotes/features", "npm:1.55.3"],\
|
||||
["@standardnotes/security", "workspace:packages/security"],\
|
||||
["reflect-metadata", "npm:0.1.13"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["@standardnotes/revisions-server", [\
|
||||
@@ -3098,7 +3064,7 @@ const RAW_RUNTIME_STATE =
|
||||
"packageDependencies": [\
|
||||
["@standardnotes/revisions-server", "workspace:packages/revisions"],\
|
||||
["@newrelic/winston-enricher", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:4.0.0"],\
|
||||
["@sentry/node", "npm:7.28.1"],\
|
||||
["@sentry/node", "npm:7.19.0"],\
|
||||
["@standardnotes/api", "npm:1.19.0"],\
|
||||
["@standardnotes/common", "workspace:packages/common"],\
|
||||
["@standardnotes/domain-core", "workspace:packages/domain-core"],\
|
||||
@@ -3143,7 +3109,7 @@ const RAW_RUNTIME_STATE =
|
||||
"packageDependencies": [\
|
||||
["@standardnotes/scheduler-server", "workspace:packages/scheduler"],\
|
||||
["@newrelic/winston-enricher", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:4.0.0"],\
|
||||
["@sentry/node", "npm:7.28.1"],\
|
||||
["@sentry/node", "npm:7.19.0"],\
|
||||
["@standardnotes/common", "workspace:packages/common"],\
|
||||
["@standardnotes/domain-core", "workspace:packages/domain-core"],\
|
||||
["@standardnotes/domain-events", "workspace:packages/domain-events"],\
|
||||
@@ -3204,7 +3170,7 @@ const RAW_RUNTIME_STATE =
|
||||
["@lerna-lite/cli", "npm:1.6.0"],\
|
||||
["@lerna-lite/list", "npm:1.6.0"],\
|
||||
["@lerna-lite/run", "npm:1.6.0"],\
|
||||
["@sentry/node", "npm:7.28.1"],\
|
||||
["@sentry/node", "npm:7.19.0"],\
|
||||
["@types/jest", "npm:29.1.1"],\
|
||||
["@types/newrelic", "npm:7.0.4"],\
|
||||
["@types/node", "npm:18.11.9"],\
|
||||
@@ -3221,15 +3187,46 @@ const RAW_RUNTIME_STATE =
|
||||
"linkType": "SOFT"\
|
||||
}]\
|
||||
]],\
|
||||
["@standardnotes/settings", [\
|
||||
["@standardnotes/settings-server", [\
|
||||
["workspace:packages/settings", {\
|
||||
"packageLocation": "./packages/settings/",\
|
||||
"packageDependencies": [\
|
||||
["@standardnotes/settings", "workspace:packages/settings"],\
|
||||
["@typescript-eslint/eslint-plugin", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:5.30.5"],\
|
||||
["eslint-plugin-prettier", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:4.2.1"],\
|
||||
["@standardnotes/settings-server", "workspace:packages/settings"],\
|
||||
["@newrelic/winston-enricher", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:4.0.0"],\
|
||||
["@sentry/node", "npm:7.19.0"],\
|
||||
["@standardnotes/api", "npm:1.20.13"],\
|
||||
["@standardnotes/domain-core", "workspace:packages/domain-core"],\
|
||||
["@standardnotes/domain-events", "workspace:packages/domain-events"],\
|
||||
["@standardnotes/domain-events-infra", "workspace:packages/domain-events-infra"],\
|
||||
["@standardnotes/security", "workspace:packages/security"],\
|
||||
["@standardnotes/time", "workspace:packages/time"],\
|
||||
["@types/cors", "npm:2.8.12"],\
|
||||
["@types/dotenv", "npm:8.2.0"],\
|
||||
["@types/express", "npm:4.17.14"],\
|
||||
["@types/inversify-express-utils", "npm:2.0.0"],\
|
||||
["@types/ioredis", "npm:5.0.0"],\
|
||||
["@types/jest", "npm:29.1.1"],\
|
||||
["@types/newrelic", "npm:7.0.4"],\
|
||||
["@typescript-eslint/eslint-plugin", "virtual:04783e12400851b8a3d76e71495851cc94959db6e62f04cb0a31190080629440b182d8c8eb4d7f2b04e281912f2783a5fd4d2c3c6ab68d38b7097246c93f4c19#npm:5.40.1"],\
|
||||
["aws-sdk", "npm:2.1260.0"],\
|
||||
["cors", "npm:2.8.5"],\
|
||||
["dotenv", "npm:16.0.1"],\
|
||||
["eslint", "npm:8.25.0"],\
|
||||
["eslint-plugin-prettier", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:4.2.1"],\
|
||||
["express", "npm:4.18.2"],\
|
||||
["helmet", "npm:6.0.0"],\
|
||||
["inversify", "npm:6.0.1"],\
|
||||
["inversify-express-utils", "npm:6.4.3"],\
|
||||
["ioredis", "npm:5.2.4"],\
|
||||
["jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.1.2"],\
|
||||
["mysql2", "npm:2.3.3"],\
|
||||
["newrelic", "npm:9.6.0"],\
|
||||
["npm-check-updates", "npm:16.0.1"],\
|
||||
["reflect-metadata", "npm:0.1.13"],\
|
||||
["typescript", "patch:typescript@npm%3A4.8.4#optional!builtin<compat/typescript>::version=4.8.4&hash=701156"]\
|
||||
["ts-jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.0.3"],\
|
||||
["typeorm", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:0.3.10"],\
|
||||
["typescript", "patch:typescript@npm%3A4.8.4#optional!builtin<compat/typescript>::version=4.8.4&hash=701156"],\
|
||||
["winston", "npm:3.8.2"]\
|
||||
],\
|
||||
"linkType": "SOFT"\
|
||||
}]\
|
||||
@@ -3242,6 +3239,14 @@ const RAW_RUNTIME_STATE =
|
||||
["reflect-metadata", "npm:0.1.13"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}],\
|
||||
["npm:1.13.3", {\
|
||||
"packageLocation": "./.yarn/cache/@standardnotes-sncrypto-common-npm-1.13.3-97ef3850ce-a73af90962.zip/node_modules/@standardnotes/sncrypto-common/",\
|
||||
"packageDependencies": [\
|
||||
["@standardnotes/sncrypto-common", "npm:1.13.3"],\
|
||||
["reflect-metadata", "npm:0.1.13"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["@standardnotes/sncrypto-node", [\
|
||||
@@ -3270,8 +3275,7 @@ const RAW_RUNTIME_STATE =
|
||||
"packageDependencies": [\
|
||||
["@standardnotes/syncing-server", "workspace:packages/syncing-server"],\
|
||||
["@newrelic/winston-enricher", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:4.0.0"],\
|
||||
["@sentry/node", "npm:7.28.1"],\
|
||||
["@sentry/tracing", "npm:7.28.1"],\
|
||||
["@sentry/node", "npm:7.19.0"],\
|
||||
["@standardnotes/common", "workspace:packages/common"],\
|
||||
["@standardnotes/domain-core", "workspace:packages/domain-core"],\
|
||||
["@standardnotes/domain-events", "workspace:packages/domain-events"],\
|
||||
@@ -3279,7 +3283,6 @@ const RAW_RUNTIME_STATE =
|
||||
["@standardnotes/payloads", "npm:1.5.1"],\
|
||||
["@standardnotes/responses", "npm:1.11.1"],\
|
||||
["@standardnotes/security", "workspace:packages/security"],\
|
||||
["@standardnotes/settings", "workspace:packages/settings"],\
|
||||
["@standardnotes/time", "workspace:packages/time"],\
|
||||
["@types/cors", "npm:2.8.12"],\
|
||||
["@types/dotenv", "npm:8.2.0"],\
|
||||
@@ -3363,6 +3366,17 @@ const RAW_RUNTIME_STATE =
|
||||
["reflect-metadata", "npm:0.1.13"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}],\
|
||||
["npm:1.13.0", {\
|
||||
"packageLocation": "./.yarn/cache/@standardnotes-utils-npm-1.13.0-28780a59f0-1578e8adb7.zip/node_modules/@standardnotes/utils/",\
|
||||
"packageDependencies": [\
|
||||
["@standardnotes/utils", "npm:1.13.0"],\
|
||||
["@standardnotes/common", "workspace:packages/common"],\
|
||||
["dompurify", "npm:2.4.1"],\
|
||||
["lodash", "npm:4.17.21"],\
|
||||
["reflect-metadata", "npm:0.1.13"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["@standardnotes/websockets-server", [\
|
||||
@@ -3371,7 +3385,7 @@ const RAW_RUNTIME_STATE =
|
||||
"packageDependencies": [\
|
||||
["@standardnotes/websockets-server", "workspace:packages/websockets"],\
|
||||
["@newrelic/winston-enricher", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:4.0.0"],\
|
||||
["@sentry/node", "npm:7.28.1"],\
|
||||
["@sentry/node", "npm:7.19.0"],\
|
||||
["@standardnotes/api", "npm:1.19.0"],\
|
||||
["@standardnotes/common", "workspace:packages/common"],\
|
||||
["@standardnotes/domain-events", "workspace:packages/domain-events"],\
|
||||
@@ -3411,7 +3425,7 @@ const RAW_RUNTIME_STATE =
|
||||
"packageDependencies": [\
|
||||
["@standardnotes/workspace-server", "workspace:packages/workspace"],\
|
||||
["@newrelic/winston-enricher", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:4.0.0"],\
|
||||
["@sentry/node", "npm:7.28.1"],\
|
||||
["@sentry/node", "npm:7.19.0"],\
|
||||
["@standardnotes/api", "npm:1.19.0"],\
|
||||
["@standardnotes/common", "workspace:packages/common"],\
|
||||
["@standardnotes/domain-core", "workspace:packages/domain-core"],\
|
||||
@@ -3818,15 +3832,6 @@ const RAW_RUNTIME_STATE =
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["@types/mixpanel", [\
|
||||
["npm:2.14.4", {\
|
||||
"packageLocation": "./.yarn/cache/@types-mixpanel-npm-2.14.4-34bd98306f-a2bf6e633e.zip/node_modules/@types/mixpanel/",\
|
||||
"packageDependencies": [\
|
||||
["@types/mixpanel", "npm:2.14.4"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["@types/newrelic", [\
|
||||
["npm:7.0.4", {\
|
||||
"packageLocation": "./.yarn/cache/@types-newrelic-npm-7.0.4-4f0b179b51-b44215b3ab.zip/node_modules/@types/newrelic/",\
|
||||
@@ -4874,31 +4879,6 @@ const RAW_RUNTIME_STATE =
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["asn1.js", [\
|
||||
["npm:5.4.1", {\
|
||||
"packageLocation": "./.yarn/cache/asn1.js-npm-5.4.1-37c7edbcb0-5c36f81388.zip/node_modules/asn1.js/",\
|
||||
"packageDependencies": [\
|
||||
["asn1.js", "npm:5.4.1"],\
|
||||
["bn.js", "npm:4.12.0"],\
|
||||
["inherits", "npm:2.0.4"],\
|
||||
["minimalistic-assert", "npm:1.0.1"],\
|
||||
["safer-buffer", "npm:2.1.2"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["asn1js", [\
|
||||
["npm:3.0.5", {\
|
||||
"packageLocation": "./.yarn/cache/asn1js-npm-3.0.5-cf5558af33-d0bc57da97.zip/node_modules/asn1js/",\
|
||||
"packageDependencies": [\
|
||||
["asn1js", "npm:3.0.5"],\
|
||||
["pvtsutils", "npm:1.3.2"],\
|
||||
["pvutils", "npm:1.1.3"],\
|
||||
["tslib", "npm:2.4.1"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["async", [\
|
||||
["npm:3.2.4", {\
|
||||
"packageLocation": "./.yarn/cache/async-npm-3.2.4-aba13508f9-9719e38d24.zip/node_modules/async/",\
|
||||
@@ -5112,15 +5092,6 @@ const RAW_RUNTIME_STATE =
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["base64url", [\
|
||||
["npm:3.0.1", {\
|
||||
"packageLocation": "./.yarn/cache/base64url-npm-3.0.1-4c171c4917-72e1401ffe.zip/node_modules/base64url/",\
|
||||
"packageDependencies": [\
|
||||
["base64url", "npm:3.0.1"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["bcryptjs", [\
|
||||
["npm:2.4.3", {\
|
||||
"packageLocation": "./.yarn/cache/bcryptjs-npm-2.4.3-32de4957eb-bf6a43e9c4.zip/node_modules/bcryptjs/",\
|
||||
@@ -5139,15 +5110,6 @@ const RAW_RUNTIME_STATE =
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["bignumber.js", [\
|
||||
["npm:9.1.1", {\
|
||||
"packageLocation": "./.yarn/cache/bignumber.js-npm-9.1.1-5929e8d8dc-e44d008049.zip/node_modules/bignumber.js/",\
|
||||
"packageDependencies": [\
|
||||
["bignumber.js", "npm:9.1.1"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["binary-extensions", [\
|
||||
["npm:2.2.0", {\
|
||||
"packageLocation": "./.yarn/cache/binary-extensions-npm-2.2.0-180c33fec7-16cf7c0cfd.zip/node_modules/binary-extensions/",\
|
||||
@@ -5169,15 +5131,6 @@ const RAW_RUNTIME_STATE =
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["bn.js", [\
|
||||
["npm:4.12.0", {\
|
||||
"packageLocation": "./.yarn/cache/bn.js-npm-4.12.0-3ec6c884f6-bfb4590775.zip/node_modules/bn.js/",\
|
||||
"packageDependencies": [\
|
||||
["bn.js", "npm:4.12.0"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["body-parser", [\
|
||||
["npm:1.20.1", {\
|
||||
"packageLocation": "./.yarn/cache/body-parser-npm-1.20.1-759fd14db9-33f202c9d5.zip/node_modules/body-parser/",\
|
||||
@@ -5245,15 +5198,6 @@ const RAW_RUNTIME_STATE =
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["brorand", [\
|
||||
["npm:1.1.0", {\
|
||||
"packageLocation": "./.yarn/cache/brorand-npm-1.1.0-ea86634c4b-f736e127fb.zip/node_modules/brorand/",\
|
||||
"packageDependencies": [\
|
||||
["brorand", "npm:1.1.0"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["browserslist", [\
|
||||
["npm:4.21.1", {\
|
||||
"packageLocation": "./.yarn/cache/browserslist-npm-4.21.1-930e90b93a-617d624493.zip/node_modules/browserslist/",\
|
||||
@@ -5489,17 +5433,6 @@ const RAW_RUNTIME_STATE =
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["cbor", [\
|
||||
["npm:5.2.0", {\
|
||||
"packageLocation": "./.yarn/cache/cbor-npm-5.2.0-4f6440587f-d60986b9d0.zip/node_modules/cbor/",\
|
||||
"packageDependencies": [\
|
||||
["cbor", "npm:5.2.0"],\
|
||||
["bignumber.js", "npm:9.1.1"],\
|
||||
["nofilter", "npm:1.0.4"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["chalk", [\
|
||||
["npm:2.4.2", {\
|
||||
"packageLocation": "./.yarn/cache/chalk-npm-2.4.2-3ea16dd91e-befd2fe888.zip/node_modules/chalk/",\
|
||||
@@ -6542,6 +6475,13 @@ const RAW_RUNTIME_STATE =
|
||||
["dompurify", "npm:2.4.0"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}],\
|
||||
["npm:2.4.1", {\
|
||||
"packageLocation": "./.yarn/cache/dompurify-npm-2.4.1-1c79f22057-ddc0633356.zip/node_modules/dompurify/",\
|
||||
"packageDependencies": [\
|
||||
["dompurify", "npm:2.4.1"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["dot-prop", [\
|
||||
@@ -6624,22 +6564,6 @@ const RAW_RUNTIME_STATE =
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["elliptic", [\
|
||||
["npm:6.5.4", {\
|
||||
"packageLocation": "./.yarn/cache/elliptic-npm-6.5.4-0ca8204a86-4453b008cf.zip/node_modules/elliptic/",\
|
||||
"packageDependencies": [\
|
||||
["elliptic", "npm:6.5.4"],\
|
||||
["bn.js", "npm:4.12.0"],\
|
||||
["brorand", "npm:1.1.0"],\
|
||||
["hash.js", "npm:1.1.7"],\
|
||||
["hmac-drbg", "npm:1.0.1"],\
|
||||
["inherits", "npm:2.0.4"],\
|
||||
["minimalistic-assert", "npm:1.0.1"],\
|
||||
["minimalistic-crypto-utils", "npm:1.0.1"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["emittery", [\
|
||||
["npm:0.10.2", {\
|
||||
"packageLocation": "./.yarn/cache/emittery-npm-0.10.2-aac10498b5-c55b286714.zip/node_modules/emittery/",\
|
||||
@@ -8146,17 +8070,6 @@ const RAW_RUNTIME_STATE =
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["hash.js", [\
|
||||
["npm:1.1.7", {\
|
||||
"packageLocation": "./.yarn/cache/hash.js-npm-1.1.7-f1ad187358-e4266370d1.zip/node_modules/hash.js/",\
|
||||
"packageDependencies": [\
|
||||
["hash.js", "npm:1.1.7"],\
|
||||
["inherits", "npm:2.0.4"],\
|
||||
["minimalistic-assert", "npm:1.0.1"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["helmet", [\
|
||||
["npm:6.0.0", {\
|
||||
"packageLocation": "./.yarn/cache/helmet-npm-6.0.0-2285459f57-73b6ba802d.zip/node_modules/helmet/",\
|
||||
@@ -8175,18 +8088,6 @@ const RAW_RUNTIME_STATE =
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["hmac-drbg", [\
|
||||
["npm:1.0.1", {\
|
||||
"packageLocation": "./.yarn/cache/hmac-drbg-npm-1.0.1-3499ad31cd-4e88d58ffc.zip/node_modules/hmac-drbg/",\
|
||||
"packageDependencies": [\
|
||||
["hmac-drbg", "npm:1.0.1"],\
|
||||
["hash.js", "npm:1.1.7"],\
|
||||
["minimalistic-assert", "npm:1.0.1"],\
|
||||
["minimalistic-crypto-utils", "npm:1.0.1"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["hosted-git-info", [\
|
||||
["npm:2.8.9", {\
|
||||
"packageLocation": "./.yarn/cache/hosted-git-info-npm-2.8.9-62c44fa93f-c24da52f98.zip/node_modules/hosted-git-info/",\
|
||||
@@ -8277,15 +8178,6 @@ const RAW_RUNTIME_STATE =
|
||||
}]\
|
||||
]],\
|
||||
["https-proxy-agent", [\
|
||||
["npm:5.0.0", {\
|
||||
"packageLocation": "./.yarn/cache/https-proxy-agent-npm-5.0.0-bb777903c3-77d11b0e2c.zip/node_modules/https-proxy-agent/",\
|
||||
"packageDependencies": [\
|
||||
["https-proxy-agent", "npm:5.0.0"],\
|
||||
["agent-base", "npm:6.0.2"],\
|
||||
["debug", "virtual:b86a9fb34323a98c6519528ed55faa0d9b44ca8879307c0b29aa384bde47ff59a7d0c9051b31246f14521dfb71ba3c5d6d0b35c29fffc17bf875aa6ad977d9e8#npm:4.3.4"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}],\
|
||||
["npm:5.0.1", {\
|
||||
"packageLocation": "./.yarn/cache/https-proxy-agent-npm-5.0.1-42d65f358e-8e767faec9.zip/node_modules/https-proxy-agent/",\
|
||||
"packageDependencies": [\
|
||||
@@ -8576,13 +8468,6 @@ const RAW_RUNTIME_STATE =
|
||||
["ipaddr.js", "npm:1.9.1"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}],\
|
||||
["npm:2.0.1", {\
|
||||
"packageLocation": "./.yarn/cache/ipaddr.js-npm-2.0.1-04e97280d7-04ce6c896c.zip/node_modules/ipaddr.js/",\
|
||||
"packageDependencies": [\
|
||||
["ipaddr.js", "npm:2.0.1"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["is-arguments", [\
|
||||
@@ -9785,15 +9670,6 @@ const RAW_RUNTIME_STATE =
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["jsrsasign", [\
|
||||
["npm:10.6.1", {\
|
||||
"packageLocation": "./.yarn/cache/jsrsasign-npm-10.6.1-a8fa295369-e8e9c1b24f.zip/node_modules/jsrsasign/",\
|
||||
"packageDependencies": [\
|
||||
["jsrsasign", "npm:10.6.1"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["jwa", [\
|
||||
["npm:1.4.1", {\
|
||||
"packageLocation": "./.yarn/cache/jwa-npm-1.4.1-4f19d6572c-0cc3e68b68.zip/node_modules/jwa/",\
|
||||
@@ -9806,18 +9682,6 @@ const RAW_RUNTIME_STATE =
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["jwk-to-pem", [\
|
||||
["npm:2.0.5", {\
|
||||
"packageLocation": "./.yarn/cache/jwk-to-pem-npm-2.0.5-aff7d9f125-fced3a75b0.zip/node_modules/jwk-to-pem/",\
|
||||
"packageDependencies": [\
|
||||
["jwk-to-pem", "npm:2.0.5"],\
|
||||
["asn1.js", "npm:5.4.1"],\
|
||||
["elliptic", "npm:6.5.4"],\
|
||||
["safe-buffer", "npm:5.2.1"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["jws", [\
|
||||
["npm:3.2.2", {\
|
||||
"packageLocation": "./.yarn/cache/jws-npm-3.2.2-c1ae59c7af-347ed7c334.zip/node_modules/jws/",\
|
||||
@@ -10431,24 +10295,6 @@ const RAW_RUNTIME_STATE =
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["minimalistic-assert", [\
|
||||
["npm:1.0.1", {\
|
||||
"packageLocation": "./.yarn/cache/minimalistic-assert-npm-1.0.1-dc8bb23d29-e2310081d8.zip/node_modules/minimalistic-assert/",\
|
||||
"packageDependencies": [\
|
||||
["minimalistic-assert", "npm:1.0.1"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["minimalistic-crypto-utils", [\
|
||||
["npm:1.0.1", {\
|
||||
"packageLocation": "./.yarn/cache/minimalistic-crypto-utils-npm-1.0.1-e66b10822e-7d909decd2.zip/node_modules/minimalistic-crypto-utils/",\
|
||||
"packageDependencies": [\
|
||||
["minimalistic-crypto-utils", "npm:1.0.1"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["minimatch", [\
|
||||
["npm:3.1.2", {\
|
||||
"packageLocation": "./.yarn/cache/minimatch-npm-3.1.2-9405269906-97f5615ee8.zip/node_modules/minimatch/",\
|
||||
@@ -10573,16 +10419,6 @@ const RAW_RUNTIME_STATE =
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["mixpanel", [\
|
||||
["npm:0.17.0", {\
|
||||
"packageLocation": "./.yarn/cache/mixpanel-npm-0.17.0-3073ce9949-5a945bdbdd.zip/node_modules/mixpanel/",\
|
||||
"packageDependencies": [\
|
||||
["mixpanel", "npm:0.17.0"],\
|
||||
["https-proxy-agent", "npm:5.0.0"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["mkdirp", [\
|
||||
["npm:1.0.4", {\
|
||||
"packageLocation": "./.yarn/cache/mkdirp-npm-1.0.4-37f6ef56b9-1233611198.zip/node_modules/mkdirp/",\
|
||||
@@ -10843,15 +10679,6 @@ const RAW_RUNTIME_STATE =
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["nofilter", [\
|
||||
["npm:1.0.4", {\
|
||||
"packageLocation": "./.yarn/cache/nofilter-npm-1.0.4-1cbdc6c03a-9a26874e7d.zip/node_modules/nofilter/",\
|
||||
"packageDependencies": [\
|
||||
["nofilter", "npm:1.0.4"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["nopt", [\
|
||||
["npm:1.0.10", {\
|
||||
"packageLocation": "./.yarn/cache/nopt-npm-1.0.10-f3db192976-efa5a9c2c1.zip/node_modules/nopt/",\
|
||||
@@ -11900,25 +11727,6 @@ const RAW_RUNTIME_STATE =
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["pvtsutils", [\
|
||||
["npm:1.3.2", {\
|
||||
"packageLocation": "./.yarn/cache/pvtsutils-npm-1.3.2-e1483da905-eb22d3df60.zip/node_modules/pvtsutils/",\
|
||||
"packageDependencies": [\
|
||||
["pvtsutils", "npm:1.3.2"],\
|
||||
["tslib", "npm:2.4.1"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["pvutils", [\
|
||||
["npm:1.1.3", {\
|
||||
"packageLocation": "./.yarn/cache/pvutils-npm-1.1.3-da8b07d6cf-0cb4f4878f.zip/node_modules/pvutils/",\
|
||||
"packageDependencies": [\
|
||||
["pvutils", "npm:1.1.3"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["q", [\
|
||||
["npm:1.5.1", {\
|
||||
"packageLocation": "./.yarn/cache/q-npm-1.5.1-a28b3cfeaf-276b7e93fc.zip/node_modules/q/",\
|
||||
@@ -13603,13 +13411,6 @@ const RAW_RUNTIME_STATE =
|
||||
["tslib", "npm:2.4.0"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}],\
|
||||
["npm:2.4.1", {\
|
||||
"packageLocation": "./.yarn/cache/tslib-npm-2.4.1-36f0ed04db-a739a21e3f.zip/node_modules/tslib/",\
|
||||
"packageDependencies": [\
|
||||
["tslib", "npm:2.4.1"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["tsutils", [\
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
.yarn/cache/@sentry-core-npm-7.19.0-151e6173ac-cabd7852ff.zip
vendored
Normal file
BIN
.yarn/cache/@sentry-core-npm-7.19.0-151e6173ac-cabd7852ff.zip
vendored
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
.yarn/cache/@sentry-types-npm-7.19.0-d6ed1960f2-541e1ef49a.zip
vendored
Normal file
BIN
.yarn/cache/@sentry-types-npm-7.19.0-d6ed1960f2-541e1ef49a.zip
vendored
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
.yarn/cache/@standardnotes-api-npm-1.20.13-3efe52d749-67bdb982ec.zip
vendored
Normal file
BIN
.yarn/cache/@standardnotes-api-npm-1.20.13-3efe52d749-67bdb982ec.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@standardnotes-encryption-npm-1.19.21-dfa10f00e6-c8c2c27bfe.zip
vendored
Normal file
BIN
.yarn/cache/@standardnotes-encryption-npm-1.19.21-dfa10f00e6-c8c2c27bfe.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@standardnotes-features-npm-1.55.3-c124505183-b39fe2d49b.zip
vendored
Normal file
BIN
.yarn/cache/@standardnotes-features-npm-1.55.3-c124505183-b39fe2d49b.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@standardnotes-models-npm-1.38.0-108f602f56-2dc2ac957e.zip
vendored
Normal file
BIN
.yarn/cache/@standardnotes-models-npm-1.38.0-108f602f56-2dc2ac957e.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@standardnotes-responses-npm-1.12.9-280dc75972-353fe1ca6d.zip
vendored
Normal file
BIN
.yarn/cache/@standardnotes-responses-npm-1.12.9-280dc75972-353fe1ca6d.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@standardnotes-sncrypto-common-npm-1.13.3-97ef3850ce-a73af90962.zip
vendored
Normal file
BIN
.yarn/cache/@standardnotes-sncrypto-common-npm-1.13.3-97ef3850ce-a73af90962.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@standardnotes-utils-npm-1.13.0-28780a59f0-1578e8adb7.zip
vendored
Normal file
BIN
.yarn/cache/@standardnotes-utils-npm-1.13.0-28780a59f0-1578e8adb7.zip
vendored
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
.yarn/cache/cbor-npm-5.2.0-4f6440587f-d60986b9d0.zip
vendored
BIN
.yarn/cache/cbor-npm-5.2.0-4f6440587f-d60986b9d0.zip
vendored
Binary file not shown.
BIN
.yarn/cache/dompurify-npm-2.4.1-1c79f22057-ddc0633356.zip
vendored
Normal file
BIN
.yarn/cache/dompurify-npm-2.4.1-1c79f22057-ddc0633356.zip
vendored
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -61,7 +61,7 @@
|
||||
},
|
||||
"packageManager": "yarn@4.0.0-rc.25",
|
||||
"dependencies": {
|
||||
"@sentry/node": "^7.28.1",
|
||||
"@sentry/node": "^7.19.0",
|
||||
"newrelic": "^9.6.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,8 +10,6 @@ DB_DATABASE=analytics
|
||||
DB_DEBUG_LEVEL=all # "all" | "query" | "schema" | "error" | "warn" | "info" | "log" | "migration"
|
||||
DB_MIGRATIONS_PATH=dist/migrations/*.js
|
||||
|
||||
ADMIN_EMAILS=test@standardnotes.com
|
||||
|
||||
REDIS_URL=redis://cache
|
||||
REDIS_EVENTS_CHANNEL=events
|
||||
|
||||
@@ -28,6 +26,3 @@ NEW_RELIC_NO_CONFIG_FILE=true
|
||||
NEW_RELIC_DISTRIBUTED_TRACING_ENABLED=false
|
||||
NEW_RELIC_LOG_ENABLED=false
|
||||
NEW_RELIC_LOG_LEVEL=info
|
||||
|
||||
# (Optional) Mixpanel
|
||||
MIXPANEL_TOKEN=
|
||||
|
||||
@@ -3,113 +3,6 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [2.19.1](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.19.0...@standardnotes/analytics@2.19.1) (2022-12-30)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **analytics:** remove unnecesary context from mixpanel events ([ba1e1ad](https://github.com/standardnotes/server/commit/ba1e1ad5ad82b052be4cc2d1cc2abdaf3b72cf4c))
|
||||
|
||||
# [2.19.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.18.0...@standardnotes/analytics@2.19.0) (2022-12-30)
|
||||
|
||||
### Features
|
||||
|
||||
* **analytics:** add mixpanel events tracking ([df6e3f0](https://github.com/standardnotes/server/commit/df6e3f06a6868e30e60dd98431122983724644b4))
|
||||
|
||||
# [2.18.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.17.8...@standardnotes/analytics@2.18.0) (2022-12-30)
|
||||
|
||||
### Features
|
||||
|
||||
* **analytics:** add mixpanel ([893d617](https://github.com/standardnotes/server/commit/893d6176c3b0b56c45e5188fe982232db2ceedc4))
|
||||
|
||||
## [2.17.8](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.17.7...@standardnotes/analytics@2.17.8) (2022-12-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/analytics
|
||||
|
||||
## [2.17.7](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.17.6...@standardnotes/analytics@2.17.7) (2022-12-20)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **analytics:** monthly numbers of active users ([b34bbca](https://github.com/standardnotes/server/commit/b34bbcac8b9604283b3a5959ab3218c468ce8a00))
|
||||
|
||||
## [2.17.6](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.17.5...@standardnotes/analytics@2.17.6) (2022-12-20)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **analytics:** filtered counts for user activity check ([17b2ea1](https://github.com/standardnotes/server/commit/17b2ea126c5ad2d7cf07657def63f9977f239a3c))
|
||||
|
||||
## [2.17.5](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.17.4...@standardnotes/analytics@2.17.5) (2022-12-20)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **analytics:** accessing analytics in report ([ef26dc8](https://github.com/standardnotes/server/commit/ef26dc8cbb967e088ae7387ff6dbec1e60dc3ee4))
|
||||
|
||||
## [2.17.4](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.17.3...@standardnotes/analytics@2.17.4) (2022-12-20)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/analytics
|
||||
|
||||
## [2.17.3](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.17.2...@standardnotes/analytics@2.17.3) (2022-12-20)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **analytics:** add debug logs for the report ([031fcd7](https://github.com/standardnotes/server/commit/031fcd75eecdcf4c2f17257754a0ba3f24ba6d6e))
|
||||
|
||||
## [2.17.2](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.17.1...@standardnotes/analytics@2.17.2) (2022-12-20)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **analytics:** calculating active users ([a304993](https://github.com/standardnotes/server/commit/a3049938a31e21a5867a314ac62bee6aa4990d57))
|
||||
|
||||
## [2.17.1](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.17.0...@standardnotes/analytics@2.17.1) (2022-12-20)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **analytics:** container binding ([04ffc69](https://github.com/standardnotes/server/commit/04ffc69e000803107d8834c286de97b3d213a842))
|
||||
* **auth:** replace date object with number timestamp ([5b4bb6e](https://github.com/standardnotes/server/commit/5b4bb6e7a78a1b0f4e663bb990619f65f6a5c757))
|
||||
|
||||
# [2.17.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.16.0...@standardnotes/analytics@2.17.0) (2022-12-20)
|
||||
|
||||
### Features
|
||||
|
||||
* **analytics:** add users activit to the report email ([ed5a4eb](https://github.com/standardnotes/server/commit/ed5a4eb960a6c8fe9d0c77331f29dc3c7ffb9100))
|
||||
|
||||
# [2.16.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.15.1...@standardnotes/analytics@2.16.0) (2022-12-20)
|
||||
|
||||
### Features
|
||||
|
||||
* **analytics:** add active users stats to report ([6e16620](https://github.com/standardnotes/server/commit/6e1662038c3340fb60939464616789bab7639160))
|
||||
|
||||
## [2.15.1](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.15.0...@standardnotes/analytics@2.15.1) (2022-12-20)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** add persisting statistics for all subscription plans ([addedb3](https://github.com/standardnotes/server/commit/addedb3091ddae81618d56663e18f2ae76a43c4e))
|
||||
|
||||
# [2.15.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.14.0...@standardnotes/analytics@2.15.0) (2022-12-19)
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** add requesting persisting statistics ([a35271f](https://github.com/standardnotes/server/commit/a35271fbb399b68a3ac7021395d8063707fba222))
|
||||
|
||||
# [2.14.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.13.0...@standardnotes/analytics@2.14.0) (2022-12-19)
|
||||
|
||||
### Features
|
||||
|
||||
* **analytics:** add persisting statistics on demand ([0f84575](https://github.com/standardnotes/server/commit/0f8457534c1829c58f3c036749d262307ddeb779))
|
||||
|
||||
# [2.13.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.27...@standardnotes/analytics@2.13.0) (2022-12-19)
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** add session traces ([8bcb552](https://github.com/standardnotes/server/commit/8bcb552783b2d12f3296b3195752168482790bc8))
|
||||
|
||||
## [2.12.27](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.26...@standardnotes/analytics@2.12.27) (2022-12-15)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/analytics
|
||||
|
||||
## [2.12.26](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.25...@standardnotes/analytics@2.12.26) (2022-12-15)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/analytics
|
||||
|
||||
## [2.12.25](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.24...@standardnotes/analytics@2.12.25) (2022-12-12)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/analytics
|
||||
|
||||
@@ -8,6 +8,7 @@ import { EmailLevel } from '@standardnotes/domain-core'
|
||||
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
|
||||
import { AnalyticsActivity } from '../src/Domain/Analytics/AnalyticsActivity'
|
||||
import { Period } from '../src/Domain/Time/Period'
|
||||
import { StatisticsMeasure } from '../src/Domain/Statistics/StatisticsMeasure'
|
||||
import { AnalyticsStoreInterface } from '../src/Domain/Analytics/AnalyticsStoreInterface'
|
||||
import { StatisticsStoreInterface } from '../src/Domain/Statistics/StatisticsStoreInterface'
|
||||
import { PeriodKeyGeneratorInterface } from '../src/Domain/Time/PeriodKeyGeneratorInterface'
|
||||
@@ -18,7 +19,6 @@ import { DomainEventFactoryInterface } from '../src/Domain/Event/DomainEventFact
|
||||
import { CalculateMonthlyRecurringRevenue } from '../src/Domain/UseCase/CalculateMonthlyRecurringRevenue/CalculateMonthlyRecurringRevenue'
|
||||
import { getBody, getSubject } from '../src/Domain/Email/DailyAnalyticsReport'
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
import { StatisticMeasureName } from '../src/Domain/Statistics/StatisticMeasureName'
|
||||
|
||||
const requestReport = async (
|
||||
analyticsStore: AnalyticsStoreInterface,
|
||||
@@ -115,16 +115,12 @@ const requestReport = async (
|
||||
}> = []
|
||||
|
||||
const thirtyDaysStatisticsNames = [
|
||||
StatisticMeasureName.NAMES.MRR,
|
||||
StatisticMeasureName.NAMES.AnnualPlansMRR,
|
||||
StatisticMeasureName.NAMES.MonthlyPlansMRR,
|
||||
StatisticMeasureName.NAMES.FiveYearPlansMRR,
|
||||
StatisticMeasureName.NAMES.PlusPlansMRR,
|
||||
StatisticMeasureName.NAMES.ProPlansMRR,
|
||||
StatisticMeasureName.NAMES.ActiveUsers,
|
||||
StatisticMeasureName.NAMES.ActiveFreeUsers,
|
||||
StatisticMeasureName.NAMES.ActivePlusUsers,
|
||||
StatisticMeasureName.NAMES.ActiveProUsers,
|
||||
StatisticsMeasure.MRR,
|
||||
StatisticsMeasure.AnnualPlansMRR,
|
||||
StatisticsMeasure.MonthlyPlansMRR,
|
||||
StatisticsMeasure.FiveYearPlansMRR,
|
||||
StatisticsMeasure.PlusPlansMRR,
|
||||
StatisticsMeasure.ProPlansMRR,
|
||||
]
|
||||
for (const statisticName of thirtyDaysStatisticsNames) {
|
||||
statisticsOverTime.push({
|
||||
@@ -134,7 +130,7 @@ const requestReport = async (
|
||||
})
|
||||
}
|
||||
|
||||
const monthlyStatisticsNames = [StatisticMeasureName.NAMES.MRR]
|
||||
const monthlyStatisticsNames = [StatisticsMeasure.MRR]
|
||||
for (const statisticName of monthlyStatisticsNames) {
|
||||
statisticsOverTime.push({
|
||||
name: statisticName,
|
||||
@@ -144,22 +140,22 @@ const requestReport = async (
|
||||
}
|
||||
|
||||
const statisticMeasureNames = [
|
||||
StatisticMeasureName.NAMES.Income,
|
||||
StatisticMeasureName.NAMES.PlusSubscriptionInitialAnnualPaymentsIncome,
|
||||
StatisticMeasureName.NAMES.PlusSubscriptionInitialMonthlyPaymentsIncome,
|
||||
StatisticMeasureName.NAMES.PlusSubscriptionRenewingAnnualPaymentsIncome,
|
||||
StatisticMeasureName.NAMES.PlusSubscriptionRenewingMonthlyPaymentsIncome,
|
||||
StatisticMeasureName.NAMES.ProSubscriptionInitialAnnualPaymentsIncome,
|
||||
StatisticMeasureName.NAMES.ProSubscriptionInitialMonthlyPaymentsIncome,
|
||||
StatisticMeasureName.NAMES.ProSubscriptionRenewingAnnualPaymentsIncome,
|
||||
StatisticMeasureName.NAMES.ProSubscriptionRenewingMonthlyPaymentsIncome,
|
||||
StatisticMeasureName.NAMES.Refunds,
|
||||
StatisticMeasureName.NAMES.RegistrationLength,
|
||||
StatisticMeasureName.NAMES.SubscriptionLength,
|
||||
StatisticMeasureName.NAMES.RegistrationToSubscriptionTime,
|
||||
StatisticMeasureName.NAMES.RemainingSubscriptionTimePercentage,
|
||||
StatisticMeasureName.NAMES.NewCustomers,
|
||||
StatisticMeasureName.NAMES.TotalCustomers,
|
||||
StatisticsMeasure.Income,
|
||||
StatisticsMeasure.PlusSubscriptionInitialAnnualPaymentsIncome,
|
||||
StatisticsMeasure.PlusSubscriptionInitialMonthlyPaymentsIncome,
|
||||
StatisticsMeasure.PlusSubscriptionRenewingAnnualPaymentsIncome,
|
||||
StatisticsMeasure.PlusSubscriptionRenewingMonthlyPaymentsIncome,
|
||||
StatisticsMeasure.ProSubscriptionInitialAnnualPaymentsIncome,
|
||||
StatisticsMeasure.ProSubscriptionInitialMonthlyPaymentsIncome,
|
||||
StatisticsMeasure.ProSubscriptionRenewingAnnualPaymentsIncome,
|
||||
StatisticsMeasure.ProSubscriptionRenewingMonthlyPaymentsIncome,
|
||||
StatisticsMeasure.Refunds,
|
||||
StatisticsMeasure.RegistrationLength,
|
||||
StatisticsMeasure.SubscriptionLength,
|
||||
StatisticsMeasure.RegistrationToSubscriptionTime,
|
||||
StatisticsMeasure.RemainingSubscriptionTimePercentage,
|
||||
StatisticsMeasure.NewCustomers,
|
||||
StatisticsMeasure.TotalCustomers,
|
||||
]
|
||||
const statisticMeasures: Array<{
|
||||
name: string
|
||||
@@ -194,10 +190,7 @@ const requestReport = async (
|
||||
|
||||
const totalCustomerCounts: Array<number> = []
|
||||
for (const dailyPeriodKey of dailyPeriodKeys) {
|
||||
const customersCount = await statisticsStore.getMeasureTotal(
|
||||
StatisticMeasureName.NAMES.TotalCustomers,
|
||||
dailyPeriodKey,
|
||||
)
|
||||
const customersCount = await statisticsStore.getMeasureTotal(StatisticsMeasure.TotalCustomers, dailyPeriodKey)
|
||||
totalCustomerCounts.push(customersCount)
|
||||
}
|
||||
const filteredTotalCustomerCounts = totalCustomerCounts.filter((count) => !!count)
|
||||
|
||||
@@ -7,5 +7,5 @@ module.exports = {
|
||||
transform: {
|
||||
...tsjPreset.transform,
|
||||
},
|
||||
coveragePathIgnorePatterns: ['/Infra/', '/Domain/Email/', '/Handler/'],
|
||||
coveragePathIgnorePatterns: ['/Infra/', '/Domain/Email/'],
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/analytics",
|
||||
"version": "2.19.1",
|
||||
"version": "2.12.25",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <19.0.0"
|
||||
},
|
||||
@@ -27,7 +27,6 @@
|
||||
"devDependencies": {
|
||||
"@types/ioredis": "^5.0.0",
|
||||
"@types/jest": "^29.1.1",
|
||||
"@types/mixpanel": "^2.14.4",
|
||||
"@types/newrelic": "^7.0.4",
|
||||
"@types/node": "^18.11.9",
|
||||
"@typescript-eslint/eslint-plugin": "^5.30.0",
|
||||
@@ -39,7 +38,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@newrelic/winston-enricher": "^4.0.0",
|
||||
"@sentry/node": "^7.28.1",
|
||||
"@sentry/node": "^7.19.0",
|
||||
"@standardnotes/common": "workspace:*",
|
||||
"@standardnotes/domain-core": "workspace:^",
|
||||
"@standardnotes/domain-events": "workspace:*",
|
||||
@@ -50,7 +49,6 @@
|
||||
"dotenv": "^16.0.1",
|
||||
"inversify": "^6.0.1",
|
||||
"ioredis": "^5.2.4",
|
||||
"mixpanel": "^0.17.0",
|
||||
"mysql2": "^2.3.3",
|
||||
"newrelic": "^9.6.0",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
|
||||
@@ -8,8 +8,6 @@ import {
|
||||
DomainEventSubscriberFactoryInterface,
|
||||
} from '@standardnotes/domain-events'
|
||||
import { MapperInterface } from '@standardnotes/domain-core'
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const Mixpanel = require('mixpanel')
|
||||
|
||||
import { Env } from './Env'
|
||||
import TYPES from './Types'
|
||||
@@ -54,9 +52,6 @@ import { RevenueModification } from '../Domain/Revenue/RevenueModification'
|
||||
import { RevenueModificationMap } from '../Domain/Map/RevenueModificationMap'
|
||||
import { SaveRevenueModification } from '../Domain/UseCase/SaveRevenueModification/SaveRevenueModification'
|
||||
import { CalculateMonthlyRecurringRevenue } from '../Domain/UseCase/CalculateMonthlyRecurringRevenue/CalculateMonthlyRecurringRevenue'
|
||||
import { PersistStatistic } from '../Domain/UseCase/PersistStatistic/PersistStatistic'
|
||||
import { StatisticMeasureRepositoryInterface } from '../Domain/Statistics/StatisticMeasureRepositoryInterface'
|
||||
import { StatisticPersistenceRequestedEventHandler } from '../Domain/Handler/StatisticPersistenceRequestedEventHandler'
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const newrelicFormatter = require('@newrelic/winston-enricher')
|
||||
@@ -136,33 +131,6 @@ export class ContainerConfigLoader {
|
||||
container.bind(TYPES.REDIS_EVENTS_CHANNEL).toConstantValue(env.get('REDIS_EVENTS_CHANNEL'))
|
||||
container.bind(TYPES.NEW_RELIC_ENABLED).toConstantValue(env.get('NEW_RELIC_ENABLED', true))
|
||||
container.bind(TYPES.ADMIN_EMAILS).toConstantValue(env.get('ADMIN_EMAILS').split(','))
|
||||
container.bind(TYPES.MIXPANEL_TOKEN).toConstantValue(env.get('MIXPANEL_TOKEN', true))
|
||||
|
||||
// Services
|
||||
container.bind<DomainEventFactory>(TYPES.DomainEventFactory).to(DomainEventFactory)
|
||||
container.bind<PeriodKeyGeneratorInterface>(TYPES.PeriodKeyGenerator).toConstantValue(new PeriodKeyGenerator())
|
||||
container
|
||||
.bind<AnalyticsStoreInterface>(TYPES.AnalyticsStore)
|
||||
.toConstantValue(new RedisAnalyticsStore(container.get(TYPES.PeriodKeyGenerator), container.get(TYPES.Redis)))
|
||||
container
|
||||
.bind<StatisticsStoreInterface>(TYPES.StatisticsStore)
|
||||
.toConstantValue(new RedisStatisticsStore(container.get(TYPES.PeriodKeyGenerator), container.get(TYPES.Redis)))
|
||||
container.bind<TimerInterface>(TYPES.Timer).toConstantValue(new Timer())
|
||||
|
||||
if (env.get('SNS_TOPIC_ARN', true)) {
|
||||
container
|
||||
.bind<SNSDomainEventPublisher>(TYPES.DomainEventPublisher)
|
||||
.toConstantValue(new SNSDomainEventPublisher(container.get(TYPES.SNS), container.get(TYPES.SNS_TOPIC_ARN)))
|
||||
} else {
|
||||
container
|
||||
.bind<RedisDomainEventPublisher>(TYPES.DomainEventPublisher)
|
||||
.toConstantValue(
|
||||
new RedisDomainEventPublisher(container.get(TYPES.Redis), container.get(TYPES.REDIS_EVENTS_CHANNEL)),
|
||||
)
|
||||
}
|
||||
if (env.get('MIXPANEL_TOKEN', true)) {
|
||||
container.bind<Mixpanel>(TYPES.MixpanelClient).toConstantValue(Mixpanel.init(env.get('MIXPANEL_TOKEN', true)))
|
||||
}
|
||||
|
||||
// Repositories
|
||||
container
|
||||
@@ -171,9 +139,6 @@ export class ContainerConfigLoader {
|
||||
container
|
||||
.bind<RevenueModificationRepositoryInterface>(TYPES.RevenueModificationRepository)
|
||||
.to(MySQLRevenueModificationRepository)
|
||||
container
|
||||
.bind<StatisticMeasureRepositoryInterface>(TYPES.StatisticMeasureRepository)
|
||||
.toConstantValue(new RedisStatisticsStore(container.get(TYPES.PeriodKeyGenerator), container.get(TYPES.Redis)))
|
||||
|
||||
// ORM
|
||||
container
|
||||
@@ -189,9 +154,6 @@ export class ContainerConfigLoader {
|
||||
container
|
||||
.bind<CalculateMonthlyRecurringRevenue>(TYPES.CalculateMonthlyRecurringRevenue)
|
||||
.to(CalculateMonthlyRecurringRevenue)
|
||||
container
|
||||
.bind<PersistStatistic>(TYPES.PersistStatistic)
|
||||
.toConstantValue(new PersistStatistic(container.get(TYPES.StatisticMeasureRepository)))
|
||||
|
||||
// Hanlders
|
||||
container.bind<UserRegisteredEventHandler>(TYPES.UserRegisteredEventHandler).to(UserRegisteredEventHandler)
|
||||
@@ -219,22 +181,35 @@ export class ContainerConfigLoader {
|
||||
.bind<SubscriptionReactivatedEventHandler>(TYPES.SubscriptionReactivatedEventHandler)
|
||||
.to(SubscriptionReactivatedEventHandler)
|
||||
container.bind<RefundProcessedEventHandler>(TYPES.RefundProcessedEventHandler).to(RefundProcessedEventHandler)
|
||||
container
|
||||
.bind<StatisticPersistenceRequestedEventHandler>(TYPES.StatisticPersistenceRequestedEventHandler)
|
||||
.toConstantValue(
|
||||
new StatisticPersistenceRequestedEventHandler(
|
||||
container.get(TYPES.PersistStatistic),
|
||||
container.get(TYPES.Timer),
|
||||
container.get(TYPES.Logger),
|
||||
env.get('MIXPANEL_TOKEN', true) ? container.get(TYPES.MixpanelClient) : null,
|
||||
),
|
||||
)
|
||||
|
||||
// Maps
|
||||
container
|
||||
.bind<MapperInterface<RevenueModification, TypeORMRevenueModification>>(TYPES.RevenueModificationMap)
|
||||
.to(RevenueModificationMap)
|
||||
|
||||
// Services
|
||||
container.bind<DomainEventFactory>(TYPES.DomainEventFactory).to(DomainEventFactory)
|
||||
container.bind<PeriodKeyGeneratorInterface>(TYPES.PeriodKeyGenerator).toConstantValue(new PeriodKeyGenerator())
|
||||
container
|
||||
.bind<AnalyticsStoreInterface>(TYPES.AnalyticsStore)
|
||||
.toConstantValue(new RedisAnalyticsStore(container.get(TYPES.PeriodKeyGenerator), container.get(TYPES.Redis)))
|
||||
container
|
||||
.bind<StatisticsStoreInterface>(TYPES.StatisticsStore)
|
||||
.toConstantValue(new RedisStatisticsStore(container.get(TYPES.PeriodKeyGenerator), container.get(TYPES.Redis)))
|
||||
container.bind<TimerInterface>(TYPES.Timer).toConstantValue(new Timer())
|
||||
|
||||
if (env.get('SNS_TOPIC_ARN', true)) {
|
||||
container
|
||||
.bind<SNSDomainEventPublisher>(TYPES.DomainEventPublisher)
|
||||
.toConstantValue(new SNSDomainEventPublisher(container.get(TYPES.SNS), container.get(TYPES.SNS_TOPIC_ARN)))
|
||||
} else {
|
||||
container
|
||||
.bind<RedisDomainEventPublisher>(TYPES.DomainEventPublisher)
|
||||
.toConstantValue(
|
||||
new RedisDomainEventPublisher(container.get(TYPES.Redis), container.get(TYPES.REDIS_EVENTS_CHANNEL)),
|
||||
)
|
||||
}
|
||||
|
||||
const eventHandlers: Map<string, DomainEventHandlerInterface> = new Map([
|
||||
['USER_REGISTERED', container.get(TYPES.UserRegisteredEventHandler)],
|
||||
['ACCOUNT_DELETION_REQUESTED', container.get(TYPES.AccountDeletionRequestedEventHandler)],
|
||||
@@ -247,7 +222,6 @@ export class ContainerConfigLoader {
|
||||
['SUBSCRIPTION_EXPIRED', container.get(TYPES.SubscriptionExpiredEventHandler)],
|
||||
['SUBSCRIPTION_REACTIVATED', container.get(TYPES.SubscriptionReactivatedEventHandler)],
|
||||
['REFUND_PROCESSED', container.get(TYPES.RefundProcessedEventHandler)],
|
||||
['STATISTIC_PERSISTENCE_REQUESTED', container.get(TYPES.StatisticPersistenceRequestedEventHandler)],
|
||||
])
|
||||
|
||||
if (env.get('SQS_QUEUE_URL', true)) {
|
||||
|
||||
@@ -12,11 +12,9 @@ const TYPES = {
|
||||
REDIS_EVENTS_CHANNEL: Symbol.for('REDIS_EVENTS_CHANNEL'),
|
||||
NEW_RELIC_ENABLED: Symbol.for('NEW_RELIC_ENABLED'),
|
||||
ADMIN_EMAILS: Symbol.for('ADMIN_EMAILS'),
|
||||
MIXPANEL_TOKEN: Symbol.for('MIXPANEL_TOKEN'),
|
||||
// Repositories
|
||||
AnalyticsEntityRepository: Symbol.for('AnalyticsEntityRepository'),
|
||||
RevenueModificationRepository: Symbol.for('RevenueModificationRepository'),
|
||||
StatisticMeasureRepository: Symbol.for('StatisticMeasureRepository'),
|
||||
// ORM
|
||||
ORMAnalyticsEntityRepository: Symbol.for('ORMAnalyticsEntityRepository'),
|
||||
ORMRevenueModificationRepository: Symbol.for('ORMRevenueModificationRepository'),
|
||||
@@ -24,7 +22,6 @@ const TYPES = {
|
||||
GetUserAnalyticsId: Symbol.for('GetUserAnalyticsId'),
|
||||
SaveRevenueModification: Symbol.for('SaveRevenueModification'),
|
||||
CalculateMonthlyRecurringRevenue: Symbol.for('CalculateMonthlyRecurringRevenue'),
|
||||
PersistStatistic: Symbol.for('PersistStatistic'),
|
||||
// Handlers
|
||||
UserRegisteredEventHandler: Symbol.for('UserRegisteredEventHandler'),
|
||||
AccountDeletionRequestedEventHandler: Symbol.for('AccountDeletionRequestedEventHandler'),
|
||||
@@ -37,7 +34,6 @@ const TYPES = {
|
||||
SubscriptionExpiredEventHandler: Symbol.for('SubscriptionExpiredEventHandler'),
|
||||
SubscriptionReactivatedEventHandler: Symbol.for('SubscriptionReactivatedEventHandler'),
|
||||
RefundProcessedEventHandler: Symbol.for('RefundProcessedEventHandler'),
|
||||
StatisticPersistenceRequestedEventHandler: Symbol.for('StatisticPersistenceRequestedEventHandler'),
|
||||
// Maps
|
||||
RevenueModificationMap: Symbol.for('RevenueModificationMap'),
|
||||
// Services
|
||||
@@ -49,7 +45,6 @@ const TYPES = {
|
||||
StatisticsStore: Symbol.for('StatisticsStore'),
|
||||
Timer: Symbol.for('Timer'),
|
||||
PeriodKeyGenerator: Symbol.for('PeriodKeyGenerator'),
|
||||
MixpanelClient: Symbol.for('MixpanelClient'),
|
||||
}
|
||||
|
||||
export default TYPES
|
||||
|
||||
@@ -2,41 +2,9 @@
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
|
||||
import { AnalyticsActivity } from '../Analytics/AnalyticsActivity'
|
||||
import { StatisticMeasureName } from '../Statistics/StatisticMeasureName'
|
||||
import { StatisticsMeasure } from '../Statistics/StatisticsMeasure'
|
||||
import { Period } from '../Time/Period'
|
||||
|
||||
const countActiveUsers = (measureName: string, data: any): { yesterday: number; last30Days: number } => {
|
||||
const totalActiveUsersLast30DaysIncludingToday = data.statisticsOverTime.find(
|
||||
(a: { name: string; period: number }) => a.name === measureName && a.period === 27,
|
||||
)
|
||||
|
||||
const totalActiveUsersYesterday =
|
||||
totalActiveUsersLast30DaysIncludingToday.counts[totalActiveUsersLast30DaysIncludingToday.counts.length - 2]
|
||||
.totalCount
|
||||
|
||||
const filteredCounts = totalActiveUsersLast30DaysIncludingToday.counts.filter(
|
||||
(count: { totalCount: number }) => count.totalCount !== 0,
|
||||
)
|
||||
if (filteredCounts.length === 0) {
|
||||
return {
|
||||
yesterday: 0,
|
||||
last30Days: 0,
|
||||
}
|
||||
}
|
||||
|
||||
const last30DaysNumbers = filteredCounts.map((count: { totalCount: number }) => count.totalCount)
|
||||
const last30DaysCount = last30DaysNumbers.reduce((previousValue: number, currentValue: number) => {
|
||||
return previousValue + currentValue
|
||||
})
|
||||
|
||||
const averageActiveUsersLast30Days = Math.floor(last30DaysCount / last30DaysNumbers.length)
|
||||
|
||||
return {
|
||||
yesterday: totalActiveUsersYesterday,
|
||||
last30Days: averageActiveUsersLast30Days,
|
||||
}
|
||||
}
|
||||
|
||||
const getChartUrls = (
|
||||
data: any,
|
||||
): {
|
||||
@@ -44,6 +12,7 @@ const getChartUrls = (
|
||||
users: string
|
||||
quarterlyPerformance: string
|
||||
churn: string
|
||||
mrr: string
|
||||
mrrMonthly: string
|
||||
} => {
|
||||
const subscriptionPurchasingOverTime = data.activityStatisticsOverTime.find(
|
||||
@@ -268,6 +237,82 @@ const getChartUrls = (
|
||||
},
|
||||
}
|
||||
|
||||
const mrrOverTime = data.statisticsOverTime.find(
|
||||
(a: { name: string; period: number }) => a.name === 'mrr' && a.period === 27,
|
||||
)
|
||||
const monthlyPlansMrrOverTime = data.statisticsOverTime.find(
|
||||
(a: { name: string; period: number }) => a.name === 'monthly-plans-mrr' && a.period === 27,
|
||||
)
|
||||
const annualPlansMrrOverTime = data.statisticsOverTime.find(
|
||||
(a: { name: string; period: number }) => a.name === 'annual-plans-mrr' && a.period === 27,
|
||||
)
|
||||
const fiveYearPlansMrrOverTime = data.statisticsOverTime.find(
|
||||
(a: { name: string; period: number }) => a.name === 'five-year-plans-mrr' && a.period === 27,
|
||||
)
|
||||
const proPlansMrrOverTime = data.statisticsOverTime.find(
|
||||
(a: { name: string; period: number }) => a.name === 'pro-plans-mrr' && a.period === 27,
|
||||
)
|
||||
const plusPlansMrrOverTime = data.statisticsOverTime.find(
|
||||
(a: { name: string; period: number }) => a.name === 'plus-plans-mrr' && a.period === 27,
|
||||
)
|
||||
|
||||
const mrrOverTimeConfig = {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: mrrOverTime?.counts.map((count: { periodKey: any }) => count.periodKey),
|
||||
datasets: [
|
||||
{
|
||||
label: 'MRR',
|
||||
backgroundColor: 'rgb(25, 255, 140)',
|
||||
borderColor: 'rgb(25, 255, 140)',
|
||||
data: mrrOverTime?.counts.map((count: { totalCount: any }) => count.totalCount),
|
||||
fill: false,
|
||||
pointRadius: 2,
|
||||
},
|
||||
{
|
||||
label: 'MRR - Monthly Plans',
|
||||
backgroundColor: 'rgb(54, 162, 235)',
|
||||
borderColor: 'rgb(54, 162, 235)',
|
||||
data: monthlyPlansMrrOverTime?.counts.map((count: { totalCount: any }) => count.totalCount),
|
||||
fill: false,
|
||||
pointRadius: 2,
|
||||
},
|
||||
{
|
||||
label: 'MRR - Annual Plans',
|
||||
backgroundColor: 'rgb(255, 221, 51)',
|
||||
borderColor: 'rgb(255, 221, 51)',
|
||||
data: annualPlansMrrOverTime?.counts.map((count: { totalCount: any }) => count.totalCount),
|
||||
fill: false,
|
||||
pointRadius: 2,
|
||||
},
|
||||
{
|
||||
label: 'MRR - Five Year Plans',
|
||||
backgroundColor: 'rgb(255, 120, 120)',
|
||||
borderColor: 'rgb(255, 120, 120)',
|
||||
data: fiveYearPlansMrrOverTime?.counts.map((count: { totalCount: any }) => count.totalCount),
|
||||
fill: false,
|
||||
pointRadius: 2,
|
||||
},
|
||||
{
|
||||
label: 'MRR - PRO Plans',
|
||||
backgroundColor: 'rgb(255, 99, 132)',
|
||||
borderColor: 'rgb(255, 99, 132)',
|
||||
data: proPlansMrrOverTime?.counts.map((count: { totalCount: any }) => count.totalCount),
|
||||
fill: false,
|
||||
pointRadius: 2,
|
||||
},
|
||||
{
|
||||
label: 'MRR - PLUS Plans',
|
||||
backgroundColor: 'rgb(221, 51, 255)',
|
||||
borderColor: 'rgb(221, 51, 255)',
|
||||
data: plusPlansMrrOverTime?.counts.map((count: { totalCount: any }) => count.totalCount),
|
||||
fill: false,
|
||||
pointRadius: 2,
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
const mrrMonthlyOverTime = data.statisticsOverTime
|
||||
.find((a: { name: string; period: Period }) => a.name === 'mrr' && a.period === Period.ThisYear)
|
||||
?.counts.map((count: { totalCount: number }) => +count.totalCount.toFixed(2))
|
||||
@@ -326,6 +371,7 @@ const getChartUrls = (
|
||||
JSON.stringify(quarterlyConfig),
|
||||
)}`,
|
||||
churn: `https://quickchart.io/chart?width=800&c=${encodeURIComponent(JSON.stringify(churnConfig))}`,
|
||||
mrr: `https://quickchart.io/chart?width=800&c=${encodeURIComponent(JSON.stringify(mrrOverTimeConfig))}`,
|
||||
mrrMonthly: `https://quickchart.io/chart?width=800&c=${encodeURIComponent(JSON.stringify(mrrMonthlyConfig))}`,
|
||||
}
|
||||
}
|
||||
@@ -371,170 +417,156 @@ export const html = (data: any, timer: TimerInterface) => {
|
||||
a.name === AnalyticsActivity.DeleteAccount && a.period === Period.Last30Days,
|
||||
)
|
||||
const incomeMeasureYesterday = data.statisticMeasures.find(
|
||||
(a: { name: string; period: Period }) =>
|
||||
a.name === StatisticMeasureName.NAMES.Income && a.period === Period.Yesterday,
|
||||
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||
a.name === StatisticsMeasure.Income && a.period === Period.Yesterday,
|
||||
)
|
||||
const refundMeasureYesterday = data.statisticMeasures.find(
|
||||
(a: { name: string; period: Period }) =>
|
||||
a.name === StatisticMeasureName.NAMES.Refunds && a.period === Period.Yesterday,
|
||||
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||
a.name === StatisticsMeasure.Refunds && a.period === Period.Yesterday,
|
||||
)
|
||||
const incomeYesterday = incomeMeasureYesterday?.totalValue ?? 0
|
||||
const refundsYesterday = refundMeasureYesterday?.totalValue ?? 0
|
||||
const revenueYesterday = incomeYesterday - refundsYesterday
|
||||
|
||||
const subscriptionLengthMeasureYesterday = data.statisticMeasures.find(
|
||||
(a: { name: string; period: Period }) =>
|
||||
a.name === StatisticMeasureName.NAMES.SubscriptionLength && a.period === Period.Yesterday,
|
||||
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||
a.name === StatisticsMeasure.SubscriptionLength && a.period === Period.Yesterday,
|
||||
)
|
||||
const subscriptionLengthDurationYesterday = timer.convertMicrosecondsToTimeStructure(
|
||||
Math.floor(subscriptionLengthMeasureYesterday?.average ?? 0),
|
||||
)
|
||||
|
||||
const subscriptionRemainingTimePercentageMeasureYesterday = data.statisticMeasures.find(
|
||||
(a: { name: string; period: Period }) =>
|
||||
a.name === StatisticMeasureName.NAMES.RemainingSubscriptionTimePercentage && a.period === Period.Yesterday,
|
||||
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||
a.name === StatisticsMeasure.RemainingSubscriptionTimePercentage && a.period === Period.Yesterday,
|
||||
)
|
||||
const subscriptionRemainingTimePercentageYesterday = Math.floor(
|
||||
subscriptionRemainingTimePercentageMeasureYesterday?.average ?? 0,
|
||||
)
|
||||
|
||||
const registrationLengthMeasureYesterday = data.statisticMeasures.find(
|
||||
(a: { name: string; period: Period }) =>
|
||||
a.name === StatisticMeasureName.NAMES.RegistrationLength && a.period === Period.Yesterday,
|
||||
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||
a.name === StatisticsMeasure.RegistrationLength && a.period === Period.Yesterday,
|
||||
)
|
||||
const registrationLengthDurationYesterday = timer.convertMicrosecondsToTimeStructure(
|
||||
Math.floor(registrationLengthMeasureYesterday?.average ?? 0),
|
||||
)
|
||||
|
||||
const registrationToSubscriptionMeasureYesterday = data.statisticMeasures.find(
|
||||
(a: { name: string; period: Period }) =>
|
||||
a.name === StatisticMeasureName.NAMES.RegistrationToSubscriptionTime && a.period === Period.Yesterday,
|
||||
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||
a.name === StatisticsMeasure.RegistrationToSubscriptionTime && a.period === Period.Yesterday,
|
||||
)
|
||||
const registrationToSubscriptionDurationYesterday = timer.convertMicrosecondsToTimeStructure(
|
||||
Math.floor(registrationToSubscriptionMeasureYesterday?.average ?? 0),
|
||||
)
|
||||
|
||||
const incomeMeasureThisMonth = data.statisticMeasures.find(
|
||||
(a: { name: string; period: Period }) =>
|
||||
a.name === StatisticMeasureName.NAMES.Income && a.period === Period.ThisMonth,
|
||||
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||
a.name === StatisticsMeasure.Income && a.period === Period.ThisMonth,
|
||||
)
|
||||
const refundMeasureThisMonth = data.statisticMeasures.find(
|
||||
(a: { name: string; period: Period }) =>
|
||||
a.name === StatisticMeasureName.NAMES.Refunds && a.period === Period.ThisMonth,
|
||||
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||
a.name === StatisticsMeasure.Refunds && a.period === Period.ThisMonth,
|
||||
)
|
||||
const incomeThisMonth = incomeMeasureThisMonth?.totalValue ?? 0
|
||||
const refundsThisMonth = refundMeasureThisMonth?.totalValue ?? 0
|
||||
const revenueThisMonth = incomeThisMonth - refundsThisMonth
|
||||
|
||||
const subscriptionLengthMeasureThisMonth = data.statisticMeasures.find(
|
||||
(a: { name: string; period: Period }) =>
|
||||
a.name === StatisticMeasureName.NAMES.SubscriptionLength && a.period === Period.ThisMonth,
|
||||
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||
a.name === StatisticsMeasure.SubscriptionLength && a.period === Period.ThisMonth,
|
||||
)
|
||||
const subscriptionLengthDurationThisMonth = timer.convertMicrosecondsToTimeStructure(
|
||||
Math.floor(subscriptionLengthMeasureThisMonth?.average ?? 0),
|
||||
)
|
||||
|
||||
const subscriptionRemainingTimePercentageMeasureThisMonth = data.statisticMeasures.find(
|
||||
(a: { name: string; period: Period }) =>
|
||||
a.name === StatisticMeasureName.NAMES.RemainingSubscriptionTimePercentage && a.period === Period.ThisMonth,
|
||||
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||
a.name === StatisticsMeasure.RemainingSubscriptionTimePercentage && a.period === Period.ThisMonth,
|
||||
)
|
||||
const subscriptionRemainingTimePercentageThisMonth = Math.floor(
|
||||
subscriptionRemainingTimePercentageMeasureThisMonth?.average ?? 0,
|
||||
)
|
||||
|
||||
const registrationLengthMeasureThisMonth = data.statisticMeasures.find(
|
||||
(a: { name: string; period: Period }) =>
|
||||
a.name === StatisticMeasureName.NAMES.RegistrationLength && a.period === Period.ThisMonth,
|
||||
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||
a.name === StatisticsMeasure.RegistrationLength && a.period === Period.ThisMonth,
|
||||
)
|
||||
const registrationLengthDurationThisMonth = timer.convertMicrosecondsToTimeStructure(
|
||||
Math.floor(registrationLengthMeasureThisMonth?.average ?? 0),
|
||||
)
|
||||
|
||||
const registrationToSubscriptionMeasureThisMonth = data.statisticMeasures.find(
|
||||
(a: { name: string; period: Period }) =>
|
||||
a.name === StatisticMeasureName.NAMES.RegistrationToSubscriptionTime && a.period === Period.ThisMonth,
|
||||
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||
a.name === StatisticsMeasure.RegistrationToSubscriptionTime && a.period === Period.ThisMonth,
|
||||
)
|
||||
const registrationToSubscriptionDurationThisMonth = timer.convertMicrosecondsToTimeStructure(
|
||||
Math.floor(registrationToSubscriptionMeasureThisMonth?.average ?? 0),
|
||||
)
|
||||
|
||||
const plusSubscriptionsInitialAnnualPaymentsYesterday = data.statisticMeasures.find(
|
||||
(a: { name: string; period: Period }) =>
|
||||
a.name === StatisticMeasureName.NAMES.PlusSubscriptionInitialAnnualPaymentsIncome &&
|
||||
a.period === Period.Yesterday,
|
||||
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||
a.name === StatisticsMeasure.PlusSubscriptionInitialAnnualPaymentsIncome && a.period === Period.Yesterday,
|
||||
)
|
||||
const plusSubscriptionsInitialMonthlyPaymentsYesterday = data.statisticMeasures.find(
|
||||
(a: { name: string; period: Period }) =>
|
||||
a.name === StatisticMeasureName.NAMES.PlusSubscriptionInitialMonthlyPaymentsIncome &&
|
||||
a.period === Period.Yesterday,
|
||||
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||
a.name === StatisticsMeasure.PlusSubscriptionInitialMonthlyPaymentsIncome && a.period === Period.Yesterday,
|
||||
)
|
||||
const plusSubscriptionsRenewingAnnualPaymentsYesterday = data.statisticMeasures.find(
|
||||
(a: { name: string; period: Period }) =>
|
||||
a.name === StatisticMeasureName.NAMES.PlusSubscriptionRenewingAnnualPaymentsIncome &&
|
||||
a.period === Period.Yesterday,
|
||||
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||
a.name === StatisticsMeasure.PlusSubscriptionRenewingAnnualPaymentsIncome && a.period === Period.Yesterday,
|
||||
)
|
||||
const plusSubscriptionsRenewingMonthlyPaymentsYesterday = data.statisticMeasures.find(
|
||||
(a: { name: string; period: Period }) =>
|
||||
a.name === StatisticMeasureName.NAMES.PlusSubscriptionRenewingMonthlyPaymentsIncome &&
|
||||
a.period === Period.Yesterday,
|
||||
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||
a.name === StatisticsMeasure.PlusSubscriptionRenewingMonthlyPaymentsIncome && a.period === Period.Yesterday,
|
||||
)
|
||||
const proSubscriptionsInitialAnnualPaymentsYesterday = data.statisticMeasures.find(
|
||||
(a: { name: string; period: Period }) =>
|
||||
a.name === StatisticMeasureName.NAMES.ProSubscriptionInitialAnnualPaymentsIncome && a.period === Period.Yesterday,
|
||||
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||
a.name === StatisticsMeasure.ProSubscriptionInitialAnnualPaymentsIncome && a.period === Period.Yesterday,
|
||||
)
|
||||
const proSubscriptionsInitialMonthlyPaymentsYesterday = data.statisticMeasures.find(
|
||||
(a: { name: string; period: Period }) =>
|
||||
a.name === StatisticMeasureName.NAMES.ProSubscriptionInitialMonthlyPaymentsIncome &&
|
||||
a.period === Period.Yesterday,
|
||||
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||
a.name === StatisticsMeasure.ProSubscriptionInitialMonthlyPaymentsIncome && a.period === Period.Yesterday,
|
||||
)
|
||||
const proSubscriptionsRenewingAnnualPaymentsYesterday = data.statisticMeasures.find(
|
||||
(a: { name: string; period: Period }) =>
|
||||
a.name === StatisticMeasureName.NAMES.ProSubscriptionRenewingAnnualPaymentsIncome &&
|
||||
a.period === Period.Yesterday,
|
||||
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||
a.name === StatisticsMeasure.ProSubscriptionRenewingAnnualPaymentsIncome && a.period === Period.Yesterday,
|
||||
)
|
||||
const proSubscriptionsRenewingMonthlyPaymentsYesterday = data.statisticMeasures.find(
|
||||
(a: { name: string; period: Period }) =>
|
||||
a.name === StatisticMeasureName.NAMES.ProSubscriptionRenewingMonthlyPaymentsIncome &&
|
||||
a.period === Period.Yesterday,
|
||||
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||
a.name === StatisticsMeasure.ProSubscriptionRenewingMonthlyPaymentsIncome && a.period === Period.Yesterday,
|
||||
)
|
||||
const plusSubscriptionsInitialAnnualPaymentsThisMonth = data.statisticMeasures.find(
|
||||
(a: { name: string; period: Period }) =>
|
||||
a.name === StatisticMeasureName.NAMES.PlusSubscriptionInitialAnnualPaymentsIncome &&
|
||||
a.period === Period.ThisMonth,
|
||||
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||
a.name === StatisticsMeasure.PlusSubscriptionInitialAnnualPaymentsIncome && a.period === Period.ThisMonth,
|
||||
)
|
||||
const plusSubscriptionsInitialMonthlyPaymentsThisMonth = data.statisticMeasures.find(
|
||||
(a: { name: string; period: Period }) =>
|
||||
a.name === StatisticMeasureName.NAMES.PlusSubscriptionInitialMonthlyPaymentsIncome &&
|
||||
a.period === Period.ThisMonth,
|
||||
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||
a.name === StatisticsMeasure.PlusSubscriptionInitialMonthlyPaymentsIncome && a.period === Period.ThisMonth,
|
||||
)
|
||||
const plusSubscriptionsRenewingAnnualPaymentsThisMonth = data.statisticMeasures.find(
|
||||
(a: { name: string; period: Period }) =>
|
||||
a.name === StatisticMeasureName.NAMES.PlusSubscriptionRenewingAnnualPaymentsIncome &&
|
||||
a.period === Period.ThisMonth,
|
||||
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||
a.name === StatisticsMeasure.PlusSubscriptionRenewingAnnualPaymentsIncome && a.period === Period.ThisMonth,
|
||||
)
|
||||
const plusSubscriptionsRenewingMonthlyPaymentsThisMonth = data.statisticMeasures.find(
|
||||
(a: { name: string; period: Period }) =>
|
||||
a.name === StatisticMeasureName.NAMES.PlusSubscriptionRenewingMonthlyPaymentsIncome &&
|
||||
a.period === Period.ThisMonth,
|
||||
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||
a.name === StatisticsMeasure.PlusSubscriptionRenewingMonthlyPaymentsIncome && a.period === Period.ThisMonth,
|
||||
)
|
||||
const proSubscriptionsInitialAnnualPaymentsThisMonth = data.statisticMeasures.find(
|
||||
(a: { name: string; period: Period }) =>
|
||||
a.name === StatisticMeasureName.NAMES.ProSubscriptionInitialAnnualPaymentsIncome && a.period === Period.ThisMonth,
|
||||
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||
a.name === StatisticsMeasure.ProSubscriptionInitialAnnualPaymentsIncome && a.period === Period.ThisMonth,
|
||||
)
|
||||
const proSubscriptionsInitialMonthlyPaymentsThisMonth = data.statisticMeasures.find(
|
||||
(a: { name: string; period: Period }) =>
|
||||
a.name === StatisticMeasureName.NAMES.ProSubscriptionInitialMonthlyPaymentsIncome &&
|
||||
a.period === Period.ThisMonth,
|
||||
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||
a.name === StatisticsMeasure.ProSubscriptionInitialMonthlyPaymentsIncome && a.period === Period.ThisMonth,
|
||||
)
|
||||
const proSubscriptionsRenewingAnnualPaymentsThisMonth = data.statisticMeasures.find(
|
||||
(a: { name: string; period: Period }) =>
|
||||
a.name === StatisticMeasureName.NAMES.ProSubscriptionRenewingAnnualPaymentsIncome &&
|
||||
a.period === Period.ThisMonth,
|
||||
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||
a.name === StatisticsMeasure.ProSubscriptionRenewingAnnualPaymentsIncome && a.period === Period.ThisMonth,
|
||||
)
|
||||
const proSubscriptionsRenewingMonthlyPaymentsThisMonth = data.statisticMeasures.find(
|
||||
(a: { name: string; period: Period }) =>
|
||||
a.name === StatisticMeasureName.NAMES.ProSubscriptionRenewingMonthlyPaymentsIncome &&
|
||||
a.period === Period.ThisMonth,
|
||||
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||
a.name === StatisticsMeasure.ProSubscriptionRenewingMonthlyPaymentsIncome && a.period === Period.ThisMonth,
|
||||
)
|
||||
|
||||
const mrrOverTime = data.statisticsOverTime.find(
|
||||
@@ -562,39 +594,12 @@ export const html = (data: any, timer: TimerInterface) => {
|
||||
(value: { periodKey: string }) => value.periodKey === thisMonthPeriodKey,
|
||||
)
|
||||
|
||||
const totalActiveUsers = countActiveUsers(StatisticMeasureName.NAMES.ActiveUsers, data)
|
||||
const totalActiveFreeUsers = countActiveUsers(StatisticMeasureName.NAMES.ActiveFreeUsers, data)
|
||||
const totalActivePlusUsers = countActiveUsers(StatisticMeasureName.NAMES.ActivePlusUsers, data)
|
||||
const totalActiveProUsers = countActiveUsers(StatisticMeasureName.NAMES.ActiveProUsers, data)
|
||||
|
||||
return ` <div>
|
||||
<p>Hello,</p>
|
||||
<p>
|
||||
<strong>Here are some statistics from yesterday:</strong>
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
<b>Active Users</b>
|
||||
<ul>
|
||||
<li>
|
||||
<b>Total:</b> ${totalActiveUsers.yesterday.toLocaleString('en-US')}
|
||||
</li>
|
||||
<li>
|
||||
<b>By Subscription Type:</b>
|
||||
<ul>
|
||||
<li>
|
||||
<b>FREE:</b> ${totalActiveFreeUsers.yesterday.toLocaleString('en-US')}
|
||||
</li>
|
||||
<li>
|
||||
<b>PLUS:</b> ${totalActivePlusUsers.yesterday.toLocaleString('en-US')}
|
||||
</li>
|
||||
<li>
|
||||
<b>PRO:</b> ${totalActiveProUsers.yesterday.toLocaleString('en-US')}
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<b>Payments</b>
|
||||
<ul>
|
||||
@@ -793,28 +798,6 @@ export const html = (data: any, timer: TimerInterface) => {
|
||||
<strong>Here are some statistics from last 30 days:</strong>
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
<b>Active Users (Average)</b>
|
||||
<ul>
|
||||
<li>
|
||||
<b>Total:</b> ${totalActiveUsers.last30Days.toLocaleString('en-US')}
|
||||
</li>
|
||||
<li>
|
||||
<b>By Subscription Type:</b>
|
||||
<ul>
|
||||
<li>
|
||||
<b>FREE:</b> ${totalActiveFreeUsers.last30Days.toLocaleString('en-US')}
|
||||
</li>
|
||||
<li>
|
||||
<b>PLUS:</b> ${totalActivePlusUsers.last30Days.toLocaleString('en-US')}
|
||||
</li>
|
||||
<li>
|
||||
<b>PRO:</b> ${totalActiveProUsers.last30Days.toLocaleString('en-US')}
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<b>Payments (This Month)</b>
|
||||
<ul>
|
||||
@@ -947,6 +930,10 @@ export const html = (data: any, timer: TimerInterface) => {
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<p>
|
||||
<strong>Here is the MRR chart over 30 days:</strong>
|
||||
</p>
|
||||
<img src=${chartUrls.mrr}></img>
|
||||
<p>
|
||||
<strong>Here is the MRR Monthly chart this year:</strong>
|
||||
</p>
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
import 'reflect-metadata'
|
||||
|
||||
import { AccountDeletionRequestedEvent } from '@standardnotes/domain-events'
|
||||
import { AccountDeletionRequestedEventHandler } from './AccountDeletionRequestedEventHandler'
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
import { AnalyticsStoreInterface } from '../Analytics/AnalyticsStoreInterface'
|
||||
import { StatisticsStoreInterface } from '../Statistics/StatisticsStoreInterface'
|
||||
import { Period } from '../Time/Period'
|
||||
import { AnalyticsEntityRepositoryInterface } from '../Entity/AnalyticsEntityRepositoryInterface'
|
||||
|
||||
describe('AccountDeletionRequestedEventHandler', () => {
|
||||
let event: AccountDeletionRequestedEvent
|
||||
let analyticsEntityRepository: AnalyticsEntityRepositoryInterface
|
||||
let analyticsStore: AnalyticsStoreInterface
|
||||
let statisticsStore: StatisticsStoreInterface
|
||||
let timer: TimerInterface
|
||||
|
||||
const createHandler = () =>
|
||||
new AccountDeletionRequestedEventHandler(analyticsEntityRepository, analyticsStore, statisticsStore, timer)
|
||||
|
||||
beforeEach(() => {
|
||||
event = {} as jest.Mocked<AccountDeletionRequestedEvent>
|
||||
event.createdAt = new Date(1)
|
||||
event.payload = {
|
||||
userUuid: '1-2-3',
|
||||
userCreatedAtTimestamp: 1,
|
||||
regularSubscriptionUuid: '2-3-4',
|
||||
}
|
||||
|
||||
analyticsEntityRepository = {} as jest.Mocked<AnalyticsEntityRepositoryInterface>
|
||||
analyticsEntityRepository.findOneByUserUuid = jest.fn().mockReturnValue({ id: 3 })
|
||||
analyticsEntityRepository.remove = jest.fn()
|
||||
|
||||
analyticsStore = {} as jest.Mocked<AnalyticsStoreInterface>
|
||||
analyticsStore.markActivity = jest.fn()
|
||||
|
||||
statisticsStore = {} as jest.Mocked<StatisticsStoreInterface>
|
||||
statisticsStore.incrementMeasure = jest.fn()
|
||||
|
||||
timer = {} as jest.Mocked<TimerInterface>
|
||||
timer.getTimestampInMicroseconds = jest.fn().mockReturnValue(123)
|
||||
})
|
||||
|
||||
it('should mark account deletion and registration length', async () => {
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(analyticsStore.markActivity).toHaveBeenCalledWith(['DeleteAccount'], 3, [
|
||||
Period.Today,
|
||||
Period.ThisWeek,
|
||||
Period.ThisMonth,
|
||||
])
|
||||
expect(statisticsStore.incrementMeasure).toHaveBeenCalledWith('registration-length', 122, [
|
||||
Period.Today,
|
||||
Period.ThisWeek,
|
||||
Period.ThisMonth,
|
||||
])
|
||||
expect(analyticsEntityRepository.remove).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should not mark anything if entity is not found', async () => {
|
||||
analyticsEntityRepository.findOneByUserUuid = jest.fn().mockReturnValue(null)
|
||||
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(analyticsStore.markActivity).not.toHaveBeenCalled()
|
||||
expect(statisticsStore.incrementMeasure).not.toHaveBeenCalled()
|
||||
expect(analyticsEntityRepository.remove).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
@@ -1,13 +1,12 @@
|
||||
import { AccountDeletionRequestedEvent, DomainEventHandlerInterface } from '@standardnotes/domain-events'
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
import { inject, injectable, optional } from 'inversify'
|
||||
import { Mixpanel } from 'mixpanel'
|
||||
import { inject, injectable } from 'inversify'
|
||||
|
||||
import TYPES from '../../Bootstrap/Types'
|
||||
import { AnalyticsActivity } from '../Analytics/AnalyticsActivity'
|
||||
import { AnalyticsStoreInterface } from '../Analytics/AnalyticsStoreInterface'
|
||||
import { AnalyticsEntityRepositoryInterface } from '../Entity/AnalyticsEntityRepositoryInterface'
|
||||
import { StatisticMeasureName } from '../Statistics/StatisticMeasureName'
|
||||
import { StatisticsMeasure } from '../Statistics/StatisticsMeasure'
|
||||
import { StatisticsStoreInterface } from '../Statistics/StatisticsStoreInterface'
|
||||
import { Period } from '../Time/Period'
|
||||
|
||||
@@ -18,7 +17,6 @@ export class AccountDeletionRequestedEventHandler implements DomainEventHandlerI
|
||||
@inject(TYPES.AnalyticsStore) private analyticsStore: AnalyticsStoreInterface,
|
||||
@inject(TYPES.StatisticsStore) private statisticsStore: StatisticsStoreInterface,
|
||||
@inject(TYPES.Timer) private timer: TimerInterface,
|
||||
@inject(TYPES.MixpanelClient) @optional() private mixpanelClient: Mixpanel | null,
|
||||
) {}
|
||||
|
||||
async handle(event: AccountDeletionRequestedEvent): Promise<void> {
|
||||
@@ -35,19 +33,12 @@ export class AccountDeletionRequestedEventHandler implements DomainEventHandlerI
|
||||
])
|
||||
|
||||
const registrationLength = this.timer.getTimestampInMicroseconds() - event.payload.userCreatedAtTimestamp
|
||||
await this.statisticsStore.incrementMeasure(StatisticMeasureName.NAMES.RegistrationLength, registrationLength, [
|
||||
await this.statisticsStore.incrementMeasure(StatisticsMeasure.RegistrationLength, registrationLength, [
|
||||
Period.Today,
|
||||
Period.ThisWeek,
|
||||
Period.ThisMonth,
|
||||
])
|
||||
|
||||
await this.analyticsEntityRepository.remove(analyticsEntity)
|
||||
|
||||
if (this.mixpanelClient !== null) {
|
||||
this.mixpanelClient.track(event.type, {
|
||||
distinct_id: analyticsEntity.id.toString(),
|
||||
user_created_at: this.timer.convertMicrosecondsToDate(event.payload.userCreatedAtTimestamp),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import 'reflect-metadata'
|
||||
|
||||
import { PaymentFailedEvent } from '@standardnotes/domain-events'
|
||||
|
||||
import { PaymentFailedEventHandler } from './PaymentFailedEventHandler'
|
||||
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
|
||||
import { AnalyticsStoreInterface } from '../Analytics/AnalyticsStoreInterface'
|
||||
|
||||
describe('PaymentFailedEventHandler', () => {
|
||||
let event: PaymentFailedEvent
|
||||
let getUserAnalyticsId: GetUserAnalyticsId
|
||||
let analyticsStore: AnalyticsStoreInterface
|
||||
|
||||
const createHandler = () => new PaymentFailedEventHandler(getUserAnalyticsId, analyticsStore)
|
||||
|
||||
beforeEach(() => {
|
||||
getUserAnalyticsId = {} as jest.Mocked<GetUserAnalyticsId>
|
||||
getUserAnalyticsId.execute = jest.fn().mockReturnValue({ analyticsId: 3 })
|
||||
|
||||
analyticsStore = {} as jest.Mocked<AnalyticsStoreInterface>
|
||||
analyticsStore.markActivity = jest.fn()
|
||||
|
||||
event = {} as jest.Mocked<PaymentFailedEvent>
|
||||
event.payload = {
|
||||
userEmail: 'test@test.com',
|
||||
}
|
||||
})
|
||||
|
||||
it('should mark payment failed for analytics', async () => {
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(analyticsStore.markActivity).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
@@ -1,6 +1,5 @@
|
||||
import { DomainEventHandlerInterface, PaymentFailedEvent } from '@standardnotes/domain-events'
|
||||
import { inject, injectable, optional } from 'inversify'
|
||||
import { Mixpanel } from 'mixpanel'
|
||||
import { inject, injectable } from 'inversify'
|
||||
|
||||
import TYPES from '../../Bootstrap/Types'
|
||||
import { AnalyticsActivity } from '../Analytics/AnalyticsActivity'
|
||||
@@ -13,7 +12,6 @@ export class PaymentFailedEventHandler implements DomainEventHandlerInterface {
|
||||
constructor(
|
||||
@inject(TYPES.GetUserAnalyticsId) private getUserAnalyticsId: GetUserAnalyticsId,
|
||||
@inject(TYPES.AnalyticsStore) private analyticsStore: AnalyticsStoreInterface,
|
||||
@inject(TYPES.MixpanelClient) @optional() private mixpanelClient: Mixpanel | null,
|
||||
) {}
|
||||
|
||||
async handle(event: PaymentFailedEvent): Promise<void> {
|
||||
@@ -23,11 +21,5 @@ export class PaymentFailedEventHandler implements DomainEventHandlerInterface {
|
||||
Period.ThisWeek,
|
||||
Period.ThisMonth,
|
||||
])
|
||||
|
||||
if (this.mixpanelClient !== null) {
|
||||
this.mixpanelClient.track(event.type, {
|
||||
distinct_id: analyticsId.toString(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
import 'reflect-metadata'
|
||||
|
||||
import { PaymentSuccessEvent } from '@standardnotes/domain-events'
|
||||
|
||||
import { PaymentSuccessEventHandler } from './PaymentSuccessEventHandler'
|
||||
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
|
||||
import { Logger } from 'winston'
|
||||
import { StatisticsStoreInterface } from '../Statistics/StatisticsStoreInterface'
|
||||
import { AnalyticsStoreInterface } from '../Analytics/AnalyticsStoreInterface'
|
||||
import { Period } from '../Time/Period'
|
||||
|
||||
describe('PaymentSuccessEventHandler', () => {
|
||||
let event: PaymentSuccessEvent
|
||||
let getUserAnalyticsId: GetUserAnalyticsId
|
||||
let analyticsStore: AnalyticsStoreInterface
|
||||
let statisticsStore: StatisticsStoreInterface
|
||||
let logger: Logger
|
||||
|
||||
const createHandler = () =>
|
||||
new PaymentSuccessEventHandler(getUserAnalyticsId, analyticsStore, statisticsStore, logger)
|
||||
|
||||
beforeEach(() => {
|
||||
getUserAnalyticsId = {} as jest.Mocked<GetUserAnalyticsId>
|
||||
getUserAnalyticsId.execute = jest.fn().mockReturnValue({ analyticsId: 3 })
|
||||
|
||||
analyticsStore = {} as jest.Mocked<AnalyticsStoreInterface>
|
||||
analyticsStore.markActivity = jest.fn()
|
||||
|
||||
statisticsStore = {} as jest.Mocked<StatisticsStoreInterface>
|
||||
statisticsStore.incrementMeasure = jest.fn()
|
||||
|
||||
event = {} as jest.Mocked<PaymentSuccessEvent>
|
||||
event.payload = {
|
||||
userEmail: 'test@test.com',
|
||||
amount: 12.45,
|
||||
billingFrequency: 12,
|
||||
paymentType: 'initial',
|
||||
subscriptionName: 'PRO_PLAN',
|
||||
}
|
||||
|
||||
logger = {} as jest.Mocked<Logger>
|
||||
logger.warn = jest.fn()
|
||||
})
|
||||
|
||||
it('should mark payment success for analytics', async () => {
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(analyticsStore.markActivity).toHaveBeenCalled()
|
||||
expect(statisticsStore.incrementMeasure).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
'pro-subscription-initial-annual-payments-income',
|
||||
12.45,
|
||||
[Period.Today, Period.ThisWeek, Period.ThisMonth],
|
||||
)
|
||||
})
|
||||
|
||||
it('should mark non-detailed payment success statistics for analytics', async () => {
|
||||
event.payload = {
|
||||
userEmail: 'test@test.com',
|
||||
amount: 12.45,
|
||||
billingFrequency: 13,
|
||||
paymentType: 'initial',
|
||||
subscriptionName: 'PRO_PLAN',
|
||||
}
|
||||
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(statisticsStore.incrementMeasure).toBeCalledTimes(1)
|
||||
expect(statisticsStore.incrementMeasure).toHaveBeenNthCalledWith(1, 'income', 12.45, [
|
||||
Period.Today,
|
||||
Period.ThisWeek,
|
||||
Period.ThisMonth,
|
||||
])
|
||||
})
|
||||
})
|
||||
@@ -1,13 +1,12 @@
|
||||
import { PaymentType, SubscriptionBillingFrequency, SubscriptionName } from '@standardnotes/common'
|
||||
import { DomainEventHandlerInterface, PaymentSuccessEvent } from '@standardnotes/domain-events'
|
||||
import { inject, injectable, optional } from 'inversify'
|
||||
import { Mixpanel } from 'mixpanel'
|
||||
import { inject, injectable } from 'inversify'
|
||||
import { Logger } from 'winston'
|
||||
|
||||
import TYPES from '../../Bootstrap/Types'
|
||||
import { AnalyticsActivity } from '../Analytics/AnalyticsActivity'
|
||||
import { AnalyticsStoreInterface } from '../Analytics/AnalyticsStoreInterface'
|
||||
import { StatisticMeasureName } from '../Statistics/StatisticMeasureName'
|
||||
import { StatisticsMeasure } from '../Statistics/StatisticsMeasure'
|
||||
import { StatisticsStoreInterface } from '../Statistics/StatisticsStoreInterface'
|
||||
import { Period } from '../Time/Period'
|
||||
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
|
||||
@@ -21,27 +20,15 @@ export class PaymentSuccessEventHandler implements DomainEventHandlerInterface {
|
||||
[
|
||||
PaymentType.Initial,
|
||||
new Map([
|
||||
[
|
||||
SubscriptionBillingFrequency.Monthly,
|
||||
StatisticMeasureName.NAMES.PlusSubscriptionInitialMonthlyPaymentsIncome,
|
||||
],
|
||||
[
|
||||
SubscriptionBillingFrequency.Annual,
|
||||
StatisticMeasureName.NAMES.PlusSubscriptionInitialAnnualPaymentsIncome,
|
||||
],
|
||||
[SubscriptionBillingFrequency.Monthly, StatisticsMeasure.PlusSubscriptionInitialMonthlyPaymentsIncome],
|
||||
[SubscriptionBillingFrequency.Annual, StatisticsMeasure.PlusSubscriptionInitialAnnualPaymentsIncome],
|
||||
]),
|
||||
],
|
||||
[
|
||||
PaymentType.Renewal,
|
||||
new Map([
|
||||
[
|
||||
SubscriptionBillingFrequency.Monthly,
|
||||
StatisticMeasureName.NAMES.PlusSubscriptionRenewingMonthlyPaymentsIncome,
|
||||
],
|
||||
[
|
||||
SubscriptionBillingFrequency.Annual,
|
||||
StatisticMeasureName.NAMES.PlusSubscriptionRenewingAnnualPaymentsIncome,
|
||||
],
|
||||
[SubscriptionBillingFrequency.Monthly, StatisticsMeasure.PlusSubscriptionRenewingMonthlyPaymentsIncome],
|
||||
[SubscriptionBillingFrequency.Annual, StatisticsMeasure.PlusSubscriptionRenewingAnnualPaymentsIncome],
|
||||
]),
|
||||
],
|
||||
]),
|
||||
@@ -52,27 +39,15 @@ export class PaymentSuccessEventHandler implements DomainEventHandlerInterface {
|
||||
[
|
||||
PaymentType.Initial,
|
||||
new Map([
|
||||
[
|
||||
SubscriptionBillingFrequency.Monthly,
|
||||
StatisticMeasureName.NAMES.ProSubscriptionInitialMonthlyPaymentsIncome,
|
||||
],
|
||||
[
|
||||
SubscriptionBillingFrequency.Annual,
|
||||
StatisticMeasureName.NAMES.ProSubscriptionInitialAnnualPaymentsIncome,
|
||||
],
|
||||
[SubscriptionBillingFrequency.Monthly, StatisticsMeasure.ProSubscriptionInitialMonthlyPaymentsIncome],
|
||||
[SubscriptionBillingFrequency.Annual, StatisticsMeasure.ProSubscriptionInitialAnnualPaymentsIncome],
|
||||
]),
|
||||
],
|
||||
[
|
||||
PaymentType.Renewal,
|
||||
new Map([
|
||||
[
|
||||
SubscriptionBillingFrequency.Monthly,
|
||||
StatisticMeasureName.NAMES.ProSubscriptionRenewingMonthlyPaymentsIncome,
|
||||
],
|
||||
[
|
||||
SubscriptionBillingFrequency.Annual,
|
||||
StatisticMeasureName.NAMES.ProSubscriptionRenewingAnnualPaymentsIncome,
|
||||
],
|
||||
[SubscriptionBillingFrequency.Monthly, StatisticsMeasure.ProSubscriptionRenewingMonthlyPaymentsIncome],
|
||||
[SubscriptionBillingFrequency.Annual, StatisticsMeasure.ProSubscriptionRenewingAnnualPaymentsIncome],
|
||||
]),
|
||||
],
|
||||
]),
|
||||
@@ -84,7 +59,6 @@ export class PaymentSuccessEventHandler implements DomainEventHandlerInterface {
|
||||
@inject(TYPES.AnalyticsStore) private analyticsStore: AnalyticsStoreInterface,
|
||||
@inject(TYPES.StatisticsStore) private statisticsStore: StatisticsStoreInterface,
|
||||
@inject(TYPES.Logger) private logger: Logger,
|
||||
@inject(TYPES.MixpanelClient) @optional() private mixpanelClient: Mixpanel | null,
|
||||
) {}
|
||||
|
||||
async handle(event: PaymentSuccessEvent): Promise<void> {
|
||||
@@ -95,7 +69,7 @@ export class PaymentSuccessEventHandler implements DomainEventHandlerInterface {
|
||||
Period.ThisMonth,
|
||||
])
|
||||
|
||||
const statisticMeasures = [StatisticMeasureName.NAMES.Income]
|
||||
const statisticMeasures = [StatisticsMeasure.Income]
|
||||
|
||||
const detailedMeasure = this.DETAILED_MEASURES.get(event.payload.subscriptionName as SubscriptionName)
|
||||
?.get(event.payload.paymentType as PaymentType)
|
||||
@@ -115,19 +89,5 @@ export class PaymentSuccessEventHandler implements DomainEventHandlerInterface {
|
||||
Period.ThisMonth,
|
||||
])
|
||||
}
|
||||
|
||||
if (this.mixpanelClient !== null) {
|
||||
this.mixpanelClient.track(event.type, {
|
||||
distinct_id: analyticsId.toString(),
|
||||
amount: event.payload.amount,
|
||||
billing_frequency: event.payload.billingFrequency,
|
||||
payment_type: event.payload.paymentType,
|
||||
subscription_name: event.payload.subscriptionName,
|
||||
})
|
||||
|
||||
this.mixpanelClient.people.track_charge(analyticsId.toString(), event.payload.amount)
|
||||
|
||||
this.mixpanelClient.people.set(analyticsId.toString(), 'subscription', event.payload.subscriptionName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import 'reflect-metadata'
|
||||
|
||||
import { RefundProcessedEvent } from '@standardnotes/domain-events'
|
||||
|
||||
import { RefundProcessedEventHandler } from './RefundProcessedEventHandler'
|
||||
import { StatisticsMeasure } from '../Statistics/StatisticsMeasure'
|
||||
import { StatisticsStoreInterface } from '../Statistics/StatisticsStoreInterface'
|
||||
import { Period } from '../Time/Period'
|
||||
|
||||
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,
|
||||
])
|
||||
})
|
||||
})
|
||||
@@ -1,36 +1,20 @@
|
||||
import { DomainEventHandlerInterface, RefundProcessedEvent } from '@standardnotes/domain-events'
|
||||
import { inject, injectable, optional } from 'inversify'
|
||||
import { Mixpanel } from 'mixpanel'
|
||||
import { inject, injectable } from 'inversify'
|
||||
|
||||
import TYPES from '../../Bootstrap/Types'
|
||||
import { StatisticMeasureName } from '../Statistics/StatisticMeasureName'
|
||||
import { StatisticsMeasure } from '../Statistics/StatisticsMeasure'
|
||||
import { StatisticsStoreInterface } from '../Statistics/StatisticsStoreInterface'
|
||||
import { Period } from '../Time/Period'
|
||||
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
|
||||
|
||||
@injectable()
|
||||
export class RefundProcessedEventHandler implements DomainEventHandlerInterface {
|
||||
constructor(
|
||||
@inject(TYPES.GetUserAnalyticsId) private getUserAnalyticsId: GetUserAnalyticsId,
|
||||
@inject(TYPES.StatisticsStore) private statisticsStore: StatisticsStoreInterface,
|
||||
@inject(TYPES.MixpanelClient) @optional() private mixpanelClient: Mixpanel | null,
|
||||
) {}
|
||||
constructor(@inject(TYPES.StatisticsStore) private statisticsStore: StatisticsStoreInterface) {}
|
||||
|
||||
async handle(event: RefundProcessedEvent): Promise<void> {
|
||||
const { analyticsId } = await this.getUserAnalyticsId.execute({ userEmail: event.payload.userEmail })
|
||||
|
||||
await this.statisticsStore.incrementMeasure(StatisticMeasureName.NAMES.Refunds, event.payload.amount, [
|
||||
await this.statisticsStore.incrementMeasure(StatisticsMeasure.Refunds, event.payload.amount, [
|
||||
Period.Today,
|
||||
Period.ThisWeek,
|
||||
Period.ThisMonth,
|
||||
])
|
||||
|
||||
if (this.mixpanelClient !== null) {
|
||||
this.mixpanelClient.track(event.type, {
|
||||
distinct_id: analyticsId.toString(),
|
||||
amount: event.payload.amount,
|
||||
})
|
||||
this.mixpanelClient.people.track_charge(analyticsId.toString(), -event.payload.amount)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
import { DomainEventHandlerInterface, StatisticPersistenceRequestedEvent } from '@standardnotes/domain-events'
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
import { Logger } from 'winston'
|
||||
import { PersistStatistic } from '../UseCase/PersistStatistic/PersistStatistic'
|
||||
import { Mixpanel } from 'mixpanel'
|
||||
|
||||
export class StatisticPersistenceRequestedEventHandler implements DomainEventHandlerInterface {
|
||||
constructor(
|
||||
private persistStatistic: PersistStatistic,
|
||||
private timer: TimerInterface,
|
||||
private logger: Logger,
|
||||
private mixpanelClient: Mixpanel | null,
|
||||
) {}
|
||||
|
||||
async handle(event: StatisticPersistenceRequestedEvent): Promise<void> {
|
||||
const result = await this.persistStatistic.execute({
|
||||
date: this.timer.convertMicrosecondsToDate(event.payload.date),
|
||||
statisticMeasureName: event.payload.statisticMeasureName,
|
||||
value: event.payload.value,
|
||||
})
|
||||
|
||||
if (result.isFailed()) {
|
||||
this.logger.error(result.getError())
|
||||
}
|
||||
|
||||
if (this.mixpanelClient !== null) {
|
||||
this.mixpanelClient.track(event.type, {
|
||||
distinct_id: 'global-stats',
|
||||
statistic: event.payload.statisticMeasureName,
|
||||
value: event.payload.value,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
import 'reflect-metadata'
|
||||
|
||||
import { SubscriptionName } from '@standardnotes/common'
|
||||
import { Result } from '@standardnotes/domain-core'
|
||||
import { SubscriptionCancelledEvent } from '@standardnotes/domain-events'
|
||||
|
||||
import { SubscriptionCancelledEventHandler } from './SubscriptionCancelledEventHandler'
|
||||
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
|
||||
import { AnalyticsStoreInterface } from '../Analytics/AnalyticsStoreInterface'
|
||||
import { StatisticsMeasure } from '../Statistics/StatisticsMeasure'
|
||||
import { StatisticsStoreInterface } from '../Statistics/StatisticsStoreInterface'
|
||||
import { Period } from '../Time/Period'
|
||||
import { RevenueModification } from '../Revenue/RevenueModification'
|
||||
import { SaveRevenueModification } from '../UseCase/SaveRevenueModification/SaveRevenueModification'
|
||||
import { Logger } from 'winston'
|
||||
|
||||
describe('SubscriptionCancelledEventHandler', () => {
|
||||
let event: SubscriptionCancelledEvent
|
||||
let getUserAnalyticsId: GetUserAnalyticsId
|
||||
let analyticsStore: AnalyticsStoreInterface
|
||||
let statisticsStore: StatisticsStoreInterface
|
||||
let saveRevenueModification: SaveRevenueModification
|
||||
let logger: Logger
|
||||
|
||||
const createHandler = () =>
|
||||
new SubscriptionCancelledEventHandler(
|
||||
getUserAnalyticsId,
|
||||
analyticsStore,
|
||||
statisticsStore,
|
||||
saveRevenueModification,
|
||||
logger,
|
||||
)
|
||||
|
||||
beforeEach(() => {
|
||||
logger = {} as jest.Mocked<Logger>
|
||||
logger.error = jest.fn()
|
||||
|
||||
getUserAnalyticsId = {} as jest.Mocked<GetUserAnalyticsId>
|
||||
getUserAnalyticsId.execute = jest.fn().mockReturnValue({ analyticsId: 3 })
|
||||
|
||||
analyticsStore = {} as jest.Mocked<AnalyticsStoreInterface>
|
||||
analyticsStore.markActivity = jest.fn()
|
||||
|
||||
statisticsStore = {} as jest.Mocked<StatisticsStoreInterface>
|
||||
statisticsStore.incrementMeasure = jest.fn()
|
||||
|
||||
event = {} as jest.Mocked<SubscriptionCancelledEvent>
|
||||
event.createdAt = new Date(1)
|
||||
event.type = 'SUBSCRIPTION_CANCELLED'
|
||||
event.payload = {
|
||||
subscriptionId: 1,
|
||||
userEmail: 'test@test.com',
|
||||
subscriptionName: SubscriptionName.ProPlan,
|
||||
subscriptionCreatedAt: 1642395451515000,
|
||||
subscriptionUpdatedAt: 1642395451515001,
|
||||
lastPayedAt: 1642395451515001,
|
||||
subscriptionEndsAt: 1642395451515000 + 10,
|
||||
timestamp: 1,
|
||||
offline: false,
|
||||
replaced: false,
|
||||
userExistingSubscriptionsCount: 1,
|
||||
billingFrequency: 1,
|
||||
payAmount: 12.99,
|
||||
}
|
||||
|
||||
saveRevenueModification = {} as jest.Mocked<SaveRevenueModification>
|
||||
saveRevenueModification.execute = jest.fn().mockReturnValue(Result.ok<RevenueModification>())
|
||||
})
|
||||
|
||||
it('should track subscription cancelled statistics', async () => {
|
||||
event.payload.timestamp = 1642395451516000
|
||||
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(analyticsStore.markActivity).toHaveBeenCalled()
|
||||
expect(statisticsStore.incrementMeasure).toHaveBeenCalledWith(StatisticsMeasure.SubscriptionLength, 1000, [
|
||||
Period.Today,
|
||||
Period.ThisWeek,
|
||||
Period.ThisMonth,
|
||||
])
|
||||
expect(saveRevenueModification.execute).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should not track statistics for subscriptions that are in a legacy 5 year plan', async () => {
|
||||
event.payload.timestamp = 1642395451516000
|
||||
event.payload.subscriptionEndsAt = 1642395451515000 + 126_230_400_000_001
|
||||
event.payload.subscriptionCreatedAt = 1642395451515000
|
||||
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(statisticsStore.incrementMeasure).not.toHaveBeenCalled()
|
||||
expect(saveRevenueModification.execute).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should log failure to save revenue modification', async () => {
|
||||
saveRevenueModification.execute = jest.fn().mockReturnValue(Result.fail('Oops'))
|
||||
|
||||
event.payload.timestamp = 1642395451516000
|
||||
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(logger.error).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
@@ -1,20 +1,18 @@
|
||||
import { DomainEventHandlerInterface, SubscriptionCancelledEvent } from '@standardnotes/domain-events'
|
||||
import { inject, injectable, optional } from 'inversify'
|
||||
import { inject, injectable } from 'inversify'
|
||||
import { Logger } from 'winston'
|
||||
import { Username } from '@standardnotes/domain-core'
|
||||
import { Mixpanel } from 'mixpanel'
|
||||
|
||||
import TYPES from '../../Bootstrap/Types'
|
||||
import { AnalyticsActivity } from '../Analytics/AnalyticsActivity'
|
||||
import { AnalyticsStoreInterface } from '../Analytics/AnalyticsStoreInterface'
|
||||
import { StatisticsMeasure } from '../Statistics/StatisticsMeasure'
|
||||
import { StatisticsStoreInterface } from '../Statistics/StatisticsStoreInterface'
|
||||
import { SubscriptionEventType } from '../Subscription/SubscriptionEventType'
|
||||
import { SubscriptionPlanName } from '../Subscription/SubscriptionPlanName'
|
||||
import { Period } from '../Time/Period'
|
||||
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
|
||||
import { SaveRevenueModification } from '../UseCase/SaveRevenueModification/SaveRevenueModification'
|
||||
import { StatisticMeasureName } from '../Statistics/StatisticMeasureName'
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
|
||||
@injectable()
|
||||
export class SubscriptionCancelledEventHandler implements DomainEventHandlerInterface {
|
||||
@@ -24,8 +22,6 @@ export class SubscriptionCancelledEventHandler implements DomainEventHandlerInte
|
||||
@inject(TYPES.StatisticsStore) private statisticsStore: StatisticsStoreInterface,
|
||||
@inject(TYPES.SaveRevenueModification) private saveRevenueModification: SaveRevenueModification,
|
||||
@inject(TYPES.Logger) private logger: Logger,
|
||||
@inject(TYPES.MixpanelClient) @optional() private mixpanelClient: Mixpanel | null,
|
||||
@inject(TYPES.Timer) private timer: TimerInterface,
|
||||
) {}
|
||||
|
||||
async handle(event: SubscriptionCancelledEvent): Promise<void> {
|
||||
@@ -54,22 +50,6 @@ export class SubscriptionCancelledEventHandler implements DomainEventHandlerInte
|
||||
`[${event.type}][${event.payload.subscriptionId}] Could not save revenue modification: ${result.getError()}`,
|
||||
)
|
||||
}
|
||||
|
||||
if (this.mixpanelClient !== null) {
|
||||
this.mixpanelClient.track(event.type, {
|
||||
distinct_id: analyticsId.toString(),
|
||||
subscription_name: event.payload.subscriptionName,
|
||||
subscription_created_at: this.timer.convertMicrosecondsToDate(event.payload.subscriptionCreatedAt),
|
||||
subscription_updated_at: this.timer.convertMicrosecondsToDate(event.payload.subscriptionUpdatedAt),
|
||||
last_payed_at: this.timer.convertMicrosecondsToDate(event.payload.lastPayedAt),
|
||||
subscription_ends_at: this.timer.convertMicrosecondsToDate(event.payload.subscriptionEndsAt),
|
||||
offline: event.payload.offline,
|
||||
replaced: event.payload.replaced,
|
||||
user_existing_subscriptions_count: event.payload.userExistingSubscriptionsCount,
|
||||
billing_frequency: event.payload.billingFrequency,
|
||||
pay_amount: event.payload.payAmount,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private async trackSubscriptionStatistics(event: SubscriptionCancelledEvent) {
|
||||
@@ -78,7 +58,7 @@ export class SubscriptionCancelledEventHandler implements DomainEventHandlerInte
|
||||
}
|
||||
|
||||
const subscriptionLength = event.payload.timestamp - event.payload.subscriptionCreatedAt
|
||||
await this.statisticsStore.incrementMeasure(StatisticMeasureName.NAMES.SubscriptionLength, subscriptionLength, [
|
||||
await this.statisticsStore.incrementMeasure(StatisticsMeasure.SubscriptionLength, subscriptionLength, [
|
||||
Period.Today,
|
||||
Period.ThisWeek,
|
||||
Period.ThisMonth,
|
||||
@@ -90,7 +70,7 @@ export class SubscriptionCancelledEventHandler implements DomainEventHandlerInte
|
||||
const remainingSubscriptionPercentage = Math.floor((remainingSubscriptionTime / totalSubscriptionTime) * 100)
|
||||
|
||||
await this.statisticsStore.incrementMeasure(
|
||||
StatisticMeasureName.NAMES.RemainingSubscriptionTimePercentage,
|
||||
StatisticsMeasure.RemainingSubscriptionTimePercentage,
|
||||
remainingSubscriptionPercentage,
|
||||
[Period.Today, Period.ThisWeek, Period.ThisMonth],
|
||||
)
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
import 'reflect-metadata'
|
||||
|
||||
import { SubscriptionName } from '@standardnotes/common'
|
||||
import { SubscriptionExpiredEvent } from '@standardnotes/domain-events'
|
||||
import { Result } from '@standardnotes/domain-core'
|
||||
|
||||
import { SubscriptionExpiredEventHandler } from './SubscriptionExpiredEventHandler'
|
||||
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
|
||||
import { StatisticsStoreInterface } from '../Statistics/StatisticsStoreInterface'
|
||||
import { AnalyticsStoreInterface } from '../Analytics/AnalyticsStoreInterface'
|
||||
import { SaveRevenueModification } from '../UseCase/SaveRevenueModification/SaveRevenueModification'
|
||||
import { RevenueModification } from '../Revenue/RevenueModification'
|
||||
import { Logger } from 'winston'
|
||||
|
||||
describe('SubscriptionExpiredEventHandler', () => {
|
||||
let event: SubscriptionExpiredEvent
|
||||
let getUserAnalyticsId: GetUserAnalyticsId
|
||||
let analyticsStore: AnalyticsStoreInterface
|
||||
let statisticsStore: StatisticsStoreInterface
|
||||
let saveRevenueModification: SaveRevenueModification
|
||||
let logger: Logger
|
||||
|
||||
const createHandler = () =>
|
||||
new SubscriptionExpiredEventHandler(
|
||||
getUserAnalyticsId,
|
||||
analyticsStore,
|
||||
statisticsStore,
|
||||
saveRevenueModification,
|
||||
logger,
|
||||
)
|
||||
|
||||
beforeEach(() => {
|
||||
logger = {} as jest.Mocked<Logger>
|
||||
logger.error = jest.fn()
|
||||
|
||||
event = {} as jest.Mocked<SubscriptionExpiredEvent>
|
||||
event.createdAt = new Date(1)
|
||||
event.type = 'SUBSCRIPTION_EXPIRED'
|
||||
event.payload = {
|
||||
subscriptionId: 1,
|
||||
userEmail: 'test@test.com',
|
||||
subscriptionName: SubscriptionName.PlusPlan,
|
||||
timestamp: 1,
|
||||
offline: false,
|
||||
totalActiveSubscriptionsCount: 123,
|
||||
userExistingSubscriptionsCount: 2,
|
||||
billingFrequency: 1,
|
||||
payAmount: 12.99,
|
||||
}
|
||||
|
||||
getUserAnalyticsId = {} as jest.Mocked<GetUserAnalyticsId>
|
||||
getUserAnalyticsId.execute = jest.fn().mockReturnValue({ analyticsId: 3 })
|
||||
|
||||
analyticsStore = {} as jest.Mocked<AnalyticsStoreInterface>
|
||||
analyticsStore.markActivity = jest.fn()
|
||||
|
||||
statisticsStore = {} as jest.Mocked<StatisticsStoreInterface>
|
||||
statisticsStore.setMeasure = jest.fn()
|
||||
|
||||
saveRevenueModification = {} as jest.Mocked<SaveRevenueModification>
|
||||
saveRevenueModification.execute = jest.fn().mockReturnValue(Result.ok<RevenueModification>())
|
||||
})
|
||||
|
||||
it('should update analytics and statistics', async () => {
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(analyticsStore.markActivity).toHaveBeenCalled()
|
||||
expect(statisticsStore.setMeasure).toHaveBeenCalled()
|
||||
expect(saveRevenueModification.execute).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should log failure to save revenue modification', async () => {
|
||||
saveRevenueModification.execute = jest.fn().mockReturnValue(Result.fail('Oops'))
|
||||
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(logger.error).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
@@ -1,13 +1,12 @@
|
||||
import { Username } from '@standardnotes/domain-core'
|
||||
import { DomainEventHandlerInterface, SubscriptionExpiredEvent } from '@standardnotes/domain-events'
|
||||
import { inject, injectable, optional } from 'inversify'
|
||||
import { inject, injectable } from 'inversify'
|
||||
import { Logger } from 'winston'
|
||||
import { Mixpanel } from 'mixpanel'
|
||||
|
||||
import TYPES from '../../Bootstrap/Types'
|
||||
import { AnalyticsActivity } from '../Analytics/AnalyticsActivity'
|
||||
import { AnalyticsStoreInterface } from '../Analytics/AnalyticsStoreInterface'
|
||||
import { StatisticMeasureName } from '../Statistics/StatisticMeasureName'
|
||||
import { StatisticsMeasure } from '../Statistics/StatisticsMeasure'
|
||||
import { StatisticsStoreInterface } from '../Statistics/StatisticsStoreInterface'
|
||||
import { SubscriptionEventType } from '../Subscription/SubscriptionEventType'
|
||||
import { SubscriptionPlanName } from '../Subscription/SubscriptionPlanName'
|
||||
@@ -23,7 +22,6 @@ export class SubscriptionExpiredEventHandler implements DomainEventHandlerInterf
|
||||
@inject(TYPES.StatisticsStore) private statisticsStore: StatisticsStoreInterface,
|
||||
@inject(TYPES.SaveRevenueModification) private saveRevenueModification: SaveRevenueModification,
|
||||
@inject(TYPES.Logger) private logger: Logger,
|
||||
@inject(TYPES.MixpanelClient) @optional() private mixpanelClient: Mixpanel | null,
|
||||
) {}
|
||||
|
||||
async handle(event: SubscriptionExpiredEvent): Promise<void> {
|
||||
@@ -35,7 +33,7 @@ export class SubscriptionExpiredEventHandler implements DomainEventHandlerInterf
|
||||
)
|
||||
|
||||
await this.statisticsStore.setMeasure(
|
||||
StatisticMeasureName.NAMES.TotalCustomers,
|
||||
StatisticsMeasure.TotalCustomers,
|
||||
event.payload.totalActiveSubscriptionsCount,
|
||||
[Period.Today, Period.ThisWeek, Period.ThisMonth, Period.ThisYear],
|
||||
)
|
||||
@@ -56,18 +54,5 @@ export class SubscriptionExpiredEventHandler implements DomainEventHandlerInterf
|
||||
`[${event.type}][${event.payload.subscriptionId}] Could not save revenue modification: ${result.getError()}`,
|
||||
)
|
||||
}
|
||||
|
||||
if (this.mixpanelClient !== null) {
|
||||
this.mixpanelClient.track(event.type, {
|
||||
distinct_id: analyticsId.toString(),
|
||||
subscription_name: event.payload.subscriptionName,
|
||||
offline: event.payload.offline,
|
||||
user_existing_subscriptions_count: event.payload.userExistingSubscriptionsCount,
|
||||
billing_frequency: event.payload.billingFrequency,
|
||||
pay_amount: event.payload.payAmount,
|
||||
})
|
||||
|
||||
this.mixpanelClient.people.set(analyticsId.toString(), 'subscription', 'free')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
import 'reflect-metadata'
|
||||
|
||||
import { SubscriptionName } from '@standardnotes/common'
|
||||
import { SubscriptionPurchasedEvent } from '@standardnotes/domain-events'
|
||||
import { Result } from '@standardnotes/domain-core'
|
||||
|
||||
import { SubscriptionPurchasedEventHandler } from './SubscriptionPurchasedEventHandler'
|
||||
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
|
||||
import { AnalyticsStoreInterface } from '../Analytics/AnalyticsStoreInterface'
|
||||
import { StatisticsStoreInterface } from '../Statistics/StatisticsStoreInterface'
|
||||
import { Period } from '../Time/Period'
|
||||
import { SaveRevenueModification } from '../UseCase/SaveRevenueModification/SaveRevenueModification'
|
||||
import { RevenueModification } from '../Revenue/RevenueModification'
|
||||
import { Logger } from 'winston'
|
||||
|
||||
describe('SubscriptionPurchasedEventHandler', () => {
|
||||
let event: SubscriptionPurchasedEvent
|
||||
let subscriptionExpiresAt: number
|
||||
let getUserAnalyticsId: GetUserAnalyticsId
|
||||
let analyticsStore: AnalyticsStoreInterface
|
||||
let statisticsStore: StatisticsStoreInterface
|
||||
let saveRevenueModification: SaveRevenueModification
|
||||
let logger: Logger
|
||||
|
||||
const createHandler = () =>
|
||||
new SubscriptionPurchasedEventHandler(
|
||||
getUserAnalyticsId,
|
||||
analyticsStore,
|
||||
statisticsStore,
|
||||
saveRevenueModification,
|
||||
logger,
|
||||
)
|
||||
|
||||
beforeEach(() => {
|
||||
logger = {} as jest.Mocked<Logger>
|
||||
logger.error = jest.fn()
|
||||
|
||||
statisticsStore = {} as jest.Mocked<StatisticsStoreInterface>
|
||||
statisticsStore.incrementMeasure = jest.fn()
|
||||
statisticsStore.setMeasure = jest.fn()
|
||||
|
||||
event = {} as jest.Mocked<SubscriptionPurchasedEvent>
|
||||
event.createdAt = new Date(1)
|
||||
event.type = 'SUBSCRIPTION_PURCHASED'
|
||||
event.payload = {
|
||||
subscriptionId: 1,
|
||||
userEmail: 'test@test.com',
|
||||
subscriptionName: SubscriptionName.ProPlan,
|
||||
subscriptionExpiresAt,
|
||||
timestamp: 60,
|
||||
offline: false,
|
||||
discountCode: null,
|
||||
limitedDiscountPurchased: false,
|
||||
newSubscriber: true,
|
||||
totalActiveSubscriptionsCount: 123,
|
||||
userRegisteredAt: 23,
|
||||
billingFrequency: 12,
|
||||
payAmount: 29.99,
|
||||
}
|
||||
|
||||
getUserAnalyticsId = {} as jest.Mocked<GetUserAnalyticsId>
|
||||
getUserAnalyticsId.execute = jest.fn().mockReturnValue({ analyticsId: 3 })
|
||||
|
||||
analyticsStore = {} as jest.Mocked<AnalyticsStoreInterface>
|
||||
analyticsStore.markActivity = jest.fn()
|
||||
analyticsStore.unmarkActivity = jest.fn()
|
||||
|
||||
saveRevenueModification = {} as jest.Mocked<SaveRevenueModification>
|
||||
saveRevenueModification.execute = jest.fn().mockReturnValue(Result.ok<RevenueModification>())
|
||||
})
|
||||
|
||||
it('should mark subscription creation statistics', async () => {
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(statisticsStore.incrementMeasure).toHaveBeenCalled()
|
||||
expect(saveRevenueModification.execute).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("should not measure registration to subscription time if this is not user's first subscription", async () => {
|
||||
event.payload.newSubscriber = false
|
||||
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(statisticsStore.incrementMeasure).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should update analytics on limited discount offer purchasing', async () => {
|
||||
event.payload.limitedDiscountPurchased = true
|
||||
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(analyticsStore.markActivity).toHaveBeenCalledWith(['limited-discount-offer-purchased'], 3, [Period.Today])
|
||||
})
|
||||
|
||||
it('should log failure to save revenue modification', async () => {
|
||||
saveRevenueModification.execute = jest.fn().mockReturnValue(Result.fail('Oops'))
|
||||
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(logger.error).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
@@ -1,14 +1,12 @@
|
||||
import { Username } from '@standardnotes/domain-core'
|
||||
import { DomainEventHandlerInterface, SubscriptionPurchasedEvent } from '@standardnotes/domain-events'
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
import { inject, injectable, optional } from 'inversify'
|
||||
import { inject, injectable } from 'inversify'
|
||||
import { Logger } from 'winston'
|
||||
import { Mixpanel } from 'mixpanel'
|
||||
|
||||
import TYPES from '../../Bootstrap/Types'
|
||||
import { AnalyticsActivity } from '../Analytics/AnalyticsActivity'
|
||||
import { AnalyticsStoreInterface } from '../Analytics/AnalyticsStoreInterface'
|
||||
import { StatisticMeasureName } from '../Statistics/StatisticMeasureName'
|
||||
import { StatisticsMeasure } from '../Statistics/StatisticsMeasure'
|
||||
import { StatisticsStoreInterface } from '../Statistics/StatisticsStoreInterface'
|
||||
import { SubscriptionEventType } from '../Subscription/SubscriptionEventType'
|
||||
import { SubscriptionPlanName } from '../Subscription/SubscriptionPlanName'
|
||||
@@ -24,8 +22,6 @@ export class SubscriptionPurchasedEventHandler implements DomainEventHandlerInte
|
||||
@inject(TYPES.StatisticsStore) private statisticsStore: StatisticsStoreInterface,
|
||||
@inject(TYPES.SaveRevenueModification) private saveRevenueModification: SaveRevenueModification,
|
||||
@inject(TYPES.Logger) private logger: Logger,
|
||||
@inject(TYPES.MixpanelClient) @optional() private mixpanelClient: Mixpanel | null,
|
||||
@inject(TYPES.Timer) private timer: TimerInterface,
|
||||
) {}
|
||||
|
||||
async handle(event: SubscriptionPurchasedEvent): Promise<void> {
|
||||
@@ -49,18 +45,18 @@ export class SubscriptionPurchasedEventHandler implements DomainEventHandlerInte
|
||||
|
||||
if (event.payload.newSubscriber) {
|
||||
await this.statisticsStore.incrementMeasure(
|
||||
StatisticMeasureName.NAMES.RegistrationToSubscriptionTime,
|
||||
StatisticsMeasure.RegistrationToSubscriptionTime,
|
||||
event.payload.timestamp - event.payload.userRegisteredAt,
|
||||
[Period.Today, Period.ThisWeek, Period.ThisMonth],
|
||||
)
|
||||
await this.statisticsStore.incrementMeasure(StatisticMeasureName.NAMES.NewCustomers, 1, [
|
||||
await this.statisticsStore.incrementMeasure(StatisticsMeasure.NewCustomers, 1, [
|
||||
Period.Today,
|
||||
Period.ThisWeek,
|
||||
Period.ThisMonth,
|
||||
Period.ThisYear,
|
||||
])
|
||||
await this.statisticsStore.setMeasure(
|
||||
StatisticMeasureName.NAMES.TotalCustomers,
|
||||
StatisticsMeasure.TotalCustomers,
|
||||
event.payload.totalActiveSubscriptionsCount,
|
||||
[Period.Today, Period.ThisWeek, Period.ThisMonth, Period.ThisYear],
|
||||
)
|
||||
@@ -82,22 +78,5 @@ export class SubscriptionPurchasedEventHandler implements DomainEventHandlerInte
|
||||
`[${event.type}][${event.payload.subscriptionId}] Could not save revenue modification: ${result.getError()}`,
|
||||
)
|
||||
}
|
||||
|
||||
if (this.mixpanelClient !== null) {
|
||||
this.mixpanelClient.track(event.type, {
|
||||
distinct_id: analyticsId.toString(),
|
||||
subscription_name: event.payload.subscriptionName,
|
||||
subscription_expires_at: this.timer.convertMicrosecondsToDate(event.payload.subscriptionExpiresAt),
|
||||
offline: event.payload.offline,
|
||||
discount_code: event.payload.discountCode,
|
||||
limited_discount_purchased: event.payload.limitedDiscountPurchased,
|
||||
new_subscriber: event.payload.newSubscriber,
|
||||
user_registered_at: this.timer.convertMicrosecondsToDate(event.payload.userRegisteredAt),
|
||||
billing_frequency: event.payload.billingFrequency,
|
||||
pay_amount: event.payload.payAmount,
|
||||
})
|
||||
|
||||
this.mixpanelClient.people.set(analyticsId.toString(), 'subscription', event.payload.subscriptionName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
import 'reflect-metadata'
|
||||
|
||||
import { SubscriptionName } from '@standardnotes/common'
|
||||
import { SubscriptionReactivatedEvent } from '@standardnotes/domain-events'
|
||||
|
||||
import { SubscriptionReactivatedEventHandler } from './SubscriptionReactivatedEventHandler'
|
||||
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
|
||||
import { AnalyticsStoreInterface } from '../Analytics/AnalyticsStoreInterface'
|
||||
import { Period } from '../Time/Period'
|
||||
|
||||
describe('SubscriptionReactivatedEventHandler', () => {
|
||||
let event: SubscriptionReactivatedEvent
|
||||
let getUserAnalyticsId: GetUserAnalyticsId
|
||||
let analyticsStore: AnalyticsStoreInterface
|
||||
|
||||
const createHandler = () => new SubscriptionReactivatedEventHandler(analyticsStore, getUserAnalyticsId)
|
||||
|
||||
beforeEach(() => {
|
||||
event = {} as jest.Mocked<SubscriptionReactivatedEvent>
|
||||
event.createdAt = new Date(1)
|
||||
event.payload = {
|
||||
previousSubscriptionId: 1,
|
||||
currentSubscriptionId: 2,
|
||||
userEmail: 'test@test.com',
|
||||
subscriptionName: SubscriptionName.PlusPlan,
|
||||
subscriptionExpiresAt: 5,
|
||||
discountCode: 'exit-20',
|
||||
}
|
||||
|
||||
getUserAnalyticsId = {} as jest.Mocked<GetUserAnalyticsId>
|
||||
getUserAnalyticsId.execute = jest.fn().mockReturnValue({ analyticsId: 3 })
|
||||
|
||||
analyticsStore = {} as jest.Mocked<AnalyticsStoreInterface>
|
||||
analyticsStore.markActivity = jest.fn()
|
||||
})
|
||||
|
||||
it('should mark subscription reactivated activity for analytics', async () => {
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(analyticsStore.markActivity).toHaveBeenCalledWith(['subscription-reactivated'], 3, [
|
||||
Period.Today,
|
||||
Period.ThisWeek,
|
||||
Period.ThisMonth,
|
||||
])
|
||||
})
|
||||
})
|
||||
@@ -1,7 +1,5 @@
|
||||
import { DomainEventHandlerInterface, SubscriptionReactivatedEvent } from '@standardnotes/domain-events'
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
import { inject, injectable, optional } from 'inversify'
|
||||
import { Mixpanel } from 'mixpanel'
|
||||
import { inject, injectable } from 'inversify'
|
||||
|
||||
import TYPES from '../../Bootstrap/Types'
|
||||
import { AnalyticsActivity } from '../Analytics/AnalyticsActivity'
|
||||
@@ -14,8 +12,6 @@ export class SubscriptionReactivatedEventHandler implements DomainEventHandlerIn
|
||||
constructor(
|
||||
@inject(TYPES.AnalyticsStore) private analyticsStore: AnalyticsStoreInterface,
|
||||
@inject(TYPES.GetUserAnalyticsId) private getUserAnalyticsId: GetUserAnalyticsId,
|
||||
@inject(TYPES.MixpanelClient) @optional() private mixpanelClient: Mixpanel | null,
|
||||
@inject(TYPES.Timer) private timer: TimerInterface,
|
||||
) {}
|
||||
|
||||
async handle(event: SubscriptionReactivatedEvent): Promise<void> {
|
||||
@@ -25,16 +21,5 @@ export class SubscriptionReactivatedEventHandler implements DomainEventHandlerIn
|
||||
Period.ThisWeek,
|
||||
Period.ThisMonth,
|
||||
])
|
||||
|
||||
if (this.mixpanelClient !== null) {
|
||||
this.mixpanelClient.track(event.type, {
|
||||
distinct_id: analyticsId.toString(),
|
||||
subscription_name: event.payload.subscriptionName,
|
||||
subscription_expires_at: this.timer.convertMicrosecondsToDate(event.payload.subscriptionExpiresAt),
|
||||
discount_code: event.payload.discountCode,
|
||||
})
|
||||
|
||||
this.mixpanelClient.people.set(analyticsId.toString(), 'subscription', event.payload.subscriptionName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
import 'reflect-metadata'
|
||||
|
||||
import { SubscriptionName } from '@standardnotes/common'
|
||||
import { SubscriptionRefundedEvent } from '@standardnotes/domain-events'
|
||||
import { Result } from '@standardnotes/domain-core'
|
||||
|
||||
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
|
||||
import { AnalyticsStoreInterface } from '../Analytics/AnalyticsStoreInterface'
|
||||
|
||||
import { SubscriptionRefundedEventHandler } from './SubscriptionRefundedEventHandler'
|
||||
import { StatisticsStoreInterface } from '../Statistics/StatisticsStoreInterface'
|
||||
import { AnalyticsActivity } from '../Analytics/AnalyticsActivity'
|
||||
import { Period } from '../Time/Period'
|
||||
import { RevenueModification } from '../Revenue/RevenueModification'
|
||||
import { SaveRevenueModification } from '../UseCase/SaveRevenueModification/SaveRevenueModification'
|
||||
import { Logger } from 'winston'
|
||||
|
||||
describe('SubscriptionRefundedEventHandler', () => {
|
||||
let event: SubscriptionRefundedEvent
|
||||
let getUserAnalyticsId: GetUserAnalyticsId
|
||||
let analyticsStore: AnalyticsStoreInterface
|
||||
let statisticsStore: StatisticsStoreInterface
|
||||
let saveRevenueModification: SaveRevenueModification
|
||||
let logger: Logger
|
||||
|
||||
const createHandler = () =>
|
||||
new SubscriptionRefundedEventHandler(
|
||||
getUserAnalyticsId,
|
||||
analyticsStore,
|
||||
statisticsStore,
|
||||
saveRevenueModification,
|
||||
logger,
|
||||
)
|
||||
|
||||
beforeEach(() => {
|
||||
logger = {} as jest.Mocked<Logger>
|
||||
logger.error = jest.fn()
|
||||
|
||||
event = {} as jest.Mocked<SubscriptionRefundedEvent>
|
||||
event.createdAt = new Date(1)
|
||||
event.type = 'SUBSCRIPTION_REFUNDED'
|
||||
event.payload = {
|
||||
subscriptionId: 1,
|
||||
userEmail: 'test@test.com',
|
||||
subscriptionName: SubscriptionName.PlusPlan,
|
||||
timestamp: 1,
|
||||
offline: false,
|
||||
userExistingSubscriptionsCount: 3,
|
||||
totalActiveSubscriptionsCount: 1,
|
||||
billingFrequency: 1,
|
||||
payAmount: 12.99,
|
||||
}
|
||||
|
||||
getUserAnalyticsId = {} as jest.Mocked<GetUserAnalyticsId>
|
||||
getUserAnalyticsId.execute = jest.fn().mockReturnValue({ analyticsId: 3 })
|
||||
|
||||
analyticsStore = {} as jest.Mocked<AnalyticsStoreInterface>
|
||||
analyticsStore.markActivity = jest.fn()
|
||||
analyticsStore.wasActivityDone = jest.fn().mockReturnValue(true)
|
||||
|
||||
statisticsStore = {} as jest.Mocked<StatisticsStoreInterface>
|
||||
statisticsStore.setMeasure = jest.fn()
|
||||
|
||||
saveRevenueModification = {} as jest.Mocked<SaveRevenueModification>
|
||||
saveRevenueModification.execute = jest.fn().mockReturnValue(Result.ok<RevenueModification>())
|
||||
})
|
||||
|
||||
it('should mark churn for new customer', async () => {
|
||||
event.payload.userExistingSubscriptionsCount = 1
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(analyticsStore.markActivity).toHaveBeenCalledWith([AnalyticsActivity.SubscriptionRefunded], 3, [
|
||||
Period.Today,
|
||||
Period.ThisWeek,
|
||||
Period.ThisMonth,
|
||||
])
|
||||
|
||||
expect(analyticsStore.markActivity).toHaveBeenCalledWith([AnalyticsActivity.NewCustomersChurn], 3, [
|
||||
Period.ThisMonth,
|
||||
])
|
||||
|
||||
expect(saveRevenueModification.execute).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should mark churn for existing customer', async () => {
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(analyticsStore.markActivity).toHaveBeenCalledWith([AnalyticsActivity.ExistingCustomersChurn], 3, [
|
||||
Period.ThisMonth,
|
||||
])
|
||||
})
|
||||
|
||||
it('should not mark churn if customer did not purchase subscription in defined analytic periods', async () => {
|
||||
analyticsStore.wasActivityDone = jest.fn().mockReturnValue(false)
|
||||
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(analyticsStore.markActivity).not.toHaveBeenCalledWith([AnalyticsActivity.ExistingCustomersChurn], 3, [
|
||||
Period.ThisMonth,
|
||||
])
|
||||
})
|
||||
|
||||
it('should log failure to save revenue modification', async () => {
|
||||
saveRevenueModification.execute = jest.fn().mockReturnValue(Result.fail('Oops'))
|
||||
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(logger.error).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
@@ -1,13 +1,12 @@
|
||||
import { Username } from '@standardnotes/domain-core'
|
||||
import { DomainEventHandlerInterface, SubscriptionRefundedEvent } from '@standardnotes/domain-events'
|
||||
import { inject, injectable, optional } from 'inversify'
|
||||
import { inject, injectable } from 'inversify'
|
||||
import { Logger } from 'winston'
|
||||
import { Mixpanel } from 'mixpanel'
|
||||
|
||||
import TYPES from '../../Bootstrap/Types'
|
||||
import { AnalyticsActivity } from '../Analytics/AnalyticsActivity'
|
||||
import { AnalyticsStoreInterface } from '../Analytics/AnalyticsStoreInterface'
|
||||
import { StatisticMeasureName } from '../Statistics/StatisticMeasureName'
|
||||
import { StatisticsMeasure } from '../Statistics/StatisticsMeasure'
|
||||
import { StatisticsStoreInterface } from '../Statistics/StatisticsStoreInterface'
|
||||
import { SubscriptionEventType } from '../Subscription/SubscriptionEventType'
|
||||
import { SubscriptionPlanName } from '../Subscription/SubscriptionPlanName'
|
||||
@@ -23,7 +22,6 @@ export class SubscriptionRefundedEventHandler implements DomainEventHandlerInter
|
||||
@inject(TYPES.StatisticsStore) private statisticsStore: StatisticsStoreInterface,
|
||||
@inject(TYPES.SaveRevenueModification) private saveRevenueModification: SaveRevenueModification,
|
||||
@inject(TYPES.Logger) private logger: Logger,
|
||||
@inject(TYPES.MixpanelClient) @optional() private mixpanelClient: Mixpanel | null,
|
||||
) {}
|
||||
|
||||
async handle(event: SubscriptionRefundedEvent): Promise<void> {
|
||||
@@ -52,18 +50,6 @@ export class SubscriptionRefundedEventHandler implements DomainEventHandlerInter
|
||||
`[${event.type}][${event.payload.subscriptionId}] Could not save revenue modification: ${result.getError()}`,
|
||||
)
|
||||
}
|
||||
|
||||
if (this.mixpanelClient !== null) {
|
||||
this.mixpanelClient.track(event.type, {
|
||||
distinct_id: analyticsId.toString(),
|
||||
subscription_name: event.payload.subscriptionName,
|
||||
user_existing_subscriptions_count: event.payload.userExistingSubscriptionsCount,
|
||||
offline: event.payload.offline,
|
||||
billing_frequency: event.payload.billingFrequency,
|
||||
pay_amount: event.payload.payAmount,
|
||||
})
|
||||
this.mixpanelClient.people.set(analyticsId.toString(), 'subscription', 'free')
|
||||
}
|
||||
}
|
||||
|
||||
private async markChurnActivity(analyticsId: number, event: SubscriptionRefundedEvent): Promise<void> {
|
||||
@@ -84,7 +70,7 @@ export class SubscriptionRefundedEventHandler implements DomainEventHandlerInter
|
||||
}
|
||||
|
||||
await this.statisticsStore.setMeasure(
|
||||
StatisticMeasureName.NAMES.TotalCustomers,
|
||||
StatisticsMeasure.TotalCustomers,
|
||||
event.payload.totalActiveSubscriptionsCount,
|
||||
[Period.Today, Period.ThisWeek, Period.ThisMonth, Period.ThisYear],
|
||||
)
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
import 'reflect-metadata'
|
||||
|
||||
import { SubscriptionName } from '@standardnotes/common'
|
||||
import { SubscriptionRenewedEvent } from '@standardnotes/domain-events'
|
||||
import { Result } from '@standardnotes/domain-core'
|
||||
|
||||
import { SubscriptionRenewedEventHandler } from './SubscriptionRenewedEventHandler'
|
||||
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
|
||||
import { AnalyticsStoreInterface } from '../Analytics/AnalyticsStoreInterface'
|
||||
import { SaveRevenueModification } from '../UseCase/SaveRevenueModification/SaveRevenueModification'
|
||||
import { RevenueModification } from '../Revenue/RevenueModification'
|
||||
import { Logger } from 'winston'
|
||||
|
||||
describe('SubscriptionRenewedEventHandler', () => {
|
||||
let event: SubscriptionRenewedEvent
|
||||
let getUserAnalyticsId: GetUserAnalyticsId
|
||||
let analyticsStore: AnalyticsStoreInterface
|
||||
let saveRevenueModification: SaveRevenueModification
|
||||
let logger: Logger
|
||||
|
||||
const createHandler = () =>
|
||||
new SubscriptionRenewedEventHandler(getUserAnalyticsId, analyticsStore, saveRevenueModification, logger)
|
||||
|
||||
beforeEach(() => {
|
||||
logger = {} as jest.Mocked<Logger>
|
||||
logger.error = jest.fn()
|
||||
|
||||
event = {} as jest.Mocked<SubscriptionRenewedEvent>
|
||||
event.createdAt = new Date(1)
|
||||
event.type = 'SUBSCRIPTION_RENEWED'
|
||||
event.payload = {
|
||||
subscriptionId: 1,
|
||||
userEmail: 'test@test.com',
|
||||
subscriptionName: SubscriptionName.ProPlan,
|
||||
subscriptionExpiresAt: 2,
|
||||
timestamp: 1,
|
||||
offline: false,
|
||||
billingFrequency: 1,
|
||||
payAmount: 12.99,
|
||||
}
|
||||
|
||||
getUserAnalyticsId = {} as jest.Mocked<GetUserAnalyticsId>
|
||||
getUserAnalyticsId.execute = jest.fn().mockReturnValue({ analyticsId: 3 })
|
||||
|
||||
analyticsStore = {} as jest.Mocked<AnalyticsStoreInterface>
|
||||
analyticsStore.markActivity = jest.fn()
|
||||
analyticsStore.unmarkActivity = jest.fn()
|
||||
|
||||
saveRevenueModification = {} as jest.Mocked<SaveRevenueModification>
|
||||
saveRevenueModification.execute = jest.fn().mockReturnValue(Result.ok<RevenueModification>())
|
||||
})
|
||||
|
||||
it('should track subscription renewed statistics', async () => {
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(analyticsStore.markActivity).toHaveBeenCalled()
|
||||
expect(analyticsStore.unmarkActivity).toHaveBeenCalled()
|
||||
expect(saveRevenueModification.execute).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should log failure to save revenue modification', async () => {
|
||||
saveRevenueModification.execute = jest.fn().mockReturnValue(Result.fail('Oops'))
|
||||
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(logger.error).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
@@ -1,7 +1,6 @@
|
||||
import { DomainEventHandlerInterface, SubscriptionRenewedEvent } from '@standardnotes/domain-events'
|
||||
import { inject, injectable, optional } from 'inversify'
|
||||
import { inject, injectable } from 'inversify'
|
||||
import { Username } from '@standardnotes/domain-core'
|
||||
import { Mixpanel } from 'mixpanel'
|
||||
|
||||
import TYPES from '../../Bootstrap/Types'
|
||||
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
|
||||
@@ -12,7 +11,6 @@ import { SaveRevenueModification } from '../UseCase/SaveRevenueModification/Save
|
||||
import { SubscriptionEventType } from '../Subscription/SubscriptionEventType'
|
||||
import { SubscriptionPlanName } from '../Subscription/SubscriptionPlanName'
|
||||
import { Logger } from 'winston'
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
|
||||
@injectable()
|
||||
export class SubscriptionRenewedEventHandler implements DomainEventHandlerInterface {
|
||||
@@ -21,8 +19,6 @@ export class SubscriptionRenewedEventHandler implements DomainEventHandlerInterf
|
||||
@inject(TYPES.AnalyticsStore) private analyticsStore: AnalyticsStoreInterface,
|
||||
@inject(TYPES.SaveRevenueModification) private saveRevenueModification: SaveRevenueModification,
|
||||
@inject(TYPES.Logger) private logger: Logger,
|
||||
@inject(TYPES.MixpanelClient) @optional() private mixpanelClient: Mixpanel | null,
|
||||
@inject(TYPES.Timer) private timer: TimerInterface,
|
||||
) {}
|
||||
|
||||
async handle(event: SubscriptionRenewedEvent): Promise<void> {
|
||||
@@ -54,17 +50,5 @@ export class SubscriptionRenewedEventHandler implements DomainEventHandlerInterf
|
||||
`[${event.type}][${event.payload.subscriptionId}] Could not save revenue modification: ${result.getError()}`,
|
||||
)
|
||||
}
|
||||
|
||||
if (this.mixpanelClient !== null) {
|
||||
this.mixpanelClient.track(event.type, {
|
||||
distinct_id: analyticsId.toString(),
|
||||
subscription_name: event.payload.subscriptionName,
|
||||
subscription_expires_at: this.timer.convertMicrosecondsToDate(event.payload.subscriptionExpiresAt),
|
||||
offline: event.payload.offline,
|
||||
billing_frequency: event.payload.billingFrequency,
|
||||
pay_amount: event.payload.payAmount,
|
||||
})
|
||||
this.mixpanelClient.people.set(analyticsId.toString(), 'subscription', event.payload.subscriptionName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import 'reflect-metadata'
|
||||
|
||||
import { UserRegisteredEvent } from '@standardnotes/domain-events'
|
||||
import { ProtocolVersion } from '@standardnotes/common'
|
||||
|
||||
import { UserRegisteredEventHandler } from './UserRegisteredEventHandler'
|
||||
import { AnalyticsEntityRepositoryInterface } from '../Entity/AnalyticsEntityRepositoryInterface'
|
||||
import { AnalyticsStoreInterface } from '../Analytics/AnalyticsStoreInterface'
|
||||
import { Period } from '../Time/Period'
|
||||
|
||||
describe('UserRegisteredEventHandler', () => {
|
||||
let analyticsEntityRepository: AnalyticsEntityRepositoryInterface
|
||||
let event: UserRegisteredEvent
|
||||
let analyticsStore: AnalyticsStoreInterface
|
||||
|
||||
const createHandler = () => new UserRegisteredEventHandler(analyticsEntityRepository, analyticsStore)
|
||||
|
||||
beforeEach(() => {
|
||||
event = {} as jest.Mocked<UserRegisteredEvent>
|
||||
event.createdAt = new Date(1)
|
||||
event.payload = {
|
||||
userUuid: '1-2-3',
|
||||
email: 'test@test.te',
|
||||
protocolVersion: ProtocolVersion.V004,
|
||||
}
|
||||
|
||||
analyticsStore = {} as jest.Mocked<AnalyticsStoreInterface>
|
||||
analyticsStore.markActivity = jest.fn()
|
||||
|
||||
analyticsEntityRepository = {} as jest.Mocked<AnalyticsEntityRepositoryInterface>
|
||||
analyticsEntityRepository.save = jest.fn().mockImplementation((entity) => ({
|
||||
...entity,
|
||||
id: 1,
|
||||
}))
|
||||
})
|
||||
|
||||
it('should save analytics entity upon user registration', async () => {
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(analyticsEntityRepository.save).toHaveBeenCalled()
|
||||
expect(analyticsStore.markActivity).toHaveBeenCalledWith(['register'], 1, [
|
||||
Period.Today,
|
||||
Period.ThisWeek,
|
||||
Period.ThisMonth,
|
||||
])
|
||||
})
|
||||
})
|
||||
@@ -1,6 +1,5 @@
|
||||
import { DomainEventHandlerInterface, UserRegisteredEvent } from '@standardnotes/domain-events'
|
||||
import { inject, injectable, optional } from 'inversify'
|
||||
import { Mixpanel } from 'mixpanel'
|
||||
import { inject, injectable } from 'inversify'
|
||||
|
||||
import TYPES from '../../Bootstrap/Types'
|
||||
import { AnalyticsActivity } from '../Analytics/AnalyticsActivity'
|
||||
@@ -14,7 +13,6 @@ export class UserRegisteredEventHandler implements DomainEventHandlerInterface {
|
||||
constructor(
|
||||
@inject(TYPES.AnalyticsEntityRepository) private analyticsEntityRepository: AnalyticsEntityRepositoryInterface,
|
||||
@inject(TYPES.AnalyticsStore) private analyticsStore: AnalyticsStoreInterface,
|
||||
@inject(TYPES.MixpanelClient) @optional() private mixpanelClient: Mixpanel | null,
|
||||
) {}
|
||||
|
||||
async handle(event: UserRegisteredEvent): Promise<void> {
|
||||
@@ -28,17 +26,5 @@ export class UserRegisteredEventHandler implements DomainEventHandlerInterface {
|
||||
Period.ThisWeek,
|
||||
Period.ThisMonth,
|
||||
])
|
||||
|
||||
if (this.mixpanelClient !== null) {
|
||||
this.mixpanelClient.track(event.type, {
|
||||
distinct_id: analyticsEntity.id.toString(),
|
||||
protocol_version: event.payload.protocolVersion,
|
||||
})
|
||||
|
||||
this.mixpanelClient.people.set(analyticsEntity.id.toString(), {
|
||||
subscription: 'free',
|
||||
protocol_version: event.payload.protocolVersion,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
import { Result, Entity, UniqueEntityId } from '@standardnotes/domain-core'
|
||||
|
||||
import { StatisticMeasureProps } from './StatisticMeasureProps'
|
||||
|
||||
export class StatisticMeasure extends Entity<StatisticMeasureProps> {
|
||||
get id(): UniqueEntityId {
|
||||
return this._id
|
||||
}
|
||||
|
||||
get name(): string {
|
||||
return this.props.name.value
|
||||
}
|
||||
|
||||
get value(): number {
|
||||
return this.props.value
|
||||
}
|
||||
|
||||
private constructor(props: StatisticMeasureProps, id?: UniqueEntityId) {
|
||||
super(props, id)
|
||||
}
|
||||
|
||||
static create(props: StatisticMeasureProps, id?: UniqueEntityId): Result<StatisticMeasure> {
|
||||
return Result.ok<StatisticMeasure>(new StatisticMeasure(props, id))
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
import { StatisticMeasureName } from './StatisticMeasureName'
|
||||
|
||||
describe('StatisticMeasureName', () => {
|
||||
it('should create a value object', () => {
|
||||
const valueOrError = StatisticMeasureName.create('pro-subscription-initial-monthly-payments-income')
|
||||
|
||||
expect(valueOrError.isFailed()).toBeFalsy()
|
||||
expect(valueOrError.getValue().value).toEqual('pro-subscription-initial-monthly-payments-income')
|
||||
})
|
||||
|
||||
it('should not create an invalid value object', () => {
|
||||
for (const value of ['', undefined, null, 0, 'foobar']) {
|
||||
const valueOrError = StatisticMeasureName.create(value as string)
|
||||
|
||||
expect(valueOrError.isFailed()).toBeTruthy()
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -1,51 +0,0 @@
|
||||
import { ValueObject, Result } from '@standardnotes/domain-core'
|
||||
|
||||
import { StatisticMeasureNameProps } from './StatisticMeasureNameProps'
|
||||
|
||||
export class StatisticMeasureName extends ValueObject<StatisticMeasureNameProps> {
|
||||
static readonly NAMES = {
|
||||
Income: 'income',
|
||||
PlusSubscriptionInitialMonthlyPaymentsIncome: 'plus-subscription-initial-monthly-payments-income',
|
||||
ProSubscriptionInitialMonthlyPaymentsIncome: 'pro-subscription-initial-monthly-payments-income',
|
||||
PlusSubscriptionInitialAnnualPaymentsIncome: 'plus-subscription-initial-annual-payments-income',
|
||||
ProSubscriptionInitialAnnualPaymentsIncome: 'pro-subscription-initial-annual-payments-income',
|
||||
PlusSubscriptionRenewingMonthlyPaymentsIncome: 'plus-subscription-renewing-monthly-payments-income',
|
||||
ProSubscriptionRenewingMonthlyPaymentsIncome: 'pro-subscription-renewing-monthly-payments-income',
|
||||
PlusSubscriptionRenewingAnnualPaymentsIncome: 'plus-subscription-renewing-annual-payments-income',
|
||||
ProSubscriptionRenewingAnnualPaymentsIncome: 'pro-subscription-renewing-annual-payments-income',
|
||||
SubscriptionLength: 'subscription-length',
|
||||
RegistrationLength: 'registration-length',
|
||||
RegistrationToSubscriptionTime: 'registration-to-subscription-time',
|
||||
RemainingSubscriptionTimePercentage: 'remaining-subscription-time-percentage',
|
||||
Refunds: 'refunds',
|
||||
NewCustomers: 'new-customers',
|
||||
TotalCustomers: 'total-customers',
|
||||
MRR: 'mrr',
|
||||
MonthlyPlansMRR: 'monthly-plans-mrr',
|
||||
AnnualPlansMRR: 'annual-plans-mrr',
|
||||
FiveYearPlansMRR: 'five-year-plans-mrr',
|
||||
ProPlansMRR: 'pro-plans-mrr',
|
||||
PlusPlansMRR: 'plus-plans-mrr',
|
||||
ActiveUsers: 'active-users',
|
||||
ActiveProUsers: 'active-pro-users',
|
||||
ActivePlusUsers: 'active-plus-users',
|
||||
ActiveFreeUsers: 'active-free-users',
|
||||
}
|
||||
|
||||
get value(): string {
|
||||
return this.props.value
|
||||
}
|
||||
|
||||
private constructor(props: StatisticMeasureNameProps) {
|
||||
super(props)
|
||||
}
|
||||
|
||||
static create(name: string): Result<StatisticMeasureName> {
|
||||
const isValidName = Object.values(this.NAMES).includes(name)
|
||||
if (!isValidName) {
|
||||
return Result.fail<StatisticMeasureName>(`Invalid statistics measure name: ${name}`)
|
||||
} else {
|
||||
return Result.ok<StatisticMeasureName>(new StatisticMeasureName({ value: name }))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
export interface StatisticMeasureNameProps {
|
||||
value: string
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
import { StatisticMeasureName } from './StatisticMeasureName'
|
||||
|
||||
export interface StatisticMeasureProps {
|
||||
name: StatisticMeasureName
|
||||
value: number
|
||||
date: Date
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
import { StatisticMeasure } from './StatisticMeasure'
|
||||
|
||||
export interface StatisticMeasureRepositoryInterface {
|
||||
save(statisticMeasure: StatisticMeasure): Promise<void>
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
export enum StatisticsMeasure {
|
||||
Income = 'income',
|
||||
PlusSubscriptionInitialMonthlyPaymentsIncome = 'plus-subscription-initial-monthly-payments-income',
|
||||
ProSubscriptionInitialMonthlyPaymentsIncome = 'pro-subscription-initial-monthly-payments-income',
|
||||
PlusSubscriptionInitialAnnualPaymentsIncome = 'plus-subscription-initial-annual-payments-income',
|
||||
ProSubscriptionInitialAnnualPaymentsIncome = 'pro-subscription-initial-annual-payments-income',
|
||||
PlusSubscriptionRenewingMonthlyPaymentsIncome = 'plus-subscription-renewing-monthly-payments-income',
|
||||
ProSubscriptionRenewingMonthlyPaymentsIncome = 'pro-subscription-renewing-monthly-payments-income',
|
||||
PlusSubscriptionRenewingAnnualPaymentsIncome = 'plus-subscription-renewing-annual-payments-income',
|
||||
ProSubscriptionRenewingAnnualPaymentsIncome = 'pro-subscription-renewing-annual-payments-income',
|
||||
SubscriptionLength = 'subscription-length',
|
||||
RegistrationLength = 'registration-length',
|
||||
RegistrationToSubscriptionTime = 'registration-to-subscription-time',
|
||||
RemainingSubscriptionTimePercentage = 'remaining-subscription-time-percentage',
|
||||
Refunds = 'refunds',
|
||||
NewCustomers = 'new-customers',
|
||||
TotalCustomers = 'total-customers',
|
||||
MRR = 'mrr',
|
||||
MonthlyPlansMRR = 'monthly-plans-mrr',
|
||||
AnnualPlansMRR = 'annual-plans-mrr',
|
||||
FiveYearPlansMRR = 'five-year-plans-mrr',
|
||||
ProPlansMRR = 'pro-plans-mrr',
|
||||
PlusPlansMRR = 'plus-plans-mrr',
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Period } from '../Time/Period'
|
||||
import { StatisticsMeasure } from './StatisticsMeasure'
|
||||
|
||||
export interface StatisticsStoreInterface {
|
||||
incrementSNJSVersionUsage(snjsVersion: string): Promise<void>
|
||||
@@ -7,13 +8,13 @@ export interface StatisticsStoreInterface {
|
||||
getYesterdaySNJSUsage(): Promise<Array<{ version: string; count: number }>>
|
||||
getYesterdayApplicationUsage(): Promise<Array<{ version: string; count: number }>>
|
||||
getYesterdayOutOfSyncIncidents(): Promise<number>
|
||||
incrementMeasure(measure: string, value: number, periods: Period[]): Promise<void>
|
||||
setMeasure(measure: string, value: number, periods: Period[]): Promise<void>
|
||||
getMeasureAverage(measure: string, period: Period): Promise<number>
|
||||
getMeasureTotal(measure: string, periodOrPeriodKey: Period | string): Promise<number>
|
||||
getMeasureIncrementCounts(measure: string, period: Period): Promise<number>
|
||||
incrementMeasure(measure: StatisticsMeasure, value: number, periods: Period[]): Promise<void>
|
||||
setMeasure(measure: StatisticsMeasure, value: number, periods: Period[]): Promise<void>
|
||||
getMeasureAverage(measure: StatisticsMeasure, period: Period): Promise<number>
|
||||
getMeasureTotal(measure: StatisticsMeasure, periodOrPeriodKey: Period | string): Promise<number>
|
||||
getMeasureIncrementCounts(measure: StatisticsMeasure, period: Period): Promise<number>
|
||||
calculateTotalCountOverPeriod(
|
||||
measure: string,
|
||||
measure: StatisticsMeasure,
|
||||
period: Period,
|
||||
): Promise<Array<{ periodKey: string; totalCount: number }>>
|
||||
}
|
||||
|
||||
@@ -137,7 +137,7 @@ export class PeriodKeyGenerator implements PeriodKeyGeneratorInterface {
|
||||
return `${this.getYear(date)}-${this.getMonth(date)}`
|
||||
}
|
||||
|
||||
getDailyKey(date?: Date): string {
|
||||
private getDailyKey(date?: Date): string {
|
||||
date = date ?? new Date()
|
||||
|
||||
return `${this.getYear(date)}-${this.getMonth(date)}-${this.getDayOfTheMonth(date)}`
|
||||
|
||||
@@ -2,7 +2,6 @@ import { Period } from './Period'
|
||||
|
||||
export interface PeriodKeyGeneratorInterface {
|
||||
getPeriodKey(period: Period): string
|
||||
getDailyKey(date?: Date): string
|
||||
convertPeriodKeyToPeriod(periodKey: string): Period
|
||||
getDiscretePeriodKeys(period: Period): string[]
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'reflect-metadata'
|
||||
|
||||
import { RevenueModificationRepositoryInterface } from '../../Revenue/RevenueModificationRepositoryInterface'
|
||||
import { StatisticMeasureName } from '../../Statistics/StatisticMeasureName'
|
||||
import { StatisticsMeasure } from '../../Statistics/StatisticsMeasure'
|
||||
import { StatisticsStoreInterface } from '../../Statistics/StatisticsStoreInterface'
|
||||
import { Period } from '../../Time/Period'
|
||||
|
||||
@@ -24,7 +24,7 @@ describe('CalculateMonthlyRecurringRevenue', () => {
|
||||
it('should calculate the MRR diff and persist it as a statistic', async () => {
|
||||
await createUseCase().execute({})
|
||||
|
||||
expect(statisticsStore.setMeasure).toHaveBeenCalledWith(StatisticMeasureName.NAMES.MRR, 123.45, [
|
||||
expect(statisticsStore.setMeasure).toHaveBeenCalledWith(StatisticsMeasure.MRR, 123.45, [
|
||||
Period.Today,
|
||||
Period.ThisMonth,
|
||||
Period.ThisYear,
|
||||
|
||||
@@ -5,11 +5,11 @@ import { Result } from '@standardnotes/domain-core'
|
||||
import TYPES from '../../../Bootstrap/Types'
|
||||
import { MonthlyRevenue } from '../../Revenue/MonthlyRevenue'
|
||||
import { RevenueModificationRepositoryInterface } from '../../Revenue/RevenueModificationRepositoryInterface'
|
||||
import { StatisticsMeasure } from '../../Statistics/StatisticsMeasure'
|
||||
import { StatisticsStoreInterface } from '../../Statistics/StatisticsStoreInterface'
|
||||
import { Period } from '../../Time/Period'
|
||||
import { DomainUseCaseInterface } from '../DomainUseCaseInterface'
|
||||
import { CalculateMonthlyRecurringRevenueDTO } from './CalculateMonthlyRecurringRevenueDTO'
|
||||
import { StatisticMeasureName } from '../../Statistics/StatisticMeasureName'
|
||||
|
||||
@injectable()
|
||||
export class CalculateMonthlyRecurringRevenue implements DomainUseCaseInterface<MonthlyRevenue> {
|
||||
@@ -24,7 +24,7 @@ export class CalculateMonthlyRecurringRevenue implements DomainUseCaseInterface<
|
||||
billingFrequencies: [SubscriptionBillingFrequency.Annual, SubscriptionBillingFrequency.Monthly],
|
||||
})
|
||||
|
||||
await this.statisticsStore.setMeasure(StatisticMeasureName.NAMES.MRR, mrrDiff, [
|
||||
await this.statisticsStore.setMeasure(StatisticsMeasure.MRR, mrrDiff, [
|
||||
Period.Today,
|
||||
Period.ThisMonth,
|
||||
Period.ThisYear,
|
||||
@@ -34,7 +34,7 @@ export class CalculateMonthlyRecurringRevenue implements DomainUseCaseInterface<
|
||||
billingFrequencies: [SubscriptionBillingFrequency.Monthly],
|
||||
})
|
||||
|
||||
await this.statisticsStore.setMeasure(StatisticMeasureName.NAMES.MonthlyPlansMRR, monthlyPlansMrrDiff, [
|
||||
await this.statisticsStore.setMeasure(StatisticsMeasure.MonthlyPlansMRR, monthlyPlansMrrDiff, [
|
||||
Period.Today,
|
||||
Period.ThisMonth,
|
||||
Period.ThisYear,
|
||||
@@ -44,7 +44,7 @@ export class CalculateMonthlyRecurringRevenue implements DomainUseCaseInterface<
|
||||
billingFrequencies: [SubscriptionBillingFrequency.Annual],
|
||||
})
|
||||
|
||||
await this.statisticsStore.setMeasure(StatisticMeasureName.NAMES.AnnualPlansMRR, annualPlansMrrDiff, [
|
||||
await this.statisticsStore.setMeasure(StatisticsMeasure.AnnualPlansMRR, annualPlansMrrDiff, [
|
||||
Period.Today,
|
||||
Period.ThisMonth,
|
||||
Period.ThisYear,
|
||||
@@ -54,7 +54,7 @@ export class CalculateMonthlyRecurringRevenue implements DomainUseCaseInterface<
|
||||
billingFrequencies: [SubscriptionBillingFrequency.FiveYear],
|
||||
})
|
||||
|
||||
await this.statisticsStore.setMeasure(StatisticMeasureName.NAMES.FiveYearPlansMRR, fiveYearPlansMrrDiff, [
|
||||
await this.statisticsStore.setMeasure(StatisticsMeasure.FiveYearPlansMRR, fiveYearPlansMrrDiff, [
|
||||
Period.Today,
|
||||
Period.ThisMonth,
|
||||
Period.ThisYear,
|
||||
@@ -65,7 +65,7 @@ export class CalculateMonthlyRecurringRevenue implements DomainUseCaseInterface<
|
||||
billingFrequencies: [SubscriptionBillingFrequency.Annual, SubscriptionBillingFrequency.Monthly],
|
||||
})
|
||||
|
||||
await this.statisticsStore.setMeasure(StatisticMeasureName.NAMES.ProPlansMRR, proPlansMrrDiff, [
|
||||
await this.statisticsStore.setMeasure(StatisticsMeasure.ProPlansMRR, proPlansMrrDiff, [
|
||||
Period.Today,
|
||||
Period.ThisMonth,
|
||||
Period.ThisYear,
|
||||
@@ -76,7 +76,7 @@ export class CalculateMonthlyRecurringRevenue implements DomainUseCaseInterface<
|
||||
billingFrequencies: [SubscriptionBillingFrequency.Annual, SubscriptionBillingFrequency.Monthly],
|
||||
})
|
||||
|
||||
await this.statisticsStore.setMeasure(StatisticMeasureName.NAMES.PlusPlansMRR, plusPlansMrrDiff, [
|
||||
await this.statisticsStore.setMeasure(StatisticsMeasure.PlusPlansMRR, plusPlansMrrDiff, [
|
||||
Period.Today,
|
||||
Period.ThisMonth,
|
||||
Period.ThisYear,
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
import { Result, UseCaseInterface } from '@standardnotes/domain-core'
|
||||
|
||||
import { StatisticMeasure } from '../../Statistics/StatisticMeasure'
|
||||
import { StatisticMeasureName } from '../../Statistics/StatisticMeasureName'
|
||||
import { StatisticMeasureRepositoryInterface } from '../../Statistics/StatisticMeasureRepositoryInterface'
|
||||
import { PersistStatisticDTO } from './PersistStatisticDTO'
|
||||
|
||||
export class PersistStatistic implements UseCaseInterface<StatisticMeasure> {
|
||||
constructor(private statisticMeasureRepository: StatisticMeasureRepositoryInterface) {}
|
||||
|
||||
async execute(dto: PersistStatisticDTO): Promise<Result<StatisticMeasure>> {
|
||||
const statisticMeasureNameOrError = StatisticMeasureName.create(dto.statisticMeasureName)
|
||||
if (statisticMeasureNameOrError.isFailed()) {
|
||||
return Result.fail(`Could not persist statistic measure: ${statisticMeasureNameOrError.getError()}`)
|
||||
}
|
||||
|
||||
const statisticMeasureOrError = StatisticMeasure.create({
|
||||
date: dto.date,
|
||||
name: statisticMeasureNameOrError.getValue(),
|
||||
value: dto.value,
|
||||
})
|
||||
if (statisticMeasureOrError.isFailed()) {
|
||||
return Result.fail(`Could not persist statistic measure: ${statisticMeasureOrError.getError()}`)
|
||||
}
|
||||
const statisticMeasure = statisticMeasureOrError.getValue()
|
||||
|
||||
await this.statisticMeasureRepository.save(statisticMeasure)
|
||||
|
||||
return Result.ok(statisticMeasure)
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
export interface PersistStatisticDTO {
|
||||
statisticMeasureName: string
|
||||
value: number
|
||||
date: Date
|
||||
}
|
||||
@@ -1,23 +1,16 @@
|
||||
import * as IORedis from 'ioredis'
|
||||
|
||||
import { StatisticMeasure } from '../../Domain/Statistics/StatisticMeasure'
|
||||
import { StatisticMeasureRepositoryInterface } from '../../Domain/Statistics/StatisticMeasureRepositoryInterface'
|
||||
import { StatisticsMeasure } from '../../Domain/Statistics/StatisticsMeasure'
|
||||
|
||||
import { StatisticsStoreInterface } from '../../Domain/Statistics/StatisticsStoreInterface'
|
||||
import { Period } from '../../Domain/Time/Period'
|
||||
import { PeriodKeyGeneratorInterface } from '../../Domain/Time/PeriodKeyGeneratorInterface'
|
||||
|
||||
export class RedisStatisticsStore implements StatisticsStoreInterface, StatisticMeasureRepositoryInterface {
|
||||
export class RedisStatisticsStore implements StatisticsStoreInterface {
|
||||
constructor(private periodKeyGenerator: PeriodKeyGeneratorInterface, private redisClient: IORedis.Redis) {}
|
||||
|
||||
async save(statisticMeasure: StatisticMeasure): Promise<void> {
|
||||
const periodKey = this.periodKeyGenerator.getDailyKey(statisticMeasure.props.date)
|
||||
|
||||
await this.setMeasure(statisticMeasure.name, statisticMeasure.value, [periodKey])
|
||||
}
|
||||
|
||||
async calculateTotalCountOverPeriod(
|
||||
measure: string,
|
||||
measure: StatisticsMeasure,
|
||||
period: Period,
|
||||
): Promise<{ periodKey: string; totalCount: number }[]> {
|
||||
if (
|
||||
@@ -45,7 +38,7 @@ export class RedisStatisticsStore implements StatisticsStoreInterface, Statistic
|
||||
return counts
|
||||
}
|
||||
|
||||
async getMeasureIncrementCounts(measure: string, period: Period): Promise<number> {
|
||||
async getMeasureIncrementCounts(measure: StatisticsMeasure, period: Period): Promise<number> {
|
||||
const increments = await this.redisClient.get(
|
||||
`count:increments:${measure}:timespan:${this.periodKeyGenerator.getPeriodKey(period)}`,
|
||||
)
|
||||
@@ -56,22 +49,17 @@ export class RedisStatisticsStore implements StatisticsStoreInterface, Statistic
|
||||
return +increments
|
||||
}
|
||||
|
||||
async setMeasure(measure: string, value: number, periodsOrPeriodKeys: Period[] | string[]): Promise<void> {
|
||||
async setMeasure(measure: StatisticsMeasure, value: number, periods: Period[]): Promise<void> {
|
||||
const pipeline = this.redisClient.pipeline()
|
||||
|
||||
for (const periodOrPeriodKey of periodsOrPeriodKeys) {
|
||||
let periodKey = periodOrPeriodKey
|
||||
if (!isNaN(+periodOrPeriodKey)) {
|
||||
periodKey = this.periodKeyGenerator.getPeriodKey(periodOrPeriodKey as Period)
|
||||
}
|
||||
|
||||
pipeline.set(`count:measure:${measure}:timespan:${periodKey}`, value)
|
||||
for (const period of periods) {
|
||||
pipeline.set(`count:measure:${measure}:timespan:${this.periodKeyGenerator.getPeriodKey(period)}`, value)
|
||||
}
|
||||
|
||||
await pipeline.exec()
|
||||
}
|
||||
|
||||
async getMeasureTotal(measure: string, periodOrPeriodKey: Period | string): Promise<number> {
|
||||
async getMeasureTotal(measure: StatisticsMeasure, periodOrPeriodKey: Period | string): Promise<number> {
|
||||
let periodKey = periodOrPeriodKey
|
||||
if (!isNaN(+periodOrPeriodKey)) {
|
||||
periodKey = this.periodKeyGenerator.getPeriodKey(periodOrPeriodKey as Period)
|
||||
@@ -86,7 +74,7 @@ export class RedisStatisticsStore implements StatisticsStoreInterface, Statistic
|
||||
return +totalValue
|
||||
}
|
||||
|
||||
async incrementMeasure(measure: string, value: number, periods: Period[]): Promise<void> {
|
||||
async incrementMeasure(measure: StatisticsMeasure, value: number, periods: Period[]): Promise<void> {
|
||||
const pipeline = this.redisClient.pipeline()
|
||||
|
||||
for (const period of periods) {
|
||||
@@ -97,7 +85,7 @@ export class RedisStatisticsStore implements StatisticsStoreInterface, Statistic
|
||||
await pipeline.exec()
|
||||
}
|
||||
|
||||
async getMeasureAverage(measure: string, period: Period): Promise<number> {
|
||||
async getMeasureAverage(measure: StatisticsMeasure, period: Period): Promise<number> {
|
||||
const increments = await this.getMeasureIncrementCounts(measure, period)
|
||||
|
||||
if (increments === 0) {
|
||||
|
||||
@@ -3,48 +3,6 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [1.45.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.44.0...@standardnotes/api-gateway@1.45.0) (2023-01-05)
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** add recovery sign in with recovery codes ([cac899a](https://github.com/standardnotes/api-gateway/commit/cac899a7e558d066895dfb3ba28418d94072f2b7))
|
||||
|
||||
# [1.44.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.43.0...@standardnotes/api-gateway@1.44.0) (2022-12-29)
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** add removing authenticator ([de50d76](https://github.com/standardnotes/api-gateway/commit/de50d76800a4240729763b2df11c4a1718951670))
|
||||
|
||||
# [1.43.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.42.0...@standardnotes/api-gateway@1.43.0) (2022-12-29)
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** add listing authenticators ([01837ea](https://github.com/standardnotes/api-gateway/commit/01837eaea9b1f219e7ad3be4d28cd0df099fe423))
|
||||
|
||||
# [1.42.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.41.3...@standardnotes/api-gateway@1.42.0) (2022-12-29)
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** add http endpoints for authenticators ([b6fda90](https://github.com/standardnotes/api-gateway/commit/b6fda901ef66a3e66541bd1e3f041b8268a1c3f5))
|
||||
|
||||
## [1.41.3](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.41.2...@standardnotes/api-gateway@1.41.3) (2022-12-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||
|
||||
## [1.41.2](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.41.1...@standardnotes/api-gateway@1.41.2) (2022-12-20)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||
|
||||
## [1.41.1](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.41.0...@standardnotes/api-gateway@1.41.1) (2022-12-19)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||
|
||||
# [1.41.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.40.2...@standardnotes/api-gateway@1.41.0) (2022-12-19)
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** add session traces ([8bcb552](https://github.com/standardnotes/api-gateway/commit/8bcb552783b2d12f3296b3195752168482790bc8))
|
||||
|
||||
## [1.40.2](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.40.1...@standardnotes/api-gateway@1.40.2) (2022-12-12)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||
|
||||
@@ -21,7 +21,6 @@ import '../src/Controller/v1/FilesController'
|
||||
import '../src/Controller/v1/SubscriptionInvitesController'
|
||||
import '../src/Controller/v1/WorkspacesController'
|
||||
import '../src/Controller/v1/InvitesController'
|
||||
import '../src/Controller/v1/AuthenticatorsController'
|
||||
|
||||
import '../src/Controller/v2/PaymentsControllerV2'
|
||||
import '../src/Controller/v2/ActionsControllerV2'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/api-gateway",
|
||||
"version": "1.45.0",
|
||||
"version": "1.40.2",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <19.0.0"
|
||||
},
|
||||
@@ -21,7 +21,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@newrelic/winston-enricher": "^4.0.0",
|
||||
"@sentry/node": "^7.28.1",
|
||||
"@sentry/node": "^7.19.0",
|
||||
"@standardnotes/common": "workspace:^",
|
||||
"@standardnotes/domain-events": "workspace:*",
|
||||
"@standardnotes/domain-events-infra": "workspace:*",
|
||||
|
||||
@@ -39,19 +39,4 @@ export class ActionsController extends BaseHttpController {
|
||||
request.body,
|
||||
)
|
||||
}
|
||||
|
||||
@httpPost('/recovery/codes', TYPES.AuthMiddleware)
|
||||
async recoveryCodes(request: Request, response: Response): Promise<void> {
|
||||
await this.httpService.callAuthServer(request, response, 'auth/recovery/codes', request.body)
|
||||
}
|
||||
|
||||
@httpPost('/recovery/login')
|
||||
async recoveryLogin(request: Request, response: Response): Promise<void> {
|
||||
await this.httpService.callAuthServer(request, response, 'auth/recovery/login', request.body)
|
||||
}
|
||||
|
||||
@httpPost('/recovery/login-params')
|
||||
async recoveryParams(request: Request, response: Response): Promise<void> {
|
||||
await this.httpService.callAuthServer(request, response, 'auth/recovery/params', request.body)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
import { inject } from 'inversify'
|
||||
import { Request, Response } from 'express'
|
||||
import { controller, BaseHttpController, httpPost, httpGet, httpDelete } from 'inversify-express-utils'
|
||||
|
||||
import TYPES from '../../Bootstrap/Types'
|
||||
import { HttpServiceInterface } from '../../Service/Http/HttpServiceInterface'
|
||||
|
||||
@controller('/v1/authenticators', TYPES.AuthMiddleware)
|
||||
export class AuthenticatorsController extends BaseHttpController {
|
||||
constructor(@inject(TYPES.HTTPService) private httpService: HttpServiceInterface) {
|
||||
super()
|
||||
}
|
||||
|
||||
@httpDelete('/:authenticatorId')
|
||||
async delete(request: Request, response: Response): Promise<void> {
|
||||
await this.httpService.callAuthServer(
|
||||
request,
|
||||
response,
|
||||
`authenticators/${request.params.authenticatorId}`,
|
||||
request.body,
|
||||
)
|
||||
}
|
||||
|
||||
@httpGet('/')
|
||||
async list(request: Request, response: Response): Promise<void> {
|
||||
await this.httpService.callAuthServer(request, response, 'authenticators/', request.body)
|
||||
}
|
||||
|
||||
@httpGet('/generate-registration-options')
|
||||
async generateRegistrationOptions(request: Request, response: Response): Promise<void> {
|
||||
await this.httpService.callAuthServer(
|
||||
request,
|
||||
response,
|
||||
'authenticators/generate-registration-options',
|
||||
request.body,
|
||||
)
|
||||
}
|
||||
|
||||
@httpGet('/generate-authentication-options')
|
||||
async generateAuthenticationOptions(request: Request, response: Response): Promise<void> {
|
||||
await this.httpService.callAuthServer(
|
||||
request,
|
||||
response,
|
||||
'authenticators/generate-authentication-options',
|
||||
request.body,
|
||||
)
|
||||
}
|
||||
|
||||
@httpPost('/verify-registration')
|
||||
async verifyRegistration(request: Request, response: Response): Promise<void> {
|
||||
await this.httpService.callAuthServer(request, response, 'authenticators/verify-registration', request.body)
|
||||
}
|
||||
|
||||
@httpPost('/verify-authentication')
|
||||
async verifyAuthentication(request: Request, response: Response): Promise<void> {
|
||||
await this.httpService.callAuthServer(request, response, 'authenticators/verify-authentication', request.body)
|
||||
}
|
||||
}
|
||||
@@ -3,209 +3,6 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.81.4](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.81.3...@standardnotes/auth-server@1.81.4) (2023-01-09)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** error messages on account recovery ([1fc3c9b](https://github.com/standardnotes/server/commit/1fc3c9b83ee2239b618dfb609b1dc2d68d063331))
|
||||
|
||||
## [1.81.3](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.81.2...@standardnotes/auth-server@1.81.3) (2023-01-09)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** remove mfa settings after recovery sign in ([a0208dd](https://github.com/standardnotes/server/commit/a0208dd5b3ce54ccfa96b3497cb36e16ccb4cf89))
|
||||
|
||||
## [1.81.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.81.1...@standardnotes/auth-server@1.81.2) (2023-01-05)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** return type to include user ([db0baf9](https://github.com/standardnotes/server/commit/db0baf92f1336071a8602a1a20ba6439f10028e6))
|
||||
|
||||
## [1.81.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.81.0...@standardnotes/auth-server@1.81.1) (2023-01-05)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** allow retrieval of recovery codes setting ([13c5c97](https://github.com/standardnotes/server/commit/13c5c97ba73fcf1f4bd2905a1d8668ef5b468016))
|
||||
|
||||
# [1.81.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.80.0...@standardnotes/auth-server@1.81.0) (2023-01-05)
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** add recovery sign in with recovery codes ([cac899a](https://github.com/standardnotes/server/commit/cac899a7e558d066895dfb3ba28418d94072f2b7))
|
||||
|
||||
# [1.80.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.79.1...@standardnotes/auth-server@1.80.0) (2023-01-04)
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** add generating recovery codes ([a360231](https://github.com/standardnotes/server/commit/a360231fd0b04afbc8cfa0ed74c8e21c4edadb4e))
|
||||
|
||||
## [1.79.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.79.0...@standardnotes/auth-server@1.79.1) (2023-01-02)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* remove @sentry/profiling-node as it is not compatible with ARM - fixes [#383](https://github.com/standardnotes/server/issues/383) ([fa6d80a](https://github.com/standardnotes/server/commit/fa6d80a753d4999818bb32a7fcb124f23c15f574))
|
||||
* remove @sentry/profiling-node integration as it is not compatible with ARM - fixes [#383](https://github.com/standardnotes/server/issues/383) ([9c72ad8](https://github.com/standardnotes/server/commit/9c72ad85a04040b3fdfce4f769e5e717ce81a3ce))
|
||||
|
||||
# [1.79.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.78.0...@standardnotes/auth-server@1.79.0) (2022-12-29)
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** add removing authenticator ([de50d76](https://github.com/standardnotes/server/commit/de50d76800a4240729763b2df11c4a1718951670))
|
||||
|
||||
# [1.78.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.77.1...@standardnotes/auth-server@1.78.0) (2022-12-29)
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** add listing authenticators ([01837ea](https://github.com/standardnotes/server/commit/01837eaea9b1f219e7ad3be4d28cd0df099fe423))
|
||||
|
||||
## [1.77.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.77.0...@standardnotes/auth-server@1.77.1) (2022-12-29)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** specs for verifying authenticator authentication response ([5455972](https://github.com/standardnotes/server/commit/5455972be2c62d7862c351b1328beacf4bd5c3da))
|
||||
|
||||
# [1.77.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.76.0...@standardnotes/auth-server@1.77.0) (2022-12-29)
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** add http endpoints for authenticators ([b6fda90](https://github.com/standardnotes/server/commit/b6fda901ef66a3e66541bd1e3f041b8268a1c3f5))
|
||||
|
||||
# [1.76.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.75.0...@standardnotes/auth-server@1.76.0) (2022-12-29)
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** add verifying authenticator authentication response ([64525a6](https://github.com/standardnotes/server/commit/64525a65f2e1677f942868903f318d6700c34c74))
|
||||
|
||||
# [1.75.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.74.1...@standardnotes/auth-server@1.75.0) (2022-12-29)
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** add generating authenticator authentication options ([8c7c1e4](https://github.com/standardnotes/server/commit/8c7c1e4745647004f3dc361ec374014390952486))
|
||||
|
||||
## [1.74.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.74.0...@standardnotes/auth-server@1.74.1) (2022-12-28)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** migrations to not include unique index for credentials id ([384dfc8](https://github.com/standardnotes/server/commit/384dfc8da4b1b640964fa6da207a67fcd68dc7ec))
|
||||
|
||||
# [1.74.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.73.1...@standardnotes/auth-server@1.74.0) (2022-12-28)
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** add verifying authenticator registration response ([f5683cf](https://github.com/standardnotes/server/commit/f5683cfd9494db8e25010e9c4ef5fd4d8fcd6bc7))
|
||||
|
||||
## [1.73.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.73.0...@standardnotes/auth-server@1.73.1) (2022-12-28)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** temporarily remove credential id index due to mysql 5.6 limitations ([a5e7132](https://github.com/standardnotes/server/commit/a5e7132d3c4b74ed13877d7d437062c509201874))
|
||||
|
||||
# [1.73.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.72.0...@standardnotes/auth-server@1.73.0) (2022-12-28)
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** add generating authencator registration options ([51ad06b](https://github.com/standardnotes/server/commit/51ad06b303d7dc994920818872fdf8bd37fc445c))
|
||||
|
||||
# [1.72.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.71.1...@standardnotes/auth-server@1.72.0) (2022-12-28)
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** add authenticator challenges model ([fa9bf0b](https://github.com/standardnotes/server/commit/fa9bf0b448acb3f19ab44c4b431ce367dab37b76))
|
||||
|
||||
## [1.71.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.71.0...@standardnotes/auth-server@1.71.1) (2022-12-28)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** credential id field type ([98e3d18](https://github.com/standardnotes/server/commit/98e3d1833530dcd9e3e34a4c4a6b14a2a01afea1))
|
||||
|
||||
# [1.71.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.70.9...@standardnotes/auth-server@1.71.0) (2022-12-28)
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** add authenticators model ([1e69a13](https://github.com/standardnotes/server/commit/1e69a13a97c4d9022aa96397cce1b349d3cede89))
|
||||
|
||||
## [1.70.9](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.70.8...@standardnotes/auth-server@1.70.9) (2022-12-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/auth-server
|
||||
|
||||
## [1.70.8](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.70.7...@standardnotes/auth-server@1.70.8) (2022-12-20)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** move tracing sessions to session creation instead of cross service token creation ([5255cfb](https://github.com/standardnotes/server/commit/5255cfbb257cc9e6ac437fe0c5b28d938e3e599b))
|
||||
|
||||
## [1.70.7](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.70.6...@standardnotes/auth-server@1.70.7) (2022-12-20)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** change severity on tracing session errors - most probably hazardous reads ([cf0b918](https://github.com/standardnotes/server/commit/cf0b91891370e1c1799ad80c10ee9f6b98087a94))
|
||||
|
||||
## [1.70.6](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.70.5...@standardnotes/auth-server@1.70.6) (2022-12-20)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** query for session traces ([14eb775](https://github.com/standardnotes/server/commit/14eb775749bfa9972dc3c07049505f3d15f0b556))
|
||||
|
||||
## [1.70.5](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.70.4...@standardnotes/auth-server@1.70.5) (2022-12-20)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** add session traces index ([b9e1e47](https://github.com/standardnotes/server/commit/b9e1e4787129f00fab8f98cb721141f2e7d75600))
|
||||
|
||||
## [1.70.4](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.70.3...@standardnotes/auth-server@1.70.4) (2022-12-20)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** replace date object with number timestamp ([5b4bb6e](https://github.com/standardnotes/server/commit/5b4bb6e7a78a1b0f4e663bb990619f65f6a5c757))
|
||||
|
||||
## [1.70.3](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.70.2...@standardnotes/auth-server@1.70.3) (2022-12-20)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** add persisting statistics for all subscription plans ([addedb3](https://github.com/standardnotes/server/commit/addedb3091ddae81618d56663e18f2ae76a43c4e))
|
||||
|
||||
## [1.70.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.70.1...@standardnotes/auth-server@1.70.2) (2022-12-20)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** docker command ([85d2f42](https://github.com/standardnotes/server/commit/85d2f42f473110e8dfca975bfecc7a56823bdef4))
|
||||
|
||||
## [1.70.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.70.0...@standardnotes/auth-server@1.70.1) (2022-12-20)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** saving subscription plan name in session traces ([3064d03](https://github.com/standardnotes/server/commit/3064d03aa9a2ac9ca3acfff30480ea8629faeb14))
|
||||
|
||||
# [1.70.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.69.1...@standardnotes/auth-server@1.70.0) (2022-12-19)
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** add requesting persisting statistics ([a35271f](https://github.com/standardnotes/server/commit/a35271fbb399b68a3ac7021395d8063707fba222))
|
||||
|
||||
## [1.69.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.69.0...@standardnotes/auth-server@1.69.1) (2022-12-19)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/auth-server
|
||||
|
||||
# [1.69.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.68.0...@standardnotes/auth-server@1.69.0) (2022-12-19)
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** add session traces cleanup procedure ([147d8fd](https://github.com/standardnotes/server/commit/147d8fd9af89d2b97cd68eefac36e53d14d511bf))
|
||||
|
||||
# [1.68.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.67.3...@standardnotes/auth-server@1.68.0) (2022-12-19)
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** add session traces ([8bcb552](https://github.com/standardnotes/server/commit/8bcb552783b2d12f3296b3195752168482790bc8))
|
||||
|
||||
## [1.67.3](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.67.2...@standardnotes/auth-server@1.67.3) (2022-12-15)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/auth-server
|
||||
|
||||
## [1.67.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.67.1...@standardnotes/auth-server@1.67.2) (2022-12-15)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/auth-server
|
||||
|
||||
## [1.67.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.67.0...@standardnotes/auth-server@1.67.1) (2022-12-12)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -3,20 +3,19 @@ import 'reflect-metadata'
|
||||
import 'newrelic'
|
||||
|
||||
import { Stream } from 'stream'
|
||||
|
||||
import { Logger } from 'winston'
|
||||
import * as dayjs from 'dayjs'
|
||||
import * as utc from 'dayjs/plugin/utc'
|
||||
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
|
||||
import { SettingName } from '@standardnotes/domain-core'
|
||||
import { PermissionName } from '@standardnotes/features'
|
||||
|
||||
import { ContainerConfigLoader } from '../src/Bootstrap/Container'
|
||||
import TYPES from '../src/Bootstrap/Types'
|
||||
import { Env } from '../src/Bootstrap/Env'
|
||||
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
|
||||
import { DomainEventFactoryInterface } from '../src/Domain/Event/DomainEventFactoryInterface'
|
||||
import { SettingRepositoryInterface } from '../src/Domain/Setting/SettingRepositoryInterface'
|
||||
import { MuteFailedBackupsEmailsOption, MuteFailedCloudBackupsEmailsOption, SettingName } from '@standardnotes/settings'
|
||||
import { RoleServiceInterface } from '../src/Domain/Role/RoleServiceInterface'
|
||||
import { PermissionName } from '@standardnotes/features'
|
||||
import { SettingServiceInterface } from '../src/Domain/Setting/SettingServiceInterface'
|
||||
|
||||
const inputArgs = process.argv.slice(2)
|
||||
@@ -30,38 +29,38 @@ const requestBackups = async (
|
||||
domainEventFactory: DomainEventFactoryInterface,
|
||||
domainEventPublisher: DomainEventPublisherInterface,
|
||||
): Promise<void> => {
|
||||
let settingName: SettingName,
|
||||
let settingName: string,
|
||||
permissionName: PermissionName,
|
||||
muteEmailsSettingName: SettingName,
|
||||
muteEmailsSettingName: string,
|
||||
muteEmailsSettingValue: string,
|
||||
providerTokenSettingName: SettingName
|
||||
providerTokenSettingName: string
|
||||
switch (backupProvider) {
|
||||
case 'email':
|
||||
settingName = SettingName.EmailBackupFrequency
|
||||
settingName = SettingName.NAMES.EmailBackupFrequency
|
||||
permissionName = PermissionName.DailyEmailBackup
|
||||
muteEmailsSettingName = SettingName.MuteFailedBackupsEmails
|
||||
muteEmailsSettingValue = MuteFailedBackupsEmailsOption.Muted
|
||||
muteEmailsSettingName = SettingName.NAMES.MuteFailedBackupsEmails
|
||||
muteEmailsSettingValue = 'muted'
|
||||
break
|
||||
case 'dropbox':
|
||||
settingName = SettingName.DropboxBackupFrequency
|
||||
settingName = SettingName.NAMES.DropboxBackupFrequency
|
||||
permissionName = PermissionName.DailyDropboxBackup
|
||||
muteEmailsSettingName = SettingName.MuteFailedCloudBackupsEmails
|
||||
muteEmailsSettingValue = MuteFailedCloudBackupsEmailsOption.Muted
|
||||
providerTokenSettingName = SettingName.DropboxBackupToken
|
||||
muteEmailsSettingName = SettingName.NAMES.MuteFailedCloudBackupsEmails
|
||||
muteEmailsSettingValue = 'muted'
|
||||
providerTokenSettingName = SettingName.NAMES.DropboxBackupToken
|
||||
break
|
||||
case 'one_drive':
|
||||
settingName = SettingName.OneDriveBackupFrequency
|
||||
settingName = SettingName.NAMES.OneDriveBackupFrequency
|
||||
permissionName = PermissionName.DailyOneDriveBackup
|
||||
muteEmailsSettingName = SettingName.MuteFailedCloudBackupsEmails
|
||||
muteEmailsSettingValue = MuteFailedCloudBackupsEmailsOption.Muted
|
||||
providerTokenSettingName = SettingName.OneDriveBackupToken
|
||||
muteEmailsSettingName = SettingName.NAMES.MuteFailedCloudBackupsEmails
|
||||
muteEmailsSettingValue = 'muted'
|
||||
providerTokenSettingName = SettingName.NAMES.OneDriveBackupToken
|
||||
break
|
||||
case 'google_drive':
|
||||
settingName = SettingName.GoogleDriveBackupFrequency
|
||||
settingName = SettingName.NAMES.GoogleDriveBackupFrequency
|
||||
permissionName = PermissionName.DailyGDriveBackup
|
||||
muteEmailsSettingName = SettingName.MuteFailedCloudBackupsEmails
|
||||
muteEmailsSettingValue = MuteFailedCloudBackupsEmailsOption.Muted
|
||||
providerTokenSettingName = SettingName.GoogleDriveBackupToken
|
||||
muteEmailsSettingName = SettingName.NAMES.MuteFailedCloudBackupsEmails
|
||||
muteEmailsSettingValue = 'muted'
|
||||
providerTokenSettingName = SettingName.NAMES.GoogleDriveBackupToken
|
||||
break
|
||||
default:
|
||||
throw new Error(`Not handled backup provider: ${backupProvider}`)
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
import 'reflect-metadata'
|
||||
|
||||
import 'newrelic'
|
||||
|
||||
import { Logger } from 'winston'
|
||||
|
||||
import { ContainerConfigLoader } from '../src/Bootstrap/Container'
|
||||
import TYPES from '../src/Bootstrap/Types'
|
||||
import { Env } from '../src/Bootstrap/Env'
|
||||
import { CleanupSessionTraces } from '../src/Domain/UseCase/CleanupSessionTraces/CleanupSessionTraces'
|
||||
|
||||
const container = new ContainerConfigLoader()
|
||||
void container.load().then((container) => {
|
||||
const env: Env = new Env()
|
||||
env.load()
|
||||
|
||||
const logger: Logger = container.get(TYPES.Logger)
|
||||
|
||||
logger.info('Starting session traces cleanup')
|
||||
|
||||
const cleanupSessionTraces: CleanupSessionTraces = container.get(TYPES.CleanupSessionTraces)
|
||||
|
||||
Promise.resolve(
|
||||
cleanupSessionTraces.execute({
|
||||
date: new Date(),
|
||||
}),
|
||||
)
|
||||
.then(() => {
|
||||
logger.info('Expired session traces cleaned.')
|
||||
|
||||
process.exit(0)
|
||||
})
|
||||
.catch((error) => {
|
||||
logger.error(`Could not clean session traces: ${error.message}`)
|
||||
|
||||
process.exit(1)
|
||||
})
|
||||
})
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user