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:
@@ -10,19 +10,24 @@ from dishka import Provider, Scope, provide
|
||||
from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession
|
||||
|
||||
from app.application import (
|
||||
CreateCommentUseCase,
|
||||
CreatePostUseCase,
|
||||
DeleteCommentUseCase,
|
||||
DeletePostUseCase,
|
||||
GetPostUseCase,
|
||||
ListCommentsUseCase,
|
||||
ListPostsUseCase,
|
||||
PublishPostUseCase,
|
||||
ToggleCommentLikeUseCase,
|
||||
TogglePostLikeUseCase,
|
||||
UpdatePostUseCase,
|
||||
)
|
||||
from app.application.interfaces import TransactionManager
|
||||
from app.domain.repositories import PostRepository
|
||||
from app.domain.repositories import CommentRepository, PostRepository
|
||||
from app.infrastructure.auth import KeycloakAuthClient, MockKeycloakClient
|
||||
from app.infrastructure.config.settings import settings
|
||||
from app.infrastructure.database.connection import AsyncSessionLocal, engine
|
||||
from app.infrastructure.repositories.comment import SQLAlchemyCommentRepository
|
||||
from app.infrastructure.repositories.post import SQLAlchemyPostRepository
|
||||
|
||||
|
||||
@@ -81,6 +86,18 @@ class RepositoryProvider(Provider):
|
||||
"""
|
||||
return SQLAlchemyPostRepository(session)
|
||||
|
||||
@provide(scope=Scope.REQUEST)
|
||||
def get_comment_repository(self, session: AsyncSession) -> CommentRepository:
|
||||
"""Provide CommentRepository implementation.
|
||||
|
||||
Args:
|
||||
session: Database session from DI container.
|
||||
|
||||
Returns:
|
||||
SQLAlchemyCommentRepository instance.
|
||||
"""
|
||||
return SQLAlchemyCommentRepository(session)
|
||||
|
||||
|
||||
class TransactionManagerProvider(Provider):
|
||||
"""Provider for transaction manager.
|
||||
@@ -257,6 +274,86 @@ class UseCaseProvider(Provider):
|
||||
tx_manager=tx_manager,
|
||||
)
|
||||
|
||||
@provide(scope=Scope.REQUEST)
|
||||
def get_create_comment_use_case(
|
||||
self,
|
||||
post_repo: PostRepository,
|
||||
comment_repo: CommentRepository,
|
||||
tx_manager: TransactionManager,
|
||||
) -> CreateCommentUseCase:
|
||||
"""Provide CreateCommentUseCase.
|
||||
|
||||
Args:
|
||||
post_repo: Post repository dependency.
|
||||
comment_repo: Comment repository dependency.
|
||||
tx_manager: Transaction manager dependency.
|
||||
|
||||
Returns:
|
||||
Configured CreateCommentUseCase instance.
|
||||
"""
|
||||
return CreateCommentUseCase(
|
||||
post_repo=post_repo,
|
||||
comment_repo=comment_repo,
|
||||
tx_manager=tx_manager,
|
||||
)
|
||||
|
||||
@provide(scope=Scope.REQUEST)
|
||||
def get_list_comments_use_case(
|
||||
self,
|
||||
comment_repo: CommentRepository,
|
||||
) -> ListCommentsUseCase:
|
||||
"""Provide ListCommentsUseCase.
|
||||
|
||||
Args:
|
||||
comment_repo: Comment repository dependency.
|
||||
|
||||
Returns:
|
||||
Configured ListCommentsUseCase instance.
|
||||
"""
|
||||
return ListCommentsUseCase(
|
||||
comment_repo=comment_repo,
|
||||
)
|
||||
|
||||
@provide(scope=Scope.REQUEST)
|
||||
def get_delete_comment_use_case(
|
||||
self,
|
||||
comment_repo: CommentRepository,
|
||||
tx_manager: TransactionManager,
|
||||
) -> DeleteCommentUseCase:
|
||||
"""Provide DeleteCommentUseCase.
|
||||
|
||||
Args:
|
||||
comment_repo: Comment repository dependency.
|
||||
tx_manager: Transaction manager dependency.
|
||||
|
||||
Returns:
|
||||
Configured DeleteCommentUseCase instance.
|
||||
"""
|
||||
return DeleteCommentUseCase(
|
||||
comment_repo=comment_repo,
|
||||
tx_manager=tx_manager,
|
||||
)
|
||||
|
||||
@provide(scope=Scope.REQUEST)
|
||||
def get_toggle_comment_like_use_case(
|
||||
self,
|
||||
comment_repo: CommentRepository,
|
||||
tx_manager: TransactionManager,
|
||||
) -> ToggleCommentLikeUseCase:
|
||||
"""Provide ToggleCommentLikeUseCase.
|
||||
|
||||
Args:
|
||||
comment_repo: Comment repository dependency.
|
||||
tx_manager: Transaction manager dependency.
|
||||
|
||||
Returns:
|
||||
Configured ToggleCommentLikeUseCase instance.
|
||||
"""
|
||||
return ToggleCommentLikeUseCase(
|
||||
comment_repo=comment_repo,
|
||||
tx_manager=tx_manager,
|
||||
)
|
||||
|
||||
|
||||
class KeycloakProvider(Provider):
|
||||
"""Provider for Keycloak authentication client.
|
||||
|
||||
Reference in New Issue
Block a user