mirror of
https://github.com/standardnotes/server
synced 2026-05-12 06:57:20 -04:00
Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7b62c7a967 | |||
| 5c3db2cb29 | |||
| 7008cbd363 | |||
| cdb7fcf831 | |||
| 628aafdd42 | |||
| 9d3ef24ba9 | |||
| 4189f11fd7 | |||
| 5ea9941519 | |||
| 7a64494d07 | |||
| 4928685198 | |||
| 0103233d4a | |||
| ee7075fe60 | |||
| 49feadd32a |
@@ -2551,6 +2551,7 @@ const RAW_RUNTIME_STATE =
|
|||||||
["@types/jest", "npm:29.1.1"],\
|
["@types/jest", "npm:29.1.1"],\
|
||||||
["@types/newrelic", "npm:7.0.3"],\
|
["@types/newrelic", "npm:7.0.3"],\
|
||||||
["@types/node", "npm:18.0.3"],\
|
["@types/node", "npm:18.0.3"],\
|
||||||
|
["@types/uuid", "npm:8.3.4"],\
|
||||||
["@typescript-eslint/eslint-plugin", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:5.30.5"],\
|
["@typescript-eslint/eslint-plugin", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:5.30.5"],\
|
||||||
["aws-sdk", "npm:2.1234.0"],\
|
["aws-sdk", "npm:2.1234.0"],\
|
||||||
["dayjs", "npm:1.11.6"],\
|
["dayjs", "npm:1.11.6"],\
|
||||||
@@ -2563,9 +2564,11 @@ const RAW_RUNTIME_STATE =
|
|||||||
["mysql2", "npm:2.3.3"],\
|
["mysql2", "npm:2.3.3"],\
|
||||||
["newrelic", "npm:9.0.0"],\
|
["newrelic", "npm:9.0.0"],\
|
||||||
["reflect-metadata", "npm:0.1.13"],\
|
["reflect-metadata", "npm:0.1.13"],\
|
||||||
|
["shallow-equal-object", "npm:1.1.1"],\
|
||||||
["ts-jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.0.3"],\
|
["ts-jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.0.3"],\
|
||||||
["typeorm", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:0.3.10"],\
|
["typeorm", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:0.3.10"],\
|
||||||
["typescript", "patch:typescript@npm%3A4.8.4#optional!builtin<compat/typescript>::version=4.8.4&hash=701156"],\
|
["typescript", "patch:typescript@npm%3A4.8.4#optional!builtin<compat/typescript>::version=4.8.4&hash=701156"],\
|
||||||
|
["uuid", "npm:9.0.0"],\
|
||||||
["winston", "npm:3.8.2"]\
|
["winston", "npm:3.8.2"]\
|
||||||
],\
|
],\
|
||||||
"linkType": "SOFT"\
|
"linkType": "SOFT"\
|
||||||
@@ -12213,6 +12216,15 @@ const RAW_RUNTIME_STATE =
|
|||||||
"linkType": "HARD"\
|
"linkType": "HARD"\
|
||||||
}]\
|
}]\
|
||||||
]],\
|
]],\
|
||||||
|
["shallow-equal-object", [\
|
||||||
|
["npm:1.1.1", {\
|
||||||
|
"packageLocation": "./.yarn/cache/shallow-equal-object-npm-1.1.1-a41b289b2e-9e5e0cd10b.zip/node_modules/shallow-equal-object/",\
|
||||||
|
"packageDependencies": [\
|
||||||
|
["shallow-equal-object", "npm:1.1.1"]\
|
||||||
|
],\
|
||||||
|
"linkType": "HARD"\
|
||||||
|
}]\
|
||||||
|
]],\
|
||||||
["shebang-command", [\
|
["shebang-command", [\
|
||||||
["npm:2.0.0", {\
|
["npm:2.0.0", {\
|
||||||
"packageLocation": "./.yarn/cache/shebang-command-npm-2.0.0-eb2b01921d-5907a8d5fa.zip/node_modules/shebang-command/",\
|
"packageLocation": "./.yarn/cache/shebang-command-npm-2.0.0-eb2b01921d-5907a8d5fa.zip/node_modules/shebang-command/",\
|
||||||
|
|||||||
Binary file not shown.
@@ -3,6 +3,37 @@
|
|||||||
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.5.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.4.0...@standardnotes/analytics@2.5.0) (2022-11-09)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **analytics:** add saving revenue modifications upon subscription expired ([5c3db2c](https://github.com/standardnotes/server/commit/5c3db2cb29a929e44b63eb8226ce4ad1d14f8a99))
|
||||||
|
|
||||||
|
# [2.4.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.3.1...@standardnotes/analytics@2.4.0) (2022-11-09)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **analytics:** add saving revenue modifications upon subscription renewed ([cdb7fcf](https://github.com/standardnotes/server/commit/cdb7fcf8311fecfabe3ef9eb656cd6ec57b87de0))
|
||||||
|
|
||||||
|
## [2.3.1](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.3.0...@standardnotes/analytics@2.3.1) (2022-11-09)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **analytics:** missing injectable annotation ([9d3ef24](https://github.com/standardnotes/server/commit/9d3ef24ba94ad28976a211d40f94f1bce8d0d305))
|
||||||
|
|
||||||
|
# [2.3.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.2.0...@standardnotes/analytics@2.3.0) (2022-11-09)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **analytics:** add saving revenue modifications upon subscription purchased ([5ea9941](https://github.com/standardnotes/server/commit/5ea9941519ffb3027527130ec869da14abc5e994))
|
||||||
|
|
||||||
|
# [2.2.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.1.0...@standardnotes/analytics@2.2.0) (2022-11-08)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **analytics:** add persistence for revenue modifications ([4928685](https://github.com/standardnotes/server/commit/49286851989f557d3b391b6b535a9aa307fbef50))
|
||||||
|
* **analytics:** create new ddd architecture for persisting revenue modifications ([0103233](https://github.com/standardnotes/server/commit/0103233d4a1e222e7c9b059475c1cdc3b2617455))
|
||||||
|
|
||||||
# [2.1.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@1.52.0...@standardnotes/analytics@2.1.0) (2022-11-07)
|
# [2.1.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@1.52.0...@standardnotes/analytics@2.1.0) (2022-11-07)
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|||||||
@@ -7,4 +7,5 @@ module.exports = {
|
|||||||
transform: {
|
transform: {
|
||||||
...tsjPreset.transform,
|
...tsjPreset.transform,
|
||||||
},
|
},
|
||||||
|
coveragePathIgnorePatterns: ['/Infra/'],
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { MigrationInterface, QueryRunner } from 'typeorm'
|
||||||
|
|
||||||
|
export class addRevenueModifications1667912580964 implements MigrationInterface {
|
||||||
|
name = 'addRevenueModifications1667912580964'
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(
|
||||||
|
'CREATE TABLE `revenue_modifications` (`uuid` varchar(36) NOT NULL, `subscription_id` int NOT NULL, `user_email` varchar(255) NOT NULL, `user_uuid` varchar(36) NOT NULL, `event_type` varchar(255) NOT NULL, `subscription_plan` varchar(255) NOT NULL, `billing_frequency` int NOT NULL, `new_customer` tinyint NOT NULL, `previous_mrr` int NOT NULL, `new_mrr` int NOT NULL, INDEX `email` (`user_email`), INDEX `user_uuid` (`user_uuid`), PRIMARY KEY (`uuid`)) ENGINE=InnoDB',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query('DROP INDEX `user_uuid` ON `revenue_modifications`')
|
||||||
|
await queryRunner.query('DROP INDEX `email` ON `revenue_modifications`')
|
||||||
|
await queryRunner.query('DROP TABLE `revenue_modifications`')
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/analytics",
|
"name": "@standardnotes/analytics",
|
||||||
"version": "2.1.0",
|
"version": "2.5.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=14.0.0 <17.0.0"
|
"node": ">=14.0.0 <17.0.0"
|
||||||
},
|
},
|
||||||
@@ -29,6 +29,7 @@
|
|||||||
"@types/jest": "^29.1.1",
|
"@types/jest": "^29.1.1",
|
||||||
"@types/newrelic": "^7.0.3",
|
"@types/newrelic": "^7.0.3",
|
||||||
"@types/node": "^18.0.0",
|
"@types/node": "^18.0.0",
|
||||||
|
"@types/uuid": "^8.3.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.30.0",
|
"@typescript-eslint/eslint-plugin": "^5.30.0",
|
||||||
"eslint": "^8.14.0",
|
"eslint": "^8.14.0",
|
||||||
"eslint-plugin-prettier": "^4.2.1",
|
"eslint-plugin-prettier": "^4.2.1",
|
||||||
@@ -51,7 +52,9 @@
|
|||||||
"mysql2": "^2.3.3",
|
"mysql2": "^2.3.3",
|
||||||
"newrelic": "^9.0.0",
|
"newrelic": "^9.0.0",
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
|
"shallow-equal-object": "^1.1.1",
|
||||||
"typeorm": "^0.3.6",
|
"typeorm": "^0.3.6",
|
||||||
|
"uuid": "^9.0.0",
|
||||||
"winston": "^3.8.1"
|
"winston": "^3.8.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,6 +44,13 @@ import { SubscriptionPurchasedEventHandler } from '../Domain/Handler/Subscriptio
|
|||||||
import { SubscriptionExpiredEventHandler } from '../Domain/Handler/SubscriptionExpiredEventHandler'
|
import { SubscriptionExpiredEventHandler } from '../Domain/Handler/SubscriptionExpiredEventHandler'
|
||||||
import { SubscriptionReactivatedEventHandler } from '../Domain/Handler/SubscriptionReactivatedEventHandler'
|
import { SubscriptionReactivatedEventHandler } from '../Domain/Handler/SubscriptionReactivatedEventHandler'
|
||||||
import { RefundProcessedEventHandler } from '../Domain/Handler/RefundProcessedEventHandler'
|
import { RefundProcessedEventHandler } from '../Domain/Handler/RefundProcessedEventHandler'
|
||||||
|
import { RevenueModificationRepositoryInterface } from '../Domain/Revenue/RevenueModificationRepositoryInterface'
|
||||||
|
import { MySQLRevenueModificationRepository } from '../Infra/MySQL/MySQLRevenueModificationRepository'
|
||||||
|
import { TypeORMRevenueModification } from '../Infra/TypeORM/TypeORMRevenueModification'
|
||||||
|
import { MapInterface } from '../Domain/Map/MapInterface'
|
||||||
|
import { RevenueModification } from '../Domain/Revenue/RevenueModification'
|
||||||
|
import { RevenueModificationMap } from '../Domain/Map/RevenueModificationMap'
|
||||||
|
import { SaveRevenueModification } from '../Domain/UseCase/SaveRevenueModification/SaveRevenueModification'
|
||||||
|
|
||||||
// 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')
|
||||||
@@ -116,14 +123,21 @@ export class ContainerConfigLoader {
|
|||||||
container
|
container
|
||||||
.bind<AnalyticsEntityRepositoryInterface>(TYPES.AnalyticsEntityRepository)
|
.bind<AnalyticsEntityRepositoryInterface>(TYPES.AnalyticsEntityRepository)
|
||||||
.to(MySQLAnalyticsEntityRepository)
|
.to(MySQLAnalyticsEntityRepository)
|
||||||
|
container
|
||||||
|
.bind<RevenueModificationRepositoryInterface>(TYPES.RevenueModificationRepository)
|
||||||
|
.to(MySQLRevenueModificationRepository)
|
||||||
|
|
||||||
// ORM
|
// ORM
|
||||||
container
|
container
|
||||||
.bind<Repository<AnalyticsEntity>>(TYPES.ORMAnalyticsEntityRepository)
|
.bind<Repository<AnalyticsEntity>>(TYPES.ORMAnalyticsEntityRepository)
|
||||||
.toConstantValue(AppDataSource.getRepository(AnalyticsEntity))
|
.toConstantValue(AppDataSource.getRepository(AnalyticsEntity))
|
||||||
|
container
|
||||||
|
.bind<Repository<TypeORMRevenueModification>>(TYPES.ORMRevenueModificationRepository)
|
||||||
|
.toConstantValue(AppDataSource.getRepository(TypeORMRevenueModification))
|
||||||
|
|
||||||
// Use Case
|
// Use Case
|
||||||
container.bind<GetUserAnalyticsId>(TYPES.GetUserAnalyticsId).to(GetUserAnalyticsId)
|
container.bind<GetUserAnalyticsId>(TYPES.GetUserAnalyticsId).to(GetUserAnalyticsId)
|
||||||
|
container.bind<SaveRevenueModification>(TYPES.SaveRevenueModification).to(SaveRevenueModification)
|
||||||
|
|
||||||
// Hanlders
|
// Hanlders
|
||||||
container.bind<UserRegisteredEventHandler>(TYPES.UserRegisteredEventHandler).to(UserRegisteredEventHandler)
|
container.bind<UserRegisteredEventHandler>(TYPES.UserRegisteredEventHandler).to(UserRegisteredEventHandler)
|
||||||
@@ -152,6 +166,11 @@ export class ContainerConfigLoader {
|
|||||||
.to(SubscriptionReactivatedEventHandler)
|
.to(SubscriptionReactivatedEventHandler)
|
||||||
container.bind<RefundProcessedEventHandler>(TYPES.RefundProcessedEventHandler).to(RefundProcessedEventHandler)
|
container.bind<RefundProcessedEventHandler>(TYPES.RefundProcessedEventHandler).to(RefundProcessedEventHandler)
|
||||||
|
|
||||||
|
// Maps
|
||||||
|
container
|
||||||
|
.bind<MapInterface<RevenueModification, TypeORMRevenueModification>>(TYPES.RevenueModificationMap)
|
||||||
|
.to(RevenueModificationMap)
|
||||||
|
|
||||||
// Services
|
// Services
|
||||||
container.bind<DomainEventFactory>(TYPES.DomainEventFactory).to(DomainEventFactory)
|
container.bind<DomainEventFactory>(TYPES.DomainEventFactory).to(DomainEventFactory)
|
||||||
container.bind<PeriodKeyGeneratorInterface>(TYPES.PeriodKeyGenerator).toConstantValue(new PeriodKeyGenerator())
|
container.bind<PeriodKeyGeneratorInterface>(TYPES.PeriodKeyGenerator).toConstantValue(new PeriodKeyGenerator())
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { DataSource, LoggerOptions } from 'typeorm'
|
import { DataSource, LoggerOptions } from 'typeorm'
|
||||||
|
|
||||||
import { AnalyticsEntity } from '../Domain/Entity/AnalyticsEntity'
|
import { AnalyticsEntity } from '../Domain/Entity/AnalyticsEntity'
|
||||||
|
import { TypeORMRevenueModification } from '../Infra/TypeORM/TypeORMRevenueModification'
|
||||||
|
|
||||||
import { Env } from './Env'
|
import { Env } from './Env'
|
||||||
|
|
||||||
@@ -36,7 +37,7 @@ export const AppDataSource = new DataSource({
|
|||||||
],
|
],
|
||||||
removeNodeErrorCount: 10,
|
removeNodeErrorCount: 10,
|
||||||
},
|
},
|
||||||
entities: [AnalyticsEntity],
|
entities: [AnalyticsEntity, TypeORMRevenueModification],
|
||||||
migrations: [env.get('DB_MIGRATIONS_PATH', true) ?? 'dist/migrations/*.js'],
|
migrations: [env.get('DB_MIGRATIONS_PATH', true) ?? 'dist/migrations/*.js'],
|
||||||
migrationsRun: true,
|
migrationsRun: true,
|
||||||
logging: <LoggerOptions>env.get('DB_DEBUG_LEVEL'),
|
logging: <LoggerOptions>env.get('DB_DEBUG_LEVEL'),
|
||||||
|
|||||||
@@ -13,10 +13,13 @@ const TYPES = {
|
|||||||
NEW_RELIC_ENABLED: Symbol.for('NEW_RELIC_ENABLED'),
|
NEW_RELIC_ENABLED: Symbol.for('NEW_RELIC_ENABLED'),
|
||||||
// Repositories
|
// Repositories
|
||||||
AnalyticsEntityRepository: Symbol.for('AnalyticsEntityRepository'),
|
AnalyticsEntityRepository: Symbol.for('AnalyticsEntityRepository'),
|
||||||
|
RevenueModificationRepository: Symbol.for('RevenueModificationRepository'),
|
||||||
// ORM
|
// ORM
|
||||||
ORMAnalyticsEntityRepository: Symbol.for('ORMAnalyticsEntityRepository'),
|
ORMAnalyticsEntityRepository: Symbol.for('ORMAnalyticsEntityRepository'),
|
||||||
|
ORMRevenueModificationRepository: Symbol.for('ORMRevenueModificationRepository'),
|
||||||
// Use Case
|
// Use Case
|
||||||
GetUserAnalyticsId: Symbol.for('GetUserAnalyticsId'),
|
GetUserAnalyticsId: Symbol.for('GetUserAnalyticsId'),
|
||||||
|
SaveRevenueModification: Symbol.for('SaveRevenueModification'),
|
||||||
// Handlers
|
// Handlers
|
||||||
UserRegisteredEventHandler: Symbol.for('UserRegisteredEventHandler'),
|
UserRegisteredEventHandler: Symbol.for('UserRegisteredEventHandler'),
|
||||||
AccountDeletionRequestedEventHandler: Symbol.for('AccountDeletionRequestedEventHandler'),
|
AccountDeletionRequestedEventHandler: Symbol.for('AccountDeletionRequestedEventHandler'),
|
||||||
@@ -29,6 +32,8 @@ const TYPES = {
|
|||||||
SubscriptionExpiredEventHandler: Symbol.for('SubscriptionExpiredEventHandler'),
|
SubscriptionExpiredEventHandler: Symbol.for('SubscriptionExpiredEventHandler'),
|
||||||
SubscriptionReactivatedEventHandler: Symbol.for('SubscriptionReactivatedEventHandler'),
|
SubscriptionReactivatedEventHandler: Symbol.for('SubscriptionReactivatedEventHandler'),
|
||||||
RefundProcessedEventHandler: Symbol.for('RefundProcessedEventHandler'),
|
RefundProcessedEventHandler: Symbol.for('RefundProcessedEventHandler'),
|
||||||
|
// Maps
|
||||||
|
RevenueModificationMap: Symbol.for('RevenueModificationMap'),
|
||||||
// Services
|
// Services
|
||||||
DomainEventPublisher: Symbol.for('DomainEventPublisher'),
|
DomainEventPublisher: Symbol.for('DomainEventPublisher'),
|
||||||
DomainEventSubscriberFactory: Symbol.for('DomainEventSubscriberFactory'),
|
DomainEventSubscriberFactory: Symbol.for('DomainEventSubscriberFactory'),
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { Email } from './Email'
|
||||||
|
|
||||||
|
describe('Email', () => {
|
||||||
|
it('should create a value object', () => {
|
||||||
|
const valueOrError = Email.create('test@test.te')
|
||||||
|
|
||||||
|
expect(valueOrError.isFailed()).toBeFalsy()
|
||||||
|
expect(valueOrError.getValue().value).toEqual('test@test.te')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not create an invalid value object', () => {
|
||||||
|
const valueOrError = Email.create('')
|
||||||
|
|
||||||
|
expect(valueOrError.isFailed()).toBeTruthy()
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { ValueObject } from '../Core/ValueObject'
|
||||||
|
import { Result } from '../Core/Result'
|
||||||
|
import { EmailProps } from './EmailProps'
|
||||||
|
|
||||||
|
export class Email extends ValueObject<EmailProps> {
|
||||||
|
get value(): string {
|
||||||
|
return this.props.value
|
||||||
|
}
|
||||||
|
|
||||||
|
private constructor(props: EmailProps) {
|
||||||
|
super(props)
|
||||||
|
}
|
||||||
|
|
||||||
|
static create(email: string): Result<Email> {
|
||||||
|
if (!!email === false || email.length === 0) {
|
||||||
|
return Result.fail<Email>('Email cannot be empty')
|
||||||
|
} else {
|
||||||
|
return Result.ok<Email>(new Email({ value: email }))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export interface EmailProps {
|
||||||
|
value: string
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { Uuid } from './Uuid'
|
||||||
|
|
||||||
|
describe('Uuid', () => {
|
||||||
|
it('should create a value object', () => {
|
||||||
|
const valueOrError = Uuid.create('1-2-3')
|
||||||
|
|
||||||
|
expect(valueOrError.isFailed()).toBeFalsy()
|
||||||
|
expect(valueOrError.getValue().value).toEqual('1-2-3')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not create an invalid value object', () => {
|
||||||
|
const valueOrError = Uuid.create('')
|
||||||
|
|
||||||
|
expect(valueOrError.isFailed()).toBeTruthy()
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { ValueObject } from '../Core/ValueObject'
|
||||||
|
import { Result } from '../Core/Result'
|
||||||
|
import { UuidProps } from './UuidProps'
|
||||||
|
|
||||||
|
export class Uuid extends ValueObject<UuidProps> {
|
||||||
|
get value(): string {
|
||||||
|
return this.props.value
|
||||||
|
}
|
||||||
|
|
||||||
|
private constructor(props: UuidProps) {
|
||||||
|
super(props)
|
||||||
|
}
|
||||||
|
|
||||||
|
static create(uuid: string): Result<Uuid> {
|
||||||
|
if (!!uuid === false || uuid.length === 0) {
|
||||||
|
return Result.fail<Uuid>('Uuid cannot be empty')
|
||||||
|
} else {
|
||||||
|
return Result.ok<Uuid>(new Uuid({ value: uuid }))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export interface UuidProps {
|
||||||
|
value: string
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
/* istanbul ignore file */
|
||||||
|
|
||||||
|
import { Entity } from './Entity'
|
||||||
|
import { UniqueEntityId } from './UniqueEntityId'
|
||||||
|
|
||||||
|
export abstract class Aggregate<T> extends Entity<T> {
|
||||||
|
get id(): UniqueEntityId {
|
||||||
|
return this._id
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
/* istanbul ignore file */
|
||||||
|
|
||||||
|
import { UniqueEntityId } from './UniqueEntityId'
|
||||||
|
|
||||||
|
export abstract class Entity<T> {
|
||||||
|
protected readonly _id: UniqueEntityId
|
||||||
|
|
||||||
|
constructor(public readonly props: T, id?: UniqueEntityId) {
|
||||||
|
this._id = id ? id : new UniqueEntityId()
|
||||||
|
}
|
||||||
|
|
||||||
|
public equals(object?: Entity<T>): boolean {
|
||||||
|
if (object == null || object == undefined) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this === object) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(object instanceof Entity)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._id.equals(object._id)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
/* istanbul ignore file */
|
||||||
|
|
||||||
|
export class Id<T> {
|
||||||
|
constructor(private value: T) {}
|
||||||
|
|
||||||
|
equals(id?: Id<T>): boolean {
|
||||||
|
if (id === null || id === undefined) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (!(id instanceof this.constructor)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return id.toValue() === this.value
|
||||||
|
}
|
||||||
|
|
||||||
|
toString() {
|
||||||
|
return String(this.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
toValue(): T {
|
||||||
|
return this.value
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
/* istanbul ignore file */
|
||||||
|
|
||||||
|
export class Result<T> {
|
||||||
|
constructor(private isSuccess: boolean, private error?: T | string, private value?: T) {
|
||||||
|
Object.freeze(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
isFailed(): boolean {
|
||||||
|
return !this.isSuccess
|
||||||
|
}
|
||||||
|
|
||||||
|
getValue(): T {
|
||||||
|
if (!this.isSuccess) {
|
||||||
|
throw new Error('Cannot get value of an unsuccessfull result')
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.value as T
|
||||||
|
}
|
||||||
|
|
||||||
|
getError(): T | string {
|
||||||
|
if (this.isSuccess || this.error === undefined) {
|
||||||
|
throw new Error('Cannot get an error of a successfull result')
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.error
|
||||||
|
}
|
||||||
|
|
||||||
|
static ok<U>(value?: U): Result<U> {
|
||||||
|
return new Result<U>(true, undefined, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
static fail<U>(error: U | string): Result<U> {
|
||||||
|
return new Result<U>(false, error)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
/* istanbul ignore file */
|
||||||
|
|
||||||
|
import { v4 as uuid } from 'uuid'
|
||||||
|
import { Id } from './Id'
|
||||||
|
|
||||||
|
export class UniqueEntityId extends Id<string | number> {
|
||||||
|
constructor(id?: string | number) {
|
||||||
|
super(id ? id : uuid())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
/* istanbul ignore file */
|
||||||
|
|
||||||
|
import { shallowEqual } from 'shallow-equal-object'
|
||||||
|
|
||||||
|
import { ValueObjectProps } from './ValueObjectProps'
|
||||||
|
|
||||||
|
export abstract class ValueObject<T extends ValueObjectProps> {
|
||||||
|
public readonly props: T
|
||||||
|
|
||||||
|
constructor(props: T) {
|
||||||
|
this.props = Object.freeze(props)
|
||||||
|
}
|
||||||
|
|
||||||
|
equals(valueObject?: ValueObject<T>): boolean {
|
||||||
|
if (valueObject === null || valueObject === undefined) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (valueObject.props === undefined) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return shallowEqual(this.props, valueObject.props)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export interface ValueObjectProps {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
[index: string]: any
|
||||||
|
}
|
||||||
@@ -7,18 +7,24 @@ import { SubscriptionExpiredEventHandler } from './SubscriptionExpiredEventHandl
|
|||||||
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
|
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
|
||||||
import { StatisticsStoreInterface } from '../Statistics/StatisticsStoreInterface'
|
import { StatisticsStoreInterface } from '../Statistics/StatisticsStoreInterface'
|
||||||
import { AnalyticsStoreInterface } from '../Analytics/AnalyticsStoreInterface'
|
import { AnalyticsStoreInterface } from '../Analytics/AnalyticsStoreInterface'
|
||||||
|
import { SaveRevenueModification } from '../UseCase/SaveRevenueModification/SaveRevenueModification'
|
||||||
|
import { Result } from '../Core/Result'
|
||||||
|
import { RevenueModification } from '../Revenue/RevenueModification'
|
||||||
|
|
||||||
describe('SubscriptionExpiredEventHandler', () => {
|
describe('SubscriptionExpiredEventHandler', () => {
|
||||||
let event: SubscriptionExpiredEvent
|
let event: SubscriptionExpiredEvent
|
||||||
let getUserAnalyticsId: GetUserAnalyticsId
|
let getUserAnalyticsId: GetUserAnalyticsId
|
||||||
let analyticsStore: AnalyticsStoreInterface
|
let analyticsStore: AnalyticsStoreInterface
|
||||||
let statisticsStore: StatisticsStoreInterface
|
let statisticsStore: StatisticsStoreInterface
|
||||||
|
let saveRevenueModification: SaveRevenueModification
|
||||||
|
|
||||||
const createHandler = () => new SubscriptionExpiredEventHandler(getUserAnalyticsId, analyticsStore, statisticsStore)
|
const createHandler = () =>
|
||||||
|
new SubscriptionExpiredEventHandler(getUserAnalyticsId, analyticsStore, statisticsStore, saveRevenueModification)
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
event = {} as jest.Mocked<SubscriptionExpiredEvent>
|
event = {} as jest.Mocked<SubscriptionExpiredEvent>
|
||||||
event.createdAt = new Date(1)
|
event.createdAt = new Date(1)
|
||||||
|
event.type = 'SUBSCRIPTION_EXPIRED'
|
||||||
event.payload = {
|
event.payload = {
|
||||||
subscriptionId: 1,
|
subscriptionId: 1,
|
||||||
userEmail: 'test@test.com',
|
userEmail: 'test@test.com',
|
||||||
@@ -26,6 +32,9 @@ describe('SubscriptionExpiredEventHandler', () => {
|
|||||||
timestamp: 1,
|
timestamp: 1,
|
||||||
offline: false,
|
offline: false,
|
||||||
totalActiveSubscriptionsCount: 123,
|
totalActiveSubscriptionsCount: 123,
|
||||||
|
userExistingSubscriptionsCount: 2,
|
||||||
|
billingFrequency: 1,
|
||||||
|
payAmount: 12.99,
|
||||||
}
|
}
|
||||||
|
|
||||||
getUserAnalyticsId = {} as jest.Mocked<GetUserAnalyticsId>
|
getUserAnalyticsId = {} as jest.Mocked<GetUserAnalyticsId>
|
||||||
@@ -36,6 +45,9 @@ describe('SubscriptionExpiredEventHandler', () => {
|
|||||||
|
|
||||||
statisticsStore = {} as jest.Mocked<StatisticsStoreInterface>
|
statisticsStore = {} as jest.Mocked<StatisticsStoreInterface>
|
||||||
statisticsStore.setMeasure = jest.fn()
|
statisticsStore.setMeasure = jest.fn()
|
||||||
|
|
||||||
|
saveRevenueModification = {} as jest.Mocked<SaveRevenueModification>
|
||||||
|
saveRevenueModification.execute = jest.fn().mockReturnValue(Result.ok<RevenueModification>())
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should update analytics and statistics', async () => {
|
it('should update analytics and statistics', async () => {
|
||||||
@@ -43,5 +55,6 @@ describe('SubscriptionExpiredEventHandler', () => {
|
|||||||
|
|
||||||
expect(analyticsStore.markActivity).toHaveBeenCalled()
|
expect(analyticsStore.markActivity).toHaveBeenCalled()
|
||||||
expect(statisticsStore.setMeasure).toHaveBeenCalled()
|
expect(statisticsStore.setMeasure).toHaveBeenCalled()
|
||||||
|
expect(saveRevenueModification.execute).toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -4,10 +4,14 @@ import { inject, injectable } from 'inversify'
|
|||||||
import TYPES from '../../Bootstrap/Types'
|
import TYPES from '../../Bootstrap/Types'
|
||||||
import { AnalyticsActivity } from '../Analytics/AnalyticsActivity'
|
import { AnalyticsActivity } from '../Analytics/AnalyticsActivity'
|
||||||
import { AnalyticsStoreInterface } from '../Analytics/AnalyticsStoreInterface'
|
import { AnalyticsStoreInterface } from '../Analytics/AnalyticsStoreInterface'
|
||||||
|
import { Email } from '../Common/Email'
|
||||||
import { StatisticsMeasure } from '../Statistics/StatisticsMeasure'
|
import { StatisticsMeasure } from '../Statistics/StatisticsMeasure'
|
||||||
import { StatisticsStoreInterface } from '../Statistics/StatisticsStoreInterface'
|
import { StatisticsStoreInterface } from '../Statistics/StatisticsStoreInterface'
|
||||||
|
import { SubscriptionEventType } from '../Subscription/SubscriptionEventType'
|
||||||
|
import { SubscriptionPlanName } from '../Subscription/SubscriptionPlanName'
|
||||||
import { Period } from '../Time/Period'
|
import { Period } from '../Time/Period'
|
||||||
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
|
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
|
||||||
|
import { SaveRevenueModification } from '../UseCase/SaveRevenueModification/SaveRevenueModification'
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class SubscriptionExpiredEventHandler implements DomainEventHandlerInterface {
|
export class SubscriptionExpiredEventHandler implements DomainEventHandlerInterface {
|
||||||
@@ -15,10 +19,11 @@ export class SubscriptionExpiredEventHandler implements DomainEventHandlerInterf
|
|||||||
@inject(TYPES.GetUserAnalyticsId) private getUserAnalyticsId: GetUserAnalyticsId,
|
@inject(TYPES.GetUserAnalyticsId) private getUserAnalyticsId: GetUserAnalyticsId,
|
||||||
@inject(TYPES.AnalyticsStore) private analyticsStore: AnalyticsStoreInterface,
|
@inject(TYPES.AnalyticsStore) private analyticsStore: AnalyticsStoreInterface,
|
||||||
@inject(TYPES.StatisticsStore) private statisticsStore: StatisticsStoreInterface,
|
@inject(TYPES.StatisticsStore) private statisticsStore: StatisticsStoreInterface,
|
||||||
|
@inject(TYPES.SaveRevenueModification) private saveRevenueModification: SaveRevenueModification,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async handle(event: SubscriptionExpiredEvent): Promise<void> {
|
async handle(event: SubscriptionExpiredEvent): Promise<void> {
|
||||||
const { analyticsId } = await this.getUserAnalyticsId.execute({ userEmail: event.payload.userEmail })
|
const { analyticsId, userUuid } = await this.getUserAnalyticsId.execute({ userEmail: event.payload.userEmail })
|
||||||
await this.analyticsStore.markActivity(
|
await this.analyticsStore.markActivity(
|
||||||
[AnalyticsActivity.SubscriptionExpired, AnalyticsActivity.ExistingCustomersChurn],
|
[AnalyticsActivity.SubscriptionExpired, AnalyticsActivity.ExistingCustomersChurn],
|
||||||
analyticsId,
|
analyticsId,
|
||||||
@@ -30,5 +35,16 @@ export class SubscriptionExpiredEventHandler implements DomainEventHandlerInterf
|
|||||||
event.payload.totalActiveSubscriptionsCount,
|
event.payload.totalActiveSubscriptionsCount,
|
||||||
[Period.Today, Period.ThisWeek, Period.ThisMonth, Period.ThisYear],
|
[Period.Today, Period.ThisWeek, Period.ThisMonth, Period.ThisYear],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
await this.saveRevenueModification.execute({
|
||||||
|
billingFrequency: event.payload.billingFrequency,
|
||||||
|
eventType: SubscriptionEventType.create(event.type).getValue(),
|
||||||
|
newSubscriber: event.payload.userExistingSubscriptionsCount === 1,
|
||||||
|
payedAmount: event.payload.payAmount,
|
||||||
|
planName: SubscriptionPlanName.create(event.payload.subscriptionName).getValue(),
|
||||||
|
subscriptionId: event.payload.subscriptionId,
|
||||||
|
userEmail: Email.create(event.payload.userEmail).getValue(),
|
||||||
|
userUuid,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,9 @@ import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyti
|
|||||||
import { AnalyticsStoreInterface } from '../Analytics/AnalyticsStoreInterface'
|
import { AnalyticsStoreInterface } from '../Analytics/AnalyticsStoreInterface'
|
||||||
import { StatisticsStoreInterface } from '../Statistics/StatisticsStoreInterface'
|
import { StatisticsStoreInterface } from '../Statistics/StatisticsStoreInterface'
|
||||||
import { Period } from '../Time/Period'
|
import { Period } from '../Time/Period'
|
||||||
|
import { SaveRevenueModification } from '../UseCase/SaveRevenueModification/SaveRevenueModification'
|
||||||
|
import { Result } from '../Core/Result'
|
||||||
|
import { RevenueModification } from '../Revenue/RevenueModification'
|
||||||
|
|
||||||
describe('SubscriptionPurchasedEventHandler', () => {
|
describe('SubscriptionPurchasedEventHandler', () => {
|
||||||
let event: SubscriptionPurchasedEvent
|
let event: SubscriptionPurchasedEvent
|
||||||
@@ -15,8 +18,10 @@ describe('SubscriptionPurchasedEventHandler', () => {
|
|||||||
let getUserAnalyticsId: GetUserAnalyticsId
|
let getUserAnalyticsId: GetUserAnalyticsId
|
||||||
let analyticsStore: AnalyticsStoreInterface
|
let analyticsStore: AnalyticsStoreInterface
|
||||||
let statisticsStore: StatisticsStoreInterface
|
let statisticsStore: StatisticsStoreInterface
|
||||||
|
let saveRevenueModification: SaveRevenueModification
|
||||||
|
|
||||||
const createHandler = () => new SubscriptionPurchasedEventHandler(getUserAnalyticsId, analyticsStore, statisticsStore)
|
const createHandler = () =>
|
||||||
|
new SubscriptionPurchasedEventHandler(getUserAnalyticsId, analyticsStore, statisticsStore, saveRevenueModification)
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
statisticsStore = {} as jest.Mocked<StatisticsStoreInterface>
|
statisticsStore = {} as jest.Mocked<StatisticsStoreInterface>
|
||||||
@@ -25,6 +30,7 @@ describe('SubscriptionPurchasedEventHandler', () => {
|
|||||||
|
|
||||||
event = {} as jest.Mocked<SubscriptionPurchasedEvent>
|
event = {} as jest.Mocked<SubscriptionPurchasedEvent>
|
||||||
event.createdAt = new Date(1)
|
event.createdAt = new Date(1)
|
||||||
|
event.type = 'SUBSCRIPTION_PURCHASED'
|
||||||
event.payload = {
|
event.payload = {
|
||||||
subscriptionId: 1,
|
subscriptionId: 1,
|
||||||
userEmail: 'test@test.com',
|
userEmail: 'test@test.com',
|
||||||
@@ -37,6 +43,8 @@ describe('SubscriptionPurchasedEventHandler', () => {
|
|||||||
newSubscriber: true,
|
newSubscriber: true,
|
||||||
totalActiveSubscriptionsCount: 123,
|
totalActiveSubscriptionsCount: 123,
|
||||||
userRegisteredAt: 23,
|
userRegisteredAt: 23,
|
||||||
|
billingFrequency: 12,
|
||||||
|
payAmount: 29.99,
|
||||||
}
|
}
|
||||||
|
|
||||||
getUserAnalyticsId = {} as jest.Mocked<GetUserAnalyticsId>
|
getUserAnalyticsId = {} as jest.Mocked<GetUserAnalyticsId>
|
||||||
@@ -45,12 +53,16 @@ describe('SubscriptionPurchasedEventHandler', () => {
|
|||||||
analyticsStore = {} as jest.Mocked<AnalyticsStoreInterface>
|
analyticsStore = {} as jest.Mocked<AnalyticsStoreInterface>
|
||||||
analyticsStore.markActivity = jest.fn()
|
analyticsStore.markActivity = jest.fn()
|
||||||
analyticsStore.unmarkActivity = jest.fn()
|
analyticsStore.unmarkActivity = jest.fn()
|
||||||
|
|
||||||
|
saveRevenueModification = {} as jest.Mocked<SaveRevenueModification>
|
||||||
|
saveRevenueModification.execute = jest.fn().mockReturnValue(Result.ok<RevenueModification>())
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should mark subscription creation statistics', async () => {
|
it('should mark subscription creation statistics', async () => {
|
||||||
await createHandler().handle(event)
|
await createHandler().handle(event)
|
||||||
|
|
||||||
expect(statisticsStore.incrementMeasure).toHaveBeenCalled()
|
expect(statisticsStore.incrementMeasure).toHaveBeenCalled()
|
||||||
|
expect(saveRevenueModification.execute).toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should not measure registration to subscription time if this is not user's first subscription", async () => {
|
it("should not measure registration to subscription time if this is not user's first subscription", async () => {
|
||||||
|
|||||||
@@ -4,10 +4,14 @@ import { inject, injectable } from 'inversify'
|
|||||||
import TYPES from '../../Bootstrap/Types'
|
import TYPES from '../../Bootstrap/Types'
|
||||||
import { AnalyticsActivity } from '../Analytics/AnalyticsActivity'
|
import { AnalyticsActivity } from '../Analytics/AnalyticsActivity'
|
||||||
import { AnalyticsStoreInterface } from '../Analytics/AnalyticsStoreInterface'
|
import { AnalyticsStoreInterface } from '../Analytics/AnalyticsStoreInterface'
|
||||||
|
import { Email } from '../Common/Email'
|
||||||
import { StatisticsMeasure } from '../Statistics/StatisticsMeasure'
|
import { StatisticsMeasure } from '../Statistics/StatisticsMeasure'
|
||||||
import { StatisticsStoreInterface } from '../Statistics/StatisticsStoreInterface'
|
import { StatisticsStoreInterface } from '../Statistics/StatisticsStoreInterface'
|
||||||
|
import { SubscriptionEventType } from '../Subscription/SubscriptionEventType'
|
||||||
|
import { SubscriptionPlanName } from '../Subscription/SubscriptionPlanName'
|
||||||
import { Period } from '../Time/Period'
|
import { Period } from '../Time/Period'
|
||||||
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
|
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
|
||||||
|
import { SaveRevenueModification } from '../UseCase/SaveRevenueModification/SaveRevenueModification'
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class SubscriptionPurchasedEventHandler implements DomainEventHandlerInterface {
|
export class SubscriptionPurchasedEventHandler implements DomainEventHandlerInterface {
|
||||||
@@ -15,10 +19,11 @@ export class SubscriptionPurchasedEventHandler implements DomainEventHandlerInte
|
|||||||
@inject(TYPES.GetUserAnalyticsId) private getUserAnalyticsId: GetUserAnalyticsId,
|
@inject(TYPES.GetUserAnalyticsId) private getUserAnalyticsId: GetUserAnalyticsId,
|
||||||
@inject(TYPES.AnalyticsStore) private analyticsStore: AnalyticsStoreInterface,
|
@inject(TYPES.AnalyticsStore) private analyticsStore: AnalyticsStoreInterface,
|
||||||
@inject(TYPES.StatisticsStore) private statisticsStore: StatisticsStoreInterface,
|
@inject(TYPES.StatisticsStore) private statisticsStore: StatisticsStoreInterface,
|
||||||
|
@inject(TYPES.SaveRevenueModification) private saveRevenueModification: SaveRevenueModification,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async handle(event: SubscriptionPurchasedEvent): Promise<void> {
|
async handle(event: SubscriptionPurchasedEvent): Promise<void> {
|
||||||
const { analyticsId } = await this.getUserAnalyticsId.execute({ userEmail: event.payload.userEmail })
|
const { analyticsId, userUuid } = await this.getUserAnalyticsId.execute({ userEmail: event.payload.userEmail })
|
||||||
await this.analyticsStore.markActivity([AnalyticsActivity.SubscriptionPurchased], analyticsId, [
|
await this.analyticsStore.markActivity([AnalyticsActivity.SubscriptionPurchased], analyticsId, [
|
||||||
Period.Today,
|
Period.Today,
|
||||||
Period.ThisWeek,
|
Period.ThisWeek,
|
||||||
@@ -54,5 +59,16 @@ export class SubscriptionPurchasedEventHandler implements DomainEventHandlerInte
|
|||||||
[Period.Today, Period.ThisWeek, Period.ThisMonth, Period.ThisYear],
|
[Period.Today, Period.ThisWeek, Period.ThisMonth, Period.ThisYear],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await this.saveRevenueModification.execute({
|
||||||
|
billingFrequency: event.payload.billingFrequency,
|
||||||
|
eventType: SubscriptionEventType.create(event.type).getValue(),
|
||||||
|
newSubscriber: event.payload.newSubscriber,
|
||||||
|
payedAmount: event.payload.payAmount,
|
||||||
|
planName: SubscriptionPlanName.create(event.payload.subscriptionName).getValue(),
|
||||||
|
subscriptionId: event.payload.subscriptionId,
|
||||||
|
userEmail: Email.create(event.payload.userEmail).getValue(),
|
||||||
|
userUuid,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,17 +6,23 @@ import { SubscriptionRenewedEvent } from '@standardnotes/domain-events'
|
|||||||
import { SubscriptionRenewedEventHandler } from './SubscriptionRenewedEventHandler'
|
import { SubscriptionRenewedEventHandler } from './SubscriptionRenewedEventHandler'
|
||||||
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
|
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
|
||||||
import { AnalyticsStoreInterface } from '../Analytics/AnalyticsStoreInterface'
|
import { AnalyticsStoreInterface } from '../Analytics/AnalyticsStoreInterface'
|
||||||
|
import { SaveRevenueModification } from '../UseCase/SaveRevenueModification/SaveRevenueModification'
|
||||||
|
import { RevenueModification } from '../Revenue/RevenueModification'
|
||||||
|
import { Result } from '../Core/Result'
|
||||||
|
|
||||||
describe('SubscriptionRenewedEventHandler', () => {
|
describe('SubscriptionRenewedEventHandler', () => {
|
||||||
let event: SubscriptionRenewedEvent
|
let event: SubscriptionRenewedEvent
|
||||||
let getUserAnalyticsId: GetUserAnalyticsId
|
let getUserAnalyticsId: GetUserAnalyticsId
|
||||||
let analyticsStore: AnalyticsStoreInterface
|
let analyticsStore: AnalyticsStoreInterface
|
||||||
|
let saveRevenueModification: SaveRevenueModification
|
||||||
|
|
||||||
const createHandler = () => new SubscriptionRenewedEventHandler(getUserAnalyticsId, analyticsStore)
|
const createHandler = () =>
|
||||||
|
new SubscriptionRenewedEventHandler(getUserAnalyticsId, analyticsStore, saveRevenueModification)
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
event = {} as jest.Mocked<SubscriptionRenewedEvent>
|
event = {} as jest.Mocked<SubscriptionRenewedEvent>
|
||||||
event.createdAt = new Date(1)
|
event.createdAt = new Date(1)
|
||||||
|
event.type = 'SUBSCRIPTION_RENEWED'
|
||||||
event.payload = {
|
event.payload = {
|
||||||
subscriptionId: 1,
|
subscriptionId: 1,
|
||||||
userEmail: 'test@test.com',
|
userEmail: 'test@test.com',
|
||||||
@@ -24,6 +30,8 @@ describe('SubscriptionRenewedEventHandler', () => {
|
|||||||
subscriptionExpiresAt: 2,
|
subscriptionExpiresAt: 2,
|
||||||
timestamp: 1,
|
timestamp: 1,
|
||||||
offline: false,
|
offline: false,
|
||||||
|
billingFrequency: 1,
|
||||||
|
payAmount: 12.99,
|
||||||
}
|
}
|
||||||
|
|
||||||
getUserAnalyticsId = {} as jest.Mocked<GetUserAnalyticsId>
|
getUserAnalyticsId = {} as jest.Mocked<GetUserAnalyticsId>
|
||||||
@@ -32,6 +40,9 @@ describe('SubscriptionRenewedEventHandler', () => {
|
|||||||
analyticsStore = {} as jest.Mocked<AnalyticsStoreInterface>
|
analyticsStore = {} as jest.Mocked<AnalyticsStoreInterface>
|
||||||
analyticsStore.markActivity = jest.fn()
|
analyticsStore.markActivity = jest.fn()
|
||||||
analyticsStore.unmarkActivity = jest.fn()
|
analyticsStore.unmarkActivity = jest.fn()
|
||||||
|
|
||||||
|
saveRevenueModification = {} as jest.Mocked<SaveRevenueModification>
|
||||||
|
saveRevenueModification.execute = jest.fn().mockReturnValue(Result.ok<RevenueModification>())
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should track subscription renewed statistics', async () => {
|
it('should track subscription renewed statistics', async () => {
|
||||||
@@ -39,5 +50,6 @@ describe('SubscriptionRenewedEventHandler', () => {
|
|||||||
|
|
||||||
expect(analyticsStore.markActivity).toHaveBeenCalled()
|
expect(analyticsStore.markActivity).toHaveBeenCalled()
|
||||||
expect(analyticsStore.unmarkActivity).toHaveBeenCalled()
|
expect(analyticsStore.unmarkActivity).toHaveBeenCalled()
|
||||||
|
expect(saveRevenueModification.execute).toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -6,16 +6,21 @@ import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyti
|
|||||||
import { AnalyticsActivity } from '../Analytics/AnalyticsActivity'
|
import { AnalyticsActivity } from '../Analytics/AnalyticsActivity'
|
||||||
import { AnalyticsStoreInterface } from '../Analytics/AnalyticsStoreInterface'
|
import { AnalyticsStoreInterface } from '../Analytics/AnalyticsStoreInterface'
|
||||||
import { Period } from '../Time/Period'
|
import { Period } from '../Time/Period'
|
||||||
|
import { SaveRevenueModification } from '../UseCase/SaveRevenueModification/SaveRevenueModification'
|
||||||
|
import { Email } from '../Common/Email'
|
||||||
|
import { SubscriptionEventType } from '../Subscription/SubscriptionEventType'
|
||||||
|
import { SubscriptionPlanName } from '../Subscription/SubscriptionPlanName'
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class SubscriptionRenewedEventHandler implements DomainEventHandlerInterface {
|
export class SubscriptionRenewedEventHandler implements DomainEventHandlerInterface {
|
||||||
constructor(
|
constructor(
|
||||||
@inject(TYPES.GetUserAnalyticsId) private getUserAnalyticsId: GetUserAnalyticsId,
|
@inject(TYPES.GetUserAnalyticsId) private getUserAnalyticsId: GetUserAnalyticsId,
|
||||||
@inject(TYPES.AnalyticsStore) private analyticsStore: AnalyticsStoreInterface,
|
@inject(TYPES.AnalyticsStore) private analyticsStore: AnalyticsStoreInterface,
|
||||||
|
@inject(TYPES.SaveRevenueModification) private saveRevenueModification: SaveRevenueModification,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async handle(event: SubscriptionRenewedEvent): Promise<void> {
|
async handle(event: SubscriptionRenewedEvent): Promise<void> {
|
||||||
const { analyticsId } = await this.getUserAnalyticsId.execute({ userEmail: event.payload.userEmail })
|
const { analyticsId, userUuid } = await this.getUserAnalyticsId.execute({ userEmail: event.payload.userEmail })
|
||||||
await this.analyticsStore.markActivity([AnalyticsActivity.SubscriptionRenewed], analyticsId, [
|
await this.analyticsStore.markActivity([AnalyticsActivity.SubscriptionRenewed], analyticsId, [
|
||||||
Period.Today,
|
Period.Today,
|
||||||
Period.ThisWeek,
|
Period.ThisWeek,
|
||||||
@@ -26,5 +31,16 @@ export class SubscriptionRenewedEventHandler implements DomainEventHandlerInterf
|
|||||||
analyticsId,
|
analyticsId,
|
||||||
[Period.Today, Period.ThisWeek, Period.ThisMonth],
|
[Period.Today, Period.ThisWeek, Period.ThisMonth],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
await this.saveRevenueModification.execute({
|
||||||
|
billingFrequency: event.payload.billingFrequency,
|
||||||
|
eventType: SubscriptionEventType.create(event.type).getValue(),
|
||||||
|
newSubscriber: false,
|
||||||
|
payedAmount: event.payload.payAmount,
|
||||||
|
planName: SubscriptionPlanName.create(event.payload.subscriptionName).getValue(),
|
||||||
|
subscriptionId: event.payload.subscriptionId,
|
||||||
|
userEmail: Email.create(event.payload.userEmail).getValue(),
|
||||||
|
userUuid,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export interface MapInterface<T, U> {
|
||||||
|
toDomain(persistence: U): T
|
||||||
|
toPersistence(domain: T): U
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
import { injectable } from 'inversify'
|
||||||
|
|
||||||
|
import { TypeORMRevenueModification } from '../../Infra/TypeORM/TypeORMRevenueModification'
|
||||||
|
import { UniqueEntityId } from '../Core/UniqueEntityId'
|
||||||
|
import { MonthlyRevenue } from '../Revenue/MonthlyRevenue'
|
||||||
|
import { RevenueModification } from '../Revenue/RevenueModification'
|
||||||
|
import { Subscription } from '../Subscription/Subscription'
|
||||||
|
import { User } from '../User/User'
|
||||||
|
import { MapInterface } from './MapInterface'
|
||||||
|
import { Email } from '../Common/Email'
|
||||||
|
import { SubscriptionPlanName } from '../Subscription/SubscriptionPlanName'
|
||||||
|
import { SubscriptionEventType } from '../Subscription/SubscriptionEventType'
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class RevenueModificationMap implements MapInterface<RevenueModification, TypeORMRevenueModification> {
|
||||||
|
toDomain(persistence: TypeORMRevenueModification): RevenueModification {
|
||||||
|
const user = User.create(
|
||||||
|
{
|
||||||
|
email: Email.create(persistence.userEmail).getValue(),
|
||||||
|
},
|
||||||
|
new UniqueEntityId(persistence.userUuid),
|
||||||
|
)
|
||||||
|
const subscription = Subscription.create(
|
||||||
|
{
|
||||||
|
billingFrequency: persistence.billingFrequency,
|
||||||
|
isFirstSubscriptionForUser: persistence.isNewCustomer,
|
||||||
|
payedAmount: persistence.billingFrequency * persistence.newMonthlyRevenue,
|
||||||
|
planName: SubscriptionPlanName.create(persistence.subscriptionPlan).getValue(),
|
||||||
|
},
|
||||||
|
new UniqueEntityId(persistence.subscriptionId),
|
||||||
|
)
|
||||||
|
const previousMonthlyRevenueOrError = MonthlyRevenue.create(persistence.previousMonthlyRevenue)
|
||||||
|
|
||||||
|
return RevenueModification.create(
|
||||||
|
{
|
||||||
|
user,
|
||||||
|
subscription,
|
||||||
|
eventType: SubscriptionEventType.create(persistence.eventType).getValue(),
|
||||||
|
previousMonthlyRevenue: previousMonthlyRevenueOrError.getValue(),
|
||||||
|
},
|
||||||
|
new UniqueEntityId(persistence.uuid),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
toPersistence(domain: RevenueModification): TypeORMRevenueModification {
|
||||||
|
const { subscription, user } = domain.props
|
||||||
|
const persistence = new TypeORMRevenueModification()
|
||||||
|
persistence.uuid = domain.id.toString()
|
||||||
|
persistence.billingFrequency = subscription.props.billingFrequency
|
||||||
|
persistence.eventType = domain.props.eventType.value
|
||||||
|
persistence.isNewCustomer = subscription.props.isFirstSubscriptionForUser
|
||||||
|
persistence.newMonthlyRevenue = domain.newMonthlyRevenue.value
|
||||||
|
persistence.previousMonthlyRevenue = domain.props.previousMonthlyRevenue.value
|
||||||
|
persistence.subscriptionId = subscription.id.toValue() as number
|
||||||
|
persistence.subscriptionPlan = subscription.props.planName.value
|
||||||
|
persistence.userEmail = user.props.email.value
|
||||||
|
persistence.userUuid = user.id.toString()
|
||||||
|
|
||||||
|
return persistence
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { MonthlyRevenue } from './MonthlyRevenue'
|
||||||
|
|
||||||
|
describe('MonthlyRevenue', () => {
|
||||||
|
it('should create a value object', () => {
|
||||||
|
const valueOrError = MonthlyRevenue.create(123)
|
||||||
|
|
||||||
|
expect(valueOrError.isFailed()).toBeFalsy()
|
||||||
|
expect(valueOrError.getValue().value).toEqual(123)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not create an invalid value object', () => {
|
||||||
|
const valueOrError = MonthlyRevenue.create(-3)
|
||||||
|
|
||||||
|
expect(valueOrError.isFailed()).toBeTruthy()
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { ValueObject } from '../Core/ValueObject'
|
||||||
|
import { Result } from '../Core/Result'
|
||||||
|
import { MonthlyRevenueProps } from './MonthlyRevenueProps'
|
||||||
|
|
||||||
|
export class MonthlyRevenue extends ValueObject<MonthlyRevenueProps> {
|
||||||
|
get value(): number {
|
||||||
|
return this.props.value
|
||||||
|
}
|
||||||
|
|
||||||
|
private constructor(props: MonthlyRevenueProps) {
|
||||||
|
super(props)
|
||||||
|
}
|
||||||
|
|
||||||
|
static create(revenue: number): Result<MonthlyRevenue> {
|
||||||
|
if (isNaN(revenue) || revenue < 0) {
|
||||||
|
return Result.fail<MonthlyRevenue>('Monthly revenue must be a non-negative number')
|
||||||
|
} else {
|
||||||
|
return Result.ok<MonthlyRevenue>(new MonthlyRevenue({ value: revenue }))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export interface MonthlyRevenueProps {
|
||||||
|
value: number
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
import { Email } from '../Common/Email'
|
||||||
|
import { Subscription } from '../Subscription/Subscription'
|
||||||
|
import { SubscriptionEventType } from '../Subscription/SubscriptionEventType'
|
||||||
|
import { SubscriptionPlanName } from '../Subscription/SubscriptionPlanName'
|
||||||
|
import { User } from '../User/User'
|
||||||
|
import { MonthlyRevenue } from './MonthlyRevenue'
|
||||||
|
import { RevenueModification } from './RevenueModification'
|
||||||
|
|
||||||
|
describe('RevenueModification', () => {
|
||||||
|
let user: User
|
||||||
|
let subscription: Subscription
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
subscription = Subscription.create({
|
||||||
|
billingFrequency: 12,
|
||||||
|
isFirstSubscriptionForUser: true,
|
||||||
|
payedAmount: 123,
|
||||||
|
planName: SubscriptionPlanName.create('PRO_PLAN').getValue(),
|
||||||
|
})
|
||||||
|
user = User.create({
|
||||||
|
email: Email.create('test@test.te').getValue(),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should create an aggregate for purchased subscription', () => {
|
||||||
|
const revenueModification = RevenueModification.create({
|
||||||
|
eventType: SubscriptionEventType.create('SUBSCRIPTION_PURCHASED').getValue(),
|
||||||
|
previousMonthlyRevenue: MonthlyRevenue.create(123).getValue(),
|
||||||
|
subscription,
|
||||||
|
user,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(revenueModification.id.toString()).toHaveLength(36)
|
||||||
|
expect(revenueModification.newMonthlyRevenue.value).toEqual(123 / 12)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should create an aggregate for subscription expired', () => {
|
||||||
|
const revenueModification = RevenueModification.create({
|
||||||
|
createdAt: new Date(1),
|
||||||
|
eventType: SubscriptionEventType.create('SUBSCRIPTION_EXPIRED').getValue(),
|
||||||
|
previousMonthlyRevenue: MonthlyRevenue.create(123).getValue(),
|
||||||
|
subscription,
|
||||||
|
user,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(revenueModification.id.toString()).toHaveLength(36)
|
||||||
|
expect(revenueModification.newMonthlyRevenue.value).toEqual(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should create an aggregate for subscription cancelled', () => {
|
||||||
|
const revenueModification = RevenueModification.create({
|
||||||
|
eventType: SubscriptionEventType.create('SUBSCRIPTION_CANCELLED').getValue(),
|
||||||
|
previousMonthlyRevenue: MonthlyRevenue.create(123).getValue(),
|
||||||
|
subscription,
|
||||||
|
user,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(revenueModification.id.toString()).toHaveLength(36)
|
||||||
|
expect(revenueModification.newMonthlyRevenue.value).toEqual(123)
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import { Aggregate } from '../Core/Aggregate'
|
||||||
|
import { UniqueEntityId } from '../Core/UniqueEntityId'
|
||||||
|
import { MonthlyRevenue } from './MonthlyRevenue'
|
||||||
|
import { RevenueModificationProps } from './RevenueModificationProps'
|
||||||
|
|
||||||
|
export class RevenueModification extends Aggregate<RevenueModificationProps> {
|
||||||
|
private constructor(props: RevenueModificationProps, id?: UniqueEntityId) {
|
||||||
|
super(props, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
static create(props: RevenueModificationProps, id?: UniqueEntityId): RevenueModification {
|
||||||
|
const revenueModification = new RevenueModification(
|
||||||
|
{
|
||||||
|
...props,
|
||||||
|
createdAt: props.createdAt ? props.createdAt : new Date(),
|
||||||
|
},
|
||||||
|
id,
|
||||||
|
)
|
||||||
|
|
||||||
|
return revenueModification
|
||||||
|
}
|
||||||
|
|
||||||
|
get newMonthlyRevenue(): MonthlyRevenue {
|
||||||
|
const { subscription } = this.props
|
||||||
|
|
||||||
|
let revenue = 0
|
||||||
|
switch (this.props.eventType.value) {
|
||||||
|
case 'SUBSCRIPTION_PURCHASED':
|
||||||
|
case 'SUBSCRIPTION_RENEWED':
|
||||||
|
revenue = subscription.props.payedAmount / subscription.props.billingFrequency
|
||||||
|
break
|
||||||
|
case 'SUBSCRIPTION_EXPIRED':
|
||||||
|
case 'SUBSCRIPTION_REFUNDED':
|
||||||
|
revenue = 0
|
||||||
|
break
|
||||||
|
case 'SUBSCRIPTION_CANCELLED':
|
||||||
|
revenue = this.props.previousMonthlyRevenue.value
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
const monthlyRevenueOrError = MonthlyRevenue.create(revenue)
|
||||||
|
|
||||||
|
return monthlyRevenueOrError.getValue()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { MonthlyRevenue } from './MonthlyRevenue'
|
||||||
|
import { Subscription } from '../Subscription/Subscription'
|
||||||
|
import { User } from '../User/User'
|
||||||
|
import { SubscriptionEventType } from '../Subscription/SubscriptionEventType'
|
||||||
|
|
||||||
|
export interface RevenueModificationProps {
|
||||||
|
user: User
|
||||||
|
subscription: Subscription
|
||||||
|
eventType: SubscriptionEventType
|
||||||
|
previousMonthlyRevenue: MonthlyRevenue
|
||||||
|
createdAt?: Date
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { Uuid } from '../Common/Uuid'
|
||||||
|
import { RevenueModification } from './RevenueModification'
|
||||||
|
|
||||||
|
export interface RevenueModificationRepositoryInterface {
|
||||||
|
findLastByUserUuid(userUuid: Uuid): Promise<RevenueModification | null>
|
||||||
|
save(revenueModification: RevenueModification): Promise<RevenueModification>
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { Subscription } from './Subscription'
|
||||||
|
import { SubscriptionPlanName } from './SubscriptionPlanName'
|
||||||
|
|
||||||
|
describe('Subscription', () => {
|
||||||
|
it('should create an entity', () => {
|
||||||
|
const subscription = Subscription.create({
|
||||||
|
billingFrequency: 1,
|
||||||
|
isFirstSubscriptionForUser: true,
|
||||||
|
payedAmount: 12.99,
|
||||||
|
planName: SubscriptionPlanName.create('PRO_PLAN').getValue(),
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(subscription.id.toString()).toHaveLength(36)
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { Entity } from '../Core/Entity'
|
||||||
|
import { UniqueEntityId } from '../Core/UniqueEntityId'
|
||||||
|
import { SubscriptionProps } from './SubscriptionProps'
|
||||||
|
|
||||||
|
export class Subscription extends Entity<SubscriptionProps> {
|
||||||
|
get id(): UniqueEntityId {
|
||||||
|
return this._id
|
||||||
|
}
|
||||||
|
|
||||||
|
private constructor(props: SubscriptionProps, id?: UniqueEntityId) {
|
||||||
|
super(props, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
static create(props: SubscriptionProps, id?: UniqueEntityId): Subscription {
|
||||||
|
return new Subscription(props, id)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { SubscriptionEventType } from './SubscriptionEventType'
|
||||||
|
|
||||||
|
describe('SubscriptionEventType', () => {
|
||||||
|
it('should create a value object', () => {
|
||||||
|
const valueOrError = SubscriptionEventType.create('SUBSCRIPTION_PURCHASED')
|
||||||
|
|
||||||
|
expect(valueOrError.isFailed()).toBeFalsy()
|
||||||
|
expect(valueOrError.getValue().value).toEqual('SUBSCRIPTION_PURCHASED')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not create an invalid value object', () => {
|
||||||
|
const valueOrError = SubscriptionEventType.create('SUBSCRIPTION_REACTIVATED')
|
||||||
|
|
||||||
|
expect(valueOrError.isFailed()).toBeTruthy()
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { ValueObject } from '../Core/ValueObject'
|
||||||
|
import { Result } from '../Core/Result'
|
||||||
|
import { SubscriptionEventTypeProps } from './SubscriptionEventTypeProps'
|
||||||
|
|
||||||
|
export class SubscriptionEventType extends ValueObject<SubscriptionEventTypeProps> {
|
||||||
|
get value(): string {
|
||||||
|
return this.props.value
|
||||||
|
}
|
||||||
|
|
||||||
|
private constructor(props: SubscriptionEventTypeProps) {
|
||||||
|
super(props)
|
||||||
|
}
|
||||||
|
|
||||||
|
static create(subscriptionEventType: string): Result<SubscriptionEventType> {
|
||||||
|
if (
|
||||||
|
![
|
||||||
|
'SUBSCRIPTION_PURCHASED',
|
||||||
|
'SUBSCRIPTION_RENEWED',
|
||||||
|
'SUBSCRIPTION_EXPIRED',
|
||||||
|
'SUBSCRIPTION_REFUNDED',
|
||||||
|
'SUBSCRIPTION_CANCELLED',
|
||||||
|
].includes(subscriptionEventType)
|
||||||
|
) {
|
||||||
|
return Result.fail<SubscriptionEventType>(`Invalid subscription event type ${subscriptionEventType}`)
|
||||||
|
} else {
|
||||||
|
return Result.ok<SubscriptionEventType>(new SubscriptionEventType({ value: subscriptionEventType }))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export interface SubscriptionEventTypeProps {
|
||||||
|
value: string
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { SubscriptionPlanName } from './SubscriptionPlanName'
|
||||||
|
|
||||||
|
describe('SubscriptionPlanName', () => {
|
||||||
|
it('should create a value object', () => {
|
||||||
|
const valueOrError = SubscriptionPlanName.create('PRO_PLAN')
|
||||||
|
|
||||||
|
expect(valueOrError.isFailed()).toBeFalsy()
|
||||||
|
expect(valueOrError.getValue().value).toEqual('PRO_PLAN')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not create an invalid value object', () => {
|
||||||
|
const valueOrError = SubscriptionPlanName.create('TEST')
|
||||||
|
|
||||||
|
expect(valueOrError.isFailed()).toBeTruthy()
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { ValueObject } from '../Core/ValueObject'
|
||||||
|
import { Result } from '../Core/Result'
|
||||||
|
import { SubscriptionPlanNameProps } from './SubscriptionPlanNameProps'
|
||||||
|
|
||||||
|
export class SubscriptionPlanName extends ValueObject<SubscriptionPlanNameProps> {
|
||||||
|
get value(): string {
|
||||||
|
return this.props.value
|
||||||
|
}
|
||||||
|
|
||||||
|
private constructor(props: SubscriptionPlanNameProps) {
|
||||||
|
super(props)
|
||||||
|
}
|
||||||
|
|
||||||
|
static create(subscriptionPlanName: string): Result<SubscriptionPlanName> {
|
||||||
|
if (!['PRO_PLAN', 'PLUS_PLAN'].includes(subscriptionPlanName)) {
|
||||||
|
return Result.fail<SubscriptionPlanName>(`Invalid subscription plan name ${subscriptionPlanName}`)
|
||||||
|
} else {
|
||||||
|
return Result.ok<SubscriptionPlanName>(new SubscriptionPlanName({ value: subscriptionPlanName }))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export interface SubscriptionPlanNameProps {
|
||||||
|
value: string
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import { SubscriptionPlanName } from '../Subscription/SubscriptionPlanName'
|
||||||
|
|
||||||
|
export interface SubscriptionProps {
|
||||||
|
planName: SubscriptionPlanName
|
||||||
|
isFirstSubscriptionForUser: boolean
|
||||||
|
payedAmount: number
|
||||||
|
billingFrequency: number
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import { Result } from '../Core/Result'
|
||||||
|
|
||||||
|
export interface DomainUseCaseInterface<T> {
|
||||||
|
execute(...args: any[]): Promise<Result<T>>
|
||||||
|
}
|
||||||
@@ -12,7 +12,11 @@ describe('GetUserAnalyticsId', () => {
|
|||||||
const createUseCase = () => new GetUserAnalyticsId(analyticsEntityRepository)
|
const createUseCase = () => new GetUserAnalyticsId(analyticsEntityRepository)
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
analyticsEntity = { id: 123 } as jest.Mocked<AnalyticsEntity>
|
analyticsEntity = {
|
||||||
|
id: 123,
|
||||||
|
userUuid: '1-2-3',
|
||||||
|
userEmail: 'test@test.te',
|
||||||
|
} as jest.Mocked<AnalyticsEntity>
|
||||||
|
|
||||||
analyticsEntityRepository = {} as jest.Mocked<AnalyticsEntityRepositoryInterface>
|
analyticsEntityRepository = {} as jest.Mocked<AnalyticsEntityRepositoryInterface>
|
||||||
analyticsEntityRepository.findOneByUserUuid = jest.fn().mockReturnValue(analyticsEntity)
|
analyticsEntityRepository.findOneByUserUuid = jest.fn().mockReturnValue(analyticsEntity)
|
||||||
@@ -20,11 +24,11 @@ describe('GetUserAnalyticsId', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should return analytics id for a user by uuid', async () => {
|
it('should return analytics id for a user by uuid', async () => {
|
||||||
expect(await createUseCase().execute({ userUuid: '1-2-3' })).toEqual({ analyticsId: 123 })
|
expect(await (await createUseCase().execute({ userUuid: '1-2-3' })).analyticsId).toEqual(123)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should return analytics id for a user by email', async () => {
|
it('should return analytics id for a user by email', async () => {
|
||||||
expect(await createUseCase().execute({ userEmail: 'test@test.te' })).toEqual({ analyticsId: 123 })
|
expect(await (await createUseCase().execute({ userEmail: 'test@test.te' })).analyticsId).toEqual(123)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should throw error if user is missing analytics entity', async () => {
|
it('should throw error if user is missing analytics entity', async () => {
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { inject, injectable } from 'inversify'
|
import { inject, injectable } from 'inversify'
|
||||||
import TYPES from '../../../Bootstrap/Types'
|
import TYPES from '../../../Bootstrap/Types'
|
||||||
|
import { Email } from '../../Common/Email'
|
||||||
|
import { Uuid } from '../../Common/Uuid'
|
||||||
import { AnalyticsEntityRepositoryInterface } from '../../Entity/AnalyticsEntityRepositoryInterface'
|
import { AnalyticsEntityRepositoryInterface } from '../../Entity/AnalyticsEntityRepositoryInterface'
|
||||||
import { UseCaseInterface } from '../UseCaseInterface'
|
import { UseCaseInterface } from '../UseCaseInterface'
|
||||||
import { GetUserAnalyticsIdDTO } from './GetUserAnalyticsIdDTO'
|
import { GetUserAnalyticsIdDTO } from './GetUserAnalyticsIdDTO'
|
||||||
@@ -25,6 +27,8 @@ export class GetUserAnalyticsId implements UseCaseInterface {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
analyticsId: analyticsEntity.id,
|
analyticsId: analyticsEntity.id,
|
||||||
|
userUuid: Uuid.create(analyticsEntity.userUuid).getValue(),
|
||||||
|
userEmail: Email.create(analyticsEntity.userEmail).getValue(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
import { Email } from '../../Common/Email'
|
||||||
|
import { Uuid } from '../../Common/Uuid'
|
||||||
|
|
||||||
export type GetUserAnalyticsIdResponse = {
|
export type GetUserAnalyticsIdResponse = {
|
||||||
analyticsId: number
|
analyticsId: number
|
||||||
|
userEmail: Email
|
||||||
|
userUuid: Uuid
|
||||||
}
|
}
|
||||||
|
|||||||
+44
@@ -0,0 +1,44 @@
|
|||||||
|
import 'reflect-metadata'
|
||||||
|
|
||||||
|
import { Email } from '../../Common/Email'
|
||||||
|
import { Uuid } from '../../Common/Uuid'
|
||||||
|
import { MonthlyRevenue } from '../../Revenue/MonthlyRevenue'
|
||||||
|
|
||||||
|
import { RevenueModification } from '../../Revenue/RevenueModification'
|
||||||
|
import { RevenueModificationRepositoryInterface } from '../../Revenue/RevenueModificationRepositoryInterface'
|
||||||
|
import { SubscriptionEventType } from '../../Subscription/SubscriptionEventType'
|
||||||
|
import { SubscriptionPlanName } from '../../Subscription/SubscriptionPlanName'
|
||||||
|
import { SaveRevenueModification } from './SaveRevenueModification'
|
||||||
|
|
||||||
|
describe('SaveRevenueModification', () => {
|
||||||
|
let revenueModificationRepository: RevenueModificationRepositoryInterface
|
||||||
|
let previousMonthlyRevenue: RevenueModification
|
||||||
|
|
||||||
|
const createUseCase = () => new SaveRevenueModification(revenueModificationRepository)
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
previousMonthlyRevenue = {
|
||||||
|
newMonthlyRevenue: MonthlyRevenue.create(2).getValue(),
|
||||||
|
} as jest.Mocked<RevenueModification>
|
||||||
|
|
||||||
|
revenueModificationRepository = {} as jest.Mocked<RevenueModificationRepositoryInterface>
|
||||||
|
revenueModificationRepository.findLastByUserUuid = jest.fn().mockReturnValue(previousMonthlyRevenue)
|
||||||
|
revenueModificationRepository.save = jest.fn()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should persist a revenue modification', async () => {
|
||||||
|
const revenueOrError = await createUseCase().execute({
|
||||||
|
billingFrequency: 1,
|
||||||
|
eventType: SubscriptionEventType.create('SUBSCRIPTION_PURCHASED').getValue(),
|
||||||
|
newSubscriber: true,
|
||||||
|
payedAmount: 12.99,
|
||||||
|
planName: SubscriptionPlanName.create('PRO_PLAN').getValue(),
|
||||||
|
subscriptionId: 1234,
|
||||||
|
userEmail: Email.create('test@test.te').getValue(),
|
||||||
|
userUuid: Uuid.create('1-2-3').getValue(),
|
||||||
|
})
|
||||||
|
expect(revenueOrError.isFailed()).toBeFalsy()
|
||||||
|
const revenue = revenueOrError.getValue()
|
||||||
|
expect(revenue.newMonthlyRevenue.value).toEqual(12.99)
|
||||||
|
})
|
||||||
|
})
|
||||||
+54
@@ -0,0 +1,54 @@
|
|||||||
|
import { inject, injectable } from 'inversify'
|
||||||
|
import TYPES from '../../../Bootstrap/Types'
|
||||||
|
import { UniqueEntityId } from '../../Core/UniqueEntityId'
|
||||||
|
import { MonthlyRevenue } from '../../Revenue/MonthlyRevenue'
|
||||||
|
import { RevenueModification } from '../../Revenue/RevenueModification'
|
||||||
|
import { RevenueModificationRepositoryInterface } from '../../Revenue/RevenueModificationRepositoryInterface'
|
||||||
|
import { Subscription } from '../../Subscription/Subscription'
|
||||||
|
import { User } from '../../User/User'
|
||||||
|
import { Result } from '../../Core/Result'
|
||||||
|
import { DomainUseCaseInterface } from '../DomainUseCaseInterface'
|
||||||
|
import { SaveRevenueModificationDTO } from './SaveRevenueModificationDTO'
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class SaveRevenueModification implements DomainUseCaseInterface<RevenueModification> {
|
||||||
|
constructor(
|
||||||
|
@inject(TYPES.RevenueModificationRepository)
|
||||||
|
private revenueModificationRepository: RevenueModificationRepositoryInterface,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async execute(dto: SaveRevenueModificationDTO): Promise<Result<RevenueModification>> {
|
||||||
|
const user = User.create(
|
||||||
|
{
|
||||||
|
email: dto.userEmail,
|
||||||
|
},
|
||||||
|
new UniqueEntityId(dto.userUuid.value),
|
||||||
|
)
|
||||||
|
const subscription = Subscription.create(
|
||||||
|
{
|
||||||
|
isFirstSubscriptionForUser: dto.newSubscriber,
|
||||||
|
payedAmount: dto.payedAmount,
|
||||||
|
planName: dto.planName,
|
||||||
|
billingFrequency: dto.billingFrequency,
|
||||||
|
},
|
||||||
|
new UniqueEntityId(dto.subscriptionId),
|
||||||
|
)
|
||||||
|
|
||||||
|
let previousMonthlyRevenue = MonthlyRevenue.create(0).getValue()
|
||||||
|
const previousRevenueModification = await this.revenueModificationRepository.findLastByUserUuid(dto.userUuid)
|
||||||
|
if (previousRevenueModification !== null) {
|
||||||
|
previousMonthlyRevenue = previousRevenueModification.newMonthlyRevenue
|
||||||
|
}
|
||||||
|
|
||||||
|
const revenueModification = RevenueModification.create({
|
||||||
|
eventType: dto.eventType,
|
||||||
|
subscription,
|
||||||
|
user,
|
||||||
|
previousMonthlyRevenue,
|
||||||
|
})
|
||||||
|
|
||||||
|
await this.revenueModificationRepository.save(revenueModification)
|
||||||
|
|
||||||
|
return Result.ok<RevenueModification>(revenueModification)
|
||||||
|
}
|
||||||
|
}
|
||||||
+15
@@ -0,0 +1,15 @@
|
|||||||
|
import { Email } from '../../Common/Email'
|
||||||
|
import { Uuid } from '../../Common/Uuid'
|
||||||
|
import { SubscriptionEventType } from '../../Subscription/SubscriptionEventType'
|
||||||
|
import { SubscriptionPlanName } from '../../Subscription/SubscriptionPlanName'
|
||||||
|
|
||||||
|
export interface SaveRevenueModificationDTO {
|
||||||
|
eventType: SubscriptionEventType
|
||||||
|
payedAmount: number
|
||||||
|
planName: SubscriptionPlanName
|
||||||
|
newSubscriber: boolean
|
||||||
|
userUuid: Uuid
|
||||||
|
userEmail: Email
|
||||||
|
subscriptionId: number
|
||||||
|
billingFrequency: number
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { Email } from '../Common/Email'
|
||||||
|
import { User } from './User'
|
||||||
|
|
||||||
|
describe('User', () => {
|
||||||
|
it('should create an entity', () => {
|
||||||
|
const user = User.create({
|
||||||
|
email: Email.create('test@test.te').getValue(),
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(user.id.toString()).toHaveLength(36)
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { Entity } from '../Core/Entity'
|
||||||
|
import { UniqueEntityId } from '../Core/UniqueEntityId'
|
||||||
|
import { UserProps } from './UserProps'
|
||||||
|
|
||||||
|
export class User extends Entity<UserProps> {
|
||||||
|
get id(): UniqueEntityId {
|
||||||
|
return this._id
|
||||||
|
}
|
||||||
|
|
||||||
|
private constructor(props: UserProps, id?: UniqueEntityId) {
|
||||||
|
super(props, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
public static create(props: UserProps, id?: UniqueEntityId): User {
|
||||||
|
return new User(props, id)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import { Email } from '../Common/Email'
|
||||||
|
|
||||||
|
export interface UserProps {
|
||||||
|
email: Email
|
||||||
|
}
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
import 'reflect-metadata'
|
|
||||||
|
|
||||||
import { Repository, SelectQueryBuilder } from 'typeorm'
|
|
||||||
|
|
||||||
import { AnalyticsEntity } from '../../Domain/Entity/AnalyticsEntity'
|
|
||||||
|
|
||||||
import { MySQLAnalyticsEntityRepository } from './MySQLAnalyticsEntityRepository'
|
|
||||||
|
|
||||||
describe('MySQLAnalyticsEntityRepository', () => {
|
|
||||||
let ormRepository: Repository<AnalyticsEntity>
|
|
||||||
let analyticsEntity: AnalyticsEntity
|
|
||||||
let queryBuilder: SelectQueryBuilder<AnalyticsEntity>
|
|
||||||
|
|
||||||
const createRepository = () => new MySQLAnalyticsEntityRepository(ormRepository)
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
analyticsEntity = {} as jest.Mocked<AnalyticsEntity>
|
|
||||||
|
|
||||||
queryBuilder = {} as jest.Mocked<SelectQueryBuilder<AnalyticsEntity>>
|
|
||||||
|
|
||||||
ormRepository = {} as jest.Mocked<Repository<AnalyticsEntity>>
|
|
||||||
ormRepository.save = jest.fn()
|
|
||||||
ormRepository.remove = jest.fn()
|
|
||||||
ormRepository.createQueryBuilder = jest.fn().mockImplementation(() => queryBuilder)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should save', async () => {
|
|
||||||
await createRepository().save(analyticsEntity)
|
|
||||||
|
|
||||||
expect(ormRepository.save).toHaveBeenCalledWith(analyticsEntity)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should remove', async () => {
|
|
||||||
await createRepository().remove(analyticsEntity)
|
|
||||||
|
|
||||||
expect(ormRepository.remove).toHaveBeenCalledWith(analyticsEntity)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should find one by user uuid', async () => {
|
|
||||||
queryBuilder.where = jest.fn().mockReturnThis()
|
|
||||||
queryBuilder.getOne = jest.fn().mockReturnValue(analyticsEntity)
|
|
||||||
|
|
||||||
const result = await createRepository().findOneByUserUuid('123')
|
|
||||||
|
|
||||||
expect(queryBuilder.where).toHaveBeenCalledWith('analytics_entity.user_uuid = :userUuid', { userUuid: '123' })
|
|
||||||
|
|
||||||
expect(result).toEqual(analyticsEntity)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should find one by user email', async () => {
|
|
||||||
queryBuilder.where = jest.fn().mockReturnThis()
|
|
||||||
queryBuilder.getOne = jest.fn().mockReturnValue(analyticsEntity)
|
|
||||||
|
|
||||||
const result = await createRepository().findOneByUserEmail('test@test.te')
|
|
||||||
|
|
||||||
expect(queryBuilder.where).toHaveBeenCalledWith('analytics_entity.user_email = :email', { email: 'test@test.te' })
|
|
||||||
|
|
||||||
expect(result).toEqual(analyticsEntity)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import { inject, injectable } from 'inversify'
|
||||||
|
import { Repository } from 'typeorm'
|
||||||
|
|
||||||
|
import TYPES from '../../Bootstrap/Types'
|
||||||
|
import { Uuid } from '../../Domain/Common/Uuid'
|
||||||
|
import { MapInterface } from '../../Domain/Map/MapInterface'
|
||||||
|
import { RevenueModification } from '../../Domain/Revenue/RevenueModification'
|
||||||
|
import { RevenueModificationRepositoryInterface } from '../../Domain/Revenue/RevenueModificationRepositoryInterface'
|
||||||
|
import { TypeORMRevenueModification } from '../TypeORM/TypeORMRevenueModification'
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class MySQLRevenueModificationRepository implements RevenueModificationRepositoryInterface {
|
||||||
|
constructor(
|
||||||
|
@inject(TYPES.ORMRevenueModificationRepository)
|
||||||
|
private ormRepository: Repository<TypeORMRevenueModification>,
|
||||||
|
@inject(TYPES.RevenueModificationMap)
|
||||||
|
private revenueModificationMap: MapInterface<RevenueModification, TypeORMRevenueModification>,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async findLastByUserUuid(userUuid: Uuid): Promise<RevenueModification | null> {
|
||||||
|
const persistence = await this.ormRepository
|
||||||
|
.createQueryBuilder()
|
||||||
|
.where('user_uuid = :userUuid', { userUuid: userUuid.value })
|
||||||
|
.orderBy('created_at', 'DESC')
|
||||||
|
.limit(1)
|
||||||
|
.getOne()
|
||||||
|
|
||||||
|
if (persistence === null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.revenueModificationMap.toDomain(persistence)
|
||||||
|
}
|
||||||
|
|
||||||
|
async save(revenueModification: RevenueModification): Promise<RevenueModification> {
|
||||||
|
let persistence = this.revenueModificationMap.toPersistence(revenueModification)
|
||||||
|
|
||||||
|
persistence = await this.ormRepository.save(persistence)
|
||||||
|
|
||||||
|
return this.revenueModificationMap.toDomain(persistence)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
import { Column, Entity, Index, PrimaryColumn } from 'typeorm'
|
||||||
|
|
||||||
|
@Entity({ name: 'revenue_modifications' })
|
||||||
|
export class TypeORMRevenueModification {
|
||||||
|
@PrimaryColumn({
|
||||||
|
type: 'uuid',
|
||||||
|
length: 36,
|
||||||
|
})
|
||||||
|
declare uuid: string
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
name: 'subscription_id',
|
||||||
|
})
|
||||||
|
declare subscriptionId: number
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
name: 'user_email',
|
||||||
|
length: 255,
|
||||||
|
})
|
||||||
|
@Index('email')
|
||||||
|
declare userEmail: string
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
name: 'user_uuid',
|
||||||
|
length: 36,
|
||||||
|
})
|
||||||
|
@Index('user_uuid')
|
||||||
|
declare userUuid: string
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
name: 'event_type',
|
||||||
|
})
|
||||||
|
declare eventType: string
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
name: 'subscription_plan',
|
||||||
|
})
|
||||||
|
declare subscriptionPlan: string
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
name: 'billing_frequency',
|
||||||
|
})
|
||||||
|
declare billingFrequency: number
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
name: 'new_customer',
|
||||||
|
})
|
||||||
|
declare isNewCustomer: boolean
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
name: 'previous_mrr',
|
||||||
|
})
|
||||||
|
declare previousMonthlyRevenue: number
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
name: 'new_mrr',
|
||||||
|
})
|
||||||
|
declare newMonthlyRevenue: number
|
||||||
|
}
|
||||||
@@ -3,6 +3,18 @@
|
|||||||
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.37.3](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.37.2...@standardnotes/api-gateway@1.37.3) (2022-11-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||||
|
|
||||||
|
## [1.37.2](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.37.1...@standardnotes/api-gateway@1.37.2) (2022-11-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||||
|
|
||||||
|
## [1.37.1](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.37.0...@standardnotes/api-gateway@1.37.1) (2022-11-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||||
|
|
||||||
# [1.37.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.36.14...@standardnotes/api-gateway@1.37.0) (2022-11-07)
|
# [1.37.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.36.14...@standardnotes/api-gateway@1.37.0) (2022-11-07)
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/api-gateway",
|
"name": "@standardnotes/api-gateway",
|
||||||
"version": "1.37.0",
|
"version": "1.37.3",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16.0.0 <17.0.0"
|
"node": ">=16.0.0 <17.0.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,6 +3,30 @@
|
|||||||
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.57.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.56.0...@standardnotes/auth-server@1.57.0) (2022-11-09)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **analytics:** add saving revenue modifications upon subscription expired ([5c3db2c](https://github.com/standardnotes/server/commit/5c3db2cb29a929e44b63eb8226ce4ad1d14f8a99))
|
||||||
|
|
||||||
|
# [1.56.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.55.0...@standardnotes/auth-server@1.56.0) (2022-11-09)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **analytics:** add saving revenue modifications upon subscription renewed ([cdb7fcf](https://github.com/standardnotes/server/commit/cdb7fcf8311fecfabe3ef9eb656cd6ec57b87de0))
|
||||||
|
|
||||||
|
# [1.55.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.54.0...@standardnotes/auth-server@1.55.0) (2022-11-09)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **analytics:** add saving revenue modifications upon subscription purchased ([5ea9941](https://github.com/standardnotes/server/commit/5ea9941519ffb3027527130ec869da14abc5e994))
|
||||||
|
|
||||||
|
# [1.54.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.53.0...@standardnotes/auth-server@1.54.0) (2022-11-07)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **auth:** remove analytics table in favor of analytics service ([49feadd](https://github.com/standardnotes/server/commit/49feadd32a5fc8994a1b63f5293d41ca60f01e02))
|
||||||
|
|
||||||
# [1.53.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.52.1...@standardnotes/auth-server@1.53.0) (2022-11-07)
|
# [1.53.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.52.1...@standardnotes/auth-server@1.53.0) (2022-11-07)
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { MigrationInterface, QueryRunner } from 'typeorm'
|
||||||
|
|
||||||
|
export class removeAnalytics1667818539829 implements MigrationInterface {
|
||||||
|
name = 'removeAnalytics1667818539829'
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query('ALTER TABLE `analytics_entities` DROP FOREIGN KEY `FK_d2717c4ce2600b9f7acb6b378c5`')
|
||||||
|
await queryRunner.query('DROP INDEX `REL_d2717c4ce2600b9f7acb6b378c` ON `analytics_entities`')
|
||||||
|
await queryRunner.query('DROP TABLE `analytics_entities`')
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(): Promise<void> {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/auth-server",
|
"name": "@standardnotes/auth-server",
|
||||||
"version": "1.53.0",
|
"version": "1.57.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16.0.0 <17.0.0"
|
"node": ">=16.0.0 <17.0.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -72,6 +72,9 @@ describe('SubscriptionExpiredEventHandler', () => {
|
|||||||
timestamp,
|
timestamp,
|
||||||
offline: false,
|
offline: false,
|
||||||
totalActiveSubscriptionsCount: 123,
|
totalActiveSubscriptionsCount: 123,
|
||||||
|
userExistingSubscriptionsCount: 2,
|
||||||
|
billingFrequency: 1,
|
||||||
|
payAmount: 12.99,
|
||||||
}
|
}
|
||||||
|
|
||||||
logger = {} as jest.Mocked<Logger>
|
logger = {} as jest.Mocked<Logger>
|
||||||
|
|||||||
@@ -90,6 +90,8 @@ describe('SubscriptionPurchasedEventHandler', () => {
|
|||||||
newSubscriber: true,
|
newSubscriber: true,
|
||||||
totalActiveSubscriptionsCount: 123,
|
totalActiveSubscriptionsCount: 123,
|
||||||
userRegisteredAt: dayjs.utc().valueOf() - 23,
|
userRegisteredAt: dayjs.utc().valueOf() - 23,
|
||||||
|
billingFrequency: 12,
|
||||||
|
payAmount: 29.99,
|
||||||
}
|
}
|
||||||
|
|
||||||
subscriptionSettingService = {} as jest.Mocked<SubscriptionSettingServiceInterface>
|
subscriptionSettingService = {} as jest.Mocked<SubscriptionSettingServiceInterface>
|
||||||
|
|||||||
@@ -81,6 +81,8 @@ describe('SubscriptionRenewedEventHandler', () => {
|
|||||||
subscriptionExpiresAt,
|
subscriptionExpiresAt,
|
||||||
timestamp,
|
timestamp,
|
||||||
offline: false,
|
offline: false,
|
||||||
|
billingFrequency: 1,
|
||||||
|
payAmount: 12.99,
|
||||||
}
|
}
|
||||||
|
|
||||||
logger = {} as jest.Mocked<Logger>
|
logger = {} as jest.Mocked<Logger>
|
||||||
|
|||||||
@@ -3,6 +3,18 @@
|
|||||||
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.16](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.9.15...@standardnotes/domain-events-infra@1.9.16) (2022-11-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/domain-events-infra
|
||||||
|
|
||||||
|
## [1.9.15](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.9.14...@standardnotes/domain-events-infra@1.9.15) (2022-11-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/domain-events-infra
|
||||||
|
|
||||||
|
## [1.9.14](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.9.13...@standardnotes/domain-events-infra@1.9.14) (2022-11-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/domain-events-infra
|
||||||
|
|
||||||
## [1.9.13](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.9.12...@standardnotes/domain-events-infra@1.9.13) (2022-11-07)
|
## [1.9.13](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.9.12...@standardnotes/domain-events-infra@1.9.13) (2022-11-07)
|
||||||
|
|
||||||
**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.13",
|
"version": "1.9.16",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16.0.0 <17.0.0"
|
"node": ">=16.0.0 <17.0.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,6 +3,24 @@
|
|||||||
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.81.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.80.0...@standardnotes/domain-events@2.81.0) (2022-11-09)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **analytics:** add saving revenue modifications upon subscription expired ([5c3db2c](https://github.com/standardnotes/server/commit/5c3db2cb29a929e44b63eb8226ce4ad1d14f8a99))
|
||||||
|
|
||||||
|
# [2.80.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.79.0...@standardnotes/domain-events@2.80.0) (2022-11-09)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **analytics:** add saving revenue modifications upon subscription renewed ([cdb7fcf](https://github.com/standardnotes/server/commit/cdb7fcf8311fecfabe3ef9eb656cd6ec57b87de0))
|
||||||
|
|
||||||
|
# [2.79.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.78.1...@standardnotes/domain-events@2.79.0) (2022-11-09)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **analytics:** add saving revenue modifications upon subscription purchased ([5ea9941](https://github.com/standardnotes/server/commit/5ea9941519ffb3027527130ec869da14abc5e994))
|
||||||
|
|
||||||
## [2.78.1](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.78.0...@standardnotes/domain-events@2.78.1) (2022-11-07)
|
## [2.78.1](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.78.0...@standardnotes/domain-events@2.78.1) (2022-11-07)
|
||||||
|
|
||||||
**Note:** Version bump only for package @standardnotes/domain-events
|
**Note:** Version bump only for package @standardnotes/domain-events
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/domain-events",
|
"name": "@standardnotes/domain-events",
|
||||||
"version": "2.78.1",
|
"version": "2.81.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16.0.0 <17.0.0"
|
"node": ">=16.0.0 <17.0.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -7,4 +7,7 @@ export interface SubscriptionExpiredEventPayload {
|
|||||||
timestamp: number
|
timestamp: number
|
||||||
offline: boolean
|
offline: boolean
|
||||||
totalActiveSubscriptionsCount: number
|
totalActiveSubscriptionsCount: number
|
||||||
|
userExistingSubscriptionsCount: number
|
||||||
|
billingFrequency: number
|
||||||
|
payAmount: number
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,4 +12,6 @@ export interface SubscriptionPurchasedEventPayload {
|
|||||||
newSubscriber: boolean
|
newSubscriber: boolean
|
||||||
totalActiveSubscriptionsCount: number
|
totalActiveSubscriptionsCount: number
|
||||||
userRegisteredAt: number
|
userRegisteredAt: number
|
||||||
|
billingFrequency: number
|
||||||
|
payAmount: number
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,4 +7,6 @@ export interface SubscriptionRenewedEventPayload {
|
|||||||
subscriptionExpiresAt: number
|
subscriptionExpiresAt: number
|
||||||
timestamp: number
|
timestamp: number
|
||||||
offline: boolean
|
offline: boolean
|
||||||
|
billingFrequency: number
|
||||||
|
payAmount: number
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,18 @@
|
|||||||
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.11](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.6.10...@standardnotes/event-store@1.6.11) (2022-11-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/event-store
|
||||||
|
|
||||||
|
## [1.6.10](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.6.9...@standardnotes/event-store@1.6.10) (2022-11-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/event-store
|
||||||
|
|
||||||
|
## [1.6.9](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.6.8...@standardnotes/event-store@1.6.9) (2022-11-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/event-store
|
||||||
|
|
||||||
## [1.6.8](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.6.7...@standardnotes/event-store@1.6.8) (2022-11-07)
|
## [1.6.8](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.6.7...@standardnotes/event-store@1.6.8) (2022-11-07)
|
||||||
|
|
||||||
**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.8",
|
"version": "1.6.11",
|
||||||
"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,18 @@
|
|||||||
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.11](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.8.10...@standardnotes/files-server@1.8.11) (2022-11-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/files-server
|
||||||
|
|
||||||
|
## [1.8.10](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.8.9...@standardnotes/files-server@1.8.10) (2022-11-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/files-server
|
||||||
|
|
||||||
|
## [1.8.9](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.8.8...@standardnotes/files-server@1.8.9) (2022-11-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/files-server
|
||||||
|
|
||||||
## [1.8.8](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.8.7...@standardnotes/files-server@1.8.8) (2022-11-07)
|
## [1.8.8](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.8.7...@standardnotes/files-server@1.8.8) (2022-11-07)
|
||||||
|
|
||||||
**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.8",
|
"version": "1.8.11",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16.0.0 <17.0.0"
|
"node": ">=16.0.0 <17.0.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,6 +3,18 @@
|
|||||||
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.13.12](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.13.11...@standardnotes/scheduler-server@1.13.12) (2022-11-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/scheduler-server
|
||||||
|
|
||||||
|
## [1.13.11](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.13.10...@standardnotes/scheduler-server@1.13.11) (2022-11-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/scheduler-server
|
||||||
|
|
||||||
|
## [1.13.10](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.13.9...@standardnotes/scheduler-server@1.13.10) (2022-11-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/scheduler-server
|
||||||
|
|
||||||
## [1.13.9](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.13.8...@standardnotes/scheduler-server@1.13.9) (2022-11-07)
|
## [1.13.9](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.13.8...@standardnotes/scheduler-server@1.13.9) (2022-11-07)
|
||||||
|
|
||||||
**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.13.9",
|
"version": "1.13.12",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16.0.0 <17.0.0"
|
"node": ">=16.0.0 <17.0.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,6 +3,18 @@
|
|||||||
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.11.3](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.11.2...@standardnotes/syncing-server@1.11.3) (2022-11-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/syncing-server
|
||||||
|
|
||||||
|
## [1.11.2](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.11.1...@standardnotes/syncing-server@1.11.2) (2022-11-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/syncing-server
|
||||||
|
|
||||||
|
## [1.11.1](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.11.0...@standardnotes/syncing-server@1.11.1) (2022-11-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/syncing-server
|
||||||
|
|
||||||
# [1.11.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.10.25...@standardnotes/syncing-server@1.11.0) (2022-11-07)
|
# [1.11.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.10.25...@standardnotes/syncing-server@1.11.0) (2022-11-07)
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/syncing-server",
|
"name": "@standardnotes/syncing-server",
|
||||||
"version": "1.11.0",
|
"version": "1.11.3",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16.0.0 <17.0.0"
|
"node": ">=16.0.0 <17.0.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,6 +3,18 @@
|
|||||||
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.4.11](https://github.com/standardnotes/server/compare/@standardnotes/websockets-server@1.4.10...@standardnotes/websockets-server@1.4.11) (2022-11-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/websockets-server
|
||||||
|
|
||||||
|
## [1.4.10](https://github.com/standardnotes/server/compare/@standardnotes/websockets-server@1.4.9...@standardnotes/websockets-server@1.4.10) (2022-11-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/websockets-server
|
||||||
|
|
||||||
|
## [1.4.9](https://github.com/standardnotes/server/compare/@standardnotes/websockets-server@1.4.8...@standardnotes/websockets-server@1.4.9) (2022-11-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/websockets-server
|
||||||
|
|
||||||
## [1.4.8](https://github.com/standardnotes/server/compare/@standardnotes/websockets-server@1.4.7...@standardnotes/websockets-server@1.4.8) (2022-11-07)
|
## [1.4.8](https://github.com/standardnotes/server/compare/@standardnotes/websockets-server@1.4.7...@standardnotes/websockets-server@1.4.8) (2022-11-07)
|
||||||
|
|
||||||
**Note:** Version bump only for package @standardnotes/websockets-server
|
**Note:** Version bump only for package @standardnotes/websockets-server
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/websockets-server",
|
"name": "@standardnotes/websockets-server",
|
||||||
"version": "1.4.8",
|
"version": "1.4.11",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16.0.0 <17.0.0"
|
"node": ">=16.0.0 <17.0.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,6 +3,18 @@
|
|||||||
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.17.11](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.17.10...@standardnotes/workspace-server@1.17.11) (2022-11-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/workspace-server
|
||||||
|
|
||||||
|
## [1.17.10](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.17.9...@standardnotes/workspace-server@1.17.10) (2022-11-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/workspace-server
|
||||||
|
|
||||||
|
## [1.17.9](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.17.8...@standardnotes/workspace-server@1.17.9) (2022-11-09)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/workspace-server
|
||||||
|
|
||||||
## [1.17.8](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.17.7...@standardnotes/workspace-server@1.17.8) (2022-11-07)
|
## [1.17.8](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.17.7...@standardnotes/workspace-server@1.17.8) (2022-11-07)
|
||||||
|
|
||||||
**Note:** Version bump only for package @standardnotes/workspace-server
|
**Note:** Version bump only for package @standardnotes/workspace-server
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/workspace-server",
|
"name": "@standardnotes/workspace-server",
|
||||||
"version": "1.17.8",
|
"version": "1.17.11",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16.0.0 <17.0.0"
|
"node": ">=16.0.0 <17.0.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1815,6 +1815,7 @@ __metadata:
|
|||||||
"@types/jest": "npm:^29.1.1"
|
"@types/jest": "npm:^29.1.1"
|
||||||
"@types/newrelic": "npm:^7.0.3"
|
"@types/newrelic": "npm:^7.0.3"
|
||||||
"@types/node": "npm:^18.0.0"
|
"@types/node": "npm:^18.0.0"
|
||||||
|
"@types/uuid": "npm:^8.3.0"
|
||||||
"@typescript-eslint/eslint-plugin": "npm:^5.30.0"
|
"@typescript-eslint/eslint-plugin": "npm:^5.30.0"
|
||||||
aws-sdk: "npm:^2.1158.0"
|
aws-sdk: "npm:^2.1158.0"
|
||||||
dayjs: "npm:^1.11.6"
|
dayjs: "npm:^1.11.6"
|
||||||
@@ -1827,9 +1828,11 @@ __metadata:
|
|||||||
mysql2: "npm:^2.3.3"
|
mysql2: "npm:^2.3.3"
|
||||||
newrelic: "npm:^9.0.0"
|
newrelic: "npm:^9.0.0"
|
||||||
reflect-metadata: "npm:^0.1.13"
|
reflect-metadata: "npm:^0.1.13"
|
||||||
|
shallow-equal-object: "npm:^1.1.1"
|
||||||
ts-jest: "npm:^29.0.3"
|
ts-jest: "npm:^29.0.3"
|
||||||
typeorm: "npm:^0.3.6"
|
typeorm: "npm:^0.3.6"
|
||||||
typescript: "npm:^4.8.4"
|
typescript: "npm:^4.8.4"
|
||||||
|
uuid: "npm:^9.0.0"
|
||||||
winston: "npm:^3.8.1"
|
winston: "npm:^3.8.1"
|
||||||
languageName: unknown
|
languageName: unknown
|
||||||
linkType: soft
|
linkType: soft
|
||||||
@@ -9969,6 +9972,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"shallow-equal-object@npm:^1.1.1":
|
||||||
|
version: 1.1.1
|
||||||
|
resolution: "shallow-equal-object@npm:1.1.1"
|
||||||
|
checksum: 9e5e0cd10ba5447f85038d7b104e66c15603e164b2112366f044f9447512bfb6f0b71bd9869e76824e76fae76568e94df3d9871bf5af8ab2ff78eee9487baecf
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"shebang-command@npm:^2.0.0":
|
"shebang-command@npm:^2.0.0":
|
||||||
version: 2.0.0
|
version: 2.0.0
|
||||||
resolution: "shebang-command@npm:2.0.0"
|
resolution: "shebang-command@npm:2.0.0"
|
||||||
|
|||||||
Reference in New Issue
Block a user