Compare commits

...

19 Commits

Author SHA1 Message Date
standardci
268fed19f9 chore(release): publish new version
- @standardnotes/analytics@1.9.0
 - @standardnotes/api-gateway@1.6.30
 - @standardnotes/auth-server@1.12.0
 - @standardnotes/domain-events-infra@1.7.28
 - @standardnotes/domain-events@2.51.0
 - @standardnotes/event-store@1.1.27
 - @standardnotes/files-server@1.5.28
 - @standardnotes/scheduler-server@1.10.0
 - @standardnotes/syncing-server@1.6.30
2022-07-26 11:54:06 +00:00
Karol Sójko
04bf414de4 feat(scheduler): enable discount applying and withdraw for everyone 2022-07-26 13:52:34 +02:00
Karol Sójko
28e1c65631 feat(domain-events): add discount code to subscription purchased event 2022-07-26 13:52:34 +02:00
Karol Sójko
e936ac4ce1 feat(auth): add analytics for purchased subscription with a limited discount offer 2022-07-26 13:52:34 +02:00
Karol Sójko
13201e7a9e feat(analytics): add limited discount offer purchased activity 2022-07-26 13:52:34 +02:00
standardci
9740b28764 chore(release): publish new version
- @standardnotes/scheduler-server@1.9.2
2022-07-26 10:37:12 +00:00
Karol Sójko
1fa94efa02 fix(scheduler): change the discount code to an absolute discount 2022-07-26 12:35:16 +02:00
standardci
44172e1a8e chore(release): publish new version
- @standardnotes/scheduler-server@1.9.1
2022-07-26 07:25:30 +00:00
Karol Sójko
4ab0d24d24 fix(scheduler): eliminate read/write concurrency hazzard while updating predicate status 2022-07-26 09:23:45 +02:00
standardci
049e66770a chore(release): publish new version
- @standardnotes/scheduler-server@1.9.0
2022-07-25 18:45:09 +00:00
Karol Sójko
bf12687f63 feat(scheduler): add job interpreting logs 2022-07-25 20:43:18 +02:00
standardci
10389d9029 chore(release): publish new version
- @standardnotes/auth-server@1.11.31
2022-07-25 18:34:13 +00:00
Karol Sójko
40996f9d48 fix(auth): marking predicate verification result if user is not existing 2022-07-25 20:32:40 +02:00
standardci
3d284461f3 chore(release): publish new version
- @standardnotes/api-gateway@1.6.29
 - @standardnotes/auth-server@1.11.30
 - @standardnotes/domain-events-infra@1.7.27
 - @standardnotes/domain-events@2.50.2
 - @standardnotes/event-store@1.1.26
 - @standardnotes/files-server@1.5.27
 - @standardnotes/predicates@1.3.0
 - @standardnotes/scheduler-server@1.8.2
 - @standardnotes/syncing-server@1.6.29
2022-07-25 18:22:30 +00:00
Karol Sójko
6642641c11 feat(predicates): add could-not-be-determined predicate verification result 2022-07-25 20:20:35 +02:00
standardci
3e637a482e chore(release): publish new version
- @standardnotes/scheduler-server@1.8.1
2022-07-25 11:02:23 +00:00
Karol Sójko
6374248132 fix(scheduler): checking for predicates fullfillment on applying discount 2022-07-25 13:00:46 +02:00
standardci
b9661d74ee chore(release): publish new version
- @standardnotes/scheduler-server@1.8.0
2022-07-25 07:45:53 +00:00
Karol Sójko
0a5b956cb9 feat(scheduler): add feature flag behind applying and withdrawing discounts 2022-07-25 09:44:06 +02:00
32 changed files with 242 additions and 104 deletions

View File

