feat: added upload file module

This commit is contained in:
2026-07-31 23:30:39 +03:30
parent a4c50496c5
commit 5dcd2d8a9f
14 changed files with 1333 additions and 18 deletions
+132
View File
@@ -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}`;
}
}