initial commit

This commit is contained in:
elangptra 2024-08-12 09:44:06 +07:00
commit ea0d3f85d7
24 changed files with 3177 additions and 0 deletions

12
.envexample Normal file
View File

@ -0,0 +1,12 @@
APP_PORT = 3001
DB_HOST = localhost
DB_USER = root
DB_PASSWORD =
DB_NAME = project_siswa
ACCESS_TOKEN_SECRET =
RESET_PASSWORD_SECRET =
EMAIL_USER =
EMAIL_PASS =

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
node_modules
.env

202
controllers/auth.js Normal file
View File

@ -0,0 +1,202 @@
import response from "../response.js";
import bcrypt from "bcrypt";
import jwt from "jsonwebtoken";
import nodemailer from 'nodemailer';
import models from "../models/index.js";
const transporter = nodemailer.createTransport({
service: 'gmail', // Anda bisa menggunakan layanan email lainnya
auth: {
user: process.env.EMAIL_USER,
pass: process.env.EMAIL_PASS,
},
});
export const registerUser = async (req, res) => {
const { name, email, password, confirmPassword } = req.body;
let roles = "student";
if (!name) {
return res.status(400).json({ message: "Name is required!" });
}
if (!email) {
return res.status(400).json({ message: "Email is required!" });
}
if (!password) {
return res.status(400).json({ message: "Password is required!" });
}
if (!confirmPassword) {
return res.status(400).json({ message: "Confirm Password is required!" });
}
if (password !== confirmPassword) {
return res.status(400).json({ message: "Passwords do not match!" });
}
try {
const salt = await bcrypt.genSalt(10);
const hashedPassword = await bcrypt.hash(password, salt);
const newUser = await models.User.create({
name,
email,
password: hashedPassword,
roles,
});
res.status(200).json({ message: "Registration success", result: newUser });
} catch (error) {
console.log(error);
// Check for unique constraint error on email
if (error.name === "SequelizeUniqueConstraintError") {
return res.status(400).json({ message: "Email already registered!" });
}
res.status(500).json({ message: "Internal Server Error" });
}
};
export const loginUser = async (req, res) => {
const { email, password } = req.body;
if (!email) {
return response(400, null, "Email is required!", res);
}
if (!password) {
return response(400, null, "Password is required!", res);
}
try {
const user = await models.User.findOne({ where: { email } });
if (!user) {
return response(404, null, "User data not found!", res);
}
const validPassword = await bcrypt.compare(password, user.password);
if (!validPassword) {
return response(401, null, "The password you entered is incorrect!", res);
}
const accessToken = jwt.sign(
{ id: user.id },
process.env.ACCESS_TOKEN_SECRET
);
// Set tokens as HTTP-only cookies
res.cookie("accessToken", accessToken, {
httpOnly: true,
secure: process.env.NODE_ENV === "production", // Use secure cookies in production
});
// Selectively pick fields to send in the response
const userResponse = {
id: user.id,
name: user.name,
email: user.email,
roles: user.roles,
};
response(200, userResponse, "Success", res);
} catch (error) {
console.log(error);
res.status(500).json({ message: "Internal Server Error" });
}
};
export const logoutUser = (req, res) => {
res.clearCookie("accessToken", {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
});
res.status(200).json({ message: "You have successfully logged out." });
};
export const forgotPassword = async (req, res) => {
const { email } = req.body;
if (!email) {
return response(400, null, "Email is required!", res);
}
try {
const user = await models.User.findOne({ where: { email } });
if (!user) {
return response(404, null, "Email is not registered!", res);
}
const resetToken = jwt.sign({ id: user.id }, process.env.RESET_PASSWORD_SECRET, {
expiresIn: '1h', // Token valid for 1 hour
});
const resetLink = `http://localhost:${process.env.APP_PORT}/resetPassword/${resetToken}`;
const mailOptions = {
from: process.env.EMAIL_USER,
to: user.email,
subject: 'Password Reset',
text: `You are receiving this because you (or someone else) have requested the reset of the password for your account.
Please click on the following link, or paste this into your browser to complete the process:
${resetLink}
If you did not request this, please ignore this email and your password will remain unchanged.`,
};
await transporter.sendMail(mailOptions);
response(200, null, "Password reset email sent successfully!", res);
} catch (error) {
console.log(error);
res.status(500).json({ message: "Internal Server Error" });
}
};
export const resetPassword = async (req, res) => {
const { token, newPassword, confirmNewPassword } = req.body;
if (!token) {
return response(400, null, "Token is required!", res);
}
if (!newPassword) {
return response(400, null, "New password is required!", res);
}
if (!confirmNewPassword) {
return response(400, null, "Confirm new password is required!", res);
}
if (newPassword !== confirmNewPassword) {
return response(400, null, "Passwords do not match!", res);
}
try {
const decoded = jwt.verify(token, process.env.RESET_PASSWORD_SECRET);
const user = await models.User.findOne({ where: { id: decoded.id } });
if (!user) {
return response(404, null, "User data not found!", res);
}
const salt = await bcrypt.genSalt(10);
const hashedPassword = await bcrypt.hash(newPassword, salt);
user.password = hashedPassword;
await user.save();
response(200, null, "Password has been reset successfully!", res);
} catch (error) {
console.log(error);
if (error.name === "TokenExpiredError") {
return response(400, null, "Reset token has expired!", res);
} else {
return res.status(500).json({ message: "Internal Server Error" });
}
}
};

