Akshar2325 commited on
Commit
4b6b4e5
·
1 Parent(s): 67cf349

feat(seaweedfs-storage): add seaweedfs module for file management

Browse files

- Add SeaweedFS storage module with file upload and deletion capabilities
- Create seaweedfs-storage service with S3-compatible API integration
- Implement seaweedfs-storage controller with upload and delete endpoints
- Add FileType enum for categorizing uploaded files (IMAGE, DOCUMENT, VIDEO)
- Create seaweedfs-storage configuration with environment variables
- Migrate old-minio-storage module to unified upload namespace structure
- Add SeaweedFS credentials to .env.example configuration
- Register SeaweedfsStorageModule in upload module exports
- Provides distributed file storage alternative with S3-compatible interface

.env.example CHANGED
@@ -85,3 +85,9 @@ OLDMINIO_S3_ACCESS_KEY="your-access-key"
85
  OLDMINIO_S3_SECRET_KEY="your-secret-key"
86
  OLDMINIO_BUCKET="streamflix"
87
  OLDMINIO_REGION="us-east-1"
 
 
 
 
 
 
 
85
  OLDMINIO_S3_SECRET_KEY="your-secret-key"
86
  OLDMINIO_BUCKET="streamflix"
87
  OLDMINIO_REGION="us-east-1"
