- PostLike domain entity (post_id, liked_by) with BaseEntity integration
- Post entity: add like_count field (default 0) and to_dict serialization
- PostRepository interface: add get_like, add_like, remove_like methods
- TogglePostLikeUseCase: toggle logic (like → unlike, unlike → like)
- PostResponseDTO/PostResponseSchema: add like_count field
- PostLikeORM model with FK to posts and cascade delete
- SQLAlchemyPostRepository: implement like query/add/remove with ORM mapping
- DI provider registration for TogglePostLikeUseCase
- API endpoint POST /api/v1/posts/{id}/like (auth required)
- Unit tests: PostLike entity, Post.like_count, TogglePostLikeUseCase (7 tests)
- API tests: POST /api/v1/posts/{id}/like (4 tests)
- Test model files: FEATURE_LIKES.md, TEST_MODEL.md updated
157 lines
3.8 KiB
Python
157 lines
3.8 KiB
Python
"""Post repository interface.
|
|
|
|
This module extends the base repository interface with post-specific
|
|
query methods including slug lookup, author filtering, search, and
|
|
like management.
|
|
"""
|
|
|
|
from abc import abstractmethod
|
|
from uuid import UUID
|
|
|
|
from app.domain.entities.like import PostLike
|
|
from app.domain.entities.post import Post
|
|
from app.domain.repositories.base import Repository
|
|
|
|
|
|
class PostRepository(Repository[Post]):
|
|
"""Repository interface for Blog Posts.
|
|
|
|
Extends the generic repository with post-specific operations
|
|
including slug-based lookup, author filtering, tag filtering,
|
|
and full-text search capabilities.
|
|
|
|
Example:
|
|
>>> posts = await repo.get_by_author("user-123", limit=10)
|
|
>>> exists = await repo.slug_exists("my-first-post")
|
|
"""
|
|
|
|
@abstractmethod
|
|
async def get_by_slug(self, slug: str) -> Post | None:
|
|
"""Get post by slug.
|
|
|
|
Args:
|
|
slug: URL-friendly slug identifier.
|
|
|
|
Returns:
|
|
Post instance if found, None otherwise.
|
|
"""
|
|
...
|
|
|
|
@abstractmethod
|
|
async def get_by_author(
|
|
self,
|
|
author_id: str,
|
|
limit: int | None = None,
|
|
offset: int | None = None,
|
|
) -> list[Post]:
|
|
"""Get all posts by author.
|
|
|
|
Args:
|
|
author_id: Identifier of the author.
|
|
limit: Maximum number of posts to return.
|
|
offset: Number of posts to skip.
|
|
|
|
Returns:
|
|
List of posts by the specified author.
|
|
"""
|
|
...
|
|
|
|
@abstractmethod
|
|
async def get_published(
|
|
self,
|
|
limit: int | None = None,
|
|
offset: int | None = None,
|
|
) -> list[Post]:
|
|
"""Get all published posts.
|
|
|
|
Args:
|
|
limit: Maximum number of posts to return.
|
|
offset: Number of posts to skip.
|
|
|
|
Returns:
|
|
List of published posts.
|
|
"""
|
|
...
|
|
|
|
@abstractmethod
|
|
async def get_by_tag(
|
|
self,
|
|
tag: str,
|
|
limit: int | None = None,
|
|
offset: int | None = None,
|
|
) -> list[Post]:
|
|
"""Get posts by tag.
|
|
|
|
Args:
|
|
tag: Tag to filter by.
|
|
limit: Maximum number of posts to return.
|
|
offset: Number of posts to skip.
|
|
|
|
Returns:
|
|
List of posts with the specified tag.
|
|
"""
|
|
...
|
|
|
|
@abstractmethod
|
|
async def slug_exists(self, slug: str) -> bool:
|
|
"""Check if slug already exists.
|
|
|
|
Args:
|
|
slug: Slug to check for existence.
|
|
|
|
Returns:
|
|
True if slug exists, False otherwise.
|
|
"""
|
|
...
|
|
|
|
@abstractmethod
|
|
async def get_like(self, post_id: UUID, liked_by: str) -> PostLike | None:
|
|
"""Get a like by post and user/device.
|
|
|
|
Args:
|
|
post_id: UUID of the post.
|
|
liked_by: User ID or device ID.
|
|
|
|
Returns:
|
|
PostLike if found, None otherwise.
|
|
"""
|
|
...
|
|
|
|
@abstractmethod
|
|
async def add_like(self, like: PostLike) -> None:
|
|
"""Add a new like.
|
|
|
|
Args:
|
|
like: PostLike entity to add.
|
|
"""
|
|
...
|
|
|
|
@abstractmethod
|
|
async def remove_like(self, post_id: UUID, liked_by: str) -> None:
|
|
"""Remove a like by post and user/device.
|
|
|
|
Args:
|
|
post_id: UUID of the post.
|
|
liked_by: User ID or device ID.
|
|
"""
|
|
...
|
|
|
|
@abstractmethod
|
|
async def search(
|
|
self,
|
|
query: str,
|
|
limit: int | None = None,
|
|
offset: int | None = None,
|
|
) -> list[Post]:
|
|
"""Search posts by query string.
|
|
|
|
Args:
|
|
query: Search query string.
|
|
limit: Maximum number of posts to return.
|
|
offset: Number of posts to skip.
|
|
|
|
Returns:
|
|
List of posts matching the search query.
|
|
"""
|
|
...
|