mirror of
https://github.com/standardnotes/server
synced 2026-10-21 15:03:42 -04:00
refactor(wip): vault system identifier
This commit is contained in:
@@ -3,7 +3,7 @@ export enum ContentType {
|
||||
Any = '*',
|
||||
Item = 'SF|Item',
|
||||
VaultItemsKey = 'SN|VaultItemsKey',
|
||||
VaultKey = 'SN|VaultKey',
|
||||
VaultKeyCopy = 'SN|VaultKeyCopy',
|
||||
TrustedContact = 'SN|TrustedContact',
|
||||
RootKey = 'SN|RootKey|NoSync',
|
||||
ItemsKey = 'SN|ItemsKey',
|
||||
|
||||
@@ -44,7 +44,7 @@ export class CreateGroupInvites1684528646896 implements MigrationInterface {
|
||||
isNullable: false,
|
||||
}),
|
||||
new TableColumn({
|
||||
name: 'encrypted_group_data',
|
||||
name: 'encrypted_vault_key_content',
|
||||
type: 'text',
|
||||
isNullable: false,
|
||||
}),
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { GetGlobalItem } from './../Domain/UseCase/GetGlobalItem/GetGlobalItem'
|
||||
import { ContactFactory } from './../Domain/Contact/Factory/ContactFactory'
|
||||
|
||||
import { GroupServiceInterface } from '../Domain/Group/Service/GroupServiceInterface'
|
||||
@@ -114,9 +113,6 @@ export class ServerContainerConfigLoader extends CommonContainerConfigLoader {
|
||||
container.bind<GetItem>(TYPES.GetItem).toDynamicValue((context: interfaces.Context) => {
|
||||
return new GetItem(context.container.get(TYPES.ItemRepository))
|
||||
})
|
||||
container.bind<GetGlobalItem>(TYPES.GetGlobalItem).toDynamicValue((context: interfaces.Context) => {
|
||||
return new GetGlobalItem(context.container.get(TYPES.ItemRepository))
|
||||
})
|
||||
|
||||
container
|
||||
.bind<CreateGroupFileValetToken>(TYPES.CreateGroupFileReadValetToken)
|
||||
@@ -148,7 +144,6 @@ export class ServerContainerConfigLoader extends CommonContainerConfigLoader {
|
||||
context.container.get(TYPES.ItemProjector),
|
||||
context.container.get(TYPES.MAX_ITEMS_LIMIT),
|
||||
context.container.get(TYPES.GroupUserRepository),
|
||||
context.container.get(TYPES.GroupService),
|
||||
context.container.get(TYPES.Logger),
|
||||
)
|
||||
})
|
||||
@@ -159,6 +154,7 @@ export class ServerContainerConfigLoader extends CommonContainerConfigLoader {
|
||||
context.container.get(TYPES.GroupFactory),
|
||||
context.container.get(TYPES.GroupUserService),
|
||||
context.container.get(TYPES.GroupInviteService),
|
||||
context.container.get(TYPES.ItemService),
|
||||
context.container.get(TYPES.Timer),
|
||||
)
|
||||
})
|
||||
|
||||
@@ -74,7 +74,6 @@ const TYPES = {
|
||||
SyncItems: Symbol.for('SyncItems'),
|
||||
CheckIntegrity: Symbol.for('CheckIntegrity'),
|
||||
GetItem: Symbol.for('GetItem'),
|
||||
GetGlobalItem: Symbol.for('GetGlobalItem'),
|
||||
CreateGroupFileReadValetToken: Symbol.for('CreateGroupFileReadValetToken'),
|
||||
// Handlers
|
||||
UserCredentialsChangedEventHandler: Symbol.for('UserCredentialsChangedEventHandler'),
|
||||
|
||||
@@ -32,7 +32,7 @@ export class GroupInvitesController extends BaseHttpController {
|
||||
groupUuid: request.params.groupUuid,
|
||||
userUuid: request.body.invitee_uuid,
|
||||
inviterPublicKey: request.body.inviter_public_key,
|
||||
encryptedGroupData: request.body.encrypted_group_data,
|
||||
encryptedVaultKeyContent: request.body.encrypted_vault_key_content,
|
||||
inviteType: request.body.invite_type,
|
||||
permissions: request.body.permissions,
|
||||
})
|
||||
@@ -53,7 +53,7 @@ export class GroupInvitesController extends BaseHttpController {
|
||||
originatorUuid: response.locals.user.uuid,
|
||||
inviteUuid: request.params.inviteUuid,
|
||||
inviterPublicKey: request.body.inviter_public_key,
|
||||
encryptedGroupData: request.body.encrypted_group_data,
|
||||
encryptedVaultKeyContent: request.body.encrypted_vault_key_content,
|
||||
permissions: request.body.permissions,
|
||||
})
|
||||
|
||||
|
||||
@@ -18,6 +18,8 @@ import { GroupProjection } from '../Projection/GroupProjection'
|
||||
import { GroupUserProjection } from '../Projection/GroupUserProjection'
|
||||
import { CreateGroupFileValetToken } from '../Domain/UseCase/CreateGroupFileValetToken/CreateGroupFileValetToken'
|
||||
import { RemovedGroupUserServiceInterface } from '../Domain/RemovedGroupUser/Service/RemovedGroupUserServiceInterface'
|
||||
import { Item } from '../Domain/Item/Item'
|
||||
import { ItemProjection } from '../Projection/ItemProjection'
|
||||
|
||||
@controller('/groups')
|
||||
export class GroupsController extends BaseHttpController {
|
||||
@@ -27,6 +29,7 @@ export class GroupsController extends BaseHttpController {
|
||||
@inject(TYPES.GroupProjector) private groupProjector: ProjectorInterface<Group, GroupProjection>,
|
||||
@inject(TYPES.GroupUserProjector) private groupUserProjector: ProjectorInterface<GroupUser, GroupUserProjection>,
|
||||
@inject(TYPES.CreateGroupFileReadValetToken) private createGroupFileReadValetToken: CreateGroupFileValetToken,
|
||||
@inject(TYPES.ItemProjector) private itemProjector: ProjectorInterface<Item, ItemProjection>,
|
||||
) {
|
||||
super()
|
||||
}
|
||||
@@ -42,36 +45,11 @@ export class GroupsController extends BaseHttpController {
|
||||
})
|
||||
}
|
||||
|
||||
@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,
|
||||
operation: request.body.operation,
|
||||
unencryptedFileSize: request.body.unencrypted_file_size,
|
||||
moveOperationType: request.body.move_operation_type,
|
||||
groupToGroupMoveTargetUuid: request.body.group_to_group_move_target_uuid,
|
||||
})
|
||||
|
||||
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,
|
||||
vaultSystemIdentifier: request.body.vault_system_identifier,
|
||||
specifiedItemsKeyUuid: request.body.specified_items_key_uuid,
|
||||
})
|
||||
|
||||
@@ -116,6 +94,46 @@ export class GroupsController extends BaseHttpController {
|
||||
return this.json({ success: true })
|
||||
}
|
||||
|
||||
@httpPost('/:groupUuid/items', TYPES.AuthMiddleware)
|
||||
public async addItemToGroup(
|
||||
request: Request,
|
||||
response: Response,
|
||||
): Promise<results.NotFoundResult | results.JsonResult> {
|
||||
const result = await this.groupService.addItemToGroup({
|
||||
groupUuid: request.params.groupUuid,
|
||||
itemUuid: request.body.item_uuid,
|
||||
originatorUuid: response.locals.user.uuid,
|
||||
})
|
||||
|
||||
if (!result) {
|
||||
return this.errorResponse(400, 'Could not add item to group')
|
||||
}
|
||||
|
||||
return this.json({
|
||||
item: this.itemProjector.projectFull(result),
|
||||
})
|
||||
}
|
||||
|
||||
@httpDelete('/:groupUuid/items', TYPES.AuthMiddleware)
|
||||
public async removeItemFromGroup(
|
||||
request: Request,
|
||||
response: Response,
|
||||
): Promise<results.NotFoundResult | results.JsonResult> {
|
||||
const result = await this.groupService.removeItemFromGroup({
|
||||
groupUuid: request.params.groupUuid,
|
||||
itemUuid: request.body.item_uuid,
|
||||
originatorUuid: response.locals.user.uuid,
|
||||
})
|
||||
|
||||
if (!result) {
|
||||
return this.errorResponse(400, 'Could not remove item from group')
|
||||
}
|
||||
|
||||
return this.json({
|
||||
item: this.itemProjector.projectFull(result),
|
||||
})
|
||||
}
|
||||
|
||||
@httpGet('/removed', TYPES.AuthMiddleware)
|
||||
public async getAllRemovedFromGroupsForCurrentUser(
|
||||
_request: Request,
|
||||
@@ -139,6 +157,31 @@ export class GroupsController extends BaseHttpController {
|
||||
})
|
||||
}
|
||||
|
||||
@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,
|
||||
operation: request.body.operation,
|
||||
unencryptedFileSize: request.body.unencrypted_file_size,
|
||||
moveOperationType: request.body.move_operation_type,
|
||||
groupToGroupMoveTargetUuid: request.body.group_to_group_move_target_uuid,
|
||||
})
|
||||
|
||||
if (valetTokenResult.success === false) {
|
||||
return this.errorResponse(400, `Failed to create group valet token: ${valetTokenResult.reason}`)
|
||||
}
|
||||
|
||||
return this.json({
|
||||
valetToken: valetTokenResult.valetToken,
|
||||
})
|
||||
}
|
||||
|
||||
private errorResponse(status: number, message?: string, tag?: string) {
|
||||
return this.json(
|
||||
{
|
||||
|
||||
@@ -11,6 +11,7 @@ export class GroupFactory implements GroupFactoryInterface {
|
||||
newGroup.uuid = dto.groupHash.uuid
|
||||
newGroup.userUuid = dto.userUuid
|
||||
newGroup.specifiedItemsKeyUuid = dto.groupHash.specified_items_key_uuid
|
||||
newGroup.vaultSystemIdentifier = dto.groupHash.vault_system_identifier
|
||||
newGroup.fileUploadBytesUsed = dto.groupHash.file_upload_bytes_used
|
||||
newGroup.fileUploadBytesLimit = dto.groupHash.file_upload_bytes_limit
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ export type GroupHash = {
|
||||
uuid: string
|
||||
user_uuid: string
|
||||
specified_items_key_uuid: string
|
||||
vault_system_identifier: string
|
||||
file_upload_bytes_used: number
|
||||
file_upload_bytes_limit: number
|
||||
created_at_timestamp?: number
|
||||
|
||||
@@ -18,6 +18,12 @@ export class Group {
|
||||
})
|
||||
declare specifiedItemsKeyUuid: string
|
||||
|
||||
@Column({
|
||||
name: 'vault_system_identifier',
|
||||
length: 36,
|
||||
})
|
||||
declare vaultSystemIdentifier: string
|
||||
|
||||
@Column({
|
||||
name: 'file_upload_bytes_used',
|
||||
type: 'bigint',
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { ItemServiceInterface } from './../../Item/ItemServiceInterface'
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
import { Group } from '../Model/Group'
|
||||
import { GroupsRepositoryInterface } from '../Repository/GroupRepositoryInterface'
|
||||
@@ -5,6 +6,8 @@ import { CreateGroupDTO, GroupServiceInterface, UpdateGroupDTO, CreateGroupResul
|
||||
import { GroupFactoryInterface } from '../Factory/GroupFactoryInterface'
|
||||
import { GroupUserServiceInterface } from '../../GroupUser/Service/GroupUserServiceInterface'
|
||||
import { GroupInviteServiceInterface } from '../../GroupInvite/Service/GroupInviteServiceInterface'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { Item } from '../../Item/Item'
|
||||
|
||||
export class GroupService implements GroupServiceInterface {
|
||||
constructor(
|
||||
@@ -12,21 +15,18 @@ export class GroupService implements GroupServiceInterface {
|
||||
private groupFactory: GroupFactoryInterface,
|
||||
private groupUserService: GroupUserServiceInterface,
|
||||
private groupInviteService: GroupInviteServiceInterface,
|
||||
private itemService: ItemServiceInterface,
|
||||
private timer: TimerInterface,
|
||||
) {}
|
||||
|
||||
async createGroup(dto: CreateGroupDTO): Promise<CreateGroupResult | null> {
|
||||
const existingGroup = await this.groupRepository.findByUuid(dto.groupUuid)
|
||||
if (existingGroup) {
|
||||
return null
|
||||
}
|
||||
|
||||
async createGroup(dto: CreateGroupDTO): Promise<CreateGroupResult> {
|
||||
const timestamp = this.timer.getTimestampInMicroseconds()
|
||||
const group = this.groupFactory.create({
|
||||
userUuid: dto.userUuid,
|
||||
groupHash: {
|
||||
uuid: dto.groupUuid,
|
||||
uuid: uuidv4(),
|
||||
user_uuid: dto.userUuid,
|
||||
vault_system_identifier: dto.vaultSystemIdentifier,
|
||||
specified_items_key_uuid: dto.specifiedItemsKeyUuid,
|
||||
file_upload_bytes_limit: 1_000_000,
|
||||
file_upload_bytes_used: 0,
|
||||
@@ -87,10 +87,12 @@ export class GroupService implements GroupServiceInterface {
|
||||
}
|
||||
|
||||
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,
|
||||
@@ -98,4 +100,46 @@ export class GroupService implements GroupServiceInterface {
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
async addItemToGroup(dto: { itemUuid: string; groupUuid: string; originatorUuid: string }): Promise<Item | null> {
|
||||
const item = await this.itemService.getItem({ itemUuid: dto.itemUuid, userUuid: dto.originatorUuid })
|
||||
if (!item) {
|
||||
return null
|
||||
}
|
||||
|
||||
const groupUser = await this.groupUserService.getUserForGroup({
|
||||
userUuid: dto.originatorUuid,
|
||||
groupUuid: dto.groupUuid,
|
||||
})
|
||||
|
||||
if (!groupUser || groupUser.permissions === 'read') {
|
||||
return null
|
||||
}
|
||||
|
||||
item.groupUuid = dto.groupUuid
|
||||
return this.itemService.saveItem(item)
|
||||
}
|
||||
|
||||
async removeItemFromGroup(dto: {
|
||||
itemUuid: string
|
||||
groupUuid: string
|
||||
originatorUuid: string
|
||||
}): Promise<Item | null> {
|
||||
const item = await this.itemService.getItem({ itemUuid: dto.itemUuid, userUuid: dto.originatorUuid })
|
||||
if (!item) {
|
||||
return null
|
||||
}
|
||||
|
||||
const groupUser = await this.groupUserService.getUserForGroup({
|
||||
userUuid: dto.originatorUuid,
|
||||
groupUuid: dto.groupUuid,
|
||||
})
|
||||
|
||||
if (!groupUser || groupUser.permissions === 'read') {
|
||||
return null
|
||||
}
|
||||
|
||||
item.groupUuid = null
|
||||
return this.itemService.saveItem(item)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { GroupUser } from './../../GroupUser/Model/GroupUser'
|
||||
import { Group } from '../Model/Group'
|
||||
import { Item } from '../../Item/Item'
|
||||
|
||||
export type CreateGroupDTO = {
|
||||
userUuid: string
|
||||
groupUuid: string
|
||||
specifiedItemsKeyUuid: string
|
||||
vaultSystemIdentifier: string
|
||||
}
|
||||
|
||||
export type CreateGroupResult = {
|
||||
@@ -19,13 +20,13 @@ export type UpdateGroupDTO = {
|
||||
}
|
||||
|
||||
export interface GroupServiceInterface {
|
||||
createGroup(dto: CreateGroupDTO): Promise<CreateGroupResult | null>
|
||||
|
||||
createGroup(dto: CreateGroupDTO): Promise<CreateGroupResult>
|
||||
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[]>
|
||||
|
||||
addItemToGroup(dto: { itemUuid: string; groupUuid: string; originatorUuid: string }): Promise<Item | null>
|
||||
removeItemFromGroup(dto: { itemUuid: string; groupUuid: string; originatorUuid: string }): Promise<Item | null>
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ export class GroupInviteFactory implements GroupInviteFactoryInterface {
|
||||
newGroupInvite.groupUuid = dto.group_uuid
|
||||
newGroupInvite.inviterUuid = dto.inviter_uuid
|
||||
newGroupInvite.inviterPublicKey = dto.inviter_public_key
|
||||
newGroupInvite.encryptedGroupData = dto.encrypted_group_data
|
||||
newGroupInvite.encryptedVaultKeyContent = dto.encrypted_vault_key_content
|
||||
newGroupInvite.inviteType = dto.invite_type
|
||||
newGroupInvite.permissions = dto.permissions
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ export type GroupInviteHash = {
|
||||
group_uuid: string
|
||||
inviter_uuid: string
|
||||
inviter_public_key: string
|
||||
encrypted_group_data: string
|
||||
encrypted_vault_key_content: string
|
||||
invite_type: GroupInviteType
|
||||
permissions: GroupUserPermission
|
||||
created_at_timestamp?: number
|
||||
|
||||
@@ -35,9 +35,9 @@ export class GroupInvite {
|
||||
declare inviterPublicKey: string
|
||||
|
||||
@Column({
|
||||
name: 'encrypted_group_data',
|
||||
name: 'encrypted_vault_key_content',
|
||||
})
|
||||
declare encryptedGroupData: string
|
||||
declare encryptedVaultKeyContent: string
|
||||
|
||||
@Column({
|
||||
name: 'permissions',
|
||||
|
||||
@@ -6,7 +6,7 @@ export type CreateInviteDTO = {
|
||||
groupUuid: string
|
||||
userUuid: string
|
||||
inviterPublicKey: string
|
||||
encryptedGroupData: string
|
||||
encryptedVaultKeyContent: string
|
||||
inviteType: GroupInviteType
|
||||
permissions: GroupUserPermission
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ export class GroupInviteService implements GroupInviteServiceInterface {
|
||||
group_uuid: dto.groupUuid,
|
||||
inviter_uuid: dto.originatorUuid,
|
||||
inviter_public_key: dto.inviterPublicKey,
|
||||
encrypted_group_data: dto.encryptedGroupData,
|
||||
encrypted_vault_key_content: dto.encryptedVaultKeyContent,
|
||||
invite_type: dto.inviteType,
|
||||
permissions: dto.permissions,
|
||||
created_at_timestamp: timestamp,
|
||||
@@ -51,7 +51,7 @@ export class GroupInviteService implements GroupInviteServiceInterface {
|
||||
}
|
||||
|
||||
groupInvite.inviterPublicKey = dto.inviterPublicKey
|
||||
groupInvite.encryptedGroupData = dto.encryptedGroupData
|
||||
groupInvite.encryptedVaultKeyContent = dto.encryptedVaultKeyContent
|
||||
if (dto.permissions) {
|
||||
groupInvite.permissions = dto.permissions
|
||||
}
|
||||
|
||||
+4
-11
@@ -5,22 +5,15 @@ 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>
|
||||
|
||||
getInvitesForUser(dto: GetUserGroupInvitesDTO): Promise<GroupInvite[]>
|
||||
getInvitesForGroup(dto: { groupUuid: string; originatorUuid: string }): Promise<GroupInvite[] | undefined>
|
||||
getOutboundInvitesForUser(dto: { userUuid: string }): Promise<GroupInvite[]>
|
||||
|
||||
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>
|
||||
}
|
||||
|
||||
@@ -4,6 +4,6 @@ export type UpdateInviteDTO = {
|
||||
originatorUuid: string
|
||||
inviteUuid: string
|
||||
inviterPublicKey: string
|
||||
encryptedGroupData: string
|
||||
encryptedVaultKeyContent: string
|
||||
permissions?: GroupUserPermission
|
||||
}
|
||||
|
||||
@@ -13,22 +13,6 @@ export class Item {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
declare uuid: string
|
||||
|
||||
@Column({
|
||||
type: 'varchar',
|
||||
name: 'group_uuid',
|
||||
length: 255,
|
||||
nullable: true,
|
||||
})
|
||||
declare groupUuid: string | null
|
||||
|
||||
@Column({
|
||||
type: 'varchar',
|
||||
name: 'vault_identifier',
|
||||
length: 255,
|
||||
nullable: true,
|
||||
})
|
||||
declare vault_identifier: string | null
|
||||
|
||||
@Column({
|
||||
type: 'varchar',
|
||||
name: 'duplicate_of',
|
||||
@@ -90,18 +74,6 @@ export class Item {
|
||||
@Index('index_items_on_user_uuid')
|
||||
declare userUuid: string
|
||||
|
||||
@Column({
|
||||
name: 'last_edited_by_uuid',
|
||||
length: 36,
|
||||
})
|
||||
declare lastEditedByUuid: string
|
||||
|
||||
@Column({
|
||||
name: 'created_by_uuid',
|
||||
length: 36,
|
||||
})
|
||||
declare createdByUuid: string
|
||||
|
||||
@Column({
|
||||
type: 'tinyint',
|
||||
precision: 1,
|
||||
@@ -111,6 +83,28 @@ export class Item {
|
||||
@Index('index_items_on_deleted')
|
||||
declare deleted: boolean
|
||||
|
||||
@Column({
|
||||
type: 'varchar',
|
||||
name: 'vault_system_identifier',
|
||||
length: 255,
|
||||
nullable: true,
|
||||
})
|
||||
declare vaultSystemIdentifier: string | null
|
||||
|
||||
@Column({
|
||||
type: 'varchar',
|
||||
name: 'group_uuid',
|
||||
length: 255,
|
||||
nullable: true,
|
||||
})
|
||||
declare groupUuid: string | null
|
||||
|
||||
@Column({
|
||||
name: 'last_edited_by_uuid',
|
||||
length: 36,
|
||||
})
|
||||
declare lastEditedByUuid: string
|
||||
|
||||
@Column({
|
||||
name: 'created_at',
|
||||
type: 'datetime',
|
||||
|
||||
@@ -34,8 +34,8 @@ export class ItemFactory implements ItemFactoryInterface {
|
||||
newItem.contentSize = 0
|
||||
newItem.createdByUuid = dto.userUuid
|
||||
|
||||
if (dto.itemHash.group_uuid) {
|
||||
newItem.groupUuid = dto.itemHash.group_uuid
|
||||
if (dto.itemHash.vault_system_identifier) {
|
||||
newItem.groupUuid = dto.itemHash.vault_system_identifier
|
||||
} else {
|
||||
newItem.userUuid = dto.userUuid
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ export type ItemHash = {
|
||||
auth_hash?: string
|
||||
enc_item_key?: string
|
||||
items_key_id?: string
|
||||
group_uuid?: string
|
||||
vault_system_identifier?: string
|
||||
last_edited_by_uuid?: string | null
|
||||
created_at?: string
|
||||
created_at_timestamp?: number
|
||||
|
||||
@@ -21,7 +21,6 @@ import { ProjectorInterface } from '../../Projection/ProjectorInterface'
|
||||
import { ItemProjection } from '../../Projection/ItemProjection'
|
||||
import { GroupUserRepositoryInterface } from '../GroupUser/Repository/GroupUserRepositoryInterface'
|
||||
import { ConflictType } from '../../Tmp/ConflictType'
|
||||
import { GroupServiceInterface } from '../Group/Service/GroupServiceInterface'
|
||||
|
||||
export class ItemService implements ItemServiceInterface {
|
||||
private readonly DEFAULT_ITEMS_LIMIT = 150
|
||||
@@ -40,7 +39,6 @@ export class ItemService implements ItemServiceInterface {
|
||||
private itemProjector: ProjectorInterface<Item, ItemProjection>,
|
||||
private maxItemsSyncLimit: number,
|
||||
private groupUsersRepository: GroupUserRepositoryInterface,
|
||||
private groupService: GroupServiceInterface,
|
||||
private logger: Logger,
|
||||
) {}
|
||||
|
||||
@@ -99,8 +97,6 @@ export class ItemService implements ItemServiceInterface {
|
||||
const savedItems: Array<Item> = []
|
||||
const conflicts: Array<ItemConflict> = []
|
||||
|
||||
await this.dynamicallyCreateGroupsBasedOnIncomingItems(dto)
|
||||
|
||||
const lastUpdatedTimestamp = this.timer.getTimestampInMicroseconds()
|
||||
|
||||
for (const itemHash of dto.itemHashes) {
|
||||
@@ -187,30 +183,6 @@ export class ItemService implements ItemServiceInterface {
|
||||
return retrievedItems
|
||||
}
|
||||
|
||||
private async dynamicallyCreateGroupsBasedOnIncomingItems(dto: SaveItemsDTO): Promise<void> {
|
||||
const processedGroupUuids = new Set()
|
||||
|
||||
for (const itemHash of dto.itemHashes) {
|
||||
if (!itemHash.group_uuid || processedGroupUuids.has(itemHash.group_uuid)) {
|
||||
continue
|
||||
}
|
||||
|
||||
processedGroupUuids.add(itemHash.group_uuid)
|
||||
|
||||
const vaultItemsKeyUuid =
|
||||
itemHash.content_type === ContentType.VaultItemsKey ? itemHash.uuid : itemHash.items_key_id
|
||||
if (!vaultItemsKeyUuid) {
|
||||
continue
|
||||
}
|
||||
|
||||
await this.groupService.createGroup({
|
||||
userUuid: dto.userUuid,
|
||||
groupUuid: itemHash.group_uuid,
|
||||
specifiedItemsKeyUuid: vaultItemsKeyUuid,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private calculateSyncToken(lastUpdatedTimestamp: number, savedItems: Array<Item>): string {
|
||||
if (savedItems.length) {
|
||||
const sortedItems = savedItems.sort((itemA: Item, itemB: Item) => {
|
||||
@@ -238,12 +210,7 @@ export class ItemService implements ItemServiceInterface {
|
||||
dto.existingItem.updatedWithSession = dto.sessionUuid
|
||||
dto.existingItem.contentSize = 0
|
||||
dto.existingItem.lastEditedByUuid = dto.userUuid
|
||||
|
||||
if (dto.itemHash.group_uuid) {
|
||||
dto.existingItem.groupUuid = dto.itemHash.group_uuid
|
||||
} else {
|
||||
dto.existingItem.groupUuid = null
|
||||
}
|
||||
dto.existingItem.vaultSystemIdentifier = dto.itemHash.vault_system_identifier ?? null
|
||||
|
||||
if (dto.itemHash.content) {
|
||||
dto.existingItem.content = dto.itemHash.content
|
||||
@@ -360,4 +327,17 @@ export class ItemService implements ItemServiceInterface {
|
||||
throw Error('Sync token is missing version part')
|
||||
}
|
||||
}
|
||||
|
||||
public async saveItem(item: Item): Promise<Item> {
|
||||
const updatedAt = this.timer.getTimestampInMicroseconds()
|
||||
item.updatedAtTimestamp = updatedAt
|
||||
item.updatedAt = this.timer.convertMicrosecondsToDate(updatedAt)
|
||||
|
||||
const savedItem = await this.itemRepository.save(item)
|
||||
return savedItem
|
||||
}
|
||||
|
||||
public async getItem(dto: { itemUuid: string; userUuid: string }): Promise<Item | null> {
|
||||
return this.itemRepository.findByUuidAndUserUuid(dto.itemUuid, dto.userUuid)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,4 +9,6 @@ export interface ItemServiceInterface {
|
||||
saveItems(dto: SaveItemsDTO): Promise<SaveItemsResult>
|
||||
frontLoadKeysItemsToTop(userUuid: string, retrievedItems: Array<Item>): Promise<Array<Item>>
|
||||
getLastSyncTime(dto: { syncToken?: string | null; cursorToken?: string | null }): number | undefined
|
||||
getItem(dto: { itemUuid: string; userUuid: string }): Promise<Item | null>
|
||||
saveItem(item: Item): Promise<Item>
|
||||
}
|
||||
|
||||
@@ -7,106 +7,69 @@ import { GroupUserPermission } from '../../GroupUser/Model/GroupUserPermission'
|
||||
import { GroupServiceInterface } from '../../Group/Service/GroupServiceInterface'
|
||||
import { ContentType } from '@standardnotes/common'
|
||||
import { ConflictType } from '../../../Tmp/ConflictType'
|
||||
import {
|
||||
GroupSaveOperation,
|
||||
AddToGroupSaveOperation,
|
||||
RemoveFromGroupSaveOperation,
|
||||
MoveToOtherGroupSaveOperation,
|
||||
SaveToGroupSaveOperation,
|
||||
CreateToGroupSaveOperation,
|
||||
} from './GroupSaveOperation'
|
||||
import { Item } from '../Item'
|
||||
|
||||
export class GroupFilter implements ItemSaveRuleInterface {
|
||||
constructor(private groupService: GroupServiceInterface, private groupUserService: GroupUserServiceInterface) {}
|
||||
|
||||
async check(dto: ItemSaveValidationDTO): Promise<ItemSaveRuleResult> {
|
||||
if (!dto.existingItem?.groupUuid && !dto.itemHash.group_uuid) {
|
||||
if (!dto.existingItem?.groupUuid) {
|
||||
return {
|
||||
passed: true,
|
||||
}
|
||||
}
|
||||
|
||||
const operation = this.getSaveOperationType(dto)
|
||||
return this.getResultForOperation(operation)
|
||||
}
|
||||
|
||||
private async getResultForOperation(operation: GroupSaveOperation): Promise<ItemSaveRuleResult> {
|
||||
if (operation.type === 'add-to-group') {
|
||||
return this.handleAddToGroupOperation(operation)
|
||||
} else if (operation.type === 'remove-from-group') {
|
||||
return this.handleRemoveFromGroupOperation(operation)
|
||||
} else if (operation.type === 'move-to-other-group') {
|
||||
return this.handleMoveToOtherGroupOperation(operation)
|
||||
} else if (operation.type === 'save-to-group') {
|
||||
return this.handleSaveToGroupOperation(operation)
|
||||
} else if (operation.type === 'create-to-group') {
|
||||
return this.handleCreateToGroupOperation(operation)
|
||||
}
|
||||
|
||||
throw new Error(`Unsupported group operation: ${operation}`)
|
||||
}
|
||||
|
||||
private getSaveOperationType(dto: ItemSaveValidationDTO): GroupSaveOperation {
|
||||
const existingItemGroupUuid = dto.existingItem?.groupUuid
|
||||
const targetItemGroupUuid = dto.itemHash.group_uuid
|
||||
|
||||
const common = {
|
||||
incomingItem: dto.itemHash,
|
||||
const groupUser = await this.groupUserService.getUserForGroup({
|
||||
userUuid: dto.userUuid,
|
||||
}
|
||||
groupUuid: dto.existingItem.groupUuid,
|
||||
})
|
||||
|
||||
if (
|
||||
dto.existingItem &&
|
||||
existingItemGroupUuid &&
|
||||
targetItemGroupUuid &&
|
||||
existingItemGroupUuid !== targetItemGroupUuid
|
||||
) {
|
||||
if (!groupUser) {
|
||||
return {
|
||||
type: 'move-to-other-group',
|
||||
groupUuid: existingItemGroupUuid,
|
||||
targetGroupUuid: targetItemGroupUuid,
|
||||
existingItem: dto.existingItem,
|
||||
...common,
|
||||
passed: false,
|
||||
conflict: {
|
||||
unsavedItem: dto.itemHash,
|
||||
type: ConflictType.GroupNotMemberError,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if (dto.existingItem && existingItemGroupUuid && !targetItemGroupUuid) {
|
||||
if (groupUser.permissions === 'read') {
|
||||
return {
|
||||
type: 'remove-from-group',
|
||||
groupUuid: existingItemGroupUuid,
|
||||
existingItem: dto.existingItem,
|
||||
...common,
|
||||
passed: false,
|
||||
conflict: {
|
||||
unsavedItem: dto.itemHash,
|
||||
serverItem: dto.existingItem,
|
||||
type: ConflictType.GroupInsufficientPermissionsError,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if (dto.existingItem && !existingItemGroupUuid && targetItemGroupUuid) {
|
||||
if (!this.isAuthorizedToSaveContentType(dto.itemHash.content_type, groupUser.permissions)) {
|
||||
return {
|
||||
type: 'add-to-group',
|
||||
groupUuid: targetItemGroupUuid,
|
||||
existingItem: dto.existingItem,
|
||||
...common,
|
||||
passed: false,
|
||||
conflict: {
|
||||
unsavedItem: dto.itemHash,
|
||||
serverItem: dto.existingItem,
|
||||
type: ConflictType.GroupInsufficientPermissionsError,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if (dto.existingItem && existingItemGroupUuid && existingItemGroupUuid === targetItemGroupUuid) {
|
||||
const usesValidKey = await this.incomingItemUsesValidItemsKey(dto.itemHash)
|
||||
if (!usesValidKey) {
|
||||
return {
|
||||
type: 'save-to-group',
|
||||
groupUuid: existingItemGroupUuid,
|
||||
existingItem: dto.existingItem,
|
||||
...common,
|
||||
passed: false,
|
||||
conflict: {
|
||||
unsavedItem: dto.itemHash,
|
||||
serverItem: dto.existingItem,
|
||||
type: ConflictType.GroupInvalidItemsKey,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if (!dto.existingItem && targetItemGroupUuid) {
|
||||
return {
|
||||
type: 'create-to-group',
|
||||
groupUuid: targetItemGroupUuid,
|
||||
...common,
|
||||
}
|
||||
return {
|
||||
passed: true,
|
||||
}
|
||||
|
||||
throw new Error('Invalid save operation')
|
||||
}
|
||||
|
||||
private isAuthorizedToSaveContentType(contentType: ContentType, permissions: GroupUserPermission): boolean {
|
||||
@@ -117,167 +80,13 @@ export class GroupFilter implements ItemSaveRuleInterface {
|
||||
return true
|
||||
}
|
||||
|
||||
private buildFailResult(operation: GroupSaveOperation, type: ConflictType, serverItem?: Item) {
|
||||
return {
|
||||
passed: false,
|
||||
conflict: {
|
||||
unsavedItem: operation.incomingItem,
|
||||
serverItem,
|
||||
type,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
private buildSuccessValue(): ItemSaveRuleResult {
|
||||
return {
|
||||
passed: true,
|
||||
}
|
||||
}
|
||||
|
||||
private async handleAddToGroupOperation(operation: AddToGroupSaveOperation): Promise<ItemSaveRuleResult> {
|
||||
const groupPermissions = await this.getGroupPermissions(operation.userUuid, operation.groupUuid)
|
||||
if (!groupPermissions) {
|
||||
return this.buildFailResult(operation, ConflictType.GroupNotMemberError)
|
||||
}
|
||||
|
||||
if (operation.existingItem.deleted || operation.incomingItem.deleted) {
|
||||
return this.buildFailResult(operation, ConflictType.GroupInvalidState)
|
||||
}
|
||||
|
||||
if (!this.isAuthorizedToSaveContentType(operation.incomingItem.content_type, groupPermissions)) {
|
||||
return this.buildFailResult(operation, ConflictType.GroupInsufficientPermissionsError)
|
||||
}
|
||||
|
||||
if (groupPermissions === 'read') {
|
||||
return this.buildFailResult(operation, ConflictType.GroupInsufficientPermissionsError)
|
||||
}
|
||||
|
||||
if (operation.existingItem.userUuid !== operation.userUuid) {
|
||||
return this.buildFailResult(operation, ConflictType.UuidConflict)
|
||||
}
|
||||
|
||||
const usesValidKey = await this.incomingItemUsesValidItemsKey(operation.incomingItem)
|
||||
if (!usesValidKey) {
|
||||
return this.buildFailResult(operation, ConflictType.GroupInvalidItemsKey, operation.existingItem)
|
||||
}
|
||||
|
||||
return this.buildSuccessValue()
|
||||
}
|
||||
|
||||
private async handleRemoveFromGroupOperation(operation: RemoveFromGroupSaveOperation): Promise<ItemSaveRuleResult> {
|
||||
const groupPermissions = await this.getGroupPermissions(operation.userUuid, operation.groupUuid)
|
||||
if (!groupPermissions) {
|
||||
return this.buildFailResult(operation, ConflictType.GroupNotMemberError)
|
||||
}
|
||||
|
||||
if (operation.existingItem.deleted || operation.incomingItem.deleted) {
|
||||
return this.buildFailResult(operation, ConflictType.GroupInvalidState)
|
||||
}
|
||||
|
||||
if (operation.existingItem.userUuid === operation.userUuid) {
|
||||
return this.buildSuccessValue()
|
||||
}
|
||||
|
||||
if (!this.isAuthorizedToSaveContentType(operation.incomingItem.content_type, groupPermissions)) {
|
||||
return this.buildFailResult(operation, ConflictType.GroupInsufficientPermissionsError)
|
||||
}
|
||||
|
||||
if (groupPermissions === 'read') {
|
||||
return this.buildFailResult(operation, ConflictType.GroupInsufficientPermissionsError)
|
||||
}
|
||||
|
||||
const usesValidKey = await this.incomingItemUsesValidItemsKey(operation.incomingItem)
|
||||
if (!usesValidKey) {
|
||||
return this.buildFailResult(operation, ConflictType.GroupInvalidItemsKey, operation.existingItem)
|
||||
}
|
||||
|
||||
return this.buildSuccessValue()
|
||||
}
|
||||
|
||||
private async handleMoveToOtherGroupOperation(operation: MoveToOtherGroupSaveOperation): Promise<ItemSaveRuleResult> {
|
||||
const sourceGroupPermissions = await this.getGroupPermissions(operation.userUuid, operation.groupUuid)
|
||||
const targetGroupPermissions = await this.getGroupPermissions(operation.userUuid, operation.targetGroupUuid)
|
||||
|
||||
if (!sourceGroupPermissions || !targetGroupPermissions) {
|
||||
return this.buildFailResult(operation, ConflictType.GroupNotMemberError)
|
||||
}
|
||||
|
||||
if (operation.existingItem.deleted || operation.incomingItem.deleted) {
|
||||
return this.buildFailResult(operation, ConflictType.GroupInvalidState)
|
||||
}
|
||||
|
||||
if (sourceGroupPermissions === 'read' || targetGroupPermissions === 'read') {
|
||||
return this.buildFailResult(operation, ConflictType.GroupInsufficientPermissionsError)
|
||||
}
|
||||
|
||||
if (
|
||||
!this.isAuthorizedToSaveContentType(operation.incomingItem.content_type, sourceGroupPermissions) ||
|
||||
!this.isAuthorizedToSaveContentType(operation.incomingItem.content_type, targetGroupPermissions)
|
||||
) {
|
||||
return this.buildFailResult(operation, ConflictType.GroupInsufficientPermissionsError)
|
||||
}
|
||||
|
||||
const usesValidKey = await this.incomingItemUsesValidItemsKey(operation.incomingItem)
|
||||
if (!usesValidKey) {
|
||||
return this.buildFailResult(operation, ConflictType.GroupInvalidItemsKey, operation.existingItem)
|
||||
}
|
||||
|
||||
return this.buildSuccessValue()
|
||||
}
|
||||
|
||||
private async handleSaveToGroupOperation(operation: SaveToGroupSaveOperation): Promise<ItemSaveRuleResult> {
|
||||
const groupPermissions = await this.getGroupPermissions(operation.userUuid, operation.groupUuid)
|
||||
|
||||
if (!groupPermissions) {
|
||||
return this.buildFailResult(operation, ConflictType.GroupNotMemberError)
|
||||
}
|
||||
|
||||
if (!this.isAuthorizedToSaveContentType(operation.incomingItem.content_type, groupPermissions)) {
|
||||
return this.buildFailResult(operation, ConflictType.GroupInsufficientPermissionsError)
|
||||
}
|
||||
|
||||
if (groupPermissions === 'read') {
|
||||
return this.buildFailResult(operation, ConflictType.GroupInsufficientPermissionsError)
|
||||
}
|
||||
|
||||
const usesValidKey = await this.incomingItemUsesValidItemsKey(operation.incomingItem)
|
||||
if (!usesValidKey) {
|
||||
return this.buildFailResult(operation, ConflictType.GroupInvalidItemsKey, operation.existingItem)
|
||||
}
|
||||
|
||||
return this.buildSuccessValue()
|
||||
}
|
||||
|
||||
private async handleCreateToGroupOperation(operation: CreateToGroupSaveOperation): Promise<ItemSaveRuleResult> {
|
||||
const groupPermissions = await this.getGroupPermissions(operation.userUuid, operation.groupUuid)
|
||||
|
||||
if (!groupPermissions) {
|
||||
return this.buildFailResult(operation, ConflictType.GroupNotMemberError)
|
||||
}
|
||||
|
||||
if (!this.isAuthorizedToSaveContentType(operation.incomingItem.content_type, groupPermissions)) {
|
||||
return this.buildFailResult(operation, ConflictType.GroupInsufficientPermissionsError)
|
||||
}
|
||||
|
||||
if (groupPermissions === 'read') {
|
||||
return this.buildFailResult(operation, ConflictType.GroupInsufficientPermissionsError)
|
||||
}
|
||||
|
||||
const usesValidKey = await this.incomingItemUsesValidItemsKey(operation.incomingItem)
|
||||
if (!usesValidKey) {
|
||||
return this.buildFailResult(operation, ConflictType.GroupInvalidItemsKey)
|
||||
}
|
||||
|
||||
return this.buildSuccessValue()
|
||||
}
|
||||
|
||||
private async incomingItemUsesValidItemsKey(itemHash: ItemHash): Promise<boolean> {
|
||||
const isItemNotEncryptedByItemsKey = itemHash.content_type === ContentType.VaultItemsKey
|
||||
if (isItemNotEncryptedByItemsKey) {
|
||||
return true
|
||||
}
|
||||
|
||||
const group = await this.groupService.getGroup({ groupUuid: itemHash.group_uuid as string })
|
||||
const group = await this.groupService.getGroup({ groupUuid: itemHash.vault_system_identifier as string })
|
||||
|
||||
if (!group) {
|
||||
return false
|
||||
@@ -285,17 +94,4 @@ export class GroupFilter implements ItemSaveRuleInterface {
|
||||
|
||||
return itemHash.items_key_id === group.specifiedItemsKeyUuid
|
||||
}
|
||||
|
||||
private async getGroupPermissions(userUuid: string, groupUuid: string): Promise<GroupUserPermission | undefined> {
|
||||
const groupUser = await this.groupUserService.getUserForGroup({
|
||||
userUuid,
|
||||
groupUuid: groupUuid,
|
||||
})
|
||||
|
||||
if (groupUser) {
|
||||
return groupUser.permissions
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
import { ItemHash } from '../ItemHash'
|
||||
import { Item } from '../Item'
|
||||
|
||||
type SaveOperationCommon = {
|
||||
incomingItem: ItemHash
|
||||
userUuid: string
|
||||
}
|
||||
|
||||
export type AddToGroupSaveOperation = SaveOperationCommon & {
|
||||
type: 'add-to-group'
|
||||
groupUuid: string
|
||||
existingItem: Item
|
||||
}
|
||||
|
||||
export type RemoveFromGroupSaveOperation = SaveOperationCommon & {
|
||||
type: 'remove-from-group'
|
||||
groupUuid: string
|
||||
existingItem: Item
|
||||
}
|
||||
|
||||
export type MoveToOtherGroupSaveOperation = SaveOperationCommon & {
|
||||
type: 'move-to-other-group'
|
||||
groupUuid: string
|
||||
targetGroupUuid: string
|
||||
existingItem: Item
|
||||
}
|
||||
|
||||
export type SaveToGroupSaveOperation = SaveOperationCommon & {
|
||||
type: 'save-to-group'
|
||||
groupUuid: string
|
||||
existingItem: Item
|
||||
}
|
||||
|
||||
export type CreateToGroupSaveOperation = SaveOperationCommon & {
|
||||
type: 'create-to-group'
|
||||
groupUuid: string
|
||||
}
|
||||
|
||||
export type GroupSaveOperation =
|
||||
| AddToGroupSaveOperation
|
||||
| RemoveFromGroupSaveOperation
|
||||
| MoveToOtherGroupSaveOperation
|
||||
| SaveToGroupSaveOperation
|
||||
| CreateToGroupSaveOperation
|
||||
@@ -12,7 +12,7 @@ export class GroupSnjsVersionFilter implements ItemSaveRuleInterface {
|
||||
passed: true,
|
||||
}
|
||||
|
||||
const groupUuidInvolved = dto.existingItem?.groupUuid || dto.itemHash.group_uuid
|
||||
const groupUuidInvolved = dto.existingItem?.groupUuid || dto.itemHash.vault_system_identifier
|
||||
if (!groupUuidInvolved) {
|
||||
return successValue
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import { ConflictType } from '../../../Tmp/ConflictType'
|
||||
|
||||
export class OwnershipFilter implements ItemSaveRuleInterface {
|
||||
async check(dto: ItemSaveValidationDTO): Promise<ItemSaveRuleResult> {
|
||||
const deferToGroupFilter = dto.existingItem?.groupUuid || dto.itemHash.group_uuid
|
||||
const deferToGroupFilter = dto.existingItem?.groupUuid
|
||||
if (deferToGroupFilter) {
|
||||
return {
|
||||
passed: true,
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
import 'reflect-metadata'
|
||||
import { Item } from '../../Item/Item'
|
||||
import { ItemRepositoryInterface } from '../../Item/ItemRepositoryInterface'
|
||||
|
||||
import { GetGlobalItem } from './GetGlobalItem'
|
||||
|
||||
describe('GetGlobalItem', () => {
|
||||
let itemRepository: ItemRepositoryInterface
|
||||
|
||||
const createUseCase = () => new GetGlobalItem(itemRepository)
|
||||
|
||||
beforeEach(() => {
|
||||
itemRepository = {} as jest.Mocked<ItemRepositoryInterface>
|
||||
itemRepository.findByUuid = jest.fn().mockReturnValue(null)
|
||||
})
|
||||
|
||||
it('should fail if item is not found', async () => {
|
||||
expect(
|
||||
await createUseCase().execute({
|
||||
itemUuid: '2-3-4',
|
||||
}),
|
||||
).toEqual({ success: false, message: 'Could not find item with uuid 2-3-4' })
|
||||
})
|
||||
|
||||
it('should succeed if item is found', async () => {
|
||||
const item = {} as jest.Mocked<Item>
|
||||
itemRepository.findByUuid = jest.fn().mockReturnValue(item)
|
||||
|
||||
expect(
|
||||
await createUseCase().execute({
|
||||
itemUuid: '2-3-4',
|
||||
}),
|
||||
).toEqual({ success: true, item })
|
||||
})
|
||||
})
|
||||
@@ -1,24 +0,0 @@
|
||||
import { ItemRepositoryInterface } from '../../Item/ItemRepositoryInterface'
|
||||
import { UseCaseInterface } from '../UseCaseInterface'
|
||||
import { GetGlobalItemDTO } from './GetGlobalItemDTO'
|
||||
import { GetGlobalItemResponse } from './GetGlobalItemResponse'
|
||||
|
||||
export class GetGlobalItem implements UseCaseInterface {
|
||||
constructor(private itemRepository: ItemRepositoryInterface) {}
|
||||
|
||||
async execute(dto: GetGlobalItemDTO): Promise<GetGlobalItemResponse> {
|
||||
const item = await this.itemRepository.findByUuid(dto.itemUuid)
|
||||
|
||||
if (item === null) {
|
||||
return {
|
||||
success: false,
|
||||
message: `Could not find item with uuid ${dto.itemUuid}`,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
item,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
export type GetGlobalItemDTO = {
|
||||
itemUuid: string
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
import { Item } from '../../Item/Item'
|
||||
|
||||
export type GetGlobalItemResponse =
|
||||
| {
|
||||
success: true
|
||||
item: Item
|
||||
}
|
||||
| {
|
||||
success: false
|
||||
message: string
|
||||
}
|
||||
@@ -6,7 +6,7 @@ export type GroupInviteProjection = {
|
||||
user_uuid: string
|
||||
inviter_uuid: string
|
||||
inviter_public_key: string
|
||||
encrypted_group_data: string
|
||||
encrypted_vault_key_content: string
|
||||
invite_type: GroupInviteType
|
||||
permissions: string
|
||||
created_at_timestamp: number
|
||||
|
||||
@@ -23,7 +23,7 @@ export class GroupInviteProjector implements ProjectorInterface<GroupInvite, Gro
|
||||
user_uuid: groupInvite.userUuid,
|
||||
inviter_uuid: groupInvite.inviterUuid,
|
||||
inviter_public_key: groupInvite.inviterPublicKey,
|
||||
encrypted_group_data: groupInvite.encryptedGroupData,
|
||||
encrypted_vault_key_content: groupInvite.encryptedVaultKeyContent,
|
||||
invite_type: groupInvite.inviteType,
|
||||
permissions: groupInvite.permissions,
|
||||
created_at_timestamp: groupInvite.createdAtTimestamp,
|
||||
|
||||
@@ -5,9 +5,10 @@ export enum ConflictType {
|
||||
ContentError = 'content_error',
|
||||
ReadOnlyError = 'readonly_error',
|
||||
UuidError = 'uuid_error',
|
||||
|
||||
SnjsVersionError = 'snjs_version_error',
|
||||
|
||||
GroupInsufficientPermissionsError = 'group_insufficient_permissions_error',
|
||||
GroupNotMemberError = 'group_not_member_error',
|
||||
GroupInvalidState = 'group_invalid_state',
|
||||
GroupInvalidItemsKey = 'group_invalid_items_key',
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user