diff --git a/packages/syncing-server/.env.sample b/packages/syncing-server/.env.sample index 66daf72f1..aa1573c20 100644 --- a/packages/syncing-server/.env.sample +++ b/packages/syncing-server/.env.sample @@ -42,3 +42,6 @@ NEW_RELIC_NO_CONFIG_FILE=true NEW_RELIC_DISTRIBUTED_TRACING_ENABLED=false NEW_RELIC_LOG_ENABLED=false NEW_RELIC_LOG_LEVEL=info + +# (Optional) Revision Dumps +FILE_UPLOAD_PATH= diff --git a/packages/syncing-server/src/Bootstrap/Container.ts b/packages/syncing-server/src/Bootstrap/Container.ts index aa1cb73af..cf06a6cf2 100644 --- a/packages/syncing-server/src/Bootstrap/Container.ts +++ b/packages/syncing-server/src/Bootstrap/Container.ts @@ -85,6 +85,7 @@ import { MapperInterface } from '@standardnotes/domain-core' import { RevisionMetadata } from '../Domain/Revision/RevisionMetadata' import { SimpleRevisionProjection } from '../Projection/SimpleRevisionProjection' import { ItemRevisionCreationRequestedEventHandler } from '../Domain/Handler/ItemRevisionCreationRequestedEventHandler' +import { FSItemBackupService } from '../Infra/FS/FSItemBackupService' // eslint-disable-next-line @typescript-eslint/no-var-requires const newrelicFormatter = require('@newrelic/winston-enricher') @@ -92,6 +93,7 @@ const newrelicFormatter = require('@newrelic/winston-enricher') export class ContainerConfigLoader { private readonly DEFAULT_CONTENT_SIZE_TRANSFER_LIMIT = 10_000_000 private readonly DEFAULT_MAX_ITEMS_LIMIT = 300 + private readonly DEFAULT_FILE_UPLOAD_PATH = `${__dirname}/../../uploads` async load(): Promise { const env: Env = new Env() @@ -203,6 +205,11 @@ export class ContainerConfigLoader { .toConstantValue( env.get('MAX_ITEMS_LIMIT', true) ? +env.get('MAX_ITEMS_LIMIT', true) : this.DEFAULT_MAX_ITEMS_LIMIT, ) + container + .bind(TYPES.FILE_UPLOAD_PATH) + .toConstantValue( + env.get('FILE_UPLOAD_PATH', true) ? env.get('FILE_UPLOAD_PATH', true) : this.DEFAULT_FILE_UPLOAD_PATH, + ) // use cases container.bind(TYPES.SyncItems).to(SyncItems) @@ -252,7 +259,11 @@ export class ContainerConfigLoader { .to(SyncResponseFactoryResolver) container.bind(TYPES.AuthHttpService).to(AuthHttpService) container.bind(TYPES.ExtensionsHttpService).to(ExtensionsHttpService) - container.bind(TYPES.ItemBackupService).to(S3ItemBackupService) + if (env.get('S3_AWS_REGION', true)) { + container.bind(TYPES.ItemBackupService).to(S3ItemBackupService) + } else { + container.bind(TYPES.ItemBackupService).to(FSItemBackupService) + } container.bind(TYPES.RevisionService).to(RevisionService) if (env.get('SNS_TOPIC_ARN', true)) { diff --git a/packages/syncing-server/src/Bootstrap/Types.ts b/packages/syncing-server/src/Bootstrap/Types.ts index d961e76db..30b187a2a 100644 --- a/packages/syncing-server/src/Bootstrap/Types.ts +++ b/packages/syncing-server/src/Bootstrap/Types.ts @@ -37,6 +37,7 @@ const TYPES = { VERSION: Symbol.for('VERSION'), CONTENT_SIZE_TRANSFER_LIMIT: Symbol.for('CONTENT_SIZE_TRANSFER_LIMIT'), MAX_ITEMS_LIMIT: Symbol.for('MAX_ITEMS_LIMIT'), + FILE_UPLOAD_PATH: Symbol.for('FILE_UPLOAD_PATH'), // use cases SyncItems: Symbol.for('SyncItems'), CheckIntegrity: Symbol.for('CheckIntegrity'), diff --git a/packages/syncing-server/src/Infra/FS/FSItemBackupService.ts b/packages/syncing-server/src/Infra/FS/FSItemBackupService.ts new file mode 100644 index 000000000..4d695ff8f --- /dev/null +++ b/packages/syncing-server/src/Infra/FS/FSItemBackupService.ts @@ -0,0 +1,34 @@ +import { KeyParamsData } from '@standardnotes/responses' +import { promises } from 'fs' +import * as uuid from 'uuid' +import { inject, injectable } from 'inversify' + +import TYPES from '../../Bootstrap/Types' +import { Item } from '../../Domain/Item/Item' +import { ItemBackupServiceInterface } from '../../Domain/Item/ItemBackupServiceInterface' +import { ItemProjection } from '../../Projection/ItemProjection' +import { ProjectorInterface } from '../../Projection/ProjectorInterface' + +@injectable() +export class FSItemBackupService implements ItemBackupServiceInterface { + constructor( + @inject(TYPES.FILE_UPLOAD_PATH) private fileUploadPath: string, + @inject(TYPES.ItemProjector) private itemProjector: ProjectorInterface, + ) {} + + async backup(_items: Item[], _authParams: KeyParamsData): Promise { + throw new Error('Method not implemented.') + } + + async dump(item: Item): Promise { + const contents = JSON.stringify({ + item: await this.itemProjector.projectFull(item), + }) + + const path = `${this.fileUploadPath}/dumps/${uuid.v4()}` + + await promises.writeFile(path, contents) + + return path + } +}