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;