const User = require('../models/User'); const TicketActivity = require('../models/TicketActivity'); const Post = require('../models/Post'); const { getMoscowDate, formatMoscowTime } = require('./moscowTime'); /** * Проверяет, можно ли начислить баллы с учетом лимитов и антифрод правил */ async function canAwardTickets(userId, actionType, options = {}) { // Используем московское время для определения дня const today = getMoscowDate(); const activity = await TicketActivity.getOrCreateToday(userId); // Проверка лимитов в зависимости от типа действия switch (actionType) { case 'post_created': return activity.postsCreated < 5; case 'like_given': return activity.likesGiven < 50; case 'like_received': return activity.likesReceived < 100; case 'comment_written': // Проверка длины комментария (антифрод) if (options.commentLength && options.commentLength < 10) { return false; } return activity.commentsWritten < 20; case 'comment_received': return true; // Нет лимита на получение комментариев case 'referral': return activity.referralsCounted < 3; case 'art_reaction': // Проверка лимита на реакцию на арт (100 баллов в сутки с одного арта) const postId = options.postId; if (!postId) return false; const postIdStr = postId.toString(); const currentPoints = (activity.artReactionsPoints && activity.artReactionsPoints[postIdStr]) || 0; const newPoints = currentPoints + (options.points || 0); return newPoints <= 100; default: return false; } } /** * Проверяет возраст аккаунта (антифрод - аккаунты младше 24 часов не считаются) */ async function isAccountOldEnough(userId) { const user = await User.findById(userId); if (!user) return false; const accountAge = Date.now() - new Date(user.createdAt).getTime(); const hours24 = 24 * 60 * 60 * 1000; return accountAge >= hours24; } /** * Начисляет баллы пользователю с проверкой лимитов и антифрод */ async function awardTickets(userId, points, actionType, options = {}) { try { // Антифрод: проверка возраста аккаунта для входящих реакций if (actionType === 'like_received' || actionType === 'comment_received') { const senderId = options.senderId; if (senderId) { const senderOldEnough = await isAccountOldEnough(senderId); if (!senderOldEnough) { console.log(`[Tickets ${formatMoscowTime()}] Пропуск начисления: аккаунт отправителя младше 24 часов (${senderId})`); return { success: false, reason: 'account_too_new' }; } } } // Проверка возможности начисления const canAward = await canAwardTickets(userId, actionType, options); if (!canAward) { console.log(`[Tickets ${formatMoscowTime()}] Лимит достигнут для ${actionType} пользователя ${userId}`); return { success: false, reason: 'limit_reached' }; } // Получить активность за сегодня const activity = await TicketActivity.getOrCreateToday(userId); // Обновить счетчики активности switch (actionType) { case 'post_created': activity.postsCreated += 1; break; case 'like_given': activity.likesGiven += 1; break; case 'like_received': activity.likesReceived += 1; break; case 'comment_written': activity.commentsWritten += 1; break; case 'comment_received': activity.commentsReceived += 1; break; case 'referral': activity.referralsCounted += 1; break; case 'art_reaction': const postId = options.postId; if (postId) { if (!activity.artReactionsPoints) { activity.artReactionsPoints = {}; } const postIdStr = postId.toString(); const currentPoints = activity.artReactionsPoints[postIdStr] || 0; activity.artReactionsPoints[postIdStr] = currentPoints + points; activity.markModified('artReactionsPoints'); } break; } await activity.save(); // Начислить баллы пользователю await User.findByIdAndUpdate(userId, { $inc: { tickets: points } }); console.log(`[Tickets ${formatMoscowTime()}] Начислено ${points} баллов пользователю ${userId} за ${actionType}`); return { success: true, points }; } catch (error) { console.error(`[Tickets] Ошибка начисления баллов:`, error); return { success: false, reason: 'error', error: error.message }; } } /** * Начисляет баллы за создание поста */ async function awardPostCreation(userId) { return await awardTickets(userId, 15, 'post_created'); } /** * Начисляет баллы за лайк (тому, кто ставит) */ async function awardLikeGiven(userId) { return await awardTickets(userId, 1, 'like_given'); } /** * Начисляет баллы за полученный лайк (автору поста) */ async function awardLikeReceived(authorId, likerId) { return await awardTickets(authorId, 2, 'like_received', { senderId: likerId }); } /** * Начисляет баллы за написанный комментарий */ async function awardCommentWritten(userId, commentLength) { return await awardTickets(userId, 4, 'comment_written', { commentLength }); } /** * Начисляет баллы за полученный комментарий (автору поста) */ async function awardCommentReceived(authorId, commenterId) { return await awardTickets(authorId, 6, 'comment_received', { senderId: commenterId }); } /** * Начисляет баллы за реферала */ async function awardReferral(userId) { return await awardTickets(userId, 100, 'referral'); } /** * Начисляет баллы за реакцию на арт (лайк) */ async function awardArtLike(authorId, likerId, postId) { return await awardTickets(authorId, 8, 'art_reaction', { senderId: likerId, postId, points: 8 }); } /** * Начисляет баллы за комментарий под артом */ async function awardArtComment(authorId, commenterId, postId) { return await awardTickets(authorId, 12, 'art_reaction', { senderId: commenterId, postId, points: 12 }); } module.exports = { awardTickets, awardPostCreation, awardLikeGiven, awardLikeReceived, awardCommentWritten, awardCommentReceived, awardReferral, awardArtLike, awardArtComment, canAwardTickets, isAccountOldEnough };