137
controllers/subject.js Normal file
View File

@ -0,0 +1,137 @@
import response from "../response.js";
import models from "../models/index.js";
import fs from "fs";
import path from "path";
export const getSubjects = async (req, res) => {
try {
const subjects = await models.Subject.findAll();
response(200, subjects, "Success", res);
} catch (error) {
console.log(error);
response(500, null, "Error retrieving subjects data!", res);
}
};
export const getSubjectById = async (req, res) => {
try {
const { id } = req.params;
const subject = await models.Subject.findByPk(id);
if (!subject) {
return response(404, null, "Subject not found", res);
}
response(200, subject, "Success", res);
} catch (error) {
console.log(error);
res.status(500).json({ message: "Internal Server Error" });
}
};
export const createSubject = async (req, res) => {
const { name, description, icon, thumbnail } = req.body;
// Validate name
if (!name) {
return response(400, null, "Name is required", res);
}
// Validate description
if (!description) {
return response(400, null, "Description is required", res);
}
try {
const newSubject = await models.Subject.create({
name,
description,
icon,
thumbnail,
});
response(201, newSubject, "Subject created successfully", res);
} catch (error) {
console.log(error);
res.status(500).json({ message: "Internal Server Error" });
}
};
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;
try {
const subject = await models.Subject.findByPk(id);
if (!subject) {
return response(404, null, "Subject not found", res);
}
// Update subject fields
if (name) subject.name = name;
if (description) subject.description = description;
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));
}
subject.icon = icon;
}
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));
}
subject.thumbnail = thumbnail;
}
await subject.save();
response(200, subject, "Subject updated successfully", res);
} catch (error) {
console.log(error);
response(500, null, "Internal Server Error", res);
}
};
export const deleteSubjectById = async (req, res) => {
const { id } = req.params;
try {
const subject = await models.Subject.findByPk(id);
if (!subject) {
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));
}
if (
subject.thumbnail &&
fs.existsSync(path.join("public/uploads", subject.thumbnail))
) {
fs.unlinkSync(path.join("public/uploads", subject.thumbnail));
}
await subject.destroy();
response(200, null, "Subject deleted successfully", res);
} catch (error) {
console.log(error);
response(500, null, "Internal Server Error", res);
}
};

117
controllers/topic.js Normal file
View File

