Spaces:
Runtime error
Runtime error
| import { Injectable, Logger, NotFoundException } from '@nestjs/common'; | |
| import { ConfigService } from '@nestjs/config'; | |
| import { | |
| S3Client, | |
| PutObjectCommand, | |
| DeleteObjectCommand, | |
| } from '@aws-sdk/client-s3'; | |
| import { getSignedUrl } from '@aws-sdk/s3-request-presigner'; | |
| () | |
| export class RustfsStorageService { | |
| private readonly logger = new Logger(RustfsStorageService.name); | |
| private s3Client: S3Client; | |
| private bucket: string; | |
| private endpoint: string; | |
| constructor(private configService: ConfigService) { | |
| this.endpoint = | |
| this.configService.get<string>('rustfsStorage.endpoint') || ''; | |
| const accessKeyId = | |
| this.configService.get<string>('rustfsStorage.accessKey') || ''; | |
| const secretAccessKey = | |
| this.configService.get<string>('rustfsStorage.secretKey') || ''; | |
| this.bucket = | |
| this.configService.get<string>('rustfsStorage.bucket') || 'streamflix'; | |
| const region = | |
| this.configService.get<string>('rustfsStorage.region') || 'us-east-1'; | |
| this.s3Client = new S3Client({ | |
| region, | |
| endpoint: this.endpoint, | |
| credentials: { | |
| accessKeyId, | |
| secretAccessKey, | |
| }, | |
| forcePathStyle: true, | |
| }); | |
| } | |
| /** | |
| * Get folder path based on file type | |
| */ | |
| private getFolderPath(fileType: string): string { | |
| const folderMap: Record<string, string> = { | |
| IMAGE: 'images', | |
| DOCUMENT: 'documents', | |
| VIDEO: 'videos', | |
| }; | |
| return folderMap[fileType.toUpperCase()] || 'others'; | |
| } | |
| /** | |
| * Generate presigned upload URL using S3 | |
| */ | |
| async getPresignedUploadUrl(fileName: string, fileType?: string) { | |
| try { | |
| const folder = fileType ? this.getFolderPath(fileType) : 'others'; | |
| const timestamp = Date.now(); | |
| const filePath = `${folder}/${timestamp}-${fileName}`; | |
| const command = new PutObjectCommand({ | |
| Bucket: this.bucket, | |
| Key: filePath, | |
| }); | |
| const uploadUrl = await getSignedUrl(this.s3Client, command, { | |
| expiresIn: 3600, | |
| }); | |
| const fileUrl = `${this.endpoint}/${this.bucket}/${filePath}`; | |
| this.logger.log( | |
| `Generated presigned upload URL for: ${filePath}${fileType ? ` (type: ${fileType})` : ''}`, | |
| ); | |
| return { | |
| uploadUrl, | |
| fileUrl, | |
| filePath, | |
| expiresIn: 3600, | |
| ...(fileType && { fileType }), | |
| }; | |
| } catch (error) { | |
| this.logger.error('Failed to generate presigned upload URL', error); | |
| throw error; | |
| } | |
| } | |
| /** | |
| * Delete a file from RustFS Storage | |
| */ | |
| async deleteFile(filePath: string): Promise<any> { | |
| try { | |
| const command = new DeleteObjectCommand({ | |
| Bucket: this.bucket, | |
| Key: filePath, | |
| }); | |
| const result = await this.s3Client.send(command); | |
| this.logger.log(`Successfully deleted file: ${filePath}`); | |
| return { | |
| success: true, | |
| deletedPath: filePath, | |
| result, | |
| }; | |
| } catch (error) { | |
| this.logger.error(`Failed to delete file: ${filePath}`, error); | |
| throw new NotFoundException('File not found or already deleted'); | |
| } | |
| } | |
| /** | |
| * Delete a file by URL | |
| */ | |
| async deleteFileByUrl(url: string): Promise<any> { | |
| try { | |
| const filePath = this.extractFilePathFromUrl(url); | |
| if (!filePath) { | |
| throw new NotFoundException('Could not extract file path from URL'); | |
| } | |
| const result = await this.deleteFile(filePath); | |
| this.logger.log(`Successfully deleted file from URL: ${url}`); | |
| return { | |
| ...result, | |
| deletedUrl: url, | |
| }; | |
| } catch (error) { | |
| if (error instanceof NotFoundException) { | |
| throw error; | |
| } | |
| this.logger.error(`Failed to delete file by URL: ${url}`, error); | |
| throw new NotFoundException('File not found or already deleted'); | |
| } | |
| } | |
| /** | |
| * Extract file path from RustFS Storage URL | |
| */ | |
| private extractFilePathFromUrl(url: string): string | null { | |
| try { | |
| const urlObj = new URL(url); | |
| const pathParts = urlObj.pathname.split('/'); | |
| const bucketIndex = pathParts.indexOf(this.bucket); | |
| if (bucketIndex !== -1 && bucketIndex < pathParts.length - 1) { | |
| return pathParts.slice(bucketIndex + 1).join('/'); | |
| } | |
| return pathParts.slice(1).join('/'); | |
| } catch { | |
| return null; | |
| } | |
| } | |
| } | |