feat: Nambahin feature notifikasi

This commit is contained in:
Rafi Athallah
2026-06-23 14:51:31 +07:00
parent 24a3ec7fd0
commit 0e9498a2d0
14 changed files with 1185 additions and 11 deletions
@@ -0,0 +1,77 @@
const notificationService = require('../services/notification.service');
const { response, error } = require('../../../helpers/utils/wrapper');
const { SUCCESS, ERROR } = require('../../../helpers/http-status/status_code');
const { InternalServerError } = require('../../../helpers/error');
const getActor = (req) => ({
id: req.id,
role: req.role,
email: req.email,
});
class NotificationController {
async getNotifications(req, res) {
try {
const actor = getActor(req);
const result = await notificationService.getNotifications(actor);
if (result.err) {
return response(res, 'fail', result);
}
return response(res, 'success', result, 'Notifications retrieved successfully', SUCCESS.OK);
} catch (err) {
return response(
res,
'fail',
error(new InternalServerError(err.message)),
'Unexpected error occurred',
ERROR.INTERNAL_ERROR
);
}
}
async markAsRead(req, res) {
try {
const actor = getActor(req);
const result = await notificationService.markAsRead(req.params.id, actor);
if (result.err) {
return response(res, 'fail', result);
}
return response(res, 'success', result, 'Notification marked as read successfully', SUCCESS.OK);
} catch (err) {
return response(
res,
'fail',
error(new InternalServerError(err.message)),
'Unexpected error occurred',
ERROR.INTERNAL_ERROR
);
}
}
async markAllAsRead(req, res) {
try {
const actor = getActor(req);
const result = await notificationService.markAllAsRead(actor);
if (result.err) {
return response(res, 'fail', result);
}
return response(res, 'success', result, 'All notifications marked as read', SUCCESS.OK);
} catch (err) {
return response(
res,
'fail',
error(new InternalServerError(err.message)),
'Unexpected error occurred',
ERROR.INTERNAL_ERROR
);
}
}
}
module.exports = new NotificationController();
+5
View File
@@ -0,0 +1,5 @@
const notificationController = require('./controllers/notification.controller');
module.exports = {
notificationController
};
@@ -0,0 +1,58 @@
const prisma = require('../../../helpers/db/db_connection');
class NotificationRepository {
async create(data) {
return prisma.notification.create({
data
});
}
async findManyByUser(userId) {
return prisma.notification.findMany({
where: { id_user: parseInt(userId) },
orderBy: { created_at: 'desc' }
});
}
async findManyByAdmin(adminId) {
return prisma.notification.findMany({
where: { id_admin: parseInt(adminId) },
orderBy: { created_at: 'desc' }
});
}
async findById(id) {
return prisma.notification.findUnique({
where: { id: parseInt(id) }
});
}
async updateReadStatus(id, isRead) {
return prisma.notification.update({
where: { id: parseInt(id) },
data: { is_read: isRead }
});
}
async markAllAsReadForUser(userId) {
return prisma.notification.updateMany({
where: {
id_user: parseInt(userId),
is_read: false
},
data: { is_read: true }
});
}
async markAllAsReadForAdmin(adminId) {
return prisma.notification.updateMany({
where: {
id_admin: parseInt(adminId),
is_read: false
},
data: { is_read: true }
});
}
}
module.exports = new NotificationRepository();
@@ -0,0 +1,141 @@
const notificationRepository = require('../repositories/notification.repository');
const taskRepository = require('../../task/repositories/task.repository');
const { getIO } = require('../../../helpers/socket/socket_connection');
const { data, error } = require('../../../helpers/utils/wrapper');
const { NotFoundError, ForbiddenError, BadRequestError } = require('../../../helpers/error');
class NotificationService {
async createNotificationForIntern(userId, title, message) {
try {
const notification = await notificationRepository.create({
id_user: parseInt(userId),
id_admin: null,
title,
message,
is_read: false
});
const io = getIO();
if (io) {
io.to(`user:${userId}`).emit('notification', notification);
}
return data(notification);
} catch (err) {
return error(err);
}
}
async createNotificationForMentor(adminId, title, message) {
try {
const notification = await notificationRepository.create({
id_user: null,
id_admin: parseInt(adminId),
title,
message,
is_read: false
});
const io = getIO();
if (io) {
io.to(`admin:${adminId}`).emit('notification', notification);
}
return data(notification);
} catch (err) {
return error(err);
}
}
async createNotificationForProjectInterns(projectId, title, message) {
try {
const project = await taskRepository.findProjectById(projectId);
if (!project || !project.members) {
return data([]);
}
const results = [];
for (const member of project.members) {
const res = await this.createNotificationForIntern(member.id_user, title, message);
if (res.data) {
results.push(res.data);
}
}
return data(results);
} catch (err) {
return error(err);
}
}
async getNotifications(actor = {}) {
try {
if (!actor?.id || !actor?.role) {
return error(new BadRequestError('Authentication data is missing'));
}
let notifications = [];
if (actor.role === 'admin' || actor.role === 'mentor') {
notifications = await notificationRepository.findManyByAdmin(actor.id);
} else if (actor.role === 'intern') {
notifications = await notificationRepository.findManyByUser(actor.id);
}
return data(notifications);
} catch (err) {
return error(err);
}
}
async markAsRead(idNotification, actor = {}) {
try {
if (!actor?.id || !actor?.role) {
return error(new BadRequestError('Authentication data is missing'));
}
const notification = await notificationRepository.findById(idNotification);
if (!notification) {
return error(new NotFoundError('Notification not found'));
}
if (actor.role === 'intern') {
if (notification.id_user !== parseInt(actor.id)) {
return error(new ForbiddenError('Access denied: not your notification'));
}
} else if (actor.role === 'admin' || actor.role === 'mentor') {
if (notification.id_admin !== parseInt(actor.id)) {
return error(new ForbiddenError('Access denied: not your notification'));
}
} else {
return error(new ForbiddenError('Access denied: invalid role'));
}
const updated = await notificationRepository.updateReadStatus(idNotification, true);
return data(updated);
} catch (err) {
return error(err);
}
}
async markAllAsRead(actor = {}) {
try {
if (!actor?.id || !actor?.role) {
return error(new BadRequestError('Authentication data is missing'));
}
if (actor.role === 'admin' || actor.role === 'mentor') {
await notificationRepository.markAllAsReadForAdmin(actor.id);
} else if (actor.role === 'intern') {
await notificationRepository.markAllAsReadForUser(actor.id);
} else {
return error(new ForbiddenError('Access denied: invalid role'));
}
return data({ success: true });
} catch (err) {
return error(err);
}
}
}
module.exports = new NotificationService();