Skip to content

Commit 268a4bc

Browse files
authored
Merge pull request #3 from igormagalhaesr/posts
posts crud and routes
2 parents b2a091d + f4bd139 commit 268a4bc

File tree

7 files changed

+198
-6
lines changed

7 files changed

+198
-6
lines changed

src/app/api/v1/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
from app.api.v1.login import router as login_router
44
from app.api.v1.users import router as users_router
5+
from app.api.v1.posts import router as posts_router
56

67
router = APIRouter(prefix="/v1")
78
router.include_router(login_router)
89
router.include_router(users_router)
10+
router.include_router(posts_router)

src/app/api/v1/posts.py

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
from typing import List, Annotated
2+
3+
from fastapi import Depends, HTTPException
4+
from sqlalchemy.ext.asyncio import AsyncSession
5+
import fastapi
6+
7+
from app.schemas.post import PostCreate, PostUpdate, PostRead, PostCreateInternal
8+
from app.schemas.user import UserRead
9+
from app.api.dependencies import get_current_user, get_current_superuser
10+
from app.core.database import async_get_db
11+
from app.crud.crud_posts import crud_posts
12+
from app.crud.crud_users import crud_users
13+
from app.api.exceptions import privileges_exception
14+
15+
router = fastapi.APIRouter(tags=["posts"])
16+
17+
@router.post("/{username}/post", response_model=PostRead, status_code=201)
18+
async def write_post(
19+
username: str,
20+
post: PostCreate,
21+
current_user: Annotated[UserRead, Depends(get_current_user)],
22+
db: Annotated[AsyncSession, Depends(async_get_db)]
23+
):
24+
db_user = await crud_users.get(db=db, username=username, is_deleted=False)
25+
if db_user is None:
26+
raise HTTPException(status_code=404, detail="User not found")
27+
28+
if current_user.id != db_user.id:
29+
raise privileges_exception
30+
31+
post_internal_dict = post.model_dump()
32+
post_internal_dict["created_by_user_id"] = db_user.id
33+
post_internal = PostCreateInternal(**post_internal_dict)
34+
return await crud_posts.create(db=db, object=post_internal)
35+
36+
37+
@router.get("/{username}/posts", response_model=List[PostRead])
38+
async def read_posts(
39+
username: str,
40+
db: Annotated[AsyncSession, Depends(async_get_db)]
41+
):
42+
db_user = await crud_users.get(db=db, username=username, is_deleted=False)
43+
if db_user is None:
44+
raise HTTPException(status_code=404, detail="User not found")
45+
46+
posts = await crud_posts.get_multi(db=db, created_by_user_id=db_user.id, is_deleted=False)
47+
return posts
48+
49+
50+
@router.get("/{username}/post/{id}", response_model=PostRead)
51+
async def read_post(
52+
username: str,
53+
id: int,
54+
db: Annotated[AsyncSession, Depends(async_get_db)]
55+
):
56+
db_user = await crud_users.get(db=db, username=username, is_deleted=False)
57+
if db_user is None:
58+
raise HTTPException(status_code=404, detail="User not found")
59+
60+
db_post = await crud_posts.get(db=db, id=id, created_by_user_id=db_user.id, is_deleted=False)
61+
if db_post is None:
62+
raise HTTPException(status_code=404, detail="Post not found")
63+
64+
return db_post
65+
66+
67+
@router.patch("/{username}/post/{id}", response_model=PostRead)
68+
async def patch_post(
69+
username: str,
70+
id: int,
71+
values: PostUpdate,
72+
current_user: Annotated[UserRead, Depends(get_current_user)],
73+
db: Annotated[AsyncSession, Depends(async_get_db)]
74+
):
75+
db_user = await crud_users.get(db=db, username=username, is_deleted=False)
76+
if db_user is None:
77+
raise HTTPException(status_code=404, detail="User not found")
78+
79+
if current_user.id != db_user.id:
80+
raise privileges_exception
81+
82+
db_post = await crud_posts.get(db=db, id=id, is_deleted=False)
83+
if db_post is None:
84+
raise HTTPException(status_code=404, detail="Post not found")
85+
86+
return await crud_posts.update(db=db, object=values, db_object=db_post, id=id)
87+
88+
89+
@router.delete("/{username}/post/{id}")
90+
async def erase_post(
91+
username: str,
92+
id: int,
93+
current_user: Annotated[UserRead, Depends(get_current_user)],
94+
db: Annotated[AsyncSession, Depends(async_get_db)]
95+
):
96+
db_user = await crud_users.get(db=db, username=username, is_deleted=False)
97+
if db_user is None:
98+
raise HTTPException(status_code=404, detail="User not found")
99+
100+
if current_user.id != db_user.id:
101+
raise privileges_exception
102+
103+
db_post = await crud_posts.get(db=db, id=id, is_deleted=False)
104+
if db_post is None:
105+
raise HTTPException(status_code=404, detail="Post not found")
106+
107+
deleted_post = await crud_posts.delete(db=db, db_object=db_post, id=id)
108+
if deleted_post.is_deleted == True:
109+
message = {"message": "Post deleted"}
110+
else:
111+
message = {"message": "Something went wrong"}
112+
113+
return message
114+
115+
116+
@router.delete("/{username}/db_post/{id}")
117+
async def erase_db_post(
118+
username: str,
119+
id: int,
120+
current_superuser: Annotated[UserRead, Depends(get_current_superuser)],
121+
db: Annotated[AsyncSession, Depends(async_get_db)]
122+
):
123+
db_user = await crud_users.get(db=db, username=username, is_deleted=False)
124+
if db_user is None:
125+
raise HTTPException(status_code=404, detail="User not found")
126+
127+
db_post = await crud_posts.get(db=db, id=id, is_deleted=False)
128+
if db_post is None:
129+
raise HTTPException(status_code=404, detail="Post not found")
130+
131+
await crud_posts.db_delete(db=db, db_object=db_post, id=id)
132+
deleted_post = await crud_posts.get(db=db, id=id)
133+
134+
if deleted_post is None:
135+
message = {"message": "Post deleted"}
136+
else:
137+
message = {"message": "Something went wrong"}
138+
139+
return message

