Skip to content

Commit fa88160

Browse files
BLEMENT33armanddidierjeanfoucblg
authored
PH v1 (#368)
### Description I sarted a PH module for the journalism association of ECL. --------- Co-authored-by: armanddidierjean <[email protected]> Co-authored-by: Foucauld Bellanger <[email protected]>
1 parent 44c0634 commit fa88160

File tree

14 files changed

+606
-3
lines changed

14 files changed

+606
-3
lines changed

app/core/groups/groups_type.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ class GroupType(str, Enum):
2828
BDE = "53a669d6-84b1-4352-8d7c-421c1fbd9c6a"
2929
CAA = "6c6d7e88-fdb8-4e42-b2b5-3d3cfd12e7d6"
3030
cinema = "ce5f36e6-5377-489f-9696-de70e2477300"
31+
ph = "4ec5ae77-f955-4309-96a5-19cc3c8be71c"
3132

3233
# Auth related groups
3334

app/core/notification/notification_types.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ class Topic(str, Enum):
1414
loan = "loan"
1515
raffle = "raffle"
1616
vote = "vote"
17+
ph = "ph"
1718

1819

1920
class CustomTopic:

app/modules/ph/__init__.py

Whitespace-only changes.

app/modules/ph/cruds_ph.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
from collections.abc import Sequence
2+
from datetime import date
3+
4+
from sqlalchemy import delete, select, update
5+
from sqlalchemy.exc import IntegrityError
6+
from sqlalchemy.ext.asyncio import AsyncSession
7+
8+
from app.modules.ph import models_ph, schemas_ph
9+
10+
11+
async def get_papers(
12+
db: AsyncSession,
13+
end_date: date | None = None,
14+
) -> Sequence[models_ph.Paper]:
15+
"""Return papers from the latest to the oldest"""
16+
result = await db.execute(
17+
select(models_ph.Paper)
18+
.where(models_ph.Paper.release_date <= (end_date or date.max))
19+
.order_by(models_ph.Paper.release_date.desc()),
20+
)
21+
return result.scalars().all()
22+
23+
24+
async def get_paper_by_id(
25+
db: AsyncSession,
26+
paper_id: str,
27+
) -> Sequence[models_ph.Paper]:
28+
result = await db.execute(
29+
select(models_ph.Paper).where(models_ph.Paper.id == paper_id),
30+
)
31+
return result.scalars().all()
32+
33+
34+
async def create_paper(
35+
paper: models_ph.Paper,
36+
db: AsyncSession,
37+
) -> models_ph.Paper:
38+
"""Create a new paper in database and return it"""
39+
40+
db.add(paper)
41+
try:
42+
await db.commit()
43+
return paper
44+
except IntegrityError as error:
45+
await db.rollback()
46+
raise ValueError(error)
47+
48+
49+
async def update_paper(
50+
paper_id: str,
51+
paper_update: schemas_ph.PaperUpdate,
52+
db: AsyncSession,
53+
):
54+
await db.execute(
55+
update(models_ph.Paper)
56+
.where(models_ph.Paper.id == paper_id)
57+
.values(**paper_update.model_dump(exclude_none=True)),
58+
)
59+
await db.commit()
60+
61+
62+
async def delete_paper(
63+
paper_id: str,
64+
db: AsyncSession,
65+
):
66+
await db.execute(
67+
delete(models_ph.Paper).where(models_ph.Paper.id == paper_id),
68+
)
69+
await db.commit()

app/modules/ph/endpoints_ph.py

Lines changed: 264 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
1+
import uuid
2+
from datetime import UTC, datetime, time, timedelta
3+
4+
from fastapi import Depends, File, HTTPException, UploadFile
5+
from fastapi.responses import FileResponse
6+
from sqlalchemy.ext.asyncio import AsyncSession
7+
8+
from app.core import models_core
9+
from app.core.groups.groups_type import GroupType
10+
from app.core.module import Module
11+
from app.core.notification.notification_types import CustomTopic, Topic
12+
from app.core.notification.schemas_notification import Message
13+
from app.dependencies import (
14+
get_db,
15+
get_notification_tool,
16+
get_request_id,
17+
is_user_a_member,
18+
is_user_a_member_of,
19+
)
20+
from app.modules.ph import cruds_ph, models_ph, schemas_ph
21+
from app.types.content_type import ContentType
22+
from app.utils.communication.notifications import NotificationTool
23+
from app.utils.tools import (
24+
delete_file_from_data,
25+
get_file_from_data,
26+
save_file_as_data,
27+
save_pdf_first_page_as_image,
28+
)
29+
30+
module = Module(
31+
root="ph",
32+
tag="ph",
33+
default_allowed_groups_ids=[GroupType.student],
34+
)
35+
36+
37+
@module.router.get(
38+
"/ph/{paper_id}/pdf",
39+
response_class=FileResponse,
40+
status_code=200,
41+
)
42+
async def get_paper_pdf(
43+
paper_id: str,
44+
db: AsyncSession = Depends(get_db),
45+
user: models_core.CoreUser = Depends(is_user_a_member),
46+
):
47+
paper = await cruds_ph.get_paper_by_id(db=db, paper_id=paper_id)
48+
if paper is None:
49+
raise HTTPException(
50+
status_code=404,
51+
detail="The paper does not exist.",
52+
)
53+
54+
return get_file_from_data(
55+
default_asset="assets/pdf/default_ph.pdf",
56+
directory="ph/pdf",
57+
filename=str(paper_id),
58+
)
59+
60+
61+
@module.router.get(
62+
"/ph/",
63+
response_model=list[schemas_ph.PaperComplete],
64+
status_code=200,
65+
)
66+
async def get_papers(
67+
db: AsyncSession = Depends(get_db),
68+
user: models_core.CoreUser = Depends(is_user_a_member),
69+
):
70+
"""
71+
Return all editions until now, sorted from the latest to the oldest
72+
"""
73+
result = await cruds_ph.get_papers(
74+
db=db,
75+
end_date=datetime.now(tz=UTC).date(),
76+
) # Return papers from the latest to the oldest until now
77+
return result
78+
79+
80+
@module.router.get(
81+
"/ph/admin",
82+
response_model=list[schemas_ph.PaperComplete],
83+
status_code=200,
84+
)
85+
async def get_papers_admin(
86+
db: AsyncSession = Depends(get_db),
87+
user: models_core.CoreUser = Depends(is_user_a_member_of(GroupType.ph)),
88+
):
89+
"""
90+
Return all editions, sorted from the latest to the oldest
91+
"""
92+
result = await cruds_ph.get_papers(
93+
db=db,
94+
) # Return all papers from the latest to the oldest
95+
return result
96+
97+
98+
@module.router.post(
99+
"/ph/",
100+
response_model=schemas_ph.PaperComplete,
101+
status_code=201,
102+
)
103+
async def create_paper(
104+
paper: schemas_ph.PaperBase,
105+
db: AsyncSession = Depends(get_db),
106+
user: models_core.CoreUser = Depends(is_user_a_member_of(GroupType.ph)),
107+
notification_tool: NotificationTool = Depends(get_notification_tool),
108+
):
109+
"""Create a new paper."""
110+
111+
paper_complete = schemas_ph.PaperComplete(
112+
id=str(uuid.uuid4()),
113+
**paper.model_dump(),
114+
)
115+
try:
116+
paper_db = models_ph.Paper(
117+
id=paper_complete.id,
118+
name=paper_complete.name,
119+
release_date=paper_complete.release_date,
120+
)
121+
122+
now = datetime.now(UTC)
123+
124+
# We only want to send a notification if the paper was released less than a month ago.
125+
if paper_db.release_date >= now.date() - timedelta(days=30):
126+
message = Message(
127+
context=f"ph-{paper_db.id}",
128+
is_visible=True,
129+
title=f"📗 PH - {paper_db.name}",
130+
content="Un nouveau journal est disponible! 🎉",
131+
delivery_datetime=datetime.combine(
132+
paper_db.release_date,
133+
time(hour=8, tzinfo=UTC),
134+
),
135+
# The notification will expire in 10 days
136+
expire_on=now + timedelta(days=10),
137+
)
138+
await notification_tool.send_notification_to_topic(
139+
custom_topic=CustomTopic(topic=Topic.ph),
140+
message=message,
141+
)
142+
return await cruds_ph.create_paper(paper=paper_db, db=db)
143+
144+
except ValueError as error:
145+
raise HTTPException(
146+
status_code=400,
147+
detail=str(error),
148+
)
149+
150+
151+
@module.router.post(
152+
"/ph/{paper_id}/pdf",
153+
status_code=201,
154+
)
155+
async def create_paper_pdf_and_cover(
156+
paper_id: str,
157+
pdf: UploadFile = File(...),
158+
user: models_core.CoreUser = Depends(is_user_a_member_of(GroupType.ph)),
159+
request_id: str = Depends(get_request_id),
160+
db: AsyncSession = Depends(get_db),
161+
):
162+
paper = await cruds_ph.get_paper_by_id(db=db, paper_id=paper_id)
163+
if paper is None:
164+
raise HTTPException(
165+
status_code=404,
166+
detail="The paper does not exist.",
167+
)
168+
169+
await save_file_as_data(
170+
upload_file=pdf,
171+
directory="ph/pdf",
172+
filename=str(paper_id),
173+
request_id=request_id,
174+
max_file_size=10 * 1024 * 1024, # 10 MB
175+
accepted_content_types=[ContentType.pdf],
176+
)
177+
178+
await save_pdf_first_page_as_image(
179+
input_pdf_directory="ph/pdf",
180+
output_image_directory="ph/cover",
181+
filename=str(paper_id),
182+
default_pdf_path="assets/pdf/default_ph.pdf",
183+
request_id=request_id,
184+
jpg_quality=95,
185+
)
186+
187+
188+
@module.router.get(
189+
"/ph/{paper_id}/cover",
190+
status_code=200,
191+
)
192+
async def get_cover(
193+
paper_id: str,
194+
user: models_core.CoreUser = Depends(is_user_a_member),
195+
db: AsyncSession = Depends(get_db),
196+
):
197+
paper = await cruds_ph.get_paper_by_id(db=db, paper_id=paper_id)
198+
if paper is None:
199+
raise HTTPException(
200+
status_code=404,
201+
detail="The paper does not exist.",
202+
)
203+
204+
return get_file_from_data(
205+
default_asset="assets/images/default_cover.jpg",
206+
directory="ph/cover",
207+
filename=str(paper_id),
208+
)
209+
210+
211+
@module.router.patch(
212+
"/ph/{paper_id}",
213+
status_code=204,
214+
)
215+
async def update_paper(
216+
paper_id: str,
217+
paper_update: schemas_ph.PaperUpdate,
218+
user: models_core.CoreUser = Depends(is_user_a_member_of(GroupType.ph)),
219+
db: AsyncSession = Depends(get_db),
220+
):
221+
paper = await cruds_ph.get_paper_by_id(paper_id=paper_id, db=db)
222+
if not paper:
223+
raise HTTPException(
224+
status_code=404,
225+
detail="Invalid paper_id",
226+
)
227+
228+
await cruds_ph.update_paper(
229+
paper_id=paper_id,
230+
paper_update=paper_update,
231+
db=db,
232+
)
233+
234+
235+
@module.router.delete(
236+
"/ph/{paper_id}",
237+
status_code=204,
238+
)
239+
async def delete_paper(
240+
paper_id: str,
241+
user: models_core.CoreUser = Depends(is_user_a_member_of(GroupType.ph)),
242+
db: AsyncSession = Depends(get_db),
243+
):
244+
paper = await cruds_ph.get_paper_by_id(paper_id=paper_id, db=db)
245+
if not paper:
246+
raise HTTPException(
247+
status_code=404,
248+
detail="Invalid paper_id",
249+
)
250+
251+
delete_file_from_data(
252+
directory="ph/pdf",
253+
filename=str(paper_id),
254+
)
255+
256+
delete_file_from_data(
257+
directory="ph/cover",
258+
filename=str(paper_id),
259+
)
260+
261+
await cruds_ph.delete_paper(
262+
paper_id=paper_id,
263+
db=db,
264+
)

app/modules/ph/models_ph.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
from datetime import date
2+
3+
from sqlalchemy import Date, String
4+
from sqlalchemy.orm import Mapped, mapped_column
5+
6+
from app.types.sqlalchemy import Base
7+
8+
9+
class Paper(Base):
10+
__tablename__ = "ph_papers"
11+
12+
id: Mapped[str] = mapped_column(String, primary_key=True, index=True)
13+
name: Mapped[str] = mapped_column(String, nullable=False)
14+
release_date: Mapped[date] = mapped_column(Date, nullable=False)

app/modules/ph/schemas_ph.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
from datetime import date
2+
3+
from pydantic import BaseModel
4+
5+
6+
class PaperBase(BaseModel):
7+
"""Base schema for paper's model"""
8+
9+
name: str
10+
release_date: date
11+
12+
13+
class PaperComplete(PaperBase):
14+
id: str
15+
16+
17+
class PaperUpdate(BaseModel):
18+
name: str | None = None
19+
release_date: date | None = None

0 commit comments

Comments
 (0)