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ı bildirim/tercih ayarlarını getir * GET /api/users/settings * Not: Özel route'u parametre yakalayıcılarından (/:userId) ÖNCE tanımlayın */ router.get('/settings', [ authenticateToken, ], asyncHandler(async (req, res) => { const key = `user:${req.user.id}:settings`; const setting = await req.prisma.setting.findUnique({ where: { key } }); let settings; if (setting) { try { settings = JSON.parse(setting.value); } catch (e) { settings = {}; } } else { const now = new Date().toISOString(); settings = { id: `user-settings-${req.user.id}`, userId: req.user.id, emailNotifications: true, pushNotifications: true, listInviteNotifications: true, itemUpdateNotifications: true, priceAlertNotifications: false, theme: 'system', language: 'tr', currency: 'TL', timezone: 'Europe/Istanbul', createdAt: now, updatedAt: now, }; } res.json({ success: true, data: { settings } }); })); /** * Kullanıcı bildirim/tercih ayarlarını güncelle * PUT /api/users/settings * Not: Özel route'u parametre yakalayıcılarından (/:userId) ÖNCE tanımlayın */ router.put('/settings', [ authenticateToken, body('emailNotifications').optional().isBoolean(), body('pushNotifications').optional().isBoolean(), body('listInviteNotifications').optional().isBoolean(), body('itemUpdateNotifications').optional().isBoolean(), body('priceAlertNotifications').optional().isBoolean(), body('theme').optional().isIn(['light', 'dark', 'system']), body('language').optional().isString(), body('currency').optional().isString(), body('timezone').optional().isString(), ], asyncHandler(async (req, res) => { const errors = validationResult(req); if (!errors.isEmpty()) { return res.status(400).json({ success: false, message: 'Girilen bilgilerde hatalar var', errors: formatValidationErrors(errors) }); } const key = `user:${req.user.id}:settings`; const existing = await req.prisma.setting.findUnique({ where: { key } }); let current = {}; if (existing) { try { current = JSON.parse(existing.value); } catch (e) { current = {}; } } const now = new Date().toISOString(); const merged = { ...current, ...req.body, id: current.id || `user-settings-${req.user.id}`, userId: req.user.id, updatedAt: now, createdAt: current.createdAt || now, }; await req.prisma.setting.upsert({ where: { key }, update: { value: JSON.stringify(merged), type: 'json' }, create: { key, value: JSON.stringify(merged), type: 'json' }, }); res.json({ success: true, message: 'Ayarlar güncellendi', data: { settings: merged } }); })); /** * Kullanıcı arama * GET /api/users/search */ router.get('/search', [ authenticateToken, query('q') .isLength({ min: 2 }) .withMessage('Arama terimi en az 2 karakter 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 { q, limit = 10 } = req.query; const users = await req.prisma.user.findMany({ where: { isActive: true, OR: [ { username: { contains: q, mode: 'insensitive' } }, { firstName: { contains: q, mode: 'insensitive' } }, { lastName: { contains: q, mode: 'insensitive' } }, { email: { contains: q, mode: 'insensitive' } } ] }, select: { id: true, username: true, firstName: true, lastName: true, email: true, avatar: true, createdAt: true }, take: parseInt(limit), orderBy: { username: 'asc' } }); res.json({ success: true, data: { users } }); })); /** * Kullanıcı profili getir * GET /api/users/:userId */ router.get('/:userId', [ authenticateToken, param('userId').isUUID().withMessage('Geçerli bir kullanıcı ID\'si girin') ], asyncHandler(async (req, res) => { const { userId } = req.params; const user = await req.prisma.user.findUnique({ where: { id: userId, isActive: true }, select: { id: true, username: true, firstName: true, lastName: true, email: true, avatar: true, createdAt: true, _count: { select: { ownedLists: { where: { isActive: true } }, sharedLists: true } } } }); if (!user) { return res.status(404).json({ success: false, message: 'Kullanıcı bulunamadı' }); } // Eğer kendi profili değilse, e-posta adresini gizle if (userId !== req.user.id && !req.user.isAdmin) { delete user.email; } res.json({ success: true, data: { user } }); })); /** * Tüm kullanıcıları listele (Admin) * GET /api/users */ router.get('/', [ authenticateToken, requireAdmin, query('page').optional().isInt({ min: 1 }).withMessage('Sayfa numarası pozitif bir sayı olmalı'), query('limit').optional().isInt({ min: 1, max: 100 }).withMessage('Limit 1-100 arasında olmalı'), query('search').optional().isLength({ min: 2 }).withMessage('Arama terimi en az 2 karakter olmalı'), query('status').optional().isIn(['active', 'inactive', 'all']).withMessage('Geçerli bir durum 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, search, status = 'active', sortBy = 'createdAt', sortOrder = 'desc' } = req.query; const skip = (page - 1) * limit; // Filtreleme koşulları const whereCondition = {}; if (status === 'active') { whereCondition.isActive = true; } else if (status === 'inactive') { whereCondition.isActive = false; } if (search) { whereCondition.OR = [ { username: { contains: search, mode: 'insensitive' } }, { firstName: { contains: search, mode: 'insensitive' } }, { lastName: { contains: search, mode: 'insensitive' } }, { email: { contains: search, mode: 'insensitive' } } ]; } // Sıralama const orderBy = {}; orderBy[sortBy] = sortOrder; const [users, totalCount] = await Promise.all([ req.prisma.user.findMany({ where: whereCondition, select: { id: true, username: true, firstName: true, lastName: true, email: true, avatar: true, isActive: true, isAdmin: true, createdAt: true, lastLoginAt: true, _count: { select: { ownedLists: { where: { isActive: true } }, sharedLists: true, notifications: { where: { isRead: false } } } } }, orderBy, skip: parseInt(skip), take: parseInt(limit) }), req.prisma.user.count({ where: whereCondition }) ]); res.json({ success: true, data: { users, pagination: { currentPage: parseInt(page), totalPages: Math.ceil(totalCount / limit), totalCount, hasNext: skip + parseInt(limit) < totalCount, hasPrev: page > 1 } } }); })); /** * Kullanıcı durumunu güncelle (Admin) * PUT /api/users/:userId/status */ router.put('/:userId/status', [ authenticateToken, requireAdmin, param('userId').isString().isLength({ min: 1 }).withMessage('Geçerli bir kullanıcı ID\'si girin'), body('isActive').isBoolean().withMessage('Durum boolean değer 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 { userId } = req.params; const { isActive } = req.body; // Kendi hesabını deaktive edemez if (userId === req.user.id) { return res.status(400).json({ success: false, message: 'Kendi hesabınızı deaktive edemezsiniz' }); } const user = await req.prisma.user.findUnique({ where: { id: userId } }); if (!user) { return res.status(404).json({ success: false, message: 'Kullanıcı bulunamadı' }); } const updatedUser = await req.prisma.user.update({ where: { id: userId }, data: { isActive }, select: { id: true, username: true, firstName: true, lastName: true, email: true, isActive: true, isAdmin: true, updatedAt: true } }); res.json({ success: true, message: `Kullanıcı ${isActive ? 'aktif' : 'pasif'} hale getirildi`, data: { user: updatedUser } }); })); /** * Kullanıcı admin yetkisi güncelle (Admin) * PUT /api/users/:userId/admin */ router.put('/:userId/admin', [ authenticateToken, requireAdmin, param('userId').isString().isLength({ min: 1 }).withMessage('Geçerli bir kullanıcı ID\'si girin'), body('isAdmin').isBoolean().withMessage('Admin durumu boolean değer 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 { userId } = req.params; const { isAdmin } = req.body; // Kendi admin yetkisini kaldıramaz if (userId === req.user.id && !isAdmin) { return res.status(400).json({ success: false, message: 'Kendi admin yetkinizi kaldıramazsınız' }); } const user = await req.prisma.user.findUnique({ where: { id: userId } }); if (!user) { return res.status(404).json({ success: false, message: 'Kullanıcı bulunamadı' }); } const updatedUser = await req.prisma.user.update({ where: { id: userId }, data: { isAdmin }, select: { id: true, username: true, firstName: true, lastName: true, email: true, isActive: true, isAdmin: true, updatedAt: true } }); res.json({ success: true, message: `Kullanıcı ${isAdmin ? 'admin' : 'normal kullanıcı'} yapıldı`, data: { user: updatedUser } }); })); /** * Kullanıcı sil (Admin) * DELETE /api/users/:userId */ router.delete('/:userId', [ authenticateToken, requireAdmin, param('userId').isUUID().withMessage('Geçerli bir kullanıcı ID\'si girin') ], asyncHandler(async (req, res) => { const { userId } = req.params; // Kendi hesabını silemez if (userId === req.user.id) { return res.status(400).json({ success: false, message: 'Kendi hesabınızı silemezsiniz' }); } const user = await req.prisma.user.findUnique({ where: { id: userId } }); if (!user) { return res.status(404).json({ success: false, message: 'Kullanıcı bulunamadı' }); } // Soft delete (isActive = false) await req.prisma.user.update({ where: { id: userId }, data: { isActive: false, email: `deleted_${Date.now()}_${user.email}`, // E-posta çakışmasını önle username: `deleted_${Date.now()}_${user.username}` // Kullanıcı adı çakışmasını önle } }); res.json({ success: true, message: 'Kullanıcı başarıyla silindi' }); })); /** * Kullanıcı istatistikleri (Admin) * GET /api/users/stats */ router.get('/stats/overview', [ authenticateToken, requireAdmin ], asyncHandler(async (req, res) => { const [ totalUsers, activeUsers, adminUsers, newUsersThisMonth, totalLists, totalProducts ] = await Promise.all([ req.prisma.user.count(), req.prisma.user.count({ where: { isActive: true } }), req.prisma.user.count({ where: { isAdmin: true, isActive: true } }), req.prisma.user.count({ where: { createdAt: { gte: new Date(new Date().getFullYear(), new Date().getMonth(), 1) } } }), req.prisma.shoppingList.count({ where: { isActive: true } }), req.prisma.product.count({ where: { isActive: true } }) ]); res.json({ success: true, data: { users: { total: totalUsers, active: activeUsers, inactive: totalUsers - activeUsers, admins: adminUsers, newThisMonth: newUsersThisMonth }, lists: { total: totalLists }, products: { total: totalProducts } } }); })); /** * Kullanıcı bildirim/tercih ayarlarını getir * GET /api/users/settings */ /** * Admin: Kullanıcı şifresini sıfırla * PUT /api/users/:userId/password */ router.put('/:userId/password', [ authenticateToken, requireAdmin, param('userId').isUUID().withMessage('Geçerli bir kullanıcı ID\'si girin'), body('newPassword').isLength({ min: 6 }).withMessage('Yeni şifre en az 6 karakter olmalı'), ], asyncHandler(async (req, res) => { const errors = validationResult(req); if (!errors.isEmpty()) { return res.status(400).json({ success: false, message: 'Girilen bilgilerde hatalar var', errors: formatValidationErrors(errors) }); } const { userId } = req.params; const { newPassword } = req.body; const user = await req.prisma.user.findUnique({ where: { id: userId } }); if (!user) { return res.status(404).json({ success: false, message: 'Kullanıcı bulunamadı' }); } const bcrypt = require('bcryptjs'); const hashedPassword = await bcrypt.hash(newPassword, 12); await req.prisma.user.update({ where: { id: userId }, data: { password: hashedPassword } }); res.json({ success: true, message: 'Kullanıcı şifresi güncellendi' }); })); module.exports = router;