88
+
89
+ # SeaweedFS Storage Credentials
90
+ SEAWEEDFS_FILER_ENDPOINT="https://your-seaweedfs-instance.com/filer/buckets/your-bucket"
91
+ SEAWEEDFS_PUBLIC_URL="https://your-seaweedfs-instance.com/your-bucket"
92
+ SEAWEEDFS_ACCESS_KEY="your-access-key"
93
+ SEAWEEDFS_SECRET_KEY="your-secret-key"
src/shared/modules/{old-minio-storage → upload/old-minio-storage}/enums/file-type.enum.ts RENAMED
File without changes
src/shared/modules/{old-minio-storage → upload/old-minio-storage}/old-minio-storage.config.ts RENAMED
File without changes
src/shared/modules/{old-minio-storage → upload/old-minio-storage}/old-minio-storage.controller.ts RENAMED
File without changes
src/shared/modules/{old-minio-storage → upload/old-minio-storage}/old-minio-storage.module.ts RENAMED
File without changes
src/shared/modules/{old-minio-storage → upload/old-minio-storage}/old-minio-storage.service.ts RENAMED
File without changes
src/shared/modules/upload/seaweedfs-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/upload/seaweedfs-storage/seaweedfs-storage.config.ts ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ import { registerAs } from '@nestjs/config';
2
+
3
+ export default registerAs('seaweedfsStorage', () => ({
4
+ filerEndpoint: process.env.SEAWEEDFS_FILER_ENDPOINT,
5
+ publicUrl: process.env.SEAWEEDFS_PUBLIC_URL,
6
+ accessKey: process.env.SEAWEEDFS_ACCESS_KEY,
7
+ secretKey: process.env.SEAWEEDFS_SECRET_KEY,
8
+ }));
src/shared/modules/upload/seaweedfs-storage/seaweedfs-storage.controller.ts ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import {
2
+ Controller,
3
+ Post,
4
+ Delete,
5
+ Query,
6
+ HttpCode,
7
+ HttpStatus,
8
+ UseInterceptors,
9
+ UploadedFile,
10
+ } from '@nestjs/common';
11
+ import { FileInterceptor } from '@nestjs/platform-express';
12
+ import {
13
+ ApiTags,
14
+ ApiOperation,
15
+ ApiQuery,
16
+ ApiConsumes,
17
+ ApiBody,
18
+ } from '@nestjs/swagger';
19
+ import { SeaweedfsStorageService } from './seaweedfs-storage.service';
20
+ import { FileType } from './enums/file-type.enum';
21
+
22
+ @ApiTags('SeaweedFS Storage')
23
+ @Controller('seaweedfs')
24
+ export class SeaweedfsStorageController {
25
+ constructor(
26
+ private readonly seaweedfsStorageService: SeaweedfsStorageService,
27
+ ) {}
28
+
29
+ @Post('upload')
30
+ @ApiOperation({ summary: 'Upload file to SeaweedFS' })
31
+ @ApiConsumes('multipart/form-data')
32
+ @ApiQuery({ name: 'fileType', enum: FileType, required: false })
33
+ @ApiBody({
34
+ schema: {
35
+ type: 'object',
36
+ properties: { file: { type: 'string', format: 'binary' } },
37
+ },
38
+ })
39
+ @UseInterceptors(FileInterceptor('file'))
40
+ async uploadFile(
41
+ @UploadedFile() file: any,
42
+ @Query('fileType') fileType?: FileType,
43
+ ) {
44
+ return this.seaweedfsStorageService.uploadFile(
45
+ file.originalname,
46
+ file.buffer,
47
+ file.mimetype,
48
+ fileType,
49
+ );
50
+ }
51
+
52
+ @Delete('by-url')
53
+ @HttpCode(HttpStatus.OK)
54
+ @ApiOperation({ summary: 'Delete file by URL' })
55
+ async deleteFileByUrl(@Query('url') url: string) {
56
+ const result = await this.seaweedfsStorageService.deleteFileByUrl(url);
57
+ return {
58
+ message: 'File deleted successfully',
59
+ ...result,
60
+ };
61
+ }
62
+ }
src/shared/modules/upload/seaweedfs-storage/seaweedfs-storage.module.ts ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Module } from '@nestjs/common';
2
+ import { ConfigModule } from '@nestjs/config';
3
+ import { SeaweedfsStorageController } from './seaweedfs-storage.controller';
4
+ import { SeaweedfsStorageService } from './seaweedfs-storage.service';
5
+ import seaweedfsStorageConfig from './seaweedfs-storage.config';
6
+
7
+ @Module({
8
+ imports: [ConfigModule.forFeature(seaweedfsStorageConfig)],
9
+ controllers: [SeaweedfsStorageController],
10
+ providers: [SeaweedfsStorageService],
11
+ exports: [SeaweedfsStorageService],
12
+ })
13
+ export class SeaweedfsStorageModule {}
src/shared/modules/upload/seaweedfs-storage/seaweedfs-storage.service.ts ADDED
@@ -0,0 +1,183 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Injectable, Logger } from '@nestjs/common';
2
+ import { ConfigService } from '@nestjs/config';
3
+ import { FileType } from './enums/file-type.enum';
4
+
5
+ @Injectable()
6
+ export class SeaweedfsStorageService {
7
+ private readonly logger = new Logger(SeaweedfsStorageService.name);
8
+ private readonly filerEndpoint: string;
9
+ private readonly publicUrl: string;
10
+ private readonly authHeader: string;
11
+
12
+ constructor(private configService: ConfigService) {
13
+ this.filerEndpoint = this.configService.get<string>(
14
+ 'seaweedfsStorage.filerEndpoint',
15
+ )!;
16
+ this.publicUrl = this.configService.get<string>(
17
+ 'seaweedfsStorage.publicUrl',
18
+ )!;
19
+
20
+ const accessKey = this.configService.get<string>(
21
+ 'seaweedfsStorage.accessKey',
22
+ )!;
23
+ const secretKey = this.configService.get<string>(
24
+ 'seaweedfsStorage.secretKey',
25
+ )!;
26
+
27
+ // Pre-generate auth header
28
+ this.authHeader = `Basic ${Buffer.from(`${accessKey}:${secretKey}`).toString('base64')}`;
29
+
30
+ this.logger.log('SeaweedFS Storage Service initialized');
31
+ }
32
+
33
+ /**
34
+ * Get folder path based on file type
35
+ * @param fileType - The file type (IMAGE, DOCUMENT, VIDEO)
36
+ * @returns Folder path for the file type
37
+ */
38
+ getFolderPath(fileType: string): string {
39
+ switch (fileType?.toUpperCase()) {
40
+ case FileType.IMAGE:
41
+ return 'images';
42
+ case FileType.DOCUMENT:
43
+ return 'documents';
44
+ case FileType.VIDEO:
45
+ return 'videos';
46
+ default:
47
+ return 'files';
48
+ }
49
+ }
50
+
51
+ /**
52
+ * Upload file to SeaweedFS
53
+ * @param fileName - Original file name
54
+ * @param fileBuffer - The file buffer
55
+ * @param contentType - The content type
56
+ * @param fileType - The file type (IMAGE, DOCUMENT, VIDEO)
57
+ * @returns Upload result
58
+ */
59
+ async uploadFile(
60
+ fileName: string,
61
+ fileBuffer: Buffer,
62
+ contentType?: string,
63
+ fileType?: string,
64
+ ): Promise<any> {
65
+ const folder = this.getFolderPath(fileType || 'IMAGE');
66
+ const timestamp = Date.now();
67
+ const sanitizedFileName = fileName.replace(/[^a-zA-Z0-9.-]/g, '_');
68
+ const key = `${folder}/${timestamp}-${sanitizedFileName}`;
69
+
70
+ return this.uploadFileDirect(key, fileBuffer, contentType);
71
+ }
72
+
73
+ /**
74
+ * Upload file directly to SeaweedFS (internal method)
75
+ * @param key - The file key/path
76
+ * @param fileBuffer - The file buffer
77
+ * @param contentType - The content type
78
+ * @returns Upload result
79
+ */
80
+ private async uploadFileDirect(
81
+ key: string,
82
+ fileBuffer: Buffer,
83
+ contentType?: string,
84
+ ): Promise<any> {
85
+ try {
86
+ const uploadUrl = `${this.filerEndpoint}/${key}`;
87
+
88
+ const response = await fetch(uploadUrl, {
89
+ method: 'PUT',
90
+ body: fileBuffer as any,
91
+ headers: {
92
+ 'Content-Type': contentType || 'application/octet-stream',
93
+ Authorization: this.authHeader,
94
+ },
95
+ });
96
+
97
+ if (!response.ok) {
98
+ const errorText = await response.text();
99
+ throw new Error(`Upload failed: ${response.statusText} - ${errorText}`);
100
+ }
101
+
102
+ const fileUrl = `${this.publicUrl}/${key}`;
103
+
104
+ this.logger.log(`File uploaded successfully: ${key}`);
105
+
106
+ return {
107
+ success: true,
108
+ fileUrl,
109
+ key,
110
+ };
111
+ } catch (error) {
112
+ this.logger.error(`Error uploading file: ${key}`, error);
113
+ throw error;
114
+ }
115
+ }
116
+
117
+ /**
118
+ * Delete a file from SeaweedFS Storage using Filer API
119
+ * @param filePath - The file path in the bucket
120
+ * @returns Deletion result
121
+ */
122
+ async deleteFile(filePath: string): Promise<any> {
123
+ try {
124
+ const deleteUrl = `${this.filerEndpoint}/${filePath}`;
125
+
126
+ const response = await fetch(deleteUrl, {
127
+ method: 'DELETE',
128
+ headers: {
129
+ Authorization: this.authHeader,
130
+ },
131
+ });
132
+
133
+ if (!response.ok) {
134
+ throw new Error(`Delete failed: ${response.statusText}`);
135
+ }
136
+
137
+ this.logger.log(`File deleted successfully: ${filePath}`);
138
+
139
+ return {
140
+ success: true,
141
+ filePath,
142
+ };
143
+ } catch (error) {
144
+ this.logger.error(`Error deleting file: ${filePath}`, error);
145
+ throw error;
146
+ }
147
+ }
148
+
149
+ /**
150
+ * Delete a file by URL
151
+ * @param url - The SeaweedFS Storage file URL
152
+ * @returns Deletion result
153
+ */
154
+ async deleteFileByUrl(url: string): Promise<any> {
155
+ try {
156
+ const filePath = this.extractFilePathFromUrl(url);
157
+
158
+ if (!filePath) {
159
+ throw new Error('Invalid SeaweedFS Storage URL');
160
+ }
161
+
162
+ return await this.deleteFile(filePath);
163
+ } catch (error) {
164
+ this.logger.error('Error deleting file by URL:', error);
165
+ throw error;
166
+ }
167
+ }
168
+
169
+ /**
170
+ * Extract file path from SeaweedFS Storage URL
171
+ * @param url - The SeaweedFS Storage URL
172
+ * @returns File path
173
+ */
174
+ private extractFilePathFromUrl(url: string): string | null {
175
+ try {
176
+ // Extract path after public URL
177
+ return url.replace(this.publicUrl + '/', '');
178
+ } catch (error) {
179
+ this.logger.error('Error extracting file path from URL:', error);
180
+ return null;
181
+ }
182
+ }
183
+ }
src/shared/modules/upload/upload.module.ts CHANGED
@@ -4,7 +4,8 @@ import { CloudinaryModule } from './cloudinary/cloudinary.module';
4
  import { SupabaseStorageModule } from './supabase-storage/supabase-storage.module';
