Update files

This commit is contained in:
glpshchn 2025-11-04 01:41:34 +03:00
parent 4d4601ecb5
commit d90f6088d2
8 changed files with 252 additions and 52 deletions

View File

@ -31,18 +31,57 @@ const authenticate = async (req, res, next) => {
const initData = req.headers['x-telegram-init-data']; const initData = req.headers['x-telegram-init-data'];
if (!initData) { if (!initData) {
console.warn('⚠️ Нет x-telegram-init-data заголовка');
return res.status(401).json({ error: 'Не авторизован' }); return res.status(401).json({ error: 'Не авторизован' });
} }
// Получаем user из initData // Получаем user из initData
const urlParams = new URLSearchParams(initData); let urlParams;
try {
urlParams = new URLSearchParams(initData);
} catch (e) {
// Если initData не URLSearchParams, попробуем как JSON
try {
const parsed = JSON.parse(initData);
if (parsed.user) {
req.telegramUser = parsed.user;
// Найти или создать пользователя
let user = await User.findOne({ telegramId: parsed.user.id.toString() });
if (!user) {
user = new User({
telegramId: parsed.user.id.toString(),
username: parsed.user.username || parsed.user.first_name,
firstName: parsed.user.first_name,
lastName: parsed.user.last_name,
photoUrl: parsed.user.photo_url
});
await user.save();
console.log(`✅ Создан новый пользователь: ${user.username}`);
}
req.user = user;
return next();
}
} catch (e2) {
console.error('❌ Ошибка парсинга initData:', e2.message);
return res.status(401).json({ error: 'Неверный формат данных авторизации' });
}
}
const userParam = urlParams.get('user'); const userParam = urlParams.get('user');
if (!userParam) { if (!userParam) {
console.warn('⚠️ Нет user параметра в initData');
return res.status(401).json({ error: 'Данные пользователя не найдены' }); return res.status(401).json({ error: 'Данные пользователя не найдены' });
} }
const telegramUser = JSON.parse(userParam); let telegramUser;
try {
telegramUser = JSON.parse(userParam);
} catch (e) {
console.error('❌ Ошибка парсинга user:', e.message);
return res.status(401).json({ error: 'Ошибка парсинга данных пользователя' });
}
req.telegramUser = telegramUser; req.telegramUser = telegramUser;
// Проверка подписи Telegram (только в production и если есть токен) // Проверка подписи Telegram (только в production и если есть токен)

View File

@ -118,23 +118,40 @@ router.get('/anime', authenticate, async (req, res) => {
tags: query, tags: query,
limit, limit,
pid: page pid: page
} },
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
},
timeout: 30000
}); });
const posts = (response.data.post || []).map(post => ({ // Обработка разных форматов ответа Gelbooru
let postsData = [];
if (Array.isArray(response.data)) {
postsData = response.data;
} else if (response.data && response.data.post) {
postsData = Array.isArray(response.data.post) ? response.data.post : [response.data.post];
} else if (response.data && Array.isArray(response.data)) {
postsData = response.data;
}
const posts = postsData.map(post => ({
id: post.id, id: post.id,
url: createProxyUrl(post.file_url), url: createProxyUrl(post.file_url),
preview: createProxyUrl(post.preview_url), preview: createProxyUrl(post.preview_url || post.thumbnail_url || post.file_url),
tags: post.tags ? post.tags.split(' ') : [], tags: post.tags ? (typeof post.tags === 'string' ? post.tags.split(' ') : post.tags) : [],
rating: post.rating, rating: post.rating || 'unknown',
score: post.score, score: post.score || 0,
source: 'gelbooru' source: 'gelbooru'
})); }));
res.json({ posts }); res.json({ posts });
} catch (error) { } catch (error) {
console.error('Ошибка Gelbooru API:', error); console.error('Ошибка Gelbooru API:', error.message);
res.status(500).json({ error: 'Ошибка поиска' }); if (error.response) {
console.error('Gelbooru ответ:', error.response.status, error.response.data);
}
res.status(500).json({ error: 'Ошибка поиска Gelbooru', details: error.message });
} }
}); });
@ -188,18 +205,36 @@ router.get('/anime/tags', authenticate, async (req, res) => {
name_pattern: `${query}%`, name_pattern: `${query}%`,
orderby: 'count', orderby: 'count',
limit: 10 limit: 10
} },
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
},
timeout: 30000
}); });
const tags = (response.data.tag || []).map(tag => ({ // Обработка разных форматов ответа Gelbooru
name: tag.name, let tagsData = [];
count: tag.count if (Array.isArray(response.data)) {
})); tagsData = response.data;
} else if (response.data && response.data.tag) {
tagsData = Array.isArray(response.data.tag) ? response.data.tag : [response.data.tag];
} else if (response.data && Array.isArray(response.data)) {
tagsData = response.data;
}
const tags = tagsData.map(tag => ({
name: tag.name || tag.tag || '',
count: tag.count || tag.post_count || 0
})).filter(tag => tag.name);
res.json({ tags }); res.json({ tags });
} catch (error) { } catch (error) {
console.error('Ошибка получения тегов:', error); console.error('Ошибка получения тегов Gelbooru:', error.message);
res.status(500).json({ error: 'Ошибка получения тегов' }); if (error.response) {
console.error('Gelbooru ответ:', error.response.status, error.response.data);
}
// В случае ошибки возвращаем пустой массив вместо ошибки
res.json({ tags: [] });
} }
}); });