@ -0,0 +1,117 @@
import response from "../response.js";
import models from "../models/index.js";
export const getTopics = async (req, res) => {
try {
const topics = await models.Topic.findAll();
response(200, topics, "Success", res);
} catch (error) {
console.log(error);
response(500, null, "Error retrieving topics data!", res);
}
};
export const getTopicById = async (req, res) => {
try {
const { id } = req.params;
const topic = await models.Topic.findByPk(id);
if (!topic) {
return response(404, null, "Topic not found", res);
}
response(200, topic, "Success", res);
} catch (error) {
console.log(error);
res.status(500).json({ message: "Internal Server Error" });
}
};
export const createTopic = async (req, res) => {
const { subject_id, title } = req.body;
// Validate subject_id
if (!subject_id) {
return response(400, null, "Subject ID is required", res);
}
// Validate title
if (!title) {
return response(400, null, "Title is required", res);
}
try {
// Verify that the subject_id exists in the m_subjects table
const subject = await models.Subject.findByPk(subject_id);
if (!subject) {
return response(404, null, "Subject not found", res);
}
const newTopic = await models.Topic.create({
subject_id,
title,
});
response(201, newTopic, "Topic created successfully", res);
} catch (error) {
console.log(error);
response(500, null, "Internal Server Error", res);
}
};
export const updateTopicById = async (req, res) => {
const { id } = req.params;
const { subject_id, title } = req.body;
try {
// Find the topic by its ID
const topic = await models.Topic.findByPk(id);
if (!topic) {
return response(404, null, "Topic not found", res);
}
// Validate and update subject_id if provided
if (subject_id) {
const subject = await models.Subject.findByPk(subject_id);
if (!subject) {
return response(404, null, "Subject not found", res);
}
topic.subject_id = subject_id;
}
// Validate and update title if provided
if (title) {
topic.title = title;
}
// Save the updated topic
await topic.save();
response(200, topic, "Topic updated successfully", res);
} catch (error) {
console.log(error);
response(500, null, "Internal Server Error", res);
}
};
export const deleteTopicById = async (req, res) => {
const { id } = req.params;
try {
// Find the topic by its ID
const topic = await models.Topic.findByPk(id);
if (!topic) {
return response(404, null, "Topic not found", res);
}
// Delete the topic
await topic.destroy();
response(200, null, "Topic deleted successfully", res);
} catch (error) {
console.log(error);
response(500, null, "Internal Server Error", res);
}
};

94
controllers/user.js Normal file
View File

@ -0,0 +1,94 @@
import response from "../response.js";
import models from "../models/index.js";
import bcrypt from "bcrypt";
export const getUsers = async (req, res) => {
try {
const users = await models.User.findAll();
response(200, users, "Success", res);
} catch (error) {
console.log(error);
response(500, null, "Error retrieving users data!", res);
}
};
export const getUserById = async (req, res) => {
try {
const { id } = req.params;
const user = await models.User.findByPk(id);
if (!user) {
return response(404, null, "User not found", res);
}
response(200, user, "Success", res);
} catch (error) {
console.log(error);
res.status(500).json({ message: "Internal Server Error" });
}
};
export const updateUserById = async (req, res) => {
try {
const { id } = req.params;
const { name, email, password, roles } = req.body;
// Find the user by ID
const user = await models.User.findByPk(id);
if (!user) {
return response(404, null, "User not found", res);
}
// Check if the email is unique if it is being updated
if (email && email !== user.email) {
const emailExists = await models.User.findOne({ where: { email } });
if (emailExists) {
return response(400, null, "Email already in use", res);
}
user.email = email;
}
// Hash the password if it is being updated
if (password) {
const salt = await bcrypt.genSalt(10);
user.password = await bcrypt.hash(password, salt);
}
// Update other user information
user.name = name || user.name;
user.roles = roles || user.roles;
// Manually update the updated_at field
user.updated_at = new Date();
// Save the updated user information
await user.save();
response(200, user, "User updated successfully", res);
} catch (error) {
console.log(error);
res.status(500).json({ message: "Internal Server Error" });
}
};
export const deleteUserById = async (req, res) => {
try {
const { id } = req.params;
// Find the user by ID
const user = await models.User.findByPk(id);
if (!user) {
return response(404, null, "User not found", res);
}
// Delete the user
await user.destroy();
response(200, null, "User deleted successfully", res);
} catch (error) {
console.log(error);
res.status(500).json({ message: "Internal Server Error" });
}
};

