base model

This commit is contained in:
bdrtr 2025-05-06 14:06:15 +03:00
parent 83389e0c10
commit 36da53a562
4 changed files with 105 additions and 69 deletions

View file

@ -1,15 +1,14 @@
from enum import Enum from enum import Enum
from backend.config import SECRET_KEY, ALGORITHM, ACCESS_TOKEN_EXPIRE_MINUTES from backend.config import SECRET_KEY, ALGORITHM, ACCESS_TOKEN_EXPIRE_MINUTES ,pwd_context, get_session_db, Base
from backend.config import pwd_context, get_session_db
from datetime import datetime, timedelta, timezone from datetime import datetime, timedelta, timezone
from pydantic import BaseModel from pydantic import BaseModel
from fastapi import Depends, HTTPException from fastapi import Depends, HTTPException
from typing import Annotated, Optional from typing import Annotated
from fastapi.security import OAuth2PasswordBearer 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 pydantic.networks import EmailStr
from sqlalchemy import Column, Integer, String
from sqlalchemy.orm import Session
import jwt
class Token(BaseModel): class Token(BaseModel):
access_token: str access_token: str
@ -28,40 +27,49 @@ class Status(str, Enum):
banned = "banned" banned = "banned"
suspended = "suspended" suspended = "suspended"
### KULLANICI MODELLERİ ### ### KULLANICI MODELLERİ ### sqlalchemy ve pydantic modelleri farklıdır
class UserBase(SQLModel): class UserBase(BaseModel): #bu bir veri tabanı modeli değil !!!! lütfen dikkat et
username: Optional[str] = None username: str | None = None #Option yerine Union kullanabilirsin
user_id: Optional[int] = None role: Role | None = None
role: Optional[Role] = None status: Status | None = None
status: Optional[Status] = None
class UserInDb(UserBase): class UserInDb(UserBase):
user_id: int | None = None
email: EmailStr | None = None
hashed_password: str | None = None hashed_password: str | None = None
class UserPublic(UserBase): class UserPublic(BaseModel):
pass username : str | None = None
role : Role | None = None
status : Status | None = None
class UserCreate(BaseModel): class UserCreate(BaseModel):
username: Optional[str] = None username: str | None = None
role: Optional[Role] = None role: Role | None = None
email : EmailStr | None = None email : EmailStr | None = None
status: Optional[Status] = None status: Status | None = None
password : str | None = None password : str | None = None
### VERİTABANI MODELİ ### ### VERİTABANI MODELİ ###
class DBUser(SQLModel, table=True): class DBUser(Base):
__tablename__ = "users" # opsiyonel, sqlmodel bunu otomatik de atar __tablename__ = "users_table"
user_id: Optional[int] = Field(default=None, primary_key=True)
username: str = Field(index=True, nullable=False) user_id = Column(Integer, primary_key=True, index=True)
hashed_password: str = Field(nullable=False) username = Column(String, unique=True, index=True, nullable=False)
role: Role = Field(default=Role.user) email = Column(String, unique=True, index=True, nullable=False)
status: Status = Field(default=Status.active) hashed_password = Column(String, nullable=False)
role = Column(String, default="user")
status = Column(String, default="active")
created_date = Column(String, default=datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S"))
### AUTH ### ### AUTH ###
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/auth/login") oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/auth/login")
### SERVİSLER ###
def verify_password(plain_password: str, hashed_password: str) -> bool: def verify_password(plain_password: str, hashed_password: str) -> bool:
return pwd_context.verify(plain_password, hashed_password) return pwd_context.verify(plain_password, hashed_password)
@ -74,21 +82,20 @@ def authenticate_user(
password: str password: str
) -> UserInDb | None: ) -> UserInDb | None:
statement = select(DBUser).where(DBUser.username == username) user = session.query(DBUser).filter(DBUser.username == username).first()
result = session.exec(statement).first() if user is None or not verify_password(password, user.hashed_password): #sqlalchemy'de bu şekilde kontrol ediliyor None ile
if not result or not verify_password(password, result.hashed_password):
return None return None
return result return user
def create_access_token( def create_access_token(
data: dict, data: dict,
expires_delta: Optional[timedelta] = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES), expires_delta: Annotated[timedelta, None] = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES),
) -> str: ) -> str:
to_encode = data.copy() to_encode = data.copy()
expire = datetime.now(timezone.utc) + expires_delta expire = datetime.now(timezone.utc) + expires_delta
to_encode.update({"exp": expire}) to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm="HS256") encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt return encoded_jwt
@ -97,9 +104,8 @@ async def get_user(
username: str username: str
) -> UserInDb | None: ) -> UserInDb | None:
statement = select(DBUser).where(DBUser.username == username) user = session.query(DBUser).filter(DBUser.username == username).first()
result = session.exec(statement).first() return user
return result
async def get_current_user( async def get_current_user(
@ -113,10 +119,13 @@ async def get_current_user(
headers={"WWW-Authenticate": "Bearer"}, headers={"WWW-Authenticate": "Bearer"},
) )
try: try:
payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"]) payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
username: Optional[str] = payload.get("sub") username : str | None = payload.get("sub")
user = UserInDb.model_validate(payload)
if username is None: if username is None:
raise credentials_exception raise credentials_exception
except jwt.PyJWTError: except jwt.PyJWTError:
raise credentials_exception raise credentials_exception
@ -133,3 +142,32 @@ async def get_current_active_user(
if current_user.status == Status.banned: if current_user.status == Status.banned:
raise HTTPException(status_code=400, detail="Inactive user") raise HTTPException(status_code=400, detail="Inactive user")
return current_user return current_user
### Kullanıcı kaydı
def register_user(
session: Annotated[Session, Depends(get_session_db)],
user: Annotated[UserCreate, Depends()]
) -> UserPublic:
user_dict = user.dict() # kullanıcıdan gelen verileri alıyoruz çunku şifreyi hashleyeceğiz
user_dict['hashed_password'] = get_password_hash(user.password) # şifreyi hashliyoruz
if not verify_password(user.password, user_dict['hashed_password']):
raise HTTPException(status_code=400, detail="Password hashing failed") # şifre hashleme işlemi başarısız oldu
# Kullanıcı adı ve e-posta adresinin benzersiz olduğunu kontrol et
existing_user = session.query(DBUser).filter(
(DBUser.username == user.username) | (DBUser.email == user.email)
).first()
if existing_user:
raise HTTPException(status_code=400, detail="Username or email already registered")
user_dict['created_date'] = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S") # kullanıcı oluşturulma tarihi
user_dict.pop('password') ##password'u veri tabanına eklemiyoruz zaten sınıfımızda tanımlı değil hata verir
db_user = DBUser(**user_dict) #alchemy ile pydantic modelleri farklıdır bir birine
session.add(db_user) # donuşum yaparken dikkat et
session.commit()
session.refresh(db_user)
return db_user

View file

@ -1,15 +1,10 @@
from fastapi import APIRouter, Depends, HTTPException, status from fastapi import APIRouter, Depends, HTTPException, status
from .models import Token, UserPublic from .models import Token, UserPublic, authenticate_user, create_access_token, UserCreate, register_user
from .models import authenticate_user, create_access_token
from datetime import timedelta from datetime import timedelta
from ..auth.models import get_password_hash, verify_password
from typing import Annotated from typing import Annotated
from sqlmodel import Session
from ..config import get_session_db from ..config import get_session_db
from fastapi import Depends
from fastapi.security import OAuth2PasswordRequestForm from fastapi.security import OAuth2PasswordRequestForm
from .models import UserCreate, DBUser from sqlalchemy.orm import Session
router = APIRouter( router = APIRouter(
prefix="/auth", prefix="/auth",
@ -38,21 +33,12 @@ async def login_for_access_token(
return Token(access_token=access_token, token_type="bearer") return Token(access_token=access_token, token_type="bearer")
@router.post('/register', response_model=UserPublic) @router.post('/register', response_model=UserPublic) #userPublic güvenli bir model
async def create_user( async def create_user(
session : Annotated[Session, Depends(get_session_db)], session : Annotated[Session, Depends(get_session_db)],
user : Annotated[UserCreate, Depends()] user : Annotated[UserCreate, Depends()]
): ):
user_dict = user.dict()
print(user.password) return register_user(session, user)
user_dict['hashed_password'] = get_password_hash(user.password)
print (user_dict['hashed_password'])
if not verify_password(user.password, user_dict['hashed_password']):
raise HTTPException(status_code=400, detail="Password hashing failed")
db_user = DBUser.model_validate(user_dict)
session.add(db_user)
session.commit()
session.refresh(db_user)
return db_user

View file

@ -4,29 +4,38 @@ from sqlalchemy.orm import sessionmaker
from fastapi import FastAPI from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from passlib.context import CryptContext from passlib.context import CryptContext
from sqlmodel import SQLModel, Field, Session
from dotenv import load_dotenv from dotenv import load_dotenv
import os import os
load_dotenv() load_dotenv()
# Veritabanı URL'sini oluştur pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
DATABASE_URL = (
f"postgresql://{os.getenv('USERNAME_DB')}:"
f"{os.getenv('PASSWORD_DB')}@"
f"{os.getenv('HOST_DB')}:"
f"{os.getenv('PORT_DB')}/"
f"{os.getenv('NAME_DB')}"
)
SECRET_KEY = os.getenv("SECRET_KEY")
ALGORITHM = os.getenv("ALGORITHM")
ACCESS_TOKEN_EXPIRE_MINUTES = int(os.getenv("ACCESS_TOKEN_EXPIRE_MINUTES", 30))
DATABASE_URL = os.getenv("DATABASE_URL")
# Engine oluştur
engine = create_engine(DATABASE_URL, echo=False) engine = create_engine(DATABASE_URL, echo=False)
def init_db(): # Session factory oluştur
SQLModel.metadata.create_all(engine) SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base() #sqlalchemy için bu sınıfı kullanıyoruz 'class DBUser(Base)' şeklinde tanımlıyoruz
#models te içe aktarmayı unutma
def init_db():
Base.metadata.drop_all(engine) # Veritabanını her başlangıcta siler burayada dikkat !!!!!!!!
Base.metadata.create_all(bind=engine)
# Session dependency (FastAPI için)
def get_session_db(): def get_session_db():
with Session(engine) as session: db = SessionLocal()
yield session try:
yield db
finally:
db.close()
### SECRET KEY ### ### SECRET KEY ###
@ -39,7 +48,7 @@ origins = [
app = FastAPI() app = FastAPI()
@app.on_event("startup") @app.on_event("startup")
def on_startup(): def startup_event():
init_db() init_db()
app.add_middleware( app.add_middleware(

View file

@ -1,5 +1,6 @@
annotated-types==0.7.0 annotated-types==0.7.0
anyio==4.9.0 anyio==4.9.0
bcrypt==4.3.0
certifi==2025.4.26 certifi==2025.4.26
click==8.1.8 click==8.1.8
dnspython==2.7.0 dnspython==2.7.0
@ -39,3 +40,5 @@ uvicorn==0.34.2
uvloop==0.21.0 uvloop==0.21.0
watchfiles==1.0.5 watchfiles==1.0.5
websockets==15.0.1 websockets==15.0.1
passlib[bcrypt]==1.7.4