Compare commits

...

48 Commits

Author SHA1 Message Date
standardci
c351f01f67 chore(release): publish new version
- @standardnotes/analytics@2.12.4
 - @standardnotes/auth-server@1.63.0
 - @standardnotes/domain-core@1.9.0
 - @standardnotes/revisions-server@1.9.7
 - @standardnotes/syncing-server@1.20.7
2022-12-07 06:06:35 +00:00
Karol Sójko
c87561fca7 feat(domain-core): rename email subscription rejection level to email level 2022-12-07 07:04:42 +01:00
standardci
a363c143fa chore(release): publish new version
- @standardnotes/auth-server@1.62.1
2022-12-06 13:15:21 +00:00
Karol Sójko
fb81d2b926 fix(auth): remove redundant specs and fix stream query 2022-12-06 14:12:54 +01:00
standardci
05b1b8f079 chore(release): publish new version
- @standardnotes/auth-server@1.62.0
2022-12-06 10:49:12 +00:00
Karol Sójko
7848dc06d4 feat(auth): add procedure for email subscriptions sync 2022-12-06 11:47:17 +01:00
standardci
3a005719b7 chore(release): publish new version
- @standardnotes/auth-server@1.61.0
2022-12-06 10:02:20 +00:00
Karol Sójko
6928988f78 feat(auth): add publishing mute emails setting changed event 2022-12-06 11:00:14 +01:00
standardci
a521894d7c chore(release): publish new version
- @standardnotes/analytics@2.12.3
 - @standardnotes/api-gateway@1.39.8
 - @standardnotes/auth-server@1.60.17
 - @standardnotes/domain-events-infra@1.9.38
 - @standardnotes/domain-events@2.94.0
 - @standardnotes/event-store@1.6.34
 - @standardnotes/files-server@1.8.34
 - @standardnotes/revisions-server@1.9.6
 - @standardnotes/scheduler-server@1.13.35
 - @standardnotes/syncing-server@1.20.6
 - @standardnotes/websockets-server@1.4.35
 - @standardnotes/workspace-server@1.17.34
2022-12-06 09:30:07 +00:00
Karol Sójko
b7fb1d9c08 feat(domain-events): add mute emails setting changed event 2022-12-06 10:28:04 +01:00
standardci
5f67e45911 chore(release): publish new version
- @standardnotes/analytics@2.12.2
 - @standardnotes/api-gateway@1.39.7
 - @standardnotes/auth-server@1.60.16
 - @standardnotes/domain-events-infra@1.9.37
 - @standardnotes/domain-events@2.93.0
 - @standardnotes/event-store@1.6.33
 - @standardnotes/files-server@1.8.33
 - @standardnotes/revisions-server@1.9.5
 - @standardnotes/scheduler-server@1.13.34
 - @standardnotes/syncing-server@1.20.5
 - @standardnotes/websockets-server@1.4.34
 - @standardnotes/workspace-server@1.17.33
2022-12-05 14:34:06 +00:00
Karol Sójko
fddf9fccbd feat(domain-events): add email subscription sync requested event 2022-12-05 15:32:10 +01:00
standardci
2bedbd7bd2 chore(release): publish new version
- @standardnotes/analytics@2.12.1
 - @standardnotes/domain-core@1.8.0
 - @standardnotes/revisions-server@1.9.4
 - @standardnotes/syncing-server@1.20.4
2022-12-05 10:40:29 +00:00
Karol Sójko
02f3c85796 feat(domain-core): add email subscription rejection levels 2022-12-05 11:38:23 +01:00
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
standardci
b912e050ea chore(release): publish new version
- @standardnotes/revisions-server@1.5.0
2022-11-28 11:06:13 +00:00
Karol Sójko
284561d093 feat(revisions): add fetching single revision 2022-11-28 12:04:00 +01:00
standardci
efc355982c chore(release): publish new version
- @standardnotes/api-gateway@1.39.4
2022-11-25 10:30:06 +00:00
Karol Sójko
8907879a19 fix(api-gateway): make revisions and workspace server urls optional 2022-11-25 11:28:02 +01:00
Karol Sójko
86f6057207 Revert "chore: tmp disable e2e to publish auth worker for email campaign"
This reverts commit ed8f82617d.
2022-11-25 07:56:07 +01:00
standardci
4c92698c73 chore(release): publish new version
- @standardnotes/auth-server@1.60.13
2022-11-25 06:45:52 +00:00
Karol Sójko
8407c3b649 fix(auth): bring back streaming all users in an email campaign send out 2022-11-25 07:43:55 +01:00
Karol Sójko
ed8f82617d chore: tmp disable e2e to publish auth worker for email campaign 2022-11-25 07:18:26 +01:00
standardci
31d040d1b6 chore(release): publish new version
- @standardnotes/auth-server@1.60.12
2022-11-25 06:16:08 +00:00
Karol Sójko
25a6796e63 fix(auth): tmp test email campaign black friday 2022 reminder on team only 2022-11-25 07:14:02 +01:00
149 changed files with 1932 additions and 2198 deletions

1
.pnp.cjs generated
View File

@@ -2649,6 +2649,7 @@ const RAW_RUNTIME_STATE =
["@sentry/node", "npm:7.19.0"],\
["@standardnotes/api", "npm:1.19.0"],\
["@standardnotes/common", "workspace:packages/common"],\
["@standardnotes/domain-core", "workspace:packages/domain-core"],\
["@standardnotes/domain-events", "workspace:packages/domain-events"],\
["@standardnotes/domain-events-infra", "workspace:packages/domain-events-infra"],\
["@standardnotes/features", "npm:1.53.1"],\

View File

@@ -3,6 +3,44 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [2.12.4](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.3...@standardnotes/analytics@2.12.4) (2022-12-07)
**Note:** Version bump only for package @standardnotes/analytics
## [2.12.3](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.2...@standardnotes/analytics@2.12.3) (2022-12-06)
**Note:** Version bump only for package @standardnotes/analytics
## [2.12.2](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.1...@standardnotes/analytics@2.12.2) (2022-12-05)
**Note:** Version bump only for package @standardnotes/analytics
## [2.12.1](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.12.0...@standardnotes/analytics@2.12.1) (2022-12-05)
**Note:** Version bump only for package @standardnotes/analytics
# [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

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/analytics",
"version": "2.11.13",
"version": "2.12.4",
"engines": {
"node": ">=18.0.0 <19.0.0"
},

View File

@@ -18,5 +18,5 @@ export class AnalyticsEntity {
nullable: true,
})
@Index('email')
declare userEmail: string
declare username: string
}

View File

@@ -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,
})

View File

@@ -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,
})

View File

@@ -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,
})

View File

@@ -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,
})

View File

@@ -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,
})

View File

@@ -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, [

View File

@@ -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

View File

@@ -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()
})

View File

@@ -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>

View File

@@ -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(),
}
}
}

View File

@@ -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
}

View File

@@ -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(),
})

View File

@@ -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),
)

View File

@@ -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
}

View File

@@ -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)

View File

@@ -1,5 +1,5 @@
import { Email } from '@standardnotes/domain-core'
import { Username } from '@standardnotes/domain-core'
export interface UserProps {
email: Email
username: Username
}

View File

