hPiBot openclaw ve Opencode ilk versiyonu[B

This commit is contained in:
2026-03-04 05:17:51 +03:00
commit d49edbfba3
75 changed files with 42117 additions and 0 deletions

611
backend/src/routes/users.js Normal file
View 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;