from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy.orm import Session from app.core.config import get_settings from app.db.session import get_db from app.repositories.users_repo import UsersRepository from app.repositories.user_sites_repo import UserSitesRepository from app.schemas.idp_admin import ProviderEnsureUserRequest, ProviderEnsureUserResponse from app.schemas.internal import InternalUpsertUserBySubResponse, InternalUserRoleItem, InternalUserRoleResponse from app.schemas.users import UserUpsertBySubRequest from app.security.api_client_auth import require_api_client from app.services.idp_admin_service import ProviderAdminService from app.services.runtime_cache import runtime_cache router = APIRouter(prefix="/internal", tags=["internal"], dependencies=[Depends(require_api_client)]) @router.post("/users/upsert-by-sub", response_model=InternalUpsertUserBySubResponse) def upsert_user_by_sub( payload: UserUpsertBySubRequest, db: Session = Depends(get_db), ) -> InternalUpsertUserBySubResponse: repo = UsersRepository(db) user = repo.upsert_by_sub( user_sub=payload.user_sub, username=payload.username, email=payload.email, display_name=payload.display_name, is_active=payload.is_active, status=payload.status, ) return InternalUpsertUserBySubResponse( id=user.id, user_sub=user.user_sub, provider_user_id=user.provider_user_id, username=user.username, email=user.email, display_name=user.display_name, is_active=user.is_active, status=user.status, ) def _build_user_role_rows(db: Session, user_sub: str) -> list[tuple[str, str, str, str, str, str, str, str, str]]: users_repo = UsersRepository(db) user_sites_repo = UserSitesRepository(db) user = users_repo.get_by_sub(user_sub) if user is None: return [] rows = user_sites_repo.get_user_role_rows(user.id) return [ ( site.site_key, site.display_name, company.company_key, company.name, system.system_key, system.name, role.role_key, role.role_code, role.name, ) for site, company, role, system in rows ] @router.get("/users/{user_sub}/roles", response_model=InternalUserRoleResponse) def get_user_roles(user_sub: str, db: Session = Depends(get_db)) -> InternalUserRoleResponse: cache_key = f"internal:user_roles:{user_sub}" cached = runtime_cache.get(cache_key) if isinstance(cached, InternalUserRoleResponse): return cached rows = _build_user_role_rows(db, user_sub) result = InternalUserRoleResponse( user_sub=user_sub, roles=[ InternalUserRoleItem( site_key=site_key, site_display_name=site_display_name, company_key=company_key, company_display_name=company_display_name, system_key=system_key, system_name=system_name, role_key=role_key, role_code=role_code, role_name=role_name, ) for ( site_key, site_display_name, company_key, company_display_name, system_key, system_name, role_key, role_code, role_name, ) in rows ], ) runtime_cache.set(cache_key, result, ttl_seconds=30) return result @router.post("/provider/users/ensure", response_model=ProviderEnsureUserResponse) def ensure_idp_user( payload: ProviderEnsureUserRequest, db: Session = Depends(get_db), ) -> ProviderEnsureUserResponse: settings = get_settings() idp_service = ProviderAdminService(settings=settings) sync_result = idp_service.ensure_user( sub=payload.user_sub, email=payload.email, username=payload.username, display_name=payload.display_name, is_active=payload.is_active, ) users_repo = UsersRepository(db) resolved_sub = payload.user_sub or sync_result.user_sub or "" if not resolved_sub: raise HTTPException(status_code=status.HTTP_502_BAD_GATEWAY, detail="idp_missing_sub") users_repo.upsert_by_sub( user_sub=resolved_sub, username=payload.username, email=payload.email, display_name=payload.display_name, is_active=payload.is_active, status="active", provider_user_id=sync_result.user_id, ) return ProviderEnsureUserResponse(provider_user_id=sync_result.user_id, action=sync_result.action)