migration to typescript

This commit is contained in:
JakkrapartXD 2026-01-09 06:28:15 +00:00
parent 924000b084
commit 9fde77468a
41 changed files with 11952 additions and 10164 deletions

View file

@ -4,54 +4,61 @@ description: How to handle file uploads (videos, attachments)
# File Upload Workflow
Follow these steps to implement file upload functionality for videos and attachments.
Follow these steps to implement file upload functionality for videos and attachments using TypeScript and TSOA.
---
## Prerequisites
- MinIO/S3 configured and running
- Multer installed: `npm install multer`
- AWS SDK or MinIO client installed
- Dependencies installed: `npm install multer @aws-sdk/client-s3 @aws-sdk/lib-storage`
- TypeScript configured
---
## Step 1: Configure S3/MinIO Client
Create `src/config/s3.config.js`:
Create `src/config/s3.config.ts`:
```javascript
const { S3Client } = require('@aws-sdk/client-s3');
const { Upload } = require('@aws-sdk/lib-storage');
```typescript
import { S3Client } from '@aws-sdk/client-s3';
const s3Client = new S3Client({
export const s3Client = new S3Client({
endpoint: process.env.S3_ENDPOINT,
region: process.env.S3_REGION || 'us-east-1',
credentials: {
accessKeyId: process.env.S3_ACCESS_KEY,
secretAccessKey: process.env.S3_SECRET_KEY
accessKeyId: process.env.S3_ACCESS_KEY!,
secretAccessKey: process.env.S3_SECRET_KEY!
},
forcePathStyle: true // Required for MinIO
});
module.exports = { s3Client };
```
---
## Step 2: Create Upload Service
Create `src/services/upload.service.js`:
Create `src/services/upload.service.ts`:
```javascript
const { s3Client } = require('../config/s3.config');
const { Upload } = require('@aws-sdk/lib-storage');
const { DeleteObjectCommand } = require('@aws-sdk/client-s3');
const { v4: uuidv4 } = require('uuid');
const path = require('path');
```typescript
import { s3Client } from '../config/s3.config';
import { Upload } from '@aws-sdk/lib-storage';
import { DeleteObjectCommand } from '@aws-sdk/client-s3';
import { v4 as uuidv4 } from 'uuid';
import path from 'path';
export interface UploadResult {
key: string;
url: string;
fileName: string;
fileSize: number;
mimeType: string;
}
type FolderType = 'videos' | 'documents' | 'images' | 'attachments';
class UploadService {
async uploadFile(file, folder) {
async uploadFile(file: Express.Multer.File, folder: FolderType): Promise<UploadResult> {
const fileExt = path.extname(file.originalname);
const fileName = `${Date.now()}-${uuidv4()}${fileExt}`;
const key = `${folder}/${fileName}`;
@ -77,7 +84,7 @@ class UploadService {
};
}
async deleteFile(key, folder) {
async deleteFile(key: string, folder: FolderType): Promise<void> {
const command = new DeleteObjectCommand({
Bucket: this.getBucket(folder),
Key: key
@ -86,179 +93,182 @@ class UploadService {
await s3Client.send(command);
}
getBucket(folder) {
const bucketMap = {
videos: process.env.S3_BUCKET_VIDEOS,
documents: process.env.S3_BUCKET_DOCUMENTS,
images: process.env.S3_BUCKET_IMAGES,
attachments: process.env.S3_BUCKET_ATTACHMENTS
private getBucket(folder: FolderType): string {
const bucketMap: Record<FolderType, string> = {
videos: process.env.S3_BUCKET_VIDEOS!,
documents: process.env.S3_BUCKET_DOCUMENTS!,
images: process.env.S3_BUCKET_IMAGES!,
attachments: process.env.S3_BUCKET_ATTACHMENTS!
};
return bucketMap[folder] || process.env.S3_BUCKET_COURSES;
return bucketMap[folder] || process.env.S3_BUCKET_COURSES!;
}
validateFileType(file, allowedTypes) {
validateFileType(file: Express.Multer.File, allowedTypes: string[]): boolean {
return allowedTypes.includes(file.mimetype);
}
validateFileSize(file, maxSize) {
validateFileSize(file: Express.Multer.File, maxSize: number): boolean {
return file.size <= maxSize;
}
}
module.exports = new UploadService();
export const uploadService = new UploadService();
```
---
## Step 3: Create Upload Middleware
Create `src/middleware/upload.middleware.js`:
Create `src/middleware/upload.middleware.ts`:
```javascript
const multer = require('multer');
```typescript
import multer from 'multer';
import { Request } from 'express';
// File type validators
const ALLOWED_VIDEO_TYPES = ['video/mp4', 'video/webm', 'video/quicktime'];
const ALLOWED_DOCUMENT_TYPES = [
export const ALLOWED_VIDEO_TYPES = ['video/mp4', 'video/webm', 'video/quicktime'];
export const ALLOWED_DOCUMENT_TYPES = [
'application/pdf',
'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'application/vnd.ms-excel',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
];
const ALLOWED_IMAGE_TYPES = ['image/jpeg', 'image/png', 'image/gif'];
export const ALLOWED_IMAGE_TYPES = ['image/jpeg', 'image/png', 'image/gif'];
// File size limits
const MAX_VIDEO_SIZE = 500 * 1024 * 1024; // 500 MB
const MAX_ATTACHMENT_SIZE = 100 * 1024 * 1024; // 100 MB
const MAX_IMAGE_SIZE = 10 * 1024 * 1024; // 10 MB
export const MAX_VIDEO_SIZE = 500 * 1024 * 1024; // 500 MB
export const MAX_ATTACHMENT_SIZE = 100 * 1024 * 1024; // 100 MB
export const MAX_IMAGE_SIZE = 10 * 1024 * 1024; // 10 MB
// Multer configuration
const storage = multer.memoryStorage();
const fileFilter = (allowedTypes) => (req, file, cb) => {
const fileFilter = (allowedTypes: string[]) => (
req: Request,
file: Express.Multer.File,
cb: multer.FileFilterCallback
) => {
if (allowedTypes.includes(file.mimetype)) {
cb(null, true);
} else {
cb(new Error('Invalid file type'), false);
cb(new Error('Invalid file type'));
}
};
// Upload configurations
const uploadVideo = multer({
export const uploadVideo = multer({
storage,
limits: { fileSize: MAX_VIDEO_SIZE },
fileFilter: fileFilter(ALLOWED_VIDEO_TYPES)
}).single('video');
const uploadAttachment = multer({
export const uploadAttachment = multer({
storage,
limits: { fileSize: MAX_ATTACHMENT_SIZE },
fileFilter: fileFilter([...ALLOWED_DOCUMENT_TYPES, ...ALLOWED_IMAGE_TYPES])
}).single('file');
const uploadAttachments = multer({
export const uploadAttachments = multer({
storage,
limits: { fileSize: MAX_ATTACHMENT_SIZE },
fileFilter: fileFilter([...ALLOWED_DOCUMENT_TYPES, ...ALLOWED_IMAGE_TYPES])
}).array('attachments', 10); // Max 10 files
const uploadImage = multer({
export const uploadImage = multer({
storage,
limits: { fileSize: MAX_IMAGE_SIZE },
fileFilter: fileFilter(ALLOWED_IMAGE_TYPES)
}).single('image');
module.exports = {
uploadVideo,
uploadAttachment,
uploadAttachments,
uploadImage,
ALLOWED_VIDEO_TYPES,
ALLOWED_DOCUMENT_TYPES,
ALLOWED_IMAGE_TYPES
};
```
---
## Step 4: Create Upload Controller
## Step 4: Create Upload Controller with TSOA
Create `src/controllers/upload.controller.js`:
Create `src/controllers/upload.controller.ts`:
```javascript
const uploadService = require('../services/upload.service');
```typescript
import { Request } from 'express';
import { Controller, Post, Route, Tags, Security, UploadedFile, SuccessResponse } from 'tsoa';
import { uploadService } from '../services/upload.service';
class UploadController {
async uploadVideo(req, res) {
try {
if (!req.file) {
return res.status(400).json({
error: {
code: 'NO_FILE',
message: 'No video file provided'
}
});
}
const result = await uploadService.uploadFile(
req.file,
'videos'
);
return res.status(200).json({
video_url: result.url,
file_size: result.fileSize,
duration: null // Will be processed later
});
} catch (error) {
console.error('Upload video error:', error);
return res.status(500).json({
error: {
code: 'UPLOAD_FAILED',
message: 'Failed to upload video'
}
});
}
}
async uploadAttachment(req, res) {
try {
if (!req.file) {
return res.status(400).json({
error: {
code: 'NO_FILE',
message: 'No file provided'
}
});
}
const result = await uploadService.uploadFile(
req.file,
'attachments'
);
return res.status(200).json({
file_name: result.fileName,
file_path: result.key,
file_size: result.fileSize,
mime_type: result.mimeType,
download_url: result.url
});
} catch (error) {
console.error('Upload attachment error:', error);
return res.status(500).json({
error: {
code: 'UPLOAD_FAILED',
message: 'Failed to upload file'
}
});
}
}
interface UploadResponse {
video_url?: string;
file_size: number;
duration?: number | null;
file_name?: string;
file_path?: string;
mime_type?: string;
download_url?: string;
}
module.exports = new UploadController();
@Route('api/upload')
@Tags('File Upload')
export class UploadController extends Controller {
/**
* Upload video file
* @summary Upload video for lesson (Instructor/Admin only)
*/
@Post('video')
@Security('jwt', ['INSTRUCTOR', 'ADMIN'])
@SuccessResponse(200, 'Success')
public async uploadVideo(
@UploadedFile() file: Express.Multer.File
): Promise<UploadResponse> {
if (!file) {
this.setStatus(400);
throw new Error('No video file provided');
}
const result = await uploadService.uploadFile(file, 'videos');
return {
video_url: result.url,
file_size: result.fileSize,
duration: null // Will be processed later
};
}
/**
* Upload attachment file
* @summary Upload attachment for lesson (Instructor/Admin only)
*/
@Post('attachment')
@Security('jwt', ['INSTRUCTOR', 'ADMIN'])
@SuccessResponse(200, 'Success')
public async uploadAttachment(
@UploadedFile() file: Express.Multer.File
): Promise<UploadResponse> {
if (!file) {
this.setStatus(400);
throw new Error('No file provided');
}
const result = await uploadService.uploadFile(file, 'attachments');
return {
file_name: result.fileName,
file_path: result.key,
file_size: result.fileSize,
mime_type: result.mimeType,
download_url: result.url
};
}
}
```
---
## Step 5: Generate TSOA Routes
// turbo
After creating the upload controller, generate routes and API documentation:
```bash
npm run tsoa:gen
```
---