feat: upload, get, and delete level file function

This commit is contained in:
elangptra 2024-10-23 09:11:42 +07:00
parent 411f392e24
commit 2e669bc281
6 changed files with 287 additions and 2 deletions

View File

@ -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);
}
};

View 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.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

View File

@ -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