diff --git a/app/dto/UserDTO.py b/app/dto/UserDTO.py new file mode 100644 index 0000000..0a705a3 --- /dev/null +++ b/app/dto/UserDTO.py @@ -0,0 +1,5 @@ +from pydantic import BaseModel + +class UserDTO(BaseModel): + uid: str + username: str \ No newline at end of file diff --git a/app/dto/__init__.py b/app/dto/__init__.py index b8e753a..a6f30f0 100644 --- a/app/dto/__init__.py +++ b/app/dto/__init__.py @@ -1,3 +1,4 @@ from .UserRegisterDTO import UserRegisterDTO from .FriendAddDTO import FriendAddDTO -from .FriendListDTO import FriendListDTO \ No newline at end of file +from .FriendListDTO import FriendListDTO +from .UserDTO import UserDTO \ No newline at end of file diff --git a/app/main.py b/app/main.py index 8c60859..807cd7a 100644 --- a/app/main.py +++ b/app/main.py @@ -1,8 +1,8 @@ from fastapi import FastAPI, Depends, HTTPException, status +from fastapi.responses import JSONResponse from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm from typing import Optional from datetime import datetime, timedelta -from pymongo import MongoClient from jose import JWTError, jwt from bson.objectid import ObjectId from app.utils import get_password_hash, verify_password @@ -20,8 +20,10 @@ from app.dto import * # Contains all constants from app.config import * +import pymongo + # Database setup -client = MongoClient(MONGODB_URL, username=MONGODB_USERNAME, password=MONGODB_PASSWORD) +client = pymongo.MongoClient(MONGODB_URL, username=MONGODB_USERNAME, password=MONGODB_PASSWORD) db = client[MONGODB_DATABASE] # FastAPI app instance @@ -73,7 +75,11 @@ async def get_current_user(token: str = Depends(oauth2_scheme)) -> User: # Routes - TODO: find workaround to display 401/409/... HTTP error codes in openapi.json -@app.post("/register", response_model=Token) +@app.post( + path="/register", + response_model=Token, + responses={409: {"model": Message}} +) async def register(user: UserRegisterDTO): user_exists = users_collection.find_one({"username": user.username}) if user_exists: @@ -90,7 +96,11 @@ async def register(user: UserRegisterDTO): return {"access_token": access_token, "token_type": "bearer", "user_id": str(user["_id"])} -@app.post("/login", response_model=Token) +@app.post( + path="/login", + response_model=Token, + responses={401: {"model": Message}} +) async def login(form_data: OAuth2PasswordRequestForm = Depends()): user = users_collection.find_one({"username": form_data.username}) if not user or not verify_password(form_data.password, user["password"]): @@ -105,12 +115,18 @@ async def login(form_data: OAuth2PasswordRequestForm = Depends()): return {"access_token": access_token, "token_type": "bearer", "user_id": str(user["_id"])} -@app.get("/logout") +@app.get( + path="/logout", + responses={401: {"model": Message}} +) async def logout(current_user: User = Depends(get_current_user)): # TODO: find usecase / what to do ?? return {"message": "Logged out"} -@app.get("/pin/{id}") +@app.get( + path="/pin/{id}", + responses={401: {"model": Message}, 404: {"model": Message}} +) async def get_pin(id: str, current_user: User = Depends(get_current_user)): pin = pins_collection.find_one({"_id": ObjectId(id)}) if pin is None: @@ -118,7 +134,10 @@ async def get_pin(id: str, current_user: User = Depends(get_current_user)): return pin -@app.patch("/pin/{id}") +@app.patch( + path="/pin/{id}", + responses={401: {"model": Message}, 404: {"model": Message}} +) async def update_pin(id: str, pin: Pin, current_user: User = Depends(get_current_user)): result = pins_collection.update_one({"_id": ObjectId(id)}, {"$set": pin.model_dump()}) if result.matched_count == 0: @@ -126,17 +145,26 @@ async def update_pin(id: str, pin: Pin, current_user: User = Depends(get_current return {"message": "Pin updated"} -@app.post("/pin/add") +@app.post( + path="/pin/add", + responses={401: {"model": Message}} +) async def add_pin(pin: Pin, current_user: User = Depends(get_current_user)): pin_id = pins_collection.insert_one(pin.model_dump()).inserted_id return {"id": str(pin_id)} -@app.get("/pins") +@app.get( + path="/pins", + responses={401: {"model": Message}} +) async def list_pins(current_user: User = Depends(get_current_user)): pins = pins_serialize(pins_collection.find().to_list()) return pins -@app.get("/friend/{id}") +@app.get( + path="/friend/{id}", + responses={401: {"model": Message}, 404: {"model": Message}} +) async def get_friend(id: str, current_user: User = Depends(get_current_user)): friend = friends_collection.find_one({"_id": ObjectId(id)}) if friend is None: @@ -144,7 +172,10 @@ async def get_friend(id: str, current_user: User = Depends(get_current_user)): return friend -@app.post("/friend/add") +@app.post( + path="/friend/add", + responses={401: {"model": Message}, 409: {"model": Message}} +) async def add_friend(friendAdd: FriendAddDTO, current_user: User = Depends(get_current_user)): # TODO: test if exists friend: Friend = friendAdd.model_dump() @@ -161,7 +192,10 @@ async def add_friend(friendAdd: FriendAddDTO, current_user: User = Depends(get_c friend_id = friends_collection.insert_one(friend).inserted_id return {"id": str(friend_id)} -@app.delete("/friend/{id}/delete") +@app.delete( + path="/friend/{id}/delete", + responses={401: {"model": Message}, 404: {"model": Message}} +) async def delete_friend(id: str, current_user: User = Depends(get_current_user)): result = friends_collection.delete_one({"_id": ObjectId(id)}) if result.deleted_count == 0: @@ -169,7 +203,10 @@ async def delete_friend(id: str, current_user: User = Depends(get_current_user)) return {"message": "Friend deleted"} -@app.patch("/friend/{id}/accept") +@app.patch( + path="/friend/{id}/accept", + responses={401: {"model": Message}, 404: {"model": Message}} +) async def accept_friend(id: str, current_user: User = Depends(get_current_user)): result = friends_collection.update_one({"_id": ObjectId(id)}, {"$set": {"status": "accepted"}}) if result.matched_count == 0: @@ -177,7 +214,10 @@ async def accept_friend(id: str, current_user: User = Depends(get_current_user)) return {"message": "Friend request accepted"} -@app.post("/friend/{id}/deny") +@app.post( + path="/friend/{id}/deny", + responses={401: {"model": Message}, 404: {"model": Message}} +) async def deny_friend(id: str, current_user: User = Depends(get_current_user)): result = friends_collection.update_one({"_id": ObjectId(id)}, {"$set": {"status": "denied"}}) if result.matched_count == 0: @@ -185,12 +225,23 @@ async def deny_friend(id: str, current_user: User = Depends(get_current_user)): return {"message": "Friend request denied"} -@app.get("/friends", response_model=FriendListDTO) +@app.get( + path="/friends", + response_model=FriendListDTO, + responses={401: {"model": Message}} +) async def list_friends(current_user: User = Depends(get_current_user)): return FriendListDTO(**friends_serialize(friends_collection.find({"user_id": current_user.uid}).to_list(), friends_collection.find({"friend_user_id": current_user.uid}))) -@app.get("/users") +@app.get( + path="/users", + responses={401: {"model": Message}, 422: {"model": Message}}, + response_model=list[UserDTO] +) async def search_users(name: str, current_user: User = Depends(get_current_user)): - # TODO: /!\ pymongo.errors.OperationFailure if regex is poop - users = users_serialize(users_collection.find({"username": {"$regex": name, "$options": "i"}}).to_list()) + try: + users = users_serialize(users_collection.find({"username": {"$regex": name, "$options": "i"}}).to_list()) + except pymongo.errors.OperationFailure: + raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail="Regex may be wrongly formatted") + return users \ No newline at end of file diff --git a/app/models/__init__.py b/app/models/__init__.py index 957a693..eae06aa 100644 --- a/app/models/__init__.py +++ b/app/models/__init__.py @@ -2,4 +2,5 @@ from .friend import Friend from .pin import Pin from .token_data import TokenData from .token import Token -from .user import User \ No newline at end of file +from .user import User +from .message import Message \ No newline at end of file diff --git a/app/models/message.py b/app/models/message.py new file mode 100644 index 0000000..e9c82e1 --- /dev/null +++ b/app/models/message.py @@ -0,0 +1,5 @@ +from pydantic import BaseModel + + +class Message(BaseModel): + detail: str diff --git a/app/serializers/user_serializer.py b/app/serializers/user_serializer.py index e1b01ee..1a29231 100644 --- a/app/serializers/user_serializer.py +++ b/app/serializers/user_serializer.py @@ -1,11 +1,12 @@ +from app.dto.UserDTO import UserDTO from app.models.user import User -def users_serialize(users: list) -> list: +def users_serialize(users: list) -> list[UserDTO]: serialized_users: list = [] for user in users: serialized_users.append({ - "id": str(user["_id"]), + "uid": str(user["_id"]), "username": user["username"] }) return serialized_users