diff --git a/package.json b/package.json index 071a9f1..f9d5880 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,7 @@ "name": "enroll", "version": "1.0.0", "description": "Student Management System", + "type": "module", "main": "index.js", "scripts": { "start": "node ./src/config/index.js", diff --git a/src/Config/config.js b/src/Config/config.js index 7f60c0f..22cad9b 100644 --- a/src/Config/config.js +++ b/src/Config/config.js @@ -1,9 +1,23 @@ -module.exports = { +/** + * Sequelize database configuration for the 'development' environment. + * This file uses ES6 module syntax (`export default`). + */ + +export default { development: { + // Uses environment variable DB_USER, falling back to 'root' username: process.env.DB_USER || 'root', + + // Uses environment variable DB_PASS, falling back to null (no password) password: process.env.DB_PASS || null, + + // Uses environment variable DB_NAME, falling back to 'EnrolNow' database: process.env.DB_NAME || 'EnrolNow', + + // Uses environment variable DB_HOST, falling back to '127.0.0.1' (localhost) host: process.env.DB_HOST || '127.0.0.1', + + // Specifies the database dialect dialect: 'mysql' } }; \ No newline at end of file diff --git a/src/Config/db.js b/src/Config/db.js index ec99cbb..437f2f6 100644 --- a/src/Config/db.js +++ b/src/Config/db.js @@ -1,7 +1,17 @@ -// db.js -const { Sequelize } = require('sequelize'); -const config = require('./config').development; +/** + * Sequelize database connection setup using ES6 modules. + * It imports the database configuration from './database.config.js'. + */ +import { Sequelize } from 'sequelize'; +// Import the default export (the configuration object) from the config file. +// Note: The original CJS referred to './config', but we use the actual file name. +import configModule from '../Config/config.js'; + +// Destructure the specific 'development' environment configuration. +const config = configModule.development; + +// Initialize Sequelize instance const sequelize = new Sequelize( config.database, config.username, @@ -11,5 +21,5 @@ const sequelize = new Sequelize( dialect: config.dialect, } ); - -module.exports = sequelize; \ No newline at end of file +// Export the initialized Sequelize instance +export default sequelize; diff --git a/src/Config/index.js b/src/Config/index.js index 08ea05e..486c42e 100644 --- a/src/Config/index.js +++ b/src/Config/index.js @@ -1,30 +1,52 @@ -global.__basedir = __dirname; -const express = require('express'); +// --- ES Module Setup for __dirname and __filename --- +// Import necessary modules for path resolution in ES modules +import path from 'path'; +import { fileURLToPath } from 'url'; + +// Derive ES module equivalents for CJS context +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +global.__basedir = __dirname; // Set the global basedir + +// --- Imports --- +import express from 'express'; +import cookieParser from 'cookie-parser'; +import { config } from 'dotenv'; + +// Initialize dotenv configuration immediately +config(); + +// Service Imports (Note: Must include .js extension for local modules) +import UserRegister from "../api/Services/UserService.js"; +import ProgramService from '../api/Services/ProgramCourseServices.js'; + +// Route Imports (Note: Must include .js extension for local modules) +import userRoutes from '../api/Routes/userRoute.js'; +import ProgramRoutes from '../api/Routes/ProgramRoutes.js'; +import departmentRoutes from '../api/Routes/DepartmentRoutes.js'; +import InstructorRoutes from "../api/Routes/InstructorRoutes.js"; + +// --- Application Setup --- const app = express(); const port = 3000; -const cookieparser = require('cookie-parser'); -app.use(express.json()); -app.use(cookieparser()); -require('dotenv').config(); -const UserRegister = require("../api/Services/UserService"); -const ProgramService = require('../api/Services/ProgramCourseServices') -// Require the router -const userRoutes = require('../api/Routes/userRoute'); -const ProgramRoutes = require('../api/Routes/ProgramRoutes'); -const departmentRoutes = require('../api/Routes/DepartmentRoutes') -const InstructorRoutes = require("../api/Routes/InstructorRoutes") +// Middleware +app.use(express.json()); +app.use(cookieParser()); +app.use(express.urlencoded({ extended: true })); // Example route app.get('/', (req, res) => { res.send('Hello from Express API!'); }); -app.use(express.urlencoded({ extended: true })); -// Use the router at a mount path + +// Use the routers at their mount paths app.use('/users', userRoutes); -app.use('/program', ProgramRoutes) -app.use('/department', departmentRoutes) -app.use("/instructor", InstructorRoutes) +app.use('/program', ProgramRoutes); +app.use('/department', departmentRoutes); +app.use("/instructor", InstructorRoutes); + +// --- Start Server --- app.listen(port, () => { console.log(`Express API listening at http://localhost:${port}`); }); \ No newline at end of file diff --git a/src/Database/Migrations/20250712044830-create-user.js b/src/Database/Migrations/20250712044830-create-user.js index bedf5b6..4886cf2 100644 --- a/src/Database/Migrations/20250712044830-create-user.js +++ b/src/Database/Migrations/20250712044830-create-user.js @@ -1,6 +1,6 @@ 'use strict'; /** @type {import('sequelize-cli').Migration} */ -module.exports = { +export default{ async up(queryInterface, Sequelize) { await queryInterface.createTable('Users', { id: { @@ -41,6 +41,7 @@ module.exports = { } }); }, + async down(queryInterface, Sequelize) { await queryInterface.dropTable('Users'); } diff --git a/src/Database/Migrations/20250713051943-create-student.js b/src/Database/Migrations/20250713051943-create-student.js index 7140533..01c39b6 100644 --- a/src/Database/Migrations/20250713051943-create-student.js +++ b/src/Database/Migrations/20250713051943-create-student.js @@ -1,6 +1,6 @@ 'use strict'; /** @type {import('sequelize-cli').Migration} */ -module.exports = { +export default{ async up(queryInterface, Sequelize) { await queryInterface.createTable('Students', { id: { diff --git a/src/Database/Migrations/20250713052213-add-userId-fk-to-students.js b/src/Database/Migrations/20250713052213-add-userId-fk-to-students.js index a41105d..ec238af 100644 --- a/src/Database/Migrations/20250713052213-add-userId-fk-to-students.js +++ b/src/Database/Migrations/20250713052213-add-userId-fk-to-students.js @@ -1,7 +1,7 @@ 'use strict'; /** @type {import('sequelize-cli').Migration} */ -module.exports = { +export default{ async up (queryInterface, Sequelize) { await queryInterface.addConstraint('students', { diff --git a/src/Database/Migrations/20250713080222-create-department.js b/src/Database/Migrations/20250713080222-create-department.js index 7d67cdd..7d44519 100644 --- a/src/Database/Migrations/20250713080222-create-department.js +++ b/src/Database/Migrations/20250713080222-create-department.js @@ -1,6 +1,6 @@ 'use strict'; /** @type {import('sequelize-cli').Migration} */ -module.exports = { +export default{ async up(queryInterface, Sequelize) { await queryInterface.createTable('Departments', { department_id: { diff --git a/src/Database/Migrations/20250713081004-create-instructor.js b/src/Database/Migrations/20250713081004-create-instructor.js index b236432..0a2642e 100644 --- a/src/Database/Migrations/20250713081004-create-instructor.js +++ b/src/Database/Migrations/20250713081004-create-instructor.js @@ -1,6 +1,6 @@ 'use strict'; /** @type {import('sequelize-cli').Migration} */ -module.exports = { +export default{ async up(queryInterface, Sequelize) { await queryInterface.createTable('Instructors', { id: { diff --git a/src/Database/Migrations/20250714085001-create-program-course.js b/src/Database/Migrations/20250714085001-create-program-course.js index 33cf042..d305d41 100644 --- a/src/Database/Migrations/20250714085001-create-program-course.js +++ b/src/Database/Migrations/20250714085001-create-program-course.js @@ -1,6 +1,6 @@ 'use strict'; /** @type {import('sequelize-cli').Migration} */ -module.exports = { +export default{ async up(queryInterface, Sequelize) { await queryInterface.createTable('ProgramCourses', { id: { diff --git a/src/Database/Migrations/20250714114202-add-timestamps-to-Program-course.js b/src/Database/Migrations/20250714114202-add-timestamps-to-Program-course.js deleted file mode 100644 index 53e90de..0000000 --- a/src/Database/Migrations/20250714114202-add-timestamps-to-Program-course.js +++ /dev/null @@ -1,38 +0,0 @@ -'use strict'; - -/** @type {import('sequelize-cli').Migration} */ -module.exports = { - async up (queryInterface, Sequelize) { - /** - * Add altering commands here. - * - * Example: - * await queryInterface.createTable('users', { id: Sequelize.INTEGER }); - */ - - await queryInterface.addColumn('ProgramCourses', 'createdAt', { - allowNull: false, - type: Sequelize.DATE, - defaultValue: Sequelize.literal('CURRENT_TIMESTAMP') - }); - - // Add updatedAt - await queryInterface.addColumn('ProgramCourses', 'updatedAt', { - allowNull: false, - type: Sequelize.DATE, - defaultValue: Sequelize.literal('CURRENT_TIMESTAMP') - }); - - }, - - async down (queryInterface, Sequelize) { - /** - * Add reverting commands here. - * - * Example: - * await queryInterface.dropTable('users'); - */ - await queryInterface.removeColumn('ProgramCourses','createdAt'); - await queryInterface.removeColumn("ProgramCourses",'updateAt') - } -}; diff --git a/src/Database/Migrations/20250716055011-add-ProgramCourse-FK.js b/src/Database/Migrations/20250716055011-add-ProgramCourse-FK.js index 3fb6832..e1e3d12 100644 --- a/src/Database/Migrations/20250716055011-add-ProgramCourse-FK.js +++ b/src/Database/Migrations/20250716055011-add-ProgramCourse-FK.js @@ -2,7 +2,7 @@ 'use strict'; /** @type {import('sequelize-cli').Migration} */ -module.exports = { +export default{ async up(queryInterface, Sequelize) { await queryInterface.addConstraint('programcourses', { fields: ['parent_id'], diff --git a/src/Database/Migrations/20250805145527-add-fk-department-to-instructors.js b/src/Database/Migrations/20250805145527-add-fk-department-to-instructors.js index 77c30fe..af91530 100644 --- a/src/Database/Migrations/20250805145527-add-fk-department-to-instructors.js +++ b/src/Database/Migrations/20250805145527-add-fk-department-to-instructors.js @@ -1,7 +1,7 @@ 'use strict'; /** @type {import('sequelize-cli').Migration} */ -module.exports = { +export default{ async up (queryInterface, Sequelize) { /** * Add altering commands here. diff --git a/src/Database/Migrations/20250806132734-add-fk-department-head.js b/src/Database/Migrations/20250806132734-add-fk-department-head.js index 9a55bb0..25232a2 100644 --- a/src/Database/Migrations/20250806132734-add-fk-department-head.js +++ b/src/Database/Migrations/20250806132734-add-fk-department-head.js @@ -1,7 +1,7 @@ 'use strict'; /** @type {import('sequelize-cli').Migration} */ -module.exports = { +export default{ async up (queryInterface, Sequelize) { /** * Add altering commands here. @@ -29,6 +29,6 @@ module.exports = { * Example: * await queryInterface.dropTable('users'); */ - await queryInterface.removeConstraint('Instructors', 'fk_head_department_id'); + await queryInterface.removeConstraint('Departments', 'fk_head_department_id'); } }; diff --git a/src/Database/Migrations/20250807123944-create-department-instructor.js b/src/Database/Migrations/20250807123944-create-department-instructor.js index ae1b86b..e99c766 100644 --- a/src/Database/Migrations/20250807123944-create-department-instructor.js +++ b/src/Database/Migrations/20250807123944-create-department-instructor.js @@ -1,7 +1,7 @@ 'use strict'; /** @type {import('sequelize-cli').Migration} */ -module.exports = { +export default{ async up (queryInterface, Sequelize) { await queryInterface.createTable('DepartmentInstructors', { id: { diff --git a/src/Database/Migrations/20250807134826-remove-department-id-from-instructors.js b/src/Database/Migrations/20250807134826-remove-department-id-from-instructors.js deleted file mode 100644 index 96bd35a..0000000 --- a/src/Database/Migrations/20250807134826-remove-department-id-from-instructors.js +++ /dev/null @@ -1,33 +0,0 @@ -'use strict'; - -/** @type {import('sequelize-cli').Migration} */ -module.exports = { - async up (queryInterface, Sequelize) { - /** - * Add altering commands here. - * - * Example: - * await queryInterface.createTable('users', { id: Sequelize.INTEGER }); - */ - - 'use strict'; - -module.exports = { - async up(queryInterface, Sequelize) { - await queryInterface.removeColumn('instructors', 'department_id'); - }, - - async down(queryInterface, Sequelize) { - await queryInterface.addColumn('instructors', 'department_id', { - type: Sequelize.INTEGER, - references: { - model: 'departments', - key: 'id' - }, - onUpdate: 'CASCADE', - onDelete: 'SET NULL' - }); - } -} -} -} diff --git a/src/Database/Migrations/20250809014716-add-fk-ProgramCourse-Deparment.js b/src/Database/Migrations/20250809014716-add-fk-ProgramCourse-Deparment.js index 7ebbb51..f6c81ea 100644 --- a/src/Database/Migrations/20250809014716-add-fk-ProgramCourse-Deparment.js +++ b/src/Database/Migrations/20250809014716-add-fk-ProgramCourse-Deparment.js @@ -1,7 +1,7 @@ 'use strict'; /** @type {import('sequelize-cli').Migration} */ -module.exports = { +export default{ async up(queryInterface, Sequelize) { await queryInterface.addConstraint('ProgramCourses', { fields: ['department_id'], diff --git a/src/Database/Migrations/20250831140916-add-Instructor-TimeStamps.js b/src/Database/Migrations/20250831140916-add-Instructor-TimeStamps.js index 1758547..8e11ac5 100644 --- a/src/Database/Migrations/20250831140916-add-Instructor-TimeStamps.js +++ b/src/Database/Migrations/20250831140916-add-Instructor-TimeStamps.js @@ -1,7 +1,7 @@ 'use strict'; /** @type {import('sequelize-cli').Migration} */ -module.exports = { +export default{ async up (queryInterface, Sequelize) { /** * Add altering commands here. diff --git a/src/Database/Migrations/20250909013953-Semesters.js b/src/Database/Migrations/20250909013953-Semesters.js index bc433d3..3ed4e3e 100644 --- a/src/Database/Migrations/20250909013953-Semesters.js +++ b/src/Database/Migrations/20250909013953-Semesters.js @@ -1,7 +1,7 @@ 'use strict'; /** @type {import('sequelize-cli').Migration} */ -module.exports = { +export default{ async up (queryInterface, Sequelize) { /** * Add altering commands here. @@ -77,7 +77,7 @@ module.exports = { * await queryInterface.dropTable('users'); */ - await queryInterface.removeConstraint('Semester', 'uq_program_semester'); + // await queryInterface.removeConstraint('Semester', 'uq_program_semester'); await queryInterface.dropTable('Semester') } }; diff --git a/src/Database/seeders/20250805140906-demo-departments.js b/src/Database/seeders/20250805140906-demo-departments.js index c8b13bd..2b227d2 100644 --- a/src/Database/seeders/20250805140906-demo-departments.js +++ b/src/Database/seeders/20250805140906-demo-departments.js @@ -1,7 +1,6 @@ 'use strict'; -const department = require("../../api/Models/department"); - -module.exports = { +/** @type {import('sequelize-cli').Migration} */ +export default{ async up(queryInterface, Sequelize) { await queryInterface.bulkInsert('Departments', [ { diff --git a/src/Database/seeders/20250806125124-demo-instructors.js b/src/Database/seeders/20250806125124-demo-instructors.js index f853612..84b077b 100644 --- a/src/Database/seeders/20250806125124-demo-instructors.js +++ b/src/Database/seeders/20250806125124-demo-instructors.js @@ -1,7 +1,7 @@ 'use strict'; /** @type {import('sequelize-cli').Migration} */ -module.exports = { +export default{ async up(queryInterface, Sequelize) { await queryInterface.bulkInsert('Instructors', [ { diff --git a/src/Database/seeders/20250807130913-demo-department-instructors.js b/src/Database/seeders/20250807130913-demo-department-instructors.js index 0aad6ed..c7d135e 100644 --- a/src/Database/seeders/20250807130913-demo-department-instructors.js +++ b/src/Database/seeders/20250807130913-demo-department-instructors.js @@ -1,6 +1,6 @@ 'use strict'; - -module.exports = { +/** @type {import('sequelize-cli').Migration} */ +export default { async up(queryInterface, Sequelize) { // Fetch all instructors const [instructors] = await queryInterface.sequelize.query( diff --git a/src/Database/seeders/20250809012319-demo-ProgramCourse.js b/src/Database/seeders/20250809012319-demo-ProgramCourse.js index 0749be6..3ff4436 100644 --- a/src/Database/seeders/20250809012319-demo-ProgramCourse.js +++ b/src/Database/seeders/20250809012319-demo-ProgramCourse.js @@ -1,6 +1,6 @@ 'use strict'; - -module.exports = { +/** @type {import('sequelize-cli').Migration} */ +export default{ async up(queryInterface, Sequelize) { // 1. Find the Computer Science department const [csDept] = await queryInterface.sequelize.query( diff --git a/src/Middlewares/authMiddleware.js b/src/Middlewares/authMiddleware.js index ec05e31..576e1dd 100644 --- a/src/Middlewares/authMiddleware.js +++ b/src/Middlewares/authMiddleware.js @@ -1,16 +1,17 @@ -const jwt = require('jsonwebtoken') +import jwt from 'jsonwebtoken'; -exports.verifyWebToken =(req, res, next)=>{ - const token = res.cookie.token; +export const verifyWebToken = (req, res, next) => { + const token = req.cookies?.token; // corrected from res.cookie.token → req.cookies.token - if(!token){ - return res.status(501).json({message:"No access"}) - } - try { - const decoded = jwt.verify(token, process.env.JWT_SECRET); - req.user = decoded; // attach user data to request - next(); - } catch (err) { - res.status(401).json({ message: 'Invalid or expired token.' }); - } -} \ No newline at end of file + if (!token) { + return res.status(501).json({ message: 'No access' }); + } + + try { + const decoded = jwt.verify(token, process.env.JWT_SECRET); + req.user = decoded; // attach user data to request + next(); + } catch (err) { + res.status(401).json({ message: 'Invalid or expired token.' }); + } +}; diff --git a/src/api/Controllers/DepartmentController.js b/src/api/Controllers/DepartmentController.js index 824ebb2..a8bfda6 100644 --- a/src/api/Controllers/DepartmentController.js +++ b/src/api/Controllers/DepartmentController.js @@ -1,61 +1,54 @@ -const DepartmentService = require('../Services/DepartmentServices'); -const path = require('path'); -const base = path.resolve(__dirname, '../../../'); -const sequelize = require(path.join(base, 'src', 'config', 'db.js')); -const { DataTypes } = require('sequelize'); -const { UpdateProgram } = require('./ProgramCourseController'); -const { error } = require('console'); -const DepartmentModel = require('../Models/department')(sequelize, DataTypes) +import DepartmentService from '../Services/DepartmentServices.js'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import { DataTypes } from 'sequelize'; +import sequelize from '../../../src/Config/db.js'; +import defineDepartmentModel from '../Models/department.js'; -exports.getAllDeparments = async(req, res)=>{ - try{ +// Fix __dirname in ES modules +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); - let {top, page, limit} = req.body +// Initialize model +const DepartmentModel = defineDepartmentModel(sequelize, DataTypes); + +// ✅ Get All Departments +const getAllDepartments = async (req, res) => { + try { + let { top, page, limit } = req.body; - // Convert string query params to numbers top = parseInt(top); page = parseInt(page); limit = parseInt(limit); - let queryOptions = { - order: [['createdAt', 'DESC']] // latest first + const queryOptions = { + order: [['createdAt', 'DESC']], }; if (top) { - // Return only top X records queryOptions.limit = top; - } - else if (page && limit) { - // Apply pagination + } else if (page && limit) { const offset = (page - 1) * limit; queryOptions.limit = limit; queryOptions.offset = offset; } - DepartmentModel.findAll(queryOptions) - .then((departments) => { - res.status(200).json({ - status: 'success', - data: departments.toJson() - }); - }) - .catch((error) => { - res.status(500).json({ - status: 'error', - message: error.message - }); - }); - - }catch(error){ - res.status(error.status || 500).json({ error: error.message || 'Internal Server Error' }); - } -} + const departments = await DepartmentModel.findAll(queryOptions); + res.status(200).json({ + status: 'success', + data: departments, + }); + } catch (error) { + res.status(error.status || 500).json({ error: error.message || 'Internal Server Error' }); + } +}; -exports.getDepartmentByID = async (req, res) => { +// ✅ Get Department by ID +const getDepartmentByID = async (req, res) => { try { const { id } = req.params; - const department = await DepartmentService.getDepartmentByID(id); + const department = await DepartmentService.getDepartmentById(id); if (!department) { return res.status(404).json({ message: "Department not found" }); @@ -68,13 +61,14 @@ exports.getDepartmentByID = async (req, res) => { } }; -exports.createDeparment = async (req, res) => { +// ✅ Create Department +const createDepartment = async (req, res) => { try { - const addDeparment = await DepartmentService.CreateDeparment(req.body); - if(addDeparment){ + const addDepartment = await DepartmentService.CreateDeparment(req.body); + if (addDepartment) { res.status(201).json({ - message: addDeparment.message, - data: addDeparment.data + message: addDepartment.message, + data: addDepartment.department }); } } catch (error) { @@ -82,20 +76,31 @@ exports.createDeparment = async (req, res) => { } }; -exports.UpdateDepartment = async (req, res) => { +// ✅ Update Department +const updateDepartment = async (req, res) => { try { - const update = await DepartmentService.UpdateDepartment(req, res); - res.status(201).json({ message: update.message }); + const update = await DepartmentService.UpdateDepartment(req.body); + res.status(201).json({ message: update }); } catch (error) { res.status(500).json({ message: error.message }); } }; -exports.DeleteDepartment = async(req, res)=>{ - try{ - const deleteDepartment = await DepartmentService.DeleteDepartment(req,res) - }catch(error){ +// ✅ Delete Department +const deleteDepartment = async (req, res) => { + try { + const result = await DepartmentService.DeleteDepartment(req.body.id); + res.status(200).json({ message: result }); + } catch (error) { res.status(500).json({ message: error.message }); } -} +}; +// ✅ Single default export +export default { + getAllDepartments, + getDepartmentByID, + createDepartment, + updateDepartment, + deleteDepartment +}; diff --git a/src/api/Controllers/InstructorController.js b/src/api/Controllers/InstructorController.js index b4b7d1e..db3b05a 100644 --- a/src/api/Controllers/InstructorController.js +++ b/src/api/Controllers/InstructorController.js @@ -1,26 +1,32 @@ -const InstructorService = require('../Services/InstructorServices'); -const path = require('path'); -const base = path.resolve(__dirname, '../../../'); -const sequelize = require(path.join(base, 'src', 'config', 'db.js')); -const { DataTypes } = require('sequelize'); -const { error } = require('console'); -const User = require("../Models/User")(sequelize, DataTypes) -const InstructorModel = require('../Models/instructor')(sequelize, DataTypes) -const { verifyWebToken } = require("../../Middlewares/authMiddleware"); - -exports.CreateInstructor = async (req, res) => { - try { +import InstructorService from "../Services/InstructorServices.js"; +import path from "path"; +import { fileURLToPath } from "url"; +import { DataTypes } from "sequelize"; +import sequelize from "../../Config/db.js"; +import UserModelDefiner from "../Models/user.js"; +import InstructorModelDefiner from "../Models/instructor.js"; +import { verifyWebToken } from "../../Middlewares/authMiddleware.js"; + +// Resolve __dirname (ESM compatible) +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const base = path.resolve(__dirname, "../../../"); - const {id, ...data} = req.body +const User = UserModelDefiner(sequelize, DataTypes); +const Instructor = InstructorModelDefiner(sequelize, DataTypes); +// ✅ Create Instructor +const createInstructor = async (req, res) => { + try { + const { id, ...data } = req.body; const findInstructor = await Instructor.findOne({ where: { user_id: id } }); if (findInstructor) { return res.status(400).json({ - message: "This user already has an instructor profile" + message: "This user already has an instructor profile" }); } - // Create Instructor + const instructor = await InstructorService.CreateInstructor(req.body); return res.status(201).json({ @@ -35,29 +41,44 @@ exports.CreateInstructor = async (req, res) => { }); } }; -exports.UpdateInstructor = async(req, res)=>{ - try{ - const update = await InstructorService.UpdateInstructor(req.param.id, req.body); - - return res.status(201).JSON({ - success: true, - message: update.message - }) - }catch(error ){ - throw error; - } -} -exports.DeleteInstructor = async (req, res) => { - try { - const DeleteInstructor = await InstructorService.DeleteInstructor(req.params.id); - return res.status(200).json({ - success:true, - message: "Instructor deleted successfully" - }); - } catch (error) { - return res.status(500).json({ - message: "Error deleting instructor", - error: error.message - }); - } -}; + +// ✅ Update Instructor +const updateInstructor = async (req, res) => { + try { + const update = await InstructorService.UpdateInstructor(req.params.id, req.body); + + return res.status(201).json({ + success: true, + message: update.message, + }); + } catch (error) { + return res.status(500).json({ + message: "Error updating instructor", + error: error.message, + }); + } +}; + +// ✅ Delete Instructor +const deleteInstructor = async (req, res) => { + try { + await InstructorService.DeleteInstructor(req.params.id); + + return res.status(200).json({ + success: true, + message: "Instructor deleted successfully", + }); + } catch (error) { + return res.status(500).json({ + message: "Error deleting instructor", + error: error.message, + }); + } +}; + +// ✅ Single default export +export default { + createInstructor, + updateInstructor, + deleteInstructor +}; diff --git a/src/api/Controllers/ProgramCourseController.js b/src/api/Controllers/ProgramCourseController.js index 98bd42a..f0fed15 100644 --- a/src/api/Controllers/ProgramCourseController.js +++ b/src/api/Controllers/ProgramCourseController.js @@ -1,17 +1,21 @@ -const ProgramService = require("../Services/ProgramCourseServices") -const path = require('path'); -const base = path.resolve(__dirname, '../../../'); -const sequelize = require(path.join(base, 'src', 'config', 'db.js')); -const { DataTypes } = require('sequelize'); -const ProgramCourse = require('../Models/programcourse')(sequelize, DataTypes) +import ProgramService from "../Services/ProgramCourseServices.js"; +import path from "path"; +import { fileURLToPath } from "url"; +import { DataTypes } from "sequelize"; +import sequelize from "../../../src/Config/db.js"; +import defineProgramCourse from "../Models/programcourse.js"; -exports.createProgram = async (req, res) => { +const ProgramCourse = defineProgramCourse(sequelize, DataTypes); +const __dirname = path.dirname(fileURLToPath(import.meta.url)); + +// ✅ Create Program +const createProgram = async (req, res) => { try { const addProgram = await ProgramService.createProgram(req.body); - if(addProgram){ + if (addProgram) { res.status(201).json({ - message: 'Program Added Successfully', - userId: addProgram.id + message: 'Program Added Successfully', + userId: addProgram.id }); } } catch (error) { @@ -19,25 +23,22 @@ exports.createProgram = async (req, res) => { } }; -exports.getAllPrograms = async (req, res) => { +// ✅ Get All Programs +const getAllPrograms = async (req, res) => { try { let { top, page, limit } = req.body; - // Convert string query params to numbers top = parseInt(top); page = parseInt(page); limit = parseInt(limit); - let queryOptions = { - order: [['createdAt', 'DESC']] // latest first + const queryOptions = { + order: [["createdAt", "DESC"]], }; if (top) { - // Return only top X records queryOptions.limit = top; - } - else if (page && limit) { - // Apply pagination + } else if (page && limit) { const offset = (page - 1) * limit; queryOptions.limit = limit; queryOptions.offset = offset; @@ -46,42 +47,53 @@ exports.getAllPrograms = async (req, res) => { const programs = await ProgramCourse.findAll(queryOptions); res.status(200).json({ - status: 'success', - data: programs + status: "success", + data: programs, }); - } catch (error) { - res.status(500).json({ - status: 'error', - message: error.message + res.status(500).json({ + status: "error", + message: error.message, }); } }; -exports.UpdateProgram = async (req, res) => { + +// ✅ Update Program +const updateProgram = async (req, res) => { try { const result = await ProgramService.UpdateProgram(req.body); - if (result.success) { - res.status(200).json({ - success: true, - message: result.message || "Operation successful", - data: result.data || null - }); - } else { - res.status(result.statusCode || 400).json({ - success: false, - message: result.message || "Operation failed", - error: result.error || null - }); - } - + if (result.success) { + res.status(200).json({ + success: true, + message: result.message || "Operation successful", + data: result.data || null + }); + } else { + res.status(result.statusCode || 400).json({ + success: false, + message: result.message || "Operation failed", + error: result.error || null + }); + } + } catch (error) { res.status(500).json({ message: "Update failed", error: error.message }); - } + } +}; + +// ✅ Delete Program +const deleteProgram = async (id) => { + return await ProgramService.DeleteProgram(id); +}; + +// ✅ Single default export +export default { + createProgram, + getAllPrograms, + updateProgram, + deleteProgram }; -exports.DeleteProgram = async(id)=>{ - return await ProgramService.DeleteProgram(id) -} \ No newline at end of file diff --git a/src/api/Controllers/userController.js b/src/api/Controllers/userController.js index 74fecb8..acc7e81 100644 --- a/src/api/Controllers/userController.js +++ b/src/api/Controllers/userController.js @@ -1,12 +1,13 @@ -const userService = require('../Services/UserService'); +import userService from "../Services/UserService.js"; -exports.registerUser = async (req, res) => { +// ✅ Register User +const registerUser = async (req, res) => { try { const newUser = await userService.registerUser(req.body); - if(newUser){ + if (newUser) { res.status(201).json({ - message: 'User registered successfully!', - userId: newUser.id + message: 'User registered successfully!', + userId: newUser.id }); } } catch (error) { @@ -14,24 +15,30 @@ exports.registerUser = async (req, res) => { } }; -exports.LoginUser = async(req,res)=>{ - try{ - - const isLogin = await userService.LoginUser(req.body) +// ✅ Login User +const loginUser = async (req, res) => { + try { + const isLogin = await userService.LoginUser(req.body); - res.cookie('token', isLogin.token, { - httpOnly: true, - secure: process.env.NODE_ENV === 'production', - sameSite: 'strict', - maxAge: 60 * 60 * 1000 - }); + if (isLogin) { + res.cookie("token", isLogin.token, { + httpOnly: true, + secure: process.env.NODE_ENV === 'production', + sameSite: 'strict', + maxAge: 60 * 60 * 1000 // 1 hour + }); - if(isLogin) { return res.status(200).json({ message: 'Login successful', }); } - }catch(error){ + } catch (error) { res.status(error.status || 500).json({ error: error.message || 'Internal Server Error' }); } -} +}; + +// Export all controller functions as a single default object +export default { + registerUser, + loginUser +}; diff --git a/src/api/Models/department.js b/src/api/Models/department.js index 702375e..09e5bbd 100644 --- a/src/api/Models/department.js +++ b/src/api/Models/department.js @@ -1,54 +1,56 @@ -'use strict'; -const { Model } = require('sequelize'); +import { Model, DataTypes } from 'sequelize'; -module.exports = (sequelize, DataTypes) => { +export default (sequelize) => { class Department extends Model { static associate(models) { - // 2. One department optionally has one department head (an instructor) + // One department optionally has one department head (an instructor) Department.belongsTo(models.Instructor, { foreignKey: 'head_id', - as: 'head' // This lets you do department.getHead() + as: 'head', // Allows department.getHead() }); } } - Department.init({ - department_id: { - type: DataTypes.INTEGER, - primaryKey: true, - autoIncrement: true + Department.init( + { + department_id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + }, + department_name: { + type: DataTypes.STRING(100), + allowNull: false, + }, + department_code: { + type: DataTypes.STRING(20), + allowNull: false, + unique: true, + }, + head_id: { + type: DataTypes.INTEGER, + allowNull: true, + }, + email: { + type: DataTypes.STRING(150), + allowNull: true, + }, + phone_number: { + type: DataTypes.STRING(20), + allowNull: true, + }, + building: { + type: DataTypes.STRING(100), + allowNull: true, + }, }, - department_name: { - type: DataTypes.STRING(100), - allowNull: false - }, - department_code: { - type: DataTypes.STRING(20), - allowNull: false, - unique: true - }, - head_id: { - type: DataTypes.INTEGER, - allowNull: true - }, - email: { - type: DataTypes.STRING(150), - allowNull: true - }, - phone_number: { - type: DataTypes.STRING(20), - allowNull: true - }, - building: { - type: DataTypes.STRING(100), - allowNull: true - }, - }, { - sequelize, - modelName: 'Department', - tableName: 'Departments', - timestamps: false // uses created_at and updated_at - }); + { + sequelize, + modelName: 'Department', + tableName: 'Departments', + timestamps: true, + } + ); return Department; -}; \ No newline at end of file +}; diff --git a/src/api/Models/index.js b/src/api/Models/index.js index a8b2997..7cca4a5 100644 --- a/src/api/Models/index.js +++ b/src/api/Models/index.js @@ -1,35 +1,46 @@ -const fs = require('fs'); -const path = require('path'); -const Sequelize = require('sequelize'); +import fs from 'fs'; +import path from 'path'; +import Sequelize, { DataTypes } from 'sequelize'; +import configFile from '../../Config/config.js'; + +// __dirname replacement for ES modules +const __dirname = path.dirname(new URL(import.meta.url).pathname); const basename = path.basename(__filename); -// Adjust the config path to point to src/config/config.json -const configPath = path.resolve(__dirname, '../../config/config.js'); -const config = require(configPath)['development']; +// Use the correct environment (you can switch to process.env.NODE_ENV if needed) +const config = configFile.development; const db = {}; +// Initialize Sequelize const sequelize = new Sequelize(config.database, config.username, config.password, config); -fs +// Dynamically import all models in this folder +const files = fs .readdirSync(__dirname) - .filter(file => { - return ( + .filter( + (file) => file.indexOf('.') !== 0 && file !== basename && - file.slice(-3) === '.js' - ); - }) - .forEach(file => { - const model = require(path.join(__dirname, file))(sequelize, Sequelize.DataTypes); - db[model.name] = model; - }); - -Object.keys(db).forEach(modelName => { + file.endsWith('.js') + ); + +for (const file of files) { + const { default: modelDefiner } = await import(path.join(__dirname, file)); + const model = modelDefiner(sequelize, DataTypes); + db[model.name] = model; +} + +// Set up associations +for (const modelName of Object.keys(db)) { if (db[modelName].associate) { db[modelName].associate(db); } -}); +} + +db.sequelize = sequelize; + +export default db; db.sequelize = sequelize; // db.Sequelize = Sequelize; diff --git a/src/api/Models/instructor.js b/src/api/Models/instructor.js index 3dcbc61..33929b1 100644 --- a/src/api/Models/instructor.js +++ b/src/api/Models/instructor.js @@ -1,35 +1,32 @@ -'use strict'; -const { - Model -} = require('sequelize'); -const department = require('./department'); -module.exports = (sequelize, DataTypes) => { +import { Model, DataTypes } from 'sequelize'; + +export default (sequelize) => { class Instructor extends Model { - /** - * Helper method for defining associations. - * This method is not a part of Sequelize lifecycle. - * The `models/index` file will call this method automatically. - */ static associate(models) { - // define association here + // Define association here Instructor.belongsTo(models.Department, { - foreignKey: 'department_id', - as: 'department' + foreignKey: 'department_id', + as: 'department', }); } } - Instructor.init({ - first_name: DataTypes.STRING, - last_name: DataTypes.STRING, - email: DataTypes.STRING, - phone_number: DataTypes.STRING, - hire_date: DataTypes.DATE, - department: DataTypes.INTEGER, - is_active: DataTypes.BOOLEAN - }, { - sequelize, - modelName: 'Instructor', - timestamps: true - }); + + Instructor.init( + { + first_name: DataTypes.STRING, + last_name: DataTypes.STRING, + email: DataTypes.STRING, + phone_number: DataTypes.STRING, + hire_date: DataTypes.DATE, + department: DataTypes.INTEGER, + is_active: DataTypes.BOOLEAN, + }, + { + sequelize, + modelName: 'Instructor', + timestamps: true, + } + ); + return Instructor; -}; \ No newline at end of file +}; diff --git a/src/api/Models/programcourse.js b/src/api/Models/programcourse.js index e9220b7..9e09bbc 100644 --- a/src/api/Models/programcourse.js +++ b/src/api/Models/programcourse.js @@ -1,60 +1,57 @@ -'use strict'; -const { - Model -} = require('sequelize'); -module.exports = (sequelize, DataTypes) => { +import { Model, DataTypes } from 'sequelize'; + +export default (sequelize) => { class ProgramCourse extends Model { - /** - * Helper method for defining associations. - * This method is not a part of Sequelize lifecycle. - * The `models/index` file will call this method automatically. - */ static associate(models) { - // define association here - + // Self-referencing association ProgramCourse.belongsTo(models.ProgramCourse, { - as: 'parent', - foreignKey: 'parent_id' + as: 'parent', + foreignKey: 'parent_id', }); ProgramCourse.hasMany(models.ProgramCourse, { - as: 'children', - foreignKey: 'parent_id' + as: 'children', + foreignKey: 'parent_id', }); // Department association ProgramCourse.belongsTo(models.Department, { foreignKey: 'department_id', - as: 'department' + as: 'department', }); } } - ProgramCourse.init({ - parent_id: DataTypes.INTEGER, - type: { - type:DataTypes.ENUM('PROGRAM','COURSE'), - allowNull:false, - defaultValue:null, - }, - code: DataTypes.STRING, - name: DataTypes.STRING, - department_id: DataTypes.INTEGER, - credit_hours: DataTypes.INTEGER, - semester: DataTypes.INTEGER, - level: { - type: DataTypes.ENUM('DIPLOMA','DEGREE','MASTER','DOCTORATE'), - allowNull:false + + ProgramCourse.init( + { + parent_id: DataTypes.INTEGER, + type: { + type: DataTypes.ENUM('PROGRAM', 'COURSE'), + allowNull: false, + defaultValue: null, + }, + code: DataTypes.STRING, + name: DataTypes.STRING, + department_id: DataTypes.INTEGER, + credit_hours: DataTypes.INTEGER, + semester: DataTypes.INTEGER, + level: { + type: DataTypes.ENUM('DIPLOMA', 'DEGREE', 'MASTER', 'DOCTORATE'), + allowNull: false, + }, + is_active: DataTypes.BOOLEAN, + mode: { + type: DataTypes.ENUM('FULLTIME', 'PARTTIME', 'ONLINE', 'HYBRID', 'DISTANCE'), + allowNull: false, + defaultValue: '', + }, }, - is_active: DataTypes.BOOLEAN, - mode: { - type: DataTypes.ENUM('FULLTIME', 'PARTTIME', 'ONLINE', 'HYBRID', 'DISTANCE'), - allowNull: false, // or false if it's required - defaultValue:'' -} - }, { - sequelize, - modelName: 'ProgramCourse', - timestamps: true - }); + { + sequelize, + modelName: 'ProgramCourse', + timestamps: true, + } + ); + return ProgramCourse; -}; \ No newline at end of file +}; diff --git a/src/api/Models/student.js b/src/api/Models/student.js index cb647fa..e978be8 100644 --- a/src/api/Models/student.js +++ b/src/api/Models/student.js @@ -1,39 +1,37 @@ -'use strict'; -const { - Model -} = require('sequelize'); -module.exports = (sequelize, DataTypes) => { +import { Model, DataTypes } from 'sequelize'; + +export default (sequelize) => { class Student extends Model { - /** - * Helper method for defining associations. - * This method is not a part of Sequelize lifecycle. - * The `models/index` file will call this method automatically. - */ static associate(models) { - // define association - Student.belongsTo(models.User, { - foreignKey: 'userId', + // Define association + Student.belongsTo(models.User, { + foreignKey: 'user_id', as: 'user', }); } } - Student.init({ - user_id: DataTypes.INTEGER, - student_number: DataTypes.STRING, - first_name: DataTypes.STRING, - last_name: DataTypes.STRING, - email: DataTypes.STRING, - phone_number: DataTypes.STRING, - date_of_birth: DataTypes.DATE, - gender: DataTypes.STRING, - enrollment_year: DataTypes.INTEGER, - department_id: DataTypes.INTEGER, - is_active: DataTypes.BOOLEAN, - created_at: DataTypes.DATE, - updated_at: DataTypes.DATE - }, { - sequelize, - modelName: 'Student', - }); + + Student.init( + { + user_id: DataTypes.INTEGER, + student_number: DataTypes.STRING, + first_name: DataTypes.STRING, + last_name: DataTypes.STRING, + email: DataTypes.STRING, + phone_number: DataTypes.STRING, + date_of_birth: DataTypes.DATE, + gender: DataTypes.STRING, + enrollment_year: DataTypes.INTEGER, + department_id: DataTypes.INTEGER, + is_active: DataTypes.BOOLEAN, + created_at: DataTypes.DATE, + updated_at: DataTypes.DATE, + }, + { + sequelize, + modelName: 'Student', + } + ); + return Student; -}; \ No newline at end of file +}; diff --git a/src/api/Models/user.js b/src/api/Models/user.js index d4686e7..8e6ade1 100644 --- a/src/api/Models/user.js +++ b/src/api/Models/user.js @@ -1,49 +1,46 @@ -'use strict'; -const { Model, DataTypes} = require('sequelize'); - -module.exports = (sequelize, DataTypes) => { +import { Model, DataTypes } from 'sequelize'; +export default (sequelize) => { class User extends Model { - /** - * Helper method for defining associations. - * This method is not a part of Sequelize lifecycle. - * The `models/index` file will call this method automatically. - */ static associate(models) { - // define association here + // Define associations here (if needed) } } - User.init({ - username: { - allowNull: false, - unique: true, - type: DataTypes.STRING, - }, - email: { - allowNull: false, - unique: true, - type: DataTypes.STRING, - }, - password: { - type: DataTypes.STRING, - }, - role: { - type: DataTypes.ENUM("STUDENT", "ADMIN", "INSTRUCTOR"), - allowNull: false, - defaultValue: "STUDENT" - }, - is_active: { - type: DataTypes.BOOLEAN, - }, - lastLogin: { - type: DataTypes.DATE, - allowNull: true, + + User.init( + { + username: { + allowNull: false, + unique: true, + type: DataTypes.STRING, + }, + email: { + allowNull: false, + unique: true, + type: DataTypes.STRING, + }, + password: { + type: DataTypes.STRING, + }, + role: { + type: DataTypes.ENUM('STUDENT', 'ADMIN', 'INSTRUCTOR'), + allowNull: false, + defaultValue: 'STUDENT', + }, + is_active: { + type: DataTypes.BOOLEAN, + }, + lastLogin: { + type: DataTypes.DATE, + allowNull: true, + }, }, - }, { - sequelize, - modelName: 'User', - timestamps: true, - }); + { + sequelize, + modelName: 'User', + timestamps: true, + } + ); return User; }; diff --git a/src/api/Routes/DepartmentRoutes.js b/src/api/Routes/DepartmentRoutes.js index b0f8b6f..2dd078a 100644 --- a/src/api/Routes/DepartmentRoutes.js +++ b/src/api/Routes/DepartmentRoutes.js @@ -1,21 +1,25 @@ -const express = require('express'); +import express from 'express'; +import DepartmentController from '../Controllers/DepartmentController.js'; +import { Sequelize, DataTypes } from 'sequelize'; + const router = express.Router(); -const DeparmentController = require('../Controllers/DepartmentController') -const { Sequelize, DataTypes } = require('sequelize'); -router.get('/', (req, res)=>{ - res.status(200).send("Welcome To Deparment API"); -}) +router.get('/', (req, res) => { + res.status(200).send('Welcome To Department API'); +}); + +router.get('/listing', async (req, res) => { + await DepartmentController.getAllDeparments(req, res); +}); -router.get('/listing', async(req, res)=>{ - await DeparmentController.getAllDeparments(req,res) -}) -// POST /api/deparment (create program) +// POST /api/department (create department) router.post('/', async (req, res) => { - await DeparmentController.createDeparment(req, res); + await DepartmentController.createDeparment(req, res); }); -// PUT /api/deparment (create program) + +// PUT /api/department (update department) router.put('/', async (req, res) => { - await DeparmentController.UpdateDepartment(req,res) + await DepartmentController.UpdateDepartment(req, res); }); -module.exports = router \ No newline at end of file + +export default router; diff --git a/src/api/Routes/InstructorRoutes.js b/src/api/Routes/InstructorRoutes.js index 1254035..dd42aa4 100644 --- a/src/api/Routes/InstructorRoutes.js +++ b/src/api/Routes/InstructorRoutes.js @@ -1,26 +1,33 @@ -const express = require('express'); +import express from 'express'; +import InstructorController from '../Controllers/InstructorController.js'; +import validationId from '../Validators/RequiredID.js'; +import validationBody from '../Validators/RequiredBody.js'; + const router = express.Router(); -const InstructorController = require('../Controllers/InstructorController') -const validationId = require('../Validators/RequiredID'); -const validationBody = require('../Validators/RequiredBody') -// GET /api/programs + +// GET /api/instructor/home router.get('/home', (req, res) => { - res.status(200).send("Welcome To Instructor API"); + res.status(200).send('Welcome To Instructor API'); +}); + +// GET /api/instructor +router.get('/', async (req, res) => { + // await InstructorController.getAllInstructor(req, res); }); -router.get('/', async(req, res)=>{ - // await InstructorController.getAllInstructor(req,res) -}) -// POST /api/deparment (create program) +// POST /api/instructor (create instructor) router.post('/', async (req, res) => { await InstructorController.CreateInstructor(req, res); }); -// PUT /api/deparment (create program) + +// PUT /api/instructor/:id (update instructor) router.put('/:id', [...validationId, ...validationBody], async (req, res) => { - await InstructorController.UpdateInstructor(req,res) + await InstructorController.UpdateInstructor(req, res); }); +// DELETE /api/instructor/:id (delete instructor) router.delete('/:id', [...validationId, ...validationBody], async (req, res) => { - await InstructorController.DeleteInstructor(req,res) + await InstructorController.DeleteInstructor(req, res); }); -module.exports = router \ No newline at end of file + +export default router; diff --git a/src/api/Routes/ProgramRoutes.js b/src/api/Routes/ProgramRoutes.js index 3bfc93b..72d9d62 100644 --- a/src/api/Routes/ProgramRoutes.js +++ b/src/api/Routes/ProgramRoutes.js @@ -1,35 +1,38 @@ -const express = require('express'); +import express from 'express'; +import programController from '../Controllers/ProgramCourseController.js'; +import { Sequelize, DataTypes } from 'sequelize'; + const router = express.Router(); -const programController = require('../Controllers/ProgramCourseController') -const { Sequelize, DataTypes } = require('sequelize'); // GET /api/programs router.get('/', (req, res) => { - res.status(200).send("Welcome To Program API"); + res.status(200).send('Welcome To Program API'); +}); + +// GET /api/programs/listing +router.get('/listing', async (req, res) => { + await programController.getAllPrograms(req, res); }); -router.get("/listing", async(req,res)=>{ - await programController.getAllPrograms(req,res); -}) -// POST /api/programs (create program) +// POST /api/programs/create (create program) router.post('/create', async (req, res) => { await programController.createProgram(req, res); }); +// PUT /api/programs/update (update program) router.put('/update', async (req, res) => { - await programController.UpdateProgram(req,res) + await programController.UpdateProgram(req, res); }); -// DELETE /delete/:id -router.delete("/delete", async (req, res) => { - try { - const {id} = req.body; // ✅ from body - const result = await programController.DeleteProgram(id); - res.status(200).json(result); - } catch (error) { - res.status(error.status || 500).json({ message: error.message }); - } +// DELETE /api/programs/delete (delete program) +router.delete('/delete', async (req, res) => { + try { + const { id } = req.body; // ✅ from body + const result = await programController.DeleteProgram(id); + res.status(200).json(result); + } catch (error) { + res.status(error.status || 500).json({ message: error.message }); + } }); - -module.exports = router \ No newline at end of file +export default router; \ No newline at end of file diff --git a/src/api/Routes/userRoute.js b/src/api/Routes/userRoute.js index 29bef73..18bf019 100644 --- a/src/api/Routes/userRoute.js +++ b/src/api/Routes/userRoute.js @@ -1,34 +1,39 @@ -const express = require('express'); +import express from 'express'; +import userController from '../Controllers/userController.js'; const router = express.Router(); -const userController = require('../Controllers/userController'); -const { json } = require('sequelize'); -// POST /api/users/register -router.get("/", (req, res) => { - res.status(200).send("Welcome To User API"); +// GET /api/users +router.get('/', (req, res) => { + res.status(200).send('Welcome To User API'); }); + +// POST /api/users/register router.post('/register', async (req, res) => { - await userController.registerUser(req, res); + await userController.registerUser(req, res); }); -router.post('/login', async(req,res)=>{ - await userController.LoginUser(req,res); -}) +// POST /api/users/login +router.post('/login', async (req, res) => { + await userController.LoginUser(req, res); +}); +// POST /api/users/logout router.post('/logout', async (req, res) => { res.clearCookie('token', { httpOnly: true, secure: process.env.NODE_ENV === 'production', - sameSite: 'strict' + sameSite: 'strict', }); res.status(200).json({ message: 'Logged out successfully' }); }); -router.get("/testProtectRoutes", (req, res) => { - console.log("Token from cookie:", req.cookies.token); // ✅ This prints to your terminal - res.status(200).json({ - message: "Protected route accessed", - token: req.cookies.token +// GET /api/users/testProtectRoutes +router.get('/testProtectRoutes', (req, res) => { + console.log('Token from cookie:', req.cookies.token); // ✅ Logs token to terminal + res.status(200).json({ + message: 'Protected route accessed', + token: req.cookies.token, }); }); -module.exports = router; + +export default router; diff --git a/src/api/Services/DepartmentServices.js b/src/api/Services/DepartmentServices.js index f817080..4f44dcc 100644 --- a/src/api/Services/DepartmentServices.js +++ b/src/api/Services/DepartmentServices.js @@ -1,86 +1,104 @@ -const path = require('path'); -const base = path.resolve(__dirname, '../../../'); -const sequelize = require(path.join(base, 'src', 'config', 'db.js')); -const { DataTypes } = require('sequelize'); -const { error } = require('console'); -const { DeleteDepartment } = require('../Controllers/DepartmentController'); -const DepartmentModel= require('../Models/department')(sequelize, DataTypes); - -exports.getDepartmentById = async (id) => { +import path from 'path'; +import { fileURLToPath } from 'url'; +import { DataTypes } from 'sequelize'; +import sequelize from '../../Config/db.js'; +import DepartmentModelFactory from '../Models/department.js'; + +// Required to simulate __dirname in ES modules +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +// Initialize model +const DepartmentModel = DepartmentModelFactory(sequelize, DataTypes); + +// 🟩 Get Department By ID +const getDepartmentById = async (id) => { if (!id) { - throw new Error("Department ID is required"); + throw new Error('Department ID is required'); } const department = await DepartmentModel.findByPk(id); return department; // null if not found }; -exports.CreateDeparment = async (departmentData) => { + +// 🟩 Create Department +const createDepartment = async (departmentData) => { try { const department = await DepartmentModel.create(departmentData); if (!department) { - throw new Error("Department creation failed"); + throw new Error('Department creation failed'); } return { - message: "Department created successfully", - department: department.toJSON() + message: 'Department created successfully', + department: department.toJSON(), }; } catch (error) { throw error; } }; -exports.UpdateDepartment = async (departmentData) => { - try{ + +// 🟩 Update Department +const updateDepartment = async (departmentData) => { + try { const { department_id, ...updateData } = departmentData; if (department_id === undefined) { - throw new Error("ID must be provided"); + throw new Error('ID must be provided'); } if (Object.keys(updateData).length === 0) { - throw new Error("At-Least One Department fields must be provided to update"); + throw new Error('At least one department field must be provided to update'); } - const findExistingDeparment = await DepartmentModel.findOne({ - where: { department_id} + const findExistingDepartment = await DepartmentModel.findOne({ + where: { department_id }, }); - if(!findExistingDeparment){ - throw new Error("No Deparment found with the given department id") + if (!findExistingDepartment) { + throw new Error('No Department found with the given department ID'); } const [updatedRows] = await DepartmentModel.update(updateData, { - where: {department_id} + where: { department_id }, }); if (updatedRows > 0) { - return "Department updated successfully"; + return 'Department updated successfully'; } else { - throw new Error("No Department found with the given ID") + throw new Error('No Department found with the given ID'); } - }catch(error){ - throw error + } catch (error) { + throw error; } }; -exports.DeleteDepartment = async (id) => { +// 🟩 Delete Department +const deleteDepartment = async (id) => { try { if (id === undefined) { - throw new Error("ID must be provided"); + throw new Error('ID must be provided'); } const deletedRows = await DepartmentModel.destroy({ - where: { department_id: id } + where: { department_id: id }, }); if (deletedRows > 0) { - return "Department deleted successfully"; + return 'Department deleted successfully'; } else { - throw new Error("No department found with the given ID") + throw new Error('No department found with the given ID'); } } catch (error) { throw error; } }; +// ✅ Single default export +export default { + getDepartmentById, + createDepartment, + updateDepartment, + deleteDepartment +}; diff --git a/src/api/Services/InstructorServices.js b/src/api/Services/InstructorServices.js index bd0e4fe..2d3387f 100644 --- a/src/api/Services/InstructorServices.js +++ b/src/api/Services/InstructorServices.js @@ -1,75 +1,97 @@ -const { error } = require('console'); -const path = require('path'); -const base = path.resolve(__dirname, '../../../'); -const sequelize = require(path.join(base, 'src', 'config', 'db.js')); -const { DataTypes } = require('sequelize'); -const Instructor = require('../Models/instructor')(sequelize, DataTypes) -const ProgramCourse = require("../Models/programcourse")(sequelize, DataTypes) +import path from 'path'; +import { fileURLToPath } from 'url'; +import { DataTypes } from 'sequelize'; +import sequelize from '../../Config/db.js'; +import InstructorModelFactory from '../Models/instructor.js'; +import ProgramCourseModelFactory from '../Models/programcourse.js'; +// 🔧 Setup __dirname for ES modules +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); -exports.CreateInstructor= async(InstructorData)=>{ - try{ +// 🧩 Initialize models +const Instructor = InstructorModelFactory(sequelize, DataTypes); +const ProgramCourse = ProgramCourseModelFactory(sequelize, DataTypes); + +// 🟩 Create Instructor +const createInstructor = async (InstructorData) => { + try { const instructor = await Instructor.create(InstructorData); - + return { success: true, - message: "Instructor created successfully", - data: instructor.toJSON() + message: 'Instructor created successfully', + data: instructor.toJSON(), }; - }catch(error){ + } catch (error) { throw error; } -} -exports.UpdateInstructor = async (id,InstructorData) => { +}; + +// 🟩 Update Instructor +const updateInstructor = async (id, InstructorData) => { try { - // Find instructor by primary key (id) const instructor = await Instructor.findByPk(id); if (!instructor) { - const error = new Error("Instructor not found with the given id"); - error.status = 404; // attach custom property + const error = new Error('Instructor not found with the given id'); + error.status = 404; throw error; } - // Update instructor with new data - const update = await instructor.update(InstructorData); - - if(update != undefined){ - return { + const updatedInstructor = await instructor.update(InstructorData); + + if (updatedInstructor) { + return { success: true, - message: "Instructor updated successfully", - data: updateFields.toJSON() - }; + message: 'Instructor updated successfully', + data: updatedInstructor.toJSON(), + }; + } else { + throw new Error('Instructor update failed'); } } catch (error) { throw error; } }; -exports.DeleteInstructor = async(InstructorData)=>{ - try{ - - // Check courses - const courseCount = await ProgramCourse.count({ where: { instructor_Id: id, type: 'course'} }); +// 🟩 Delete Instructor +const deleteInstructor = async (id) => { + try { + // Check assigned courses + const courseCount = await ProgramCourse.count({ + where: { instructor_Id: id, type: 'course' }, + }); - if (courseCount > 0) { - throw new Error("Cannot delete instructor: assigned to courses"); - } + if (courseCount > 0) { + throw new Error('Cannot delete instructor: assigned to courses'); + } - // Check programs - const programs = await Instructor.findByPk(id, { include: Program }); + // Check assigned programs + const programs = await Instructor.findByPk(id, { include: 'Programs' }); - if (programs.Programs && programs.Programs.length > 0) { - throw new Error("Cannot delete instructor: assigned to programs"); - } + if (programs?.Programs?.length > 0) { + throw new Error('Cannot delete instructor: assigned to programs'); + } - const deleteInstructor = await Instructor.destroy(InstructorData) + const deletedRows = await Instructor.destroy({ where: { instructor_Id: id } }); - return{ - success:true, - message: "Instructor Deleted Successfully" - } - }catch(error){ - throw error; + if (deletedRows > 0) { + return { + success: true, + message: 'Instructor deleted successfully', + }; + } else { + throw new Error('Instructor not found or already deleted'); } -} + } catch (error) { + throw error; + } +}; + +// ✅ Export all functions as a single default object +export default { + createInstructor, + updateInstructor, + deleteInstructor +}; diff --git a/src/api/Services/ProgramCourseServices.js b/src/api/Services/ProgramCourseServices.js index 8be1980..6a9e917 100644 --- a/src/api/Services/ProgramCourseServices.js +++ b/src/api/Services/ProgramCourseServices.js @@ -1,22 +1,29 @@ -const path = require('path'); -const base = path.resolve(__dirname, '../../../'); -const sequelize = require(path.join(base, 'src', 'config', 'db.js')); -const { DataTypes } = require('sequelize'); -const ProgramCourse = require('../Models/programcourse')(sequelize, DataTypes) -exports.createProgram = async (programData) => { +import path from 'path'; +import { fileURLToPath } from 'url'; +import { DataTypes } from 'sequelize'; +import sequelize from '../../Config/db.js'; +import ProgramCourseModelFactory from '../Models/programcourse.js'; + +// 🔧 Setup __dirname for ES modules +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +// 🧩 Initialize model +const ProgramCourse = ProgramCourseModelFactory(sequelize, DataTypes); + +// 🟩 Create Program +const createProgram = async (programData) => { try { - // Check if program with same code already exists const existingProgram = await ProgramCourse.findOne({ - where: { code: programData.code } + where: { code: programData.code }, }); if (existingProgram) { const error = new Error('Program code already exists'); error.status = 400; throw error; - } + } - // Create new program const newProgram = await ProgramCourse.create({ parent_id: null, type: 'PROGRAM', @@ -27,7 +34,7 @@ exports.createProgram = async (programData) => { semester: programData.semester || null, level: programData.level, is_active: programData.is_active ?? true, - mode: programData.mode || 'FULLTIME' + mode: programData.mode || 'FULLTIME', }); return newProgram; @@ -36,39 +43,50 @@ exports.createProgram = async (programData) => { } }; -exports.DeleteProgram = async (id) => { +// 🟩 Delete Program +const deleteProgram = async (id) => { + try { const findProgram = await ProgramCourse.findOne({ where: { id } }); if (!findProgram) { - const error = new Error("Program cannot be found"); - error.status = 404; - throw error; + const error = new Error('Program cannot be found'); + error.status = 404; + throw error; } await findProgram.destroy(); - - return { message: "Program deleted successfully" }; + return { message: 'Program deleted successfully' }; + } catch (error) { + throw error; + } }; -exports.UpdateProgram = async (programData) => { +// 🟩 Update Program +const updateProgram = async (programData) => { try { const { id, ...updateFields } = programData; if (!id) { - throw new Error("Program ID is required to update"); + throw new Error('Program ID is required to update'); } const [rowsUpdated] = await ProgramCourse.update(updateFields, { - where: { id } + where: { id }, }); if (rowsUpdated === 0) { - return { success: false, message: "No program found with the given ID" }; + return { success: false, message: 'No program found with the given ID' }; } - return { success: true, message: "Program updated successfully" }; + return { success: true, message: 'Program updated successfully' }; } catch (error) { return { success: false, message: error.message }; } }; +// ✅ Single default export +export default { + createProgram, + updateProgram, + deleteProgram +}; diff --git a/src/api/Services/StudentService.js b/src/api/Services/StudentService.js index e707c32..3de2fd0 100644 --- a/src/api/Services/StudentService.js +++ b/src/api/Services/StudentService.js @@ -1,14 +1,19 @@ +import bcrypt from "bcrypt"; +import path from "path"; +import { fileURLToPath } from "url"; +import { DataTypes } from "sequelize"; +import sequelize from "../../Config/db.js"; +import StudentModel from "../Models/Student.js"; +import User from "../Models/user.js"; -const bcrypt = require("bcrypt"); -const path = require('path'); -const base = path.resolve(__dirname, '../../../'); -const sequelize = require(path.join(base, 'src', 'config', 'db.js')); -const { DataTypes } = require('sequelize'); -const user = require("../Models/user"); -const Student = require('../Models/Student')(sequelize, DataTypes); +// Resolve __dirname for ES modules +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const Student = StudentModel(sequelize, DataTypes); -exports.createStudent = async (studentData) => { +// 🟩 Create Student +const createStudent = async (studentData) => { try { const newStudent = await Student.create(studentData); return newStudent; @@ -17,7 +22,8 @@ exports.createStudent = async (studentData) => { } }; -exports.getAllStudents = async () => { +// 🟩 Get All Students +const getAllStudents = async () => { try { const students = await Student.findAll(); return students; @@ -26,11 +32,12 @@ exports.getAllStudents = async () => { } }; -exports.getStudentById = async (id) => { +// 🟩 Get Student By ID +const getStudentById = async (id) => { try { const student = await Student.findByPk(id); if (!student) { - const error = new Error('Student not found'); + const error = new Error("Student not found"); error.status = 404; throw error; } @@ -40,11 +47,12 @@ exports.getStudentById = async (id) => { } }; -exports.updateStudent = async (id, updatedData) => { +// 🟩 Update Student +const updateStudent = async (id, updatedData) => { try { const student = await Student.findByPk(id); if (!student) { - const error = new Error('Student not found'); + const error = new Error("Student not found"); error.status = 404; throw error; } @@ -56,40 +64,51 @@ exports.updateStudent = async (id, updatedData) => { } }; -exports.deleteStudent = async (id) => { +// 🟩 Delete Student +const deleteStudent = async (id) => { try { const deleted = await Student.destroy({ where: { student_id: id } }); if (!deleted) { - const error = new Error('Student not found or already deleted'); + const error = new Error("Student not found or already deleted"); error.status = 404; throw error; } - return { message: 'Student deleted successfully' }; + return { message: "Student deleted successfully" }; } catch (error) { throw error; } }; -exports.changePassword = async(email, password, confirmpass) =>{ - try{ - +// 🟩 Change Password +const changePassword = async (email, password, confirmpass) => { + try { if (password !== confirmpass) { - throw new Error('Passwords do not match'); + throw new Error("Passwords do not match"); } + const hashedPassword = await bcrypt.hash(password, 10); - // Update user by email - const [updatedRows] = await user.update( + const [updatedRows] = await User.update( { password: hashedPassword }, { where: { email } } ); if (updatedRows === 0) { - throw new Error('User not found or password not updated'); + throw new Error("User not found or password not updated"); } - return { message: 'Password updated successfully' }; - }catch(error){ + return { message: "Password updated successfully" }; + } catch (error) { throw error; } -} \ No newline at end of file +}; + +// ✅ Single default export +export default { + createStudent, + getAllStudents, + getStudentById, + updateStudent, + deleteStudent, + changePassword +}; diff --git a/src/api/Services/UserService.js b/src/api/Services/UserService.js index 328e666..58847a3 100644 --- a/src/api/Services/UserService.js +++ b/src/api/Services/UserService.js @@ -1,70 +1,92 @@ -const bcrypt = require("bcrypt"); -const path = require('path'); -const base = path.resolve(__dirname, '../../../'); -const sequelize = require(path.join(base, 'src', 'config', 'db.js')); -const { DataTypes } = require('sequelize'); -const jwt = require('jsonwebtoken'); -const User = require('../Models/user')(sequelize, DataTypes); +import bcrypt from "bcrypt"; +import jwt from "jsonwebtoken"; +import path from "path"; +import { fileURLToPath } from "url"; +import { DataTypes } from "sequelize"; +import sequelize from "../../Config/db.js"; +import UserModel from "../Models/user.js"; -exports.registerUser = async (body) => { +// Resolve __dirname for ES module +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +const User = UserModel(sequelize, DataTypes); + +// ✅ Register User +const registerUser = async (body) => { try { - // Handle user registration - const username = body.username; - const email = body.email; - let password = body.password; + const { username, email, password } = body; - // Call DB or validation logic + // Check existing user const existingUser = await User.findOne({ where: { email } }); if (existingUser) { - const error = new Error('Email already in use'); + const error = new Error("Email already in use"); error.status = 409; throw error; } - const hashedPassword = await bcrypt.hash(password, 10) - password = hashedPassword - const newUser = await User.create({ username, email, password }); - return newUser; // controller will format and send response - + // Hash password + const hashedPassword = await bcrypt.hash(password, 10); + + // Create new user + const newUser = await User.create({ + username, + email, + password: hashedPassword, + }); + + return newUser; } catch (error) { throw error; } }; -exports.LoginUser = async(body)=>{ - try{ - - const email = body. email; - const password = body.password; - const findUser = await User.findOne({ - where: { email } - }); - - const validatePassword = await bcrypt.compare(password, findUser.password) +// ✅ Login User +const loginUser = async (body) => { + try { + const { email, password } = body; - if(!findUser){ - const error = new Error('Email does not exist'); + // Find user + const findUser = await User.findOne({ where: { email } }); + if (!findUser) { + const error = new Error("Email does not exist"); error.status = 401; - throw error + throw error; } - if(!validatePassword){ - const error = new Error('User Account does not exist. Please Register your account' ) + + // Validate password + const validatePassword = await bcrypt.compare(password, findUser.password); + if (!validatePassword) { + const error = new Error( + "Invalid credentials. Please check your password or register an account." + ); error.status = 400; - throw error + throw error; } - const token = jwt.sign({ id: findUser.id, email: findUser.email }, process.env.JWT_SECRET, { - expiresIn: '1h' - }); + // Generate token + const token = jwt.sign( + { id: findUser.id, email: findUser.email }, + process.env.JWT_SECRET, + { expiresIn: "1h" } + ); + + // Return token + sanitized user + const { password: _, ...userData } = findUser.toJSON(); - // Return both token and user data (excluding password) return { + success: true, + message: "Login successful", token, - findUser + user: userData, }; - - }catch(error){ + } catch (error) { throw error; } -} +}; +// ✅ Single default export +export default { + registerUser, + loginUser +}; diff --git a/src/api/Validators/RequiredBody.js b/src/api/Validators/RequiredBody.js index 90ec6af..5cdd112 100644 --- a/src/api/Validators/RequiredBody.js +++ b/src/api/Validators/RequiredBody.js @@ -1,4 +1,4 @@ -const { body, validationResult } = require('express-validator'); +import { body, validationResult } from 'express-validator'; const requiredBody = [ body().custom((value, { req }) => { @@ -8,4 +8,5 @@ const requiredBody = [ return true; }) ]; -module.exports = requiredBody \ No newline at end of file + +export default requiredBody; diff --git a/src/api/Validators/RequiredID.js b/src/api/Validators/RequiredID.js index 3386cef..f3fb66f 100644 --- a/src/api/Validators/RequiredID.js +++ b/src/api/Validators/RequiredID.js @@ -1,4 +1,4 @@ -const { param, validationResult } = require('express-validator'); +import { param, validationResult } from 'express-validator'; const RequiredId = [ // Check the id param @@ -19,4 +19,4 @@ const RequiredId = [ } ]; -module.exports = RequiredId; +export default RequiredId; diff --git a/tests/Instructor.test.js b/tests/Instructor.test.js index db83671..0e35ac5 100644 --- a/tests/Instructor.test.js +++ b/tests/Instructor.test.js @@ -1,106 +1,94 @@ -// tests/DepartmentService.test.js -import { describe, it, expect, beforeEach, vi } from 'vitest'; -import { options } from '../src/api/Routes/InstructorRoutes'; -import { readSync } from 'fs'; -const path = require('path'); -const base = path.resolve(__dirname, '../'); -const sequelize = require(path.join(base, 'src', 'config', 'db.js')); -const { DataTypes } = require('sequelize'); -const InstructorModel = require('../src/api/Models/instructor')(sequelize, DataTypes); -const InstructorService = require('../src/api/Services/InstructorServices'); -const newInstructor = undefined - -describe("Instructor Create", ()=>{ - const mockInstructorData = { - first_name: 'Kirukkan', - last_name: 'Kirruki', - email: 'Kirrukki@gmail.com', - phone_number: '017-7454678', - hire_date: Date.now(), - department: null - }; - - beforeEach(() => { - // reset all mocks before each test - vi.restoreAllMocks(); - }); - - it('should create a successfully', async () => { - // Mock Sequelize create method - // vi.spyOn(DepartmentModel, 'create').mockResolvedValue(null); - - const result = await InstructorService.CreateInstructor(mockInstructorData); - - - expect(result).toEqual({ - success: true, - message: "Instructor created successfully", - data: result.data, - }); - // expect(DepartmentModel.create).toHaveBeenCalledWith(mockDepartmentData); - }); -}) - -describe('test update method of', ()=>{ - const mockInstructorData = { - first_name: 'Kiruskkan', - last_name: 'Kirruki', - email: 'Kirrukki@gmais.com', - phone_number: '017-7454678', - hire_date: Date.now(), - department: null - }; - - beforeEach(() => { - // reset all mocks before each test - vi.restoreAllMocks(); - }); - - it('should throw error when Instructor not found with the given id', async () => { - // Mock Sequelize findByPk to return null - vi.spyOn(InstructorModel, 'findByPk').mockResolvedValue(null); - - // Call service and expect rejection - await expect( - InstructorService.UpdateInstructor(999, { name: 'Mock Instructor' }) - ).rejects.toThrow("Instructor not found with the given id"); - - // Optional: also check the custom status property - await expect( - InstructorService.UpdateInstructor(999, { name: 'Mock Instructor' }) - ).rejects.toMatchObject({ status: 404 }); - }); - - - it("should succesfull update the Instructor Data"), async() =>{ - const newInstructor = await InstructorModel.create({ - first_name: 'Maths Teacher', - last_name: 'Kayalvizhi', - email: 'Kayal@gmais.com', - phone_number: '017-7454678', - hire_date: Date.now(), - department: null +// tests/InstructorService.test.js +import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; +import path from 'path'; +import sequelize from '../src/Config/db.js'; +import { DataTypes } from 'sequelize'; +import InstructorModelFactory from '../src/api/Models/instructor.js'; +import * as InstructorService from '../src/api/Services/InstructorServices.js'; + +const InstructorModel = InstructorModelFactory(sequelize, DataTypes); +let newInstructor; + +describe("Instructor Create", () => { + const mockInstructorData = { + first_name: 'Kirukkan', + last_name: 'Kirruki', + email: 'Kirrukki@gmail.com', + phone_number: '017-7454678', + hire_date: Date.now(), + department: null + }; + + beforeEach(() => { + vi.restoreAllMocks(); + }); + + it('should create successfully', async () => { + const result = await InstructorService.CreateInstructor(mockInstructorData); + + expect(result).toEqual({ + success: true, + message: "Instructor created successfully", + data: result.data, }); + }); +}); + +describe("Instructor Update", () => { + const mockInstructorData = { + first_name: 'Kiruskkan', + last_name: 'Kirruki', + email: 'Kirrukki@gmais.com', + phone_number: '017-7454678', + hire_date: Date.now(), + department: null + }; + + beforeEach(() => { + vi.restoreAllMocks(); + }); + + it('should throw error when instructor not found with the given id', async () => { + vi.spyOn(InstructorModel, 'findByPk').mockResolvedValue(null); + + await expect( + InstructorService.UpdateInstructor(999, { name: 'Mock Instructor' }) + ).rejects.toThrow("Instructor not found with the given id"); + + await expect( + InstructorService.UpdateInstructor(999, { name: 'Mock Instructor' }) + ).rejects.toMatchObject({ status: 404 }); + }); + + it("should successfully update the instructor data", async () => { + newInstructor = await InstructorModel.create({ + first_name: 'Maths Teacher', + last_name: 'Kayalvizhi', + email: 'Kayal@gmais.com', + phone_number: '017-7454678', + hire_date: Date.now(), + department: null + }); + + const mockInstructorId = newInstructor.id; + + const result = await InstructorService.UpdateInstructor(mockInstructorId, mockInstructorData); - const mockInstructorId = newInstructor.id; // 👈 real generated id - - const result = await InstructorService.UpdateInstructor(mockInstructorData); expect(result).toEqual({ - success:true, + success: true, message: "Instructor updated successfully", data: expect.objectContaining(mockInstructorData) }); - console.log(result) - // Fetch from DB again to ensure it's really updated - const updatedInstructor = await Instructor.findByPk(mockInstructorId); + const updatedInstructor = await InstructorModel.findByPk(mockInstructorId); expect(updatedInstructor).not.toBeNull(); expect(updatedInstructor.toJSON()).toEqual(expect.objectContaining(mockInstructorData)); + }); + + afterEach(async () => { + if (newInstructor) { + await InstructorModel.destroy({ where: { id: newInstructor.id } }); + newInstructor = undefined; } - afterEach(async () => { - // Delete the test record to clean DB - if (newInstructor) { - await InstructorModel.destroy({ where: { id: newInstructor.id } }); - } - }); -}) \ No newline at end of file + }); +}); diff --git a/tests/department_integration.test.js b/tests/department_integration.test.js new file mode 100644 index 0000000..afb462c --- /dev/null +++ b/tests/department_integration.test.js @@ -0,0 +1,63 @@ +import { describe, it, expect, beforeAll, afterAll } from 'vitest'; +import request from 'supertest'; +import express from 'express'; +import DepartmentRouter from '../Routes/DepartmentRoute.js'; +import sequelize from '../Config/db.js'; // Your Sequelize instance +import DepartmentModelFactory from '../Models/department.js'; +import { DataTypes } from 'sequelize'; + +// Setup test app +const app = express(); +app.use(express.json()); +app.use('/api/department', DepartmentRouter); + +// Initialize Department model for testing +const Department = DepartmentModelFactory(sequelize, DataTypes); + +beforeAll(async () => { + // Sync database (force: true to reset) + await sequelize.sync({ force: true }); +}); + +afterAll(async () => { + // Close DB connection after tests + await sequelize.close(); +}); + +describe('Department Routes Integration Test', () => { + let departmentId; + + it('GET /api/department should return welcome message', async () => { + const res = await request(app).get('/api/department'); + expect(res.status).toBe(200); + expect(res.text).toBe('Welcome To Department API'); + }); + + it('POST /api/department should create a new department', async () => { + const res = await request(app) + .post('/api/department') + .send({ name: 'HR', description: 'Human Resources' }); + + expect(res.status).toBe(201); + expect(res.body).toHaveProperty('id'); + expect(res.body.name).toBe('HR'); + + departmentId = res.body.id; + }); + + it('GET /api/department/listing should return all departments', async () => { + const res = await request(app).get('/api/department/listing'); + expect(res.status).toBe(200); + expect(Array.isArray(res.body)).toBe(true); + expect(res.body.length).toBeGreaterThan(0); + }); + + it('PUT /api/department should update a department', async () => { + const res = await request(app) + .put('/api/department') + .send({ id: departmentId, name: 'HR Updated', description: 'Updated Description' }); + + expect(res.status).toBe(200); + expect(res.body.name).toBe('HR Updated'); + }); +}); diff --git a/tests/department_test.js b/tests/department_test.test.js similarity index 64% rename from tests/department_test.js rename to tests/department_test.test.js index 40d61a6..288f16b 100644 --- a/tests/department_test.js +++ b/tests/department_test.test.js @@ -1,11 +1,12 @@ // tests/DepartmentService.test.js import { describe, it, expect, beforeEach, vi } from 'vitest'; -const path = require('path'); -const base = path.resolve(__dirname, '../'); -const sequelize = require(path.join(base, 'src', 'config', 'db.js')); -const { DataTypes } = require('sequelize'); -const DepartmentModel = require('../src/api/Models/department')(sequelize, DataTypes) -const DepartmentService = require('../src/api/Services/DepartmentServices') +import path from 'path'; +import sequelize from '../src/Config/db.js' +import { DataTypes } from 'sequelize'; +import DepartmentModelFactory from '../src/api/Models/department.js'; +import * as DepartmentService from '../src/api/Services/DepartmentServices.js'; + +const DepartmentModel = DepartmentModelFactory(sequelize, DataTypes); describe('DepartmentService - CreateDepartment', () => { const mockDepartmentData = { @@ -21,16 +22,12 @@ describe('DepartmentService - CreateDepartment', () => { }); it('should create a department successfully', async () => { - // Mock Sequelize create method -// vi.spyOn(DepartmentModel, 'create').mockResolvedValue(null); - const result = await DepartmentService.CreateDeparment(mockDepartmentData); expect(result).toEqual({ message: 'Department created successfully', department: result.department }); - // expect(DepartmentModel.create).toHaveBeenCalledWith(mockDepartmentData); }); it('should throw error if creation fails due to empty department code', async () => { @@ -39,9 +36,6 @@ describe('DepartmentService - CreateDepartment', () => { department_code: '' // force empty code }; - // // Mock create to simulate DB returning null - // vi.spyOn(DepartmentModel, 'create').mockResolvedValue(null); - await expect(DepartmentService.CreateDeparment(invalidDepartmentData)) .rejects .toThrow('Validation error'); @@ -65,26 +59,24 @@ describe("UpdateDepartment Service", () => { ).rejects.toThrow("At-Least One Department fields must be provided to update"); }); -it("should throw error if department not found", async () => { - vi.spyOn(DepartmentModel, "findOne").mockResolvedValue(null); - await expect( - DepartmentService.UpdateDepartment({ - department_id: 123, // make sure this matches your service - department_code:"DDSF", - department_name: "Finance", - }) - ).rejects.toThrow("No Deparment found with the given department id"); -}); + it("should throw error if department not found", async () => { + vi.spyOn(DepartmentModel, "findOne").mockResolvedValue(null); + await expect( + DepartmentService.UpdateDepartment({ + department_id: 123, + department_code:"DDSF", + department_name: "Finance", + }) + ).rejects.toThrow("No Deparment found with the given department id"); + }); it("should update department successfully using real DB record", async () => { - // First, get an existing department from the database - const existingDepartment = await DepartmentModel.findOne(); // fetch first available record + const existingDepartment = await DepartmentModel.findOne(); if (!existingDepartment) { throw new Error("No department found in DB to test update"); } - // Update the department name - const newName = "Finance_" + Date.now(); // to avoid duplicate names + const newName = "Finance_" + Date.now(); const result = await DepartmentService.UpdateDepartment({ department_id: existingDepartment.department_id, department_name: newName, @@ -92,13 +84,13 @@ it("should throw error if department not found", async () => { expect(result).toBe("Department updated successfully"); - // Verify the update in DB const updatedDepartment = await DepartmentModel.findOne({ where: { department_id: existingDepartment.department_id }, }); expect(updatedDepartment.department_name).toBe(newName); }); }); + describe("Delete-Department", () => { it("should throw 'ID Must Be Provided'", async () => { await expect(DepartmentService.DeleteDepartment()) @@ -106,8 +98,9 @@ describe("Delete-Department", () => { .toThrow("ID must be provided"); }); - it("should throw 'No department found with the given ID'", async()=>{ + it("should throw 'No department found with the given ID'", async () => { await expect(DepartmentService.DeleteDepartment(1231)) - .rejects.toThrow("No department found with the given ID"); - }) + .rejects + .toThrow("No department found with the given ID"); + }); });