update router getType
All checks were successful
Deploy to Production / deploy (push) Successful in 21s

This commit is contained in:
silverpro89
2026-01-28 18:59:06 +07:00
parent 3791b7cae1
commit fa5c293a7e
19 changed files with 717 additions and 11 deletions

126
UPLOAD_GUIDE.md Normal file
View File

@@ -0,0 +1,126 @@
# Upload API
API endpoint để upload các loại file khác nhau.
## Quy tắc phân loại file
- **Images** (jpg, jpeg, png, gif, bmp, webp, svg, ico) → `public/images/`
- **Audio** (mp3, wav, ogg, m4a, aac, flac, wma) → `public/media/`
- **Video** (mp4, avi, mov, wmv, flv, mkv, webm) → `public/media/`
- **Các file khác** → `public/files/`
## API Endpoints
### 1. Upload single file
```
POST /api/upload/single
Content-Type: multipart/form-data
Body:
- file: (binary file)
```
**Response:**
```json
{
"success": true,
"message": "File uploaded successfully",
"data": {
"filename": "example-1234567890.jpg",
"originalname": "example.jpg",
"mimetype": "image/jpeg",
"size": 12345,
"type": "image",
"path": "public/images/example-1234567890.jpg",
"url": "/public/images/example-1234567890.jpg"
}
}
```
### 2. Upload multiple files
```
POST /api/upload/multiple
Content-Type: multipart/form-data
Body:
- files: (array of binary files, max 10)
```
**Response:**
```json
{
"success": true,
"message": "2 files uploaded successfully",
"data": [
{
"filename": "photo1-1234567890.jpg",
"originalname": "photo1.jpg",
"mimetype": "image/jpeg",
"size": 12345,
"type": "image",
"path": "public/images/photo1-1234567890.jpg",
"url": "/public/images/photo1-1234567890.jpg"
},
{
"filename": "audio-1234567891.mp3",
"originalname": "audio.mp3",
"mimetype": "audio/mpeg",
"size": 54321,
"type": "audio",
"path": "public/media/audio-1234567891.mp3",
"url": "/public/media/audio-1234567891.mp3"
}
]
}
```
### 3. Delete file
```
DELETE /api/upload/file
Content-Type: application/json
Body:
{
"filepath": "public/images/example-1234567890.jpg"
}
```
### 4. Get file info
```
GET /api/upload/info?filepath=public/images/example-1234567890.jpg
```
## Giới hạn
- **Max file size:** 50MB
- **Max files per upload (multiple):** 10 files
## Sử dụng với Postman/cURL
### Postman:
1. Chọn POST method
2. URL: `http://localhost:10001/api/upload/single`
3. Body → form-data
4. Key: `file`, Type: File
5. Chọn file cần upload
### cURL:
```bash
# Upload single file
curl -X POST http://localhost:10001/api/upload/single \
-F "file=@/path/to/your/file.jpg"
# Upload multiple files
curl -X POST http://localhost:10001/api/upload/multiple \
-F "files=@/path/to/file1.jpg" \
-F "files=@/path/to/file2.mp3"
```
## Truy cập file đã upload
Sau khi upload thành công, file có thể truy cập qua URL:
```
http://localhost:10001/public/images/example-1234567890.jpg
http://localhost:10001/public/media/audio-1234567890.mp3
http://localhost:10001/public/files/document-1234567890.pdf
```

3
app.js
View File