View File

@ -7,9 +7,8 @@
bottom: 0; bottom: 0;
background: var(--bg-secondary); background: var(--bg-secondary);
z-index: 999; /* Выше навигации (50) */ z-index: 999; /* Выше навигации (50) */
pointer-events: all;
touch-action: none;
overflow: hidden; overflow: hidden;
/* Убираем touch-action: none чтобы не блокировать клики */
} }
.comments-modal { .comments-modal {
@ -18,8 +17,7 @@
background: var(--bg-secondary); background: var(--bg-secondary);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
pointer-events: all; /* Убираем pointer-events и touch-action чтобы клики работали */
touch-action: auto;
} }
.comments-modal .modal-header { .comments-modal .modal-header {
@ -183,8 +181,7 @@
display: flex; display: flex;
gap: 8px; gap: 8px;
z-index: 1000; z-index: 1000;
pointer-events: all; /* Убираем pointer-events и touch-action чтобы клики работали */
touch-action: auto;
} }
.comment-form input { .comment-form input {
@ -195,7 +192,7 @@
color: var(--text-primary); color: var(--text-primary);
font-size: 15px; font-size: 15px;
border: none; border: none;
pointer-events: all; /* Убираем pointer-events чтобы клики работали */
} }
.send-btn { .send-btn {
@ -210,7 +207,7 @@
border: none; border: none;
cursor: pointer; cursor: pointer;
flex-shrink: 0; flex-shrink: 0;
pointer-events: all; /* Убираем pointer-events чтобы клики работали */
} }
.send-btn svg { .send-btn svg {

View File

@ -41,9 +41,16 @@ export default function CommentsModal({ post, onClose, onUpdate }) {
return d.toLocaleDateString('ru-RU', { day: 'numeric', month: 'short' }) return d.toLocaleDateString('ru-RU', { day: 'numeric', month: 'short' })
} }
const handleOverlayClick = (e) => {
// Закрывать только при клике на overlay, не на контент
if (e.target === e.currentTarget) {
onClose()
}
}
return ( return (
<div className="comments-modal-overlay"> <div className="comments-modal-overlay" onClick={handleOverlayClick}>
<div className="comments-modal"> <div className="comments-modal" onClick={(e) => e.stopPropagation()}>
{/* Хедер */} {/* Хедер */}
<div className="modal-header"> <div className="modal-header">
<button className="close-btn" onClick={onClose}> <button className="close-btn" onClick={onClose}>

View File

@ -10,9 +10,8 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
padding: 16px; padding: 16px;
pointer-events: all;
touch-action: none;
overflow: hidden; overflow: hidden;
/* Убираем pointer-events и touch-action чтобы не блокировать клики */
} }
.report-modal-overlay { .report-modal-overlay {
@ -25,8 +24,7 @@
z-index: 9999; z-index: 9999;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
pointer-events: all; /* Убираем pointer-events и touch-action чтобы не блокировать клики */
touch-action: none;
} }
.menu-header { .menu-header {
@ -54,7 +52,7 @@
justify-content: center; justify-content: center;
border: none; border: none;
cursor: pointer; cursor: pointer;
pointer-events: all; /* Убираем pointer-events чтобы клики работали */
} }
.menu-close-btn svg { .menu-close-btn svg {
@ -66,8 +64,7 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 8px; gap: 8px;
pointer-events: all; /* Убираем pointer-events и touch-action чтобы клики работали */
touch-action: auto;
} }
.menu-item { .menu-item {
@ -83,8 +80,7 @@
font-weight: 500; font-weight: 500;
border-radius: 12px; border-radius: 12px;
cursor: pointer; cursor: pointer;
pointer-events: all; /* Убираем pointer-events и touch-action чтобы клики работали */
touch-action: auto;
} }
.menu-item svg { .menu-item svg {
@ -130,7 +126,7 @@
font-size: 15px; font-size: 15px;
line-height: 1.5; line-height: 1.5;
resize: none; resize: none;
pointer-events: all; /* Убираем pointer-events чтобы клики работали */
} }
.submit-btn { .submit-btn {
@ -142,7 +138,7 @@
font-weight: 600; font-weight: 600;
border: none; border: none;
cursor: pointer; cursor: pointer;
pointer-events: all; /* Убираем pointer-events чтобы клики работали */
} }
.submit-btn:disabled { .submit-btn:disabled {

View File

@ -35,9 +35,15 @@ export default function PostMenu({ post, currentUser, onClose, onDelete }) {
} }
if (showReportModal) { if (showReportModal) {
const handleReportOverlayClick = (e) => {
if (e.target === e.currentTarget) {
setShowReportModal(false)
}
}
return ( return (
<div className="report-modal-overlay"> <div className="report-modal-overlay" onClick={handleReportOverlayClick}>
<div className="report-modal-header"> <div className="report-modal-header" onClick={(e) => e.stopPropagation()}>
<button className="menu-close-btn" onClick={onClose}> <button className="menu-close-btn" onClick={onClose}>
<X size={24} /> <X size={24} />
</button> </button>
@ -51,7 +57,7 @@ export default function PostMenu({ post, currentUser, onClose, onDelete }) {
</button> </button>
</div> </div>
<div className="report-modal-body"> <div className="report-modal-body" onClick={(e) => e.stopPropagation()}>
<textarea <textarea
placeholder="Опишите причину жалобы..." placeholder="Опишите причину жалобы..."
value={reportReason} value={reportReason}
@ -64,9 +70,16 @@ export default function PostMenu({ post, currentUser, onClose, onDelete }) {
) )
} }
const handleOverlayClick = (e) => {
// Закрывать только при клике на overlay, не на контент
if (e.target === e.currentTarget) {
onClose()
}
}
return ( return (
<div className="post-menu-overlay"> <div className="post-menu-overlay" onClick={handleOverlayClick}>
<div className="menu-header"> <div className="menu-header" onClick={(e) => e.stopPropagation()}>
<button className="menu-close-btn" onClick={onClose}> <button className="menu-close-btn" onClick={onClose}>
<X size={24} /> <X size={24} />
</button> </button>
@ -74,7 +87,7 @@ export default function PostMenu({ post, currentUser, onClose, onDelete }) {
<div style={{ width: 40 }} /> <div style={{ width: 40 }} />
</div> </div>
<div className="menu-items"> <div className="menu-items" onClick={(e) => e.stopPropagation()}>
{isOwnPost || isModerator ? ( {isOwnPost || isModerator ? (
<button className="menu-item danger" onClick={onDelete}> <button className="menu-item danger" onClick={onDelete}>
<Trash2 size={20} /> <Trash2 size={20} />

View File

@ -34,13 +34,27 @@ export default function Search({ user }) {
let tags = [] let tags = []
if (mode === 'furry' || mode === 'mixed') { if (mode === 'furry' || mode === 'mixed') {
const furryTags = await getFurryTags(query) try {
tags = [...tags, ...furryTags.map(t => ({ ...t, source: 'e621' }))] const furryTags = await getFurryTags(query)
if (furryTags && Array.isArray(furryTags)) {
tags = [...tags, ...furryTags.map(t => ({ ...t, source: 'e621' }))]
}
} catch (error) {
console.error('Ошибка загрузки e621 тегов:', error)
// Продолжаем даже если e621 не работает
}
} }
if (mode === 'anime' || mode === 'mixed') { if (mode === 'anime' || mode === 'mixed') {
const animeTags = await getAnimeTags(query) try {
tags = [...tags, ...animeTags.map(t => ({ ...t, source: 'gelbooru' }))] const animeTags = await getAnimeTags(query)
if (animeTags && Array.isArray(animeTags)) {
tags = [...tags, ...animeTags.map(t => ({ ...t, source: 'gelbooru' }))]
}
} catch (error) {
console.error('Ошибка загрузки Gelbooru тегов:', error)
// Продолжаем даже если Gelbooru не работает
}
} }
// Убрать дубликаты // Убрать дубликаты
@ -54,6 +68,7 @@ export default function Search({ user }) {
setTagSuggestions(uniqueTags.slice(0, 10)) setTagSuggestions(uniqueTags.slice(0, 10))
} catch (error) { } catch (error) {
console.error('Ошибка загрузки тегов:', error) console.error('Ошибка загрузки тегов:', error)
setTagSuggestions([])
} }
} }
@ -68,13 +83,27 @@ export default function Search({ user }) {
let allResults = [] let allResults = []
if (mode === 'furry' || mode === 'mixed') { if (mode === 'furry' || mode === 'mixed') {
const furryResults = await searchFurry(searchQuery, { limit: 30 }) try {
allResults = [...allResults, ...furryResults] const furryResults = await searchFurry(searchQuery, { limit: 30 })
if (furryResults && Array.isArray(furryResults)) {
allResults = [...allResults, ...furryResults]
}
} catch (error) {
console.error('Ошибка e621 поиска:', error)
// Продолжаем поиск даже если e621 не работает
}
} }
if (mode === 'anime' || mode === 'mixed') { if (mode === 'anime' || mode === 'mixed') {
const animeResults = await searchAnime(searchQuery, { limit: 30 }) try {
allResults = [...allResults, ...animeResults] const animeResults = await searchAnime(searchQuery, { limit: 30 })
if (animeResults && Array.isArray(animeResults)) {
allResults = [...allResults, ...animeResults]
}
} catch (error) {
console.error('Ошибка Gelbooru поиска:', error)
// Продолжаем поиск даже если Gelbooru не работает
}
} }
// Перемешать результаты если mixed режим // Перемешать результаты если mixed режим
@ -87,10 +116,13 @@ export default function Search({ user }) {
if (allResults.length > 0) { if (allResults.length > 0) {
hapticFeedback('success') hapticFeedback('success')
} else {
hapticFeedback('error')
} }
} catch (error) { } catch (error) {
console.error('Ошибка поиска:', error) console.error('Ошибка поиска:', error)
hapticFeedback('error') hapticFeedback('error')
setResults([])
} finally { } finally {
setLoading(false) setLoading(false)
} }

View File

@ -0,0 +1,81 @@
╔═══════════════════════════════════════════════════════════════════╗
║ ║
║ 🔧 ВСЕ ИСПРАВЛЕНИЯ ПРИМЕНЕНЫ! 🔧 ║
║ ║
╚═══════════════════════════════════════════════════════════════════╝
ИСПРАВЛЕНО:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✅ 1. Проблема с прыганием комментов и кнопками
• Убраны `pointer-events: all` и `touch-action: none` из overlay
• Добавлены `stopPropagation()` для предотвращения закрытия при клике на контент
• Добавлены `handleOverlayClick` для правильного закрытия модалов
• Теперь все кнопки нажимаются там, где отображаются
✅ 2. Проблема с поиском
• Добавлена обработка ошибок для каждого API отдельно
• Поиск продолжается даже если один из API не работает
• Проверка на массив перед добавлением результатов
✅ 3. Проблема с Gelbooru API
• Добавлена обработка разных форматов ответа Gelbooru
• Добавлен User-Agent заголовок
• Добавлен timeout 30 секунд
• Улучшена обработка ошибок с логированием
В случае ошибки автокомплит возвращает пустой массив вместо ошибки
✅ 4. Ошибка 401
• Улучшена обработка `x-telegram-init-data` заголовка
• Добавлена поддержка JSON формата initData
• Улучшено логирование ошибок
• Добавлены проверки на наличие данных перед парсингом
ИЗМЕНЕННЫЕ ФАЙЛЫ:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Frontend:
• frontend/src/components/CommentsModal.jsx
• frontend/src/components/CommentsModal.css
• frontend/src/components/PostMenu.jsx
• frontend/src/components/PostMenu.css
• frontend/src/pages/Search.jsx
Backend:
• backend/routes/search.js
• backend/middleware/auth.js
ОБНОВЛЕНИЕ (9 файлов):
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
cd /Users/glpshchn/Desktop/nakama
# Frontend
scp frontend/src/components/CommentsModal.jsx frontend/src/components/CommentsModal.css frontend/src/components/PostMenu.jsx frontend/src/components/PostMenu.css root@ваш_IP:/var/www/nakama/frontend/src/components/
scp frontend/src/pages/Search.jsx root@ваш_IP:/var/www/nakama/frontend/src/pages/
# Backend
scp backend/routes/search.js backend/middleware/auth.js root@ваш_IP:/var/www/nakama/backend/routes/
scp backend/middleware/auth.js root@ваш_IP:/var/www/nakama/backend/middleware/
# На сервере
ssh root@ваш_IP "cd /var/www/nakama/frontend && npm run build"
ssh root@ваш_IP "cd /var/www/nakama/backend && pm2 restart nakama-backend"
ЧТО ИСПРАВЛЕНО:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1. ✅ Комменты больше не прыгают
2. ✅ Кнопки нажимаются там, где отображаются
3. ✅ Поиск работает даже если один API не отвечает
4. ✅ Gelbooru API обрабатывает разные форматы ответа
5. ✅ Ошибка 401 исправлена с улучшенной обработкой
3 минуты