Compare commits

..

15 Commits

Author SHA1 Message Date
standardci
2606f6d929 chore(release): publish new version
- @standardnotes/analytics@2.24.8
 - @standardnotes/api-gateway@1.65.5
 - @standardnotes/auth-server@1.122.2
 - @standardnotes/domain-core@1.21.1
 - @standardnotes/domain-events-infra@1.12.9
 - @standardnotes/domain-events@2.113.1
 - @standardnotes/event-store@1.11.5
 - @standardnotes/files-server@1.19.5
 - @standardnotes/home-server@1.11.32
 - @standardnotes/revisions-server@1.23.9
 - @standardnotes/scheduler-server@1.20.7
 - @standardnotes/settings@1.21.12
 - @standardnotes/syncing-server@1.58.1
 - @standardnotes/websockets-server@1.9.8
2023-07-07 13:30:56 +00:00
Karol Sójko
c288e5d8dc fix: transfer notifications from auth to syncing-server. (#648)
* fix: transfer notifications from auth to syncing-server.

Co-authored-by: Mo <mo@standardnotes.com>

* fix: add notification to data source init

---------

Co-authored-by: Mo <mo@standardnotes.com>
2023-07-07 15:12:27 +02:00
standardci
4b76d4b71e chore(release): publish new version
- @standardnotes/home-server@1.11.31
 - @standardnotes/syncing-server@1.58.0
2023-07-07 11:53:44 +00:00
Karol Sójko
72310130d2 feat: shared vault invites controller and use cases (#647)
* feat: get shared vault invites sent by user.

Co-authored-by: Mo <mo@standardnotes.com>

* feat: shared vault invites controller.

Co-authored-by: Mo <mo@standardnotes.com>

---------

Co-authored-by: Mo <mo@standardnotes.com>
2023-07-07 13:39:43 +02:00
standardci
f9e51ef06e chore(release): publish new version
- @standardnotes/home-server@1.11.30
 - @standardnotes/syncing-server@1.57.0
2023-07-06 12:09:05 +00:00
Karol Sójko
92a5eb0d98 feat: remove inbound shared vault invites. (#646)
Co-authored-by: Mo <mo@standardnotes.com>
2023-07-06 13:53:52 +02:00
standardci
77d2ea1a1f chore(release): publish new version
- @standardnotes/home-server@1.11.29
 - @standardnotes/syncing-server@1.56.0
2023-07-06 11:06:03 +00:00
Karol Sójko
92f96ddb84 feat: accept and decline shared vault invites (#645)
* feat: accept shared vault invite.

Co-authored-by: Mo <mo@standardnotes.com>

* feat: decline shared vault invite.

Co-authored-by: Mo <mo@standardnotes.com>

---------

Co-authored-by: Mo <mo@standardnotes.com>
2023-07-06 12:47:48 +02:00
standardci
15a914e25e chore(release): publish new version
- @standardnotes/home-server@1.11.28
 - @standardnotes/syncing-server@1.55.0
2023-07-06 10:12:49 +00:00
Karol Sójko
912a29d091 feat: update shared vault invite. (#644)
Co-authored-by: Mo <mo@standardnotes.com>
2023-07-06 11:58:45 +02:00
Karol Sójko
b2c32ce70e feat: shared vault users controller. (#643)
Co-authored-by: Mo <mo@standardnotes.com>
2023-07-06 11:41:36 +02:00
standardci
ed1a708c40 chore(release): publish new version
- @standardnotes/analytics@2.24.7
 - @standardnotes/api-gateway@1.65.4
 - @standardnotes/auth-server@1.122.1
 - @standardnotes/domain-core@1.21.0
 - @standardnotes/event-store@1.11.4
 - @standardnotes/files-server@1.19.4
 - @standardnotes/home-server@1.11.27
 - @standardnotes/revisions-server@1.23.8
 - @standardnotes/scheduler-server@1.20.6
 - @standardnotes/settings@1.21.11
 - @standardnotes/syncing-server@1.54.0
 - @standardnotes/websockets-server@1.9.7
2023-07-06 09:34:50 +00:00
Karol Sójko
e905128d45 feat: getting shared vault users and removing shared vault user (#642)
* feat: getting shared vault users.

Co-authored-by: Mo <mo@standardnotes.com>

* feat: removing shared vault user.

Co-authored-by: Mo <mo@standardnotes.com>

---------

Co-authored-by: Mo <mo@standardnotes.com>
2023-07-06 11:18:06 +02:00
standardci
fd598f372a chore(release): publish new version
- @standardnotes/home-server@1.11.26
 - @standardnotes/syncing-server@1.53.0
2023-07-05 13:33:44 +00:00
Karol Sójko
7a3946a9e2 feat: http controllers for shared vaults. (#641)
Co-authored-by: Mo <mo@standardnotes.com>
2023-07-05 15:17:43 +02:00
105 changed files with 2732 additions and 119 deletions

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [2.24.8](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.24.7...@standardnotes/analytics@2.24.8) (2023-07-07)
**Note:** Version bump only for package @standardnotes/analytics
## [2.24.7](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.24.6...@standardnotes/analytics@2.24.7) (2023-07-06)
**Note:** Version bump only for package @standardnotes/analytics
## [2.24.6](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.24.5...@standardnotes/analytics@2.24.6) (2023-07-05)
**Note:** Version bump only for package @standardnotes/analytics

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/analytics",
"version": "2.24.6",
"version": "2.24.8",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.65.5](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.65.4...@standardnotes/api-gateway@1.65.5) (2023-07-07)
**Note:** Version bump only for package @standardnotes/api-gateway
## [1.65.4](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.65.3...@standardnotes/api-gateway@1.65.4) (2023-07-06)
**Note:** Version bump only for package @standardnotes/api-gateway
## [1.65.3](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.65.2...@standardnotes/api-gateway@1.65.3) (2023-07-05)
**Note:** Version bump only for package @standardnotes/api-gateway

View File

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

View File

@@ -3,6 +3,16 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.122.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.122.1...@standardnotes/auth-server@1.122.2) (2023-07-07)
### Bug Fixes
* transfer notifications from auth to syncing-server. ([#648](https://github.com/standardnotes/server/issues/648)) ([c288e5d](https://github.com/standardnotes/server/commit/c288e5d8dc54778a96a9fc33e3c9cae00583fade))
## [1.122.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.122.0...@standardnotes/auth-server@1.122.1) (2023-07-06)
**Note:** Version bump only for package @standardnotes/auth-server
# [1.122.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.121.0...@standardnotes/auth-server@1.122.0) (2023-07-05)
### Features

View File

@@ -0,0 +1,16 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class RemoveNotifications1688540448428 implements MigrationInterface {
name = 'RemoveNotifications1688540448428'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('DROP INDEX `index_notifications_on_user_uuid` ON `notifications`')
await queryRunner.query('DROP TABLE `notifications`')
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
'CREATE TABLE `notifications` (`uuid` varchar(36) NOT NULL, `user_uuid` varchar(36) NOT NULL, `type` varchar(36) NOT NULL, `payload` text NOT NULL, `created_at_timestamp` bigint NOT NULL, `updated_at_timestamp` bigint NOT NULL, INDEX `index_notifications_on_user_uuid` (`user_uuid`), PRIMARY KEY (`uuid`)) ENGINE=InnoDB',
)
}
}

View File

@@ -0,0 +1,17 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class RemoveNotifications1688540623273 implements MigrationInterface {
name = 'RemoveNotifications1688540623273'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('DROP INDEX "index_notifications_on_user_uuid"')
await queryRunner.query('DROP TABLE "notifications"')
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
'CREATE TABLE "notifications" ("uuid" varchar PRIMARY KEY NOT NULL, "user_uuid" varchar(36) NOT NULL, "type" varchar(36) NOT NULL, "payload" text NOT NULL, "created_at_timestamp" bigint NOT NULL, "updated_at_timestamp" bigint NOT NULL)',
)
await queryRunner.query('CREATE INDEX "index_notifications_on_user_uuid" ON "notifications" ("user_uuid") ')
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/auth-server",
"version": "1.122.0",
"version": "1.122.2",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -18,7 +18,6 @@ import { TypeORMEmergencyAccessInvitation } from '../Infra/TypeORM/TypeORMEmerge
import { TypeORMSessionTrace } from '../Infra/TypeORM/TypeORMSessionTrace'
import { Env } from './Env'
import { SqliteConnectionOptions } from 'typeorm/driver/sqlite/SqliteConnectionOptions'
import { TypeORMNotification } from '../Infra/TypeORM/TypeORMNotification'
export class AppDataSource {
private _dataSource: DataSource | undefined
@@ -65,7 +64,6 @@ export class AppDataSource {
TypeORMAuthenticatorChallenge,
TypeORMEmergencyAccessInvitation,
TypeORMCacheEntry,
TypeORMNotification,
],
migrations: [`${__dirname}/../../migrations/${isConfiguredForMySQL ? 'mysql' : 'sqlite'}/*.js`],
migrationsRun: true,

View File

@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.21.1](https://github.com/standardnotes/server/compare/@standardnotes/domain-core@1.21.0...@standardnotes/domain-core@1.21.1) (2023-07-07)
### Bug Fixes
* transfer notifications from auth to syncing-server. ([#648](https://github.com/standardnotes/server/issues/648)) ([c288e5d](https://github.com/standardnotes/server/commit/c288e5d8dc54778a96a9fc33e3c9cae00583fade))
# [1.21.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-core@1.20.0...@standardnotes/domain-core@1.21.0) (2023-07-06)
### Features
* getting shared vault users and removing shared vault user ([#642](https://github.com/standardnotes/server/issues/642)) ([e905128](https://github.com/standardnotes/server/commit/e905128d45eaadb34d3465d4480dfb3a2c5f3f79))
# [1.20.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-core@1.19.0...@standardnotes/domain-core@1.20.0) (2023-07-05)
### Features

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/domain-core",
"version": "1.20.0",
"version": "1.21.1",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -28,7 +28,7 @@ export class RoleNameCollection extends ValueObject<RoleNameCollectionProps> {
return false
}
equals(roleNameCollection: RoleNameCollection): boolean {
override equals(roleNameCollection: RoleNameCollection): boolean {
if (this.props.value.length !== roleNameCollection.value.length) {
return false
}

View File

@@ -13,4 +13,25 @@ describe('Uuid', () => {
expect(valueOrError.isFailed()).toBeTruthy()
})
it('should check equality between two value objects', () => {
const uuid1 = Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue()
const uuid2 = Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue()
expect(uuid1.equals(uuid2)).toBeTruthy()
})
it('should check inequality between two value objects', () => {
const uuid1 = Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue()
const uuid2 = Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1e').getValue()
expect(uuid1.equals(uuid2)).toBeFalsy()
})
it('should check inequality between two value objects of different types', () => {
const uuid1 = Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue()
expect(uuid1.equals(null as unknown as Uuid)).toBeFalsy()
expect(uuid1.equals(undefined as unknown as Uuid)).toBeFalsy()
})
})

View File

@@ -7,4 +7,12 @@ export abstract class ValueObject<T extends ValueObjectProps> {
constructor(props: T) {
this.props = Object.freeze(props)
}
public equals(vo?: ValueObject<T>): boolean {
if (vo === null || vo === undefined) {
return false
}
return JSON.stringify(this.props) === JSON.stringify(vo.props)
}
}

View File

@@ -43,9 +43,6 @@ export * from './Env/AbstractEnv'
export * from './Mapping/MapperInterface'
export * from './Notification/NotificationType'
export * from './Notification/NotificationTypeProps'
export * from './Service/ServiceConfiguration'
export * from './Service/ServiceContainer'
export * from './Service/ServiceContainerInterface'

View File

@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.12.9](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.12.8...@standardnotes/domain-events-infra@1.12.9) (2023-07-07)
**Note:** Version bump only for package @standardnotes/domain-events-infra
## [1.12.8](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.12.7...@standardnotes/domain-events-infra@1.12.8) (2023-07-05)
**Note:** Version bump only for package @standardnotes/domain-events-infra

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/domain-events-infra",
"version": "1.12.8",
"version": "1.12.9",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -3,6 +3,12 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [2.113.1](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.113.0...@standardnotes/domain-events@2.113.1) (2023-07-07)
### Bug Fixes
* transfer notifications from auth to syncing-server. ([#648](https://github.com/standardnotes/server/issues/648)) ([c288e5d](https://github.com/standardnotes/server/commit/c288e5d8dc54778a96a9fc33e3c9cae00583fade))
# [2.113.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.112.1...@standardnotes/domain-events@2.113.0) (2023-07-05)
### Features

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/domain-events",
"version": "2.113.0",
"version": "2.113.1",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -1,7 +0,0 @@
import { DomainEventInterface } from './DomainEventInterface'
import { NotificationRequestedEventPayload } from './NotificationRequestedEventPayload'
export interface NotificationRequestedEvent extends DomainEventInterface {
type: 'NOTIFICATION_REQUESTED'
payload: NotificationRequestedEventPayload
}

View File

@@ -1,5 +0,0 @@
export interface NotificationRequestedEventPayload {
userUuid: string
type: string
payload: string
}

View File

@@ -42,8 +42,6 @@ export * from './Event/ListedAccountRequestedEvent'
export * from './Event/ListedAccountRequestedEventPayload'
export * from './Event/MuteEmailsSettingChangedEvent'
export * from './Event/MuteEmailsSettingChangedEventPayload'
export * from './Event/NotificationRequestedEvent'
export * from './Event/NotificationRequestedEventPayload'
export * from './Event/PaymentFailedEvent'
export * from './Event/PaymentFailedEventPayload'
export * from './Event/PaymentSuccessEvent'

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.11.5](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.11.4...@standardnotes/event-store@1.11.5) (2023-07-07)
**Note:** Version bump only for package @standardnotes/event-store
## [1.11.4](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.11.3...@standardnotes/event-store@1.11.4) (2023-07-06)
**Note:** Version bump only for package @standardnotes/event-store
## [1.11.3](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.11.2...@standardnotes/event-store@1.11.3) (2023-07-05)
**Note:** Version bump only for package @standardnotes/event-store

View File

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

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.19.5](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.19.4...@standardnotes/files-server@1.19.5) (2023-07-07)
**Note:** Version bump only for package @standardnotes/files-server
## [1.19.4](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.19.3...@standardnotes/files-server@1.19.4) (2023-07-06)
**Note:** Version bump only for package @standardnotes/files-server
## [1.19.3](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.19.2...@standardnotes/files-server@1.19.3) (2023-07-05)
**Note:** Version bump only for package @standardnotes/files-server

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/files-server",
"version": "1.19.3",
"version": "1.19.5",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -3,6 +3,34 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.11.32](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.11.31...@standardnotes/home-server@1.11.32) (2023-07-07)
**Note:** Version bump only for package @standardnotes/home-server
## [1.11.31](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.11.30...@standardnotes/home-server@1.11.31) (2023-07-07)
**Note:** Version bump only for package @standardnotes/home-server
## [1.11.30](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.11.29...@standardnotes/home-server@1.11.30) (2023-07-06)
**Note:** Version bump only for package @standardnotes/home-server
## [1.11.29](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.11.28...@standardnotes/home-server@1.11.29) (2023-07-06)
**Note:** Version bump only for package @standardnotes/home-server
## [1.11.28](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.11.27...@standardnotes/home-server@1.11.28) (2023-07-06)
**Note:** Version bump only for package @standardnotes/home-server
## [1.11.27](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.11.26...@standardnotes/home-server@1.11.27) (2023-07-06)
**Note:** Version bump only for package @standardnotes/home-server
## [1.11.26](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.11.25...@standardnotes/home-server@1.11.26) (2023-07-05)
**Note:** Version bump only for package @standardnotes/home-server
## [1.11.25](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.11.24...@standardnotes/home-server@1.11.25) (2023-07-05)
**Note:** Version bump only for package @standardnotes/home-server

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/home-server",
"version": "1.11.25",
"version": "1.11.32",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.23.9](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.23.8...@standardnotes/revisions-server@1.23.9) (2023-07-07)
**Note:** Version bump only for package @standardnotes/revisions-server
## [1.23.8](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.23.7...@standardnotes/revisions-server@1.23.8) (2023-07-06)
**Note:** Version bump only for package @standardnotes/revisions-server
## [1.23.7](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.23.6...@standardnotes/revisions-server@1.23.7) (2023-07-05)
**Note:** Version bump only for package @standardnotes/revisions-server

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/revisions-server",
"version": "1.23.7",
"version": "1.23.9",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.20.7](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.20.6...@standardnotes/scheduler-server@1.20.7) (2023-07-07)
**Note:** Version bump only for package @standardnotes/scheduler-server
## [1.20.6](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.20.5...@standardnotes/scheduler-server@1.20.6) (2023-07-06)
**Note:** Version bump only for package @standardnotes/scheduler-server
## [1.20.5](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.20.4...@standardnotes/scheduler-server@1.20.5) (2023-07-05)
**Note:** Version bump only for package @standardnotes/scheduler-server

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/scheduler-server",
"version": "1.20.5",
"version": "1.20.7",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.21.12](https://github.com/standardnotes/server/compare/@standardnotes/settings@1.21.11...@standardnotes/settings@1.21.12) (2023-07-07)
**Note:** Version bump only for package @standardnotes/settings
## [1.21.11](https://github.com/standardnotes/server/compare/@standardnotes/settings@1.21.10...@standardnotes/settings@1.21.11) (2023-07-06)
**Note:** Version bump only for package @standardnotes/settings
## [1.21.10](https://github.com/standardnotes/server/compare/@standardnotes/settings@1.21.9...@standardnotes/settings@1.21.10) (2023-07-05)
**Note:** Version bump only for package @standardnotes/settings

View File

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

View File

@@ -3,6 +3,49 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.58.1](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.58.0...@standardnotes/syncing-server@1.58.1) (2023-07-07)
### Bug Fixes
* transfer notifications from auth to syncing-server. ([#648](https://github.com/standardnotes/syncing-server-js/issues/648)) ([c288e5d](https://github.com/standardnotes/syncing-server-js/commit/c288e5d8dc54778a96a9fc33e3c9cae00583fade))
# [1.58.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.57.0...@standardnotes/syncing-server@1.58.0) (2023-07-07)
### Features
* shared vault invites controller and use cases ([#647](https://github.com/standardnotes/syncing-server-js/issues/647)) ([7231013](https://github.com/standardnotes/syncing-server-js/commit/72310130d215047a8097a0c42a7b7dddeb4e3827))
# [1.57.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.56.0...@standardnotes/syncing-server@1.57.0) (2023-07-06)
### Features
* remove inbound shared vault invites. ([#646](https://github.com/standardnotes/syncing-server-js/issues/646)) ([92a5eb0](https://github.com/standardnotes/syncing-server-js/commit/92a5eb0d98486f25b761f37bc5710c45bd95d965))
# [1.56.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.55.0...@standardnotes/syncing-server@1.56.0) (2023-07-06)
### Features
* accept and decline shared vault invites ([#645](https://github.com/standardnotes/syncing-server-js/issues/645)) ([92f96dd](https://github.com/standardnotes/syncing-server-js/commit/92f96ddb84b9b7662899ef187ac38ad2a5769640))
# [1.55.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.54.0...@standardnotes/syncing-server@1.55.0) (2023-07-06)
### Features
* shared vault users controller. ([#643](https://github.com/standardnotes/syncing-server-js/issues/643)) ([b2c32ce](https://github.com/standardnotes/syncing-server-js/commit/b2c32ce70e9020b8d755a65432cb286b624a009c))
* update shared vault invite. ([#644](https://github.com/standardnotes/syncing-server-js/issues/644)) ([912a29d](https://github.com/standardnotes/syncing-server-js/commit/912a29d091ed1ca0af1712cbd09986a1c173a960))
# [1.54.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.53.0...@standardnotes/syncing-server@1.54.0) (2023-07-06)
### Features
* getting shared vault users and removing shared vault user ([#642](https://github.com/standardnotes/syncing-server-js/issues/642)) ([e905128](https://github.com/standardnotes/syncing-server-js/commit/e905128d45eaadb34d3465d4480dfb3a2c5f3f79))
# [1.53.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.52.0...@standardnotes/syncing-server@1.53.0) (2023-07-05)
### Features
* http controllers for shared vaults. ([#641](https://github.com/standardnotes/syncing-server-js/issues/641)) ([7a3946a](https://github.com/standardnotes/syncing-server-js/commit/7a3946a9e2d4168a1d286df321d9972588252b5d))
# [1.52.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.51.0...@standardnotes/syncing-server@1.52.0) (2023-07-05)
### Features

View File

@@ -0,0 +1,16 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class AddNotifications1688540448427 implements MigrationInterface {
name = 'AddNotifications1688540448427'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
'CREATE TABLE `notifications` (`uuid` varchar(36) NOT NULL, `user_uuid` varchar(36) NOT NULL, `type` varchar(36) NOT NULL, `payload` text NOT NULL, `created_at_timestamp` bigint NOT NULL, `updated_at_timestamp` bigint NOT NULL, INDEX `index_notifications_on_user_uuid` (`user_uuid`), PRIMARY KEY (`uuid`)) ENGINE=InnoDB',
)
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('DROP INDEX `index_notifications_on_user_uuid` ON `notifications`')
await queryRunner.query('DROP TABLE `notifications`')
}
}

View File

@@ -0,0 +1,17 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class AddNotifications1688540623272 implements MigrationInterface {
name = 'AddNotifications1688540623272'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
'CREATE TABLE "notifications" ("uuid" varchar PRIMARY KEY NOT NULL, "user_uuid" varchar(36) NOT NULL, "type" varchar(36) NOT NULL, "payload" text NOT NULL, "created_at_timestamp" bigint NOT NULL, "updated_at_timestamp" bigint NOT NULL)',
)
await queryRunner.query('CREATE INDEX "index_notifications_on_user_uuid" ON "notifications" ("user_uuid") ')
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query('DROP INDEX "index_notifications_on_user_uuid"')
await queryRunner.query('DROP TABLE "notifications"')
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/syncing-server",
"version": "1.52.0",
"version": "1.58.1",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -1,23 +1,28 @@
import { DataSource, EntityTarget, LoggerOptions, ObjectLiteral, Repository } from 'typeorm'
import { MysqlConnectionOptions } from 'typeorm/driver/mysql/MysqlConnectionOptions'
import { Item } from '../Domain/Item/Item'
import { Notification } from '../Domain/Notifications/Notification'
import { Env } from './Env'
import { SqliteConnectionOptions } from 'typeorm/driver/sqlite/SqliteConnectionOptions'
export class AppDataSource {
private dataSource: DataSource | undefined
private _dataSource: DataSource | undefined
constructor(private env: Env) {}
getRepository<Entity extends ObjectLiteral>(target: EntityTarget<Entity>): Repository<Entity> {
if (!this.dataSource) {
if (!this._dataSource) {
throw new Error('DataSource not initialized')
}
return this.dataSource.getRepository(target)
return this._dataSource.getRepository(target)
}
async initialize(): Promise<void> {
await this.dataSource.initialize()
}
get dataSource(): DataSource {
this.env.load()
const isConfiguredForMySQL = this.env.get('DB_TYPE') === 'mysql'
@@ -28,7 +33,7 @@ export class AppDataSource {
const commonDataSourceOptions = {
maxQueryExecutionTime,
entities: [Item],
entities: [Item, Notification],
migrations: [`${__dirname}/../../migrations/${isConfiguredForMySQL ? 'mysql' : 'sqlite'}/*.js`],
migrationsRun: true,
logging: <LoggerOptions>this.env.get('DB_DEBUG_LEVEL', true) ?? 'info',
@@ -72,7 +77,7 @@ export class AppDataSource {
database: inReplicaMode ? undefined : this.env.get('DB_DATABASE'),
}
this.dataSource = new DataSource(mySQLDataSourceOptions)
this._dataSource = new DataSource(mySQLDataSourceOptions)
} else {
const sqliteDataSourceOptions: SqliteConnectionOptions = {
...commonDataSourceOptions,
@@ -80,9 +85,9 @@ export class AppDataSource {
database: this.env.get('DB_SQLITE_DATABASE_PATH'),
}
this.dataSource = new DataSource(sqliteDataSourceOptions)
this._dataSource = new DataSource(sqliteDataSourceOptions)
}
await this.dataSource.initialize()
return this._dataSource
}
}

View File

@@ -0,0 +1,7 @@
import { AppDataSource } from './DataSource'
import { Env } from './Env'
const env: Env = new Env()
env.load()
export const MigrationsDataSource = new AppDataSource(env).dataSource

View File

@@ -38,6 +38,21 @@ const TYPES = {
Sync_SyncItems: Symbol.for('Sync_SyncItems'),
Sync_CheckIntegrity: Symbol.for('Sync_CheckIntegrity'),
Sync_GetItem: Symbol.for('Sync_GetItem'),
Sync_GetSharedVaults: Symbol.for('Sync_GetSharedVaults'),
Sync_CreateSharedVault: Symbol.for('Sync_CreateSharedVault'),
Sync_DeleteSharedVault: Symbol.for('Sync_DeleteSharedVault'),
Sync_CreateSharedVaultFileValetToken: Symbol.for('Sync_CreateSharedVaultFileValetToken'),
Sync_GetSharedVaultUsers: Symbol.for('Sync_GetSharedVaultUsers'),
Sync_RemoveSharedVaultUser: Symbol.for('Sync_RemoveSharedVaultUser'),
Sync_InviteUserToSharedVault: Symbol.for('Sync_InviteUserToSharedVault'),
Sync_UpdateSharedVaultInvite: Symbol.for('Sync_UpdateSharedVaultInvite'),
Sync_AcceptInviteToSharedVault: Symbol.for('Sync_AcceptInviteToSharedVault'),
Sync_DeclineInviteToSharedVault: Symbol.for('Sync_DeclineInviteToSharedVault'),
Sync_DeleteSharedVaultInvitesToUser: Symbol.for('Sync_DeleteSharedVaultInvitesToUser'),
Sync_DeleteSharedVaultInvitesSentByUser: Symbol.for('Sync_DeleteSharedVaultInvitesSentByUser'),
Sync_GetSharedVaultInvitesSentByUser: Symbol.for('Sync_GetSharedVaultInvitesSentByUser'),
Sync_GetSharedVaultInvitesSentToUser: Symbol.for('Sync_GetSharedVaultInvitesSentToUser'),
Sync_SharedVaultInviteHttpMapper: Symbol.for('Sync_SharedVaultInviteHttpMapper'),
// Handlers
Sync_AccountDeletionRequestedEventHandler: Symbol.for('Sync_AccountDeletionRequestedEventHandler'),
Sync_DuplicateItemSyncedEventHandler: Symbol.for('Sync_DuplicateItemSyncedEventHandler'),
@@ -68,6 +83,9 @@ const TYPES = {
Sync_ItemTransferCalculator: Symbol.for('Sync_ItemTransferCalculator'),
Sync_ControllerContainer: Symbol.for('Sync_ControllerContainer'),
Sync_HomeServerItemsController: Symbol.for('Sync_HomeServerItemsController'),
// Mapping
Sync_SharedVaultHttpMapper: Symbol.for('Sync_SharedVaultHttpMapper'),
Sync_SharedVaultUserHttpMapper: Symbol.for('Sync_SharedVaultUserHttpMapper'),
}
export default TYPES

View File

@@ -5,7 +5,6 @@ import {
EmailRequestedEvent,
ItemDumpedEvent,
ItemRevisionCreationRequestedEvent,
NotificationRequestedEvent,
RevisionsCopyRequestedEvent,
} from '@standardnotes/domain-events'
import { TimerInterface } from '@standardnotes/time'
@@ -14,25 +13,6 @@ import { DomainEventFactoryInterface } from './DomainEventFactoryInterface'
export class DomainEventFactory implements DomainEventFactoryInterface {
constructor(private timer: TimerInterface) {}
createNotificationRequestedEvent(dto: {
userUuid: string
type: string
payload: string
}): NotificationRequestedEvent {
return {
type: 'NOTIFICATION_REQUESTED',
createdAt: this.timer.getUTCDate(),
meta: {
correlation: {
userIdentifier: dto.userUuid,
userIdentifierType: 'uuid',
},
origin: DomainEventService.SyncingServer,
},
payload: dto,
}
}
createRevisionsCopyRequestedEvent(
userUuid: string,
dto: {

View File

@@ -3,12 +3,10 @@ import {
EmailRequestedEvent,
ItemDumpedEvent,
ItemRevisionCreationRequestedEvent,
NotificationRequestedEvent,
RevisionsCopyRequestedEvent,
} from '@standardnotes/domain-events'
export interface DomainEventFactoryInterface {
createNotificationRequestedEvent(dto: { userUuid: string; type: string; payload: string }): NotificationRequestedEvent
createEmailRequestedEvent(dto: {
userEmail: string
messageIdentifier: string

View File

@@ -1,6 +1,7 @@
import { NotificationType, Timestamps, Uuid } from '@standardnotes/domain-core'
import { Timestamps, Uuid } from '@standardnotes/domain-core'
import { Notification } from './Notification'
import { NotificationType } from './NotificationType'
describe('Notification', () => {
it('should create an entity', () => {

View File

@@ -1,4 +1,6 @@
import { NotificationType, Timestamps, Uuid } from '@standardnotes/domain-core'
import { Timestamps, Uuid } from '@standardnotes/domain-core'
import { NotificationType } from './NotificationType'
export interface NotificationProps {
userUuid: Uuid

View File

@@ -0,0 +1,5 @@
import { Notification } from './Notification'
export interface NotificationRepositoryInterface {
save(notification: Notification): Promise<void>
}

View File

@@ -1,5 +1,4 @@
import { Result } from '../Core/Result'
import { ValueObject } from '../Core/ValueObject'
import { ValueObject, Result } from '@standardnotes/domain-core'
import { NotificationTypeProps } from './NotificationTypeProps'

View File

@@ -7,5 +7,8 @@ export interface SharedVaultInviteRepositoryInterface {
save(sharedVaultInvite: SharedVaultInvite): Promise<void>
remove(sharedVaultInvite: SharedVaultInvite): Promise<void>
removeBySharedVaultUuid(sharedVaultUuid: Uuid): Promise<void>
findByUserUuid(userUuid: Uuid): Promise<SharedVaultInvite[]>
findBySenderUuid(senderUuid: Uuid): Promise<SharedVaultInvite[]>
findByUserUuidAndSharedVaultUuid(dto: { userUuid: Uuid; sharedVaultUuid: Uuid }): Promise<SharedVaultInvite | null>
findBySenderUuidAndSharedVaultUuid(dto: { senderUuid: Uuid; sharedVaultUuid: Uuid }): Promise<SharedVaultInvite[]>
}

View File

@@ -0,0 +1,107 @@
import { Result, Timestamps, Uuid } from '@standardnotes/domain-core'
import { SharedVaultInviteRepositoryInterface } from '../../SharedVault/User/Invite/SharedVaultInviteRepositoryInterface'
import { AddUserToSharedVault } from '../AddUserToSharedVault/AddUserToSharedVault'
import { AcceptInviteToSharedVault } from './AcceptInviteToSharedVault'
import { SharedVaultInvite } from '../../SharedVault/User/Invite/SharedVaultInvite'
import { SharedVaultUserPermission } from '../../SharedVault/User/SharedVaultUserPermission'
describe('AcceptInviteToSharedVault', () => {
let addUserToSharedVault: AddUserToSharedVault
let sharedVaultInviteRepository: SharedVaultInviteRepositoryInterface
let invite: SharedVaultInvite
const createUseCase = () => new AcceptInviteToSharedVault(addUserToSharedVault, sharedVaultInviteRepository)
beforeEach(() => {
invite = SharedVaultInvite.create({
sharedVaultUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
senderUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
encryptedMessage: 'encrypted-message',
permission: SharedVaultUserPermission.create(SharedVaultUserPermission.PERMISSIONS.Read).getValue(),
timestamps: Timestamps.create(123, 123).getValue(),
}).getValue()
addUserToSharedVault = {} as jest.Mocked<AddUserToSharedVault>
addUserToSharedVault.execute = jest.fn().mockReturnValue(Result.ok())
sharedVaultInviteRepository = {} as jest.Mocked<SharedVaultInviteRepositoryInterface>
sharedVaultInviteRepository.findByUuid = jest.fn().mockResolvedValue(invite)
sharedVaultInviteRepository.remove = jest.fn()
})
it('should fail if invite uuid is invalid', async () => {
const useCase = createUseCase()
const result = await useCase.execute({
inviteUuid: 'invalid',
originatorUuid: '00000000-0000-0000-0000-000000000000',
})
expect(result.isFailed()).toBe(true)
expect(result.getError()).toBe('Given value is not a valid uuid: invalid')
})
it('should fail if originator uuid is invalid', async () => {
const useCase = createUseCase()
const result = await useCase.execute({
inviteUuid: '00000000-0000-0000-0000-000000000000',
originatorUuid: 'invalid',
})
expect(result.isFailed()).toBe(true)
expect(result.getError()).toBe('Given value is not a valid uuid: invalid')
})
it('should fail if invite is not found', async () => {
sharedVaultInviteRepository.findByUuid = jest.fn().mockResolvedValue(null)
const useCase = createUseCase()
const result = await useCase.execute({
inviteUuid: '00000000-0000-0000-0000-000000000000',
originatorUuid: '00000000-0000-0000-0000-000000000000',
})
expect(result.isFailed()).toBe(true)
expect(result.getError()).toBe('Invite not found')
})
it('should fail if originator is not the recipient of the invite', async () => {
const useCase = createUseCase()
const result = await useCase.execute({
inviteUuid: '00000000-0000-0000-0000-000000000000',
originatorUuid: '00000000-0000-0000-0000-000000000001',
})
expect(result.isFailed()).toBe(true)
expect(result.getError()).toBe('Only the recipient of the invite can accept it')
})
it('should fail if adding user to shared vault fails', async () => {
addUserToSharedVault.execute = jest.fn().mockReturnValue(Result.fail('Failed to add user to shared vault'))
const useCase = createUseCase()
const result = await useCase.execute({
inviteUuid: '00000000-0000-0000-0000-000000000000',
originatorUuid: '00000000-0000-0000-0000-000000000000',
})
expect(result.isFailed()).toBe(true)
expect(result.getError()).toBe('Failed to add user to shared vault')
})
it('should delete invite after adding user to shared vault', async () => {
const useCase = createUseCase()
await useCase.execute({
inviteUuid: '00000000-0000-0000-0000-000000000000',
originatorUuid: '00000000-0000-0000-0000-000000000000',
})
expect(sharedVaultInviteRepository.remove).toHaveBeenCalled()
})
})

View File

@@ -0,0 +1,47 @@
import { Result, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
import { AcceptInviteToSharedVaultDTO } from './AcceptInviteToSharedVaultDTO'
import { SharedVaultInviteRepositoryInterface } from '../../SharedVault/User/Invite/SharedVaultInviteRepositoryInterface'
import { AddUserToSharedVault } from '../AddUserToSharedVault/AddUserToSharedVault'
export class AcceptInviteToSharedVault implements UseCaseInterface<void> {
constructor(
private addUserToSharedVault: AddUserToSharedVault,
private sharedVaultInviteRepository: SharedVaultInviteRepositoryInterface,
) {}
async execute(dto: AcceptInviteToSharedVaultDTO): Promise<Result<void>> {
const inviteUuidOrError = Uuid.create(dto.inviteUuid)
if (inviteUuidOrError.isFailed()) {
return Result.fail(inviteUuidOrError.getError())
}
const inviteUuid = inviteUuidOrError.getValue()
const originatorUuidOrError = Uuid.create(dto.originatorUuid)
if (originatorUuidOrError.isFailed()) {
return Result.fail(originatorUuidOrError.getError())
}
const originatorUuid = originatorUuidOrError.getValue()
const invite = await this.sharedVaultInviteRepository.findByUuid(inviteUuid)
if (!invite) {
return Result.fail('Invite not found')
}
if (!invite.props.userUuid.equals(originatorUuid)) {
return Result.fail('Only the recipient of the invite can accept it')
}
const result = await this.addUserToSharedVault.execute({
sharedVaultUuid: invite.props.sharedVaultUuid.value,
userUuid: invite.props.userUuid.value,
permission: invite.props.permission.value,
})
if (result.isFailed()) {
return Result.fail(result.getError())
}
await this.sharedVaultInviteRepository.remove(invite)
return Result.ok()
}
}

View File

@@ -0,0 +1,4 @@
export interface AcceptInviteToSharedVaultDTO {
inviteUuid: string
originatorUuid: string
}

View File

@@ -0,0 +1,94 @@
import { TimerInterface } from '@standardnotes/time'
import { Result } from '@standardnotes/domain-core'
import { NotificationRepositoryInterface } from '../../Notifications/NotificationRepositoryInterface'
import { Notification } from '../../Notifications/Notification'
import { AddNotificationForUser } from './AddNotificationForUser'
import { NotificationType } from '../../Notifications/NotificationType'
describe('AddNotificationForUser', () => {
let notificationRepository: NotificationRepositoryInterface
let timer: TimerInterface
const createUseCase = () => new AddNotificationForUser(notificationRepository, timer)
beforeEach(() => {
notificationRepository = {} as jest.Mocked<NotificationRepositoryInterface>
notificationRepository.save = jest.fn()
timer = {} as jest.Mocked<TimerInterface>
timer.getTimestampInMicroseconds = jest.fn().mockReturnValue(123456789)
})
it('should save notification', async () => {
const useCase = createUseCase()
const result = await useCase.execute({
userUuid: '0e8c3c7e-3f1a-4f7a-9b5a-5b2b0a7d4b1e',
type: NotificationType.TYPES.RemovedFromSharedVault,
payload: 'payload',
version: '1.0',
})
expect(result.isFailed()).toBeFalsy()
})
it('should return error if user uuid is invalid', async () => {
const useCase = createUseCase()
const result = await useCase.execute({
userUuid: 'invalid',
type: NotificationType.TYPES.RemovedFromSharedVault,
payload: 'payload',
version: '1.0',
})
expect(result.isFailed()).toBeTruthy()
})
it('should return error if notification type is invalid', async () => {
const useCase = createUseCase()
const result = await useCase.execute({
userUuid: '0e8c3c7e-3f1a-4f7a-9b5a-5b2b0a7d4b1e',
type: 'invalid',
payload: 'payload',
version: '1.0',
})
expect(result.isFailed()).toBeTruthy()
})
it('should return error if notification payload is invalid', async () => {
const useCase = createUseCase()
const result = await useCase.execute({
userUuid: '0e8c3c7e-3f1a-4f7a-9b5a-5b2b0a7d4b1e',
type: NotificationType.TYPES.RemovedFromSharedVault,
payload: '',
version: '1.0',
})
expect(result.isFailed()).toBeTruthy()
})
it('should return error if notification could not be created', async () => {
const mock = jest.spyOn(Notification, 'create')
mock.mockImplementation(() => {
return Result.fail('Oops')
})
const useCase = createUseCase()
const result = await useCase.execute({
userUuid: '0e8c3c7e-3f1a-4f7a-9b5a-5b2b0a7d4b1e',
type: NotificationType.TYPES.RemovedFromSharedVault,
payload: 'payload',
version: '1.0',
})
expect(result.isFailed()).toBeTruthy()
mock.mockRestore()
})
})

View File

@@ -0,0 +1,48 @@
import { Result, Timestamps, UseCaseInterface, Uuid, Validator } from '@standardnotes/domain-core'
import { TimerInterface } from '@standardnotes/time'
import { AddNotificationForUserDTO } from './AddNotificationForUserDTO'
import { NotificationRepositoryInterface } from '../../Notifications/NotificationRepositoryInterface'
import { Notification } from '../../Notifications/Notification'
import { NotificationType } from '../../Notifications/NotificationType'
export class AddNotificationForUser implements UseCaseInterface<Notification> {
constructor(private notificationRepository: NotificationRepositoryInterface, private timer: TimerInterface) {}
async execute(dto: AddNotificationForUserDTO): Promise<Result<Notification>> {
const userUuidOrError = Uuid.create(dto.userUuid)
if (userUuidOrError.isFailed()) {
return Result.fail(userUuidOrError.getError())
}
const userUuid = userUuidOrError.getValue()
const typeOrError = NotificationType.create(dto.type)
if (typeOrError.isFailed()) {
return Result.fail(typeOrError.getError())
}
const type = typeOrError.getValue()
const paylodNotEmptyValidationResult = Validator.isNotEmpty(dto.payload)
if (paylodNotEmptyValidationResult.isFailed()) {
return Result.fail(paylodNotEmptyValidationResult.getError())
}
const notificationOrError = Notification.create({
userUuid,
type,
payload: dto.payload,
timestamps: Timestamps.create(
this.timer.getTimestampInMicroseconds(),
this.timer.getTimestampInMicroseconds(),
).getValue(),
})
if (notificationOrError.isFailed()) {
return Result.fail(notificationOrError.getError())
}
const notification = notificationOrError.getValue()
await this.notificationRepository.save(notification)
return Result.ok(notification)
}
}

View File

@@ -0,0 +1,6 @@
export interface AddNotificationForUserDTO {
version: string
type: string
userUuid: string
payload: string
}

View File

@@ -41,7 +41,9 @@ export class CreateSharedVaultFileValetToken implements UseCaseInterface<string>
}
if (
sharedVaultUser.props.permission.value === SharedVaultUserPermission.PERMISSIONS.Read &&
sharedVaultUser.props.permission.equals(
SharedVaultUserPermission.create(SharedVaultUserPermission.PERMISSIONS.Read).getValue(),
) &&
dto.operation !== ValetTokenOperation.Read
) {
return Result.fail('User does not have permission to perform this operation')
@@ -72,7 +74,11 @@ export class CreateSharedVaultFileValetToken implements UseCaseInterface<string>
return Result.fail('Shared vault target user not found')
}
if (toSharedVaultUser.props.permission.value === SharedVaultUserPermission.PERMISSIONS.Read) {
if (
toSharedVaultUser.props.permission.equals(
SharedVaultUserPermission.create(SharedVaultUserPermission.PERMISSIONS.Read).getValue(),
)
) {
return Result.fail('User does not have permission to perform this operation')
}
}

View File

@@ -0,0 +1,88 @@
import { Timestamps, Uuid } from '@standardnotes/domain-core'
import { SharedVaultInviteRepositoryInterface } from '../../SharedVault/User/Invite/SharedVaultInviteRepositoryInterface'
import { DeclineInviteToSharedVault } from './DeclineInviteToSharedVault'
import { SharedVaultInvite } from '../../SharedVault/User/Invite/SharedVaultInvite'
import { SharedVaultUserPermission } from '../../SharedVault/User/SharedVaultUserPermission'
describe('DeclineInviteToSharedVault', () => {
let sharedVaultInviteRepository: SharedVaultInviteRepositoryInterface
let invite: SharedVaultInvite
const createUseCase = () => new DeclineInviteToSharedVault(sharedVaultInviteRepository)
beforeEach(() => {
invite = SharedVaultInvite.create({
sharedVaultUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
senderUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
encryptedMessage: 'encrypted-message',
permission: SharedVaultUserPermission.create(SharedVaultUserPermission.PERMISSIONS.Read).getValue(),
timestamps: Timestamps.create(123, 123).getValue(),
}).getValue()
sharedVaultInviteRepository = {} as jest.Mocked<SharedVaultInviteRepositoryInterface>
sharedVaultInviteRepository.findByUuid = jest.fn().mockResolvedValue(invite)
sharedVaultInviteRepository.remove = jest.fn()
})
it('should fail if invite uuid is invalid', async () => {
const useCase = createUseCase()
const result = await useCase.execute({
inviteUuid: 'invalid',
originatorUuid: '00000000-0000-0000-0000-000000000000',
})
expect(result.isFailed()).toBe(true)
expect(result.getError()).toBe('Given value is not a valid uuid: invalid')
})
it('should fail if originator uuid is invalid', async () => {
const useCase = createUseCase()
const result = await useCase.execute({
inviteUuid: '00000000-0000-0000-0000-000000000000',
originatorUuid: 'invalid',
})
expect(result.isFailed()).toBe(true)
expect(result.getError()).toBe('Given value is not a valid uuid: invalid')
})
it('should fail if invite is not found', async () => {
sharedVaultInviteRepository.findByUuid = jest.fn().mockResolvedValue(null)
const useCase = createUseCase()
const result = await useCase.execute({
inviteUuid: '00000000-0000-0000-0000-000000000000',
originatorUuid: '00000000-0000-0000-0000-000000000000',
})
expect(result.isFailed()).toBe(true)
expect(result.getError()).toBe('Invite not found')
})
it('should fail if originator is not the recipient of the invite', async () => {
const useCase = createUseCase()
const result = await useCase.execute({
inviteUuid: '00000000-0000-0000-0000-000000000000',
originatorUuid: '00000000-0000-0000-0000-000000000001',
})
expect(result.isFailed()).toBe(true)
expect(result.getError()).toBe('Only the recipient of the invite can decline it')
})
it('should delete invite', async () => {
const useCase = createUseCase()
await useCase.execute({
inviteUuid: '00000000-0000-0000-0000-000000000000',
originatorUuid: '00000000-0000-0000-0000-000000000000',
})
expect(sharedVaultInviteRepository.remove).toHaveBeenCalled()
})
})

View File

@@ -0,0 +1,34 @@
import { Result, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
import { DeclineInviteToSharedVaultDTO } from './DeclineInviteToSharedVaultDTO'
import { SharedVaultInviteRepositoryInterface } from '../../SharedVault/User/Invite/SharedVaultInviteRepositoryInterface'
export class DeclineInviteToSharedVault implements UseCaseInterface<void> {
constructor(private sharedVaultInviteRepository: SharedVaultInviteRepositoryInterface) {}
async execute(dto: DeclineInviteToSharedVaultDTO): Promise<Result<void>> {
const inviteUuidOrError = Uuid.create(dto.inviteUuid)
if (inviteUuidOrError.isFailed()) {
return Result.fail(inviteUuidOrError.getError())
}
const inviteUuid = inviteUuidOrError.getValue()
const originatorUuidOrError = Uuid.create(dto.originatorUuid)
if (originatorUuidOrError.isFailed()) {
return Result.fail(originatorUuidOrError.getError())
}
const originatorUuid = originatorUuidOrError.getValue()
const invite = await this.sharedVaultInviteRepository.findByUuid(inviteUuid)
if (!invite) {
return Result.fail('Invite not found')
}
if (!invite.props.userUuid.equals(originatorUuid)) {
return Result.fail('Only the recipient of the invite can decline it')
}
await this.sharedVaultInviteRepository.remove(invite)
return Result.ok()
}
}

View File

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

View File

@@ -1,21 +1,19 @@
import { DomainEventPublisherInterface, NotificationRequestedEvent } from '@standardnotes/domain-events'
import { Uuid, Timestamps } from '@standardnotes/domain-core'
import { Uuid, Timestamps, Result } from '@standardnotes/domain-core'
import { SharedVaultRepositoryInterface } from '../../SharedVault/SharedVaultRepositoryInterface'
import { SharedVaultInviteRepositoryInterface } from '../../SharedVault/User/Invite/SharedVaultInviteRepositoryInterface'
import { SharedVaultUserRepositoryInterface } from '../../SharedVault/User/SharedVaultUserRepositoryInterface'
import { DeleteSharedVault } from './DeleteSharedVault'
import { DomainEventFactoryInterface } from '../../Event/DomainEventFactoryInterface'
import { SharedVault } from '../../SharedVault/SharedVault'
import { SharedVaultUser } from '../../SharedVault/User/SharedVaultUser'
import { SharedVaultUserPermission } from '../../SharedVault/User/SharedVaultUserPermission'
import { RemoveUserFromSharedVault } from '../RemoveUserFromSharedVault/RemoveUserFromSharedVault'
describe('DeleteSharedVault', () => {
let sharedVaultRepository: SharedVaultRepositoryInterface
let sharedVaultUserRepository: SharedVaultUserRepositoryInterface
let sharedVaultInviteRepository: SharedVaultInviteRepositoryInterface
let domainEventPublisher: DomainEventPublisherInterface
let domainEventFactory: DomainEventFactoryInterface
let removeUserFromSharedVault: RemoveUserFromSharedVault
let sharedVault: SharedVault
let sharedVaultUser: SharedVaultUser
@@ -24,8 +22,7 @@ describe('DeleteSharedVault', () => {
sharedVaultRepository,
sharedVaultUserRepository,
sharedVaultInviteRepository,
domainEventPublisher,
domainEventFactory,
removeUserFromSharedVault,
)
beforeEach(() => {
@@ -47,18 +44,12 @@ describe('DeleteSharedVault', () => {
}).getValue()
sharedVaultUserRepository = {} as jest.Mocked<SharedVaultUserRepositoryInterface>
sharedVaultUserRepository.findBySharedVaultUuid = jest.fn().mockResolvedValue([sharedVaultUser])
sharedVaultUserRepository.removeBySharedVaultUuid = jest.fn()
sharedVaultInviteRepository = {} as jest.Mocked<SharedVaultInviteRepositoryInterface>
sharedVaultInviteRepository.removeBySharedVaultUuid = jest.fn()
domainEventPublisher = {} as jest.Mocked<DomainEventPublisherInterface>
domainEventPublisher.publish = jest.fn()
domainEventFactory = {} as jest.Mocked<DomainEventFactoryInterface>
domainEventFactory.createNotificationRequestedEvent = jest
.fn()
.mockReturnValue({} as jest.Mocked<NotificationRequestedEvent>)
removeUserFromSharedVault = {} as jest.Mocked<RemoveUserFromSharedVault>
removeUserFromSharedVault.execute = jest.fn().mockReturnValue(Result.ok())
})
it('should remove shared vault', async () => {
@@ -71,9 +62,8 @@ describe('DeleteSharedVault', () => {
expect(result.isFailed()).toBeFalsy()
expect(sharedVaultRepository.remove).toHaveBeenCalled()
expect(sharedVaultUserRepository.removeBySharedVaultUuid).toHaveBeenCalled()
expect(sharedVaultInviteRepository.removeBySharedVaultUuid).toHaveBeenCalled()
expect(domainEventPublisher.publish).toHaveBeenCalled()
expect(removeUserFromSharedVault.execute).toHaveBeenCalled()
})
it('should return error when shared vault does not exist', async () => {
@@ -87,9 +77,8 @@ describe('DeleteSharedVault', () => {
expect(result.isFailed()).toBeTruthy()
expect(sharedVaultRepository.remove).not.toHaveBeenCalled()
expect(sharedVaultUserRepository.removeBySharedVaultUuid).not.toHaveBeenCalled()
expect(sharedVaultInviteRepository.removeBySharedVaultUuid).not.toHaveBeenCalled()
expect(domainEventPublisher.publish).not.toHaveBeenCalled()
expect(removeUserFromSharedVault.execute).not.toHaveBeenCalled()
})
it('should return error when shared vault uuid is invalid', async () => {
@@ -102,9 +91,8 @@ describe('DeleteSharedVault', () => {
expect(result.isFailed()).toBeTruthy()
expect(sharedVaultRepository.remove).not.toHaveBeenCalled()
expect(sharedVaultUserRepository.removeBySharedVaultUuid).not.toHaveBeenCalled()
expect(sharedVaultInviteRepository.removeBySharedVaultUuid).not.toHaveBeenCalled()
expect(domainEventPublisher.publish).not.toHaveBeenCalled()
expect(removeUserFromSharedVault.execute).not.toHaveBeenCalled()
})
it('should return error when originator uuid is invalid', async () => {
@@ -117,9 +105,8 @@ describe('DeleteSharedVault', () => {
expect(result.isFailed()).toBeTruthy()
expect(sharedVaultRepository.remove).not.toHaveBeenCalled()
expect(sharedVaultUserRepository.removeBySharedVaultUuid).not.toHaveBeenCalled()
expect(sharedVaultInviteRepository.removeBySharedVaultUuid).not.toHaveBeenCalled()
expect(domainEventPublisher.publish).not.toHaveBeenCalled()
expect(removeUserFromSharedVault.execute).not.toHaveBeenCalled()
})
it('should return error when originator of the delete request is not the owner of the shared vault', async () => {
@@ -139,8 +126,22 @@ describe('DeleteSharedVault', () => {
expect(result.isFailed()).toBeTruthy()
expect(sharedVaultRepository.remove).not.toHaveBeenCalled()
expect(sharedVaultUserRepository.removeBySharedVaultUuid).not.toHaveBeenCalled()
expect(sharedVaultInviteRepository.removeBySharedVaultUuid).not.toHaveBeenCalled()
expect(domainEventPublisher.publish).not.toHaveBeenCalled()
expect(removeUserFromSharedVault.execute).not.toHaveBeenCalled()
})
it('should return error if removing user from shared vault fails', async () => {
removeUserFromSharedVault.execute = jest.fn().mockReturnValue(Result.fail('failed'))
const useCase = createUseCase()
const result = await useCase.execute({
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
originatorUuid: '00000000-0000-0000-0000-000000000000',
})
expect(result.isFailed()).toBeTruthy()
expect(sharedVaultRepository.remove).not.toHaveBeenCalled()
expect(sharedVaultInviteRepository.removeBySharedVaultUuid).not.toHaveBeenCalled()
expect(removeUserFromSharedVault.execute).toHaveBeenCalled()
})
})

View File

@@ -1,18 +1,17 @@
import { NotificationType, Result, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
import { Result, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
import { DeleteSharedVaultDTO } from './DeleteSharedVaultDTO'
import { SharedVaultRepositoryInterface } from '../../SharedVault/SharedVaultRepositoryInterface'
import { SharedVaultUserRepositoryInterface } from '../../SharedVault/User/SharedVaultUserRepositoryInterface'
import { DomainEventPublisherInterface } from '@standardnotes/domain-events'
import { DomainEventFactoryInterface } from '../../Event/DomainEventFactoryInterface'
import { SharedVaultInviteRepositoryInterface } from '../../SharedVault/User/Invite/SharedVaultInviteRepositoryInterface'
import { RemoveUserFromSharedVault } from '../RemoveUserFromSharedVault/RemoveUserFromSharedVault'
export class DeleteSharedVault implements UseCaseInterface<void> {
constructor(
private sharedVaultRepository: SharedVaultRepositoryInterface,
private sharedVaultUserRepository: SharedVaultUserRepositoryInterface,
private sharedVaultInviteRepository: SharedVaultInviteRepositoryInterface,
private domainEventPublisher: DomainEventPublisherInterface,
private domainEventFactory: DomainEventFactoryInterface,
private removeUserFromSharedVault: RemoveUserFromSharedVault,
) {}
async execute(dto: DeleteSharedVaultDTO): Promise<Result<void>> {
@@ -39,22 +38,19 @@ export class DeleteSharedVault implements UseCaseInterface<void> {
const sharedVaultUsers = await this.sharedVaultUserRepository.findBySharedVaultUuid(sharedVaultUuid)
for (const sharedVaultUser of sharedVaultUsers) {
await this.domainEventPublisher.publish(
this.domainEventFactory.createNotificationRequestedEvent({
payload: JSON.stringify({
sharedVaultUuid: sharedVault.id.toString(),
version: '1.0',
}),
userUuid: sharedVaultUser.props.userUuid.value,
type: NotificationType.TYPES.RemovedFromSharedVault,
}),
)
const result = await this.removeUserFromSharedVault.execute({
originatorUuid: originatorUuid.value,
sharedVaultUuid: sharedVaultUuid.value,
userUuid: sharedVaultUser.props.userUuid.value,
})
if (result.isFailed()) {
return Result.fail(result.getError())
}
}
await this.sharedVaultInviteRepository.removeBySharedVaultUuid(sharedVaultUuid)
await this.sharedVaultUserRepository.removeBySharedVaultUuid(sharedVaultUuid)
await this.sharedVaultRepository.remove(sharedVault)
return Result.ok()

View File

@@ -0,0 +1,79 @@
import { Result, Timestamps, Uuid } from '@standardnotes/domain-core'
import { SharedVaultInviteRepositoryInterface } from '../../SharedVault/User/Invite/SharedVaultInviteRepositoryInterface'
import { DeclineInviteToSharedVault } from '../DeclineInviteToSharedVault/DeclineInviteToSharedVault'
import { DeleteSharedVaultInvitesSentByUser } from './DeleteSharedVaultInvitesSentByUser'
import { SharedVaultInvite } from '../../SharedVault/User/Invite/SharedVaultInvite'
import { SharedVaultUserPermission } from '../../SharedVault/User/SharedVaultUserPermission'
describe('DeleteSharedVaultInvitesSentByUser', () => {
let sharedVaultInviteRepository: SharedVaultInviteRepositoryInterface
let declineInviteToSharedVault: DeclineInviteToSharedVault
let sharedVaultInvite: SharedVaultInvite
const createUseCase = () =>
new DeleteSharedVaultInvitesSentByUser(sharedVaultInviteRepository, declineInviteToSharedVault)
beforeEach(() => {
sharedVaultInvite = SharedVaultInvite.create({
sharedVaultUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
senderUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
encryptedMessage: 'encrypted-message',
permission: SharedVaultUserPermission.create(SharedVaultUserPermission.PERMISSIONS.Read).getValue(),
timestamps: Timestamps.create(123, 123).getValue(),
}).getValue()
sharedVaultInviteRepository = {} as jest.Mocked<SharedVaultInviteRepositoryInterface>
sharedVaultInviteRepository.findBySenderUuidAndSharedVaultUuid = jest.fn().mockReturnValue([sharedVaultInvite])
declineInviteToSharedVault = {} as jest.Mocked<DeclineInviteToSharedVault>
declineInviteToSharedVault.execute = jest.fn().mockReturnValue(Result.ok())
})
it('should decline all invites by user', async () => {
const useCase = createUseCase()
const result = await useCase.execute({
userUuid: '00000000-0000-0000-0000-000000000000',
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
})
expect(result.isFailed()).toBeFalsy()
expect(declineInviteToSharedVault.execute).toHaveBeenCalled()
})
it('should return error when user uuid is invalid', async () => {
const useCase = createUseCase()
const result = await useCase.execute({
userUuid: 'invalid-uuid',
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
})
expect(result.isFailed()).toBeTruthy()
})
it('should return error when shared vault uuid is invalid', async () => {
const useCase = createUseCase()
const result = await useCase.execute({
userUuid: '00000000-0000-0000-0000-000000000000',
sharedVaultUuid: 'invalid-uuid',
})
expect(result.isFailed()).toBeTruthy()
})
it('should return error when declineInviteToSharedVault fails', async () => {
declineInviteToSharedVault.execute = jest.fn().mockReturnValue(Result.fail('error'))
const useCase = createUseCase()
const result = await useCase.execute({
userUuid: '00000000-0000-0000-0000-000000000000',
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
})
expect(result.isFailed()).toBeTruthy()
})
})

View File

@@ -0,0 +1,41 @@
import { Result, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
import { DeleteSharedVaultInvitesSentByUserDTO } from './DeleteSharedVaultInvitesSentByUserDTO'
import { DeclineInviteToSharedVault } from '../DeclineInviteToSharedVault/DeclineInviteToSharedVault'
import { SharedVaultInviteRepositoryInterface } from '../../SharedVault/User/Invite/SharedVaultInviteRepositoryInterface'
export class DeleteSharedVaultInvitesSentByUser implements UseCaseInterface<void> {
constructor(
private sharedVaultInviteRepository: SharedVaultInviteRepositoryInterface,
private declineInviteToSharedVault: DeclineInviteToSharedVault,
) {}
async execute(dto: DeleteSharedVaultInvitesSentByUserDTO): Promise<Result<void>> {
const userUuidOrError = Uuid.create(dto.userUuid)
if (userUuidOrError.isFailed()) {
return Result.fail(userUuidOrError.getError())
}
const userUuid = userUuidOrError.getValue()
const sharedVaultUuidOrError = Uuid.create(dto.sharedVaultUuid)
if (sharedVaultUuidOrError.isFailed()) {
return Result.fail(sharedVaultUuidOrError.getError())
}
const sharedVaultUuid = sharedVaultUuidOrError.getValue()
const inboundInvites = await this.sharedVaultInviteRepository.findBySenderUuidAndSharedVaultUuid({
senderUuid: userUuid,
sharedVaultUuid,
})
for (const invite of inboundInvites) {
const result = await this.declineInviteToSharedVault.execute({
inviteUuid: invite.id.toString(),
originatorUuid: userUuid.value,
})
if (result.isFailed()) {
return Result.fail(result.getError())
}
}
return Result.ok()
}
}

View File

@@ -0,0 +1,4 @@
export interface DeleteSharedVaultInvitesSentByUserDTO {
userUuid: string
sharedVaultUuid: string
}

View File

@@ -0,0 +1,65 @@
import { Result, Timestamps, Uuid } from '@standardnotes/domain-core'
import { SharedVaultInviteRepositoryInterface } from '../../SharedVault/User/Invite/SharedVaultInviteRepositoryInterface'
import { DeclineInviteToSharedVault } from '../DeclineInviteToSharedVault/DeclineInviteToSharedVault'
import { DeleteSharedVaultInvitesToUser } from './DeleteSharedVaultInvitesToUser'
import { SharedVaultInvite } from '../../SharedVault/User/Invite/SharedVaultInvite'
import { SharedVaultUserPermission } from '../../SharedVault/User/SharedVaultUserPermission'
describe('DeleteSharedVaultInvitesToUser', () => {
let sharedVaultInviteRepository: SharedVaultInviteRepositoryInterface
let declineInviteToSharedVault: DeclineInviteToSharedVault
let sharedVaultInvite: SharedVaultInvite
const createUseCase = () =>
new DeleteSharedVaultInvitesToUser(sharedVaultInviteRepository, declineInviteToSharedVault)
beforeEach(() => {
sharedVaultInvite = SharedVaultInvite.create({
sharedVaultUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
senderUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
encryptedMessage: 'encrypted-message',
permission: SharedVaultUserPermission.create(SharedVaultUserPermission.PERMISSIONS.Read).getValue(),
timestamps: Timestamps.create(123, 123).getValue(),
}).getValue()
sharedVaultInviteRepository = {} as jest.Mocked<SharedVaultInviteRepositoryInterface>
sharedVaultInviteRepository.findByUserUuid = jest.fn().mockReturnValue([sharedVaultInvite])
declineInviteToSharedVault = {} as jest.Mocked<DeclineInviteToSharedVault>
declineInviteToSharedVault.execute = jest.fn().mockReturnValue(Result.ok())
})
it('should decline all invites to user', async () => {
const useCase = createUseCase()
const result = await useCase.execute({
userUuid: '00000000-0000-0000-0000-000000000000',
})
expect(result.isFailed()).toBeFalsy()
expect(declineInviteToSharedVault.execute).toHaveBeenCalled()
})
it('should return error when user uuid is invalid', async () => {
const useCase = createUseCase()
const result = await useCase.execute({
userUuid: 'invalid-uuid',
})
expect(result.isFailed()).toBeTruthy()
})
it('should return error when declineInviteToSharedVault fails', async () => {
declineInviteToSharedVault.execute = jest.fn().mockReturnValue(Result.fail('error'))
const useCase = createUseCase()
const result = await useCase.execute({
userUuid: '00000000-0000-0000-0000-000000000000',
})
expect(result.isFailed()).toBeTruthy()
})
})

View File

@@ -0,0 +1,32 @@
import { Result, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
import { DeleteSharedVaultInvitesToUserDTO } from './DeleteSharedVaultInvitesToUserDTO'
import { DeclineInviteToSharedVault } from '../DeclineInviteToSharedVault/DeclineInviteToSharedVault'
import { SharedVaultInviteRepositoryInterface } from '../../SharedVault/User/Invite/SharedVaultInviteRepositoryInterface'
export class DeleteSharedVaultInvitesToUser implements UseCaseInterface<void> {
constructor(
private sharedVaultInviteRepository: SharedVaultInviteRepositoryInterface,
private declineInviteToSharedVault: DeclineInviteToSharedVault,
) {}
async execute(dto: DeleteSharedVaultInvitesToUserDTO): Promise<Result<void>> {
const userUuidOrError = Uuid.create(dto.userUuid)
if (userUuidOrError.isFailed()) {
return Result.fail(userUuidOrError.getError())
}
const userUuid = userUuidOrError.getValue()
const inboundInvites = await this.sharedVaultInviteRepository.findByUserUuid(userUuid)
for (const invite of inboundInvites) {
const result = await this.declineInviteToSharedVault.execute({
inviteUuid: invite.id.toString(),
originatorUuid: userUuid.value,
})
if (result.isFailed()) {
return Result.fail(result.getError())
}
}
return Result.ok()
}
}

View File

@@ -0,0 +1,3 @@
export interface DeleteSharedVaultInvitesToUserDTO {
userUuid: string
}

View File

@@ -0,0 +1,83 @@
import { Uuid, Timestamps } from '@standardnotes/domain-core'
import { SharedVaultInvite } from '../../SharedVault/User/Invite/SharedVaultInvite'
import { SharedVaultInviteRepositoryInterface } from '../../SharedVault/User/Invite/SharedVaultInviteRepositoryInterface'
import { SharedVaultUserPermission } from '../../SharedVault/User/SharedVaultUserPermission'
import { GetSharedVaultInvitesSentByUser } from './GetSharedVaultInvitesSentByUser'
describe('GetSharedVaultInvitesSentByUser', () => {
let sharedVaultInviteRepository: SharedVaultInviteRepositoryInterface
let invite: SharedVaultInvite
const createUseCase = () => new GetSharedVaultInvitesSentByUser(sharedVaultInviteRepository)
beforeEach(() => {
invite = SharedVaultInvite.create({
sharedVaultUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
senderUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
encryptedMessage: 'encrypted-message',
permission: SharedVaultUserPermission.create(SharedVaultUserPermission.PERMISSIONS.Read).getValue(),
timestamps: Timestamps.create(123, 123).getValue(),
}).getValue()
sharedVaultInviteRepository = {} as jest.Mocked<SharedVaultInviteRepositoryInterface>
sharedVaultInviteRepository.findBySenderUuid = jest.fn().mockResolvedValue([invite])
})
it('should return invites sent by user', async () => {
const useCase = createUseCase()
const result = await useCase.execute({
senderUuid: '00000000-0000-0000-0000-000000000000',
})
expect(result.getValue()).toEqual([invite])
})
it('should return empty array if no invites found', async () => {
const useCase = createUseCase()
sharedVaultInviteRepository.findBySenderUuid = jest.fn().mockResolvedValue([])
const result = await useCase.execute({
senderUuid: '00000000-0000-0000-0000-000000000000',
})
expect(result.getValue()).toEqual([])
})
it('should fail if sender uuid is not valid', async () => {
const useCase = createUseCase()
const result = await useCase.execute({
senderUuid: 'invalid-uuid',
})
expect(result.isFailed()).toBeTruthy()
})
it('should return invites sent by user for specific shared vault', async () => {
const useCase = createUseCase()
sharedVaultInviteRepository.findBySenderUuidAndSharedVaultUuid = jest.fn().mockResolvedValue([invite])
const result = await useCase.execute({
senderUuid: '00000000-0000-0000-0000-000000000000',
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
})
expect(result.getValue()).toEqual([invite])
})
it('should fail if shared vault uuid is invalid', async () => {
const useCase = createUseCase()
const result = await useCase.execute({
senderUuid: '00000000-0000-0000-0000-000000000000',
sharedVaultUuid: 'invalid-uuid',
})
expect(result.isFailed()).toBeTruthy()
})
})

View File

@@ -0,0 +1,36 @@
import { Result, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
import { SharedVaultInvite } from '../../SharedVault/User/Invite/SharedVaultInvite'
import { GetSharedVaultInvitesSentByUserDTO } from './GetSharedVaultInvitesSentByUserDTO'
import { SharedVaultInviteRepositoryInterface } from '../../SharedVault/User/Invite/SharedVaultInviteRepositoryInterface'
export class GetSharedVaultInvitesSentByUser implements UseCaseInterface<SharedVaultInvite[]> {
constructor(private sharedVaultInviteRepository: SharedVaultInviteRepositoryInterface) {}
async execute(dto: GetSharedVaultInvitesSentByUserDTO): Promise<Result<SharedVaultInvite[]>> {
const senderUuidOrError = Uuid.create(dto.senderUuid)
if (senderUuidOrError.isFailed()) {
return Result.fail(senderUuidOrError.getError())
}
const senderUuid = senderUuidOrError.getValue()
let sharedVaultUuid: Uuid | undefined
if (dto.sharedVaultUuid) {
const sharedVaultUuidOrError = Uuid.create(dto.sharedVaultUuid)
if (sharedVaultUuidOrError.isFailed()) {
return Result.fail(sharedVaultUuidOrError.getError())
}
sharedVaultUuid = sharedVaultUuidOrError.getValue()
}
if (sharedVaultUuid) {
return Result.ok(
await this.sharedVaultInviteRepository.findBySenderUuidAndSharedVaultUuid({
senderUuid,
sharedVaultUuid,
}),
)
}
return Result.ok(await this.sharedVaultInviteRepository.findBySenderUuid(senderUuid))
}
}

View File

@@ -0,0 +1,4 @@
export interface GetSharedVaultInvitesSentByUserDTO {
senderUuid: string
sharedVaultUuid?: string
}

View File

@@ -0,0 +1,59 @@
import { Uuid, Timestamps } from '@standardnotes/domain-core'
import { SharedVaultInvite } from '../../SharedVault/User/Invite/SharedVaultInvite'
import { SharedVaultInviteRepositoryInterface } from '../../SharedVault/User/Invite/SharedVaultInviteRepositoryInterface'
import { SharedVaultUserPermission } from '../../SharedVault/User/SharedVaultUserPermission'
import { GetSharedVaultInvitesSentToUser } from './GetSharedVaultInvitesSentToUser'
describe('GetSharedVaultInvitesSentToUser', () => {
let sharedVaultInviteRepository: SharedVaultInviteRepositoryInterface
let invite: SharedVaultInvite
const createUseCase = () => new GetSharedVaultInvitesSentToUser(sharedVaultInviteRepository)
beforeEach(() => {
invite = SharedVaultInvite.create({
sharedVaultUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
senderUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
encryptedMessage: 'encrypted-message',
permission: SharedVaultUserPermission.create(SharedVaultUserPermission.PERMISSIONS.Read).getValue(),
timestamps: Timestamps.create(123, 123).getValue(),
}).getValue()
sharedVaultInviteRepository = {} as jest.Mocked<SharedVaultInviteRepositoryInterface>
sharedVaultInviteRepository.findByUserUuid = jest.fn().mockResolvedValue([invite])
})
it('should return invites sent to user', async () => {
const useCase = createUseCase()
const result = await useCase.execute({
userUuid: '00000000-0000-0000-0000-000000000000',
})
expect(result.getValue()).toEqual([invite])
})
it('should return empty array if no invites found', async () => {
const useCase = createUseCase()
sharedVaultInviteRepository.findByUserUuid = jest.fn().mockReturnValue([])
const result = await useCase.execute({
userUuid: '00000000-0000-0000-0000-000000000000',
})
expect(result.getValue()).toEqual([])
})
it('should fail if sender uuid is not valid', async () => {
const useCase = createUseCase()
const result = await useCase.execute({
userUuid: 'invalid-uuid',
})
expect(result.isFailed()).toBeTruthy()
})
})

View File

@@ -0,0 +1,18 @@
import { Result, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
import { SharedVaultInvite } from '../../SharedVault/User/Invite/SharedVaultInvite'
import { GetSharedVaultInvitesSentToUserDTO } from './GetSharedVaultInvitesSentToUserDTO'
import { SharedVaultInviteRepositoryInterface } from '../../SharedVault/User/Invite/SharedVaultInviteRepositoryInterface'
export class GetSharedVaultInvitesSentToUser implements UseCaseInterface<SharedVaultInvite[]> {
constructor(private sharedVaultInviteRepository: SharedVaultInviteRepositoryInterface) {}
async execute(dto: GetSharedVaultInvitesSentToUserDTO): Promise<Result<SharedVaultInvite[]>> {
const userUuidOrError = Uuid.create(dto.userUuid)
if (userUuidOrError.isFailed()) {
return Result.fail(userUuidOrError.getError())
}
const userUuid = userUuidOrError.getValue()
return Result.ok(await this.sharedVaultInviteRepository.findByUserUuid(userUuid))
}
}

View File

@@ -0,0 +1,3 @@
export interface GetSharedVaultInvitesSentToUserDTO {
userUuid: string
}

View File

@@ -0,0 +1,103 @@
import { Uuid, Timestamps } from '@standardnotes/domain-core'
import { SharedVault } from '../../SharedVault/SharedVault'
import { SharedVaultRepositoryInterface } from '../../SharedVault/SharedVaultRepositoryInterface'
import { SharedVaultUser } from '../../SharedVault/User/SharedVaultUser'
import { SharedVaultUserPermission } from '../../SharedVault/User/SharedVaultUserPermission'
import { SharedVaultUserRepositoryInterface } from '../../SharedVault/User/SharedVaultUserRepositoryInterface'
import { GetSharedVaultUsers } from './GetSharedVaultUsers'
describe('GetSharedVaultUsers', () => {
let sharedVault: SharedVault
let sharedVaultUser: SharedVaultUser
let sharedVaultUsersRepository: SharedVaultUserRepositoryInterface
let sharedVaultRepository: SharedVaultRepositoryInterface
const createUseCase = () => new GetSharedVaultUsers(sharedVaultUsersRepository, sharedVaultRepository)
beforeEach(() => {
sharedVault = SharedVault.create({
fileUploadBytesLimit: 100,
fileUploadBytesUsed: 2,
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
timestamps: Timestamps.create(123, 123).getValue(),
}).getValue()
sharedVaultUser = SharedVaultUser.create({
permission: SharedVaultUserPermission.create(SharedVaultUserPermission.PERMISSIONS.Read).getValue(),
sharedVaultUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
timestamps: Timestamps.create(123, 123).getValue(),
}).getValue()
sharedVaultRepository = {} as jest.Mocked<SharedVaultRepositoryInterface>
sharedVaultRepository.findByUuid = jest.fn().mockResolvedValue(sharedVault)
sharedVaultUsersRepository = {} as jest.Mocked<SharedVaultUserRepositoryInterface>
sharedVaultUsersRepository.findBySharedVaultUuid = jest.fn().mockResolvedValue([sharedVaultUser])
})
it('returns shared vault users', async () => {
const useCase = createUseCase()
const result = await useCase.execute({
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
originatorUuid: '00000000-0000-0000-0000-000000000000',
})
expect(result.isFailed()).toBe(false)
expect(result.getValue()).toEqual([sharedVaultUser])
})
it('returns error when shared vault is not found', async () => {
sharedVaultRepository.findByUuid = jest.fn().mockResolvedValue(null)
const useCase = createUseCase()
const result = await useCase.execute({
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
originatorUuid: '00000000-0000-0000-0000-000000000000',
})
expect(result.isFailed()).toBe(true)
expect(result.getError()).toBe('Shared vault not found')
})
it('returns error when originator is not the owner of the shared vault', async () => {
sharedVault = SharedVault.create({
fileUploadBytesLimit: 100,
fileUploadBytesUsed: 2,
userUuid: Uuid.create('00000000-0000-0000-0000-000000000001').getValue(),
timestamps: Timestamps.create(123, 123).getValue(),
}).getValue()
sharedVaultRepository.findByUuid = jest.fn().mockResolvedValue(sharedVault)
const useCase = createUseCase()
const result = await useCase.execute({
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
originatorUuid: '00000000-0000-0000-0000-000000000000',
})
expect(result.isFailed()).toBe(true)
expect(result.getError()).toBe('Only the owner can get shared vault users')
})
it('returns error when shared vault uuid is invalid', async () => {
const useCase = createUseCase()
const result = await useCase.execute({
sharedVaultUuid: 'invalid',
originatorUuid: '00000000-0000-0000-0000-000000000000',
})
expect(result.isFailed()).toBe(true)
expect(result.getError()).toBe('Given value is not a valid uuid: invalid')
})
it('returns error when originator uuid is invalid', async () => {
const useCase = createUseCase()
const result = await useCase.execute({
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
originatorUuid: 'invalid',
})
expect(result.isFailed()).toBe(true)
expect(result.getError()).toBe('Given value is not a valid uuid: invalid')
})
})

View File

@@ -0,0 +1,40 @@
import { Result, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
import { SharedVaultUser } from '../../SharedVault/User/SharedVaultUser'
import { GetSharedVaultUsersDTO } from './GetSharedVaultUsersDTO'
import { SharedVaultUserRepositoryInterface } from '../../SharedVault/User/SharedVaultUserRepositoryInterface'
import { SharedVaultRepositoryInterface } from '../../SharedVault/SharedVaultRepositoryInterface'
export class GetSharedVaultUsers implements UseCaseInterface<SharedVaultUser[]> {
constructor(
private sharedVaultUsersRepository: SharedVaultUserRepositoryInterface,
private sharedVaultRepository: SharedVaultRepositoryInterface,
) {}
async execute(dto: GetSharedVaultUsersDTO): Promise<Result<SharedVaultUser[]>> {
const sharedVaultUuidOrError = Uuid.create(dto.sharedVaultUuid)
if (sharedVaultUuidOrError.isFailed()) {
return Result.fail(sharedVaultUuidOrError.getError())
}
const sharedVaultUuid = sharedVaultUuidOrError.getValue()
const originatorUuidOrError = Uuid.create(dto.originatorUuid)
if (originatorUuidOrError.isFailed()) {
return Result.fail(originatorUuidOrError.getError())
}
const originatorUuid = originatorUuidOrError.getValue()
const sharedVault = await this.sharedVaultRepository.findByUuid(sharedVaultUuid)
if (!sharedVault) {
return Result.fail('Shared vault not found')
}
const isOriginatorTheOwnerOfTheSharedVault = sharedVault.props.userUuid.equals(originatorUuid)
if (!isOriginatorTheOwnerOfTheSharedVault) {
return Result.fail('Only the owner can get shared vault users')
}
const sharedVaultUsers = await this.sharedVaultUsersRepository.findBySharedVaultUuid(sharedVaultUuid)
return Result.ok(sharedVaultUsers)
}
}

View File

@@ -0,0 +1,4 @@
export interface GetSharedVaultUsersDTO {
sharedVaultUuid: string
originatorUuid: string
}

View File

@@ -0,0 +1,176 @@
import { Uuid, Timestamps, Result } from '@standardnotes/domain-core'
import { SharedVault } from '../../SharedVault/SharedVault'
import { SharedVaultRepositoryInterface } from '../../SharedVault/SharedVaultRepositoryInterface'
import { SharedVaultUser } from '../../SharedVault/User/SharedVaultUser'
import { SharedVaultUserPermission } from '../../SharedVault/User/SharedVaultUserPermission'
import { SharedVaultUserRepositoryInterface } from '../../SharedVault/User/SharedVaultUserRepositoryInterface'
import { RemoveUserFromSharedVault } from './RemoveUserFromSharedVault'
import { AddNotificationForUser } from '../AddNotificationForUser/AddNotificationForUser'
describe('RemoveUserFromSharedVault', () => {
let sharedVaultRepository: SharedVaultRepositoryInterface
let sharedVaultUserRepository: SharedVaultUserRepositoryInterface
let addNotificationForUser: AddNotificationForUser
let sharedVault: SharedVault
let sharedVaultUser: SharedVaultUser
const createUseCase = () =>
new RemoveUserFromSharedVault(sharedVaultUserRepository, sharedVaultRepository, addNotificationForUser)
beforeEach(() => {
sharedVault = SharedVault.create({
fileUploadBytesLimit: 100,
fileUploadBytesUsed: 2,
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
timestamps: Timestamps.create(123, 123).getValue(),
}).getValue()
sharedVaultRepository = {} as jest.Mocked<SharedVaultRepositoryInterface>
sharedVaultRepository.findByUuid = jest.fn().mockResolvedValue(sharedVault)
sharedVaultRepository.remove = jest.fn()
sharedVaultUser = SharedVaultUser.create({
permission: SharedVaultUserPermission.create(SharedVaultUserPermission.PERMISSIONS.Read).getValue(),
sharedVaultUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
timestamps: Timestamps.create(123, 123).getValue(),
}).getValue()
sharedVaultUserRepository = {} as jest.Mocked<SharedVaultUserRepositoryInterface>
sharedVaultUserRepository.findByUserUuidAndSharedVaultUuid = jest.fn().mockResolvedValue(sharedVaultUser)
sharedVaultUserRepository.remove = jest.fn()
addNotificationForUser = {} as jest.Mocked<AddNotificationForUser>
addNotificationForUser.execute = jest.fn().mockReturnValue(Result.ok())
})
it('should remove user from shared vault', async () => {
const useCase = createUseCase()
await useCase.execute({
originatorUuid: '00000000-0000-0000-0000-000000000000',
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
userUuid: '00000000-0000-0000-0000-000000000001',
})
expect(sharedVaultUserRepository.remove).toHaveBeenCalledWith(sharedVaultUser)
})
it('should return error when shared vault is not found', async () => {
sharedVaultRepository.findByUuid = jest.fn().mockResolvedValue(null)
const useCase = createUseCase()
const result = await useCase.execute({
originatorUuid: '00000000-0000-0000-0000-000000000000',
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
userUuid: '00000000-0000-0000-0000-000000000001',
})
expect(result.isFailed()).toBe(true)
expect(result.getError()).toBe('Shared vault not found')
})
it('should return error when shared vault user is not found', async () => {
sharedVaultUserRepository.findByUserUuidAndSharedVaultUuid = jest.fn().mockResolvedValue(null)
const useCase = createUseCase()
const result = await useCase.execute({
originatorUuid: '00000000-0000-0000-0000-000000000000',
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
userUuid: '00000000-0000-0000-0000-000000000001',
})
expect(result.isFailed()).toBe(true)
expect(result.getError()).toBe('User is not a member of the shared vault')
})
it('should return error when user is not owner of shared vault', async () => {
sharedVault = SharedVault.create({
fileUploadBytesLimit: 100,
fileUploadBytesUsed: 2,
userUuid: Uuid.create('00000000-0000-0000-0000-000000000002').getValue(),
timestamps: Timestamps.create(123, 123).getValue(),
}).getValue()
sharedVaultRepository.findByUuid = jest.fn().mockResolvedValue(sharedVault)
const useCase = createUseCase()
const result = await useCase.execute({
originatorUuid: '00000000-0000-0000-0000-000000000000',
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
userUuid: '00000000-0000-0000-0000-000000000001',
})
expect(result.isFailed()).toBe(true)
expect(result.getError()).toBe('Only owner can remove users from shared vault')
})
it('should return error when user is owner of shared vault', async () => {
const useCase = createUseCase()
const result = await useCase.execute({
originatorUuid: '00000000-0000-0000-0000-000000000000',
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
userUuid: '00000000-0000-0000-0000-000000000000',
})
expect(result.isFailed()).toBe(true)
expect(result.getError()).toBe('Owner cannot be removed from shared vault')
})
it('should return error if shared vault uuid is invalid', async () => {
const useCase = createUseCase()
const result = await useCase.execute({
originatorUuid: '00000000-0000-0000-0000-000000000000',
sharedVaultUuid: 'invalid',
userUuid: '00000000-0000-0000-0000-000000000001',
})
expect(result.isFailed()).toBe(true)
expect(result.getError()).toBe('Given value is not a valid uuid: invalid')
})
it('should return error if user uuid is invalid', async () => {
const useCase = createUseCase()
const result = await useCase.execute({
originatorUuid: '00000000-0000-0000-0000-000000000000',
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
userUuid: 'invalid',
})
expect(result.isFailed()).toBe(true)
expect(result.getError()).toBe('Given value is not a valid uuid: invalid')
})
it('should return error if originator uuid is invalid', async () => {
const useCase = createUseCase()
const result = await useCase.execute({
originatorUuid: 'invalid',
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
userUuid: '00000000-0000-0000-0000-000000000001',
})
expect(result.isFailed()).toBe(true)
expect(result.getError()).toBe('Given value is not a valid uuid: invalid')
})
it('should add notification for user', async () => {
const useCase = createUseCase()
await useCase.execute({
originatorUuid: '00000000-0000-0000-0000-000000000000',
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
userUuid: '00000000-0000-0000-0000-000000000001',
})
expect(addNotificationForUser.execute).toHaveBeenCalled()
})
it('should return error if notification could not be added', async () => {
addNotificationForUser.execute = jest.fn().mockResolvedValue(Result.fail('Could not add notification'))
const useCase = createUseCase()
const result = await useCase.execute({
originatorUuid: '00000000-0000-0000-0000-000000000000',
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
userUuid: '00000000-0000-0000-0000-000000000001',
})
expect(result.isFailed()).toBe(true)
})
})

View File

@@ -0,0 +1,74 @@
import { Result, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
import { RemoveUserFromSharedVaultDTO } from './RemoveUserFromSharedVaultDTO'
import { SharedVaultRepositoryInterface } from '../../SharedVault/SharedVaultRepositoryInterface'
import { SharedVaultUserRepositoryInterface } from '../../SharedVault/User/SharedVaultUserRepositoryInterface'
import { AddNotificationForUser } from '../AddNotificationForUser/AddNotificationForUser'
import { NotificationType } from '../../Notifications/NotificationType'
export class RemoveUserFromSharedVault implements UseCaseInterface<void> {
constructor(
private sharedVaultUsersRepository: SharedVaultUserRepositoryInterface,
private sharedVaultRepository: SharedVaultRepositoryInterface,
private addNotificationForUser: AddNotificationForUser,
) {}
async execute(dto: RemoveUserFromSharedVaultDTO): Promise<Result<void>> {
const sharedVaultUuidOrError = Uuid.create(dto.sharedVaultUuid)
if (sharedVaultUuidOrError.isFailed()) {
return Result.fail(sharedVaultUuidOrError.getError())
}
const sharedVaultUuid = sharedVaultUuidOrError.getValue()
const originatorUuidOrError = Uuid.create(dto.originatorUuid)
if (originatorUuidOrError.isFailed()) {
return Result.fail(originatorUuidOrError.getError())
}
const originatorUuid = originatorUuidOrError.getValue()
const userUuidOrError = Uuid.create(dto.userUuid)
if (userUuidOrError.isFailed()) {
return Result.fail(userUuidOrError.getError())
}
const userUuid = userUuidOrError.getValue()
const sharedVault = await this.sharedVaultRepository.findByUuid(sharedVaultUuid)
if (!sharedVault) {
return Result.fail('Shared vault not found')
}
const originatorIsOwner = sharedVault.props.userUuid.equals(originatorUuid)
if (!originatorIsOwner) {
return Result.fail('Only owner can remove users from shared vault')
}
const removingOwner = sharedVault.props.userUuid.equals(userUuid)
if (removingOwner) {
return Result.fail('Owner cannot be removed from shared vault')
}
const sharedVaultUser = await this.sharedVaultUsersRepository.findByUserUuidAndSharedVaultUuid({
userUuid,
sharedVaultUuid,
})
if (!sharedVaultUser) {
return Result.fail('User is not a member of the shared vault')
}
await this.sharedVaultUsersRepository.remove(sharedVaultUser)
const result = await this.addNotificationForUser.execute({
userUuid: sharedVaultUser.props.userUuid.value,
type: NotificationType.TYPES.RemovedFromSharedVault,
payload: JSON.stringify({
sharedVaultUuid: sharedVault.id.toString(),
}),
version: '1.0',
})
if (result.isFailed()) {
return Result.fail(result.getError())
}
return Result.ok()
}
}

View File

@@ -0,0 +1,5 @@
export interface RemoveUserFromSharedVaultDTO {
sharedVaultUuid: string
originatorUuid: string
userUuid: string
}

View File

@@ -0,0 +1,141 @@
import { TimerInterface } from '@standardnotes/time'
import { SharedVaultInviteRepositoryInterface } from '../../SharedVault/User/Invite/SharedVaultInviteRepositoryInterface'
import { UpdateSharedVaultInvite } from './UpdateSharedVaultInvite'
import { SharedVaultInvite } from '../../SharedVault/User/Invite/SharedVaultInvite'
import { Timestamps, Uuid } from '@standardnotes/domain-core'
import { SharedVaultUserPermission } from '../../SharedVault/User/SharedVaultUserPermission'
describe('UpdateSharedVaultInvite', () => {
let sharedVaultInviteRepository: SharedVaultInviteRepositoryInterface
let timer: TimerInterface
let invite: SharedVaultInvite
const createUseCase = () => new UpdateSharedVaultInvite(sharedVaultInviteRepository, timer)
beforeEach(() => {
invite = SharedVaultInvite.create({
sharedVaultUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
senderUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
encryptedMessage: 'encrypted message',
permission: SharedVaultUserPermission.create(SharedVaultUserPermission.PERMISSIONS.Read).getValue(),
timestamps: Timestamps.create(123, 123).getValue(),
}).getValue()
sharedVaultInviteRepository = {} as jest.Mocked<SharedVaultInviteRepositoryInterface>
sharedVaultInviteRepository.findByUuid = jest.fn().mockResolvedValue(invite)
sharedVaultInviteRepository.save = jest.fn().mockResolvedValue(null)
timer = {} as jest.Mocked<TimerInterface>
timer.getTimestampInMicroseconds = jest.fn().mockReturnValue(123)
})
it('should update the invite', async () => {
const useCase = createUseCase()
const result = await useCase.execute({
inviteUuid: '00000000-0000-0000-0000-000000000000',
senderUuid: '00000000-0000-0000-0000-000000000000',
encryptedMessage: 'new encrypted message',
})
expect(result.isFailed()).toBe(false)
expect(sharedVaultInviteRepository.save).toHaveBeenCalled()
})
it('should update the invite with new permission', async () => {
const useCase = createUseCase()
const result = await useCase.execute({
inviteUuid: '00000000-0000-0000-0000-000000000000',
senderUuid: '00000000-0000-0000-0000-000000000000',
encryptedMessage: 'new encrypted message',
permission: SharedVaultUserPermission.PERMISSIONS.Write,
})
expect(result.isFailed()).toBe(false)
expect(sharedVaultInviteRepository.save).toHaveBeenCalled()
})
it('should fail if invite is not found', async () => {
sharedVaultInviteRepository.findByUuid = jest.fn().mockResolvedValue(null)
const useCase = createUseCase()
const result = await useCase.execute({
inviteUuid: '00000000-0000-0000-0000-000000000000',
senderUuid: '00000000-0000-0000-0000-000000000000',
encryptedMessage: 'new encrypted message',
})
expect(result.isFailed()).toBe(true)
expect(sharedVaultInviteRepository.save).not.toHaveBeenCalled()
})
it('should fail if sender is not the same as the invite sender', async () => {
const useCase = createUseCase()
const result = await useCase.execute({
inviteUuid: '00000000-0000-0000-0000-000000000000',
senderUuid: '00000000-0000-0000-0000-000000000001',
encryptedMessage: 'new encrypted message',
})
expect(result.isFailed()).toBe(true)
expect(result.getError()).toBe('Only the sender can update the invite')
expect(sharedVaultInviteRepository.save).not.toHaveBeenCalled()
})
it('should fail if the invite uuid is not valid', async () => {
const useCase = createUseCase()
const result = await useCase.execute({
inviteUuid: 'invalid-uuid',
senderUuid: '00000000-0000-0000-0000-000000000000',
encryptedMessage: 'new encrypted message',
})
expect(result.isFailed()).toBe(true)
expect(sharedVaultInviteRepository.save).not.toHaveBeenCalled()
})
it('should fail if the sender uuid is not valid', async () => {
const useCase = createUseCase()
const result = await useCase.execute({
inviteUuid: '00000000-0000-0000-0000-000000000000',
senderUuid: 'invalid-uuid',
encryptedMessage: 'new encrypted message',
})
expect(result.isFailed()).toBe(true)
expect(sharedVaultInviteRepository.save).not.toHaveBeenCalled()
})
it('should fail if the encrypted message is not valid', async () => {
const useCase = createUseCase()
const result = await useCase.execute({
inviteUuid: '00000000-0000-0000-0000-000000000000',
senderUuid: '00000000-0000-0000-0000-000000000000',
encryptedMessage: '',
})
expect(result.isFailed()).toBe(true)
expect(sharedVaultInviteRepository.save).not.toHaveBeenCalled()
})
it('should fail if the permission is not valid', async () => {
const useCase = createUseCase()
const result = await useCase.execute({
inviteUuid: '00000000-0000-0000-0000-000000000000',
senderUuid: '00000000-0000-0000-0000-000000000000',
encryptedMessage: 'new encrypted message',
permission: 'invalid-permission',
})
expect(result.isFailed()).toBe(true)
expect(sharedVaultInviteRepository.save).not.toHaveBeenCalled()
})
})

View File

@@ -0,0 +1,63 @@
import { Result, Timestamps, UseCaseInterface, Uuid, Validator } from '@standardnotes/domain-core'
import { SharedVaultInviteRepositoryInterface } from '../../SharedVault/User/Invite/SharedVaultInviteRepositoryInterface'
import { UpdateSharedVaultInviteDTO } from './UpdateSharedVaultInviteDTO'
import { SharedVaultUserPermission } from '../../SharedVault/User/SharedVaultUserPermission'
import { TimerInterface } from '@standardnotes/time'
import { SharedVaultInvite } from '../../SharedVault/User/Invite/SharedVaultInvite'
export class UpdateSharedVaultInvite implements UseCaseInterface<SharedVaultInvite> {
constructor(
private sharedVaultInviteRepository: SharedVaultInviteRepositoryInterface,
private timer: TimerInterface,
) {}
async execute(dto: UpdateSharedVaultInviteDTO): Promise<Result<SharedVaultInvite>> {
const inviteUuidOrError = Uuid.create(dto.inviteUuid)
if (inviteUuidOrError.isFailed()) {
return Result.fail(inviteUuidOrError.getError())
}
const inviteUuid = inviteUuidOrError.getValue()
const senderUuidOrError = Uuid.create(dto.senderUuid)
if (senderUuidOrError.isFailed()) {
return Result.fail(senderUuidOrError.getError())
}
const senderUuid = senderUuidOrError.getValue()
const emptyMessageValidation = Validator.isNotEmpty(dto.encryptedMessage)
if (emptyMessageValidation.isFailed()) {
return Result.fail(emptyMessageValidation.getError())
}
const invite = await this.sharedVaultInviteRepository.findByUuid(inviteUuid)
if (!invite) {
return Result.fail('Invite not found')
}
if (!invite.props.senderUuid.equals(senderUuid)) {
return Result.fail('Only the sender can update the invite')
}
invite.props.encryptedMessage = dto.encryptedMessage
if (dto.permission !== undefined) {
const permissionOrError = SharedVaultUserPermission.create(dto.permission)
if (permissionOrError.isFailed()) {
return Result.fail(permissionOrError.getError())
}
const permission = permissionOrError.getValue()
invite.props.permission = permission
}
invite.props.timestamps = Timestamps.create(
invite.props.timestamps.createdAt,
this.timer.getTimestampInMicroseconds(),
).getValue()
await this.sharedVaultInviteRepository.save(invite)
return Result.ok(invite)
}
}

View File

@@ -0,0 +1,6 @@
export interface UpdateSharedVaultInviteDTO {
encryptedMessage: string
inviteUuid: string
senderUuid: string
permission?: string
}

View File

@@ -0,0 +1,278 @@
import { Request, Response } from 'express'
import { BaseHttpController, results } from 'inversify-express-utils'
import { HttpStatusCode } from '@standardnotes/responses'
import { ControllerContainerInterface, MapperInterface } from '@standardnotes/domain-core'
import { InviteUserToSharedVault } from '../../../Domain/UseCase/InviteUserToSharedVault/InviteUserToSharedVault'
import { SharedVaultInvite } from '../../../Domain/SharedVault/User/Invite/SharedVaultInvite'
import { SharedVaultInviteHttpRepresentation } from '../../../Mapping/Http/SharedVaultInviteHttpRepresentation'
import { UpdateSharedVaultInvite } from '../../../Domain/UseCase/UpdateSharedVaultInvite/UpdateSharedVaultInvite'
import { AcceptInviteToSharedVault } from '../../../Domain/UseCase/AcceptInviteToSharedVault/AcceptInviteToSharedVault'
import { DeclineInviteToSharedVault } from '../../../Domain/UseCase/DeclineInviteToSharedVault/DeclineInviteToSharedVault'
import { DeleteSharedVaultInvitesToUser } from '../../../Domain/UseCase/DeleteSharedVaultInvitesToUser/DeleteSharedVaultInvitesToUser'
import { GetSharedVaultInvitesSentByUser } from '../../../Domain/UseCase/GetSharedVaultInvitesSentByUser/GetSharedVaultInvitesSentByUser'
import { DeleteSharedVaultInvitesSentByUser } from '../../../Domain/UseCase/DeleteSharedVaultInvitesSentByUser/DeleteSharedVaultInvitesSentByUser'
import { GetSharedVaultInvitesSentToUser } from '../../../Domain/UseCase/GetSharedVaultInvitesSentToUser/GetSharedVaultInvitesSentToUser'
export class HomeServerSharedVaultInvitesController extends BaseHttpController {
constructor(
protected inviteUserToSharedVaultUseCase: InviteUserToSharedVault,
protected updateSharedVaultInviteUseCase: UpdateSharedVaultInvite,
protected acceptSharedVaultInviteUseCase: AcceptInviteToSharedVault,
protected declineSharedVaultInviteUseCase: DeclineInviteToSharedVault,
protected deleteSharedVaultInvitesToUserUseCase: DeleteSharedVaultInvitesToUser,
protected deleteSharedVaultInvitesSentByUserUseCase: DeleteSharedVaultInvitesSentByUser,
protected getSharedVaultInvitesSentByUserUseCase: GetSharedVaultInvitesSentByUser,
protected getSharedVaultInvitesSentToUserUseCase: GetSharedVaultInvitesSentToUser,
protected sharedVaultInviteHttpMapper: MapperInterface<SharedVaultInvite, SharedVaultInviteHttpRepresentation>,
private controllerContainer?: ControllerContainerInterface,
) {
super()
if (this.controllerContainer !== undefined) {
this.controllerContainer.register('sync.shared-vault-invites.create', this.createSharedVaultInvite.bind(this))
this.controllerContainer.register('sync.shared-vault-invites.update', this.updateSharedVaultInvite.bind(this))
this.controllerContainer.register('sync.shared-vault-invites.accept', this.acceptSharedVaultInvite.bind(this))
this.controllerContainer.register('sync.shared-vault-invites.decline', this.declineSharedVaultInvite.bind(this))
this.controllerContainer.register(
'sync.shared-vault-invites.delete-inbound',
this.deleteInboundUserInvites.bind(this),
)
this.controllerContainer.register(
'sync.shared-vault-invites.get-outbound',
this.getOutboundUserInvites.bind(this),
)
this.controllerContainer.register('sync.shared-vault-invites.get-user-invites', this.getUserInvites.bind(this))
this.controllerContainer.register(
'sync.shared-vault-invites.delete-invite',
this.deleteSharedVaultInvite.bind(this),
)
this.controllerContainer.register(
'sync.shared-vault-invites.delete-all',
this.deleteAllSharedVaultInvites.bind(this),
)
}
}
async createSharedVaultInvite(request: Request, response: Response): Promise<results.JsonResult> {
const result = await this.inviteUserToSharedVaultUseCase.execute({
sharedVaultUuid: request.params.sharedVaultUuid,
senderUuid: response.locals.user.uuid,
recipientUuid: request.body.recipient_uid,
encryptedMessage: request.body.encrypted_message,
permission: request.body.permission,
})
if (result.isFailed()) {
return this.json(
{
error: {
message: result.getError(),
},
},
HttpStatusCode.BadRequest,
)
}
return this.json({
invite: this.sharedVaultInviteHttpMapper.toProjection(result.getValue()),
})
}
async updateSharedVaultInvite(request: Request, response: Response): Promise<results.JsonResult> {
const result = await this.updateSharedVaultInviteUseCase.execute({
encryptedMessage: request.body.encrypted_message,
inviteUuid: request.params.inviteUuid,
senderUuid: response.locals.user.uuid,
permission: request.body.permission,
})
if (result.isFailed()) {
return this.json(
{
error: {
message: result.getError(),
},
},
HttpStatusCode.BadRequest,
)
}
return this.json({
invite: this.sharedVaultInviteHttpMapper.toProjection(result.getValue()),
})
}
async acceptSharedVaultInvite(request: Request, response: Response): Promise<results.JsonResult> {
const result = await this.acceptSharedVaultInviteUseCase.execute({
inviteUuid: request.params.inviteUuid,
originatorUuid: response.locals.user.uuid,
})
if (result.isFailed()) {
return this.json(
{
error: {
message: result.getError(),
},
},
HttpStatusCode.BadRequest,
)
}
return this.json({
success: true,
})
}
async declineSharedVaultInvite(request: Request, response: Response): Promise<results.JsonResult> {
const result = await this.declineSharedVaultInviteUseCase.execute({
inviteUuid: request.params.inviteUuid,
originatorUuid: response.locals.user.uuid,
})
if (result.isFailed()) {
return this.json(
{
error: {
message: result.getError(),
},
},
HttpStatusCode.BadRequest,
)
}
return this.json({
success: true,
})
}
async deleteInboundUserInvites(_request: Request, response: Response): Promise<results.JsonResult> {
const result = await this.deleteSharedVaultInvitesToUserUseCase.execute({
userUuid: response.locals.user.uuid,
})
if (result.isFailed()) {
return this.json(
{
error: {
message: result.getError(),
},
},
HttpStatusCode.BadRequest,
)
}
return this.json({
success: true,
})
}
async getOutboundUserInvites(_request: Request, response: Response): Promise<results.JsonResult> {
const result = await this.getSharedVaultInvitesSentByUserUseCase.execute({
senderUuid: response.locals.user.uuid,
})
if (result.isFailed()) {
return this.json(
{
error: {
message: result.getError(),
},
},
HttpStatusCode.BadRequest,
)
}
return this.json({
invites: result.getValue().map((invite) => this.sharedVaultInviteHttpMapper.toProjection(invite)),
})
}
async getSharedVaultInvites(request: Request, response: Response): Promise<results.JsonResult> {
const result = await this.getSharedVaultInvitesSentByUserUseCase.execute({
senderUuid: response.locals.user.uuid,
sharedVaultUuid: request.params.sharedVaultUuid,
})
if (result.isFailed()) {
return this.json(
{
error: {
message: result.getError(),
},
},
HttpStatusCode.BadRequest,
)
}
return this.json({
invites: result.getValue().map((invite) => this.sharedVaultInviteHttpMapper.toProjection(invite)),
})
}
async getUserInvites(_request: Request, response: Response): Promise<results.JsonResult> {
const result = await this.getSharedVaultInvitesSentToUserUseCase.execute({
userUuid: response.locals.user.uuid,
})
if (result.isFailed()) {
return this.json(
{
error: {
message: result.getError(),
},
},
HttpStatusCode.BadRequest,
)
}
return this.json({
invites: result.getValue().map((invite) => this.sharedVaultInviteHttpMapper.toProjection(invite)),
})
}
async deleteSharedVaultInvite(request: Request, response: Response): Promise<results.JsonResult> {
const result = await this.declineSharedVaultInviteUseCase.execute({
inviteUuid: request.params.inviteUuid,
originatorUuid: response.locals.user.uuid,
})
if (result.isFailed()) {
return this.json(
{
error: {
message: result.getError(),
},
},
HttpStatusCode.BadRequest,
)
}
return this.json({
success: true,
})
}
async deleteAllSharedVaultInvites(request: Request, response: Response): Promise<results.JsonResult> {
const result = await this.deleteSharedVaultInvitesSentByUserUseCase.execute({
userUuid: response.locals.user.uuid,
sharedVaultUuid: request.params.sharedVaultUuid,
})
if (result.isFailed()) {
return this.json(
{
error: {
message: result.getError(),
},
},
HttpStatusCode.BadRequest,
)
}
return this.json({
success: true,
})
}
}

View File

@@ -0,0 +1,73 @@
import { Request, Response } from 'express'
import { BaseHttpController, results } from 'inversify-express-utils'
import { HttpStatusCode } from '@standardnotes/responses'
import { ControllerContainerInterface, MapperInterface } from '@standardnotes/domain-core'
import { SharedVaultUser } from '../../../Domain/SharedVault/User/SharedVaultUser'
import { SharedVaultUserHttpRepresentation } from '../../../Mapping/Http/SharedVaultUserHttpRepresentation'
import { GetSharedVaultUsers } from '../../../Domain/UseCase/GetSharedVaultUsers/GetSharedVaultUsers'
import { RemoveUserFromSharedVault } from '../../../Domain/UseCase/RemoveUserFromSharedVault/RemoveUserFromSharedVault'
export class HomeServerSharedVaultUsersController extends BaseHttpController {
constructor(
protected getSharedVaultUsersUseCase: GetSharedVaultUsers,
protected removeUserFromSharedVaultUseCase: RemoveUserFromSharedVault,
protected sharedVaultUserHttpMapper: MapperInterface<SharedVaultUser, SharedVaultUserHttpRepresentation>,
private controllerContainer?: ControllerContainerInterface,
) {
super()
if (this.controllerContainer !== undefined) {
this.controllerContainer.register('sync.shared-vault-users.get-users', this.getSharedVaultUsers.bind(this))
this.controllerContainer.register(
'sync.shared-vault-users.remove-user',
this.removeUserFromSharedVault.bind(this),
)
}
}
async getSharedVaultUsers(request: Request, response: Response): Promise<results.JsonResult> {
const result = await this.getSharedVaultUsersUseCase.execute({
originatorUuid: response.locals.user.uuid,
sharedVaultUuid: request.params.sharedVaultUuid,
})
if (result.isFailed()) {
return this.json(
{
error: {
message: result.getError(),
},
},
HttpStatusCode.BadRequest,
)
}
return this.json({
users: result.getValue().map((sharedVault) => this.sharedVaultUserHttpMapper.toProjection(sharedVault)),
})
}
async removeUserFromSharedVault(request: Request, response: Response): Promise<results.JsonResult> {
const result = await this.removeUserFromSharedVaultUseCase.execute({
sharedVaultUuid: request.params.sharedVaultUuid,
userUuid: request.params.userUuid,
originatorUuid: response.locals.user.uuid,
})
if (result.isFailed()) {
return this.json(
{
error: {
message: result.getError(),
},
},
HttpStatusCode.BadRequest,
)
}
return this.json({
success: true,
})
}
}

View File

@@ -0,0 +1,128 @@
import { Request, Response } from 'express'
import { BaseHttpController, results } from 'inversify-express-utils'
import { HttpStatusCode } from '@standardnotes/responses'
import { ControllerContainerInterface, MapperInterface } from '@standardnotes/domain-core'
import { GetSharedVaults } from '../../../Domain/UseCase/GetSharedVaults/GetSharedVaults'
import { SharedVault } from '../../../Domain/SharedVault/SharedVault'
import { SharedVaultHttpRepresentation } from '../../../Mapping/Http/SharedVaultHttpRepresentation'
import { CreateSharedVault } from '../../../Domain/UseCase/CreateSharedVault/CreateSharedVault'
import { SharedVaultUser } from '../../../Domain/SharedVault/User/SharedVaultUser'
import { SharedVaultUserHttpRepresentation } from '../../../Mapping/Http/SharedVaultUserHttpRepresentation'
import { DeleteSharedVault } from '../../../Domain/UseCase/DeleteSharedVault/DeleteSharedVault'
import { CreateSharedVaultFileValetToken } from '../../../Domain/UseCase/CreateSharedVaultFileValetToken/CreateSharedVaultFileValetToken'
export class HomeServerSharedVaultsController extends BaseHttpController {
constructor(
protected getSharedVaultsUseCase: GetSharedVaults,
protected createSharedVaultUseCase: CreateSharedVault,
protected deleteSharedVaultUseCase: DeleteSharedVault,
protected createSharedVaultFileValetTokenUseCase: CreateSharedVaultFileValetToken,
protected sharedVaultHttpMapper: MapperInterface<SharedVault, SharedVaultHttpRepresentation>,
protected sharedVaultUserHttpMapper: MapperInterface<SharedVaultUser, SharedVaultUserHttpRepresentation>,
private controllerContainer?: ControllerContainerInterface,
) {
super()
if (this.controllerContainer !== undefined) {
this.controllerContainer.register('sync.shared-vaults.get-vaults', this.getSharedVaults.bind(this))
this.controllerContainer.register('sync.shared-vaults.create-vault', this.createSharedVault.bind(this))
this.controllerContainer.register('sync.shared-vaults.delete-vault', this.deleteSharedVault.bind(this))
this.controllerContainer.register(
'sync.shared-vaults.create-file-valet-token',
this.createValetTokenForSharedVaultFile.bind(this),
)
}
}
async getSharedVaults(_request: Request, response: Response): Promise<results.JsonResult> {
const result = await this.getSharedVaultsUseCase.execute({
userUuid: response.locals.user.uuid,
})
if (result.isFailed()) {
return this.json(
{
error: {
message: result.getError(),
},
},
HttpStatusCode.BadRequest,
)
}
return this.json({
sharedVaults: result.getValue().map((sharedVault) => this.sharedVaultHttpMapper.toProjection(sharedVault)),
})
}
async createSharedVault(_request: Request, response: Response): Promise<results.JsonResult> {
const result = await this.createSharedVaultUseCase.execute({
userUuid: response.locals.user.uuid,
})
if (result.isFailed()) {
return this.json(
{
error: {
message: result.getError(),
},
},
HttpStatusCode.BadRequest,
)
}
return this.json({
sharedVault: this.sharedVaultHttpMapper.toProjection(result.getValue().sharedVault),
sharedVaultUser: this.sharedVaultUserHttpMapper.toProjection(result.getValue().sharedVaultUser),
})
}
async deleteSharedVault(request: Request, response: Response): Promise<results.JsonResult> {
const result = await this.deleteSharedVaultUseCase.execute({
sharedVaultUuid: request.params.sharedVaultUuid,
originatorUuid: response.locals.user.uuid,
})
if (result.isFailed()) {
return this.json(
{
error: {
message: result.getError(),
},
},
HttpStatusCode.BadRequest,
)
}
return this.json({ success: true })
}
async createValetTokenForSharedVaultFile(request: Request, response: Response): Promise<results.JsonResult> {
const result = await this.createSharedVaultFileValetTokenUseCase.execute({
userUuid: response.locals.user.uuid,
sharedVaultUuid: request.params.sharedVaultUuid,
fileUuid: request.body.file_uuid,
remoteIdentifier: request.body.remote_identifier,
operation: request.body.operation,
unencryptedFileSize: request.body.unencrypted_file_size,
moveOperationType: request.body.move_operation_type,
sharedVaultToSharedVaultMoveTargetUuid: request.body.shared_vault_to_shared_vault_move_target_uuid,
})
if (result.isFailed()) {
return this.json(
{
error: {
message: result.getError(),
},
},
HttpStatusCode.BadRequest,
)
}
return this.json({
valetToken: result.getValue(),
})
}
}

View File

@@ -0,0 +1,99 @@
import { controller, httpDelete, httpGet, httpPatch, httpPost, results } from 'inversify-express-utils'
import { MapperInterface } from '@standardnotes/domain-core'
import { Request, Response } from 'express'
import TYPES from '../../Bootstrap/Types'
import { SharedVaultInvite } from '../../Domain/SharedVault/User/Invite/SharedVaultInvite'
import { AcceptInviteToSharedVault } from '../../Domain/UseCase/AcceptInviteToSharedVault/AcceptInviteToSharedVault'
import { DeclineInviteToSharedVault } from '../../Domain/UseCase/DeclineInviteToSharedVault/DeclineInviteToSharedVault'
import { DeleteSharedVaultInvitesSentByUser } from '../../Domain/UseCase/DeleteSharedVaultInvitesSentByUser/DeleteSharedVaultInvitesSentByUser'
import { DeleteSharedVaultInvitesToUser } from '../../Domain/UseCase/DeleteSharedVaultInvitesToUser/DeleteSharedVaultInvitesToUser'
import { GetSharedVaultInvitesSentByUser } from '../../Domain/UseCase/GetSharedVaultInvitesSentByUser/GetSharedVaultInvitesSentByUser'
import { InviteUserToSharedVault } from '../../Domain/UseCase/InviteUserToSharedVault/InviteUserToSharedVault'
import { UpdateSharedVaultInvite } from '../../Domain/UseCase/UpdateSharedVaultInvite/UpdateSharedVaultInvite'
import { SharedVaultInviteHttpRepresentation } from '../../Mapping/Http/SharedVaultInviteHttpRepresentation'
import { HomeServerSharedVaultInvitesController } from './HomeServer/HomeServerSharedVaultInvitesController'
import { GetSharedVaultInvitesSentToUser } from '../../Domain/UseCase/GetSharedVaultInvitesSentToUser/GetSharedVaultInvitesSentToUser'
import { inject } from 'inversify'
@controller('/shared-vaults', TYPES.Sync_AuthMiddleware)
export class InversifyExpressSharedVaultInvitesController extends HomeServerSharedVaultInvitesController {
constructor(
@inject(TYPES.Sync_InviteUserToSharedVault) override inviteUserToSharedVaultUseCase: InviteUserToSharedVault,
@inject(TYPES.Sync_UpdateSharedVaultInvite) override updateSharedVaultInviteUseCase: UpdateSharedVaultInvite,
@inject(TYPES.Sync_AcceptInviteToSharedVault) override acceptSharedVaultInviteUseCase: AcceptInviteToSharedVault,
@inject(TYPES.Sync_DeclineInviteToSharedVault) override declineSharedVaultInviteUseCase: DeclineInviteToSharedVault,
@inject(TYPES.Sync_DeleteSharedVaultInvitesToUser)
override deleteSharedVaultInvitesToUserUseCase: DeleteSharedVaultInvitesToUser,
@inject(TYPES.Sync_DeleteSharedVaultInvitesSentByUser)
override deleteSharedVaultInvitesSentByUserUseCase: DeleteSharedVaultInvitesSentByUser,
@inject(TYPES.Sync_GetSharedVaultInvitesSentByUser)
override getSharedVaultInvitesSentByUserUseCase: GetSharedVaultInvitesSentByUser,
@inject(TYPES.Sync_GetSharedVaultInvitesSentToUser)
override getSharedVaultInvitesSentToUserUseCase: GetSharedVaultInvitesSentToUser,
@inject(TYPES.Sync_SharedVaultInviteHttpMapper)
override sharedVaultInviteHttpMapper: MapperInterface<SharedVaultInvite, SharedVaultInviteHttpRepresentation>,
) {
super(
inviteUserToSharedVaultUseCase,
updateSharedVaultInviteUseCase,
acceptSharedVaultInviteUseCase,
declineSharedVaultInviteUseCase,
deleteSharedVaultInvitesToUserUseCase,
deleteSharedVaultInvitesSentByUserUseCase,
getSharedVaultInvitesSentByUserUseCase,
getSharedVaultInvitesSentToUserUseCase,
sharedVaultInviteHttpMapper,
)
}
@httpPost('/:sharedVaultUuid/invites')
override async createSharedVaultInvite(request: Request, response: Response): Promise<results.JsonResult> {
return super.createSharedVaultInvite(request, response)
}
@httpPatch('/:sharedVaultUuid/invites/:inviteUuid')
override async updateSharedVaultInvite(request: Request, response: Response): Promise<results.JsonResult> {
return super.updateSharedVaultInvite(request, response)
}
@httpPost('/:sharedVaultUuid/invites/:inviteUuid/accept')
override async acceptSharedVaultInvite(request: Request, response: Response): Promise<results.JsonResult> {
return super.acceptSharedVaultInvite(request, response)
}
@httpPost('/:sharedVaultUuid/invites/:inviteUuid/decline')
override async declineSharedVaultInvite(request: Request, response: Response): Promise<results.JsonResult> {
return super.declineSharedVaultInvite(request, response)
}
@httpDelete('/invites/inbound')
override async deleteInboundUserInvites(request: Request, response: Response): Promise<results.JsonResult> {
return super.deleteInboundUserInvites(request, response)
}
@httpGet('/invites/outbound')
override async getOutboundUserInvites(request: Request, response: Response): Promise<results.JsonResult> {
return super.getOutboundUserInvites(request, response)
}
@httpGet('/invites')
override async getUserInvites(request: Request, response: Response): Promise<results.JsonResult> {
return super.getUserInvites(request, response)
}
@httpGet('/:sharedVaultUuid/invites')
override async getSharedVaultInvites(request: Request, response: Response): Promise<results.JsonResult> {
return super.getSharedVaultInvites(request, response)
}
@httpDelete('/:sharedVaultUuid/invites/:inviteUuid')
override async deleteSharedVaultInvite(request: Request, response: Response): Promise<results.JsonResult> {
return super.deleteSharedVaultInvite(request, response)
}
@httpDelete('/:sharedVaultUuid/invites')
override async deleteAllSharedVaultInvites(request: Request, response: Response): Promise<results.JsonResult> {
return super.deleteAllSharedVaultInvites(request, response)
}
}

View File

@@ -0,0 +1,33 @@
import { controller, httpDelete, httpGet, results } from 'inversify-express-utils'
import { inject } from 'inversify'
import { MapperInterface } from '@standardnotes/domain-core'
import { Request, Response } from 'express'
import { HomeServerSharedVaultUsersController } from './HomeServer/HomeServerSharedVaultUsersController'
import TYPES from '../../Bootstrap/Types'
import { SharedVaultUser } from '../../Domain/SharedVault/User/SharedVaultUser'
import { SharedVaultUserHttpRepresentation } from '../../Mapping/Http/SharedVaultUserHttpRepresentation'
import { GetSharedVaultUsers } from '../../Domain/UseCase/GetSharedVaultUsers/GetSharedVaultUsers'
import { RemoveUserFromSharedVault } from '../../Domain/UseCase/RemoveUserFromSharedVault/RemoveUserFromSharedVault'
@controller('/shared-vaults/:sharedVaultUuid/users', TYPES.Sync_AuthMiddleware)
export class InversifyExpressSharedVaultUsersController extends HomeServerSharedVaultUsersController {
constructor(
@inject(TYPES.Sync_GetSharedVaultUsers) override getSharedVaultUsersUseCase: GetSharedVaultUsers,
@inject(TYPES.Sync_RemoveSharedVaultUser) override removeUserFromSharedVaultUseCase: RemoveUserFromSharedVault,
@inject(TYPES.Sync_SharedVaultUserHttpMapper)
override sharedVaultUserHttpMapper: MapperInterface<SharedVaultUser, SharedVaultUserHttpRepresentation>,
) {
super(getSharedVaultUsersUseCase, removeUserFromSharedVaultUseCase, sharedVaultUserHttpMapper)
}
@httpGet('/')
override async getSharedVaultUsers(request: Request, response: Response): Promise<results.JsonResult> {
return super.getSharedVaultUsers(request, response)
}
@httpDelete('/:userUuid')
override async removeUserFromSharedVault(request: Request, response: Response): Promise<results.JsonResult> {
return super.removeUserFromSharedVault(request, response)
}
}

View File

@@ -0,0 +1,59 @@
import { controller, httpDelete, httpGet, httpPost, results } from 'inversify-express-utils'
import { inject } from 'inversify'
import { MapperInterface } from '@standardnotes/domain-core'
import { Request, Response } from 'express'
import { HomeServerSharedVaultsController } from './HomeServer/HomeServerSharedVaultsController'
import TYPES from '../../Bootstrap/Types'
import { SharedVault } from '../../Domain/SharedVault/SharedVault'
import { SharedVaultUser } from '../../Domain/SharedVault/User/SharedVaultUser'
import { CreateSharedVault } from '../../Domain/UseCase/CreateSharedVault/CreateSharedVault'
import { CreateSharedVaultFileValetToken } from '../../Domain/UseCase/CreateSharedVaultFileValetToken/CreateSharedVaultFileValetToken'
import { DeleteSharedVault } from '../../Domain/UseCase/DeleteSharedVault/DeleteSharedVault'
import { GetSharedVaults } from '../../Domain/UseCase/GetSharedVaults/GetSharedVaults'
import { SharedVaultHttpRepresentation } from '../../Mapping/Http/SharedVaultHttpRepresentation'
import { SharedVaultUserHttpRepresentation } from '../../Mapping/Http/SharedVaultUserHttpRepresentation'
@controller('/shared-vaults', TYPES.Sync_AuthMiddleware)
export class InversifyExpressSharedVaultsController extends HomeServerSharedVaultsController {
constructor(
@inject(TYPES.Sync_GetSharedVaults) override getSharedVaultsUseCase: GetSharedVaults,
@inject(TYPES.Sync_CreateSharedVault) override createSharedVaultUseCase: CreateSharedVault,
@inject(TYPES.Sync_DeleteSharedVault) override deleteSharedVaultUseCase: DeleteSharedVault,
@inject(TYPES.Sync_CreateSharedVaultFileValetToken)
override createSharedVaultFileValetTokenUseCase: CreateSharedVaultFileValetToken,
@inject(TYPES.Sync_SharedVaultHttpMapper)
override sharedVaultHttpMapper: MapperInterface<SharedVault, SharedVaultHttpRepresentation>,
@inject(TYPES.Sync_SharedVaultUserHttpMapper)
override sharedVaultUserHttpMapper: MapperInterface<SharedVaultUser, SharedVaultUserHttpRepresentation>,
) {
super(
getSharedVaultsUseCase,
createSharedVaultUseCase,
deleteSharedVaultUseCase,
createSharedVaultFileValetTokenUseCase,
sharedVaultHttpMapper,
sharedVaultUserHttpMapper,
)
}
@httpGet('/')
override async getSharedVaults(request: Request, response: Response): Promise<results.JsonResult> {
return super.getSharedVaults(request, response)
}
@httpPost('/')
override async createSharedVault(request: Request, response: Response): Promise<results.JsonResult> {
return super.createSharedVault(request, response)
}
@httpDelete('/:sharedVaultUuid')
override async deleteSharedVault(request: Request, response: Response): Promise<results.JsonResult> {
return super.deleteSharedVault(request, response)
}
@httpPost('/:sharedVaultUuid/valet-tokens')
override async createValetTokenForSharedVaultFile(request: Request, response: Response): Promise<results.JsonResult> {
return super.createValetTokenForSharedVaultFile(request, response)
}
}

View File

@@ -0,0 +1,19 @@
import { Repository } from 'typeorm'
import { MapperInterface } from '@standardnotes/domain-core'
import { NotificationRepositoryInterface } from '../../Domain/Notifications/NotificationRepositoryInterface'
import { TypeORMNotification } from './TypeORMNotification'
import { Notification } from '../../Domain/Notifications/Notification'
export class TypeORMNotificationRepository implements NotificationRepositoryInterface {
constructor(
private ormRepository: Repository<TypeORMNotification>,
private mapper: MapperInterface<Notification, TypeORMNotification>,
) {}
async save(sharedVault: Notification): Promise<void> {
const persistence = this.mapper.toProjection(sharedVault)
await this.ormRepository.save(persistence)
}
}

View File

@@ -11,6 +11,45 @@ export class TypeORMSharedVaultInviteRepository implements SharedVaultInviteRepo
private mapper: MapperInterface<SharedVaultInvite, TypeORMSharedVaultInvite>,
) {}
async findBySenderUuidAndSharedVaultUuid(dto: {
senderUuid: Uuid
sharedVaultUuid: Uuid
}): Promise<SharedVaultInvite[]> {
const persistence = await this.ormRepository
.createQueryBuilder('shared_vault_invite')
.where('shared_vault_invite.sender_uuid = :uuid', {
senderUuid: dto.senderUuid.value,
})
.andWhere('shared_vault_invite.shared_vault_uuid = :sharedVaultUuid', {
sharedVaultUuid: dto.sharedVaultUuid.value,
})
.getMany()
return persistence.map((p) => this.mapper.toDomain(p))
}
async findBySenderUuid(senderUuid: Uuid): Promise<SharedVaultInvite[]> {
const persistence = await this.ormRepository
.createQueryBuilder('shared_vault_invite')
.where('shared_vault_invite.sender_uuid = :senderUuid', {
senderUuid: senderUuid.value,
})
.getMany()
return persistence.map((p) => this.mapper.toDomain(p))
}
async findByUserUuid(userUuid: Uuid): Promise<SharedVaultInvite[]> {
const persistence = await this.ormRepository
.createQueryBuilder('shared_vault_invite')
.where('shared_vault_invite.user_uuid = :userUuid', {
userUuid: userUuid.value,
})
.getMany()
return persistence.map((p) => this.mapper.toDomain(p))
}
async removeBySharedVaultUuid(sharedVaultUuid: Uuid): Promise<void> {
await this.ormRepository
.createQueryBuilder('shared_vault_invite')

View File

@@ -0,0 +1,21 @@
import { MapperInterface } from '@standardnotes/domain-core'
import { SharedVault } from '../../Domain/SharedVault/SharedVault'
import { SharedVaultHttpRepresentation } from './SharedVaultHttpRepresentation'
export class SharedVaultHttpMapper implements MapperInterface<SharedVault, SharedVaultHttpRepresentation> {
toDomain(_projection: SharedVaultHttpRepresentation): SharedVault {
throw new Error('Mapping from http representation to domain is not implemented.')
}
toProjection(domain: SharedVault): SharedVaultHttpRepresentation {
return {
uuid: domain.id.toString(),
user_uuid: domain.props.userUuid.value,
file_upload_bytes_limit: domain.props.fileUploadBytesLimit,
file_upload_bytes_used: domain.props.fileUploadBytesUsed,
created_at_timestamp: domain.props.timestamps.createdAt,
updated_at_timestamp: domain.props.timestamps.updatedAt,
}
}
}

View File

@@ -0,0 +1,8 @@
export interface SharedVaultHttpRepresentation {
uuid: string
user_uuid: string
file_upload_bytes_used: number
file_upload_bytes_limit: number
created_at_timestamp: number
updated_at_timestamp: number
}

View File

@@ -0,0 +1,25 @@
import { MapperInterface } from '@standardnotes/domain-core'
import { SharedVaultInvite } from '../../Domain/SharedVault/User/Invite/SharedVaultInvite'
import { SharedVaultInviteHttpRepresentation } from './SharedVaultInviteHttpRepresentation'
export class SharedVaultInviteHttpMapper
implements MapperInterface<SharedVaultInvite, SharedVaultInviteHttpRepresentation>
{
toDomain(_projection: SharedVaultInviteHttpRepresentation): SharedVaultInvite {
throw new Error('Mapping from http representation to domain is not implemented.')
}
toProjection(domain: SharedVaultInvite): SharedVaultInviteHttpRepresentation {
return {
uuid: domain.id.toString(),
shared_vault_uuid: domain.props.sharedVaultUuid.value,
user_uuid: domain.props.userUuid.value,
sender_uuid: domain.props.senderUuid.value,
encrypted_message: domain.props.encryptedMessage,
permissions: domain.props.permission.value,
created_at_timestamp: domain.props.timestamps.createdAt,
updated_at_timestamp: domain.props.timestamps.updatedAt,
}
}
}

View File

@@ -0,0 +1,10 @@
export interface SharedVaultInviteHttpRepresentation {
uuid: string
shared_vault_uuid: string
user_uuid: string
sender_uuid: string
encrypted_message: string
permissions: string
created_at_timestamp: number
updated_at_timestamp: number
}

View File

@@ -0,0 +1,21 @@
import { MapperInterface } from '@standardnotes/domain-core'
import { SharedVaultUser } from '../../Domain/SharedVault/User/SharedVaultUser'
import { SharedVaultUserHttpRepresentation } from './SharedVaultUserHttpRepresentation'
export class SharedVaultUserHttpMapper implements MapperInterface<SharedVaultUser, SharedVaultUserHttpRepresentation> {
toDomain(_projection: SharedVaultUserHttpRepresentation): SharedVaultUser {
throw new Error('Mapping from http representation to domain is not implemented.')
}
toProjection(domain: SharedVaultUser): SharedVaultUserHttpRepresentation {
return {
uuid: domain.id.toString(),
user_uuid: domain.props.userUuid.value,
permission: domain.props.permission.value,
shared_vault_uuid: domain.props.sharedVaultUuid.value,
created_at_timestamp: domain.props.timestamps.createdAt,
updated_at_timestamp: domain.props.timestamps.updatedAt,
}
}
}

View File

@@ -0,0 +1,8 @@
export interface SharedVaultUserHttpRepresentation {
uuid: string
shared_vault_uuid: string
user_uuid: string
permission: string
created_at_timestamp: number
updated_at_timestamp: number
}

View File

@@ -0,0 +1,57 @@
import { Timestamps, MapperInterface, UniqueEntityId, Uuid } from '@standardnotes/domain-core'
import { Notification } from '../../Domain/Notifications/Notification'
import { TypeORMNotification } from '../../Infra/TypeORM/TypeORMNotification'
import { NotificationType } from '../../Domain/Notifications/NotificationType'
export class NotificationPersistenceMapper implements MapperInterface<Notification, TypeORMNotification> {
toDomain(projection: TypeORMNotification): Notification {
const userUuidOrError = Uuid.create(projection.userUuid)
if (userUuidOrError.isFailed()) {
throw new Error(`Failed to create notification from projection: ${userUuidOrError.getError()}`)
}
const userUuid = userUuidOrError.getValue()
const timestampsOrError = Timestamps.create(projection.createdAtTimestamp, projection.updatedAtTimestamp)
if (timestampsOrError.isFailed()) {
throw new Error(`Failed to create notification from projection: ${timestampsOrError.getError()}`)
}
const timestamps = timestampsOrError.getValue()
const typeOrError = NotificationType.create(projection.type)
if (typeOrError.isFailed()) {
throw new Error(`Failed to create notification from projection: ${typeOrError.getError()}`)
}
const type = typeOrError.getValue()
const notificationOrError = Notification.create(
{
userUuid,
payload: projection.payload,
type,
timestamps,
},
new UniqueEntityId(projection.uuid),
)
if (notificationOrError.isFailed()) {
throw new Error(`Failed to create notification from projection: ${notificationOrError.getError()}`)
}
const notification = notificationOrError.getValue()
return notification
}
toProjection(domain: Notification): TypeORMNotification {
const typeorm = new TypeORMNotification()
typeorm.uuid = domain.id.toString()
typeorm.userUuid = domain.props.userUuid.value
typeorm.payload = domain.props.payload
typeorm.type = domain.props.type.value
typeorm.createdAtTimestamp = domain.props.timestamps.createdAt
typeorm.updatedAtTimestamp = domain.props.timestamps.updatedAt
return typeorm
}
}

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