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