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(); 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 !== 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", }); } };