Compare commits

...

28 Commits

Author SHA1 Message Date
standardci
31b6988f17 chore(release): publish new version
- @standardnotes/analytics@1.15.0
 - @standardnotes/api-gateway@1.13.0
 - @standardnotes/auth-server@1.17.0
 - @standardnotes/syncing-server@1.6.43
2022-08-11 07:28:16 +00:00
Karol Sójko
16076382ba feat: add analytics for subscription cancelling, refunding and account deletion 2022-08-11 09:26:21 +02:00
standardci
666c919b70 chore(release): publish new version
- @standardnotes/api-gateway@1.12.0
2022-08-10 13:44:58 +00:00
Karol Sójko
dea5fd717d feat(api-gateway): add publishing subscription purchased, renewed and registration analytics 2022-08-10 15:43:30 +02:00
standardci
02a4b6f721 chore(release): publish new version
- @standardnotes/api-gateway@1.11.5
 - @standardnotes/auth-server@1.16.2
 - @standardnotes/domain-events-infra@1.7.31
 - @standardnotes/event-store@1.1.30
 - @standardnotes/files-server@1.5.33
 - @standardnotes/scheduler-server@1.10.7
 - @standardnotes/syncing-server@1.6.42
2022-08-10 09:29:01 +00:00
Karol Sójko
815a6c4e3b chore(dep): upgrade newrelic version 2022-08-10 11:27:30 +02:00
standardci
ac26024990 chore(release): publish new version
- @standardnotes/auth-server@1.16.1
2022-08-10 09:21:31 +00:00
Karol Sójko
f67be9d3ba fix(auth): docker entrypoing command for single user backup 2022-08-10 11:19:53 +02:00
standardci
bb85e3ef11 chore(release): publish new version
- @standardnotes/auth-server@1.16.0
2022-08-10 09:19:42 +00:00
Karol Sójko
edd254531d feat(auth): add triggering email backup for a single user 2022-08-10 11:18:01 +02:00
standardci
0f1ff97a60 chore(release): publish new version
- @standardnotes/analytics@1.14.0
 - @standardnotes/api-gateway@1.11.4
 - @standardnotes/auth-server@1.15.0
 - @standardnotes/syncing-server@1.6.41
2022-08-09 18:49:11 +00:00
Karol Sójko
24e439f017 feat(auth): add subscription events to analytics 2022-08-09 20:47:29 +02:00
standardci
0a01fd58eb chore(release): publish new version
- @standardnotes/analytics@1.13.0
 - @standardnotes/api-gateway@1.11.3
 - @standardnotes/auth-server@1.14.0
 - @standardnotes/syncing-server@1.6.40
2022-08-09 18:20:44 +00:00
Karol Sójko
f25195b2c1 feat(auth): track registration in analytics 2022-08-09 20:18:53 +02:00
standardci
29674b02e6 chore(release): publish new version
- @standardnotes/analytics@1.12.2
 - @standardnotes/api-gateway@1.11.2
 - @standardnotes/auth-server@1.13.6
 - @standardnotes/syncing-server@1.6.39
2022-08-09 13:24:49 +00:00
Karol Sójko
572ea3febe fix(analytics): replace AND to OR operation on bitop 2022-08-09 15:23:10 +02:00
standardci
f8334cf9d2 chore(release): publish new version
- @standardnotes/analytics@1.12.1
 - @standardnotes/api-gateway@1.11.1
 - @standardnotes/auth-server@1.13.5
 - @standardnotes/syncing-server@1.6.38
2022-08-09 13:00:18 +00:00
Karol Sójko
0ffec66bea fix(analytics): bitop over analytics time 2022-08-09 14:58:37 +02:00
standardci
becb386d10 chore(release): publish new version
- @standardnotes/analytics@1.12.0
 - @standardnotes/api-gateway@1.11.0
 - @standardnotes/auth-server@1.13.4
 - @standardnotes/domain-events-infra@1.7.30
 - @standardnotes/domain-events@2.53.0
 - @standardnotes/event-store@1.1.29
 - @standardnotes/files-server@1.5.32
 - @standardnotes/scheduler-server@1.10.6
 - @standardnotes/syncing-server@1.6.37
2022-08-09 12:23:19 +00:00
Karol Sójko
0b9524eb26 feat: add total count of analytics over time 2022-08-09 14:21:04 +02:00
standardci
ff5cd0128a chore(release): publish new version
- @standardnotes/api-gateway@1.10.0
2022-08-09 11:08:58 +00:00
Karol Sójko
b9225cd9b6 feat(api-gateway): add editing items count over time 2022-08-09 13:07:26 +02:00
standardci
8f0ed3c5b3 chore(release): publish new version
- @standardnotes/api-gateway@1.9.1
2022-08-08 19:11:13 +00:00
Karol Sójko
12e3a768dd fix(api-gateway): add general activity to calculating activity retention 2022-08-08 21:09:11 +02:00
standardci
a04ab5b0e8 chore(release): publish new version
- @standardnotes/analytics@1.11.0
 - @standardnotes/api-gateway@1.9.0
 - @standardnotes/auth-server@1.13.3
 - @standardnotes/domain-events-infra@1.7.29
 - @standardnotes/domain-events@2.52.0
 - @standardnotes/event-store@1.1.28
 - @standardnotes/files-server@1.5.31
 - @standardnotes/scheduler-server@1.10.5
 - @standardnotes/syncing-server@1.6.36
2022-08-08 19:07:09 +00:00
Karol Sójko
845f08b060 feat(api-gateway): add analytics over time to daily report event 2022-08-08 21:05:31 +02:00
Karol Sójko
31c849cc2d fix(analytics): format of changes over time to total count 2022-08-08 21:04:54 +02:00
Karol Sójko
977757d346 feat(analytics): add calculating analytics over time 2022-08-08 20:48:18 +02:00
104 changed files with 1005 additions and 148 deletions

83
.pnp.cjs generated
View File

