feat: Implement lesson creation with file uploads (video, attachments) and quiz data, integrating MinIO for storage.

This commit is contained in:
JakkrapartXD 2026-01-20 16:51:42 +07:00
parent 4851182f4a
commit 04e2da43c4
6 changed files with 715 additions and 4 deletions

111
Backend/src/config/minio.ts Normal file
View file

@ -0,0 +1,111 @@
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;
}
}