Update files
This commit is contained in:
parent
01f1e1ae94
commit
bc2d103e50
2
LICENSE
2
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
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 query = { author: req.params.id };
|
||||
|
||||
const posts = await Post.find({ 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');
|
||||
|
|
|
|||
|
|
@ -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
|
|||
<span>Отметить как NSFW</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{/* Homo переключатель */}
|
||||
<div className="nsfw-toggle">
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={isHomo}
|
||||
onChange={e => setIsHomo(e.target.checked)}
|
||||
/>
|
||||
<span>Отметить как Homo</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Футер с действиями */}
|
||||
|
|
|
|||
|
|
@ -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 }) {
|
|||
<span className="toggle-slider" />
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="setting-item card">
|
||||
<div>
|
||||
<div className="setting-name">Скрыть Homo</div>
|
||||
<div className="setting-desc">Не показывать посты с гомосексуальным контентом</div>
|
||||
</div>
|
||||
<label className="toggle">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={settings.whitelist.noHomo}
|
||||
onChange={(e) => updateWhitelistSetting('noHomo', e.target.checked)}
|
||||
/>
|
||||
<span className="toggle-slider" />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Модальное окно редактирования bio */}
|
||||
|
|
@ -276,6 +293,21 @@ export default function Profile({ user, setUser }) {
|
|||
<span className="toggle-slider" />
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="setting-row">
|
||||
<div>
|
||||
<div className="setting-name">Скрыть Homo</div>
|
||||
<div className="setting-desc">Убрать гомосексуальный контент из ленты и поиска</div>
|
||||
</div>
|
||||
<label className="toggle">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={settings.whitelist.noHomo}
|
||||
onChange={(e) => updateWhitelistSetting('noHomo', e.target.checked)}
|
||||
/>
|
||||
<span className="toggle-slider" />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="settings-section">
|
||||
|
|
|
|||
|
|
@ -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 && (
|
||||
<button className="btn warn" onClick={() => handleBanAuthor(report.post.id)}>
|
||||
<Ban size={16} />
|
||||
Забанить автора
|
||||
Забанить автора (срок)
|
||||
</button>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<button className="btn" onClick={() => handleReportStatus(report.id, 'resolved')}>
|
||||
Решено
|
||||
Закрыть как решённый
|
||||
</button>
|
||||
<button className="btn" onClick={() => handleReportStatus(report.id, 'dismissed')}>
|
||||
Отклонить репорт
|
||||
Пропустить
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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\"",
|
||||
|
|
|
|||
4
start.sh
4
start.sh
|
|
@ -1,8 +1,8 @@
|
|||
#!/bin/bash
|
||||
|
||||
# NakamaSpace - Скрипт быстрого запуска
|
||||
# Nakama - Скрипт быстрого запуска
|
||||
|
||||
echo "🚀 Запуск NakamaSpace..."
|
||||
echo "🚀 Запуск Nakama..."
|
||||
|
||||
# Проверка MongoDB
|
||||
if ! pgrep -x "mongod" > /dev/null; then
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Скрипт обновления NakamaSpace на сервере
|
||||
# Скрипт обновления Nakama на сервере
|
||||
# Использование: ./update-server.sh
|
||||
|
||||
echo "🚀 Обновление NakamaSpace..."
|
||||
echo "🚀 Обновление Nakama..."
|
||||
|
||||
# 1. Перейти в директорию проекта
|
||||
cd /var/www/nakama || exit 1
|
||||
|
|
|
|||
Loading…
Reference in New Issue