88import logging
99from typing import Any
1010
11+ from bson import ObjectId
12+ from bson .errors import InvalidId
1113from fastapi import APIRouter , Depends , HTTPException
1214from pydantic import BaseModel , Field
1315
@@ -50,27 +52,27 @@ class AdminUserResponse(BaseModel):
5052 created_at : str | None = None
5153
5254
55+ def _to_admin_response (u : dict ) -> AdminUserResponse :
56+ return AdminUserResponse (
57+ id = str (u ["_id" ]),
58+ username = u .get ("username" , "" ),
59+ email = u .get ("email" ),
60+ github_id = str (u .get ("github_id" , "" )),
61+ avatar_url = u .get ("avatar_url" ),
62+ role = u .get ("role" , "user" ),
63+ plan = u .get ("plan" , "free" ),
64+ created_at = str (u .get ("created_at" , "" )),
65+ )
66+
67+
5368@router .get ("/users" , response_model = list [AdminUserResponse ])
5469async def list_users (
5570 _admin : User = Depends (require_admin ),
5671) -> list [AdminUserResponse ]:
5772 """List all users with their roles and plans."""
5873 user_repo = UserRepository ()
5974 users = await user_repo .list (limit = 500 )
60-
61- return [
62- AdminUserResponse (
63- id = str (u ["_id" ]),
64- username = u .get ("username" , "" ),
65- email = u .get ("email" ),
66- github_id = str (u .get ("github_id" , "" )),
67- avatar_url = u .get ("avatar_url" ),
68- role = u .get ("role" , "user" ),
69- plan = u .get ("plan" , "free" ),
70- created_at = str (u .get ("created_at" , "" )),
71- )
72- for u in users
73- ]
75+ return [_to_admin_response (u ) for u in users ]
7476
7577
7678@router .patch ("/users/{user_id}" , response_model = AdminUserResponse )
@@ -80,6 +82,11 @@ async def update_user_role(
8082 _admin : User = Depends (require_admin ),
8183) -> AdminUserResponse :
8284 """Update a user's role and/or plan."""
85+ try :
86+ ObjectId (user_id )
87+ except (InvalidId , Exception ):
88+ raise HTTPException (status_code = 400 , detail = "Invalid user ID format" )
89+
8390 update_data : dict [str , Any ] = {}
8491
8592 if update .role is not None :
@@ -88,6 +95,11 @@ async def update_user_role(
8895 status_code = 400 ,
8996 detail = f"Invalid role: { update .role } . Must be one of { VALID_ROLES } " ,
9097 )
98+ if update .role != "admin" and user_id == _admin .id :
99+ raise HTTPException (
100+ status_code = 400 ,
101+ detail = "Cannot demote yourself. Ask another admin." ,
102+ )
91103 update_data ["role" ] = update .role
92104
93105 if update .plan is not None :
@@ -107,15 +119,8 @@ async def update_user_role(
107119 if not updated :
108120 raise HTTPException (status_code = 404 , detail = "User not found" )
109121
110- logger .info (f"[Admin] User { user_id } updated: { update_data } by admin { _admin .id } " )
111-
112- return AdminUserResponse (
113- id = str (updated ["_id" ]),
114- username = updated .get ("username" , "" ),
115- email = updated .get ("email" ),
116- github_id = str (updated .get ("github_id" , "" )),
117- avatar_url = updated .get ("avatar_url" ),
118- role = updated .get ("role" , "user" ),
119- plan = updated .get ("plan" , "free" ),
120- created_at = str (updated .get ("created_at" , "" )),
122+ logger .info (
123+ "[Admin] User %s updated: %s by admin %s" , user_id , update_data , _admin .id
121124 )
125+
126+ return _to_admin_response (updated )
0 commit comments