Compare commits

..
Author SHA1 Message Date
Hannah dagemark
4f2a376f8f Fix routing end slash 2026-09-14 23:03:53 +02:00
d7085c6785 Fix readme from merge 2026-09-09 23:29:36 +02:00
a506bb1047 Merge branch 'Devbranch' of https://dagemark.se/git/Hannah/Dagemark-Backend into Devbranch 2026-09-09 23:27:47 +02:00
295b28688e Misc 2026-09-09 23:26:26 +02:00
Hannah dagemark
fbf731acb6 Fix login errors, add get /users and get /user (username or id) routes. 2026-09-09 17:08:28 +02:00
Hannah dagemark
97afe5447a Add user creation 2026-09-02 20:52:39 +02:00
6 changed files with 89 additions and 25 deletions

View file

@ -1,3 +1,8 @@
# Initial Commit :D # Initial Commit :D
Run 'uv export --format requirements-txt --no-dev --no-emit-project --output-file requirements.txt' for major release versions (Maybe I make this a git actions thing?)
Run 'uv run fastapi dev' for dev environment
Recommended vscode extensions used for formatting: Ruff, Pylance. Use strict type checking.
# Run 'uv export --format requirements-txt --no-dev --no-emit-project --output-file requirements.txt' for major release versions (Maybe I make this a git actions thing?) # Run 'uv export --format requirements-txt --no-dev --no-emit-project --output-file requirements.txt' for major release versions (Maybe I make this a git actions thing?)

View file

