Files
hMarket/backend/src/routes/notifications.js

541 lines
12 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const express = require('express');
const { body, validationResult, param, query } = require('express-validator');
const { asyncHandler, formatValidationErrors } = require('../middleware/errorHandler');
const { authenticateToken, requireAdmin } = require('../middleware/auth');
const router = express.Router();
/**
* Kullanıcının bildirimlerini getir
* GET /api/notifications
*/
router.get('/', [
authenticateToken,
query('page').optional().isInt({ min: 1 }).withMessage('Sayfa numarası pozitif bir sayı olmalı'),
query('limit').optional().isInt({ min: 1, max: 50 }).withMessage('Limit 1-50 arasında olmalı'),
query('unreadOnly').optional().isBoolean().withMessage('unreadOnly boolean değer olmalı'),
query('type').optional().isIn(['list_invite', 'item_added', 'item_removed', 'item_purchased', 'list_shared', 'system']).withMessage('Geçerli bir bildirim türü seçin')
], asyncHandler(async (req, res) => {
// Validation hatalarını kontrol et
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
success: false,
message: 'Girilen bilgilerde hatalar var',
errors: formatValidationErrors(errors)
});
}
const {
page = 1,
limit = 20,
unreadOnly = false,
type
} = req.query;
const skip = (page - 1) * limit;
// Filtreleme koşulları
const whereCondition = {
userId: req.user.id
};
if (unreadOnly) {
whereCondition.isRead = false;
}
if (type) {
whereCondition.type = type;
}
const [notifications, totalCount, unreadCount] = await Promise.all([
req.prisma.notification.findMany({
where: whereCondition,
include: {
relatedList: {
select: {
id: true,
name: true
}
},
relatedUser: {
select: {
id: true,
username: true,
firstName: true,
lastName: true,
avatar: true
}
}
},
orderBy: {
createdAt: 'desc'
},
skip: parseInt(skip),
take: parseInt(limit)
}),
req.prisma.notification.count({ where: whereCondition }),
req.prisma.notification.count({
where: {
userId: req.user.id,
isRead: false
}
})
]);
res.json({
success: true,
data: {
notifications,
pagination: {
currentPage: parseInt(page),
totalPages: Math.ceil(totalCount / limit),
totalCount,
hasNext: skip + parseInt(limit) < totalCount,
hasPrev: page > 1
},
unreadCount
}
});
}));
/**
* Okunmamış bildirim sayısını getir
* GET /api/notifications/unread-count
*/
router.get('/unread-count', [
authenticateToken
], asyncHandler(async (req, res) => {
const unreadCount = await req.prisma.notification.count({
where: {
userId: req.user.id,
isRead: false
}
});
res.json({
success: true,
data: { unreadCount }
});
}));
/**
* Bildirimi okundu olarak işaretle
* PUT /api/notifications/:notificationId/read
*/
router.put('/:notificationId/read', [
authenticateToken,
param('notificationId').isUUID().withMessage('Geçerli bir bildirim ID\'si girin')
], asyncHandler(async (req, res) => {
const { notificationId } = req.params;
const notification = await req.prisma.notification.findUnique({
where: {
id: notificationId,
userId: req.user.id
}
});
if (!notification) {
return res.status(404).json({
success: false,
message: 'Bildirim bulunamadı'
});
}
const updatedNotification = await req.prisma.notification.update({
where: { id: notificationId },
data: {
isRead: true,
readAt: new Date()
}
});
res.json({
success: true,
message: 'Bildirim okundu olarak işaretlendi',
data: { notification: updatedNotification }
});
}));
/**
* Tüm bildirimleri okundu olarak işaretle
* PUT /api/notifications/mark-all-read
*/
router.put('/mark-all-read', [
authenticateToken
], asyncHandler(async (req, res) => {
const result = await req.prisma.notification.updateMany({
where: {
userId: req.user.id,
isRead: false
},
data: {
isRead: true,
readAt: new Date()
}
});
res.json({
success: true,
message: `${result.count} bildirim okundu olarak işaretlendi`,
data: { updatedCount: result.count }
});
}));
/**
* Bildirimi sil
* DELETE /api/notifications/:notificationId
*/
router.delete('/:notificationId', [
authenticateToken,
param('notificationId').isUUID().withMessage('Geçerli bir bildirim ID\'si girin')
], asyncHandler(async (req, res) => {
const { notificationId } = req.params;
const notification = await req.prisma.notification.findUnique({
where: {
id: notificationId,
userId: req.user.id
}
});
if (!notification) {
return res.status(404).json({
success: false,
message: 'Bildirim bulunamadı'
});
}
await req.prisma.notification.delete({
where: { id: notificationId }
});
res.json({
success: true,
message: 'Bildirim başarıyla silindi'
});
}));
/**
* Okunmuş bildirimleri temizle
* DELETE /api/notifications/clear-read
*/
router.delete('/clear-read', [
authenticateToken
], asyncHandler(async (req, res) => {
const result = await req.prisma.notification.deleteMany({
where: {
userId: req.user.id,
isRead: true
}
});
res.json({
success: true,
message: `${result.count} okunmuş bildirim temizlendi`,
data: { deletedCount: result.count }
});
}));
/**
* Tüm bildirimleri temizle
* DELETE /api/notifications/clear-all
*/
router.delete('/clear-all', [
authenticateToken
], asyncHandler(async (req, res) => {
const result = await req.prisma.notification.deleteMany({
where: {
userId: req.user.id
}
});
res.json({
success: true,
message: `${result.count} bildirim temizlendi`,
data: { deletedCount: result.count }
});
}));
/**
* Bildirim ayarlarını getir
* GET /api/notifications/settings
*/
router.get('/settings', [
authenticateToken
], asyncHandler(async (req, res) => {
const settings = await req.prisma.setting.findMany({
where: {
userId: req.user.id,
key: {
startsWith: 'notification_'
}
}
});
// Varsayılan ayarlar
const defaultSettings = {
notification_list_invite: 'true',
notification_item_added: 'true',
notification_item_removed: 'true',
notification_item_purchased: 'true',
notification_list_shared: 'true',
notification_system: 'true',
notification_push_enabled: 'true',
notification_email_enabled: 'false'
};
// Kullanıcı ayarlarını varsayılanlarla birleştir
const userSettings = {};
settings.forEach(setting => {
userSettings[setting.key] = setting.value;
});
const finalSettings = { ...defaultSettings, ...userSettings };
res.json({
success: true,
data: { settings: finalSettings }
});
}));
/**
* Bildirim ayarlarını güncelle
* PUT /api/notifications/settings
*/
router.put('/settings', [
authenticateToken,
body('settings').isObject().withMessage('Ayarlar obje formatında olmalı')
], asyncHandler(async (req, res) => {
// Validation hatalarını kontrol et
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
success: false,
message: 'Girilen bilgilerde hatalar var',
errors: formatValidationErrors(errors)
});
}
const { settings } = req.body;
// Geçerli ayar anahtarları
const validKeys = [
'notification_list_invite',
'notification_item_added',
'notification_item_removed',
'notification_item_purchased',
'notification_list_shared',
'notification_system',
'notification_push_enabled',
'notification_email_enabled'
];
// Geçersiz anahtarları filtrele
const validSettings = {};
Object.keys(settings).forEach(key => {
if (validKeys.includes(key)) {
validSettings[key] = settings[key].toString();
}
});
if (Object.keys(validSettings).length === 0) {
return res.status(400).json({
success: false,
message: 'Geçerli ayar bulunamadı'
});
}
// Ayarları güncelle veya oluştur
const updatePromises = Object.entries(validSettings).map(([key, value]) =>
req.prisma.setting.upsert({
where: {
userId_key: {
userId: req.user.id,
key
}
},
update: { value },
create: {
userId: req.user.id,
key,
value
}
})
);
await Promise.all(updatePromises);
res.json({
success: true,
message: 'Bildirim ayarları güncellendi',
data: { settings: validSettings }
});
}));
/**
* Sistem bildirimi gönder (Admin)
* POST /api/notifications/system
*/
router.post('/system', [
authenticateToken,
requireAdmin,
body('title')
.trim()
.isLength({ min: 1, max: 100 })
.withMessage('Başlık 1-100 karakter arasında olmalı'),
body('message')
.trim()
.isLength({ min: 1, max: 500 })
.withMessage('Mesaj 1-500 karakter arasında olmalı'),
body('targetUsers')
.optional()
.isArray()
.withMessage('Hedef kullanıcılar dizi formatında olmalı'),
body('targetUsers.*')
.optional()
.isUUID()
.withMessage('Geçerli kullanıcı ID\'leri girin')
], asyncHandler(async (req, res) => {
// Validation hatalarını kontrol et
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
success: false,
message: 'Girilen bilgilerde hatalar var',
errors: formatValidationErrors(errors)
});
}
const { title, message, targetUsers } = req.body;
let users;
if (targetUsers && targetUsers.length > 0) {
// Belirli kullanıcılara gönder
users = await req.prisma.user.findMany({
where: {
id: { in: targetUsers },
isActive: true
},
select: { id: true }
});
} else {
// Tüm aktif kullanıcılara gönder
users = await req.prisma.user.findMany({
where: { isActive: true },
select: { id: true }
});
}
if (users.length === 0) {
return res.status(400).json({
success: false,
message: 'Hedef kullanıcı bulunamadı'
});
}
// Bildirimleri oluştur
const notifications = users.map(user => ({
userId: user.id,
type: 'system',
title,
message,
data: JSON.stringify({
adminId: req.user.id,
timestamp: new Date().toISOString()
})
}));
const result = await req.prisma.notification.createMany({
data: notifications
});
// Socket.IO ile gerçek zamanlı bildirim gönder
if (req.io) {
users.forEach(user => {
req.io.to(`user_${user.id}`).emit('notification', {
type: 'system',
title,
message,
createdAt: new Date()
});
});
}
res.status(201).json({
success: true,
message: `${result.count} kullanıcıya sistem bildirimi gönderildi`,
data: { sentCount: result.count }
});
}));
/**
* Bildirim istatistikleri (Admin)
* GET /api/notifications/stats
*/
router.get('/stats/overview', [
authenticateToken,
requireAdmin
], asyncHandler(async (req, res) => {
const [
totalNotifications,
unreadNotifications,
notificationsByType,
recentNotifications
] = await Promise.all([
req.prisma.notification.count(),
req.prisma.notification.count({ where: { isRead: false } }),
req.prisma.notification.groupBy({
by: ['type'],
_count: {
id: true
},
orderBy: {
_count: {
id: 'desc'
}
}
}),
req.prisma.notification.findMany({
where: {
createdAt: {
gte: new Date(Date.now() - 24 * 60 * 60 * 1000) // Son 24 saat
}
},
include: {
user: {
select: {
username: true,
firstName: true,
lastName: true
}
}
},
orderBy: {
createdAt: 'desc'
},
take: 10
})
]);
const typeStats = {};
notificationsByType.forEach(item => {
typeStats[item.type] = item._count.id;
});
res.json({
success: true,
data: {
overview: {
total: totalNotifications,
unread: unreadNotifications,
read: totalNotifications - unreadNotifications
},
byType: typeStats,
recent: recentNotifications
}
});
}));
module.exports = router;