From b24d46050448477a31ff77a043c16aab595c745e Mon Sep 17 00:00:00 2001 From: Jonathan Mezach Date: Mon, 6 Jan 2025 15:46:52 +0100 Subject: [PATCH] Implement deletion of attachments through interface Signed-off-by: Jonathan Mezach --- .../src/service/routes/attachments.ts | 35 ++----------------- .../service/upload/attachmentStorageEngine.ts | 1 + .../src/service/upload/azureBlobStorage.ts | 7 ++++ .../src/service/upload/database.ts | 4 +++ .../src/service/upload/filesystem.ts | 4 +++ plugins/qeta-backend/src/service/upload/s3.ts | 19 +++++++++- 6 files changed, 36 insertions(+), 34 deletions(-) diff --git a/plugins/qeta-backend/src/service/routes/attachments.ts b/plugins/qeta-backend/src/service/routes/attachments.ts index 6521a1e..52bfbf6 100644 --- a/plugins/qeta-backend/src/service/routes/attachments.ts +++ b/plugins/qeta-backend/src/service/routes/attachments.ts @@ -8,11 +8,6 @@ import AzureBlobStorageEngine from '../upload/azureBlobStorage'; import fs from 'fs'; import FileType from 'file-type'; import { File, RouteOptions } from '../types'; -import { - DeleteObjectCommand, - DeleteObjectCommandOutput, -} from '@aws-sdk/client-s3'; -import { getS3Client } from '../util'; import { AttachmentStorageEngine, AttachmentStorageEngineOptions } from '../upload/attachmentStorageEngine'; const DEFAULT_IMAGE_SIZE_LIMIT = 2500000; @@ -157,34 +152,8 @@ export const attachmentsRoutes = (router: Router, options: RouteOptions) => { return; } - const deleteS3Image = async () => { - const bucket = config.getOptionalString('qeta.storage.bucket'); - if (!bucket) { - throw new Error('Bucket name is required for S3 storage'); - } - const s3 = getS3Client(config); - const output: DeleteObjectCommandOutput = await s3.send( - new DeleteObjectCommand({ - Bucket: bucket, - Key: attachment.path, - }), - ); - if (output.$metadata.httpStatusCode !== 204) { - throw new Error('Failed to delete object'); - } - }; - - switch (attachment.locationType) { - case 's3': - await deleteS3Image(); - break; - case 'filesystem': - await fs.promises.rm(attachment.path); - break; - default: - case 'database': - break; - } + const engine = getStorageEngine(attachment.locationType, options); + await engine.deleteAttachment(attachment); const result = await database.deleteAttachment(uuid); if (!result) { diff --git a/plugins/qeta-backend/src/service/upload/attachmentStorageEngine.ts b/plugins/qeta-backend/src/service/upload/attachmentStorageEngine.ts index f8015a4..07411fc 100644 --- a/plugins/qeta-backend/src/service/upload/attachmentStorageEngine.ts +++ b/plugins/qeta-backend/src/service/upload/attachmentStorageEngine.ts @@ -12,4 +12,5 @@ export type AttachmentStorageEngineOptions = { export interface AttachmentStorageEngine { handleFile: (file: File, options?: { postId?: number; answerId?: number; collectionId?: number }) => Promise; getAttachmentBuffer: (attachment: Attachment) => Promise; + deleteAttachment(attachment: Attachment): Promise; } \ No newline at end of file diff --git a/plugins/qeta-backend/src/service/upload/azureBlobStorage.ts b/plugins/qeta-backend/src/service/upload/azureBlobStorage.ts index b66471f..ce9bf9a 100644 --- a/plugins/qeta-backend/src/service/upload/azureBlobStorage.ts +++ b/plugins/qeta-backend/src/service/upload/azureBlobStorage.ts @@ -59,6 +59,13 @@ class AzureBlobStorageEngine implements AttachmentStorageEngine { const blob = container.getBlockBlobClient(attachment.path); return blob.downloadToBuffer(); } + + deleteAttachment = async (attachment: Attachment) => { + const client = getAzureBlobServiceClient(this.config); + const container = client.getContainerClient(this.container); + const blob = container.getBlockBlobClient(attachment.path); + await blob.delete(); + } } export default (opts: AttachmentStorageEngineOptions) => { diff --git a/plugins/qeta-backend/src/service/upload/database.ts b/plugins/qeta-backend/src/service/upload/database.ts index d423bc7..6ee36c7 100644 --- a/plugins/qeta-backend/src/service/upload/database.ts +++ b/plugins/qeta-backend/src/service/upload/database.ts @@ -39,6 +39,10 @@ class DatabaseStoreEngine implements AttachmentStorageEngine { getAttachmentBuffer = async (attachment: Attachment) => { return attachment.binaryImage; }; + + deleteAttachment = async (_attachment: Attachment) => { + // Nothing to do here, since the attachment is stored in the database + } } export default (opts: AttachmentStorageEngineOptions) => { diff --git a/plugins/qeta-backend/src/service/upload/filesystem.ts b/plugins/qeta-backend/src/service/upload/filesystem.ts index 7cdf149..01f5b91 100644 --- a/plugins/qeta-backend/src/service/upload/filesystem.ts +++ b/plugins/qeta-backend/src/service/upload/filesystem.ts @@ -54,6 +54,10 @@ class FilesystemStoreEngine implements AttachmentStorageEngine { getAttachmentBuffer = async (attachment: Attachment) => { return await fs.promises.readFile(attachment.path); }; + + deleteAttachment = async (attachment: Attachment) => { + await fs.promises.rm(attachment.path); + } } export default (opts: AttachmentStorageEngineOptions) => { diff --git a/plugins/qeta-backend/src/service/upload/s3.ts b/plugins/qeta-backend/src/service/upload/s3.ts index eea4b55..73cd3f5 100644 --- a/plugins/qeta-backend/src/service/upload/s3.ts +++ b/plugins/qeta-backend/src/service/upload/s3.ts @@ -4,7 +4,7 @@ import { Config } from '@backstage/config'; import { QetaStore } from '../../database/QetaStore'; import { Attachment } from '@drodil/backstage-plugin-qeta-common'; import { File } from '../types'; -import { GetObjectCommand, GetObjectCommandOutput, PutObjectCommand } from '@aws-sdk/client-s3'; +import { DeleteObjectCommand, DeleteObjectCommandOutput, GetObjectCommand, GetObjectCommandOutput, PutObjectCommand } from '@aws-sdk/client-s3'; import { getS3Client } from '../util'; import { AttachmentStorageEngine, AttachmentStorageEngineOptions } from './attachmentStorageEngine'; @@ -81,6 +81,23 @@ class S3StoreEngine implements AttachmentStorageEngine { const bytes = await object.Body.transformToByteArray(); return Buffer.from(bytes); }; + + deleteAttachment = async (attachment: Attachment) => { + const bucket = this.config.getOptionalString('qeta.storage.bucket'); + if (!bucket) { + throw new Error('Bucket name is required for S3 storage'); + } + const s3 = getS3Client(this.config); + const output: DeleteObjectCommandOutput = await s3.send( + new DeleteObjectCommand({ + Bucket: bucket, + Key: attachment.path, + }), + ); + if (output.$metadata.httpStatusCode !== 204) { + throw new Error('Failed to delete object'); + } + } } export default (opts: AttachmentStorageEngineOptions) => {