🎨 Moved routes into separated files (!!!)
continuous-integration/drone/push Build is passing
Details
continuous-integration/drone/push Build is passing
Details
parent
cd926b6487
commit
adc09564f4
@ -0,0 +1,69 @@
|
||||
from datetime import timedelta
|
||||
|
||||
from fastapi.security import OAuth2PasswordRequestForm
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
import pymongo
|
||||
|
||||
import app.config as config
|
||||
from app.models import User, Token, HTTPError
|
||||
from app.dto import UserRegisterDTO
|
||||
from app.serializers import user_serialize
|
||||
from app.utils import get_current_user, create_access_token, get_password_hash, verify_password
|
||||
|
||||
# Database setup
|
||||
client = pymongo.MongoClient(config.MONGODB_URL, username=config.MONGODB_USERNAME, password=config.MONGODB_PASSWORD)
|
||||
db = client[config.MONGODB_DATABASE]
|
||||
|
||||
users_collection = db["users"]
|
||||
|
||||
auth_router = APIRouter(
|
||||
tags=["Auth"]
|
||||
)
|
||||
|
||||
@auth_router.post(
|
||||
path="/register",
|
||||
response_model=Token,
|
||||
responses={409: {"model": HTTPError}}
|
||||
)
|
||||
async def register(user: UserRegisterDTO):
|
||||
user_exists = users_collection.find_one({"username": user.username})
|
||||
if user_exists:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_409_CONFLICT,
|
||||
detail="Username already used"
|
||||
)
|
||||
|
||||
hashed_password = get_password_hash(user.password)
|
||||
user_id = users_collection.insert_one({"username": user.username, "password": hashed_password})
|
||||
|
||||
access_token_expires = timedelta(minutes=config.ACCESS_TOKEN_EXPIRE_MINUTES)
|
||||
access_token = create_access_token(data={"sub": user.username}, expires_delta=access_token_expires)
|
||||
|
||||
return {"access_token": access_token, "token_type": "bearer", "user_id": str(user_id.inserted_id)}
|
||||
|
||||
@auth_router.post(
|
||||
path="/login",
|
||||
response_model=Token,
|
||||
responses={401: {"model": HTTPError}}
|
||||
)
|
||||
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"]):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Incorrect username or password",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
||||
access_token_expires = timedelta(minutes=config.ACCESS_TOKEN_EXPIRE_MINUTES)
|
||||
access_token = create_access_token(data={"sub": form_data.username}, expires_delta=access_token_expires)
|
||||
|
||||
return {"access_token": access_token, "token_type": "bearer", "user_id": str(user["_id"])}
|
||||
|
||||
|
||||
@auth_router.get(
|
||||
path="/logout",
|
||||
responses={401: {"model": HTTPError}}
|
||||
)
|
||||
async def logout(current_user: User = Depends(get_current_user)):
|
||||
return {"message": "Logged out"}
|
@ -0,0 +1,116 @@
|
||||
|
||||
from bson import ObjectId
|
||||
import bson
|
||||
from fastapi import APIRouter, HTTPException, status
|
||||
from fastapi.params import Depends
|
||||
import pymongo
|
||||
|
||||
from app.dto import FriendAddDTO
|
||||
from app.models import HTTPError, User, Friend
|
||||
from app.utils import friend_not_found, get_current_user, objectid_misformatted
|
||||
import app.config as config
|
||||
|
||||
# Best workaround found for _id typed as ObjectId (creating Exception bcause JSON doesn't support custom types countrary to BSON, used by Mongo)
|
||||
# also allows to create DTOs at the time, but not at it's best (project structure is chaotic FTM :s)
|
||||
import app.serializers as serializers # Import all serializers (detailed in __init__.py)
|
||||
|
||||
# Database setup
|
||||
client = pymongo.MongoClient(config.MONGODB_URL, username=config.MONGODB_USERNAME, password=config.MONGODB_PASSWORD)
|
||||
db = client[config.MONGODB_DATABASE]
|
||||
|
||||
friends_collection = db["friends"]
|
||||
|
||||
friends_router = APIRouter(
|
||||
prefix="/friend",
|
||||
tags=["Friends"]
|
||||
)
|
||||
|
||||
@friends_router.get(
|
||||
path="/{id}",
|
||||
responses={401: {"model": HTTPError}, 404: {"model": HTTPError}, 422: {"model": HTTPError}}
|
||||
)
|
||||
async def get_friend(id: str, current_user: User = Depends(get_current_user)):
|
||||
try:
|
||||
friend = friends_collection.find_one({"_id": ObjectId(id)})
|
||||
except bson.errors.InvalidId:
|
||||
objectid_misformatted()
|
||||
|
||||
if friend is None: friend_not_found()
|
||||
|
||||
return serializers.friend_serialize(friend,False if friend['user_id']==current_user.uid else True)
|
||||
|
||||
@friends_router.post(
|
||||
path="/add",
|
||||
responses={401: {"model": HTTPError}, 409: {"model": HTTPError}}
|
||||
)
|
||||
async def add_friend(friend_to_add: FriendAddDTO, current_user: User = Depends(get_current_user)):
|
||||
# TODO: test if exists
|
||||
friend: Friend = friend_to_add.model_dump()
|
||||
|
||||
if(current_user.uid == friend["friend_user_id"]):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_409_CONFLICT,
|
||||
detail="Cannot add yourself as a friend"
|
||||
)
|
||||
|
||||
friend["user_id"] = current_user.uid
|
||||
friend["status"] = "pending"
|
||||
friend_id = friends_collection.insert_one(friend).inserted_id
|
||||
return {"id": str(friend_id)}
|
||||
|
||||
@friends_router.delete(
|
||||
path="/{id}/delete",
|
||||
responses={401: {"model": HTTPError}, 404: {"model": HTTPError}, 422: {"model": HTTPError}}
|
||||
)
|
||||
async def delete_friend(id: str, current_user: User = Depends(get_current_user)):
|
||||
try:
|
||||
result = friends_collection.delete_one({"_id": ObjectId(id)})
|
||||
except bson.errors.InvalidId:
|
||||
objectid_misformatted()
|
||||
|
||||
if result.deleted_count == 0: friend_not_found()
|
||||
|
||||
return {"message": "Friend deleted"}
|
||||
|
||||
@friends_router.patch(
|
||||
path="/{id}/accept",
|
||||
responses={401: {"model": HTTPError}, 404: {"model": HTTPError}, 422: {"model": HTTPError}}
|
||||
)
|
||||
async def accept_friend(id: str, current_user: User = Depends(get_current_user)):
|
||||
try:
|
||||
check_friend = friends_collection.find_one({"_id": ObjectId(id)})
|
||||
if check_friend is None: friend_not_found()
|
||||
|
||||
if check_friend["status"] != "pending":
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
||||
detail="Friend request already accepted"
|
||||
)
|
||||
|
||||
friends_collection.update_one({"_id": ObjectId(id)}, {"$set": {"status": "accepted"}})
|
||||
except bson.errors.InvalidId:
|
||||
objectid_misformatted()
|
||||
|
||||
return {"message": "Friend request accepted"}
|
||||
|
||||
@friends_router.post(
|
||||
path="/{id}/deny",
|
||||
responses={401: {"model": HTTPError}, 404: {"model": HTTPError}, 422: {"model": HTTPError}}
|
||||
)
|
||||
async def deny_friend(id: str, current_user: User = Depends(get_current_user)):
|
||||
try:
|
||||
result = friends_collection.update_one({"_id": ObjectId(id)}, {"$set": {"status": "denied"}})
|
||||
except bson.errors.InvalidId:
|
||||
objectid_misformatted()
|
||||
|
||||
if result.matched_count == 0: friend_not_found()
|
||||
|
||||
return {"message": "Friend request denied"}
|
||||
|
||||
@friends_router.get(
|
||||
path="s",
|
||||
response_model=list[Friend],
|
||||
responses={401: {"model": HTTPError}}
|
||||
)
|
||||
async def list_friends(current_user: User = Depends(get_current_user)):
|
||||
return serializers.friends_serialize(friends_collection.find({"user_id": current_user.uid}).to_list(), friends_collection.find({"friend_user_id": current_user.uid}).to_list())
|
@ -0,0 +1,73 @@
|
||||
|
||||
from bson import ObjectId
|
||||
import bson
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from fastapi.params import Depends
|
||||
import pymongo
|
||||
from app.dto import PinDTO
|
||||
from app.models import HTTPError
|
||||
from app.models.user import User
|
||||
from app.utils import get_current_user, objectid_misformatted
|
||||
import app.config as config
|
||||
|
||||
# Best workaround found for _id typed as ObjectId (creating Exception bcause JSON doesn't support custom types countrary to BSON, used by Mongo)
|
||||
# also allows to create DTOs at the time, but not at it's best (project structure is chaotic FTM :s)
|
||||
import app.serializers as serializers # Import all serializers (detailed in __init__.py)
|
||||
|
||||
# Database setup
|
||||
client = pymongo.MongoClient(config.MONGODB_URL, username=config.MONGODB_USERNAME, password=config.MONGODB_PASSWORD)
|
||||
db = client[config.MONGODB_DATABASE]
|
||||
|
||||
pins_collection = db["pins"]
|
||||
|
||||
pins_router = APIRouter(
|
||||
prefix="/pin",
|
||||
tags=["Pins"]
|
||||
)
|
||||
|
||||
@pins_router.get(
|
||||
path="/{id}",
|
||||
responses={401: {"model": HTTPError}, 404: {"model": HTTPError}, 422: {"model": HTTPError}}
|
||||
)
|
||||
async def get_pin(id: str, current_user: User = Depends(get_current_user)):
|
||||
try:
|
||||
pin = pins_collection.find_one({"_id": ObjectId(id)})
|
||||
except bson.errors.InvalidId:
|
||||
objectid_misformatted()
|
||||
|
||||
if pin is None:
|
||||
raise HTTPException(status_code=404, detail="Pin not found")
|
||||
|
||||
return serializers.pin_serialize(pin)
|
||||
|
||||
@pins_router.patch(
|
||||
path="/{id}",
|
||||
responses={401: {"model": HTTPError}, 404: {"model": HTTPError}, 422: {"model": HTTPError}}
|
||||
)
|
||||
async def update_pin(id: str, pin: PinDTO, current_user: User = Depends(get_current_user)):
|
||||
try:
|
||||
result = pins_collection.update_one({"_id": ObjectId(id)}, {"$set": pin.model_dump()})
|
||||
except bson.errors.InvalidId:
|
||||
objectid_misformatted()
|
||||
|
||||
if result.matched_count == 0:
|
||||
raise HTTPException(status_code=404, detail="Pin not found")
|
||||
|
||||
return {"message": "Pin updated"}
|
||||
|
||||
@pins_router.post(
|
||||
path="/add",
|
||||
responses={401: {"model": HTTPError}}
|
||||
)
|
||||
async def add_pin(pin: PinDTO, current_user: User = Depends(get_current_user)):
|
||||
pin.user_id = current_user.uid
|
||||
pin_id = pins_collection.insert_one(pin.model_dump()).inserted_id
|
||||
return {"id": str(pin_id)}
|
||||
|
||||
@pins_router.get(
|
||||
path="s",
|
||||
responses={401: {"model": HTTPError}}
|
||||
)
|
||||
async def list_pins(current_user: User = Depends(get_current_user)):
|
||||
pins = serializers.pins_serialize(pins_collection.find().to_list(), current_user.uid)
|
||||
return pins
|
@ -0,0 +1,56 @@
|
||||
|
||||
|
||||
import bson
|
||||
from bson import ObjectId
|
||||
from fastapi import APIRouter, HTTPException, status
|
||||
from fastapi.params import Depends
|
||||
import pymongo
|
||||
|
||||
from app.dto import UserDTO
|
||||
from app.models import HTTPError, User
|
||||
from app.utils import get_current_user, objectid_misformatted
|
||||
import app.config as config
|
||||
|
||||
# Database setup
|
||||
client = pymongo.MongoClient(config.MONGODB_URL, username=config.MONGODB_USERNAME, password=config.MONGODB_PASSWORD)
|
||||
db = client[config.MONGODB_DATABASE]
|
||||
|
||||
users_collection = db["users"]
|
||||
|
||||
# Best workaround found for _id typed as ObjectId (creating Exception bcause JSON doesn't support custom types countrary to BSON, used by Mongo)
|
||||
# also allows to create DTOs at the time, but not at it's best (project structure is chaotic FTM :s)
|
||||
import app.serializers as serializers # Import all serializers (detailed in __init__.py)
|
||||
|
||||
users_router = APIRouter(
|
||||
prefix="/user",
|
||||
tags=["Users"]
|
||||
)
|
||||
|
||||
@users_router.get(
|
||||
path="s",
|
||||
responses={401: {"model": HTTPError}, 422: {"model": HTTPError}},
|
||||
response_model=list[UserDTO]
|
||||
)
|
||||
async def search_users(name: str, current_user: User = Depends(get_current_user)):
|
||||
try:
|
||||
users = serializers.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
|
||||
|
||||
@users_router.get(
|
||||
path="/{id}",
|
||||
responses={401: {"model": HTTPError}, 422: {"model": HTTPError}, 404: {"model": HTTPError}},
|
||||
response_model=UserDTO
|
||||
)
|
||||
async def get_user(id: str, current_user: User = Depends(get_current_user)):
|
||||
try:
|
||||
user = users_collection.find_one({"_id": ObjectId(id)})
|
||||
except bson.errors.InvalidId:
|
||||
objectid_misformatted()
|
||||
|
||||
if user is None:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
|
||||
return serializers.user_serialize(user)
|
@ -1,7 +1,74 @@
|
||||
import hashlib
|
||||
from fastapi import Depends, HTTPException, status
|
||||
from fastapi.security import OAuth2PasswordBearer
|
||||
from joserfc import jwt
|
||||
from joserfc.errors import JoseError
|
||||
from joserfc.jwk import OctKey
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import pymongo
|
||||
from app.models import User
|
||||
import app.config as config
|
||||
from app.models.token_data import TokenData
|
||||
from app.serializers import user_serialize
|
||||
|
||||
# Database setup
|
||||
client = pymongo.MongoClient(config.MONGODB_URL, username=config.MONGODB_USERNAME, password=config.MONGODB_PASSWORD)
|
||||
db = client[config.MONGODB_DATABASE]
|
||||
|
||||
users_collection = db["users"]
|
||||
|
||||
def verify_password(plain_password, hashed_password):
|
||||
return hashlib.sha256(plain_password.encode()).hexdigest() == hashed_password
|
||||
|
||||
def get_password_hash(password):
|
||||
return hashlib.sha256(password.encode()).hexdigest()
|
||||
|
||||
oauth2_scheme = OAuth2PasswordBearer(tokenUrl=config.TOKEN_URL)
|
||||
|
||||
async def get_current_user(token: str = Depends(oauth2_scheme)) -> User:
|
||||
credentials_exception = HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Could not validate credentials",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
||||
try:
|
||||
payload = jwt.decode(token, OctKey.import_key(config.SECRET_KEY))
|
||||
username: str = payload.claims["sub"]
|
||||
expire_date = payload.claims["exp"]
|
||||
if username is None or int(datetime.now().timestamp()) > expire_date:
|
||||
raise credentials_exception
|
||||
token_data = TokenData(username=username)
|
||||
except JoseError:
|
||||
raise credentials_exception
|
||||
|
||||
user = users_collection.find_one({"username": token_data.username})
|
||||
if user is None:
|
||||
raise credentials_exception
|
||||
|
||||
return user_serialize(user)
|
||||
|
||||
def create_access_token(data: dict, expires_delta: timedelta):
|
||||
to_encode = data.copy()
|
||||
|
||||
expire = datetime.now() + expires_delta
|
||||
|
||||
to_encode.update({"exp": expire})
|
||||
header = {"alg": config.ALGORITHM}
|
||||
encoded_jwt = jwt.encode(header, to_encode, OctKey.import_key(config.SECRET_KEY))
|
||||
|
||||
return encoded_jwt
|
||||
|
||||
# Exceptions
|
||||
def friend_not_found():
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Friend not found"
|
||||
)
|
||||
|
||||
def objectid_misformatted():
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
||||
detail="The ObjectID is misformatted"
|
||||
)
|
Loading…
Reference in new issue