Akshar2325 commited on
Commit
76fba7a
·
1 Parent(s): 806651d

feat(new-minio-storage): add new minio vesrion s3 storage module for file management

Browse files

- Add NewMinioStorageModule with S3Client integration for MinIO compatibility
- Create MinioStorageService with presigned URL generation and file deletion capabilities
- Implement MinioStorageController with endpoints for presigned uploads and file deletion
- Add FileType enum to categorize files (IMAGE, DOCUMENT, VIDEO) with folder organization
- Create minioStorageConfig for environment-based configuration management
- Add MinIO credentials to .env.example (endpoint, access key, secret key, bucket, region)
- Register NewMinioStorageModule in AppModule for application-wide availability
- Support file organization by type with automatic folder path generation
- Enable presigned URL generation for secure client-side uploads
- Provide file deletion by URL with proper error handling and logging

.env.example CHANGED
@@ -69,3 +69,11 @@ CLOUDFLARE_API_TOKEN="your-api-token"
69
  # Filestack Credentials
70
  FILESTACK_API_KEY_DEV="your-filestack-api-key"
71
  FILESTACK_APP_SECRET_DEV="your-filestack-app-secret"
 
 
 
 
 
 
 
 
 
69
  # Filestack Credentials
70
  FILESTACK_API_KEY_DEV="your-filestack-api-key"
71
  FILESTACK_APP_SECRET_DEV="your-filestack-app-secret"
72
+
73
+ # NewMinio Storage Credentials
74
+ NEWMINIO_PROJECT_URL="https://your-minio-instance.com"
75
+ NEWMINIO_S3_ENDPOINT="https://your-minio-instance.com"
76
+ NEWMINIO_S3_ACCESS_KEY="your-access-key"
77
+ NEWMINIO_S3_SECRET_KEY="your-secret-key"
78
+ NEWMINIO_BUCKET="streamflix"
79
+ NEWMINIO_REGION="us-east-1"
src/app.module.ts CHANGED
@@ -11,6 +11,7 @@ import { ImagekitioModule } from './shared/modules/imagekitio/imagekitio.module'
11
  import { CloudinaryModule } from './shared/modules/cloudinary/cloudinary.module';
12
  import { SupabaseStorageModule } from './shared/modules/supabase-storage/supabase-storage.module';
13
  import { BackblazeStorageModule } from './shared/modules/backblaze-storage/backblaze-storage.module';
 
14
  import { FilestackModule } from './shared/modules/filestack/filestack.module';
15
  import { AppController } from './app.controller';
16
 
@@ -33,6 +34,7 @@ import { AppController } from './app.controller';
33
  CloudinaryModule,
34
  SupabaseStorageModule,
35
  BackblazeStorageModule,
 
36
  FilestackModule,
37
  UserModule,
38
  SuperAdminModule,
 
11
  import { CloudinaryModule } from './shared/modules/cloudinary/cloudinary.module';
12
  import { SupabaseStorageModule } from './shared/modules/supabase-storage/supabase-storage.module';
13
  import { BackblazeStorageModule } from './shared/modules/backblaze-storage/backblaze-storage.module';
14
+ import { NewMinioStorageModule } from './shared/modules/new-minio-storage/new-minio-storage.module';
15
  import { FilestackModule } from './shared/modules/filestack/filestack.module';
16
  import { AppController } from './app.controller';
17
 
 
34
  CloudinaryModule,
35
  SupabaseStorageModule,
36
  BackblazeStorageModule,
37
+ NewMinioStorageModule,
38
  FilestackModule,
39
  UserModule,
40
  SuperAdminModule,
