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

View file

@ -0,0 +1,121 @@
import multer from 'multer';
import { Request } from 'express';
import { config } from '../config';
import { ValidationError } from './errorHandler';
/**
* Allowed MIME types for different file categories
*/
export const ALLOWED_VIDEO_TYPES = [
'video/mp4',
'video/quicktime',
'video/x-msvideo',
'video/webm',
'video/x-matroska',
];
export const ALLOWED_ATTACHMENT_TYPES = [
// Documents
'application/pdf',
'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'application/vnd.ms-excel',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'application/vnd.ms-powerpoint',
'application/vnd.openxmlformats-officedocument.presentationml.presentation',
// Text
'text/plain',
'text/csv',
// Images
'image/jpeg',
'image/png',
'image/gif',
'image/webp',
'image/svg+xml',
// Archives
'application/zip',
'application/x-rar-compressed',
'application/x-7z-compressed',
];
/**
* Custom file filter for lesson uploads
*/
const fileFilter = (
req: Request,
file: Express.Multer.File,
callback: multer.FileFilterCallback
) => {
if (file.fieldname === 'video') {
if (ALLOWED_VIDEO_TYPES.includes(file.mimetype)) {
callback(null, true);
} else {
callback(new ValidationError(`Invalid video file type: ${file.mimetype}. Allowed types: ${ALLOWED_VIDEO_TYPES.join(', ')}`));
}
} else if (file.fieldname === 'attachments') {
if (ALLOWED_ATTACHMENT_TYPES.includes(file.mimetype)) {
callback(null, true);
} else {
callback(new ValidationError(`Invalid attachment file type: ${file.mimetype}. Allowed types: PDF, Word, Excel, PowerPoint, images, and archives`));
}
} else {
callback(new ValidationError(`Unknown field: ${file.fieldname}`));
}
};
/**
* Multer configuration for lesson file uploads
* Stores files in memory for direct upload to MinIO
*/
export const lessonUpload = multer({
storage: multer.memoryStorage(),
limits: {
fileSize: config.upload.maxVideoSize, // Max file size (500MB for videos)
files: config.upload.maxAttachmentsPerLesson + 1, // +1 for video
},
fileFilter,
}).fields([
{ name: 'video', maxCount: 1 },
{ name: 'attachments', maxCount: config.upload.maxAttachmentsPerLesson },
]);
/**
* Extended Request interface for uploaded files
*/
export interface LessonUploadRequest extends Request {
files?: {
video?: Express.Multer.File[];
attachments?: Express.Multer.File[];
};
}
/**
* Validate total attachment size
*/
export function validateTotalAttachmentSize(
files: Express.Multer.File[] | undefined
): void {
if (!files) return;
const totalSize = files.reduce((sum, file) => sum + file.size, 0);
const maxTotalSize = config.upload.maxAttachmentSize * config.upload.maxAttachmentsPerLesson;
if (totalSize > maxTotalSize) {
throw new ValidationError(
`Total attachment size (${(totalSize / 1024 / 1024).toFixed(2)} MB) exceeds maximum allowed (${(maxTotalSize / 1024 / 1024).toFixed(2)} MB)`
);
}
}
/**
* Validate video file size
*/
export function validateVideoSize(file: Express.Multer.File | undefined): void {
if (!file) return;
if (file.size > config.upload.maxVideoSize) {
throw new ValidationError(
`Video file size (${(file.size / 1024 / 1024).toFixed(2)} MB) exceeds maximum allowed (${(config.upload.maxVideoSize / 1024 / 1024).toFixed(2)} MB)`
);
}
}