Compare commits

...

10 Commits

Author SHA1 Message Date
standardci
2e82be47ed chore(release): publish new version
- @standardnotes/analytics@2.10.2
2022-11-14 13:01:32 +00:00
Karol Sójko
15dfd6dcba fix(analytics): imports from domain-core 2022-11-14 13:59:06 +01:00
standardci
dfd38943b0 chore(release): publish new version
- @standardnotes/syncing-server@1.13.11
2022-11-14 12:51:16 +00:00
Karol Sójko
500756d582 fix(syncing-server): decrease logs severity for content recalculation 2022-11-14 13:49:28 +01:00
standardci
f855f541d8 chore(release): publish new version
- @standardnotes/auth-server@1.60.0
2022-11-14 12:48:49 +00:00
Karol Sójko
590ec6643d feat(auth): add content size recalculation procedure trigger 2022-11-14 13:46:40 +01:00
standardci
b9efd35b50 chore(release): publish new version
- @standardnotes/syncing-server@1.13.10
2022-11-14 12:32:48 +00:00
Karol Sójko
3be1bfe58a fix(syncing-server): linter issues 2022-11-14 13:30:41 +01:00
standardci
bfbd2de778 chore(release): publish new version
- @standardnotes/analytics@2.10.1
 - @standardnotes/domain-core@1.1.1
 - @standardnotes/syncing-server@1.13.9
2022-11-14 12:25:09 +00:00
Karol Sójko
50f7ae338a fix(syncing-server): retrieving revisions 2022-11-14 13:23:12 +01:00
42 changed files with 340 additions and 543 deletions

1
.pnp.cjs generated
View File

@@ -3142,6 +3142,7 @@ const RAW_RUNTIME_STATE =
["@newrelic/winston-enricher", "virtual:c66bf20e88479ada0172094776519a9f51acc4731d22079b60a295bcec7ea42d5545cbce58a77a50d932bf953298799135e99707486e343da6d99ba1d167bdbd#npm:4.0.0"],\
["@sentry/node", "npm:7.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/payloads", "npm:1.5.1"],\

View File

