Skip to content

Commit 1b6fbf0

Browse files
Delete like endpoint
- Complete like endpoint - Store encripted email
1 parent 798336d commit 1b6fbf0

4 files changed

Lines changed: 170 additions & 21 deletions

File tree

app/routers/news/routes.py

Lines changed: 48 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@
77
from app.routers.authentication import get_current_active_community
88
from app.schemas import News
99
from app.services.database.models import Community as DBCommunity
10-
from app.services.database.orm.news import (
11-
create_news,
12-
get_news_by_query_params,
13-
like_news,
14-
)
10+
import app.services.database.orm.news as orm_news
11+
12+
import os
13+
import jwt
14+
15+
SECRET_KEY = os.getenv("SECRET_KEY", "default_fallback_key")
16+
ALGORITHM = os.getenv("ALGORITHM", "HS256")
1517

1618

1719
class NewsPostResponse(BaseModel):
@@ -26,6 +28,12 @@ class NewsGetResponse(BaseModel):
2628
class NewsLikeResponse(BaseModel):
2729
total_likes: int | None
2830

31+
class LikeRequest(BaseModel):
32+
email: str
33+
34+
def encode_email(email: str) -> str:
35+
"""Encodes the email to be safely stored in database."""
36+
return jwt.encode({"email": email}, SECRET_KEY, algorithm=ALGORITHM)
2937