37
database/db.js Normal file
View File

@ -0,0 +1,37 @@
import { Sequelize } from "sequelize";
import dotenv from "dotenv";
dotenv.config();
const host = process.env.DB_HOST;
const name = process.env.DB_NAME;
const username = process.env.DB_USER;
const password = process.env.DB_PASSWORD;
const db = new Sequelize(name, username, password, {
host: host,
dialect: "mysql",
logging: false,
});
const testConnection = async () => {
try {
await db.authenticate();
console.log("Database connected");
} catch (error) {
console.log("Error connecting to database", error);
}
};
const query = async (query, value) => {
try {
const [rows] = await db.query(query, value);
return rows;
} catch (error) {
console.log(error);
}
};
export { testConnection, query };
export default db;

22
index.js Normal file
View File

@ -0,0 +1,22 @@
import express from 'express';
import cors from 'cors';
import dotenv from 'dotenv';
import { testConnection } from "./database/db.js";
import router from "./routes/index.js";
import cookieParser from 'cookie-parser';
dotenv.config();
const app = express();
app.use(cors());
app.use(cookieParser());
app.use(express.json());
app.use(router);
// Serve static files from the uploads directory
app.use(express.static('public'));
app.listen(process.env.APP_PORT, () => {
testConnection();
console.log(`Server running on port http://localhost:${process.env.APP_PORT}`);
})

60
middlewares/authUser.js Normal file
View File

@ -0,0 +1,60 @@
import jwt from "jsonwebtoken";
import models from "../models/index.js";
export const verifyLoginUser = async (req, res, next) => {
const { accessToken } = req.cookies;
if (!accessToken) {
return res
.status(401)
.json({ message: "Please log in to your account first!" });
}
try {
// Verifikasi token dan dapatkan payload yang didekode
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);
if (!user) {
return res.status(404).json({ message: "User not found!" });
}
// Simpan informasi user di req.user untuk penggunaan selanjutnya
req.user = user;
// Lanjutkan ke route handler berikutnya
next();
} catch (error) {
if (error.name === "JsonWebTokenError") {
return res.status(403).json({ message: "Invalid token!" });
} else {
return res
.status(500)
.json({ message: "An error occurred on the server!" });
}
}
};
// Middleware untuk memverifikasi apakah pengguna adalah admin
export const adminOnly = (req, res, next) => {
if (!req.user || req.user.roles !== "admin") {
return res.status(403).json({
message:
"Access denied! You do not have admin access.",
});
}
next();
};
// Middleware untuk memverifikasi apakah pengguna adalah teacher
export const teacherOnly = (req, res, next) => {
if (!req.user || req.user.roles !== "teacher") {
return res.status(403).json({
message:
"Access denied! You do not have teacher access.",
});
}
next();
};

74
middlewares/upload.js Normal file
View File

