Compare commits

..

1 Commits

Author SHA1 Message Date
dependabot[bot]
0055edc3e4 chore(deps): bump actions/setup-node from 3 to 4
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 3 to 4.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-06 06:55:37 +00:00
113 changed files with 844 additions and 1096 deletions

2
.github/ci.env vendored
View File

@@ -26,5 +26,3 @@ MYSQL_ROOT_PASSWORD=changeme123
AUTH_JWT_SECRET=f95259c5e441f5a4646d76422cfb3df4c4488842901aa50b6c51b8be2e0040e9
AUTH_SERVER_ENCRYPTION_SERVER_KEY=1087415dfde3093797f9a7ca93a49e7d7aa1861735eb0d32aae9c303b8c3d060
VALET_TOKEN_SECRET=4b886819ebe1e908077c6cae96311b48a8416bd60cc91c03060e15bdf6b30d1f
SYNCING_SERVER_CONTENT_SIZE_TRANSFER_LIMIT=1000000

View File

@@ -42,7 +42,7 @@ jobs:
key: ${{ runner.os }}-${{ inputs.service_name }}-build-${{ github.sha }}
- name: Set up Node
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
registry-url: 'https://registry.npmjs.org'
node-version-file: '.nvmrc'

View File

@@ -46,7 +46,7 @@ jobs:
- uses: actions/checkout@v4
- name: Set up Node
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
registry-url: 'https://registry.npmjs.org'
node-version-file: '.nvmrc'
@@ -70,7 +70,6 @@ jobs:
echo "ACCESS_TOKEN_AGE=4" >> packages/home-server/.env
echo "REFRESH_TOKEN_AGE=10" >> packages/home-server/.env
echo "REVISIONS_FREQUENCY=2" >> packages/home-server/.env
echo "CONTENT_SIZE_TRANSFER_LIMIT=1000000" >> packages/home-server/.env
echo "DB_HOST=localhost" >> packages/home-server/.env
echo "DB_PORT=3306" >> packages/home-server/.env
echo "DB_DATABASE=standardnotes" >> packages/home-server/.env

View File

@@ -29,7 +29,7 @@ jobs:
- uses: actions/checkout@v4
- name: Set up Node
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
registry-url: 'https://registry.npmjs.org'
node-version-file: '.nvmrc'

View File

@@ -20,7 +20,7 @@ jobs:
key: ${{ runner.os }}-build-${{ github.sha }}
- name: Set up Node
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
registry-url: 'https://registry.npmjs.org'
node-version-file: '.nvmrc'
@@ -48,7 +48,7 @@ jobs:
key: ${{ runner.os }}-build-${{ github.sha }}
- name: Set up Node
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
registry-url: 'https://registry.npmjs.org'
node-version-file: '.nvmrc'
@@ -80,7 +80,7 @@ jobs:
key: ${{ runner.os }}-build-${{ github.sha }}
- name: Set up Node
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
registry-url: 'https://registry.npmjs.org'
node-version-file: '.nvmrc'

View File

@@ -20,7 +20,7 @@ jobs:
key: ${{ runner.os }}-build-${{ github.sha }}
- name: Set up Node
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
registry-url: 'https://registry.npmjs.org'
node-version-file: '.nvmrc'
@@ -48,7 +48,7 @@ jobs:
key: ${{ runner.os }}-build-${{ github.sha }}
- name: Set up Node
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
registry-url: 'https://registry.npmjs.org'
node-version-file: '.nvmrc'
@@ -80,7 +80,7 @@ jobs:
key: ${{ runner.os }}-build-${{ github.sha }}
- name: Set up Node
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
registry-url: 'https://registry.npmjs.org'
node-version-file: '.nvmrc'
@@ -151,7 +151,7 @@ jobs:
git_commit_gpgsign: true
- name: Set up Node
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
registry-url: 'https://registry.npmjs.org'
node-version-file: '.nvmrc'

View File

