feat: upload, get, and delete level file function
This commit is contained in:
parent
411f392e24
commit
2e669bc281
|
|
@ -716,6 +716,54 @@ export const deleteLevelFileById = async (req, res) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const deleteLevelFilesById = async (req, res) => {
|
||||||
|
const { id } = req.params;
|
||||||
|
const { fileName } = req.body;
|
||||||
|
|
||||||
|
const filePattern =
|
||||||
|
/^(AUDIO|IMAGE)-([a-f0-9-]{36})-[a-f0-9]{32}\.(mp3|jpg|jpeg|png)$/;
|
||||||
|
const match = fileName.match(filePattern);
|
||||||
|
|
||||||
|
if (!match) {
|
||||||
|
return response(400, null, "Invalid file name format", res);
|
||||||
|
}
|
||||||
|
|
||||||
|
const fileType = match[1];
|
||||||
|
const levelIdFromFile = match[2];
|
||||||
|
|
||||||
|
if (levelIdFromFile !== id) {
|
||||||
|
return response(400, null, "Level ID in file name does not match", res);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const level = await models.Level.findByPk(id);
|
||||||
|
|
||||||
|
if (!level) {
|
||||||
|
return response(404, null, "Level not found", res);
|
||||||
|
}
|
||||||
|
|
||||||
|
let filePath;
|
||||||
|
|
||||||
|
if (fileType === "AUDIO") {
|
||||||
|
filePath = path.join("public/uploads/level/audio", fileName);
|
||||||
|
} else if (fileType === "IMAGE") {
|
||||||
|
filePath = path.join("public/uploads/level/image", fileName);
|
||||||
|
} else {
|
||||||
|
return response(400, null, "Invalid file type", res);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filePath && fs.existsSync(filePath)) {
|
||||||
|
fs.unlinkSync(filePath);
|
||||||
|
return response(200, null, `${fileType} file deleted successfully`, res);
|
||||||
|
} else {
|
||||||
|
return response(404, null, "File not found on the server", res);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
response(500, null, "Internal Server Error", res);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const getPreviousLevel = async (req, res) => {
|
export const getPreviousLevel = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { next_learning } = req.params;
|
const { next_learning } = req.params;
|
||||||
|
|
@ -874,3 +922,96 @@ export const getPreviousLevel = async (req, res) => {
|
||||||
res.status(500).json({ message: "Internal Server Error" });
|
res.status(500).json({ message: "Internal Server Error" });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const uploadLevelFile = async (req, res) => {
|
||||||
|
const { levelId } = req.params;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const level = await models.Level.findByPk(levelId);
|
||||||
|
|
||||||
|
if (!level) {
|
||||||
|
return response(404, null, "Level not found", res);
|
||||||
|
}
|
||||||
|
|
||||||
|
const filesToSave = req.filesToSave;
|
||||||
|
|
||||||
|
if (!filesToSave || Object.keys(filesToSave).length === 0) {
|
||||||
|
return response(400, null, "No files to upload", res);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { ID_TOPIC, ID_SECTION } = level;
|
||||||
|
const savedFiles = {};
|
||||||
|
|
||||||
|
Object.keys(filesToSave).forEach((key) => {
|
||||||
|
let filename;
|
||||||
|
if (key.startsWith("AUDIO")) {
|
||||||
|
const audioFile = filesToSave[key];
|
||||||
|
filename = saveFileToDisk(
|
||||||
|
audioFile,
|
||||||
|
"AUDIO",
|
||||||
|
ID_TOPIC,
|
||||||
|
ID_SECTION,
|
||||||
|
levelId
|
||||||
|
);
|
||||||
|
} else if (key.startsWith("IMAGE")) {
|
||||||
|
const imageFile = filesToSave[key];
|
||||||
|
filename = saveFileToDisk(
|
||||||
|
imageFile,
|
||||||
|
"IMAGE",
|
||||||
|
ID_TOPIC,
|
||||||
|
ID_SECTION,
|
||||||
|
levelId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filename) {
|
||||||
|
savedFiles[key] = filename;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (Object.keys(savedFiles).length === 0) {
|
||||||
|
return response(400, null, "Failed to save files", res);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response(200, savedFiles, "Files uploaded successfully", res);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
return response(500, null, "Internal server error", res);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getLevelFiles = async (req, res) => {
|
||||||
|
const { levelId } = req.params;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const audioFolderPath = path.join("public/uploads/level/audio");
|
||||||
|
const imageFolderPath = path.join("public/uploads/level/image");
|
||||||
|
|
||||||
|
const getFilesByLevelId = (folderPath, fileType) => {
|
||||||
|
if (fs.existsSync(folderPath)) {
|
||||||
|
const files = fs.readdirSync(folderPath);
|
||||||
|
return files.filter((file) =>
|
||||||
|
file.startsWith(`${fileType}-${levelId}`)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
};
|
||||||
|
|
||||||
|
const audioFiles = getFilesByLevelId(audioFolderPath, "AUDIO");
|
||||||
|
const imageFiles = getFilesByLevelId(imageFolderPath, "IMAGE");
|
||||||
|
|
||||||
|
if (audioFiles.length === 0 && imageFiles.length === 0) {
|
||||||
|
return response(404, null, "No files found for this level", res);
|
||||||
|
}
|
||||||
|
|
||||||
|
const levelFiles = {
|
||||||
|
audioFiles,
|
||||||
|
imageFiles,
|
||||||
|
};
|
||||||
|
|
||||||
|
return response(200, levelFiles, "Files retrieved successfully", res);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
return response(500, null, "Internal server error", res);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
|
||||||
137
middlewares/Level/uploadLevelFile.js
Normal file
137
middlewares/Level/uploadLevelFile.js
Normal file
|
|
@ -0,0 +1,137 @@
|
||||||
|
import multer from "multer";
|
||||||
|
import crypto from "crypto";
|
||||||
|
import path from "path";
|
||||||
|
import fs from "fs";
|
||||||
|
import response from "../../response.js";
|
||||||
|
|
||||||
|
const memoryStorage = multer.memoryStorage();
|
||||||
|
|
||||||
|
const fileFilter = (req, file, cb) => {
|
||||||
|
const ext = path.extname(file.originalname).toLowerCase();
|
||||||
|
|
||||||
|
if (file.fieldname.startsWith("AUDIO")) {
|
||||||
|
if (ext === ".mp3") {
|
||||||
|
cb(null, true);
|
||||||
|
} else {
|
||||||
|
cb(
|
||||||
|
new Error("Invalid file type, only .mp3 files are allowed for audio!"),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if (file.fieldname.startsWith("IMAGE")) {
|
||||||
|
if (ext === ".jpg" || ext === ".jpeg" || ext === ".png") {
|
||||||
|
cb(null, true);
|
||||||
|
} else {
|
||||||
|
cb(
|
||||||
|
new Error(
|
||||||
|
"Invalid file type, only .jpg, .jpeg, and .png files are allowed for image!"
|
||||||
|
),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cb(new Error("Invalid file type!"), false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const upload = multer({
|
||||||
|
storage: memoryStorage,
|
||||||
|
fileFilter,
|
||||||
|
limits: {
|
||||||
|
fileSize: 100 * 1024 * 1024,
|
||||||
|
},
|
||||||
|
}).any();
|
||||||
|
|
||||||
|
const handleUploadFile = (req, res, next) => {
|
||||||
|
upload(req, res, (err) => {
|
||||||
|
if (err) {
|
||||||
|
return response(400, null, err.message, res);
|
||||||
|
}
|
||||||
|
|
||||||
|
const files = req.files || [];
|
||||||
|
req.filesToSave = {};
|
||||||
|
|
||||||
|
let validFiles = true;
|
||||||
|
let errorMessages = [];
|
||||||
|
|
||||||
|
files.forEach((file) => {
|
||||||
|
if (file.fieldname.startsWith("AUDIO")) {
|
||||||
|
if (file.size > 10 * 1024 * 1024) {
|
||||||
|
validFiles = false;
|
||||||
|
errorMessages.push(`Audio file exceeds the size limit of 10MB`);
|
||||||
|
} else {
|
||||||
|
req.filesToSave[file.fieldname] = file;
|
||||||
|
}
|
||||||
|
} else if (file.fieldname.startsWith("IMAGE")) {
|
||||||
|
if (file.size > 5 * 1024 * 1024) {
|
||||||
|
validFiles = false;
|
||||||
|
errorMessages.push(`Image file exceeds the size limit of 5MB`);
|
||||||
|
} else {
|
||||||
|
req.filesToSave[file.fieldname] = file;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (validFiles) {
|
||||||
|
next();
|
||||||
|
} else {
|
||||||
|
clearFileBuffers(req.filesToSave);
|
||||||
|
return response(400, null, errorMessages.join("; "), res);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const clearFileBuffers = (files) => {
|
||||||
|
for (const file of Object.values(files)) {
|
||||||
|
if (file && file.buffer) {
|
||||||
|
file.buffer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const generateHash = (
|
||||||
|
levelId,
|
||||||
|
sectionId,
|
||||||
|
topicId,
|
||||||
|
filename,
|
||||||
|
bufferLength
|
||||||
|
) => {
|
||||||
|
return crypto
|
||||||
|
.createHash("md5")
|
||||||
|
.update(levelId + sectionId + topicId + filename + bufferLength)
|
||||||
|
.digest("hex");
|
||||||
|
};
|
||||||
|
|
||||||
|
export const saveFileToDisk = (file, type, topicId, sectionId, levelId) => {
|
||||||
|
const ext = path.extname(file.originalname);
|
||||||
|
const hash = generateHash(
|
||||||
|
levelId,
|
||||||
|
sectionId,
|
||||||
|
topicId,
|
||||||
|
file.originalname,
|
||||||
|
file.buffer.length
|
||||||
|
);
|
||||||
|
const filename = `${type}-${levelId}-${hash}${ext}`;
|
||||||
|
|
||||||
|
let folderPath;
|
||||||
|
switch (type) {
|
||||||
|
case "AUDIO":
|
||||||
|
folderPath = path.join("public/uploads/level/audio");
|
||||||
|
break;
|
||||||
|
case "IMAGE":
|
||||||
|
folderPath = path.join("public/uploads/level/image");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
folderPath = path.join("public/uploads/level");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fs.existsSync(folderPath)) {
|
||||||
|
fs.mkdirSync(folderPath, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
const filepath = path.join(folderPath, filename);
|
||||||
|
fs.writeFileSync(filepath, file.buffer);
|
||||||
|
return filename;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default handleUploadFile;
|
||||||
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 87 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 7.7 KiB |
|
|
@ -1,7 +1,8 @@
|
||||||
import express from "express";
|
import express from "express";
|
||||||
import { getLevels, getLevelById, getLevelForAdmin, getLevelsByTopicId, createLevel, updateLevelById, deleteLevelById, deleteLevelFileById, getPreviousLevel } from "../../controllers/contentControllers/level.js";
|
import { getLevels, getLevelById, getLevelForAdmin, getLevelsByTopicId, createLevel, updateLevelById, deleteLevelById, deleteLevelFilesById, getPreviousLevel, uploadLevelFile, getLevelFiles } from "../../controllers/contentControllers/level.js";
|
||||||
import { verifyLoginUser, adminOnly } from "../../middlewares/User/authUser.js";
|
import { verifyLoginUser, adminOnly } from "../../middlewares/User/authUser.js";
|
||||||
import handleUpload from '../../middlewares/Level/uploadLevel.js';
|
import handleUpload from '../../middlewares/Level/uploadLevel.js';
|
||||||
|
import handleUploadFile from "../../middlewares/Level/uploadLevelFile.js";
|
||||||
import {checkLevelsPerTopic, autoCalculateRoutes, getSectionAndTopicByLevelId } from '../../middlewares/Level/checkLevel.js';
|
import {checkLevelsPerTopic, autoCalculateRoutes, getSectionAndTopicByLevelId } from '../../middlewares/Level/checkLevel.js';
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -17,12 +18,18 @@ router.get("/level/:id", verifyLoginUser, getLevelById);
|
||||||
|
|
||||||
router.get("/previous/level/:next_learning", verifyLoginUser, getPreviousLevel);
|
router.get("/previous/level/:next_learning", verifyLoginUser, getPreviousLevel);
|
||||||
|
|
||||||
|
router.get("/level/file/:levelId", verifyLoginUser, getLevelFiles);
|
||||||
|
|
||||||
router.post("/level", verifyLoginUser, adminOnly, handleUpload, checkLevelsPerTopic, autoCalculateRoutes, createLevel);
|
router.post("/level", verifyLoginUser, adminOnly, handleUpload, checkLevelsPerTopic, autoCalculateRoutes, createLevel);
|
||||||
|
|
||||||
|
router.post("/level/file/:levelId", verifyLoginUser, adminOnly, handleUploadFile, uploadLevelFile);
|
||||||
|
|
||||||
router.put("/level/:id", verifyLoginUser, adminOnly, handleUpload, getSectionAndTopicByLevelId, autoCalculateRoutes, updateLevelById);
|
router.put("/level/:id", verifyLoginUser, adminOnly, handleUpload, getSectionAndTopicByLevelId, autoCalculateRoutes, updateLevelById);
|
||||||
|
|
||||||
router.delete("/level/:id", verifyLoginUser, adminOnly, getSectionAndTopicByLevelId, deleteLevelById);
|
router.delete("/level/:id", verifyLoginUser, adminOnly, getSectionAndTopicByLevelId, deleteLevelById);
|
||||||
|
|
||||||
router.delete("/level/file/:id", verifyLoginUser, adminOnly, deleteLevelFileById);
|
// router.delete("/level/file/:id", verifyLoginUser, adminOnly, deleteLevelFileById);
|
||||||
|
|
||||||
|
router.delete("/level/file/:id", verifyLoginUser, adminOnly, deleteLevelFilesById);
|
||||||
|
|
||||||
export default router
|
export default router
|
||||||
Loading…
Reference in New Issue
Block a user