Skip to content

Commit dcf0e27

Browse files
feat: Added deleteFolder implementation method for both S3 and Azure Blob Storages (#1021)
…Blob Storages
2 parents 526687c + 13befed commit dcf0e27

File tree

2 files changed

+52
-2
lines changed

2 files changed

+52
-2
lines changed

apps/api/src/app/upload/usecases/uploadcleanup-scheduler/uploadcleanup-scheduler.service.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,15 @@ export class UploadCleanupSchedulerService {
3030
for (const upload of uploads) {
3131
try {
3232
const files = await this.fileRepository.find({ _id: upload._uploadedFileId });
33+
await this.storageService.deleteFolder(upload.id);
3334

34-
await Promise.all(
35+
await Promise.allSettled(
3536
files.map(async (file) => {
3637
try {
3738
await Upload.updateOne({ _uploadedFileId: file._id }, { $set: { _uploadedFileId: '' } });
3839

3940
// Delete file from storage and db
4041
try {
41-
await this.storageService.deleteFile(file.path);
4242
await this.fileRepository.delete({ _id: file._id });
4343
} catch (error) {}
4444

libs/services/src/storage/storage.service.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import {
55
GetObjectCommand,
66
DeleteObjectCommand,
77
ListBucketsCommand,
8+
ListObjectsV2Command,
9+
DeleteObjectsCommand,
810
} from '@aws-sdk/client-s3';
911
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
1012
import { FileNotExistError, Defaults } from '@impler/shared';
@@ -28,6 +30,7 @@ export abstract class StorageService {
2830
abstract getFileStream(key: string): Promise<Readable>;
2931
abstract writeStream(key: string, stream: Readable, contentType: string): Promise<void>;
3032
abstract deleteFile(key: string): Promise<void>;
33+
abstract deleteFolder(key: string): Promise<void>;
3134
abstract isConnected(): boolean;
3235
abstract getSignedUrl(key: string): Promise<string>;
3336
}
@@ -64,6 +67,46 @@ export class S3StorageService implements StorageService {
6467
this.isS3Connected = false;
6568
});
6669
}
70+
async deleteFolder(prefix: string): Promise<void> {
71+
try {
72+
// Ensure prefix ends with '/' if it's not empty
73+
const folderPrefix = prefix && !prefix.endsWith('/') ? `${prefix}/` : prefix;
74+
75+
let continuationToken: string | undefined;
76+
77+
do {
78+
// List objects with the given prefix
79+
const listCommand = new ListObjectsV2Command({
80+
Bucket: process.env.S3_BUCKET_NAME,
81+
Prefix: folderPrefix,
82+
ContinuationToken: continuationToken,
83+
});
84+
85+
const listResponse = await this.s3.send(listCommand);
86+
87+
if (listResponse.Contents && listResponse.Contents.length > 0) {
88+
// Prepare objects for deletion (max 1000 objects per delete request)
89+
const objectsToDelete = listResponse.Contents.map((obj) => ({
90+
Key: obj.Key,
91+
}));
92+
93+
// Delete the objects
94+
const deleteCommand = new DeleteObjectsCommand({
95+
Bucket: process.env.S3_BUCKET_NAME,
96+
Delete: {
97+
Objects: objectsToDelete,
98+
Quiet: true, // Don't return details about deleted objects
99+
},
100+
});
101+
102+
await this.s3.send(deleteCommand);
103+
}
104+
105+
// Check if there are more objects to delete
106+
continuationToken = listResponse.NextContinuationToken;
107+
} while (continuationToken);
108+
} catch (error) {}
109+
}
67110

68111
async uploadFile(key: string, file: Buffer | string | Readable, contentType: string): Promise<IStorageResponse> {
69112
const command = new PutObjectCommand({
@@ -275,6 +318,13 @@ export class AzureStorageService implements StorageService {
275318
await blockBlobClient.delete();
276319
}
277320

321+
async deleteFolder(prefix: string): Promise<void> {
322+
for await (const blob of this.containerClient.listBlobsFlat({ prefix })) {
323+
const blockBlobClient = this.containerClient.getBlockBlobClient(blob.name);
324+
await blockBlobClient.deleteIfExists();
325+
}
326+
}
327+
278328
isConnected(): boolean {
279329
return this.isAzureConnected;
280330
}

0 commit comments

Comments
 (0)