@@ -18,7 +18,7 @@ export class TypeORMRevenueModification {
length: 255,
})
@Index('email')
declare userEmail: string
declare username: string
@Column({
name: 'user_uuid',

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.
## [1.39.8](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.39.7...@standardnotes/api-gateway@1.39.8) (2022-12-06)
**Note:** Version bump only for package @standardnotes/api-gateway
## [1.39.7](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.39.6...@standardnotes/api-gateway@1.39.7) (2022-12-05)
**Note:** Version bump only for package @standardnotes/api-gateway
## [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
* **api-gateway:** make revisions and workspace server urls optional ([8907879](https://github.com/standardnotes/api-gateway/commit/8907879a194d2d8328fbd3ca8ec9d0b608c2da50))
## [1.39.3](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.39.2...@standardnotes/api-gateway@1.39.3) (2022-11-25)
**Note:** Version bump only for package @standardnotes/api-gateway

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/api-gateway",
"version": "1.39.3",
"version": "1.39.8",
"engines": {
"node": ">=18.0.0 <19.0.0"
},

View File

@@ -54,11 +54,11 @@ export class ContainerConfigLoader {
// env vars
container.bind(TYPES.SYNCING_SERVER_JS_URL).toConstantValue(env.get('SYNCING_SERVER_JS_URL'))
container.bind(TYPES.AUTH_SERVER_URL).toConstantValue(env.get('AUTH_SERVER_URL'))
container.bind(TYPES.REVISIONS_SERVER_URL).toConstantValue(env.get('REVISIONS_SERVER_URL'))
container.bind(TYPES.REVISIONS_SERVER_URL).toConstantValue(env.get('REVISIONS_SERVER_URL', true))
container.bind(TYPES.PAYMENTS_SERVER_URL).toConstantValue(env.get('PAYMENTS_SERVER_URL', true))
container.bind(TYPES.FILES_SERVER_URL).toConstantValue(env.get('FILES_SERVER_URL', true))
container.bind(TYPES.AUTH_JWT_SECRET).toConstantValue(env.get('AUTH_JWT_SECRET'))
container.bind(TYPES.WORKSPACE_SERVER_URL).toConstantValue(env.get('WORKSPACE_SERVER_URL'))
container.bind(TYPES.WORKSPACE_SERVER_URL).toConstantValue(env.get('WORKSPACE_SERVER_URL', true))
container.bind(TYPES.WEB_SOCKET_SERVER_URL).toConstantValue(env.get('WEB_SOCKET_SERVER_URL', true))
container
.bind(TYPES.HTTP_CALL_TIMEOUT)

View File

@@ -39,6 +39,11 @@ export class HttpService implements HttpServiceInterface {
endpoint: string,
payload?: Record<string, unknown> | string,
): Promise<void> {
if (!this.revisionsServerUrl) {
response.status(400).send({ message: 'Revisions Server not configured' })
return
}
await this.callServer(this.revisionsServerUrl, request, response, endpoint, payload)
}
@@ -66,6 +71,12 @@ export class HttpService implements HttpServiceInterface {
endpoint: string,
payload?: Record<string, unknown> | string,
): Promise<void> {
if (!this.workspaceServerUrl) {
response.status(400).send({ message: 'Workspace Server not configured' })
return
}
await this.callServer(this.workspaceServerUrl, request, response, endpoint, payload)
}

View File

@@ -3,6 +3,58 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.63.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.62.1...@standardnotes/auth-server@1.63.0) (2022-12-07)
### Features
* **domain-core:** rename email subscription rejection level to email level ([c87561f](https://github.com/standardnotes/server/commit/c87561fca782883b84f58b4f0b9f85ecc279ca50))
## [1.62.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.62.0...@standardnotes/auth-server@1.62.1) (2022-12-06)
### Bug Fixes
* **auth:** remove redundant specs and fix stream query ([fb81d2b](https://github.com/standardnotes/server/commit/fb81d2b9260cf7bee3e3e6911d5a6e8eb1d650e3))
# [1.62.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.61.0...@standardnotes/auth-server@1.62.0) (2022-12-06)
### Features
* **auth:** add procedure for email subscriptions sync ([7848dc0](https://github.com/standardnotes/server/commit/7848dc06d4f4fe8c380ed45c32e23ac0e62014fa))
# [1.61.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.60.17...@standardnotes/auth-server@1.61.0) (2022-12-06)
### Features
* **auth:** add publishing mute emails setting changed event ([6928988](https://github.com/standardnotes/server/commit/6928988f7855c939f2365e35cb6cb0ff18e5c37a))
## [1.60.17](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.60.16...@standardnotes/auth-server@1.60.17) (2022-12-06)
**Note:** Version bump only for package @standardnotes/auth-server
## [1.60.16](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.60.15...@standardnotes/auth-server@1.60.16) (2022-12-05)
**Note:** Version bump only for package @standardnotes/auth-server
## [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
* **auth:** bring back streaming all users in an email campaign send out ([8407c3b](https://github.com/standardnotes/server/commit/8407c3b64910c87591a97b856f5b0c0aebc98e51))
## [1.60.12](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.60.11...@standardnotes/auth-server@1.60.12) (2022-11-25)
### Bug Fixes
* **auth:** tmp test email campaign black friday 2022 reminder on team only ([25a6796](https://github.com/standardnotes/server/commit/25a6796e636bc30de99001bd16a2a1084b608b6a))
## [1.60.11](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.60.10...@standardnotes/auth-server@1.60.11) (2022-11-25)
**Note:** Version bump only for package @standardnotes/auth-server

View File

@@ -5,64 +5,64 @@ COMMAND=$1 && shift 1
case "$COMMAND" in
'start-local' )
echo "Starting Web..."
echo "[Docker] Starting Web..."
yarn workspace @standardnotes/auth-server start:local
;;
'start-web' )
echo "Starting Web..."
echo "[Docker] Starting Web..."
yarn workspace @standardnotes/auth-server start
;;
'start-worker' )
echo "Starting Worker..."
echo "[Docker] Starting Worker..."
yarn workspace @standardnotes/auth-server worker
;;
'email-daily-backup' )
echo "Starting Email Daily Backup..."
echo "[Docker] Starting Email Daily Backup..."
yarn workspace @standardnotes/auth-server daily-backup:email
;;
'email-weekly-backup' )
echo "Starting Email Weekly Backup..."
echo "[Docker] Starting Email Weekly Backup..."
yarn workspace @standardnotes/auth-server weekly-backup:email
;;
'email-backup' )
echo "Starting Email Backup For Single User..."
echo "[Docker] Starting Email Backup For Single User..."
EMAIL=$1 && shift 1
yarn workspace @standardnotes/auth-server user-email-backup $EMAIL
;;
'dropbox-daily-backup' )
echo "Starting Dropbox Daily Backup..."
echo "[Docker] Starting Dropbox Daily Backup..."
yarn workspace @standardnotes/auth-server daily-backup:dropbox
;;
'google-drive-daily-backup' )
echo "Starting Google Drive Daily Backup..."
echo "[Docker] Starting Google Drive Daily Backup..."
yarn workspace @standardnotes/auth-server daily-backup:google_drive
;;
'one-drive-daily-backup' )
echo "Starting One Drive Daily Backup..."
echo "[Docker] Starting One Drive Daily Backup..."
yarn workspace @standardnotes/auth-server daily-backup:one_drive
;;
'email-campaign' )
echo "Starting Email Campaign Sending..."
echo "[Docker] Starting Email Campaign Sending..."
MESSAGE_IDENTIFIER=$1 && shift 1
yarn workspace @standardnotes/auth-server email-campaign $MESSAGE_IDENTIFIER
;;
'content-recalculation' )
echo "Starting Content Size Recalculation..."
echo "[Docker] Starting Content Size Recalculation..."
yarn workspace @standardnotes/auth-server content-recalculation
;;
* )
echo "Unknown command"
echo "[Docker] Unknown command"
;;
esac

View File

@@ -7,6 +7,6 @@ module.exports = {
transform: {
...tsjPreset.transform,
},
coveragePathIgnorePatterns: ['/Bootstrap/', '/InversifyExpressUtils/', 'HealthCheckController'],
coveragePathIgnorePatterns: ['/Bootstrap/', '/InversifyExpressUtils/', '/Infra/', '/Projection/'],
setupFilesAfterEnv: ['./test-setup.ts'],
}

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/auth-server",
"version": "1.60.11",
"version": "1.63.0",
"engines": {
"node": ">=18.0.0 <19.0.0"
},
@@ -35,6 +35,7 @@
"@sentry/node": "^7.19.0",
"@standardnotes/api": "^1.19.0",
"@standardnotes/common": "workspace:*",
"@standardnotes/domain-core": "workspace:^",
"@standardnotes/domain-events": "workspace:*",
"@standardnotes/domain-events-infra": "workspace:*",
"@standardnotes/features": "^1.52.1",

View File

@@ -20,6 +20,8 @@ import {
WebSocketMessageRequestedEvent,
ExitDiscountApplyRequestedEvent,
UserContentSizeRecalculationRequestedEvent,
MuteEmailsSettingChangedEvent,
EmailSubscriptionSyncRequestedEvent,
} from '@standardnotes/domain-events'
import { Predicate, PredicateVerificationResult } from '@standardnotes/predicates'
import { TimerInterface } from '@standardnotes/time'
@@ -32,6 +34,48 @@ import { DomainEventFactoryInterface } from './DomainEventFactoryInterface'
export class DomainEventFactory implements DomainEventFactoryInterface {
constructor(@inject(TYPES.Timer) private timer: TimerInterface) {}
createEmailSubscriptionSyncRequestedEvent(dto: {
username: string
userUuid: string
subscriptionPlanName: string | null
muteFailedBackupsEmails: boolean
muteFailedCloudBackupsEmails: boolean
muteMarketingEmails: boolean
muteSignInEmails: boolean
}): EmailSubscriptionSyncRequestedEvent {
return {
type: 'EMAIL_SUBSCRIPTION_SYNC_REQUESTED',
createdAt: this.timer.getUTCDate(),
meta: {
correlation: {
userIdentifier: dto.userUuid,
userIdentifierType: 'uuid',
},
origin: DomainEventService.Auth,
},
payload: dto,
}
}
createMuteEmailsSettingChangedEvent(dto: {
username: string
mute: boolean
emailSubscriptionRejectionLevel: string
}): MuteEmailsSettingChangedEvent {
return {
type: 'MUTE_EMAILS_SETTING_CHANGED',
createdAt: this.timer.getUTCDate(),
meta: {
correlation: {
userIdentifier: dto.username,
userIdentifierType: 'email',
},
origin: DomainEventService.Auth,
},
payload: dto,
}
}
createUserContentSizeRecalculationRequestedEvent(userUuid: string): UserContentSizeRecalculationRequestedEvent {
return {
type: 'USER_CONTENT_SIZE_RECALCULATION_REQUESTED',

View File

@@ -18,6 +18,8 @@ import {
WebSocketMessageRequestedEvent,
ExitDiscountApplyRequestedEvent,
UserContentSizeRecalculationRequestedEvent,
MuteEmailsSettingChangedEvent,
EmailSubscriptionSyncRequestedEvent,
} from '@standardnotes/domain-events'
import { InviteeIdentifierType } from '../SharedSubscription/InviteeIdentifierType'
@@ -91,4 +93,18 @@ export interface DomainEventFactoryInterface {
userEmail: string
discountCode: string
}): ExitDiscountApplyRequestedEvent
createMuteEmailsSettingChangedEvent(dto: {
username: string
mute: boolean
emailSubscriptionRejectionLevel: string
}): MuteEmailsSettingChangedEvent
createEmailSubscriptionSyncRequestedEvent(dto: {
username: string
userUuid: string
subscriptionPlanName: string | null
muteFailedBackupsEmails: boolean
muteFailedCloudBackupsEmails: boolean
muteMarketingEmails: boolean
muteSignInEmails: boolean
}): EmailSubscriptionSyncRequestedEvent
}

View File

@@ -2,11 +2,13 @@ import {
CloudBackupRequestedEvent,
DomainEventPublisherInterface,
EmailBackupRequestedEvent,
MuteEmailsSettingChangedEvent,
UserDisabledSessionUserAgentLoggingEvent,
} from '@standardnotes/domain-events'
import {
EmailBackupFrequency,
LogSessionUserAgentOption,
MuteMarketingEmailsOption,
OneDriveBackupFrequency,
SettingName,
} from '@standardnotes/settings'
@@ -57,6 +59,9 @@ describe('SettingInterpreter', () => {
domainEventFactory.createUserDisabledSessionUserAgentLoggingEvent = jest
.fn()
.mockReturnValue({} as jest.Mocked<UserDisabledSessionUserAgentLoggingEvent>)
domainEventFactory.createMuteEmailsSettingChangedEvent = jest
.fn()
.mockReturnValue({} as jest.Mocked<MuteEmailsSettingChangedEvent>)
logger = {} as jest.Mocked<Logger>
logger.debug = jest.fn()
@@ -201,6 +206,23 @@ describe('SettingInterpreter', () => {
)
})
it('should trigger mute subscription emails rejection if mute setting changed', async () => {
const setting = {
name: SettingName.MuteMarketingEmails,
value: MuteMarketingEmailsOption.Muted,
} as jest.Mocked<Setting>
settingRepository.findOneByNameAndUserUuid = jest.fn().mockReturnValue(null)
await createInterpreter().interpretSettingUpdated(setting, user, MuteMarketingEmailsOption.Muted)
expect(domainEventPublisher.publish).toHaveBeenCalled()
expect(domainEventFactory.createMuteEmailsSettingChangedEvent).toHaveBeenCalledWith({
emailSubscriptionRejectionLevel: 'MARKETING',
mute: true,
username: 'test@test.te',
})
})
it('should trigger cloud backup if backup frequency setting is updated and a backup token setting is present', async () => {
settingRepository.findLastByNameAndUserUuid = jest.fn().mockReturnValueOnce({
name: SettingName.OneDriveBackupToken,

View File

@@ -1,4 +1,5 @@
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
import { EmailLevel } from '@standardnotes/domain-core'
import {
DropboxBackupFrequency,
EmailBackupFrequency,
@@ -39,6 +40,13 @@ export class SettingInterpreter implements SettingInterpreterInterface {
OneDriveBackupFrequency.Disabled,
]
private readonly emailSettingToSubscriptionRejectionLevelMap: Map<SettingName, string> = new Map([
[SettingName.MuteFailedBackupsEmails, EmailLevel.LEVELS.FailedEmailBackup],
[SettingName.MuteFailedCloudBackupsEmails, EmailLevel.LEVELS.FailedCloudBackup],
[SettingName.MuteMarketingEmails, EmailLevel.LEVELS.Marketing],
[SettingName.MuteSignInEmails, EmailLevel.LEVELS.SignIn],
])
constructor(
@inject(TYPES.DomainEventPublisher) private domainEventPublisher: DomainEventPublisherInterface,
@inject(TYPES.DomainEventFactory) private domainEventFactory: DomainEventFactoryInterface,
@@ -48,6 +56,10 @@ export class SettingInterpreter implements SettingInterpreterInterface {
) {}
async interpretSettingUpdated(updatedSetting: Setting, user: User, unencryptedValue: string | null): Promise<void> {
if (this.isChangingMuteEmailsSetting(updatedSetting)) {
await this.triggerEmailSubscriptionChange(user, updatedSetting.name as SettingName, unencryptedValue)
}
if (this.isEnablingEmailBackupSetting(updatedSetting)) {
await this.triggerEmailBackup(user.uuid)
}
@@ -78,6 +90,15 @@ export class SettingInterpreter implements SettingInterpreterInterface {
)
}
private isChangingMuteEmailsSetting(setting: Setting): boolean {
return [
SettingName.MuteFailedBackupsEmails,
SettingName.MuteFailedCloudBackupsEmails,
SettingName.MuteMarketingEmails,
SettingName.MuteSignInEmails,
].includes(setting.name as SettingName)
}
private isEnablingEmailBackupSetting(setting: Setting): boolean {
return setting.name === SettingName.EmailBackupFrequency && setting.value !== EmailBackupFrequency.Disabled
}
@@ -96,6 +117,20 @@ export class SettingInterpreter implements SettingInterpreterInterface {
return SettingName.LogSessionUserAgent === setting.name && LogSessionUserAgentOption.Disabled === setting.value
}
private async triggerEmailSubscriptionChange(
user: User,
settingName: SettingName,
unencryptedValue: string | null,
): Promise<void> {
await this.domainEventPublisher.publish(
this.domainEventFactory.createMuteEmailsSettingChangedEvent({
username: user.email,
mute: unencryptedValue === 'muted',
emailSubscriptionRejectionLevel: this.emailSettingToSubscriptionRejectionLevelMap.get(settingName) as string,
}),
)
}
private async triggerSessionUserAgentCleanup(user: User) {
await this.domainEventPublisher.publish(
this.domainEventFactory.createUserDisabledSessionUserAgentLoggingEvent({

View File

@@ -3,6 +3,7 @@ import { User } from './User'
export interface UserRepositoryInterface {
streamAll(): Promise<ReadStream>
streamTeam(memberEmail?: string): Promise<ReadStream>
findOneByUuid(uuid: string): Promise<User | null>
findOneByEmail(email: string): Promise<User | null>
save(user: User): Promise<User>

View File

@@ -1,57 +0,0 @@
import 'reflect-metadata'
import { Repository, SelectQueryBuilder } from 'typeorm'
import { OfflineSetting } from '../../Domain/Setting/OfflineSetting'
import { OfflineSettingName } from '../../Domain/Setting/OfflineSettingName'
import { MySQLOfflineSettingRepository } from './MySQLOfflineSettingRepository'
describe('MySQLOfflineSettingRepository', () => {
let queryBuilder: SelectQueryBuilder<OfflineSetting>
let offlineSetting: OfflineSetting
let ormRepository: Repository<OfflineSetting>
const createRepository = () => new MySQLOfflineSettingRepository(ormRepository)
beforeEach(() => {
queryBuilder = {} as jest.Mocked<SelectQueryBuilder<OfflineSetting>>
offlineSetting = {} as jest.Mocked<OfflineSetting>
ormRepository = {} as jest.Mocked<Repository<OfflineSetting>>
ormRepository.createQueryBuilder = jest.fn().mockImplementation(() => queryBuilder)
ormRepository.save = jest.fn()
})
it('should save', async () => {
await createRepository().save(offlineSetting)
expect(ormRepository.save).toHaveBeenCalledWith(offlineSetting)
})
it('should find one setting by name and user email', async () => {
queryBuilder.where = jest.fn().mockReturnThis()
queryBuilder.getOne = jest.fn().mockReturnValue(offlineSetting)
const result = await createRepository().findOneByNameAndEmail(OfflineSettingName.FeaturesToken, 'test@test.com')
expect(queryBuilder.where).toHaveBeenCalledWith('offline_setting.name = :name AND offline_setting.email = :email', {
name: 'FEATURES_TOKEN',
email: 'test@test.com',
})
expect(result).toEqual(offlineSetting)
})
it('should find one setting by name and value', async () => {
queryBuilder.where = jest.fn().mockReturnThis()
queryBuilder.getOne = jest.fn().mockReturnValue(offlineSetting)
const result = await createRepository().findOneByNameAndValue(OfflineSettingName.FeaturesToken, 'features-token')
expect(queryBuilder.where).toHaveBeenCalledWith('offline_setting.name = :name AND offline_setting.value = :value', {
name: 'FEATURES_TOKEN',
value: 'features-token',
})
expect(result).toEqual(offlineSetting)
})
})

View File

@@ -1,189 +0,0 @@
import 'reflect-metadata'
import { SubscriptionName } from '@standardnotes/common'
import { Repository, SelectQueryBuilder, UpdateQueryBuilder } from 'typeorm'
import { MySQLOfflineUserSubscriptionRepository } from './MySQLOfflineUserSubscriptionRepository'
import { OfflineUserSubscription } from '../../Domain/Subscription/OfflineUserSubscription'
describe('MySQLOfflineUserSubscriptionRepository', () => {
let selectQueryBuilder: SelectQueryBuilder<OfflineUserSubscription>
let updateQueryBuilder: UpdateQueryBuilder<OfflineUserSubscription>
let offlineSubscription: OfflineUserSubscription
let ormRepository: Repository<OfflineUserSubscription>
const createRepository = () => new MySQLOfflineUserSubscriptionRepository(ormRepository)
beforeEach(() => {
selectQueryBuilder = {} as jest.Mocked<SelectQueryBuilder<OfflineUserSubscription>>
updateQueryBuilder = {} as jest.Mocked<UpdateQueryBuilder<OfflineUserSubscription>>
offlineSubscription = {
planName: SubscriptionName.ProPlan,
cancelled: false,
email: 'test@test.com',
} as jest.Mocked<OfflineUserSubscription>
ormRepository = {} as jest.Mocked<Repository<OfflineUserSubscription>>
ormRepository.createQueryBuilder = jest.fn().mockImplementation(() => selectQueryBuilder)
ormRepository.save = jest.fn()
})
it('should save', async () => {
await createRepository().save(offlineSubscription)
expect(ormRepository.save).toHaveBeenCalledWith(offlineSubscription)
})
it('should find one longest lasting uncanceled subscription by user email if there are canceled ones', async () => {
const canceledSubscription = {
planName: SubscriptionName.ProPlan,
cancelled: true,
email: 'test@test.com',
} as jest.Mocked<OfflineUserSubscription>
ormRepository.createQueryBuilder = jest.fn().mockImplementation(() => selectQueryBuilder)
selectQueryBuilder.where = jest.fn().mockReturnThis()
selectQueryBuilder.orderBy = jest.fn().mockReturnThis()
selectQueryBuilder.getMany = jest.fn().mockReturnValue([canceledSubscription, offlineSubscription])
const result = await createRepository().findOneByEmail('test@test.com')
expect(selectQueryBuilder.where).toHaveBeenCalledWith('email = :email', {
email: 'test@test.com',
})
expect(selectQueryBuilder.orderBy).toHaveBeenCalledWith('ends_at', 'DESC')
expect(selectQueryBuilder.getMany).toHaveBeenCalled()
expect(result).toEqual(offlineSubscription)
})
it('should find one, longest lasting subscription by user email if there are no canceled ones', async () => {
ormRepository.createQueryBuilder = jest.fn().mockImplementation(() => selectQueryBuilder)
selectQueryBuilder.where = jest.fn().mockReturnThis()
selectQueryBuilder.orderBy = jest.fn().mockReturnThis()
selectQueryBuilder.getMany = jest.fn().mockReturnValue([offlineSubscription])
const result = await createRepository().findOneByEmail('test@test.com')
expect(selectQueryBuilder.where).toHaveBeenCalledWith('email = :email', {
email: 'test@test.com',
})
expect(selectQueryBuilder.orderBy).toHaveBeenCalledWith('ends_at', 'DESC')
expect(selectQueryBuilder.getMany).toHaveBeenCalled()
expect(result).toEqual(offlineSubscription)
})
it('should find one, longest lasting subscription by user email if there are no ucanceled ones', async () => {
offlineSubscription.cancelled = true
ormRepository.createQueryBuilder = jest.fn().mockImplementation(() => selectQueryBuilder)
selectQueryBuilder.where = jest.fn().mockReturnThis()
selectQueryBuilder.orderBy = jest.fn().mockReturnThis()
selectQueryBuilder.getMany = jest.fn().mockReturnValue([offlineSubscription])
const result = await createRepository().findOneByEmail('test@test.com')
expect(selectQueryBuilder.where).toHaveBeenCalledWith('email = :email', {
email: 'test@test.com',
})
expect(selectQueryBuilder.orderBy).toHaveBeenCalledWith('ends_at', 'DESC')
expect(selectQueryBuilder.getMany).toHaveBeenCalled()
expect(result).toEqual(offlineSubscription)
})
it('should find none if there are no subscriptions for the user', async () => {
ormRepository.createQueryBuilder = jest.fn().mockImplementation(() => selectQueryBuilder)
selectQueryBuilder.where = jest.fn().mockReturnThis()
selectQueryBuilder.orderBy = jest.fn().mockReturnThis()
selectQueryBuilder.getMany = jest.fn().mockReturnValue([])
const result = await createRepository().findOneByEmail('test@test.com')
expect(selectQueryBuilder.where).toHaveBeenCalledWith('email = :email', {
email: 'test@test.com',
})
expect(selectQueryBuilder.orderBy).toHaveBeenCalledWith('ends_at', 'DESC')
expect(selectQueryBuilder.getMany).toHaveBeenCalled()
expect(result).toBeNull()
})
it('should find multiple by user email active after', async () => {
ormRepository.createQueryBuilder = jest.fn().mockImplementation(() => selectQueryBuilder)
selectQueryBuilder.where = jest.fn().mockReturnThis()
selectQueryBuilder.orderBy = jest.fn().mockReturnThis()
selectQueryBuilder.getMany = jest.fn().mockReturnValue([offlineSubscription])
const result = await createRepository().findByEmail('test@test.com', 123)
expect(selectQueryBuilder.where).toHaveBeenCalledWith('email = :email AND ends_at > :endsAt', {
email: 'test@test.com',
endsAt: 123,
})
expect(selectQueryBuilder.orderBy).toHaveBeenCalledWith('ends_at', 'DESC')
expect(selectQueryBuilder.getMany).toHaveBeenCalled()
expect(result).toEqual([offlineSubscription])
})
it('should update cancelled by subscription id', async () => {
ormRepository.createQueryBuilder = jest.fn().mockImplementation(() => updateQueryBuilder)
updateQueryBuilder.update = jest.fn().mockReturnThis()
updateQueryBuilder.set = jest.fn().mockReturnThis()
updateQueryBuilder.where = jest.fn().mockReturnThis()
updateQueryBuilder.execute = jest.fn()
await createRepository().updateCancelled(1, true, 1000)
expect(updateQueryBuilder.update).toHaveBeenCalled()
expect(updateQueryBuilder.set).toHaveBeenCalledWith({
updatedAt: expect.any(Number),
cancelled: true,
})
expect(updateQueryBuilder.where).toHaveBeenCalledWith('subscription_id = :subscriptionId', {
subscriptionId: 1,
})
expect(updateQueryBuilder.execute).toHaveBeenCalled()
})
it('should update ends at by subscription id', async () => {
ormRepository.createQueryBuilder = jest.fn().mockImplementation(() => updateQueryBuilder)
updateQueryBuilder.update = jest.fn().mockReturnThis()
updateQueryBuilder.set = jest.fn().mockReturnThis()
updateQueryBuilder.where = jest.fn().mockReturnThis()
updateQueryBuilder.execute = jest.fn()
await createRepository().updateEndsAt(1, 1000, 1000)
expect(updateQueryBuilder.update).toHaveBeenCalled()
expect(updateQueryBuilder.set).toHaveBeenCalledWith({
updatedAt: expect.any(Number),
endsAt: 1000,
})
expect(updateQueryBuilder.where).toHaveBeenCalledWith('subscription_id = :subscriptionId', {
subscriptionId: 1,
})
expect(updateQueryBuilder.execute).toHaveBeenCalled()
})
it('should find one offline user subscription by user subscription id', async () => {
ormRepository.createQueryBuilder = jest.fn().mockImplementation(() => selectQueryBuilder)
selectQueryBuilder.where = jest.fn().mockReturnThis()
selectQueryBuilder.getOne = jest.fn().mockReturnValue(offlineSubscription)
const result = await createRepository().findOneBySubscriptionId(123)
expect(selectQueryBuilder.where).toHaveBeenCalledWith('subscription_id = :subscriptionId', {
subscriptionId: 123,
})
expect(selectQueryBuilder.getOne).toHaveBeenCalled()
expect(result).toEqual(offlineSubscription)
})
})

View File

@@ -1,78 +0,0 @@
import 'reflect-metadata'
import { Repository, SelectQueryBuilder, UpdateQueryBuilder } from 'typeorm'
import { RevokedSession } from '../../Domain/Session/RevokedSession'
import { MySQLRevokedSessionRepository } from './MySQLRevokedSessionRepository'
describe('MySQLRevokedSessionRepository', () => {
let ormRepository: Repository<RevokedSession>
let queryBuilder: SelectQueryBuilder<RevokedSession>
let updateQueryBuilder: UpdateQueryBuilder<RevokedSession>
let session: RevokedSession
const createRepository = () => new MySQLRevokedSessionRepository(ormRepository)
beforeEach(() => {
queryBuilder = {} as jest.Mocked<SelectQueryBuilder<RevokedSession>>
updateQueryBuilder = {} as jest.Mocked<UpdateQueryBuilder<RevokedSession>>
session = {} as jest.Mocked<RevokedSession>
ormRepository = {} as jest.Mocked<Repository<RevokedSession>>
ormRepository.createQueryBuilder = jest.fn().mockImplementation(() => queryBuilder)
ormRepository.save = jest.fn()
ormRepository.remove = jest.fn()
})
it('should save', async () => {
await createRepository().save(session)
expect(ormRepository.save).toHaveBeenCalledWith(session)
})
it('should remove', async () => {
await createRepository().remove(session)
expect(ormRepository.remove).toHaveBeenCalledWith(session)
})
it('should clear user agent data on all user sessions', async () => {
ormRepository.createQueryBuilder = jest.fn().mockImplementation(() => updateQueryBuilder)
updateQueryBuilder.update = jest.fn().mockReturnThis()
updateQueryBuilder.set = jest.fn().mockReturnThis()
updateQueryBuilder.where = jest.fn().mockReturnThis()
updateQueryBuilder.execute = jest.fn()
await createRepository().clearUserAgentByUserUuid('1-2-3')
expect(updateQueryBuilder.update).toHaveBeenCalled()
expect(updateQueryBuilder.set).toHaveBeenCalledWith({
userAgent: null,
})
expect(updateQueryBuilder.where).toHaveBeenCalledWith('user_uuid = :userUuid', { userUuid: '1-2-3' })
expect(updateQueryBuilder.execute).toHaveBeenCalled()
})
it('should find one session by id', async () => {
queryBuilder.where = jest.fn().mockReturnThis()
queryBuilder.getOne = jest.fn().mockReturnValue(session)
const result = await createRepository().findOneByUuid('123')
expect(queryBuilder.where).toHaveBeenCalledWith('revoked_session.uuid = :uuid', { uuid: '123' })
expect(result).toEqual(session)
})
it('should find all revoked sessions by user id', async () => {
queryBuilder.where = jest.fn().mockReturnThis()
queryBuilder.getMany = jest.fn().mockReturnValue([session])
const result = await createRepository().findAllByUserUuid('123')
expect(queryBuilder.where).toHaveBeenCalledWith('revoked_session.user_uuid = :user_uuid', { user_uuid: '123' })
expect(result).toEqual([session])
})
})

View File

@@ -1,49 +0,0 @@
import 'reflect-metadata'
import { Repository, SelectQueryBuilder } from 'typeorm'
import { Role } from '../../Domain/Role/Role'
import { MySQLRoleRepository } from './MySQLRoleRepository'
describe('MySQLRoleRepository', () => {
let ormRepository: Repository<Role>
let queryBuilder: SelectQueryBuilder<Role>
let role: Role
const createRepository = () => new MySQLRoleRepository(ormRepository)
beforeEach(() => {
queryBuilder = {} as jest.Mocked<SelectQueryBuilder<Role>>
queryBuilder.cache = jest.fn().mockReturnThis()
role = {} as jest.Mocked<Role>
ormRepository = {} as jest.Mocked<Repository<Role>>
ormRepository.createQueryBuilder = jest.fn().mockImplementation(() => queryBuilder)
})
it('should find latest version of a role by name', async () => {
queryBuilder.where = jest.fn().mockReturnThis()
queryBuilder.take = jest.fn().mockReturnThis()
queryBuilder.orderBy = jest.fn().mockReturnThis()
queryBuilder.getMany = jest.fn().mockReturnValue([role])
const result = await createRepository().findOneByName('test')
expect(queryBuilder.where).toHaveBeenCalledWith('role.name = :name', { name: 'test' })
expect(queryBuilder.take).toHaveBeenCalledWith(1)
expect(queryBuilder.orderBy).toHaveBeenCalledWith('version', 'DESC')
expect(result).toEqual(role)
})
it('should return null if not found the latest version of a role by name', async () => {
queryBuilder.where = jest.fn().mockReturnThis()
queryBuilder.take = jest.fn().mockReturnThis()
queryBuilder.orderBy = jest.fn().mockReturnThis()
queryBuilder.getMany = jest.fn().mockReturnValue([])
const result = await createRepository().findOneByName('test')
expect(result).toBeNull()
})
})

View File

@@ -1,174 +0,0 @@
import 'reflect-metadata'
import * as dayjs from 'dayjs'
import { Repository, SelectQueryBuilder, UpdateQueryBuilder } from 'typeorm'
import { Session } from '../../Domain/Session/Session'
import { MySQLSessionRepository } from './MySQLSessionRepository'
describe('MySQLSessionRepository', () => {
let ormRepository: Repository<Session>
let queryBuilder: SelectQueryBuilder<Session>
let updateQueryBuilder: UpdateQueryBuilder<Session>
let session: Session
const createRepository = () => new MySQLSessionRepository(ormRepository)
beforeEach(() => {
queryBuilder = {} as jest.Mocked<SelectQueryBuilder<Session>>
updateQueryBuilder = {} as jest.Mocked<UpdateQueryBuilder<Session>>
session = {} as jest.Mocked<Session>
ormRepository = {} as jest.Mocked<Repository<Session>>
ormRepository.createQueryBuilder = jest.fn().mockImplementation(() => queryBuilder)
ormRepository.save = jest.fn()
ormRepository.remove = jest.fn()
})
it('should save', async () => {
await createRepository().save(session)
expect(ormRepository.save).toHaveBeenCalledWith(session)
})
it('should remove', async () => {
await createRepository().remove(session)
expect(ormRepository.remove).toHaveBeenCalledWith(session)
})
it('should clear user agent data on all user sessions', async () => {
ormRepository.createQueryBuilder = jest.fn().mockImplementation(() => updateQueryBuilder)
updateQueryBuilder.update = jest.fn().mockReturnThis()
updateQueryBuilder.set = jest.fn().mockReturnThis()
updateQueryBuilder.where = jest.fn().mockReturnThis()
updateQueryBuilder.execute = jest.fn()
await createRepository().clearUserAgentByUserUuid('1-2-3')
expect(updateQueryBuilder.update).toHaveBeenCalled()
expect(updateQueryBuilder.set).toHaveBeenCalledWith({
userAgent: null,
})
expect(updateQueryBuilder.where).toHaveBeenCalledWith('user_uuid = :userUuid', { userUuid: '1-2-3' })
expect(updateQueryBuilder.execute).toHaveBeenCalled()
})
it('should update hashed tokens on a session', async () => {
ormRepository.createQueryBuilder = jest.fn().mockImplementation(() => updateQueryBuilder)
updateQueryBuilder.update = jest.fn().mockReturnThis()
updateQueryBuilder.set = jest.fn().mockReturnThis()
updateQueryBuilder.where = jest.fn().mockReturnThis()
updateQueryBuilder.execute = jest.fn()
await createRepository().updateHashedTokens('123', '234', '345')
expect(updateQueryBuilder.update).toHaveBeenCalled()
expect(updateQueryBuilder.set).toHaveBeenCalledWith({
hashedAccessToken: '234',
hashedRefreshToken: '345',
})
expect(updateQueryBuilder.where).toHaveBeenCalledWith('uuid = :uuid', { uuid: '123' })
expect(updateQueryBuilder.execute).toHaveBeenCalled()
})
it('should update token expiration dates on a session', async () => {
ormRepository.createQueryBuilder = jest.fn().mockImplementation(() => updateQueryBuilder)
updateQueryBuilder.update = jest.fn().mockReturnThis()
updateQueryBuilder.set = jest.fn().mockReturnThis()
updateQueryBuilder.where = jest.fn().mockReturnThis()
updateQueryBuilder.execute = jest.fn()
await createRepository().updatedTokenExpirationDates(
'123',
dayjs.utc('2020-11-26 13:34').toDate(),
dayjs.utc('2020-11-26 14:34').toDate(),
)
expect(updateQueryBuilder.update).toHaveBeenCalled()
expect(updateQueryBuilder.set).toHaveBeenCalledWith({
accessExpiration: dayjs.utc('2020-11-26T13:34:00.000Z').toDate(),
refreshExpiration: dayjs.utc('2020-11-26T14:34:00.000Z').toDate(),
})
expect(updateQueryBuilder.where).toHaveBeenCalledWith('uuid = :uuid', { uuid: '123' })
expect(updateQueryBuilder.execute).toHaveBeenCalled()
})
it('should find active sessions by user id', async () => {
queryBuilder.where = jest.fn().mockReturnThis()
queryBuilder.getMany = jest.fn().mockReturnValue([session])
const result = await createRepository().findAllByRefreshExpirationAndUserUuid('123')
expect(queryBuilder.where).toHaveBeenCalledWith(
'session.refresh_expiration > :refresh_expiration AND session.user_uuid = :user_uuid',
{ refresh_expiration: expect.any(Date), user_uuid: '123' },
)
expect(result).toEqual([session])
})
it('should find all sessions by user id', async () => {
queryBuilder.where = jest.fn().mockReturnThis()
queryBuilder.getMany = jest.fn().mockReturnValue([session])
const result = await createRepository().findAllByUserUuid('123')
expect(queryBuilder.where).toHaveBeenCalledWith('session.user_uuid = :user_uuid', { user_uuid: '123' })
expect(result).toEqual([session])
})
it('should find one session by id', async () => {
queryBuilder.where = jest.fn().mockReturnThis()
queryBuilder.getOne = jest.fn().mockReturnValue(session)
const result = await createRepository().findOneByUuid('123')
expect(queryBuilder.where).toHaveBeenCalledWith('session.uuid = :uuid', { uuid: '123' })
expect(result).toEqual(session)
})
it('should find one session by id and user id', async () => {
queryBuilder.where = jest.fn().mockReturnThis()
queryBuilder.getOne = jest.fn().mockReturnValue(session)
const result = await createRepository().findOneByUuidAndUserUuid('123', '234')
expect(queryBuilder.where).toHaveBeenCalledWith('session.uuid = :uuid AND session.user_uuid = :user_uuid', {
uuid: '123',
user_uuid: '234',
})
expect(result).toEqual(session)
})
it('should delete all session for a user except the current one', async () => {
queryBuilder.where = jest.fn().mockReturnThis()
queryBuilder.delete = jest.fn().mockReturnThis()
queryBuilder.execute = jest.fn()
await createRepository().deleteAllByUserUuid('123', '234')
expect(queryBuilder.delete).toHaveBeenCalled()
expect(queryBuilder.where).toHaveBeenCalledWith('user_uuid = :user_uuid AND uuid != :current_session_uuid', {
user_uuid: '123',
current_session_uuid: '234',
})
expect(queryBuilder.execute).toHaveBeenCalled()
})
it('should delete one session by id', async () => {
queryBuilder.where = jest.fn().mockReturnThis()
queryBuilder.delete = jest.fn().mockReturnThis()
queryBuilder.execute = jest.fn()
await createRepository().deleteOneByUuid('123')
expect(queryBuilder.delete).toHaveBeenCalled()
expect(queryBuilder.where).toHaveBeenCalledWith('uuid = :uuid', { uuid: '123' })
expect(queryBuilder.execute).toHaveBeenCalled()
})
})

View File

@@ -1,140 +0,0 @@
import 'reflect-metadata'
import { ReadStream } from 'fs'
import { Repository, SelectQueryBuilder } from 'typeorm'
import { Setting } from '../../Domain/Setting/Setting'
import { MySQLSettingRepository } from './MySQLSettingRepository'
import { EmailBackupFrequency, SettingName } from '@standardnotes/settings'
describe('MySQLSettingRepository', () => {
let ormRepository: Repository<Setting>
let queryBuilder: SelectQueryBuilder<Setting>
let setting: Setting
const createRepository = () => new MySQLSettingRepository(ormRepository)
beforeEach(() => {
queryBuilder = {} as jest.Mocked<SelectQueryBuilder<Setting>>
setting = {} as jest.Mocked<Setting>
ormRepository = {} as jest.Mocked<Repository<Setting>>
ormRepository.createQueryBuilder = jest.fn().mockImplementation(() => queryBuilder)
ormRepository.save = jest.fn()
})
it('should save', async () => {
await createRepository().save(setting)
expect(ormRepository.save).toHaveBeenCalledWith(setting)
})
it('should stream all settings by name and value', async () => {
const stream = {} as jest.Mocked<ReadStream>
queryBuilder.where = jest.fn().mockReturnThis()
queryBuilder.orderBy = jest.fn().mockReturnThis()
queryBuilder.stream = jest.fn().mockReturnValue(stream)
const result = await createRepository().streamAllByNameAndValue(
SettingName.EmailBackupFrequency,
EmailBackupFrequency.Daily,
)
expect(result).toEqual(stream)
})
it('should find one setting by uuid', async () => {
queryBuilder.where = jest.fn().mockReturnThis()
queryBuilder.getOne = jest.fn().mockReturnValue(setting)
const result = await createRepository().findOneByUuid('1-2-3')
expect(queryBuilder.where).toHaveBeenCalledWith('setting.uuid = :uuid', { uuid: '1-2-3' })
expect(result).toEqual(setting)
})
it('should find one setting by name and user uuid', async () => {
queryBuilder.where = jest.fn().mockReturnThis()
queryBuilder.getOne = jest.fn().mockReturnValue(setting)
const result = await createRepository().findOneByNameAndUserUuid('test', '1-2-3')
expect(queryBuilder.where).toHaveBeenCalledWith('setting.name = :name AND setting.user_uuid = :user_uuid', {
name: 'test',
user_uuid: '1-2-3',
})
expect(result).toEqual(setting)
})
it('should find one setting by name and uuid', async () => {
queryBuilder.where = jest.fn().mockReturnThis()
queryBuilder.getOne = jest.fn().mockReturnValue(setting)
const result = await createRepository().findOneByUuidAndNames('1-2-3', ['test' as SettingName])
expect(queryBuilder.where).toHaveBeenCalledWith('setting.uuid = :uuid AND setting.name IN (:...names)', {
names: ['test'],
uuid: '1-2-3',
})
expect(result).toEqual(setting)
})
it('should find last setting by name and user uuid', async () => {
queryBuilder.where = jest.fn().mockReturnThis()
queryBuilder.orderBy = jest.fn().mockReturnThis()
queryBuilder.limit = jest.fn().mockReturnThis()
queryBuilder.getMany = jest.fn().mockReturnValue([setting])
const result = await createRepository().findLastByNameAndUserUuid('test', '1-2-3')
expect(queryBuilder.where).toHaveBeenCalledWith('setting.name = :name AND setting.user_uuid = :user_uuid', {
name: 'test',
user_uuid: '1-2-3',
})
expect(queryBuilder.orderBy).toHaveBeenCalledWith('updated_at', 'DESC')
expect(queryBuilder.limit).toHaveBeenCalledWith(1)
expect(result).toEqual(setting)
})
it('should return null if not found last setting by name and user uuid', async () => {
queryBuilder.where = jest.fn().mockReturnThis()
queryBuilder.orderBy = jest.fn().mockReturnThis()
queryBuilder.limit = jest.fn().mockReturnThis()
queryBuilder.getMany = jest.fn().mockReturnValue([])
const result = await createRepository().findLastByNameAndUserUuid('test', '1-2-3')
expect(result).toBeNull()
})
it('should find all by user uuid', async () => {
queryBuilder.where = jest.fn().mockReturnThis()
const settings = [setting]
queryBuilder.getMany = jest.fn().mockReturnValue(settings)
const userUuid = '123'
const result = await createRepository().findAllByUserUuid(userUuid)
expect(queryBuilder.where).toHaveBeenCalledWith('setting.user_uuid = :user_uuid', { user_uuid: userUuid })
expect(result).toEqual(settings)
})
it('should delete setting if it does exist', async () => {
const queryBuilder = {
delete: () => queryBuilder,
where: () => queryBuilder,
execute: () => undefined,
}
ormRepository.createQueryBuilder = jest.fn().mockImplementation(() => queryBuilder)
const result = await createRepository().deleteByUserUuid({
userUuid: 'userUuid',
settingName: 'settingName',
})
expect(result).toEqual(undefined)
})
})

View File

@@ -1,100 +0,0 @@
import 'reflect-metadata'
import { Repository, SelectQueryBuilder } from 'typeorm'
import { MySQLSharedSubscriptionInvitationRepository } from './MySQLSharedSubscriptionInvitationRepository'
import { SharedSubscriptionInvitation } from '../../Domain/SharedSubscription/SharedSubscriptionInvitation'
import { InvitationStatus } from '../../Domain/SharedSubscription/InvitationStatus'
describe('MySQLSharedSubscriptionInvitationRepository', () => {
let ormRepository: Repository<SharedSubscriptionInvitation>
let queryBuilder: SelectQueryBuilder<SharedSubscriptionInvitation>
let invitation: SharedSubscriptionInvitation
const createRepository = () => new MySQLSharedSubscriptionInvitationRepository(ormRepository)
beforeEach(() => {
queryBuilder = {} as jest.Mocked<SelectQueryBuilder<SharedSubscriptionInvitation>>
invitation = {} as jest.Mocked<SharedSubscriptionInvitation>
ormRepository = {} as jest.Mocked<Repository<SharedSubscriptionInvitation>>
ormRepository.createQueryBuilder = jest.fn().mockImplementation(() => queryBuilder)
ormRepository.save = jest.fn()
})
it('should save', async () => {
await createRepository().save(invitation)
expect(ormRepository.save).toHaveBeenCalledWith(invitation)
})
it('should get invitations by inviter email', async () => {
queryBuilder.where = jest.fn().mockReturnThis()
queryBuilder.getMany = jest.fn().mockReturnValue([])
const result = await createRepository().findByInviterEmail('test@test.te')
expect(queryBuilder.where).toHaveBeenCalledWith('invitation.inviter_identifier = :inviterEmail', {
inviterEmail: 'test@test.te',
})
expect(result).toEqual([])
})
it('should count invitations by inviter email and statuses', async () => {
queryBuilder.where = jest.fn().mockReturnThis()
queryBuilder.getCount = jest.fn().mockReturnValue(3)
const result = await createRepository().countByInviterEmailAndStatus('test@test.te', [InvitationStatus.Sent])
expect(queryBuilder.where).toHaveBeenCalledWith(
'invitation.inviter_identifier = :inviterEmail AND invitation.status IN (:...statuses)',
{ inviterEmail: 'test@test.te', statuses: ['sent'] },
)
expect(result).toEqual(3)
})
it('should find one invitation by name and uuid', async () => {
queryBuilder.where = jest.fn().mockReturnThis()
queryBuilder.getOne = jest.fn().mockReturnValue(invitation)
const result = await createRepository().findOneByUuidAndStatus('1-2-3', InvitationStatus.Sent)
expect(queryBuilder.where).toHaveBeenCalledWith('invitation.uuid = :uuid AND invitation.status = :status', {
uuid: '1-2-3',
status: 'sent',
})
expect(result).toEqual(invitation)
})
it('should find one invitation by invitee and inviter email', async () => {
queryBuilder.where = jest.fn().mockReturnThis()
queryBuilder.getOne = jest.fn().mockReturnValue(invitation)
const result = await createRepository().findOneByInviteeAndInviterEmail('invitee@test.te', 'inviter@test.te')
expect(queryBuilder.where).toHaveBeenCalledWith(
'invitation.inviter_identifier = :inviterEmail AND invitation.invitee_identifier = :inviteeEmail',
{
inviterEmail: 'inviter@test.te',
inviteeEmail: 'invitee@test.te',
},
)
expect(result).toEqual(invitation)
})
it('should find one invitation by uuid', async () => {
queryBuilder.where = jest.fn().mockReturnThis()
queryBuilder.getOne = jest.fn().mockReturnValue(invitation)
const result = await createRepository().findOneByUuid('1-2-3')
expect(queryBuilder.where).toHaveBeenCalledWith('invitation.uuid = :uuid', { uuid: '1-2-3' })
expect(result).toEqual(invitation)
})
})

View File

@@ -1,68 +0,0 @@
import 'reflect-metadata'
import { Repository, SelectQueryBuilder } from 'typeorm'
import { SubscriptionSetting } from '../../Domain/Setting/SubscriptionSetting'
import { MySQLSubscriptionSettingRepository } from './MySQLSubscriptionSettingRepository'
describe('MySQLSubscriptionSettingRepository', () => {
let ormRepository: Repository<SubscriptionSetting>
let queryBuilder: SelectQueryBuilder<SubscriptionSetting>
let setting: SubscriptionSetting
const createRepository = () => new MySQLSubscriptionSettingRepository(ormRepository)
beforeEach(() => {
queryBuilder = {} as jest.Mocked<SelectQueryBuilder<SubscriptionSetting>>
setting = {} as jest.Mocked<SubscriptionSetting>
ormRepository = {} as jest.Mocked<Repository<SubscriptionSetting>>
ormRepository.createQueryBuilder = jest.fn().mockImplementation(() => queryBuilder)
ormRepository.save = jest.fn()
})
it('should save', async () => {
await createRepository().save(setting)
expect(ormRepository.save).toHaveBeenCalledWith(setting)
})
it('should find one setting by uuid', async () => {
queryBuilder.where = jest.fn().mockReturnThis()
queryBuilder.getOne = jest.fn().mockReturnValue(setting)
const result = await createRepository().findOneByUuid('1-2-3')
expect(queryBuilder.where).toHaveBeenCalledWith('setting.uuid = :uuid', { uuid: '1-2-3' })
expect(result).toEqual(setting)
})
it('should find last setting by name and user uuid', async () => {
queryBuilder.where = jest.fn().mockReturnThis()
queryBuilder.orderBy = jest.fn().mockReturnThis()
queryBuilder.limit = jest.fn().mockReturnThis()
queryBuilder.getMany = jest.fn().mockReturnValue([setting])
const result = await createRepository().findLastByNameAndUserSubscriptionUuid('test', '1-2-3')
expect(queryBuilder.where).toHaveBeenCalledWith(
'setting.name = :name AND setting.user_subscription_uuid = :userSubscriptionUuid',
{ name: 'test', userSubscriptionUuid: '1-2-3' },
)
expect(queryBuilder.orderBy).toHaveBeenCalledWith('updated_at', 'DESC')
expect(queryBuilder.limit).toHaveBeenCalledWith(1)
expect(result).toEqual(setting)
})
it('should return null if not found last setting by name and user uuid', async () => {
queryBuilder.where = jest.fn().mockReturnThis()
queryBuilder.orderBy = jest.fn().mockReturnThis()
queryBuilder.limit = jest.fn().mockReturnThis()
queryBuilder.getMany = jest.fn().mockReturnValue([])
const result = await createRepository().findLastByNameAndUserSubscriptionUuid('test', '1-2-3')
expect(result).toBeNull()
})
})

View File

@@ -1,69 +0,0 @@
import 'reflect-metadata'
import { ReadStream } from 'fs'
import { Repository, SelectQueryBuilder } from 'typeorm'
import { User } from '../../Domain/User/User'
import { MySQLUserRepository } from './MySQLUserRepository'
describe('MySQLUserRepository', () => {
let ormRepository: Repository<User>
let queryBuilder: SelectQueryBuilder<User>
let user: User
const createRepository = () => new MySQLUserRepository(ormRepository)
beforeEach(() => {
queryBuilder = {} as jest.Mocked<SelectQueryBuilder<User>>
queryBuilder.cache = jest.fn().mockReturnThis()
user = {} as jest.Mocked<User>
ormRepository = {} as jest.Mocked<Repository<User>>
ormRepository.createQueryBuilder = jest.fn().mockImplementation(() => queryBuilder)
ormRepository.save = jest.fn()
ormRepository.remove = jest.fn()
})
it('should save', async () => {
await createRepository().save(user)
expect(ormRepository.save).toHaveBeenCalledWith(user)
})
it('should remove', async () => {
await createRepository().remove(user)
expect(ormRepository.remove).toHaveBeenCalledWith(user)
})
it('should find one user by id', async () => {
queryBuilder.where = jest.fn().mockReturnThis()
queryBuilder.getOne = jest.fn().mockReturnValue(user)
const result = await createRepository().findOneByUuid('123')
expect(queryBuilder.where).toHaveBeenCalledWith('user.uuid = :uuid', { uuid: '123' })
expect(result).toEqual(user)
})
it('should stream all users', async () => {
const stream = {} as jest.Mocked<ReadStream>
queryBuilder.stream = jest.fn().mockReturnValue(stream)
const result = await createRepository().streamAll()
expect(result).toEqual(stream)
})
it('should find one user by email', async () => {
queryBuilder.where = jest.fn().mockReturnThis()
queryBuilder.getOne = jest.fn().mockReturnValue(user)
const result = await createRepository().findOneByEmail('test@test.te')
expect(queryBuilder.where).toHaveBeenCalledWith('user.email = :email', { email: 'test@test.te' })
expect(result).toEqual(user)
})
})

View File

@@ -22,7 +22,21 @@ export class MySQLUserRepository implements UserRepositoryInterface {
}
async streamAll(): Promise<ReadStream> {
return this.ormRepository.createQueryBuilder('user').stream()
return this.ormRepository
.createQueryBuilder('user')
.where('created_at < :createdAt', { createdAt: new Date().toISOString() })
.stream()
}
async streamTeam(memberEmail?: string): Promise<ReadStream> {
const queryBuilder = this.ormRepository.createQueryBuilder()
if (memberEmail !== undefined) {
queryBuilder.where('email = :email', { email: memberEmail })
} else {
queryBuilder.where('email LIKE :email', { email: '%@standardnotes.com' })
}
return queryBuilder.stream()
}
async findOneByUuid(uuid: string): Promise<User | null> {

View File

@@ -1,287 +0,0 @@
import 'reflect-metadata'
import { SubscriptionName } from '@standardnotes/common'
import { Repository, SelectQueryBuilder, UpdateQueryBuilder } from 'typeorm'
import { UserSubscription } from '../../Domain/Subscription/UserSubscription'
import { MySQLUserSubscriptionRepository } from './MySQLUserSubscriptionRepository'
import { UserSubscriptionType } from '../../Domain/Subscription/UserSubscriptionType'
import { TimerInterface } from '@standardnotes/time'
describe('MySQLUserSubscriptionRepository', () => {
let ormRepository: Repository<UserSubscription>
let selectQueryBuilder: SelectQueryBuilder<UserSubscription>
let updateQueryBuilder: UpdateQueryBuilder<UserSubscription>
let subscription: UserSubscription
let timer: TimerInterface
const createRepository = () => new MySQLUserSubscriptionRepository(ormRepository, timer)
beforeEach(() => {
selectQueryBuilder = {} as jest.Mocked<SelectQueryBuilder<UserSubscription>>
updateQueryBuilder = {} as jest.Mocked<UpdateQueryBuilder<UserSubscription>>
subscription = {
planName: SubscriptionName.ProPlan,
cancelled: false,
} as jest.Mocked<UserSubscription>
ormRepository = {} as jest.Mocked<Repository<UserSubscription>>
ormRepository.createQueryBuilder = jest.fn().mockImplementation(() => selectQueryBuilder)
ormRepository.save = jest.fn()
timer = {} as jest.Mocked<TimerInterface>
timer.getTimestampInMicroseconds = jest.fn().mockReturnValue(123)
})
it('should save', async () => {
await createRepository().save(subscription)
expect(ormRepository.save).toHaveBeenCalledWith(subscription)
})
it('should find all subscriptions by user uuid', async () => {
const canceledSubscription = {
planName: SubscriptionName.ProPlan,
cancelled: true,
} as jest.Mocked<UserSubscription>
ormRepository.createQueryBuilder = jest.fn().mockImplementation(() => selectQueryBuilder)
selectQueryBuilder.where = jest.fn().mockReturnThis()
selectQueryBuilder.orderBy = jest.fn().mockReturnThis()
selectQueryBuilder.getMany = jest.fn().mockReturnValue([canceledSubscription, subscription])
const result = await createRepository().findByUserUuid('123')
expect(selectQueryBuilder.where).toHaveBeenCalledWith('user_uuid = :user_uuid', {
user_uuid: '123',
})
expect(selectQueryBuilder.orderBy).toHaveBeenCalledWith('ends_at', 'DESC')
expect(selectQueryBuilder.getMany).toHaveBeenCalled()
expect(result).toEqual([canceledSubscription, subscription])
})
it('should count all active subscriptions', async () => {
ormRepository.createQueryBuilder = jest.fn().mockImplementation(() => selectQueryBuilder)
selectQueryBuilder.groupBy = jest.fn().mockReturnThis()
selectQueryBuilder.where = jest.fn().mockReturnThis()
selectQueryBuilder.getCount = jest.fn().mockReturnValue(2)
const result = await createRepository().countActiveSubscriptions()
expect(selectQueryBuilder.where).toHaveBeenCalledWith('ends_at > :timestamp', {
timestamp: 123,
})
expect(selectQueryBuilder.groupBy).toHaveBeenCalledWith('user_uuid')
expect(selectQueryBuilder.getCount).toHaveBeenCalled()
expect(result).toEqual(2)
})
it('should find one longest lasting uncanceled subscription by user uuid if there are canceled ones', async () => {
const canceledSubscription = {
planName: SubscriptionName.ProPlan,
cancelled: true,
} as jest.Mocked<UserSubscription>
ormRepository.createQueryBuilder = jest.fn().mockImplementation(() => selectQueryBuilder)
selectQueryBuilder.where = jest.fn().mockReturnThis()
selectQueryBuilder.orderBy = jest.fn().mockReturnThis()
selectQueryBuilder.getMany = jest.fn().mockReturnValue([canceledSubscription, subscription])
const result = await createRepository().findOneByUserUuid('123')
expect(selectQueryBuilder.where).toHaveBeenCalledWith('user_uuid = :user_uuid', {
user_uuid: '123',
})
expect(selectQueryBuilder.orderBy).toHaveBeenCalledWith('ends_at', 'DESC')
expect(selectQueryBuilder.getMany).toHaveBeenCalled()
expect(result).toEqual(subscription)
})
it('should find one, longest lasting subscription by user uuid if there are no canceled ones', async () => {
ormRepository.createQueryBuilder = jest.fn().mockImplementation(() => selectQueryBuilder)
selectQueryBuilder.where = jest.fn().mockReturnThis()
selectQueryBuilder.orderBy = jest.fn().mockReturnThis()
selectQueryBuilder.getMany = jest.fn().mockReturnValue([subscription])
const result = await createRepository().findOneByUserUuid('123')
expect(selectQueryBuilder.where).toHaveBeenCalledWith('user_uuid = :user_uuid', {
user_uuid: '123',
})
expect(selectQueryBuilder.orderBy).toHaveBeenCalledWith('ends_at', 'DESC')
expect(selectQueryBuilder.getMany).toHaveBeenCalled()
expect(result).toEqual(subscription)
})
it('should count by user uuid', async () => {
ormRepository.createQueryBuilder = jest.fn().mockImplementation(() => selectQueryBuilder)
selectQueryBuilder.where = jest.fn().mockReturnThis()
selectQueryBuilder.getCount = jest.fn().mockReturnValue(2)
const result = await createRepository().countByUserUuid('123')
expect(selectQueryBuilder.where).toHaveBeenCalledWith('user_uuid = :user_uuid', {
user_uuid: '123',
})
expect(selectQueryBuilder.getCount).toHaveBeenCalled()
expect(result).toEqual(2)
})
it('should find one, longest lasting subscription by user uuid if there are no ucanceled ones', async () => {
subscription.cancelled = true
ormRepository.createQueryBuilder = jest.fn().mockImplementation(() => selectQueryBuilder)
selectQueryBuilder.where = jest.fn().mockReturnThis()
selectQueryBuilder.orderBy = jest.fn().mockReturnThis()
selectQueryBuilder.getMany = jest.fn().mockReturnValue([subscription])
const result = await createRepository().findOneByUserUuid('123')
expect(selectQueryBuilder.where).toHaveBeenCalledWith('user_uuid = :user_uuid', {
user_uuid: '123',
})
expect(selectQueryBuilder.orderBy).toHaveBeenCalledWith('ends_at', 'DESC')
expect(selectQueryBuilder.getMany).toHaveBeenCalled()
expect(result).toEqual(subscription)
})
it('should find none if there are no subscriptions for the user', async () => {
ormRepository.createQueryBuilder = jest.fn().mockImplementation(() => selectQueryBuilder)
selectQueryBuilder.where = jest.fn().mockReturnThis()
selectQueryBuilder.orderBy = jest.fn().mockReturnThis()
selectQueryBuilder.getMany = jest.fn().mockReturnValue([])
const result = await createRepository().findOneByUserUuid('123')
expect(selectQueryBuilder.where).toHaveBeenCalledWith('user_uuid = :user_uuid', {
user_uuid: '123',
})
expect(selectQueryBuilder.orderBy).toHaveBeenCalledWith('ends_at', 'DESC')
expect(selectQueryBuilder.getMany).toHaveBeenCalled()
expect(result).toBeNull()
})
it('should update ends at by subscription id', async () => {
ormRepository.createQueryBuilder = jest.fn().mockImplementation(() => updateQueryBuilder)
updateQueryBuilder.update = jest.fn().mockReturnThis()
updateQueryBuilder.set = jest.fn().mockReturnThis()
updateQueryBuilder.where = jest.fn().mockReturnThis()
updateQueryBuilder.execute = jest.fn()
await createRepository().updateEndsAt(1, 1000, 1000)
expect(updateQueryBuilder.update).toHaveBeenCalled()
expect(updateQueryBuilder.set).toHaveBeenCalledWith({
updatedAt: 1000,
renewedAt: 1000,
endsAt: 1000,
})
expect(updateQueryBuilder.where).toHaveBeenCalledWith('subscription_id = :subscriptionId', {
subscriptionId: 1,
})
expect(updateQueryBuilder.execute).toHaveBeenCalled()
})
it('should update cancelled by subscription id', async () => {
ormRepository.createQueryBuilder = jest.fn().mockImplementation(() => updateQueryBuilder)
updateQueryBuilder.update = jest.fn().mockReturnThis()
updateQueryBuilder.set = jest.fn().mockReturnThis()
updateQueryBuilder.where = jest.fn().mockReturnThis()
updateQueryBuilder.execute = jest.fn()
await createRepository().updateCancelled(1, true, 1000)
expect(updateQueryBuilder.update).toHaveBeenCalled()
expect(updateQueryBuilder.set).toHaveBeenCalledWith({
updatedAt: expect.any(Number),
cancelled: true,
})
expect(updateQueryBuilder.where).toHaveBeenCalledWith('subscription_id = :subscriptionId', {
subscriptionId: 1,
})
expect(updateQueryBuilder.execute).toHaveBeenCalled()
})
it('should find subscriptions by id', async () => {
ormRepository.createQueryBuilder = jest.fn().mockImplementation(() => selectQueryBuilder)
selectQueryBuilder.where = jest.fn().mockReturnThis()
selectQueryBuilder.orderBy = jest.fn().mockReturnThis()
selectQueryBuilder.getMany = jest.fn().mockReturnValue([subscription])
const result = await createRepository().findBySubscriptionId(123)
expect(selectQueryBuilder.where).toHaveBeenCalledWith('subscription_id = :subscriptionId', {
subscriptionId: 123,
})
expect(selectQueryBuilder.orderBy).toHaveBeenCalledWith('created_at', 'DESC')
expect(selectQueryBuilder.getMany).toHaveBeenCalled()
expect(result).toEqual([subscription])
})
it('should find subscriptions by id and type', async () => {
ormRepository.createQueryBuilder = jest.fn().mockImplementation(() => selectQueryBuilder)
selectQueryBuilder.where = jest.fn().mockReturnThis()
selectQueryBuilder.orderBy = jest.fn().mockReturnThis()
selectQueryBuilder.getMany = jest.fn().mockReturnValue([subscription])
const result = await createRepository().findBySubscriptionIdAndType(123, UserSubscriptionType.Regular)
expect(selectQueryBuilder.where).toHaveBeenCalledWith(
'subscription_id = :subscriptionId AND subscription_type = :type',
{
subscriptionId: 123,
type: 'regular',
},
)
expect(selectQueryBuilder.orderBy).toHaveBeenCalledWith('created_at', 'DESC')
expect(selectQueryBuilder.getMany).toHaveBeenCalled()
expect(result).toEqual([subscription])
})
it('should find one subscription by id and user uuid', async () => {
ormRepository.createQueryBuilder = jest.fn().mockImplementation(() => selectQueryBuilder)
selectQueryBuilder.where = jest.fn().mockReturnThis()
selectQueryBuilder.getOne = jest.fn().mockReturnValue(subscription)
const result = await createRepository().findOneByUserUuidAndSubscriptionId('1-2-3', 5)
expect(selectQueryBuilder.where).toHaveBeenCalledWith(
'user_uuid = :userUuid AND subscription_id = :subscriptionId',
{
subscriptionId: 5,
userUuid: '1-2-3',
},
)
expect(selectQueryBuilder.getOne).toHaveBeenCalled()
expect(result).toEqual(subscription)
})
it('should find one subscription by uuid', async () => {
ormRepository.createQueryBuilder = jest.fn().mockImplementation(() => selectQueryBuilder)
selectQueryBuilder.where = jest.fn().mockReturnThis()
selectQueryBuilder.getOne = jest.fn().mockReturnValue(subscription)
const result = await createRepository().findOneByUuid('1-2-3')
expect(selectQueryBuilder.where).toHaveBeenCalledWith('uuid = :uuid', {
uuid: '1-2-3',
})
expect(selectQueryBuilder.getOne).toHaveBeenCalled()
expect(result).toEqual(subscription)
})
})

View File

@@ -1,92 +0,0 @@
import 'reflect-metadata'
import * as IORedis from 'ioredis'
import { LockRepository } from './LockRepository'
describe('LockRepository', () => {
let redisClient: IORedis.Redis
const maxLoginAttempts = 3
const failedLoginLockout = 120
const createRepository = () => new LockRepository(redisClient, maxLoginAttempts, failedLoginLockout)
beforeEach(() => {
redisClient = {} as jest.Mocked<IORedis.Redis>
redisClient.expire = jest.fn()
redisClient.del = jest.fn()
redisClient.get = jest.fn()
redisClient.setex = jest.fn()
})
it('should lock a successfully used OTP for the lockout period', async () => {
await createRepository().lockSuccessfullOTP('test@test.te', '123456')
expect(redisClient.setex).toHaveBeenCalledWith('otp-lock:test@test.te', 60, '123456')
})
it('should indicate if an OTP was already used in the lockout period', async () => {
redisClient.get = jest.fn().mockReturnValue('123456')
expect(await createRepository().isOTPLocked('test@test.te', '123456')).toEqual(true)
})
it('should indicate if an OTP was not already used in the lockout period', async () => {
redisClient.get = jest.fn().mockReturnValue('654321')
expect(await createRepository().isOTPLocked('test@test.te', '123456')).toEqual(false)
})
it('should lock a user for the lockout period', async () => {
await createRepository().lockUser('123')
expect(redisClient.expire).toHaveBeenCalledWith('lock:123', 120)
})
it('should tell a user is locked if his counter is above threshold', async () => {
redisClient.get = jest.fn().mockReturnValue('4')
expect(await createRepository().isUserLocked('123')).toBeTruthy()
})
it('should tell a user is locked if his counter is at the threshold', async () => {
redisClient.get = jest.fn().mockReturnValue('3')
expect(await createRepository().isUserLocked('123')).toBeTruthy()
})
it('should tell a user is not locked if his counter is below threshold', async () => {
redisClient.get = jest.fn().mockReturnValue('2')
expect(await createRepository().isUserLocked('123')).toBeFalsy()
})
it('should tell a user is not locked if he has no counter', async () => {
redisClient.get = jest.fn().mockReturnValue(null)
expect(await createRepository().isUserLocked('123')).toBeFalsy()
})
it('should tell what the user lock counter is', async () => {
redisClient.get = jest.fn().mockReturnValue('3')
expect(await createRepository().getLockCounter('123')).toStrictEqual(3)
})
it('should tell that the user lock counter is 0 when there is no counter', async () => {
redisClient.get = jest.fn().mockReturnValue(null)
expect(await createRepository().getLockCounter('123')).toStrictEqual(0)
})
it('should reset a lock counter', async () => {
await createRepository().resetLockCounter('123')
expect(redisClient.del).toHaveBeenCalledWith('lock:123')
})
it('should update a lock counter', async () => {
await createRepository().updateLockCounter('123', 3)
expect(redisClient.setex).toHaveBeenCalledWith('lock:123', 120, 3)
})
})

View File

@@ -1,152 +0,0 @@
import 'reflect-metadata'
import * as IORedis from 'ioredis'
import { RedisEphemeralSessionRepository } from './RedisEphemeralSessionRepository'
import { EphemeralSession } from '../../Domain/Session/EphemeralSession'
describe('RedisEphemeralSessionRepository', () => {
let redisClient: IORedis.Redis
let pipeline: IORedis.Pipeline
const createRepository = () => new RedisEphemeralSessionRepository(redisClient, 3600)
beforeEach(() => {
redisClient = {} as jest.Mocked<IORedis.Redis>
redisClient.get = jest.fn()
redisClient.smembers = jest.fn()
pipeline = {} as jest.Mocked<IORedis.Pipeline>
pipeline.setex = jest.fn()
pipeline.expire = jest.fn()
pipeline.sadd = jest.fn()
pipeline.del = jest.fn()
pipeline.srem = jest.fn()
pipeline.exec = jest.fn()
redisClient.pipeline = jest.fn().mockReturnValue(pipeline)
})
it('should delete an ephemeral', async () => {
await createRepository().deleteOne('1-2-3', '2-3-4')
expect(pipeline.del).toHaveBeenCalledWith('session:1-2-3:2-3-4')
expect(pipeline.del).toHaveBeenCalledWith('session:1-2-3')
expect(pipeline.srem).toHaveBeenCalledWith('user-sessions:2-3-4', '1-2-3')
})
it('should save an ephemeral session', async () => {
const ephemeralSession = new EphemeralSession()
ephemeralSession.uuid = '1-2-3'
ephemeralSession.userUuid = '2-3-4'
ephemeralSession.userAgent = 'Mozilla Firefox'
ephemeralSession.createdAt = new Date(1)
ephemeralSession.updatedAt = new Date(2)
await createRepository().save(ephemeralSession)
expect(pipeline.setex).toHaveBeenCalledWith(
'session:1-2-3:2-3-4',
3600,
'{"uuid":"1-2-3","userUuid":"2-3-4","userAgent":"Mozilla Firefox","createdAt":"1970-01-01T00:00:00.001Z","updatedAt":"1970-01-01T00:00:00.002Z"}',
)
expect(pipeline.sadd).toHaveBeenCalledWith('user-sessions:2-3-4', '1-2-3')
expect(pipeline.expire).toHaveBeenCalledWith('user-sessions:2-3-4', 3600)
})
it('should find all ephemeral sessions by user uuid', async () => {
redisClient.smembers = jest.fn().mockReturnValue(['1-2-3', '2-3-4', '3-4-5'])
redisClient.get = jest
.fn()
.mockReturnValueOnce(
'{"uuid":"1-2-3","userUuid":"2-3-4","userAgent":"Mozilla Firefox","createdAt":"1970-01-01T00:00:00.001Z","updatedAt":"1970-01-01T00:00:00.002Z"}',
)
.mockReturnValueOnce(
'{"uuid":"2-3-4","userUuid":"2-3-4","userAgent":"Google Chrome","createdAt":"1970-01-01T00:00:00.001Z","updatedAt":"1970-01-01T00:00:00.002Z"}',
)
.mockReturnValueOnce(null)
const ephemeralSessions = await createRepository().findAllByUserUuid('2-3-4')
expect(ephemeralSessions.length).toEqual(2)
expect(ephemeralSessions[1].userAgent).toEqual('Google Chrome')
})
it('should find an ephemeral session by uuid', async () => {
redisClient.get = jest
.fn()
.mockReturnValue(
'{"uuid":"1-2-3","userUuid":"2-3-4","userAgent":"Mozilla Firefox","createdAt":"1970-01-01T00:00:00.001Z","updatedAt":"1970-01-01T00:00:00.002Z"}',
)
const ephemeralSession = <EphemeralSession>await createRepository().findOneByUuid('1-2-3')
expect(ephemeralSession).not.toBeUndefined()
expect(ephemeralSession.userAgent).toEqual('Mozilla Firefox')
})
it('should find an ephemeral session by uuid and user uuid', async () => {
redisClient.get = jest
.fn()
.mockReturnValue(
'{"uuid":"1-2-3","userUuid":"2-3-4","userAgent":"Mozilla Firefox","createdAt":"1970-01-01T00:00:00.001Z","updatedAt":"1970-01-01T00:00:00.002Z"}',
)
const ephemeralSession = <EphemeralSession>await createRepository().findOneByUuidAndUserUuid('1-2-3', '2-3-4')
expect(ephemeralSession).not.toBeUndefined()
expect(ephemeralSession.userAgent).toEqual('Mozilla Firefox')
})
it('should return undefined if session is not found', async () => {
redisClient.get = jest.fn().mockReturnValue(null)
const ephemeralSession = <EphemeralSession>await createRepository().findOneByUuid('1-2-3')
expect(ephemeralSession).toBeNull()
})
it('should return undefined if ephemeral session is not found', async () => {
redisClient.get = jest.fn().mockReturnValue(null)
const ephemeralSession = <EphemeralSession>await createRepository().findOneByUuidAndUserUuid('1-2-3', '2-3-4')
expect(ephemeralSession).toBeNull()
})
it('should update tokens and expirations dates', async () => {
redisClient.get = jest
.fn()
.mockReturnValue(
'{"uuid":"1-2-3","userUuid":"2-3-4","userAgent":"Mozilla Firefox","createdAt":"1970-01-01T00:00:00.001Z","updatedAt":"1970-01-01T00:00:00.002Z"}',
)
await createRepository().updateTokensAndExpirationDates(
'1-2-3',
'dummy_access_token',
'dummy_refresh_token',
new Date(3),
new Date(4),
)
expect(pipeline.setex).toHaveBeenCalledWith(
'session:1-2-3:2-3-4',
3600,
'{"uuid":"1-2-3","userUuid":"2-3-4","userAgent":"Mozilla Firefox","createdAt":"1970-01-01T00:00:00.001Z","updatedAt":"1970-01-01T00:00:00.002Z","hashedAccessToken":"dummy_access_token","hashedRefreshToken":"dummy_refresh_token","accessExpiration":"1970-01-01T00:00:00.003Z","refreshExpiration":"1970-01-01T00:00:00.004Z"}',
)
})
it('should not update tokens and expirations dates if the ephemeral session does not exist', async () => {
await createRepository().updateTokensAndExpirationDates(
'1-2-3',
'dummy_access_token',
'dummy_refresh_token',
new Date(3),
new Date(4),
)
expect(pipeline.setex).not.toHaveBeenCalled()
})
})

View File

@@ -1,59 +0,0 @@
import 'reflect-metadata'
import * as IORedis from 'ioredis'
import { TimerInterface } from '@standardnotes/time'
import { RedisOfflineSubscriptionTokenRepository } from './RedisOfflineSubscriptionTokenRepository'
import { OfflineSubscriptionToken } from '../../Domain/Auth/OfflineSubscriptionToken'
import { Logger } from 'winston'
describe('RedisOfflineSubscriptionTokenRepository', () => {
let redisClient: IORedis.Redis
let timer: TimerInterface
let logger: Logger
const createRepository = () => new RedisOfflineSubscriptionTokenRepository(redisClient, timer, logger)
beforeEach(() => {
redisClient = {} as jest.Mocked<IORedis.Redis>
redisClient.set = jest.fn()
redisClient.get = jest.fn()
redisClient.expireat = jest.fn()
timer = {} as jest.Mocked<TimerInterface>
timer.convertMicrosecondsToSeconds = jest.fn().mockReturnValue(1)
logger = {} as jest.Mocked<Logger>
logger.debug = jest.fn()
})
it('should get a user uuid in exchange for an dashboard token', async () => {
redisClient.get = jest.fn().mockReturnValue('test@test.com')
expect(await createRepository().getUserEmailByToken('random-string')).toEqual('test@test.com')
expect(redisClient.get).toHaveBeenCalledWith('offline-subscription-token:random-string')
})
it('should return undefined if a user uuid is not exchanged for an dashboard token', async () => {
redisClient.get = jest.fn().mockReturnValue(null)
expect(await createRepository().getUserEmailByToken('random-string')).toBeUndefined()
expect(redisClient.get).toHaveBeenCalledWith('offline-subscription-token:random-string')
})
it('should save an dashboard token', async () => {
const offlineSubscriptionToken: OfflineSubscriptionToken = {
userEmail: 'test@test.com',
token: 'random-string',
expiresAt: 123,
}
await createRepository().save(offlineSubscriptionToken)
expect(redisClient.set).toHaveBeenCalledWith('offline-subscription-token:random-string', 'test@test.com')
expect(redisClient.expireat).toHaveBeenCalledWith('offline-subscription-token:random-string', 1)
})
})

View File

@@ -1,34 +0,0 @@
import 'reflect-metadata'
import * as IORedis from 'ioredis'
import { Logger } from 'winston'
import { RedisPKCERepository } from './RedisPKCERepository'
describe('RedisPKCERepository', () => {
let redisClient: IORedis.Redis
let logger: Logger
const createRepository = () => new RedisPKCERepository(redisClient, logger)
beforeEach(() => {
redisClient = {} as jest.Mocked<IORedis.Redis>
redisClient.setex = jest.fn()
redisClient.del = jest.fn().mockReturnValue(1)
logger = {} as jest.Mocked<Logger>
logger.debug = jest.fn()
})
it('should store a code challenge', async () => {
await createRepository().storeCodeChallenge('test')
expect(redisClient.setex).toHaveBeenCalledWith('pkce:test', 3600, 'test')
})
it('should remove a code challenge and notify of success', async () => {
expect(await createRepository().removeCodeChallenge('test')).toBeTruthy()
expect(redisClient.del).toHaveBeenCalledWith('pkce:test')
})
})

View File

@@ -1,70 +0,0 @@
import 'reflect-metadata'
import * as IORedis from 'ioredis'
import { TimerInterface } from '@standardnotes/time'
import { RedisSubscriptionTokenRepository } from './RedisSubscriptionTokenRepository'
import { SubscriptionToken } from '../../Domain/Subscription/SubscriptionToken'
describe('RedisSubscriptionTokenRepository', () => {
let redisClient: IORedis.Redis
let timer: TimerInterface
const createRepository = () => new RedisSubscriptionTokenRepository(redisClient, timer)
beforeEach(() => {
redisClient = {} as jest.Mocked<IORedis.Redis>
redisClient.set = jest.fn().mockReturnValue('OK')
redisClient.get = jest.fn()
redisClient.expireat = jest.fn().mockReturnValue(1)
timer = {} as jest.Mocked<TimerInterface>
timer.convertMicrosecondsToSeconds = jest.fn().mockReturnValue(1)
})
it('should get a user uuid in exchange for an subscription token', async () => {
redisClient.get = jest.fn().mockReturnValue('1-2-3')
expect(await createRepository().getUserUuidByToken('random-string')).toEqual('1-2-3')
expect(redisClient.get).toHaveBeenCalledWith('subscription-token:random-string')
})
it('should return undefined if a user uuid is not exchanged for an subscription token', async () => {
redisClient.get = jest.fn().mockReturnValue(null)
expect(await createRepository().getUserUuidByToken('random-string')).toBeUndefined()
expect(redisClient.get).toHaveBeenCalledWith('subscription-token:random-string')
})
it('should save an subscription token', async () => {
const subscriptionToken: SubscriptionToken = {
userUuid: '1-2-3',
token: 'random-string',
expiresAt: 123,
}
expect(await createRepository().save(subscriptionToken)).toBeTruthy()
expect(redisClient.set).toHaveBeenCalledWith('subscription-token:random-string', '1-2-3')
expect(redisClient.expireat).toHaveBeenCalledWith('subscription-token:random-string', 1)
})
it('should indicate subscription token was not saved', async () => {
redisClient.set = jest.fn().mockReturnValue(null)
const subscriptionToken: SubscriptionToken = {
userUuid: '1-2-3',
token: 'random-string',
expiresAt: 123,
}
expect(await createRepository().save(subscriptionToken)).toBeFalsy()
expect(redisClient.set).toHaveBeenCalledWith('subscription-token:random-string', '1-2-3')
expect(redisClient.expireat).toHaveBeenCalledWith('subscription-token:random-string', 1)
})
})

View File

@@ -1,53 +0,0 @@
import 'reflect-metadata'
import {
DomainEventPublisherInterface,
UserRolesChangedEvent,
WebSocketMessageRequestedEvent,
} from '@standardnotes/domain-events'
import { RoleName } from '@standardnotes/common'
import { User } from '../../Domain/User/User'
import { WebSocketsClientService } from './WebSocketsClientService'
import { DomainEventFactoryInterface } from '../../Domain/Event/DomainEventFactoryInterface'
describe('WebSocketsClientService', () => {
let user: User
let event: UserRolesChangedEvent
let domainEventFactory: DomainEventFactoryInterface
let domainEventPublisher: DomainEventPublisherInterface
const createService = () => new WebSocketsClientService(domainEventFactory, domainEventPublisher)
beforeEach(() => {
user = {
uuid: '123',
email: 'test@test.com',
roles: Promise.resolve([
{
name: RoleName.ProUser,
},
]),
} as jest.Mocked<User>
event = {} as jest.Mocked<UserRolesChangedEvent>
domainEventFactory = {} as jest.Mocked<DomainEventFactoryInterface>
domainEventFactory.createUserRolesChangedEvent = jest.fn().mockReturnValue(event)
domainEventFactory.createWebSocketMessageRequestedEvent = jest
.fn()
.mockReturnValue({} as jest.Mocked<WebSocketMessageRequestedEvent>)
domainEventPublisher = {} as jest.Mocked<DomainEventPublisherInterface>
domainEventPublisher.publish = jest.fn()
})
it('should request a message about a user role changed', async () => {
await createService().sendUserRolesChangedEvent(user)
expect(domainEventFactory.createUserRolesChangedEvent).toHaveBeenCalledWith('123', 'test@test.com', [
RoleName.ProUser,
])
expect(domainEventPublisher.publish).toHaveBeenCalled()
})
})

View File

@@ -1,47 +0,0 @@
import 'reflect-metadata'
import { Permission } from '../Domain/Permission/Permission'
import { PermissionProjector } from './PermissionProjector'
describe('PermissionProjector', () => {
let permission: Permission
const createProjector = () => new PermissionProjector()
beforeEach(() => {
permission = new Permission()
permission.uuid = '123'
permission.name = 'permission1'
permission.createdAt = new Date(1)
permission.updatedAt = new Date(2)
})
it('should create a simple projection', () => {
const projection = createProjector().projectSimple(permission)
expect(projection).toMatchObject({
uuid: '123',
name: 'permission1',
})
})
it('should throw error on custom projection', () => {
let error = null
try {
createProjector().projectCustom('test', permission)
} catch (e) {
error = e
}
expect(error).not.toBeNull()
})
it('should throw error on not implemetned full projection', () => {
let error = null
try {
createProjector().projectFull(permission)
} catch (e) {
error = e
}
expect(error).not.toBeNull()
})
})

View File

@@ -1,47 +0,0 @@
import 'reflect-metadata'
import { Role } from '../Domain/Role/Role'
import { RoleProjector } from './RoleProjector'
describe('RoleProjector', () => {
let role: Role
const createProjector = () => new RoleProjector()
beforeEach(() => {
role = new Role()
role.uuid = '123'
role.name = 'role1'
role.createdAt = new Date(1)
role.updatedAt = new Date(2)
})
it('should create a simple projection', () => {
const projection = createProjector().projectSimple(role)
expect(projection).toMatchObject({
uuid: '123',
name: 'role1',
})
})
it('should throw error on custom projection', () => {
let error = null
try {
createProjector().projectCustom('test', role)
} catch (e) {
error = e
}
expect(error).not.toBeNull()
})
it('should throw error on not implemetned full projection', () => {
let error = null
try {
createProjector().projectFull(role)
} catch (e) {
error = e
}
expect(error).not.toBeNull()
})
})

View File

@@ -1,109 +0,0 @@
import 'reflect-metadata'
import { SessionServiceInterface } from '../Domain/Session/SessionServiceInterface'
import { SessionProjector } from './SessionProjector'
import { Session } from '../Domain/Session/Session'
import { TimerInterface } from '@standardnotes/time'
describe('SessionProjector', () => {
let session: Session
let currentSession: Session
let sessionService: SessionServiceInterface
let timer: TimerInterface
const createProjector = () => new SessionProjector(sessionService, timer)
beforeEach(() => {
session = new Session()
session.uuid = '123'
session.hashedAccessToken = 'hashed access token'
session.userUuid = '234'
session.apiVersion = '004'
session.createdAt = new Date(1)
session.updatedAt = new Date(1)
session.accessExpiration = new Date(1)
session.refreshExpiration = new Date(1)
session.readonlyAccess = false
currentSession = new Session()
currentSession.uuid = '234'
sessionService = {} as jest.Mocked<SessionServiceInterface>
sessionService.getDeviceInfo = jest.fn().mockReturnValue('Some Device Info')
timer = {} as jest.Mocked<TimerInterface>
timer.convertDateToISOString = jest.fn().mockReturnValue('2020-11-26T13:34:00.000Z')
})
it('should create a simple projection of a session', () => {
const projection = createProjector().projectSimple(session)
expect(projection).toMatchObject({
uuid: '123',
api_version: '004',
created_at: '2020-11-26T13:34:00.000Z',
updated_at: '2020-11-26T13:34:00.000Z',
device_info: 'Some Device Info',
readonly_access: false,
access_expiration: '2020-11-26T13:34:00.000Z',
refresh_expiration: '2020-11-26T13:34:00.000Z',
})
})
it('should create a custom projection of a session', () => {
const projection = createProjector().projectCustom(
SessionProjector.CURRENT_SESSION_PROJECTION.toString(),
session,
currentSession,
)
expect(projection).toMatchObject({
uuid: '123',
api_version: '004',
created_at: '2020-11-26T13:34:00.000Z',
updated_at: '2020-11-26T13:34:00.000Z',
device_info: 'Some Device Info',
current: false,
readonly_access: false,
})
})
it('should create a custom projection of a current session', () => {
currentSession.uuid = '123'
const projection = createProjector().projectCustom(
SessionProjector.CURRENT_SESSION_PROJECTION.toString(),
session,
currentSession,
)
expect(projection).toMatchObject({
uuid: '123',
api_version: '004',
created_at: '2020-11-26T13:34:00.000Z',
updated_at: '2020-11-26T13:34:00.000Z',
device_info: 'Some Device Info',
current: true,
readonly_access: false,
})
})
it('should throw error on unknown custom projection', () => {
let error = null
try {
createProjector().projectCustom('test', session, currentSession)
} catch (e) {
error = e
}
expect((error as Error).message).toEqual('Not supported projection type: test')
})
it('should throw error on not implemetned full projection', () => {
let error = null
try {
createProjector().projectFull(session)
} catch (e) {
error = e
}
expect((error as Error).message).toEqual('not implemented')
})
})

View File

@@ -1,48 +0,0 @@
import 'reflect-metadata'
import { Setting } from '../Domain/Setting/Setting'
import { SettingProjector } from './SettingProjector'
describe('SettingProjector', () => {
let setting: Setting
const createProjector = () => new SettingProjector()
beforeEach(() => {
setting = {
uuid: 'setting-uuid',
name: 'setting-name',
value: 'setting-value',
serverEncryptionVersion: 1,
createdAt: 1,
updatedAt: 2,
sensitive: false,
} as jest.Mocked<Setting>
})
it('should create a simple projection of a setting', async () => {
const projection = await createProjector().projectSimple(setting)
expect(projection).toStrictEqual({
uuid: 'setting-uuid',
name: 'setting-name',
value: 'setting-value',
createdAt: 1,
updatedAt: 2,
sensitive: false,
})
})
it('should create a simple projection of list of settings', async () => {
const projection = await createProjector().projectManySimple([setting])
expect(projection).toStrictEqual([
{
uuid: 'setting-uuid',
name: 'setting-name',
value: 'setting-value',
createdAt: 1,
updatedAt: 2,
sensitive: false,
},
])
})
})

View File

@@ -1,48 +0,0 @@
import 'reflect-metadata'
import { SubscriptionSetting } from '../Domain/Setting/SubscriptionSetting'
import { SubscriptionSettingProjector } from './SubscriptionSettingProjector'
describe('SubscriptionSettingProjector', () => {
let setting: SubscriptionSetting
const createProjector = () => new SubscriptionSettingProjector()
beforeEach(() => {
setting = {
uuid: 'setting-uuid',
name: 'setting-name',
value: 'setting-value',
serverEncryptionVersion: 1,
createdAt: 1,
updatedAt: 2,
sensitive: false,
} as jest.Mocked<SubscriptionSetting>
})
it('should create a simple projection of a setting', async () => {
const projection = await createProjector().projectSimple(setting)
expect(projection).toStrictEqual({
uuid: 'setting-uuid',
name: 'setting-name',
value: 'setting-value',
createdAt: 1,
updatedAt: 2,
sensitive: false,
})
})
it('should create a simple projection of list of settings', async () => {
const projection = await createProjector().projectManySimple([setting])
expect(projection).toStrictEqual([
{
uuid: 'setting-uuid',
name: 'setting-name',
value: 'setting-value',
createdAt: 1,
updatedAt: 2,
sensitive: false,
},
])
})
})

View File

@@ -1,45 +0,0 @@
import 'reflect-metadata'
import { UserProjector } from './UserProjector'
import { User } from '../Domain/User/User'
describe('UserProjector', () => {
let user: User
const createProjector = () => new UserProjector()
beforeEach(() => {
user = new User()
user.uuid = '123'
user.email = 'test@test.te'
user.encryptedPassword = '123qwe345'
})
it('should create a simple projection of a user', () => {
const projection = createProjector().projectSimple(user)
expect(projection).toMatchObject({
uuid: '123',
email: 'test@test.te',
})
})
it('should throw error on custom projection', () => {
let error = null
try {
createProjector().projectCustom('test', user)
} catch (e) {
error = e
}
expect(error).not.toBeNull()
})
it('should throw error on not implemetned full projection', () => {
let error = null
try {
createProjector().projectFull(user)
} catch (e) {
error = e
}
expect(error).not.toBeNull()
})
})

View File

@@ -3,6 +3,36 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.9.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-core@1.8.0...@standardnotes/domain-core@1.9.0) (2022-12-07)
### Features
* **domain-core:** rename email subscription rejection level to email level ([c87561f](https://github.com/standardnotes/server/commit/c87561fca782883b84f58b4f0b9f85ecc279ca50))
# [1.8.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-core@1.7.0...@standardnotes/domain-core@1.8.0) (2022-12-05)
### Features
* **domain-core:** add email subscription rejection levels ([02f3c85](https://github.com/standardnotes/server/commit/02f3c85796ade7cb69edbdda2367c0d91ac1bdf0))
# [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

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/domain-core",
"version": "1.5.1",
"version": "1.9.0",
"engines": {
"node": ">=18.0.0 <19.0.0"
},

View File

@@ -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()
})

View File

@@ -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 }))
}
}

View File

@@ -1,4 +1,4 @@
export interface TimestampsProps {
export interface DatesProps {
createdAt: Date
updatedAt: Date
}

View File

@@ -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()
})

View File

@@ -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 }))
}
}

View File

@@ -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()
})
})

View File

@@ -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',

View File

@@ -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()
})
})

