new api endpoint and api rate limit
This commit is contained in:
@@ -2,6 +2,9 @@ import express from 'express';
|
||||
import cors from 'cors';
|
||||
import dotenv from 'dotenv';
|
||||
import http from 'http';
|
||||
import helmet from 'helmet';
|
||||
import rateLimit from 'express-rate-limit';
|
||||
import compression from 'compression';
|
||||
import passport from './modules/auth/passport.config';
|
||||
import authRoutes from './modules/auth/auth.routes';
|
||||
import oauthRoutes from './modules/auth/oauth.routes';
|
||||
@@ -66,6 +69,35 @@ app.use(cors({
|
||||
allowedHeaders: ['Content-Type', 'Authorization']
|
||||
}));
|
||||
|
||||
// Security headers
|
||||
app.use(helmet({
|
||||
contentSecurityPolicy: false, // Отключаем CSP для совместимости с WebSocket
|
||||
crossOriginEmbedderPolicy: false
|
||||
}));
|
||||
|
||||
// Response compression
|
||||
app.use(compression());
|
||||
|
||||
// Global rate limiter - 1000 requests per 15 minutes
|
||||
const globalLimiter = rateLimit({
|
||||
windowMs: 15 * 60 * 1000,
|
||||
max: 1000,
|
||||
message: 'Too many requests, please try again later',
|
||||
standardHeaders: true,
|
||||
legacyHeaders: false,
|
||||
});
|
||||
|
||||
// Strict rate limiter for auth endpoints - 10 requests per 15 minutes
|
||||
const authLimiter = rateLimit({
|
||||
windowMs: 15 * 60 * 1000,
|
||||
max: 10,
|
||||
message: 'Too many authentication attempts, please try again later',
|
||||
standardHeaders: true,
|
||||
legacyHeaders: false,
|
||||
});
|
||||
|
||||
app.use(globalLimiter);
|
||||
|
||||
app.use(express.json({ limit: '100mb' }));
|
||||
app.use(express.urlencoded({ limit: '100mb', extended: true }));
|
||||
app.use(passport.initialize());
|
||||
@@ -91,21 +123,8 @@ app.get('/health', (_req, res) => {
|
||||
res.json({ status: 'ok', timestamp: new Date().toISOString() });
|
||||
});
|
||||
|
||||
app.get('/', async (req, res) => {
|
||||
// Статистика WebSocket
|
||||
const wsConnectedUsers = getConnectedUsersCount();
|
||||
const wsRoomsStats = getRoomsStats();
|
||||
|
||||
res.json({
|
||||
message: 'Сервер ospab.host запущен!',
|
||||
timestamp: new Date().toISOString(),
|
||||
port: PORT,
|
||||
database: process.env.DATABASE_URL ? 'подключена' : 'НЕ НАСТРОЕНА',
|
||||
websocket: {
|
||||
connected_users: wsConnectedUsers,
|
||||
rooms: wsRoomsStats
|
||||
}
|
||||
});
|
||||
app.get('/', (_req, res) => {
|
||||
res.json({ status: 'active', message: 'ospab backend active' });
|
||||
});
|
||||
|
||||
// ==================== SITEMAP ====================
|
||||
@@ -306,8 +325,8 @@ app.use('/api/auth', (req, res, next) => {
|
||||
});
|
||||
next();
|
||||
});
|
||||
app.use('/api/auth', authRoutes);
|
||||
app.use('/api/auth', oauthRoutes);
|
||||
app.use('/api/auth', authLimiter, authRoutes);
|
||||
app.use('/api/auth', authLimiter, oauthRoutes);
|
||||
app.use('/api/admin', adminRoutes);
|
||||
app.use('/api/ticket', ticketRoutes);
|
||||
app.use('/api/check', checkRoutes);
|
||||
@@ -318,7 +337,7 @@ app.use('/api/sessions', sessionRoutes);
|
||||
app.use('/api/qr-auth', qrAuthRoutes);
|
||||
app.use('/api/storage', storageRoutes);
|
||||
|
||||
const PORT = process.env.PORT || 5000;
|
||||
const PORT = parseInt(process.env.PORT || '5000', 10);
|
||||
|
||||
import { initWebSocketServer, getConnectedUsersCount, getRoomsStats } from './websocket/server';
|
||||
import https from 'https';
|
||||
@@ -381,7 +400,7 @@ app.use((err: any, _req: any, res: any, _next: any) => {
|
||||
}
|
||||
});
|
||||
|
||||
server.listen(PORT, () => {
|
||||
server.listen(PORT, '0.0.0.0', () => {
|
||||
logger.info(`${protocolLabel} сервер запущен на порту ${PORT}`);
|
||||
logger.info(`База данных: ${process.env.DATABASE_URL ? 'подключена' : 'НЕ НАСТРОЕНА'}`);
|
||||
logger.info(`API доступен: ${normalizedApiOrigin}`);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { logger } from '../../utils/logger';
|
||||
import {
|
||||
requestPasswordChange,
|
||||
confirmPasswordChange,
|
||||
@@ -29,7 +30,7 @@ export const getAccountInfo = async (req: Request, res: Response) => {
|
||||
const userInfo = await getUserInfo(userId);
|
||||
res.json(userInfo);
|
||||
} catch (error) {
|
||||
console.error('Ошибка получения информации об аккаунте:', error);
|
||||
logger.error('Ошибка получения информации об аккаунте:', error);
|
||||
res.status(500).json({ error: 'Ошибка сервера' });
|
||||
}
|
||||
};
|
||||
@@ -76,7 +77,7 @@ export const requestPasswordChangeHandler = async (req: Request, res: Response)
|
||||
message: 'Код подтверждения отправлен на вашу почту'
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
console.error('Ошибка запроса смены пароля:', error);
|
||||
logger.error('Ошибка запроса смены пароля:', error);
|
||||
res.status(500).json({ error: getErrorMessage(error) || 'Ошибка сервера' });
|
||||
}
|
||||
};
|
||||
@@ -104,7 +105,7 @@ export const confirmPasswordChangeHandler = async (req: Request, res: Response)
|
||||
message: 'Пароль успешно изменён'
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
console.error('Ошибка подтверждения смены пароля:', error);
|
||||
logger.error('Ошибка подтверждения смены пароля:', error);
|
||||
res.status(400).json({ error: getErrorMessage(error) || 'Ошибка подтверждения' });
|
||||
}
|
||||
};
|
||||
@@ -144,7 +145,7 @@ export const requestUsernameChangeHandler = async (req: Request, res: Response)
|
||||
message: 'Код подтверждения отправлен на вашу почту'
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
console.error('Ошибка запроса смены имени:', error);
|
||||
logger.error('Ошибка запроса смены имени:', error);
|
||||
res.status(400).json({ error: getErrorMessage(error) || 'Ошибка сервера' });
|
||||
}
|
||||
};
|
||||
@@ -172,7 +173,7 @@ export const confirmUsernameChangeHandler = async (req: Request, res: Response)
|
||||
message: 'Имя пользователя успешно изменено'
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
console.error('Ошибка подтверждения смены имени:', error);
|
||||
logger.error('Ошибка подтверждения смены имени:', error);
|
||||
res.status(400).json({ error: getErrorMessage(error) || 'Ошибка подтверждения' });
|
||||
}
|
||||
};
|
||||
@@ -194,7 +195,7 @@ export const requestAccountDeletionHandler = async (req: Request, res: Response)
|
||||
message: 'Код подтверждения отправлен на вашу почту. После подтверждения аккаунт будет удалён безвозвратно.'
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
console.error('Ошибка запроса удаления аккаунта:', error);
|
||||
logger.error('Ошибка запроса удаления аккаунта:', error);
|
||||
res.status(500).json({ error: getErrorMessage(error) || 'Ошибка сервера' });
|
||||
}
|
||||
};
|
||||
@@ -222,7 +223,7 @@ export const confirmAccountDeletionHandler = async (req: Request, res: Response)
|
||||
message: 'Аккаунт успешно удалён'
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
console.error('Ошибка подтверждения удаления аккаунта:', error);
|
||||
logger.error('Ошибка подтверждения удаления аккаунта:', error);
|
||||
res.status(400).json({ error: getErrorMessage(error) || 'Ошибка подтверждения' });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { prisma } from '../../prisma/client';
|
||||
import { Prisma } from '@prisma/client';
|
||||
import bcrypt from 'bcrypt';
|
||||
import crypto from 'crypto';
|
||||
import nodemailer from 'nodemailer';
|
||||
@@ -275,7 +276,7 @@ export async function confirmAccountDeletion(userId: number, code: string): Prom
|
||||
|
||||
try {
|
||||
// Каскадное удаление всех связанных данных пользователя в правильном порядке
|
||||
await prisma.$transaction(async (tx) => {
|
||||
await prisma.$transaction(async (tx: Prisma.TransactionClient) => {
|
||||
// 1. Удаляем ответы в тикетах где пользователь является оператором
|
||||
const responses = await tx.response.deleteMany({
|
||||
where: { operatorId: userId }
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { prisma } from '../../prisma/client';
|
||||
import { Prisma } from '@prisma/client';
|
||||
import { createNotification } from '../notification/notification.controller';
|
||||
import { sendNotificationEmail } from '../notification/email.service';
|
||||
import { logger } from '../../utils/logger';
|
||||
|
||||
function toNumeric(value: unknown): number {
|
||||
if (typeof value === 'bigint') {
|
||||
@@ -35,7 +37,7 @@ export const requireAdmin = async (req: Request, res: Response, next: any) => {
|
||||
|
||||
next();
|
||||
} catch (error) {
|
||||
console.error('Ошибка проверки прав админа:', error);
|
||||
logger.error('Ошибка проверки прав админа:', error);
|
||||
res.status(500).json({ message: 'Ошибка сервера' });
|
||||
}
|
||||
};
|
||||
@@ -69,7 +71,7 @@ export class AdminController {
|
||||
|
||||
res.json({ status: 'success', data: users });
|
||||
} catch (error) {
|
||||
console.error('Ошибка получения пользователей:', error);
|
||||
logger.error('Ошибка получения пользователей:', error);
|
||||
res.status(500).json({ message: 'Ошибка получения пользователей' });
|
||||
}
|
||||
}
|
||||
@@ -118,7 +120,7 @@ export class AdminController {
|
||||
|
||||
res.json({ status: 'success', data: safeUser });
|
||||
} catch (error) {
|
||||
console.error('Ошибка получения данных пользователя:', error);
|
||||
logger.error('Ошибка получения данных пользователя:', error);
|
||||
res.status(500).json({ message: 'Ошибка получения данных' });
|
||||
}
|
||||
}
|
||||
@@ -177,7 +179,7 @@ export class AdminController {
|
||||
newBalance: balanceAfter
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Ошибка пополнения баланса:', error);
|
||||
logger.error('Ошибка пополнения баланса:', error);
|
||||
res.status(500).json({ message: 'Ошибка пополнения баланса' });
|
||||
}
|
||||
}
|
||||
@@ -240,7 +242,7 @@ export class AdminController {
|
||||
newBalance: balanceAfter
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Ошибка списания средств:', error);
|
||||
logger.error('Ошибка списания средств:', error);
|
||||
res.status(500).json({ message: 'Ошибка списания средств' });
|
||||
}
|
||||
}
|
||||
@@ -279,7 +281,7 @@ export class AdminController {
|
||||
message: `Бакет «${bucket.name}» удалён`
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Ошибка удаления бакета:', error);
|
||||
logger.error('Ошибка удаления бакета:', error);
|
||||
res.status(500).json({ message: 'Ошибка удаления бакета' });
|
||||
}
|
||||
}
|
||||
@@ -372,7 +374,7 @@ export class AdminController {
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Ошибка получения статистики:', error);
|
||||
logger.error('Ошибка получения статистики:', error);
|
||||
res.status(500).json({ message: 'Ошибка получения статистики' });
|
||||
}
|
||||
}
|
||||
@@ -399,7 +401,7 @@ export class AdminController {
|
||||
message: 'Права пользователя обновлены'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Ошибка обновления прав:', error);
|
||||
logger.error('Ошибка обновления прав:', error);
|
||||
res.status(500).json({ message: 'Ошибка обновления прав' });
|
||||
}
|
||||
}
|
||||
@@ -428,7 +430,7 @@ export class AdminController {
|
||||
return res.status(404).json({ message: 'Пользователь не найден' });
|
||||
}
|
||||
|
||||
await prisma.$transaction(async (tx) => {
|
||||
await prisma.$transaction(async (tx: Prisma.TransactionClient) => {
|
||||
await tx.ticket.updateMany({
|
||||
where: { assignedTo: userId },
|
||||
data: { assignedTo: null }
|
||||
@@ -460,7 +462,7 @@ export class AdminController {
|
||||
message: `Пользователь ${user.username} удалён.`
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Ошибка удаления пользователя администратором:', error);
|
||||
logger.error('Ошибка удаления пользователя администратором:', error);
|
||||
res.status(500).json({ message: 'Не удалось удалить пользователя' });
|
||||
}
|
||||
}
|
||||
@@ -479,7 +481,7 @@ export class AdminController {
|
||||
|
||||
const now = new Date().toISOString();
|
||||
const logMsg = `[Admin] PUSH-TEST | userId=${user.id} | username=${user.username} | email=${user.email} | time=${now}`;
|
||||
console.log(logMsg);
|
||||
logger.info(logMsg);
|
||||
|
||||
// Здесь должна быть реальная отправка push (имитация)
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
@@ -497,7 +499,7 @@ export class AdminController {
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('[Admin] Ошибка при тестировании push-уведомления:', error);
|
||||
logger.error('[Admin] Ошибка при тестировании push-уведомления:', error);
|
||||
const message = error instanceof Error ? error.message : 'Неизвестная ошибка';
|
||||
return res.status(500).json({ error: `Ошибка при тестировании: ${message}` });
|
||||
}
|
||||
@@ -521,7 +523,7 @@ export class AdminController {
|
||||
|
||||
const now = new Date().toISOString();
|
||||
const logMsg = `[Admin] EMAIL-TEST | userId=${user.id} | username=${user.username} | email=${user.email} | time=${now}`;
|
||||
console.log(logMsg);
|
||||
logger.info(logMsg);
|
||||
|
||||
// Отправляем реальное email уведомление
|
||||
const emailResult = await sendNotificationEmail({
|
||||
@@ -562,7 +564,7 @@ export class AdminController {
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('[Admin] Ошибка при тестировании email-уведомления:', error);
|
||||
logger.error('[Admin] Ошибка при тестировании email-уведомления:', error);
|
||||
const message = error instanceof Error ? error.message : 'Неизвестная ошибка';
|
||||
return res.status(500).json({ error: `Ошибка при тестировании: ${message}` });
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import { prisma } from '../../prisma/client';
|
||||
import { logger } from '../../utils/logger';
|
||||
const JWT_SECRET = process.env.JWT_SECRET || 'your_super_secret_key';
|
||||
|
||||
export const authMiddleware = async (req: Request, res: Response, next: NextFunction) => {
|
||||
@@ -19,14 +20,14 @@ export const authMiddleware = async (req: Request, res: Response, next: NextFunc
|
||||
const user = await prisma.user.findUnique({ where: { id: decoded.id } });
|
||||
|
||||
if (!user) {
|
||||
console.warn(`[Auth] Пользователь с ID ${decoded.id} не найден, токен отклонён`);
|
||||
logger.warn(`[Auth] Пользователь с ID ${decoded.id} не найден, токен отклонён`);
|
||||
return res.status(401).json({ message: 'Сессия недействительна. Авторизуйтесь снова.' });
|
||||
}
|
||||
|
||||
req.user = user;
|
||||
return next();
|
||||
} catch (error) {
|
||||
console.error('Ошибка в мидлваре аутентификации:', error);
|
||||
logger.error('Ошибка в мидлваре аутентификации:', error);
|
||||
if (error instanceof jwt.JsonWebTokenError) {
|
||||
return res.status(401).json({ message: 'Неверный или просроченный токен.' });
|
||||
}
|
||||
@@ -65,18 +66,18 @@ export const optionalAuthMiddleware = async (req: Request, res: Response, next:
|
||||
const decoded = jwt.verify(token, JWT_SECRET) as { id: number };
|
||||
const user = await prisma.user.findUnique({ where: { id: decoded.id } });
|
||||
if (!user) {
|
||||
console.warn(`[Auth][optional] Пользователь с ID ${decoded.id} не найден, токен отклонён`);
|
||||
logger.warn(`[Auth][optional] Пользователь с ID ${decoded.id} не найден, токен отклонён`);
|
||||
return res.status(401).json({ message: 'Сессия недействительна. Авторизуйтесь снова.' });
|
||||
}
|
||||
req.user = user;
|
||||
} catch (err) {
|
||||
console.warn('[Auth][optional] Ошибка проверки токена:', err);
|
||||
logger.warn('[Auth][optional] Ошибка проверки токена:', err);
|
||||
return res.status(401).json({ message: 'Неверный или просроченный токен.' });
|
||||
}
|
||||
|
||||
return next();
|
||||
} catch (error) {
|
||||
console.error('Ошибка в optionalAuthMiddleware:', error);
|
||||
logger.error('Ошибка в optionalAuthMiddleware:', error);
|
||||
return res.status(503).json({ message: 'Авторизация временно недоступна. Попробуйте позже.' });
|
||||
}
|
||||
};
|
||||
@@ -1,4 +1,5 @@
|
||||
import axios from 'axios';
|
||||
import { logger } from '../../utils/logger';
|
||||
|
||||
const TURNSTILE_SECRET_KEY = process.env.TURNSTILE_SECRET_KEY;
|
||||
const TURNSTILE_VERIFY_URL = 'https://challenges.cloudflare.com/turnstile/v0/siteverify';
|
||||
@@ -20,7 +21,7 @@ export async function validateTurnstileToken(
|
||||
remoteip?: string
|
||||
): Promise<TurnstileValidationResult> {
|
||||
if (!TURNSTILE_SECRET_KEY) {
|
||||
console.error('TURNSTILE_SECRET_KEY не найден в переменных окружения');
|
||||
logger.error('TURNSTILE_SECRET_KEY не найден в переменных окружения');
|
||||
return {
|
||||
success: false,
|
||||
message: 'Turnstile не настроен на сервере',
|
||||
@@ -60,7 +61,7 @@ export async function validateTurnstileToken(
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Ошибка при валидации Turnstile:', error);
|
||||
logger.error('Ошибка при валидации Turnstile:', error);
|
||||
return {
|
||||
success: false,
|
||||
message: 'Ошибка при проверке капчи',
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { prisma } from '../../prisma/client';
|
||||
import { logger } from '../../utils/logger';
|
||||
|
||||
// Получить все опубликованные посты (публичный доступ)
|
||||
export const getAllPosts = async (req: Request, res: Response) => {
|
||||
@@ -19,7 +20,7 @@ export const getAllPosts = async (req: Request, res: Response) => {
|
||||
|
||||
res.json({ success: true, data: posts });
|
||||
} catch (error) {
|
||||
console.error('Ошибка получения постов:', error);
|
||||
logger.error('Ошибка получения постов:', error);
|
||||
res.status(500).json({ success: false, message: 'Ошибка сервера' });
|
||||
}
|
||||
};
|
||||
@@ -59,7 +60,7 @@ export const getPostByUrl = async (req: Request, res: Response) => {
|
||||
|
||||
res.json({ success: true, data: post });
|
||||
} catch (error) {
|
||||
console.error('Ошибка получения поста:', error);
|
||||
logger.error('Ошибка получения поста:', error);
|
||||
res.status(500).json({ success: false, message: 'Ошибка сервера' });
|
||||
}
|
||||
};
|
||||
@@ -105,7 +106,7 @@ export const addComment = async (req: Request, res: Response) => {
|
||||
|
||||
res.json({ success: true, data: comment, message: 'Комментарий отправлен на модерацию' });
|
||||
} catch (error) {
|
||||
console.error('Ошибка добавления комментария:', error);
|
||||
logger.error('Ошибка добавления комментария:', error);
|
||||
res.status(500).json({ success: false, message: 'Ошибка сервера' });
|
||||
}
|
||||
};
|
||||
@@ -129,7 +130,7 @@ export const getAllPostsAdmin = async (req: Request, res: Response) => {
|
||||
|
||||
res.json({ success: true, data: posts });
|
||||
} catch (error) {
|
||||
console.error('Ошибка получения постов:', error);
|
||||
logger.error('Ошибка получения постов:', error);
|
||||
res.status(500).json({ success: false, message: 'Ошибка сервера' });
|
||||
}
|
||||
};
|
||||
@@ -157,7 +158,7 @@ export const getPostByIdAdmin = async (req: Request, res: Response) => {
|
||||
|
||||
res.json({ success: true, data: post });
|
||||
} catch (error) {
|
||||
console.error('Ошибка получения поста:', error);
|
||||
logger.error('Ошибка получения поста:', error);
|
||||
res.status(500).json({ success: false, message: 'Ошибка сервера' });
|
||||
}
|
||||
};
|
||||
@@ -201,7 +202,7 @@ export const createPost = async (req: Request, res: Response) => {
|
||||
|
||||
res.json({ success: true, data: post });
|
||||
} catch (error) {
|
||||
console.error('Ошибка создания поста:', error);
|
||||
logger.error('Ошибка создания поста:', error);
|
||||
res.status(500).json({ success: false, message: 'Ошибка сервера' });
|
||||
}
|
||||
};
|
||||
@@ -244,7 +245,7 @@ export const updatePost = async (req: Request, res: Response) => {
|
||||
|
||||
res.json({ success: true, data: post });
|
||||
} catch (error) {
|
||||
console.error('Ошибка обновления поста:', error);
|
||||
logger.error('Ошибка обновления поста:', error);
|
||||
res.status(500).json({ success: false, message: 'Ошибка сервера' });
|
||||
}
|
||||
};
|
||||
@@ -260,7 +261,7 @@ export const deletePost = async (req: Request, res: Response) => {
|
||||
|
||||
res.json({ success: true, message: 'Пост удалён' });
|
||||
} catch (error) {
|
||||
console.error('Ошибка удаления поста:', error);
|
||||
logger.error('Ошибка удаления поста:', error);
|
||||
res.status(500).json({ success: false, message: 'Ошибка сервера' });
|
||||
}
|
||||
};
|
||||
@@ -282,7 +283,7 @@ export const getAllComments = async (req: Request, res: Response) => {
|
||||
|
||||
res.json({ success: true, data: comments });
|
||||
} catch (error) {
|
||||
console.error('Ошибка получения комментариев:', error);
|
||||
logger.error('Ошибка получения комментариев:', error);
|
||||
res.status(500).json({ success: false, message: 'Ошибка сервера' });
|
||||
}
|
||||
};
|
||||
@@ -304,7 +305,7 @@ export const moderateComment = async (req: Request, res: Response) => {
|
||||
|
||||
res.json({ success: true, data: comment });
|
||||
} catch (error) {
|
||||
console.error('Ошибка модерации комментария:', error);
|
||||
logger.error('Ошибка модерации комментария:', error);
|
||||
res.status(500).json({ success: false, message: 'Ошибка сервера' });
|
||||
}
|
||||
};
|
||||
@@ -320,7 +321,7 @@ export const deleteComment = async (req: Request, res: Response) => {
|
||||
|
||||
res.json({ success: true, message: 'Комментарий удалён' });
|
||||
} catch (error) {
|
||||
console.error('Ошибка удаления комментария:', error);
|
||||
logger.error('Ошибка удаления комментария:', error);
|
||||
res.status(500).json({ success: false, message: 'Ошибка сервера' });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { Request, Response } from 'express';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import { logger } from '../../utils/logger';
|
||||
|
||||
export const uploadImage = async (req: Request, res: Response) => {
|
||||
try {
|
||||
@@ -23,7 +24,7 @@ export const uploadImage = async (req: Request, res: Response) => {
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Ошибка загрузки изображения:', error);
|
||||
logger.error('Ошибка загрузки изображения:', error);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: 'Ошибка загрузки изображения'
|
||||
@@ -58,7 +59,7 @@ export const deleteImage = async (req: Request, res: Response) => {
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Ошибка удаления изображения:', error);
|
||||
logger.error('Ошибка удаления изображения:', error);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: 'Ошибка удаления изображения'
|
||||
|
||||
@@ -114,7 +114,7 @@ export const getNotifications = async (req: Request, res: Response) => {
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Ошибка получения уведомлений:', error);
|
||||
logger.error('Ошибка получения уведомлений:', error);
|
||||
res.status(500).json({ success: false, message: 'Ошибка сервера' });
|
||||
}
|
||||
};
|
||||
@@ -134,7 +134,7 @@ export const getUnreadCount = async (req: Request, res: Response) => {
|
||||
|
||||
res.json({ success: true, count });
|
||||
} catch (error) {
|
||||
console.error('Ошибка подсчета непрочитанных:', error);
|
||||
logger.error('Ошибка подсчета непрочитанных:', error);
|
||||
res.status(500).json({ success: false, message: 'Ошибка сервера' });
|
||||
}
|
||||
};
|
||||
@@ -174,7 +174,7 @@ export const markAsRead = async (req: Request, res: Response) => {
|
||||
|
||||
res.json({ success: true, message: 'Отмечено как прочитанное' });
|
||||
} catch (error) {
|
||||
console.error('Ошибка отметки уведомления:', error);
|
||||
logger.error('Ошибка отметки уведомления:', error);
|
||||
res.status(500).json({ success: false, message: 'Ошибка сервера' });
|
||||
}
|
||||
};
|
||||
@@ -195,7 +195,7 @@ export const markAllAsRead = async (req: Request, res: Response) => {
|
||||
|
||||
res.json({ success: true, message: 'Все уведомления прочитаны' });
|
||||
} catch (error) {
|
||||
console.error('Ошибка отметки всех уведомлений:', error);
|
||||
logger.error('Ошибка отметки всех уведомлений:', error);
|
||||
res.status(500).json({ success: false, message: 'Ошибка сервера' });
|
||||
}
|
||||
};
|
||||
@@ -234,7 +234,7 @@ export const deleteNotification = async (req: Request, res: Response) => {
|
||||
|
||||
res.json({ success: true, message: 'Уведомление удалено' });
|
||||
} catch (error) {
|
||||
console.error('Ошибка удаления уведомления:', error);
|
||||
logger.error('Ошибка удаления уведомления:', error);
|
||||
res.status(500).json({ success: false, message: 'Ошибка сервера' });
|
||||
}
|
||||
};
|
||||
@@ -254,7 +254,7 @@ export const deleteAllRead = async (req: Request, res: Response) => {
|
||||
|
||||
res.json({ success: true, message: 'Прочитанные уведомления удалены' });
|
||||
} catch (error) {
|
||||
console.error('Ошибка удаления прочитанных:', error);
|
||||
logger.error('Ошибка удаления прочитанных:', error);
|
||||
res.status(500).json({ success: false, message: 'Ошибка сервера' });
|
||||
}
|
||||
};
|
||||
@@ -322,7 +322,7 @@ export async function createNotification(params: CreateNotificationParams) {
|
||||
}
|
||||
});
|
||||
} catch (pushError) {
|
||||
console.error('Ошибка отправки Push:', pushError);
|
||||
logger.error('Ошибка отправки Push:', pushError);
|
||||
// Не прерываем выполнение если Push не отправился
|
||||
}
|
||||
} else {
|
||||
@@ -346,7 +346,7 @@ export async function createNotification(params: CreateNotificationParams) {
|
||||
logger.warn(`[Email] Уведомление ${notification.id} пропущено: ${result.message}`);
|
||||
}
|
||||
} catch (emailError) {
|
||||
console.error('Ошибка отправки email уведомления:', emailError);
|
||||
logger.error('Ошибка отправки email уведомления:', emailError);
|
||||
}
|
||||
} else if (!email) {
|
||||
logger.debug(`Email уведомление для пользователя ${params.userId} пропущено: отсутствует адрес`);
|
||||
@@ -356,7 +356,7 @@ export async function createNotification(params: CreateNotificationParams) {
|
||||
|
||||
return notification;
|
||||
} catch (error) {
|
||||
console.error('Ошибка создания уведомления:', error);
|
||||
logger.error('Ошибка создания уведомления:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -367,7 +367,7 @@ export const getVapidKey = async (req: Request, res: Response) => {
|
||||
const publicKey = getVapidPublicKey();
|
||||
res.json({ success: true, publicKey });
|
||||
} catch (error) {
|
||||
console.error('Ошибка получения VAPID ключа:', error);
|
||||
logger.error('Ошибка получения VAPID ключа:', error);
|
||||
res.status(500).json({ success: false, message: 'Ошибка сервера' });
|
||||
}
|
||||
};
|
||||
@@ -388,7 +388,7 @@ export const subscribe = async (req: Request, res: Response) => {
|
||||
|
||||
res.json({ success: true, message: 'Push-уведомления подключены' });
|
||||
} catch (error) {
|
||||
console.error('Ошибка подписки на Push:', error);
|
||||
logger.error('Ошибка подписки на Push:', error);
|
||||
res.status(500).json({ success: false, message: 'Ошибка сервера' });
|
||||
}
|
||||
};
|
||||
@@ -408,7 +408,7 @@ export const unsubscribe = async (req: Request, res: Response) => {
|
||||
|
||||
res.json({ success: true, message: 'Push-уведомления отключены' });
|
||||
} catch (error) {
|
||||
console.error('Ошибка отписки от Push:', error);
|
||||
logger.error('Ошибка отписки от Push:', error);
|
||||
res.status(500).json({ success: false, message: 'Ошибка сервера' });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import webpush from 'web-push';
|
||||
import { prisma } from '../../prisma/client';
|
||||
import { logger } from '../../utils/logger';
|
||||
|
||||
// VAPID ключи (нужно сгенерировать один раз и сохранить в .env)
|
||||
// Для генерации: npx web-push generate-vapid-keys
|
||||
@@ -55,7 +56,7 @@ export async function subscribePush(userId: number, subscription: {
|
||||
|
||||
return pushSubscription;
|
||||
} catch (error) {
|
||||
console.error('Ошибка сохранения Push-подписки:', error);
|
||||
logger.error('Ошибка сохранения Push-подписки:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -70,7 +71,7 @@ export async function unsubscribePush(userId: number, endpoint: string) {
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Ошибка удаления Push-подписки:', error);
|
||||
logger.error('Ошибка удаления Push-подписки:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -130,14 +131,14 @@ export async function sendPushNotification(
|
||||
where: { id: sub.id }
|
||||
});
|
||||
} else {
|
||||
console.error(`Ошибка отправки Push на ${sub.endpoint}:`, error);
|
||||
logger.error(`Ошибка отправки Push на ${sub.endpoint}:`, error);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
await Promise.allSettled(promises);
|
||||
} catch (error) {
|
||||
console.error('Ошибка отправки Push-уведомлений:', error);
|
||||
logger.error('Ошибка отправки Push-уведомлений:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ export async function createQRLoginRequest(req: Request, res: Response) {
|
||||
});
|
||||
|
||||
// Ensure QR creation is visible in production logs: write directly to stdout
|
||||
console.log('[QR Create] Создан QR-запрос', JSON.stringify({
|
||||
logger.info('[QR Create] Создан QR-запрос', JSON.stringify({
|
||||
code: qrRequest.code,
|
||||
ipAddress: qrRequest.ipAddress,
|
||||
userAgent: qrRequest.userAgent?.toString?.().slice(0, 200),
|
||||
|
||||
@@ -81,7 +81,7 @@ export async function getUserSessions(req: Request, res: Response) {
|
||||
|
||||
res.json(sessionsWithCurrent);
|
||||
} catch (error) {
|
||||
console.error('Ошибка получения сессий:', error);
|
||||
logger.error('Ошибка получения сессий:', error);
|
||||
res.status(500).json({ error: 'Ошибка получения сессий' });
|
||||
}
|
||||
}
|
||||
@@ -109,7 +109,7 @@ export async function deleteSession(req: Request, res: Response) {
|
||||
|
||||
res.json({ message: 'Сессия удалена' });
|
||||
} catch (error) {
|
||||
console.error('Ошибка удаления сессии:', error);
|
||||
logger.error('Ошибка удаления сессии:', error);
|
||||
res.status(500).json({ error: 'Ошибка удаления сессии' });
|
||||
}
|
||||
}
|
||||
@@ -136,7 +136,7 @@ export async function deleteAllOtherSessions(req: Request, res: Response) {
|
||||
deletedCount: result.count
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Ошибка удаления сессий:', error);
|
||||
logger.error('Ошибка удаления сессий:', error);
|
||||
res.status(500).json({ error: 'Ошибка удаления сессий' });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { prisma } from '../../prisma/client';
|
||||
import { logger } from '../../utils/logger';
|
||||
|
||||
export async function generateSitemap(req: Request, res: Response) {
|
||||
try {
|
||||
@@ -51,7 +52,7 @@ export async function generateSitemap(req: Request, res: Response) {
|
||||
res.header('Content-Type', 'application/xml');
|
||||
res.send(xml);
|
||||
} catch (error) {
|
||||
console.error('Ошибка генерации sitemap:', error);
|
||||
logger.error('Ошибка генерации sitemap:', error);
|
||||
res.status(500).json({ error: 'Ошибка генерации sitemap' });
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import axios from 'axios';
|
||||
import { logger } from '../../utils/logger';
|
||||
|
||||
/**
|
||||
* Cloudflare API для проверки файлов
|
||||
@@ -32,7 +33,7 @@ export async function scanFileWithVirusTotal(
|
||||
fileName: string,
|
||||
): Promise<FileScanResult> {
|
||||
if (!VIRUSTOTAL_API_KEY) {
|
||||
console.warn('[FileScanner] VirusTotal API key не настроена');
|
||||
logger.warn('[FileScanner] VirusTotal API key не настроена');
|
||||
return {
|
||||
isSafe: true,
|
||||
detections: 0,
|
||||
@@ -82,7 +83,7 @@ export async function scanFileWithVirusTotal(
|
||||
return uploadFileForAnalysis(fileBuffer, fileName);
|
||||
}
|
||||
|
||||
console.error('[FileScanner] Ошибка при проверке по хешу:', hashError);
|
||||
logger.error('[FileScanner] Ошибка при проверке по хешу:', hashError);
|
||||
throw new Error('Не удалось проверить файл на вирусы');
|
||||
}
|
||||
}
|
||||
@@ -117,7 +118,7 @@ async function uploadFileForAnalysis(
|
||||
|
||||
return analysisResult;
|
||||
} catch (error) {
|
||||
console.error('[FileScanner] Ошибка при загрузке файла на анализ:', error);
|
||||
logger.error('[FileScanner] Ошибка при загрузке файла на анализ:', error);
|
||||
throw new Error('Не удалось загрузить файл на анализ');
|
||||
}
|
||||
}
|
||||
@@ -167,7 +168,7 @@ async function waitForAnalysisCompletion(
|
||||
// Ждём перед следующей попыткой
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
} catch (error) {
|
||||
console.error(`[FileScanner] Ошибка при проверке статуса анализа (попытка ${attempt + 1}):`, error);
|
||||
logger.error(`[FileScanner] Ошибка при проверке статуса анализа (попытка ${attempt + 1}):`, error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -260,7 +261,7 @@ export async function validateFileForUpload(
|
||||
};
|
||||
} catch (error) {
|
||||
// Если сканирование не удалось, позволяем загрузку, но логируем ошибку
|
||||
console.error('[FileScanner] Ошибка сканирования:', error);
|
||||
logger.error('[FileScanner] Ошибка сканирования:', error);
|
||||
return {
|
||||
isValid: true, // Не блокируем загрузку при ошибке сканирования
|
||||
};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Router } from 'express';
|
||||
import axios from 'axios';
|
||||
import { logger } from '../../utils/logger';
|
||||
import {
|
||||
createBucket,
|
||||
listBuckets,
|
||||
@@ -62,7 +63,7 @@ router.put('/plans/:id', authMiddleware, async (req, res) => {
|
||||
|
||||
return res.json({ success: true, plan: updated });
|
||||
} catch (error) {
|
||||
console.error('[Storage] Ошибка обновления тарифа:', error);
|
||||
logger.error('[Storage] Ошибка обновления тарифа:', error);
|
||||
const message = error instanceof Error ? error.message : 'Не удалось обновить тариф';
|
||||
return res.status(500).json({ error: message });
|
||||
}
|
||||
@@ -73,7 +74,7 @@ router.get('/plans', async (_req, res) => {
|
||||
const plans = await listStoragePlans();
|
||||
return res.json({ plans });
|
||||
} catch (error) {
|
||||
console.error('[Storage] Ошибка получения тарифов:', error);
|
||||
logger.error('[Storage] Ошибка получения тарифов:', error);
|
||||
return res.status(500).json({ error: 'Не удалось загрузить тарифы' });
|
||||
}
|
||||
});
|
||||
@@ -83,7 +84,7 @@ router.get('/regions', async (_req, res) => {
|
||||
const regions = await listStorageRegions();
|
||||
return res.json({ regions });
|
||||
} catch (error) {
|
||||
console.error('[Storage] Ошибка получения регионов:', error);
|
||||
logger.error('[Storage] Ошибка получения регионов:', error);
|
||||
return res.status(500).json({ error: 'Не удалось загрузить список регионов' });
|
||||
}
|
||||
});
|
||||
@@ -93,7 +94,7 @@ router.get('/classes', async (_req, res) => {
|
||||
const classes = await listStorageClasses();
|
||||
return res.json({ classes });
|
||||
} catch (error) {
|
||||
console.error('[Storage] Ошибка получения классов хранения:', error);
|
||||
logger.error('[Storage] Ошибка получения классов хранения:', error);
|
||||
return res.status(500).json({ error: 'Не удалось загрузить список классов хранения' });
|
||||
}
|
||||
});
|
||||
@@ -103,7 +104,7 @@ router.get('/status', async (_req, res) => {
|
||||
const status = await getStorageStatus();
|
||||
return res.json(status);
|
||||
} catch (error) {
|
||||
console.error('[Storage] Ошибка получения статуса хранилища:', error);
|
||||
logger.error('[Storage] Ошибка получения статуса хранилища:', error);
|
||||
return res.status(500).json({ error: 'Не удалось получить статус хранилища' });
|
||||
}
|
||||
});
|
||||
@@ -128,7 +129,7 @@ router.post('/checkout', optionalAuthMiddleware, async (req, res) => {
|
||||
return res.json(session);
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Не удалось создать корзину';
|
||||
console.error('[Storage] Ошибка создания корзины:', error);
|
||||
logger.error('[Storage] Ошибка создания корзины:', error);
|
||||
return res.status(400).json({ error: message });
|
||||
}
|
||||
});
|
||||
@@ -323,20 +324,20 @@ router.post('/buckets/:id/objects/download-from-uri', async (req, res) => {
|
||||
const id = Number(req.params.id);
|
||||
const { url } = req.body ?? {};
|
||||
|
||||
console.log(`[Storage URI Download] Начало загрузки - userId: ${userId}, bucketId: ${id}, url: ${url}`);
|
||||
logger.info(`[Storage URI Download] Начало загрузки - userId: ${userId}, bucketId: ${id}, url: ${url}`);
|
||||
|
||||
if (!url) {
|
||||
console.log('[Storage URI Download] Ошибка: URL не указан');
|
||||
logger.info('[Storage URI Download] Ошибка: URL не указан');
|
||||
return res.status(400).json({ error: 'Не указан URL' });
|
||||
}
|
||||
|
||||
// Проверяем что пользователь имеет доступ к бакету
|
||||
console.log('[Storage URI Download] Проверка доступа к бакету...');
|
||||
logger.info('[Storage URI Download] Проверка доступа к бакету...');
|
||||
await getBucket(userId, id); // Проверка доступа
|
||||
console.log('[Storage URI Download] Доступ к бакету подтверждён');
|
||||
logger.info('[Storage URI Download] Доступ к бакету подтверждён');
|
||||
|
||||
// Загружаем файл с URL с увеличенным timeout
|
||||
console.log(`[Storage URI Download] Загрузка файла с ${url}...`);
|
||||
logger.info(`[Storage URI Download] Загрузка файла с ${url}...`);
|
||||
const response = await axios.get(url, {
|
||||
responseType: 'arraybuffer',
|
||||
timeout: 120000, // 120 seconds (2 minutes)
|
||||
@@ -350,11 +351,11 @@ router.post('/buckets/:id/objects/download-from-uri', async (req, res) => {
|
||||
const buffer = response.data;
|
||||
const bufferSize = Buffer.isBuffer(buffer) ? buffer.length : (buffer as ArrayBuffer).byteLength;
|
||||
|
||||
console.log(`[Storage URI Download] Файл загружен успешно - размер: ${bufferSize} байт, mimeType: ${mimeType}`);
|
||||
logger.info(`[Storage URI Download] Файл загружен успешно - размер: ${bufferSize} байт, mimeType: ${mimeType}`);
|
||||
|
||||
// Конвертируем в base64
|
||||
const base64Data = Buffer.isBuffer(buffer) ? buffer.toString('base64') : Buffer.from(buffer).toString('base64');
|
||||
console.log(`[Storage URI Download] Base64 длина: ${base64Data.length} символов`);
|
||||
logger.info(`[Storage URI Download] Base64 длина: ${base64Data.length} символов`);
|
||||
|
||||
return res.json({
|
||||
blob: base64Data,
|
||||
@@ -362,10 +363,10 @@ router.post('/buckets/:id/objects/download-from-uri', async (req, res) => {
|
||||
});
|
||||
} catch (e: unknown) {
|
||||
let message = 'Ошибка загрузки файла по URI';
|
||||
console.error('[Storage URI Download] Ошибка:', e);
|
||||
logger.error('[Storage URI Download] Ошибка:', e);
|
||||
|
||||
if (axios.isAxiosError(e)) {
|
||||
console.error('[Storage URI Download] Axios ошибка:', {
|
||||
logger.error('[Storage URI Download] Axios ошибка:', {
|
||||
status: e.response?.status,
|
||||
statusText: e.response?.statusText,
|
||||
headers: e.response?.headers,
|
||||
@@ -388,7 +389,7 @@ router.post('/buckets/:id/objects/download-from-uri', async (req, res) => {
|
||||
message = e.message;
|
||||
}
|
||||
|
||||
console.error('[Storage URI Download] Возвращаем ошибку клиенту:', message);
|
||||
logger.error('[Storage URI Download] Возвращаем ошибку клиенту:', message);
|
||||
return res.status(400).json({ error: message });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -4,6 +4,8 @@ import axios from 'axios';
|
||||
import { exec } from 'child_process';
|
||||
import { promisify } from 'util';
|
||||
import type { StorageBucket } from '@prisma/client';
|
||||
import { Prisma } from '@prisma/client';
|
||||
import { logger } from '../../utils/logger';
|
||||
|
||||
import { prisma } from '../../prisma/client';
|
||||
import { ensureBucketExists, buildPhysicalBucketName, minioClient } from './minioClient';
|
||||
@@ -57,11 +59,11 @@ async function ensureMinioAlias(): Promise<void> {
|
||||
try {
|
||||
// Quick check that alias exists and works
|
||||
await execAsync(`mc admin info ${MINIO_ALIAS}`, { timeout: 10000 });
|
||||
console.info(`[MinIO Admin] Using existing alias: ${MINIO_ALIAS}`);
|
||||
logger.info(`[MinIO Admin] Using existing alias: ${MINIO_ALIAS}`);
|
||||
minioAliasConfigured = true;
|
||||
return;
|
||||
} catch (error) {
|
||||
console.error(`[MinIO Admin] Existing alias "${MINIO_ALIAS}" not working:`, (error as Error).message);
|
||||
logger.error(`[MinIO Admin] Existing alias "${MINIO_ALIAS}" not working:`, (error as Error).message);
|
||||
throw new Error(`mc alias "${MINIO_ALIAS}" не настроен или не работает. Настройте вручную: mc alias set ${MINIO_ALIAS} <url> <access> <secret>`);
|
||||
}
|
||||
}
|
||||
@@ -80,10 +82,10 @@ async function ensureMinioAlias(): Promise<void> {
|
||||
const escapedSecretKey = escapeShellArg(MINIO_SECRET_KEY);
|
||||
const setupAliasCmd = `mc alias set ${MINIO_ALIAS} "${MINIO_ADMIN_URL}" "${escapedAccessKey}" "${escapedSecretKey}" --api S3v4`;
|
||||
const { stdout, stderr } = await execAsync(setupAliasCmd, { timeout: 10000 });
|
||||
console.info(`[MinIO Admin] Alias configured:`, stdout.trim() || stderr.trim() || 'OK');
|
||||
logger.info(`[MinIO Admin] Alias configured:`, stdout.trim() || stderr.trim() || 'OK');
|
||||
minioAliasConfigured = true;
|
||||
} catch (error) {
|
||||
console.error('[MinIO Admin] Failed to configure alias:', (error as Error).message);
|
||||
logger.error('[MinIO Admin] Failed to configure alias:', (error as Error).message);
|
||||
throw new Error(`Не удалось настроить подключение к MinIO: ${(error as Error).message}`);
|
||||
}
|
||||
}
|
||||
@@ -205,7 +207,7 @@ function logConsoleWarning(error: unknown) {
|
||||
return;
|
||||
}
|
||||
consoleSupportLogged = true;
|
||||
console.warn('[Storage] Таблица storage_console_credential недоступна. Продолжаем без данных консоли.', error);
|
||||
logger.warn('[Storage] Таблица storage_console_credential недоступна. Продолжаем без данных консоли.', error);
|
||||
}
|
||||
|
||||
async function ensureConsoleCredentialSupport(client: any = prisma): Promise<boolean> {
|
||||
@@ -230,11 +232,11 @@ async function ensureConsoleCredentialSupport(client: any = prisma): Promise<boo
|
||||
} catch (error) {
|
||||
// If the error is a MinIO authentication error or bucket not found, surface a clear message and skip cleanup
|
||||
if ((error as any)?.code === 'MINIO_AUTH_ERROR') {
|
||||
console.error('[Storage] MinIO authentication error while creating bucket — check MINIO_ACCESS_KEY/MINIO_SECRET_KEY');
|
||||
logger.error('[Storage] MinIO authentication error while creating bucket — check MINIO_ACCESS_KEY/MINIO_SECRET_KEY');
|
||||
throw new Error('MinIO authentication failed. Пожалуйста, проверьте настройки MINIO_ACCESS_KEY и MINIO_SECRET_KEY.');
|
||||
}
|
||||
if ((error as any)?.code === 'MINIO_BUCKET_NOT_FOUND') {
|
||||
console.warn('[Storage] MinIO reports bucket inaccessible or not found during create; skipping cleanup');
|
||||
logger.warn('[Storage] MinIO reports bucket inaccessible or not found during create; skipping cleanup');
|
||||
throw new Error('MinIO bucket not found or inaccessible. Проверьте доступность MinIO и права доступа.');
|
||||
}
|
||||
if (isConsoleCredentialError(error)) {
|
||||
@@ -380,7 +382,7 @@ function generateConsolePassword(): string {
|
||||
*/
|
||||
async function createMinioUser(username: string, password: string): Promise<void> {
|
||||
if (!MINIO_MC_ENABLED) {
|
||||
console.warn(`[MinIO Admin] mc CLI disabled, skipping user creation for ${username}`);
|
||||
logger.warn(`[MinIO Admin] mc CLI disabled, skipping user creation for ${username}`);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -393,21 +395,21 @@ async function createMinioUser(username: string, password: string): Promise<void
|
||||
|
||||
try {
|
||||
const { stdout, stderr } = await execAsync(createUserCmd, { timeout: 10000 });
|
||||
console.info(`[MinIO Admin] User ${username} created/updated:`, stdout.trim() || stderr.trim());
|
||||
logger.info(`[MinIO Admin] User ${username} created/updated:`, stdout.trim() || stderr.trim());
|
||||
} catch (error: unknown) {
|
||||
const errorMsg = (error as Record<string, any>)?.stderr || (error as Error)?.message || '';
|
||||
|
||||
// Check if error is because user already exists
|
||||
if (errorMsg.includes('already exists') || errorMsg.includes('exists')) {
|
||||
console.warn(`[MinIO Admin] User ${username} already exists, updating password`);
|
||||
logger.warn(`[MinIO Admin] User ${username} already exists, updating password`);
|
||||
|
||||
// Try to update password
|
||||
try {
|
||||
const changePassCmd = `mc admin user chpass ${MINIO_ALIAS} "${username}" "${password}"`;
|
||||
const { stdout: chpassOut } = await execAsync(changePassCmd, { timeout: 10000 });
|
||||
console.info(`[MinIO Admin] Password updated for user ${username}:`, chpassOut.trim());
|
||||
logger.info(`[MinIO Admin] Password updated for user ${username}:`, chpassOut.trim());
|
||||
} catch (changeError: unknown) {
|
||||
console.error(`[MinIO Admin] Could not update password:`, (changeError as Error)?.message);
|
||||
logger.error(`[MinIO Admin] Could not update password:`, (changeError as Error)?.message);
|
||||
// Don't throw, user exists anyway
|
||||
}
|
||||
} else {
|
||||
@@ -416,10 +418,10 @@ async function createMinioUser(username: string, password: string): Promise<void
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
console.error('[MinIO Admin] Error creating user:', error.message);
|
||||
logger.error('[MinIO Admin] Error creating user:', error.message);
|
||||
// Don't throw - this is a non-critical operation
|
||||
// The credential will still be saved in DB
|
||||
console.warn(`[MinIO Admin] User creation failed but continuing. User will need to be created manually via mc: mc admin user add minio "${username}" "<password>"`);
|
||||
logger.warn(`[MinIO Admin] User creation failed but continuing. User will need to be created manually via mc: mc admin user add minio "${username}" "<password>"`);
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
@@ -432,7 +434,7 @@ async function createMinioUser(username: string, password: string): Promise<void
|
||||
*/
|
||||
async function createMinioServiceAccount(accessKey: string, secretKey: string, bucketName: string): Promise<void> {
|
||||
if (!MINIO_MC_ENABLED) {
|
||||
console.warn(`[MinIO Admin] mc CLI disabled, skipping service account creation for ${accessKey}`);
|
||||
logger.warn(`[MinIO Admin] mc CLI disabled, skipping service account creation for ${accessKey}`);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -445,13 +447,13 @@ async function createMinioServiceAccount(accessKey: string, secretKey: string, b
|
||||
|
||||
try {
|
||||
const { stdout, stderr } = await execAsync(createUserCmd, { timeout: 10000 });
|
||||
console.info(`[MinIO Admin] Service account ${accessKey} created:`, stdout.trim() || stderr.trim());
|
||||
logger.info(`[MinIO Admin] Service account ${accessKey} created:`, stdout.trim() || stderr.trim());
|
||||
} catch (error: unknown) {
|
||||
const errorMsg = (error as Record<string, any>)?.stderr || (error as Error)?.message || '';
|
||||
if (!errorMsg.includes('already exists') && !errorMsg.includes('exists')) {
|
||||
throw error;
|
||||
}
|
||||
console.warn(`[MinIO Admin] User ${accessKey} already exists`);
|
||||
logger.warn(`[MinIO Admin] User ${accessKey} already exists`);
|
||||
}
|
||||
|
||||
// Create bucket-specific policy JSON
|
||||
@@ -496,21 +498,21 @@ async function createMinioServiceAccount(accessKey: string, secretKey: string, b
|
||||
const addPolicyCmd = `mc admin policy create ${MINIO_ALIAS} "${policyName}" "${policyFile}"`;
|
||||
try {
|
||||
const { stdout } = await execAsync(addPolicyCmd, { timeout: 10000 });
|
||||
console.info(`[MinIO Admin] Policy ${policyName} created:`, stdout.trim());
|
||||
logger.info(`[MinIO Admin] Policy ${policyName} created:`, stdout.trim());
|
||||
} catch (policyError: unknown) {
|
||||
const policyErrMsg = (policyError as Record<string, any>)?.stderr || (policyError as Error)?.message || '';
|
||||
// Policy might already exist, try to update it
|
||||
if (policyErrMsg.includes('already exists') || policyErrMsg.includes('exists')) {
|
||||
console.info(`[MinIO Admin] Policy ${policyName} already exists, skipping...`);
|
||||
logger.info(`[MinIO Admin] Policy ${policyName} already exists, skipping...`);
|
||||
} else {
|
||||
console.warn(`[MinIO Admin] Policy creation warning:`, policyErrMsg);
|
||||
logger.warn(`[MinIO Admin] Policy creation warning:`, policyErrMsg);
|
||||
}
|
||||
}
|
||||
|
||||
// Attach policy to user
|
||||
const attachPolicyCmd = `mc admin policy attach ${MINIO_ALIAS} "${policyName}" --user "${accessKey}"`;
|
||||
const { stdout: attachOut } = await execAsync(attachPolicyCmd, { timeout: 10000 });
|
||||
console.info(`[MinIO Admin] Policy ${policyName} attached to user ${accessKey}:`, attachOut.trim());
|
||||
logger.info(`[MinIO Admin] Policy ${policyName} attached to user ${accessKey}:`, attachOut.trim());
|
||||
} finally {
|
||||
// Cleanup temp file
|
||||
try {
|
||||
@@ -520,10 +522,10 @@ async function createMinioServiceAccount(accessKey: string, secretKey: string, b
|
||||
}
|
||||
}
|
||||
|
||||
console.info(`[MinIO Admin] Service account ${accessKey} created with access to bucket ${bucketName}`);
|
||||
logger.info(`[MinIO Admin] Service account ${accessKey} created with access to bucket ${bucketName}`);
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
console.error('[MinIO Admin] Error creating service account:', error.message);
|
||||
logger.error('[MinIO Admin] Error creating service account:', error.message);
|
||||
throw new Error(`Не удалось создать ключ доступа в MinIO: ${error.message}`);
|
||||
}
|
||||
throw error;
|
||||
@@ -866,7 +868,7 @@ export async function markCheckoutSessionConsumed(cartId: string) {
|
||||
const session = await checkoutSessionDelegate().findUnique({ where: { id: cartId } }) as CheckoutSessionRecord | null;
|
||||
if (!session) throw new Error('Корзина не найдена');
|
||||
|
||||
await prisma.$transaction(async (tx) => {
|
||||
await prisma.$transaction(async (tx: Prisma.TransactionClient) => {
|
||||
const checkoutDelegate = (tx as any).storageCheckoutSession;
|
||||
if (checkoutDelegate) {
|
||||
await checkoutDelegate.update({ where: { id: cartId }, data: { consumedAt: new Date() } });
|
||||
@@ -877,7 +879,7 @@ export async function markCheckoutSessionConsumed(cartId: string) {
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.warn(`[Storage] Не удалось пометить корзину ${cartId} как использованную`, error);
|
||||
logger.warn(`[Storage] Не удалось пометить корзину ${cartId} как использованную`, error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1016,7 +1018,7 @@ async function syncBucketUsage(bucket: BucketWithPlan): Promise<BucketWithPlan>
|
||||
});
|
||||
return updated as BucketWithPlan;
|
||||
} catch (error) {
|
||||
console.error(`[Storage] Не удалось синхронизировать usage для бакета ${bucket.id}`, error);
|
||||
logger.error(`[Storage] Не удалось синхронизировать usage для бакета ${bucket.id}`, error);
|
||||
return bucket;
|
||||
}
|
||||
}
|
||||
@@ -1051,7 +1053,7 @@ async function applyPublicPolicy(physicalName: string, isPublic: boolean) {
|
||||
await minioClient.setBucketPolicy(physicalName, '');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`[Storage] Не удалось применить политику для бакета ${physicalName}`, error);
|
||||
logger.error(`[Storage] Не удалось применить политику для бакета ${physicalName}`, error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1061,7 +1063,7 @@ async function applyVersioning(physicalName: string, enabled: boolean) {
|
||||
Status: enabled ? 'Enabled' : 'Suspended'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`[Storage] Не удалось обновить версионирование для ${physicalName}`, error);
|
||||
logger.error(`[Storage] Не удалось обновить версионирование для ${physicalName}`, error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1133,7 +1135,7 @@ export async function createBucket(data: CreateBucketInput) {
|
||||
await ensureBucketExists(physicalName, regionCode);
|
||||
|
||||
try {
|
||||
const createdBucket = await prisma.$transaction<BucketWithPlan>(async (tx) => {
|
||||
const createdBucket = await prisma.$transaction<BucketWithPlan>(async (tx: Prisma.TransactionClient) => {
|
||||
const reloadedUser = await tx.user.findUnique({ where: { id: data.userId } });
|
||||
if (!reloadedUser) throw new Error('Пользователь не найден');
|
||||
if (toPlainNumber(reloadedUser.balance) < planPrice) throw new Error('Недостаточно средств');
|
||||
@@ -1236,11 +1238,11 @@ export async function createBucket(data: CreateBucketInput) {
|
||||
} catch (cleanupError) {
|
||||
// If cleanup fails due to auth or missing bucket, avoid spamming logs with stack traces
|
||||
if ((cleanupError as any)?.code === 'MINIO_AUTH_ERROR') {
|
||||
console.error('[Storage] Cleanup skipped due to MinIO authentication error');
|
||||
logger.error('[Storage] Cleanup skipped due to MinIO authentication error');
|
||||
} else if ((cleanupError as any)?.code === 'MINIO_BUCKET_NOT_FOUND') {
|
||||
console.warn('[Storage] Cleanup skipped, bucket not found');
|
||||
logger.warn('[Storage] Cleanup skipped, bucket not found');
|
||||
} else {
|
||||
console.error('[Storage] Ошибка очистки бакета после неудачного создания', cleanupError);
|
||||
logger.error('[Storage] Ошибка очистки бакета после неудачного создания', cleanupError);
|
||||
}
|
||||
}
|
||||
throw error;
|
||||
@@ -1306,11 +1308,11 @@ export async function generateConsoleCredentials(userId: number, id: number) {
|
||||
try {
|
||||
await createMinioUser(login, password);
|
||||
} catch (minioError) {
|
||||
console.warn('[Storage] MinIO user creation failed, but continuing:', (minioError as Error).message);
|
||||
logger.warn('[Storage] MinIO user creation failed, but continuing:', (minioError as Error).message);
|
||||
}
|
||||
|
||||
try {
|
||||
await prisma.$transaction(async (tx) => {
|
||||
await prisma.$transaction(async (tx: Prisma.TransactionClient) => {
|
||||
if (!(await ensureConsoleCredentialSupport(tx))) {
|
||||
throw new Error('MinIO Console недоступна. Обратитесь в поддержку.');
|
||||
}
|
||||
@@ -1343,7 +1345,7 @@ export async function generateConsoleCredentials(userId: number, id: number) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
console.info(`[Storage] Пользователь ${userId} сгенерировал данные входа MinIO Console для бакета ${id}`);
|
||||
logger.info(`[Storage] Пользователь ${userId} сгенерировал данные входа MinIO Console для бакета ${id}`);
|
||||
|
||||
return {
|
||||
login,
|
||||
@@ -1361,11 +1363,11 @@ export async function deleteBucket(userId: number, id: number, force = false) {
|
||||
keys = await collectObjectKeys(physicalName);
|
||||
} catch (err: unknown) {
|
||||
if ((err as any)?.code === 'MINIO_AUTH_ERROR') {
|
||||
console.error('[Storage] MinIO authentication error while deleting bucket — aborting deletion');
|
||||
logger.error('[Storage] MinIO authentication error while deleting bucket — aborting deletion');
|
||||
throw new Error('MinIO authentication failed. Пожалуйста, проверьте настройки MINIO_ACCESS_KEY и MINIO_SECRET_KEY.');
|
||||
}
|
||||
if ((err as any)?.code === 'MINIO_BUCKET_NOT_FOUND') {
|
||||
console.warn('[Storage] Bucket not found in MinIO while attempting delete; continuing with DB cleanup');
|
||||
logger.warn('[Storage] Bucket not found in MinIO while attempting delete; continuing with DB cleanup');
|
||||
keys = [];
|
||||
} else {
|
||||
throw err;
|
||||
@@ -1384,11 +1386,11 @@ export async function deleteBucket(userId: number, id: number, force = false) {
|
||||
await minioClient.removeObjects(physicalName, chunk);
|
||||
} catch (err: unknown) {
|
||||
if ((err as any)?.code === 'MINIO_AUTH_ERROR') {
|
||||
console.error('[Storage] MinIO authentication error while deleting objects');
|
||||
logger.error('[Storage] MinIO authentication error while deleting objects');
|
||||
throw new Error('MinIO authentication failed. Пожалуйста, проверьте настройки MINIO_ACCESS_KEY и MINIO_SECRET_KEY.');
|
||||
}
|
||||
if ((err as any)?.code === 'MINIO_BUCKET_NOT_FOUND') {
|
||||
console.warn('[Storage] Bucket not found while deleting objects; skipping');
|
||||
logger.warn('[Storage] Bucket not found while deleting objects; skipping');
|
||||
break;
|
||||
}
|
||||
throw err;
|
||||
@@ -1400,11 +1402,11 @@ export async function deleteBucket(userId: number, id: number, force = false) {
|
||||
await minioClient.removeBucket(physicalName);
|
||||
} catch (err: unknown) {
|
||||
if ((err as any)?.code === 'MINIO_AUTH_ERROR') {
|
||||
console.error('[Storage] MinIO authentication error while removing bucket');
|
||||
logger.error('[Storage] MinIO authentication error while removing bucket');
|
||||
throw new Error('MinIO authentication failed. Пожалуйста, проверьте настройки MINIO_ACCESS_KEY и MINIO_SECRET_KEY.');
|
||||
}
|
||||
if ((err as any)?.code === 'MINIO_BUCKET_NOT_FOUND') {
|
||||
console.warn('[Storage] Bucket not found when attempting to remove; continuing with DB cleanup');
|
||||
logger.warn('[Storage] Bucket not found when attempting to remove; continuing with DB cleanup');
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
@@ -1663,7 +1665,7 @@ export async function revokeAccessKey(userId: number, id: number, keyId: number)
|
||||
*/
|
||||
async function deleteMinioServiceAccount(accessKey: string): Promise<void> {
|
||||
if (!MINIO_MC_ENABLED) {
|
||||
console.warn(`[MinIO Admin] mc CLI disabled, skipping service account deletion for ${accessKey}`);
|
||||
logger.warn(`[MinIO Admin] mc CLI disabled, skipping service account deletion for ${accessKey}`);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1676,17 +1678,17 @@ async function deleteMinioServiceAccount(accessKey: string): Promise<void> {
|
||||
|
||||
try {
|
||||
const { stdout } = await execAsync(removeUserCmd, { timeout: 10000 });
|
||||
console.info(`[MinIO Admin] Service account ${accessKey} removed:`, stdout.trim());
|
||||
logger.info(`[MinIO Admin] Service account ${accessKey} removed:`, stdout.trim());
|
||||
} catch (error: unknown) {
|
||||
const errorMsg = (error as Record<string, any>)?.stderr || (error as Error)?.message || '';
|
||||
// User might not exist, that's okay
|
||||
if (!errorMsg.includes('does not exist') && !errorMsg.includes('not found')) {
|
||||
console.warn(`[MinIO Admin] Warning removing user ${accessKey}:`, errorMsg);
|
||||
logger.warn(`[MinIO Admin] Warning removing user ${accessKey}:`, errorMsg);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// Non-critical - user will be orphaned in MinIO but key removed from DB
|
||||
console.error('[MinIO Admin] Error deleting service account:', (error as Error).message);
|
||||
logger.error('[MinIO Admin] Error deleting service account:', (error as Error).message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { Request, Response } from 'express';
|
||||
import multer from 'multer';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import { logger } from '../../utils/logger';
|
||||
|
||||
interface SerializedUserSummary {
|
||||
id: number;
|
||||
@@ -198,7 +199,7 @@ export async function createTicket(req: Request, res: Response) {
|
||||
|
||||
return res.json({ ticket: normalizedTicket });
|
||||
} catch (err) {
|
||||
console.error('Ошибка создания тикета:', err);
|
||||
logger.error('Ошибка создания тикета:', err);
|
||||
return res.status(500).json({ error: 'Ошибка создания тикета' });
|
||||
}
|
||||
}
|
||||
@@ -342,7 +343,7 @@ export async function getTickets(req: Request, res: Response) {
|
||||
stats,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('Ошибка получения тикетов:', err);
|
||||
logger.error('Ошибка получения тикетов:', err);
|
||||
return res.status(500).json({ error: 'Ошибка получения тикетов' });
|
||||
}
|
||||
}
|
||||
@@ -409,7 +410,7 @@ export async function getTicketById(req: Request, res: Response) {
|
||||
|
||||
return res.json({ ticket: normalizedTicket });
|
||||
} catch (err) {
|
||||
console.error('Ошибка получения тикета:', err);
|
||||
logger.error('Ошибка получения тикета:', err);
|
||||
return res.status(500).json({ error: 'Ошибка получения тикета' });
|
||||
}
|
||||
}
|
||||
@@ -503,7 +504,7 @@ export async function respondTicket(req: Request, res: Response) {
|
||||
assignedTo: updateData.assignedTo ?? ticket.assignedTo ?? null,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('Ошибка ответа на тикет:', err);
|
||||
logger.error('Ошибка ответа на тикет:', err);
|
||||
return res.status(500).json({ error: 'Ошибка ответа на тикет' });
|
||||
}
|
||||
}
|
||||
@@ -573,7 +574,7 @@ export async function updateTicketStatus(req: Request, res: Response) {
|
||||
|
||||
return res.json({ ticket: normalizedTicket });
|
||||
} catch (err) {
|
||||
console.error('Ошибка изменения статуса тикета:', err);
|
||||
logger.error('Ошибка изменения статуса тикета:', err);
|
||||
return res.status(500).json({ error: 'Ошибка изменения статуса тикета' });
|
||||
}
|
||||
}
|
||||
@@ -644,7 +645,7 @@ export async function assignTicket(req: Request, res: Response) {
|
||||
|
||||
return res.json({ ticket: normalizedTicket });
|
||||
} catch (err) {
|
||||
console.error('Ошибка назначения тикета:', err);
|
||||
logger.error('Ошибка назначения тикета:', err);
|
||||
return res.status(500).json({ error: 'Ошибка назначения тикета' });
|
||||
}
|
||||
}
|
||||
@@ -689,7 +690,7 @@ export async function closeTicket(req: Request, res: Response) {
|
||||
|
||||
return res.json({ success: true, message: 'Тикет закрыт' });
|
||||
} catch (err) {
|
||||
console.error('Ошибка закрытия тикета:', err);
|
||||
logger.error('Ошибка закрытия тикета:', err);
|
||||
return res.status(500).json({ error: 'Ошибка закрытия тикета' });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Request, Response } from 'express';
|
||||
import { prisma } from '../../prisma/client';
|
||||
import bcrypt from 'bcrypt';
|
||||
import crypto from 'crypto';
|
||||
import { logger } from '../../utils/logger';
|
||||
|
||||
// Получить профиль пользователя (расширенный)
|
||||
export const getProfile = async (req: Request, res: Response) => {
|
||||
@@ -34,7 +35,7 @@ export const getProfile = async (req: Request, res: Response) => {
|
||||
|
||||
res.json({ success: true, data: userWithoutPassword });
|
||||
} catch (error: unknown) {
|
||||
console.error('Ошибка получения профиля:', error);
|
||||
logger.error('Ошибка получения профиля:', error);
|
||||
res.status(500).json({ success: false, message: 'Ошибка сервера' });
|
||||
}
|
||||
};
|
||||
@@ -49,7 +50,8 @@ export const updateProfile = async (req: Request, res: Response) => {
|
||||
// Проверка email на уникальность
|
||||
if (email) {
|
||||
const existingUser = await prisma.user.findFirst({
|
||||
where: { email, id: { not: userId } }
|
||||
where: { email, id: { not: userId } },
|
||||
select: { id: true }
|
||||
});
|
||||
if (existingUser) {
|
||||
return res.status(400).json({ success: false, message: 'Email уже используется' });
|
||||
@@ -87,7 +89,7 @@ export const updateProfile = async (req: Request, res: Response) => {
|
||||
data: { user: updatedUser, profile }
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
console.error('Ошибка обновления профиля:', error);
|
||||
logger.error('Ошибка обновления профиля:', error);
|
||||
res.status(500).json({ success: false, message: 'Ошибка сервера' });
|
||||
}
|
||||
};
|
||||
@@ -108,7 +110,10 @@ export const changePassword = async (req: Request, res: Response) => {
|
||||
}
|
||||
|
||||
// Проверка текущего пароля
|
||||
const user = await prisma.user.findUnique({ where: { id: userId } });
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { id: userId },
|
||||
select: { id: true, password: true }
|
||||
});
|
||||
if (!user) {
|
||||
return res.status(404).json({ success: false, message: 'Пользователь не найден' });
|
||||
}
|
||||
@@ -132,7 +137,7 @@ export const changePassword = async (req: Request, res: Response) => {
|
||||
|
||||
res.json({ success: true, message: 'Пароль успешно изменён' });
|
||||
} catch (error: unknown) {
|
||||
console.error('Ошибка смены пароля:', error);
|
||||
logger.error('Ошибка смены пароля:', error);
|
||||
res.status(500).json({ success: false, message: 'Ошибка сервера' });
|
||||
}
|
||||
};
|
||||
@@ -162,7 +167,7 @@ export const uploadAvatar = async (req: Request, res: Response) => {
|
||||
data: { avatarUrl }
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
console.error('Ошибка загрузки аватара:', error);
|
||||
logger.error('Ошибка загрузки аватара:', error);
|
||||
res.status(500).json({ success: false, message: 'Ошибка сервера' });
|
||||
}
|
||||
};
|
||||
@@ -180,7 +185,7 @@ export const deleteAvatar = async (req: Request, res: Response) => {
|
||||
|
||||
res.json({ success: true, message: 'Аватар удалён' });
|
||||
} catch (error: unknown) {
|
||||
console.error('Ошибка удаления аватара:', error);
|
||||
logger.error('Ошибка удаления аватара:', error);
|
||||
res.status(500).json({ success: false, message: 'Ошибка сервера' });
|
||||
}
|
||||
};
|
||||
@@ -201,7 +206,7 @@ export const getSessions = async (req: Request, res: Response) => {
|
||||
|
||||
res.json({ success: true, data: sessions });
|
||||
} catch (error: unknown) {
|
||||
console.error('Ошибка получения сеансов:', error);
|
||||
logger.error('Ошибка получения сеансов:', error);
|
||||
res.status(500).json({ success: false, message: 'Ошибка сервера' });
|
||||
}
|
||||
};
|
||||
@@ -229,7 +234,7 @@ export const terminateSession = async (req: Request, res: Response) => {
|
||||
|
||||
res.json({ success: true, message: 'Сеанс завершён' });
|
||||
} catch (error: unknown) {
|
||||
console.error('Ошибка завершения сеанса:', error);
|
||||
logger.error('Ошибка завершения сеанса:', error);
|
||||
res.status(500).json({ success: false, message: 'Ошибка сервера' });
|
||||
}
|
||||
};
|
||||
@@ -248,7 +253,7 @@ export const getLoginHistory = async (req: Request, res: Response) => {
|
||||
|
||||
res.json({ success: true, data: history });
|
||||
} catch (error: unknown) {
|
||||
console.error('Ошибка получения истории:', error);
|
||||
logger.error('Ошибка получения истории:', error);
|
||||
res.status(500).json({ success: false, message: 'Ошибка сервера' });
|
||||
}
|
||||
};
|
||||
@@ -277,7 +282,7 @@ export const getAPIKeys = async (req: Request, res: Response) => {
|
||||
|
||||
res.json({ success: true, data: keys });
|
||||
} catch (error: unknown) {
|
||||
console.error('Ошибка получения API ключей:', error);
|
||||
logger.error('Ошибка получения API ключей:', error);
|
||||
res.status(500).json({ success: false, message: 'Ошибка сервера' });
|
||||
}
|
||||
};
|
||||
@@ -314,7 +319,7 @@ export const createAPIKey = async (req: Request, res: Response) => {
|
||||
data: { ...apiKey, fullKey: key }
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
console.error('Ошибка создания API ключа:', error);
|
||||
logger.error('Ошибка создания API ключа:', error);
|
||||
res.status(500).json({ success: false, message: 'Ошибка сервера' });
|
||||
}
|
||||
};
|
||||
@@ -340,7 +345,7 @@ export const deleteAPIKey = async (req: Request, res: Response) => {
|
||||
|
||||
res.json({ success: true, message: 'API ключ удалён' });
|
||||
} catch (error: unknown) {
|
||||
console.error('Ошибка удаления API ключа:', error);
|
||||
logger.error('Ошибка удаления API ключа:', error);
|
||||
res.status(500).json({ success: false, message: 'Ошибка сервера' });
|
||||
}
|
||||
};
|
||||
@@ -363,7 +368,7 @@ export const getNotificationSettings = async (req: Request, res: Response) => {
|
||||
|
||||
res.json({ success: true, data: settings });
|
||||
} catch (error: unknown) {
|
||||
console.error('Ошибка получения настроек уведомлений:', error);
|
||||
logger.error('Ошибка получения настроек уведомлений:', error);
|
||||
res.status(500).json({ success: false, message: 'Ошибка сервера' });
|
||||
}
|
||||
};
|
||||
@@ -386,7 +391,7 @@ export const updateNotificationSettings = async (req: Request, res: Response) =>
|
||||
data: updated
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
console.error('Ошибка обновления настроек уведомлений:', error);
|
||||
logger.error('Ошибка обновления настроек уведомлений:', error);
|
||||
res.status(500).json({ success: false, message: 'Ошибка сервера' });
|
||||
}
|
||||
};
|
||||
@@ -426,7 +431,7 @@ export const exportUserData = async (req: Request, res: Response) => {
|
||||
exportedAt: new Date().toISOString()
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
console.error('Ошибка экспорта данных:', error);
|
||||
logger.error('Ошибка экспорта данных:', error);
|
||||
res.status(500).json({ success: false, message: 'Ошибка сервера' });
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user