@ -0,0 +1,74 @@
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 (ext === ".png" || ext === ".jpg" || ext === ".jpeg") {
cb(null, true);
} else {
cb(
new Error(
"Invalid file type, only .png, .jpg, and .jpeg files are allowed!"
),
false
);
}
};
const upload = multer({
storage: memoryStorage,
fileFilter,
limits: { fileSize: 5 * 1024 * 1024 }, // Limit file size to 5MB
}).fields([
{ name: "icon", maxCount: 1 },
{ name: "thumbnail", maxCount: 1 },
]);
const saveFileToDisk = (file) => {
const md5sum = crypto
.createHash("md5")
.update(Date.now().toString())
.digest("hex");
const ext = path.extname(file.originalname);
const filename = `${md5sum}${ext}`;
const filepath = path.join("public/uploads", filename);
fs.writeFileSync(filepath, file.buffer);
return filename;
};
const handleUpload = (req, res, next) => {
upload(req, res, (err) => {
if (err) {
return response(400, null, err.message, res);
}
const files = req.files;
const icon = files?.icon ? files.icon[0] : null;
const thumbnail = files?.thumbnail ? files.thumbnail[0] : null;
try {
// Validate icon and thumbnail before saving
if (icon && thumbnail) {
const iconFilename = saveFileToDisk(icon);
const thumbnailFilename = saveFileToDisk(thumbnail);
// Update the filenames in the request object for further processing
req.body.icon = iconFilename;
req.body.thumbnail = thumbnailFilename;
next();
} else {
return response(400, null, "Both icon and thumbnail are required", res);
}
} catch (error) {
return response(500, null, "Internal Server Error", res);
}
});
};
export default handleUpload;

12
models/index.js Normal file
View File

@ -0,0 +1,12 @@
import { Sequelize } from "sequelize";
import UserModel from "./userModel.js";
import SubjectModel from "./subjectModel.js";
import TopicModel from "./topicModel.js";
const models = {
User: UserModel(Sequelize.DataTypes),
Subject: SubjectModel(Sequelize.DataTypes),
Topic: TopicModel(Sequelize.DataTypes),
};
export default models;

51
models/subjectModel.js Normal file
View File

@ -0,0 +1,51 @@
import db from "../database/db.js";
const SubjectModel = (DataTypes) => {
const Subjects = db.define(
"m_subjects",
{
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
validate: {
notEmpty: true,
},
},
name: {
type: DataTypes.STRING(50),
allowNull: false,
validate: {
notEmpty: true,
},
},
description: {
type: DataTypes.TEXT,
allowNull: false,
validate: {
notEmpty: true,
},
},
icon: {
type: DataTypes.STRING(255),
allowNull: true,
},
thumbnail: {
type: DataTypes.STRING(255),
allowNull: true,
},
ts_entri: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW,
},
},
{
timestamps: false, // Disable Sequelize's automatic timestamp fields (createdAt, updatedAt)
tableName: "m_subjects", // Ensure the table name matches the actual table name
}
);
return Subjects;
};
export default SubjectModel;

47
models/topicModel.js Normal file
View File

@ -0,0 +1,47 @@
import db from "../database/db.js";
const TopicModel = (DataTypes) => {
const Topics = db.define(
"m_topics",
{
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
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
},
},
title: {
type: DataTypes.STRING(255),
allowNull: false,
validate: {
notEmpty: true,
},
},
ts_entri: {
type: DataTypes.DATE,
allowNull: true,
defaultValue: DataTypes.NOW,
},
},
{
timestamps: false, // Disable Sequelize's automatic timestamp fields (createdAt, updatedAt)
tableName: "m_topics", // Ensure the table name matches the actual table name
}
);
return Topics;
};
export default TopicModel;

66
models/userModel.js Normal file
View File

@ -0,0 +1,66 @@
import db from "../database/db.js";
const UserModel = (DataTypes) => {
const Users = db.define(
"users",
{
id: {
type: DataTypes.BIGINT,
primaryKey: true,
autoIncrement: true,
validate: {
notEmpty: true,
},
},
name: {
type: DataTypes.STRING,
allowNull: false,
validate: {
notEmpty: true,
},
},
email: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
validate: {
notEmpty: true,
isEmail: true,
},
},
email_verified_at: {
type: DataTypes.DATE,
allowNull: true,
},
password: {
type: DataTypes.STRING,
allowNull: false,
},
roles: {
type: DataTypes.STRING,
allowNull: true,
},
remember_token: {
type: DataTypes.STRING(100),
allowNull: true,
},
created_at: {
type: DataTypes.DATE,
allowNull: true,
defaultValue: DataTypes.NOW,
},
updated_at: {
type: DataTypes.DATE,
allowNull: true,
defaultValue: DataTypes.NOW,
},
},
{
timestamps: false, // Disable Sequelize's automatic timestamp fields (createdAt, updatedAt)
tableName: "users", // Ensure the table name matches the actual table name
}
);
return Users;
};
export default UserModel;

