mirror of
https://github.com/standardnotes/server
synced 2026-01-26 23:01:10 -05:00
Compare commits
16 Commits
@standardn
...
@standardn
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
148542dd5a | ||
|
|
d2b2c339f2 | ||
|
|
d2578c48f0 | ||
|
|
fecfd54728 | ||
|
|
17e4162d3e | ||
|
|
742209d773 | ||
|
|
1fa4b7cf27 | ||
|
|
5dc5507039 | ||
|
|
3035a20b9f | ||
|
|
04b3bb034f | ||
|
|
bf84be0136 | ||
|
|
890cf48749 | ||
|
|
2b3436c6ce | ||
|
|
4df8c3b2e5 | ||
|
|
25a2696c32 | ||
|
|
52f879f842 |
@@ -3,6 +3,12 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [1.121.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.120.2...@standardnotes/auth-server@1.121.0) (2023-07-05)
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** add notifications model ([#638](https://github.com/standardnotes/server/issues/638)) ([fecfd54](https://github.com/standardnotes/server/commit/fecfd5472824b5adae708db95d351e4ad65ee87b))
|
||||
|
||||
## [1.120.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.120.1...@standardnotes/auth-server@1.120.2) (2023-06-30)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/auth-server
|
||||
|
||||
BIN
packages/auth/database.sqlite
Normal file
BIN
packages/auth/database.sqlite
Normal file
Binary file not shown.
@@ -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`')
|
||||
}
|
||||
}
|
||||
@@ -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"')
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/auth-server",
|
||||
"version": "1.120.2",
|
||||
"version": "1.121.0",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -18,21 +18,26 @@ 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
|
||||
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'
|
||||
@@ -60,6 +65,7 @@ export class AppDataSource {
|
||||
TypeORMAuthenticatorChallenge,
|
||||
TypeORMEmergencyAccessInvitation,
|
||||
TypeORMCacheEntry,
|
||||
TypeORMNotification,
|
||||
],
|
||||
migrations: [`${__dirname}/../../migrations/${isConfiguredForMySQL ? 'mysql' : 'sqlite'}/*.js`],
|
||||
migrationsRun: true,
|
||||
@@ -104,7 +110,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,
|
||||
@@ -112,9 +118,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
|
||||
}
|
||||
}
|
||||
|
||||
7
packages/auth/src/Bootstrap/MigrationsDataSource.ts
Normal file
7
packages/auth/src/Bootstrap/MigrationsDataSource.ts
Normal 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
|
||||
18
packages/auth/src/Domain/Notifications/Notification.spec.ts
Normal file
18
packages/auth/src/Domain/Notifications/Notification.spec.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Timestamps, Uuid } from '@standardnotes/domain-core'
|
||||
|
||||
import { Notification } from './Notification'
|
||||
import { NotificationType } from './NotificationType'
|
||||
|
||||
describe('Notification', () => {
|
||||
it('should create an entity', () => {
|
||||
const entityOrError = Notification.create({
|
||||
timestamps: Timestamps.create(123456789, 123456789).getValue(),
|
||||
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
payload: 'payload',
|
||||
type: NotificationType.create(NotificationType.TYPES.SharedVaultItemRemoved).getValue(),
|
||||
})
|
||||
|
||||
expect(entityOrError.isFailed()).toBeFalsy()
|
||||
expect(entityOrError.getValue().id).not.toBeNull()
|
||||
})
|
||||
})
|
||||
17
packages/auth/src/Domain/Notifications/Notification.ts
Normal file
17
packages/auth/src/Domain/Notifications/Notification.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { Entity, Result, UniqueEntityId } from '@standardnotes/domain-core'
|
||||
|
||||
import { NotificationProps } from './NotificationProps'
|
||||
|
||||
export class Notification extends Entity<NotificationProps> {
|
||||
get id(): UniqueEntityId {
|
||||
return this._id
|
||||
}
|
||||
|
||||
private constructor(props: NotificationProps, id?: UniqueEntityId) {
|
||||
super(props, id)
|
||||
}
|
||||
|
||||
static create(props: NotificationProps, id?: UniqueEntityId): Result<Notification> {
|
||||
return Result.ok<Notification>(new Notification(props, id))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { Timestamps, Uuid } from '@standardnotes/domain-core'
|
||||
import { NotificationType } from './NotificationType'
|
||||
|
||||
export interface NotificationProps {
|
||||
userUuid: Uuid
|
||||
type: NotificationType
|
||||
payload: string
|
||||
timestamps: Timestamps
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { NotificationType } from './NotificationType'
|
||||
|
||||
describe('NotificationType', () => {
|
||||
it('should create a value object', () => {
|
||||
const valueOrError = NotificationType.create(NotificationType.TYPES.SharedVaultItemRemoved)
|
||||
|
||||
expect(valueOrError.isFailed()).toBeFalsy()
|
||||
expect(valueOrError.getValue().value).toEqual('shared_vault_item_removed')
|
||||
})
|
||||
|
||||
it('should not create an invalid value object', () => {
|
||||
const valueOrError = NotificationType.create('TEST')
|
||||
|
||||
expect(valueOrError.isFailed()).toBeTruthy()
|
||||
})
|
||||
})
|
||||
27
packages/auth/src/Domain/Notifications/NotificationType.ts
Normal file
27
packages/auth/src/Domain/Notifications/NotificationType.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { Result, ValueObject } from '@standardnotes/domain-core'
|
||||
|
||||
import { NotificationTypeProps } from './NotificationTypeProps'
|
||||
|
||||
export class NotificationType extends ValueObject<NotificationTypeProps> {
|
||||
static readonly TYPES = {
|
||||
SharedVaultItemRemoved: 'shared_vault_item_removed',
|
||||
RemovedFromSharedVault: 'removed_from_shared_vault',
|
||||
}
|
||||
|
||||
get value(): string {
|
||||
return this.props.value
|
||||
}
|
||||
|
||||
private constructor(props: NotificationTypeProps) {
|
||||
super(props)
|
||||
}
|
||||
|
||||
static create(notificationType: string): Result<NotificationType> {
|
||||
const isValidPermission = Object.values(this.TYPES).includes(notificationType)
|
||||
if (!isValidPermission) {
|
||||
return Result.fail<NotificationType>(`Invalid shared vault user permission ${notificationType}`)
|
||||
} else {
|
||||
return Result.ok<NotificationType>(new NotificationType({ value: notificationType }))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export interface NotificationTypeProps {
|
||||
value: string
|
||||
}
|
||||
38
packages/auth/src/Infra/TypeORM/TypeORMNotification.ts
Normal file
38
packages/auth/src/Infra/TypeORM/TypeORMNotification.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { Column, Entity, Index, PrimaryGeneratedColumn } from 'typeorm'
|
||||
|
||||
@Entity({ name: 'notifications' })
|
||||
export class TypeORMNotification {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
declare uuid: string
|
||||
|
||||
@Column({
|
||||
name: 'user_uuid',
|
||||
length: 36,
|
||||
})
|
||||
@Index('index_notifications_on_user_uuid')
|
||||
declare userUuid: string
|
||||
|
||||
@Column({
|
||||
name: 'type',
|
||||
length: 36,
|
||||
})
|
||||
declare type: string
|
||||
|
||||
@Column({
|
||||
name: 'payload',
|
||||
type: 'text',
|
||||
})
|
||||
declare payload: string
|
||||
|
||||
@Column({
|
||||
name: 'created_at_timestamp',
|
||||
type: 'bigint',
|
||||
})
|
||||
declare createdAtTimestamp: number
|
||||
|
||||
@Column({
|
||||
name: 'updated_at_timestamp',
|
||||
type: 'bigint',
|
||||
})
|
||||
declare updatedAtTimestamp: number
|
||||
}
|
||||
@@ -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.24](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.11.23...@standardnotes/home-server@1.11.24) (2023-07-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.11.23](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.11.22...@standardnotes/home-server@1.11.23) (2023-07-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.11.22](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.11.21...@standardnotes/home-server@1.11.22) (2023-07-03)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.11.21](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.11.20...@standardnotes/home-server@1.11.21) (2023-07-03)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.11.20](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.11.19...@standardnotes/home-server@1.11.20) (2023-07-03)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.11.19](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.11.18...@standardnotes/home-server@1.11.19) (2023-06-30)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.11.18](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.11.17...@standardnotes/home-server@1.11.18) (2023-06-30)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.11.17](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.11.16...@standardnotes/home-server@1.11.17) (2023-06-30)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/home-server",
|
||||
"version": "1.11.17",
|
||||
"version": "1.11.24",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,42 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [1.51.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.50.0...@standardnotes/syncing-server@1.51.0) (2023-07-05)
|
||||
|
||||
### Features
|
||||
|
||||
* add getting shared vaults for a user ([#639](https://github.com/standardnotes/syncing-server-js/issues/639)) ([d2b2c33](https://github.com/standardnotes/syncing-server-js/commit/d2b2c339f2089ea5d538ee40118af083983be5ef))
|
||||
|
||||
# [1.50.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.49.0...@standardnotes/syncing-server@1.50.0) (2023-07-03)
|
||||
|
||||
### Features
|
||||
|
||||
* add invite users to a shared vault. ([#636](https://github.com/standardnotes/syncing-server-js/issues/636)) ([5dc5507](https://github.com/standardnotes/syncing-server-js/commit/5dc5507039c0dfb9df82a85377846651fef73c57))
|
||||
|
||||
# [1.49.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.48.0...@standardnotes/syncing-server@1.49.0) (2023-07-03)
|
||||
|
||||
### Features
|
||||
|
||||
* add creating shared vault file valet tokens. ([#635](https://github.com/standardnotes/syncing-server-js/issues/635)) ([04b3bb0](https://github.com/standardnotes/syncing-server-js/commit/04b3bb034fb5bf6f9d00d5b2e8a1abc4832c5417))
|
||||
|
||||
# [1.48.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.47.0...@standardnotes/syncing-server@1.48.0) (2023-07-03)
|
||||
|
||||
### Features
|
||||
|
||||
* add shared vault invite model. ([#634](https://github.com/standardnotes/syncing-server-js/issues/634)) ([890cf48](https://github.com/standardnotes/syncing-server-js/commit/890cf48749b120212080563e6d3070bd43641f1a))
|
||||
|
||||
# [1.47.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.46.0...@standardnotes/syncing-server@1.47.0) (2023-06-30)
|
||||
|
||||
### Features
|
||||
|
||||
* add use case for creating shared vaults and adding users to it. ([#633](https://github.com/standardnotes/syncing-server-js/issues/633)) ([4df8c3b](https://github.com/standardnotes/syncing-server-js/commit/4df8c3b2e5ba4b7d510849ac71b19ed1749f098c))
|
||||
|
||||
# [1.46.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.45.0...@standardnotes/syncing-server@1.46.0) (2023-06-30)
|
||||
|
||||
### Features
|
||||
|
||||
* add shared vaults user model. ([#632](https://github.com/standardnotes/syncing-server-js/issues/632)) ([52f879f](https://github.com/standardnotes/syncing-server-js/commit/52f879f84216084d8affcb3522b1d99cb1135104))
|
||||
|
||||
# [1.45.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.44.6...@standardnotes/syncing-server@1.45.0) (2023-06-30)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/syncing-server",
|
||||
"version": "1.45.0",
|
||||
"version": "1.51.0",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import { Timestamps, Uuid } from '@standardnotes/domain-core'
|
||||
|
||||
import { SharedVault } from './SharedVault'
|
||||
|
||||
describe('SharedVault', () => {
|
||||
it('should create an entity', () => {
|
||||
const entityOrError = SharedVault.create({
|
||||
fileUploadBytesLimit: 1_000_000,
|
||||
fileUploadBytesUsed: 0,
|
||||
timestamps: Timestamps.create(123456789, 123456789).getValue(),
|
||||
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
})
|
||||
|
||||
expect(entityOrError.isFailed()).toBeFalsy()
|
||||
expect(entityOrError.getValue().id).not.toBeNull()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,9 @@
|
||||
import { Uuid } from '@standardnotes/domain-core'
|
||||
import { SharedVault } from './SharedVault'
|
||||
|
||||
export interface SharedVaultRepositoryInterface {
|
||||
findByUuid(uuid: Uuid): Promise<SharedVault | null>
|
||||
findByUuids(uuids: Uuid[], lastSyncTime?: number): Promise<SharedVault[]>
|
||||
save(sharedVault: SharedVault): Promise<void>
|
||||
remove(sharedVault: SharedVault): Promise<void>
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
import { SharedVault } from './SharedVault'
|
||||
|
||||
export interface SharedVaultsRepositoryInterface {
|
||||
findByUuid(uuid: string): Promise<SharedVault | null>
|
||||
save(sharedVault: SharedVault): Promise<void>
|
||||
remove(sharedVault: SharedVault): Promise<void>
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { Timestamps, Uuid } from '@standardnotes/domain-core'
|
||||
|
||||
import { SharedVaultInvite } from './SharedVaultInvite'
|
||||
import { SharedVaultUserPermission } from '../SharedVaultUserPermission'
|
||||
|
||||
describe('SharedVaultInvite', () => {
|
||||
it('should create an entity', () => {
|
||||
const entityOrError = SharedVaultInvite.create({
|
||||
permission: SharedVaultUserPermission.create('read').getValue(),
|
||||
sharedVaultUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
senderUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
encryptedMessage: 'encryptedMessage',
|
||||
timestamps: Timestamps.create(123456789, 123456789).getValue(),
|
||||
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
})
|
||||
|
||||
expect(entityOrError.isFailed()).toBeFalsy()
|
||||
expect(entityOrError.getValue().id).not.toBeNull()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,17 @@
|
||||
import { Entity, Result, UniqueEntityId } from '@standardnotes/domain-core'
|
||||
|
||||
import { SharedVaultInviteProps } from './SharedVaultInviteProps'
|
||||
|
||||
export class SharedVaultInvite extends Entity<SharedVaultInviteProps> {
|
||||
get id(): UniqueEntityId {
|
||||
return this._id
|
||||
}
|
||||
|
||||
private constructor(props: SharedVaultInviteProps, id?: UniqueEntityId) {
|
||||
super(props, id)
|
||||
}
|
||||
|
||||
static create(props: SharedVaultInviteProps, id?: UniqueEntityId): Result<SharedVaultInvite> {
|
||||
return Result.ok<SharedVaultInvite>(new SharedVaultInvite(props, id))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { Timestamps, Uuid } from '@standardnotes/domain-core'
|
||||
import { SharedVaultUserPermission } from '../SharedVaultUserPermission'
|
||||
|
||||
export interface SharedVaultInviteProps {
|
||||
sharedVaultUuid: Uuid
|
||||
userUuid: Uuid
|
||||
senderUuid: Uuid
|
||||
encryptedMessage: string
|
||||
permission: SharedVaultUserPermission
|
||||
timestamps: Timestamps
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { Uuid } from '@standardnotes/domain-core'
|
||||
|
||||
import { SharedVaultInvite } from './SharedVaultInvite'
|
||||
|
||||
export interface SharedVaultInviteRepositoryInterface {
|
||||
findByUuid(sharedVaultInviteUuid: Uuid): Promise<SharedVaultInvite | null>
|
||||
save(sharedVaultInvite: SharedVaultInvite): Promise<void>
|
||||
remove(sharedVaultInvite: SharedVaultInvite): Promise<void>
|
||||
findByUserUuidAndSharedVaultUuid(dto: { userUuid: Uuid; sharedVaultUuid: Uuid }): Promise<SharedVaultInvite | null>
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { Timestamps, Uuid } from '@standardnotes/domain-core'
|
||||
|
||||
import { SharedVaultUser } from './SharedVaultUser'
|
||||
import { SharedVaultUserPermission } from './SharedVaultUserPermission'
|
||||
|
||||
describe('SharedVaultUser', () => {
|
||||
it('should create an entity', () => {
|
||||
const entityOrError = SharedVaultUser.create({
|
||||
permission: SharedVaultUserPermission.create('read').getValue(),
|
||||
sharedVaultUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
timestamps: Timestamps.create(123456789, 123456789).getValue(),
|
||||
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
})
|
||||
|
||||
expect(entityOrError.isFailed()).toBeFalsy()
|
||||
expect(entityOrError.getValue().id).not.toBeNull()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,17 @@
|
||||
import { Entity, Result, UniqueEntityId } from '@standardnotes/domain-core'
|
||||
|
||||
import { SharedVaultUserProps } from './SharedVaultUserProps'
|
||||
|
||||
export class SharedVaultUser extends Entity<SharedVaultUserProps> {
|
||||
get id(): UniqueEntityId {
|
||||
return this._id
|
||||
}
|
||||
|
||||
private constructor(props: SharedVaultUserProps, id?: UniqueEntityId) {
|
||||
super(props, id)
|
||||
}
|
||||
|
||||
static create(props: SharedVaultUserProps, id?: UniqueEntityId): Result<SharedVaultUser> {
|
||||
return Result.ok<SharedVaultUser>(new SharedVaultUser(props, id))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { SharedVaultUserPermission } from './SharedVaultUserPermission'
|
||||
|
||||
describe('SharedVaultUserPermission', () => {
|
||||
it('should create a value object', () => {
|
||||
const valueOrError = SharedVaultUserPermission.create('read')
|
||||
|
||||
expect(valueOrError.isFailed()).toBeFalsy()
|
||||
expect(valueOrError.getValue().value).toEqual('read')
|
||||
})
|
||||
|
||||
it('should not create an invalid value object', () => {
|
||||
const valueOrError = SharedVaultUserPermission.create('TEST')
|
||||
|
||||
expect(valueOrError.isFailed()).toBeTruthy()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,28 @@
|
||||
import { Result, ValueObject } from '@standardnotes/domain-core'
|
||||
|
||||
import { SharedVaultUserPermissionProps } from './SharedVaultUserPermissionProps'
|
||||
|
||||
export class SharedVaultUserPermission extends ValueObject<SharedVaultUserPermissionProps> {
|
||||
static readonly PERMISSIONS = {
|
||||
Read: 'read',
|
||||
Write: 'write',
|
||||
Admin: 'admin',
|
||||
}
|
||||
|
||||
get value(): string {
|
||||
return this.props.value
|
||||
}
|
||||
|
||||
private constructor(props: SharedVaultUserPermissionProps) {
|
||||
super(props)
|
||||
}
|
||||
|
||||
static create(sharedVaultUserPermission: string): Result<SharedVaultUserPermission> {
|
||||
const isValidPermission = Object.values(this.PERMISSIONS).includes(sharedVaultUserPermission)
|
||||
if (!isValidPermission) {
|
||||
return Result.fail<SharedVaultUserPermission>(`Invalid shared vault user permission ${sharedVaultUserPermission}`)
|
||||
} else {
|
||||
return Result.ok<SharedVaultUserPermission>(new SharedVaultUserPermission({ value: sharedVaultUserPermission }))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export interface SharedVaultUserPermissionProps {
|
||||
value: string
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { Timestamps, Uuid } from '@standardnotes/domain-core'
|
||||
|
||||
import { SharedVaultUserPermission } from './SharedVaultUserPermission'
|
||||
|
||||
export interface SharedVaultUserProps {
|
||||
sharedVaultUuid: Uuid
|
||||
userUuid: Uuid
|
||||
permission: SharedVaultUserPermission
|
||||
timestamps: Timestamps
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { Uuid } from '@standardnotes/domain-core'
|
||||
|
||||
import { SharedVaultUser } from './SharedVaultUser'
|
||||
|
||||
export interface SharedVaultUserRepositoryInterface {
|
||||
findByUuid(sharedVaultUserUuid: Uuid): Promise<SharedVaultUser | null>
|
||||
findByUserUuid(userUuid: Uuid): Promise<SharedVaultUser[]>
|
||||
save(sharedVaultUser: SharedVaultUser): Promise<void>
|
||||
remove(sharedVault: SharedVaultUser): Promise<void>
|
||||
findByUserUuidAndSharedVaultUuid(dto: { userUuid: Uuid; sharedVaultUuid: Uuid }): Promise<SharedVaultUser | null>
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
import { SharedVaultRepositoryInterface } from '../../SharedVault/SharedVaultRepositoryInterface'
|
||||
import { SharedVaultUserRepositoryInterface } from '../../SharedVault/User/SharedVaultUserRepositoryInterface'
|
||||
import { RemoveUserEvents } from '../RemoveUserEvents/RemoveUserEvents'
|
||||
import { AddUserToSharedVault } from './AddUserToSharedVault'
|
||||
import { Result } from '@standardnotes/domain-core'
|
||||
import { SharedVault } from '../../SharedVault/SharedVault'
|
||||
import { SharedVaultUser } from '../../SharedVault/User/SharedVaultUser'
|
||||
|
||||
describe('AddUserToSharedVault', () => {
|
||||
let removeUserEvents: RemoveUserEvents
|
||||
let sharedVaultRepository: SharedVaultRepositoryInterface
|
||||
let sharedVaultUserRepository: SharedVaultUserRepositoryInterface
|
||||
let timer: TimerInterface
|
||||
let sharedVault: SharedVault
|
||||
|
||||
const validUuid = '00000000-0000-0000-0000-000000000000'
|
||||
|
||||
const createUseCase = () =>
|
||||
new AddUserToSharedVault(removeUserEvents, sharedVaultRepository, sharedVaultUserRepository, timer)
|
||||
|
||||
beforeEach(() => {
|
||||
removeUserEvents = {} as jest.Mocked<RemoveUserEvents>
|
||||
removeUserEvents.execute = jest.fn().mockResolvedValue(Result.ok())
|
||||
|
||||
sharedVault = {} as jest.Mocked<SharedVault>
|
||||
|
||||
sharedVaultRepository = {} as jest.Mocked<SharedVaultRepositoryInterface>
|
||||
sharedVaultRepository.findByUuid = jest.fn().mockResolvedValue(sharedVault)
|
||||
|
||||
sharedVaultUserRepository = {} as jest.Mocked<SharedVaultUserRepositoryInterface>
|
||||
sharedVaultUserRepository.save = jest.fn()
|
||||
|
||||
timer = {} as jest.Mocked<TimerInterface>
|
||||
timer.getTimestampInMicroseconds = jest.fn().mockReturnValue(123456789)
|
||||
})
|
||||
|
||||
it('should return a failure result if the shared vault uuid is invalid', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
sharedVaultUuid: 'invalid-uuid',
|
||||
userUuid: validUuid,
|
||||
permission: 'read',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
expect(result.getError()).toBe('Given value is not a valid uuid: invalid-uuid')
|
||||
})
|
||||
|
||||
it('should return a failure result if the user uuid is invalid', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
sharedVaultUuid: validUuid,
|
||||
userUuid: 'invalid-uuid',
|
||||
permission: 'read',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
expect(result.getError()).toBe('Given value is not a valid uuid: invalid-uuid')
|
||||
})
|
||||
|
||||
it('should return a failure result if the permission is invalid', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
sharedVaultUuid: validUuid,
|
||||
userUuid: validUuid,
|
||||
permission: 'test',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
expect(result.getError()).toBe('Invalid shared vault user permission test')
|
||||
})
|
||||
|
||||
it('should return a failure result if the shared vault does not exist', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
sharedVaultRepository.findByUuid = jest.fn().mockResolvedValueOnce(null)
|
||||
|
||||
const result = await useCase.execute({
|
||||
sharedVaultUuid: validUuid,
|
||||
userUuid: validUuid,
|
||||
permission: 'read',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
expect(result.getError()).toBe('Attempting to add a shared vault user to a non-existent shared vault')
|
||||
})
|
||||
|
||||
it('should return a failure result if removing user events fails', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
removeUserEvents.execute = jest.fn().mockResolvedValueOnce(Result.fail('test'))
|
||||
|
||||
const result = await useCase.execute({
|
||||
sharedVaultUuid: validUuid,
|
||||
userUuid: validUuid,
|
||||
permission: 'read',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
expect(result.getError()).toBe('test')
|
||||
})
|
||||
|
||||
it('should return a failure result if creating the shared vault user fails', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
const mockSharedVaultUser = jest.spyOn(SharedVaultUser, 'create')
|
||||
mockSharedVaultUser.mockImplementation(() => {
|
||||
return Result.fail('Oops')
|
||||
})
|
||||
|
||||
const result = await useCase.execute({
|
||||
sharedVaultUuid: validUuid,
|
||||
userUuid: validUuid,
|
||||
permission: 'read',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
expect(result.getError()).toBe('Oops')
|
||||
|
||||
mockSharedVaultUser.mockRestore()
|
||||
})
|
||||
|
||||
it('should add a user to a shared vault', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
sharedVaultUuid: validUuid,
|
||||
userUuid: validUuid,
|
||||
permission: 'read',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(false)
|
||||
expect(sharedVaultUserRepository.save).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,71 @@
|
||||
import { Result, Timestamps, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
|
||||
import { AddUserToSharedVaultDTO } from './AddUserToSharedVaultDTO'
|
||||
import { SharedVaultRepositoryInterface } from '../../SharedVault/SharedVaultRepositoryInterface'
|
||||
import { SharedVaultUser } from '../../SharedVault/User/SharedVaultUser'
|
||||
import { SharedVaultUserRepositoryInterface } from '../../SharedVault/User/SharedVaultUserRepositoryInterface'
|
||||
import { SharedVaultUserPermission } from '../../SharedVault/User/SharedVaultUserPermission'
|
||||
import { RemoveUserEvents } from '../RemoveUserEvents/RemoveUserEvents'
|
||||
|
||||
export class AddUserToSharedVault implements UseCaseInterface<SharedVaultUser> {
|
||||
constructor(
|
||||
private removeUserEvents: RemoveUserEvents,
|
||||
private sharedVaultRepository: SharedVaultRepositoryInterface,
|
||||
private sharedVaultUserRepository: SharedVaultUserRepositoryInterface,
|
||||
private timer: TimerInterface,
|
||||
) {}
|
||||
|
||||
async execute(dto: AddUserToSharedVaultDTO): Promise<Result<SharedVaultUser>> {
|
||||
const sharedVaultUuidOrError = Uuid.create(dto.sharedVaultUuid)
|
||||
if (sharedVaultUuidOrError.isFailed()) {
|
||||
return Result.fail(sharedVaultUuidOrError.getError())
|
||||
}
|
||||
const sharedVaultUuid = sharedVaultUuidOrError.getValue()
|
||||
|
||||
const sharedVault = await this.sharedVaultRepository.findByUuid(sharedVaultUuid)
|
||||
if (!sharedVault) {
|
||||
return Result.fail('Attempting to add a shared vault user to a non-existent shared vault')
|
||||
}
|
||||
|
||||
const userUuidOrError = Uuid.create(dto.userUuid)
|
||||
if (userUuidOrError.isFailed()) {
|
||||
return Result.fail(userUuidOrError.getError())
|
||||
}
|
||||
const userUuid = userUuidOrError.getValue()
|
||||
|
||||
const permissionOrError = SharedVaultUserPermission.create(dto.permission)
|
||||
if (permissionOrError.isFailed()) {
|
||||
return Result.fail(permissionOrError.getError())
|
||||
}
|
||||
const permission = permissionOrError.getValue()
|
||||
|
||||
const removingEventsResult = await this.removeUserEvents.execute({
|
||||
sharedVaultUuid: sharedVaultUuid.value,
|
||||
userUuid: userUuid.value,
|
||||
})
|
||||
if (removingEventsResult.isFailed()) {
|
||||
return Result.fail(removingEventsResult.getError())
|
||||
}
|
||||
|
||||
const timestamps = Timestamps.create(
|
||||
this.timer.getTimestampInMicroseconds(),
|
||||
this.timer.getTimestampInMicroseconds(),
|
||||
).getValue()
|
||||
|
||||
const sharedVaultUserOrError = SharedVaultUser.create({
|
||||
userUuid,
|
||||
sharedVaultUuid,
|
||||
permission,
|
||||
timestamps,
|
||||
})
|
||||
if (sharedVaultUserOrError.isFailed()) {
|
||||
return Result.fail(sharedVaultUserOrError.getError())
|
||||
}
|
||||
const sharedVaultUser = sharedVaultUserOrError.getValue()
|
||||
|
||||
await this.sharedVaultUserRepository.save(sharedVaultUser)
|
||||
|
||||
return Result.ok(sharedVaultUser)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export interface AddUserToSharedVaultDTO {
|
||||
sharedVaultUuid: string
|
||||
userUuid: string
|
||||
permission: string
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
import { Result } from '@standardnotes/domain-core'
|
||||
|
||||
import { SharedVaultRepositoryInterface } from '../../SharedVault/SharedVaultRepositoryInterface'
|
||||
import { AddUserToSharedVault } from '../AddUserToSharedVault/AddUserToSharedVault'
|
||||
import { CreateSharedVault } from './CreateSharedVault'
|
||||
import { SharedVault } from '../../SharedVault/SharedVault'
|
||||
|
||||
describe('CreateSharedVault', () => {
|
||||
let addUserToSharedVault: AddUserToSharedVault
|
||||
let sharedVaultRepository: SharedVaultRepositoryInterface
|
||||
let timer: TimerInterface
|
||||
|
||||
const createUseCase = () => new CreateSharedVault(addUserToSharedVault, sharedVaultRepository, timer)
|
||||
|
||||
beforeEach(() => {
|
||||
addUserToSharedVault = {} as jest.Mocked<AddUserToSharedVault>
|
||||
addUserToSharedVault.execute = jest.fn().mockResolvedValue(Result.ok())
|
||||
|
||||
sharedVaultRepository = {} as jest.Mocked<SharedVaultRepositoryInterface>
|
||||
sharedVaultRepository.save = jest.fn()
|
||||
|
||||
timer = {} as jest.Mocked<TimerInterface>
|
||||
timer.getTimestampInMicroseconds = jest.fn().mockReturnValue(123456789)
|
||||
})
|
||||
|
||||
it('should return a failure result if the user uuid is invalid', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: 'invalid-uuid',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
expect(result.getError()).toBe('Given value is not a valid uuid: invalid-uuid')
|
||||
})
|
||||
|
||||
it('should return a failure result if the shared vault could not be created', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
const mockSharedVault = jest.spyOn(SharedVault, 'create')
|
||||
mockSharedVault.mockImplementation(() => {
|
||||
return Result.fail('Oops')
|
||||
})
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
expect(result.getError()).toBe('Oops')
|
||||
|
||||
mockSharedVault.mockRestore()
|
||||
})
|
||||
|
||||
it('should return a failure result if the user could not be added to the shared vault', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
addUserToSharedVault.execute = jest.fn().mockResolvedValue(Result.fail('Oops'))
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
expect(result.getError()).toBe('Oops')
|
||||
})
|
||||
|
||||
it('should create a shared vault', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
await useCase.execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
})
|
||||
|
||||
expect(addUserToSharedVault.execute).toHaveBeenCalledWith({
|
||||
sharedVaultUuid: expect.any(String),
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
permission: 'admin',
|
||||
})
|
||||
expect(sharedVaultRepository.save).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,55 @@
|
||||
import { Result, Timestamps, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
|
||||
import { CreateSharedVaultResult } from './CreateSharedVaultResult'
|
||||
import { CreateSharedVaultDTO } from './CreateSharedVaultDTO'
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
import { SharedVaultRepositoryInterface } from '../../SharedVault/SharedVaultRepositoryInterface'
|
||||
import { AddUserToSharedVault } from '../AddUserToSharedVault/AddUserToSharedVault'
|
||||
import { SharedVault } from '../../SharedVault/SharedVault'
|
||||
|
||||
export class CreateSharedVault implements UseCaseInterface<CreateSharedVaultResult> {
|
||||
private readonly FILE_UPLOAD_BYTES_LIMIT = 1_000_000
|
||||
|
||||
constructor(
|
||||
private addUserToSharedVault: AddUserToSharedVault,
|
||||
private sharedVaultRepository: SharedVaultRepositoryInterface,
|
||||
private timer: TimerInterface,
|
||||
) {}
|
||||
|
||||
async execute(dto: CreateSharedVaultDTO): Promise<Result<CreateSharedVaultResult>> {
|
||||
const userUuidOrError = Uuid.create(dto.userUuid)
|
||||
if (userUuidOrError.isFailed()) {
|
||||
return Result.fail(userUuidOrError.getError())
|
||||
}
|
||||
const userUuid = userUuidOrError.getValue()
|
||||
|
||||
const timestamps = Timestamps.create(
|
||||
this.timer.getTimestampInMicroseconds(),
|
||||
this.timer.getTimestampInMicroseconds(),
|
||||
).getValue()
|
||||
|
||||
const sharedVaultOrError = SharedVault.create({
|
||||
fileUploadBytesLimit: this.FILE_UPLOAD_BYTES_LIMIT,
|
||||
fileUploadBytesUsed: 0,
|
||||
userUuid,
|
||||
timestamps,
|
||||
})
|
||||
if (sharedVaultOrError.isFailed()) {
|
||||
return Result.fail(sharedVaultOrError.getError())
|
||||
}
|
||||
const sharedVault = sharedVaultOrError.getValue()
|
||||
|
||||
await this.sharedVaultRepository.save(sharedVault)
|
||||
|
||||
const sharedVaultUserOrError = await this.addUserToSharedVault.execute({
|
||||
sharedVaultUuid: sharedVault.id.toString(),
|
||||
userUuid: dto.userUuid,
|
||||
permission: 'admin',
|
||||
})
|
||||
if (sharedVaultUserOrError.isFailed()) {
|
||||
return Result.fail(sharedVaultUserOrError.getError())
|
||||
}
|
||||
const sharedVaultUser = sharedVaultUserOrError.getValue()
|
||||
|
||||
return Result.ok({ sharedVault, sharedVaultUser })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export interface CreateSharedVaultDTO {
|
||||
userUuid: string
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { SharedVault } from '../../SharedVault/SharedVault'
|
||||
import { SharedVaultUser } from '../../SharedVault/User/SharedVaultUser'
|
||||
|
||||
export interface CreateSharedVaultResult {
|
||||
sharedVaultUser: SharedVaultUser
|
||||
sharedVault: SharedVault
|
||||
}
|
||||
@@ -0,0 +1,363 @@
|
||||
import { SharedVaultValetTokenData, TokenEncoderInterface, ValetTokenOperation } from '@standardnotes/security'
|
||||
import { SharedVaultRepositoryInterface } from '../../SharedVault/SharedVaultRepositoryInterface'
|
||||
import { SharedVaultUserRepositoryInterface } from '../../SharedVault/User/SharedVaultUserRepositoryInterface'
|
||||
import { CreateSharedVaultFileValetToken } from './CreateSharedVaultFileValetToken'
|
||||
import { SharedVault } from '../../SharedVault/SharedVault'
|
||||
import { SharedVaultUser } from '../../SharedVault/User/SharedVaultUser'
|
||||
import { SharedVaultUserPermission } from '../../SharedVault/User/SharedVaultUserPermission'
|
||||
import { Timestamps, Uuid } from '@standardnotes/domain-core'
|
||||
|
||||
describe('CreateSharedVaultFileValetToken', () => {
|
||||
let sharedVaultRepository: SharedVaultRepositoryInterface
|
||||
let sharedVaultUserRepository: SharedVaultUserRepositoryInterface
|
||||
let tokenEncoder: TokenEncoderInterface<SharedVaultValetTokenData>
|
||||
const valetTokenTTL = 3600
|
||||
let sharedVault: SharedVault
|
||||
let sharedVaultUser: SharedVaultUser
|
||||
|
||||
const createUseCase = () =>
|
||||
new CreateSharedVaultFileValetToken(sharedVaultRepository, sharedVaultUserRepository, tokenEncoder, valetTokenTTL)
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
tokenEncoder = {} as jest.Mocked<TokenEncoderInterface<SharedVaultValetTokenData>>
|
||||
tokenEncoder.encodeExpirableToken = jest.fn().mockReturnValue('encoded-token')
|
||||
})
|
||||
|
||||
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',
|
||||
remoteIdentifier: 'remote-identifier',
|
||||
operation: ValetTokenOperation.Read,
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
expect(result.getError()).toBe('Given value is not a valid uuid: invalid-uuid')
|
||||
})
|
||||
|
||||
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',
|
||||
remoteIdentifier: 'remote-identifier',
|
||||
operation: ValetTokenOperation.Read,
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
expect(result.getError()).toBe('Given value is not a valid uuid: invalid-uuid')
|
||||
})
|
||||
|
||||
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({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
|
||||
remoteIdentifier: 'remote-identifier',
|
||||
operation: ValetTokenOperation.Read,
|
||||
})
|
||||
|
||||
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({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
|
||||
remoteIdentifier: 'remote-identifier',
|
||||
operation: ValetTokenOperation.Read,
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
expect(result.getError()).toBe('Shared vault user not found')
|
||||
})
|
||||
|
||||
it('should return error when shared vault user does not have permission', async () => {
|
||||
const useCase = createUseCase()
|
||||
const result = await useCase.execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
|
||||
remoteIdentifier: 'remote-identifier',
|
||||
operation: ValetTokenOperation.Write,
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
expect(result.getError()).toBe('User does not have permission to perform this operation')
|
||||
})
|
||||
|
||||
it('should create a shared vault file valet token', async () => {
|
||||
sharedVaultUser = SharedVaultUser.create({
|
||||
permission: SharedVaultUserPermission.create(SharedVaultUserPermission.PERMISSIONS.Write).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.findByUserUuidAndSharedVaultUuid = jest.fn().mockResolvedValue(sharedVaultUser)
|
||||
|
||||
const useCase = createUseCase()
|
||||
const result = await useCase.execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
|
||||
remoteIdentifier: 'remote-identifier',
|
||||
operation: ValetTokenOperation.Write,
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(false)
|
||||
expect(result.getValue()).toBe('encoded-token')
|
||||
})
|
||||
|
||||
describe('move operation', () => {
|
||||
beforeEach(() => {
|
||||
sharedVaultUserRepository.findByUserUuidAndSharedVaultUuid = jest
|
||||
.fn()
|
||||
.mockReturnValueOnce(
|
||||
SharedVaultUser.create({
|
||||
permission: SharedVaultUserPermission.create(SharedVaultUserPermission.PERMISSIONS.Write).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(),
|
||||
)
|
||||
.mockReturnValueOnce(
|
||||
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(),
|
||||
)
|
||||
})
|
||||
|
||||
it('should return error when move operation type is not specified', async () => {
|
||||
const useCase = createUseCase()
|
||||
const result = await useCase.execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
|
||||
remoteIdentifier: 'remote-identifier',
|
||||
operation: ValetTokenOperation.Move,
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
expect(result.getError()).toBe('Move operation type is required')
|
||||
})
|
||||
|
||||
it('should return error when target uuid is missing on a shared-vault-to-shared-vault move operation', async () => {
|
||||
const useCase = createUseCase()
|
||||
const result = await useCase.execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
|
||||
remoteIdentifier: 'remote-identifier',
|
||||
operation: ValetTokenOperation.Move,
|
||||
moveOperationType: 'shared-vault-to-shared-vault',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
expect(result.getError()).toBe('Shared vault to shared vault move target uuid is required')
|
||||
})
|
||||
|
||||
it('should return error when target uuid is invalid on a shared-vault-to-shared-vault move operation', async () => {
|
||||
const useCase = createUseCase()
|
||||
const result = await useCase.execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
|
||||
remoteIdentifier: 'remote-identifier',
|
||||
operation: ValetTokenOperation.Move,
|
||||
moveOperationType: 'shared-vault-to-shared-vault',
|
||||
sharedVaultToSharedVaultMoveTargetUuid: 'invalid-uuid',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
expect(result.getError()).toBe('Given value is not a valid uuid: invalid-uuid')
|
||||
})
|
||||
|
||||
it('should return error when target shared vault user is not found on a shared-vault-to-shared-vault move operation', async () => {
|
||||
sharedVaultUserRepository.findByUserUuidAndSharedVaultUuid = jest
|
||||
.fn()
|
||||
.mockReturnValueOnce(
|
||||
SharedVaultUser.create({
|
||||
permission: SharedVaultUserPermission.create(SharedVaultUserPermission.PERMISSIONS.Write).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(),
|
||||
)
|
||||
.mockReturnValueOnce(null)
|
||||
|
||||
const useCase = createUseCase()
|
||||
const result = await useCase.execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
|
||||
remoteIdentifier: 'remote-identifier',
|
||||
operation: ValetTokenOperation.Move,
|
||||
moveOperationType: 'shared-vault-to-shared-vault',
|
||||
sharedVaultToSharedVaultMoveTargetUuid: '00000000-0000-0000-0000-000000000000',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
expect(result.getError()).toBe('Shared vault target user not found')
|
||||
})
|
||||
|
||||
it('should return error when target shared vault user does not have permission on a shared-vault-to-shared-vault move operation', async () => {
|
||||
sharedVaultUserRepository.findByUserUuidAndSharedVaultUuid = jest
|
||||
.fn()
|
||||
.mockReturnValueOnce(
|
||||
SharedVaultUser.create({
|
||||
permission: SharedVaultUserPermission.create(SharedVaultUserPermission.PERMISSIONS.Write).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(),
|
||||
)
|
||||
.mockReturnValueOnce(
|
||||
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(),
|
||||
)
|
||||
|
||||
const useCase = createUseCase()
|
||||
const result = await useCase.execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
|
||||
remoteIdentifier: 'remote-identifier',
|
||||
operation: ValetTokenOperation.Move,
|
||||
moveOperationType: 'shared-vault-to-shared-vault',
|
||||
sharedVaultToSharedVaultMoveTargetUuid: '00000000-0000-0000-0000-000000000000',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
expect(result.getError()).toBe('User does not have permission to perform this operation')
|
||||
})
|
||||
|
||||
it('should create move valet token for shared-vault-to-shared-vault operation', async () => {
|
||||
sharedVaultUserRepository.findByUserUuidAndSharedVaultUuid = jest
|
||||
.fn()
|
||||
.mockReturnValueOnce(
|
||||
SharedVaultUser.create({
|
||||
permission: SharedVaultUserPermission.create(SharedVaultUserPermission.PERMISSIONS.Write).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(),
|
||||
)
|
||||
.mockReturnValueOnce(
|
||||
SharedVaultUser.create({
|
||||
permission: SharedVaultUserPermission.create(SharedVaultUserPermission.PERMISSIONS.Write).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(),
|
||||
)
|
||||
|
||||
const useCase = createUseCase()
|
||||
const result = await useCase.execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
|
||||
remoteIdentifier: 'remote-identifier',
|
||||
operation: ValetTokenOperation.Move,
|
||||
moveOperationType: 'shared-vault-to-shared-vault',
|
||||
sharedVaultToSharedVaultMoveTargetUuid: '00000000-0000-0000-0000-000000000000',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(false)
|
||||
expect(result.getValue()).toBe('encoded-token')
|
||||
})
|
||||
|
||||
it('should create move valet token for shared-vault-to-user operation', async () => {
|
||||
sharedVaultUserRepository.findByUserUuidAndSharedVaultUuid = jest
|
||||
.fn()
|
||||
.mockReturnValueOnce(
|
||||
SharedVaultUser.create({
|
||||
permission: SharedVaultUserPermission.create(SharedVaultUserPermission.PERMISSIONS.Write).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(),
|
||||
)
|
||||
.mockReturnValueOnce(
|
||||
SharedVaultUser.create({
|
||||
permission: SharedVaultUserPermission.create(SharedVaultUserPermission.PERMISSIONS.Write).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(),
|
||||
)
|
||||
|
||||
const useCase = createUseCase()
|
||||
const result = await useCase.execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
|
||||
remoteIdentifier: 'remote-identifier',
|
||||
operation: ValetTokenOperation.Move,
|
||||
moveOperationType: 'shared-vault-to-user',
|
||||
sharedVaultToSharedVaultMoveTargetUuid: '00000000-0000-0000-0000-000000000000',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(false)
|
||||
expect(result.getValue()).toBe('encoded-token')
|
||||
})
|
||||
|
||||
it('should create move valet token for user-to-shared-vault operation', async () => {
|
||||
sharedVaultUserRepository.findByUserUuidAndSharedVaultUuid = jest
|
||||
.fn()
|
||||
.mockReturnValueOnce(
|
||||
SharedVaultUser.create({
|
||||
permission: SharedVaultUserPermission.create(SharedVaultUserPermission.PERMISSIONS.Write).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(),
|
||||
)
|
||||
.mockReturnValueOnce(
|
||||
SharedVaultUser.create({
|
||||
permission: SharedVaultUserPermission.create(SharedVaultUserPermission.PERMISSIONS.Write).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(),
|
||||
)
|
||||
|
||||
const useCase = createUseCase()
|
||||
const result = await useCase.execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
|
||||
remoteIdentifier: 'remote-identifier',
|
||||
operation: ValetTokenOperation.Move,
|
||||
moveOperationType: 'user-to-shared-vault',
|
||||
sharedVaultToSharedVaultMoveTargetUuid: '00000000-0000-0000-0000-000000000000',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(false)
|
||||
expect(result.getValue()).toBe('encoded-token')
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,124 @@
|
||||
import { SharedVaultValetTokenData, TokenEncoderInterface, ValetTokenOperation } from '@standardnotes/security'
|
||||
|
||||
import { SharedVaultRepositoryInterface } from '../../SharedVault/SharedVaultRepositoryInterface'
|
||||
import { Result, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
|
||||
import { SharedVaultUserRepositoryInterface } from '../../SharedVault/User/SharedVaultUserRepositoryInterface'
|
||||
import { CreateSharedVaultFileValetTokenDTO } from './CreateSharedVaultFileValetTokenDTO'
|
||||
import { SharedVaultUserPermission } from '../../SharedVault/User/SharedVaultUserPermission'
|
||||
|
||||
export class CreateSharedVaultFileValetToken implements UseCaseInterface<string> {
|
||||
constructor(
|
||||
private sharedVaultRepository: SharedVaultRepositoryInterface,
|
||||
private sharedVaultUserRepository: SharedVaultUserRepositoryInterface,
|
||||
private tokenEncoder: TokenEncoderInterface<SharedVaultValetTokenData>,
|
||||
private valetTokenTTL: number,
|
||||
) {}
|
||||
|
||||
async execute(dto: CreateSharedVaultFileValetTokenDTO): Promise<Result<string>> {
|
||||
const sharedVaultUuidOrError = Uuid.create(dto.sharedVaultUuid)
|
||||
if (sharedVaultUuidOrError.isFailed()) {
|
||||
return Result.fail(sharedVaultUuidOrError.getError())
|
||||
}
|
||||
const sharedVaultUuid = sharedVaultUuidOrError.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 sharedVaultUser = await this.sharedVaultUserRepository.findByUserUuidAndSharedVaultUuid({
|
||||
userUuid: userUuid,
|
||||
sharedVaultUuid: sharedVaultUuid,
|
||||
})
|
||||
if (!sharedVaultUser) {
|
||||
return Result.fail('Shared vault user not found')
|
||||
}
|
||||
|
||||
if (
|
||||
sharedVaultUser.props.permission.value === SharedVaultUserPermission.PERMISSIONS.Read &&
|
||||
dto.operation !== ValetTokenOperation.Read
|
||||
) {
|
||||
return Result.fail('User does not have permission to perform this operation')
|
||||
}
|
||||
|
||||
if (dto.operation === ValetTokenOperation.Move) {
|
||||
if (!dto.moveOperationType) {
|
||||
return Result.fail('Move operation type is required')
|
||||
}
|
||||
|
||||
if (dto.moveOperationType === 'shared-vault-to-shared-vault') {
|
||||
if (!dto.sharedVaultToSharedVaultMoveTargetUuid) {
|
||||
return Result.fail('Shared vault to shared vault move target uuid is required')
|
||||
}
|
||||
|
||||
const sharedVaultTargetUuidOrError = Uuid.create(dto.sharedVaultToSharedVaultMoveTargetUuid)
|
||||
if (sharedVaultTargetUuidOrError.isFailed()) {
|
||||
return Result.fail(sharedVaultTargetUuidOrError.getError())
|
||||
}
|
||||
const sharedVaultTargetUuid = sharedVaultTargetUuidOrError.getValue()
|
||||
|
||||
const toSharedVaultUser = await this.sharedVaultUserRepository.findByUserUuidAndSharedVaultUuid({
|
||||
userUuid: userUuid,
|
||||
sharedVaultUuid: sharedVaultTargetUuid,
|
||||
})
|
||||
|
||||
if (!toSharedVaultUser) {
|
||||
return Result.fail('Shared vault target user not found')
|
||||
}
|
||||
|
||||
if (toSharedVaultUser.props.permission.value === SharedVaultUserPermission.PERMISSIONS.Read) {
|
||||
return Result.fail('User does not have permission to perform this operation')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const tokenData: SharedVaultValetTokenData = {
|
||||
sharedVaultUuid: dto.sharedVaultUuid,
|
||||
permittedOperation: dto.operation,
|
||||
remoteIdentifier: dto.remoteIdentifier,
|
||||
uploadBytesUsed: sharedVault.props.fileUploadBytesUsed,
|
||||
uploadBytesLimit: sharedVault.props.fileUploadBytesLimit,
|
||||
unencryptedFileSize: dto.unencryptedFileSize,
|
||||
moveOperation: this.createMoveOperationData(dto),
|
||||
}
|
||||
|
||||
const valetToken = this.tokenEncoder.encodeExpirableToken(tokenData, this.valetTokenTTL)
|
||||
|
||||
return Result.ok(valetToken)
|
||||
}
|
||||
|
||||
private createMoveOperationData(dto: CreateSharedVaultFileValetTokenDTO): SharedVaultValetTokenData['moveOperation'] {
|
||||
if (!dto.moveOperationType) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
let fromUuid: string
|
||||
let toUuid: string
|
||||
switch (dto.moveOperationType) {
|
||||
case 'shared-vault-to-user':
|
||||
fromUuid = dto.sharedVaultUuid
|
||||
toUuid = dto.userUuid
|
||||
break
|
||||
case 'user-to-shared-vault':
|
||||
fromUuid = dto.userUuid
|
||||
toUuid = dto.sharedVaultUuid
|
||||
break
|
||||
case 'shared-vault-to-shared-vault':
|
||||
fromUuid = dto.sharedVaultUuid
|
||||
toUuid = dto.sharedVaultToSharedVaultMoveTargetUuid as string
|
||||
break
|
||||
}
|
||||
|
||||
return {
|
||||
type: dto.moveOperationType,
|
||||
fromUuid,
|
||||
toUuid,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { SharedVaultMoveType, ValetTokenOperation } from '@standardnotes/security'
|
||||
|
||||
export interface CreateSharedVaultFileValetTokenDTO {
|
||||
userUuid: string
|
||||
sharedVaultUuid: string
|
||||
fileUuid?: string
|
||||
remoteIdentifier: string
|
||||
operation: ValetTokenOperation
|
||||
unencryptedFileSize?: number
|
||||
moveOperationType?: SharedVaultMoveType
|
||||
sharedVaultToSharedVaultMoveTargetUuid?: string
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
import { Timestamps, Uuid } 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 { GetSharedVaults } from './GetSharedVaults'
|
||||
|
||||
describe('GetSharedVaults', () => {
|
||||
let sharedVaultUserRepository: SharedVaultUserRepositoryInterface
|
||||
let sharedVaultRepository: SharedVaultRepositoryInterface
|
||||
let sharedVault: SharedVault
|
||||
let sharedVaultUser: SharedVaultUser
|
||||
|
||||
const createUseCase = () => new GetSharedVaults(sharedVaultUserRepository, sharedVaultRepository)
|
||||
|
||||
beforeEach(() => {
|
||||
sharedVaultUser = SharedVaultUser.create({
|
||||
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
sharedVaultUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
permission: SharedVaultUserPermission.create(SharedVaultUserPermission.PERMISSIONS.Admin).getValue(),
|
||||
timestamps: Timestamps.create(123, 123).getValue(),
|
||||
}).getValue()
|
||||
sharedVaultUserRepository = {} as jest.Mocked<SharedVaultUserRepositoryInterface>
|
||||
sharedVaultUserRepository.findByUserUuid = jest.fn().mockResolvedValue([sharedVaultUser])
|
||||
|
||||
sharedVault = SharedVault.create({
|
||||
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
timestamps: Timestamps.create(123, 123).getValue(),
|
||||
fileUploadBytesLimit: 123,
|
||||
fileUploadBytesUsed: 123,
|
||||
}).getValue()
|
||||
sharedVaultRepository = {} as jest.Mocked<SharedVaultRepositoryInterface>
|
||||
sharedVaultRepository.findByUuids = jest.fn().mockResolvedValue([sharedVault])
|
||||
})
|
||||
|
||||
it('returns shared vaults', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
})
|
||||
|
||||
expect(result.getValue()).toEqual([sharedVault])
|
||||
})
|
||||
|
||||
it('returns empty array if no shared vaults found', async () => {
|
||||
sharedVaultUserRepository.findByUserUuid = jest.fn().mockResolvedValue([])
|
||||
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
})
|
||||
|
||||
expect(result.getValue()).toEqual([])
|
||||
})
|
||||
|
||||
it('returns error if user uuid is invalid', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: 'invalid',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBeTruthy()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,35 @@
|
||||
import { Result, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
|
||||
|
||||
import { SharedVault } from '../../SharedVault/SharedVault'
|
||||
import { GetSharedVaultsDTO } from './GetSharedVaultsDTO'
|
||||
import { SharedVaultUserRepositoryInterface } from '../../SharedVault/User/SharedVaultUserRepositoryInterface'
|
||||
import { SharedVaultRepositoryInterface } from '../../SharedVault/SharedVaultRepositoryInterface'
|
||||
|
||||
export class GetSharedVaults implements UseCaseInterface<SharedVault[]> {
|
||||
constructor(
|
||||
private sharedVaultUserRepository: SharedVaultUserRepositoryInterface,
|
||||
private sharedVaultRepository: SharedVaultRepositoryInterface,
|
||||
) {}
|
||||
|
||||
async execute(dto: GetSharedVaultsDTO): Promise<Result<SharedVault[]>> {
|
||||
const userUuidOrError = Uuid.create(dto.userUuid)
|
||||
if (userUuidOrError.isFailed()) {
|
||||
return Result.fail(userUuidOrError.getError())
|
||||
}
|
||||
const userUuid = userUuidOrError.getValue()
|
||||
|
||||
const ownedSharedVaultsAssociations = await this.sharedVaultUserRepository.findByUserUuid(userUuid)
|
||||
|
||||
const sharedVaultUuids = ownedSharedVaultsAssociations.map(
|
||||
(sharedVaultUser) => sharedVaultUser.props.sharedVaultUuid,
|
||||
)
|
||||
|
||||
if (sharedVaultUuids.length === 0) {
|
||||
return Result.ok([])
|
||||
}
|
||||
|
||||
const sharedVaults = await this.sharedVaultRepository.findByUuids(sharedVaultUuids, dto.lastSyncTime)
|
||||
|
||||
return Result.ok(sharedVaults)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export interface GetSharedVaultsDTO {
|
||||
userUuid: string
|
||||
lastSyncTime?: number
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
import { SharedVaultRepositoryInterface } from '../../SharedVault/SharedVaultRepositoryInterface'
|
||||
import { SharedVaultInviteRepositoryInterface } from '../../SharedVault/User/Invite/SharedVaultInviteRepositoryInterface'
|
||||
import { InviteUserToSharedVault } from './InviteUserToSharedVault'
|
||||
import { SharedVault } from '../../SharedVault/SharedVault'
|
||||
import { SharedVaultInvite } from '../../SharedVault/User/Invite/SharedVaultInvite'
|
||||
import { Uuid, Timestamps, Result } from '@standardnotes/domain-core'
|
||||
import { SharedVaultUserPermission } from '../../SharedVault/User/SharedVaultUserPermission'
|
||||
|
||||
describe('InviteUserToSharedVault', () => {
|
||||
let sharedVaultRepository: SharedVaultRepositoryInterface
|
||||
let sharedVaultInviteRepository: SharedVaultInviteRepositoryInterface
|
||||
let timer: TimerInterface
|
||||
let sharedVault: SharedVault
|
||||
|
||||
const createUseCase = () => new InviteUserToSharedVault(sharedVaultRepository, sharedVaultInviteRepository, timer)
|
||||
|
||||
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)
|
||||
|
||||
sharedVaultInviteRepository = {} as jest.Mocked<SharedVaultInviteRepositoryInterface>
|
||||
sharedVaultInviteRepository.findByUserUuidAndSharedVaultUuid = jest.fn().mockResolvedValue(null)
|
||||
sharedVaultInviteRepository.save = jest.fn()
|
||||
sharedVaultInviteRepository.remove = jest.fn()
|
||||
|
||||
timer = {} as jest.Mocked<TimerInterface>
|
||||
timer.getTimestampInMicroseconds = jest.fn().mockReturnValue(123)
|
||||
})
|
||||
|
||||
it('should return a failure result if the shared vault uuid is invalid', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
sharedVaultUuid: 'invalid',
|
||||
senderUuid: '00000000-0000-0000-0000-000000000000',
|
||||
recipientUuid: '00000000-0000-0000-0000-000000000000',
|
||||
permission: SharedVaultUserPermission.PERMISSIONS.Read,
|
||||
encryptedMessage: 'encryptedMessage',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
expect(result.getError()).toBe('Given value is not a valid uuid: invalid')
|
||||
})
|
||||
|
||||
it('should return a failure result if the shared vault does not exist', async () => {
|
||||
const useCase = createUseCase()
|
||||
sharedVaultRepository.findByUuid = jest.fn().mockResolvedValue(undefined)
|
||||
|
||||
const result = await useCase.execute({
|
||||
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
|
||||
senderUuid: '00000000-0000-0000-0000-000000000000',
|
||||
recipientUuid: '00000000-0000-0000-0000-000000000000',
|
||||
permission: SharedVaultUserPermission.PERMISSIONS.Read,
|
||||
encryptedMessage: 'encryptedMessage',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
expect(result.getError()).toBe('Attempting to invite a user to a non-existent shared vault')
|
||||
})
|
||||
|
||||
it('should return a failure result if the sender uuid is invalid', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
|
||||
senderUuid: 'invalid',
|
||||
recipientUuid: '00000000-0000-0000-0000-000000000000',
|
||||
permission: SharedVaultUserPermission.PERMISSIONS.Read,
|
||||
encryptedMessage: 'encryptedMessage',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
expect(result.getError()).toBe('Given value is not a valid uuid: invalid')
|
||||
})
|
||||
|
||||
it('should return a failure result if the recipient uuid is invalid', async () => {
|
||||
const useCase = createUseCase()
|
||||
sharedVaultRepository.findByUuid = jest.fn().mockResolvedValue(sharedVault)
|
||||
|
||||
const result = await useCase.execute({
|
||||
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
|
||||
senderUuid: '00000000-0000-0000-0000-000000000000',
|
||||
recipientUuid: 'invalid',
|
||||
permission: SharedVaultUserPermission.PERMISSIONS.Read,
|
||||
encryptedMessage: 'encryptedMessage',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
expect(result.getError()).toBe('Given value is not a valid uuid: invalid')
|
||||
})
|
||||
|
||||
it('should remove an already existing invite', async () => {
|
||||
const useCase = createUseCase()
|
||||
sharedVaultInviteRepository.findByUserUuidAndSharedVaultUuid = jest
|
||||
.fn()
|
||||
.mockResolvedValue({} as jest.Mocked<SharedVaultInvite>)
|
||||
|
||||
const result = await useCase.execute({
|
||||
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
|
||||
senderUuid: '00000000-0000-0000-0000-000000000000',
|
||||
recipientUuid: '00000000-0000-0000-0000-000000000000',
|
||||
permission: SharedVaultUserPermission.PERMISSIONS.Read,
|
||||
encryptedMessage: 'encryptedMessage',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(false)
|
||||
expect(sharedVaultInviteRepository.remove).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should create a shared vault invite', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
|
||||
senderUuid: '00000000-0000-0000-0000-000000000000',
|
||||
recipientUuid: '00000000-0000-0000-0000-000000000000',
|
||||
permission: SharedVaultUserPermission.PERMISSIONS.Read,
|
||||
encryptedMessage: 'encryptedMessage',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(false)
|
||||
expect(result.getValue().props.sharedVaultUuid.value).toBe('00000000-0000-0000-0000-000000000000')
|
||||
})
|
||||
|
||||
it('should return a failure if the permission is invalid', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
|
||||
senderUuid: '00000000-0000-0000-0000-000000000000',
|
||||
recipientUuid: '00000000-0000-0000-0000-000000000000',
|
||||
permission: 'invalid',
|
||||
encryptedMessage: 'encryptedMessage',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
expect(result.getError()).toBe('Invalid shared vault user permission invalid')
|
||||
})
|
||||
|
||||
it('should return a failure if the sender is not the owner of the shared vault', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
sharedVault = SharedVault.create({
|
||||
fileUploadBytesLimit: 100,
|
||||
fileUploadBytesUsed: 2,
|
||||
userUuid: Uuid.create('10000000-0000-0000-0000-000000000000').getValue(),
|
||||
timestamps: Timestamps.create(123, 123).getValue(),
|
||||
}).getValue()
|
||||
sharedVaultRepository.findByUuid = jest.fn().mockResolvedValue(sharedVault)
|
||||
|
||||
const result = await useCase.execute({
|
||||
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
|
||||
senderUuid: '00000000-0000-0000-0000-000000000001',
|
||||
recipientUuid: '00000000-0000-0000-0000-000000000000',
|
||||
permission: SharedVaultUserPermission.PERMISSIONS.Read,
|
||||
encryptedMessage: 'encryptedMessage',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
expect(result.getError()).toBe('Only the owner of a shared vault can invite users to it')
|
||||
})
|
||||
|
||||
it('should return a failure if the shared vault invite could not be created', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
const mockSharedVaultInvite = jest.spyOn(SharedVaultInvite, 'create')
|
||||
mockSharedVaultInvite.mockImplementation(() => {
|
||||
return Result.fail('Oops')
|
||||
})
|
||||
|
||||
const result = await useCase.execute({
|
||||
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
|
||||
senderUuid: '00000000-0000-0000-0000-000000000000',
|
||||
recipientUuid: '00000000-0000-0000-0000-000000000000',
|
||||
permission: SharedVaultUserPermission.PERMISSIONS.Read,
|
||||
encryptedMessage: 'encryptedMessage',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
expect(result.getError()).toBe('Oops')
|
||||
|
||||
mockSharedVaultInvite.mockRestore()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,77 @@
|
||||
import { Result, Timestamps, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
|
||||
import { SharedVaultInvite } from '../../SharedVault/User/Invite/SharedVaultInvite'
|
||||
import { SharedVaultRepositoryInterface } from '../../SharedVault/SharedVaultRepositoryInterface'
|
||||
import { InviteUserToSharedVaultDTO } from './InviteUserToSharedVaultDTO'
|
||||
import { SharedVaultInviteRepositoryInterface } from '../../SharedVault/User/Invite/SharedVaultInviteRepositoryInterface'
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
import { SharedVaultUserPermission } from '../../SharedVault/User/SharedVaultUserPermission'
|
||||
|
||||
export class InviteUserToSharedVault implements UseCaseInterface<SharedVaultInvite> {
|
||||
constructor(
|
||||
private sharedVaultRepository: SharedVaultRepositoryInterface,
|
||||
private sharedVaultInviteRepository: SharedVaultInviteRepositoryInterface,
|
||||
private timer: TimerInterface,
|
||||
) {}
|
||||
async execute(dto: InviteUserToSharedVaultDTO): Promise<Result<SharedVaultInvite>> {
|
||||
const sharedVaultUuidOrError = Uuid.create(dto.sharedVaultUuid)
|
||||
if (sharedVaultUuidOrError.isFailed()) {
|
||||
return Result.fail(sharedVaultUuidOrError.getError())
|
||||
}
|
||||
const sharedVaultUuid = sharedVaultUuidOrError.getValue()
|
||||
|
||||
const senderUuidOrError = Uuid.create(dto.senderUuid)
|
||||
if (senderUuidOrError.isFailed()) {
|
||||
return Result.fail(senderUuidOrError.getError())
|
||||
}
|
||||
const senderUuid = senderUuidOrError.getValue()
|
||||
|
||||
const recipientUuidOrError = Uuid.create(dto.recipientUuid)
|
||||
if (recipientUuidOrError.isFailed()) {
|
||||
return Result.fail(recipientUuidOrError.getError())
|
||||
}
|
||||
const recipientUuid = recipientUuidOrError.getValue()
|
||||
|
||||
const permissionOrError = SharedVaultUserPermission.create(dto.permission)
|
||||
if (permissionOrError.isFailed()) {
|
||||
return Result.fail(permissionOrError.getError())
|
||||
}
|
||||
const permission = permissionOrError.getValue()
|
||||
|
||||
const sharedVault = await this.sharedVaultRepository.findByUuid(sharedVaultUuid)
|
||||
if (!sharedVault) {
|
||||
return Result.fail('Attempting to invite a user to a non-existent shared vault')
|
||||
}
|
||||
|
||||
if (sharedVault.props.userUuid.value !== senderUuid.value) {
|
||||
return Result.fail('Only the owner of a shared vault can invite users to it')
|
||||
}
|
||||
|
||||
const existingInvite = await this.sharedVaultInviteRepository.findByUserUuidAndSharedVaultUuid({
|
||||
userUuid: recipientUuid,
|
||||
sharedVaultUuid,
|
||||
})
|
||||
if (existingInvite) {
|
||||
await this.sharedVaultInviteRepository.remove(existingInvite)
|
||||
}
|
||||
|
||||
const sharedVaultInviteOrError = SharedVaultInvite.create({
|
||||
encryptedMessage: dto.encryptedMessage,
|
||||
userUuid: recipientUuid,
|
||||
sharedVaultUuid,
|
||||
senderUuid,
|
||||
permission,
|
||||
timestamps: Timestamps.create(
|
||||
this.timer.getTimestampInMicroseconds(),
|
||||
this.timer.getTimestampInMicroseconds(),
|
||||
).getValue(),
|
||||
})
|
||||
if (sharedVaultInviteOrError.isFailed()) {
|
||||
return Result.fail(sharedVaultInviteOrError.getError())
|
||||
}
|
||||
const sharedVaultInvite = sharedVaultInviteOrError.getValue()
|
||||
|
||||
await this.sharedVaultInviteRepository.save(sharedVaultInvite)
|
||||
|
||||
return Result.ok(sharedVaultInvite)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
export interface InviteUserToSharedVaultDTO {
|
||||
sharedVaultUuid: string
|
||||
senderUuid: string
|
||||
recipientUuid: string
|
||||
encryptedMessage: string
|
||||
permission: string
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { Result, UseCaseInterface } from '@standardnotes/domain-core'
|
||||
|
||||
import { RemoveUserEventsDTO } from './RemoveUserEventsDTO'
|
||||
|
||||
export class RemoveUserEvents implements UseCaseInterface<void> {
|
||||
async execute(_dto: RemoveUserEventsDTO): Promise<Result<void>> {
|
||||
throw new Error('Method not implemented.')
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export interface RemoveUserEventsDTO {
|
||||
sharedVaultUuid: string
|
||||
userUuid: string
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'
|
||||
|
||||
@Entity({ name: 'shared_vault_invites' })
|
||||
export class TypeORMSharedVaultInvite {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
declare uuid: string
|
||||
|
||||
@Column({
|
||||
name: 'shared_vault_uuid',
|
||||
length: 36,
|
||||
})
|
||||
declare sharedVaultUuid: string
|
||||
|
||||
@Column({
|
||||
name: 'user_uuid',
|
||||
length: 36,
|
||||
})
|
||||
declare userUuid: string
|
||||
|
||||
@Column({
|
||||
name: 'sender_uuid',
|
||||
length: 36,
|
||||
})
|
||||
declare senderUuid: string
|
||||
|
||||
@Column({
|
||||
name: 'encrypted_message',
|
||||
type: 'text',
|
||||
})
|
||||
declare encryptedMessage: string
|
||||
|
||||
@Column({
|
||||
name: 'permission',
|
||||
type: 'varchar',
|
||||
length: 24,
|
||||
})
|
||||
declare permission: string
|
||||
|
||||
@Column({
|
||||
name: 'created_at_timestamp',
|
||||
type: 'bigint',
|
||||
})
|
||||
declare createdAtTimestamp: number
|
||||
|
||||
@Column({
|
||||
name: 'updated_at_timestamp',
|
||||
type: 'bigint',
|
||||
})
|
||||
declare updatedAtTimestamp: number
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import { Repository } from 'typeorm'
|
||||
import { MapperInterface, Uuid } from '@standardnotes/domain-core'
|
||||
|
||||
import { TypeORMSharedVaultInvite } from './TypeORMSharedVaultInvite'
|
||||
import { SharedVaultInvite } from '../../Domain/SharedVault/User/Invite/SharedVaultInvite'
|
||||
import { SharedVaultInviteRepositoryInterface } from '../../Domain/SharedVault/User/Invite/SharedVaultInviteRepositoryInterface'
|
||||
|
||||
export class TypeORMSharedVaultInviteRepository implements SharedVaultInviteRepositoryInterface {
|
||||
constructor(
|
||||
private ormRepository: Repository<TypeORMSharedVaultInvite>,
|
||||
private mapper: MapperInterface<SharedVaultInvite, TypeORMSharedVaultInvite>,
|
||||
) {}
|
||||
|
||||
async findByUserUuidAndSharedVaultUuid(dto: {
|
||||
userUuid: Uuid
|
||||
sharedVaultUuid: Uuid
|
||||
}): Promise<SharedVaultInvite | null> {
|
||||
const persistence = await this.ormRepository
|
||||
.createQueryBuilder('shared_vault_invite')
|
||||
.where('shared_vault_invite.user_uuid = :uuid', {
|
||||
uuid: dto.userUuid.value,
|
||||
})
|
||||
.andWhere('shared_vault_invite.shared_vault_uuid = :sharedVaultUuid', {
|
||||
sharedVaultUuid: dto.sharedVaultUuid.value,
|
||||
})
|
||||
.getOne()
|
||||
|
||||
if (persistence === null) {
|
||||
return null
|
||||
}
|
||||
|
||||
return this.mapper.toDomain(persistence)
|
||||
}
|
||||
|
||||
async save(sharedVaultInvite: SharedVaultInvite): Promise<void> {
|
||||
const persistence = this.mapper.toProjection(sharedVaultInvite)
|
||||
|
||||
await this.ormRepository.save(persistence)
|
||||
}
|
||||
|
||||
async findByUuid(uuid: Uuid): Promise<SharedVaultInvite | null> {
|
||||
const persistence = await this.ormRepository
|
||||
.createQueryBuilder('shared_vault_invite')
|
||||
.where('shared_vault_invite.uuid = :uuid', {
|
||||
uuid: uuid.value,
|
||||
})
|
||||
.getOne()
|
||||
|
||||
if (persistence === null) {
|
||||
return null
|
||||
}
|
||||
|
||||
return this.mapper.toDomain(persistence)
|
||||
}
|
||||
|
||||
async remove(sharedVaultInvite: SharedVaultInvite): Promise<void> {
|
||||
await this.ormRepository.remove(this.mapper.toProjection(sharedVaultInvite))
|
||||
}
|
||||
}
|
||||
@@ -1,27 +1,41 @@
|
||||
import { Repository } from 'typeorm'
|
||||
import { MapperInterface } from '@standardnotes/domain-core'
|
||||
import { MapperInterface, Uuid } from '@standardnotes/domain-core'
|
||||
|
||||
import { SharedVaultsRepositoryInterface } from '../../Domain/SharedVault/SharedVaultsRepositoryInterface'
|
||||
import { SharedVaultRepositoryInterface } from '../../Domain/SharedVault/SharedVaultRepositoryInterface'
|
||||
import { TypeORMSharedVault } from './TypeORMSharedVault'
|
||||
import { SharedVault } from '../../Domain/SharedVault/SharedVault'
|
||||
|
||||
export class TypeORMSharedVaultRepository implements SharedVaultsRepositoryInterface {
|
||||
export class TypeORMSharedVaultRepository implements SharedVaultRepositoryInterface {
|
||||
constructor(
|
||||
private ormRepository: Repository<TypeORMSharedVault>,
|
||||
private mapper: MapperInterface<SharedVault, TypeORMSharedVault>,
|
||||
) {}
|
||||
|
||||
async findByUuids(uuids: Uuid[], lastSyncTime?: number | undefined): Promise<SharedVault[]> {
|
||||
const queryBuilder = await this.ormRepository
|
||||
.createQueryBuilder('shared_vault')
|
||||
.where('shared_vault.uuid IN (:...sharedVaultUuids)', { sharedVaultUuids: uuids.map((uuid) => uuid.value) })
|
||||
|
||||
if (lastSyncTime !== undefined) {
|
||||
queryBuilder.andWhere('shared_vault.updated_at_timestamp > :lastSyncTime', { lastSyncTime })
|
||||
}
|
||||
|
||||
const persistence = await queryBuilder.getMany()
|
||||
|
||||
return persistence.map((p) => this.mapper.toDomain(p))
|
||||
}
|
||||
|
||||
async save(sharedVault: SharedVault): Promise<void> {
|
||||
const persistence = this.mapper.toProjection(sharedVault)
|
||||
|
||||
await this.ormRepository.save(persistence)
|
||||
}
|
||||
|
||||
async findByUuid(uuid: string): Promise<SharedVault | null> {
|
||||
async findByUuid(uuid: Uuid): Promise<SharedVault | null> {
|
||||
const persistence = await this.ormRepository
|
||||
.createQueryBuilder('shared_vault')
|
||||
.where('shared_vault.uuid = :uuid', {
|
||||
uuid,
|
||||
uuid: uuid.value,
|
||||
})
|
||||
.getOne()
|
||||
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'
|
||||
|
||||
@Entity({ name: 'shared_vault_users' })
|
||||
export class TypeORMSharedVaultUser {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
declare uuid: string
|
||||
|
||||
@Column({
|
||||
name: 'shared_vault_uuid',
|
||||
length: 36,
|
||||
})
|
||||
declare sharedVaultUuid: string
|
||||
|
||||
@Column({
|
||||
name: 'user_uuid',
|
||||
length: 36,
|
||||
})
|
||||
declare userUuid: string
|
||||
|
||||
@Column({
|
||||
name: 'permission',
|
||||
type: 'varchar',
|
||||
length: 24,
|
||||
})
|
||||
declare permission: string
|
||||
|
||||
@Column({
|
||||
name: 'created_at_timestamp',
|
||||
type: 'bigint',
|
||||
})
|
||||
declare createdAtTimestamp: number
|
||||
|
||||
@Column({
|
||||
name: 'updated_at_timestamp',
|
||||
type: 'bigint',
|
||||
})
|
||||
declare updatedAtTimestamp: number
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import { Repository } from 'typeorm'
|
||||
import { MapperInterface, Uuid } from '@standardnotes/domain-core'
|
||||
|
||||
import { TypeORMSharedVaultUser } from './TypeORMSharedVaultUser'
|
||||
import { SharedVaultUser } from '../../Domain/SharedVault/User/SharedVaultUser'
|
||||
import { SharedVaultUserRepositoryInterface } from '../../Domain/SharedVault/User/SharedVaultUserRepositoryInterface'
|
||||
|
||||
export class TypeORMSharedVaultUserRepository implements SharedVaultUserRepositoryInterface {
|
||||
constructor(
|
||||
private ormRepository: Repository<TypeORMSharedVaultUser>,
|
||||
private mapper: MapperInterface<SharedVaultUser, TypeORMSharedVaultUser>,
|
||||
) {}
|
||||
|
||||
async findByUserUuid(userUuid: Uuid): Promise<SharedVaultUser[]> {
|
||||
const persistence = await this.ormRepository
|
||||
.createQueryBuilder('shared_vault_user')
|
||||
.where('shared_vault_user.user_uuid = :userUuid', {
|
||||
userUuid: userUuid.value,
|
||||
})
|
||||
.getMany()
|
||||
|
||||
return persistence.map((p) => this.mapper.toDomain(p))
|
||||
}
|
||||
|
||||
async findByUserUuidAndSharedVaultUuid(dto: {
|
||||
userUuid: Uuid
|
||||
sharedVaultUuid: Uuid
|
||||
}): Promise<SharedVaultUser | null> {
|
||||
const persistence = await this.ormRepository
|
||||
.createQueryBuilder('shared_vault_user')
|
||||
.where('shared_vault_user.user_uuid = :userUuid', {
|
||||
userUuid: dto.userUuid.value,
|
||||
})
|
||||
.andWhere('shared_vault_user.shared_vault_uuid = :sharedVaultUuid', {
|
||||
sharedVaultUuid: dto.sharedVaultUuid.value,
|
||||
})
|
||||
.getOne()
|
||||
|
||||
if (persistence === null) {
|
||||
return null
|
||||
}
|
||||
|
||||
return this.mapper.toDomain(persistence)
|
||||
}
|
||||
|
||||
async save(sharedVaultUser: SharedVaultUser): Promise<void> {
|
||||
const persistence = this.mapper.toProjection(sharedVaultUser)
|
||||
|
||||
await this.ormRepository.save(persistence)
|
||||
}
|
||||
|
||||
async findByUuid(uuid: Uuid): Promise<SharedVaultUser | null> {
|
||||
const persistence = await this.ormRepository
|
||||
.createQueryBuilder('shared_vault_user')
|
||||
.where('shared_vault_user.uuid = :uuid', {
|
||||
uuid: uuid.value,
|
||||
})
|
||||
.getOne()
|
||||
|
||||
if (persistence === null) {
|
||||
return null
|
||||
}
|
||||
|
||||
return this.mapper.toDomain(persistence)
|
||||
}
|
||||
|
||||
async remove(sharedVaultUser: SharedVaultUser): Promise<void> {
|
||||
await this.ormRepository.remove(this.mapper.toProjection(sharedVaultUser))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
import { Timestamps, MapperInterface, UniqueEntityId, Uuid, Validator } from '@standardnotes/domain-core'
|
||||
|
||||
import { SharedVaultInvite } from '../Domain/SharedVault/User/Invite/SharedVaultInvite'
|
||||
import { TypeORMSharedVaultInvite } from '../Infra/TypeORM/TypeORMSharedVaultInvite'
|
||||
import { SharedVaultUserPermission } from '../Domain/SharedVault/User/SharedVaultUserPermission'
|
||||
|
||||
export class SharedVaultInvitePersistenceMapper
|
||||
implements MapperInterface<SharedVaultInvite, TypeORMSharedVaultInvite>
|
||||
{
|
||||
toDomain(projection: TypeORMSharedVaultInvite): SharedVaultInvite {
|
||||
const userUuidOrError = Uuid.create(projection.userUuid)
|
||||
if (userUuidOrError.isFailed()) {
|
||||
throw new Error(`Failed to create shared vault invite from projection: ${userUuidOrError.getError()}`)
|
||||
}
|
||||
const userUuid = userUuidOrError.getValue()
|
||||
|
||||
const senderUuidOrError = Uuid.create(projection.senderUuid)
|
||||
if (senderUuidOrError.isFailed()) {
|
||||
throw new Error(`Failed to create shared vault invite from projection: ${senderUuidOrError.getError()}`)
|
||||
}
|
||||
const senderUuid = senderUuidOrError.getValue()
|
||||
|
||||
const sharedVaultUuidOrError = Uuid.create(projection.sharedVaultUuid)
|
||||
if (sharedVaultUuidOrError.isFailed()) {
|
||||
throw new Error(`Failed to create shared vault invite from projection: ${sharedVaultUuidOrError.getError()}`)
|
||||
}
|
||||
const sharedVaultUuid = sharedVaultUuidOrError.getValue()
|
||||
|
||||
const timestampsOrError = Timestamps.create(projection.createdAtTimestamp, projection.updatedAtTimestamp)
|
||||
if (timestampsOrError.isFailed()) {
|
||||
throw new Error(`Failed to create shared vault invite from projection: ${timestampsOrError.getError()}`)
|
||||
}
|
||||
const timestamps = timestampsOrError.getValue()
|
||||
|
||||
const permissionOrError = SharedVaultUserPermission.create(projection.permission)
|
||||
if (permissionOrError.isFailed()) {
|
||||
throw new Error(`Failed to create shared vault invite from projection: ${permissionOrError.getError()}`)
|
||||
}
|
||||
const permission = permissionOrError.getValue()
|
||||
|
||||
const notEmptyMessageValidationResult = Validator.isNotEmpty(projection.encryptedMessage)
|
||||
if (notEmptyMessageValidationResult.isFailed()) {
|
||||
throw new Error(
|
||||
`Failed to create shared vault invite from projection: ${notEmptyMessageValidationResult.getError()}`,
|
||||
)
|
||||
}
|
||||
|
||||
const sharedVaultInviteOrError = SharedVaultInvite.create(
|
||||
{
|
||||
userUuid,
|
||||
sharedVaultUuid,
|
||||
senderUuid,
|
||||
permission,
|
||||
timestamps,
|
||||
encryptedMessage: projection.encryptedMessage,
|
||||
},
|
||||
new UniqueEntityId(projection.uuid),
|
||||
)
|
||||
if (sharedVaultInviteOrError.isFailed()) {
|
||||
throw new Error(`Failed to create shared vault invite from projection: ${sharedVaultInviteOrError.getError()}`)
|
||||
}
|
||||
const sharedVaultInvite = sharedVaultInviteOrError.getValue()
|
||||
|
||||
return sharedVaultInvite
|
||||
}
|
||||
|
||||
toProjection(domain: SharedVaultInvite): TypeORMSharedVaultInvite {
|
||||
const typeorm = new TypeORMSharedVaultInvite()
|
||||
|
||||
typeorm.uuid = domain.id.toString()
|
||||
typeorm.sharedVaultUuid = domain.props.sharedVaultUuid.value
|
||||
typeorm.userUuid = domain.props.userUuid.value
|
||||
typeorm.permission = domain.props.permission.value
|
||||
typeorm.senderUuid = domain.props.senderUuid.value
|
||||
typeorm.encryptedMessage = domain.props.encryptedMessage
|
||||
typeorm.createdAtTimestamp = domain.props.timestamps.createdAt
|
||||
typeorm.updatedAtTimestamp = domain.props.timestamps.updatedAt
|
||||
|
||||
return typeorm
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
import { Timestamps, MapperInterface, UniqueEntityId, Uuid } from '@standardnotes/domain-core'
|
||||
|
||||
import { SharedVaultUser } from '../Domain/SharedVault/User/SharedVaultUser'
|
||||
import { TypeORMSharedVaultUser } from '../Infra/TypeORM/TypeORMSharedVaultUser'
|
||||
import { SharedVaultUserPermission } from '../Domain/SharedVault/User/SharedVaultUserPermission'
|
||||
|
||||
export class SharedVaultUserPersistenceMapper implements MapperInterface<SharedVaultUser, TypeORMSharedVaultUser> {
|
||||
toDomain(projection: TypeORMSharedVaultUser): SharedVaultUser {
|
||||
const userUuidOrError = Uuid.create(projection.userUuid)
|
||||
if (userUuidOrError.isFailed()) {
|
||||
throw new Error(`Failed to create shared vault user from projection: ${userUuidOrError.getError()}`)
|
||||
}
|
||||
const userUuid = userUuidOrError.getValue()
|
||||
|
||||
const sharedVaultUuidOrError = Uuid.create(projection.sharedVaultUuid)
|
||||
if (sharedVaultUuidOrError.isFailed()) {
|
||||
throw new Error(`Failed to create shared vault user from projection: ${sharedVaultUuidOrError.getError()}`)
|
||||
}
|
||||
const sharedVaultUuid = sharedVaultUuidOrError.getValue()
|
||||
|
||||
const timestampsOrError = Timestamps.create(projection.createdAtTimestamp, projection.updatedAtTimestamp)
|
||||
if (timestampsOrError.isFailed()) {
|
||||
throw new Error(`Failed to create shared vault user from projection: ${timestampsOrError.getError()}`)
|
||||
}
|
||||
const timestamps = timestampsOrError.getValue()
|
||||
|
||||
const permissionOrError = SharedVaultUserPermission.create(projection.permission)
|
||||
if (permissionOrError.isFailed()) {
|
||||
throw new Error(`Failed to create shared vault user from projection: ${permissionOrError.getError()}`)
|
||||
}
|
||||
const permission = permissionOrError.getValue()
|
||||
|
||||
const sharedVaultUserOrError = SharedVaultUser.create(
|
||||
{
|
||||
userUuid,
|
||||
sharedVaultUuid,
|
||||
permission,
|
||||
timestamps,
|
||||
},
|
||||
new UniqueEntityId(projection.uuid),
|
||||
)
|
||||
if (sharedVaultUserOrError.isFailed()) {
|
||||
throw new Error(`Failed to create shared vault user from projection: ${sharedVaultUserOrError.getError()}`)
|
||||
}
|
||||
const sharedVaultUser = sharedVaultUserOrError.getValue()
|
||||
|
||||
return sharedVaultUser
|
||||
}
|
||||
|
||||
toProjection(domain: SharedVaultUser): TypeORMSharedVaultUser {
|
||||
const typeorm = new TypeORMSharedVaultUser()
|
||||
|
||||
typeorm.uuid = domain.id.toString()
|
||||
typeorm.sharedVaultUuid = domain.props.sharedVaultUuid.value
|
||||
typeorm.userUuid = domain.props.userUuid.value
|
||||
typeorm.permission = domain.props.permission.value
|
||||
typeorm.createdAtTimestamp = domain.props.timestamps.createdAt
|
||||
typeorm.updatedAtTimestamp = domain.props.timestamps.updatedAt
|
||||
|
||||
return typeorm
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user