streamflix-api / src /shared /modules /upload /rustfs-storage /rustfs-storage.service.ts
Akshar2325
feat(upload): add rustfs storage integration
51c6e04
Raw
History Blame
4.33 kB
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';
@Injectable()
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;
}
}
}