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:
80
app/domain/repositories/comment.py
Normal file
80
app/domain/repositories/comment.py
Normal file
@@ -0,0 +1,80 @@
|
||||
"""Comment repository interface.
|
||||
|
||||
This module defines the repository interface for Comment entities
|
||||
including nested comment queries and like management.
|
||||
"""
|
||||
|
||||
from abc import abstractmethod
|
||||
from uuid import UUID
|
||||
|
||||
from app.domain.entities.comment import Comment
|
||||
from app.domain.entities.comment_like import CommentLike
|
||||
from app.domain.repositories.base import Repository
|
||||
|
||||
|
||||
class CommentRepository(Repository[Comment]):
|
||||
"""Repository interface for Comments.
|
||||
|
||||
Extends the generic repository with comment-specific operations
|
||||
including post-based listing and like management.
|
||||
|
||||
Example:
|
||||
>>> comments = await repo.get_by_post(post_id)
|
||||
>>> like = await repo.get_like(comment_id, "user-123")
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
async def get_by_post(self, post_id: UUID) -> list[Comment]:
|
||||
"""Get all comments for a post, ordered by creation time.
|
||||
|
||||
Args:
|
||||
post_id: UUID of the post.
|
||||
|
||||
Returns:
|
||||
List of Comment entities for the post.
|
||||
"""
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
async def get_like(self, comment_id: UUID, liked_by: str) -> CommentLike | None:
|
||||
"""Get a like by comment and user.
|
||||
|
||||
Args:
|
||||
comment_id: UUID of the comment.
|
||||
liked_by: User ID.
|
||||
|
||||
Returns:
|
||||
CommentLike if found, None otherwise.
|
||||
"""
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
async def add_like(self, like: CommentLike) -> None:
|
||||
"""Add a new like to a comment.
|
||||
|
||||
Args:
|
||||
like: CommentLike entity to add.
|
||||
"""
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
async def count_by_post(self, post_id: UUID) -> int:
|
||||
"""Get comment count for a post.
|
||||
|
||||
Args:
|
||||
post_id: UUID of the post.
|
||||
|
||||
Returns:
|
||||
Number of comments on the post.
|
||||
"""
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
async def remove_like(self, comment_id: UUID, liked_by: str) -> None:
|
||||
"""Remove a like from a comment by user.
|
||||
|
||||
Args:
|
||||
comment_id: UUID of the comment.
|
||||
liked_by: User ID.
|
||||
"""
|
||||
...
|
||||
Reference in New Issue
Block a user