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 models from "../models/index.js";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
|
import {
|
||||||
|
clearFileBuffers,
|
||||||
|
saveFileToDisk,
|
||||||
|
} from "../middlewares/uploadSubject.js";
|
||||||
|
|
||||||
export const getSubjects = async (req, res) => {
|
export const getSubjects = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -30,29 +34,42 @@ export const getSubjectById = async (req, res) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createSubject = 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
|
// Validate name
|
||||||
if (!name) {
|
if (!name) {
|
||||||
|
clearFileBuffers({ icon, thumbnail });
|
||||||
return response(400, null, "Name is required", res);
|
return response(400, null, "Name is required", res);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate description
|
// Validate description
|
||||||
if (!description) {
|
if (!description) {
|
||||||
|
clearFileBuffers({ icon, thumbnail });
|
||||||
return response(400, null, "Description is required", res);
|
return response(400, null, "Description is required", res);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
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({
|
const newSubject = await models.Subject.create({
|
||||||
name,
|
name,
|
||||||
description,
|
description,
|
||||||
icon,
|
icon: iconFilename,
|
||||||
thumbnail,
|
thumbnail: thumbnailFilename,
|
||||||
});
|
});
|
||||||
|
|
||||||
response(201, newSubject, "Subject created successfully", res);
|
response(201, newSubject, "Subject created successfully", res);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
|
clearFileBuffers({ icon, thumbnail });
|
||||||
res.status(500).json({ message: "Internal Server Error" });
|
res.status(500).json({ message: "Internal Server Error" });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -60,38 +77,45 @@ export const createSubject = async (req, res) => {
|
||||||
export const updateSubjectById = async (req, res) => {
|
export const updateSubjectById = async (req, res) => {
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
const { name, description } = req.body;
|
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 {
|
try {
|
||||||
const subject = await models.Subject.findByPk(id);
|
const subject = await models.Subject.findByPk(id);
|
||||||
|
|
||||||
if (!subject) {
|
if (!subject) {
|
||||||
|
clearFileBuffers({ icon, thumbnail });
|
||||||
return response(404, null, "Subject not found", res);
|
return response(404, null, "Subject not found", res);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update subject fields
|
// Update subject fields
|
||||||
if (name) subject.name = name;
|
if (name) subject.name = name;
|
||||||
if (description) subject.description = description;
|
if (description) subject.description = description;
|
||||||
|
|
||||||
|
// Handle icon update
|
||||||
if (icon) {
|
if (icon) {
|
||||||
// Remove old icon if it exists
|
if (subject.icon) {
|
||||||
if (
|
const oldIconPath = path.join("public/uploads/subject", subject.icon);
|
||||||
subject.icon &&
|
if (fs.existsSync(oldIconPath)) {
|
||||||
fs.existsSync(path.join("public/uploads", subject.icon))
|
fs.unlinkSync(oldIconPath);
|
||||||
) {
|
}
|
||||||
fs.unlinkSync(path.join("public/uploads", subject.icon));
|
|
||||||
}
|
}
|
||||||
subject.icon = icon;
|
subject.icon = saveFileToDisk(icon, `${name}-icon`, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle thumbnail update
|
||||||
if (thumbnail) {
|
if (thumbnail) {
|
||||||
// Remove old thumbnail if it exists
|
if (subject.thumbnail) {
|
||||||
if (
|
const oldThumbnailPath = path.join(
|
||||||
subject.thumbnail &&
|
"public/uploads/subject",
|
||||||
fs.existsSync(path.join("public/uploads", subject.thumbnail))
|
subject.thumbnail
|
||||||
) {
|
);
|
||||||
fs.unlinkSync(path.join("public/uploads", subject.thumbnail));
|
if (fs.existsSync(oldThumbnailPath)) {
|
||||||
|
fs.unlinkSync(oldThumbnailPath);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
subject.thumbnail = thumbnail;
|
subject.thumbnail = saveFileToDisk(thumbnail, `${name}-thumbnail`, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
await subject.save();
|
await subject.save();
|
||||||
|
|
@ -99,6 +123,7 @@ export const updateSubjectById = async (req, res) => {
|
||||||
response(200, subject, "Subject updated successfully", res);
|
response(200, subject, "Subject updated successfully", res);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
|
clearFileBuffers({ icon, thumbnail });
|
||||||
response(500, null, "Internal Server Error", res);
|
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);
|
return response(404, null, "Subject not found", res);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove associated files if they exist
|
// Remove associated icon if it exists
|
||||||
if (
|
if (subject.icon) {
|
||||||
subject.icon &&
|
const iconPath = path.join("public/uploads/subject", subject.icon);
|
||||||
fs.existsSync(path.join("public/uploads", subject.icon))
|
if (fs.existsSync(iconPath)) {
|
||||||
) {
|
fs.unlinkSync(iconPath);
|
||||||
fs.unlinkSync(path.join("public/uploads", subject.icon));
|
}
|
||||||
}
|
}
|
||||||
if (
|
|
||||||
subject.thumbnail &&
|
// Remove associated thumbnail if it exists
|
||||||
fs.existsSync(path.join("public/uploads", subject.thumbnail))
|
if (subject.thumbnail) {
|
||||||
) {
|
const thumbnailPath = path.join(
|
||||||
fs.unlinkSync(path.join("public/uploads", subject.thumbnail));
|
"public/uploads/subject",
|
||||||
|
subject.thumbnail
|
||||||
|
);
|
||||||
|
if (fs.existsSync(thumbnailPath)) {
|
||||||
|
fs.unlinkSync(thumbnailPath);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await subject.destroy();
|
await subject.destroy();
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,11 @@ import bcrypt from "bcrypt";
|
||||||
|
|
||||||
export const getUsers = async (req, res) => {
|
export const getUsers = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const users = await models.User.findAll();
|
const users = await models.User.findAll({
|
||||||
|
attributes: {
|
||||||
|
exclude: ["password"],
|
||||||
|
},
|
||||||
|
});
|
||||||
response(200, users, "Success", res);
|
response(200, users, "Success", res);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
|
|
@ -15,7 +19,11 @@ export const getUsers = async (req, res) => {
|
||||||
export const getUserById = async (req, res) => {
|
export const getUserById = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
const user = await models.User.findByPk(id);
|
const user = await models.User.findByPk(id, {
|
||||||
|
attributes: {
|
||||||
|
exclude: ["password"],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return response(404, null, "User not found", res);
|
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(cors());
|
||||||
app.use(cookieParser());
|
app.use(cookieParser());
|
||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
|
app.use(express.urlencoded({ extended: true }));
|
||||||
app.use(router);
|
app.use(router);
|
||||||
|
|
||||||
// Serve static files from the uploads directory
|
// 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);
|
const decoded = jwt.verify(accessToken, process.env.ACCESS_TOKEN_SECRET);
|
||||||
|
|
||||||
// Cari user berdasarkan id yang ada di token
|
// 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) {
|
if (!user) {
|
||||||
return res.status(404).json({ message: "User not found!" });
|
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 UserModel from "./userModel.js";
|
||||||
import SubjectModel from "./subjectModel.js";
|
import SubjectModel from "./subjectModel.js";
|
||||||
import TopicModel from "./topicModel.js";
|
import TopicModel from "./topicModel.js";
|
||||||
|
import LevelModel from "./levelModel.js";
|
||||||
|
|
||||||
|
// Impor operator Op
|
||||||
|
const Op = Sequelize.Op;
|
||||||
|
|
||||||
const models = {
|
const models = {
|
||||||
User: UserModel(Sequelize.DataTypes),
|
User: UserModel(Sequelize.DataTypes),
|
||||||
Subject: SubjectModel(Sequelize.DataTypes),
|
Subject: SubjectModel(Sequelize.DataTypes),
|
||||||
Topic: TopicModel(Sequelize.DataTypes),
|
Topic: TopicModel(Sequelize.DataTypes),
|
||||||
|
Level: LevelModel(Sequelize.DataTypes),
|
||||||
|
Sequelize,
|
||||||
|
Op,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default models;
|
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",
|
"users",
|
||||||
{
|
{
|
||||||
id: {
|
id: {
|
||||||
type: DataTypes.BIGINT,
|
type: DataTypes.UUID,
|
||||||
primaryKey: true,
|
primaryKey: true,
|
||||||
autoIncrement: true,
|
defaultValue: DataTypes.UUIDV4,
|
||||||
|
allowNull: false,
|
||||||
validate: {
|
validate: {
|
||||||
notEmpty: true,
|
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({
|
res.status(statusCode).json({
|
||||||
payload: data,
|
payload: data,
|
||||||
message: message,
|
message: message,
|
||||||
pagination: {
|
|
||||||
prev: "",
|
|
||||||
next: "",
|
|
||||||
max: "",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,13 @@ import user_routes from "./user.js";
|
||||||
import auth_routes from "./auth.js";
|
import auth_routes from "./auth.js";
|
||||||
import subject_routes from "./subject.js";
|
import subject_routes from "./subject.js";
|
||||||
import topic_routes from "./topic.js";
|
import topic_routes from "./topic.js";
|
||||||
|
import level_routes from "./level.js";
|
||||||
|
|
||||||
const route = express();
|
const route = express();
|
||||||
route.use(user_routes);
|
route.use(user_routes);
|
||||||
route.use(auth_routes);
|
route.use(auth_routes);
|
||||||
route.use(subject_routes);
|
route.use(subject_routes);
|
||||||
route.use(topic_routes);
|
route.use(topic_routes);
|
||||||
|
route.use(level_routes);
|
||||||
|
|
||||||
export default route;
|
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();
|
const router = express.Router();
|
||||||
|
|
||||||
router.get("/user", verifyLoginUser, teacherOnly, getUsers);
|
router.get("/user", verifyLoginUser, adminOnly, getUsers);
|
||||||
|
|
||||||
router.get("/user/:id", getUserById);
|
router.get("/user/:id", getUserById);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user