@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [2.10.2](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.10.1...@standardnotes/analytics@2.10.2) (2022-11-14)
### Bug Fixes
* **analytics:** imports from domain-core ([15dfd6d](https://github.com/standardnotes/server/commit/15dfd6dcba75a772000eeb01b78a532067b01d5b))
## [2.10.1](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.10.0...@standardnotes/analytics@2.10.1) (2022-11-14)
### Bug Fixes
* **syncing-server:** retrieving revisions ([50f7ae3](https://github.com/standardnotes/server/commit/50f7ae338ad66d3465fa16c31e7c47c57b1e0c3c))
# [2.10.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.9.9...@standardnotes/analytics@2.10.0) (2022-11-14)
### Features

View File

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

View File

@@ -7,6 +7,7 @@ import {
DomainEventMessageHandlerInterface,
DomainEventSubscriberFactoryInterface,
} from '@standardnotes/domain-events'
import { MapInterface } from '@standardnotes/domain-core'
import { Env } from './Env'
import TYPES from './Types'
@@ -47,7 +48,6 @@ import { RefundProcessedEventHandler } from '../Domain/Handler/RefundProcessedEv
import { RevenueModificationRepositoryInterface } from '../Domain/Revenue/RevenueModificationRepositoryInterface'
import { MySQLRevenueModificationRepository } from '../Infra/MySQL/MySQLRevenueModificationRepository'
import { TypeORMRevenueModification } from '../Infra/TypeORM/TypeORMRevenueModification'
import { MapInterface } from '../Domain/Map/MapInterface'
import { RevenueModification } from '../Domain/Revenue/RevenueModification'
import { RevenueModificationMap } from '../Domain/Map/RevenueModificationMap'
import { SaveRevenueModification } from '../Domain/UseCase/SaveRevenueModification/SaveRevenueModification'

View File

@@ -1,6 +1,7 @@
import 'reflect-metadata'
import { SubscriptionName } from '@standardnotes/common'
import { Result } from '@standardnotes/domain-core'
import { SubscriptionCancelledEvent } from '@standardnotes/domain-events'
import { SubscriptionCancelledEventHandler } from './SubscriptionCancelledEventHandler'
@@ -9,7 +10,6 @@ import { AnalyticsStoreInterface } from '../Analytics/AnalyticsStoreInterface'
import { StatisticsMeasure } from '../Statistics/StatisticsMeasure'
import { StatisticsStoreInterface } from '../Statistics/StatisticsStoreInterface'
import { Period } from '../Time/Period'
import { Result } from '../Core/Result'
import { RevenueModification } from '../Revenue/RevenueModification'
import { SaveRevenueModification } from '../UseCase/SaveRevenueModification/SaveRevenueModification'
import { Logger } from 'winston'

View File

@@ -2,13 +2,13 @@ import 'reflect-metadata'
import { SubscriptionName } from '@standardnotes/common'
import { SubscriptionExpiredEvent } from '@standardnotes/domain-events'
import { Result } from '@standardnotes/domain-core'
import { SubscriptionExpiredEventHandler } from './SubscriptionExpiredEventHandler'
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
import { StatisticsStoreInterface } from '../Statistics/StatisticsStoreInterface'
import { AnalyticsStoreInterface } from '../Analytics/AnalyticsStoreInterface'
import { SaveRevenueModification } from '../UseCase/SaveRevenueModification/SaveRevenueModification'
import { Result } from '../Core/Result'
import { RevenueModification } from '../Revenue/RevenueModification'
import { Logger } from 'winston'

View File

@@ -2,6 +2,7 @@ import 'reflect-metadata'
import { SubscriptionName } from '@standardnotes/common'
import { SubscriptionPurchasedEvent } from '@standardnotes/domain-events'
import { Result } from '@standardnotes/domain-core'
import { SubscriptionPurchasedEventHandler } from './SubscriptionPurchasedEventHandler'
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
@@ -9,7 +10,6 @@ import { AnalyticsStoreInterface } from '../Analytics/AnalyticsStoreInterface'
import { StatisticsStoreInterface } from '../Statistics/StatisticsStoreInterface'
import { Period } from '../Time/Period'
import { SaveRevenueModification } from '../UseCase/SaveRevenueModification/SaveRevenueModification'
import { Result } from '../Core/Result'
import { RevenueModification } from '../Revenue/RevenueModification'
import { Logger } from 'winston'

View File

@@ -2,6 +2,7 @@ import 'reflect-metadata'
import { SubscriptionName } from '@standardnotes/common'
import { SubscriptionRefundedEvent } from '@standardnotes/domain-events'
import { Result } from '@standardnotes/domain-core'
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
import { AnalyticsStoreInterface } from '../Analytics/AnalyticsStoreInterface'
@@ -10,7 +11,6 @@ import { SubscriptionRefundedEventHandler } from './SubscriptionRefundedEventHan
import { StatisticsStoreInterface } from '../Statistics/StatisticsStoreInterface'
import { AnalyticsActivity } from '../Analytics/AnalyticsActivity'
import { Period } from '../Time/Period'
import { Result } from '../Core/Result'
import { RevenueModification } from '../Revenue/RevenueModification'
import { SaveRevenueModification } from '../UseCase/SaveRevenueModification/SaveRevenueModification'
import { Logger } from 'winston'

View File

@@ -2,13 +2,13 @@ import 'reflect-metadata'
import { SubscriptionName } from '@standardnotes/common'
import { SubscriptionRenewedEvent } from '@standardnotes/domain-events'
import { Result } from '@standardnotes/domain-core'
import { SubscriptionRenewedEventHandler } from './SubscriptionRenewedEventHandler'
import { GetUserAnalyticsId } from '../UseCase/GetUserAnalyticsId/GetUserAnalyticsId'
import { AnalyticsStoreInterface } from '../Analytics/AnalyticsStoreInterface'
import { SaveRevenueModification } from '../UseCase/SaveRevenueModification/SaveRevenueModification'
import { RevenueModification } from '../Revenue/RevenueModification'
import { Result } from '../Core/Result'
import { Logger } from 'winston'
describe('SubscriptionRenewedEventHandler', () => {

View File

@@ -1,12 +1,11 @@
import { injectable } from 'inversify'
import { Email, UniqueEntityId } from '@standardnotes/domain-core'
import { Email, MapInterface, UniqueEntityId } from '@standardnotes/domain-core'
import { TypeORMRevenueModification } from '../../Infra/TypeORM/TypeORMRevenueModification'
import { MonthlyRevenue } from '../Revenue/MonthlyRevenue'
import { RevenueModification } from '../Revenue/RevenueModification'
import { Subscription } from '../Subscription/Subscription'
import { User } from '../User/User'
import { MapInterface } from './MapInterface'
import { SubscriptionPlanName } from '../Subscription/SubscriptionPlanName'
import { SubscriptionEventType } from '../Subscription/SubscriptionEventType'
@@ -60,7 +59,7 @@ export class RevenueModificationMap implements MapInterface<RevenueModification,
return revenuModificationOrError.getValue()
}
toPersistence(domain: RevenueModification): TypeORMRevenueModification {
toProjection(domain: RevenueModification): TypeORMRevenueModification {
const { subscription, user } = domain.props
const persistence = new TypeORMRevenueModification()
persistence.uuid = domain.id.toString()

View File

@@ -1,4 +1,5 @@
import { Email } from '../Common/Email'
import { Email } from '@standardnotes/domain-core'
import { Subscription } from '../Subscription/Subscription'
import { SubscriptionEventType } from '../Subscription/SubscriptionEventType'
import { SubscriptionPlanName } from '../Subscription/SubscriptionPlanName'

View File

@@ -1,9 +1,8 @@
import 'reflect-metadata'
import { TimerInterface } from '@standardnotes/time'
import { Email, Result, Uuid } from '@standardnotes/domain-core'
import { Email } from '../../Common/Email'
import { Uuid } from '../../Common/Uuid'
import { MonthlyRevenue } from '../../Revenue/MonthlyRevenue'
import { RevenueModification } from '../../Revenue/RevenueModification'
@@ -12,7 +11,6 @@ import { SubscriptionEventType } from '../../Subscription/SubscriptionEventType'
import { SubscriptionPlanName } from '../../Subscription/SubscriptionPlanName'
import { SaveRevenueModification } from './SaveRevenueModification'
import { User } from '../../User/User'
import { Result } from '../../Core/Result'
import { Subscription } from '../../Subscription/Subscription'
describe('SaveRevenueModification', () => {

View File

@@ -1,4 +1,5 @@
import { Email } from '../Common/Email'
import { Email } from '@standardnotes/domain-core'
import { User } from './User'
describe('User', () => {

View File

@@ -1,9 +1,8 @@
import { inject, injectable } from 'inversify'
import { Repository } from 'typeorm'
import { Uuid } from '@standardnotes/domain-core'
import { MapInterface, Uuid } from '@standardnotes/domain-core'
import TYPES from '../../Bootstrap/Types'
import { MapInterface } from '../../Domain/Map/MapInterface'
import { RevenueModification } from '../../Domain/Revenue/RevenueModification'
import { RevenueModificationRepositoryInterface } from '../../Domain/Revenue/RevenueModificationRepositoryInterface'
import { TypeORMRevenueModification } from '../TypeORM/TypeORMRevenueModification'
@@ -52,7 +51,7 @@ export class MySQLRevenueModificationRepository implements RevenueModificationRe
}
async save(revenueModification: RevenueModification): Promise<RevenueModification> {
let persistence = this.revenueModificationMap.toPersistence(revenueModification)
let persistence = this.revenueModificationMap.toProjection(revenueModification)
persistence = await this.ormRepository.save(persistence)

View File

@@ -3,6 +3,12 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.60.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.59.11...@standardnotes/auth-server@1.60.0) (2022-11-14)
### Features
* **auth:** add content size recalculation procedure trigger ([590ec66](https://github.com/standardnotes/server/commit/590ec6643db57adf3e202c6ccab4bac36aae8b59))
## [1.59.11](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.59.10...@standardnotes/auth-server@1.59.11) (2022-11-14)
**Note:** Version bump only for package @standardnotes/auth-server

View File

@@ -0,0 +1,74 @@
import 'reflect-metadata'
import 'newrelic'
import { Logger } from 'winston'
import * as dayjs from 'dayjs'
import * as utc from 'dayjs/plugin/utc'
import { ContainerConfigLoader } from '../src/Bootstrap/Container'
import TYPES from '../src/Bootstrap/Types'
import { Env } from '../src/Bootstrap/Env'
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
import { DomainEventFactoryInterface } from '../src/Domain/Event/DomainEventFactoryInterface'
import { UserRepositoryInterface } from '../src/Domain/User/UserRepositoryInterface'
import { Stream } from 'stream'
const requestRecalculation = async (
userRepository: UserRepositoryInterface,
domainEventFactory: DomainEventFactoryInterface,
domainEventPublisher: DomainEventPublisherInterface,
logger: Logger,
): Promise<void> => {
const stream = await userRepository.streamAll()
return new Promise((resolve, reject) => {
stream
.pipe(
new Stream.Transform({
objectMode: true,
transform: async (rawUserData, _encoding, callback) => {
try {
await domainEventPublisher.publish(
domainEventFactory.createUserContentSizeRecalculationRequestedEvent(rawUserData.user_uuid),
)
} catch (error) {
logger.error(`Could not process user ${rawUserData.user_uuid}: ${(error as Error).message}`)
}
callback()
},
}),
)
.on('finish', resolve)
.on('error', reject)
})
}
const container = new ContainerConfigLoader()
void container.load().then((container) => {
dayjs.extend(utc)
const env: Env = new Env()
env.load()
const logger: Logger = container.get(TYPES.Logger)
logger.info('Starting content size recalculation requests ...')
const userRepository: UserRepositoryInterface = container.get(TYPES.UserRepository)
const domainEventFactory: DomainEventFactoryInterface = container.get(TYPES.DomainEventFactory)
const domainEventPublisher: DomainEventPublisherInterface = container.get(TYPES.DomainEventPublisher)
Promise.resolve(requestRecalculation(userRepository, domainEventFactory, domainEventPublisher, logger))
.then(() => {
logger.info('content size recalculation requesting complete')
process.exit(0)
})
.catch((error) => {
logger.error(`Could not finish content size recalculation requesting : ${error.message}`)
process.exit(1)
})
})

View File

@@ -56,6 +56,11 @@ case "$COMMAND" in
yarn workspace @standardnotes/auth-server email-campaign $MESSAGE_IDENTIFIER
;;
'content-recalculation' )
echo "Starting Content Size Recalculation..."
yarn workspace @standardnotes/auth-server content-recalculation
;;
* )
echo "Unknown command"
;;

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/auth-server",
"version": "1.59.11",
"version": "1.60.0",
"engines": {
"node": ">=18.0.0 <19.0.0"
},
@@ -25,6 +25,7 @@
"daily-backup:google_drive": "yarn node dist/bin/backup.js google_drive daily",
"daily-backup:one_drive": "yarn node dist/bin/backup.js one_drive daily",
"weekly-backup:email": "yarn node dist/bin/backup.js email weekly",
"content-recalculation": "yarn node dist/bin/content.js",
"email-campaign": "yarn node dist/bin/email.js",
"typeorm": "typeorm-ts-node-commonjs",
"upgrade:snjs": "yarn ncu -u '@standardnotes/*'"

View File

@@ -1,406 +0,0 @@
import 'reflect-metadata'
import { EmailMessageIdentifier, ProtocolVersion, RoleName } from '@standardnotes/common'
import { PredicateName, PredicateAuthority, PredicateVerificationResult } from '@standardnotes/predicates'
import { TimerInterface } from '@standardnotes/time'
import { DomainEventFactory } from './DomainEventFactory'
import { InviteeIdentifierType } from '../SharedSubscription/InviteeIdentifierType'
describe('DomainEventFactory', () => {
let timer: TimerInterface
const createFactory = () => new DomainEventFactory(timer)
beforeEach(() => {
timer = {} as jest.Mocked<TimerInterface>
timer.getTimestampInMicroseconds = jest.fn().mockReturnValue(1)
timer.getUTCDate = jest.fn().mockReturnValue(new Date(1))
})
it('should create a EXIT_DISCOUNT_APPLY_REQUESTED event', () => {
expect(
createFactory().createExitDiscountApplyRequestedEvent({
userEmail: 'test@test.te',
discountCode: 'exit-20',
}),
).toEqual({
createdAt: expect.any(Date),
meta: {
correlation: {
userIdentifier: 'test@test.te',
userIdentifierType: 'email',
},
origin: 'auth',
},
payload: {
userEmail: 'test@test.te',
discountCode: 'exit-20',
},
type: 'EXIT_DISCOUNT_APPLY_REQUESTED',
})
})
it('should create a WEB_SOCKET_MESSAGE_REQUESTED event', () => {
expect(
createFactory().createWebSocketMessageRequestedEvent({
userUuid: '1-2-3',
message: 'foobar',
}),
).toEqual({
createdAt: expect.any(Date),
meta: {
correlation: {
userIdentifier: '1-2-3',
userIdentifierType: 'uuid',
},
origin: 'auth',
},
payload: {
userUuid: '1-2-3',
message: 'foobar',
},
type: 'WEB_SOCKET_MESSAGE_REQUESTED',
})
})
it('should create a EMAIL_MESSAGE_REQUESTED event', () => {
expect(
createFactory().createEmailMessageRequestedEvent({
userEmail: 'test@test.te',
messageIdentifier: EmailMessageIdentifier.ENCOURAGE_EMAIL_BACKUPS,
context: {
foo: 'bar',
},
}),
).toEqual({
createdAt: expect.any(Date),
meta: {
correlation: {
userIdentifier: 'test@test.te',
userIdentifierType: 'email',
},
origin: 'auth',
},
payload: {
messageIdentifier: 'ENCOURAGE_EMAIL_BACKUPS',
userEmail: 'test@test.te',
context: {
foo: 'bar',
},
},
type: 'EMAIL_MESSAGE_REQUESTED',
})
})
it('should create a PREDICATE_VERIFIED event', () => {
expect(
createFactory().createPredicateVerifiedEvent({
predicate: {
authority: PredicateAuthority.Auth,
jobUuid: '1-2-3',
name: PredicateName.EmailBackupsEnabled,
},
predicateVerificationResult: PredicateVerificationResult.Affirmed,
userUuid: '2-3-4',
}),
).toEqual({
createdAt: expect.any(Date),
meta: {
correlation: {
userIdentifier: '2-3-4',
userIdentifierType: 'uuid',
},
origin: 'auth',
},
payload: {
predicate: {
authority: 'auth',
jobUuid: '1-2-3',
name: 'email-backups-enabled',
},
predicateVerificationResult: 'affirmed',
},
type: 'PREDICATE_VERIFIED',
})
})
it('should create a SHARED_SUBSCRIPTION_INVITATION_CANCELED event', () => {
expect(
createFactory().createSharedSubscriptionInvitationCanceledEvent({
inviterEmail: 'test@test.te',
inviterSubscriptionId: 1,
inviterSubscriptionUuid: '2-3-4',
inviteeIdentifier: 'invitee@test.te',
inviteeIdentifierType: InviteeIdentifierType.Email,
sharedSubscriptionInvitationUuid: '1-2-3',
}),
).toEqual({
createdAt: expect.any(Date),
meta: {
correlation: {
userIdentifier: 'test@test.te',
userIdentifierType: 'email',
},
origin: 'auth',
},
payload: {
inviterEmail: 'test@test.te',
inviterSubscriptionId: 1,
inviterSubscriptionUuid: '2-3-4',
inviteeIdentifier: 'invitee@test.te',
inviteeIdentifierType: InviteeIdentifierType.Email,
sharedSubscriptionInvitationUuid: '1-2-3',
},
type: 'SHARED_SUBSCRIPTION_INVITATION_CANCELED',
})
})
it('should create a SHARED_SUBSCRIPTION_INVITATION_CREATED event', () => {
expect(
createFactory().createSharedSubscriptionInvitationCreatedEvent({
inviterEmail: 'test@test.te',
inviterSubscriptionId: 1,
inviteeIdentifier: 'invitee@test.te',
inviteeIdentifierType: InviteeIdentifierType.Email,
sharedSubscriptionInvitationUuid: '1-2-3',
}),
).toEqual({
createdAt: expect.any(Date),
meta: {
correlation: {
userIdentifier: 'test@test.te',
userIdentifierType: 'email',
},
origin: 'auth',
},
payload: {
inviterEmail: 'test@test.te',
inviterSubscriptionId: 1,
inviteeIdentifier: 'invitee@test.te',
inviteeIdentifierType: InviteeIdentifierType.Email,
sharedSubscriptionInvitationUuid: '1-2-3',
},
type: 'SHARED_SUBSCRIPTION_INVITATION_CREATED',
})
})
it('should create a USER_DISABLED_SESSION_USER_AGENT_LOGGING event', () => {
expect(
createFactory().createUserDisabledSessionUserAgentLoggingEvent({
email: 'test@test.te',
userUuid: '1-2-3',
}),
).toEqual({
createdAt: expect.any(Date),
meta: {
correlation: {
userIdentifier: '1-2-3',
userIdentifierType: 'uuid',
},
origin: 'auth',
},
payload: {
userUuid: '1-2-3',
email: 'test@test.te',
},
type: 'USER_DISABLED_SESSION_USER_AGENT_LOGGING',
})
})
it('should create a USER_SIGNED_IN event', () => {
expect(
createFactory().createUserSignedInEvent({
browser: 'Firefox 1',
device: 'iOS 1',
userEmail: 'test@test.te',
userUuid: '1-2-3',
signInAlertEnabled: true,
muteSignInEmailsSettingUuid: '2-3-4',
}),
).toEqual({
createdAt: expect.any(Date),
meta: {
correlation: {
userIdentifier: '1-2-3',
userIdentifierType: 'uuid',
},
origin: 'auth',
},
payload: {
userUuid: '1-2-3',
userEmail: 'test@test.te',
browser: 'Firefox 1',
device: 'iOS 1',
signInAlertEnabled: true,
muteSignInEmailsSettingUuid: '2-3-4',
},
type: 'USER_SIGNED_IN',
})
})
it('should create a LISTED_ACCOUNT_REQUESTED event', () => {
expect(createFactory().createListedAccountRequestedEvent('1-2-3', 'test@test.te')).toEqual({
createdAt: expect.any(Date),
meta: {
correlation: {
userIdentifier: '1-2-3',
userIdentifierType: 'uuid',
},
origin: 'auth',
},
payload: {
userUuid: '1-2-3',
userEmail: 'test@test.te',
},
type: 'LISTED_ACCOUNT_REQUESTED',
})
})
it('should create a USER_REGISTERED event', () => {
expect(
createFactory().createUserRegisteredEvent({
userUuid: '1-2-3',
email: 'test@test.te',
protocolVersion: ProtocolVersion.V004,
}),
).toEqual({
createdAt: expect.any(Date),
meta: {
correlation: {
userIdentifier: '1-2-3',
userIdentifierType: 'uuid',
},
origin: 'auth',
},
payload: {
userUuid: '1-2-3',
email: 'test@test.te',
protocolVersion: '004',
},
type: 'USER_REGISTERED',
})
})
it('should create a OFFLINE_SUBSCRIPTION_TOKEN_CREATED event', () => {
expect(createFactory().createOfflineSubscriptionTokenCreatedEvent('1-2-3', 'test@test.te')).toEqual({
createdAt: expect.any(Date),
meta: {
correlation: {
userIdentifier: 'test@test.te',
userIdentifierType: 'email',
},
origin: 'auth',
},
payload: {
token: '1-2-3',
email: 'test@test.te',
},
type: 'OFFLINE_SUBSCRIPTION_TOKEN_CREATED',
})
})
it('should create a USER_CHANGED_EMAIL event', () => {
expect(createFactory().createUserEmailChangedEvent('1-2-3', 'test@test.te', 'test2@test.te')).toEqual({
createdAt: expect.any(Date),
meta: {
correlation: {
userIdentifier: '1-2-3',
userIdentifierType: 'uuid',
},
origin: 'auth',
},
payload: {
userUuid: '1-2-3',
fromEmail: 'test@test.te',
toEmail: 'test2@test.te',
},
type: 'USER_EMAIL_CHANGED',
})
})
it('should create a CLOUD_BACKUP_REQUESTED event', () => {
expect(createFactory().createCloudBackupRequestedEvent('GOOGLE_DRIVE', 'test', '1-2-3', '2-3-4', true)).toEqual({
createdAt: expect.any(Date),
meta: {
correlation: {
userIdentifier: '1-2-3',
userIdentifierType: 'uuid',
},
origin: 'auth',
},
payload: {
cloudProvider: 'GOOGLE_DRIVE',
cloudProviderToken: 'test',
userUuid: '1-2-3',
muteEmailsSettingUuid: '2-3-4',
userHasEmailsMuted: true,
},
type: 'CLOUD_BACKUP_REQUESTED',
})
})
it('should create a EMAIL_BACKUP_REQUESTED event', () => {
expect(createFactory().createEmailBackupRequestedEvent('1-2-3', '2-3-4', true)).toEqual({
createdAt: expect.any(Date),
meta: {
correlation: {
userIdentifier: '1-2-3',
userIdentifierType: 'uuid',
},
origin: 'auth',
},
payload: {
userUuid: '1-2-3',
muteEmailsSettingUuid: '2-3-4',
userHasEmailsMuted: true,
},
type: 'EMAIL_BACKUP_REQUESTED',
})
})
it('should create a ACCOUNT_DELETION_REQUESTED event', () => {
expect(
createFactory().createAccountDeletionRequestedEvent({
userUuid: '1-2-3',
userCreatedAtTimestamp: 123,
regularSubscriptionUuid: '2-3-4',
}),
).toEqual({
createdAt: expect.any(Date),
meta: {
correlation: {
userIdentifier: '1-2-3',
userIdentifierType: 'uuid',
},
origin: 'auth',
},
payload: {
userUuid: '1-2-3',
userCreatedAtTimestamp: 123,
regularSubscriptionUuid: '2-3-4',
},
type: 'ACCOUNT_DELETION_REQUESTED',
})
})
it('should create a USER_ROLE_CHANGED event', () => {
expect(createFactory().createUserRolesChangedEvent('1-2-3', 'test@test.com', [RoleName.ProUser])).toEqual({
createdAt: expect.any(Date),
meta: {
correlation: {
userIdentifier: '1-2-3',
userIdentifierType: 'uuid',
},
origin: 'auth',
},
payload: {
userUuid: '1-2-3',
email: 'test@test.com',
currentRoles: [RoleName.ProUser],
timestamp: expect.any(Number),
},
type: 'USER_ROLES_CHANGED',
})
})
})

View File

@@ -1,3 +1,5 @@
/* istanbul ignore file */
import { EmailMessageIdentifier, JSONString, ProtocolVersion, RoleName, Uuid } from '@standardnotes/common'
import {
AccountDeletionRequestedEvent,
@@ -17,6 +19,7 @@ import {
EmailMessageRequestedEvent,
WebSocketMessageRequestedEvent,
ExitDiscountApplyRequestedEvent,
UserContentSizeRecalculationRequestedEvent,
} from '@standardnotes/domain-events'
import { Predicate, PredicateVerificationResult } from '@standardnotes/predicates'
import { TimerInterface } from '@standardnotes/time'
@@ -29,6 +32,23 @@ import { DomainEventFactoryInterface } from './DomainEventFactoryInterface'
export class DomainEventFactory implements DomainEventFactoryInterface {
constructor(@inject(TYPES.Timer) private timer: TimerInterface) {}
createUserContentSizeRecalculationRequestedEvent(userUuid: string): UserContentSizeRecalculationRequestedEvent {
return {
type: 'USER_CONTENT_SIZE_RECALCULATION_REQUESTED',
createdAt: this.timer.getUTCDate(),
meta: {
correlation: {
userIdentifier: userUuid,
userIdentifierType: 'uuid',
},
origin: DomainEventService.Auth,
},
payload: {
userUuid,
},
}
}
createExitDiscountApplyRequestedEvent(dto: {
userEmail: string
discountCode: string

View File

@@ -17,10 +17,12 @@ import {
EmailMessageRequestedEvent,
WebSocketMessageRequestedEvent,
ExitDiscountApplyRequestedEvent,
UserContentSizeRecalculationRequestedEvent,
} from '@standardnotes/domain-events'
import { InviteeIdentifierType } from '../SharedSubscription/InviteeIdentifierType'
export interface DomainEventFactoryInterface {
createUserContentSizeRecalculationRequestedEvent(userUuid: string): UserContentSizeRecalculationRequestedEvent
createWebSocketMessageRequestedEvent(dto: { userUuid: Uuid; message: JSONString }): WebSocketMessageRequestedEvent
createEmailMessageRequestedEvent(dto: {
userEmail: string

View File

@@ -3,6 +3,12 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.1.1](https://github.com/standardnotes/server/compare/@standardnotes/domain-core@1.1.0...@standardnotes/domain-core@1.1.1) (2022-11-14)
### Bug Fixes
* **syncing-server:** retrieving revisions ([50f7ae3](https://github.com/standardnotes/server/commit/50f7ae338ad66d3465fa16c31e7c47c57b1e0c3c))
# 1.1.0 (2022-11-14)
### Features

View File

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

View File

@@ -1,4 +1,4 @@
export interface MapInterface<T, U> {
toDomain(persistence: U): T
toPersistence(domain: T): U
toProjection(domain: T): U
}

View File

@@ -10,3 +10,5 @@ export * from './Core/Result'
export * from './Core/UniqueEntityId'
export * from './Core/ValueObject'
export * from './Core/ValueObjectProps'
export * from './Map/MapInterface'

View File

@@ -3,6 +3,24 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.13.11](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.13.10...@standardnotes/syncing-server@1.13.11) (2022-11-14)
### Bug Fixes
* **syncing-server:** decrease logs severity for content recalculation ([500756d](https://github.com/standardnotes/syncing-server-js/commit/500756d58239ea4f639362542476827f9faaa88b))
## [1.13.10](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.13.9...@standardnotes/syncing-server@1.13.10) (2022-11-14)
### Bug Fixes
* **syncing-server:** linter issues ([3be1bfe](https://github.com/standardnotes/syncing-server-js/commit/3be1bfe58a0dcdda4f593cf5327426cbdcee7c45))
## [1.13.9](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.13.8...@standardnotes/syncing-server@1.13.9) (2022-11-14)
### Bug Fixes
* **syncing-server:** retrieving revisions ([50f7ae3](https://github.com/standardnotes/syncing-server-js/commit/50f7ae338ad66d3465fa16c31e7c47c57b1e0c3c))
## [1.13.8](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.13.7...@standardnotes/syncing-server@1.13.8) (2022-11-14)
**Note:** Version bump only for package @standardnotes/syncing-server

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/syncing-server",
"version": "1.13.8",
"version": "1.13.11",
"engines": {
"node": ">=18.0.0 <19.0.0"
},
@@ -16,6 +16,7 @@
"setup:env": "cp .env.sample .env",
"build": "tsc --build",
"lint": "eslint . --ext .ts",
"lint:fix": "eslint . --ext .ts --fix",
"pretest": "yarn lint && yarn build",
"test": "jest --coverage --config=./jest.config.js --maxWorkers=50%",
"start": "yarn node dist/bin/server.js",
@@ -28,6 +29,7 @@
"@newrelic/winston-enricher": "^4.0.0",
"@sentry/node": "^7.19.0",
"@standardnotes/common": "workspace:*",
"@standardnotes/domain-core": "workspace:^",
"@standardnotes/domain-events": "workspace:*",
"@standardnotes/domain-events-infra": "workspace:*",
"@standardnotes/payloads": "^1.5.1",

View File

@@ -80,6 +80,10 @@ import { RevisionRepositoryInterface } from '../Domain/Revision/RevisionReposito
import { ItemRepositoryInterface } from '../Domain/Item/ItemRepositoryInterface'
import { Repository } from 'typeorm'
import { UserContentSizeRecalculationRequestedEventHandler } from '../Domain/Handler/UserContentSizeRecalculationRequestedEventHandler'
import { RevisionMetadataMap } from '../Domain/Map/RevisionMetadataMap'
import { MapInterface } from '@standardnotes/domain-core'
import { RevisionMetadata } from '../Domain/Revision/RevisionMetadata'
import { SimpleRevisionProjection } from '../Projection/SimpleRevisionProjection'
// eslint-disable-next-line @typescript-eslint/no-var-requires
const newrelicFormatter = require('@newrelic/winston-enricher')
@@ -225,6 +229,11 @@ export class ContainerConfigLoader {
.bind<UserContentSizeRecalculationRequestedEventHandler>(TYPES.UserContentSizeRecalculationRequestedEventHandler)
.to(UserContentSizeRecalculationRequestedEventHandler)
// Map
container
.bind<MapInterface<RevisionMetadata, SimpleRevisionProjection>>(TYPES.RevisionMetadataMap)
.to(RevisionMetadataMap)
// Services
container.bind<ContentDecoder>(TYPES.ContentDecoder).to(ContentDecoder)
container.bind<DomainEventFactoryInterface>(TYPES.DomainEventFactory).to(DomainEventFactory)

View File

@@ -49,6 +49,8 @@ const TYPES = {
EmailBackupRequestedEventHandler: Symbol.for('EmailBackupRequestedEventHandler'),
CloudBackupRequestedEventHandler: Symbol.for('CloudBackupRequestedEventHandler'),
UserContentSizeRecalculationRequestedEventHandler: Symbol.for('UserContentSizeRecalculationRequestedEventHandler'),
// Map
RevisionMetadataMap: Symbol.for('RevisionMetadataMap'),
// Services
ContentDecoder: Symbol.for('ContentDecoder'),
DomainEventPublisher: Symbol.for('DomainEventPublisher'),

View File

@@ -8,23 +8,32 @@ import { results } from 'inversify-express-utils'
import { ProjectorInterface } from '../Projection/ProjectorInterface'
import { RevisionServiceInterface } from '../Domain/Revision/RevisionServiceInterface'
import { RevisionProjection } from '../Projection/RevisionProjection'
import { MapInterface } from '@standardnotes/domain-core'
import { RevisionMetadata } from '../Domain/Revision/RevisionMetadata'
import { SimpleRevisionProjection } from '../Projection/SimpleRevisionProjection'
describe('RevisionsController', () => {
let revisionProjector: ProjectorInterface<Revision, RevisionProjection>
let revisionMap: MapInterface<RevisionMetadata, SimpleRevisionProjection>
let revisionService: RevisionServiceInterface
let revision: Revision
let revisionMetadata: RevisionMetadata
let request: express.Request
let response: express.Response
const createController = () => new RevisionsController(revisionService, revisionProjector)
const createController = () => new RevisionsController(revisionService, revisionProjector, revisionMap)
beforeEach(() => {
revision = {} as jest.Mocked<Revision>
revisionMetadata = {} as jest.Mocked<RevisionMetadata>
revisionMap = {} as jest.Mocked<MapInterface<RevisionMetadata, SimpleRevisionProjection>>
revisionProjector = {} as jest.Mocked<ProjectorInterface<Revision, RevisionProjection>>
revisionService = {} as jest.Mocked<RevisionServiceInterface>
revisionService.getRevisions = jest.fn().mockReturnValue([revision])
revisionService.getRevisionsMetadata = jest.fn().mockReturnValue([revisionMetadata])
revisionService.getRevision = jest.fn().mockReturnValue(revision)
revisionService.removeRevision = jest.fn().mockReturnValue(true)
@@ -42,7 +51,7 @@ describe('RevisionsController', () => {
})
it('should return revisions for an item', async () => {
revisionProjector.projectSimple = jest.fn().mockReturnValue({ foo: 'bar' })
revisionMap.toProjection = jest.fn().mockReturnValue({ foo: 'bar' })
const revisionResponse = await createController().getRevisions(request, response)

View File

@@ -8,26 +8,31 @@ import { Revision } from '../Domain/Revision/Revision'
import { RevisionServiceInterface } from '../Domain/Revision/RevisionServiceInterface'
import { ErrorTag } from '@standardnotes/common'
import { RevisionProjection } from '../Projection/RevisionProjection'
import { MapInterface } from '@standardnotes/domain-core'
import { RevisionMetadata } from '../Domain/Revision/RevisionMetadata'
import { SimpleRevisionProjection } from '../Projection/SimpleRevisionProjection'
@controller('/items/:itemUuid/revisions', TYPES.AuthMiddleware)
export class RevisionsController extends BaseHttpController {
constructor(
@inject(TYPES.RevisionService) private revisionService: RevisionServiceInterface,
@inject(TYPES.RevisionProjector) private revisionProjector: ProjectorInterface<Revision, RevisionProjection>,
@inject(TYPES.RevisionMetadataMap)
private revisionMetadataMap: MapInterface<RevisionMetadata, SimpleRevisionProjection>,
) {
super()
}
@httpGet('/')
public async getRevisions(req: Request, response: Response): Promise<results.JsonResult> {
const revisions = await this.revisionService.getRevisions(response.locals.user.uuid, req.params.itemUuid)
const metadatas = await this.revisionService.getRevisionsMetadata(response.locals.user.uuid, req.params.itemUuid)
const revisionProjections = []
for (const revision of revisions) {
revisionProjections.push(await this.revisionProjector.projectSimple(revision))
const metadataProjections = []
for (const metadata of metadatas) {
metadataProjections.push(this.revisionMetadataMap.toProjection(metadata))
}
return this.json(revisionProjections)
return this.json(metadataProjections)
}
@httpGet('/:uuid')

View File

@@ -19,7 +19,7 @@ export class UserContentSizeRecalculationRequestedEventHandler implements Domain
) {}
async handle(event: UserContentSizeRecalculationRequestedEvent): Promise<void> {
this.logger.info(`Starting content size recalculation for user: ${event.payload.userUuid}`)
this.logger.debug(`Starting content size recalculation for user: ${event.payload.userUuid}`)
const stream = await this.itemRepository.streamAll({
deleted: false,
@@ -41,7 +41,7 @@ export class UserContentSizeRecalculationRequestedEventHandler implements Domain
return
}
loggerHandle.info(`Fixing content size for item ${item.item_uuid}`)
loggerHandle.debug(`Fixing content size for item ${item.item_uuid}`)
const modelItem = await this.itemRepository.findByUuid(item.item_uuid)
if (modelItem !== null) {
@@ -49,7 +49,7 @@ export class UserContentSizeRecalculationRequestedEventHandler implements Domain
JSON.stringify(await this.itemProjector.projectFull(modelItem)),
)
if (modelItem.contentSize !== fixedContentSize) {
loggerHandle.info(`Fixing content size from ${modelItem.contentSize} to ${fixedContentSize}`)
loggerHandle.debug(`Fixing content size from ${modelItem.contentSize} to ${fixedContentSize}`)
modelItem.contentSize = fixedContentSize

View File

@@ -0,0 +1,44 @@
/* istanbul ignore file */
import { ContentType } from '@standardnotes/common'
import { MapInterface, UniqueEntityId } from '@standardnotes/domain-core'
import { TimerInterface } from '@standardnotes/time'
import { inject, injectable } from 'inversify'
import TYPES from '../../Bootstrap/Types'
import { SimpleRevisionProjection } from '../../Projection/SimpleRevisionProjection'
import { RevisionMetadata } from '../Revision/RevisionMetadata'
import { RevisionServiceInterface } from '../Revision/RevisionServiceInterface'
@injectable()
export class RevisionMetadataMap implements MapInterface<RevisionMetadata, SimpleRevisionProjection> {
constructor(
@inject(TYPES.RevisionService) private revisionService: RevisionServiceInterface,
@inject(TYPES.Timer) private timer: TimerInterface,
) {}
toDomain(persistence: SimpleRevisionProjection): RevisionMetadata {
const revisionMetadatOrError = RevisionMetadata.create(
{
contentType: persistence.content_type,
createdAt: this.timer.convertStringDateToDate(persistence.created_at),
updatedAt: this.timer.convertStringDateToDate(persistence.updated_at),
},
new UniqueEntityId(persistence.uuid),
)
if (revisionMetadatOrError.isFailed()) {
throw new Error(revisionMetadatOrError.getError())
}
return revisionMetadatOrError.getValue()
}
toProjection(domain: RevisionMetadata): SimpleRevisionProjection {
return {
uuid: domain.id.toString(),
content_type: domain.props.contentType as ContentType | null,
required_role: this.revisionService.calculateRequiredRoleBasedOnRevisionDate(domain.props.createdAt),
created_at: this.timer.convertDateToISOString(domain.props.createdAt),
updated_at: this.timer.convertDateToISOString(domain.props.updatedAt),
}
}
}

View File

@@ -0,0 +1,28 @@
import { UniqueEntityId, Entity, Result } from '@standardnotes/domain-core'
import { RevisionMetadataProps } from './RevisionMetadataProps'
export class RevisionMetadata extends Entity<RevisionMetadataProps> {
get id(): UniqueEntityId {
return this._id
}
private constructor(props: RevisionMetadataProps, id?: UniqueEntityId) {
super(props, id)
}
static create(props: RevisionMetadataProps, id?: UniqueEntityId): Result<RevisionMetadata> {
if (!(props.createdAt instanceof Date)) {
return Result.fail<RevisionMetadata>(
`Could not create Revision Metadata. Creation date should be a date object, given: ${props.createdAt}`,
)
}
if (!(props.updatedAt instanceof Date)) {
return Result.fail<RevisionMetadata>(
`Could not create Revision Metadata. Update date should be a date object, given: ${props.updatedAt}`,
)
}
return Result.ok<RevisionMetadata>(new RevisionMetadata(props, id))
}
}

View File

@@ -0,0 +1,5 @@
export interface RevisionMetadataProps {
contentType: string | null
createdAt: Date
updatedAt: Date
}

View File

@@ -1,8 +1,10 @@
import { Revision } from './Revision'
import { RevisionMetadata } from './RevisionMetadata'
export interface RevisionRepositoryInterface {
findByItemId(parameters: { itemUuid: string; afterDate?: Date }): Promise<Array<Revision>>
findOneById(itemId: string, id: string): Promise<Revision | null>
save(revision: Revision): Promise<Revision>
removeByUuid(itemUuid: string, revisionUuid: string): Promise<void>
findMetadataByItemId(itemUuid: string): Promise<Array<RevisionMetadata>>
}

View File

@@ -4,6 +4,7 @@ import { TimerInterface } from '@standardnotes/time'
import { Item } from '../Item/Item'
import { ItemRepositoryInterface } from '../Item/ItemRepositoryInterface'
import { Revision } from './Revision'
import { RevisionMetadata } from './RevisionMetadata'
import { RevisionRepositoryInterface } from './RevisionRepositoryInterface'
import { RevisionService } from './RevisionService'
@@ -50,6 +51,7 @@ describe('RevisionService', () => {
} as jest.Mocked<Revision>
revisionRepository.findByItemId = jest.fn().mockReturnValue([revision1, revision2])
revisionRepository.findMetadataByItemId = jest.fn().mockReturnValue([{} as jest.Mocked<RevisionMetadata>])
revisionRepository.findOneById = jest.fn().mockReturnValue(revision1)
revisionRepository.removeByUuid = jest.fn()
@@ -177,24 +179,24 @@ describe('RevisionService', () => {
).toBeNull()
})
it('should get revisions for an item', async () => {
await createService().getRevisions('1-2-3', '2-3-4')
it('should get revisions metadata for an item', async () => {
await createService().getRevisionsMetadata('1-2-3', '2-3-4')
expect(revisionRepository.findByItemId).toHaveBeenCalledWith({ itemUuid: '2-3-4' })
expect(revisionRepository.findMetadataByItemId).toHaveBeenCalledWith('2-3-4')
})
it('should not get revisions for an non existing item', async () => {
it('should not get revisions metadata for an non existing item', async () => {
itemRepository.findByUuid = jest.fn().mockReturnValue(null)
expect(await createService().getRevisions('1-2-3', '2-3-4')).toEqual([])
expect(await createService().getRevisionsMetadata('1-2-3', '2-3-4')).toEqual([])
expect(revisionRepository.findByItemId).not.toHaveBeenCalled()
expect(revisionRepository.findMetadataByItemId).not.toHaveBeenCalled()
})
it("should not get revisions for another user's item", async () => {
expect(await createService().getRevisions('3-4-5', '4-5-6')).toEqual([])
it("should not get revisions metadata for another user's item", async () => {
expect(await createService().getRevisionsMetadata('3-4-5', '4-5-6')).toEqual([])
expect(revisionRepository.findByItemId).not.toHaveBeenCalled()
expect(revisionRepository.findMetadataByItemId).not.toHaveBeenCalled()
})
it('should save a revision for a note item', async () => {

View File

@@ -8,6 +8,7 @@ import { Revision } from './Revision'
import { RevisionRepositoryInterface } from './RevisionRepositoryInterface'
import { RevisionServiceInterface } from './RevisionServiceInterface'
import { ItemRepositoryInterface } from '../Item/ItemRepositoryInterface'
import { RevisionMetadata } from './RevisionMetadata'
@injectable()
export class RevisionService implements RevisionServiceInterface {
@@ -28,15 +29,13 @@ export class RevisionService implements RevisionServiceInterface {
return true
}
async getRevisions(userUuid: string, itemUuid: string): Promise<Revision[]> {
async getRevisionsMetadata(userUuid: string, itemUuid: string): Promise<RevisionMetadata[]> {
const userItem = await this.itemRepository.findByUuid(itemUuid)
if (userItem === null || userItem.userUuid !== userUuid) {
return []
}
const revisions = await this.revisionRepository.findByItemId({ itemUuid })
return revisions
return this.revisionRepository.findMetadataByItemId(itemUuid)
}
async getRevision(dto: {

View File

@@ -1,11 +1,12 @@
import { RoleName } from '@standardnotes/common'
import { Item } from '../Item/Item'
import { Revision } from './Revision'
import { RevisionMetadata } from './RevisionMetadata'
export interface RevisionServiceInterface {
createRevision(item: Item): Promise<void>
copyRevisions(fromItemUuid: string, toItemUuid: string): Promise<void>
getRevisions(userUuid: string, itemUuid: string): Promise<Revision[]>
getRevisionsMetadata(userUuid: string, itemUuid: string): Promise<RevisionMetadata[]>
getRevision(dto: {
userUuid: string
userRoles: RoleName[]

View File

@@ -1,89 +0,0 @@
import 'reflect-metadata'
import { Repository, SelectQueryBuilder } from 'typeorm'
import { Revision } from '../../Domain/Revision/Revision'
import { MySQLRevisionRepository } from './MySQLRevisionRepository'
describe('MySQLRevisionRepository', () => {
let ormRepository: Repository<Revision>
let queryBuilder: SelectQueryBuilder<Revision>
let revision: Revision
const createRepository = () => new MySQLRevisionRepository(ormRepository)
beforeEach(() => {
queryBuilder = {} as jest.Mocked<SelectQueryBuilder<Revision>>
revision = {} as jest.Mocked<Revision>
ormRepository = {} as jest.Mocked<Repository<Revision>>
ormRepository.save = jest.fn()
ormRepository.createQueryBuilder = jest.fn().mockImplementation(() => queryBuilder)
})
it('should save', async () => {
await createRepository().save(revision)
expect(ormRepository.save).toHaveBeenCalledWith(revision)
})
it('should delete a revision for an item', async () => {
queryBuilder.where = jest.fn().mockReturnThis()
queryBuilder.delete = jest.fn().mockReturnThis()
queryBuilder.from = jest.fn().mockReturnThis()
queryBuilder.execute = jest.fn()
await createRepository().removeByUuid('1-2-3', '3-4-5')
expect(queryBuilder.delete).toHaveBeenCalled()
expect(queryBuilder.from).toHaveBeenCalledWith('revisions')
expect(queryBuilder.where).toHaveBeenCalledWith('uuid = :revisionUuid AND item_uuid = :itemUuid', {
itemUuid: '1-2-3',
revisionUuid: '3-4-5',
})
expect(queryBuilder.execute).toHaveBeenCalled()
})
it('should find revisions by item id', async () => {
queryBuilder.where = jest.fn().mockReturnThis()
queryBuilder.orderBy = jest.fn().mockReturnThis()
queryBuilder.getMany = jest.fn().mockReturnValue([revision])
const result = await createRepository().findByItemId({ itemUuid: '123' })
expect(queryBuilder.where).toHaveBeenCalledWith('revision.item_uuid = :item_uuid', { item_uuid: '123' })
expect(queryBuilder.orderBy).toHaveBeenCalledWith('revision.created_at', 'DESC')
expect(result).toEqual([revision])
})
it('should find revisions by item id after certain date', async () => {
queryBuilder.where = jest.fn().mockReturnThis()
queryBuilder.andWhere = jest.fn().mockReturnThis()
queryBuilder.orderBy = jest.fn().mockReturnThis()
queryBuilder.getMany = jest.fn().mockReturnValue([revision])
const result = await createRepository().findByItemId({ itemUuid: '123', afterDate: new Date(2) })
expect(queryBuilder.where).toHaveBeenCalledWith('revision.item_uuid = :item_uuid', { item_uuid: '123' })
expect(queryBuilder.andWhere).toHaveBeenCalledWith('revision.creation_date >= :after_date', {
after_date: new Date(2),
})
expect(queryBuilder.orderBy).toHaveBeenCalledWith('revision.created_at', 'DESC')
expect(result).toEqual([revision])
})
it('should find one revision by id and item id', async () => {
queryBuilder.where = jest.fn().mockReturnThis()
queryBuilder.getOne = jest.fn().mockReturnValue(revision)
const result = await createRepository().findOneById('123', '234')
expect(queryBuilder.where).toHaveBeenCalledWith('revision.uuid = :uuid AND revision.item_uuid = :item_uuid', {
uuid: '234',
item_uuid: '123',
})
expect(result).toEqual(revision)
})
})

View File

@@ -1,7 +1,11 @@
/* istanbul ignore file */
import { UniqueEntityId } from '@standardnotes/domain-core'
import { inject, injectable } from 'inversify'
import { Repository } from 'typeorm'
import TYPES from '../../Bootstrap/Types'
import { Revision } from '../../Domain/Revision/Revision'
import { RevisionMetadata } from '../../Domain/Revision/RevisionMetadata'
import { RevisionRepositoryInterface } from '../../Domain/Revision/RevisionRepositoryInterface'
@injectable()
@@ -36,6 +40,40 @@ export class MySQLRevisionRepository implements RevisionRepositoryInterface {
return queryBuilder.orderBy('revision.created_at', 'DESC').getMany()
}
async findMetadataByItemId(itemUuid: string): Promise<Array<RevisionMetadata>> {
const queryBuilder = this.ormRepository
.createQueryBuilder()
.select('uuid', 'uuid')
.addSelect('content_type', 'contentType')
.addSelect('created_at', 'createdAt')
.addSelect('updated_at', 'updatedAt')
.where('item_uuid = :item_uuid', {
item_uuid: itemUuid,
})
const simplifiedRevisions = await queryBuilder.orderBy('created_at', 'DESC').getRawMany()
const metadata = []
for (const simplifiedRevision of simplifiedRevisions) {
const revisionMetadataOrError = RevisionMetadata.create(
{
contentType: simplifiedRevision.contentType,
updatedAt: simplifiedRevision.updatedAt,
createdAt: simplifiedRevision.createdAt,
},
new UniqueEntityId(simplifiedRevision.uuid),
)
if (revisionMetadataOrError.isFailed()) {
throw new Error(revisionMetadataOrError.getError())
}
metadata.push(revisionMetadataOrError.getValue())
}
return metadata
}
async findOneById(itemId: string, id: string): Promise<Revision | null> {
return this.ormRepository
.createQueryBuilder('revision')

View File

@@ -1979,7 +1979,7 @@ __metadata:
languageName: node
linkType: hard
"@standardnotes/domain-core@workspace:*, @standardnotes/domain-core@workspace:packages/domain-core":
"@standardnotes/domain-core@workspace:*, @standardnotes/domain-core@workspace:^, @standardnotes/domain-core@workspace:packages/domain-core":
version: 0.0.0-use.local
resolution: "@standardnotes/domain-core@workspace:packages/domain-core"
dependencies:
@@ -2365,6 +2365,7 @@ __metadata:
"@newrelic/winston-enricher": "npm:^4.0.0"
"@sentry/node": "npm:^7.19.0"
"@standardnotes/common": "workspace:*"
"@standardnotes/domain-core": "workspace:^"
"@standardnotes/domain-events": "workspace:*"
"@standardnotes/domain-events-infra": "workspace:*"
"@standardnotes/payloads": "npm:^1.5.1"