mirror of
https://github.com/standardnotes/server
synced 2026-10-17 03:03:10 -04:00
refactor: rename groups to vaults
This commit is contained in:
@@ -12,7 +12,7 @@ import '../src/Controller/v1/InvoicesController'
|
||||
import '../src/Controller/v1/RevisionsController'
|
||||
import '../src/Controller/v1/ItemsController'
|
||||
import '../src/Controller/v1/ContactsController'
|
||||
import '../src/Controller/v1/GroupsController'
|
||||
import '../src/Controller/v1/VaultsController'
|
||||
import '../src/Controller/v1/PaymentsController'
|
||||
import '../src/Controller/v1/WebSocketsController'
|
||||
import '../src/Controller/v1/TokensController'
|
||||
|
||||
@@ -10,7 +10,7 @@ export * from './v1/FilesController'
|
||||
export * from './v1/InvoicesController'
|
||||
export * from './v1/ItemsController'
|
||||
export * from './v1/ContactsController'
|
||||
export * from './v1/GroupsController'
|
||||
export * from './v1/VaultsController'
|
||||
export * from './v1/OfflineController'
|
||||
export * from './v1/PaymentsController'
|
||||
export * from './v1/RevisionsController'
|
||||
|
||||
+2
-2
@@ -4,8 +4,8 @@ import { BaseHttpController, controller, all } from 'inversify-express-utils'
|
||||
import { TYPES } from '../../Bootstrap/Types'
|
||||
import { HttpServiceInterface } from '../../Service/Http/HttpServiceInterface'
|
||||
|
||||
@controller('/v1/groups')
|
||||
export class GroupsController extends BaseHttpController {
|
||||
@controller('/v1/vaults')
|
||||
export class VaultsController extends BaseHttpController {
|
||||
constructor(@inject(TYPES.HTTPService) private httpService: HttpServiceInterface) {
|
||||
super()
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm'
|
||||
|
||||
export class removeGroups1665047863774 implements MigrationInterface {
|
||||
name = 'removeGroups1665047863774'
|
||||
export class removeVaults1665047863774 implements MigrationInterface {
|
||||
name = 'removeVaults1665047863774'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query('ALTER TABLE `group_users` DROP FOREIGN KEY `FK_9d1bcb8c649eb05d7a2eb62114e`')
|
||||
|
||||
@@ -3,7 +3,7 @@ export enum ContentType {
|
||||
Any = '*',
|
||||
Item = 'SF|Item',
|
||||
SharedItemsKey = 'SN|SharedItemsKey',
|
||||
GroupKey = 'SN|GroupKey',
|
||||
VaultKey = 'SN|VaultKey',
|
||||
TrustedContact = 'SN|TrustedContact',
|
||||
RootKey = 'SN|RootKey|NoSync',
|
||||
ItemsKey = 'SN|ItemsKey',
|
||||
|
||||
@@ -6,7 +6,7 @@ import * as busboy from 'connect-busboy'
|
||||
|
||||
import '../src/Controller/HealthCheckController'
|
||||
import '../src/Controller/FilesController'
|
||||
import '../src/Controller/GroupFilesController'
|
||||
import '../src/Controller/VaultFilesController'
|
||||
|
||||
import helmet from 'helmet'
|
||||
import * as cors from 'cors'
|
||||
|
||||
@@ -44,7 +44,7 @@ import { MarkFilesToBeRemoved } from '../Domain/UseCase/MarkFilesToBeRemoved/Mar
|
||||
import { AccountDeletionRequestedEventHandler } from '../Domain/Handler/AccountDeletionRequestedEventHandler'
|
||||
import { SharedSubscriptionInvitationCanceledEventHandler } from '../Domain/Handler/SharedSubscriptionInvitationCanceledEventHandler'
|
||||
import { InMemoryUploadRepository } from '../Infra/InMemory/InMemoryUploadRepository'
|
||||
import { GroupValetTokenAuthMiddleware } from '../Controller/GroupValetTokenAuthMiddleware'
|
||||
import { VaultValetTokenAuthMiddleware } from '../Controller/VaultValetTokenAuthMiddleware'
|
||||
|
||||
export class ContainerConfigLoader {
|
||||
async load(): Promise<Container> {
|
||||
@@ -150,7 +150,7 @@ export class ContainerConfigLoader {
|
||||
|
||||
// middleware
|
||||
container.bind<ValetTokenAuthMiddleware>(TYPES.ValetTokenAuthMiddleware).to(ValetTokenAuthMiddleware)
|
||||
container.bind<GroupValetTokenAuthMiddleware>(TYPES.GroupValetTokenAuthMiddleware).to(GroupValetTokenAuthMiddleware)
|
||||
container.bind<VaultValetTokenAuthMiddleware>(TYPES.VaultValetTokenAuthMiddleware).to(VaultValetTokenAuthMiddleware)
|
||||
|
||||
// services
|
||||
container
|
||||
|
||||
@@ -29,7 +29,7 @@ const TYPES = {
|
||||
|
||||
// middleware
|
||||
ValetTokenAuthMiddleware: Symbol.for('ValetTokenAuthMiddleware'),
|
||||
GroupValetTokenAuthMiddleware: Symbol.for('GroupValetTokenAuthMiddleware'),
|
||||
VaultValetTokenAuthMiddleware: Symbol.for('VaultValetTokenAuthMiddleware'),
|
||||
|
||||
// env vars
|
||||
S3_ENDPOINT: Symbol.for('S3_ENDPOINT'),
|
||||
|
||||
+6
-6
@@ -5,10 +5,10 @@ import { Writable } from 'stream'
|
||||
import TYPES from '../Bootstrap/Types'
|
||||
import { StreamDownloadFile } from '../Domain/UseCase/StreamDownloadFile/StreamDownloadFile'
|
||||
import { GetFileMetadata } from '../Domain/UseCase/GetFileMetadata/GetFileMetadata'
|
||||
import { GroupValetTokenData, ValetTokenOperation } from '@standardnotes/security'
|
||||
import { VaultValetTokenData, ValetTokenOperation } from '@standardnotes/security'
|
||||
|
||||
@controller('/v1/groups/files', TYPES.GroupValetTokenAuthMiddleware)
|
||||
export class GroupFilesController extends BaseHttpController {
|
||||
@controller('/v1/vault/files', TYPES.VaultValetTokenAuthMiddleware)
|
||||
export class VaultFilesController extends BaseHttpController {
|
||||
constructor(
|
||||
@inject(TYPES.StreamDownloadFile) private streamDownloadFile: StreamDownloadFile,
|
||||
@inject(TYPES.GetFileMetadata) private getFileMetadata: GetFileMetadata,
|
||||
@@ -36,10 +36,10 @@ export class GroupFilesController extends BaseHttpController {
|
||||
chunkSize = this.maxChunkBytes
|
||||
}
|
||||
|
||||
const locals = response.locals as GroupValetTokenData
|
||||
const locals = response.locals as VaultValetTokenData
|
||||
|
||||
const fileMetadata = await this.getFileMetadata.execute({
|
||||
userUuid: locals.fileOwnerUuid,
|
||||
vaultUuid: locals.vaultUuid,
|
||||
resourceRemoteIdentifier: locals.remoteIdentifier,
|
||||
})
|
||||
|
||||
@@ -60,7 +60,7 @@ export class GroupFilesController extends BaseHttpController {
|
||||
response.writeHead(206, headers)
|
||||
|
||||
const result = await this.streamDownloadFile.execute({
|
||||
userUuid: locals.fileOwnerUuid,
|
||||
vaultUuid: locals.vaultUuid,
|
||||
resourceRemoteIdentifier: locals.remoteIdentifier,
|
||||
startRange,
|
||||
endRange,
|
||||
+7
-8
@@ -1,4 +1,4 @@
|
||||
import { GroupValetTokenData, TokenDecoderInterface } from '@standardnotes/security'
|
||||
import { VaultValetTokenData, TokenDecoderInterface } from '@standardnotes/security'
|
||||
import { Uuid } from '@standardnotes/domain-core'
|
||||
import { NextFunction, Request, Response } from 'express'
|
||||
import { inject, injectable } from 'inversify'
|
||||
@@ -7,9 +7,9 @@ import { Logger } from 'winston'
|
||||
import TYPES from '../Bootstrap/Types'
|
||||
|
||||
@injectable()
|
||||
export class GroupValetTokenAuthMiddleware extends BaseMiddleware {
|
||||
export class VaultValetTokenAuthMiddleware extends BaseMiddleware {
|
||||
constructor(
|
||||
@inject(TYPES.ValetTokenDecoder) private tokenDecoder: TokenDecoderInterface<GroupValetTokenData>,
|
||||
@inject(TYPES.ValetTokenDecoder) private tokenDecoder: TokenDecoderInterface<VaultValetTokenData>,
|
||||
@inject(TYPES.Logger) private logger: Logger,
|
||||
) {
|
||||
super()
|
||||
@@ -19,7 +19,7 @@ export class GroupValetTokenAuthMiddleware extends BaseMiddleware {
|
||||
try {
|
||||
const valetToken = request.headers['x-valet-token'] || request.body.valetToken || request.query.valetToken
|
||||
if (!valetToken) {
|
||||
this.logger.debug('GroupValetTokenAuthMiddleware missing valet token.')
|
||||
this.logger.debug('VaultValetTokenAuthMiddleware missing valet token.')
|
||||
|
||||
response.status(401).send({
|
||||
error: {
|
||||
@@ -34,7 +34,7 @@ export class GroupValetTokenAuthMiddleware extends BaseMiddleware {
|
||||
const valetTokenData = this.tokenDecoder.decodeToken(valetToken)
|
||||
|
||||
if (valetTokenData === undefined) {
|
||||
this.logger.debug('GroupValetTokenAuthMiddleware authentication failure.')
|
||||
this.logger.debug('VaultValetTokenAuthMiddleware authentication failure.')
|
||||
|
||||
response.status(401).send({
|
||||
error: {
|
||||
@@ -60,9 +60,8 @@ export class GroupValetTokenAuthMiddleware extends BaseMiddleware {
|
||||
return
|
||||
}
|
||||
|
||||
const whitelistedData: GroupValetTokenData = {
|
||||
groupUuid: valetTokenData.groupUuid,
|
||||
fileOwnerUuid: valetTokenData.fileOwnerUuid,
|
||||
const whitelistedData: VaultValetTokenData = {
|
||||
vaultUuid: valetTokenData.vaultUuid,
|
||||
remoteIdentifier: valetTokenData.remoteIdentifier,
|
||||
permittedOperation: valetTokenData.permittedOperation,
|
||||
}
|
||||
@@ -3,7 +3,7 @@ import { Logger } from 'winston'
|
||||
import TYPES from '../../../Bootstrap/Types'
|
||||
import { FileDownloaderInterface } from '../../Services/FileDownloaderInterface'
|
||||
import { UseCaseInterface } from '../UseCaseInterface'
|
||||
import { GetFileMetadataDTO } from './GetFileMetadataDTO'
|
||||
import { GetFileMetadataDTO, isGetFileMetadataVaultOwned } from './GetFileMetadataDTO'
|
||||
import { GetFileMetadataResponse } from './GetFileMetadataResponse'
|
||||
|
||||
@injectable()
|
||||
@@ -14,15 +14,16 @@ export class GetFileMetadata implements UseCaseInterface {
|
||||
) {}
|
||||
|
||||
async execute(dto: GetFileMetadataDTO): Promise<GetFileMetadataResponse> {
|
||||
const ownerUuid = isGetFileMetadataVaultOwned(dto) ? dto.vaultUuid : dto.userUuid
|
||||
try {
|
||||
const size = await this.fileDownloader.getFileSize(`${dto.userUuid}/${dto.resourceRemoteIdentifier}`)
|
||||
const size = await this.fileDownloader.getFileSize(`${ownerUuid}/${dto.resourceRemoteIdentifier}`)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
size,
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.error(`Could not get file metadata for resource: ${dto.userUuid}/${dto.resourceRemoteIdentifier}`)
|
||||
this.logger.error(`Could not get file metadata for resource: ${ownerUuid}/${dto.resourceRemoteIdentifier}`)
|
||||
return {
|
||||
success: false,
|
||||
message: 'Could not get file metadata.',
|
||||
|
||||
@@ -1,4 +1,15 @@
|
||||
export type GetFileMetadataDTO = {
|
||||
export type GetFileMetadataDTO = GetVaultFileMetadataDTO | GetUserFileMetadataDTO
|
||||
|
||||
export type GetVaultFileMetadataDTO = {
|
||||
vaultUuid: string
|
||||
resourceRemoteIdentifier: string
|
||||
}
|
||||
|
||||
export type GetUserFileMetadataDTO = {
|
||||
userUuid: string
|
||||
resourceRemoteIdentifier: string
|
||||
}
|
||||
|
||||
export function isGetFileMetadataVaultOwned(dto: GetFileMetadataDTO): dto is GetVaultFileMetadataDTO {
|
||||
return (dto as GetVaultFileMetadataDTO).vaultUuid !== undefined
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Logger } from 'winston'
|
||||
import TYPES from '../../../Bootstrap/Types'
|
||||
import { FileDownloaderInterface } from '../../Services/FileDownloaderInterface'
|
||||
import { UseCaseInterface } from '../UseCaseInterface'
|
||||
import { StreamDownloadFileDTO } from './StreamDownloadFileDTO'
|
||||
import { StreamDownloadFileDTO, isStreamDownloadFileVaultOwned } from './StreamDownloadFileDTO'
|
||||
import { StreamDownloadFileResponse } from './StreamDownloadFileResponse'
|
||||
|
||||
@injectable()
|
||||
@@ -14,9 +14,10 @@ export class StreamDownloadFile implements UseCaseInterface {
|
||||
) {}
|
||||
|
||||
async execute(dto: StreamDownloadFileDTO): Promise<StreamDownloadFileResponse> {
|
||||
const ownerUuid = isStreamDownloadFileVaultOwned(dto) ? dto.vaultUuid : dto.userUuid
|
||||
try {
|
||||
const readStream = await this.fileDownloader.createDownloadStream(
|
||||
`${dto.userUuid}/${dto.resourceRemoteIdentifier}`,
|
||||
`${ownerUuid}/${dto.resourceRemoteIdentifier}`,
|
||||
dto.startRange,
|
||||
dto.endRange,
|
||||
)
|
||||
@@ -26,9 +27,7 @@ export class StreamDownloadFile implements UseCaseInterface {
|
||||
readStream,
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.error(
|
||||
`Could not create a download stream for resource: ${dto.userUuid}/${dto.resourceRemoteIdentifier}`,
|
||||
)
|
||||
this.logger.error(`Could not create a download stream for resource: ${ownerUuid}/${dto.resourceRemoteIdentifier}`)
|
||||
|
||||
return {
|
||||
success: false,
|
||||
|
||||
@@ -1,6 +1,19 @@
|
||||
export type StreamDownloadFileDTO = {
|
||||
export type StreamDownloadFileDTO = StreamDownloadVaultFileDTO | StreamDownloadUserFileDTO
|
||||
|
||||
export type StreamDownloadVaultFileDTO = {
|
||||
vaultUuid: string
|
||||
resourceRemoteIdentifier: string
|
||||
startRange: number
|
||||
endRange: number
|
||||
}
|
||||
|
||||
export type StreamDownloadUserFileDTO = {
|
||||
userUuid: string
|
||||
resourceRemoteIdentifier: string
|
||||
startRange: number
|
||||
endRange: number
|
||||
}
|
||||
|
||||
export function isStreamDownloadFileVaultOwned(dto: StreamDownloadFileDTO): dto is StreamDownloadVaultFileDTO {
|
||||
return (dto as StreamDownloadVaultFileDTO).vaultUuid !== undefined
|
||||
}
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
import { ValetTokenOperation } from './ValetTokenOperation'
|
||||
|
||||
export type GroupValetTokenData = {
|
||||
fileOwnerUuid: string
|
||||
groupUuid: string
|
||||
permittedOperation: ValetTokenOperation.Read
|
||||
remoteIdentifier: string
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { ValetTokenOperation } from './ValetTokenOperation'
|
||||
|
||||
export type VaultValetTokenData = {
|
||||
vaultUuid: string
|
||||
permittedOperation: ValetTokenOperation
|
||||
remoteIdentifier: string
|
||||
}
|
||||
@@ -10,7 +10,7 @@ export * from './Token/CrossServiceTokenData'
|
||||
export * from './Token/OfflineFeaturesTokenData'
|
||||
export * from './Token/OfflineUserTokenData'
|
||||
export * from './Token/SessionTokenData'
|
||||
export * from './Token/GroupValetTokenData'
|
||||
export * from './Token/VaultValetTokenData'
|
||||
export * from './Token/ValetTokenData'
|
||||
export * from './Token/ValetTokenOperation'
|
||||
export * from './Token/WebSocketConnectionToken'
|
||||
|
||||
@@ -3,5 +3,5 @@
|
||||
### Generating a migration
|
||||
|
||||
```
|
||||
yarn build && yarn typeorm migration:generate -d dist/src/Bootstrap/DataSource.js migrations/mysql/add_group_uuid_to_items
|
||||
yarn build && yarn typeorm migration:generate -d dist/src/Bootstrap/DataSource.js migrations/mysql/add_vault_uuid_to_items
|
||||
```
|
||||
@@ -4,9 +4,9 @@ import 'newrelic'
|
||||
|
||||
import '../src/Controller/HealthCheckController'
|
||||
import '../src/Controller/ItemsController'
|
||||
import '../src/Controller/GroupsController'
|
||||
import '../src/Controller/GroupUsersController'
|
||||
import '../src/Controller/GroupInvitesController'
|
||||
import '../src/Controller/VaultsController'
|
||||
import '../src/Controller/VaultUsersController'
|
||||
import '../src/Controller/VaultInvitesController'
|
||||
import '../src/Controller/ContactsController'
|
||||
|
||||
import helmet from 'helmet'
|
||||
|
||||
+5
-5
@@ -1,11 +1,11 @@
|
||||
import { MigrationInterface, QueryRunner, Table, TableColumn } from 'typeorm'
|
||||
|
||||
export class CreateGroup1684173017359 implements MigrationInterface {
|
||||
name = 'createGroup1684173017359'
|
||||
export class CreateVault1684173017359 implements MigrationInterface {
|
||||
name = 'createVault1684173017359'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
const table = new Table({
|
||||
name: 'groups',
|
||||
name: 'vaults',
|
||||
columns: [
|
||||
new TableColumn({
|
||||
name: 'uuid',
|
||||
@@ -26,7 +26,7 @@ export class CreateGroup1684173017359 implements MigrationInterface {
|
||||
isNullable: false,
|
||||
}),
|
||||
new TableColumn({
|
||||
name: 'group_key_timestamp',
|
||||
name: 'vault_key_timestamp',
|
||||
type: 'bigint',
|
||||
}),
|
||||
new TableColumn({
|
||||
@@ -44,6 +44,6 @@ export class CreateGroup1684173017359 implements MigrationInterface {
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.dropTable('groups')
|
||||
await queryRunner.dropTable('vaults')
|
||||
}
|
||||
}
|
||||
+5
-5
@@ -1,11 +1,11 @@
|
||||
import { MigrationInterface, QueryRunner, Table, TableColumn } from 'typeorm'
|
||||
|
||||
export class CreateGroupUser1684173023883 implements MigrationInterface {
|
||||
name = 'createGroupUser1684173023883'
|
||||
export class CreateVaultUser1684173023883 implements MigrationInterface {
|
||||
name = 'createVaultUser1684173023883'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
const table = new Table({
|
||||
name: 'group_users',
|
||||
name: 'vault_users',
|
||||
columns: [
|
||||
new TableColumn({
|
||||
name: 'uuid',
|
||||
@@ -14,7 +14,7 @@ export class CreateGroupUser1684173023883 implements MigrationInterface {
|
||||
isPrimary: true,
|
||||
}),
|
||||
new TableColumn({
|
||||
name: 'group_uuid',
|
||||
name: 'vault_uuid',
|
||||
type: 'varchar',
|
||||
length: '36',
|
||||
isNullable: false,
|
||||
@@ -46,6 +46,6 @@ export class CreateGroupUser1684173023883 implements MigrationInterface {
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.dropTable('group_users')
|
||||
await queryRunner.dropTable('vault_users')
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -1,13 +1,13 @@
|
||||
import { MigrationInterface, QueryRunner, TableColumn } from 'typeorm'
|
||||
|
||||
export class AddGroupUuidToItems1684318117584 implements MigrationInterface {
|
||||
name = 'addGroupUuidToItems1684318117584'
|
||||
export class AddVaultUuidToItems1684318117584 implements MigrationInterface {
|
||||
name = 'addVaultUuidToItems1684318117584'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.addColumn(
|
||||
'items',
|
||||
new TableColumn({
|
||||
name: 'group_uuid',
|
||||
name: 'vault_uuid',
|
||||
type: 'varchar',
|
||||
length: '36',
|
||||
isNullable: true,
|
||||
@@ -25,7 +25,7 @@ export class AddGroupUuidToItems1684318117584 implements MigrationInterface {
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.dropColumn('items', 'group_uuid')
|
||||
await queryRunner.dropColumn('items', 'vault_uuid')
|
||||
await queryRunner.dropColumn('items', 'last_edited_by_uuid')
|
||||
}
|
||||
}
|
||||
+6
-6
@@ -1,11 +1,11 @@
|
||||
import { MigrationInterface, QueryRunner, Table, TableColumn } from 'typeorm'
|
||||
|
||||
export class CreateGroupInvites1684528646896 implements MigrationInterface {
|
||||
name = 'createGroupInvites1684528646896'
|
||||
export class CreateVaultInvites1684528646896 implements MigrationInterface {
|
||||
name = 'createVaultInvites1684528646896'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
const table = new Table({
|
||||
name: 'group_invites',
|
||||
name: 'vault_invites',
|
||||
columns: [
|
||||
new TableColumn({
|
||||
name: 'uuid',
|
||||
@@ -14,7 +14,7 @@ export class CreateGroupInvites1684528646896 implements MigrationInterface {
|
||||
isPrimary: true,
|
||||
}),
|
||||
new TableColumn({
|
||||
name: 'group_uuid',
|
||||
name: 'vault_uuid',
|
||||
type: 'varchar',
|
||||
length: '36',
|
||||
isNullable: false,
|
||||
@@ -44,7 +44,7 @@ export class CreateGroupInvites1684528646896 implements MigrationInterface {
|
||||
isNullable: false,
|
||||
}),
|
||||
new TableColumn({
|
||||
name: 'encrypted_group_data',
|
||||
name: 'encrypted_vault_data',
|
||||
type: 'text',
|
||||
isNullable: false,
|
||||
}),
|
||||
@@ -69,6 +69,6 @@ export class CreateGroupInvites1684528646896 implements MigrationInterface {
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.dropTable('group_invites')
|
||||
await queryRunner.dropTable('vault_invites')
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
import { MigrationInterface, QueryRunner, TableColumn } from 'typeorm'
|
||||
|
||||
export class AllowItemUserUuidToBeNull1685276859271 implements MigrationInterface {
|
||||
name = 'allowItemUserUuidToBeNull1685276859271'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.changeColumn(
|
||||
'items',
|
||||
'user_uuid',
|
||||
new TableColumn({
|
||||
name: 'user_uuid',
|
||||
type: 'varchar',
|
||||
length: '36',
|
||||
isNullable: true,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.changeColumn(
|
||||
'items',
|
||||
'user_uuid',
|
||||
new TableColumn({
|
||||
name: 'user_uuid',
|
||||
type: 'varchar',
|
||||
length: '36',
|
||||
isNullable: false,
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
import { TypeORMGroupInviteRepository } from './../Domain/GroupInvite/Repository/TypeORMGroupInviteRepository'
|
||||
import { TypeORMVaultInviteRepository } from '../Domain/VaultInvite/Repository/TypeORMVaultInviteRepository'
|
||||
import { Contact } from './../Domain/Contact/Model/Contact'
|
||||
import { GroupInvite } from './../Domain/GroupInvite/Model/GroupInvite'
|
||||
import { GroupUser } from '../Domain/GroupUser/Model/GroupUser'
|
||||
import { Group } from './../Domain/Group/Model/Group'
|
||||
import { TypeORMGroupRepository } from './../Domain/Group/Repository/TypeORMGroupRepository'
|
||||
import { VaultInvite } from '../Domain/VaultInvite/Model/VaultInvite'
|
||||
import { VaultUser } from '../Domain/VaultUser/Model/VaultUser'
|
||||
import { Vault } from '../Domain/Vault/Model/Vault'
|
||||
import { TypeORMVaultRepository } from '../Domain/Vault/Repository/TypeORMVaultRepository'
|
||||
import * as winston from 'winston'
|
||||
import { Container, interfaces } from 'inversify'
|
||||
|
||||
@@ -24,18 +24,18 @@ import { DomainEventFactory } from '../Domain/Event/DomainEventFactory'
|
||||
import { Timer, TimerInterface } from '@standardnotes/time'
|
||||
import { ItemTransferCalculatorInterface } from '../Domain/Item/ItemTransferCalculatorInterface'
|
||||
import { ItemTransferCalculator } from '../Domain/Item/ItemTransferCalculator'
|
||||
import { GroupsRepositoryInterface } from '../Domain/Group/Repository/GroupRepositoryInterface'
|
||||
import { GroupUserRepositoryInterface } from '../Domain/GroupUser/Repository/GroupUserRepositoryInterface'
|
||||
import { TypeORMGroupUserRepository } from '../Domain/GroupUser/Repository/TypeORMGroupUserRepository'
|
||||
import { GroupUserProjection } from '../Projection/GroupUserProjection'
|
||||
import { GroupUserProjector } from '../Projection/GroupUserProjector'
|
||||
import { GroupProjection } from '../Projection/GroupProjection'
|
||||
import { GroupProjector } from '../Projection/GroupProjector'
|
||||
import { GroupInviteProjection } from '../Projection/GroupInviteProjection'
|
||||
import { GroupInviteProjector } from '../Projection/GroupInviteProjector'
|
||||
import { VaultsRepositoryInterface } from '../Domain/Vault/Repository/VaultRepositoryInterface'
|
||||
import { VaultUserRepositoryInterface } from '../Domain/VaultUser/Repository/VaultUserRepositoryInterface'
|
||||
import { TypeORMVaultUserRepository } from '../Domain/VaultUser/Repository/TypeORMVaultUserRepository'
|
||||
import { VaultUserProjection } from '../Projection/VaultUserProjection'
|
||||
import { VaultUserProjector } from '../Projection/VaultUserProjector'
|
||||
import { VaultProjection } from '../Projection/VaultProjection'
|
||||
import { VaultProjector } from '../Projection/VaultProjector'
|
||||
import { VaultInviteProjection } from '../Projection/VaultInviteProjection'
|
||||
import { VaultInviteProjector } from '../Projection/VaultInviteProjector'
|
||||
import { ContactProjection } from '../Projection/ContactProjection'
|
||||
import { ContactProjector } from '../Projection/ContactProjector'
|
||||
import { GroupInviteRepositoryInterface } from '../Domain/GroupInvite/Repository/GroupInviteRepositoryInterface'
|
||||
import { VaultInviteRepositoryInterface } from '../Domain/VaultInvite/Repository/VaultInviteRepositoryInterface'
|
||||
import { ContactsRepositoryInterface } from '../Domain/Contact/Repository/ContactRepositoryInterface'
|
||||
import { TypeORMContactRepository } from '../Domain/Contact/Repository/TypeORMContactRepository'
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
@@ -96,18 +96,18 @@ export class CommonContainerConfigLoader {
|
||||
container.bind<ItemRepositoryInterface>(TYPES.ItemRepository).toDynamicValue((context: interfaces.Context) => {
|
||||
return new TypeORMItemRepository(context.container.get(TYPES.ORMItemRepository))
|
||||
})
|
||||
container.bind<GroupsRepositoryInterface>(TYPES.GroupRepository).toDynamicValue((context: interfaces.Context) => {
|
||||
return new TypeORMGroupRepository(context.container.get(TYPES.ORMGroupRepository))
|
||||
container.bind<VaultsRepositoryInterface>(TYPES.VaultRepository).toDynamicValue((context: interfaces.Context) => {
|
||||
return new TypeORMVaultRepository(context.container.get(TYPES.ORMVaultRepository))
|
||||
})
|
||||
container
|
||||
.bind<GroupUserRepositoryInterface>(TYPES.GroupUserRepository)
|
||||
.bind<VaultUserRepositoryInterface>(TYPES.VaultUserRepository)
|
||||
.toDynamicValue((context: interfaces.Context) => {
|
||||
return new TypeORMGroupUserRepository(context.container.get(TYPES.ORMGroupUserRepository))
|
||||
return new TypeORMVaultUserRepository(context.container.get(TYPES.ORMVaultUserRepository))
|
||||
})
|
||||
container
|
||||
.bind<GroupInviteRepositoryInterface>(TYPES.GroupInviteRepository)
|
||||
.bind<VaultInviteRepositoryInterface>(TYPES.VaultInviteRepository)
|
||||
.toDynamicValue((context: interfaces.Context) => {
|
||||
return new TypeORMGroupInviteRepository(context.container.get(TYPES.ORMGroupInviteRepository))
|
||||
return new TypeORMVaultInviteRepository(context.container.get(TYPES.ORMVaultInviteRepository))
|
||||
})
|
||||
container
|
||||
.bind<ContactsRepositoryInterface>(TYPES.ContactRepository)
|
||||
@@ -117,13 +117,13 @@ export class CommonContainerConfigLoader {
|
||||
|
||||
// ORM
|
||||
container.bind<Repository<Item>>(TYPES.ORMItemRepository).toDynamicValue(() => AppDataSource.getRepository(Item))
|
||||
container.bind<Repository<Group>>(TYPES.ORMGroupRepository).toDynamicValue(() => AppDataSource.getRepository(Group))
|
||||
container.bind<Repository<Vault>>(TYPES.ORMVaultRepository).toDynamicValue(() => AppDataSource.getRepository(Vault))
|
||||
container
|
||||
.bind<Repository<GroupUser>>(TYPES.ORMGroupUserRepository)
|
||||
.toDynamicValue(() => AppDataSource.getRepository(GroupUser))
|
||||
.bind<Repository<VaultUser>>(TYPES.ORMVaultUserRepository)
|
||||
.toDynamicValue(() => AppDataSource.getRepository(VaultUser))
|
||||
container
|
||||
.bind<Repository<GroupInvite>>(TYPES.ORMGroupInviteRepository)
|
||||
.toDynamicValue(() => AppDataSource.getRepository(GroupInvite))
|
||||
.bind<Repository<VaultInvite>>(TYPES.ORMVaultInviteRepository)
|
||||
.toDynamicValue(() => AppDataSource.getRepository(VaultInvite))
|
||||
container
|
||||
.bind<Repository<Contact>>(TYPES.ORMContactRepository)
|
||||
.toDynamicValue(() => AppDataSource.getRepository(Contact))
|
||||
@@ -134,16 +134,16 @@ export class CommonContainerConfigLoader {
|
||||
.toDynamicValue((context: interfaces.Context) => {
|
||||
return new ItemProjector(context.container.get(TYPES.Timer))
|
||||
})
|
||||
container.bind<ProjectorInterface<Group, GroupProjection>>(TYPES.GroupProjector).toDynamicValue(() => {
|
||||
return new GroupProjector()
|
||||
container.bind<ProjectorInterface<Vault, VaultProjection>>(TYPES.VaultProjector).toDynamicValue(() => {
|
||||
return new VaultProjector()
|
||||
})
|
||||
container.bind<ProjectorInterface<GroupUser, GroupUserProjection>>(TYPES.GroupUserProjector).toDynamicValue(() => {
|
||||
return new GroupUserProjector()
|
||||
container.bind<ProjectorInterface<VaultUser, VaultUserProjection>>(TYPES.VaultUserProjector).toDynamicValue(() => {
|
||||
return new VaultUserProjector()
|
||||
})
|
||||
container
|
||||
.bind<ProjectorInterface<GroupInvite, GroupInviteProjection>>(TYPES.GroupInviteProjector)
|
||||
.bind<ProjectorInterface<VaultInvite, VaultInviteProjection>>(TYPES.VaultInviteProjector)
|
||||
.toDynamicValue(() => {
|
||||
return new GroupInviteProjector()
|
||||
return new VaultInviteProjector()
|
||||
})
|
||||
container.bind<ProjectorInterface<Contact, ContactProjection>>(TYPES.ContactProjector).toDynamicValue(() => {
|
||||
return new ContactProjector()
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { Contact } from './../Domain/Contact/Model/Contact'
|
||||
import { GroupInvite } from './../Domain/GroupInvite/Model/GroupInvite'
|
||||
import { GroupUser } from '../Domain/GroupUser/Model/GroupUser'
|
||||
import { VaultInvite } from '../Domain/VaultInvite/Model/VaultInvite'
|
||||
import { VaultUser } from '../Domain/VaultUser/Model/VaultUser'
|
||||
import { DataSource, LoggerOptions } from 'typeorm'
|
||||
import { MysqlConnectionOptions } from 'typeorm/driver/mysql/MysqlConnectionOptions'
|
||||
import { Item } from '../Domain/Item/Item'
|
||||
import { Env } from './Env'
|
||||
import { SqliteConnectionOptions } from 'typeorm/driver/sqlite/SqliteConnectionOptions'
|
||||
import { Group } from '../Domain/Group/Model/Group'
|
||||
import { Vault } from '../Domain/Vault/Model/Vault'
|
||||
|
||||
const env: Env = new Env()
|
||||
env.load()
|
||||
@@ -42,7 +42,7 @@ const replicationConfig = {
|
||||
|
||||
const commonDataSourceOptions = {
|
||||
maxQueryExecutionTime,
|
||||
entities: [Item, Group, GroupUser, GroupInvite, Contact],
|
||||
entities: [Item, Vault, VaultUser, VaultInvite, Contact],
|
||||
migrations: [`dist/migrations/${isConfiguredForMySQL ? 'mysql' : 'sqlite'}/*.js`],
|
||||
migrationsRun: true,
|
||||
logging: <LoggerOptions>env.get('DB_DEBUG_LEVEL'),
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { GetGlobalItem } from './../Domain/UseCase/GetGlobalItem/GetGlobalItem'
|
||||
import { ContactFactory } from './../Domain/Contact/Factory/ContactFactory'
|
||||
|
||||
import { GroupServiceInterface } from './../Domain/Group/Service/GroupServiceInterface'
|
||||
import { GroupUserFactory } from '../Domain/GroupUser/Factory/GroupUserFactory'
|
||||
import { GroupFactoryInterface } from '../Domain/Group/Factory/GroupFactoryInterface'
|
||||
import { VaultServiceInterface } from '../Domain/Vault/Service/VaultServiceInterface'
|
||||
import { VaultUserFactory } from '../Domain/VaultUser/Factory/VaultUserFactory'
|
||||
import { VaultFactoryInterface } from '../Domain/Vault/Factory/VaultFactoryInterface'
|
||||
import { Container, interfaces } from 'inversify'
|
||||
|
||||
import { Env } from './Env'
|
||||
@@ -36,18 +36,18 @@ import { ItemConflict } from '../Domain/Item/ItemConflict'
|
||||
import { ItemConflictProjection } from '../Projection/ItemConflictProjection'
|
||||
import { CommonContainerConfigLoader } from './CommonContainerConfigLoader'
|
||||
import { TokenEncoder, TokenEncoderInterface, ValetTokenData } from '@standardnotes/security'
|
||||
import { GroupFactory } from '../Domain/Group/Factory/GroupFactory'
|
||||
import { GroupService } from '../Domain/Group/Service/GroupService'
|
||||
import { GroupUserService } from '../Domain/GroupUser/Service/GroupUserService'
|
||||
import { GroupUserServiceInterface } from '../Domain/GroupUser/Service/GroupUserServiceInterface'
|
||||
import { GroupInviteServiceInterface } from '../Domain/GroupInvite/Service/GroupInviteServiceInterface'
|
||||
import { GroupInviteService } from '../Domain/GroupInvite/Service/GroupInviteService'
|
||||
import { GroupInviteFactory } from '../Domain/GroupInvite/Factory/GroupInviteFactory'
|
||||
import { VaultFactory } from '../Domain/Vault/Factory/VaultFactory'
|
||||
import { VaultService } from '../Domain/Vault/Service/VaultService'
|
||||
import { VaultUserService } from '../Domain/VaultUser/Service/VaultUserService'
|
||||
import { VaultUserServiceInterface } from '../Domain/VaultUser/Service/VaultUserServiceInterface'
|
||||
import { VaultInviteServiceInterface } from '../Domain/VaultInvite/Service/VaultInviteServiceInterface'
|
||||
import { VaultInviteService } from '../Domain/VaultInvite/Service/VaultInviteService'
|
||||
import { VaultInviteFactory } from '../Domain/VaultInvite/Factory/VaultInviteFactory'
|
||||
import { ContactServiceInterface } from '../Domain/Contact/Service/ContactServiceInterface'
|
||||
import { ContactService } from '../Domain/Contact/Service/ContactService'
|
||||
import { ContactFactoryInterface } from '../Domain/Contact/Factory/ContactFactoryInterface'
|
||||
import { SnjsVersionFilter } from '../Domain/Item/SaveRule/SnjsVersionFilter'
|
||||
import { CreateGroupFileReadValetToken } from '../Domain/UseCase/CreateGroupFileValetToken/CreateGroupFileReadValetToken'
|
||||
import { CreateVaultFileReadValetToken } from '../Domain/UseCase/CreateVaultFileValetToken/CreateVaultFileReadValetToken'
|
||||
|
||||
export class ServerContainerConfigLoader extends CommonContainerConfigLoader {
|
||||
private readonly DEFAULT_CONTENT_SIZE_TRANSFER_LIMIT = 10_000_000
|
||||
@@ -99,8 +99,8 @@ export class ServerContainerConfigLoader extends CommonContainerConfigLoader {
|
||||
container.bind<SyncItems>(TYPES.SyncItems).toDynamicValue((context: interfaces.Context) => {
|
||||
return new SyncItems(
|
||||
context.container.get(TYPES.ItemService),
|
||||
context.container.get(TYPES.GroupService),
|
||||
context.container.get(TYPES.GroupInviteService),
|
||||
context.container.get(TYPES.VaultService),
|
||||
context.container.get(TYPES.VaultInviteService),
|
||||
context.container.get(TYPES.ContactService),
|
||||
)
|
||||
})
|
||||
@@ -115,11 +115,11 @@ export class ServerContainerConfigLoader extends CommonContainerConfigLoader {
|
||||
})
|
||||
|
||||
container
|
||||
.bind<CreateGroupFileReadValetToken>(TYPES.CreateGroupFileReadValetToken)
|
||||
.bind<CreateVaultFileReadValetToken>(TYPES.CreateVaultFileReadValetToken)
|
||||
.toDynamicValue((context: interfaces.Context) => {
|
||||
return new CreateGroupFileReadValetToken(
|
||||
return new CreateVaultFileReadValetToken(
|
||||
context.container.get(TYPES.GetGlobalItem),
|
||||
context.container.get(TYPES.GroupUserService),
|
||||
context.container.get(TYPES.VaultUserService),
|
||||
context.container.get(TYPES.ValetTokenEncoder),
|
||||
context.container.get(TYPES.VALET_TOKEN_TTL),
|
||||
)
|
||||
@@ -143,36 +143,36 @@ export class ServerContainerConfigLoader extends CommonContainerConfigLoader {
|
||||
context.container.get(TYPES.Timer),
|
||||
context.container.get(TYPES.ItemProjector),
|
||||
context.container.get(TYPES.MAX_ITEMS_LIMIT),
|
||||
context.container.get(TYPES.GroupUserRepository),
|
||||
context.container.get(TYPES.VaultUserRepository),
|
||||
context.container.get(TYPES.Logger),
|
||||
)
|
||||
})
|
||||
// Services
|
||||
container.bind<GroupServiceInterface>(TYPES.GroupService).toDynamicValue((context: interfaces.Context) => {
|
||||
return new GroupService(
|
||||
context.container.get(TYPES.GroupRepository),
|
||||
context.container.get(TYPES.GroupFactory),
|
||||
context.container.get(TYPES.GroupUserService),
|
||||
context.container.get(TYPES.GroupInviteService),
|
||||
container.bind<VaultServiceInterface>(TYPES.VaultService).toDynamicValue((context: interfaces.Context) => {
|
||||
return new VaultService(
|
||||
context.container.get(TYPES.VaultRepository),
|
||||
context.container.get(TYPES.VaultFactory),
|
||||
context.container.get(TYPES.VaultUserService),
|
||||
context.container.get(TYPES.VaultInviteService),
|
||||
context.container.get(TYPES.Timer),
|
||||
)
|
||||
})
|
||||
container.bind<GroupUserServiceInterface>(TYPES.GroupUserService).toDynamicValue((context: interfaces.Context) => {
|
||||
return new GroupUserService(
|
||||
context.container.get(TYPES.GroupRepository),
|
||||
context.container.get(TYPES.GroupUserRepository),
|
||||
context.container.get(TYPES.GroupUserFactory),
|
||||
container.bind<VaultUserServiceInterface>(TYPES.VaultUserService).toDynamicValue((context: interfaces.Context) => {
|
||||
return new VaultUserService(
|
||||
context.container.get(TYPES.VaultRepository),
|
||||
context.container.get(TYPES.VaultUserRepository),
|
||||
context.container.get(TYPES.VaultUserFactory),
|
||||
context.container.get(TYPES.Timer),
|
||||
)
|
||||
})
|
||||
container
|
||||
.bind<GroupInviteServiceInterface>(TYPES.GroupInviteService)
|
||||
.bind<VaultInviteServiceInterface>(TYPES.VaultInviteService)
|
||||
.toDynamicValue((context: interfaces.Context) => {
|
||||
return new GroupInviteService(
|
||||
context.container.get(TYPES.GroupRepository),
|
||||
context.container.get(TYPES.GroupInviteRepository),
|
||||
context.container.get(TYPES.GroupInviteFactory),
|
||||
context.container.get(TYPES.GroupUserService),
|
||||
return new VaultInviteService(
|
||||
context.container.get(TYPES.VaultRepository),
|
||||
context.container.get(TYPES.VaultInviteRepository),
|
||||
context.container.get(TYPES.VaultInviteFactory),
|
||||
context.container.get(TYPES.VaultUserService),
|
||||
context.container.get(TYPES.Timer),
|
||||
)
|
||||
})
|
||||
@@ -196,9 +196,9 @@ export class ServerContainerConfigLoader extends CommonContainerConfigLoader {
|
||||
context.container.get(TYPES.ItemProjector),
|
||||
context.container.get(TYPES.ItemConflictProjector),
|
||||
context.container.get(TYPES.SavedItemProjector),
|
||||
context.container.get(TYPES.GroupInviteProjector),
|
||||
context.container.get(TYPES.VaultInviteProjector),
|
||||
context.container.get(TYPES.ContactProjector),
|
||||
context.container.get(TYPES.GroupProjector),
|
||||
context.container.get(TYPES.VaultProjector),
|
||||
)
|
||||
})
|
||||
container
|
||||
@@ -213,14 +213,14 @@ export class ServerContainerConfigLoader extends CommonContainerConfigLoader {
|
||||
container.bind<ItemFactoryInterface>(TYPES.ItemFactory).toDynamicValue((context: interfaces.Context) => {
|
||||
return new ItemFactory(context.container.get(TYPES.Timer), context.container.get(TYPES.ItemProjector))
|
||||
})
|
||||
container.bind<GroupFactoryInterface>(TYPES.GroupFactory).toDynamicValue((context: interfaces.Context) => {
|
||||
return new GroupFactory(context.container.get(TYPES.Timer))
|
||||
container.bind<VaultFactoryInterface>(TYPES.VaultFactory).toDynamicValue((context: interfaces.Context) => {
|
||||
return new VaultFactory(context.container.get(TYPES.Timer))
|
||||
})
|
||||
container.bind<GroupUserFactory>(TYPES.GroupUserFactory).toDynamicValue((context: interfaces.Context) => {
|
||||
return new GroupUserFactory(context.container.get(TYPES.Timer))
|
||||
container.bind<VaultUserFactory>(TYPES.VaultUserFactory).toDynamicValue((context: interfaces.Context) => {
|
||||
return new VaultUserFactory(context.container.get(TYPES.Timer))
|
||||
})
|
||||
container.bind<GroupInviteFactory>(TYPES.GroupInviteFactory).toDynamicValue((context: interfaces.Context) => {
|
||||
return new GroupInviteFactory(context.container.get(TYPES.Timer))
|
||||
container.bind<VaultInviteFactory>(TYPES.VaultInviteFactory).toDynamicValue((context: interfaces.Context) => {
|
||||
return new VaultInviteFactory(context.container.get(TYPES.Timer))
|
||||
})
|
||||
container.bind<ContactFactoryInterface>(TYPES.ContactFactory).toDynamicValue((context: interfaces.Context) => {
|
||||
return new ContactFactory(context.container.get(TYPES.Timer))
|
||||
@@ -230,7 +230,7 @@ export class ServerContainerConfigLoader extends CommonContainerConfigLoader {
|
||||
.bind<OwnershipFilter>(TYPES.OwnershipFilter)
|
||||
.toDynamicValue(
|
||||
(context: interfaces.Context) =>
|
||||
new OwnershipFilter(context.container.get(TYPES.GroupService), context.container.get(TYPES.GroupUserService)),
|
||||
new OwnershipFilter(context.container.get(TYPES.VaultService), context.container.get(TYPES.VaultUserService)),
|
||||
)
|
||||
container
|
||||
.bind<TimeDifferenceFilter>(TYPES.TimeDifferenceFilter)
|
||||
|
||||
@@ -9,17 +9,17 @@ const TYPES = {
|
||||
|
||||
// Repositories
|
||||
ItemRepository: Symbol.for('ItemRepository'),
|
||||
GroupRepository: Symbol.for('GroupRepository'),
|
||||
GroupUserRepository: Symbol.for('GroupUserRepository'),
|
||||
VaultRepository: Symbol.for('VaultRepository'),
|
||||
VaultUserRepository: Symbol.for('VaultUserRepository'),
|
||||
ContactRepository: Symbol.for('ContactRepository'),
|
||||
GroupInviteRepository: Symbol.for('GroupInviteRepository'),
|
||||
VaultInviteRepository: Symbol.for('VaultInviteRepository'),
|
||||
|
||||
// ORM
|
||||
ORMItemRepository: Symbol.for('ORMItemRepository'),
|
||||
ORMGroupRepository: Symbol.for('ORMGroupRepository'),
|
||||
ORMGroupUserRepository: Symbol.for('ORMGroupUserRepository'),
|
||||
ORMVaultRepository: Symbol.for('ORMVaultRepository'),
|
||||
ORMVaultUserRepository: Symbol.for('ORMVaultUserRepository'),
|
||||
ORMContactRepository: Symbol.for('ORMContactRepository'),
|
||||
ORMGroupInviteRepository: Symbol.for('ORMGroupInviteRepository'),
|
||||
ORMVaultInviteRepository: Symbol.for('ORMVaultInviteRepository'),
|
||||
|
||||
// Middleware
|
||||
AuthMiddleware: Symbol.for('AuthMiddleware'),
|
||||
@@ -28,23 +28,23 @@ const TYPES = {
|
||||
ItemProjector: Symbol.for('ItemProjector'),
|
||||
SavedItemProjector: Symbol.for('SavedItemProjector'),
|
||||
ItemConflictProjector: Symbol.for('ItemConflictProjector'),
|
||||
GroupProjector: Symbol.for('GroupProjector'),
|
||||
GroupUserProjector: Symbol.for('GroupUserProjector'),
|
||||
VaultProjector: Symbol.for('VaultProjector'),
|
||||
VaultUserProjector: Symbol.for('VaultUserProjector'),
|
||||
ContactProjector: Symbol.for('ContactProjector'),
|
||||
GroupInviteProjector: Symbol.for('GroupInviteProjector'),
|
||||
VaultInviteProjector: Symbol.for('VaultInviteProjector'),
|
||||
|
||||
// Services
|
||||
GroupService: Symbol.for('GroupService'),
|
||||
GroupUserService: Symbol.for('GroupUserService'),
|
||||
VaultService: Symbol.for('VaultService'),
|
||||
VaultUserService: Symbol.for('VaultUserService'),
|
||||
ContactService: Symbol.for('ContactService'),
|
||||
GroupInviteService: Symbol.for('GroupInviteService'),
|
||||
VaultInviteService: Symbol.for('VaultInviteService'),
|
||||
|
||||
// Factories
|
||||
ItemFactory: Symbol.for('ItemFactory'),
|
||||
GroupFactory: Symbol.for('GroupFactory'),
|
||||
GroupUserFactory: Symbol.for('GroupUserFactory'),
|
||||
VaultFactory: Symbol.for('VaultFactory'),
|
||||
VaultUserFactory: Symbol.for('VaultUserFactory'),
|
||||
ContactFactory: Symbol.for('ContactFactory'),
|
||||
GroupInviteFactory: Symbol.for('GroupInviteFactory'),
|
||||
VaultInviteFactory: Symbol.for('VaultInviteFactory'),
|
||||
|
||||
// env vars
|
||||
REDIS_URL: Symbol.for('REDIS_URL'),
|
||||
@@ -71,7 +71,7 @@ const TYPES = {
|
||||
CheckIntegrity: Symbol.for('CheckIntegrity'),
|
||||
GetItem: Symbol.for('GetItem'),
|
||||
GetGlobalItem: Symbol.for('GetGlobalItem'),
|
||||
CreateGroupFileReadValetToken: Symbol.for('CreateGroupFileReadValetToken'),
|
||||
CreateVaultFileReadValetToken: Symbol.for('CreateVaultFileReadValetToken'),
|
||||
// Handlers
|
||||
UserCredentialsChangedEventHandler: Symbol.for('UserCredentialsChangedEventHandler'),
|
||||
AccountDeletionRequestedEventHandler: Symbol.for('AccountDeletionRequestedEventHandler'),
|
||||
|
||||
@@ -138,7 +138,7 @@ export class WorkerContainerConfigLoader extends CommonContainerConfigLoader {
|
||||
.toDynamicValue((context: interfaces.Context) => {
|
||||
return new UserCredentialsChangedEventHandler(
|
||||
context.container.get(TYPES.ContactRepository),
|
||||
context.container.get(TYPES.GroupInviteRepository),
|
||||
context.container.get(TYPES.VaultInviteRepository),
|
||||
context.container.get(TYPES.Timer),
|
||||
)
|
||||
})
|
||||
|
||||
@@ -1,135 +0,0 @@
|
||||
import { GroupUser } from '../Domain/GroupUser/Model/GroupUser'
|
||||
import { GroupServiceInterface } from './../Domain/Group/Service/GroupServiceInterface'
|
||||
import { Request, Response } from 'express'
|
||||
import {
|
||||
BaseHttpController,
|
||||
controller,
|
||||
httpPost,
|
||||
results,
|
||||
httpDelete,
|
||||
httpPatch,
|
||||
httpGet,
|
||||
} from 'inversify-express-utils'
|
||||
import TYPES from '../Bootstrap/Types'
|
||||
import { inject } from 'inversify'
|
||||
import { ProjectorInterface } from '../Projection/ProjectorInterface'
|
||||
import { Group } from '../Domain/Group/Model/Group'
|
||||
import { GroupProjection } from '../Projection/GroupProjection'
|
||||
import { GroupUserProjection } from '../Projection/GroupUserProjection'
|
||||
import { GroupUserServiceInterface } from '../Domain/GroupUser/Service/GroupUserServiceInterface'
|
||||
import { CreateGroupFileReadValetToken } from '../Domain/UseCase/CreateGroupFileValetToken/CreateGroupFileReadValetToken'
|
||||
|
||||
@controller('/groups')
|
||||
export class GroupsController extends BaseHttpController {
|
||||
constructor(
|
||||
@inject(TYPES.GroupService) private groupService: GroupServiceInterface,
|
||||
@inject(TYPES.GroupUserService) private groupUserService: GroupUserServiceInterface,
|
||||
@inject(TYPES.GroupProjector) private groupProjector: ProjectorInterface<Group, GroupProjection>,
|
||||
@inject(TYPES.GroupUserProjector) private groupUserProjector: ProjectorInterface<GroupUser, GroupUserProjection>,
|
||||
@inject(TYPES.CreateGroupFileReadValetToken) private createGroupFileReadValetToken: CreateGroupFileReadValetToken,
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
||||
@httpGet('/', TYPES.AuthMiddleware)
|
||||
public async getGroups(_request: Request, response: Response): Promise<results.JsonResult> {
|
||||
const groups = await this.groupService.getGroups({
|
||||
userUuid: response.locals.user.uuid,
|
||||
})
|
||||
|
||||
return this.json({
|
||||
groups: groups.map((group) => this.groupProjector.projectFull(group)),
|
||||
})
|
||||
}
|
||||
|
||||
@httpPost('/:groupUuid/valet-tokens', TYPES.AuthMiddleware)
|
||||
public async getValetTokenForGroupFile(
|
||||
request: Request,
|
||||
response: Response,
|
||||
): Promise<results.NotFoundResult | results.JsonResult> {
|
||||
const valetTokenResult = await this.createGroupFileReadValetToken.execute({
|
||||
userUuid: response.locals.user.uuid,
|
||||
groupUuid: request.params.groupUuid,
|
||||
fileUuid: request.body.file_uuid,
|
||||
remoteIdentifier: request.body.remote_identifier,
|
||||
})
|
||||
|
||||
if (valetTokenResult.success === false) {
|
||||
return this.errorResponse(400, `Failed to create group valet token: ${valetTokenResult.reason}`)
|
||||
}
|
||||
|
||||
return this.json({
|
||||
valetToken: valetTokenResult.valetToken,
|
||||
})
|
||||
}
|
||||
|
||||
@httpPost('/', TYPES.AuthMiddleware)
|
||||
public async createGroup(request: Request, response: Response): Promise<results.NotFoundResult | results.JsonResult> {
|
||||
const result = await this.groupService.createGroup({
|
||||
userUuid: response.locals.user.uuid,
|
||||
groupUuid: request.body.group_uuid,
|
||||
groupKeyTimestamp: request.body.group_key_timestamp,
|
||||
specifiedItemsKeyUuid: request.body.specified_items_key_uuid,
|
||||
})
|
||||
|
||||
if (!result) {
|
||||
return this.errorResponse(400, 'Could not create group')
|
||||
}
|
||||
|
||||
const groupUser = await this.groupUserService.addGroupUser({
|
||||
groupUuid: result.uuid,
|
||||
userUuid: response.locals.user.uuid,
|
||||
permissions: 'admin',
|
||||
})
|
||||
|
||||
if (!groupUser) {
|
||||
return this.errorResponse(400, 'Could not add user to group')
|
||||
}
|
||||
|
||||
return this.json({
|
||||
group: this.groupProjector.projectFull(result),
|
||||
groupUser: this.groupUserProjector.projectFull(groupUser),
|
||||
})
|
||||
}
|
||||
|
||||
@httpPatch('/:groupUuid', TYPES.AuthMiddleware)
|
||||
public async updateGroup(request: Request, response: Response): Promise<results.NotFoundResult | results.JsonResult> {
|
||||
const result = await this.groupService.updateGroup({
|
||||
groupUuid: request.params.groupUuid,
|
||||
originatorUuid: response.locals.user.uuid,
|
||||
groupKeyTimestamp: request.body.group_key_timestamp,
|
||||
specifiedItemsKeyUuid: request.body.specified_items_key_uuid,
|
||||
})
|
||||
|
||||
if (!result) {
|
||||
return this.errorResponse(400, 'Could not update group')
|
||||
}
|
||||
|
||||
return this.json({
|
||||
group: this.groupProjector.projectFull(result),
|
||||
})
|
||||
}
|
||||
|
||||
@httpDelete('/:groupUuid', TYPES.AuthMiddleware)
|
||||
public async deleteGroup(request: Request, response: Response): Promise<results.NotFoundResult | results.JsonResult> {
|
||||
const result = await this.groupService.deleteGroup({
|
||||
groupUuid: request.params.groupUuid,
|
||||
originatorUuid: response.locals.user.uuid,
|
||||
})
|
||||
|
||||
if (!result) {
|
||||
return this.errorResponse(400, 'Could not delete group')
|
||||
}
|
||||
|
||||
return this.json({ success: true })
|
||||
}
|
||||
|
||||
private errorResponse(status: number, message?: string, tag?: string) {
|
||||
return this.json(
|
||||
{
|
||||
error: { message, tag },
|
||||
},
|
||||
status,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,7 @@ export class ItemsController extends BaseHttpController {
|
||||
computeIntegrityHash: request.body.compute_integrity === true,
|
||||
syncToken: request.body.sync_token,
|
||||
cursorToken: request.body.cursor_token,
|
||||
groupUuids: request.body.group_uuids,
|
||||
vaultUuids: request.body.vault_uuids,
|
||||
limit: request.body.limit,
|
||||
contentType: request.body.content_type,
|
||||
apiVersion: request.body.api ?? ApiVersion.v20161215,
|
||||
|
||||
+40
-40
@@ -10,29 +10,29 @@ import {
|
||||
} from 'inversify-express-utils'
|
||||
import TYPES from '../Bootstrap/Types'
|
||||
import { inject } from 'inversify'
|
||||
import { GroupInviteServiceInterface } from '../Domain/GroupInvite/Service/GroupInviteServiceInterface'
|
||||
import { GroupInviteProjector } from '../Projection/GroupInviteProjector'
|
||||
import { VaultInviteServiceInterface } from '../Domain/VaultInvite/Service/VaultInviteServiceInterface'
|
||||
import { VaultInviteProjector } from '../Projection/VaultInviteProjector'
|
||||
|
||||
@controller('/groups')
|
||||
export class GroupInvitesController extends BaseHttpController {
|
||||
@controller('/vaults')
|
||||
export class VaultInvitesController extends BaseHttpController {
|
||||
constructor(
|
||||
@inject(TYPES.GroupInviteService) private groupInviteService: GroupInviteServiceInterface,
|
||||
@inject(TYPES.GroupInviteProjector) private groupInviteProjector: GroupInviteProjector,
|
||||
@inject(TYPES.VaultInviteService) private vaultInviteService: VaultInviteServiceInterface,
|
||||
@inject(TYPES.VaultInviteProjector) private vaultInviteProjector: VaultInviteProjector,
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
||||
@httpPost('/:groupUuid/invites', TYPES.AuthMiddleware)
|
||||
public async createGroupInvite(
|
||||
@httpPost('/:vaultUuid/invites', TYPES.AuthMiddleware)
|
||||
public async createVaultInvite(
|
||||
request: Request,
|
||||
response: Response,
|
||||
): Promise<results.NotFoundResult | results.JsonResult> {
|
||||
const result = await this.groupInviteService.createInvite({
|
||||
const result = await this.vaultInviteService.createInvite({
|
||||
originatorUuid: response.locals.user.uuid,
|
||||
groupUuid: request.params.groupUuid,
|
||||
vaultUuid: request.params.vaultUuid,
|
||||
userUuid: request.body.invitee_uuid,
|
||||
inviterPublicKey: request.body.inviter_public_key,
|
||||
encryptedGroupData: request.body.encrypted_group_data,
|
||||
encryptedVaultData: request.body.encrypted_vault_data,
|
||||
inviteType: request.body.invite_type,
|
||||
permissions: request.body.permissions,
|
||||
})
|
||||
@@ -41,19 +41,19 @@ export class GroupInvitesController extends BaseHttpController {
|
||||
return this.errorResponse(400, 'Could not create invite')
|
||||
}
|
||||
|
||||
return this.json({ invite: this.groupInviteProjector.projectFull(result) })
|
||||
return this.json({ invite: this.vaultInviteProjector.projectFull(result) })
|
||||
}
|
||||
|
||||
@httpPatch('/:groupUuid/invites/:inviteUuid', TYPES.AuthMiddleware)
|
||||
public async updateGroupInvite(
|
||||
@httpPatch('/:vaultUuid/invites/:inviteUuid', TYPES.AuthMiddleware)
|
||||
public async updateVaultInvite(
|
||||
request: Request,
|
||||
response: Response,
|
||||
): Promise<results.NotFoundResult | results.JsonResult> {
|
||||
const result = await this.groupInviteService.updateInvite({
|
||||
const result = await this.vaultInviteService.updateInvite({
|
||||
originatorUuid: response.locals.user.uuid,
|
||||
inviteUuid: request.params.inviteUuid,
|
||||
inviterPublicKey: request.body.inviter_public_key,
|
||||
encryptedGroupData: request.body.encrypted_group_data,
|
||||
encryptedVaultData: request.body.encrypted_vault_data,
|
||||
permissions: request.body.permissions,
|
||||
})
|
||||
|
||||
@@ -61,15 +61,15 @@ export class GroupInvitesController extends BaseHttpController {
|
||||
return this.errorResponse(400, 'Could not update invite')
|
||||
}
|
||||
|
||||
return this.json({ invite: this.groupInviteProjector.projectFull(result) })
|
||||
return this.json({ invite: this.vaultInviteProjector.projectFull(result) })
|
||||
}
|
||||
|
||||
@httpPost('/:groupUuid/invites/:inviteUuid/accept', TYPES.AuthMiddleware)
|
||||
public async acceptGroupInvite(
|
||||
@httpPost('/:vaultUuid/invites/:inviteUuid/accept', TYPES.AuthMiddleware)
|
||||
public async acceptVaultInvite(
|
||||
request: Request,
|
||||
response: Response,
|
||||
): Promise<results.NotFoundResult | results.JsonResult> {
|
||||
const result = await this.groupInviteService.acceptInvite({
|
||||
const result = await this.vaultInviteService.acceptInvite({
|
||||
originatorUuid: response.locals.user.uuid,
|
||||
inviteUuid: request.params.inviteUuid,
|
||||
})
|
||||
@@ -81,12 +81,12 @@ export class GroupInvitesController extends BaseHttpController {
|
||||
return this.json({ success: true })
|
||||
}
|
||||
|
||||
@httpPost('/:groupUuid/invites/:inviteUuid/decline', TYPES.AuthMiddleware)
|
||||
public async declineGroupInvite(
|
||||
@httpPost('/:vaultUuid/invites/:inviteUuid/decline', TYPES.AuthMiddleware)
|
||||
public async declineVaultInvite(
|
||||
request: Request,
|
||||
response: Response,
|
||||
): Promise<results.NotFoundResult | results.JsonResult> {
|
||||
const result = await this.groupInviteService.declineInvite({
|
||||
const result = await this.vaultInviteService.declineInvite({
|
||||
originatorUuid: response.locals.user.uuid,
|
||||
inviteUuid: request.params.inviteUuid,
|
||||
})
|
||||
@@ -103,7 +103,7 @@ export class GroupInvitesController extends BaseHttpController {
|
||||
_request: Request,
|
||||
response: Response,
|
||||
): Promise<results.NotFoundResult | results.JsonResult> {
|
||||
await this.groupInviteService.deleteAllInboundInvites({
|
||||
await this.vaultInviteService.deleteAllInboundInvites({
|
||||
userUuid: response.locals.user.uuid,
|
||||
})
|
||||
|
||||
@@ -115,15 +115,15 @@ export class GroupInvitesController extends BaseHttpController {
|
||||
_request: Request,
|
||||
response: Response,
|
||||
): Promise<results.NotFoundResult | results.JsonResult> {
|
||||
const result = await this.groupInviteService.getOutboundInvitesForUser({
|
||||
const result = await this.vaultInviteService.getOutboundInvitesForUser({
|
||||
userUuid: response.locals.user.uuid,
|
||||
})
|
||||
|
||||
if (!result) {
|
||||
return this.errorResponse(400, 'Could not get group invites')
|
||||
return this.errorResponse(400, 'Could not get vault invites')
|
||||
}
|
||||
|
||||
const projected = result.map((invite) => this.groupInviteProjector.projectFull(invite))
|
||||
const projected = result.map((invite) => this.vaultInviteProjector.projectFull(invite))
|
||||
|
||||
return this.json({ invites: projected })
|
||||
}
|
||||
@@ -133,46 +133,46 @@ export class GroupInvitesController extends BaseHttpController {
|
||||
_request: Request,
|
||||
response: Response,
|
||||
): Promise<results.NotFoundResult | results.JsonResult> {
|
||||
const result = await this.groupInviteService.getInvitesForUser({
|
||||
const result = await this.vaultInviteService.getInvitesForUser({
|
||||
userUuid: response.locals.user.uuid,
|
||||
})
|
||||
|
||||
if (!result) {
|
||||
return this.errorResponse(400, 'Could not get group invites')
|
||||
return this.errorResponse(400, 'Could not get vault invites')
|
||||
}
|
||||
|
||||
const projected = result.map((invite) => this.groupInviteProjector.projectFull(invite))
|
||||
const projected = result.map((invite) => this.vaultInviteProjector.projectFull(invite))
|
||||
|
||||
return this.json({ invites: projected })
|
||||
}
|
||||
|
||||
@httpGet('/:groupUuid/invites', TYPES.AuthMiddleware)
|
||||
public async getGroupInvites(
|
||||
@httpGet('/:vaultUuid/invites', TYPES.AuthMiddleware)
|
||||
public async getVaultInvites(
|
||||
request: Request,
|
||||
response: Response,
|
||||
): Promise<results.NotFoundResult | results.JsonResult> {
|
||||
const result = await this.groupInviteService.getInvitesForGroup({
|
||||
const result = await this.vaultInviteService.getInvitesForVault({
|
||||
originatorUuid: response.locals.user.uuid,
|
||||
groupUuid: request.params.groupUuid,
|
||||
vaultUuid: request.params.vaultUuid,
|
||||
})
|
||||
|
||||
if (!result) {
|
||||
return this.errorResponse(400, 'Could not get group invites')
|
||||
return this.errorResponse(400, 'Could not get vault invites')
|
||||
}
|
||||
|
||||
const projected = result.map((invite) => this.groupInviteProjector.projectFull(invite))
|
||||
const projected = result.map((invite) => this.vaultInviteProjector.projectFull(invite))
|
||||
|
||||
return this.json({ invites: projected })
|
||||
}
|
||||
|
||||
@httpDelete('/:groupUuid/invites/:inviteUuid', TYPES.AuthMiddleware)
|
||||
public async deleteGroupInvite(
|
||||
@httpDelete('/:vaultUuid/invites/:inviteUuid', TYPES.AuthMiddleware)
|
||||
public async deleteVaultInvite(
|
||||
request: Request,
|
||||
response: Response,
|
||||
): Promise<results.NotFoundResult | results.JsonResult> {
|
||||
const result = await this.groupInviteService.deleteInvite({
|
||||
const result = await this.vaultInviteService.deleteInvite({
|
||||
originatorUuid: response.locals.user.uuid,
|
||||
groupUuid: request.params.groupUuid,
|
||||
vaultUuid: request.params.vaultUuid,
|
||||
inviteUuid: request.params.inviteUuid,
|
||||
})
|
||||
|
||||
+15
-15
@@ -2,48 +2,48 @@ import { Request, Response } from 'express'
|
||||
import { BaseHttpController, controller, results, httpDelete, httpGet } from 'inversify-express-utils'
|
||||
import TYPES from '../Bootstrap/Types'
|
||||
import { inject } from 'inversify'
|
||||
import { GroupUserServiceInterface } from '../Domain/GroupUser/Service/GroupUserServiceInterface'
|
||||
import { GroupUserProjector } from '../Projection/GroupUserProjector'
|
||||
import { VaultUserServiceInterface } from '../Domain/VaultUser/Service/VaultUserServiceInterface'
|
||||
import { VaultUserProjector } from '../Projection/VaultUserProjector'
|
||||
|
||||
@controller('/groups/:groupUuid/users')
|
||||
export class GroupUsersController extends BaseHttpController {
|
||||
@controller('/vaults/:vaultUuid/users')
|
||||
export class VaultUsersController extends BaseHttpController {
|
||||
constructor(
|
||||
@inject(TYPES.GroupUserService) private groupUserService: GroupUserServiceInterface,
|
||||
@inject(TYPES.GroupUserProjector) private groupUserProjector: GroupUserProjector,
|
||||
@inject(TYPES.VaultUserService) private vaultUserService: VaultUserServiceInterface,
|
||||
@inject(TYPES.VaultUserProjector) private vaultUserProjector: VaultUserProjector,
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
||||
@httpGet('/', TYPES.AuthMiddleware)
|
||||
public async getGroupUsers(
|
||||
public async getVaultUsers(
|
||||
request: Request,
|
||||
response: Response,
|
||||
): Promise<results.NotFoundResult | results.JsonResult> {
|
||||
const result = await this.groupUserService.getGroupUsersForGroup({
|
||||
const result = await this.vaultUserService.getVaultUsersForVault({
|
||||
originatorUuid: response.locals.user.uuid,
|
||||
groupUuid: request.params.groupUuid,
|
||||
vaultUuid: request.params.vaultUuid,
|
||||
})
|
||||
|
||||
if (!result) {
|
||||
return this.errorResponse(400, 'Could not get group users')
|
||||
return this.errorResponse(400, 'Could not get vault users')
|
||||
}
|
||||
|
||||
const { users, isAdmin } = result
|
||||
|
||||
const projected = users.map((groupUser) =>
|
||||
this.groupUserProjector.projectAsDisplayableUserForOtherGroupMembers(groupUser, isAdmin),
|
||||
const projected = users.map((vaultUser) =>
|
||||
this.vaultUserProjector.projectAsDisplayableUserForOtherVaultMembers(vaultUser, isAdmin),
|
||||
)
|
||||
|
||||
return this.json({ users: projected })
|
||||
}
|
||||
|
||||
@httpDelete('/:userUuid', TYPES.AuthMiddleware)
|
||||
public async deleteGroupUser(
|
||||
public async deleteVaultUser(
|
||||
request: Request,
|
||||
response: Response,
|
||||
): Promise<results.NotFoundResult | results.JsonResult> {
|
||||
const result = await this.groupUserService.deleteGroupUser({
|
||||
groupUuid: request.params.groupUuid,
|
||||
const result = await this.vaultUserService.deleteVaultUser({
|
||||
vaultUuid: request.params.vaultUuid,
|
||||
userUuid: request.params.userUuid,
|
||||
originatorUuid: response.locals.user.uuid,
|
||||
})
|
||||
@@ -0,0 +1,135 @@
|
||||
import { VaultUser } from '../Domain/VaultUser/Model/VaultUser'
|
||||
import { VaultServiceInterface } from '../Domain/Vault/Service/VaultServiceInterface'
|
||||
import { Request, Response } from 'express'
|
||||
import {
|
||||
BaseHttpController,
|
||||
controller,
|
||||
httpPost,
|
||||
results,
|
||||
httpDelete,
|
||||
httpPatch,
|
||||
httpGet,
|
||||
} from 'inversify-express-utils'
|
||||
import TYPES from '../Bootstrap/Types'
|
||||
import { inject } from 'inversify'
|
||||
import { ProjectorInterface } from '../Projection/ProjectorInterface'
|
||||
import { Vault } from '../Domain/Vault/Model/Vault'
|
||||
import { VaultProjection } from '../Projection/VaultProjection'
|
||||
import { VaultUserProjection } from '../Projection/VaultUserProjection'
|
||||
import { VaultUserServiceInterface } from '../Domain/VaultUser/Service/VaultUserServiceInterface'
|
||||
import { CreateVaultFileReadValetToken } from '../Domain/UseCase/CreateVaultFileValetToken/CreateVaultFileReadValetToken'
|
||||
|
||||
@controller('/vaults')
|
||||
export class VaultsController extends BaseHttpController {
|
||||
constructor(
|
||||
@inject(TYPES.VaultService) private vaultService: VaultServiceInterface,
|
||||
@inject(TYPES.VaultUserService) private vaultUserService: VaultUserServiceInterface,
|
||||
@inject(TYPES.VaultProjector) private vaultProjector: ProjectorInterface<Vault, VaultProjection>,
|
||||
@inject(TYPES.VaultUserProjector) private vaultUserProjector: ProjectorInterface<VaultUser, VaultUserProjection>,
|
||||
@inject(TYPES.CreateVaultFileReadValetToken) private createVaultFileReadValetToken: CreateVaultFileReadValetToken,
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
||||
@httpGet('/', TYPES.AuthMiddleware)
|
||||
public async getVaults(_request: Request, response: Response): Promise<results.JsonResult> {
|
||||
const vaults = await this.vaultService.getVaults({
|
||||
userUuid: response.locals.user.uuid,
|
||||
})
|
||||
|
||||
return this.json({
|
||||
vaults: vaults.map((vault) => this.vaultProjector.projectFull(vault)),
|
||||
})
|
||||
}
|
||||
|
||||
@httpPost('/:vaultUuid/valet-tokens', TYPES.AuthMiddleware)
|
||||
public async getValetTokenForVaultFile(
|
||||
request: Request,
|
||||
response: Response,
|
||||
): Promise<results.NotFoundResult | results.JsonResult> {
|
||||
const valetTokenResult = await this.createVaultFileReadValetToken.execute({
|
||||
userUuid: response.locals.user.uuid,
|
||||
vaultUuid: request.params.vaultUuid,
|
||||
fileUuid: request.body.file_uuid,
|
||||
remoteIdentifier: request.body.remote_identifier,
|
||||
})
|
||||
|
||||
if (valetTokenResult.success === false) {
|
||||
return this.errorResponse(400, `Failed to create vault valet token: ${valetTokenResult.reason}`)
|
||||
}
|
||||
|
||||
return this.json({
|
||||
valetToken: valetTokenResult.valetToken,
|
||||
})
|
||||
}
|
||||
|
||||
@httpPost('/', TYPES.AuthMiddleware)
|
||||
public async createVault(request: Request, response: Response): Promise<results.NotFoundResult | results.JsonResult> {
|
||||
const result = await this.vaultService.createVault({
|
||||
userUuid: response.locals.user.uuid,
|
||||
vaultUuid: request.body.vault_uuid,
|
||||
vaultKeyTimestamp: request.body.vault_key_timestamp,
|
||||
specifiedItemsKeyUuid: request.body.specified_items_key_uuid,
|
||||
})
|
||||
|
||||
if (!result) {
|
||||
return this.errorResponse(400, 'Could not create vault')
|
||||
}
|
||||
|
||||
const vaultUser = await this.vaultUserService.addVaultUser({
|
||||
vaultUuid: result.uuid,
|
||||
userUuid: response.locals.user.uuid,
|
||||
permissions: 'admin',
|
||||
})
|
||||
|
||||
if (!vaultUser) {
|
||||
return this.errorResponse(400, 'Could not add user to vault')
|
||||
}
|
||||
|
||||
return this.json({
|
||||
vault: this.vaultProjector.projectFull(result),
|
||||
vaultUser: this.vaultUserProjector.projectFull(vaultUser),
|
||||
})
|
||||
}
|
||||
|
||||
@httpPatch('/:vaultUuid', TYPES.AuthMiddleware)
|
||||
public async updateVault(request: Request, response: Response): Promise<results.NotFoundResult | results.JsonResult> {
|
||||
const result = await this.vaultService.updateVault({
|
||||
vaultUuid: request.params.vaultUuid,
|
||||
originatorUuid: response.locals.user.uuid,
|
||||
vaultKeyTimestamp: request.body.vault_key_timestamp,
|
||||
specifiedItemsKeyUuid: request.body.specified_items_key_uuid,
|
||||
})
|
||||
|
||||
if (!result) {
|
||||
return this.errorResponse(400, 'Could not update vault')
|
||||
}
|
||||
|
||||
return this.json({
|
||||
vault: this.vaultProjector.projectFull(result),
|
||||
})
|
||||
}
|
||||
|
||||
@httpDelete('/:vaultUuid', TYPES.AuthMiddleware)
|
||||
public async deleteVault(request: Request, response: Response): Promise<results.NotFoundResult | results.JsonResult> {
|
||||
const result = await this.vaultService.deleteVault({
|
||||
vaultUuid: request.params.vaultUuid,
|
||||
originatorUuid: response.locals.user.uuid,
|
||||
})
|
||||
|
||||
if (!result) {
|
||||
return this.errorResponse(400, 'Could not delete vault')
|
||||
}
|
||||
|
||||
return this.json({ success: true })
|
||||
}
|
||||
|
||||
private errorResponse(status: number, message?: string, tag?: string) {
|
||||
return this.json(
|
||||
{
|
||||
error: { message, tag },
|
||||
},
|
||||
status,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
import { GroupFactoryInterface } from './GroupFactoryInterface'
|
||||
import { GroupHash } from './GroupHash'
|
||||
import { Group } from '../Model/Group'
|
||||
|
||||
export class GroupFactory implements GroupFactoryInterface {
|
||||
constructor(private timer: TimerInterface) {}
|
||||
|
||||
create(dto: { userUuid: string; groupHash: GroupHash }): Group {
|
||||
const newGroup = new Group()
|
||||
newGroup.uuid = dto.groupHash.uuid
|
||||
newGroup.userUuid = dto.userUuid
|
||||
newGroup.specifiedItemsKeyUuid = dto.groupHash.specified_items_key_uuid
|
||||
newGroup.groupKeyTimestamp = dto.groupHash.group_key_timestamp
|
||||
|
||||
const now = this.timer.getTimestampInMicroseconds()
|
||||
newGroup.updatedAtTimestamp = now
|
||||
newGroup.createdAtTimestamp = now
|
||||
|
||||
if (dto.groupHash.created_at_timestamp) {
|
||||
newGroup.createdAtTimestamp = dto.groupHash.created_at_timestamp
|
||||
}
|
||||
|
||||
return newGroup
|
||||
}
|
||||
|
||||
createStub(dto: { userUuid: string; groupHash: GroupHash }): Group {
|
||||
const item = this.create(dto)
|
||||
return item
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
import { Group } from '../Model/Group'
|
||||
import { GroupHash } from './GroupHash'
|
||||
|
||||
export interface GroupFactoryInterface {
|
||||
create(dto: { userUuid: string; groupHash: GroupHash }): Group
|
||||
createStub(dto: { userUuid: string; groupHash: GroupHash }): Group
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
import { Group } from '../Model/Group'
|
||||
|
||||
export type GroupQuery = {
|
||||
userUuid: string
|
||||
}
|
||||
|
||||
export type UserGroupsQuery = {
|
||||
userUuid?: string
|
||||
groupUuids?: string[]
|
||||
lastSyncTime?: number
|
||||
}
|
||||
|
||||
export interface GroupsRepositoryInterface {
|
||||
findByUuid(uuid: string): Promise<Group | null>
|
||||
create(group: Group): Promise<Group>
|
||||
save(group: Group): Promise<Group>
|
||||
remove(group: Group): Promise<Group>
|
||||
findAll(query: UserGroupsQuery): Promise<Group[]>
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
import { Repository, SelectQueryBuilder } from 'typeorm'
|
||||
import { GroupsRepositoryInterface, UserGroupsQuery } from './GroupRepositoryInterface'
|
||||
import { Group } from '../Model/Group'
|
||||
|
||||
export class TypeORMGroupRepository implements GroupsRepositoryInterface {
|
||||
constructor(private ormRepository: Repository<Group>) {}
|
||||
|
||||
async create(group: Group): Promise<Group> {
|
||||
return this.ormRepository.save(group)
|
||||
}
|
||||
|
||||
async save(group: Group): Promise<Group> {
|
||||
return this.ormRepository.save(group)
|
||||
}
|
||||
|
||||
findByUuid(uuid: string): Promise<Group | null> {
|
||||
return this.ormRepository
|
||||
.createQueryBuilder('group')
|
||||
.where('group.uuid = :uuid', {
|
||||
uuid,
|
||||
})
|
||||
.getOne()
|
||||
}
|
||||
|
||||
async remove(group: Group): Promise<Group> {
|
||||
return this.ormRepository.remove(group)
|
||||
}
|
||||
|
||||
async findAll(query: UserGroupsQuery): Promise<Group[]> {
|
||||
return this.createFindAllQueryBuilder(query).getMany()
|
||||
}
|
||||
|
||||
private createFindAllQueryBuilder(query: UserGroupsQuery): SelectQueryBuilder<Group> {
|
||||
const queryBuilder = this.ormRepository.createQueryBuilder('group')
|
||||
|
||||
if (!query.userUuid && !query.groupUuids) {
|
||||
throw new Error('Either userUuid or groupUuids must be provided')
|
||||
}
|
||||
|
||||
if (query.userUuid) {
|
||||
queryBuilder.where('group.user_uuid = :userUuid', { userUuid: query.userUuid })
|
||||
}
|
||||
|
||||
if (query.groupUuids) {
|
||||
queryBuilder.where('group.uuid IN (:...groupUuids)', { groupUuids: query.groupUuids })
|
||||
}
|
||||
|
||||
if (query.lastSyncTime) {
|
||||
queryBuilder.andWhere('group.updated_at_timestamp > :lastSyncTime', {
|
||||
lastSyncTime: query.lastSyncTime,
|
||||
})
|
||||
}
|
||||
|
||||
return queryBuilder
|
||||
}
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
import { Group } from '../Model/Group'
|
||||
import { GroupsRepositoryInterface } from '../Repository/GroupRepositoryInterface'
|
||||
import { CreateGroupDTO, GroupServiceInterface, UpdateGroupDTO } from './GroupServiceInterface'
|
||||
import { GroupFactoryInterface } from '../Factory/GroupFactoryInterface'
|
||||
import { GroupUserServiceInterface } from '../../GroupUser/Service/GroupUserServiceInterface'
|
||||
import { GroupInviteServiceInterface } from '../../GroupInvite/Service/GroupInviteServiceInterface'
|
||||
|
||||
export class GroupService implements GroupServiceInterface {
|
||||
constructor(
|
||||
private groupRepository: GroupsRepositoryInterface,
|
||||
private groupFactory: GroupFactoryInterface,
|
||||
private groupUserService: GroupUserServiceInterface,
|
||||
private groupInviteService: GroupInviteServiceInterface,
|
||||
private timer: TimerInterface,
|
||||
) {}
|
||||
|
||||
async createGroup(dto: CreateGroupDTO): Promise<Group | null> {
|
||||
const existingGroup = await this.groupRepository.findByUuid(dto.groupUuid)
|
||||
if (existingGroup) {
|
||||
return null
|
||||
}
|
||||
|
||||
const timestamp = this.timer.getTimestampInMicroseconds()
|
||||
const group = this.groupFactory.create({
|
||||
userUuid: dto.userUuid,
|
||||
groupHash: {
|
||||
uuid: dto.groupUuid,
|
||||
user_uuid: dto.userUuid,
|
||||
specified_items_key_uuid: dto.specifiedItemsKeyUuid,
|
||||
group_key_timestamp: dto.groupKeyTimestamp,
|
||||
created_at_timestamp: timestamp,
|
||||
updated_at_timestamp: timestamp,
|
||||
},
|
||||
})
|
||||
|
||||
const savedGroup = await this.groupRepository.create(group)
|
||||
|
||||
return savedGroup
|
||||
}
|
||||
|
||||
async getGroup(dto: { groupUuid: string }): Promise<Group | null> {
|
||||
const group = await this.groupRepository.findByUuid(dto.groupUuid)
|
||||
|
||||
return group
|
||||
}
|
||||
|
||||
async getGroups(dto: { userUuid: string; lastSyncTime?: number }): Promise<Group[]> {
|
||||
const groupUsers = await this.groupUserService.getAllGroupUsersForUser({
|
||||
userUuid: dto.userUuid,
|
||||
})
|
||||
|
||||
const groupUuids = groupUsers.map((groupUser) => groupUser.groupUuid)
|
||||
|
||||
if (groupUuids.length === 0) {
|
||||
return []
|
||||
}
|
||||
|
||||
return this.groupRepository.findAll({ groupUuids, lastSyncTime: dto.lastSyncTime })
|
||||
}
|
||||
|
||||
async updateGroup(dto: UpdateGroupDTO): Promise<Group | null> {
|
||||
const group = await this.groupRepository.findByUuid(dto.groupUuid)
|
||||
if (!group || group.userUuid !== dto.originatorUuid) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (dto.groupKeyTimestamp < group.groupKeyTimestamp) {
|
||||
return null
|
||||
}
|
||||
|
||||
group.specifiedItemsKeyUuid = dto.specifiedItemsKeyUuid
|
||||
group.groupKeyTimestamp = dto.groupKeyTimestamp
|
||||
group.updatedAtTimestamp = this.timer.getTimestampInMicroseconds()
|
||||
|
||||
const savedGroup = await this.groupRepository.save(group)
|
||||
|
||||
return savedGroup
|
||||
}
|
||||
|
||||
async deleteGroup(dto: { groupUuid: string; originatorUuid: string }): Promise<boolean> {
|
||||
const group = await this.groupRepository.findByUuid(dto.groupUuid)
|
||||
if (!group || group.userUuid !== dto.originatorUuid) {
|
||||
return false
|
||||
}
|
||||
|
||||
await this.groupRepository.remove(group)
|
||||
await this.groupUserService.deleteAllGroupUsersForGroup({
|
||||
groupUuid: dto.groupUuid,
|
||||
originatorUuid: dto.originatorUuid,
|
||||
})
|
||||
await this.groupInviteService.deleteAllInvitesForGroup({
|
||||
groupUuid: dto.groupUuid,
|
||||
originatorUuid: dto.originatorUuid,
|
||||
})
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
import { Group } from '../Model/Group'
|
||||
|
||||
export type CreateGroupDTO = {
|
||||
userUuid: string
|
||||
groupUuid: string
|
||||
groupKeyTimestamp: number
|
||||
specifiedItemsKeyUuid: string
|
||||
}
|
||||
|
||||
export type UpdateGroupDTO = {
|
||||
originatorUuid: string
|
||||
groupUuid: string
|
||||
groupKeyTimestamp: number
|
||||
specifiedItemsKeyUuid: string
|
||||
}
|
||||
|
||||
export interface GroupServiceInterface {
|
||||
createGroup(dto: CreateGroupDTO): Promise<Group | null>
|
||||
|
||||
updateGroup(dto: UpdateGroupDTO): Promise<Group | null>
|
||||
|
||||
deleteGroup(dto: { groupUuid: string; originatorUuid: string }): Promise<boolean>
|
||||
|
||||
getGroup(dto: { groupUuid: string }): Promise<Group | null>
|
||||
|
||||
getGroups(dto: { userUuid: string; lastSyncTime?: number }): Promise<Group[]>
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
import { GroupInviteFactoryInterface } from './GroupInviteFactoryInterface'
|
||||
import { GroupInviteHash } from './GroupInviteHash'
|
||||
import { GroupInvite } from '../Model/GroupInvite'
|
||||
|
||||
export class GroupInviteFactory implements GroupInviteFactoryInterface {
|
||||
constructor(private timer: TimerInterface) {}
|
||||
|
||||
create(dto: GroupInviteHash): GroupInvite {
|
||||
const newGroupInvite = new GroupInvite()
|
||||
newGroupInvite.uuid = dto.uuid
|
||||
newGroupInvite.userUuid = dto.user_uuid
|
||||
newGroupInvite.groupUuid = dto.group_uuid
|
||||
newGroupInvite.inviterUuid = dto.inviter_uuid
|
||||
newGroupInvite.inviterPublicKey = dto.inviter_public_key
|
||||
newGroupInvite.encryptedGroupData = dto.encrypted_group_data
|
||||
newGroupInvite.inviteType = dto.invite_type
|
||||
newGroupInvite.permissions = dto.permissions
|
||||
|
||||
const now = this.timer.getTimestampInMicroseconds()
|
||||
newGroupInvite.updatedAtTimestamp = now
|
||||
newGroupInvite.createdAtTimestamp = now
|
||||
|
||||
if (dto.created_at_timestamp) {
|
||||
newGroupInvite.createdAtTimestamp = dto.created_at_timestamp
|
||||
}
|
||||
|
||||
return newGroupInvite
|
||||
}
|
||||
|
||||
createStub(dto: GroupInviteHash): GroupInvite {
|
||||
const item = this.create(dto)
|
||||
return item
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
import { GroupInvite } from '../Model/GroupInvite'
|
||||
import { GroupInviteHash } from './GroupInviteHash'
|
||||
|
||||
export interface GroupInviteFactoryInterface {
|
||||
create(dto: GroupInviteHash): GroupInvite
|
||||
createStub(dto: GroupInviteHash): GroupInvite
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
import { GroupUserPermission } from '../../GroupUser/Model/GroupUserPermission'
|
||||
import { GroupInviteType } from '../Model/GroupInviteType'
|
||||
|
||||
export type GroupInviteHash = {
|
||||
uuid: string
|
||||
user_uuid: string
|
||||
group_uuid: string
|
||||
inviter_uuid: string
|
||||
inviter_public_key: string
|
||||
encrypted_group_data: string
|
||||
invite_type: GroupInviteType
|
||||
permissions: GroupUserPermission
|
||||
created_at_timestamp?: number
|
||||
updated_at_timestamp?: number
|
||||
}
|
||||
-25
@@ -1,25 +0,0 @@
|
||||
import { GroupInvite } from '../Model/GroupInvite'
|
||||
|
||||
export type GroupInviteQuery = {
|
||||
userUuid: string
|
||||
}
|
||||
|
||||
export type GroupInviteFindAllForUserQuery = {
|
||||
userUuid?: string
|
||||
lastSyncTime?: number
|
||||
inviterUuid?: string
|
||||
}
|
||||
|
||||
export type GroupInviteFindAllForGroup = {
|
||||
groupUuid: string
|
||||
}
|
||||
|
||||
export interface GroupInviteRepositoryInterface {
|
||||
findByUuid(groupInviteUuid: string): Promise<GroupInvite | null>
|
||||
create(group: GroupInvite): Promise<GroupInvite>
|
||||
save(groupInvite: GroupInvite): Promise<GroupInvite>
|
||||
remove(group: GroupInvite): Promise<GroupInvite>
|
||||
findAll(query: GroupInviteFindAllForUserQuery): Promise<GroupInvite[]>
|
||||
findAllForGroup(query: GroupInviteFindAllForGroup): Promise<GroupInvite[]>
|
||||
findByUserUuidAndGroupUuid(userUuid: string, groupUuid: string): Promise<GroupInvite | null>
|
||||
}
|
||||
-71
@@ -1,71 +0,0 @@
|
||||
import { GroupInvite } from '../Model/GroupInvite'
|
||||
import { Repository, SelectQueryBuilder } from 'typeorm'
|
||||
import {
|
||||
GroupInviteFindAllForGroup,
|
||||
GroupInviteFindAllForUserQuery,
|
||||
GroupInviteRepositoryInterface,
|
||||
} from './GroupInviteRepositoryInterface'
|
||||
|
||||
export class TypeORMGroupInviteRepository implements GroupInviteRepositoryInterface {
|
||||
constructor(private ormRepository: Repository<GroupInvite>) {}
|
||||
|
||||
async create(groupInvite: GroupInvite): Promise<GroupInvite> {
|
||||
return this.ormRepository.save(groupInvite)
|
||||
}
|
||||
|
||||
async save(groupInvite: GroupInvite): Promise<GroupInvite> {
|
||||
return this.ormRepository.save(groupInvite)
|
||||
}
|
||||
|
||||
findByUuid(uuid: string): Promise<GroupInvite | null> {
|
||||
return this.ormRepository
|
||||
.createQueryBuilder('group_invite')
|
||||
.where('group_invite.uuid = :uuid', {
|
||||
uuid,
|
||||
})
|
||||
.getOne()
|
||||
}
|
||||
|
||||
findByUserUuidAndGroupUuid(userUuid: string, groupUuid: string): Promise<GroupInvite | null> {
|
||||
return this.ormRepository
|
||||
.createQueryBuilder('group_invite')
|
||||
.where('group_invite.user_uuid = :userUuid', { userUuid })
|
||||
.andWhere('group_invite.group_uuid = :groupUuid', { groupUuid })
|
||||
.getOne()
|
||||
}
|
||||
|
||||
async remove(groupInvite: GroupInvite): Promise<GroupInvite> {
|
||||
return this.ormRepository.remove(groupInvite)
|
||||
}
|
||||
|
||||
findAllForGroup(query: GroupInviteFindAllForGroup): Promise<GroupInvite[]> {
|
||||
return this.ormRepository
|
||||
.createQueryBuilder('group_invite')
|
||||
.where('group_invite.group_uuid = :groupUuid', { groupUuid: query.groupUuid })
|
||||
.getMany()
|
||||
}
|
||||
|
||||
async findAll(query: GroupInviteFindAllForUserQuery): Promise<GroupInvite[]> {
|
||||
return this.createFindAllQueryBuilder(query).getMany()
|
||||
}
|
||||
|
||||
private createFindAllQueryBuilder(query: GroupInviteFindAllForUserQuery): SelectQueryBuilder<GroupInvite> {
|
||||
const queryBuilder = this.ormRepository.createQueryBuilder('group_invite')
|
||||
|
||||
if (query.userUuid) {
|
||||
queryBuilder.where('group_invite.user_uuid = :userUuid', { userUuid: query.userUuid })
|
||||
} else if (query.inviterUuid) {
|
||||
queryBuilder.where('group_invite.inviter_uuid = :inviterUuid', {
|
||||
inviterUuid: query.inviterUuid,
|
||||
})
|
||||
}
|
||||
|
||||
if (query.lastSyncTime) {
|
||||
queryBuilder.andWhere('group_invite.updated_at_timestamp > :lastSyncTime', {
|
||||
lastSyncTime: query.lastSyncTime,
|
||||
})
|
||||
}
|
||||
|
||||
return queryBuilder
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
import { GroupInviteType } from '../Model/GroupInviteType'
|
||||
import { GroupUserPermission } from '../../GroupUser/Model/GroupUserPermission'
|
||||
|
||||
export type CreateInviteDTO = {
|
||||
originatorUuid: string
|
||||
groupUuid: string
|
||||
userUuid: string
|
||||
inviterPublicKey: string
|
||||
encryptedGroupData: string
|
||||
inviteType: GroupInviteType
|
||||
permissions: GroupUserPermission
|
||||
}
|
||||
@@ -1,172 +0,0 @@
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
import { GroupInvite } from '../Model/GroupInvite'
|
||||
import { GroupInviteFactoryInterface } from '../Factory/GroupInviteFactoryInterface'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { GroupInviteRepositoryInterface } from '../Repository/GroupInviteRepositoryInterface'
|
||||
import { GroupInviteServiceInterface } from './GroupInviteServiceInterface'
|
||||
import { GetUserGroupInvitesDTO } from './GetUserGroupInvitesDTO'
|
||||
import { GroupsRepositoryInterface } from '../../Group/Repository/GroupRepositoryInterface'
|
||||
import { GroupUserServiceInterface } from '../../GroupUser/Service/GroupUserServiceInterface'
|
||||
import { GroupInviteType } from '../Model/GroupInviteType'
|
||||
import { CreateInviteDTO } from './CreateInviteDTO'
|
||||
import { UpdateInviteDTO } from './UpdateInviteDTO'
|
||||
|
||||
export class GroupInviteService implements GroupInviteServiceInterface {
|
||||
constructor(
|
||||
private groupRepository: GroupsRepositoryInterface,
|
||||
private groupInviteRepository: GroupInviteRepositoryInterface,
|
||||
private groupInviteFactory: GroupInviteFactoryInterface,
|
||||
private groupUserService: GroupUserServiceInterface,
|
||||
private timer: TimerInterface,
|
||||
) {}
|
||||
|
||||
async createInvite(dto: CreateInviteDTO): Promise<GroupInvite | null> {
|
||||
const group = await this.groupRepository.findByUuid(dto.groupUuid)
|
||||
if (!group || group.userUuid !== dto.originatorUuid) {
|
||||
return null
|
||||
}
|
||||
|
||||
const timestamp = this.timer.getTimestampInMicroseconds()
|
||||
|
||||
const groupInvite = this.groupInviteFactory.create({
|
||||
uuid: uuidv4(),
|
||||
user_uuid: dto.userUuid,
|
||||
group_uuid: dto.groupUuid,
|
||||
inviter_uuid: dto.originatorUuid,
|
||||
inviter_public_key: dto.inviterPublicKey,
|
||||
encrypted_group_data: dto.encryptedGroupData,
|
||||
invite_type: dto.inviteType,
|
||||
permissions: dto.permissions,
|
||||
created_at_timestamp: timestamp,
|
||||
updated_at_timestamp: timestamp,
|
||||
})
|
||||
|
||||
return this.groupInviteRepository.create(groupInvite)
|
||||
}
|
||||
|
||||
async updateInvite(dto: UpdateInviteDTO): Promise<GroupInvite | null> {
|
||||
const groupInvite = await this.groupInviteRepository.findByUuid(dto.inviteUuid)
|
||||
if (!groupInvite || groupInvite.inviterUuid !== dto.originatorUuid) {
|
||||
return null
|
||||
}
|
||||
|
||||
groupInvite.inviterPublicKey = dto.inviterPublicKey
|
||||
groupInvite.encryptedGroupData = dto.encryptedGroupData
|
||||
if (dto.permissions) {
|
||||
groupInvite.permissions = dto.permissions
|
||||
}
|
||||
groupInvite.updatedAtTimestamp = this.timer.getTimestampInMicroseconds()
|
||||
|
||||
return this.groupInviteRepository.save(groupInvite)
|
||||
}
|
||||
|
||||
getInvitesForUser(dto: GetUserGroupInvitesDTO): Promise<GroupInvite[]> {
|
||||
return this.groupInviteRepository.findAll(dto)
|
||||
}
|
||||
|
||||
getOutboundInvitesForUser(dto: { userUuid: string }): Promise<GroupInvite[]> {
|
||||
return this.groupInviteRepository.findAll({
|
||||
inviterUuid: dto.userUuid,
|
||||
})
|
||||
}
|
||||
|
||||
async deleteAllInboundInvites(dto: { userUuid: string }): Promise<void> {
|
||||
const inboundInvites = await this.groupInviteRepository.findAll({
|
||||
userUuid: dto.userUuid,
|
||||
})
|
||||
|
||||
for (const invite of inboundInvites) {
|
||||
await this.groupInviteRepository.remove(invite)
|
||||
}
|
||||
}
|
||||
|
||||
async getInvitesForGroup(dto: { groupUuid: string; originatorUuid: string }): Promise<GroupInvite[] | undefined> {
|
||||
const group = await this.groupRepository.findByUuid(dto.groupUuid)
|
||||
if (!group) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const isUserGroupAdmin = group && group.userUuid === dto.originatorUuid
|
||||
if (!isUserGroupAdmin) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const users = await this.groupInviteRepository.findAllForGroup({ groupUuid: dto.groupUuid })
|
||||
|
||||
return users
|
||||
}
|
||||
|
||||
async acceptInvite(dto: { originatorUuid: string; inviteUuid: string }): Promise<boolean> {
|
||||
const invite = await this.groupInviteRepository.findByUuid(dto.inviteUuid)
|
||||
if (!invite) {
|
||||
return false
|
||||
}
|
||||
|
||||
const isAuthorized = invite.userUuid === dto.originatorUuid
|
||||
if (!isAuthorized) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (invite.inviteType === GroupInviteType.Join) {
|
||||
const addedUser = await this.groupUserService.addGroupUser({
|
||||
userUuid: dto.originatorUuid,
|
||||
groupUuid: invite.groupUuid,
|
||||
permissions: invite.permissions,
|
||||
})
|
||||
|
||||
if (!addedUser) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
await this.groupInviteRepository.remove(invite)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
async declineInvite(dto: { originatorUuid: string; inviteUuid: string }): Promise<boolean> {
|
||||
const invite = await this.groupInviteRepository.findByUuid(dto.inviteUuid)
|
||||
if (!invite) {
|
||||
return false
|
||||
}
|
||||
|
||||
const isAuthorized = invite.userUuid === dto.originatorUuid
|
||||
if (!isAuthorized) {
|
||||
return false
|
||||
}
|
||||
|
||||
await this.groupInviteRepository.remove(invite)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
async deleteInvite(dto: { originatorUuid: string; inviteUuid: string }): Promise<boolean> {
|
||||
const invite = await this.groupInviteRepository.findByUuid(dto.inviteUuid)
|
||||
if (!invite) {
|
||||
return false
|
||||
}
|
||||
|
||||
const isAuthorized = invite.inviterUuid === dto.originatorUuid || invite.userUuid === dto.originatorUuid
|
||||
if (!isAuthorized) {
|
||||
return false
|
||||
}
|
||||
|
||||
await this.groupInviteRepository.remove(invite)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
async deleteAllInvitesForGroup(dto: { originatorUuid: string; groupUuid: string }): Promise<boolean> {
|
||||
const group = await this.groupRepository.findByUuid(dto.groupUuid)
|
||||
if (!group || group.userUuid !== dto.originatorUuid) {
|
||||
return false
|
||||
}
|
||||
|
||||
const groupInvites = await this.groupInviteRepository.findAllForGroup({ groupUuid: dto.groupUuid })
|
||||
for (const groupInvite of groupInvites) {
|
||||
await this.groupInviteRepository.remove(groupInvite)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
import { GroupInvite } from '../Model/GroupInvite'
|
||||
import { CreateInviteDTO } from './CreateInviteDTO'
|
||||
import { GetUserGroupInvitesDTO } from './GetUserGroupInvitesDTO'
|
||||
import { UpdateInviteDTO } from './UpdateInviteDTO'
|
||||
|
||||
export interface GroupInviteServiceInterface {
|
||||
createInvite(dto: CreateInviteDTO): Promise<GroupInvite | null>
|
||||
|
||||
updateInvite(dto: UpdateInviteDTO): Promise<GroupInvite | null>
|
||||
|
||||
getInvitesForUser(dto: GetUserGroupInvitesDTO): Promise<GroupInvite[]>
|
||||
|
||||
getInvitesForGroup(dto: { groupUuid: string; originatorUuid: string }): Promise<GroupInvite[] | undefined>
|
||||
|
||||
getOutboundInvitesForUser(dto: { userUuid: string }): Promise<GroupInvite[]>
|
||||
|
||||
acceptInvite(dto: { originatorUuid: string; inviteUuid: string }): Promise<boolean>
|
||||
|
||||
declineInvite(dto: { originatorUuid: string; inviteUuid: string }): Promise<boolean>
|
||||
|
||||
deleteInvite(dto: { originatorUuid: string; groupUuid: string; inviteUuid: string }): Promise<boolean>
|
||||
|
||||
deleteAllInvitesForGroup(dto: { originatorUuid: string; groupUuid: string }): Promise<boolean>
|
||||
|
||||
deleteAllInboundInvites(dto: { userUuid: string }): Promise<void>
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
import { GroupUserPermission } from '../../GroupUser/Model/GroupUserPermission'
|
||||
|
||||
export type UpdateInviteDTO = {
|
||||
originatorUuid: string
|
||||
inviteUuid: string
|
||||
inviterPublicKey: string
|
||||
encryptedGroupData: string
|
||||
permissions?: GroupUserPermission
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
import { GroupUserFactoryInterface } from './GroupUserFactoryInterface'
|
||||
import { GroupUserHash } from './GroupUserHash'
|
||||
import { GroupUser } from '../Model/GroupUser'
|
||||
|
||||
export class GroupUserFactory implements GroupUserFactoryInterface {
|
||||
constructor(private timer: TimerInterface) {}
|
||||
|
||||
create(dto: GroupUserHash): GroupUser {
|
||||
const newGroupUser = new GroupUser()
|
||||
newGroupUser.uuid = dto.uuid
|
||||
newGroupUser.userUuid = dto.user_uuid
|
||||
newGroupUser.groupUuid = dto.group_uuid
|
||||
newGroupUser.permissions = dto.permissions
|
||||
|
||||
const now = this.timer.getTimestampInMicroseconds()
|
||||
newGroupUser.updatedAtTimestamp = now
|
||||
newGroupUser.createdAtTimestamp = now
|
||||
|
||||
if (dto.created_at_timestamp) {
|
||||
newGroupUser.createdAtTimestamp = dto.created_at_timestamp
|
||||
}
|
||||
|
||||
return newGroupUser
|
||||
}
|
||||
|
||||
createStub(dto: GroupUserHash): GroupUser {
|
||||
const item = this.create(dto)
|
||||
return item
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
import { GroupUser } from '../Model/GroupUser'
|
||||
import { GroupUserHash } from './GroupUserHash'
|
||||
|
||||
export interface GroupUserFactoryInterface {
|
||||
create(dto: GroupUserHash): GroupUser
|
||||
createStub(dto: GroupUserHash): GroupUser
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
import { GroupUserPermission } from '../Model/GroupUserPermission'
|
||||
|
||||
export type GroupUserHash = {
|
||||
uuid: string
|
||||
user_uuid: string
|
||||
group_uuid: string
|
||||
permissions: GroupUserPermission
|
||||
created_at_timestamp?: number
|
||||
updated_at_timestamp?: number
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export type GroupUserPermission = 'read' | 'write' | 'admin'
|
||||
-24
@@ -1,24 +0,0 @@
|
||||
import { GroupUser } from '../Model/GroupUser'
|
||||
|
||||
export type GroupUserQuery = {
|
||||
userUuid: string
|
||||
}
|
||||
|
||||
export type GroupUserFindAllForUserQuery = {
|
||||
userUuid: string
|
||||
lastSyncTime?: number
|
||||
}
|
||||
|
||||
export type GroupUserFindAllForGroup = {
|
||||
groupUuid: string
|
||||
}
|
||||
|
||||
export interface GroupUserRepositoryInterface {
|
||||
findByUuid(groupUserUuid: string): Promise<GroupUser | null>
|
||||
create(group: GroupUser): Promise<GroupUser>
|
||||
save(groupUser: GroupUser): Promise<GroupUser>
|
||||
remove(group: GroupUser): Promise<GroupUser>
|
||||
findAllForUser(query: GroupUserFindAllForUserQuery): Promise<GroupUser[]>
|
||||
findAllForGroup(query: GroupUserFindAllForGroup): Promise<GroupUser[]>
|
||||
findByUserUuidAndGroupUuid(userUuid: string, groupUuid: string): Promise<GroupUser | null>
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
import { GroupUser } from '../Model/GroupUser'
|
||||
import { Repository, SelectQueryBuilder } from 'typeorm'
|
||||
import {
|
||||
GroupUserFindAllForGroup,
|
||||
GroupUserFindAllForUserQuery,
|
||||
GroupUserRepositoryInterface,
|
||||
} from './GroupUserRepositoryInterface'
|
||||
|
||||
export class TypeORMGroupUserRepository implements GroupUserRepositoryInterface {
|
||||
constructor(private ormRepository: Repository<GroupUser>) {}
|
||||
|
||||
async create(groupUser: GroupUser): Promise<GroupUser> {
|
||||
return this.ormRepository.save(groupUser)
|
||||
}
|
||||
|
||||
async save(groupUser: GroupUser): Promise<GroupUser> {
|
||||
return this.ormRepository.save(groupUser)
|
||||
}
|
||||
|
||||
findByUuid(uuid: string): Promise<GroupUser | null> {
|
||||
return this.ormRepository
|
||||
.createQueryBuilder('group_user')
|
||||
.where('group_user.uuid = :uuid', {
|
||||
uuid,
|
||||
})
|
||||
.getOne()
|
||||
}
|
||||
|
||||
findByUserUuidAndGroupUuid(userUuid: string, groupUuid: string): Promise<GroupUser | null> {
|
||||
return this.ormRepository
|
||||
.createQueryBuilder('group_user')
|
||||
.where('group_user.user_uuid = :userUuid', { userUuid })
|
||||
.andWhere('group_user.group_uuid = :groupUuid', { groupUuid })
|
||||
.getOne()
|
||||
}
|
||||
|
||||
async remove(groupUser: GroupUser): Promise<GroupUser> {
|
||||
return this.ormRepository.remove(groupUser)
|
||||
}
|
||||
|
||||
findAllForGroup(query: GroupUserFindAllForGroup): Promise<GroupUser[]> {
|
||||
return this.ormRepository
|
||||
.createQueryBuilder('group_user')
|
||||
.where('group_user.group_uuid = :groupUuid', { groupUuid: query.groupUuid })
|
||||
.getMany()
|
||||
}
|
||||
|
||||
async findAllForUser(query: GroupUserFindAllForUserQuery): Promise<GroupUser[]> {
|
||||
return this.createFindAllQueryBuilder(query).getMany()
|
||||
}
|
||||
|
||||
private createFindAllQueryBuilder(query: GroupUserFindAllForUserQuery): SelectQueryBuilder<GroupUser> {
|
||||
const queryBuilder = this.ormRepository.createQueryBuilder('group_user')
|
||||
|
||||
queryBuilder.where('group_user.user_uuid = :userUuid', { userUuid: query.userUuid })
|
||||
|
||||
if (query.lastSyncTime) {
|
||||
queryBuilder.andWhere('group_user.updated_at_timestamp > :lastSyncTime', {
|
||||
lastSyncTime: query.lastSyncTime,
|
||||
})
|
||||
}
|
||||
|
||||
return queryBuilder
|
||||
}
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
import { GroupUser } from '../Model/GroupUser'
|
||||
import { GroupUserFactoryInterface } from '../Factory/GroupUserFactoryInterface'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { GroupUserRepositoryInterface } from '../Repository/GroupUserRepositoryInterface'
|
||||
import { GroupUserServiceInterface } from './GroupUserServiceInterface'
|
||||
import { GetGroupUsersDTO } from './GetGroupUsersDTO'
|
||||
import { GroupsRepositoryInterface } from '../../Group/Repository/GroupRepositoryInterface'
|
||||
import { GroupUserPermission } from '../Model/GroupUserPermission'
|
||||
|
||||
export class GroupUserService implements GroupUserServiceInterface {
|
||||
constructor(
|
||||
private groupRepository: GroupsRepositoryInterface,
|
||||
private groupUserRepository: GroupUserRepositoryInterface,
|
||||
private groupUserFactory: GroupUserFactoryInterface,
|
||||
private timer: TimerInterface,
|
||||
) {}
|
||||
|
||||
async addGroupUser(dto: {
|
||||
groupUuid: string
|
||||
userUuid: string
|
||||
permissions: GroupUserPermission
|
||||
}): Promise<GroupUser | null> {
|
||||
const group = await this.groupRepository.findByUuid(dto.groupUuid)
|
||||
if (!group) {
|
||||
return null
|
||||
}
|
||||
|
||||
const timestamp = this.timer.getTimestampInMicroseconds()
|
||||
const groupUser = this.groupUserFactory.create({
|
||||
uuid: uuidv4(),
|
||||
user_uuid: dto.userUuid,
|
||||
group_uuid: dto.groupUuid,
|
||||
permissions: dto.permissions,
|
||||
created_at_timestamp: timestamp,
|
||||
updated_at_timestamp: timestamp,
|
||||
})
|
||||
|
||||
return this.groupUserRepository.create(groupUser)
|
||||
}
|
||||
|
||||
getAllGroupUsersForUser(dto: GetGroupUsersDTO): Promise<GroupUser[]> {
|
||||
return this.groupUserRepository.findAllForUser(dto)
|
||||
}
|
||||
|
||||
getUserForGroup(dto: { userUuid: string; groupUuid: string }): Promise<GroupUser | null> {
|
||||
return this.groupUserRepository.findByUserUuidAndGroupUuid(dto.userUuid, dto.groupUuid)
|
||||
}
|
||||
|
||||
async getGroupUsersForGroup(dto: {
|
||||
groupUuid: string
|
||||
originatorUuid: string
|
||||
}): Promise<{ users: GroupUser[]; isAdmin: boolean } | undefined> {
|
||||
const group = await this.groupRepository.findByUuid(dto.groupUuid)
|
||||
if (!group) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const isUserGroupAdmin = group && group.userUuid === dto.originatorUuid
|
||||
const doesUserBelongToGroup = await this.groupUserRepository.findByUserUuidAndGroupUuid(
|
||||
dto.originatorUuid,
|
||||
dto.groupUuid,
|
||||
)
|
||||
|
||||
if (!isUserGroupAdmin && !doesUserBelongToGroup) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const users = await this.groupUserRepository.findAllForGroup({ groupUuid: dto.groupUuid })
|
||||
|
||||
return {
|
||||
users,
|
||||
isAdmin: isUserGroupAdmin,
|
||||
}
|
||||
}
|
||||
|
||||
async deleteGroupUser(dto: { originatorUuid: string; groupUuid: string; userUuid: string }): Promise<boolean> {
|
||||
const group = await this.groupRepository.findByUuid(dto.groupUuid)
|
||||
if (!group) {
|
||||
return false
|
||||
}
|
||||
|
||||
const isAuthorized = group.userUuid === dto.originatorUuid || dto.userUuid === dto.originatorUuid
|
||||
if (!isAuthorized) {
|
||||
return false
|
||||
}
|
||||
|
||||
const groupUser = await this.groupUserRepository.findByUserUuidAndGroupUuid(dto.userUuid, dto.groupUuid)
|
||||
if (!groupUser) {
|
||||
return false
|
||||
}
|
||||
|
||||
await this.groupUserRepository.remove(groupUser)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
async deleteAllGroupUsersForGroup(dto: { originatorUuid: string; groupUuid: string }): Promise<boolean> {
|
||||
const group = await this.groupRepository.findByUuid(dto.groupUuid)
|
||||
if (!group || group.userUuid !== dto.originatorUuid) {
|
||||
return false
|
||||
}
|
||||
|
||||
const groupUsers = await this.groupUserRepository.findAllForGroup({ groupUuid: dto.groupUuid })
|
||||
for (const groupUser of groupUsers) {
|
||||
await this.groupUserRepository.remove(groupUser)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
import { GroupUser } from '../Model/GroupUser'
|
||||
import { GroupUserPermission } from '../Model/GroupUserPermission'
|
||||
import { GetGroupUsersDTO } from './GetGroupUsersDTO'
|
||||
|
||||
export interface GroupUserServiceInterface {
|
||||
addGroupUser(dto: {
|
||||
groupUuid: string
|
||||
userUuid: string
|
||||
permissions: GroupUserPermission
|
||||
}): Promise<GroupUser | null>
|
||||
|
||||
getAllGroupUsersForUser(dto: GetGroupUsersDTO): Promise<GroupUser[]>
|
||||
|
||||
getUserForGroup(dto: { userUuid: string; groupUuid: string }): Promise<GroupUser | null>
|
||||
|
||||
getGroupUsersForGroup(dto: {
|
||||
groupUuid: string
|
||||
originatorUuid: string
|
||||
}): Promise<{ users: GroupUser[]; isAdmin: boolean } | undefined>
|
||||
|
||||
deleteGroupUser(dto: { originatorUuid: string; groupUuid: string; userUuid: string }): Promise<boolean>
|
||||
|
||||
deleteAllGroupUsersForGroup(dto: { originatorUuid: string; groupUuid: string }): Promise<boolean>
|
||||
}
|
||||
@@ -5,18 +5,18 @@ import {
|
||||
} from '@standardnotes/domain-events'
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
import { ContactsRepositoryInterface } from '../Contact/Repository/ContactRepositoryInterface'
|
||||
import { GroupInviteRepositoryInterface } from '../GroupInvite/Repository/GroupInviteRepositoryInterface'
|
||||
import { VaultInviteRepositoryInterface } from '../VaultInvite/Repository/VaultInviteRepositoryInterface'
|
||||
|
||||
export class UserCredentialsChangedEventHandler implements DomainEventHandlerInterface {
|
||||
constructor(
|
||||
private contactRepository: ContactsRepositoryInterface,
|
||||
private groupInviteRepository: GroupInviteRepositoryInterface,
|
||||
private vaultInviteRepository: VaultInviteRepositoryInterface,
|
||||
private timer: TimerInterface,
|
||||
) {}
|
||||
|
||||
async handle(event: UserCredentialsChangedEvent): Promise<void> {
|
||||
await this.updatePublicKeyOfAllContacts(event.payload)
|
||||
await this.nullifyPendingInboundGroupInvites(event.payload)
|
||||
await this.nullifyPendingInboundVaultInvites(event.payload)
|
||||
}
|
||||
|
||||
private async updatePublicKeyOfAllContacts(payload: UserCredentialsChangedEventPayload): Promise<void> {
|
||||
@@ -31,13 +31,13 @@ export class UserCredentialsChangedEventHandler implements DomainEventHandlerInt
|
||||
}
|
||||
}
|
||||
|
||||
private async nullifyPendingInboundGroupInvites(payload: UserCredentialsChangedEventPayload): Promise<void> {
|
||||
const inboundInvites = await this.groupInviteRepository.findAll({
|
||||
private async nullifyPendingInboundVaultInvites(payload: UserCredentialsChangedEventPayload): Promise<void> {
|
||||
const inboundInvites = await this.vaultInviteRepository.findAll({
|
||||
userUuid: payload.userUuid,
|
||||
})
|
||||
|
||||
for (const invite of inboundInvites) {
|
||||
await this.groupInviteRepository.remove(invite)
|
||||
await this.vaultInviteRepository.remove(invite)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,5 +4,5 @@ export type GetItemsDTO = {
|
||||
cursorToken?: string | null
|
||||
limit?: number
|
||||
contentType?: string
|
||||
groupUuids?: string[] | null
|
||||
vaultUuids?: string[] | null
|
||||
}
|
||||
|
||||
@@ -15,11 +15,11 @@ export class Item {
|
||||
|
||||
@Column({
|
||||
type: 'varchar',
|
||||
name: 'group_uuid',
|
||||
name: 'vault_uuid',
|
||||
length: 255,
|
||||
nullable: true,
|
||||
})
|
||||
declare groupUuid: string | null
|
||||
declare vaultUuid: string | null
|
||||
|
||||
@Column({
|
||||
type: 'varchar',
|
||||
@@ -77,9 +77,11 @@ export class Item {
|
||||
@Column({
|
||||
name: 'user_uuid',
|
||||
length: 36,
|
||||
type: 'varchar',
|
||||
nullable: true,
|
||||
})
|
||||
@Index('index_items_on_user_uuid')
|
||||
declare userUuid: string
|
||||
declare userUuid: string | null
|
||||
|
||||
@Column({
|
||||
name: 'last_edited_by_uuid',
|
||||
|
||||
@@ -40,8 +40,8 @@ export class ItemFactory implements ItemFactoryInterface {
|
||||
if (dto.itemHash.last_edited_by_uuid) {
|
||||
newItem.lastEditedByUuid = dto.itemHash.last_edited_by_uuid
|
||||
}
|
||||
if (dto.itemHash.group_uuid) {
|
||||
newItem.groupUuid = dto.itemHash.group_uuid
|
||||
if (dto.itemHash.vault_uuid) {
|
||||
newItem.vaultUuid = dto.itemHash.vault_uuid
|
||||
}
|
||||
if (dto.itemHash.content_type) {
|
||||
newItem.contentType = dto.itemHash.content_type
|
||||
|
||||
@@ -10,7 +10,7 @@ export type ItemHash = {
|
||||
auth_hash?: string
|
||||
enc_item_key?: string
|
||||
items_key_id?: string
|
||||
group_uuid?: string
|
||||
vault_uuid?: string
|
||||
last_edited_by_uuid?: string | null
|
||||
created_at?: string
|
||||
created_at_timestamp?: number
|
||||
|
||||
@@ -6,8 +6,8 @@ export type ItemQuery = {
|
||||
lastSyncTime?: number
|
||||
syncTimeComparison?: '>' | '>='
|
||||
contentType?: string | string[]
|
||||
includeGroupUuids?: string[]
|
||||
exclusiveGroupUuids?: string[] | null
|
||||
includeVaultUuids?: string[]
|
||||
exclusiveVaultUuids?: string[] | null
|
||||
deleted?: boolean
|
||||
offset?: number
|
||||
limit?: number
|
||||
|
||||
@@ -20,7 +20,7 @@ import { ConflictType } from '@standardnotes/responses'
|
||||
import { ItemTransferCalculatorInterface } from './ItemTransferCalculatorInterface'
|
||||
import { ProjectorInterface } from '../../Projection/ProjectorInterface'
|
||||
import { ItemProjection } from '../../Projection/ItemProjection'
|
||||
import { GroupUserRepositoryInterface } from '../GroupUser/Repository/GroupUserRepositoryInterface'
|
||||
import { VaultUserRepositoryInterface } from '../VaultUser/Repository/VaultUserRepositoryInterface'
|
||||
|
||||
export class ItemService implements ItemServiceInterface {
|
||||
private readonly DEFAULT_ITEMS_LIMIT = 150
|
||||
@@ -38,7 +38,7 @@ export class ItemService implements ItemServiceInterface {
|
||||
private timer: TimerInterface,
|
||||
private itemProjector: ProjectorInterface<Item, ItemProjection>,
|
||||
private maxItemsSyncLimit: number,
|
||||
private groupUsersRepository: GroupUserRepositoryInterface,
|
||||
private vaultUsersRepository: VaultUserRepositoryInterface,
|
||||
private logger: Logger,
|
||||
) {}
|
||||
|
||||
@@ -48,16 +48,16 @@ export class ItemService implements ItemServiceInterface {
|
||||
const limit = dto.limit === undefined || dto.limit < 1 ? this.DEFAULT_ITEMS_LIMIT : dto.limit
|
||||
const upperBoundLimit = limit < this.maxItemsSyncLimit ? limit : this.maxItemsSyncLimit
|
||||
|
||||
const groupUsers = await this.groupUsersRepository.findAllForUser({ userUuid: dto.userUuid })
|
||||
const userGroupUuids = groupUsers.map((groupUser) => groupUser.groupUuid)
|
||||
const exclusiveGroupUuids = dto.groupUuids
|
||||
? dto.groupUuids.filter((groupUuid) => userGroupUuids.includes(groupUuid))
|
||||
const vaultUsers = await this.vaultUsersRepository.findAllForUser({ userUuid: dto.userUuid })
|
||||
const userVaultUuids = vaultUsers.map((vaultUser) => vaultUser.vaultUuid)
|
||||
const exclusiveVaultUuids = dto.vaultUuids
|
||||
? dto.vaultUuids.filter((vaultUuid) => userVaultUuids.includes(vaultUuid))
|
||||
: undefined
|
||||
|
||||
const itemQuery: ItemQuery = {
|
||||
userUuid: dto.userUuid,
|
||||
includeGroupUuids: !dto.groupUuids ? userGroupUuids : undefined,
|
||||
exclusiveGroupUuids: exclusiveGroupUuids,
|
||||
includeVaultUuids: !dto.vaultUuids ? userVaultUuids : undefined,
|
||||
exclusiveVaultUuids: exclusiveVaultUuids,
|
||||
lastSyncTime,
|
||||
syncTimeComparison,
|
||||
contentType: dto.contentType,
|
||||
@@ -199,6 +199,28 @@ export class ItemService implements ItemServiceInterface {
|
||||
).toString('base64')
|
||||
}
|
||||
|
||||
async addItemToVault(dto: {
|
||||
userUuid: string
|
||||
itemUuid: string
|
||||
vaultUuid: string
|
||||
sessionUuid: string | null
|
||||
}): Promise<Item | null> {
|
||||
const item = await this.itemRepository.findByUuid(dto.itemUuid)
|
||||
if (!item || item.userUuid !== dto.userUuid) {
|
||||
return null
|
||||
}
|
||||
|
||||
// item.userUuid = null
|
||||
item.vaultUuid = dto.vaultUuid
|
||||
item.updatedWithSession = dto.sessionUuid
|
||||
item.lastEditedByUuid = dto.userUuid
|
||||
const updatedAt = this.timer.getTimestampInMicroseconds()
|
||||
item.updatedAtTimestamp = updatedAt
|
||||
item.updatedAt = this.timer.convertMicrosecondsToDate(updatedAt)
|
||||
const savedItem = await this.itemRepository.save(item)
|
||||
return savedItem
|
||||
}
|
||||
|
||||
private async updateExistingItem(dto: {
|
||||
existingItem: Item
|
||||
itemHash: ItemHash
|
||||
@@ -207,7 +229,6 @@ export class ItemService implements ItemServiceInterface {
|
||||
}): Promise<Item> {
|
||||
dto.existingItem.updatedWithSession = dto.sessionUuid
|
||||
dto.existingItem.contentSize = 0
|
||||
dto.existingItem.groupUuid = dto.itemHash.group_uuid ?? null
|
||||
dto.existingItem.lastEditedByUuid = dto.userUuid
|
||||
|
||||
if (dto.itemHash.content) {
|
||||
@@ -265,14 +286,14 @@ export class ItemService implements ItemServiceInterface {
|
||||
const savedItem = await this.itemRepository.save(dto.existingItem)
|
||||
|
||||
if (secondsFromLastUpdate >= this.revisionFrequency) {
|
||||
if ([ContentType.Note, ContentType.File].includes(savedItem.contentType as ContentType)) {
|
||||
if ([ContentType.Note, ContentType.File].includes(savedItem.contentType as ContentType) && savedItem.userUuid) {
|
||||
await this.domainEventPublisher.publish(
|
||||
this.domainEventFactory.createItemRevisionCreationRequested(savedItem.uuid, savedItem.userUuid),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (wasMarkedAsDuplicate) {
|
||||
if (wasMarkedAsDuplicate && savedItem.userUuid) {
|
||||
await this.domainEventPublisher.publish(
|
||||
this.domainEventFactory.createDuplicateItemSyncedEvent(savedItem.uuid, savedItem.userUuid),
|
||||
)
|
||||
@@ -286,13 +307,13 @@ export class ItemService implements ItemServiceInterface {
|
||||
|
||||
const savedItem = await this.itemRepository.save(newItem)
|
||||
|
||||
if ([ContentType.Note, ContentType.File].includes(savedItem.contentType as ContentType)) {
|
||||
if ([ContentType.Note, ContentType.File].includes(savedItem.contentType as ContentType) && savedItem.userUuid) {
|
||||
await this.domainEventPublisher.publish(
|
||||
this.domainEventFactory.createItemRevisionCreationRequested(savedItem.uuid, savedItem.userUuid),
|
||||
)
|
||||
}
|
||||
|
||||
if (savedItem.duplicateOf) {
|
||||
if (savedItem.duplicateOf && savedItem.userUuid) {
|
||||
await this.domainEventPublisher.publish(
|
||||
this.domainEventFactory.createDuplicateItemSyncedEvent(savedItem.uuid, savedItem.userUuid),
|
||||
)
|
||||
|
||||
@@ -2,14 +2,14 @@ import { ItemSaveValidationDTO } from '../SaveValidator/ItemSaveValidationDTO'
|
||||
import { ItemSaveRuleResult } from './ItemSaveRuleResult'
|
||||
import { ItemSaveRuleInterface } from './ItemSaveRuleInterface'
|
||||
import { ConflictType } from '@standardnotes/responses'
|
||||
import { GroupUserServiceInterface } from '../../GroupUser/Service/GroupUserServiceInterface'
|
||||
import { VaultUserServiceInterface } from '../../VaultUser/Service/VaultUserServiceInterface'
|
||||
import { ItemHash } from '../ItemHash'
|
||||
import { GroupUserPermission } from '../../GroupUser/Model/GroupUserPermission'
|
||||
import { GroupServiceInterface } from '../../Group/Service/GroupServiceInterface'
|
||||
import { VaultUserPermission } from '../../VaultUser/Model/VaultUserPermission'
|
||||
import { VaultServiceInterface } from '../../Vault/Service/VaultServiceInterface'
|
||||
import { ContentType } from '@standardnotes/common'
|
||||
|
||||
export class OwnershipFilter implements ItemSaveRuleInterface {
|
||||
constructor(private groupService: GroupServiceInterface, private groupUserService: GroupUserServiceInterface) {}
|
||||
constructor(private vaultService: VaultServiceInterface, private vaultUserService: VaultUserServiceInterface) {}
|
||||
|
||||
async check(dto: ItemSaveValidationDTO): Promise<ItemSaveRuleResult> {
|
||||
const itemBelongsToADifferentUser = dto.existingItem != null && dto.existingItem.userUuid !== dto.userUuid
|
||||
@@ -18,7 +18,7 @@ export class OwnershipFilter implements ItemSaveRuleInterface {
|
||||
passed: true,
|
||||
}
|
||||
|
||||
const groupReadonlyFail = {
|
||||
const vaultReadonlyFail = {
|
||||
passed: false,
|
||||
conflict: {
|
||||
unsavedItem: dto.itemHash,
|
||||
@@ -34,39 +34,39 @@ export class OwnershipFilter implements ItemSaveRuleInterface {
|
||||
},
|
||||
}
|
||||
|
||||
const groupUuidInvolved = dto.existingItem?.groupUuid || dto.itemHash.group_uuid
|
||||
if (itemBelongsToADifferentUser && !groupUuidInvolved) {
|
||||
const vaultUuidInvolved = dto.existingItem?.vaultUuid || dto.itemHash.vault_uuid
|
||||
if (itemBelongsToADifferentUser && !vaultUuidInvolved) {
|
||||
return ownershipFail
|
||||
}
|
||||
|
||||
if (groupUuidInvolved) {
|
||||
const groupAuthorization = await this.groupAuthorizationForItem(dto.userUuid, groupUuidInvolved)
|
||||
if (!groupAuthorization) {
|
||||
if (vaultUuidInvolved) {
|
||||
const vaultAuthorization = await this.vaultAuthorizationForItem(dto.userUuid, vaultUuidInvolved)
|
||||
if (!vaultAuthorization) {
|
||||
return ownershipFail
|
||||
}
|
||||
|
||||
if (groupAuthorization === 'read') {
|
||||
return groupReadonlyFail
|
||||
if (vaultAuthorization === 'read') {
|
||||
return vaultReadonlyFail
|
||||
}
|
||||
|
||||
const isItemBeingRemovedFromGroup =
|
||||
dto.existingItem != null && dto.existingItem.groupUuid != null && dto.itemHash.group_uuid == null
|
||||
const isItemBeingRemovedFromVault =
|
||||
dto.existingItem != null && dto.existingItem.vaultUuid != null && dto.itemHash.vault_uuid == null
|
||||
|
||||
const isItemBeingDeleted = dto.itemHash.deleted === true
|
||||
|
||||
if (isItemBeingRemovedFromGroup || isItemBeingDeleted) {
|
||||
if (isItemBeingRemovedFromVault || isItemBeingDeleted) {
|
||||
if (itemBelongsToADifferentUser) {
|
||||
return groupAuthorization === 'admin' ? successValue : groupReadonlyFail
|
||||
return vaultAuthorization === 'admin' ? successValue : vaultReadonlyFail
|
||||
} else {
|
||||
return successValue
|
||||
}
|
||||
}
|
||||
|
||||
if (dto.itemHash.content_type === ContentType.SharedItemsKey && groupAuthorization !== 'admin') {
|
||||
return groupReadonlyFail
|
||||
if (dto.itemHash.content_type === ContentType.SharedItemsKey && vaultAuthorization !== 'admin') {
|
||||
return vaultReadonlyFail
|
||||
}
|
||||
|
||||
const usingValidKey = await this.groupItemIsBeingSavedWithValidItemsKey(dto.itemHash)
|
||||
const usingValidKey = await this.vaultItemIsBeingSavedWithValidItemsKey(dto.itemHash)
|
||||
|
||||
if (!usingValidKey) {
|
||||
return {
|
||||
@@ -82,32 +82,32 @@ export class OwnershipFilter implements ItemSaveRuleInterface {
|
||||
return successValue
|
||||
}
|
||||
|
||||
private async groupItemIsBeingSavedWithValidItemsKey(itemHash: ItemHash): Promise<boolean> {
|
||||
private async vaultItemIsBeingSavedWithValidItemsKey(itemHash: ItemHash): Promise<boolean> {
|
||||
const isItemNotEncryptedByItemsKey = itemHash.content_type === ContentType.SharedItemsKey
|
||||
if (isItemNotEncryptedByItemsKey) {
|
||||
return true
|
||||
}
|
||||
|
||||
const group = await this.groupService.getGroup({ groupUuid: itemHash.group_uuid as string })
|
||||
const vault = await this.vaultService.getVault({ vaultUuid: itemHash.vault_uuid as string })
|
||||
|
||||
if (!group) {
|
||||
if (!vault) {
|
||||
return false
|
||||
}
|
||||
|
||||
return itemHash.items_key_id === group.specifiedItemsKeyUuid
|
||||
return itemHash.items_key_id === vault.specifiedItemsKeyUuid
|
||||
}
|
||||
|
||||
private async groupAuthorizationForItem(
|
||||
private async vaultAuthorizationForItem(
|
||||
userUuid: string,
|
||||
groupUuid: string,
|
||||
): Promise<GroupUserPermission | undefined> {
|
||||
const groupUser = await this.groupUserService.getUserForGroup({
|
||||
vaultUuid: string,
|
||||
): Promise<VaultUserPermission | undefined> {
|
||||
const vaultUser = await this.vaultUserService.getUserForVault({
|
||||
userUuid,
|
||||
groupUuid: groupUuid,
|
||||
vaultUuid: vaultUuid,
|
||||
})
|
||||
|
||||
if (groupUser) {
|
||||
return groupUser.permissions
|
||||
if (vaultUser) {
|
||||
return vaultUser.permissions
|
||||
}
|
||||
|
||||
return undefined
|
||||
|
||||
@@ -4,7 +4,7 @@ import { ItemSaveRuleInterface } from './ItemSaveRuleInterface'
|
||||
import { ConflictType } from '@standardnotes/responses'
|
||||
import { lt } from 'semver'
|
||||
|
||||
const MINIMUM_SNJS_VERSION_FOR_GROUP_OPERATIONS = '2.200.0'
|
||||
const MINIMUM_SNJS_VERSION_FOR_VAULT_OPERATIONS = '2.200.0'
|
||||
|
||||
export class SnjsVersionFilter implements ItemSaveRuleInterface {
|
||||
async check(dto: ItemSaveValidationDTO): Promise<ItemSaveRuleResult> {
|
||||
@@ -12,12 +12,12 @@ export class SnjsVersionFilter implements ItemSaveRuleInterface {
|
||||
passed: true,
|
||||
}
|
||||
|
||||
const groupUuidInvolved = dto.existingItem?.groupUuid || dto.itemHash.group_uuid
|
||||
if (!groupUuidInvolved) {
|
||||
const vaultUuidInvolved = dto.existingItem?.vaultUuid || dto.itemHash.vault_uuid
|
||||
if (!vaultUuidInvolved) {
|
||||
return successValue
|
||||
}
|
||||
|
||||
const isClientSnjsVersionLessThanMinimum = lt(dto.snjsVersion, MINIMUM_SNJS_VERSION_FOR_GROUP_OPERATIONS)
|
||||
const isClientSnjsVersionLessThanMinimum = lt(dto.snjsVersion, MINIMUM_SNJS_VERSION_FOR_VAULT_OPERATIONS)
|
||||
|
||||
if (isClientSnjsVersionLessThanMinimum) {
|
||||
return {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ContactProjection } from '../../../Projection/ContactProjection'
|
||||
import { GroupInviteProjection } from '../../../Projection/GroupInviteProjection'
|
||||
import { GroupProjection } from '../../../Projection/GroupProjection'
|
||||
import { VaultInviteProjection } from '../../../Projection/VaultInviteProjection'
|
||||
import { VaultProjection } from '../../../Projection/VaultProjection'
|
||||
import { ItemConflictProjection } from '../../../Projection/ItemConflictProjection'
|
||||
import { ItemProjection } from '../../../Projection/ItemProjection'
|
||||
import { SavedItemProjection } from '../../../Projection/SavedItemProjection'
|
||||
@@ -12,6 +12,6 @@ export type SyncResponse20200115 = {
|
||||
sync_token: string
|
||||
cursor_token?: string
|
||||
contacts: Array<ContactProjection>
|
||||
groups: Array<GroupProjection>
|
||||
group_invites: Array<GroupInviteProjection>
|
||||
vaults: Array<VaultProjection>
|
||||
vault_invites: Array<VaultInviteProjection>
|
||||
}
|
||||
|
||||
+14
-14
@@ -1,4 +1,4 @@
|
||||
import { GroupInvite } from './../../GroupInvite/Model/GroupInvite'
|
||||
import { VaultInvite } from '../../VaultInvite/Model/VaultInvite'
|
||||
import { Contact } from './../../Contact/Model/Contact'
|
||||
import { ProjectorInterface } from '../../../Projection/ProjectorInterface'
|
||||
import { SyncItemsResponse } from '../../UseCase/SyncItemsResponse'
|
||||
@@ -10,18 +10,18 @@ import { SyncResponse20200115 } from './SyncResponse20200115'
|
||||
import { SyncResponseFactoryInterface } from './SyncResponseFactoryInterface'
|
||||
import { SavedItemProjection } from '../../../Projection/SavedItemProjection'
|
||||
import { ContactProjection } from '../../../Projection/ContactProjection'
|
||||
import { GroupInviteProjection } from '../../../Projection/GroupInviteProjection'
|
||||
import { GroupProjection } from '../../../Projection/GroupProjection'
|
||||
import { Group } from '../../Group/Model/Group'
|
||||
import { VaultInviteProjection } from '../../../Projection/VaultInviteProjection'
|
||||
import { VaultProjection } from '../../../Projection/VaultProjection'
|
||||
import { Vault } from '../../Vault/Model/Vault'
|
||||
|
||||
export class SyncResponseFactory20200115 implements SyncResponseFactoryInterface {
|
||||
constructor(
|
||||
private itemProjector: ProjectorInterface<Item, ItemProjection>,
|
||||
private itemConflictProjector: ProjectorInterface<ItemConflict, ItemConflictProjection>,
|
||||
private savedItemProjector: ProjectorInterface<Item, SavedItemProjection>,
|
||||
private groupInviteProjector: ProjectorInterface<GroupInvite, GroupInviteProjection>,
|
||||
private vaultInviteProjector: ProjectorInterface<VaultInvite, VaultInviteProjection>,
|
||||
private contactProjector: ProjectorInterface<Contact, ContactProjection>,
|
||||
private groupProjector: ProjectorInterface<Group, GroupProjection>,
|
||||
private vaultProjector: ProjectorInterface<Vault, VaultProjection>,
|
||||
) {}
|
||||
|
||||
async createResponse(syncItemsResponse: SyncItemsResponse): Promise<SyncResponse20200115> {
|
||||
@@ -40,14 +40,14 @@ export class SyncResponseFactory20200115 implements SyncResponseFactoryInterface
|
||||
conflicts.push(this.itemConflictProjector.projectFull(itemConflict))
|
||||
}
|
||||
|
||||
const groups = []
|
||||
for (const group of syncItemsResponse.groups) {
|
||||
groups.push(this.groupProjector.projectFull(group))
|
||||
const vaults = []
|
||||
for (const vault of syncItemsResponse.vaults) {
|
||||
vaults.push(this.vaultProjector.projectFull(vault))
|
||||
}
|
||||
|
||||
const groupInvites = []
|
||||
for (const groupInvite of syncItemsResponse.groupInvites) {
|
||||
groupInvites.push(this.groupInviteProjector.projectFull(groupInvite))
|
||||
const vaultInvites = []
|
||||
for (const vaultInvite of syncItemsResponse.vaultInvites) {
|
||||
vaultInvites.push(this.vaultInviteProjector.projectFull(vaultInvite))
|
||||
}
|
||||
|
||||
const contacts = []
|
||||
@@ -61,8 +61,8 @@ export class SyncResponseFactory20200115 implements SyncResponseFactoryInterface
|
||||
conflicts,
|
||||
sync_token: syncItemsResponse.syncToken,
|
||||
cursor_token: syncItemsResponse.cursorToken,
|
||||
groups,
|
||||
group_invites: groupInvites,
|
||||
vaults,
|
||||
vault_invites: vaultInvites,
|
||||
contacts,
|
||||
}
|
||||
}
|
||||
|
||||
-6
@@ -1,6 +0,0 @@
|
||||
export type CreateGroupFileReadValetTokenDTO = {
|
||||
userUuid: string
|
||||
groupUuid: string
|
||||
fileUuid: string
|
||||
remoteIdentifier: string
|
||||
}
|
||||
+14
-15
@@ -1,30 +1,30 @@
|
||||
import { inject, injectable } from 'inversify'
|
||||
import { GroupValetTokenData, TokenEncoderInterface, ValetTokenOperation } from '@standardnotes/security'
|
||||
import { VaultValetTokenData, TokenEncoderInterface, ValetTokenOperation } from '@standardnotes/security'
|
||||
import { CreateValetTokenResponseData } from '@standardnotes/responses'
|
||||
import TYPES from '../../../Bootstrap/Types'
|
||||
import { UseCaseInterface } from '../UseCaseInterface'
|
||||
import { CreateGroupFileReadValetTokenDTO } from './CreateGroupFileReadValetTokenDTO'
|
||||
import { CreateVaultFileReadValetTokenDTO } from './CreateVaultFileReadValetTokenDTO'
|
||||
import { GetGlobalItem } from '../GetGlobalItem/GetGlobalItem'
|
||||
import { GroupUserServiceInterface } from '../../GroupUser/Service/GroupUserServiceInterface'
|
||||
import { VaultUserServiceInterface } from '../../VaultUser/Service/VaultUserServiceInterface'
|
||||
|
||||
@injectable()
|
||||
export class CreateGroupFileReadValetToken implements UseCaseInterface {
|
||||
export class CreateVaultFileReadValetToken implements UseCaseInterface {
|
||||
constructor(
|
||||
@inject(TYPES.GetGlobalItem) private getGlobalItem: GetGlobalItem,
|
||||
@inject(TYPES.GroupUserService) private groupUserService: GroupUserServiceInterface,
|
||||
@inject(TYPES.ValetTokenEncoder) private tokenEncoder: TokenEncoderInterface<GroupValetTokenData>,
|
||||
@inject(TYPES.VaultUserService) private vaultUserService: VaultUserServiceInterface,
|
||||
@inject(TYPES.ValetTokenEncoder) private tokenEncoder: TokenEncoderInterface<VaultValetTokenData>,
|
||||
@inject(TYPES.VALET_TOKEN_TTL) private valetTokenTTL: number,
|
||||
) {}
|
||||
|
||||
async execute(dto: CreateGroupFileReadValetTokenDTO): Promise<CreateValetTokenResponseData> {
|
||||
async execute(dto: CreateVaultFileReadValetTokenDTO): Promise<CreateValetTokenResponseData> {
|
||||
const itemResponse = await this.getGlobalItem.execute({
|
||||
itemUuid: dto.fileUuid,
|
||||
})
|
||||
|
||||
if (
|
||||
itemResponse.success === false ||
|
||||
!itemResponse.item.groupUuid ||
|
||||
itemResponse.item.groupUuid !== dto.groupUuid
|
||||
!itemResponse.item.vaultUuid ||
|
||||
itemResponse.item.vaultUuid !== dto.vaultUuid
|
||||
) {
|
||||
return {
|
||||
success: false,
|
||||
@@ -32,21 +32,20 @@ export class CreateGroupFileReadValetToken implements UseCaseInterface {
|
||||
}
|
||||
}
|
||||
|
||||
const groupUser = await this.groupUserService.getUserForGroup({
|
||||
const vaultUser = await this.vaultUserService.getUserForVault({
|
||||
userUuid: dto.userUuid,
|
||||
groupUuid: dto.groupUuid,
|
||||
vaultUuid: dto.vaultUuid,
|
||||
})
|
||||
|
||||
if (!groupUser) {
|
||||
if (!vaultUser) {
|
||||
return {
|
||||
success: false,
|
||||
reason: 'invalid-parameters',
|
||||
}
|
||||
}
|
||||
|
||||
const tokenData: GroupValetTokenData = {
|
||||
groupUuid: dto.groupUuid,
|
||||
fileOwnerUuid: itemResponse.item.userUuid,
|
||||
const tokenData: VaultValetTokenData = {
|
||||
vaultUuid: dto.vaultUuid,
|
||||
permittedOperation: ValetTokenOperation.Read,
|
||||
remoteIdentifier: dto.remoteIdentifier,
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
export type CreateVaultFileReadValetTokenDTO = {
|
||||
userUuid: string
|
||||
vaultUuid: string
|
||||
fileUuid: string
|
||||
remoteIdentifier: string
|
||||
}
|
||||
@@ -5,14 +5,14 @@ import { ItemServiceInterface } from '../Item/ItemServiceInterface'
|
||||
import { SyncItemsDTO } from './SyncItemsDTO'
|
||||
import { SyncItemsResponse } from './SyncItemsResponse'
|
||||
import { UseCaseInterface } from './UseCaseInterface'
|
||||
import { GroupInviteServiceInterface } from '../GroupInvite/Service/GroupInviteServiceInterface'
|
||||
import { GroupServiceInterface } from '../Group/Service/GroupServiceInterface'
|
||||
import { VaultInviteServiceInterface } from '../VaultInvite/Service/VaultInviteServiceInterface'
|
||||
import { VaultServiceInterface } from '../Vault/Service/VaultServiceInterface'
|
||||
|
||||
export class SyncItems implements UseCaseInterface {
|
||||
constructor(
|
||||
private itemService: ItemServiceInterface,
|
||||
private groupService: GroupServiceInterface,
|
||||
private groupInviteService: GroupInviteServiceInterface,
|
||||
private vaultService: VaultServiceInterface,
|
||||
private vaultInviteService: VaultInviteServiceInterface,
|
||||
private contactService: ContactServiceInterface,
|
||||
) {}
|
||||
|
||||
@@ -20,7 +20,7 @@ export class SyncItems implements UseCaseInterface {
|
||||
const getItemsResult = await this.itemService.getItems({
|
||||
userUuid: dto.userUuid,
|
||||
syncToken: dto.syncToken,
|
||||
groupUuids: dto.groupUuids,
|
||||
vaultUuids: dto.vaultUuids,
|
||||
cursorToken: dto.cursorToken,
|
||||
limit: dto.limit,
|
||||
contentType: dto.contentType,
|
||||
@@ -36,8 +36,8 @@ export class SyncItems implements UseCaseInterface {
|
||||
})
|
||||
|
||||
let retrievedItems = this.filterOutSyncConflictsForConsecutiveSyncs(getItemsResult.items, saveItemsResult.conflicts)
|
||||
const isGroupExclusiveSync = dto.groupUuids && dto.groupUuids.length > 0
|
||||
if (this.isFirstSync(dto) && !isGroupExclusiveSync) {
|
||||
const isVaultExclusiveSync = dto.vaultUuids && dto.vaultUuids.length > 0
|
||||
if (this.isFirstSync(dto) && !isVaultExclusiveSync) {
|
||||
retrievedItems = await this.itemService.frontLoadKeysItemsToTop(dto.userUuid, retrievedItems)
|
||||
}
|
||||
|
||||
@@ -46,12 +46,12 @@ export class SyncItems implements UseCaseInterface {
|
||||
cursorToken: dto.cursorToken,
|
||||
})
|
||||
|
||||
const groups = await this.groupService.getGroups({
|
||||
const vaults = await this.vaultService.getVaults({
|
||||
userUuid: dto.userUuid,
|
||||
lastSyncTime,
|
||||
})
|
||||
|
||||
const groupInvites = await this.groupInviteService.getInvitesForUser({
|
||||
const vaultInvites = await this.vaultInviteService.getInvitesForUser({
|
||||
userUuid: dto.userUuid,
|
||||
lastSyncTime,
|
||||
})
|
||||
@@ -67,8 +67,8 @@ export class SyncItems implements UseCaseInterface {
|
||||
savedItems: saveItemsResult.savedItems,
|
||||
conflicts: saveItemsResult.conflicts,
|
||||
cursorToken: getItemsResult.cursorToken,
|
||||
groups,
|
||||
groupInvites,
|
||||
vaults,
|
||||
vaultInvites,
|
||||
contacts,
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ export type SyncItemsDTO = {
|
||||
itemHashes: Array<ItemHash>
|
||||
computeIntegrityHash: boolean
|
||||
limit: number
|
||||
groupUuids?: string[] | null
|
||||
vaultUuids?: string[] | null
|
||||
syncToken?: string | null
|
||||
cursorToken?: string | null
|
||||
contentType?: string
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { GroupInvite } from './../GroupInvite/Model/GroupInvite'
|
||||
import { VaultInvite } from '../VaultInvite/Model/VaultInvite'
|
||||
import { Contact } from '../Contact/Model/Contact'
|
||||
import { Item } from '../Item/Item'
|
||||
import { ItemConflict } from '../Item/ItemConflict'
|
||||
import { Group } from '../Group/Model/Group'
|
||||
import { Vault } from '../Vault/Model/Vault'
|
||||
|
||||
export type SyncItemsResponse = {
|
||||
retrievedItems: Array<Item>
|
||||
savedItems: Array<Item>
|
||||
conflicts: Array<ItemConflict>
|
||||
groups: Array<Group>
|
||||
groupInvites: Array<GroupInvite>
|
||||
vaults: Array<Vault>
|
||||
vaultInvites: Array<VaultInvite>
|
||||
contacts: Array<Contact>
|
||||
syncToken: string
|
||||
cursorToken?: string
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
import { VaultFactoryInterface } from './VaultFactoryInterface'
|
||||
import { VaultHash } from './VaultHash'
|
||||
import { Vault } from '../Model/Vault'
|
||||
|
||||
export class VaultFactory implements VaultFactoryInterface {
|
||||
constructor(private timer: TimerInterface) {}
|
||||
|
||||
create(dto: { userUuid: string; vaultHash: VaultHash }): Vault {
|
||||
const newVault = new Vault()
|
||||
newVault.uuid = dto.vaultHash.uuid
|
||||
newVault.userUuid = dto.userUuid
|
||||
newVault.specifiedItemsKeyUuid = dto.vaultHash.specified_items_key_uuid
|
||||
newVault.vaultKeyTimestamp = dto.vaultHash.vault_key_timestamp
|
||||
|
||||
const now = this.timer.getTimestampInMicroseconds()
|
||||
newVault.updatedAtTimestamp = now
|
||||
newVault.createdAtTimestamp = now
|
||||
|
||||
if (dto.vaultHash.created_at_timestamp) {
|
||||
newVault.createdAtTimestamp = dto.vaultHash.created_at_timestamp
|
||||
}
|
||||
|
||||
return newVault
|
||||
}
|
||||
|
||||
createStub(dto: { userUuid: string; vaultHash: VaultHash }): Vault {
|
||||
const item = this.create(dto)
|
||||
return item
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { Vault } from '../Model/Vault'
|
||||
import { VaultHash } from './VaultHash'
|
||||
|
||||
export interface VaultFactoryInterface {
|
||||
create(dto: { userUuid: string; vaultHash: VaultHash }): Vault
|
||||
createStub(dto: { userUuid: string; vaultHash: VaultHash }): Vault
|
||||
}
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
export type GroupHash = {
|
||||
export type VaultHash = {
|
||||
uuid: string
|
||||
user_uuid: string
|
||||
specified_items_key_uuid: string
|
||||
group_key_timestamp: number
|
||||
vault_key_timestamp: number
|
||||
created_at_timestamp?: number
|
||||
updated_at_timestamp?: number
|
||||
}
|
||||
+5
-5
@@ -1,7 +1,7 @@
|
||||
import { Column, Entity, Index, PrimaryGeneratedColumn } from 'typeorm'
|
||||
|
||||
@Entity({ name: 'groups' })
|
||||
export class Group {
|
||||
@Entity({ name: 'vaults' })
|
||||
export class Vault {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
declare uuid: string
|
||||
|
||||
@@ -9,7 +9,7 @@ export class Group {
|
||||
name: 'user_uuid',
|
||||
length: 36,
|
||||
})
|
||||
@Index('index_groups_on_user_uuid')
|
||||
@Index('index_vaults_on_user_uuid')
|
||||
declare userUuid: string
|
||||
|
||||
@Column({
|
||||
@@ -19,10 +19,10 @@ export class Group {
|
||||
declare specifiedItemsKeyUuid: string
|
||||
|
||||
@Column({
|
||||
name: 'group_key_timestamp',
|
||||
name: 'vault_key_timestamp',
|
||||
type: 'bigint',
|
||||
})
|
||||
declare groupKeyTimestamp: number
|
||||
declare vaultKeyTimestamp: number
|
||||
|
||||
@Column({
|
||||
name: 'created_at_timestamp',
|
||||
@@ -0,0 +1,56 @@
|
||||
import { Repository, SelectQueryBuilder } from 'typeorm'
|
||||
import { VaultsRepositoryInterface, UserVaultsQuery } from './VaultRepositoryInterface'
|
||||
import { Vault } from '../Model/Vault'
|
||||
|
||||
export class TypeORMVaultRepository implements VaultsRepositoryInterface {
|
||||
constructor(private ormRepository: Repository<Vault>) {}
|
||||
|
||||
async create(vault: Vault): Promise<Vault> {
|
||||
return this.ormRepository.save(vault)
|
||||
}
|
||||
|
||||
async save(vault: Vault): Promise<Vault> {
|
||||
return this.ormRepository.save(vault)
|
||||
}
|
||||
|
||||
findByUuid(uuid: string): Promise<Vault | null> {
|
||||
return this.ormRepository
|
||||
.createQueryBuilder('vault')
|
||||
.where('vault.uuid = :uuid', {
|
||||
uuid,
|
||||
})
|
||||
.getOne()
|
||||
}
|
||||
|
||||
async remove(vault: Vault): Promise<Vault> {
|
||||
return this.ormRepository.remove(vault)
|
||||
}
|
||||
|
||||
async findAll(query: UserVaultsQuery): Promise<Vault[]> {
|
||||
return this.createFindAllQueryBuilder(query).getMany()
|
||||
}
|
||||
|
||||
private createFindAllQueryBuilder(query: UserVaultsQuery): SelectQueryBuilder<Vault> {
|
||||
const queryBuilder = this.ormRepository.createQueryBuilder('vault')
|
||||
|
||||
if (!query.userUuid && !query.vaultUuids) {
|
||||
throw new Error('Either userUuid or vaultUuids must be provided')
|
||||
}
|
||||
|
||||
if (query.userUuid) {
|
||||
queryBuilder.where('vault.user_uuid = :userUuid', { userUuid: query.userUuid })
|
||||
}
|
||||
|
||||
if (query.vaultUuids) {
|
||||
queryBuilder.where('vault.uuid IN (:...vaultUuids)', { vaultUuids: query.vaultUuids })
|
||||
}
|
||||
|
||||
if (query.lastSyncTime) {
|
||||
queryBuilder.andWhere('vault.updated_at_timestamp > :lastSyncTime', {
|
||||
lastSyncTime: query.lastSyncTime,
|
||||
})
|
||||
}
|
||||
|
||||
return queryBuilder
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { Vault } from '../Model/Vault'
|
||||
|
||||
export type VaultQuery = {
|
||||
userUuid: string
|
||||
}
|
||||
|
||||
export type UserVaultsQuery = {
|
||||
userUuid?: string
|
||||
vaultUuids?: string[]
|
||||
lastSyncTime?: number
|
||||
}
|
||||
|
||||
export interface VaultsRepositoryInterface {
|
||||
findByUuid(uuid: string): Promise<Vault | null>
|
||||
create(vault: Vault): Promise<Vault>
|
||||
save(vault: Vault): Promise<Vault>
|
||||
remove(vault: Vault): Promise<Vault>
|
||||
findAll(query: UserVaultsQuery): Promise<Vault[]>
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
import { Vault } from '../Model/Vault'
|
||||
import { VaultsRepositoryInterface } from '../Repository/VaultRepositoryInterface'
|
||||
import { CreateVaultDTO, VaultServiceInterface, UpdateVaultDTO } from './VaultServiceInterface'
|
||||
import { VaultFactoryInterface } from '../Factory/VaultFactoryInterface'
|
||||
import { VaultUserServiceInterface } from '../../VaultUser/Service/VaultUserServiceInterface'
|
||||
import { VaultInviteServiceInterface } from '../../VaultInvite/Service/VaultInviteServiceInterface'
|
||||
|
||||
export class VaultService implements VaultServiceInterface {
|
||||
constructor(
|
||||
private vaultRepository: VaultsRepositoryInterface,
|
||||
private vaultFactory: VaultFactoryInterface,
|
||||
private vaultUserService: VaultUserServiceInterface,
|
||||
private vaultInviteService: VaultInviteServiceInterface,
|
||||
private timer: TimerInterface,
|
||||
) {}
|
||||
|
||||
async createVault(dto: CreateVaultDTO): Promise<Vault | null> {
|
||||
const existingVault = await this.vaultRepository.findByUuid(dto.vaultUuid)
|
||||
if (existingVault) {
|
||||
return null
|
||||
}
|
||||
|
||||
const timestamp = this.timer.getTimestampInMicroseconds()
|
||||
const vault = this.vaultFactory.create({
|
||||
userUuid: dto.userUuid,
|
||||
vaultHash: {
|
||||
uuid: dto.vaultUuid,
|
||||
user_uuid: dto.userUuid,
|
||||
specified_items_key_uuid: dto.specifiedItemsKeyUuid,
|
||||
vault_key_timestamp: dto.vaultKeyTimestamp,
|
||||
created_at_timestamp: timestamp,
|
||||
updated_at_timestamp: timestamp,
|
||||
},
|
||||
})
|
||||
|
||||
const savedVault = await this.vaultRepository.create(vault)
|
||||
|
||||
return savedVault
|
||||
}
|
||||
|
||||
async getVault(dto: { vaultUuid: string }): Promise<Vault | null> {
|
||||
const vault = await this.vaultRepository.findByUuid(dto.vaultUuid)
|
||||
|
||||
return vault
|
||||
}
|
||||
|
||||
async getVaults(dto: { userUuid: string; lastSyncTime?: number }): Promise<Vault[]> {
|
||||
const vaultUsers = await this.vaultUserService.getAllVaultUsersForUser({
|
||||
userUuid: dto.userUuid,
|
||||
})
|
||||
|
||||
const vaultUuids = vaultUsers.map((vaultUser) => vaultUser.vaultUuid)
|
||||
|
||||
if (vaultUuids.length === 0) {
|
||||
return []
|
||||
}
|
||||
|
||||
return this.vaultRepository.findAll({ vaultUuids, lastSyncTime: dto.lastSyncTime })
|
||||
}
|
||||
|
||||
async updateVault(dto: UpdateVaultDTO): Promise<Vault | null> {
|
||||
const vault = await this.vaultRepository.findByUuid(dto.vaultUuid)
|
||||
if (!vault || vault.userUuid !== dto.originatorUuid) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (dto.vaultKeyTimestamp < vault.vaultKeyTimestamp) {
|
||||
return null
|
||||
}
|
||||
|
||||
vault.specifiedItemsKeyUuid = dto.specifiedItemsKeyUuid
|
||||
vault.vaultKeyTimestamp = dto.vaultKeyTimestamp
|
||||
vault.updatedAtTimestamp = this.timer.getTimestampInMicroseconds()
|
||||
|
||||
const savedVault = await this.vaultRepository.save(vault)
|
||||
|
||||
return savedVault
|
||||
}
|
||||
|
||||
async deleteVault(dto: { vaultUuid: string; originatorUuid: string }): Promise<boolean> {
|
||||
const vault = await this.vaultRepository.findByUuid(dto.vaultUuid)
|
||||
if (!vault || vault.userUuid !== dto.originatorUuid) {
|
||||
return false
|
||||
}
|
||||
|
||||
await this.vaultRepository.remove(vault)
|
||||
await this.vaultUserService.deleteAllVaultUsersForVault({
|
||||
vaultUuid: dto.vaultUuid,
|
||||
originatorUuid: dto.originatorUuid,
|
||||
})
|
||||
await this.vaultInviteService.deleteAllInvitesForVault({
|
||||
vaultUuid: dto.vaultUuid,
|
||||
originatorUuid: dto.originatorUuid,
|
||||
})
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { Vault } from '../Model/Vault'
|
||||
|
||||
export type CreateVaultDTO = {
|
||||
userUuid: string
|
||||
vaultUuid: string
|
||||
vaultKeyTimestamp: number
|
||||
specifiedItemsKeyUuid: string
|
||||
}
|
||||
|
||||
export type UpdateVaultDTO = {
|
||||
originatorUuid: string
|
||||
vaultUuid: string
|
||||
vaultKeyTimestamp: number
|
||||
specifiedItemsKeyUuid: string
|
||||
}
|
||||
|
||||
export interface VaultServiceInterface {
|
||||
createVault(dto: CreateVaultDTO): Promise<Vault | null>
|
||||
|
||||
updateVault(dto: UpdateVaultDTO): Promise<Vault | null>
|
||||
|
||||
deleteVault(dto: { vaultUuid: string; originatorUuid: string }): Promise<boolean>
|
||||
|
||||
getVault(dto: { vaultUuid: string }): Promise<Vault | null>
|
||||
|
||||
getVaults(dto: { userUuid: string; lastSyncTime?: number }): Promise<Vault[]>
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
import { VaultInviteFactoryInterface } from './VaultInviteFactoryInterface'
|
||||
import { VaultInviteHash } from './VaultInviteHash'
|
||||
import { VaultInvite } from '../Model/VaultInvite'
|
||||
|
||||
export class VaultInviteFactory implements VaultInviteFactoryInterface {
|
||||
constructor(private timer: TimerInterface) {}
|
||||
|
||||
create(dto: VaultInviteHash): VaultInvite {
|
||||
const newVaultInvite = new VaultInvite()
|
||||
newVaultInvite.uuid = dto.uuid
|
||||
newVaultInvite.userUuid = dto.user_uuid
|
||||
newVaultInvite.vaultUuid = dto.vault_uuid
|
||||
newVaultInvite.inviterUuid = dto.inviter_uuid
|
||||
newVaultInvite.inviterPublicKey = dto.inviter_public_key
|
||||
newVaultInvite.encryptedVaultData = dto.encrypted_vault_data
|
||||
newVaultInvite.inviteType = dto.invite_type
|
||||
newVaultInvite.permissions = dto.permissions
|
||||
|
||||
const now = this.timer.getTimestampInMicroseconds()
|
||||
newVaultInvite.updatedAtTimestamp = now
|
||||
newVaultInvite.createdAtTimestamp = now
|
||||
|
||||
if (dto.created_at_timestamp) {
|
||||
newVaultInvite.createdAtTimestamp = dto.created_at_timestamp
|
||||
}
|
||||
|
||||
return newVaultInvite
|
||||
}
|
||||
|
||||
createStub(dto: VaultInviteHash): VaultInvite {
|
||||
const item = this.create(dto)
|
||||
return item
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { VaultInvite } from '../Model/VaultInvite'
|
||||
import { VaultInviteHash } from './VaultInviteHash'
|
||||
|
||||
export interface VaultInviteFactoryInterface {
|
||||
create(dto: VaultInviteHash): VaultInvite
|
||||
createStub(dto: VaultInviteHash): VaultInvite
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { VaultUserPermission } from '../../VaultUser/Model/VaultUserPermission'
|
||||
import { VaultInviteType } from '../Model/VaultInviteType'
|
||||
|
||||
export type VaultInviteHash = {
|
||||
uuid: string
|
||||
user_uuid: string
|
||||
vault_uuid: string
|
||||
inviter_uuid: string
|
||||
inviter_public_key: string
|
||||
encrypted_vault_data: string
|
||||
invite_type: VaultInviteType
|
||||
permissions: VaultUserPermission
|
||||
created_at_timestamp?: number
|
||||
updated_at_timestamp?: number
|
||||
}
|
||||
+10
-10
@@ -1,17 +1,17 @@
|
||||
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'
|
||||
import { GroupInviteType } from './GroupInviteType'
|
||||
import { GroupUserPermission } from '../../GroupUser/Model/GroupUserPermission'
|
||||
import { VaultInviteType } from './VaultInviteType'
|
||||
import { VaultUserPermission } from '../../VaultUser/Model/VaultUserPermission'
|
||||
|
||||
@Entity({ name: 'group_invites' })
|
||||
export class GroupInvite {
|
||||
@Entity({ name: 'vault_invites' })
|
||||
export class VaultInvite {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
declare uuid: string
|
||||
|
||||
@Column({
|
||||
name: 'group_uuid',
|
||||
name: 'vault_uuid',
|
||||
length: 36,
|
||||
})
|
||||
declare groupUuid: string
|
||||
declare vaultUuid: string
|
||||
|
||||
@Column({
|
||||
name: 'user_uuid',
|
||||
@@ -27,7 +27,7 @@ export class GroupInvite {
|
||||
@Column({
|
||||
name: 'invite_type',
|
||||
})
|
||||
declare inviteType: GroupInviteType
|
||||
declare inviteType: VaultInviteType
|
||||
|
||||
@Column({
|
||||
name: 'inviter_public_key',
|
||||
@@ -35,14 +35,14 @@ export class GroupInvite {
|
||||
declare inviterPublicKey: string
|
||||
|
||||
@Column({
|
||||
name: 'encrypted_group_data',
|
||||
name: 'encrypted_vault_data',
|
||||
})
|
||||
declare encryptedGroupData: string
|
||||
declare encryptedVaultData: string
|
||||
|
||||
@Column({
|
||||
name: 'permissions',
|
||||
})
|
||||
declare permissions: GroupUserPermission
|
||||
declare permissions: VaultUserPermission
|
||||
|
||||
@Column({
|
||||
name: 'created_at_timestamp',
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
export enum GroupInviteType {
|
||||
export enum VaultInviteType {
|
||||
Join = 'join',
|
||||
KeyChange = 'key-change',
|
||||
}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
import { VaultInvite } from '../Model/VaultInvite'
|
||||
import { Repository, SelectQueryBuilder } from 'typeorm'
|
||||
import {
|
||||
VaultInviteFindAllForVault,
|
||||
VaultInviteFindAllForUserQuery,
|
||||
VaultInviteRepositoryInterface,
|
||||
} from './VaultInviteRepositoryInterface'
|
||||
|
||||
export class TypeORMVaultInviteRepository implements VaultInviteRepositoryInterface {
|
||||
constructor(private ormRepository: Repository<VaultInvite>) {}
|
||||
|
||||
async create(vaultInvite: VaultInvite): Promise<VaultInvite> {
|
||||
return this.ormRepository.save(vaultInvite)
|
||||
}
|
||||
|
||||
async save(vaultInvite: VaultInvite): Promise<VaultInvite> {
|
||||
return this.ormRepository.save(vaultInvite)
|
||||
}
|
||||
|
||||
findByUuid(uuid: string): Promise<VaultInvite | null> {
|
||||
return this.ormRepository
|
||||
.createQueryBuilder('vault_invite')
|
||||
.where('vault_invite.uuid = :uuid', {
|
||||
uuid,
|
||||
})
|
||||
.getOne()
|
||||
}
|
||||
|
||||
findByUserUuidAndVaultUuid(userUuid: string, vaultUuid: string): Promise<VaultInvite | null> {
|
||||
return this.ormRepository
|
||||
.createQueryBuilder('vault_invite')
|
||||
.where('vault_invite.user_uuid = :userUuid', { userUuid })
|
||||
.andWhere('vault_invite.vault_uuid = :vaultUuid', { vaultUuid })
|
||||
.getOne()
|
||||
}
|
||||
|
||||
async remove(vaultInvite: VaultInvite): Promise<VaultInvite> {
|
||||
return this.ormRepository.remove(vaultInvite)
|
||||
}
|
||||
|
||||
findAllForVault(query: VaultInviteFindAllForVault): Promise<VaultInvite[]> {
|
||||
return this.ormRepository
|
||||
.createQueryBuilder('vault_invite')
|
||||
.where('vault_invite.vault_uuid = :vaultUuid', { vaultUuid: query.vaultUuid })
|
||||
.getMany()
|
||||
}
|
||||
|
||||
async findAll(query: VaultInviteFindAllForUserQuery): Promise<VaultInvite[]> {
|
||||
return this.createFindAllQueryBuilder(query).getMany()
|
||||
}
|
||||
|
||||
private createFindAllQueryBuilder(query: VaultInviteFindAllForUserQuery): SelectQueryBuilder<VaultInvite> {
|
||||
const queryBuilder = this.ormRepository.createQueryBuilder('vault_invite')
|
||||
|
||||
if (query.userUuid) {
|
||||
queryBuilder.where('vault_invite.user_uuid = :userUuid', { userUuid: query.userUuid })
|
||||
} else if (query.inviterUuid) {
|
||||
queryBuilder.where('vault_invite.inviter_uuid = :inviterUuid', {
|
||||
inviterUuid: query.inviterUuid,
|
||||
})
|
||||
}
|
||||
|
||||
if (query.lastSyncTime) {
|
||||
queryBuilder.andWhere('vault_invite.updated_at_timestamp > :lastSyncTime', {
|
||||
lastSyncTime: query.lastSyncTime,
|
||||
})
|
||||
}
|
||||
|
||||
return queryBuilder
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
import { VaultInvite } from '../Model/VaultInvite'
|
||||
|
||||
export type VaultInviteQuery = {
|
||||
userUuid: string
|
||||
}
|
||||
|
||||
export type VaultInviteFindAllForUserQuery = {
|
||||
userUuid?: string
|
||||
lastSyncTime?: number
|
||||
inviterUuid?: string
|
||||
}
|
||||
|
||||
export type VaultInviteFindAllForVault = {
|
||||
vaultUuid: string
|
||||
}
|
||||
|
||||
export interface VaultInviteRepositoryInterface {
|
||||
findByUuid(vaultInviteUuid: string): Promise<VaultInvite | null>
|
||||
create(vault: VaultInvite): Promise<VaultInvite>
|
||||
save(vaultInvite: VaultInvite): Promise<VaultInvite>
|
||||
remove(vault: VaultInvite): Promise<VaultInvite>
|
||||
findAll(query: VaultInviteFindAllForUserQuery): Promise<VaultInvite[]>
|
||||
findAllForVault(query: VaultInviteFindAllForVault): Promise<VaultInvite[]>
|
||||
findByUserUuidAndVaultUuid(userUuid: string, vaultUuid: string): Promise<VaultInvite | null>
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { VaultInviteType } from '../Model/VaultInviteType'
|
||||
import { VaultUserPermission } from '../../VaultUser/Model/VaultUserPermission'
|
||||
|
||||
export type CreateInviteDTO = {
|
||||
originatorUuid: string
|
||||
vaultUuid: string
|
||||
userUuid: string
|
||||
inviterPublicKey: string
|
||||
encryptedVaultData: string
|
||||
inviteType: VaultInviteType
|
||||
permissions: VaultUserPermission
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
export interface GetUserGroupInvitesDTO {
|
||||
export interface GetUserVaultInvitesDTO {
|
||||
userUuid: string
|
||||
lastSyncTime?: number
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { VaultUserPermission } from '../../VaultUser/Model/VaultUserPermission'
|
||||
|
||||
export type UpdateInviteDTO = {
|
||||
originatorUuid: string
|
||||
inviteUuid: string
|
||||
inviterPublicKey: string
|
||||
encryptedVaultData: string
|
||||
permissions?: VaultUserPermission
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
import { VaultInvite } from '../Model/VaultInvite'
|
||||
import { VaultInviteFactoryInterface } from '../Factory/VaultInviteFactoryInterface'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { VaultInviteRepositoryInterface } from '../Repository/VaultInviteRepositoryInterface'
|
||||
import { VaultInviteServiceInterface } from './VaultInviteServiceInterface'
|
||||
import { GetUserVaultInvitesDTO } from './GetUserVaultInvitesDTO'
|
||||
import { VaultsRepositoryInterface } from '../../Vault/Repository/VaultRepositoryInterface'
|
||||
import { VaultUserServiceInterface } from '../../VaultUser/Service/VaultUserServiceInterface'
|
||||
import { VaultInviteType } from '../Model/VaultInviteType'
|
||||
import { CreateInviteDTO } from './CreateInviteDTO'
|
||||
import { UpdateInviteDTO } from './UpdateInviteDTO'
|
||||
|
||||
export class VaultInviteService implements VaultInviteServiceInterface {
|
||||
constructor(
|
||||
private vaultRepository: VaultsRepositoryInterface,
|
||||
private vaultInviteRepository: VaultInviteRepositoryInterface,
|
||||
private vaultInviteFactory: VaultInviteFactoryInterface,
|
||||
private vaultUserService: VaultUserServiceInterface,
|
||||
private timer: TimerInterface,
|
||||
) {}
|
||||
|
||||
async createInvite(dto: CreateInviteDTO): Promise<VaultInvite | null> {
|
||||
const vault = await this.vaultRepository.findByUuid(dto.vaultUuid)
|
||||
if (!vault || vault.userUuid !== dto.originatorUuid) {
|
||||
return null
|
||||
}
|
||||
|
||||
const timestamp = this.timer.getTimestampInMicroseconds()
|
||||
|
||||
const vaultInvite = this.vaultInviteFactory.create({
|
||||
uuid: uuidv4(),
|
||||
user_uuid: dto.userUuid,
|
||||
vault_uuid: dto.vaultUuid,
|
||||
inviter_uuid: dto.originatorUuid,
|
||||
inviter_public_key: dto.inviterPublicKey,
|
||||
encrypted_vault_data: dto.encryptedVaultData,
|
||||
invite_type: dto.inviteType,
|
||||
permissions: dto.permissions,
|
||||
created_at_timestamp: timestamp,
|
||||
updated_at_timestamp: timestamp,
|
||||
})
|
||||
|
||||
return this.vaultInviteRepository.create(vaultInvite)
|
||||
}
|
||||
|
||||
async updateInvite(dto: UpdateInviteDTO): Promise<VaultInvite | null> {
|
||||
const vaultInvite = await this.vaultInviteRepository.findByUuid(dto.inviteUuid)
|
||||
if (!vaultInvite || vaultInvite.inviterUuid !== dto.originatorUuid) {
|
||||
return null
|
||||
}
|
||||
|
||||
vaultInvite.inviterPublicKey = dto.inviterPublicKey
|
||||
vaultInvite.encryptedVaultData = dto.encryptedVaultData
|
||||
if (dto.permissions) {
|
||||
vaultInvite.permissions = dto.permissions
|
||||
}
|
||||
vaultInvite.updatedAtTimestamp = this.timer.getTimestampInMicroseconds()
|
||||
|
||||
return this.vaultInviteRepository.save(vaultInvite)
|
||||
}
|
||||
|
||||
getInvitesForUser(dto: GetUserVaultInvitesDTO): Promise<VaultInvite[]> {
|
||||
return this.vaultInviteRepository.findAll(dto)
|
||||
}
|
||||
|
||||
getOutboundInvitesForUser(dto: { userUuid: string }): Promise<VaultInvite[]> {
|
||||
return this.vaultInviteRepository.findAll({
|
||||
inviterUuid: dto.userUuid,
|
||||
})
|
||||
}
|
||||
|
||||
async deleteAllInboundInvites(dto: { userUuid: string }): Promise<void> {
|
||||
const inboundInvites = await this.vaultInviteRepository.findAll({
|
||||
userUuid: dto.userUuid,
|
||||
})
|
||||
|
||||
for (const invite of inboundInvites) {
|
||||
await this.vaultInviteRepository.remove(invite)
|
||||
}
|
||||
}
|
||||
|
||||
async getInvitesForVault(dto: { vaultUuid: string; originatorUuid: string }): Promise<VaultInvite[] | undefined> {
|
||||
const vault = await this.vaultRepository.findByUuid(dto.vaultUuid)
|
||||
if (!vault) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const isUserVaultAdmin = vault && vault.userUuid === dto.originatorUuid
|
||||
if (!isUserVaultAdmin) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const users = await this.vaultInviteRepository.findAllForVault({ vaultUuid: dto.vaultUuid })
|
||||
|
||||
return users
|
||||
}
|
||||
|
||||
async acceptInvite(dto: { originatorUuid: string; inviteUuid: string }): Promise<boolean> {
|
||||
const invite = await this.vaultInviteRepository.findByUuid(dto.inviteUuid)
|
||||
if (!invite) {
|
||||
return false
|
||||
}
|
||||
|
||||
const isAuthorized = invite.userUuid === dto.originatorUuid
|
||||
if (!isAuthorized) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (invite.inviteType === VaultInviteType.Join) {
|
||||
const addedUser = await this.vaultUserService.addVaultUser({
|
||||
userUuid: dto.originatorUuid,
|
||||
vaultUuid: invite.vaultUuid,
|
||||
permissions: invite.permissions,
|
||||
})
|
||||
|
||||
if (!addedUser) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
await this.vaultInviteRepository.remove(invite)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
async declineInvite(dto: { originatorUuid: string; inviteUuid: string }): Promise<boolean> {
|
||||
const invite = await this.vaultInviteRepository.findByUuid(dto.inviteUuid)
|
||||
if (!invite) {
|
||||
return false
|
||||
}
|
||||
|
||||
const isAuthorized = invite.userUuid === dto.originatorUuid
|
||||
if (!isAuthorized) {
|
||||
return false
|
||||
}
|
||||
|
||||
await this.vaultInviteRepository.remove(invite)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
async deleteInvite(dto: { originatorUuid: string; inviteUuid: string }): Promise<boolean> {
|
||||
const invite = await this.vaultInviteRepository.findByUuid(dto.inviteUuid)
|
||||
if (!invite) {
|
||||
return false
|
||||
}
|
||||
|
||||
const isAuthorized = invite.inviterUuid === dto.originatorUuid || invite.userUuid === dto.originatorUuid
|
||||
if (!isAuthorized) {
|
||||
return false
|
||||
}
|
||||
|
||||
await this.vaultInviteRepository.remove(invite)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
async deleteAllInvitesForVault(dto: { originatorUuid: string; vaultUuid: string }): Promise<boolean> {
|
||||
const vault = await this.vaultRepository.findByUuid(dto.vaultUuid)
|
||||
if (!vault || vault.userUuid !== dto.originatorUuid) {
|
||||
return false
|
||||
}
|
||||
|
||||
const vaultInvites = await this.vaultInviteRepository.findAllForVault({ vaultUuid: dto.vaultUuid })
|
||||
for (const vaultInvite of vaultInvites) {
|
||||
await this.vaultInviteRepository.remove(vaultInvite)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { VaultInvite } from '../Model/VaultInvite'
|
||||
import { CreateInviteDTO } from './CreateInviteDTO'
|
||||
import { GetUserVaultInvitesDTO } from './GetUserVaultInvitesDTO'
|
||||
import { UpdateInviteDTO } from './UpdateInviteDTO'
|
||||
|
||||
export interface VaultInviteServiceInterface {
|
||||
createInvite(dto: CreateInviteDTO): Promise<VaultInvite | null>
|
||||
|
||||
updateInvite(dto: UpdateInviteDTO): Promise<VaultInvite | null>
|
||||
|
||||
getInvitesForUser(dto: GetUserVaultInvitesDTO): Promise<VaultInvite[]>
|
||||
|
||||
getInvitesForVault(dto: { vaultUuid: string; originatorUuid: string }): Promise<VaultInvite[] | undefined>
|
||||
|
||||
getOutboundInvitesForUser(dto: { userUuid: string }): Promise<VaultInvite[]>
|
||||
|
||||
acceptInvite(dto: { originatorUuid: string; inviteUuid: string }): Promise<boolean>
|
||||
|
||||
declineInvite(dto: { originatorUuid: string; inviteUuid: string }): Promise<boolean>
|
||||
|
||||
deleteInvite(dto: { originatorUuid: string; vaultUuid: string; inviteUuid: string }): Promise<boolean>
|
||||
|
||||
deleteAllInvitesForVault(dto: { originatorUuid: string; vaultUuid: string }): Promise<boolean>
|
||||
|
||||
deleteAllInboundInvites(dto: { userUuid: string }): Promise<void>
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
import { VaultUserFactoryInterface } from './VaultUserFactoryInterface'
|
||||
import { VaultUserHash } from './VaultUserHash'
|
||||
import { VaultUser } from '../Model/VaultUser'
|
||||
|
||||
export class VaultUserFactory implements VaultUserFactoryInterface {
|
||||
constructor(private timer: TimerInterface) {}
|
||||
|
||||
create(dto: VaultUserHash): VaultUser {
|
||||
const newVaultUser = new VaultUser()
|
||||
newVaultUser.uuid = dto.uuid
|
||||
newVaultUser.userUuid = dto.user_uuid
|
||||
newVaultUser.vaultUuid = dto.vault_uuid
|
||||
newVaultUser.permissions = dto.permissions
|
||||
|
||||
const now = this.timer.getTimestampInMicroseconds()
|
||||
newVaultUser.updatedAtTimestamp = now
|
||||
newVaultUser.createdAtTimestamp = now
|
||||
|
||||
if (dto.created_at_timestamp) {
|
||||
newVaultUser.createdAtTimestamp = dto.created_at_timestamp
|
||||
}
|
||||
|
||||
return newVaultUser
|
||||
}
|
||||
|
||||
createStub(dto: VaultUserHash): VaultUser {
|
||||
const item = this.create(dto)
|
||||
return item
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { VaultUser } from '../Model/VaultUser'
|
||||
import { VaultUserHash } from './VaultUserHash'
|
||||
|
||||
export interface VaultUserFactoryInterface {
|
||||
create(dto: VaultUserHash): VaultUser
|
||||
createStub(dto: VaultUserHash): VaultUser
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { VaultUserPermission } from '../Model/VaultUserPermission'
|
||||
|
||||
export type VaultUserHash = {
|
||||
uuid: string
|
||||
user_uuid: string
|
||||
vault_uuid: string
|
||||
permissions: VaultUserPermission
|
||||
created_at_timestamp?: number
|
||||
updated_at_timestamp?: number
|
||||
}
|
||||
+6
-6
@@ -1,16 +1,16 @@
|
||||
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'
|
||||
import { GroupUserPermission } from './GroupUserPermission'
|
||||
import { VaultUserPermission } from './VaultUserPermission'
|
||||
|
||||
@Entity({ name: 'group_users' })
|
||||
export class GroupUser {
|
||||
@Entity({ name: 'vault_users' })
|
||||
export class VaultUser {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
declare uuid: string
|
||||
|
||||
@Column({
|
||||
name: 'group_uuid',
|
||||
name: 'vault_uuid',
|
||||
length: 36,
|
||||
})
|
||||
declare groupUuid: string
|
||||
declare vaultUuid: string
|
||||
|
||||
@Column({
|
||||
name: 'user_uuid',
|
||||
@@ -21,7 +21,7 @@ export class GroupUser {
|
||||
@Column({
|
||||
name: 'permissions',
|
||||
})
|
||||
declare permissions: GroupUserPermission
|
||||
declare permissions: VaultUserPermission
|
||||
|
||||
@Column({
|
||||
name: 'created_at_timestamp',
|
||||
@@ -0,0 +1 @@
|
||||
export type VaultUserPermission = 'read' | 'write' | 'admin'
|
||||
@@ -0,0 +1,65 @@
|
||||
import { VaultUser } from '../Model/VaultUser'
|
||||
import { Repository, SelectQueryBuilder } from 'typeorm'
|
||||
import {
|
||||
VaultUserFindAllForVault,
|
||||
VaultUserFindAllForUserQuery,
|
||||
VaultUserRepositoryInterface,
|
||||
} from './VaultUserRepositoryInterface'
|
||||
|
||||
export class TypeORMVaultUserRepository implements VaultUserRepositoryInterface {
|
||||
constructor(private ormRepository: Repository<VaultUser>) {}
|
||||
|
||||
async create(vaultUser: VaultUser): Promise<VaultUser> {
|
||||
return this.ormRepository.save(vaultUser)
|
||||
}
|
||||
|
||||
async save(vaultUser: VaultUser): Promise<VaultUser> {
|
||||
return this.ormRepository.save(vaultUser)
|
||||
}
|
||||
|
||||
findByUuid(uuid: string): Promise<VaultUser | null> {
|
||||
return this.ormRepository
|
||||
.createQueryBuilder('vault_user')
|
||||
.where('vault_user.uuid = :uuid', {
|
||||
uuid,
|
||||
})
|
||||
.getOne()
|
||||
}
|
||||
|
||||
findByUserUuidAndVaultUuid(userUuid: string, vaultUuid: string): Promise<VaultUser | null> {
|
||||
return this.ormRepository
|
||||
.createQueryBuilder('vault_user')
|
||||
.where('vault_user.user_uuid = :userUuid', { userUuid })
|
||||
.andWhere('vault_user.vault_uuid = :vaultUuid', { vaultUuid })
|
||||
.getOne()
|
||||
}
|
||||
|
||||
async remove(vaultUser: VaultUser): Promise<VaultUser> {
|
||||
return this.ormRepository.remove(vaultUser)
|
||||
}
|
||||
|
||||
findAllForVault(query: VaultUserFindAllForVault): Promise<VaultUser[]> {
|
||||
return this.ormRepository
|
||||
.createQueryBuilder('vault_user')
|
||||
.where('vault_user.vault_uuid = :vaultUuid', { vaultUuid: query.vaultUuid })
|
||||
.getMany()
|
||||
}
|
||||
|
||||
async findAllForUser(query: VaultUserFindAllForUserQuery): Promise<VaultUser[]> {
|
||||
return this.createFindAllQueryBuilder(query).getMany()
|
||||
}
|
||||
|
||||
private createFindAllQueryBuilder(query: VaultUserFindAllForUserQuery): SelectQueryBuilder<VaultUser> {
|
||||
const queryBuilder = this.ormRepository.createQueryBuilder('vault_user')
|
||||
|
||||
queryBuilder.where('vault_user.user_uuid = :userUuid', { userUuid: query.userUuid })
|
||||
|
||||
if (query.lastSyncTime) {
|
||||
queryBuilder.andWhere('vault_user.updated_at_timestamp > :lastSyncTime', {
|
||||
lastSyncTime: query.lastSyncTime,
|
||||
})
|
||||
}
|
||||
|
||||
return queryBuilder
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user