View File

@@ -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 }))
}
}

View File

@@ -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()
})
})

View File

@@ -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 }))
}
}

View File

@@ -0,0 +1,3 @@
export interface UsernameProps {
value: string
}

View File

@@ -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()
}
})
})

View File

@@ -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()
}
}

View File

@@ -0,0 +1,18 @@
import { EmailLevel } from './EmailLevel'
describe('EmailLevel', () => {
it('should create a value object', () => {
const valueOrError = EmailLevel.create(EmailLevel.LEVELS.SignIn)
expect(valueOrError.isFailed()).toBeFalsy()
expect(valueOrError.getValue().value).toEqual('SIGN_IN')
})
it('should not create an invalid value object', () => {
for (const value of ['', undefined, null, 0, 'FOOBAR']) {
const valueOrError = EmailLevel.create(value as string)
expect(valueOrError.isFailed()).toBeTruthy()
}
})
})

View File

@@ -0,0 +1,31 @@
import { Result } from '../Core/Result'
import { ValueObject } from '../Core/ValueObject'
import { EmailLevelProps } from './EmailLevelProps'
export class EmailLevel extends ValueObject<EmailLevelProps> {
static readonly LEVELS = {
System: 'SYSTEM',
SignIn: 'SIGN_IN',
Marketing: 'MARKETING',
FailedCloudBackup: 'FAILED_CLOUD_BACKUP',
FailedEmailBackup: 'FAILED_EMAIL_BACKUP',
}
get value(): string {
return this.props.value
}
private constructor(props: EmailLevelProps) {
super(props)
}
static create(name: string): Result<EmailLevel> {
const isValidName = Object.values(this.LEVELS).includes(name)
if (!isValidName) {
return Result.fail<EmailLevel>(`Invalid subscription rejection level: ${name}`)
} else {
return Result.ok<EmailLevel>(new EmailLevel({ value: name }))
}
}
}

