Skip to content

Commit

Permalink
Feature/notification crud logic (#14)
Browse files Browse the repository at this point in the history
* fix: fix target user on course to user not userentity

* feat: add the notification entity

* wip: update the appdata source to sync the notification to database

* wip: add notification service

* wip: add notification controller

* wip: add notification route

* wip: protect notification route with middleware

* wip: use notification route on server

* feat: Implement notification CRUD with type filtering and duplicate prevention

* feat: update the notification to be used on other dashbaords
  • Loading branch information
Smartdevs17 authored Feb 26, 2025
1 parent f48c2f8 commit 1ec587f
Show file tree
Hide file tree
Showing 7 changed files with 264 additions and 2 deletions.
3 changes: 2 additions & 1 deletion config/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Course } from "../entities/course.entity.js"
import { UserCourse } from "../entities/userCourse.entity.js"
import UserEntity from "../entities/user.entity.js";
import Job from "../entities/job.entity.js";
import Notification from "../entities/notification.entity.js";

dotenv.config()

Expand All @@ -16,7 +17,7 @@ const AppDataSource = new DataSource({
database: process.env.DB_NAME,
synchronize: true,
logging: true,
entities: [UserEntity, Course, UserCourse, Job],
entities: [UserEntity, Course, UserCourse, Job, Notification],
subscribers: [],
migrations: [],
})
Expand Down
84 changes: 84 additions & 0 deletions controllers/notification.controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import NotificationService from "../services/notificationService.js";

class NotificationController {
constructor() {
this.notificationService = NotificationService;
}

async create(req, res) {
try {
const notification = await this.notificationService.create(req.body);
res.status(201).json(notification);
} catch (error) {
res.status(400).json({ error: error.message });
}
}

async findAll(req, res) {
try {
const notifications = await this.notificationService.findAll();
res.json(notifications);
} catch (error) {
res.status(500).json({ error: error.message });
}
}

async findById(req, res) {
try {
const notification = await this.notificationService.findById(req.params.id);
if (!notification) {
return res.status(404).json({ error: "Notification not found" });
}
res.json(notification);
} catch (error) {
res.status(500).json({ error: error.message });
}
}

async findBySender(req, res) {
try {
const notifications = await this.notificationService.findBySender(req.params.senderId);
res.json(notifications);
} catch (error) {
res.status(500).json({ error: error.message });
}
}

async findByReceiver(req, res) {
try {
const notifications = await this.notificationService.findByReceiver(req.params.receiverId);
res.json(notifications);
} catch (error) {
res.status(500).json({ error: error.message });
}
}

async findByType(req, res) {
try {
const notifications = await this.notificationService.findByType(req.params.type);
res.json(notifications);
} catch (error) {
res.status(400).json({ error: error.message });
}
}

async update(req, res) {
try {
const notification = await this.notificationService.update(req.params.id, req.body);
res.json(notification);
} catch (error) {
res.status(400).json({ error: error.message });
}
}

async delete(req, res) {
try {
await this.notificationService.delete(req.params.id);
res.status(204).send();
} catch (error) {
res.status(500).json({ error: error.message });
}
}
}

