Compare commits

..

24 Commits

Author SHA1 Message Date
standardci 3b5bd6a47f chore(release): publish new version
- @standardnotes/analytics@2.12.0
 - @standardnotes/domain-core@1.7.0
 - @standardnotes/revisions-server@1.9.3
 - @standardnotes/syncing-server@1.20.3
2022-12-05 09:25:02 +00:00
Karol Sójko 06fd404d44 feat(domain-core): distinguish between username and email 2022-12-05 10:22:59 +01:00
standardci d931c52508 chore(release): publish new version
- @standardnotes/analytics@2.11.17
 - @standardnotes/domain-core@1.6.0
 - @standardnotes/revisions-server@1.9.2
 - @standardnotes/syncing-server@1.20.2
2022-12-02 08:33:51 +00:00
Karol Sójko 800fe9e4c8 feat(domain-core): add subscription plan name value object 2022-12-02 09:32:05 +01:00
standardci 8b3d78678f chore(release): publish new version
- @standardnotes/analytics@2.11.16
 - @standardnotes/domain-core@1.5.2
 - @standardnotes/revisions-server@1.9.1
 - @standardnotes/syncing-server@1.20.1
2022-12-02 08:30:40 +00:00
Karol Sójko 2351cd3ad6 fix(revisions): change timestamps to dates value object 2022-12-01 11:31:11 +01:00
Karol Sójko dd86c5bcdf fix(domain-core): rename timestamps to dates 2022-12-01 11:25:38 +01:00
standardci d0c00e306e chore(release): publish new version
- @standardnotes/syncing-server@1.20.0
2022-11-30 17:15:44 +00:00
Karol Sójko 6cd68ddd6a feat(syncing-server): add revisions ownership fix procedure 2022-11-30 18:13:43 +01:00
standardci 02639cddb2 chore(release): publish new version
- @standardnotes/analytics@2.11.15
 - @standardnotes/api-gateway@1.39.6
 - @standardnotes/auth-server@1.60.15
 - @standardnotes/domain-events-infra@1.9.36
 - @standardnotes/domain-events@2.92.0
 - @standardnotes/event-store@1.6.32
 - @standardnotes/files-server@1.8.32
 - @standardnotes/revisions-server@1.9.0
 - @standardnotes/scheduler-server@1.13.33
 - @standardnotes/syncing-server@1.19.1
 - @standardnotes/websockets-server@1.4.33
 - @standardnotes/workspace-server@1.17.32
2022-11-30 12:46:26 +00:00
Karol Sójko 0f67aa4058 feat(revisions): add updating user uuid on revisions in async processing 2022-11-30 13:44:01 +01:00
standardci 78c3403d5f chore(release): publish new version
- @standardnotes/revisions-server@1.8.2
2022-11-29 15:30:07 +00:00
Karol Sójko fc8f8c574d fix(revisions): make user uuid nullable 2022-11-29 16:27:39 +01:00
standardci 3972ee580d chore(release): publish new version
- @standardnotes/revisions-server@1.8.1
2022-11-29 09:11:04 +00:00
Karol Sójko b0a994d5be fix(revisions): mysql queries 2022-11-29 10:08:35 +01:00
standardci 80df28a0c4 chore(release): publish new version
- @standardnotes/analytics@2.11.14
 - @standardnotes/api-gateway@1.39.5
 - @standardnotes/auth-server@1.60.14
 - @standardnotes/domain-events-infra@1.9.35
 - @standardnotes/domain-events@2.91.0
 - @standardnotes/event-store@1.6.31
 - @standardnotes/files-server@1.8.31
 - @standardnotes/revisions-server@1.8.0
 - @standardnotes/scheduler-server@1.13.32
 - @standardnotes/syncing-server@1.19.0
 - @standardnotes/websockets-server@1.4.32
 - @standardnotes/workspace-server@1.17.31