@@ -3,6 +3,12 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.9.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@1.8.3...@standardnotes/analytics@1.9.0) (2022-07-26)
### Features
* **analytics:** add limited discount offer purchased activity ([13201e7](https://github.com/standardnotes/server/commit/13201e7a9ec875796f527b2c500cf631345c36dd))
## [1.8.3](https://github.com/standardnotes/server/compare/@standardnotes/analytics@1.8.2...@standardnotes/analytics@1.8.3) (2022-07-15)
### Bug Fixes

View File

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

View File

@@ -3,4 +3,5 @@ export enum AnalyticsActivity {
Login = 'login',
EmailUnbackedUpData = 'email-unbacked-up-data',
EmailBackup = 'email-backup',
LimitedDiscountOfferPurchased = 'limited-discount-offer-purchased',
}

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.6.30](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.6.29...@standardnotes/api-gateway@1.6.30) (2022-07-26)
**Note:** Version bump only for package @standardnotes/api-gateway
## [1.6.29](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.6.28...@standardnotes/api-gateway@1.6.29) (2022-07-25)
**Note:** Version bump only for package @standardnotes/api-gateway
## [1.6.28](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.6.27...@standardnotes/api-gateway@1.6.28) (2022-07-15)
**Note:** Version bump only for package @standardnotes/api-gateway

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/api-gateway",
"version": "1.6.28",
"version": "1.6.30",
"engines": {
"node": ">=16.0.0 <17.0.0"
},

View File

@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.12.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.11.31...@standardnotes/auth-server@1.12.0) (2022-07-26)
### Features
* **auth:** add analytics for purchased subscription with a limited discount offer ([e936ac4](https://github.com/standardnotes/server/commit/e936ac4ce18fa43e47a50462c44f63a9ba1c1aa4))
## [1.11.31](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.11.30...@standardnotes/auth-server@1.11.31) (2022-07-25)
### Bug Fixes
* **auth:** marking predicate verification result if user is not existing ([40996f9](https://github.com/standardnotes/server/commit/40996f9d485686f92ee57fe6337102d94378b39b))
## [1.11.30](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.11.29...@standardnotes/auth-server@1.11.30) (2022-07-25)
**Note:** Version bump only for package @standardnotes/auth-server
## [1.11.29](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.11.28...@standardnotes/auth-server@1.11.29) (2022-07-15)
**Note:** Version bump only for package @standardnotes/auth-server

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/auth-server",
"version": "1.11.29",
"version": "1.12.0",
"engines": {
"node": ">=16.0.0 <17.0.0"
},

View File

@@ -96,7 +96,7 @@ describe('PredicateVerificationRequestedEventHandler', () => {
expect(domainEventPublisher.publish).toHaveBeenCalled()
})
it('should not verify a predicate if user is missing', async () => {
it('should mark a predicate verification with undetermined result if user is missing', async () => {
event.meta = {
correlation: {
userIdentifier: 'test@test.te',
@@ -110,6 +110,6 @@ describe('PredicateVerificationRequestedEventHandler', () => {
await createHandler().handle(event)
expect(verifyPredicate.execute).not.toHaveBeenCalled()
expect(domainEventPublisher.publish).not.toHaveBeenCalled()
expect(domainEventPublisher.publish).toHaveBeenCalled()
})
})

View File

@@ -3,6 +3,7 @@ import {
DomainEventPublisherInterface,
PredicateVerificationRequestedEvent,
} from '@standardnotes/domain-events'
import { PredicateVerificationResult } from '@standardnotes/predicates'
import { inject, injectable } from 'inversify'
import { Logger } from 'winston'
@@ -28,7 +29,13 @@ export class PredicateVerificationRequestedEventHandler implements DomainEventHa
if (event.meta.correlation.userIdentifierType === 'email') {
const user = await this.userRepository.findOneByEmail(event.meta.correlation.userIdentifier)
if (user === null) {
this.logger.warn(`Could not find user ${event.meta.correlation.userIdentifier} for predicate verification`)
await this.domainEventPublisher.publish(
this.domainEventFactory.createPredicateVerifiedEvent({
predicate: event.payload.predicate,
predicateVerificationResult: PredicateVerificationResult.CouldNotBeDetermined,
userUuid,
}),
)
return
}

View File

@@ -16,6 +16,8 @@ import { OfflineUserSubscriptionRepositoryInterface } from '../Subscription/Offl
import { OfflineUserSubscription } from '../Subscription/OfflineUserSubscription'
import { SubscriptionSettingServiceInterface } from '../Setting/SubscriptionSettingServiceInterface'
import { UserSubscriptionType } from '../Subscription/UserSubscriptionType'
import { AnalyticsStoreInterface, Period } from '@standardnotes/analytics'
import { AnalyticsEntity } from '../Analytics/AnalyticsEntity'
describe('SubscriptionPurchasedEventHandler', () => {
let userRepository: UserRepositoryInterface
@@ -29,6 +31,7 @@ describe('SubscriptionPurchasedEventHandler', () => {
let event: SubscriptionPurchasedEvent
let subscriptionExpiresAt: number
let subscriptionSettingService: SubscriptionSettingServiceInterface
let analyticsStore: AnalyticsStoreInterface
let timestamp: number
const createHandler = () =>
@@ -38,6 +41,7 @@ describe('SubscriptionPurchasedEventHandler', () => {
offlineUserSubscriptionRepository,
roleService,
subscriptionSettingService,
analyticsStore,
logger,
)
@@ -83,11 +87,15 @@ describe('SubscriptionPurchasedEventHandler', () => {
subscriptionExpiresAt,
timestamp: dayjs.utc().valueOf(),
offline: false,
discountCode: null,
}
subscriptionSettingService = {} as jest.Mocked<SubscriptionSettingServiceInterface>
subscriptionSettingService.applyDefaultSubscriptionSettingsForSubscription = jest.fn()
analyticsStore = {} as jest.Mocked<AnalyticsStoreInterface>
analyticsStore.markActivity = jest.fn()
logger = {} as jest.Mocked<Logger>
logger.info = jest.fn()
logger.warn = jest.fn()
@@ -134,6 +142,28 @@ describe('SubscriptionPurchasedEventHandler', () => {
})
})
it('should update analytics on limited discount offer purchasing', async () => {
const analyticsEntity = { id: 3 } as jest.Mocked<AnalyticsEntity>
user = {
uuid: '123',
email: 'test@test.com',
roles: Promise.resolve([
{
name: RoleName.CoreUser,
},
]),
analyticsEntity: Promise.resolve(analyticsEntity),
} as jest.Mocked<User>
userRepository.findOneByEmail = jest.fn().mockReturnValue(user)
event.payload.discountCode = 'limited-10'
await createHandler().handle(event)
expect(analyticsStore.markActivity).toHaveBeenCalledWith(['limited-discount-offer-purchased'], 3, [Period.Today])
})
it('should create an offline subscription', async () => {
event.payload.offline = true

View File

@@ -13,6 +13,7 @@ import { OfflineUserSubscription } from '../Subscription/OfflineUserSubscription
import { OfflineUserSubscriptionRepositoryInterface } from '../Subscription/OfflineUserSubscriptionRepositoryInterface'
import { UserSubscriptionType } from '../Subscription/UserSubscriptionType'
import { SubscriptionSettingServiceInterface } from '../Setting/SubscriptionSettingServiceInterface'
import { AnalyticsActivity, AnalyticsStoreInterface, Period } from '@standardnotes/analytics'
@injectable()
export class SubscriptionPurchasedEventHandler implements DomainEventHandlerInterface {
@@ -23,6 +24,7 @@ export class SubscriptionPurchasedEventHandler implements DomainEventHandlerInte
private offlineUserSubscriptionRepository: OfflineUserSubscriptionRepositoryInterface,
@inject(TYPES.RoleService) private roleService: RoleServiceInterface,
@inject(TYPES.SubscriptionSettingService) private subscriptionSettingService: SubscriptionSettingServiceInterface,
@inject(TYPES.AnalyticsStore) private analyticsStore: AnalyticsStoreInterface,
@inject(TYPES.Logger) private logger: Logger,
) {}
@@ -62,6 +64,16 @@ export class SubscriptionPurchasedEventHandler implements DomainEventHandlerInte
userSubscription,
event.payload.subscriptionName,
)
const limitedDiscountPurchased = event.payload.discountCode === 'limited-10'
if (limitedDiscountPurchased) {
const analyticsEntity = await user.analyticsEntity
if (analyticsEntity) {
await this.analyticsStore.markActivity([AnalyticsActivity.LimitedDiscountOfferPurchased], analyticsEntity.id, [
Period.Today,
])
}
}
}
private async addUserRole(user: User, subscriptionName: SubscriptionName): Promise<void> {

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.7.28](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.7.27...@standardnotes/domain-events-infra@1.7.28) (2022-07-26)
**Note:** Version bump only for package @standardnotes/domain-events-infra
## [1.7.27](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.7.26...@standardnotes/domain-events-infra@1.7.27) (2022-07-25)
**Note:** Version bump only for package @standardnotes/domain-events-infra
## [1.7.26](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.7.25...@standardnotes/domain-events-infra@1.7.26) (2022-07-15)
**Note:** Version bump only for package @standardnotes/domain-events-infra

View File

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

View File

@@ -3,6 +3,16 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [2.51.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.50.2...@standardnotes/domain-events@2.51.0) (2022-07-26)
### Features
* **domain-events:** add discount code to subscription purchased event ([28e1c65](https://github.com/standardnotes/server/commit/28e1c656312ae9a7c4afec1aa65bb104f788b8b6))
## [2.50.2](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.50.1...@standardnotes/domain-events@2.50.2) (2022-07-25)
**Note:** Version bump only for package @standardnotes/domain-events
## [2.50.1](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.50.0...@standardnotes/domain-events@2.50.1) (2022-07-15)
### Bug Fixes

View File

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

View File

@@ -7,4 +7,5 @@ export interface SubscriptionPurchasedEventPayload {
subscriptionExpiresAt: number
timestamp: number
offline: boolean
discountCode: string | null
}

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.1.27](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.1.26...@standardnotes/event-store@1.1.27) (2022-07-26)
**Note:** Version bump only for package @standardnotes/event-store
## [1.1.26](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.1.25...@standardnotes/event-store@1.1.26) (2022-07-25)
**Note:** Version bump only for package @standardnotes/event-store
## [1.1.25](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.1.24...@standardnotes/event-store@1.1.25) (2022-07-15)
**Note:** Version bump only for package @standardnotes/event-store

View File

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

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.5.28](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.5.27...@standardnotes/files-server@1.5.28) (2022-07-26)
**Note:** Version bump only for package @standardnotes/files-server
## [1.5.27](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.5.26...@standardnotes/files-server@1.5.27) (2022-07-25)
**Note:** Version bump only for package @standardnotes/files-server
## [1.5.26](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.5.25...@standardnotes/files-server@1.5.26) (2022-07-15)
**Note:** Version bump only for package @standardnotes/files-server

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/files-server",
"version": "1.5.26",
"version": "1.5.28",
"engines": {
"node": ">=16.0.0 <17.0.0"
},

View File

@@ -3,6 +3,12 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.3.0](https://github.com/standardnotes/server/compare/@standardnotes/predicates@1.2.6...@standardnotes/predicates@1.3.0) (2022-07-25)
### Features
* **predicates:** add could-not-be-determined predicate verification result ([6642641](https://github.com/standardnotes/server/commit/6642641c1161986a1c1186698f6b8151ce3aee87))
## [1.2.6](https://github.com/standardnotes/server/compare/@standardnotes/predicates@1.2.5...@standardnotes/predicates@1.2.6) (2022-07-14)
**Note:** Version bump only for package @standardnotes/predicates

View File

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

View File

@@ -1,4 +1,5 @@
export enum PredicateVerificationResult {
Affirmed = 'affirmed',
Denied = 'denied',
CouldNotBeDetermined = 'could-not-be-determined',
}

View File

@@ -3,6 +3,46 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.10.0](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.9.2...@standardnotes/scheduler-server@1.10.0) (2022-07-26)
### Features
* **scheduler:** enable discount applying and withdraw for everyone ([04bf414](https://github.com/standardnotes/server/commit/04bf414de41ecba255b068fd8e72bc569ace9ed1))
## [1.9.2](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.9.1...@standardnotes/scheduler-server@1.9.2) (2022-07-26)
### Bug Fixes
* **scheduler:** change the discount code to an absolute discount ([1fa94ef](https://github.com/standardnotes/server/commit/1fa94efa02f169ee25d11e9403ab3368b696cc33))
## [1.9.1](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.9.0...@standardnotes/scheduler-server@1.9.1) (2022-07-26)
### Bug Fixes
* **scheduler:** eliminate read/write concurrency hazzard while updating predicate status ([4ab0d24](https://github.com/standardnotes/server/commit/4ab0d24d24b62babf5d0e36fbcb3a6364abb71bc))
# [1.9.0](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.8.2...@standardnotes/scheduler-server@1.9.0) (2022-07-25)
### Features
* **scheduler:** add job interpreting logs ([bf12687](https://github.com/standardnotes/server/commit/bf12687f63738a6eac46ab1778826de5d076e4ab))
## [1.8.2](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.8.1...@standardnotes/scheduler-server@1.8.2) (2022-07-25)
**Note:** Version bump only for package @standardnotes/scheduler-server
## [1.8.1](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.8.0...@standardnotes/scheduler-server@1.8.1) (2022-07-25)
### Bug Fixes
* **scheduler:** checking for predicates fullfillment on applying discount ([6374248](https://github.com/standardnotes/server/commit/637424813278b7dd81969e1783cbc38d1a916cab))
# [1.8.0](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.7.0...@standardnotes/scheduler-server@1.8.0) (2022-07-25)
### Features
* **scheduler:** add feature flag behind applying and withdrawing discounts ([0a5b956](https://github.com/standardnotes/server/commit/0a5b956cb9586d353ac68c79e8473d74f8d9796a))
# [1.7.0](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.6.0...@standardnotes/scheduler-server@1.7.0) (2022-07-25)
### Features

View File

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

View File

@@ -6,6 +6,7 @@ import {
} from '@standardnotes/domain-events'
import { PredicateName } from '@standardnotes/predicates'
import 'reflect-metadata'
import { Logger } from 'winston'
import { DomainEventFactoryInterface } from '../Event/DomainEventFactoryInterface'
import { Predicate } from '../Predicate/Predicate'
import { PredicateRepositoryInterface } from '../Predicate/PredicateRepositoryInterface'
@@ -23,9 +24,10 @@ describe('JobDoneInterpreter', () => {
let domainEventFactory: DomainEventFactoryInterface
let domainEventPublisher: DomainEventPublisherInterface
let job: Job
let logger: Logger
const createInterpreter = () =>
new JobDoneInterpreter(jobRepository, predicateRepository, domainEventFactory, domainEventPublisher)
new JobDoneInterpreter(jobRepository, predicateRepository, domainEventFactory, domainEventPublisher, logger)
beforeEach(() => {
job = {} as jest.Mocked<Job>
@@ -49,6 +51,10 @@ describe('JobDoneInterpreter', () => {
domainEventPublisher = {} as jest.Mocked<DomainEventPublisherInterface>
domainEventPublisher.publish = jest.fn()
logger = {} as jest.Mocked<Logger>
logger.info = jest.fn()
logger.warn = jest.fn()
})
it('should do nothing if job is not found', async () => {
@@ -186,15 +192,20 @@ describe('JobDoneInterpreter', () => {
it('should request discount apply', async () => {
jobRepository.findOneByUuid = jest.fn().mockReturnValue({
name: JobName.APPLY_SUBSCRIPTION_DISCOUNT,
userIdentifier: 'test@test.te',
userIdentifier: 'test@standardnotes.com',
userIdentifierType: 'email',
} as jest.Mocked<Job>)
predicateRepository.findByJobUuid = jest
.fn()
.mockReturnValue([
{ name: PredicateName.SubscriptionPurchased, status: PredicateStatus.Denied } as jest.Mocked<Predicate>,
])
await createInterpreter().interpret('1-2-3')
expect(domainEventFactory.createDiscountApplyRequestedEvent).toHaveBeenCalledWith({
userEmail: 'test@test.te',
discountCode: 'econ-10',
userEmail: 'test@standardnotes.com',
discountCode: 'limited-10',
})
expect(domainEventPublisher.publish).toHaveBeenCalled()
})
@@ -215,15 +226,15 @@ describe('JobDoneInterpreter', () => {
it('should request discount withdraw', async () => {
jobRepository.findOneByUuid = jest.fn().mockReturnValue({
name: JobName.WITHDRAW_SUBSCRIPTION_DISCOUNT,
userIdentifier: 'test@test.te',
userIdentifier: 'test@standardnotes.com',
userIdentifierType: 'email',
} as jest.Mocked<Job>)
await createInterpreter().interpret('1-2-3')
expect(domainEventFactory.createDiscountWithdrawRequestedEvent).toHaveBeenCalledWith({
userEmail: 'test@test.te',
discountCode: 'econ-10',
userEmail: 'test@standardnotes.com',
discountCode: 'limited-10',
})
expect(domainEventPublisher.publish).toHaveBeenCalled()
})

View File

@@ -2,6 +2,7 @@ import { EmailMessageIdentifier } from '@standardnotes/common'
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
import { PredicateName } from '@standardnotes/predicates'
import { inject, injectable } from 'inversify'
import { Logger } from 'winston'
import TYPES from '../../Bootstrap/Types'
import { DomainEventFactoryInterface } from '../Event/DomainEventFactoryInterface'
@@ -20,6 +21,7 @@ export class JobDoneInterpreter implements JobDoneInterpreterInterface {
@inject(TYPES.PredicateRepository) private predicateRepository: PredicateRepositoryInterface,
@inject(TYPES.DomainEventFactory) private domainEventFactory: DomainEventFactoryInterface,
@inject(TYPES.DomainEventPublisher) private domainEventPublisher: DomainEventPublisherInterface,
@inject(TYPES.Logger) private logger: Logger,
) {}
async interpret(jobUuid: string): Promise<void> {
@@ -29,14 +31,18 @@ export class JobDoneInterpreter implements JobDoneInterpreterInterface {
return
}
this.logger.info(`[${jobUuid}]${job.name}: Interpreting job as done.`)
if (!(await this.predicatesAreFulfilled(job))) {
this.logger.info(`[${jobUuid}]${job.name}: predicates are not fulfilled.`)
return
}
switch (job.name) {
case JobName.ENCOURAGE_EMAIL_BACKUPS:
if (job.userIdentifierType === 'email') {
await this.requestEmailBackupEncouragementEmail(job.userIdentifier)
await this.requestEmailBackupEncouragementEmail(job)
}
return
case JobName.ENCOURAGE_SUBSCRIPTION_PURCHASING:
@@ -46,28 +52,32 @@ export class JobDoneInterpreter implements JobDoneInterpreterInterface {
return
case JobName.EXIT_INTERVIEW:
if (job.userIdentifierType === 'email') {
await this.requestExitInterviewEmail(job.userIdentifier)
await this.requestExitInterviewEmail(job)
}
return
case JobName.APPLY_SUBSCRIPTION_DISCOUNT:
if (job.userIdentifierType === 'email') {
await this.requestDiscountApply(job.userIdentifier)
await this.requestDiscountApply(job)
}
return
case JobName.WITHDRAW_SUBSCRIPTION_DISCOUNT:
if (job.userIdentifierType === 'email') {
await this.requestDiscountWithdraw(job.userIdentifier)
await this.requestDiscountWithdraw(job)
}
return
default:
this.logger.warn(`[${jobUuid}]${job.name}: job is not interpretable.`)
return
}
}
private async requestEmailBackupEncouragementEmail(userEmail: string): Promise<void> {
private async requestEmailBackupEncouragementEmail(job: Job): Promise<void> {
this.logger.info(`[${job.uuid}]${job.name}: requesting email backup encouragement email.`)
await this.domainEventPublisher.publish(
this.domainEventFactory.createEmailMessageRequestedEvent({
userEmail,
userEmail: job.userIdentifier,
messageIdentifier: EmailMessageIdentifier.ENCOURAGE_EMAIL_BACKUPS,
context: {},
}),
@@ -75,6 +85,8 @@ export class JobDoneInterpreter implements JobDoneInterpreterInterface {
}
private async requestSubscriptionPurchaseEncouragementEmail(job: Job): Promise<void> {
this.logger.info(`[${job.uuid}]${job.name}: requesting subscription purchase encouragement email.`)
await this.domainEventPublisher.publish(
this.domainEventFactory.createEmailMessageRequestedEvent({
userEmail: job.userIdentifier,
@@ -86,30 +98,36 @@ export class JobDoneInterpreter implements JobDoneInterpreterInterface {
)
}
private async requestExitInterviewEmail(userEmail: string): Promise<void> {
private async requestExitInterviewEmail(job: Job): Promise<void> {
this.logger.info(`[${job.uuid}]${job.name}: requesting exit interview email.`)
await this.domainEventPublisher.publish(
this.domainEventFactory.createEmailMessageRequestedEvent({
userEmail,
userEmail: job.userIdentifier,
messageIdentifier: EmailMessageIdentifier.EXIT_INTERVIEW,
context: {},
}),
)
}
private async requestDiscountApply(userEmail: string): Promise<void> {
private async requestDiscountApply(job: Job): Promise<void> {
this.logger.info(`[${job.uuid}]${job.name}: requesting discount applying.`)
await this.domainEventPublisher.publish(
this.domainEventFactory.createDiscountApplyRequestedEvent({
userEmail,
discountCode: 'econ-10',
userEmail: job.userIdentifier,
discountCode: 'limited-10',
}),
)
}
private async requestDiscountWithdraw(userEmail: string): Promise<void> {
private async requestDiscountWithdraw(job: Job): Promise<void> {
this.logger.info(`[${job.uuid}]${job.name}: requesting discount withdraw.`)
await this.domainEventPublisher.publish(
this.domainEventFactory.createDiscountWithdrawRequestedEvent({
userEmail,
discountCode: 'econ-10',
userEmail: job.userIdentifier,
discountCode: 'limited-10',
}),
)
}
@@ -124,6 +142,7 @@ export class JobDoneInterpreter implements JobDoneInterpreterInterface {
PredicateStatus.Denied
)
case JobName.ENCOURAGE_SUBSCRIPTION_PURCHASING:
case JobName.APPLY_SUBSCRIPTION_DISCOUNT:
return (
predicates.find((predicate) => predicate.name === PredicateName.SubscriptionPurchased)?.status ===
PredicateStatus.Denied

View File

@@ -1,8 +1,7 @@
import { PredicateAuthority, PredicateName, PredicateVerificationResult } from '@standardnotes/predicates'
import 'reflect-metadata'
import { JobDoneInterpreterInterface } from '../../Job/JobDoneInterpreterInterface'
import { JobRepositoryInterface } from '../../Job/JobRepositoryInterface'
import { PredicateAuthority, PredicateName, PredicateVerificationResult } from '@standardnotes/predicates'
import { Predicate } from '../../Predicate/Predicate'
import { PredicateRepositoryInterface } from '../../Predicate/PredicateRepositoryInterface'
import { PredicateStatus } from '../../Predicate/PredicateStatus'
@@ -11,12 +10,10 @@ import { UpdatePredicateStatus } from './UpdatePredicateStatus'
describe('UpdatePredicateStatus', () => {
let predicateRepository: PredicateRepositoryInterface
let jobRepository: JobRepositoryInterface
let jobDoneInterpreter: JobDoneInterpreterInterface
let predicateComplete: Predicate
let predicateIncomplete: Predicate
const createUseCase = () => new UpdatePredicateStatus(predicateRepository, jobRepository, jobDoneInterpreter)
const createUseCase = () => new UpdatePredicateStatus(predicateRepository)
beforeEach(() => {
predicateComplete = {
@@ -33,15 +30,9 @@ describe('UpdatePredicateStatus', () => {
predicateRepository = {} as jest.Mocked<PredicateRepositoryInterface>
predicateRepository.findByJobUuid = jest.fn().mockReturnValue([predicateComplete, predicateIncomplete])
predicateRepository.save = jest.fn()
jobRepository = {} as jest.Mocked<JobRepositoryInterface>
jobRepository.markJobAsDone = jest.fn()
jobDoneInterpreter = {} as jest.Mocked<JobDoneInterpreterInterface>
jobDoneInterpreter.interpret = jest.fn()
})
it('should mark a predicate as complete and update job as done', async () => {
it('should mark a predicate as complete', async () => {
expect(
await createUseCase().execute({
predicate: { name: PredicateName.EmailBackupsEnabled, jobUuid: '1-2-3', authority: PredicateAuthority.Auth },
@@ -49,7 +40,6 @@ describe('UpdatePredicateStatus', () => {
}),
).toEqual({
success: true,
allPredicatesChecked: true,
})
expect(predicateRepository.save).toHaveBeenCalledWith({
@@ -57,37 +47,5 @@ describe('UpdatePredicateStatus', () => {
name: 'email-backups-enabled',
status: 'denied',
})
expect(jobRepository.markJobAsDone).toHaveBeenCalled()
expect(jobDoneInterpreter.interpret).toHaveBeenCalled()
})
it('should mark a predicate as complete and not update job as done if there are still incomplete predicates', async () => {
predicateRepository.findByJobUuid = jest
.fn()
.mockReturnValue([
predicateComplete,
predicateIncomplete,
{ uuid: '3-4-5', status: PredicateStatus.Pending } as jest.Mocked<Predicate>,
])
expect(
await createUseCase().execute({
predicate: { name: PredicateName.EmailBackupsEnabled, jobUuid: '1-2-3', authority: PredicateAuthority.Auth },
predicateVerificationResult: PredicateVerificationResult.Denied,
}),
).toEqual({
success: true,
allPredicatesChecked: false,
})
expect(predicateRepository.save).toHaveBeenCalledWith({
uuid: '2-3-4',
name: 'email-backups-enabled',
status: 'denied',
})
expect(jobRepository.markJobAsDone).not.toHaveBeenCalled()
expect(jobDoneInterpreter.interpret).not.toHaveBeenCalled()
})
})

View File

@@ -1,8 +1,6 @@
import { inject, injectable } from 'inversify'
import TYPES from '../../../Bootstrap/Types'
import { JobDoneInterpreterInterface } from '../../Job/JobDoneInterpreterInterface'
import { JobRepositoryInterface } from '../../Job/JobRepositoryInterface'
import { PredicateRepositoryInterface } from '../../Predicate/PredicateRepositoryInterface'
import { PredicateStatus } from '../../Predicate/PredicateStatus'
import { UseCaseInterface } from '../UseCaseInterface'
@@ -12,35 +10,20 @@ import { UpdatePredicateStatusResponse } from './UpdatePredicateStatusResponse'
@injectable()
export class UpdatePredicateStatus implements UseCaseInterface {
constructor(
@inject(TYPES.PredicateRepository) private predicateRepository: PredicateRepositoryInterface,
@inject(TYPES.JobRepository) private jobRepository: JobRepositoryInterface,
@inject(TYPES.JobDoneInterpreter) private jobDoneInterpreter: JobDoneInterpreterInterface,
) {}
constructor(@inject(TYPES.PredicateRepository) private predicateRepository: PredicateRepositoryInterface) {}
async execute(dto: UpdatePredicateStatusDTO): Promise<UpdatePredicateStatusResponse> {
const predicates = await this.predicateRepository.findByJobUuid(dto.predicate.jobUuid)
let allPredicatesChecked = true
for (const predicate of predicates) {
if (predicate.name === dto.predicate.name) {
predicate.status = dto.predicateVerificationResult as unknown as PredicateStatus
await this.predicateRepository.save(predicate)
}
if (predicate.status === PredicateStatus.Pending) {
allPredicatesChecked = false
}
}
if (allPredicatesChecked) {
await this.jobDoneInterpreter.interpret(dto.predicate.jobUuid)
await this.jobRepository.markJobAsDone(dto.predicate.jobUuid)
}
return {
success: true,
allPredicatesChecked,
}
}
}

View File

@@ -1,6 +1,3 @@
export type UpdatePredicateStatusResponse =
| {
success: true
allPredicatesChecked: boolean
}
| { success: false }
export type UpdatePredicateStatusResponse = {
success: boolean
}

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.6.30](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.6.29...@standardnotes/syncing-server@1.6.30) (2022-07-26)
**Note:** Version bump only for package @standardnotes/syncing-server
## [1.6.29](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.6.28...@standardnotes/syncing-server@1.6.29) (2022-07-25)
**Note:** Version bump only for package @standardnotes/syncing-server
## [1.6.28](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.6.27...@standardnotes/syncing-server@1.6.28) (2022-07-15)
**Note:** Version bump only for package @standardnotes/syncing-server

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/syncing-server",
"version": "1.6.28",
"version": "1.6.30",
"engines": {
"node": ">=16.0.0 <17.0.0"
},