export default new NotificationController();
2 changes: 1 addition & 1 deletion entities/course.entity.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export const Course = new EntitySchema({
relations: {
users: {
type: "many-to-many",
target: "UserEntity",
target: "User",
joinTable: {
name: "user_courses",
joinColumn: {
Expand Down
58 changes: 58 additions & 0 deletions entities/notification.entity.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { EntitySchema } from "typeorm";

const Notification = new EntitySchema({
name: "Notification",
tableName: "notifications",
columns: {
id: {
primary: true,
type: "int",
generated: true,
},
senderId: {
type: "int",
nullable: false,
},
receiverId: {
type: "int",
nullable: false,
},
notificationType: {
type: "varchar", //represents the type of notification for which dashboard it will be displayed
nullable: true
},
type: {
type: "enum",
enum: ["exam", "certification", "candidate", "new-student", "question", "payment", "announcement", "message", "new-request"],
nullable: false,
},
message: {
type: "text",
nullable: false,
},
read: {
type: "boolean",
default: false,
},
createdAt: {
type: "timestamp",
createDate: true,
},
},
relations: {
sender: {
type: "many-to-one",
target: "User",
joinColumn: { name: "senderId" },
onDelete: "CASCADE",
},
receiver: {
type: "many-to-one",
target: "User",
joinColumn: { name: "receiverId" },
onDelete: "CASCADE",
},
},
});

export default Notification;
17 changes: 17 additions & 0 deletions routes/notificationRoutes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import express from "express";
import NotificationController from "../controllers/notification.controller.js";
import { authenticateToken } from "../middlewares/auth.middleware.js"

const router = express.Router();
router.use(authenticateToken);

router.post("/", (req, res) => NotificationController.create(req, res));
router.get("/", (req, res) => NotificationController.findAll(req, res));
router.get("/:id", (req, res) => NotificationController.findById(req, res));
router.get("/sender/:senderId", (req, res) => NotificationController.findBySender(req, res));
router.get("/receiver/:receiverId", (req, res) => NotificationController.findByReceiver(req, res));
router.get("/type/:type", (req, res) => NotificationController.findByType(req, res));
router.put("/:id", (req, res) => NotificationController.update(req, res));
router.delete("/:id", (req, res) => NotificationController.delete(req, res));

export default router;
2 changes: 2 additions & 0 deletions server.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import express, { json } from 'express';
import authRouter from './routes/auth.routes.js';
import jobRoutes from './routes/jobRoutes.js';
import courseRoutes from "./routes/courseRoutes.js"
import notificationRoutes from "./routes/notificationRoutes.js"

import helmet from 'helmet';
import cors from 'cors';
Expand All @@ -25,6 +26,7 @@ AppDataSource.initialize()
app.use('/api/auth', authRouter);
app.use('/api/jobs', jobRoutes);
app.use("/api/courses", courseRoutes)
app.use("/api/notifications", notificationRoutes)

// Error handling
app.use((err, req, res, next) => {
Expand Down
100 changes: 100 additions & 0 deletions services/notificationService.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import AppDataSource from "../config/config.js";
import Notification from "../entities/notification.entity.js";

class NotificationService {
constructor() {
this.notificationRepository = AppDataSource.getRepository(Notification);
}

async create(notificationData) {
const { senderId, receiverId, notificationType , type, message } = notificationData;

const VALID_TYPES = [
"exam",
"certification",
"candidate",
"new-student",
"question",
"payment",
"announcement",
"message",
"new-request",
];
if (!VALID_TYPES.includes(type)) {
throw new Error(
"Invalid notification type. Must be one of 'exam', 'certification', 'candidate', 'new-student', 'question', 'payment', 'announcement', 'message', or 'new-request'."
);
}

// Check for duplicate notifications with the same sender, receiver, type, and message
const existingNotification = await this.notificationRepository.findOne({
where: { senderId, receiverId, notificationType, type, message },
});

if (existingNotification) {
throw new Error("Duplicate notification detected.");
}

// Create and save the notification
const notification = this.notificationRepository.create(notificationData);
return await this.notificationRepository.save(notification);
}

async findAll() {
return await this.notificationRepository.find();
}

async findById(id) {
return await this.notificationRepository.findOne({ where: { id } });
}

async findBySender(senderId) {
return await this.notificationRepository.find({ where: { senderId } });
}

async findByReceiver(receiverId) {
return await this.notificationRepository.find({ where: { receiverId } });
}

async findByType(type) {
const VALID_TYPES = [
"exam",
"certification",
"candidate",
"new-student",
"question",
"payment",
"announcement",
"message",
"new-request",
];
if (!VALID_TYPES.includes(type)) {
throw new Error(
"Invalid notification type. Must be one of 'exam', 'certification', 'candidate', 'new-student', 'question', 'payment', 'announcement', 'message', or 'new-request'."
);
}

return await this.notificationRepository.find({ where: { type } });
}

async update(id, notificationData) {
const existingNotification = await this.findById(id);
if (!existingNotification) {
throw new Error("Notification not found");
}

await this.notificationRepository.update(id, notificationData);
return this.findById(id);
}

async delete(id) {
const notification = await this.findById(id);
if (!notification) {
throw new Error("Notification not found");
}

return await this.notificationRepository.delete(id);
}
}

export default new NotificationService();

0 comments on commit 1ec587f

Please sign in to comment.