diff --git a/api/v1/routes/blog.py b/api/v1/routes/blog.py index ceff01d68..4d37d99c0 100644 --- a/api/v1/routes/blog.py +++ b/api/v1/routes/blog.py @@ -427,3 +427,43 @@ def delete_blog_dislike( # delete blog dislike return blog_dislike_service.delete(blog_dislike_id, current_user.id) + +@blog.get("/author/{author_id}", response_model=success_response) +def get_blogs_by_author( + author_id: str, + db: Session = Depends(get_db), + limit: int = 10, + skip: int = 0 +): + """ + Retrieve all blog posts authored by a specific user. + + Args: + author_id (str): The ID of the author. + db (Session): The database session. + limit (int, optional): Maximum number of blog posts to return. Defaults to 10. + skip (int, optional): Number of blog posts to skip. Defaults to 0. + + Returns: + success_response: The paginated list of the author's blog posts. + """ + blog_service = BlogService(db) + + # Get all blogs by the author + blogs = blog_service.get_blogs_by_author(author_id, limit, skip) + + # Get total count for pagination + total_count = blog_service.count_author_blogs(author_id) + + return success_response( + message="Author's blog posts retrieved successfully!", + status_code=200, + data={ + "items": jsonable_encoder(blogs), + "total": total_count, + "page": skip // limit + 1 if limit > 0 else 1, + "size": limit, + "pages": (total_count + limit - 1) // limit if limit > 0 else 1 + } + ) + diff --git a/api/v1/services/blog.py b/api/v1/services/blog.py index bb117f80f..f8b76e9ea 100644 --- a/api/v1/services/blog.py +++ b/api/v1/services/blog.py @@ -284,8 +284,46 @@ def update_blog_comment( ) return comment + def get_blogs_by_author(self, author_id: str, limit: int = 10, skip: int = 0): + """ + Retrieve all blog posts by a specific author. + + Args: + author_id (str): The ID of the author. + limit (int, optional): Maximum number of blog posts to return. Defaults to 10. + skip (int, optional): Number of blog posts to skip. Defaults to 0. + + Returns: + list: List of blog posts authored by the specified user. + """ + blogs = ( + self.db.query(Blog) + .filter(Blog.author_id == author_id, Blog.is_deleted == False) + .order_by(Blog.created_at.desc()) + .offset(skip) + .limit(limit) + .all() + ) + + return blogs + def count_author_blogs(self, author_id: str): + """ + Count the number of blog posts authored by a specific user. + + Args: + author_id (str): The ID of the author. + + Returns: + int: The number of blog posts authored by the specified user. + """ + return ( + self.db.query(Blog) + .filter(Blog.author_id == author_id, Blog.is_deleted == False) + .count() + ) + #BlogLikeService and BlogDislikeService inherits from baseclass BaseBlogInteractionService class BlogLikeService(BaseBlogInteractionService[BlogLike]): """BlogLike service functionality""" diff --git a/tests/v1/blog/test_get_blog_author_by_id.py b/tests/v1/blog/test_get_blog_author_by_id.py new file mode 100644 index 000000000..1b7f24650 --- /dev/null +++ b/tests/v1/blog/test_get_blog_author_by_id.py @@ -0,0 +1,76 @@ +import pytest +import uuid +from fastapi.testclient import TestClient +from sqlalchemy.orm import Session +from unittest.mock import MagicMock +from datetime import datetime, timezone, timedelta + +from main import app +from api.v1.routes.blog import get_db + + +@pytest.fixture +def db_session_mock(): + """Fixture to create a mock database session.""" + return MagicMock(spec=Session) + + +@pytest.fixture +def client(db_session_mock): + """Fixture to override FastAPI dependency injection for testing.""" + app.dependency_overrides[get_db] = lambda: db_session_mock + client = TestClient(app) + try: + yield client + finally: + app.dependency_overrides = {} + + +def create_mock_blog(blog_id: uuid.UUID, author_id: uuid.UUID, title: str): + """Helper function to create a mock blog object with UUIDs.""" + timezone_offset = -8.0 + tzinfo = timezone(timedelta(hours=timezone_offset)) + timeinfo = datetime.now(tzinfo) + return { + "id": str(blog_id), + "author_id": str(author_id), + "title": title, + "content": "Sample content", + "image_url": "http://example.com/image.png", + "tags": "test,blog", + "is_deleted": False, + "excerpt": "Test Excerpt", + "created_at": timeinfo.isoformat(), + "updated_at": timeinfo.isoformat() + } + + +def test_get_blogs_by_author(client, db_session_mock): + """Test retrieving blogs by an author using UUIDs.""" + author_id = uuid.uuid4() + mock_blogs = [ + create_mock_blog(uuid.uuid4(), author_id, "Blog 1"), + create_mock_blog(uuid.uuid4(), author_id, "Blog 2"), + ] + + db_session_mock.query().filter().order_by().offset().limit().all.return_value = mock_blogs + + response = client.get(f"/api/v1/blogs/author/{author_id}") + + assert response.status_code == 200 + assert len(response.json()["data"]["items"]) == 2 # Fixed assertion + assert response.json()["data"]["items"][0]["title"] == "Blog 1" + + +def test_get_blogs_by_author_empty(client, db_session_mock): + """Test retrieving blogs by an author when no blogs exist.""" + author_id = uuid.uuid4() + + db_session_mock.query().filter().order_by().offset().limit().all.return_value = [] + + response = client.get(f"/api/v1/blogs/author/{author_id}") + + assert response.status_code == 200 + assert response.json()["data"]["items"] == [] # Fixed assertion + +