feat: added upload file module
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
import { Readable } from "stream";
|
||||
|
||||
import { DeleteObjectCommand, GetObjectCommand, PutObjectCommand, S3Client } from "@aws-sdk/client-s3";
|
||||
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
|
||||
import { Inject, Injectable, Logger } from "@nestjs/common";
|
||||
|
||||
import type { IS3Configs } from "src/config/s3.config";
|
||||
import { S3_CONFIG } from "../constants";
|
||||
import { FileType } from "../interfaces/file.interface";
|
||||
import { S3UploadResult } from "../interfaces/s3.interface";
|
||||
|
||||
@Injectable()
|
||||
export class S3Service {
|
||||
private readonly logger = new Logger(S3Service.name);
|
||||
private readonly s3Client: S3Client;
|
||||
|
||||
constructor(@Inject(S3_CONFIG) private readonly s3Configs: IS3Configs) {
|
||||
this.s3Client = new S3Client({
|
||||
region: this.s3Configs.region,
|
||||
endpoint: this.s3Configs.endpoint,
|
||||
credentials: {
|
||||
accessKeyId: this.s3Configs.accessKeyId,
|
||||
secretAccessKey: this.s3Configs.secretAccessKey,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload file to S3
|
||||
*/
|
||||
async uploadFile(buffer: Buffer, key: string, contentType: string, metadata?: Record<string, string>): Promise<S3UploadResult> {
|
||||
try {
|
||||
const command = new PutObjectCommand({
|
||||
Bucket: this.s3Configs.bucketName,
|
||||
Key: key,
|
||||
Body: buffer,
|
||||
ACL: "public-read",
|
||||
ContentType: contentType,
|
||||
Metadata: metadata,
|
||||
});
|
||||
|
||||
await this.s3Client.send(command);
|
||||
|
||||
const url = `${this.s3Configs.bucketUploadedUrl}/${key}`;
|
||||
|
||||
this.logger.log(`File uploaded to S3: ${key}`);
|
||||
|
||||
return {
|
||||
key,
|
||||
url,
|
||||
bucket: this.s3Configs.bucketName,
|
||||
};
|
||||
} catch (error: unknown) {
|
||||
this.logger.error(`Failed to upload file to S3: ${(error as Error).message}`, (error as Error).stack);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get file stream from S3
|
||||
*/
|
||||
async getFileStream(key: string): Promise<Readable> {
|
||||
try {
|
||||
const command = new GetObjectCommand({
|
||||
Bucket: this.s3Configs.bucketName,
|
||||
Key: key,
|
||||
});
|
||||
|
||||
const response = await this.s3Client.send(command);
|
||||
return response.Body as Readable;
|
||||
} catch (error: unknown) {
|
||||
this.logger.error(`Failed to get file from S3: ${(error as Error).message}`, (error as Error).stack);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate presigned URL for file download
|
||||
*/
|
||||
async getPresignedUrl(key: string, expiresIn: number = 3600): Promise<string> {
|
||||
try {
|
||||
const command = new GetObjectCommand({
|
||||
Bucket: this.s3Configs.bucketName,
|
||||
Key: key,
|
||||
});
|
||||
|
||||
return await getSignedUrl(this.s3Client, command, { expiresIn });
|
||||
} catch (error: unknown) {
|
||||
this.logger.error(`Failed to generate presigned URL: ${(error as Error).message}`, (error as Error).stack);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete file from S3
|
||||
*/
|
||||
async deleteFile(key: string): Promise<void> {
|
||||
try {
|
||||
const command = new DeleteObjectCommand({
|
||||
Bucket: this.s3Configs.bucketName,
|
||||
Key: key,
|
||||
});
|
||||
|
||||
await this.s3Client.send(command);
|
||||
this.logger.log(`File deleted from S3: ${key}`);
|
||||
} catch (error: unknown) {
|
||||
this.logger.error(`Failed to delete file from S3: ${(error as Error).message}`, (error as Error).stack);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate S3 key for file
|
||||
*/
|
||||
generateFileKey(originalName: string, fileType: FileType): string {
|
||||
const timestamp = Date.now();
|
||||
const randomSuffix = Math.round(Math.random() * 1e9);
|
||||
const extension = originalName.split(".").pop();
|
||||
|
||||
const basePathMap: Record<FileType, string> = {
|
||||
image: "images",
|
||||
video: "videos",
|
||||
audio: "audio",
|
||||
document: "documents",
|
||||
text: "texts",
|
||||
unknown: "unknowns",
|
||||
};
|
||||
|
||||
const basePath = basePathMap[fileType];
|
||||
return `${basePath}/${timestamp}-${randomSuffix}.${extension}`;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user