Add more comprehensive logging

This commit is contained in:
Esa Kataja
2025-05-10 14:28:34 +03:00
parent aecc0731bc
commit 39da976ef3
4 changed files with 100 additions and 32 deletions
+4
View File
@@ -220,6 +220,10 @@ Automated tests are not implemented at this time. Manual testing via the API doc
## TODOs ## TODOs
* [ ] Replace password hashing with passlib * [ ] Replace password hashing with passlib
* [ ] Switch to PostgreSQL from DuckDB
* [x] Implement more comprehensive logging
* [ ] Implement user disabling functionality
* [ ] Implement user password change functionality
## Contributing ## Contributing
+44 -6
View File
@@ -1,9 +1,10 @@
from fastapi import APIRouter, Body, HTTPException from fastapi import APIRouter, Body, HTTPException, Request
from models.user import CreateUser from models.user import CreateUser
from models.team import TeamBase, Team from models.team import TeamBase, Team
from lib.db import get_connection from lib.db import get_connection
from lib.helpers import hash_password from lib.helpers import hash_password
from lib.logger import logger
router = APIRouter(prefix="/admin", tags=["Admin"]) router = APIRouter(prefix="/admin", tags=["Admin"])
@@ -19,22 +20,46 @@ router = APIRouter(prefix="/admin", tags=["Admin"])
@router.post("/teams") @router.post("/teams")
async def create_team(team: TeamBase): async def create_team(team: TeamBase, request: Request):
logger.info(
f"Admin action: Creating new team '{team.name}' from IP: {request.client.host}"
)
try:
with get_connection() as conn: with get_connection() as conn:
conn.execute("INSERT INTO Team (name) VALUES (?)", (team.name,)) conn.execute("INSERT INTO Team (name) VALUES (?)", (team.name,))
logger.info(f"Team '{team.name}' created successfully")
return {"message": "Team created successfully"} return {"message": "Team created successfully"}
except Exception as e:
logger.error(f"Failed to create team '{team.name}': {str(e)}")
raise HTTPException(status_code=500, detail="Failed to create team")
@router.get("/teams", response_model=list[Team]) @router.get("/teams", response_model=list[Team])
async def list_teams(): async def list_teams(request: Request):
logger.debug(f"Admin action: Listing all teams from IP: {request.client.host}")
with get_connection() as conn: with get_connection() as conn:
teams = conn.execute("SELECT * FROM Team").fetchdf() teams = conn.execute("SELECT * FROM Team").fetchdf()
return teams.to_dict(orient="records") return teams.to_dict(orient="records")
@router.post("/users") @router.post("/users")
async def create_user(user: CreateUser): async def create_user(user: CreateUser, request: Request):
logger.info(
f"Admin action: Creating new user '{user.username}' from IP: {request.client.host}"
)
try:
with get_connection() as conn: with get_connection() as conn:
# Check if username already exists
existing = conn.execute(
"SELECT COUNT(*) as count FROM User WHERE username = ?",
(user.username,),
).fetchdf()
if existing["count"][0] > 0:
logger.warning(
f"Failed to create user: Username '{user.username}' already exists"
)
raise HTTPException(status_code=400, detail="Username already exists")
conn.execute( conn.execute(
"INSERT INTO User (username, hashed_password, team_id, is_admin) VALUES (?, ?, ?, ?)", "INSERT INTO User (username, hashed_password, team_id, is_admin) VALUES (?, ?, ?, ?)",
( (
@@ -44,11 +69,24 @@ async def create_user(user: CreateUser):
user.is_admin, user.is_admin,
), ),
) )
logger.info(
f"User '{user.username}' created successfully with team_id: {user.team_id}, admin status: {user.is_admin}"
)
return {"message": "User created successfully"} return {"message": "User created successfully"}
except HTTPException:
# Re-raise HTTP exceptions
raise
except Exception as e:
logger.error(f"Failed to create user '{user.username}': {str(e)}")
raise HTTPException(status_code=500, detail="Failed to create user")
@router.post("/users/") @router.post("/users/disable")
async def disable_user(user_id: int = Body(..., embed=True)): async def disable_user(user_id: int = Body(..., embed=True), request: Request = None):
logger.info(
f"Admin action: Attempting to disable user with ID: {user_id} from IP: {request.client.host}"
)
logger.warning(f"Disable user functionality not implemented for user_id: {user_id}")
raise HTTPException(status_code=405, detail="Method not yet implemented") raise HTTPException(status_code=405, detail="Method not yet implemented")
+21 -6
View File
@@ -1,24 +1,34 @@
from fastapi import APIRouter, HTTPException from fastapi import APIRouter, HTTPException, Request
from lib.db import get_connection from lib.db import get_connection
from models.user import User, UserLogin from models.user import User, UserLogin
from lib.helpers import verify_password from lib.helpers import verify_password
from lib.logger import logger
router = APIRouter(prefix="/auth", tags=["auth"]) router = APIRouter(prefix="/auth", tags=["auth"])
@router.post("/token", response_model=User) @router.post("/token", response_model=User)
async def login(user_login: UserLogin): async def login(user_login: UserLogin, request: Request):
logger.debug(
f"Login attempt for user: {user_login.username} from IP: {request.client.host}"
)
with get_connection() as conn: with get_connection() as conn:
user_df = conn.execute( user_df = conn.execute(
'SELECT * FROM "User" WHERE username = ?', (user_login.username,) 'SELECT * FROM "User" WHERE username = ?', (user_login.username,)
).fetchdf() ).fetchdf()
if user_df.empty: if user_df.empty:
logger.warning(
f"Failed login: Username {user_login.username} not found - IP: {request.client.host}"
)
raise HTTPException(status_code=401, detail="Invalid username or password") raise HTTPException(status_code=401, detail="Invalid username or password")
user_data = user_df.to_dict(orient="records")[0] user_data = user_df.to_dict(orient="records")[0]
if not verify_password(user_login.password, user_data["hashed_password"]): if not verify_password(user_login.password, user_data["hashed_password"]):
logger.warning(
f"Failed login: Incorrect password for user {user_login.username} - IP: {request.client.host}"
)
raise HTTPException(status_code=401, detail="Invalid username or password") raise HTTPException(status_code=401, detail="Invalid username or password")
# Update the login timestamp in the database # Update the login timestamp in the database
@@ -29,9 +39,14 @@ async def login(user_login: UserLogin):
) )
# Fetch the updated user data with the new last_login timestamp # Fetch the updated user data with the new last_login timestamp
updated_user = conn.execute( updated_user = (
'SELECT * FROM "User" WHERE id = ?', conn.execute('SELECT * FROM "User" WHERE id = ?', (user_data["id"],))
(user_data["id"],) .fetchdf()
).fetchdf().to_dict(orient="records")[0] .to_dict(orient="records")[0]
)
logger.info(
f"Successful login: User {user_login.username} (ID: {user_data['id']}) logged in from {request.client.host}"
)
return User(**updated_user) return User(**updated_user)
+16 -5
View File
@@ -1,14 +1,15 @@
from fastapi import APIRouter from fastapi import APIRouter, HTTPException, Request
from fastapi import HTTPException
from models.user import User, UserChangePassword from models.user import User, UserChangePassword
from lib.db import get_connection from lib.db import get_connection
from lib.logger import logger
router = APIRouter(prefix="/users", tags=["users"]) router = APIRouter(prefix="/users", tags=["users"])
@router.get("/", response_model=list[User]) @router.get("/", response_model=list[User])
async def list_users(): async def list_users(request: Request):
logger.info(f"User list requested from {request.client.host}")
with get_connection() as conn: with get_connection() as conn:
users = conn.execute('SELECT * FROM "User"').fetchdf() users = conn.execute('SELECT * FROM "User"').fetchdf()
@@ -18,19 +19,29 @@ async def list_users():
@router.get("/{user_id}", response_model=User) @router.get("/{user_id}", response_model=User)
async def get_user(user_id: int): async def get_user(user_id: int, request: Request):
logger.debug(
f"User details requested for user_id: {user_id} from {request.client.host}"
)
with get_connection() as conn: with get_connection() as conn:
user_df = conn.execute( user_df = conn.execute(
'SELECT * FROM "User" WHERE id = ?', (user_id,) 'SELECT * FROM "User" WHERE id = ?', (user_id,)
).fetchdf() ).fetchdf()
if user_df.empty: if user_df.empty:
logger.warning(f"Failed user lookup: user_id {user_id} not found")
raise HTTPException(status_code=404, detail="User not found") raise HTTPException(status_code=404, detail="User not found")
user_data = user_df.to_dict(orient="records")[0] user_data = user_df.to_dict(orient="records")[0]
logger.debug(f"User details retrieved: {user_data}")
return User(**user_data) return User(**user_data)
@router.patch("/{user_id}") @router.patch("/{user_id}")
async def change_password(user: UserChangePassword): async def change_password(user_id: int, user: UserChangePassword, request: Request):
logger.info(
f"Password change attempt for user_id: {user_id} from {request.client.host}"
)
# Implementation will go here when completed
logger.warning(f"Password change not implemented for user_id: {user_id}")
raise HTTPException(status_code=401, detail="Not implemented") raise HTTPException(status_code=401, detail="Not implemented")