@@ -39,6 +39,7 @@ const vocabRoutes = require('./routes/vocabRoutes');
const grammarRoutes = require('./routes/grammarRoutes'); const grammarRoutes = require('./routes/grammarRoutes');
const storyRoutes = require('./routes/storyRoutes'); const storyRoutes = require('./routes/storyRoutes');
const learningContentRoutes = require('./routes/learningContentRoutes'); const learningContentRoutes = require('./routes/learningContentRoutes');
const uploadRoutes = require('./routes/uploadRoutes');
/** /**
* Initialize Express Application * Initialize Express Application
@@ -153,6 +154,7 @@ app.get('/api', (req, res) => {
games: '/api/games', games: '/api/games',
gameTypes: '/api/game-types', gameTypes: '/api/game-types',
vocab: '/api/vocab', vocab: '/api/vocab',
upload: '/api/upload',
}, },
documentation: '/api-docs', documentation: '/api-docs',
}); });
@@ -210,6 +212,7 @@ app.use('/api/vocab', vocabRoutes);
app.use('/api/grammar', grammarRoutes); app.use('/api/grammar', grammarRoutes);
app.use('/api/stories', storyRoutes); app.use('/api/stories', storyRoutes);
app.use('/api/learning-content', learningContentRoutes); app.use('/api/learning-content', learningContentRoutes);
app.use('/api/upload', uploadRoutes);
/** /**
* Queue Status Endpoint * Queue Status Endpoint

View File

@@ -16,6 +16,7 @@ class GameTypeController {
const cacheKey = `game_types:list:${page}:${limit}:${is_active || 'all'}:${is_premium || 'all'}:${difficulty_level || 'all'}`; const cacheKey = `game_types:list:${page}:${limit}:${is_active || 'all'}:${is_premium || 'all'}:${difficulty_level || 'all'}`;
const cached = await cacheUtils.get(cacheKey); const cached = await cacheUtils.get(cacheKey);
/*
if (cached) { if (cached) {
return res.json({ return res.json({
success: true, success: true,
@@ -23,6 +24,7 @@ class GameTypeController {
cached: true, cached: true,
}); });
} }
*/
const where = {}; const where = {};
if (is_active !== undefined) where.is_active = is_active === 'true'; if (is_active !== undefined) where.is_active = is_active === 'true';

View File

