update README

This commit is contained in:
Georgiy Syralev
2025-11-26 21:43:57 +03:00
parent c4c2610480
commit 753696cc93
58 changed files with 8674 additions and 3752 deletions

View File

@@ -2,6 +2,17 @@ import { Request, Response } from 'express';
import { prisma } from '../../prisma/client';
import { createNotification } from '../notification/notification.controller';
function toNumeric(value: unknown): number {
if (typeof value === 'bigint') {
return Number(value);
}
if (typeof value === 'number') {
return value;
}
const parsed = Number(value);
return Number.isFinite(parsed) ? parsed : 0;
}
/**
* Middleware для проверки прав администратора
*/
@@ -274,7 +285,8 @@ export class AdminController {
totalBalance,
pendingChecks,
openTickets,
bucketsAggregates
bucketsAggregates,
bucketStatusCounts
] = await Promise.all([
prisma.user.count(),
prisma.storageBucket.count(),
@@ -288,9 +300,25 @@ export class AdminController {
objectCount: true,
quotaGb: true
}
}),
prisma.storageBucket.groupBy({
by: ['status'],
_count: { _all: true }
})
]);
const statusMap = bucketStatusCounts.reduce<Record<string, number>>((acc, item) => {
acc[item.status] = item._count._all;
return acc;
}, {});
const servers = {
total: totalBuckets,
active: statusMap['active'] ?? 0,
suspended: statusMap['suspended'] ?? 0,
grace: statusMap['grace'] ?? 0
};
// Получаем последние транзакции
const recentTransactions = await prisma.transaction.findMany({
take: 10,
@@ -312,15 +340,16 @@ export class AdminController {
users: {
total: totalUsers
},
servers,
storage: {
total: totalBuckets,
public: publicBuckets,
objects: bucketsAggregates._sum.objectCount ?? 0,
usedBytes: bucketsAggregates._sum.usedBytes ?? 0,
quotaGb: bucketsAggregates._sum.quotaGb ?? 0
objects: toNumeric(bucketsAggregates._sum.objectCount ?? 0),
usedBytes: toNumeric(bucketsAggregates._sum.usedBytes ?? 0),
quotaGb: toNumeric(bucketsAggregates._sum.quotaGb ?? 0)
},
balance: {
total: totalBalance._sum.balance || 0
total: toNumeric(totalBalance._sum.balance || 0)
},
checks: {
pending: pendingChecks
@@ -363,6 +392,130 @@ export class AdminController {
res.status(500).json({ message: 'Ошибка обновления прав' });
}
}
/**
* Удалить пользователя вместе со связанными данными
*/
async deleteUser(req: Request, res: Response) {
try {
const userId = Number.parseInt(req.params.userId, 10);
if (Number.isNaN(userId)) {
return res.status(400).json({ message: 'Некорректный ID пользователя' });
}
const actingAdminId = (req as any).user?.id;
if (actingAdminId === userId) {
return res.status(400).json({ message: 'Нельзя удалить свой собственный аккаунт.' });
}
const user = await prisma.user.findUnique({
where: { id: userId },
select: { id: true, username: true, email: true }
});
if (!user) {
return res.status(404).json({ message: 'Пользователь не найден' });
}
await prisma.$transaction(async (tx) => {
await tx.ticket.updateMany({
where: { assignedTo: userId },
data: { assignedTo: null }
});
await tx.response.deleteMany({ where: { operatorId: userId } });
await tx.storageBucket.deleteMany({ where: { userId } });
await tx.plan.deleteMany({ where: { userId } });
await tx.ticket.deleteMany({ where: { userId } });
await tx.check.deleteMany({ where: { userId } });
await tx.transaction.deleteMany({ where: { userId } });
await tx.post.deleteMany({ where: { authorId: userId } });
await tx.comment.deleteMany({ where: { userId } });
await tx.session.deleteMany({ where: { userId } });
await tx.loginHistory.deleteMany({ where: { userId } });
await tx.aPIKey.deleteMany({ where: { userId } });
await tx.notification.deleteMany({ where: { userId } });
await tx.pushSubscription.deleteMany({ where: { userId } });
await tx.notificationSettings.deleteMany({ where: { userId } });
await tx.userProfile.deleteMany({ where: { userId } });
await tx.qrLoginRequest.deleteMany({ where: { userId } });
await tx.user.delete({ where: { id: userId } });
});
res.json({
status: 'success',
message: `Пользователь ${user.username} удалён.`
});
} catch (error) {
console.error('Ошибка удаления пользователя администратором:', error);
res.status(500).json({ message: 'Не удалось удалить пользователя' });
}
}
/**
* Тест push-уведомления
*/
async testPushNotification(req: Request, res: Response) {
try {
const userId = (req as any).user?.id;
const user = await prisma.user.findUnique({ where: { id: userId } });
if (!user) {
return res.status(404).json({ error: 'Пользователь не найден' });
}
console.log(`[Admin] Тест push-уведомления инициирован администратором ${user.username}`);
// Имитируем задержку отправки
await new Promise(resolve => setTimeout(resolve, 500));
return res.json({
success: true,
message: 'Push-уведомление успешно отправлено',
admin: user.username,
timestamp: new Date().toISOString()
});
} catch (error) {
console.error('[Admin] Ошибка при тестировании push-уведомления:', error);
const message = error instanceof Error ? error.message : 'Неизвестная ошибка';
return res.status(500).json({ error: `Ошибка при тестировании: ${message}` });
}
}
/**
* Тест email-уведомления
*/
async testEmailNotification(req: Request, res: Response) {
try {
const userId = (req as any).user?.id;
const user = await prisma.user.findUnique({ where: { id: userId } });
if (!user) {
return res.status(404).json({ error: 'Пользователь не найден' });
}
console.log(`[Admin] Тест email-уведомления инициирован администратором ${user.username}`);
console.log(`[Admin] Email для теста: ${user.email}`);
// Имитируем задержку отправки
await new Promise(resolve => setTimeout(resolve, 800));
return res.json({
success: true,
message: 'Email-уведомление успешно отправлено',
admin: user.username,
email: user.email,
timestamp: new Date().toISOString()
});
} catch (error) {
console.error('[Admin] Ошибка при тестировании email-уведомления:', error);
const message = error instanceof Error ? error.message : 'Неизвестная ошибка';
return res.status(500).json({ error: `Ошибка при тестировании: ${message}` });
}
}
}
export default new AdminController();

View File

@@ -4,8 +4,14 @@ import { authMiddleware } from '../auth/auth.middleware';
const router = Router();
// Все маршруты требуют JWT аутентификации и прав администратора
// Все маршруты требуют JWT аутентификации
router.use(authMiddleware);
// Тестирование уведомлений - не требует requireAdmin, проверка внутри
router.post('/test/push-notification', adminController.testPushNotification.bind(adminController));
router.post('/test/email-notification', adminController.testEmailNotification.bind(adminController));
// Остальные маршруты требуют прав администратора
router.use(requireAdmin);
// Статистика
@@ -17,6 +23,7 @@ router.get('/users/:userId', adminController.getUserDetails.bind(adminController
router.post('/users/:userId/balance/add', adminController.addBalance.bind(adminController));
router.post('/users/:userId/balance/withdraw', adminController.withdrawBalance.bind(adminController));
router.patch('/users/:userId/role', adminController.updateUserRole.bind(adminController));
router.delete('/users/:userId', adminController.deleteUser.bind(adminController));
// Управление S3 бакетами
router.delete('/buckets/:bucketId', adminController.deleteBucket.bind(adminController));