diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..58eb6e1 --- /dev/null +++ b/.env.example @@ -0,0 +1,10 @@ +PORT=4000 +TOKEN_SECRET=mysecret +ENVIRONMENT=local +DB_URL=mongodb://mongo1:30001,mongo2:30002,mongo3:30003/?replicaSet=my-replica-set +EMAIL_HOST= +EMAIL_PORT= +EMAIL_USER= +EMAIL_PASS= + +# get email stuff from mailtrap \ No newline at end of file diff --git a/app.js b/app.js index 892f8a4..77d3578 100644 --- a/app.js +++ b/app.js @@ -31,6 +31,7 @@ import performarouter from "#routes/performance"; import notificationRouter from "#routes/notification"; import topicRouter from "#routes/topic"; import courseRouter from "#routes/course"; +import activityBlueprintRouter from "#routes/activityBlueprint"; const app = express(); const currDirName = dirname(fileURLToPath(import.meta.url)); @@ -76,5 +77,6 @@ app.use("/performance", performarouter); app.use("/notification", notificationRouter); app.use("/topic", topicRouter); app.use("/course", courseRouter); +app.use("/activityBlueprint", activityBlueprintRouter); export default app; diff --git a/controller/activity.js b/controller/activity.js index a276267..106514c 100644 --- a/controller/activity.js +++ b/controller/activity.js @@ -10,7 +10,6 @@ async function addActivity(req, res) { const { activityBlueprint, startTime, - duration, course, faculty, type, @@ -22,7 +21,6 @@ async function addActivity(req, res) { const newActivity = await createActivity( activityBlueprint, startTime, - duration, course, faculty, type, diff --git a/controller/activityBlueprint.js b/controller/activityBlueprint.js new file mode 100644 index 0000000..031d389 --- /dev/null +++ b/controller/activityBlueprint.js @@ -0,0 +1,88 @@ +import { + createActivityBP, + updateActivityBlueprintById, + deleteActivityBlueprintById, + activityBlueprintList, +} from "#services/activityBlueprint"; +import { logger } from "#util"; + +async function addActivityBP(req, res) { + const { + number, + academicYear, + day, + startTime, + duration, + infra, + course, + faculty, + type, + group, + } = req.body; + try { + const newActivityBP = await createActivityBP( + number, + academicYear, + day, + startTime, + duration, + infra, + course, + faculty, + type, + group, + ); + return res.json({ + res: `added activity ${newActivityBP.id}`, + id: newActivityBP.id, + }); + } catch (error) { + logger.error("Error while inserting", error); + res.status(500); + return res.json({ err: "Error while inserting in DB" }); + } +} + +async function updateActivityBP(req, res) { + const { id } = req.params; + const { ...data } = req.body; + try { + await updateActivityBlueprintById(id, data); + return res.json({ res: `updated activity with id ${id}` }); + } catch (error) { + logger.error("Error while updating", error); + res.status(500); + return res.json({ err: "Error while updating in DB" }); + } +} + +async function getActivityBP(req, res) { + try { + const filter = req.body; + const { limit, page } = req.query; + const activitylist = await activityBlueprintList(filter, limit, page); + return res.json({ res: activitylist }); + } catch (error) { + logger.error("Error while fetching", error); + res.status(500); + return res.json({ err: "Error while fetching the data" }); + } +} + +async function deleteActivityBP(res, req) { + const { id } = req.params; + try { + await deleteActivityBlueprintById(id); + return res.json({ res: `Deleted activity with ID ${id}` }); + } catch (error) { + logger.error("Error while deleting", error); + return res.status(500).json({ error: "Error while deleting from DB" }); + } +} + +export default { + addActivityBP, + deleteActivityBP, + getActivityBP, + updateActivityBP, +}; diff --git a/jest.config.js b/jest.config.js index a52c300..965471b 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,8 +1,10 @@ const config = { - transform: { - }, + transform: {}, testTimeout: 15000, - setupFiles: [ + globalSetup: "./test/config/globalSetup.js", + globalTeardown: "./test/config/globalTeardown.js", + setupFilesAfterEnv: [ + // "/test/setupFile.ts", "./test/config/setup.js", "./test/config/teardown.js", ], diff --git a/misc/initDB.js b/misc/initDB.js index c669e18..0d6c9dd 100644 --- a/misc/initDB.js +++ b/misc/initDB.js @@ -9,6 +9,18 @@ import Tutorial from "#models/tutorial"; import Practical from "#models/practical"; import Semester from "#models/semester"; import Course from "#models/course"; +import Faculty from "#models/faculty"; +import Student from "#models/student"; +import Attendance from "#models/attendance"; +import Notification from "#models/notification"; +import User from "#models/user"; +import Group from "#models/group"; +import ActivityBlueprint from "#models/activityBlueprint"; +import Activity from "#models/activity"; +import Exam from "#models/exam"; +import Paper from "#models/paper"; +import Coursework from "#models/coursework"; +import Timetable from "#models/timetable"; import generateOrganizations from "#mockDB/orgMock"; import ACCREDS from "#mockDB/accredMock"; import TOPICS from "#mockDB/topicMock"; @@ -19,6 +31,18 @@ import generatePracticals from "#mockDB/pracMock"; import generateTutorials from "#mockDB/tutMock"; import generateSemesters from "#mockDB/semMock"; import generateCourses from "#mockDB/courseMock"; +import generateFaculty from "#mockDB/facultyMock"; +import generateStudents from "#mockDB/studentMock"; +import generateAttendance from "#mockDB/attendanceMock"; +import generateNotifications from "#mockDB/notificationMock"; +import generateUsers from "#mockDB/userMock"; // eslint-disable-line no-unused-vars +import generateGroups from "#mockDB/groupMock"; +import generateActivityBP from "#mockDB/activityBPMock"; +import generateActivity from "#mockDB/activityMock"; +import generateExams from "#mockDB/examMock"; +import generatePaper from "#mockDB/paperMock"; +import generateCoursework from "#mockDB/courseworkMock"; +import generateTimetables from "#mockDB/timetableMock"; /* eslint-disable no-underscore-dangle */ const createdAccreds = await Accreditation.createMultiple(ACCREDS); @@ -60,7 +84,7 @@ const createdDepts = await Department.createMultiple(DEPTS); const createdTopics = await Topics.createMultiple(TOPICS); -const MODULES = await generateModules( +const MODULES = generateModules( createdTopics.map((createdTopic) => createdTopic._id), ); @@ -86,6 +110,119 @@ const COURSES = generateCourses( createdDepts.map((createdDept) => createdDept._id), ); -const createdCourses = await Course.createMultiple(COURSES); // eslint-disable-line no-unused-vars +const createdCourses = await Course.createMultiple(COURSES); + +const FACULTY = generateFaculty( + createdDepts.map((createdDept) => createdDept._id), + createdCourses.map((createdCourse) => createdCourse._id), +); + +const createdFaculty = await Faculty.createMultiple(FACULTY); + +const STUDENTS = generateStudents( + createdDepts.map((createdDept) => createdDept._id), + createdCourses.map((createdCourse) => createdCourse._id), +); + +const createdStudents = await Student.createMultiple(STUDENTS); + +const studentCourseList = createdStudents.map((student) => { + const studentId = student._id.toString(); + const coursesOpted = student.coursesOpted.map((courseId) => + courseId.toString(), + ); + return { studentId, coursesOpted }; +}); + +const ATTENDANCE = generateAttendance(studentCourseList); + +const createdAttendance = await Attendance.createMultiple(ATTENDANCE); // eslint-disable-line no-unused-vars + +// const USERS = generateUsers( // TODO this takes forever bruhh +// createdStudents.map((createdStudent) => createdStudent.ERPID), +// createdFaculty.map((createdFaculty) => createdFaculty.ERPID), +// ) + +// const createdUsers = await User.createMultiple(USERS) + +const createdUsers = await User.read(); // use this after you initialized Users at least once, or wait for years every time + +const NOTIFS = generateNotifications( + createdUsers.data // remove data from each of these if you are initializing users for the first time + .filter((user) => user.userType === "STUDENT") + .map((student) => student._id), + createdUsers.data + .filter((user) => user.userType === "FACULTY") + .map((faculty) => faculty._id), + createdUsers.data + .filter((user) => user.userType === "ADMIN") + .map((admin) => admin._id), +); + +const createdNotifications = await Notification.createMultiple(NOTIFS); // eslint-disable-line no-unused-vars + +const GROUPS = generateGroups( + createdUsers.data + .filter((user) => user.userType === "STUDENT") + .map((student) => student._id), +); + +const createdGroups = await Group.createMultiple(GROUPS); + +const ACTIVITYBP = generateActivityBP( + createdInfras.map((createdInfra) => createdInfra._id), + createdCourses.map((createdCourse) => createdCourse._id), + createdFaculty.map((faculty) => faculty._id), + createdGroups.map((createdGroup) => createdGroup._id), +); + +const createdActivityBP = await ActivityBlueprint.createMultiple(ACTIVITYBP); + +const ACTIVITY = generateActivity( + createdActivityBP.map((activityBP) => activityBP._id), + createdCourses.map((createdCourse) => createdCourse._id), + createdFaculty.map((faculty) => faculty._id), + createdGroups.map((createdGroup) => createdGroup._id), + createdTuts.map((createdTut) => createdTut._id), + createdPracs.map((createdPrac) => createdPrac._id), + createdTopics.map((createdTopic) => createdTopic._id), + createdStudents.map((createdStudent) => createdStudent._id), +); + +const createdActivity = await Activity.createMultiple(ACTIVITY); + +const EXAMS = generateExams( + createdInfras.map((createdInfra) => createdInfra._id), + createdCourses.map((createdCourse) => createdCourse._id), + createdFaculty.map((faculty) => faculty._id), +); + +const createdExams = await Exam.createMultiple(EXAMS); + +const PAPERS = generatePaper( + createdStudents.map((createdStudent) => createdStudent._id), + createdExams.map((createdExam) => createdExam._id), + createdFaculty.map((faculty) => faculty._id), +); + +const createdPapers = await Paper.createMultiple(PAPERS); // eslint-disable-line no-unused-vars + +const COURSEWORK = generateCoursework( + createdStudents.map((createdStudent) => createdStudent._id), + createdCourses.map((createdCourse) => createdCourse._id), + createdTuts.map((createdTut) => createdTut._id), + createdPracs.map((createdPrac) => createdPrac._id), + createdActivity.map((activity) => activity._id), +); + +const createdCoursework = await Coursework.createMultiple(COURSEWORK); // eslint-disable-line no-unused-vars + +const TIMETABLE = generateTimetables( + createdActivityBP.map((activityBP) => activityBP._id), + createdGroups.map((createdGroup) => createdGroup._id), + createdFaculty.map((faculty) => faculty._id), +); + +const createdTimetables = await Timetable.createMultiple(TIMETABLE); // eslint-disable-line no-unused-vars process.exit(0); diff --git a/misc/mockDB/activityBPMock.js b/misc/mockDB/activityBPMock.js new file mode 100644 index 0000000..59011fe --- /dev/null +++ b/misc/mockDB/activityBPMock.js @@ -0,0 +1,48 @@ +import { faker } from "@faker-js/faker"; // eslint-disable-line import/no-extraneous-dependencies +// TODO, not accurate to IRL!! +const generateRandomActivityBP = ( + randomInfra, + randomCourse, + randomFaculty, + randomGroup, +) => ({ + number: faker.number.int(), + academicYear: "2023", + day: faker.helpers.arrayElement([ + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + ]), + startTime: faker.helpers.arrayElement([ + "09:00", + "11:00", + "13:00", + "15:00", + "17:00", + ]), + duration: faker.number.int({ min: 1, max: 2 }), + infra: randomInfra, + course: randomCourse, + faculty: randomFaculty, + type: faker.helpers.arrayElement(["lecture", "practical", "tutorial"]), + group: randomGroup, +}); + +const generateActivityBP = (infraList, courseList, facultyList, groupList) => { + const activityBP = []; + for (let i = 0; i < 1000; i += 1) { + activityBP.push( + generateRandomActivityBP( + faker.helpers.arrayElement(infraList), + faker.helpers.arrayElement(courseList), + faker.helpers.arrayElement(facultyList), + faker.helpers.arrayElement(groupList), + ), + ); + } + return activityBP; +}; + +export default generateActivityBP; diff --git a/misc/mockDB/activityMock.js b/misc/mockDB/activityMock.js new file mode 100644 index 0000000..e949b1f --- /dev/null +++ b/misc/mockDB/activityMock.js @@ -0,0 +1,82 @@ +import { faker } from "@faker-js/faker"; // eslint-disable-line import/no-extraneous-dependencies + +const generateRandomActivity = ( + randomBlueprint, + randomCourse, + randomFaculty, + randomGroup, + tutorialList, + practicalList, + topicList, + studentList, +) => { + const activityType = faker.helpers.arrayElement([ + "TUTORIAL", + "PRACTICAL", + "LECTURE", + ]); // Randomly select activity type + let taskIds; + + if (activityType === "TUTORIAL") { + taskIds = [faker.helpers.arrayElement(tutorialList, { min: 2, max: 3 })]; + } else if (activityType === "PRACTICAL") { + taskIds = [faker.helpers.arrayElement(practicalList, { min: 1, max: 2 })]; + } else { + taskIds = faker.helpers.arrayElements(topicList, { min: 2, max: 5 }); + } + + const studentsCount = faker.number.int({ min: 1, max: 30 }); // Random number of students (1 to 30) + const studentsIds = faker.helpers.arrayElements(studentList, studentsCount); // Randomly select students + + return { + activityBlueprint: randomBlueprint, // Assuming `randomInfra` contains activityBlueprint reference + course: randomCourse, // Assuming `randomCourse` is an object with an _id property + faculty: randomFaculty, // Assuming `randomFaculty` is an object with an _id property + task: taskIds, // Assuming `task` is an object with an _id property (from tutorialList, practicalList, or topicList) + type: activityType, // Assuming `task` is an object with an _id property (from tutorialList, practicalList, or topicList) + group: randomGroup, // Assuming `randomGroup` is an object with an _id property + students: studentsIds, // Assuming each student object in `studentList` has an _id property + }; +}; + +const checkIteration = (initial, testLength) => { + if (testLength) { + return testLength; + } + return initial; +}; +const generateActivity = ( + activityBlueprintList, + courseList, + facultyList, + groupList, + tutorialList, + practicalList, + topicList, + studentList, + testLength, +) => { + const iterationLength = checkIteration(100000, testLength); + const activities = []; + for (let i = 0; i < iterationLength; i += 1) { + const randomBlueprint = faker.helpers.arrayElement(activityBlueprintList); + const randomCourse = faker.helpers.arrayElement(courseList); + const randomFaculty = faker.helpers.arrayElement(facultyList); + const randomGroup = faker.helpers.arrayElement(groupList); + + const activityData = generateRandomActivity( + randomBlueprint, + randomCourse, + randomFaculty, + randomGroup, + tutorialList, + practicalList, + topicList, + studentList, + ); + activities.push(activityData); + } + return activities; +}; + +export default generateActivity; diff --git a/misc/mockDB/attendanceMock.js b/misc/mockDB/attendanceMock.js new file mode 100644 index 0000000..78a1d0d --- /dev/null +++ b/misc/mockDB/attendanceMock.js @@ -0,0 +1,42 @@ +import { faker } from "@faker-js/faker"; // eslint-disable-line import/no-extraneous-dependencies + +const createRandomAttendance = (studentId, courseId) => { + const monthlyAttendedInt = faker.number.int({ min: 0, max: 25 }); + return { + student: studentId, + course: courseId, + monthlyAttended: monthlyAttendedInt, + monthlyOccured: faker.number.int({ min: 25, max: 27 }), + cumulativeAttended: + monthlyAttendedInt + faker.number.int({ min: 0, max: 150 }), + cumulativeOccured: + monthlyAttendedInt + faker.number.int({ min: 150, max: 175 }), + }; +}; + +const checkIteration = (initial, testLength) => { + if (testLength) { + return testLength; + } + return initial; +}; + +const generateAttendance = (studentCourseList, testLength) => { + const attendance = []; + const iterationLength = checkIteration(studentCourseList.length, testLength); + for (let i = 0, j = 0; i < iterationLength; j += 1) { + attendance.push( + createRandomAttendance( + studentCourseList[i].studentId, + studentCourseList[i].coursesOpted[j], + ), + ); + if (j >= 6) { + i += 1; + j = 0; + } + } + return attendance; +}; + +export default generateAttendance; diff --git a/misc/mockDB/courseworkMock.js b/misc/mockDB/courseworkMock.js new file mode 100644 index 0000000..a3e57f7 --- /dev/null +++ b/misc/mockDB/courseworkMock.js @@ -0,0 +1,40 @@ +import { faker } from "@faker-js/faker"; // eslint-disable-line import/no-extraneous-dependencies + +const generateRandomCoursework = ( + randomStudent, + randomCourse, + randomTutorial, + randomPractical, + randomActivity, +) => ({ + student: randomStudent, + type: faker.helpers.arrayElement(["onCampus", "offCampus"]), + course: randomCourse, + task: faker.helpers.arrayElement([randomTutorial, randomPractical]), + activity: randomActivity, + marks: faker.number.int({ min: 0, max: 100 }), +}); + +const generateCoursework = ( + studentList, + courseList, + tutorialList, + practicalList, + activityList, +) => { + const courseworks = []; + for (let i = 0; i < 10000; i += 1) { + courseworks.push( + generateRandomCoursework( + faker.helpers.arrayElement(studentList), + faker.helpers.arrayElement(courseList), + faker.helpers.arrayElement(tutorialList), + faker.helpers.arrayElement(practicalList), + faker.helpers.arrayElement(activityList), + ), + ); + } + return courseworks; +}; + +export default generateCoursework; diff --git a/misc/mockDB/examMock.js b/misc/mockDB/examMock.js new file mode 100644 index 0000000..6c7ee82 --- /dev/null +++ b/misc/mockDB/examMock.js @@ -0,0 +1,29 @@ +import { faker } from "@faker-js/faker"; // eslint-disable-line import/no-extraneous-dependencies + +const generateRandomExams = (randomInfra, randomCourse, randomFaculty) => ({ + date: faker.date.future(), + startTime: faker.date.between({ + from: "2020-01-01T00:00:00.000Z", + to: "2030-01-01T00:00:00.000Z", + }), + duration: faker.number.int({ min: 1, max: 2 }), + supervisor: randomFaculty, + infrastructure: randomInfra, + course: randomCourse, +}); + +const generateExams = (infraList, courseList, facultyList) => { + const exams = []; + for (let i = 0; i < 3000; i += 1) { + exams.push( + generateRandomExams( + faker.helpers.arrayElement(infraList), + faker.helpers.arrayElement(courseList), + faker.helpers.arrayElement(facultyList), + ), + ); + } + return exams; +}; + +export default generateExams; diff --git a/misc/mockDB/facultyMock.js b/misc/mockDB/facultyMock.js new file mode 100644 index 0000000..258792c --- /dev/null +++ b/misc/mockDB/facultyMock.js @@ -0,0 +1,97 @@ +import { faker } from "@faker-js/faker"; // eslint-disable-line import/no-extraneous-dependencies + +function getRandomLetter() { + const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + const randomIndex = Math.floor(Math.random() * alphabet.length); + return alphabet[randomIndex]; +} + +function randomDate(start, end) { + return new Date( + start.getTime() + Math.random() * (end.getTime() - start.getTime()), + ); +} + +// Function to generate random 4-digit number +function getRandomNumber() { + return Math.floor(1000 + Math.random() * 9000); +} + +const generatedIDs = []; + +const createRandomFaculty = (departmentId, createdCourses) => { + let id; + do { + const letter = getRandomLetter(); + const number = getRandomNumber(); + id = `F${letter}${number}`; + } while (generatedIDs.includes(id)); + generatedIDs.push(id); + const doj = faker.date.past({ years: 5 }); + return { + ERPID: id, + dateOfJoining: doj, + dateOfLeaving: faker.datatype.boolean({ probability: 0.4 }) + ? randomDate(doj, new Date()) + : null, + profileLink: faker.internet.url(), + qualifications: [ + faker.lorem.word(), + faker.lorem.word(), + faker.lorem.word(), + ], + totalExperience: faker.number.int({ min: 1, max: 20 }), + achievements: [ + faker.lorem.words(), + faker.lorem.words(), + faker.lorem.words(), + ], + areaOfSpecialization: [ + faker.lorem.word(), + faker.lorem.word(), + faker.lorem.word(), + ], + papersPublishedPG: faker.number.int({ min: 0, max: 50 }), + papersPublishedUG: faker.number.int({ min: 0, max: 50 }), + department: departmentId, + preferredSubjects: createdCourses, + designation: faker.helpers.arrayElements( + [ + "HOD", + "Assistant Professor", + "Associate Professor", + "Activity Head", + "Professor", + "Director", + "T&P Officer", + "R&D Activity Head", + ], + { min: 1, max: 3 }, + ), + natureOfAssociation: faker.helpers.arrayElement([ + "Regular", + "Contract", + "Adjunct", + ]), + additionalResponsibilities: faker.lorem.words(), + }; +}; + +const generateFaculty = (createdDepts, createdCourses) => { + const faculty = []; + let selectedCourses; + for (let i = 0, j = 1; i < createdDepts.length; j += 1) { + selectedCourses = faker.helpers.arrayElements(createdCourses, { + min: 4, + max: 8, + }); + faculty.push(createRandomFaculty(createdDepts[i], selectedCourses)); + if (j > 30) { + i += 1; + j = 0; + } + } + return faculty; +}; + +export default generateFaculty; diff --git a/misc/mockDB/groupMock.js b/misc/mockDB/groupMock.js new file mode 100644 index 0000000..d9659eb --- /dev/null +++ b/misc/mockDB/groupMock.js @@ -0,0 +1,29 @@ +import { faker } from "@faker-js/faker"; // eslint-disable-line import/no-extraneous-dependencies + +const generateGroups = (studentList) => { + const groups = []; + + // First iteration: 30 students per group + for (let i = 0; i < studentList.length; i += 30) { + const groupTitle = faker.lorem.words(2); // Generate a random group title + const group = { + title: groupTitle, + students: studentList.slice(i, i + 30), // Take 30 students for this iteration + }; + groups.push(group); + } + + // Second iteration: 5 students per group + for (let i = 0; i < studentList.length; i += 5) { + const groupTitle = faker.lorem.words(2); // Generate a random group title + const group = { + title: groupTitle, + students: studentList.slice(i, i + 5), // Take 5 students for this iteration + }; + groups.push(group); + } + + return groups; +}; + +export default generateGroups; diff --git a/misc/mockDB/notificationMock.js b/misc/mockDB/notificationMock.js new file mode 100644 index 0000000..4037c3f --- /dev/null +++ b/misc/mockDB/notificationMock.js @@ -0,0 +1,33 @@ +import { faker } from "@faker-js/faker"; // eslint-disable-line import/no-extraneous-dependencies + +const generateNotifications = (studentIds, facultyIds, adminIds) => { + const notifications = []; + const notificationTypes = ["Student", "Faculty"]; + + for (let i = 0; i < 300; i += 1) { + const notification = { + data: faker.lorem.lines({ min: 3, max: 7 }), + title: faker.lorem.sentence(), + from: faker.helpers.arrayElement([...facultyIds, ...adminIds]), + type: faker.helpers.arrayElement(notificationTypes), + filter: [], + }; + + if (notification.type === "Student") { + notification.filter = faker.helpers.arrayElements(studentIds, { + min: 5, + max: 100, + }); + } else { + notification.filter = faker.helpers.arrayElements( + [...facultyIds, ...adminIds], + { min: 5, max: 100 }, + ); + } + notifications.push(notification); + } + + return notifications; +}; + +export default generateNotifications; diff --git a/misc/mockDB/paperMock.js b/misc/mockDB/paperMock.js new file mode 100644 index 0000000..8e51973 --- /dev/null +++ b/misc/mockDB/paperMock.js @@ -0,0 +1,25 @@ +import { faker } from "@faker-js/faker"; // eslint-disable-line import/no-extraneous-dependencies + +const generateRandomPapers = (randomStudent, randomExam, randomFaculty) => ({ + answerSheetID: faker.string.alphanumeric(10), + exam: randomExam, + student: randomStudent, + checkedBy: randomFaculty, + mark: faker.number.int({ min: 0, max: 100 }), +}); + +const generatePapers = (studentList, examList, facultyList) => { + const papers = []; + for (let i = 0; i < 1000; i += 1) { + papers.push( + generateRandomPapers( + faker.helpers.arrayElement(studentList), + faker.helpers.arrayElement(examList), + faker.helpers.arrayElement(facultyList), + ), + ); + } + return papers; +}; + +export default generatePapers; diff --git a/misc/mockDB/studentMock.js b/misc/mockDB/studentMock.js new file mode 100644 index 0000000..50212c4 --- /dev/null +++ b/misc/mockDB/studentMock.js @@ -0,0 +1,84 @@ +import { faker } from "@faker-js/faker"; // eslint-disable-line import/no-extraneous-dependencies + +const departmentAbbrev = [ + "ME", + "CE", + "CS", + "IT", + "ETE", + "ECS", + "AIDS", + "IOT", + "AIML", + "CSS", + "MEMM", + "AIDS", + "SD", + "AGD", + "DA", +]; + +function generateRandomYear() { + const currentYear = new Date().getFullYear(); + const availableYears = Array.from( + { length: 4 }, + (_, index) => currentYear - index, + ); + const randomIndex = Math.floor(Math.random() * availableYears.length); + return availableYears[randomIndex].toString().slice(-2); +} + +function generateRandomThreeDigitNumber() { + return Math.floor(100 + Math.random() * 900); +} + +function generateStudentID(i) { + const departmentInitials = departmentAbbrev[i]; + const year = generateRandomYear(); + const randomThreeDigitNumber = generateRandomThreeDigitNumber(); + return `S${departmentInitials}${year}${randomThreeDigitNumber}`; +} + +const generatedIDs = []; + +const createRandomStudent = (department, createdCourses, k, j, i) => { + let id; + let divisionLetter; + do id = generateStudentID(i); + while (generatedIDs.includes(id)); + generatedIDs.push(id); + if (j >= 120) divisionLetter = "C"; + else if (j >= 60) divisionLetter = "B"; + else divisionLetter = "A"; + return { + ERPID: id, + name: faker.person.fullName(), + joiningYear: new Date().getFullYear() - k, + branch: department, + division: divisionLetter, + rollNo: (j % 60) + 1, + coursesOpted: createdCourses, + }; +}; + +const generateStudents = (createdDepts, createdCourses) => { + const students = []; + let selectedCourses; + for (let i = 0, j = 0, k = 0; k < 4; j += 1) { + selectedCourses = faker.helpers.arrayElements(createdCourses, 7); + students.push( + createRandomStudent(createdDepts[i], selectedCourses, k, j, i), + ); + if (j >= 179) { + i += 1; + j = -1; + } + if (i === createdDepts.length) { + i = 0; + k += 1; + } + } + return students; +}; + +export default generateStudents; diff --git a/misc/mockDB/timetableMock.js b/misc/mockDB/timetableMock.js new file mode 100644 index 0000000..dba3f51 --- /dev/null +++ b/misc/mockDB/timetableMock.js @@ -0,0 +1,33 @@ +import { faker } from "@faker-js/faker"; // eslint-disable-line import/no-extraneous-dependencies + +const generateRandomTimetables = ( + randomActivityBP, + randomFaculty, + randomGroup, +) => ({ + startDate: faker.date.past(), + endDate: faker.date.future(), + classIncharge: randomFaculty, + group: randomGroup, + activityBlueprints: randomActivityBP, + lunchbreakStartTime: faker.helpers.arrayElement(["12:00", "13:00", "14:00"]), + lunchbreakDuration: faker.number.int({ min: 30, max: 120 }), + teabreakStartTime: faker.helpers.arrayElement(["10:00", "15:00", "16:00"]), + teabreakDuration: faker.number.int({ min: 15, max: 60 }), +}); + +const generateTimetable = (activityBPList, groupList, facultyList) => { + const timetables = []; + for (let i = 0; i < 300; i += 1) { + timetables.push( + generateRandomTimetables( + faker.helpers.arrayElement(activityBPList), + faker.helpers.arrayElement(facultyList), + faker.helpers.arrayElement(groupList), + ), + ); + } + return timetables; +}; + +export default generateTimetable; diff --git a/misc/mockDB/userMock.js b/misc/mockDB/userMock.js new file mode 100644 index 0000000..40cb9fc --- /dev/null +++ b/misc/mockDB/userMock.js @@ -0,0 +1,83 @@ +import { faker } from "@faker-js/faker"; // eslint-disable-line import/no-extraneous-dependencies + +const departmentAbbrev = [ + "ME", + "CE", + "CS", + "IT", + "ETE", + "ECS", + "AIDS", + "IOT", + "AIML", + "CSS", + "MEMM", + "SD", + "AGD", + "DA", +]; + +function getRandomLetter() { + const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + const randomIndex = Math.floor(Math.random() * alphabet.length); + return alphabet[randomIndex]; +} + +function getRandomNumber() { + return Math.floor(100 + Math.random() * 900); +} + +const generatedEmails = new Set(); + +const createRandomUser = (ID, type) => { + let email; + do { + email = faker.internet.email(); + } while (generatedEmails.has(email)); + + generatedEmails.add(email); + + return { + name: faker.person.fullName(), + emailId: email, + password: faker.internet.password(), + uid: ID, + userType: type, + }; +}; + +const generatedIDs = []; + +const checkIteration = (initial, testLength) => { + if (testLength) { + return testLength; + } + return initial; +}; + +const generateUsers = (studentIds, facultyIds, testLength) => { + const users = []; + let iterationLength = checkIteration(studentIds.length, testLength); + for (let i = 0; i < iterationLength; i += 1) { + users.push(createRandomUser(studentIds[i], "STUDENT")); + } + iterationLength = checkIteration(facultyIds.length, testLength); + for (let i = 0; i < iterationLength; i += 1) { + users.push(createRandomUser(facultyIds[i], "FACULTY")); + } + for (let i = 0; i < iterationLength; i += 1) { + let id; + do { + id = `E${getRandomLetter()}${getRandomLetter()}${getRandomNumber()}`; + } while (generatedIDs.includes(id)); + generatedIDs.push(id); + users.push(createRandomUser(id, "EMPLOYEE")); + } + for (let i = 0; i < departmentAbbrev.length; i += 1) { + users.push(createRandomUser(`A${departmentAbbrev[i]}001`, "ADMIN")); + } + users.push(createRandomUser("A001", "ADMIN")); + return users; +}; + +export default generateUsers; diff --git a/misc/testMock.js b/misc/testMock.js new file mode 100644 index 0000000..1e31ca6 --- /dev/null +++ b/misc/testMock.js @@ -0,0 +1,232 @@ +import { faker } from "@faker-js/faker"; // eslint-disable-line import/no-extraneous-dependencies +import Infrastructure from "#models/infrastructure"; +import Accreditation from "#models/accreditation"; +import Organization from "#models/organization"; +import Department from "#models/department"; +import Topics from "#models/topic"; +import Module from "#models/module"; +import Tutorial from "#models/tutorial"; +import Practical from "#models/practical"; +import Semester from "#models/semester"; +import Course from "#models/course"; +import Faculty from "#models/faculty"; +import Student from "#models/student"; +import Attendance from "#models/attendance"; +import Notification from "#models/notification"; +import User from "#models/user"; +import Group from "#models/group"; +import ActivityBlueprint from "#models/activityBlueprint"; +import Activity from "#models/activity"; +import Exam from "#models/exam"; +import Paper from "#models/paper"; +import Coursework from "#models/coursework"; +import Timetable from "#models/timetable"; +import generateOrganizations from "#mockDB/orgMock"; +import ACCREDS from "#mockDB/accredMock"; +import TOPICS from "#mockDB/topicMock"; +import generateDepartments from "#mockDB/deptMock"; +import generateModules from "#mockDB/moduleMock"; +import generateInfrastructures from "#mockDB/infraMock"; +import generatePracticals from "#mockDB/pracMock"; +import generateTutorials from "#mockDB/tutMock"; +import generateSemesters from "#mockDB/semMock"; +import generateCourses from "#mockDB/courseMock"; +import generateFaculty from "#mockDB/facultyMock"; +import generateStudents from "#mockDB/studentMock"; +import generateAttendance from "#mockDB/attendanceMock"; +import generateNotifications from "#mockDB/notificationMock"; +import generateUsers from "#mockDB/userMock"; // eslint-disable-line no-unused-vars +import generateGroups from "#mockDB/groupMock"; +import generateActivityBP from "#mockDB/activityBPMock"; +import generateActivity from "#mockDB/activityMock"; +import generateExams from "#mockDB/examMock"; +import generatePaper from "#mockDB/paperMock"; +import generateCoursework from "#mockDB/courseworkMock"; +import generateTimetables from "#mockDB/timetableMock"; +/* eslint-disable no-underscore-dangle */ +const createdAccreds = await Accreditation.createMultiple(ACCREDS); + +const parentOrg = await Organization.create({ + startDate: faker.date.past({ years: 10 }), + name: "Thakur Education", + accreditations: [ + createdAccreds[faker.number.int({ min: 0, max: createdAccreds.length - 1 })] + ._id, + createdAccreds[faker.number.int({ min: 0, max: createdAccreds.length - 1 })] + ._id, + ], +}); + +const ORGS = generateOrganizations(parentOrg._id, createdAccreds); + +const createdOrgs = await Organization.createMultiple(ORGS); + +const tcetObject = createdOrgs.filter( + (obj) => obj.name === "Thakur College of Engineering & Technology", +)[0]; + +const INFRA = generateInfrastructures(tcetObject._id); + +const createdInfras = await Infrastructure.createMultiple(INFRA); + +const filteredInfrastructures = createdInfras.filter((infrastructure) => { + const allowedTypes = ["LAB", "CLASSROOM", "COMPUTER LAB"]; + return allowedTypes.includes(infrastructure.type); +}); + +const DEPTS = generateDepartments( + tcetObject._id, + createdAccreds.map((createdAccred) => createdAccred._id), + filteredInfrastructures.map((createdInfra) => createdInfra._id), +); + +const createdDepts = await Department.createMultiple(DEPTS); + +const createdTopics = await Topics.createMultiple(TOPICS); + +const MODULES = generateModules( + createdTopics.map((createdTopic) => createdTopic._id), +); + +const createdModules = await Module.createMultiple(MODULES); + +const PRACS = generatePracticals(); + +const createdPracs = await Practical.createMultiple(PRACS); + +const TUTS = generateTutorials(); + +const createdTuts = await Tutorial.createMultiple(TUTS); + +const SEMS = generateSemesters(); + +const createdSems = await Semester.createMultiple(SEMS); + +const COURSES = generateCourses( + createdSems.map((createdSem) => createdSem._id), + createdModules.map((createdModule) => createdModule._id), + createdPracs.map((createdPrac) => createdPrac._id), + createdTuts.map((createdTut) => createdTut._id), + createdDepts.map((createdDept) => createdDept._id), +); + +const createdCourses = await Course.createMultiple(COURSES); + +const FACULTY = generateFaculty( + createdDepts.map((createdDept) => createdDept._id), + createdCourses.map((createdCourse) => createdCourse._id), +); + +const createdFaculty = await Faculty.createMultiple(FACULTY); + +const STUDENTS = generateStudents( + createdDepts.map((createdDept) => createdDept._id), + createdCourses.map((createdCourse) => createdCourse._id), +); + +const createdStudents = await Student.createMultiple(STUDENTS); + +const studentCourseList = createdStudents.map((student) => { + const studentId = student._id.toString(); + const coursesOpted = student.coursesOpted.map((courseId) => + courseId.toString(), + ); + return { studentId, coursesOpted }; +}); + +const ATTENDANCE = generateAttendance(studentCourseList, 100); + +const createdAttendance = await Attendance.createMultiple(ATTENDANCE); // eslint-disable-line no-unused-vars + +const USERS = generateUsers( + // TODO this takes forever bruhh + createdStudents.map((createdStudent) => createdStudent.ERPID), + createdFaculty.map((createdFac) => createdFac.ERPID), + 10, +); + +const createdUsers = await User.createMultiple(USERS); + +// const createdUsers = await User.read(); // use this after you initialized Users at least once, or wait for years every time + +const NOTIFS = generateNotifications( + createdUsers // remove data from each of these if you are initializing users for the first time + .filter((user) => user.userType === "STUDENT") + .map((student) => student._id), + createdUsers + .filter((user) => user.userType === "FACULTY") + .map((faculty) => faculty._id), + createdUsers + .filter((user) => user.userType === "ADMIN") + .map((admin) => admin._id), + 10, +); + +const createdNotifications = await Notification.createMultiple(NOTIFS); // eslint-disable-line no-unused-vars + +const GROUPS = generateGroups( + createdUsers + .filter((user) => user.userType === "STUDENT") + .map((student) => student._id), +); + +const createdGroups = await Group.createMultiple(GROUPS); + +const ACTIVITYBP = generateActivityBP( + createdInfras.map((createdInfra) => createdInfra._id), + createdCourses.map((createdCourse) => createdCourse._id), + createdFaculty.map((faculty) => faculty._id), + createdGroups.map((createdGroup) => createdGroup._id), +); + +const createdActivityBP = await ActivityBlueprint.createMultiple(ACTIVITYBP); + +const ACTIVITY = generateActivity( + createdActivityBP.map((activityBP) => activityBP._id), + createdCourses.map((createdCourse) => createdCourse._id), + createdFaculty.map((faculty) => faculty._id), + createdGroups.map((createdGroup) => createdGroup._id), + createdTuts.map((createdTut) => createdTut._id), + createdPracs.map((createdPrac) => createdPrac._id), + createdTopics.map((createdTopic) => createdTopic._id), + createdStudents.map((createdStudent) => createdStudent._id), + 1000, +); + +const createdActivity = await Activity.createMultiple(ACTIVITY); + +const EXAMS = generateExams( + createdInfras.map((createdInfra) => createdInfra._id), + createdCourses.map((createdCourse) => createdCourse._id), + createdFaculty.map((faculty) => faculty._id), +); + +const createdExams = await Exam.createMultiple(EXAMS); + +const PAPERS = generatePaper( + createdStudents.map((createdStudent) => createdStudent._id), + createdExams.map((createdExam) => createdExam._id), + createdFaculty.map((faculty) => faculty._id), +); + +const createdPapers = await Paper.createMultiple(PAPERS); // eslint-disable-line no-unused-vars + +const COURSEWORK = generateCoursework( + createdStudents.map((createdStudent) => createdStudent._id), + createdCourses.map((createdCourse) => createdCourse._id), + createdTuts.map((createdTut) => createdTut._id), + createdPracs.map((createdPrac) => createdPrac._id), + createdActivity.map((activity) => activity._id), +); + +const createdCoursework = await Coursework.createMultiple(COURSEWORK); // eslint-disable-line no-unused-vars + +const TIMETABLE = generateTimetables( + createdActivityBP.map((activityBP) => activityBP._id), + createdGroups.map((createdGroup) => createdGroup._id), + createdFaculty.map((faculty) => faculty._id), +); + +const createdTimetables = await Timetable.createMultiple(TIMETABLE); // eslint-disable-line no-unused-vars + +process.exit(0); diff --git a/models/activity.js b/models/activity.js index e581be7..d0d08ea 100644 --- a/models/activity.js +++ b/models/activity.js @@ -6,8 +6,6 @@ const activitySchema = { ref: "ActivityBlueprint", required: true, }, - startTime: { type: Date, required: true }, - duration: { type: Number, required: true }, course: { type: connector.Schema.Types.ObjectId, ref: "Course", @@ -20,13 +18,12 @@ const activitySchema = { }, type: { type: String, + enum: ["TUTORIAL", "PRACTICAL", "TOPIC", "LECTURE"], required: true, - enum: ["LECTURE", "PRACTICAL", "TUTORIAL"], }, task: [ { type: connector.Schema.Types.ObjectId, - ref: ["Topic", "Practical", "Tutorial"], required: true, }, ], @@ -49,7 +46,6 @@ async function create(activityData) { const { activityBlueprint, startTime, - duration, course, faculty, type, @@ -60,7 +56,6 @@ async function create(activityData) { const activity = new Activity({ activityBlueprint, startTime, - duration, course, faculty, type, @@ -72,6 +67,34 @@ async function create(activityData) { return activityDoc; } +async function createMultiple(activityDataArray) { + const activities = activityDataArray.map( + ({ + activityBlueprint, + startTime, + course, + faculty, + type, + task, + group, + students, + }) => + Activity({ + activityBlueprint, + startTime, + course, + faculty, + type, + task, + group, + students, + }), + ); + + const activityDocs = await Activity.insertMany(activities); + return activityDocs; +} + // Retrieve activity based on a given filter and limit async function read(filter, limit = 0, page = 1) { const activityDoc = await Activity.find(filter) @@ -106,4 +129,5 @@ export default { read, update, remove, + createMultiple, }; diff --git a/models/activityBlueprint.js b/models/activityBlueprint.js index 5f6d557..7c062d9 100644 --- a/models/activityBlueprint.js +++ b/models/activityBlueprint.js @@ -1,19 +1,46 @@ import connector from "#models/databaseUtil"; const activityBluePrintSchema = { - number: { type: Number, required: true }, - academicYear: { + day: { type: String, + enum: [ + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday", + "Sunday", + ], + required: true, + }, + startTime: { type: String, required: true }, + duration: { type: Number, required: true }, + infra: { + type: connector.Schema.Types.ObjectId, + ref: "Infrastructure", + required: true, + }, + course: { + type: connector.Schema.Types.ObjectId, + ref: "Course", + required: true, + }, + faculty: { + type: connector.Schema.Types.ObjectId, + ref: "Faculty", + required: true, + }, + type: { + type: String, + enum: ["lecture", "practical", "tutorial"], + required: true, + }, + group: { + type: connector.Schema.Types.ObjectId, + ref: "Group", required: true, - validate: { - validator: (value) => /^20\d{2}$/.test(value), // changed the valid year format starting from "20" !! - message: (props) => - `${props.value} is not a valid year format starting with "2"!`, - }, }, - type: { enum: ["ODD", "EVEN"], required: true }, - startDate: { type: Date, required: true }, - endDate: { type: Date, required: true }, }; // eslint-disable-next-line no-unused-vars @@ -28,17 +55,65 @@ async function remove(filter) { } async function create(activityBlueprintData) { - const { number, academicYear, type, startDate, endDate } = - activityBlueprintData; - const activityblueprint = new ActivityBlueprint({ + const { + number, + academicYear, + day, + startTime, + duration, + infra, + course, + faculty, + type, + group, + } = activityBlueprintData; + const activityBlueprint = new ActivityBlueprint({ number, academicYear, + day, + startTime, + duration, + infra, + course, + faculty, type, - startDate, - endDate, + group, }); - const activityblueprintDoc = await activityblueprint.save(); - return activityblueprintDoc; + const activityBlueprintDoc = await activityBlueprint.save(); + return activityBlueprintDoc; +} + +async function createMultiple(activityBlueprintDataArray) { + const activityBlueprints = activityBlueprintDataArray.map( + ({ + number, + academicYear, + day, + startTime, + duration, + infra, + course, + faculty, + type, + group, + }) => + ActivityBlueprint({ + number, + academicYear, + day, + startTime, + duration, + infra, + course, + faculty, + type, + group, + }), + ); + + const activityBlueprintDocs = + await ActivityBlueprint.insertMany(activityBlueprints); + return activityBlueprintDocs; } async function read(filter, limit = 0, page = 1) { @@ -65,4 +140,5 @@ export default { read, update, remove, + createMultiple, }; diff --git a/models/attendance.js b/models/attendance.js index c1ef29c..3bfb51b 100644 --- a/models/attendance.js +++ b/models/attendance.js @@ -1,7 +1,5 @@ import connector from "#models/databaseUtil"; -connector.set("debug", true); - const attendanceSchema = { student: { type: connector.Schema.Types.ObjectId, @@ -42,6 +40,30 @@ async function create(attendanceData) { return attendanceDoc; } +async function createMultiple(attendanceDataArray) { + const attendances = attendanceDataArray.map( + ({ + student, + course, + monthlyAttended, + monthlyOccured, + cumulativeAttended, + cumulativeOccured, + }) => + Attendance({ + student, + course, + monthlyAttended, + monthlyOccured, + cumulativeAttended, + cumulativeOccured, + }), + ); + + const attendanceDocs = await Attendance.insertMany(attendances); + return attendanceDocs; +} + async function read(filter, limit = 0, page = 1) { const attendanceDoc = await Attendance.find(filter) .limit(limit) @@ -70,4 +92,5 @@ export default { remove, update, read, + createMultiple, }; diff --git a/models/coursework.js b/models/coursework.js index 52a36f8..51c9acf 100644 --- a/models/coursework.js +++ b/models/coursework.js @@ -14,12 +14,6 @@ const courseworkSchema = { }, task: { type: connector.Schema.Types.ObjectId, - refPath: "objectID", - required: true, - }, - objectID: { - type: String, - enum: ["Practical", "Tutorial", "Assignment"], required: true, }, activity: { @@ -53,6 +47,24 @@ async function create(courseworkData) { return courseworkDoc; } +async function createMultiple(courseworkDataArray) { + const courseworks = courseworkDataArray.map( + ({ student, type, course, task, objectID, activity, marks }) => + Coursework({ + student, + type, + course, + task, + objectID, + activity, + marks, + }), + ); + + const courseworkDocs = await Coursework.insertMany(courseworks); + return courseworkDocs; +} + async function read(filter, limit = 0, page = 1) { const courseworkDoc = await Coursework.find(filter) .limit(limit) @@ -77,4 +89,5 @@ export default { read, update, remove, + createMultiple, }; diff --git a/models/exam.js b/models/exam.js index e5c2c0b..087cb19 100644 --- a/models/exam.js +++ b/models/exam.js @@ -24,11 +24,37 @@ const examSchema = { const Exam = connector.model("Exam", examSchema); async function create(examData) { - const exam = new Exam(examData); + const { date, startTime, duration, supervisor, infrastructure, course } = + examData; + const exam = new Exam({ + date, + startTime, + duration, + supervisor, + infrastructure, + course, + }); const examDoc = await exam.save(); return examDoc; } +async function createMultiple(examDataArray) { + const exams = examDataArray.map( + ({ date, startTime, duration, supervisor, infrastructure, course }) => + Exam({ + date, + startTime, + duration, + supervisor, + infrastructure, + course, + }), + ); + + const examDocs = await Exam.insertMany(exams); + return examDocs; +} + async function read(filter, limit = 0, page = 1) { const examDoc = await Exam.find(filter) .limit(limit) @@ -58,4 +84,5 @@ export default { read, update, remove, + createMultiple, }; diff --git a/models/faculty.js b/models/faculty.js index c63a316..e94a1bf 100644 --- a/models/faculty.js +++ b/models/faculty.js @@ -26,6 +26,10 @@ const facultySchema = { "Assistant Professor", "Associate Professor", "Activity Head", + "Professor", + "Director", + "T&P Officer", + "R&D Activity Head", ], required: true, }, @@ -52,11 +56,52 @@ async function create(facultyData) { return facultyDoc; } +async function createMultiple(facultyDataArray) { + const facultys = facultyDataArray.map( + ({ + ERPID, + dateOfJoining, + dateOfLeaving, + profileLink, + qualifications, + totalExperience, + achievements, + areaOfSpecialization, + papersPublishedPG, + papersPublishedUG, + department, + preferredSubjects, + designation, + natureOfAssociation, + additionalResponsibilities, + }) => + Faculty({ + ERPID, + dateOfJoining, + dateOfLeaving, + profileLink, + qualifications, + totalExperience, + achievements, + areaOfSpecialization, + papersPublishedPG, + papersPublishedUG, + department, + preferredSubjects, + designation, + natureOfAssociation, + additionalResponsibilities, + }), + ); + + const facultyDocs = await Faculty.insertMany(facultys); + return facultyDocs; +} + async function read(filter, limit = 0, page = 1) { const facultyDoc = await Faculty.find(filter) .limit(limit) - .skip((page - 1) * limit) - .exec(); + .skip((page - 1) * limit); const count = await Faculty.count(); const totalPages = Math.ceil(count / limit); return { totalPages, data: facultyDoc }; @@ -76,4 +121,5 @@ export default { read, update, remove, + createMultiple, }; diff --git a/models/group.js b/models/group.js index 3a62f77..ce9d8ae 100644 --- a/models/group.js +++ b/models/group.js @@ -15,6 +15,18 @@ async function create(groupData) { return groupDoc; } +async function createMultiple(groupDataArray) { + const groups = groupDataArray.map(({ title, students }) => + Group({ + title, + students, + }), + ); + + const groupDocs = await Group.insertMany(groups); + return groupDocs; +} + async function read(filter, limit = 0, page = 1) { const groupDoc = await Group.find(filter) .limit(limit) @@ -43,4 +55,5 @@ export default { read, update, remove, + createMultiple, }; diff --git a/models/notification.js b/models/notification.js index 8cac4fe..61da260 100644 --- a/models/notification.js +++ b/models/notification.js @@ -11,7 +11,7 @@ const notificationSchema = { }, from: { type: connector.Schema.Types.ObjectId, - ref: "Faculty", // Reference to the Faculty model + ref: "User", // Reference to the Faculty model required: true, }, type: { @@ -44,6 +44,22 @@ async function create(notificationData) { return notificationDOC; } +async function createMultiple(notificationDataArray) { + const notifications = notificationDataArray.map( + ({ data, title, from, type, filter }) => + Notification({ + data, + title, + from, + type, + filter, + }), + ); + + const notificationDocs = await Notification.insertMany(notifications); + return notificationDocs; +} + async function read(filter, limit = 0, page = 1) { const notificationDoc = await Notification.find(filter) .limit(limit) @@ -73,4 +89,5 @@ export default { read, update, remove, + createMultiple, }; diff --git a/models/paper.js b/models/paper.js index 29785c0..b0bb773 100644 --- a/models/paper.js +++ b/models/paper.js @@ -2,15 +2,17 @@ import connector from "#models/databaseUtil"; const paperSchema = { answerSheetID: { type: String, required: true }, - exam: [ - { type: connector.Schema.Types.ObjectId, ref: "Exam", required: true }, - ], - student: [ - { type: connector.Schema.Types.ObjectId, ref: "Student", required: true }, - ], - checkedBy: [ - { type: connector.Schema.Types.ObjectId, ref: "Faculty", required: true }, - ], + exam: { type: connector.Schema.Types.ObjectId, ref: "Exam", required: true }, + student: { + type: connector.Schema.Types.ObjectId, + ref: "Student", + required: true, + }, + checkedBy: { + type: connector.Schema.Types.ObjectId, + ref: "Faculty", + required: true, + }, mark: { type: Number, required: true }, }; @@ -36,6 +38,22 @@ async function create(paperData) { return paperDoc; } +async function createMultiple(paperDataArray) { + const papers = paperDataArray.map( + ({ answerSheetID, exam, student, checkedBy, mark }) => + Paper({ + answerSheetID, + exam, + student, + checkedBy, + mark, + }), + ); + + const paperDocs = await Paper.insertMany(papers); + return paperDocs; +} + async function read(filter, limit = 0, page = 1) { const paperDoc = await Paper.find(filter) .limit(limit) @@ -60,4 +78,5 @@ export default { read, update, remove, + createMultiple, }; diff --git a/models/student.js b/models/student.js index afdab1f..f72d06d 100644 --- a/models/student.js +++ b/models/student.js @@ -43,6 +43,24 @@ async function create(studentData) { return studentDoc; } +async function createMultiple(studentDataArray) { + const students = studentDataArray.map( + ({ ERPID, name, joiningYear, branch, division, rollNo, coursesOpted }) => + Student({ + ERPID, + name, + joiningYear, + branch, + division, + rollNo, + coursesOpted, + }), + ); + + const studentDocs = await Student.insertMany(students); + return studentDocs; +} + async function read(filter, limit = 0, page = 1) { const studentDoc = await Student.find(filter) .limit(limit) @@ -67,4 +85,5 @@ export default { read, update, remove, + createMultiple, }; diff --git a/models/timetable.js b/models/timetable.js index 44d1510..9730fa8 100644 --- a/models/timetable.js +++ b/models/timetable.js @@ -58,6 +58,36 @@ async function create(timetableData) { return timetableDoc; } +async function createMultiple(timetableDataArray) { + const timetables = timetableDataArray.map( + ({ + startDate, + endDate, + classIncharge, + group, + activityBlueprints, + lunchbreakStartTime, + lunchbreakDuration, + teabreakStartTime, + teabreakDuration, + }) => + Timetable({ + startDate, + endDate, + classIncharge, + group, + activityBlueprints, + lunchbreakStartTime, + lunchbreakDuration, + teabreakStartTime, + teabreakDuration, + }), + ); + + const timetableDocs = await Timetable.insertMany(timetables); + return timetableDocs; +} + async function read(filter, limit = 0, page = 1) { const timetableDoc = await Timetable.find(filter) .limit(limit) @@ -82,4 +112,5 @@ export default { read, update, remove, + createMultiple, }; diff --git a/models/user.js b/models/user.js index 00c2f24..e100c45 100644 --- a/models/user.js +++ b/models/user.js @@ -1,7 +1,6 @@ import connector from "#models/databaseUtil"; import { hashPassword } from "#util"; -connector.set("debug", true); const userSchema = { name: { type: String, required: true }, emailId: { type: String, unique: true, required: true }, @@ -10,7 +9,7 @@ const userSchema = { userType: { type: String, required: true, - enum: ["ADMIN", "FACULTY", "EMPLOYEE", "STUDENT"], + enum: ["ADMIN", "FACULTY", "EMPLOYEE", "STUDENT", "PARENT", "DEV"], default: "ADMIN", // for now we are keeping the default usertype as ADMIN }, @@ -37,6 +36,23 @@ async function create(userData) { return userDoc; } +async function createMultiple(userDataArray) { + const hashPromises = userDataArray.map(async (userData) => { + const { name, emailId, password, uid, userType } = userData; + const hashedPassword = await hashPassword(password); + return User({ + name, + password: hashedPassword, + emailId, + uid, + userType, + }); + }); + const users = await Promise.all(hashPromises); + const userDocs = await User.insertMany(users); + return userDocs; +} + async function read(filter, limit = 0, page = 1) { const userDoc = await User.find(filter) .limit(limit) @@ -61,4 +77,5 @@ export default { read, update, remove, + createMultiple, }; diff --git a/package-lock.json b/package-lock.json index 5cc3458..32656c2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,6 +35,7 @@ "husky": "^8.0.0", "jest": "^29.5.0", "lint-staged": "^14.0.1", + "mongodb-memory-server": "^9.0.1", "nodemon": "^3.0.1", "prettier": "^3.0.3", "supertest": "^6.3.3" @@ -3675,6 +3676,15 @@ "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==" }, + "node_modules/async-mutex": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.4.0.tgz", + "integrity": "sha512-eJFZ1YhRR8UN8eBLoNzcDPcy/jqjsg6I1AP+KvWQX80BqOSW1oJPJXDylPUEeMr2ZQvHgnQ//Lp6f3RQ1zI7HA==", + "dev": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -3693,6 +3703,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/b4a": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.4.tgz", + "integrity": "sha512-fpWrvyVHEKyeEvbKZTVOeZF3VSKKWtJxFIxX/jaVPf+cLbGUSitjb49pHLqPV2BUNNZ0LcoeEGfE/YCpyDYHIw==", + "dev": true + }, "node_modules/babel-jest": { "version": "29.6.2", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.6.2.tgz", @@ -4021,6 +4037,15 @@ "ieee754": "^1.1.13" } }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", @@ -4424,6 +4449,12 @@ "node": ">=14" } }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true + }, "node_modules/component-emitter": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", @@ -5667,6 +5698,12 @@ "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", "dev": true }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "dev": true + }, "node_modules/fast-glob": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", @@ -5762,6 +5799,15 @@ "bser": "2.1.1" } }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dev": true, + "dependencies": { + "pend": "~1.2.0" + } + }, "node_modules/fecha": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", @@ -5816,6 +5862,23 @@ "node": ">= 0.8" } }, + "node_modules/find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dev": true, + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/avajs/find-cache-dir?sponsor=1" + } + }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -5856,6 +5919,26 @@ "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" }, + "node_modules/follow-redirects": { + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", + "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/for-each": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", @@ -8497,6 +8580,153 @@ "whatwg-url": "^11.0.0" } }, + "node_modules/mongodb-memory-server": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/mongodb-memory-server/-/mongodb-memory-server-9.0.1.tgz", + "integrity": "sha512-Qqph8PoJHtHjvP8JynWMx9K9N4Eh0GDj+bexL/vB6jtvzw1nUcARnxidPvaKFzU+hDULz9sgTUqYJmjqowmXTQ==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "mongodb-memory-server-core": "9.0.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.20.1" + } + }, + "node_modules/mongodb-memory-server-core": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/mongodb-memory-server-core/-/mongodb-memory-server-core-9.0.1.tgz", + "integrity": "sha512-+dGsBoviujeAiDiU4O0zUXQ33pLsjQhDuWA8HucLfwgm0d+xdfOhSe5Xz/6uQjE1MNutpwExBvFyVKXaVuNkYg==", + "dev": true, + "dependencies": { + "async-mutex": "^0.4.0", + "camelcase": "^6.3.0", + "debug": "^4.3.4", + "find-cache-dir": "^3.3.2", + "follow-redirects": "^1.15.3", + "https-proxy-agent": "^7.0.2", + "mongodb": "^5.9.0", + "new-find-package-json": "^2.0.0", + "semver": "^7.5.4", + "tar-stream": "^3.0.0", + "tslib": "^2.6.2", + "yauzl": "^2.10.0" + }, + "engines": { + "node": ">=14.20.1" + } + }, + "node_modules/mongodb-memory-server-core/node_modules/agent-base": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", + "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", + "dev": true, + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/mongodb-memory-server-core/node_modules/bson": { + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/bson/-/bson-5.5.1.tgz", + "integrity": "sha512-ix0EwukN2EpC0SRWIj/7B5+A6uQMQy6KMREI9qQqvgpkV2frH63T0UDVd1SYedL6dNCmDBYB3QtXi4ISk9YT+g==", + "dev": true, + "engines": { + "node": ">=14.20.1" + } + }, + "node_modules/mongodb-memory-server-core/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mongodb-memory-server-core/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/mongodb-memory-server-core/node_modules/https-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz", + "integrity": "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==", + "dev": true, + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/mongodb-memory-server-core/node_modules/mongodb": { + "version": "5.9.1", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-5.9.1.tgz", + "integrity": "sha512-NBGA8AfJxGPeB12F73xXwozt8ZpeIPmCUeWRwl9xejozTXFes/3zaep9zhzs1B/nKKsw4P3I4iPfXl3K7s6g+Q==", + "dev": true, + "dependencies": { + "bson": "^5.5.0", + "mongodb-connection-string-url": "^2.6.0", + "socks": "^2.7.1" + }, + "engines": { + "node": ">=14.20.1" + }, + "optionalDependencies": { + "@mongodb-js/saslprep": "^1.1.0" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.188.0", + "@mongodb-js/zstd": "^1.0.0", + "kerberos": "^1.0.0 || ^2.0.0", + "mongodb-client-encryption": ">=2.3.0 <3", + "snappy": "^7.2.2" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + } + } + }, + "node_modules/mongodb-memory-server-core/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, "node_modules/mongoose": { "version": "6.12.0", "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-6.12.0.tgz", @@ -8622,6 +8852,41 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true }, + "node_modules/new-find-package-json": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/new-find-package-json/-/new-find-package-json-2.0.0.tgz", + "integrity": "sha512-lDcBsjBSMlj3LXH2v/FW3txlh2pYTjmbOXPYJD93HI5EwuLzI11tdHSIpUMmfq/IOsldj4Ps8M8flhm+pCK4Ew==", + "dev": true, + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">=12.22.0" + } + }, + "node_modules/new-find-package-json/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/new-find-package-json/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, "node_modules/node-addon-api": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", @@ -9116,6 +9381,12 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true + }, "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -9379,6 +9650,12 @@ } ] }, + "node_modules/queue-tick": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", + "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==", + "dev": true + }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -10038,6 +10315,16 @@ "node": ">= 0.8" } }, + "node_modules/streamx": { + "version": "2.15.2", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.15.2.tgz", + "integrity": "sha512-b62pAV/aeMjUoRN2C/9F0n+G8AfcJjNC0zw/ZmOHeFsIe4m4GzjVW9m6VHXVjk536NbdU9JRwKMJRfkc+zUFTg==", + "dev": true, + "dependencies": { + "fast-fifo": "^1.1.0", + "queue-tick": "^1.0.1" + } + }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -10323,6 +10610,17 @@ "node": ">=10" } }, + "node_modules/tar-stream": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.6.tgz", + "integrity": "sha512-B/UyjYwPpMBv+PaFSWAmtYjwdrlEaZQEhMIBFNC5oEG8lpiW8XjcSdmEaClj28ArfKScKHs2nshz3k2le6crsg==", + "dev": true, + "dependencies": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, "node_modules/tar/node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", @@ -11260,6 +11558,16 @@ "node": ">=12" } }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dev": true, + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index b1ca24b..53e7716 100644 --- a/package.json +++ b/package.json @@ -21,17 +21,18 @@ "devstart": "nodemon ./bin/www", "serverstart": "DEBUG=api:* npm run devstart", "serverstartWin": "SET DEBUG=api:* && npm run devstart", - "test": "NODE_OPTIONS=--experimental-vm-modules npx jest", + "test": "NODE_OPTIONS=--experimental-vm-modules npx jest --runInBand", "test:watch": "NODE_OPTIONS=--experimental-vm-modules npx jest --watch", "test:openHandles": "NODE_OPTIONS=--experimental-vm-modules npx jest --detectOpenHandles", - "testWin": "SET NODE_OPTIONS=--experimental-vm-modules && npx jest", + "testWin": "SET NODE_OPTIONS=--experimental-vm-modules && npx jest --runInBand", "testWin:watch": "SET NODE_OPTIONS=--experimental-vm-modules && npx jest --watch", "testWin:openHandles": "SET NODE_OPTIONS=--experimental-vm-modules && npx jest --detectOpenHandles", "eslint": "eslint", "backup": "node ./misc/erpbackup", "restore": "node ./misc/erprestore", "prepare": "husky install", - "initialize": "node ./misc/initDB" + "initialize": "node ./misc/initDB", + "testInit": "node ./misc/testMock" }, "dependencies": { "bcrypt": "^5.1.0", @@ -50,6 +51,8 @@ "winston-daily-rotate-file": "^4.7.1" }, "devDependencies": { + "@faker-js/faker": "^8.2.0", + "apidoc": "^1.2.0", "eslint": "^8.38.0", "eslint-config-airbnb-base": "^15.0.0", "eslint-config-prettier": "^9.0.0", @@ -59,10 +62,9 @@ "husky": "^8.0.0", "jest": "^29.5.0", "lint-staged": "^14.0.1", + "mongodb-memory-server": "^9.0.1", "nodemon": "^3.0.1", "prettier": "^3.0.3", - "supertest": "^6.3.3", - "apidoc": "^1.2.0", - "@faker-js/faker": "^8.2.0" + "supertest": "^6.3.3" } } diff --git a/routes/activityBlueprint.js b/routes/activityBlueprint.js new file mode 100644 index 0000000..3790a34 --- /dev/null +++ b/routes/activityBlueprint.js @@ -0,0 +1,32 @@ +import express from "express"; +import authenticateToken from "#middleware/auth"; +import authorization from "#middleware/authorization"; +import activityBlueprintController from "#controller/activityBlueprint"; + +const router = express.Router(); +router.post( + "/add", + authenticateToken, + authorization(["ADMIN", "FACULTY"]), + activityBlueprintController.addActivityBP, +); +router.get( + "/list", + authenticateToken, + authorization(["ADMIN", "FACULTY"]), + activityBlueprintController.getActivityBP, +); +router.post( + "/update/:id", + authenticateToken, + authorization(["ADMIN", "FACULTY"]), + activityBlueprintController.updateActivityBP, +); +router.delete( + "/delete/:id", + authenticateToken, + authorization(["ADMIN", "FACULTY"]), + activityBlueprintController.deleteActivityBP, +); + +export default router; diff --git a/services/activity.js b/services/activity.js index 10ceab8..7248012 100644 --- a/services/activity.js +++ b/services/activity.js @@ -4,7 +4,6 @@ import databaseError from "#error/database"; export async function createActivity( activityBlueprint, startTime, - duration, course, faculty, type, @@ -15,7 +14,6 @@ export async function createActivity( const newActivity = await Activity.create({ activityBlueprint, startTime, - duration, course, faculty, task, diff --git a/services/activityBlueprint.js b/services/activityBlueprint.js new file mode 100644 index 0000000..6d2a71a --- /dev/null +++ b/services/activityBlueprint.js @@ -0,0 +1,57 @@ +import ActivityBlueprint from "#models/activityBlueprint"; +import databaseError from "#error/database"; + +export async function createActivityBP( + number, + academicYear, + day, + startTime, + duration, + infra, + course, + faculty, + type, + group, +) { + const newActivityBP = await ActivityBlueprint.create({ + number, + academicYear, + day, + startTime, + duration, + infra, + course, + faculty, + type, + group, + }); + if (newActivityBP) { + return newActivityBP; + } + throw new databaseError.DataEntryError("actvity blueprint"); +} + +export async function updateActivityBlueprintById(id, data) { + const updated = await ActivityBlueprint.update({ _id: id }, data); + if (updated) { + return updated; + } + throw new databaseError.DataEntryError("activityBlueprint"); +} + +export async function activityBlueprintList(filter, limit, page) { + const activityBlueprintlist = await ActivityBlueprint.read( + filter, + limit, + page, + ); + return activityBlueprintlist; +} + +export async function deleteActivityBlueprintById(id) { + const deleted = await ActivityBlueprint.remove({ _id: id }); + if (deleted) { + return deleted; + } + throw new databaseError.DataDeleteError("activityBlueprint"); +} diff --git a/test/config/config.js b/test/config/config.js new file mode 100644 index 0000000..0379916 --- /dev/null +++ b/test/config/config.js @@ -0,0 +1,6 @@ +export const config = { + Memory: true, + IP: "127.0.0.1", + Port: "27017", + Database: "somedb", +}; diff --git a/test/config/globalSetup.js b/test/config/globalSetup.js new file mode 100644 index 0000000..4ae3e37 --- /dev/null +++ b/test/config/globalSetup.js @@ -0,0 +1,53 @@ +import { spawn } from "child_process"; +import { MongoMemoryServer } from "mongodb-memory-server"; +import { config } from "./config.js"; // eslint-disable-line import/extensions + +function runChildProcessWithTimeout(command, args, timeout) { + return new Promise((resolve, reject) => { + const child = spawn(command, args); + + const timeoutId = setTimeout(() => { + child.kill(); // Kill the child process if it exceeds the timeout + reject(new Error("Child process timed out")); + }, timeout); + + child.on("exit", (code, signal) => { + clearTimeout(timeoutId); // Clear the timeout if the child process exits before the timeout + if (code === 0) { + resolve(); + } else { + reject( + new Error( + `Child process failed with code ${code} with signal ${signal}`, + ), + ); + } + }); + + child.on("error", (err) => { + clearTimeout(timeoutId); // Clear the timeout if an error occurs in the child process + reject(err); + }); + }); +} + +export default async function globalSetup() { + console.log("initializing testDB"); + if (config.Memory) { + // Config to decided if an mongodb-memory-server instance should be used + // it"s needed in global space, because we don"t want to create a new instance every test-suite + const instance = await MongoMemoryServer.create(); + const uri = instance.getUri(); + global.MONGOINSTANCE = instance; + process.env.DB_URL = uri.slice(0, uri.lastIndexOf("/")); + } else { + process.env.DB_URL = `mongodb://${config.IP}:${config.Port}`; + } + await runChildProcessWithTimeout("node", ["./misc/testMock"], 100000) + .then(() => { + console.log("Child process completed successfully."); + }) + .catch((err) => { + console.error(`Error running child process: ${err.message}`); + }); +} diff --git a/test/config/globalTeardown.js b/test/config/globalTeardown.js new file mode 100644 index 0000000..fec7fa1 --- /dev/null +++ b/test/config/globalTeardown.js @@ -0,0 +1,9 @@ +import { config } from "./config.js"; // eslint-disable-line import/extensions + +export default async function globalTeardown() { + if (config.Memory) { + // Config to decided if an mongodb-memory-server instance should be used + const instance = global.MONGOINSTANCE; + await instance.stop(); + } +} diff --git a/test/config/setup.js b/test/config/setup.js index d90be2c..61d3b33 100644 --- a/test/config/setup.js +++ b/test/config/setup.js @@ -1,4 +1,3 @@ -import { spawn } from "child_process"; import request from "supertest"; import app from "#app"; // Update this import based on your app"s structure import connector from "#models/databaseUtil"; // Update this import @@ -8,12 +7,9 @@ const server = app.listen(null, () => { }); const agent = request.agent(server); -const child = spawn("node", ["./misc/initDB"]); global.server = server; global.agent = agent; -global.child = child; export default async () => { global.server = server; global.agent = agent; - global.child = child; }; diff --git a/test/config/teardown.js b/test/config/teardown.js index d5b73a5..dd3688c 100644 --- a/test/config/teardown.js +++ b/test/config/teardown.js @@ -1,11 +1,3 @@ -const teardownProcess = () => { - global.child.kill(); -}; - global.server.close(); global.agent.app.close(); -global.child.stdin.end(); -global.child.stdout.destroy(); -global.child.stderr.destroy(); -setTimeout(teardownProcess, 500); export default async () => {}; diff --git a/test/routes/activity.test.js b/test/routes/activity.test.js index eac7179..f248b21 100644 --- a/test/routes/activity.test.js +++ b/test/routes/activity.test.js @@ -1,4 +1,4 @@ -import { jest } from "@jest/globals"; // eslint-disable-line import/no-extraneous-dependencies +import { jest } from "@jest/globals"; // eslint-disable-line import/no-extraneous-dependencies import connector from "#models/databaseUtil"; // Update this import import activityModel from "#models/activity"; // Update this import @@ -8,7 +8,7 @@ const { agent } = global; function cleanUp(callback) { activityModel .remove({ - course: "5f8778b54b553439ac49a03a" + course: "5f8778b54b553439ac49a03a", }) .then(() => { connector.disconnect((DBerr) => { @@ -18,7 +18,6 @@ function cleanUp(callback) { }); } - afterAll((done) => { cleanUp(done); }); @@ -28,15 +27,14 @@ describe("Activity API", () => { const response = await agent.post("/activity/add").send({ activityBlueprint: "5f8778b54b553439ac49a03a", startTime: "2023-06-18T14:11:30Z", - duration: 2, course: "5f8778b54b553439ac49a03a", faculty: "5f8778b54b553439ac49a03a", type: "LECTURE", task: ["5f8778b54b553439ac49a03a"], group: "5f8778b54b553439ac49a03a", - students: ["5f8778b54b553439ac49a03a"] + students: ["5f8778b54b553439ac49a03a"], }); - + expect(response.status).toBe(200); expect(response.body.res).toMatch(/added activity/); }); @@ -45,15 +43,14 @@ describe("Activity API", () => { let id; beforeEach(async () => { id = await agent.post("/activity/add").send({ - activityBlueprint: "64fc3c8bde9fa947ea1f412f", + activityBlueprint: "5f8778b54b553439ac49a03a", startTime: "2023-06-18T14:11:30Z", - duration: 2, course: "5f8778b54b553439ac49a03a", - faculty: "64fc3c8bde9fa947ea1f412f", + faculty: "5f8778b54b553439ac49a03a", type: "LECTURE", - task: ["64fc3c8bde9fa947ea1f412f"], - group: "64fc3c8bde9fa947ea1f412f", - students: ["64fc3c8bde9fa947ea1f412f"] + task: ["5f8778b54b553439ac49a03a"], + group: "5f8778b54b553439ac49a03a", + students: ["5f8778b54b553439ac49a03a"], }); id = JSON.parse(id.res.text).id; }); @@ -61,14 +58,6 @@ describe("Activity API", () => { afterEach(async () => { await activityModel.remove({ activityBlueprint: "64fc3c8bde9fa947ea1f412f", - startTime: "2023-06-18T14:11:30Z", - duration: 2, - course: "5f8778b54b553439ac49a03a", - faculty: "64fc3c8bde9fa947ea1f412f", - type: "LECTURE", - task: ["64fc3c8bde9fa947ea1f412f"], - group: "64fc3c8bde9fa947ea1f412f", - students: ["64fc3c8bde9fa947ea1f412f"] }); }); @@ -81,12 +70,9 @@ describe("Activity API", () => { }); it("should update activity", async () => { - const response = await agent - .post(`/activity/update/${id}`) - .send({ - duration: 5, - }); - + const response = await agent.post(`/activity/update/${id}`).send({ + type: "TUTORIAL", + }); expect(response.status).toBe(200); expect(response.body.res).toMatch(/updated activity/); }); diff --git a/test/routes/exam.test.js b/test/routes/exam.test.js index 5500fb4..dece571 100644 --- a/test/routes/exam.test.js +++ b/test/routes/exam.test.js @@ -1,14 +1,20 @@ import { jest } from "@jest/globals"; // eslint-disable-line import/no-extraneous-dependencies import connector from "#models/databaseUtil"; import examModel from "#models/exam"; +import facultyModel from "#models/faculty"; +import courseModel from "#models/course"; +import infraModel from "#models/infrastructure"; +/* eslint-disable no-underscore-dangle */ jest.mock("#util"); const { agent } = global; - +let supervisorId; +let infraId; +let courseId; function cleanUp(callback) { examModel .remove({ - supervisor: "5f8778b54b553439ac49a03a", + supervisor: supervisorId, }) .then(() => { connector.disconnect((DBerr) => { @@ -18,19 +24,33 @@ function cleanUp(callback) { }); } +async function getIds(callback) { + supervisorId = await facultyModel.read({}, 1); + supervisorId = supervisorId.data[0]._id; + infraId = await infraModel.read({}, 1); + infraId = infraId.data[0]._id; + courseId = await courseModel.read({}, 1); + courseId = courseId.data[0]._id; + callback(); +} + afterAll((done) => { cleanUp(done); }); +beforeAll((done) => { + getIds(done); +}); + describe("exam API", () => { it("should create exam", async () => { const response = await agent.post("/exam/add").send({ date: "2023-06-18T14:11:30Z", startTime: "2023-06-18T14:11:30Z", duration: 5, - supervisor: "5f8778b54b553439ac49a03a", - infrastructure: "5f8778b54b553439ac49a03a", - course: "5f8778b54b553439ac49a03a", + supervisor: supervisorId, + infrastructure: infraId, + course: courseId, }); expect(response.headers["content-type"]).toMatch(/json/); expect(response.status).toBe(200); @@ -44,21 +64,21 @@ describe("exam API", () => { date: "2023-06-18T14:11:30Z", startTime: "2023-06-18T14:11:30Z", duration: 5, - supervisor: "64453a62c8f2146f2f34c73a", - infrastructure: "64453a62c8f2146f2f34c73a", - course: "64453a62c8f2146f2f34c73a", + supervisor: supervisorId, + infrastructure: infraId, + course: courseId, }); id = JSON.parse(id.res.text).id; }); afterEach(async () => { - await examModel.remove({ supervisor: "64453a62c8f2146f2f34c73a" }); + await examModel.remove({ supervisor: supervisorId }); }); it("should read exam", async () => { const response = await agent .get("/exam/list") - .send({ supervisor: "64453a62c8f2146f2f34c73a" }); + .send({ supervisor: supervisorId }); expect(response.status).toBe(200); expect(response.body.res).toBeDefined(); });