2122
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

27
package.json Normal file
View File

@ -0,0 +1,27 @@
{
"name": "backend",
"version": "1.0.0",
"description": "",
"main": "index.js",
"type": "module",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"api-start": "nodemon index.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"bcrypt": "^5.1.1",
"cookie-parser": "^1.4.6",
"cors": "^2.8.5",
"dotenv": "^16.4.5",
"express": "^4.19.2",
"jsonwebtoken": "^9.0.2",
"multer": "^1.4.5-lts.1",
"mysql2": "^3.11.0",
"nodemailer": "^6.9.14",
"nodemon": "^3.1.4",
"sequelize": "^6.37.3"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

13
response.js Normal file
View File

@ -0,0 +1,13 @@
const response = (statusCode, data, message, res) => {
res.status(statusCode).json({
payload: data,
message: message,
pagination: {
prev: "",
next: "",
max: "",
},
});
};
export default response;

16
routes/auth.js Normal file
View File

@ -0,0 +1,16 @@
import express from "express";
import { registerUser, loginUser, logoutUser, forgotPassword, resetPassword } from "../controllers/auth.js";
const router = express.Router();
router.post("/register", registerUser);
router.post("/login", loginUser);
router.post("/logout", logoutUser);
router.post("/forgotPassword", forgotPassword)
router.post("/resetPassword", resetPassword)
export default router;

13
routes/index.js Normal file
View File

@ -0,0 +1,13 @@
import express from "express";
import user_routes from "./user.js";
import auth_routes from "./auth.js";
import subject_routes from "./subject.js";
import topic_routes from "./topic.js";
const route = express();
route.use(user_routes);
route.use(auth_routes);
route.use(subject_routes);
route.use(topic_routes);
export default route;

19
routes/subject.js Normal file
View File

@ -0,0 +1,19 @@
import express from "express";
import handleUpload from '../middlewares/upload.js';
import { getSubjects, getSubjectById, createSubject, updateSubjectById, deleteSubjectById } from "../controllers/subject.js";
import { verifyLoginUser, adminOnly, teacherOnly } from "../middlewares/authUser.js";
const router = express.Router();
router.get("/subject", verifyLoginUser, adminOnly, getSubjects);
router.get("/subject/:id", getSubjectById);
router.post("/subject", handleUpload, createSubject);
router.put('/subject/:id', handleUpload, updateSubjectById);
router.delete('/subject/:id', deleteSubjectById);
export default router

18
routes/topic.js Normal file
View File

@ -0,0 +1,18 @@
import express from "express";
import { getTopics, getTopicById, createTopic, updateTopicById, deleteTopicById } from "../controllers/topic.js";
import { verifyLoginUser, adminOnly, teacherOnly } from "../middlewares/authUser.js";
const router = express.Router();
router.get("/topic", getTopics);
router.get("/topic/:id", getTopicById);
router.post("/topic", createTopic);
router.put("/topic/:id", updateTopicById);
router.delete("/topic/:id", deleteTopicById);
export default router

16
routes/user.js Normal file
View File

@ -0,0 +1,16 @@
import express from "express";
import { getUsers, getUserById, updateUserById, deleteUserById } from "../controllers/user.js";
import { verifyLoginUser, adminOnly, teacherOnly } from "../middlewares/authUser.js";
const router = express.Router();
router.get("/user", verifyLoginUser, teacherOnly, getUsers);
router.get("/user/:id", getUserById);
router.post("/user/update/:id", updateUserById);
router.delete("/user/delete/:id", deleteUserById);
export default router