Files
member-platform/backend/app/api/internal.py

137 lines
4.7 KiB
Python

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]]:
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.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_name=role_name,
)
for (
site_key,
site_display_name,
company_key,
company_display_name,
system_key,
system_name,
role_key,
role_name,
) in rows
],
)
runtime_cache.set(cache_key, result, ttl_seconds=30)
return result
@router.post("/provider/users/ensure", response_model=ProviderEnsureUserResponse)
@router.post("/idp/users/ensure", response_model=ProviderEnsureUserResponse, include_in_schema=False)
@router.post("/keycloak/users/ensure", response_model=ProviderEnsureUserResponse, include_in_schema=False)
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)