@@ -121,7 +121,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
["eslint", "npm:8.19.0"],\
["eslint-config-prettier", "virtual:8859b278716fedf3e7458b5628625f7e35678c418626878559a0b816445001b7e24c55546f4677ba4c20b521aa0cf52cc33ac07deff171e383ada6eeab69933f#npm:8.5.0"],\
["ini", "npm:3.0.0"],\
["newrelic", "npm:8.14.1"],\
["newrelic", "npm:9.0.0"],\
["npm-check-updates", "npm:16.0.1"],\
["prettier", "npm:2.7.1"],\
["ts-node", "virtual:c0eab07e71af57f5501e97e7ca7a2a4f4965035bd2455ad124a8b09fa55780657c55fe3df41019fa6c2c44487c897668c842a0939e380b3c1f13b3756d128543#npm:10.8.2"],\
@@ -1788,20 +1788,19 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
}]\
]],\
["@newrelic/aws-sdk", [\
["npm:4.1.2", {\
"packageLocation": "./.yarn/cache/@newrelic-aws-sdk-npm-4.1.2-9930120a02-610f6353a7.zip/node_modules/@newrelic/aws-sdk/",\
["npm:5.0.0", {\
"packageLocation": "./.yarn/cache/@newrelic-aws-sdk-npm-5.0.0-7d9d10d58f-b467b7306f.zip/node_modules/@newrelic/aws-sdk/",\
"packageDependencies": [\
["@newrelic/aws-sdk", "npm:4.1.2"]\
["@newrelic/aws-sdk", "npm:5.0.0"]\
],\
"linkType": "SOFT"\
}],\
["virtual:b659d4d19ce37732618223b586685dc7b27d11c52dca80d8411fdda38a4e6a7967776a5d7bdcce0be779cd6a41e2d84f8bbd3d3b5c06ce5ae40ad814517dfcfb#npm:4.1.2", {\
"packageLocation": "./.yarn/__virtual__/@newrelic-aws-sdk-virtual-a259cc13ec/0/cache/@newrelic-aws-sdk-npm-4.1.2-9930120a02-610f6353a7.zip/node_modules/@newrelic/aws-sdk/",\
["virtual:65e8703d5df08d5ff3f7296fcb759f276254ee430ae6f3cc1f03da392ff63066a3c6c59591c38f36f19d3e877285287a834b5c55e22d764dc2cb0938f7128707#npm:5.0.0", {\
"packageLocation": "./.yarn/__virtual__/@newrelic-aws-sdk-virtual-e9040e4121/0/cache/@newrelic-aws-sdk-npm-5.0.0-7d9d10d58f-b467b7306f.zip/node_modules/@newrelic/aws-sdk/",\
"packageDependencies": [\
["@newrelic/aws-sdk", "virtual:b659d4d19ce37732618223b586685dc7b27d11c52dca80d8411fdda38a4e6a7967776a5d7bdcce0be779cd6a41e2d84f8bbd3d3b5c06ce5ae40ad814517dfcfb#npm:4.1.2"],\
["@newrelic/aws-sdk", "virtual:65e8703d5df08d5ff3f7296fcb759f276254ee430ae6f3cc1f03da392ff63066a3c6c59591c38f36f19d3e877285287a834b5c55e22d764dc2cb0938f7128707#npm:5.0.0"],\
["@types/newrelic", null],\
["newrelic", "npm:8.14.1"],\
["semver", "npm:7.3.7"]\
["newrelic", "npm:9.0.0"]\
],\
"packagePeers": [\
"@types/newrelic",\
@@ -1811,19 +1810,19 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
}]\
]],\
["@newrelic/koa", [\
["npm:6.1.2", {\
"packageLocation": "./.yarn/cache/@newrelic-koa-npm-6.1.2-df0f7c71b5-e269d37b13.zip/node_modules/@newrelic/koa/",\
["npm:7.0.0", {\
"packageLocation": "./.yarn/cache/@newrelic-koa-npm-7.0.0-903c251b9f-13fb2bf0df.zip/node_modules/@newrelic/koa/",\
"packageDependencies": [\
["@newrelic/koa", "npm:6.1.2"]\
["@newrelic/koa", "npm:7.0.0"]\
],\
"linkType": "SOFT"\
}],\
["virtual:b659d4d19ce37732618223b586685dc7b27d11c52dca80d8411fdda38a4e6a7967776a5d7bdcce0be779cd6a41e2d84f8bbd3d3b5c06ce5ae40ad814517dfcfb#npm:6.1.2", {\
"packageLocation": "./.yarn/__virtual__/@newrelic-koa-virtual-2df93240b5/0/cache/@newrelic-koa-npm-6.1.2-df0f7c71b5-e269d37b13.zip/node_modules/@newrelic/koa/",\
["virtual:65e8703d5df08d5ff3f7296fcb759f276254ee430ae6f3cc1f03da392ff63066a3c6c59591c38f36f19d3e877285287a834b5c55e22d764dc2cb0938f7128707#npm:7.0.0", {\
"packageLocation": "./.yarn/__virtual__/@newrelic-koa-virtual-d6376894e6/0/cache/@newrelic-koa-npm-7.0.0-903c251b9f-13fb2bf0df.zip/node_modules/@newrelic/koa/",\
"packageDependencies": [\
["@newrelic/koa", "virtual:b659d4d19ce37732618223b586685dc7b27d11c52dca80d8411fdda38a4e6a7967776a5d7bdcce0be779cd6a41e2d84f8bbd3d3b5c06ce5ae40ad814517dfcfb#npm:6.1.2"],\
["@newrelic/koa", "virtual:65e8703d5df08d5ff3f7296fcb759f276254ee430ae6f3cc1f03da392ff63066a3c6c59591c38f36f19d3e877285287a834b5c55e22d764dc2cb0938f7128707#npm:7.0.0"],\
["@types/newrelic", null],\
["newrelic", "npm:8.14.1"]\
["newrelic", "npm:9.0.0"]\
],\
"packagePeers": [\
"@types/newrelic",\
@@ -1833,10 +1832,10 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
}]\
]],\
["@newrelic/native-metrics", [\
["npm:8.0.0", {\
"packageLocation": "./.yarn/unplugged/@newrelic-native-metrics-npm-8.0.0-c254393d6e/node_modules/@newrelic/native-metrics/",\
["npm:9.0.0", {\
"packageLocation": "./.yarn/unplugged/@newrelic-native-metrics-npm-9.0.0-590d2e713a/node_modules/@newrelic/native-metrics/",\
"packageDependencies": [\
["@newrelic/native-metrics", "npm:8.0.0"],\
["@newrelic/native-metrics", "npm:9.0.0"],\
["https-proxy-agent", "npm:5.0.1"],\
["nan", "npm:2.16.0"],\
["node-gyp", "npm:9.0.0"],\
@@ -1846,19 +1845,19 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
}]\
]],\
["@newrelic/superagent", [\
["npm:5.1.1", {\
"packageLocation": "./.yarn/cache/@newrelic-superagent-npm-5.1.1-0d3c1fccf8-b43f7b9bb6.zip/node_modules/@newrelic/superagent/",\
["npm:6.0.0", {\
"packageLocation": "./.yarn/cache/@newrelic-superagent-npm-6.0.0-db8b77d0f3-d9e9b20d75.zip/node_modules/@newrelic/superagent/",\
"packageDependencies": [\
["@newrelic/superagent", "npm:5.1.1"]\
["@newrelic/superagent", "npm:6.0.0"]\
],\
"linkType": "SOFT"\
}],\
["virtual:b659d4d19ce37732618223b586685dc7b27d11c52dca80d8411fdda38a4e6a7967776a5d7bdcce0be779cd6a41e2d84f8bbd3d3b5c06ce5ae40ad814517dfcfb#npm:5.1.1", {\
"packageLocation": "./.yarn/__virtual__/@newrelic-superagent-virtual-d7de4099d8/0/cache/@newrelic-superagent-npm-5.1.1-0d3c1fccf8-b43f7b9bb6.zip/node_modules/@newrelic/superagent/",\
["virtual:65e8703d5df08d5ff3f7296fcb759f276254ee430ae6f3cc1f03da392ff63066a3c6c59591c38f36f19d3e877285287a834b5c55e22d764dc2cb0938f7128707#npm:6.0.0", {\
"packageLocation": "./.yarn/__virtual__/@newrelic-superagent-virtual-c2a5d7b8a8/0/cache/@newrelic-superagent-npm-6.0.0-db8b77d0f3-d9e9b20d75.zip/node_modules/@newrelic/superagent/",\
"packageDependencies": [\
["@newrelic/superagent", "virtual:b659d4d19ce37732618223b586685dc7b27d11c52dca80d8411fdda38a4e6a7967776a5d7bdcce0be779cd6a41e2d84f8bbd3d3b5c06ce5ae40ad814517dfcfb#npm:5.1.1"],\
["@newrelic/superagent", "virtual:65e8703d5df08d5ff3f7296fcb759f276254ee430ae6f3cc1f03da392ff63066a3c6c59591c38f36f19d3e877285287a834b5c55e22d764dc2cb0938f7128707#npm:6.0.0"],\
["@types/newrelic", null],\
["newrelic", "npm:8.14.1"]\
["newrelic", "npm:9.0.0"]\
],\
"packagePeers": [\
"@types/newrelic",\
@@ -1880,7 +1879,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
"packageDependencies": [\
["@newrelic/winston-enricher", "virtual:04783e12400851b8a3d76e71495851cc94959db6e62f04cb0a31190080629440b182d8c8eb4d7f2b04e281912f2783a5fd4d2c3c6ab68d38b7097246c93f4c19#npm:4.0.0"],\
["@types/newrelic", "npm:7.0.3"],\
["newrelic", "npm:8.14.1"]\
["newrelic", "npm:9.0.0"]\
],\
"packagePeers": [\
"@types/newrelic",\
@@ -2532,7 +2531,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
["ioredis", "npm:5.2.0"],\
["jest", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:28.1.2"],\
["jsonwebtoken", "npm:8.5.1"],\
["newrelic", "npm:8.14.1"],\
["newrelic", "npm:9.0.0"],\
["nodemon", "npm:2.0.19"],\
["npm-check-updates", "npm:16.0.1"],\
["prettyjson", "npm:1.2.5"],\
@@ -2599,7 +2598,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
["ioredis", "npm:5.2.0"],\
["jest", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:28.1.2"],\
["mysql2", "npm:2.3.3"],\
["newrelic", "npm:8.14.1"],\
["newrelic", "npm:9.0.0"],\
["nodemon", "npm:2.0.19"],\
["npm-check-updates", "npm:16.0.1"],\
["otplib", "npm:12.0.1"],\
@@ -2677,7 +2676,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
["eslint-plugin-prettier", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:4.2.1"],\
["ioredis", "npm:5.2.0"],\
["jest", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:28.1.2"],\
["newrelic", "npm:8.14.1"],\
["newrelic", "npm:9.0.0"],\
["reflect-metadata", "npm:0.1.13"],\
["sqs-consumer", "virtual:685a6222c3349423674bb7f0684ba34e2ab20912010f352e04dcf707a156e13183fc382e2417cb37a60f3e7b52fd0178c53181674890e1773eb83e190dc13378#npm:5.7.0"],\
["ts-jest", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:28.0.5"],\
@@ -2723,7 +2722,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
["ioredis", "npm:5.2.0"],\
["jest", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:28.1.2"],\
["mysql2", "npm:2.3.3"],\
["newrelic", "npm:8.14.1"],\
["newrelic", "npm:9.0.0"],\
["reflect-metadata", "npm:0.1.13"],\
["ts-jest", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:28.0.5"],\
["typeorm", "virtual:31b5a94a105c89c9294c3d524a7f8929fe63ee5a2efadf21951ca4c0cfd2ecf02e8f4ef5a066bbda091f1e3a56e57c6749069a080618c96b22e51131a330fc4a#npm:0.3.7"],\
@@ -2783,7 +2782,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
["ioredis", "npm:5.2.0"],\
["jest", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:28.1.2"],\
["jsonwebtoken", "npm:8.5.1"],\
["newrelic", "npm:8.14.1"],\
["newrelic", "npm:9.0.0"],\
["nodemon", "npm:2.0.19"],\
["npm-check-updates", "npm:16.0.1"],\
["prettyjson", "npm:1.2.5"],\
@@ -2876,7 +2875,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
["ioredis", "npm:5.2.0"],\
["jest", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:28.1.2"],\
["mysql2", "npm:2.3.3"],\
["newrelic", "npm:8.14.1"],\
["newrelic", "npm:9.0.0"],\
["npm-check-updates", "npm:16.0.1"],\
["reflect-metadata", "npm:0.1.13"],\
["ts-jest", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:28.0.5"],\
@@ -2922,7 +2921,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
["eslint", "npm:8.19.0"],\
["eslint-config-prettier", "virtual:8859b278716fedf3e7458b5628625f7e35678c418626878559a0b816445001b7e24c55546f4677ba4c20b521aa0cf52cc33ac07deff171e383ada6eeab69933f#npm:8.5.0"],\
["ini", "npm:3.0.0"],\
["newrelic", "npm:8.14.1"],\
["newrelic", "npm:9.0.0"],\
["npm-check-updates", "npm:16.0.1"],\
["prettier", "npm:2.7.1"],\
["ts-node", "virtual:c0eab07e71af57f5501e97e7ca7a2a4f4965035bd2455ad124a8b09fa55780657c55fe3df41019fa6c2c44487c897668c842a0939e380b3c1f13b3756d128543#npm:10.8.2"],\
@@ -3028,7 +3027,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
["jest", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:28.1.2"],\
["jsonwebtoken", "npm:8.5.1"],\
["mysql2", "npm:2.3.3"],\
["newrelic", "npm:8.14.1"],\
["newrelic", "npm:9.0.0"],\
["nodemon", "npm:2.0.19"],\
["npm-check-updates", "npm:16.0.1"],\
["prettyjson", "npm:1.2.5"],\
@@ -9540,16 +9539,16 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
}]\
]],\
["newrelic", [\
["npm:8.14.1", {\
"packageLocation": "./.yarn/cache/newrelic-npm-8.14.1-b659d4d19c-cd12bb2ac9.zip/node_modules/newrelic/",\
["npm:9.0.0", {\
"packageLocation": "./.yarn/cache/newrelic-npm-9.0.0-65e8703d5d-e92af072fd.zip/node_modules/newrelic/",\
"packageDependencies": [\
["newrelic", "npm:8.14.1"],\
["newrelic", "npm:9.0.0"],\
["@grpc/grpc-js", "npm:1.6.7"],\
["@grpc/proto-loader", "npm:0.6.13"],\
["@newrelic/aws-sdk", "virtual:b659d4d19ce37732618223b586685dc7b27d11c52dca80d8411fdda38a4e6a7967776a5d7bdcce0be779cd6a41e2d84f8bbd3d3b5c06ce5ae40ad814517dfcfb#npm:4.1.2"],\
["@newrelic/koa", "virtual:b659d4d19ce37732618223b586685dc7b27d11c52dca80d8411fdda38a4e6a7967776a5d7bdcce0be779cd6a41e2d84f8bbd3d3b5c06ce5ae40ad814517dfcfb#npm:6.1.2"],\
["@newrelic/native-metrics", "npm:8.0.0"],\
["@newrelic/superagent", "virtual:b659d4d19ce37732618223b586685dc7b27d11c52dca80d8411fdda38a4e6a7967776a5d7bdcce0be779cd6a41e2d84f8bbd3d3b5c06ce5ae40ad814517dfcfb#npm:5.1.1"],\
["@newrelic/aws-sdk", "virtual:65e8703d5df08d5ff3f7296fcb759f276254ee430ae6f3cc1f03da392ff63066a3c6c59591c38f36f19d3e877285287a834b5c55e22d764dc2cb0938f7128707#npm:5.0.0"],\
["@newrelic/koa", "virtual:65e8703d5df08d5ff3f7296fcb759f276254ee430ae6f3cc1f03da392ff63066a3c6c59591c38f36f19d3e877285287a834b5c55e22d764dc2cb0938f7128707#npm:7.0.0"],\
["@newrelic/native-metrics", "npm:9.0.0"],\
["@newrelic/superagent", "virtual:65e8703d5df08d5ff3f7296fcb759f276254ee430ae6f3cc1f03da392ff63066a3c6c59591c38f36f19d3e877285287a834b5c55e22d764dc2cb0938f7128707#npm:6.0.0"],\
["@tyriar/fibonacci-heap", "npm:2.0.9"],\
["async", "npm:3.2.4"],\
["concat-stream", "npm:2.0.0"],\

Binary file not shown.

View File

@@ -1,3 +1,17 @@
### v9.0.0 (2022-08-01)
* **BREAKING** Removed support for Node 12.
The minimum supported version is now Node v14. For further information on our support policy, see: https://docs.newrelic.com/docs/agents/nodejs-agent/getting-started/compatibility-requirements-nodejs-agent.
* Added support for Node 18.
* Fixed GC binder to properly record Garbage Collection metrics in Node 18.
* Resolved several dev-dependency audit warnings.
* Bumped minimum `nan` version to ^2.16.0.
### v8.0.0 (2022-03-22)
* **BREAKING** Removed RUSageMeter. This was used to get resource usage statistics via `libuv`. It is no longer needed since Node.js version 12 has support via `process.cpuUsage`.

View File

@@ -59,16 +59,12 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
### nan
This product includes source derived from [nan](https://github.com/nodejs/nan) ([v2.15.0](https://github.com/nodejs/nan/tree/v2.15.0)), distributed under the [MIT License](https://github.com/nodejs/nan/blob/v2.15.0/LICENSE.md):
This product includes source derived from [nan](https://github.com/nodejs/nan) ([v2.16.0](https://github.com/nodejs/nan/tree/v2.16.0)), distributed under the [MIT License](https://github.com/nodejs/nan/blob/v2.16.0/LICENSE.md):
```
The MIT License (MIT)
=====================
Copyright (c) 2018 NAN contributors
-----------------------------------
*NAN contributors listed at <https://github.com/nodejs/nan#contributors>*
Copyright (c) 2018 [NAN contributors](<https://github.com/nodejs/nan#wg-members--collaborators>)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
@@ -106,7 +102,7 @@ IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
### @newrelic/eslint-config
This product includes source derived from [@newrelic/eslint-config](https://github.com/newrelic/eslint-config-newrelic) ([v0.0.2](https://github.com/newrelic/eslint-config-newrelic/tree/v0.0.2)), distributed under the [Apache-2.0 License](https://github.com/newrelic/eslint-config-newrelic/blob/v0.0.2/LICENSE):
This product includes source derived from [@newrelic/eslint-config](https://github.com/newrelic/eslint-config-newrelic) ([v0.0.4](https://github.com/newrelic/eslint-config-newrelic/tree/v0.0.4)), distributed under the [Apache-2.0 License](https://github.com/newrelic/eslint-config-newrelic/blob/v0.0.4/LICENSE):
```
Apache License
@@ -539,7 +535,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
### async
This product includes source derived from [async](https://github.com/caolan/async) ([v3.2.0](https://github.com/caolan/async/tree/v3.2.0)), distributed under the [MIT License](https://github.com/caolan/async/blob/v3.2.0/LICENSE):
This product includes source derived from [async](https://github.com/caolan/async) ([v3.2.3](https://github.com/caolan/async/tree/v3.2.3)), distributed under the [MIT License](https://github.com/caolan/async/blob/v3.2.3/LICENSE):
```
Copyright (c) 2010-2018 Caolan McMahon
@@ -1081,12 +1077,12 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
### tap
This product includes source derived from [tap](https://github.com/tapjs/node-tap) ([v15.0.9](https://github.com/tapjs/node-tap/tree/v15.0.9)), distributed under the [ISC License](https://github.com/tapjs/node-tap/blob/v15.0.9/LICENSE):
This product includes source derived from [tap](https://github.com/tapjs/node-tap) ([v16.0.1](https://github.com/tapjs/node-tap/tree/v16.0.1)), distributed under the [ISC License](https://github.com/tapjs/node-tap/blob/v16.0.1/LICENSE):
```
The ISC License
Copyright (c) Isaac Z. Schlueter and Contributors
Copyright (c) 2011-2022 Isaac Z. Schlueter and Contributors
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above

View File

@@ -326,8 +326,8 @@ ifeq ($(strip $(foreach prefix,$(NO_LOAD),\
endif
quiet_cmd_regen_makefile = ACTION Regenerating $@
cmd_regen_makefile = cd $(srcdir); /Users/karolsojko/workspace/server/.yarn/unplugged/node-gyp-npm-9.0.0-0eccfca4d1/node_modules/node-gyp/gyp/gyp_main.py -fmake --ignore-environment "-Dlibrary=shared_library" "-Dvisibility=default" "-Dnode_root_dir=/Users/karolsojko/Library/Caches/node-gyp/16.15.1" "-Dnode_gyp_dir=/Users/karolsojko/workspace/server/.yarn/unplugged/node-gyp-npm-9.0.0-0eccfca4d1/node_modules/node-gyp" "-Dnode_lib_file=/Users/karolsojko/Library/Caches/node-gyp/16.15.1/<(target_arch)/node.lib" "-Dmodule_root_dir=/Users/karolsojko/workspace/server/.yarn/unplugged/@newrelic-native-metrics-npm-8.0.0-c254393d6e/node_modules/@newrelic/native-metrics" "-Dnode_engine=v8" "--depth=." "-Goutput_dir=." "--generator-output=build" -I/Users/karolsojko/workspace/server/.yarn/unplugged/@newrelic-native-metrics-npm-8.0.0-c254393d6e/node_modules/@newrelic/native-metrics/build/config.gypi -I/Users/karolsojko/workspace/server/.yarn/unplugged/node-gyp-npm-9.0.0-0eccfca4d1/node_modules/node-gyp/addon.gypi -I/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/common.gypi "--toplevel-dir=." binding.gyp
Makefile: $(srcdir)/../../../../node-gyp-npm-9.0.0-0eccfca4d1/node_modules/node-gyp/addon.gypi $(srcdir)/../../../../../../../../Library/Caches/node-gyp/16.15.1/include/node/common.gypi $(srcdir)/binding.gyp $(srcdir)/build/config.gypi
cmd_regen_makefile = cd $(srcdir); /Users/karolsojko/workspace/server/.yarn/unplugged/node-gyp-npm-9.0.0-0eccfca4d1/node_modules/node-gyp/gyp/gyp_main.py -fmake --ignore-environment "-Dlibrary=shared_library" "-Dvisibility=default" "-Dnode_root_dir=/Users/karolsojko/Library/Caches/node-gyp/16.15.1" "-Dnode_gyp_dir=/Users/karolsojko/workspace/server/.yarn/unplugged/node-gyp-npm-9.0.0-0eccfca4d1/node_modules/node-gyp" "-Dnode_lib_file=/Users/karolsojko/Library/Caches/node-gyp/16.15.1/<(target_arch)/node.lib" "-Dmodule_root_dir=/Users/karolsojko/workspace/server/.yarn/unplugged/@newrelic-native-metrics-npm-9.0.0-590d2e713a/node_modules/@newrelic/native-metrics" "-Dnode_engine=v8" "--depth=." "-Goutput_dir=." "--generator-output=build" -I/Users/karolsojko/workspace/server/.yarn/unplugged/@newrelic-native-metrics-npm-9.0.0-590d2e713a/node_modules/@newrelic/native-metrics/build/config.gypi -I/Users/karolsojko/workspace/server/.yarn/unplugged/node-gyp-npm-9.0.0-0eccfca4d1/node_modules/node-gyp/addon.gypi -I/Users/karolsojko/Library/Caches/node-gyp/16.15.1/include/node/common.gypi "--toplevel-dir=." binding.gyp
Makefile: $(srcdir)/binding.gyp $(srcdir)/../../../../../../../../Library/Caches/node-gyp/16.15.1/include/node/common.gypi $(srcdir)/build/config.gypi $(srcdir)/../../../../node-gyp-npm-9.0.0-0eccfca4d1/node_modules/node-gyp/addon.gypi
$(call do_cmd,regen_makefile)
# "all" is a concatenation of the "all" targets from all the included

View File

@@ -5,20 +5,36 @@
'use strict'
var EventEmitter = require('events').EventEmitter
var util = require('util')
var preBuild = require('./lib/pre-build')
var natives = preBuild.load('native_metrics')
const EventEmitter = require('events').EventEmitter
const util = require('util')
const preBuild = require('./lib/pre-build')
const natives = preBuild.load('native_metrics')
const semver = require('semver')
var DEFAULT_TIMEOUT = 15 * 1000 // 15 seconds
var GC_TYPE_NAMES = {
1: 'Scavenge',
2: 'MarkSweepCompact',
4: 'IncrementalMarking',
8: 'ProcessWeakCallbacks',
const DEFAULT_TIMEOUT = 15 * 1000 // 15 seconds
let GC_TYPE_NAMES = null
3: 'All', // Node v4 and earlier only have Scavenge and MarkSweepCompact.
15: 'All'
// In Node 18(v8 10) the GCType enum added `MinorMarkCompact`
// we have to update our mapping to properly account for this
if (semver.satisfies(process.version, '>=18')) {
GC_TYPE_NAMES = {
1: 'Scavenge',
2: 'MinorMarkCompact',
4: 'MarkSweepCompact',
8: 'IncrementalMarking',
16: 'ProcessWeakCallbacks',
31: 'All'
}
} else {
GC_TYPE_NAMES = {
1: 'Scavenge',
2: 'MarkSweepCompact',
4: 'IncrementalMarking',
8: 'ProcessWeakCallbacks',
3: 'All', // Node v4 and earlier only have Scavenge and MarkSweepCompact.
15: 'All'
}
}
/**
@@ -135,11 +151,11 @@ NativeMetricEmitter.prototype.getLoopMetrics = function getLoopMetrics() {
* information on the GC events that happened.
*/
NativeMetricEmitter.prototype.getGCMetrics = function getGCMetrics() {
var gcMetrics = this._gcBinder.read()
var results = Object.create(null)
for (var typeId in gcMetrics) {
const gcMetrics = this._gcBinder.read()
const results = Object.create(null)
for (const typeId in gcMetrics) {
if (gcMetrics.hasOwnProperty(typeId) && gcMetrics[typeId].count > 0) {
var typeName = GC_TYPE_NAMES[String(typeId)]
const typeName = GC_TYPE_NAMES[String(typeId)]
results[typeName] = {
typeId: parseInt(typeId, 10),
type: typeName,
@@ -151,7 +167,7 @@ NativeMetricEmitter.prototype.getGCMetrics = function getGCMetrics() {
return results
}
var emitter = null
let emitter = null
/**
* Retrieves the {@link NativeMetricEmitter} singleton instance.

View File

@@ -237,8 +237,8 @@ function download(target, cb) {
return cb(new Error('Failed to download ' + url + ': code ' + res.statusCode))
}
var unzip = zlib.createGunzip()
var buffers = []
const unzip = zlib.createGunzip()
const buffers = []
let size = 0
res.pipe(unzip).on('data', function onResData(data) {
buffers.push(data)

View File

@@ -1,6 +1,6 @@
{
"name": "@newrelic/native-metrics",
"version": "8.0.0",
"version": "9.0.0",
"description": "A module for generating metrics from V8.",
"main": "index.js",
"scripts": {
@@ -73,14 +73,14 @@
],
"license": "Apache-2.0",
"engines": {
"node": ">=12",
"node": ">=14",
"npm": ">=6"
},
"devDependencies": {
"@newrelic/eslint-config": "^0.0.2",
"@newrelic/eslint-config": "^0.0.4",
"@newrelic/newrelic-oss-cli": "^0.1.2",
"@newrelic/proxy": "^2.0.0",
"async": "^3.2.0",
"async": "^3.2.2",
"aws-sdk": "^2.266.1",
"eslint": "^7.32.0",
"eslint-config-prettier": "^8.3.0",
@@ -93,11 +93,11 @@
"prettier": "^2.3.2",
"segfault-handler": "^1.3.0",
"sinon": "^11.1.2",
"tap": "^15.0.9"
"tap": "^16.0.1"
},
"dependencies": {
"https-proxy-agent": "^5.0.0",
"nan": "^2.15.0",
"nan": "^2.16.0",
"semver": "^5.5.1"
},
"files": [

View File

@@ -66,6 +66,6 @@
"packageManager": "yarn@3.2.1",
"dependencies": {
"@sentry/node": "^7.3.0",
"newrelic": "^8.14.1"
"newrelic": "^9.0.0"
}
}

View File

@@ -3,6 +3,52 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.15.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@1.14.0...@standardnotes/analytics@1.15.0) (2022-08-11)
### Features
* add analytics for subscription cancelling, refunding and account deletion ([1607638](https://github.com/standardnotes/server/commit/16076382bae74552a35901bb5474e2c2c2d96f43))
# [1.14.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@1.13.0...@standardnotes/analytics@1.14.0) (2022-08-09)
### Features
* **auth:** add subscription events to analytics ([24e439f](https://github.com/standardnotes/server/commit/24e439f017df23d0158940848c10e0b3398720b2))
# [1.13.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@1.12.2...@standardnotes/analytics@1.13.0) (2022-08-09)
### Features
* **auth:** track registration in analytics ([f25195b](https://github.com/standardnotes/server/commit/f25195b2c156fa03ca5806ef568c4195da7b688a))
## [1.12.2](https://github.com/standardnotes/server/compare/@standardnotes/analytics@1.12.1...@standardnotes/analytics@1.12.2) (2022-08-09)
### Bug Fixes
* **analytics:** replace AND to OR operation on bitop ([572ea3f](https://github.com/standardnotes/server/commit/572ea3febe136518a33154937cf39347adf040ff))
## [1.12.1](https://github.com/standardnotes/server/compare/@standardnotes/analytics@1.12.0...@standardnotes/analytics@1.12.1) (2022-08-09)
### Bug Fixes
* **analytics:** bitop over analytics time ([0ffec66](https://github.com/standardnotes/server/commit/0ffec66bea480fe9cec55415d90b608fddc26a84))
# [1.12.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@1.11.0...@standardnotes/analytics@1.12.0) (2022-08-09)
### Features
* add total count of analytics over time ([0b9524e](https://github.com/standardnotes/server/commit/0b9524eb26c39aabe8ad0f9cdbb3aaca63a65b0e))
# [1.11.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@1.10.0...@standardnotes/analytics@1.11.0) (2022-08-08)
### Bug Fixes
* **analytics:** format of changes over time to total count ([31c849c](https://github.com/standardnotes/server/commit/31c849cc2d13bc97690da1cbec3d1868ddb733dc))
### Features
* **analytics:** add calculating analytics over time ([977757d](https://github.com/standardnotes/server/commit/977757d346ba94794cc090f13d4dfdf489eb9d3a))
# [1.10.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@1.9.0...@standardnotes/analytics@1.10.0) (2022-08-08)
### Features

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/analytics",
"version": "1.10.0",
"version": "1.15.0",
"engines": {
"node": ">=14.0.0 <17.0.0"
},

View File

@@ -2,6 +2,12 @@ export enum AnalyticsActivity {
GeneralActivity = 'general-activity',
EditingItems = 'editing-items',
Login = 'login',
Register = 'register',
DeleteAccount = 'DeleteAccount',
SubscriptionPurchased = 'subscription-purchased',
SubscriptionRenewed = 'subscription-renewed',
SubscriptionRefunded = 'subscription-refunded',
SubscriptionCancelled = 'subscription-cancelled',
EmailUnbackedUpData = 'email-unbacked-up-data',
EmailBackup = 'email-backup',
LimitedDiscountOfferPurchased = 'limited-discount-offer-purchased',

View File

@@ -7,4 +7,9 @@ export interface AnalyticsStoreInterface {
wasActivityDone(activity: AnalyticsActivity, analyticsId: number, period: Period): Promise<boolean>
calculateActivityRetention(activity: AnalyticsActivity, firstPeriod: Period, secondPeriod: Period): Promise<number>
calculateActivityTotalCount(activity: AnalyticsActivity, period: Period): Promise<number>
calculateActivityChangesTotalCount(
activity: AnalyticsActivity,
period: Period,
): Promise<Array<{ periodKey: string; totalCount: number }>>
calculateActivityTotalCountOverTime(activity: AnalyticsActivity, period: Period): Promise<number>
}

View File

@@ -7,4 +7,5 @@ export enum Period {
WeekBeforeLastWeek,
ThisMonth,
LastMonth,
Last30Days,
}

View File

@@ -5,7 +5,7 @@ describe('PeriodKeyGenerator', () => {
const createGenerator = () => new PeriodKeyGenerator()
beforeEach(() => {
jest.useFakeTimers('modern')
jest.useFakeTimers()
jest.setSystemTime(1653395155000)
})
@@ -13,6 +13,41 @@ describe('PeriodKeyGenerator', () => {
jest.useRealTimers()
})
it('should generate period keys for last 30 days', () => {
expect(createGenerator().getDiscretePeriodKeys(Period.Last30Days)).toEqual([
'2022-4-24',
'2022-4-25',
'2022-4-26',
'2022-4-27',
'2022-4-28',
'2022-4-29',
'2022-4-30',
'2022-5-1',
'2022-5-2',
'2022-5-3',
'2022-5-4',
'2022-5-5',
'2022-5-6',
'2022-5-7',
'2022-5-8',
'2022-5-9',
'2022-5-10',
'2022-5-11',
'2022-5-12',
'2022-5-13',
'2022-5-14',
'2022-5-15',
'2022-5-16',
'2022-5-17',
'2022-5-18',
'2022-5-19',
'2022-5-20',
'2022-5-21',
'2022-5-22',
'2022-5-23',
])
})
it('should generate a period key for today', () => {
expect(createGenerator().getPeriodKey(Period.Today)).toEqual('2022-5-24')
})
@@ -55,4 +90,15 @@ describe('PeriodKeyGenerator', () => {
expect(error).not.toBeNull()
})
it('should throw error on unsupported period for discrete generation', () => {
let error = null
try {
createGenerator().getDiscretePeriodKeys(Period.Today)
} catch (caughtError) {
error = caughtError
}
expect(error).not.toBeNull()
})
})

View File

@@ -2,6 +2,21 @@ import { Period } from './Period'
import { PeriodKeyGeneratorInterface } from './PeriodKeyGeneratorInterface'
export class PeriodKeyGenerator implements PeriodKeyGeneratorInterface {
getDiscretePeriodKeys(period: Period): string[] {
const periodKeys = []
switch (period) {
case Period.Last30Days:
for (let i = 1; i <= 30; i++) {
periodKeys.unshift(this.getDailyKey(this.getDateNDaysBefore(i)))
}
return periodKeys
default:
throw new Error(`Unsuporrted period: ${period}`)
}
}
getPeriodKey(period: Period): string {
switch (period) {
case Period.Today:
@@ -61,11 +76,15 @@ export class PeriodKeyGenerator implements PeriodKeyGeneratorInterface {
return date.getDate().toString()
}
private getYesterdayDate(): Date {
const yesterday = new Date()
yesterday.setDate(new Date().getDate() - 1)
private getDateNDaysBefore(n: number) {
const date = new Date()
date.setDate(new Date().getDate() - n)
return yesterday
return date
}
private getYesterdayDate(): Date {
return this.getDateNDaysBefore(1)
}
private getDayBeforeYesterdayDate(): Date {

View File

@@ -2,4 +2,5 @@ import { Period } from './Period'
export interface PeriodKeyGeneratorInterface {
getPeriodKey(period: Period): string
getDiscretePeriodKeys(period: Period): string[]
}

View File

@@ -29,6 +29,67 @@ describe('RedisAnalyticsStore', () => {
periodKeyGenerator.getPeriodKey = jest.fn().mockReturnValue('period-key')
})
it('should calculate total count over time of activities', async () => {
redisClient.bitcount = jest.fn().mockReturnValue(70)
periodKeyGenerator.getDiscretePeriodKeys = jest.fn().mockReturnValue(['2022-4-24', '2022-4-25', '2022-4-26'])
await createStore().calculateActivityTotalCountOverTime(AnalyticsActivity.EditingItems, Period.Last30Days)
expect(redisClient.bitop).toHaveBeenCalledTimes(1)
expect(redisClient.bitop).toHaveBeenNthCalledWith(
1,
'OR',
'bitmap:action:editing-items:timespan:2022-4-24-2022-4-26',
'bitmap:action:editing-items:timespan:2022-4-24',
'bitmap:action:editing-items:timespan:2022-4-25',
'bitmap:action:editing-items:timespan:2022-4-26',
)
expect(redisClient.bitcount).toHaveBeenCalledWith('bitmap:action:editing-items:timespan:2022-4-24-2022-4-26')
})
it('should calculate total count changes of activities', async () => {
periodKeyGenerator.getDiscretePeriodKeys = jest.fn().mockReturnValue(['2022-4-24', '2022-4-25', '2022-4-26'])
redisClient.bitcount = jest.fn().mockReturnValueOnce(70).mockReturnValueOnce(71).mockReturnValueOnce(72)
expect(
await createStore().calculateActivityChangesTotalCount(AnalyticsActivity.EditingItems, Period.Last30Days),
).toEqual([
{
periodKey: '2022-4-24',
totalCount: 70,
},
{
periodKey: '2022-4-25',
totalCount: 71,
},
{
periodKey: '2022-4-26',
totalCount: 72,
},
])
expect(redisClient.bitcount).toHaveBeenNthCalledWith(1, 'bitmap:action:editing-items:timespan:2022-4-24')
expect(redisClient.bitcount).toHaveBeenNthCalledWith(2, 'bitmap:action:editing-items:timespan:2022-4-25')
expect(redisClient.bitcount).toHaveBeenNthCalledWith(3, 'bitmap:action:editing-items:timespan:2022-4-26')
})
it('should throw error on calculating total count changes of activities on unsupported period', async () => {
periodKeyGenerator.getDiscretePeriodKeys = jest.fn().mockReturnValue(['2022-4-24', '2022-4-25', '2022-4-26'])
redisClient.bitcount = jest.fn().mockReturnValueOnce(70).mockReturnValueOnce(71).mockReturnValueOnce(72)
let caughtError = null
try {
await createStore().calculateActivityChangesTotalCount(AnalyticsActivity.EditingItems, Period.LastWeek)
} catch (error) {
caughtError = error
}
expect(caughtError).not.toBeNull()
})
it('should calculate total count of activities', async () => {
redisClient.bitcount = jest.fn().mockReturnValue(70)

View File

@@ -8,6 +8,43 @@ import { AnalyticsStoreInterface } from '../../Domain/Analytics/AnalyticsStoreIn
export class RedisAnalyticsStore implements AnalyticsStoreInterface {
constructor(private periodKeyGenerator: PeriodKeyGeneratorInterface, private redisClient: IORedis.Redis) {}
async calculateActivityTotalCountOverTime(activity: AnalyticsActivity, period: Period): Promise<number> {
if (period !== Period.Last30Days) {
throw new Error(`Unsuporrted period: ${period}`)
}
const periodKeys = this.periodKeyGenerator.getDiscretePeriodKeys(Period.Last30Days)
await this.redisClient.bitop(
'OR',
`bitmap:action:${activity}:timespan:${periodKeys[0]}-${periodKeys[periodKeys.length - 1]}`,
...periodKeys.map((p) => `bitmap:action:${activity}:timespan:${p}`),
)
return this.redisClient.bitcount(
`bitmap:action:${activity}:timespan:${periodKeys[0]}-${periodKeys[periodKeys.length - 1]}`,
)
}
async calculateActivityChangesTotalCount(
activity: AnalyticsActivity,
period: Period,
): Promise<Array<{ periodKey: string; totalCount: number }>> {
if (period !== Period.Last30Days) {
throw new Error(`Unsuporrted period: ${period}`)
}
const periodKeys = this.periodKeyGenerator.getDiscretePeriodKeys(Period.Last30Days)
const counts = []
for (const periodKey of periodKeys) {
counts.push({
periodKey,
totalCount: await this.redisClient.bitcount(`bitmap:action:${activity}:timespan:${periodKey}`),
})
}
return counts
}
async markActivity(activities: AnalyticsActivity[], analyticsId: number, periods: Period[]): Promise<void> {
const pipeline = this.redisClient.pipeline()

View File

@@ -21,7 +21,6 @@ describe('RedisStatisticsStore', () => {
redisClient.incr = jest.fn()
redisClient.setbit = jest.fn()
redisClient.getbit = jest.fn().mockReturnValue(1)
redisClient.send_command = jest.fn()
periodKeyGenerator = {} as jest.Mocked<PeriodKeyGeneratorInterface>
periodKeyGenerator.getPeriodKey = jest.fn().mockReturnValue('period-key')

View File

@@ -3,6 +3,62 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.13.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.12.0...@standardnotes/api-gateway@1.13.0) (2022-08-11)
### Features
* add analytics for subscription cancelling, refunding and account deletion ([1607638](https://github.com/standardnotes/api-gateway/commit/16076382bae74552a35901bb5474e2c2c2d96f43))
# [1.12.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.11.5...@standardnotes/api-gateway@1.12.0) (2022-08-10)
### Features
* **api-gateway:** add publishing subscription purchased, renewed and registration analytics ([dea5fd7](https://github.com/standardnotes/api-gateway/commit/dea5fd717d222d96bcbbd16a8d84a84ed20144a8))
## [1.11.5](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.11.4...@standardnotes/api-gateway@1.11.5) (2022-08-10)
**Note:** Version bump only for package @standardnotes/api-gateway
## [1.11.4](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.11.3...@standardnotes/api-gateway@1.11.4) (2022-08-09)
**Note:** Version bump only for package @standardnotes/api-gateway
## [1.11.3](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.11.2...@standardnotes/api-gateway@1.11.3) (2022-08-09)
**Note:** Version bump only for package @standardnotes/api-gateway
## [1.11.2](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.11.1...@standardnotes/api-gateway@1.11.2) (2022-08-09)
**Note:** Version bump only for package @standardnotes/api-gateway
## [1.11.1](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.11.0...@standardnotes/api-gateway@1.11.1) (2022-08-09)
**Note:** Version bump only for package @standardnotes/api-gateway
# [1.11.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.10.0...@standardnotes/api-gateway@1.11.0) (2022-08-09)
### Features
* add total count of analytics over time ([0b9524e](https://github.com/standardnotes/api-gateway/commit/0b9524eb26c39aabe8ad0f9cdbb3aaca63a65b0e))
# [1.10.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.9.1...@standardnotes/api-gateway@1.10.0) (2022-08-09)
### Features
* **api-gateway:** add editing items count over time ([b9225cd](https://github.com/standardnotes/api-gateway/commit/b9225cd9b6496301da2d8edc44c2a9861e03406b))
## [1.9.1](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.9.0...@standardnotes/api-gateway@1.9.1) (2022-08-08)
### Bug Fixes
* **api-gateway:** add general activity to calculating activity retention ([12e3a76](https://github.com/standardnotes/api-gateway/commit/12e3a768dd365198340ab4e2cd463e9392344e38))
# [1.9.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.8.0...@standardnotes/api-gateway@1.9.0) (2022-08-08)
### Features
* **api-gateway:** add analytics over time to daily report event ([845f08b](https://github.com/standardnotes/api-gateway/commit/845f08b060beda5dea69e16fbda132150de7d5f2))
# [1.8.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.7.4...@standardnotes/api-gateway@1.8.0) (2022-08-08)
### Features

View File

@@ -54,6 +54,116 @@ const requestReport = async (
Period.Yesterday,
),
},
{
name: AnalyticsActivity.GeneralActivity,
retention: await analyticsStore.calculateActivityRetention(
AnalyticsActivity.GeneralActivity,
Period.DayBeforeYesterday,
Period.Yesterday,
),
totalCount: await analyticsStore.calculateActivityTotalCount(
AnalyticsActivity.GeneralActivity,
Period.Yesterday,
),
},
],
activityStatisticsOverTime: [
{
name: AnalyticsActivity.GeneralActivity,
period: Period.Last30Days,
counts: await analyticsStore.calculateActivityChangesTotalCount(
AnalyticsActivity.GeneralActivity,
Period.Last30Days,
),
totalCount: await analyticsStore.calculateActivityTotalCountOverTime(
AnalyticsActivity.GeneralActivity,
Period.Last30Days,
),
},
{
name: AnalyticsActivity.EditingItems,
period: Period.Last30Days,
counts: await analyticsStore.calculateActivityChangesTotalCount(
AnalyticsActivity.EditingItems,
Period.Last30Days,
),
totalCount: await analyticsStore.calculateActivityTotalCountOverTime(
AnalyticsActivity.EditingItems,
Period.Last30Days,
),
},
{
name: AnalyticsActivity.SubscriptionPurchased,
period: Period.Last30Days,
counts: await analyticsStore.calculateActivityChangesTotalCount(
AnalyticsActivity.SubscriptionPurchased,
Period.Last30Days,
),
totalCount: await analyticsStore.calculateActivityTotalCountOverTime(
AnalyticsActivity.SubscriptionPurchased,
Period.Last30Days,
),
},
{
name: AnalyticsActivity.Register,
period: Period.Last30Days,
counts: await analyticsStore.calculateActivityChangesTotalCount(
AnalyticsActivity.Register,
Period.Last30Days,
),
totalCount: await analyticsStore.calculateActivityTotalCountOverTime(
AnalyticsActivity.Register,
Period.Last30Days,
),
},
{
name: AnalyticsActivity.SubscriptionRenewed,
period: Period.Last30Days,
counts: await analyticsStore.calculateActivityChangesTotalCount(
AnalyticsActivity.SubscriptionRenewed,
Period.Last30Days,
),
totalCount: await analyticsStore.calculateActivityTotalCountOverTime(
AnalyticsActivity.SubscriptionRenewed,
Period.Last30Days,
),
},
{
name: AnalyticsActivity.DeleteAccount,
period: Period.Last30Days,
counts: await analyticsStore.calculateActivityChangesTotalCount(
AnalyticsActivity.DeleteAccount,
Period.Last30Days,
),
totalCount: await analyticsStore.calculateActivityTotalCountOverTime(
AnalyticsActivity.DeleteAccount,
Period.Last30Days,
),
},
{
name: AnalyticsActivity.SubscriptionCancelled,
period: Period.Last30Days,
counts: await analyticsStore.calculateActivityChangesTotalCount(
AnalyticsActivity.SubscriptionCancelled,
Period.Last30Days,
),
totalCount: await analyticsStore.calculateActivityTotalCountOverTime(
AnalyticsActivity.SubscriptionCancelled,
Period.Last30Days,
),
},
{
name: AnalyticsActivity.SubscriptionRefunded,
period: Period.Last30Days,
counts: await analyticsStore.calculateActivityChangesTotalCount(
AnalyticsActivity.SubscriptionRefunded,
Period.Last30Days,
),
totalCount: await analyticsStore.calculateActivityTotalCountOverTime(
AnalyticsActivity.SubscriptionRefunded,
Period.Last30Days,
),
},
],
},
}

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/api-gateway",
"version": "1.8.0",
"version": "1.13.0",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
@@ -39,7 +39,7 @@
"inversify-express-utils": "^6.4.3",
"ioredis": "^5.2.0",
"jsonwebtoken": "8.5.1",
"newrelic": "^8.14.1",
"newrelic": "^9.0.0",
"prettyjson": "^1.2.5",
"reflect-metadata": "0.1.13",
"winston": "^3.8.1"

View File

@@ -3,6 +3,56 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.17.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.16.2...@standardnotes/auth-server@1.17.0) (2022-08-11)
### Features
* add analytics for subscription cancelling, refunding and account deletion ([1607638](https://github.com/standardnotes/server/commit/16076382bae74552a35901bb5474e2c2c2d96f43))
## [1.16.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.16.1...@standardnotes/auth-server@1.16.2) (2022-08-10)
**Note:** Version bump only for package @standardnotes/auth-server
## [1.16.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.16.0...@standardnotes/auth-server@1.16.1) (2022-08-10)
### Bug Fixes
* **auth:** docker entrypoing command for single user backup ([f67be9d](https://github.com/standardnotes/server/commit/f67be9d3ba56f67ad63cb067c6304766de3f3b69))
# [1.16.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.15.0...@standardnotes/auth-server@1.16.0) (2022-08-10)
### Features
* **auth:** add triggering email backup for a single user ([edd2545](https://github.com/standardnotes/server/commit/edd254531ddff6726aa4395193e489023358e422))
# [1.15.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.14.0...@standardnotes/auth-server@1.15.0) (2022-08-09)
### Features
* **auth:** add subscription events to analytics ([24e439f](https://github.com/standardnotes/server/commit/24e439f017df23d0158940848c10e0b3398720b2))
# [1.14.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.13.6...@standardnotes/auth-server@1.14.0) (2022-08-09)
### Features
* **auth:** track registration in analytics ([f25195b](https://github.com/standardnotes/server/commit/f25195b2c156fa03ca5806ef568c4195da7b688a))
## [1.13.6](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.13.5...@standardnotes/auth-server@1.13.6) (2022-08-09)
**Note:** Version bump only for package @standardnotes/auth-server
## [1.13.5](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.13.4...@standardnotes/auth-server@1.13.5) (2022-08-09)
**Note:** Version bump only for package @standardnotes/auth-server
## [1.13.4](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.13.3...@standardnotes/auth-server@1.13.4) (2022-08-09)
**Note:** Version bump only for package @standardnotes/auth-server
## [1.13.3](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.13.2...@standardnotes/auth-server@1.13.3) (2022-08-08)
**Note:** Version bump only for package @standardnotes/auth-server
## [1.13.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.13.1...@standardnotes/auth-server@1.13.2) (2022-08-08)
**Note:** Version bump only for package @standardnotes/auth-server

View File

@@ -0,0 +1,123 @@
import 'reflect-metadata'
import 'newrelic'
import { Logger } from 'winston'
import * as dayjs from 'dayjs'
import * as utc from 'dayjs/plugin/utc'
import { AnalyticsActivity, AnalyticsStoreInterface, Period } from '@standardnotes/analytics'
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, SettingName } from '@standardnotes/settings'
import { RoleServiceInterface } from '../src/Domain/Role/RoleServiceInterface'
import { PermissionName } from '@standardnotes/features'
import { AnalyticsEntityRepositoryInterface } from '../src/Domain/Analytics/AnalyticsEntityRepositoryInterface'
import { UserRepositoryInterface } from '../src/Domain/User/UserRepositoryInterface'
const inputArgs = process.argv.slice(2)
const backupEmail = inputArgs[0]
const requestBackups = async (
userRepository: UserRepositoryInterface,
settingRepository: SettingRepositoryInterface,
roleService: RoleServiceInterface,
domainEventFactory: DomainEventFactoryInterface,
domainEventPublisher: DomainEventPublisherInterface,
analyticsEntityRepository: AnalyticsEntityRepositoryInterface,
analyticsStore: AnalyticsStoreInterface,
): Promise<void> => {
const permissionName = PermissionName.DailyEmailBackup
const muteEmailsSettingName = SettingName.MuteFailedBackupsEmails
const muteEmailsSettingValue = MuteFailedBackupsEmailsOption.Muted
if (!backupEmail) {
throw new Error('Could not trigger email backup for user - missing email parameter')
}
const user = await userRepository.findOneByEmail(backupEmail)
if (user === null) {
throw new Error(`Could not find user with email: ${backupEmail}`)
}
const userIsPermittedForEmailBackups = await roleService.userHasPermission(user.uuid, permissionName)
if (!userIsPermittedForEmailBackups) {
throw new Error(`User ${backupEmail} is not permitted for email backups`)
}
let userHasEmailsMuted = false
const emailsMutedSetting = await settingRepository.findOneByNameAndUserUuid(muteEmailsSettingName, user.uuid)
if (emailsMutedSetting !== null && emailsMutedSetting.value !== null) {
userHasEmailsMuted = emailsMutedSetting.value === muteEmailsSettingValue
}
const analyticsEntity = await analyticsEntityRepository.findOneByUserUuid(user.uuid)
if (analyticsEntity === null) {
return
}
await domainEventPublisher.publish(
domainEventFactory.createEmailBackupRequestedEvent(
user.uuid,
emailsMutedSetting?.uuid as string,
userHasEmailsMuted,
),
)
await analyticsStore.markActivity([AnalyticsActivity.EmailBackup], analyticsEntity.id, [
Period.Today,
Period.ThisWeek,
])
await analyticsStore.unmarkActivity([AnalyticsActivity.EmailUnbackedUpData], analyticsEntity.id, [
Period.Today,
Period.ThisWeek,
])
return
}
const container = new ContainerConfigLoader()
void container.load().then((container) => {
dayjs.extend(utc)
const env: Env = new Env()
env.load()
const logger: Logger = container.get(TYPES.Logger)
logger.info(`Starting email backup requesting for ${backupEmail} ...`)
const settingRepository: SettingRepositoryInterface = container.get(TYPES.SettingRepository)
const userRepository: UserRepositoryInterface = container.get(TYPES.UserRepository)
const roleService: RoleServiceInterface = container.get(TYPES.RoleService)
const domainEventFactory: DomainEventFactoryInterface = container.get(TYPES.DomainEventFactory)
const domainEventPublisher: DomainEventPublisherInterface = container.get(TYPES.DomainEventPublisher)
const analyticsEntityRepository: AnalyticsEntityRepositoryInterface = container.get(TYPES.AnalyticsEntityRepository)
const analyticsStore: AnalyticsStoreInterface = container.get(TYPES.AnalyticsStore)
Promise.resolve(
requestBackups(
userRepository,
settingRepository,
roleService,
domainEventFactory,
domainEventPublisher,
analyticsEntityRepository,
analyticsStore,
),
)
.then(() => {
logger.info(`Email backup requesting complete for ${backupEmail}`)
process.exit(0)
})
.catch((error) => {
logger.error(`Could not finish email backup requesting for ${backupEmail}: ${error.message}`)
process.exit(1)
})
})

View File

@@ -29,6 +29,12 @@ case "$COMMAND" in
yarn workspace @standardnotes/auth-server weekly-backup:email
;;
'email-backup' )
echo "Starting Email Backup For Single User..."
EMAIL=$1 && shift 1
yarn workspace @standardnotes/auth-server user-email-backup $EMAIL
;;
'dropbox-daily-backup' )
echo "Starting Dropbox Daily Backup..."
yarn workspace @standardnotes/auth-server daily-backup:dropbox

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/auth-server",
"version": "1.13.2",
"version": "1.17.0",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
@@ -21,6 +21,7 @@
"start": "yarn node dist/bin/server.js",
"worker": "yarn node dist/bin/worker.js",
"daily-backup:email": "yarn node dist/bin/backup.js email daily",
"user-email-backup": "yarn node dist/bin/user_email_backup.js",
"daily-backup:dropbox": "yarn node dist/bin/backup.js dropbox daily",
"daily-backup:google_drive": "yarn node dist/bin/backup.js google_drive daily",
"daily-backup:one_drive": "yarn node dist/bin/backup.js one_drive daily",
@@ -56,7 +57,7 @@
"inversify-express-utils": "^6.4.3",
"ioredis": "^5.2.0",
"mysql2": "^2.3.3",
"newrelic": "^8.14.1",
"newrelic": "^9.0.0",
"otplib": "12.0.1",
"prettyjson": "^1.2.5",
"reflect-metadata": "0.1.13",

View File

@@ -11,6 +11,8 @@ import { SessionRepositoryInterface } from '../Session/SessionRepositoryInterfac
import { User } from '../User/User'
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
import { AccountDeletionRequestedEventHandler } from './AccountDeletionRequestedEventHandler'
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
import { AnalyticsStoreInterface } from '@standardnotes/analytics'
describe('AccountDeletionRequestedEventHandler', () => {
let userRepository: UserRepositoryInterface
@@ -23,6 +25,8 @@ describe('AccountDeletionRequestedEventHandler', () => {
let revokedSession: RevokedSession
let user: User
let event: AccountDeletionRequestedEvent
let getUserAnalyticsId: GetUserAnalyticsId
let analyticsStore: AnalyticsStoreInterface
const createHandler = () =>
new AccountDeletionRequestedEventHandler(
@@ -30,6 +34,8 @@ describe('AccountDeletionRequestedEventHandler', () => {
sessionRepository,
ephemeralSessionRepository,
revokedSessionRepository,
getUserAnalyticsId,
analyticsStore,
logger,
)
@@ -72,6 +78,12 @@ describe('AccountDeletionRequestedEventHandler', () => {
regularSubscriptionUuid: '2-3-4',
}
getUserAnalyticsId = {} as jest.Mocked<GetUserAnalyticsId>
getUserAnalyticsId.execute = jest.fn().mockReturnValue({ analyticsId: 3 })
analyticsStore = {} as jest.Mocked<AnalyticsStoreInterface>
analyticsStore.markActivity = jest.fn()
logger = {} as jest.Mocked<Logger>
logger.info = jest.fn()
logger.warn = jest.fn()

View File

@@ -1,3 +1,4 @@
import { AnalyticsActivity, AnalyticsStoreInterface, Period } from '@standardnotes/analytics'
import { AccountDeletionRequestedEvent, DomainEventHandlerInterface } from '@standardnotes/domain-events'
import { inject, injectable } from 'inversify'
import { Logger } from 'winston'
@@ -5,6 +6,7 @@ import TYPES from '../../Bootstrap/Types'
import { EphemeralSessionRepositoryInterface } from '../Session/EphemeralSessionRepositoryInterface'
import { RevokedSessionRepositoryInterface } from '../Session/RevokedSessionRepositoryInterface'
import { SessionRepositoryInterface } from '../Session/SessionRepositoryInterface'
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
@injectable()
@@ -14,6 +16,8 @@ export class AccountDeletionRequestedEventHandler implements DomainEventHandlerI
@inject(TYPES.SessionRepository) private sessionRepository: SessionRepositoryInterface,
@inject(TYPES.EphemeralSessionRepository) private ephemeralSessionRepository: EphemeralSessionRepositoryInterface,
@inject(TYPES.RevokedSessionRepository) private revokedSessionRepository: RevokedSessionRepositoryInterface,
@inject(TYPES.GetUserAnalyticsId) private getUserAnalyticsId: GetUserAnalyticsId,
@inject(TYPES.AnalyticsStore) private analyticsStore: AnalyticsStoreInterface,
@inject(TYPES.Logger) private logger: Logger,
) {}
@@ -28,6 +32,13 @@ export class AccountDeletionRequestedEventHandler implements DomainEventHandlerI
await this.removeSessions(event.payload.userUuid)
const { analyticsId } = await this.getUserAnalyticsId.execute({ userUuid: user.uuid })
await this.analyticsStore.markActivity([AnalyticsActivity.DeleteAccount], analyticsId, [
Period.Today,
Period.ThisWeek,
Period.ThisMonth,
])
await this.userRepository.remove(user)
this.logger.info(`Finished account cleanup for user: ${event.payload.userUuid}`)

View File

@@ -8,17 +8,41 @@ import * as dayjs from 'dayjs'
import { SubscriptionCancelledEventHandler } from './SubscriptionCancelledEventHandler'
import { UserSubscriptionRepositoryInterface } from '../Subscription/UserSubscriptionRepositoryInterface'
import { OfflineUserSubscriptionRepositoryInterface } from '../Subscription/OfflineUserSubscriptionRepositoryInterface'
import { AnalyticsStoreInterface } from '@standardnotes/analytics'
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
import { User } from '../User/User'
describe('SubscriptionCancelledEventHandler', () => {
let userSubscriptionRepository: UserSubscriptionRepositoryInterface
let offlineUserSubscriptionRepository: OfflineUserSubscriptionRepositoryInterface
let event: SubscriptionCancelledEvent
let userRepository: UserRepositoryInterface
let getUserAnalyticsId: GetUserAnalyticsId
let analyticsStore: AnalyticsStoreInterface
let timestamp: number
const createHandler = () =>
new SubscriptionCancelledEventHandler(userSubscriptionRepository, offlineUserSubscriptionRepository)
new SubscriptionCancelledEventHandler(
userSubscriptionRepository,
offlineUserSubscriptionRepository,
userRepository,
getUserAnalyticsId,
analyticsStore,
)
beforeEach(() => {
const user = { uuid: '1-2-3' } as jest.Mocked<User>
userRepository = {} as jest.Mocked<UserRepositoryInterface>
userRepository.findOneByEmail = jest.fn().mockReturnValue(user)
getUserAnalyticsId = {} as jest.Mocked<GetUserAnalyticsId>
getUserAnalyticsId.execute = jest.fn().mockReturnValue({ analyticsId: 3 })
analyticsStore = {} as jest.Mocked<AnalyticsStoreInterface>
analyticsStore.markActivity = jest.fn()
userSubscriptionRepository = {} as jest.Mocked<UserSubscriptionRepositoryInterface>
userSubscriptionRepository.updateCancelled = jest.fn()
@@ -42,6 +66,16 @@ describe('SubscriptionCancelledEventHandler', () => {
await createHandler().handle(event)
expect(userSubscriptionRepository.updateCancelled).toHaveBeenCalledWith(1, true, timestamp)
expect(analyticsStore.markActivity).toHaveBeenCalled()
})
it('should update subscription cancelled - user not found', async () => {
userRepository.findOneByEmail = jest.fn().mockReturnValue(null)
await createHandler().handle(event)
expect(userSubscriptionRepository.updateCancelled).toHaveBeenCalledWith(1, true, timestamp)
expect(analyticsStore.markActivity).not.toHaveBeenCalled()
})
it('should update offline subscription cancelled', async () => {

View File

@@ -4,6 +4,9 @@ import { inject, injectable } from 'inversify'
import TYPES from '../../Bootstrap/Types'
import { UserSubscriptionRepositoryInterface } from '../Subscription/UserSubscriptionRepositoryInterface'
import { OfflineUserSubscriptionRepositoryInterface } from '../Subscription/OfflineUserSubscriptionRepositoryInterface'
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
import { AnalyticsActivity, AnalyticsStoreInterface, Period } from '@standardnotes/analytics'
@injectable()
export class SubscriptionCancelledEventHandler implements DomainEventHandlerInterface {
@@ -11,6 +14,9 @@ export class SubscriptionCancelledEventHandler implements DomainEventHandlerInte
@inject(TYPES.UserSubscriptionRepository) private userSubscriptionRepository: UserSubscriptionRepositoryInterface,
@inject(TYPES.OfflineUserSubscriptionRepository)
private offlineUserSubscriptionRepository: OfflineUserSubscriptionRepositoryInterface,
@inject(TYPES.UserRepository) private userRepository: UserRepositoryInterface,
@inject(TYPES.GetUserAnalyticsId) private getUserAnalyticsId: GetUserAnalyticsId,
@inject(TYPES.AnalyticsStore) private analyticsStore: AnalyticsStoreInterface,
) {}
async handle(event: SubscriptionCancelledEvent): Promise<void> {
if (event.payload.offline) {
@@ -20,6 +26,16 @@ export class SubscriptionCancelledEventHandler implements DomainEventHandlerInte
}
await this.updateSubscriptionCancelled(event.payload.subscriptionId, event.payload.timestamp)
const user = await this.userRepository.findOneByEmail(event.payload.userEmail)
if (user !== null) {
const { analyticsId } = await this.getUserAnalyticsId.execute({ userUuid: user.uuid })
await this.analyticsStore.markActivity([AnalyticsActivity.SubscriptionCancelled], analyticsId, [
Period.Today,
Period.ThisWeek,
Period.ThisMonth,
])
}
}
private async updateSubscriptionCancelled(subscriptionId: number, timestamp: number): Promise<void> {

View File

@@ -18,6 +18,7 @@ import { SubscriptionSettingServiceInterface } from '../Setting/SubscriptionSett
import { UserSubscriptionType } from '../Subscription/UserSubscriptionType'
import { AnalyticsStoreInterface, Period } from '@standardnotes/analytics'
import { AnalyticsEntity } from '../Analytics/AnalyticsEntity'
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
describe('SubscriptionPurchasedEventHandler', () => {
let userRepository: UserRepositoryInterface
@@ -31,6 +32,7 @@ describe('SubscriptionPurchasedEventHandler', () => {
let event: SubscriptionPurchasedEvent
let subscriptionExpiresAt: number
let subscriptionSettingService: SubscriptionSettingServiceInterface
let getUserAnalyticsId: GetUserAnalyticsId
let analyticsStore: AnalyticsStoreInterface
let timestamp: number
@@ -41,6 +43,7 @@ describe('SubscriptionPurchasedEventHandler', () => {
offlineUserSubscriptionRepository,
roleService,
subscriptionSettingService,
getUserAnalyticsId,
analyticsStore,
logger,
)
@@ -93,6 +96,9 @@ describe('SubscriptionPurchasedEventHandler', () => {
subscriptionSettingService = {} as jest.Mocked<SubscriptionSettingServiceInterface>
subscriptionSettingService.applyDefaultSubscriptionSettingsForSubscription = jest.fn()
getUserAnalyticsId = {} as jest.Mocked<GetUserAnalyticsId>
getUserAnalyticsId.execute = jest.fn().mockReturnValue({ analyticsId: 3 })
analyticsStore = {} as jest.Mocked<AnalyticsStoreInterface>
analyticsStore.markActivity = jest.fn()

View File

@@ -14,6 +14,7 @@ import { OfflineUserSubscriptionRepositoryInterface } from '../Subscription/Offl
import { UserSubscriptionType } from '../Subscription/UserSubscriptionType'
import { SubscriptionSettingServiceInterface } from '../Setting/SubscriptionSettingServiceInterface'
import { AnalyticsActivity, AnalyticsStoreInterface, Period } from '@standardnotes/analytics'
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
@injectable()
export class SubscriptionPurchasedEventHandler implements DomainEventHandlerInterface {
@@ -24,6 +25,7 @@ export class SubscriptionPurchasedEventHandler implements DomainEventHandlerInte
private offlineUserSubscriptionRepository: OfflineUserSubscriptionRepositoryInterface,
@inject(TYPES.RoleService) private roleService: RoleServiceInterface,
@inject(TYPES.SubscriptionSettingService) private subscriptionSettingService: SubscriptionSettingServiceInterface,
@inject(TYPES.GetUserAnalyticsId) private getUserAnalyticsId: GetUserAnalyticsId,
@inject(TYPES.AnalyticsStore) private analyticsStore: AnalyticsStoreInterface,
@inject(TYPES.Logger) private logger: Logger,
) {}
@@ -65,14 +67,18 @@ export class SubscriptionPurchasedEventHandler implements DomainEventHandlerInte
event.payload.subscriptionName,
)
const { analyticsId } = await this.getUserAnalyticsId.execute({ userUuid: user.uuid })
await this.analyticsStore.markActivity([AnalyticsActivity.SubscriptionPurchased], analyticsId, [
Period.Today,
Period.ThisWeek,
Period.ThisMonth,
])
const limitedDiscountPurchased = event.payload.discountCode === 'limited-10'
if (limitedDiscountPurchased) {
const analyticsEntity = await user.analyticsEntity
if (analyticsEntity) {
await this.analyticsStore.markActivity([AnalyticsActivity.LimitedDiscountOfferPurchased], analyticsEntity.id, [
Period.Today,
])
}
await this.analyticsStore.markActivity([AnalyticsActivity.LimitedDiscountOfferPurchased], analyticsId, [
Period.Today,
])
}
}

View File

@@ -13,6 +13,8 @@ import { UserSubscriptionRepositoryInterface } from '../Subscription/UserSubscri
import { RoleServiceInterface } from '../Role/RoleServiceInterface'
import { OfflineUserSubscriptionRepositoryInterface } from '../Subscription/OfflineUserSubscriptionRepositoryInterface'
import { UserSubscription } from '../Subscription/UserSubscription'
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
import { AnalyticsStoreInterface } from '@standardnotes/analytics'
describe('SubscriptionRefundedEventHandler', () => {
let userRepository: UserRepositoryInterface
@@ -23,6 +25,8 @@ describe('SubscriptionRefundedEventHandler', () => {
let user: User
let event: SubscriptionRefundedEvent
let timestamp: number
let getUserAnalyticsId: GetUserAnalyticsId
let analyticsStore: AnalyticsStoreInterface
const createHandler = () =>
new SubscriptionRefundedEventHandler(
@@ -30,6 +34,8 @@ describe('SubscriptionRefundedEventHandler', () => {
userSubscriptionRepository,
offlineUserSubscriptionRepository,
roleService,
getUserAnalyticsId,
analyticsStore,
logger,
)
@@ -72,6 +78,12 @@ describe('SubscriptionRefundedEventHandler', () => {
offline: false,
}
getUserAnalyticsId = {} as jest.Mocked<GetUserAnalyticsId>
getUserAnalyticsId.execute = jest.fn().mockReturnValue({ analyticsId: 3 })
analyticsStore = {} as jest.Mocked<AnalyticsStoreInterface>
analyticsStore.markActivity = jest.fn()
logger = {} as jest.Mocked<Logger>
logger.info = jest.fn()
logger.warn = jest.fn()

View File

@@ -8,6 +8,8 @@ import { RoleServiceInterface } from '../Role/RoleServiceInterface'
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
import { UserSubscriptionRepositoryInterface } from '../Subscription/UserSubscriptionRepositoryInterface'
import { OfflineUserSubscriptionRepositoryInterface } from '../Subscription/OfflineUserSubscriptionRepositoryInterface'
import { AnalyticsActivity, AnalyticsStoreInterface, Period } from '@standardnotes/analytics'
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
@injectable()
export class SubscriptionRefundedEventHandler implements DomainEventHandlerInterface {
@@ -17,6 +19,8 @@ export class SubscriptionRefundedEventHandler implements DomainEventHandlerInter
@inject(TYPES.OfflineUserSubscriptionRepository)
private offlineUserSubscriptionRepository: OfflineUserSubscriptionRepositoryInterface,
@inject(TYPES.RoleService) private roleService: RoleServiceInterface,
@inject(TYPES.GetUserAnalyticsId) private getUserAnalyticsId: GetUserAnalyticsId,
@inject(TYPES.AnalyticsStore) private analyticsStore: AnalyticsStoreInterface,
@inject(TYPES.Logger) private logger: Logger,
) {}
@@ -36,6 +40,13 @@ export class SubscriptionRefundedEventHandler implements DomainEventHandlerInter
await this.updateSubscriptionEndsAt(event.payload.subscriptionId, event.payload.timestamp)
await this.removeRoleFromSubscriptionUsers(event.payload.subscriptionId, event.payload.subscriptionName)
const { analyticsId } = await this.getUserAnalyticsId.execute({ userUuid: user.uuid })
await this.analyticsStore.markActivity([AnalyticsActivity.SubscriptionRefunded], analyticsId, [
Period.Today,
Period.ThisWeek,
Period.ThisMonth,
])
}
private async removeRoleFromSubscriptionUsers(

View File

@@ -13,6 +13,8 @@ import { UserSubscription } from '../Subscription/UserSubscription'
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
import { OfflineUserSubscription } from '../Subscription/OfflineUserSubscription'
import { RoleServiceInterface } from '../Role/RoleServiceInterface'
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
import { AnalyticsStoreInterface } from '@standardnotes/analytics'
describe('SubscriptionRenewedEventHandler', () => {
let userRepository: UserRepositoryInterface
@@ -26,6 +28,8 @@ describe('SubscriptionRenewedEventHandler', () => {
let event: SubscriptionRenewedEvent
let subscriptionExpiresAt: number
let timestamp: number
let getUserAnalyticsId: GetUserAnalyticsId
let analyticsStore: AnalyticsStoreInterface
const createHandler = () =>
new SubscriptionRenewedEventHandler(
@@ -33,6 +37,8 @@ describe('SubscriptionRenewedEventHandler', () => {
userSubscriptionRepository,
offlineUserSubscriptionRepository,
roleService,
getUserAnalyticsId,
analyticsStore,
logger,
)
@@ -83,6 +89,12 @@ describe('SubscriptionRenewedEventHandler', () => {
offline: false,
}
getUserAnalyticsId = {} as jest.Mocked<GetUserAnalyticsId>
getUserAnalyticsId.execute = jest.fn().mockReturnValue({ analyticsId: 3 })
analyticsStore = {} as jest.Mocked<AnalyticsStoreInterface>
analyticsStore.markActivity = jest.fn()
logger = {} as jest.Mocked<Logger>
logger.warn = jest.fn()
})

View File

@@ -1,5 +1,6 @@
import { DomainEventHandlerInterface, SubscriptionRenewedEvent } from '@standardnotes/domain-events'
import { inject, injectable } from 'inversify'
import { AnalyticsActivity, AnalyticsStoreInterface, Period } from '@standardnotes/analytics'
import TYPES from '../../Bootstrap/Types'
import { UserSubscriptionRepositoryInterface } from '../Subscription/UserSubscriptionRepositoryInterface'
@@ -9,6 +10,7 @@ import { RoleServiceInterface } from '../Role/RoleServiceInterface'
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
import { Logger } from 'winston'
import { OfflineUserSubscription } from '../Subscription/OfflineUserSubscription'
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
@injectable()
export class SubscriptionRenewedEventHandler implements DomainEventHandlerInterface {
@@ -18,6 +20,8 @@ export class SubscriptionRenewedEventHandler implements DomainEventHandlerInterf
@inject(TYPES.OfflineUserSubscriptionRepository)
private offlineUserSubscriptionRepository: OfflineUserSubscriptionRepositoryInterface,
@inject(TYPES.RoleService) private roleService: RoleServiceInterface,
@inject(TYPES.GetUserAnalyticsId) private getUserAnalyticsId: GetUserAnalyticsId,
@inject(TYPES.AnalyticsStore) private analyticsStore: AnalyticsStoreInterface,
@inject(TYPES.Logger) private logger: Logger,
) {}
@@ -55,6 +59,13 @@ export class SubscriptionRenewedEventHandler implements DomainEventHandlerInterf
}
await this.addRoleToSubscriptionUsers(event.payload.subscriptionId, event.payload.subscriptionName)
const { analyticsId } = await this.getUserAnalyticsId.execute({ userUuid: user.uuid })
await this.analyticsStore.markActivity([AnalyticsActivity.SubscriptionRenewed], analyticsId, [
Period.Today,
Period.ThisWeek,
Period.ThisMonth,
])
}
private async addRoleToSubscriptionUsers(subscriptionId: number, subscriptionName: SubscriptionName): Promise<void> {

View File

@@ -4,16 +4,27 @@ import { Logger } from 'winston'
import { UserRegisteredEventHandler } from './UserRegisteredEventHandler'
import { AxiosInstance } from 'axios'
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
import { AnalyticsStoreInterface } from '@standardnotes/analytics'
describe('UserRegisteredEventHandler', () => {
let httpClient: AxiosInstance
const userServerRegistrationUrl = 'https://user-server/registration'
const userServerAuthKey = 'auth-key'
let event: UserRegisteredEvent
let getUserAnalyticsId: GetUserAnalyticsId
let analyticsStore: AnalyticsStoreInterface
let logger: Logger
const createHandler = () =>
new UserRegisteredEventHandler(httpClient, userServerRegistrationUrl, userServerAuthKey, logger)
new UserRegisteredEventHandler(
httpClient,
userServerRegistrationUrl,
userServerAuthKey,
getUserAnalyticsId,
analyticsStore,
logger,
)
beforeEach(() => {
httpClient = {} as jest.Mocked<AxiosInstance>
@@ -26,6 +37,12 @@ describe('UserRegisteredEventHandler', () => {
email: 'test@test.te',
}
getUserAnalyticsId = {} as jest.Mocked<GetUserAnalyticsId>
getUserAnalyticsId.execute = jest.fn().mockReturnValue({ analyticsId: 3 })
analyticsStore = {} as jest.Mocked<AnalyticsStoreInterface>
analyticsStore.markActivity = jest.fn()
logger = {} as jest.Mocked<Logger>
logger.debug = jest.fn()
})
@@ -52,7 +69,14 @@ describe('UserRegisteredEventHandler', () => {
})
it('should not send a request to the user management server about a registration if url is not defined', async () => {
const handler = new UserRegisteredEventHandler(httpClient, '', userServerAuthKey, logger)
const handler = new UserRegisteredEventHandler(
httpClient,
'',
userServerAuthKey,
getUserAnalyticsId,
analyticsStore,
logger,
)
await handler.handle(event)
expect(httpClient.request).not.toHaveBeenCalled()

View File

@@ -1,9 +1,11 @@
import { AnalyticsActivity, AnalyticsStoreInterface, Period } from '@standardnotes/analytics'
import { DomainEventHandlerInterface, UserRegisteredEvent } from '@standardnotes/domain-events'
import { AxiosInstance } from 'axios'
import { inject, injectable } from 'inversify'
import { Logger } from 'winston'
import TYPES from '../../Bootstrap/Types'
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
@injectable()
export class UserRegisteredEventHandler implements DomainEventHandlerInterface {
@@ -11,6 +13,8 @@ export class UserRegisteredEventHandler implements DomainEventHandlerInterface {
@inject(TYPES.HTTPClient) private httpClient: AxiosInstance,
@inject(TYPES.USER_SERVER_REGISTRATION_URL) private userServerRegistrationUrl: string,
@inject(TYPES.USER_SERVER_AUTH_KEY) private userServerAuthKey: string,
@inject(TYPES.GetUserAnalyticsId) private getUserAnalyticsId: GetUserAnalyticsId,
@inject(TYPES.AnalyticsStore) private analyticsStore: AnalyticsStoreInterface,
@inject(TYPES.Logger) private logger: Logger,
) {}
@@ -20,6 +24,13 @@ export class UserRegisteredEventHandler implements DomainEventHandlerInterface {
return
}
const { analyticsId } = await this.getUserAnalyticsId.execute({ userUuid: event.payload.userUuid })
await this.analyticsStore.markActivity([AnalyticsActivity.Register], analyticsId, [
Period.Today,
Period.ThisWeek,
Period.ThisMonth,
])
await this.httpClient.request({
method: 'POST',
url: this.userServerRegistrationUrl,

View File

@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.7.31](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.7.30...@standardnotes/domain-events-infra@1.7.31) (2022-08-10)
**Note:** Version bump only for package @standardnotes/domain-events-infra
## [1.7.30](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.7.29...@standardnotes/domain-events-infra@1.7.30) (2022-08-09)
**Note:** Version bump only for package @standardnotes/domain-events-infra
## [1.7.29](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.7.28...@standardnotes/domain-events-infra@1.7.29) (2022-08-08)
**Note:** Version bump only for package @standardnotes/domain-events-infra
## [1.7.28](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.7.27...@standardnotes/domain-events-infra@1.7.28) (2022-07-26)
**Note:** Version bump only for package @standardnotes/domain-events-infra

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/domain-events-infra",
"version": "1.7.28",
"version": "1.7.31",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
@@ -27,7 +27,7 @@
"@standardnotes/domain-events": "workspace:*",
"aws-sdk": "^2.1082.0",
"ioredis": "^5.2.0",
"newrelic": "^8.8.0",
"newrelic": "^9.0.0",
"reflect-metadata": "^0.1.13",
"sqs-consumer": "^5.6.0",
"winston": "^3.8.1"

View File

@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [2.53.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.52.0...@standardnotes/domain-events@2.53.0) (2022-08-09)
### Features
* add total count of analytics over time ([0b9524e](https://github.com/standardnotes/server/commit/0b9524eb26c39aabe8ad0f9cdbb3aaca63a65b0e))
# [2.52.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.51.0...@standardnotes/domain-events@2.52.0) (2022-08-08)
### Features
* **api-gateway:** add analytics over time to daily report event ([845f08b](https://github.com/standardnotes/server/commit/845f08b060beda5dea69e16fbda132150de7d5f2))
# [2.51.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.50.2...@standardnotes/domain-events@2.51.0) (2022-07-26)
### Features

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/domain-events",
"version": "2.51.0",
"version": "2.53.0",
"engines": {
"node": ">=16.0.0 <17.0.0"
},

View File

@@ -12,5 +12,14 @@ export interface DailyAnalyticsReportGeneratedEventPayload {
retention: number
totalCount: number
}>
activityStatisticsOverTime: Array<{
name: string
period: number
counts: Array<{
periodKey: string
totalCount: number
}>
totalCount: number
}>
outOfSyncIncidents: number
}

View File

@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.1.30](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.1.29...@standardnotes/event-store@1.1.30) (2022-08-10)
**Note:** Version bump only for package @standardnotes/event-store
## [1.1.29](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.1.28...@standardnotes/event-store@1.1.29) (2022-08-09)
**Note:** Version bump only for package @standardnotes/event-store
## [1.1.28](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.1.27...@standardnotes/event-store@1.1.28) (2022-08-08)
**Note:** Version bump only for package @standardnotes/event-store
## [1.1.27](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.1.26...@standardnotes/event-store@1.1.27) (2022-07-26)
**Note:** Version bump only for package @standardnotes/event-store

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/event-store",
"version": "1.1.27",
"version": "1.1.30",
"description": "Event Store Service",
"private": true,
"main": "dist/src/index.js",
@@ -39,7 +39,7 @@
"inversify": "^6.0.1",
"ioredis": "^5.2.0",
"mysql2": "^2.3.3",
"newrelic": "^8.14.1",
"newrelic": "^9.0.0",
"reflect-metadata": "0.1.13",
"typeorm": "^0.3.6",
"winston": "^3.8.1"

View File

@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.5.33](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.5.32...@standardnotes/files-server@1.5.33) (2022-08-10)
**Note:** Version bump only for package @standardnotes/files-server
## [1.5.32](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.5.31...@standardnotes/files-server@1.5.32) (2022-08-09)
**Note:** Version bump only for package @standardnotes/files-server
## [1.5.31](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.5.30...@standardnotes/files-server@1.5.31) (2022-08-08)
**Note:** Version bump only for package @standardnotes/files-server
## [1.5.30](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.5.29...@standardnotes/files-server@1.5.30) (2022-07-27)
**Note:** Version bump only for package @standardnotes/files-server

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/files-server",
"version": "1.5.30",
"version": "1.5.33",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
@@ -46,7 +46,7 @@
"inversify-express-utils": "^6.4.3",
"ioredis": "^5.2.0",
"jsonwebtoken": "^8.5.1",
"newrelic": "^8.14.1",
"newrelic": "^9.0.0",
"nodemon": "^2.0.19",
"prettyjson": "^1.2.5",
"reflect-metadata": "^0.1.13",

View File

@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.10.7](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.10.6...@standardnotes/scheduler-server@1.10.7) (2022-08-10)
**Note:** Version bump only for package @standardnotes/scheduler-server
## [1.10.6](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.10.5...@standardnotes/scheduler-server@1.10.6) (2022-08-09)
**Note:** Version bump only for package @standardnotes/scheduler-server
## [1.10.5](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.10.4...@standardnotes/scheduler-server@1.10.5) (2022-08-08)
**Note:** Version bump only for package @standardnotes/scheduler-server
## [1.10.4](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.10.3...@standardnotes/scheduler-server@1.10.4) (2022-07-29)
**Note:** Version bump only for package @standardnotes/scheduler-server

Some files were not shown because too many files have changed in this diff Show More