mirror of
https://github.com/standardnotes/server
synced 2026-04-23 03:01:42 -04:00
Compare commits
74 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 684ffbadbc | |||
| 1c4d4c57de | |||
| d83111a199 | |||
| f10fa839fb | |||
| 1f20395ff3 | |||
| bfe6f4255a | |||
| b9032f3012 | |||
| ce53c459e6 | |||
| 6df42fb0d5 | |||
| 1e2b496f4f | |||
| 528c1b0d57 | |||
| 22fba8ba80 | |||
| 6f26261ebe | |||
| 4b1fe3ba91 | |||
| 9f95262bd4 | |||
| 2ec28e541e | |||
| 4764d4b19a | |||
| 9b27547dae | |||
| a96f2c9153 | |||
| 225e0aaf88 | |||
| f0c85910bc | |||
| 124c443528 | |||
| 37c7f8d39f | |||
| c419f1ce22 | |||
| 4949cdfe2f | |||
| cd101b96ea | |||
| 40d0e4631f | |||
| a55a995660 | |||
| 1d576d48ad | |||
| 4ff8030f87 | |||
| c15e2e2c8f | |||
| 41d31a8d75 | |||
| 10e2a26352 | |||
| 6e547f77d0 | |||
| 530a426601 | |||
| 642d6bab77 | |||
| 7980af3d82 | |||
| 2980c42e88 | |||
| b03994f9db | |||
| 41906ec2f9 | |||
| 4d1e7ff2a5 | |||
| 7f18fcfc13 | |||
| ff02ce0747 | |||
| a6056600eb | |||
| 24c94326d5 | |||
| 48c0cb5e62 | |||
| 9968efe1b2 | |||
| 6368342149 | |||
| b5f73db210 | |||
| 22d6a02d04 | |||
| 4e0bcfcccf | |||
| 104313c15d | |||
| 814289af46 | |||
| 3096cd98d5 | |||
| 45dfefbc7a | |||
| 20d92149a8 | |||
| 9c01fffca5 | |||
| 61c1cfff4b | |||
| 7e74261f62 | |||
| 32601f34f1 | |||
| aef69a1a96 | |||
| 130f90bdb6 | |||
| 851c7de87f | |||
| 118156c62d | |||
| cdad3143c9 | |||
| 00fe32136e | |||
| 52f56eeb68 | |||
| b595264e31 | |||
| bf04262170 | |||
| fd589922bb | |||
| fb7029f5c1 | |||
| cc4b4f9bf8 | |||
| b048d6d7e3 | |||
| cffc1f442f |
@@ -187,7 +187,7 @@ jobs:
|
|||||||
tags: standardnotes/${{ inputs.service_name }}:${{ github.sha }}
|
tags: standardnotes/${{ inputs.service_name }}:${{ github.sha }}
|
||||||
|
|
||||||
- name: Run E2E test suite
|
- name: Run E2E test suite
|
||||||
uses: convictional/trigger-workflow-and-wait@v1.6.3
|
uses: convictional/trigger-workflow-and-wait@master
|
||||||
with:
|
with:
|
||||||
owner: standardnotes
|
owner: standardnotes
|
||||||
repo: e2e
|
repo: e2e
|
||||||
|
|||||||
@@ -2737,10 +2737,6 @@ const RAW_RUNTIME_STATE =
|
|||||||
"packageLocation": "./packages/domain-core/",\
|
"packageLocation": "./packages/domain-core/",\
|
||||||
"packageDependencies": [\
|
"packageDependencies": [\
|
||||||
["@standardnotes/domain-core", "workspace:packages/domain-core"],\
|
["@standardnotes/domain-core", "workspace:packages/domain-core"],\
|
||||||
["@standardnotes/common", "workspace:packages/common"],\
|
|
||||||
["@standardnotes/features", "npm:1.53.1"],\
|
|
||||||
["@standardnotes/predicates", "workspace:packages/predicates"],\
|
|
||||||
["@standardnotes/security", "workspace:packages/security"],\
|
|
||||||
["@types/jest", "npm:29.1.1"],\
|
["@types/jest", "npm:29.1.1"],\
|
||||||
["@types/uuid", "npm:8.3.4"],\
|
["@types/uuid", "npm:8.3.4"],\
|
||||||
["@typescript-eslint/eslint-plugin", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:5.30.5"],\
|
["@typescript-eslint/eslint-plugin", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:5.30.5"],\
|
||||||
@@ -3324,6 +3320,7 @@ const RAW_RUNTIME_STATE =
|
|||||||
["@sentry/node", "npm:7.19.0"],\
|
["@sentry/node", "npm:7.19.0"],\
|
||||||
["@standardnotes/api", "npm:1.19.0"],\
|
["@standardnotes/api", "npm:1.19.0"],\
|
||||||
["@standardnotes/common", "workspace:packages/common"],\
|
["@standardnotes/common", "workspace:packages/common"],\
|
||||||
|
["@standardnotes/domain-core", "workspace:packages/domain-core"],\
|
||||||
["@standardnotes/domain-events", "workspace:packages/domain-events"],\
|
["@standardnotes/domain-events", "workspace:packages/domain-events"],\
|
||||||
["@standardnotes/domain-events-infra", "workspace:packages/domain-events-infra"],\
|
["@standardnotes/domain-events-infra", "workspace:packages/domain-events-infra"],\
|
||||||
["@standardnotes/models", "npm:1.28.0"],\
|
["@standardnotes/models", "npm:1.28.0"],\
|
||||||
|
|||||||
@@ -3,6 +3,72 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [2.12.26](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.25...@standardnotes/analytics@2.12.26) (2022-12-15)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.12.25](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.24...@standardnotes/analytics@2.12.25) (2022-12-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.12.24](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.23...@standardnotes/analytics@2.12.24) (2022-12-12)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **analytics:** daily analytics report template ([41906ec](https://github.com/standardnotes/server/commit/41906ec2f9fd4d605b1c002826173e14fb534e00))
|
||||||
|
|
||||||
|
## [2.12.23](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.22...@standardnotes/analytics@2.12.23) (2022-12-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.12.22](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.21...@standardnotes/analytics@2.12.22) (2022-12-12)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **analytics:** report event publishing ([a605660](https://github.com/standardnotes/server/commit/a6056600eb96bf175189ad6d62870c9d736f331b))
|
||||||
|
|
||||||
|
## [2.12.21](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.20...@standardnotes/analytics@2.12.21) (2022-12-12)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **analytics:** add debug logs for report ([48c0cb5](https://github.com/standardnotes/server/commit/48c0cb5e62dc8af930de191deaa1eb3ff6c5a29f))
|
||||||
|
|
||||||
|
## [2.12.20](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.19...@standardnotes/analytics@2.12.20) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.12.19](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.18...@standardnotes/analytics@2.12.19) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.12.18](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.17...@standardnotes/analytics@2.12.18) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.12.17](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.16...@standardnotes/analytics@2.12.17) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.12.16](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.15...@standardnotes/analytics@2.12.16) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.12.15](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.14...@standardnotes/analytics@2.12.15) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.12.14](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.13...@standardnotes/analytics@2.12.14) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.12.13](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.12...@standardnotes/analytics@2.12.13) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
|
## [2.12.12](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.11...@standardnotes/analytics@2.12.12) (2022-12-08)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|
||||||
## [2.12.11](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.10...@standardnotes/analytics@2.12.11) (2022-12-08)
|
## [2.12.11](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.10...@standardnotes/analytics@2.12.11) (2022-12-08)
|
||||||
|
|
||||||
**Note:** Version bump only for package @standardnotes/analytics
|
**Note:** Version bump only for package @standardnotes/analytics
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import 'newrelic'
|
|||||||
|
|
||||||
import { Logger } from 'winston'
|
import { Logger } from 'winston'
|
||||||
|
|
||||||
|
import { EmailLevel } from '@standardnotes/domain-core'
|
||||||
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
|
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
|
||||||
import { AnalyticsActivity } from '../src/Domain/Analytics/AnalyticsActivity'
|
import { AnalyticsActivity } from '../src/Domain/Analytics/AnalyticsActivity'
|
||||||
import { Period } from '../src/Domain/Time/Period'
|
import { Period } from '../src/Domain/Time/Period'
|
||||||
@@ -16,6 +17,8 @@ import TYPES from '../src/Bootstrap/Types'
|
|||||||
import { Env } from '../src/Bootstrap/Env'
|
import { Env } from '../src/Bootstrap/Env'
|
||||||
import { DomainEventFactoryInterface } from '../src/Domain/Event/DomainEventFactoryInterface'
|
import { DomainEventFactoryInterface } from '../src/Domain/Event/DomainEventFactoryInterface'
|
||||||
import { CalculateMonthlyRecurringRevenue } from '../src/Domain/UseCase/CalculateMonthlyRecurringRevenue/CalculateMonthlyRecurringRevenue'
|
import { CalculateMonthlyRecurringRevenue } from '../src/Domain/UseCase/CalculateMonthlyRecurringRevenue/CalculateMonthlyRecurringRevenue'
|
||||||
|
import { getBody, getSubject } from '../src/Domain/Email/DailyAnalyticsReport'
|
||||||
|
import { TimerInterface } from '@standardnotes/time'
|
||||||
|
|
||||||
const requestReport = async (
|
const requestReport = async (
|
||||||
analyticsStore: AnalyticsStoreInterface,
|
analyticsStore: AnalyticsStoreInterface,
|
||||||
@@ -24,6 +27,8 @@ const requestReport = async (
|
|||||||
domainEventPublisher: DomainEventPublisherInterface,
|
domainEventPublisher: DomainEventPublisherInterface,
|
||||||
periodKeyGenerator: PeriodKeyGeneratorInterface,
|
periodKeyGenerator: PeriodKeyGeneratorInterface,
|
||||||
calculateMonthlyRecurringRevenue: CalculateMonthlyRecurringRevenue,
|
calculateMonthlyRecurringRevenue: CalculateMonthlyRecurringRevenue,
|
||||||
|
timer: TimerInterface,
|
||||||
|
adminEmails: string[],
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
await calculateMonthlyRecurringRevenue.execute({})
|
await calculateMonthlyRecurringRevenue.execute({})
|
||||||
|
|
||||||
@@ -213,18 +218,29 @@ const requestReport = async (
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const event = domainEventFactory.createDailyAnalyticsReportGeneratedEvent({
|
for (const adminEmail of adminEmails) {
|
||||||
activityStatistics: yesterdayActivityStatistics,
|
await domainEventPublisher.publish(
|
||||||
activityStatisticsOverTime: analyticsOverTime,
|
domainEventFactory.createEmailRequestedEvent({
|
||||||
statisticsOverTime,
|
messageIdentifier: 'VERSION_ADOPTION_REPORT',
|
||||||
statisticMeasures,
|
subject: getSubject(),
|
||||||
churn: {
|
body: getBody(
|
||||||
periodKeys: monthlyPeriodKeys,
|
{
|
||||||
values: churnRates,
|
activityStatistics: yesterdayActivityStatistics,
|
||||||
},
|
activityStatisticsOverTime: analyticsOverTime,
|
||||||
})
|
statisticsOverTime,
|
||||||
|
statisticMeasures,
|
||||||
await domainEventPublisher.publish(event)
|
churn: {
|
||||||
|
periodKeys: monthlyPeriodKeys,
|
||||||
|
values: churnRates,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
timer,
|
||||||
|
),
|
||||||
|
level: EmailLevel.LEVELS.System,
|
||||||
|
userEmail: adminEmail,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const container = new ContainerConfigLoader()
|
const container = new ContainerConfigLoader()
|
||||||
@@ -241,9 +257,13 @@ void container.load().then((container) => {
|
|||||||
const domainEventFactory: DomainEventFactoryInterface = container.get(TYPES.DomainEventFactory)
|
const domainEventFactory: DomainEventFactoryInterface = container.get(TYPES.DomainEventFactory)
|
||||||
const domainEventPublisher: DomainEventPublisherInterface = container.get(TYPES.DomainEventPublisher)
|
const domainEventPublisher: DomainEventPublisherInterface = container.get(TYPES.DomainEventPublisher)
|
||||||
const periodKeyGenerator: PeriodKeyGeneratorInterface = container.get(TYPES.PeriodKeyGenerator)
|
const periodKeyGenerator: PeriodKeyGeneratorInterface = container.get(TYPES.PeriodKeyGenerator)
|
||||||
|
const timer: TimerInterface = container.get(TYPES.Timer)
|
||||||
const calculateMonthlyRecurringRevenue: CalculateMonthlyRecurringRevenue = container.get(
|
const calculateMonthlyRecurringRevenue: CalculateMonthlyRecurringRevenue = container.get(
|
||||||
TYPES.CalculateMonthlyRecurringRevenue,
|
TYPES.CalculateMonthlyRecurringRevenue,
|
||||||
)
|
)
|
||||||
|
const adminEmails = container.get(TYPES.ADMIN_EMAILS) as string[]
|
||||||
|
|
||||||
|
logger.info(`Sending report to following admins: ${adminEmails}`)
|
||||||
|
|
||||||
Promise.resolve(
|
Promise.resolve(
|
||||||
requestReport(
|
requestReport(
|
||||||
@@ -253,6 +273,8 @@ void container.load().then((container) => {
|
|||||||
domainEventPublisher,
|
domainEventPublisher,
|
||||||
periodKeyGenerator,
|
periodKeyGenerator,
|
||||||
calculateMonthlyRecurringRevenue,
|
calculateMonthlyRecurringRevenue,
|
||||||
|
timer,
|
||||||
|
adminEmails,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
|
|||||||
@@ -5,17 +5,17 @@ COMMAND=$1 && shift 1
|
|||||||
|
|
||||||
case "$COMMAND" in
|
case "$COMMAND" in
|
||||||
'start-worker' )
|
'start-worker' )
|
||||||
echo "Starting Worker..."
|
echo "[Docker] Starting Worker..."
|
||||||
yarn workspace @standardnotes/analytics worker
|
yarn workspace @standardnotes/analytics worker
|
||||||
;;
|
;;
|
||||||
|
|
||||||
'report' )
|
'report' )
|
||||||
echo "Starting Usage Report Generation..."
|
echo "[Docker] Starting Usage Report Generation..."
|
||||||
yarn workspace @standardnotes/analytics report
|
yarn workspace @standardnotes/analytics report
|
||||||
;;
|
;;
|
||||||
|
|
||||||
* )
|
* )
|
||||||
echo "Unknown command"
|
echo "[Docker] Unknown command"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
|
|||||||
@@ -7,5 +7,5 @@ module.exports = {
|
|||||||
transform: {
|
transform: {
|
||||||
...tsjPreset.transform,
|
...tsjPreset.transform,
|
||||||
},
|
},
|
||||||
coveragePathIgnorePatterns: ['/Infra/'],
|
coveragePathIgnorePatterns: ['/Infra/', '/Domain/Email/'],
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/analytics",
|
"name": "@standardnotes/analytics",
|
||||||
"version": "2.12.11",
|
"version": "2.12.26",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0 <19.0.0"
|
"node": ">=18.0.0 <19.0.0"
|
||||||
},
|
},
|
||||||
@@ -40,7 +40,7 @@
|
|||||||
"@newrelic/winston-enricher": "^4.0.0",
|
"@newrelic/winston-enricher": "^4.0.0",
|
||||||
"@sentry/node": "^7.19.0",
|
"@sentry/node": "^7.19.0",
|
||||||
"@standardnotes/common": "workspace:*",
|
"@standardnotes/common": "workspace:*",
|
||||||
"@standardnotes/domain-core": "workspace:*",
|
"@standardnotes/domain-core": "workspace:^",
|
||||||
"@standardnotes/domain-events": "workspace:*",
|
"@standardnotes/domain-events": "workspace:*",
|
||||||
"@standardnotes/domain-events-infra": "workspace:*",
|
"@standardnotes/domain-events-infra": "workspace:*",
|
||||||
"@standardnotes/time": "workspace:*",
|
"@standardnotes/time": "workspace:*",
|
||||||
|
|||||||
@@ -130,6 +130,7 @@ export class ContainerConfigLoader {
|
|||||||
container.bind(TYPES.SQS_QUEUE_URL).toConstantValue(env.get('SQS_QUEUE_URL', true))
|
container.bind(TYPES.SQS_QUEUE_URL).toConstantValue(env.get('SQS_QUEUE_URL', true))
|
||||||
container.bind(TYPES.REDIS_EVENTS_CHANNEL).toConstantValue(env.get('REDIS_EVENTS_CHANNEL'))
|
container.bind(TYPES.REDIS_EVENTS_CHANNEL).toConstantValue(env.get('REDIS_EVENTS_CHANNEL'))
|
||||||
container.bind(TYPES.NEW_RELIC_ENABLED).toConstantValue(env.get('NEW_RELIC_ENABLED', true))
|
container.bind(TYPES.NEW_RELIC_ENABLED).toConstantValue(env.get('NEW_RELIC_ENABLED', true))
|
||||||
|
container.bind(TYPES.ADMIN_EMAILS).toConstantValue(env.get('ADMIN_EMAILS').split(','))
|
||||||
|
|
||||||
// Repositories
|
// Repositories
|
||||||
container
|
container
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ const TYPES = {
|
|||||||
SQS_AWS_REGION: Symbol.for('SQS_AWS_REGION'),
|
SQS_AWS_REGION: Symbol.for('SQS_AWS_REGION'),
|
||||||
REDIS_EVENTS_CHANNEL: Symbol.for('REDIS_EVENTS_CHANNEL'),
|
REDIS_EVENTS_CHANNEL: Symbol.for('REDIS_EVENTS_CHANNEL'),
|
||||||
NEW_RELIC_ENABLED: Symbol.for('NEW_RELIC_ENABLED'),
|
NEW_RELIC_ENABLED: Symbol.for('NEW_RELIC_ENABLED'),
|
||||||
|
ADMIN_EMAILS: Symbol.for('ADMIN_EMAILS'),
|
||||||
// Repositories
|
// Repositories
|
||||||
AnalyticsEntityRepository: Symbol.for('AnalyticsEntityRepository'),
|
AnalyticsEntityRepository: Symbol.for('AnalyticsEntityRepository'),
|
||||||
RevenueModificationRepository: Symbol.for('RevenueModificationRepository'),
|
RevenueModificationRepository: Symbol.for('RevenueModificationRepository'),
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { TimerInterface } from '@standardnotes/time'
|
||||||
|
|
||||||
|
import { html } from './daily-analytics-report.html'
|
||||||
|
|
||||||
|
export function getSubject(): string {
|
||||||
|
return `Daily analytics report ${new Date().toLocaleDateString('en-US')}`
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getBody(data: unknown, timer: TimerInterface): string {
|
||||||
|
return html(data, timer)
|
||||||
|
}
|
||||||
@@ -0,0 +1,966 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
import { TimerInterface } from '@standardnotes/time'
|
||||||
|
|
||||||
|
import { AnalyticsActivity } from '../Analytics/AnalyticsActivity'
|
||||||
|
import { StatisticsMeasure } from '../Statistics/StatisticsMeasure'
|
||||||
|
import { Period } from '../Time/Period'
|
||||||
|
|
||||||
|
const getChartUrls = (
|
||||||
|
data: any,
|
||||||
|
): {
|
||||||
|
subscriptions: string
|
||||||
|
users: string
|
||||||
|
quarterlyPerformance: string
|
||||||
|
churn: string
|
||||||
|
mrr: string
|
||||||
|
mrrMonthly: string
|
||||||
|
} => {
|
||||||
|
const subscriptionPurchasingOverTime = data.activityStatisticsOverTime.find(
|
||||||
|
(a: { name: AnalyticsActivity; period: Period }) =>
|
||||||
|
a.name === AnalyticsActivity.SubscriptionPurchased && a.period === Period.Last30Days,
|
||||||
|
)
|
||||||
|
const subscriptionRenewingOverTime = data.activityStatisticsOverTime.find(
|
||||||
|
(a: { name: AnalyticsActivity; period: Period }) =>
|
||||||
|
a.name === AnalyticsActivity.SubscriptionRenewed && a.period === Period.Last30Days,
|
||||||
|
)
|
||||||
|
const subscriptionRefundingOverTime = data.activityStatisticsOverTime.find(
|
||||||
|
(a: { name: AnalyticsActivity; period: Period }) =>
|
||||||
|
a.name === AnalyticsActivity.SubscriptionRefunded && a.period === Period.Last30Days,
|
||||||
|
)
|
||||||
|
const subscriptionCancelledOverTime = data.activityStatisticsOverTime.find(
|
||||||
|
(a: { name: AnalyticsActivity; period: Period }) =>
|
||||||
|
a.name === AnalyticsActivity.SubscriptionCancelled && a.period === Period.Last30Days,
|
||||||
|
)
|
||||||
|
const subscriptionReactivatedOverTime = data.activityStatisticsOverTime.find(
|
||||||
|
(a: { name: AnalyticsActivity; period: Period }) =>
|
||||||
|
a.name === AnalyticsActivity.SubscriptionReactivated && a.period === Period.Last30Days,
|
||||||
|
)
|
||||||
|
|
||||||
|
const subscriptionsLinerOverTimeConfig = {
|
||||||
|
type: 'line',
|
||||||
|
data: {
|
||||||
|
labels: subscriptionPurchasingOverTime?.counts.map((count: { periodKey: any }) => count.periodKey),
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: 'Subscription Purchases',
|
||||||
|
backgroundColor: 'rgb(25, 255, 140)',
|
||||||
|
borderColor: 'rgb(25, 255, 140)',
|
||||||
|
data: subscriptionPurchasingOverTime?.counts.map((count: { totalCount: any }) => count.totalCount),
|
||||||
|
fill: false,
|
||||||
|
pointRadius: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Subscription Renewals',
|
||||||
|
backgroundColor: 'rgb(54, 162, 235)',
|
||||||
|
borderColor: 'rgb(54, 162, 235)',
|
||||||
|
data: subscriptionRenewingOverTime?.counts.map((count: { totalCount: any }) => count.totalCount),
|
||||||
|
fill: false,
|
||||||
|
pointRadius: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Subscription Refunds',
|
||||||
|
backgroundColor: 'rgb(255, 221, 51)',
|
||||||
|
borderColor: 'rgb(255, 221, 51)',
|
||||||
|
data: subscriptionRefundingOverTime?.counts.map((count: { totalCount: any }) => count.totalCount),
|
||||||
|
fill: false,
|
||||||
|
pointRadius: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Subscription Cancels',
|
||||||
|
backgroundColor: 'rgb(255, 99, 132)',
|
||||||
|
borderColor: 'rgb(255, 99, 132)',
|
||||||
|
data: subscriptionCancelledOverTime?.counts.map((count: { totalCount: any }) => count.totalCount),
|
||||||
|
fill: false,
|
||||||
|
pointRadius: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Subscription Reactivations',
|
||||||
|
backgroundColor: 'rgb(221, 51, 255)',
|
||||||
|
borderColor: 'rgb(221, 51, 255)',
|
||||||
|
data: subscriptionReactivatedOverTime?.counts.map((count: { totalCount: any }) => count.totalCount),
|
||||||
|
fill: false,
|
||||||
|
pointRadius: 2,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const userRegistrationOverTime = data.activityStatisticsOverTime.find(
|
||||||
|
(a: { name: AnalyticsActivity; period: Period }) =>
|
||||||
|
a.name === AnalyticsActivity.Register && a.period === Period.Last30Days,
|
||||||
|
)
|
||||||
|
const userDeletionOverTime = data.activityStatisticsOverTime.find(
|
||||||
|
(a: { name: AnalyticsActivity; period: Period }) =>
|
||||||
|
a.name === AnalyticsActivity.DeleteAccount && a.period === Period.Last30Days,
|
||||||
|
)
|
||||||
|
|
||||||
|
const usersLinerOverTimeConfig = {
|
||||||
|
type: 'line',
|
||||||
|
data: {
|
||||||
|
labels: userRegistrationOverTime?.counts.map((count: { periodKey: any }) => count.periodKey),
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: 'User Registrations',
|
||||||
|
backgroundColor: 'rgb(25, 255, 140)',
|
||||||
|
borderColor: 'rgb(25, 255, 140)',
|
||||||
|
data: userRegistrationOverTime?.counts.map((count: { totalCount: any }) => count.totalCount),
|
||||||
|
fill: false,
|
||||||
|
pointRadius: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Account Deletions',
|
||||||
|
backgroundColor: 'rgb(255, 99, 132)',
|
||||||
|
borderColor: 'rgb(255, 99, 132)',
|
||||||
|
data: userDeletionOverTime?.counts.map((count: { totalCount: any }) => count.totalCount),
|
||||||
|
fill: false,
|
||||||
|
pointRadius: 2,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const quarters = [Period.Q1ThisYear, Period.Q2ThisYear, Period.Q3ThisYear, Period.Q4ThisYear]
|
||||||
|
const quarterlyUserRegistrations = []
|
||||||
|
const quarterlySubscriptionPurchases = []
|
||||||
|
const quarterlySubscriptionRenewals = []
|
||||||
|
for (const quarter of quarters) {
|
||||||
|
const registrations =
|
||||||
|
data.activityStatisticsOverTime.find(
|
||||||
|
(a: { name: AnalyticsActivity; period: Period }) =>
|
||||||
|
a.name === AnalyticsActivity.Register && a.period === quarter,
|
||||||
|
)?.totalCount ?? 0
|
||||||
|
const purchases =
|
||||||
|
data.activityStatisticsOverTime.find(
|
||||||
|
(a: { name: AnalyticsActivity; period: Period }) =>
|
||||||
|
a.name === AnalyticsActivity.SubscriptionPurchased && a.period === quarter,
|
||||||
|
)?.totalCount ?? 0
|
||||||
|
const renewals =
|
||||||
|
data.activityStatisticsOverTime.find(
|
||||||
|
(a: { name: AnalyticsActivity; period: Period }) =>
|
||||||
|
a.name === AnalyticsActivity.SubscriptionRenewed && a.period === quarter,
|
||||||
|
)?.totalCount ?? 0
|
||||||
|
quarterlyUserRegistrations.push(registrations)
|
||||||
|
quarterlySubscriptionPurchases.push(purchases)
|
||||||
|
quarterlySubscriptionRenewals.push(renewals)
|
||||||
|
}
|
||||||
|
|
||||||
|
const quarterlyConfig = {
|
||||||
|
type: 'bar',
|
||||||
|
data: {
|
||||||
|
labels: ['Q1', 'Q2', 'Q3', 'Q4'],
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: 'User Registrations',
|
||||||
|
backgroundColor: 'rgba(255, 99, 132, 0.5)',
|
||||||
|
borderColor: 'rgb(255, 99, 132)',
|
||||||
|
borderWidth: 1,
|
||||||
|
data: quarterlyUserRegistrations,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Subscription Purchases',
|
||||||
|
backgroundColor: 'rgba(54, 162, 235, 0.5)',
|
||||||
|
borderColor: 'rgb(54, 162, 235)',
|
||||||
|
borderWidth: 1,
|
||||||
|
data: quarterlySubscriptionPurchases,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Subscription Renewals',
|
||||||
|
backgroundColor: 'rgb(25, 255, 140, 0.5)',
|
||||||
|
borderColor: 'rgb(25, 255, 140)',
|
||||||
|
borderWidth: 1,
|
||||||
|
data: quarterlySubscriptionRenewals,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: 'Quarterly Performance',
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
datalabels: {
|
||||||
|
anchor: 'center',
|
||||||
|
align: 'center',
|
||||||
|
color: '#666',
|
||||||
|
font: {
|
||||||
|
weight: 'normal',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const monthlyChurnRates = data.churn.values.map((value: { rate: number }) => +value.rate.toFixed(2))
|
||||||
|
|
||||||
|
const churnConfig = {
|
||||||
|
type: 'bar',
|
||||||
|
data: {
|
||||||
|
labels: [
|
||||||
|
'January',
|
||||||
|
'February',
|
||||||
|
'March',
|
||||||
|
'April',
|
||||||
|
'May',
|
||||||
|
'June',
|
||||||
|
'July',
|
||||||
|
'August',
|
||||||
|
'September',
|
||||||
|
'October',
|
||||||
|
'November',
|
||||||
|
'December',
|
||||||
|
],
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: 'Churn Percent',
|
||||||
|
backgroundColor: 'rgba(255, 99, 132, 0.5)',
|
||||||
|
borderColor: 'rgb(255, 99, 132)',
|
||||||
|
borderWidth: 1,
|
||||||
|
data: monthlyChurnRates,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: 'Monthly Churn Rate',
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
datalabels: {
|
||||||
|
anchor: 'center',
|
||||||
|
align: 'center',
|
||||||
|
color: '#666',
|
||||||
|
font: {
|
||||||
|
weight: 'normal',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const mrrOverTime = data.statisticsOverTime.find(
|
||||||
|
(a: { name: string; period: number }) => a.name === 'mrr' && a.period === 27,
|
||||||
|
)
|
||||||
|
const monthlyPlansMrrOverTime = data.statisticsOverTime.find(
|
||||||
|
(a: { name: string; period: number }) => a.name === 'monthly-plans-mrr' && a.period === 27,
|
||||||
|
)
|
||||||
|
const annualPlansMrrOverTime = data.statisticsOverTime.find(
|
||||||
|
(a: { name: string; period: number }) => a.name === 'annual-plans-mrr' && a.period === 27,
|
||||||
|
)
|
||||||
|
const fiveYearPlansMrrOverTime = data.statisticsOverTime.find(
|
||||||
|
(a: { name: string; period: number }) => a.name === 'five-year-plans-mrr' && a.period === 27,
|
||||||
|
)
|
||||||
|
const proPlansMrrOverTime = data.statisticsOverTime.find(
|
||||||
|
(a: { name: string; period: number }) => a.name === 'pro-plans-mrr' && a.period === 27,
|
||||||
|
)
|
||||||
|
const plusPlansMrrOverTime = data.statisticsOverTime.find(
|
||||||
|
(a: { name: string; period: number }) => a.name === 'plus-plans-mrr' && a.period === 27,
|
||||||
|
)
|
||||||
|
|
||||||
|
const mrrOverTimeConfig = {
|
||||||
|
type: 'line',
|
||||||
|
data: {
|
||||||
|
labels: mrrOverTime?.counts.map((count: { periodKey: any }) => count.periodKey),
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: 'MRR',
|
||||||
|
backgroundColor: 'rgb(25, 255, 140)',
|
||||||
|
borderColor: 'rgb(25, 255, 140)',
|
||||||
|
data: mrrOverTime?.counts.map((count: { totalCount: any }) => count.totalCount),
|
||||||
|
fill: false,
|
||||||
|
pointRadius: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'MRR - Monthly Plans',
|
||||||
|
backgroundColor: 'rgb(54, 162, 235)',
|
||||||
|
borderColor: 'rgb(54, 162, 235)',
|
||||||
|
data: monthlyPlansMrrOverTime?.counts.map((count: { totalCount: any }) => count.totalCount),
|
||||||
|
fill: false,
|
||||||
|
pointRadius: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'MRR - Annual Plans',
|
||||||
|
backgroundColor: 'rgb(255, 221, 51)',
|
||||||
|
borderColor: 'rgb(255, 221, 51)',
|
||||||
|
data: annualPlansMrrOverTime?.counts.map((count: { totalCount: any }) => count.totalCount),
|
||||||
|
fill: false,
|
||||||
|
pointRadius: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'MRR - Five Year Plans',
|
||||||
|
backgroundColor: 'rgb(255, 120, 120)',
|
||||||
|
borderColor: 'rgb(255, 120, 120)',
|
||||||
|
data: fiveYearPlansMrrOverTime?.counts.map((count: { totalCount: any }) => count.totalCount),
|
||||||
|
fill: false,
|
||||||
|
pointRadius: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'MRR - PRO Plans',
|
||||||
|
backgroundColor: 'rgb(255, 99, 132)',
|
||||||
|
borderColor: 'rgb(255, 99, 132)',
|
||||||
|
data: proPlansMrrOverTime?.counts.map((count: { totalCount: any }) => count.totalCount),
|
||||||
|
fill: false,
|
||||||
|
pointRadius: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'MRR - PLUS Plans',
|
||||||
|
backgroundColor: 'rgb(221, 51, 255)',
|
||||||
|
borderColor: 'rgb(221, 51, 255)',
|
||||||
|
data: plusPlansMrrOverTime?.counts.map((count: { totalCount: any }) => count.totalCount),
|
||||||
|
fill: false,
|
||||||
|
pointRadius: 2,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const mrrMonthlyOverTime = data.statisticsOverTime
|
||||||
|
.find((a: { name: string; period: Period }) => a.name === 'mrr' && a.period === Period.ThisYear)
|
||||||
|
?.counts.map((count: { totalCount: number }) => +count.totalCount.toFixed(2))
|
||||||
|
|
||||||
|
const mrrMonthlyConfig = {
|
||||||
|
type: 'bar',
|
||||||
|
data: {
|
||||||
|
labels: [
|
||||||
|
'January',
|
||||||
|
'February',
|
||||||
|
'March',
|
||||||
|
'April',
|
||||||
|
'May',
|
||||||
|
'June',
|
||||||
|
'July',
|
||||||
|
'August',
|
||||||
|
'September',
|
||||||
|
'October',
|
||||||
|
'November',
|
||||||
|
'December',
|
||||||
|
],
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: 'MRR',
|
||||||
|
backgroundColor: 'rgba(25, 255, 140, 0.5)',
|
||||||
|
borderColor: 'rgb(25, 255, 140)',
|
||||||
|
borderWidth: 1,
|
||||||
|
data: mrrMonthlyOverTime,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: 'Monthly MRR',
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
datalabels: {
|
||||||
|
anchor: 'center',
|
||||||
|
align: 'center',
|
||||||
|
color: '#666',
|
||||||
|
font: {
|
||||||
|
weight: 'normal',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
subscriptions: `https://quickchart.io/chart?width=800&c=${encodeURIComponent(
|
||||||
|
JSON.stringify(subscriptionsLinerOverTimeConfig),
|
||||||
|
)}`,
|
||||||
|
users: `https://quickchart.io/chart?width=800&c=${encodeURIComponent(JSON.stringify(usersLinerOverTimeConfig))}`,
|
||||||
|
quarterlyPerformance: `https://quickchart.io/chart?width=800&c=${encodeURIComponent(
|
||||||
|
JSON.stringify(quarterlyConfig),
|
||||||
|
)}`,
|
||||||
|
churn: `https://quickchart.io/chart?width=800&c=${encodeURIComponent(JSON.stringify(churnConfig))}`,
|
||||||
|
mrr: `https://quickchart.io/chart?width=800&c=${encodeURIComponent(JSON.stringify(mrrOverTimeConfig))}`,
|
||||||
|
mrrMonthly: `https://quickchart.io/chart?width=800&c=${encodeURIComponent(JSON.stringify(mrrMonthlyConfig))}`,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const html = (data: any, timer: TimerInterface) => {
|
||||||
|
const chartUrls = getChartUrls(data)
|
||||||
|
|
||||||
|
const successfullPaymentsActivity = data.activityStatistics.find(
|
||||||
|
(a: { name: AnalyticsActivity }) => a.name === AnalyticsActivity.PaymentSuccess && Period.Yesterday,
|
||||||
|
)
|
||||||
|
const failedPaymentsActivity = data.activityStatistics.find(
|
||||||
|
(a: { name: AnalyticsActivity }) => a.name === AnalyticsActivity.PaymentFailed && Period.Yesterday,
|
||||||
|
)
|
||||||
|
const limitedDiscountPurchasedActivity = data.activityStatistics.find(
|
||||||
|
(a: { name: AnalyticsActivity }) => a.name === AnalyticsActivity.LimitedDiscountOfferPurchased && Period.Yesterday,
|
||||||
|
)
|
||||||
|
const subscriptionPurchasingOverTime = data.activityStatisticsOverTime.find(
|
||||||
|
(a: { name: AnalyticsActivity; period: Period }) =>
|
||||||
|
a.name === AnalyticsActivity.SubscriptionPurchased && a.period === Period.Last30Days,
|
||||||
|
)
|
||||||
|
const subscriptionRenewingOverTime = data.activityStatisticsOverTime.find(
|
||||||
|
(a: { name: AnalyticsActivity; period: Period }) =>
|
||||||
|
a.name === AnalyticsActivity.SubscriptionRenewed && a.period === Period.Last30Days,
|
||||||
|
)
|
||||||
|
const subscriptionRefundingOverTime = data.activityStatisticsOverTime.find(
|
||||||
|
(a: { name: AnalyticsActivity; period: Period }) =>
|
||||||
|
a.name === AnalyticsActivity.SubscriptionRefunded && a.period === Period.Last30Days,
|
||||||
|
)
|
||||||
|
const subscriptionCancelledOverTime = data.activityStatisticsOverTime.find(
|
||||||
|
(a: { name: AnalyticsActivity; period: Period }) =>
|
||||||
|
a.name === AnalyticsActivity.SubscriptionCancelled && a.period === Period.Last30Days,
|
||||||
|
)
|
||||||
|
const subscriptionReactivatedOverTime = data.activityStatisticsOverTime.find(
|
||||||
|
(a: { name: AnalyticsActivity; period: Period }) =>
|
||||||
|
a.name === AnalyticsActivity.SubscriptionReactivated && a.period === Period.Last30Days,
|
||||||
|
)
|
||||||
|
const userRegistrationOverTime = data.activityStatisticsOverTime.find(
|
||||||
|
(a: { name: AnalyticsActivity; period: Period }) =>
|
||||||
|
a.name === AnalyticsActivity.Register && a.period === Period.Last30Days,
|
||||||
|
)
|
||||||
|
const userDeletionOverTime = data.activityStatisticsOverTime.find(
|
||||||
|
(a: { name: AnalyticsActivity; period: Period }) =>
|
||||||
|
a.name === AnalyticsActivity.DeleteAccount && a.period === Period.Last30Days,
|
||||||
|
)
|
||||||
|
const incomeMeasureYesterday = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.Income && a.period === Period.Yesterday,
|
||||||
|
)
|
||||||
|
const refundMeasureYesterday = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.Refunds && a.period === Period.Yesterday,
|
||||||
|
)
|
||||||
|
const incomeYesterday = incomeMeasureYesterday?.totalValue ?? 0
|
||||||
|
const refundsYesterday = refundMeasureYesterday?.totalValue ?? 0
|
||||||
|
const revenueYesterday = incomeYesterday - refundsYesterday
|
||||||
|
|
||||||
|
const subscriptionLengthMeasureYesterday = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.SubscriptionLength && a.period === Period.Yesterday,
|
||||||
|
)
|
||||||
|
const subscriptionLengthDurationYesterday = timer.convertMicrosecondsToTimeStructure(
|
||||||
|
Math.floor(subscriptionLengthMeasureYesterday?.average ?? 0),
|
||||||
|
)
|
||||||
|
|
||||||
|
const subscriptionRemainingTimePercentageMeasureYesterday = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.RemainingSubscriptionTimePercentage && a.period === Period.Yesterday,
|
||||||
|
)
|
||||||
|
const subscriptionRemainingTimePercentageYesterday = Math.floor(
|
||||||
|
subscriptionRemainingTimePercentageMeasureYesterday?.average ?? 0,
|
||||||
|
)
|
||||||
|
|
||||||
|
const registrationLengthMeasureYesterday = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.RegistrationLength && a.period === Period.Yesterday,
|
||||||
|
)
|
||||||
|
const registrationLengthDurationYesterday = timer.convertMicrosecondsToTimeStructure(
|
||||||
|
Math.floor(registrationLengthMeasureYesterday?.average ?? 0),
|
||||||
|
)
|
||||||
|
|
||||||
|
const registrationToSubscriptionMeasureYesterday = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.RegistrationToSubscriptionTime && a.period === Period.Yesterday,
|
||||||
|
)
|
||||||
|
const registrationToSubscriptionDurationYesterday = timer.convertMicrosecondsToTimeStructure(
|
||||||
|
Math.floor(registrationToSubscriptionMeasureYesterday?.average ?? 0),
|
||||||
|
)
|
||||||
|
|
||||||
|
const incomeMeasureThisMonth = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.Income && a.period === Period.ThisMonth,
|
||||||
|
)
|
||||||
|
const refundMeasureThisMonth = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.Refunds && a.period === Period.ThisMonth,
|
||||||
|
)
|
||||||
|
const incomeThisMonth = incomeMeasureThisMonth?.totalValue ?? 0
|
||||||
|
const refundsThisMonth = refundMeasureThisMonth?.totalValue ?? 0
|
||||||
|
const revenueThisMonth = incomeThisMonth - refundsThisMonth
|
||||||
|
|
||||||
|
const subscriptionLengthMeasureThisMonth = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.SubscriptionLength && a.period === Period.ThisMonth,
|
||||||
|
)
|
||||||
|
const subscriptionLengthDurationThisMonth = timer.convertMicrosecondsToTimeStructure(
|
||||||
|
Math.floor(subscriptionLengthMeasureThisMonth?.average ?? 0),
|
||||||
|
)
|
||||||
|
|
||||||
|
const subscriptionRemainingTimePercentageMeasureThisMonth = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.RemainingSubscriptionTimePercentage && a.period === Period.ThisMonth,
|
||||||
|
)
|
||||||
|
const subscriptionRemainingTimePercentageThisMonth = Math.floor(
|
||||||
|
subscriptionRemainingTimePercentageMeasureThisMonth?.average ?? 0,
|
||||||
|
)
|
||||||
|
|
||||||
|
const registrationLengthMeasureThisMonth = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.RegistrationLength && a.period === Period.ThisMonth,
|
||||||
|
)
|
||||||
|
const registrationLengthDurationThisMonth = timer.convertMicrosecondsToTimeStructure(
|
||||||
|
Math.floor(registrationLengthMeasureThisMonth?.average ?? 0),
|
||||||
|
)
|
||||||
|
|
||||||
|
const registrationToSubscriptionMeasureThisMonth = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.RegistrationToSubscriptionTime && a.period === Period.ThisMonth,
|
||||||
|
)
|
||||||
|
const registrationToSubscriptionDurationThisMonth = timer.convertMicrosecondsToTimeStructure(
|
||||||
|
Math.floor(registrationToSubscriptionMeasureThisMonth?.average ?? 0),
|
||||||
|
)
|
||||||
|
|
||||||
|
const plusSubscriptionsInitialAnnualPaymentsYesterday = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.PlusSubscriptionInitialAnnualPaymentsIncome && a.period === Period.Yesterday,
|
||||||
|
)
|
||||||
|
const plusSubscriptionsInitialMonthlyPaymentsYesterday = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.PlusSubscriptionInitialMonthlyPaymentsIncome && a.period === Period.Yesterday,
|
||||||
|
)
|
||||||
|
const plusSubscriptionsRenewingAnnualPaymentsYesterday = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.PlusSubscriptionRenewingAnnualPaymentsIncome && a.period === Period.Yesterday,
|
||||||
|
)
|
||||||
|
const plusSubscriptionsRenewingMonthlyPaymentsYesterday = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.PlusSubscriptionRenewingMonthlyPaymentsIncome && a.period === Period.Yesterday,
|
||||||
|
)
|
||||||
|
const proSubscriptionsInitialAnnualPaymentsYesterday = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.ProSubscriptionInitialAnnualPaymentsIncome && a.period === Period.Yesterday,
|
||||||
|
)
|
||||||
|
const proSubscriptionsInitialMonthlyPaymentsYesterday = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.ProSubscriptionInitialMonthlyPaymentsIncome && a.period === Period.Yesterday,
|
||||||
|
)
|
||||||
|
const proSubscriptionsRenewingAnnualPaymentsYesterday = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.ProSubscriptionRenewingAnnualPaymentsIncome && a.period === Period.Yesterday,
|
||||||
|
)
|
||||||
|
const proSubscriptionsRenewingMonthlyPaymentsYesterday = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.ProSubscriptionRenewingMonthlyPaymentsIncome && a.period === Period.Yesterday,
|
||||||
|
)
|
||||||
|
const plusSubscriptionsInitialAnnualPaymentsThisMonth = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.PlusSubscriptionInitialAnnualPaymentsIncome && a.period === Period.ThisMonth,
|
||||||
|
)
|
||||||
|
const plusSubscriptionsInitialMonthlyPaymentsThisMonth = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.PlusSubscriptionInitialMonthlyPaymentsIncome && a.period === Period.ThisMonth,
|
||||||
|
)
|
||||||
|
const plusSubscriptionsRenewingAnnualPaymentsThisMonth = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.PlusSubscriptionRenewingAnnualPaymentsIncome && a.period === Period.ThisMonth,
|
||||||
|
)
|
||||||
|
const plusSubscriptionsRenewingMonthlyPaymentsThisMonth = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.PlusSubscriptionRenewingMonthlyPaymentsIncome && a.period === Period.ThisMonth,
|
||||||
|
)
|
||||||
|
const proSubscriptionsInitialAnnualPaymentsThisMonth = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.ProSubscriptionInitialAnnualPaymentsIncome && a.period === Period.ThisMonth,
|
||||||
|
)
|
||||||
|
const proSubscriptionsInitialMonthlyPaymentsThisMonth = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.ProSubscriptionInitialMonthlyPaymentsIncome && a.period === Period.ThisMonth,
|
||||||
|
)
|
||||||
|
const proSubscriptionsRenewingAnnualPaymentsThisMonth = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.ProSubscriptionRenewingAnnualPaymentsIncome && a.period === Period.ThisMonth,
|
||||||
|
)
|
||||||
|
const proSubscriptionsRenewingMonthlyPaymentsThisMonth = data.statisticMeasures.find(
|
||||||
|
(a: { name: StatisticsMeasure; period: Period }) =>
|
||||||
|
a.name === StatisticsMeasure.ProSubscriptionRenewingMonthlyPaymentsIncome && a.period === Period.ThisMonth,
|
||||||
|
)
|
||||||
|
|
||||||
|
const mrrOverTime = data.statisticsOverTime.find(
|
||||||
|
(a: { name: string; period: number }) => a.name === 'mrr' && a.period === 27,
|
||||||
|
)
|
||||||
|
const monthlyPlansMrrOverTime = data.statisticsOverTime.find(
|
||||||
|
(a: { name: string; period: number }) => a.name === 'monthly-plans-mrr' && a.period === 27,
|
||||||
|
)
|
||||||
|
const annualPlansMrrOverTime = data.statisticsOverTime.find(
|
||||||
|
(a: { name: string; period: number }) => a.name === 'annual-plans-mrr' && a.period === 27,
|
||||||
|
)
|
||||||
|
const fiveYearPlansMrrOverTime = data.statisticsOverTime.find(
|
||||||
|
(a: { name: string; period: number }) => a.name === 'five-year-plans-mrr' && a.period === 27,
|
||||||
|
)
|
||||||
|
const proPlansMrrOverTime = data.statisticsOverTime.find(
|
||||||
|
(a: { name: string; period: number }) => a.name === 'pro-plans-mrr' && a.period === 27,
|
||||||
|
)
|
||||||
|
const plusPlansMrrOverTime = data.statisticsOverTime.find(
|
||||||
|
(a: { name: string; period: number }) => a.name === 'plus-plans-mrr' && a.period === 27,
|
||||||
|
)
|
||||||
|
|
||||||
|
const today = new Date()
|
||||||
|
const thisMonthPeriodKey = `${today.getFullYear().toString()}-${(today.getMonth() + 1).toString()}`
|
||||||
|
const thisMonthChurn = data.churn.values.find(
|
||||||
|
(value: { periodKey: string }) => value.periodKey === thisMonthPeriodKey,
|
||||||
|
)
|
||||||
|
|
||||||
|
return ` <div>
|
||||||
|
<p>Hello,</p>
|
||||||
|
<p>
|
||||||
|
<strong>Here are some statistics from yesterday:</strong>
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<b>Payments</b>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
Revenue: <b>$${revenueYesterday.toLocaleString('en-US')}</b> (Income: $
|
||||||
|
${incomeYesterday.toLocaleString('en-US')}, Refunds: $${refundsYesterday.toLocaleString('en-US')})
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Successfull payments: <b>${successfullPaymentsActivity?.totalCount.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Failed payments: <b>${failedPaymentsActivity?.totalCount.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>MRR Breakdown</b>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<b>Total:</b> $${mrrOverTime?.counts[mrrOverTime?.counts.length - 1].totalCount.toLocaleString('en-US')}
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>By Subscription Type:</b>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<b>PLUS:</b> $
|
||||||
|
${plusPlansMrrOverTime?.counts[plusPlansMrrOverTime?.counts.length - 1].totalCount.toLocaleString('en-US')}
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>PRO:</b> $
|
||||||
|
${proPlansMrrOverTime?.counts[proPlansMrrOverTime?.counts.length - 1].totalCount.toLocaleString('en-US')}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>By Billing Frequency:</b>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<b>Monthly:</b> $
|
||||||
|
${monthlyPlansMrrOverTime?.counts[monthlyPlansMrrOverTime?.counts.length - 1].totalCount.toLocaleString(
|
||||||
|
'en-US',
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>Annual:</b> $
|
||||||
|
${annualPlansMrrOverTime?.counts[annualPlansMrrOverTime?.counts.length - 1].totalCount.toLocaleString(
|
||||||
|
'en-US',
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>5-year:</b> $
|
||||||
|
${fiveYearPlansMrrOverTime?.counts[fiveYearPlansMrrOverTime?.counts.length - 1].totalCount.toLocaleString(
|
||||||
|
'en-US',
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>Income Breakdown</b>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<b>Plus Subscription:</b>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<b>${plusSubscriptionsInitialMonthlyPaymentsYesterday?.increments.toLocaleString('en-US')}</b>${' '}
|
||||||
|
<i>initial</i> payments on <u>monhtly</u> plan, totaling${' '}
|
||||||
|
<b>$${plusSubscriptionsInitialMonthlyPaymentsYesterday?.totalValue.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>${plusSubscriptionsInitialAnnualPaymentsYesterday?.increments.toLocaleString('en-US')}</b>${' '}
|
||||||
|
<i>initial</i> payments on <u>annual</u> plan, totaling${' '}
|
||||||
|
<b>$${plusSubscriptionsInitialAnnualPaymentsYesterday?.totalValue.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>${plusSubscriptionsRenewingMonthlyPaymentsYesterday?.increments.toLocaleString('en-US')}</b>${' '}
|
||||||
|
<i>renewing</i> payments on <u>monthly</u> plan, totaling${' '}
|
||||||
|
<b>$${plusSubscriptionsRenewingMonthlyPaymentsYesterday?.totalValue.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>${plusSubscriptionsRenewingAnnualPaymentsYesterday?.increments.toLocaleString('en-US')}</b>${' '}
|
||||||
|
<i>renewing</i> payments on <u>annual</u> plan, totaling${' '}
|
||||||
|
<b>$${plusSubscriptionsRenewingAnnualPaymentsYesterday?.totalValue.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>Pro Subscription:</b>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<b>${proSubscriptionsInitialMonthlyPaymentsYesterday?.increments.toLocaleString('en-US')}</b>${' '}
|
||||||
|
<i>initial</i> payments on <u>monhtly</u> plan, totaling${' '}
|
||||||
|
<b>$${proSubscriptionsInitialMonthlyPaymentsYesterday?.totalValue.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>${proSubscriptionsInitialAnnualPaymentsYesterday?.increments.toLocaleString('en-US')}</b>${' '}
|
||||||
|
<i>initial</i> payments on <u>annual</u> plan, totaling${' '}
|
||||||
|
<b>$${proSubscriptionsInitialAnnualPaymentsYesterday?.totalValue.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>${proSubscriptionsRenewingMonthlyPaymentsYesterday?.increments.toLocaleString('en-US')}</b>${' '}
|
||||||
|
<i>renewing</i> payments on <u>monthly</u> plan, totaling${' '}
|
||||||
|
<b>$${proSubscriptionsRenewingMonthlyPaymentsYesterday?.totalValue.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>${proSubscriptionsRenewingAnnualPaymentsYesterday?.increments.toLocaleString('en-US')}</b>${' '}
|
||||||
|
<i>renewing</i> payments on <u>annual</u> plan, totaling${' '}
|
||||||
|
<b>$${proSubscriptionsRenewingAnnualPaymentsYesterday?.totalValue.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>Users</b>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
Number of users registered:${' '}
|
||||||
|
<b>
|
||||||
|
${userRegistrationOverTime?.counts[userRegistrationOverTime?.counts.length - 1]?.totalCount.toLocaleString(
|
||||||
|
'en-US',
|
||||||
|
)}
|
||||||
|
</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Number of users unregistered:${' '}
|
||||||
|
<b>
|
||||||
|
${userDeletionOverTime?.counts[userDeletionOverTime?.counts.length - 1]?.totalCount.toLocaleString('en-US')}
|
||||||
|
</b>${' '}
|
||||||
|
(average account duration: ${registrationLengthDurationYesterday.days} days${' '}
|
||||||
|
${registrationLengthDurationYesterday.hours} hours ${registrationLengthDurationYesterday.minutes} minutes)
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>Subscriptions</b>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
Number of subscriptions purchased:${' '}
|
||||||
|
<b>
|
||||||
|
${subscriptionPurchasingOverTime?.counts[
|
||||||
|
subscriptionPurchasingOverTime?.counts.length - 1
|
||||||
|
]?.totalCount.toLocaleString('en-US')}
|
||||||
|
</b>${' '}
|
||||||
|
(includes <b>${limitedDiscountPurchasedActivity?.totalCount.toLocaleString('en-US')}</b> limited time
|
||||||
|
offer purchases)
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Number of subscriptions renewed:${' '}
|
||||||
|
<b>
|
||||||
|
${subscriptionRenewingOverTime?.counts[
|
||||||
|
subscriptionRenewingOverTime?.counts.length - 1
|
||||||
|
]?.totalCount.toLocaleString('en-US')}
|
||||||
|
</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Number of subscriptions refunded:${' '}
|
||||||
|
<b>
|
||||||
|
${subscriptionRefundingOverTime?.counts[
|
||||||
|
subscriptionRefundingOverTime?.counts.length - 1
|
||||||
|
]?.totalCount.toLocaleString('en-US')}
|
||||||
|
</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Number of subscriptions cancelled:${' '}
|
||||||
|
<b>
|
||||||
|
${subscriptionCancelledOverTime?.counts[
|
||||||
|
subscriptionCancelledOverTime?.counts.length - 1
|
||||||
|
]?.totalCount.toLocaleString('en-US')}
|
||||||
|
</b>${' '}
|
||||||
|
(average subscription duration: ${subscriptionLengthDurationYesterday.days} days${' '}
|
||||||
|
${subscriptionLengthDurationYesterday.hours} hours ${subscriptionLengthDurationYesterday.minutes} minutes,
|
||||||
|
average remaining subscription percentage: ${subscriptionRemainingTimePercentageYesterday}%)
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Number of subscriptions reactivated:${' '}
|
||||||
|
<b>
|
||||||
|
${subscriptionReactivatedOverTime?.counts[
|
||||||
|
subscriptionReactivatedOverTime?.counts.length - 1
|
||||||
|
]?.totalCount.toLocaleString('en-US')}
|
||||||
|
</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Average time from registration to subscription purchase:${' '}
|
||||||
|
<b>
|
||||||
|
${registrationToSubscriptionDurationYesterday.days} days${' '}
|
||||||
|
${registrationToSubscriptionDurationYesterday.hours} hours${' '}
|
||||||
|
${registrationToSubscriptionDurationYesterday.minutes} minutes
|
||||||
|
</b>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p>
|
||||||
|
<strong>Here are some statistics from last 30 days:</strong>
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<b>Payments (This Month)</b>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
Revenue: <b>$${revenueThisMonth.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Income: <b>$${incomeThisMonth.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Refunds: <b>$${refundsThisMonth.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>Income Breakdown (This Month)</b>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<b>Plus Subscription:</b>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<b>${plusSubscriptionsInitialMonthlyPaymentsThisMonth?.increments.toLocaleString('en-US')}</b>${' '}
|
||||||
|
<i>initial</i> payments on <u>monhtly</u> plan, totaling${' '}
|
||||||
|
<b>$${plusSubscriptionsInitialMonthlyPaymentsThisMonth?.totalValue.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>${plusSubscriptionsInitialAnnualPaymentsThisMonth?.increments.toLocaleString('en-US')}</b>${' '}
|
||||||
|
<i>initial</i> payments on <u>annual</u> plan, totaling${' '}
|
||||||
|
<b>$${plusSubscriptionsInitialAnnualPaymentsThisMonth?.totalValue.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>${plusSubscriptionsRenewingMonthlyPaymentsThisMonth?.increments.toLocaleString('en-US')}</b>${' '}
|
||||||
|
<i>renewing</i> payments on <u>monthly</u> plan, totaling${' '}
|
||||||
|
<b>$${plusSubscriptionsRenewingMonthlyPaymentsThisMonth?.totalValue.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>${plusSubscriptionsRenewingAnnualPaymentsThisMonth?.increments.toLocaleString('en-US')}</b>${' '}
|
||||||
|
<i>renewing</i> payments on <u>annual</u> plan, totaling${' '}
|
||||||
|
<b>$${plusSubscriptionsRenewingAnnualPaymentsThisMonth?.totalValue.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>Pro Subscription:</b>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<b>${proSubscriptionsInitialMonthlyPaymentsThisMonth?.increments.toLocaleString('en-US')}</b>${' '}
|
||||||
|
<i>initial</i> payments on <u>monhtly</u> plan, totaling${' '}
|
||||||
|
<b>$${proSubscriptionsInitialMonthlyPaymentsThisMonth?.totalValue.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>${proSubscriptionsInitialAnnualPaymentsThisMonth?.increments.toLocaleString('en-US')}</b>${' '}
|
||||||
|
<i>initial</i> payments on <u>annual</u> plan, totaling${' '}
|
||||||
|
<b>$${proSubscriptionsInitialAnnualPaymentsThisMonth?.totalValue.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>${proSubscriptionsRenewingMonthlyPaymentsThisMonth?.increments.toLocaleString('en-US')}</b>${' '}
|
||||||
|
<i>renewing</i> payments on <u>monthly</u> plan, totaling${' '}
|
||||||
|
<b>$${proSubscriptionsRenewingMonthlyPaymentsThisMonth?.totalValue.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>${proSubscriptionsRenewingAnnualPaymentsThisMonth?.increments.toLocaleString('en-US')}</b>${' '}
|
||||||
|
<i>renewing</i> payments on <u>annual</u> plan, totaling${' '}
|
||||||
|
<b>$${proSubscriptionsRenewingAnnualPaymentsThisMonth?.totalValue.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>Users</b>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
Number of users registered: <b>${userRegistrationOverTime?.totalCount.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Number of users unregistered: <b>${userDeletionOverTime?.totalCount.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Average account duration this month:${' '}
|
||||||
|
<b>
|
||||||
|
${registrationLengthDurationThisMonth.days} days ${registrationLengthDurationThisMonth.hours} hours${' '}
|
||||||
|
${registrationLengthDurationThisMonth.minutes} minutes
|
||||||
|
</b>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<b>Subscriptions</b>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
Number of subscriptions purchased:${' '}
|
||||||
|
<b>${subscriptionPurchasingOverTime?.totalCount.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Number of subscriptions renewed:${' '}
|
||||||
|
<b>${subscriptionRenewingOverTime?.totalCount.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Number of subscriptions refunded:${' '}
|
||||||
|
<b>${subscriptionRefundingOverTime?.totalCount.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Number of subscriptions cancelled:${' '}
|
||||||
|
<b>${subscriptionCancelledOverTime?.totalCount.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Number of subscriptions reactivated:${' '}
|
||||||
|
<b>${subscriptionReactivatedOverTime?.totalCount.toLocaleString('en-US')}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Average subscription duration this month:${' '}
|
||||||
|
<b>
|
||||||
|
${subscriptionLengthDurationThisMonth.days} days ${subscriptionLengthDurationThisMonth.hours} hours${' '}
|
||||||
|
${subscriptionLengthDurationThisMonth.minutes} minutes
|
||||||
|
</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Average subscription remaining percentage this month:${' '}
|
||||||
|
<b>${subscriptionRemainingTimePercentageThisMonth}%</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Average time from registration to subscription purchase this month:${' '}
|
||||||
|
<b>
|
||||||
|
${registrationToSubscriptionDurationThisMonth.days} days${' '}
|
||||||
|
${registrationToSubscriptionDurationThisMonth.hours} hours${' '}
|
||||||
|
${registrationToSubscriptionDurationThisMonth.minutes} minutes
|
||||||
|
</b>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p>
|
||||||
|
<strong>Here is the MRR chart over 30 days:</strong>
|
||||||
|
</p>
|
||||||
|
<img src=${chartUrls.mrr}></img>
|
||||||
|
<p>
|
||||||
|
<strong>Here is the MRR Monthly chart this year:</strong>
|
||||||
|
</p>
|
||||||
|
<img src=${chartUrls.mrrMonthly}></img>
|
||||||
|
<p>
|
||||||
|
<strong>Here is the subscription chart over 30 days:</strong>
|
||||||
|
</p>
|
||||||
|
<img src=${chartUrls.subscriptions}></img>
|
||||||
|
<p>
|
||||||
|
<strong>Here is the users chart over 30 days:</strong>
|
||||||
|
</p>
|
||||||
|
<img src=${chartUrls.users}></img>
|
||||||
|
<p>
|
||||||
|
<strong>Here is the monthly churn rate percentage:</strong>
|
||||||
|
</p>
|
||||||
|
<p>✅ GREAT! Up to 7% 🔶 OKAY: 8-10% 🩸 BAD: 11 -15 % 🚨 TERRIBLE! 16-20%</p>
|
||||||
|
<p>Churn is calculated by the following formula:</p>
|
||||||
|
<p>
|
||||||
|
( Existing Customers Churn [${thisMonthChurn?.existingCustomersChurn}] + New Customers Churn [
|
||||||
|
${thisMonthChurn?.newCustomersChurn}] ) * 100 / Average Customers Count This Month [
|
||||||
|
${thisMonthChurn?.averageCustomersCount}]
|
||||||
|
</p>
|
||||||
|
<img src=${chartUrls.churn}></img>
|
||||||
|
<p>
|
||||||
|
<strong>Here is quarterly performance chart:</strong>
|
||||||
|
</p>
|
||||||
|
<img src=${chartUrls.quarterlyPerformance}></img>
|
||||||
|
<p>Thanks,SN</p>
|
||||||
|
</div>`
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
/* istanbul ignore file */
|
/* istanbul ignore file */
|
||||||
|
|
||||||
import { DomainEventService, DailyAnalyticsReportGeneratedEvent } from '@standardnotes/domain-events'
|
import { DomainEventService, EmailRequestedEvent } from '@standardnotes/domain-events'
|
||||||
import { TimerInterface } from '@standardnotes/time'
|
import { TimerInterface } from '@standardnotes/time'
|
||||||
import { inject, injectable } from 'inversify'
|
import { inject, injectable } from 'inversify'
|
||||||
import TYPES from '../../Bootstrap/Types'
|
import TYPES from '../../Bootstrap/Types'
|
||||||
@@ -9,55 +9,20 @@ import { DomainEventFactoryInterface } from './DomainEventFactoryInterface'
|
|||||||
@injectable()
|
@injectable()
|
||||||
export class DomainEventFactory implements DomainEventFactoryInterface {
|
export class DomainEventFactory implements DomainEventFactoryInterface {
|
||||||
constructor(@inject(TYPES.Timer) private timer: TimerInterface) {}
|
constructor(@inject(TYPES.Timer) private timer: TimerInterface) {}
|
||||||
|
createEmailRequestedEvent(dto: {
|
||||||
createDailyAnalyticsReportGeneratedEvent(dto: {
|
userEmail: string
|
||||||
activityStatistics: Array<{
|
messageIdentifier: string
|
||||||
name: string
|
level: string
|
||||||
retention: number
|
body: string
|
||||||
totalCount: number
|
subject: string
|
||||||
}>
|
}): EmailRequestedEvent {
|
||||||
statisticMeasures: Array<{
|
|
||||||
name: string
|
|
||||||
totalValue: number
|
|
||||||
average: number
|
|
||||||
increments: number
|
|
||||||
period: number
|
|
||||||
}>
|
|
||||||
activityStatisticsOverTime: Array<{
|
|
||||||
name: string
|
|
||||||
period: number
|
|
||||||
counts: Array<{
|
|
||||||
periodKey: string
|
|
||||||
totalCount: number
|
|
||||||
}>
|
|
||||||
totalCount: number
|
|
||||||
}>
|
|
||||||
statisticsOverTime: Array<{
|
|
||||||
name: string
|
|
||||||
period: number
|
|
||||||
counts: Array<{
|
|
||||||
periodKey: string
|
|
||||||
totalCount: number
|
|
||||||
}>
|
|
||||||
}>
|
|
||||||
churn: {
|
|
||||||
periodKeys: Array<string>
|
|
||||||
values: Array<{
|
|
||||||
rate: number
|
|
||||||
periodKey: string
|
|
||||||
averageCustomersCount: number
|
|
||||||
existingCustomersChurn: number
|
|
||||||
newCustomersChurn: number
|
|
||||||
}>
|
|
||||||
}
|
|
||||||
}): DailyAnalyticsReportGeneratedEvent {
|
|
||||||
return {
|
return {
|
||||||
type: 'DAILY_ANALYTICS_REPORT_GENERATED',
|
type: 'EMAIL_REQUESTED',
|
||||||
createdAt: this.timer.getUTCDate(),
|
createdAt: this.timer.getUTCDate(),
|
||||||
meta: {
|
meta: {
|
||||||
correlation: {
|
correlation: {
|
||||||
userIdentifier: '',
|
userIdentifier: dto.userEmail,
|
||||||
userIdentifierType: 'uuid',
|
userIdentifierType: 'email',
|
||||||
},
|
},
|
||||||
origin: DomainEventService.Analytics,
|
origin: DomainEventService.Analytics,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,45 +1,11 @@
|
|||||||
import { DailyAnalyticsReportGeneratedEvent } from '@standardnotes/domain-events'
|
import { EmailRequestedEvent } from '@standardnotes/domain-events'
|
||||||
|
|
||||||
export interface DomainEventFactoryInterface {
|
export interface DomainEventFactoryInterface {
|
||||||
createDailyAnalyticsReportGeneratedEvent(dto: {
|
createEmailRequestedEvent(dto: {
|
||||||
activityStatistics: Array<{
|
userEmail: string
|
||||||
name: string
|
messageIdentifier: string
|
||||||
retention: number
|
level: string
|
||||||
totalCount: number
|
body: string
|
||||||
}>
|
subject: string
|
||||||
statisticMeasures: Array<{
|
}): EmailRequestedEvent
|
||||||
name: string
|
|
||||||
totalValue: number
|
|
||||||
average: number
|
|
||||||
increments: number
|
|
||||||
period: number
|
|
||||||
}>
|
|
||||||
activityStatisticsOverTime: Array<{
|
|
||||||
name: string
|
|
||||||
period: number
|
|
||||||
counts: Array<{
|
|
||||||
periodKey: string
|
|
||||||
totalCount: number
|
|
||||||
}>
|
|
||||||
totalCount: number
|
|
||||||
}>
|
|
||||||
statisticsOverTime: Array<{
|
|
||||||
name: string
|
|
||||||
period: number
|
|
||||||
counts: Array<{
|
|
||||||
periodKey: string
|
|
||||||
totalCount: number
|
|
||||||
}>
|
|
||||||
}>
|
|
||||||
churn: {
|
|
||||||
periodKeys: Array<string>
|
|
||||||
values: Array<{
|
|
||||||
rate: number
|
|
||||||
periodKey: string
|
|
||||||
averageCustomersCount: number
|
|
||||||
existingCustomersChurn: number
|
|
||||||
newCustomersChurn: number
|
|
||||||
}>
|
|
||||||
}
|
|
||||||
}): DailyAnalyticsReportGeneratedEvent
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ WEB_SOCKET_SERVER_URL=http://websockets:3000
|
|||||||
PAYMENTS_SERVER_URL=http://payments:3000
|
PAYMENTS_SERVER_URL=http://payments:3000
|
||||||
FILES_SERVER_URL=http://files:3000
|
FILES_SERVER_URL=http://files:3000
|
||||||
REVISIONS_SERVER_URL=http://revisions:3000
|
REVISIONS_SERVER_URL=http://revisions:3000
|
||||||
|
EMAIL_SERVER_URL=http://email:3000
|
||||||
|
|
||||||
HTTP_CALL_TIMEOUT=60000
|
HTTP_CALL_TIMEOUT=60000
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,56 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [1.40.2](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.40.1...@standardnotes/api-gateway@1.40.2) (2022-12-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||||
|
|
||||||
|
## [1.40.1](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.40.0...@standardnotes/api-gateway@1.40.1) (2022-12-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||||
|
|
||||||
|
# [1.40.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.39.24...@standardnotes/api-gateway@1.40.0) (2022-12-12)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **api-gateway:** add unsubscribe from emails endpoint ([22d6a02](https://github.com/standardnotes/api-gateway/commit/22d6a02d049ba3bde890c7def91e19f013ba3e22))
|
||||||
|
|
||||||
|
## [1.39.24](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.39.23...@standardnotes/api-gateway@1.39.24) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||||
|
|
||||||
|
## [1.39.23](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.39.22...@standardnotes/api-gateway@1.39.23) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||||
|
|
||||||
|
## [1.39.22](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.39.21...@standardnotes/api-gateway@1.39.22) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||||
|
|
||||||
|
## [1.39.21](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.39.20...@standardnotes/api-gateway@1.39.21) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||||
|
|
||||||
|
## [1.39.20](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.39.19...@standardnotes/api-gateway@1.39.20) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||||
|
|
||||||
|
## [1.39.19](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.39.18...@standardnotes/api-gateway@1.39.19) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||||
|
|
||||||
|
## [1.39.18](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.39.17...@standardnotes/api-gateway@1.39.18) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||||
|
|
||||||
|
## [1.39.17](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.39.16...@standardnotes/api-gateway@1.39.17) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||||
|
|
||||||
|
## [1.39.16](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.39.15...@standardnotes/api-gateway@1.39.16) (2022-12-08)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||||
|
|
||||||
## [1.39.15](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.39.14...@standardnotes/api-gateway@1.39.15) (2022-12-08)
|
## [1.39.15](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.39.14...@standardnotes/api-gateway@1.39.15) (2022-12-08)
|
||||||
|
|
||||||
**Note:** Version bump only for package @standardnotes/api-gateway
|
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/api-gateway",
|
"name": "@standardnotes/api-gateway",
|
||||||
"version": "1.39.15",
|
"version": "1.40.2",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0 <19.0.0"
|
"node": ">=18.0.0 <19.0.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ export class ContainerConfigLoader {
|
|||||||
container.bind(TYPES.SYNCING_SERVER_JS_URL).toConstantValue(env.get('SYNCING_SERVER_JS_URL'))
|
container.bind(TYPES.SYNCING_SERVER_JS_URL).toConstantValue(env.get('SYNCING_SERVER_JS_URL'))
|
||||||
container.bind(TYPES.AUTH_SERVER_URL).toConstantValue(env.get('AUTH_SERVER_URL'))
|
container.bind(TYPES.AUTH_SERVER_URL).toConstantValue(env.get('AUTH_SERVER_URL'))
|
||||||
container.bind(TYPES.REVISIONS_SERVER_URL).toConstantValue(env.get('REVISIONS_SERVER_URL', true))
|
container.bind(TYPES.REVISIONS_SERVER_URL).toConstantValue(env.get('REVISIONS_SERVER_URL', true))
|
||||||
|
container.bind(TYPES.EMAIL_SERVER_URL).toConstantValue(env.get('EMAIL_SERVER_URL', true))
|
||||||
container.bind(TYPES.PAYMENTS_SERVER_URL).toConstantValue(env.get('PAYMENTS_SERVER_URL', true))
|
container.bind(TYPES.PAYMENTS_SERVER_URL).toConstantValue(env.get('PAYMENTS_SERVER_URL', true))
|
||||||
container.bind(TYPES.FILES_SERVER_URL).toConstantValue(env.get('FILES_SERVER_URL', true))
|
container.bind(TYPES.FILES_SERVER_URL).toConstantValue(env.get('FILES_SERVER_URL', true))
|
||||||
container.bind(TYPES.AUTH_JWT_SECRET).toConstantValue(env.get('AUTH_JWT_SECRET'))
|
container.bind(TYPES.AUTH_JWT_SECRET).toConstantValue(env.get('AUTH_JWT_SECRET'))
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ const TYPES = {
|
|||||||
PAYMENTS_SERVER_URL: Symbol.for('PAYMENTS_SERVER_URL'),
|
PAYMENTS_SERVER_URL: Symbol.for('PAYMENTS_SERVER_URL'),
|
||||||
FILES_SERVER_URL: Symbol.for('FILES_SERVER_URL'),
|
FILES_SERVER_URL: Symbol.for('FILES_SERVER_URL'),
|
||||||
REVISIONS_SERVER_URL: Symbol.for('REVISIONS_SERVER_URL'),
|
REVISIONS_SERVER_URL: Symbol.for('REVISIONS_SERVER_URL'),
|
||||||
|
EMAIL_SERVER_URL: Symbol.for('EMAIL_SERVER_URL'),
|
||||||
WORKSPACE_SERVER_URL: Symbol.for('WORKSPACE_SERVER_URL'),
|
WORKSPACE_SERVER_URL: Symbol.for('WORKSPACE_SERVER_URL'),
|
||||||
WEB_SOCKET_SERVER_URL: Symbol.for('WEB_SOCKET_SERVER_URL'),
|
WEB_SOCKET_SERVER_URL: Symbol.for('WEB_SOCKET_SERVER_URL'),
|
||||||
AUTH_JWT_SECRET: Symbol.for('AUTH_JWT_SECRET'),
|
AUTH_JWT_SECRET: Symbol.for('AUTH_JWT_SECRET'),
|
||||||
|
|||||||
@@ -29,4 +29,14 @@ export class ActionsController extends BaseHttpController {
|
|||||||
async methods(request: Request, response: Response): Promise<void> {
|
async methods(request: Request, response: Response): Promise<void> {
|
||||||
await this.httpService.callAuthServer(request, response, 'auth/methods', request.body)
|
await this.httpService.callAuthServer(request, response, 'auth/methods', request.body)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@httpGet('/unsubscribe/:token')
|
||||||
|
async emailUnsubscribe(request: Request, response: Response): Promise<void> {
|
||||||
|
await this.httpService.callEmailServer(
|
||||||
|
request,
|
||||||
|
response,
|
||||||
|
`subscriptions/actions/unsubscribe/${request.params.token}`,
|
||||||
|
request.body,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ export class HttpService implements HttpServiceInterface {
|
|||||||
@inject(TYPES.WORKSPACE_SERVER_URL) private workspaceServerUrl: string,
|
@inject(TYPES.WORKSPACE_SERVER_URL) private workspaceServerUrl: string,
|
||||||
@inject(TYPES.WEB_SOCKET_SERVER_URL) private webSocketServerUrl: string,
|
@inject(TYPES.WEB_SOCKET_SERVER_URL) private webSocketServerUrl: string,
|
||||||
@inject(TYPES.REVISIONS_SERVER_URL) private revisionsServerUrl: string,
|
@inject(TYPES.REVISIONS_SERVER_URL) private revisionsServerUrl: string,
|
||||||
|
@inject(TYPES.EMAIL_SERVER_URL) private emailServerUrl: string,
|
||||||
@inject(TYPES.HTTP_CALL_TIMEOUT) private httpCallTimeout: number,
|
@inject(TYPES.HTTP_CALL_TIMEOUT) private httpCallTimeout: number,
|
||||||
@inject(TYPES.CrossServiceTokenCache) private crossServiceTokenCache: CrossServiceTokenCacheInterface,
|
@inject(TYPES.CrossServiceTokenCache) private crossServiceTokenCache: CrossServiceTokenCacheInterface,
|
||||||
@inject(TYPES.Logger) private logger: Logger,
|
@inject(TYPES.Logger) private logger: Logger,
|
||||||
@@ -65,6 +66,21 @@ export class HttpService implements HttpServiceInterface {
|
|||||||
await this.callServer(this.authServerUrl, request, response, endpoint, payload)
|
await this.callServer(this.authServerUrl, request, response, endpoint, payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async callEmailServer(
|
||||||
|
request: Request,
|
||||||
|
response: Response,
|
||||||
|
endpoint: string,
|
||||||
|
payload?: Record<string, unknown> | string,
|
||||||
|
): Promise<void> {
|
||||||
|
if (!this.emailServerUrl) {
|
||||||
|
response.status(400).send({ message: 'Email Server not configured' })
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.callServer(this.emailServerUrl, request, response, endpoint, payload)
|
||||||
|
}
|
||||||
|
|
||||||
async callWorkspaceServer(
|
async callWorkspaceServer(
|
||||||
request: Request,
|
request: Request,
|
||||||
response: Response,
|
response: Response,
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
import { Request, Response } from 'express'
|
import { Request, Response } from 'express'
|
||||||
|
|
||||||
export interface HttpServiceInterface {
|
export interface HttpServiceInterface {
|
||||||
|
callEmailServer(
|
||||||
|
request: Request,
|
||||||
|
response: Response,
|
||||||
|
endpoint: string,
|
||||||
|
payload?: Record<string, unknown> | string,
|
||||||
|
): Promise<void>
|
||||||
callAuthServer(
|
callAuthServer(
|
||||||
request: Request,
|
request: Request,
|
||||||
response: Response,
|
response: Response,
|
||||||
|
|||||||
@@ -3,6 +3,80 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [1.67.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.67.1...@standardnotes/auth-server@1.67.2) (2022-12-15)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/auth-server
|
||||||
|
|
||||||
|
## [1.67.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.67.0...@standardnotes/auth-server@1.67.1) (2022-12-12)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* user signed in email template ([c15e2e2](https://github.com/standardnotes/server/commit/c15e2e2c8f3a6c177e227d25440501fa38dd3d0e))
|
||||||
|
|
||||||
|
# [1.67.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.66.9...@standardnotes/auth-server@1.67.0) (2022-12-12)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **auth:** add email subscription unsubscribed event handler ([10e2a26](https://github.com/standardnotes/server/commit/10e2a263522dfa33c06940f29cb77f783f66b20c))
|
||||||
|
|
||||||
|
## [1.66.9](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.66.8...@standardnotes/auth-server@1.66.9) (2022-12-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/auth-server
|
||||||
|
|
||||||
|
## [1.66.8](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.66.7...@standardnotes/auth-server@1.66.8) (2022-12-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/auth-server
|
||||||
|
|
||||||
|
## [1.66.7](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.66.6...@standardnotes/auth-server@1.66.7) (2022-12-09)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **auth:** linter issue ([104313c](https://github.com/standardnotes/server/commit/104313c15df79f6308d23e21f65111e5bd3d9c72))
|
||||||
|
|
||||||
|
## [1.66.6](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.66.5...@standardnotes/auth-server@1.66.6) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/auth-server
|
||||||
|
|
||||||
|
## [1.66.5](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.66.4...@standardnotes/auth-server@1.66.5) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/auth-server
|
||||||
|
|
||||||
|
## [1.66.4](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.66.3...@standardnotes/auth-server@1.66.4) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/auth-server
|
||||||
|
|
||||||
|
## [1.66.3](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.66.2...@standardnotes/auth-server@1.66.3) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/auth-server
|
||||||
|
|
||||||
|
## [1.66.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.66.1...@standardnotes/auth-server@1.66.2) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/auth-server
|
||||||
|
|
||||||
|
## [1.66.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.66.0...@standardnotes/auth-server@1.66.1) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/auth-server
|
||||||
|
|
||||||
|
# [1.66.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.65.0...@standardnotes/auth-server@1.66.0) (2022-12-09)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **email:** replace offline subscription token created event in favour of email requested ([b595264](https://github.com/standardnotes/server/commit/b595264e313ac5ae5404f6a4a05b90b8c11f7f02))
|
||||||
|
|
||||||
|
# [1.65.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.64.7...@standardnotes/auth-server@1.65.0) (2022-12-09)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **auth:** remove offline subscription token created event in favour of email requested ([fd58992](https://github.com/standardnotes/server/commit/fd589922bba29595a0dfd154a42fe158024fad28))
|
||||||
|
|
||||||
|
## [1.64.7](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.64.6...@standardnotes/auth-server@1.64.7) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/auth-server
|
||||||
|
|
||||||
|
## [1.64.6](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.64.5...@standardnotes/auth-server@1.64.6) (2022-12-08)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/auth-server
|
||||||
|
|
||||||
## [1.64.5](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.64.4...@standardnotes/auth-server@1.64.5) (2022-12-08)
|
## [1.64.5](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.64.4...@standardnotes/auth-server@1.64.5) (2022-12-08)
|
||||||
|
|
||||||
**Note:** Version bump only for package @standardnotes/auth-server
|
**Note:** Version bump only for package @standardnotes/auth-server
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/auth-server",
|
"name": "@standardnotes/auth-server",
|
||||||
"version": "1.64.5",
|
"version": "1.67.2",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0 <19.0.0"
|
"node": ">=18.0.0 <19.0.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -193,6 +193,7 @@ import { SubscriptionInvitesController } from '../Controller/SubscriptionInvites
|
|||||||
import { CreateCrossServiceToken } from '../Domain/UseCase/CreateCrossServiceToken/CreateCrossServiceToken'
|
import { CreateCrossServiceToken } from '../Domain/UseCase/CreateCrossServiceToken/CreateCrossServiceToken'
|
||||||
import { ProcessUserRequest } from '../Domain/UseCase/ProcessUserRequest/ProcessUserRequest'
|
import { ProcessUserRequest } from '../Domain/UseCase/ProcessUserRequest/ProcessUserRequest'
|
||||||
import { UserRequestsController } from '../Controller/UserRequestsController'
|
import { UserRequestsController } from '../Controller/UserRequestsController'
|
||||||
|
import { EmailSubscriptionUnsubscribedEventHandler } from '../Domain/Handler/EmailSubscriptionUnsubscribedEventHandler'
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
const newrelicFormatter = require('@newrelic/winston-enricher')
|
const newrelicFormatter = require('@newrelic/winston-enricher')
|
||||||
@@ -560,6 +561,15 @@ export class ContainerConfigLoader {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
container
|
||||||
|
.bind<EmailSubscriptionUnsubscribedEventHandler>(TYPES.EmailSubscriptionUnsubscribedEventHandler)
|
||||||
|
.toConstantValue(
|
||||||
|
new EmailSubscriptionUnsubscribedEventHandler(
|
||||||
|
container.get(TYPES.UserRepository),
|
||||||
|
container.get(TYPES.SettingService),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
const eventHandlers: Map<string, DomainEventHandlerInterface> = new Map([
|
const eventHandlers: Map<string, DomainEventHandlerInterface> = new Map([
|
||||||
['USER_REGISTERED', container.get(TYPES.UserRegisteredEventHandler)],
|
['USER_REGISTERED', container.get(TYPES.UserRegisteredEventHandler)],
|
||||||
['ACCOUNT_DELETION_REQUESTED', container.get(TYPES.AccountDeletionRequestedEventHandler)],
|
['ACCOUNT_DELETION_REQUESTED', container.get(TYPES.AccountDeletionRequestedEventHandler)],
|
||||||
@@ -582,6 +592,7 @@ export class ContainerConfigLoader {
|
|||||||
],
|
],
|
||||||
['SHARED_SUBSCRIPTION_INVITATION_CREATED', container.get(TYPES.SharedSubscriptionInvitationCreatedEventHandler)],
|
['SHARED_SUBSCRIPTION_INVITATION_CREATED', container.get(TYPES.SharedSubscriptionInvitationCreatedEventHandler)],
|
||||||
['PREDICATE_VERIFICATION_REQUESTED', container.get(TYPES.PredicateVerificationRequestedEventHandler)],
|
['PREDICATE_VERIFICATION_REQUESTED', container.get(TYPES.PredicateVerificationRequestedEventHandler)],
|
||||||
|
['EMAIL_SUBSCRIPTION_UNSUBSCRIBED', container.get(TYPES.EmailSubscriptionUnsubscribedEventHandler)],
|
||||||
])
|
])
|
||||||
|
|
||||||
if (env.get('SQS_QUEUE_URL', true)) {
|
if (env.get('SQS_QUEUE_URL', true)) {
|
||||||
|
|||||||
@@ -138,6 +138,7 @@ const TYPES = {
|
|||||||
UserDisabledSessionUserAgentLoggingEventHandler: Symbol.for('UserDisabledSessionUserAgentLoggingEventHandler'),
|
UserDisabledSessionUserAgentLoggingEventHandler: Symbol.for('UserDisabledSessionUserAgentLoggingEventHandler'),
|
||||||
SharedSubscriptionInvitationCreatedEventHandler: Symbol.for('SharedSubscriptionInvitationCreatedEventHandler'),
|
SharedSubscriptionInvitationCreatedEventHandler: Symbol.for('SharedSubscriptionInvitationCreatedEventHandler'),
|
||||||
PredicateVerificationRequestedEventHandler: Symbol.for('PredicateVerificationRequestedEventHandler'),
|
PredicateVerificationRequestedEventHandler: Symbol.for('PredicateVerificationRequestedEventHandler'),
|
||||||
|
EmailSubscriptionUnsubscribedEventHandler: Symbol.for('EmailSubscriptionUnsubscribedEventHandler'),
|
||||||
// Services
|
// Services
|
||||||
DeviceDetector: Symbol.for('DeviceDetector'),
|
DeviceDetector: Symbol.for('DeviceDetector'),
|
||||||
SessionService: Symbol.for('SessionService'),
|
SessionService: Symbol.for('SessionService'),
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { html } from './offline-subscription-token-created.html'
|
||||||
|
|
||||||
|
export function getSubject(): string {
|
||||||
|
return 'Access to your Standard Notes Subscription Dashboard'
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getBody(email: string, offlineSubscriptionDashboardUrl: string): string {
|
||||||
|
return html(email, offlineSubscriptionDashboardUrl)
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { html } from './shared-subscription-invitation-created.html'
|
||||||
|
|
||||||
|
export function getSubject(): string {
|
||||||
|
return 'You have been invited to a Standard Notes subscription'
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getBody(inviterIdentifier: string, inviteUuid: string): string {
|
||||||
|
return html(inviterIdentifier, inviteUuid)
|
||||||
|
}
|
||||||
@@ -5,11 +5,5 @@ export function getSubject(email: string): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getBody(email: string, device: string, browser: string, date: Date): string {
|
export function getBody(email: string, device: string, browser: string, date: Date): string {
|
||||||
const body = html
|
return html(email, device, browser, date.toLocaleString())
|
||||||
|
|
||||||
return body
|
|
||||||
.replace('%%EMAIL%%', email)
|
|
||||||
.replace('%%DEVICE%%', device)
|
|
||||||
.replace('%%BROWSER%%', browser)
|
|
||||||
.replace('%%TIME_AND_DATE%%', date.toLocaleString())
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
export const html = (userEmail: string, offlineSubscriptionDashboardUrl: string) => `<div class="sn-component">
|
||||||
|
<div class="sk-panel static">
|
||||||
|
<div class="sk-panel-content">
|
||||||
|
<div class="sk-panel-section">
|
||||||
|
<h1 class="h1 title sk-panel-row">
|
||||||
|
<div class="sk-panel-column">
|
||||||
|
Access your Standard Notes Subscription Dashboard,
|
||||||
|
</div>
|
||||||
|
</h1>
|
||||||
|
<div class="faded sk-panel-row small">Registered as ${userEmail}</div>
|
||||||
|
</div>
|
||||||
|
<div class="sk-panel-section">
|
||||||
|
<div class="title">Link to your subscription dashboard: <a
|
||||||
|
href="${offlineSubscriptionDashboardUrl}">${offlineSubscriptionDashboardUrl}</a></div>
|
||||||
|
</div>
|
||||||
|
<div class="sk-panel-section">
|
||||||
|
<p>
|
||||||
|
Get help any time by visiting our <a href="https://standardnotes.com/help">Help page</a>
|
||||||
|
or by replying directly to this email.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>`
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
export const html = (inviterIdentifier: string, inviteUuid: string) => `<p>Hello,</p>
|
||||||
|
<p>You've been invited to join a Standard Notes premium subscription at no cost. ${inviterIdentifier} has invited you to share the benefits of their subscription plan.</p>
|
||||||
|
<p>
|
||||||
|
<a href='https://app.standardnotes.com/?accept-subscription-invite=${inviteUuid}'>Accept Invite</a>
|
||||||
|
</p>`
|
||||||
@@ -1,15 +1,15 @@
|
|||||||
export const html = `
|
export const html = (email: string, device: string, browser: string, timeAndDate: string) => `
|
||||||
<div>
|
<div>
|
||||||
<p>Hello,</p>
|
<p>Hello,</p>
|
||||||
<p>We've detected a new sign-in to your account %%EMAIL%%.</p>
|
<p>We've detected a new sign-in to your account ${email}</p>
|
||||||
<p>
|
<p>
|
||||||
<b>Device type</b>: %%DEVICE%%
|
<b>Device type</b>: ${device}
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<b>Browser type</b>: %%BROWSER%%
|
<b>Browser type</b>: ${browser}
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<strong>Time and date</strong>: <span>%%TIME_AND_DATE%%</span>
|
<strong>Time and date</strong>: <span>${timeAndDate}</span>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
If this was you, please disregard this email. If it wasn't you, we recommend signing into your account and
|
If this was you, please disregard this email. If it wasn't you, we recommend signing into your account and
|
||||||
@@ -20,6 +20,5 @@ export const html = `
|
|||||||
<br />
|
<br />
|
||||||
SN
|
SN
|
||||||
</p>
|
</p>
|
||||||
<a href="https://app.standardnotes.com/?settings=account">Mute these emails</a>
|
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import {
|
|||||||
UserEmailChangedEvent,
|
UserEmailChangedEvent,
|
||||||
UserRegisteredEvent,
|
UserRegisteredEvent,
|
||||||
UserRolesChangedEvent,
|
UserRolesChangedEvent,
|
||||||
OfflineSubscriptionTokenCreatedEvent,
|
|
||||||
EmailBackupRequestedEvent,
|
EmailBackupRequestedEvent,
|
||||||
CloudBackupRequestedEvent,
|
CloudBackupRequestedEvent,
|
||||||
ListedAccountRequestedEvent,
|
ListedAccountRequestedEvent,
|
||||||
@@ -290,24 +289,6 @@ export class DomainEventFactory implements DomainEventFactoryInterface {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
createOfflineSubscriptionTokenCreatedEvent(token: string, email: string): OfflineSubscriptionTokenCreatedEvent {
|
|
||||||
return {
|
|
||||||
type: 'OFFLINE_SUBSCRIPTION_TOKEN_CREATED',
|
|
||||||
createdAt: this.timer.getUTCDate(),
|
|
||||||
meta: {
|
|
||||||
correlation: {
|
|
||||||
userIdentifier: email,
|
|
||||||
userIdentifierType: 'email',
|
|
||||||
},
|
|
||||||
origin: DomainEventService.Auth,
|
|
||||||
},
|
|
||||||
payload: {
|
|
||||||
token,
|
|
||||||
email,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
createUserRegisteredEvent(dto: {
|
createUserRegisteredEvent(dto: {
|
||||||
userUuid: string
|
userUuid: string
|
||||||
email: string
|
email: string
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import {
|
|||||||
UserRegisteredEvent,
|
UserRegisteredEvent,
|
||||||
UserRolesChangedEvent,
|
UserRolesChangedEvent,
|
||||||
UserEmailChangedEvent,
|
UserEmailChangedEvent,
|
||||||
OfflineSubscriptionTokenCreatedEvent,
|
|
||||||
EmailBackupRequestedEvent,
|
EmailBackupRequestedEvent,
|
||||||
ListedAccountRequestedEvent,
|
ListedAccountRequestedEvent,
|
||||||
UserDisabledSessionUserAgentLoggingEvent,
|
UserDisabledSessionUserAgentLoggingEvent,
|
||||||
@@ -56,7 +55,6 @@ export interface DomainEventFactoryInterface {
|
|||||||
}): AccountDeletionRequestedEvent
|
}): AccountDeletionRequestedEvent
|
||||||
createUserRolesChangedEvent(userUuid: string, email: string, currentRoles: RoleName[]): UserRolesChangedEvent
|
createUserRolesChangedEvent(userUuid: string, email: string, currentRoles: RoleName[]): UserRolesChangedEvent
|
||||||
createUserEmailChangedEvent(userUuid: string, fromEmail: string, toEmail: string): UserEmailChangedEvent
|
createUserEmailChangedEvent(userUuid: string, fromEmail: string, toEmail: string): UserEmailChangedEvent
|
||||||
createOfflineSubscriptionTokenCreatedEvent(token: string, email: string): OfflineSubscriptionTokenCreatedEvent
|
|
||||||
createUserDisabledSessionUserAgentLoggingEvent(dto: {
|
createUserDisabledSessionUserAgentLoggingEvent(dto: {
|
||||||
userUuid: Uuid
|
userUuid: Uuid
|
||||||
email: string
|
email: string
|
||||||
|
|||||||
@@ -0,0 +1,109 @@
|
|||||||
|
import { EmailLevel } from '@standardnotes/domain-core'
|
||||||
|
import { EmailSubscriptionUnsubscribedEvent } from '@standardnotes/domain-events'
|
||||||
|
|
||||||
|
import { SettingServiceInterface } from '../Setting/SettingServiceInterface'
|
||||||
|
import { User } from '../User/User'
|
||||||
|
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
|
||||||
|
import { EmailSubscriptionUnsubscribedEventHandler } from './EmailSubscriptionUnsubscribedEventHandler'
|
||||||
|
|
||||||
|
describe('EmailSubscriptionUnsubscribedEventHandler', () => {
|
||||||
|
let userRepository: UserRepositoryInterface
|
||||||
|
let settingsService: SettingServiceInterface
|
||||||
|
let event: EmailSubscriptionUnsubscribedEvent
|
||||||
|
|
||||||
|
const createHandler = () => new EmailSubscriptionUnsubscribedEventHandler(userRepository, settingsService)
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
userRepository = {} as jest.Mocked<UserRepositoryInterface>
|
||||||
|
userRepository.findOneByEmail = jest.fn().mockReturnValue({} as jest.Mocked<User>)
|
||||||
|
|
||||||
|
settingsService = {} as jest.Mocked<SettingServiceInterface>
|
||||||
|
settingsService.createOrReplace = jest.fn()
|
||||||
|
|
||||||
|
event = {
|
||||||
|
payload: {
|
||||||
|
userEmail: 'test@test.te',
|
||||||
|
level: EmailLevel.LEVELS.Marketing,
|
||||||
|
},
|
||||||
|
} as jest.Mocked<EmailSubscriptionUnsubscribedEvent>
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not do anything if user is not found', async () => {
|
||||||
|
userRepository.findOneByEmail = jest.fn().mockReturnValue(null)
|
||||||
|
|
||||||
|
await createHandler().handle(event)
|
||||||
|
|
||||||
|
expect(settingsService.createOrReplace).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should update user marketing email settings', async () => {
|
||||||
|
await createHandler().handle(event)
|
||||||
|
|
||||||
|
expect(settingsService.createOrReplace).toHaveBeenCalledWith({
|
||||||
|
user: {},
|
||||||
|
props: {
|
||||||
|
name: 'MUTE_MARKETING_EMAILS',
|
||||||
|
unencryptedValue: 'muted',
|
||||||
|
sensitive: false,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should update user sign in email settings', async () => {
|
||||||
|
event.payload.level = EmailLevel.LEVELS.SignIn
|
||||||
|
|
||||||
|
await createHandler().handle(event)
|
||||||
|
|
||||||
|
expect(settingsService.createOrReplace).toHaveBeenCalledWith({
|
||||||
|
user: {},
|
||||||
|
props: {
|
||||||
|
name: 'MUTE_SIGN_IN_EMAILS',
|
||||||
|
unencryptedValue: 'muted',
|
||||||
|
sensitive: false,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should update user email backup email settings', async () => {
|
||||||
|
event.payload.level = EmailLevel.LEVELS.FailedEmailBackup
|
||||||
|
|
||||||
|
await createHandler().handle(event)
|
||||||
|
|
||||||
|
expect(settingsService.createOrReplace).toHaveBeenCalledWith({
|
||||||
|
user: {},
|
||||||
|
props: {
|
||||||
|
name: 'MUTE_FAILED_BACKUPS_EMAILS',
|
||||||
|
unencryptedValue: 'muted',
|
||||||
|
sensitive: false,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should update user email backup email settings', async () => {
|
||||||
|
event.payload.level = EmailLevel.LEVELS.FailedCloudBackup
|
||||||
|
|
||||||
|
await createHandler().handle(event)
|
||||||
|
|
||||||
|
expect(settingsService.createOrReplace).toHaveBeenCalledWith({
|
||||||
|
user: {},
|
||||||
|
props: {
|
||||||
|
name: 'MUTE_FAILED_CLOUD_BACKUPS_EMAILS',
|
||||||
|
unencryptedValue: 'muted',
|
||||||
|
sensitive: false,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should throw error for unrecognized level', async () => {
|
||||||
|
event.payload.level = 'foobar'
|
||||||
|
|
||||||
|
let caughtError = null
|
||||||
|
try {
|
||||||
|
await createHandler().handle(event)
|
||||||
|
} catch (error) {
|
||||||
|
caughtError = error
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(caughtError).not.toBeNull()
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import { EmailLevel } from '@standardnotes/domain-core'
|
||||||
|
import { DomainEventHandlerInterface, EmailSubscriptionUnsubscribedEvent } from '@standardnotes/domain-events'
|
||||||
|
import { SettingName } from '@standardnotes/settings'
|
||||||
|
|
||||||
|
import { SettingServiceInterface } from '../Setting/SettingServiceInterface'
|
||||||
|
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
|
||||||
|
|
||||||
|
export class EmailSubscriptionUnsubscribedEventHandler implements DomainEventHandlerInterface {
|
||||||
|
constructor(private userRepository: UserRepositoryInterface, private settingsService: SettingServiceInterface) {}
|
||||||
|
|
||||||
|
async handle(event: EmailSubscriptionUnsubscribedEvent): Promise<void> {
|
||||||
|
const user = await this.userRepository.findOneByEmail(event.payload.userEmail)
|
||||||
|
if (user === null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.settingsService.createOrReplace({
|
||||||
|
user,
|
||||||
|
props: {
|
||||||
|
name: this.getSettingNameFromLevel(event.payload.level),
|
||||||
|
unencryptedValue: 'muted',
|
||||||
|
sensitive: false,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private getSettingNameFromLevel(level: string): string {
|
||||||
|
switch (level) {
|
||||||
|
case EmailLevel.LEVELS.FailedCloudBackup:
|
||||||
|
return SettingName.MuteFailedCloudBackupsEmails
|
||||||
|
case EmailLevel.LEVELS.FailedEmailBackup:
|
||||||
|
return SettingName.MuteFailedBackupsEmails
|
||||||
|
case EmailLevel.LEVELS.Marketing:
|
||||||
|
return SettingName.MuteMarketingEmails
|
||||||
|
case EmailLevel.LEVELS.SignIn:
|
||||||
|
return SettingName.MuteSignInEmails
|
||||||
|
default:
|
||||||
|
throw new Error(`Unknown level: ${level}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+6
-11
@@ -5,7 +5,7 @@ import { TimerInterface } from '@standardnotes/time'
|
|||||||
import { OfflineSubscriptionTokenRepositoryInterface } from '../../Auth/OfflineSubscriptionTokenRepositoryInterface'
|
import { OfflineSubscriptionTokenRepositoryInterface } from '../../Auth/OfflineSubscriptionTokenRepositoryInterface'
|
||||||
|
|
||||||
import { CreateOfflineSubscriptionToken } from './CreateOfflineSubscriptionToken'
|
import { CreateOfflineSubscriptionToken } from './CreateOfflineSubscriptionToken'
|
||||||
import { DomainEventPublisherInterface, OfflineSubscriptionTokenCreatedEvent } from '@standardnotes/domain-events'
|
import { DomainEventPublisherInterface, EmailRequestedEvent } from '@standardnotes/domain-events'
|
||||||
import { DomainEventFactoryInterface } from '../../Event/DomainEventFactoryInterface'
|
import { DomainEventFactoryInterface } from '../../Event/DomainEventFactoryInterface'
|
||||||
import { OfflineUserSubscriptionRepositoryInterface } from '../../Subscription/OfflineUserSubscriptionRepositoryInterface'
|
import { OfflineUserSubscriptionRepositoryInterface } from '../../Subscription/OfflineUserSubscriptionRepositoryInterface'
|
||||||
import { OfflineUserSubscription } from '../../Subscription/OfflineUserSubscription'
|
import { OfflineUserSubscription } from '../../Subscription/OfflineUserSubscription'
|
||||||
@@ -47,9 +47,7 @@ describe('CreateOfflineSubscriptionToken', () => {
|
|||||||
domainEventPublisher.publish = jest.fn()
|
domainEventPublisher.publish = jest.fn()
|
||||||
|
|
||||||
domainEventFactory = {} as jest.Mocked<DomainEventFactoryInterface>
|
domainEventFactory = {} as jest.Mocked<DomainEventFactoryInterface>
|
||||||
domainEventFactory.createOfflineSubscriptionTokenCreatedEvent = jest
|
domainEventFactory.createEmailRequestedEvent = jest.fn().mockReturnValue({} as jest.Mocked<EmailRequestedEvent>)
|
||||||
.fn()
|
|
||||||
.mockReturnValue({} as jest.Mocked<OfflineSubscriptionTokenCreatedEvent>)
|
|
||||||
|
|
||||||
timer = {} as jest.Mocked<TimerInterface>
|
timer = {} as jest.Mocked<TimerInterface>
|
||||||
timer.convertStringDateToMicroseconds = jest.fn().mockReturnValue(1)
|
timer.convertStringDateToMicroseconds = jest.fn().mockReturnValue(1)
|
||||||
@@ -71,10 +69,7 @@ describe('CreateOfflineSubscriptionToken', () => {
|
|||||||
expiresAt: 1,
|
expiresAt: 1,
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(domainEventFactory.createOfflineSubscriptionTokenCreatedEvent).toHaveBeenCalledWith(
|
expect(domainEventFactory.createEmailRequestedEvent).toHaveBeenCalled()
|
||||||
'random-string',
|
|
||||||
'test@test.com',
|
|
||||||
)
|
|
||||||
expect(domainEventPublisher.publish).toHaveBeenCalled()
|
expect(domainEventPublisher.publish).toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -91,7 +86,7 @@ describe('CreateOfflineSubscriptionToken', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
expect(offlineSubscriptionTokenRepository.save).not.toHaveBeenCalled()
|
expect(offlineSubscriptionTokenRepository.save).not.toHaveBeenCalled()
|
||||||
expect(domainEventFactory.createOfflineSubscriptionTokenCreatedEvent).not.toHaveBeenCalled()
|
expect(domainEventFactory.createEmailRequestedEvent).not.toHaveBeenCalled()
|
||||||
expect(domainEventPublisher.publish).not.toHaveBeenCalled()
|
expect(domainEventPublisher.publish).not.toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -110,7 +105,7 @@ describe('CreateOfflineSubscriptionToken', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
expect(offlineSubscriptionTokenRepository.save).not.toHaveBeenCalled()
|
expect(offlineSubscriptionTokenRepository.save).not.toHaveBeenCalled()
|
||||||
expect(domainEventFactory.createOfflineSubscriptionTokenCreatedEvent).not.toHaveBeenCalled()
|
expect(domainEventFactory.createEmailRequestedEvent).not.toHaveBeenCalled()
|
||||||
expect(domainEventPublisher.publish).not.toHaveBeenCalled()
|
expect(domainEventPublisher.publish).not.toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -129,7 +124,7 @@ describe('CreateOfflineSubscriptionToken', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
expect(offlineSubscriptionTokenRepository.save).not.toHaveBeenCalled()
|
expect(offlineSubscriptionTokenRepository.save).not.toHaveBeenCalled()
|
||||||
expect(domainEventFactory.createOfflineSubscriptionTokenCreatedEvent).not.toHaveBeenCalled()
|
expect(domainEventFactory.createEmailRequestedEvent).not.toHaveBeenCalled()
|
||||||
expect(domainEventPublisher.publish).not.toHaveBeenCalled()
|
expect(domainEventPublisher.publish).not.toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
+9
-1
@@ -1,4 +1,5 @@
|
|||||||
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
|
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
|
||||||
|
import { EmailLevel } from '@standardnotes/domain-core'
|
||||||
import { CryptoNode } from '@standardnotes/sncrypto-node'
|
import { CryptoNode } from '@standardnotes/sncrypto-node'
|
||||||
import { TimerInterface } from '@standardnotes/time'
|
import { TimerInterface } from '@standardnotes/time'
|
||||||
import { inject, injectable } from 'inversify'
|
import { inject, injectable } from 'inversify'
|
||||||
@@ -6,6 +7,7 @@ import { Logger } from 'winston'
|
|||||||
|
|
||||||
import TYPES from '../../../Bootstrap/Types'
|
import TYPES from '../../../Bootstrap/Types'
|
||||||
import { OfflineSubscriptionTokenRepositoryInterface } from '../../Auth/OfflineSubscriptionTokenRepositoryInterface'
|
import { OfflineSubscriptionTokenRepositoryInterface } from '../../Auth/OfflineSubscriptionTokenRepositoryInterface'
|
||||||
|
import { getBody, getSubject } from '../../Email/OfflineSubscriptionTokenCreated'
|
||||||
import { DomainEventFactoryInterface } from '../../Event/DomainEventFactoryInterface'
|
import { DomainEventFactoryInterface } from '../../Event/DomainEventFactoryInterface'
|
||||||
import { OfflineUserSubscriptionRepositoryInterface } from '../../Subscription/OfflineUserSubscriptionRepositoryInterface'
|
import { OfflineUserSubscriptionRepositoryInterface } from '../../Subscription/OfflineUserSubscriptionRepositoryInterface'
|
||||||
import { UseCaseInterface } from '../UseCaseInterface'
|
import { UseCaseInterface } from '../UseCaseInterface'
|
||||||
@@ -62,7 +64,13 @@ export class CreateOfflineSubscriptionToken implements UseCaseInterface {
|
|||||||
await this.offlineSubscriptionTokenRepository.save(offlineSubscriptionToken)
|
await this.offlineSubscriptionTokenRepository.save(offlineSubscriptionToken)
|
||||||
|
|
||||||
await this.domainEventPublisher.publish(
|
await this.domainEventPublisher.publish(
|
||||||
this.domainEventFactory.createOfflineSubscriptionTokenCreatedEvent(token, dto.userEmail),
|
this.domainEventFactory.createEmailRequestedEvent({
|
||||||
|
body: getBody(dto.userEmail, `https://standardnotes.com/dashboard/offline?subscription_token=${token}`),
|
||||||
|
level: EmailLevel.LEVELS.System,
|
||||||
|
subject: getSubject(),
|
||||||
|
messageIdentifier: 'OFFLINE_SUBSCRIPTION_ACCESS',
|
||||||
|
userEmail: dto.userEmail,
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
+6
-1
@@ -1,6 +1,10 @@
|
|||||||
import 'reflect-metadata'
|
import 'reflect-metadata'
|
||||||
|
|
||||||
import { DomainEventPublisherInterface, SharedSubscriptionInvitationCreatedEvent } from '@standardnotes/domain-events'
|
import {
|
||||||
|
DomainEventPublisherInterface,
|
||||||
|
SharedSubscriptionInvitationCreatedEvent,
|
||||||
|
EmailRequestedEvent,
|
||||||
|
} from '@standardnotes/domain-events'
|
||||||
import { TimerInterface } from '@standardnotes/time'
|
import { TimerInterface } from '@standardnotes/time'
|
||||||
import { DomainEventFactoryInterface } from '../../Event/DomainEventFactoryInterface'
|
import { DomainEventFactoryInterface } from '../../Event/DomainEventFactoryInterface'
|
||||||
import { SharedSubscriptionInvitationRepositoryInterface } from '../../SharedSubscription/SharedSubscriptionInvitationRepositoryInterface'
|
import { SharedSubscriptionInvitationRepositoryInterface } from '../../SharedSubscription/SharedSubscriptionInvitationRepositoryInterface'
|
||||||
@@ -51,6 +55,7 @@ describe('InviteToSharedSubscription', () => {
|
|||||||
domainEventFactory.createSharedSubscriptionInvitationCreatedEvent = jest
|
domainEventFactory.createSharedSubscriptionInvitationCreatedEvent = jest
|
||||||
.fn()
|
.fn()
|
||||||
.mockReturnValue({} as jest.Mocked<SharedSubscriptionInvitationCreatedEvent>)
|
.mockReturnValue({} as jest.Mocked<SharedSubscriptionInvitationCreatedEvent>)
|
||||||
|
domainEventFactory.createEmailRequestedEvent = jest.fn().mockReturnValue({} as jest.Mocked<EmailRequestedEvent>)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should not create an inivitation for sharing the subscription if inviter has no subscription', async () => {
|
it('should not create an inivitation for sharing the subscription if inviter has no subscription', async () => {
|
||||||
|
|||||||
+12
@@ -1,9 +1,11 @@
|
|||||||
import { RoleName } from '@standardnotes/common'
|
import { RoleName } from '@standardnotes/common'
|
||||||
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
|
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
|
||||||
|
import { EmailLevel } from '@standardnotes/domain-core'
|
||||||
import { TimerInterface } from '@standardnotes/time'
|
import { TimerInterface } from '@standardnotes/time'
|
||||||
import { inject, injectable } from 'inversify'
|
import { inject, injectable } from 'inversify'
|
||||||
|
|
||||||
import TYPES from '../../../Bootstrap/Types'
|
import TYPES from '../../../Bootstrap/Types'
|
||||||
|
import { getBody, getSubject } from '../../Email/SharedSubscriptionInvitationCreated'
|
||||||
import { DomainEventFactoryInterface } from '../../Event/DomainEventFactoryInterface'
|
import { DomainEventFactoryInterface } from '../../Event/DomainEventFactoryInterface'
|
||||||
import { InvitationStatus } from '../../SharedSubscription/InvitationStatus'
|
import { InvitationStatus } from '../../SharedSubscription/InvitationStatus'
|
||||||
import { InviteeIdentifierType } from '../../SharedSubscription/InviteeIdentifierType'
|
import { InviteeIdentifierType } from '../../SharedSubscription/InviteeIdentifierType'
|
||||||
@@ -89,6 +91,16 @@ export class InviteToSharedSubscription implements UseCaseInterface {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
await this.domainEventPublisher.publish(
|
||||||
|
this.domainEventFactory.createEmailRequestedEvent({
|
||||||
|
userEmail: dto.inviteeIdentifier,
|
||||||
|
level: EmailLevel.LEVELS.System,
|
||||||
|
body: getBody(dto.inviterEmail, savedInvitation.uuid),
|
||||||
|
messageIdentifier: 'SHARED_SUBSCRIPTION_INVITATION',
|
||||||
|
subject: getSubject(),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
sharedSubscriptionInvitationUuid: savedInvitation.uuid,
|
sharedSubscriptionInvitationUuid: savedInvitation.uuid,
|
||||||
|
|||||||
@@ -3,6 +3,12 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
# [1.10.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-core@1.9.0...@standardnotes/domain-core@1.10.0) (2022-12-15)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **domain-core:** add session model ([1c4d4c5](https://github.com/standardnotes/server/commit/1c4d4c57dea1187dc130f1ae8b7dc8ede332320f))
|
||||||
|
|
||||||
# [1.9.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-core@1.8.0...@standardnotes/domain-core@1.9.0) (2022-12-07)
|
# [1.9.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-core@1.8.0...@standardnotes/domain-core@1.9.0) (2022-12-07)
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/domain-core",
|
"name": "@standardnotes/domain-core",
|
||||||
"version": "1.9.0",
|
"version": "1.10.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0 <19.0.0"
|
"node": ">=18.0.0 <19.0.0"
|
||||||
},
|
},
|
||||||
@@ -23,10 +23,6 @@
|
|||||||
"test": "jest spec --coverage --passWithNoTests"
|
"test": "jest spec --coverage --passWithNoTests"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@standardnotes/common": "workspace:*",
|
|
||||||
"@standardnotes/features": "^1.52.1",
|
|
||||||
"@standardnotes/predicates": "workspace:*",
|
|
||||||
"@standardnotes/security": "workspace:*",
|
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
"shallow-equal-object": "^1.1.1",
|
"shallow-equal-object": "^1.1.1",
|
||||||
"uuid": "^9.0.0"
|
"uuid": "^9.0.0"
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { Session } from './Session'
|
||||||
|
import { SessionToken } from './SessionToken'
|
||||||
|
|
||||||
|
describe('Session', () => {
|
||||||
|
it('should create a session value object', () => {
|
||||||
|
const accessToken = SessionToken.create('foobar1', 1234567890).getValue()
|
||||||
|
const refreshToken = SessionToken.create('foobar2', 1234567890).getValue()
|
||||||
|
|
||||||
|
const valueOrError = Session.create(accessToken, refreshToken)
|
||||||
|
|
||||||
|
expect(valueOrError.isFailed()).toBeFalsy()
|
||||||
|
expect(valueOrError.getValue().accessToken.value).toEqual('foobar1')
|
||||||
|
expect(valueOrError.getValue().refreshToken.value).toEqual('foobar2')
|
||||||
|
expect(valueOrError.getValue().isReadOnly()).toEqual(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should create a session reado-only value object', () => {
|
||||||
|
const accessToken = SessionToken.create('foobar', 1234567890).getValue()
|
||||||
|
const refreshToken = SessionToken.create('foobar', 1234567890).getValue()
|
||||||
|
|
||||||
|
const valueOrError = Session.create(accessToken, refreshToken, true)
|
||||||
|
|
||||||
|
expect(valueOrError.isFailed()).toBeFalsy()
|
||||||
|
expect(valueOrError.getValue().isReadOnly()).toEqual(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { ValueObject } from '../Core/ValueObject'
|
||||||
|
import { Result } from '../Core/Result'
|
||||||
|
import { SessionProps } from './SessionProps'
|
||||||
|
import { SessionToken } from './SessionToken'
|
||||||
|
|
||||||
|
export class Session extends ValueObject<SessionProps> {
|
||||||
|
get accessToken(): SessionToken {
|
||||||
|
return this.props.accessToken
|
||||||
|
}
|
||||||
|
|
||||||
|
get refreshToken(): SessionToken {
|
||||||
|
return this.props.refreshToken
|
||||||
|
}
|
||||||
|
|
||||||
|
isReadOnly(): boolean {
|
||||||
|
return this.props.readonlyAccess || false
|
||||||
|
}
|
||||||
|
|
||||||
|
private constructor(props: SessionProps) {
|
||||||
|
super(props)
|
||||||
|
}
|
||||||
|
|
||||||
|
static create(accessToken: SessionToken, refreshToken: SessionToken, readonlyAccess?: boolean): Result<Session> {
|
||||||
|
return Result.ok<Session>(new Session({ accessToken, refreshToken, readonlyAccess }))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { SessionToken } from './SessionToken'
|
||||||
|
|
||||||
|
export interface SessionProps {
|
||||||
|
accessToken: SessionToken
|
||||||
|
refreshToken: SessionToken
|
||||||
|
readonlyAccess?: boolean
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { SessionToken } from './SessionToken'
|
||||||
|
|
||||||
|
describe('SessionToken', () => {
|
||||||
|
it('should create a value object', () => {
|
||||||
|
const valueOrError = SessionToken.create('foobar', 1234567890)
|
||||||
|
|
||||||
|
expect(valueOrError.isFailed()).toBeFalsy()
|
||||||
|
expect(valueOrError.getValue().value).toEqual('foobar')
|
||||||
|
expect(valueOrError.getValue().expiresAt).toEqual(1234567890)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not create an invalid value object', () => {
|
||||||
|
let valueOrError = SessionToken.create('', 1234567890)
|
||||||
|
|
||||||
|
expect(valueOrError.isFailed()).toBeTruthy()
|
||||||
|
|
||||||
|
valueOrError = SessionToken.create('foobar', undefined as unknown as number)
|
||||||
|
|
||||||
|
expect(valueOrError.isFailed()).toBeTruthy()
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { ValueObject } from '../Core/ValueObject'
|
||||||
|
import { Result } from '../Core/Result'
|
||||||
|
import { SessionTokenProps } from './SessionTokenProps'
|
||||||
|
import { Validator } from '../Core/Validator'
|
||||||
|
|
||||||
|
export class SessionToken extends ValueObject<SessionTokenProps> {
|
||||||
|
get value(): string {
|
||||||
|
return this.props.value
|
||||||
|
}
|
||||||
|
|
||||||
|
get expiresAt(): number {
|
||||||
|
return this.props.expiresAt
|
||||||
|
}
|
||||||
|
|
||||||
|
private constructor(props: SessionTokenProps) {
|
||||||
|
super(props)
|
||||||
|
}
|
||||||
|
|
||||||
|
static create(value: string, expiresAt: number): Result<SessionToken> {
|
||||||
|
if (Validator.isNotEmpty(value).isFailed()) {
|
||||||
|
return Result.fail<SessionToken>('Could not create session token. Token value is empty')
|
||||||
|
}
|
||||||
|
if (Validator.isNotEmpty(expiresAt).isFailed()) {
|
||||||
|
return Result.fail<SessionToken>('Could not create session token. Token expiration is empty')
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok<SessionToken>(new SessionToken({ value, expiresAt }))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export interface SessionTokenProps {
|
||||||
|
value: string
|
||||||
|
expiresAt: number
|
||||||
|
}
|
||||||
@@ -1,3 +1,8 @@
|
|||||||
|
export * from './Auth/Session'
|
||||||
|
export * from './Auth/SessionProps'
|
||||||
|
export * from './Auth/SessionToken'
|
||||||
|
export * from './Auth/SessionTokenProps'
|
||||||
|
|
||||||
export * from './Common/Dates'
|
export * from './Common/Dates'
|
||||||
export * from './Common/DatesProps'
|
export * from './Common/DatesProps'
|
||||||
export * from './Common/Email'
|
export * from './Common/Email'
|
||||||
|
|||||||
@@ -3,6 +3,50 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [1.9.56](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.9.55...@standardnotes/domain-events-infra@1.9.56) (2022-12-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/domain-events-infra
|
||||||
|
|
||||||
|
## [1.9.55](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.9.54...@standardnotes/domain-events-infra@1.9.55) (2022-12-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/domain-events-infra
|
||||||
|
|
||||||
|
## [1.9.54](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.9.53...@standardnotes/domain-events-infra@1.9.54) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/domain-events-infra
|
||||||
|
|
||||||
|
## [1.9.53](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.9.52...@standardnotes/domain-events-infra@1.9.53) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/domain-events-infra
|
||||||
|
|
||||||
|
## [1.9.52](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.9.51...@standardnotes/domain-events-infra@1.9.52) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/domain-events-infra
|
||||||
|
|
||||||
|
## [1.9.51](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.9.50...@standardnotes/domain-events-infra@1.9.51) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/domain-events-infra
|
||||||
|
|
||||||
|
## [1.9.50](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.9.49...@standardnotes/domain-events-infra@1.9.50) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/domain-events-infra
|
||||||
|
|
||||||
|
## [1.9.49](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.9.48...@standardnotes/domain-events-infra@1.9.49) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/domain-events-infra
|
||||||
|
|
||||||
|
## [1.9.48](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.9.47...@standardnotes/domain-events-infra@1.9.48) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/domain-events-infra
|
||||||
|
|
||||||
|
## [1.9.47](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.9.46...@standardnotes/domain-events-infra@1.9.47) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/domain-events-infra
|
||||||
|
|
||||||
|
## [1.9.46](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.9.45...@standardnotes/domain-events-infra@1.9.46) (2022-12-08)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/domain-events-infra
|
||||||
|
|
||||||
## [1.9.45](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.9.44...@standardnotes/domain-events-infra@1.9.45) (2022-12-08)
|
## [1.9.45](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.9.44...@standardnotes/domain-events-infra@1.9.45) (2022-12-08)
|
||||||
|
|
||||||
**Note:** Version bump only for package @standardnotes/domain-events-infra
|
**Note:** Version bump only for package @standardnotes/domain-events-infra
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/domain-events-infra",
|
"name": "@standardnotes/domain-events-infra",
|
||||||
"version": "1.9.45",
|
"version": "1.9.56",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0 <19.0.0"
|
"node": ">=18.0.0 <19.0.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,6 +3,70 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [2.104.1](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.104.0...@standardnotes/domain-events@2.104.1) (2022-12-12)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **domain-events:** add additional domain event services ([2980c42](https://github.com/standardnotes/server/commit/2980c42e88b6be5f065c91c86bf85a706975f801))
|
||||||
|
|
||||||
|
# [2.104.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.103.2...@standardnotes/domain-events@2.104.0) (2022-12-12)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **domain-events:** add event for email subscription unsubscribed ([7f18fcf](https://github.com/standardnotes/server/commit/7f18fcfc139911620f2ea72729357aefd0613315))
|
||||||
|
|
||||||
|
## [2.103.2](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.103.1...@standardnotes/domain-events@2.103.2) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/domain-events
|
||||||
|
|
||||||
|
## [2.103.1](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.103.0...@standardnotes/domain-events@2.103.1) (2022-12-09)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **domain-events:** add additional styles option for sending email ([20d9214](https://github.com/standardnotes/server/commit/20d92149a8c559edf6aa25932b3dbcbc00b2e878))
|
||||||
|
|
||||||
|
# [2.103.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.102.0...@standardnotes/domain-events@2.103.0) (2022-12-09)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **workspace:** replace workspace invite created event with email requested ([61c1cff](https://github.com/standardnotes/server/commit/61c1cfff4bcee09e1f933cb3e085412b6f07cc42))
|
||||||
|
|
||||||
|
# [2.102.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.101.0...@standardnotes/domain-events@2.102.0) (2022-12-09)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **syncing-server:** replace email backup attachment created with email requested ([32601f3](https://github.com/standardnotes/server/commit/32601f34f181b29b7c62cd2926111a0887d97fbf))
|
||||||
|
|
||||||
|
# [2.101.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.100.0...@standardnotes/domain-events@2.101.0) (2022-12-09)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **syncing-server:** replace one drive backup failed event with email requested ([130f90b](https://github.com/standardnotes/server/commit/130f90bdb6cc88e073b9380e8aed5eebe8c49c1e))
|
||||||
|
|
||||||
|
# [2.100.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.99.0...@standardnotes/domain-events@2.100.0) (2022-12-09)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **syncing-server:** remove google drive backup failed event in favour of email requested ([00fe321](https://github.com/standardnotes/server/commit/00fe32136e7add627e58e8ea223f7f484f1d3718))
|
||||||
|
|
||||||
|
# [2.99.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.98.4...@standardnotes/domain-events@2.99.0) (2022-12-09)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **auth:** remove offline subscription token created event in favour of email requested ([fd58992](https://github.com/standardnotes/server/commit/fd589922bba29595a0dfd154a42fe158024fad28))
|
||||||
|
|
||||||
|
## [2.98.4](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.98.3...@standardnotes/domain-events@2.98.4) (2022-12-09)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **domain-events:** remove unused event ([cc4b4f9](https://github.com/standardnotes/server/commit/cc4b4f9bf831b9aabec7d506d977ee1df50d5222))
|
||||||
|
|
||||||
|
## [2.98.3](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.98.2...@standardnotes/domain-events@2.98.3) (2022-12-08)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **domain-events:** remove unused event ([cffc1f4](https://github.com/standardnotes/server/commit/cffc1f442f3c6f781c4468ac96245e13f57115d5))
|
||||||
|
|
||||||
## [2.98.2](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.98.1...@standardnotes/domain-events@2.98.2) (2022-12-08)
|
## [2.98.2](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.98.1...@standardnotes/domain-events@2.98.2) (2022-12-08)
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/domain-events",
|
"name": "@standardnotes/domain-events",
|
||||||
"version": "2.98.2",
|
"version": "2.104.1",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0 <19.0.0"
|
"node": ">=18.0.0 <19.0.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
import { DomainEventInterface } from './DomainEventInterface'
|
|
||||||
import { DailyAnalyticsReportGeneratedEventPayload } from './DailyAnalyticsReportGeneratedEventPayload'
|
|
||||||
|
|
||||||
export interface DailyAnalyticsReportGeneratedEvent extends DomainEventInterface {
|
|
||||||
type: 'DAILY_ANALYTICS_REPORT_GENERATED'
|
|
||||||
payload: DailyAnalyticsReportGeneratedEventPayload
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
export interface DailyAnalyticsReportGeneratedEventPayload {
|
|
||||||
activityStatistics: Array<{
|
|
||||||
name: string
|
|
||||||
retention: number
|
|
||||||
totalCount: number
|
|
||||||
}>
|
|
||||||
statisticMeasures: Array<{
|
|
||||||
name: string
|
|
||||||
totalValue: number
|
|
||||||
average: number
|
|
||||||
increments: number
|
|
||||||
period: number
|
|
||||||
}>
|
|
||||||
activityStatisticsOverTime: Array<{
|
|
||||||
name: string
|
|
||||||
period: number
|
|
||||||
counts: Array<{
|
|
||||||
periodKey: string
|
|
||||||
totalCount: number
|
|
||||||
}>
|
|
||||||
totalCount: number
|
|
||||||
}>
|
|
||||||
statisticsOverTime: Array<{
|
|
||||||
name: string
|
|
||||||
period: number
|
|
||||||
counts: Array<{
|
|
||||||
periodKey: string
|
|
||||||
totalCount: number
|
|
||||||
}>
|
|
||||||
}>
|
|
||||||
churn: {
|
|
||||||
periodKeys: Array<string>
|
|
||||||
values: Array<{
|
|
||||||
rate: number
|
|
||||||
averageCustomersCount: number
|
|
||||||
existingCustomersChurn: number
|
|
||||||
newCustomersChurn: number
|
|
||||||
periodKey: string
|
|
||||||
}>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -10,4 +10,7 @@ export enum DomainEventService {
|
|||||||
Scheduler = 'scheduler',
|
Scheduler = 'scheduler',
|
||||||
Workspace = 'workspace',
|
Workspace = 'workspace',
|
||||||
Analytics = 'analytics',
|
Analytics = 'analytics',
|
||||||
|
Revisions = 'revisions',
|
||||||
|
Email = 'email',
|
||||||
|
Settings = 'settings',
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
import { DomainEventInterface } from './DomainEventInterface'
|
|
||||||
|
|
||||||
import { DropboxBackupFailedEventPayload } from './DropboxBackupFailedEventPayload'
|
|
||||||
|
|
||||||
export interface DropboxBackupFailedEvent extends DomainEventInterface {
|
|
||||||
type: 'DROPBOX_BACKUP_FAILED'
|
|
||||||
payload: DropboxBackupFailedEventPayload
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
export interface DropboxBackupFailedEventPayload {
|
|
||||||
muteCloudEmailsSettingUuid: string
|
|
||||||
extensionSettingUuid?: string
|
|
||||||
email: string
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
import { DomainEventInterface } from './DomainEventInterface'
|
|
||||||
import { EmailBackupAttachmentCreatedEventPayload } from './EmailBackupAttachmentCreatedEventPayload'
|
|
||||||
|
|
||||||
export interface EmailBackupAttachmentCreatedEvent extends DomainEventInterface {
|
|
||||||
type: 'EMAIL_BACKUP_ATTACHMENT_CREATED'
|
|
||||||
payload: EmailBackupAttachmentCreatedEventPayload
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
export interface EmailBackupAttachmentCreatedEventPayload {
|
|
||||||
backupFileName: string
|
|
||||||
backupFileIndex: number
|
|
||||||
backupFilesTotal: number
|
|
||||||
email: string
|
|
||||||
}
|
|
||||||
@@ -4,6 +4,8 @@ export interface EmailRequestedEventPayload {
|
|||||||
level: string
|
level: string
|
||||||
subject: string
|
subject: string
|
||||||
body: string
|
body: string
|
||||||
|
sender?: string
|
||||||
|
additionalStyles?: string
|
||||||
attachments?: Array<{
|
attachments?: Array<{
|
||||||
filePath: string
|
filePath: string
|
||||||
fileName: string
|
fileName: string
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { DomainEventInterface } from './DomainEventInterface'
|
||||||
|
import { EmailSubscriptionUnsubscribedEventPayload } from './EmailSubscriptionUnsubscribedEventPayload'
|
||||||
|
|
||||||
|
export interface EmailSubscriptionUnsubscribedEvent extends DomainEventInterface {
|
||||||
|
type: 'EMAIL_SUBSCRIPTION_UNSUBSCRIBED'
|
||||||
|
payload: EmailSubscriptionUnsubscribedEventPayload
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export interface EmailSubscriptionUnsubscribedEventPayload {
|
||||||
|
userEmail: string
|
||||||
|
level: string
|
||||||
|
}
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
import { DomainEventInterface } from './DomainEventInterface'
|
|
||||||
|
|
||||||
import { GoogleDriveBackupFailedEventPayload } from './GoogleDriveBackupFailedEventPayload'
|
|
||||||
|
|
||||||
export interface GoogleDriveBackupFailedEvent extends DomainEventInterface {
|
|
||||||
type: 'GOOGLE_DRIVE_BACKUP_FAILED'
|
|
||||||
payload: GoogleDriveBackupFailedEventPayload
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
export interface GoogleDriveBackupFailedEventPayload {
|
|
||||||
muteCloudEmailsSettingUuid: string
|
|
||||||
extensionSettingUuid?: string
|
|
||||||
email: string
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
import { DomainEventInterface } from './DomainEventInterface'
|
|
||||||
|
|
||||||
import { OfflineSubscriptionTokenCreatedEventPayload } from './OfflineSubscriptionTokenCreatedEventPayload'
|
|
||||||
|
|
||||||
export interface OfflineSubscriptionTokenCreatedEvent extends DomainEventInterface {
|
|
||||||
type: 'OFFLINE_SUBSCRIPTION_TOKEN_CREATED'
|
|
||||||
payload: OfflineSubscriptionTokenCreatedEventPayload
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
export interface OfflineSubscriptionTokenCreatedEventPayload {
|
|
||||||
token: string
|
|
||||||
email: string
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
import { DomainEventInterface } from './DomainEventInterface'
|
|
||||||
|
|
||||||
import { OneDriveBackupFailedEventPayload } from './OneDriveBackupFailedEventPayload'
|
|
||||||
|
|
||||||
export interface OneDriveBackupFailedEvent extends DomainEventInterface {
|
|
||||||
type: 'ONE_DRIVE_BACKUP_FAILED'
|
|
||||||
payload: OneDriveBackupFailedEventPayload
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
export interface OneDriveBackupFailedEventPayload {
|
|
||||||
muteCloudEmailsSettingUuid: string
|
|
||||||
extensionSettingUuid?: string
|
|
||||||
email: string
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
import { DomainEventInterface } from './DomainEventInterface'
|
|
||||||
|
|
||||||
import { RefundRequestedEventPayload } from './RefundRequestedEventPayload'
|
|
||||||
|
|
||||||
export interface RefundRequestedEvent extends DomainEventInterface {
|
|
||||||
type: 'REFUND_REQUESTED'
|
|
||||||
payload: RefundRequestedEventPayload
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
export interface RefundRequestedEventPayload {
|
|
||||||
userEmail: string
|
|
||||||
refundProcessingLink: string
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
import { DomainEventInterface } from './DomainEventInterface'
|
|
||||||
|
|
||||||
import { SubscriptionRateAdjustedEventPayload } from './SubscriptionRateAdjustedEventPayload'
|
|
||||||
|
|
||||||
export interface SubscriptionRateAdjustedEvent extends DomainEventInterface {
|
|
||||||
type: 'SUBSCRIPTION_RATE_ADJUSTED'
|
|
||||||
payload: SubscriptionRateAdjustedEventPayload
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
export interface SubscriptionRateAdjustedEventPayload {
|
|
||||||
userEmail: string
|
|
||||||
newRateFormatted: string
|
|
||||||
refundAmountInDollarsFormatted: string
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
import { DomainEventInterface } from './DomainEventInterface'
|
|
||||||
import { WorkspaceInviteCreatedEventPayload } from './WorkspaceInviteCreatedEventPayload'
|
|
||||||
|
|
||||||
export interface WorkspaceInviteCreatedEvent extends DomainEventInterface {
|
|
||||||
type: 'WORKSPACE_INVITE_CREATED'
|
|
||||||
payload: WorkspaceInviteCreatedEventPayload
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
export interface WorkspaceInviteCreatedEventPayload {
|
|
||||||
inviterUuid: string
|
|
||||||
inviteeEmail: string
|
|
||||||
inviteUuid: string
|
|
||||||
workspaceUuid: string
|
|
||||||
}
|
|
||||||
@@ -2,26 +2,22 @@ export * from './Event/AccountDeletionRequestedEvent'
|
|||||||
export * from './Event/AccountDeletionRequestedEventPayload'
|
export * from './Event/AccountDeletionRequestedEventPayload'
|
||||||
export * from './Event/CloudBackupRequestedEvent'
|
export * from './Event/CloudBackupRequestedEvent'
|
||||||
export * from './Event/CloudBackupRequestedEventPayload'
|
export * from './Event/CloudBackupRequestedEventPayload'
|
||||||
export * from './Event/DailyAnalyticsReportGeneratedEvent'
|
|
||||||
export * from './Event/DailyAnalyticsReportGeneratedEventPayload'
|
|
||||||
export * from './Event/DiscountApplyRequestedEvent'
|
export * from './Event/DiscountApplyRequestedEvent'
|
||||||
export * from './Event/DiscountApplyRequestedEventPayload'
|
export * from './Event/DiscountApplyRequestedEventPayload'
|
||||||
export * from './Event/DiscountWithdrawRequestedEvent'
|
export * from './Event/DiscountWithdrawRequestedEvent'
|
||||||
export * from './Event/DiscountWithdrawRequestedEventPayload'
|
export * from './Event/DiscountWithdrawRequestedEventPayload'
|
||||||
export * from './Event/DomainEventInterface'
|
export * from './Event/DomainEventInterface'
|
||||||
export * from './Event/DomainEventService'
|
export * from './Event/DomainEventService'
|
||||||
export * from './Event/DropboxBackupFailedEvent'
|
|
||||||
export * from './Event/DropboxBackupFailedEventPayload'
|
|
||||||
export * from './Event/DuplicateItemSyncedEvent'
|
export * from './Event/DuplicateItemSyncedEvent'
|
||||||
export * from './Event/DuplicateItemSyncedEventPayload'
|
export * from './Event/DuplicateItemSyncedEventPayload'
|
||||||
export * from './Event/EmailArchiveExtensionSyncedEvent'
|
export * from './Event/EmailArchiveExtensionSyncedEvent'
|
||||||
export * from './Event/EmailArchiveExtensionSyncedEventPayload'
|
export * from './Event/EmailArchiveExtensionSyncedEventPayload'
|
||||||
export * from './Event/EmailBackupAttachmentCreatedEvent'
|
|
||||||
export * from './Event/EmailBackupAttachmentCreatedEventPayload'
|
|
||||||
export * from './Event/EmailBackupRequestedEvent'
|
export * from './Event/EmailBackupRequestedEvent'
|
||||||
export * from './Event/EmailBackupRequestedEventPayload'
|
export * from './Event/EmailBackupRequestedEventPayload'
|
||||||
export * from './Event/EmailRequestedEvent'
|
export * from './Event/EmailRequestedEvent'
|
||||||
export * from './Event/EmailRequestedEventPayload'
|
export * from './Event/EmailRequestedEventPayload'
|
||||||
|
export * from './Event/EmailSubscriptionUnsubscribedEvent'
|
||||||
|
export * from './Event/EmailSubscriptionUnsubscribedEventPayload'
|
||||||
export * from './Event/ExitDiscountAppliedEvent'
|
export * from './Event/ExitDiscountAppliedEvent'
|
||||||
export * from './Event/ExitDiscountAppliedEventPayload'
|
export * from './Event/ExitDiscountAppliedEventPayload'
|
||||||
export * from './Event/ExitDiscountApplyRequestedEvent'
|
export * from './Event/ExitDiscountApplyRequestedEvent'
|
||||||
@@ -34,8 +30,6 @@ export * from './Event/FileRemovedEvent'
|
|||||||
export * from './Event/FileRemovedEventPayload'
|
export * from './Event/FileRemovedEventPayload'
|
||||||
export * from './Event/FileUploadedEvent'
|
export * from './Event/FileUploadedEvent'
|
||||||
export * from './Event/FileUploadedEventPayload'
|
export * from './Event/FileUploadedEventPayload'
|
||||||
export * from './Event/GoogleDriveBackupFailedEvent'
|
|
||||||
export * from './Event/GoogleDriveBackupFailedEventPayload'
|
|
||||||
export * from './Event/ItemDumpedEvent'
|
export * from './Event/ItemDumpedEvent'
|
||||||
export * from './Event/ItemDumpedEventPayload'
|
export * from './Event/ItemDumpedEventPayload'
|
||||||
export * from './Event/ItemRevisionCreationRequestedEvent'
|
export * from './Event/ItemRevisionCreationRequestedEvent'
|
||||||
@@ -50,10 +44,6 @@ export * from './Event/ListedAccountRequestedEvent'
|
|||||||
export * from './Event/ListedAccountRequestedEventPayload'
|
export * from './Event/ListedAccountRequestedEventPayload'
|
||||||
export * from './Event/MuteEmailsSettingChangedEvent'
|
export * from './Event/MuteEmailsSettingChangedEvent'
|
||||||
export * from './Event/MuteEmailsSettingChangedEventPayload'
|
export * from './Event/MuteEmailsSettingChangedEventPayload'
|
||||||
export * from './Event/OfflineSubscriptionTokenCreatedEvent'
|
|
||||||
export * from './Event/OfflineSubscriptionTokenCreatedEventPayload'
|
|
||||||
export * from './Event/OneDriveBackupFailedEvent'
|
|
||||||
export * from './Event/OneDriveBackupFailedEventPayload'
|
|
||||||
export * from './Event/PaymentFailedEvent'
|
export * from './Event/PaymentFailedEvent'
|
||||||
export * from './Event/PaymentFailedEventPayload'
|
export * from './Event/PaymentFailedEventPayload'
|
||||||
export * from './Event/PaymentSuccessEvent'
|
export * from './Event/PaymentSuccessEvent'
|
||||||
@@ -62,8 +52,6 @@ export * from './Event/PredicateVerificationRequestedEvent'
|
|||||||
export * from './Event/PredicateVerificationRequestedEventPayload'
|
export * from './Event/PredicateVerificationRequestedEventPayload'
|
||||||
export * from './Event/PredicateVerifiedEvent'
|
export * from './Event/PredicateVerifiedEvent'
|
||||||
export * from './Event/PredicateVerifiedEventPayload'
|
export * from './Event/PredicateVerifiedEventPayload'
|
||||||
export * from './Event/RefundRequestedEvent'
|
|
||||||
export * from './Event/RefundRequestedEventPayload'
|
|
||||||
export * from './Event/RefundProcessedEvent'
|
export * from './Event/RefundProcessedEvent'
|
||||||
export * from './Event/RefundProcessedEventPayload'
|
export * from './Event/RefundProcessedEventPayload'
|
||||||
export * from './Event/RevisionsCopyRequestedEvent'
|
export * from './Event/RevisionsCopyRequestedEvent'
|
||||||
@@ -78,8 +66,6 @@ export * from './Event/SubscriptionCancelledEvent'
|
|||||||
export * from './Event/SubscriptionCancelledEventPayload'
|
export * from './Event/SubscriptionCancelledEventPayload'
|
||||||
export * from './Event/SubscriptionPurchasedEvent'
|
export * from './Event/SubscriptionPurchasedEvent'
|
||||||
export * from './Event/SubscriptionPurchasedEventPayload'
|
export * from './Event/SubscriptionPurchasedEventPayload'
|
||||||
export * from './Event/SubscriptionRateAdjustedEvent'
|
|
||||||
export * from './Event/SubscriptionRateAdjustedEventPayload'
|
|
||||||
export * from './Event/SubscriptionReactivatedEvent'
|
export * from './Event/SubscriptionReactivatedEvent'
|
||||||
export * from './Event/SubscriptionReactivatedEventPayload'
|
export * from './Event/SubscriptionReactivatedEventPayload'
|
||||||
export * from './Event/SubscriptionReassignedEvent'
|
export * from './Event/SubscriptionReassignedEvent'
|
||||||
@@ -108,8 +94,6 @@ export * from './Event/WebSocketMessageRequestedEvent'
|
|||||||
export * from './Event/WebSocketMessageRequestedEventPayload'
|
export * from './Event/WebSocketMessageRequestedEventPayload'
|
||||||
export * from './Event/WorkspaceInviteAcceptedEvent'
|
export * from './Event/WorkspaceInviteAcceptedEvent'
|
||||||
export * from './Event/WorkspaceInviteAcceptedEventPayload'
|
export * from './Event/WorkspaceInviteAcceptedEventPayload'
|
||||||
export * from './Event/WorkspaceInviteCreatedEvent'
|
|
||||||
export * from './Event/WorkspaceInviteCreatedEventPayload'
|
|
||||||
|
|
||||||
export * from './Handler/DomainEventHandlerInterface'
|
export * from './Handler/DomainEventHandlerInterface'
|
||||||
export * from './Handler/DomainEventMessageHandlerInterface'
|
export * from './Handler/DomainEventMessageHandlerInterface'
|
||||||
|
|||||||
@@ -3,6 +3,50 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [1.6.53](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.6.52...@standardnotes/event-store@1.6.53) (2022-12-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/event-store
|
||||||
|
|
||||||
|
## [1.6.52](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.6.51...@standardnotes/event-store@1.6.52) (2022-12-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/event-store
|
||||||
|
|
||||||
|
## [1.6.51](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.6.50...@standardnotes/event-store@1.6.51) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/event-store
|
||||||
|
|
||||||
|
## [1.6.50](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.6.49...@standardnotes/event-store@1.6.50) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/event-store
|
||||||
|
|
||||||
|
## [1.6.49](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.6.48...@standardnotes/event-store@1.6.49) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/event-store
|
||||||
|
|
||||||
|
## [1.6.48](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.6.47...@standardnotes/event-store@1.6.48) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/event-store
|
||||||
|
|
||||||
|
## [1.6.47](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.6.46...@standardnotes/event-store@1.6.47) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/event-store
|
||||||
|
|
||||||
|
## [1.6.46](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.6.45...@standardnotes/event-store@1.6.46) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/event-store
|
||||||
|
|
||||||
|
## [1.6.45](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.6.44...@standardnotes/event-store@1.6.45) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/event-store
|
||||||
|
|
||||||
|
## [1.6.44](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.6.43...@standardnotes/event-store@1.6.44) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/event-store
|
||||||
|
|
||||||
|
## [1.6.43](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.6.42...@standardnotes/event-store@1.6.43) (2022-12-08)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/event-store
|
||||||
|
|
||||||
## [1.6.42](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.6.41...@standardnotes/event-store@1.6.42) (2022-12-08)
|
## [1.6.42](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.6.41...@standardnotes/event-store@1.6.42) (2022-12-08)
|
||||||
|
|
||||||
**Note:** Version bump only for package @standardnotes/event-store
|
**Note:** Version bump only for package @standardnotes/event-store
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/event-store",
|
"name": "@standardnotes/event-store",
|
||||||
"version": "1.6.42",
|
"version": "1.6.53",
|
||||||
"description": "Event Store Service",
|
"description": "Event Store Service",
|
||||||
"private": true,
|
"private": true,
|
||||||
"main": "dist/src/index.js",
|
"main": "dist/src/index.js",
|
||||||
|
|||||||
@@ -3,6 +3,50 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [1.8.52](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.8.51...@standardnotes/files-server@1.8.52) (2022-12-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/files-server
|
||||||
|
|
||||||
|
## [1.8.51](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.8.50...@standardnotes/files-server@1.8.51) (2022-12-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/files-server
|
||||||
|
|
||||||
|
## [1.8.50](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.8.49...@standardnotes/files-server@1.8.50) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/files-server
|
||||||
|
|
||||||
|
## [1.8.49](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.8.48...@standardnotes/files-server@1.8.49) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/files-server
|
||||||
|
|
||||||
|
## [1.8.48](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.8.47...@standardnotes/files-server@1.8.48) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/files-server
|
||||||
|
|
||||||
|
## [1.8.47](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.8.46...@standardnotes/files-server@1.8.47) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/files-server
|
||||||
|
|
||||||
|
## [1.8.46](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.8.45...@standardnotes/files-server@1.8.46) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/files-server
|
||||||
|
|
||||||
|
## [1.8.45](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.8.44...@standardnotes/files-server@1.8.45) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/files-server
|
||||||
|
|
||||||
|
## [1.8.44](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.8.43...@standardnotes/files-server@1.8.44) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/files-server
|
||||||
|
|
||||||
|
## [1.8.43](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.8.42...@standardnotes/files-server@1.8.43) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/files-server
|
||||||
|
|
||||||
|
## [1.8.42](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.8.41...@standardnotes/files-server@1.8.42) (2022-12-08)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/files-server
|
||||||
|
|
||||||
## [1.8.41](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.8.40...@standardnotes/files-server@1.8.41) (2022-12-08)
|
## [1.8.41](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.8.40...@standardnotes/files-server@1.8.41) (2022-12-08)
|
||||||
|
|
||||||
**Note:** Version bump only for package @standardnotes/files-server
|
**Note:** Version bump only for package @standardnotes/files-server
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/files-server",
|
"name": "@standardnotes/files-server",
|
||||||
"version": "1.8.41",
|
"version": "1.8.52",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0 <19.0.0"
|
"node": ">=18.0.0 <19.0.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,6 +3,60 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [1.9.27](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.9.26...@standardnotes/revisions-server@1.9.27) (2022-12-15)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/revisions-server
|
||||||
|
|
||||||
|
## [1.9.26](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.9.25...@standardnotes/revisions-server@1.9.26) (2022-12-12)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **revisions:** responses to match previous response structure ([530a426](https://github.com/standardnotes/server/commit/530a42660157b63d034cd2228fc83a3fcce921e0))
|
||||||
|
|
||||||
|
## [1.9.25](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.9.24...@standardnotes/revisions-server@1.9.25) (2022-12-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/revisions-server
|
||||||
|
|
||||||
|
## [1.9.24](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.9.23...@standardnotes/revisions-server@1.9.24) (2022-12-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/revisions-server
|
||||||
|
|
||||||
|
## [1.9.23](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.9.22...@standardnotes/revisions-server@1.9.23) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/revisions-server
|
||||||
|
|
||||||
|
## [1.9.22](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.9.21...@standardnotes/revisions-server@1.9.22) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/revisions-server
|
||||||
|
|
||||||
|
## [1.9.21](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.9.20...@standardnotes/revisions-server@1.9.21) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/revisions-server
|
||||||
|
|
||||||
|
## [1.9.20](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.9.19...@standardnotes/revisions-server@1.9.20) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/revisions-server
|
||||||
|
|
||||||
|
## [1.9.19](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.9.18...@standardnotes/revisions-server@1.9.19) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/revisions-server
|
||||||
|
|
||||||
|
## [1.9.18](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.9.17...@standardnotes/revisions-server@1.9.18) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/revisions-server
|
||||||
|
|
||||||
|
## [1.9.17](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.9.16...@standardnotes/revisions-server@1.9.17) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/revisions-server
|
||||||
|
|
||||||
|
## [1.9.16](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.9.15...@standardnotes/revisions-server@1.9.16) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/revisions-server
|
||||||
|
|
||||||
|
## [1.9.15](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.9.14...@standardnotes/revisions-server@1.9.15) (2022-12-08)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/revisions-server
|
||||||
|
|
||||||
## [1.9.14](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.9.13...@standardnotes/revisions-server@1.9.14) (2022-12-08)
|
## [1.9.14](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.9.13...@standardnotes/revisions-server@1.9.14) (2022-12-08)
|
||||||
|
|
||||||
**Note:** Version bump only for package @standardnotes/revisions-server
|
**Note:** Version bump only for package @standardnotes/revisions-server
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/revisions-server",
|
"name": "@standardnotes/revisions-server",
|
||||||
"version": "1.9.14",
|
"version": "1.9.27",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0 <19.0.0"
|
"node": ">=18.0.0 <19.0.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,11 +2,13 @@ import { Logger } from 'winston'
|
|||||||
import { HttpResponse, HttpStatusCode } from '@standardnotes/api'
|
import { HttpResponse, HttpStatusCode } from '@standardnotes/api'
|
||||||
|
|
||||||
import { GetRevisionsMetada } from '../Domain/UseCase/GetRevisionsMetada/GetRevisionsMetada'
|
import { GetRevisionsMetada } from '../Domain/UseCase/GetRevisionsMetada/GetRevisionsMetada'
|
||||||
import { GetRevisionsMetadataRequestParams } from '../Infra/Http/GetRevisionsMetadataRequestParams'
|
import { GetRevisionsMetadataRequestParams } from '../Infra/Http/Request/GetRevisionsMetadataRequestParams'
|
||||||
import { GetRevisionRequestParams } from '../Infra/Http/GetRevisionRequestParams'
|
import { GetRevisionRequestParams } from '../Infra/Http/Request/GetRevisionRequestParams'
|
||||||
|
import { DeleteRevisionRequestParams } from '../Infra/Http/Request/DeleteRevisionRequestParams'
|
||||||
import { GetRevision } from '../Domain/UseCase/GetRevision/GetRevision'
|
import { GetRevision } from '../Domain/UseCase/GetRevision/GetRevision'
|
||||||
import { DeleteRevision } from '../Domain/UseCase/DeleteRevision/DeleteRevision'
|
import { DeleteRevision } from '../Domain/UseCase/DeleteRevision/DeleteRevision'
|
||||||
import { DeleteRevisionRequestParams } from '../Infra/Http/DeleteRevisionRequestParams'
|
import { GetRevisionsMetadataResponse } from '../Infra/Http/Response/GetRevisionsMetadataResponse'
|
||||||
|
import { GetRevisionResponse } from '../Infra/Http/Response/GetRevisionResponse'
|
||||||
|
|
||||||
export class RevisionsController {
|
export class RevisionsController {
|
||||||
constructor(
|
constructor(
|
||||||
@@ -16,7 +18,7 @@ export class RevisionsController {
|
|||||||
private logger: Logger,
|
private logger: Logger,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async getRevisions(params: GetRevisionsMetadataRequestParams): Promise<HttpResponse> {
|
async getRevisions(params: GetRevisionsMetadataRequestParams): Promise<GetRevisionsMetadataResponse> {
|
||||||
const revisionMetadataOrError = await this.getRevisionsMetadata.execute({
|
const revisionMetadataOrError = await this.getRevisionsMetadata.execute({
|
||||||
itemUuid: params.itemUuid,
|
itemUuid: params.itemUuid,
|
||||||
userUuid: params.userUuid,
|
userUuid: params.userUuid,
|
||||||
@@ -41,7 +43,7 @@ export class RevisionsController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getRevision(params: GetRevisionRequestParams): Promise<HttpResponse> {
|
async getRevision(params: GetRevisionRequestParams): Promise<GetRevisionResponse> {
|
||||||
const revisionOrError = await this.doGetRevision.execute({
|
const revisionOrError = await this.doGetRevision.execute({
|
||||||
revisionUuid: params.revisionUuid,
|
revisionUuid: params.revisionUuid,
|
||||||
userUuid: params.userUuid,
|
userUuid: params.userUuid,
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import { HttpErrorResponseBody, HttpResponse } from '@standardnotes/api'
|
||||||
|
import { Either } from '@standardnotes/common'
|
||||||
|
|
||||||
|
import { GetRevisionResponseBody } from './GetRevisionResponseBody'
|
||||||
|
|
||||||
|
export interface GetRevisionResponse extends HttpResponse {
|
||||||
|
data: Either<GetRevisionResponseBody, HttpErrorResponseBody>
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import { Revision } from '../../../Domain/Revision/Revision'
|
||||||
|
|
||||||
|
export interface GetRevisionResponseBody {
|
||||||
|
revision: Revision
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import { HttpErrorResponseBody, HttpResponse } from '@standardnotes/api'
|
||||||
|
import { Either } from '@standardnotes/common'
|
||||||
|
|
||||||
|
import { GetRevisionsMetadataResponseBody } from './GetRevisionsMetadataResponseBody'
|
||||||
|
|
||||||
|
export interface GetRevisionsMetadataResponse extends HttpResponse {
|
||||||
|
data: Either<GetRevisionsMetadataResponseBody, HttpErrorResponseBody>
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import { RevisionMetadata } from '../../../Domain/Revision/RevisionMetadata'
|
||||||
|
|
||||||
|
export interface GetRevisionsMetadataResponseBody {
|
||||||
|
revisions: Array<RevisionMetadata>
|
||||||
|
}
|
||||||
+2
-2
@@ -18,7 +18,7 @@ export class InversifyExpressRevisionsController extends BaseHttpController {
|
|||||||
userUuid: response.locals.user.uuid,
|
userUuid: response.locals.user.uuid,
|
||||||
})
|
})
|
||||||
|
|
||||||
return this.json(result.data, result.status)
|
return this.json(result.data.error ? result.data : result.data.revisions, result.status)
|
||||||
}
|
}
|
||||||
|
|
||||||
@httpGet('/:uuid')
|
@httpGet('/:uuid')
|
||||||
@@ -28,7 +28,7 @@ export class InversifyExpressRevisionsController extends BaseHttpController {
|
|||||||
userUuid: response.locals.user.uuid,
|
userUuid: response.locals.user.uuid,
|
||||||
})
|
})
|
||||||
|
|
||||||
return this.json(result.data, result.status)
|
return this.json(result.data.error ? result.data : result.data.revision, result.status)
|
||||||
}
|
}
|
||||||
|
|
||||||
@httpDelete('/:uuid')
|
@httpDelete('/:uuid')
|
||||||
|
|||||||
@@ -3,6 +3,56 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [1.15.7](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.15.6...@standardnotes/scheduler-server@1.15.7) (2022-12-15)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/scheduler-server
|
||||||
|
|
||||||
|
## [1.15.6](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.15.5...@standardnotes/scheduler-server@1.15.6) (2022-12-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/scheduler-server
|
||||||
|
|
||||||
|
## [1.15.5](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.15.4...@standardnotes/scheduler-server@1.15.5) (2022-12-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/scheduler-server
|
||||||
|
|
||||||
|
## [1.15.4](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.15.3...@standardnotes/scheduler-server@1.15.4) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/scheduler-server
|
||||||
|
|
||||||
|
## [1.15.3](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.15.2...@standardnotes/scheduler-server@1.15.3) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/scheduler-server
|
||||||
|
|
||||||
|
## [1.15.2](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.15.1...@standardnotes/scheduler-server@1.15.2) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/scheduler-server
|
||||||
|
|
||||||
|
## [1.15.1](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.15.0...@standardnotes/scheduler-server@1.15.1) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/scheduler-server
|
||||||
|
|
||||||
|
# [1.15.0](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.14.10...@standardnotes/scheduler-server@1.15.0) (2022-12-09)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **syncing-server:** replace one drive backup failed event with email requested ([130f90b](https://github.com/standardnotes/server/commit/130f90bdb6cc88e073b9380e8aed5eebe8c49c1e))
|
||||||
|
|
||||||
|
## [1.14.10](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.14.9...@standardnotes/scheduler-server@1.14.10) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/scheduler-server
|
||||||
|
|
||||||
|
## [1.14.9](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.14.8...@standardnotes/scheduler-server@1.14.9) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/scheduler-server
|
||||||
|
|
||||||
|
## [1.14.8](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.14.7...@standardnotes/scheduler-server@1.14.8) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/scheduler-server
|
||||||
|
|
||||||
|
## [1.14.7](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.14.6...@standardnotes/scheduler-server@1.14.7) (2022-12-08)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/scheduler-server
|
||||||
|
|
||||||
## [1.14.6](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.14.5...@standardnotes/scheduler-server@1.14.6) (2022-12-08)
|
## [1.14.6](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.14.5...@standardnotes/scheduler-server@1.14.6) (2022-12-08)
|
||||||
|
|
||||||
**Note:** Version bump only for package @standardnotes/scheduler-server
|
**Note:** Version bump only for package @standardnotes/scheduler-server
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/scheduler-server",
|
"name": "@standardnotes/scheduler-server",
|
||||||
"version": "1.14.6",
|
"version": "1.15.7",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0 <19.0.0"
|
"node": ">=18.0.0 <19.0.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import {
|
|||||||
DiscountApplyRequestedEvent,
|
DiscountApplyRequestedEvent,
|
||||||
DiscountWithdrawRequestedEvent,
|
DiscountWithdrawRequestedEvent,
|
||||||
DomainEventPublisherInterface,
|
DomainEventPublisherInterface,
|
||||||
EmailMessageRequestedEvent,
|
EmailRequestedEvent,
|
||||||
ExitDiscountWithdrawRequestedEvent,
|
ExitDiscountWithdrawRequestedEvent,
|
||||||
} from '@standardnotes/domain-events'
|
} from '@standardnotes/domain-events'
|
||||||
import { PredicateName } from '@standardnotes/predicates'
|
import { PredicateName } from '@standardnotes/predicates'
|
||||||
@@ -45,9 +45,7 @@ describe('JobDoneInterpreter', () => {
|
|||||||
predicateRepository.findByJobUuid = jest.fn().mockReturnValue([])
|
predicateRepository.findByJobUuid = jest.fn().mockReturnValue([])
|
||||||
|
|
||||||
domainEventFactory = {} as jest.Mocked<DomainEventFactoryInterface>
|
domainEventFactory = {} as jest.Mocked<DomainEventFactoryInterface>
|
||||||
domainEventFactory.createEmailRequestedEvent = jest
|
domainEventFactory.createEmailRequestedEvent = jest.fn().mockReturnValue({} as jest.Mocked<EmailRequestedEvent>)
|
||||||
.fn()
|
|
||||||
.mockReturnValue({} as jest.Mocked<EmailMessageRequestedEvent>)
|
|
||||||
domainEventFactory.createDiscountApplyRequestedEvent = jest
|
domainEventFactory.createDiscountApplyRequestedEvent = jest
|
||||||
.fn()
|
.fn()
|
||||||
.mockReturnValue({} as jest.Mocked<DiscountApplyRequestedEvent>)
|
.mockReturnValue({} as jest.Mocked<DiscountApplyRequestedEvent>)
|
||||||
|
|||||||
@@ -3,6 +3,156 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [1.26.6](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.26.5...@standardnotes/syncing-server@1.26.6) (2022-12-15)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/syncing-server
|
||||||
|
|
||||||
|
## [1.26.5](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.26.4...@standardnotes/syncing-server@1.26.5) (2022-12-15)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **syncing-server:** revisions processing limit ([f10fa83](https://github.com/standardnotes/syncing-server-js/commit/f10fa839fbe1baec32fd234b41d8cd42fc50931a))
|
||||||
|
|
||||||
|
## [1.26.4](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.26.3...@standardnotes/syncing-server@1.26.4) (2022-12-15)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **syncing-server:** user uuid field name ([bfe6f42](https://github.com/standardnotes/syncing-server-js/commit/bfe6f4255a2d3f6e7dfa5eab1509dd770d9bff18))
|
||||||
|
|
||||||
|
## [1.26.3](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.26.2...@standardnotes/syncing-server@1.26.3) (2022-12-15)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **syncing-server:** select fields in query for revisions ([ce53c45](https://github.com/standardnotes/syncing-server-js/commit/ce53c459e6ad0d469fcd0ebd7bf4caeb0e1d9c9c))
|
||||||
|
|
||||||
|
## [1.26.2](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.26.1...@standardnotes/syncing-server@1.26.2) (2022-12-14)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **syncing-server:** revisions procedure logs ([1e2b496](https://github.com/standardnotes/syncing-server-js/commit/1e2b496f4f87fd49ae8fba8ed9b76d3b6a2c31fa))
|
||||||
|
|
||||||
|
## [1.26.1](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.26.0...@standardnotes/syncing-server@1.26.1) (2022-12-14)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **syncing-server:** revisions procedure logs ([22fba8b](https://github.com/standardnotes/syncing-server-js/commit/22fba8ba806115b0f4bb4b083ae8595a3f0010b0))
|
||||||
|
|
||||||
|
# [1.26.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.25.6...@standardnotes/syncing-server@1.26.0) (2022-12-14)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **syncing-server:** change revisions procedure to pagination instead of streaming ([4b1fe3b](https://github.com/standardnotes/syncing-server-js/commit/4b1fe3ba91594858e15cbdfbc21062c428dd03b4))
|
||||||
|
|
||||||
|
## [1.25.6](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.25.5...@standardnotes/syncing-server@1.25.6) (2022-12-14)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **syncing-server:** additional stream events handling on revisions procedure ([2ec28e5](https://github.com/standardnotes/syncing-server-js/commit/2ec28e541efa2bd9172431d45c5c1560692a912c))
|
||||||
|
|
||||||
|
## [1.25.5](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.25.4...@standardnotes/syncing-server@1.25.5) (2022-12-14)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **syncing-server:** revisions procedure with env var defined ranges ([9b27547](https://github.com/standardnotes/syncing-server-js/commit/9b27547dae1e5d5e6d071a069803e2bf3f8acdda))
|
||||||
|
|
||||||
|
## [1.25.4](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.25.3...@standardnotes/syncing-server@1.25.4) (2022-12-13)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **syncing-server:** logs on revisions procedure ([225e0aa](https://github.com/standardnotes/syncing-server-js/commit/225e0aaf88a396bf308c2e5eed0bb6e130cb2d64))
|
||||||
|
|
||||||
|
## [1.25.3](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.25.2...@standardnotes/syncing-server@1.25.3) (2022-12-13)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **syncing-server:** revisions ownership procedure destructured ([124c443](https://github.com/standardnotes/syncing-server-js/commit/124c4435285c2c2e8d0ce8b47907ebd47af27576))
|
||||||
|
|
||||||
|
## [1.25.2](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.25.1...@standardnotes/syncing-server@1.25.2) (2022-12-13)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **syncing-server:** change revisions migration to notes ([c419f1c](https://github.com/standardnotes/syncing-server-js/commit/c419f1ce220c27acabfc813a30b3edd6c4aadaa1))
|
||||||
|
|
||||||
|
## [1.25.1](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.25.0...@standardnotes/syncing-server@1.25.1) (2022-12-13)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **syncing-server:** revisions procedure properties ([cd101b9](https://github.com/standardnotes/syncing-server-js/commit/cd101b96eae8969a4dd2387deb1d4e8679ead216))
|
||||||
|
|
||||||
|
# [1.25.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.24.7...@standardnotes/syncing-server@1.25.0) (2022-12-12)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **syncing-server:** fix streaming items for revisions update ([a55a995](https://github.com/standardnotes/syncing-server-js/commit/a55a9956602bee7dbb0f93f058aceff7a2136ffd))
|
||||||
|
|
||||||
|
## [1.24.7](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.24.6...@standardnotes/syncing-server@1.24.7) (2022-12-12)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **syncing-server:** revisions updating - select fields ([4ff8030](https://github.com/standardnotes/syncing-server-js/commit/4ff8030f8709ee18853c2e782cfc5d99c826f074))
|
||||||
|
|
||||||
|
## [1.24.6](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.24.5...@standardnotes/syncing-server@1.24.6) (2022-12-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/syncing-server
|
||||||
|
|
||||||
|
## [1.24.5](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.24.4...@standardnotes/syncing-server@1.24.5) (2022-12-12)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/syncing-server
|
||||||
|
|
||||||
|
## [1.24.4](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.24.3...@standardnotes/syncing-server@1.24.4) (2022-12-12)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **syncing-server:** data integrity check on revisions fix ([6368342](https://github.com/standardnotes/syncing-server-js/commit/6368342149d658898aef62651bfafddf51c26dbe))
|
||||||
|
|
||||||
|
## [1.24.3](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.24.2...@standardnotes/syncing-server@1.24.3) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/syncing-server
|
||||||
|
|
||||||
|
## [1.24.2](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.24.1...@standardnotes/syncing-server@1.24.2) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/syncing-server
|
||||||
|
|
||||||
|
## [1.24.1](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.24.0...@standardnotes/syncing-server@1.24.1) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/syncing-server
|
||||||
|
|
||||||
|
# [1.24.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.23.0...@standardnotes/syncing-server@1.24.0) (2022-12-09)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **syncing-server:** replace email backup attachment created with email requested ([32601f3](https://github.com/standardnotes/syncing-server-js/commit/32601f34f181b29b7c62cd2926111a0887d97fbf))
|
||||||
|
|
||||||
|
# [1.23.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.22.0...@standardnotes/syncing-server@1.23.0) (2022-12-09)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **syncing-server:** replace one drive backup failed event with email requested ([130f90b](https://github.com/standardnotes/syncing-server-js/commit/130f90bdb6cc88e073b9380e8aed5eebe8c49c1e))
|
||||||
|
|
||||||
|
# [1.22.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.21.0...@standardnotes/syncing-server@1.22.0) (2022-12-09)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **syncing-serfver:** remove dropbox backup failed event in favour of email requested ([118156c](https://github.com/standardnotes/syncing-server-js/commit/118156c62de70eca8fd89414f6e409abd0363e62))
|
||||||
|
|
||||||
|
# [1.21.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.20.17...@standardnotes/syncing-server@1.21.0) (2022-12-09)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **syncing-server:** remove google drive backup failed event in favour of email requested ([00fe321](https://github.com/standardnotes/syncing-server-js/commit/00fe32136e7add627e58e8ea223f7f484f1d3718))
|
||||||
|
|
||||||
|
## [1.20.17](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.20.16...@standardnotes/syncing-server@1.20.17) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/syncing-server
|
||||||
|
|
||||||
|
## [1.20.16](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.20.15...@standardnotes/syncing-server@1.20.16) (2022-12-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/syncing-server
|
||||||
|
|
||||||
|
## [1.20.15](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.20.14...@standardnotes/syncing-server@1.20.15) (2022-12-08)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/syncing-server
|
||||||
|
|
||||||
## [1.20.14](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.20.13...@standardnotes/syncing-server@1.20.14) (2022-12-08)
|
## [1.20.14](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.20.13...@standardnotes/syncing-server@1.20.14) (2022-12-08)
|
||||||
|
|
||||||
**Note:** Version bump only for package @standardnotes/syncing-server
|
**Note:** Version bump only for package @standardnotes/syncing-server
|
||||||
|
|||||||
@@ -10,45 +10,82 @@ import { Env } from '../src/Bootstrap/Env'
|
|||||||
import { DomainEventFactoryInterface } from '../src/Domain/Event/DomainEventFactoryInterface'
|
import { DomainEventFactoryInterface } from '../src/Domain/Event/DomainEventFactoryInterface'
|
||||||
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
|
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
|
||||||
import { ItemRepositoryInterface } from '../src/Domain/Item/ItemRepositoryInterface'
|
import { ItemRepositoryInterface } from '../src/Domain/Item/ItemRepositoryInterface'
|
||||||
import { Stream } from 'stream'
|
import { ContentType } from '@standardnotes/common'
|
||||||
|
|
||||||
const fixRevisionsOwnership = async (
|
const fixRevisionsOwnership = async (
|
||||||
|
year: number,
|
||||||
|
month: number,
|
||||||
|
revisionsProcessingLimit: number,
|
||||||
itemRepository: ItemRepositoryInterface,
|
itemRepository: ItemRepositoryInterface,
|
||||||
domainEventFactory: DomainEventFactoryInterface,
|
domainEventFactory: DomainEventFactoryInterface,
|
||||||
domainEventPublisher: DomainEventPublisherInterface,
|
domainEventPublisher: DomainEventPublisherInterface,
|
||||||
logger: Logger,
|
logger: Logger,
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
const stream = await itemRepository.streamAll({
|
const createdAfter = new Date(`${year}-${month}-1`)
|
||||||
sortBy: 'updated_at_timestamp',
|
const createdBefore = new Date(`${month !== 12 ? year : year + 1}-${month !== 12 ? month + 1 : 1}-1`)
|
||||||
|
|
||||||
|
logger.info(`[${createdAfter.toISOString()} - ${createdBefore.toISOString()}] Processing items`)
|
||||||
|
|
||||||
|
const itemsCount = await itemRepository.countAll({
|
||||||
|
createdBetween: [createdAfter, createdBefore],
|
||||||
|
selectFields: ['uuid', 'user_uuid'],
|
||||||
|
contentType: [ContentType.Note, ContentType.File],
|
||||||
sortOrder: 'ASC',
|
sortOrder: 'ASC',
|
||||||
createdBefore: new Date('2022-11-23'),
|
sortBy: 'uuid',
|
||||||
selectFields: ['user_uuid', 'item_uuid'],
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
logger.info(
|
||||||
stream
|
`[${createdAfter.toISOString()} - ${createdBefore.toISOString()}] There are ${itemsCount} items to process.`,
|
||||||
.pipe(
|
)
|
||||||
new Stream.Transform({
|
|
||||||
objectMode: true,
|
|
||||||
transform: async (rawItemData, _encoding, callback) => {
|
|
||||||
try {
|
|
||||||
await domainEventPublisher.publish(
|
|
||||||
domainEventFactory.createRevisionsOwnershipUpdateRequestedEvent({
|
|
||||||
userUuid: rawItemData.item_user_uuid,
|
|
||||||
itemUuid: rawItemData.item_uuid,
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
} catch (error) {
|
|
||||||
logger.error(`Could not process item ${rawItemData.item_uuid}: ${(error as Error).message}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
callback()
|
const amountOfPages = Math.ceil(itemsCount / revisionsProcessingLimit)
|
||||||
},
|
const tenPercentOfPages = Math.ceil(amountOfPages / 10)
|
||||||
|
let itemsProcessedCounter = 0
|
||||||
|
let itemsSkippedCounter = 0
|
||||||
|
for (let page = 1; page <= amountOfPages; page++) {
|
||||||
|
if (page % tenPercentOfPages === 0) {
|
||||||
|
logger.info(
|
||||||
|
`[${createdAfter.toISOString()} - ${createdBefore.toISOString()}] Processing page ${page}/${amountOfPages} of items.`,
|
||||||
|
)
|
||||||
|
logger.info(
|
||||||
|
`[${createdAfter.toISOString()} - ${createdBefore.toISOString()}] Processed successfully/skipped items: ${itemsProcessedCounter}/${itemsSkippedCounter}.`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const items = await itemRepository.findAll({
|
||||||
|
createdBetween: [createdAfter, createdBefore],
|
||||||
|
selectFields: ['uuid', 'user_uuid'],
|
||||||
|
contentType: [ContentType.Note, ContentType.File],
|
||||||
|
offset: (page - 1) * revisionsProcessingLimit,
|
||||||
|
limit: revisionsProcessingLimit,
|
||||||
|
sortOrder: 'ASC',
|
||||||
|
sortBy: 'uuid',
|
||||||
|
})
|
||||||
|
|
||||||
|
if (items.length === 0) {
|
||||||
|
logger.warn(
|
||||||
|
`[${createdAfter.toISOString()} - ${createdBefore.toISOString()}] No items fetched for offset ${
|
||||||
|
(page - 1) * revisionsProcessingLimit
|
||||||
|
} and limit ${revisionsProcessingLimit}.`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const item of items) {
|
||||||
|
if (!item.userUuid || !item.uuid) {
|
||||||
|
itemsSkippedCounter++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
await domainEventPublisher.publish(
|
||||||
|
domainEventFactory.createRevisionsOwnershipUpdateRequestedEvent({
|
||||||
|
userUuid: item.userUuid,
|
||||||
|
itemUuid: item.uuid,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.on('finish', resolve)
|
|
||||||
.on('error', reject)
|
itemsProcessedCounter++
|
||||||
})
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const container = new ContainerConfigLoader()
|
const container = new ContainerConfigLoader()
|
||||||
@@ -64,7 +101,28 @@ void container.load().then((container) => {
|
|||||||
const domainEventFactory: DomainEventFactoryInterface = container.get(TYPES.DomainEventFactory)
|
const domainEventFactory: DomainEventFactoryInterface = container.get(TYPES.DomainEventFactory)
|
||||||
const domainEventPublisher: DomainEventPublisherInterface = container.get(TYPES.DomainEventPublisher)
|
const domainEventPublisher: DomainEventPublisherInterface = container.get(TYPES.DomainEventPublisher)
|
||||||
|
|
||||||
Promise.resolve(fixRevisionsOwnership(itemRepository, domainEventFactory, domainEventPublisher, logger))
|
const years = env.get('REVISION_YEARS').split(',')
|
||||||
|
const months = env.get('REVISION_MONTHS').split(',')
|
||||||
|
const revisionsProcessingLimit = env.get('REVISIONS_PROCESSING_LIMIT')
|
||||||
|
|
||||||
|
const promises = []
|
||||||
|
for (const year of years) {
|
||||||
|
for (const month of months) {
|
||||||
|
promises.push(
|
||||||
|
fixRevisionsOwnership(
|
||||||
|
+year,
|
||||||
|
+month,
|
||||||
|
+revisionsProcessingLimit,
|
||||||
|
itemRepository,
|
||||||
|
domainEventFactory,
|
||||||
|
domainEventPublisher,
|
||||||
|
logger,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Promise.all(promises)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
logger.info('revisions ownership fix complete.')
|
logger.info('revisions ownership fix complete.')
|
||||||
|
|
||||||
|
|||||||
@@ -5,33 +5,33 @@ COMMAND=$1 && shift 1
|
|||||||
|
|
||||||
case "$COMMAND" in
|
case "$COMMAND" in
|
||||||
'start-local')
|
'start-local')
|
||||||
echo "Starting Web in Local Mode..."
|
echo "[Docker] Starting Web in Local Mode..."
|
||||||
yarn workspace @standardnotes/syncing-server start:local
|
yarn workspace @standardnotes/syncing-server start:local
|
||||||
;;
|
;;
|
||||||
|
|
||||||
'start-web' )
|
'start-web' )
|
||||||
echo "Starting Web..."
|
echo "[Docker] Starting Web..."
|
||||||
yarn workspace @standardnotes/syncing-server start
|
yarn workspace @standardnotes/syncing-server start
|
||||||
;;
|
;;
|
||||||
|
|
||||||
'start-worker' )
|
'start-worker' )
|
||||||
echo "Starting Worker..."
|
echo "[Docker] Starting Worker..."
|
||||||
yarn workspace @standardnotes/syncing-server worker
|
yarn workspace @standardnotes/syncing-server worker
|
||||||
;;
|
;;
|
||||||
|
|
||||||
'content-size-recalculate' )
|
'content-size-recalculate' )
|
||||||
echo "Starting Content Size Recalculation..."
|
echo "[Docker] Starting Content Size Recalculation..."
|
||||||
USER_UUID=$1 && shift 1
|
USER_UUID=$1 && shift 1
|
||||||
yarn workspace @standardnotes/syncing-server content-size $USER_UUID
|
yarn workspace @standardnotes/syncing-server content-size $USER_UUID
|
||||||
;;
|
;;
|
||||||
|
|
||||||
'revisions-ownership-fix' )
|
'revisions-ownership-fix' )
|
||||||
echo "Starting Revisions Ownership Fixing..."
|
echo "[Docker] Starting Revisions Ownership Fixing..."
|
||||||
yarn workspace @standardnotes/syncing-server revisions-ownership
|
yarn workspace @standardnotes/syncing-server revisions-ownership
|
||||||
;;
|
;;
|
||||||
|
|
||||||
* )
|
* )
|
||||||
echo "Unknown command"
|
echo "[Docker] Unknown command"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,6 @@ module.exports = {
|
|||||||
transform: {
|
transform: {
|
||||||
...tsjPreset.transform,
|
...tsjPreset.transform,
|
||||||
},
|
},
|
||||||
coveragePathIgnorePatterns: ['/Bootstrap/', 'HealthCheckController', '/Infra/'],
|
coveragePathIgnorePatterns: ['/Bootstrap/', 'HealthCheckController', '/Infra/', '/Domain/Email/'],
|
||||||
setupFilesAfterEnv: ['./test-setup.ts'],
|
setupFilesAfterEnv: ['./test-setup.ts'],
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/syncing-server",
|
"name": "@standardnotes/syncing-server",
|
||||||
"version": "1.20.14",
|
"version": "1.26.6",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0 <19.0.0"
|
"node": ">=18.0.0 <19.0.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { html } from './dropbox-backup-failed.html'
|
||||||
|
|
||||||
|
export function getSubject(): string {
|
||||||
|
return 'Failed Daily Backup to Dropbox'
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getBody(): string {
|
||||||
|
return html
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import { html } from './email-backup-attachment-created.html'
|
||||||
|
|
||||||
|
export function getSubject(fileIndex: number, numberOfFiles: number, date: string): string {
|
||||||
|
let subject = `Data Backup for ${date}`
|
||||||
|
if (numberOfFiles > 1) {
|
||||||
|
subject = `Data Backup for ${date} - Part ${fileIndex} Of ${numberOfFiles}`
|
||||||
|
}
|
||||||
|
|
||||||
|
return subject
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getBody(email: string): string {
|
||||||
|
return html(email)
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user