import { Request, Response } from 'express'; import axios from "axios"; import path from "path"; import { prisma } from '../../../lib/prisma'; import { AuthRequest } from '../../../middleware/auth'; import { checkPermission } from '../../../utils/permissions'; import { logger } from '../../../config/logger'; import { Article } from "../../../types"; import { uploadBufferToS3 } from "../../../services/s3Service"; const DEFAULT_COVER_IMAGE = '/images/cover-placeholder.webp'; export async function getArticle(req: Request, res: Response) : Promise { try { const article = await prisma.article.findUnique({ where: { id: req.params.id }, include: { author: { select: { id: true, displayName: true, avatarUrl: true, email: true } }, gallery: { orderBy: {order: "asc"}, // Сортировка изображений по порядку select: { id: true, url: true, caption: true, alt: true, width: true, height: true, size: true, format: true, order: true, // Добавляем порядок сортировки }, } } }); if (!article) { logger.warn(`Статья не найдена: ${req.params.id}`); res.status(404).json({ error: 'Статья не найдена' }); return } res.json(article); } catch (error) { logger.error('Error fetching article:', error); res.status(500).json({ error: 'Server error' }); } } export async function createArticle(req: AuthRequest, res: Response) : Promise { try { const { title, excerpt, content, categoryId, cityId, coverImage, readTime } = req.body; if (!req.user) { logger.warn('Пользователь не вошел в систему'); res.status(401).json({ error: 'Пользователь не аутентифицирован' }); return } if (!categoryId || categoryId < 1 || categoryId > 8) { logger.warn(`Недопустимый ID категории: ${categoryId}`); res.status(400).json({ error: 'Недопустимая категория' }); return } if (!checkPermission(req.user, categoryId, 'create')) { logger.warn(`Не разрешено для пользователя ${req.user.id} создавать статьи в категории ${categoryId}`); res.status(403).json({ error: 'В разрешении отказано' }); return } const article = await prisma.article.create({ data: { title, excerpt, content, categoryId, cityId, coverImage, readTime, authorId: req.user.id }, include: { author: { select: { id: true, displayName: true, email: true } } } }); logger.info(`Создана статья: ${article.id} пользователем ${req.user.id}`); res.status(201).json(article); } catch (error) { logger.error('Ошибка создания статьи:', error); res.status(500).json({ error: 'Серверная ошибка' }); } } export async function updateArticle(req: AuthRequest, res: Response) : Promise { try { const { title, excerpt, content, categoryId, cityId, coverImage, readTime } = req.body; if (!req.user) { res.status(401).json({ error: 'Пользователь не вошел в систему' }); return } const article = await prisma.article.findUnique({ where: { id: req.params.id } }); if (!article) { res.status(404).json({ error: 'Статья не найдена' }); return } if (!checkPermission(req.user, categoryId, 'edit')) { res.status(403).json({ error: 'В разрешении отказано' }); return } const updatedArticle = await prisma.article.update({ where: { id: req.params.id }, data: { title, excerpt, content, categoryId: Number(categoryId), cityId, coverImage, readTime }, include: { author: { select: { id: true, displayName: true, email: true } } } }); res.json(updatedArticle); } catch (error) { logger.error('Ошибка редактирования статьи:', error); res.status(500).json({ error: 'Серверная ошибка' }); } } export async function activeArticle(req: AuthRequest, res: Response) : Promise { try { const { isActive } = req.body; if (!req.user) { res.status(401).json({ error: 'Пользователь не вошел в систему' }); return } const article = await prisma.article.findUnique({ where: { id: req.params.id } }); if (!article) { res.status(404).json({ error: 'Статья не найдена' }); return } const updatedArticle = await prisma.article.update({ where: { id: req.params.id }, data: { isActive: !isActive }, include: { author: { select: { id: true, displayName: true, email: true } } } }); res.json(updatedArticle); } catch (error) { logger.error('Ошибка активирования статьи:', error); res.status(500).json({ error: 'Серверная ошибка' }); } } export async function reactArticle(req: AuthRequest, res: Response) : Promise { try { const { reaction, likes, dislikes } = req.body; let newLikes:number = Number(likes); let newDisLikes:number = Number(dislikes); if (reaction === 'like') newLikes++; if (reaction === 'dislike') newDisLikes++; const updatedArticle = await prisma.article.update({ where: { id: req.params.id }, data: { likes: newLikes, dislikes: newDisLikes }, include: { author: { select: { id: true, displayName: true, email: true } } } }); res.json(updatedArticle); } catch (error) { logger.error('Ошибка установки реакции на статью:', error); res.status(500).json({ error: 'Серверная ошибка' }); } } export async function deleteArticle(req: AuthRequest, res: Response) : Promise { try { if (!req.user) { res.status(401).json({ error: 'Пользователь не вошел в систему' }); return } const article = await prisma.article.findUnique({ where: { id: req.params.id } }); if (!article) { res.status(404).json({ error: 'Статья не найдена' }); return } if (!checkPermission(req.user, article.categoryId.toString(), 'delete')) { res.status(403).json({ error: 'В разрешении отказано' }); return } await prisma.article.delete({ where: { id: req.params.id } }); res.json({ message: 'Статья успешно удалена' }); } catch (error) { logger.error('Ошибка удаления статьи:', error); res.status(500).json({ error: 'Серверная ошибка' }); } } export async function importArticles(req: AuthRequest, res: Response) : Promise { const articles: Article[] = req.body; if (!req.user) { res.status(401).json({ error: 'Пользователь не вошел в систему' }); return } if (!Array.isArray(articles) || articles.length === 0) { res.status(400).json({ message: 'Ожидается непустой массив статей в теле запроса' }); return } let importedCount = 0; try { for (const article of articles) { try { // Шаг 1: Создание статьи const newArticle = await prisma.article.create({ data: { title: article.title, excerpt: article.excerpt, content: article.content, categoryId: article.categoryId, cityId: article.cityId, coverImage: DEFAULT_COVER_IMAGE, readTime: article.readTime, publishedAt: new Date(article.publishedAt), likes: article.likes || 0, dislikes: article.dislikes || 0, author: { connect: { id: article.author.id }, }, }, }); // Шаг 2: Обработка coverImage if (article.coverImage) { const folder = `articles/${newArticle.id}`; const imageUrl = article.coverImage; try { // Извлечение изображение в виде буфера массива const imageResponse = await axios.get(imageUrl, { responseType: 'arraybuffer' }); // Создание буфера из данных ответа const imageBuffer = Buffer.from(imageResponse.data, 'binary'); // Получить тип контента и расширение const contentType = imageResponse.headers['content-type']; const fileExtension = path.extname(new URL(imageUrl).pathname) || (contentType.includes('image/') ? `.${contentType.split('/')[1]}` : ''); // Генерация имени файла const filename = `cover-${Date.now()}${fileExtension}`; // Загрузка буфера в S3 const uploadedUrl = await uploadBufferToS3( imageBuffer, folder, filename, contentType, 'original', 80 ); } catch (imageError) { console.error(`Ошибка загрузки coverImage для статьи ${article.id}:`, imageError); } } importedCount++; } catch (articleError) { console.error(`Ошибка импорта статьи ${article.id}:`, articleError); } } res.status(200).json({ message: `Импорт завершен. Успешно импортировано ${importedCount} из ${articles.length} статей.`, }); } catch (err) { console.error('Ошибка обработки запроса:', err); res.status(500).json({ message: 'Ошибка сервера при импорте' }); } finally { await prisma.$disconnect(); } }