Fix audit log target for deleted users + create user 500 error
1. Audit log: COALESCE target_username with detail JSON fallback so
deleted users still show their username in the target column
(target_user_id is SET NULL by FK cascade, but detail JSON
preserves the username).
2. Create/get user: add exclude={"active_sessions"} to model_dump()
calls — UserListItem defaults active_sessions=0, so model_dump()
includes it, then the explicit active_sessions=N keyword argument
causes a duplicate keyword TypeError. DB commit already happened,
so the user exists but the response was a 500.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
48e15fa677
commit
c3654dc069
@ -16,6 +16,7 @@ from typing import Optional
|
|||||||
|
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
from fastapi import APIRouter, Depends, HTTPException, Path, Query, Request
|
from fastapi import APIRouter, Depends, HTTPException, Path, Query, Request
|
||||||
|
from sqlalchemy.dialects.postgresql import JSONB
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from app.database import get_db
|
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)
|
# Session revocation helper (used in multiple endpoints)
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@ -152,7 +169,7 @@ async def get_user(
|
|||||||
active_sessions = session_result.scalar_one()
|
active_sessions = session_result.scalar_one()
|
||||||
|
|
||||||
return UserDetailResponse(
|
return UserDetailResponse(
|
||||||
**UserListItem.model_validate(user).model_dump(),
|
**UserListItem.model_validate(user).model_dump(exclude={"active_sessions"}),
|
||||||
active_sessions=active_sessions,
|
active_sessions=active_sessions,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -197,7 +214,7 @@ async def create_user(
|
|||||||
await db.commit()
|
await db.commit()
|
||||||
|
|
||||||
return UserDetailResponse(
|
return UserDetailResponse(
|
||||||
**UserListItem.model_validate(new_user).model_dump(),
|
**UserListItem.model_validate(new_user).model_dump(exclude={"active_sessions"}),
|
||||||
active_sessions=0,
|
active_sessions=0,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -668,7 +685,7 @@ async def admin_dashboard(
|
|||||||
sa.select(
|
sa.select(
|
||||||
AuditLog,
|
AuditLog,
|
||||||
actor_user.username.label("actor_username"),
|
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(actor_user, AuditLog.actor_user_id == actor_user.id)
|
||||||
.outerjoin(target_user, AuditLog.target_user_id == target_user.id)
|
.outerjoin(target_user, AuditLog.target_user_id == target_user.id)
|
||||||
@ -722,7 +739,7 @@ async def get_audit_log(
|
|||||||
sa.select(
|
sa.select(
|
||||||
AuditLog,
|
AuditLog,
|
||||||
actor_user.username.label("actor_username"),
|
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(actor_user, AuditLog.actor_user_id == actor_user.id)
|
||||||
.outerjoin(target_user, AuditLog.target_user_id == target_user.id)
|
.outerjoin(target_user, AuditLog.target_user_id == target_user.id)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user