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 переключатель */} +