YouTube-এর মত nested comment
তুমি কীভাবে বানাবে?
রহিমের comment-এ করিম reply করল,
করিমের reply-তে জাবের reply করল —
এই ৩ level
কীভাবে database-এ রাখব?
Self-Referential Table
Recursive Fetch
Admin Dashboard
Soft Delete
3-Layer Check
Production Comment System বানাব
SLIDE 01
সবাই যে ভুলগুলো করে
Simple comment system কেন fail করে
❌
ভুল ১: Separate reply table — reply_of_reply table আলাদা করলে ৫ level হলে
৫টা table লাগবে!
❌
ভুল ২: Hard delete — comment delete করলে সব reply মুছে যায়। রহিমের thread
ধ্বংস।
❌
ভুল ৩: No permission check — যেকোনো user যেকোনো course-এ comment করতে
পারে?
❌
ভুল ৪: N+1 query — প্রতিটা comment-এর জন্য আলাদা query → ১০০ comment = ১০০
DB query
✅
সমাধান ১: Self-Referential table — একটাই table, parentId দিয়ে hierarchy
✅
সমাধান ২: Soft delete — isDeleted flag, data থাকে কিন্তু দেখায় না
✅
সমাধান ৩: 3-layer permission check — enrollment → host → role
✅
সমাধান ৪: nestedSelectFields() — একটা query-তে সব level fetch
SLIDE 02
Self-Referential Table Design
একটাই table, অসীম level
-- Comment Table
CREATE TABLE Comment (
id UUID PRIMARY KEY,
content TEXT NOT NULL,
authorId UUID REFERENCES User(id),
lessonId UUID REFERENCES Lesson(id),
parentId UUID REFERENCES Comment(id),
-- নিজেকেই reference করছে!
isDeleted BOOLEAN DEFAULT false,
createdAt TIMESTAMP DEFAULT NOW()
);
-- রহিমের মূল comment: parentId = NULL
-- করিমের reply: parentId = রহিমের comment id
-- জাবেরের reply: parentId = করিমের reply id
💬 রহিমের comment (parentId: null)
↳ 💬 করিমের reply (parentId: রহিম.id)
↳ 💬 জাবেরের reply (parentId: করিম.id)
↳ 💬 হাচানের reply (parentId: করিম.id)
↳ 💬 অন্যর reply (parentId: রহিম.id)
Prisma Schema
model Comment {
id String @id
content String
author User @relation(...)
lesson Lesson @relation(...)
// Self-reference — জাদু এখানে
parentId String?
parent Comment? @relation(
"Replies", fields:[parentId],
references:[id])
replies Comment[] @relation("Replies")
isDeleted Boolean @default(false)
createdAt DateTime @default(now())
}
💡
একটাই Comment table — ৩ level হোক, ১০ level হোক, সবই এখানে। parentId = null মানে মূল
comment।
SLIDE 03
Recursive Reply Fetch — nestedSelectFields()
একটা query-তে সব level নামিয়ে আনা
// Nested select তৈরি করো
function nestedSelectFields(depth = 3) {
if (depth === 0) return {};
return {
id: true,
content: true,
isDeleted: true,
createdAt: true,
author: {
select: { id: true, name: true }
},
replies: {
where: { isDeleted: false },
orderBy: { createdAt: 'asc' },
select: {
...nestedSelectFields(depth - 1),
// Recursion! নিজেকে call করছে
}
}
};
}
// Use করো
const comments = await prisma.comment.findMany({
where: { lessonId, parentId: null },
select: nestedSelectFields(3),
orderBy: { createdAt: 'desc' }
});
Output Structure
[
{
id: "c1",
content: "রহিমের comment",
replies: [
{
id: "c2",
content: "করিমের reply",
replies: [
{
id: "c3",
content: "জাবেরের reply",
replies: [] // depth শেষ
}
]
}
]
}
]
✅
depth=3 মানে ৩ level deep fetch। একটাই Prisma query! ৩টা আলাদা query নয়।
SLIDE 04
Admin Dashboard — ৫টা Key Query
Admin কী দেখতে পাবে?
১
Total Comments — lesson-এ মোট কতটা comment আছে (isDeleted: false)
২
Flagged Comments — report করা comment list — Admin review করবে
৩
Top Commenters — কোন user সবচেয়ে বেশি active → engagement দেখা
৪
Deleted Comments — soft delete হওয়া comment — audit trail
৫
Comment Analytics — দিনে কতটা comment হচ্ছে → growth track
// Admin dashboard data
async function getAdminStats(lessonId) {
const [total, flagged, topUsers] =
await Promise.all([
// Query 1: total
prisma.comment.count({
where: { lessonId, isDeleted: false }
}),
// Query 2: flagged
prisma.comment.findMany({
where: { lessonId, isFlagged: true },
include: { author: true }
}),
// Query 3: top commenters
prisma.comment.groupBy({
by: ['authorId'],
where: { lessonId },
_count: { id: true },
orderBy: { _count: { id: 'desc' } },
take: 10
})
]);
return { total, flagged, topUsers };
}
SLIDE 05
Host-Based + Role-Based Filtering
কোন platform-এ কে কী দেখতে পাবে?
Host-Based Filtering
- Platform A-এর comment Platform B দেখবে না
- host field দিয়ে isolate করা হয়
- Multi-tenant system-এ জরুরি
Role-Based Filtering
- Student: শুধু নিজের course-এর comment
- Teacher: নিজের course-এর সব comment
- Admin: সব course-এর সব comment
- SuperAdmin: সব platform-এর সব comment
function buildCommentFilter(user, host) {
const base = {
host, // Host isolation
isDeleted: false
};
switch (user.role) {
case 'student':
return {
...base,
lesson: {
course: {
enrollments: {
some: { studentId: user.id }
}
}
}
};
case 'teacher':
return {
...base,
lesson: {
course: { teacherId: user.id }
}
};
case 'admin':
return { ...base };
case 'superadmin':
return { isDeleted: false }; // no host filter
}
}
SLIDE 06
Soft Delete & Push Notification
Delete মানে মোছা নয় — লুকানো
❌
Hard Delete সমস্যা: রহিমের comment delete করলে করিম ও জাবেরের reply orphan
হয়ে যাবে। Thread নষ্ট।
// Soft Delete — data রাখো, hide করো
async function deleteComment(commentId, userId) {
const comment = await prisma.comment
.findUnique({ where: { id: commentId } });
// শুধু নিজের comment delete করতে পারবে
// (অথবা admin)
if (comment.authorId !== userId) {
throw new AppError('Permission নেই', 403);
}
await prisma.comment.update({
where: { id: commentId },
data: {
isDeleted: true,
content: '[এই comment মুছে ফেলা হয়েছে]'
}
});
}
✅
Soft delete করলে রহিমের comment "[মুছে ফেলা হয়েছে]" দেখাবে কিন্তু করিমের reply ঠিকই দেখা
যাবে। Thread অক্ষুণ্ণ।
Push Notification
- করিম reply করলে → রহিম notification পাবে
- Admin delete করলে → author-কে জানাও
- Comment flag হলে → Admin alert
// Reply করলে parent-কে notify করো
if (comment.parentId) {
const parent = await prisma.comment.findUnique(
{ where: { id: comment.parentId },
include: { author: true } }
);
await sendPushNotification(
parent.author.id,
`${user.name} তোমার comment-এ reply করেছে`
);
}
SLIDE 07
Comment করার আগে ৩ Layer Check
যে কেউ comment করতে পারবে না
১
Authentication — Login করা আছে? JWT valid? না থাকলে ৪০১।
২
Enrollment Check — রহিম কি এই course-এ enrolled? কিনেছে? না হলে ৪০৩।
৩
Content Validation — Comment ৫০০ char-এর বেশি? Empty? Spam keyword আছে? তাহলে
৪০০।
✅
সব check পাস করলে তবেই DB-তে insert। কোনো check fail করলে error response — comment সেভ হয় না।
async function createComment(req, res) {
const { content, lessonId, parentId } = req.body;
// Layer 1: authenticate middleware handled it
// Layer 2: enrollment check
const enrolled = await checkEnrollment(
req.user.id, lessonId
);
if (!enrolled) throw new AppError(
'এই course-এ enrolled নও', 403
);
// Layer 3: content validation (Zod schema-তে)
// content: z.string().min(1).max(500)
const comment = await prisma.comment.create({
data: { content, lessonId, parentId,
authorId: req.user.id }
});
// notify parent author...
}
SLIDE 08
এই Comment System কেন Best?
Production-ready, scalable, safe
Flexible
- Self-referential → unlimited nesting
- nestedSelectFields() → recursive fetch
- একটা table সব কাজ করে
Safe
- 3-layer check → unauthorized comment নেই
- Soft delete → data হারায় না
- Host isolation → cross-platform leak নেই
Smart
- Role-based filtering → সবাই সঠিক data দেখে
- Admin dashboard → ৫ key query
- Push notification → engagement বাড়ে
🎯
রহিম comment করবে, করিম reply করবে, জাবের reply-তে reply করবে — সব কিছু একটা table-এ, একটা
query-তে, তিনটা check-এ। এটাই professional comment system।