179 lines
5.0 KiB
JavaScript
179 lines
5.0 KiB
JavaScript
const dealDB = require("../db/deal.db")
|
||
const commentDB = require("../db/comment.db")
|
||
const prisma = require("../db/client")
|
||
|
||
function assertPositiveInt(v, name = "id") {
|
||
const n = Number(v)
|
||
if (!Number.isInteger(n) || n <= 0) throw new Error(`Geçersiz ${name}.`)
|
||
return n
|
||
}
|
||
|
||
const DEFAULT_LIMIT = 20
|
||
const MAX_LIMIT = 50
|
||
const MAX_SKIP = 5000
|
||
|
||
function clampPagination({ page, limit }) {
|
||
const rawPage = Number(page)
|
||
const rawLimit = Number(limit)
|
||
const normalizedPage = Number.isFinite(rawPage) ? Math.max(1, Math.floor(rawPage)) : 1
|
||
let normalizedLimit = Number.isFinite(rawLimit) ? Math.max(1, Math.floor(rawLimit)) : DEFAULT_LIMIT
|
||
normalizedLimit = Math.min(MAX_LIMIT, normalizedLimit)
|
||
const skip = (normalizedPage - 1) * normalizedLimit
|
||
if (skip > MAX_SKIP) throw new Error("PAGE_TOO_DEEP")
|
||
return { page: normalizedPage, limit: normalizedLimit, skip }
|
||
}
|
||
|
||
function parseParentId(value) {
|
||
if (value === undefined) return null
|
||
if (value === null) return null
|
||
if (value === "" || value === "null") return null
|
||
const pid = Number(value)
|
||
if (!Number.isInteger(pid) || pid <= 0) throw new Error("Geçersiz 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 getCommentsByDealId(dealId, { parentId, page, limit, sort, viewer } = {}) {
|
||
const id = Number(dealId)
|
||
|
||
const deal = await dealDB.findDeal({ id })
|
||
if (!deal) throw new Error("Deal bulunamadı.")
|
||
|
||
const include = {
|
||
user: { select: { id: true, username: true, avatarUrl: true } },
|
||
_count: { select: { replies: true } },
|
||
}
|
||
const pagination = clampPagination({ page, limit })
|
||
const parsedParentId = parseParentId(parentId)
|
||
const sortMode = normalizeSort(sort)
|
||
const orderBy =
|
||
sortMode === "TOP"
|
||
? [{ likeCount: "desc" }, { createdAt: "desc" }]
|
||
: [{ createdAt: "desc" }]
|
||
|
||
const where = { dealId: id, parentId: parsedParentId }
|
||
const [results, total] = await Promise.all([
|
||
commentDB.findComments(where, {
|
||
include,
|
||
orderBy,
|
||
skip: pagination.skip,
|
||
take: pagination.limit,
|
||
}),
|
||
commentDB.countComments(where),
|
||
])
|
||
|
||
let likedIds = new Set()
|
||
if (viewer?.userId && results.length > 0) {
|
||
const commentLikeDb = require("../db/commentLike.db")
|
||
const likes = await commentLikeDb.findLikesByUserAndCommentIds(
|
||
viewer.userId,
|
||
results.map((c) => c.id)
|
||
)
|
||
likedIds = new Set(likes.map((l) => l.commentId))
|
||
}
|
||
|
||
const enriched = results.map((comment) => ({
|
||
...comment,
|
||
myLike: likedIds.has(comment.id),
|
||
}))
|
||
|
||
return {
|
||
page: pagination.page,
|
||
total,
|
||
totalPages: Math.ceil(total / pagination.limit),
|
||
results: enriched,
|
||
}
|
||
}
|
||
|
||
async function createComment({ dealId, userId, text, parentId = null }) {
|
||
if (!text || typeof text !== "string" || !text.trim()) {
|
||
throw new Error("Yorum boş olamaz.")
|
||
}
|
||
|
||
const trimmed = text.trim()
|
||
const include = { user: { select: { id: true, username: true, avatarUrl: true } } }
|
||
|
||
return prisma.$transaction(async (tx) => {
|
||
const deal = await dealDB.findDeal({ id: dealId }, { select: { id: true, status: true } }, tx)
|
||
if (!deal) throw new Error("Deal bulunamadı.")
|
||
if (deal.status !== "ACTIVE" && deal.status !== "EXPIRED") {
|
||
throw new Error("Bu deal için yorum açılamaz.")
|
||
}
|
||
|
||
// ✅ Reply ise parent doğrula
|
||
let parent = null
|
||
if (parentId != null) {
|
||
const pid = Number(parentId)
|
||
if (!Number.isFinite(pid) || pid <= 0) throw new Error("Geçersiz parentId.")
|
||
|
||
parent = await commentDB.findComment({ id: pid }, { select: { id: true, dealId: true } }, tx)
|
||
if (!parent) throw new Error("Yanıtlanan yorum bulunamadı.")
|
||
if (parent.dealId !== dealId) throw new Error("Yanıtlanan yorum bu deal'a ait değil.")
|
||
}
|
||
|
||
const comment = await commentDB.createComment(
|
||
{
|
||
text: trimmed,
|
||
userId,
|
||
dealId,
|
||
parentId: parent ? parent.id : null,
|
||
},
|
||
{ include },
|
||
tx
|
||
)
|
||
|
||
await dealDB.updateDeal(
|
||
{ id: dealId },
|
||
{ commentCount: { increment: 1 } },
|
||
{},
|
||
tx
|
||
)
|
||
|
||
return comment
|
||
})
|
||
}
|
||
|
||
|
||
async function deleteComment(commentId, userId) {
|
||
|
||
const comment = await commentDB.findComment(
|
||
{ id: commentId },
|
||
{ select: { userId: true, dealId: true, deletedAt: true } }
|
||
)
|
||
|
||
if (!comment || comment.deletedAt) throw new Error("Yorum bulunamadı.")
|
||
if (comment.userId !== userId) throw new Error("Bu yorumu silme yetkin yok.")
|
||
|
||
await prisma.$transaction(async (tx) => {
|
||
const result = await commentDB.softDeleteComment({ id: commentId, deletedAt: null }, tx)
|
||
if (result.count > 0) {
|
||
await dealDB.updateDeal(
|
||
{ id: comment.dealId },
|
||
{ commentCount: { decrement: 1 } },
|
||
{},
|
||
tx
|
||
)
|
||
}
|
||
})
|
||
|
||
return { message: "Yorum silindi." }
|
||
}
|
||
|
||
async function commentChange(length,dealId){
|
||
|
||
|
||
|
||
}
|
||
|
||
module.exports = {
|
||
getCommentsByDealId,
|
||
createComment,
|
||
deleteComment,
|
||
}
|