import { Client } from 'minio'; // Initialize MinIO client export const getMinioClient = () => { const config = useRuntimeConfig().minio; return new Client({ endPoint: config.endPoint, port: config.port, useSSL: config.useSSL, accessKey: config.accessKey, secretKey: config.secretKey, }); }; // Upload file with content type detection export const uploadFile = async (filePath: string, fileBuffer: Buffer, contentType: string) => { const client = getMinioClient(); const bucketName = useRuntimeConfig().minio.bucketName; return await client.putObject(bucketName, filePath, fileBuffer, fileBuffer.length, { 'Content-Type': contentType, }); }; // Generate presigned URL for secure downloads export const getDownloadUrl = async (fileName: string, expiry: number = 60 * 60) => { const client = getMinioClient(); const bucketName = useRuntimeConfig().minio.bucketName; // Extract just the filename from the full path let filename = fileName.split('/').pop() || fileName; // Remove timestamp prefix if present (e.g., "1234567890-filename.pdf" -> "filename.pdf") const timestampMatch = filename.match(/^\d{10,}-(.+)$/); if (timestampMatch) { filename = timestampMatch[1]; } // Force download with Content-Disposition header const responseHeaders = { 'response-content-disposition': `attachment; filename="${filename}"`, }; return await client.presignedGetObject(bucketName, fileName, expiry, responseHeaders); }; // Get presigned URL for file preview (inline display) export const getPreviewUrl = async (fileName: string, contentType: string) => { const client = getMinioClient(); const bucketName = useRuntimeConfig().minio.bucketName; // For images and PDFs, generate a presigned URL with appropriate response headers const responseHeaders = { 'response-content-type': contentType, 'response-content-disposition': 'inline', }; return await client.presignedGetObject(bucketName, fileName, 60 * 60, responseHeaders); }; // Delete file export const deleteFile = async (fileName: string) => { const client = getMinioClient(); const bucketName = useRuntimeConfig().minio.bucketName; return await client.removeObject(bucketName, fileName); }; // Get file statistics export const getFileStats = async (fileName: string) => { const client = getMinioClient(); const bucketName = useRuntimeConfig().minio.bucketName; return await client.statObject(bucketName, fileName); }; // Create bucket if it doesn't exist export const createBucketIfNotExists = async (bucketName?: string) => { const client = getMinioClient(); const bucket = bucketName || useRuntimeConfig().minio.bucketName; try { const exists = await client.bucketExists(bucket); if (!exists) { await client.makeBucket(bucket); console.log(`Bucket '${bucket}' created successfully`); } return true; } catch (error) { console.error('Error creating bucket:', error); throw error; } }; // List files with prefix support export const listFiles = async (prefix: string = '', recursive: boolean = false) => { const client = getMinioClient(); const bucketName = useRuntimeConfig().minio.bucketName; const files: any[] = []; const folders = new Set(); return new Promise(async (resolve, reject) => { try { const stream = client.listObjectsV2(bucketName, prefix, recursive); stream.on('data', (obj) => { // Handle folder prefixes returned by MinIO if (obj && obj.prefix) { folders.add(obj.prefix); return; } // Skip objects without a name if (!obj || typeof obj.name !== 'string') { return; } if (!recursive) { if (prefix) { // Extract folder structure when inside a folder const relativePath = obj.name.substring(prefix.length); if (!relativePath) return; // Skip if no relative path const firstSlash = relativePath.indexOf('/'); if (firstSlash > -1) { // This is a folder const folderName = relativePath.substring(0, firstSlash); folders.add(prefix + folderName + '/'); } else if (relativePath && !obj.name.endsWith('/')) { // This is a file in the current folder files.push({ name: obj.name, size: obj.size || 0, lastModified: obj.lastModified || new Date(), etag: obj.etag || '', isFolder: false, }); } } else { // At root level const firstSlash = obj.name.indexOf('/'); if (obj.name.endsWith('/')) { // This is a folder placeholder folders.add(obj.name); } else if (firstSlash > -1) { // This is inside a folder, extract the folder const folderName = obj.name.substring(0, firstSlash); folders.add(folderName + '/'); } else { // This is a file at root files.push({ name: obj.name, size: obj.size || 0, lastModified: obj.lastModified || new Date(), etag: obj.etag || '', isFolder: false, }); } } } else { // When recursive, include all files if (!obj.name.endsWith('/')) { files.push({ name: obj.name, size: obj.size || 0, lastModified: obj.lastModified || new Date(), etag: obj.etag || '', isFolder: false, }); } } }); stream.on('error', (error) => { console.error('Stream error:', error); reject(error); }); stream.on('end', () => { // Add folders to the result const folderItems = Array.from(folders).map(folder => ({ name: folder, size: 0, lastModified: new Date(), etag: '', isFolder: true, })); resolve([...folderItems, ...files]); }); } catch (error) { console.error('Error in listFiles:', error); reject(error); } }); };