diff --git a/backend/src/__test__/controller.test.ts b/backend/src/__test__/controller.test.ts new file mode 100644 index 000000000..1d0a804cb --- /dev/null +++ b/backend/src/__test__/controller.test.ts @@ -0,0 +1,218 @@ +import { Request, Response } from "express"; +import { StudentController } from "../controller/studentController"; +import { StudentService } from "../services/studentService"; +import { getSocketInstance } from "../services/socketService"; +import { Student } from "../entity/student"; + +jest.mock("../services/studentService"); +jest.mock("../services/socketService"); +jest.mock("../entity/Student"); + +const mockSave = jest.fn(); +const mockFindOne = jest.fn(); +jest.mock("../entity/Student", () => ({ + PrimaryGeneratedColumn: jest.fn(), + Entity: jest.fn(), + Column: jest.fn(), + PrimaryColumn: jest.fn(), + getConnection: jest.fn().mockReturnValue({ + getRepository: jest.fn().mockReturnValue({ + save: mockSave, + findOne: mockFindOne, + }), + }), +})); + +describe("StudentController", () => { + const mockRequest = {}; + const mockResponse = { + status: jest.fn().mockReturnThis(), + send: jest.fn(), + }; + beforeEach(() => { + jest.clearAllMocks(); + }); + + it("should send all students successfully", async () => { + const expectedStudents = [ + { id: 1, name: "John Doe" }, + { id: 2, name: "Jane Doe" }, + ]; + (StudentService.prototype.getAllStudents as jest.Mock).mockResolvedValue( + expectedStudents + ); + + const studentController = new StudentController(); + + await studentController.all(mockRequest as Request, mockResponse as any); + + expect(mockResponse.status).toHaveBeenCalledWith(200); + expect(mockResponse.send).toHaveBeenCalledWith(expectedStudents); + }); + + it("should handle an error and send a 500 response", async () => { + const expectedError = new Error("An error occurred"); + (StudentService.prototype.getAllStudents as jest.Mock).mockRejectedValue( + expectedError + ); + + const studentController = new StudentController(); + + await studentController.all(mockRequest as Request, mockResponse as any); + + expect(mockResponse.status).toHaveBeenCalledWith(500); + expect(mockResponse.send).toHaveBeenCalledWith(expectedError); + }); +}); + +//add one student test + +describe("StudentController", () => { + const mockRequest = { + body: { + id: 1, + name: "John Doe", + gender: "Male", + address: "123 Main St", + mobile: "123-456-7890", + birthday: "2000-01-01", + age: 21, + }, + params: { + userId: "user123", + }, + }; + const mockResponse = { + status: jest.fn().mockReturnThis(), + send: jest.fn(), + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it("should add one student successfully", async () => { + const expectedStudent = {}; + (StudentService.prototype.createStudent as jest.Mock).mockResolvedValue( + expectedStudent + ); + + const studentController = new StudentController(); + + await studentController.add(mockRequest as any, mockResponse as any); + + expect(mockResponse.status).toHaveBeenCalledWith(200); + expect(mockResponse.send).toHaveBeenCalledWith(expectedStudent); + }); + + it("should handle an error and send a 500 response", async () => { + const expectedError = new Error("An error occurred"); + (StudentService.prototype.createStudent as jest.Mock).mockRejectedValue( + expectedError + ); + + const studentController = new StudentController(); + + await studentController.add(mockRequest as any, mockResponse as any); + + expect(mockResponse.status).toHaveBeenCalledWith(500); + expect(mockResponse.send).toHaveBeenCalledWith(expectedError); + }); +}); + +//update one student test + +describe("StudentController", () => { + const mockRequest = { + body: { + id: 1, + name: "John Doe", + }, + params: { + userId: "user123", + }, + }; + const mockResponse = { + status: jest.fn().mockReturnThis(), + send: jest.fn(), + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it("should update one student successfully", async () => { + const expectedStudent = {}; + (StudentService.prototype.updateStudent as jest.Mock).mockResolvedValue( + expectedStudent + ); + + const studentController = new StudentController(); + + await studentController.update(mockRequest as any, mockResponse as any); + + expect(mockResponse.status).toHaveBeenCalledWith(200); + expect(mockResponse.send).toHaveBeenCalledWith(expectedStudent); + }); + + it("should handle an error and send a 500 response", async () => { + const expectedError = new Error("An error occurred"); + (StudentService.prototype.updateStudent as jest.Mock).mockRejectedValue( + expectedError + ); + + const studentController = new StudentController(); + + await studentController.update(mockRequest as any, mockResponse as any); + + expect(mockResponse.status).toHaveBeenCalledWith(500); + expect(mockResponse.send).toHaveBeenCalledWith(expectedError); + }); +}); + +//delete one student test + +describe("StudentController", () => { + const mockRequest = { + params: { + id: 1, + userId: "user123", + }, + }; + const mockResponse = { + status: jest.fn().mockReturnThis(), + send: jest.fn(), + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it("should delete one student successfully", async () => { + const expectedStudent = {}; + (StudentService.prototype.removeStudent as jest.Mock).mockResolvedValue( + expectedStudent + ); + + const studentController = new StudentController(); + + await studentController.remove(mockRequest as any, mockResponse as any); + + expect(mockResponse.status).toHaveBeenCalledWith(200); + expect(mockResponse.send).toHaveBeenCalledWith(expectedStudent); + }); + + it("should handle an error and send a 500 response", async () => { + const expectedError = new Error("An error occurred"); + (StudentService.prototype.removeStudent as jest.Mock).mockRejectedValue( + expectedError + ); + + const studentController = new StudentController(); + + await studentController.remove(mockRequest as any, mockResponse as any); + + expect(mockResponse.status).toHaveBeenCalledWith(500); + expect(mockResponse.send).toHaveBeenCalledWith(expectedError); + }); +}); diff --git a/backend/src/__test__/service.test.ts b/backend/src/__test__/service.test.ts new file mode 100644 index 000000000..a11c209de --- /dev/null +++ b/backend/src/__test__/service.test.ts @@ -0,0 +1,127 @@ +// StudentService.test.ts + +import { StudentService } from "../services/studentService"; +import { AppDataSource } from "../config/data-source"; +import { Student } from "../entity/student"; + +jest.mock("../config/data-source"); + +// Mock the AppDataSource getRepository method +const mockGetRepository = jest.fn(); + +// Mock the repository methods +const mockFind = jest.fn(); +const mockFindOne = jest.fn(); +const mockCreate = jest.fn(); +const mockSave = jest.fn(); +const mockRemove = jest.fn(); +const mockUpdate = jest.fn(); + +beforeEach(() => { + // Reset mocks and create fresh instances for each test + jest.clearAllMocks(); + + // Mock the AppDataSource getRepository method to return the mock repository methods + (AppDataSource.getRepository as jest.Mock).mockImplementation(() => ({ + find: mockFind, + findOne: mockFindOne, + create: mockCreate, + save: mockSave, + remove: mockRemove, + update: mockUpdate, + })); +}); + +describe("StudentService", () => { + let studentService: StudentService; + + beforeEach(() => { + studentService = new StudentService(); + }); + + it("should get all students successfully", async () => { + // Arrange + const expectedStudents = [ + { id: 1, name: "John Doe" }, + { id: 2, name: "Jane Doe" }, + ]; + mockFind.mockResolvedValue(expectedStudents); + + // Act + const result = await studentService.getAllStudents(); + + // Assert + expect(result).toEqual(expectedStudents); + }); + + it("should get a student by id successfully", async () => { + // Arrange + const expectedStudent = { id: 1, name: "John Doe" }; + mockFindOne.mockResolvedValue(expectedStudent); + + // Act + const result = await studentService.getStudentById(1); + + // Assert + expect(result).toEqual(expectedStudent); + }); + + it("should create a new student successfully", async () => { + const student = new Student(); + const expectedStudent = { + id: 1, + name: "John Doe", + gender: "Male", + address: "123 Main St", + mobile: "123-456-7890", + birthday: new Date(), + age: 21, + }; + + mockCreate.mockReturnValue(expectedStudent); + mockSave.mockResolvedValue(expectedStudent); + + // Act + const result = await studentService.createStudent( + 1, + "John Doe", + "Male", + "123 Main St", + "123-456-7890", + new Date(), + 21 + ); + + // Assert + expect(result).toEqual(expectedStudent); + expect(mockSave).toHaveBeenCalledWith(expectedStudent); + }); + + it("should remove a student successfully", async () => { + // Arrange + const expectedMessage = "Student has been removed"; + mockFindOne.mockResolvedValue({ id: 1, name: "John Doe" }); + + // Act + const result = await studentService.removeStudent(1); + + // Assert + expect(result).toEqual(expectedMessage); + expect(mockRemove).toHaveBeenCalledWith({ id: 1, name: "John Doe" }); + }); + + it("should update a student successfully", async () => { + // Arrange + const expectedUpdatedStudent = { affected: 1 }; + mockUpdate.mockResolvedValue({ affected: 1 }); + + // Act + const result = await studentService.updateStudent(1, { + name: "Updated John Doe", + }); + + // Assert + expect(result).toEqual(expectedUpdatedStudent); + expect(mockUpdate).toHaveBeenCalledWith(1, { name: "Updated John Doe" }); + }); +}); diff --git a/backend/src/config/data-source.ts b/backend/src/config/data-source.ts new file mode 100644 index 000000000..ccdd05804 --- /dev/null +++ b/backend/src/config/data-source.ts @@ -0,0 +1,18 @@ +import "reflect-metadata"; +import { DataSource } from "typeorm"; +import { Student } from "../entity/student"; +import * as dotenv from "dotenv"; +dotenv.config(); + +export const AppDataSource = new DataSource({ + url: process.env.PG_DATABASE_URL, + ssl: { + rejectUnauthorized: false, + }, + type: "postgres", + synchronize: true, + logging: false, + entities: [Student], + migrations: [], + subscribers: [], +}); diff --git a/backend/src/controller/studentController.ts b/backend/src/controller/studentController.ts new file mode 100644 index 000000000..5fb533f14 --- /dev/null +++ b/backend/src/controller/studentController.ts @@ -0,0 +1,103 @@ +import { Request, Response } from "express"; +import { StudentService } from "../services/studentService"; +import { getSocketInstance } from "../services/socketService"; // Import the socket manager +import { userSockets } from "../services/socketService"; +import { Server } from "socket.io"; + +export class StudentController { + private studentService = new StudentService(); + private io = getSocketInstance(); + async all(request: Request, response: Response) { + try { + const students = await this.studentService.getAllStudents(); + response.status(200).send(students); + } catch (error) { + response.status(500).send(error); + } + } + + async one(request: Request, response: Response) { + const id = parseInt(request.params.id); + + try { + const student = await this.studentService.getStudentById(id); + response.status(200).send(student); + } catch (error) { + response.status(500).send(error); + } + } + + async add(request: Request, response: Response) { + const { id, name, gender, address, mobile, birthday, age } = request.body; + const userId = request.params.userId; + try { + const student = await this.studentService.createStudent( + id, + name, + gender, + address, + mobile, + birthday, + age + ); + sendMessage(this.io, userId, "added_successfully", id); + response.status(200).send(student); + } catch (error) { + sendMessage(this.io, userId, "add_unsuccessfull", id); + response.status(500).send(error); + } + } + + async update(request: Request, response: Response) { + const studentId = Number(request.params.id); + const userId = request.params.userId; + const { id, name, gender, address, mobile, birthday, age } = request.body; + const student = { + id, + name, + gender, + address, + mobile, + birthday, + age, + }; + try { + const updatedStudent = await this.studentService.updateStudent( + studentId, + student + ); + sendMessage(this.io, userId, "updated_successfully", studentId); + response.status(200).send(updatedStudent); + } catch (error) { + sendMessage(this.io, userId, "update_unsuccessful", studentId); + response.status(500).send(error); + } + } + + async remove(request: Request, response: Response) { + const id = parseInt(request.params.id); + const userId = request.params.userId; + + try { + const removedStudent = await this.studentService.removeStudent(id); + sendMessage(this.io, userId, "deleted_successfully", id); + response.status(200).send(removedStudent); + } catch (error) { + sendMessage(this.io, userId, "delete_unsuccessful", id); + response.status(500).send(error); + } + } +} +async function sendMessage( + io: Server, + userId: string, + message: string, + studentId: number +) { + const socketId = userSockets.get(userId); + if (socketId) { + io.to(socketId).emit(message, studentId); + } else { + console.warn("User not found:", userId); + } +} diff --git a/backend/src/entity/student.ts b/backend/src/entity/student.ts new file mode 100644 index 000000000..fa2f5bf5a --- /dev/null +++ b/backend/src/entity/student.ts @@ -0,0 +1,26 @@ +import { Entity, Column, PrimaryColumn } from "typeorm"; + +@Entity() +export class Student { + @PrimaryColumn() + id: number; + + @Column() + name: string; + + @Column() + gender: string; + + @Column() + address: string; + + @Column() + mobile: string; + + @Column() + birthday: Date; + + @Column() + age: number; +} + diff --git a/backend/src/index.ts b/backend/src/index.ts new file mode 100644 index 000000000..49394c581 --- /dev/null +++ b/backend/src/index.ts @@ -0,0 +1,54 @@ +import * as express from "express"; +import * as bodyParser from "body-parser"; +import { AppDataSource } from "./config/data-source"; +import { Routes } from "./routes/routes"; +import cors = require("cors"); +import { createServer } from "http"; + +import { initializeSocketIO } from "../src/services/socketService"; // Import the socket manager + +AppDataSource.initialize().then(async () => { + const app = express(); + app.use(bodyParser.json()); + app.use(bodyParser.urlencoded({ extended: true })); + app.use( + cors({ + origin: "*", + methods: "GET,HEAD,PUT,PATCH,POST,DELETE", + credentials: true, + }) + ); + + const server = createServer(app); + initializeSocketIO(server); + + Routes.forEach((route) => { + (app as any)[route.method]( + route.route, + ( + req: express.Request, + res: express.Response, + next: express.NextFunction + ) => { + const result = new (route.controller as any)()[route.action]( + req, + res, + next + ); + if (result instanceof Promise) { + result.then((result) => + result !== null && result !== undefined + ? res.send(result) + : undefined + ); + } else if (result !== null && result !== undefined) { + res.json(result); + } + } + ); + }); + + server.listen(process.env.PORT); + + console.log(`Express server has started on port ${process.env.PORT}.`); +}); diff --git a/backend/src/routes/routes.ts b/backend/src/routes/routes.ts new file mode 100644 index 000000000..01b5ddcab --- /dev/null +++ b/backend/src/routes/routes.ts @@ -0,0 +1,34 @@ +import { StudentController } from "../controller/studentController"; + +export const Routes = [ + { + method: "get", + route: "/students", + controller: StudentController, + action: "all", + }, + { + method: "get", + route: "/students/:id", + controller: StudentController, + action: "one", + }, + { + method: "post", + route: "/students/:userId", + controller: StudentController, + action: "add", + }, + { + method: "put", + route: "/students/:id/:userId", + controller: StudentController, + action: "update", + }, + { + method: "delete", + route: "/students/:id/:userId", + controller: StudentController, + action: "remove", + }, +]; diff --git a/backend/src/services/socketService.ts b/backend/src/services/socketService.ts new file mode 100644 index 000000000..8ddb487fa --- /dev/null +++ b/backend/src/services/socketService.ts @@ -0,0 +1,39 @@ +import { Server, Socket } from "socket.io"; +import * as http from "http"; +let io: Server; + +export const userSockets = new Map(); + +export const initializeSocketIO = async (server: http.Server) => { + io = new Server(server, { + cors: { + origin: "*", + methods: ["GET", "POST", "PUT", "DELETE", "PATCH"], + }, + }); + + io.on("connection", (socket) => { + socket.on("authenticate", (userId) => { + userSockets.set(userId, socket.id); + }); + socket.on("messageReceived", (message) => { + console.log(userSockets); + console.log("Received confirmation from client:", message); + }); + }); + + io.on("privateMessage", (message) => { + console.log("Received private message notified:", message); + }); + + io.on("disconnect", () => { + console.log("User disconnected"); + }); +}; + +export const getSocketInstance = (): Server => { + if (!io) { + throw new Error("Socket.IO has not been initialized yet"); + } + return io; +}; diff --git a/backend/src/services/studentService.ts b/backend/src/services/studentService.ts new file mode 100644 index 000000000..b316fc769 --- /dev/null +++ b/backend/src/services/studentService.ts @@ -0,0 +1,69 @@ +import { AppDataSource } from "../config/data-source"; +import { Student } from "../entity/student"; + +export class StudentService { + private studentRepository = AppDataSource.getRepository(Student); + + async getAllStudents() { + const students = await this.studentRepository.find(); + if (!students) { + throw new Error("No student found"); + } + return students; + } + + async getStudentById(id: number) { + const student = await this.studentRepository.findOne({ + where: { id }, + }); + if (!student) { + throw new Error("Student not found"); + } + return student; + } + + async createStudent( + id: number, + name: string, + gender: string, + address: string, + mobile: string, + birthday: Date, + age: number + ) { + const student = this.studentRepository.create({ + id, + name, + gender, + address, + mobile, + birthday, + age, + }); + const response = await this.studentRepository.save(student); + return response; + } + + async removeStudent(id: number) { + const studentToRemove = await this.studentRepository.findOne({ + where: { id }, + }); + + if (!studentToRemove) { + throw new Error("Student not found"); + } + await this.studentRepository.remove(studentToRemove); + return "Student has been removed"; + } + + async updateStudent(studentId: number, student: any) { + const updatedStudent = await this.studentRepository.update( + studentId, + student + ); + if (!updatedStudent.affected) { + throw new Error("Student not found"); + } + return updatedStudent; + } +}