Skip to content

Commit 13b71f6

Browse files
committed
python mongodb crud app
1 parent cbcfeba commit 13b71f6

File tree

9 files changed

+187
-4
lines changed

9 files changed

+187
-4
lines changed

app/database.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,6 @@
77

88
db = client[settings.MONGO_INITDB_DATABASE]
99
User = db.users
10+
Post = db.posts
1011
User.create_index([("email", pymongo.ASCENDING)], unique=True)
12+
Post.create_index([("title", pymongo.ASCENDING)], unique=True)

app/main.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from fastapi.middleware.cors import CORSMiddleware
33

44
from app.config import settings
5-
from app.routers import auth, user
5+
from app.routers import auth, user, post
66

77
app = FastAPI()
88

@@ -21,6 +21,7 @@
2121

2222
app.include_router(auth.router, tags=['Auth'], prefix='/api/auth')
2323
app.include_router(user.router, tags=['Users'], prefix='/api/users')
24+
app.include_router(post.router, tags=['Posts'], prefix='/api/posts')
2425

2526

2627
@app.get("/api/healthchecker")

app/oauth2.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from pydantic import BaseModel
66
from bson.objectid import ObjectId
77

8-
from app.serializers import userEntity
8+
from app.serializers.userSerializers import userEntity
99

1010
from .database import User
1111
from .config import settings

app/routers/auth.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from app import oauth2
99
from app.database import User
1010
from app.email import Email
11-
from app.serializers import userEntity
11+
from app.serializers.userSerializers import userEntity
1212
from .. import schemas, utils
1313
from app.oauth2 import AuthJWT
1414
from ..config import settings

app/routers/post.py

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
from datetime import datetime
2+
from fastapi import Depends, HTTPException, status, APIRouter, Response
3+
from pymongo.collection import ReturnDocument
4+
from app import schemas
5+
from app.database import Post
6+
from app.oauth2 import require_user
7+
from app.serializers.postSerializers import postEntity, postListEntity
8+
from bson.objectid import ObjectId
9+
10+
router = APIRouter()
11+
12+
13+
@router.get('/')
14+
def get_posts(limit: int = 10, page: int = 1, search: str = '', user_id: str = Depends(require_user)):
15+
skip = (page - 1) * limit
16+
pipeline = [
17+
{'$match': {}},
18+
{'$lookup': {'from': 'users', 'localField': 'user',
19+
'foreignField': '_id', 'as': 'user'}},
20+
{'$unwind': '$user'},
21+
{
22+
'$skip': skip
23+
}, {
24+
'$limit': limit
25+
}
26+
]
27+
posts = postListEntity(Post.aggregate(pipeline))
28+
return {'status': 'success', 'results': len(posts), 'posts': posts}
29+
30+
31+
@router.post('/', status_code=status.HTTP_201_CREATED)
32+
def create_post(post: schemas.CreatePostSchema, user_id: str = Depends(require_user)):
33+
post.user = ObjectId(user_id)
34+
post.created_at = datetime.utcnow()
35+
post.updated_at = post.created_at
36+
result = Post.insert_one(post.dict())
37+
pipeline = [
38+
{'$match': {'_id': result.inserted_id}},
39+
{'$lookup': {'from': 'users', 'localField': 'user',
40+
'foreignField': '_id', 'as': 'user'}},
41+
{'$unwind': '$user'},
42+
]
43+
new_post = postListEntity(Post.aggregate(pipeline))[0]
44+
return new_post
45+
46+
47+
@router.put('/{id}')
48+
def update_post(id: str, payload: schemas.UpdatePostSchema, user_id: str = Depends(require_user)):
49+
if not ObjectId.is_valid(id):
50+
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND,
51+
detail=f"Invalid id: {id}")
52+
updated_post = Post.find_one_and_update(
53+
{'_id': ObjectId(id)}, {'$set': payload.dict(exclude_none=True)}, return_document=ReturnDocument.AFTER)
54+
if not updated_post:
55+
raise HTTPException(status_code=status.HTTP_200_OK,
56+
detail=f'No post with this id: {id} found')
57+
return postEntity(updated_post)
58+
59+
60+
@router.get('/{id}')
61+
def get_post(id: str, user_id: str = Depends(require_user)):
62+
if not ObjectId.is_valid(id):
63+
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND,
64+
detail=f"Invalid id: {id}")
65+
pipeline = [
66+
{'$match': {'_id': ObjectId(id)}},
67+
{'$lookup': {'from': 'users', 'localField': 'user',
68+
'foreignField': '_id', 'as': 'user'}},
69+
{'$unwind': '$user'},
70+
]
71+
post = postListEntity(Post.aggregate(pipeline))[0]
72+
if not post:
73+
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND,
74+
detail=f"No post with this id: {id} found")
75+
return post
76+
77+
78+
@router.delete('/{id}')
79+
def delete_post(id: str, user_id: str = Depends(require_user)):
80+
if not ObjectId.is_valid(id):
81+
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND,
82+
detail=f"Invalid id: {id}")
83+
post = Post.find_one_and_delete({'_id': ObjectId(id)})
84+
if not post:
85+
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND,
86+
detail=f'No post with this id: {id} found')
87+
return Response(status_code=status.HTTP_204_NO_CONTENT)

