From 97afe5447a63c2171c856a85063406aeb9f63957 Mon Sep 17 00:00:00 2001 From: Hannah dagemark Date: Wed, 2 Sep 2026 20:51:47 +0200 Subject: [PATCH 1/5] Add user creation --- app/logic/user_logic.py | 14 ++++++++++++-- app/models/user.py | 4 ++++ app/routers/user_route.py | 14 +++++++++++++- 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/app/logic/user_logic.py b/app/logic/user_logic.py index ecd3e06..206c0da 100644 --- a/app/logic/user_logic.py +++ b/app/logic/user_logic.py @@ -7,9 +7,9 @@ from fastapi.security import OAuth2PasswordBearer from pwdlib import PasswordHash from app.config import Settings -from app.database import get_from_database_where +from app.database import add_to_database, get_from_database_where 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") @@ -37,6 +37,16 @@ def get_user(username: str): return UserInDB.model_validate(user[0]) +def save_user(user: UserIn): + user_in_db = UserInDB( + username=user.username, + authority=user.authority, + hashed_password=get_password_hash(user.password), + ) + add_to_database(user_in_db) + return user_in_db + + def authenticate_user(username: str, password: str): user = get_user(username) if not user: diff --git a/app/models/user.py b/app/models/user.py index c953516..234ba61 100644 --- a/app/models/user.py +++ b/app/models/user.py @@ -9,5 +9,9 @@ class User(SQLModel, table=True): authority: int +class UserIn(User): + password: str + + class UserInDB(User): hashed_password: str diff --git a/app/routers/user_route.py b/app/routers/user_route.py index 371ff0f..1f37c38 100644 --- a/app/routers/user_route.py +++ b/app/routers/user_route.py @@ -9,9 +9,10 @@ from app.logic.user_logic import ( authenticate_user, create_access_token, get_current_user, + save_user, ) 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"]) @@ -21,6 +22,17 @@ def get_default(): return {"message": "Root for Users API"} +@router.post("/") +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("/me") async def read_me(current_user: Annotated[User, Depends(get_current_user)]): return current_user From fbf731acb6404c94852d80d35ee16f2445eef414 Mon Sep 17 00:00:00 2001 From: Hannah dagemark Date: Wed, 9 Sep 2026 17:08:28 +0200 Subject: [PATCH 2/5] Fix login errors, add get /users and get /user (username or id) routes. --- README.md | 2 ++ app/logic/user_logic.py | 43 +++++++++++++++++++++++++++------------ app/main.py | 3 ++- app/models/user.py | 4 ++-- app/routers/user_route.py | 31 ++++++++++++++++++++-------- 5 files changed, 59 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 429247c..10df58d 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ # Initial Commit :D +To run the devenvironment, run 'uv run fastapi dev' + # 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?) diff --git a/app/logic/user_logic.py b/app/logic/user_logic.py index 206c0da..9e051d7 100644 --- a/app/logic/user_logic.py +++ b/app/logic/user_logic.py @@ -6,12 +6,17 @@ from fastapi import Depends, HTTPException, status from fastapi.security import OAuth2PasswordBearer from pwdlib import PasswordHash -from app.config import Settings -from app.database import add_to_database, get_from_database_where +from app.config import settings +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.user import User, UserIn, UserInDB -oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") +oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/users/auth") password_hash = PasswordHash.recommended() @@ -26,25 +31,37 @@ def get_password_hash(password: str): return password_hash.hash(password) +def get_users(): + return get_from_database(UserInDB) + + def get_user(username: str): user = get_from_database_where( - User, - User.username, # type: ignore (Type correct on runtime) + UserInDB, + UserInDB.username, # type: ignore (Type correct on runtime) username, - True, + limit=1, ) 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) - return user_in_db + print("Saved user") + return User.model_validate(get_user(user.username)) def authenticate_user(username: str, password: str): @@ -69,8 +86,8 @@ def create_access_token( to_encode.update({"exp": expire}) encoded_jwt = jwt.encode( # pyright: ignore[reportUnknownMemberType] (Third party skill issue) to_encode, - Settings.secret_key, - algorithm=Settings.algorithm, + settings.secret_key, + algorithm=settings.algorithm, ) return encoded_jwt @@ -84,8 +101,8 @@ async def get_current_user(token: Annotated[str, Depends(oauth2_scheme)]): try: payload = jwt.decode( # pyright: ignore[reportUnknownMemberType] (Third party skill issue) token, - Settings.secret_key, - algorithms=[Settings.algorithm], + settings.secret_key, + algorithms=[settings.algorithm], ) username = payload.get("sub") if username is None: @@ -96,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! if user is None: raise credentials_exception - return user + return User.model_validate(user) async def get_current_active_user( diff --git a/app/main.py b/app/main.py index 186eed0..3c6e7ae 100644 --- a/app/main.py +++ b/app/main.py @@ -4,7 +4,7 @@ from fastapi import FastAPI from app.config import settings 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 @@ -24,3 +24,4 @@ app = FastAPI( app.include_router(project_route.router) app.include_router(service_route.router) +app.include_router(user_route.router) diff --git a/app/models/user.py b/app/models/user.py index 234ba61..26f83ef 100644 --- a/app/models/user.py +++ b/app/models/user.py @@ -1,7 +1,7 @@ from sqlmodel import Field, SQLModel -class User(SQLModel, table=True): +class User(SQLModel): id: int | None = Field(default=None, primary_key=True) username: str @@ -13,5 +13,5 @@ class UserIn(User): password: str -class UserInDB(User): +class UserInDB(User, table=True): hashed_password: str diff --git a/app/routers/user_route.py b/app/routers/user_route.py index 1f37c38..775f007 100644 --- a/app/routers/user_route.py +++ b/app/routers/user_route.py @@ -4,11 +4,13 @@ from typing import Annotated from fastapi import APIRouter, Depends, HTTPException, status from fastapi.security import OAuth2PasswordRequestForm -from app.config import Settings +from app.config import settings from app.logic.user_logic import ( authenticate_user, create_access_token, get_current_user, + get_user_by_id, + get_users, save_user, ) from app.models.token import Token @@ -18,14 +20,17 @@ router = APIRouter(prefix="/users", tags=["users"]) @router.get("/") -def get_default(): - return {"message": "Root for Users API"} +def fetch_users(): + users = get_users() + if not users: + return None + return [User.model_validate(user) for user in users] @router.post("/") async def create_user(user_in: UserIn): user = save_user(user_in) - 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( data={"sub": user.username}, expires_delta=access_token_expires, @@ -33,12 +38,22 @@ async def create_user(user_in: UserIn): return Token(access_token=access_token, token_type="bearer") -@router.get("/me") -async def read_me(current_user: Annotated[User, Depends(get_current_user)]): +@router.get("/user") +async def fetch_current_user(current_user: Annotated[User, Depends(get_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( form_data: Annotated[OAuth2PasswordRequestForm, Depends()], ) -> Token: @@ -49,7 +64,7 @@ async def login_for_access_token( detail="Incorrect username or password", 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( data={"sub": user.username}, expires_delta=access_token_expires, From 295b28688e9a6ff442bb0e2178d8474d4c1d78b2 Mon Sep 17 00:00:00 2001 From: Hannah Date: Wed, 9 Sep 2026 23:26:26 +0200 Subject: [PATCH 3/5] Misc --- README.md | 5 ++++- data/.path | 0 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 data/.path diff --git a/README.md b/README.md index 429247c..35fa759 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,6 @@ # 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 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. \ No newline at end of file diff --git a/data/.path b/data/.path new file mode 100644 index 0000000..e69de29 From d7085c67855b17c27aaf2c38da87a162946d53c8 Mon Sep 17 00:00:00 2001 From: Hannah Date: Wed, 9 Sep 2026 23:29:36 +0200 Subject: [PATCH 4/5] Fix readme from merge --- README.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/README.md b/README.md index d534972..367e8ba 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,8 @@ # Initial Commit :D -<<<<<<< HEAD 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 +Run 'uv run fastapi dev' for dev environment Recommended vscode extensions used for formatting: Ruff, Pylance. Use strict type checking. -======= -To run the devenvironment, run 'uv run fastapi dev' # 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?) ->>>>>>> fbf731acb6404c94852d80d35ee16f2445eef414 From 4f2a376f8f89cf8832800221773173c84d3f386c Mon Sep 17 00:00:00 2001 From: Hannah dagemark Date: Mon, 14 Sep 2026 23:03:53 +0200 Subject: [PATCH 5/5] Fix routing end slash --- app/routers/user_route.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/routers/user_route.py b/app/routers/user_route.py index 775f007..00cee2d 100644 --- a/app/routers/user_route.py +++ b/app/routers/user_route.py @@ -19,7 +19,7 @@ from app.models.user import User, UserIn router = APIRouter(prefix="/users", tags=["users"]) -@router.get("/") +@router.get("") def fetch_users(): users = get_users() if not users: @@ -27,7 +27,7 @@ def fetch_users(): return [User.model_validate(user) for user in users] -@router.post("/") +@router.post("") async def create_user(user_in: UserIn): user = save_user(user_in) access_token_expires = timedelta(minutes=settings.access_token_expire_minutes)