@ -6,12 +6,17 @@ from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer from fastapi.security import OAuth2PasswordBearer
from pwdlib import PasswordHash from pwdlib import PasswordHash
from app.config import Settings from app.config import settings
from app.database import get_from_database_where from app.database import (
add_to_database,
get_from_database,
get_from_database_where,
get_from_database_where_id,
)
from app.models.token import TokenData from app.models.token import TokenData
from app.models.user import User, UserInDB from app.models.user import User, UserIn, UserInDB
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/users/auth")
password_hash = PasswordHash.recommended() password_hash = PasswordHash.recommended()
@ -26,15 +31,37 @@ def get_password_hash(password: str):
return password_hash.hash(password) return password_hash.hash(password)
def get_users():
return get_from_database(UserInDB)
def get_user(username: str): def get_user(username: str):
user = get_from_database_where( user = get_from_database_where(
User, UserInDB,
User.username, # type: ignore (Type correct on runtime) UserInDB.username, # type: ignore (Type correct on runtime)
username, username,
True, limit=1,
) )
if user is not None: if user is not None:
return UserInDB.model_validate(user[0]) return user[0]
def get_user_by_id(id: int):
user = get_from_database_where_id(UserInDB, id)
if user is not None:
return user
def save_user(user: UserIn):
print("Got save user call")
user_in_db = UserInDB(
username=user.username,
authority=user.authority,
hashed_password=get_password_hash(user.password),
)
add_to_database(user_in_db)
print("Saved user")
return User.model_validate(get_user(user.username))
def authenticate_user(username: str, password: str): def authenticate_user(username: str, password: str):
@ -59,8 +86,8 @@ def create_access_token(
to_encode.update({"exp": expire}) to_encode.update({"exp": expire})
encoded_jwt = jwt.encode( # pyright: ignore[reportUnknownMemberType] (Third party skill issue) encoded_jwt = jwt.encode( # pyright: ignore[reportUnknownMemberType] (Third party skill issue)
to_encode, to_encode,
Settings.secret_key, settings.secret_key,
algorithm=Settings.algorithm, algorithm=settings.algorithm,
) )
return encoded_jwt return encoded_jwt
@ -74,8 +101,8 @@ async def get_current_user(token: Annotated[str, Depends(oauth2_scheme)]):
try: try:
payload = jwt.decode( # pyright: ignore[reportUnknownMemberType] (Third party skill issue) payload = jwt.decode( # pyright: ignore[reportUnknownMemberType] (Third party skill issue)
token, token,
Settings.secret_key, settings.secret_key,
algorithms=[Settings.algorithm], algorithms=[settings.algorithm],
) )
username = payload.get("sub") username = payload.get("sub")
if username is None: if username is None:
@ -86,7 +113,7 @@ async def get_current_user(token: Annotated[str, Depends(oauth2_scheme)]):
user = get_user(token_data.username) # type: ignore Look just above here idiot! user = get_user(token_data.username) # type: ignore Look just above here idiot!
if user is None: if user is None:
raise credentials_exception raise credentials_exception
return user return User.model_validate(user)
async def get_current_active_user( async def get_current_active_user(

View file

@ -4,7 +4,7 @@ from fastapi import FastAPI
from app.config import settings from app.config import settings
from app.database import create_database from app.database import create_database
from app.routers import project_route, service_route from app.routers import project_route, service_route, user_route
@asynccontextmanager @asynccontextmanager
@ -24,3 +24,4 @@ app = FastAPI(
app.include_router(project_route.router) app.include_router(project_route.router)
app.include_router(service_route.router) app.include_router(service_route.router)
app.include_router(user_route.router)

View file

@ -1,7 +1,7 @@
from sqlmodel import Field, SQLModel from sqlmodel import Field, SQLModel
class User(SQLModel, table=True): class User(SQLModel):
id: int | None = Field(default=None, primary_key=True) id: int | None = Field(default=None, primary_key=True)
username: str username: str
@ -9,5 +9,9 @@ class User(SQLModel, table=True):
authority: int authority: int
class UserInDB(User): class UserIn(User):
password: str
class UserInDB(User, table=True):
hashed_password: str hashed_password: str

View file

@ -4,29 +4,56 @@ from typing import Annotated
from fastapi import APIRouter, Depends, HTTPException, status from fastapi import APIRouter, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordRequestForm from fastapi.security import OAuth2PasswordRequestForm
from app.config import Settings from app.config import settings
from app.logic.user_logic import ( from app.logic.user_logic import (
authenticate_user, authenticate_user,
create_access_token, create_access_token,
get_current_user, get_current_user,
get_user_by_id,
get_users,
save_user,
) )
from app.models.token import Token from app.models.token import Token
from app.models.user import User from app.models.user import User, UserIn
router = APIRouter(prefix="/users", tags=["users"]) router = APIRouter(prefix="/users", tags=["users"])
@router.get("/") @router.get("")
def get_default(): def fetch_users():
return {"message": "Root for Users API"} users = get_users()
if not users:
return None
return [User.model_validate(user) for user in users]
@router.get("/me") @router.post("")
async def read_me(current_user: Annotated[User, Depends(get_current_user)]): async def create_user(user_in: UserIn):
user = save_user(user_in)
access_token_expires = timedelta(minutes=settings.access_token_expire_minutes)
access_token = create_access_token(
data={"sub": user.username},
expires_delta=access_token_expires,
)
return Token(access_token=access_token, token_type="bearer")
@router.get("/user")
async def fetch_current_user(current_user: Annotated[User, Depends(get_current_user)]):
return current_user return current_user
@router.post("/token") @router.get("/user/id/{user_id}")
async def fetch_user_by_id(user_id: int):
return User.model_validate(get_user_by_id(user_id))
@router.get("/user/name/{user_name}")
async def fetch_user_by_name(user_id: int):
return User.model_validate(get_user_by_id(user_id))
@router.post("/auth")
async def login_for_access_token( async def login_for_access_token(
form_data: Annotated[OAuth2PasswordRequestForm, Depends()], form_data: Annotated[OAuth2PasswordRequestForm, Depends()],
) -> Token: ) -> Token:
@ -37,7 +64,7 @@ async def login_for_access_token(
detail="Incorrect username or password", detail="Incorrect username or password",
headers={"WWW-Authenticate": "Bearer"}, headers={"WWW-Authenticate": "Bearer"},
) )
access_token_expires = timedelta(minutes=Settings.access_token_expire_minutes) access_token_expires = timedelta(minutes=settings.access_token_expire_minutes)
access_token = create_access_token( access_token = create_access_token(
data={"sub": user.username}, data={"sub": user.username},
expires_delta=access_token_expires, expires_delta=access_token_expires,

0
data/.path Normal file
View file