Compare commits

...

14 Commits

Author SHA1 Message Date
standardci
0188f290f9 chore(release): publish new version
- @standardnotes/analytics@2.27.2
 - @standardnotes/api-gateway@1.75.7
 - @standardnotes/auth-server@1.148.0
 - @standardnotes/domain-events-infra@1.13.1
 - @standardnotes/domain-events@2.131.1
 - @standardnotes/event-store@1.12.2
 - @standardnotes/files-server@1.25.0
 - @standardnotes/home-server@1.16.9
 - @standardnotes/revisions-server@1.38.2
 - @standardnotes/scheduler-server@1.21.2
 - @standardnotes/security@1.15.0
 - @standardnotes/syncing-server@1.109.2
 - @standardnotes/time@1.17.0
 - @standardnotes/websockets-server@1.11.2
2023-09-28 11:39:49 +00:00
Karol Sójko
676cf36f8d feat: block file operations during transition (#856)
* feat: block file operations during transition

* fix: tracing sessions

* fix fs file removal

* fix: checking if directory exists before listing files

* fix: removing shared vault user on auth side
2023-09-28 13:10:58 +02:00
Karol Sójko
f8aef6c8ef feat: archiving failed run logs 2023-09-27 14:20:34 +02:00
Karol Sójko
5bf8cf49c1 feat: parametrize test suite to be run 2023-09-27 12:14:33 +02:00
standardci
51cd0a4dad chore(release): publish new version
- @standardnotes/analytics@2.27.1
 - @standardnotes/api-gateway@1.75.6
 - @standardnotes/auth-server@1.147.1
 - @standardnotes/domain-core@1.34.1
 - @standardnotes/event-store@1.12.1
 - @standardnotes/files-server@1.24.1
 - @standardnotes/home-server@1.16.8
 - @standardnotes/revisions-server@1.38.1
 - @standardnotes/scheduler-server@1.21.1
 - @standardnotes/settings@1.21.41
 - @standardnotes/syncing-server@1.109.1
 - @standardnotes/websockets-server@1.11.1
2023-09-27 09:45:40 +00:00
Karol Sójko
1d06ffe9d5 fix: removing items in a vault and notifying about designated survivor (#855)
* fix: removing items in a vault and notifying about designated survivor

* fix deleting shared vault items
2023-09-27 11:10:41 +02:00
standardci
dbf532f55e chore(release): publish new version
- @standardnotes/analytics@2.27.0
 - @standardnotes/api-gateway@1.75.5
 - @standardnotes/auth-server@1.147.0
 - @standardnotes/common@1.51.0
 - @standardnotes/domain-core@1.34.0
 - @standardnotes/domain-events-infra@1.13.0
 - @standardnotes/domain-events@2.131.0
 - @standardnotes/event-store@1.12.0
 - @standardnotes/files-server@1.24.0
 - @standardnotes/home-server@1.16.7
 - @standardnotes/predicates@1.7.0
 - @standardnotes/revisions-server@1.38.0
 - @standardnotes/scheduler-server@1.21.0
 - @standardnotes/security@1.14.0
 - @standardnotes/settings@1.21.40
 - @standardnotes/syncing-server@1.109.0
 - @standardnotes/time@1.16.0
 - @standardnotes/websockets-server@1.11.0
2023-09-26 10:05:20 +00:00
Karol Sójko
ca6dbc0053 feat: refactor handling revision creation from dump (#854)
* feat: refactor handling revision creation from dump

* fix: dump repository handling
2023-09-26 11:47:41 +02:00
standardci
1bb5980b45 chore(release): publish new version
- @standardnotes/analytics@2.26.24
 - @standardnotes/api-gateway@1.75.4
 - @standardnotes/auth-server@1.146.4
 - @standardnotes/domain-core@1.33.2
 - @standardnotes/event-store@1.11.52
 - @standardnotes/files-server@1.23.2
 - @standardnotes/home-server@1.16.6
 - @standardnotes/revisions-server@1.37.3
 - @standardnotes/scheduler-server@1.20.56
 - @standardnotes/settings@1.21.39
 - @standardnotes/syncing-server@1.108.2
 - @standardnotes/websockets-server@1.10.53
2023-09-25 16:42:43 +00:00
Karol Sójko
a02a28774b fix(domain-core): notification paylod to string casting 2023-09-25 18:24:38 +02:00
standardci
2d9b3578b6 chore(release): publish new version
- @standardnotes/analytics@2.26.23
 - @standardnotes/api-gateway@1.75.3
 - @standardnotes/auth-server@1.146.3
 - @standardnotes/domain-core@1.33.1
 - @standardnotes/event-store@1.11.51
 - @standardnotes/files-server@1.23.1
 - @standardnotes/home-server@1.16.5
 - @standardnotes/revisions-server@1.37.2
 - @standardnotes/scheduler-server@1.20.55
 - @standardnotes/settings@1.21.38
 - @standardnotes/syncing-server@1.108.1
 - @standardnotes/websockets-server@1.10.52
2023-09-25 15:28:21 +00:00
Karol Sójko
3d5e747590 fix(syncing-server): another spec 2023-09-25 17:10:30 +02:00
Karol Sójko
94467747ac fix(syncing-server): specs 2023-09-25 15:52:35 +02:00
Karol Sójko
cebab59a02 fix: refactor the structure of notifications (#853) 2023-09-25 15:40:51 +02:00
122 changed files with 1491 additions and 608 deletions

View File

@@ -23,6 +23,7 @@ jobs:
strategy:
fail-fast: false
matrix:
suite: ['base', 'vaults']
secondary_db_enabled: [true, false]
runs-on: ubuntu-latest
@@ -55,21 +56,24 @@ jobs:
run: docker/is-available.sh http://localhost:3123 $(pwd)/logs
- name: Run E2E Test Suite
run: yarn dlx mocha-headless-chrome --timeout 3600000 -f http://localhost:9001/mocha/test.html
run: yarn dlx mocha-headless-chrome --timeout 3600000 -f http://localhost:9001/mocha/test.html?suite=${{ matrix.suite }}
- name: Show logs on failure
- name: Archive failed run logs
if: ${{ failure() }}
run: |
echo "# Errors:"
tail -n 100 logs/*.err
echo "# Logs:"
tail -n 100 logs/*.log
uses: actions/upload-artifact@v3
with:
name: self-hosted-failure-logs-${{ matrix.suite }}-${{ matrix.secondary_db_enabled }}
retention-days: 5
path: |
logs/*.err
logs/*.log
e2e-home-server:
name: (Home Server) E2E Test Suite
strategy:
fail-fast: false
matrix:
suite: ['base', 'vaults']
db_type: [mysql, sqlite]
cache_type: [redis, memory]
secondary_db_enabled: [true, false]
@@ -159,8 +163,13 @@ jobs:
run: for i in {1..30}; do curl -s http://localhost:3123/healthcheck && break || sleep 1; done
- name: Run E2E Test Suite
run: yarn dlx mocha-headless-chrome --timeout 3600000 -f http://localhost:9001/mocha/test.html
run: yarn dlx mocha-headless-chrome --timeout 3600000 -f http://localhost:9001/mocha/test.html?suite=${{ matrix.suite }}
- name: Show logs on failure
- name: Archive failed run logs
if: ${{ failure() }}
run: tail -n 500 logs/output.log
uses: actions/upload-artifact@v3
with:
name: home-server-failure-logs-${{ matrix.suite }}-${{ matrix.db_type }}-${{ matrix.cache_type }}-${{ matrix.secondary_db_enabled }}
retention-days: 5
path: |
logs/output.log

View File

@@ -20,7 +20,7 @@
"release": "lerna version --conventional-graduate --conventional-commits --yes -m \"chore(release): publish new version\"",
"publish": "lerna publish from-git --yes --no-verify-access --loglevel verbose",
"postversion": "./scripts/push-tags-one-by-one.sh",
"e2e": "yarn workspace @standardnotes/home-server run build && PORT=3123 yarn workspace @standardnotes/home-server start",
"e2e": "yarn build && PORT=3123 yarn workspace @standardnotes/home-server start",
"start": "yarn workspace @standardnotes/home-server run build && yarn workspace @standardnotes/home-server start"
},
"devDependencies": {

View File

@@ -3,6 +3,28 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [2.27.2](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.27.1...@standardnotes/analytics@2.27.2) (2023-09-28)
**Note:** Version bump only for package @standardnotes/analytics
## [2.27.1](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.27.0...@standardnotes/analytics@2.27.1) (2023-09-27)
**Note:** Version bump only for package @standardnotes/analytics
# [2.27.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.26.24...@standardnotes/analytics@2.27.0) (2023-09-26)
### Features
* refactor handling revision creation from dump ([#854](https://github.com/standardnotes/server/issues/854)) ([ca6dbc0](https://github.com/standardnotes/server/commit/ca6dbc00537bb20f508f9310b1a838421f53a643))
## [2.26.24](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.26.23...@standardnotes/analytics@2.26.24) (2023-09-25)
**Note:** Version bump only for package @standardnotes/analytics
## [2.26.23](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.26.22...@standardnotes/analytics@2.26.23) (2023-09-25)
**Note:** Version bump only for package @standardnotes/analytics
## [2.26.22](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.26.21...@standardnotes/analytics@2.26.22) (2023-09-25)
**Note:** Version bump only for package @standardnotes/analytics

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/analytics",
"version": "2.26.22",
"version": "2.27.2",
"engines": {
"node": ">=18.0.0 <21.0.0"
},
@@ -18,7 +18,7 @@
"build": "tsc --build",
"lint": "eslint . --ext .ts",
"lint:fix": "eslint . --ext .ts --fix",
"test": "jest --coverage --config=./jest.config.js --maxWorkers=50%",
"test": "jest --coverage --no-cache --config=./jest.config.js --maxWorkers=50%",
"worker": "yarn node dist/bin/worker.js",
"report": "yarn node dist/bin/report.js",
"setup:env": "cp .env.sample .env",

View File

@@ -3,6 +3,26 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.75.7](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.75.6...@standardnotes/api-gateway@1.75.7) (2023-09-28)
**Note:** Version bump only for package @standardnotes/api-gateway
## [1.75.6](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.75.5...@standardnotes/api-gateway@1.75.6) (2023-09-27)
**Note:** Version bump only for package @standardnotes/api-gateway
## [1.75.5](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.75.4...@standardnotes/api-gateway@1.75.5) (2023-09-26)
**Note:** Version bump only for package @standardnotes/api-gateway
## [1.75.4](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.75.3...@standardnotes/api-gateway@1.75.4) (2023-09-25)
**Note:** Version bump only for package @standardnotes/api-gateway
## [1.75.3](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.75.2...@standardnotes/api-gateway@1.75.3) (2023-09-25)
**Note:** Version bump only for package @standardnotes/api-gateway
## [1.75.2](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.75.1...@standardnotes/api-gateway@1.75.2) (2023-09-25)
**Note:** Version bump only for package @standardnotes/api-gateway

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/api-gateway",
"version": "1.75.2",
"version": "1.75.7",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -3,6 +3,30 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.148.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.147.1...@standardnotes/auth-server@1.148.0) (2023-09-28)
### Features
* block file operations during transition ([#856](https://github.com/standardnotes/server/issues/856)) ([676cf36](https://github.com/standardnotes/server/commit/676cf36f8d769aa433880b2800f0457d06fbbf14))
## [1.147.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.147.0...@standardnotes/auth-server@1.147.1) (2023-09-27)
**Note:** Version bump only for package @standardnotes/auth-server
# [1.147.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.146.4...@standardnotes/auth-server@1.147.0) (2023-09-26)
### Features
* refactor handling revision creation from dump ([#854](https://github.com/standardnotes/server/issues/854)) ([ca6dbc0](https://github.com/standardnotes/server/commit/ca6dbc00537bb20f508f9310b1a838421f53a643))
## [1.146.4](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.146.3...@standardnotes/auth-server@1.146.4) (2023-09-25)
**Note:** Version bump only for package @standardnotes/auth-server
## [1.146.3](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.146.2...@standardnotes/auth-server@1.146.3) (2023-09-25)
**Note:** Version bump only for package @standardnotes/auth-server
## [1.146.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.146.1...@standardnotes/auth-server@1.146.2) (2023-09-25)
**Note:** Version bump only for package @standardnotes/auth-server

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/auth-server",
"version": "1.146.2",
"version": "1.148.0",
"engines": {
"node": ">=18.0.0 <21.0.0"
},
@@ -19,7 +19,7 @@
"lint": "eslint . --ext .ts",
"lint:fix": "eslint . --fix --ext .ts",
"pretest": "yarn lint && yarn build",
"test": "jest --coverage --config=./jest.config.js --maxWorkers=50%",
"test": "jest --coverage --no-cache --config=./jest.config.js --maxWorkers=50%",
"start": "yarn node dist/bin/server.js",
"worker": "yarn node dist/bin/worker.js",
"cleanup": "yarn node dist/bin/cleanup.js",

View File

@@ -369,7 +369,7 @@ export class ContainerConfigLoader {
// Mapping
container
.bind<MapperInterface<SessionTrace, TypeORMSessionTrace>>(TYPES.Auth_SessionTracePersistenceMapper)
.toConstantValue(new SessionTracePersistenceMapper())
.toConstantValue(new SessionTracePersistenceMapper(container.get<TimerInterface>(TYPES.Auth_Timer)))
container
.bind<MapperInterface<Authenticator, TypeORMAuthenticator>>(TYPES.Auth_AuthenticatorPersistenceMapper)
.toConstantValue(new AuthenticatorPersistenceMapper())
@@ -458,8 +458,9 @@ export class ContainerConfigLoader {
.bind<SessionTraceRepositoryInterface>(TYPES.Auth_SessionTraceRepository)
.toConstantValue(
new TypeORMSessionTraceRepository(
container.get(TYPES.Auth_ORMSessionTraceRepository),
container.get(TYPES.Auth_SessionTracePersistenceMapper),
container.get<Repository<TypeORMSessionTrace>>(TYPES.Auth_ORMSessionTraceRepository),
container.get<MapperInterface<SessionTrace, TypeORMSessionTrace>>(TYPES.Auth_SessionTracePersistenceMapper),
container.get<TimerInterface>(TYPES.Auth_Timer),
),
)
container
@@ -1011,7 +1012,6 @@ export class ContainerConfigLoader {
container.get<SessionRepositoryInterface>(TYPES.Auth_SessionRepository),
container.get<EphemeralSessionRepositoryInterface>(TYPES.Auth_EphemeralSessionRepository),
container.get<RevokedSessionRepositoryInterface>(TYPES.Auth_RevokedSessionRepository),
container.get<RemoveSharedVaultUser>(TYPES.Auth_RemoveSharedVaultUser),
container.get<winston.Logger>(TYPES.Auth_Logger),
),
)

View File

@@ -6,7 +6,6 @@ import { EphemeralSessionRepositoryInterface } from '../Session/EphemeralSession
import { RevokedSessionRepositoryInterface } from '../Session/RevokedSessionRepositoryInterface'
import { SessionRepositoryInterface } from '../Session/SessionRepositoryInterface'
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
import { RemoveSharedVaultUser } from '../UseCase/RemoveSharedVaultUser/RemoveSharedVaultUser'
export class AccountDeletionRequestedEventHandler implements DomainEventHandlerInterface {
constructor(
@@ -14,7 +13,6 @@ export class AccountDeletionRequestedEventHandler implements DomainEventHandlerI
private sessionRepository: SessionRepositoryInterface,
private ephemeralSessionRepository: EphemeralSessionRepositoryInterface,
private revokedSessionRepository: RevokedSessionRepositoryInterface,
private removeSharedVaultUser: RemoveSharedVaultUser,
private logger: Logger,
) {}
@@ -37,13 +35,6 @@ export class AccountDeletionRequestedEventHandler implements DomainEventHandlerI
await this.removeSessions(userUuid.value)
const result = await this.removeSharedVaultUser.execute({
userUuid: userUuid.value,
})
if (result.isFailed()) {
this.logger.error(`Could not remove shared vault user: ${result.getError()}`)
}
await this.userRepository.remove(user)
this.logger.info(`Finished account cleanup for user: ${userUuid.value}`)

View File

@@ -3,7 +3,7 @@ import { SubscriptionPlanName, Uuid } from '@standardnotes/domain-core'
import { SessionTrace } from './SessionTrace'
export interface SessionTraceRepositoryInterface {
save(sessionTrace: SessionTrace): Promise<void>
insert(sessionTrace: SessionTrace): Promise<void>
removeExpiredBefore(date: Date): Promise<void>
findOneByUserUuidAndDate(userUuid: Uuid, date: Date): Promise<SessionTrace | null>
countByDate(date: Date): Promise<number>

View File

@@ -1,14 +1,17 @@
import 'reflect-metadata'
import { TokenEncoderInterface, ValetTokenData, ValetTokenOperation } from '@standardnotes/security'
import { CreateValetToken } from './CreateValetToken'
import { TransitionStatus } from '@standardnotes/domain-core'
import { TimerInterface } from '@standardnotes/time'
import { TokenEncoderInterface, ValetTokenData, ValetTokenOperation } from '@standardnotes/security'
import { CreateValetToken } from './CreateValetToken'
import { UserSubscription } from '../../Subscription/UserSubscription'
import { SubscriptionSettingServiceInterface } from '../../Setting/SubscriptionSettingServiceInterface'
import { User } from '../../User/User'
import { UserSubscriptionType } from '../../Subscription/UserSubscriptionType'
import { SubscriptionSettingsAssociationServiceInterface } from '../../Setting/SubscriptionSettingsAssociationServiceInterface'
import { UserSubscriptionServiceInterface } from '../../Subscription/UserSubscriptionServiceInterface'
import { TransitionStatusRepositoryInterface } from '../../Transition/TransitionStatusRepositoryInterface'
describe('CreateValetToken', () => {
let tokenEncoder: TokenEncoderInterface<ValetTokenData>
@@ -20,6 +23,7 @@ describe('CreateValetToken', () => {
let regularSubscription: UserSubscription
let sharedSubscription: UserSubscription
let user: User
let transitionStatusRepository: TransitionStatusRepositoryInterface
const createUseCase = () =>
new CreateValetToken(
@@ -29,6 +33,7 @@ describe('CreateValetToken', () => {
userSubscriptionService,
timer,
valetTokenTTL,
transitionStatusRepository,
)
beforeEach(() => {
@@ -66,6 +71,11 @@ describe('CreateValetToken', () => {
timer = {} as jest.Mocked<TimerInterface>
timer.getTimestampInMicroseconds = jest.fn().mockReturnValue(100)
transitionStatusRepository = {} as jest.Mocked<TransitionStatusRepositoryInterface>
transitionStatusRepository.getStatus = jest
.fn()
.mockReturnValue(TransitionStatus.create(TransitionStatus.STATUSES.Verified).getValue())
})
it('should create a read valet token', async () => {
@@ -166,6 +176,7 @@ describe('CreateValetToken', () => {
{
sharedSubscriptionUuid: undefined,
regularSubscriptionUuid: '1-2-3',
ongoingTransition: false,
permittedOperation: 'write',
permittedResources: [
{
@@ -206,6 +217,7 @@ describe('CreateValetToken', () => {
{
sharedSubscriptionUuid: '2-3-4',
regularSubscriptionUuid: '1-2-3',
ongoingTransition: false,
permittedOperation: 'write',
permittedResources: [
{
@@ -266,6 +278,7 @@ describe('CreateValetToken', () => {
{
sharedSubscriptionUuid: undefined,
regularSubscriptionUuid: '1-2-3',
ongoingTransition: false,
permittedOperation: 'write',
permittedResources: [
{

View File

@@ -13,6 +13,8 @@ import { CreateValetTokenDTO } from './CreateValetTokenDTO'
import { SubscriptionSettingsAssociationServiceInterface } from '../../Setting/SubscriptionSettingsAssociationServiceInterface'
import { UserSubscriptionServiceInterface } from '../../Subscription/UserSubscriptionServiceInterface'
import { CreateValetTokenPayload } from '../../ValetToken/CreateValetTokenPayload'
import { TransitionStatusRepositoryInterface } from '../../Transition/TransitionStatusRepositoryInterface'
import { TransitionStatus } from '@standardnotes/domain-core'
@injectable()
export class CreateValetToken implements UseCaseInterface {
@@ -25,6 +27,8 @@ export class CreateValetToken implements UseCaseInterface {
@inject(TYPES.Auth_UserSubscriptionService) private userSubscriptionService: UserSubscriptionServiceInterface,
@inject(TYPES.Auth_Timer) private timer: TimerInterface,
@inject(TYPES.Auth_VALET_TOKEN_TTL) private valetTokenTTL: number,
@inject(TYPES.Auth_TransitionStatusRepository)
private transitionStatusRepository: TransitionStatusRepositoryInterface,
) {}
async execute(dto: CreateValetTokenDTO): Promise<CreateValetTokenResponseData> {
@@ -83,6 +87,8 @@ export class CreateValetToken implements UseCaseInterface {
sharedSubscriptionUuid = sharedSubscription.uuid
}
const transitionStatus = await this.transitionStatusRepository.getStatus(userUuid, 'items')
const tokenData: ValetTokenData = {
userUuid: dto.userUuid,
permittedOperation: dto.operation,
@@ -91,6 +97,7 @@ export class CreateValetToken implements UseCaseInterface {
uploadBytesLimit,
sharedSubscriptionUuid,
regularSubscriptionUuid: regularSubscription.uuid,
ongoingTransition: transitionStatus?.value === TransitionStatus.STATUSES.InProgress,
}
const valetToken = this.tokenEncoder.encodeExpirableToken(tokenData, this.valetTokenTTL)

View File

@@ -15,7 +15,7 @@ describe('TraceSession', () => {
beforeEach(() => {
sessionTraceRepository = {} as jest.Mocked<SessionTraceRepositoryInterface>
sessionTraceRepository.findOneByUserUuidAndDate = jest.fn().mockReturnValue(null)
sessionTraceRepository.save = jest.fn()
sessionTraceRepository.insert = jest.fn()
timer = {} as jest.Mocked<TimerInterface>
timer.getUTCDateNDaysAhead = jest.fn().mockReturnValue(new Date())
@@ -30,7 +30,7 @@ describe('TraceSession', () => {
expect(result.isFailed()).toBe(false)
expect(result.getValue().props.userUuid.value).toEqual('0702b137-4f5c-438a-915e-8f8b46572ce5')
expect(sessionTraceRepository.save).toHaveBeenCalledTimes(1)
expect(sessionTraceRepository.insert).toHaveBeenCalledTimes(1)
})
it('should not save a session trace if one already exists for the same user and date', async () => {
@@ -43,7 +43,7 @@ describe('TraceSession', () => {
})
expect(result.isFailed()).toBe(false)
expect(sessionTraceRepository.save).not.toHaveBeenCalled()
expect(sessionTraceRepository.insert).not.toHaveBeenCalled()
})
it('should return an error if userUuid is invalid', async () => {
@@ -54,7 +54,7 @@ describe('TraceSession', () => {
})
expect(result.isFailed()).toBe(true)
expect(sessionTraceRepository.save).not.toHaveBeenCalled()
expect(sessionTraceRepository.insert).not.toHaveBeenCalled()
})
it('should return an error if username is invalid', async () => {
@@ -65,7 +65,7 @@ describe('TraceSession', () => {
})
expect(result.isFailed()).toBe(true)
expect(sessionTraceRepository.save).not.toHaveBeenCalled()
expect(sessionTraceRepository.insert).not.toHaveBeenCalled()
})
it('should return an error if subscriptionPlanName is invalid', async () => {
@@ -76,7 +76,7 @@ describe('TraceSession', () => {
})
expect(result.isFailed()).toBe(true)
expect(sessionTraceRepository.save).not.toHaveBeenCalled()
expect(sessionTraceRepository.insert).not.toHaveBeenCalled()
})
it('should not save a session trace if creating of the session trace fails', async () => {
@@ -90,7 +90,7 @@ describe('TraceSession', () => {
})
expect(result.isFailed()).toBe(true)
expect(sessionTraceRepository.save).not.toHaveBeenCalled()
expect(sessionTraceRepository.insert).not.toHaveBeenCalled()
mock.mockRestore()
})

View File

@@ -53,7 +53,7 @@ export class TraceSession implements UseCaseInterface<SessionTrace> {
}
const sessionTrace = sessionTraceOrError.getValue()
await this.sessionTraceRepository.save(sessionTrace)
await this.sessionTraceRepository.insert(sessionTrace)
return Result.ok<SessionTrace>(sessionTrace)
}

View File

@@ -1,5 +1,7 @@
import { MapperInterface, SubscriptionPlanName, Uuid } from '@standardnotes/domain-core'
import { TimerInterface } from '@standardnotes/time'
import { Repository } from 'typeorm'
import { SessionTrace } from '../../Domain/Session/SessionTrace'
import { SessionTraceRepositoryInterface } from '../../Domain/Session/SessionTraceRepositoryInterface'
import { TypeORMSessionTrace } from './TypeORMSessionTrace'
@@ -8,13 +10,14 @@ export class TypeORMSessionTraceRepository implements SessionTraceRepositoryInte
constructor(
private ormRepository: Repository<TypeORMSessionTrace>,
private mapper: MapperInterface<SessionTrace, TypeORMSessionTrace>,
private timer: TimerInterface,
) {}
async countByDateAndSubscriptionPlanName(date: Date, subscriptionPlanName: SubscriptionPlanName): Promise<number> {
return this.ormRepository
.createQueryBuilder('trace')
.where('trace.creation_date = :creationDate', {
creationDate: `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`,
creationDate: this.timer.convertDateToFormattedString(date, 'YYYY-MM-DD'),
})
.andWhere('trace.subscription_plan_name = :subscriptionPlanName', {
subscriptionPlanName: subscriptionPlanName.value,
@@ -26,7 +29,7 @@ export class TypeORMSessionTraceRepository implements SessionTraceRepositoryInte
return this.ormRepository
.createQueryBuilder('trace')
.where('trace.creation_date = :creationDate', {
creationDate: `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`,
creationDate: this.timer.convertDateToFormattedString(date, 'YYYY-MM-DD'),
})
.getCount()
}
@@ -44,7 +47,7 @@ export class TypeORMSessionTraceRepository implements SessionTraceRepositoryInte
.createQueryBuilder('trace')
.where('trace.user_uuid = :userUuid AND trace.creation_date = :creationDate', {
userUuid: userUuid.value,
creationDate: `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`,
creationDate: this.timer.convertDateToFormattedString(date, 'YYYY-MM-DD'),
})
.getOne()
@@ -55,9 +58,9 @@ export class TypeORMSessionTraceRepository implements SessionTraceRepositoryInte
return this.mapper.toDomain(typeOrm)
}
async save(sessionTrace: SessionTrace): Promise<void> {
async insert(sessionTrace: SessionTrace): Promise<void> {
const persistence = this.mapper.toProjection(sessionTrace)
await this.ormRepository.save(persistence)
await this.ormRepository.insert(persistence)
}
}

View File

@@ -1,8 +1,11 @@
import { MapperInterface, SubscriptionPlanName, UniqueEntityId, Username, Uuid } from '@standardnotes/domain-core'
import { SessionTrace } from '../Domain/Session/SessionTrace'
import { TypeORMSessionTrace } from '../Infra/TypeORM/TypeORMSessionTrace'
import { TimerInterface } from '@standardnotes/time'
export class SessionTracePersistenceMapper implements MapperInterface<SessionTrace, TypeORMSessionTrace> {
constructor(private timer: TimerInterface) {}
toDomain(projection: TypeORMSessionTrace): SessionTrace {
const userUuidOrError = Uuid.create(projection.userUuid)
if (userUuidOrError.isFailed()) {
@@ -50,11 +53,7 @@ export class SessionTracePersistenceMapper implements MapperInterface<SessionTra
typeOrm.username = domain.props.username.value
typeOrm.subscriptionPlanName = domain.props.subscriptionPlanName ? domain.props.subscriptionPlanName.value : null
typeOrm.createdAt = domain.props.createdAt
typeOrm.creationDate = new Date(
domain.props.createdAt.getFullYear(),
domain.props.createdAt.getMonth(),
domain.props.createdAt.getDate(),
)
typeOrm.creationDate = new Date(this.timer.convertDateToFormattedString(domain.props.createdAt, 'YYYY-MM-DD'))
typeOrm.expiresAt = domain.props.expiresAt
return typeOrm

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.51.0](https://github.com/standardnotes/server/compare/@standardnotes/common@1.50.4...@standardnotes/common@1.51.0) (2023-09-26)
### Features
* refactor handling revision creation from dump ([#854](https://github.com/standardnotes/server/issues/854)) ([ca6dbc0](https://github.com/standardnotes/server/commit/ca6dbc00537bb20f508f9310b1a838421f53a643))
## [1.50.4](https://github.com/standardnotes/server/compare/@standardnotes/common@1.50.3...@standardnotes/common@1.50.4) (2023-09-04)
**Note:** Version bump only for package @standardnotes/common

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/common",
"version": "1.50.4",
"version": "1.51.0",
"engines": {
"node": ">=18.0.0 <21.0.0"
},
@@ -20,7 +20,7 @@
"clean": "rm -fr dist",
"build": "tsc --build",
"lint": "eslint . --ext .ts",
"test": "jest spec --coverage"
"test": "jest --coverage --no-cache"
},
"devDependencies": {
"@types/jest": "^29.5.1",

View File

@@ -3,6 +3,30 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.34.1](https://github.com/standardnotes/server/compare/@standardnotes/domain-core@1.34.0...@standardnotes/domain-core@1.34.1) (2023-09-27)
### Bug Fixes
* removing items in a vault and notifying about designated survivor ([#855](https://github.com/standardnotes/server/issues/855)) ([1d06ffe](https://github.com/standardnotes/server/commit/1d06ffe9d51722ada7baa040e1d5ed351fc28f39))
# [1.34.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-core@1.33.2...@standardnotes/domain-core@1.34.0) (2023-09-26)
### Features
* refactor handling revision creation from dump ([#854](https://github.com/standardnotes/server/issues/854)) ([ca6dbc0](https://github.com/standardnotes/server/commit/ca6dbc00537bb20f508f9310b1a838421f53a643))
## [1.33.2](https://github.com/standardnotes/server/compare/@standardnotes/domain-core@1.33.1...@standardnotes/domain-core@1.33.2) (2023-09-25)
### Bug Fixes
* **domain-core:** notification paylod to string casting ([a02a287](https://github.com/standardnotes/server/commit/a02a28774b6d500200043faefb9ebac3719e7661))
## [1.33.1](https://github.com/standardnotes/server/compare/@standardnotes/domain-core@1.33.0...@standardnotes/domain-core@1.33.1) (2023-09-25)
### Bug Fixes
* refactor the structure of notifications ([#853](https://github.com/standardnotes/server/issues/853)) ([cebab59](https://github.com/standardnotes/server/commit/cebab59a026c6868886e0945787a8ddb0442fbc3))
# [1.33.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-core@1.32.0...@standardnotes/domain-core@1.33.0) (2023-09-21)
### Features

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/domain-core",
"version": "1.33.0",
"version": "1.34.1",
"engines": {
"node": ">=18.0.0 <21.0.0"
},
@@ -21,7 +21,7 @@
"build": "tsc --build",
"lint": "eslint . --ext .ts",
"lint:fix": "eslint . --ext .ts --fix",
"test": "jest spec --coverage --passWithNoTests"
"test": "jest --coverage --no-cache --passWithNoTests"
},
"dependencies": {
"uuid": "^9.0.0"

View File

@@ -4,6 +4,7 @@ import { Result } from '../Core/Result'
import { NotificationPayloadProps } from './NotificationPayloadProps'
import { NotificationType } from './NotificationType'
import { Uuid } from '../Common/Uuid'
import { NotificationPayloadIdentifierType } from './NotificationPayloadIdentifierType'
export class NotificationPayload extends ValueObject<NotificationPayloadProps> {
private constructor(props: NotificationPayloadProps) {
@@ -14,8 +15,10 @@ export class NotificationPayload extends ValueObject<NotificationPayloadProps> {
return JSON.stringify({
version: this.props.version,
type: this.props.type.value,
sharedVaultUuid: this.props.sharedVaultUuid.value,
itemUuid: this.props.itemUuid ? this.props.itemUuid.value : undefined,
primaryIdentifier: this.props.primaryIdentifier.value,
primaryIndentifierType: this.props.primaryIndentifierType.value,
secondaryIdentifier: this.props.secondaryIdentifier?.value,
secondaryIdentifierType: this.props.secondaryIdentifierType?.value,
})
}
@@ -29,26 +32,43 @@ export class NotificationPayload extends ValueObject<NotificationPayloadProps> {
}
const type = typeOrError.getValue()
const sharedVaultUuidOrError = Uuid.create(props.sharedVaultUuid)
if (sharedVaultUuidOrError.isFailed()) {
return Result.fail<NotificationPayload>(sharedVaultUuidOrError.getError())
const primaryIdentifierOrError = Uuid.create(props.primaryIdentifier)
if (primaryIdentifierOrError.isFailed()) {
return Result.fail<NotificationPayload>(primaryIdentifierOrError.getError())
}
const sharedVaultUuid = sharedVaultUuidOrError.getValue()
const primaryIdentifier = primaryIdentifierOrError.getValue()
let itemUuid: Uuid | undefined = undefined
if (props.itemUuid) {
const itemUuidOrError = Uuid.create(props.itemUuid)
if (itemUuidOrError.isFailed()) {
return Result.fail<NotificationPayload>(itemUuidOrError.getError())
const primaryIndentifierTypeOrError = NotificationPayloadIdentifierType.create(props.primaryIndentifierType)
if (primaryIndentifierTypeOrError.isFailed()) {
return Result.fail<NotificationPayload>(primaryIndentifierTypeOrError.getError())
}
const primaryIndentifierType = primaryIndentifierTypeOrError.getValue()
let secondaryIdentifier: Uuid | undefined
if (props.secondaryIdentifier) {
const secondaryIdentifierOrError = Uuid.create(props.secondaryIdentifier)
if (secondaryIdentifierOrError.isFailed()) {
return Result.fail<NotificationPayload>(secondaryIdentifierOrError.getError())
}
itemUuid = itemUuidOrError.getValue()
secondaryIdentifier = secondaryIdentifierOrError.getValue()
}
let secondaryIdentifierType: NotificationPayloadIdentifierType | undefined
if (props.secondaryIdentifierType) {
const secondaryIdentifierTypeOrError = NotificationPayloadIdentifierType.create(props.secondaryIdentifierType)
if (secondaryIdentifierTypeOrError.isFailed()) {
return Result.fail<NotificationPayload>(secondaryIdentifierTypeOrError.getError())
}
secondaryIdentifierType = secondaryIdentifierTypeOrError.getValue()
}
return NotificationPayload.create({
version: props.version,
type,
sharedVaultUuid,
itemUuid,
primaryIdentifier,
primaryIndentifierType,
secondaryIdentifier,
secondaryIdentifierType,
})
} catch (error) {
return Result.fail<NotificationPayload>((error as Error).message)
@@ -57,7 +77,7 @@ export class NotificationPayload extends ValueObject<NotificationPayloadProps> {
static create(props: NotificationPayloadProps): Result<NotificationPayload> {
if (
props.itemUuid === undefined &&
props.secondaryIdentifier === undefined &&
props.type.equals(NotificationType.create(NotificationType.TYPES.SharedVaultItemRemoved).getValue())
) {
return Result.fail<NotificationPayload>(

View File

@@ -0,0 +1,28 @@
import { Result } from '../Core/Result'
import { ValueObject } from '../Core/ValueObject'
import { NotificationPayloadIdentifierTypeProps } from './NotificationPayloadIdentifierTypeProps'
export class NotificationPayloadIdentifierType extends ValueObject<NotificationPayloadIdentifierTypeProps> {
static readonly TYPES = {
SharedVaultUuid: 'shared_vault_uuid',
UserUuid: 'user_uuid',
SharedVaultInviteUuid: 'shared_vault_invite_uuid',
ItemUuid: 'item_uuid',
}
private constructor(props: NotificationPayloadIdentifierTypeProps) {
super(props)
}
get value(): string {
return this.props.value
}
static create(type: string): Result<NotificationPayloadIdentifierType> {
if (!Object.values(this.TYPES).includes(type)) {
return Result.fail<NotificationPayloadIdentifierType>(`Invalid notification payload identifier type: ${type}`)
}
return Result.ok<NotificationPayloadIdentifierType>(new NotificationPayloadIdentifierType({ value: type }))
}
}

View File

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

View File

@@ -1,9 +1,12 @@
import { Uuid } from '../Common/Uuid'
import { NotificationPayloadIdentifierType } from './NotificationPayloadIdentifierType'
import { NotificationType } from './NotificationType'
export interface NotificationPayloadProps {
type: NotificationType
sharedVaultUuid: Uuid
primaryIdentifier: Uuid
primaryIndentifierType: NotificationPayloadIdentifierType
secondaryIdentifier?: Uuid
secondaryIdentifierType?: NotificationPayloadIdentifierType
version: string
itemUuid?: Uuid
}

View File

@@ -7,8 +7,9 @@ export class NotificationType extends ValueObject<NotificationTypeProps> {
SharedVaultItemRemoved: 'shared_vault_item_removed',
SelfRemovedFromSharedVault: 'self_removed_from_shared_vault',
UserRemovedFromSharedVault: 'user_removed_from_shared_vault',
UserDesignatedAsSurvivor: 'user_designated_as_survivor',
UserAddedToSharedVault: 'user_added_to_shared_vault',
SharedVaultInviteDeclined: 'shared_vault_invite_declined',
SharedVaultInviteCanceled: 'shared_vault_invite_canceled',
SharedVaultFileUploaded: 'shared_vault_file_uploaded',
SharedVaultFileRemoved: 'shared_vault_file_removed',
}

View File

@@ -48,6 +48,8 @@ export * from './Env/AbstractEnv'
export * from './Mapping/MapperInterface'
export * from './Notification/NotificationPayload'
export * from './Notification/NotificationPayloadIdentifierType'
export * from './Notification/NotificationPayloadIdentifierTypeProps'
export * from './Notification/NotificationPayloadProps'
export * from './Notification/NotificationType'
export * from './Notification/NotificationTypeProps'

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.13.1](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.13.0...@standardnotes/domain-events-infra@1.13.1) (2023-09-28)
**Note:** Version bump only for package @standardnotes/domain-events-infra
# [1.13.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.12.34...@standardnotes/domain-events-infra@1.13.0) (2023-09-26)
### Features
* refactor handling revision creation from dump ([#854](https://github.com/standardnotes/server/issues/854)) ([ca6dbc0](https://github.com/standardnotes/server/commit/ca6dbc00537bb20f508f9310b1a838421f53a643))
## [1.12.34](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.12.33...@standardnotes/domain-events-infra@1.12.34) (2023-09-25)
**Note:** Version bump only for package @standardnotes/domain-events-infra

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/domain-events-infra",
"version": "1.12.34",
"version": "1.13.1",
"engines": {
"node": ">=18.0.0 <21.0.0"
},
@@ -21,7 +21,7 @@
"build": "tsc --build",
"lint": "eslint . --ext .ts",
"lint:fix": "eslint . --ext .ts --fix",
"test": "jest spec --coverage"
"test": "jest --coverage --no-cache"
},
"dependencies": {
"@aws-sdk/client-sns": "^3.332.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.131.1](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.131.0...@standardnotes/domain-events@2.131.1) (2023-09-28)
**Note:** Version bump only for package @standardnotes/domain-events
# [2.131.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.130.0...@standardnotes/domain-events@2.131.0) (2023-09-26)
### Features
* refactor handling revision creation from dump ([#854](https://github.com/standardnotes/server/issues/854)) ([ca6dbc0](https://github.com/standardnotes/server/commit/ca6dbc00537bb20f508f9310b1a838421f53a643))
# [2.130.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.129.1...@standardnotes/domain-events@2.130.0) (2023-09-25)
### Features

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/domain-events",
"version": "2.130.0",
"version": "2.131.1",
"engines": {
"node": ">=18.0.0 <21.0.0"
},
@@ -20,7 +20,7 @@
"clean": "rm -fr dist",
"build": "tsc --build",
"lint": "eslint . --ext .ts",
"test": "jest spec --coverage --passWithNoTests"
"test": "jest --coverage --no-cache --passWithNoTests"
},
"dependencies": {
"@standardnotes/predicates": "workspace:*",

View File

@@ -3,6 +3,28 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.12.2](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.12.1...@standardnotes/event-store@1.12.2) (2023-09-28)
**Note:** Version bump only for package @standardnotes/event-store
## [1.12.1](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.12.0...@standardnotes/event-store@1.12.1) (2023-09-27)
**Note:** Version bump only for package @standardnotes/event-store
# [1.12.0](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.11.52...@standardnotes/event-store@1.12.0) (2023-09-26)
### Features
* refactor handling revision creation from dump ([#854](https://github.com/standardnotes/server/issues/854)) ([ca6dbc0](https://github.com/standardnotes/server/commit/ca6dbc00537bb20f508f9310b1a838421f53a643))
## [1.11.52](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.11.51...@standardnotes/event-store@1.11.52) (2023-09-25)
**Note:** Version bump only for package @standardnotes/event-store
## [1.11.51](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.11.50...@standardnotes/event-store@1.11.51) (2023-09-25)
**Note:** Version bump only for package @standardnotes/event-store
## [1.11.50](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.11.49...@standardnotes/event-store@1.11.50) (2023-09-25)
**Note:** Version bump only for package @standardnotes/event-store

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/event-store",
"version": "1.11.50",
"version": "1.12.2",
"description": "Event Store Service",
"private": true,
"main": "dist/src/index.js",
@@ -13,7 +13,7 @@
"build": "tsc --build",
"lint": "eslint . --ext .ts",
"pretest": "yarn lint && yarn build",
"test": "jest --coverage --config=./jest.config.js --maxWorkers=50%",
"test": "jest --coverage --no-cache --config=./jest.config.js --maxWorkers=50%",
"worker": "yarn node dist/bin/worker.js"
},
"author": "Karol Sójko <karol@standardnotes.com>",

View File

@@ -3,6 +3,30 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.25.0](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.24.1...@standardnotes/files-server@1.25.0) (2023-09-28)
### Features
* block file operations during transition ([#856](https://github.com/standardnotes/files/issues/856)) ([676cf36](https://github.com/standardnotes/files/commit/676cf36f8d769aa433880b2800f0457d06fbbf14))
## [1.24.1](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.24.0...@standardnotes/files-server@1.24.1) (2023-09-27)
**Note:** Version bump only for package @standardnotes/files-server
# [1.24.0](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.23.2...@standardnotes/files-server@1.24.0) (2023-09-26)
### Features
* refactor handling revision creation from dump ([#854](https://github.com/standardnotes/files/issues/854)) ([ca6dbc0](https://github.com/standardnotes/files/commit/ca6dbc00537bb20f508f9310b1a838421f53a643))
## [1.23.2](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.23.1...@standardnotes/files-server@1.23.2) (2023-09-25)
**Note:** Version bump only for package @standardnotes/files-server
## [1.23.1](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.23.0...@standardnotes/files-server@1.23.1) (2023-09-25)
**Note:** Version bump only for package @standardnotes/files-server
# [1.23.0](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.22.28...@standardnotes/files-server@1.23.0) (2023-09-25)
### Features

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/files-server",
"version": "1.23.0",
"version": "1.25.0",
"engines": {
"node": ">=18.0.0 <21.0.0"
},
@@ -22,7 +22,7 @@
"lint": "eslint . --ext .ts",
"lint:fix": "eslint . --fix --ext .ts",
"pretest": "yarn lint && yarn build",
"test": "jest --coverage --config=./jest.config.js --maxWorkers=50%",
"test": "jest --coverage --no-cache --config=./jest.config.js --maxWorkers=50%",
"start": "yarn node dist/bin/server.js",
"worker": "yarn node dist/bin/worker.js"
},

View File

@@ -198,7 +198,9 @@ export class ContainerConfigLoader {
.toConstantValue(
new FSFileUploader(container.get(TYPES.Files_FILE_UPLOAD_PATH), container.get(TYPES.Files_Logger)),
)
container.bind<FileRemoverInterface>(TYPES.Files_FileRemover).to(FSFileRemover)
container
.bind<FileRemoverInterface>(TYPES.Files_FileRemover)
.toConstantValue(new FSFileRemover(container.get<string>(TYPES.Files_FILE_UPLOAD_PATH)))
container.bind<FileMoverInterface>(TYPES.Files_FileMover).to(FSFileMover)
}
@@ -247,12 +249,26 @@ export class ContainerConfigLoader {
// Handlers
container
.bind<AccountDeletionRequestedEventHandler>(TYPES.Files_AccountDeletionRequestedEventHandler)
.to(AccountDeletionRequestedEventHandler)
.toConstantValue(
new AccountDeletionRequestedEventHandler(
container.get<MarkFilesToBeRemoved>(TYPES.Files_MarkFilesToBeRemoved),
container.get<DomainEventPublisherInterface>(TYPES.Files_DomainEventPublisher),
container.get<DomainEventFactoryInterface>(TYPES.Files_DomainEventFactory),
container.get<winston.Logger>(TYPES.Files_Logger),
),
)
container
.bind<SharedSubscriptionInvitationCanceledEventHandler>(
TYPES.Files_SharedSubscriptionInvitationCanceledEventHandler,
)
.to(SharedSubscriptionInvitationCanceledEventHandler)
.toConstantValue(
new SharedSubscriptionInvitationCanceledEventHandler(
container.get<MarkFilesToBeRemoved>(TYPES.Files_MarkFilesToBeRemoved),
container.get<DomainEventPublisherInterface>(TYPES.Files_DomainEventPublisher),
container.get<DomainEventFactoryInterface>(TYPES.Files_DomainEventFactory),
container.get<winston.Logger>(TYPES.Files_Logger),
),
)
const eventHandlers: Map<string, DomainEventHandlerInterface> = new Map([
['ACCOUNT_DELETION_REQUESTED', container.get(TYPES.Files_AccountDeletionRequestedEventHandler)],

View File

@@ -1,5 +1,5 @@
export type RemovedFileDescription = {
userUuid: string
userOrSharedVaultUuid: string
filePath: string
fileName: string
fileByteSize: number

View File

@@ -3,18 +3,17 @@ import {
DomainEventHandlerInterface,
DomainEventPublisherInterface,
} from '@standardnotes/domain-events'
import { inject, injectable } from 'inversify'
import { Logger } from 'winston'
import TYPES from '../../Bootstrap/Types'
import { DomainEventFactoryInterface } from '../Event/DomainEventFactoryInterface'
import { MarkFilesToBeRemoved } from '../UseCase/MarkFilesToBeRemoved/MarkFilesToBeRemoved'
@injectable()
export class AccountDeletionRequestedEventHandler implements DomainEventHandlerInterface {
constructor(
@inject(TYPES.Files_MarkFilesToBeRemoved) private markFilesToBeRemoved: MarkFilesToBeRemoved,
@inject(TYPES.Files_DomainEventPublisher) private domainEventPublisher: DomainEventPublisherInterface,
@inject(TYPES.Files_DomainEventFactory) private domainEventFactory: DomainEventFactoryInterface,
private markFilesToBeRemoved: MarkFilesToBeRemoved,
private domainEventPublisher: DomainEventPublisherInterface,
private domainEventFactory: DomainEventFactoryInterface,
private logger: Logger,
) {}
async handle(event: AccountDeletionRequestedEvent): Promise<void> {
@@ -27,16 +26,23 @@ export class AccountDeletionRequestedEventHandler implements DomainEventHandlerI
})
if (result.isFailed()) {
this.logger.error(`Could not mark files for removal for user ${event.payload.userUuid}: ${result.getError()}`)
return
}
const filesRemoved = result.getValue()
this.logger.debug(`Marked ${filesRemoved.length} files for removal for user ${event.payload.userUuid}`)
for (const fileRemoved of filesRemoved) {
await this.domainEventPublisher.publish(
this.domainEventFactory.createFileRemovedEvent({
regularSubscriptionUuid: event.payload.regularSubscriptionUuid,
...fileRemoved,
userUuid: fileRemoved.userOrSharedVaultUuid,
filePath: fileRemoved.filePath,
fileName: fileRemoved.fileName,
fileByteSize: fileRemoved.fileByteSize,
}),
)
}

View File

@@ -3,18 +3,17 @@ import {
DomainEventHandlerInterface,
DomainEventPublisherInterface,
} from '@standardnotes/domain-events'
import { inject, injectable } from 'inversify'
import { Logger } from 'winston'
import TYPES from '../../Bootstrap/Types'
import { DomainEventFactoryInterface } from '../Event/DomainEventFactoryInterface'
import { MarkFilesToBeRemoved } from '../UseCase/MarkFilesToBeRemoved/MarkFilesToBeRemoved'
@injectable()
export class SharedSubscriptionInvitationCanceledEventHandler implements DomainEventHandlerInterface {
constructor(
@inject(TYPES.Files_MarkFilesToBeRemoved) private markFilesToBeRemoved: MarkFilesToBeRemoved,
@inject(TYPES.Files_DomainEventPublisher) private domainEventPublisher: DomainEventPublisherInterface,
@inject(TYPES.Files_DomainEventFactory) private domainEventFactory: DomainEventFactoryInterface,
private markFilesToBeRemoved: MarkFilesToBeRemoved,
private domainEventPublisher: DomainEventPublisherInterface,
private domainEventFactory: DomainEventFactoryInterface,
private logger: Logger,
) {}
async handle(event: SharedSubscriptionInvitationCanceledEvent): Promise<void> {
@@ -27,16 +26,25 @@ export class SharedSubscriptionInvitationCanceledEventHandler implements DomainE
})
if (result.isFailed()) {
this.logger.error(
`Could not mark files to be removed for invitee: ${event.payload.inviteeIdentifier}: ${result.getError()}`,
)
return
}
const filesRemoved = result.getValue()
this.logger.debug(`Marked ${filesRemoved.length} files for removal for invitee ${event.payload.inviteeIdentifier}`)
for (const fileRemoved of filesRemoved) {
await this.domainEventPublisher.publish(
this.domainEventFactory.createFileRemovedEvent({
regularSubscriptionUuid: event.payload.inviterSubscriptionUuid,
...fileRemoved,
userUuid: fileRemoved.userOrSharedVaultUuid,
filePath: fileRemoved.filePath,
fileName: fileRemoved.fileName,
fileByteSize: fileRemoved.fileByteSize,
}),
)
}

View File

@@ -29,6 +29,10 @@ export class SharedVaultRemovedEventHandler implements DomainEventHandlerInterfa
const filesRemoved = result.getValue()
this.logger.debug(
`Marked ${filesRemoved.length} files for removal for shared vault ${event.payload.sharedVaultUuid}`,
)
for (const fileRemoved of filesRemoved) {
await this.domainEventPublisher.publish(
this.domainEventFactory.createSharedVaultFileRemovedEvent({

View File

@@ -2,5 +2,5 @@ import { RemovedFileDescription } from '../File/RemovedFileDescription'
export interface FileRemoverInterface {
remove(filePath: string): Promise<number>
markFilesToBeRemoved(userUuid: string): Promise<Array<RemovedFileDescription>>
markFilesToBeRemoved(userOrSharedVaultUuid: string): Promise<Array<RemovedFileDescription>>
}

View File

@@ -1,18 +1,42 @@
import { inject, injectable } from 'inversify'
import { promises } from 'fs'
import { FileRemoverInterface } from '../../Domain/Services/FileRemoverInterface'
import { RemovedFileDescription } from '../../Domain/File/RemovedFileDescription'
import TYPES from '../../Bootstrap/Types'
@injectable()
export class FSFileRemover implements FileRemoverInterface {
constructor(@inject(TYPES.Files_FILE_UPLOAD_PATH) private fileUploadPath: string) {}
constructor(private fileUploadPath: string) {}
async markFilesToBeRemoved(userUuid: string): Promise<Array<RemovedFileDescription>> {
await promises.rmdir(`${this.fileUploadPath}/${userUuid}`)
async markFilesToBeRemoved(userOrSharedVaultUuid: string): Promise<Array<RemovedFileDescription>> {
const removedFileDescriptions: RemovedFileDescription[] = []
return []
let directoryExists: boolean
try {
await promises.access(`${this.fileUploadPath}/${userOrSharedVaultUuid}`)
directoryExists = true
} catch (error) {
directoryExists = false
}
if (!directoryExists) {
return []
}
const files = await promises.readdir(`${this.fileUploadPath}/${userOrSharedVaultUuid}`, { withFileTypes: true })
for (const file of files) {
const filePath = `${this.fileUploadPath}/${userOrSharedVaultUuid}/${file.name}`
const fileByteSize = await this.remove(`${userOrSharedVaultUuid}/${file.name}`)
removedFileDescriptions.push({
filePath,
fileByteSize,
userOrSharedVaultUuid,
fileName: file.name,
})
}
return removedFileDescriptions
}
async remove(filePath: string): Promise<number> {

View File

@@ -13,6 +13,7 @@ describe('ValetTokenAuthMiddleware', () => {
const logger = {
debug: jest.fn(),
error: jest.fn(),
} as unknown as jest.Mocked<Logger>
const createMiddleware = () => new ValetTokenAuthMiddleware(tokenDecoder, logger)
@@ -222,4 +223,27 @@ describe('ValetTokenAuthMiddleware', () => {
expect(next).toHaveBeenCalledWith(error)
})
it('should throw an error if the valet token indicates an ongoing transition', async () => {
request.headers['x-valet-token'] = 'valet-token'
tokenDecoder.decodeToken = jest.fn().mockReturnValue({
userUuid: '1-2-3',
permittedResources: [
{
remoteIdentifier: '00000000-0000-0000-0000-000000000000',
unencryptedFileSize: 30,
},
],
permittedOperation: 'write',
uploadBytesLimit: -1,
uploadBytesUsed: 80,
ongoingTransition: true,
})
await createMiddleware().handler(request, response, next)
expect(response.status).toHaveBeenCalledWith(500)
expect(next).not.toHaveBeenCalled()
})
})

View File

@@ -46,6 +46,19 @@ export class ValetTokenAuthMiddleware extends BaseMiddleware {
return
}
if (valetTokenData.ongoingTransition === true) {
this.logger.error(`Cannot perform file operations for user ${valetTokenData.userUuid} during transition`)
response.status(500).send({
error: {
tag: 'ongoing-transition',
message: 'Cannot perform file operations during transition',
},
})
return
}
for (const resource of valetTokenData.permittedResources) {
const resourceUuidOrError = Uuid.create(resource.remoteIdentifier)
if (resourceUuidOrError.isFailed()) {

View File

@@ -59,7 +59,7 @@ export class S3FileRemover implements FileRemoverInterface {
fileByteSize: file.Size as number,
fileName: file.Key.replace(`${userUuid}/`, ''),
filePath: file.Key,
userUuid,
userOrSharedVaultUuid: userUuid,
})
}

View File

@@ -3,6 +3,26 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.16.9](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.16.8...@standardnotes/home-server@1.16.9) (2023-09-28)
**Note:** Version bump only for package @standardnotes/home-server
## [1.16.8](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.16.7...@standardnotes/home-server@1.16.8) (2023-09-27)
**Note:** Version bump only for package @standardnotes/home-server
## [1.16.7](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.16.6...@standardnotes/home-server@1.16.7) (2023-09-26)
**Note:** Version bump only for package @standardnotes/home-server
## [1.16.6](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.16.5...@standardnotes/home-server@1.16.6) (2023-09-25)
**Note:** Version bump only for package @standardnotes/home-server
## [1.16.5](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.16.4...@standardnotes/home-server@1.16.5) (2023-09-25)
**Note:** Version bump only for package @standardnotes/home-server
## [1.16.4](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.16.3...@standardnotes/home-server@1.16.4) (2023-09-25)
**Note:** Version bump only for package @standardnotes/home-server

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/home-server",
"version": "1.16.4",
"version": "1.16.9",
"engines": {
"node": ">=18.0.0 <21.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.7.0](https://github.com/standardnotes/server/compare/@standardnotes/predicates@1.6.11...@standardnotes/predicates@1.7.0) (2023-09-26)
### Features
* refactor handling revision creation from dump ([#854](https://github.com/standardnotes/server/issues/854)) ([ca6dbc0](https://github.com/standardnotes/server/commit/ca6dbc00537bb20f508f9310b1a838421f53a643))
## [1.6.11](https://github.com/standardnotes/server/compare/@standardnotes/predicates@1.6.10...@standardnotes/predicates@1.6.11) (2023-09-04)
**Note:** Version bump only for package @standardnotes/predicates

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/predicates",
"version": "1.6.11",
"version": "1.7.0",
"engines": {
"node": ">=18.0.0 <21.0.0"
},
@@ -22,7 +22,7 @@
"start": "tsc -p tsconfig.json --watch",
"build": "tsc --build",
"lint": "eslint . --ext .ts",
"test": "jest spec --coverage --passWithNoTests"
"test": "jest --coverage --no-cache --passWithNoTests"
},
"devDependencies": {
"@types/jest": "^29.5.1",

View File

@@ -3,6 +3,28 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.38.2](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.38.1...@standardnotes/revisions-server@1.38.2) (2023-09-28)
**Note:** Version bump only for package @standardnotes/revisions-server
## [1.38.1](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.38.0...@standardnotes/revisions-server@1.38.1) (2023-09-27)
**Note:** Version bump only for package @standardnotes/revisions-server
# [1.38.0](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.37.3...@standardnotes/revisions-server@1.38.0) (2023-09-26)
### Features
* refactor handling revision creation from dump ([#854](https://github.com/standardnotes/server/issues/854)) ([ca6dbc0](https://github.com/standardnotes/server/commit/ca6dbc00537bb20f508f9310b1a838421f53a643))
## [1.37.3](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.37.2...@standardnotes/revisions-server@1.37.3) (2023-09-25)
**Note:** Version bump only for package @standardnotes/revisions-server
## [1.37.2](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.37.1...@standardnotes/revisions-server@1.37.2) (2023-09-25)
**Note:** Version bump only for package @standardnotes/revisions-server
## [1.37.1](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.37.0...@standardnotes/revisions-server@1.37.1) (2023-09-25)
**Note:** Version bump only for package @standardnotes/revisions-server

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/revisions-server",
"version": "1.37.1",
"version": "1.38.2",
"engines": {
"node": ">=18.0.0 <21.0.0"
},
@@ -20,7 +20,7 @@
"lint": "eslint . --ext .ts",
"lint:fix": "eslint . --ext .ts --fix",
"pretest": "yarn lint && yarn build",
"test": "jest --coverage --config=./jest.config.js --maxWorkers=50%",
"test": "jest --coverage --no-cache --config=./jest.config.js --maxWorkers=50%",
"start": "yarn node dist/bin/server.js",
"worker": "yarn node dist/bin/worker.js"
},

View File

@@ -71,6 +71,7 @@ import { TransitionRequestedEventHandler } from '../Domain/Handler/TransitionReq
import { SharedVaultRemovedEventHandler } from '../Domain/Handler/SharedVaultRemovedEventHandler'
import { TransitionRepositoryInterface } from '../Domain/Transition/TransitionRepositoryInterface'
import { RedisTransitionRepository } from '../Infra/Redis/RedisTransitionRepository'
import { CreateRevisionFromDump } from '../Domain/UseCase/CreateRevisionFromDump/CreateRevisionFromDump'
export class ContainerConfigLoader {
constructor(private mode: 'server' | 'worker' = 'server') {}
@@ -330,6 +331,21 @@ export class ContainerConfigLoader {
.toDynamicValue((context: interfaces.Context) => {
return new RevisionMetadataHttpMapper(context.container.get(TYPES.Revisions_GetRequiredRoleToViewRevision))
})
container
.bind<MapperInterface<Revision, string>>(TYPES.Revisions_RevisionItemStringMapper)
.toDynamicValue(() => new RevisionItemStringMapper())
container
.bind<DumpRepositoryInterface>(TYPES.Revisions_DumpRepository)
.toConstantValue(
env.get('S3_AWS_REGION', true)
? new S3DumpRepository(
container.get(TYPES.Revisions_S3_BACKUP_BUCKET_NAME),
container.get(TYPES.Revisions_S3),
container.get(TYPES.Revisions_RevisionItemStringMapper),
)
: new FSDumpRepository(container.get(TYPES.Revisions_RevisionItemStringMapper)),
)
// use cases
container
@@ -385,6 +401,14 @@ export class ContainerConfigLoader {
: container.get<RevisionRepositoryInterface>(TYPES.Revisions_SQLRevisionRepository),
),
)
container
.bind<CreateRevisionFromDump>(TYPES.Revisions_CreateRevisionFromDump)
.toConstantValue(
new CreateRevisionFromDump(
container.get<DumpRepositoryInterface>(TYPES.Revisions_DumpRepository),
container.get<RevisionRepositoryResolverInterface>(TYPES.Revisions_RevisionRepositoryResolver),
),
)
// env vars
container.bind(TYPES.Revisions_AUTH_JWT_SECRET).toConstantValue(env.get('AUTH_JWT_SECRET'))
@@ -409,31 +433,12 @@ export class ContainerConfigLoader {
)
})
// Map
container
.bind<MapperInterface<Revision, string>>(TYPES.Revisions_RevisionItemStringMapper)
.toDynamicValue(() => new RevisionItemStringMapper())
container
.bind<DumpRepositoryInterface>(TYPES.Revisions_DumpRepository)
.toConstantValue(
env.get('S3_AWS_REGION', true)
? new S3DumpRepository(
container.get(TYPES.Revisions_S3_BACKUP_BUCKET_NAME),
container.get(TYPES.Revisions_S3),
container.get(TYPES.Revisions_RevisionItemStringMapper),
container.get(TYPES.Revisions_Logger),
)
: new FSDumpRepository(container.get(TYPES.Revisions_RevisionItemStringMapper)),
)
// Handlers
container
.bind<ItemDumpedEventHandler>(TYPES.Revisions_ItemDumpedEventHandler)
.toConstantValue(
new ItemDumpedEventHandler(
container.get<DumpRepositoryInterface>(TYPES.Revisions_DumpRepository),
container.get<RevisionRepositoryResolverInterface>(TYPES.Revisions_RevisionRepositoryResolver),
container.get<CreateRevisionFromDump>(TYPES.Revisions_CreateRevisionFromDump),
container.get<winston.Logger>(TYPES.Revisions_Logger),
),
)

View File

@@ -49,6 +49,7 @@ const TYPES = {
'Revisions_TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser',
),
Revisions_RemoveRevisionsFromSharedVault: Symbol.for('Revisions_RemoveRevisionsFromSharedVault'),
Revisions_CreateRevisionFromDump: Symbol.for('Revisions_CreateRevisionFromDump'),
// Controller
Revisions_ControllerContainer: Symbol.for('Revisions_ControllerContainer'),
Revisions_RevisionsController: Symbol.for('Revisions_RevisionsController'),

View File

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

View File

@@ -1,79 +0,0 @@
import { ItemDumpedEvent } from '@standardnotes/domain-events'
import { Logger } from 'winston'
import { Uuid, ContentType, Dates } from '@standardnotes/domain-core'
import { DumpRepositoryInterface } from '../Dump/DumpRepositoryInterface'
import { Revision } from '../Revision/Revision'
import { RevisionRepositoryInterface } from '../Revision/RevisionRepositoryInterface'
import { ItemDumpedEventHandler } from './ItemDumpedEventHandler'
import { RevisionRepositoryResolverInterface } from '../Revision/RevisionRepositoryResolverInterface'
describe('ItemDumpedEventHandler', () => {
let dumpRepository: DumpRepositoryInterface
let revisionRepository: RevisionRepositoryInterface
let revisionRepositoryResolver: RevisionRepositoryResolverInterface
let revision: Revision
let event: ItemDumpedEvent
let logger: Logger
const createHandler = () => new ItemDumpedEventHandler(dumpRepository, revisionRepositoryResolver, logger)
beforeEach(() => {
revision = Revision.create({
itemUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
userUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
content: 'test',
contentType: ContentType.create('Note').getValue(),
itemsKeyId: 'test',
encItemKey: 'test',
authHash: 'test',
creationDate: new Date(1),
dates: Dates.create(new Date(1), new Date(2)).getValue(),
}).getValue()
dumpRepository = {} as jest.Mocked<DumpRepositoryInterface>
dumpRepository.getRevisionFromDumpPath = jest.fn().mockReturnValue(revision)
dumpRepository.removeDump = jest.fn()
revisionRepository = {} as jest.Mocked<RevisionRepositoryInterface>
revisionRepository.insert = jest.fn()
revisionRepositoryResolver = {} as jest.Mocked<RevisionRepositoryResolverInterface>
revisionRepositoryResolver.resolve = jest.fn().mockReturnValue(revisionRepository)
event = {} as jest.Mocked<ItemDumpedEvent>
event.payload = {
fileDumpPath: 'foobar',
roleNames: ['CORE_USER'],
}
logger = {} as jest.Mocked<Logger>
logger.debug = jest.fn()
logger.error = jest.fn()
})
it('should save a revision from file dump', async () => {
await createHandler().handle(event)
expect(revisionRepository.insert).toHaveBeenCalled()
expect(dumpRepository.removeDump).toHaveBeenCalled()
})
it('should do nothing if role names are not valid', async () => {
event.payload.roleNames = ['INVALID_ROLE_NAME']
await createHandler().handle(event)
expect(revisionRepository.insert).not.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.insert).not.toHaveBeenCalled()
expect(dumpRepository.removeDump).toHaveBeenCalled()
})
})

View File

@@ -1,44 +1,22 @@
import { DomainEventHandlerInterface, ItemDumpedEvent } from '@standardnotes/domain-events'
import { DumpRepositoryInterface } from '../Dump/DumpRepositoryInterface'
import { RevisionRepositoryResolverInterface } from '../Revision/RevisionRepositoryResolverInterface'
import { RoleNameCollection } from '@standardnotes/domain-core'
import { Logger } from 'winston'
import { CreateRevisionFromDump } from '../UseCase/CreateRevisionFromDump/CreateRevisionFromDump'
export class ItemDumpedEventHandler implements DomainEventHandlerInterface {
constructor(
private dumpRepository: DumpRepositoryInterface,
private revisionRepositoryResolver: RevisionRepositoryResolverInterface,
private createRevisionFromDump: CreateRevisionFromDump,
private logger: Logger,
) {}
async handle(event: ItemDumpedEvent): Promise<void> {
const revision = await this.dumpRepository.getRevisionFromDumpPath(event.payload.fileDumpPath)
if (revision === null) {
this.logger.error(`Revision not found for dump path ${event.payload.fileDumpPath}`)
const result = await this.createRevisionFromDump.execute({
filePath: event.payload.fileDumpPath,
roleNames: event.payload.roleNames,
})
await this.dumpRepository.removeDump(event.payload.fileDumpPath)
return
if (result.isFailed()) {
this.logger.error(`Item dumped event handler failed: ${result.getError()}`)
}
const roleNamesOrError = RoleNameCollection.create(event.payload.roleNames)
if (roleNamesOrError.isFailed()) {
this.logger.error(`Invalid role names ${event.payload.roleNames}`)
await this.dumpRepository.removeDump(event.payload.fileDumpPath)
return
}
const roleNames = roleNamesOrError.getValue()
const revisionRepository = this.revisionRepositoryResolver.resolve(roleNames)
const successfullyInserted = await revisionRepository.insert(revision)
if (!successfullyInserted) {
this.logger.error(`Could not insert revision ${revision.id.toString()}`)
}
await this.dumpRepository.removeDump(event.payload.fileDumpPath)
}
}

View File

@@ -0,0 +1,99 @@
import { Uuid, ContentType, Dates, Result } from '@standardnotes/domain-core'
import { DumpRepositoryInterface } from '../../Dump/DumpRepositoryInterface'
import { Revision } from '../../Revision/Revision'
import { RevisionRepositoryInterface } from '../../Revision/RevisionRepositoryInterface'
import { RevisionRepositoryResolverInterface } from '../../Revision/RevisionRepositoryResolverInterface'
import { CreateRevisionFromDump } from './CreateRevisionFromDump'
describe('CreateRevisionFromDump', () => {
let revisionRepository: RevisionRepositoryInterface
let revision: Revision
let dumpRepository: DumpRepositoryInterface
let revisionRepositoryResolver: RevisionRepositoryResolverInterface
const createUseCase = () => new CreateRevisionFromDump(dumpRepository, revisionRepositoryResolver)
beforeEach(() => {
revision = Revision.create({
itemUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
userUuid: Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue(),
content: 'test',
contentType: ContentType.create('Note').getValue(),
itemsKeyId: 'test',
encItemKey: 'test',
authHash: 'test',
creationDate: new Date(1),
dates: Dates.create(new Date(1), new Date(2)).getValue(),
}).getValue()
dumpRepository = {} as jest.Mocked<DumpRepositoryInterface>
dumpRepository.getRevisionFromDumpPath = jest.fn().mockReturnValue(Result.ok(revision))
dumpRepository.removeDump = jest.fn()
revisionRepository = {} as jest.Mocked<RevisionRepositoryInterface>
revisionRepository.insert = jest.fn().mockReturnValue(true)
revisionRepositoryResolver = {} as jest.Mocked<RevisionRepositoryResolverInterface>
revisionRepositoryResolver.resolve = jest.fn().mockReturnValue(revisionRepository)
})
it('should create a revision from file dump', async () => {
const result = await createUseCase().execute({
filePath: 'foobar',
roleNames: ['CORE_USER'],
})
expect(result.isFailed()).toBeFalsy()
expect(revisionRepository.insert).toHaveBeenCalled()
expect(dumpRepository.removeDump).toHaveBeenCalled()
})
it('should fail if file path is empty', async () => {
const result = await createUseCase().execute({
filePath: '',
roleNames: ['CORE_USER'],
})
expect(result.isFailed()).toBeTruthy()
expect(revisionRepository.insert).not.toHaveBeenCalled()
expect(dumpRepository.removeDump).not.toHaveBeenCalled()
})
it('should fail if role name is invalid', async () => {
const result = await createUseCase().execute({
filePath: 'foobar',
roleNames: ['INVALID_ROLE_NAME'],
})
expect(result.isFailed()).toBeTruthy()
expect(revisionRepository.insert).not.toHaveBeenCalled()
expect(dumpRepository.removeDump).toHaveBeenCalled()
})
it('should fail if revision cannot be found', async () => {
dumpRepository.getRevisionFromDumpPath = jest.fn().mockReturnValue(Result.fail('Oops'))
const result = await createUseCase().execute({
filePath: 'foobar',
roleNames: ['CORE_USER'],
})
expect(result.isFailed()).toBeTruthy()
expect(revisionRepository.insert).not.toHaveBeenCalled()
expect(dumpRepository.removeDump).toHaveBeenCalled()
})
it('should fail if revision cannot be inserted', async () => {
revisionRepository.insert = jest.fn().mockReturnValue(false)
const result = await createUseCase().execute({
filePath: 'foobar',
roleNames: ['CORE_USER'],
})
expect(result.isFailed()).toBeTruthy()
expect(revisionRepository.insert).toHaveBeenCalled()
expect(dumpRepository.removeDump).toHaveBeenCalled()
})
})

View File

@@ -0,0 +1,47 @@
import { Result, RoleNameCollection, UseCaseInterface, Validator } from '@standardnotes/domain-core'
import { DumpRepositoryInterface } from '../../Dump/DumpRepositoryInterface'
import { RevisionRepositoryResolverInterface } from '../../Revision/RevisionRepositoryResolverInterface'
import { CreateRevisionFromDumpDTO } from './CreateRevisionFromDumpDTO'
export class CreateRevisionFromDump implements UseCaseInterface<void> {
constructor(
private dumpRepository: DumpRepositoryInterface,
private revisionRepositoryResolver: RevisionRepositoryResolverInterface,
) {}
async execute(dto: CreateRevisionFromDumpDTO): Promise<Result<void>> {
const filePathValidationResult = Validator.isNotEmptyString(dto.filePath)
if (filePathValidationResult.isFailed()) {
return Result.fail(`Could not create revision from dump: ${filePathValidationResult.getError()}`)
}
const revisionOrError = await this.dumpRepository.getRevisionFromDumpPath(dto.filePath)
if (revisionOrError.isFailed()) {
await this.dumpRepository.removeDump(dto.filePath)
return Result.fail(`Could not create revision from dump: ${revisionOrError.getError()}`)
}
const revision = revisionOrError.getValue()
const roleNamesOrError = RoleNameCollection.create(dto.roleNames)
if (roleNamesOrError.isFailed()) {
await this.dumpRepository.removeDump(dto.filePath)
return Result.fail(`Could not create revision from dump: ${roleNamesOrError.getError()}`)
}
const roleNames = roleNamesOrError.getValue()
const revisionRepository = this.revisionRepositoryResolver.resolve(roleNames)
const successfullyInserted = await revisionRepository.insert(revision)
if (!successfullyInserted) {
await this.dumpRepository.removeDump(dto.filePath)
return Result.fail(`Could not insert revision from dump: ${revision.id.toString()}`)
}
await this.dumpRepository.removeDump(dto.filePath)
return Result.ok()
}
}

View File

@@ -0,0 +1,4 @@
export interface CreateRevisionFromDumpDTO {
filePath: string
roleNames: string[]
}

View File

@@ -1,4 +1,4 @@
import { MapperInterface } from '@standardnotes/domain-core'
import { MapperInterface, Result } from '@standardnotes/domain-core'
import { promises } from 'fs'
import { DumpRepositoryInterface } from '../../Domain/Dump/DumpRepositoryInterface'
@@ -7,12 +7,16 @@ import { Revision } from '../../Domain/Revision/Revision'
export class FSDumpRepository implements DumpRepositoryInterface {
constructor(private revisionStringItemMapper: MapperInterface<Revision, string>) {}
async getRevisionFromDumpPath(path: string): Promise<Revision | null> {
const contents = (await promises.readFile(path)).toString()
async getRevisionFromDumpPath(path: string): Promise<Result<Revision>> {
try {
const contents = (await promises.readFile(path)).toString()
const revision = this.revisionStringItemMapper.toDomain(contents)
const revision = this.revisionStringItemMapper.toDomain(contents)
return revision
return Result.ok(revision)
} catch (error) {
return Result.fail(`Failed to read dump file: ${(error as Error).message}`)
}
}
async removeDump(path: string): Promise<void> {

View File

@@ -1,6 +1,5 @@
import { DeleteObjectCommand, GetObjectCommand, S3Client } from '@aws-sdk/client-s3'
import { MapperInterface } from '@standardnotes/domain-core'
import { Logger } from 'winston'
import { MapperInterface, Result } from '@standardnotes/domain-core'
import { DumpRepositoryInterface } from '../../Domain/Dump/DumpRepositoryInterface'
import { Revision } from '../../Domain/Revision/Revision'
@@ -10,26 +9,27 @@ export class S3DumpRepository implements DumpRepositoryInterface {
private dumpBucketName: string,
private s3Client: S3Client,
private revisionStringItemMapper: MapperInterface<Revision, string>,
private logger: Logger,
) {}
async getRevisionFromDumpPath(path: string): Promise<Revision | null> {
const s3Object = await this.s3Client.send(
new GetObjectCommand({
Bucket: this.dumpBucketName,
Key: path,
}),
)
async getRevisionFromDumpPath(path: string): Promise<Result<Revision>> {
try {
const s3Object = await this.s3Client.send(
new GetObjectCommand({
Bucket: this.dumpBucketName,
Key: path,
}),
)
if (s3Object.Body === undefined) {
this.logger.warn(`Could not find revision dump at path: ${path}`)
if (s3Object.Body === undefined) {
return Result.fail(`Could not find revision dump at path: ${path}`)
}
return null
const revision = this.revisionStringItemMapper.toDomain(await s3Object.Body.transformToString())
return Result.ok(revision)
} catch (error) {
return Result.fail(`Failed to read dump file: ${(error as Error).message}`)
}
const revision = this.revisionStringItemMapper.toDomain(await s3Object.Body.transformToString())
return revision
}
async removeDump(path: string): Promise<void> {

View File

@@ -3,6 +3,28 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.21.2](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.21.1...@standardnotes/scheduler-server@1.21.2) (2023-09-28)
**Note:** Version bump only for package @standardnotes/scheduler-server
## [1.21.1](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.21.0...@standardnotes/scheduler-server@1.21.1) (2023-09-27)
**Note:** Version bump only for package @standardnotes/scheduler-server
# [1.21.0](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.20.56...@standardnotes/scheduler-server@1.21.0) (2023-09-26)
### Features
* refactor handling revision creation from dump ([#854](https://github.com/standardnotes/server/issues/854)) ([ca6dbc0](https://github.com/standardnotes/server/commit/ca6dbc00537bb20f508f9310b1a838421f53a643))
## [1.20.56](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.20.55...@standardnotes/scheduler-server@1.20.56) (2023-09-25)
**Note:** Version bump only for package @standardnotes/scheduler-server
## [1.20.55](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.20.54...@standardnotes/scheduler-server@1.20.55) (2023-09-25)
**Note:** Version bump only for package @standardnotes/scheduler-server
## [1.20.54](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.20.53...@standardnotes/scheduler-server@1.20.54) (2023-09-25)
**Note:** Version bump only for package @standardnotes/scheduler-server

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/scheduler-server",
"version": "1.20.54",
"version": "1.21.2",
"engines": {
"node": ">=18.0.0 <21.0.0"
},
@@ -16,7 +16,7 @@
"lint": "eslint . --ext .ts",
"lint:fix": "eslint . --ext .ts --fix",
"pretest": "yarn lint && yarn build",
"test": "jest --coverage --config=./jest.config.js --maxWorkers=50%",
"test": "jest --coverage --no-cache --config=./jest.config.js --maxWorkers=50%",
"worker": "yarn node dist/bin/worker.js",
"verify:jobs": "yarn node dist/bin/verify.js",
"setup:env": "cp .env.sample .env",

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.
# [1.15.0](https://github.com/standardnotes/server/compare/@standardnotes/security@1.14.0...@standardnotes/security@1.15.0) (2023-09-28)
### Features
* block file operations during transition ([#856](https://github.com/standardnotes/server/issues/856)) ([676cf36](https://github.com/standardnotes/server/commit/676cf36f8d769aa433880b2800f0457d06fbbf14))
# [1.14.0](https://github.com/standardnotes/server/compare/@standardnotes/security@1.13.1...@standardnotes/security@1.14.0) (2023-09-26)
### Features
* refactor handling revision creation from dump ([#854](https://github.com/standardnotes/server/issues/854)) ([ca6dbc0](https://github.com/standardnotes/server/commit/ca6dbc00537bb20f508f9310b1a838421f53a643))
## [1.13.1](https://github.com/standardnotes/server/compare/@standardnotes/security@1.13.0...@standardnotes/security@1.13.1) (2023-09-12)
### Bug Fixes

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/security",
"version": "1.13.1",
"version": "1.15.0",
"engines": {
"node": ">=18.0.0 <21.0.0"
},
@@ -22,7 +22,7 @@
"start": "tsc -p tsconfig.json --watch",
"build": "tsc --build",
"lint": "eslint . --ext .ts",
"test": "jest spec --coverage"
"test": "jest --coverage --no-cache"
},
"dependencies": {
"jsonwebtoken": "^9.0.0",

View File

@@ -11,4 +11,5 @@ export type ValetTokenData = {
}>
uploadBytesUsed: number
uploadBytesLimit: number
ongoingTransition?: boolean
}

View File

@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.21.41](https://github.com/standardnotes/server/compare/@standardnotes/settings@1.21.40...@standardnotes/settings@1.21.41) (2023-09-27)
**Note:** Version bump only for package @standardnotes/settings
## [1.21.40](https://github.com/standardnotes/server/compare/@standardnotes/settings@1.21.39...@standardnotes/settings@1.21.40) (2023-09-26)
**Note:** Version bump only for package @standardnotes/settings
## [1.21.39](https://github.com/standardnotes/server/compare/@standardnotes/settings@1.21.38...@standardnotes/settings@1.21.39) (2023-09-25)
**Note:** Version bump only for package @standardnotes/settings
## [1.21.38](https://github.com/standardnotes/server/compare/@standardnotes/settings@1.21.37...@standardnotes/settings@1.21.38) (2023-09-25)
**Note:** Version bump only for package @standardnotes/settings
## [1.21.37](https://github.com/standardnotes/server/compare/@standardnotes/settings@1.21.36...@standardnotes/settings@1.21.37) (2023-09-21)
**Note:** Version bump only for package @standardnotes/settings

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/settings",
"version": "1.21.37",
"version": "1.21.41",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -3,6 +3,34 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.109.2](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.109.1...@standardnotes/syncing-server@1.109.2) (2023-09-28)
**Note:** Version bump only for package @standardnotes/syncing-server
## [1.109.1](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.109.0...@standardnotes/syncing-server@1.109.1) (2023-09-27)
### Bug Fixes
* removing items in a vault and notifying about designated survivor ([#855](https://github.com/standardnotes/syncing-server-js/issues/855)) ([1d06ffe](https://github.com/standardnotes/syncing-server-js/commit/1d06ffe9d51722ada7baa040e1d5ed351fc28f39))
# [1.109.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.108.2...@standardnotes/syncing-server@1.109.0) (2023-09-26)
### Features
* refactor handling revision creation from dump ([#854](https://github.com/standardnotes/syncing-server-js/issues/854)) ([ca6dbc0](https://github.com/standardnotes/syncing-server-js/commit/ca6dbc00537bb20f508f9310b1a838421f53a643))
## [1.108.2](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.108.1...@standardnotes/syncing-server@1.108.2) (2023-09-25)
**Note:** Version bump only for package @standardnotes/syncing-server
## [1.108.1](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.108.0...@standardnotes/syncing-server@1.108.1) (2023-09-25)
### Bug Fixes
* refactor the structure of notifications ([#853](https://github.com/standardnotes/syncing-server-js/issues/853)) ([cebab59](https://github.com/standardnotes/syncing-server-js/commit/cebab59a026c6868886e0945787a8ddb0442fbc3))
* **syncing-server:** another spec ([3d5e747](https://github.com/standardnotes/syncing-server-js/commit/3d5e7475901c5eb7741f461a35febdb996bcfd1d))
* **syncing-server:** specs ([9446774](https://github.com/standardnotes/syncing-server-js/commit/94467747acca83b954129702111f903c3d1ceab8))
# [1.108.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.107.0...@standardnotes/syncing-server@1.108.0) (2023-09-25)
### Features

View File

@@ -0,0 +1,11 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class RemoveNotifications1695643525793 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('DELETE FROM `notifications`')
}
public async down(): Promise<void> {
return
}
}

View File

@@ -0,0 +1,11 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class RemoveNotifications1695643525793 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('DELETE FROM `notifications`')
}
public async down(): Promise<void> {
return
}
}

View File

@@ -0,0 +1,11 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class RemoveNotifications1695643525793 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('DELETE FROM `notifications`')
}
public async down(): Promise<void> {
return
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/syncing-server",
"version": "1.108.0",
"version": "1.109.2",
"engines": {
"node": ">=18.0.0 <21.0.0"
},
@@ -20,7 +20,7 @@
"lint": "eslint . --ext .ts",
"lint:fix": "eslint . --ext .ts --fix",
"pretest": "yarn lint && yarn build",
"test": "jest --coverage --config=./jest.config.js --maxWorkers=50%",
"test": "jest --coverage --no-cache --config=./jest.config.js --maxWorkers=50%",
"start": "yarn node dist/bin/server.js",
"worker": "yarn node dist/bin/worker.js",
"content-size": "yarn node dist/bin/content.js",

View File

@@ -103,7 +103,7 @@ import { TypeORMSharedVaultInviteRepository } from '../Infra/TypeORM/TypeORMShar
import { UpdateSharedVaultInvite } from '../Domain/UseCase/SharedVaults/UpdateSharedVaultInvite/UpdateSharedVaultInvite'
import { AcceptInviteToSharedVault } from '../Domain/UseCase/SharedVaults/AcceptInviteToSharedVault/AcceptInviteToSharedVault'
import { AddUserToSharedVault } from '../Domain/UseCase/SharedVaults/AddUserToSharedVault/AddUserToSharedVault'
import { DeclineInviteToSharedVault } from '../Domain/UseCase/SharedVaults/DeclineInviteToSharedVault/DeclineInviteToSharedVault'
import { CancelInviteToSharedVault } from '../Domain/UseCase/SharedVaults/CancelInviteToSharedVault/CancelInviteToSharedVault'
import { DeleteSharedVaultInvitesToUser } from '../Domain/UseCase/SharedVaults/DeleteSharedVaultInvitesToUser/DeleteSharedVaultInvitesToUser'
import { DeleteSharedVaultInvitesSentByUser } from '../Domain/UseCase/SharedVaults/DeleteSharedVaultInvitesSentByUser/DeleteSharedVaultInvitesSentByUser'
import { GetSharedVaultInvitesSentByUser } from '../Domain/UseCase/SharedVaults/GetSharedVaultInvitesSentByUser/GetSharedVaultInvitesSentByUser'
@@ -175,6 +175,7 @@ import { TransferSharedVault } from '../Domain/UseCase/SharedVaults/TransferShar
import { TransitionRepositoryInterface } from '../Domain/Transition/TransitionRepositoryInterface'
import { RedisTransitionRepository } from '../Infra/Redis/RedisTransitionRepository'
import { TransferSharedVaultItems } from '../Domain/UseCase/SharedVaults/TransferSharedVaultItems/TransferSharedVaultItems'
import { DumpItem } from '../Domain/UseCase/Syncing/DumpItem/DumpItem'
export class ContainerConfigLoader {
private readonly DEFAULT_CONTENT_SIZE_TRANSFER_LIMIT = 10_000_000
@@ -590,6 +591,24 @@ export class ContainerConfigLoader {
]),
)
container
.bind<ItemBackupServiceInterface>(TYPES.Sync_ItemBackupService)
.toConstantValue(
env.get('S3_AWS_REGION', true)
? new S3ItemBackupService(
container.get(TYPES.Sync_S3_BACKUP_BUCKET_NAME),
container.get(TYPES.Sync_ItemBackupMapper),
container.get(TYPES.Sync_ItemHttpMapper),
container.get(TYPES.Sync_Logger),
container.get(TYPES.Sync_S3),
)
: new FSItemBackupService(
container.get(TYPES.Sync_FILE_UPLOAD_PATH),
container.get(TYPES.Sync_ItemBackupMapper),
container.get(TYPES.Sync_Logger),
),
)
// use cases
container
.bind<GetItems>(TYPES.Sync_GetItems)
@@ -749,9 +768,9 @@ export class ContainerConfigLoader {
),
)
container
.bind<DeclineInviteToSharedVault>(TYPES.Sync_DeclineInviteToSharedVault)
.bind<CancelInviteToSharedVault>(TYPES.Sync_DeclineInviteToSharedVault)
.toConstantValue(
new DeclineInviteToSharedVault(
new CancelInviteToSharedVault(
container.get<SharedVaultInviteRepositoryInterface>(TYPES.Sync_SharedVaultInviteRepository),
container.get<AddNotificationForUser>(TYPES.Sync_AddNotificationForUser),
),
@@ -880,6 +899,7 @@ export class ContainerConfigLoader {
container.get<TimerInterface>(TYPES.Sync_Timer),
container.get<DomainEventFactoryInterface>(TYPES.Sync_DomainEventFactory),
container.get<DomainEventPublisherInterface>(TYPES.Sync_DomainEventPublisher),
container.get<AddNotificationForUser>(TYPES.Sync_AddNotificationForUser),
),
)
container
@@ -918,7 +938,7 @@ export class ContainerConfigLoader {
container.get<SharedVaultUserRepositoryInterface>(TYPES.Sync_SharedVaultUserRepository),
container.get<SharedVaultInviteRepositoryInterface>(TYPES.Sync_SharedVaultInviteRepository),
container.get<RemoveUserFromSharedVault>(TYPES.Sync_RemoveSharedVaultUser),
container.get<DeclineInviteToSharedVault>(TYPES.Sync_DeclineInviteToSharedVault),
container.get<CancelInviteToSharedVault>(TYPES.Sync_DeclineInviteToSharedVault),
container.get<DomainEventFactoryInterface>(TYPES.Sync_DomainEventFactory),
container.get<DomainEventPublisherInterface>(TYPES.Sync_DomainEventPublisher),
container.get<TransferSharedVault>(TYPES.Sync_TransferSharedVault),
@@ -932,6 +952,16 @@ export class ContainerConfigLoader {
container.get<DeleteSharedVault>(TYPES.Sync_DeleteSharedVault),
),
)
container
.bind<DumpItem>(TYPES.Sync_DumpItem)
.toConstantValue(
new DumpItem(
container.get<ItemRepositoryResolverInterface>(TYPES.Sync_ItemRepositoryResolver),
container.get<ItemBackupServiceInterface>(TYPES.Sync_ItemBackupService),
container.get<DomainEventFactoryInterface>(TYPES.Sync_DomainEventFactory),
container.get<DomainEventPublisherInterface>(TYPES.Sync_DomainEventPublisher),
),
)
// Services
container
@@ -959,24 +989,6 @@ export class ContainerConfigLoader {
)
})
container
.bind<ItemBackupServiceInterface>(TYPES.Sync_ItemBackupService)
.toConstantValue(
env.get('S3_AWS_REGION', true)
? new S3ItemBackupService(
container.get(TYPES.Sync_S3_BACKUP_BUCKET_NAME),
container.get(TYPES.Sync_ItemBackupMapper),
container.get(TYPES.Sync_ItemHttpMapper),
container.get(TYPES.Sync_Logger),
container.get(TYPES.Sync_S3),
)
: new FSItemBackupService(
container.get(TYPES.Sync_FILE_UPLOAD_PATH),
container.get(TYPES.Sync_ItemBackupMapper),
container.get(TYPES.Sync_Logger),
),
)
// Handlers
container
.bind<DuplicateItemSyncedEventHandler>(TYPES.Sync_DuplicateItemSyncedEventHandler)
@@ -1002,10 +1014,8 @@ export class ContainerConfigLoader {
.bind<ItemRevisionCreationRequestedEventHandler>(TYPES.Sync_ItemRevisionCreationRequestedEventHandler)
.toConstantValue(
new ItemRevisionCreationRequestedEventHandler(
container.get<ItemRepositoryResolverInterface>(TYPES.Sync_ItemRepositoryResolver),
container.get<ItemBackupServiceInterface>(TYPES.Sync_ItemBackupService),
container.get<DomainEventFactoryInterface>(TYPES.Sync_DomainEventFactory),
container.get<DomainEventPublisherInterface>(TYPES.Sync_DomainEventPublisher),
container.get<DumpItem>(TYPES.Sync_DumpItem),
container.get<Logger>(TYPES.Sync_Logger),
),
)
container

View File

@@ -92,6 +92,7 @@ const TYPES = {
Sync_RemoveUserFromSharedVaults: Symbol.for('Sync_RemoveUserFromSharedVaults'),
Sync_TransferSharedVault: Symbol.for('Sync_TransferSharedVault'),
Sync_TransferSharedVaultItems: Symbol.for('Sync_TransferSharedVaultItems'),
Sync_DumpItem: Symbol.for('Sync_DumpItem'),
// Handlers
Sync_AccountDeletionRequestedEventHandler: Symbol.for('Sync_AccountDeletionRequestedEventHandler'),
Sync_DuplicateItemSyncedEventHandler: Symbol.for('Sync_DuplicateItemSyncedEventHandler'),

View File

@@ -1,5 +1,5 @@
import { AccountDeletionRequestedEvent, DomainEventHandlerInterface } from '@standardnotes/domain-events'
import { RoleNameCollection } from '@standardnotes/domain-core'
import { RoleNameCollection, Uuid } from '@standardnotes/domain-core'
import { Logger } from 'winston'
import { ItemRepositoryResolverInterface } from '../Item/ItemRepositoryResolverInterface'
@@ -15,15 +15,25 @@ export class AccountDeletionRequestedEventHandler implements DomainEventHandlerI
) {}
async handle(event: AccountDeletionRequestedEvent): Promise<void> {
const userUuidOrError = Uuid.create(event.payload.userUuid)
if (userUuidOrError.isFailed()) {
this.logger.error(`AccountDeletionRequestedEventHandler failed: ${userUuidOrError.getError()}`)
return
}
const userUuid = userUuidOrError.getValue()
const roleNamesOrError = RoleNameCollection.create(event.payload.roleNames)
if (roleNamesOrError.isFailed()) {
this.logger.error(`AccountDeletionRequestedEventHandler failed: ${roleNamesOrError.getError()}`)
return
}
const roleNames = roleNamesOrError.getValue()
const itemRepository = this.itemRepositoryResolver.resolve(roleNames)
await itemRepository.deleteByUserUuid(event.payload.userUuid)
await itemRepository.deleteByUserUuidAndNotInSharedVault(userUuid)
const deletingVaultsResult = await this.deleteSharedVaults.execute({
ownerUuid: event.payload.userUuid,
@@ -34,6 +44,16 @@ export class AccountDeletionRequestedEventHandler implements DomainEventHandlerI
)
}
const deletedSharedVaultUuids = Array.from(deletingVaultsResult.getValue().keys())
this.logger.debug(
`Deleting items from shared vaults: ${deletedSharedVaultUuids.map((uuid) => uuid.value).join(', ')}`,
)
if (deletedSharedVaultUuids.length !== 0) {
await itemRepository.deleteByUserUuidInSharedVaults(userUuid, deletedSharedVaultUuids)
}
const deletingUserFromOtherVaultsResult = await this.removeUserFromSharedVaults.execute({
userUuid: event.payload.userUuid,
})

View File

@@ -1,125 +0,0 @@
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'
import { Uuid, ContentType, Dates, Timestamps, UniqueEntityId } from '@standardnotes/domain-core'
import { ItemRepositoryResolverInterface } from '../Item/ItemRepositoryResolverInterface'
describe('ItemRevisionCreationRequestedEventHandler', () => {
let itemRepositoryResolver: ItemRepositoryResolverInterface
let itemRepository: ItemRepositoryInterface
let event: ItemRevisionCreationRequestedEvent
let item: Item
let itemBackupService: ItemBackupServiceInterface
let domainEventFactory: DomainEventFactoryInterface
let domainEventPublisher: DomainEventPublisherInterface
const createHandler = () =>
new ItemRevisionCreationRequestedEventHandler(
itemRepositoryResolver,
itemBackupService,
domainEventFactory,
domainEventPublisher,
)
beforeEach(() => {
item = Item.create(
{
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
updatedWithSession: null,
content: 'foobar1',
contentType: ContentType.create(ContentType.TYPES.Note).getValue(),
encItemKey: null,
authHash: null,
itemsKeyId: null,
duplicateOf: null,
deleted: false,
dates: Dates.create(new Date(1616164633241311), new Date(1616164633241311)).getValue(),
timestamps: Timestamps.create(1616164633241311, 1616164633241311).getValue(),
},
new UniqueEntityId('00000000-0000-0000-0000-000000000000'),
).getValue()
itemRepository = {} as jest.Mocked<ItemRepositoryInterface>
itemRepository.findByUuid = jest.fn().mockReturnValue(item)
itemRepositoryResolver = {} as jest.Mocked<ItemRepositoryResolverInterface>
itemRepositoryResolver.resolve = jest.fn().mockReturnValue(itemRepository)
event = {} as jest.Mocked<ItemRevisionCreationRequestedEvent>
event.createdAt = new Date(1)
event.payload = {
itemUuid: '00000000-0000-0000-0000-000000000000',
roleNames: ['CORE_USER'],
}
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 do nothing if roles names are not valid', async () => {
event.payload.roleNames = ['INVALID_ROLE_NAME']
await createHandler().handle(event)
expect(domainEventPublisher.publish).not.toHaveBeenCalled()
expect(domainEventFactory.createItemDumpedEvent).not.toHaveBeenCalled()
})
it('should not create a revision for an item that does not exist', async () => {
itemRepository.findByUuid = jest.fn().mockReturnValue(null)
itemRepositoryResolver.resolve = jest.fn().mockReturnValue(itemRepository)
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()
})
it('should not create a revision if the item uuid is invalid', async () => {
event.payload.itemUuid = 'invalid-uuid'
await createHandler().handle(event)
expect(domainEventPublisher.publish).not.toHaveBeenCalled()
expect(domainEventFactory.createItemDumpedEvent).not.toHaveBeenCalled()
})
})

View File

@@ -1,59 +1,22 @@
import {
ItemRevisionCreationRequestedEvent,
DomainEventHandlerInterface,
DomainEventPublisherInterface,
} from '@standardnotes/domain-events'
import { RoleNameCollection, Uuid } from '@standardnotes/domain-core'
import { ItemRevisionCreationRequestedEvent, DomainEventHandlerInterface } from '@standardnotes/domain-events'
import { DomainEventFactoryInterface } from '../Event/DomainEventFactoryInterface'
import { ItemBackupServiceInterface } from '../Item/ItemBackupServiceInterface'
import { ItemRepositoryInterface } from '../Item/ItemRepositoryInterface'
import { ItemRepositoryResolverInterface } from '../Item/ItemRepositoryResolverInterface'
import { DumpItem } from '../UseCase/Syncing/DumpItem/DumpItem'
import { Logger } from 'winston'
export class ItemRevisionCreationRequestedEventHandler implements DomainEventHandlerInterface {
constructor(
private itemRepositoryResolver: ItemRepositoryResolverInterface,
private itemBackupService: ItemBackupServiceInterface,
private domainEventFactory: DomainEventFactoryInterface,
private domainEventPublisher: DomainEventPublisherInterface,
private dumpItem: DumpItem,
private logger: Logger,
) {}
async handle(event: ItemRevisionCreationRequestedEvent): Promise<void> {
const roleNamesOrError = RoleNameCollection.create(event.payload.roleNames)
if (roleNamesOrError.isFailed()) {
return
}
const roleNames = roleNamesOrError.getValue()
const result = await this.dumpItem.execute({
itemUuid: event.payload.itemUuid,
roleNames: event.payload.roleNames,
})
const itemRepository = this.itemRepositoryResolver.resolve(roleNames)
await this.createItemDump(event, itemRepository)
}
private async createItemDump(
event: ItemRevisionCreationRequestedEvent,
itemRepository: ItemRepositoryInterface,
): Promise<void> {
const itemUuidOrError = Uuid.create(event.payload.itemUuid)
if (itemUuidOrError.isFailed()) {
return
}
const itemUuid = itemUuidOrError.getValue()
const item = await itemRepository.findByUuid(itemUuid)
if (item === null) {
return
}
const fileDumpPath = await this.itemBackupService.dump(item)
if (fileDumpPath) {
await this.domainEventPublisher.publish(
this.domainEventFactory.createItemDumpedEvent({
fileDumpPath,
userUuid: event.meta.correlation.userIdentifier,
roleNames: event.payload.roleNames,
}),
)
if (result.isFailed()) {
this.logger.error(`Item revision requested handler failed: ${result.getError()}`)
}
}
}

View File

@@ -1,5 +1,10 @@
import { DomainEventHandlerInterface, SharedVaultFileMovedEvent } from '@standardnotes/domain-events'
import { NotificationPayload, NotificationType, Uuid } from '@standardnotes/domain-core'
import {
NotificationPayload,
NotificationPayloadIdentifierType,
NotificationType,
Uuid,
} from '@standardnotes/domain-core'
import { Logger } from 'winston'
import { UpdateStorageQuotaUsedInSharedVault } from '../UseCase/SharedVaults/UpdateStorageQuotaUsedInSharedVault/UpdateStorageQuotaUsedInSharedVault'
@@ -34,7 +39,10 @@ export class SharedVaultFileMovedEventHandler implements DomainEventHandlerInter
}
const notificationPayload = NotificationPayload.create({
sharedVaultUuid: sharedVaultUuid,
primaryIdentifier: sharedVaultUuid,
primaryIndentifierType: NotificationPayloadIdentifierType.create(
NotificationPayloadIdentifierType.TYPES.SharedVaultUuid,
).getValue(),
type: NotificationType.create(NotificationType.TYPES.SharedVaultFileRemoved).getValue(),
version: '1.0',
}).getValue()
@@ -71,7 +79,10 @@ export class SharedVaultFileMovedEventHandler implements DomainEventHandlerInter
}
const notificationPayload = NotificationPayload.create({
sharedVaultUuid: sharedVaultUuid,
primaryIdentifier: sharedVaultUuid,
primaryIndentifierType: NotificationPayloadIdentifierType.create(
NotificationPayloadIdentifierType.TYPES.SharedVaultUuid,
).getValue(),
type: NotificationType.create(NotificationType.TYPES.SharedVaultFileUploaded).getValue(),
version: '1.0',
}).getValue()

View File

@@ -1,5 +1,10 @@
import { DomainEventHandlerInterface, SharedVaultFileRemovedEvent } from '@standardnotes/domain-events'
import { NotificationPayload, NotificationType, Uuid } from '@standardnotes/domain-core'
import {
NotificationPayload,
NotificationPayloadIdentifierType,
NotificationType,
Uuid,
} from '@standardnotes/domain-core'
import { Logger } from 'winston'
import { UpdateStorageQuotaUsedInSharedVault } from '../UseCase/SharedVaults/UpdateStorageQuotaUsedInSharedVault/UpdateStorageQuotaUsedInSharedVault'
@@ -33,7 +38,10 @@ export class SharedVaultFileRemovedEventHandler implements DomainEventHandlerInt
}
const notificationPayload = NotificationPayload.create({
sharedVaultUuid,
primaryIdentifier: sharedVaultUuid,
primaryIndentifierType: NotificationPayloadIdentifierType.create(
NotificationPayloadIdentifierType.TYPES.SharedVaultUuid,
).getValue(),
type: NotificationType.create(NotificationType.TYPES.SharedVaultFileRemoved).getValue(),
version: '1.0',
}).getValue()

View File

@@ -1,5 +1,10 @@
import { DomainEventHandlerInterface, SharedVaultFileUploadedEvent } from '@standardnotes/domain-events'
import { NotificationPayload, NotificationType, Uuid } from '@standardnotes/domain-core'
import {
NotificationPayload,
NotificationPayloadIdentifierType,
NotificationType,
Uuid,
} from '@standardnotes/domain-core'
import { Logger } from 'winston'
import { UpdateStorageQuotaUsedInSharedVault } from '../UseCase/SharedVaults/UpdateStorageQuotaUsedInSharedVault/UpdateStorageQuotaUsedInSharedVault'
@@ -33,7 +38,10 @@ export class SharedVaultFileUploadedEventHandler implements DomainEventHandlerIn
}
const notificationPayload = NotificationPayload.create({
sharedVaultUuid,
primaryIdentifier: sharedVaultUuid,
primaryIndentifierType: NotificationPayloadIdentifierType.create(
NotificationPayloadIdentifierType.TYPES.SharedVaultUuid,
).getValue(),
type: NotificationType.create(NotificationType.TYPES.SharedVaultFileUploaded).getValue(),
version: '1.0',
}).getValue()

View File

@@ -1,7 +1,9 @@
import { KeyParamsData } from '@standardnotes/responses'
import { Result } from '@standardnotes/domain-core'
import { Item } from './Item'
export interface ItemBackupServiceInterface {
backup(items: Array<Item>, authParams: KeyParamsData, contentSizeLimit?: number): Promise<string[]>
dump(item: Item): Promise<string>
dump(item: Item): Promise<Result<string>>
}

View File

@@ -6,7 +6,8 @@ import { ExtendedIntegrityPayload } from './ExtendedIntegrityPayload'
import { ItemContentSizeDescriptor } from './ItemContentSizeDescriptor'
export interface ItemRepositoryInterface {
deleteByUserUuid(userUuid: string): Promise<void>
deleteByUserUuidAndNotInSharedVault(userUuid: Uuid): Promise<void>
deleteByUserUuidInSharedVaults(userUuid: Uuid, sharedVaultUuids: Uuid[]): Promise<void>
findAll(query: ItemQuery): Promise<Item[]>
countAll(query: ItemQuery): Promise<number>
findContentSizeForComputingTransferLimit(query: ItemQuery): Promise<Array<ItemContentSizeDescriptor>>

View File

@@ -1,11 +1,20 @@
import { NotificationPayload, NotificationType, Timestamps, Uuid } from '@standardnotes/domain-core'
import {
NotificationPayload,
NotificationPayloadIdentifierType,
NotificationType,
Timestamps,
Uuid,
} from '@standardnotes/domain-core'
import { Notification } from './Notification'
describe('Notification', () => {
it('should create an entity', () => {
const payload = NotificationPayload.create({
sharedVaultUuid: Uuid.create('0e8c3c7e-3f1a-4f7a-9b5a-5b2b0a7d4b1e').getValue(),
primaryIdentifier: Uuid.create('0e8c3c7e-3f1a-4f7a-9b5a-5b2b0a7d4b1e').getValue(),
primaryIndentifierType: NotificationPayloadIdentifierType.create(
NotificationPayloadIdentifierType.TYPES.SharedVaultUuid,
).getValue(),
type: NotificationType.create(NotificationType.TYPES.SelfRemovedFromSharedVault).getValue(),
version: '1.0',
}).getValue()

View File

@@ -1,5 +1,11 @@
import { TimerInterface } from '@standardnotes/time'
import { NotificationPayload, NotificationType, Result, Uuid } from '@standardnotes/domain-core'
import {
NotificationPayload,
NotificationPayloadIdentifierType,
NotificationType,
Result,
Uuid,
} from '@standardnotes/domain-core'
import { NotificationRepositoryInterface } from '../../../Notifications/NotificationRepositoryInterface'
import { Notification } from '../../../Notifications/Notification'
@@ -28,7 +34,10 @@ describe('AddNotificationForUser', () => {
timer.getTimestampInMicroseconds = jest.fn().mockReturnValue(123456789)
payload = NotificationPayload.create({
sharedVaultUuid: Uuid.create('0e8c3c7e-3f1a-4f7a-9b5a-5b2b0a7d4b1e').getValue(),
primaryIdentifier: Uuid.create('0e8c3c7e-3f1a-4f7a-9b5a-5b2b0a7d4b1e').getValue(),
primaryIndentifierType: NotificationPayloadIdentifierType.create(
NotificationPayloadIdentifierType.TYPES.SharedVaultUuid,
).getValue(),
type: NotificationType.create(NotificationType.TYPES.SelfRemovedFromSharedVault).getValue(),
version: '1.0',
}).getValue()

View File

@@ -6,6 +6,7 @@ import {
NotificationPayload,
NotificationType,
SharedVaultUser,
NotificationPayloadIdentifierType,
} from '@standardnotes/domain-core'
import { SharedVaultUserRepositoryInterface } from '../../../SharedVault/User/SharedVaultUserRepositoryInterface'
import { AddNotificationForUser } from '../AddNotificationForUser/AddNotificationForUser'
@@ -35,7 +36,10 @@ describe('AddNotificationsForUsers', () => {
addNotificationForUser.execute = jest.fn().mockResolvedValue(Result.ok())
payload = NotificationPayload.create({
sharedVaultUuid: Uuid.create('0e8c3c7e-3f1a-4f7a-9b5a-5b2b0a7d4b1e').getValue(),
primaryIdentifier: Uuid.create('0e8c3c7e-3f1a-4f7a-9b5a-5b2b0a7d4b1e').getValue(),
primaryIndentifierType: NotificationPayloadIdentifierType.create(
NotificationPayloadIdentifierType.TYPES.SharedVaultUuid,
).getValue(),
type: NotificationType.create(NotificationType.TYPES.SharedVaultFileUploaded).getValue(),
version: '1.0',
}).getValue()

View File

@@ -1,4 +1,10 @@
import { NotificationPayload, NotificationType, Timestamps, Uuid } from '@standardnotes/domain-core'
import {
NotificationPayload,
NotificationPayloadIdentifierType,
NotificationType,
Timestamps,
Uuid,
} from '@standardnotes/domain-core'
import { NotificationRepositoryInterface } from '../../../Notifications/NotificationRepositoryInterface'
import { RemoveNotificationsForUser } from './RemoveNotificationsForUser'
@@ -15,8 +21,14 @@ describe('RemoveNotificationsForUser', () => {
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
type: NotificationType.create(NotificationType.TYPES.SharedVaultItemRemoved).getValue(),
payload: NotificationPayload.create({
itemUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
sharedVaultUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
primaryIdentifier: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
primaryIndentifierType: NotificationPayloadIdentifierType.create(
NotificationPayloadIdentifierType.TYPES.SharedVaultUuid,
).getValue(),
secondaryIdentifier: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
secondaryIdentifierType: NotificationPayloadIdentifierType.create(
NotificationPayloadIdentifierType.TYPES.ItemUuid,
).getValue(),
type: NotificationType.create(NotificationType.TYPES.SharedVaultItemRemoved).getValue(),
version: '1.0',
}).getValue(),

View File

@@ -1,5 +1,6 @@
import {
NotificationPayload,
NotificationPayloadIdentifierType,
NotificationType,
Result,
SharedVaultUser,
@@ -73,7 +74,10 @@ export class AddUserToSharedVault implements UseCaseInterface<SharedVaultUser> {
await this.sharedVaultUserRepository.save(sharedVaultUser)
const notificationPayloadOrError = NotificationPayload.create({
sharedVaultUuid: sharedVaultUuid,
primaryIdentifier: sharedVaultUuid,
primaryIndentifierType: NotificationPayloadIdentifierType.create(
NotificationPayloadIdentifierType.TYPES.SharedVaultUuid,
).getValue(),
type: NotificationType.create(NotificationType.TYPES.UserAddedToSharedVault).getValue(),
version: '1.0',
})

View File

@@ -1,15 +1,15 @@
import { NotificationPayload, Result, SharedVaultUserPermission, Timestamps, Uuid } from '@standardnotes/domain-core'
import { SharedVaultInviteRepositoryInterface } from '../../../SharedVault/User/Invite/SharedVaultInviteRepositoryInterface'
import { DeclineInviteToSharedVault } from './DeclineInviteToSharedVault'
import { CancelInviteToSharedVault } from './CancelInviteToSharedVault'
import { SharedVaultInvite } from '../../../SharedVault/User/Invite/SharedVaultInvite'
import { AddNotificationForUser } from '../../Messaging/AddNotificationForUser/AddNotificationForUser'
describe('DeclineInviteToSharedVault', () => {
describe('CancelInviteToSharedVault', () => {
let sharedVaultInviteRepository: SharedVaultInviteRepositoryInterface
let invite: SharedVaultInvite
let addNotificationForUser: AddNotificationForUser
const createUseCase = () => new DeclineInviteToSharedVault(sharedVaultInviteRepository, addNotificationForUser)
const createUseCase = () => new CancelInviteToSharedVault(sharedVaultInviteRepository, addNotificationForUser)
beforeEach(() => {
invite = SharedVaultInvite.create({

View File

@@ -1,16 +1,23 @@
import { NotificationPayload, NotificationType, Result, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
import {
NotificationPayload,
NotificationPayloadIdentifierType,
NotificationType,
Result,
UseCaseInterface,
Uuid,
} from '@standardnotes/domain-core'
import { DeclineInviteToSharedVaultDTO } from './DeclineInviteToSharedVaultDTO'
import { CancelInviteToSharedVaultDTO } from './CancelInviteToSharedVaultDTO'
import { SharedVaultInviteRepositoryInterface } from '../../../SharedVault/User/Invite/SharedVaultInviteRepositoryInterface'
import { AddNotificationForUser } from '../../Messaging/AddNotificationForUser/AddNotificationForUser'
export class DeclineInviteToSharedVault implements UseCaseInterface<void> {
export class CancelInviteToSharedVault implements UseCaseInterface<void> {
constructor(
private sharedVaultInviteRepository: SharedVaultInviteRepositoryInterface,
private addNotificationForUser: AddNotificationForUser,
) {}
async execute(dto: DeclineInviteToSharedVaultDTO): Promise<Result<void>> {
async execute(dto: CancelInviteToSharedVaultDTO): Promise<Result<void>> {
const inviteUuidOrError = Uuid.create(dto.inviteUuid)
if (inviteUuidOrError.isFailed()) {
return Result.fail(inviteUuidOrError.getError())
@@ -35,8 +42,15 @@ export class DeclineInviteToSharedVault implements UseCaseInterface<void> {
await this.sharedVaultInviteRepository.remove(invite)
const notificationPayloadOrError = NotificationPayload.create({
sharedVaultUuid: invite.props.sharedVaultUuid,
type: NotificationType.create(NotificationType.TYPES.SharedVaultInviteDeclined).getValue(),
primaryIdentifier: Uuid.create(invite.id.toString()).getValue(),
primaryIndentifierType: NotificationPayloadIdentifierType.create(
NotificationPayloadIdentifierType.TYPES.SharedVaultInviteUuid,
).getValue(),
secondaryIdentifier: invite.props.sharedVaultUuid,
secondaryIdentifierType: NotificationPayloadIdentifierType.create(
NotificationPayloadIdentifierType.TYPES.SharedVaultUuid,
).getValue(),
type: NotificationType.create(NotificationType.TYPES.SharedVaultInviteCanceled).getValue(),
version: '1.0',
})
if (notificationPayloadOrError.isFailed()) {
@@ -46,7 +60,7 @@ export class DeclineInviteToSharedVault implements UseCaseInterface<void> {
const result = await this.addNotificationForUser.execute({
userUuid: invite.props.userUuid.value,
type: NotificationType.TYPES.SharedVaultInviteDeclined,
type: NotificationType.TYPES.SharedVaultInviteCanceled,
payload: notificationPayload,
version: '1.0',
})

View File

@@ -0,0 +1,4 @@
export interface CancelInviteToSharedVaultDTO {
inviteUuid: string
userUuid: string
}

View File

@@ -1,4 +0,0 @@
export interface DeclineInviteToSharedVaultDTO {
inviteUuid: string
userUuid: string
}

View File

@@ -7,7 +7,7 @@ import { SharedVaultUserRepositoryInterface } from '../../../SharedVault/User/Sh
import { DeleteSharedVault } from './DeleteSharedVault'
import { SharedVault } from '../../../SharedVault/SharedVault'
import { RemoveUserFromSharedVault } from '../RemoveUserFromSharedVault/RemoveUserFromSharedVault'
import { DeclineInviteToSharedVault } from '../DeclineInviteToSharedVault/DeclineInviteToSharedVault'
import { CancelInviteToSharedVault } from '../CancelInviteToSharedVault/CancelInviteToSharedVault'
import { SharedVaultInvite } from '../../../SharedVault/User/Invite/SharedVaultInvite'
import { DomainEventFactoryInterface } from '../../../Event/DomainEventFactoryInterface'
import { TransferSharedVault } from '../TransferSharedVault/TransferSharedVault'
@@ -17,7 +17,7 @@ describe('DeleteSharedVault', () => {
let sharedVaultUserRepository: SharedVaultUserRepositoryInterface
let sharedVaultInviteRepository: SharedVaultInviteRepositoryInterface
let removeUserFromSharedVault: RemoveUserFromSharedVault
let declineInviteToSharedVault: DeclineInviteToSharedVault
let cancelInviteToSharedVault: CancelInviteToSharedVault
let sharedVault: SharedVault
let sharedVaultUser: SharedVaultUser
let sharedVaultInvite: SharedVaultInvite
@@ -31,7 +31,7 @@ describe('DeleteSharedVault', () => {
sharedVaultUserRepository,
sharedVaultInviteRepository,
removeUserFromSharedVault,
declineInviteToSharedVault,
cancelInviteToSharedVault,
domainEventFactory,
domainEventPublisher,
transferSharedVault,
@@ -72,8 +72,8 @@ describe('DeleteSharedVault', () => {
sharedVaultInviteRepository = {} as jest.Mocked<SharedVaultInviteRepositoryInterface>
sharedVaultInviteRepository.findBySharedVaultUuid = jest.fn().mockReturnValue([sharedVaultInvite])
declineInviteToSharedVault = {} as jest.Mocked<DeclineInviteToSharedVault>
declineInviteToSharedVault.execute = jest.fn().mockReturnValue(Result.ok())
cancelInviteToSharedVault = {} as jest.Mocked<CancelInviteToSharedVault>
cancelInviteToSharedVault.execute = jest.fn().mockReturnValue(Result.ok())
removeUserFromSharedVault = {} as jest.Mocked<RemoveUserFromSharedVault>
removeUserFromSharedVault.execute = jest.fn().mockReturnValue(Result.ok())
@@ -100,7 +100,7 @@ describe('DeleteSharedVault', () => {
expect(result.isFailed()).toBeFalsy()
expect(sharedVaultRepository.remove).toHaveBeenCalled()
expect(declineInviteToSharedVault.execute).toHaveBeenCalled()
expect(cancelInviteToSharedVault.execute).toHaveBeenCalled()
expect(removeUserFromSharedVault.execute).toHaveBeenCalled()
})
@@ -115,7 +115,7 @@ describe('DeleteSharedVault', () => {
expect(result.isFailed()).toBeTruthy()
expect(sharedVaultRepository.remove).not.toHaveBeenCalled()
expect(declineInviteToSharedVault.execute).not.toHaveBeenCalled()
expect(cancelInviteToSharedVault.execute).not.toHaveBeenCalled()
expect(removeUserFromSharedVault.execute).not.toHaveBeenCalled()
})
@@ -129,7 +129,7 @@ describe('DeleteSharedVault', () => {
expect(result.isFailed()).toBeTruthy()
expect(sharedVaultRepository.remove).not.toHaveBeenCalled()
expect(declineInviteToSharedVault.execute).not.toHaveBeenCalled()
expect(cancelInviteToSharedVault.execute).not.toHaveBeenCalled()
expect(removeUserFromSharedVault.execute).not.toHaveBeenCalled()
})
@@ -143,7 +143,7 @@ describe('DeleteSharedVault', () => {
expect(result.isFailed()).toBeTruthy()
expect(sharedVaultRepository.remove).not.toHaveBeenCalled()
expect(declineInviteToSharedVault.execute).not.toHaveBeenCalled()
expect(cancelInviteToSharedVault.execute).not.toHaveBeenCalled()
expect(removeUserFromSharedVault.execute).not.toHaveBeenCalled()
})
@@ -163,7 +163,7 @@ describe('DeleteSharedVault', () => {
expect(result.isFailed()).toBeTruthy()
expect(sharedVaultRepository.remove).not.toHaveBeenCalled()
expect(declineInviteToSharedVault.execute).not.toHaveBeenCalled()
expect(cancelInviteToSharedVault.execute).not.toHaveBeenCalled()
expect(removeUserFromSharedVault.execute).not.toHaveBeenCalled()
})
@@ -182,7 +182,7 @@ describe('DeleteSharedVault', () => {
})
it('should return error if declining invite to shared vault fails', async () => {
declineInviteToSharedVault.execute = jest.fn().mockReturnValue(Result.fail('failed'))
cancelInviteToSharedVault.execute = jest.fn().mockReturnValue(Result.fail('failed'))
const useCase = createUseCase()
const result = await useCase.execute({
@@ -192,7 +192,7 @@ describe('DeleteSharedVault', () => {
expect(result.isFailed()).toBeTruthy()
expect(sharedVaultRepository.remove).not.toHaveBeenCalled()
expect(declineInviteToSharedVault.execute).toHaveBeenCalled()
expect(cancelInviteToSharedVault.execute).toHaveBeenCalled()
expect(removeUserFromSharedVault.execute).not.toHaveBeenCalled()
})
@@ -211,7 +211,7 @@ describe('DeleteSharedVault', () => {
expect(result.isFailed()).toBeFalsy()
expect(sharedVaultRepository.remove).not.toHaveBeenCalled()
expect(declineInviteToSharedVault.execute).toHaveBeenCalled()
expect(cancelInviteToSharedVault.execute).toHaveBeenCalled()
expect(removeUserFromSharedVault.execute).toHaveBeenCalled()
expect(transferSharedVault.execute).toHaveBeenCalled()
})
@@ -227,7 +227,7 @@ describe('DeleteSharedVault', () => {
expect(result.isFailed()).toBeTruthy()
expect(sharedVaultRepository.remove).not.toHaveBeenCalled()
expect(declineInviteToSharedVault.execute).toHaveBeenCalled()
expect(cancelInviteToSharedVault.execute).toHaveBeenCalled()
expect(removeUserFromSharedVault.execute).not.toHaveBeenCalled()
expect(transferSharedVault.execute).toHaveBeenCalled()
})
@@ -243,7 +243,7 @@ describe('DeleteSharedVault', () => {
expect(result.isFailed()).toBeTruthy()
expect(sharedVaultRepository.remove).not.toHaveBeenCalled()
expect(declineInviteToSharedVault.execute).toHaveBeenCalled()
expect(cancelInviteToSharedVault.execute).toHaveBeenCalled()
expect(removeUserFromSharedVault.execute).toHaveBeenCalled()
expect(transferSharedVault.execute).toHaveBeenCalled()
})

View File

@@ -6,23 +6,23 @@ import { SharedVaultRepositoryInterface } from '../../../SharedVault/SharedVault
import { SharedVaultUserRepositoryInterface } from '../../../SharedVault/User/SharedVaultUserRepositoryInterface'
import { SharedVaultInviteRepositoryInterface } from '../../../SharedVault/User/Invite/SharedVaultInviteRepositoryInterface'
import { RemoveUserFromSharedVault } from '../RemoveUserFromSharedVault/RemoveUserFromSharedVault'
import { DeclineInviteToSharedVault } from '../DeclineInviteToSharedVault/DeclineInviteToSharedVault'
import { CancelInviteToSharedVault } from '../CancelInviteToSharedVault/CancelInviteToSharedVault'
import { DomainEventFactoryInterface } from '../../../Event/DomainEventFactoryInterface'
import { TransferSharedVault } from '../TransferSharedVault/TransferSharedVault'
export class DeleteSharedVault implements UseCaseInterface<void> {
export class DeleteSharedVault implements UseCaseInterface<{ status: 'deleted' | 'transitioned' }> {
constructor(
private sharedVaultRepository: SharedVaultRepositoryInterface,
private sharedVaultUserRepository: SharedVaultUserRepositoryInterface,
private sharedVaultInviteRepository: SharedVaultInviteRepositoryInterface,
private removeUserFromSharedVault: RemoveUserFromSharedVault,
private declineInviteToSharedVault: DeclineInviteToSharedVault,
private cancelInviteToSharedVault: CancelInviteToSharedVault,
private domainEventFactory: DomainEventFactoryInterface,
private domainEventPublisher: DomainEventPublisherInterface,
private transferSharedVault: TransferSharedVault,
) {}
async execute(dto: DeleteSharedVaultDTO): Promise<Result<void>> {
async execute(dto: DeleteSharedVaultDTO): Promise<Result<{ status: 'deleted' | 'transitioned' }>> {
const originatorUuidOrError = Uuid.create(dto.originatorUuid)
if (originatorUuidOrError.isFailed()) {
return Result.fail(originatorUuidOrError.getError())
@@ -46,7 +46,7 @@ export class DeleteSharedVault implements UseCaseInterface<void> {
const sharedVaultInvites = await this.sharedVaultInviteRepository.findBySharedVaultUuid(sharedVaultUuid)
for (const sharedVaultInvite of sharedVaultInvites) {
const result = await this.declineInviteToSharedVault.execute({
const result = await this.cancelInviteToSharedVault.execute({
inviteUuid: sharedVaultInvite.id.toString(),
userUuid: sharedVaultInvite.props.userUuid.value,
})
@@ -79,7 +79,7 @@ export class DeleteSharedVault implements UseCaseInterface<void> {
return Result.fail(removingOwnerFromSharedVaultResult.getError())
}
return Result.ok()
return Result.ok({ status: 'transitioned' })
}
const sharedVaultUsers = await this.sharedVaultUserRepository.findBySharedVaultUuid(sharedVaultUuid)
@@ -105,6 +105,6 @@ export class DeleteSharedVault implements UseCaseInterface<void> {
}),
)
return Result.ok()
return Result.ok({ status: 'deleted' })
}
}

View File

@@ -1,16 +1,16 @@
import { Result, SharedVaultUserPermission, Timestamps, Uuid } from '@standardnotes/domain-core'
import { SharedVaultInviteRepositoryInterface } from '../../../SharedVault/User/Invite/SharedVaultInviteRepositoryInterface'
import { DeclineInviteToSharedVault } from '../DeclineInviteToSharedVault/DeclineInviteToSharedVault'
import { CancelInviteToSharedVault } from '../CancelInviteToSharedVault/CancelInviteToSharedVault'
import { DeleteSharedVaultInvitesSentByUser } from './DeleteSharedVaultInvitesSentByUser'
import { SharedVaultInvite } from '../../../SharedVault/User/Invite/SharedVaultInvite'
describe('DeleteSharedVaultInvitesSentByUser', () => {
let sharedVaultInviteRepository: SharedVaultInviteRepositoryInterface
let declineInviteToSharedVault: DeclineInviteToSharedVault
let cancelInviteToSharedVault: CancelInviteToSharedVault
let sharedVaultInvite: SharedVaultInvite
const createUseCase = () =>
new DeleteSharedVaultInvitesSentByUser(sharedVaultInviteRepository, declineInviteToSharedVault)
new DeleteSharedVaultInvitesSentByUser(sharedVaultInviteRepository, cancelInviteToSharedVault)
beforeEach(() => {
sharedVaultInvite = SharedVaultInvite.create({
@@ -26,8 +26,8 @@ describe('DeleteSharedVaultInvitesSentByUser', () => {
sharedVaultInviteRepository.findBySenderUuidAndSharedVaultUuid = jest.fn().mockReturnValue([sharedVaultInvite])
sharedVaultInviteRepository.findBySenderUuid = jest.fn().mockReturnValue([sharedVaultInvite])
declineInviteToSharedVault = {} as jest.Mocked<DeclineInviteToSharedVault>
declineInviteToSharedVault.execute = jest.fn().mockReturnValue(Result.ok())
cancelInviteToSharedVault = {} as jest.Mocked<CancelInviteToSharedVault>
cancelInviteToSharedVault.execute = jest.fn().mockReturnValue(Result.ok())
})
it('should decline all invites by user', async () => {
@@ -39,7 +39,7 @@ describe('DeleteSharedVaultInvitesSentByUser', () => {
})
expect(result.isFailed()).toBeFalsy()
expect(declineInviteToSharedVault.execute).toHaveBeenCalled()
expect(cancelInviteToSharedVault.execute).toHaveBeenCalled()
})
it('should return error when user uuid is invalid', async () => {
@@ -64,8 +64,8 @@ describe('DeleteSharedVaultInvitesSentByUser', () => {
expect(result.isFailed()).toBeTruthy()
})
it('should return error when declineInviteToSharedVault fails', async () => {
declineInviteToSharedVault.execute = jest.fn().mockReturnValue(Result.fail('error'))
it('should return error when cancelInviteToSharedVault fails', async () => {
cancelInviteToSharedVault.execute = jest.fn().mockReturnValue(Result.fail('error'))
const useCase = createUseCase()
@@ -85,6 +85,6 @@ describe('DeleteSharedVaultInvitesSentByUser', () => {
})
expect(result.isFailed()).toBeFalsy()
expect(declineInviteToSharedVault.execute).toHaveBeenCalled()
expect(cancelInviteToSharedVault.execute).toHaveBeenCalled()
})
})

View File

@@ -1,12 +1,12 @@
import { Result, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
import { DeleteSharedVaultInvitesSentByUserDTO } from './DeleteSharedVaultInvitesSentByUserDTO'
import { DeclineInviteToSharedVault } from '../DeclineInviteToSharedVault/DeclineInviteToSharedVault'
import { CancelInviteToSharedVault } from '../CancelInviteToSharedVault/CancelInviteToSharedVault'
import { SharedVaultInviteRepositoryInterface } from '../../../SharedVault/User/Invite/SharedVaultInviteRepositoryInterface'
export class DeleteSharedVaultInvitesSentByUser implements UseCaseInterface<void> {
constructor(
private sharedVaultInviteRepository: SharedVaultInviteRepositoryInterface,
private declineInviteToSharedVault: DeclineInviteToSharedVault,
private cancelInviteToSharedVault: CancelInviteToSharedVault,
) {}
async execute(dto: DeleteSharedVaultInvitesSentByUserDTO): Promise<Result<void>> {
@@ -33,7 +33,7 @@ export class DeleteSharedVaultInvitesSentByUser implements UseCaseInterface<void
}
for (const invite of inboundInvites) {
const result = await this.declineInviteToSharedVault.execute({
const result = await this.cancelInviteToSharedVault.execute({
inviteUuid: invite.id.toString(),
userUuid: userUuid.value,
})

View File

@@ -1,16 +1,15 @@
import { Result, SharedVaultUserPermission, Timestamps, Uuid } from '@standardnotes/domain-core'
import { SharedVaultInviteRepositoryInterface } from '../../../SharedVault/User/Invite/SharedVaultInviteRepositoryInterface'
import { DeclineInviteToSharedVault } from '../DeclineInviteToSharedVault/DeclineInviteToSharedVault'
import { CancelInviteToSharedVault } from '../CancelInviteToSharedVault/CancelInviteToSharedVault'
import { DeleteSharedVaultInvitesToUser } from './DeleteSharedVaultInvitesToUser'
import { SharedVaultInvite } from '../../../SharedVault/User/Invite/SharedVaultInvite'
describe('DeleteSharedVaultInvitesToUser', () => {
let sharedVaultInviteRepository: SharedVaultInviteRepositoryInterface
let declineInviteToSharedVault: DeclineInviteToSharedVault
let cancelInviteToSharedVault: CancelInviteToSharedVault
let sharedVaultInvite: SharedVaultInvite
const createUseCase = () =>
new DeleteSharedVaultInvitesToUser(sharedVaultInviteRepository, declineInviteToSharedVault)
const createUseCase = () => new DeleteSharedVaultInvitesToUser(sharedVaultInviteRepository, cancelInviteToSharedVault)
beforeEach(() => {
sharedVaultInvite = SharedVaultInvite.create({
@@ -25,8 +24,8 @@ describe('DeleteSharedVaultInvitesToUser', () => {
sharedVaultInviteRepository = {} as jest.Mocked<SharedVaultInviteRepositoryInterface>
sharedVaultInviteRepository.findByUserUuid = jest.fn().mockReturnValue([sharedVaultInvite])
declineInviteToSharedVault = {} as jest.Mocked<DeclineInviteToSharedVault>
declineInviteToSharedVault.execute = jest.fn().mockReturnValue(Result.ok())
cancelInviteToSharedVault = {} as jest.Mocked<CancelInviteToSharedVault>
cancelInviteToSharedVault.execute = jest.fn().mockReturnValue(Result.ok())
})
it('should decline all invites to user', async () => {
@@ -37,7 +36,7 @@ describe('DeleteSharedVaultInvitesToUser', () => {
})
expect(result.isFailed()).toBeFalsy()
expect(declineInviteToSharedVault.execute).toHaveBeenCalled()
expect(cancelInviteToSharedVault.execute).toHaveBeenCalled()
})
it('should return error when user uuid is invalid', async () => {
@@ -50,8 +49,8 @@ describe('DeleteSharedVaultInvitesToUser', () => {
expect(result.isFailed()).toBeTruthy()
})
it('should return error when declineInviteToSharedVault fails', async () => {
declineInviteToSharedVault.execute = jest.fn().mockReturnValue(Result.fail('error'))
it('should return error when cancelInviteToSharedVault fails', async () => {
cancelInviteToSharedVault.execute = jest.fn().mockReturnValue(Result.fail('error'))
const useCase = createUseCase()

View File

@@ -1,12 +1,12 @@
import { Result, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
import { DeleteSharedVaultInvitesToUserDTO } from './DeleteSharedVaultInvitesToUserDTO'
import { DeclineInviteToSharedVault } from '../DeclineInviteToSharedVault/DeclineInviteToSharedVault'
import { CancelInviteToSharedVault } from '../CancelInviteToSharedVault/CancelInviteToSharedVault'
import { SharedVaultInviteRepositoryInterface } from '../../../SharedVault/User/Invite/SharedVaultInviteRepositoryInterface'
export class DeleteSharedVaultInvitesToUser implements UseCaseInterface<void> {
constructor(
private sharedVaultInviteRepository: SharedVaultInviteRepositoryInterface,
private declineInviteToSharedVault: DeclineInviteToSharedVault,
private cancelInviteToSharedVault: CancelInviteToSharedVault,
) {}
async execute(dto: DeleteSharedVaultInvitesToUserDTO): Promise<Result<void>> {
@@ -18,7 +18,7 @@ export class DeleteSharedVaultInvitesToUser implements UseCaseInterface<void> {
const inboundInvites = await this.sharedVaultInviteRepository.findByUserUuid(userUuid)
for (const invite of inboundInvites) {
const result = await this.declineInviteToSharedVault.execute({
const result = await this.cancelInviteToSharedVault.execute({
inviteUuid: invite.id.toString(),
userUuid: userUuid.value,
})

View File

@@ -24,7 +24,7 @@ describe('DeleteSharedVaults', () => {
sharedVaultRepository.findByUserUuid = jest.fn().mockResolvedValue([sharedVault])
deleteSharedVaultUseCase = {} as jest.Mocked<DeleteSharedVault>
deleteSharedVaultUseCase.execute = jest.fn().mockResolvedValue(Result.ok())
deleteSharedVaultUseCase.execute = jest.fn().mockResolvedValue(Result.ok({ status: 'deleted' }))
})
it('should delete all shared vaults for a user', async () => {

View File

@@ -4,13 +4,13 @@ import { DeleteSharedVaultsDTO } from './DeleteSharedVaultsDTO'
import { DeleteSharedVault } from '../DeleteSharedVault/DeleteSharedVault'
import { SharedVaultRepositoryInterface } from '../../../SharedVault/SharedVaultRepositoryInterface'
export class DeleteSharedVaults implements UseCaseInterface<void> {
export class DeleteSharedVaults implements UseCaseInterface<Map<Uuid, 'deleted' | 'transitioned'>> {
constructor(
private sharedVaultRepository: SharedVaultRepositoryInterface,
private deleteSharedVaultUseCase: DeleteSharedVault,
) {}
async execute(dto: DeleteSharedVaultsDTO): Promise<Result<void>> {
async execute(dto: DeleteSharedVaultsDTO): Promise<Result<Map<Uuid, 'deleted' | 'transitioned'>>> {
const ownerUuidOrError = Uuid.create(dto.ownerUuid)
if (ownerUuidOrError.isFailed()) {
return Result.fail(ownerUuidOrError.getError())
@@ -19,6 +19,7 @@ export class DeleteSharedVaults implements UseCaseInterface<void> {
const sharedVaults = await this.sharedVaultRepository.findByUserUuid(ownerUuid)
const results = new Map<Uuid, 'deleted' | 'transitioned'>()
for (const sharedVault of sharedVaults) {
const result = await this.deleteSharedVaultUseCase.execute({
originatorUuid: ownerUuid.value,
@@ -27,8 +28,10 @@ export class DeleteSharedVaults implements UseCaseInterface<void> {
if (result.isFailed()) {
return Result.fail(result.getError())
}
results.set(sharedVault.uuid, result.getValue().status)
}
return Result.ok()
return Result.ok(results)
}
}

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