elearning/Backend/src/config/minio.ts
2026-01-22 15:56:56 +07:00

189 lines
5.3 KiB
TypeScript

import { Client } from 'minio';
import { config } from './index';
import { logger } from './logger';
/**
* MinIO Client Configuration
* Used for uploading videos and attachments to S3-compatible storage
*/
export const minioClient = new Client({
endPoint: new URL(config.s3.endpoint).hostname,
port: parseInt(new URL(config.s3.endpoint).port) || (config.s3.useSSL ? 443 : 9000),
useSSL: config.s3.useSSL,
accessKey: config.s3.accessKey,
secretKey: config.s3.secretKey,
});
/**
* Ensure bucket exists, create if not
*/
export async function ensureBucketExists(): Promise<void> {
try {
const exists = await minioClient.bucketExists(config.s3.bucket);
if (!exists) {
await minioClient.makeBucket(config.s3.bucket, 'us-east-1');
logger.info(`Bucket '${config.s3.bucket}' created successfully`);
}
} catch (error) {
logger.error(`Error ensuring bucket exists: ${error}`);
throw error;
}
}
/**
* Generate a unique file path for storage
*/
export function generateFilePath(
courseId: number,
lessonId: number,
fileType: 'video' | 'attachment',
originalFilename: string
): string {
const timestamp = Date.now();
const uniqueId = Math.random().toString(36).substring(2, 15);
const extension = originalFilename.split('.').pop() || '';
const safeFilename = `${timestamp}-${uniqueId}.${extension}`;
if (fileType === 'video') {
return `courses/${courseId}/lessons/${lessonId}/video/${safeFilename}`;
}
return `courses/${courseId}/lessons/${lessonId}/attachments/${safeFilename}`;
}
/**
* Upload a file to MinIO
*/
export async function uploadFile(
filePath: string,
fileBuffer: Buffer,
mimeType: string
): Promise<string> {
try {
await ensureBucketExists();
await minioClient.putObject(
config.s3.bucket,
filePath,
fileBuffer,
fileBuffer.length,
{ 'Content-Type': mimeType }
);
logger.info(`File uploaded successfully: ${filePath}`);
return filePath;
} catch (error) {
logger.error(`Error uploading file: ${error}`);
throw error;
}
}
/**
* Delete a file from MinIO
*/
export async function deleteFile(filePath: string): Promise<void> {
try {
await minioClient.removeObject(config.s3.bucket, filePath);
logger.info(`File deleted successfully: ${filePath}`);
} catch (error) {
logger.error(`Error deleting file: ${error}`);
throw error;
}
}
/**
* Get a presigned URL for file access
*/
export async function getPresignedUrl(
filePath: string,
expirySeconds: number = 3600
): Promise<string> {
try {
const url = await minioClient.presignedGetObject(
config.s3.bucket,
filePath,
expirySeconds
);
return url;
} catch (error) {
logger.error(`Error generating presigned URL: ${error}`);
throw error;
}
}
/**
* List all objects in a path prefix
*/
export async function listObjects(prefix: string): Promise<{ name: string; size: number; lastModified: Date }[]> {
return new Promise((resolve, reject) => {
const objects: { name: string; size: number; lastModified: Date }[] = [];
const stream = minioClient.listObjectsV2(config.s3.bucket, prefix, true);
stream.on('data', (obj) => {
if (obj.name) {
objects.push({
name: obj.name,
size: obj.size || 0,
lastModified: obj.lastModified || new Date(),
});
}
});
stream.on('error', (err) => {
logger.error(`Error listing objects: ${err}`);
reject(err);
});
stream.on('end', () => {
resolve(objects);
});
});
}
/**
* List folders in a path prefix
* Uses listObjectsV2 with recursive=false to get folder prefixes
*/
export async function listFolders(prefix: string): Promise<string[]> {
return new Promise((resolve, reject) => {
const folders = new Set<string>();
const stream = minioClient.listObjectsV2(config.s3.bucket, prefix, false);
stream.on('data', (obj: any) => {
if (obj.prefix) {
// Direct folder prefix
folders.add(obj.prefix);
} else if (obj.name) {
// Extract folder part from file path
const path = obj.name.replace(prefix, '');
const folder = path.split('/')[0];
if (folder && path.includes('/')) {
folders.add(prefix + folder + '/');
}
}
});
stream.on('error', (err) => {
logger.error(`Error listing folders: ${err}`);
reject(err);
});
stream.on('end', () => {
resolve(Array.from(folders));
});
});
}
/**
* Get attachments folder path for a lesson
*/
export function getAttachmentsFolder(courseId: number, lessonId: number): string {
return `courses/${courseId}/lessons/${lessonId}/attachments/`;
}
/**
* Get video folder path for a lesson
*/
export function getVideoFolder(courseId: number, lessonId: number): string {
return `courses/${courseId}/lessons/${lessonId}/video/`;
}