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 { 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 { 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 { try { // Ensure we don't have a leading slash which can cause issues with removeObject const cleanedName = objectName.startsWith('/') ? objectName.slice(1) : objectName; await this.minioClient.removeObject(this.bucketName, cleanedName); this.logger.log( `Object "${cleanedName}" deleted successfully from bucket "${this.bucketName}".`, ); } catch (error: any) { this.logger.error( `Error deleting file "${objectName}": ${error.message}`, ); // We don't necessarily want to throw if the file is already gone } } }