From e620d5c19eed3e4c09a579863a119b17f7e98b3e Mon Sep 17 00:00:00 2001 From: Esa Kataja Date: Sat, 9 Nov 2024 16:25:05 +0200 Subject: [PATCH] Add User login route --- src/models/user.py | 59 +++++++++++++++++++++++++++++++++++++--------- src/routes/user.py | 50 ++++++++++++++++++++++++++++++++------- 2 files changed, 90 insertions(+), 19 deletions(-) diff --git a/src/models/user.py b/src/models/user.py index a7de8c9..3846424 100644 --- a/src/models/user.py +++ b/src/models/user.py @@ -1,15 +1,52 @@ -from sqlmodel import Field, SQLModel +from pydantic import BaseModel, Field from datetime import datetime from typing import Optional +from lib import settings -class User(SQLModel, table=True): - id: Optional[int] = Field(default=None, primary_key=True) - name: str - email: str = Field(unique=True) - password: str - avatar_url: str - is_active: bool = Field(default=True) - is_admin: bool = Field(default=False) - created_at: datetime = Field(default_factory=datetime.utcnow) - updated_at: datetime = Field(default_factory=datetime.utcnow) + +class UserCredentials(BaseModel): + username: str = Field(description="Username of the user", examples=["admin"]) + password: str = Field(description="Password of the user", examples=["password"]) + + +class UserBase(BaseModel): + id: Optional[str] = Field( + default=None, + description="UUID representation of the user", + examples=["123e4567-e89b-12d3-a456-426655440000"], + ) + username: str = Field(description="Username of the user", examples=["Palli-Pate"]) + email: Optional[str] = Field( + default=None, description="Email of the user", examples=["pate@example.com"] + ) + avatar_url: str = Field( + default=settings.default_avatar_url, description="Avatar URL of the user" + ) + is_active: bool = Field( + default=True, description="Whether the user is active or not" + ) + is_admin: bool = Field( + default=False, description="Whether the user is an admin or not" + ) + last_login: Optional[datetime] = Field( + default=None, + description="Last login date of the user", + examples=[datetime.utcnow()], + ) + created_at: datetime = Field( + default_factory=datetime.utcnow, description="Creation date of the user" + ) + modified_at: datetime = Field( + default_factory=datetime.utcnow, description="Modification date of the user" + ) + + +class UserIn(UserBase): + password: Optional[str] = Field( + default=None, description="Password of the user", examples=["password"] + ) + + +class UserOut(UserBase): + pass diff --git a/src/routes/user.py b/src/routes/user.py index e924943..d48fc50 100644 --- a/src/routes/user.py +++ b/src/routes/user.py @@ -1,6 +1,10 @@ -from fastapi import APIRouter +from fastapi import APIRouter, Response +import bcrypt + +from models.user import UserIn, UserOut, UserCredentials +from models.general import Message +from lib.database import get_all_users, get_user_by_username -from models import User router = APIRouter( prefix="/user", @@ -9,11 +13,41 @@ router = APIRouter( ) -@router.get("/") -async def get_users(): - return {"users": ["user1", "user2", "user3"]} +@router.get("/", response_model=list[UserOut]) +async def get_users() -> list[UserOut]: + """Get all users""" + return get_all_users() -@router.post("/login") -async def login(): - return {"token": "token"} +@router.post( + "/login", + responses={ + 200: {"model": UserOut, "description": "Login successful"}, + 400: {"model": Message}, + 404: {"model": Message}, + }, +) +async def login(user: UserCredentials): + """Login a user""" + user_from_db: UserIn = get_user_by_username(user.username) + if user_from_db is None: + return Response( + status_code=404, + media_type="application/json", + content=Message( + message="User not found", message_type="error" + ).model_dump_json(), + ) + + password = user.password.encode("utf-8") + + if bcrypt.checkpw(password, user_from_db.password.encode("utf-8")): + return UserOut(**user_from_db.model_dump()) + + return Response( + status_code=400, + media_type="application/json", + content=Message( + message="Invalid password", message_type="error" + ).model_dump_json(), + )