feat (level): Implement CRUD function for table level with upload file logic + get and edit route field only
This commit is contained in:
parent
935232a01d
commit
a27ea19c5b
397
controllers/level.js
Normal file
397
controllers/level.js
Normal file
|
|
@ -0,0 +1,397 @@
|
|||
import response from "../response.js";
|
||||
import models from "../models/index.js";
|
||||
import {
|
||||
clearFileBuffers,
|
||||
saveFileToDisk,
|
||||
generateHash,
|
||||
} from "../middlewares/uploadLevel.js";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import crypto from "crypto";
|
||||
|
||||
export const getAllLevels = async (req, res) => {
|
||||
try {
|
||||
const levels = await models.Level.findAll();
|
||||
response(200, levels, "Success", res);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
response(500, null, "Error retrieving levels data!", res);
|
||||
}
|
||||
};
|
||||
|
||||
export const getAllLevelById = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const level = await models.Level.findByPk(id);
|
||||
|
||||
if (!level) {
|
||||
return response(404, null, "Level not found", res);
|
||||
}
|
||||
|
||||
response(200, level, "Success", res);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
res.status(500).json({ message: "Internal Server Error" });
|
||||
}
|
||||
};
|
||||
|
||||
export const getLevels = async (req, res) => {
|
||||
try {
|
||||
const levels = await models.Level.findAll({
|
||||
attributes: {
|
||||
exclude: ["route1", "route2", "route3", "route4"],
|
||||
},
|
||||
});
|
||||
|
||||
response(200, levels, "Success", res);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
res.status(500).json({ message: "Internal Server Error" });
|
||||
}
|
||||
};
|
||||
|
||||
export const getLevelById = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const level = await models.Level.findByPk(id, {
|
||||
attributes: {
|
||||
exclude: ["route1", "route2", "route3", "route4"],
|
||||
},
|
||||
});
|
||||
|
||||
if (!level) {
|
||||
return response(404, null, "Level not found", res);
|
||||
}
|
||||
|
||||
response(200, level, "Success", res);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
res.status(500).json({ message: "Internal Server Error" });
|
||||
}
|
||||
};
|
||||
|
||||
export const createLevel = async (req, res) => {
|
||||
const { title, subject_id, topic_id, is_pretest, content, youtube } =
|
||||
req.body;
|
||||
|
||||
// Files to be saved if everything else is okay
|
||||
const { video, audio, image } = req.filesToSave || {};
|
||||
|
||||
// Validate title
|
||||
if (!title) {
|
||||
clearFileBuffers({ video, audio, image });
|
||||
return response(400, null, "Title is required", res);
|
||||
}
|
||||
|
||||
// Validate subject_id
|
||||
if (!subject_id) {
|
||||
clearFileBuffers({ video, audio, image });
|
||||
return response(400, null, "Subject ID is required", res);
|
||||
}
|
||||
|
||||
// Validate topic_id
|
||||
if (!topic_id) {
|
||||
clearFileBuffers({ video, audio, image });
|
||||
return response(400, null, "Topic ID is required", res);
|
||||
}
|
||||
|
||||
try {
|
||||
// Check if the title already exists under the same topic_id
|
||||
const existingLevel = await models.Level.findOne({
|
||||
where: { title, topic_id },
|
||||
});
|
||||
|
||||
if (existingLevel) {
|
||||
clearFileBuffers({ video, audio, image });
|
||||
return response(
|
||||
409,
|
||||
null,
|
||||
"A level with this title already exists under this topic",
|
||||
res
|
||||
); // 409 Conflict
|
||||
}
|
||||
|
||||
// Save files to disk
|
||||
const videoFilename = video
|
||||
? saveFileToDisk(video, "video", title, topic_id, subject_id)
|
||||
: null;
|
||||
const audioFilename = audio
|
||||
? saveFileToDisk(audio, "audio", title, topic_id, subject_id)
|
||||
: null;
|
||||
const imageFilename = image
|
||||
? saveFileToDisk(image, "image", title, topic_id, subject_id)
|
||||
: null;
|
||||
|
||||
// Create the new level
|
||||
const newLevel = await models.Level.create({
|
||||
title,
|
||||
subject_id,
|
||||
topic_id,
|
||||
is_pretest: is_pretest || 0,
|
||||
content,
|
||||
video: videoFilename,
|
||||
audio: audioFilename,
|
||||
image: imageFilename,
|
||||
youtube,
|
||||
route1: 0,
|
||||
route2: 0,
|
||||
route3: 0,
|
||||
route4: 0,
|
||||
});
|
||||
|
||||
// Update routes with the newly created level's ID
|
||||
await newLevel.update({
|
||||
route1: newLevel.id,
|
||||
route2: newLevel.id,
|
||||
route3: newLevel.id,
|
||||
route4: newLevel.id,
|
||||
});
|
||||
|
||||
response(201, newLevel, "Level created successfully", res);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
clearFileBuffers({ video, audio, image });
|
||||
return response(500, null, "Internal Server Error", res);
|
||||
}
|
||||
};
|
||||
|
||||
export const updateLevelById = async (req, res) => {
|
||||
const { id } = req.params;
|
||||
const { title, subject_id, topic_id, is_pretest, content, youtube } =
|
||||
req.body;
|
||||
|
||||
// Files to be saved if everything else is okay
|
||||
const { video, audio, image } = req.filesToSave || {};
|
||||
|
||||
try {
|
||||
// Find the existing level by ID
|
||||
const level = await models.Level.findByPk(id);
|
||||
|
||||
if (!level) {
|
||||
clearFileBuffers({ video, audio, image });
|
||||
return response(404, null, "Level not found", res);
|
||||
}
|
||||
|
||||
// Check if a level with the same title under the same topic already exists
|
||||
if (title && topic_id) {
|
||||
const existingLevel = await models.Level.findOne({
|
||||
where: {
|
||||
title,
|
||||
topic_id,
|
||||
id: { [models.Sequelize.Op.ne]: id }, // Exclude the current level from the check
|
||||
},
|
||||
});
|
||||
|
||||
if (existingLevel) {
|
||||
clearFileBuffers({ video, audio, image });
|
||||
return response(
|
||||
409,
|
||||
null,
|
||||
"A level with this title already exists under this topic",
|
||||
res
|
||||
); // 409 Conflict
|
||||
}
|
||||
}
|
||||
|
||||
// Update level fields
|
||||
if (title) level.title = title;
|
||||
if (subject_id) level.subject_id = subject_id;
|
||||
if (topic_id) level.topic_id = topic_id;
|
||||
if (is_pretest !== undefined) level.is_pretest = is_pretest;
|
||||
if (content) level.content = content;
|
||||
if (youtube) level.youtube = youtube;
|
||||
|
||||
// Handle video update
|
||||
if (video) {
|
||||
if (level.video) {
|
||||
const oldVideoPath = path.join(
|
||||
"public/uploads/level/video",
|
||||
level.video
|
||||
);
|
||||
if (fs.existsSync(oldVideoPath)) {
|
||||
fs.unlinkSync(oldVideoPath);
|
||||
}
|
||||
}
|
||||
level.video = saveFileToDisk(
|
||||
video,
|
||||
"video",
|
||||
title || level.title,
|
||||
topic_id || level.topic_id,
|
||||
subject_id || level.subject_id
|
||||
);
|
||||
}
|
||||
|
||||
// Handle audio update
|
||||
if (audio) {
|
||||
if (level.audio) {
|
||||
const oldAudioPath = path.join(
|
||||
"public/uploads/level/audio",
|
||||
level.audio
|
||||
);
|
||||
if (fs.existsSync(oldAudioPath)) {
|
||||
fs.unlinkSync(oldAudioPath);
|
||||
}
|
||||
}
|
||||
level.audio = saveFileToDisk(
|
||||
audio,
|
||||
"audio",
|
||||
title || level.title,
|
||||
topic_id || level.topic_id,
|
||||
subject_id || level.subject_id
|
||||
);
|
||||
}
|
||||
|
||||
// Handle image update
|
||||
if (image) {
|
||||
if (level.image) {
|
||||
const oldImagePath = path.join(
|
||||
"public/uploads/level/image",
|
||||
level.image
|
||||
);
|
||||
if (fs.existsSync(oldImagePath)) {
|
||||
fs.unlinkSync(oldImagePath);
|
||||
}
|
||||
}
|
||||
level.image = saveFileToDisk(
|
||||
image,
|
||||
"image",
|
||||
title || level.title,
|
||||
topic_id || level.topic_id,
|
||||
subject_id || level.subject_id
|
||||
);
|
||||
}
|
||||
|
||||
await level.save();
|
||||
|
||||
response(200, level, "Level updated successfully", res);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
clearFileBuffers({ video, audio, image });
|
||||
return response(500, null, "Internal Server Error", res);
|
||||
}
|
||||
};
|
||||
|
||||
export const deleteLevelById = async (req, res) => {
|
||||
const { id } = req.params;
|
||||
|
||||
try {
|
||||
// Find the existing level by ID
|
||||
const level = await models.Level.findByPk(id);
|
||||
|
||||
if (!level) {
|
||||
return response(404, null, "Level not found", res);
|
||||
}
|
||||
|
||||
// Delete associated files from disk if they exist
|
||||
const deleteFile = (filePath) => {
|
||||
if (fs.existsSync(filePath)) {
|
||||
fs.unlinkSync(filePath);
|
||||
}
|
||||
};
|
||||
|
||||
if (level.video) {
|
||||
const videoPath = path.join("public/uploads/level/video", level.video);
|
||||
deleteFile(videoPath);
|
||||
}
|
||||
|
||||
if (level.audio) {
|
||||
const audioPath = path.join("public/uploads/level/audio", level.audio);
|
||||
deleteFile(audioPath);
|
||||
}
|
||||
|
||||
if (level.image) {
|
||||
const imagePath = path.join("public/uploads/level/image", level.image);
|
||||
deleteFile(imagePath);
|
||||
}
|
||||
|
||||
// Delete the level from the database
|
||||
await level.destroy();
|
||||
|
||||
response(200, null, "Level deleted successfully", res);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return response(500, null, "Internal Server Error", res);
|
||||
}
|
||||
};
|
||||
|
||||
export const getRoutes = async (req, res) => {
|
||||
try {
|
||||
const levels = await models.Level.findAll({
|
||||
attributes: {
|
||||
exclude: [
|
||||
"subject_id",
|
||||
"topic_id",
|
||||
"is_pretest",
|
||||
"content",
|
||||
"video",
|
||||
"audio",
|
||||
"image",
|
||||
"youtube",
|
||||
"ts_entri",
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
response(200, levels, "Success", res);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
res.status(500).json({ message: "Internal Server Error" });
|
||||
}
|
||||
};
|
||||
|
||||
export const getRouteById = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const level = await models.Level.findByPk(id, {
|
||||
attributes: {
|
||||
exclude: [
|
||||
"subject_id",
|
||||
"topic_id",
|
||||
"is_pretest",
|
||||
"content",
|
||||
"video",
|
||||
"audio",
|
||||
"image",
|
||||
"youtube",
|
||||
"ts_entri",
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
if (!level) {
|
||||
return response(404, null, "Level not found", res);
|
||||
}
|
||||
|
||||
response(200, level, "Success", res);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
res.status(500).json({ message: "Internal Server Error" });
|
||||
}
|
||||
};
|
||||
|
||||
export const updateRouteById = async (req, res) => {
|
||||
const { id } = req.params;
|
||||
const { route1, route2, route3, route4 } = req.body;
|
||||
|
||||
try {
|
||||
// Find the existing level by ID
|
||||
const level = await models.Level.findByPk(id);
|
||||
|
||||
if (!level) {
|
||||
return response(404, null, "Level not found", res);
|
||||
}
|
||||
|
||||
// Update only the route fields
|
||||
await level.update({
|
||||
route1: route1 !== undefined ? route1 : level.route1,
|
||||
route2: route2 !== undefined ? route2 : level.route2,
|
||||
route3: route3 !== undefined ? route3 : level.route3,
|
||||
route4: route4 !== undefined ? route4 : level.route4,
|
||||
});
|
||||
|
||||
response(200, level, "Routes updated successfully", res);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return response(500, null, "Internal Server Error", res);
|
||||
}
|
||||
};
|
||||
|
|
@ -2,6 +2,10 @@ import response from "../response.js";
|
|||
import models from "../models/index.js";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import {
|
||||
clearFileBuffers,
|
||||
saveFileToDisk,
|
||||
} from "../middlewares/uploadSubject.js";
|
||||
|
||||
export const getSubjects = async (req, res) => {
|
||||
try {
|
||||
|
|
@ -30,29 +34,42 @@ export const getSubjectById = async (req, res) => {
|
|||
};
|
||||
|
||||
export const createSubject = async (req, res) => {
|
||||
const { name, description, icon, thumbnail } = req.body;
|
||||
const { name, description } = req.body;
|
||||
|
||||
// Files to be saved if everything else is okay
|
||||
const { icon, thumbnail } = req.filesToSave || {};
|
||||
|
||||
// Validate name
|
||||
if (!name) {
|
||||
clearFileBuffers({ icon, thumbnail });
|
||||
return response(400, null, "Name is required", res);
|
||||
}
|
||||
|
||||
// Validate description
|
||||
if (!description) {
|
||||
clearFileBuffers({ icon, thumbnail });
|
||||
return response(400, null, "Description is required", res);
|
||||
}
|
||||
|
||||
try {
|
||||
const iconFilename = icon
|
||||
? saveFileToDisk(icon, `${name}-icon`, name)
|
||||
: null;
|
||||
const thumbnailFilename = thumbnail
|
||||
? saveFileToDisk(thumbnail, `${name}-thumbnail`, name)
|
||||
: null;
|
||||
|
||||
const newSubject = await models.Subject.create({
|
||||
name,
|
||||
description,
|
||||
icon,
|
||||
thumbnail,
|
||||
icon: iconFilename,
|
||||
thumbnail: thumbnailFilename,
|
||||
});
|
||||
|
||||
response(201, newSubject, "Subject created successfully", res);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
clearFileBuffers({ icon, thumbnail });
|
||||
res.status(500).json({ message: "Internal Server Error" });
|
||||
}
|
||||
};
|
||||
|
|
@ -60,38 +77,45 @@ export const createSubject = async (req, res) => {
|
|||
export const updateSubjectById = async (req, res) => {
|
||||
const { id } = req.params;
|
||||
const { name, description } = req.body;
|
||||
const icon = req.body.icon;
|
||||
const thumbnail = req.body.thumbnail;
|
||||
|
||||
// Files to be saved if everything else is okay
|
||||
const { icon, thumbnail } = req.filesToSave || {};
|
||||
|
||||
try {
|
||||
const subject = await models.Subject.findByPk(id);
|
||||
|
||||
if (!subject) {
|
||||
clearFileBuffers({ icon, thumbnail });
|
||||
return response(404, null, "Subject not found", res);
|
||||
}
|
||||
|
||||
// Update subject fields
|
||||
if (name) subject.name = name;
|
||||
if (description) subject.description = description;
|
||||
|
||||
// Handle icon update
|
||||
if (icon) {
|
||||
// Remove old icon if it exists
|
||||
if (
|
||||
subject.icon &&
|
||||
fs.existsSync(path.join("public/uploads", subject.icon))
|
||||
) {
|
||||
fs.unlinkSync(path.join("public/uploads", subject.icon));
|
||||
if (subject.icon) {
|
||||
const oldIconPath = path.join("public/uploads/subject", subject.icon);
|
||||
if (fs.existsSync(oldIconPath)) {
|
||||
fs.unlinkSync(oldIconPath);
|
||||
}
|
||||
subject.icon = icon;
|
||||
}
|
||||
subject.icon = saveFileToDisk(icon, `${name}-icon`, name);
|
||||
}
|
||||
|
||||
// Handle thumbnail update
|
||||
if (thumbnail) {
|
||||
// Remove old thumbnail if it exists
|
||||
if (
|
||||
subject.thumbnail &&
|
||||
fs.existsSync(path.join("public/uploads", subject.thumbnail))
|
||||
) {
|
||||
fs.unlinkSync(path.join("public/uploads", subject.thumbnail));
|
||||
if (subject.thumbnail) {
|
||||
const oldThumbnailPath = path.join(
|
||||
"public/uploads/subject",
|
||||
subject.thumbnail
|
||||
);
|
||||
if (fs.existsSync(oldThumbnailPath)) {
|
||||
fs.unlinkSync(oldThumbnailPath);
|
||||
}
|
||||
subject.thumbnail = thumbnail;
|
||||
}
|
||||
subject.thumbnail = saveFileToDisk(thumbnail, `${name}-thumbnail`, name);
|
||||
}
|
||||
|
||||
await subject.save();
|
||||
|
|
@ -99,6 +123,7 @@ export const updateSubjectById = async (req, res) => {
|
|||
response(200, subject, "Subject updated successfully", res);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
clearFileBuffers({ icon, thumbnail });
|
||||
response(500, null, "Internal Server Error", res);
|
||||
}
|
||||
};
|
||||
|
|
@ -113,18 +138,23 @@ export const deleteSubjectById = async (req, res) => {
|
|||
return response(404, null, "Subject not found", res);
|
||||
}
|
||||
|
||||
// Remove associated files if they exist
|
||||
if (
|
||||
subject.icon &&
|
||||
fs.existsSync(path.join("public/uploads", subject.icon))
|
||||
) {
|
||||
fs.unlinkSync(path.join("public/uploads", subject.icon));
|
||||
// Remove associated icon if it exists
|
||||
if (subject.icon) {
|
||||
const iconPath = path.join("public/uploads/subject", subject.icon);
|
||||
if (fs.existsSync(iconPath)) {
|
||||
fs.unlinkSync(iconPath);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove associated thumbnail if it exists
|
||||
if (subject.thumbnail) {
|
||||
const thumbnailPath = path.join(
|
||||
"public/uploads/subject",
|
||||
subject.thumbnail
|
||||
);
|
||||
if (fs.existsSync(thumbnailPath)) {
|
||||
fs.unlinkSync(thumbnailPath);
|
||||
}
|
||||
if (
|
||||
subject.thumbnail &&
|
||||
fs.existsSync(path.join("public/uploads", subject.thumbnail))
|
||||
) {
|
||||
fs.unlinkSync(path.join("public/uploads", subject.thumbnail));
|
||||
}
|
||||
|
||||
await subject.destroy();
|
||||
|
|
|
|||
|
|
@ -4,7 +4,11 @@ import bcrypt from "bcrypt";
|
|||
|
||||
export const getUsers = async (req, res) => {
|
||||
try {
|
||||
const users = await models.User.findAll();
|
||||
const users = await models.User.findAll({
|
||||
attributes: {
|
||||
exclude: ["password"],
|
||||
},
|
||||
});
|
||||
response(200, users, "Success", res);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
|
|
@ -15,7 +19,11 @@ export const getUsers = async (req, res) => {
|
|||
export const getUserById = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const user = await models.User.findByPk(id);
|
||||
const user = await models.User.findByPk(id, {
|
||||
attributes: {
|
||||
exclude: ["password"],
|
||||
},
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return response(404, null, "User not found", res);
|
||||
|
|
|
|||
1
index.js
1
index.js
|
|
@ -11,6 +11,7 @@ const app = express();
|
|||
app.use(cors());
|
||||
app.use(cookieParser());
|
||||
app.use(express.json());
|
||||
app.use(express.urlencoded({ extended: true }));
|
||||
app.use(router);
|
||||
|
||||
// Serve static files from the uploads directory
|
||||
|
|
|
|||
|
|
@ -15,7 +15,11 @@ export const verifyLoginUser = async (req, res, next) => {
|
|||
const decoded = jwt.verify(accessToken, process.env.ACCESS_TOKEN_SECRET);
|
||||
|
||||
// Cari user berdasarkan id yang ada di token
|
||||
const user = await models.User.findByPk(decoded.id);
|
||||
const user = await models.User.findByPk(decoded.id, {
|
||||
attributes: {
|
||||
exclude: ["password"],
|
||||
},
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).json({ message: "User not found!" });
|
||||
|
|
|
|||
29
middlewares/checkLevel.js
Normal file
29
middlewares/checkLevel.js
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import models from "../models/index.js";
|
||||
import response from "../response.js";
|
||||
|
||||
export const checkMaxLevelsPerTopic = async (req, res, next) => {
|
||||
const { topic_id } = req.body;
|
||||
|
||||
try {
|
||||
// Hitung jumlah level yang ada pada topic_id yang diberikan
|
||||
const levelCount = await models.Level.count({
|
||||
where: { topic_id },
|
||||
});
|
||||
|
||||
// Periksa apakah jumlah level sudah mencapai 5
|
||||
if (levelCount >= 5) {
|
||||
return response(
|
||||
400,
|
||||
null,
|
||||
"Cannot add more than 5 levels to a single topic",
|
||||
res
|
||||
);
|
||||
}
|
||||
|
||||
// Lanjutkan ke middleware atau route handler berikutnya jika belum mencapai 5
|
||||
next();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return response(500, null, "Internal Server Error", res);
|
||||
}
|
||||
};
|
||||
171
middlewares/uploadLevel.js
Normal file
171
middlewares/uploadLevel.js
Normal file
|
|
@ -0,0 +1,171 @@
|
|||
import multer from "multer";
|
||||
import crypto from "crypto";
|
||||
import path from "path";
|
||||
import fs from "fs";
|
||||
import response from "../response.js";
|
||||
|
||||
// Setup memory storage for Multer
|
||||
const memoryStorage = multer.memoryStorage();
|
||||
|
||||
// Filter untuk membatasi tipe file dan ukuran file
|
||||
const fileFilter = (req, file, cb) => {
|
||||
const ext = path.extname(file.originalname).toLowerCase();
|
||||
|
||||
switch (file.fieldname) {
|
||||
case "video":
|
||||
if (ext === ".mp4") {
|
||||
cb(null, true);
|
||||
} else {
|
||||
cb(
|
||||
new Error(
|
||||
"Invalid file type, only .mp4 files are allowed for video!"
|
||||
),
|
||||
false
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
||||
case "audio":
|
||||
if (ext === ".mp3") {
|
||||
cb(null, true);
|
||||
} else {
|
||||
cb(
|
||||
new Error(
|
||||
"Invalid file type, only .mp3 files are allowed for audio!"
|
||||
),
|
||||
false
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
||||
case "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
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
cb(new Error("Invalid file type!"), false);
|
||||
}
|
||||
};
|
||||
|
||||
// Set up Multer untuk menangani upload
|
||||
const upload = multer({
|
||||
storage: memoryStorage,
|
||||
fileFilter,
|
||||
limits: {
|
||||
fileSize: 100 * 1024 * 1024, // Total file size limit if needed
|
||||
},
|
||||
}).fields([
|
||||
{ name: "video", maxCount: 1 },
|
||||
{ name: "audio", maxCount: 1 },
|
||||
{ name: "image", maxCount: 1 },
|
||||
]);
|
||||
|
||||
// Middleware untuk menangani upload dan pengecekan file size
|
||||
const handleUpload = (req, res, next) => {
|
||||
upload(req, res, (err) => {
|
||||
if (err) {
|
||||
return response(400, null, err.message, res);
|
||||
}
|
||||
|
||||
const files = req.files;
|
||||
const video = files?.video ? files.video[0] : null;
|
||||
const audio = files?.audio ? files.audio[0] : null;
|
||||
const image = files?.image ? files.image[0] : null;
|
||||
|
||||
try {
|
||||
let validFiles = true;
|
||||
let errorMessages = [];
|
||||
|
||||
// Validate file sizes
|
||||
if (video && video.size > 30 * 1024 * 1024) {
|
||||
validFiles = false;
|
||||
video.buffer = null;
|
||||
errorMessages.push("Video file exceeds the size limit of 30MB");
|
||||
}
|
||||
|
||||
if (audio && audio.size > 10 * 1024 * 1024) {
|
||||
validFiles = false;
|
||||
audio.buffer = null;
|
||||
errorMessages.push("Audio file exceeds the size limit of 10MB");
|
||||
}
|
||||
|
||||
if (image && image.size > 5 * 1024 * 1024) {
|
||||
validFiles = false;
|
||||
image.buffer = null;
|
||||
errorMessages.push("Image file exceeds the size limit of 5MB");
|
||||
}
|
||||
|
||||
if (validFiles) {
|
||||
// Attach files to the request object for further processing
|
||||
req.filesToSave = { video, audio, image };
|
||||
next();
|
||||
} else {
|
||||
// Clear file buffers and return error response with specific messages
|
||||
clearFileBuffers({ video, audio, image });
|
||||
return response(400, null, errorMessages.join("; "), res);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
clearFileBuffers({ video, audio, image });
|
||||
return response(500, null, "Internal Server Error", res);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Function to clear file buffers
|
||||
export const clearFileBuffers = (files) => {
|
||||
for (const file of Object.values(files)) {
|
||||
if (file && file.buffer) {
|
||||
file.buffer = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const generateHash = (subjectId, filename, bufferLength) => {
|
||||
return crypto
|
||||
.createHash("md5")
|
||||
.update(subjectId + filename + bufferLength)
|
||||
.digest("hex");
|
||||
};
|
||||
|
||||
// Function to save files to disk
|
||||
export const saveFileToDisk = (file, type, title, topicId, subjectId) => {
|
||||
const formattedTitle = title.replace(/\s+/g, '').toLowerCase();
|
||||
const ext = path.extname(file.originalname);
|
||||
const hash = generateHash(subjectId, file.originalname, file.buffer.length);
|
||||
const filename = `${topicId}-${formattedTitle}-${type}-${hash}${ext}`;
|
||||
|
||||
let folderPath;
|
||||
switch (type) {
|
||||
case "video":
|
||||
folderPath = path.join("public/uploads/level/video");
|
||||
break;
|
||||
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 handleUpload;
|
||||
|
|
@ -2,11 +2,18 @@ import { Sequelize } from "sequelize";
|
|||
import UserModel from "./userModel.js";
|
||||
import SubjectModel from "./subjectModel.js";
|
||||
import TopicModel from "./topicModel.js";
|
||||
import LevelModel from "./levelModel.js";
|
||||
|
||||
// Impor operator Op
|
||||
const Op = Sequelize.Op;
|
||||
|
||||
const models = {
|
||||
User: UserModel(Sequelize.DataTypes),
|
||||
Subject: SubjectModel(Sequelize.DataTypes),
|
||||
Topic: TopicModel(Sequelize.DataTypes),
|
||||
Level: LevelModel(Sequelize.DataTypes),
|
||||
Sequelize,
|
||||
Op,
|
||||
};
|
||||
|
||||
export default models;
|
||||
103
models/levelModel.js
Normal file
103
models/levelModel.js
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
import db from "../database/db.js";
|
||||
|
||||
const LevelModel = (DataTypes) => {
|
||||
const Levels = db.define(
|
||||
"m_levels",
|
||||
{
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
validate: {
|
||||
notEmpty: true,
|
||||
},
|
||||
},
|
||||
title: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: false,
|
||||
validate: {
|
||||
notEmpty: true,
|
||||
},
|
||||
},
|
||||
subject_id: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
validate: {
|
||||
notEmpty: true,
|
||||
},
|
||||
references: {
|
||||
model: 'm_subjects', // Name of the referenced table
|
||||
key: 'id', // Key in the referenced table
|
||||
},
|
||||
},
|
||||
topic_id: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
validate: {
|
||||
notEmpty: true,
|
||||
},
|
||||
references: {
|
||||
model: 'm_topics', // Name of the referenced table
|
||||
key: 'id', // Key in the referenced table
|
||||
},
|
||||
},
|
||||
is_pretest: {
|
||||
type: DataTypes.TINYINT(1),
|
||||
allowNull: true,
|
||||
defaultValue: 0,
|
||||
validate: {
|
||||
min: 0,
|
||||
max: 1,
|
||||
},
|
||||
},
|
||||
content: {
|
||||
type: DataTypes.STRING(200),
|
||||
allowNull: true,
|
||||
},
|
||||
video: {
|
||||
type: DataTypes.STRING(200),
|
||||
allowNull: true,
|
||||
},
|
||||
audio: {
|
||||
type: DataTypes.STRING(200),
|
||||
allowNull: true,
|
||||
},
|
||||
image: {
|
||||
type: DataTypes.STRING(200),
|
||||
allowNull: true,
|
||||
},
|
||||
youtube: {
|
||||
type: DataTypes.STRING(200),
|
||||
allowNull: true,
|
||||
},
|
||||
route1: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
},
|
||||
route2: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
},
|
||||
route3: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
},
|
||||
route4: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
},
|
||||
ts_entri: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: true,
|
||||
defaultValue: DataTypes.NOW,
|
||||
},
|
||||
},
|
||||
{
|
||||
timestamps: false, // Disable Sequelize's automatic timestamp fields (createdAt, updatedAt)
|
||||
tableName: "m_levels", // Ensure the table name matches the actual table name
|
||||
}
|
||||
);
|
||||
return Levels;
|
||||
};
|
||||
|
||||
export default LevelModel;
|
||||
|
|
@ -5,9 +5,10 @@ const UserModel = (DataTypes) => {
|
|||
"users",
|
||||
{
|
||||
id: {
|
||||
type: DataTypes.BIGINT,
|
||||
type: DataTypes.UUID,
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
defaultValue: DataTypes.UUIDV4,
|
||||
allowNull: false,
|
||||
validate: {
|
||||
notEmpty: true,
|
||||
},
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 63 KiB |
Binary file not shown.
|
|
@ -2,11 +2,6 @@ const response = (statusCode, data, message, res) => {
|
|||
res.status(statusCode).json({
|
||||
payload: data,
|
||||
message: message,
|
||||
pagination: {
|
||||
prev: "",
|
||||
next: "",
|
||||
max: "",
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -3,11 +3,13 @@ import user_routes from "./user.js";
|
|||
import auth_routes from "./auth.js";
|
||||
import subject_routes from "./subject.js";
|
||||
import topic_routes from "./topic.js";
|
||||
import level_routes from "./level.js";
|
||||
|
||||
const route = express();
|
||||
route.use(user_routes);
|
||||
route.use(auth_routes);
|
||||
route.use(subject_routes);
|
||||
route.use(topic_routes);
|
||||
route.use(level_routes);
|
||||
|
||||
export default route;
|
||||
|
|
|
|||
30
routes/level.js
Normal file
30
routes/level.js
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
import express from "express";
|
||||
import { getAllLevels, getAllLevelById, getLevels, getLevelById, createLevel, updateLevelById, deleteLevelById, getRoutes, getRouteById, updateRouteById } from "../controllers/level.js";
|
||||
import { verifyLoginUser, adminOnly, teacherOnly } from "../middlewares/authUser.js";
|
||||
import handleUpload from '../middlewares/uploadLevel.js';
|
||||
import {checkMaxLevelsPerTopic } from '../middlewares/checkLevel.js';
|
||||
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.get("/levels", getAllLevels);
|
||||
|
||||
router.get("/levels/:id", getAllLevelById);
|
||||
|
||||
router.get("/level", getLevels);
|
||||
|
||||
router.get("/level/:id", getLevelById);
|
||||
|
||||
router.post("/level", handleUpload, checkMaxLevelsPerTopic, createLevel);
|
||||
|
||||
router.put("/level/:id", handleUpload, updateLevelById);
|
||||
|
||||
router.delete("/level/:id", deleteLevelById);
|
||||
|
||||
router.get("/route", getRoutes);
|
||||
|
||||
router.get("/route/:id", getRouteById);
|
||||
|
||||
router.put("/route/:id", updateRouteById);
|
||||
|
||||
export default router
|
||||
|
|
@ -5,7 +5,7 @@ import { verifyLoginUser, adminOnly, teacherOnly } from "../middlewares/authUser
|
|||
|
||||
const router = express.Router();
|
||||
|
||||
router.get("/user", verifyLoginUser, teacherOnly, getUsers);
|
||||
router.get("/user", verifyLoginUser, adminOnly, getUsers);
|
||||
|
||||
router.get("/user/:id", getUserById);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user