This commit is contained in:
Robin COuret
2026-03-05 17:30:39 +01:00
parent 339270aefd
commit 02bc680982
30 changed files with 175 additions and 12 deletions

View File

@@ -2,3 +2,5 @@ DATABASE_URI="sqlite:///database.db"
LANGUAGE_MODEL_API="http://localhost:8080/v1" LANGUAGE_MODEL_API="http://localhost:8080/v1"
MODEL_NAME="SmolLM3-Q4_K_M.gguf" MODEL_NAME="SmolLM3-Q4_K_M.gguf"
ORIGIN="http://localhost:5173" ORIGIN="http://localhost:5173"
SECRET_SIGN="xxxx" #generate secure random secret key: openssl rand -hex 32
ACCESS_TOKEN_EXPIRE_MINUTES=10080

View File

@@ -3,5 +3,7 @@ sqlmodel==0.0.32
python-dotenv==1.2.1 python-dotenv==1.2.1
openai==2.21.0 openai==2.21.0
spacy==3.8.11 spacy==3.8.11
PyJWT>=2.11.0
argon2-cffi>=25.1.0
# python -m spacy download en_core_web_sm # python -m spacy download en_core_web_sm
# python -m spacy download fr_core_news_sm # python -m spacy download fr_core_news_sm

View File

@@ -2,7 +2,9 @@ from fastapi import APIRouter
from .knowledges import router as knowledge_router from .knowledges import router as knowledge_router
from .metrics import router as metric_router from .metrics import router as metric_router
from .users import router as user_router
router = APIRouter(prefix="/v1") router = APIRouter(prefix="/v1")
router.include_router(knowledge_router) router.include_router(knowledge_router)
router.include_router(metric_router) router.include_router(metric_router)
router.include_router(user_router)

View File

@@ -1,3 +1,4 @@
from typing import Annotated
from fastapi import APIRouter from fastapi import APIRouter
from src.app.models.knowledge import Knowledge from src.app.models.knowledge import Knowledge

View File

@@ -0,0 +1,36 @@
from typing import Annotated
from fastapi import Depends, APIRouter, HTTPException, status
from fastapi.security import OAuth2PasswordRequestForm, OAuth2PasswordBearer
from datetime import timedelta
from src.app.models.user import User
from src.app.crud.crud_user import create_user
from src.app.services.auth import get_current_user, authenticate_user, create_access_token, hash_password, Token
router = APIRouter(tags=["users"])
@router.post("/token")
async def login(form_data: Annotated[OAuth2PasswordRequestForm, Depends()]) -> Token:
user = authenticate_user(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 = create_access_token(data={"sub": user.username})
return Token(access_token=access_token, token_type="bearer")
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="api/v1/token")
@router.get("/user")
async def user(current_user: Annotated[str, Depends(get_current_user)]):
return current_user
@router.post("/user")
async def create(username, password):
hashed_password = await hash_password(password)
user = User(username = username, hashed_password = hashed_password)
created_user = create_user(user)
return created_user

View File

@@ -20,9 +20,6 @@ def read_knowledges():
def read_knowledge(knowledge_id: int): def read_knowledge(knowledge_id: int):
with Session(engine) as session: with Session(engine) as session:
#statement = select(Knowledge).where(Knowledge.id == knowledge_id)
#results = session.exec(statement)
#knowledge = results.first()
knowledge = session.get(Knowledge, knowledge_id) knowledge = session.get(Knowledge, knowledge_id)
return knowledge return knowledge
@@ -36,6 +33,7 @@ def update_knowledge(knowledge_id: int, content: str, uri: str):
session.add(knowledge) session.add(knowledge)
session.commit() session.commit()
session.refresh(knowledge) session.refresh(knowledge)
return knowledge
def delete_knowledge(knowledge_id: int): def delete_knowledge(knowledge_id: int):
with Session(engine) as session: with Session(engine) as session:

View File

@@ -0,0 +1,38 @@
from sqlmodel import Session, select
from src.app.models.user import User
from src.app.database import engine
def create_user(user: User):
with Session(engine) as session:
session.add(user)
session.commit()
session.refresh(user)
return user
def get_user(username: str):
with Session(engine) as session:
statement = select(User).where(User.username == username)
results = session.exec(statement)
result = results.first()
return result
def read_users():
with Session(engine) as session:
statement = select(User)
results = session.exec(statement)
users = results.all()
return users
def update_user(user_id: int, username: str, hash: str):
with Session(engine) as session:
user = session.get(User, user_id)
user.username = username if username else user.username
user.hash = hash if hash else user.hash
return user
def delete_user(user_id: int):
with Session(engine) as session:
user = session.get(User, user_id)
session.delete(user)
session.commit()

View File

