Compare commits

...

12 Commits

Author SHA1 Message Date
standardci
47119fb346 chore(release): publish new version
- @standardnotes/analytics@2.11.6
 - @standardnotes/api-gateway@1.38.9
 - @standardnotes/auth-server@1.60.4
 - @standardnotes/common@1.46.0
 - @standardnotes/domain-core@1.2.2
 - @standardnotes/domain-events-infra@1.9.31
 - @standardnotes/domain-events@2.90.1
 - @standardnotes/event-store@1.6.26
 - @standardnotes/files-server@1.8.26
 - @standardnotes/predicates@1.6.1
 - @standardnotes/revisions-server@1.2.2
 - @standardnotes/scheduler-server@1.13.27
 - @standardnotes/security@1.7.1
 - @standardnotes/syncing-server@1.16.1
 - @standardnotes/websockets-server@1.4.28
 - @standardnotes/workspace-server@1.17.26
2022-11-22 07:25:59 +00:00
Karol Sójko
d77eb7f5f1 feat(common): add marketing campaign for black friday 2022 email message identifier 2022-11-22 08:23:32 +01:00
standardci
1b0a2bb34c chore(release): publish new version
- @standardnotes/revisions-server@1.2.1
2022-11-21 13:20:13 +00:00
Karol Sójko
a363039fa1 fix(revisions): add missing worker process 2022-11-21 14:18:16 +01:00
standardci
32c740b58e chore(release): publish new version
- @standardnotes/revisions-server@1.2.0
 - @standardnotes/syncing-server@1.16.0
2022-11-21 11:58:16 +00:00
Karol Sójko
822ee890af feat(revisions): add persisting revisions from s3 dump 2022-11-21 12:56:17 +01:00
standardci
b0406dd8aa chore(release): publish new version
- @standardnotes/analytics@2.11.5
 - @standardnotes/api-gateway@1.38.8
 - @standardnotes/auth-server@1.60.3
 - @standardnotes/domain-events-infra@1.9.30
 - @standardnotes/domain-events@2.90.0
 - @standardnotes/event-store@1.6.25
 - @standardnotes/files-server@1.8.25
 - @standardnotes/revisions-server@1.1.3
 - @standardnotes/scheduler-server@1.13.26
 - @standardnotes/syncing-server@1.15.0
 - @standardnotes/websockets-server@1.4.27
 - @standardnotes/workspace-server@1.17.25
2022-11-21 08:36:38 +00:00
Karol Sójko
8d152ddfcb feat(syncing-server): add creating item dumps for revision service 2022-11-21 09:34:19 +01:00
standardci
1a16d2e4f4 chore(release): publish new version
- @standardnotes/syncing-server@1.14.0
2022-11-21 08:12:48 +00:00
Karol Sójko
1ca8531305 feat(syncing-server): add creating revisions in async way 2022-11-21 09:10:37 +01:00
standardci
6190e7d092 chore(release): publish new version
- @standardnotes/analytics@2.11.4
 - @standardnotes/domain-core@1.2.1
 - @standardnotes/revisions-server@1.1.2
 - @standardnotes/syncing-server@1.13.17
2022-11-21 07:53:58 +00:00
Karol Sójko
a6542dd638 fix(domain-core): remove revisions related models to revisions microservice 2022-11-21 08:51:57 +01:00
70 changed files with 721 additions and 136 deletions

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.11.6](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.11.5...@standardnotes/analytics@2.11.6) (2022-11-22)
**Note:** Version bump only for package @standardnotes/analytics
## [2.11.5](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.11.4...@standardnotes/analytics@2.11.5) (2022-11-21)
**Note:** Version bump only for package @standardnotes/analytics
## [2.11.4](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.11.3...@standardnotes/analytics@2.11.4) (2022-11-21)
**Note:** Version bump only for package @standardnotes/analytics
## [2.11.3](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.11.2...@standardnotes/analytics@2.11.3) (2022-11-18)
**Note:** Version bump only for package @standardnotes/analytics

View File

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

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.38.9](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.38.8...@standardnotes/api-gateway@1.38.9) (2022-11-22)
**Note:** Version bump only for package @standardnotes/api-gateway
## [1.38.8](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.38.7...@standardnotes/api-gateway@1.38.8) (2022-11-21)
**Note:** Version bump only for package @standardnotes/api-gateway
## [1.38.7](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.38.6...@standardnotes/api-gateway@1.38.7) (2022-11-18)
**Note:** Version bump only for package @standardnotes/api-gateway

View File

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

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.60.4](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.60.3...@standardnotes/auth-server@1.60.4) (2022-11-22)
**Note:** Version bump only for package @standardnotes/auth-server
## [1.60.3](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.60.2...@standardnotes/auth-server@1.60.3) (2022-11-21)
**Note:** Version bump only for package @standardnotes/auth-server
## [1.60.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.60.1...@standardnotes/auth-server@1.60.2) (2022-11-18)
**Note:** Version bump only for package @standardnotes/auth-server

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/auth-server",
"version": "1.60.2",
"version": "1.60.4",
"engines": {
"node": ">=18.0.0 <19.0.0"
},

View File

