feat: add comments feature with nested replies and recursive rendering
All checks were successful
ci/woodpecker/pr/pipeline Pipeline was successful
All checks were successful
ci/woodpecker/pr/pipeline Pipeline was successful
Implement full comments system: domain entities (Comment, CommentLike), value objects (CommentContent), use cases (CRUD, like toggle), SQLAlchemy repository, API v1 endpoints, web UI with comment form and nested replies, i18n translations (EN/RU/FR/DE), and E2E tests. Fix nested reply (reply-to-reply) not displaying — the flat reply_comments dict was only queried for top-level comment IDs, so deeply nested replies were saved to DB (incrementing comment count) but never rendered. Switch to a recursive Jinja2 macro that renders any nesting depth.
This commit is contained in:
79
app/domain/entities/comment.py
Normal file
79
app/domain/entities/comment.py
Normal file
@@ -0,0 +1,79 @@
|
||||
"""Domain entity for Comment.
|
||||
|
||||
This module defines the Comment entity that represents a comment on a blog
|
||||
post. Comments can be top-level (parent_id=None) or replies to other
|
||||
comments (parent_id set).
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
from uuid import UUID
|
||||
|
||||
from app.domain.entities.base import BaseEntity
|
||||
from app.domain.value_objects.comment_content import CommentContent
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class Comment(BaseEntity):
|
||||
"""Comment domain entity.
|
||||
|
||||
Represents a comment on a blog post with optional parent reference
|
||||
for nested replies. Supports Markdown content and like tracking.
|
||||
|
||||
Attributes:
|
||||
post_id: UUID of the post this comment belongs to.
|
||||
author_id: Identifier of the comment author.
|
||||
content: CommentContent value object with Markdown text.
|
||||
parent_id: UUID of parent comment for replies, or None.
|
||||
like_count: Number of likes on this comment.
|
||||
"""
|
||||
|
||||
post_id: UUID
|
||||
author_id: str
|
||||
content: CommentContent
|
||||
parent_id: UUID | None = None
|
||||
like_count: int = 0
|
||||
|
||||
def to_dict(self) -> dict[str, Any]:
|
||||
"""Convert entity to dictionary.
|
||||
|
||||
Returns:
|
||||
Dictionary representation with all comment attributes.
|
||||
"""
|
||||
return {
|
||||
"id": str(self.id),
|
||||
"post_id": str(self.post_id),
|
||||
"author_id": self.author_id,
|
||||
"content": self.content.value,
|
||||
"parent_id": str(self.parent_id) if self.parent_id else None,
|
||||
"like_count": self.like_count,
|
||||
"created_at": self.created_at.isoformat(),
|
||||
"updated_at": self.updated_at.isoformat(),
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def create(
|
||||
cls,
|
||||
post_id: UUID,
|
||||
author_id: str,
|
||||
content_str: str,
|
||||
parent_id: UUID | None = None,
|
||||
) -> "Comment":
|
||||
"""Factory method to create a new comment.
|
||||
|
||||
Args:
|
||||
post_id: UUID of the post to comment on.
|
||||
author_id: Identifier of the comment author.
|
||||
content_str: Comment content string (Markdown supported).
|
||||
parent_id: Optional UUID of parent comment for replies.
|
||||
|
||||
Returns:
|
||||
New Comment instance with validated content.
|
||||
"""
|
||||
content = CommentContent(content_str)
|
||||
return cls(
|
||||
post_id=post_id,
|
||||
author_id=author_id,
|
||||
content=content,
|
||||
parent_id=parent_id,
|
||||
)
|
||||
Reference in New Issue
Block a user