- Updated python_coding_microproject.csv to use NVIDIA NIM Kimi K2 - Updated kali_linux_shell_simulator.csv to use NVIDIA NIM Kimi K2 - Made more general-purpose (flexible targets, expanded tools) - Added nemotron-mini-agent.csv for fast local inference via Ollama - Added nemotron-agent.csv for advanced reasoning via Ollama - Added wiki page: Projects for NVIDIA NIMs and Nemotron
349 lines
11 KiB
Python
349 lines
11 KiB
Python
"""
|
|
User API endpoints for GT 2.0 Tenant Backend
|
|
|
|
Handles user preferences and favorite agents management.
|
|
Follows GT 2.0 principles: no mocks, real implementations, fail fast.
|
|
"""
|
|
|
|
import structlog
|
|
from fastapi import APIRouter, HTTPException, status, Depends, Header
|
|
from typing import Optional
|
|
|
|
from app.services.user_service import UserService
|
|
from app.schemas.user import (
|
|
UserPreferencesResponse,
|
|
UpdateUserPreferencesRequest,
|
|
FavoriteAgentsResponse,
|
|
UpdateFavoriteAgentsRequest,
|
|
AddFavoriteAgentRequest,
|
|
RemoveFavoriteAgentRequest,
|
|
CustomCategoriesResponse,
|
|
UpdateCustomCategoriesRequest
|
|
)
|
|
|
|
logger = structlog.get_logger()
|
|
router = APIRouter(prefix="/users", tags=["users"])
|
|
|
|
|
|
def get_user_context(
|
|
x_tenant_domain: Optional[str] = Header(None),
|
|
x_user_id: Optional[str] = Header(None),
|
|
x_user_email: Optional[str] = Header(None)
|
|
) -> tuple[str, str, str]:
|
|
"""Extract user context from headers"""
|
|
if not x_tenant_domain:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail="X-Tenant-Domain header is required"
|
|
)
|
|
if not x_user_id:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail="X-User-ID header is required"
|
|
)
|
|
|
|
return x_tenant_domain, x_user_id, x_user_email or x_user_id
|
|
|
|
|
|
# User Preferences Endpoints
|
|
|
|
@router.get("/me/preferences", response_model=UserPreferencesResponse)
|
|
async def get_user_preferences(
|
|
user_context: tuple = Depends(get_user_context)
|
|
):
|
|
"""
|
|
Get current user's preferences from PostgreSQL.
|
|
|
|
Returns all user preferences stored in the JSONB preferences column.
|
|
"""
|
|
tenant_domain, user_id, user_email = user_context
|
|
|
|
try:
|
|
logger.info("Getting user preferences", user_id=user_id, tenant_domain=tenant_domain)
|
|
|
|
service = UserService(tenant_domain, user_id, user_email)
|
|
preferences = await service.get_user_preferences()
|
|
|
|
return UserPreferencesResponse(preferences=preferences)
|
|
|
|
except Exception as e:
|
|
logger.error("Failed to get user preferences", error=str(e), user_id=user_id)
|
|
raise HTTPException(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
detail="Failed to retrieve user preferences"
|
|
)
|
|
|
|
|
|
@router.put("/me/preferences")
|
|
async def update_user_preferences(
|
|
request: UpdateUserPreferencesRequest,
|
|
user_context: tuple = Depends(get_user_context)
|
|
):
|
|
"""
|
|
Update current user's preferences in PostgreSQL.
|
|
|
|
Merges provided preferences with existing preferences using JSONB || operator.
|
|
"""
|
|
tenant_domain, user_id, user_email = user_context
|
|
|
|
try:
|
|
logger.info("Updating user preferences", user_id=user_id, tenant_domain=tenant_domain)
|
|
|
|
service = UserService(tenant_domain, user_id, user_email)
|
|
success = await service.update_user_preferences(request.preferences)
|
|
|
|
if not success:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="User not found"
|
|
)
|
|
|
|
return {"success": True, "message": "Preferences updated successfully"}
|
|
|
|
except HTTPException:
|
|
raise
|
|
except Exception as e:
|
|
logger.error("Failed to update user preferences", error=str(e), user_id=user_id)
|
|
raise HTTPException(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
detail="Failed to update user preferences"
|
|
)
|
|
|
|
|
|
# Favorite Agents Endpoints
|
|
|
|
@router.get("/me/favorite-agents", response_model=FavoriteAgentsResponse)
|
|
async def get_favorite_agents(
|
|
user_context: tuple = Depends(get_user_context)
|
|
):
|
|
"""
|
|
Get current user's favorited agent IDs from PostgreSQL.
|
|
|
|
Returns list of agent UUIDs that the user has marked as favorites.
|
|
"""
|
|
tenant_domain, user_id, user_email = user_context
|
|
|
|
try:
|
|
logger.info("Getting favorite agent IDs", user_id=user_id, tenant_domain=tenant_domain)
|
|
|
|
service = UserService(tenant_domain, user_id, user_email)
|
|
favorite_ids = await service.get_favorite_agent_ids()
|
|
|
|
return FavoriteAgentsResponse(favorite_agent_ids=favorite_ids)
|
|
|
|
except Exception as e:
|
|
logger.error("Failed to get favorite agents", error=str(e), user_id=user_id)
|
|
raise HTTPException(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
detail="Failed to retrieve favorite agents"
|
|
)
|
|
|
|
|
|
@router.put("/me/favorite-agents")
|
|
async def update_favorite_agents(
|
|
request: UpdateFavoriteAgentsRequest,
|
|
user_context: tuple = Depends(get_user_context)
|
|
):
|
|
"""
|
|
Update current user's favorite agent IDs in PostgreSQL.
|
|
|
|
Replaces the entire list of favorite agent IDs with the provided list.
|
|
"""
|
|
tenant_domain, user_id, user_email = user_context
|
|
|
|
try:
|
|
logger.info(
|
|
"Updating favorite agent IDs",
|
|
user_id=user_id,
|
|
tenant_domain=tenant_domain,
|
|
agent_count=len(request.agent_ids)
|
|
)
|
|
|
|
service = UserService(tenant_domain, user_id, user_email)
|
|
success = await service.update_favorite_agent_ids(request.agent_ids)
|
|
|
|
if not success:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="User not found"
|
|
)
|
|
|
|
return {
|
|
"success": True,
|
|
"message": "Favorite agents updated successfully",
|
|
"favorite_agent_ids": request.agent_ids
|
|
}
|
|
|
|
except HTTPException:
|
|
raise
|
|
except Exception as e:
|
|
logger.error("Failed to update favorite agents", error=str(e), user_id=user_id)
|
|
raise HTTPException(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
detail="Failed to update favorite agents"
|
|
)
|
|
|
|
|
|
@router.post("/me/favorite-agents/add")
|
|
async def add_favorite_agent(
|
|
request: AddFavoriteAgentRequest,
|
|
user_context: tuple = Depends(get_user_context)
|
|
):
|
|
"""
|
|
Add a single agent to user's favorites.
|
|
|
|
Idempotent - does nothing if agent is already in favorites.
|
|
"""
|
|
tenant_domain, user_id, user_email = user_context
|
|
|
|
try:
|
|
logger.info(
|
|
"Adding agent to favorites",
|
|
user_id=user_id,
|
|
tenant_domain=tenant_domain,
|
|
agent_id=request.agent_id
|
|
)
|
|
|
|
service = UserService(tenant_domain, user_id, user_email)
|
|
success = await service.add_favorite_agent(request.agent_id)
|
|
|
|
if not success:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="User not found"
|
|
)
|
|
|
|
return {
|
|
"success": True,
|
|
"message": "Agent added to favorites",
|
|
"agent_id": request.agent_id
|
|
}
|
|
|
|
except HTTPException:
|
|
raise
|
|
except Exception as e:
|
|
logger.error("Failed to add favorite agent", error=str(e), user_id=user_id)
|
|
raise HTTPException(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
detail="Failed to add favorite agent"
|
|
)
|
|
|
|
|
|
@router.post("/me/favorite-agents/remove")
|
|
async def remove_favorite_agent(
|
|
request: RemoveFavoriteAgentRequest,
|
|
user_context: tuple = Depends(get_user_context)
|
|
):
|
|
"""
|
|
Remove a single agent from user's favorites.
|
|
|
|
Idempotent - does nothing if agent is not in favorites.
|
|
"""
|
|
tenant_domain, user_id, user_email = user_context
|
|
|
|
try:
|
|
logger.info(
|
|
"Removing agent from favorites",
|
|
user_id=user_id,
|
|
tenant_domain=tenant_domain,
|
|
agent_id=request.agent_id
|
|
)
|
|
|
|
service = UserService(tenant_domain, user_id, user_email)
|
|
success = await service.remove_favorite_agent(request.agent_id)
|
|
|
|
if not success:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="User not found"
|
|
)
|
|
|
|
return {
|
|
"success": True,
|
|
"message": "Agent removed from favorites",
|
|
"agent_id": request.agent_id
|
|
}
|
|
|
|
except HTTPException:
|
|
raise
|
|
except Exception as e:
|
|
logger.error("Failed to remove favorite agent", error=str(e), user_id=user_id)
|
|
raise HTTPException(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
detail="Failed to remove favorite agent"
|
|
)
|
|
|
|
|
|
# Custom Categories Endpoints
|
|
|
|
@router.get("/me/custom-categories", response_model=CustomCategoriesResponse)
|
|
async def get_custom_categories(
|
|
user_context: tuple = Depends(get_user_context)
|
|
):
|
|
"""
|
|
Get current user's custom agent categories from PostgreSQL.
|
|
|
|
Returns list of custom categories with name and description.
|
|
"""
|
|
tenant_domain, user_id, user_email = user_context
|
|
|
|
try:
|
|
logger.info("Getting custom categories", user_id=user_id, tenant_domain=tenant_domain)
|
|
|
|
service = UserService(tenant_domain, user_id, user_email)
|
|
categories = await service.get_custom_categories()
|
|
|
|
return CustomCategoriesResponse(categories=categories)
|
|
|
|
except Exception as e:
|
|
logger.error("Failed to get custom categories", error=str(e), user_id=user_id)
|
|
raise HTTPException(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
detail="Failed to retrieve custom categories"
|
|
)
|
|
|
|
|
|
@router.put("/me/custom-categories")
|
|
async def update_custom_categories(
|
|
request: UpdateCustomCategoriesRequest,
|
|
user_context: tuple = Depends(get_user_context)
|
|
):
|
|
"""
|
|
Update current user's custom agent categories in PostgreSQL.
|
|
|
|
Replaces the entire list of custom categories with the provided list.
|
|
"""
|
|
tenant_domain, user_id, user_email = user_context
|
|
|
|
try:
|
|
logger.info(
|
|
"Updating custom categories",
|
|
user_id=user_id,
|
|
tenant_domain=tenant_domain,
|
|
category_count=len(request.categories)
|
|
)
|
|
|
|
service = UserService(tenant_domain, user_id, user_email)
|
|
success = await service.update_custom_categories(request.categories)
|
|
|
|
if not success:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="User not found"
|
|
)
|
|
|
|
return {
|
|
"success": True,
|
|
"message": "Custom categories updated successfully",
|
|
"categories": [cat.dict() for cat in request.categories]
|
|
}
|
|
|
|
except HTTPException:
|
|
raise
|
|
except Exception as e:
|
|
logger.error("Failed to update custom categories", error=str(e), user_id=user_id)
|
|
raise HTTPException(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
detail="Failed to update custom categories"
|
|
)
|