diff --git a/backend/middleware/auth.js b/backend/middleware/auth.js index 1d20791..a375c79 100644 --- a/backend/middleware/auth.js +++ b/backend/middleware/auth.js @@ -257,43 +257,49 @@ const authenticate = async (req, res, next) => { user.loginDates = []; } + const { getMoscowStartOfDay, getMoscowDate } = require('../utils/moscowTime'); + const today = getMoscowDate(); + const todayTime = today.getTime(); + // Получить уникальные даты из существующего массива (только даты, без времени по московскому времени) - const { getMoscowStartOfDay } = require('../utils/moscowTime'); const uniqueDates = new Set(); user.loginDates.forEach(date => { const dateObj = getMoscowStartOfDay(new Date(date)); uniqueDates.add(dateObj.getTime()); }); - // Если уже есть 2 уникальные даты, сразу засчитать реферал без добавления новой даты + // Добавить сегодняшнюю дату, если её еще нет + const todayExists = uniqueDates.has(todayTime); + if (!todayExists) { + user.loginDates.push(today); + uniqueDates.add(todayTime); + } + + // Если уже есть 2 или более уникальные даты, засчитать реферала if (uniqueDates.size >= 2) { const User = require('../models/User'); - await User.findByIdAndUpdate(user.referredBy, { - $inc: { referralsCount: 1 } - }); + const referrer = await User.findById(user.referredBy); - // Начислить баллы за реферала - const { awardReferral } = require('../utils/tickets'); - await awardReferral(user.referredBy); + if (referrer) { + // Увеличить счетчик рефералов + referrer.referralsCount = (referrer.referralsCount || 0) + 1; + await referrer.save(); + + console.log(`✅ Реферал засчитан: пользователь ${user.username} (${user._id}) засчитан для ${referrer.username} (${referrer._id}). Всего рефералов: ${referrer.referralsCount}`); + + // Начислить баллы за реферала + const { awardReferral } = require('../utils/tickets'); + await awardReferral(user.referredBy); + } user.referralCounted = true; // Очистить loginDates после засчета, чтобы не хранить лишние данные user.loginDates = []; await user.save(); } else { - // Если еще нет 2 уникальных дат, добавить сегодняшнюю дату по московскому времени (если её нет) - const { getMoscowDate } = require('../utils/moscowTime'); - const today = getMoscowDate(); - const todayTime = today.getTime(); - - // Проверить, есть ли уже сегодняшняя дата - const todayExists = uniqueDates.has(todayTime); - - // Если сегодняшней даты нет, добавить её - if (!todayExists) { - user.loginDates.push(today); - await user.save(); - } + // Если еще нет 2 уникальных дат, сохранить обновленный массив loginDates + await user.save(); + console.log(`📅 Реферал ${user.username} (${user._id}): добавлена дата входа. Уникальных дат: ${uniqueDates.size}/2`); } } diff --git a/backend/middleware/upload.js b/backend/middleware/upload.js index 698f665..d66cce5 100644 --- a/backend/middleware/upload.js +++ b/backend/middleware/upload.js @@ -28,14 +28,31 @@ const multerConfig = { return cb(new Error('Запрещенный тип файла')); } - // Разрешенные типы изображений и видео + // Разрешенные типы изображений и видео (расширенный список) const allowedMimes = [ + // Основные форматы изображений 'image/jpeg', 'image/jpg', 'image/png', 'image/gif', 'image/webp', - 'video/mp4', 'video/quicktime', 'video/x-msvideo' + // Дополнительные форматы изображений + 'image/bmp', 'image/x-ms-bmp', 'image/tiff', 'image/tif', + 'image/heic', 'image/heif', 'image/avif', 'image/x-icon', + // Видео форматы + 'video/mp4', 'video/quicktime', 'video/x-msvideo', 'video/webm', + 'video/x-matroska', 'video/avi' ]; - if (!allowedMimes.includes(file.mimetype)) { - return cb(new Error('Только изображения и видео разрешены')); + // Также проверяем по расширению файла (на случай неправильного MIME типа) + const allowedExts = [ + '.jpg', '.jpeg', '.png', '.gif', '.webp', '.bmp', '.tiff', '.tif', + '.heic', '.heif', '.avif', '.ico', + '.mp4', '.mov', '.avi', '.webm', '.mkv' + ]; + + const fileExt = ext.toLowerCase(); + const isValidMime = allowedMimes.includes(file.mimetype); + const isValidExt = allowedExts.includes(fileExt); + + if (!isValidMime && !isValidExt) { + return cb(new Error(`Неподдерживаемый формат файла. Разрешены: JPEG, PNG, GIF, WebP, BMP, HEIC, MP4, MOV, AVI, WebM. Получен: ${file.mimetype || 'неизвестно'}`)); } cb(null, true); diff --git a/backend/scripts/fixReferrals.js b/backend/scripts/fixReferrals.js new file mode 100644 index 0000000..ba47fcd --- /dev/null +++ b/backend/scripts/fixReferrals.js @@ -0,0 +1,99 @@ +const mongoose = require('mongoose'); +const dotenv = require('dotenv'); +const path = require('path'); + +// Загрузить переменные окружения +dotenv.config({ path: path.join(__dirname, '../.env') }); + +const User = require('../models/User'); +const { getMoscowStartOfDay } = require('../utils/moscowTime'); +const { awardReferral } = require('../utils/tickets'); + +async function fixReferrals() { + try { + const mongoUri = process.env.MONGO_URI || 'mongodb://localhost:27017/nakama'; + await mongoose.connect(mongoUri); + console.log('✅ Подключено к MongoDB'); + + // Найти всех пользователей, у которых есть referredBy, но они еще не засчитаны + const usersWithReferrals = await User.find({ + referredBy: { $exists: true, $ne: null }, + referralCounted: { $ne: true } + }).populate('referredBy', 'username referralCode referralsCount'); + + console.log(`\n📊 Найдено ${usersWithReferrals.length} пользователей с незасчитанными рефералами\n`); + + let fixed = 0; + let needMoreDays = 0; + + for (const user of usersWithReferrals) { + if (!user.referredBy) { + console.log(`⚠️ Пользователь ${user.username} (${user._id}) имеет referredBy, но реферер не найден`); + continue; + } + + // Инициализировать loginDates если его нет + if (!user.loginDates || user.loginDates.length === 0) { + console.log(`📅 Пользователь ${user.username} (${user._id}) не имеет дат входа. Пропускаем.`); + needMoreDays++; + continue; + } + + // Получить уникальные даты из массива loginDates + const uniqueDates = new Set(); + user.loginDates.forEach(date => { + const dateObj = getMoscowStartOfDay(new Date(date)); + uniqueDates.add(dateObj.getTime()); + }); + + console.log(`👤 ${user.username} (${user._id})`); + console.log(` Реферер: ${user.referredBy.username} (${user.referredBy._id})`); + console.log(` Уникальных дат входа: ${uniqueDates.size}`); + console.log(` Даты: ${Array.from(uniqueDates).map(t => new Date(t).toLocaleDateString('ru-RU')).join(', ')}`); + + // Если есть 2 или более уникальные даты, засчитать реферала + if (uniqueDates.size >= 2) { + const referrer = user.referredBy; + + // Увеличить счетчик рефералов + referrer.referralsCount = (referrer.referralsCount || 0) + 1; + await referrer.save(); + + console.log(` ✅ РЕФЕРАЛ ЗАСЧИТАН! Новый счетчик: ${referrer.referralsCount}`); + + // Начислить баллы за реферала + try { + await awardReferral(referrer._id); + console.log(` ✅ Баллы начислены`); + } catch (error) { + console.error(` ⚠️ Ошибка начисления баллов:`, error.message); + } + + // Пометить как засчитанный + user.referralCounted = true; + user.loginDates = []; + await user.save(); + + fixed++; + } else { + console.log(` ⏳ Нужно еще ${2 - uniqueDates.size} уникальных дат входа`); + needMoreDays++; + } + + console.log(''); + } + + console.log(`\n📊 Итого:`); + console.log(` ✅ Засчитано рефералов: ${fixed}`); + console.log(` ⏳ Требуется больше дней: ${needMoreDays}`); + + await mongoose.connection.close(); + console.log('✅ Готово!'); + } catch (error) { + console.error('❌ Ошибка:', error); + process.exit(1); + } +} + +fixReferrals(); + diff --git a/frontend/src/components/CreatePostModal.jsx b/frontend/src/components/CreatePostModal.jsx index d6d34f3..0c40c23 100644 --- a/frontend/src/components/CreatePostModal.jsx +++ b/frontend/src/components/CreatePostModal.jsx @@ -72,32 +72,103 @@ export default function CreatePostModal({ user, onClose, onPostCreated, initialI }, []) const handleImageSelect = (e) => { - const files = Array.from(e.target.files) - if (files.length === 0) return + const files = Array.from(e.target.files || []) + if (files.length === 0) { + console.warn('No files selected') + return + } - const remainingSlots = 5 - images.length - const filesToAdd = files.slice(0, remainingSlots) - - filesToAdd.forEach(file => { - const reader = new FileReader() - reader.onloadend = () => { - setImagePreviews(prev => [...prev, reader.result]) + // Валидация файлов на фронтенде + const validFiles = files.filter(file => { + // Проверка типа файла + const validTypes = [ + 'image/jpeg', 'image/jpg', 'image/png', 'image/gif', 'image/webp', + 'image/bmp', 'image/tiff', 'image/heic', 'image/heif', 'image/avif', + 'video/mp4', 'video/quicktime', 'video/x-msvideo', 'video/webm' + ] + + if (!validTypes.includes(file.type)) { + console.warn('Invalid file type:', file.type, file.name) + alert(`Файл "${file.name}" имеет неподдерживаемый формат. Разрешены: JPEG, PNG, GIF, WebP, BMP, HEIC, MP4, MOV, AVI, WebM`) + return false } - reader.readAsDataURL(file) + + // Проверка размера (10MB) + const maxSize = 10 * 1024 * 1024 + if (file.size > maxSize) { + console.warn('File too large:', file.name, file.size) + alert(`Файл "${file.name}" слишком большой. Максимальный размер: 10MB`) + return false + } + + return true }) - setImages(prev => [...prev, ...filesToAdd]) + if (validFiles.length === 0) { + return + } + + const remainingSlots = 5 - images.length + const filesToAdd = validFiles.slice(0, remainingSlots) + + if (filesToAdd.length < validFiles.length) { + alert(`Можно добавить максимум 5 изображений. Добавлено ${filesToAdd.length} из ${validFiles.length}`) + } + + // Создаем превью для каждого файла + const previewPromises = filesToAdd.map((file, index) => { + return new Promise((resolve, reject) => { + const reader = new FileReader() + reader.onloadend = () => { + resolve({ index, result: reader.result }) + } + reader.onerror = (error) => { + console.error('Error reading file:', error, file.name) + reject(new Error(`Ошибка чтения файла "${file.name}"`)) + } + reader.readAsDataURL(file) + }) + }) + + // Ждем загрузки всех превью, затем обновляем состояние + Promise.all(previewPromises) + .then((previewResults) => { + // Сортируем результаты по индексу + previewResults.sort((a, b) => a.index - b.index) + const newPreviews = previewResults.map(r => r.result) + + setImagePreviews(prev => [...prev, ...newPreviews]) + + // Добавляем файлы в массив после успешной загрузки превью + setImages(prev => { + const newImages = [...prev, ...filesToAdd] + console.log('Images after add:', newImages.length, newImages.map(f => ({ name: f.name, type: f.type, size: f.size }))) + return newImages + }) + }) + .catch((error) => { + console.error('Error loading previews:', error) + alert(error.message || 'Ошибка загрузки превью изображений') + }) + hapticFeedback('light') + // Очищаем input для возможности повторного выбора того же файла if (fileInputRef.current) { fileInputRef.current.value = '' } } const handleRemoveImage = (index) => { - setImages(prev => prev.filter((_, i) => i !== index)) + console.log('Removing image at index:', index, 'Total images:', images.length) + setImages(prev => { + const newImages = prev.filter((_, i) => i !== index) + console.log('Images after remove:', newImages.length) + return newImages + }) setImagePreviews(prev => prev.filter((_, i) => i !== index)) setExternalImages(prev => prev.filter((_, i) => i !== index)) + hapticFeedback('light') } const handleTagInputChange = (e) => { @@ -216,12 +287,22 @@ export default function CreatePostModal({ user, onClose, onPostCreated, initialI formData.append('isNSFW', isNSFW) formData.append('isHomo', isHomo) - images.forEach((image, index) => { - if (image instanceof File) { - formData.append('images', image) - } + // Отправляем все файлы, которые являются File объектами + const fileImages = images.filter(img => img instanceof File) + + if (fileImages.length === 0 && images.length > 0) { + console.warn('No File objects found in images array:', images.map(img => typeof img)) + } + + fileImages.forEach((image, index) => { + formData.append('images', image, image.name) + console.log(`Appending image ${index}:`, image.name, image.type, image.size) }) + console.log('FormData images count:', fileImages.length, 'Total images:', images.length) + + // Если есть externalImages (из поиска), добавляем их отдельно + if (externalImages.length > 0) { formData.append('externalImages', JSON.stringify(externalImages)) } @@ -405,10 +486,10 @@ export default function CreatePostModal({ user, onClose, onPostCreated, initialI {/* Футер с действиями */}