Compare commits

..

2 Commits

16 changed files with 136 additions and 11 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.32.13](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.32.12...@standardnotes/auth-server@1.32.13) (2022-09-29)
### Bug Fixes
* **auth:** finding previous subscription setting for irreplacable subscription settings ([0a5b7e1](https://github.com/standardnotes/server/commit/0a5b7e13cd51ddbad40f67d629b0daf50b176fac))
## [1.32.12](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.32.11...@standardnotes/auth-server@1.32.12) (2022-09-29)
### Bug Fixes

View File

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

View File

@@ -132,6 +132,7 @@ describe('SubscriptionPurchasedEventHandler', () => {
expect(subscriptionSettingService.applyDefaultSubscriptionSettingsForSubscription).toHaveBeenCalledWith(
subscription,
SubscriptionName.ProPlan,
'123',
)
})

View File

@@ -76,6 +76,7 @@ export class SubscriptionPurchasedEventHandler implements DomainEventHandlerInte
await this.subscriptionSettingService.applyDefaultSubscriptionSettingsForSubscription(
userSubscription,
event.payload.subscriptionName,
user.uuid,
)
const { analyticsId } = await this.getUserAnalyticsId.execute({ userUuid: user.uuid })

View File

@@ -94,6 +94,7 @@ describe('SubscriptionReassignedEventHandler', () => {
expect(subscriptionSettingService.applyDefaultSubscriptionSettingsForSubscription).toHaveBeenCalledWith(
subscription,
SubscriptionName.ProPlan,
'123',
)
})

View File

@@ -58,6 +58,7 @@ export class SubscriptionReassignedEventHandler implements DomainEventHandlerInt
await this.subscriptionSettingService.applyDefaultSubscriptionSettingsForSubscription(
userSubscription,
event.payload.subscriptionName,
user.uuid,
)
}

View File

@@ -130,6 +130,7 @@ describe('SubscriptionSyncRequestedEventHandler', () => {
expect(subscriptionSettingService.applyDefaultSubscriptionSettingsForSubscription).toHaveBeenCalledWith(
subscription,
SubscriptionName.ProPlan,
'123',
)
expect(settingService.createOrReplace).toHaveBeenCalledWith({

View File

@@ -89,6 +89,7 @@ export class SubscriptionSyncRequestedEventHandler implements DomainEventHandler
await this.subscriptionSettingService.applyDefaultSubscriptionSettingsForSubscription(
userSubscription,
event.payload.subscriptionName,
user.uuid,
)
await this.settingService.createOrReplace({

View File

@@ -13,6 +13,7 @@ import { SubscriptionName } from '@standardnotes/common'
import { User } from '../User/User'
import { SettingFactoryInterface } from './SettingFactoryInterface'
import { SubscriptionSettingsAssociationServiceInterface } from './SubscriptionSettingsAssociationServiceInterface'
import { UserSubscriptionRepositoryInterface } from '../Subscription/UserSubscriptionRepositoryInterface'
describe('SubscriptionSettingService', () => {
let setting: SubscriptionSetting
@@ -22,6 +23,7 @@ describe('SubscriptionSettingService', () => {
let subscriptionSettingRepository: SubscriptionSettingRepositoryInterface
let subscriptionSettingsAssociationService: SubscriptionSettingsAssociationServiceInterface
let settingDecrypter: SettingDecrypterInterface
let userSubscriptionRepository: UserSubscriptionRepositoryInterface
let logger: Logger
const createService = () =>
@@ -30,6 +32,7 @@ describe('SubscriptionSettingService', () => {
subscriptionSettingRepository,
subscriptionSettingsAssociationService,
settingDecrypter,
userSubscriptionRepository,
logger,
)
@@ -51,6 +54,16 @@ describe('SubscriptionSettingService', () => {
subscriptionSettingRepository.findLastByNameAndUserSubscriptionUuid = jest.fn().mockReturnValue(null)
subscriptionSettingRepository.save = jest.fn().mockImplementation((setting) => setting)
userSubscriptionRepository = {} as jest.Mocked<UserSubscriptionRepositoryInterface>
userSubscriptionRepository.findByUserUuid = jest.fn().mockReturnValue([
{
uuid: 's-1-2-3',
} as jest.Mocked<UserSubscription>,
{
uuid: 's-2-3-4',
} as jest.Mocked<UserSubscription>,
])
subscriptionSettingsAssociationService = {} as jest.Mocked<SubscriptionSettingsAssociationServiceInterface>
subscriptionSettingsAssociationService.getDefaultSettingsAndValuesForSubscriptionName = jest.fn().mockReturnValue(
new Map([
@@ -76,7 +89,11 @@ describe('SubscriptionSettingService', () => {
})
it('should create default settings for a subscription', async () => {
await createService().applyDefaultSubscriptionSettingsForSubscription(userSubscription, SubscriptionName.PlusPlan)
await createService().applyDefaultSubscriptionSettingsForSubscription(
userSubscription,
SubscriptionName.PlusPlan,
'1-2-3',
)
expect(subscriptionSettingRepository.save).toHaveBeenCalledWith(setting)
})
@@ -97,7 +114,11 @@ describe('SubscriptionSettingService', () => {
)
subscriptionSettingRepository.findLastByNameAndUserSubscriptionUuid = jest.fn().mockReturnValue(setting)
await createService().applyDefaultSubscriptionSettingsForSubscription(userSubscription, SubscriptionName.PlusPlan)
await createService().applyDefaultSubscriptionSettingsForSubscription(
userSubscription,
SubscriptionName.PlusPlan,
'1-2-3',
)
expect(subscriptionSettingRepository.save).toHaveBeenCalled()
})
@@ -118,7 +139,41 @@ describe('SubscriptionSettingService', () => {
)
subscriptionSettingRepository.findLastByNameAndUserSubscriptionUuid = jest.fn().mockReturnValue(null)
await createService().applyDefaultSubscriptionSettingsForSubscription(userSubscription, SubscriptionName.PlusPlan)
await createService().applyDefaultSubscriptionSettingsForSubscription(
userSubscription,
SubscriptionName.PlusPlan,
'1-2-3',
)
expect(subscriptionSettingRepository.save).toHaveBeenCalledWith(setting)
})
it('should create default settings for a subscription if it is not replaceable and no previous subscription existed', async () => {
subscriptionSettingsAssociationService.getDefaultSettingsAndValuesForSubscriptionName = jest.fn().mockReturnValue(
new Map([
[
SubscriptionSettingName.FileUploadBytesUsed,
{
value: '0',
sensitive: 0,
serverEncryptionVersion: EncryptionVersion.Unencrypted,
replaceable: false,
},
],
]),
)
subscriptionSettingRepository.findLastByNameAndUserSubscriptionUuid = jest.fn().mockReturnValue(null)
userSubscriptionRepository.findByUserUuid = jest.fn().mockReturnValue([
{
uuid: '1-2-3',
} as jest.Mocked<UserSubscription>,
])
await createService().applyDefaultSubscriptionSettingsForSubscription(
userSubscription,
SubscriptionName.PlusPlan,
'1-2-3',
)
expect(subscriptionSettingRepository.save).toHaveBeenCalledWith(setting)
})
@@ -128,7 +183,11 @@ describe('SubscriptionSettingService', () => {
.fn()
.mockReturnValue(undefined)
await createService().applyDefaultSubscriptionSettingsForSubscription(userSubscription, SubscriptionName.PlusPlan)
await createService().applyDefaultSubscriptionSettingsForSubscription(
userSubscription,
SubscriptionName.PlusPlan,
'1-2-3',
)
expect(subscriptionSettingRepository.save).not.toHaveBeenCalled()
})

View File

@@ -1,4 +1,4 @@
import { SubscriptionName } from '@standardnotes/common'
import { SubscriptionName, Uuid } from '@standardnotes/common'
import { SubscriptionSettingName } from '@standardnotes/settings'
import { inject, injectable } from 'inversify'
import { Logger } from 'winston'
@@ -16,6 +16,7 @@ import { FindSubscriptionSettingDTO } from './FindSubscriptionSettingDTO'
import { SubscriptionSettingRepositoryInterface } from './SubscriptionSettingRepositoryInterface'
import { SettingFactoryInterface } from './SettingFactoryInterface'
import { SubscriptionSettingsAssociationServiceInterface } from './SubscriptionSettingsAssociationServiceInterface'
import { UserSubscriptionRepositoryInterface } from '../Subscription/UserSubscriptionRepositoryInterface'
@injectable()
export class SubscriptionSettingService implements SubscriptionSettingServiceInterface {
@@ -26,12 +27,14 @@ export class SubscriptionSettingService implements SubscriptionSettingServiceInt
@inject(TYPES.SubscriptionSettingsAssociationService)
private subscriptionSettingAssociationService: SubscriptionSettingsAssociationServiceInterface,
@inject(TYPES.SettingDecrypter) private settingDecrypter: SettingDecrypterInterface,
@inject(TYPES.UserSubscriptionRepository) private userSubscriptionRepository: UserSubscriptionRepositoryInterface,
@inject(TYPES.Logger) private logger: Logger,
) {}
async applyDefaultSubscriptionSettingsForSubscription(
userSubscription: UserSubscription,
subscriptionName: SubscriptionName,
userUuid: Uuid,
): Promise<void> {
const defaultSettingsWithValues =
await this.subscriptionSettingAssociationService.getDefaultSettingsAndValuesForSubscriptionName(subscriptionName)
@@ -44,10 +47,7 @@ export class SubscriptionSettingService implements SubscriptionSettingServiceInt
for (const settingName of defaultSettingsWithValues.keys()) {
const setting = defaultSettingsWithValues.get(settingName) as SettingDescription
if (!setting.replaceable) {
const existingSetting = await this.subscriptionSettingRepository.findLastByNameAndUserSubscriptionUuid(
settingName,
userSubscription.uuid,
)
const existingSetting = await this.findPreviousSubscriptionSetting(settingName, userSubscription.uuid, userUuid)
if (existingSetting !== null) {
existingSetting.userSubscription = Promise.resolve(userSubscription)
await this.subscriptionSettingRepository.save(existingSetting)
@@ -126,4 +126,22 @@ export class SubscriptionSettingService implements SubscriptionSettingServiceInt
subscriptionSetting,
}
}
private async findPreviousSubscriptionSetting(
settingName: SubscriptionSettingName,
currentUserSubscriptionUuid: Uuid,
userUuid: Uuid,
): Promise<SubscriptionSetting | null> {
const userSubscriptions = await this.userSubscriptionRepository.findByUserUuid(userUuid)
const previousSubscriptions = userSubscriptions.filter(
(subscription) => subscription.uuid !== currentUserSubscriptionUuid,
)
const lastSubscription = previousSubscriptions.shift()
if (!lastSubscription) {
return null
}
return this.subscriptionSettingRepository.findLastByNameAndUserSubscriptionUuid(settingName, lastSubscription.uuid)
}
}

View File

@@ -1,4 +1,4 @@
import { SubscriptionName } from '@standardnotes/common'
import { SubscriptionName, Uuid } from '@standardnotes/common'
import { UserSubscription } from '../Subscription/UserSubscription'
import { CreateOrReplaceSubscriptionSettingDTO } from './CreateOrReplaceSubscriptionSettingDTO'
@@ -10,6 +10,7 @@ export interface SubscriptionSettingServiceInterface {
applyDefaultSubscriptionSettingsForSubscription(
userSubscription: UserSubscription,
subscriptionName: SubscriptionName,
userUuid: Uuid,
): Promise<void>
createOrReplace(dto: CreateOrReplaceSubscriptionSettingDTO): Promise<CreateOrReplaceSubscriptionSettingResponse>
findSubscriptionSettingWithDecryptedValue(dto: FindSubscriptionSettingDTO): Promise<SubscriptionSetting | null>

View File

@@ -6,6 +6,7 @@ export interface UserSubscriptionRepositoryInterface {
findOneByUuid(uuid: Uuid): Promise<UserSubscription | null>
countByUserUuid(userUuid: Uuid): Promise<number>
findOneByUserUuid(userUuid: Uuid): Promise<UserSubscription | null>
findByUserUuid(userUuid: Uuid): Promise<UserSubscription[]>
findOneByUserUuidAndSubscriptionId(userUuid: Uuid, subscriptionId: number): Promise<UserSubscription | null>
findBySubscriptionIdAndType(subscriptionId: number, type: UserSubscriptionType): Promise<UserSubscription[]>
findBySubscriptionId(subscriptionId: number): Promise<UserSubscription[]>

View File

@@ -104,6 +104,7 @@ describe('AcceptSharedSubscriptionInvitation', () => {
expect(subscriptionSettingService.applyDefaultSubscriptionSettingsForSubscription).toHaveBeenCalledWith(
inviteeSubscription,
'PLUS_PLAN',
'123',
)
})

View File

@@ -75,6 +75,7 @@ export class AcceptSharedSubscriptionInvitation implements UseCaseInterface {
await this.subscriptionSettingService.applyDefaultSubscriptionSettingsForSubscription(
inviteeSubscription,
inviteeSubscription.planName as SubscriptionName,
invitee.uuid,
)
return {

View File

@@ -36,6 +36,28 @@ describe('MySQLUserSubscriptionRepository', () => {
expect(ormRepository.save).toHaveBeenCalledWith(subscription)
})
it('should find all subscriptions by user uuid', async () => {
const canceledSubscription = {
planName: SubscriptionName.ProPlan,
cancelled: true,
} as jest.Mocked<UserSubscription>
ormRepository.createQueryBuilder = jest.fn().mockImplementation(() => selectQueryBuilder)
selectQueryBuilder.where = jest.fn().mockReturnThis()
selectQueryBuilder.orderBy = jest.fn().mockReturnThis()
selectQueryBuilder.getMany = jest.fn().mockReturnValue([canceledSubscription, subscription])
const result = await createRepository().findByUserUuid('123')
expect(selectQueryBuilder.where).toHaveBeenCalledWith('user_uuid = :user_uuid', {
user_uuid: '123',
})
expect(selectQueryBuilder.orderBy).toHaveBeenCalledWith('ends_at', 'DESC')
expect(selectQueryBuilder.getMany).toHaveBeenCalled()
expect(result).toEqual([canceledSubscription, subscription])
})
it('should find one longest lasting uncanceled subscription by user uuid if there are canceled ones', async () => {
const canceledSubscription = {
planName: SubscriptionName.ProPlan,

View File

@@ -14,6 +14,16 @@ export class MySQLUserSubscriptionRepository implements UserSubscriptionReposito
private ormRepository: Repository<UserSubscription>,
) {}
async findByUserUuid(userUuid: string): Promise<UserSubscription[]> {
return await this.ormRepository
.createQueryBuilder()
.where('user_uuid = :user_uuid', {
user_uuid: userUuid,
})
.orderBy('ends_at', 'DESC')
.getMany()
}
async countByUserUuid(userUuid: Uuid): Promise<number> {
return await this.ormRepository
.createQueryBuilder()