Compare commits

..

49 Commits

Author SHA1 Message Date
standardci
8070c70152 chore(release): publish new version
- @standardnotes/api-gateway@1.19.1
 - @standardnotes/auth-server@1.25.11
 - @standardnotes/common@1.32.0
 - @standardnotes/domain-events-infra@1.8.9
 - @standardnotes/domain-events@2.60.3
 - @standardnotes/event-store@1.3.14
 - @standardnotes/files-server@1.5.51
 - @standardnotes/predicates@1.4.1
 - @standardnotes/scheduler-server@1.10.28
 - @standardnotes/security@1.3.1
 - @standardnotes/syncing-server@1.8.1
2022-09-09 12:04:24 +00:00
Karol Sójko
c3ebb321cf feat(common): add either and only types 2022-09-09 14:02:25 +02:00
standardci
e54deb594a chore(release): publish new version
- @standardnotes/event-store@1.3.13
2022-09-09 10:13:14 +00:00
Karol Sójko
432d071ec8 fix(event-store): add missing event subscriptions 2022-09-09 12:11:45 +02:00
standardci
b9c06f1f5d chore(release): publish new version
- @standardnotes/analytics@1.28.0
 - @standardnotes/api-gateway@1.19.0
 - @standardnotes/auth-server@1.25.10
 - @standardnotes/syncing-server@1.8.0
2022-09-09 09:46:00 +00:00
Karol Sójko
52cc6462a6 feat(syncing-server): add tracking files count in stats 2022-09-09 11:44:02 +02:00
standardci
35c2afef67 chore(release): publish new version
- @standardnotes/analytics@1.27.0
 - @standardnotes/api-gateway@1.18.0
 - @standardnotes/auth-server@1.25.9
 - @standardnotes/syncing-server@1.7.1
2022-09-09 09:02:40 +00:00
Karol Sójko
339c86fca0 fix(api-gateway): add general activity breakdown to yesterdays report stats 2022-09-09 11:01:06 +02:00
Karol Sójko
0afd3de977 feat(api-gateway): add tracking general activity for free and paid users breakdown 2022-09-09 10:59:46 +02:00
standardci
e699569d46 chore(release): publish new version
- @standardnotes/api-gateway@1.17.4
2022-09-09 08:51:10 +00:00
Karol Sójko
ced852d9db fix(api-gateway): add notes count statistics to report 2022-09-09 10:49:15 +02:00
standardci
a63612613e chore(release): publish new version
- @standardnotes/analytics@1.26.0
 - @standardnotes/api-gateway@1.17.3
 - @standardnotes/auth-server@1.25.8
 - @standardnotes/syncing-server@1.7.0
2022-09-09 08:44:23 +00:00
Karol Sójko
c9ec7b492a feat(syncing-server): add statistics for notes count for free and paid users 2022-09-09 10:42:12 +02:00
standardci
bf8ffc07ee chore(release): publish new version
- @standardnotes/event-store@1.3.12
2022-09-08 14:11:09 +00:00
Karol Sójko
73e1ea7f93 fix(event-store): add listening to refund processed event 2022-09-08 16:08:57 +02:00
standardci
5979b99398 chore(release): publish new version
- @standardnotes/api-gateway@1.17.2
 - @standardnotes/auth-server@1.25.7
 - @standardnotes/domain-events-infra@1.8.8
 - @standardnotes/domain-events@2.60.2
 - @standardnotes/event-store@1.3.11
 - @standardnotes/files-server@1.5.50
 - @standardnotes/scheduler-server@1.10.27
 - @standardnotes/syncing-server@1.6.70
2022-09-08 13:27:38 +00:00
Karol Sójko
50ddb918cc fix(api-gateway): retention data structure to include both period keys 2022-09-08 15:25:41 +02:00
standardci
6b19eb8876 chore(release): publish new version
- @standardnotes/api-gateway@1.17.1
 - @standardnotes/auth-server@1.25.6
 - @standardnotes/domain-events-infra@1.8.7
 - @standardnotes/domain-events@2.60.1
 - @standardnotes/event-store@1.3.10
 - @standardnotes/files-server@1.5.49
 - @standardnotes/scheduler-server@1.10.26
 - @standardnotes/syncing-server@1.6.69
2022-09-08 13:13:13 +00:00
Karol Sójko
47be0841fc fix(api-gateway): retention data structure 2022-09-08 15:11:12 +02:00
standardci
99c7bb70fc chore(release): publish new version
- @standardnotes/api-gateway@1.17.0
 - @standardnotes/auth-server@1.25.5
 - @standardnotes/domain-events-infra@1.8.6
 - @standardnotes/domain-events@2.60.0
 - @standardnotes/event-store@1.3.9
 - @standardnotes/files-server@1.5.48
 - @standardnotes/scheduler-server@1.10.25
 - @standardnotes/syncing-server@1.6.68
2022-09-08 09:54:52 +00:00
Karol Sójko
f139bb0036 feat(api-gateway): add registration-to-activity retention analytics to report 2022-09-08 11:52:44 +02:00
standardci
23f592ca24 chore(release): publish new version
- @standardnotes/api-gateway@1.16.8
 - @standardnotes/auth-server@1.25.4
 - @standardnotes/domain-events-infra@1.8.5
 - @standardnotes/domain-events@2.59.3
 - @standardnotes/event-store@1.3.8
 - @standardnotes/files-server@1.5.47
 - @standardnotes/scheduler-server@1.10.24
 - @standardnotes/syncing-server@1.6.67
2022-09-08 08:33:27 +00:00
Karol Sójko
fe4821d4f7 Revert "fix(domain-events): add boolean for sending emails on refund processed"
This reverts commit d7e6758089.
2022-09-08 10:32:00 +02:00
standardci
c338d4fec5 chore(release): publish new version
- @standardnotes/api-gateway@1.16.7
 - @standardnotes/auth-server@1.25.3
 - @standardnotes/domain-events-infra@1.8.4
 - @standardnotes/domain-events@2.59.2
 - @standardnotes/event-store@1.3.7
 - @standardnotes/files-server@1.5.46
 - @standardnotes/scheduler-server@1.10.23
 - @standardnotes/syncing-server@1.6.66
2022-09-08 08:05:47 +00:00
Karol Sójko
d7e6758089 fix(domain-events): add boolean for sending emails on refund processed 2022-09-08 10:03:41 +02:00
standardci
0ad62636b9 chore(release): publish new version
- @standardnotes/analytics@1.25.0
 - @standardnotes/api-gateway@1.16.6
 - @standardnotes/auth-server@1.25.2
 - @standardnotes/syncing-server@1.6.65
2022-09-07 14:24:28 +00:00
Karol Sójko
f872c7dfe9 feat(analytics): add discrete period key generation for last 7 days 2022-09-07 16:22:40 +02:00
standardci
c5fdd59eb1 chore(release): publish new version
- @standardnotes/analytics@1.24.0
 - @standardnotes/api-gateway@1.16.5
 - @standardnotes/auth-server@1.25.1
 - @standardnotes/syncing-server@1.6.64
2022-09-07 14:18:45 +00:00
Karol Sójko
7132dc3ac0 feat(analytics): add calculation retention for two activities 2022-09-07 16:16:27 +02:00
standardci
956d5be959 chore(release): publish new version
- @standardnotes/api-gateway@1.16.4
2022-09-07 13:43:04 +00:00
Karol Sójko
936591d40b fix(api-gateway): add registration-to-subscription time to analytics report 2022-09-07 15:41:02 +02:00
standardci
686e4f8ddf chore(release): publish new version
- @standardnotes/analytics@1.23.0
 - @standardnotes/api-gateway@1.16.3
 - @standardnotes/auth-server@1.25.0
 - @standardnotes/syncing-server@1.6.63
2022-09-07 12:36:46 +00:00
Karol Sójko
b61825235e feat(auth): add measuring registration to subscription time statistics 2022-09-07 14:34:45 +02:00
standardci
8157f324a0 chore(release): publish new version
- @standardnotes/auth-server@1.24.4
2022-09-07 08:37:22 +00:00
Karol Sójko
132b617aaa fix(auth): forbid users on shared subscription to send out invitations 2022-09-07 10:35:06 +02:00
standardci
25b1f3e9ea chore(release): publish new version
- @standardnotes/api-gateway@1.16.2
 - @standardnotes/auth-server@1.24.3
 - @standardnotes/domain-events-infra@1.8.3
 - @standardnotes/domain-events@2.59.1
 - @standardnotes/event-store@1.3.6
 - @standardnotes/files-server@1.5.45
 - @standardnotes/scheduler-server@1.10.22
 - @standardnotes/syncing-server@1.6.62
2022-09-06 10:30:52 +00:00
Karol Sójko
f94c8fc26e fix(api-gateway): period types on analytics report 2022-09-06 12:28:48 +02:00
Karol Sójko
d149f46cf6 fix(api-gateway): include period key in statistics measures 2022-09-06 12:16:07 +02:00
standardci
6a24ba5d56 chore(release): publish new version
- @standardnotes/api-gateway@1.16.1
 - @standardnotes/auth-server@1.24.2
 - @standardnotes/event-store@1.3.5
 - @standardnotes/files-server@1.5.44
 - @standardnotes/scheduler-server@1.10.21
 - @standardnotes/syncing-server@1.6.61
 - @standardnotes/time@1.11.0
2022-09-06 10:10:20 +00:00
Karol Sójko
db8333a858 feat(time): add converting microseconds to time structure 2022-09-06 12:08:39 +02:00
standardci
3af254d7c7 chore(release): publish new version
- @standardnotes/api-gateway@1.16.0
 - @standardnotes/auth-server@1.24.1
 - @standardnotes/domain-events-infra@1.8.2
 - @standardnotes/domain-events@2.59.0
 - @standardnotes/event-store@1.3.4
 - @standardnotes/files-server@1.5.43
 - @standardnotes/scheduler-server@1.10.20
 - @standardnotes/syncing-server@1.6.60
2022-09-06 09:28:23 +00:00
Karol Sójko
8151bb108a feat(api-gateway): add statistics measures to report generation 2022-09-06 11:26:27 +02:00
Karol Sójko
3b18769c2d chore(deps): upgrade uuid 2022-09-06 11:26:27 +02:00
standardci
2883cac6d4 chore(release): publish new version
- @standardnotes/analytics@1.22.0
 - @standardnotes/api-gateway@1.15.12
 - @standardnotes/auth-server@1.24.0
 - @standardnotes/domain-events-infra@1.8.1
 - @standardnotes/domain-events@2.58.0
 - @standardnotes/event-store@1.3.3
 - @standardnotes/files-server@1.5.42
 - @standardnotes/scheduler-server@1.10.19
 - @standardnotes/syncing-server@1.6.59
2022-09-06 08:56:45 +00:00
Karol Sójko
d7ae2f0625 feat(auth): add statistics for refunds and account deletions 2022-09-06 10:54:50 +02:00
standardci
318f6d0986 chore(release): publish new version
- @standardnotes/auth-server@1.23.2
2022-09-06 07:42:31 +00:00
Karol Sójko
2ca430f40c fix(auth): add debug logs for subscription canceling 2022-09-06 09:40:57 +02:00
standardci
fd65060a8e chore(release): publish new version
- @standardnotes/analytics@1.21.1
 - @standardnotes/api-gateway@1.15.11
 - @standardnotes/auth-server@1.23.1
 - @standardnotes/syncing-server@1.6.58
2022-09-06 07:36:07 +00:00
Karol Sójko
cb81f819ba fix(analytics): increment by float instead of integer on measures 2022-09-06 09:34:13 +02:00
78 changed files with 1218 additions and 75 deletions

14
.pnp.cjs generated
View File

