mirror of
https://github.com/standardnotes/server
synced 2026-07-14 00:01:54 -04:00
feat: on the fly vault creation during sync
This commit is contained in:
@@ -25,10 +25,6 @@ export class CreateVault1684173017359 implements MigrationInterface {
|
||||
length: '36',
|
||||
isNullable: false,
|
||||
}),
|
||||
new TableColumn({
|
||||
name: 'vault_key_timestamp',
|
||||
type: 'bigint',
|
||||
}),
|
||||
new TableColumn({
|
||||
name: 'file_upload_bytes_used',
|
||||
type: 'bigint',
|
||||
|
||||
@@ -148,6 +148,7 @@ export class ServerContainerConfigLoader extends CommonContainerConfigLoader {
|
||||
context.container.get(TYPES.ItemProjector),
|
||||
context.container.get(TYPES.MAX_ITEMS_LIMIT),
|
||||
context.container.get(TYPES.VaultUserRepository),
|
||||
context.container.get(TYPES.VaultService),
|
||||
context.container.get(TYPES.Logger),
|
||||
)
|
||||
})
|
||||
|
||||
@@ -16,7 +16,6 @@ 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 { CreateVaultFileValetToken } from '../Domain/UseCase/CreateVaultFileValetToken/CreateVaultFileValetToken'
|
||||
import { RemovedVaultUserServiceInterface } from '../Domain/RemovedVaultUser/Service/RemovedVaultUserServiceInterface'
|
||||
|
||||
@@ -24,7 +23,6 @@ import { RemovedVaultUserServiceInterface } from '../Domain/RemovedVaultUser/Ser
|
||||
export class VaultsController extends BaseHttpController {
|
||||
constructor(
|
||||
@inject(TYPES.VaultService) private vaultService: VaultServiceInterface,
|
||||
@inject(TYPES.VaultUserService) private vaultUserService: VaultUserServiceInterface,
|
||||
@inject(TYPES.RemovedVaultUserService) private removedVaultUserService: RemovedVaultUserServiceInterface,
|
||||
@inject(TYPES.VaultProjector) private vaultProjector: ProjectorInterface<Vault, VaultProjection>,
|
||||
@inject(TYPES.VaultUserProjector) private vaultUserProjector: ProjectorInterface<VaultUser, VaultUserProjection>,
|
||||
@@ -74,7 +72,6 @@ export class VaultsController extends BaseHttpController {
|
||||
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,
|
||||
})
|
||||
|
||||
@@ -82,19 +79,9 @@ export class VaultsController extends BaseHttpController {
|
||||
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),
|
||||
vault: this.vaultProjector.projectFull(result.vault),
|
||||
vaultUser: this.vaultUserProjector.projectFull(result.vaultUser),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -103,7 +90,6 @@ export class VaultsController extends BaseHttpController {
|
||||
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,
|
||||
})
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ import { ProjectorInterface } from '../../Projection/ProjectorInterface'
|
||||
import { ItemProjection } from '../../Projection/ItemProjection'
|
||||
import { VaultUserRepositoryInterface } from '../VaultUser/Repository/VaultUserRepositoryInterface'
|
||||
import { ConflictType } from '../../Tmp/ConflictType'
|
||||
import { VaultServiceInterface } from '../Vault/Service/VaultServiceInterface'
|
||||
|
||||
export class ItemService implements ItemServiceInterface {
|
||||
private readonly DEFAULT_ITEMS_LIMIT = 150
|
||||
@@ -39,6 +40,7 @@ export class ItemService implements ItemServiceInterface {
|
||||
private itemProjector: ProjectorInterface<Item, ItemProjection>,
|
||||
private maxItemsSyncLimit: number,
|
||||
private vaultUsersRepository: VaultUserRepositoryInterface,
|
||||
private vaultService: VaultServiceInterface,
|
||||
private logger: Logger,
|
||||
) {}
|
||||
|
||||
@@ -97,6 +99,8 @@ export class ItemService implements ItemServiceInterface {
|
||||
const savedItems: Array<Item> = []
|
||||
const conflicts: Array<ItemConflict> = []
|
||||
|
||||
await this.dynamicallyCreateVaultsBasedOnIncomingItems(dto)
|
||||
|
||||
const lastUpdatedTimestamp = this.timer.getTimestampInMicroseconds()
|
||||
|
||||
for (const itemHash of dto.itemHashes) {
|
||||
@@ -183,6 +187,30 @@ export class ItemService implements ItemServiceInterface {
|
||||
return retrievedItems
|
||||
}
|
||||
|
||||
private async dynamicallyCreateVaultsBasedOnIncomingItems(dto: SaveItemsDTO): Promise<void> {
|
||||
const processedVaultUuids = new Set()
|
||||
|
||||
for (const itemHash of dto.itemHashes) {
|
||||
if (!itemHash.vault_uuid || processedVaultUuids.has(itemHash.vault_uuid)) {
|
||||
continue
|
||||
}
|
||||
|
||||
processedVaultUuids.add(itemHash.vault_uuid)
|
||||
|
||||
const vaultItemsKeyUuid =
|
||||
itemHash.content_type === ContentType.VaultItemsKey ? itemHash.uuid : itemHash.items_key_id
|
||||
if (!vaultItemsKeyUuid) {
|
||||
continue
|
||||
}
|
||||
|
||||
await this.vaultService.createVault({
|
||||
userUuid: dto.userUuid,
|
||||
vaultUuid: itemHash.vault_uuid,
|
||||
specifiedItemsKeyUuid: vaultItemsKeyUuid,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private calculateSyncToken(lastUpdatedTimestamp: number, savedItems: Array<Item>): string {
|
||||
if (savedItems.length) {
|
||||
const sortedItems = savedItems.sort((itemA: Item, itemB: Item) => {
|
||||
|
||||
@@ -64,7 +64,7 @@ export class VaultFilter implements ItemSaveRuleInterface {
|
||||
) {
|
||||
return {
|
||||
type: 'move-to-other-vault',
|
||||
sourceVaultUuid: existingItemVaultUuid,
|
||||
vaultUuid: existingItemVaultUuid,
|
||||
targetVaultUuid: targetItemVaultUuid,
|
||||
existingItem: dto.existingItem,
|
||||
...common,
|
||||
@@ -195,7 +195,7 @@ export class VaultFilter implements ItemSaveRuleInterface {
|
||||
}
|
||||
|
||||
private async handleMoveToOtherVaultOperation(operation: MoveToOtherVaultSaveOperation): Promise<ItemSaveRuleResult> {
|
||||
const sourceVaultPermissions = await this.getVaultPermissions(operation.userUuid, operation.sourceVaultUuid)
|
||||
const sourceVaultPermissions = await this.getVaultPermissions(operation.userUuid, operation.vaultUuid)
|
||||
const targetVaultPermissions = await this.getVaultPermissions(operation.userUuid, operation.targetVaultUuid)
|
||||
|
||||
if (!sourceVaultPermissions || !targetVaultPermissions) {
|
||||
|
||||
@@ -20,7 +20,7 @@ export type RemoveFromVaultSaveOperation = SaveOperationCommon & {
|
||||
|
||||
export type MoveToOtherVaultSaveOperation = SaveOperationCommon & {
|
||||
type: 'move-to-other-vault'
|
||||
sourceVaultUuid: string
|
||||
vaultUuid: string
|
||||
targetVaultUuid: string
|
||||
existingItem: Item
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ export class VaultFactory implements VaultFactoryInterface {
|
||||
newVault.uuid = dto.vaultHash.uuid
|
||||
newVault.userUuid = dto.userUuid
|
||||
newVault.specifiedItemsKeyUuid = dto.vaultHash.specified_items_key_uuid
|
||||
newVault.vaultKeyTimestamp = dto.vaultHash.vault_key_timestamp
|
||||
newVault.fileUploadBytesUsed = dto.vaultHash.file_upload_bytes_used
|
||||
newVault.fileUploadBytesLimit = dto.vaultHash.file_upload_bytes_limit
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ export type VaultHash = {
|
||||
uuid: string
|
||||
user_uuid: string
|
||||
specified_items_key_uuid: string
|
||||
vault_key_timestamp: number
|
||||
file_upload_bytes_used: number
|
||||
file_upload_bytes_limit: number
|
||||
created_at_timestamp?: number
|
||||
|
||||
@@ -18,12 +18,6 @@ export class Vault {
|
||||
})
|
||||
declare specifiedItemsKeyUuid: string
|
||||
|
||||
@Column({
|
||||
name: 'vault_key_timestamp',
|
||||
type: 'bigint',
|
||||
})
|
||||
declare vaultKeyTimestamp: number
|
||||
|
||||
@Column({
|
||||
name: 'file_upload_bytes_used',
|
||||
type: 'bigint',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
import { Vault } from '../Model/Vault'
|
||||
import { VaultsRepositoryInterface } from '../Repository/VaultRepositoryInterface'
|
||||
import { CreateVaultDTO, VaultServiceInterface, UpdateVaultDTO } from './VaultServiceInterface'
|
||||
import { CreateVaultDTO, VaultServiceInterface, UpdateVaultDTO, CreateVaultResult } from './VaultServiceInterface'
|
||||
import { VaultFactoryInterface } from '../Factory/VaultFactoryInterface'
|
||||
import { VaultUserServiceInterface } from '../../VaultUser/Service/VaultUserServiceInterface'
|
||||
import { VaultInviteServiceInterface } from '../../VaultInvite/Service/VaultInviteServiceInterface'
|
||||
@@ -15,7 +15,7 @@ export class VaultService implements VaultServiceInterface {
|
||||
private timer: TimerInterface,
|
||||
) {}
|
||||
|
||||
async createVault(dto: CreateVaultDTO): Promise<Vault | null> {
|
||||
async createVault(dto: CreateVaultDTO): Promise<CreateVaultResult | null> {
|
||||
const existingVault = await this.vaultRepository.findByUuid(dto.vaultUuid)
|
||||
if (existingVault) {
|
||||
return null
|
||||
@@ -28,7 +28,6 @@ export class VaultService implements VaultServiceInterface {
|
||||
uuid: dto.vaultUuid,
|
||||
user_uuid: dto.userUuid,
|
||||
specified_items_key_uuid: dto.specifiedItemsKeyUuid,
|
||||
vault_key_timestamp: dto.vaultKeyTimestamp,
|
||||
file_upload_bytes_limit: 1_000_000,
|
||||
file_upload_bytes_used: 0,
|
||||
created_at_timestamp: timestamp,
|
||||
@@ -38,7 +37,13 @@ export class VaultService implements VaultServiceInterface {
|
||||
|
||||
const savedVault = await this.vaultRepository.create(vault)
|
||||
|
||||
return savedVault
|
||||
const vaultUser = await this.vaultUserService.addVaultUser({
|
||||
vaultUuid: savedVault.uuid,
|
||||
userUuid: dto.userUuid,
|
||||
permissions: 'admin',
|
||||
})
|
||||
|
||||
return { vault, vaultUser }
|
||||
}
|
||||
|
||||
async getVault(dto: { vaultUuid: string }): Promise<Vault | null> {
|
||||
@@ -67,12 +72,7 @@ export class VaultService implements VaultServiceInterface {
|
||||
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)
|
||||
|
||||
@@ -1,21 +1,25 @@
|
||||
import { VaultUser } from './../../VaultUser/Model/VaultUser'
|
||||
import { Vault } from '../Model/Vault'
|
||||
|
||||
export type CreateVaultDTO = {
|
||||
userUuid: string
|
||||
vaultUuid: string
|
||||
vaultKeyTimestamp: number
|
||||
specifiedItemsKeyUuid: string
|
||||
}
|
||||
|
||||
export type CreateVaultResult = {
|
||||
vault: Vault
|
||||
vaultUser: VaultUser
|
||||
}
|
||||
|
||||
export type UpdateVaultDTO = {
|
||||
originatorUuid: string
|
||||
vaultUuid: string
|
||||
vaultKeyTimestamp: number
|
||||
specifiedItemsKeyUuid: string
|
||||
}
|
||||
|
||||
export interface VaultServiceInterface {
|
||||
createVault(dto: CreateVaultDTO): Promise<Vault | null>
|
||||
createVault(dto: CreateVaultDTO): Promise<CreateVaultResult | null>
|
||||
|
||||
updateVault(dto: UpdateVaultDTO): Promise<Vault | null>
|
||||
|
||||
|
||||
@@ -22,10 +22,10 @@ export class VaultUserService implements VaultUserServiceInterface {
|
||||
vaultUuid: string
|
||||
userUuid: string
|
||||
permissions: VaultUserPermission
|
||||
}): Promise<VaultUser | null> {
|
||||
}): Promise<VaultUser> {
|
||||
const vault = await this.vaultRepository.findByUuid(dto.vaultUuid)
|
||||
if (!vault) {
|
||||
return null
|
||||
throw new Error('Attempting to add a vault user to a non-existent vault')
|
||||
}
|
||||
|
||||
await this.removedVaultUserService.deleteRemovedVaultUser({
|
||||
|
||||
@@ -3,11 +3,7 @@ import { VaultUserPermission } from '../Model/VaultUserPermission'
|
||||
import { GetVaultUsersDTO } from './GetVaultUsersDTO'
|
||||
|
||||
export interface VaultUserServiceInterface {
|
||||
addVaultUser(dto: {
|
||||
vaultUuid: string
|
||||
userUuid: string
|
||||
permissions: VaultUserPermission
|
||||
}): Promise<VaultUser | null>
|
||||
addVaultUser(dto: { vaultUuid: string; userUuid: string; permissions: VaultUserPermission }): Promise<VaultUser>
|
||||
|
||||
getAllVaultUsersForUser(dto: GetVaultUsersDTO): Promise<VaultUser[]>
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ export type VaultProjection = {
|
||||
uuid: string
|
||||
user_uuid: string
|
||||
specified_items_key_uuid: string
|
||||
vault_key_timestamp: number
|
||||
file_upload_bytes_used: number
|
||||
file_upload_bytes_limit: number
|
||||
created_at_timestamp: number
|
||||
|
||||
@@ -21,7 +21,6 @@ export class VaultProjector implements ProjectorInterface<Vault, VaultProjection
|
||||
uuid: vault.uuid,
|
||||
user_uuid: vault.userUuid,
|
||||
specified_items_key_uuid: vault.specifiedItemsKeyUuid,
|
||||
vault_key_timestamp: vault.vaultKeyTimestamp,
|
||||
file_upload_bytes_used: vault.fileUploadBytesUsed,
|
||||
file_upload_bytes_limit: vault.fileUploadBytesLimit,
|
||||
created_at_timestamp: vault.createdAtTimestamp,
|
||||
|
||||
Reference in New Issue
Block a user