From 1d3d74d9d62b439fca4d457da8b34b9ab9ebe25a Mon Sep 17 00:00:00 2001 From: osbm Date: Mon, 5 May 2025 20:38:37 +0300 Subject: [PATCH] add updates to show bedir --- auth/models.py | 146 +++++++----------------------------------------- auth/router.py | 86 ++++++++++++++-------------- auth/schemas.py | 23 ++++++++ config.py | 35 ++++++------ items/models.py | 25 +++++---- items/router.py | 13 ++--- 6 files changed, 118 insertions(+), 210 deletions(-) create mode 100644 auth/schemas.py diff --git a/auth/models.py b/auth/models.py index 7a3dd73..41cb69f 100644 --- a/auth/models.py +++ b/auth/models.py @@ -1,135 +1,27 @@ -from enum import Enum -from backend.config import SECRET_KEY, ALGORITHM, ACCESS_TOKEN_EXPIRE_MINUTES -from backend.config import pwd_context, get_session_db -from datetime import datetime, timedelta, timezone -from pydantic import BaseModel -from fastapi import Depends, HTTPException -from typing import Annotated, Optional -from fastapi.security import OAuth2PasswordBearer -from passlib.context import CryptContext -import jwt -from sqlmodel import SQLModel, Field, Session, select -from pydantic.networks import EmailStr +from sqlalchemy import Column, Integer, String, Enum, DateTime +import enum, datetime -class Token(BaseModel): - access_token: str - token_type: str +from ..config import Base - -### ENUMS ### -class Role(str, Enum): - user = "user" +class Role(str, enum.Enum): admin = "admin" - guest = "guest" + user = "user" mod = "mod" -class Status(str, Enum): - active = "active" +class Status(str, enum.Enum): banned = "banned" + active = "active" suspended = "suspended" -### KULLANICI MODELLERİ ### -class UserBase(SQLModel): - username: Optional[str] = None - user_id: Optional[int] = None - role: Optional[Role] = None - status: Optional[Status] = None - -class UserInDb(UserBase): - hashed_password: str | None = None - -class UserPublic(UserBase): - pass - -class UserCreate(BaseModel): - username: Optional[str] = None - role: Optional[Role] = None - email : EmailStr | None = None - status: Optional[Status] = None - password : str | None = None - -### VERİTABANI MODELİ ### -class DBUser(SQLModel, table=True): - __tablename__ = "users" # opsiyonel, sqlmodel bunu otomatik de atar - user_id: Optional[int] = Field(default=None, primary_key=True) - username: str = Field(index=True, nullable=False) - hashed_password: str = Field(nullable=False) - role: Role = Field(default=Role.user) - status: Status = Field(default=Status.active) - - -### AUTH ### -oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/auth/login") - - -def verify_password(plain_password: str, hashed_password: str) -> bool: - return pwd_context.verify(plain_password, hashed_password) - -def get_password_hash(password: str) -> str: - return pwd_context.hash(password) - -def authenticate_user( - session: Annotated[Session, Depends(get_session_db)], - username: str, - password: str - ) -> UserInDb | None: - - statement = select(DBUser).where(DBUser.username == username) - result = session.exec(statement).first() - if not result or not verify_password(password, result.hashed_password): - return None - return result - - -def create_access_token( - data: dict, - expires_delta: Optional[timedelta] = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES), -) -> str: - to_encode = data.copy() - expire = datetime.now(timezone.utc) + expires_delta - to_encode.update({"exp": expire}) - encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm="HS256") - return encoded_jwt - - -async def get_user( - session: Annotated[Session, Depends(get_session_db)], - username: str - ) -> UserInDb | None: - - statement = select(DBUser).where(DBUser.username == username) - result = session.exec(statement).first() - return result - - -async def get_current_user( - token: Annotated[str, Depends(oauth2_scheme)], - session: Annotated[Session, Depends(get_session_db)] -) -> UserPublic: - - credentials_exception = HTTPException( - status_code=401, - detail="Invalid credentials", - headers={"WWW-Authenticate": "Bearer"}, - ) - try: - payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"]) - username: Optional[str] = payload.get("sub") - if username is None: - raise credentials_exception - except jwt.PyJWTError: - raise credentials_exception - - user = await get_user(session, username) - if user is None: - raise credentials_exception - return user - - -async def get_current_active_user( - current_user: Annotated[UserInDb, Depends(get_current_user)] -) -> UserPublic: - - if current_user.status == Status.banned: - raise HTTPException(status_code=400, detail="Inactive user") - return current_user +class User(Base): + __tablename__ = "users" + user_id = Column(Integer, primary_key=True) + username = Column(String, unique=True) + name = Column(String) + surname = Column(String) + hashedPassword = Column(String) + email = Column(String, unique=True) + role = Column(Enum(Role), default=Role.user) + status = Column(Enum(Status), default=Status.active) + bio = Column(String(144)) + created_date = Column(DateTime, default=datetime.datetime.utcnow) diff --git a/auth/router.py b/auth/router.py index 0b8517e..92affa8 100644 --- a/auth/router.py +++ b/auth/router.py @@ -1,15 +1,14 @@ -from fastapi import APIRouter, Depends, HTTPException, status -from .models import Token, UserPublic -from .models import authenticate_user, create_access_token -from datetime import timedelta -from ..auth.models import get_password_hash, verify_password -from typing import Annotated -from sqlmodel import Session -from ..config import get_session_db -from fastapi import Depends -from fastapi.security import OAuth2PasswordRequestForm -from .models import UserCreate, DBUser +import os +from fastapi import APIRouter, HTTPException +import bcrypt +import jwt + +from fastapi import Depends +from sqlalchemy.orm import Session +from .models import User +from .schemas import UserCreate, UserOut, UserLogin +from ..config import get_db router = APIRouter( prefix="/auth", @@ -18,41 +17,40 @@ router = APIRouter( dependencies=[], ) -@router.post('/login') -async def login_for_access_token( - form_data : Annotated[OAuth2PasswordRequestForm, Depends()], - session : Annotated[Session, Depends(get_session_db)], -) -> Token: +def create_token(user: User): + return jwt.encode({"sub": user.username}, os.getenv("SECRET_KEY"), algorithm=os.getenv("ALGORITHM")) - user = authenticate_user(session, form_data.username, form_data.password) - if not user: - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="Incorrect username or password", - headers={"WWW-Authenticate": "Bearer"}, - ) - access_token_expires = timedelta(minutes=30) - access_token = create_access_token( - data={"sub": user.username, "role": user.role, 'status': user.status}, expires_delta=access_token_expires - ) - return Token(access_token=access_token, token_type="bearer") +def verify_token(token: str): + try: + data = jwt.decode(token, os.getenv("SECRET_KEY"), algorithms=[os.getenv("ALGORITHM")]) + return data.get("sub") + except jwt.ExpiredSignatureError: + raise HTTPException(401, "Token expired") + except jwt.InvalidTokenError: + raise HTTPException(401, "Invalid token") -@router.post('/register', response_model=UserPublic) -async def create_user( - session : Annotated[Session, Depends(get_session_db)], - user : Annotated[UserCreate, Depends()] -): - user_dict = user.dict() - print(user.password) - user_dict['hashed_password'] = get_password_hash(user.password) - print (user_dict['hashed_password']) +@router.post("/register") +def register(user: UserCreate, db: Session = Depends(get_db)): + if db.query(User).filter_by(username=user.username).first(): + raise HTTPException(400, "Username taken") + hashed = bcrypt.hashpw(user.password.encode(), bcrypt.gensalt()).decode() + db_user = User(**user.model_dump(exclude={"password"}), hashedPassword=hashed) + db.add(db_user) + db.commit() + return {"msg": "User created"} - if not verify_password(user.password, user_dict['hashed_password']): - raise HTTPException(status_code=400, detail="Password hashing failed") +@router.post("/login") +def login(user: UserLogin, db: Session = Depends(get_db)): + db_user = db.query(User).filter_by(username=user.username).first() + if not db_user or not bcrypt.checkpw(user.password.encode(), db_user.hashedPassword.encode()): + raise HTTPException(401, "Invalid creds") + return {"token": create_token(db_user)} - db_user = DBUser.model_validate(user_dict) - session.add(db_user) - session.commit() - session.refresh(db_user) - return db_user \ No newline at end of file +@router.get("/me", response_model=UserOut) +def get_me(token: str, db: Session = Depends(get_db)): + username = verify_token(token) + if not username: + raise HTTPException(401, "Invalid token") + user = db.query(User).filter_by(username=username).first() + return user \ No newline at end of file diff --git a/auth/schemas.py b/auth/schemas.py new file mode 100644 index 0000000..b9f3a16 --- /dev/null +++ b/auth/schemas.py @@ -0,0 +1,23 @@ +from pydantic import BaseModel, EmailStr +from .models import Role, Status + +class UserCreate(BaseModel): + username: str + name: str + surname: str + password: str + email: EmailStr + bio: str = "" + +class UserOut(BaseModel): + username: str + name: str + surname: str + email: EmailStr + role: Role + status: Status + bio: str + +class UserLogin(BaseModel): + username: str + password: str \ No newline at end of file diff --git a/config.py b/config.py index 07d6e59..e4641c4 100644 --- a/config.py +++ b/config.py @@ -1,17 +1,13 @@ from sqlalchemy import create_engine -from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy.orm import sessionmaker +from sqlalchemy.orm import sessionmaker, declarative_base from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware -from passlib.context import CryptContext -from sqlmodel import SQLModel, Field, Session from dotenv import load_dotenv import os load_dotenv() -# Veritabanı URL'sini oluştur DATABASE_URL = ( f"postgresql://{os.getenv('USERNAME_DB')}:" f"{os.getenv('PASSWORD_DB')}@" @@ -20,16 +16,24 @@ DATABASE_URL = ( f"{os.getenv('NAME_DB')}" ) -engine = create_engine(DATABASE_URL, echo=False) -def init_db(): - SQLModel.metadata.create_all(engine) -def get_session_db(): - with Session(engine) as session: - yield session +engine = create_engine(DATABASE_URL) +SessionLocal = sessionmaker(bind=engine) +Base = declarative_base() + +from .auth.models import * +from .items.models import * + +Base.metadata.create_all(bind=engine) -### SECRET KEY ### +def get_db(): + db = SessionLocal() + try: + yield db + finally: + db.close() + origins = [ "http://localhost", "http://localhost:8080", @@ -38,10 +42,6 @@ origins = [ ] app = FastAPI() -@app.on_event("startup") -def on_startup(): - init_db() - app.add_middleware( CORSMiddleware, allow_origins=origins, @@ -49,6 +49,3 @@ app.add_middleware( allow_methods=["*"], allow_headers=["*"], ) - - - diff --git a/items/models.py b/items/models.py index 8d7cb69..75215e3 100644 --- a/items/models.py +++ b/items/models.py @@ -1,12 +1,15 @@ -from datetime import datetime, timedelta, timezone -from ..auth.models import UserBase - -class UserProfile(UserBase): - bio : str | None = None - created_date : datetime | None = None - collections : list[str] | None = None - items :list[str] | None = None - - - +from datetime import datetime +from ..config import Base +from sqlalchemy import Column, Integer, String, DateTime, ForeignKey +from sqlalchemy.orm import relationship, Mapped, mapped_column +class Item(Base): + __tablename__ = "items" + item_id = Column(Integer, primary_key=True) + name = Column(String(100), nullable=False) + description = Column(String(500), nullable=True) + price = Column(Integer, nullable=False) + created_date = Column(DateTime, default=datetime.utcnow) + updated_date = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) + user_id: Mapped[int] = mapped_column(ForeignKey("users.id")) + user: Mapped["User"] = relationship(back_populates="items") \ No newline at end of file diff --git a/items/router.py b/items/router.py index 7fd3c4b..2961d4f 100644 --- a/items/router.py +++ b/items/router.py @@ -1,7 +1,4 @@ -from .models import UserProfile from fastapi import APIRouter, Depends -from typing import Annotated -from ..auth.models import get_current_active_user router = APIRouter( prefix="/items", @@ -10,9 +7,7 @@ router = APIRouter( dependencies=[], ) -@router.get('/profile', response_model=UserProfile) -async def get_user_profile( - current_user: Annotated[UserProfile, Depends(get_current_active_user)] -) -> UserProfile: - - return current_user \ No newline at end of file + +@router.get("/") +async def get_items(): + return {"message": "List of items"} \ No newline at end of file -- 2.51.0