@@ -2506,6 +2506,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
["@newrelic/winston-enricher", "virtual:04783e12400851b8a3d76e71495851cc94959db6e62f04cb0a31190080629440b182d8c8eb4d7f2b04e281912f2783a5fd4d2c3c6ab68d38b7097246c93f4c19#npm:4.0.0"],\
["@sentry/node", "npm:7.5.0"],\
["@standardnotes/analytics", "workspace:packages/analytics"],\
["@standardnotes/common", "workspace:packages/common"],\
["@standardnotes/domain-events", "workspace:packages/domain-events"],\
["@standardnotes/domain-events-infra", "workspace:packages/domain-events-infra"],\
["@standardnotes/security", "workspace:packages/security"],\
@@ -2607,7 +2608,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
["ts-jest", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:28.0.5"],\
["typeorm", "virtual:31b5a94a105c89c9294c3d524a7f8929fe63ee5a2efadf21951ca4c0cfd2ecf02e8f4ef5a066bbda091f1e3a56e57c6749069a080618c96b22e51131a330fc4a#npm:0.3.7"],\
["ua-parser-js", "npm:1.0.2"],\
["uuid", "npm:8.3.2"],\
["uuid", "npm:9.0.0"],\
["winston", "npm:3.8.1"]\
],\
"linkType": "SOFT"\
@@ -2789,7 +2790,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
["reflect-metadata", "npm:0.1.13"],\
["ts-jest", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:28.0.5"],\
["ts-node", "virtual:b442cf0427cc365d1c137f7340f9b81f9b204561afe791a8564ae9590c3a7fc4b5f793aaf8817b946f75a3cb64d03ef8790eb847f8b576b41e700da7b00c240c#npm:10.9.1"],\
["uuid", "npm:8.3.2"],\
["uuid", "npm:9.0.0"],\
["winston", "npm:3.8.1"]\
],\
"linkType": "SOFT"\
@@ -3036,7 +3037,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
["ts-jest", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:28.0.5"],\
["typeorm", "virtual:31b5a94a105c89c9294c3d524a7f8929fe63ee5a2efadf21951ca4c0cfd2ecf02e8f4ef5a066bbda091f1e3a56e57c6749069a080618c96b22e51131a330fc4a#npm:0.3.7"],\
["ua-parser-js", "npm:1.0.2"],\
["uuid", "npm:8.3.2"],\
["uuid", "npm:9.0.0"],\
["winston", "npm:3.8.1"]\
],\
"linkType": "SOFT"\
@@ -12779,6 +12780,13 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
["uuid", "npm:8.3.2"]\
],\
"linkType": "HARD"\
}],\
["npm:9.0.0", {\
"packageLocation": "./.yarn/cache/uuid-npm-9.0.0-46c41e3e43-8dd2c83c43.zip/node_modules/uuid/",\
"packageDependencies": [\
["uuid", "npm:9.0.0"]\
],\
"linkType": "HARD"\
}]\
]],\
["v8-compile-cache", [\

Binary file not shown.

View File

@@ -3,6 +3,54 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.28.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@1.27.0...@standardnotes/analytics@1.28.0) (2022-09-09)
### Features
* **syncing-server:** add tracking files count in stats ([52cc646](https://github.com/standardnotes/server/commit/52cc6462a66dae3bd6c05f551d4ba661c8a9b8c8))
# [1.27.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@1.26.0...@standardnotes/analytics@1.27.0) (2022-09-09)
### Features
* **api-gateway:** add tracking general activity for free and paid users breakdown ([0afd3de](https://github.com/standardnotes/server/commit/0afd3de9779e2abe10deede24626a3cbe6b15e6c))
# [1.26.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@1.25.0...@standardnotes/analytics@1.26.0) (2022-09-09)
### Features
* **syncing-server:** add statistics for notes count for free and paid users ([c9ec7b4](https://github.com/standardnotes/server/commit/c9ec7b492aea1911e441ed8ad9a155f871be2ef7))
# [1.25.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@1.24.0...@standardnotes/analytics@1.25.0) (2022-09-07)
### Features
* **analytics:** add discrete period key generation for last 7 days ([f872c7d](https://github.com/standardnotes/server/commit/f872c7dfe9f120f40dd0c28a9e0f5749eb251643))
# [1.24.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@1.23.0...@standardnotes/analytics@1.24.0) (2022-09-07)
### Features
* **analytics:** add calculation retention for two activities ([7132dc3](https://github.com/standardnotes/server/commit/7132dc3ac0cf878d2c326243747343e8a6746e2f))
# [1.23.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@1.22.0...@standardnotes/analytics@1.23.0) (2022-09-07)
### Features
* **auth:** add measuring registration to subscription time statistics ([b618252](https://github.com/standardnotes/server/commit/b61825235eebaf5eddb55cbda173176ca43c0099))
# [1.22.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@1.21.1...@standardnotes/analytics@1.22.0) (2022-09-06)
### Features
* **auth:** add statistics for refunds and account deletions ([d7ae2f0](https://github.com/standardnotes/server/commit/d7ae2f06255b19eb5d3403a4989610390064754e))
## [1.21.1](https://github.com/standardnotes/server/compare/@standardnotes/analytics@1.21.0...@standardnotes/analytics@1.21.1) (2022-09-06)
### Bug Fixes
* **analytics:** increment by float instead of integer on measures ([cb81f81](https://github.com/standardnotes/server/commit/cb81f819ba30a45f27ec344480b5ef22e5a0a50d))
# [1.21.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@1.20.0...@standardnotes/analytics@1.21.0) (2022-09-05)
### Features

View File

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

View File

@@ -1,6 +1,9 @@
export enum AnalyticsActivity {
GeneralActivity = 'general-activity',
GeneralActivityFreeUsers = 'general-activity-free-users',
GeneralActivityPaidUsers = 'general-activity-paid-users',
EditingItems = 'editing-items',
CheckingIntegrity = 'checking-integrity',
Login = 'login',
Register = 'register',
DeleteAccount = 'DeleteAccount',

View File

@@ -6,6 +6,12 @@ export interface AnalyticsStoreInterface {
markActivity(activities: AnalyticsActivity[], analyticsId: number, periods: Period[]): Promise<void>
wasActivityDone(activity: AnalyticsActivity, analyticsId: number, period: Period): Promise<boolean>
calculateActivityRetention(activity: AnalyticsActivity, firstPeriod: Period, secondPeriod: Period): Promise<number>
calculateActivitiesRetention(parameters: {
firstActivity: AnalyticsActivity
firstActivityPeriodKey: string
secondActivity: AnalyticsActivity
secondActivityPeriodKey: string
}): Promise<number>
calculateActivityTotalCount(activity: AnalyticsActivity, period: Period): Promise<number>
calculateActivityChangesTotalCount(
activity: AnalyticsActivity,

View File

@@ -1,4 +1,10 @@
export enum StatisticsMeasure {
PaymentSuccess = 'payment-success',
Income = 'income',
SubscriptionLength = 'subscription-length',
RegistrationLength = 'registration-length',
RegistrationToSubscriptionTime = 'registration-to-subscription-time',
Refunds = 'refunds',
NotesCountFreeUsers = 'notes-count-free-users',
NotesCountPaidUsers = 'notes-count-paid-users',
FilesCount = 'files-count',
}

View File

@@ -8,6 +8,7 @@ export enum Period {
ThisMonth,
LastMonth,
Last30Days,
Last7Days,
Q1ThisYear,
Q2ThisYear,
Q3ThisYear,

View File

@@ -48,6 +48,18 @@ describe('PeriodKeyGenerator', () => {
])
})
it('should generate period keys for last 7 days', () => {
expect(createGenerator().getDiscretePeriodKeys(Period.Last7Days)).toEqual([
'2022-5-17',
'2022-5-18',
'2022-5-19',
'2022-5-20',
'2022-5-21',
'2022-5-22',
'2022-5-23',
])
})
it('should generate period keys for Q1', () => {
expect(createGenerator().getDiscretePeriodKeys(Period.Q1ThisYear)).toEqual(['2022-1', '2022-2', '2022-3'])
})

View File

@@ -11,6 +11,12 @@ export class PeriodKeyGenerator implements PeriodKeyGeneratorInterface {
periodKeys.unshift(this.getDailyKey(this.getDateNDaysBefore(i)))
}
return periodKeys
case Period.Last7Days:
for (let i = 1; i <= 7; i++) {
periodKeys.unshift(this.getDailyKey(this.getDateNDaysBefore(i)))
}
return periodKeys
case Period.Q1ThisYear:
return this.generateMonthlyKeysRange(0, 3)

View File

@@ -125,7 +125,7 @@ describe('RedisAnalyticsStore', () => {
expect(redisClient.bitop).toHaveBeenCalledWith(
'AND',
'bitmap:action:editing-items:timespan:period-key-period-key',
'bitmap:action:editing-items-editing-items:timespan:period-key',
'bitmap:action:editing-items:timespan:period-key',
'bitmap:action:editing-items:timespan:period-key',
)

View File

@@ -95,21 +95,19 @@ export class RedisAnalyticsStore implements AnalyticsStoreInterface {
return bitValue === 1
}
async calculateActivityRetention(
activity: AnalyticsActivity,
firstPeriod: Period,
secondPeriod: Period,
): Promise<number> {
const initialPeriodKey = this.periodKeyGenerator.getPeriodKey(firstPeriod)
const subsequentPeriodKey = this.periodKeyGenerator.getPeriodKey(secondPeriod)
const diffKey = `bitmap:action:${activity}:timespan:${initialPeriodKey}-${subsequentPeriodKey}`
async calculateActivitiesRetention(parameters: {
firstActivity: AnalyticsActivity
firstActivityPeriodKey: string
secondActivity: AnalyticsActivity
secondActivityPeriodKey: string
}): Promise<number> {
const diffKey = `bitmap:action:${parameters.firstActivity}-${parameters.secondActivity}:timespan:${parameters.secondActivityPeriodKey}`
await this.redisClient.bitop(
'AND',
diffKey,
`bitmap:action:${activity}:timespan:${initialPeriodKey}`,
`bitmap:action:${activity}:timespan:${subsequentPeriodKey}`,
`bitmap:action:${parameters.firstActivity}:timespan:${parameters.firstActivityPeriodKey}`,
`bitmap:action:${parameters.secondActivity}:timespan:${parameters.secondActivityPeriodKey}`,
)
await this.redisClient.expire(diffKey, 3600)
@@ -117,12 +115,25 @@ export class RedisAnalyticsStore implements AnalyticsStoreInterface {
const retainedTotalInActivity = await this.redisClient.bitcount(diffKey)
const initialTotalInActivity = await this.redisClient.bitcount(
`bitmap:action:${activity}:timespan:${initialPeriodKey}`,
`bitmap:action:${parameters.firstActivity}:timespan:${parameters.firstActivityPeriodKey}`,
)
return Math.ceil((retainedTotalInActivity * 100) / initialTotalInActivity)
}
async calculateActivityRetention(
activity: AnalyticsActivity,
firstPeriod: Period,
secondPeriod: Period,
): Promise<number> {
return this.calculateActivitiesRetention({
firstActivity: activity,
firstActivityPeriodKey: this.periodKeyGenerator.getPeriodKey(firstPeriod),
secondActivity: activity,
secondActivityPeriodKey: this.periodKeyGenerator.getPeriodKey(secondPeriod),
})
}
async calculateActivityTotalCount(activity: AnalyticsActivity, period: Period): Promise<number> {
return this.redisClient.bitcount(
`bitmap:action:${activity}:timespan:${this.periodKeyGenerator.getPeriodKey(period)}`,

View File

@@ -1,4 +1,5 @@
import * as IORedis from 'ioredis'
import { Period, PeriodKeyGeneratorInterface } from '../../Domain'
import { StatisticsMeasure } from '../../Domain/Statistics/StatisticsMeasure'
@@ -14,7 +15,7 @@ describe('RedisStatisticsStore', () => {
beforeEach(() => {
pipeline = {} as jest.Mocked<IORedis.Pipeline>
pipeline.incr = jest.fn()
pipeline.incrby = jest.fn()
pipeline.incrbyfloat = jest.fn()
pipeline.setbit = jest.fn()
pipeline.exec = jest.fn()
@@ -92,28 +93,28 @@ describe('RedisStatisticsStore', () => {
})
it('should increment measure by a value', async () => {
await createStore().incrementMeasure(StatisticsMeasure.PaymentSuccess, 2, [Period.Today, Period.ThisMonth])
await createStore().incrementMeasure(StatisticsMeasure.Income, 2, [Period.Today, Period.ThisMonth])
expect(pipeline.incr).toHaveBeenCalledTimes(2)
expect(pipeline.incrby).toHaveBeenCalledTimes(2)
expect(pipeline.incrbyfloat).toHaveBeenCalledTimes(2)
expect(pipeline.exec).toHaveBeenCalled()
})
it('should count a measurement average', async () => {
redisClient.get = jest.fn().mockReturnValueOnce('5').mockReturnValueOnce('2')
expect(await createStore().getMeasureAverage(StatisticsMeasure.PaymentSuccess, Period.Today)).toEqual(2 / 5)
expect(await createStore().getMeasureAverage(StatisticsMeasure.Income, Period.Today)).toEqual(2 / 5)
})
it('should count a measurement average - 0 increments', async () => {
redisClient.get = jest.fn().mockReturnValueOnce(null).mockReturnValueOnce(null)
expect(await createStore().getMeasureAverage(StatisticsMeasure.PaymentSuccess, Period.Today)).toEqual(0)
expect(await createStore().getMeasureAverage(StatisticsMeasure.Income, Period.Today)).toEqual(0)
})
it('should count a measurement average - 0 total value', async () => {
redisClient.get = jest.fn().mockReturnValueOnce(5).mockReturnValueOnce(null)
expect(await createStore().getMeasureAverage(StatisticsMeasure.PaymentSuccess, Period.Today)).toEqual(0)
expect(await createStore().getMeasureAverage(StatisticsMeasure.Income, Period.Today)).toEqual(0)
})
})

View File

@@ -24,7 +24,7 @@ export class RedisStatisticsStore implements StatisticsStoreInterface {
const pipeline = this.redisClient.pipeline()
for (const period of periods) {
pipeline.incrby(`count:measure:${measure}:timespan:${this.periodKeyGenerator.getPeriodKey(period)}`, value)
pipeline.incrbyfloat(`count:measure:${measure}:timespan:${this.periodKeyGenerator.getPeriodKey(period)}`, value)
pipeline.incr(`count:increments:${measure}:timespan:${this.periodKeyGenerator.getPeriodKey(period)}`)
}

View File

@@ -3,6 +3,105 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.19.1](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.19.0...@standardnotes/api-gateway@1.19.1) (2022-09-09)
**Note:** Version bump only for package @standardnotes/api-gateway
# [1.19.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.18.0...@standardnotes/api-gateway@1.19.0) (2022-09-09)
### Features
* **syncing-server:** add tracking files count in stats ([52cc646](https://github.com/standardnotes/api-gateway/commit/52cc6462a66dae3bd6c05f551d4ba661c8a9b8c8))
# [1.18.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.17.4...@standardnotes/api-gateway@1.18.0) (2022-09-09)
### Bug Fixes
* **api-gateway:** add general activity breakdown to yesterdays report stats ([339c86f](https://github.com/standardnotes/api-gateway/commit/339c86fca073b02054260417b7519c08874e1e4e))
### Features
* **api-gateway:** add tracking general activity for free and paid users breakdown ([0afd3de](https://github.com/standardnotes/api-gateway/commit/0afd3de9779e2abe10deede24626a3cbe6b15e6c))
## [1.17.4](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.17.3...@standardnotes/api-gateway@1.17.4) (2022-09-09)
### Bug Fixes
* **api-gateway:** add notes count statistics to report ([ced852d](https://github.com/standardnotes/api-gateway/commit/ced852d9dbf8cab4c235b94a834968a5fc5e7d36))
## [1.17.3](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.17.2...@standardnotes/api-gateway@1.17.3) (2022-09-09)
**Note:** Version bump only for package @standardnotes/api-gateway
## [1.17.2](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.17.1...@standardnotes/api-gateway@1.17.2) (2022-09-08)
### Bug Fixes
* **api-gateway:** retention data structure to include both period keys ([50ddb91](https://github.com/standardnotes/api-gateway/commit/50ddb918ccc52bee4caad82504cb899bc5936150))
## [1.17.1](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.17.0...@standardnotes/api-gateway@1.17.1) (2022-09-08)
### Bug Fixes
* **api-gateway:** retention data structure ([47be084](https://github.com/standardnotes/api-gateway/commit/47be0841fc6d5fa00892e775bb3a40f404a6382b))
# [1.17.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.16.8...@standardnotes/api-gateway@1.17.0) (2022-09-08)
### Features
* **api-gateway:** add registration-to-activity retention analytics to report ([f139bb0](https://github.com/standardnotes/api-gateway/commit/f139bb003669bb41f98ad4bb59a036c489f43606))
## [1.16.8](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.16.7...@standardnotes/api-gateway@1.16.8) (2022-09-08)
**Note:** Version bump only for package @standardnotes/api-gateway
## [1.16.7](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.16.6...@standardnotes/api-gateway@1.16.7) (2022-09-08)
**Note:** Version bump only for package @standardnotes/api-gateway
## [1.16.6](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.16.5...@standardnotes/api-gateway@1.16.6) (2022-09-07)
**Note:** Version bump only for package @standardnotes/api-gateway
## [1.16.5](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.16.4...@standardnotes/api-gateway@1.16.5) (2022-09-07)
**Note:** Version bump only for package @standardnotes/api-gateway
## [1.16.4](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.16.3...@standardnotes/api-gateway@1.16.4) (2022-09-07)
### Bug Fixes
* **api-gateway:** add registration-to-subscription time to analytics report ([936591d](https://github.com/standardnotes/api-gateway/commit/936591d40b5f5beb5c0a824c92cdfa20fff51c97))
## [1.16.3](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.16.2...@standardnotes/api-gateway@1.16.3) (2022-09-07)
**Note:** Version bump only for package @standardnotes/api-gateway
## [1.16.2](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.16.1...@standardnotes/api-gateway@1.16.2) (2022-09-06)
### Bug Fixes
* **api-gateway:** include period key in statistics measures ([d149f46](https://github.com/standardnotes/api-gateway/commit/d149f46cf6456201dd8690977f64ed32a75f3459))
* **api-gateway:** period types on analytics report ([f94c8fc](https://github.com/standardnotes/api-gateway/commit/f94c8fc26e684a07101cc5282ebb9cda3c8c6961))
## [1.16.1](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.16.0...@standardnotes/api-gateway@1.16.1) (2022-09-06)
**Note:** Version bump only for package @standardnotes/api-gateway
# [1.16.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.15.12...@standardnotes/api-gateway@1.16.0) (2022-09-06)
### Features
* **api-gateway:** add statistics measures to report generation ([8151bb1](https://github.com/standardnotes/api-gateway/commit/8151bb108affb2b5cfa1ab365f99a9f0170a7795))
## [1.15.12](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.15.11...@standardnotes/api-gateway@1.15.12) (2022-09-06)
**Note:** Version bump only for package @standardnotes/api-gateway
## [1.15.11](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.15.10...@standardnotes/api-gateway@1.15.11) (2022-09-06)
**Note:** Version bump only for package @standardnotes/api-gateway
## [1.15.10](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.15.9...@standardnotes/api-gateway@1.15.10) (2022-09-05)
**Note:** Version bump only for package @standardnotes/api-gateway

View File

@@ -12,12 +12,20 @@ import {
DailyAnalyticsReportGeneratedEvent,
DomainEventService,
} from '@standardnotes/domain-events'
import { AnalyticsActivity, AnalyticsStoreInterface, Period, StatisticsStoreInterface } from '@standardnotes/analytics'
import {
AnalyticsActivity,
AnalyticsStoreInterface,
Period,
PeriodKeyGeneratorInterface,
StatisticsMeasure,
StatisticsStoreInterface,
} from '@standardnotes/analytics'
const requestReport = async (
analyticsStore: AnalyticsStoreInterface,
statisticsStore: StatisticsStoreInterface,
domainEventPublisher: DomainEventPublisherInterface,
periodKeyGenerator: PeriodKeyGeneratorInterface,
): Promise<void> => {
const analyticsOverTime = []
@@ -60,9 +68,10 @@ const requestReport = async (
const yesterdayActivityStatistics = []
const yesterdayActivityNames = [
AnalyticsActivity.EditingItems,
AnalyticsActivity.LimitedDiscountOfferPurchased,
AnalyticsActivity.GeneralActivity,
AnalyticsActivity.GeneralActivityFreeUsers,
AnalyticsActivity.GeneralActivityPaidUsers,
AnalyticsActivity.PaymentFailed,
AnalyticsActivity.PaymentSuccess,
]
@@ -79,6 +88,47 @@ const requestReport = async (
})
}
const statisticMeasureNames = [
StatisticsMeasure.Income,
StatisticsMeasure.Refunds,
StatisticsMeasure.RegistrationLength,
StatisticsMeasure.SubscriptionLength,
StatisticsMeasure.RegistrationToSubscriptionTime,
StatisticsMeasure.NotesCountFreeUsers,
StatisticsMeasure.NotesCountPaidUsers,
StatisticsMeasure.FilesCount,
]
const statisticMeasures = []
for (const statisticMeasureName of statisticMeasureNames) {
for (const period of [Period.Yesterday, Period.ThisMonth]) {
statisticMeasures.push({
name: statisticMeasureName,
period,
totalValue: await statisticsStore.getMeasureTotal(statisticMeasureName, period),
average: await statisticsStore.getMeasureAverage(statisticMeasureName, period),
})
}
}
const periodKeys = periodKeyGenerator.getDiscretePeriodKeys(Period.Last7Days)
const retentionOverDays = []
for (let i = 0; i < periodKeys.length; i++) {
for (let j = 0; j < periodKeys.length - i; j++) {
const dailyRetention = await analyticsStore.calculateActivitiesRetention({
firstActivity: AnalyticsActivity.Register,
firstActivityPeriodKey: periodKeys[i],
secondActivity: AnalyticsActivity.GeneralActivity,
secondActivityPeriodKey: periodKeys[i + j],
})
retentionOverDays.push({
firstPeriodKey: periodKeys[i],
secondPeriodKey: periodKeys[i + j],
value: dailyRetention,
})
}
}
const event: DailyAnalyticsReportGeneratedEvent = {
type: 'DAILY_ANALYTICS_REPORT_GENERATED',
createdAt: new Date(),
@@ -95,6 +145,17 @@ const requestReport = async (
outOfSyncIncidents: await statisticsStore.getYesterdayOutOfSyncIncidents(),
activityStatistics: yesterdayActivityStatistics,
activityStatisticsOverTime: analyticsOverTime,
statisticMeasures,
retentionStatistics: [
{
firstActivity: AnalyticsActivity.Register,
secondActivity: AnalyticsActivity.GeneralActivity,
retention: {
periodKeys,
values: retentionOverDays,
},
},
],
},
}
@@ -113,8 +174,9 @@ void container.load().then((container) => {
const analyticsStore: AnalyticsStoreInterface = container.get(TYPES.AnalyticsStore)
const statisticsStore: StatisticsStoreInterface = container.get(TYPES.StatisticsStore)
const domainEventPublisher: DomainEventPublisherInterface = container.get(TYPES.DomainEventPublisher)
const periodKeyGenerator: PeriodKeyGeneratorInterface = container.get(TYPES.PeriodKeyGenerator)
Promise.resolve(requestReport(analyticsStore, statisticsStore, domainEventPublisher))
Promise.resolve(requestReport(analyticsStore, statisticsStore, domainEventPublisher, periodKeyGenerator))
.then(() => {
logger.info('Usage report generation complete')

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/api-gateway",
"version": "1.15.10",
"version": "1.19.1",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
@@ -25,6 +25,7 @@
"@newrelic/winston-enricher": "^4.0.0",
"@sentry/node": "^7.3.0",
"@standardnotes/analytics": "workspace:*",
"@standardnotes/common": "workspace:^",
"@standardnotes/domain-events": "workspace:*",
"@standardnotes/domain-events-infra": "workspace:*",
"@standardnotes/security": "workspace:*",

View File

@@ -6,6 +6,7 @@ import * as AWS from 'aws-sdk'
import {
AnalyticsStoreInterface,
PeriodKeyGenerator,
PeriodKeyGeneratorInterface,
RedisAnalyticsStore,
RedisStatisticsStore,
StatisticsStoreInterface,
@@ -91,13 +92,13 @@ export class ContainerConfigLoader {
// Services
container.bind<HttpServiceInterface>(TYPES.HTTPService).to(HttpService)
const periodKeyGenerator = new PeriodKeyGenerator()
container.bind<PeriodKeyGeneratorInterface>(TYPES.PeriodKeyGenerator).toConstantValue(new PeriodKeyGenerator())
container
.bind<AnalyticsStoreInterface>(TYPES.AnalyticsStore)
.toConstantValue(new RedisAnalyticsStore(periodKeyGenerator, container.get(TYPES.Redis)))
.toConstantValue(new RedisAnalyticsStore(container.get(TYPES.PeriodKeyGenerator), container.get(TYPES.Redis)))
container
.bind<StatisticsStoreInterface>(TYPES.StatisticsStore)
.toConstantValue(new RedisStatisticsStore(periodKeyGenerator, container.get(TYPES.Redis)))
.toConstantValue(new RedisStatisticsStore(container.get(TYPES.PeriodKeyGenerator), container.get(TYPES.Redis)))
container.bind<CrossServiceTokenCacheInterface>(TYPES.CrossServiceTokenCache).to(RedisCrossServiceTokenCache)
container.bind<TimerInterface>(TYPES.Timer).toConstantValue(new Timer())

View File

@@ -26,6 +26,7 @@ const TYPES = {
StatisticsStore: Symbol.for('StatisticsStore'),
DomainEventPublisher: Symbol.for('DomainEventPublisher'),
Timer: Symbol.for('Timer'),
PeriodKeyGenerator: Symbol.for('PeriodKeyGenerator'),
}
export default TYPES

View File

@@ -1,4 +1,5 @@
import { CrossServiceTokenData } from '@standardnotes/security'
import { RoleName } from '@standardnotes/common'
import { AnalyticsActivity, AnalyticsStoreInterface, Period } from '@standardnotes/analytics'
import { TimerInterface } from '@standardnotes/time'
import { NextFunction, Request, Response } from 'express'
@@ -75,9 +76,20 @@ export class AuthMiddleware extends BaseMiddleware {
const decodedToken = <CrossServiceTokenData>verify(crossServiceToken, this.jwtSecret, { algorithms: ['HS256'] })
await this.analyticsStore.markActivity([AnalyticsActivity.GeneralActivity], decodedToken.analyticsId as number, [
Period.Today,
])
response.locals.freeUser =
decodedToken.roles.length === 1 &&
decodedToken.roles.find((role) => role.name === RoleName.CoreUser) !== undefined
await this.analyticsStore.markActivity(
[
AnalyticsActivity.GeneralActivity,
response.locals.freeUser
? AnalyticsActivity.GeneralActivityFreeUsers
: AnalyticsActivity.GeneralActivityPaidUsers,
],
decodedToken.analyticsId as number,
[Period.Today],
)
if (this.crossServiceTokenCacheTTL && !crossServiceTokenFetchedFromCache) {
await this.crossServiceTokenCache.set({

View File

@@ -3,6 +3,90 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.25.11](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.25.10...@standardnotes/auth-server@1.25.11) (2022-09-09)
**Note:** Version bump only for package @standardnotes/auth-server
## [1.25.10](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.25.9...@standardnotes/auth-server@1.25.10) (2022-09-09)
**Note:** Version bump only for package @standardnotes/auth-server
## [1.25.9](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.25.8...@standardnotes/auth-server@1.25.9) (2022-09-09)
**Note:** Version bump only for package @standardnotes/auth-server
## [1.25.8](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.25.7...@standardnotes/auth-server@1.25.8) (2022-09-09)
**Note:** Version bump only for package @standardnotes/auth-server
## [1.25.7](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.25.6...@standardnotes/auth-server@1.25.7) (2022-09-08)
**Note:** Version bump only for package @standardnotes/auth-server
## [1.25.6](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.25.5...@standardnotes/auth-server@1.25.6) (2022-09-08)
**Note:** Version bump only for package @standardnotes/auth-server
## [1.25.5](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.25.4...@standardnotes/auth-server@1.25.5) (2022-09-08)
**Note:** Version bump only for package @standardnotes/auth-server
## [1.25.4](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.25.3...@standardnotes/auth-server@1.25.4) (2022-09-08)
**Note:** Version bump only for package @standardnotes/auth-server
## [1.25.3](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.25.2...@standardnotes/auth-server@1.25.3) (2022-09-08)
**Note:** Version bump only for package @standardnotes/auth-server
## [1.25.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.25.1...@standardnotes/auth-server@1.25.2) (2022-09-07)
**Note:** Version bump only for package @standardnotes/auth-server
## [1.25.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.25.0...@standardnotes/auth-server@1.25.1) (2022-09-07)
**Note:** Version bump only for package @standardnotes/auth-server
# [1.25.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.24.4...@standardnotes/auth-server@1.25.0) (2022-09-07)
### Features
* **auth:** add measuring registration to subscription time statistics ([b618252](https://github.com/standardnotes/server/commit/b61825235eebaf5eddb55cbda173176ca43c0099))
## [1.24.4](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.24.3...@standardnotes/auth-server@1.24.4) (2022-09-07)
### Bug Fixes
* **auth:** forbid users on shared subscription to send out invitations ([132b617](https://github.com/standardnotes/server/commit/132b617aaa8a703877fd7e8d23711fb1ec234524))
## [1.24.3](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.24.2...@standardnotes/auth-server@1.24.3) (2022-09-06)
**Note:** Version bump only for package @standardnotes/auth-server
## [1.24.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.24.1...@standardnotes/auth-server@1.24.2) (2022-09-06)
**Note:** Version bump only for package @standardnotes/auth-server
## [1.24.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.24.0...@standardnotes/auth-server@1.24.1) (2022-09-06)
**Note:** Version bump only for package @standardnotes/auth-server
# [1.24.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.23.2...@standardnotes/auth-server@1.24.0) (2022-09-06)
### Features
* **auth:** add statistics for refunds and account deletions ([d7ae2f0](https://github.com/standardnotes/server/commit/d7ae2f06255b19eb5d3403a4989610390064754e))
## [1.23.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.23.1...@standardnotes/auth-server@1.23.2) (2022-09-06)
### Bug Fixes
* **auth:** add debug logs for subscription canceling ([2ca430f](https://github.com/standardnotes/server/commit/2ca430f40ce6a8d56aafa27e9c2d0b0dd561c650))
## [1.23.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.23.0...@standardnotes/auth-server@1.23.1) (2022-09-06)
**Note:** Version bump only for package @standardnotes/auth-server
# [1.23.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.22.1...@standardnotes/auth-server@1.23.0) (2022-09-05)
### Features

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/auth-server",
"version": "1.23.0",
"version": "1.25.11",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
@@ -63,7 +63,7 @@
"reflect-metadata": "0.1.13",
"typeorm": "^0.3.6",
"ua-parser-js": "1.0.2",
"uuid": "8.3.2",
"uuid": "^9.0.0",
"winston": "^3.8.1"
},
"devDependencies": {

View File

@@ -199,6 +199,7 @@ import { PredicateVerificationRequestedEventHandler } from '../Domain/Handler/Pr
import { MuteMarketingEmails } from '../Domain/UseCase/MuteMarketingEmails/MuteMarketingEmails'
import { PaymentFailedEventHandler } from '../Domain/Handler/PaymentFailedEventHandler'
import { PaymentSuccessEventHandler } from '../Domain/Handler/PaymentSuccessEventHandler'
import { RefundProcessedEventHandler } from '../Domain/Handler/RefundProcessedEventHandler'
// eslint-disable-next-line @typescript-eslint/no-var-requires
const newrelicFormatter = require('@newrelic/winston-enricher')
@@ -488,6 +489,7 @@ export class ContainerConfigLoader {
.to(PredicateVerificationRequestedEventHandler)
container.bind<PaymentFailedEventHandler>(TYPES.PaymentFailedEventHandler).to(PaymentFailedEventHandler)
container.bind<PaymentSuccessEventHandler>(TYPES.PaymentSuccessEventHandler).to(PaymentSuccessEventHandler)
container.bind<RefundProcessedEventHandler>(TYPES.RefundProcessedEventHandler).to(RefundProcessedEventHandler)
// Services
container.bind<UAParser>(TYPES.DeviceDetector).toConstantValue(new UAParser())
@@ -592,6 +594,7 @@ export class ContainerConfigLoader {
['PREDICATE_VERIFICATION_REQUESTED', container.get(TYPES.PredicateVerificationRequestedEventHandler)],
['PAYMENT_FAILED', container.get(TYPES.PaymentFailedEventHandler)],
['PAYMENT_SUCCESS', container.get(TYPES.PaymentSuccessEventHandler)],
['REFUND_PROCESSED', container.get(TYPES.RefundProcessedEventHandler)],
])
if (env.get('SQS_QUEUE_URL', true)) {

View File

@@ -145,6 +145,7 @@ const TYPES = {
PredicateVerificationRequestedEventHandler: Symbol.for('PredicateVerificationRequestedEventHandler'),
PaymentFailedEventHandler: Symbol.for('PaymentFailedEventHandler'),
PaymentSuccessEventHandler: Symbol.for('PaymentSuccessEventHandler'),
RefundProcessedEventHandler: Symbol.for('RefundProcessedEventHandler'),
// Services
DeviceDetector: Symbol.for('DeviceDetector'),
SessionService: Symbol.for('SessionService'),

View File

@@ -12,7 +12,8 @@ import { User } from '../User/User'
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
import { AccountDeletionRequestedEventHandler } from './AccountDeletionRequestedEventHandler'
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
import { AnalyticsStoreInterface } from '@standardnotes/analytics'
import { AnalyticsStoreInterface, StatisticsStoreInterface } from '@standardnotes/analytics'
import { TimerInterface } from '@standardnotes/time'
describe('AccountDeletionRequestedEventHandler', () => {
let userRepository: UserRepositoryInterface
@@ -27,6 +28,8 @@ describe('AccountDeletionRequestedEventHandler', () => {
let event: AccountDeletionRequestedEvent
let getUserAnalyticsId: GetUserAnalyticsId
let analyticsStore: AnalyticsStoreInterface
let statisticsStore: StatisticsStoreInterface
let timer: TimerInterface
const createHandler = () =>
new AccountDeletionRequestedEventHandler(
@@ -36,6 +39,8 @@ describe('AccountDeletionRequestedEventHandler', () => {
revokedSessionRepository,
getUserAnalyticsId,
analyticsStore,
statisticsStore,
timer,
logger,
)
@@ -87,6 +92,13 @@ describe('AccountDeletionRequestedEventHandler', () => {
logger = {} as jest.Mocked<Logger>
logger.info = jest.fn()
logger.warn = jest.fn()
statisticsStore = {} as jest.Mocked<StatisticsStoreInterface>
statisticsStore.incrementMeasure = jest.fn()
timer = {} as jest.Mocked<TimerInterface>
timer.getTimestampInMicroseconds = jest.fn().mockReturnValue(123)
timer.convertDateToMicroseconds = jest.fn().mockReturnValue(100)
})
it('should remove a user', async () => {

View File

@@ -1,5 +1,12 @@
import { AnalyticsActivity, AnalyticsStoreInterface, Period } from '@standardnotes/analytics'
import {
AnalyticsActivity,
AnalyticsStoreInterface,
Period,
StatisticsMeasure,
StatisticsStoreInterface,
} from '@standardnotes/analytics'
import { AccountDeletionRequestedEvent, DomainEventHandlerInterface } from '@standardnotes/domain-events'
import { TimerInterface } from '@standardnotes/time'
import { inject, injectable } from 'inversify'
import { Logger } from 'winston'
import TYPES from '../../Bootstrap/Types'
@@ -18,6 +25,8 @@ export class AccountDeletionRequestedEventHandler implements DomainEventHandlerI
@inject(TYPES.RevokedSessionRepository) private revokedSessionRepository: RevokedSessionRepositoryInterface,
@inject(TYPES.GetUserAnalyticsId) private getUserAnalyticsId: GetUserAnalyticsId,
@inject(TYPES.AnalyticsStore) private analyticsStore: AnalyticsStoreInterface,
@inject(TYPES.StatisticsStore) private statisticsStore: StatisticsStoreInterface,
@inject(TYPES.Timer) private timer: TimerInterface,
@inject(TYPES.Logger) private logger: Logger,
) {}
@@ -39,6 +48,14 @@ export class AccountDeletionRequestedEventHandler implements DomainEventHandlerI
Period.ThisMonth,
])
const registrationLength =
this.timer.getTimestampInMicroseconds() - this.timer.convertDateToMicroseconds(user.createdAt)
await this.statisticsStore.incrementMeasure(StatisticsMeasure.RegistrationLength, registrationLength, [
Period.Today,
Period.ThisWeek,
Period.ThisMonth,
])
await this.userRepository.remove(user)
this.logger.info(`Finished account cleanup for user: ${event.payload.userUuid}`)

View File

@@ -34,7 +34,7 @@ export class PaymentSuccessEventHandler implements DomainEventHandlerInterface {
Period.ThisMonth,
])
await this.statisticsStore.incrementMeasure(StatisticsMeasure.PaymentSuccess, event.payload.amount, [
await this.statisticsStore.incrementMeasure(StatisticsMeasure.Income, event.payload.amount, [
Period.Today,
Period.ThisWeek,
Period.ThisMonth,

View File

@@ -0,0 +1,34 @@
import 'reflect-metadata'
import { RefundProcessedEvent } from '@standardnotes/domain-events'
import { Period, StatisticsMeasure, StatisticsStoreInterface } from '@standardnotes/analytics'
import { RefundProcessedEventHandler } from './RefundProcessedEventHandler'
describe('RefundProcessedEventHandler', () => {
let event: RefundProcessedEvent
let statisticsStore: StatisticsStoreInterface
const createHandler = () => new RefundProcessedEventHandler(statisticsStore)
beforeEach(() => {
statisticsStore = {} as jest.Mocked<StatisticsStoreInterface>
statisticsStore.incrementMeasure = jest.fn()
event = {} as jest.Mocked<RefundProcessedEvent>
event.payload = {
userEmail: 'test@test.com',
amount: 12.45,
}
})
it('should mark refunds for statistics', async () => {
await createHandler().handle(event)
expect(statisticsStore.incrementMeasure).toHaveBeenCalledWith(StatisticsMeasure.Refunds, 12.45, [
Period.Today,
Period.ThisWeek,
Period.ThisMonth,
])
})
})

View File

@@ -0,0 +1,18 @@
import { Period, StatisticsMeasure, StatisticsStoreInterface } from '@standardnotes/analytics'
import { DomainEventHandlerInterface, RefundProcessedEvent } from '@standardnotes/domain-events'
import { inject, injectable } from 'inversify'
import TYPES from '../../Bootstrap/Types'
@injectable()
export class RefundProcessedEventHandler implements DomainEventHandlerInterface {
constructor(@inject(TYPES.StatisticsStore) private statisticsStore: StatisticsStoreInterface) {}
async handle(event: RefundProcessedEvent): Promise<void> {
await this.statisticsStore.incrementMeasure(StatisticsMeasure.Refunds, event.payload.amount, [
Period.Today,
Period.ThisWeek,
Period.ThisMonth,
])
}
}

View File

@@ -13,6 +13,7 @@ import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyti
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
import { User } from '../User/User'
import { UserSubscription } from '../Subscription/UserSubscription'
import { Logger } from 'winston'
describe('SubscriptionCancelledEventHandler', () => {
let userSubscriptionRepository: UserSubscriptionRepositoryInterface
@@ -23,6 +24,7 @@ describe('SubscriptionCancelledEventHandler', () => {
let analyticsStore: AnalyticsStoreInterface
let statisticsStore: StatisticsStoreInterface
let timestamp: number
let logger: Logger
const createHandler = () =>
new SubscriptionCancelledEventHandler(
@@ -32,6 +34,7 @@ describe('SubscriptionCancelledEventHandler', () => {
getUserAnalyticsId,
analyticsStore,
statisticsStore,
logger,
)
beforeEach(() => {
@@ -72,6 +75,9 @@ describe('SubscriptionCancelledEventHandler', () => {
offline: false,
replaced: false,
}
logger = {} as jest.Mocked<Logger>
logger.info = jest.fn()
})
it('should update subscription cancelled', async () => {

View File

@@ -14,6 +14,7 @@ import { OfflineUserSubscriptionRepositoryInterface } from '../Subscription/Offl
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
import { UserSubscription } from '../Subscription/UserSubscription'
import { Logger } from 'winston'
@injectable()
export class SubscriptionCancelledEventHandler implements DomainEventHandlerInterface {
@@ -25,6 +26,7 @@ export class SubscriptionCancelledEventHandler implements DomainEventHandlerInte
@inject(TYPES.GetUserAnalyticsId) private getUserAnalyticsId: GetUserAnalyticsId,
@inject(TYPES.AnalyticsStore) private analyticsStore: AnalyticsStoreInterface,
@inject(TYPES.StatisticsStore) private statisticsStore: StatisticsStoreInterface,
@inject(TYPES.Logger) private logger: Logger,
) {}
async handle(event: SubscriptionCancelledEvent): Promise<void> {
if (event.payload.offline) {
@@ -46,8 +48,11 @@ export class SubscriptionCancelledEventHandler implements DomainEventHandlerInte
const subscriptions = await this.userSubscriptionRepository.findBySubscriptionId(event.payload.subscriptionId)
if (subscriptions.length !== 0) {
const lastSubscription = subscriptions.shift()
const subscriptionLength = event.payload.timestamp - (lastSubscription as UserSubscription).createdAt
const lastSubscription = subscriptions.shift() as UserSubscription
const subscriptionLength = event.payload.timestamp - lastSubscription.createdAt
this.logger.info(
`Canceling subscription ${lastSubscription.uuid} - lasted for ${subscriptionLength} microseconds`,
)
await this.statisticsStore.incrementMeasure(StatisticsMeasure.SubscriptionLength, subscriptionLength, [
Period.Today,
Period.ThisWeek,

View File

@@ -16,9 +16,10 @@ import { OfflineUserSubscriptionRepositoryInterface } from '../Subscription/Offl
import { OfflineUserSubscription } from '../Subscription/OfflineUserSubscription'
import { SubscriptionSettingServiceInterface } from '../Setting/SubscriptionSettingServiceInterface'
import { UserSubscriptionType } from '../Subscription/UserSubscriptionType'
import { AnalyticsStoreInterface, Period } from '@standardnotes/analytics'
import { AnalyticsStoreInterface, Period, StatisticsStoreInterface } from '@standardnotes/analytics'
import { AnalyticsEntity } from '../Analytics/AnalyticsEntity'
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
import { TimerInterface } from '@standardnotes/time'
describe('SubscriptionPurchasedEventHandler', () => {
let userRepository: UserRepositoryInterface
@@ -35,6 +36,8 @@ describe('SubscriptionPurchasedEventHandler', () => {
let getUserAnalyticsId: GetUserAnalyticsId
let analyticsStore: AnalyticsStoreInterface
let timestamp: number
let statisticsStore: StatisticsStoreInterface
let timer: TimerInterface
const createHandler = () =>
new SubscriptionPurchasedEventHandler(
@@ -45,6 +48,8 @@ describe('SubscriptionPurchasedEventHandler', () => {
subscriptionSettingService,
getUserAnalyticsId,
analyticsStore,
statisticsStore,
timer,
logger,
)
@@ -66,7 +71,14 @@ describe('SubscriptionPurchasedEventHandler', () => {
userRepository.findOneByEmail = jest.fn().mockReturnValue(user)
userRepository.save = jest.fn().mockReturnValue(user)
statisticsStore = {} as jest.Mocked<StatisticsStoreInterface>
statisticsStore.incrementMeasure = jest.fn()
timer = {} as jest.Mocked<TimerInterface>
timer.convertDateToMicroseconds = jest.fn().mockReturnValue(1)
userSubscriptionRepository = {} as jest.Mocked<UserSubscriptionRepositoryInterface>
userSubscriptionRepository.countByUserUuid = jest.fn().mockReturnValue(0)
userSubscriptionRepository.save = jest.fn().mockReturnValue(subscription)
offlineUserSubscription = {} as jest.Mocked<OfflineUserSubscription>
@@ -146,6 +158,15 @@ describe('SubscriptionPurchasedEventHandler', () => {
updatedAt: expect.any(Number),
cancelled: false,
})
expect(statisticsStore.incrementMeasure).toHaveBeenCalled()
})
it("should not measure registration to subscription time if this is not user's first subscription", async () => {
userSubscriptionRepository.countByUserUuid = jest.fn().mockReturnValue(1)
await createHandler().handle(event)
expect(statisticsStore.incrementMeasure).not.toHaveBeenCalled()
})
it('should update analytics on limited discount offer purchasing', async () => {

View File

@@ -13,8 +13,15 @@ import { OfflineUserSubscription } from '../Subscription/OfflineUserSubscription
import { OfflineUserSubscriptionRepositoryInterface } from '../Subscription/OfflineUserSubscriptionRepositoryInterface'
import { UserSubscriptionType } from '../Subscription/UserSubscriptionType'
import { SubscriptionSettingServiceInterface } from '../Setting/SubscriptionSettingServiceInterface'
import { AnalyticsActivity, AnalyticsStoreInterface, Period } from '@standardnotes/analytics'
import {
AnalyticsActivity,
AnalyticsStoreInterface,
Period,
StatisticsMeasure,
StatisticsStoreInterface,
} from '@standardnotes/analytics'
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
import { TimerInterface } from '@standardnotes/time'
@injectable()
export class SubscriptionPurchasedEventHandler implements DomainEventHandlerInterface {
@@ -27,6 +34,8 @@ export class SubscriptionPurchasedEventHandler implements DomainEventHandlerInte
@inject(TYPES.SubscriptionSettingService) private subscriptionSettingService: SubscriptionSettingServiceInterface,
@inject(TYPES.GetUserAnalyticsId) private getUserAnalyticsId: GetUserAnalyticsId,
@inject(TYPES.AnalyticsStore) private analyticsStore: AnalyticsStoreInterface,
@inject(TYPES.StatisticsStore) private statisticsStore: StatisticsStoreInterface,
@inject(TYPES.Timer) private timer: TimerInterface,
@inject(TYPES.Logger) private logger: Logger,
) {}
@@ -52,6 +61,8 @@ export class SubscriptionPurchasedEventHandler implements DomainEventHandlerInte
return
}
const previousSubscriptionCount = await this.userSubscriptionRepository.countByUserUuid(user.uuid)
const userSubscription = await this.createSubscription(
event.payload.subscriptionId,
event.payload.subscriptionName,
@@ -80,6 +91,14 @@ export class SubscriptionPurchasedEventHandler implements DomainEventHandlerInte
Period.Today,
])
}
if (previousSubscriptionCount === 0) {
await this.statisticsStore.incrementMeasure(
StatisticsMeasure.RegistrationToSubscriptionTime,
event.payload.timestamp - this.timer.convertDateToMicroseconds(user.createdAt),
[Period.Today, Period.ThisWeek, Period.ThisMonth],
)
}
}
private async addUserRole(user: User, subscriptionName: SubscriptionName): Promise<void> {

View File

@@ -4,6 +4,7 @@ import { UserSubscriptionType } from './UserSubscriptionType'
export interface UserSubscriptionRepositoryInterface {
findOneByUuid(uuid: Uuid): Promise<UserSubscription | null>
countByUserUuid(userUuid: Uuid): Promise<number>
findOneByUserUuid(userUuid: Uuid): Promise<UserSubscription | null>
findOneByUserUuidAndSubscriptionId(userUuid: Uuid, subscriptionId: number): Promise<UserSubscription | null>
findBySubscriptionIdAndType(subscriptionId: number, type: UserSubscriptionType): Promise<UserSubscription[]>

View File

@@ -9,6 +9,7 @@ import { InviteToSharedSubscription } from './InviteToSharedSubscription'
import { UserSubscriptionRepositoryInterface } from '../../Subscription/UserSubscriptionRepositoryInterface'
import { UserSubscription } from '../../Subscription/UserSubscription'
import { RoleName } from '@standardnotes/common'
import { UserSubscriptionType } from '../../Subscription/UserSubscriptionType'
describe('InviteToSharedSubscription', () => {
let userSubscriptionRepository: UserSubscriptionRepositoryInterface
@@ -28,9 +29,10 @@ describe('InviteToSharedSubscription', () => {
beforeEach(() => {
userSubscriptionRepository = {} as jest.Mocked<UserSubscriptionRepositoryInterface>
userSubscriptionRepository.findOneByUserUuid = jest
.fn()
.mockReturnValue({ subscriptionId: 2 } as jest.Mocked<UserSubscription>)
userSubscriptionRepository.findOneByUserUuid = jest.fn().mockReturnValue({
subscriptionId: 2,
subscriptionType: UserSubscriptionType.Regular,
} as jest.Mocked<UserSubscription>)
timer = {} as jest.Mocked<TimerInterface>
timer.getTimestampInMicroseconds = jest.fn().mockReturnValue(1)
@@ -159,4 +161,24 @@ describe('InviteToSharedSubscription', () => {
})
expect(domainEventPublisher.publish).toHaveBeenCalled()
})
it('should not create an inivitation for sharing the subscription if the inviter is on a shared subscription', async () => {
userSubscriptionRepository.findOneByUserUuid = jest.fn().mockReturnValue({
subscriptionId: 2,
subscriptionType: UserSubscriptionType.Shared,
} as jest.Mocked<UserSubscription>)
await createUseCase().execute({
inviteeIdentifier: 'invitee@test.te',
inviterUuid: '1-2-3',
inviterEmail: 'inviter@test.te',
inviterRoles: [RoleName.ProUser],
})
expect(sharedSubscriptionInvitationRepository.save).not.toHaveBeenCalled()
expect(domainEventFactory.createSharedSubscriptionInvitationCreatedEvent).not.toHaveBeenCalled()
expect(domainEventPublisher.publish).not.toHaveBeenCalled()
})
})

View File

@@ -11,6 +11,7 @@ import { InviterIdentifierType } from '../../SharedSubscription/InviterIdentifie
import { SharedSubscriptionInvitation } from '../../SharedSubscription/SharedSubscriptionInvitation'
import { SharedSubscriptionInvitationRepositoryInterface } from '../../SharedSubscription/SharedSubscriptionInvitationRepositoryInterface'
import { UserSubscriptionRepositoryInterface } from '../../Subscription/UserSubscriptionRepositoryInterface'
import { UserSubscriptionType } from '../../Subscription/UserSubscriptionType'
import { UseCaseInterface } from '../UseCaseInterface'
import { InviteToSharedSubscriptionDTO } from './InviteToSharedSubscriptionDTO'
@@ -35,18 +36,18 @@ export class InviteToSharedSubscription implements UseCaseInterface {
}
}
const numberOfUsedInvites = await this.sharedSubscriptionInvitationRepository.countByInviterEmailAndStatus(
dto.inviterEmail,
[InvitationStatus.Sent, InvitationStatus.Accepted],
)
if (numberOfUsedInvites >= this.MAX_NUMBER_OF_INVITES) {
const inviterUserSubscription = await this.userSubscriptionRepository.findOneByUserUuid(dto.inviterUuid)
if (inviterUserSubscription === null || inviterUserSubscription.subscriptionType === UserSubscriptionType.Shared) {
return {
success: false,
}
}
const inviterUserSubscription = await this.userSubscriptionRepository.findOneByUserUuid(dto.inviterUuid)
if (inviterUserSubscription === null) {
const numberOfUsedInvites = await this.sharedSubscriptionInvitationRepository.countByInviterEmailAndStatus(
dto.inviterEmail,
[InvitationStatus.Sent, InvitationStatus.Accepted],
)
if (numberOfUsedInvites >= this.MAX_NUMBER_OF_INVITES) {
return {
success: false,
}

View File

@@ -75,6 +75,21 @@ describe('MySQLUserSubscriptionRepository', () => {
expect(result).toEqual(subscription)
})
it('should count by user uuid', async () => {
ormRepository.createQueryBuilder = jest.fn().mockImplementation(() => selectQueryBuilder)
selectQueryBuilder.where = jest.fn().mockReturnThis()
selectQueryBuilder.getCount = jest.fn().mockReturnValue(2)
const result = await createRepository().countByUserUuid('123')
expect(selectQueryBuilder.where).toHaveBeenCalledWith('user_uuid = :user_uuid', {
user_uuid: '123',
})
expect(selectQueryBuilder.getCount).toHaveBeenCalled()
expect(result).toEqual(2)
})
it('should find one, longest lasting subscription by user uuid if there are no ucanceled ones', async () => {
subscription.cancelled = true

View File

@@ -14,6 +14,15 @@ export class MySQLUserSubscriptionRepository implements UserSubscriptionReposito
private ormRepository: Repository<UserSubscription>,
) {}
async countByUserUuid(userUuid: Uuid): Promise<number> {
return await this.ormRepository
.createQueryBuilder()
.where('user_uuid = :user_uuid', {
user_uuid: userUuid,
})
.getCount()
}
async save(subscription: UserSubscription): Promise<UserSubscription> {
return this.ormRepository.save(subscription)
}

View File

@@ -3,6 +3,12 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.32.0](https://github.com/standardnotes/server/compare/@standardnotes/common@1.31.0...@standardnotes/common@1.32.0) (2022-09-09)
### Features
* **common:** add either and only types ([c3ebb32](https://github.com/standardnotes/server/commit/c3ebb321cfacd20769ebfd99413e283859b6e260))
# [1.31.0](https://github.com/standardnotes/server/compare/@standardnotes/common@1.30.0...@standardnotes/common@1.31.0) (2022-09-05)
### Features

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/common",
"version": "1.31.0",
"version": "1.32.0",
"engines": {
"node": ">=16.0.0 <17.0.0"
},

View File

@@ -0,0 +1,3 @@
import { Only } from './Only'
export type Either<T, U> = Only<T, U> | Only<U, T>

View File

@@ -0,0 +1,5 @@
export type Only<T, U> = {
[P in keyof T]: T[P]
} & {
[P in keyof U]?: never
}

View File

@@ -18,3 +18,5 @@ export * from './Protocol/ProtocolVersion'
export * from './Role/PaidRoles'
export * from './Role/RoleName'
export * from './Subscription/SubscriptionName'
export * from './Type/Either'
export * from './Type/Only'

View File

@@ -3,6 +3,42 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.8.9](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.8.8...@standardnotes/domain-events-infra@1.8.9) (2022-09-09)
**Note:** Version bump only for package @standardnotes/domain-events-infra
## [1.8.8](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.8.7...@standardnotes/domain-events-infra@1.8.8) (2022-09-08)
**Note:** Version bump only for package @standardnotes/domain-events-infra
## [1.8.7](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.8.6...@standardnotes/domain-events-infra@1.8.7) (2022-09-08)
**Note:** Version bump only for package @standardnotes/domain-events-infra
## [1.8.6](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.8.5...@standardnotes/domain-events-infra@1.8.6) (2022-09-08)
**Note:** Version bump only for package @standardnotes/domain-events-infra
## [1.8.5](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.8.4...@standardnotes/domain-events-infra@1.8.5) (2022-09-08)
**Note:** Version bump only for package @standardnotes/domain-events-infra
## [1.8.4](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.8.3...@standardnotes/domain-events-infra@1.8.4) (2022-09-08)
**Note:** Version bump only for package @standardnotes/domain-events-infra
## [1.8.3](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.8.2...@standardnotes/domain-events-infra@1.8.3) (2022-09-06)
**Note:** Version bump only for package @standardnotes/domain-events-infra
## [1.8.2](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.8.1...@standardnotes/domain-events-infra@1.8.2) (2022-09-06)
**Note:** Version bump only for package @standardnotes/domain-events-infra
## [1.8.1](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.8.0...@standardnotes/domain-events-infra@1.8.1) (2022-09-06)
**Note:** Version bump only for package @standardnotes/domain-events-infra
# [1.8.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.7.37...@standardnotes/domain-events-infra@1.8.0) (2022-09-05)
### Features

View File

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

View File

@@ -3,6 +3,59 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [2.60.3](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.60.2...@standardnotes/domain-events@2.60.3) (2022-09-09)
**Note:** Version bump only for package @standardnotes/domain-events
## [2.60.2](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.60.1...@standardnotes/domain-events@2.60.2) (2022-09-08)
### Bug Fixes
* **api-gateway:** retention data structure to include both period keys ([50ddb91](https://github.com/standardnotes/server/commit/50ddb918ccc52bee4caad82504cb899bc5936150))
## [2.60.1](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.60.0...@standardnotes/domain-events@2.60.1) (2022-09-08)
### Bug Fixes
* **api-gateway:** retention data structure ([47be084](https://github.com/standardnotes/server/commit/47be0841fc6d5fa00892e775bb3a40f404a6382b))
# [2.60.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.59.3...@standardnotes/domain-events@2.60.0) (2022-09-08)
### Features
* **api-gateway:** add registration-to-activity retention analytics to report ([f139bb0](https://github.com/standardnotes/server/commit/f139bb003669bb41f98ad4bb59a036c489f43606))
## [2.59.3](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.59.2...@standardnotes/domain-events@2.59.3) (2022-09-08)
### Reverts
* Revert "fix(domain-events): add boolean for sending emails on refund processed" ([fe4821d](https://github.com/standardnotes/server/commit/fe4821d4f7df38297cb92314b9cd3fde3d2c58b6))
## [2.59.2](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.59.1...@standardnotes/domain-events@2.59.2) (2022-09-08)
### Bug Fixes
* **domain-events:** add boolean for sending emails on refund processed ([d7e6758](https://github.com/standardnotes/server/commit/d7e6758089c5e9485fda345949ec8d58732afa90))
## [2.59.1](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.59.0...@standardnotes/domain-events@2.59.1) (2022-09-06)
### Bug Fixes
* **api-gateway:** include period key in statistics measures ([d149f46](https://github.com/standardnotes/server/commit/d149f46cf6456201dd8690977f64ed32a75f3459))
* **api-gateway:** period types on analytics report ([f94c8fc](https://github.com/standardnotes/server/commit/f94c8fc26e684a07101cc5282ebb9cda3c8c6961))
# [2.59.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.58.0...@standardnotes/domain-events@2.59.0) (2022-09-06)
### Features
* **api-gateway:** add statistics measures to report generation ([8151bb1](https://github.com/standardnotes/server/commit/8151bb108affb2b5cfa1ab365f99a9f0170a7795))
# [2.58.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.57.0...@standardnotes/domain-events@2.58.0) (2022-09-06)
### Features
* **auth:** add statistics for refunds and account deletions ([d7ae2f0](https://github.com/standardnotes/server/commit/d7ae2f06255b19eb5d3403a4989610390064754e))
# [2.57.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.56.0...@standardnotes/domain-events@2.57.0) (2022-09-05)
### Features

View File

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

View File

@@ -12,6 +12,12 @@ export interface DailyAnalyticsReportGeneratedEventPayload {
retention: number
totalCount: number
}>
statisticMeasures: Array<{
name: string
totalValue: number
average: number
period: number
}>
activityStatisticsOverTime: Array<{
name: string
period: number
@@ -22,4 +28,16 @@ export interface DailyAnalyticsReportGeneratedEventPayload {
totalCount: number
}>
outOfSyncIncidents: number
retentionStatistics: Array<{
firstActivity: string
secondActivity: string
retention: {
periodKeys: Array<string>
values: Array<{
firstPeriodKey: string
secondPeriodKey: string
value: number
}>
}
}>
}

View File

@@ -1,3 +1,4 @@
export interface RefundProcessedEventPayload {
userEmail: string
amount: number
}

View File

@@ -3,6 +3,58 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.3.14](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.3.13...@standardnotes/event-store@1.3.14) (2022-09-09)
**Note:** Version bump only for package @standardnotes/event-store
## [1.3.13](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.3.12...@standardnotes/event-store@1.3.13) (2022-09-09)
### Bug Fixes
* **event-store:** add missing event subscriptions ([432d071](https://github.com/standardnotes/server/commit/432d071ec88a49f90513be6c55a06005a471b174))
## [1.3.12](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.3.11...@standardnotes/event-store@1.3.12) (2022-09-08)
### Bug Fixes
* **event-store:** add listening to refund processed event ([73e1ea7](https://github.com/standardnotes/server/commit/73e1ea7f93b7d7956dd4a82298098e81ff9c85b1))
## [1.3.11](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.3.10...@standardnotes/event-store@1.3.11) (2022-09-08)
**Note:** Version bump only for package @standardnotes/event-store
## [1.3.10](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.3.9...@standardnotes/event-store@1.3.10) (2022-09-08)
**Note:** Version bump only for package @standardnotes/event-store
## [1.3.9](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.3.8...@standardnotes/event-store@1.3.9) (2022-09-08)
**Note:** Version bump only for package @standardnotes/event-store
## [1.3.8](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.3.7...@standardnotes/event-store@1.3.8) (2022-09-08)
**Note:** Version bump only for package @standardnotes/event-store
## [1.3.7](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.3.6...@standardnotes/event-store@1.3.7) (2022-09-08)
**Note:** Version bump only for package @standardnotes/event-store
## [1.3.6](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.3.5...@standardnotes/event-store@1.3.6) (2022-09-06)
**Note:** Version bump only for package @standardnotes/event-store
## [1.3.5](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.3.4...@standardnotes/event-store@1.3.5) (2022-09-06)
**Note:** Version bump only for package @standardnotes/event-store
## [1.3.4](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.3.3...@standardnotes/event-store@1.3.4) (2022-09-06)
**Note:** Version bump only for package @standardnotes/event-store
## [1.3.3](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.3.2...@standardnotes/event-store@1.3.3) (2022-09-06)
**Note:** Version bump only for package @standardnotes/event-store
## [1.3.2](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.3.1...@standardnotes/event-store@1.3.2) (2022-09-05)
**Note:** Version bump only for package @standardnotes/event-store

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/event-store",
"version": "1.3.2",
"version": "1.3.14",
"description": "Event Store Service",
"private": true,
"main": "dist/src/index.js",

View File

@@ -80,6 +80,12 @@ export class ContainerConfigLoader {
['PAYMENT_SUCCESS', container.get(TYPES.EventHandler)],
['ACCOUNT_CLAIM_REQUESTED', container.get(TYPES.EventHandler)],
['SUBSCRIPTION_REVERT_REQUESTED', container.get(TYPES.EventHandler)],
['REFUND_PROCESSED', container.get(TYPES.EventHandler)],
['ACCOUNT_RESET_REQUESTED', container.get(TYPES.EventHandler)],
['DISCOUNT_APPLIED', container.get(TYPES.EventHandler)],
['SUBSCRIPTION_RATE_ADJUSTED', container.get(TYPES.EventHandler)],
['REFUND_REQUESTED', container.get(TYPES.EventHandler)],
['INVOICE_GENERATED', container.get(TYPES.EventHandler)],
])
container

View File

@@ -3,6 +3,46 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.5.51](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.5.50...@standardnotes/files-server@1.5.51) (2022-09-09)
**Note:** Version bump only for package @standardnotes/files-server
## [1.5.50](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.5.49...@standardnotes/files-server@1.5.50) (2022-09-08)
**Note:** Version bump only for package @standardnotes/files-server
## [1.5.49](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.5.48...@standardnotes/files-server@1.5.49) (2022-09-08)
**Note:** Version bump only for package @standardnotes/files-server
## [1.5.48](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.5.47...@standardnotes/files-server@1.5.48) (2022-09-08)
**Note:** Version bump only for package @standardnotes/files-server
## [1.5.47](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.5.46...@standardnotes/files-server@1.5.47) (2022-09-08)
**Note:** Version bump only for package @standardnotes/files-server
## [1.5.46](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.5.45...@standardnotes/files-server@1.5.46) (2022-09-08)
**Note:** Version bump only for package @standardnotes/files-server
## [1.5.45](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.5.44...@standardnotes/files-server@1.5.45) (2022-09-06)
**Note:** Version bump only for package @standardnotes/files-server
## [1.5.44](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.5.43...@standardnotes/files-server@1.5.44) (2022-09-06)
**Note:** Version bump only for package @standardnotes/files-server
## [1.5.43](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.5.42...@standardnotes/files-server@1.5.43) (2022-09-06)
**Note:** Version bump only for package @standardnotes/files-server
## [1.5.42](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.5.41...@standardnotes/files-server@1.5.42) (2022-09-06)
**Note:** Version bump only for package @standardnotes/files-server
## [1.5.41](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.5.40...@standardnotes/files-server@1.5.41) (2022-09-05)
**Note:** Version bump only for package @standardnotes/files-server

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/files-server",
"version": "1.5.41",
"version": "1.5.51",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
@@ -71,6 +71,6 @@
"nodemon": "^2.0.19",
"npm-check-updates": "^16.0.1",
"ts-jest": "^28.0.5",
"uuid": "^8.3.2"
"uuid": "^9.0.0"
}
}

View File

@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.4.1](https://github.com/standardnotes/server/compare/@standardnotes/predicates@1.4.0...@standardnotes/predicates@1.4.1) (2022-09-09)
**Note:** Version bump only for package @standardnotes/predicates
# [1.4.0](https://github.com/standardnotes/server/compare/@standardnotes/predicates@1.3.0...@standardnotes/predicates@1.4.0) (2022-09-05)
### Features

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/predicates",
"version": "1.4.0",
"version": "1.4.1",
"engines": {
"node": ">=16.0.0 <17.0.0"
},

View File

@@ -3,6 +3,46 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.10.28](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.10.27...@standardnotes/scheduler-server@1.10.28) (2022-09-09)
**Note:** Version bump only for package @standardnotes/scheduler-server
## [1.10.27](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.10.26...@standardnotes/scheduler-server@1.10.27) (2022-09-08)
**Note:** Version bump only for package @standardnotes/scheduler-server
## [1.10.26](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.10.25...@standardnotes/scheduler-server@1.10.26) (2022-09-08)
**Note:** Version bump only for package @standardnotes/scheduler-server
## [1.10.25](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.10.24...@standardnotes/scheduler-server@1.10.25) (2022-09-08)
**Note:** Version bump only for package @standardnotes/scheduler-server
## [1.10.24](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.10.23...@standardnotes/scheduler-server@1.10.24) (2022-09-08)
**Note:** Version bump only for package @standardnotes/scheduler-server
## [1.10.23](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.10.22...@standardnotes/scheduler-server@1.10.23) (2022-09-08)
**Note:** Version bump only for package @standardnotes/scheduler-server
## [1.10.22](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.10.21...@standardnotes/scheduler-server@1.10.22) (2022-09-06)
**Note:** Version bump only for package @standardnotes/scheduler-server
## [1.10.21](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.10.20...@standardnotes/scheduler-server@1.10.21) (2022-09-06)
**Note:** Version bump only for package @standardnotes/scheduler-server
## [1.10.20](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.10.19...@standardnotes/scheduler-server@1.10.20) (2022-09-06)
**Note:** Version bump only for package @standardnotes/scheduler-server
## [1.10.19](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.10.18...@standardnotes/scheduler-server@1.10.19) (2022-09-06)
**Note:** Version bump only for package @standardnotes/scheduler-server
## [1.10.18](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.10.17...@standardnotes/scheduler-server@1.10.18) (2022-09-05)
**Note:** Version bump only for package @standardnotes/scheduler-server

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/scheduler-server",
"version": "1.10.18",
"version": "1.10.28",
"engines": {
"node": ">=16.0.0 <17.0.0"
},

View File

@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.3.1](https://github.com/standardnotes/server/compare/@standardnotes/security@1.3.0...@standardnotes/security@1.3.1) (2022-09-09)
**Note:** Version bump only for package @standardnotes/security
# [1.3.0](https://github.com/standardnotes/server/compare/@standardnotes/security@1.2.6...@standardnotes/security@1.3.0) (2022-09-05)
### Features

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/security",
"version": "1.3.0",
"version": "1.3.1",
"engines": {
"node": ">=16.0.0 <17.0.0"
},

View File

@@ -3,6 +3,78 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.8.1](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.8.0...@standardnotes/syncing-server@1.8.1) (2022-09-09)
**Note:** Version bump only for package @standardnotes/syncing-server
# [1.8.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.7.1...@standardnotes/syncing-server@1.8.0) (2022-09-09)
### Features
* **syncing-server:** add tracking files count in stats ([52cc646](https://github.com/standardnotes/syncing-server-js/commit/52cc6462a66dae3bd6c05f551d4ba661c8a9b8c8))
## [1.7.1](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.7.0...@standardnotes/syncing-server@1.7.1) (2022-09-09)
**Note:** Version bump only for package @standardnotes/syncing-server
# [1.7.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.6.70...@standardnotes/syncing-server@1.7.0) (2022-09-09)
### Features
* **syncing-server:** add statistics for notes count for free and paid users ([c9ec7b4](https://github.com/standardnotes/syncing-server-js/commit/c9ec7b492aea1911e441ed8ad9a155f871be2ef7))
## [1.6.70](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.6.69...@standardnotes/syncing-server@1.6.70) (2022-09-08)
**Note:** Version bump only for package @standardnotes/syncing-server
## [1.6.69](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.6.68...@standardnotes/syncing-server@1.6.69) (2022-09-08)
**Note:** Version bump only for package @standardnotes/syncing-server
## [1.6.68](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.6.67...@standardnotes/syncing-server@1.6.68) (2022-09-08)
**Note:** Version bump only for package @standardnotes/syncing-server
## [1.6.67](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.6.66...@standardnotes/syncing-server@1.6.67) (2022-09-08)
**Note:** Version bump only for package @standardnotes/syncing-server
## [1.6.66](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.6.65...@standardnotes/syncing-server@1.6.66) (2022-09-08)
**Note:** Version bump only for package @standardnotes/syncing-server
## [1.6.65](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.6.64...@standardnotes/syncing-server@1.6.65) (2022-09-07)
**Note:** Version bump only for package @standardnotes/syncing-server
## [1.6.64](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.6.63...@standardnotes/syncing-server@1.6.64) (2022-09-07)
**Note:** Version bump only for package @standardnotes/syncing-server
## [1.6.63](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.6.62...@standardnotes/syncing-server@1.6.63) (2022-09-07)
**Note:** Version bump only for package @standardnotes/syncing-server
## [1.6.62](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.6.61...@standardnotes/syncing-server@1.6.62) (2022-09-06)
**Note:** Version bump only for package @standardnotes/syncing-server
## [1.6.61](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.6.60...@standardnotes/syncing-server@1.6.61) (2022-09-06)
**Note:** Version bump only for package @standardnotes/syncing-server
## [1.6.60](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.6.59...@standardnotes/syncing-server@1.6.60) (2022-09-06)
**Note:** Version bump only for package @standardnotes/syncing-server
## [1.6.59](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.6.58...@standardnotes/syncing-server@1.6.59) (2022-09-06)
**Note:** Version bump only for package @standardnotes/syncing-server
## [1.6.58](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.6.57...@standardnotes/syncing-server@1.6.58) (2022-09-06)
**Note:** Version bump only for package @standardnotes/syncing-server
## [1.6.57](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.6.56...@standardnotes/syncing-server@1.6.57) (2022-09-05)
**Note:** Version bump only for package @standardnotes/syncing-server

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/syncing-server",
"version": "1.6.57",
"version": "1.8.1",
"engines": {
"node": ">=16.0.0 <17.0.0"
},
@@ -52,7 +52,7 @@
"reflect-metadata": "0.1.13",
"typeorm": "^0.3.6",
"ua-parser-js": "1.0.2",
"uuid": "8.3.2",
"uuid": "^9.0.0",
"winston": "^3.8.1"
},
"devDependencies": {

View File

@@ -35,7 +35,7 @@ describe('AuthMiddleware', () => {
next = jest.fn()
})
it('should authorize user from an auth JWT token if present', async () => {
it('should authorize a paid user from an auth JWT token if present', async () => {
const authToken = sign(
{
user: { uuid: '123' },
@@ -66,6 +66,34 @@ describe('AuthMiddleware', () => {
expect(response.locals.session).toEqual({ uuid: '234' })
expect(response.locals.readOnlyAccess).toBeFalsy()
expect(response.locals.analyticsId).toEqual(123)
expect(response.locals.freeUser).toEqual(false)
expect(next).toHaveBeenCalled()
})
it('should authorize a free user from an auth JWT token if present', async () => {
const authToken = sign(
{
user: { uuid: '123' },
session: { uuid: '234' },
roles: [
{
uuid: '1-2-3',
name: RoleName.CoreUser,
},
],
analyticsId: 123,
permissions: [],
},
jwtSecret,
{ algorithm: 'HS256' },
)
request.header = jest.fn().mockReturnValue(authToken)
await createMiddleware().handler(request, response, next)
expect(response.locals.freeUser).toEqual(true)
expect(next).toHaveBeenCalled()
})

View File

@@ -5,6 +5,7 @@ import { verify } from 'jsonwebtoken'
import { CrossServiceTokenData } from '@standardnotes/security'
import * as winston from 'winston'
import TYPES from '../Bootstrap/Types'
import { RoleName } from '@standardnotes/common'
@injectable()
export class AuthMiddleware extends BaseMiddleware {
@@ -27,6 +28,8 @@ export class AuthMiddleware extends BaseMiddleware {
response.locals.user = decodedToken.user
response.locals.roleNames = decodedToken.roles.map((role) => role.name)
response.locals.freeUser =
response.locals.roleNames.length === 1 && response.locals.roleNames[0] === RoleName.CoreUser
response.locals.session = decodedToken.session
response.locals.readOnlyAccess = decodedToken.session?.readonly_access ?? false
response.locals.analyticsId = decodedToken.analyticsId

View File

@@ -75,6 +75,7 @@ describe('ItemsController', () => {
uuid: '123',
}
response.locals.analyticsId = 123
response.locals.freeUser = false
syncResponse = {} as jest.Mocked<SyncResponse20200115>
@@ -132,6 +133,8 @@ describe('ItemsController', () => {
},
],
userUuid: '123',
analyticsId: 123,
freeUser: false,
})
expect(result.statusCode).toEqual(200)
@@ -147,6 +150,8 @@ describe('ItemsController', () => {
expect(checkIntegrity.execute).toHaveBeenCalledWith({
integrityPayloads: [],
userUuid: '123',
analyticsId: 123,
freeUser: false,
})
expect(result.statusCode).toEqual(200)

View File

@@ -62,6 +62,8 @@ export class ItemsController extends BaseHttpController {
const result = await this.checkIntegrity.execute({
userUuid: response.locals.user.uuid,
integrityPayloads,
analyticsId: response.locals.analyticsId,
freeUser: response.locals.freeUser,
})
return this.json(result)

View File

@@ -1,6 +1,6 @@
import 'reflect-metadata'
import { StatisticsStoreInterface } from '@standardnotes/analytics'
import { AnalyticsStoreInterface, Period, StatisticsStoreInterface } from '@standardnotes/analytics'
import { ItemRepositoryInterface } from '../../Item/ItemRepositoryInterface'
@@ -10,8 +10,9 @@ import { ContentType } from '@standardnotes/common'
describe('CheckIntegrity', () => {
let itemRepository: ItemRepositoryInterface
let statisticsStore: StatisticsStoreInterface
let analyticsStore: AnalyticsStoreInterface
const createUseCase = () => new CheckIntegrity(itemRepository, statisticsStore)
const createUseCase = () => new CheckIntegrity(itemRepository, statisticsStore, analyticsStore)
beforeEach(() => {
itemRepository = {} as jest.Mocked<ItemRepositoryInterface>
@@ -36,16 +37,28 @@ describe('CheckIntegrity', () => {
updated_at_timestamp: 4,
content_type: ContentType.ItemsKey,
},
{
uuid: '5-6-7',
updated_at_timestamp: 5,
content_type: ContentType.File,
},
])
statisticsStore = {} as jest.Mocked<StatisticsStoreInterface>
statisticsStore.incrementOutOfSyncIncidents = jest.fn()
statisticsStore.incrementMeasure = jest.fn()
analyticsStore = {} as jest.Mocked<AnalyticsStoreInterface>
analyticsStore.wasActivityDone = jest.fn().mockReturnValue(false)
analyticsStore.markActivity = jest.fn()
})
it('should return an empty result if there are no integrity mismatches', async () => {
expect(
await createUseCase().execute({
userUuid: '1-2-3',
analyticsId: 1,
freeUser: false,
integrityPayloads: [
{
uuid: '1-2-3',
@@ -59,6 +72,10 @@ describe('CheckIntegrity', () => {
uuid: '3-4-5',
updated_at_timestamp: 3,
},
{
uuid: '5-6-7',
updated_at_timestamp: 5,
},
],
}),
).toEqual({
@@ -70,6 +87,8 @@ describe('CheckIntegrity', () => {
expect(
await createUseCase().execute({
userUuid: '1-2-3',
analyticsId: 1,
freeUser: false,
integrityPayloads: [
{
uuid: '1-2-3',
@@ -83,6 +102,10 @@ describe('CheckIntegrity', () => {
uuid: '3-4-5',
updated_at_timestamp: 3,
},
{
uuid: '5-6-7',
updated_at_timestamp: 5,
},
],
}),
).toEqual({
@@ -101,6 +124,8 @@ describe('CheckIntegrity', () => {
expect(
await createUseCase().execute({
userUuid: '1-2-3',
analyticsId: 1,
freeUser: false,
integrityPayloads: [
{
uuid: '1-2-3',
@@ -110,6 +135,10 @@ describe('CheckIntegrity', () => {
uuid: '2-3-4',
updated_at_timestamp: 2,
},
{
uuid: '5-6-7',
updated_at_timestamp: 5,
},
],
}),
).toEqual({
@@ -121,4 +150,87 @@ describe('CheckIntegrity', () => {
],
})
})
it('should count notes for statistics of free users', async () => {
await createUseCase().execute({
userUuid: '1-2-3',
analyticsId: 1,
freeUser: true,
integrityPayloads: [
{
uuid: '1-2-3',
updated_at_timestamp: 1,
},
{
uuid: '2-3-4',
updated_at_timestamp: 1,
},
{
uuid: '3-4-5',
updated_at_timestamp: 3,
},
],
})
expect(statisticsStore.incrementMeasure).toHaveBeenCalledWith('notes-count-free-users', 3, [
Period.Today,
Period.ThisMonth,
])
expect(analyticsStore.markActivity).toHaveBeenCalledWith(['checking-integrity'], 1, [Period.Today])
})
it('should count notes for statistics of paid users', async () => {
await createUseCase().execute({
userUuid: '1-2-3',
analyticsId: 1,
freeUser: false,
integrityPayloads: [
{
uuid: '1-2-3',
updated_at_timestamp: 1,
},
{
uuid: '2-3-4',
updated_at_timestamp: 1,
},
{
uuid: '3-4-5',
updated_at_timestamp: 3,
},
],
})
expect(statisticsStore.incrementMeasure).toHaveBeenCalledWith('notes-count-paid-users', 3, [
Period.Today,
Period.ThisMonth,
])
expect(analyticsStore.markActivity).toHaveBeenCalledWith(['checking-integrity'], 1, [Period.Today])
})
it('should not count notes for statistics if they were already counted today', async () => {
analyticsStore.wasActivityDone = jest.fn().mockReturnValue(true)
await createUseCase().execute({
userUuid: '1-2-3',
analyticsId: 1,
freeUser: false,
integrityPayloads: [
{
uuid: '1-2-3',
updated_at_timestamp: 1,
},
{
uuid: '2-3-4',
updated_at_timestamp: 1,
},
{
uuid: '3-4-5',
updated_at_timestamp: 3,
},
],
})
expect(statisticsStore.incrementMeasure).not.toHaveBeenCalled()
expect(analyticsStore.markActivity).not.toHaveBeenCalled()
})
})

View File

@@ -1,6 +1,12 @@
import { inject, injectable } from 'inversify'
import { IntegrityPayload } from '@standardnotes/payloads'
import { StatisticsStoreInterface } from '@standardnotes/analytics'
import {
AnalyticsActivity,
AnalyticsStoreInterface,
Period,
StatisticsMeasure,
StatisticsStoreInterface,
} from '@standardnotes/analytics'
import TYPES from '../../../Bootstrap/Types'
import { ItemRepositoryInterface } from '../../Item/ItemRepositoryInterface'
@@ -15,16 +21,27 @@ export class CheckIntegrity implements UseCaseInterface {
constructor(
@inject(TYPES.ItemRepository) private itemRepository: ItemRepositoryInterface,
@inject(TYPES.StatisticsStore) private statisticsStore: StatisticsStoreInterface,
@inject(TYPES.AnalyticsStore) private analyticsStore: AnalyticsStoreInterface,
) {}
async execute(dto: CheckIntegrityDTO): Promise<CheckIntegrityResponse> {
const serverItemIntegrityPayloads = await this.itemRepository.findItemsForComputingIntegrityPayloads(dto.userUuid)
let notesCount = 0
let filesCount = 0
const serverItemIntegrityPayloadsMap = new Map<string, ExtendedIntegrityPayload>()
for (const serverItemIntegrityPayload of serverItemIntegrityPayloads) {
serverItemIntegrityPayloadsMap.set(serverItemIntegrityPayload.uuid, serverItemIntegrityPayload)
if (serverItemIntegrityPayload.content_type === ContentType.Note) {
notesCount++
}
if (serverItemIntegrityPayload.content_type === ContentType.File) {
filesCount++
}
}
await this.saveNotesCountStatistics(dto.freeUser, dto.analyticsId, { notes: notesCount, files: filesCount })
const clientItemIntegrityPayloadsMap = new Map<string, number>()
for (const clientItemIntegrityPayload of dto.integrityPayloads) {
clientItemIntegrityPayloadsMap.set(
@@ -74,4 +91,31 @@ export class CheckIntegrity implements UseCaseInterface {
mismatches,
}
}
private async saveNotesCountStatistics(
freeUser: boolean,
analyticsId: number,
counts: { notes: number; files: number },
) {
const integrityWasCheckedToday = await this.analyticsStore.wasActivityDone(
AnalyticsActivity.CheckingIntegrity,
analyticsId,
Period.Today,
)
if (!integrityWasCheckedToday) {
await this.analyticsStore.markActivity([AnalyticsActivity.CheckingIntegrity], analyticsId, [Period.Today])
await this.statisticsStore.incrementMeasure(
freeUser ? StatisticsMeasure.NotesCountFreeUsers : StatisticsMeasure.NotesCountPaidUsers,
counts.notes,
[Period.Today, Period.ThisMonth],
)
await this.statisticsStore.incrementMeasure(StatisticsMeasure.FilesCount, counts.files, [
Period.Today,
Period.ThisMonth,
])
}
}
}

View File

@@ -4,4 +4,6 @@ import { IntegrityPayload } from '@standardnotes/payloads'
export type CheckIntegrityDTO = {
userUuid: Uuid
integrityPayloads: IntegrityPayload[]
freeUser: boolean
analyticsId: number
}

View File

@@ -3,6 +3,12 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.11.0](https://github.com/standardnotes/server/compare/@standardnotes/time@1.10.0...@standardnotes/time@1.11.0) (2022-09-06)
### Features
* **time:** add converting microseconds to time structure ([db8333a](https://github.com/standardnotes/server/commit/db8333a858ba0fa6750116967355d26e72b8c16d))
# [1.10.0](https://github.com/standardnotes/server/compare/@standardnotes/time@1.9.1...@standardnotes/time@1.10.0) (2022-09-05)
### Features

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/time",
"version": "1.10.0",
"version": "1.11.0",
"engines": {
"node": ">=16.0.0 <17.0.0"
},

View File

@@ -1,4 +1,7 @@
export enum Time {
MicrosecondsInADay = 86_400_000_000,
MicrosecondsInAnHour = 3_600_000_000,
MicrosecondsInAMinute = 60_000_000,
MicrosecondsInASecond = 1_000_000,
MicrosecondsInAMillisecond = 1_000,
}

View File

@@ -0,0 +1,6 @@
export type TimeStructure = {
days: number
hours: number
minutes: number
seconds: number
}

View File

@@ -108,4 +108,13 @@ describe('Timer', () => {
it('should format date', () => {
expect(createTimer().formatDate(new Date('2021-03-29T08:00:05.233123Z'), 'YYYY-MM-DD')).toEqual('2021-03-29')
})
it('should convert microseconds to time structure', () => {
expect(createTimer().convertMicrosecondsToTimeStructure(1821050982073)).toEqual({
days: 21,
hours: 1,
minutes: 50,
seconds: 50,
})
})
})

View File

@@ -3,12 +3,33 @@ import * as utc from 'dayjs/plugin/utc'
import * as microtime from 'microtime'
import { Time } from './Time'
import { TimerInterface } from './TimerInterface'
import { TimeStructure } from './TimeStructure'
export class Timer implements TimerInterface {
constructor() {
dayjs.extend(utc)
}
convertMicrosecondsToTimeStructure(microseconds: number): TimeStructure {
const days = Math.floor(microseconds / Time.MicrosecondsInADay)
const hoursLeftOver = microseconds % Time.MicrosecondsInADay
const hours = Math.floor(hoursLeftOver / Time.MicrosecondsInAnHour)
const minutesLeftOver = microseconds % Time.MicrosecondsInAnHour
const minutes = Math.floor(minutesLeftOver / Time.MicrosecondsInAMinute)
const secondsLeftOver = microseconds % Time.MicrosecondsInAMinute
const seconds = Math.floor(secondsLeftOver / Time.MicrosecondsInASecond)
return {
days,
hours,
minutes,
seconds,
}
}
formatDate(date: Date, format: string): string {
return dayjs.utc(date).format(format)
}

View File

@@ -1,3 +1,5 @@
import { TimeStructure } from './TimeStructure'
export interface TimerInterface {
getTimestampInMicroseconds(): number
getTimestampInSeconds(): number
@@ -17,6 +19,7 @@ export interface TimerInterface {
convertMicrosecondsToSeconds(microseconds: number): number
convertMicrosecondsToStringDate(microseconds: number): string
convertMicrosecondsToDate(microseconds: number): Date
convertMicrosecondsToTimeStructure(microseconds: number): TimeStructure
formatDate(date: Date, format: string): string
dateWasNDaysAgo(date: Date): number
}

View File

@@ -1,3 +1,4 @@
export * from './Time/Time'
export * from './Time/Timer'
export * from './Time/TimerInterface'
export * from './Time/TimeStructure'

View File

@@ -1767,6 +1767,7 @@ __metadata:
"@newrelic/winston-enricher": ^4.0.0
"@sentry/node": ^7.3.0
"@standardnotes/analytics": "workspace:*"
"@standardnotes/common": "workspace:^"
"@standardnotes/domain-events": "workspace:*"
"@standardnotes/domain-events-infra": "workspace:*"
"@standardnotes/security": "workspace:*"
@@ -1869,7 +1870,7 @@ __metadata:
ts-jest: ^28.0.5
typeorm: ^0.3.6
ua-parser-js: 1.0.2
uuid: 8.3.2
uuid: ^9.0.0
winston: ^3.8.1
languageName: unknown
linkType: soft
@@ -1884,7 +1885,7 @@ __metadata:
languageName: node
linkType: hard
"@standardnotes/common@^1.19.1, @standardnotes/common@^1.23.1, @standardnotes/common@workspace:*, @standardnotes/common@workspace:packages/common":
"@standardnotes/common@^1.19.1, @standardnotes/common@^1.23.1, @standardnotes/common@workspace:*, @standardnotes/common@workspace:^, @standardnotes/common@workspace:packages/common":
version: 0.0.0-use.local
resolution: "@standardnotes/common@workspace:packages/common"
dependencies:
@@ -2048,7 +2049,7 @@ __metadata:
reflect-metadata: ^0.1.13
ts-jest: ^28.0.5
ts-node: ^10.9.1
uuid: ^8.3.2
uuid: ^9.0.0
winston: ^3.8.1
languageName: unknown
linkType: soft
@@ -2275,7 +2276,7 @@ __metadata:
ts-jest: ^28.0.5
typeorm: ^0.3.6
ua-parser-js: 1.0.2
uuid: 8.3.2
uuid: ^9.0.0
winston: ^3.8.1
languageName: unknown
linkType: soft
@@ -10429,7 +10430,7 @@ __metadata:
languageName: node
linkType: hard
"uuid@npm:8.3.2, uuid@npm:^8.3.2":
"uuid@npm:^8.3.2":
version: 8.3.2
resolution: "uuid@npm:8.3.2"
bin:
@@ -10438,6 +10439,15 @@ __metadata:
languageName: node
linkType: hard
"uuid@npm:^9.0.0":
version: 9.0.0
resolution: "uuid@npm:9.0.0"
bin:
uuid: dist/bin/uuid
checksum: 8dd2c83c43ddc7e1c71e36b60aea40030a6505139af6bee0f382ebcd1a56f6cd3028f7f06ffb07f8cf6ced320b76aea275284b224b002b289f89fe89c389b028
languageName: node
linkType: hard
"v8-compile-cache-lib@npm:^3.0.1":
version: 3.0.1
resolution: "v8-compile-cache-lib@npm:3.0.1"