mirror of
https://github.com/standardnotes/server
synced 2026-01-28 02:01:09 -05:00
Compare commits
37 Commits
@standardn
...
@standardn
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2606f6d929 | ||
|
|
c288e5d8dc | ||
|
|
4b76d4b71e | ||
|
|
72310130d2 | ||
|
|
f9e51ef06e | ||
|
|
92a5eb0d98 | ||
|
|
77d2ea1a1f | ||
|
|
92f96ddb84 | ||
|
|
15a914e25e | ||
|
|
912a29d091 | ||
|
|
b2c32ce70e | ||
|
|
ed1a708c40 | ||
|
|
e905128d45 | ||
|
|
fd598f372a | ||
|
|
7a3946a9e2 | ||
|
|
cbdd2584d0 | ||
|
|
f3161c2712 | ||
|
|
148542dd5a | ||
|
|
d2b2c339f2 | ||
|
|
d2578c48f0 | ||
|
|
fecfd54728 | ||
|
|
17e4162d3e | ||
|
|
742209d773 | ||
|
|
1fa4b7cf27 | ||
|
|
5dc5507039 | ||
|
|
3035a20b9f | ||
|
|
04b3bb034f | ||
|
|
bf84be0136 | ||
|
|
890cf48749 | ||
|
|
2b3436c6ce | ||
|
|
4df8c3b2e5 | ||
|
|
25a2696c32 | ||
|
|
52f879f842 | ||
|
|
4f70fa156d | ||
|
|
38e77f04be | ||
|
|
060206ddd4 | ||
|
|
0bc0909386 |
3
.pnp.cjs
generated
3
.pnp.cjs
generated
@@ -5179,6 +5179,7 @@ const RAW_RUNTIME_STATE =
|
||||
["@standardnotes/responses", "npm:1.13.24"],\
|
||||
["@standardnotes/security", "workspace:packages/security"],\
|
||||
["@standardnotes/settings", "workspace:packages/settings"],\
|
||||
["@standardnotes/sncrypto-node", "workspace:packages/sncrypto-node"],\
|
||||
["@standardnotes/time", "workspace:packages/time"],\
|
||||
["@types/cors", "npm:2.8.13"],\
|
||||
["@types/dotenv", "npm:8.2.0"],\
|
||||
@@ -5188,6 +5189,7 @@ const RAW_RUNTIME_STATE =
|
||||
["@types/newrelic", "npm:9.14.0"],\
|
||||
["@types/node", "npm:20.2.5"],\
|
||||
["@types/prettyjson", "npm:0.0.30"],\
|
||||
["@types/semver", "npm:7.5.0"],\
|
||||
["@types/ua-parser-js", "npm:0.7.36"],\
|
||||
["@types/uuid", "npm:8.3.4"],\
|
||||
["@typescript-eslint/eslint-plugin", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:5.59.8"],\
|
||||
@@ -5210,6 +5212,7 @@ const RAW_RUNTIME_STATE =
|
||||
["prettier", "npm:2.8.8"],\
|
||||
["prettyjson", "npm:1.2.5"],\
|
||||
["reflect-metadata", "npm:0.1.13"],\
|
||||
["semver", "npm:7.5.1"],\
|
||||
["sqlite3", "virtual:31b5a94a105c89c9294c3d524a7f8929fe63ee5a2efadf21951ca4c0cfd2ecf02e8f4ef5a066bbda091f1e3a56e57c6749069a080618c96b22e51131a330fc4a#npm:5.1.6"],\
|
||||
["ts-jest", "virtual:fd909b174d079e30b336c4ce72c38a88c1e447767b1a8dd7655e07719a1e31b97807f0931368724fc78897ff15e6a6d00b83316c0f76d11f85111f342e08bb79#npm:29.1.0"],\
|
||||
["typeorm", "virtual:365b8c88cdf194291829ee28b79556e2328175d26a621363e703848100bea0042e9500db2a1206c9bbc3a4a76a1d169639ef774b2ea3a1a98584a9936b58c6be#npm:0.3.16"],\
|
||||
|
||||
@@ -3,6 +3,26 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [2.24.8](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.24.7...@standardnotes/analytics@2.24.8) (2023-07-07)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/analytics
|
||||
|
||||
## [2.24.7](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.24.6...@standardnotes/analytics@2.24.7) (2023-07-06)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/analytics
|
||||
|
||||
## [2.24.6](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.24.5...@standardnotes/analytics@2.24.6) (2023-07-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/analytics
|
||||
|
||||
## [2.24.5](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.24.4...@standardnotes/analytics@2.24.5) (2023-06-30)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/analytics
|
||||
|
||||
## [2.24.4](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.24.3...@standardnotes/analytics@2.24.4) (2023-06-30)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/analytics
|
||||
|
||||
## [2.24.3](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.24.2...@standardnotes/analytics@2.24.3) (2023-06-30)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/analytics
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/analytics",
|
||||
"version": "2.24.3",
|
||||
"version": "2.24.8",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,26 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.65.5](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.65.4...@standardnotes/api-gateway@1.65.5) (2023-07-07)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||
|
||||
## [1.65.4](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.65.3...@standardnotes/api-gateway@1.65.4) (2023-07-06)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||
|
||||
## [1.65.3](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.65.2...@standardnotes/api-gateway@1.65.3) (2023-07-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||
|
||||
## [1.65.2](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.65.1...@standardnotes/api-gateway@1.65.2) (2023-06-30)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||
|
||||
## [1.65.1](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.65.0...@standardnotes/api-gateway@1.65.1) (2023-06-30)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||
|
||||
# [1.65.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.64.3...@standardnotes/api-gateway@1.65.0) (2023-06-30)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/api-gateway",
|
||||
"version": "1.65.0",
|
||||
"version": "1.65.5",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,36 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.122.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.122.1...@standardnotes/auth-server@1.122.2) (2023-07-07)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* transfer notifications from auth to syncing-server. ([#648](https://github.com/standardnotes/server/issues/648)) ([c288e5d](https://github.com/standardnotes/server/commit/c288e5d8dc54778a96a9fc33e3c9cae00583fade))
|
||||
|
||||
## [1.122.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.122.0...@standardnotes/auth-server@1.122.1) (2023-07-06)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/auth-server
|
||||
|
||||
# [1.122.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.121.0...@standardnotes/auth-server@1.122.0) (2023-07-05)
|
||||
|
||||
### Features
|
||||
|
||||
* deleting shared vaults. ([#640](https://github.com/standardnotes/server/issues/640)) ([f3161c2](https://github.com/standardnotes/server/commit/f3161c271296159331639814b2dbb2e566cc54c9))
|
||||
|
||||
# [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
|
||||
|
||||
## [1.120.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.120.0...@standardnotes/auth-server@1.120.1) (2023-06-30)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/auth-server
|
||||
|
||||
# [1.120.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.119.6...@standardnotes/auth-server@1.120.0) (2023-06-30)
|
||||
|
||||
### Features
|
||||
|
||||
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,16 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm'
|
||||
|
||||
export class RemoveNotifications1688540448428 implements MigrationInterface {
|
||||
name = 'RemoveNotifications1688540448428'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query('DROP INDEX `index_notifications_on_user_uuid` ON `notifications`')
|
||||
await queryRunner.query('DROP TABLE `notifications`')
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
'CREATE TABLE `notifications` (`uuid` varchar(36) NOT NULL, `user_uuid` varchar(36) NOT NULL, `type` varchar(36) NOT NULL, `payload` text NOT NULL, `created_at_timestamp` bigint NOT NULL, `updated_at_timestamp` bigint NOT NULL, INDEX `index_notifications_on_user_uuid` (`user_uuid`), PRIMARY KEY (`uuid`)) ENGINE=InnoDB',
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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"')
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm'
|
||||
|
||||
export class RemoveNotifications1688540623273 implements MigrationInterface {
|
||||
name = 'RemoveNotifications1688540623273'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query('DROP INDEX "index_notifications_on_user_uuid"')
|
||||
await queryRunner.query('DROP TABLE "notifications"')
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
'CREATE TABLE "notifications" ("uuid" varchar PRIMARY KEY NOT NULL, "user_uuid" varchar(36) NOT NULL, "type" varchar(36) NOT NULL, "payload" text NOT NULL, "created_at_timestamp" bigint NOT NULL, "updated_at_timestamp" bigint NOT NULL)',
|
||||
)
|
||||
await queryRunner.query('CREATE INDEX "index_notifications_on_user_uuid" ON "notifications" ("user_uuid") ')
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/auth-server",
|
||||
"version": "1.120.0",
|
||||
"version": "1.122.2",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -20,19 +20,23 @@ import { Env } from './Env'
|
||||
import { SqliteConnectionOptions } from 'typeorm/driver/sqlite/SqliteConnectionOptions'
|
||||
|
||||
export class AppDataSource {
|
||||
private dataSource: DataSource | undefined
|
||||
private _dataSource: DataSource | undefined
|
||||
|
||||
constructor(private env: Env) {}
|
||||
|
||||
getRepository<Entity extends ObjectLiteral>(target: EntityTarget<Entity>): Repository<Entity> {
|
||||
if (!this.dataSource) {
|
||||
if (!this._dataSource) {
|
||||
throw new Error('DataSource not initialized')
|
||||
}
|
||||
|
||||
return this.dataSource.getRepository(target)
|
||||
return this._dataSource.getRepository(target)
|
||||
}
|
||||
|
||||
async initialize(): Promise<void> {
|
||||
await this.dataSource.initialize()
|
||||
}
|
||||
|
||||
get dataSource(): DataSource {
|
||||
this.env.load()
|
||||
|
||||
const isConfiguredForMySQL = this.env.get('DB_TYPE') === 'mysql'
|
||||
@@ -104,7 +108,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 +116,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
|
||||
@@ -63,11 +63,6 @@ export class AuthController implements UserServerInterface {
|
||||
kpOrigination: params.origination,
|
||||
kpCreated: params.created,
|
||||
version: params.version,
|
||||
// @TODO: awaiting publishing of new standardnotes/api package
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
publicKey: (params as any).public_key,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
signingPublicKey: (params as any).signing_key_public,
|
||||
})
|
||||
|
||||
if (!registerResult.success) {
|
||||
|
||||
@@ -10,6 +10,4 @@ export type RegisterDTO = {
|
||||
kpOrigination?: string
|
||||
kpCreated?: string
|
||||
version?: string
|
||||
publicKey?: string
|
||||
signingPublicKey?: string
|
||||
}
|
||||
|
||||
@@ -2,6 +2,4 @@ export type SimpleUserProjection = {
|
||||
uuid: string
|
||||
email: string
|
||||
protocolVersion: string
|
||||
publicKey?: string
|
||||
signingPublicKey?: string
|
||||
}
|
||||
|
||||
@@ -3,6 +3,30 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.21.1](https://github.com/standardnotes/server/compare/@standardnotes/domain-core@1.21.0...@standardnotes/domain-core@1.21.1) (2023-07-07)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* transfer notifications from auth to syncing-server. ([#648](https://github.com/standardnotes/server/issues/648)) ([c288e5d](https://github.com/standardnotes/server/commit/c288e5d8dc54778a96a9fc33e3c9cae00583fade))
|
||||
|
||||
# [1.21.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-core@1.20.0...@standardnotes/domain-core@1.21.0) (2023-07-06)
|
||||
|
||||
### Features
|
||||
|
||||
* getting shared vault users and removing shared vault user ([#642](https://github.com/standardnotes/server/issues/642)) ([e905128](https://github.com/standardnotes/server/commit/e905128d45eaadb34d3465d4480dfb3a2c5f3f79))
|
||||
|
||||
# [1.20.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-core@1.19.0...@standardnotes/domain-core@1.20.0) (2023-07-05)
|
||||
|
||||
### Features
|
||||
|
||||
* deleting shared vaults. ([#640](https://github.com/standardnotes/server/issues/640)) ([f3161c2](https://github.com/standardnotes/server/commit/f3161c271296159331639814b2dbb2e566cc54c9))
|
||||
|
||||
# [1.19.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-core@1.18.0...@standardnotes/domain-core@1.19.0) (2023-06-30)
|
||||
|
||||
### Features
|
||||
|
||||
* add shared vaults model. ([#631](https://github.com/standardnotes/server/issues/631)) ([38e77f0](https://github.com/standardnotes/server/commit/38e77f04be441b7506c3390fb0d9894b34119c3e))
|
||||
|
||||
# [1.18.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-core@1.17.0...@standardnotes/domain-core@1.18.0) (2023-06-02)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/domain-core",
|
||||
"version": "1.18.0",
|
||||
"version": "1.21.1",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -20,7 +20,7 @@ export class Dates extends ValueObject<DatesProps> {
|
||||
return Result.fail<Dates>(`Could not create Dates. Creation date should be a date object, given: ${createdAt}`)
|
||||
}
|
||||
if (!(updatedAt instanceof Date)) {
|
||||
return Result.fail<Dates>(`Could not create Dates. Update date should be a date object, given: ${createdAt}`)
|
||||
return Result.fail<Dates>(`Could not create Dates. Update date should be a date object, given: ${updatedAt}`)
|
||||
}
|
||||
|
||||
return Result.ok<Dates>(new Dates({ createdAt, updatedAt }))
|
||||
|
||||
@@ -28,7 +28,7 @@ export class RoleNameCollection extends ValueObject<RoleNameCollectionProps> {
|
||||
return false
|
||||
}
|
||||
|
||||
equals(roleNameCollection: RoleNameCollection): boolean {
|
||||
override equals(roleNameCollection: RoleNameCollection): boolean {
|
||||
if (this.props.value.length !== roleNameCollection.value.length) {
|
||||
return false
|
||||
}
|
||||
|
||||
30
packages/domain-core/src/Domain/Common/Timestamps.ts
Normal file
30
packages/domain-core/src/Domain/Common/Timestamps.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { Result } from '../Core/Result'
|
||||
import { ValueObject } from '../Core/ValueObject'
|
||||
import { TimestampsProps } from './TimestampsProps'
|
||||
|
||||
export class Timestamps extends ValueObject<TimestampsProps> {
|
||||
get createdAt(): number {
|
||||
return this.props.createdAt
|
||||
}
|
||||
|
||||
get updatedAt(): number {
|
||||
return this.props.updatedAt
|
||||
}
|
||||
|
||||
private constructor(props: TimestampsProps) {
|
||||
super(props)
|
||||
}
|
||||
|
||||
static create(createdAt: number, updatedAt: number): Result<Timestamps> {
|
||||
if (isNaN(createdAt)) {
|
||||
return Result.fail<Timestamps>(
|
||||
`Could not create Timestamps. Creation date should be a number, given: ${createdAt}`,
|
||||
)
|
||||
}
|
||||
if (isNaN(updatedAt)) {
|
||||
return Result.fail<Timestamps>(`Could not create Timestamps. Update date should be a number, given: ${updatedAt}`)
|
||||
}
|
||||
|
||||
return Result.ok<Timestamps>(new Timestamps({ createdAt, updatedAt }))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export interface TimestampsProps {
|
||||
createdAt: number
|
||||
updatedAt: number
|
||||
}
|
||||
@@ -13,4 +13,25 @@ describe('Uuid', () => {
|
||||
|
||||
expect(valueOrError.isFailed()).toBeTruthy()
|
||||
})
|
||||
|
||||
it('should check equality between two value objects', () => {
|
||||
const uuid1 = Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue()
|
||||
const uuid2 = Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue()
|
||||
|
||||
expect(uuid1.equals(uuid2)).toBeTruthy()
|
||||
})
|
||||
|
||||
it('should check inequality between two value objects', () => {
|
||||
const uuid1 = Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue()
|
||||
const uuid2 = Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1e').getValue()
|
||||
|
||||
expect(uuid1.equals(uuid2)).toBeFalsy()
|
||||
})
|
||||
|
||||
it('should check inequality between two value objects of different types', () => {
|
||||
const uuid1 = Uuid.create('84c0f8e8-544a-4c7e-9adf-26209303bc1d').getValue()
|
||||
|
||||
expect(uuid1.equals(null as unknown as Uuid)).toBeFalsy()
|
||||
expect(uuid1.equals(undefined as unknown as Uuid)).toBeFalsy()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -7,4 +7,12 @@ export abstract class ValueObject<T extends ValueObjectProps> {
|
||||
constructor(props: T) {
|
||||
this.props = Object.freeze(props)
|
||||
}
|
||||
|
||||
public equals(vo?: ValueObject<T>): boolean {
|
||||
if (vo === null || vo === undefined) {
|
||||
return false
|
||||
}
|
||||
|
||||
return JSON.stringify(this.props) === JSON.stringify(vo.props)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@ export * from './Common/RoleName'
|
||||
export * from './Common/RoleNameProps'
|
||||
export * from './Common/RoleNameCollection'
|
||||
export * from './Common/RoleNameCollectionProps'
|
||||
export * from './Common/Timestamps'
|
||||
export * from './Common/TimestampsProps'
|
||||
export * from './Common/Username'
|
||||
export * from './Common/UsernameProps'
|
||||
export * from './Common/Uuid'
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.12.9](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.12.8...@standardnotes/domain-events-infra@1.12.9) (2023-07-07)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/domain-events-infra
|
||||
|
||||
## [1.12.8](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.12.7...@standardnotes/domain-events-infra@1.12.8) (2023-07-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/domain-events-infra
|
||||
|
||||
## [1.12.7](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.12.6...@standardnotes/domain-events-infra@1.12.7) (2023-06-30)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/domain-events-infra
|
||||
|
||||
## [1.12.6](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.12.5...@standardnotes/domain-events-infra@1.12.6) (2023-06-30)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/domain-events-infra
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/domain-events-infra",
|
||||
"version": "1.12.6",
|
||||
"version": "1.12.9",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,22 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [2.113.1](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.113.0...@standardnotes/domain-events@2.113.1) (2023-07-07)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* transfer notifications from auth to syncing-server. ([#648](https://github.com/standardnotes/server/issues/648)) ([c288e5d](https://github.com/standardnotes/server/commit/c288e5d8dc54778a96a9fc33e3c9cae00583fade))
|
||||
|
||||
# [2.113.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.112.1...@standardnotes/domain-events@2.113.0) (2023-07-05)
|
||||
|
||||
### Features
|
||||
|
||||
* deleting shared vaults. ([#640](https://github.com/standardnotes/server/issues/640)) ([f3161c2](https://github.com/standardnotes/server/commit/f3161c271296159331639814b2dbb2e566cc54c9))
|
||||
|
||||
## [2.112.1](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.112.0...@standardnotes/domain-events@2.112.1) (2023-06-30)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/domain-events
|
||||
|
||||
# [2.112.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.111.4...@standardnotes/domain-events@2.112.0) (2023-06-30)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/domain-events",
|
||||
"version": "2.112.0",
|
||||
"version": "2.113.1",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,26 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.11.5](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.11.4...@standardnotes/event-store@1.11.5) (2023-07-07)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/event-store
|
||||
|
||||
## [1.11.4](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.11.3...@standardnotes/event-store@1.11.4) (2023-07-06)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/event-store
|
||||
|
||||
## [1.11.3](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.11.2...@standardnotes/event-store@1.11.3) (2023-07-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/event-store
|
||||
|
||||
## [1.11.2](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.11.1...@standardnotes/event-store@1.11.2) (2023-06-30)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/event-store
|
||||
|
||||
## [1.11.1](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.11.0...@standardnotes/event-store@1.11.1) (2023-06-30)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/event-store
|
||||
|
||||
# [1.11.0](https://github.com/standardnotes/server/compare/@standardnotes/event-store@1.10.1...@standardnotes/event-store@1.11.0) (2023-06-30)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/event-store",
|
||||
"version": "1.11.0",
|
||||
"version": "1.11.5",
|
||||
"description": "Event Store Service",
|
||||
"private": true,
|
||||
"main": "dist/src/index.js",
|
||||
|
||||
@@ -78,7 +78,6 @@ export class ContainerConfigLoader {
|
||||
['SUBSCRIPTION_EXPIRED', container.get(TYPES.EventHandler)],
|
||||
['EXTENSION_KEY_GRANTED', container.get(TYPES.EventHandler)],
|
||||
['SUBSCRIPTION_REASSIGNED', container.get(TYPES.EventHandler)],
|
||||
['USER_CREDENTIALS_CHANGED', container.get(TYPES.EventHandler)],
|
||||
['USER_EMAIL_CHANGED', container.get(TYPES.EventHandler)],
|
||||
['FILE_UPLOADED', container.get(TYPES.EventHandler)],
|
||||
['FILE_REMOVED', container.get(TYPES.EventHandler)],
|
||||
|
||||
@@ -3,6 +3,26 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.19.5](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.19.4...@standardnotes/files-server@1.19.5) (2023-07-07)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files-server
|
||||
|
||||
## [1.19.4](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.19.3...@standardnotes/files-server@1.19.4) (2023-07-06)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files-server
|
||||
|
||||
## [1.19.3](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.19.2...@standardnotes/files-server@1.19.3) (2023-07-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files-server
|
||||
|
||||
## [1.19.2](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.19.1...@standardnotes/files-server@1.19.2) (2023-06-30)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files-server
|
||||
|
||||
## [1.19.1](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.19.0...@standardnotes/files-server@1.19.1) (2023-06-30)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files-server
|
||||
|
||||
# [1.19.0](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.18.3...@standardnotes/files-server@1.19.0) (2023-06-30)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/files-server",
|
||||
"version": "1.19.0",
|
||||
"version": "1.19.5",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,74 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.11.32](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.11.31...@standardnotes/home-server@1.11.32) (2023-07-07)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.11.31](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.11.30...@standardnotes/home-server@1.11.31) (2023-07-07)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.11.30](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.11.29...@standardnotes/home-server@1.11.30) (2023-07-06)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.11.29](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.11.28...@standardnotes/home-server@1.11.29) (2023-07-06)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.11.28](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.11.27...@standardnotes/home-server@1.11.28) (2023-07-06)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.11.27](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.11.26...@standardnotes/home-server@1.11.27) (2023-07-06)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.11.26](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.11.25...@standardnotes/home-server@1.11.26) (2023-07-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.11.25](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.11.24...@standardnotes/home-server@1.11.25) (2023-07-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [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.11.16](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.11.15...@standardnotes/home-server@1.11.16) (2023-06-30)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.11.15](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.11.14...@standardnotes/home-server@1.11.15) (2023-06-30)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/home-server",
|
||||
"version": "1.11.15",
|
||||
"version": "1.11.32",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,26 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.23.9](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.23.8...@standardnotes/revisions-server@1.23.9) (2023-07-07)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/revisions-server
|
||||
|
||||
## [1.23.8](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.23.7...@standardnotes/revisions-server@1.23.8) (2023-07-06)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/revisions-server
|
||||
|
||||
## [1.23.7](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.23.6...@standardnotes/revisions-server@1.23.7) (2023-07-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/revisions-server
|
||||
|
||||
## [1.23.6](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.23.5...@standardnotes/revisions-server@1.23.6) (2023-06-30)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/revisions-server
|
||||
|
||||
## [1.23.5](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.23.4...@standardnotes/revisions-server@1.23.5) (2023-06-30)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/revisions-server
|
||||
|
||||
## [1.23.4](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.23.3...@standardnotes/revisions-server@1.23.4) (2023-06-30)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/revisions-server
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/revisions-server",
|
||||
"version": "1.23.4",
|
||||
"version": "1.23.9",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,26 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.20.7](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.20.6...@standardnotes/scheduler-server@1.20.7) (2023-07-07)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/scheduler-server
|
||||
|
||||
## [1.20.6](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.20.5...@standardnotes/scheduler-server@1.20.6) (2023-07-06)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/scheduler-server
|
||||
|
||||
## [1.20.5](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.20.4...@standardnotes/scheduler-server@1.20.5) (2023-07-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/scheduler-server
|
||||
|
||||
## [1.20.4](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.20.3...@standardnotes/scheduler-server@1.20.4) (2023-06-30)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/scheduler-server
|
||||
|
||||
## [1.20.3](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.20.2...@standardnotes/scheduler-server@1.20.3) (2023-06-30)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/scheduler-server
|
||||
|
||||
## [1.20.2](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.20.1...@standardnotes/scheduler-server@1.20.2) (2023-06-30)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/scheduler-server
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/scheduler-server",
|
||||
"version": "1.20.2",
|
||||
"version": "1.20.7",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.8.1](https://github.com/standardnotes/server/compare/@standardnotes/security@1.8.0...@standardnotes/security@1.8.1) (2023-06-30)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/security
|
||||
|
||||
# [1.8.0](https://github.com/standardnotes/server/compare/@standardnotes/security@1.7.10...@standardnotes/security@1.8.0) (2023-06-30)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/security",
|
||||
"version": "1.8.0",
|
||||
"version": "1.8.1",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -4,7 +4,6 @@ export type CrossServiceTokenData = {
|
||||
user: {
|
||||
uuid: string
|
||||
email: string
|
||||
publicKey?: string
|
||||
}
|
||||
roles: Array<Role>
|
||||
session?: {
|
||||
|
||||
@@ -3,6 +3,22 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.21.12](https://github.com/standardnotes/server/compare/@standardnotes/settings@1.21.11...@standardnotes/settings@1.21.12) (2023-07-07)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/settings
|
||||
|
||||
## [1.21.11](https://github.com/standardnotes/server/compare/@standardnotes/settings@1.21.10...@standardnotes/settings@1.21.11) (2023-07-06)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/settings
|
||||
|
||||
## [1.21.10](https://github.com/standardnotes/server/compare/@standardnotes/settings@1.21.9...@standardnotes/settings@1.21.10) (2023-07-05)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/settings
|
||||
|
||||
## [1.21.9](https://github.com/standardnotes/server/compare/@standardnotes/settings@1.21.8...@standardnotes/settings@1.21.9) (2023-06-30)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/settings
|
||||
|
||||
## [1.21.8](https://github.com/standardnotes/server/compare/@standardnotes/settings@1.21.7...@standardnotes/settings@1.21.8) (2023-06-02)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/settings
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/settings",
|
||||
"version": "1.21.8",
|
||||
"version": "1.21.12",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
|
||||
@@ -20,6 +20,9 @@ DB_TYPE=mysql
|
||||
REDIS_URL=redis://cache
|
||||
CACHE_TYPE=redis
|
||||
|
||||
VALET_TOKEN_SECRET=change-me-!
|
||||
VALET_TOKEN_TTL=1000
|
||||
|
||||
SNS_TOPIC_ARN=
|
||||
SNS_AWS_REGION=
|
||||
SQS_QUEUE_URL=
|
||||
|
||||
@@ -3,6 +3,101 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.58.1](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.58.0...@standardnotes/syncing-server@1.58.1) (2023-07-07)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* transfer notifications from auth to syncing-server. ([#648](https://github.com/standardnotes/syncing-server-js/issues/648)) ([c288e5d](https://github.com/standardnotes/syncing-server-js/commit/c288e5d8dc54778a96a9fc33e3c9cae00583fade))
|
||||
|
||||
# [1.58.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.57.0...@standardnotes/syncing-server@1.58.0) (2023-07-07)
|
||||
|
||||
### Features
|
||||
|
||||
* shared vault invites controller and use cases ([#647](https://github.com/standardnotes/syncing-server-js/issues/647)) ([7231013](https://github.com/standardnotes/syncing-server-js/commit/72310130d215047a8097a0c42a7b7dddeb4e3827))
|
||||
|
||||
# [1.57.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.56.0...@standardnotes/syncing-server@1.57.0) (2023-07-06)
|
||||
|
||||
### Features
|
||||
|
||||
* remove inbound shared vault invites. ([#646](https://github.com/standardnotes/syncing-server-js/issues/646)) ([92a5eb0](https://github.com/standardnotes/syncing-server-js/commit/92a5eb0d98486f25b761f37bc5710c45bd95d965))
|
||||
|
||||
# [1.56.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.55.0...@standardnotes/syncing-server@1.56.0) (2023-07-06)
|
||||
|
||||
### Features
|
||||
|
||||
* accept and decline shared vault invites ([#645](https://github.com/standardnotes/syncing-server-js/issues/645)) ([92f96dd](https://github.com/standardnotes/syncing-server-js/commit/92f96ddb84b9b7662899ef187ac38ad2a5769640))
|
||||
|
||||
# [1.55.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.54.0...@standardnotes/syncing-server@1.55.0) (2023-07-06)
|
||||
|
||||
### Features
|
||||
|
||||
* shared vault users controller. ([#643](https://github.com/standardnotes/syncing-server-js/issues/643)) ([b2c32ce](https://github.com/standardnotes/syncing-server-js/commit/b2c32ce70e9020b8d755a65432cb286b624a009c))
|
||||
* update shared vault invite. ([#644](https://github.com/standardnotes/syncing-server-js/issues/644)) ([912a29d](https://github.com/standardnotes/syncing-server-js/commit/912a29d091ed1ca0af1712cbd09986a1c173a960))
|
||||
|
||||
# [1.54.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.53.0...@standardnotes/syncing-server@1.54.0) (2023-07-06)
|
||||
|
||||
### Features
|
||||
|
||||
* getting shared vault users and removing shared vault user ([#642](https://github.com/standardnotes/syncing-server-js/issues/642)) ([e905128](https://github.com/standardnotes/syncing-server-js/commit/e905128d45eaadb34d3465d4480dfb3a2c5f3f79))
|
||||
|
||||
# [1.53.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.52.0...@standardnotes/syncing-server@1.53.0) (2023-07-05)
|
||||
|
||||
### Features
|
||||
|
||||
* http controllers for shared vaults. ([#641](https://github.com/standardnotes/syncing-server-js/issues/641)) ([7a3946a](https://github.com/standardnotes/syncing-server-js/commit/7a3946a9e2d4168a1d286df321d9972588252b5d))
|
||||
|
||||
# [1.52.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.51.0...@standardnotes/syncing-server@1.52.0) (2023-07-05)
|
||||
|
||||
### Features
|
||||
|
||||
* deleting shared vaults. ([#640](https://github.com/standardnotes/syncing-server-js/issues/640)) ([f3161c2](https://github.com/standardnotes/syncing-server-js/commit/f3161c271296159331639814b2dbb2e566cc54c9))
|
||||
|
||||
# [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
|
||||
|
||||
* add shared vaults model. ([#631](https://github.com/standardnotes/syncing-server-js/issues/631)) ([38e77f0](https://github.com/standardnotes/syncing-server-js/commit/38e77f04be441b7506c3390fb0d9894b34119c3e))
|
||||
|
||||
## [1.44.6](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.44.5...@standardnotes/syncing-server@1.44.6) (2023-06-30)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/syncing-server
|
||||
|
||||
## [1.44.5](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.44.4...@standardnotes/syncing-server@1.44.5) (2023-06-30)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/syncing-server
|
||||
|
||||
@@ -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/syncing-server",
|
||||
"version": "1.44.5",
|
||||
"version": "1.58.1",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
@@ -24,7 +24,8 @@
|
||||
"start": "yarn node dist/bin/server.js",
|
||||
"worker": "yarn node dist/bin/worker.js",
|
||||
"content-size": "yarn node dist/bin/content.js",
|
||||
"upgrade:snjs": "yarn ncu -u '@standardnotes/*'"
|
||||
"upgrade:snjs": "yarn ncu -u '@standardnotes/*'",
|
||||
"migrate": "yarn clean && yarn build && yarn typeorm migration:run -d dist/src/Bootstrap/DataSource.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "^3.332.0",
|
||||
@@ -38,6 +39,7 @@
|
||||
"@standardnotes/responses": "^1.13.9",
|
||||
"@standardnotes/security": "workspace:*",
|
||||
"@standardnotes/settings": "workspace:*",
|
||||
"@standardnotes/sncrypto-node": "workspace:*",
|
||||
"@standardnotes/time": "workspace:*",
|
||||
"axios": "^1.1.3",
|
||||
"cors": "2.8.5",
|
||||
@@ -51,6 +53,7 @@
|
||||
"nodemon": "^2.0.19",
|
||||
"prettyjson": "^1.2.5",
|
||||
"reflect-metadata": "0.1.13",
|
||||
"semver": "^7.5.1",
|
||||
"sqlite3": "^5.1.6",
|
||||
"typeorm": "^0.3.15",
|
||||
"ua-parser-js": "^1.0.32",
|
||||
@@ -65,6 +68,7 @@
|
||||
"@types/jsonwebtoken": "^9.0.1",
|
||||
"@types/node": "^20.2.5",
|
||||
"@types/prettyjson": "^0.0.30",
|
||||
"@types/semver": "^7.5.0",
|
||||
"@types/ua-parser-js": "^0.7.36",
|
||||
"@types/uuid": "^8.3.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.59.2",
|
||||
|
||||
@@ -1,23 +1,28 @@
|
||||
import { DataSource, EntityTarget, LoggerOptions, ObjectLiteral, Repository } from 'typeorm'
|
||||
import { MysqlConnectionOptions } from 'typeorm/driver/mysql/MysqlConnectionOptions'
|
||||
import { Item } from '../Domain/Item/Item'
|
||||
import { Notification } from '../Domain/Notifications/Notification'
|
||||
import { Env } from './Env'
|
||||
import { SqliteConnectionOptions } from 'typeorm/driver/sqlite/SqliteConnectionOptions'
|
||||
|
||||
export class AppDataSource {
|
||||
private dataSource: DataSource | undefined
|
||||
private _dataSource: DataSource | undefined
|
||||
|
||||
constructor(private env: Env) {}
|
||||
|
||||
getRepository<Entity extends ObjectLiteral>(target: EntityTarget<Entity>): Repository<Entity> {
|
||||
if (!this.dataSource) {
|
||||
if (!this._dataSource) {
|
||||
throw new Error('DataSource not initialized')
|
||||
}
|
||||
|
||||
return this.dataSource.getRepository(target)
|
||||
return this._dataSource.getRepository(target)
|
||||
}
|
||||
|
||||
async initialize(): Promise<void> {
|
||||
await this.dataSource.initialize()
|
||||
}
|
||||
|
||||
get dataSource(): DataSource {
|
||||
this.env.load()
|
||||
|
||||
const isConfiguredForMySQL = this.env.get('DB_TYPE') === 'mysql'
|
||||
@@ -28,7 +33,7 @@ export class AppDataSource {
|
||||
|
||||
const commonDataSourceOptions = {
|
||||
maxQueryExecutionTime,
|
||||
entities: [Item],
|
||||
entities: [Item, Notification],
|
||||
migrations: [`${__dirname}/../../migrations/${isConfiguredForMySQL ? 'mysql' : 'sqlite'}/*.js`],
|
||||
migrationsRun: true,
|
||||
logging: <LoggerOptions>this.env.get('DB_DEBUG_LEVEL', true) ?? 'info',
|
||||
@@ -72,7 +77,7 @@ export class AppDataSource {
|
||||
database: inReplicaMode ? undefined : this.env.get('DB_DATABASE'),
|
||||
}
|
||||
|
||||
this.dataSource = new DataSource(mySQLDataSourceOptions)
|
||||
this._dataSource = new DataSource(mySQLDataSourceOptions)
|
||||
} else {
|
||||
const sqliteDataSourceOptions: SqliteConnectionOptions = {
|
||||
...commonDataSourceOptions,
|
||||
@@ -80,9 +85,9 @@ export class AppDataSource {
|
||||
database: this.env.get('DB_SQLITE_DATABASE_PATH'),
|
||||
}
|
||||
|
||||
this.dataSource = new DataSource(sqliteDataSourceOptions)
|
||||
this._dataSource = new DataSource(sqliteDataSourceOptions)
|
||||
}
|
||||
|
||||
await this.dataSource.initialize()
|
||||
return this._dataSource
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -38,6 +38,21 @@ const TYPES = {
|
||||
Sync_SyncItems: Symbol.for('Sync_SyncItems'),
|
||||
Sync_CheckIntegrity: Symbol.for('Sync_CheckIntegrity'),
|
||||
Sync_GetItem: Symbol.for('Sync_GetItem'),
|
||||
Sync_GetSharedVaults: Symbol.for('Sync_GetSharedVaults'),
|
||||
Sync_CreateSharedVault: Symbol.for('Sync_CreateSharedVault'),
|
||||
Sync_DeleteSharedVault: Symbol.for('Sync_DeleteSharedVault'),
|
||||
Sync_CreateSharedVaultFileValetToken: Symbol.for('Sync_CreateSharedVaultFileValetToken'),
|
||||
Sync_GetSharedVaultUsers: Symbol.for('Sync_GetSharedVaultUsers'),
|
||||
Sync_RemoveSharedVaultUser: Symbol.for('Sync_RemoveSharedVaultUser'),
|
||||
Sync_InviteUserToSharedVault: Symbol.for('Sync_InviteUserToSharedVault'),
|
||||
Sync_UpdateSharedVaultInvite: Symbol.for('Sync_UpdateSharedVaultInvite'),
|
||||
Sync_AcceptInviteToSharedVault: Symbol.for('Sync_AcceptInviteToSharedVault'),
|
||||
Sync_DeclineInviteToSharedVault: Symbol.for('Sync_DeclineInviteToSharedVault'),
|
||||
Sync_DeleteSharedVaultInvitesToUser: Symbol.for('Sync_DeleteSharedVaultInvitesToUser'),
|
||||
Sync_DeleteSharedVaultInvitesSentByUser: Symbol.for('Sync_DeleteSharedVaultInvitesSentByUser'),
|
||||
Sync_GetSharedVaultInvitesSentByUser: Symbol.for('Sync_GetSharedVaultInvitesSentByUser'),
|
||||
Sync_GetSharedVaultInvitesSentToUser: Symbol.for('Sync_GetSharedVaultInvitesSentToUser'),
|
||||
Sync_SharedVaultInviteHttpMapper: Symbol.for('Sync_SharedVaultInviteHttpMapper'),
|
||||
// Handlers
|
||||
Sync_AccountDeletionRequestedEventHandler: Symbol.for('Sync_AccountDeletionRequestedEventHandler'),
|
||||
Sync_DuplicateItemSyncedEventHandler: Symbol.for('Sync_DuplicateItemSyncedEventHandler'),
|
||||
@@ -68,6 +83,9 @@ const TYPES = {
|
||||
Sync_ItemTransferCalculator: Symbol.for('Sync_ItemTransferCalculator'),
|
||||
Sync_ControllerContainer: Symbol.for('Sync_ControllerContainer'),
|
||||
Sync_HomeServerItemsController: Symbol.for('Sync_HomeServerItemsController'),
|
||||
// Mapping
|
||||
Sync_SharedVaultHttpMapper: Symbol.for('Sync_SharedVaultHttpMapper'),
|
||||
Sync_SharedVaultUserHttpMapper: Symbol.for('Sync_SharedVaultUserHttpMapper'),
|
||||
}
|
||||
|
||||
export default TYPES
|
||||
|
||||
@@ -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()
|
||||
})
|
||||
})
|
||||
@@ -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,10 @@
|
||||
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,5 @@
|
||||
import { Notification } from './Notification'
|
||||
|
||||
export interface NotificationRepositoryInterface {
|
||||
save(notification: Notification): Promise<void>
|
||||
}
|
||||
@@ -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()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,27 @@
|
||||
import { ValueObject, Result } 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
|
||||
}
|
||||
@@ -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,17 @@
|
||||
import { Entity, Result, UniqueEntityId } from '@standardnotes/domain-core'
|
||||
|
||||
import { SharedVaultProps } from './SharedVaultProps'
|
||||
|
||||
export class SharedVault extends Entity<SharedVaultProps> {
|
||||
get id(): UniqueEntityId {
|
||||
return this._id
|
||||
}
|
||||
|
||||
private constructor(props: SharedVaultProps, id?: UniqueEntityId) {
|
||||
super(props, id)
|
||||
}
|
||||
|
||||
static create(props: SharedVaultProps, id?: UniqueEntityId): Result<SharedVault> {
|
||||
return Result.ok<SharedVault>(new SharedVault(props, id))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { Uuid, Timestamps } from '@standardnotes/domain-core'
|
||||
|
||||
export interface SharedVaultProps {
|
||||
userUuid: Uuid
|
||||
fileUploadBytesUsed: number
|
||||
fileUploadBytesLimit: number
|
||||
timestamps: Timestamps
|
||||
}
|
||||
@@ -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>
|
||||
}
|
||||
@@ -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,14 @@
|
||||
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>
|
||||
removeBySharedVaultUuid(sharedVaultUuid: Uuid): Promise<void>
|
||||
findByUserUuid(userUuid: Uuid): Promise<SharedVaultInvite[]>
|
||||
findBySenderUuid(senderUuid: Uuid): Promise<SharedVaultInvite[]>
|
||||
findByUserUuidAndSharedVaultUuid(dto: { userUuid: Uuid; sharedVaultUuid: Uuid }): Promise<SharedVaultInvite | null>
|
||||
findBySenderUuidAndSharedVaultUuid(dto: { senderUuid: Uuid; sharedVaultUuid: Uuid }): Promise<SharedVaultInvite[]>
|
||||
}
|
||||
@@ -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,13 @@
|
||||
import { Uuid } from '@standardnotes/domain-core'
|
||||
|
||||
import { SharedVaultUser } from './SharedVaultUser'
|
||||
|
||||
export interface SharedVaultUserRepositoryInterface {
|
||||
findByUuid(sharedVaultUserUuid: Uuid): Promise<SharedVaultUser | null>
|
||||
findByUserUuid(userUuid: Uuid): Promise<SharedVaultUser[]>
|
||||
findBySharedVaultUuid(sharedVaultUuid: Uuid): Promise<SharedVaultUser[]>
|
||||
save(sharedVaultUser: SharedVaultUser): Promise<void>
|
||||
remove(sharedVault: SharedVaultUser): Promise<void>
|
||||
removeBySharedVaultUuid(sharedVaultUuid: Uuid): Promise<void>
|
||||
findByUserUuidAndSharedVaultUuid(dto: { userUuid: Uuid; sharedVaultUuid: Uuid }): Promise<SharedVaultUser | null>
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
import { Result, Timestamps, Uuid } from '@standardnotes/domain-core'
|
||||
import { SharedVaultInviteRepositoryInterface } from '../../SharedVault/User/Invite/SharedVaultInviteRepositoryInterface'
|
||||
import { AddUserToSharedVault } from '../AddUserToSharedVault/AddUserToSharedVault'
|
||||
import { AcceptInviteToSharedVault } from './AcceptInviteToSharedVault'
|
||||
import { SharedVaultInvite } from '../../SharedVault/User/Invite/SharedVaultInvite'
|
||||
import { SharedVaultUserPermission } from '../../SharedVault/User/SharedVaultUserPermission'
|
||||
|
||||
describe('AcceptInviteToSharedVault', () => {
|
||||
let addUserToSharedVault: AddUserToSharedVault
|
||||
let sharedVaultInviteRepository: SharedVaultInviteRepositoryInterface
|
||||
let invite: SharedVaultInvite
|
||||
|
||||
const createUseCase = () => new AcceptInviteToSharedVault(addUserToSharedVault, sharedVaultInviteRepository)
|
||||
|
||||
beforeEach(() => {
|
||||
invite = SharedVaultInvite.create({
|
||||
sharedVaultUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
senderUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
encryptedMessage: 'encrypted-message',
|
||||
permission: SharedVaultUserPermission.create(SharedVaultUserPermission.PERMISSIONS.Read).getValue(),
|
||||
timestamps: Timestamps.create(123, 123).getValue(),
|
||||
}).getValue()
|
||||
|
||||
addUserToSharedVault = {} as jest.Mocked<AddUserToSharedVault>
|
||||
addUserToSharedVault.execute = jest.fn().mockReturnValue(Result.ok())
|
||||
|
||||
sharedVaultInviteRepository = {} as jest.Mocked<SharedVaultInviteRepositoryInterface>
|
||||
sharedVaultInviteRepository.findByUuid = jest.fn().mockResolvedValue(invite)
|
||||
sharedVaultInviteRepository.remove = jest.fn()
|
||||
})
|
||||
|
||||
it('should fail if invite uuid is invalid', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
inviteUuid: 'invalid',
|
||||
originatorUuid: '00000000-0000-0000-0000-000000000000',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
expect(result.getError()).toBe('Given value is not a valid uuid: invalid')
|
||||
})
|
||||
|
||||
it('should fail if originator uuid is invalid', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
inviteUuid: '00000000-0000-0000-0000-000000000000',
|
||||
originatorUuid: 'invalid',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
expect(result.getError()).toBe('Given value is not a valid uuid: invalid')
|
||||
})
|
||||
|
||||
it('should fail if invite is not found', async () => {
|
||||
sharedVaultInviteRepository.findByUuid = jest.fn().mockResolvedValue(null)
|
||||
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
inviteUuid: '00000000-0000-0000-0000-000000000000',
|
||||
originatorUuid: '00000000-0000-0000-0000-000000000000',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
expect(result.getError()).toBe('Invite not found')
|
||||
})
|
||||
|
||||
it('should fail if originator is not the recipient of the invite', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
inviteUuid: '00000000-0000-0000-0000-000000000000',
|
||||
originatorUuid: '00000000-0000-0000-0000-000000000001',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
expect(result.getError()).toBe('Only the recipient of the invite can accept it')
|
||||
})
|
||||
|
||||
it('should fail if adding user to shared vault fails', async () => {
|
||||
addUserToSharedVault.execute = jest.fn().mockReturnValue(Result.fail('Failed to add user to shared vault'))
|
||||
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
inviteUuid: '00000000-0000-0000-0000-000000000000',
|
||||
originatorUuid: '00000000-0000-0000-0000-000000000000',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
expect(result.getError()).toBe('Failed to add user to shared vault')
|
||||
})
|
||||
|
||||
it('should delete invite after adding user to shared vault', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
await useCase.execute({
|
||||
inviteUuid: '00000000-0000-0000-0000-000000000000',
|
||||
originatorUuid: '00000000-0000-0000-0000-000000000000',
|
||||
})
|
||||
|
||||
expect(sharedVaultInviteRepository.remove).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,47 @@
|
||||
import { Result, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
|
||||
import { AcceptInviteToSharedVaultDTO } from './AcceptInviteToSharedVaultDTO'
|
||||
import { SharedVaultInviteRepositoryInterface } from '../../SharedVault/User/Invite/SharedVaultInviteRepositoryInterface'
|
||||
import { AddUserToSharedVault } from '../AddUserToSharedVault/AddUserToSharedVault'
|
||||
|
||||
export class AcceptInviteToSharedVault implements UseCaseInterface<void> {
|
||||
constructor(
|
||||
private addUserToSharedVault: AddUserToSharedVault,
|
||||
private sharedVaultInviteRepository: SharedVaultInviteRepositoryInterface,
|
||||
) {}
|
||||
|
||||
async execute(dto: AcceptInviteToSharedVaultDTO): Promise<Result<void>> {
|
||||
const inviteUuidOrError = Uuid.create(dto.inviteUuid)
|
||||
if (inviteUuidOrError.isFailed()) {
|
||||
return Result.fail(inviteUuidOrError.getError())
|
||||
}
|
||||
const inviteUuid = inviteUuidOrError.getValue()
|
||||
|
||||
const originatorUuidOrError = Uuid.create(dto.originatorUuid)
|
||||
if (originatorUuidOrError.isFailed()) {
|
||||
return Result.fail(originatorUuidOrError.getError())
|
||||
}
|
||||
const originatorUuid = originatorUuidOrError.getValue()
|
||||
|
||||
const invite = await this.sharedVaultInviteRepository.findByUuid(inviteUuid)
|
||||
if (!invite) {
|
||||
return Result.fail('Invite not found')
|
||||
}
|
||||
|
||||
if (!invite.props.userUuid.equals(originatorUuid)) {
|
||||
return Result.fail('Only the recipient of the invite can accept it')
|
||||
}
|
||||
|
||||
const result = await this.addUserToSharedVault.execute({
|
||||
sharedVaultUuid: invite.props.sharedVaultUuid.value,
|
||||
userUuid: invite.props.userUuid.value,
|
||||
permission: invite.props.permission.value,
|
||||
})
|
||||
if (result.isFailed()) {
|
||||
return Result.fail(result.getError())
|
||||
}
|
||||
|
||||
await this.sharedVaultInviteRepository.remove(invite)
|
||||
|
||||
return Result.ok()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export interface AcceptInviteToSharedVaultDTO {
|
||||
inviteUuid: string
|
||||
originatorUuid: string
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
import { Result } from '@standardnotes/domain-core'
|
||||
|
||||
import { NotificationRepositoryInterface } from '../../Notifications/NotificationRepositoryInterface'
|
||||
import { Notification } from '../../Notifications/Notification'
|
||||
import { AddNotificationForUser } from './AddNotificationForUser'
|
||||
import { NotificationType } from '../../Notifications/NotificationType'
|
||||
|
||||
describe('AddNotificationForUser', () => {
|
||||
let notificationRepository: NotificationRepositoryInterface
|
||||
let timer: TimerInterface
|
||||
|
||||
const createUseCase = () => new AddNotificationForUser(notificationRepository, timer)
|
||||
|
||||
beforeEach(() => {
|
||||
notificationRepository = {} as jest.Mocked<NotificationRepositoryInterface>
|
||||
notificationRepository.save = jest.fn()
|
||||
|
||||
timer = {} as jest.Mocked<TimerInterface>
|
||||
timer.getTimestampInMicroseconds = jest.fn().mockReturnValue(123456789)
|
||||
})
|
||||
|
||||
it('should save notification', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: '0e8c3c7e-3f1a-4f7a-9b5a-5b2b0a7d4b1e',
|
||||
type: NotificationType.TYPES.RemovedFromSharedVault,
|
||||
payload: 'payload',
|
||||
version: '1.0',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBeFalsy()
|
||||
})
|
||||
|
||||
it('should return error if user uuid is invalid', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: 'invalid',
|
||||
type: NotificationType.TYPES.RemovedFromSharedVault,
|
||||
payload: 'payload',
|
||||
version: '1.0',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBeTruthy()
|
||||
})
|
||||
|
||||
it('should return error if notification type is invalid', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: '0e8c3c7e-3f1a-4f7a-9b5a-5b2b0a7d4b1e',
|
||||
type: 'invalid',
|
||||
payload: 'payload',
|
||||
version: '1.0',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBeTruthy()
|
||||
})
|
||||
|
||||
it('should return error if notification payload is invalid', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: '0e8c3c7e-3f1a-4f7a-9b5a-5b2b0a7d4b1e',
|
||||
type: NotificationType.TYPES.RemovedFromSharedVault,
|
||||
payload: '',
|
||||
version: '1.0',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBeTruthy()
|
||||
})
|
||||
|
||||
it('should return error if notification could not be created', async () => {
|
||||
const mock = jest.spyOn(Notification, 'create')
|
||||
mock.mockImplementation(() => {
|
||||
return Result.fail('Oops')
|
||||
})
|
||||
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: '0e8c3c7e-3f1a-4f7a-9b5a-5b2b0a7d4b1e',
|
||||
type: NotificationType.TYPES.RemovedFromSharedVault,
|
||||
payload: 'payload',
|
||||
version: '1.0',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBeTruthy()
|
||||
|
||||
mock.mockRestore()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,48 @@
|
||||
import { Result, Timestamps, UseCaseInterface, Uuid, Validator } from '@standardnotes/domain-core'
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
|
||||
import { AddNotificationForUserDTO } from './AddNotificationForUserDTO'
|
||||
import { NotificationRepositoryInterface } from '../../Notifications/NotificationRepositoryInterface'
|
||||
import { Notification } from '../../Notifications/Notification'
|
||||
import { NotificationType } from '../../Notifications/NotificationType'
|
||||
|
||||
export class AddNotificationForUser implements UseCaseInterface<Notification> {
|
||||
constructor(private notificationRepository: NotificationRepositoryInterface, private timer: TimerInterface) {}
|
||||
|
||||
async execute(dto: AddNotificationForUserDTO): Promise<Result<Notification>> {
|
||||
const userUuidOrError = Uuid.create(dto.userUuid)
|
||||
if (userUuidOrError.isFailed()) {
|
||||
return Result.fail(userUuidOrError.getError())
|
||||
}
|
||||
const userUuid = userUuidOrError.getValue()
|
||||
|
||||
const typeOrError = NotificationType.create(dto.type)
|
||||
if (typeOrError.isFailed()) {
|
||||
return Result.fail(typeOrError.getError())
|
||||
}
|
||||
const type = typeOrError.getValue()
|
||||
|
||||
const paylodNotEmptyValidationResult = Validator.isNotEmpty(dto.payload)
|
||||
if (paylodNotEmptyValidationResult.isFailed()) {
|
||||
return Result.fail(paylodNotEmptyValidationResult.getError())
|
||||
}
|
||||
|
||||
const notificationOrError = Notification.create({
|
||||
userUuid,
|
||||
type,
|
||||
payload: dto.payload,
|
||||
timestamps: Timestamps.create(
|
||||
this.timer.getTimestampInMicroseconds(),
|
||||
this.timer.getTimestampInMicroseconds(),
|
||||
).getValue(),
|
||||
})
|
||||
if (notificationOrError.isFailed()) {
|
||||
return Result.fail(notificationOrError.getError())
|
||||
}
|
||||
const notification = notificationOrError.getValue()
|
||||
|
||||
await this.notificationRepository.save(notification)
|
||||
|
||||
return Result.ok(notification)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export interface AddNotificationForUserDTO {
|
||||
version: string
|
||||
type: string
|
||||
userUuid: string
|
||||
payload: string
|
||||
}
|
||||
@@ -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,130 @@
|
||||
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.equals(
|
||||
SharedVaultUserPermission.create(SharedVaultUserPermission.PERMISSIONS.Read).getValue(),
|
||||
) &&
|
||||
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.equals(
|
||||
SharedVaultUserPermission.create(SharedVaultUserPermission.PERMISSIONS.Read).getValue(),
|
||||
)
|
||||
) {
|
||||
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,88 @@
|
||||
import { Timestamps, Uuid } from '@standardnotes/domain-core'
|
||||
import { SharedVaultInviteRepositoryInterface } from '../../SharedVault/User/Invite/SharedVaultInviteRepositoryInterface'
|
||||
import { DeclineInviteToSharedVault } from './DeclineInviteToSharedVault'
|
||||
import { SharedVaultInvite } from '../../SharedVault/User/Invite/SharedVaultInvite'
|
||||
import { SharedVaultUserPermission } from '../../SharedVault/User/SharedVaultUserPermission'
|
||||
|
||||
describe('DeclineInviteToSharedVault', () => {
|
||||
let sharedVaultInviteRepository: SharedVaultInviteRepositoryInterface
|
||||
let invite: SharedVaultInvite
|
||||
|
||||
const createUseCase = () => new DeclineInviteToSharedVault(sharedVaultInviteRepository)
|
||||
|
||||
beforeEach(() => {
|
||||
invite = SharedVaultInvite.create({
|
||||
sharedVaultUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
senderUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
encryptedMessage: 'encrypted-message',
|
||||
permission: SharedVaultUserPermission.create(SharedVaultUserPermission.PERMISSIONS.Read).getValue(),
|
||||
timestamps: Timestamps.create(123, 123).getValue(),
|
||||
}).getValue()
|
||||
|
||||
sharedVaultInviteRepository = {} as jest.Mocked<SharedVaultInviteRepositoryInterface>
|
||||
sharedVaultInviteRepository.findByUuid = jest.fn().mockResolvedValue(invite)
|
||||
sharedVaultInviteRepository.remove = jest.fn()
|
||||
})
|
||||
|
||||
it('should fail if invite uuid is invalid', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
inviteUuid: 'invalid',
|
||||
originatorUuid: '00000000-0000-0000-0000-000000000000',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
expect(result.getError()).toBe('Given value is not a valid uuid: invalid')
|
||||
})
|
||||
|
||||
it('should fail if originator uuid is invalid', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
inviteUuid: '00000000-0000-0000-0000-000000000000',
|
||||
originatorUuid: 'invalid',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
expect(result.getError()).toBe('Given value is not a valid uuid: invalid')
|
||||
})
|
||||
|
||||
it('should fail if invite is not found', async () => {
|
||||
sharedVaultInviteRepository.findByUuid = jest.fn().mockResolvedValue(null)
|
||||
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
inviteUuid: '00000000-0000-0000-0000-000000000000',
|
||||
originatorUuid: '00000000-0000-0000-0000-000000000000',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
expect(result.getError()).toBe('Invite not found')
|
||||
})
|
||||
|
||||
it('should fail if originator is not the recipient of the invite', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
inviteUuid: '00000000-0000-0000-0000-000000000000',
|
||||
originatorUuid: '00000000-0000-0000-0000-000000000001',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
expect(result.getError()).toBe('Only the recipient of the invite can decline it')
|
||||
})
|
||||
|
||||
it('should delete invite', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
await useCase.execute({
|
||||
inviteUuid: '00000000-0000-0000-0000-000000000000',
|
||||
originatorUuid: '00000000-0000-0000-0000-000000000000',
|
||||
})
|
||||
|
||||
expect(sharedVaultInviteRepository.remove).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,34 @@
|
||||
import { Result, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
|
||||
import { DeclineInviteToSharedVaultDTO } from './DeclineInviteToSharedVaultDTO'
|
||||
import { SharedVaultInviteRepositoryInterface } from '../../SharedVault/User/Invite/SharedVaultInviteRepositoryInterface'
|
||||
|
||||
export class DeclineInviteToSharedVault implements UseCaseInterface<void> {
|
||||
constructor(private sharedVaultInviteRepository: SharedVaultInviteRepositoryInterface) {}
|
||||
|
||||
async execute(dto: DeclineInviteToSharedVaultDTO): Promise<Result<void>> {
|
||||
const inviteUuidOrError = Uuid.create(dto.inviteUuid)
|
||||
if (inviteUuidOrError.isFailed()) {
|
||||
return Result.fail(inviteUuidOrError.getError())
|
||||
}
|
||||
const inviteUuid = inviteUuidOrError.getValue()
|
||||
|
||||
const originatorUuidOrError = Uuid.create(dto.originatorUuid)
|
||||
if (originatorUuidOrError.isFailed()) {
|
||||
return Result.fail(originatorUuidOrError.getError())
|
||||
}
|
||||
const originatorUuid = originatorUuidOrError.getValue()
|
||||
|
||||
const invite = await this.sharedVaultInviteRepository.findByUuid(inviteUuid)
|
||||
if (!invite) {
|
||||
return Result.fail('Invite not found')
|
||||
}
|
||||
|
||||
if (!invite.props.userUuid.equals(originatorUuid)) {
|
||||
return Result.fail('Only the recipient of the invite can decline it')
|
||||
}
|
||||
|
||||
await this.sharedVaultInviteRepository.remove(invite)
|
||||
|
||||
return Result.ok()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export interface DeclineInviteToSharedVaultDTO {
|
||||
inviteUuid: string
|
||||
originatorUuid: string
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
import { Uuid, Timestamps, Result } from '@standardnotes/domain-core'
|
||||
|
||||
import { SharedVaultRepositoryInterface } from '../../SharedVault/SharedVaultRepositoryInterface'
|
||||
import { SharedVaultInviteRepositoryInterface } from '../../SharedVault/User/Invite/SharedVaultInviteRepositoryInterface'
|
||||
import { SharedVaultUserRepositoryInterface } from '../../SharedVault/User/SharedVaultUserRepositoryInterface'
|
||||
import { DeleteSharedVault } from './DeleteSharedVault'
|
||||
import { SharedVault } from '../../SharedVault/SharedVault'
|
||||
import { SharedVaultUser } from '../../SharedVault/User/SharedVaultUser'
|
||||
import { SharedVaultUserPermission } from '../../SharedVault/User/SharedVaultUserPermission'
|
||||
import { RemoveUserFromSharedVault } from '../RemoveUserFromSharedVault/RemoveUserFromSharedVault'
|
||||
|
||||
describe('DeleteSharedVault', () => {
|
||||
let sharedVaultRepository: SharedVaultRepositoryInterface
|
||||
let sharedVaultUserRepository: SharedVaultUserRepositoryInterface
|
||||
let sharedVaultInviteRepository: SharedVaultInviteRepositoryInterface
|
||||
let removeUserFromSharedVault: RemoveUserFromSharedVault
|
||||
let sharedVault: SharedVault
|
||||
let sharedVaultUser: SharedVaultUser
|
||||
|
||||
const createUseCase = () =>
|
||||
new DeleteSharedVault(
|
||||
sharedVaultRepository,
|
||||
sharedVaultUserRepository,
|
||||
sharedVaultInviteRepository,
|
||||
removeUserFromSharedVault,
|
||||
)
|
||||
|
||||
beforeEach(() => {
|
||||
sharedVault = SharedVault.create({
|
||||
fileUploadBytesLimit: 100,
|
||||
fileUploadBytesUsed: 2,
|
||||
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
timestamps: Timestamps.create(123, 123).getValue(),
|
||||
}).getValue()
|
||||
sharedVaultRepository = {} as jest.Mocked<SharedVaultRepositoryInterface>
|
||||
sharedVaultRepository.findByUuid = jest.fn().mockResolvedValue(sharedVault)
|
||||
sharedVaultRepository.remove = jest.fn()
|
||||
|
||||
sharedVaultUser = SharedVaultUser.create({
|
||||
permission: SharedVaultUserPermission.create(SharedVaultUserPermission.PERMISSIONS.Read).getValue(),
|
||||
sharedVaultUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
timestamps: Timestamps.create(123, 123).getValue(),
|
||||
}).getValue()
|
||||
sharedVaultUserRepository = {} as jest.Mocked<SharedVaultUserRepositoryInterface>
|
||||
sharedVaultUserRepository.findBySharedVaultUuid = jest.fn().mockResolvedValue([sharedVaultUser])
|
||||
|
||||
sharedVaultInviteRepository = {} as jest.Mocked<SharedVaultInviteRepositoryInterface>
|
||||
sharedVaultInviteRepository.removeBySharedVaultUuid = jest.fn()
|
||||
|
||||
removeUserFromSharedVault = {} as jest.Mocked<RemoveUserFromSharedVault>
|
||||
removeUserFromSharedVault.execute = jest.fn().mockReturnValue(Result.ok())
|
||||
})
|
||||
|
||||
it('should remove shared vault', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
|
||||
originatorUuid: '00000000-0000-0000-0000-000000000000',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBeFalsy()
|
||||
expect(sharedVaultRepository.remove).toHaveBeenCalled()
|
||||
expect(sharedVaultInviteRepository.removeBySharedVaultUuid).toHaveBeenCalled()
|
||||
expect(removeUserFromSharedVault.execute).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should return error when shared vault does not exist', async () => {
|
||||
sharedVaultRepository.findByUuid = jest.fn().mockResolvedValue(null)
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
|
||||
originatorUuid: '00000000-0000-0000-0000-000000000000',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBeTruthy()
|
||||
expect(sharedVaultRepository.remove).not.toHaveBeenCalled()
|
||||
expect(sharedVaultInviteRepository.removeBySharedVaultUuid).not.toHaveBeenCalled()
|
||||
expect(removeUserFromSharedVault.execute).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should return error when shared vault uuid is invalid', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
sharedVaultUuid: 'invalid',
|
||||
originatorUuid: '00000000-0000-0000-0000-000000000000',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBeTruthy()
|
||||
expect(sharedVaultRepository.remove).not.toHaveBeenCalled()
|
||||
expect(sharedVaultInviteRepository.removeBySharedVaultUuid).not.toHaveBeenCalled()
|
||||
expect(removeUserFromSharedVault.execute).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should return error when originator uuid is invalid', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
|
||||
originatorUuid: 'invalid',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBeTruthy()
|
||||
expect(sharedVaultRepository.remove).not.toHaveBeenCalled()
|
||||
expect(sharedVaultInviteRepository.removeBySharedVaultUuid).not.toHaveBeenCalled()
|
||||
expect(removeUserFromSharedVault.execute).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should return error when originator of the delete request is not the owner of the shared vault', async () => {
|
||||
sharedVault = SharedVault.create({
|
||||
fileUploadBytesLimit: 100,
|
||||
fileUploadBytesUsed: 2,
|
||||
userUuid: Uuid.create('00000000-0000-0000-0000-000000000001').getValue(),
|
||||
timestamps: Timestamps.create(123, 123).getValue(),
|
||||
}).getValue()
|
||||
sharedVaultRepository.findByUuid = jest.fn().mockResolvedValue(sharedVault)
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
|
||||
originatorUuid: '00000000-0000-0000-0000-000000000000',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBeTruthy()
|
||||
expect(sharedVaultRepository.remove).not.toHaveBeenCalled()
|
||||
expect(sharedVaultInviteRepository.removeBySharedVaultUuid).not.toHaveBeenCalled()
|
||||
expect(removeUserFromSharedVault.execute).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should return error if removing user from shared vault fails', async () => {
|
||||
removeUserFromSharedVault.execute = jest.fn().mockReturnValue(Result.fail('failed'))
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
|
||||
originatorUuid: '00000000-0000-0000-0000-000000000000',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBeTruthy()
|
||||
expect(sharedVaultRepository.remove).not.toHaveBeenCalled()
|
||||
expect(sharedVaultInviteRepository.removeBySharedVaultUuid).not.toHaveBeenCalled()
|
||||
expect(removeUserFromSharedVault.execute).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,58 @@
|
||||
import { Result, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
|
||||
|
||||
import { DeleteSharedVaultDTO } from './DeleteSharedVaultDTO'
|
||||
import { SharedVaultRepositoryInterface } from '../../SharedVault/SharedVaultRepositoryInterface'
|
||||
import { SharedVaultUserRepositoryInterface } from '../../SharedVault/User/SharedVaultUserRepositoryInterface'
|
||||
import { SharedVaultInviteRepositoryInterface } from '../../SharedVault/User/Invite/SharedVaultInviteRepositoryInterface'
|
||||
import { RemoveUserFromSharedVault } from '../RemoveUserFromSharedVault/RemoveUserFromSharedVault'
|
||||
|
||||
export class DeleteSharedVault implements UseCaseInterface<void> {
|
||||
constructor(
|
||||
private sharedVaultRepository: SharedVaultRepositoryInterface,
|
||||
private sharedVaultUserRepository: SharedVaultUserRepositoryInterface,
|
||||
private sharedVaultInviteRepository: SharedVaultInviteRepositoryInterface,
|
||||
private removeUserFromSharedVault: RemoveUserFromSharedVault,
|
||||
) {}
|
||||
|
||||
async execute(dto: DeleteSharedVaultDTO): Promise<Result<void>> {
|
||||
const originatorUuidOrError = Uuid.create(dto.originatorUuid)
|
||||
if (originatorUuidOrError.isFailed()) {
|
||||
return Result.fail(originatorUuidOrError.getError())
|
||||
}
|
||||
const originatorUuid = originatorUuidOrError.getValue()
|
||||
|
||||
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('Shared vault not found')
|
||||
}
|
||||
|
||||
if (sharedVault.props.userUuid.value !== originatorUuid.value) {
|
||||
return Result.fail('Shared vault does not belong to the user')
|
||||
}
|
||||
|
||||
const sharedVaultUsers = await this.sharedVaultUserRepository.findBySharedVaultUuid(sharedVaultUuid)
|
||||
for (const sharedVaultUser of sharedVaultUsers) {
|
||||
const result = await this.removeUserFromSharedVault.execute({
|
||||
originatorUuid: originatorUuid.value,
|
||||
sharedVaultUuid: sharedVaultUuid.value,
|
||||
userUuid: sharedVaultUser.props.userUuid.value,
|
||||
})
|
||||
|
||||
if (result.isFailed()) {
|
||||
return Result.fail(result.getError())
|
||||
}
|
||||
}
|
||||
|
||||
await this.sharedVaultInviteRepository.removeBySharedVaultUuid(sharedVaultUuid)
|
||||
|
||||
await this.sharedVaultRepository.remove(sharedVault)
|
||||
|
||||
return Result.ok()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export interface DeleteSharedVaultDTO {
|
||||
originatorUuid: string
|
||||
sharedVaultUuid: string
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
import { Result, Timestamps, Uuid } from '@standardnotes/domain-core'
|
||||
import { SharedVaultInviteRepositoryInterface } from '../../SharedVault/User/Invite/SharedVaultInviteRepositoryInterface'
|
||||
import { DeclineInviteToSharedVault } from '../DeclineInviteToSharedVault/DeclineInviteToSharedVault'
|
||||
import { DeleteSharedVaultInvitesSentByUser } from './DeleteSharedVaultInvitesSentByUser'
|
||||
import { SharedVaultInvite } from '../../SharedVault/User/Invite/SharedVaultInvite'
|
||||
import { SharedVaultUserPermission } from '../../SharedVault/User/SharedVaultUserPermission'
|
||||
|
||||
describe('DeleteSharedVaultInvitesSentByUser', () => {
|
||||
let sharedVaultInviteRepository: SharedVaultInviteRepositoryInterface
|
||||
let declineInviteToSharedVault: DeclineInviteToSharedVault
|
||||
let sharedVaultInvite: SharedVaultInvite
|
||||
|
||||
const createUseCase = () =>
|
||||
new DeleteSharedVaultInvitesSentByUser(sharedVaultInviteRepository, declineInviteToSharedVault)
|
||||
|
||||
beforeEach(() => {
|
||||
sharedVaultInvite = SharedVaultInvite.create({
|
||||
sharedVaultUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
senderUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
encryptedMessage: 'encrypted-message',
|
||||
permission: SharedVaultUserPermission.create(SharedVaultUserPermission.PERMISSIONS.Read).getValue(),
|
||||
timestamps: Timestamps.create(123, 123).getValue(),
|
||||
}).getValue()
|
||||
|
||||
sharedVaultInviteRepository = {} as jest.Mocked<SharedVaultInviteRepositoryInterface>
|
||||
sharedVaultInviteRepository.findBySenderUuidAndSharedVaultUuid = jest.fn().mockReturnValue([sharedVaultInvite])
|
||||
|
||||
declineInviteToSharedVault = {} as jest.Mocked<DeclineInviteToSharedVault>
|
||||
declineInviteToSharedVault.execute = jest.fn().mockReturnValue(Result.ok())
|
||||
})
|
||||
|
||||
it('should decline all invites by user', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBeFalsy()
|
||||
expect(declineInviteToSharedVault.execute).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should return error when user uuid is invalid', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: 'invalid-uuid',
|
||||
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBeTruthy()
|
||||
})
|
||||
|
||||
it('should return error when shared vault uuid is invalid', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
sharedVaultUuid: 'invalid-uuid',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBeTruthy()
|
||||
})
|
||||
|
||||
it('should return error when declineInviteToSharedVault fails', async () => {
|
||||
declineInviteToSharedVault.execute = jest.fn().mockReturnValue(Result.fail('error'))
|
||||
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
sharedVaultUuid: '00000000-0000-0000-0000-000000000000',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBeTruthy()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,41 @@
|
||||
import { Result, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
|
||||
import { DeleteSharedVaultInvitesSentByUserDTO } from './DeleteSharedVaultInvitesSentByUserDTO'
|
||||
import { DeclineInviteToSharedVault } from '../DeclineInviteToSharedVault/DeclineInviteToSharedVault'
|
||||
import { SharedVaultInviteRepositoryInterface } from '../../SharedVault/User/Invite/SharedVaultInviteRepositoryInterface'
|
||||
|
||||
export class DeleteSharedVaultInvitesSentByUser implements UseCaseInterface<void> {
|
||||
constructor(
|
||||
private sharedVaultInviteRepository: SharedVaultInviteRepositoryInterface,
|
||||
private declineInviteToSharedVault: DeclineInviteToSharedVault,
|
||||
) {}
|
||||
|
||||
async execute(dto: DeleteSharedVaultInvitesSentByUserDTO): Promise<Result<void>> {
|
||||
const userUuidOrError = Uuid.create(dto.userUuid)
|
||||
if (userUuidOrError.isFailed()) {
|
||||
return Result.fail(userUuidOrError.getError())
|
||||
}
|
||||
const userUuid = userUuidOrError.getValue()
|
||||
|
||||
const sharedVaultUuidOrError = Uuid.create(dto.sharedVaultUuid)
|
||||
if (sharedVaultUuidOrError.isFailed()) {
|
||||
return Result.fail(sharedVaultUuidOrError.getError())
|
||||
}
|
||||
const sharedVaultUuid = sharedVaultUuidOrError.getValue()
|
||||
|
||||
const inboundInvites = await this.sharedVaultInviteRepository.findBySenderUuidAndSharedVaultUuid({
|
||||
senderUuid: userUuid,
|
||||
sharedVaultUuid,
|
||||
})
|
||||
for (const invite of inboundInvites) {
|
||||
const result = await this.declineInviteToSharedVault.execute({
|
||||
inviteUuid: invite.id.toString(),
|
||||
originatorUuid: userUuid.value,
|
||||
})
|
||||
if (result.isFailed()) {
|
||||
return Result.fail(result.getError())
|
||||
}
|
||||
}
|
||||
|
||||
return Result.ok()
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user