This commit is contained in:
Timothy Jaeryang Baek
2026-03-17 17:58:01 -05:00
parent fcf7208352
commit de3317e26b
220 changed files with 17200 additions and 22836 deletions
+79 -113
View File
@@ -54,7 +54,7 @@ router = APIRouter()
PAGE_ITEM_COUNT = 30
@router.get("/", response_model=UserGroupIdsListResponse)
@router.get('/', response_model=UserGroupIdsListResponse)
async def get_users(
query: Optional[str] = None,
order_by: Optional[str] = None,
@@ -70,38 +70,38 @@ async def get_users(
filter = {}
if query:
filter["query"] = query
filter['query'] = query
if order_by:
filter["order_by"] = order_by
filter['order_by'] = order_by
if direction:
filter["direction"] = direction
filter['direction'] = direction
filter["direction"] = direction
filter['direction'] = direction
result = Users.get_users(filter=filter, skip=skip, limit=limit, db=db)
users = result["users"]
total = result["total"]
users = result['users']
total = result['total']
# Fetch groups for all users in a single query to avoid N+1
user_ids = [user.id for user in users]
user_groups = Groups.get_groups_by_member_ids(user_ids, db=db)
return {
"users": [
'users': [
UserGroupIdsModel(
**{
**user.model_dump(),
"group_ids": [group.id for group in user_groups.get(user.id, [])],
'group_ids': [group.id for group in user_groups.get(user.id, [])],
}
)
for user in users
],
"total": total,
'total': total,
}
@router.get("/all", response_model=UserInfoListResponse)
@router.get('/all', response_model=UserInfoListResponse)
async def get_all_users(
user=Depends(get_admin_user),
db: Session = Depends(get_session),
@@ -109,7 +109,7 @@ async def get_all_users(
return Users.get_users(db=db)
@router.get("/search", response_model=UserInfoListResponse)
@router.get('/search', response_model=UserInfoListResponse)
async def search_users(
query: Optional[str] = None,
order_by: Optional[str] = None,
@@ -125,11 +125,11 @@ async def search_users(
filter = {}
if query:
filter["query"] = query
filter['query'] = query
if order_by:
filter["order_by"] = order_by
filter['order_by'] = order_by
if direction:
filter["direction"] = direction
filter['direction'] = direction
return Users.get_users(filter=filter, skip=skip, limit=limit, db=db)
@@ -139,10 +139,8 @@ async def search_users(
############################
@router.get("/groups")
async def get_user_groups(
user=Depends(get_verified_user), db: Session = Depends(get_session)
):
@router.get('/groups')
async def get_user_groups(user=Depends(get_verified_user), db: Session = Depends(get_session)):
return Groups.get_groups_by_member_id(user.id, db=db)
@@ -151,15 +149,13 @@ async def get_user_groups(
############################
@router.get("/permissions")
@router.get('/permissions')
async def get_user_permissisions(
request: Request,
user=Depends(get_verified_user),
db: Session = Depends(get_session),
):
user_permissions = get_permissions(
user.id, request.app.state.config.USER_PERMISSIONS, db=db
)
user_permissions = get_permissions(user.id, request.app.state.config.USER_PERMISSIONS, db=db)
return user_permissions
@@ -249,34 +245,20 @@ class UserPermissions(BaseModel):
settings: SettingsPermissions
@router.get("/default/permissions", response_model=UserPermissions)
@router.get('/default/permissions', response_model=UserPermissions)
async def get_default_user_permissions(request: Request, user=Depends(get_admin_user)):
return {
"workspace": WorkspacePermissions(
**request.app.state.config.USER_PERMISSIONS.get("workspace", {})
),
"sharing": SharingPermissions(
**request.app.state.config.USER_PERMISSIONS.get("sharing", {})
),
"access_grants": AccessGrantsPermissions(
**request.app.state.config.USER_PERMISSIONS.get("access_grants", {})
),
"chat": ChatPermissions(
**request.app.state.config.USER_PERMISSIONS.get("chat", {})
),
"features": FeaturesPermissions(
**request.app.state.config.USER_PERMISSIONS.get("features", {})
),
"settings": SettingsPermissions(
**request.app.state.config.USER_PERMISSIONS.get("settings", {})
),
'workspace': WorkspacePermissions(**request.app.state.config.USER_PERMISSIONS.get('workspace', {})),
'sharing': SharingPermissions(**request.app.state.config.USER_PERMISSIONS.get('sharing', {})),
'access_grants': AccessGrantsPermissions(**request.app.state.config.USER_PERMISSIONS.get('access_grants', {})),
'chat': ChatPermissions(**request.app.state.config.USER_PERMISSIONS.get('chat', {})),
'features': FeaturesPermissions(**request.app.state.config.USER_PERMISSIONS.get('features', {})),
'settings': SettingsPermissions(**request.app.state.config.USER_PERMISSIONS.get('settings', {})),
}
@router.post("/default/permissions")
async def update_default_user_permissions(
request: Request, form_data: UserPermissions, user=Depends(get_admin_user)
):
@router.post('/default/permissions')
async def update_default_user_permissions(request: Request, form_data: UserPermissions, user=Depends(get_admin_user)):
request.app.state.config.USER_PERMISSIONS = form_data.model_dump()
return request.app.state.config.USER_PERMISSIONS
@@ -286,10 +268,8 @@ async def update_default_user_permissions(
############################
@router.get("/user/settings", response_model=Optional[UserSettings])
async def get_user_settings_by_session_user(
user=Depends(get_verified_user), db: Session = Depends(get_session)
):
@router.get('/user/settings', response_model=Optional[UserSettings])
async def get_user_settings_by_session_user(user=Depends(get_verified_user), db: Session = Depends(get_session)):
user = Users.get_user_by_id(user.id, db=db)
if user:
return user.settings
@@ -305,7 +285,7 @@ async def get_user_settings_by_session_user(
############################
@router.post("/user/settings/update", response_model=UserSettings)
@router.post('/user/settings/update', response_model=UserSettings)
async def update_user_settings_by_session_user(
request: Request,
form_data: UserSettings,
@@ -313,19 +293,19 @@ async def update_user_settings_by_session_user(
db: Session = Depends(get_session),
):
updated_user_settings = form_data.model_dump()
ui_settings = updated_user_settings.get("ui")
ui_settings = updated_user_settings.get('ui')
if (
user.role != "admin"
user.role != 'admin'
and ui_settings is not None
and "toolServers" in ui_settings.keys()
and 'toolServers' in ui_settings.keys()
and not has_permission(
user.id,
"features.direct_tool_servers",
'features.direct_tool_servers',
request.app.state.config.USER_PERMISSIONS,
)
):
# If the user is not an admin and does not have permission to use tool servers, remove the key
updated_user_settings["ui"].pop("toolServers", None)
updated_user_settings['ui'].pop('toolServers', None)
user = Users.update_user_settings_by_id(user.id, updated_user_settings, db=db)
if user:
@@ -342,7 +322,7 @@ async def update_user_settings_by_session_user(
############################
@router.get("/user/status")
@router.get('/user/status')
async def get_user_status_by_session_user(
request: Request,
user=Depends(get_verified_user),
@@ -368,7 +348,7 @@ async def get_user_status_by_session_user(
############################
@router.post("/user/status/update")
@router.post('/user/status/update')
async def update_user_status_by_session_user(
request: Request,
form_data: UserStatus,
@@ -396,10 +376,8 @@ async def update_user_status_by_session_user(
############################
@router.get("/user/info", response_model=Optional[dict])
async def get_user_info_by_session_user(
user=Depends(get_verified_user), db: Session = Depends(get_session)
):
@router.get('/user/info', response_model=Optional[dict])
async def get_user_info_by_session_user(user=Depends(get_verified_user), db: Session = Depends(get_session)):
user = Users.get_user_by_id(user.id, db=db)
if user:
return user.info
@@ -415,7 +393,7 @@ async def get_user_info_by_session_user(
############################
@router.post("/user/info/update", response_model=Optional[dict])
@router.post('/user/info/update', response_model=Optional[dict])
async def update_user_info_by_session_user(
form_data: dict, user=Depends(get_verified_user), db: Session = Depends(get_session)
):
@@ -424,9 +402,7 @@ async def update_user_info_by_session_user(
if user.info is None:
user.info = {}
user = Users.update_user_by_id(
user.id, {"info": {**user.info, **form_data}}, db=db
)
user = Users.update_user_by_id(user.id, {'info': {**user.info, **form_data}}, db=db)
if user:
return user.info
else:
@@ -452,17 +428,15 @@ class UserActiveResponse(UserStatus):
groups: Optional[list] = []
is_active: bool
model_config = ConfigDict(extra="allow")
model_config = ConfigDict(extra='allow')
@router.get("/{user_id}", response_model=UserActiveResponse)
async def get_user_by_id(
user_id: str, user=Depends(get_admin_user), db: Session = Depends(get_session)
):
@router.get('/{user_id}', response_model=UserActiveResponse)
async def get_user_by_id(user_id: str, user=Depends(get_admin_user), db: Session = Depends(get_session)):
# Check if user_id is a shared chat
# If it is, get the user_id from the chat
if user_id.startswith("shared-"):
chat_id = user_id.replace("shared-", "")
if user_id.startswith('shared-'):
chat_id = user_id.replace('shared-', '')
chat = Chats.get_chat_by_id(chat_id)
if chat:
user_id = chat.user_id
@@ -478,8 +452,8 @@ async def get_user_by_id(
return UserActiveResponse(
**{
**user.model_dump(),
"groups": [{"id": group.id, "name": group.name} for group in groups],
"is_active": Users.is_user_active(user_id, db=db),
'groups': [{'id': group.id, 'name': group.name} for group in groups],
'is_active': Users.is_user_active(user_id, db=db),
}
)
else:
@@ -489,18 +463,16 @@ async def get_user_by_id(
)
@router.get("/{user_id}/info", response_model=UserInfoResponse)
async def get_user_info_by_id(
user_id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)
):
@router.get('/{user_id}/info', response_model=UserInfoResponse)
async def get_user_info_by_id(user_id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)):
user = Users.get_user_by_id(user_id, db=db)
if user:
groups = Groups.get_groups_by_member_id(user_id, db=db)
return UserInfoResponse(
**{
**user.model_dump(),
"groups": [{"id": group.id, "name": group.name} for group in groups],
"is_active": Users.is_user_active(user_id, db=db),
'groups': [{'id': group.id, 'name': group.name} for group in groups],
'is_active': Users.is_user_active(user_id, db=db),
}
)
else:
@@ -510,10 +482,8 @@ async def get_user_info_by_id(
)
@router.get("/{user_id}/oauth/sessions")
async def get_user_oauth_sessions_by_id(
user_id: str, user=Depends(get_admin_user), db: Session = Depends(get_session)
):
@router.get('/{user_id}/oauth/sessions')
async def get_user_oauth_sessions_by_id(user_id: str, user=Depends(get_admin_user), db: Session = Depends(get_session)):
sessions = OAuthSessions.get_sessions_by_user_id(user_id, db=db)
if sessions and len(sessions) > 0:
return sessions
@@ -529,32 +499,32 @@ async def get_user_oauth_sessions_by_id(
############################
@router.get("/{user_id}/profile/image")
@router.get('/{user_id}/profile/image')
def get_user_profile_image_by_id(user_id: str, user=Depends(get_verified_user)):
user = Users.get_user_by_id(user_id)
if user:
if user.profile_image_url:
# check if it's url or base64
if user.profile_image_url.startswith("http"):
if user.profile_image_url.startswith('http'):
return Response(
status_code=status.HTTP_302_FOUND,
headers={"Location": user.profile_image_url},
headers={'Location': user.profile_image_url},
)
elif user.profile_image_url.startswith("data:image"):
elif user.profile_image_url.startswith('data:image'):
try:
header, base64_data = user.profile_image_url.split(",", 1)
header, base64_data = user.profile_image_url.split(',', 1)
image_data = base64.b64decode(base64_data)
image_buffer = io.BytesIO(image_data)
media_type = header.split(";")[0].lstrip("data:")
media_type = header.split(';')[0].lstrip('data:')
return StreamingResponse(
image_buffer,
media_type=media_type,
headers={"Content-Disposition": "inline"},
headers={'Content-Disposition': 'inline'},
)
except Exception as e:
pass
return FileResponse(f"{STATIC_DIR}/user.png")
return FileResponse(f'{STATIC_DIR}/user.png')
else:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
@@ -567,12 +537,12 @@ def get_user_profile_image_by_id(user_id: str, user=Depends(get_verified_user)):
############################
@router.get("/{user_id}/active", response_model=dict)
@router.get('/{user_id}/active', response_model=dict)
async def get_user_active_status_by_id(
user_id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)
):
return {
"active": Users.is_user_active(user_id, db=db),
'active': Users.is_user_active(user_id, db=db),
}
@@ -581,7 +551,7 @@ async def get_user_active_status_by_id(
############################
@router.post("/{user_id}/update", response_model=Optional[UserModel])
@router.post('/{user_id}/update', response_model=Optional[UserModel])
async def update_user_by_id(
user_id: str,
form_data: UserUpdateForm,
@@ -600,7 +570,7 @@ async def update_user_by_id(
detail=ERROR_MESSAGES.ACTION_PROHIBITED,
)
if form_data.role != "admin":
if form_data.role != 'admin':
# If the primary admin is trying to change their own role, prevent it
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
@@ -608,10 +578,10 @@ async def update_user_by_id(
)
except Exception as e:
log.error(f"Error checking primary admin status: {e}")
log.error(f'Error checking primary admin status: {e}')
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Could not verify primary admin status.",
detail='Could not verify primary admin status.',
)
user = Users.get_user_by_id(user_id, db=db)
@@ -638,10 +608,10 @@ async def update_user_by_id(
updated_user = Users.update_user_by_id(
user_id,
{
"role": form_data.role,
"name": form_data.name,
"email": form_data.email.lower(),
"profile_image_url": form_data.profile_image_url,
'role': form_data.role,
'name': form_data.name,
'email': form_data.email.lower(),
'profile_image_url': form_data.profile_image_url,
},
db=db,
)
@@ -665,10 +635,8 @@ async def update_user_by_id(
############################
@router.delete("/{user_id}", response_model=bool)
async def delete_user_by_id(
user_id: str, user=Depends(get_admin_user), db: Session = Depends(get_session)
):
@router.delete('/{user_id}', response_model=bool)
async def delete_user_by_id(user_id: str, user=Depends(get_admin_user), db: Session = Depends(get_session)):
# Prevent deletion of the primary admin user
try:
first_user = Users.get_first_user(db=db)
@@ -678,10 +646,10 @@ async def delete_user_by_id(
detail=ERROR_MESSAGES.ACTION_PROHIBITED,
)
except Exception as e:
log.error(f"Error checking primary admin status: {e}")
log.error(f'Error checking primary admin status: {e}')
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Could not verify primary admin status.",
detail='Could not verify primary admin status.',
)
if user.id != user_id:
@@ -707,8 +675,6 @@ async def delete_user_by_id(
############################
@router.get("/{user_id}/groups")
async def get_user_groups_by_id(
user_id: str, user=Depends(get_admin_user), db: Session = Depends(get_session)
):
@router.get('/{user_id}/groups')
async def get_user_groups_by_id(user_id: str, user=Depends(get_admin_user), db: Session = Depends(get_session)):
return Groups.get_groups_by_member_id(user_id, db=db)