mirror of
https://github.com/standardnotes/server
synced 2026-01-25 02:01:08 -05:00
Compare commits
20 Commits
@standardn
...
@standardn
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7980af3d82 | ||
|
|
2980c42e88 | ||
|
|
b03994f9db | ||
|
|
41906ec2f9 | ||
|
|
4d1e7ff2a5 | ||
|
|
7f18fcfc13 | ||
|
|
ff02ce0747 | ||
|
|
a6056600eb | ||
|
|
24c94326d5 | ||
|
|
48c0cb5e62 | ||
|
|
9968efe1b2 | ||
|
|
6368342149 | ||
|
|
b5f73db210 | ||
|
|
22d6a02d04 | ||
|
|
4e0bcfcccf | ||
|
|
104313c15d | ||
|
|
814289af46 | ||
|
|
3096cd98d5 | ||
|
|
45dfefbc7a | ||
|
|
20d92149a8 |
@@ -3,6 +3,40 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [2.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
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'newrelic'
|
||||
|
||||
import { Logger } from 'winston'
|
||||
|
||||
import { EmailLevel } from '@standardnotes/domain-core'
|
||||
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
|
||||
import { AnalyticsActivity } from '../src/Domain/Analytics/AnalyticsActivity'
|
||||
import { Period } from '../src/Domain/Time/Period'
|
||||
@@ -16,6 +17,8 @@ import TYPES from '../src/Bootstrap/Types'
|
||||
import { Env } from '../src/Bootstrap/Env'
|
||||
import { DomainEventFactoryInterface } from '../src/Domain/Event/DomainEventFactoryInterface'
|
||||
import { CalculateMonthlyRecurringRevenue } from '../src/Domain/UseCase/CalculateMonthlyRecurringRevenue/CalculateMonthlyRecurringRevenue'
|
||||
import { getBody, getSubject } from '../src/Domain/Email/DailyAnalyticsReport'
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
|
||||
const requestReport = async (
|
||||
analyticsStore: AnalyticsStoreInterface,
|
||||
@@ -24,6 +27,8 @@ const requestReport = async (
|
||||
domainEventPublisher: DomainEventPublisherInterface,
|
||||
periodKeyGenerator: PeriodKeyGeneratorInterface,
|
||||
calculateMonthlyRecurringRevenue: CalculateMonthlyRecurringRevenue,
|
||||
timer: TimerInterface,
|
||||
adminEmails: string[],
|
||||
): Promise<void> => {
|
||||
await calculateMonthlyRecurringRevenue.execute({})
|
||||
|
||||
@@ -213,18 +218,29 @@ const requestReport = async (
|
||||
})
|
||||
}
|
||||
|
||||
const event = domainEventFactory.createDailyAnalyticsReportGeneratedEvent({
|
||||
activityStatistics: yesterdayActivityStatistics,
|
||||
activityStatisticsOverTime: analyticsOverTime,
|
||||
statisticsOverTime,
|
||||
statisticMeasures,
|
||||
churn: {
|
||||
periodKeys: monthlyPeriodKeys,
|
||||
values: churnRates,
|
||||
},
|
||||
})
|
||||
|
||||
await domainEventPublisher.publish(event)
|
||||
for (const adminEmail of adminEmails) {
|
||||
await domainEventPublisher.publish(
|
||||
domainEventFactory.createEmailRequestedEvent({
|
||||
messageIdentifier: 'VERSION_ADOPTION_REPORT',
|
||||
subject: getSubject(),
|
||||
body: getBody(
|
||||
{
|
||||
activityStatistics: yesterdayActivityStatistics,
|
||||
activityStatisticsOverTime: analyticsOverTime,
|
||||
statisticsOverTime,
|
||||
statisticMeasures,
|
||||
churn: {
|
||||
periodKeys: monthlyPeriodKeys,
|
||||
values: churnRates,
|
||||
},
|
||||
},
|
||||
timer,
|
||||
),
|
||||
level: EmailLevel.LEVELS.System,
|
||||
userEmail: adminEmail,
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const container = new ContainerConfigLoader()
|
||||
@@ -241,9 +257,13 @@ void container.load().then((container) => {
|
||||
const domainEventFactory: DomainEventFactoryInterface = container.get(TYPES.DomainEventFactory)
|
||||
const domainEventPublisher: DomainEventPublisherInterface = container.get(TYPES.DomainEventPublisher)
|
||||
const periodKeyGenerator: PeriodKeyGeneratorInterface = container.get(TYPES.PeriodKeyGenerator)
|
||||
const timer: TimerInterface = container.get(TYPES.Timer)
|
||||
const calculateMonthlyRecurringRevenue: CalculateMonthlyRecurringRevenue = container.get(
|
||||
TYPES.CalculateMonthlyRecurringRevenue,
|
||||
)
|
||||
const adminEmails = container.get(TYPES.ADMIN_EMAILS) as string[]
|
||||
|
||||
logger.info(`Sending report to following admins: ${adminEmails}`)
|
||||
|
||||
Promise.resolve(
|
||||
requestReport(
|
||||
@@ -253,6 +273,8 @@ void container.load().then((container) => {
|
||||
domainEventPublisher,
|
||||
periodKeyGenerator,
|
||||
calculateMonthlyRecurringRevenue,
|
||||
timer,
|
||||
adminEmails,
|
||||
),
|
||||
)
|
||||
.then(() => {
|
||||
|
||||
@@ -5,17 +5,17 @@ COMMAND=$1 && shift 1
|
||||
|
||||
case "$COMMAND" in
|
||||
'start-worker' )
|
||||
echo "Starting Worker..."
|
||||
echo "[Docker] Starting Worker..."
|
||||
yarn workspace @standardnotes/analytics worker
|
||||
;;
|
||||
|
||||
'report' )
|
||||
echo "Starting Usage Report Generation..."
|
||||
echo "[Docker] Starting Usage Report Generation..."
|
||||
yarn workspace @standardnotes/analytics report
|
||||
;;
|
||||
|
||||
* )
|
||||
echo "Unknown command"
|
||||
echo "[Docker] Unknown command"
|
||||
;;
|
||||
esac
|
||||
|
||||
|
||||
@@ -7,5 +7,5 @@ module.exports = {
|
||||
transform: {
|
||||
...tsjPreset.transform,
|
||||
},
|
||||
coveragePathIgnorePatterns: ['/Infra/'],
|
||||
coveragePathIgnorePatterns: ['/Infra/', '/Domain/Email/'],
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/analytics",
|
||||
"version": "2.12.18",
|
||||
"version": "2.12.25",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <19.0.0"
|
||||
},
|
||||
@@ -40,7 +40,7 @@
|
||||
"@newrelic/winston-enricher": "^4.0.0",
|
||||
"@sentry/node": "^7.19.0",
|
||||
"@standardnotes/common": "workspace:*",
|
||||
"@standardnotes/domain-core": "workspace:*",
|
||||
"@standardnotes/domain-core": "workspace:^",
|
||||
"@standardnotes/domain-events": "workspace:*",
|
||||
"@standardnotes/domain-events-infra": "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.REDIS_EVENTS_CHANNEL).toConstantValue(env.get('REDIS_EVENTS_CHANNEL'))
|
||||
container.bind(TYPES.NEW_RELIC_ENABLED).toConstantValue(env.get('NEW_RELIC_ENABLED', true))
|
||||
container.bind(TYPES.ADMIN_EMAILS).toConstantValue(env.get('ADMIN_EMAILS').split(','))
|
||||
|
||||
// Repositories
|
||||
container
|
||||
|
||||
@@ -11,6 +11,7 @@ const TYPES = {
|
||||
SQS_AWS_REGION: Symbol.for('SQS_AWS_REGION'),
|
||||
REDIS_EVENTS_CHANNEL: Symbol.for('REDIS_EVENTS_CHANNEL'),
|
||||
NEW_RELIC_ENABLED: Symbol.for('NEW_RELIC_ENABLED'),
|
||||
ADMIN_EMAILS: Symbol.for('ADMIN_EMAILS'),
|
||||
// Repositories
|
||||
AnalyticsEntityRepository: Symbol.for('AnalyticsEntityRepository'),
|
||||
RevenueModificationRepository: Symbol.for('RevenueModificationRepository'),
|
||||
|
||||
11
packages/analytics/src/Domain/Email/DailyAnalyticsReport.ts
Normal file
11
packages/analytics/src/Domain/Email/DailyAnalyticsReport.ts
Normal file
@@ -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 */
|
||||
|
||||
import { DomainEventService, DailyAnalyticsReportGeneratedEvent } from '@standardnotes/domain-events'
|
||||
import { DomainEventService, EmailRequestedEvent } from '@standardnotes/domain-events'
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
import { inject, injectable } from 'inversify'
|
||||
import TYPES from '../../Bootstrap/Types'
|
||||
@@ -9,55 +9,20 @@ import { DomainEventFactoryInterface } from './DomainEventFactoryInterface'
|
||||
@injectable()
|
||||
export class DomainEventFactory implements DomainEventFactoryInterface {
|
||||
constructor(@inject(TYPES.Timer) private timer: TimerInterface) {}
|
||||
|
||||
createDailyAnalyticsReportGeneratedEvent(dto: {
|
||||
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
|
||||
periodKey: string
|
||||
averageCustomersCount: number
|
||||
existingCustomersChurn: number
|
||||
newCustomersChurn: number
|
||||
}>
|
||||
}
|
||||
}): DailyAnalyticsReportGeneratedEvent {
|
||||
createEmailRequestedEvent(dto: {
|
||||
userEmail: string
|
||||
messageIdentifier: string
|
||||
level: string
|
||||
body: string
|
||||
subject: string
|
||||
}): EmailRequestedEvent {
|
||||
return {
|
||||
type: 'DAILY_ANALYTICS_REPORT_GENERATED',
|
||||
type: 'EMAIL_REQUESTED',
|
||||
createdAt: this.timer.getUTCDate(),
|
||||
meta: {
|
||||
correlation: {
|
||||
userIdentifier: '',
|
||||
userIdentifierType: 'uuid',
|
||||
userIdentifier: dto.userEmail,
|
||||
userIdentifierType: 'email',
|
||||
},
|
||||
origin: DomainEventService.Analytics,
|
||||
},
|
||||
|
||||
@@ -1,45 +1,11 @@
|
||||
import { DailyAnalyticsReportGeneratedEvent } from '@standardnotes/domain-events'
|
||||
import { EmailRequestedEvent } from '@standardnotes/domain-events'
|
||||
|
||||
export interface DomainEventFactoryInterface {
|
||||
createDailyAnalyticsReportGeneratedEvent(dto: {
|
||||
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
|
||||
periodKey: string
|
||||
averageCustomersCount: number
|
||||
existingCustomersChurn: number
|
||||
newCustomersChurn: number
|
||||
}>
|
||||
}
|
||||
}): DailyAnalyticsReportGeneratedEvent
|
||||
createEmailRequestedEvent(dto: {
|
||||
userEmail: string
|
||||
messageIdentifier: string
|
||||
level: string
|
||||
body: string
|
||||
subject: string
|
||||
}): EmailRequestedEvent
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ WEB_SOCKET_SERVER_URL=http://websockets:3000
|
||||
PAYMENTS_SERVER_URL=http://payments:3000
|
||||
FILES_SERVER_URL=http://files:3000
|
||||
REVISIONS_SERVER_URL=http://revisions:3000
|
||||
EMAIL_SERVER_URL=http://email:3000
|
||||
|
||||
HTTP_CALL_TIMEOUT=60000
|
||||
|
||||
|
||||
@@ -3,6 +3,28 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
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,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/api-gateway",
|
||||
"version": "1.39.22",
|
||||
"version": "1.40.2",
|
||||
"engines": {
|
||||
"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.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.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.FILES_SERVER_URL).toConstantValue(env.get('FILES_SERVER_URL', true))
|
||||
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'),
|
||||
FILES_SERVER_URL: Symbol.for('FILES_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'),
|
||||
WEB_SOCKET_SERVER_URL: Symbol.for('WEB_SOCKET_SERVER_URL'),
|
||||
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> {
|
||||
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.WEB_SOCKET_SERVER_URL) private webSocketServerUrl: 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.CrossServiceTokenCache) private crossServiceTokenCache: CrossServiceTokenCacheInterface,
|
||||
@inject(TYPES.Logger) private logger: Logger,
|
||||
@@ -65,6 +66,21 @@ export class HttpService implements HttpServiceInterface {
|
||||
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(
|
||||
request: Request,
|
||||
response: Response,
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
import { Request, Response } from 'express'
|
||||
|
||||
export interface HttpServiceInterface {
|
||||
callEmailServer(
|
||||
request: Request,
|
||||
response: Response,
|
||||
endpoint: string,
|
||||
payload?: Record<string, unknown> | string,
|
||||
): Promise<void>
|
||||
callAuthServer(
|
||||
request: Request,
|
||||
response: Response,
|
||||
|
||||
@@ -3,6 +3,28 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [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,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/auth-server",
|
||||
"version": "1.66.4",
|
||||
"version": "1.66.9",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <19.0.0"
|
||||
},
|
||||
|
||||
@@ -47,9 +47,7 @@ describe('CreateOfflineSubscriptionToken', () => {
|
||||
domainEventPublisher.publish = jest.fn()
|
||||
|
||||
domainEventFactory = {} as jest.Mocked<DomainEventFactoryInterface>
|
||||
domainEventFactory.createEmailRequestedEvent = jest
|
||||
.fn()
|
||||
.mockReturnValue({} as jest.Mocked<EmailRequestedEvent>)
|
||||
domainEventFactory.createEmailRequestedEvent = jest.fn().mockReturnValue({} as jest.Mocked<EmailRequestedEvent>)
|
||||
|
||||
timer = {} as jest.Mocked<TimerInterface>
|
||||
timer.convertStringDateToMicroseconds = jest.fn().mockReturnValue(1)
|
||||
|
||||
@@ -3,6 +3,22 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
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,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/domain-events-infra",
|
||||
"version": "1.9.52",
|
||||
"version": "1.9.56",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <19.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,28 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/domain-events",
|
||||
"version": "2.103.0",
|
||||
"version": "2.104.1",
|
||||
"engines": {
|
||||
"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',
|
||||
Workspace = 'workspace',
|
||||
Analytics = 'analytics',
|
||||
Revisions = 'revisions',
|
||||
Email = 'email',
|
||||
Settings = 'settings',
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ export interface EmailRequestedEventPayload {
|
||||
subject: string
|
||||
body: string
|
||||
sender?: string
|
||||
additionalStyles?: string
|
||||
attachments?: Array<{
|
||||
filePath: 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
|
||||
}
|
||||
@@ -2,8 +2,6 @@ export * from './Event/AccountDeletionRequestedEvent'
|
||||
export * from './Event/AccountDeletionRequestedEventPayload'
|
||||
export * from './Event/CloudBackupRequestedEvent'
|
||||
export * from './Event/CloudBackupRequestedEventPayload'
|
||||
export * from './Event/DailyAnalyticsReportGeneratedEvent'
|
||||
export * from './Event/DailyAnalyticsReportGeneratedEventPayload'
|
||||
export * from './Event/DiscountApplyRequestedEvent'
|
||||
export * from './Event/DiscountApplyRequestedEventPayload'
|
||||
export * from './Event/DiscountWithdrawRequestedEvent'
|
||||
@@ -18,6 +16,8 @@ export * from './Event/EmailBackupRequestedEvent'
|
||||
export * from './Event/EmailBackupRequestedEventPayload'
|
||||
export * from './Event/EmailRequestedEvent'
|
||||
export * from './Event/EmailRequestedEventPayload'
|
||||
export * from './Event/EmailSubscriptionUnsubscribedEvent'
|
||||
export * from './Event/EmailSubscriptionUnsubscribedEventPayload'
|
||||
export * from './Event/ExitDiscountAppliedEvent'
|
||||
export * from './Event/ExitDiscountAppliedEventPayload'
|
||||
export * from './Event/ExitDiscountApplyRequestedEvent'
|
||||
|
||||
@@ -3,6 +3,22 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
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 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/event-store",
|
||||
"version": "1.6.49",
|
||||
"version": "1.6.53",
|
||||
"description": "Event Store Service",
|
||||
"private": true,
|
||||
"main": "dist/src/index.js",
|
||||
|
||||
@@ -3,6 +3,22 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
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,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/files-server",
|
||||
"version": "1.8.48",
|
||||
"version": "1.8.52",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <19.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,22 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [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,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/revisions-server",
|
||||
"version": "1.9.21",
|
||||
"version": "1.9.25",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <19.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,22 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [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,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/scheduler-server",
|
||||
"version": "1.15.2",
|
||||
"version": "1.15.6",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <19.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,28 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [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
|
||||
|
||||
@@ -32,6 +32,12 @@ const fixRevisionsOwnership = async (
|
||||
objectMode: true,
|
||||
transform: async (rawItemData, _encoding, callback) => {
|
||||
try {
|
||||
if (!rawItemData.item_user_uuid || !rawItemData.item_uuid) {
|
||||
logger.error('Could not process item %O', rawItemData)
|
||||
|
||||
return callback()
|
||||
}
|
||||
|
||||
await domainEventPublisher.publish(
|
||||
domainEventFactory.createRevisionsOwnershipUpdateRequestedEvent({
|
||||
userUuid: rawItemData.item_user_uuid,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/syncing-server",
|
||||
"version": "1.24.1",
|
||||
"version": "1.24.6",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <19.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,22 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.4.53](https://github.com/standardnotes/server/compare/@standardnotes/websockets-server@1.4.52...@standardnotes/websockets-server@1.4.53) (2022-12-12)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/websockets-server
|
||||
|
||||
## [1.4.52](https://github.com/standardnotes/server/compare/@standardnotes/websockets-server@1.4.51...@standardnotes/websockets-server@1.4.52) (2022-12-12)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/websockets-server
|
||||
|
||||
## [1.4.51](https://github.com/standardnotes/server/compare/@standardnotes/websockets-server@1.4.50...@standardnotes/websockets-server@1.4.51) (2022-12-09)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/websockets-server
|
||||
|
||||
## [1.4.50](https://github.com/standardnotes/server/compare/@standardnotes/websockets-server@1.4.49...@standardnotes/websockets-server@1.4.50) (2022-12-09)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/websockets-server
|
||||
|
||||
## [1.4.49](https://github.com/standardnotes/server/compare/@standardnotes/websockets-server@1.4.48...@standardnotes/websockets-server@1.4.49) (2022-12-09)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/websockets-server
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/websockets-server",
|
||||
"version": "1.4.49",
|
||||
"version": "1.4.53",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <19.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,22 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.18.4](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.18.3...@standardnotes/workspace-server@1.18.4) (2022-12-12)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/workspace-server
|
||||
|
||||
## [1.18.3](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.18.2...@standardnotes/workspace-server@1.18.3) (2022-12-12)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/workspace-server
|
||||
|
||||
## [1.18.2](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.18.1...@standardnotes/workspace-server@1.18.2) (2022-12-09)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/workspace-server
|
||||
|
||||
## [1.18.1](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.18.0...@standardnotes/workspace-server@1.18.1) (2022-12-09)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/workspace-server
|
||||
|
||||
# [1.18.0](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.17.47...@standardnotes/workspace-server@1.18.0) (2022-12-09)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/workspace-server",
|
||||
"version": "1.18.0",
|
||||
"version": "1.18.4",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <19.0.0"
|
||||
},
|
||||
|
||||
@@ -1795,7 +1795,7 @@ __metadata:
|
||||
"@newrelic/winston-enricher": "npm:^4.0.0"
|
||||
"@sentry/node": "npm:^7.19.0"
|
||||
"@standardnotes/common": "workspace:*"
|
||||
"@standardnotes/domain-core": "workspace:*"
|
||||
"@standardnotes/domain-core": "workspace:^"
|
||||
"@standardnotes/domain-events": "workspace:*"
|
||||
"@standardnotes/domain-events-infra": "workspace:*"
|
||||
"@standardnotes/time": "workspace:*"
|
||||
@@ -1977,7 +1977,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@standardnotes/domain-core@workspace:*, @standardnotes/domain-core@workspace:^, @standardnotes/domain-core@workspace:packages/domain-core":
|
||||
"@standardnotes/domain-core@workspace:^, @standardnotes/domain-core@workspace:packages/domain-core":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@standardnotes/domain-core@workspace:packages/domain-core"
|
||||
dependencies:
|
||||
|
||||
Reference in New Issue
Block a user