From ce181ebf67353160a7bf02bb030252c42711e53d Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 3 Apr 2026 00:53:31 +0800 Subject: [PATCH] refactor(idp-groups): use display name as keycloak group name --- app/api/admin_catalog.py | 78 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 74 insertions(+), 4 deletions(-) diff --git a/app/api/admin_catalog.py b/app/api/admin_catalog.py index 50c0dfc..1494343 100644 --- a/app/api/admin_catalog.py +++ b/app/api/admin_catalog.py @@ -117,6 +117,20 @@ def _member_item(user) -> MemberItem: ) +def _company_group_name(display_name: str, company_key: str) -> str: + normalized = display_name.strip() if isinstance(display_name, str) else "" + if not normalized: + return company_key + return normalized + + +def _site_group_name(display_name: str, site_key: str) -> str: + normalized = display_name.strip() if isinstance(display_name, str) else "" + if not normalized: + return site_key + return normalized + + @router.get("/companies", response_model=ListResponse) def list_companies( db: Session = Depends(get_db), @@ -135,8 +149,9 @@ def create_company(payload: CompanyCreateRequest, db: Session = Depends(get_db)) repo = CompaniesRepository(db) idp = KeycloakAdminService(get_settings()) company_key = _generate_unique_key("CP", lambda key: repo.get_by_key(key) is not None) + group_name = _company_group_name(payload.display_name, company_key) group = idp.ensure_group( - name=company_key, + name=group_name, attributes={ "member_entity_type": "company", "company_key": company_key, @@ -164,9 +179,10 @@ def update_company(company_key: str, payload: CompanyUpdateRequest, db: Session resolved_display_name = payload.display_name if payload.display_name is not None else item.display_name resolved_status = payload.status if payload.status is not None else item.status resolved_group_id = payload.idp_group_id or item.idp_group_id + group_name = _company_group_name(resolved_display_name, company_key) group = idp.ensure_group( group_id=resolved_group_id, - name=company_key, + name=group_name, attributes={ "member_entity_type": "company", "company_key": company_key, @@ -242,9 +258,10 @@ def create_site(payload: SiteCreateRequest, db: Session = Depends(get_db)) -> Si raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="company_not_found") site_key = _generate_unique_key("ST", lambda key: sites_repo.get_by_key(key) is not None) + group_name = _site_group_name(payload.display_name, site_key) group = idp.ensure_group( group_id=None, - name=site_key, + name=group_name, parent_group_id=company.idp_group_id, attributes={ "member_entity_type": "site", @@ -290,9 +307,10 @@ def update_site(site_key: str, payload: SiteUpdateRequest, db: Session = Depends resolved_domain = payload.domain if payload.domain is not None else item.domain resolved_status = payload.status if payload.status is not None else item.status resolved_group_id = payload.idp_group_id or item.idp_group_id + group_name = _site_group_name(resolved_display_name, site_key) group = idp.ensure_group( group_id=resolved_group_id, - name=site_key, + name=group_name, parent_group_id=target_company.idp_group_id, attributes={ "member_entity_type": "site", @@ -823,6 +841,58 @@ def sync_catalog_from_keycloak(db: Session = Depends(get_db), force: bool = Quer return sync_from_keycloak(db, force=force) +@router.post("/sync/keycloak-group-names") +def sync_keycloak_group_names(db: Session = Depends(get_db)) -> dict[str, int]: + companies_repo = CompaniesRepository(db) + sites_repo = SitesRepository(db) + idp = KeycloakAdminService(get_settings()) + + companies, _ = companies_repo.list(limit=5000, offset=0) + company_count = 0 + for company in companies: + if not company.idp_group_id: + continue + group_name = _company_group_name(company.display_name, company.company_key) + idp.ensure_group( + group_id=company.idp_group_id, + name=group_name, + attributes={ + "member_entity_type": "company", + "company_key": company.company_key, + "display_name": company.display_name, + "status": company.status, + }, + ) + company_count += 1 + + sites, _ = sites_repo.list(limit=5000, offset=0) + site_count = 0 + company_map = {company.id: company for company in companies} + for site in sites: + if not site.idp_group_id: + continue + company = company_map.get(site.company_id) + if not company: + continue + group_name = _site_group_name(site.display_name, site.site_key) + idp.ensure_group( + group_id=site.idp_group_id, + name=group_name, + parent_group_id=company.idp_group_id, + attributes={ + "member_entity_type": "site", + "site_key": site.site_key, + "company_key": company.company_key, + "display_name": site.display_name, + "domain": site.domain or "", + "status": site.status, + }, + ) + site_count += 1 + + return {"companies_updated": company_count, "sites_updated": site_count} + + @router.post("/api-clients", response_model=ApiClientCreateResponse) def create_api_client(payload: ApiClientCreateRequest, db: Session = Depends(get_db)) -> ApiClientCreateResponse: repo = ApiClientsRepository(db)