backend_adaptive_learning/controllers/usersControllers/user.js

813 lines
20 KiB
JavaScript

import response from "../../response.js";
import models from "../../models/index.js";
import bcrypt from "bcryptjs";
import fs from "fs";
import path from "path";
import {
clearFileBuffers,
saveFileToDisk,
} from "../../middlewares/User/uploadUser.js";
export const getUsers = async (req, res) => {
try {
const users = await models.User.findAll({
attributes: {
exclude: ["PASSWORD"],
include: [
[
models.Sequelize.literal(
`COALESCE(\`teachers\`.\`NIP\`, \`students\`.\`NISN\`)`
),
"NIP/NISN",
],
],
},
include: [
{
model: models.Teacher,
as: "teachers",
attributes: [],
},
{
model: models.Student,
as: "students",
attributes: [],
},
],
});
response(200, users, "Success", res);
} catch (error) {
console.log(error);
response(500, null, "Error retrieving users data!", res);
}
};
export const getAdmins = async (req, res) => {
try {
const admins = await models.User.findAll({
where: {
ROLE: "admin",
},
attributes: {
exclude: ["PASSWORD"],
},
});
response(200, admins, "Success", res);
} catch (error) {
console.log(error);
response(500, null, "Error retrieving admin data!", res);
}
};
export const getTeachers = async (req, res) => {
const { page = 1, limit = 10, search = "", sort = "time" } = req.query;
try {
const totalTeachersCount = await models.User.count({
where: {
ROLE: "teacher",
},
});
const { count, rows: teachers } = await models.User.findAndCountAll({
where: {
ROLE: "teacher",
...(search && {
[models.Op.or]: [
{
NAME_USERS: {
[models.Op.like]: `%${search}%`,
},
},
{
EMAIL: {
[models.Op.like]: `%${search}%`,
},
},
{
"$teachers.NIP$": {
[models.Op.like]: `%${search}%`,
},
},
],
}),
},
attributes: ["ID", "NAME_USERS", "EMAIL", "ROLE", "TIME_USERS"],
include: [
{
model: models.Teacher,
as: "teachers",
attributes: ["NIP"],
},
],
distinct: true,
raw: true,
nest: true,
});
const formattedTeachers = teachers.map((teacher) => ({
ID: teacher.ID,
NAME_USERS: teacher.NAME_USERS,
EMAIL: teacher.EMAIL,
NIP: teacher.teachers.NIP,
ROLE: teacher.ROLE,
TIME_USERS: teacher.TIME_USERS,
}));
if (sort === "nip") {
formattedTeachers.sort((a, b) => a.NIP.localeCompare(b.NIP));
} else if (sort === "name") {
formattedTeachers.sort((a, b) =>
a.NAME_USERS.localeCompare(b.NAME_USERS)
);
} else if (sort === "email") {
formattedTeachers.sort((a, b) => a.EMAIL.localeCompare(b.EMAIL));
} else {
formattedTeachers.sort(
(a, b) => new Date(b.TIME_USERS) - new Date(a.TIME_USERS)
);
}
const paginatedTeachers = formattedTeachers.slice(
(page - 1) * limit,
page * limit
);
const totalPages = Math.ceil(count / limit);
const currentPage = parseInt(page);
response(
200,
{
teachers: paginatedTeachers,
currentPage,
totalPages,
totalSearchedItems: count,
totalTeachers: totalTeachersCount,
},
"Teachers retrieved successfully",
res
);
} catch (error) {
console.log(error);
response(500, null, "Error retrieving teacher data!", res);
}
};
export const getStudents = async (req, res) => {
const { page = 1, limit = 10, search = "", sort = "time" } = req.query;
try {
const totalStudentsCount = await models.User.count({
where: {
ROLE: "student",
},
});
const { count, rows: students } = await models.User.findAndCountAll({
where: {
ROLE: "student",
...(search && {
[models.Op.or]: [
{
NAME_USERS: {
[models.Op.like]: `%${search}%`,
},
},
{
EMAIL: {
[models.Op.like]: `%${search}%`,
},
},
{
"$students.NISN$": {
[models.Op.like]: `%${search}%`,
},
},
],
}),
},
attributes: ["ID", "NAME_USERS", "EMAIL", "ROLE", "TIME_USERS"],
include: [
{
model: models.Student,
as: "students",
attributes: ["NISN"],
include: [
{
model: models.Class,
as: "studentClass",
attributes: ["NAME_CLASS"],
},
],
},
],
distinct: true,
raw: true,
nest: true,
});
const formattedStudents = students.map((student) => ({
ID: student.ID,
NISN: student.students.NISN,
NAME_USERS: student.NAME_USERS,
EMAIL: student.EMAIL,
NAME_CLASS: student.students.studentClass.NAME_CLASS,
ROLE: student.ROLE,
TIME_USERS: student.TIME_USERS,
}));
if (sort === "nisn") {
formattedStudents.sort((a, b) =>
String(a.NISN).localeCompare(String(b.NISN))
);
} else if (sort === "name") {
formattedStudents.sort((a, b) =>
a.NAME_USERS.localeCompare(b.NAME_USERS)
);
} else if (sort === "email") {
formattedStudents.sort((a, b) => a.EMAIL.localeCompare(b.EMAIL));
} else {
formattedStudents.sort(
(a, b) => new Date(b.TIME_USERS) - new Date(a.TIME_USERS)
);
}
const paginatedStudents = formattedStudents.slice(
(page - 1) * limit,
page * limit
);
const totalPages = Math.ceil(count / limit);
const currentPage = parseInt(page);
response(
200,
{
students: paginatedStudents,
currentPage,
totalPages,
totalSearchedItems: count,
totalStudents: totalStudentsCount,
},
"Students retrieved successfully",
res
);
} catch (error) {
console.log(error);
response(500, null, "Error retrieving students data!", res);
}
};
export const getUserById = async (req, res) => {
try {
const { id } = req.params;
const userWithDetails = await models.User.findByPk(id, {
attributes: {
exclude: ["PASSWORD"],
},
include: [
{
model: models.Teacher,
as: "teachers",
attributes: ["NIP"],
},
{
model: models.Student,
as: "students",
attributes: ["NISN"],
include: [
{
model: models.Class,
as: "studentClass",
attributes: ["NAME_CLASS"],
},
],
},
],
});
if (!userWithDetails) {
return response(404, null, "User not found", res);
}
let additionalField = null;
if (userWithDetails.ROLE === "teacher") {
additionalField = { NIP: userWithDetails.teachers.NIP };
} else if (userWithDetails.ROLE === "student") {
additionalField = {
NISN: userWithDetails.students.NISN,
NAME_CLASS: userWithDetails.students.studentClass
? userWithDetails.students.studentClass.NAME_CLASS
: null,
};
}
const responseObject = {
ID: userWithDetails.ID,
NAME_USERS: userWithDetails.NAME_USERS,
EMAIL: userWithDetails.EMAIL,
ROLE: userWithDetails.ROLE,
...additionalField,
PICTURE: userWithDetails.PICTURE,
};
response(200, responseObject, "Success", res);
} catch (error) {
console.error(error);
response(500, null, "Error retrieving user data!", res);
}
};
export const getUserByName = async (req, res) => {
try {
const { name } = req.params;
const userWithDetails = await models.User.findOne({
where: { NAME_USERS: name },
attributes: {
exclude: ["PASSWORD"],
},
include: [
{
model: models.Teacher,
as: "teachers",
attributes: ["NIP"],
},
{
model: models.Student,
as: "students",
attributes: ["NISN"],
include: [
{
model: models.Class,
as: "studentClass",
attributes: ["NAME_CLASS"],
},
],
},
],
});
if (!userWithDetails) {
return response(404, null, "User not found", res);
}
let additionalField = null;
if (userWithDetails.ROLE === "teacher") {
additionalField = { NIP: userWithDetails.teachers.NIP };
} else if (userWithDetails.ROLE === "student") {
additionalField = {
NISN: userWithDetails.students.NISN,
NAME_CLASS: userWithDetails.students.studentClass
? userWithDetails.students.studentClass.NAME_CLASS
: null,
};
}
const responseObject = {
ID: userWithDetails.ID,
NAME_USERS: userWithDetails.NAME_USERS,
EMAIL: userWithDetails.EMAIL,
ROLE: userWithDetails.ROLE,
...additionalField,
PICTURE: userWithDetails.PICTURE,
};
response(200, responseObject, "Success", res);
} catch (error) {
console.error(error);
response(500, null, "Error retrieving user data!", res);
}
};
export const updateUserById = async (req, res) => {
const transaction = await models.db.transaction();
const { PICTURE } = req.filesToSave || {};
try {
const { id } = req.params;
const { NAME_USERS, EMAIL, NIP, NISN } = req.body;
const user = await models.User.findByPk(id, {
include: [
{
model: models.Teacher,
as: "teachers",
attributes: ["NIP"],
},
{
model: models.Student,
as: "students",
attributes: ["NISN"],
},
],
transaction,
});
if (!user) {
clearFileBuffers({ PICTURE });
await transaction.rollback();
return response(404, null, "User not found", res);
}
if (user.ROLE === "teacher" && NISN) {
clearFileBuffers({ PICTURE });
await transaction.rollback();
return response(400, null, "Role is teacher, but NISN is provided", res);
}
if (user.ROLE === "student" && NIP) {
clearFileBuffers({ PICTURE });
await transaction.rollback();
return response(400, null, "Role is student, but NIP is provided", res);
}
if (EMAIL && EMAIL !== user.EMAIL) {
const emailExists = await models.User.findOne({
where: { EMAIL: EMAIL },
transaction,
});
if (emailExists) {
clearFileBuffers({ PICTURE });
await transaction.rollback();
return response(400, null, "Email already in use", res);
}
user.EMAIL = EMAIL;
}
user.NAME_USERS = NAME_USERS || user.NAME_USERS;
if (user.ROLE === "teacher" && NIP) {
let teacher = await models.Teacher.findOne({
where: { ID: id },
transaction,
});
if (teacher) {
teacher.NIP = NIP;
await teacher.save({ transaction });
} else {
teacher = await models.Teacher.create(
{ ID: id, NIP: NIP },
{ transaction }
);
}
}
if (user.ROLE === "student" && NISN) {
let student = await models.Student.findOne({
where: { ID: id },
transaction,
});
if (student) {
student.NISN = NISN;
await student.save({ transaction });
} else {
student = await models.Student.create(
{ ID: id, NISN: NISN },
{ transaction }
);
}
}
if (PICTURE) {
if (user.PICTURE) {
const oldPicturePath = path.join(
process.cwd(),
"media/uploads/avatar",
user.PICTURE
);
if (fs.existsSync(oldPicturePath)) {
fs.unlinkSync(oldPicturePath);
}
}
user.PICTURE = saveFileToDisk(PICTURE, user.ID, user.NAME_USERS);
}
await user.save({ transaction });
await user.reload({
include: [
{
model: models.Teacher,
as: "teachers",
attributes: ["NIP"],
},
{
model: models.Student,
as: "students",
attributes: ["NISN"],
},
],
transaction,
});
await transaction.commit();
let userResponse = {
ID: user.ID,
NAME_USERS: user.NAME_USERS,
EMAIL: user.EMAIL,
ROLE: user.ROLE,
PICTURE: user.PICTURE,
};
if (user.ROLE === "student" && user.students) {
userResponse.NISN = user.students.NISN;
} else if (user.ROLE === "teacher" && user.teachers) {
userResponse.NIP = user.teachers.NIP;
}
return response(200, userResponse, "User updated successfully", res);
} catch (error) {
clearFileBuffers({ PICTURE });
await transaction.rollback();
if (error.name === "SequelizeValidationError") {
const validationErrors = error.errors.map((err) => err.message);
return response(400, null, validationErrors.join("; "), res);
}
if (error.name === "SequelizeUniqueConstraintError") {
const tableMatch =
error.original.sqlMessage.match(/for key '(.+)\.(.+)'/);
const tableName = tableMatch ? tableMatch[1] : null;
const constraintName = tableMatch ? tableMatch[2] : null;
if (tableName === "users" && constraintName === "user_unique_email") {
return response(400, null, "Email already registered!", res);
}
if (tableName === "student" && constraintName === "student_unique_nisn") {
return response(400, null, "NISN already registered!", res);
}
if (tableName === "teacher" && constraintName === "teacher_unique_nip") {
return response(400, null, "NIP already registered!", res);
}
return response(400, null, "Unique constraint violation!", res);
}
console.log(error);
return response(500, null, "Internal Server Error", res);
}
};
export const updateUserPasswordById = async (req, res) => {
try {
const { id } = req.params;
const { OLD_PASSWORD, PASSWORD, CONFIRM_PASSWORD } = req.body;
if (!OLD_PASSWORD || !PASSWORD || !CONFIRM_PASSWORD) {
return response(400, null, "All fields must be filled.", res);
}
if (PASSWORD.length < 8) {
return response(
400,
null,
"Password must be at least 8 characters long!",
res
);
}
if (PASSWORD !== CONFIRM_PASSWORD) {
return response(
400,
null,
"New password and confirm password do not match.",
res
);
}
const user = await models.User.findByPk(id);
if (!user) {
return response(404, null, "User not found.", res);
}
const isMatch = await bcrypt.compare(OLD_PASSWORD, user.PASSWORD);
if (!isMatch) {
return response(400, null, "Incorrect old password.", res);
}
const salt = await bcrypt.genSalt(10);
const hashedPassword = await bcrypt.hash(PASSWORD, salt);
user.PASSWORD = hashedPassword;
await user.save();
response(200, null, "Password updated successfully.", res);
} catch (error) {
response(500, null, "Internal Server Error", res);
}
};
export const deleteUserById = async (req, res) => {
const transaction = await models.db.transaction();
try {
const { id } = req.params;
const user = await models.User.findByPk(id, { transaction });
if (!user) {
await transaction.rollback();
return response(404, null, "User not found", res);
}
const studentLearnings = await models.StdLearning.findAll({
where: { ID: id },
attributes: ["ID_STUDENT_LEARNING"],
transaction,
});
const studentLearningIds = studentLearnings.map(
(sl) => sl.ID_STUDENT_LEARNING
);
if (studentLearningIds.length > 0) {
await models.Monitoring.destroy({
where: {
ID_STUDENT_LEARNING: studentLearningIds,
},
transaction,
});
await models.StdExercise.destroy({
where: {
ID_STUDENT_LEARNING: studentLearningIds,
},
transaction,
});
}
await models.StdLearning.destroy({
where: { ID: id },
transaction,
});
await models.Report.destroy({
where: { ID: id },
transaction,
});
if (user.ROLE === "teacher") {
const teacher = await models.Teacher.findOne({
where: { ID: id },
attributes: ["ID_GURU"],
transaction,
});
if (teacher) {
await models.Monitoring.update(
{ ID_GURU: null },
{
where: { ID_GURU: teacher.ID_GURU },
transaction,
}
);
await teacher.destroy({ transaction });
}
await models.Teacher.destroy({ where: { ID: id }, transaction });
} else if (user.ROLE === "student") {
await models.Student.destroy({ where: { ID: id }, transaction });
}
await user.destroy({ transaction });
await transaction.commit();
return response(200, null, "User deleted successfully", res);
} catch (error) {
await transaction.rollback();
console.log(error);
return response(500, null, "Internal Server Error", res);
}
};
export const getMe = async (req, res) => {
try {
const user = req.user;
const userWithDetails = await models.User.findByPk(user.ID, {
attributes: {
exclude: ["PASSWORD"],
},
include: [
{
model: models.Teacher,
as: "teachers",
attributes: ["NIP"],
},
{
model: models.Student,
as: "students",
attributes: ["NISN"],
include: [
{
model: models.Class,
as: "studentClass",
attributes: ["NAME_CLASS"],
},
],
},
],
});
if (!userWithDetails) {
return response(404, null, "User not found", res);
}
let additionalField = null;
if (userWithDetails.ROLE === "teacher") {
additionalField = { NIP: userWithDetails.teachers.NIP };
} else if (userWithDetails.ROLE === "student") {
additionalField = {
NISN: userWithDetails.students.NISN,
NAME_CLASS: userWithDetails.students.studentClass
? userWithDetails.students.studentClass.NAME_CLASS
: null,
};
}
const responseObject = {
ID: userWithDetails.ID,
NAME_USERS: userWithDetails.NAME_USERS,
EMAIL: userWithDetails.EMAIL,
ROLE: userWithDetails.ROLE,
...additionalField,
PICTURE: userWithDetails.PICTURE,
};
response(200, responseObject, "Success", res);
} catch (error) {
console.error(error);
response(500, null, "Error retrieving user data!", res);
}
};
export const sendExcelTemplate = async (req, res) => {
try {
const filePath = path.join(
process.cwd(),
"media/uploads/excel/excel template.xlsx"
);
if (!fs.existsSync(filePath)) {
return res.status(404).json({
success: false,
message: "File not found!",
});
}
const fileName = "excel_template.xlsx";
res.setHeader("Content-Disposition", `attachment; filename=${fileName}`);
res.setHeader(
"Content-Type",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
);
return res.sendFile(filePath);
} catch (error) {
console.error("Error sending file:", error);
return res.status(500).json({
success: false,
message: "Internal Server Error",
});
}
};
export const sendExcelExample = async (req, res) => {
try {
const filePath = path.join(
process.cwd(),
"media/uploads/excel/excel example.xlsx"
);
if (!fs.existsSync(filePath)) {
return res.status(404).json({
success: false,
message: "File not found!",
});
}
const fileName = "excel_example.xlsx";
res.setHeader("Content-Disposition", `attachment; filename=${fileName}`);
res.setHeader(
"Content-Type",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
);
return res.sendFile(filePath);
} catch (error) {
console.error("Error sending file:", error);
return res.status(500).json({
success: false,
message: "Internal Server Error",
});
}
};