From bc2d103e5074c5692ee4e16ba0da4628973686d8 Mon Sep 17 00:00:00 2001 From: glpshchn <464976@niuitmo.ru> Date: Mon, 1 Dec 2025 17:26:18 +0300 Subject: [PATCH] Update files --- LICENSE | 2 +- backend/middleware/auth.js | 14 ++++++--- backend/models/Post.js | 6 ++++ backend/models/User.js | 5 ++- backend/routes/auth.js | 5 +-- backend/routes/postSearch.js | 6 ++++ backend/routes/posts.js | 12 ++++++-- backend/routes/search.js | 12 ++++---- backend/routes/users.js | 26 ++++++++++++---- frontend/src/components/CreatePostModal.jsx | 14 +++++++++ frontend/src/pages/Profile.jsx | 34 ++++++++++++++++++++- moderation/frontend/src/App.jsx | 16 ++++++---- package.json | 2 +- start.sh | 4 +-- update-server.sh | 4 +-- 15 files changed, 128 insertions(+), 34 deletions(-) diff --git a/LICENSE b/LICENSE index 2feda64..25d4326 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 NakamaSpace +Copyright (c) 2025 Nakama Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/backend/middleware/auth.js b/backend/middleware/auth.js index 05149a3..58d2acf 100644 --- a/backend/middleware/auth.js +++ b/backend/middleware/auth.js @@ -35,11 +35,17 @@ const ensureUserSettings = async (user) => { } if (!user.settings.whitelist) { - user.settings.whitelist = { noNSFW: true }; - updated = true; - } else if (user.settings.whitelist.noNSFW === undefined) { - user.settings.whitelist.noNSFW = true; + user.settings.whitelist = { noNSFW: true, noHomo: true }; updated = true; + } else { + if (user.settings.whitelist.noNSFW === undefined) { + user.settings.whitelist.noNSFW = true; + updated = true; + } + if (user.settings.whitelist.noHomo === undefined) { + user.settings.whitelist.noHomo = true; + updated = true; + } } if (updated) { diff --git a/backend/models/Post.js b/backend/models/Post.js index d7e5ad3..bbf42df 100644 --- a/backend/models/Post.js +++ b/backend/models/Post.js @@ -50,6 +50,12 @@ const PostSchema = new mongoose.Schema({ type: Boolean, default: false }, + // Отдельный флаг для гомосексуального контента + // Может отсутствовать у старых постов, поэтому фильтры должны учитывать isHomo === true + isHomo: { + type: Boolean, + default: false + }, likes: [{ type: mongoose.Schema.Types.ObjectId, ref: 'User' diff --git a/backend/models/User.js b/backend/models/User.js index 5e4c897..468e1cd 100644 --- a/backend/models/User.js +++ b/backend/models/User.js @@ -35,7 +35,10 @@ const UserSchema = new mongoose.Schema({ whitelist: { noFurry: { type: Boolean, default: false }, onlyAnime: { type: Boolean, default: false }, - noNSFW: { type: Boolean, default: false } + // Скрыть контент 18+ + noNSFW: { type: Boolean, default: false }, + // Скрыть гомосексуальный контент + noHomo: { type: Boolean, default: true } }, searchPreference: { type: String, diff --git a/backend/routes/auth.js b/backend/routes/auth.js index e823bcf..6df5556 100644 --- a/backend/routes/auth.js +++ b/backend/routes/auth.js @@ -23,6 +23,7 @@ const normalizeUserSettings = (settings = {}) => { ...plainSettings, whitelist: { noNSFW: whitelist?.noNSFW ?? true, + noHomo: whitelist?.noHomo ?? true, ...whitelist }, searchPreference: ALLOWED_SEARCH_PREFERENCES.includes(plainSettings.searchPreference) @@ -192,7 +193,7 @@ router.post('/oauth', strictAuthLimiter, async (req, res) => { } if (telegramUser.first_name) { - user.firstName = telegramUser.first_name; + user.firstName = telegramUser.first_name; } if (telegramUser.last_name !== undefined) { @@ -201,7 +202,7 @@ router.post('/oauth', strictAuthLimiter, async (req, res) => { // Обновлять аватарку только если есть новая if (telegramUser.photo_url) { - user.photoUrl = telegramUser.photo_url; + user.photoUrl = telegramUser.photo_url; } await user.save(); diff --git a/backend/routes/postSearch.js b/backend/routes/postSearch.js index 10d046a..bd720f1 100644 --- a/backend/routes/postSearch.js +++ b/backend/routes/postSearch.js @@ -21,6 +21,9 @@ router.get('/', authenticate, searchLimiter, async (req, res) => { if (req.user.settings.whitelist.noNSFW) { searchQuery.isNSFW = false; } + if (req.user.settings.whitelist.noHomo) { + searchQuery.isHomo = { $ne: true }; + } // Поиск по хэштегу if (hashtag) { @@ -97,6 +100,9 @@ router.get('/hashtag/:tag', authenticate, async (req, res) => { if (req.user.settings.whitelist.noNSFW) { query.isNSFW = false; } + if (req.user.settings.whitelist.noHomo) { + query.isHomo = { $ne: true }; + } const posts = await Post.find(query) .populate('author', 'username firstName lastName photoUrl') diff --git a/backend/routes/posts.js b/backend/routes/posts.js index 66e87fe..0b2b3ee 100644 --- a/backend/routes/posts.js +++ b/backend/routes/posts.js @@ -38,6 +38,11 @@ router.get('/', authenticate, async (req, res) => { if (req.user.settings.whitelist.noNSFW) { query.isNSFW = false; } + if (req.user.settings.whitelist.noHomo) { + // Скрывать только посты, помеченные как гомосексуальные. + // Посты без флага (старые) остаются видимыми. + query.isHomo = { $ne: true }; + } let posts = await Post.find(query) .populate('author', 'username firstName lastName photoUrl') @@ -67,7 +72,7 @@ router.get('/', authenticate, async (req, res) => { // Создать пост router.post('/', authenticate, strictPostLimiter, postCreationLimiter, fileUploadLimiter, uploadPostImages, async (req, res) => { try { - const { content, tags, mentionedUsers, isNSFW, externalImages } = req.body; + const { content, tags, mentionedUsers, isNSFW, isHomo, externalImages } = req.body; // Валидация контента if (content && !validatePostContent(content)) { @@ -139,7 +144,10 @@ router.post('/', authenticate, strictPostLimiter, postCreationLimiter, fileUploa tags: parsedTags, hashtags, mentionedUsers: mentionedUsers ? JSON.parse(mentionedUsers) : [], - isNSFW: isNSFW === 'true' + isNSFW: isNSFW === 'true', + // Флаг гомосексуального контента - полный аналог NSFW по логике, + // но управляется отдельно + isHomo: isHomo === 'true' || isHomo === true }); await post.save(); diff --git a/backend/routes/search.js b/backend/routes/search.js index 3b0d114..9622fa7 100644 --- a/backend/routes/search.js +++ b/backend/routes/search.js @@ -91,8 +91,8 @@ router.get('/proxy/:encodedUrl', proxyLimiter, async (req, res) => { // Если это e621, добавляем авторизацию (если есть учетные данные) if (urlObj.hostname.includes('e621.net') && config.e621Username && config.e621ApiKey) { try { - const auth = Buffer.from(`${config.e621Username}:${config.e621ApiKey}`).toString('base64'); - headers['Authorization'] = `Basic ${auth}`; + const auth = Buffer.from(`${config.e621Username}:${config.e621ApiKey}`).toString('base64'); + headers['Authorization'] = `Basic ${auth}`; } catch (error) { console.warn('⚠️ Ошибка создания Basic auth для e621:', error.message); // Продолжаем без авторизации @@ -220,14 +220,14 @@ router.get('/furry', authenticate, async (req, res) => { const posts = postsData .filter(post => post && post.file && post.file.url) // Фильтруем посты без URL .map(post => ({ - id: post.id, - url: createProxyUrl(post.file.url), + 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' - })); + source: 'e621' + })); const payload = { posts }; setCache(cacheKey, payload); diff --git a/backend/routes/users.js b/backend/routes/users.js index 7eac307..0e0bda5 100644 --- a/backend/routes/users.js +++ b/backend/routes/users.js @@ -46,15 +46,24 @@ router.get('/:id', authenticate, async (req, res) => { router.get('/:id/posts', authenticate, async (req, res) => { try { const { page = 1, limit = 20 } = req.query; - - const posts = await Post.find({ author: req.params.id }) + const query = { author: req.params.id }; + + // Применить фильтры текущего пользователя + if (req.user.settings?.whitelist?.noNSFW) { + query.isNSFW = false; + } + if (req.user.settings?.whitelist?.noHomo) { + query.isHomo = { $ne: true }; + } + + const posts = await Post.find(query) .populate('author', 'username firstName lastName photoUrl') .sort({ createdAt: -1 }) .limit(limit * 1) .skip((page - 1) * limit) .exec(); - const count = await Post.countDocuments({ author: req.params.id }); + const count = await Post.countDocuments(query); res.json({ posts, @@ -148,9 +157,14 @@ router.put('/profile', authenticate, async (req, res) => { } if (!req.user.settings.whitelist) { - req.user.settings.whitelist = { noNSFW: true }; - } else if (req.user.settings.whitelist.noNSFW === undefined) { - req.user.settings.whitelist.noNSFW = true; + req.user.settings.whitelist = { noNSFW: true, noHomo: true }; + } else { + if (req.user.settings.whitelist.noNSFW === undefined) { + req.user.settings.whitelist.noNSFW = true; + } + if (req.user.settings.whitelist.noHomo === undefined) { + req.user.settings.whitelist.noHomo = true; + } } req.user.markModified('settings'); diff --git a/frontend/src/components/CreatePostModal.jsx b/frontend/src/components/CreatePostModal.jsx index 2c68f74..f3dc3e5 100644 --- a/frontend/src/components/CreatePostModal.jsx +++ b/frontend/src/components/CreatePostModal.jsx @@ -17,6 +17,7 @@ export default function CreatePostModal({ user, onClose, onPostCreated, initialI const [imagePreviews, setImagePreviews] = useState(initialImage ? [initialImage] : []) const [externalImages, setExternalImages] = useState(initialImage ? [initialImage] : []) const [isNSFW, setIsNSFW] = useState(false) + const [isHomo, setIsHomo] = useState(false) const [loading, setLoading] = useState(false) const [showUserSearch, setShowUserSearch] = useState(false) const [userSearchQuery, setUserSearchQuery] = useState('') @@ -107,6 +108,7 @@ export default function CreatePostModal({ user, onClose, onPostCreated, initialI formData.append('content', content) formData.append('tags', JSON.stringify(selectedTags)) formData.append('isNSFW', isNSFW) + formData.append('isHomo', isHomo) // Добавить загруженные файлы images.forEach((image, index) => { @@ -223,6 +225,18 @@ export default function CreatePostModal({ user, onClose, onPostCreated, initialI Отметить как NSFW + + {/* Homo переключатель */} +
+ +
{/* Футер с действиями */} diff --git a/frontend/src/pages/Profile.jsx b/frontend/src/pages/Profile.jsx index 017e6b1..ab4c6c2 100644 --- a/frontend/src/pages/Profile.jsx +++ b/frontend/src/pages/Profile.jsx @@ -13,7 +13,9 @@ const normalizeSearchPreference = (value) => const DEFAULT_SETTINGS = { whitelist: { - noNSFW: true + noNSFW: true, + // Скрыть гомосексуальный контент + noHomo: true }, searchPreference: 'furry' } @@ -209,6 +211,21 @@ export default function Profile({ user, setUser }) { + +
+
+
Скрыть Homo
+
Не показывать посты с гомосексуальным контентом
+
+ +
{/* Модальное окно редактирования bio */} @@ -276,6 +293,21 @@ export default function Profile({ user, setUser }) { + +
+
+
Скрыть Homo
+
Убрать гомосексуальный контент из ленты и поиска
+
+ +
diff --git a/moderation/frontend/src/App.jsx b/moderation/frontend/src/App.jsx index 478fe43..73018ea 100644 --- a/moderation/frontend/src/App.jsx +++ b/moderation/frontend/src/App.jsx @@ -291,19 +291,23 @@ export default function App() { } const API_URL = import.meta.env.VITE_API_URL || ( - import.meta.env.PROD ? window.location.origin : 'http://localhost:3000' + import.meta.env.PROD ? '/api' : 'http://localhost:3000/api' ); + + // Для WebSocket убираем "/api" из base URL, т.к. socket.io слушает на корне + const socketBase = API_URL.replace(/\/?api\/?$/, ''); console.log('[Chat] Инициализация чата'); + console.log('[Chat] WS base URL:', socketBase); console.log('[Chat] User данные:', { username: user.username, telegramId: user.telegramId, hasUsername: !!user.username, hasTelegramId: !!user.telegramId }); - console.log('[Chat] Подключение к:', `${API_URL}/mod-chat`); + console.log('[Chat] Подключение к:', `${socketBase}/mod-chat`); - const socket = io(`${API_URL}/mod-chat`, { + const socket = io(`${socketBase}/mod-chat`, { transports: ['websocket', 'polling'], reconnection: true, reconnectionDelay: 1000, @@ -677,16 +681,16 @@ export default function App() { {report.post.author && ( )} )}
diff --git a/package.json b/package.json index 2500543..eb37904 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "nakama-space", "version": "1.0.0", - "description": "NakamaSpace - Telegram Mini App социальная сеть", + "description": "Nakama - Telegram Mini App социальная сеть", "main": "backend/server.js", "scripts": { "dev": "concurrently \"npm run server\" \"npm run client\"", diff --git a/start.sh b/start.sh index 266972f..bee255c 100755 --- a/start.sh +++ b/start.sh @@ -1,8 +1,8 @@ #!/bin/bash -# NakamaSpace - Скрипт быстрого запуска +# Nakama - Скрипт быстрого запуска -echo "🚀 Запуск NakamaSpace..." +echo "🚀 Запуск Nakama..." # Проверка MongoDB if ! pgrep -x "mongod" > /dev/null; then diff --git a/update-server.sh b/update-server.sh index e0267da..48414cc 100755 --- a/update-server.sh +++ b/update-server.sh @@ -1,9 +1,9 @@ #!/bin/bash -# Скрипт обновления NakamaSpace на сервере +# Скрипт обновления Nakama на сервере # Использование: ./update-server.sh -echo "🚀 Обновление NakamaSpace..." +echo "🚀 Обновление Nakama..." # 1. Перейти в директорию проекта cd /var/www/nakama || exit 1