feat(auth): add triggering post setting update actions (#905)

* feat(auth): add triggering post setting update actions

* feat(auth): refactor email backups

* fix: add extra logs for backups

* fix: specs
This commit is contained in:
Karol Sójko 2023-11-07 15:14:51 +01:00 committed by GitHub
parent 0cb234aa47
commit d228a86f48
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 550 additions and 415 deletions

View file

@ -1,9 +1,5 @@
import 'reflect-metadata'
import { SettingName } from '@standardnotes/domain-core'
import { Stream } from 'stream'
import { Logger } from 'winston'
import * as dayjs from 'dayjs'
import * as utc from 'dayjs/plugin/utc'
@ -11,78 +7,13 @@ import * as utc from 'dayjs/plugin/utc'
import { ContainerConfigLoader } from '../src/Bootstrap/Container'
import TYPES from '../src/Bootstrap/Types'
import { Env } from '../src/Bootstrap/Env'
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
import { DomainEventFactoryInterface } from '../src/Domain/Event/DomainEventFactoryInterface'
import { SettingRepositoryInterface } from '../src/Domain/Setting/SettingRepositoryInterface'
import { MuteFailedBackupsEmailsOption } from '@standardnotes/settings'
import { RoleServiceInterface } from '../src/Domain/Role/RoleServiceInterface'
import { PermissionName } from '@standardnotes/features'
import { GetUserKeyParams } from '../src/Domain/UseCase/GetUserKeyParams/GetUserKeyParams'
import { TriggerEmailBackupForAllUsers } from '../src/Domain/UseCase/TriggerEmailBackupForAllUsers/TriggerEmailBackupForAllUsers'
const inputArgs = process.argv.slice(2)
const backupProvider = inputArgs[0]
const backupFrequency = inputArgs[1]
const backupFrequency = inputArgs[0]
const requestBackups = async (
settingRepository: SettingRepositoryInterface,
roleService: RoleServiceInterface,
domainEventFactory: DomainEventFactoryInterface,
domainEventPublisher: DomainEventPublisherInterface,
getUserKeyParamsUseCase: GetUserKeyParams,
): Promise<void> => {
const settingName = SettingName.create(SettingName.NAMES.EmailBackupFrequency).getValue()
const permissionName = PermissionName.DailyEmailBackup
const muteEmailsSettingName = SettingName.NAMES.MuteFailedBackupsEmails
const muteEmailsSettingValue = MuteFailedBackupsEmailsOption.Muted
const stream = await settingRepository.streamAllByNameAndValue(settingName, backupFrequency)
return new Promise((resolve, reject) => {
stream
.pipe(
new Stream.Transform({
objectMode: true,
transform: async (setting, _encoding, callback) => {
const userIsPermittedForEmailBackups = await roleService.userHasPermission(
setting.setting_user_uuid,
permissionName,
)
if (!userIsPermittedForEmailBackups) {
callback()
return
}
let userHasEmailsMuted = false
const emailsMutedSetting = await settingRepository.findOneByNameAndUserUuid(
muteEmailsSettingName,
setting.setting_user_uuid,
)
if (emailsMutedSetting !== null && emailsMutedSetting.props.value !== null) {
userHasEmailsMuted = emailsMutedSetting.props.value === muteEmailsSettingValue
}
const keyParamsResponse = await getUserKeyParamsUseCase.execute({
userUuid: setting.setting_user_uuid,
authenticated: false,
})
await domainEventPublisher.publish(
domainEventFactory.createEmailBackupRequestedEvent(
setting.setting_user_uuid,
emailsMutedSetting?.id.toString() as string,
userHasEmailsMuted,
keyParamsResponse.keyParams,
),
)
callback()
},
}),
)
.on('finish', resolve)
.on('error', reject)
})
const requestBackups = async (triggerEmailBackupForAllUsers: TriggerEmailBackupForAllUsers): Promise<void> => {
await triggerEmailBackupForAllUsers.execute({ backupFrequency })
}
const container = new ContainerConfigLoader('worker')
@ -94,24 +25,20 @@ void container.load().then((container) => {
const logger: Logger = container.get(TYPES.Auth_Logger)
logger.info(`Starting ${backupFrequency} ${backupProvider} backup requesting...`)
logger.info(`Starting ${backupFrequency} email backup requesting...`)
const settingRepository: SettingRepositoryInterface = container.get(TYPES.Auth_SettingRepository)
const roleService: RoleServiceInterface = container.get(TYPES.Auth_RoleService)
const domainEventFactory: DomainEventFactoryInterface = container.get(TYPES.Auth_DomainEventFactory)
const domainEventPublisher: DomainEventPublisherInterface = container.get(TYPES.Auth_DomainEventPublisher)
const getUserKeyParamsUseCase: GetUserKeyParams = container.get(TYPES.Auth_GetUserKeyParams)
Promise.resolve(
requestBackups(settingRepository, roleService, domainEventFactory, domainEventPublisher, getUserKeyParamsUseCase),
const triggerEmailBackupForAllUsers: TriggerEmailBackupForAllUsers = container.get(
TYPES.Auth_TriggerEmailBackupForAllUsers,
)
Promise.resolve(requestBackups(triggerEmailBackupForAllUsers))
.then(() => {
logger.info(`${backupFrequency} ${backupProvider} backup requesting complete`)
logger.info(`${backupFrequency} email backup requesting complete`)
process.exit(0)
})
.catch((error) => {
logger.error(`Could not finish ${backupFrequency} ${backupProvider} backup requesting: ${error.message}`)
logger.error(`Could not finish ${backupFrequency} email backup requesting: ${error.message}`)
process.exit(1)
})

View file

@ -26,12 +26,12 @@ case "$COMMAND" in
'email-daily-backup' )
echo "[Docker] Starting Email Daily Backup..."
node docker/entrypoint-backup.js email daily
node docker/entrypoint-backup.js daily
;;
'email-weekly-backup' )
echo "[Docker] Starting Email Weekly Backup..."
node docker/entrypoint-backup.js email weekly
node docker/entrypoint-backup.js weekly
;;
'email-backup' )
@ -40,21 +40,6 @@ case "$COMMAND" in
node docker/entrypoint-user-email-backup.js $EMAIL
;;
'dropbox-daily-backup' )
echo "[Docker] Starting Dropbox Daily Backup..."
node docker/entrypoint-backup.js dropbox daily
;;
'google-drive-daily-backup' )
echo "[Docker] Starting Google Drive Daily Backup..."
node docker/entrypoint-backup.js google_drive daily
;;
'one-drive-daily-backup' )
echo "[Docker] Starting One Drive Daily Backup..."
node docker/entrypoint-backup.js one_drive daily
;;
* )
echo "[Docker] Unknown command"
;;