3038
def setup():
3139
router = APIRouter(prefix="/news", tags=["news"])
@@ -50,7 +58,7 @@ async def post_news(
5058
"""
5159
news_dict = news.__dict__
5260
news_dict["user_email"] = user_email
53-
await create_news(
61+
await orm_news.create_news(
5462
session=request.app.db_session_factory, news=news_dict
5563
)
5664
return NewsPostResponse()
@@ -75,7 +83,7 @@ async def get_news(
7583
"""
7684
Get News endpoint that retrieves news filtered by user and query params.
7785
"""
78-
news_list = await get_news_by_query_params(
86+
news_list = await orm_news.get_news_by_query_params(
7987
session=request.app.db_session_factory,
8088
id=id,
8189
email=user_email,
@@ -86,7 +94,7 @@ async def get_news(
8694

8795
@router.post(
8896
path="/{news_id}/like",
89-
response_model=NewsPostResponse,
97+
response_model=NewsLikeResponse,
9098
status_code=status.HTTP_200_OK,
9199
summary="News like endpoint",
92100
description="Allows user to like a news item",
@@ -96,16 +104,45 @@ async def post_like(
96104
current_community: Annotated[
97105
DBCommunity, Depends(get_current_active_community)
98106
],
99-
news_id,
107+
news_id: str,
108+
body: LikeRequest,
109+
user_email: str = Header(..., alias="user-email"),
110+
):
111+
"""
112+
News endpoint where user can set like to news item.
113+
"""
114+
encoded_email = encode_email(body.email)
115+
total_likes = await orm_news.like_news(
116+
session=request.app.db_session_factory,
117+
news_id=news_id,
118+
email=encoded_email,
119+
)
120+
return NewsLikeResponse(total_likes=total_likes)
121+
122+
@router.delete(
123+
path="/{news_id}/like",
124+
response_model=NewsLikeResponse,
125+
status_code=status.HTTP_200_OK,
126+
summary="News undo like endpoint",
127+
description="Allows user to undo a like to a news item",
128+
)
129+
async def delete_like(
130+
request: Request,
131+
current_community: Annotated[
132+
DBCommunity, Depends(get_current_active_community)
133+
],
134+
news_id: str,
135+
email: str,
100136
user_email: str = Header(..., alias="user-email"),
101137
):
102138
"""
103139
News endpoint where user can set like to news item.
104140
"""
105-
total_likes = await like_news(
141+
encoded_email = encode_email(email)
142+
total_likes = await orm_news.delete_like(
106143
session=request.app.db_session_factory,
107144
news_id=news_id,
108-
user_email=user_email,
145+
email=encoded_email,
109146
)
110147
return NewsLikeResponse(total_likes=total_likes)
111148

app/services/database/models/news.py

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,37 @@
55

66

77
class News(SQLModel, table=True):
8+
"""
9+
Represents a news article in the database.
10+
11+
Attributes:
12+
id (Optional[int]): Unique identifier for the news article.
13+
Auto-generated primary key.
14+
title (str): The headline or title of the news article.
15+
content (str): The main body or text content of the news.
16+
category (str): Category or topic classification
17+
(e.g., "Politics", "Tech").
18+
user_email (str): Email of the user who submitted or is associated
19+
with this news.
20+
source_url (str): URL pointing to the original source of the news.
21+
tags (str): Comma-separated or JSON-encoded string of tags for
22+
search/filtering.
23+
user_email_list (str): encoded list of emails of users who liked
24+
this news. Defaults to an empty list.
25+
social_media_url (str): URL to the social media post or share link
26+
for this news.
27+
likes (int): Number of likes this news article has received.
28+
Defaults to 0.
29+
30+
community_id (Optional[int]): Foreign key to the associated community
31+
(communities.id).
32+
33+
created_at (Optional[datetime]): Timestamp when the news was first
34+
created. Defaults to now.
35+
updated_at (Optional[datetime]): Timestamp when the news was last
36+
updated. Auto-updates on modification.
37+
"""
38+
839
__tablename__ = "news"
940

1041
# Campos obrigatórios e suas definições
@@ -15,18 +46,19 @@ class News(SQLModel, table=True):
1546
user_email: str
1647
source_url: str
1748
tags: str
49+
user_email_list: str = Field(default="[]")
1850
social_media_url: str
1951
likes: int = Field(default=0)
2052

2153
# Chaves estrangeiras
2254
community_id: Optional[int] = Field(
23-
default=None,
24-
foreign_key="communities.id")
55+
default=None, foreign_key="communities.id"
56+
)
2557
# library_id: Optional[int]=Field(default=None, foreign_key="libraries.id")
2658

2759
# Campos de data/hora
2860
created_at: Optional[datetime] = Field(default_factory=datetime.now)
2961
updated_at: Optional[datetime] = Field(
3062
default_factory=datetime.now,
31-
sa_column_kwargs={"onupdate": datetime.now}
63+
sa_column_kwargs={"onupdate": datetime.now},
3264
)

app/services/database/orm/news.py

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import ast
12
from typing import Optional
23

34
from sqlmodel import select
@@ -45,15 +46,38 @@ async def get_news_by_query_params(
4546

4647

4748
async def like_news(
48-
session: AsyncSession, news_id: str, user_email: str
49+
session: AsyncSession, news_id: str, email: str
4950
) -> int | None:
5051
statement = select(News).where(News.id == news_id)
5152
results = await session.exec(statement)
5253
news_item = results.first()
5354
if news_item:
54-
news_item.likes += 1
55-
session.add(news_item)
56-
await session.commit()
57-
await session.refresh(news_item)
58-
return news_item.likes
55+
users = ast.literal_eval(news_item.user_email_list)
56+
if email not in users:
57+
users.append(email)
58+
news_item.user_email_list = str(users)
59+
news_item.likes += 1
60+
session.add(news_item)
61+
await session.commit()
62+
await session.refresh(news_item)
63+
return news_item.likes
64+
return None
65+
66+
67+
async def delete_like(
68+
session: AsyncSession, news_id: str, email: str
69+
) -> int | None:
70+
statement = select(News).where(News.id == news_id)
71+
results = await session.exec(statement)
72+
news_item = results.first()
73+
if news_item:
74+
users = ast.literal_eval(news_item.user_email_list)
75+
if email in users:
76+
users.remove(email)
77+
news_item.user_email_list = str(users)
78+
news_item.likes -= 1
79+
session.add(news_item)
80+
await session.commit()
81+
await session.refresh(news_item)
82+
return news_item.likes
5983
return None

tests/test_news.py

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from sqlmodel import select
99
from sqlmodel.ext.asyncio.session import AsyncSession
1010

11+
from app.routers.news.routes import encode_email
1112
from app.services.database.models import Community, News
1213

1314

@@ -311,13 +312,68 @@ async def test_insert_news_likes_endpoint(
311312
assert stored_news is not None
312313
assert stored_news.likes == 0
313314

315+
email = "like@test.com"
316+
317+
response = await async_client.post(
318+
f"/api/news/{stored_news.id}/like",
319+
json={"email": email},
320+
headers=valid_auth_headers,
321+
)
322+
assert response.status_code == status.HTTP_200_OK
323+
statement = select(News).where(News.title == news_data["title"])
324+
result = await session.exec(statement)
325+
stored_news = result.first()
326+
assert stored_news.likes == 1
327+
assert stored_news.user_email_list == f"['{encode_email(email)}']"
328+
329+
330+
@pytest.mark.asyncio
331+
async def test_delete_news_likes_endpoint(
332+
session: AsyncSession,
333+
async_client: AsyncClient,
334+
community: Community,
335+
valid_auth_headers: Mapping[str, str],
336+
):
337+
news_data = {
338+
"title": "Test News",
339+
"content": "Test news content.",
340+
"category": "test_category",
341+
"tags": "test_tag",
342+
"source_url": "https://example.com/test-news",
343+
"social_media_url": "https://test.com/test_news",
344+
}
345+
response = await async_client.post(
346+
"/api/news", json=news_data, headers=valid_auth_headers
347+
)
348+
assert response.status_code == status.HTTP_200_OK
349+
statement = select(News).where(News.title == news_data["title"])
350+
result = await session.exec(statement)
351+
stored_news = result.first()
352+
assert stored_news is not None
353+
assert stored_news.likes == 0
354+
355+
email = "like@test.com"
356+
314357
response = await async_client.post(
315358
f"/api/news/{stored_news.id}/like",
316-
json=news_data,
359+
json={"email": email},
317360
headers=valid_auth_headers,
318361
)
319362
assert response.status_code == status.HTTP_200_OK
320363
statement = select(News).where(News.title == news_data["title"])
321364
result = await session.exec(statement)
322365
stored_news = result.first()
323366
assert stored_news.likes == 1
367+
assert stored_news.user_email_list == f"['{encode_email(email)}']"
368+
369+
response = await async_client.delete(
370+
f"/api/news/{stored_news.id}/like",
371+
params={"email": email},
372+
headers=valid_auth_headers,
373+
)
374+
assert response.status_code == status.HTTP_200_OK
375+
statement = select(News).where(News.title == news_data["title"])
376+
result = await session.exec(statement)
377+
stored_news = result.first()
378+
assert stored_news.likes == 0
379+
assert stored_news.user_email_list == "[]"

0 commit comments

Comments
 (0)