@@ -263,12 +263,6 @@ class TeacherController {
next(error); next(error);
} }
} }
jobId: job.id,
});
} catch (error) {
next(error);
}
}
/** /**
* Get teacher datatypes * Get teacher datatypes

View File

@@ -0,0 +1,276 @@
const multer = require('multer');
const path = require('path');
const fs = require('fs');
/**
* Upload Controller - Quản lý upload file
*/
class UploadController {
constructor() {
// Danh sách các extension cho từng loại file
this.imageExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp', '.svg', '.ico'];
this.audioExtensions = ['.mp3', '.wav', '.ogg', '.m4a', '.aac', '.flac', '.wma'];
this.videoExtensions = ['.mp4', '.avi', '.mov', '.wmv', '.flv', '.mkv', '.webm'];
// Tạo các thư mục nếu chưa tồn tại
this.ensureDirectories();
// Cấu hình multer storage
this.storage = multer.diskStorage({
destination: (req, file, cb) => {
const fileExt = path.extname(file.originalname).toLowerCase();
let uploadPath = 'public/files';
if (this.imageExtensions.includes(fileExt)) {
uploadPath = 'public/images';
} else if (this.audioExtensions.includes(fileExt) || this.videoExtensions.includes(fileExt)) {
uploadPath = 'public/media';
}
cb(null, uploadPath);
},
filename: (req, file, cb) => {
// Tạo tên file unique: timestamp-random-originalname
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
const ext = path.extname(file.originalname);
const nameWithoutExt = path.basename(file.originalname, ext);
const sanitizedName = nameWithoutExt.replace(/[^a-zA-Z0-9]/g, '_');
cb(null, `${sanitizedName}-${uniqueSuffix}${ext}`);
}
});
// Cấu hình multer với giới hạn kích thước
this.upload = multer({
storage: this.storage,
limits: {
fileSize: 50 * 1024 * 1024, // 50MB max file size
},
fileFilter: (req, file, cb) => {
// Cho phép tất cả các loại file
cb(null, true);
}
});
}
/**
* Đảm bảo các thư mục upload tồn tại
*/
ensureDirectories() {
const directories = [
'public/images',
'public/media',
'public/files'
];
directories.forEach(dir => {
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
});
}
/**
* Upload single file
*/
uploadSingle(req, res, next) {
const uploadHandler = this.upload.single('file');
uploadHandler(req, res, (err) => {
if (err instanceof multer.MulterError) {
return res.status(400).json({
success: false,
message: 'Upload error',
error: err.message,
});
} else if (err) {
return res.status(500).json({
success: false,
message: 'Server error during upload',
error: err.message,
});
}
if (!req.file) {
return res.status(400).json({
success: false,
message: 'No file uploaded',
});
}
// Xác định loại file
const fileExt = path.extname(req.file.originalname).toLowerCase();
let fileType = 'file';
if (this.imageExtensions.includes(fileExt)) {
fileType = 'image';
} else if (this.audioExtensions.includes(fileExt)) {
fileType = 'audio';
} else if (this.videoExtensions.includes(fileExt)) {
fileType = 'video';
}
// Tạo URL truy cập file
const fileUrl = `/${req.file.path.replace(/\\/g, '/')}`;
res.status(201).json({
success: true,
message: 'File uploaded successfully',
data: {
filename: req.file.filename,
originalname: req.file.originalname,
mimetype: req.file.mimetype,
size: req.file.size,
type: fileType,
path: req.file.path,
url: fileUrl,
},
});
});
}
/**
* Upload multiple files
*/
uploadMultiple(req, res, next) {
const uploadHandler = this.upload.array('files', 10); // Max 10 files
uploadHandler(req, res, (err) => {
if (err instanceof multer.MulterError) {
return res.status(400).json({
success: false,
message: 'Upload error',
error: err.message,
});
} else if (err) {
return res.status(500).json({
success: false,
message: 'Server error during upload',
error: err.message,
});
}
if (!req.files || req.files.length === 0) {
return res.status(400).json({
success: false,
message: 'No files uploaded',
});
}
const uploadedFiles = req.files.map(file => {
const fileExt = path.extname(file.originalname).toLowerCase();
let fileType = 'file';
if (this.imageExtensions.includes(fileExt)) {
fileType = 'image';
} else if (this.audioExtensions.includes(fileExt)) {
fileType = 'audio';
} else if (this.videoExtensions.includes(fileExt)) {
fileType = 'video';
}
const fileUrl = `/${file.path.replace(/\\/g, '/')}`;
return {
filename: file.filename,
originalname: file.originalname,
mimetype: file.mimetype,
size: file.size,
type: fileType,
path: file.path,
url: fileUrl,
};
});
res.status(201).json({
success: true,
message: `${uploadedFiles.length} files uploaded successfully`,
data: uploadedFiles,
});
});
}
/**
* Delete file
*/
async deleteFile(req, res, next) {
try {
const { filepath } = req.body;
if (!filepath) {
return res.status(400).json({
success: false,
message: 'Filepath is required',
});
}
// Kiểm tra file có tồn tại không
if (!fs.existsSync(filepath)) {
return res.status(404).json({
success: false,
message: 'File not found',
});
}
// Xóa file
fs.unlinkSync(filepath);
res.json({
success: true,
message: 'File deleted successfully',
});
} catch (error) {
next(error);
}
}
/**
* Get file info
*/
async getFileInfo(req, res, next) {
try {
const { filepath } = req.query;
if (!filepath) {
return res.status(400).json({
success: false,
message: 'Filepath is required',
});
}
// Kiểm tra file có tồn tại không
if (!fs.existsSync(filepath)) {
return res.status(404).json({
success: false,
message: 'File not found',
});
}
const stats = fs.statSync(filepath);
const fileExt = path.extname(filepath).toLowerCase();
let fileType = 'file';
if (this.imageExtensions.includes(fileExt)) {
fileType = 'image';
} else if (this.audioExtensions.includes(fileExt)) {
fileType = 'audio';
} else if (this.videoExtensions.includes(fileExt)) {
fileType = 'video';
}
res.json({
success: true,
data: {
path: filepath,
size: stats.size,
type: fileType,
created: stats.birthtime,
modified: stats.mtime,
},
});
} catch (error) {
next(error);
}
}
}
module.exports = new UploadController();

View File