src/shared/modules/new-minio-storage/enums/file-type.enum.ts ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ export enum FileType {
2
+ IMAGE = 'IMAGE',
3
+ DOCUMENT = 'DOCUMENT',
4
+ VIDEO = 'VIDEO',
5
+ }
src/shared/modules/new-minio-storage/new-minio-storage.config.ts ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ import { registerAs } from '@nestjs/config';
2
+
3
+ export default registerAs('minioStorage', () => ({
4
+ endpoint: process.env.NEWMINIO_S3_ENDPOINT,
5
+ region: process.env.NEWMINIO_REGION || 'us-east-1',
6
+ accessKeyId: process.env.NEWMINIO_S3_ACCESS_KEY,
7
+ secretAccessKey: process.env.NEWMINIO_S3_SECRET_KEY,
8
+ bucket: process.env.NEWMINIO_BUCKET || 'streamflix',
9
+ projectUrl: process.env.NEWMINIO_PROJECT_URL,
10
+ }));
src/shared/modules/new-minio-storage/new-minio-storage.controller.ts ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import {
2
+ Controller,
3
+ Get,
4
+ Delete,
5
+ Query,
6
+ HttpCode,
7
+ HttpStatus,
8
+ } from '@nestjs/common';
9
+ import { ApiTags, ApiOperation, ApiQuery } from '@nestjs/swagger';
10
+ import { MinioStorageService } from './new-minio-storage.service';
11
+ import { FileType } from './enums/file-type.enum';
12
+
13
+ @ApiTags('NewMinio Storage')
14
+ @Controller('newminio')
15
+ export class MinioStorageController {
16
+ constructor(private readonly minioStorageService: MinioStorageService) {}
17
+
18
+ @Get('presigned-upload')
19
+ @ApiOperation({ summary: 'Get presigned upload URL' })
20
+ @ApiQuery({ name: 'fileName', required: true })
21
+ @ApiQuery({ name: 'fileType', enum: FileType, required: false })
22
+ async getPresignedUploadUrl(
23
+ @Query('fileName') fileName: string,
24
+ @Query('fileType') fileType?: FileType,
25
+ ) {
26
+ return this.minioStorageService.getPresignedUploadUrl(fileName, fileType);
27
+ }
28
+
29
+ @Delete('by-url')
30
+ @HttpCode(HttpStatus.OK)
31
+ @ApiOperation({ summary: 'Delete file by URL' })
32
+ async deleteFileByUrl(@Query('url') url: string) {
33
+ const result = await this.minioStorageService.deleteFileByUrl(url);
34
+ return {
35
+ message: 'File deleted successfully',
36
+ ...result,
37
+ };
38
+ }
39
+ }
src/shared/modules/new-minio-storage/new-minio-storage.module.ts ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Module } from '@nestjs/common';
2
+ import { ConfigModule } from '@nestjs/config';
3
+ import { MinioStorageController } from './new-minio-storage.controller';
4
+ import { MinioStorageService } from './new-minio-storage.service';
5
+ import minioStorageConfig from './new-minio-storage.config';
6
+
7
+ @Module({
8
+ imports: [ConfigModule.forFeature(minioStorageConfig)],
9
+ controllers: [MinioStorageController],
10
+ providers: [MinioStorageService],
11
+ exports: [MinioStorageService],
12
+ })
13
+ export class NewMinioStorageModule {}
src/shared/modules/new-minio-storage/new-minio-storage.service.ts ADDED
@@ -0,0 +1,171 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Injectable, Logger } from '@nestjs/common';
2
+ import { ConfigService } from '@nestjs/config';
3
+ import {
4
+ S3Client,
5
+ PutObjectCommand,
6
+ DeleteObjectCommand,
7
+ } from '@aws-sdk/client-s3';
8
+ import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
9
+ import { FileType } from './enums/file-type.enum';
10
+
11
+ @Injectable()
12
+ export class MinioStorageService {
13
+ private readonly logger = new Logger(MinioStorageService.name);
14
+ private s3Client: S3Client;
15
+ private bucket: string;
16
+ private region: string;
17
+ private endpoint: string;
18
+ private projectUrl: string;
19
+
20
+ constructor(private configService: ConfigService) {
21
+ this.endpoint = this.configService.get<string>('minioStorage.endpoint')!;
22
+ this.region = this.configService.get<string>('minioStorage.region')!;
23
+ this.bucket = this.configService.get<string>('minioStorage.bucket')!;
24
+ this.projectUrl = this.configService.get<string>('minioStorage.projectUrl')!;
25
+
26
+ const accessKeyId = this.configService.get<string>(
27
+ 'minioStorage.accessKeyId',
28
+ )!;
29
+ const secretAccessKey = this.configService.get<string>(
30
+ 'minioStorage.secretAccessKey',
31
+ )!;
32
+
33
+ this.s3Client = new S3Client({
34
+ region: this.region,
35
+ endpoint: this.endpoint,
36
+ credentials: {
37
+ accessKeyId,
38
+ secretAccessKey,
39
+ },
40
+ forcePathStyle: true, // Required for MinIO
41
+ });
42
+
43
+ this.logger.log('MinIO Storage Service initialized');
44
+ }
45
+
46
+ /**
47
+ * Get folder path based on file type
48
+ * @param fileType - The file type (IMAGE, DOCUMENT, VIDEO)
49
+ * @returns Folder path for the file type
50
+ */
51
+ private getFolderPath(fileType: string): string {
52
+ switch (fileType?.toUpperCase()) {
53
+ case FileType.IMAGE:
54
+ return 'images';
55
+ case FileType.DOCUMENT:
56
+ return 'documents';
57
+ case FileType.VIDEO:
58
+ return 'videos';
59
+ default:
60
+ return 'files';
61
+ }
62
+ }
63
+
64
+ /**
65
+ * Generate presigned upload URL using S3
66
+ * @param fileName - The file name
67
+ * @param fileType - The file type (IMAGE, DOCUMENT, VIDEO)
68
+ * @returns Presigned upload URL and file path
69
+ */
70
+ async getPresignedUploadUrl(fileName: string, fileType?: string) {
71
+ try {
72
+ const folder = this.getFolderPath(fileType || '');
73
+ const timestamp = Date.now();
74
+ const sanitizedFileName = fileName.replace(/[^a-zA-Z0-9.-]/g, '_');
75
+ const key = `${folder}/${timestamp}-${sanitizedFileName}`;
76
+
77
+ const command = new PutObjectCommand({
78
+ Bucket: this.bucket,
79
+ Key: key,
80
+ });
81
+
82
+ const uploadUrl = await getSignedUrl(this.s3Client, command, {
83
+ expiresIn: 3600, // 1 hour
84
+ });
85
+
86
+ const fileUrl = `${this.projectUrl}/${this.bucket}/${key}`;
87
+
88
+ this.logger.log(`Generated presigned upload URL for: ${key}`);
89
+
90
+ return {
91
+ uploadUrl,
92
+ fileUrl,
93
+ key,
94
+ bucket: this.bucket,
95
+ expiresIn: 3600,
96
+ };
97
+ } catch (error) {
98
+ this.logger.error('Error generating presigned URL:', error);
99
+ throw error;
100
+ }
101
+ }
102
+
103
+ /**
104
+ * Delete a file from MinIO Storage using S3
105
+ * @param filePath - The file path in the bucket
106
+ * @returns Deletion result
107
+ */
108
+ async deleteFile(filePath: string): Promise<any> {
109
+ try {
110
+ const command = new DeleteObjectCommand({
111
+ Bucket: this.bucket,
112
+ Key: filePath,
113
+ });
114
+
115
+ await this.s3Client.send(command);
116
+
117
+ this.logger.log(`File deleted successfully: ${filePath}`);
118
+
119
+ return {
120
+ success: true,
121
+ filePath,
122
+ };
123
+ } catch (error) {
124
+ this.logger.error(`Error deleting file: ${filePath}`, error);
125
+ throw error;
126
+ }
127
+ }
128
+
129
+ /**
130
+ * Delete a file by URL
131
+ * @param url - The MinIO Storage file URL
132
+ * @returns Deletion result
133
+ */
134
+ async deleteFileByUrl(url: string): Promise<any> {
135
+ try {
136
+ const filePath = this.extractFilePathFromUrl(url);
137
+
138
+ if (!filePath) {
139
+ throw new Error('Invalid MinIO Storage URL');
140
+ }
141
+
142
+ return await this.deleteFile(filePath);
143
+ } catch (error) {
144
+ this.logger.error('Error deleting file by URL:', error);
145
+ throw error;
146
+ }
147
+ }
148
+
149
+ /**
150
+ * Extract file path from MinIO Storage URL
151
+ * @param url - The MinIO Storage URL
152
+ * @returns File path
153
+ */
154
+ private extractFilePathFromUrl(url: string): string | null {
155
+ try {
156
+ // URL format: https://lucifer925-minio-storage.hf.space/streamflix/images/1701234567890-avatar.jpg
157
+ const urlObj = new URL(url);
158
+ const pathParts = urlObj.pathname.split('/').filter(Boolean);
159
+
160
+ // Remove bucket name from path
161
+ if (pathParts[0] === this.bucket) {
162
+ pathParts.shift();
163
+ }
164
+
165
+ return pathParts.join('/');
166
+ } catch (error) {
167
+ this.logger.error('Error extracting file path from URL:', error);
168
+ return null;
169
+ }
170
+ }
171
+ }