mirror of
https://github.com/standardnotes/server
synced 2026-01-23 14:01:09 -05:00
Compare commits
33 Commits
@standardn
...
@standardn
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8070c70152 | ||
|
|
c3ebb321cf | ||
|
|
e54deb594a | ||
|
|
432d071ec8 | ||
|
|
b9c06f1f5d | ||
|
|
52cc6462a6 | ||
|
|
35c2afef67 | ||
|
|
339c86fca0 | ||
|
|
0afd3de977 | ||
|
|
e699569d46 | ||
|
|
ced852d9db | ||
|
|
a63612613e | ||
|
|
c9ec7b492a | ||
|
|
bf8ffc07ee | ||
|
|
73e1ea7f93 | ||
|
|
5979b99398 | ||
|
|
50ddb918cc | ||
|
|
6b19eb8876 | ||
|
|
47be0841fc | ||
|
|
99c7bb70fc | ||
|
|
f139bb0036 | ||
|
|
23f592ca24 | ||
|
|
fe4821d4f7 | ||
|
|
c338d4fec5 | ||
|
|
d7e6758089 | ||
|
|
0ad62636b9 | ||
|
|
f872c7dfe9 | ||
|
|
c5fdd59eb1 | ||
|
|
7132dc3ac0 | ||
|
|
956d5be959 | ||
|
|
936591d40b | ||
|
|
686e4f8ddf | ||
|
|
b61825235e |
1
.pnp.cjs
generated
1
.pnp.cjs
generated
@@ -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"],\
|
||||
|
||||
@@ -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.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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/analytics",
|
||||
"version": "1.22.0",
|
||||
"version": "1.28.0",
|
||||
"engines": {
|
||||
"node": ">=14.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
export enum AnalyticsActivity {
|
||||
GeneralActivity = 'general-activity',
|
||||
GeneralActivityFreeUsers = 'general-activity-free-users',
|
||||
GeneralActivityPaidUsers = 'general-activity-paid-users',
|
||||
EditingItems = 'editing-items',
|
||||
CheckingIntegrity = 'checking-integrity',
|
||||
Login = 'login',
|
||||
Register = 'register',
|
||||
DeleteAccount = 'DeleteAccount',
|
||||
|
||||
@@ -6,6 +6,12 @@ export interface AnalyticsStoreInterface {
|
||||
markActivity(activities: AnalyticsActivity[], analyticsId: number, periods: Period[]): Promise<void>
|
||||
wasActivityDone(activity: AnalyticsActivity, analyticsId: number, period: Period): Promise<boolean>
|
||||
calculateActivityRetention(activity: AnalyticsActivity, firstPeriod: Period, secondPeriod: Period): Promise<number>
|
||||
calculateActivitiesRetention(parameters: {
|
||||
firstActivity: AnalyticsActivity
|
||||
firstActivityPeriodKey: string
|
||||
secondActivity: AnalyticsActivity
|
||||
secondActivityPeriodKey: string
|
||||
}): Promise<number>
|
||||
calculateActivityTotalCount(activity: AnalyticsActivity, period: Period): Promise<number>
|
||||
calculateActivityChangesTotalCount(
|
||||
activity: AnalyticsActivity,
|
||||
|
||||
@@ -2,5 +2,9 @@ export enum StatisticsMeasure {
|
||||
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',
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ export enum Period {
|
||||
ThisMonth,
|
||||
LastMonth,
|
||||
Last30Days,
|
||||
Last7Days,
|
||||
Q1ThisYear,
|
||||
Q2ThisYear,
|
||||
Q3ThisYear,
|
||||
|
||||
@@ -48,6 +48,18 @@ describe('PeriodKeyGenerator', () => {
|
||||
])
|
||||
})
|
||||
|
||||
it('should generate period keys for last 7 days', () => {
|
||||
expect(createGenerator().getDiscretePeriodKeys(Period.Last7Days)).toEqual([
|
||||
'2022-5-17',
|
||||
'2022-5-18',
|
||||
'2022-5-19',
|
||||
'2022-5-20',
|
||||
'2022-5-21',
|
||||
'2022-5-22',
|
||||
'2022-5-23',
|
||||
])
|
||||
})
|
||||
|
||||
it('should generate period keys for Q1', () => {
|
||||
expect(createGenerator().getDiscretePeriodKeys(Period.Q1ThisYear)).toEqual(['2022-1', '2022-2', '2022-3'])
|
||||
})
|
||||
|
||||
@@ -11,6 +11,12 @@ export class PeriodKeyGenerator implements PeriodKeyGeneratorInterface {
|
||||
periodKeys.unshift(this.getDailyKey(this.getDateNDaysBefore(i)))
|
||||
}
|
||||
|
||||
return periodKeys
|
||||
case Period.Last7Days:
|
||||
for (let i = 1; i <= 7; i++) {
|
||||
periodKeys.unshift(this.getDailyKey(this.getDateNDaysBefore(i)))
|
||||
}
|
||||
|
||||
return periodKeys
|
||||
case Period.Q1ThisYear:
|
||||
return this.generateMonthlyKeysRange(0, 3)
|
||||
|
||||
@@ -125,7 +125,7 @@ describe('RedisAnalyticsStore', () => {
|
||||
|
||||
expect(redisClient.bitop).toHaveBeenCalledWith(
|
||||
'AND',
|
||||
'bitmap:action:editing-items:timespan:period-key-period-key',
|
||||
'bitmap:action:editing-items-editing-items:timespan:period-key',
|
||||
'bitmap:action:editing-items:timespan:period-key',
|
||||
'bitmap:action:editing-items:timespan:period-key',
|
||||
)
|
||||
|
||||
@@ -95,21 +95,19 @@ export class RedisAnalyticsStore implements AnalyticsStoreInterface {
|
||||
return bitValue === 1
|
||||
}
|
||||
|
||||
async calculateActivityRetention(
|
||||
activity: AnalyticsActivity,
|
||||
firstPeriod: Period,
|
||||
secondPeriod: Period,
|
||||
): Promise<number> {
|
||||
const initialPeriodKey = this.periodKeyGenerator.getPeriodKey(firstPeriod)
|
||||
const subsequentPeriodKey = this.periodKeyGenerator.getPeriodKey(secondPeriod)
|
||||
|
||||
const diffKey = `bitmap:action:${activity}:timespan:${initialPeriodKey}-${subsequentPeriodKey}`
|
||||
async calculateActivitiesRetention(parameters: {
|
||||
firstActivity: AnalyticsActivity
|
||||
firstActivityPeriodKey: string
|
||||
secondActivity: AnalyticsActivity
|
||||
secondActivityPeriodKey: string
|
||||
}): Promise<number> {
|
||||
const diffKey = `bitmap:action:${parameters.firstActivity}-${parameters.secondActivity}:timespan:${parameters.secondActivityPeriodKey}`
|
||||
|
||||
await this.redisClient.bitop(
|
||||
'AND',
|
||||
diffKey,
|
||||
`bitmap:action:${activity}:timespan:${initialPeriodKey}`,
|
||||
`bitmap:action:${activity}:timespan:${subsequentPeriodKey}`,
|
||||
`bitmap:action:${parameters.firstActivity}:timespan:${parameters.firstActivityPeriodKey}`,
|
||||
`bitmap:action:${parameters.secondActivity}:timespan:${parameters.secondActivityPeriodKey}`,
|
||||
)
|
||||
|
||||
await this.redisClient.expire(diffKey, 3600)
|
||||
@@ -117,12 +115,25 @@ export class RedisAnalyticsStore implements AnalyticsStoreInterface {
|
||||
const retainedTotalInActivity = await this.redisClient.bitcount(diffKey)
|
||||
|
||||
const initialTotalInActivity = await this.redisClient.bitcount(
|
||||
`bitmap:action:${activity}:timespan:${initialPeriodKey}`,
|
||||
`bitmap:action:${parameters.firstActivity}:timespan:${parameters.firstActivityPeriodKey}`,
|
||||
)
|
||||
|
||||
return Math.ceil((retainedTotalInActivity * 100) / initialTotalInActivity)
|
||||
}
|
||||
|
||||
async calculateActivityRetention(
|
||||
activity: AnalyticsActivity,
|
||||
firstPeriod: Period,
|
||||
secondPeriod: Period,
|
||||
): Promise<number> {
|
||||
return this.calculateActivitiesRetention({
|
||||
firstActivity: activity,
|
||||
firstActivityPeriodKey: this.periodKeyGenerator.getPeriodKey(firstPeriod),
|
||||
secondActivity: activity,
|
||||
secondActivityPeriodKey: this.periodKeyGenerator.getPeriodKey(secondPeriod),
|
||||
})
|
||||
}
|
||||
|
||||
async calculateActivityTotalCount(activity: AnalyticsActivity, period: Period): Promise<number> {
|
||||
return this.redisClient.bitcount(
|
||||
`bitmap:action:${activity}:timespan:${this.periodKeyGenerator.getPeriodKey(period)}`,
|
||||
|
||||
@@ -93,7 +93,7 @@ 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.incrbyfloat).toHaveBeenCalledTimes(2)
|
||||
@@ -103,18 +103,18 @@ describe('RedisStatisticsStore', () => {
|
||||
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)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -3,6 +3,80 @@
|
||||
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
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
AnalyticsActivity,
|
||||
AnalyticsStoreInterface,
|
||||
Period,
|
||||
PeriodKeyGeneratorInterface,
|
||||
StatisticsMeasure,
|
||||
StatisticsStoreInterface,
|
||||
} from '@standardnotes/analytics'
|
||||
@@ -24,6 +25,7 @@ const requestReport = async (
|
||||
analyticsStore: AnalyticsStoreInterface,
|
||||
statisticsStore: StatisticsStoreInterface,
|
||||
domainEventPublisher: DomainEventPublisherInterface,
|
||||
periodKeyGenerator: PeriodKeyGeneratorInterface,
|
||||
): Promise<void> => {
|
||||
const analyticsOverTime = []
|
||||
|
||||
@@ -68,6 +70,8 @@ const requestReport = async (
|
||||
const yesterdayActivityNames = [
|
||||
AnalyticsActivity.LimitedDiscountOfferPurchased,
|
||||
AnalyticsActivity.GeneralActivity,
|
||||
AnalyticsActivity.GeneralActivityFreeUsers,
|
||||
AnalyticsActivity.GeneralActivityPaidUsers,
|
||||
AnalyticsActivity.PaymentFailed,
|
||||
AnalyticsActivity.PaymentSuccess,
|
||||
]
|
||||
@@ -89,6 +93,10 @@ const requestReport = async (
|
||||
StatisticsMeasure.Refunds,
|
||||
StatisticsMeasure.RegistrationLength,
|
||||
StatisticsMeasure.SubscriptionLength,
|
||||
StatisticsMeasure.RegistrationToSubscriptionTime,
|
||||
StatisticsMeasure.NotesCountFreeUsers,
|
||||
StatisticsMeasure.NotesCountPaidUsers,
|
||||
StatisticsMeasure.FilesCount,
|
||||
]
|
||||
const statisticMeasures = []
|
||||
for (const statisticMeasureName of statisticMeasureNames) {
|
||||
@@ -102,6 +110,25 @@ const requestReport = async (
|
||||
}
|
||||
}
|
||||
|
||||
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(),
|
||||
@@ -119,6 +146,16 @@ const requestReport = async (
|
||||
activityStatistics: yesterdayActivityStatistics,
|
||||
activityStatisticsOverTime: analyticsOverTime,
|
||||
statisticMeasures,
|
||||
retentionStatistics: [
|
||||
{
|
||||
firstActivity: AnalyticsActivity.Register,
|
||||
secondActivity: AnalyticsActivity.GeneralActivity,
|
||||
retention: {
|
||||
periodKeys,
|
||||
values: retentionOverDays,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
@@ -137,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')
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/api-gateway",
|
||||
"version": "1.16.2",
|
||||
"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:*",
|
||||
|
||||
@@ -6,6 +6,7 @@ import * as AWS from 'aws-sdk'
|
||||
import {
|
||||
AnalyticsStoreInterface,
|
||||
PeriodKeyGenerator,
|
||||
PeriodKeyGeneratorInterface,
|
||||
RedisAnalyticsStore,
|
||||
RedisStatisticsStore,
|
||||
StatisticsStoreInterface,
|
||||
@@ -91,13 +92,13 @@ export class ContainerConfigLoader {
|
||||
|
||||
// Services
|
||||
container.bind<HttpServiceInterface>(TYPES.HTTPService).to(HttpService)
|
||||
const periodKeyGenerator = new PeriodKeyGenerator()
|
||||
container.bind<PeriodKeyGeneratorInterface>(TYPES.PeriodKeyGenerator).toConstantValue(new PeriodKeyGenerator())
|
||||
container
|
||||
.bind<AnalyticsStoreInterface>(TYPES.AnalyticsStore)
|
||||
.toConstantValue(new RedisAnalyticsStore(periodKeyGenerator, container.get(TYPES.Redis)))
|
||||
.toConstantValue(new RedisAnalyticsStore(container.get(TYPES.PeriodKeyGenerator), container.get(TYPES.Redis)))
|
||||
container
|
||||
.bind<StatisticsStoreInterface>(TYPES.StatisticsStore)
|
||||
.toConstantValue(new RedisStatisticsStore(periodKeyGenerator, container.get(TYPES.Redis)))
|
||||
.toConstantValue(new RedisStatisticsStore(container.get(TYPES.PeriodKeyGenerator), container.get(TYPES.Redis)))
|
||||
container.bind<CrossServiceTokenCacheInterface>(TYPES.CrossServiceTokenCache).to(RedisCrossServiceTokenCache)
|
||||
container.bind<TimerInterface>(TYPES.Timer).toConstantValue(new Timer())
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ const TYPES = {
|
||||
StatisticsStore: Symbol.for('StatisticsStore'),
|
||||
DomainEventPublisher: Symbol.for('DomainEventPublisher'),
|
||||
Timer: Symbol.for('Timer'),
|
||||
PeriodKeyGenerator: Symbol.for('PeriodKeyGenerator'),
|
||||
}
|
||||
|
||||
export default TYPES
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { CrossServiceTokenData } from '@standardnotes/security'
|
||||
import { RoleName } from '@standardnotes/common'
|
||||
import { AnalyticsActivity, AnalyticsStoreInterface, Period } from '@standardnotes/analytics'
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
import { NextFunction, Request, Response } from 'express'
|
||||
@@ -75,9 +76,20 @@ export class AuthMiddleware extends BaseMiddleware {
|
||||
|
||||
const decodedToken = <CrossServiceTokenData>verify(crossServiceToken, this.jwtSecret, { algorithms: ['HS256'] })
|
||||
|
||||
await this.analyticsStore.markActivity([AnalyticsActivity.GeneralActivity], decodedToken.analyticsId as number, [
|
||||
Period.Today,
|
||||
])
|
||||
response.locals.freeUser =
|
||||
decodedToken.roles.length === 1 &&
|
||||
decodedToken.roles.find((role) => role.name === RoleName.CoreUser) !== undefined
|
||||
|
||||
await this.analyticsStore.markActivity(
|
||||
[
|
||||
AnalyticsActivity.GeneralActivity,
|
||||
response.locals.freeUser
|
||||
? AnalyticsActivity.GeneralActivityFreeUsers
|
||||
: AnalyticsActivity.GeneralActivityPaidUsers,
|
||||
],
|
||||
decodedToken.analyticsId as number,
|
||||
[Period.Today],
|
||||
)
|
||||
|
||||
if (this.crossServiceTokenCacheTTL && !crossServiceTokenFetchedFromCache) {
|
||||
await this.crossServiceTokenCache.set({
|
||||
|
||||
@@ -3,6 +3,56 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/auth-server",
|
||||
"version": "1.24.4",
|
||||
"version": "1.25.11",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -16,9 +16,10 @@ import { OfflineUserSubscriptionRepositoryInterface } from '../Subscription/Offl
|
||||
import { OfflineUserSubscription } from '../Subscription/OfflineUserSubscription'
|
||||
import { SubscriptionSettingServiceInterface } from '../Setting/SubscriptionSettingServiceInterface'
|
||||
import { UserSubscriptionType } from '../Subscription/UserSubscriptionType'
|
||||
import { AnalyticsStoreInterface, Period } from '@standardnotes/analytics'
|
||||
import { AnalyticsStoreInterface, Period, StatisticsStoreInterface } from '@standardnotes/analytics'
|
||||
import { AnalyticsEntity } from '../Analytics/AnalyticsEntity'
|
||||
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
|
||||
describe('SubscriptionPurchasedEventHandler', () => {
|
||||
let userRepository: UserRepositoryInterface
|
||||
@@ -35,6 +36,8 @@ describe('SubscriptionPurchasedEventHandler', () => {
|
||||
let getUserAnalyticsId: GetUserAnalyticsId
|
||||
let analyticsStore: AnalyticsStoreInterface
|
||||
let timestamp: number
|
||||
let statisticsStore: StatisticsStoreInterface
|
||||
let timer: TimerInterface
|
||||
|
||||
const createHandler = () =>
|
||||
new SubscriptionPurchasedEventHandler(
|
||||
@@ -45,6 +48,8 @@ describe('SubscriptionPurchasedEventHandler', () => {
|
||||
subscriptionSettingService,
|
||||
getUserAnalyticsId,
|
||||
analyticsStore,
|
||||
statisticsStore,
|
||||
timer,
|
||||
logger,
|
||||
)
|
||||
|
||||
@@ -66,7 +71,14 @@ describe('SubscriptionPurchasedEventHandler', () => {
|
||||
userRepository.findOneByEmail = jest.fn().mockReturnValue(user)
|
||||
userRepository.save = jest.fn().mockReturnValue(user)
|
||||
|
||||
statisticsStore = {} as jest.Mocked<StatisticsStoreInterface>
|
||||
statisticsStore.incrementMeasure = jest.fn()
|
||||
|
||||
timer = {} as jest.Mocked<TimerInterface>
|
||||
timer.convertDateToMicroseconds = jest.fn().mockReturnValue(1)
|
||||
|
||||
userSubscriptionRepository = {} as jest.Mocked<UserSubscriptionRepositoryInterface>
|
||||
userSubscriptionRepository.countByUserUuid = jest.fn().mockReturnValue(0)
|
||||
userSubscriptionRepository.save = jest.fn().mockReturnValue(subscription)
|
||||
|
||||
offlineUserSubscription = {} as jest.Mocked<OfflineUserSubscription>
|
||||
@@ -146,6 +158,15 @@ describe('SubscriptionPurchasedEventHandler', () => {
|
||||
updatedAt: expect.any(Number),
|
||||
cancelled: false,
|
||||
})
|
||||
expect(statisticsStore.incrementMeasure).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("should not measure registration to subscription time if this is not user's first subscription", async () => {
|
||||
userSubscriptionRepository.countByUserUuid = jest.fn().mockReturnValue(1)
|
||||
|
||||
await createHandler().handle(event)
|
||||
|
||||
expect(statisticsStore.incrementMeasure).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should update analytics on limited discount offer purchasing', async () => {
|
||||
|
||||
@@ -13,8 +13,15 @@ import { OfflineUserSubscription } from '../Subscription/OfflineUserSubscription
|
||||
import { OfflineUserSubscriptionRepositoryInterface } from '../Subscription/OfflineUserSubscriptionRepositoryInterface'
|
||||
import { UserSubscriptionType } from '../Subscription/UserSubscriptionType'
|
||||
import { SubscriptionSettingServiceInterface } from '../Setting/SubscriptionSettingServiceInterface'
|
||||
import { AnalyticsActivity, AnalyticsStoreInterface, Period } from '@standardnotes/analytics'
|
||||
import {
|
||||
AnalyticsActivity,
|
||||
AnalyticsStoreInterface,
|
||||
Period,
|
||||
StatisticsMeasure,
|
||||
StatisticsStoreInterface,
|
||||
} from '@standardnotes/analytics'
|
||||
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
|
||||
@injectable()
|
||||
export class SubscriptionPurchasedEventHandler implements DomainEventHandlerInterface {
|
||||
@@ -27,6 +34,8 @@ export class SubscriptionPurchasedEventHandler implements DomainEventHandlerInte
|
||||
@inject(TYPES.SubscriptionSettingService) private subscriptionSettingService: SubscriptionSettingServiceInterface,
|
||||
@inject(TYPES.GetUserAnalyticsId) private getUserAnalyticsId: GetUserAnalyticsId,
|
||||
@inject(TYPES.AnalyticsStore) private analyticsStore: AnalyticsStoreInterface,
|
||||
@inject(TYPES.StatisticsStore) private statisticsStore: StatisticsStoreInterface,
|
||||
@inject(TYPES.Timer) private timer: TimerInterface,
|
||||
@inject(TYPES.Logger) private logger: Logger,
|
||||
) {}
|
||||
|
||||
@@ -52,6 +61,8 @@ export class SubscriptionPurchasedEventHandler implements DomainEventHandlerInte
|
||||
return
|
||||
}
|
||||
|
||||
const previousSubscriptionCount = await this.userSubscriptionRepository.countByUserUuid(user.uuid)
|
||||
|
||||
const userSubscription = await this.createSubscription(
|
||||
event.payload.subscriptionId,
|
||||
event.payload.subscriptionName,
|
||||
@@ -80,6 +91,14 @@ export class SubscriptionPurchasedEventHandler implements DomainEventHandlerInte
|
||||
Period.Today,
|
||||
])
|
||||
}
|
||||
|
||||
if (previousSubscriptionCount === 0) {
|
||||
await this.statisticsStore.incrementMeasure(
|
||||
StatisticsMeasure.RegistrationToSubscriptionTime,
|
||||
event.payload.timestamp - this.timer.convertDateToMicroseconds(user.createdAt),
|
||||
[Period.Today, Period.ThisWeek, Period.ThisMonth],
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private async addUserRole(user: User, subscriptionName: SubscriptionName): Promise<void> {
|
||||
|
||||
@@ -4,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[]>
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/common",
|
||||
"version": "1.31.0",
|
||||
"version": "1.32.0",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
3
packages/common/src/Domain/Type/Either.ts
Normal file
3
packages/common/src/Domain/Type/Either.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { Only } from './Only'
|
||||
|
||||
export type Either<T, U> = Only<T, U> | Only<U, T>
|
||||
5
packages/common/src/Domain/Type/Only.ts
Normal file
5
packages/common/src/Domain/Type/Only.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export type Only<T, U> = {
|
||||
[P in keyof T]: T[P]
|
||||
} & {
|
||||
[P in keyof U]?: never
|
||||
}
|
||||
@@ -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'
|
||||
|
||||
@@ -3,6 +3,30 @@
|
||||
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,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/domain-events-infra",
|
||||
"version": "1.8.3",
|
||||
"version": "1.8.9",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,40 @@
|
||||
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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/domain-events",
|
||||
"version": "2.59.1",
|
||||
"version": "2.60.3",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -28,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
|
||||
}>
|
||||
}
|
||||
}>
|
||||
}
|
||||
|
||||
@@ -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.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,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/event-store",
|
||||
"version": "1.3.6",
|
||||
"version": "1.3.14",
|
||||
"description": "Event Store Service",
|
||||
"private": true,
|
||||
"main": "dist/src/index.js",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -3,6 +3,30 @@
|
||||
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,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/files-server",
|
||||
"version": "1.5.45",
|
||||
"version": "1.5.51",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/predicates",
|
||||
"version": "1.4.0",
|
||||
"version": "1.4.1",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,30 @@
|
||||
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,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/scheduler-server",
|
||||
"version": "1.10.22",
|
||||
"version": "1.10.28",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/security",
|
||||
"version": "1.3.0",
|
||||
"version": "1.3.1",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -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.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 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/syncing-server",
|
||||
"version": "1.6.62",
|
||||
"version": "1.8.1",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -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()
|
||||
})
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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,
|
||||
])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,4 +4,6 @@ import { IntegrityPayload } from '@standardnotes/payloads'
|
||||
export type CheckIntegrityDTO = {
|
||||
userUuid: Uuid
|
||||
integrityPayloads: IntegrityPayload[]
|
||||
freeUser: boolean
|
||||
analyticsId: number
|
||||
}
|
||||
|
||||
@@ -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:*"
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user