mirror of
https://github.com/standardnotes/server
synced 2026-05-07 00:57:31 -04:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 34b956b482 | |||
| 681e0378ae | |||
| 609e85f926 | |||
| e4ca310707 | |||
| d606493356 | |||
| 5ef6c5c14a | |||
| 0188f290f9 | |||
| 676cf36f8d | |||
| f8aef6c8ef | |||
| 5bf8cf49c1 | |||
| 51cd0a4dad | |||
| 1d06ffe9d5 |
@@ -23,6 +23,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
|
suite: ['base', 'vaults']
|
||||||
secondary_db_enabled: [true, false]
|
secondary_db_enabled: [true, false]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
@@ -55,21 +56,24 @@ jobs:
|
|||||||
run: docker/is-available.sh http://localhost:3123 $(pwd)/logs
|
run: docker/is-available.sh http://localhost:3123 $(pwd)/logs
|
||||||
|
|
||||||
- name: Run E2E Test Suite
|
- 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() }}
|
if: ${{ failure() }}
|
||||||
run: |
|
uses: actions/upload-artifact@v3
|
||||||
echo "# Errors:"
|
with:
|
||||||
tail -n 100 logs/*.err
|
name: self-hosted-failure-logs-${{ matrix.suite }}-${{ matrix.secondary_db_enabled }}
|
||||||
echo "# Logs:"
|
retention-days: 5
|
||||||
tail -n 100 logs/*.log
|
path: |
|
||||||
|
logs/*.err
|
||||||
|
logs/*.log
|
||||||
|
|
||||||
e2e-home-server:
|
e2e-home-server:
|
||||||
name: (Home Server) E2E Test Suite
|
name: (Home Server) E2E Test Suite
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
|
suite: ['base', 'vaults']
|
||||||
db_type: [mysql, sqlite]
|
db_type: [mysql, sqlite]
|
||||||
cache_type: [redis, memory]
|
cache_type: [redis, memory]
|
||||||
secondary_db_enabled: [true, false]
|
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
|
run: for i in {1..30}; do curl -s http://localhost:3123/healthcheck && break || sleep 1; done
|
||||||
|
|
||||||
- name: Run E2E Test Suite
|
- 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() }}
|
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
|
||||||
|
|||||||
@@ -3,6 +3,14 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
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)
|
# [2.27.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.26.24...@standardnotes/analytics@2.27.0) (2023-09-26)
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/analytics",
|
"name": "@standardnotes/analytics",
|
||||||
"version": "2.27.0",
|
"version": "2.27.2",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0 <21.0.0"
|
"node": ">=18.0.0 <21.0.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,6 +3,14 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
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)
|
## [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
|
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/api-gateway",
|
"name": "@standardnotes/api-gateway",
|
||||||
"version": "1.75.5",
|
"version": "1.75.7",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0 <21.0.0"
|
"node": ">=18.0.0 <21.0.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,6 +3,28 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [1.148.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.148.1...@standardnotes/auth-server@1.148.2) (2023-09-29)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **auth:** increase ttl for in progress duration of transitions ([681e037](https://github.com/standardnotes/server/commit/681e0378ae6b97c838b0b34ccc630194b304b81a))
|
||||||
|
|
||||||
|
## [1.148.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.148.0...@standardnotes/auth-server@1.148.1) (2023-09-29)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **auth:** disable transitions retriggering if they are in progress ([5ef6c5c](https://github.com/standardnotes/server/commit/5ef6c5c14a9f7a558de7ac9ff0ab99a5f831c127))
|
||||||
|
|
||||||
|
# [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)
|
# [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
|
### Features
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ const requestTransition = async (
|
|||||||
|
|
||||||
let wasTransitionRequested = false
|
let wasTransitionRequested = false
|
||||||
|
|
||||||
if (itemsTransitionStatus?.value !== TransitionStatus.STATUSES.Verified) {
|
if (itemsTransitionStatus === null || itemsTransitionStatus.value === TransitionStatus.STATUSES.Failed) {
|
||||||
wasTransitionRequested = true
|
wasTransitionRequested = true
|
||||||
await transitionStatusRepository.remove(user.uuid, 'items')
|
await transitionStatusRepository.remove(user.uuid, 'items')
|
||||||
|
|
||||||
@@ -68,7 +68,7 @@ const requestTransition = async (
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (revisionsTransitionStatus?.value !== TransitionStatus.STATUSES.Verified) {
|
if (revisionsTransitionStatus === null || revisionsTransitionStatus.value === TransitionStatus.STATUSES.Failed) {
|
||||||
wasTransitionRequested = true
|
wasTransitionRequested = true
|
||||||
await transitionStatusRepository.remove(user.uuid, 'revisions')
|
await transitionStatusRepository.remove(user.uuid, 'revisions')
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/auth-server",
|
"name": "@standardnotes/auth-server",
|
||||||
"version": "1.147.0",
|
"version": "1.148.2",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0 <21.0.0"
|
"node": ">=18.0.0 <21.0.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -369,7 +369,7 @@ export class ContainerConfigLoader {
|
|||||||
// Mapping
|
// Mapping
|
||||||
container
|
container
|
||||||
.bind<MapperInterface<SessionTrace, TypeORMSessionTrace>>(TYPES.Auth_SessionTracePersistenceMapper)
|
.bind<MapperInterface<SessionTrace, TypeORMSessionTrace>>(TYPES.Auth_SessionTracePersistenceMapper)
|
||||||
.toConstantValue(new SessionTracePersistenceMapper())
|
.toConstantValue(new SessionTracePersistenceMapper(container.get<TimerInterface>(TYPES.Auth_Timer)))
|
||||||
container
|
container
|
||||||
.bind<MapperInterface<Authenticator, TypeORMAuthenticator>>(TYPES.Auth_AuthenticatorPersistenceMapper)
|
.bind<MapperInterface<Authenticator, TypeORMAuthenticator>>(TYPES.Auth_AuthenticatorPersistenceMapper)
|
||||||
.toConstantValue(new AuthenticatorPersistenceMapper())
|
.toConstantValue(new AuthenticatorPersistenceMapper())
|
||||||
@@ -458,8 +458,9 @@ export class ContainerConfigLoader {
|
|||||||
.bind<SessionTraceRepositoryInterface>(TYPES.Auth_SessionTraceRepository)
|
.bind<SessionTraceRepositoryInterface>(TYPES.Auth_SessionTraceRepository)
|
||||||
.toConstantValue(
|
.toConstantValue(
|
||||||
new TypeORMSessionTraceRepository(
|
new TypeORMSessionTraceRepository(
|
||||||
container.get(TYPES.Auth_ORMSessionTraceRepository),
|
container.get<Repository<TypeORMSessionTrace>>(TYPES.Auth_ORMSessionTraceRepository),
|
||||||
container.get(TYPES.Auth_SessionTracePersistenceMapper),
|
container.get<MapperInterface<SessionTrace, TypeORMSessionTrace>>(TYPES.Auth_SessionTracePersistenceMapper),
|
||||||
|
container.get<TimerInterface>(TYPES.Auth_Timer),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
container
|
container
|
||||||
@@ -1011,7 +1012,6 @@ export class ContainerConfigLoader {
|
|||||||
container.get<SessionRepositoryInterface>(TYPES.Auth_SessionRepository),
|
container.get<SessionRepositoryInterface>(TYPES.Auth_SessionRepository),
|
||||||
container.get<EphemeralSessionRepositoryInterface>(TYPES.Auth_EphemeralSessionRepository),
|
container.get<EphemeralSessionRepositoryInterface>(TYPES.Auth_EphemeralSessionRepository),
|
||||||
container.get<RevokedSessionRepositoryInterface>(TYPES.Auth_RevokedSessionRepository),
|
container.get<RevokedSessionRepositoryInterface>(TYPES.Auth_RevokedSessionRepository),
|
||||||
container.get<RemoveSharedVaultUser>(TYPES.Auth_RemoveSharedVaultUser),
|
|
||||||
container.get<winston.Logger>(TYPES.Auth_Logger),
|
container.get<winston.Logger>(TYPES.Auth_Logger),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import { EphemeralSessionRepositoryInterface } from '../Session/EphemeralSession
|
|||||||
import { RevokedSessionRepositoryInterface } from '../Session/RevokedSessionRepositoryInterface'
|
import { RevokedSessionRepositoryInterface } from '../Session/RevokedSessionRepositoryInterface'
|
||||||
import { SessionRepositoryInterface } from '../Session/SessionRepositoryInterface'
|
import { SessionRepositoryInterface } from '../Session/SessionRepositoryInterface'
|
||||||
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
|
import { UserRepositoryInterface } from '../User/UserRepositoryInterface'
|
||||||
import { RemoveSharedVaultUser } from '../UseCase/RemoveSharedVaultUser/RemoveSharedVaultUser'
|
|
||||||
|
|
||||||
export class AccountDeletionRequestedEventHandler implements DomainEventHandlerInterface {
|
export class AccountDeletionRequestedEventHandler implements DomainEventHandlerInterface {
|
||||||
constructor(
|
constructor(
|
||||||
@@ -14,7 +13,6 @@ export class AccountDeletionRequestedEventHandler implements DomainEventHandlerI
|
|||||||
private sessionRepository: SessionRepositoryInterface,
|
private sessionRepository: SessionRepositoryInterface,
|
||||||
private ephemeralSessionRepository: EphemeralSessionRepositoryInterface,
|
private ephemeralSessionRepository: EphemeralSessionRepositoryInterface,
|
||||||
private revokedSessionRepository: RevokedSessionRepositoryInterface,
|
private revokedSessionRepository: RevokedSessionRepositoryInterface,
|
||||||
private removeSharedVaultUser: RemoveSharedVaultUser,
|
|
||||||
private logger: Logger,
|
private logger: Logger,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@@ -37,13 +35,6 @@ export class AccountDeletionRequestedEventHandler implements DomainEventHandlerI
|
|||||||
|
|
||||||
await this.removeSessions(userUuid.value)
|
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)
|
await this.userRepository.remove(user)
|
||||||
|
|
||||||
this.logger.info(`Finished account cleanup for user: ${userUuid.value}`)
|
this.logger.info(`Finished account cleanup for user: ${userUuid.value}`)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { SubscriptionPlanName, Uuid } from '@standardnotes/domain-core'
|
|||||||
import { SessionTrace } from './SessionTrace'
|
import { SessionTrace } from './SessionTrace'
|
||||||
|
|
||||||
export interface SessionTraceRepositoryInterface {
|
export interface SessionTraceRepositoryInterface {
|
||||||
save(sessionTrace: SessionTrace): Promise<void>
|
insert(sessionTrace: SessionTrace): Promise<void>
|
||||||
removeExpiredBefore(date: Date): Promise<void>
|
removeExpiredBefore(date: Date): Promise<void>
|
||||||
findOneByUserUuidAndDate(userUuid: Uuid, date: Date): Promise<SessionTrace | null>
|
findOneByUserUuidAndDate(userUuid: Uuid, date: Date): Promise<SessionTrace | null>
|
||||||
countByDate(date: Date): Promise<number>
|
countByDate(date: Date): Promise<number>
|
||||||
|
|||||||
@@ -1,14 +1,17 @@
|
|||||||
import 'reflect-metadata'
|
import 'reflect-metadata'
|
||||||
|
|
||||||
import { TokenEncoderInterface, ValetTokenData, ValetTokenOperation } from '@standardnotes/security'
|
import { TransitionStatus } from '@standardnotes/domain-core'
|
||||||
import { CreateValetToken } from './CreateValetToken'
|
|
||||||
import { TimerInterface } from '@standardnotes/time'
|
import { TimerInterface } from '@standardnotes/time'
|
||||||
|
import { TokenEncoderInterface, ValetTokenData, ValetTokenOperation } from '@standardnotes/security'
|
||||||
|
|
||||||
|
import { CreateValetToken } from './CreateValetToken'
|
||||||
import { UserSubscription } from '../../Subscription/UserSubscription'
|
import { UserSubscription } from '../../Subscription/UserSubscription'
|
||||||
import { SubscriptionSettingServiceInterface } from '../../Setting/SubscriptionSettingServiceInterface'
|
import { SubscriptionSettingServiceInterface } from '../../Setting/SubscriptionSettingServiceInterface'
|
||||||
import { User } from '../../User/User'
|
import { User } from '../../User/User'
|
||||||
import { UserSubscriptionType } from '../../Subscription/UserSubscriptionType'
|
import { UserSubscriptionType } from '../../Subscription/UserSubscriptionType'
|
||||||
import { SubscriptionSettingsAssociationServiceInterface } from '../../Setting/SubscriptionSettingsAssociationServiceInterface'
|
import { SubscriptionSettingsAssociationServiceInterface } from '../../Setting/SubscriptionSettingsAssociationServiceInterface'
|
||||||
import { UserSubscriptionServiceInterface } from '../../Subscription/UserSubscriptionServiceInterface'
|
import { UserSubscriptionServiceInterface } from '../../Subscription/UserSubscriptionServiceInterface'
|
||||||
|
import { TransitionStatusRepositoryInterface } from '../../Transition/TransitionStatusRepositoryInterface'
|
||||||
|
|
||||||
describe('CreateValetToken', () => {
|
describe('CreateValetToken', () => {
|
||||||
let tokenEncoder: TokenEncoderInterface<ValetTokenData>
|
let tokenEncoder: TokenEncoderInterface<ValetTokenData>
|
||||||
@@ -20,6 +23,7 @@ describe('CreateValetToken', () => {
|
|||||||
let regularSubscription: UserSubscription
|
let regularSubscription: UserSubscription
|
||||||
let sharedSubscription: UserSubscription
|
let sharedSubscription: UserSubscription
|
||||||
let user: User
|
let user: User
|
||||||
|
let transitionStatusRepository: TransitionStatusRepositoryInterface
|
||||||
|
|
||||||
const createUseCase = () =>
|
const createUseCase = () =>
|
||||||
new CreateValetToken(
|
new CreateValetToken(
|
||||||
@@ -29,6 +33,7 @@ describe('CreateValetToken', () => {
|
|||||||
userSubscriptionService,
|
userSubscriptionService,
|
||||||
timer,
|
timer,
|
||||||
valetTokenTTL,
|
valetTokenTTL,
|
||||||
|
transitionStatusRepository,
|
||||||
)
|
)
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -66,6 +71,11 @@ describe('CreateValetToken', () => {
|
|||||||
|
|
||||||
timer = {} as jest.Mocked<TimerInterface>
|
timer = {} as jest.Mocked<TimerInterface>
|
||||||
timer.getTimestampInMicroseconds = jest.fn().mockReturnValue(100)
|
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 () => {
|
it('should create a read valet token', async () => {
|
||||||
@@ -166,6 +176,7 @@ describe('CreateValetToken', () => {
|
|||||||
{
|
{
|
||||||
sharedSubscriptionUuid: undefined,
|
sharedSubscriptionUuid: undefined,
|
||||||
regularSubscriptionUuid: '1-2-3',
|
regularSubscriptionUuid: '1-2-3',
|
||||||
|
ongoingTransition: false,
|
||||||
permittedOperation: 'write',
|
permittedOperation: 'write',
|
||||||
permittedResources: [
|
permittedResources: [
|
||||||
{
|
{
|
||||||
@@ -206,6 +217,7 @@ describe('CreateValetToken', () => {
|
|||||||
{
|
{
|
||||||
sharedSubscriptionUuid: '2-3-4',
|
sharedSubscriptionUuid: '2-3-4',
|
||||||
regularSubscriptionUuid: '1-2-3',
|
regularSubscriptionUuid: '1-2-3',
|
||||||
|
ongoingTransition: false,
|
||||||
permittedOperation: 'write',
|
permittedOperation: 'write',
|
||||||
permittedResources: [
|
permittedResources: [
|
||||||
{
|
{
|
||||||
@@ -266,6 +278,7 @@ describe('CreateValetToken', () => {
|
|||||||
{
|
{
|
||||||
sharedSubscriptionUuid: undefined,
|
sharedSubscriptionUuid: undefined,
|
||||||
regularSubscriptionUuid: '1-2-3',
|
regularSubscriptionUuid: '1-2-3',
|
||||||
|
ongoingTransition: false,
|
||||||
permittedOperation: 'write',
|
permittedOperation: 'write',
|
||||||
permittedResources: [
|
permittedResources: [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ import { CreateValetTokenDTO } from './CreateValetTokenDTO'
|
|||||||
import { SubscriptionSettingsAssociationServiceInterface } from '../../Setting/SubscriptionSettingsAssociationServiceInterface'
|
import { SubscriptionSettingsAssociationServiceInterface } from '../../Setting/SubscriptionSettingsAssociationServiceInterface'
|
||||||
import { UserSubscriptionServiceInterface } from '../../Subscription/UserSubscriptionServiceInterface'
|
import { UserSubscriptionServiceInterface } from '../../Subscription/UserSubscriptionServiceInterface'
|
||||||
import { CreateValetTokenPayload } from '../../ValetToken/CreateValetTokenPayload'
|
import { CreateValetTokenPayload } from '../../ValetToken/CreateValetTokenPayload'
|
||||||
|
import { TransitionStatusRepositoryInterface } from '../../Transition/TransitionStatusRepositoryInterface'
|
||||||
|
import { TransitionStatus } from '@standardnotes/domain-core'
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class CreateValetToken implements UseCaseInterface {
|
export class CreateValetToken implements UseCaseInterface {
|
||||||
@@ -25,6 +27,8 @@ export class CreateValetToken implements UseCaseInterface {
|
|||||||
@inject(TYPES.Auth_UserSubscriptionService) private userSubscriptionService: UserSubscriptionServiceInterface,
|
@inject(TYPES.Auth_UserSubscriptionService) private userSubscriptionService: UserSubscriptionServiceInterface,
|
||||||
@inject(TYPES.Auth_Timer) private timer: TimerInterface,
|
@inject(TYPES.Auth_Timer) private timer: TimerInterface,
|
||||||
@inject(TYPES.Auth_VALET_TOKEN_TTL) private valetTokenTTL: number,
|
@inject(TYPES.Auth_VALET_TOKEN_TTL) private valetTokenTTL: number,
|
||||||
|
@inject(TYPES.Auth_TransitionStatusRepository)
|
||||||
|
private transitionStatusRepository: TransitionStatusRepositoryInterface,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async execute(dto: CreateValetTokenDTO): Promise<CreateValetTokenResponseData> {
|
async execute(dto: CreateValetTokenDTO): Promise<CreateValetTokenResponseData> {
|
||||||
@@ -83,6 +87,8 @@ export class CreateValetToken implements UseCaseInterface {
|
|||||||
sharedSubscriptionUuid = sharedSubscription.uuid
|
sharedSubscriptionUuid = sharedSubscription.uuid
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const transitionStatus = await this.transitionStatusRepository.getStatus(userUuid, 'items')
|
||||||
|
|
||||||
const tokenData: ValetTokenData = {
|
const tokenData: ValetTokenData = {
|
||||||
userUuid: dto.userUuid,
|
userUuid: dto.userUuid,
|
||||||
permittedOperation: dto.operation,
|
permittedOperation: dto.operation,
|
||||||
@@ -91,6 +97,7 @@ export class CreateValetToken implements UseCaseInterface {
|
|||||||
uploadBytesLimit,
|
uploadBytesLimit,
|
||||||
sharedSubscriptionUuid,
|
sharedSubscriptionUuid,
|
||||||
regularSubscriptionUuid: regularSubscription.uuid,
|
regularSubscriptionUuid: regularSubscription.uuid,
|
||||||
|
ongoingTransition: transitionStatus?.value === TransitionStatus.STATUSES.InProgress,
|
||||||
}
|
}
|
||||||
|
|
||||||
const valetToken = this.tokenEncoder.encodeExpirableToken(tokenData, this.valetTokenTTL)
|
const valetToken = this.tokenEncoder.encodeExpirableToken(tokenData, this.valetTokenTTL)
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ describe('TraceSession', () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
sessionTraceRepository = {} as jest.Mocked<SessionTraceRepositoryInterface>
|
sessionTraceRepository = {} as jest.Mocked<SessionTraceRepositoryInterface>
|
||||||
sessionTraceRepository.findOneByUserUuidAndDate = jest.fn().mockReturnValue(null)
|
sessionTraceRepository.findOneByUserUuidAndDate = jest.fn().mockReturnValue(null)
|
||||||
sessionTraceRepository.save = jest.fn()
|
sessionTraceRepository.insert = jest.fn()
|
||||||
|
|
||||||
timer = {} as jest.Mocked<TimerInterface>
|
timer = {} as jest.Mocked<TimerInterface>
|
||||||
timer.getUTCDateNDaysAhead = jest.fn().mockReturnValue(new Date())
|
timer.getUTCDateNDaysAhead = jest.fn().mockReturnValue(new Date())
|
||||||
@@ -30,7 +30,7 @@ describe('TraceSession', () => {
|
|||||||
|
|
||||||
expect(result.isFailed()).toBe(false)
|
expect(result.isFailed()).toBe(false)
|
||||||
expect(result.getValue().props.userUuid.value).toEqual('0702b137-4f5c-438a-915e-8f8b46572ce5')
|
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 () => {
|
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(result.isFailed()).toBe(false)
|
||||||
expect(sessionTraceRepository.save).not.toHaveBeenCalled()
|
expect(sessionTraceRepository.insert).not.toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should return an error if userUuid is invalid', async () => {
|
it('should return an error if userUuid is invalid', async () => {
|
||||||
@@ -54,7 +54,7 @@ describe('TraceSession', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
expect(result.isFailed()).toBe(true)
|
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 () => {
|
it('should return an error if username is invalid', async () => {
|
||||||
@@ -65,7 +65,7 @@ describe('TraceSession', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
expect(result.isFailed()).toBe(true)
|
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 () => {
|
it('should return an error if subscriptionPlanName is invalid', async () => {
|
||||||
@@ -76,7 +76,7 @@ describe('TraceSession', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
expect(result.isFailed()).toBe(true)
|
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 () => {
|
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(result.isFailed()).toBe(true)
|
||||||
expect(sessionTraceRepository.save).not.toHaveBeenCalled()
|
expect(sessionTraceRepository.insert).not.toHaveBeenCalled()
|
||||||
|
|
||||||
mock.mockRestore()
|
mock.mockRestore()
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ export class TraceSession implements UseCaseInterface<SessionTrace> {
|
|||||||
}
|
}
|
||||||
const sessionTrace = sessionTraceOrError.getValue()
|
const sessionTrace = sessionTraceOrError.getValue()
|
||||||
|
|
||||||
await this.sessionTraceRepository.save(sessionTrace)
|
await this.sessionTraceRepository.insert(sessionTrace)
|
||||||
|
|
||||||
return Result.ok<SessionTrace>(sessionTrace)
|
return Result.ok<SessionTrace>(sessionTrace)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,8 +19,8 @@ export class RedisTransitionStatusRepository implements TransitionStatusReposito
|
|||||||
await this.redisClient.set(`${this.PREFIX}:${transitionType}:${userUuid}`, status.value)
|
await this.redisClient.set(`${this.PREFIX}:${transitionType}:${userUuid}`, status.value)
|
||||||
break
|
break
|
||||||
case TransitionStatus.STATUSES.InProgress: {
|
case TransitionStatus.STATUSES.InProgress: {
|
||||||
const ttl2Hourse = 7_200
|
const ttl24Hours = 86_400
|
||||||
await this.redisClient.setex(`${this.PREFIX}:${transitionType}:${userUuid}`, ttl2Hourse, status.value)
|
await this.redisClient.setex(`${this.PREFIX}:${transitionType}:${userUuid}`, ttl24Hours, status.value)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { MapperInterface, SubscriptionPlanName, Uuid } from '@standardnotes/domain-core'
|
import { MapperInterface, SubscriptionPlanName, Uuid } from '@standardnotes/domain-core'
|
||||||
|
import { TimerInterface } from '@standardnotes/time'
|
||||||
import { Repository } from 'typeorm'
|
import { Repository } from 'typeorm'
|
||||||
|
|
||||||
import { SessionTrace } from '../../Domain/Session/SessionTrace'
|
import { SessionTrace } from '../../Domain/Session/SessionTrace'
|
||||||
import { SessionTraceRepositoryInterface } from '../../Domain/Session/SessionTraceRepositoryInterface'
|
import { SessionTraceRepositoryInterface } from '../../Domain/Session/SessionTraceRepositoryInterface'
|
||||||
import { TypeORMSessionTrace } from './TypeORMSessionTrace'
|
import { TypeORMSessionTrace } from './TypeORMSessionTrace'
|
||||||
@@ -8,13 +10,14 @@ export class TypeORMSessionTraceRepository implements SessionTraceRepositoryInte
|
|||||||
constructor(
|
constructor(
|
||||||
private ormRepository: Repository<TypeORMSessionTrace>,
|
private ormRepository: Repository<TypeORMSessionTrace>,
|
||||||
private mapper: MapperInterface<SessionTrace, TypeORMSessionTrace>,
|
private mapper: MapperInterface<SessionTrace, TypeORMSessionTrace>,
|
||||||
|
private timer: TimerInterface,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async countByDateAndSubscriptionPlanName(date: Date, subscriptionPlanName: SubscriptionPlanName): Promise<number> {
|
async countByDateAndSubscriptionPlanName(date: Date, subscriptionPlanName: SubscriptionPlanName): Promise<number> {
|
||||||
return this.ormRepository
|
return this.ormRepository
|
||||||
.createQueryBuilder('trace')
|
.createQueryBuilder('trace')
|
||||||
.where('trace.creation_date = :creationDate', {
|
.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', {
|
.andWhere('trace.subscription_plan_name = :subscriptionPlanName', {
|
||||||
subscriptionPlanName: subscriptionPlanName.value,
|
subscriptionPlanName: subscriptionPlanName.value,
|
||||||
@@ -26,7 +29,7 @@ export class TypeORMSessionTraceRepository implements SessionTraceRepositoryInte
|
|||||||
return this.ormRepository
|
return this.ormRepository
|
||||||
.createQueryBuilder('trace')
|
.createQueryBuilder('trace')
|
||||||
.where('trace.creation_date = :creationDate', {
|
.where('trace.creation_date = :creationDate', {
|
||||||
creationDate: `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`,
|
creationDate: this.timer.convertDateToFormattedString(date, 'YYYY-MM-DD'),
|
||||||
})
|
})
|
||||||
.getCount()
|
.getCount()
|
||||||
}
|
}
|
||||||
@@ -44,7 +47,7 @@ export class TypeORMSessionTraceRepository implements SessionTraceRepositoryInte
|
|||||||
.createQueryBuilder('trace')
|
.createQueryBuilder('trace')
|
||||||
.where('trace.user_uuid = :userUuid AND trace.creation_date = :creationDate', {
|
.where('trace.user_uuid = :userUuid AND trace.creation_date = :creationDate', {
|
||||||
userUuid: userUuid.value,
|
userUuid: userUuid.value,
|
||||||
creationDate: `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`,
|
creationDate: this.timer.convertDateToFormattedString(date, 'YYYY-MM-DD'),
|
||||||
})
|
})
|
||||||
.getOne()
|
.getOne()
|
||||||
|
|
||||||
@@ -55,9 +58,9 @@ export class TypeORMSessionTraceRepository implements SessionTraceRepositoryInte
|
|||||||
return this.mapper.toDomain(typeOrm)
|
return this.mapper.toDomain(typeOrm)
|
||||||
}
|
}
|
||||||
|
|
||||||
async save(sessionTrace: SessionTrace): Promise<void> {
|
async insert(sessionTrace: SessionTrace): Promise<void> {
|
||||||
const persistence = this.mapper.toProjection(sessionTrace)
|
const persistence = this.mapper.toProjection(sessionTrace)
|
||||||
|
|
||||||
await this.ormRepository.save(persistence)
|
await this.ormRepository.insert(persistence)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
import { MapperInterface, SubscriptionPlanName, UniqueEntityId, Username, Uuid } from '@standardnotes/domain-core'
|
import { MapperInterface, SubscriptionPlanName, UniqueEntityId, Username, Uuid } from '@standardnotes/domain-core'
|
||||||
import { SessionTrace } from '../Domain/Session/SessionTrace'
|
import { SessionTrace } from '../Domain/Session/SessionTrace'
|
||||||
import { TypeORMSessionTrace } from '../Infra/TypeORM/TypeORMSessionTrace'
|
import { TypeORMSessionTrace } from '../Infra/TypeORM/TypeORMSessionTrace'
|
||||||
|
import { TimerInterface } from '@standardnotes/time'
|
||||||
|
|
||||||
export class SessionTracePersistenceMapper implements MapperInterface<SessionTrace, TypeORMSessionTrace> {
|
export class SessionTracePersistenceMapper implements MapperInterface<SessionTrace, TypeORMSessionTrace> {
|
||||||
|
constructor(private timer: TimerInterface) {}
|
||||||
|
|
||||||
toDomain(projection: TypeORMSessionTrace): SessionTrace {
|
toDomain(projection: TypeORMSessionTrace): SessionTrace {
|
||||||
const userUuidOrError = Uuid.create(projection.userUuid)
|
const userUuidOrError = Uuid.create(projection.userUuid)
|
||||||
if (userUuidOrError.isFailed()) {
|
if (userUuidOrError.isFailed()) {
|
||||||
@@ -50,11 +53,7 @@ export class SessionTracePersistenceMapper implements MapperInterface<SessionTra
|
|||||||
typeOrm.username = domain.props.username.value
|
typeOrm.username = domain.props.username.value
|
||||||
typeOrm.subscriptionPlanName = domain.props.subscriptionPlanName ? domain.props.subscriptionPlanName.value : null
|
typeOrm.subscriptionPlanName = domain.props.subscriptionPlanName ? domain.props.subscriptionPlanName.value : null
|
||||||
typeOrm.createdAt = domain.props.createdAt
|
typeOrm.createdAt = domain.props.createdAt
|
||||||
typeOrm.creationDate = new Date(
|
typeOrm.creationDate = new Date(this.timer.convertDateToFormattedString(domain.props.createdAt, 'YYYY-MM-DD'))
|
||||||
domain.props.createdAt.getFullYear(),
|
|
||||||
domain.props.createdAt.getMonth(),
|
|
||||||
domain.props.createdAt.getDate(),
|
|
||||||
)
|
|
||||||
typeOrm.expiresAt = domain.props.expiresAt
|
typeOrm.expiresAt = domain.props.expiresAt
|
||||||
|
|
||||||
return typeOrm
|
return typeOrm
|
||||||
|
|||||||
@@ -3,6 +3,12 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
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)
|
# [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
|
### Features
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/domain-core",
|
"name": "@standardnotes/domain-core",
|
||||||
"version": "1.34.0",
|
"version": "1.34.1",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0 <21.0.0"
|
"node": ">=18.0.0 <21.0.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { NotificationPayloadIdentifierTypeProps } from './NotificationPayloadIde
|
|||||||
export class NotificationPayloadIdentifierType extends ValueObject<NotificationPayloadIdentifierTypeProps> {
|
export class NotificationPayloadIdentifierType extends ValueObject<NotificationPayloadIdentifierTypeProps> {
|
||||||
static readonly TYPES = {
|
static readonly TYPES = {
|
||||||
SharedVaultUuid: 'shared_vault_uuid',
|
SharedVaultUuid: 'shared_vault_uuid',
|
||||||
|
UserUuid: 'user_uuid',
|
||||||
SharedVaultInviteUuid: 'shared_vault_invite_uuid',
|
SharedVaultInviteUuid: 'shared_vault_invite_uuid',
|
||||||
ItemUuid: 'item_uuid',
|
ItemUuid: 'item_uuid',
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ export class NotificationType extends ValueObject<NotificationTypeProps> {
|
|||||||
SharedVaultItemRemoved: 'shared_vault_item_removed',
|
SharedVaultItemRemoved: 'shared_vault_item_removed',
|
||||||
SelfRemovedFromSharedVault: 'self_removed_from_shared_vault',
|
SelfRemovedFromSharedVault: 'self_removed_from_shared_vault',
|
||||||
UserRemovedFromSharedVault: 'user_removed_from_shared_vault',
|
UserRemovedFromSharedVault: 'user_removed_from_shared_vault',
|
||||||
|
UserDesignatedAsSurvivor: 'user_designated_as_survivor',
|
||||||
UserAddedToSharedVault: 'user_added_to_shared_vault',
|
UserAddedToSharedVault: 'user_added_to_shared_vault',
|
||||||
SharedVaultInviteCanceled: 'shared_vault_invite_canceled',
|
SharedVaultInviteCanceled: 'shared_vault_invite_canceled',
|
||||||
SharedVaultFileUploaded: 'shared_vault_file_uploaded',
|
SharedVaultFileUploaded: 'shared_vault_file_uploaded',
|
||||||
|
|||||||
@@ -3,6 +3,10 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
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)
|
# [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
|
### Features
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/domain-events-infra",
|
"name": "@standardnotes/domain-events-infra",
|
||||||
"version": "1.13.0",
|
"version": "1.13.1",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0 <21.0.0"
|
"node": ">=18.0.0 <21.0.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,6 +3,10 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
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)
|
# [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
|
### Features
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/domain-events",
|
"name": "@standardnotes/domain-events",
|
||||||
"version": "2.131.0",
|
"version": "2.131.1",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0 <21.0.0"
|
"node": ">=18.0.0 <21.0.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,6 +3,14 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
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)
|
# [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
|
### Features
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/event-store",
|
"name": "@standardnotes/event-store",
|
||||||
"version": "1.12.0",
|
"version": "1.12.2",
|
||||||
"description": "Event Store Service",
|
"description": "Event Store Service",
|
||||||
"private": true,
|
"private": true,
|
||||||
"main": "dist/src/index.js",
|
"main": "dist/src/index.js",
|
||||||
|
|||||||
@@ -3,6 +3,16 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
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)
|
# [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
|
### Features
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/files-server",
|
"name": "@standardnotes/files-server",
|
||||||
"version": "1.24.0",
|
"version": "1.25.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0 <21.0.0"
|
"node": ">=18.0.0 <21.0.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -198,7 +198,9 @@ export class ContainerConfigLoader {
|
|||||||
.toConstantValue(
|
.toConstantValue(
|
||||||
new FSFileUploader(container.get(TYPES.Files_FILE_UPLOAD_PATH), container.get(TYPES.Files_Logger)),
|
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)
|
container.bind<FileMoverInterface>(TYPES.Files_FileMover).to(FSFileMover)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -247,12 +249,26 @@ export class ContainerConfigLoader {
|
|||||||
// Handlers
|
// Handlers
|
||||||
container
|
container
|
||||||
.bind<AccountDeletionRequestedEventHandler>(TYPES.Files_AccountDeletionRequestedEventHandler)
|
.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
|
container
|
||||||
.bind<SharedSubscriptionInvitationCanceledEventHandler>(
|
.bind<SharedSubscriptionInvitationCanceledEventHandler>(
|
||||||
TYPES.Files_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([
|
const eventHandlers: Map<string, DomainEventHandlerInterface> = new Map([
|
||||||
['ACCOUNT_DELETION_REQUESTED', container.get(TYPES.Files_AccountDeletionRequestedEventHandler)],
|
['ACCOUNT_DELETION_REQUESTED', container.get(TYPES.Files_AccountDeletionRequestedEventHandler)],
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
export type RemovedFileDescription = {
|
export type RemovedFileDescription = {
|
||||||
userUuid: string
|
userOrSharedVaultUuid: string
|
||||||
filePath: string
|
filePath: string
|
||||||
fileName: string
|
fileName: string
|
||||||
fileByteSize: number
|
fileByteSize: number
|
||||||
|
|||||||
@@ -3,18 +3,17 @@ import {
|
|||||||
DomainEventHandlerInterface,
|
DomainEventHandlerInterface,
|
||||||
DomainEventPublisherInterface,
|
DomainEventPublisherInterface,
|
||||||
} from '@standardnotes/domain-events'
|
} from '@standardnotes/domain-events'
|
||||||
import { inject, injectable } from 'inversify'
|
import { Logger } from 'winston'
|
||||||
|
|
||||||
import TYPES from '../../Bootstrap/Types'
|
|
||||||
import { DomainEventFactoryInterface } from '../Event/DomainEventFactoryInterface'
|
import { DomainEventFactoryInterface } from '../Event/DomainEventFactoryInterface'
|
||||||
import { MarkFilesToBeRemoved } from '../UseCase/MarkFilesToBeRemoved/MarkFilesToBeRemoved'
|
import { MarkFilesToBeRemoved } from '../UseCase/MarkFilesToBeRemoved/MarkFilesToBeRemoved'
|
||||||
|
|
||||||
@injectable()
|
|
||||||
export class AccountDeletionRequestedEventHandler implements DomainEventHandlerInterface {
|
export class AccountDeletionRequestedEventHandler implements DomainEventHandlerInterface {
|
||||||
constructor(
|
constructor(
|
||||||
@inject(TYPES.Files_MarkFilesToBeRemoved) private markFilesToBeRemoved: MarkFilesToBeRemoved,
|
private markFilesToBeRemoved: MarkFilesToBeRemoved,
|
||||||
@inject(TYPES.Files_DomainEventPublisher) private domainEventPublisher: DomainEventPublisherInterface,
|
private domainEventPublisher: DomainEventPublisherInterface,
|
||||||
@inject(TYPES.Files_DomainEventFactory) private domainEventFactory: DomainEventFactoryInterface,
|
private domainEventFactory: DomainEventFactoryInterface,
|
||||||
|
private logger: Logger,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async handle(event: AccountDeletionRequestedEvent): Promise<void> {
|
async handle(event: AccountDeletionRequestedEvent): Promise<void> {
|
||||||
@@ -27,16 +26,23 @@ export class AccountDeletionRequestedEventHandler implements DomainEventHandlerI
|
|||||||
})
|
})
|
||||||
|
|
||||||
if (result.isFailed()) {
|
if (result.isFailed()) {
|
||||||
|
this.logger.error(`Could not mark files for removal for user ${event.payload.userUuid}: ${result.getError()}`)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const filesRemoved = result.getValue()
|
const filesRemoved = result.getValue()
|
||||||
|
|
||||||
|
this.logger.debug(`Marked ${filesRemoved.length} files for removal for user ${event.payload.userUuid}`)
|
||||||
|
|
||||||
for (const fileRemoved of filesRemoved) {
|
for (const fileRemoved of filesRemoved) {
|
||||||
await this.domainEventPublisher.publish(
|
await this.domainEventPublisher.publish(
|
||||||
this.domainEventFactory.createFileRemovedEvent({
|
this.domainEventFactory.createFileRemovedEvent({
|
||||||
regularSubscriptionUuid: event.payload.regularSubscriptionUuid,
|
regularSubscriptionUuid: event.payload.regularSubscriptionUuid,
|
||||||
...fileRemoved,
|
userUuid: fileRemoved.userOrSharedVaultUuid,
|
||||||
|
filePath: fileRemoved.filePath,
|
||||||
|
fileName: fileRemoved.fileName,
|
||||||
|
fileByteSize: fileRemoved.fileByteSize,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-7
@@ -3,18 +3,17 @@ import {
|
|||||||
DomainEventHandlerInterface,
|
DomainEventHandlerInterface,
|
||||||
DomainEventPublisherInterface,
|
DomainEventPublisherInterface,
|
||||||
} from '@standardnotes/domain-events'
|
} from '@standardnotes/domain-events'
|
||||||
import { inject, injectable } from 'inversify'
|
import { Logger } from 'winston'
|
||||||
|
|
||||||
import TYPES from '../../Bootstrap/Types'
|
|
||||||
import { DomainEventFactoryInterface } from '../Event/DomainEventFactoryInterface'
|
import { DomainEventFactoryInterface } from '../Event/DomainEventFactoryInterface'
|
||||||
import { MarkFilesToBeRemoved } from '../UseCase/MarkFilesToBeRemoved/MarkFilesToBeRemoved'
|
import { MarkFilesToBeRemoved } from '../UseCase/MarkFilesToBeRemoved/MarkFilesToBeRemoved'
|
||||||
|
|
||||||
@injectable()
|
|
||||||
export class SharedSubscriptionInvitationCanceledEventHandler implements DomainEventHandlerInterface {
|
export class SharedSubscriptionInvitationCanceledEventHandler implements DomainEventHandlerInterface {
|
||||||
constructor(
|
constructor(
|
||||||
@inject(TYPES.Files_MarkFilesToBeRemoved) private markFilesToBeRemoved: MarkFilesToBeRemoved,
|
private markFilesToBeRemoved: MarkFilesToBeRemoved,
|
||||||
@inject(TYPES.Files_DomainEventPublisher) private domainEventPublisher: DomainEventPublisherInterface,
|
private domainEventPublisher: DomainEventPublisherInterface,
|
||||||
@inject(TYPES.Files_DomainEventFactory) private domainEventFactory: DomainEventFactoryInterface,
|
private domainEventFactory: DomainEventFactoryInterface,
|
||||||
|
private logger: Logger,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async handle(event: SharedSubscriptionInvitationCanceledEvent): Promise<void> {
|
async handle(event: SharedSubscriptionInvitationCanceledEvent): Promise<void> {
|
||||||
@@ -27,16 +26,25 @@ export class SharedSubscriptionInvitationCanceledEventHandler implements DomainE
|
|||||||
})
|
})
|
||||||
|
|
||||||
if (result.isFailed()) {
|
if (result.isFailed()) {
|
||||||
|
this.logger.error(
|
||||||
|
`Could not mark files to be removed for invitee: ${event.payload.inviteeIdentifier}: ${result.getError()}`,
|
||||||
|
)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const filesRemoved = result.getValue()
|
const filesRemoved = result.getValue()
|
||||||
|
|
||||||
|
this.logger.debug(`Marked ${filesRemoved.length} files for removal for invitee ${event.payload.inviteeIdentifier}`)
|
||||||
|
|
||||||
for (const fileRemoved of filesRemoved) {
|
for (const fileRemoved of filesRemoved) {
|
||||||
await this.domainEventPublisher.publish(
|
await this.domainEventPublisher.publish(
|
||||||
this.domainEventFactory.createFileRemovedEvent({
|
this.domainEventFactory.createFileRemovedEvent({
|
||||||
regularSubscriptionUuid: event.payload.inviterSubscriptionUuid,
|
regularSubscriptionUuid: event.payload.inviterSubscriptionUuid,
|
||||||
...fileRemoved,
|
userUuid: fileRemoved.userOrSharedVaultUuid,
|
||||||
|
filePath: fileRemoved.filePath,
|
||||||
|
fileName: fileRemoved.fileName,
|
||||||
|
fileByteSize: fileRemoved.fileByteSize,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,10 @@ export class SharedVaultRemovedEventHandler implements DomainEventHandlerInterfa
|
|||||||
|
|
||||||
const filesRemoved = result.getValue()
|
const filesRemoved = result.getValue()
|
||||||
|
|
||||||
|
this.logger.debug(
|
||||||
|
`Marked ${filesRemoved.length} files for removal for shared vault ${event.payload.sharedVaultUuid}`,
|
||||||
|
)
|
||||||
|
|
||||||
for (const fileRemoved of filesRemoved) {
|
for (const fileRemoved of filesRemoved) {
|
||||||
await this.domainEventPublisher.publish(
|
await this.domainEventPublisher.publish(
|
||||||
this.domainEventFactory.createSharedVaultFileRemovedEvent({
|
this.domainEventFactory.createSharedVaultFileRemovedEvent({
|
||||||
|
|||||||
@@ -2,5 +2,5 @@ import { RemovedFileDescription } from '../File/RemovedFileDescription'
|
|||||||
|
|
||||||
export interface FileRemoverInterface {
|
export interface FileRemoverInterface {
|
||||||
remove(filePath: string): Promise<number>
|
remove(filePath: string): Promise<number>
|
||||||
markFilesToBeRemoved(userUuid: string): Promise<Array<RemovedFileDescription>>
|
markFilesToBeRemoved(userOrSharedVaultUuid: string): Promise<Array<RemovedFileDescription>>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,42 @@
|
|||||||
import { inject, injectable } from 'inversify'
|
|
||||||
import { promises } from 'fs'
|
import { promises } from 'fs'
|
||||||
|
|
||||||
import { FileRemoverInterface } from '../../Domain/Services/FileRemoverInterface'
|
import { FileRemoverInterface } from '../../Domain/Services/FileRemoverInterface'
|
||||||
import { RemovedFileDescription } from '../../Domain/File/RemovedFileDescription'
|
import { RemovedFileDescription } from '../../Domain/File/RemovedFileDescription'
|
||||||
import TYPES from '../../Bootstrap/Types'
|
|
||||||
|
|
||||||
@injectable()
|
|
||||||
export class FSFileRemover implements FileRemoverInterface {
|
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>> {
|
async markFilesToBeRemoved(userOrSharedVaultUuid: string): Promise<Array<RemovedFileDescription>> {
|
||||||
await promises.rmdir(`${this.fileUploadPath}/${userUuid}`)
|
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> {
|
async remove(filePath: string): Promise<number> {
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ describe('ValetTokenAuthMiddleware', () => {
|
|||||||
|
|
||||||
const logger = {
|
const logger = {
|
||||||
debug: jest.fn(),
|
debug: jest.fn(),
|
||||||
|
error: jest.fn(),
|
||||||
} as unknown as jest.Mocked<Logger>
|
} as unknown as jest.Mocked<Logger>
|
||||||
|
|
||||||
const createMiddleware = () => new ValetTokenAuthMiddleware(tokenDecoder, logger)
|
const createMiddleware = () => new ValetTokenAuthMiddleware(tokenDecoder, logger)
|
||||||
@@ -222,4 +223,27 @@ describe('ValetTokenAuthMiddleware', () => {
|
|||||||
|
|
||||||
expect(next).toHaveBeenCalledWith(error)
|
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()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -46,6 +46,19 @@ export class ValetTokenAuthMiddleware extends BaseMiddleware {
|
|||||||
return
|
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) {
|
for (const resource of valetTokenData.permittedResources) {
|
||||||
const resourceUuidOrError = Uuid.create(resource.remoteIdentifier)
|
const resourceUuidOrError = Uuid.create(resource.remoteIdentifier)
|
||||||
if (resourceUuidOrError.isFailed()) {
|
if (resourceUuidOrError.isFailed()) {
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ export class S3FileRemover implements FileRemoverInterface {
|
|||||||
fileByteSize: file.Size as number,
|
fileByteSize: file.Size as number,
|
||||||
fileName: file.Key.replace(`${userUuid}/`, ''),
|
fileName: file.Key.replace(`${userUuid}/`, ''),
|
||||||
filePath: file.Key,
|
filePath: file.Key,
|
||||||
userUuid,
|
userOrSharedVaultUuid: userUuid,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,22 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [1.16.11](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.16.10...@standardnotes/home-server@1.16.11) (2023-09-29)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/home-server
|
||||||
|
|
||||||
|
## [1.16.10](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.16.9...@standardnotes/home-server@1.16.10) (2023-09-29)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/home-server
|
||||||
|
|
||||||
|
## [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)
|
## [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
|
**Note:** Version bump only for package @standardnotes/home-server
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/home-server",
|
"name": "@standardnotes/home-server",
|
||||||
"version": "1.16.7",
|
"version": "1.16.11",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0 <21.0.0"
|
"node": ">=18.0.0 <21.0.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,6 +3,20 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [1.38.3](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.38.2...@standardnotes/revisions-server@1.38.3) (2023-09-29)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* add paging memory to integrity check ([e4ca310](https://github.com/standardnotes/server/commit/e4ca310707b12b1c08073a391e8857ee52acd92b))
|
||||||
|
|
||||||
|
## [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)
|
# [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
|
### Features
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/revisions-server",
|
"name": "@standardnotes/revisions-server",
|
||||||
"version": "1.38.0",
|
"version": "1.38.3",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0 <21.0.0"
|
"node": ">=18.0.0 <21.0.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
export interface TransitionRepositoryInterface {
|
export interface TransitionRepositoryInterface {
|
||||||
getPagingProgress(userUuid: string): Promise<number>
|
getPagingProgress(userUuid: string): Promise<number>
|
||||||
setPagingProgress(userUuid: string, progress: number): Promise<void>
|
setPagingProgress(userUuid: string, progress: number): Promise<void>
|
||||||
|
getIntegrityProgress(userUuid: string): Promise<number>
|
||||||
|
setIntegrityProgress(userUuid: string, progress: number): Promise<void>
|
||||||
}
|
}
|
||||||
|
|||||||
+33
-20
@@ -42,7 +42,6 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
|
|||||||
if (migrationResult.isFailed()) {
|
if (migrationResult.isFailed()) {
|
||||||
return Result.fail(migrationResult.getError())
|
return Result.fail(migrationResult.getError())
|
||||||
}
|
}
|
||||||
const revisionsToSkipInIntegrityCheck = migrationResult.getValue()
|
|
||||||
|
|
||||||
this.logger.info(`[${dto.userUuid}] Revisions migrated`)
|
this.logger.info(`[${dto.userUuid}] Revisions migrated`)
|
||||||
|
|
||||||
@@ -50,11 +49,11 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
|
|||||||
|
|
||||||
this.logger.info(`[${dto.userUuid}] Checking integrity between primary and secondary database`)
|
this.logger.info(`[${dto.userUuid}] Checking integrity between primary and secondary database`)
|
||||||
|
|
||||||
const integrityCheckResult = await this.checkIntegrityBetweenPrimaryAndSecondaryDatabase(
|
const integrityCheckResult = await this.checkIntegrityBetweenPrimaryAndSecondaryDatabase(userUuid)
|
||||||
userUuid,
|
|
||||||
revisionsToSkipInIntegrityCheck,
|
|
||||||
)
|
|
||||||
if (integrityCheckResult.isFailed()) {
|
if (integrityCheckResult.isFailed()) {
|
||||||
|
await (this.transitionStatusRepository as TransitionRepositoryInterface).setPagingProgress(userUuid.value, 1)
|
||||||
|
await (this.transitionStatusRepository as TransitionRepositoryInterface).setIntegrityProgress(userUuid.value, 1)
|
||||||
|
|
||||||
return Result.fail(integrityCheckResult.getError())
|
return Result.fail(integrityCheckResult.getError())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,7 +74,7 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
|
|||||||
return Result.ok()
|
return Result.ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
private async migrateRevisionsForUser(userUuid: Uuid): Promise<Result<string[]>> {
|
private async migrateRevisionsForUser(userUuid: Uuid): Promise<Result<void>> {
|
||||||
try {
|
try {
|
||||||
const initialPage = await (this.transitionStatusRepository as TransitionRepositoryInterface).getPagingProgress(
|
const initialPage = await (this.transitionStatusRepository as TransitionRepositoryInterface).getPagingProgress(
|
||||||
userUuid.value,
|
userUuid.value,
|
||||||
@@ -85,7 +84,6 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
|
|||||||
|
|
||||||
const totalRevisionsCountForUser = await this.primaryRevisionsRepository.countByUserUuid(userUuid)
|
const totalRevisionsCountForUser = await this.primaryRevisionsRepository.countByUserUuid(userUuid)
|
||||||
const totalPages = Math.ceil(totalRevisionsCountForUser / this.pageSize)
|
const totalPages = Math.ceil(totalRevisionsCountForUser / this.pageSize)
|
||||||
const revisionsToSkipInIntegrityCheck = []
|
|
||||||
for (let currentPage = initialPage; currentPage <= totalPages; currentPage++) {
|
for (let currentPage = initialPage; currentPage <= totalPages; currentPage++) {
|
||||||
await (this.transitionStatusRepository as TransitionRepositoryInterface).setPagingProgress(
|
await (this.transitionStatusRepository as TransitionRepositoryInterface).setPagingProgress(
|
||||||
userUuid.value,
|
userUuid.value,
|
||||||
@@ -113,7 +111,6 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
|
|||||||
this.logger.info(
|
this.logger.info(
|
||||||
`[${userUuid.value}] Revision ${revision.id.toString()} is older than revision in secondary database`,
|
`[${userUuid.value}] Revision ${revision.id.toString()} is older than revision in secondary database`,
|
||||||
)
|
)
|
||||||
revisionsToSkipInIntegrityCheck.push(revision.id.toString())
|
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -145,7 +142,7 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Result.ok(revisionsToSkipInIntegrityCheck)
|
return Result.ok()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return Result.fail(`Errored when migrating revisions for user ${userUuid.value}: ${(error as Error).message}`)
|
return Result.fail(`Errored when migrating revisions for user ${userUuid.value}: ${(error as Error).message}`)
|
||||||
}
|
}
|
||||||
@@ -171,11 +168,14 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
|
|||||||
await this.timer.sleep(twoSecondsInMilliseconds)
|
await this.timer.sleep(twoSecondsInMilliseconds)
|
||||||
}
|
}
|
||||||
|
|
||||||
private async checkIntegrityBetweenPrimaryAndSecondaryDatabase(
|
private async checkIntegrityBetweenPrimaryAndSecondaryDatabase(userUuid: Uuid): Promise<Result<boolean>> {
|
||||||
userUuid: Uuid,
|
|
||||||
revisionsToSkipInIntegrityCheck: string[],
|
|
||||||
): Promise<Result<boolean>> {
|
|
||||||
try {
|
try {
|
||||||
|
const initialPage = await (this.transitionStatusRepository as TransitionRepositoryInterface).getIntegrityProgress(
|
||||||
|
userUuid.value,
|
||||||
|
)
|
||||||
|
|
||||||
|
this.logger.info(`[${userUuid.value}] Checking integrity from page ${initialPage}`)
|
||||||
|
|
||||||
const totalRevisionsCountForUserInPrimary = await this.primaryRevisionsRepository.countByUserUuid(userUuid)
|
const totalRevisionsCountForUserInPrimary = await this.primaryRevisionsRepository.countByUserUuid(userUuid)
|
||||||
const totalRevisionsCountForUserInSecondary = await (
|
const totalRevisionsCountForUserInSecondary = await (
|
||||||
this.secondRevisionsRepository as RevisionRepositoryInterface
|
this.secondRevisionsRepository as RevisionRepositoryInterface
|
||||||
@@ -188,7 +188,12 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
const totalPages = Math.ceil(totalRevisionsCountForUserInPrimary / this.pageSize)
|
const totalPages = Math.ceil(totalRevisionsCountForUserInPrimary / this.pageSize)
|
||||||
for (let currentPage = 1; currentPage <= totalPages; currentPage++) {
|
for (let currentPage = initialPage; currentPage <= totalPages; currentPage++) {
|
||||||
|
await (this.transitionStatusRepository as TransitionRepositoryInterface).setIntegrityProgress(
|
||||||
|
userUuid.value,
|
||||||
|
currentPage,
|
||||||
|
)
|
||||||
|
|
||||||
const query = {
|
const query = {
|
||||||
userUuid: userUuid,
|
userUuid: userUuid,
|
||||||
offset: (currentPage - 1) * this.pageSize,
|
offset: (currentPage - 1) * this.pageSize,
|
||||||
@@ -212,17 +217,25 @@ export class TransitionRevisionsFromPrimaryToSecondaryDatabaseForUser implements
|
|||||||
return Result.fail(`Revision ${revision.id.toString()} not found in secondary database`)
|
return Result.fail(`Revision ${revision.id.toString()} not found in secondary database`)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (revisionsToSkipInIntegrityCheck.includes(revision.id.toString())) {
|
if (revision.isIdenticalTo(revisionInSecondary)) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!revision.isIdenticalTo(revisionInSecondary)) {
|
if (revisionInSecondary.props.dates.updatedAt > revision.props.dates.updatedAt) {
|
||||||
return Result.fail(
|
this.logger.info(
|
||||||
`Revision ${revision.id.toString()} is not identical in primary and secondary database. Revision in primary database: ${JSON.stringify(
|
`[${
|
||||||
revision,
|
userUuid.value
|
||||||
)}, revision in secondary database: ${JSON.stringify(revisionInSecondary)}`,
|
}] Integrity check of revision ${revision.id.toString()} - is older than revision in secondary database`,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return Result.fail(
|
||||||
|
`Revision ${revision.id.toString()} is not identical in primary and secondary database. Revision in primary database: ${JSON.stringify(
|
||||||
|
revision,
|
||||||
|
)}, revision in secondary database: ${JSON.stringify(revisionInSecondary)}`,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,10 +3,25 @@ import * as IORedis from 'ioredis'
|
|||||||
import { TransitionRepositoryInterface } from '../../Domain/Transition/TransitionRepositoryInterface'
|
import { TransitionRepositoryInterface } from '../../Domain/Transition/TransitionRepositoryInterface'
|
||||||
|
|
||||||
export class RedisTransitionRepository implements TransitionRepositoryInterface {
|
export class RedisTransitionRepository implements TransitionRepositoryInterface {
|
||||||
private readonly PREFIX = 'transition-revisions-paging-progress'
|
private readonly PREFIX = 'transition-revisions-migration-progress'
|
||||||
|
private readonly INTEGRITY_PREFIX = 'transition-revisions-integrity-progress'
|
||||||
|
|
||||||
constructor(private redisClient: IORedis.Redis) {}
|
constructor(private redisClient: IORedis.Redis) {}
|
||||||
|
|
||||||
|
async getIntegrityProgress(userUuid: string): Promise<number> {
|
||||||
|
const progress = await this.redisClient.get(`${this.INTEGRITY_PREFIX}:${userUuid}`)
|
||||||
|
|
||||||
|
if (progress === null) {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return parseInt(progress)
|
||||||
|
}
|
||||||
|
|
||||||
|
async setIntegrityProgress(userUuid: string, progress: number): Promise<void> {
|
||||||
|
await this.redisClient.setex(`${this.INTEGRITY_PREFIX}:${userUuid}`, 172_800, progress.toString())
|
||||||
|
}
|
||||||
|
|
||||||
async getPagingProgress(userUuid: string): Promise<number> {
|
async getPagingProgress(userUuid: string): Promise<number> {
|
||||||
const progress = await this.redisClient.get(`${this.PREFIX}:${userUuid}`)
|
const progress = await this.redisClient.get(`${this.PREFIX}:${userUuid}`)
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ export class SQLLegacyRevisionRepository implements RevisionRepositoryInterface
|
|||||||
const queryBuilder = this.ormRepository
|
const queryBuilder = this.ormRepository
|
||||||
.createQueryBuilder('revision')
|
.createQueryBuilder('revision')
|
||||||
.where('revision.user_uuid = :userUuid', { userUuid: dto.userUuid.value })
|
.where('revision.user_uuid = :userUuid', { userUuid: dto.userUuid.value })
|
||||||
.orderBy('revision.uuid', 'ASC')
|
.orderBy('revision.created_at', 'ASC')
|
||||||
|
|
||||||
if (dto.offset !== undefined) {
|
if (dto.offset !== undefined) {
|
||||||
queryBuilder.skip(dto.offset)
|
queryBuilder.skip(dto.offset)
|
||||||
|
|||||||
@@ -3,6 +3,14 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
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)
|
# [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
|
### Features
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/scheduler-server",
|
"name": "@standardnotes/scheduler-server",
|
||||||
"version": "1.21.0",
|
"version": "1.21.2",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0 <21.0.0"
|
"node": ">=18.0.0 <21.0.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,6 +3,12 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
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)
|
# [1.14.0](https://github.com/standardnotes/server/compare/@standardnotes/security@1.13.1...@standardnotes/security@1.14.0) (2023-09-26)
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/security",
|
"name": "@standardnotes/security",
|
||||||
"version": "1.14.0",
|
"version": "1.15.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0 <21.0.0"
|
"node": ">=18.0.0 <21.0.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -11,4 +11,5 @@ export type ValetTokenData = {
|
|||||||
}>
|
}>
|
||||||
uploadBytesUsed: number
|
uploadBytesUsed: number
|
||||||
uploadBytesLimit: number
|
uploadBytesLimit: number
|
||||||
|
ongoingTransition?: boolean
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,10 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
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)
|
## [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
|
**Note:** Version bump only for package @standardnotes/settings
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/settings",
|
"name": "@standardnotes/settings",
|
||||||
"version": "1.21.40",
|
"version": "1.21.41",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0 <21.0.0"
|
"node": ">=18.0.0 <21.0.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,6 +3,26 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
# [1.110.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.109.2...@standardnotes/syncing-server@1.110.0) (2023-09-29)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* add paging memory to integrity check ([e4ca310](https://github.com/standardnotes/syncing-server-js/commit/e4ca310707b12b1c08073a391e8857ee52acd92b))
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **syncing-server:** allow surviving only upon account deletion ([#857](https://github.com/standardnotes/syncing-server-js/issues/857)) ([609e85f](https://github.com/standardnotes/syncing-server-js/commit/609e85f926ebbc2887656c46df18471c68d70185))
|
||||||
|
|
||||||
|
## [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)
|
# [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
|
### Features
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/syncing-server",
|
"name": "@standardnotes/syncing-server",
|
||||||
"version": "1.109.0",
|
"version": "1.110.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0 <21.0.0"
|
"node": ">=18.0.0 <21.0.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -899,6 +899,7 @@ export class ContainerConfigLoader {
|
|||||||
container.get<TimerInterface>(TYPES.Sync_Timer),
|
container.get<TimerInterface>(TYPES.Sync_Timer),
|
||||||
container.get<DomainEventFactoryInterface>(TYPES.Sync_DomainEventFactory),
|
container.get<DomainEventFactoryInterface>(TYPES.Sync_DomainEventFactory),
|
||||||
container.get<DomainEventPublisherInterface>(TYPES.Sync_DomainEventPublisher),
|
container.get<DomainEventPublisherInterface>(TYPES.Sync_DomainEventPublisher),
|
||||||
|
container.get<AddNotificationForUser>(TYPES.Sync_AddNotificationForUser),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
container
|
container
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { AccountDeletionRequestedEvent, DomainEventHandlerInterface } from '@standardnotes/domain-events'
|
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 { Logger } from 'winston'
|
||||||
|
|
||||||
import { ItemRepositoryResolverInterface } from '../Item/ItemRepositoryResolverInterface'
|
import { ItemRepositoryResolverInterface } from '../Item/ItemRepositoryResolverInterface'
|
||||||
@@ -15,18 +15,29 @@ export class AccountDeletionRequestedEventHandler implements DomainEventHandlerI
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
async handle(event: AccountDeletionRequestedEvent): Promise<void> {
|
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)
|
const roleNamesOrError = RoleNameCollection.create(event.payload.roleNames)
|
||||||
if (roleNamesOrError.isFailed()) {
|
if (roleNamesOrError.isFailed()) {
|
||||||
|
this.logger.error(`AccountDeletionRequestedEventHandler failed: ${roleNamesOrError.getError()}`)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const roleNames = roleNamesOrError.getValue()
|
const roleNames = roleNamesOrError.getValue()
|
||||||
|
|
||||||
const itemRepository = this.itemRepositoryResolver.resolve(roleNames)
|
const itemRepository = this.itemRepositoryResolver.resolve(roleNames)
|
||||||
|
|
||||||
await itemRepository.deleteByUserUuid(event.payload.userUuid)
|
await itemRepository.deleteByUserUuidAndNotInSharedVault(userUuid)
|
||||||
|
|
||||||
const deletingVaultsResult = await this.deleteSharedVaults.execute({
|
const deletingVaultsResult = await this.deleteSharedVaults.execute({
|
||||||
ownerUuid: event.payload.userUuid,
|
ownerUuid: event.payload.userUuid,
|
||||||
|
allowSurviving: true,
|
||||||
})
|
})
|
||||||
if (deletingVaultsResult.isFailed()) {
|
if (deletingVaultsResult.isFailed()) {
|
||||||
this.logger.error(
|
this.logger.error(
|
||||||
@@ -34,6 +45,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({
|
const deletingUserFromOtherVaultsResult = await this.removeUserFromSharedVaults.execute({
|
||||||
userUuid: event.payload.userUuid,
|
userUuid: event.payload.userUuid,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ import { ExtendedIntegrityPayload } from './ExtendedIntegrityPayload'
|
|||||||
import { ItemContentSizeDescriptor } from './ItemContentSizeDescriptor'
|
import { ItemContentSizeDescriptor } from './ItemContentSizeDescriptor'
|
||||||
|
|
||||||
export interface ItemRepositoryInterface {
|
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[]>
|
findAll(query: ItemQuery): Promise<Item[]>
|
||||||
countAll(query: ItemQuery): Promise<number>
|
countAll(query: ItemQuery): Promise<number>
|
||||||
findContentSizeForComputingTransferLimit(query: ItemQuery): Promise<Array<ItemContentSizeDescriptor>>
|
findContentSizeForComputingTransferLimit(query: ItemQuery): Promise<Array<ItemContentSizeDescriptor>>
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
export interface TransitionRepositoryInterface {
|
export interface TransitionRepositoryInterface {
|
||||||
getPagingProgress(userUuid: string): Promise<number>
|
getPagingProgress(userUuid: string): Promise<number>
|
||||||
setPagingProgress(userUuid: string, progress: number): Promise<void>
|
setPagingProgress(userUuid: string, progress: number): Promise<void>
|
||||||
|
getIntegrityProgress(userUuid: string): Promise<number>
|
||||||
|
setIntegrityProgress(userUuid: string, progress: number): Promise<void>
|
||||||
}
|
}
|
||||||
|
|||||||
+10
@@ -96,6 +96,7 @@ describe('DeleteSharedVault', () => {
|
|||||||
const result = await useCase.execute({
|
const result = await useCase.execute({
|
||||||
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
|
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
originatorUuid: '00000000-0000-0000-0000-000000000000',
|
originatorUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
|
allowSurviving: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(result.isFailed()).toBeFalsy()
|
expect(result.isFailed()).toBeFalsy()
|
||||||
@@ -111,6 +112,7 @@ describe('DeleteSharedVault', () => {
|
|||||||
const result = await useCase.execute({
|
const result = await useCase.execute({
|
||||||
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
|
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
originatorUuid: '00000000-0000-0000-0000-000000000000',
|
originatorUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
|
allowSurviving: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(result.isFailed()).toBeTruthy()
|
expect(result.isFailed()).toBeTruthy()
|
||||||
@@ -125,6 +127,7 @@ describe('DeleteSharedVault', () => {
|
|||||||
const result = await useCase.execute({
|
const result = await useCase.execute({
|
||||||
sharedVaultUuid: 'invalid',
|
sharedVaultUuid: 'invalid',
|
||||||
originatorUuid: '00000000-0000-0000-0000-000000000000',
|
originatorUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
|
allowSurviving: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(result.isFailed()).toBeTruthy()
|
expect(result.isFailed()).toBeTruthy()
|
||||||
@@ -139,6 +142,7 @@ describe('DeleteSharedVault', () => {
|
|||||||
const result = await useCase.execute({
|
const result = await useCase.execute({
|
||||||
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
|
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
originatorUuid: 'invalid',
|
originatorUuid: 'invalid',
|
||||||
|
allowSurviving: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(result.isFailed()).toBeTruthy()
|
expect(result.isFailed()).toBeTruthy()
|
||||||
@@ -159,6 +163,7 @@ describe('DeleteSharedVault', () => {
|
|||||||
const result = await useCase.execute({
|
const result = await useCase.execute({
|
||||||
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
|
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
originatorUuid: '00000000-0000-0000-0000-000000000000',
|
originatorUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
|
allowSurviving: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(result.isFailed()).toBeTruthy()
|
expect(result.isFailed()).toBeTruthy()
|
||||||
@@ -174,6 +179,7 @@ describe('DeleteSharedVault', () => {
|
|||||||
const result = await useCase.execute({
|
const result = await useCase.execute({
|
||||||
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
|
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
originatorUuid: '00000000-0000-0000-0000-000000000000',
|
originatorUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
|
allowSurviving: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(result.isFailed()).toBeTruthy()
|
expect(result.isFailed()).toBeTruthy()
|
||||||
@@ -188,6 +194,7 @@ describe('DeleteSharedVault', () => {
|
|||||||
const result = await useCase.execute({
|
const result = await useCase.execute({
|
||||||
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
|
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
originatorUuid: '00000000-0000-0000-0000-000000000000',
|
originatorUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
|
allowSurviving: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(result.isFailed()).toBeTruthy()
|
expect(result.isFailed()).toBeTruthy()
|
||||||
@@ -207,6 +214,7 @@ describe('DeleteSharedVault', () => {
|
|||||||
const result = await useCase.execute({
|
const result = await useCase.execute({
|
||||||
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
|
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
originatorUuid: '00000000-0000-0000-0000-000000000000',
|
originatorUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
|
allowSurviving: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(result.isFailed()).toBeFalsy()
|
expect(result.isFailed()).toBeFalsy()
|
||||||
@@ -223,6 +231,7 @@ describe('DeleteSharedVault', () => {
|
|||||||
const result = await useCase.execute({
|
const result = await useCase.execute({
|
||||||
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
|
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
originatorUuid: '00000000-0000-0000-0000-000000000000',
|
originatorUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
|
allowSurviving: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(result.isFailed()).toBeTruthy()
|
expect(result.isFailed()).toBeTruthy()
|
||||||
@@ -239,6 +248,7 @@ describe('DeleteSharedVault', () => {
|
|||||||
const result = await useCase.execute({
|
const result = await useCase.execute({
|
||||||
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
|
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
originatorUuid: '00000000-0000-0000-0000-000000000000',
|
originatorUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
|
allowSurviving: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(result.isFailed()).toBeTruthy()
|
expect(result.isFailed()).toBeTruthy()
|
||||||
|
|||||||
+27
-25
@@ -10,7 +10,7 @@ import { CancelInviteToSharedVault } from '../CancelInviteToSharedVault/CancelIn
|
|||||||
import { DomainEventFactoryInterface } from '../../../Event/DomainEventFactoryInterface'
|
import { DomainEventFactoryInterface } from '../../../Event/DomainEventFactoryInterface'
|
||||||
import { TransferSharedVault } from '../TransferSharedVault/TransferSharedVault'
|
import { TransferSharedVault } from '../TransferSharedVault/TransferSharedVault'
|
||||||
|
|
||||||
export class DeleteSharedVault implements UseCaseInterface<void> {
|
export class DeleteSharedVault implements UseCaseInterface<{ status: 'deleted' | 'transitioned' }> {
|
||||||
constructor(
|
constructor(
|
||||||
private sharedVaultRepository: SharedVaultRepositoryInterface,
|
private sharedVaultRepository: SharedVaultRepositoryInterface,
|
||||||
private sharedVaultUserRepository: SharedVaultUserRepositoryInterface,
|
private sharedVaultUserRepository: SharedVaultUserRepositoryInterface,
|
||||||
@@ -22,7 +22,7 @@ export class DeleteSharedVault implements UseCaseInterface<void> {
|
|||||||
private transferSharedVault: TransferSharedVault,
|
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)
|
const originatorUuidOrError = Uuid.create(dto.originatorUuid)
|
||||||
if (originatorUuidOrError.isFailed()) {
|
if (originatorUuidOrError.isFailed()) {
|
||||||
return Result.fail(originatorUuidOrError.getError())
|
return Result.fail(originatorUuidOrError.getError())
|
||||||
@@ -56,30 +56,32 @@ export class DeleteSharedVault implements UseCaseInterface<void> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const sharedVaultDesignatedSurvivor =
|
if (dto.allowSurviving) {
|
||||||
await this.sharedVaultUserRepository.findDesignatedSurvivorBySharedVaultUuid(sharedVaultUuid)
|
const sharedVaultDesignatedSurvivor =
|
||||||
if (sharedVaultDesignatedSurvivor) {
|
await this.sharedVaultUserRepository.findDesignatedSurvivorBySharedVaultUuid(sharedVaultUuid)
|
||||||
const result = await this.transferSharedVault.execute({
|
if (sharedVaultDesignatedSurvivor) {
|
||||||
sharedVaultUid: sharedVaultUuid.value,
|
const result = await this.transferSharedVault.execute({
|
||||||
fromUserUuid: originatorUuid.value,
|
sharedVaultUid: sharedVaultUuid.value,
|
||||||
toUserUuid: sharedVaultDesignatedSurvivor.props.userUuid.value,
|
fromUserUuid: originatorUuid.value,
|
||||||
})
|
toUserUuid: sharedVaultDesignatedSurvivor.props.userUuid.value,
|
||||||
|
})
|
||||||
|
|
||||||
if (result.isFailed()) {
|
if (result.isFailed()) {
|
||||||
return Result.fail(result.getError())
|
return Result.fail(result.getError())
|
||||||
|
}
|
||||||
|
|
||||||
|
const removingOwnerFromSharedVaultResult = await this.removeUserFromSharedVault.execute({
|
||||||
|
originatorUuid: originatorUuid.value,
|
||||||
|
sharedVaultUuid: sharedVaultUuid.value,
|
||||||
|
userUuid: originatorUuid.value,
|
||||||
|
forceRemoveOwner: true,
|
||||||
|
})
|
||||||
|
if (removingOwnerFromSharedVaultResult.isFailed()) {
|
||||||
|
return Result.fail(removingOwnerFromSharedVaultResult.getError())
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok({ status: 'transitioned' })
|
||||||
}
|
}
|
||||||
|
|
||||||
const removingOwnerFromSharedVaultResult = await this.removeUserFromSharedVault.execute({
|
|
||||||
originatorUuid: originatorUuid.value,
|
|
||||||
sharedVaultUuid: sharedVaultUuid.value,
|
|
||||||
userUuid: originatorUuid.value,
|
|
||||||
forceRemoveOwner: true,
|
|
||||||
})
|
|
||||||
if (removingOwnerFromSharedVaultResult.isFailed()) {
|
|
||||||
return Result.fail(removingOwnerFromSharedVaultResult.getError())
|
|
||||||
}
|
|
||||||
|
|
||||||
return Result.ok()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const sharedVaultUsers = await this.sharedVaultUserRepository.findBySharedVaultUuid(sharedVaultUuid)
|
const sharedVaultUsers = await this.sharedVaultUserRepository.findBySharedVaultUuid(sharedVaultUuid)
|
||||||
@@ -105,6 +107,6 @@ export class DeleteSharedVault implements UseCaseInterface<void> {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
return Result.ok()
|
return Result.ok({ status: 'deleted' })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
@@ -1,4 +1,5 @@
|
|||||||
export interface DeleteSharedVaultDTO {
|
export interface DeleteSharedVaultDTO {
|
||||||
originatorUuid: string
|
originatorUuid: string
|
||||||
sharedVaultUuid: string
|
sharedVaultUuid: string
|
||||||
|
allowSurviving: boolean
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-1
@@ -24,7 +24,7 @@ describe('DeleteSharedVaults', () => {
|
|||||||
sharedVaultRepository.findByUserUuid = jest.fn().mockResolvedValue([sharedVault])
|
sharedVaultRepository.findByUserUuid = jest.fn().mockResolvedValue([sharedVault])
|
||||||
|
|
||||||
deleteSharedVaultUseCase = {} as jest.Mocked<DeleteSharedVault>
|
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 () => {
|
it('should delete all shared vaults for a user', async () => {
|
||||||
@@ -32,6 +32,7 @@ describe('DeleteSharedVaults', () => {
|
|||||||
|
|
||||||
const result = await useCase.execute({
|
const result = await useCase.execute({
|
||||||
ownerUuid: '00000000-0000-0000-0000-000000000000',
|
ownerUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
|
allowSurviving: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(result.isFailed()).toBe(false)
|
expect(result.isFailed()).toBe(false)
|
||||||
@@ -39,6 +40,7 @@ describe('DeleteSharedVaults', () => {
|
|||||||
expect(deleteSharedVaultUseCase.execute).toHaveBeenCalledWith({
|
expect(deleteSharedVaultUseCase.execute).toHaveBeenCalledWith({
|
||||||
originatorUuid: '00000000-0000-0000-0000-000000000000',
|
originatorUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
|
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
|
allowSurviving: true,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -48,6 +50,7 @@ describe('DeleteSharedVaults', () => {
|
|||||||
|
|
||||||
const result = await useCase.execute({
|
const result = await useCase.execute({
|
||||||
ownerUuid: '00000000-0000-0000-0000-000000000000',
|
ownerUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
|
allowSurviving: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(result.isFailed()).toBe(true)
|
expect(result.isFailed()).toBe(true)
|
||||||
@@ -55,6 +58,7 @@ describe('DeleteSharedVaults', () => {
|
|||||||
expect(deleteSharedVaultUseCase.execute).toHaveBeenCalledWith({
|
expect(deleteSharedVaultUseCase.execute).toHaveBeenCalledWith({
|
||||||
originatorUuid: '00000000-0000-0000-0000-000000000000',
|
originatorUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
|
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
|
allowSurviving: true,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -63,6 +67,7 @@ describe('DeleteSharedVaults', () => {
|
|||||||
|
|
||||||
const result = await useCase.execute({
|
const result = await useCase.execute({
|
||||||
ownerUuid: 'invalid',
|
ownerUuid: 'invalid',
|
||||||
|
allowSurviving: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(result.isFailed()).toBeTruthy()
|
expect(result.isFailed()).toBeTruthy()
|
||||||
|
|||||||
+7
-3
@@ -4,13 +4,13 @@ import { DeleteSharedVaultsDTO } from './DeleteSharedVaultsDTO'
|
|||||||
import { DeleteSharedVault } from '../DeleteSharedVault/DeleteSharedVault'
|
import { DeleteSharedVault } from '../DeleteSharedVault/DeleteSharedVault'
|
||||||
import { SharedVaultRepositoryInterface } from '../../../SharedVault/SharedVaultRepositoryInterface'
|
import { SharedVaultRepositoryInterface } from '../../../SharedVault/SharedVaultRepositoryInterface'
|
||||||
|
|
||||||
export class DeleteSharedVaults implements UseCaseInterface<void> {
|
export class DeleteSharedVaults implements UseCaseInterface<Map<Uuid, 'deleted' | 'transitioned'>> {
|
||||||
constructor(
|
constructor(
|
||||||
private sharedVaultRepository: SharedVaultRepositoryInterface,
|
private sharedVaultRepository: SharedVaultRepositoryInterface,
|
||||||
private deleteSharedVaultUseCase: DeleteSharedVault,
|
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)
|
const ownerUuidOrError = Uuid.create(dto.ownerUuid)
|
||||||
if (ownerUuidOrError.isFailed()) {
|
if (ownerUuidOrError.isFailed()) {
|
||||||
return Result.fail(ownerUuidOrError.getError())
|
return Result.fail(ownerUuidOrError.getError())
|
||||||
@@ -19,16 +19,20 @@ export class DeleteSharedVaults implements UseCaseInterface<void> {
|
|||||||
|
|
||||||
const sharedVaults = await this.sharedVaultRepository.findByUserUuid(ownerUuid)
|
const sharedVaults = await this.sharedVaultRepository.findByUserUuid(ownerUuid)
|
||||||
|
|
||||||
|
const results = new Map<Uuid, 'deleted' | 'transitioned'>()
|
||||||
for (const sharedVault of sharedVaults) {
|
for (const sharedVault of sharedVaults) {
|
||||||
const result = await this.deleteSharedVaultUseCase.execute({
|
const result = await this.deleteSharedVaultUseCase.execute({
|
||||||
originatorUuid: ownerUuid.value,
|
originatorUuid: ownerUuid.value,
|
||||||
sharedVaultUuid: sharedVault.id.toString(),
|
sharedVaultUuid: sharedVault.id.toString(),
|
||||||
|
allowSurviving: dto.allowSurviving,
|
||||||
})
|
})
|
||||||
if (result.isFailed()) {
|
if (result.isFailed()) {
|
||||||
return Result.fail(result.getError())
|
return Result.fail(result.getError())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
results.set(sharedVault.uuid, result.getValue().status)
|
||||||
}
|
}
|
||||||
|
|
||||||
return Result.ok()
|
return Result.ok(results)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
@@ -1,3 +1,4 @@
|
|||||||
export interface DeleteSharedVaultsDTO {
|
export interface DeleteSharedVaultsDTO {
|
||||||
ownerUuid: string
|
ownerUuid: string
|
||||||
|
allowSurviving: boolean
|
||||||
}
|
}
|
||||||
|
|||||||
+49
-1
@@ -1,4 +1,11 @@
|
|||||||
import { SharedVaultUser, SharedVaultUserPermission, Timestamps, Uuid } from '@standardnotes/domain-core'
|
import {
|
||||||
|
NotificationPayload,
|
||||||
|
Result,
|
||||||
|
SharedVaultUser,
|
||||||
|
SharedVaultUserPermission,
|
||||||
|
Timestamps,
|
||||||
|
Uuid,
|
||||||
|
} from '@standardnotes/domain-core'
|
||||||
|
|
||||||
import { SharedVaultUserRepositoryInterface } from '../../../SharedVault/User/SharedVaultUserRepositoryInterface'
|
import { SharedVaultUserRepositoryInterface } from '../../../SharedVault/User/SharedVaultUserRepositoryInterface'
|
||||||
import { DesignateSurvivor } from './DesignateSurvivor'
|
import { DesignateSurvivor } from './DesignateSurvivor'
|
||||||
@@ -7,6 +14,7 @@ import { DomainEventInterface, DomainEventPublisherInterface } from '@standardno
|
|||||||
import { DomainEventFactoryInterface } from '../../../Event/DomainEventFactoryInterface'
|
import { DomainEventFactoryInterface } from '../../../Event/DomainEventFactoryInterface'
|
||||||
import { SharedVault } from '../../../SharedVault/SharedVault'
|
import { SharedVault } from '../../../SharedVault/SharedVault'
|
||||||
import { SharedVaultRepositoryInterface } from '../../../SharedVault/SharedVaultRepositoryInterface'
|
import { SharedVaultRepositoryInterface } from '../../../SharedVault/SharedVaultRepositoryInterface'
|
||||||
|
import { AddNotificationForUser } from '../../Messaging/AddNotificationForUser/AddNotificationForUser'
|
||||||
|
|
||||||
describe('DesignateSurvivor', () => {
|
describe('DesignateSurvivor', () => {
|
||||||
let sharedVault: SharedVault
|
let sharedVault: SharedVault
|
||||||
@@ -17,6 +25,7 @@ describe('DesignateSurvivor', () => {
|
|||||||
let timer: TimerInterface
|
let timer: TimerInterface
|
||||||
let domainEventFactory: DomainEventFactoryInterface
|
let domainEventFactory: DomainEventFactoryInterface
|
||||||
let domainEventPublisher: DomainEventPublisherInterface
|
let domainEventPublisher: DomainEventPublisherInterface
|
||||||
|
let addNotificationForUser: AddNotificationForUser
|
||||||
|
|
||||||
const createUseCase = () =>
|
const createUseCase = () =>
|
||||||
new DesignateSurvivor(
|
new DesignateSurvivor(
|
||||||
@@ -25,6 +34,7 @@ describe('DesignateSurvivor', () => {
|
|||||||
timer,
|
timer,
|
||||||
domainEventFactory,
|
domainEventFactory,
|
||||||
domainEventPublisher,
|
domainEventPublisher,
|
||||||
|
addNotificationForUser,
|
||||||
)
|
)
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -68,6 +78,9 @@ describe('DesignateSurvivor', () => {
|
|||||||
|
|
||||||
domainEventPublisher = {} as jest.Mocked<DomainEventPublisherInterface>
|
domainEventPublisher = {} as jest.Mocked<DomainEventPublisherInterface>
|
||||||
domainEventPublisher.publish = jest.fn()
|
domainEventPublisher.publish = jest.fn()
|
||||||
|
|
||||||
|
addNotificationForUser = {} as jest.Mocked<AddNotificationForUser>
|
||||||
|
addNotificationForUser.execute = jest.fn().mockReturnValue(Result.ok())
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should fail if shared vault uuid is invalid', async () => {
|
it('should fail if shared vault uuid is invalid', async () => {
|
||||||
@@ -189,4 +202,39 @@ describe('DesignateSurvivor', () => {
|
|||||||
expect(sharedVaultUser.props.isDesignatedSurvivor).toBe(true)
|
expect(sharedVaultUser.props.isDesignatedSurvivor).toBe(true)
|
||||||
expect(sharedVaultUserRepository.save).toBeCalledTimes(2)
|
expect(sharedVaultUserRepository.save).toBeCalledTimes(2)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should fail if it fails to add notification for user', async () => {
|
||||||
|
sharedVaultUserRepository.findBySharedVaultUuid = jest.fn().mockReturnValue([sharedVaultOwner, sharedVaultUser])
|
||||||
|
|
||||||
|
addNotificationForUser.execute = jest.fn().mockReturnValue(Result.fail('Failed to add notification'))
|
||||||
|
|
||||||
|
const useCase = createUseCase()
|
||||||
|
|
||||||
|
const result = await useCase.execute({
|
||||||
|
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
|
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
|
originatorUuid: '00000000-0000-0000-0000-000000000002',
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result.isFailed()).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should fail if it fails to create notification payload', async () => {
|
||||||
|
sharedVaultUserRepository.findBySharedVaultUuid = jest.fn().mockReturnValue([sharedVaultOwner, sharedVaultUser])
|
||||||
|
|
||||||
|
const mock = jest.spyOn(NotificationPayload, 'create')
|
||||||
|
mock.mockReturnValue(Result.fail('Oops'))
|
||||||
|
|
||||||
|
const useCase = createUseCase()
|
||||||
|
|
||||||
|
const result = await useCase.execute({
|
||||||
|
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
|
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||||
|
originatorUuid: '00000000-0000-0000-0000-000000000002',
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result.isFailed()).toBe(true)
|
||||||
|
|
||||||
|
mock.mockRestore()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
+37
-5
@@ -1,6 +1,9 @@
|
|||||||
import { TimerInterface } from '@standardnotes/time'
|
import { TimerInterface } from '@standardnotes/time'
|
||||||
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
|
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
|
||||||
import {
|
import {
|
||||||
|
NotificationPayload,
|
||||||
|
NotificationPayloadIdentifierType,
|
||||||
|
NotificationType,
|
||||||
Result,
|
Result,
|
||||||
SharedVaultUser,
|
SharedVaultUser,
|
||||||
SharedVaultUserPermission,
|
SharedVaultUserPermission,
|
||||||
@@ -13,6 +16,7 @@ import { SharedVaultUserRepositoryInterface } from '../../../SharedVault/User/Sh
|
|||||||
import { DesignateSurvivorDTO } from './DesignateSurvivorDTO'
|
import { DesignateSurvivorDTO } from './DesignateSurvivorDTO'
|
||||||
import { DomainEventFactoryInterface } from '../../../Event/DomainEventFactoryInterface'
|
import { DomainEventFactoryInterface } from '../../../Event/DomainEventFactoryInterface'
|
||||||
import { SharedVaultRepositoryInterface } from '../../../SharedVault/SharedVaultRepositoryInterface'
|
import { SharedVaultRepositoryInterface } from '../../../SharedVault/SharedVaultRepositoryInterface'
|
||||||
|
import { AddNotificationForUser } from '../../Messaging/AddNotificationForUser/AddNotificationForUser'
|
||||||
|
|
||||||
export class DesignateSurvivor implements UseCaseInterface<void> {
|
export class DesignateSurvivor implements UseCaseInterface<void> {
|
||||||
constructor(
|
constructor(
|
||||||
@@ -21,6 +25,7 @@ export class DesignateSurvivor implements UseCaseInterface<void> {
|
|||||||
private timer: TimerInterface,
|
private timer: TimerInterface,
|
||||||
private domainEventFactory: DomainEventFactoryInterface,
|
private domainEventFactory: DomainEventFactoryInterface,
|
||||||
private domainEventPublisher: DomainEventPublisherInterface,
|
private domainEventPublisher: DomainEventPublisherInterface,
|
||||||
|
private addNotificationForUser: AddNotificationForUser,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async execute(dto: DesignateSurvivorDTO): Promise<Result<void>> {
|
async execute(dto: DesignateSurvivorDTO): Promise<Result<void>> {
|
||||||
@@ -91,6 +96,13 @@ export class DesignateSurvivor implements UseCaseInterface<void> {
|
|||||||
|
|
||||||
await this.sharedVaultUserRepository.save(toBeDesignatedAsASurvivor)
|
await this.sharedVaultUserRepository.save(toBeDesignatedAsASurvivor)
|
||||||
|
|
||||||
|
sharedVault.props.timestamps = Timestamps.create(
|
||||||
|
sharedVault.props.timestamps.createdAt,
|
||||||
|
this.timer.getTimestampInMicroseconds(),
|
||||||
|
).getValue()
|
||||||
|
|
||||||
|
await this.sharedVaultRepository.save(sharedVault)
|
||||||
|
|
||||||
await this.domainEventPublisher.publish(
|
await this.domainEventPublisher.publish(
|
||||||
this.domainEventFactory.createUserDesignatedAsSurvivorInSharedVaultEvent({
|
this.domainEventFactory.createUserDesignatedAsSurvivorInSharedVaultEvent({
|
||||||
sharedVaultUuid: sharedVaultUuid.value,
|
sharedVaultUuid: sharedVaultUuid.value,
|
||||||
@@ -99,12 +111,32 @@ export class DesignateSurvivor implements UseCaseInterface<void> {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
sharedVault.props.timestamps = Timestamps.create(
|
const notificationPayloadOrError = NotificationPayload.create({
|
||||||
sharedVault.props.timestamps.createdAt,
|
primaryIdentifier: sharedVault.uuid,
|
||||||
this.timer.getTimestampInMicroseconds(),
|
primaryIndentifierType: NotificationPayloadIdentifierType.create(
|
||||||
).getValue()
|
NotificationPayloadIdentifierType.TYPES.SharedVaultUuid,
|
||||||
|
).getValue(),
|
||||||
|
secondaryIdentifier: userUuid,
|
||||||
|
secondaryIdentifierType: NotificationPayloadIdentifierType.create(
|
||||||
|
NotificationPayloadIdentifierType.TYPES.UserUuid,
|
||||||
|
).getValue(),
|
||||||
|
type: NotificationType.create(NotificationType.TYPES.UserDesignatedAsSurvivor).getValue(),
|
||||||
|
version: '1.0',
|
||||||
|
})
|
||||||
|
if (notificationPayloadOrError.isFailed()) {
|
||||||
|
return Result.fail(notificationPayloadOrError.getError())
|
||||||
|
}
|
||||||
|
const notificationPayload = notificationPayloadOrError.getValue()
|
||||||
|
|
||||||
await this.sharedVaultRepository.save(sharedVault)
|
const result = await this.addNotificationForUser.execute({
|
||||||
|
userUuid: sharedVault.props.userUuid.value,
|
||||||
|
type: NotificationType.TYPES.UserDesignatedAsSurvivor,
|
||||||
|
payload: notificationPayload,
|
||||||
|
version: '1.0',
|
||||||
|
})
|
||||||
|
if (result.isFailed()) {
|
||||||
|
return Result.fail(result.getError())
|
||||||
|
}
|
||||||
|
|
||||||
return Result.ok()
|
return Result.ok()
|
||||||
}
|
}
|
||||||
|
|||||||
+36
-25
@@ -43,7 +43,6 @@ export class TransitionItemsFromPrimaryToSecondaryDatabaseForUser implements Use
|
|||||||
if (migrationResult.isFailed()) {
|
if (migrationResult.isFailed()) {
|
||||||
return Result.fail(migrationResult.getError())
|
return Result.fail(migrationResult.getError())
|
||||||
}
|
}
|
||||||
const itemsToSkipInIntegrityCheck = migrationResult.getValue()
|
|
||||||
|
|
||||||
this.logger.info(`[${dto.userUuid}] Items migrated`)
|
this.logger.info(`[${dto.userUuid}] Items migrated`)
|
||||||
|
|
||||||
@@ -51,11 +50,11 @@ export class TransitionItemsFromPrimaryToSecondaryDatabaseForUser implements Use
|
|||||||
|
|
||||||
this.logger.info(`[${dto.userUuid}] Checking integrity between primary and secondary database`)
|
this.logger.info(`[${dto.userUuid}] Checking integrity between primary and secondary database`)
|
||||||
|
|
||||||
const integrityCheckResult = await this.checkIntegrityBetweenPrimaryAndSecondaryDatabase(
|
const integrityCheckResult = await this.checkIntegrityBetweenPrimaryAndSecondaryDatabase(userUuid)
|
||||||
userUuid,
|
|
||||||
itemsToSkipInIntegrityCheck,
|
|
||||||
)
|
|
||||||
if (integrityCheckResult.isFailed()) {
|
if (integrityCheckResult.isFailed()) {
|
||||||
|
await (this.transitionStatusRepository as TransitionRepositoryInterface).setPagingProgress(userUuid.value, 1)
|
||||||
|
await (this.transitionStatusRepository as TransitionRepositoryInterface).setIntegrityProgress(userUuid.value, 1)
|
||||||
|
|
||||||
return Result.fail(integrityCheckResult.getError())
|
return Result.fail(integrityCheckResult.getError())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,7 +80,7 @@ export class TransitionItemsFromPrimaryToSecondaryDatabaseForUser implements Use
|
|||||||
await this.timer.sleep(twoSecondsInMilliseconds)
|
await this.timer.sleep(twoSecondsInMilliseconds)
|
||||||
}
|
}
|
||||||
|
|
||||||
private async migrateItemsForUser(userUuid: Uuid): Promise<Result<string[]>> {
|
private async migrateItemsForUser(userUuid: Uuid): Promise<Result<void>> {
|
||||||
try {
|
try {
|
||||||
const initialPage = await (this.transitionStatusRepository as TransitionRepositoryInterface).getPagingProgress(
|
const initialPage = await (this.transitionStatusRepository as TransitionRepositoryInterface).getPagingProgress(
|
||||||
userUuid.value,
|
userUuid.value,
|
||||||
@@ -91,7 +90,6 @@ export class TransitionItemsFromPrimaryToSecondaryDatabaseForUser implements Use
|
|||||||
|
|
||||||
const totalItemsCountForUser = await this.primaryItemRepository.countAll({ userUuid: userUuid.value })
|
const totalItemsCountForUser = await this.primaryItemRepository.countAll({ userUuid: userUuid.value })
|
||||||
const totalPages = Math.ceil(totalItemsCountForUser / this.pageSize)
|
const totalPages = Math.ceil(totalItemsCountForUser / this.pageSize)
|
||||||
const itemsToSkipInIntegrityCheck = []
|
|
||||||
for (let currentPage = initialPage; currentPage <= totalPages; currentPage++) {
|
for (let currentPage = initialPage; currentPage <= totalPages; currentPage++) {
|
||||||
await (this.transitionStatusRepository as TransitionRepositoryInterface).setPagingProgress(
|
await (this.transitionStatusRepository as TransitionRepositoryInterface).setPagingProgress(
|
||||||
userUuid.value,
|
userUuid.value,
|
||||||
@@ -102,7 +100,7 @@ export class TransitionItemsFromPrimaryToSecondaryDatabaseForUser implements Use
|
|||||||
userUuid: userUuid.value,
|
userUuid: userUuid.value,
|
||||||
offset: (currentPage - 1) * this.pageSize,
|
offset: (currentPage - 1) * this.pageSize,
|
||||||
limit: this.pageSize,
|
limit: this.pageSize,
|
||||||
sortBy: 'uuid',
|
sortBy: 'created_at_timestamp',
|
||||||
sortOrder: 'ASC',
|
sortOrder: 'ASC',
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,7 +118,6 @@ export class TransitionItemsFromPrimaryToSecondaryDatabaseForUser implements Use
|
|||||||
}
|
}
|
||||||
if (itemInSecondary.props.timestamps.updatedAt > item.props.timestamps.updatedAt) {
|
if (itemInSecondary.props.timestamps.updatedAt > item.props.timestamps.updatedAt) {
|
||||||
this.logger.info(`[${userUuid.value}] Item ${item.uuid.value} is older than item in secondary database`)
|
this.logger.info(`[${userUuid.value}] Item ${item.uuid.value} is older than item in secondary database`)
|
||||||
itemsToSkipInIntegrityCheck.push(item.uuid.value)
|
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -143,7 +140,7 @@ export class TransitionItemsFromPrimaryToSecondaryDatabaseForUser implements Use
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Result.ok(itemsToSkipInIntegrityCheck)
|
return Result.ok()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return Result.fail((error as Error).message)
|
return Result.fail((error as Error).message)
|
||||||
}
|
}
|
||||||
@@ -153,7 +150,7 @@ export class TransitionItemsFromPrimaryToSecondaryDatabaseForUser implements Use
|
|||||||
try {
|
try {
|
||||||
this.logger.info(`[${userUuid.value}] Cleaning up primary database items`)
|
this.logger.info(`[${userUuid.value}] Cleaning up primary database items`)
|
||||||
|
|
||||||
await itemRepository.deleteByUserUuid(userUuid.value)
|
await itemRepository.deleteByUserUuidAndNotInSharedVault(userUuid)
|
||||||
|
|
||||||
return Result.ok()
|
return Result.ok()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -161,11 +158,14 @@ export class TransitionItemsFromPrimaryToSecondaryDatabaseForUser implements Use
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async checkIntegrityBetweenPrimaryAndSecondaryDatabase(
|
private async checkIntegrityBetweenPrimaryAndSecondaryDatabase(userUuid: Uuid): Promise<Result<boolean>> {
|
||||||
userUuid: Uuid,
|
|
||||||
itemsToSkipInIntegrityCheck: string[],
|
|
||||||
): Promise<Result<boolean>> {
|
|
||||||
try {
|
try {
|
||||||
|
const initialPage = await (this.transitionStatusRepository as TransitionRepositoryInterface).getIntegrityProgress(
|
||||||
|
userUuid.value,
|
||||||
|
)
|
||||||
|
|
||||||
|
this.logger.info(`[${userUuid.value}] Checking integrity from page ${initialPage}`)
|
||||||
|
|
||||||
const totalItemsCountForUserInPrimary = await this.primaryItemRepository.countAll({ userUuid: userUuid.value })
|
const totalItemsCountForUserInPrimary = await this.primaryItemRepository.countAll({ userUuid: userUuid.value })
|
||||||
const totalItemsCountForUserInSecondary = await (
|
const totalItemsCountForUserInSecondary = await (
|
||||||
this.secondaryItemRepository as ItemRepositoryInterface
|
this.secondaryItemRepository as ItemRepositoryInterface
|
||||||
@@ -180,12 +180,17 @@ export class TransitionItemsFromPrimaryToSecondaryDatabaseForUser implements Use
|
|||||||
}
|
}
|
||||||
|
|
||||||
const totalPages = Math.ceil(totalItemsCountForUserInPrimary / this.pageSize)
|
const totalPages = Math.ceil(totalItemsCountForUserInPrimary / this.pageSize)
|
||||||
for (let currentPage = 1; currentPage <= totalPages; currentPage++) {
|
for (let currentPage = initialPage; currentPage <= totalPages; currentPage++) {
|
||||||
|
await (this.transitionStatusRepository as TransitionRepositoryInterface).setIntegrityProgress(
|
||||||
|
userUuid.value,
|
||||||
|
currentPage,
|
||||||
|
)
|
||||||
|
|
||||||
const query: ItemQuery = {
|
const query: ItemQuery = {
|
||||||
userUuid: userUuid.value,
|
userUuid: userUuid.value,
|
||||||
offset: (currentPage - 1) * this.pageSize,
|
offset: (currentPage - 1) * this.pageSize,
|
||||||
limit: this.pageSize,
|
limit: this.pageSize,
|
||||||
sortBy: 'uuid',
|
sortBy: 'created_at_timestamp',
|
||||||
sortOrder: 'ASC',
|
sortOrder: 'ASC',
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,19 +202,25 @@ export class TransitionItemsFromPrimaryToSecondaryDatabaseForUser implements Use
|
|||||||
return Result.fail(`Item ${item.uuid.value} not found in secondary database`)
|
return Result.fail(`Item ${item.uuid.value} not found in secondary database`)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (itemsToSkipInIntegrityCheck.includes(item.id.toString())) {
|
if (item.isIdenticalTo(itemInSecondary)) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!item.isIdenticalTo(itemInSecondary)) {
|
if (itemInSecondary.props.timestamps.updatedAt > item.props.timestamps.updatedAt) {
|
||||||
return Result.fail(
|
this.logger.info(
|
||||||
`Item ${
|
`[${userUuid.value}] Integrity check of Item ${item.uuid.value} - is older than item in secondary database`,
|
||||||
item.uuid.value
|
|
||||||
} is not identical in primary and secondary database. Item in primary database: ${JSON.stringify(
|
|
||||||
item,
|
|
||||||
)}, item in secondary database: ${JSON.stringify(itemInSecondary)}`,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return Result.fail(
|
||||||
|
`Item ${
|
||||||
|
item.uuid.value
|
||||||
|
} is not identical in primary and secondary database. Item in primary database: ${JSON.stringify(
|
||||||
|
item,
|
||||||
|
)}, item in secondary database: ${JSON.stringify(itemInSecondary)}`,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
@@ -92,6 +92,7 @@ export class BaseSharedVaultsController extends BaseHttpController {
|
|||||||
const result = await this.deleteSharedVaultUseCase.execute({
|
const result = await this.deleteSharedVaultUseCase.execute({
|
||||||
sharedVaultUuid: request.params.sharedVaultUuid,
|
sharedVaultUuid: request.params.sharedVaultUuid,
|
||||||
originatorUuid: response.locals.user.uuid,
|
originatorUuid: response.locals.user.uuid,
|
||||||
|
allowSurviving: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (result.isFailed()) {
|
if (result.isFailed()) {
|
||||||
|
|||||||
@@ -3,10 +3,25 @@ import * as IORedis from 'ioredis'
|
|||||||
import { TransitionRepositoryInterface } from '../../Domain/Transition/TransitionRepositoryInterface'
|
import { TransitionRepositoryInterface } from '../../Domain/Transition/TransitionRepositoryInterface'
|
||||||
|
|
||||||
export class RedisTransitionRepository implements TransitionRepositoryInterface {
|
export class RedisTransitionRepository implements TransitionRepositoryInterface {
|
||||||
private readonly PREFIX = 'transition-items-paging-progress'
|
private readonly PREFIX = 'transition-items-migration-progress'
|
||||||
|
private readonly INTEGRITY_PREFIX = 'transition-items-integrity-progress'
|
||||||
|
|
||||||
constructor(private redisClient: IORedis.Redis) {}
|
constructor(private redisClient: IORedis.Redis) {}
|
||||||
|
|
||||||
|
async getIntegrityProgress(userUuid: string): Promise<number> {
|
||||||
|
const progress = await this.redisClient.get(`${this.INTEGRITY_PREFIX}:${userUuid}`)
|
||||||
|
|
||||||
|
if (progress === null) {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return parseInt(progress)
|
||||||
|
}
|
||||||
|
|
||||||
|
async setIntegrityProgress(userUuid: string, progress: number): Promise<void> {
|
||||||
|
await this.redisClient.setex(`${this.INTEGRITY_PREFIX}:${userUuid}`, 172_800, progress.toString())
|
||||||
|
}
|
||||||
|
|
||||||
async getPagingProgress(userUuid: string): Promise<number> {
|
async getPagingProgress(userUuid: string): Promise<number> {
|
||||||
const progress = await this.redisClient.get(`${this.PREFIX}:${userUuid}`)
|
const progress = await this.redisClient.get(`${this.PREFIX}:${userUuid}`)
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,15 @@ export class MongoDBItemRepository implements ItemRepositoryInterface {
|
|||||||
private logger: Logger,
|
private logger: Logger,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
async deleteByUserUuidInSharedVaults(userUuid: Uuid, sharedVaultUuids: Uuid[]): Promise<void> {
|
||||||
|
await this.mongoRepository.deleteMany({
|
||||||
|
$and: [
|
||||||
|
{ userUuid: { $eq: userUuid.value } },
|
||||||
|
{ sharedVaultUuid: { $in: sharedVaultUuids.map((uuid) => uuid.value) } },
|
||||||
|
],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
async updateSharedVaultOwner(dto: { sharedVaultUuid: Uuid; fromOwnerUuid: Uuid; toOwnerUuid: Uuid }): Promise<void> {
|
async updateSharedVaultOwner(dto: { sharedVaultUuid: Uuid; fromOwnerUuid: Uuid; toOwnerUuid: Uuid }): Promise<void> {
|
||||||
await this.mongoRepository.updateMany(
|
await this.mongoRepository.updateMany(
|
||||||
{
|
{
|
||||||
@@ -37,8 +46,10 @@ export class MongoDBItemRepository implements ItemRepositoryInterface {
|
|||||||
await this.mongoRepository.deleteOne({ _id: { $eq: BSON.UUID.createFromHexString(uuid.value) } })
|
await this.mongoRepository.deleteOne({ _id: { $eq: BSON.UUID.createFromHexString(uuid.value) } })
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteByUserUuid(userUuid: string): Promise<void> {
|
async deleteByUserUuidAndNotInSharedVault(userUuid: Uuid): Promise<void> {
|
||||||
await this.mongoRepository.deleteMany({ userUuid })
|
await this.mongoRepository.deleteMany({
|
||||||
|
$and: [{ userUuid: { $eq: userUuid.value } }, { sharedVaultUuid: { $eq: null } }],
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async findAll(query: ItemQuery): Promise<Item[]> {
|
async findAll(query: ItemQuery): Promise<Item[]> {
|
||||||
|
|||||||
@@ -16,6 +16,28 @@ export class SQLItemRepository extends SQLLegacyItemRepository {
|
|||||||
super(ormRepository, mapper, logger)
|
super(ormRepository, mapper, logger)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override async deleteByUserUuidInSharedVaults(userUuid: Uuid, sharedVaultUuids: Uuid[]): Promise<void> {
|
||||||
|
await this.ormRepository
|
||||||
|
.createQueryBuilder('item')
|
||||||
|
.delete()
|
||||||
|
.from('items')
|
||||||
|
.where('user_uuid = :userUuid', { userUuid: userUuid.value })
|
||||||
|
.andWhere('shared_vault_uuid IN (:...sharedVaultUuids)', {
|
||||||
|
sharedVaultUuids: sharedVaultUuids.map((uuid) => uuid.value),
|
||||||
|
})
|
||||||
|
.execute()
|
||||||
|
}
|
||||||
|
|
||||||
|
override async deleteByUserUuidAndNotInSharedVault(userUuid: Uuid): Promise<void> {
|
||||||
|
await this.ormRepository
|
||||||
|
.createQueryBuilder('item')
|
||||||
|
.delete()
|
||||||
|
.from('items')
|
||||||
|
.where('user_uuid = :userUuid', { userUuid: userUuid.value })
|
||||||
|
.andWhere('shared_vault_uuid IS NULL')
|
||||||
|
.execute()
|
||||||
|
}
|
||||||
|
|
||||||
override async updateSharedVaultOwner(dto: {
|
override async updateSharedVaultOwner(dto: {
|
||||||
sharedVaultUuid: Uuid
|
sharedVaultUuid: Uuid
|
||||||
fromOwnerUuid: Uuid
|
fromOwnerUuid: Uuid
|
||||||
|
|||||||
@@ -16,6 +16,10 @@ export class SQLLegacyItemRepository implements ItemRepositoryInterface {
|
|||||||
protected logger: Logger,
|
protected logger: Logger,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
async deleteByUserUuidInSharedVaults(_userUuid: Uuid, _sharedVaultUuids: Uuid[]): Promise<void> {
|
||||||
|
this.logger.error('Method deleteByUserUuidInSharedVaults not supported.')
|
||||||
|
}
|
||||||
|
|
||||||
async updateSharedVaultOwner(_dto: { sharedVaultUuid: Uuid; fromOwnerUuid: Uuid; toOwnerUuid: Uuid }): Promise<void> {
|
async updateSharedVaultOwner(_dto: { sharedVaultUuid: Uuid; fromOwnerUuid: Uuid; toOwnerUuid: Uuid }): Promise<void> {
|
||||||
this.logger.error('Method updateSharedVaultOwner not supported.')
|
this.logger.error('Method updateSharedVaultOwner not supported.')
|
||||||
}
|
}
|
||||||
@@ -80,12 +84,12 @@ export class SQLLegacyItemRepository implements ItemRepositoryInterface {
|
|||||||
return itemContentSizeDescriptors
|
return itemContentSizeDescriptors
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteByUserUuid(userUuid: string): Promise<void> {
|
async deleteByUserUuidAndNotInSharedVault(userUuid: Uuid): Promise<void> {
|
||||||
await this.ormRepository
|
await this.ormRepository
|
||||||
.createQueryBuilder('item')
|
.createQueryBuilder('item')
|
||||||
.delete()
|
.delete()
|
||||||
.from('items')
|
.from('items')
|
||||||
.where('user_uuid = :userUuid', { userUuid })
|
.where('user_uuid = :userUuid', { userUuid: userUuid.value })
|
||||||
.execute()
|
.execute()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,12 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
# [1.17.0](https://github.com/standardnotes/server/compare/@standardnotes/time@1.16.0...@standardnotes/time@1.17.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.16.0](https://github.com/standardnotes/server/compare/@standardnotes/time@1.15.3...@standardnotes/time@1.16.0) (2023-09-26)
|
# [1.16.0](https://github.com/standardnotes/server/compare/@standardnotes/time@1.15.3...@standardnotes/time@1.16.0) (2023-09-26)
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/time",
|
"name": "@standardnotes/time",
|
||||||
"version": "1.16.0",
|
"version": "1.17.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0 <21.0.0"
|
"node": ">=18.0.0 <21.0.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -76,6 +76,11 @@ describe('Timer', () => {
|
|||||||
expect(isoString).toEqual('2021-03-29T08:00:05.000Z')
|
expect(isoString).toEqual('2021-03-29T08:00:05.000Z')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should convert a date to formatted string', () => {
|
||||||
|
const isoString = createTimer().convertDateToFormattedString(new Date(Date.UTC(2021, 2, 29, 8, 0, 5)), 'YYYY-MM-DD')
|
||||||
|
expect(isoString).toEqual('2021-03-29')
|
||||||
|
})
|
||||||
|
|
||||||
it('should convert a string date to microseconds', () => {
|
it('should convert a string date to microseconds', () => {
|
||||||
const timestamp = createTimer().convertStringDateToMicroseconds('2021-03-29 08:00:05.233Z')
|
const timestamp = createTimer().convertStringDateToMicroseconds('2021-03-29 08:00:05.233Z')
|
||||||
expect(timestamp).toEqual(1617004805233000)
|
expect(timestamp).toEqual(1617004805233000)
|
||||||
|
|||||||
@@ -95,6 +95,10 @@ export class Timer implements TimerInterface {
|
|||||||
return dayjs.utc(date).toISOString()
|
return dayjs.utc(date).toISOString()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
convertDateToFormattedString(date: Date, format: string): string {
|
||||||
|
return dayjs.utc(date).format(format)
|
||||||
|
}
|
||||||
|
|
||||||
dateWasNDaysAgo(date: Date): number {
|
dateWasNDaysAgo(date: Date): number {
|
||||||
return dayjs.utc().diff(date, 'days')
|
return dayjs.utc().diff(date, 'days')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ export interface TimerInterface {
|
|||||||
convertDateToMilliseconds(date: Date): number
|
convertDateToMilliseconds(date: Date): number
|
||||||
convertDateToMicroseconds(date: Date): number
|
convertDateToMicroseconds(date: Date): number
|
||||||
convertDateToISOString(date: Date): string
|
convertDateToISOString(date: Date): string
|
||||||
|
convertDateToFormattedString(date: Date, format: string): string
|
||||||
convertStringDateToDate(date: string): Date
|
convertStringDateToDate(date: string): Date
|
||||||
convertStringDateToMicroseconds(date: string): number
|
convertStringDateToMicroseconds(date: string): number
|
||||||
convertStringDateToMilliseconds(date: string): number
|
convertStringDateToMilliseconds(date: string): number
|
||||||
|
|||||||
@@ -3,6 +3,14 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [1.11.2](https://github.com/standardnotes/server/compare/@standardnotes/websockets-server@1.11.1...@standardnotes/websockets-server@1.11.2) (2023-09-28)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/websockets-server
|
||||||
|
|
||||||
|
## [1.11.1](https://github.com/standardnotes/server/compare/@standardnotes/websockets-server@1.11.0...@standardnotes/websockets-server@1.11.1) (2023-09-27)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @standardnotes/websockets-server
|
||||||
|
|
||||||
# [1.11.0](https://github.com/standardnotes/server/compare/@standardnotes/websockets-server@1.10.53...@standardnotes/websockets-server@1.11.0) (2023-09-26)
|
# [1.11.0](https://github.com/standardnotes/server/compare/@standardnotes/websockets-server@1.10.53...@standardnotes/websockets-server@1.11.0) (2023-09-26)
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@standardnotes/websockets-server",
|
"name": "@standardnotes/websockets-server",
|
||||||
"version": "1.11.0",
|
"version": "1.11.2",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0 <21.0.0"
|
"node": ">=18.0.0 <21.0.0"
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user