@@ -3,6 +3,12 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.46.0](https://github.com/standardnotes/server/compare/@standardnotes/common@1.45.0...@standardnotes/common@1.46.0) (2022-11-22)
### Features
* **common:** add marketing campaign for black friday 2022 email message identifier ([d77eb7f](https://github.com/standardnotes/server/commit/d77eb7f5f11bcc7cd5c6fa6d20e891b466af7b45))
# [1.45.0](https://github.com/standardnotes/server/compare/@standardnotes/common@1.44.4...@standardnotes/common@1.45.0) (2022-11-14)
### Features

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/common",
"version": "1.45.0",
"version": "1.46.0",
"engines": {
"node": ">=18.0.0 <19.0.0"
},

View File

@@ -18,6 +18,7 @@ export enum EmailMessageIdentifier {
STUDENT_DISCOUNT_REQUESTED = 'STUDENT_DISCOUNT_REQUESTED',
STUDENT_DISCOUNT_APPROVED = 'STUDENT_DISCOUNT_APPROVED',
MARKETING_CAMPAIGN_FILES = 'MARKETING_CAMPAIGN_FILES',
MARKETING_BLACK_FRIDAY_2022 = 'MARKETING_BLACK_FRIDAY_2022',
PAYMENT_FAILED = 'PAYMENT_FAILED',
SEND_INVOICE = 'SEND_INVOICE',
DISCOUNT_NOTICE = 'DISCOUNT_NOTICE',

View File

@@ -3,6 +3,16 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.2.2](https://github.com/standardnotes/server/compare/@standardnotes/domain-core@1.2.1...@standardnotes/domain-core@1.2.2) (2022-11-22)
**Note:** Version bump only for package @standardnotes/domain-core
## [1.2.1](https://github.com/standardnotes/server/compare/@standardnotes/domain-core@1.2.0...@standardnotes/domain-core@1.2.1) (2022-11-21)
### Bug Fixes
* **domain-core:** remove revisions related models to revisions microservice ([a6542dd](https://github.com/standardnotes/server/commit/a6542dd63870a8ada5fd8143d8e2133a570d9329))
# [1.2.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-core@1.1.1...@standardnotes/domain-core@1.2.0) (2022-11-18)
### Features

View File

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

View File

@@ -16,11 +16,4 @@ export * from './Core/ValueObjectProps'
export * from './Mapping/MapperInterface'
export * from './Revision/ContentType'
export * from './Revision/ContentTypeProps'
export * from './Revision/Revision'
export * from './Revision/RevisionMetadata'
export * from './Revision/RevisionMetadataProps'
export * from './Revision/RevisionProps'
export * from './UseCase/UseCaseInterface'

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.9.31](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.9.30...@standardnotes/domain-events-infra@1.9.31) (2022-11-22)
**Note:** Version bump only for package @standardnotes/domain-events-infra
## [1.9.30](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.9.29...@standardnotes/domain-events-infra@1.9.30) (2022-11-21)
**Note:** Version bump only for package @standardnotes/domain-events-infra
## [1.9.29](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.9.28...@standardnotes/domain-events-infra@1.9.29) (2022-11-18)
**Note:** Version bump only for package @standardnotes/domain-events-infra

View File

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

View File

@@ -3,6 +3,16 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [2.90.1](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.90.0...@standardnotes/domain-events@2.90.1) (2022-11-22)
**Note:** Version bump only for package @standardnotes/domain-events
# [2.90.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.89.0...@standardnotes/domain-events@2.90.0) (2022-11-21)
### Features
* **syncing-server:** add creating item dumps for revision service ([8d152dd](https://github.com/standardnotes/server/commit/8d152ddfcb3c88cbbf9df04e3ed6e2c02571d821))
# [2.89.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.88.0...@standardnotes/domain-events@2.89.0) (2022-11-18)
### Features

View File

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

View File

@@ -0,0 +1,7 @@
import { DomainEventInterface } from './DomainEventInterface'
import { ItemDumpedEventPayload } from './ItemDumpedEventPayload'
export interface ItemDumpedEvent extends DomainEventInterface {
type: 'ITEM_DUMPED'
payload: ItemDumpedEventPayload
}

View File

@@ -0,0 +1,3 @@
export interface ItemDumpedEventPayload {
fileDumpPath: string
}

View File

@@ -46,6 +46,8 @@ export * from './Event/GoogleDriveBackupFailedEvent'
export * from './Event/GoogleDriveBackupFailedEventPayload'
export * from './Event/InvoiceGeneratedEvent'
export * from './Event/InvoiceGeneratedEventPayload'
export * from './Event/ItemDumpedEvent'
export * from './Event/ItemDumpedEventPayload'
export * from './Event/ItemRevisionCreationRequestedEvent'
export * from './Event/ItemRevisionCreationRequestedEventPayload'
export * from './Event/ItemsSyncedEvent'

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.6.26](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.6.25...@standardnotes/event-store@1.6.26) (2022-11-22)
**Note:** Version bump only for package @standardnotes/event-store
## [1.6.25](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.6.24...@standardnotes/event-store@1.6.25) (2022-11-21)
**Note:** Version bump only for package @standardnotes/event-store
## [1.6.24](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.6.23...@standardnotes/event-store@1.6.24) (2022-11-18)
**Note:** Version bump only for package @standardnotes/event-store

View File

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

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.8.26](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.8.25...@standardnotes/files-server@1.8.26) (2022-11-22)
**Note:** Version bump only for package @standardnotes/files-server
## [1.8.25](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.8.24...@standardnotes/files-server@1.8.25) (2022-11-21)
**Note:** Version bump only for package @standardnotes/files-server
## [1.8.24](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.8.23...@standardnotes/files-server@1.8.24) (2022-11-18)
**Note:** Version bump only for package @standardnotes/files-server

View File

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

View File

@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.6.1](https://github.com/standardnotes/server/compare/@standardnotes/predicates@1.6.0...@standardnotes/predicates@1.6.1) (2022-11-22)
**Note:** Version bump only for package @standardnotes/predicates
# [1.6.0](https://github.com/standardnotes/server/compare/@standardnotes/predicates@1.5.7...@standardnotes/predicates@1.6.0) (2022-11-14)
### Features

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/predicates",
"version": "1.6.0",
"version": "1.6.1",
"engines": {
"node": ">=18.0.0 <19.0.0"
},

View File

@@ -3,6 +3,32 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.2.2](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.2.1...@standardnotes/revisions-server@1.2.2) (2022-11-22)
**Note:** Version bump only for package @standardnotes/revisions-server
## [1.2.1](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.2.0...@standardnotes/revisions-server@1.2.1) (2022-11-21)
### Bug Fixes
* **revisions:** add missing worker process ([a363039](https://github.com/standardnotes/server/commit/a363039fa1f1c75842d1eaba2a476257eba385f7))
# [1.2.0](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.1.3...@standardnotes/revisions-server@1.2.0) (2022-11-21)
### Features
* **revisions:** add persisting revisions from s3 dump ([822ee89](https://github.com/standardnotes/server/commit/822ee890aff80cd099fc67b778ee02b8e9ef40eb))
## [1.1.3](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.1.2...@standardnotes/revisions-server@1.1.3) (2022-11-21)
**Note:** Version bump only for package @standardnotes/revisions-server
## [1.1.2](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.1.1...@standardnotes/revisions-server@1.1.2) (2022-11-21)
### Bug Fixes
* **domain-core:** remove revisions related models to revisions microservice ([a6542dd](https://github.com/standardnotes/server/commit/a6542dd63870a8ada5fd8143d8e2133a570d9329))
## [1.1.1](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.1.0...@standardnotes/revisions-server@1.1.1) (2022-11-18)
**Note:** Version bump only for package @standardnotes/revisions-server

View File

@@ -0,0 +1,25 @@
import 'reflect-metadata'
import 'newrelic'
import { Logger } from 'winston'
import { ContainerConfigLoader } from '../src/Bootstrap/Container'
import TYPES from '../src/Bootstrap/Types'
import { Env } from '../src/Bootstrap/Env'
import { DomainEventSubscriberFactoryInterface } from '@standardnotes/domain-events'
const container = new ContainerConfigLoader()
void container.load().then((container) => {
const env: Env = new Env()
env.load()
const logger: Logger = container.get(TYPES.Logger)
logger.info('Starting worker...')
const subscriberFactory: DomainEventSubscriberFactoryInterface = container.get(TYPES.DomainEventSubscriberFactory)
subscriberFactory.create().start()
setInterval(() => logger.info('Alive and kicking!'), 20 * 60 * 1000)
})

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/revisions-server",
"version": "1.1.1",
"version": "1.2.2",
"engines": {
"node": ">=18.0.0 <19.0.0"
},

View File

@@ -2,6 +2,7 @@ import * as winston from 'winston'
import Redis from 'ioredis'
import * as AWS from 'aws-sdk'
import { Container } from 'inversify'
import { Repository } from 'typeorm'
import {
DomainEventHandlerInterface,
DomainEventMessageHandlerInterface,
@@ -15,6 +16,7 @@ import {
SQSEventMessageHandler,
SQSNewRelicEventMessageHandler,
} from '@standardnotes/domain-events-infra'
import { MapperInterface } from '@standardnotes/domain-core'
import { Env } from './Env'
import TYPES from './Types'
@@ -25,9 +27,14 @@ import { GetRevisionsMetada } from '../Domain/UseCase/GetRevisionsMetada/GetRevi
import { RevisionRepositoryInterface } from '../Domain/Revision/RevisionRepositoryInterface'
import { MySQLRevisionRepository } from '../Infra/MySQL/MySQLRevisionRepository'
import { RevisionMetadataPersistenceMapper } from '../Mapping/RevisionMetadataPersistenceMapper'
import { MapperInterface, RevisionMetadata } from '@standardnotes/domain-core'
import { TypeORMRevision } from '../Infra/TypeORM/TypeORMRevision'
import { Repository } from 'typeorm'
import { RevisionMetadata } from '../Domain/Revision/RevisionMetadata'
import { Revision } from '../Domain/Revision/Revision'
import { RevisionItemStringMapper } from '../Mapping/RevisionItemStringMapper'
import { RevisionPersistenceMapper } from '../Mapping/RevisionPersistenceMapper'
import { ItemDumpedEventHandler } from '../Domain/Handler/ItemDumpedEventHandler'
import { DumpRepositoryInterface } from '../Domain/Dump/DumpRepositoryInterface'
import { S3DumpRepository } from '../Infra/S3/S3ItemDumpRepository'
// eslint-disable-next-line @typescript-eslint/no-var-requires
const newrelicFormatter = require('@newrelic/winston-enricher')
@@ -87,22 +94,18 @@ export class ContainerConfigLoader {
container
.bind<MapperInterface<RevisionMetadata, TypeORMRevision>>(TYPES.RevisionMetadataPersistenceMapper)
.toConstantValue(new RevisionMetadataPersistenceMapper())
container
.bind<MapperInterface<Revision, TypeORMRevision>>(TYPES.RevisionPersistenceMapper)
.toConstantValue(new RevisionPersistenceMapper())
container
.bind<MapperInterface<Revision, string>>(TYPES.RevisionItemStringMapper)
.toConstantValue(new RevisionItemStringMapper())
// ORM
container
.bind<Repository<TypeORMRevision>>(TYPES.ORMRevisionRepository)
.toConstantValue(AppDataSource.getRepository(TypeORMRevision))
// Repositories
container
.bind<RevisionRepositoryInterface>(TYPES.RevisionRepository)
.toConstantValue(
new MySQLRevisionRepository(
container.get(TYPES.ORMRevisionRepository),
container.get(TYPES.RevisionMetadataPersistenceMapper),
),
)
// env vars
container.bind(TYPES.REDIS_URL).toConstantValue(env.get('REDIS_URL'))
container.bind(TYPES.SQS_QUEUE_URL).toConstantValue(env.get('SQS_QUEUE_URL', true))
@@ -113,6 +116,26 @@ export class ContainerConfigLoader {
container.bind(TYPES.NEW_RELIC_ENABLED).toConstantValue(env.get('NEW_RELIC_ENABLED', true))
container.bind(TYPES.VERSION).toConstantValue(env.get('VERSION'))
// Repositories
container
.bind<RevisionRepositoryInterface>(TYPES.RevisionRepository)
.toConstantValue(
new MySQLRevisionRepository(
container.get(TYPES.ORMRevisionRepository),
container.get(TYPES.RevisionMetadataPersistenceMapper),
container.get(TYPES.RevisionPersistenceMapper),
),
)
container
.bind<DumpRepositoryInterface>(TYPES.DumpRepository)
.toConstantValue(
new S3DumpRepository(
container.get(TYPES.S3_BACKUP_BUCKET_NAME),
container.get(TYPES.S3),
container.get(TYPES.RevisionItemStringMapper),
),
)
// use cases
container
.bind<GetRevisionsMetada>(TYPES.GetRevisionsMetada)
@@ -124,6 +147,11 @@ export class ContainerConfigLoader {
.toConstantValue(new RevisionsController(container.get(TYPES.GetRevisionsMetada), container.get(TYPES.Logger)))
// Handlers
container
.bind<ItemDumpedEventHandler>(TYPES.ItemDumpedEventHandler)
.toConstantValue(
new ItemDumpedEventHandler(container.get(TYPES.DumpRepository), container.get(TYPES.RevisionRepository)),
)
// Services
container
@@ -135,7 +163,9 @@ export class ContainerConfigLoader {
.bind<InversifyExpressApiGatewayAuthMiddleware>(TYPES.ApiGatewayAuthMiddleware)
.to(InversifyExpressApiGatewayAuthMiddleware)
const eventHandlers: Map<string, DomainEventHandlerInterface> = new Map([])
const eventHandlers: Map<string, DomainEventHandlerInterface> = new Map([
['ITEM_DUMPED', container.get(TYPES.ItemDumpedEventHandler)],
])
if (env.get('SQS_QUEUE_URL', true)) {
container

View File

@@ -6,10 +6,13 @@ const TYPES = {
S3: Symbol.for('S3'),
// Map
RevisionMetadataPersistenceMapper: Symbol.for('RevisionMetadataPersistenceMapper'),
RevisionPersistenceMapper: Symbol.for('RevisionPersistenceMapper'),
RevisionItemStringMapper: Symbol.for('RevisionItemStringMapper'),
// ORM
ORMRevisionRepository: Symbol.for('ORMRevisionRepository'),
// Repositories
RevisionRepository: Symbol.for('RevisionRepository'),
DumpRepository: Symbol.for('DumpRepository'),
// env vars
REDIS_URL: Symbol.for('REDIS_URL'),
SQS_QUEUE_URL: Symbol.for('SQS_QUEUE_URL'),
@@ -25,6 +28,7 @@ const TYPES = {
// Controller
RevisionsController: Symbol.for('RevisionsController'),
// Handlers
ItemDumpedEventHandler: Symbol.for('ItemDumpedEventHandler'),
// Services
CrossServiceTokenDecoder: Symbol.for('CrossServiceTokenDecoder'),
DomainEventSubscriberFactory: Symbol.for('DomainEventSubscriberFactory'),

View File

@@ -0,0 +1,6 @@
import { Revision } from '../Revision/Revision'
export interface DumpRepositoryInterface {
getRevisionFromDumpPath(path: string): Promise<Revision | null>
removeDump(path: string): Promise<void>
}

View File

@@ -0,0 +1,46 @@
import { ItemDumpedEvent } from '@standardnotes/domain-events'
import { DumpRepositoryInterface } from '../Dump/DumpRepositoryInterface'
import { Revision } from '../Revision/Revision'
import { RevisionRepositoryInterface } from '../Revision/RevisionRepositoryInterface'
import { ItemDumpedEventHandler } from './ItemDumpedEventHandler'
describe('ItemDumpedEventHandler', () => {
let dumpRepository: DumpRepositoryInterface
let revisionRepository: RevisionRepositoryInterface
let revision: Revision
let event: ItemDumpedEvent
const createHandler = () => new ItemDumpedEventHandler(dumpRepository, revisionRepository)
beforeEach(() => {
revision = {} as jest.Mocked<Revision>
dumpRepository = {} as jest.Mocked<DumpRepositoryInterface>
dumpRepository.getRevisionFromDumpPath = jest.fn().mockReturnValue(revision)
dumpRepository.removeDump = jest.fn()
revisionRepository = {} as jest.Mocked<RevisionRepositoryInterface>
revisionRepository.save = jest.fn()
event = {} as jest.Mocked<ItemDumpedEvent>
event.payload = {
fileDumpPath: 'foobar',
}
})
it('should save a revision from file dump', async () => {
await createHandler().handle(event)
expect(revisionRepository.save).toHaveBeenCalled()
expect(dumpRepository.removeDump).toHaveBeenCalled()
})
it('should not save a revision if it could not be created from dump', async () => {
dumpRepository.getRevisionFromDumpPath = jest.fn().mockReturnValue(null)
await createHandler().handle(event)
expect(revisionRepository.save).not.toHaveBeenCalled()
expect(dumpRepository.removeDump).toHaveBeenCalled()
})
})

View File

@@ -0,0 +1,24 @@
import { DomainEventHandlerInterface, ItemDumpedEvent } from '@standardnotes/domain-events'
import { DumpRepositoryInterface } from '../Dump/DumpRepositoryInterface'
import { RevisionRepositoryInterface } from '../Revision/RevisionRepositoryInterface'
export class ItemDumpedEventHandler implements DomainEventHandlerInterface {
constructor(
private dumpRepository: DumpRepositoryInterface,
private revisionRepository: RevisionRepositoryInterface,
) {}
async handle(event: ItemDumpedEvent): Promise<void> {
const revision = await this.dumpRepository.getRevisionFromDumpPath(event.payload.fileDumpPath)
if (revision === null) {
await this.dumpRepository.removeDump(event.payload.fileDumpPath)
return
}
await this.revisionRepository.save(revision)
await this.dumpRepository.removeDump(event.payload.fileDumpPath)
}
}

View File

@@ -1,7 +1,7 @@
import { ValueObject } from '../Core/ValueObject'
import { Result } from '../Core/Result'
import { ContentTypeProps } from './ContentTypeProps'
import { ContentType as ContentTypeValues } from '@standardnotes/common'
import { Result, ValueObject } from '@standardnotes/domain-core'
import { ContentTypeProps } from './ContentTypeProps'
export class ContentType extends ValueObject<ContentTypeProps> {
get value(): string | null {

View File

@@ -1,6 +1,4 @@
import { Entity } from '../Core/Entity'
import { Result } from '../Core/Result'
import { UniqueEntityId } from '../Core/UniqueEntityId'
import { Entity, Result, UniqueEntityId } from '@standardnotes/domain-core'
import { RevisionProps } from './RevisionProps'

View File

@@ -1,6 +1,4 @@
import { Entity } from '../Core/Entity'
import { Result } from '../Core/Result'
import { UniqueEntityId } from '../Core/UniqueEntityId'
import { Entity, Result, UniqueEntityId } from '@standardnotes/domain-core'
import { RevisionMetadataProps } from './RevisionMetadataProps'

View File

@@ -1,4 +1,4 @@
import { Timestamps } from '../Common/Timestamps'
import { Timestamps } from '@standardnotes/domain-core'
import { ContentType } from './ContentType'

View File

@@ -1,4 +1,5 @@
import { Uuid } from '../Common/Uuid'
import { Timestamps, Uuid } from '@standardnotes/domain-core'
import { ContentType } from './ContentType'
export interface RevisionProps {
@@ -9,6 +10,5 @@ export interface RevisionProps {
encItemKey: string | null
authHash: string | null
creationDate: Date
createdAt: Date
updatedAt: Date
timestamps: Timestamps
}

View File

@@ -1,5 +1,9 @@
import { Uuid, RevisionMetadata } from '@standardnotes/domain-core'
import { Uuid } from '@standardnotes/domain-core'
import { Revision } from './Revision'
import { RevisionMetadata } from './RevisionMetadata'
export interface RevisionRepositoryInterface {
findMetadataByItemId(itemUuid: Uuid): Promise<Array<RevisionMetadata>>
save(revision: Revision): Promise<Revision>
}

View File

@@ -1,5 +1,4 @@
import { RevisionMetadata } from '@standardnotes/domain-core'
import { RevisionMetadata } from '../../Revision/RevisionMetadata'
import { RevisionRepositoryInterface } from '../../Revision/RevisionRepositoryInterface'
import { GetRevisionsMetada } from './GetRevisionsMetada'

View File

@@ -1,5 +1,6 @@
import { Result, RevisionMetadata, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
import { Result, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
import { RevisionMetadata } from '../../Revision/RevisionMetadata'
import { RevisionRepositoryInterface } from '../../Revision/RevisionRepositoryInterface'
import { GetRevisionsMetadaDTO } from './GetRevisionsMetadaDTO'

View File

@@ -1,15 +1,26 @@
import { MapperInterface, RevisionMetadata, Uuid } from '@standardnotes/domain-core'
import { MapperInterface, Uuid } from '@standardnotes/domain-core'
import { Repository } from 'typeorm'
import { Revision } from '../../Domain/Revision/Revision'
import { RevisionMetadata } from '../../Domain/Revision/RevisionMetadata'
import { RevisionRepositoryInterface } from '../../Domain/Revision/RevisionRepositoryInterface'
import { TypeORMRevision } from '../TypeORM/TypeORMRevision'
export class MySQLRevisionRepository implements RevisionRepositoryInterface {
constructor(
private ormRepository: Repository<TypeORMRevision>,
private revisionMapper: MapperInterface<RevisionMetadata, TypeORMRevision>,
private revisionMetadataMapper: MapperInterface<RevisionMetadata, TypeORMRevision>,
private revisionMapper: MapperInterface<Revision, TypeORMRevision>,
) {}
async save(revision: Revision): Promise<Revision> {
const typeormRevision = this.revisionMapper.toProjection(revision)
await this.ormRepository.save(typeormRevision)
return revision
}
async findMetadataByItemId(itemUuid: Uuid): Promise<Array<RevisionMetadata>> {
const queryBuilder = this.ormRepository
.createQueryBuilder()
@@ -26,7 +37,7 @@ export class MySQLRevisionRepository implements RevisionRepositoryInterface {
const metadata = []
for (const simplifiedRevision of simplifiedRevisions) {
metadata.push(this.revisionMapper.toDomain(simplifiedRevision))
metadata.push(this.revisionMetadataMapper.toDomain(simplifiedRevision))
}
return metadata

View File

@@ -0,0 +1,39 @@
import { MapperInterface } from '@standardnotes/domain-core'
import { S3 } from 'aws-sdk'
import { DumpRepositoryInterface } from '../../Domain/Dump/DumpRepositoryInterface'
import { Revision } from '../../Domain/Revision/Revision'
export class S3DumpRepository implements DumpRepositoryInterface {
constructor(
private dumpBucketName: string,
private s3Client: S3,
private revisionStringItemMapper: MapperInterface<Revision, string>,
) {}
async getRevisionFromDumpPath(path: string): Promise<Revision | null> {
const s3Object = await this.s3Client
.getObject({
Bucket: this.dumpBucketName,
Key: path,
})
.promise()
if (s3Object.Body === undefined) {
return null
}
const revision = this.revisionStringItemMapper.toDomain(s3Object.Body as string)
return revision
}
async removeDump(path: string): Promise<void> {
await this.s3Client
.deleteObject({
Bucket: this.dumpBucketName,
Key: path,
})
.promise()
}
}

View File

@@ -1,5 +1,3 @@
import { ContentType } from '@standardnotes/common'
import { Column, Entity, Index, PrimaryGeneratedColumn } from 'typeorm'
@Entity({ name: 'revisions' })
@@ -25,7 +23,7 @@ export class TypeORMRevision {
length: 255,
nullable: true,
})
declare contentType: ContentType | null
declare contentType: string | null
@Column({
type: 'varchar',

View File

@@ -0,0 +1,43 @@
import { MapperInterface, Timestamps, Uuid } from '@standardnotes/domain-core'
import { ContentType } from '../Domain/Revision/ContentType'
import { Revision } from '../Domain/Revision/Revision'
export class RevisionItemStringMapper implements MapperInterface<Revision, string> {
toDomain(projection: string): Revision {
const item = JSON.parse(projection)
const contentTypeOrError = ContentType.create(item.content_type)
if (contentTypeOrError.isFailed()) {
throw new Error(`Could not map item string to revision: ${contentTypeOrError.getError()}`)
}
const contentType = contentTypeOrError.getValue()
const itemUuidOrError = Uuid.create(item.uuid)
if (itemUuidOrError.isFailed()) {
throw new Error(`Could not map item string to revision: ${itemUuidOrError.getError()}`)
}
const itemUuid = itemUuidOrError.getValue()
const revisionOrError = Revision.create({
itemUuid,
authHash: item.auth_hash,
content: item.content,
contentType,
itemsKeyId: item.items_key_id,
encItemKey: item.enc_item_key,
creationDate: new Date(),
timestamps: Timestamps.create(new Date(), new Date()).getValue(),
})
if (revisionOrError.isFailed()) {
throw new Error(`Could not map item string to revision: ${revisionOrError.getError()}`)
}
return revisionOrError.getValue()
}
toProjection(domain: Revision): string {
return JSON.stringify(domain)
}
}

View File

@@ -1,5 +1,7 @@
import { RevisionMetadata, MapperInterface, UniqueEntityId, ContentType, Timestamps } from '@standardnotes/domain-core'
import { MapperInterface, Timestamps, UniqueEntityId } from '@standardnotes/domain-core'
import { ContentType } from '../Domain/Revision/ContentType'
import { RevisionMetadata } from '../Domain/Revision/RevisionMetadata'
import { TypeORMRevision } from '../Infra/TypeORM/TypeORMRevision'
export class RevisionMetadataPersistenceMapper implements MapperInterface<RevisionMetadata, TypeORMRevision> {

View File

@@ -0,0 +1,62 @@
import { MapperInterface, Timestamps, UniqueEntityId, Uuid } from '@standardnotes/domain-core'
import { ContentType } from '../Domain/Revision/ContentType'
import { Revision } from '../Domain/Revision/Revision'
import { TypeORMRevision } from '../Infra/TypeORM/TypeORMRevision'
export class RevisionPersistenceMapper implements MapperInterface<Revision, TypeORMRevision> {
toDomain(projection: TypeORMRevision): Revision {
const contentTypeOrError = ContentType.create(projection.contentType)
if (contentTypeOrError.isFailed()) {
throw new Error(`Could not map typeorm revision to domain revision: ${contentTypeOrError.getError()}`)
}
const contentType = contentTypeOrError.getValue()
const timestampsOrError = Timestamps.create(projection.createdAt, projection.updatedAt)
if (timestampsOrError.isFailed()) {
throw new Error(`Could not map typeorm revision to domain revision: ${timestampsOrError.getError()}`)
}
const timestamps = timestampsOrError.getValue()
const itemUuidOrError = Uuid.create(projection.itemUuid)
if (itemUuidOrError.isFailed()) {
throw new Error(`Could not map typeorm revision to domain revision: ${itemUuidOrError.getError()}`)
}
const itemUuid = itemUuidOrError.getValue()
const revisionOrError = Revision.create(
{
authHash: projection.authHash,
content: projection.content,
contentType,
creationDate: projection.creationDate,
encItemKey: projection.encItemKey,
itemsKeyId: projection.itemsKeyId,
itemUuid,
timestamps,
},
new UniqueEntityId(projection.uuid),
)
if (revisionOrError.isFailed()) {
throw new Error(`Could not map typeorm revision to domain revision: ${revisionOrError.getError()}`)
}
return revisionOrError.getValue()
}
toProjection(domain: Revision): TypeORMRevision {
const typeormRevision = new TypeORMRevision()
typeormRevision.authHash = domain.props.authHash
typeormRevision.content = domain.props.content
typeormRevision.contentType = domain.props.contentType.value
typeormRevision.createdAt = domain.props.timestamps.createdAt
typeormRevision.updatedAt = domain.props.timestamps.updatedAt
typeormRevision.creationDate = domain.props.creationDate
typeormRevision.encItemKey = domain.props.encItemKey
typeormRevision.itemUuid = domain.props.itemUuid.value
typeormRevision.itemsKeyId = domain.props.itemsKeyId
typeormRevision.uuid = domain.id.toString()
return typeormRevision
}
}

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.13.27](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.13.26...@standardnotes/scheduler-server@1.13.27) (2022-11-22)
**Note:** Version bump only for package @standardnotes/scheduler-server
## [1.13.26](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.13.25...@standardnotes/scheduler-server@1.13.26) (2022-11-21)
**Note:** Version bump only for package @standardnotes/scheduler-server
## [1.13.25](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.13.24...@standardnotes/scheduler-server@1.13.25) (2022-11-18)
**Note:** Version bump only for package @standardnotes/scheduler-server

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/scheduler-server",
"version": "1.13.25",
"version": "1.13.27",
"engines": {
"node": ">=18.0.0 <19.0.0"
},

View File

@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.7.1](https://github.com/standardnotes/server/compare/@standardnotes/security@1.7.0...@standardnotes/security@1.7.1) (2022-11-22)
**Note:** Version bump only for package @standardnotes/security
# [1.7.0](https://github.com/standardnotes/server/compare/@standardnotes/security@1.6.4...@standardnotes/security@1.7.0) (2022-11-14)
### Features

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/security",
"version": "1.7.0",
"version": "1.7.1",
"engines": {
"node": ">=18.0.0 <19.0.0"
},

View File

@@ -3,6 +3,32 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.16.1](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.16.0...@standardnotes/syncing-server@1.16.1) (2022-11-22)
**Note:** Version bump only for package @standardnotes/syncing-server
# [1.16.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.15.0...@standardnotes/syncing-server@1.16.0) (2022-11-21)
### Features
* **revisions:** add persisting revisions from s3 dump ([822ee89](https://github.com/standardnotes/syncing-server-js/commit/822ee890aff80cd099fc67b778ee02b8e9ef40eb))
# [1.15.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.14.0...@standardnotes/syncing-server@1.15.0) (2022-11-21)
### Features
* **syncing-server:** add creating item dumps for revision service ([8d152dd](https://github.com/standardnotes/syncing-server-js/commit/8d152ddfcb3c88cbbf9df04e3ed6e2c02571d821))
# [1.14.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.13.17...@standardnotes/syncing-server@1.14.0) (2022-11-21)
### Features
* **syncing-server:** add creating revisions in async way ([1ca8531](https://github.com/standardnotes/syncing-server-js/commit/1ca853130547ebfc26bdd9abce0dfb550e8217f6))
## [1.13.17](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.13.16...@standardnotes/syncing-server@1.13.17) (2022-11-21)
**Note:** Version bump only for package @standardnotes/syncing-server
## [1.13.16](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.13.15...@standardnotes/syncing-server@1.13.16) (2022-11-18)
**Note:** Version bump only for package @standardnotes/syncing-server

View File

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

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/syncing-server",
"version": "1.13.16",
"version": "1.16.1",
"engines": {
"node": ">=18.0.0 <19.0.0"
},

View File

@@ -84,6 +84,7 @@ import { RevisionMetadataMap } from '../Domain/Map/RevisionMetadataMap'
import { MapperInterface } from '@standardnotes/domain-core'
import { RevisionMetadata } from '../Domain/Revision/RevisionMetadata'
import { SimpleRevisionProjection } from '../Projection/SimpleRevisionProjection'
import { ItemRevisionCreationRequestedEventHandler } from '../Domain/Handler/ItemRevisionCreationRequestedEventHandler'
// eslint-disable-next-line @typescript-eslint/no-var-requires
const newrelicFormatter = require('@newrelic/winston-enricher')
@@ -228,6 +229,9 @@ export class ContainerConfigLoader {
container
.bind<UserContentSizeRecalculationRequestedEventHandler>(TYPES.UserContentSizeRecalculationRequestedEventHandler)
.to(UserContentSizeRecalculationRequestedEventHandler)
container
.bind<ItemRevisionCreationRequestedEventHandler>(TYPES.ItemRevisionCreationRequestedEventHandler)
.to(ItemRevisionCreationRequestedEventHandler)
// Map
container
@@ -274,6 +278,7 @@ export class ContainerConfigLoader {
'USER_CONTENT_SIZE_RECALCULATION_REQUESTED',
container.get(TYPES.UserContentSizeRecalculationRequestedEventHandler),
],
['ITEM_REVISION_CREATION_REQUESTED', container.get(TYPES.ItemRevisionCreationRequestedEventHandler)],
])
if (env.get('SQS_QUEUE_URL', true)) {

View File

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

View File

@@ -6,6 +6,8 @@ import {
EmailArchiveExtensionSyncedEvent,
EmailBackupAttachmentCreatedEvent,
GoogleDriveBackupFailedEvent,
ItemDumpedEvent,
ItemRevisionCreationRequestedEvent,
ItemsSyncedEvent,
OneDriveBackupFailedEvent,
UserContentSizeRecalculationRequestedEvent,
@@ -19,6 +21,40 @@ import { DomainEventFactoryInterface } from './DomainEventFactoryInterface'
export class DomainEventFactory implements DomainEventFactoryInterface {
constructor(@inject(TYPES.Timer) private timer: TimerInterface) {}
createItemDumpedEvent(fileDumpPath: string, userUuid: string): ItemDumpedEvent {
return {
type: 'ITEM_DUMPED',
createdAt: this.timer.getUTCDate(),
meta: {
correlation: {
userIdentifier: userUuid,
userIdentifierType: 'uuid',
},
origin: DomainEventService.SyncingServer,
},
payload: {
fileDumpPath,
},
}
}
createItemRevisionCreationRequested(itemUuid: string, userUuid: string): ItemRevisionCreationRequestedEvent {
return {
type: 'ITEM_REVISION_CREATION_REQUESTED',
createdAt: this.timer.getUTCDate(),
meta: {
correlation: {
userIdentifier: userUuid,
userIdentifierType: 'uuid',
},
origin: DomainEventService.SyncingServer,
},
payload: {
itemUuid,
},
}
}
createUserContentSizeRecalculationRequestedEvent(userUuid: string): UserContentSizeRecalculationRequestedEvent {
return {
type: 'USER_CONTENT_SIZE_RECALCULATION_REQUESTED',

View File

@@ -4,6 +4,8 @@ import {
EmailArchiveExtensionSyncedEvent,
EmailBackupAttachmentCreatedEvent,
GoogleDriveBackupFailedEvent,
ItemDumpedEvent,
ItemRevisionCreationRequestedEvent,
ItemsSyncedEvent,
OneDriveBackupFailedEvent,
UserContentSizeRecalculationRequestedEvent,
@@ -31,4 +33,6 @@ export interface DomainEventFactoryInterface {
email: string
}): EmailBackupAttachmentCreatedEvent
createDuplicateItemSyncedEvent(itemUuid: string, userUuid: string): DuplicateItemSyncedEvent
createItemRevisionCreationRequested(itemUuid: string, userUuid: string): ItemRevisionCreationRequestedEvent
createItemDumpedEvent(fileDumpPath: string, userUuid: string): ItemDumpedEvent
}

View File

@@ -0,0 +1,86 @@
import 'reflect-metadata'
import {
DomainEventPublisherInterface,
DomainEventService,
ItemRevisionCreationRequestedEvent,
} from '@standardnotes/domain-events'
import { Item } from '../Item/Item'
import { ItemRepositoryInterface } from '../Item/ItemRepositoryInterface'
import { ItemRevisionCreationRequestedEventHandler } from './ItemRevisionCreationRequestedEventHandler'
import { ItemBackupServiceInterface } from '../Item/ItemBackupServiceInterface'
import { DomainEventFactoryInterface } from '../Event/DomainEventFactoryInterface'
describe('ItemRevisionCreationRequestedEventHandler', () => {
let itemRepository: ItemRepositoryInterface
let event: ItemRevisionCreationRequestedEvent
let item: Item
let itemBackupService: ItemBackupServiceInterface
let domainEventFactory: DomainEventFactoryInterface
let domainEventPublisher: DomainEventPublisherInterface
const createHandler = () =>
new ItemRevisionCreationRequestedEventHandler(
itemRepository,
itemBackupService,
domainEventFactory,
domainEventPublisher,
)
beforeEach(() => {
item = {
uuid: '1-2-3',
content: 'test',
} as jest.Mocked<Item>
itemRepository = {} as jest.Mocked<ItemRepositoryInterface>
itemRepository.findByUuid = jest.fn().mockReturnValue(item)
event = {} as jest.Mocked<ItemRevisionCreationRequestedEvent>
event.createdAt = new Date(1)
event.payload = {
itemUuid: '2-3-4',
}
event.meta = {
correlation: {
userIdentifier: '1-2-3',
userIdentifierType: 'uuid',
},
origin: DomainEventService.SyncingServer,
}
itemBackupService = {} as jest.Mocked<ItemBackupServiceInterface>
itemBackupService.dump = jest.fn().mockReturnValue('foo://bar')
domainEventFactory = {} as jest.Mocked<DomainEventFactoryInterface>
domainEventFactory.createItemDumpedEvent = jest.fn()
domainEventPublisher = {} as jest.Mocked<DomainEventPublisherInterface>
domainEventPublisher.publish = jest.fn()
})
it('should create a revision for an item', async () => {
await createHandler().handle(event)
expect(domainEventPublisher.publish).toHaveBeenCalled()
expect(domainEventFactory.createItemDumpedEvent).toHaveBeenCalled()
})
it('should not create a revision for an item that does not exist', async () => {
itemRepository.findByUuid = jest.fn().mockReturnValue(null)
await createHandler().handle(event)
expect(domainEventPublisher.publish).not.toHaveBeenCalled()
})
it('should not create a revision for an item if the dump was not created', async () => {
itemBackupService.dump = jest.fn().mockReturnValue('')
await createHandler().handle(event)
expect(domainEventPublisher.publish).not.toHaveBeenCalled()
expect(domainEventFactory.createItemDumpedEvent).not.toHaveBeenCalled()
})
})

View File

@@ -0,0 +1,35 @@
import {
ItemRevisionCreationRequestedEvent,
DomainEventHandlerInterface,
DomainEventPublisherInterface,
} from '@standardnotes/domain-events'
import { inject, injectable } from 'inversify'
import TYPES from '../../Bootstrap/Types'
import { DomainEventFactoryInterface } from '../Event/DomainEventFactoryInterface'
import { ItemBackupServiceInterface } from '../Item/ItemBackupServiceInterface'
import { ItemRepositoryInterface } from '../Item/ItemRepositoryInterface'
@injectable()
export class ItemRevisionCreationRequestedEventHandler implements DomainEventHandlerInterface {
constructor(
@inject(TYPES.ItemRepository) private itemRepository: ItemRepositoryInterface,
@inject(TYPES.ItemBackupService) private itemBackupService: ItemBackupServiceInterface,
@inject(TYPES.DomainEventFactory) private domainEventFactory: DomainEventFactoryInterface,
@inject(TYPES.DomainEventPublisher) private domainEventPublisher: DomainEventPublisherInterface,
) {}
async handle(event: ItemRevisionCreationRequestedEvent): Promise<void> {
const item = await this.itemRepository.findByUuid(event.payload.itemUuid)
if (item === null) {
return
}
const fileDumpPath = await this.itemBackupService.dump(item)
if (fileDumpPath) {
await this.domainEventPublisher.publish(
this.domainEventFactory.createItemDumpedEvent(fileDumpPath, event.meta.correlation.userIdentifier),
)
}
}
}

View File

@@ -3,4 +3,5 @@ import { Item } from './Item'
export interface ItemBackupServiceInterface {
backup(items: Array<Item>, authParams: KeyParamsData): Promise<string>
dump(item: Item): Promise<string>
}

View File

@@ -7,7 +7,6 @@ import { ItemHash } from './ItemHash'
import { ItemRepositoryInterface } from './ItemRepositoryInterface'
import { ItemService } from './ItemService'
import { ApiVersion } from '../Api/ApiVersion'
import { RevisionServiceInterface } from '../Revision/RevisionServiceInterface'
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
import { DomainEventFactoryInterface } from '../Event/DomainEventFactoryInterface'
import { Logger } from 'winston'
@@ -21,7 +20,6 @@ import { ItemProjection } from '../../Projection/ItemProjection'
describe('ItemService', () => {
let itemRepository: ItemRepositoryInterface
let revisionService: RevisionServiceInterface
let domainEventPublisher: DomainEventPublisherInterface
let domainEventFactory: DomainEventFactoryInterface
const revisionFrequency = 300
@@ -47,7 +45,6 @@ describe('ItemService', () => {
itemSaveValidator,
itemFactory,
itemRepository,
revisionService,
domainEventPublisher,
domainEventFactory,
revisionFrequency,
@@ -125,9 +122,6 @@ describe('ItemService', () => {
itemRepository.countAll = jest.fn().mockReturnValue(2)
itemRepository.save = jest.fn().mockImplementation((item: Item) => item)
revisionService = {} as jest.Mocked<RevisionServiceInterface>
revisionService.createRevision = jest.fn()
timer = {} as jest.Mocked<TimerInterface>
timer.getTimestampInMicroseconds = jest.fn().mockReturnValue(1616164633241568)
timer.getUTCDate = jest.fn().mockReturnValue(new Date())
@@ -147,6 +141,7 @@ describe('ItemService', () => {
domainEventFactory = {} as jest.Mocked<DomainEventFactoryInterface>
domainEventFactory.createDuplicateItemSyncedEvent = jest.fn()
domainEventFactory.createItemRevisionCreationRequested = jest.fn()
logger = {} as jest.Mocked<Logger>
logger.error = jest.fn()
@@ -491,7 +486,8 @@ describe('ItemService', () => {
syncToken: 'MjpOYU4=',
})
expect(revisionService.createRevision).toHaveBeenCalledTimes(1)
expect(domainEventFactory.createItemRevisionCreationRequested).toHaveBeenCalledTimes(1)
expect(domainEventPublisher.publish).toHaveBeenCalledTimes(1)
})
it('should not save new items in read only access mode', async () => {
@@ -515,8 +511,6 @@ describe('ItemService', () => {
savedItems: [],
syncToken: 'MjoxNjE2MTY0NjMzLjI0MTU2OQ==',
})
expect(revisionService.createRevision).toHaveBeenCalledTimes(0)
})
it('should save new items that are duplicates', async () => {
@@ -538,8 +532,8 @@ describe('ItemService', () => {
syncToken: 'MjoxNjE2MTY0NjMzLjI0MTU3MQ==',
})
expect(revisionService.createRevision).toHaveBeenCalledTimes(1)
expect(domainEventPublisher.publish).toHaveBeenCalledTimes(1)
expect(domainEventFactory.createItemRevisionCreationRequested).toHaveBeenCalledTimes(1)
expect(domainEventPublisher.publish).toHaveBeenCalledTimes(2)
expect(domainEventFactory.createDuplicateItemSyncedEvent).toHaveBeenCalledTimes(1)
})
@@ -933,8 +927,9 @@ describe('ItemService', () => {
],
syncToken: 'MjoxNjE2MTY0NjMzLjI0MTU2OQ==',
})
expect(domainEventPublisher.publish).toHaveBeenCalledTimes(1)
expect(domainEventPublisher.publish).toHaveBeenCalledTimes(2)
expect(domainEventFactory.createDuplicateItemSyncedEvent).toHaveBeenCalledTimes(1)
expect(domainEventFactory.createItemRevisionCreationRequested).toHaveBeenCalledTimes(1)
})
it('should skip saving conflicting items and mark them as sync conflicts when saving to database fails', async () => {

View File

@@ -6,7 +6,6 @@ import { Logger } from 'winston'
import TYPES from '../../Bootstrap/Types'
import { DomainEventFactoryInterface } from '../Event/DomainEventFactoryInterface'
import { RevisionServiceInterface } from '../Revision/RevisionServiceInterface'
import { GetItemsDTO } from './GetItemsDTO'
import { GetItemsResult } from './GetItemsResult'
import { Item } from './Item'
@@ -33,7 +32,6 @@ export class ItemService implements ItemServiceInterface {
@inject(TYPES.ItemSaveValidator) private itemSaveValidator: ItemSaveValidatorInterface,
@inject(TYPES.ItemFactory) private itemFactory: ItemFactoryInterface,
@inject(TYPES.ItemRepository) private itemRepository: ItemRepositoryInterface,
@inject(TYPES.RevisionService) private revisionService: RevisionServiceInterface,
@inject(TYPES.DomainEventPublisher) private domainEventPublisher: DomainEventPublisherInterface,
@inject(TYPES.DomainEventFactory) private domainEventFactory: DomainEventFactoryInterface,
@inject(TYPES.REVISIONS_FREQUENCY) private revisionFrequency: number,
@@ -253,7 +251,9 @@ export class ItemService implements ItemServiceInterface {
const savedItem = await this.itemRepository.save(dto.existingItem)
if (secondsFromLastUpdate >= this.revisionFrequency) {
await this.revisionService.createRevision(savedItem)
await this.domainEventPublisher.publish(
this.domainEventFactory.createItemRevisionCreationRequested(savedItem.uuid, savedItem.userUuid),
)
}
if (wasMarkedAsDuplicate) {
@@ -270,7 +270,9 @@ export class ItemService implements ItemServiceInterface {
const savedItem = await this.itemRepository.save(newItem)
await this.revisionService.createRevision(savedItem)
await this.domainEventPublisher.publish(
this.domainEventFactory.createItemRevisionCreationRequested(savedItem.uuid, savedItem.userUuid),
)
if (savedItem.duplicateOf) {
await this.domainEventPublisher.publish(

View File

@@ -1,59 +0,0 @@
import 'reflect-metadata'
import { KeyParamsData } from '@standardnotes/responses'
import { S3 } from 'aws-sdk'
import { Logger } from 'winston'
import { Item } from '../../Domain/Item/Item'
import { S3ItemBackupService } from './S3ItemBackupService'
import { ProjectorInterface } from '../../Projection/ProjectorInterface'
import { ItemProjection } from '../../Projection/ItemProjection'
describe('S3ItemBackupService', () => {
let s3Client: S3 | undefined
let itemProjector: ProjectorInterface<Item, ItemProjection>
let s3BackupBucketName = 'backup-bucket'
let logger: Logger
let item: Item
let keyParams: KeyParamsData
const createService = () => new S3ItemBackupService(s3BackupBucketName, itemProjector, logger, s3Client)
beforeEach(() => {
s3Client = {} as jest.Mocked<S3>
s3Client.upload = jest.fn().mockReturnValue({
promise: jest.fn().mockReturnValue(Promise.resolve({ Key: 'test' })),
})
logger = {} as jest.Mocked<Logger>
logger.warn = jest.fn()
item = {} as jest.Mocked<Item>
keyParams = {} as jest.Mocked<KeyParamsData>
itemProjector = {} as jest.Mocked<ProjectorInterface<Item, ItemProjection>>
itemProjector.projectFull = jest.fn().mockReturnValue({ foo: 'bar' })
})
it('should upload items to S3 as a backup file', async () => {
await createService().backup([item], keyParams)
expect((<S3>s3Client).upload).toHaveBeenCalledWith({
Body: '{"items":[{"foo":"bar"}],"auth_params":{}}',
Bucket: 'backup-bucket',
Key: expect.any(String),
})
})
it('should not upload items to S3 if bucket name is not configured', async () => {
s3BackupBucketName = ''
await createService().backup([item], keyParams)
expect((<S3>s3Client).upload).not.toHaveBeenCalled()
})
it('should not upload items to S3 if S3 client is not configured', async () => {
s3Client = undefined
expect(await createService().backup([item], keyParams)).toEqual('')
})
})

View File

@@ -3,6 +3,7 @@ import { KeyParamsData } from '@standardnotes/responses'
import { S3 } from 'aws-sdk'
import { inject, injectable } from 'inversify'
import { Logger } from 'winston'
import TYPES from '../../Bootstrap/Types'
import { Item } from '../../Domain/Item/Item'
import { ItemBackupServiceInterface } from '../../Domain/Item/ItemBackupServiceInterface'
@@ -18,6 +19,26 @@ export class S3ItemBackupService implements ItemBackupServiceInterface {
@inject(TYPES.S3) private s3Client?: S3,
) {}
async dump(item: Item): Promise<string> {
if (!this.s3BackupBucketName || this.s3Client === undefined) {
this.logger.warn('S3 backup not configured')
return ''
}
const uploadResult = await this.s3Client
.upload({
Bucket: this.s3BackupBucketName,
Key: uuid.v4(),
Body: JSON.stringify({
item: await this.itemProjector.projectFull(item),
}),
})
.promise()
return uploadResult.Key
}
async backup(items: Item[], authParams: KeyParamsData): Promise<string> {
if (!this.s3BackupBucketName || this.s3Client === undefined) {
this.logger.warn('S3 backup not configured')

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.4.28](https://github.com/standardnotes/server/compare/@standardnotes/websockets-server@1.4.27...@standardnotes/websockets-server@1.4.28) (2022-11-22)
**Note:** Version bump only for package @standardnotes/websockets-server
## [1.4.27](https://github.com/standardnotes/server/compare/@standardnotes/websockets-server@1.4.26...@standardnotes/websockets-server@1.4.27) (2022-11-21)
**Note:** Version bump only for package @standardnotes/websockets-server
## [1.4.26](https://github.com/standardnotes/server/compare/@standardnotes/websockets-server@1.4.25...@standardnotes/websockets-server@1.4.26) (2022-11-18)
**Note:** Version bump only for package @standardnotes/websockets-server

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/websockets-server",
"version": "1.4.26",
"version": "1.4.28",
"engines": {
"node": ">=18.0.0 <19.0.0"
},

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.17.26](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.17.25...@standardnotes/workspace-server@1.17.26) (2022-11-22)
**Note:** Version bump only for package @standardnotes/workspace-server
## [1.17.25](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.17.24...@standardnotes/workspace-server@1.17.25) (2022-11-21)
**Note:** Version bump only for package @standardnotes/workspace-server
## [1.17.24](https://github.com/standardnotes/server/compare/@standardnotes/workspace-server@1.17.23...@standardnotes/workspace-server@1.17.24) (2022-11-18)
**Note:** Version bump only for package @standardnotes/workspace-server

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/workspace-server",
"version": "1.17.24",
"version": "1.17.26",
"engines": {
"node": ">=18.0.0 <19.0.0"
},