@@ -3,16 +3,6 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [2.32.6](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.32.5...@standardnotes/analytics@2.32.6) (2023-11-07)
**Note:** Version bump only for package @standardnotes/analytics
## [2.32.5](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.32.4...@standardnotes/analytics@2.32.5) (2023-11-07)
### Bug Fixes
* remove open telemetry from code ([#903](https://github.com/standardnotes/server/issues/903)) ([751f3b2](https://github.com/standardnotes/server/commit/751f3b25476c5be3d663ad8540c43266acd39493))
## [2.32.4](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.32.3...@standardnotes/analytics@2.32.4) (2023-10-26)
**Note:** Version bump only for package @standardnotes/analytics

View File

@@ -1,5 +1,11 @@
import 'reflect-metadata'
import { OpenTelemetrySDK, OpenTelemetryTracer } from '@standardnotes/domain-events-infra'
import { EmailLevel, ServiceIdentifier } from '@standardnotes/domain-core'
const sdk = new OpenTelemetrySDK({ serviceName: ServiceIdentifier.NAMES.AnalyticsScheduledTask })
sdk.start()
import { Logger } from 'winston'
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
@@ -16,7 +22,6 @@ import { CalculateMonthlyRecurringRevenue } from '../src/Domain/UseCase/Calculat
import { getBody, getSubject } from '../src/Domain/Email/DailyAnalyticsReport'
import { TimerInterface } from '@standardnotes/time'
import { StatisticMeasureName } from '../src/Domain/Statistics/StatisticMeasureName'
import { EmailLevel } from '@standardnotes/domain-core'
const requestReport = async (
analyticsStore: AnalyticsStoreInterface,
@@ -270,6 +275,9 @@ void container.load().then((container) => {
logger.info(`Sending report to following admins: ${adminEmails}`)
const tracer = new OpenTelemetryTracer()
tracer.startSpan(ServiceIdentifier.NAMES.AnalyticsScheduledTask, 'report')
Promise.resolve(
requestReport(
analyticsStore,
@@ -285,11 +293,15 @@ void container.load().then((container) => {
.then(() => {
logger.info('Usage report generation complete')
tracer.stopSpan()
process.exit(0)
})
.catch((error) => {
logger.error(`Could not finish usage report generation: ${error.message}`)
tracer.stopSpanWithError(error)
process.exit(1)
})
})

View File

@@ -1,5 +1,11 @@
import 'reflect-metadata'
import { OpenTelemetrySDK } from '@standardnotes/domain-events-infra'
import { ServiceIdentifier } from '@standardnotes/domain-core'
const sdk = new OpenTelemetrySDK({ serviceName: ServiceIdentifier.NAMES.AnalyticsWorker })
sdk.start()
import { Logger } from 'winston'
import { DomainEventSubscriberInterface } from '@standardnotes/domain-events'
import * as dayjs from 'dayjs'

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/analytics",
"version": "2.32.6",
"version": "2.32.4",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -7,7 +7,7 @@ import {
DomainEventPublisherInterface,
DomainEventSubscriberInterface,
} from '@standardnotes/domain-events'
import { MapperInterface } from '@standardnotes/domain-core'
import { MapperInterface, ServiceIdentifier } from '@standardnotes/domain-core'
// eslint-disable-next-line @typescript-eslint/no-var-requires
const Mixpanel = require('mixpanel')
@@ -16,9 +16,9 @@ import TYPES from './Types'
import { AppDataSource } from './DataSource'
import { DomainEventFactory } from '../Domain/Event/DomainEventFactory'
import {
SNSDomainEventPublisher,
SQSDomainEventSubscriber,
SNSOpenTelemetryDomainEventPublisher,
SQSEventMessageHandler,
SQSOpenTelemetryDomainEventSubscriber,
} from '@standardnotes/domain-events-infra'
import { Timer, TimerInterface } from '@standardnotes/time'
import { PeriodKeyGeneratorInterface } from '../Domain/Time/PeriodKeyGeneratorInterface'
@@ -139,7 +139,9 @@ export class ContainerConfigLoader {
container
.bind<DomainEventPublisherInterface>(TYPES.DomainEventPublisher)
.toConstantValue(new SNSDomainEventPublisher(container.get(TYPES.SNS), container.get(TYPES.SNS_TOPIC_ARN)))
.toConstantValue(
new SNSOpenTelemetryDomainEventPublisher(container.get(TYPES.SNS), container.get(TYPES.SNS_TOPIC_ARN)),
)
if (env.get('MIXPANEL_TOKEN', true)) {
container.bind<Mixpanel>(TYPES.MixpanelClient).toConstantValue(Mixpanel.init(env.get('MIXPANEL_TOKEN', true)))
}
@@ -240,7 +242,8 @@ export class ContainerConfigLoader {
container
.bind<DomainEventSubscriberInterface>(TYPES.DomainEventSubscriber)
.toConstantValue(
new SQSDomainEventSubscriber(
new SQSOpenTelemetryDomainEventSubscriber(
ServiceIdentifier.NAMES.AnalyticsWorker,
container.get<SQSClient>(TYPES.SQS),
container.get<string>(TYPES.SQS_QUEUE_URL),
container.get<DomainEventMessageHandlerInterface>(TYPES.DomainEventMessageHandler),

View File

@@ -3,22 +3,6 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.81.11](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.81.10...@standardnotes/api-gateway@1.81.11) (2023-11-07)
### Bug Fixes
* **api-gateway:** remove calling both auth and payments on account deletion request ([6b554c2](https://github.com/standardnotes/api-gateway/commit/6b554c28b731a9080d7ad2942d3fa05c01dcabf2))
## [1.81.10](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.81.9...@standardnotes/api-gateway@1.81.10) (2023-11-07)
**Note:** Version bump only for package @standardnotes/api-gateway
## [1.81.9](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.81.8...@standardnotes/api-gateway@1.81.9) (2023-11-07)
### Bug Fixes
* remove open telemetry from code ([#903](https://github.com/standardnotes/api-gateway/issues/903)) ([751f3b2](https://github.com/standardnotes/api-gateway/commit/751f3b25476c5be3d663ad8540c43266acd39493))
## [1.81.8](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.81.7...@standardnotes/api-gateway@1.81.8) (2023-11-03)
### Bug Fixes

View File

@@ -1,5 +1,11 @@
import 'reflect-metadata'
import { OpenTelemetrySDK } from '@standardnotes/domain-events-infra'
import { ServiceIdentifier } from '@standardnotes/domain-core'
const sdk = new OpenTelemetrySDK({ serviceName: ServiceIdentifier.NAMES.ApiGateway })
sdk.start()
import '../src/Controller/LegacyController'
import '../src/Controller/HealthCheckController'

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/api-gateway",
"version": "1.81.11",
"version": "1.81.8",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -23,6 +23,7 @@ export class UsersController extends BaseHttpController {
@inject(TYPES.ApiGateway_ServiceProxy) private httpService: ServiceProxyInterface,
@inject(TYPES.ApiGateway_EndpointResolver) private endpointResolver: EndpointResolverInterface,
@inject(TYPES.ApiGateway_Logger) private logger: Logger,
@inject(TYPES.ApiGateway_IS_CONFIGURED_FOR_HOME_SERVER) private isConfiguredForHomeServer: boolean,
) {
super()
}
@@ -237,6 +238,10 @@ export class UsersController extends BaseHttpController {
@httpDelete('/:userUuid', TYPES.ApiGateway_RequiredCrossServiceTokenMiddleware)
async deleteUser(request: Request, response: Response): Promise<void> {
if (!this.isConfiguredForHomeServer) {
await this.httpService.callPaymentsServer(request, response, 'api/account', request.body, true)
}
await this.httpService.callAuthServer(
request,
response,

View File

@@ -151,6 +151,7 @@ export class HttpServiceProxy implements ServiceProxyInterface {
response: Response,
endpointOrMethodIdentifier: string,
payload?: Record<string, unknown> | string,
returnRawResponse?: boolean,
): Promise<void | Response<unknown, Record<string, unknown>>> {
if (!this.paymentsServerUrl) {
this.logger.debug('Payments Server URL not defined. Skipped request to Payments API.')
@@ -158,13 +159,18 @@ export class HttpServiceProxy implements ServiceProxyInterface {
return
}
await this.callServerWithLegacyFormat(
const rawResponse = await this.callServerWithLegacyFormat(
this.paymentsServerUrl,
request,
response,
endpointOrMethodIdentifier,
payload,
returnRawResponse,
)
if (returnRawResponse) {
return rawResponse
}
}
async callAuthServerWithLegacyFormat(
@@ -339,6 +345,7 @@ export class HttpServiceProxy implements ServiceProxyInterface {
response: Response,
endpointOrMethodIdentifier: string,
payload?: Record<string, unknown> | string,
returnRawResponse?: boolean,
): Promise<void | Response<unknown, Record<string, unknown>>> {
const serviceResponse = await this.getServerResponse(
serverUrl,
@@ -357,10 +364,18 @@ export class HttpServiceProxy implements ServiceProxyInterface {
if (serviceResponse.request._redirectable._redirectCount > 0) {
response.status(302)
if (returnRawResponse) {
return response
}
response.redirect(serviceResponse.request.res.responseUrl)
} else {
response.status(serviceResponse.status)
if (returnRawResponse) {
return response
}
response.send(serviceResponse.data)
}
}

View File

@@ -42,6 +42,7 @@ export interface ServiceProxyInterface {
response: Response,
endpointOrMethodIdentifier: string,
payload?: Record<string, unknown> | string,
returnRawResponse?: boolean,
): Promise<void | Response<unknown, Record<string, unknown>>>
callWebSocketServer(
request: Request,

View File

@@ -3,24 +3,6 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.166.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.165.4...@standardnotes/auth-server@1.166.0) (2023-11-07)
### Features
* **auth:** add triggering post setting update actions ([#905](https://github.com/standardnotes/server/issues/905)) ([d228a86](https://github.com/standardnotes/server/commit/d228a86f48c9ff62b7810244c347abf7770e2b9f))
## [1.165.4](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.165.3...@standardnotes/auth-server@1.165.4) (2023-11-07)
### Bug Fixes
* account deletion event ([#904](https://github.com/standardnotes/server/issues/904)) ([d66ae62](https://github.com/standardnotes/server/commit/d66ae62cf4f413cac5f6f4eac45dc0f1ddbc9e32))
## [1.165.3](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.165.2...@standardnotes/auth-server@1.165.3) (2023-11-07)
### Bug Fixes
* remove open telemetry from code ([#903](https://github.com/standardnotes/server/issues/903)) ([751f3b2](https://github.com/standardnotes/server/commit/751f3b25476c5be3d663ad8540c43266acd39493))
## [1.165.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.165.1...@standardnotes/auth-server@1.165.2) (2023-11-03)
### Bug Fixes

View File

@@ -1,5 +1,13 @@
import 'reflect-metadata'
import { OpenTelemetrySDK, OpenTelemetryTracer } from '@standardnotes/domain-events-infra'
import { ServiceIdentifier, SettingName } from '@standardnotes/domain-core'
const sdk = new OpenTelemetrySDK({ serviceName: ServiceIdentifier.NAMES.AuthScheduledTask })
sdk.start()
import { Stream } from 'stream'
import { Logger } from 'winston'
import * as dayjs from 'dayjs'
import * as utc from 'dayjs/plugin/utc'
@@ -7,13 +15,78 @@ 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 { TriggerEmailBackupForAllUsers } from '../src/Domain/UseCase/TriggerEmailBackupForAllUsers/TriggerEmailBackupForAllUsers'
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'
const inputArgs = process.argv.slice(2)
const backupFrequency = inputArgs[0]
const backupProvider = inputArgs[0]
const backupFrequency = inputArgs[1]
const requestBackups = async (triggerEmailBackupForAllUsers: TriggerEmailBackupForAllUsers): Promise<void> => {
await triggerEmailBackupForAllUsers.execute({ backupFrequency })
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 container = new ContainerConfigLoader('worker')
@@ -25,20 +98,31 @@ void container.load().then((container) => {
const logger: Logger = container.get(TYPES.Auth_Logger)
logger.info(`Starting ${backupFrequency} email backup requesting...`)
logger.info(`Starting ${backupFrequency} ${backupProvider} backup requesting...`)
const triggerEmailBackupForAllUsers: TriggerEmailBackupForAllUsers = container.get(
TYPES.Auth_TriggerEmailBackupForAllUsers,
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)
const tracer = new OpenTelemetryTracer()
tracer.startSpan(ServiceIdentifier.NAMES.AuthScheduledTask, 'backup')
Promise.resolve(
requestBackups(settingRepository, roleService, domainEventFactory, domainEventPublisher, getUserKeyParamsUseCase),
)
Promise.resolve(requestBackups(triggerEmailBackupForAllUsers))
.then(() => {
logger.info(`${backupFrequency} email backup requesting complete`)
logger.info(`${backupFrequency} ${backupProvider} backup requesting complete`)
tracer.stopSpan()
process.exit(0)
})
.catch((error) => {
logger.error(`Could not finish ${backupFrequency} email backup requesting: ${error.message}`)
logger.error(`Could not finish ${backupFrequency} ${backupProvider} backup requesting: ${error.message}`)
tracer.stopSpanWithError(error)
process.exit(1)
})

View File

@@ -1,5 +1,11 @@
import 'reflect-metadata'
import { OpenTelemetrySDK, OpenTelemetryTracer } from '@standardnotes/domain-events-infra'
import { ServiceIdentifier } from '@standardnotes/domain-core'
const sdk = new OpenTelemetrySDK({ serviceName: ServiceIdentifier.NAMES.AuthScheduledTask })
sdk.start()
import { Logger } from 'winston'
import { ContainerConfigLoader } from '../src/Bootstrap/Container'
@@ -30,15 +36,22 @@ void container.load().then((container) => {
const cleanupSessionTraces: CleanupSessionTraces = container.get(TYPES.Auth_CleanupSessionTraces)
const cleanupExpiredSessions: CleanupExpiredSessions = container.get(TYPES.Auth_CleanupExpiredSessions)
const tracer = new OpenTelemetryTracer()
tracer.startSpan(ServiceIdentifier.NAMES.AuthScheduledTask, 'cleanup')
Promise.resolve(cleanup(cleanupSessionTraces, cleanupExpiredSessions))
.then(() => {
logger.info('Expired sessions and session traces cleaned.')
tracer.stopSpan()
process.exit(0)
})
.catch((error) => {
logger.error(`Could not clean sessions and session traces: ${error.message}`)
tracer.stopSpanWithError(error)
process.exit(1)
})
})

View File

@@ -1,5 +1,11 @@
import 'reflect-metadata'
import { OpenTelemetrySDK } from '@standardnotes/domain-events-infra'
import { ServiceIdentifier } from '@standardnotes/domain-core'
const sdk = new OpenTelemetrySDK({ serviceName: ServiceIdentifier.NAMES.Auth })
sdk.start()
import '../src/Infra/InversifyExpressUtils/AnnotatedAuthController'
import '../src/Infra/InversifyExpressUtils/AnnotatedAuthenticatorsController'
import '../src/Infra/InversifyExpressUtils/AnnotatedSessionsController'

View File

@@ -1,5 +1,11 @@
import 'reflect-metadata'
import { OpenTelemetrySDK, OpenTelemetryTracer } from '@standardnotes/domain-events-infra'
import { ServiceIdentifier } from '@standardnotes/domain-core'
const sdk = new OpenTelemetrySDK({ serviceName: ServiceIdentifier.NAMES.AuthScheduledTask })
sdk.start()
import { Logger } from 'winston'
import { TimerInterface } from '@standardnotes/time'
@@ -20,6 +26,9 @@ void container.load().then((container) => {
const persistStats: PersistStatistics = container.get(TYPES.Auth_PersistStatistics)
const timer: TimerInterface = container.get(TYPES.Auth_Timer)
const tracer = new OpenTelemetryTracer()
tracer.startSpan(ServiceIdentifier.NAMES.AuthScheduledTask, 'stats')
Promise.resolve(
persistStats.execute({
sessionsInADay: timer.getUTCDateNDaysAgo(1),
@@ -28,11 +37,15 @@ void container.load().then((container) => {
.then(() => {
logger.info('Stats persisted.')
tracer.stopSpan()
process.exit(0)
})
.catch((error) => {
logger.error(`Could not persist stats: ${error.message}`)
tracer.stopSpanWithError(error)
process.exit(1)
})
})

View File

@@ -1,5 +1,11 @@
import 'reflect-metadata'
import { OpenTelemetrySDK, OpenTelemetryTracer } from '@standardnotes/domain-events-infra'
import { Email, ServiceIdentifier, SettingName } from '@standardnotes/domain-core'
const sdk = new OpenTelemetrySDK({ serviceName: ServiceIdentifier.NAMES.AuthScheduledTask })
sdk.start()
import { Logger } from 'winston'
import * as dayjs from 'dayjs'
import * as utc from 'dayjs/plugin/utc'
@@ -15,7 +21,6 @@ import { RoleServiceInterface } from '../src/Domain/Role/RoleServiceInterface'
import { PermissionName } from '@standardnotes/features'
import { UserRepositoryInterface } from '../src/Domain/User/UserRepositoryInterface'
import { GetUserKeyParams } from '../src/Domain/UseCase/GetUserKeyParams/GetUserKeyParams'
import { Email, SettingName } from '@standardnotes/domain-core'
const inputArgs = process.argv.slice(2)
const backupEmail = inputArgs[0]
@@ -89,6 +94,9 @@ void container.load().then((container) => {
const domainEventPublisher: DomainEventPublisherInterface = container.get(TYPES.Auth_DomainEventPublisher)
const getUserKeyParamsUseCase: GetUserKeyParams = container.get(TYPES.Auth_GetUserKeyParams)
const tracer = new OpenTelemetryTracer()
tracer.startSpan(ServiceIdentifier.NAMES.AuthScheduledTask, 'user_email_backup')
Promise.resolve(
requestBackups(
userRepository,
@@ -102,11 +110,15 @@ void container.load().then((container) => {
.then(() => {
logger.info(`Email backup requesting complete for ${backupEmail}`)
tracer.stopSpan()
process.exit(0)
})
.catch((error) => {
logger.error(`Could not finish email backup requesting for ${backupEmail}: ${error.message}`)
tracer.stopSpanWithError(error)
process.exit(1)
})
})

View File

@@ -1,5 +1,11 @@
import 'reflect-metadata'
import { OpenTelemetrySDK } from '@standardnotes/domain-events-infra'
import { ServiceIdentifier } from '@standardnotes/domain-core'
const sdk = new OpenTelemetrySDK({ serviceName: ServiceIdentifier.NAMES.AuthWorker })
sdk.start()
import { Logger } from 'winston'
import { ContainerConfigLoader } from '../src/Bootstrap/Container'

View File

@@ -26,12 +26,12 @@ case "$COMMAND" in
'email-daily-backup' )
echo "[Docker] Starting Email Daily Backup..."
node docker/entrypoint-backup.js daily
node docker/entrypoint-backup.js email daily
;;
'email-weekly-backup' )
echo "[Docker] Starting Email Weekly Backup..."
node docker/entrypoint-backup.js weekly
node docker/entrypoint-backup.js email weekly
;;
'email-backup' )
@@ -40,6 +40,21 @@ 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

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/auth-server",
"version": "1.166.0",
"version": "1.165.2",
"engines": {
"node": ">=18.0.0 <21.0.0"
},
@@ -24,9 +24,12 @@
"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 daily",
"daily-backup:email": "yarn node dist/bin/backup.js email daily",
"user-email-backup": "yarn node dist/bin/user_email_backup.js",
"weekly-backup:email": "yarn node dist/bin/backup.js weekly",
"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",
"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

@@ -79,9 +79,9 @@ import { ExtensionKeyGrantedEventHandler } from '../Domain/Handler/ExtensionKeyG
import {
DirectCallDomainEventPublisher,
DirectCallEventMessageHandler,
SNSDomainEventPublisher,
SQSDomainEventSubscriber,
SNSOpenTelemetryDomainEventPublisher,
SQSEventMessageHandler,
SQSOpenTelemetryDomainEventSubscriber,
} from '@standardnotes/domain-events-infra'
import { GetUserSubscription } from '../Domain/UseCase/GetUserSubscription/GetUserSubscription'
import { ChangeCredentials } from '../Domain/UseCase/ChangeCredentials/ChangeCredentials'
@@ -130,6 +130,8 @@ 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'
@@ -168,6 +170,7 @@ import {
ControllerContainer,
ControllerContainerInterface,
MapperInterface,
ServiceIdentifier,
SharedVaultUser,
} from '@standardnotes/domain-core'
import { SessionTracePersistenceMapper } from '../Mapping/SessionTracePersistenceMapper'
@@ -273,9 +276,6 @@ 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') {}
@@ -379,7 +379,10 @@ export class ContainerConfigLoader {
.toConstantValue(
isConfiguredForHomeServer
? directCallDomainEventPublisher
: new SNSDomainEventPublisher(container.get(TYPES.Auth_SNS), container.get(TYPES.Auth_SNS_TOPIC_ARN)),
: new SNSOpenTelemetryDomainEventPublisher(
container.get(TYPES.Auth_SNS),
container.get(TYPES.Auth_SNS_TOPIC_ARN),
),
)
// Mapping
@@ -773,6 +776,16 @@ 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())
@@ -782,16 +795,7 @@ export class ContainerConfigLoader {
container
.bind<SubscriptionSettingsAssociationServiceInterface>(TYPES.Auth_SubscriptionSettingsAssociationService)
.to(SubscriptionSettingsAssociationService)
container
.bind<FeatureServiceInterface>(TYPES.Auth_FeatureService)
.toConstantValue(
new FeatureService(
container.get<RoleToSubscriptionMapInterface>(TYPES.Auth_RoleToSubscriptionMap),
container.get<OfflineUserSubscriptionRepositoryInterface>(TYPES.Auth_OfflineUserSubscriptionRepository),
container.get<TimerInterface>(TYPES.Auth_Timer),
container.get<UserSubscriptionRepositoryInterface>(TYPES.Auth_UserSubscriptionRepository),
),
)
container.bind<FeatureServiceInterface>(TYPES.Auth_FeatureService).to(FeatureService)
container
.bind<SelectorInterface<boolean>>(TYPES.Auth_BooleanSelector)
.toConstantValue(new DeterministicSelector<boolean>())
@@ -1104,7 +1108,6 @@ export class ContainerConfigLoader {
new DeleteAccount(
container.get<UserRepositoryInterface>(TYPES.Auth_UserRepository),
container.get<GetRegularSubscriptionForUser>(TYPES.Auth_GetRegularSubscriptionForUser),
container.get<GetSharedSubscriptionForUser>(TYPES.Auth_GetSharedSubscriptionForUser),
container.get<DomainEventPublisherInterface>(TYPES.Auth_DomainEventPublisher),
container.get<DomainEventFactoryInterface>(TYPES.Auth_DomainEventFactory),
container.get<TimerInterface>(TYPES.Auth_Timer),
@@ -1222,35 +1225,6 @@ 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
@@ -1540,7 +1514,8 @@ export class ContainerConfigLoader {
container
.bind<DomainEventSubscriberInterface>(TYPES.Auth_DomainEventSubscriber)
.toConstantValue(
new SQSDomainEventSubscriber(
new SQSOpenTelemetryDomainEventSubscriber(
ServiceIdentifier.NAMES.AuthWorker,
container.get<SQSClient>(TYPES.Auth_SQS),
container.get<string>(TYPES.Auth_SQS_QUEUE_URL),
container.get<DomainEventMessageHandlerInterface>(TYPES.Auth_DomainEventMessageHandler),
@@ -1675,13 +1650,11 @@ 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,9 +164,6 @@ 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'),
@@ -233,6 +230,7 @@ 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

@@ -281,16 +281,9 @@ export class DomainEventFactory implements DomainEventFactoryInterface {
createAccountDeletionRequestedEvent(dto: {
userUuid: string
email: string
userCreatedAtTimestamp: number
regularSubscription?: {
uuid: string
ownerUuid: string
}
sharedSubscription?: {
uuid: string
ownerUuid: string
}
regularSubscriptionUuid: string | undefined
roleNames: string[]
}): AccountDeletionRequestedEvent {
return {
type: 'ACCOUNT_DELETION_REQUESTED',

View File

@@ -45,16 +45,9 @@ export interface DomainEventFactoryInterface {
): EmailBackupRequestedEvent
createAccountDeletionRequestedEvent(dto: {
userUuid: string
email: string
userCreatedAtTimestamp: number
regularSubscription?: {
uuid: string
ownerUuid: string
}
sharedSubscription?: {
uuid: string
ownerUuid: string
}
regularSubscriptionUuid: string | undefined
roleNames: string[]
}): AccountDeletionRequestedEvent
createUserRolesChangedEvent(userUuid: string, email: string, currentRoles: string[]): UserRolesChangedEvent
createUserEmailChangedEvent(userUuid: string, fromEmail: string, toEmail: string): UserEmailChangedEvent

View File

@@ -35,7 +35,6 @@ import { OfflineUserSubscriptionRepositoryInterface } from '../Subscription/Offl
import { TimerInterface } from '@standardnotes/time'
import { OfflineUserSubscription } from '../Subscription/OfflineUserSubscription'
import { UserSubscriptionType } from '../Subscription/UserSubscriptionType'
import { UserSubscriptionRepositoryInterface } from '../Subscription/UserSubscriptionRepositoryInterface'
describe('FeatureService', () => {
let roleToSubscriptionMap: RoleToSubscriptionMapInterface
@@ -53,10 +52,8 @@ describe('FeatureService', () => {
let offlineUserSubscriptionRepository: OfflineUserSubscriptionRepositoryInterface
let timer: TimerInterface
let offlineUserSubscription: OfflineUserSubscription
let userSubscriptionRepository: UserSubscriptionRepositoryInterface
const createService = () =>
new FeatureService(roleToSubscriptionMap, offlineUserSubscriptionRepository, timer, userSubscriptionRepository)
const createService = () => new FeatureService(roleToSubscriptionMap, offlineUserSubscriptionRepository, timer)
beforeEach(() => {
roleToSubscriptionMap = {} as jest.Mocked<RoleToSubscriptionMapInterface>
@@ -110,7 +107,7 @@ describe('FeatureService', () => {
renewedAt: null,
planName: SubscriptionName.PlusPlan,
endsAt: 555,
userUuid: 'user-1-1-1',
user: Promise.resolve(user),
cancelled: false,
subscriptionId: 1,
subscriptionType: UserSubscriptionType.Regular,
@@ -123,7 +120,7 @@ describe('FeatureService', () => {
renewedAt: null,
planName: SubscriptionName.ProPlan,
endsAt: 777,
userUuid: 'user-1-1-1',
user: Promise.resolve(user),
cancelled: false,
subscriptionId: 2,
subscriptionType: UserSubscriptionType.Regular,
@@ -136,7 +133,7 @@ describe('FeatureService', () => {
renewedAt: null,
planName: SubscriptionName.PlusPlan,
endsAt: 333,
userUuid: 'user-1-1-1',
user: Promise.resolve(user),
cancelled: true,
subscriptionId: 3,
subscriptionType: UserSubscriptionType.Regular,
@@ -149,7 +146,7 @@ describe('FeatureService', () => {
renewedAt: null,
planName: SubscriptionName.PlusPlan,
endsAt: 333,
userUuid: 'user-1-1-1',
user: Promise.resolve(user),
cancelled: true,
subscriptionId: 4,
subscriptionType: UserSubscriptionType.Regular,
@@ -158,11 +155,9 @@ describe('FeatureService', () => {
user = {
uuid: 'user-1-1-1',
roles: Promise.resolve([role1]),
subscriptions: Promise.resolve([subscription1]),
} as jest.Mocked<User>
userSubscriptionRepository = {} as jest.Mocked<UserSubscriptionRepositoryInterface>
userSubscriptionRepository.findByUserUuid = jest.fn().mockReturnValue([subscription1])
offlineUserSubscription = {
roles: Promise.resolve([role1]),
uuid: 'subscription-1-1-1',
@@ -252,10 +247,9 @@ describe('FeatureService', () => {
user = {
uuid: 'user-1-1-1',
roles: Promise.resolve([role1, role2, nonSubscriptionRole]),
subscriptions: Promise.resolve([subscription1, subscription2]),
} as jest.Mocked<User>
userSubscriptionRepository.findByUserUuid = jest.fn().mockReturnValue([subscription1, subscription2])
expect(await createService().userIsEntitledToFeature(user, 'files-beta')).toBe(true)
})
@@ -275,12 +269,9 @@ describe('FeatureService', () => {
user = {
uuid: 'user-1-1-1',
roles: Promise.resolve([role1]),
subscriptions: Promise.resolve([subscription3, subscription1, subscription4]),
} as jest.Mocked<User>
userSubscriptionRepository.findByUserUuid = jest
.fn()
.mockReturnValue([subscription3, subscription1, subscription4])
const features = await createService().getFeaturesForUser(user)
expect(features).toEqual(
expect.arrayContaining([
@@ -293,13 +284,14 @@ describe('FeatureService', () => {
})
it('should not return user features if a subscription could not be found', async () => {
const subscriptions: Array<UserSubscription> = []
user = {
uuid: 'user-1-1-1',
roles: Promise.resolve([role1]),
subscriptions: Promise.resolve(subscriptions),
} as jest.Mocked<User>
userSubscriptionRepository.findByUserUuid = jest.fn().mockReturnValue([])
expect(await createService().getFeaturesForUser(user)).toEqual([])
})
@@ -315,12 +307,9 @@ describe('FeatureService', () => {
user = {
uuid: 'user-1-1-1',
roles: Promise.resolve([role1]),
subscriptions: Promise.resolve([subscription3, subscription1, subscription4]),
} as jest.Mocked<User>
userSubscriptionRepository.findByUserUuid = jest
.fn()
.mockReturnValue([subscription3, subscription1, subscription4])
expect(await createService().getFeaturesForUser(user)).toEqual([])
})
@@ -332,7 +321,7 @@ describe('FeatureService', () => {
renewedAt: null,
planName: 'non existing plan name' as SubscriptionName,
endsAt: 555,
userUuid: 'user-1-1-1',
user: Promise.resolve(user),
cancelled: false,
subscriptionId: 1,
subscriptionType: UserSubscriptionType.Regular,
@@ -341,10 +330,9 @@ describe('FeatureService', () => {
user = {
uuid: 'user-1-1-1',
roles: Promise.resolve([role1]),
subscriptions: Promise.resolve([subscription1]),
} as jest.Mocked<User>
userSubscriptionRepository.findByUserUuid = jest.fn().mockReturnValue([subscription1])
expect(await createService().getFeaturesForUser(user)).toEqual([])
})
@@ -363,10 +351,9 @@ describe('FeatureService', () => {
user = {
uuid: 'user-1-1-1',
roles: Promise.resolve([role1, role2]),
subscriptions: Promise.resolve([subscription1, subscription2]),
} as jest.Mocked<User>
userSubscriptionRepository.findByUserUuid = jest.fn().mockReturnValue([subscription1, subscription2])
const features = await createService().getFeaturesForUser(user)
expect(features).toEqual(
expect.arrayContaining([
@@ -422,10 +409,9 @@ describe('FeatureService', () => {
user = {
uuid: 'user-1-1-1',
roles: Promise.resolve([role1, role2, nonSubscriptionRole]),
subscriptions: Promise.resolve([subscription1, subscription2]),
} as jest.Mocked<User>
userSubscriptionRepository.findByUserUuid = jest.fn().mockReturnValue([subscription1, subscription2])
const features = await createService().getFeaturesForUser(user)
expect(features).toEqual(
expect.arrayContaining([
@@ -459,10 +445,9 @@ describe('FeatureService', () => {
user = {
uuid: 'user-1-1-1',
roles: Promise.resolve([role1, role2]),
subscriptions: Promise.resolve([subscription1, subscription2]),
} as jest.Mocked<User>
userSubscriptionRepository.findByUserUuid = jest.fn().mockReturnValue([subscription1, subscription2])
const longestExpireAt = 777
const features = await createService().getFeaturesForUser(user)
@@ -497,10 +482,9 @@ describe('FeatureService', () => {
user = {
uuid: 'user-1-1-1',
roles: Promise.resolve([role1, role2]),
subscriptions: Promise.resolve([subscription1, subscription2]),
} as jest.Mocked<User>
userSubscriptionRepository.findByUserUuid = jest.fn().mockReturnValue([subscription1, subscription2])
const features = await createService().getFeaturesForUser(user)
expect(features).toEqual(
expect.arrayContaining([

View File

@@ -1,22 +1,24 @@
import { SubscriptionName } from '@standardnotes/common'
import { FeatureDescription, GetFeatures } from '@standardnotes/features'
import { TimerInterface } from '@standardnotes/time'
import { inject, injectable } from 'inversify'
import TYPES from '../../Bootstrap/Types'
import { RoleToSubscriptionMapInterface } from '../Role/RoleToSubscriptionMapInterface'
import { User } from '../User/User'
import { UserSubscription } from '../Subscription/UserSubscription'
import { FeatureServiceInterface } from './FeatureServiceInterface'
import { OfflineUserSubscriptionRepositoryInterface } from '../Subscription/OfflineUserSubscriptionRepositoryInterface'
import { Role } from '../Role/Role'
import { OfflineUserSubscription } from '../Subscription/OfflineUserSubscription'
import { UserSubscriptionRepositoryInterface } from '../Subscription/UserSubscriptionRepositoryInterface'
import { TimerInterface } from '@standardnotes/time'
@injectable()
export class FeatureService implements FeatureServiceInterface {
constructor(
private roleToSubscriptionMap: RoleToSubscriptionMapInterface,
@inject(TYPES.Auth_RoleToSubscriptionMap) private roleToSubscriptionMap: RoleToSubscriptionMapInterface,
@inject(TYPES.Auth_OfflineUserSubscriptionRepository)
private offlineUserSubscriptionRepository: OfflineUserSubscriptionRepositoryInterface,
private timer: TimerInterface,
private userSubscriptionRepository: UserSubscriptionRepositoryInterface,
@inject(TYPES.Auth_Timer) private timer: TimerInterface,
) {}
async userIsEntitledToFeature(user: User, featureIdentifier: string): Promise<boolean> {
@@ -59,7 +61,7 @@ export class FeatureService implements FeatureServiceInterface {
}
async getFeaturesForUser(user: User): Promise<Array<FeatureDescription>> {
const userSubscriptions = await this.userSubscriptionRepository.findByUserUuid(user.uuid)
const userSubscriptions = await user.subscriptions
return this.getFeaturesForSubscriptions(userSubscriptions, await user.roles)
}

View File

@@ -7,7 +7,7 @@ import { RoleServiceInterface } from '../Role/RoleServiceInterface'
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
import { UserSubscriptionRepositoryInterface } from '../Subscription/UserSubscriptionRepositoryInterface'
import { OfflineUserSubscriptionRepositoryInterface } from '../Subscription/OfflineUserSubscriptionRepositoryInterface'
import { Username, Uuid } from '@standardnotes/domain-core'
import { Username } from '@standardnotes/domain-core'
@injectable()
export class SubscriptionExpiredEventHandler implements DomainEventHandlerInterface {
@@ -48,22 +48,7 @@ export class SubscriptionExpiredEventHandler implements DomainEventHandlerInterf
private async removeRoleFromSubscriptionUsers(subscriptionId: number, subscriptionName: string): Promise<void> {
const userSubscriptions = await this.userSubscriptionRepository.findBySubscriptionId(subscriptionId)
for (const userSubscription of userSubscriptions) {
const userUuidOrError = Uuid.create(userSubscription.userUuid)
if (userUuidOrError.isFailed()) {
this.logger.warn(`Could not remove role from user with uuid: ${userUuidOrError.getError()}`)
continue
}
const userUuid = userUuidOrError.getValue()
const user = await this.userRepository.findOneByUuid(userUuid)
if (user === null) {
this.logger.warn(`Could not find user with uuid: ${userUuid.value}`)
continue
}
await this.roleService.removeUserRoleBasedOnSubscription(user, subscriptionName)
await this.roleService.removeUserRoleBasedOnSubscription(await userSubscription.user, subscriptionName)
}
}

View File

@@ -84,7 +84,7 @@ export class SubscriptionPurchasedEventHandler implements DomainEventHandlerInte
): Promise<UserSubscription> {
const subscription = new UserSubscription()
subscription.planName = subscriptionName
subscription.userUuid = user.uuid
subscription.user = Promise.resolve(user)
subscription.createdAt = timestamp
subscription.updatedAt = timestamp
subscription.endsAt = subscriptionExpiresAt

View File

@@ -80,7 +80,7 @@ export class SubscriptionReassignedEventHandler implements DomainEventHandlerInt
): Promise<UserSubscription> {
const subscription = new UserSubscription()
subscription.planName = subscriptionName
subscription.userUuid = user.uuid
subscription.user = Promise.resolve(user)
subscription.createdAt = timestamp
subscription.updatedAt = timestamp
subscription.endsAt = subscriptionExpiresAt

View File

@@ -7,7 +7,7 @@ import { RoleServiceInterface } from '../Role/RoleServiceInterface'
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
import { UserSubscriptionRepositoryInterface } from '../Subscription/UserSubscriptionRepositoryInterface'
import { OfflineUserSubscriptionRepositoryInterface } from '../Subscription/OfflineUserSubscriptionRepositoryInterface'
import { Username, Uuid } from '@standardnotes/domain-core'
import { Username } from '@standardnotes/domain-core'
@injectable()
export class SubscriptionRefundedEventHandler implements DomainEventHandlerInterface {
@@ -48,22 +48,7 @@ export class SubscriptionRefundedEventHandler implements DomainEventHandlerInter
private async removeRoleFromSubscriptionUsers(subscriptionId: number, subscriptionName: string): Promise<void> {
const userSubscriptions = await this.userSubscriptionRepository.findBySubscriptionId(subscriptionId)
for (const userSubscription of userSubscriptions) {
const userUuidOrError = Uuid.create(userSubscription.userUuid)
if (userUuidOrError.isFailed()) {
this.logger.warn(`Could not remove role from user with uuid: ${userUuidOrError.getError()}`)
continue
}
const userUuid = userUuidOrError.getValue()
const user = await this.userRepository.findOneByUuid(userUuid)
if (user === null) {
this.logger.warn(`Could not find user with uuid: ${userUuid.value}`)
continue
}
await this.roleService.removeUserRoleBasedOnSubscription(user, subscriptionName)
await this.roleService.removeUserRoleBasedOnSubscription(await userSubscription.user, subscriptionName)
}
}

View File

@@ -8,7 +8,7 @@ import { RoleServiceInterface } from '../Role/RoleServiceInterface'
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
import { Logger } from 'winston'
import { OfflineUserSubscription } from '../Subscription/OfflineUserSubscription'
import { Username, Uuid } from '@standardnotes/domain-core'
import { Username } from '@standardnotes/domain-core'
@injectable()
export class SubscriptionRenewedEventHandler implements DomainEventHandlerInterface {
@@ -71,20 +71,7 @@ export class SubscriptionRenewedEventHandler implements DomainEventHandlerInterf
private async addRoleToSubscriptionUsers(subscriptionId: number, subscriptionName: string): Promise<void> {
const userSubscriptions = await this.userSubscriptionRepository.findBySubscriptionId(subscriptionId)
for (const userSubscription of userSubscriptions) {
const userUuidOrError = Uuid.create(userSubscription.userUuid)
if (userUuidOrError.isFailed()) {
this.logger.warn(`Could not add role to user with uuid: ${userUuidOrError.getError()}`)
continue
}
const userUuid = userUuidOrError.getValue()
const user = await this.userRepository.findOneByUuid(userUuid)
if (user === null) {
this.logger.warn(`Could not find user with uuid: ${userUuid.value}`)
continue
}
const user = await userSubscription.user
await this.roleService.addUserRoleBasedOnSubscription(user, subscriptionName)
}

View File

@@ -128,7 +128,7 @@ export class SubscriptionSyncRequestedEventHandler implements DomainEventHandler
}
subscription.planName = subscriptionName
subscription.userUuid = user.uuid
subscription.user = Promise.resolve(user)
subscription.createdAt = timestamp
subscription.updatedAt = timestamp
subscription.endsAt = subscriptionExpiresAt

View File

@@ -0,0 +1,153 @@
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

@@ -0,0 +1,113 @@
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

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

View File

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

View File

@@ -1,4 +1,5 @@
import { Column, Entity, Index, PrimaryGeneratedColumn } from 'typeorm'
import { Column, Entity, Index, JoinColumn, ManyToOne, PrimaryGeneratedColumn } from 'typeorm'
import { User } from '../User/User'
import { UserSubscriptionType } from './UserSubscriptionType'
@Entity({ name: 'user_subscriptions' })
@@ -62,9 +63,14 @@ export class UserSubscription {
})
declare subscriptionType: UserSubscriptionType
@Column({
name: 'user_uuid',
length: 36,
})
declare userUuid: string
@ManyToOne(
/* istanbul ignore next */
() => User,
/* istanbul ignore next */
(user) => user.subscriptions,
/* istanbul ignore next */
{ onDelete: 'CASCADE', nullable: false, lazy: true, eager: false },
)
@JoinColumn({ name: 'user_uuid', referencedColumnName: 'uuid' })
declare user: Promise<User>
}

View File

@@ -107,7 +107,7 @@ describe('AcceptSharedSubscriptionInvitation', () => {
subscriptionId: 3,
subscriptionType: 'shared',
updatedAt: 1,
userUuid: '123',
user: Promise.resolve(invitee),
})
expect(roleService.addUserRoleBasedOnSubscription).toHaveBeenCalledWith(invitee, 'PLUS_PLAN')
expect(applyDefaultSubscriptionSettings.execute).toHaveBeenCalled()
@@ -145,7 +145,7 @@ describe('AcceptSharedSubscriptionInvitation', () => {
subscriptionId: 3,
subscriptionType: 'shared',
updatedAt: 3,
userUuid: '123',
user: Promise.resolve(invitee),
})
expect(roleService.addUserRoleBasedOnSubscription).toHaveBeenCalledWith(invitee, 'PLUS_PLAN')
expect(applyDefaultSubscriptionSettings.execute).toHaveBeenCalled()

View File

@@ -115,7 +115,7 @@ export class AcceptSharedSubscriptionInvitation implements UseCaseInterface {
): Promise<UserSubscription> {
const subscription = new UserSubscription()
subscription.planName = subscriptionName
subscription.userUuid = user.uuid
subscription.user = Promise.resolve(user)
const timestamp = this.timer.getTimestampInMicroseconds()
subscription.createdAt = timestamp
subscription.updatedAt = timestamp

View File

@@ -53,7 +53,7 @@ export class ActivatePremiumFeatures implements UseCaseInterface<string> {
const subscription = new UserSubscription()
subscription.planName = subscriptionPlanName.value
subscription.userUuid = user.uuid
subscription.user = Promise.resolve(user)
subscription.createdAt = timestamp
subscription.updatedAt = timestamp
subscription.endsAt = this.timer.convertDateToMicroseconds(endsAt)

View File

@@ -80,7 +80,7 @@ describe('CancelSharedSubscriptionInvitation', () => {
userSubscriptionRepository.findBySubscriptionIdAndType = jest.fn().mockReturnValue([inviterSubscription])
userSubscriptionRepository.findOneByUserUuidAndSubscriptionId = jest
.fn()
.mockReturnValue({ userUuid: '123' } as jest.Mocked<UserSubscription>)
.mockReturnValue({ user: Promise.resolve(invitee) } as jest.Mocked<UserSubscription>)
userSubscriptionRepository.save = jest.fn()
roleService = {} as jest.Mocked<RoleServiceInterface>
@@ -120,7 +120,7 @@ describe('CancelSharedSubscriptionInvitation', () => {
})
expect(userSubscriptionRepository.save).toHaveBeenCalledWith({
endsAt: 1,
userUuid: '123',
user: Promise.resolve(invitee),
})
expect(roleService.removeUserRoleBasedOnSubscription).toHaveBeenCalledWith(invitee, 'PLUS_PLAN')
expect(domainEventPublisher.publish).toHaveBeenCalled()

View File

@@ -5,6 +5,7 @@ import { TokenEncoderInterface, ValetTokenData, ValetTokenOperation } from '@sta
import { CreateValetToken } from './CreateValetToken'
import { UserSubscription } from '../../Subscription/UserSubscription'
import { User } from '../../User/User'
import { UserSubscriptionType } from '../../Subscription/UserSubscriptionType'
import { SubscriptionSettingsAssociationServiceInterface } from '../../Setting/SubscriptionSettingsAssociationServiceInterface'
import { GetRegularSubscriptionForUser } from '../GetRegularSubscriptionForUser/GetRegularSubscriptionForUser'
@@ -24,6 +25,7 @@ describe('CreateValetToken', () => {
const valetTokenTTL = 123
let regularSubscription: UserSubscription
let sharedSubscription: UserSubscription
let user: User
const createUseCase = () =>
new CreateValetToken(
@@ -57,16 +59,20 @@ describe('CreateValetToken', () => {
subscriptionSettingsAssociationService = {} as jest.Mocked<SubscriptionSettingsAssociationServiceInterface>
subscriptionSettingsAssociationService.getFileUploadLimit = jest.fn().mockReturnValue(5_368_709_120)
user = {
uuid: '123',
} as jest.Mocked<User>
regularSubscription = {
uuid: '1-2-3',
subscriptionType: UserSubscriptionType.Regular,
userUuid: '123',
user: Promise.resolve(user),
} as jest.Mocked<UserSubscription>
sharedSubscription = {
uuid: '2-3-4',
subscriptionType: UserSubscriptionType.Shared,
userUuid: '123',
user: Promise.resolve(user),
} as jest.Mocked<UserSubscription>
getRegularSubscription = {} as jest.Mocked<GetRegularSubscriptionForUser>

View File

@@ -11,46 +11,29 @@ import { TimerInterface } from '@standardnotes/time'
import { Result, RoleName } from '@standardnotes/domain-core'
import { Role } from '../../Role/Role'
import { GetRegularSubscriptionForUser } from '../GetRegularSubscriptionForUser/GetRegularSubscriptionForUser'
import { GetSharedSubscriptionForUser } from '../GetSharedSubscriptionForUser/GetSharedSubscriptionForUser'
describe('DeleteAccount', () => {
let userRepository: UserRepositoryInterface
let domainEventPublisher: DomainEventPublisherInterface
let domainEventFactory: DomainEventFactoryInterface
let getRegularSubscription: GetRegularSubscriptionForUser
let getSharedSubscription: GetSharedSubscriptionForUser
let user: User
let regularSubscription: UserSubscription
let sharedSubscription: UserSubscription
let timer: TimerInterface
const createUseCase = () =>
new DeleteAccount(
userRepository,
getRegularSubscription,
getSharedSubscription,
domainEventPublisher,
domainEventFactory,
timer,
)
new DeleteAccount(userRepository, getRegularSubscription, domainEventPublisher, domainEventFactory, timer)
beforeEach(() => {
user = {
uuid: '1-2-3',
email: 'test@test.te',
} as jest.Mocked<User>
user.roles = Promise.resolve([{ name: RoleName.NAMES.CoreUser } as jest.Mocked<Role>])
regularSubscription = {
uuid: '1-2-3',
subscriptionType: UserSubscriptionType.Regular,
userUuid: 'u-1-2-3',
} as jest.Mocked<UserSubscription>
sharedSubscription = {
uuid: '1-2-3',
subscriptionType: UserSubscriptionType.Shared,
userUuid: 'u-1-2-3',
user: Promise.resolve(user),
} as jest.Mocked<UserSubscription>
userRepository = {} as jest.Mocked<UserRepositoryInterface>
@@ -60,9 +43,6 @@ describe('DeleteAccount', () => {
getRegularSubscription = {} as jest.Mocked<GetRegularSubscriptionForUser>
getRegularSubscription.execute = jest.fn().mockReturnValue(Result.ok(regularSubscription))
getSharedSubscription = {} as jest.Mocked<GetSharedSubscriptionForUser>
getSharedSubscription.execute = jest.fn().mockReturnValue(Result.ok(sharedSubscription))
domainEventPublisher = {} as jest.Mocked<DomainEventPublisherInterface>
domainEventPublisher.publish = jest.fn()
@@ -78,7 +58,6 @@ describe('DeleteAccount', () => {
describe('when user uuid is provided', () => {
it('should trigger account deletion - no subscription', async () => {
getRegularSubscription.execute = jest.fn().mockReturnValue(Result.fail('not found'))
getSharedSubscription.execute = jest.fn().mockReturnValue(Result.fail('not found'))
const result = await createUseCase().execute({ userUuid: '00000000-0000-0000-0000-000000000000' })
@@ -87,11 +66,12 @@ describe('DeleteAccount', () => {
expect(domainEventFactory.createAccountDeletionRequestedEvent).toHaveBeenLastCalledWith({
userUuid: '1-2-3',
userCreatedAtTimestamp: 1,
email: 'test@test.te',
regularSubscriptionUuid: undefined,
roleNames: ['CORE_USER'],
})
})
it('should trigger account deletion - shared subscription present', async () => {
it('should trigger account deletion - subscription present', async () => {
const result = await createUseCase().execute({ userUuid: '00000000-0000-0000-0000-000000000000' })
expect(result.isFailed()).toBeFalsy()
@@ -99,35 +79,9 @@ describe('DeleteAccount', () => {
expect(domainEventPublisher.publish).toHaveBeenCalledTimes(1)
expect(domainEventFactory.createAccountDeletionRequestedEvent).toHaveBeenLastCalledWith({
userUuid: '1-2-3',
email: 'test@test.te',
userCreatedAtTimestamp: 1,
regularSubscription: {
ownerUuid: 'u-1-2-3',
uuid: '1-2-3',
},
sharedSubscription: {
ownerUuid: 'u-1-2-3',
uuid: '1-2-3',
},
})
})
it('should trigger account deletion - regular subscription present', async () => {
getSharedSubscription.execute = jest.fn().mockReturnValue(Result.fail('not found'))
const result = await createUseCase().execute({ userUuid: '00000000-0000-0000-0000-000000000000' })
expect(result.isFailed()).toBeFalsy()
expect(domainEventPublisher.publish).toHaveBeenCalledTimes(1)
expect(domainEventFactory.createAccountDeletionRequestedEvent).toHaveBeenLastCalledWith({
userUuid: '1-2-3',
email: 'test@test.te',
userCreatedAtTimestamp: 1,
regularSubscription: {
ownerUuid: 'u-1-2-3',
uuid: '1-2-3',
},
regularSubscriptionUuid: '1-2-3',
roleNames: ['CORE_USER'],
})
})
@@ -155,7 +109,6 @@ describe('DeleteAccount', () => {
describe('when username is provided', () => {
it('should trigger account deletion - no subscription', async () => {
getRegularSubscription.execute = jest.fn().mockReturnValue(Result.fail('not found'))
getSharedSubscription.execute = jest.fn().mockReturnValue(Result.fail('not found'))
const result = await createUseCase().execute({ username: 'test@test.te' })
@@ -163,12 +116,13 @@ describe('DeleteAccount', () => {
expect(domainEventPublisher.publish).toHaveBeenCalledTimes(1)
expect(domainEventFactory.createAccountDeletionRequestedEvent).toHaveBeenLastCalledWith({
userUuid: '1-2-3',
email: 'test@test.te',
userCreatedAtTimestamp: 1,
regularSubscriptionUuid: undefined,
roleNames: ['CORE_USER'],
})
})
it('should trigger account deletion - shared subscription present', async () => {
it('should trigger account deletion - subscription present', async () => {
const result = await createUseCase().execute({ username: 'test@test.te' })
expect(result.isFailed()).toBeFalsy()
@@ -176,35 +130,9 @@ describe('DeleteAccount', () => {
expect(domainEventPublisher.publish).toHaveBeenCalledTimes(1)
expect(domainEventFactory.createAccountDeletionRequestedEvent).toHaveBeenLastCalledWith({
userUuid: '1-2-3',
email: 'test@test.te',
userCreatedAtTimestamp: 1,
regularSubscription: {
ownerUuid: 'u-1-2-3',
uuid: '1-2-3',
},
sharedSubscription: {
ownerUuid: 'u-1-2-3',
uuid: '1-2-3',
},
})
})
it('should trigger account deletion - regular subscription present', async () => {
getSharedSubscription.execute = jest.fn().mockReturnValue(Result.fail('not found'))
const result = await createUseCase().execute({ username: 'test@test.te' })
expect(result.isFailed()).toBeFalsy()
expect(domainEventPublisher.publish).toHaveBeenCalledTimes(1)
expect(domainEventFactory.createAccountDeletionRequestedEvent).toHaveBeenLastCalledWith({
userUuid: '1-2-3',
email: 'test@test.te',
userCreatedAtTimestamp: 1,
regularSubscription: {
ownerUuid: 'u-1-2-3',
uuid: '1-2-3',
},
regularSubscriptionUuid: '1-2-3',
roleNames: ['CORE_USER'],
})
})

View File

@@ -8,14 +8,11 @@ import { UserRepositoryInterface } from '../../User/UserRepositoryInterface'
import { DeleteAccountDTO } from './DeleteAccountDTO'
import { User } from '../../User/User'
import { GetRegularSubscriptionForUser } from '../GetRegularSubscriptionForUser/GetRegularSubscriptionForUser'
import { UserSubscription } from '../../Subscription/UserSubscription'
import { GetSharedSubscriptionForUser } from '../GetSharedSubscriptionForUser/GetSharedSubscriptionForUser'
export class DeleteAccount implements UseCaseInterface<string> {
constructor(
private userRepository: UserRepositoryInterface,
private getRegularSubscription: GetRegularSubscriptionForUser,
private getSharedSubscription: GetSharedSubscriptionForUser,
private domainEventPublisher: DomainEventPublisherInterface,
private domainEventFactory: DomainEventFactoryInterface,
private timer: TimerInterface,
@@ -47,39 +44,23 @@ export class DeleteAccount implements UseCaseInterface<string> {
return Result.ok('User already deleted.')
}
let sharedSubscription: UserSubscription | undefined
const sharedSubscriptionOrError = await this.getSharedSubscription.execute({
userUuid: user.uuid,
})
if (!sharedSubscriptionOrError.isFailed()) {
sharedSubscription = sharedSubscriptionOrError.getValue()
}
const roles = await user.roles
let regularSubscription: UserSubscription | undefined
const regularSubscriptionOrError = await this.getRegularSubscription.execute({
let regularSubscriptionUuid: string | undefined
const result = await this.getRegularSubscription.execute({
userUuid: user.uuid,
})
if (!regularSubscriptionOrError.isFailed()) {
regularSubscription = regularSubscriptionOrError.getValue()
if (!result.isFailed()) {
const regularSubscription = result.getValue()
regularSubscriptionUuid = regularSubscription.uuid
}
await this.domainEventPublisher.publish(
this.domainEventFactory.createAccountDeletionRequestedEvent({
userUuid: user.uuid,
email: user.email,
userCreatedAtTimestamp: this.timer.convertDateToMicroseconds(user.createdAt),
regularSubscription: regularSubscription
? {
ownerUuid: regularSubscription.userUuid,
uuid: regularSubscription.uuid,
}
: undefined,
sharedSubscription: sharedSubscription
? {
ownerUuid: sharedSubscription.userUuid,
uuid: sharedSubscription.uuid,
}
: undefined,
regularSubscriptionUuid,
roleNames: roles.map((role) => role.name),
}),
)

View File

@@ -1,47 +0,0 @@
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

@@ -1,55 +0,0 @@
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

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

View File

@@ -1,78 +0,0 @@
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

@@ -1,66 +0,0 @@
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

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

View File

@@ -1,104 +0,0 @@
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

@@ -1,83 +0,0 @@
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

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

View File

@@ -45,13 +45,13 @@ describe('UpdateStorageQuotaUsedForUser', () => {
regularSubscription = {
uuid: '00000000-0000-0000-0000-000000000000',
subscriptionType: UserSubscriptionType.Regular,
userUuid: '123',
user: Promise.resolve(user),
} as jest.Mocked<UserSubscription>
sharedSubscription = {
uuid: '2-3-4',
subscriptionType: UserSubscriptionType.Shared,
userUuid: '123',
user: Promise.resolve(user),
} as jest.Mocked<UserSubscription>
getSharedSubscription = {} as jest.Mocked<GetSharedSubscriptionForUser>

View File

@@ -56,6 +56,7 @@ export class UpdateStorageQuotaUsedForUser implements UseCaseInterface<void> {
private async updateUploadBytesUsedSetting(subscription: UserSubscription, bytesUsed: number): Promise<void> {
let bytesAlreadyUsed = '0'
const subscriptionUser = await subscription.user
const bytesUsedSettingExists = await this.getSubscriptionSetting.execute({
userSubscriptionUuid: subscription.uuid,
@@ -76,7 +77,7 @@ export class UpdateStorageQuotaUsedForUser implements UseCaseInterface<void> {
/* istanbul ignore next */
if (result.isFailed()) {
this.logger.error(`Could not set file upload bytes used for subscription ${subscription.uuid}`)
this.logger.error(`Could not set file upload bytes used for user ${subscriptionUser.uuid}`)
}
}
}

View File

@@ -1,6 +1,7 @@
import { Column, Entity, Index, JoinTable, ManyToMany, OneToMany, PrimaryGeneratedColumn } from 'typeorm'
import { RevokedSession } from '../Session/RevokedSession'
import { Role } from '../Role/Role'
import { UserSubscription } from '../Subscription/UserSubscription'
import { ProtocolVersion } from '@standardnotes/common'
import { TypeORMEmergencyAccessInvitation } from '../../Infra/TypeORM/TypeORMEmergencyAccessInvitation'
@@ -160,6 +161,16 @@ export class User {
})
declare roles: Promise<Array<Role>>
@OneToMany(
/* istanbul ignore next */
() => UserSubscription,
/* istanbul ignore next */
(subscription) => subscription.user,
/* istanbul ignore next */
{ lazy: true, eager: false },
)
declare subscriptions: Promise<UserSubscription[]>
@OneToMany(
/* istanbul ignore next */
() => TypeORMEmergencyAccessInvitation,

View File

@@ -19,8 +19,6 @@ 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 {
@@ -28,23 +26,18 @@ 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,7 +2,6 @@ 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'
@@ -12,21 +11,18 @@ 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()
@@ -179,16 +175,6 @@ 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,3 +1,4 @@
import { ReadStream } from 'fs'
import { Repository } from 'typeorm'
import { Setting } from '../../Domain/Setting/Setting'
@@ -12,36 +13,6 @@ 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)
@@ -71,6 +42,27 @@ 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')

View File

@@ -3,16 +3,6 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.20.4](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.20.3...@standardnotes/domain-events-infra@1.20.4) (2023-11-07)
**Note:** Version bump only for package @standardnotes/domain-events-infra
## [1.20.3](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.20.2...@standardnotes/domain-events-infra@1.20.3) (2023-11-07)
### Bug Fixes
* remove open telemetry from code ([#903](https://github.com/standardnotes/server/issues/903)) ([751f3b2](https://github.com/standardnotes/server/commit/751f3b25476c5be3d663ad8540c43266acd39493))
## [1.20.2](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.20.1...@standardnotes/domain-events-infra@1.20.2) (2023-10-19)
**Note:** Version bump only for package @standardnotes/domain-events-infra

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/domain-events-infra",
"version": "1.20.4",
"version": "1.20.2",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -1,36 +0,0 @@
import { Consumer } from 'sqs-consumer'
import { Message, SQSClient } from '@aws-sdk/client-sqs'
import { DomainEventSubscriberInterface, DomainEventMessageHandlerInterface } from '@standardnotes/domain-events'
import { Logger } from 'winston'
export class SQSDomainEventSubscriber implements DomainEventSubscriberInterface {
constructor(
private sqs: SQSClient,
private queueUrl: string,
private domainEventMessageHandler: DomainEventMessageHandlerInterface,
private logger: Logger,
) {}
start(): void {
const sqsConsumer = Consumer.create({
attributeNames: ['All'],
messageAttributeNames: ['All'],
queueUrl: this.queueUrl,
sqs: this.sqs,
handleMessage: this.handleMessage.bind(this),
})
sqsConsumer.on('error', this.handleError.bind(this))
sqsConsumer.on('processing_error', this.handleError.bind(this))
sqsConsumer.start()
}
async handleMessage(message: Message): Promise<void> {
await this.domainEventMessageHandler.handleMessage(<string>message.Body)
}
handleError(error: Error): void {
this.logger.error('Error occured while handling SQS message: %O', error)
}
}

View File

@@ -15,7 +15,6 @@ export * from './SNS/SNSDomainEventPublisher'
export * from './SNS/SNSOpenTelemetryDomainEventPublisher'
export * from './SQS/SQSBounceNotificiationHandler'
export * from './SQS/SQSDomainEventSubscriber'
export * from './SQS/SQSDomainEventSubscriberFactory'
export * from './SQS/SQSEventMessageHandler'
export * from './SQS/SQSOpenTelemetryDomainEventSubscriber'

View File

@@ -3,12 +3,6 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [2.133.1](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.133.0...@standardnotes/domain-events@2.133.1) (2023-11-07)
### Bug Fixes
* account deletion event ([#904](https://github.com/standardnotes/server/issues/904)) ([d66ae62](https://github.com/standardnotes/server/commit/d66ae62cf4f413cac5f6f4eac45dc0f1ddbc9e32))
# [2.133.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.132.3...@standardnotes/domain-events@2.133.0) (2023-10-19)
### Features

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/domain-events",
"version": "2.133.1",
"version": "2.133.0",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -1,13 +1,5 @@
export interface AccountDeletionRequestedEventPayload {
userUuid: string
email: string
userCreatedAtTimestamp: number
regularSubscription?: {
uuid: string
ownerUuid: string
}
sharedSubscription?: {
uuid: string
ownerUuid: string
}
regularSubscriptionUuid: string | undefined
}

View File

@@ -1,5 +1,6 @@
export interface FileRemovedEventPayload {
userUuid: string
regularSubscriptionUuid: string
fileByteSize: number
filePath: string
fileName: string

View File

@@ -3,14 +3,6 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.13.19](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.13.18...@standardnotes/event-store@1.13.19) (2023-11-07)
**Note:** Version bump only for package @standardnotes/event-store
## [1.13.18](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.13.17...@standardnotes/event-store@1.13.18) (2023-11-07)
**Note:** Version bump only for package @standardnotes/event-store
## [1.13.17](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.13.16...@standardnotes/event-store@1.13.17) (2023-10-26)
**Note:** Version bump only for package @standardnotes/event-store

View File

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

View File

@@ -3,18 +3,6 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.32.5](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.32.4...@standardnotes/files-server@1.32.5) (2023-11-07)
### Bug Fixes
* account deletion event ([#904](https://github.com/standardnotes/files/issues/904)) ([d66ae62](https://github.com/standardnotes/files/commit/d66ae62cf4f413cac5f6f4eac45dc0f1ddbc9e32))
## [1.32.4](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.32.3...@standardnotes/files-server@1.32.4) (2023-11-07)
### Bug Fixes
* remove open telemetry from code ([#903](https://github.com/standardnotes/files/issues/903)) ([751f3b2](https://github.com/standardnotes/files/commit/751f3b25476c5be3d663ad8540c43266acd39493))
## [1.32.3](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.32.2...@standardnotes/files-server@1.32.3) (2023-10-31)
### Bug Fixes

View File

@@ -1,5 +1,11 @@
import 'reflect-metadata'
import { OpenTelemetrySDK } from '@standardnotes/domain-events-infra'
import { ServiceIdentifier } from '@standardnotes/domain-core'
const sdk = new OpenTelemetrySDK({ serviceName: ServiceIdentifier.NAMES.Files })
sdk.start()
import * as busboy from 'connect-busboy'
import '../src/Infra/InversifyExpress/AnnotatedFallbackController'

View File

@@ -1,5 +1,11 @@
import 'reflect-metadata'
import { OpenTelemetrySDK } from '@standardnotes/domain-events-infra'
import { ServiceIdentifier } from '@standardnotes/domain-core'
const sdk = new OpenTelemetrySDK({ serviceName: ServiceIdentifier.NAMES.FilesWorker })
sdk.start()
import { Logger } from 'winston'
import { ContainerConfigLoader } from '../src/Bootstrap/Container'

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/files-server",
"version": "1.32.5",
"version": "1.32.3",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -16,9 +16,9 @@ import { DomainEventFactory } from '../Domain/Event/DomainEventFactory'
import {
DirectCallDomainEventPublisher,
DirectCallEventMessageHandler,
SNSDomainEventPublisher,
SQSDomainEventSubscriber,
SNSOpenTelemetryDomainEventPublisher,
SQSEventMessageHandler,
SQSOpenTelemetryDomainEventSubscriber,
} from '@standardnotes/domain-events-infra'
import { StreamDownloadFile } from '../Domain/UseCase/StreamDownloadFile/StreamDownloadFile'
import { FileDownloaderInterface } from '../Domain/Services/FileDownloaderInterface'
@@ -52,6 +52,7 @@ import { S3FileMover } from '../Infra/S3/S3FileMover'
import { FSFileMover } from '../Infra/FS/FSFileMover'
import { MoveFile } from '../Domain/UseCase/MoveFile/MoveFile'
import { SharedVaultValetTokenAuthMiddleware } from '../Infra/InversifyExpress/Middleware/SharedVaultValetTokenAuthMiddleware'
import { ServiceIdentifier } from '@standardnotes/domain-core'
export class ContainerConfigLoader {
async load(configuration?: {
@@ -174,7 +175,10 @@ export class ContainerConfigLoader {
container
.bind<DomainEventPublisherInterface>(TYPES.Files_DomainEventPublisher)
.toConstantValue(
new SNSDomainEventPublisher(container.get(TYPES.Files_SNS), container.get(TYPES.Files_SNS_TOPIC_ARN)),
new SNSOpenTelemetryDomainEventPublisher(
container.get(TYPES.Files_SNS),
container.get(TYPES.Files_SNS_TOPIC_ARN),
),
)
}
@@ -297,7 +301,8 @@ export class ContainerConfigLoader {
container
.bind<DomainEventSubscriberInterface>(TYPES.Files_DomainEventSubscriber)
.toConstantValue(
new SQSDomainEventSubscriber(
new SQSOpenTelemetryDomainEventSubscriber(
ServiceIdentifier.NAMES.FilesWorker,
container.get<SQSClient>(TYPES.Files_SQS),
container.get<string>(TYPES.Files_SQS_QUEUE_URL),
container.get<DomainEventMessageHandlerInterface>(TYPES.Files_DomainEventMessageHandler),

View File

@@ -18,6 +18,7 @@ export class DomainEventFactory implements DomainEventFactoryInterface {
filePath: string
fileName: string
fileByteSize: number
regularSubscriptionUuid: string
}): FileRemovedEvent {
return {
type: 'FILE_REMOVED',

View File

@@ -18,6 +18,7 @@ export interface DomainEventFactoryInterface {
filePath: string
fileName: string
fileByteSize: number
regularSubscriptionUuid: string
}): FileRemovedEvent
createSharedVaultFileMovedEvent(payload: {
fileByteSize: number

View File

@@ -17,7 +17,7 @@ export class AccountDeletionRequestedEventHandler implements DomainEventHandlerI
) {}
async handle(event: AccountDeletionRequestedEvent): Promise<void> {
if (event.payload.regularSubscription === undefined) {
if (event.payload.regularSubscriptionUuid === undefined) {
return
}
@@ -38,6 +38,7 @@ export class AccountDeletionRequestedEventHandler implements DomainEventHandlerI
for (const fileRemoved of filesRemoved) {
await this.domainEventPublisher.publish(
this.domainEventFactory.createFileRemovedEvent({
regularSubscriptionUuid: event.payload.regularSubscriptionUuid,
userUuid: fileRemoved.userOrSharedVaultUuid,
filePath: fileRemoved.filePath,
fileName: fileRemoved.fileName,

View File

@@ -40,6 +40,7 @@ export class SharedSubscriptionInvitationCanceledEventHandler implements DomainE
for (const fileRemoved of filesRemoved) {
await this.domainEventPublisher.publish(
this.domainEventFactory.createFileRemovedEvent({
regularSubscriptionUuid: event.payload.inviterSubscriptionUuid,
userUuid: fileRemoved.userOrSharedVaultUuid,
filePath: fileRemoved.filePath,
fileName: fileRemoved.fileName,

View File

@@ -36,6 +36,7 @@ export class RemoveFile implements UseCaseInterface<boolean> {
filePath: `${dto.userInput.userUuid}/${dto.userInput.resourceRemoteIdentifier}`,
fileName: dto.userInput.resourceRemoteIdentifier,
fileByteSize: removedFileSize,
regularSubscriptionUuid: dto.userInput.regularSubscriptionUuid,
}),
)
} else if (dto.vaultInput !== undefined) {

View File

@@ -3,26 +3,6 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.18.25](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.18.24...@standardnotes/home-server@1.18.25) (2023-11-07)
**Note:** Version bump only for package @standardnotes/home-server
## [1.18.24](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.18.23...@standardnotes/home-server@1.18.24) (2023-11-07)
**Note:** Version bump only for package @standardnotes/home-server
## [1.18.23](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.18.22...@standardnotes/home-server@1.18.23) (2023-11-07)
**Note:** Version bump only for package @standardnotes/home-server
## [1.18.22](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.18.21...@standardnotes/home-server@1.18.22) (2023-11-07)
**Note:** Version bump only for package @standardnotes/home-server
## [1.18.21](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.18.20...@standardnotes/home-server@1.18.21) (2023-11-06)
**Note:** Version bump only for package @standardnotes/home-server
## [1.18.20](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.18.19...@standardnotes/home-server@1.18.20) (2023-11-03)
**Note:** Version bump only for package @standardnotes/home-server

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/home-server",
"version": "1.18.25",
"version": "1.18.20",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -3,18 +3,6 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.47.5](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.47.4...@standardnotes/revisions-server@1.47.5) (2023-11-07)
### Bug Fixes
* account deletion event ([#904](https://github.com/standardnotes/server/issues/904)) ([d66ae62](https://github.com/standardnotes/server/commit/d66ae62cf4f413cac5f6f4eac45dc0f1ddbc9e32))
## [1.47.4](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.47.3...@standardnotes/revisions-server@1.47.4) (2023-11-07)
### Bug Fixes
* remove open telemetry from code ([#903](https://github.com/standardnotes/server/issues/903)) ([751f3b2](https://github.com/standardnotes/server/commit/751f3b25476c5be3d663ad8540c43266acd39493))
## [1.47.3](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.47.2...@standardnotes/revisions-server@1.47.3) (2023-10-26)
**Note:** Version bump only for package @standardnotes/revisions-server

View File

@@ -1,5 +1,11 @@
import 'reflect-metadata'
import { OpenTelemetrySDK } from '@standardnotes/domain-events-infra'
import { ServiceIdentifier } from '@standardnotes/domain-core'
const sdk = new OpenTelemetrySDK({ serviceName: ServiceIdentifier.NAMES.Revisions })
sdk.start()
import * as cors from 'cors'
import { urlencoded, json, Request, Response, NextFunction } from 'express'
import * as winston from 'winston'

View File

@@ -1,5 +1,11 @@
import 'reflect-metadata'
import { OpenTelemetrySDK } from '@standardnotes/domain-events-infra'
import { ServiceIdentifier } from '@standardnotes/domain-core'
const sdk = new OpenTelemetrySDK({ serviceName: ServiceIdentifier.NAMES.RevisionsWorker })
sdk.start()
import { Logger } from 'winston'
import TYPES from '../src/Bootstrap/Types'

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/revisions-server",
"version": "1.47.5",
"version": "1.47.3",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -1,4 +1,9 @@
import { ControllerContainer, ControllerContainerInterface, MapperInterface } from '@standardnotes/domain-core'
import {
ControllerContainer,
ControllerContainerInterface,
MapperInterface,
ServiceIdentifier,
} from '@standardnotes/domain-core'
import { Container, interfaces } from 'inversify'
import { Repository } from 'typeorm'
import * as winston from 'winston'
@@ -29,7 +34,7 @@ import {
SQSEventMessageHandler,
DirectCallEventMessageHandler,
DirectCallDomainEventPublisher,
SQSDomainEventSubscriber,
SQSOpenTelemetryDomainEventSubscriber,
} from '@standardnotes/domain-events-infra'
import { DumpRepositoryInterface } from '../Domain/Dump/DumpRepositoryInterface'
import { AccountDeletionRequestedEventHandler } from '../Domain/Handler/AccountDeletionRequestedEventHandler'
@@ -337,7 +342,8 @@ export class ContainerConfigLoader {
container
.bind<DomainEventSubscriberInterface>(TYPES.Revisions_DomainEventSubscriber)
.toConstantValue(
new SQSDomainEventSubscriber(
new SQSOpenTelemetryDomainEventSubscriber(
ServiceIdentifier.NAMES.RevisionsWorker,
container.get<SQSClient>(TYPES.Revisions_SQS),
container.get<string>(TYPES.Revisions_SQS_QUEUE_URL),
container.get<DomainEventMessageHandlerInterface>(TYPES.Revisions_DomainEventMessageHandler),

View File

@@ -0,0 +1,46 @@
import 'reflect-metadata'
import { AccountDeletionRequestedEvent } from '@standardnotes/domain-events'
import { Logger } from 'winston'
import { AccountDeletionRequestedEventHandler } from './AccountDeletionRequestedEventHandler'
import { RevisionRepositoryInterface } from '../Revision/RevisionRepositoryInterface'
describe('AccountDeletionRequestedEventHandler', () => {
let revisionRepository: RevisionRepositoryInterface
let logger: Logger
let event: AccountDeletionRequestedEvent
const createHandler = () => new AccountDeletionRequestedEventHandler(revisionRepository, logger)
beforeEach(() => {
revisionRepository = {} as jest.Mocked<RevisionRepositoryInterface>
revisionRepository.removeByUserUuid = jest.fn()
logger = {} as jest.Mocked<Logger>
logger.info = jest.fn()
logger.warn = jest.fn()
logger.error = jest.fn()
event = {} as jest.Mocked<AccountDeletionRequestedEvent>
event.createdAt = new Date(1)
event.payload = {
userUuid: '2-3-4',
userCreatedAtTimestamp: 1,
regularSubscriptionUuid: '1-2-3',
}
})
it('should remove all revisions for a user', async () => {
event.payload.userUuid = '84c0f8e8-544a-4c7e-9adf-26209303bc1d'
await createHandler().handle(event)
expect(revisionRepository.removeByUserUuid).toHaveBeenCalled()
})
it('should not remove all revisions for an invalid user uuid', async () => {
await createHandler().handle(event)
expect(revisionRepository.removeByUserUuid).not.toHaveBeenCalled()
})
})

View File

@@ -3,16 +3,6 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.26.6](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.26.5...@standardnotes/scheduler-server@1.26.6) (2023-11-07)
**Note:** Version bump only for package @standardnotes/scheduler-server
## [1.26.5](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.26.4...@standardnotes/scheduler-server@1.26.5) (2023-11-07)
### Bug Fixes
* remove open telemetry from code ([#903](https://github.com/standardnotes/server/issues/903)) ([751f3b2](https://github.com/standardnotes/server/commit/751f3b25476c5be3d663ad8540c43266acd39493))
## [1.26.4](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.26.3...@standardnotes/scheduler-server@1.26.4) (2023-10-26)
**Note:** Version bump only for package @standardnotes/scheduler-server

View File

@@ -1,5 +1,11 @@
import 'reflect-metadata'
import { OpenTelemetrySDK, OpenTelemetryTracer } from '@standardnotes/domain-events-infra'
import { ServiceIdentifier } from '@standardnotes/domain-core'
const sdk = new OpenTelemetrySDK({ serviceName: ServiceIdentifier.NAMES.SchedulerScheduledTask })
sdk.start()
import { Logger } from 'winston'
import * as dayjs from 'dayjs'
import * as utc from 'dayjs/plugin/utc'
@@ -29,15 +35,22 @@ void container.load().then((container) => {
const verifyPredicates: VerifyPredicates = container.get(TYPES.VerifyPredicates)
const tracer = new OpenTelemetryTracer()
tracer.startSpan(ServiceIdentifier.NAMES.SchedulerScheduledTask, 'verify')
Promise.resolve(verifyJobs(now, verifyPredicates))
.then(() => {
logger.info('Verification of overdue jobs complete.')
tracer.stopSpan()
process.exit(0)
})
.catch((error) => {
logger.error(`Could not finish verification of overdue jobs: ${error.message}`)
tracer.stopSpanWithError(error)
process.exit(1)
})
})

View File

@@ -1,5 +1,11 @@
import 'reflect-metadata'
import { OpenTelemetrySDK } from '@standardnotes/domain-events-infra'
import { ServiceIdentifier } from '@standardnotes/domain-core'
const sdk = new OpenTelemetrySDK({ serviceName: ServiceIdentifier.NAMES.SchedulerWorker })
sdk.start()
import { Logger } from 'winston'
import { DomainEventSubscriberInterface } from '@standardnotes/domain-events'
import * as dayjs from 'dayjs'

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/scheduler-server",
"version": "1.26.6",
"version": "1.26.4",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -15,9 +15,9 @@ import TYPES from './Types'
import { AppDataSource } from './DataSource'
import { DomainEventFactory } from '../Domain/Event/DomainEventFactory'
import {
SNSDomainEventPublisher,
SQSDomainEventSubscriber,
SNSOpenTelemetryDomainEventPublisher,
SQSEventMessageHandler,
SQSOpenTelemetryDomainEventSubscriber,
} from '@standardnotes/domain-events-infra'
import { Timer, TimerInterface } from '@standardnotes/time'
import { PredicateRepositoryInterface } from '../Domain/Predicate/PredicateRepositoryInterface'
@@ -35,6 +35,7 @@ import { VerifyPredicates } from '../Domain/UseCase/VerifyPredicates/VerifyPredi
import { UserRegisteredEventHandler } from '../Domain/Handler/UserRegisteredEventHandler'
import { SubscriptionCancelledEventHandler } from '../Domain/Handler/SubscriptionCancelledEventHandler'
import { ExitDiscountAppliedEventHandler } from '../Domain/Handler/ExitDiscountAppliedEventHandler'
import { ServiceIdentifier } from '@standardnotes/domain-core'
export class ContainerConfigLoader {
async load(): Promise<Container> {
@@ -135,7 +136,9 @@ export class ContainerConfigLoader {
container
.bind<DomainEventPublisherInterface>(TYPES.DomainEventPublisher)
.toConstantValue(new SNSDomainEventPublisher(container.get(TYPES.SNS), container.get(TYPES.SNS_TOPIC_ARN)))
.toConstantValue(
new SNSOpenTelemetryDomainEventPublisher(container.get(TYPES.SNS), container.get(TYPES.SNS_TOPIC_ARN)),
)
const eventHandlers: Map<string, DomainEventHandlerInterface> = new Map([
['PREDICATE_VERIFIED', container.get(TYPES.PredicateVerifiedEventHandler)],
@@ -150,7 +153,8 @@ export class ContainerConfigLoader {
container
.bind<DomainEventSubscriberInterface>(TYPES.DomainEventSubscriber)
.toConstantValue(
new SQSDomainEventSubscriber(
new SQSOpenTelemetryDomainEventSubscriber(
ServiceIdentifier.NAMES.SchedulerWorker,
container.get<SQSClient>(TYPES.SQS),
container.get<string>(TYPES.SQS_QUEUE_URL),
container.get<DomainEventMessageHandlerInterface>(TYPES.DomainEventMessageHandler),

View File

@@ -3,22 +3,6 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.120.3](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.120.2...@standardnotes/syncing-server@1.120.3) (2023-11-07)
**Note:** Version bump only for package @standardnotes/syncing-server
## [1.120.2](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.120.1...@standardnotes/syncing-server@1.120.2) (2023-11-07)
### Bug Fixes
* remove open telemetry from code ([#903](https://github.com/standardnotes/syncing-server-js/issues/903)) ([751f3b2](https://github.com/standardnotes/syncing-server-js/commit/751f3b25476c5be3d663ad8540c43266acd39493))
## [1.120.1](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.120.0...@standardnotes/syncing-server@1.120.1) (2023-11-06)
### Bug Fixes
* **syncing-server:** return cursor token upon transfer limit breached ([#902](https://github.com/standardnotes/syncing-server-js/issues/902)) ([71689c1](https://github.com/standardnotes/syncing-server-js/commit/71689c1497728569fc6a07e21fa7bdba68c1bac0))
# [1.120.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.119.4...@standardnotes/syncing-server@1.120.0) (2023-11-02)
### Features

View File

@@ -1,5 +1,11 @@
import 'reflect-metadata'
import { OpenTelemetrySDK } from '@standardnotes/domain-events-infra'
import { ServiceIdentifier } from '@standardnotes/domain-core'
const sdk = new OpenTelemetrySDK({ serviceName: ServiceIdentifier.NAMES.SyncingServer })
sdk.start()
import '../src/Infra/InversifyExpressUtils/AnnotatedFallbackController'
import '../src/Infra/InversifyExpressUtils/AnnotatedHealthCheckController'
import '../src/Infra/InversifyExpressUtils/AnnotatedItemsController'

Some files were not shown because too many files have changed in this diff Show More