Skip to content
Merged
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
101 changes: 76 additions & 25 deletions app/routers/libraries/routes.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
from typing import List
from typing import Annotated, List

from fastapi import APIRouter, HTTPException, Request, status
from fastapi import APIRouter, Header, HTTPException, Request, status
from pydantic import BaseModel

from app.schemas import Library as LibrarySchema
from app.schemas import LibraryNews
from app.schemas import LibraryRequest as LibraryRequestSchema
from app.schemas import Subscription as SubscriptionSchema
from app.services.database.models import Library, Subscription
from app.services.database.models.libraries_request import LibraryRequest
from app.services.database.orm.library import (
get_libraries_by_language,
get_library_ids_by_multiple_names,
insert_library,
)
from app.services.database.orm.library_request import insert_library_request
from app.services.database.orm.subscription import upsert_multiple_subscription


Expand All @@ -34,27 +37,32 @@ def setup():
description="Get libraries by language",
)
async def get_by_language(request: Request, language: str):
libraryList = await get_libraries_by_language(
language=language, session=request.app.db_session_factory
)
return [
LibrarySchema(
library_name=libraryDb.library_name,
news=[
LibraryNews(
tag=news["tag"], description=news["description"]
)
for news in libraryDb.news
],
logo=libraryDb.logo,
version=libraryDb.version,
release_date=libraryDb.release_date,
releases_doc_url=libraryDb.releases_doc_url,
fixed_release_url=libraryDb.fixed_release_url,
language=libraryDb.language,
try:
libraryList = await get_libraries_by_language(
language=language, session=request.app.db_session_factory
)
for libraryDb in libraryList
]
return [
LibrarySchema(
library_name=libraryDb.library_name,
news=[
LibraryNews(
tag=news["tag"], description=news["description"]
)
for news in libraryDb.news
],
logo=libraryDb.logo,
version=libraryDb.version,
release_date=libraryDb.release_date,
releases_doc_url=libraryDb.releases_doc_url,
fixed_release_url=libraryDb.fixed_release_url,
language=libraryDb.language,
)
for libraryDb in libraryList
]
except HTTPException as e:
raise e
except Exception as e:
HTTPException(status_code=500, detail=f"Unexpected error: {e}")

@router.post(
"",
Expand All @@ -80,9 +88,11 @@ async def create_library(
try:
await insert_library(library, request.app.db_session_factory)
return LibraryResponse()
except HTTPException as e:
raise e
except Exception as e:
raise HTTPException(
status_code=500, detail=f"Failed to create library: {e}"
status_code=500, detail=f"Unexpected error: {e}"
)

@router.post(
Expand All @@ -97,14 +107,22 @@ async def create_library(
async def subscribe_libraries(
request: Request,
body: SubscriptionSchema,
user_email: Annotated[str, Header(alias="user-email")],
):
try:
library_ids = await get_library_ids_by_multiple_names(
body.libraries_list, request.app.db_session_factory
)

if (library_ids is None) or (len(library_ids) == 0):
raise HTTPException(
status_code=404, detail="Libraries not found"
)

subscriptions = [
Subscription(email=body.email, tags=body.tags, library_id=id)
Subscription(
user_email=user_email, tags=body.tags, library_id=id
)
for id in library_ids
]

Expand All @@ -113,9 +131,42 @@ async def subscribe_libraries(
)

return SubscribeLibraryResponse()
except HTTPException as e:
raise e
except Exception as e:
raise HTTPException(
status_code=500, detail=f"Unexpected error: {e}"
)

@router.post(
"/request",
response_model=LibraryResponse,
status_code=status.HTTP_200_OK,
summary="Request a library",
description="Request a library to follow",
)
async def request_library(
request: Request,
body: LibraryRequestSchema,
user_email: Annotated[str, Header(alias="user-email")],
):
try:
library_request = LibraryRequest(
user_email=user_email,
library_name=body.library_name,
library_home_page=body.library_home_page,
)

await insert_library_request(
library_request, request.app.db_session_factory
)

return LibraryResponse()
except HTTPException as e:
raise e
except Exception as e:
raise HTTPException(
status_code=500, detail=f"Subscription failed: {e}"
status_code=500, detail=f"Unexpected error: {e}"
)

return router
6 changes: 5 additions & 1 deletion app/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ class Library(BaseModel):
language: str


class LibraryRequest(BaseModel):
library_name: str
library_home_page: str


# Community / User Class
class Community(BaseModel):
username: str
Expand Down Expand Up @@ -55,6 +60,5 @@ class TokenPayload(BaseModel):


class Subscription(BaseModel):
email: str
tags: List[LibraryTagUpdatesEnum]
libraries_list: List[str]
4 changes: 2 additions & 2 deletions app/services/database/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from app.services.database.models.communities import Community
from app.services.database.models.libraries import Library
from app.services.database.models.libraries_request import LibraryRequest
from app.services.database.models.news import News
from app.services.database.models.subscriptions import Subscription


__all__ = ["Community", "Library","News", "Subscription"]
__all__ = ["Community", "Library", "News", "Subscription", "LibraryRequest"]
6 changes: 6 additions & 0 deletions app/services/database/models/communities.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from datetime import datetime
from typing import Optional

from sqlmodel import Field, SQLModel
Expand All @@ -10,3 +11,8 @@ class Community(SQLModel, table=True):
username: str
email: str
password: str
created_at: Optional[datetime] = Field(default_factory=datetime.now)
updated_at: Optional[datetime] = Field(
default_factory=datetime.now,
sa_column_kwargs={"onupdate": datetime.now},
)
7 changes: 6 additions & 1 deletion app/services/database/models/libraries.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from datetime import date
from datetime import date, datetime
from typing import List, Optional

from sqlalchemy import JSON, Column
Expand All @@ -20,3 +20,8 @@ class Library(SQLModel, table=True):
community_id: Optional[int] = Field(
default=None, foreign_key="communities.id"
)
created_at: Optional[datetime] = Field(default_factory=datetime.now)
updated_at: Optional[datetime] = Field(
default_factory=datetime.now,
sa_column_kwargs={"onupdate": datetime.now},
)
21 changes: 21 additions & 0 deletions app/services/database/models/libraries_request.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from datetime import datetime
from typing import Optional

from sqlmodel import Field, SQLModel


class LibraryRequest(SQLModel, table=True):
__tablename__ = "libraries_request"

id: Optional[int] = Field(default=None, primary_key=True)
user_email: str
library_name: str
library_home_page: str
community_id: Optional[int] = Field(
default=None, foreign_key="communities.id"
)
created_at: Optional[datetime] = Field(default_factory=datetime.now)
updated_at: Optional[datetime] = Field(
default_factory=datetime.now,
sa_column_kwargs={"onupdate": datetime.now},
)
8 changes: 7 additions & 1 deletion app/services/database/models/subscriptions.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from datetime import datetime
from typing import List, Optional

from sqlalchemy import JSON, Column
Expand All @@ -10,9 +11,14 @@ class Subscription(SQLModel, table=True):
__tablename__ = "subscriptions" # type: ignore

id: Optional[int] = Field(default=None, primary_key=True)
email: str
user_email: str
tags: List[LibraryTagUpdatesEnum] = Field(sa_column=Column(JSON))
community_id: Optional[int] = Field(
default=None, foreign_key="communities.id"
)
library_id: Optional[int] = Field(default=None, foreign_key="libraries.id")
created_at: Optional[datetime] = Field(default_factory=datetime.now)
updated_at: Optional[datetime] = Field(
default_factory=datetime.now,
sa_column_kwargs={"onupdate": datetime.now},
)
23 changes: 23 additions & 0 deletions app/services/database/orm/library_request.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from typing import List

from sqlmodel import select
from sqlmodel.ext.asyncio.session import AsyncSession

from app.services.database.models.libraries_request import LibraryRequest


async def insert_library_request(
library_request: LibraryRequest,
session: AsyncSession,
):
session.add(library_request)
await session.commit()
await session.refresh(library_request)


async def get_all_library_requests(
session: AsyncSession,
) -> List[LibraryRequest]:
statement = select(LibraryRequest)
result = await session.exec(statement)
return [library_request for library_request in result.all()]
13 changes: 8 additions & 5 deletions app/services/database/orm/subscription.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,21 @@ async def upsert_multiple_subscription(
if not subscriptions:
return []

incoming_map: Dict[Tuple[str, int], Subscription] = {
(sub.email, sub.library_id): sub for sub in subscriptions
incoming_map: Dict[Tuple[str, int | None], Subscription] = {
(sub.user_email, sub.library_id): sub for sub in subscriptions
}

keys_to_check = incoming_map.keys()
stmt = select(Subscription).where(
tuple_(Subscription.email, Subscription.library_id).in_(keys_to_check)
tuple_(Subscription.user_email, Subscription.library_id).in_(
keys_to_check
)
)

result = await session.exec(stmt)
existing_subscriptions = result.all()
existing_map: Dict[Tuple[str, int], Subscription] = {
(sub.email, sub.library_id): sub for sub in existing_subscriptions
existing_map: Dict[Tuple[str, int | None], Subscription] = {
(sub.user_email, sub.library_id): sub for sub in existing_subscriptions
}

new_subscriptions: List[Subscription] = []
Expand Down
65 changes: 65 additions & 0 deletions tests/test_libraries_request.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import pytest
import pytest_asyncio
from httpx import AsyncClient
from sqlmodel import select
from sqlmodel.ext.asyncio.session import AsyncSession

from app.services.database.models import Community, LibraryRequest


@pytest_asyncio.fixture
async def community(session: AsyncSession):
community = Community(username="admin", email="a@a.com", password="123")
session.add(community)
await session.commit()
await session.refresh(community)
return community


@pytest.mark.asyncio
async def test_insert_libraries(session: AsyncSession, community: Community):
library = LibraryRequest(
library_name="Flask",
user_email="teste@teste.com",
library_home_page="http://teste.com/",
community_id=community.id,
)
session.add(library)
await session.commit()

statement = select(LibraryRequest).where(
LibraryRequest.library_name == "Flask"
)
result = await session.exec(statement)
found = result.first()

assert found is not None
assert found.user_email == "teste@teste.com"
assert found.library_home_page == "http://teste.com/"
assert found.community_id == community.id


@pytest.mark.asyncio
async def test_post_libraries_endpoint(
async_client: AsyncClient, session: AsyncSession
):
body = {"library_name": "FastAPI", "library_home_page": "http://teste.com/"}

response = await async_client.post(
"/api/libraries/request",
json=body,
headers={"Content-Type": "application/json", "user-email": "a@a.com"},
)

assert response.status_code == 200
assert response.json()["status"] == "Library created successfully"

statement = select(LibraryRequest).where(
LibraryRequest.library_name == body["library_name"]
)
result = await session.exec(statement)
created_request = result.first()

assert created_request is not None
assert created_request.user_email == "a@a.com"
assert created_request.library_home_page == "http://teste.com/"
Loading