541 lines
12 KiB
JavaScript
541 lines
12 KiB
JavaScript
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; |