710 lines
18 KiB
JavaScript
710 lines
18 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("public/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);
|
|
}
|
|
};
|