import { CACHE_MANAGER } from '@nestjs/cache-manager'; import { Cache } from 'cache-manager'; import { Inject, Injectable } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { Client, ItemBucketMetadata } from 'minio'; import { UploadedObjectInfo } from 'minio/dist/main/internal/type'; import { open, readFileSync } from 'fs'; @Injectable() export class MinioService { private readonly client: Client; public readonly defaultBucketName: string; constructor( private readonly configService: ConfigService, @Inject(CACHE_MANAGER) private cacheManager: Cache, ) { this.client = new Client({ endPoint: this.configService.get('S3_ENDPOINT', 's3.hooli.co'), port: Number(this.configService.get('S3_PORT', 443)), useSSL: this.configService.get('S3_USE_SSL', 'true') === 'true', accessKey: this.configService.get('S3_ACCESS_KEY', ''), secretKey: this.configService.get('S3_SECRET_KEY', ''), region: this.configService.get('S3_REGION', 'auto'), }); this.defaultBucketName = this.configService.get('S3_BUCKET', ''); } public async uploadFile( bucketName: string, objectName: string, filePath: string, metadata?: ItemBucketMetadata, ): Promise { const file = readFileSync(filePath); return this.uploadBuffer(bucketName, objectName, file, metadata); } public async uploadBuffer( bucketName: string, objectName: string, buffer: Buffer, metadata?: ItemBucketMetadata, ): Promise { return this.client.putObject( bucketName, objectName, buffer, buffer.length, metadata, ); } public async getCachedBuffer( bucketName: string, objectName: string, cacheTtl: number = 0.1, ): Promise { const cacheKey = `${bucketName}/${objectName}`; const cachedValue = await this.cacheManager.get(cacheKey); if (cachedValue) { return Buffer.from(cachedValue); } const object = await this.getBuffer(bucketName, objectName); this.cacheManager.set(cacheKey, object, cacheTtl); return object; } public async getBuffer( bucketName: string, objectName: string, ): Promise { const objectStream = await this.client.getObject(bucketName, objectName); return await new Promise((resolve, reject) => { const chunks: Buffer[] = []; objectStream.on('data', (chunk) => chunks.push(chunk)); objectStream.on('end', () => resolve(Buffer.concat(chunks))); objectStream.on('error', (err) => reject(err)); }); } public async getObjectMetadata( bucketName: string, objectName: string, ): Promise { return await this.client.statObject(bucketName, objectName); } public async listBucketObjects( bucketName: string, prefix?: string, recursive: boolean = false, ): Promise { const objectStream = await this.client.listObjectsV2( bucketName, prefix, recursive, ); const objects = await new Promise( (resolve, reject) => { const objects: ItemBucketMetadata[] = []; objectStream.on('data', (object) => objects.push(object)); objectStream.on('end', () => resolve(objects)); objectStream.on('error', (err) => reject(err)); }, ); return objects.map((object) => object.name); } public async deleteObject( bucketName: string, objectName: string, ): Promise { await this.client.removeObject(bucketName, objectName); } public async makeBucket(bucketName: string): Promise { return this.client.makeBucket(bucketName); } /** * * @param bucketName * @param objectName * @param expiry Expiry in seconds * @returns */ public async generatePresignedUrl( bucketName: string, objectName: string, expiry: number = 60 * 60 * 24, ): Promise { return await this.client.presignedGetObject(bucketName, objectName, expiry); } public async objectExists( bucketName: string, objectName: string, ): Promise { try { await this.client.statObject(bucketName, objectName); return true; } catch (e) { return false; } } }