Compare commits

..

9 Commits

Author SHA1 Message Date
standardci
94448bb5d8 chore(release): publish new version
- @standardnotes/analytics@2.26.0
 - @standardnotes/api-gateway@1.73.4
 - @standardnotes/auth-server@1.137.3
 - @standardnotes/domain-events-infra@1.12.18
 - @standardnotes/domain-events@2.121.0
 - @standardnotes/event-store@1.11.28
 - @standardnotes/files-server@1.22.7
 - @standardnotes/home-server@1.15.18
 - @standardnotes/revisions-server@1.30.8
 - @standardnotes/scheduler-server@1.20.32
 - @standardnotes/syncing-server@1.90.0
 - @standardnotes/websockets-server@1.10.25
2023-09-01 08:56:44 +00:00
Karol Sójko
9a568b0f73 feat: send websocket event to user when a message is sent (#802) 2023-09-01 09:39:10 +02:00
Karol Sójko
a1ee491dc5 fix(revisions): add transition start info 2023-09-01 08:22:29 +02:00
Karol Sójko
e5c118c262 fix(revisions): info logs on total revisions transitioned count 2023-09-01 08:21:41 +02:00
Karol Sójko
1bef1279e6 fix: remove the alive and kicking info logs on workers 2023-09-01 08:19:14 +02:00
Karol Sójko
c511f259c7 fix(analytics): throwing errors on unexisting users 2023-09-01 08:17:22 +02:00
standardci
f77ed8ef94 chore(release): publish new version
- @standardnotes/home-server@1.15.17
 - @standardnotes/revisions-server@1.30.7
2023-08-31 13:57:20 +00:00
Karol Sójko
a4929af2ee fix(revisions): add more verbose messages about failures in revision transitioning 2023-08-31 14:46:11 +02:00
Karol Sójko
095811dda9 fix(revisions): revisions transition check for total count at the end 2023-08-31 14:00:05 +02:00
55 changed files with 359 additions and 80 deletions

View File

@@ -3,6 +3,17 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [2.26.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.25.21...@standardnotes/analytics@2.26.0) (2023-09-01)
### Bug Fixes
* **analytics:** throwing errors on unexisting users ([c511f25](https://github.com/standardnotes/server/commit/c511f259c765fe5cb5b022213d2a59d67390a3c4))
* remove the alive and kicking info logs on workers ([1bef127](https://github.com/standardnotes/server/commit/1bef1279e6dbf3cbdfa87e44aa9108ed6dbb3b0f))
### Features
* send websocket event to user when a message is sent ([#802](https://github.com/standardnotes/server/issues/802)) ([9a568b0](https://github.com/standardnotes/server/commit/9a568b0f73078ab74d4771bac469903a124e67da))
## [2.25.21](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.25.20...@standardnotes/analytics@2.25.21) (2023-08-31)
**Note:** Version bump only for package @standardnotes/analytics

View File

@@ -22,6 +22,4 @@ void container.load().then((container) => {
const subscriberFactory: DomainEventSubscriberFactoryInterface = container.get(TYPES.DomainEventSubscriberFactory)
subscriberFactory.create().start()
setInterval(() => logger.info('Alive and kicking!'), 20 * 60 * 1000)
})

View File

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

View File

@@ -41,13 +41,13 @@ export class AccountDeletionRequestedEventHandler implements DomainEventHandlerI
Period.ThisMonth,
])
await this.analyticsEntityRepository.remove(analyticsEntity)
if (this.mixpanelClient !== null) {
this.mixpanelClient.track(event.type, {
distinct_id: analyticsEntity.id.toString(),
user_created_at: this.timer.convertMicrosecondsToDate(event.payload.userCreatedAtTimestamp),
})
}
await this.analyticsEntityRepository.remove(analyticsEntity)
}
}

View File

@@ -17,7 +17,11 @@ export class PaymentFailedEventHandler implements DomainEventHandlerInterface {
) {}
async handle(event: PaymentFailedEvent): Promise<void> {
const { analyticsId } = await this.getUserAnalyticsId.execute({ userEmail: event.payload.userEmail })
const analyticsMetadataOrError = await this.getUserAnalyticsId.execute({ userEmail: event.payload.userEmail })
if (analyticsMetadataOrError.isFailed()) {
return
}
const { analyticsId } = analyticsMetadataOrError.getValue()
await this.analyticsStore.markActivity([AnalyticsActivity.PaymentFailed], analyticsId, [
Period.Today,
Period.ThisWeek,

View File

@@ -88,7 +88,11 @@ export class PaymentSuccessEventHandler implements DomainEventHandlerInterface {
) {}
async handle(event: PaymentSuccessEvent): Promise<void> {
const { analyticsId } = await this.getUserAnalyticsId.execute({ userEmail: event.payload.userEmail })
const analyticsMetadataOrError = await this.getUserAnalyticsId.execute({ userEmail: event.payload.userEmail })
if (analyticsMetadataOrError.isFailed()) {
return
}
const { analyticsId } = analyticsMetadataOrError.getValue()
await this.analyticsStore.markActivity([AnalyticsActivity.PaymentSuccess], analyticsId, [
Period.Today,
Period.ThisWeek,

View File

@@ -17,8 +17,11 @@ export class RefundProcessedEventHandler implements DomainEventHandlerInterface
) {}
async handle(event: RefundProcessedEvent): Promise<void> {
const { analyticsId } = await this.getUserAnalyticsId.execute({ userEmail: event.payload.userEmail })
const analyticsMetadataOrError = await this.getUserAnalyticsId.execute({ userEmail: event.payload.userEmail })
if (analyticsMetadataOrError.isFailed()) {
return
}
const { analyticsId } = analyticsMetadataOrError.getValue()
await this.statisticsStore.incrementMeasure(StatisticMeasureName.NAMES.Refunds, event.payload.amount, [
Period.Today,
Period.ThisWeek,

View File

@@ -13,7 +13,11 @@ export class SessionCreatedEventHandler implements DomainEventHandlerInterface {
) {}
async handle(event: SessionCreatedEvent): Promise<void> {
const { analyticsId } = await this.getUserAnalyticsId.execute({ userUuid: event.payload.userUuid })
const analyticsMetadataOrError = await this.getUserAnalyticsId.execute({ userUuid: event.payload.userUuid })
if (analyticsMetadataOrError.isFailed()) {
return
}
const { analyticsId } = analyticsMetadataOrError.getValue()
if (this.mixpanelClient !== null) {
this.mixpanelClient.track(event.type, {

View File

@@ -13,7 +13,11 @@ export class SessionRefreshedEventHandler implements DomainEventHandlerInterface
) {}
async handle(event: SessionRefreshedEvent): Promise<void> {
const { analyticsId } = await this.getUserAnalyticsId.execute({ userUuid: event.payload.userUuid })
const analyticsMetadataOrError = await this.getUserAnalyticsId.execute({ userUuid: event.payload.userUuid })
if (analyticsMetadataOrError.isFailed()) {
return
}
const { analyticsId } = analyticsMetadataOrError.getValue()
if (this.mixpanelClient !== null) {
this.mixpanelClient.track(event.type, {

View File

@@ -29,7 +29,11 @@ export class SubscriptionCancelledEventHandler implements DomainEventHandlerInte
) {}
async handle(event: SubscriptionCancelledEvent): Promise<void> {
const { analyticsId, userUuid } = await this.getUserAnalyticsId.execute({ userEmail: event.payload.userEmail })
const analyticsMetadataOrError = await this.getUserAnalyticsId.execute({ userEmail: event.payload.userEmail })
if (analyticsMetadataOrError.isFailed()) {
return
}
const { analyticsId, userUuid } = analyticsMetadataOrError.getValue()
await this.analyticsStore.markActivity([AnalyticsActivity.SubscriptionCancelled], analyticsId, [
Period.Today,
Period.ThisWeek,

View File

@@ -27,7 +27,11 @@ export class SubscriptionExpiredEventHandler implements DomainEventHandlerInterf
) {}
async handle(event: SubscriptionExpiredEvent): Promise<void> {
const { analyticsId, userUuid } = await this.getUserAnalyticsId.execute({ userEmail: event.payload.userEmail })
const analyticsMetadataOrError = await this.getUserAnalyticsId.execute({ userEmail: event.payload.userEmail })
if (analyticsMetadataOrError.isFailed()) {
return
}
const { analyticsId, userUuid } = analyticsMetadataOrError.getValue()
await this.analyticsStore.markActivity(
[AnalyticsActivity.SubscriptionExpired, AnalyticsActivity.ExistingCustomersChurn],
analyticsId,

View File

@@ -29,7 +29,11 @@ export class SubscriptionPurchasedEventHandler implements DomainEventHandlerInte
) {}
async handle(event: SubscriptionPurchasedEvent): Promise<void> {
const { analyticsId, userUuid } = await this.getUserAnalyticsId.execute({ userEmail: event.payload.userEmail })
const analyticsMetadataOrError = await this.getUserAnalyticsId.execute({ userEmail: event.payload.userEmail })
if (analyticsMetadataOrError.isFailed()) {
return
}
const { analyticsId, userUuid } = analyticsMetadataOrError.getValue()
await this.analyticsStore.markActivity([AnalyticsActivity.SubscriptionPurchased], analyticsId, [
Period.Today,
Period.ThisWeek,

View File

@@ -19,7 +19,11 @@ export class SubscriptionReactivatedEventHandler implements DomainEventHandlerIn
) {}
async handle(event: SubscriptionReactivatedEvent): Promise<void> {
const { analyticsId } = await this.getUserAnalyticsId.execute({ userEmail: event.payload.userEmail })
const analyticsMetadataOrError = await this.getUserAnalyticsId.execute({ userEmail: event.payload.userEmail })
if (analyticsMetadataOrError.isFailed()) {
return
}
const { analyticsId } = analyticsMetadataOrError.getValue()
await this.analyticsStore.markActivity([AnalyticsActivity.SubscriptionReactivated], analyticsId, [
Period.Today,
Period.ThisWeek,

View File

@@ -27,7 +27,11 @@ export class SubscriptionRefundedEventHandler implements DomainEventHandlerInter
) {}
async handle(event: SubscriptionRefundedEvent): Promise<void> {
const { analyticsId, userUuid } = await this.getUserAnalyticsId.execute({ userEmail: event.payload.userEmail })
const analyticsMetadataOrError = await this.getUserAnalyticsId.execute({ userEmail: event.payload.userEmail })
if (analyticsMetadataOrError.isFailed()) {
return
}
const { analyticsId, userUuid } = analyticsMetadataOrError.getValue()
await this.analyticsStore.markActivity([AnalyticsActivity.SubscriptionRefunded], analyticsId, [
Period.Today,
Period.ThisWeek,

View File

@@ -26,7 +26,11 @@ export class SubscriptionRenewedEventHandler implements DomainEventHandlerInterf
) {}
async handle(event: SubscriptionRenewedEvent): Promise<void> {
const { analyticsId, userUuid } = await this.getUserAnalyticsId.execute({ userEmail: event.payload.userEmail })
const analyticsMetadataOrError = await this.getUserAnalyticsId.execute({ userEmail: event.payload.userEmail })
if (analyticsMetadataOrError.isFailed()) {
return
}
const { analyticsId, userUuid } = analyticsMetadataOrError.getValue()
await this.analyticsStore.markActivity([AnalyticsActivity.SubscriptionRenewed], analyticsId, [
Period.Today,
Period.ThisWeek,

View File

@@ -24,23 +24,18 @@ describe('GetUserAnalyticsId', () => {
})
it('should return analytics id for a user by uuid', async () => {
expect((await createUseCase().execute({ userUuid: '1-2-3' })).analyticsId).toEqual(123)
expect((await createUseCase().execute({ userUuid: '1-2-3' })).getValue().analyticsId).toEqual(123)
})
it('should return analytics id for a user by email', async () => {
expect((await createUseCase().execute({ userEmail: 'test@test.te' })).analyticsId).toEqual(123)
expect((await createUseCase().execute({ userEmail: 'test@test.te' })).getValue().analyticsId).toEqual(123)
})
it('should throw error if user is missing analytics entity', async () => {
analyticsEntityRepository.findOneByUserUuid = jest.fn().mockReturnValue(null)
let error = null
try {
await createUseCase().execute({ userUuid: '1-2-3' })
} catch (caughtError) {
error = caughtError
}
const result = await createUseCase().execute({ userUuid: '1-2-3' })
expect(error).not.toBeNull()
expect(result.isFailed()).toEqual(true)
})
})

View File

@@ -1,19 +1,18 @@
import { inject, injectable } from 'inversify'
import { Username, Uuid } from '@standardnotes/domain-core'
import { Result, UseCaseInterface, Username, Uuid } from '@standardnotes/domain-core'
import TYPES from '../../../Bootstrap/Types'
import { AnalyticsEntityRepositoryInterface } from '../../Entity/AnalyticsEntityRepositoryInterface'
import { UseCaseInterface } from '../UseCaseInterface'
import { GetUserAnalyticsIdDTO } from './GetUserAnalyticsIdDTO'
import { GetUserAnalyticsIdResponse } from './GetUserAnalyticsIdResponse'
@injectable()
export class GetUserAnalyticsId implements UseCaseInterface {
export class GetUserAnalyticsId implements UseCaseInterface<GetUserAnalyticsIdResponse> {
constructor(
@inject(TYPES.AnalyticsEntityRepository) private analyticsEntityRepository: AnalyticsEntityRepositoryInterface,
) {}
async execute(dto: GetUserAnalyticsIdDTO): Promise<GetUserAnalyticsIdResponse> {
async execute(dto: GetUserAnalyticsIdDTO): Promise<Result<GetUserAnalyticsIdResponse>> {
let analyticsEntity = null
if (dto.userUuid) {
analyticsEntity = await this.analyticsEntityRepository.findOneByUserUuid(dto.userUuid)
@@ -22,13 +21,13 @@ export class GetUserAnalyticsId implements UseCaseInterface {
}
if (analyticsEntity === null) {
throw new Error(`Could not find analytics entity for user ${dto.userUuid}`)
return Result.fail(`Could not find analytics entity ${dto.userUuid}`)
}
return {
return Result.ok({
analyticsId: analyticsEntity.id,
userUuid: Uuid.create(analyticsEntity.userUuid).getValue(),
username: Username.create(analyticsEntity.username).getValue(),
}
})
}
}

View File

@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.73.4](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.73.3...@standardnotes/api-gateway@1.73.4) (2023-09-01)
**Note:** Version bump only for package @standardnotes/api-gateway
## [1.73.3](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.73.2...@standardnotes/api-gateway@1.73.3) (2023-08-31)
**Note:** Version bump only for package @standardnotes/api-gateway

View File

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

View File

@@ -3,6 +3,12 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.137.3](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.137.2...@standardnotes/auth-server@1.137.3) (2023-09-01)
### Bug Fixes
* remove the alive and kicking info logs on workers ([1bef127](https://github.com/standardnotes/server/commit/1bef1279e6dbf3cbdfa87e44aa9108ed6dbb3b0f))
## [1.137.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.137.1...@standardnotes/auth-server@1.137.2) (2023-08-31)
**Note:** Version bump only for package @standardnotes/auth-server

View File

@@ -24,6 +24,4 @@ void container.load().then((container) => {
TYPES.Auth_DomainEventSubscriberFactory,
)
subscriberFactory.create().start()
setInterval(() => logger.info('Alive and kicking!'), 20 * 60 * 1000)
})

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/auth-server",
"version": "1.137.2",
"version": "1.137.3",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.12.18](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.12.17...@standardnotes/domain-events-infra@1.12.18) (2023-09-01)
**Note:** Version bump only for package @standardnotes/domain-events-infra
## [1.12.17](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.12.16...@standardnotes/domain-events-infra@1.12.17) (2023-08-31)
**Note:** Version bump only for package @standardnotes/domain-events-infra

View File

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

View File

@@ -3,6 +3,12 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [2.121.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.120.0...@standardnotes/domain-events@2.121.0) (2023-09-01)
### Features
* send websocket event to user when a message is sent ([#802](https://github.com/standardnotes/server/issues/802)) ([9a568b0](https://github.com/standardnotes/server/commit/9a568b0f73078ab74d4771bac469903a124e67da))
# [2.120.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.119.0...@standardnotes/domain-events@2.120.0) (2023-08-31)
### Features

View File

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

View File

@@ -0,0 +1,8 @@
import { DomainEventInterface } from './DomainEventInterface'
import { MessageSentToUserEventPayload } from './MessageSentToUserEventPayload'
export interface MessageSentToUserEvent extends DomainEventInterface {
type: 'MESSAGE_SENT_TO_USER'
payload: MessageSentToUserEventPayload
}

View File

@@ -0,0 +1,11 @@
export interface MessageSentToUserEventPayload {
message: {
uuid: string
recipient_uuid: string
sender_uuid: string
encrypted_message: string
replaceability_identifier: string | null
created_at_timestamp: number
updated_at_timestamp: number
}
}

View File

@@ -40,6 +40,8 @@ export * from './Event/ListedAccountDeletedEvent'
export * from './Event/ListedAccountDeletedEventPayload'
export * from './Event/ListedAccountRequestedEvent'
export * from './Event/ListedAccountRequestedEventPayload'
export * from './Event/MessageSentToUserEvent'
export * from './Event/MessageSentToUserEventPayload'
export * from './Event/MuteEmailsSettingChangedEvent'
export * from './Event/MuteEmailsSettingChangedEventPayload'
export * from './Event/NotificationAddedForUserEvent'

View File

@@ -3,6 +3,12 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.11.28](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.11.27...@standardnotes/event-store@1.11.28) (2023-09-01)
### Bug Fixes
* remove the alive and kicking info logs on workers ([1bef127](https://github.com/standardnotes/server/commit/1bef1279e6dbf3cbdfa87e44aa9108ed6dbb3b0f))
## [1.11.27](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.11.26...@standardnotes/event-store@1.11.27) (2023-08-31)
**Note:** Version bump only for package @standardnotes/event-store

View File

@@ -18,6 +18,4 @@ void container.load().then((container) => {
const subscriberFactory: DomainEventSubscriberFactoryInterface = container.get(TYPES.DomainEventSubscriberFactory)
subscriberFactory.create().start()
setInterval(() => logger.info('Alive and kicking!'), 20 * 60 * 1000)
})

View File

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

View File

@@ -3,6 +3,12 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.22.7](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.22.6...@standardnotes/files-server@1.22.7) (2023-09-01)
### Bug Fixes
* remove the alive and kicking info logs on workers ([1bef127](https://github.com/standardnotes/files/commit/1bef1279e6dbf3cbdfa87e44aa9108ed6dbb3b0f))
## [1.22.6](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.22.5...@standardnotes/files-server@1.22.6) (2023-08-31)
**Note:** Version bump only for package @standardnotes/files-server

View File

@@ -24,6 +24,4 @@ void container.load().then((container) => {
TYPES.Files_DomainEventSubscriberFactory,
)
subscriberFactory.create().start()
setInterval(() => logger.info('Alive and kicking!'), 20 * 60 * 1000)
})

View File

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

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.15.18](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.17...@standardnotes/home-server@1.15.18) (2023-09-01)
**Note:** Version bump only for package @standardnotes/home-server
## [1.15.17](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.16...@standardnotes/home-server@1.15.17) (2023-08-31)
**Note:** Version bump only for package @standardnotes/home-server
## [1.15.16](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.15.15...@standardnotes/home-server@1.15.16) (2023-08-31)
**Note:** Version bump only for package @standardnotes/home-server

View File

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

View File

@@ -3,6 +3,20 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.30.8](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.30.7...@standardnotes/revisions-server@1.30.8) (2023-09-01)
### Bug Fixes
* **revisions:** add transition start info ([a1ee491](https://github.com/standardnotes/server/commit/a1ee491dc5835bfe9521b34f449085d2f13d5c68))
* **revisions:** info logs on total revisions transitioned count ([e5c118c](https://github.com/standardnotes/server/commit/e5c118c262535971b42177db2a5a70d959b1c5d7))
## [1.30.7](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.30.6...@standardnotes/revisions-server@1.30.7) (2023-08-31)
### Bug Fixes
* **revisions:** add more verbose messages about failures in revision transitioning ([a4929af](https://github.com/standardnotes/server/commit/a4929af2ee3df4db9f57c2b0fb250b6828095421))
* **revisions:** revisions transition check for total count at the end ([095811d](https://github.com/standardnotes/server/commit/095811dda929e6947af36e096a659f18f1b5f8b8))
## [1.30.6](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.30.5...@standardnotes/revisions-server@1.30.6) (2023-08-31)
**Note:** Version bump only for package @standardnotes/revisions-server

View File

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

View File

@@ -175,7 +175,7 @@ describe('TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser', () => {
expect(logger.error).toHaveBeenCalledTimes(1)
expect(logger.error).toHaveBeenCalledWith(
'Failed to clean up primary database revisions for user 00000000-0000-0000-0000-000000000000: error',
'Failed to clean up primary database revisions for user 00000000-0000-0000-0000-000000000000: Errored when deleting revisions for user 00000000-0000-0000-0000-000000000000: error',
)
})
})
@@ -220,12 +220,32 @@ describe('TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser', () => {
})
expect(result.isFailed()).toBeTruthy()
expect(result.getError()).toEqual('error')
expect(result.getError()).toEqual(
'Errored when migrating revisions for user 00000000-0000-0000-0000-000000000000: error',
)
expect((secondaryRevisionRepository as RevisionRepositoryInterface).removeByUserUuid).toHaveBeenCalledTimes(1)
expect(primaryRevisionRepository.removeByUserUuid).not.toHaveBeenCalled()
})
it('should return an error for a specific revision if it errors when saving to secondary database', async () => {
;(secondaryRevisionRepository as RevisionRepositoryInterface).save = jest
.fn()
.mockResolvedValueOnce(true)
.mockRejectedValueOnce(new Error('error'))
const useCase = createUseCase()
const result = await useCase.execute({
userUuid: '84c0f8e8-544a-4c7e-9adf-26209303bc1d',
})
expect(result.isFailed()).toBeTruthy()
expect(result.getError()).toEqual(
'Errored when saving revision 00000000-0000-0000-0000-000000000001 to secondary database: error',
)
})
it('should log an error if deleting Revisions from secondary database fails upon migration failure', async () => {
primaryRevisionRepository.findByUserUuid = jest
.fn()
@@ -245,7 +265,7 @@ describe('TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser', () => {
expect(logger.error).toHaveBeenCalledTimes(1)
expect(logger.error).toHaveBeenCalledWith(
'Failed to clean up secondary database revisions for user 00000000-0000-0000-0000-000000000000: error',
'Failed to clean up secondary database revisions for user 00000000-0000-0000-0000-000000000000: Errored when deleting revisions for user 00000000-0000-0000-0000-000000000000: error',
)
})
@@ -273,7 +293,7 @@ describe('TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser', () => {
expect(logger.error).toHaveBeenCalledTimes(1)
expect(logger.error).toHaveBeenCalledWith(
'Failed to clean up secondary database revisions for user 00000000-0000-0000-0000-000000000000: error',
'Failed to clean up secondary database revisions for user 00000000-0000-0000-0000-000000000000: Errored when deleting revisions for user 00000000-0000-0000-0000-000000000000: error',
)
})
@@ -351,7 +371,7 @@ describe('TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser', () => {
expect(primaryRevisionRepository.countByUserUuid).toHaveBeenCalledWith(
Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
)
expect((secondaryRevisionRepository as RevisionRepositoryInterface).countByUserUuid).toHaveBeenCalledTimes(1)
expect((secondaryRevisionRepository as RevisionRepositoryInterface).countByUserUuid).not.toHaveBeenCalled()
expect(primaryRevisionRepository.removeByUserUuid).not.toHaveBeenCalled()
expect((secondaryRevisionRepository as RevisionRepositoryInterface).removeByUserUuid).toHaveBeenCalledTimes(1)
})
@@ -368,7 +388,7 @@ describe('TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser', () => {
})
expect(result.isFailed()).toBeTruthy()
expect(result.getError()).toEqual('error')
expect(result.getError()).toEqual('Errored when checking integrity between primary and secondary database: error')
expect(primaryRevisionRepository.removeByUserUuid).not.toHaveBeenCalled()
expect((secondaryRevisionRepository as RevisionRepositoryInterface).removeByUserUuid).toHaveBeenCalledTimes(1)

View File

@@ -26,6 +26,8 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
const migrationTimeStart = this.timer.getTimestampInMicroseconds()
this.logger.info(`Transitioning revisions for user ${userUuid.value}`)
const migrationResult = await this.migrateRevisionsForUser(userUuid)
if (migrationResult.isFailed()) {
const cleanupResult = await this.deleteRevisionsForUser(userUuid, this.secondRevisionsRepository)
@@ -74,6 +76,7 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
private async migrateRevisionsForUser(userUuid: Uuid): Promise<Result<void>> {
try {
const totalRevisionsCountForUser = await this.primaryRevisionsRepository.countByUserUuid(userUuid)
let totalRevisionsCountTransitionedToSecondary = 0
const pageSize = 1
const totalPages = Math.ceil(totalRevisionsCountForUser / pageSize)
for (let currentPage = 1; currentPage <= totalPages; currentPage++) {
@@ -86,16 +89,27 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
const revisions = await this.primaryRevisionsRepository.findByUserUuid(query)
for (const revision of revisions) {
const didSave = await (this.secondRevisionsRepository as RevisionRepositoryInterface).save(revision)
if (!didSave) {
return Result.fail(`Failed to save revision ${revision.id.toString()} to secondary database`)
try {
const didSave = await (this.secondRevisionsRepository as RevisionRepositoryInterface).save(revision)
if (!didSave) {
return Result.fail(`Failed to save revision ${revision.id.toString()} to secondary database`)
}
totalRevisionsCountTransitionedToSecondary++
} catch (error) {
return Result.fail(
`Errored when saving revision ${revision.id.toString()} to secondary database: ${
(error as Error).message
}`,
)
}
}
}
this.logger.info(`Transitioned ${totalRevisionsCountTransitionedToSecondary} revisions to secondary database`)
return Result.ok()
} catch (error) {
return Result.fail((error as Error).message)
return Result.fail(`Errored when migrating revisions for user ${userUuid.value}: ${(error as Error).message}`)
}
}
@@ -108,7 +122,7 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
return Result.ok()
} catch (error) {
return Result.fail((error as Error).message)
return Result.fail(`Errored when deleting revisions for user ${userUuid.value}: ${(error as Error).message}`)
}
}
@@ -120,15 +134,6 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
private async checkIntegrityBetweenPrimaryAndSecondaryDatabase(userUuid: Uuid): Promise<Result<boolean>> {
try {
const totalRevisionsCountForUserInPrimary = await this.primaryRevisionsRepository.countByUserUuid(userUuid)
const totalRevisionsCountForUserInSecondary = await (
this.secondRevisionsRepository as RevisionRepositoryInterface
).countByUserUuid(userUuid)
if (totalRevisionsCountForUserInPrimary !== totalRevisionsCountForUserInSecondary) {
return Result.fail(
`Total revisions count for user ${userUuid.value} in primary database (${totalRevisionsCountForUserInPrimary}) does not match total revisions count in secondary database (${totalRevisionsCountForUserInSecondary})`,
)
}
const pageSize = 1
const totalPages = Math.ceil(totalRevisionsCountForUserInPrimary / pageSize)
@@ -162,9 +167,21 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
}
}
const totalRevisionsCountForUserInSecondary = await (
this.secondRevisionsRepository as RevisionRepositoryInterface
).countByUserUuid(userUuid)
if (totalRevisionsCountForUserInPrimary !== totalRevisionsCountForUserInSecondary) {
return Result.fail(
`Total revisions count for user ${userUuid.value} in primary database (${totalRevisionsCountForUserInPrimary}) does not match total revisions count in secondary database (${totalRevisionsCountForUserInSecondary})`,
)
}
return Result.ok()
} catch (error) {
return Result.fail((error as Error).message)
return Result.fail(
`Errored when checking integrity between primary and secondary database: ${(error as Error).message}`,
)
}
}
}

View File

@@ -3,6 +3,12 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.20.32](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.20.31...@standardnotes/scheduler-server@1.20.32) (2023-09-01)
### Bug Fixes
* remove the alive and kicking info logs on workers ([1bef127](https://github.com/standardnotes/server/commit/1bef1279e6dbf3cbdfa87e44aa9108ed6dbb3b0f))
## [1.20.31](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.20.30...@standardnotes/scheduler-server@1.20.31) (2023-08-31)
**Note:** Version bump only for package @standardnotes/scheduler-server

View File

@@ -22,6 +22,4 @@ void container.load().then((container) => {
const subscriberFactory: DomainEventSubscriberFactoryInterface = container.get(TYPES.DomainEventSubscriberFactory)
subscriberFactory.create().start()
setInterval(() => logger.info('Alive and kicking!'), 20 * 60 * 1000)
})

View File

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

View File

@@ -3,6 +3,16 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.90.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.89.0...@standardnotes/syncing-server@1.90.0) (2023-09-01)
### Bug Fixes
* remove the alive and kicking info logs on workers ([1bef127](https://github.com/standardnotes/syncing-server-js/commit/1bef1279e6dbf3cbdfa87e44aa9108ed6dbb3b0f))
### Features
* send websocket event to user when a message is sent ([#802](https://github.com/standardnotes/syncing-server-js/issues/802)) ([9a568b0](https://github.com/standardnotes/syncing-server-js/commit/9a568b0f73078ab74d4771bac469903a124e67da))
# [1.89.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.88.3...@standardnotes/syncing-server@1.89.0) (2023-08-31)
### Features

View File

@@ -20,6 +20,4 @@ void container.load().then((container) => {
TYPES.Sync_DomainEventSubscriberFactory,
)
subscriberFactory.create().start()
setInterval(() => logger.info('Alive and kicking!'), 20 * 60 * 1000)
})

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/syncing-server",
"version": "1.89.0",
"version": "1.90.0",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -783,7 +783,13 @@ export class ContainerConfigLoader {
container
.bind<SendMessageToUser>(TYPES.Sync_SendMessageToUser)
.toConstantValue(
new SendMessageToUser(container.get(TYPES.Sync_MessageRepository), container.get(TYPES.Sync_Timer)),
new SendMessageToUser(
container.get<MessageRepositoryInterface>(TYPES.Sync_MessageRepository),
container.get<TimerInterface>(TYPES.Sync_Timer),
container.get<DomainEventFactoryInterface>(TYPES.Sync_DomainEventFactory),
container.get<SendEventToClient>(TYPES.Sync_SendEventToClient),
container.get<Logger>(TYPES.Sync_Logger),
),
)
container
.bind<DeleteMessage>(TYPES.Sync_DeleteMessage)

View File

@@ -5,6 +5,7 @@ import {
EmailRequestedEvent,
ItemDumpedEvent,
ItemRevisionCreationRequestedEvent,
MessageSentToUserEvent,
NotificationAddedForUserEvent,
RevisionsCopyRequestedEvent,
TransitionStatusUpdatedEvent,
@@ -16,6 +17,31 @@ import { DomainEventFactoryInterface } from './DomainEventFactoryInterface'
export class DomainEventFactory implements DomainEventFactoryInterface {
constructor(private timer: TimerInterface) {}
createMessageSentToUserEvent(dto: {
message: {
uuid: string
recipient_uuid: string
sender_uuid: string
encrypted_message: string
replaceability_identifier: string | null
created_at_timestamp: number
updated_at_timestamp: number
}
}): MessageSentToUserEvent {
return {
type: 'MESSAGE_SENT_TO_USER',
createdAt: this.timer.getUTCDate(),
meta: {
correlation: {
userIdentifier: dto.message.recipient_uuid,
userIdentifierType: 'uuid',
},
origin: DomainEventService.SyncingServer,
},
payload: dto,
}
}
createNotificationAddedForUserEvent(dto: {
notification: {
uuid: string

View File

@@ -3,6 +3,7 @@ import {
EmailRequestedEvent,
ItemDumpedEvent,
ItemRevisionCreationRequestedEvent,
MessageSentToUserEvent,
NotificationAddedForUserEvent,
RevisionsCopyRequestedEvent,
TransitionStatusUpdatedEvent,
@@ -11,6 +12,17 @@ import {
export interface DomainEventFactoryInterface {
createWebSocketMessageRequestedEvent(dto: { userUuid: string; message: string }): WebSocketMessageRequestedEvent
createMessageSentToUserEvent(dto: {
message: {
uuid: string
recipient_uuid: string
sender_uuid: string
encrypted_message: string
replaceability_identifier: string | null
created_at_timestamp: number
updated_at_timestamp: number
}
}): MessageSentToUserEvent
createNotificationAddedForUserEvent(dto: {
notification: {
uuid: string

View File

@@ -3,13 +3,21 @@ import { MessageRepositoryInterface } from '../../../Message/MessageRepositoryIn
import { SendMessageToUser } from './SendMessageToUser'
import { Message } from '../../../Message/Message'
import { Result } from '@standardnotes/domain-core'
import { Logger } from 'winston'
import { DomainEventFactoryInterface } from '../../../Event/DomainEventFactoryInterface'
import { SendEventToClient } from '../../Syncing/SendEventToClient/SendEventToClient'
import { MessageSentToUserEvent } from '@standardnotes/domain-events'
describe('SendMessageToUser', () => {
let messageRepository: MessageRepositoryInterface
let timer: TimerInterface
let existingMessage: Message
let domainEventFactory: DomainEventFactoryInterface
let sendEventToClientUseCase: SendEventToClient
let logger: Logger
const createUseCase = () => new SendMessageToUser(messageRepository, timer)
const createUseCase = () =>
new SendMessageToUser(messageRepository, timer, domainEventFactory, sendEventToClientUseCase, logger)
beforeEach(() => {
existingMessage = {} as jest.Mocked<Message>
@@ -21,6 +29,17 @@ describe('SendMessageToUser', () => {
timer = {} as jest.Mocked<TimerInterface>
timer.getTimestampInMicroseconds = jest.fn().mockReturnValue(123456789)
domainEventFactory = {} as jest.Mocked<DomainEventFactoryInterface>
domainEventFactory.createMessageSentToUserEvent = jest.fn().mockReturnValue({
type: 'MESSAGE_SENT_TO_USER',
} as jest.Mocked<MessageSentToUserEvent>)
sendEventToClientUseCase = {} as jest.Mocked<SendEventToClient>
sendEventToClientUseCase.execute = jest.fn().mockReturnValue(Result.ok())
logger = {} as jest.Mocked<Logger>
logger.error = jest.fn()
})
it('saves a new message', async () => {
@@ -104,4 +123,19 @@ describe('SendMessageToUser', () => {
mock.mockRestore()
})
it('should log error if event could not be sent to user', async () => {
sendEventToClientUseCase.execute = jest.fn().mockReturnValue(Result.fail('Oops'))
const useCase = createUseCase()
const result = await useCase.execute({
recipientUuid: '00000000-0000-0000-0000-000000000000',
senderUuid: '00000000-0000-0000-0000-000000000000',
encryptedMessage: 'encrypted-message',
})
expect(result.isFailed()).toBeFalsy()
expect(logger.error).toHaveBeenCalled()
})
})

View File

@@ -4,9 +4,18 @@ import { TimerInterface } from '@standardnotes/time'
import { SendMessageToUserDTO } from './SendMessageToUserDTO'
import { MessageRepositoryInterface } from '../../../Message/MessageRepositoryInterface'
import { Message } from '../../../Message/Message'
import { DomainEventFactoryInterface } from '../../../Event/DomainEventFactoryInterface'
import { SendEventToClient } from '../../Syncing/SendEventToClient/SendEventToClient'
import { Logger } from 'winston'
export class SendMessageToUser implements UseCaseInterface<Message> {
constructor(private messageRepository: MessageRepositoryInterface, private timer: TimerInterface) {}
constructor(
private messageRepository: MessageRepositoryInterface,
private timer: TimerInterface,
private domainEventFactory: DomainEventFactoryInterface,
private sendEventToClientUseCase: SendEventToClient,
private logger: Logger,
) {}
async execute(dto: SendMessageToUserDTO): Promise<Result<Message>> {
const recipientUuidOrError = Uuid.create(dto.recipientUuid)
@@ -54,6 +63,30 @@ export class SendMessageToUser implements UseCaseInterface<Message> {
await this.messageRepository.save(message)
const event = this.domainEventFactory.createMessageSentToUserEvent({
message: {
uuid: message.id.toString(),
recipient_uuid: message.props.recipientUuid.value,
sender_uuid: message.props.senderUuid.value,
encrypted_message: message.props.encryptedMessage,
replaceability_identifier: message.props.replaceabilityIdentifier,
created_at_timestamp: message.props.timestamps.createdAt,
updated_at_timestamp: message.props.timestamps.updatedAt,
},
})
const result = await this.sendEventToClientUseCase.execute({
userUuid: message.props.recipientUuid.value,
event,
})
if (result.isFailed()) {
this.logger.error(
`Failed to send message sent event to client for user ${
message.props.recipientUuid.value
}: ${result.getError()}`,
)
}
return Result.ok(message)
}
}

View File

@@ -3,6 +3,12 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.10.25](https://github.com/standardnotes/server/compare/@standardnotes/websockets-server@1.10.24...@standardnotes/websockets-server@1.10.25) (2023-09-01)
### Bug Fixes
* remove the alive and kicking info logs on workers ([1bef127](https://github.com/standardnotes/server/commit/1bef1279e6dbf3cbdfa87e44aa9108ed6dbb3b0f))
## [1.10.24](https://github.com/standardnotes/server/compare/@standardnotes/websockets-server@1.10.23...@standardnotes/websockets-server@1.10.24) (2023-08-31)
**Note:** Version bump only for package @standardnotes/websockets-server

View File

@@ -18,6 +18,4 @@ void container.load().then((container) => {
const subscriberFactory: DomainEventSubscriberFactoryInterface = container.get(TYPES.DomainEventSubscriberFactory)
subscriberFactory.create().start()
setInterval(() => logger.info('Alive and kicking!'), 20 * 60 * 1000)
})

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/websockets-server",
"version": "1.10.24",
"version": "1.10.25",
"engines": {
"node": ">=18.0.0 <21.0.0"
},