diff --git a/backend/routes/modApp.js b/backend/routes/modApp.js index ae99240..4b6ca51 100644 --- a/backend/routes/modApp.js +++ b/backend/routes/modApp.js @@ -354,6 +354,10 @@ router.delete('/posts/:id', authenticateModeration, requireModerationAccess, asy console.error('❌ Ошибка удаления файлов из MinIO:', error); } + // Списать все билеты, связанные с удаленным постом (только если пост создан сегодня) + const { deductPostDeletion } = require('../utils/tickets'); + await deductPostDeletion(post); + await Post.deleteOne({ _id: post._id }); res.json({ success: true }); }); diff --git a/backend/routes/moderation.js b/backend/routes/moderation.js index 3916ec8..5dbf278 100644 --- a/backend/routes/moderation.js +++ b/backend/routes/moderation.js @@ -84,6 +84,12 @@ router.put('/reports/:id', authenticate, requireModerator, async (req, res) => { // Выполнить действие if (action === 'delete_post' && report.post) { + const post = await Post.findById(report.post._id); + if (post) { + // Списать все билеты, связанные с удаленным постом (только если пост создан сегодня) + const { deductPostDeletion } = require('../utils/tickets'); + await deductPostDeletion(post); + } await Post.findByIdAndDelete(report.post._id); report.status = 'resolved'; } else if (action === 'ban_user' && report.post) { diff --git a/backend/routes/posts.js b/backend/routes/posts.js index 62679c1..72ebe8a 100644 --- a/backend/routes/posts.js +++ b/backend/routes/posts.js @@ -391,6 +391,10 @@ router.delete('/:id', authenticate, async (req, res) => { // Продолжаем удаление поста даже если файлы не удалились } + // Списать все билеты, связанные с удаленным постом (только если пост создан сегодня) + const { deductPostDeletion } = require('../utils/tickets'); + await deductPostDeletion(post); + await Post.findByIdAndDelete(req.params.id); res.json({ message: 'Пост удален' }); } catch (error) { diff --git a/backend/utils/tickets.js b/backend/utils/tickets.js index f956e06..1e132c0 100644 --- a/backend/utils/tickets.js +++ b/backend/utils/tickets.js @@ -205,6 +205,164 @@ async function awardArtComment(authorId, commenterId, postId) { }); } +/** + * Списывает билеты за действие (лайк/комментарий) с удаленным постом + */ +async function deductAction(userId, points, actionType, postCreatedAt) { + try { + const { getMoscowStartOfDay } = require('./moscowTime'); + + // Проверяем, что действие было в день создания поста + const postDate = getMoscowStartOfDay(postCreatedAt); + const today = getMoscowStartOfDay(); + + // Если пост создан не сегодня, не списываем билеты + if (postDate.getTime() !== today.getTime()) { + return { success: false, reason: 'post_not_today' }; + } + + // Получить активность за сегодня + const activity = await TicketActivity.getOrCreateToday(userId); + + // Уменьшить счетчики активности + switch (actionType) { + case 'post_created': + if (activity.postsCreated > 0) { + activity.postsCreated -= 1; + } + break; + case 'like_given': + if (activity.likesGiven > 0) { + activity.likesGiven -= 1; + } + break; + case 'like_received': + if (activity.likesReceived > 0) { + activity.likesReceived -= 1; + } + break; + case 'comment_written': + if (activity.commentsWritten > 0) { + activity.commentsWritten -= 1; + } + break; + case 'comment_received': + if (activity.commentsReceived > 0) { + activity.commentsReceived -= 1; + } + break; + } + + await activity.save(); + + // Списать билеты (но не меньше 0) + const user = await User.findById(userId); + if (user) { + const ticketsToDeduct = Math.min(points, user.tickets || 0); + if (ticketsToDeduct > 0) { + await User.findByIdAndUpdate(userId, { + $inc: { tickets: -ticketsToDeduct } + }); + console.log(`[Tickets ${formatMoscowTime()}] Списано ${ticketsToDeduct} билетов пользователю ${userId} за ${actionType}`); + return { success: true, points: -ticketsToDeduct }; + } + } + + return { success: true, points: 0 }; + } catch (error) { + console.error(`[Tickets] Ошибка списания билетов за действие:`, error); + return { success: false, reason: 'error', error: error.message }; + } +} + +/** + * Списывает все билеты, связанные с удаленным постом + * Списывает только действия, которые были в день создания поста + */ +async function deductPostDeletion(post) { + try { + const { getMoscowStartOfDay } = require('./moscowTime'); + + // Проверяем, что пост был создан сегодня + const postDate = getMoscowStartOfDay(post.createdAt); + const today = getMoscowStartOfDay(); + + // Если пост создан не сегодня, не списываем билеты + if (postDate.getTime() !== today.getTime()) { + console.log(`[Tickets ${formatMoscowTime()}] Пост создан не сегодня, билеты не списываются`); + return { success: false, reason: 'post_not_today' }; + } + + const authorId = post.author; + const postCreatedAt = post.createdAt; + + // 1. Списываем билеты за создание поста у автора + await deductAction(authorId, 15, 'post_created', postCreatedAt); + + // 2. Списываем билеты за полученные лайки у автора + if (post.likes && post.likes.length > 0) { + for (const likerId of post.likes) { + // Проверяем, что лайк был поставлен в день создания поста + // (лайки не имеют даты, но если пост создан сегодня, то и лайки сегодня) + await deductAction(authorId, 2, 'like_received', postCreatedAt); + } + } + + // 3. Списываем билеты за полученные комментарии у автора + if (post.comments && post.comments.length > 0) { + for (const comment of post.comments) { + // Проверяем, что комментарий был написан в день создания поста + const commentDate = getMoscowStartOfDay(comment.createdAt || comment.created_at || postCreatedAt); + if (commentDate.getTime() === postDate.getTime()) { + await deductAction(authorId, 6, 'comment_received', postCreatedAt); + } + } + } + + // 4. Списываем билеты у тех, кто поставил лайк (за поставленный лайк) + if (post.likes && post.likes.length > 0) { + for (const likerId of post.likes) { + // Проверяем, что это не автор поста + if (!likerId.equals(authorId)) { + await deductAction(likerId, 1, 'like_given', postCreatedAt); + } + } + } + + // 5. Списываем билеты у тех, кто написал комментарий (за написанный комментарий) + if (post.comments && post.comments.length > 0) { + for (const comment of post.comments) { + const commentAuthorId = comment.author; + if (commentAuthorId && !commentAuthorId.equals(authorId)) { + // Проверяем, что комментарий был написан в день создания поста + const commentDate = getMoscowStartOfDay(comment.createdAt || comment.created_at || postCreatedAt); + if (commentDate.getTime() === postDate.getTime()) { + // Проверяем длину комментария (>= 10 символов для начисления) + const commentLength = (comment.content || '').trim().length; + if (commentLength >= 10) { + await deductAction(commentAuthorId, 4, 'comment_written', postCreatedAt); + } + } + } + } + } + + console.log(`[Tickets ${formatMoscowTime()}] Списаны все билеты за удаление поста ${post._id}`); + return { success: true }; + } catch (error) { + console.error(`[Tickets] Ошибка списания билетов за удаление поста:`, error); + return { success: false, reason: 'error', error: error.message }; + } +} + +/** + * Списывает билеты при удалении поста (старая функция для обратной совместимости) + * @deprecated Используйте deductPostDeletion вместо этого + */ +async function deductPostCreation(userId, postCreatedAt) { + return await deductAction(userId, 15, 'post_created', postCreatedAt); +} + module.exports = { awardTickets, awardPostCreation, @@ -215,6 +373,9 @@ module.exports = { awardReferral, awardArtLike, awardArtComment, + deductPostCreation, + deductPostDeletion, + deductAction, canAwardTickets, isAccountOldEnough };