Implement 'Add Password' and 'Change Permissions' tools in V2 (#4195)

# Description of Changes
Implement Add Password and Change Permissions tools in V2 (both in one
because Change Permissions is a fake endpoint which just calls Add
Password behind the scenes).

---------

Co-authored-by: James <james@crosscourtanalytics.com>
This commit is contained in:
James Brunton
2025-08-14 14:27:23 +01:00
committed by GitHub
parent 0ea4410dd3
commit ecf30d1028
31 changed files with 1936 additions and 88 deletions

View File

@@ -0,0 +1,144 @@
import { describe, expect, test, vi, beforeEach, MockedFunction } from 'vitest';
import { renderHook } from '@testing-library/react';
import { useAddPasswordOperation } from './useAddPasswordOperation';
import type { AddPasswordFullParameters, AddPasswordParameters } from './useAddPasswordParameters';
// Mock the useToolOperation hook
vi.mock('../shared/useToolOperation', () => ({
useToolOperation: vi.fn()
}));
// Mock the translation hook
const mockT = vi.fn((key: string) => `translated-${key}`);
vi.mock('react-i18next', () => ({
useTranslation: () => ({ t: mockT })
}));
// Mock the error handler
vi.mock('../../../utils/toolErrorHandler', () => ({
createStandardErrorHandler: vi.fn(() => 'error-handler-function')
}));
// Import the mocked function
import { ToolOperationConfig, ToolOperationHook, useToolOperation } from '../shared/useToolOperation';
describe('useAddPasswordOperation', () => {
const mockUseToolOperation = vi.mocked(useToolOperation);
const getToolConfig = (): ToolOperationConfig<AddPasswordFullParameters> => mockUseToolOperation.mock.calls[0][0];
const mockToolOperationReturn: ToolOperationHook<unknown> = {
files: [],
thumbnails: [],
downloadUrl: null,
downloadFilename: '',
isLoading: false,
errorMessage: null,
status: '',
isGeneratingThumbnails: false,
progress: null,
executeOperation: vi.fn(),
resetResults: vi.fn(),
clearError: vi.fn(),
cancelOperation: vi.fn(),
};
beforeEach(() => {
vi.clearAllMocks();
mockUseToolOperation.mockReturnValue(mockToolOperationReturn);
});
test('should configure useToolOperation with correct parameters', () => {
renderHook(() => useAddPasswordOperation());
expect(mockUseToolOperation).toHaveBeenCalledWith({
operationType: 'addPassword',
endpoint: '/api/v1/security/add-password',
buildFormData: expect.any(Function),
filePrefix: 'translated-addPassword.filenamePrefix_',
multiFileEndpoint: false,
getErrorMessage: 'error-handler-function'
});
});
test('should return the result from useToolOperation', () => {
const { result } = renderHook(() => useAddPasswordOperation());
expect(result.current).toBe(mockToolOperationReturn);
});
test.each([
{
description: 'with all parameters filled',
password: 'user-password',
ownerPassword: 'owner-password',
keyLength: 256
},
{
description: 'with empty passwords',
password: '',
ownerPassword: '',
keyLength: 128
},
{
description: 'with 40-bit key length',
password: 'test',
ownerPassword: '',
keyLength: 40
}
])('should create form data correctly $description', ({ password, ownerPassword, keyLength }) => {
renderHook(() => useAddPasswordOperation());
const callArgs = getToolConfig();
const buildFormData = callArgs.buildFormData;
const testParameters: AddPasswordFullParameters = {
password,
ownerPassword,
keyLength,
permissions: {
preventAssembly: false,
preventExtractContent: false,
preventExtractForAccessibility: false,
preventFillInForm: false,
preventModify: false,
preventModifyAnnotations: false,
preventPrinting: false,
preventPrintingFaithful: false
}
};
const testFile = new File(['test content'], 'test.pdf', { type: 'application/pdf' });
const formData = buildFormData(testParameters, testFile as any /* FIX ME */);
// Verify the form data contains the file
expect(formData.get('fileInput')).toBe(testFile);
// Verify password parameters
expect(formData.get('password')).toBe(password);
expect(formData.get('ownerPassword')).toBe(ownerPassword);
expect(formData.get('keyLength')).toBe(keyLength.toString());
});
test('should use correct translation for error messages', () => {
renderHook(() => useAddPasswordOperation());
expect(mockT).toHaveBeenCalledWith(
'addPassword.error.failed',
'An error occurred while encrypting the PDF.'
);
});
test.each([
{ property: 'multiFileEndpoint' as const, expectedValue: false },
{ property: 'endpoint' as const, expectedValue: '/api/v1/security/add-password' },
{ property: 'filePrefix' as const, expectedValue: 'translated-addPassword.filenamePrefix_' },
{ property: 'operationType' as const, expectedValue: 'addPassword' }
])('should configure $property correctly', ({ property, expectedValue }) => {
renderHook(() => useAddPasswordOperation());
const callArgs = getToolConfig();
expect(callArgs[property]).toBe(expectedValue);
});
});

View File

@@ -0,0 +1,30 @@
import { useTranslation } from 'react-i18next';
import { useToolOperation } from '../shared/useToolOperation';
import { createStandardErrorHandler } from '../../../utils/toolErrorHandler';
import { AddPasswordFullParameters } from './useAddPasswordParameters';
import { getFormData } from '../changePermissions/useChangePermissionsOperation';
export const useAddPasswordOperation = () => {
const { t } = useTranslation();
const buildFormData = (parameters: AddPasswordFullParameters, file: File): FormData => {
const formData = new FormData();
formData.append("fileInput", file);
formData.append("password", parameters.password);
formData.append("ownerPassword", parameters.ownerPassword);
formData.append("keyLength", parameters.keyLength.toString());
getFormData(parameters.permissions).forEach(([key, value]) => {
formData.append(key, value);
});
return formData;
};
return useToolOperation<AddPasswordFullParameters>({
operationType: 'addPassword',
endpoint: '/api/v1/security/add-password',
buildFormData,
filePrefix: t('addPassword.filenamePrefix', 'encrypted') + '_',
multiFileEndpoint: false,
getErrorMessage: createStandardErrorHandler(t('addPassword.error.failed', 'An error occurred while encrypting the PDF.'))
});
};

View File

@@ -0,0 +1,152 @@
import { describe, expect, test } from 'vitest';
import { renderHook, act } from '@testing-library/react';
import { useAddPasswordParameters, defaultParameters, AddPasswordParametersHook } from './useAddPasswordParameters';
import { defaultParameters as defaultChangePermissionsParameters, ChangePermissionsParameters } from '../changePermissions/useChangePermissionsParameters';
describe('useAddPasswordParameters', () => {
test('should initialize with default parameters', () => {
const { result } = renderHook(() => useAddPasswordParameters());
expect(result.current.parameters).toStrictEqual(defaultParameters);
});
test.each([
{ paramName: 'password' as const, value: 'test-password' },
{ paramName: 'ownerPassword' as const, value: 'owner-password' },
{ paramName: 'keyLength' as const, value: 256 }
])('should update parameter $paramName', ({ paramName, value }) => {
const { result } = renderHook(() => useAddPasswordParameters());
act(() => {
result.current.updateParameter(paramName, value);
});
expect(result.current.parameters[paramName]).toBe(value);
});
test.each([
{ paramName: 'preventAssembly' as const },
{ paramName: 'preventPrinting' as const }
])('should update boolean permission parameter $paramName', ({ paramName }) => {
const { result } = renderHook(() => useAddPasswordParameters());
act(() => {
result.current.permissions.updateParameter(paramName, true);
});
expect(result.current.permissions.parameters[paramName]).toBe(true);
});
test('should reset parameters to defaults', () => {
const { result } = renderHook(() => useAddPasswordParameters());
// First, change some parameters
act(() => {
result.current.updateParameter('password', 'test');
result.current.updateParameter('keyLength', 256);
result.current.permissions.updateParameter('preventAssembly', true);
});
expect(result.current.parameters.password).toBe('test');
expect(result.current.parameters.keyLength).toBe(256);
expect(result.current.permissions.parameters.preventAssembly).toBe(true);
// Then reset
act(() => {
result.current.resetParameters();
});
expect(result.current.parameters).toStrictEqual(defaultParameters);
});
test('should return correct endpoint name', () => {
const { result } = renderHook(() => useAddPasswordParameters());
expect(result.current.getEndpointName()).toBe('add-password');
});
test.each([
{
description: 'with user password only',
setup: (hook: AddPasswordParametersHook) => {
hook.updateParameter('password', 'user-password');
}
},
{
description: 'with owner password only',
setup: (hook: AddPasswordParametersHook) => {
hook.updateParameter('ownerPassword', 'owner-password');
}
},
{
description: 'with both passwords',
setup: (hook: AddPasswordParametersHook) => {
hook.updateParameter('password', 'user-password');
hook.updateParameter('ownerPassword', 'owner-password');
}
},
{
description: 'with whitespace only password',
setup: (hook: AddPasswordParametersHook) => {
hook.updateParameter('password', ' \t ');
}
},
{
description: 'with whitespace only owner password',
setup: (hook: AddPasswordParametersHook) => {
hook.updateParameter('ownerPassword', ' \t ');
}
},
{
description: 'with restrictions only',
setup: (hook: AddPasswordParametersHook) => {
hook.permissions.updateParameter('preventAssembly', true);
hook.permissions.updateParameter('preventPrinting', true);
}
},
{
description: 'with passwords and restrictions',
setup: (hook: AddPasswordParametersHook) => {
hook.updateParameter('password', 'test-password');
hook.permissions.updateParameter('preventAssembly', true);
}
}
])('should validate parameters correctly $description', ({ setup }) => {
const { result } = renderHook(() => useAddPasswordParameters());
// Default state should be valid
expect(result.current.validateParameters()).toBe(true);
// Apply the test scenario setup
act(() => {
setup(result.current);
});
expect(result.current.validateParameters()).toBe(true);
});
test.each(Object.keys(defaultChangePermissionsParameters) as Array<keyof ChangePermissionsParameters>)('should handle boolean restriction parameter %s', (param) => {
const { result } = renderHook(() => useAddPasswordParameters());
act(() => {
result.current.resetParameters();
result.current.permissions.updateParameter(param, true);
});
expect(result.current.validateParameters()).toBe(true);
});
test('should handle mixed parameter types in updateParameter', () => {
const { result } = renderHook(() => useAddPasswordParameters());
act(() => {
result.current.updateParameter('password', 'test-string');
result.current.updateParameter('keyLength', 40);
result.current.permissions.updateParameter('preventAssembly', true);
});
expect(result.current.parameters.password).toBe('test-string');
expect(result.current.parameters.keyLength).toBe(40);
expect(result.current.permissions.parameters.preventAssembly).toBe(true);
});
});

View File

@@ -0,0 +1,69 @@
import { useState } from 'react';
import { ChangePermissionsParameters, ChangePermissionsParametersHook, useChangePermissionsParameters } from '../changePermissions/useChangePermissionsParameters';
export interface AddPasswordParameters {
password: string;
ownerPassword: string;
keyLength: number;
}
export interface AddPasswordFullParameters extends AddPasswordParameters {
permissions: ChangePermissionsParameters;
}
export interface AddPasswordParametersHook {
fullParameters: AddPasswordFullParameters;
parameters: AddPasswordParameters;
permissions: ChangePermissionsParametersHook;
updateParameter: <K extends keyof AddPasswordParameters>(parameter: K, value: AddPasswordParameters[K]) => void;
resetParameters: () => void;
validateParameters: () => boolean;
getEndpointName: () => string;
}
export const defaultParameters: AddPasswordParameters = {
password: '',
ownerPassword: '',
keyLength: 128,
};
export const useAddPasswordParameters = (): AddPasswordParametersHook => {
const [parameters, setParameters] = useState<AddPasswordParameters>(defaultParameters);
const permissions = useChangePermissionsParameters();
const fullParameters: AddPasswordFullParameters = {
...parameters,
permissions: permissions.parameters,
};
const updateParameter = <K extends keyof AddPasswordParameters>(parameter: K, value: AddPasswordParameters[K]) => {
setParameters(prev => ({
...prev,
[parameter]: value,
})
);
};
const resetParameters = () => {
setParameters(defaultParameters);
permissions.resetParameters();
};
const validateParameters = () => {
// No required parameters for Add Password. Defer to permissions validation.
return permissions.validateParameters();
};
const getEndpointName = () => {
return 'add-password';
};
return {
fullParameters,
parameters,
permissions,
updateParameter,
resetParameters,
validateParameters,
getEndpointName,
};
};

View File

@@ -0,0 +1,138 @@
import { describe, expect, test, vi, beforeEach } from 'vitest';
import { renderHook } from '@testing-library/react';
import { useChangePermissionsOperation } from './useChangePermissionsOperation';
import type { ChangePermissionsParameters } from './useChangePermissionsParameters';
// Mock the useToolOperation hook
vi.mock('../shared/useToolOperation', () => ({
useToolOperation: vi.fn()
}));
// Mock the translation hook
const mockT = vi.fn((key: string) => `translated-${key}`);
vi.mock('react-i18next', () => ({
useTranslation: () => ({ t: mockT })
}));
// Mock the error handler
vi.mock('../../../utils/toolErrorHandler', () => ({
createStandardErrorHandler: vi.fn(() => 'error-handler-function')
}));
// Import the mocked function
import { ToolOperationConfig, ToolOperationHook, useToolOperation } from '../shared/useToolOperation';
describe('useChangePermissionsOperation', () => {
const mockUseToolOperation = vi.mocked(useToolOperation);
const getToolConfig = (): ToolOperationConfig<ChangePermissionsParameters> => mockUseToolOperation.mock.calls[0][0];
const mockToolOperationReturn: ToolOperationHook<unknown> = {
files: [],
thumbnails: [],
downloadUrl: null,
downloadFilename: '',
isLoading: false,
errorMessage: null,
status: '',
isGeneratingThumbnails: false,
progress: null,
executeOperation: vi.fn(),
resetResults: vi.fn(),
clearError: vi.fn(),
cancelOperation: vi.fn(),
};
beforeEach(() => {
vi.clearAllMocks();
mockUseToolOperation.mockReturnValue(mockToolOperationReturn);
});
test('should configure useToolOperation with correct parameters', () => {
renderHook(() => useChangePermissionsOperation());
expect(mockUseToolOperation).toHaveBeenCalledWith({
operationType: 'changePermissions',
endpoint: '/api/v1/security/add-password',
buildFormData: expect.any(Function),
filePrefix: 'permissions_',
multiFileEndpoint: false,
getErrorMessage: 'error-handler-function'
});
});
test('should return the result from useToolOperation', () => {
const { result } = renderHook(() => useChangePermissionsOperation());
expect(result.current).toBe(mockToolOperationReturn);
});
test.each([
{
preventAssembly: false,
preventExtractContent: false,
preventExtractForAccessibility: false,
preventFillInForm: false,
preventModify: false,
preventModifyAnnotations: false,
preventPrinting: false,
preventPrintingFaithful: false,
},
{
preventAssembly: true,
preventExtractContent: false,
preventExtractForAccessibility: true,
preventFillInForm: false,
preventModify: true,
preventModifyAnnotations: false,
preventPrinting: true,
preventPrintingFaithful: false,
},
{
preventAssembly: true,
preventExtractContent: true,
preventExtractForAccessibility: true,
preventFillInForm: true,
preventModify: true,
preventModifyAnnotations: true,
preventPrinting: true,
preventPrintingFaithful: true,
},
])('should create form data correctly', (testParameters: ChangePermissionsParameters) => {
renderHook(() => useChangePermissionsOperation());
const callArgs = getToolConfig();
const buildFormData = callArgs.buildFormData;
const testFile = new File(['test content'], 'test.pdf', { type: 'application/pdf' });
const formData = buildFormData(testParameters, testFile as any /* FIX ME */);
// Verify the form data contains the file
expect(formData.get('fileInput')).toBe(testFile);
(Object.keys(testParameters) as Array<keyof ChangePermissionsParameters>).forEach(key => {
expect(formData.get(key), `Parameter ${key} should be set correctly`).toBe(testParameters[key].toString());
});
});
test('should use correct translation for error messages', () => {
renderHook(() => useChangePermissionsOperation());
expect(mockT).toHaveBeenCalledWith(
'changePermissions.error.failed',
'An error occurred while changing PDF permissions.'
);
});
test.each([
{ property: 'multiFileEndpoint' as const, expectedValue: false },
{ property: 'endpoint' as const, expectedValue: '/api/v1/security/add-password' },
{ property: 'filePrefix' as const, expectedValue: 'permissions_' },
{ property: 'operationType' as const, expectedValue: 'changePermissions' }
])('should configure $property correctly', ({ property, expectedValue }) => {
renderHook(() => useChangePermissionsOperation());
const callArgs = getToolConfig();
expect(callArgs[property]).toBe(expectedValue);
});
});

View File

@@ -0,0 +1,37 @@
import { useTranslation } from 'react-i18next';
import { useToolOperation } from '../shared/useToolOperation';
import { createStandardErrorHandler } from '../../../utils/toolErrorHandler';
import type { ChangePermissionsParameters } from './useChangePermissionsParameters';
export const getFormData = ((parameters: ChangePermissionsParameters) =>
Object.entries(parameters).map(([key, value]) =>
[key, value.toString()]
) as string[][]
);
export const useChangePermissionsOperation = () => {
const { t } = useTranslation();
const buildFormData = (parameters: ChangePermissionsParameters, file: File): FormData => {
const formData = new FormData();
formData.append("fileInput", file);
// Add all permission parameters
getFormData(parameters).forEach(([key, value]) => {
formData.append(key, value);
});
return formData;
};
return useToolOperation({
operationType: 'changePermissions',
endpoint: '/api/v1/security/add-password', // Change Permissions is a fake endpoint for the Add Password tool
buildFormData,
filePrefix: 'permissions_',
multiFileEndpoint: false,
getErrorMessage: createStandardErrorHandler(
t('changePermissions.error.failed', 'An error occurred while changing PDF permissions.')
)
});
};

View File

@@ -0,0 +1,110 @@
import { describe, expect, test } from 'vitest';
import { renderHook, act } from '@testing-library/react';
import { useChangePermissionsParameters, defaultParameters, ChangePermissionsParameters } from './useChangePermissionsParameters';
describe('useChangePermissionsParameters', () => {
test('should initialize with default parameters', () => {
const { result } = renderHook(() => useChangePermissionsParameters());
expect(result.current.parameters).toStrictEqual(defaultParameters);
});
test('should update individual boolean parameters', () => {
const { result } = renderHook(() => useChangePermissionsParameters());
act(() => {
result.current.updateParameter('preventAssembly', true);
});
expect(result.current.parameters.preventAssembly).toBe(true);
expect(result.current.parameters.preventPrinting).toBe(false); // Other parameters should remain unchanged
act(() => {
result.current.updateParameter('preventPrinting', true);
});
expect(result.current.parameters.preventPrinting).toBe(true);
expect(result.current.parameters.preventAssembly).toBe(true);
});
test('should update all permission parameters', () => {
const { result } = renderHook(() => useChangePermissionsParameters());
const permissionKeys = Object.keys(defaultParameters) as Array<keyof ChangePermissionsParameters>;
// Set all to true
act(() => {
permissionKeys.forEach(key => {
result.current.updateParameter(key, true);
});
});
permissionKeys.forEach(key => {
expect(result.current.parameters[key]).toBe(true);
});
// Set all to false
act(() => {
permissionKeys.forEach(key => {
result.current.updateParameter(key, false);
});
});
permissionKeys.forEach(key => {
expect(result.current.parameters[key]).toBe(false);
});
});
test('should reset parameters to defaults', () => {
const { result } = renderHook(() => useChangePermissionsParameters());
// First, change some parameters
act(() => {
result.current.updateParameter('preventAssembly', true);
result.current.updateParameter('preventPrinting', true);
result.current.updateParameter('preventModify', true);
});
expect(result.current.parameters.preventAssembly).toBe(true);
expect(result.current.parameters.preventPrinting).toBe(true);
expect(result.current.parameters.preventModify).toBe(true);
// Then reset
act(() => {
result.current.resetParameters();
});
expect(result.current.parameters).toStrictEqual(defaultParameters);
});
test('should return correct endpoint name', () => {
const { result } = renderHook(() => useChangePermissionsParameters());
expect(result.current.getEndpointName()).toBe('add-password');
});
test('should always validate as true', () => {
const { result } = renderHook(() => useChangePermissionsParameters());
// Default state should be valid
expect(result.current.validateParameters()).toBe(true);
// Set some restrictions - should still be valid
act(() => {
result.current.updateParameter('preventAssembly', true);
result.current.updateParameter('preventPrinting', true);
});
expect(result.current.validateParameters()).toBe(true);
// Set all restrictions - should still be valid
act(() => {
const permissionKeys = Object.keys(defaultParameters) as Array<keyof ChangePermissionsParameters>;
permissionKeys.forEach(key => {
result.current.updateParameter(key, true);
});
});
expect(result.current.validateParameters()).toBe(true);
});
});

View File

@@ -0,0 +1,64 @@
import { useState } from 'react';
export interface ChangePermissionsParameters {
preventAssembly: boolean;
preventExtractContent: boolean;
preventExtractForAccessibility: boolean;
preventFillInForm: boolean;
preventModify: boolean;
preventModifyAnnotations: boolean;
preventPrinting: boolean;
preventPrintingFaithful: boolean;
}
export interface ChangePermissionsParametersHook {
parameters: ChangePermissionsParameters;
updateParameter: (parameter: keyof ChangePermissionsParameters, value: boolean) => void;
resetParameters: () => void;
validateParameters: () => boolean;
getEndpointName: () => string;
}
export const defaultParameters: ChangePermissionsParameters = {
preventAssembly: false,
preventExtractContent: false,
preventExtractForAccessibility: false,
preventFillInForm: false,
preventModify: false,
preventModifyAnnotations: false,
preventPrinting: false,
preventPrintingFaithful: false,
};
export const useChangePermissionsParameters = (): ChangePermissionsParametersHook => {
const [parameters, setParameters] = useState<ChangePermissionsParameters>(defaultParameters);
const updateParameter = <K extends keyof ChangePermissionsParameters>(parameter: K, value: ChangePermissionsParameters[K]) => {
setParameters(prev => ({
...prev,
[parameter]: value,
})
);
};
const resetParameters = () => {
setParameters(defaultParameters);
};
const validateParameters = () => {
// Always valid - any combination of permissions is allowed
return true;
};
const getEndpointName = () => {
return 'add-password'; // Change Permissions is a fake endpoint for the Add Password tool
};
return {
parameters,
updateParameter,
resetParameters,
validateParameters,
getEndpointName,
};
};

View File

@@ -36,7 +36,7 @@ export interface ToolOperationConfig<TParams = void> {
* - (params, files: File[]) => FormData: Multi-file processing
* Not used when customProcessor is provided.
*/
buildFormData: ((params: TParams, file: File) => FormData) | ((params: TParams, files: File[]) => FormData);
buildFormData: ((params: TParams, file: File) => FormData) | ((params: TParams, files: File[]) => FormData); /* FIX ME */
/** Prefix added to processed filenames (e.g., 'compressed_', 'split_') */
filePrefix: string;

View File

@@ -1,7 +1,7 @@
import { useTranslation } from 'react-i18next';
import { useToolOperation } from '../shared/useToolOperation';
import { createStandardErrorHandler } from '../../../utils/toolErrorHandler';
import { SplitParameters } from '../../../components/tools/split/SplitSettings';
import { SplitParameters } from './useSplitParameters';
import { SPLIT_MODES } from '../../../constants/splitConstants';

View File

@@ -1,6 +1,18 @@
import { useState } from 'react';
import { SPLIT_MODES, SPLIT_TYPES, ENDPOINTS, type SplitMode } from '../../../constants/splitConstants';
import { SplitParameters } from '../../../components/tools/split/SplitSettings';
import { SPLIT_MODES, SPLIT_TYPES, ENDPOINTS, type SplitMode, SplitType } from '../../../constants/splitConstants';
export interface SplitParameters {
mode: SplitMode | '';
pages: string;
hDiv: string;
vDiv: string;
merge: boolean;
splitType: SplitType | '';
splitValue: string;
bookmarkLevel: string;
includeMetadata: boolean;
allowDuplicates: boolean;
}
export interface SplitParametersHook {
parameters: SplitParameters;

View File

@@ -5,6 +5,7 @@ import ZoomInMapIcon from "@mui/icons-material/ZoomInMap";
import SwapHorizIcon from "@mui/icons-material/SwapHoriz";
import ApiIcon from "@mui/icons-material/Api";
import CleaningServicesIcon from "@mui/icons-material/CleaningServices";
import LockIcon from "@mui/icons-material/Lock";
import { useMultipleEndpointsEnabled } from "./useEndpointConfig";
import { Tool, ToolDefinition, BaseToolProps, ToolRegistry } from "../types/tool";
@@ -85,6 +86,24 @@ const toolDefinitions: Record<string, ToolDefinition> = {
description: "Remove potentially harmful elements from PDF files",
endpoints: ["sanitize-pdf"]
},
addPassword: {
id: "addPassword",
icon: <LockIcon />,
component: React.lazy(() => import("../tools/AddPassword")),
maxFiles: -1,
category: "security",
description: "Add password protection and restrictions to PDF files",
endpoints: ["add-password"]
},
changePermissions: {
id: "changePermissions",
icon: <LockIcon />,
component: React.lazy(() => import("../tools/ChangePermissions")),
maxFiles: -1,
category: "security",
description: "Change document restrictions and permissions",
endpoints: ["add-password"]
},
};