src/app/api/v1/users.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
router = fastapi.APIRouter(tags=["users"])
1515

16-
@router.post("/user", response_model=UserBase, status_code=201)
16+
@router.post("/user", response_model=UserRead, status_code=201)
1717
async def write_user(user: UserCreate, db: AsyncSession = Depends(async_get_db)):
1818
db_user = await crud_users.get(db=db, email=user.email)
1919
if db_user:
@@ -53,7 +53,7 @@ async def read_user(username: str, db: AsyncSession = Depends(async_get_db)):
5353
return db_user
5454

5555

56-
@router.patch("/user/{username}", response_model=UserUpdate)
56+
@router.patch("/user/{username}", response_model=UserRead)
5757
async def patch_user(
5858
values: UserUpdate,
5959
username: str,

src/app/crud/crud_posts.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from app.crud.crud_base import CRUDBase
2+
from app.models.post import Post
3+
from app.schemas.post import PostCreateInternal, PostUpdate, PostUpdateInternal, PostDelete
4+
5+
CRUDPost = CRUDBase[Post, PostCreateInternal, PostUpdate, PostUpdateInternal, PostDelete]
6+
crud_posts = CRUDPost(Post)

src/app/models/post.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@ class Post(Base):
1616
created_by_user_id: Mapped[int] = mapped_column(ForeignKey("user.id"))
1717
title: Mapped[str] = mapped_column(String(30))
1818
text: Mapped[str] = mapped_column(String(63206))
19-
media_url: Mapped[str] = mapped_column(String)
19+
media_url: Mapped[str | None] = mapped_column(String, default=None)
2020

21-
user: Mapped["User"] = relationship(back_populates="posts", lazy="selectin")
21+
user: Mapped["User"] = relationship(back_populates="posts", lazy="selectin", init=False)
2222

2323
created_at: Mapped[datetime] = mapped_column(
2424
DateTime, default_factory=datetime.utcnow

src/app/schemas/post.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@ class PostRead(BaseModel):
4141
media_url: Annotated[
4242
str | None,
4343
Field(
44-
pattern=r"^(https?|ftp)://[^\s/$.?#].[^\s]*$",
4544
examples=["https://www.postimageurl.com"],
4645
default=None
4746
),
@@ -53,8 +52,18 @@ class PostRead(BaseModel):
5352
class PostCreate(PostBase):
5453
model_config = ConfigDict(extra='forbid')
5554

55+
media_url: Annotated[
56+
str | None,
57+
Field(
58+
pattern=r"^(https?|ftp)://[^\s/$.?#].[^\s]*$",
59+
examples=["https://www.postimageurl.com"],
60+
default=None
61+
),
62+
]
63+
64+
65+
class PostCreateInternal(PostCreate):
5666
created_by_user_id: int
57-
media_url: str | None = None
5867

5968

6069
class PostUpdate(PostBase):
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
"""empty message
2+
3+
Revision ID: 474ddfe32ea7
4+
Revises: 211a8b0d0080
5+
Create Date: 2023-10-07 02:22:22.612109
6+
7+
"""
8+
from typing import Sequence, Union
9+
10+
from alembic import op
11+
import sqlalchemy as sa
12+
13+
14+
# revision identifiers, used by Alembic.
15+
revision: str = '474ddfe32ea7'
16+
down_revision: Union[str, None] = '211a8b0d0080'
17+
branch_labels: Union[str, Sequence[str], None] = None
18+
depends_on: Union[str, Sequence[str], None] = None
19+
20+
21+
def upgrade() -> None:
22+
# ### commands auto generated by Alembic - please adjust! ###
23+
op.alter_column('post', 'media_url',
24+
existing_type=sa.VARCHAR(),
25+
nullable=True)
26+
op.create_unique_constraint(None, 'post', ['id'])
27+
# ### end Alembic commands ###
28+
29+
30+
def downgrade() -> None:
31+
# ### commands auto generated by Alembic - please adjust! ###
32+
op.drop_constraint(None, 'post', type_='unique')
33+
op.alter_column('post', 'media_url',
34+
existing_type=sa.VARCHAR(),
35+
nullable=False)
36+
# ### end Alembic commands ###

0 commit comments

Comments
 (0)