119 lines
3.7 KiB
TypeScript
119 lines
3.7 KiB
TypeScript
import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
|
|
import * as Minio from 'minio';
|
|
import { envs } from '../config/envs';
|
|
|
|
@Injectable()
|
|
export class MinioService implements OnModuleInit {
|
|
private readonly minioClient: Minio.Client;
|
|
private readonly logger = new Logger(MinioService.name);
|
|
private readonly bucketName = envs.minio_bucket;
|
|
|
|
constructor() {
|
|
this.minioClient = new Minio.Client({
|
|
endPoint: envs.minio_endpoint,
|
|
port: envs.minio_port,
|
|
useSSL: envs.minio_use_ssl,
|
|
accessKey: envs.minio_access_key,
|
|
secretKey: envs.minio_secret_key,
|
|
});
|
|
}
|
|
|
|
async onModuleInit() {
|
|
await this.ensureBucketExists();
|
|
}
|
|
|
|
private async ensureBucketExists() {
|
|
// Ejecuta esto siempre al menos una vez para asegurar que sea público
|
|
const policy = {
|
|
Version: '2012-10-17',
|
|
Statement: [
|
|
{
|
|
Effect: 'Allow',
|
|
Principal: { AWS: ['*'] },
|
|
Action: ['s3:GetObject'],
|
|
Resource: [`arn:aws:s3:::${this.bucketName}/*`],
|
|
},
|
|
],
|
|
};
|
|
try {
|
|
// const bucketExists = await this.minioClient.bucketExists(this.bucketName);
|
|
// if (!bucketExists) {
|
|
// await this.minioClient.makeBucket(this.bucketName);
|
|
// }
|
|
|
|
await this.minioClient.setBucketPolicy(
|
|
this.bucketName,
|
|
JSON.stringify(policy),
|
|
);
|
|
this.logger.log(`Public policy ensured for bucket "${this.bucketName}"`);
|
|
} catch (error: any) {
|
|
this.logger.error(`Error checking/creating bucket: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
async upload(
|
|
file: Express.Multer.File,
|
|
folder: string = 'general',
|
|
): Promise<string> {
|
|
const fileName = `${Date.now()}-${Math.round(Math.random() * 1e9)}-${file.originalname.replace(/\s/g, '_')}`;
|
|
const objectName = `${folder}/${fileName}`;
|
|
|
|
try {
|
|
await this.minioClient.putObject(
|
|
this.bucketName,
|
|
objectName,
|
|
file.buffer,
|
|
file.size,
|
|
{
|
|
'Content-Type': file.mimetype,
|
|
},
|
|
);
|
|
|
|
// Return the URL or the object path.
|
|
// Usually, we store the object path and generate a signed URL or use a proxy.
|
|
// The user asked for the URL to be stored in the database.
|
|
return objectName;
|
|
} catch (error: any) {
|
|
this.logger.error(`Error uploading file: ${error.message}`);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async getFileUrl(objectName: string): Promise<string> {
|
|
try {
|
|
// If the bucket is public, we can just return the URL.
|
|
// If private, we need a signed URL.
|
|
// For simplicity and common use cases in these projects, I'll generate a signed URL with a long expiration
|
|
// or assume there is some way to access it.
|
|
// But let's use signed URL for 1 week (maximum is 7 days) if needed,
|
|
// or just return the object name if the backend handles the serving.
|
|
// The user wants the URL stored in the DB.
|
|
|
|
return await this.minioClient.presignedUrl(
|
|
'GET',
|
|
this.bucketName,
|
|
objectName,
|
|
604800,
|
|
);
|
|
} catch (error: any) {
|
|
this.logger.error(`Error getting file URL: ${error.message}`);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
getPublicUrl(objectName: string): string {
|
|
const protocol = envs.minio_use_ssl ? 'https' : 'http';
|
|
return `${protocol}://${envs.minio_endpoint}:${envs.minio_port}/${this.bucketName}/${objectName}`;
|
|
}
|
|
|
|
async delete(objectName: string): Promise<void> {
|
|
try {
|
|
await this.minioClient.removeObject(this.bucketName, objectName);
|
|
this.logger.log(`Object "${objectName}" deleted successfully.`);
|
|
} catch (error: any) {
|
|
this.logger.error(`Error deleting file: ${error.message}`);
|
|
// We don't necessarily want to throw if the file is already gone
|
|
}
|
|
}
|
|
}
|