Skip to content

Commit d40db98

Browse files
authored
Merge pull request #50 from PythonFloripa/feature/#32
Feature/#32
2 parents 19637ef + 0866c95 commit d40db98

File tree

11 files changed

+285
-48
lines changed

11 files changed

+285
-48
lines changed

app/routers/libraries/routes.py

Lines changed: 76 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
1-
from typing import List
1+
from typing import Annotated, List
22

3-
from fastapi import APIRouter, HTTPException, Request, status
3+
from fastapi import APIRouter, Header, HTTPException, Request, status
44
from pydantic import BaseModel
55

66
from app.schemas import Library as LibrarySchema
77
from app.schemas import LibraryNews
8+
from app.schemas import LibraryRequest as LibraryRequestSchema
89
from app.schemas import Subscription as SubscriptionSchema
910
from app.services.database.models import Library, Subscription
11+
from app.services.database.models.libraries_request import LibraryRequest
1012
from app.services.database.orm.library import (
1113
get_libraries_by_language,
1214
get_library_ids_by_multiple_names,
1315
insert_library,
1416
)
17+
from app.services.database.orm.library_request import insert_library_request
1518
from app.services.database.orm.subscription import upsert_multiple_subscription
1619

1720

@@ -34,27 +37,32 @@ def setup():
3437
description="Get libraries by language",
3538
)
3639
async def get_by_language(request: Request, language: str):
37-
libraryList = await get_libraries_by_language(
38-
language=language, session=request.app.db_session_factory
39-
)
40-
return [
41-
LibrarySchema(
42-
library_name=libraryDb.library_name,
43-
news=[
44-
LibraryNews(
45-
tag=news["tag"], description=news["description"]
46-
)
47-
for news in libraryDb.news
48-
],
49-
logo=libraryDb.logo,
50-
version=libraryDb.version,
51-
release_date=libraryDb.release_date,
52-
releases_doc_url=libraryDb.releases_doc_url,
53-
fixed_release_url=libraryDb.fixed_release_url,
54-
language=libraryDb.language,
40+
try:
41+
libraryList = await get_libraries_by_language(
42+
language=language, session=request.app.db_session_factory
5543
)
56-
for libraryDb in libraryList
57-
]
44+
return [
45+
LibrarySchema(
46+
library_name=libraryDb.library_name,
47+
news=[
48+
LibraryNews(
49+
tag=news["tag"], description=news["description"]
50+
)
51+
for news in libraryDb.news
52+
],
53+
logo=libraryDb.logo,
54+
version=libraryDb.version,
55+
release_date=libraryDb.release_date,
56+
releases_doc_url=libraryDb.releases_doc_url,
57+
fixed_release_url=libraryDb.fixed_release_url,
58+
language=libraryDb.language,
59+
)
60+
for libraryDb in libraryList
61+
]
62+
except HTTPException as e:
63+
raise e
64+
except Exception as e:
65+
HTTPException(status_code=500, detail=f"Unexpected error: {e}")
5866

5967
@router.post(
6068
"",
@@ -80,9 +88,11 @@ async def create_library(
8088
try:
8189
await insert_library(library, request.app.db_session_factory)
8290
return LibraryResponse()
91+
except HTTPException as e:
92+
raise e
8393
except Exception as e:
8494
raise HTTPException(
85-
status_code=500, detail=f"Failed to create library: {e}"
95+
status_code=500, detail=f"Unexpected error: {e}"
8696
)
8797

8898
@router.post(
@@ -97,14 +107,22 @@ async def create_library(
97107
async def subscribe_libraries(
98108
request: Request,
99109
body: SubscriptionSchema,
110+
user_email: Annotated[str, Header(alias="user-email")],
100111
):
101112
try:
102113
library_ids = await get_library_ids_by_multiple_names(
103114
body.libraries_list, request.app.db_session_factory
104115
)
105116

117+
if (library_ids is None) or (len(library_ids) == 0):
118+
raise HTTPException(
119+
status_code=404, detail="Libraries not found"
120+
)
121+
106122
subscriptions = [
107-
Subscription(email=body.email, tags=body.tags, library_id=id)
123+
Subscription(
124+
user_email=user_email, tags=body.tags, library_id=id
125+
)
108126
for id in library_ids
109127
]
110128

@@ -113,9 +131,42 @@ async def subscribe_libraries(
113131
)
114132

115133
return SubscribeLibraryResponse()
134+
except HTTPException as e:
135+
raise e
136+
except Exception as e:
137+
raise HTTPException(
138+
status_code=500, detail=f"Unexpected error: {e}"
139+
)
140+
141+
@router.post(
142+
"/request",
143+
response_model=LibraryResponse,
144+
status_code=status.HTTP_200_OK,
145+
summary="Request a library",
146+
description="Request a library to follow",
147+
)
148+
async def request_library(
149+
request: Request,
150+
body: LibraryRequestSchema,
151+
user_email: Annotated[str, Header(alias="user-email")],
152+
):
153+
try:
154+
library_request = LibraryRequest(
155+
user_email=user_email,
156+
library_name=body.library_name,
157+
library_home_page=body.library_home_page,
158+
)
159+
160+
await insert_library_request(
161+
library_request, request.app.db_session_factory
162+
)
163+
164+
return LibraryResponse()
165+
except HTTPException as e:
166+
raise e
116167
except Exception as e:
117168
raise HTTPException(
118-
status_code=500, detail=f"Subscription failed: {e}"
169+
status_code=500, detail=f"Unexpected error: {e}"
119170
)
120171

121172
return router

app/schemas.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ class Library(BaseModel):
2323
language: str
2424

2525

26+
class LibraryRequest(BaseModel):
27+
library_name: str
28+
library_home_page: str
29+
30+
2631
# Community / User Class
2732
class Community(BaseModel):
2833
username: str
@@ -55,6 +60,5 @@ class TokenPayload(BaseModel):
5560

5661

5762
class Subscription(BaseModel):
58-
email: str
5963
tags: List[LibraryTagUpdatesEnum]
6064
libraries_list: List[str]
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from app.services.database.models.communities import Community
22
from app.services.database.models.libraries import Library
3+
from app.services.database.models.libraries_request import LibraryRequest
34
from app.services.database.models.news import News
45
from app.services.database.models.subscriptions import Subscription
56

6-
7-
__all__ = ["Community", "Library","News", "Subscription"]
7+
__all__ = ["Community", "Library", "News", "Subscription", "LibraryRequest"]

app/services/database/models/communities.py

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

34
from sqlmodel import Field, SQLModel
@@ -10,3 +11,8 @@ class Community(SQLModel, table=True):
1011
username: str
1112
email: str
1213
password: str
14+
created_at: Optional[datetime] = Field(default_factory=datetime.now)
15+
updated_at: Optional[datetime] = Field(
16+
default_factory=datetime.now,
17+
sa_column_kwargs={"onupdate": datetime.now},
18+
)

app/services/database/models/libraries.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from datetime import date
1+
from datetime import date, datetime
22
from typing import List, Optional
33

44
from sqlalchemy import JSON, Column
@@ -20,3 +20,8 @@ class Library(SQLModel, table=True):
2020
community_id: Optional[int] = Field(
2121
default=None, foreign_key="communities.id"
2222
)
23+
created_at: Optional[datetime] = Field(default_factory=datetime.now)
24+
updated_at: Optional[datetime] = Field(
25+
default_factory=datetime.now,
26+
sa_column_kwargs={"onupdate": datetime.now},
27+
)
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
from datetime import datetime
2+
from typing import Optional
3+
4+
from sqlmodel import Field, SQLModel
5+
6+
7+
class LibraryRequest(SQLModel, table=True):
8+
__tablename__ = "libraries_request"
9+
10+
id: Optional[int] = Field(default=None, primary_key=True)
11+
user_email: str
12+
library_name: str
13+
library_home_page: str
14+
community_id: Optional[int] = Field(
15+
default=None, foreign_key="communities.id"
16+
)
17+
created_at: Optional[datetime] = Field(default_factory=datetime.now)
18+
updated_at: Optional[datetime] = Field(
19+
default_factory=datetime.now,
20+
sa_column_kwargs={"onupdate": datetime.now},
21+
)

app/services/database/models/subscriptions.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from datetime import datetime
12
from typing import List, Optional
23

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

1213
id: Optional[int] = Field(default=None, primary_key=True)
13-
email: str
14+
user_email: str
1415
tags: List[LibraryTagUpdatesEnum] = Field(sa_column=Column(JSON))
1516
community_id: Optional[int] = Field(
1617
default=None, foreign_key="communities.id"
1718
)
1819
library_id: Optional[int] = Field(default=None, foreign_key="libraries.id")
20+
created_at: Optional[datetime] = Field(default_factory=datetime.now)
21+
updated_at: Optional[datetime] = Field(
22+
default_factory=datetime.now,
23+
sa_column_kwargs={"onupdate": datetime.now},
24+
)
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
from typing import List
2+
3+
from sqlmodel import select
4+
from sqlmodel.ext.asyncio.session import AsyncSession
5+
6+
from app.services.database.models.libraries_request import LibraryRequest
7+
8+
9+
async def insert_library_request(
10+
library_request: LibraryRequest,
11+
session: AsyncSession,
12+
):
13+
session.add(library_request)
14+
await session.commit()
15+
await session.refresh(library_request)
16+
17+
18+
async def get_all_library_requests(
19+
session: AsyncSession,
20+
) -> List[LibraryRequest]:
21+
statement = select(LibraryRequest)
22+
result = await session.exec(statement)
23+
return [library_request for library_request in result.all()]

app/services/database/orm/subscription.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,21 @@ async def upsert_multiple_subscription(
1414
if not subscriptions:
1515
return []
1616

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

2121
keys_to_check = incoming_map.keys()
2222
stmt = select(Subscription).where(
23-
tuple_(Subscription.email, Subscription.library_id).in_(keys_to_check)
23+
tuple_(Subscription.user_email, Subscription.library_id).in_(
24+
keys_to_check
25+
)
2426
)
27+
2528
result = await session.exec(stmt)
2629
existing_subscriptions = result.all()
27-
existing_map: Dict[Tuple[str, int], Subscription] = {
28-
(sub.email, sub.library_id): sub for sub in existing_subscriptions
30+
existing_map: Dict[Tuple[str, int | None], Subscription] = {
31+
(sub.user_email, sub.library_id): sub for sub in existing_subscriptions
2932
}
3033

3134
new_subscriptions: List[Subscription] = []

tests/test_libraries_request.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import pytest
2+
import pytest_asyncio
3+
from httpx import AsyncClient
4+
from sqlmodel import select
5+
from sqlmodel.ext.asyncio.session import AsyncSession
6+
7+
from app.services.database.models import Community, LibraryRequest
8+
9+
10+
@pytest_asyncio.fixture
11+
async def community(session: AsyncSession):
12+
community = Community(username="admin", email="[email protected]", password="123")
13+
session.add(community)
14+
await session.commit()
15+
await session.refresh(community)
16+
return community
17+
18+
19+
@pytest.mark.asyncio
20+
async def test_insert_libraries(session: AsyncSession, community: Community):
21+
library = LibraryRequest(
22+
library_name="Flask",
23+
user_email="[email protected]",
24+
library_home_page="http://teste.com/",
25+
community_id=community.id,
26+
)
27+
session.add(library)
28+
await session.commit()
29+
30+
statement = select(LibraryRequest).where(
31+
LibraryRequest.library_name == "Flask"
32+
)
33+
result = await session.exec(statement)
34+
found = result.first()
35+
36+
assert found is not None
37+
assert found.user_email == "[email protected]"
38+
assert found.library_home_page == "http://teste.com/"
39+
assert found.community_id == community.id
40+
41+
42+
@pytest.mark.asyncio
43+
async def test_post_libraries_endpoint(
44+
async_client: AsyncClient, session: AsyncSession
45+
):
46+
body = {"library_name": "FastAPI", "library_home_page": "http://teste.com/"}
47+
48+
response = await async_client.post(
49+
"/api/libraries/request",
50+
json=body,
51+
headers={"Content-Type": "application/json", "user-email": "[email protected]"},
52+
)
53+
54+
assert response.status_code == 200
55+
assert response.json()["status"] == "Library created successfully"
56+
57+
statement = select(LibraryRequest).where(
58+
LibraryRequest.library_name == body["library_name"]
59+
)
60+
result = await session.exec(statement)
61+
created_request = result.first()
62+
63+
assert created_request is not None
64+
assert created_request.user_email == "[email protected]"
65+
assert created_request.library_home_page == "http://teste.com/"

0 commit comments

Comments
 (0)