const express = require('express'); const router = express.Router(); const multer = require('multer'); const path = require('path'); const fs = require('fs'); const { authenticate } = require('../middleware/auth'); const { postCreationLimiter, interactionLimiter } = require('../middleware/rateLimiter'); const { searchLimiter } = require('../middleware/rateLimiter'); const Post = require('../models/Post'); const Notification = require('../models/Notification'); const { extractHashtags } = require('../utils/hashtags'); // Настройка multer для загрузки изображений const storage = multer.diskStorage({ destination: (req, file, cb) => { const dir = path.join(__dirname, '../uploads/posts'); if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } cb(null, dir); }, filename: (req, file, cb) => { const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9); cb(null, uniqueSuffix + path.extname(file.originalname)); } }); const upload = multer({ storage: storage, limits: { fileSize: 10 * 1024 * 1024 }, // 10MB fileFilter: (req, file, cb) => { const allowedTypes = /jpeg|jpg|png|gif|webp/; const extname = allowedTypes.test(path.extname(file.originalname).toLowerCase()); const mimetype = allowedTypes.test(file.mimetype); if (mimetype && extname) { return cb(null, true); } else { cb(new Error('Только изображения разрешены')); } } }); // Поддержка до 5 изображений в одном посте const uploadMultiple = upload.array('images', 5); // Получить ленту постов router.get('/', authenticate, async (req, res) => { try { const { page = 1, limit = 20, tag, userId } = req.query; const query = {}; // Фильтр по тегу if (tag) { query.tags = tag; } // Фильтр по пользователю if (userId) { query.author = userId; } // Применить whitelist настройки пользователя if (req.user.settings.whitelist.noFurry) { query.tags = { $ne: 'furry' }; } if (req.user.settings.whitelist.onlyAnime) { query.tags = 'anime'; } if (req.user.settings.whitelist.noNSFW) { query.isNSFW = false; } const posts = await Post.find(query) .populate('author', 'username firstName lastName photoUrl') .populate('mentionedUsers', 'username firstName lastName') .populate('comments.author', 'username firstName lastName photoUrl') .sort({ createdAt: -1 }) .limit(limit * 1) .skip((page - 1) * limit) .exec(); const count = await Post.countDocuments(query); res.json({ posts, totalPages: Math.ceil(count / limit), currentPage: page }); } catch (error) { console.error('Ошибка получения постов:', error); res.status(500).json({ error: 'Ошибка сервера' }); } }); // Создать пост router.post('/', authenticate, postCreationLimiter, uploadMultiple, async (req, res) => { try { const { content, tags, mentionedUsers, isNSFW, externalImages } = req.body; // Проверка тегов const parsedTags = JSON.parse(tags || '[]'); if (!parsedTags.length) { return res.status(400).json({ error: 'Теги обязательны' }); } // Извлечь хэштеги из контента const hashtags = extractHashtags(content); // Обработка изображений let images = []; // Загруженные файлы if (req.files && req.files.length > 0) { images = req.files.map(file => `/uploads/posts/${file.filename}`); } // Внешние изображения (из поиска) if (externalImages) { const externalUrls = JSON.parse(externalImages); images = [...images, ...externalUrls]; } // Обратная совместимость - imageUrl для первого изображения const imageUrl = images.length > 0 ? images[0] : null; const post = new Post({ author: req.user._id, content, imageUrl, // Для совместимости images, // Новое поле tags: parsedTags, hashtags, mentionedUsers: mentionedUsers ? JSON.parse(mentionedUsers) : [], isNSFW: isNSFW === 'true' }); await post.save(); await post.populate('author', 'username firstName lastName photoUrl'); // Создать уведомления для упомянутых пользователей if (post.mentionedUsers.length > 0) { const notifications = post.mentionedUsers.map(userId => ({ recipient: userId, sender: req.user._id, type: 'mention', post: post._id })); await Notification.insertMany(notifications); } res.status(201).json({ post }); } catch (error) { console.error('Ошибка создания поста:', error); res.status(500).json({ error: 'Ошибка создания поста' }); } }); // Лайкнуть пост router.post('/:id/like', authenticate, interactionLimiter, async (req, res) => { try { const post = await Post.findById(req.params.id); if (!post) { return res.status(404).json({ error: 'Пост не найден' }); } const alreadyLiked = post.likes.includes(req.user._id); if (alreadyLiked) { // Убрать лайк post.likes = post.likes.filter(id => !id.equals(req.user._id)); } else { // Добавить лайк post.likes.push(req.user._id); // Создать уведомление if (!post.author.equals(req.user._id)) { const notification = new Notification({ recipient: post.author, sender: req.user._id, type: 'like', post: post._id }); await notification.save(); } } await post.save(); res.json({ likes: post.likes.length, liked: !alreadyLiked }); } catch (error) { console.error('Ошибка лайка:', error); res.status(500).json({ error: 'Ошибка сервера' }); } }); // Добавить комментарий router.post('/:id/comment', authenticate, interactionLimiter, async (req, res) => { try { const { content } = req.body; if (!content || content.trim().length === 0) { return res.status(400).json({ error: 'Комментарий не может быть пустым' }); } const post = await Post.findById(req.params.id); if (!post) { return res.status(404).json({ error: 'Пост не найден' }); } post.comments.push({ author: req.user._id, content }); await post.save(); await post.populate('comments.author', 'username firstName lastName photoUrl'); // Создать уведомление if (!post.author.equals(req.user._id)) { const notification = new Notification({ recipient: post.author, sender: req.user._id, type: 'comment', post: post._id }); await notification.save(); } res.json({ comments: post.comments }); } catch (error) { console.error('Ошибка комментария:', error); res.status(500).json({ error: 'Ошибка сервера' }); } }); // Удалить пост (автор или модератор) router.delete('/:id', authenticate, async (req, res) => { try { const post = await Post.findById(req.params.id); if (!post) { return res.status(404).json({ error: 'Пост не найден' }); } // Проверить права if (!post.author.equals(req.user._id) && req.user.role !== 'moderator' && req.user.role !== 'admin') { return res.status(403).json({ error: 'Нет прав на удаление' }); } // Удалить изображение если есть if (post.imageUrl) { const imagePath = path.join(__dirname, '..', post.imageUrl); if (fs.existsSync(imagePath)) { fs.unlinkSync(imagePath); } } await Post.findByIdAndDelete(req.params.id); res.json({ message: 'Пост удален' }); } catch (error) { console.error('Ошибка удаления поста:', error); res.status(500).json({ error: 'Ошибка сервера' }); } }); module.exports = router;