refactor: class model function
This commit is contained in:
parent
80de2108b8
commit
4ced73bca2
30
config/config.js
Normal file
30
config/config.js
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
import dotenv from "dotenv";
|
||||||
|
|
||||||
|
dotenv.config();
|
||||||
|
|
||||||
|
export default {
|
||||||
|
development: {
|
||||||
|
username: process.env.DB_USER,
|
||||||
|
password: process.env.DB_PASSWORD,
|
||||||
|
database: process.env.DB_NAME,
|
||||||
|
host: process.env.DB_HOST,
|
||||||
|
dialect: "mysql",
|
||||||
|
timezone: "+07:00",
|
||||||
|
},
|
||||||
|
test: {
|
||||||
|
username: process.env.DB_USER,
|
||||||
|
password: process.env.DB_PASSWORD,
|
||||||
|
database: process.env.DB_NAME_TEST,
|
||||||
|
host: process.env.DB_HOST,
|
||||||
|
dialect: "mysql",
|
||||||
|
timezone: "+07:00",
|
||||||
|
},
|
||||||
|
production: {
|
||||||
|
username: process.env.DB_USER,
|
||||||
|
password: process.env.DB_PASSWORD,
|
||||||
|
database: process.env.DB_NAME,
|
||||||
|
host: process.env.DB_HOST,
|
||||||
|
dialect: "mysql",
|
||||||
|
timezone: "+07:00",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
@ -272,7 +272,6 @@ export const getLevelsByTopicId = async (req, res) => {
|
||||||
.json({ message: "No levels found for the given topic." });
|
.json({ message: "No levels found for the given topic." });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mencari pembelajaran terakhir yang selesai
|
|
||||||
const lastCompletedLearning = await models.StdLearning.findOne({
|
const lastCompletedLearning = await models.StdLearning.findOne({
|
||||||
where: {
|
where: {
|
||||||
ID: ID,
|
ID: ID,
|
||||||
|
|
@ -290,7 +289,6 @@ export const getLevelsByTopicId = async (req, res) => {
|
||||||
|
|
||||||
let currentLearningLevel = null;
|
let currentLearningLevel = null;
|
||||||
|
|
||||||
// Jika ada pembelajaran yang selesai, kita dapat memeriksa level berikutnya dari NEXT_LEARNING
|
|
||||||
if (lastCompletedLearning?.NEXT_LEARNING) {
|
if (lastCompletedLearning?.NEXT_LEARNING) {
|
||||||
currentLearningLevel = await models.Level.findOne({
|
currentLearningLevel = await models.Level.findOne({
|
||||||
where: {
|
where: {
|
||||||
|
|
@ -303,7 +301,6 @@ export const getLevelsByTopicId = async (req, res) => {
|
||||||
|
|
||||||
const nextLearningLevel = currentLearningLevel;
|
const nextLearningLevel = currentLearningLevel;
|
||||||
|
|
||||||
// Memasukkan level berikutnya (jika ada) ke dalam UNLOCKED_LEVELS
|
|
||||||
const unlockedLevels = lastCompletedLearning
|
const unlockedLevels = lastCompletedLearning
|
||||||
? await models.Level.findAll({
|
? await models.Level.findAll({
|
||||||
where: {
|
where: {
|
||||||
|
|
@ -321,8 +318,10 @@ export const getLevelsByTopicId = async (req, res) => {
|
||||||
|
|
||||||
const unlockedLevelNames = unlockedLevels.map((lvl) => lvl.NAME_LEVEL);
|
const unlockedLevelNames = unlockedLevels.map((lvl) => lvl.NAME_LEVEL);
|
||||||
|
|
||||||
// Menambahkan level berikutnya ke dalam UNLOCKED_LEVELS jika belum termasuk
|
if (
|
||||||
if (nextLearningLevel && !unlockedLevelNames.includes(nextLearningLevel.NAME_LEVEL)) {
|
nextLearningLevel &&
|
||||||
|
!unlockedLevelNames.includes(nextLearningLevel.NAME_LEVEL)
|
||||||
|
) {
|
||||||
unlockedLevelNames.push(nextLearningLevel.NAME_LEVEL);
|
unlockedLevelNames.push(nextLearningLevel.NAME_LEVEL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,95 @@ export const getClassById = async (req, res) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getClassForAdmin = async (req, res) => {
|
||||||
|
const { page = 1, limit = 10, search = "", sort = "time" } = req.query;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { count, rows: classes } = await models.Class.findAndCountAll({
|
||||||
|
where: {
|
||||||
|
...(search && {
|
||||||
|
[models.Op.or]: [
|
||||||
|
{
|
||||||
|
NAME_CLASS: {
|
||||||
|
[models.Op.like]: `%${search}%`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
models.Sequelize.where(
|
||||||
|
models.Sequelize.literal(
|
||||||
|
`(SELECT COUNT(*) FROM student WHERE student.ID_CLASS = class.ID_CLASS)`
|
||||||
|
),
|
||||||
|
{
|
||||||
|
[models.Op.like]: `%${search}%`,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
{
|
||||||
|
TOTAL_STUDENT: {
|
||||||
|
[models.Op.like]: `%${search}%`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
attributes: [
|
||||||
|
"ID_CLASS",
|
||||||
|
"NAME_CLASS",
|
||||||
|
"TOTAL_STUDENT",
|
||||||
|
"TIME_CLASS",
|
||||||
|
[
|
||||||
|
models.Sequelize.literal(
|
||||||
|
`(SELECT COUNT(*) FROM student WHERE student.ID_CLASS = class.ID_CLASS)`
|
||||||
|
),
|
||||||
|
"STUDENTS",
|
||||||
|
],
|
||||||
|
],
|
||||||
|
distinct: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const formattedClasses = classes.map((classItem) => ({
|
||||||
|
ID_CLASS: classItem.ID_CLASS,
|
||||||
|
NAME_CLASS: classItem.NAME_CLASS,
|
||||||
|
STUDENTS: classItem.get("STUDENTS"),
|
||||||
|
TOTAL_STUDENT: classItem.TOTAL_STUDENT,
|
||||||
|
TIME_CLASS: classItem.TIME_CLASS,
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (sort === "class") {
|
||||||
|
formattedClasses.sort((a, b) => a.NAME_CLASS.localeCompare(b.NAME_CLASS));
|
||||||
|
} else if (sort === "total") {
|
||||||
|
formattedClasses.sort((a, b) => a.TOTAL_STUDENT - b.TOTAL_STUDENT);
|
||||||
|
} else if (sort === "students") {
|
||||||
|
formattedClasses.sort((a, b) => a.STUDENTS - b.STUDENTS);
|
||||||
|
} else {
|
||||||
|
formattedClasses.sort(
|
||||||
|
(a, b) => new Date(b.TIME_CLASS) - new Date(a.TIME_CLASS)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const paginatedClasses = formattedClasses.slice(
|
||||||
|
(page - 1) * limit,
|
||||||
|
page * limit
|
||||||
|
);
|
||||||
|
|
||||||
|
const totalPages = Math.ceil(count / limit);
|
||||||
|
const currentPage = parseInt(page);
|
||||||
|
|
||||||
|
response(
|
||||||
|
200,
|
||||||
|
{
|
||||||
|
classes: paginatedClasses,
|
||||||
|
currentPage,
|
||||||
|
totalPages,
|
||||||
|
totalItems: count,
|
||||||
|
},
|
||||||
|
"Classes retrieved successfully",
|
||||||
|
res
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
response(500, null, "Error retrieving classes data!", res);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const createClass = async (req, res) => {
|
export const createClass = async (req, res) => {
|
||||||
const { NAME_CLASS, TOTAL_STUDENT } = req.body;
|
const { NAME_CLASS, TOTAL_STUDENT } = req.body;
|
||||||
|
|
||||||
|
|
@ -85,9 +174,28 @@ export const deleteClassById = async (req, res) => {
|
||||||
return response(404, null, "Class not found", res);
|
return response(404, null, "Class not found", res);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await models.Student.update(
|
||||||
|
{ ID_CLASS: null },
|
||||||
|
{
|
||||||
|
where: { ID_CLASS: id },
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
await models.Monitoring.update(
|
||||||
|
{ ID_CLASS: null },
|
||||||
|
{
|
||||||
|
where: { ID_CLASS: id },
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
await classes.destroy();
|
await classes.destroy();
|
||||||
|
|
||||||
response(200, null, "Class deleted successfully", res);
|
response(
|
||||||
|
200,
|
||||||
|
null,
|
||||||
|
"Class deleted successfully and related models updated",
|
||||||
|
res
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
response(500, null, "Internal Server Error", res);
|
response(500, null, "Internal Server Error", res);
|
||||||
|
|
|
||||||
|
|
@ -111,75 +111,130 @@ export const updateMonitoringClass = async ({
|
||||||
};
|
};
|
||||||
|
|
||||||
export const monitoringStudentsProgress = async (req, res) => {
|
export const monitoringStudentsProgress = async (req, res) => {
|
||||||
try {
|
const { page = 1, limit = 10, search = "", sort = "time" } = req.query;
|
||||||
const result = await models.Monitoring.findAll({
|
|
||||||
include: [
|
|
||||||
{
|
|
||||||
model: models.StdLearning,
|
|
||||||
as: "stdLearningMonitoring",
|
|
||||||
include: [
|
|
||||||
{
|
|
||||||
model: models.User,
|
|
||||||
as: "learningUser",
|
|
||||||
attributes: ["NAME_USERS"],
|
|
||||||
include: [
|
|
||||||
{
|
|
||||||
model: models.Student,
|
|
||||||
as: "students",
|
|
||||||
attributes: ["NISN"],
|
|
||||||
include: [
|
|
||||||
{
|
|
||||||
model: models.Class,
|
|
||||||
as: "studentClass",
|
|
||||||
attributes: ["NAME_CLASS"],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
model: models.Level,
|
|
||||||
as: "level",
|
|
||||||
attributes: ["NAME_LEVEL"],
|
|
||||||
include: [
|
|
||||||
{
|
|
||||||
model: models.Topic,
|
|
||||||
as: "levelTopic",
|
|
||||||
attributes: ["NAME_TOPIC"],
|
|
||||||
include: [
|
|
||||||
{
|
|
||||||
model: models.Section,
|
|
||||||
as: "topicSection",
|
|
||||||
attributes: ["NAME_SECTION"],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
const formattedResult = result.map((monitoring) => {
|
try {
|
||||||
return {
|
const { count, rows: monitorings } =
|
||||||
ID_MONITORING: monitoring.ID_MONITORING,
|
await models.Monitoring.findAndCountAll({
|
||||||
NISN: monitoring.stdLearningMonitoring?.learningUser?.students?.NISN,
|
include: [
|
||||||
NAME_USERS: monitoring.stdLearningMonitoring?.learningUser?.NAME_USERS,
|
{
|
||||||
NAME_SECTION:
|
model: models.StdLearning,
|
||||||
monitoring.stdLearningMonitoring?.level?.levelTopic?.topicSection
|
as: "stdLearningMonitoring",
|
||||||
?.NAME_SECTION,
|
include: [
|
||||||
NAME_TOPIC:
|
{
|
||||||
monitoring.stdLearningMonitoring?.level?.levelTopic?.NAME_TOPIC,
|
model: models.User,
|
||||||
NAME_CLASS:
|
as: "learningUser",
|
||||||
monitoring.stdLearningMonitoring?.learningUser?.students?.studentClass
|
attributes: ["NAME_USERS"],
|
||||||
?.NAME_CLASS ?? null,
|
include: [
|
||||||
};
|
{
|
||||||
});
|
model: models.Student,
|
||||||
|
as: "students",
|
||||||
|
attributes: ["NISN"],
|
||||||
|
include: [
|
||||||
|
{
|
||||||
|
model: models.Class,
|
||||||
|
as: "studentClass",
|
||||||
|
attributes: ["NAME_CLASS"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
model: models.Level,
|
||||||
|
as: "level",
|
||||||
|
attributes: ["NAME_LEVEL"],
|
||||||
|
include: [
|
||||||
|
{
|
||||||
|
model: models.Topic,
|
||||||
|
as: "levelTopic",
|
||||||
|
attributes: ["NAME_TOPIC"],
|
||||||
|
include: [
|
||||||
|
{
|
||||||
|
model: models.Section,
|
||||||
|
as: "topicSection",
|
||||||
|
attributes: ["NAME_SECTION"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
where: {
|
||||||
|
...(search && {
|
||||||
|
[models.Op.or]: [
|
||||||
|
{
|
||||||
|
"$stdLearningMonitoring.learningUser.students.NISN$": {
|
||||||
|
[models.Op.like]: `%${search}%`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$stdLearningMonitoring.learningUser.NAME_USERS$": {
|
||||||
|
[models.Op.like]: `%${search}%`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$stdLearningMonitoring.level.levelTopic.topicSection.NAME_SECTION$":
|
||||||
|
{
|
||||||
|
[models.Op.like]: `%${search}%`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$stdLearningMonitoring.level.levelTopic.NAME_TOPIC$": {
|
||||||
|
[models.Op.like]: `%${search}%`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
distinct: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const formattedResult = monitorings.map((monitoring) => ({
|
||||||
|
ID_MONITORING: monitoring.ID_MONITORING,
|
||||||
|
NISN: monitoring.stdLearningMonitoring?.learningUser?.students?.NISN,
|
||||||
|
NAME_USERS: monitoring.stdLearningMonitoring?.learningUser?.NAME_USERS,
|
||||||
|
NAME_SECTION:
|
||||||
|
monitoring.stdLearningMonitoring?.level?.levelTopic?.topicSection
|
||||||
|
?.NAME_SECTION,
|
||||||
|
NAME_TOPIC:
|
||||||
|
monitoring.stdLearningMonitoring?.level?.levelTopic?.NAME_TOPIC,
|
||||||
|
NAME_CLASS:
|
||||||
|
monitoring.stdLearningMonitoring?.learningUser?.students?.studentClass
|
||||||
|
?.NAME_CLASS ?? null,
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (sort === "nisn") {
|
||||||
|
formattedResult.sort((a, b) => a.NISN.localeCompare(b.NISN));
|
||||||
|
} else if (sort === "name") {
|
||||||
|
formattedResult.sort((a, b) => a.NAME_USERS.localeCompare(b.NAME_USERS));
|
||||||
|
} else if (sort === "section") {
|
||||||
|
formattedResult.sort((a, b) =>
|
||||||
|
a.NAME_SECTION.localeCompare(b.NAME_SECTION)
|
||||||
|
);
|
||||||
|
} else if (sort === "topic") {
|
||||||
|
formattedResult.sort((a, b) => a.NAME_TOPIC.localeCompare(b.NAME_TOPIC));
|
||||||
|
} else {
|
||||||
|
formattedResult.sort((a, b) => new Date(b.TIME) - new Date(a.TIME));
|
||||||
|
}
|
||||||
|
|
||||||
|
const paginatedResult = formattedResult.slice(
|
||||||
|
(page - 1) * limit,
|
||||||
|
page * limit
|
||||||
|
);
|
||||||
|
const totalPages = Math.ceil(count / limit);
|
||||||
|
const currentPage = parseInt(page);
|
||||||
|
|
||||||
response(
|
response(
|
||||||
200,
|
200,
|
||||||
formattedResult,
|
{
|
||||||
|
monitorings: paginatedResult,
|
||||||
|
currentPage,
|
||||||
|
totalPages,
|
||||||
|
totalItems: count,
|
||||||
|
},
|
||||||
"Success retrieving student monitoring progress!",
|
"Success retrieving student monitoring progress!",
|
||||||
res
|
res
|
||||||
);
|
);
|
||||||
|
|
@ -191,6 +246,7 @@ export const monitoringStudentsProgress = async (req, res) => {
|
||||||
|
|
||||||
export const monitoringStudentProgressById = async (req, res) => {
|
export const monitoringStudentProgressById = async (req, res) => {
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
|
const { page = 1, limit = 10, search = "", sort = "start" } = req.query;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const monitoring = await models.Monitoring.findOne({
|
const monitoring = await models.Monitoring.findOne({
|
||||||
|
|
@ -213,6 +269,32 @@ export const monitoringStudentProgressById = async (req, res) => {
|
||||||
model: models.Level,
|
model: models.Level,
|
||||||
as: "level",
|
as: "level",
|
||||||
attributes: ["ID_TOPIC", "NAME_LEVEL"],
|
attributes: ["ID_TOPIC", "NAME_LEVEL"],
|
||||||
|
include: [
|
||||||
|
{
|
||||||
|
model: models.Topic,
|
||||||
|
as: "levelTopic",
|
||||||
|
attributes: ["NAME_TOPIC"],
|
||||||
|
include: [
|
||||||
|
{
|
||||||
|
model: models.Section,
|
||||||
|
as: "topicSection",
|
||||||
|
attributes: ["NAME_SECTION"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
model: models.User,
|
||||||
|
as: "learningUser",
|
||||||
|
attributes: ["NAME_USERS"],
|
||||||
|
include: [
|
||||||
|
{
|
||||||
|
model: models.Student,
|
||||||
|
as: "students",
|
||||||
|
attributes: ["NISN"],
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
@ -231,10 +313,38 @@ export const monitoringStudentProgressById = async (req, res) => {
|
||||||
|
|
||||||
const userID = stdLearning.ID;
|
const userID = stdLearning.ID;
|
||||||
const topicID = stdLearning.level.ID_TOPIC;
|
const topicID = stdLearning.level.ID_TOPIC;
|
||||||
|
const studentName = stdLearning.learningUser.NAME_USERS;
|
||||||
|
const nisn = stdLearning.learningUser.students.NISN;
|
||||||
|
const topicName = stdLearning.level.levelTopic.NAME_TOPIC;
|
||||||
|
const sectionName = stdLearning.level.levelTopic.topicSection.NAME_SECTION;
|
||||||
|
|
||||||
const levels = await models.StdLearning.findAll({
|
// Fetch levels with pagination, search, and sort
|
||||||
|
const levels = await models.StdLearning.findAndCountAll({
|
||||||
where: {
|
where: {
|
||||||
ID: userID,
|
ID: userID,
|
||||||
|
...(search && {
|
||||||
|
[models.Op.or]: [
|
||||||
|
{
|
||||||
|
"$level.NAME_LEVEL$": { [models.Op.like]: `%${search}%` },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
SCORE: { [models.Op.like]: `%${search}%` },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
FEEDBACK_STUDENT: { [models.Op.like]: `%${search}%` },
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// [models.Sequelize.fn('DATE_FORMAT', models.Sequelize.col('STUDENT_START'), '%Y-%m-%d')]: {
|
||||||
|
// [models.Op.like]: `%${search}%`,
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// [models.Sequelize.fn('DATE_FORMAT', models.Sequelize.col('STUDENT_FINISH'), '%Y-%m-%d')]: {
|
||||||
|
// [models.Op.like]: `%${search}%`,
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
],
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
include: [
|
include: [
|
||||||
{
|
{
|
||||||
|
|
@ -252,18 +362,10 @@ export const monitoringStudentProgressById = async (req, res) => {
|
||||||
"STUDENT_START",
|
"STUDENT_START",
|
||||||
"STUDENT_FINISH",
|
"STUDENT_FINISH",
|
||||||
],
|
],
|
||||||
|
distinct: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (levels.length === 0) {
|
const levelArray = levels.rows
|
||||||
return response(
|
|
||||||
404,
|
|
||||||
null,
|
|
||||||
"No levels found for the given user and topic!",
|
|
||||||
res
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const levelArray = levels
|
|
||||||
.map((learning) => ({
|
.map((learning) => ({
|
||||||
NAME_LEVEL: learning.level.NAME_LEVEL,
|
NAME_LEVEL: learning.level.NAME_LEVEL,
|
||||||
SCORE: learning.SCORE,
|
SCORE: learning.SCORE,
|
||||||
|
|
@ -272,18 +374,35 @@ export const monitoringStudentProgressById = async (req, res) => {
|
||||||
STUDENT_FINISH: learning.STUDENT_FINISH,
|
STUDENT_FINISH: learning.STUDENT_FINISH,
|
||||||
}))
|
}))
|
||||||
.sort((a, b) => {
|
.sort((a, b) => {
|
||||||
if (a.NAME_LEVEL === "Pretest") return -1;
|
if (sort === "level") {
|
||||||
if (b.NAME_LEVEL === "Pretest") return 1;
|
return a.NAME_LEVEL.localeCompare(b.NAME_LEVEL);
|
||||||
|
} else if (sort === "score") {
|
||||||
const levelNumberA = parseInt(a.NAME_LEVEL.replace("Level ", ""), 10);
|
return b.SCORE - a.SCORE;
|
||||||
const levelNumberB = parseInt(b.NAME_LEVEL.replace("Level ", ""), 10);
|
} else if (sort === "feedback") {
|
||||||
return levelNumberA - levelNumberB;
|
return a.FEEDBACK_STUDENT.localeCompare(b.FEEDBACK_STUDENT);
|
||||||
|
} else if (sort === "start") {
|
||||||
|
return new Date(a.STUDENT_START) - new Date(b.STUDENT_START);
|
||||||
|
} else if (sort === "finish") {
|
||||||
|
return new Date(a.STUDENT_FINISH) - new Date(b.STUDENT_FINISH);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Pagination logic
|
||||||
|
const paginatedResult = levelArray.slice((page - 1) * limit, page * limit);
|
||||||
|
const totalPages = Math.ceil(levels.count / limit);
|
||||||
|
const currentPage = parseInt(page);
|
||||||
|
|
||||||
const result = {
|
const result = {
|
||||||
ID_MONITORING: monitoring.ID_MONITORING,
|
ID_MONITORING: monitoring.ID_MONITORING,
|
||||||
levels: levelArray,
|
NAME_SECTION: sectionName,
|
||||||
FEEDBACK_GURU: monitoring.FEEDBACK_GURU,
|
NAME_TOPIC: topicName,
|
||||||
|
NAME_USERS: studentName,
|
||||||
|
NISN: nisn,
|
||||||
|
levels: paginatedResult,
|
||||||
|
currentPage,
|
||||||
|
totalPages,
|
||||||
|
totalItems: levels.count,
|
||||||
};
|
};
|
||||||
|
|
||||||
response(200, result, "Success retrieving student progress!", res);
|
response(200, result, "Success retrieving student progress!", res);
|
||||||
|
|
|
||||||
51
database/migrations/20241012202559-create-users.cjs
Normal file
51
database/migrations/20241012202559-create-users.cjs
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
const { Sequelize } = require("sequelize");
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
async up(queryInterface) {
|
||||||
|
await queryInterface.createTable("users", {
|
||||||
|
ID: {
|
||||||
|
type: Sequelize.UUID,
|
||||||
|
primaryKey: true,
|
||||||
|
defaultValue: Sequelize.UUIDV4,
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
|
NAME_USERS: {
|
||||||
|
type: Sequelize.STRING(100),
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
|
EMAIL: {
|
||||||
|
type: Sequelize.STRING(100),
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
|
PASSWORD: {
|
||||||
|
type: Sequelize.STRING(100),
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
|
ROLE: {
|
||||||
|
type: Sequelize.STRING(100),
|
||||||
|
allowNull: true,
|
||||||
|
},
|
||||||
|
PICTURE: {
|
||||||
|
type: Sequelize.STRING(1024),
|
||||||
|
allowNull: true,
|
||||||
|
},
|
||||||
|
REFRESH_TOKEN: {
|
||||||
|
type: Sequelize.STRING(256),
|
||||||
|
allowNull: true,
|
||||||
|
},
|
||||||
|
TIME_USERS: {
|
||||||
|
type: Sequelize.DATE,
|
||||||
|
defaultValue: Sequelize.NOW,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await queryInterface.addConstraint("users", {
|
||||||
|
fields: ["EMAIL"],
|
||||||
|
type: "unique",
|
||||||
|
name: "user_unique_email",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
async down(queryInterface) {
|
||||||
|
await queryInterface.dropTable("users");
|
||||||
|
},
|
||||||
|
};
|
||||||
21
database/seeders/20241012193900-create-admin-user.cjs
Normal file
21
database/seeders/20241012193900-create-admin-user.cjs
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
const bcrypt = require('bcrypt');
|
||||||
|
const { v4: uuidv4 } = require('uuid');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
up: async (queryInterface) => {
|
||||||
|
const hashedPassword = await bcrypt.hash('adminsealspolinema24', 10);
|
||||||
|
await queryInterface.bulkInsert('users', [
|
||||||
|
{
|
||||||
|
ID: uuidv4(),
|
||||||
|
NAME_USERS: 'Administrator',
|
||||||
|
EMAIL: 'adminseals@gmail.com',
|
||||||
|
PASSWORD: hashedPassword,
|
||||||
|
ROLE: 'admin',
|
||||||
|
TIME_USERS: new Date(),
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
down: async (queryInterface) => {
|
||||||
|
await queryInterface.bulkDelete('users', { EMAIL: 'adminseals@gmail.com' });
|
||||||
|
},
|
||||||
|
};
|
||||||
14
generateMigration.js
Normal file
14
generateMigration.js
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { execSync } from "child_process";
|
||||||
|
|
||||||
|
const name = process.argv[2];
|
||||||
|
if (!name) {
|
||||||
|
console.error("Error: Migration name is required.");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
execSync(
|
||||||
|
`npx sequelize-cli migration:generate --migrations-path database/migrations --config config/config.js --name ${name}`,
|
||||||
|
{ stdio: "inherit" }
|
||||||
|
);
|
||||||
|
|
||||||
|
execSync("node renameFiles.js", { stdio: "inherit" });
|
||||||
14
generateSeed.js
Normal file
14
generateSeed.js
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { execSync } from "child_process";
|
||||||
|
|
||||||
|
const name = process.argv[2];
|
||||||
|
if (!name) {
|
||||||
|
console.error("Error: Migration name is required.");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
execSync(
|
||||||
|
`npx sequelize-cli seed:generate --seeders-path database/seeders --config config/config.js --name ${name}`,
|
||||||
|
{ stdio: "inherit" }
|
||||||
|
);
|
||||||
|
|
||||||
|
execSync("node renameFiles.js", { stdio: "inherit" });
|
||||||
|
|
@ -21,7 +21,7 @@ const ClassModel = (DataTypes) => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
TOTAL_STUDENT: {
|
TOTAL_STUDENT: {
|
||||||
type: DataTypes.INTEGER,
|
type: DataTypes.INTEGER(11),
|
||||||
allowNull: true,
|
allowNull: true,
|
||||||
},
|
},
|
||||||
TIME_CLASS: {
|
TIME_CLASS: {
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ const StudentModel = (DataTypes) => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
NISN: {
|
NISN: {
|
||||||
type: DataTypes.INTEGER,
|
type: DataTypes.BIGINT(11),
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
unique: true,
|
unique: true,
|
||||||
validate: {
|
validate: {
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ const TeacherModel = (DataTypes) => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
NIP: {
|
NIP: {
|
||||||
type: DataTypes.INTEGER,
|
type: DataTypes.BIGINT(11),
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
unique: true,
|
unique: true,
|
||||||
validate: {
|
validate: {
|
||||||
|
|
|
||||||
|
|
@ -14,14 +14,14 @@ const UserModel = (DataTypes) => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
NAME_USERS: {
|
NAME_USERS: {
|
||||||
type: DataTypes.STRING,
|
type: DataTypes.STRING(100),
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
validate: {
|
validate: {
|
||||||
notEmpty: true,
|
notEmpty: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
EMAIL: {
|
EMAIL: {
|
||||||
type: DataTypes.STRING,
|
type: DataTypes.STRING(100),
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
unique: true,
|
unique: true,
|
||||||
validate: {
|
validate: {
|
||||||
|
|
@ -30,19 +30,20 @@ const UserModel = (DataTypes) => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PASSWORD: {
|
PASSWORD: {
|
||||||
type: DataTypes.STRING,
|
type: DataTypes.STRING(100),
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
},
|
},
|
||||||
ROLE: {
|
ROLE: {
|
||||||
type: DataTypes.STRING,
|
type: DataTypes.STRING(100),
|
||||||
allowNull: true,
|
allowNull: true,
|
||||||
},
|
},
|
||||||
PICTURE: {
|
PICTURE: {
|
||||||
type: DataTypes.STRING,
|
type: DataTypes.STRING(1024),
|
||||||
allowNull: true,
|
allowNull: true,
|
||||||
},
|
},
|
||||||
REFRESH_TOKEN: {
|
REFRESH_TOKEN: {
|
||||||
type: DataTypes.STRING,
|
type: DataTypes.STRING(256),
|
||||||
|
allowNull: true,
|
||||||
},
|
},
|
||||||
TIME_USERS: {
|
TIME_USERS: {
|
||||||
type: DataTypes.DATE,
|
type: DataTypes.DATE,
|
||||||
|
|
|
||||||
863
package-lock.json
generated
863
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
12
package.json
12
package.json
|
|
@ -6,7 +6,13 @@
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1",
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
"api-start": "nodemon index.js"
|
"api-start": "nodemon index.js",
|
||||||
|
"db:migrate": "npx sequelize-cli db:migrate --migrations-path database/migrations --config config/config.js",
|
||||||
|
"db:seed": "npx sequelize-cli db:seed:all --seeders-path database/seeders --config config/config.js",
|
||||||
|
"db:migrate:undo": "npx sequelize-cli db:migrate:undo --migrations-path database/migrations --config config/config.js",
|
||||||
|
"db:seed:undo": "npx sequelize-cli db:seed:undo --seeders-path database/seeders --config config/config.js",
|
||||||
|
"migrate:generate": "node generateMigration.js",
|
||||||
|
"seed:generate": "node generateSeed.js"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "",
|
"author": "",
|
||||||
|
|
@ -23,6 +29,8 @@
|
||||||
"mysql2": "^3.11.0",
|
"mysql2": "^3.11.0",
|
||||||
"nodemailer": "^6.9.14",
|
"nodemailer": "^6.9.14",
|
||||||
"nodemon": "^3.1.4",
|
"nodemon": "^3.1.4",
|
||||||
"sequelize": "^6.37.3"
|
"sequelize": "^6.37.3",
|
||||||
|
"sequelize-cli": "^6.6.2",
|
||||||
|
"uuid": "^10.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
30
renameFiles.js
Normal file
30
renameFiles.js
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
import fs from "fs";
|
||||||
|
import path from "path";
|
||||||
|
|
||||||
|
function renameFilesInDirectory(directory) {
|
||||||
|
fs.readdir(directory, (err, files) => {
|
||||||
|
if (err) {
|
||||||
|
console.error(`Error reading directory ${directory}:`, err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
files.forEach((file) => {
|
||||||
|
const ext = path.extname(file);
|
||||||
|
if (ext === ".js") {
|
||||||
|
const oldPath = path.join(directory, file);
|
||||||
|
const newPath = path.join(directory, file.replace(".js", ".cjs"));
|
||||||
|
|
||||||
|
fs.rename(oldPath, newPath, (renameErr) => {
|
||||||
|
if (renameErr) {
|
||||||
|
console.error(`Error renaming file ${file}:`, renameErr);
|
||||||
|
} else {
|
||||||
|
console.log(`Renamed: ${file} -> ${path.basename(newPath)}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
renameFilesInDirectory("./database/migrations");
|
||||||
|
renameFilesInDirectory("./database/seeders");
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
import express from "express";
|
import express from "express";
|
||||||
import { getClasses, getClassById, getStudentsByClassId, getStudentsWithNoClass, createClass, updateClassById, deleteClassById, updateStudentClassByName } from "../../controllers/monitoringControllers/class.js";
|
import { getClasses, getClassById, getStudentsByClassId, getClassForAdmin, getStudentsWithNoClass, createClass, updateClassById, deleteClassById, updateStudentClassByName } from "../../controllers/monitoringControllers/class.js";
|
||||||
import { verifyLoginUser, adminOrTeacherOnly } from "../../middlewares/User/authUser.js";
|
import { verifyLoginUser, adminOrTeacherOnly } from "../../middlewares/User/authUser.js";
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
router.get("/classes", verifyLoginUser, getClasses);
|
router.get("/classes", verifyLoginUser, getClasses);
|
||||||
|
|
||||||
|
router.get("/class/admin", verifyLoginUser, getClassForAdmin);
|
||||||
|
|
||||||
router.get("/class/:id", verifyLoginUser, getClassById);
|
router.get("/class/:id", verifyLoginUser, getClassById);
|
||||||
|
|
||||||
router.get("/class/student/unassigned", verifyLoginUser, adminOrTeacherOnly, getStudentsWithNoClass);
|
router.get("/class/student/unassigned", verifyLoginUser, adminOrTeacherOnly, getStudentsWithNoClass);
|
||||||
|
|
@ -14,7 +16,7 @@ router.get("/class/student/:classId", verifyLoginUser, adminOrTeacherOnly, getSt
|
||||||
|
|
||||||
router.post("/class", verifyLoginUser, createClass);
|
router.post("/class", verifyLoginUser, createClass);
|
||||||
|
|
||||||
router.put("/class/update/:classId", verifyLoginUser, updateClassById);
|
router.put("/class/update/:id", verifyLoginUser, updateClassById);
|
||||||
|
|
||||||
router.delete("/class/delete/:id", verifyLoginUser, deleteClassById);
|
router.delete("/class/delete/:id", verifyLoginUser, deleteClassById);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user