Skip to content
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

Feature retrieves total likes dislikes by author #1228

Draft
wants to merge 7 commits into
base: dev
Choose a base branch
from
28 changes: 28 additions & 0 deletions api/v1/routes/blog.py
Original file line number Diff line number Diff line change
Expand Up @@ -541,3 +541,31 @@ def delete_blog_dislike(

# delete blog dislike
return blog_dislike_service.delete(blog_dislike_id, current_user.id)

@blog.get("/authors/{author_id}/likes-dislikes", tags=["Fetch Likes and Dislikes"])
def get_likes_dislikes_for_author(
author_id: str, # This is the ID of the author
db: Session = Depends(get_db), # This connects to our database
current_user: User = Depends(user_service.get_current_user), # This checks who is asking
):
# Call the service to get the likes and dislikes
blog_service = BlogService(db)
total_likes, total_dislikes = blog_service.get_likes_dislikes_by_author(db, author_id)

# If we don't find the author, return an error
if total_likes is None or total_dislikes is None:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Author not found",
)

# Return the total likes and dislikes
return success_response(
status_code=status.HTTP_200_OK,
message="Likes and dislikes for the author retrieved successfully",
data={
"author_id": author_id,
"total_likes": total_likes,
"total_dislikes": total_dislikes
}
)
37 changes: 35 additions & 2 deletions api/v1/services/blog.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

from fastapi import HTTPException, status
from sqlalchemy.orm import Session
from sqlalchemy import func
from sqlalchemy import and_


from api.core.base.services import Service
from api.utils.db_validators import check_model_existence
from api.v1.models.blog import Blog, BlogDislike, BlogLike
Expand All @@ -17,7 +19,7 @@ class BaseBlogInteractionService(Generic[ModelType]):
"""Base service for blog interactions (likes/dislikes)"""

def __init__(self, db: Session, model: type[ModelType]):
self.db = db
super().__init__(db, BlogDislike)
self.model = model

def fetch(self, item_id: str) -> ModelType:
Expand Down Expand Up @@ -246,6 +248,16 @@ def delete_opposite_blog_like_or_dislike(self, blog: Blog, user: User, creating:
status_code=status.HTTP_400_BAD_REQUEST,
detail="Invalid `creating` value for blog like/dislike"
)

# This function counts the likes and dislikes
def get_likes_dislikes_by_author(self, db: Session, author_id: str):
total_likes = db.query(func.count(BlogLike.id)).join(Blog).filter(Blog.author_id == author_id).scalar()
total_dislikes = db.query(func.count(BlogDislike.id)).join(Blog).filter(Blog.author_id == author_id).scalar()

if total_likes is None or total_dislikes is None:
return None, None

return total_likes, total_dislikes

def num_of_likes(self, blog_id: str) -> int:
"""Get the number of likes a blog post has"""
Expand Down Expand Up @@ -365,4 +377,25 @@ class BlogDislikeService(BaseBlogInteractionService[BlogDislike]):
"""BlogDislike service functionality"""

def __init__(self, db: Session):
super().__init__(db, BlogDislike)
self.db = db

def fetch(self, blog_dislike_id: str):
"""Fetch a blog dislike by its ID"""
return check_model_existence(self.db, BlogDislike, blog_dislike_id)

def delete(self, blog_dislike_id: str, user_id: str):
"""Delete blog dislike"""
blog_dislike = self.fetch(blog_dislike_id)

# check that current user owns the blog like
if blog_dislike.user_id != user_id:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Insufficient permission"
)

self.db.delete(blog_dislike)
self.db.commit()

blog_service = BlogService(db=None)
super().__init__(db, BlogDislike)
3 changes: 3 additions & 0 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from starlette.requests import Request
from starlette.middleware.sessions import SessionMiddleware # required by google oauth

from api.v1.routes.blog import blog
from api.utils.json_response import JsonResponseDict
from api.utils.logger import logger
from api.v1.routes import api_version_one
Expand All @@ -38,6 +39,8 @@ async def lifespan(app: FastAPI):
version="1.0.0",
)

# Include the blog routes
app.include_router(blog)

# Initialize the rate limiter
limiter = Limiter(key_func=get_remote_address)
Expand Down
22 changes: 13 additions & 9 deletions tests/v1/blog/test_delete_blog_like.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from api.db.database import get_db
from datetime import datetime, timezone
from api.v1.models import User, BlogLike
from api.v1.services.blog import BlogLikeService
from fastapi.testclient import TestClient
from unittest.mock import patch, MagicMock
from api.v1.services.user import user_service
Expand All @@ -26,9 +27,10 @@ def mock_user_service():


@pytest.fixture
def mock_blog_service():
with patch("api.v1.services.blog.BlogService", autospec=True) as blog_service_mock:
yield blog_service_mock
def mock_blog_service(mocker):
mock_db = mocker.MagicMock(spec=Session) # Mock DB session
blog_service_mock = mocker.MagicMock(spec=BlogLikeService)
return blog_service_mock


# Test User
Expand Down Expand Up @@ -83,22 +85,23 @@ def make_request(blog_like_id, token):


# test for successful delete
@patch("api.v1.services.blog.BlogLikeService.fetch")
def test_successful_delete_bloglike(
mock_fetch_blog_like,
mock_blog_service, # Use fixture instead of patching
mock_db_session,
test_user,
test_blog_like,
access_token_user
):
# mock current-user AND blog-like
# Mock behavior
mock_blog_service.fetch.return_value = test_blog_like
mock_db_session.query().filter().first.return_value = test_user
mock_fetch_blog_like.return_value = test_blog_like

resp = make_request(test_blog_like.id, access_token_user)

assert resp.status_code == 204



# Test for wrong blog like id
def test_wrong_blog_like_id(
# mock_fetch_blog_like,
Expand Down Expand Up @@ -130,15 +133,16 @@ def test_wrong_auth_token(

# Test for wrong owner request
def test_wrong_owner_request(
mock_user_service,
mock_db_session,
test_blog_like,
another_user,
access_token_another
):
mock_user_service.get_current_user = another_user
mock_user_service.get_current_user.return_value = another_user
mock_db_session.get.return_value = test_blog_like

### TEST ATTEMPT BY NON OWNER ###
resp = make_request(test_blog_like.id, access_token_another)

assert resp.status_code == 401
assert resp.json()['message'] == 'Insufficient permission'
Loading