hPiBot openclaw ve Opencode ilk versiyonu[B
This commit is contained in:
541
backend/src/routes/notifications.js
Normal file
541
backend/src/routes/notifications.js
Normal file
@@ -0,0 +1,541 @@
|
||||
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;
|
||||
Reference in New Issue
Block a user