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.logging_utils import structured_log from app.services.domains.users import application 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) structured_log(logger, "info", "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 structured_log( logger, "info", "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), ) structured_log( logger, "info", "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), ) structured_log( logger, "info", "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}"}, )