Skip to content
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
3 changes: 2 additions & 1 deletion app/routers/libraries/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
)
from app.services.database.orm.library_request import insert_library_request
from app.services.database.orm.subscription import upsert_multiple_subscription
from app.services.encryption import encrypt_email
from app.services.limiter import limiter


Expand Down Expand Up @@ -145,7 +146,7 @@ async def subscribe_libraries(

subscriptions = [
Subscription(
user_email=user_email,
user_email=encrypt_email(user_email),
tags=body.tags,
library_id=id,
community_id=current_community.id,
Expand Down
49 changes: 40 additions & 9 deletions app/routers/news/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@

import app.services.database.orm.news as orm_news
from app.routers.authentication import get_current_active_community
from app.schemas import News
from app.schemas import News, NewsWithPublishStatus
from app.services.database.models import Community as DBCommunity
from app.services.limiter import limiter

SECRET_KEY = os.getenv("SECRET_KEY", "default_fallback_key")
ALGORITHM = os.getenv("ALGORITHM", "HS256")


class NewsPostResponse(BaseModel):
class NewsCreateResponse(BaseModel):
status: str = "News Criada"


Expand All @@ -25,6 +25,10 @@ class NewsGetResponse(BaseModel):
news_list: list = []


class NewsUpdateResponse(BaseModel):
status: str = "News Atualizada"


class NewsLikeResponse(BaseModel):
total_likes: int | None

Expand All @@ -35,11 +39,11 @@ def encode_email(email: str) -> str:


def setup():
router = APIRouter(prefix="/news", tags=["news"])
router: APIRouter = APIRouter(prefix="/news", tags=["news"])

@router.post(
"",
response_model=NewsPostResponse,
path="",
response_model=NewsCreateResponse,
status_code=status.HTTP_200_OK,
summary="News endpoint",
description="Creates news and returns a confirmation message",
Expand All @@ -61,7 +65,7 @@ async def post_news(
await orm_news.create_news(
session=request.app.db_session_factory, news=news_dict
)
return NewsPostResponse()
return NewsCreateResponse()

@router.get(
"",
Expand Down Expand Up @@ -93,18 +97,45 @@ async def get_news(
)
return NewsGetResponse(news_list=news_list)

@router.put(
path="/{news_id}",
status_code=status.HTTP_200_OK,
summary="PUT News",
description="Updates news and sets publish value",
)
@limiter.limit(limit_value="60/minute")
async def put_news(
request: Request,
current_community: Annotated[
DBCommunity, Depends(get_current_active_community)
],
news_id: str,
news: NewsWithPublishStatus,
user_email: str = Header(..., alias="user-email"),
):
"""
Get News endpoint that retrieves news filtered by user and query params.
"""
await orm_news.update_news(
session=request.app.db_session_factory,
news=news.__dict__,
news_id=news_id,
user_email=user_email,
)
return NewsUpdateResponse()

@router.post(
path="/{news_id}/like",
response_model=NewsLikeResponse,
status_code=status.HTTP_200_OK,
summary="News like endpoint",
description="Allows user to like a news item",
)
@limiter.limit("60/minute")
@limiter.limit(limit_value="60/minute")
async def post_like(
request: Request,
current_community: Annotated[
DBCommunity, Depends(get_current_active_community)
DBCommunity, Depends(dependency=get_current_active_community)
],
news_id: str,
user_email: str = Header(..., alias="user-email"),
Expand All @@ -127,7 +158,7 @@ async def post_like(
summary="News undo like endpoint",
description="Allows user to undo a like to a news item",
)
@limiter.limit("60/minute")
@limiter.limit(limit_value="60/minute")
async def delete_like(
request: Request,
current_community: Annotated[
Expand Down
7 changes: 5 additions & 2 deletions app/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,13 @@ class News(BaseModel):
title: str
content: str
category: str
tags: str | None = None
source_url: str
tags: str | None = None
social_media_url: str | None = None
likes: int = 0


class NewsWithPublishStatus(News):
publish: bool = False


class Token(BaseModel):
Expand Down
3 changes: 3 additions & 0 deletions app/services/database/models/news.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ class News(SQLModel, table=True):
for this news.
likes (int): Number of likes this news article has received.
Defaults to 0.
publish (bool): Indicates whether the news is published or not.
Defaults to False.

community_id (Optional[int]): Foreign key to the associated community
(communities.id).
Expand All @@ -49,6 +51,7 @@ class News(SQLModel, table=True):
user_email_list: str = Field(default="[]")
social_media_url: str
likes: int = Field(default=0)
publish: bool = Field(default=False)

# Chaves estrangeiras
community_id: Optional[int] = Field(
Expand Down
23 changes: 21 additions & 2 deletions app/services/database/orm/news.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ async def create_news(session: AsyncSession, news: dict) -> None:
source_url=news["source_url"],
tags=news["tags"] or "",
social_media_url=news["social_media_url"] or "",
likes=news["likes"],
)
session.add(_news)
await session.commit()
Expand All @@ -42,7 +41,27 @@ async def get_news_by_query_params(

statement = select(News).where(*filters)
results = await session.exec(statement)
return results.all()
return list(results.all())


async def update_news(
session: AsyncSession,
news: dict,
news_id: str,
user_email: str,
) -> None:
statement = select(News).where(
News.id == news_id and News.user_email == user_email
)
results = await session.exec(statement)
news_item = results.first()
if news_item:
for key, value in news.items():
if key != "id" and value is not None:
setattr(news_item, key, value)
session.add(news_item)
await session.commit()
await session.refresh(news_item)


async def like_news(
Expand Down
2 changes: 1 addition & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
# --- Configurações do Banco de Dados em Memória para Testes ---
# Usamos engine e AsyncSessionLocal apenas para os testes.
# Isso garante que os testes são isolados e usam o banco de dados em memória.
TEST_DATABASE_URL = "sqlite+aiosqlite:///:memory:"
TEST_DATABASE_URL = "sqlite+aiosqlite:////tmp/pynewsdb.db"
os.environ["ADMIN_USER"] = "ADMIN_USER"
os.environ["ADMIN_PASSWORD"] = "ADMIN_PASSWORD"
os.environ["ADMIN_EMAIL"] = "ADMIN_EMAIL"
Expand Down
4 changes: 2 additions & 2 deletions tests/test_healthcheck.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ async def test_healthcheck_endpoint(
)

assert response.status_code == status.HTTP_200_OK
assert response.json() == {"status": "healthy", "version": "2.0.0"}
assert response.json()["version"] == "2.0.0"


@pytest.mark.asyncio
Expand All @@ -28,4 +28,4 @@ async def test_healthcheck_endpoint_without_auth(async_client: AsyncClient):
response = await async_client.get("/api/healthcheck")

assert response.status_code == status.HTTP_200_OK
assert response.json() == {"status": "healthy", "version": "2.0.0"}
assert response.json()["version"] == "2.0.0"
3 changes: 2 additions & 1 deletion tests/test_libraries_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from sqlmodel.ext.asyncio.session import AsyncSession

from app.services.database.models import Community, LibraryRequest
from tests.conftest import CommunityCredentials


@pytest.mark.asyncio
Expand Down Expand Up @@ -56,5 +57,5 @@ async def test_post_libraries_endpoint(
created_request = result.first()

assert created_request is not None
assert created_request.user_email == community.email
assert created_request.user_email == CommunityCredentials.email
assert created_request.library_home_page == "http://teste.com/"
Loading