2022-11-28 14:08:28 +00:00
Karol Sójko 1c6c6a9296 fix(revisions): binding for revisions copy request handler 2022-11-28 15:06:26 +01:00
Karol Sójko 7bb698e442 feat(revisions): add copying revisions on duplicated items 2022-11-28 15:04:33 +01:00
standardci 784728cd54 chore(release): publish new version
- @standardnotes/revisions-server@1.7.1
2022-11-28 11:58:29 +00:00
Karol Sójko 4b883b68de fix(revisions): remove unnecessary indexes 2022-11-28 12:56:00 +01:00
standardci dec2cc2aaf chore(release): publish new version
- @standardnotes/revisions-server@1.7.0
2022-11-28 11:44:16 +00:00
Karol Sójko b4e8971ad2 feat(revisions): add handling account deletion requests 2022-11-28 12:42:25 +01:00
standardci 84e436265e chore(release): publish new version
- @standardnotes/revisions-server@1.6.0
2022-11-28 11:31:09 +00:00
Karol Sójko ac8a69f8d4 feat(revisions): add deleting revisions 2022-11-28 12:28:38 +01:00
105 changed files with 1335 additions and 172 deletions
+22
View File
@@ -3,6 +3,28 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [2.12.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.11.17...@standardnotes/analytics@2.12.0) (2022-12-05)
### Features
* **domain-core:** distinguish between username and email ([06fd404](https://github.com/standardnotes/server/commit/06fd404d44b44a53733f889aabd4da63f21e2f36))
## [2.11.17](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.11.16...@standardnotes/analytics@2.11.17) (2022-12-02)
**Note:** Version bump only for package @standardnotes/analytics
## [2.11.16](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.11.15...@standardnotes/analytics@2.11.16) (2022-12-02)
**Note:** Version bump only for package @standardnotes/analytics
## [2.11.15](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.11.14...@standardnotes/analytics@2.11.15) (2022-11-30)
**Note:** Version bump only for package @standardnotes/analytics
## [2.11.14](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.11.13...@standardnotes/analytics@2.11.14) (2022-11-28)
**Note:** Version bump only for package @standardnotes/analytics
## [2.11.13](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.11.12...@standardnotes/analytics@2.11.13) (2022-11-25)
**Note:** Version bump only for package @standardnotes/analytics
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/analytics",
"version": "2.11.13",
"version": "2.12.0",
"engines": {
"node": ">=18.0.0 <19.0.0"
},
@@ -18,5 +18,5 @@ export class AnalyticsEntity {
nullable: true,
})
@Index('email')
declare userEmail: string
declare username: string
}
@@ -1,7 +1,7 @@
import { DomainEventHandlerInterface, SubscriptionCancelledEvent } from '@standardnotes/domain-events'
import { inject, injectable } from 'inversify'
import { Logger } from 'winston'
import { Email } from '@standardnotes/domain-core'
import { Username } from '@standardnotes/domain-core'
import TYPES from '../../Bootstrap/Types'
import { AnalyticsActivity } from '../Analytics/AnalyticsActivity'
@@ -41,7 +41,7 @@ export class SubscriptionCancelledEventHandler implements DomainEventHandlerInte
payedAmount: event.payload.payAmount,
planName: SubscriptionPlanName.create(event.payload.subscriptionName).getValue(),
subscriptionId: event.payload.subscriptionId,
userEmail: Email.create(event.payload.userEmail).getValue(),
username: Username.create(event.payload.userEmail).getValue(),
userUuid,
})
@@ -1,7 +1,7 @@
import { Username } from '@standardnotes/domain-core'
import { DomainEventHandlerInterface, SubscriptionExpiredEvent } from '@standardnotes/domain-events'
import { inject, injectable } from 'inversify'
import { Logger } from 'winston'
import { Email } from '@standardnotes/domain-core'
import TYPES from '../../Bootstrap/Types'
import { AnalyticsActivity } from '../Analytics/AnalyticsActivity'
@@ -45,7 +45,7 @@ export class SubscriptionExpiredEventHandler implements DomainEventHandlerInterf
payedAmount: event.payload.payAmount,
planName: SubscriptionPlanName.create(event.payload.subscriptionName).getValue(),
subscriptionId: event.payload.subscriptionId,
userEmail: Email.create(event.payload.userEmail).getValue(),
username: Username.create(event.payload.userEmail).getValue(),
userUuid,
})
@@ -1,7 +1,7 @@
import { Username } from '@standardnotes/domain-core'
import { DomainEventHandlerInterface, SubscriptionPurchasedEvent } from '@standardnotes/domain-events'
import { inject, injectable } from 'inversify'
import { Logger } from 'winston'
import { Email } from '@standardnotes/domain-core'
import TYPES from '../../Bootstrap/Types'
import { AnalyticsActivity } from '../Analytics/AnalyticsActivity'
@@ -69,7 +69,7 @@ export class SubscriptionPurchasedEventHandler implements DomainEventHandlerInte
payedAmount: event.payload.payAmount,
planName: SubscriptionPlanName.create(event.payload.subscriptionName).getValue(),
subscriptionId: event.payload.subscriptionId,
userEmail: Email.create(event.payload.userEmail).getValue(),
username: Username.create(event.payload.userEmail).getValue(),
userUuid,
})
@@ -1,7 +1,7 @@
import { Username } from '@standardnotes/domain-core'
import { DomainEventHandlerInterface, SubscriptionRefundedEvent } from '@standardnotes/domain-events'
import { inject, injectable } from 'inversify'
import { Logger } from 'winston'
import { Email } from '@standardnotes/domain-core'
import TYPES from '../../Bootstrap/Types'
import { AnalyticsActivity } from '../Analytics/AnalyticsActivity'
@@ -41,7 +41,7 @@ export class SubscriptionRefundedEventHandler implements DomainEventHandlerInter
payedAmount: event.payload.payAmount,
planName: SubscriptionPlanName.create(event.payload.subscriptionName).getValue(),
subscriptionId: event.payload.subscriptionId,
userEmail: Email.create(event.payload.userEmail).getValue(),
username: Username.create(event.payload.userEmail).getValue(),
userUuid,
})
@@ -1,6 +1,6 @@
import { DomainEventHandlerInterface, SubscriptionRenewedEvent } from '@standardnotes/domain-events'
import { inject, injectable } from 'inversify'
import { Email } from '@standardnotes/domain-core'
import { Username } from '@standardnotes/domain-core'
import TYPES from '../../Bootstrap/Types'
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
@@ -41,7 +41,7 @@ export class SubscriptionRenewedEventHandler implements DomainEventHandlerInterf
payedAmount: event.payload.payAmount,
planName: SubscriptionPlanName.create(event.payload.subscriptionName).getValue(),
subscriptionId: event.payload.subscriptionId,
userEmail: Email.create(event.payload.userEmail).getValue(),
username: Username.create(event.payload.userEmail).getValue(),
userUuid,
})
@@ -18,7 +18,7 @@ export class UserRegisteredEventHandler implements DomainEventHandlerInterface {
async handle(event: UserRegisteredEvent): Promise<void> {
let analyticsEntity = new AnalyticsEntity()
analyticsEntity.userUuid = event.payload.userUuid
analyticsEntity.userEmail = event.payload.email
analyticsEntity.username = event.payload.email
analyticsEntity = await this.analyticsEntityRepository.save(analyticsEntity)
await this.analyticsStore.markActivity([AnalyticsActivity.Register], analyticsEntity.id, [
@@ -1,5 +1,5 @@
import { injectable } from 'inversify'
import { Email, MapperInterface, UniqueEntityId } from '@standardnotes/domain-core'
import { MapperInterface, UniqueEntityId, Username } from '@standardnotes/domain-core'
import { TypeORMRevenueModification } from '../../Infra/TypeORM/TypeORMRevenueModification'
import { MonthlyRevenue } from '../Revenue/MonthlyRevenue'
@@ -14,7 +14,7 @@ export class RevenueModificationMap implements MapperInterface<RevenueModificati
toDomain(persistence: TypeORMRevenueModification): RevenueModification {
const userOrError = User.create(
{
email: Email.create(persistence.userEmail).getValue(),
username: Username.create(persistence.username).getValue(),
},
new UniqueEntityId(persistence.userUuid),
)
@@ -70,7 +70,7 @@ export class RevenueModificationMap implements MapperInterface<RevenueModificati
persistence.previousMonthlyRevenue = domain.props.previousMonthlyRevenue.value
persistence.subscriptionId = subscription.id.toValue() as number
persistence.subscriptionPlan = subscription.props.planName.value
persistence.userEmail = user.props.email.value
persistence.username = user.props.username.value
persistence.userUuid = user.id.toString()
persistence.createdAt = domain.props.createdAt
@@ -1,4 +1,4 @@
import { Email } from '@standardnotes/domain-core'
import { Username } from '@standardnotes/domain-core'
import { Subscription } from '../Subscription/Subscription'
import { SubscriptionEventType } from '../Subscription/SubscriptionEventType'
@@ -19,7 +19,7 @@ describe('RevenueModification', () => {
planName: SubscriptionPlanName.create('PRO_PLAN').getValue(),
}).getValue()
user = User.create({
email: Email.create('test@test.te').getValue(),
username: Username.create('test@test.te').getValue(),
}).getValue()
})
@@ -15,7 +15,7 @@ describe('GetUserAnalyticsId', () => {
analyticsEntity = {
id: 123,
userUuid: '84c0f8e8-544a-4c7e-9adf-26209303bc1d',
userEmail: 'test@test.te',
username: 'test@test.te',
} as jest.Mocked<AnalyticsEntity>
analyticsEntityRepository = {} as jest.Mocked<AnalyticsEntityRepositoryInterface>
@@ -1,5 +1,5 @@
import { inject, injectable } from 'inversify'
import { Email, Uuid } from '@standardnotes/domain-core'
import { Username, Uuid } from '@standardnotes/domain-core'
import TYPES from '../../../Bootstrap/Types'
import { AnalyticsEntityRepositoryInterface } from '../../Entity/AnalyticsEntityRepositoryInterface'
@@ -28,7 +28,7 @@ export class GetUserAnalyticsId implements UseCaseInterface {
return {
analyticsId: analyticsEntity.id,
userUuid: Uuid.create(analyticsEntity.userUuid).getValue(),
userEmail: Email.create(analyticsEntity.userEmail).getValue(),
username: Username.create(analyticsEntity.username).getValue(),
}
}
}
@@ -1,7 +1,7 @@
import { Email, Uuid } from '@standardnotes/domain-core'
import { Username, Uuid } from '@standardnotes/domain-core'
export type GetUserAnalyticsIdResponse = {
analyticsId: number
userEmail: Email
username: Username
userUuid: Uuid
}
@@ -1,7 +1,7 @@
import 'reflect-metadata'
import { TimerInterface } from '@standardnotes/time'
import { Email, Result, Uuid } from '@standardnotes/domain-core'
import { Result, Username, Uuid } from '@standardnotes/domain-core'
import { MonthlyRevenue } from '../../Revenue/MonthlyRevenue'
@@ -45,7 +45,7 @@ describe('SaveRevenueModification', () => {
payedAmount: 12.99,
planName: SubscriptionPlanName.create('PRO_PLAN').getValue(),
subscriptionId: 1234,
userEmail: Email.create('test@test.te').getValue(),
username: Username.create('test@test.te').getValue(),
userUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
})
@@ -63,7 +63,7 @@ describe('SaveRevenueModification', () => {
payedAmount: 12.99,
planName: SubscriptionPlanName.create('PRO_PLAN').getValue(),
subscriptionId: 1234,
userEmail: Email.create('test@test.te').getValue(),
username: Username.create('test@test.te').getValue(),
userUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
})
@@ -81,7 +81,7 @@ describe('SaveRevenueModification', () => {
payedAmount: 2,
planName: SubscriptionPlanName.create('PRO_PLAN').getValue(),
subscriptionId: 1234,
userEmail: Email.create('test@test.te').getValue(),
username: Username.create('test@test.te').getValue(),
userUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
})
@@ -101,7 +101,7 @@ describe('SaveRevenueModification', () => {
payedAmount: 12.99,
planName: SubscriptionPlanName.create('PRO_PLAN').getValue(),
subscriptionId: 1234,
userEmail: Email.create('test@test.te').getValue(),
username: Username.create('test@test.te').getValue(),
userUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
})
@@ -122,7 +122,7 @@ describe('SaveRevenueModification', () => {
payedAmount: 12.99,
planName: SubscriptionPlanName.create('PRO_PLAN').getValue(),
subscriptionId: 1234,
userEmail: Email.create('test@test.te').getValue(),
username: Username.create('test@test.te').getValue(),
userUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
})
@@ -142,7 +142,7 @@ describe('SaveRevenueModification', () => {
payedAmount: 12.99,
planName: SubscriptionPlanName.create('PRO_PLAN').getValue(),
subscriptionId: 1234,
userEmail: Email.create('test@test.te').getValue(),
username: Username.create('test@test.te').getValue(),
userUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
})
@@ -162,7 +162,7 @@ describe('SaveRevenueModification', () => {
payedAmount: 12.99,
planName: SubscriptionPlanName.create('PRO_PLAN').getValue(),
subscriptionId: 1234,
userEmail: Email.create('test@test.te').getValue(),
username: Username.create('test@test.te').getValue(),
userUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
})
@@ -182,7 +182,7 @@ describe('SaveRevenueModification', () => {
payedAmount: 12.99,
planName: SubscriptionPlanName.create('PRO_PLAN').getValue(),
subscriptionId: 1234,
userEmail: Email.create('test@test.te').getValue(),
username: Username.create('test@test.te').getValue(),
userUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
})
@@ -202,7 +202,7 @@ describe('SaveRevenueModification', () => {
payedAmount: 12.99,
planName: SubscriptionPlanName.create('PRO_PLAN').getValue(),
subscriptionId: 1234,
userEmail: Email.create('test@test.te').getValue(),
username: Username.create('test@test.te').getValue(),
userUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
})
@@ -23,7 +23,7 @@ export class SaveRevenueModification implements DomainUseCaseInterface<RevenueMo
async execute(dto: SaveRevenueModificationDTO): Promise<Result<RevenueModification>> {
const userOrError = User.create(
{
email: dto.userEmail,
username: dto.username,
},
new UniqueEntityId(dto.userUuid.value),
)
@@ -1,4 +1,4 @@
import { Email, Uuid } from '@standardnotes/domain-core'
import { Username, Uuid } from '@standardnotes/domain-core'
import { SubscriptionEventType } from '../../Subscription/SubscriptionEventType'
import { SubscriptionPlanName } from '../../Subscription/SubscriptionPlanName'
@@ -9,7 +9,7 @@ export interface SaveRevenueModificationDTO {
planName: SubscriptionPlanName
newSubscriber: boolean
userUuid: Uuid
userEmail: Email
username: Username
subscriptionId: number
billingFrequency: number
}
@@ -1,11 +1,11 @@
import { Email } from '@standardnotes/domain-core'
import { Username } from '@standardnotes/domain-core'
import { User } from './User'
describe('User', () => {
it('should create an entity', () => {
const user = User.create({
email: Email.create('test@test.te').getValue(),
username: Username.create('test@test.te').getValue(),
}).getValue()
expect(user.id.toString()).toHaveLength(36)
@@ -1,5 +1,5 @@
import { Email } from '@standardnotes/domain-core'
import { Username } from '@standardnotes/domain-core'
export interface UserProps {
email: Email
username: Username
}
@@ -18,7 +18,7 @@ export class TypeORMRevenueModification {
length: 255,
})
@Index('email')
declare userEmail: string
declare username: string
@Column({
name: 'user_uuid',
+8
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.39.6](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.39.5...@standardnotes/api-gateway@1.39.6) (2022-11-30)
**Note:** Version bump only for package @standardnotes/api-gateway
## [1.39.5](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.39.4...@standardnotes/api-gateway@1.39.5) (2022-11-28)
**Note:** Version bump only for package @standardnotes/api-gateway
## [1.39.4](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.39.3...@standardnotes/api-gateway@1.39.4) (2022-11-25)
### Bug Fixes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/api-gateway",
"version": "1.39.4",
"version": "1.39.6",
"engines": {
"node": ">=18.0.0 <19.0.0"
},
+8
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.60.15](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.60.14...@standardnotes/auth-server@1.60.15) (2022-11-30)
**Note:** Version bump only for package @standardnotes/auth-server
## [1.60.14](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.60.13...@standardnotes/auth-server@1.60.14) (2022-11-28)
**Note:** Version bump only for package @standardnotes/auth-server
## [1.60.13](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.60.12...@standardnotes/auth-server@1.60.13) (2022-11-25)
### Bug Fixes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/auth-server",
"version": "1.60.13",
"version": "1.60.15",
"engines": {
"node": ">=18.0.0 <19.0.0"
},
+18
View File
@@ -3,6 +3,24 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.7.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-core@1.6.0...@standardnotes/domain-core@1.7.0) (2022-12-05)
### Features
* **domain-core:** distinguish between username and email ([06fd404](https://github.com/standardnotes/server/commit/06fd404d44b44a53733f889aabd4da63f21e2f36))
# [1.6.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-core@1.5.2...@standardnotes/domain-core@1.6.0) (2022-12-02)
### Features
* **domain-core:** add subscription plan name value object ([800fe9e](https://github.com/standardnotes/server/commit/800fe9e4c80c33f2da8097b5a153f470a23b7984))
## [1.5.2](https://github.com/standardnotes/server/compare/@standardnotes/domain-core@1.5.1...@standardnotes/domain-core@1.5.2) (2022-12-02)
### Bug Fixes
* **domain-core:** rename timestamps to dates ([dd86c5b](https://github.com/standardnotes/server/commit/dd86c5bcdf3a1a37d684f6416d4cc6f24497fe5e))
## [1.5.1](https://github.com/standardnotes/server/compare/@standardnotes/domain-core@1.5.0...@standardnotes/domain-core@1.5.1) (2022-11-25)
**Note:** Version bump only for package @standardnotes/domain-core
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/domain-core",
"version": "1.5.1",
"version": "1.7.0",
"engines": {
"node": ">=18.0.0 <19.0.0"
},
@@ -1,8 +1,8 @@
import { Timestamps } from './Timestamps'
import { Dates } from './Dates'
describe('Timestamps', () => {
describe('Dates', () => {
it('should create a value object', () => {
const valueOrError = Timestamps.create(new Date(1), new Date(2))
const valueOrError = Dates.create(new Date(1), new Date(2))
expect(valueOrError.isFailed()).toBeFalsy()
expect(valueOrError.getValue().createdAt).toEqual(new Date(1))
@@ -10,11 +10,11 @@ describe('Timestamps', () => {
})
it('should not create an invalid value object', () => {
let valueOrError = Timestamps.create(null as unknown as Date, '2' as unknown as Date)
let valueOrError = Dates.create(null as unknown as Date, '2' as unknown as Date)
expect(valueOrError.isFailed()).toBeTruthy()
valueOrError = Timestamps.create(new Date(2), '2' as unknown as Date)
valueOrError = Dates.create(new Date(2), '2' as unknown as Date)
expect(valueOrError.isFailed()).toBeTruthy()
})
@@ -0,0 +1,28 @@
import { Result } from '../Core/Result'
import { ValueObject } from '../Core/ValueObject'
import { DatesProps } from './DatesProps'
export class Dates extends ValueObject<DatesProps> {
get createdAt(): Date {
return this.props.createdAt
}
get updatedAt(): Date {
return this.props.updatedAt
}
private constructor(props: DatesProps) {
super(props)
}
static create(createdAt: Date, updatedAt: Date): Result<Dates> {
if (!(createdAt instanceof Date)) {
return Result.fail<Dates>(`Could not create Dates. Creation date should be a date object, given: ${createdAt}`)
}
if (!(updatedAt instanceof Date)) {
return Result.fail<Dates>(`Could not create Dates. Update date should be a date object, given: ${createdAt}`)
}
return Result.ok<Dates>(new Dates({ createdAt, updatedAt }))
}
}
@@ -1,4 +1,4 @@
export interface TimestampsProps {
export interface DatesProps {
createdAt: Date
updatedAt: Date
}
@@ -9,7 +9,7 @@ describe('Email', () => {
})
it('should not create an invalid value object', () => {
const valueOrError = Email.create('')
const valueOrError = Email.create('foobar')
expect(valueOrError.isFailed()).toBeTruthy()
})
@@ -1,6 +1,7 @@
import { ValueObject } from '../Core/ValueObject'
import { Result } from '../Core/Result'
import { EmailProps } from './EmailProps'
import { Validator } from '../Core/Validator'
export class Email extends ValueObject<EmailProps> {
get value(): string {
@@ -12,10 +13,11 @@ export class Email extends ValueObject<EmailProps> {
}
static create(email: string): Result<Email> {
if (!!email === false || email.length === 0) {
return Result.fail<Email>('Email cannot be empty')
} else {
return Result.ok<Email>(new Email({ value: email }))
const emailValidation = Validator.isValidEmail(email)
if (emailValidation.isFailed()) {
return Result.fail<Email>(emailValidation.getError())
}
return Result.ok<Email>(new Email({ value: email }))
}
}
@@ -2,7 +2,7 @@ import { RoleName } from './RoleName'
describe('RoleName', () => {
it('should create a value object', () => {
const valueOrError = RoleName.create('PRO_USER')
const valueOrError = RoleName.create(RoleName.NAMES.ProUser)
expect(valueOrError.isFailed()).toBeFalsy()
expect(valueOrError.getValue().value).toEqual('PRO_USER')
@@ -17,9 +17,9 @@ describe('RoleName', () => {
})
it('should say if a role has more power or equal power to another role', () => {
const proUserRole = RoleName.create('PRO_USER').getValue()
const plusUserRole = RoleName.create('PLUS_USER').getValue()
const coreUser = RoleName.create('CORE_USER').getValue()
const proUserRole = RoleName.create(RoleName.NAMES.ProUser).getValue()
const plusUserRole = RoleName.create(RoleName.NAMES.PlusUser).getValue()
const coreUser = RoleName.create(RoleName.NAMES.CoreUser).getValue()
expect(proUserRole.hasMoreOrEqualPowerTo(proUserRole)).toBeTruthy()
expect(proUserRole.hasMoreOrEqualPowerTo(plusUserRole)).toBeTruthy()
@@ -32,5 +32,7 @@ describe('RoleName', () => {
expect(coreUser.hasMoreOrEqualPowerTo(proUserRole)).toBeFalsy()
expect(coreUser.hasMoreOrEqualPowerTo(plusUserRole)).toBeFalsy()
expect(coreUser.hasMoreOrEqualPowerTo(coreUser)).toBeTruthy()
expect(RoleName.create(RoleName.NAMES.FilesBetaUser).getValue().hasMoreOrEqualPowerTo(coreUser)).toBeFalsy()
})
})
@@ -3,7 +3,7 @@ import { Result } from '../Core/Result'
import { RoleNameProps } from './RoleNameProps'
export class RoleName extends ValueObject<RoleNameProps> {
private static readonly NAMES = {
static readonly NAMES = {
CoreUser: 'CORE_USER',
PlusUser: 'PLUS_USER',
ProUser: 'PRO_USER',
@@ -3,7 +3,7 @@ import { RoleNameCollection } from './RoleNameCollection'
describe('RoleNameCollection', () => {
it('should create a value object', () => {
const role1 = RoleName.create('PRO_USER').getValue()
const role1 = RoleName.create(RoleName.NAMES.ProUser).getValue()
const valueOrError = RoleNameCollection.create([role1])
@@ -12,23 +12,38 @@ describe('RoleNameCollection', () => {
})
it('should tell if collections are not equal', () => {
const roles1 = [RoleName.create('PRO_USER').getValue(), RoleName.create('PLUS_USER').getValue()]
const roles1 = [
RoleName.create(RoleName.NAMES.ProUser).getValue(),
RoleName.create(RoleName.NAMES.PlusUser).getValue(),
]
const roles2 = RoleNameCollection.create([
RoleName.create('PRO_USER').getValue(),
RoleName.create('CORE_USER').getValue(),
let roles2 = RoleNameCollection.create([
RoleName.create(RoleName.NAMES.ProUser).getValue(),
RoleName.create(RoleName.NAMES.CoreUser).getValue(),
]).getValue()
const valueOrError = RoleNameCollection.create(roles1)
let valueOrError = RoleNameCollection.create(roles1)
expect(valueOrError.getValue().equals(roles2)).toBeFalsy()
roles2 = RoleNameCollection.create([
RoleName.create(RoleName.NAMES.ProUser).getValue(),
RoleName.create(RoleName.NAMES.PlusUser).getValue(),
RoleName.create(RoleName.NAMES.CoreUser).getValue(),
]).getValue()
valueOrError = RoleNameCollection.create(roles1)
expect(valueOrError.getValue().equals(roles2)).toBeFalsy()
})
it('should tell if collections are equal', () => {
const roles1 = [RoleName.create('PRO_USER').getValue(), RoleName.create('PLUS_USER').getValue()]
const roles1 = [
RoleName.create(RoleName.NAMES.ProUser).getValue(),
RoleName.create(RoleName.NAMES.PlusUser).getValue(),
]
const roles2 = RoleNameCollection.create([
RoleName.create('PRO_USER').getValue(),
RoleName.create('PLUS_USER').getValue(),
RoleName.create(RoleName.NAMES.ProUser).getValue(),
RoleName.create(RoleName.NAMES.PlusUser).getValue(),
]).getValue()
const valueOrError = RoleNameCollection.create(roles1)
@@ -36,42 +51,62 @@ describe('RoleNameCollection', () => {
})
it('should tell if collection includes element', () => {
const roles1 = [RoleName.create('PRO_USER').getValue(), RoleName.create('PLUS_USER').getValue()]
const roles1 = [
RoleName.create(RoleName.NAMES.ProUser).getValue(),
RoleName.create(RoleName.NAMES.PlusUser).getValue(),
]
const valueOrError = RoleNameCollection.create(roles1)
expect(valueOrError.getValue().includes(RoleName.create('PRO_USER').getValue())).toBeTruthy()
expect(valueOrError.getValue().includes(RoleName.create(RoleName.NAMES.ProUser).getValue())).toBeTruthy()
})
it('should tell if collection does not includes element', () => {
const roles1 = [RoleName.create('PRO_USER').getValue(), RoleName.create('PLUS_USER').getValue()]
const roles1 = [
RoleName.create(RoleName.NAMES.ProUser).getValue(),
RoleName.create(RoleName.NAMES.PlusUser).getValue(),
]
const valueOrError = RoleNameCollection.create(roles1)
expect(valueOrError.getValue().includes(RoleName.create('CORE_USER').getValue())).toBeFalsy()
expect(valueOrError.getValue().includes(RoleName.create(RoleName.NAMES.CoreUser).getValue())).toBeFalsy()
})
it('should tell if collection has a role with more or equal power to', () => {
let roles = [RoleName.create('CORE_USER').getValue()]
let roles = [RoleName.create(RoleName.NAMES.CoreUser).getValue()]
let valueOrError = RoleNameCollection.create(roles)
let roleNames = valueOrError.getValue()
expect(roleNames.hasARoleNameWithMoreOrEqualPowerTo(RoleName.create('PLUS_USER').getValue())).toBeFalsy()
expect(roleNames.hasARoleNameWithMoreOrEqualPowerTo(RoleName.create('PRO_USER').getValue())).toBeFalsy()
expect(roleNames.hasARoleNameWithMoreOrEqualPowerTo(RoleName.create('CORE_USER').getValue())).toBeTruthy()
expect(
roleNames.hasARoleNameWithMoreOrEqualPowerTo(RoleName.create(RoleName.NAMES.PlusUser).getValue()),
).toBeFalsy()
expect(roleNames.hasARoleNameWithMoreOrEqualPowerTo(RoleName.create(RoleName.NAMES.ProUser).getValue())).toBeFalsy()
expect(
roleNames.hasARoleNameWithMoreOrEqualPowerTo(RoleName.create(RoleName.NAMES.CoreUser).getValue()),
).toBeTruthy()
roles = [RoleName.create('CORE_USER').getValue(), RoleName.create('PLUS_USER').getValue()]
roles = [RoleName.create(RoleName.NAMES.CoreUser).getValue(), RoleName.create(RoleName.NAMES.PlusUser).getValue()]
valueOrError = RoleNameCollection.create(roles)
roleNames = valueOrError.getValue()
expect(roleNames.hasARoleNameWithMoreOrEqualPowerTo(RoleName.create('PLUS_USER').getValue())).toBeTruthy()
expect(roleNames.hasARoleNameWithMoreOrEqualPowerTo(RoleName.create('PRO_USER').getValue())).toBeFalsy()
expect(roleNames.hasARoleNameWithMoreOrEqualPowerTo(RoleName.create('CORE_USER').getValue())).toBeTruthy()
expect(
roleNames.hasARoleNameWithMoreOrEqualPowerTo(RoleName.create(RoleName.NAMES.PlusUser).getValue()),
).toBeTruthy()
expect(roleNames.hasARoleNameWithMoreOrEqualPowerTo(RoleName.create(RoleName.NAMES.ProUser).getValue())).toBeFalsy()
expect(
roleNames.hasARoleNameWithMoreOrEqualPowerTo(RoleName.create(RoleName.NAMES.CoreUser).getValue()),
).toBeTruthy()
roles = [RoleName.create('PRO_USER').getValue(), RoleName.create('PLUS_USER').getValue()]
roles = [RoleName.create(RoleName.NAMES.ProUser).getValue(), RoleName.create(RoleName.NAMES.PlusUser).getValue()]
valueOrError = RoleNameCollection.create(roles)
roleNames = valueOrError.getValue()
expect(roleNames.hasARoleNameWithMoreOrEqualPowerTo(RoleName.create('PLUS_USER').getValue())).toBeTruthy()
expect(roleNames.hasARoleNameWithMoreOrEqualPowerTo(RoleName.create('PRO_USER').getValue())).toBeTruthy()
expect(roleNames.hasARoleNameWithMoreOrEqualPowerTo(RoleName.create('CORE_USER').getValue())).toBeTruthy()
expect(
roleNames.hasARoleNameWithMoreOrEqualPowerTo(RoleName.create(RoleName.NAMES.PlusUser).getValue()),
).toBeTruthy()
expect(
roleNames.hasARoleNameWithMoreOrEqualPowerTo(RoleName.create(RoleName.NAMES.ProUser).getValue()),
).toBeTruthy()
expect(
roleNames.hasARoleNameWithMoreOrEqualPowerTo(RoleName.create(RoleName.NAMES.CoreUser).getValue()),
).toBeTruthy()
})
})
@@ -1,32 +0,0 @@
import { Result } from '../Core/Result'
import { ValueObject } from '../Core/ValueObject'
import { TimestampsProps } from './TimestampsProps'
export class Timestamps extends ValueObject<TimestampsProps> {
get createdAt(): Date {
return this.props.createdAt
}
get updatedAt(): Date {
return this.props.updatedAt
}
private constructor(props: TimestampsProps) {
super(props)
}
static create(createdAt: Date, updatedAt: Date): Result<Timestamps> {
if (!(createdAt instanceof Date)) {
return Result.fail<Timestamps>(
`Could not create Timestamps. Creation date should be a date object, given: ${createdAt}`,
)
}
if (!(updatedAt instanceof Date)) {
return Result.fail<Timestamps>(
`Could not create Timestamps. Update date should be a date object, given: ${createdAt}`,
)
}
return Result.ok<Timestamps>(new Timestamps({ createdAt, updatedAt }))
}
}
@@ -0,0 +1,16 @@
import { Username } from './Username'
describe('Username', () => {
it('should create a value object', () => {
const valueOrError = Username.create('test@test.te')
expect(valueOrError.isFailed()).toBeFalsy()
expect(valueOrError.getValue().value).toEqual('test@test.te')
})
it('should not create an invalid value object', () => {
const valueOrError = Username.create('')
expect(valueOrError.isFailed()).toBeTruthy()
})
})
@@ -0,0 +1,22 @@
import { ValueObject } from '../Core/ValueObject'
import { Result } from '../Core/Result'
import { UsernameProps } from './UsernameProps'
import { Validator } from '../Core/Validator'
export class Username extends ValueObject<UsernameProps> {
get value(): string {
return this.props.value
}
private constructor(props: UsernameProps) {
super(props)
}
static create(username: string): Result<Username> {
if (Validator.isNotEmpty(username).isFailed()) {
return Result.fail<Username>('Username cannot be empty')
}
return Result.ok<Username>(new Username({ value: username }))
}
}
@@ -0,0 +1,3 @@
export interface UsernameProps {
value: string
}
@@ -18,6 +18,31 @@ describe('Validator', () => {
'../../escaped.sh',
]
const validEmails = [
'something@something.com',
'someone@localhost.localdomain',
'a/b@domain.com',
'{}@domain.com',
'karol+test@standardnotes.com',
"m*'!%@something.sa",
'tu!!7n7.ad##0!!!@company.ca',
'%@com.com',
"!#$%&'*+/=?^_`{|}~.-@com.com",
'someone@do-ma-in.com',
'""testlah""@example.com',
]
const invalidEmails = [
'someone@127.0.0.1',
'a@b.b',
'',
null,
'.wooly@example.com',
'wo..oly@example.com',
'somebody@example',
'a @p.com',
]
it('should validate proper uuids', () => {
for (const validUuid of validUuids) {
expect(Validator.isValidUuid(validUuid).isFailed()).toBeFalsy()
@@ -29,4 +54,28 @@ describe('Validator', () => {
expect(Validator.isValidUuid(invalidUuid as string).isFailed()).toBeTruthy()
}
})
it('should validate proper emails', () => {
for (const validEmail of validEmails) {
expect(Validator.isValidEmail(validEmail).isFailed()).toBeFalsy()
}
})
it('should not validate invalid emails', () => {
for (const invalidEmail of invalidEmails) {
expect(Validator.isValidEmail(invalidEmail as string).isFailed()).toBeTruthy()
}
})
it('should validate value if not empty', () => {
for (const value of [1, 'foobar', {}, 0]) {
expect(Validator.isNotEmpty(value).isFailed()).toBeFalsy()
}
})
it('should not validate value if empty', () => {
for (const value of [null, undefined, '', []]) {
expect(Validator.isNotEmpty(value).isFailed()).toBeTruthy()
}
})
})
@@ -2,6 +2,8 @@ import { Result } from './Result'
export class Validator {
private static readonly UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-5][0-9a-f]{3}-[089ab][0-9a-f]{3}-[0-9a-f]{12}$/i
private static readonly EMAIL_REGEX =
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
static isValidUuid(value: string): Result<string> {
const matchesUuidRegex = String(value).toLowerCase().match(Validator.UUID_REGEX) !== null
@@ -11,4 +13,25 @@ export class Validator {
return Result.fail(`Given value is not a valid uuid: ${value}`)
}
static isValidEmail(value: string): Result<string> {
const matchesUuidRegex = String(value).toLowerCase().match(Validator.EMAIL_REGEX) !== null
if (matchesUuidRegex) {
return Result.ok()
}
return Result.fail(`Given value is not a valid email address: ${value}`)
}
static isNotEmpty(value: unknown): Result<string> {
if (value instanceof Array && value.length === 0) {
return Result.fail(`Given value is empty: ${value}`)
}
if (value === null || value === undefined || value === '') {
return Result.fail(`Given value is empty: ${value}`)
}
return Result.ok()
}
}
@@ -0,0 +1,18 @@
import { SubscriptionPlanName } from './SubscriptionPlanName'
describe('SubscriptionPlanName', () => {
it('should create a value object', () => {
const valueOrError = SubscriptionPlanName.create('PRO_PLAN')
expect(valueOrError.isFailed()).toBeFalsy()
expect(valueOrError.getValue().value).toEqual('PRO_PLAN')
})
it('should not create an invalid value object', () => {
for (const value of ['', undefined, null, 0, 'SOME_PLAN']) {
const valueOrError = SubscriptionPlanName.create(value as string)
expect(valueOrError.isFailed()).toBeTruthy()
}
})
})
@@ -0,0 +1,27 @@
import { ValueObject } from '../Core/ValueObject'
import { Result } from '../Core/Result'
import { SubscriptionPlanNameProps } from './SubscriptionPlanNameProps'
export class SubscriptionPlanName extends ValueObject<SubscriptionPlanNameProps> {
static readonly NAMES = {
PlusPlan: 'PLUS_PLAN',
ProPlan: 'PRO_PLAN',
}
get value(): string {
return this.props.value
}
private constructor(props: SubscriptionPlanNameProps) {
super(props)
}
static create(name: string): Result<SubscriptionPlanName> {
const isValidName = Object.values(this.NAMES).includes(name)
if (!isValidName) {
return Result.fail<SubscriptionPlanName>(`Invalid subscription plan name: ${name}`)
} else {
return Result.ok<SubscriptionPlanName>(new SubscriptionPlanName({ value: name }))
}
}
}
@@ -0,0 +1,3 @@
export interface SubscriptionPlanNameProps {
value: string
}
+7 -2
View File
@@ -1,11 +1,13 @@
export * from './Common/Dates'
export * from './Common/DatesProps'
export * from './Common/Email'
export * from './Common/EmailProps'
export * from './Common/RoleName'
export * from './Common/RoleNameProps'
export * from './Common/RoleNameCollection'
export * from './Common/RoleNameCollectionProps'
export * from './Common/Timestamps'
export * from './Common/TimestampsProps'
export * from './Common/Username'
export * from './Common/UsernameProps'
export * from './Common/Uuid'
export * from './Common/UuidProps'
@@ -20,4 +22,7 @@ export * from './Core/ValueObjectProps'
export * from './Mapping/MapperInterface'
export * from './Subscription/SubscriptionPlanName'
export * from './Subscription/SubscriptionPlanNameProps'
export * from './UseCase/UseCaseInterface'
@@ -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.9.36](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.9.35...@standardnotes/domain-events-infra@1.9.36) (2022-11-30)
**Note:** Version bump only for package @standardnotes/domain-events-infra
## [1.9.35](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.9.34...@standardnotes/domain-events-infra@1.9.35) (2022-11-28)
**Note:** Version bump only for package @standardnotes/domain-events-infra
## [1.9.34](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.9.33...@standardnotes/domain-events-infra@1.9.34) (2022-11-25)
**Note:** Version bump only for package @standardnotes/domain-events-infra
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/domain-events-infra",
"version": "1.9.34",
"version": "1.9.36",
"engines": {
"node": ">=18.0.0 <19.0.0"
},
+12
View File
@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [2.92.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.91.0...@standardnotes/domain-events@2.92.0) (2022-11-30)
### Features
* **revisions:** add updating user uuid on revisions in async processing ([0f67aa4](https://github.com/standardnotes/server/commit/0f67aa4058301dfa92794b90a842966173f71b95))
# [2.91.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.90.2...@standardnotes/domain-events@2.91.0) (2022-11-28)
### Features
* **revisions:** add copying revisions on duplicated items ([7bb698e](https://github.com/standardnotes/server/commit/7bb698e44222ef128d9642d625e96b7d26ee4dbf))
## [2.90.2](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.90.1...@standardnotes/domain-events@2.90.2) (2022-11-25)
**Note:** Version bump only for package @standardnotes/domain-events
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/domain-events",
"version": "2.90.2",
"version": "2.92.0",
"engines": {
"node": ">=18.0.0 <19.0.0"
},
@@ -0,0 +1,7 @@
import { DomainEventInterface } from './DomainEventInterface'
import { RevisionsCopyRequestedEventPayload } from './RevisionsCopyRequestedEventPayload'
export interface RevisionsCopyRequestedEvent extends DomainEventInterface {
type: 'REVISIONS_COPY_REQUESTED'
payload: RevisionsCopyRequestedEventPayload
}
@@ -0,0 +1,4 @@
export interface RevisionsCopyRequestedEventPayload {
newItemUuid: string
originalItemUuid: string
}
@@ -0,0 +1,7 @@
import { DomainEventInterface } from './DomainEventInterface'
import { RevisionsOwnershipUpdateRequestedEventPayload } from './RevisionsOwnershipUpdateRequestedEventPayload'
export interface RevisionsOwnershipUpdateRequestedEvent extends DomainEventInterface {
type: 'REVISIONS_OWNERSHIP_UPDATE_REQUESTED'
payload: RevisionsOwnershipUpdateRequestedEventPayload
}
@@ -0,0 +1,4 @@
export interface RevisionsOwnershipUpdateRequestedEventPayload {
itemUuid: string
userUuid: string
}
@@ -74,6 +74,10 @@ export * from './Event/RefundRequestedEvent'
export * from './Event/RefundRequestedEventPayload'
export * from './Event/RefundProcessedEvent'
export * from './Event/RefundProcessedEventPayload'
export * from './Event/RevisionsCopyRequestedEvent'
export * from './Event/RevisionsCopyRequestedEventPayload'
export * from './Event/RevisionsOwnershipUpdateRequestedEvent'
export * from './Event/RevisionsOwnershipUpdateRequestedEventPayload'
export * from './Event/SharedSubscriptionInvitationCanceledEvent'
export * from './Event/SharedSubscriptionInvitationCanceledEventPayload'
export * from './Event/SharedSubscriptionInvitationCreatedEvent'
+8
View File
@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.6.32](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.6.31...@standardnotes/event-store@1.6.32) (2022-11-30)
**Note:** Version bump only for package @standardnotes/event-store
## [1.6.31](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.6.30...@standardnotes/event-store@1.6.31) (2022-11-28)
**Note:** Version bump only for package @standardnotes/event-store
## [1.6.30](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.6.29...@standardnotes/event-store@1.6.30) (2022-11-25)
**Note:** Version bump only for package @standardnotes/event-store
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/event-store",
"version": "1.6.30",
"version": "1.6.32",
"description": "Event Store Service",
"private": true,
"main": "dist/src/index.js",
+8
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.8.32](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.8.31...@standardnotes/files-server@1.8.32) (2022-11-30)
**Note:** Version bump only for package @standardnotes/files-server
## [1.8.31](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.8.30...@standardnotes/files-server@1.8.31) (2022-11-28)
**Note:** Version bump only for package @standardnotes/files-server
## [1.8.30](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.8.29...@standardnotes/files-server@1.8.30) (2022-11-25)
**Note:** Version bump only for package @standardnotes/files-server
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/files-server",
"version": "1.8.30",
"version": "1.8.32",
"engines": {
"node": ">=18.0.0 <19.0.0"
},
+60
View File
@@ -3,6 +3,66 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.9.3](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.9.2...@standardnotes/revisions-server@1.9.3) (2022-12-05)
**Note:** Version bump only for package @standardnotes/revisions-server
## [1.9.2](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.9.1...@standardnotes/revisions-server@1.9.2) (2022-12-02)
**Note:** Version bump only for package @standardnotes/revisions-server
## [1.9.1](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.9.0...@standardnotes/revisions-server@1.9.1) (2022-12-02)
### Bug Fixes
* **revisions:** change timestamps to dates value object ([2351cd3](https://github.com/standardnotes/server/commit/2351cd3ad660c81b3b6bbc3759bc1c32a03406af))
# [1.9.0](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.8.2...@standardnotes/revisions-server@1.9.0) (2022-11-30)
### Features
* **revisions:** add updating user uuid on revisions in async processing ([0f67aa4](https://github.com/standardnotes/server/commit/0f67aa4058301dfa92794b90a842966173f71b95))
## [1.8.2](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.8.1...@standardnotes/revisions-server@1.8.2) (2022-11-29)
### Bug Fixes
* **revisions:** make user uuid nullable ([fc8f8c5](https://github.com/standardnotes/server/commit/fc8f8c574dbc8901cc4a12986154841d39abcc9b))
## [1.8.1](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.8.0...@standardnotes/revisions-server@1.8.1) (2022-11-29)
### Bug Fixes
* **revisions:** mysql queries ([b0a994d](https://github.com/standardnotes/server/commit/b0a994d5be3b35f054a14a6e9661232090ec11e5))
# [1.8.0](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.7.1...@standardnotes/revisions-server@1.8.0) (2022-11-28)
### Bug Fixes
* **revisions:** binding for revisions copy request handler ([1c6c6a9](https://github.com/standardnotes/server/commit/1c6c6a9296d91c35699a15b2cb4182e26233eeb2))
### Features
* **revisions:** add copying revisions on duplicated items ([7bb698e](https://github.com/standardnotes/server/commit/7bb698e44222ef128d9642d625e96b7d26ee4dbf))
## [1.7.1](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.7.0...@standardnotes/revisions-server@1.7.1) (2022-11-28)
### Bug Fixes
* **revisions:** remove unnecessary indexes ([4b883b6](https://github.com/standardnotes/server/commit/4b883b68def777b0c0682cc6a8af6fd968b18d9f))
# [1.7.0](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.6.0...@standardnotes/revisions-server@1.7.0) (2022-11-28)
### Features
* **revisions:** add handling account deletion requests ([b4e8971](https://github.com/standardnotes/server/commit/b4e8971ad27fd198239f6eb976b8286575373ed6))
# [1.6.0](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.5.0...@standardnotes/revisions-server@1.6.0) (2022-11-28)
### Features
* **revisions:** add deleting revisions ([ac8a69f](https://github.com/standardnotes/server/commit/ac8a69f8d428e3cf8e4df5269db3cb31d9b118d5))
# [1.5.0](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.4.8...@standardnotes/revisions-server@1.5.0) (2022-11-28)
### Features
@@ -0,0 +1,15 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class removeDateIndexes1669636497932 implements MigrationInterface {
name = 'removeDateIndexes1669636497932'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('DROP INDEX `created_at` ON `revisions`')
await queryRunner.query('DROP INDEX `creation_date` ON `revisions`')
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('CREATE INDEX `creation_date` ON `revisions` (`creation_date`)')
await queryRunner.query('CREATE INDEX `created_at` ON `revisions` (`created_at`)')
}
}
@@ -0,0 +1,13 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class makeUserUuidNullable1669735585016 implements MigrationInterface {
name = 'makeUserUuidNullable1669735585016'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('ALTER TABLE `revisions` CHANGE `user_uuid` `user_uuid` varchar(36) NULL')
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('ALTER TABLE `revisions` CHANGE `user_uuid` `user_uuid` varchar(36) NOT NULL')
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/revisions-server",
"version": "1.5.0",
"version": "1.9.3",
"engines": {
"node": ">=18.0.0 <19.0.0"
},
@@ -37,6 +37,11 @@ import { DumpRepositoryInterface } from '../Domain/Dump/DumpRepositoryInterface'
import { S3DumpRepository } from '../Infra/S3/S3ItemDumpRepository'
import { FSDumpRepository } from '../Infra/FS/FSDumpRepository'
import { GetRevision } from '../Domain/UseCase/GetRevision/GetRevision'
import { DeleteRevision } from '../Domain/UseCase/DeleteRevision/DeleteRevision'
import { AccountDeletionRequestedEventHandler } from '../Domain/Handler/AccountDeletionRequestedEventHandler'
import { RevisionsCopyRequestedEventHandler } from '../Domain/Handler/RevisionsCopyRequestedEventHandler'
import { CopyRevisions } from '../Domain/UseCase/CopyRevisions/CopyRevisions'
import { RevisionsOwnershipUpdateRequestedEventHandler } from '../Domain/Handler/RevisionsOwnershipUpdateRequestedEventHandler'
// eslint-disable-next-line @typescript-eslint/no-var-requires
const newrelicFormatter = require('@newrelic/winston-enricher')
@@ -156,6 +161,12 @@ export class ContainerConfigLoader {
container
.bind<GetRevision>(TYPES.GetRevision)
.toConstantValue(new GetRevision(container.get(TYPES.RevisionRepository)))
container
.bind<DeleteRevision>(TYPES.DeleteRevision)
.toConstantValue(new DeleteRevision(container.get(TYPES.RevisionRepository)))
container
.bind<CopyRevisions>(TYPES.CopyRevisions)
.toConstantValue(new CopyRevisions(container.get(TYPES.RevisionRepository)))
// Controller
container
@@ -164,6 +175,7 @@ export class ContainerConfigLoader {
new RevisionsController(
container.get(TYPES.GetRevisionsMetada),
container.get(TYPES.GetRevision),
container.get(TYPES.DeleteRevision),
container.get(TYPES.Logger),
),
)
@@ -174,6 +186,19 @@ export class ContainerConfigLoader {
.toConstantValue(
new ItemDumpedEventHandler(container.get(TYPES.DumpRepository), container.get(TYPES.RevisionRepository)),
)
container
.bind<AccountDeletionRequestedEventHandler>(TYPES.AccountDeletionRequestedEventHandler)
.toConstantValue(
new AccountDeletionRequestedEventHandler(container.get(TYPES.RevisionRepository), container.get(TYPES.Logger)),
)
container
.bind<RevisionsCopyRequestedEventHandler>(TYPES.RevisionsCopyRequestedEventHandler)
.toConstantValue(
new RevisionsCopyRequestedEventHandler(container.get(TYPES.CopyRevisions), container.get(TYPES.Logger)),
)
container
.bind<RevisionsOwnershipUpdateRequestedEventHandler>(TYPES.RevisionsOwnershipUpdateRequestedEventHandler)
.toConstantValue(new RevisionsOwnershipUpdateRequestedEventHandler(container.get(TYPES.RevisionRepository)))
// Services
container
@@ -187,6 +212,9 @@ export class ContainerConfigLoader {
const eventHandlers: Map<string, DomainEventHandlerInterface> = new Map([
['ITEM_DUMPED', container.get(TYPES.ItemDumpedEventHandler)],
['ACCOUNT_DELETION_REQUESTED', container.get(TYPES.AccountDeletionRequestedEventHandler)],
['REVISIONS_COPY_REQUESTED', container.get(TYPES.RevisionsCopyRequestedEventHandler)],
['REVISIONS_OWNERSHIP_UPDATE_REQUESTED', container.get(TYPES.RevisionsOwnershipUpdateRequestedEventHandler)],
])
if (env.get('SQS_QUEUE_URL', true)) {
@@ -26,10 +26,15 @@ const TYPES = {
// use cases
GetRevisionsMetada: Symbol.for('GetRevisionsMetada'),
GetRevision: Symbol.for('GetRevision'),
DeleteRevision: Symbol.for('DeleteRevision'),
CopyRevisions: Symbol.for('CopyRevisions'),
// Controller
RevisionsController: Symbol.for('RevisionsController'),
// Handlers
ItemDumpedEventHandler: Symbol.for('ItemDumpedEventHandler'),
AccountDeletionRequestedEventHandler: Symbol.for('AccountDeletionRequestedEventHandler'),
RevisionsCopyRequestedEventHandler: Symbol.for('RevisionsCopyRequestedEventHandler'),
RevisionsOwnershipUpdateRequestedEventHandler: Symbol.for('RevisionsOwnershipUpdateRequestedEventHandler'),
// Services
CrossServiceTokenDecoder: Symbol.for('CrossServiceTokenDecoder'),
DomainEventSubscriberFactory: Symbol.for('DomainEventSubscriberFactory'),
@@ -1,6 +1,7 @@
import { Result } from '@standardnotes/domain-core'
import { Logger } from 'winston'
import { DeleteRevision } from '../Domain/UseCase/DeleteRevision/DeleteRevision'
import { GetRevision } from '../Domain/UseCase/GetRevision/GetRevision'
import { GetRevisionsMetada } from '../Domain/UseCase/GetRevisionsMetada/GetRevisionsMetada'
@@ -9,9 +10,10 @@ import { RevisionsController } from './RevisionsController'
describe('RevisionsController', () => {
let getRevisionsMetadata: GetRevisionsMetada
let getRevision: GetRevision
let deleteRevision: DeleteRevision
let logger: Logger
const createController = () => new RevisionsController(getRevisionsMetadata, getRevision, logger)
const createController = () => new RevisionsController(getRevisionsMetadata, getRevision, deleteRevision, logger)
beforeEach(() => {
getRevisionsMetadata = {} as jest.Mocked<GetRevisionsMetada>
@@ -20,6 +22,9 @@ describe('RevisionsController', () => {
getRevision = {} as jest.Mocked<GetRevision>
getRevision.execute = jest.fn().mockReturnValue(Result.ok())
deleteRevision = {} as jest.Mocked<DeleteRevision>
deleteRevision.execute = jest.fn().mockReturnValue(Result.ok())
logger = {} as jest.Mocked<Logger>
logger.warn = jest.fn()
})
@@ -49,4 +54,17 @@ describe('RevisionsController', () => {
expect(response.status).toEqual(400)
})
it('should delete revision', async () => {
const response = await createController().deleteRevision({ revisionUuid: '1-2-3', userUuid: '1-2-3' })
expect(response.status).toEqual(200)
})
it('should indicate failure to delete revision', async () => {
deleteRevision.execute = jest.fn().mockReturnValue(Result.fail('Oops'))
const response = await createController().deleteRevision({ revisionUuid: '1-2-3', userUuid: '1-2-3' })
expect(response.status).toEqual(400)
})
})
@@ -5,11 +5,14 @@ import { GetRevisionsMetada } from '../Domain/UseCase/GetRevisionsMetada/GetRevi
import { GetRevisionsMetadataRequestParams } from '../Infra/Http/GetRevisionsMetadataRequestParams'
import { GetRevisionRequestParams } from '../Infra/Http/GetRevisionRequestParams'
import { GetRevision } from '../Domain/UseCase/GetRevision/GetRevision'
import { DeleteRevision } from '../Domain/UseCase/DeleteRevision/DeleteRevision'
import { DeleteRevisionRequestParams } from '../Infra/Http/DeleteRevisionRequestParams'
export class RevisionsController {
constructor(
private getRevisionsMetadata: GetRevisionsMetada,
private doGetRevision: GetRevision,
private doDeleteRevision: DeleteRevision,
private logger: Logger,
) {}
@@ -39,13 +42,13 @@ export class RevisionsController {
}
async getRevision(params: GetRevisionRequestParams): Promise<HttpResponse> {
const revisionMetadataOrError = await this.doGetRevision.execute({
const revisionOrError = await this.doGetRevision.execute({
revisionUuid: params.revisionUuid,
userUuid: params.userUuid,
})
if (revisionMetadataOrError.isFailed()) {
this.logger.warn(revisionMetadataOrError.getError())
if (revisionOrError.isFailed()) {
this.logger.warn(revisionOrError.getError())
return {
status: HttpStatusCode.BadRequest,
@@ -59,7 +62,32 @@ export class RevisionsController {
return {
status: HttpStatusCode.Success,
data: { revision: revisionMetadataOrError.getValue() },
data: { revision: revisionOrError.getValue() },
}
}
async deleteRevision(params: DeleteRevisionRequestParams): Promise<HttpResponse> {
const revisionOrError = await this.doDeleteRevision.execute({
revisionUuid: params.revisionUuid,
userUuid: params.userUuid,
})
if (revisionOrError.isFailed()) {
this.logger.warn(revisionOrError.getError())
return {
status: HttpStatusCode.BadRequest,
data: {
error: {
message: 'Could not delete revision.',
},
},
}
}
return {
status: HttpStatusCode.Success,
data: { message: revisionOrError.getValue() },
}
}
}
@@ -0,0 +1,45 @@
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()
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()
})
})
@@ -0,0 +1,23 @@
import { Uuid } from '@standardnotes/domain-core'
import { AccountDeletionRequestedEvent, DomainEventHandlerInterface } from '@standardnotes/domain-events'
import { Logger } from 'winston'
import { RevisionRepositoryInterface } from '../Revision/RevisionRepositoryInterface'
export class AccountDeletionRequestedEventHandler implements DomainEventHandlerInterface {
constructor(private revisionRepository: RevisionRepositoryInterface, private logger: Logger) {}
async handle(event: AccountDeletionRequestedEvent): Promise<void> {
const userUuidOrError = Uuid.create(event.payload.userUuid)
if (userUuidOrError.isFailed()) {
this.logger.warn(`Failed account cleanup: ${userUuidOrError.getError()}`)
return
}
const userUuid = userUuidOrError.getValue()
await this.revisionRepository.removeByUserUuid(userUuid)
this.logger.info(`Finished account cleanup for user: ${event.payload.userUuid}`)
}
}
@@ -0,0 +1,42 @@
import { Result } from '@standardnotes/domain-core'
import { RevisionsCopyRequestedEvent } from '@standardnotes/domain-events'
import { Logger } from 'winston'
import { CopyRevisions } from '../UseCase/CopyRevisions/CopyRevisions'
import { RevisionsCopyRequestedEventHandler } from './RevisionsCopyRequestedEventHandler'
describe('RevisionsCopyRequestedEventHandler', () => {
let copyRevisions: CopyRevisions
let logger: Logger
let event: RevisionsCopyRequestedEvent
const createHandler = () => new RevisionsCopyRequestedEventHandler(copyRevisions, logger)
beforeEach(() => {
copyRevisions = {} as jest.Mocked<CopyRevisions>
copyRevisions.execute = jest.fn().mockReturnValue(Result.ok())
logger = {} as jest.Mocked<Logger>
logger.error = jest.fn()
event = {} as jest.Mocked<RevisionsCopyRequestedEvent>
event.payload = {
newItemUuid: '1-2-3',
originalItemUuid: '2-3-4',
}
})
it('should copy revisions', async () => {
await createHandler().handle(event)
expect(copyRevisions.execute).toHaveBeenCalled()
})
it('should indicate if copying revisions fail', async () => {
copyRevisions.execute = jest.fn().mockReturnValue(Result.fail('Oops'))
await createHandler().handle(event)
expect(logger.error).toHaveBeenCalled()
})
})
@@ -0,0 +1,18 @@
import { DomainEventHandlerInterface, RevisionsCopyRequestedEvent } from '@standardnotes/domain-events'
import { Logger } from 'winston'
import { CopyRevisions } from '../UseCase/CopyRevisions/CopyRevisions'
export class RevisionsCopyRequestedEventHandler implements DomainEventHandlerInterface {
constructor(private copyRevisions: CopyRevisions, private logger: Logger) {}
async handle(event: RevisionsCopyRequestedEvent): Promise<void> {
const result = await this.copyRevisions.execute({
newItemUuid: event.payload.newItemUuid,
originalItemUuid: event.payload.originalItemUuid,
})
if (result.isFailed()) {
this.logger.error(`Could not copy revisions: ${result.getError()}`)
}
}
}
@@ -0,0 +1,25 @@
/* istanbul ignore file */
import { Uuid } from '@standardnotes/domain-core'
import { DomainEventHandlerInterface, RevisionsOwnershipUpdateRequestedEvent } from '@standardnotes/domain-events'
import { RevisionRepositoryInterface } from '../Revision/RevisionRepositoryInterface'
export class RevisionsOwnershipUpdateRequestedEventHandler implements DomainEventHandlerInterface {
constructor(private revisionRepository: RevisionRepositoryInterface) {}
async handle(event: RevisionsOwnershipUpdateRequestedEvent): Promise<void> {
const userUuidOrError = Uuid.create(event.payload.userUuid)
if (userUuidOrError.isFailed()) {
return
}
const userUuid = userUuidOrError.getValue()
const itemUuidOrError = Uuid.create(event.payload.itemUuid)
if (itemUuidOrError.isFailed()) {
return
}
const itemUuid = itemUuidOrError.getValue()
await this.revisionRepository.updateUserUuid(itemUuid, userUuid)
}
}
@@ -0,0 +1,16 @@
import { ContentType } from './ContentType'
describe('ContentType', () => {
it('should create a value obejct', () => {
const valueOrError = ContentType.create('Note')
expect(valueOrError.isFailed()).toBeFalsy()
expect(valueOrError.getValue().value).not.toBeNull()
})
it('should fail to create a value obejct', () => {
const valueOrError = ContentType.create('test')
expect(valueOrError.isFailed()).toBeTruthy()
})
})
@@ -0,0 +1,22 @@
import { Dates, Uuid } from '@standardnotes/domain-core'
import { ContentType } from './ContentType'
import { Revision } from './Revision'
describe('Revision', () => {
it('should create an entity', () => {
const entityOrError = Revision.create({
itemUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
userUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
content: 'test',
contentType: ContentType.create('Note').getValue(),
itemsKeyId: 'test',
encItemKey: 'test',
authHash: 'test',
creationDate: new Date(1),
dates: Dates.create(new Date(1), new Date(2)).getValue(),
})
expect(entityOrError.isFailed()).toBeFalsy()
expect(entityOrError.getValue().id).not.toBeNull()
})
})
@@ -1,8 +1,8 @@
import { Timestamps } from '@standardnotes/domain-core'
import { Dates } from '@standardnotes/domain-core'
import { ContentType } from './ContentType'
export interface RevisionMetadataProps {
contentType: ContentType
timestamps: Timestamps
dates: Dates
}
@@ -1,15 +1,15 @@
import { Timestamps, Uuid } from '@standardnotes/domain-core'
import { Dates, Uuid } from '@standardnotes/domain-core'
import { ContentType } from './ContentType'
export interface RevisionProps {
itemUuid: Uuid
userUuid: Uuid
userUuid: Uuid | null
content: string | null
contentType: ContentType
itemsKeyId: string | null
encItemKey: string | null
authHash: string | null
creationDate: Date
timestamps: Timestamps
dates: Dates
}
@@ -4,7 +4,11 @@ import { Revision } from './Revision'
import { RevisionMetadata } from './RevisionMetadata'
export interface RevisionRepositoryInterface {
removeByUserUuid(userUuid: Uuid): Promise<void>
removeOneByUuid(revisionUuid: Uuid, userUuid: Uuid): Promise<void>
findOneByUuid(revisionUuid: Uuid, userUuid: Uuid): Promise<Revision | null>
findByItemUuid(itemUuid: Uuid): Promise<Array<Revision>>
findMetadataByItemId(itemUuid: Uuid, userUuid: Uuid): Promise<Array<RevisionMetadata>>
updateUserUuid(itemUuid: Uuid, userUuid: Uuid): Promise<void>
save(revision: Revision): Promise<Revision>
}
@@ -0,0 +1,59 @@
import { Result } from '@standardnotes/domain-core'
import { Revision } from '../../Revision/Revision'
import { RevisionRepositoryInterface } from '../../Revision/RevisionRepositoryInterface'
import { CopyRevisions } from './CopyRevisions'
describe('CopyRevisions', () => {
let revisionRepository: RevisionRepositoryInterface
const createUseCase = () => new CopyRevisions(revisionRepository)
beforeEach(() => {
revisionRepository = {} as jest.Mocked<RevisionRepositoryInterface>
revisionRepository.findByItemUuid = jest.fn().mockReturnValue([{} as jest.Mocked<Revision>])
revisionRepository.save = jest.fn()
})
it('should not copy revisions to new item if revision creation fails', async () => {
const revisionMock = jest.spyOn(Revision, 'create')
revisionMock.mockImplementation(() => Result.fail('Oops'))
const result = await createUseCase().execute({
originalItemUuid: '84c0f8e8-544a-4c7e-9adf-26209303bc1d',
newItemUuid: '84c0f8e8-544a-4c7e-9adf-26209303bc1d',
})
expect(result.isFailed()).toBeTruthy()
revisionMock.mockRestore()
})
it('should copy revisions to new item', async () => {
const result = await createUseCase().execute({
originalItemUuid: '84c0f8e8-544a-4c7e-9adf-26209303bc1d',
newItemUuid: '84c0f8e8-544a-4c7e-9adf-26209303bc1d',
})
expect(result.isFailed()).toBeFalsy()
expect(revisionRepository.save).toHaveBeenCalled()
expect(result.getValue()).toEqual('Revisions copied')
})
it('should not copy revisions for an invalid item uuid', async () => {
const result = await createUseCase().execute({
originalItemUuid: '1-2-3',
newItemUuid: '84c0f8e8-544a-4c7e-9adf-26209303bc1d',
})
expect(result.isFailed()).toBeTruthy()
})
it('should not delete revision for a an invalid new item uuid', async () => {
const result = await createUseCase().execute({
newItemUuid: '1-2-3',
originalItemUuid: '84c0f8e8-544a-4c7e-9adf-26209303bc1d',
})
expect(result.isFailed()).toBeTruthy()
})
})
@@ -0,0 +1,43 @@
import { Result, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
import { Revision } from '../../Revision/Revision'
import { RevisionRepositoryInterface } from '../../Revision/RevisionRepositoryInterface'
import { CopyRevisionsDTO } from './CopyRevisionsDTO'
export class CopyRevisions implements UseCaseInterface<string> {
constructor(private revisionRepository: RevisionRepositoryInterface) {}
async execute(dto: CopyRevisionsDTO): Promise<Result<string>> {
const orignalItemUuidOrError = Uuid.create(dto.originalItemUuid)
if (orignalItemUuidOrError.isFailed()) {
return Result.fail<string>(`Could not copy revisions: ${orignalItemUuidOrError.getError()}`)
}
const originalItemUuid = orignalItemUuidOrError.getValue()
const newItemUuidOrError = Uuid.create(dto.newItemUuid)
if (newItemUuidOrError.isFailed()) {
return Result.fail<string>(`Could not copy revisions: ${newItemUuidOrError.getError()}`)
}
const newItemUuid = newItemUuidOrError.getValue()
const revisions = await this.revisionRepository.findByItemUuid(originalItemUuid)
for (const existingRevision of revisions) {
const revisionCopyOrError = Revision.create({
...existingRevision.props,
itemUuid: newItemUuid,
})
if (revisionCopyOrError.isFailed()) {
return Result.fail<string>(`Could not create revision copy: ${revisionCopyOrError.getError()}`)
}
const revisionCopy = revisionCopyOrError.getValue()
await this.revisionRepository.save(revisionCopy)
}
return Result.ok<string>('Revisions copied')
}
}
@@ -0,0 +1,4 @@
export interface CopyRevisionsDTO {
originalItemUuid: string
newItemUuid: string
}
@@ -0,0 +1,41 @@
import { RevisionRepositoryInterface } from '../../Revision/RevisionRepositoryInterface'
import { DeleteRevision } from './DeleteRevision'
describe('DeleteRevision', () => {
let revisionRepository: RevisionRepositoryInterface
const createUseCase = () => new DeleteRevision(revisionRepository)
beforeEach(() => {
revisionRepository = {} as jest.Mocked<RevisionRepositoryInterface>
revisionRepository.removeOneByUuid = jest.fn()
})
it('should delete revision', async () => {
const result = await createUseCase().execute({
revisionUuid: '84c0f8e8-544a-4c7e-9adf-26209303bc1d',
userUuid: '84c0f8e8-544a-4c7e-9adf-26209303bc1d',
})
expect(result.isFailed()).toBeFalsy()
expect(result.getValue()).toEqual('Revision removed')
})
it('should not delete revision for an invalid item uuid', async () => {
const result = await createUseCase().execute({
revisionUuid: '1-2-3',
userUuid: '84c0f8e8-544a-4c7e-9adf-26209303bc1d',
})
expect(result.isFailed()).toBeTruthy()
})
it('should not delete revision for a an invalid user uuid', async () => {
const result = await createUseCase().execute({
userUuid: '1-2-3',
revisionUuid: '84c0f8e8-544a-4c7e-9adf-26209303bc1d',
})
expect(result.isFailed()).toBeTruthy()
})
})
@@ -0,0 +1,26 @@
import { Result, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
import { RevisionRepositoryInterface } from '../../Revision/RevisionRepositoryInterface'
import { DeleteRevisionDTO } from './DeleteRevisionDTO'
export class DeleteRevision implements UseCaseInterface<string> {
constructor(private revisionRepository: RevisionRepositoryInterface) {}
async execute(dto: DeleteRevisionDTO): Promise<Result<string>> {
const revisionUuidOrError = Uuid.create(dto.revisionUuid)
if (revisionUuidOrError.isFailed()) {
return Result.fail<string>(`Could not delete revision: ${revisionUuidOrError.getError()}`)
}
const revisionUuid = revisionUuidOrError.getValue()
const userUuidOrError = Uuid.create(dto.userUuid)
if (userUuidOrError.isFailed()) {
return Result.fail<string>(`Could not delete revision: ${userUuidOrError.getError()}`)
}
const userUuid = userUuidOrError.getValue()
await this.revisionRepository.removeOneByUuid(revisionUuid, userUuid)
return Result.ok<string>('Revision removed')
}
}
@@ -0,0 +1,4 @@
export interface DeleteRevisionDTO {
userUuid: string
revisionUuid: string
}
@@ -16,7 +16,7 @@ export class GetRevision implements UseCaseInterface<Revision> {
const userUuidOrError = Uuid.create(dto.userUuid)
if (userUuidOrError.isFailed()) {
return Result.fail<Revision>(`Could not get revisions: ${userUuidOrError.getError()}`)
return Result.fail<Revision>(`Could not get revision: ${userUuidOrError.getError()}`)
}
const userUuid = userUuidOrError.getValue()
@@ -0,0 +1,4 @@
export interface DeleteRevisionRequestParams {
revisionUuid: string
userUuid: string
}
@@ -1,5 +1,5 @@
import { Request, Response } from 'express'
import { BaseHttpController, controller, httpGet, results } from 'inversify-express-utils'
import { BaseHttpController, controller, httpDelete, httpGet, results } from 'inversify-express-utils'
import { inject } from 'inversify'
import TYPES from '../../Bootstrap/Types'
@@ -20,4 +20,24 @@ export class InversifyExpressRevisionsController extends BaseHttpController {
return this.json(result.data, result.status)
}
@httpGet('/:uuid')
public async getRevision(req: Request, response: Response): Promise<results.JsonResult> {
const result = await this.revisionsController.getRevision({
revisionUuid: req.params.uuid,
userUuid: response.locals.user.uuid,
})
return this.json(result.data, result.status)
}
@httpDelete('/:uuid')
public async deleteRevision(req: Request, response: Response): Promise<results.JsonResult> {
const result = await this.revisionsController.deleteRevision({
revisionUuid: req.params.uuid,
userUuid: response.locals.user.uuid,
})
return this.json(result.data, result.status)
}
}
@@ -1,7 +1,7 @@
import { MapperInterface, Uuid } from '@standardnotes/domain-core'
import { Repository } from 'typeorm'
import { Revision } from '../../Domain/Revision/Revision'
import { Revision } from '../../Domain/Revision/Revision'
import { RevisionMetadata } from '../../Domain/Revision/RevisionMetadata'
import { RevisionRepositoryInterface } from '../../Domain/Revision/RevisionRepositoryInterface'
import { TypeORMRevision } from '../TypeORM/TypeORMRevision'
@@ -13,11 +13,57 @@ export class MySQLRevisionRepository implements RevisionRepositoryInterface {
private revisionMapper: MapperInterface<Revision, TypeORMRevision>,
) {}
async updateUserUuid(itemUuid: Uuid, userUuid: Uuid): Promise<void> {
await this.ormRepository
.createQueryBuilder()
.update()
.set({
userUuid: userUuid.value,
})
.where('item_uuid = :itemUuid', { itemUuid: itemUuid.value })
.execute()
}
async findByItemUuid(itemUuid: Uuid): Promise<Revision[]> {
const typeormRevisions = await this.ormRepository
.createQueryBuilder()
.where('item_uuid = :itemUuid', { itemUuid: itemUuid.value })
.getMany()
const revisions = []
for (const revision of typeormRevisions) {
revisions.push(this.revisionMapper.toDomain(revision))
}
return revisions
}
async removeByUserUuid(userUuid: Uuid): Promise<void> {
await this.ormRepository
.createQueryBuilder()
.delete()
.from('revisions')
.where('user_uuid = :userUuid', { userUuid: userUuid.value })
.execute()
}
async removeOneByUuid(revisionUuid: Uuid, userUuid: Uuid): Promise<void> {
await this.ormRepository
.createQueryBuilder()
.delete()
.from('revisions')
.where('uuid = :revisionUuid AND user_uuid = :userUuid', {
userUuid: userUuid.value,
revisionUuid: revisionUuid.value,
})
.execute()
}
async findOneByUuid(revisionUuid: Uuid, userUuid: Uuid): Promise<Revision | null> {
const typeormRevision = await this.ormRepository
.createQueryBuilder()
.where('uuid = :revisionUuid', { revisionUuid })
.andWhere('user_uuid = :userUuid', { userUuid })
.where('uuid = :revisionUuid', { revisionUuid: revisionUuid.value })
.andWhere('user_uuid = :userUuid', { userUuid: userUuid.value })
.getOne()
if (typeormRevision === null) {
@@ -42,8 +88,8 @@ export class MySQLRevisionRepository implements RevisionRepositoryInterface {
.addSelect('content_type', 'contentType')
.addSelect('created_at', 'createdAt')
.addSelect('updated_at', 'updatedAt')
.where('item_uuid = :itemUuid', { itemUuid })
.andWhere('user_uuid = :userUuid', { userUuid })
.where('item_uuid = :itemUuid', { itemUuid: itemUuid.value })
.andWhere('user_uuid = :userUuid', { userUuid: userUuid.value })
.orderBy('created_at', 'DESC')
const simplifiedRevisions = await queryBuilder.getMany()
@@ -15,9 +15,11 @@ export class TypeORMRevision {
@Column({
name: 'user_uuid',
length: 36,
type: 'varchar',
nullable: true,
})
@Index('user_uuid')
declare userUuid: string
declare userUuid: string | null
@Column({
type: 'mediumtext',
@@ -61,7 +63,6 @@ export class TypeORMRevision {
type: 'date',
nullable: true,
})
@Index('creation_date')
declare creationDate: Date
@Column({
@@ -70,7 +71,6 @@ export class TypeORMRevision {
precision: 6,
nullable: true,
})
@Index('created_at')
declare createdAt: Date
@Column({
@@ -1,4 +1,4 @@
import { MapperInterface, Timestamps, Uuid } from '@standardnotes/domain-core'
import { MapperInterface, Dates, Uuid } from '@standardnotes/domain-core'
import { ContentType } from '../Domain/Revision/ContentType'
import { Revision } from '../Domain/Revision/Revision'
@@ -34,7 +34,7 @@ export class RevisionItemStringMapper implements MapperInterface<Revision, strin
itemsKeyId: item.items_key_id,
encItemKey: item.enc_item_key,
creationDate: new Date(),
timestamps: Timestamps.create(new Date(), new Date()).getValue(),
dates: Dates.create(new Date(), new Date()).getValue(),
})
if (revisionOrError.isFailed()) {
@@ -1,4 +1,4 @@
import { MapperInterface, Timestamps, UniqueEntityId } from '@standardnotes/domain-core'
import { MapperInterface, Dates, UniqueEntityId } from '@standardnotes/domain-core'
import { ContentType } from '../Domain/Revision/ContentType'
import { RevisionMetadata } from '../Domain/Revision/RevisionMetadata'
@@ -12,16 +12,16 @@ export class RevisionMetadataPersistenceMapper implements MapperInterface<Revisi
}
const contentType = contentTypeOrError.getValue()
const timestampsOrError = Timestamps.create(projection.createdAt, projection.updatedAt)
if (timestampsOrError.isFailed()) {
throw new Error(`Could not create timestamps: ${timestampsOrError.getError()}`)
const datesOrError = Dates.create(projection.createdAt, projection.updatedAt)
if (datesOrError.isFailed()) {
throw new Error(`Could not create dates: ${datesOrError.getError()}`)
}
const timestamps = timestampsOrError.getValue()
const dates = datesOrError.getValue()
const revisionMetadataOrError = RevisionMetadata.create(
{
contentType,
timestamps,
dates,
},
new UniqueEntityId(projection.uuid),
)
@@ -1,4 +1,4 @@
import { MapperInterface, Timestamps, UniqueEntityId, Uuid } from '@standardnotes/domain-core'
import { MapperInterface, Dates, UniqueEntityId, Uuid } from '@standardnotes/domain-core'
import { ContentType } from '../Domain/Revision/ContentType'
import { Revision } from '../Domain/Revision/Revision'
import { TypeORMRevision } from '../Infra/TypeORM/TypeORMRevision'
@@ -11,11 +11,11 @@ export class RevisionPersistenceMapper implements MapperInterface<Revision, Type
}
const contentType = contentTypeOrError.getValue()
const timestampsOrError = Timestamps.create(projection.createdAt, projection.updatedAt)
if (timestampsOrError.isFailed()) {
throw new Error(`Could not map typeorm revision to domain revision: ${timestampsOrError.getError()}`)
const datesOrError = Dates.create(projection.createdAt, projection.updatedAt)
if (datesOrError.isFailed()) {
throw new Error(`Could not map typeorm revision to domain revision: ${datesOrError.getError()}`)
}
const timestamps = timestampsOrError.getValue()
const dates = datesOrError.getValue()
const itemUuidOrError = Uuid.create(projection.itemUuid)
if (itemUuidOrError.isFailed()) {
@@ -23,11 +23,14 @@ export class RevisionPersistenceMapper implements MapperInterface<Revision, Type
}
const itemUuid = itemUuidOrError.getValue()
const userUuidOrError = Uuid.create(projection.userUuid)
if (userUuidOrError.isFailed()) {
throw new Error(`Could not map typeorm revision to domain revision: ${userUuidOrError.getError()}`)
let userUuid = null
if (projection.userUuid !== null) {
const userUuidOrError = Uuid.create(projection.userUuid)
if (userUuidOrError.isFailed()) {
throw new Error(`Could not map typeorm revision to domain revision: ${userUuidOrError.getError()}`)
}
userUuid = userUuidOrError.getValue()
}
const userUuid = userUuidOrError.getValue()
const revisionOrError = Revision.create(
{
@@ -39,7 +42,7 @@ export class RevisionPersistenceMapper implements MapperInterface<Revision, Type
itemsKeyId: projection.itemsKeyId,
itemUuid,
userUuid,
timestamps,
dates,
},
new UniqueEntityId(projection.uuid),
)
@@ -56,13 +59,13 @@ export class RevisionPersistenceMapper implements MapperInterface<Revision, Type
typeormRevision.authHash = domain.props.authHash
typeormRevision.content = domain.props.content
typeormRevision.contentType = domain.props.contentType.value
typeormRevision.createdAt = domain.props.timestamps.createdAt
typeormRevision.updatedAt = domain.props.timestamps.updatedAt
typeormRevision.createdAt = domain.props.dates.createdAt
typeormRevision.updatedAt = domain.props.dates.updatedAt
typeormRevision.creationDate = domain.props.creationDate
typeormRevision.encItemKey = domain.props.encItemKey
typeormRevision.itemUuid = domain.props.itemUuid.value
typeormRevision.itemsKeyId = domain.props.itemsKeyId
typeormRevision.userUuid = domain.props.userUuid.value
typeormRevision.userUuid = domain.props.userUuid ? domain.props.userUuid.value : null
typeormRevision.uuid = domain.id.toString()
return typeormRevision
+8
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.13.33](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.13.32...@standardnotes/scheduler-server@1.13.33) (2022-11-30)
**Note:** Version bump only for package @standardnotes/scheduler-server
## [1.13.32](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.13.31...@standardnotes/scheduler-server@1.13.32) (2022-11-28)
**Note:** Version bump only for package @standardnotes/scheduler-server
## [1.13.31](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.13.30...@standardnotes/scheduler-server@1.13.31) (2022-11-25)
**Note:** Version bump only for package @standardnotes/scheduler-server
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/scheduler-server",
"version": "1.13.31",
"version": "1.13.33",
"engines": {
"node": ">=18.0.0 <19.0.0"
},
+28
View File
@@ -3,6 +3,34 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.20.3](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.20.2...@standardnotes/syncing-server@1.20.3) (2022-12-05)
**Note:** Version bump only for package @standardnotes/syncing-server
## [1.20.2](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.20.1...@standardnotes/syncing-server@1.20.2) (2022-12-02)
**Note:** Version bump only for package @standardnotes/syncing-server
## [1.20.1](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.20.0...@standardnotes/syncing-server@1.20.1) (2022-12-02)
**Note:** Version bump only for package @standardnotes/syncing-server
# [1.20.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.19.1...@standardnotes/syncing-server@1.20.0) (2022-11-30)
### Features
* **syncing-server:** add revisions ownership fix procedure ([6cd68dd](https://github.com/standardnotes/syncing-server-js/commit/6cd68ddd6af0b1adde6c0d1cb3acef6e1aa9811b))
## [1.19.1](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.19.0...@standardnotes/syncing-server@1.19.1) (2022-11-30)
**Note:** Version bump only for package @standardnotes/syncing-server
# [1.19.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.18.12...@standardnotes/syncing-server@1.19.0) (2022-11-28)
### Features
* **revisions:** add copying revisions on duplicated items ([7bb698e](https://github.com/standardnotes/syncing-server-js/commit/7bb698e44222ef128d9642d625e96b7d26ee4dbf))
## [1.18.12](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.18.11...@standardnotes/syncing-server@1.18.12) (2022-11-25)
**Note:** Version bump only for package @standardnotes/syncing-server
+78
View File
@@ -0,0 +1,78 @@
import 'reflect-metadata'
import 'newrelic'
import { Logger } from 'winston'
import { ContainerConfigLoader } from '../src/Bootstrap/Container'
import TYPES from '../src/Bootstrap/Types'
import { Env } from '../src/Bootstrap/Env'
import { DomainEventFactoryInterface } from '../src/Domain/Event/DomainEventFactoryInterface'
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
import { ItemRepositoryInterface } from '../src/Domain/Item/ItemRepositoryInterface'
import { Stream } from 'stream'
const fixRevisionsOwnership = async (
itemRepository: ItemRepositoryInterface,
domainEventFactory: DomainEventFactoryInterface,
domainEventPublisher: DomainEventPublisherInterface,
logger: Logger,
): Promise<void> => {
const stream = await itemRepository.streamAll({
sortBy: 'updated_at_timestamp',
sortOrder: 'ASC',
createdBefore: new Date('2022-11-23'),
selectFields: ['user_uuid', 'item_uuid'],
})
return new Promise((resolve, reject) => {
stream
.pipe(
new Stream.Transform({
objectMode: true,
transform: async (rawItemData, _encoding, callback) => {
try {
await domainEventPublisher.publish(
domainEventFactory.createRevisionsOwnershipUpdateRequestedEvent({
userUuid: rawItemData.item_user_uuid,
itemUuid: rawItemData.item_uuid,
}),
)
} catch (error) {
logger.error(`Could not process item ${rawItemData.item_uuid}: ${(error as Error).message}`)
}
callback()
},
}),
)
.on('finish', resolve)
.on('error', reject)
})
}
const container = new ContainerConfigLoader()
void container.load().then((container) => {
const env: Env = new Env()
env.load()
const logger: Logger = container.get(TYPES.Logger)
logger.info('Starting revisions ownership fixing')
const itemRepository: ItemRepositoryInterface = container.get(TYPES.ItemRepository)
const domainEventFactory: DomainEventFactoryInterface = container.get(TYPES.DomainEventFactory)
const domainEventPublisher: DomainEventPublisherInterface = container.get(TYPES.DomainEventPublisher)
Promise.resolve(fixRevisionsOwnership(itemRepository, domainEventFactory, domainEventPublisher, logger))
.then(() => {
logger.info('revisions ownership fix complete.')
process.exit(0)
})
.catch((error) => {
logger.error(`Could not finish revisions ownership fix: ${error.message}`)
process.exit(1)
})
})
@@ -25,6 +25,11 @@ case "$COMMAND" in
yarn workspace @standardnotes/syncing-server content-size $USER_UUID
;;
'revisions-ownership-fix' )
echo "Starting Revisions Ownership Fixing..."
yarn workspace @standardnotes/syncing-server revisions-ownership
;;
* )
echo "Unknown command"
;;
+2 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/syncing-server",
"version": "1.18.12",
"version": "1.20.3",
"engines": {
"node": ">=18.0.0 <19.0.0"
},
@@ -22,6 +22,7 @@
"start": "yarn node dist/bin/server.js",
"worker": "yarn node dist/bin/worker.js",
"content-size": "yarn node dist/bin/content.js",
"revisions-ownership": "yarn node dist/bin/revisions.js",
"upgrade:snjs": "yarn ncu -u '@standardnotes/*'"
},
"dependencies": {
@@ -10,6 +10,8 @@ import {
ItemRevisionCreationRequestedEvent,
ItemsSyncedEvent,
OneDriveBackupFailedEvent,
RevisionsCopyRequestedEvent,
RevisionsOwnershipUpdateRequestedEvent,
UserContentSizeRecalculationRequestedEvent,
} from '@standardnotes/domain-events'
import { TimerInterface } from '@standardnotes/time'
@@ -21,6 +23,45 @@ import { DomainEventFactoryInterface } from './DomainEventFactoryInterface'
export class DomainEventFactory implements DomainEventFactoryInterface {
constructor(@inject(TYPES.Timer) private timer: TimerInterface) {}
createRevisionsOwnershipUpdateRequestedEvent(dto: {
userUuid: string
itemUuid: string
}): RevisionsOwnershipUpdateRequestedEvent {
return {
type: 'REVISIONS_OWNERSHIP_UPDATE_REQUESTED',
createdAt: this.timer.getUTCDate(),
meta: {
correlation: {
userIdentifier: dto.userUuid,
userIdentifierType: 'uuid',
},
origin: DomainEventService.SyncingServer,
},
payload: dto,
}
}
createRevisionsCopyRequestedEvent(
userUuid: string,
dto: {
originalItemUuid: string
newItemUuid: string
},
): RevisionsCopyRequestedEvent {
return {
type: 'REVISIONS_COPY_REQUESTED',
createdAt: this.timer.getUTCDate(),
meta: {
correlation: {
userIdentifier: userUuid,
userIdentifierType: 'uuid',
},
origin: DomainEventService.SyncingServer,
},
payload: dto,
}
}
createItemDumpedEvent(fileDumpPath: string, userUuid: string): ItemDumpedEvent {
return {
type: 'ITEM_DUMPED',
@@ -8,6 +8,8 @@ import {
ItemRevisionCreationRequestedEvent,
ItemsSyncedEvent,
OneDriveBackupFailedEvent,
RevisionsCopyRequestedEvent,
RevisionsOwnershipUpdateRequestedEvent,
UserContentSizeRecalculationRequestedEvent,
} from '@standardnotes/domain-events'
@@ -35,4 +37,12 @@ export interface DomainEventFactoryInterface {
createDuplicateItemSyncedEvent(itemUuid: string, userUuid: string): DuplicateItemSyncedEvent
createItemRevisionCreationRequested(itemUuid: string, userUuid: string): ItemRevisionCreationRequestedEvent
createItemDumpedEvent(fileDumpPath: string, userUuid: string): ItemDumpedEvent
createRevisionsCopyRequestedEvent(
userUuid: string,
dto: { originalItemUuid: string; newItemUuid: string },
): RevisionsCopyRequestedEvent
createRevisionsOwnershipUpdateRequestedEvent(dto: {
userUuid: string
itemUuid: string
}): RevisionsOwnershipUpdateRequestedEvent
}
@@ -1,11 +1,16 @@
import 'reflect-metadata'
import { DuplicateItemSyncedEvent } from '@standardnotes/domain-events'
import {
DomainEventPublisherInterface,
DuplicateItemSyncedEvent,
RevisionsCopyRequestedEvent,
} from '@standardnotes/domain-events'
import { Logger } from 'winston'
import { Item } from '../Item/Item'
import { ItemRepositoryInterface } from '../Item/ItemRepositoryInterface'
import { DuplicateItemSyncedEventHandler } from './DuplicateItemSyncedEventHandler'
import { RevisionServiceInterface } from '../Revision/RevisionServiceInterface'
import { DomainEventFactoryInterface } from '../Event/DomainEventFactoryInterface'
describe('DuplicateItemSyncedEventHandler', () => {
let itemRepository: ItemRepositoryInterface
@@ -14,8 +19,17 @@ describe('DuplicateItemSyncedEventHandler', () => {
let duplicateItem: Item
let originalItem: Item
let event: DuplicateItemSyncedEvent
let domainEventFactory: DomainEventFactoryInterface
let domainEventPublisher: DomainEventPublisherInterface
const createHandler = () => new DuplicateItemSyncedEventHandler(itemRepository, revisionService, logger)
const createHandler = () =>
new DuplicateItemSyncedEventHandler(
itemRepository,
revisionService,
domainEventFactory,
domainEventPublisher,
logger,
)
beforeEach(() => {
originalItem = {
@@ -45,6 +59,14 @@ describe('DuplicateItemSyncedEventHandler', () => {
userUuid: '1-2-3',
itemUuid: '2-3-4',
}
domainEventFactory = {} as jest.Mocked<DomainEventFactoryInterface>
domainEventFactory.createRevisionsCopyRequestedEvent = jest
.fn()
.mockReturnValue({} as jest.Mocked<RevisionsCopyRequestedEvent>)
domainEventPublisher = {} as jest.Mocked<DomainEventPublisherInterface>
domainEventPublisher.publish = jest.fn()
})
it('should copy revisions from original item to the duplicate item', async () => {
@@ -1,7 +1,12 @@
import { DomainEventHandlerInterface, DuplicateItemSyncedEvent } from '@standardnotes/domain-events'
import {
DomainEventHandlerInterface,
DomainEventPublisherInterface,
DuplicateItemSyncedEvent,
} from '@standardnotes/domain-events'
import { inject, injectable } from 'inversify'
import { Logger } from 'winston'
import TYPES from '../../Bootstrap/Types'
import { DomainEventFactoryInterface } from '../Event/DomainEventFactoryInterface'
import { ItemRepositoryInterface } from '../Item/ItemRepositoryInterface'
import { RevisionServiceInterface } from '../Revision/RevisionServiceInterface'
@@ -10,6 +15,8 @@ export class DuplicateItemSyncedEventHandler implements DomainEventHandlerInterf
constructor(
@inject(TYPES.ItemRepository) private itemRepository: ItemRepositoryInterface,
@inject(TYPES.RevisionService) private revisionService: RevisionServiceInterface,
@inject(TYPES.DomainEventFactory) private domainEventFactory: DomainEventFactoryInterface,
@inject(TYPES.DomainEventPublisher) private domainEventPublisher: DomainEventPublisherInterface,
@inject(TYPES.Logger) private logger: Logger,
) {}
@@ -35,6 +42,13 @@ export class DuplicateItemSyncedEventHandler implements DomainEventHandlerInterf
if (existingOriginalItem !== null) {
await this.revisionService.copyRevisions(existingOriginalItem.uuid, item.uuid)
await this.domainEventPublisher.publish(
this.domainEventFactory.createRevisionsCopyRequestedEvent(event.payload.userUuid, {
originalItemUuid: existingOriginalItem.uuid,
newItemUuid: item.uuid,
}),
)
}
}
}
@@ -9,4 +9,6 @@ export type ItemQuery = {
deleted?: boolean
offset?: number
limit?: number
createdBefore?: Date
selectFields?: string[]
}

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