Skip to content

feat: add endpoint to retrieve blogs by author #1213

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions api/v1/routes/blog.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
)

38 changes: 38 additions & 0 deletions api/v1/services/blog.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"""
Expand Down
76 changes: 76 additions & 0 deletions tests/v1/blog/test_get_blog_author_by_id.py
Original file line number Diff line number Diff line change
@@ -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