View file

@ -24,12 +24,9 @@
"worker": "yarn node dist/bin/worker.js",
"cleanup": "yarn node dist/bin/cleanup.js",
"stats": "yarn node dist/bin/stats.js",
"daily-backup:email": "yarn node dist/bin/backup.js email daily",
"daily-backup:email": "yarn node dist/bin/backup.js daily",
"user-email-backup": "yarn node dist/bin/user_email_backup.js",
"daily-backup:dropbox": "yarn node dist/bin/backup.js dropbox daily",
"daily-backup:google_drive": "yarn node dist/bin/backup.js google_drive daily",
"daily-backup:one_drive": "yarn node dist/bin/backup.js one_drive daily",
"weekly-backup:email": "yarn node dist/bin/backup.js email weekly",
"weekly-backup:email": "yarn node dist/bin/backup.js weekly",
"content-recalculation": "yarn node dist/bin/content.js",
"typeorm": "typeorm-ts-node-commonjs",
"migrate": "yarn build && yarn typeorm migration:run -d dist/src/Bootstrap/DataSource.js"

View file

@ -130,8 +130,6 @@ import { ListedAccountCreatedEventHandler } from '../Domain/Handler/ListedAccoun
import { ListedAccountDeletedEventHandler } from '../Domain/Handler/ListedAccountDeletedEventHandler'
import { FileRemovedEventHandler } from '../Domain/Handler/FileRemovedEventHandler'
import { UserDisabledSessionUserAgentLoggingEventHandler } from '../Domain/Handler/UserDisabledSessionUserAgentLoggingEventHandler'
import { SettingInterpreterInterface } from '../Domain/Setting/SettingInterpreterInterface'
import { SettingInterpreter } from '../Domain/Setting/SettingInterpreter'
import { SettingCrypterInterface } from '../Domain/Setting/SettingCrypterInterface'
import { SettingCrypter } from '../Domain/Setting/SettingCrypter'
import { SharedSubscriptionInvitationRepositoryInterface } from '../Domain/SharedSubscription/SharedSubscriptionInvitationRepositoryInterface'
@ -275,6 +273,9 @@ import { SubscriptionSettingPersistenceMapper } from '../Mapping/Persistence/Sub
import { ApplyDefaultSettings } from '../Domain/UseCase/ApplyDefaultSettings/ApplyDefaultSettings'
import { AuthResponseFactoryResolverInterface } from '../Domain/Auth/AuthResponseFactoryResolverInterface'
import { UserInvitedToSharedVaultEventHandler } from '../Domain/Handler/UserInvitedToSharedVaultEventHandler'
import { TriggerPostSettingUpdateActions } from '../Domain/UseCase/TriggerPostSettingUpdateActions/TriggerPostSettingUpdateActions'
import { TriggerEmailBackupForUser } from '../Domain/UseCase/TriggerEmailBackupForUser/TriggerEmailBackupForUser'
import { TriggerEmailBackupForAllUsers } from '../Domain/UseCase/TriggerEmailBackupForAllUsers/TriggerEmailBackupForAllUsers'
export class ContainerConfigLoader {
constructor(private mode: 'server' | 'worker' = 'server') {}
@ -772,16 +773,6 @@ export class ContainerConfigLoader {
container.get<winston.Logger>(TYPES.Auth_Logger),
),
)
container
.bind<SettingInterpreterInterface>(TYPES.Auth_SettingInterpreter)
.toConstantValue(
new SettingInterpreter(
container.get<DomainEventPublisherInterface>(TYPES.Auth_DomainEventPublisher),
container.get<DomainEventFactoryInterface>(TYPES.Auth_DomainEventFactory),
container.get<SettingRepositoryInterface>(TYPES.Auth_SettingRepository),
container.get<GetUserKeyParams>(TYPES.Auth_GetUserKeyParams),
),
)
container.bind<OfflineSettingServiceInterface>(TYPES.Auth_OfflineSettingService).to(OfflineSettingService)
container.bind<ContentDecoderInterface>(TYPES.Auth_ContenDecoder).toConstantValue(new ContentDecoder())
@ -1231,6 +1222,35 @@ export class ContainerConfigLoader {
container.get<GetSharedOrRegularSubscriptionForUser>(TYPES.Auth_GetSharedOrRegularSubscriptionForUser),
),
)
container
.bind<TriggerEmailBackupForUser>(TYPES.Auth_TriggerEmailBackupForUser)
.toConstantValue(
new TriggerEmailBackupForUser(
container.get<RoleServiceInterface>(TYPES.Auth_RoleService),
container.get<GetSetting>(TYPES.Auth_GetSetting),
container.get<GetUserKeyParams>(TYPES.Auth_GetUserKeyParams),
container.get<DomainEventPublisherInterface>(TYPES.Auth_DomainEventPublisher),
container.get<DomainEventFactoryInterface>(TYPES.Auth_DomainEventFactory),
),
)
container
.bind<TriggerEmailBackupForAllUsers>(TYPES.Auth_TriggerEmailBackupForAllUsers)
.toConstantValue(
new TriggerEmailBackupForAllUsers(
container.get<SettingRepositoryInterface>(TYPES.Auth_SettingRepository),
container.get<TriggerEmailBackupForUser>(TYPES.Auth_TriggerEmailBackupForUser),
container.get<winston.Logger>(TYPES.Auth_Logger),
),
)
container
.bind<TriggerPostSettingUpdateActions>(TYPES.Auth_TriggerPostSettingUpdateActions)
.toConstantValue(
new TriggerPostSettingUpdateActions(
container.get<DomainEventPublisherInterface>(TYPES.Auth_DomainEventPublisher),
container.get<DomainEventFactoryInterface>(TYPES.Auth_DomainEventFactory),
container.get<TriggerEmailBackupForUser>(TYPES.Auth_TriggerEmailBackupForUser),
),
)
// Controller
container
@ -1655,11 +1675,13 @@ export class ContainerConfigLoader {
container.get<GetAllSettingsForUser>(TYPES.Auth_GetAllSettingsForUser),
container.get<GetSetting>(TYPES.Auth_GetSetting),
container.get<SetSettingValue>(TYPES.Auth_SetSettingValue),
container.get<TriggerPostSettingUpdateActions>(TYPES.Auth_TriggerPostSettingUpdateActions),
container.get<DeleteSetting>(TYPES.Auth_DeleteSetting),
container.get<MapperInterface<Setting, SettingHttpRepresentation>>(TYPES.Auth_SettingHttpMapper),
container.get<MapperInterface<SubscriptionSetting, SubscriptionSettingHttpRepresentation>>(
TYPES.Auth_SubscriptionSettingHttpMapper,
),
container.get<winston.Logger>(TYPES.Auth_Logger),
container.get<ControllerContainerInterface>(TYPES.Auth_ControllerContainer),
),
)

