scholarr/app/api/routers/admin.py
2026-02-17 14:51:25 +01:00

202 lines
5.7 KiB
Python

from __future__ import annotations
import logging
from fastapi import APIRouter, Depends, Request
from sqlalchemy.ext.asyncio import AsyncSession
from app.api.deps import get_api_admin_user
from app.api.errors import ApiException
from app.api.responses import success_payload
from app.api.schemas import (
AdminResetPasswordRequest,
AdminUserActiveUpdateRequest,
AdminUserCreateRequest,
AdminUserEnvelope,
AdminUsersListEnvelope,
MessageEnvelope,
)
from app.auth.deps import get_auth_service
from app.auth.service import AuthService
from app.db.models import User
from app.db.session import get_db_session
from app.services import users as user_service
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/admin/users", tags=["api-admin-users"])
def _serialize_user(user: User) -> dict[str, object]:
return {
"id": int(user.id),
"email": user.email,
"is_active": bool(user.is_active),
"is_admin": bool(user.is_admin),
"created_at": user.created_at,
"updated_at": user.updated_at,
}
@router.get(
"",
response_model=AdminUsersListEnvelope,
)
async def list_users(
request: Request,
db_session: AsyncSession = Depends(get_db_session),
admin_user: User = Depends(get_api_admin_user),
):
users = await user_service.list_users(db_session)
logger.info(
"api.admin.users_listed",
extra={
"event": "api.admin.users_listed",
"admin_user_id": int(admin_user.id),
"user_count": len(users),
},
)
return success_payload(
request,
data={
"users": [_serialize_user(user) for user in users],
},
)
@router.post(
"",
response_model=AdminUserEnvelope,
status_code=201,
)
async def create_user(
payload: AdminUserCreateRequest,
request: Request,
db_session: AsyncSession = Depends(get_db_session),
auth_service: AuthService = Depends(get_auth_service),
admin_user: User = Depends(get_api_admin_user),
):
try:
validated_email = user_service.validate_email(payload.email)
validated_password = user_service.validate_password(payload.password)
created_user = await user_service.create_user(
db_session,
email=validated_email,
password_hash=auth_service.hash_password(validated_password),
is_admin=bool(payload.is_admin),
)
except user_service.UserServiceError as exc:
raise ApiException(
status_code=400,
code="invalid_user_input",
message=str(exc),
) from exc
logger.info(
"api.admin.user_created",
extra={
"event": "api.admin.user_created",
"admin_user_id": int(admin_user.id),
"target_user_id": int(created_user.id),
"target_is_admin": bool(created_user.is_admin),
},
)
return success_payload(
request,
data=_serialize_user(created_user),
)
@router.patch(
"/{user_id}/active",
response_model=AdminUserEnvelope,
)
async def set_user_active(
user_id: int,
payload: AdminUserActiveUpdateRequest,
request: Request,
db_session: AsyncSession = Depends(get_db_session),
admin_user: User = Depends(get_api_admin_user),
):
target_user = await user_service.get_user_by_id(db_session, user_id)
if target_user is None:
raise ApiException(
status_code=404,
code="user_not_found",
message="User not found.",
)
if (
int(target_user.id) == int(admin_user.id)
and bool(target_user.is_active)
and not bool(payload.is_active)
):
raise ApiException(
status_code=400,
code="cannot_deactivate_self",
message="You cannot deactivate your own account.",
)
updated_user = await user_service.set_user_active(
db_session,
user=target_user,
is_active=bool(payload.is_active),
)
logger.info(
"api.admin.user_active_updated",
extra={
"event": "api.admin.user_active_updated",
"admin_user_id": int(admin_user.id),
"target_user_id": int(updated_user.id),
"is_active": bool(updated_user.is_active),
},
)
return success_payload(
request,
data=_serialize_user(updated_user),
)
@router.post(
"/{user_id}/reset-password",
response_model=MessageEnvelope,
)
async def reset_user_password(
user_id: int,
payload: AdminResetPasswordRequest,
request: Request,
db_session: AsyncSession = Depends(get_db_session),
auth_service: AuthService = Depends(get_auth_service),
admin_user: User = Depends(get_api_admin_user),
):
target_user = await user_service.get_user_by_id(db_session, user_id)
if target_user is None:
raise ApiException(
status_code=404,
code="user_not_found",
message="User not found.",
)
try:
validated_password = user_service.validate_password(payload.new_password)
except user_service.UserServiceError as exc:
raise ApiException(
status_code=400,
code="invalid_password",
message=str(exc),
) from exc
await user_service.set_user_password_hash(
db_session,
user=target_user,
password_hash=auth_service.hash_password(validated_password),
)
logger.info(
"api.admin.user_password_reset",
extra={
"event": "api.admin.user_password_reset",
"admin_user_id": int(admin_user.id),
"target_user_id": int(target_user.id),
},
)
return success_payload(
request,
data={"message": f"Password reset: {target_user.email}"},
)