View File

@@ -0,0 +1,3 @@
export interface EmailLevelProps {
value: string
}

View File

@@ -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()
}
})
})

View File

@@ -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 }))
}
}
}

View File

@@ -0,0 +1,3 @@
export interface SubscriptionPlanNameProps {
value: string
}

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'
@@ -18,6 +20,12 @@ export * from './Core/Validator'
export * from './Core/ValueObject'
export * from './Core/ValueObjectProps'
export * from './Email/EmailLevel'
export * from './Email/EmailLevelProps'
export * from './Mapping/MapperInterface'
export * from './Subscription/SubscriptionPlanName'
export * from './Subscription/SubscriptionPlanNameProps'
export * from './UseCase/UseCaseInterface'

View File

@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.9.38](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.9.37...@standardnotes/domain-events-infra@1.9.38) (2022-12-06)
**Note:** Version bump only for package @standardnotes/domain-events-infra
## [1.9.37](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.9.36...@standardnotes/domain-events-infra@1.9.37) (2022-12-05)
**Note:** Version bump only for package @standardnotes/domain-events-infra
## [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

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/domain-events-infra",
"version": "1.9.34",
"version": "1.9.38",
"engines": {
"node": ">=18.0.0 <19.0.0"
},

View File

@@ -3,6 +3,30 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [2.94.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.93.0...@standardnotes/domain-events@2.94.0) (2022-12-06)
### Features
* **domain-events:** add mute emails setting changed event ([b7fb1d9](https://github.com/standardnotes/server/commit/b7fb1d9c08f66b0366f9af9cea8241f4c5ea9a18))
# [2.93.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.92.0...@standardnotes/domain-events@2.93.0) (2022-12-05)
### Features
* **domain-events:** add email subscription sync requested event ([fddf9fc](https://github.com/standardnotes/server/commit/fddf9fccbd92fa4279e97bcd2420ec9e270fedbb))
# [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

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/domain-events",
"version": "2.90.2",
"version": "2.94.0",
"engines": {
"node": ">=18.0.0 <19.0.0"
},

View File

@@ -0,0 +1,8 @@
import { DomainEventInterface } from './DomainEventInterface'
import { EmailSubscriptionSyncRequestedEventPayload } from './EmailSubscriptionSyncRequestedEventPayload'
export interface EmailSubscriptionSyncRequestedEvent extends DomainEventInterface {
type: 'EMAIL_SUBSCRIPTION_SYNC_REQUESTED'
payload: EmailSubscriptionSyncRequestedEventPayload
}

View File

@@ -0,0 +1,9 @@
export interface EmailSubscriptionSyncRequestedEventPayload {
username: string
userUuid: string
subscriptionPlanName: string | null
muteFailedBackupsEmails: boolean
muteFailedCloudBackupsEmails: boolean
muteMarketingEmails: boolean
muteSignInEmails: boolean
}

View File

@@ -0,0 +1,8 @@
import { DomainEventInterface } from './DomainEventInterface'
import { MuteEmailsSettingChangedEventPayload } from './MuteEmailsSettingChangedEventPayload'
export interface MuteEmailsSettingChangedEvent extends DomainEventInterface {
type: 'MUTE_EMAILS_SETTING_CHANGED'
payload: MuteEmailsSettingChangedEventPayload
}

View File

@@ -0,0 +1,5 @@
export interface MuteEmailsSettingChangedEventPayload {
username: string
mute: boolean
emailSubscriptionRejectionLevel: string
}

View File

@@ -0,0 +1,7 @@
import { DomainEventInterface } from './DomainEventInterface'
import { RevisionsCopyRequestedEventPayload } from './RevisionsCopyRequestedEventPayload'
export interface RevisionsCopyRequestedEvent extends DomainEventInterface {
type: 'REVISIONS_COPY_REQUESTED'
payload: RevisionsCopyRequestedEventPayload
}

View File

@@ -0,0 +1,4 @@
export interface RevisionsCopyRequestedEventPayload {
newItemUuid: string
originalItemUuid: string
}

View File

@@ -0,0 +1,7 @@
import { DomainEventInterface } from './DomainEventInterface'
import { RevisionsOwnershipUpdateRequestedEventPayload } from './RevisionsOwnershipUpdateRequestedEventPayload'
export interface RevisionsOwnershipUpdateRequestedEvent extends DomainEventInterface {
type: 'REVISIONS_OWNERSHIP_UPDATE_REQUESTED'
payload: RevisionsOwnershipUpdateRequestedEventPayload
}

View File

@@ -0,0 +1,4 @@
export interface RevisionsOwnershipUpdateRequestedEventPayload {
itemUuid: string
userUuid: string
}

View File

@@ -30,6 +30,8 @@ export * from './Event/EmailBackupRequestedEvent'
export * from './Event/EmailBackupRequestedEventPayload'
export * from './Event/EmailMessageRequestedEvent'
export * from './Event/EmailMessageRequestedEventPayload'
export * from './Event/EmailSubscriptionSyncRequestedEvent'
export * from './Event/EmailSubscriptionSyncRequestedEventPayload'
export * from './Event/ExitDiscountAppliedEvent'
export * from './Event/ExitDiscountAppliedEventPayload'
export * from './Event/ExitDiscountApplyRequestedEvent'
@@ -58,6 +60,8 @@ export * from './Event/ListedAccountDeletedEvent'
export * from './Event/ListedAccountDeletedEventPayload'
export * from './Event/ListedAccountRequestedEvent'
export * from './Event/ListedAccountRequestedEventPayload'
export * from './Event/MuteEmailsSettingChangedEvent'
export * from './Event/MuteEmailsSettingChangedEventPayload'
export * from './Event/OfflineSubscriptionTokenCreatedEvent'
export * from './Event/OfflineSubscriptionTokenCreatedEventPayload'
export * from './Event/OneDriveBackupFailedEvent'
@@ -74,6 +78,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'

View File

@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.6.34](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.6.33...@standardnotes/event-store@1.6.34) (2022-12-06)
**Note:** Version bump only for package @standardnotes/event-store
## [1.6.33](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.6.32...@standardnotes/event-store@1.6.33) (2022-12-05)
**Note:** Version bump only for package @standardnotes/event-store
## [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

View File

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

View File

@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.8.34](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.8.33...@standardnotes/files-server@1.8.34) (2022-12-06)
**Note:** Version bump only for package @standardnotes/files-server
## [1.8.33](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.8.32...@standardnotes/files-server@1.8.33) (2022-12-05)
**Note:** Version bump only for package @standardnotes/files-server
## [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

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/files-server",
"version": "1.8.30",
"version": "1.8.34",
"engines": {
"node": ">=18.0.0 <19.0.0"
},

View File

@@ -3,6 +3,88 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.9.7](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.9.6...@standardnotes/revisions-server@1.9.7) (2022-12-07)
**Note:** Version bump only for package @standardnotes/revisions-server
## [1.9.6](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.9.5...@standardnotes/revisions-server@1.9.6) (2022-12-06)
**Note:** Version bump only for package @standardnotes/revisions-server
## [1.9.5](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.9.4...@standardnotes/revisions-server@1.9.5) (2022-12-05)
**Note:** Version bump only for package @standardnotes/revisions-server
## [1.9.4](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.9.3...@standardnotes/revisions-server@1.9.4) (2022-12-05)
**Note:** Version bump only for package @standardnotes/revisions-server
## [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
* **revisions:** add fetching single revision ([284561d](https://github.com/standardnotes/server/commit/284561d093eaa6d73af888142583ec705ba18f79))
## [1.4.8](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.4.7...@standardnotes/revisions-server@1.4.8) (2022-11-25)
**Note:** Version bump only for package @standardnotes/revisions-server

View File

@@ -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`)')
}
}

View File

@@ -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')
}
}

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