diff --git a/nodes/SecloreProtect/Services/SecloreDRMApiService.ts b/nodes/SecloreProtect/Services/SecloreDRMApiService.ts index 2870132..94285b8 100644 --- a/nodes/SecloreProtect/Services/SecloreDRMApiService.ts +++ b/nodes/SecloreProtect/Services/SecloreDRMApiService.ts @@ -505,4 +505,56 @@ export class SecloreDRMApiService { this.handleHttpError(error as NodeApiError); } } + + /** + * Deletes a file with fileStorageId from file storage of currently logged in Tenant. + * + * @param fileStorageId - Storage ID of the file to be deleted + * @param accessToken - JWT access token for authorization + * @param correlationId - Optional request ID for logging purpose + * @returns Promise - No response body on successful deletion + * @throws Error on authentication failure or server error + */ + async deleteFile( + fileStorageId: string, + accessToken: string, + correlationId?: string, + ): Promise { + const who = "SecloreDRMApiService::deleteFile:: "; + try { + Logger.debug(who + 'Deleting file', { + fileStorageId, + correlationId + }); + + const headers: { [key: string]: string } = { + Authorization: `Bearer ${accessToken}`, + }; + + // Add correlation ID if provided + if (correlationId) { + headers['X-SECLORE-CORRELATION-ID'] = correlationId; + } + + const options: IHttpRequestOptions = { + method: 'DELETE', + url: `${this.baseUrl}/seclore/drm/filestorage/1.0/${fileStorageId}`, + headers, + }; + + Logger.debug(who + 'Making HTTP request', { url: options.url, method: options.method, fileStorageId, correlationId }); + await this.context.helpers.httpRequest(options); + Logger.debug(who + 'File deletion successful', { + fileStorageId, + correlationId + }); + } catch (error: unknown) { + Logger.error(who + 'File deletion failed', { + error, + fileStorageId, + correlationId + }); + this.handleHttpError(error as NodeApiError); + } + } } diff --git a/nodes/SecloreProtect/Services/SecloreDRMFileService.ts b/nodes/SecloreProtect/Services/SecloreDRMFileService.ts index 42349da..92f0d5d 100644 --- a/nodes/SecloreProtect/Services/SecloreDRMFileService.ts +++ b/nodes/SecloreProtect/Services/SecloreDRMFileService.ts @@ -349,6 +349,29 @@ export class SecloreDRMFileService { } } + /** + * Delete file with automatic authentication and retry + */ + async deleteFile( + fileStorageId: string, + correlationId?: string, + retryCount?: number, + ): Promise { + const who = "SecloreDRMFileService::deleteFile:: "; + try { + Logger.debug(who + 'Deleting file', { fileStorageId, correlationId }); + await this.executeWithRetry( + (accessToken) => this.apiService.deleteFile(fileStorageId, accessToken, correlationId), + retryCount, + correlationId, + ); + Logger.info(who + 'File deleted successfully', { fileStorageId, correlationId }); + } catch (error) { + Logger.error(who + 'Delete file failed', { error, fileStorageId, correlationId }); + throw error; + } + } + /** * Get current access token (for debugging/monitoring) */ diff --git a/nodes/SecloreProtect/operations/unprotect.ts b/nodes/SecloreProtect/operations/unprotect.ts index a0d4031..9c9d5b5 100644 --- a/nodes/SecloreProtect/operations/unprotect.ts +++ b/nodes/SecloreProtect/operations/unprotect.ts @@ -8,6 +8,41 @@ import { import { SecloreDRMFileService } from '../Services/SecloreDRMFileService'; +/** + * Deletes a file from storage with error handling (does not throw errors) + * @param fileService - The SecloreDRMFileService instance + * @param fileStorageId - The file storage ID to delete + * @param correlationId - Optional correlation ID for tracking + * @param retryCount - Number of retries for operations + */ +async function deleteFile( + fileService: SecloreDRMFileService, + fileStorageId: string, + correlationId?: string, + retryCount: number = 3, +): Promise { + const who = "unprotect::deleteFileWithErrorHandling:: "; + try { + Logger.debug(who + 'Attempting to delete file', { fileStorageId, correlationId, retryCount }); + + await fileService.deleteFile( + fileStorageId, + correlationId, + retryCount, + ); + + Logger.debug(who + 'File deleted successfully', { fileStorageId, correlationId }); + } catch (error) { + // Log error but don't throw - this is for cleanup operations + Logger.error(who + 'File deletion failed, continuing operation', { + error, + fileStorageId, + correlationId, + message: 'This is a cleanup operation, continuing despite deletion failure' + }); + } +} + /** * Uploads a file, unprotects it, and downloads the unprotected version * @param fileService - The SecloreDRMFileService instance @@ -31,91 +66,144 @@ async function unprotectFile( fileName: string; fileSize: number; }> { - // Upload the protected file - const uploadResult = await fileService.uploadFile( - new Uint8Array(fileBuffer), - fileName, - correlationId, - retryCount, - ); + const who = "unprotect::unprotectFile:: "; + var originalFileStorageId: string = ''; + try { + Logger.debug(who + 'Starting unprotect file operation', { fileName, fileSize: fileBuffer.length, correlationId, retryCount }); + + // Upload the protected file + Logger.debug(who + 'Uploading protected file', { fileName, fileSize: fileBuffer.length, correlationId }); + const uploadResult = await fileService.uploadFile( + new Uint8Array(fileBuffer), + fileName, + correlationId, + retryCount, + ); - Logger.info('File uploaded successfully', { fileStorageId: uploadResult.fileStorageId, fileName }); - Logger.debug('File upload response', {uploadResult}); + Logger.debug(who + 'File uploaded successfully', { fileStorageId: uploadResult.fileStorageId, fileName, correlationId }); - // Unprotect the uploaded file - Logger.debug('Unprotecting file', { fileStorageId: uploadResult.fileStorageId, fileName }); - const unprotectResult = await fileService.unprotect( - { - fileStorageId: uploadResult.fileStorageId, - }, - correlationId, - retryCount, - ); + originalFileStorageId = uploadResult.fileStorageId; - Logger.info('File unprotected successfully', { originalFileStorageId: uploadResult.fileStorageId, unprotectedFileStorageId: unprotectResult.fileStorageId, fileName }); + // check if the file is already unprotected + if (!uploadResult.protected) { + Logger.debug(who + 'File is already unprotected', { fileStorageId: uploadResult.fileStorageId, fileName, correlationId }); + return { + unprotectedFileData: fileBuffer, + originalFileStorageId: uploadResult.fileStorageId, + unprotectedFileStorageId: uploadResult.fileStorageId, + fileName, + fileSize: fileBuffer.length, + }; + } - Logger.info('Downloading unprotected file', { fileStorageId: unprotectResult.fileStorageId, fileName }); - // Download the unprotected file - const unprotectedFileData = await fileService.downloadFile( - unprotectResult.fileStorageId, - correlationId, - retryCount, - ); + // Unprotect the uploaded file + Logger.debug(who + 'Unprotecting file', { fileStorageId: uploadResult.fileStorageId, fileName, correlationId }); + const unprotectResult = await fileService.unprotect( + { + fileStorageId: uploadResult.fileStorageId, + }, + correlationId, + retryCount, + ); - Logger.info('Unprotected file downloaded successfully', { fileStorageId: unprotectResult.fileStorageId, fileSize: unprotectedFileData.length, fileName }); + Logger.debug(who + 'File unprotected successfully', { + originalFileStorageId: uploadResult.fileStorageId, + unprotectedFileStorageId: unprotectResult.fileStorageId, + fileName, + correlationId + }); - return { - unprotectedFileData, - originalFileStorageId: uploadResult.fileStorageId, - unprotectedFileStorageId: unprotectResult.fileStorageId, - fileName, - fileSize: unprotectedFileData.length, - }; + // Download the unprotected file + Logger.debug(who + 'Downloading unprotected file', { fileStorageId: unprotectResult.fileStorageId, fileName, correlationId }); + const unprotectedFileData = await fileService.downloadFile( + unprotectResult.fileStorageId, + correlationId, + retryCount, + ); + + Logger.debug(who + 'Unprotected file downloaded successfully', { + fileStorageId: unprotectResult.fileStorageId, + fileSize: unprotectedFileData.length, + fileName, + correlationId + }); + + const result = { + unprotectedFileData, + originalFileStorageId: uploadResult.fileStorageId, + unprotectedFileStorageId: unprotectResult.fileStorageId, + fileName, + fileSize: unprotectedFileData.length, + }; + + Logger.debug(who + 'Unprotect file operation completed successfully', { + fileName: result.fileName, + originalFileStorageId: result.originalFileStorageId, + unprotectedFileStorageId: result.unprotectedFileStorageId, + fileSize: result.fileSize, + correlationId + }); + + return result; + } catch (error) { + Logger.error(who + 'Unprotect file operation failed', { error, fileName, correlationId }); + throw error; + } finally { + if (originalFileStorageId !== '') { + await deleteFile(fileService, originalFileStorageId, correlationId, retryCount); + } + } } export async function unprotect(this: IExecuteFunctions): Promise { + const who = "unprotect::unprotect:: "; const items = this.getInputData(); const returnData: INodeExecutionData[] = []; // Initialize logger with the current execution context Logger.init(this.logger); - Logger.info('Seclore Unprotect operation started', { itemCount: items.length }); + Logger.debug(who + 'Seclore Unprotect operation started', { itemCount: items.length }); // Get credentials + Logger.debug(who + 'Getting credentials', {}); const credentials = await this.getCredentials('secloreProtectApi'); const baseUrl = credentials.baseUrl as string; const tenantId = credentials.tenantId as string; const tenantSecret = credentials.tenantSecret as string; // Initialize the file service + Logger.debug(who + 'Initializing file service', { baseUrl, tenantId }); const fileService = new SecloreDRMFileService(this, baseUrl, tenantId, tenantSecret); for (let i = 0; i < items.length; i++) { - Logger.debug('Processing item', { itemIndex: i, itemData: items[i] }); + Logger.debug(who + 'Processing item', { itemIndex: i }); try { // Get parameters for this item + Logger.debug(who + 'Getting node parameters', { itemIndex: i }); const binaryPropertyName = this.getNodeParameter('binaryPropertyName', i) as string; const correlationId = this.getNodeParameter('correlationId', i) as string; const retryCount = this.getNodeParameter('retryCount', i) as number; - Logger.debug('Asserting binary data', { binaryPropertyName, itemIndex: i }); + Logger.debug(who + 'Asserting binary data', { binaryPropertyName, itemIndex: i }); // Get input binary data const binaryData = this.helpers.assertBinaryData(i, binaryPropertyName); - Logger.debug('Getting binary data buffer', { binaryPropertyName, itemIndex: i }); + Logger.debug(who + 'Getting binary data buffer', { binaryPropertyName, itemIndex: i }); const fileBuffer = await this.helpers.getBinaryDataBuffer(i, binaryPropertyName); - Logger.debug('Binary data retrieved', { + Logger.debug(who + 'Binary data retrieved', { fileName: binaryData.fileName, fileSize: fileBuffer.length, mimeType: binaryData.mimeType, - itemIndex: i + itemIndex: i, + correlationId, + retryCount }); // Use the combined upload, unprotect, and download function try { - Logger.debug('Starting unprotect file operation', { + Logger.debug(who + 'Starting unprotect file operation', { fileName: binaryData.fileName, correlationId, retryCount, @@ -131,15 +219,22 @@ export async function unprotect(this: IExecuteFunctions): Promise { retryCount, ); - Logger.info('Unprotect file operation completed successfully', { + Logger.debug(who + 'Unprotect file operation completed successfully', { fileName: result.fileName, originalFileStorageId: result.originalFileStorageId, unprotectedFileStorageId: result.unprotectedFileStorageId, fileSize: result.fileSize, - itemIndex: i + itemIndex: i, + correlationId }); // Create output binary data + Logger.debug(who + 'Preparing binary data for output', { + fileName: binaryData.fileName, + mimeType: binaryData.mimeType, + fileSize: result.fileSize, + itemIndex: i + }); const outputBinaryData = await this.helpers.prepareBinaryData( Buffer.from(result.unprotectedFileData), binaryData.fileName || 'unprotected_file', @@ -161,17 +256,19 @@ export async function unprotect(this: IExecuteFunctions): Promise { }, }; + Logger.debug(who + 'Adding result to return data', { itemIndex: i, success: true }); returnData.push(returnItem); } catch (unprotectError) { - Logger.error('Unprotect file operation failed with error', { unprotectError }); + Logger.error(who + 'Unprotect file operation failed', { unprotectError, itemIndex: i }); // Re-throw the error to be handled by the outer catch block throw unprotectError; } } catch (error) { // Handle errors gracefully - Logger.error('Unprotect file operation failed with error', { error }); + Logger.error(who + 'Item processing failed', { error, itemIndex: i }); if (this.continueOnFail()) { + Logger.debug(who + 'Continuing on fail, adding error item', { itemIndex: i, errorMessage: error.message }); const returnItem: INodeExecutionData = { json: { success: false, @@ -181,6 +278,7 @@ export async function unprotect(this: IExecuteFunctions): Promise { }; returnData.push(returnItem); } else { + Logger.error(who + 'Throwing NodeOperationError', { error: error.message, itemIndex: i }); throw new NodeOperationError(this.getNode(), error.message, { itemIndex: i, }); @@ -188,7 +286,7 @@ export async function unprotect(this: IExecuteFunctions): Promise { } } - Logger.info('Seclore Unprotect operation completed', { + Logger.debug(who + 'Seclore Unprotect operation completed', { processedItems: returnData.length, successfulItems: returnData.filter(item => item.json.success).length });