nakama/backend/utils/tickets.js

222 lines
7.1 KiB
JavaScript
Raw Normal View History

2025-12-07 02:20:45 +00:00
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
};