mirror of
https://github.com/standardnotes/server
synced 2026-01-31 20:01:14 -05:00
Compare commits
10 Commits
@standardn
...
@standardn
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2e82be47ed | ||
|
|
15dfd6dcba | ||
|
|
dfd38943b0 | ||
|
|
500756d582 | ||
|
|
f855f541d8 | ||
|
|
590ec6643d | ||
|
|
b9efd35b50 | ||
|
|
3be1bfe58a | ||
|
|
bfbd2de778 | ||
|
|
50f7ae338a |
1
.pnp.cjs
generated
1
.pnp.cjs
generated
@@ -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"],\
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/analytics",
|
||||
"version": "2.10.0",
|
||||
"version": "2.10.2",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <19.0.0"
|
||||
},
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Email } from '../Common/Email'
|
||||
import { Email } from '@standardnotes/domain-core'
|
||||
|
||||
import { User } from './User'
|
||||
|
||||
describe('User', () => {
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
74
packages/auth/bin/content.ts
Normal file
74
packages/auth/bin/content.ts
Normal 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)
|
||||
})
|
||||
})
|
||||
@@ -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"
|
||||
;;
|
||||
|
||||
@@ -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/*'"
|
||||
|
||||
@@ -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',
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/domain-core",
|
||||
"version": "1.1.0",
|
||||
"version": "1.1.1",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <19.0.0"
|
||||
},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export interface MapInterface<T, U> {
|
||||
toDomain(persistence: U): T
|
||||
toPersistence(domain: T): U
|
||||
toProjection(domain: T): U
|
||||
}
|
||||
@@ -10,3 +10,5 @@ export * from './Core/Result'
|
||||
export * from './Core/UniqueEntityId'
|
||||
export * from './Core/ValueObject'
|
||||
export * from './Core/ValueObjectProps'
|
||||
|
||||
export * from './Map/MapInterface'
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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'),
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export interface RevisionMetadataProps {
|
||||
contentType: string | null
|
||||
createdAt: Date
|
||||
updatedAt: Date
|
||||
}
|
||||
@@ -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>>
|
||||
}
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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[]
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
})
|
||||
@@ -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')
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user