View file

@ -164,6 +164,9 @@ const TYPES = {
Auth_DesignateSurvivor: Symbol.for('Auth_DesignateSurvivor'),
Auth_GetSharedOrRegularSubscriptionForUser: Symbol.for('Auth_GetSharedOrRegularSubscriptionForUser'),
Auth_DisableEmailSettingBasedOnEmailSubscription: Symbol.for('Auth_DisableEmailSettingBasedOnEmailSubscription'),
Auth_TriggerPostSettingUpdateActions: Symbol.for('Auth_TriggerPostSettingUpdateActions'),
Auth_TriggerEmailBackupForUser: Symbol.for('Auth_TriggerEmailBackupForUser'),
Auth_TriggerEmailBackupForAllUsers: Symbol.for('Auth_TriggerEmailBackupForAllUsers'),
// Handlers
Auth_AccountDeletionRequestedEventHandler: Symbol.for('Auth_AccountDeletionRequestedEventHandler'),
Auth_SubscriptionPurchasedEventHandler: Symbol.for('Auth_SubscriptionPurchasedEventHandler'),
@ -230,7 +233,6 @@ const TYPES = {
Auth_SubscriptionSettingsAssociationService: Symbol.for('Auth_SubscriptionSettingsAssociationService'),
Auth_FeatureService: Symbol.for('Auth_FeatureService'),
Auth_SettingCrypter: Symbol.for('Auth_SettingCrypter'),
Auth_SettingInterpreter: Symbol.for('Auth_SettingInterpreter'),
Auth_ProtocolVersionSelector: Symbol.for('Auth_ProtocolVersionSelector'),
Auth_BooleanSelector: Symbol.for('Auth_BooleanSelector'),
Auth_BaseAuthController: Symbol.for('Auth_BaseAuthController'),

View file

@ -1,153 +0,0 @@
import {
DomainEventPublisherInterface,
EmailBackupRequestedEvent,
MuteEmailsSettingChangedEvent,
UserDisabledSessionUserAgentLoggingEvent,
} from '@standardnotes/domain-events'
import { EmailBackupFrequency, LogSessionUserAgentOption, MuteMarketingEmailsOption } from '@standardnotes/settings'
import 'reflect-metadata'
import { Logger } from 'winston'
import { DomainEventFactoryInterface } from '../Event/DomainEventFactoryInterface'
import { User } from '../User/User'
import { Setting } from './Setting'
import { SettingCrypterInterface } from './SettingCrypterInterface'
import { SettingInterpreter } from './SettingInterpreter'
import { SettingRepositoryInterface } from './SettingRepositoryInterface'
import { GetUserKeyParams } from '../UseCase/GetUserKeyParams/GetUserKeyParams'
import { KeyParamsData } from '@standardnotes/responses'
import { Uuid, Timestamps, UniqueEntityId, SettingName } from '@standardnotes/domain-core'
describe('SettingInterpreter', () => {
let user: User
let domainEventPublisher: DomainEventPublisherInterface
let domainEventFactory: DomainEventFactoryInterface
let settingRepository: SettingRepositoryInterface
let settingCrypter: SettingCrypterInterface
let logger: Logger
let getUserKeyParams: GetUserKeyParams
const createInterpreter = () =>
new SettingInterpreter(domainEventPublisher, domainEventFactory, settingRepository, getUserKeyParams)
beforeEach(() => {
user = {
uuid: '4-5-6',
email: 'test@test.te',
} as jest.Mocked<User>
settingRepository = {} as jest.Mocked<SettingRepositoryInterface>
settingRepository.findLastByNameAndUserUuid = jest.fn().mockReturnValue(null)
settingRepository.findOneByNameAndUserUuid = jest.fn().mockReturnValue(null)
settingCrypter = {} as jest.Mocked<SettingCrypterInterface>
settingCrypter.decryptSettingValue = jest.fn().mockReturnValue('decrypted')
domainEventPublisher = {} as jest.Mocked<DomainEventPublisherInterface>
domainEventPublisher.publish = jest.fn()
domainEventFactory = {} as jest.Mocked<DomainEventFactoryInterface>
domainEventFactory.createEmailBackupRequestedEvent = jest
.fn()
.mockReturnValue({} as jest.Mocked<EmailBackupRequestedEvent>)
domainEventFactory.createUserDisabledSessionUserAgentLoggingEvent = jest
.fn()
.mockReturnValue({} as jest.Mocked<UserDisabledSessionUserAgentLoggingEvent>)
domainEventFactory.createMuteEmailsSettingChangedEvent = jest
.fn()
.mockReturnValue({} as jest.Mocked<MuteEmailsSettingChangedEvent>)
logger = {} as jest.Mocked<Logger>
logger.debug = jest.fn()
logger.warn = jest.fn()
logger.error = jest.fn()
getUserKeyParams = {} as jest.Mocked<GetUserKeyParams>
getUserKeyParams.execute = jest.fn().mockReturnValue({ keyParams: {} as jest.Mocked<KeyParamsData> })
})
it('should trigger session cleanup if user is disabling session user agent logging', async () => {
await createInterpreter().interpretSettingUpdated(
SettingName.NAMES.LogSessionUserAgent,
user,
LogSessionUserAgentOption.Disabled,
)
expect(domainEventPublisher.publish).toHaveBeenCalled()
expect(domainEventFactory.createUserDisabledSessionUserAgentLoggingEvent).toHaveBeenCalledWith({
userUuid: '4-5-6',
email: 'test@test.te',
})
})
it('should trigger backup if email backup setting is created - emails not muted', async () => {
await createInterpreter().interpretSettingUpdated(
SettingName.NAMES.EmailBackupFrequency,
user,
EmailBackupFrequency.Daily,
)
expect(domainEventPublisher.publish).toHaveBeenCalled()
expect(domainEventFactory.createEmailBackupRequestedEvent).toHaveBeenCalledWith('4-5-6', '', false, {})
})
it('should trigger backup if email backup setting is created - emails muted', async () => {
const setting = Setting.create(
{
name: SettingName.NAMES.MuteFailedBackupsEmails,
value: 'muted',
serverEncryptionVersion: 0,
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
sensitive: false,
timestamps: Timestamps.create(123, 123).getValue(),
},
new UniqueEntityId('7fb54003-1dd2-40bd-8900-2bacd6cf629c'),
).getValue()
settingRepository.findOneByNameAndUserUuid = jest.fn().mockReturnValue(setting)
await createInterpreter().interpretSettingUpdated(
SettingName.NAMES.EmailBackupFrequency,
user,
EmailBackupFrequency.Daily,
)
expect(domainEventPublisher.publish).toHaveBeenCalled()
expect(domainEventFactory.createEmailBackupRequestedEvent).toHaveBeenCalledWith(
'4-5-6',
'7fb54003-1dd2-40bd-8900-2bacd6cf629c',
true,
{},
)
})
it('should not trigger backup if email backup setting is disabled', async () => {
settingRepository.findOneByNameAndUserUuid = jest.fn().mockReturnValue(null)
await createInterpreter().interpretSettingUpdated(
SettingName.NAMES.EmailBackupFrequency,
user,
EmailBackupFrequency.Disabled,
)
expect(domainEventPublisher.publish).not.toHaveBeenCalled()
expect(domainEventFactory.createEmailBackupRequestedEvent).not.toHaveBeenCalled()
})
it('should trigger mute subscription emails rejection if mute setting changed', async () => {
settingRepository.findOneByNameAndUserUuid = jest.fn().mockReturnValue(null)
await createInterpreter().interpretSettingUpdated(
SettingName.NAMES.MuteMarketingEmails,
user,
MuteMarketingEmailsOption.Muted,
)
expect(domainEventPublisher.publish).toHaveBeenCalled()
expect(domainEventFactory.createMuteEmailsSettingChangedEvent).toHaveBeenCalledWith({
emailSubscriptionRejectionLevel: 'MARKETING',
mute: true,
username: 'test@test.te',
})
})
})

View file

@ -1,113 +0,0 @@
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
import { EmailLevel, SettingName } from '@standardnotes/domain-core'
import { EmailBackupFrequency, LogSessionUserAgentOption, MuteFailedBackupsEmailsOption } from '@standardnotes/settings'
import { DomainEventFactoryInterface } from '../Event/DomainEventFactoryInterface'
import { User } from '../User/User'
import { SettingInterpreterInterface } from './SettingInterpreterInterface'
import { SettingRepositoryInterface } from './SettingRepositoryInterface'
import { GetUserKeyParams } from '../UseCase/GetUserKeyParams/GetUserKeyParams'
export class SettingInterpreter implements SettingInterpreterInterface {
private readonly emailSettingToSubscriptionRejectionLevelMap: Map<string, string> = new Map([
[SettingName.NAMES.MuteFailedBackupsEmails, EmailLevel.LEVELS.FailedEmailBackup],
[SettingName.NAMES.MuteFailedCloudBackupsEmails, EmailLevel.LEVELS.FailedCloudBackup],
[SettingName.NAMES.MuteMarketingEmails, EmailLevel.LEVELS.Marketing],
[SettingName.NAMES.MuteSignInEmails, EmailLevel.LEVELS.SignIn],
])
constructor(
private domainEventPublisher: DomainEventPublisherInterface,
private domainEventFactory: DomainEventFactoryInterface,
private settingRepository: SettingRepositoryInterface,
private getUserKeyParams: GetUserKeyParams,
) {}
async interpretSettingUpdated(
updatedSettingName: string,
user: User,
unencryptedValue: string | null,
): Promise<void> {
if (this.isChangingMuteEmailsSetting(updatedSettingName)) {
await this.triggerEmailSubscriptionChange(user, updatedSettingName, unencryptedValue)
}
if (this.isEnablingEmailBackupSetting(updatedSettingName, unencryptedValue)) {
await this.triggerEmailBackup(user.uuid)
}
if (this.isDisablingSessionUserAgentLogging(updatedSettingName, unencryptedValue)) {
await this.triggerSessionUserAgentCleanup(user)
}
}
private async triggerEmailBackup(userUuid: string): Promise<void> {
let userHasEmailsMuted = false
let muteEmailsSettingUuid = ''
const muteFailedEmailsBackupSetting = await this.settingRepository.findOneByNameAndUserUuid(
SettingName.NAMES.MuteFailedBackupsEmails,
userUuid,
)
if (muteFailedEmailsBackupSetting !== null) {
userHasEmailsMuted = muteFailedEmailsBackupSetting.props.value === MuteFailedBackupsEmailsOption.Muted
muteEmailsSettingUuid = muteFailedEmailsBackupSetting.id.toString()
}
const keyParamsResponse = await this.getUserKeyParams.execute({
authenticated: false,
userUuid,
})
await this.domainEventPublisher.publish(
this.domainEventFactory.createEmailBackupRequestedEvent(
userUuid,
muteEmailsSettingUuid,
userHasEmailsMuted,
keyParamsResponse.keyParams,
),
)
}
private isChangingMuteEmailsSetting(settingName: string): boolean {
return [
SettingName.NAMES.MuteFailedBackupsEmails,
SettingName.NAMES.MuteFailedCloudBackupsEmails,
SettingName.NAMES.MuteMarketingEmails,
SettingName.NAMES.MuteSignInEmails,
].includes(settingName)
}
private isEnablingEmailBackupSetting(settingName: string, newValue: string | null): boolean {
return (
settingName === SettingName.NAMES.EmailBackupFrequency &&
[EmailBackupFrequency.Daily, EmailBackupFrequency.Weekly].includes(newValue as EmailBackupFrequency)
)
}
private isDisablingSessionUserAgentLogging(settingName: string, newValue: string | null): boolean {
return SettingName.NAMES.LogSessionUserAgent === settingName && LogSessionUserAgentOption.Disabled === newValue
}
private async triggerEmailSubscriptionChange(
user: User,
settingName: string,
unencryptedValue: string | null,
): Promise<void> {
await this.domainEventPublisher.publish(
this.domainEventFactory.createMuteEmailsSettingChangedEvent({
username: user.email,
mute: unencryptedValue === 'muted',
emailSubscriptionRejectionLevel: this.emailSettingToSubscriptionRejectionLevelMap.get(settingName) as string,
}),
)
}
private async triggerSessionUserAgentCleanup(user: User) {
await this.domainEventPublisher.publish(
this.domainEventFactory.createUserDisabledSessionUserAgentLoggingEvent({
userUuid: user.uuid,
email: user.email,
}),
)
}
}

View file

@ -1,5 +0,0 @@
import { User } from '../User/User'
export interface SettingInterpreterInterface {
interpretSettingUpdated(updatedSettingName: string, user: User, newUnencryptedValue: string | null): Promise<void>
}

View file

@ -1,4 +1,3 @@
import { ReadStream } from 'fs'
import { SettingName } from '@standardnotes/domain-core'
import { DeleteSettingDto } from '../UseCase/DeleteSetting/DeleteSettingDto'
@ -10,8 +9,8 @@ export interface SettingRepositoryInterface {
findOneByNameAndUserUuid(name: string, userUuid: string): Promise<Setting | null>
findLastByNameAndUserUuid(name: string, userUuid: string): Promise<Setting | null>
findAllByUserUuid(userUuid: string): Promise<Setting[]>
streamAllByNameAndValue(name: SettingName, value: string): Promise<ReadStream>
streamAllByName(name: SettingName): Promise<ReadStream>
countAllByNameAndValue(dto: { name: SettingName; value: string }): Promise<number>
findAllByNameAndValue(dto: { name: SettingName; value: string; offset: number; limit: number }): Promise<Setting[]>
deleteByUserUuid(dto: DeleteSettingDto): Promise<void>
insert(setting: Setting): Promise<void>
update(setting: Setting): Promise<void>

View file

@ -0,0 +1,47 @@
import { Logger } from 'winston'
import { SettingRepositoryInterface } from '../../Setting/SettingRepositoryInterface'
import { TriggerEmailBackupForUser } from '../TriggerEmailBackupForUser/TriggerEmailBackupForUser'
import { TriggerEmailBackupForAllUsers } from './TriggerEmailBackupForAllUsers'
import { EncryptionVersion } from '../../Encryption/EncryptionVersion'
import { Setting } from '../../Setting/Setting'
import { Result, SettingName, Timestamps, Uuid } from '@standardnotes/domain-core'
describe('TriggerEmailBackupForAllUsers', () => {
let settingRepository: SettingRepositoryInterface
let triggerEmailBackupForUserUseCase: TriggerEmailBackupForUser
let logger: Logger
const createUseCase = () =>
new TriggerEmailBackupForAllUsers(settingRepository, triggerEmailBackupForUserUseCase, logger)
beforeEach(() => {
const setting = Setting.create({
name: SettingName.NAMES.EmailBackupFrequency,
value: null,
serverEncryptionVersion: EncryptionVersion.Default,
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
sensitive: false,
timestamps: Timestamps.create(123, 123).getValue(),
}).getValue()
settingRepository = {} as jest.Mocked<SettingRepositoryInterface>
settingRepository.countAllByNameAndValue = jest.fn().mockResolvedValue(1)
settingRepository.findAllByNameAndValue = jest.fn().mockResolvedValue([setting])
triggerEmailBackupForUserUseCase = {} as jest.Mocked<TriggerEmailBackupForUser>
triggerEmailBackupForUserUseCase.execute = jest.fn().mockResolvedValue(Result.ok())
logger = {} as jest.Mocked<Logger>
logger.error = jest.fn()
logger.info = jest.fn()
})
it('triggers email backup for all users', async () => {
const useCase = createUseCase()
const result = await useCase.execute({ backupFrequency: 'daily' })
expect(result.isFailed()).toBeFalsy()
})
})

View file

@ -0,0 +1,55 @@
import { Result, SettingName, UseCaseInterface } from '@standardnotes/domain-core'
import { TriggerEmailBackupForUser } from '../TriggerEmailBackupForUser/TriggerEmailBackupForUser'
import { SettingRepositoryInterface } from '../../Setting/SettingRepositoryInterface'
import { TriggerEmailBackupForAllUsersDTO } from './TriggerEmailBackupForAllUsersDTO'
import { Logger } from 'winston'
export class TriggerEmailBackupForAllUsers implements UseCaseInterface<void> {
private PAGING_LIMIT = 100
constructor(
private settingRepository: SettingRepositoryInterface,
private triggerEmailBackupForUserUseCase: TriggerEmailBackupForUser,
private logger: Logger,
) {}
async execute(dto: TriggerEmailBackupForAllUsersDTO): Promise<Result<void>> {
const emailBackupFrequencySettingName = SettingName.create(SettingName.NAMES.EmailBackupFrequency).getValue()
const allSettingsCount = await this.settingRepository.countAllByNameAndValue({
name: emailBackupFrequencySettingName,
value: dto.backupFrequency,
})
this.logger.info(`Found ${allSettingsCount} users with email backup frequency set to ${dto.backupFrequency}`)
let failedUsers = 0
const numberOfPages = Math.ceil(allSettingsCount / this.PAGING_LIMIT)
for (let i = 0; i < numberOfPages; i++) {
const settings = await this.settingRepository.findAllByNameAndValue({
name: emailBackupFrequencySettingName,
value: dto.backupFrequency,
offset: i * this.PAGING_LIMIT,
limit: this.PAGING_LIMIT,
})
for (const setting of settings) {
const result = await this.triggerEmailBackupForUserUseCase.execute({
userUuid: setting.props.userUuid.value,
})
/* istanbul ignore next */
if (result.isFailed()) {
this.logger.error(`Failed to trigger email backup for user ${setting.props.userUuid.value}`)
failedUsers++
}
}
}
/* istanbul ignore next */
if (failedUsers > 0) {
this.logger.error(`Failed to trigger email backup for ${failedUsers} users`)
}
return Result.ok()
}
}

View file

@ -0,0 +1,3 @@
export interface TriggerEmailBackupForAllUsersDTO {
backupFrequency: string
}

View file

@ -0,0 +1,78 @@
import { DomainEventPublisherInterface, EmailBackupRequestedEvent } from '@standardnotes/domain-events'
import { Result, SettingName, Timestamps, Uuid } from '@standardnotes/domain-core'
import { RoleServiceInterface } from '../../Role/RoleServiceInterface'
import { GetSetting } from '../GetSetting/GetSetting'
import { GetUserKeyParams } from '../GetUserKeyParams/GetUserKeyParams'
import { TriggerEmailBackupForUser } from './TriggerEmailBackupForUser'
import { DomainEventFactoryInterface } from '../../Event/DomainEventFactoryInterface'
import { Setting } from '../../Setting/Setting'
import { EncryptionVersion } from '../../Encryption/EncryptionVersion'
describe('TriggerEmailBackupForUser', () => {
let roleService: RoleServiceInterface
let getSetting: GetSetting
let getUserKeyParamsUseCase: GetUserKeyParams
let domainEventPublisher: DomainEventPublisherInterface
let domainEventFactory: DomainEventFactoryInterface
const createUseCase = () =>
new TriggerEmailBackupForUser(
roleService,
getSetting,
getUserKeyParamsUseCase,
domainEventPublisher,
domainEventFactory,
)
beforeEach(() => {
roleService = {} as jest.Mocked<RoleServiceInterface>
roleService.userHasPermission = jest.fn().mockResolvedValue(true)
const setting = Setting.create({
name: SettingName.NAMES.ListedAuthorSecrets,
value: null,
serverEncryptionVersion: EncryptionVersion.Default,
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
sensitive: false,
timestamps: Timestamps.create(123, 123).getValue(),
}).getValue()
getSetting = {} as jest.Mocked<GetSetting>
getSetting.execute = jest.fn().mockResolvedValue(Result.ok({ setting, decryptedValue: 'not_muted' }))
getUserKeyParamsUseCase = {} as jest.Mocked<GetUserKeyParams>
getUserKeyParamsUseCase.execute = jest.fn().mockResolvedValue({ keyParams: {} })
domainEventPublisher = {} as jest.Mocked<DomainEventPublisherInterface>
domainEventPublisher.publish = jest.fn()
domainEventFactory = {} as jest.Mocked<DomainEventFactoryInterface>
domainEventFactory.createEmailBackupRequestedEvent = jest.fn().mockReturnValue({} as EmailBackupRequestedEvent)
})
it('publishes EmailBackupRequestedEvent', async () => {
const useCase = createUseCase()
const result = await useCase.execute({ userUuid: '00000000-0000-0000-0000-000000000000' })
expect(result.isFailed()).toBeFalsy()
})
it('returns error if user uuid is invalid', async () => {
const useCase = createUseCase()
const result = await useCase.execute({ userUuid: 'invalid-uuid' })
expect(result.isFailed()).toBe(true)
})
it('returns error if user is not permitted for email backups', async () => {
roleService.userHasPermission = jest.fn().mockResolvedValue(false)
const useCase = createUseCase()
const result = await useCase.execute({ userUuid: '00000000-0000-0000-0000-000000000000' })
expect(result.isFailed()).toBe(true)
})
})

View file

@ -0,0 +1,66 @@
import { Result, SettingName, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
import { PermissionName } from '@standardnotes/features'
import { TriggerEmailBackupForUserDTO } from './TriggerEmailBackupForUserDTO'
import { RoleServiceInterface } from '../../Role/RoleServiceInterface'
import { GetSetting } from '../GetSetting/GetSetting'
import { MuteFailedBackupsEmailsOption } from '@standardnotes/settings'
import { GetUserKeyParams } from '../GetUserKeyParams/GetUserKeyParams'
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
import { DomainEventFactoryInterface } from '../../Event/DomainEventFactoryInterface'
export class TriggerEmailBackupForUser implements UseCaseInterface<void> {
constructor(
private roleService: RoleServiceInterface,
private getSetting: GetSetting,
private getUserKeyParamsUseCase: GetUserKeyParams,
private domainEventPublisher: DomainEventPublisherInterface,
private domainEventFactory: DomainEventFactoryInterface,
) {}
async execute(dto: TriggerEmailBackupForUserDTO): Promise<Result<void>> {
const userUuidOrError = Uuid.create(dto.userUuid)
if (userUuidOrError.isFailed()) {
return Result.fail(userUuidOrError.getError())
}
const userUuid = userUuidOrError.getValue()
const userIsPermittedForEmailBackups = await this.roleService.userHasPermission(
userUuid.value,
PermissionName.DailyEmailBackup,
)
if (!userIsPermittedForEmailBackups) {
return Result.fail(`User ${userUuid.value} is not permitted for email backups`)
}
let userHasEmailsMuted = false
const emailsMutedSettingOrError = await this.getSetting.execute({
allowSensitiveRetrieval: true,
decrypted: true,
settingName: SettingName.NAMES.MuteFailedBackupsEmails,
userUuid: userUuid.value,
})
let emailsMutedSetting = null
if (!emailsMutedSettingOrError.isFailed()) {
emailsMutedSetting = emailsMutedSettingOrError.getValue()
userHasEmailsMuted = emailsMutedSetting.decryptedValue === MuteFailedBackupsEmailsOption.Muted
}
const keyParamsResponse = await this.getUserKeyParamsUseCase.execute({
userUuid: userUuid.value,
authenticated: false,
})
await this.domainEventPublisher.publish(
this.domainEventFactory.createEmailBackupRequestedEvent(
userUuid.value,
emailsMutedSetting?.setting.id.toString() as string,
userHasEmailsMuted,
keyParamsResponse.keyParams,
),
)
return Result.ok()
}
}

View file

@ -0,0 +1,3 @@
export interface TriggerEmailBackupForUserDTO {
userUuid: string
}

View file

@ -0,0 +1,104 @@
import {
DomainEventPublisherInterface,
EmailBackupRequestedEvent,
MuteEmailsSettingChangedEvent,
UserDisabledSessionUserAgentLoggingEvent,
} from '@standardnotes/domain-events'
import { EmailBackupFrequency, LogSessionUserAgentOption, MuteMarketingEmailsOption } from '@standardnotes/settings'
import { SettingName, Result } from '@standardnotes/domain-core'
import { TriggerPostSettingUpdateActions } from './TriggerPostSettingUpdateActions'
import { DomainEventFactoryInterface } from '../../Event/DomainEventFactoryInterface'
import { TriggerEmailBackupForUser } from '../TriggerEmailBackupForUser/TriggerEmailBackupForUser'
describe('TriggerPostSettingUpdateActions', () => {
let domainEventPublisher: DomainEventPublisherInterface
let domainEventFactory: DomainEventFactoryInterface
let triggerEmailBackupForUser: TriggerEmailBackupForUser
const createUseCase = () =>
new TriggerPostSettingUpdateActions(domainEventPublisher, domainEventFactory, triggerEmailBackupForUser)
beforeEach(() => {
triggerEmailBackupForUser = {} as jest.Mocked<TriggerEmailBackupForUser>
triggerEmailBackupForUser.execute = jest.fn().mockReturnValue(Result.ok())
domainEventPublisher = {} as jest.Mocked<DomainEventPublisherInterface>
domainEventPublisher.publish = jest.fn()
domainEventFactory = {} as jest.Mocked<DomainEventFactoryInterface>
domainEventFactory.createEmailBackupRequestedEvent = jest
.fn()
.mockReturnValue({} as jest.Mocked<EmailBackupRequestedEvent>)
domainEventFactory.createUserDisabledSessionUserAgentLoggingEvent = jest
.fn()
.mockReturnValue({} as jest.Mocked<UserDisabledSessionUserAgentLoggingEvent>)
domainEventFactory.createMuteEmailsSettingChangedEvent = jest
.fn()
.mockReturnValue({} as jest.Mocked<MuteEmailsSettingChangedEvent>)
})
it('should trigger session cleanup if user is disabling session user agent logging', async () => {
await createUseCase().execute({
updatedSettingName: SettingName.NAMES.LogSessionUserAgent,
userUuid: '4-5-6',
userEmail: 'test@test.te',
unencryptedValue: LogSessionUserAgentOption.Disabled,
})
expect(domainEventPublisher.publish).toHaveBeenCalled()
expect(domainEventFactory.createUserDisabledSessionUserAgentLoggingEvent).toHaveBeenCalledWith({
userUuid: '4-5-6',
email: 'test@test.te',
})
})
it('should trigger backup if email backup setting is created - emails not muted', async () => {
await createUseCase().execute({
updatedSettingName: SettingName.NAMES.EmailBackupFrequency,
userUuid: '4-5-6',
userEmail: 'test@test.te',
unencryptedValue: EmailBackupFrequency.Daily,
})
expect(triggerEmailBackupForUser.execute).toHaveBeenCalled()
})
it('should trigger backup if email backup setting is created - emails muted', async () => {
await createUseCase().execute({
updatedSettingName: SettingName.NAMES.EmailBackupFrequency,
userUuid: '4-5-6',
userEmail: 'test@test.te',
unencryptedValue: EmailBackupFrequency.Daily,
})
expect(triggerEmailBackupForUser.execute).toHaveBeenCalled()
})
it('should not trigger backup if email backup setting is disabled', async () => {
await createUseCase().execute({
updatedSettingName: SettingName.NAMES.EmailBackupFrequency,
userUuid: '4-5-6',
userEmail: 'test@test.te',
unencryptedValue: EmailBackupFrequency.Disabled,
})
expect(triggerEmailBackupForUser.execute).not.toHaveBeenCalled()
})
it('should trigger mute subscription emails rejection if mute setting changed', async () => {
await createUseCase().execute({
updatedSettingName: SettingName.NAMES.MuteMarketingEmails,
userUuid: '4-5-6',
userEmail: 'test@test.te',
unencryptedValue: MuteMarketingEmailsOption.Muted,
})
expect(domainEventPublisher.publish).toHaveBeenCalled()
expect(domainEventFactory.createMuteEmailsSettingChangedEvent).toHaveBeenCalledWith({
emailSubscriptionRejectionLevel: 'MARKETING',
mute: true,
username: 'test@test.te',
})
})
})

View file

@ -0,0 +1,83 @@
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
import { EmailLevel, Result, SettingName, UseCaseInterface } from '@standardnotes/domain-core'
import { EmailBackupFrequency, LogSessionUserAgentOption } from '@standardnotes/settings'
import { TriggerPostSettingUpdateActionsDTO } from './TriggerPostSettingUpdateActionsDTO'
import { DomainEventFactoryInterface } from '../../Event/DomainEventFactoryInterface'
import { TriggerEmailBackupForUser } from '../TriggerEmailBackupForUser/TriggerEmailBackupForUser'
export class TriggerPostSettingUpdateActions implements UseCaseInterface<void> {
private readonly emailSettingToSubscriptionRejectionLevelMap: Map<string, string> = new Map([
[SettingName.NAMES.MuteFailedBackupsEmails, EmailLevel.LEVELS.FailedEmailBackup],
[SettingName.NAMES.MuteFailedCloudBackupsEmails, EmailLevel.LEVELS.FailedCloudBackup],
[SettingName.NAMES.MuteMarketingEmails, EmailLevel.LEVELS.Marketing],
[SettingName.NAMES.MuteSignInEmails, EmailLevel.LEVELS.SignIn],
])
constructor(
private domainEventPublisher: DomainEventPublisherInterface,
private domainEventFactory: DomainEventFactoryInterface,
private triggerEmailBackupForUser: TriggerEmailBackupForUser,
) {}
async execute(dto: TriggerPostSettingUpdateActionsDTO): Promise<Result<void>> {
if (this.isChangingMuteEmailsSetting(dto.updatedSettingName)) {
await this.triggerEmailSubscriptionChange(dto.userEmail, dto.updatedSettingName, dto.unencryptedValue)
}
if (this.isEnablingEmailBackupSetting(dto.updatedSettingName, dto.unencryptedValue)) {
await this.triggerEmailBackupForUser.execute({
userUuid: dto.userUuid,
})
}
if (this.isDisablingSessionUserAgentLogging(dto.updatedSettingName, dto.unencryptedValue)) {
await this.triggerSessionUserAgentCleanup(dto.userEmail, dto.userUuid)
}
return Result.ok()
}
private isChangingMuteEmailsSetting(settingName: string): boolean {
return [
SettingName.NAMES.MuteFailedBackupsEmails,
SettingName.NAMES.MuteFailedCloudBackupsEmails,
SettingName.NAMES.MuteMarketingEmails,
SettingName.NAMES.MuteSignInEmails,
].includes(settingName)
}
private isEnablingEmailBackupSetting(settingName: string, newValue: string | null): boolean {
return (
settingName === SettingName.NAMES.EmailBackupFrequency &&
[EmailBackupFrequency.Daily, EmailBackupFrequency.Weekly].includes(newValue as EmailBackupFrequency)
)
}
private isDisablingSessionUserAgentLogging(settingName: string, newValue: string | null): boolean {
return SettingName.NAMES.LogSessionUserAgent === settingName && LogSessionUserAgentOption.Disabled === newValue
}
private async triggerEmailSubscriptionChange(
userEmail: string,
settingName: string,
unencryptedValue: string | null,
): Promise<void> {
await this.domainEventPublisher.publish(
this.domainEventFactory.createMuteEmailsSettingChangedEvent({
username: userEmail,
mute: unencryptedValue === 'muted',
emailSubscriptionRejectionLevel: this.emailSettingToSubscriptionRejectionLevelMap.get(settingName) as string,
}),
)
}
private async triggerSessionUserAgentCleanup(userEmail: string, userUuid: string) {
await this.domainEventPublisher.publish(
this.domainEventFactory.createUserDisabledSessionUserAgentLoggingEvent({
userUuid,
email: userEmail,
}),
)
}
}

View file

@ -0,0 +1,6 @@
export interface TriggerPostSettingUpdateActionsDTO {
updatedSettingName: string
userUuid: string
userEmail: string
unencryptedValue: string | null
}

View file

@ -19,6 +19,8 @@ import { SubscriptionSetting } from '../../Domain/Setting/SubscriptionSetting'
import { SettingHttpRepresentation } from '../../Mapping/Http/SettingHttpRepresentation'
import { SubscriptionSettingHttpRepresentation } from '../../Mapping/Http/SubscriptionSettingHttpRepresentation'
import { GetAllSettingsForUser } from '../../Domain/UseCase/GetAllSettingsForUser/GetAllSettingsForUser'
import { TriggerPostSettingUpdateActions } from '../../Domain/UseCase/TriggerPostSettingUpdateActions/TriggerPostSettingUpdateActions'
import { Logger } from 'winston'
@controller('/users/:userUuid')
export class AnnotatedSettingsController extends BaseSettingsController {
@ -26,18 +28,23 @@ export class AnnotatedSettingsController extends BaseSettingsController {
@inject(TYPES.Auth_GetAllSettingsForUser) override doGetSettings: GetAllSettingsForUser,
@inject(TYPES.Auth_GetSetting) override doGetSetting: GetSetting,
@inject(TYPES.Auth_SetSettingValue) override setSettingValue: SetSettingValue,
@inject(TYPES.Auth_TriggerPostSettingUpdateActions)
override triggerPostSettingUpdateActions: TriggerPostSettingUpdateActions,
@inject(TYPES.Auth_DeleteSetting) override doDeleteSetting: DeleteSetting,
@inject(TYPES.Auth_SettingHttpMapper) settingHttMapper: MapperInterface<Setting, SettingHttpRepresentation>,
@inject(TYPES.Auth_SubscriptionSettingHttpMapper)
subscriptionSettingHttpMapper: MapperInterface<SubscriptionSetting, SubscriptionSettingHttpRepresentation>,
@inject(TYPES.Auth_Logger) logger: Logger,
) {
super(
doGetSettings,
doGetSetting,
setSettingValue,
triggerPostSettingUpdateActions,
doDeleteSetting,
settingHttMapper,
subscriptionSettingHttpMapper,
logger,
)
}

View file

@ -2,6 +2,7 @@ import { ControllerContainerInterface, MapperInterface } from '@standardnotes/do
import { BaseHttpController, results } from 'inversify-express-utils'
import { ErrorTag } from '@standardnotes/responses'
import { Request, Response } from 'express'
import { Logger } from 'winston'
import { DeleteSetting } from '../../../Domain/UseCase/DeleteSetting/DeleteSetting'
import { GetSetting } from '../../../Domain/UseCase/GetSetting/GetSetting'
@ -11,18 +12,21 @@ import { Setting } from '../../../Domain/Setting/Setting'
import { SubscriptionSetting } from '../../../Domain/Setting/SubscriptionSetting'
import { SubscriptionSettingHttpRepresentation } from '../../../Mapping/Http/SubscriptionSettingHttpRepresentation'
import { SettingHttpRepresentation } from '../../../Mapping/Http/SettingHttpRepresentation'
import { TriggerPostSettingUpdateActions } from '../../../Domain/UseCase/TriggerPostSettingUpdateActions/TriggerPostSettingUpdateActions'
export class BaseSettingsController extends BaseHttpController {
constructor(
protected doGetSettings: GetAllSettingsForUser,
protected doGetSetting: GetSetting,
protected setSettingValue: SetSettingValue,
protected triggerPostSettingUpdateActions: TriggerPostSettingUpdateActions,
protected doDeleteSetting: DeleteSetting,
protected settingHttMapper: MapperInterface<Setting, SettingHttpRepresentation>,
protected subscriptionSettingHttpMapper: MapperInterface<
SubscriptionSetting,
SubscriptionSettingHttpRepresentation
>,
protected logger: Logger,
private controllerContainer?: ControllerContainerInterface,
) {
super()
@ -175,6 +179,16 @@ export class BaseSettingsController extends BaseHttpController {
}
const setting = result.getValue()
const triggerResult = await this.triggerPostSettingUpdateActions.execute({
updatedSettingName: setting.props.name,
userUuid: response.locals.user.uuid,
userEmail: response.locals.user.email,
unencryptedValue: value,
})
if (triggerResult.isFailed()) {
this.logger.error(`Failed to trigger post setting update actions: ${triggerResult.getError()}`)
}
return this.json({
success: true,
setting: setting.props.sensitive ? undefined : this.settingHttMapper.toProjection(setting),

View file

@ -1,4 +1,3 @@
import { ReadStream } from 'fs'
import { Repository } from 'typeorm'
import { Setting } from '../../Domain/Setting/Setting'
@ -13,6 +12,36 @@ export class TypeORMSettingRepository implements SettingRepositoryInterface {
private mapper: MapperInterface<Setting, TypeORMSetting>,
) {}
async countAllByNameAndValue(dto: { name: SettingName; value: string }): Promise<number> {
return this.ormRepository
.createQueryBuilder()
.where('name = :name AND value = :value', {
name: dto.name.value,
value: dto.value,
})
.getCount()
}
async findAllByNameAndValue(dto: {
name: SettingName
value: string
offset: number
limit: number
}): Promise<Setting[]> {
const persistence = await this.ormRepository
.createQueryBuilder()
.where('name = :name AND value = :value', {
name: dto.name.value,
value: dto.value,
})
.orderBy('created_at', 'ASC')
.take(dto.limit)
.skip(dto.offset)
.getMany()
return persistence.map((p) => this.mapper.toDomain(p))
}
async insert(setting: Setting): Promise<void> {
const persistence = this.mapper.toProjection(setting)
@ -42,27 +71,6 @@ export class TypeORMSettingRepository implements SettingRepositoryInterface {
return this.mapper.toDomain(persistence)
}
async streamAllByName(name: SettingName): Promise<ReadStream> {
return this.ormRepository
.createQueryBuilder('setting')
.where('setting.name = :name', {
name: name.value,
})
.orderBy('updated_at', 'ASC')
.stream()
}
async streamAllByNameAndValue(name: SettingName, value: string): Promise<ReadStream> {
return this.ormRepository
.createQueryBuilder('setting')
.where('setting.name = :name AND setting.value = :value', {
name: name.value,
value,
})
.orderBy('updated_at', 'ASC')
.stream()
}
async findOneByUuid(uuid: string): Promise<Setting | null> {
const persistence = await this.ormRepository
.createQueryBuilder('setting')