app/routers/user.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from fastapi import APIRouter, Depends
22
from bson.objectid import ObjectId
3-
from app.serializers import userResponseEntity
3+
from app.serializers.userSerializers import userResponseEntity
44

55
from app.database import User
66
from .. import schemas, oauth2

app/schemas.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
from datetime import datetime
2+
from typing import List
23
from pydantic import BaseModel, EmailStr, constr
4+
from bson.objectid import ObjectId
35

46

57
class UserBaseSchema(BaseModel):
@@ -33,3 +35,54 @@ class UserResponseSchema(UserBaseSchema):
3335
class UserResponse(BaseModel):
3436
status: str
3537
user: UserResponseSchema
38+
39+
40+
class FilteredUserResponse(UserBaseSchema):
41+
id: str
42+
43+
44+
class PostBaseSchema(BaseModel):
45+
title: str
46+
content: str
47+
category: str
48+
image: str
49+
created_at: datetime | None = None
50+
updated_at: datetime | None = None
51+
52+
class Config:
53+
orm_mode = True
54+
allow_population_by_field_name = True
55+
arbitrary_types_allowed = True
56+
json_encoders = {ObjectId: str}
57+
58+
59+
class CreatePostSchema(PostBaseSchema):
60+
user: ObjectId | None = None
61+
pass
62+
63+
64+
class PostResponse(PostBaseSchema):
65+
id: str
66+
user: FilteredUserResponse
67+
created_at: datetime
68+
updated_at: datetime
69+
70+
71+
class UpdatePostSchema(BaseModel):
72+
title: str | None = None
73+
content: str | None = None
74+
category: str | None = None
75+
image: str | None = None
76+
user: str | None = None
77+
78+
class Config:
79+
orm_mode = True
80+
allow_population_by_field_name = True
81+
arbitrary_types_allowed = True
82+
json_encoders = {ObjectId: str}
83+
84+
85+
class ListPostResponse(BaseModel):
86+
status: str
87+
results: int
88+
posts: List[PostResponse]

app/serializers/postSerializers.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
from app.serializers.userSerializers import embeddedUserResponse
2+
3+
4+
def postEntity(post) -> dict:
5+
return {
6+
"id": str(post["_id"]),
7+
"title": post["title"],
8+
"category": post["category"],
9+
"content": post["content"],
10+
"image": post["image"],
11+
"user": str(post["user"]),
12+
"created_at": post["created_at"],
13+
"updated_at": post["updated_at"]
14+
}
15+
16+
17+
def populatedPostEntity(post) -> dict:
18+
return {
19+
"id": str(post["_id"]),
20+
"title": post["title"],
21+
"category": post["category"],
22+
"content": post["content"],
23+
"image": post["image"],
24+
"user": embeddedUserResponse(post["user"]),
25+
"created_at": post["created_at"],
26+
"updated_at": post["updated_at"]
27+
}
28+
29+
30+
def postListEntity(posts) -> list:
31+
return [populatedPostEntity(post) for post in posts]

app/serializers.py renamed to app/serializers/userSerializers.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,14 @@ def userResponseEntity(user) -> dict:
2424
}
2525

2626

27+
def embeddedUserResponse(user) -> dict:
28+
return {
29+
"id": str(user["_id"]),
30+
"name": user["name"],
31+
"email": user["email"],
32+
"photo": user["photo"]
33+
}
34+
35+
2736
def userListEntity(users) -> list:
2837
return [userEntity(user) for user in users]

0 commit comments

Comments
 (0)