diff --git a/backend/app/routers/admin.py b/backend/app/routers/admin.py index 963fe4a..c4acdb6 100644 --- a/backend/app/routers/admin.py +++ b/backend/app/routers/admin.py @@ -16,6 +16,7 @@ from typing import Optional import sqlalchemy as sa from fastapi import APIRouter, Depends, HTTPException, Path, Query, Request +from sqlalchemy.dialects.postgresql import JSONB from sqlalchemy.ext.asyncio import AsyncSession from app.database import get_db @@ -58,6 +59,22 @@ router = APIRouter( ) +# --------------------------------------------------------------------------- +# Audit log helper — resolve target username even for deleted users +# --------------------------------------------------------------------------- + +def _target_username_col(target_alias, audit_model): + """ + COALESCE: prefer the live username from the users table, + fall back to the username stored in the audit detail JSON + (survives user deletion since audit_log.target_user_id → SET NULL). + """ + return sa.func.coalesce( + target_alias.username, + sa.cast(audit_model.detail, JSONB)["username"].as_string(), + ).label("target_username") + + # --------------------------------------------------------------------------- # Session revocation helper (used in multiple endpoints) # --------------------------------------------------------------------------- @@ -152,7 +169,7 @@ async def get_user( active_sessions = session_result.scalar_one() return UserDetailResponse( - **UserListItem.model_validate(user).model_dump(), + **UserListItem.model_validate(user).model_dump(exclude={"active_sessions"}), active_sessions=active_sessions, ) @@ -197,7 +214,7 @@ async def create_user( await db.commit() return UserDetailResponse( - **UserListItem.model_validate(new_user).model_dump(), + **UserListItem.model_validate(new_user).model_dump(exclude={"active_sessions"}), active_sessions=0, ) @@ -668,7 +685,7 @@ async def admin_dashboard( sa.select( AuditLog, actor_user.username.label("actor_username"), - target_user.username.label("target_username"), + _target_username_col(target_user, AuditLog), ) .outerjoin(actor_user, AuditLog.actor_user_id == actor_user.id) .outerjoin(target_user, AuditLog.target_user_id == target_user.id) @@ -722,7 +739,7 @@ async def get_audit_log( sa.select( AuditLog, actor_user.username.label("actor_username"), - target_user.username.label("target_username"), + _target_username_col(target_user, AuditLog), ) .outerjoin(actor_user, AuditLog.actor_user_id == actor_user.id) .outerjoin(target_user, AuditLog.target_user_id == target_user.id)