@@ -31,7 +31,7 @@ const GameType = sequelize.define('game_types', {
comment: 'Mô tả loại game (tiếng Việt)' comment: 'Mô tả loại game (tiếng Việt)'
}, },
type: { type: {
type: DataTypes.TEXT, type: DataTypes.STRING(100),
allowNull: false, allowNull: false,
unique: true, unique: true,
comment: 'Mã định danh loại game: counting_quiz, math_game, word_puzzle, etc.' comment: 'Mã định danh loại game: counting_quiz, math_game, word_puzzle, etc.'

96
package-lock.json generated
View File

@@ -22,6 +22,7 @@
"joi": "^17.11.0", "joi": "^17.11.0",
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.2",
"morgan": "^1.10.0", "morgan": "^1.10.0",
"multer": "^2.0.2",
"mysql2": "^3.6.5", "mysql2": "^3.6.5",
"sequelize": "^6.35.2", "sequelize": "^6.35.2",
"swagger-jsdoc": "^6.2.8", "swagger-jsdoc": "^6.2.8",
@@ -1656,6 +1657,12 @@
"node": ">= 8" "node": ">= 8"
} }
}, },
"node_modules/append-field": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz",
"integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==",
"license": "MIT"
},
"node_modules/aproba": { "node_modules/aproba": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/aproba/-/aproba-2.1.0.tgz", "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.1.0.tgz",
@@ -2019,7 +2026,6 @@
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/bullmq": { "node_modules/bullmq": {
@@ -2037,6 +2043,17 @@
"uuid": "11.1.0" "uuid": "11.1.0"
} }
}, },
"node_modules/busboy": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
"integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==",
"dependencies": {
"streamsearch": "^1.1.0"
},
"engines": {
"node": ">=10.16.0"
}
},
"node_modules/bytes": { "node_modules/bytes": {
"version": "3.1.2", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
@@ -2422,6 +2439,21 @@
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/concat-stream": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz",
"integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==",
"engines": [
"node >= 6.0"
],
"license": "MIT",
"dependencies": {
"buffer-from": "^1.0.0",
"inherits": "^2.0.3",
"readable-stream": "^3.0.2",
"typedarray": "^0.0.6"
}
},
"node_modules/console-control-strings": { "node_modules/console-control-strings": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
@@ -5150,6 +5182,15 @@
"node": "*" "node": "*"
} }
}, },
"node_modules/minimist": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/minipass": { "node_modules/minipass": {
"version": "3.3.6", "version": "3.3.6",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
@@ -5288,6 +5329,36 @@
"@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.3" "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.3"
} }
}, },
"node_modules/multer": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/multer/-/multer-2.0.2.tgz",
"integrity": "sha512-u7f2xaZ/UG8oLXHvtF/oWTRvT44p9ecwBBqTwgJVq0+4BW1g8OW01TyMEGWBHbyMOYVHXslaut7qEQ1meATXgw==",
"license": "MIT",
"dependencies": {
"append-field": "^1.0.0",
"busboy": "^1.6.0",
"concat-stream": "^2.0.0",
"mkdirp": "^0.5.6",
"object-assign": "^4.1.1",
"type-is": "^1.6.18",
"xtend": "^4.0.2"
},
"engines": {
"node": ">= 10.16.0"
}
},
"node_modules/multer/node_modules/mkdirp": {
"version": "0.5.6",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
"integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
"license": "MIT",
"dependencies": {
"minimist": "^1.2.6"
},
"bin": {
"mkdirp": "bin/cmd.js"
}
},
"node_modules/mysql2": { "node_modules/mysql2": {
"version": "3.16.0", "version": "3.16.0",
"resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.16.0.tgz", "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.16.0.tgz",
@@ -6494,6 +6565,14 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/streamsearch": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
"integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==",
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/string_decoder": { "node_modules/string_decoder": {
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
@@ -6889,6 +6968,12 @@
"node": ">= 0.6" "node": ">= 0.6"
} }
}, },
"node_modules/typedarray": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
"integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==",
"license": "MIT"
},
"node_modules/undefsafe": { "node_modules/undefsafe": {
"version": "2.0.5", "version": "2.0.5",
"resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
@@ -7157,6 +7242,15 @@
"node": "^12.13.0 || ^14.15.0 || >=16.0.0" "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
} }
}, },
"node_modules/xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
"license": "MIT",
"engines": {
"node": ">=0.4"
}
},
"node_modules/y18n": { "node_modules/y18n": {
"version": "5.0.8", "version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",

View File

@@ -45,6 +45,7 @@
"joi": "^17.11.0", "joi": "^17.11.0",
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.2",
"morgan": "^1.10.0", "morgan": "^1.10.0",
"multer": "^2.0.2",
"mysql2": "^3.6.5", "mysql2": "^3.6.5",
"sequelize": "^6.35.2", "sequelize": "^6.35.2",
"swagger-jsdoc": "^6.2.8", "swagger-jsdoc": "^6.2.8",

3
public/files/.gitkeep Normal file
View File

@@ -0,0 +1,3 @@
# Thư mục lưu trữ các file khác
*
!.gitkeep

10
public/images/.gitkeep Normal file
View File

@@ -0,0 +1,10 @@
# Thư mục lưu trữ hình ảnh
*.jpg
*.jpeg
*.png
*.gif
*.bmp
*.webp
*.svg
*.ico
!.gitkeep

BIN
public/images/quiz01.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

BIN
public/images/quiz02.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

BIN
public/images/quiz03.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

BIN
public/images/quiz04.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

BIN
public/images/quiz05.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

16
public/media/.gitkeep Normal file
View File

@@ -0,0 +1,16 @@
# Thư mục lưu trữ media (audio/video)
*.mp3
*.wav
*.ogg
*.m4a
*.aac
*.flac
*.wma
*.mp4
*.avi
*.mov
*.wmv
*.flv
*.mkv
*.webm
!.gitkeep

119
routes/uploadRoutes.js Normal file
View File

@@ -0,0 +1,119 @@
const express = require('express');
const router = express.Router();
const uploadController = require('../controllers/uploadController');
/**
* @swagger
* tags:
* name: Upload
* description: File upload management
*/
/**
* @swagger
* /api/upload/single:
* post:
* summary: Upload single file
* tags: [Upload]
* requestBody:
* required: true
* content:
* multipart/form-data:
* schema:
* type: object
* properties:
* file:
* type: string
* format: binary
* description: File to upload (images → public/images, audio/video → public/media, others → public/files)
* responses:
* 201:
* description: File uploaded successfully
* 400:
* description: No file uploaded or upload error
* 500:
* description: Server error
*/
router.post('/single', (req, res, next) => {
uploadController.uploadSingle(req, res, next);
});
/**
* @swagger
* /api/upload/multiple:
* post:
* summary: Upload multiple files (max 10)
* tags: [Upload]
* requestBody:
* required: true
* content:
* multipart/form-data:
* schema:
* type: object
* properties:
* files:
* type: array
* items:
* type: string
* format: binary
* description: Files to upload (max 10 files)
* responses:
* 201:
* description: Files uploaded successfully
* 400:
* description: No files uploaded or upload error
* 500:
* description: Server error
*/
router.post('/multiple', (req, res, next) => {
uploadController.uploadMultiple(req, res, next);
});
/**
* @swagger
* /api/upload/file:
* delete:
* summary: Delete uploaded file
* tags: [Upload]
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - filepath
* properties:
* filepath:
* type: string
* example: "public/images/example-1234567890.jpg"
* responses:
* 200:
* description: File deleted successfully
* 404:
* description: File not found
*/
router.delete('/file', uploadController.deleteFile);
/**
* @swagger
* /api/upload/info:
* get:
* summary: Get file information
* tags: [Upload]
* parameters:
* - in: query
* name: filepath
* required: true
* schema:
* type: string
* description: Path to the file
* responses:
* 200:
* description: File info retrieved successfully
* 404:
* description: File not found
*/
router.get('/info', uploadController.getFileInfo);
module.exports = router;

View File

@@ -5,7 +5,7 @@ const { closeQueues } = require('./config/bullmq');
const config = require('./config/config.json'); const config = require('./config/config.json');
const PORT = config.server.port || 3000; const PORT = config.server.port || 3000;
const HOST = '0.0.0.0'; const HOST = 'localhost';
let server; let server;

View File

@@ -939,6 +939,11 @@ anymatch@^3.0.3, anymatch@~3.1.2:
normalize-path "^3.0.0" normalize-path "^3.0.0"
picomatch "^2.0.4" picomatch "^2.0.4"
append-field@^1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz"
integrity sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==
"aproba@^1.0.3 || ^2.0.0": "aproba@^1.0.3 || ^2.0.0":
version "2.1.0" version "2.1.0"
resolved "https://registry.npmjs.org/aproba/-/aproba-2.1.0.tgz" resolved "https://registry.npmjs.org/aproba/-/aproba-2.1.0.tgz"
@@ -1156,6 +1161,13 @@ bullmq@^5.1.0:
tslib "2.8.1" tslib "2.8.1"
uuid "11.1.0" uuid "11.1.0"
busboy@^1.6.0:
version "1.6.0"
resolved "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz"
integrity sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==
dependencies:
streamsearch "^1.1.0"
bytes@~3.1.2, bytes@3.1.2: bytes@~3.1.2, bytes@3.1.2:
version "3.1.2" version "3.1.2"
resolved "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz" resolved "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz"
@@ -1360,6 +1372,16 @@ concat-map@0.0.1:
resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz"
integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
concat-stream@^2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz"
integrity sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==
dependencies:
buffer-from "^1.0.0"
inherits "^2.0.3"
readable-stream "^3.0.2"
typedarray "^0.0.6"
console-control-strings@^1.0.0, console-control-strings@^1.1.0: console-control-strings@^1.0.0, console-control-strings@^1.1.0:
version "1.1.0" version "1.1.0"
resolved "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz" resolved "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz"
@@ -3029,6 +3051,11 @@ minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2:
dependencies: dependencies:
brace-expansion "^1.1.7" brace-expansion "^1.1.7"
minimist@^1.2.6:
version "1.2.8"
resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz"
integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==
minipass@^3.0.0: minipass@^3.0.0:
version "3.3.6" version "3.3.6"
resolved "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz" resolved "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz"
@@ -3049,6 +3076,13 @@ minizlib@^2.1.1:
minipass "^3.0.0" minipass "^3.0.0"
yallist "^4.0.0" yallist "^4.0.0"
mkdirp@^0.5.6:
version "0.5.6"
resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz"
integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==
dependencies:
minimist "^1.2.6"
mkdirp@^1.0.3: mkdirp@^1.0.3:
version "1.0.4" version "1.0.4"
resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz" resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz"
@@ -3108,6 +3142,19 @@ msgpackr@1.11.5:
optionalDependencies: optionalDependencies:
msgpackr-extract "^3.0.2" msgpackr-extract "^3.0.2"
multer@^2.0.2:
version "2.0.2"
resolved "https://registry.npmjs.org/multer/-/multer-2.0.2.tgz"
integrity sha512-u7f2xaZ/UG8oLXHvtF/oWTRvT44p9ecwBBqTwgJVq0+4BW1g8OW01TyMEGWBHbyMOYVHXslaut7qEQ1meATXgw==
dependencies:
append-field "^1.0.0"
busboy "^1.6.0"
concat-stream "^2.0.0"
mkdirp "^0.5.6"
object-assign "^4.1.1"
type-is "^1.6.18"
xtend "^4.0.2"
mysql2@^3.6.5: mysql2@^3.6.5:
version "3.16.0" version "3.16.0"
resolved "https://registry.npmjs.org/mysql2/-/mysql2-3.16.0.tgz" resolved "https://registry.npmjs.org/mysql2/-/mysql2-3.16.0.tgz"
@@ -3470,7 +3517,7 @@ react-is@^18.0.0:
resolved "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz" resolved "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz"
integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==
readable-stream@^3.4.0, readable-stream@^3.6.0, readable-stream@^3.6.2: readable-stream@^3.0.2, readable-stream@^3.4.0, readable-stream@^3.6.0, readable-stream@^3.6.2:
version "3.6.2" version "3.6.2"
resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz" resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz"
integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==
@@ -3776,6 +3823,11 @@ statuses@~2.0.1, statuses@~2.0.2:
resolved "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz" resolved "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz"
integrity sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw== integrity sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==
streamsearch@^1.1.0:
version "1.1.0"
resolved "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz"
integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==
string_decoder@^1.1.1: string_decoder@^1.1.1:
version "1.3.0" version "1.3.0"
resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz" resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz"
@@ -3989,7 +4041,7 @@ type-fest@^0.21.3:
resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz" resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz"
integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==
type-is@~1.6.18: type-is@^1.6.18, type-is@~1.6.18:
version "1.6.18" version "1.6.18"
resolved "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz" resolved "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz"
integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==
@@ -3997,6 +4049,11 @@ type-is@~1.6.18:
media-typer "0.3.0" media-typer "0.3.0"
mime-types "~2.1.24" mime-types "~2.1.24"
typedarray@^0.0.6:
version "0.0.6"
resolved "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz"
integrity sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==
undefsafe@^2.0.5: undefsafe@^2.0.5:
version "2.0.5" version "2.0.5"
resolved "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz" resolved "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz"
@@ -4160,6 +4217,11 @@ write-file-atomic@^4.0.2:
imurmurhash "^0.1.4" imurmurhash "^0.1.4"
signal-exit "^3.0.7" signal-exit "^3.0.7"
xtend@^4.0.2:
version "4.0.2"
resolved "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz"
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
y18n@^5.0.5: y18n@^5.0.5:
version "5.0.8" version "5.0.8"
resolved "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz" resolved "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz"