update router getType
All checks were successful
Deploy to Production / deploy (push) Successful in 21s
All checks were successful
Deploy to Production / deploy (push) Successful in 21s
This commit is contained in:
126
UPLOAD_GUIDE.md
Normal file
126
UPLOAD_GUIDE.md
Normal 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
3
app.js
@@ -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
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|||||||
@@ -263,12 +263,6 @@ class TeacherController {
|
|||||||
next(error);
|
next(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
jobId: job.id,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
next(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get teacher datatypes
|
* Get teacher datatypes
|
||||||
|
|||||||
276
controllers/uploadController.js
Normal file
276
controllers/uploadController.js
Normal 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();
|
||||||
@@ -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
96
package-lock.json
generated
@@ -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",
|
||||||
|
|||||||
@@ -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
3
public/files/.gitkeep
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Thư mục lưu trữ các file khác
|
||||||
|
*
|
||||||
|
!.gitkeep
|
||||||
10
public/images/.gitkeep
Normal file
10
public/images/.gitkeep
Normal 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
BIN
public/images/quiz01.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 120 KiB |
BIN
public/images/quiz02.png
Normal file
BIN
public/images/quiz02.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 97 KiB |
BIN
public/images/quiz03.png
Normal file
BIN
public/images/quiz03.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 88 KiB |
BIN
public/images/quiz04.png
Normal file
BIN
public/images/quiz04.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 112 KiB |
BIN
public/images/quiz05.png
Normal file
BIN
public/images/quiz05.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 68 KiB |
16
public/media/.gitkeep
Normal file
16
public/media/.gitkeep
Normal 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
119
routes/uploadRoutes.js
Normal 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;
|
||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
66
yarn.lock
66
yarn.lock
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user