diff --git a/backend/middleware/rateLimiter.js b/backend/middleware/rateLimiter.js index 1f18dd0..3ec96ce 100644 --- a/backend/middleware/rateLimiter.js +++ b/backend/middleware/rateLimiter.js @@ -38,11 +38,20 @@ const interactionLimiter = rateLimit({ message: 'Вы слишком активны, немного подождите', }); +// Мягкий лимит для прокси изображений (больше запросов, так как это медиа) +const proxyLimiter = rateLimit({ + windowMs: 15 * 1000, // 15 секунд + max: 200, // 200 запросов (больше для загрузки множества изображений) + message: 'Слишком много запросов изображений', + skipSuccessfulRequests: true, // Не считать успешные запросы +}); + module.exports = { generalLimiter, postCreationLimiter, authLimiter, searchLimiter, - interactionLimiter + interactionLimiter, + proxyLimiter }; diff --git a/backend/routes/search.js b/backend/routes/search.js index 803eab0..44f9b8c 100644 --- a/backend/routes/search.js +++ b/backend/routes/search.js @@ -2,6 +2,7 @@ const express = require('express'); const router = express.Router(); const axios = require('axios'); const { authenticate } = require('../middleware/auth'); +const { proxyLimiter } = require('../middleware/rateLimiter'); const config = require('../config'); // e621 требует описательный User-Agent с контактами @@ -55,7 +56,8 @@ function createProxyUrl(originalUrl) { } // Эндпоинт для проксирования изображений -router.get('/proxy/:encodedUrl', async (req, res) => { +// Используем более мягкий rate limiter для прокси +router.get('/proxy/:encodedUrl', proxyLimiter, async (req, res) => { try { const { encodedUrl } = req.params; @@ -156,21 +158,36 @@ router.get('/furry', authenticate, async (req, res) => { return res.json({ posts: [] }); } - // Проверка на наличие данных - if (!response.data || !response.data.posts || !Array.isArray(response.data.posts)) { - console.warn('⚠️ e621 вернул неверный формат данных'); + // Проверка на наличие данных (e621 может возвращать массив напрямую или объект с posts) + let postsData = null; + + if (Array.isArray(response.data)) { + postsData = response.data; + } else if (response.data && Array.isArray(response.data.posts)) { + postsData = response.data.posts; + } else if (response.data && Array.isArray(response.data.data)) { + postsData = response.data.data; + } else { + console.warn('⚠️ e621 вернул неверный формат данных для постов:', { + type: typeof response.data, + keys: response.data ? Object.keys(response.data) : null, + hasPosts: !!(response.data && response.data.posts), + isArray: Array.isArray(response.data) + }); return res.json({ posts: [] }); } - const posts = response.data.posts.map(post => ({ - id: post.id, - url: createProxyUrl(post.file.url), - preview: createProxyUrl(post.preview.url), - tags: post.tags.general, - rating: post.rating, - score: post.score.total, - source: 'e621' - })); + const posts = postsData + .filter(post => post && post.file && post.file.url) // Фильтруем посты без URL + .map(post => ({ + id: post.id, + url: createProxyUrl(post.file.url), + preview: post.preview && post.preview.url ? createProxyUrl(post.preview.url) : null, + tags: post.tags && post.tags.general ? post.tags.general : [], + rating: post.rating || 'q', + score: post.score && post.score.total ? post.score.total : 0, + source: 'e621' + })); const payload = { posts }; setCache(cacheKey, payload); @@ -312,15 +329,27 @@ router.get('/furry/tags', authenticate, async (req, res) => { return res.json({ tags: [] }); } - // Проверка на массив - if (!response.data || !Array.isArray(response.data)) { - console.warn('⚠️ e621 вернул не массив:', typeof response.data); + // Проверка на массив (e621 может возвращать массив напрямую или объект с массивом) + let tagsData = null; + + if (Array.isArray(response.data)) { + tagsData = response.data; + } else if (response.data && Array.isArray(response.data.tags)) { + tagsData = response.data.tags; + } else if (response.data && Array.isArray(response.data.data)) { + tagsData = response.data.data; + } else { + console.warn('⚠️ e621 вернул неверный формат данных для тегов:', { + type: typeof response.data, + keys: response.data ? Object.keys(response.data) : null, + data: response.data + }); return res.json({ tags: [] }); } - const tags = response.data.map(tag => ({ + const tags = tagsData.map(tag => ({ name: tag.name, - count: tag.post_count + count: tag.post_count || tag.count || 0 })); const payload = { tags }; @@ -350,6 +379,12 @@ router.get('/anime/tags', authenticate, async (req, res) => { if (!query || query.length < 2) { return res.json({ tags: [] }); } + + const cacheKey = getCacheKey('gelbooru-tags', { query: query.trim().toLowerCase() }); + const cached = getFromCache(cacheKey); + if (cached) { + return res.json(cached); + } try { const response = await axios.get('https://gelbooru.com/index.php', {