@@ -7,7 +7,7 @@ load_dotenv()
database_uri=os.environ.get("DATABASE_URI") database_uri=os.environ.get("DATABASE_URI")
connect_args = {"check_same_thread": False} connect_args = {"check_same_thread": False}
engine = create_engine(database_uri, echo=True, connect_args=connect_args) engine = create_engine(database_uri, echo=False, connect_args=connect_args)
def create_db_and_tables(): def create_db_and_tables():
SQLModel.metadata.create_all(engine) SQLModel.metadata.create_all(engine)

View File

@@ -1,7 +1,7 @@
from src.app.models.knowledge import Knowledge from src.app.models.knowledge import Knowledge
from src.app.crud.crud_knowledges import create_knowledge, read_knowledges, read_knowledge, update_knowledge, delete_knowledge from src.app.crud.crud_knowledges import create_knowledge
from src.app.models.question import Question from src.app.models.question import Question
from src.app.crud.crud_questions import create_question, read_questions, read_question from src.app.crud.crud_questions import create_question
from src.app.models.metric import Metric from src.app.models.metric import Metric
from src.app.crud.crud_metrics import create_metric from src.app.crud.crud_metrics import create_metric
@@ -37,3 +37,4 @@ def faker():
create_metric(metric3) create_metric(metric3)
create_metric(metric4) create_metric(metric4)
create_metric(metric5) create_metric(metric5)

View File

@@ -11,6 +11,7 @@ from src.app.database import create_db_and_tables
from src.app.models.question import Question from src.app.models.question import Question
from src.app.models.knowledge import Knowledge from src.app.models.knowledge import Knowledge
from src.app.models.metric import Metric from src.app.models.metric import Metric
from src.app.models.user import User
from .api import router from .api import router

Binary file not shown.

View File

@@ -1,5 +1,4 @@
from sqlmodel import Field, SQLModel, Relationship from sqlmodel import Field, SQLModel, Relationship
#TODO : add pydantic validation
class Knowledge(SQLModel, table=True): class Knowledge(SQLModel, table=True):
id: int | None = Field(default=None, primary_key=True) id: int | None = Field(default=None, primary_key=True)

View File

@@ -1,8 +1,6 @@
from sqlmodel import Field, SQLModel, Relationship from sqlmodel import Field, SQLModel, Relationship
from src.app.models.question import Question from src.app.models.question import Question
#TODO : add pydantic validation
class Metric(SQLModel, table=True): class Metric(SQLModel, table=True):
id: int | None = Field(default=None, primary_key=True) id: int | None = Field(default=None, primary_key=True)

View File

@@ -1,6 +1,5 @@
from sqlmodel import Field, SQLModel, Relationship from sqlmodel import Field, SQLModel, Relationship
from src.app.models.knowledge import Knowledge from src.app.models.knowledge import Knowledge
#TODO : add pydantic validation
class Question(SQLModel, table=True): class Question(SQLModel, table=True):
id: int | None = Field(default=None, primary_key=True) id: int | None = Field(default=None, primary_key=True)

View File

@@ -0,0 +1,6 @@
from sqlmodel import Field, SQLModel, Relationship
class User(SQLModel, table=True):
id: int | None = Field(default=None, primary_key=True)
username: str
hashed_password: str

View File

@@ -0,0 +1,80 @@
import os
from dotenv import load_dotenv
from datetime import timedelta, datetime, timezone
from typing import Annotated
from pydantic import BaseModel
import jwt
from jwt.exceptions import InvalidTokenError
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
from argon2 import PasswordHasher
from src.app.models.user import User
from src.app.crud.crud_user import get_user
load_dotenv()
secret_key = os.environ.get("SECRET")
algorithm = "HS256"
access_token_expire_minutes = 10080
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/token")
password_hasher = PasswordHasher()
class Token(BaseModel):
access_token: str
token_type: str
class TokenData(BaseModel):
username: str | None = None
def authenticate_user(username: str, password: str):
user: User = get_user(username)
if not user:
verify_password(password, user.hashed_password)
return False
if not verify_password(password, user.hashed_password):
return False
return user
def verify_password(plain_password: str, hashed_password: str) -> bool:
isValidated: bool = False
try:
isValidated = password_hasher.verify(hashed_password, plain_password)
except:
isValidated = False
return isValidated
def create_access_token(data: dict):
expire = datetime.now(timezone.utc) + timedelta(minutes=access_token_expire_minutes)
to_encode = data.copy()
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, secret_key, algorithm=algorithm)
return encoded_jwt
async def hash_password(password: str) -> str:
return password_hasher.hash(password)
async def get_current_user(token: Annotated[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, secret_key, algorithm)
username = payload.get("sub")
if username is None:
raise credentials_exception
token_data = TokenData(username=username)
except InvalidTokenError:
raise credentials_exception
user = get_user(username=token_data.username)
if user is None:
raise credentials_exception
return user