const userDB = require("../db/user.db") const commentDB = require("../db/comment.db") const { addCommentToRedis, removeCommentFromRedis, getCommentsForDeal, } = require("./redis/commentCache.service") const { getOrCacheDeal, getDealIdByCommentId } = require("./redis/dealCache.service") const { generateCommentId } = require("./redis/commentId.service") const { queueCommentCreate, queueCommentDelete, queueNotificationCreate, } = require("./redis/dbSync.service") const { publishNotification } = require("./redis/notificationPubsub.service") const { trackUserCategoryInterest, USER_INTEREST_ACTIONS } = require("./userInterest.service") const { sanitizeOptionalPlainText } = require("../utils/inputSanitizer") function parseParentId(value) { if (value === undefined || value === null || value === "" || value === "null") return null const pid = Number(value) if (!Number.isInteger(pid) || pid <= 0) throw new Error("Gecersiz parentId.") return pid } function normalizeSort(value) { const normalized = String(value || "new").trim().toLowerCase() if (["top", "best", "liked"].includes(normalized)) return "TOP" return "NEW" } async function ensureDealCached(dealId) { return getOrCacheDeal(dealId, { ttlSeconds: 15 * 60 }) } async function getCommentsByDealId(dealId, { parentId, page, limit, sort, viewer } = {}) { const id = Number(dealId) const deal = await ensureDealCached(id) if (!deal) throw new Error("Deal bulunamadi.") return getCommentsForDeal({ dealId: id, deal, parentId: parseParentId(parentId), page, limit, sort: normalizeSort(sort), viewerId: viewer?.userId ?? null, }) } async function createComment({ dealId, userId, text, parentId = null }) { const normalizedText = sanitizeOptionalPlainText(text, { maxLength: 2000 }) if (!normalizedText) { throw new Error("Yorum bos olamaz.") } const deal = await ensureDealCached(dealId) if (!deal) throw new Error("Deal bulunamadi.") if (deal.status !== "ACTIVE" && deal.status !== "EXPIRED") { throw new Error("Bu deal icin yorum acilamaz.") } let parent = null if (parentId != null) { const pid = parseParentId(parentId) const comments = Array.isArray(deal.comments) ? deal.comments : [] const cachedParent = comments.find((c) => Number(c.id) === Number(pid)) if (!cachedParent || cachedParent.deletedAt) throw new Error("Yanıtlanan yorum bulunamadi.") if (Number(cachedParent.dealId) !== Number(dealId)) { throw new Error("Yanıtlanan yorum bu deal'a ait degil.") } parent = { id: cachedParent.id, dealId: cachedParent.dealId, userId: Number(cachedParent.userId) || null, } } const user = await userDB.findUser( { id: userId }, { select: { id: true, username: true, avatarUrl: true } } ) if (!user) throw new Error("Kullanici bulunamadi.") const createdAt = new Date() const commentId = await generateCommentId() const comment = { id: commentId, text: normalizedText, userId, dealId, parentId: parent ? parent.id : null, createdAt, likeCount: 0, repliesCount: 0, user, } await addCommentToRedis({ ...comment, repliesCount: 0, }) queueCommentCreate({ commentId, dealId, userId, text: normalizedText, parentId: parent ? parent.id : null, createdAt: createdAt.toISOString(), }).catch((err) => console.error("DB sync comment create failed:", err?.message || err)) trackUserCategoryInterest({ userId, categoryId: deal.categoryId, action: USER_INTEREST_ACTIONS.COMMENT_CREATE, }).catch((err) => console.error("User interest track failed:", err?.message || err)) const parentUserId = Number(parent?.userId) if ( parent && Number.isInteger(parentUserId) && parentUserId > 0 && parentUserId !== Number(userId) ) { const notificationPayload = { userId: parentUserId, message: "Yorumuna cevap geldi.", type: "COMMENT_REPLY", extras: { dealId: Number(dealId), commentId: Number(commentId), parentCommentId: Number(parent.id), }, createdAt: createdAt.toISOString(), } queueNotificationCreate(notificationPayload).catch((err) => console.error("DB sync comment reply notification failed:", err?.message || err) ) publishNotification(notificationPayload).catch((err) => console.error("Comment reply notification publish failed:", err?.message || err) ) } return comment } async function deleteComment(commentId, userId) { const cid = Number(commentId) if (!Number.isInteger(cid) || cid <= 0) throw new Error("Gecersiz commentId.") let dealId = await getDealIdByCommentId(cid) let dbFallback = null if (!dealId) { dbFallback = await commentDB.findComment( { id: cid }, { select: { id: true, dealId: true, userId: true, parentId: true, deletedAt: true } } ) if (!dbFallback || dbFallback.deletedAt) throw new Error("Yorum bulunamadi.") dealId = dbFallback.dealId } const deal = await ensureDealCached(dealId) if (!deal) throw new Error("Yorum bulunamadi.") const comments = Array.isArray(deal.comments) ? deal.comments : [] const comment = comments.find((c) => Number(c.id) === cid) const effective = comment || dbFallback if (!effective || effective.deletedAt) throw new Error("Yorum bulunamadi.") if (Number(effective.userId) !== Number(userId)) throw new Error("Bu yorumu silme yetkin yok.") queueCommentDelete({ commentId: cid, dealId: effective.dealId, createdAt: new Date().toISOString(), }).catch((err) => console.error("DB sync comment delete failed:", err?.message || err)) await removeCommentFromRedis({ commentId: cid, dealId: effective.dealId, }) return { message: "Yorum silindi." } } async function deleteCommentAsMod(commentId) { const cid = Number(commentId) if (!Number.isInteger(cid) || cid <= 0) throw new Error("Gecersiz commentId.") let dealId = await getDealIdByCommentId(cid) let dbFallback = null if (!dealId) { dbFallback = await commentDB.findComment( { id: cid }, { select: { id: true, dealId: true, userId: true, parentId: true, deletedAt: true } } ) if (!dbFallback || dbFallback.deletedAt) throw new Error("Yorum bulunamadi.") dealId = dbFallback.dealId } const deal = await ensureDealCached(dealId) if (!deal) throw new Error("Yorum bulunamadi.") const comments = Array.isArray(deal.comments) ? deal.comments : [] const comment = comments.find((c) => Number(c.id) === cid) const effective = comment || dbFallback if (!effective || effective.deletedAt) throw new Error("Yorum bulunamadi.") queueCommentDelete({ commentId: cid, dealId: effective.dealId, createdAt: new Date().toISOString(), }).catch((err) => console.error("DB sync comment delete failed:", err?.message || err)) await removeCommentFromRedis({ commentId: cid, dealId: effective.dealId, }) return { message: "Yorum silindi." } } module.exports = { getCommentsByDealId, createComment, deleteComment, deleteCommentAsMod, }