5
  import { BackblazeStorageModule } from './backblaze-storage/backblaze-storage.module';
6
  import { NewMinioStorageModule } from './new-minio-storage/new-minio-storage.module';
7
- import { OldMinioStorageModule } from '../old-minio-storage/old-minio-storage.module';
 
8
  import { FilestackModule } from './filestack/filestack.module';
9
 
10
  @Module({
@@ -13,9 +14,10 @@ import { FilestackModule } from './filestack/filestack.module';
13
  CloudinaryModule,
14
  SupabaseStorageModule,
15
  BackblazeStorageModule,
16
- FilestackModule,
17
  NewMinioStorageModule,
18
  OldMinioStorageModule,
 
 
19
  ],
20
  })
21
  export class UploadModule {}
 
4
  import { SupabaseStorageModule } from './supabase-storage/supabase-storage.module';
5
  import { BackblazeStorageModule } from './backblaze-storage/backblaze-storage.module';
6
  import { NewMinioStorageModule } from './new-minio-storage/new-minio-storage.module';
7
+ import { OldMinioStorageModule } from './old-minio-storage/old-minio-storage.module';
8
+ import { SeaweedfsStorageModule } from './seaweedfs-storage/seaweedfs-storage.module';
9
  import { FilestackModule } from './filestack/filestack.module';
10
 
11
  @Module({
 
14
  CloudinaryModule,
15
  SupabaseStorageModule,
16
  BackblazeStorageModule,
 
17
  NewMinioStorageModule,
18
  OldMinioStorageModule,
19
+ SeaweedfsStorageModule,
20
+ FilestackModule,
21
  ],
22
  })
23
  export class UploadModule {}