diff --git a/app/api/admin_catalog.py b/app/api/admin_catalog.py index 1708d8c..b050e03 100644 --- a/app/api/admin_catalog.py +++ b/app/api/admin_catalog.py @@ -138,7 +138,6 @@ def list_companies( limit: int = Query(default=100, ge=1, le=500), offset: int = Query(default=0, ge=0), ) -> ListResponse: - sync_from_provider(db) repo = CompaniesRepository(db) items, total = repo.list(keyword=keyword, limit=limit, offset=offset) return ListResponse(items=[_company_item(i) for i in items], total=total, limit=limit, offset=offset) @@ -231,7 +230,6 @@ def list_sites( limit: int = Query(default=100, ge=1, le=500), offset: int = Query(default=0, ge=0), ) -> ListResponse: - sync_from_provider(db) companies_repo = CompaniesRepository(db) sites_repo = SitesRepository(db) company_id = None @@ -356,7 +354,6 @@ def list_systems( limit: int = Query(default=100, ge=1, le=500), offset: int = Query(default=0, ge=0), ) -> ListResponse: - sync_from_provider(db) repo = SystemsRepository(db) items, total = repo.list(keyword=keyword, status=status_filter, limit=limit, offset=offset) return ListResponse(items=[_system_item(i) for i in items], total=total, limit=limit, offset=offset) @@ -386,7 +383,6 @@ def list_roles( limit: int = Query(default=100, ge=1, le=500), offset: int = Query(default=0, ge=0), ) -> ListResponse: - sync_from_provider(db) systems_repo = SystemsRepository(db) roles_repo = RolesRepository(db) @@ -679,7 +675,6 @@ def list_members( limit: int = Query(default=100, ge=1, le=500), offset: int = Query(default=0, ge=0), ) -> ListResponse: - sync_from_provider(db) repo = UsersRepository(db) rows, total = repo.list(keyword=keyword, is_active=is_active, limit=limit, offset=offset) return ListResponse(items=[_member_item(r) for r in rows], total=total, limit=limit, offset=offset) diff --git a/app/services/idp_catalog_sync.py b/app/services/idp_catalog_sync.py index cfad473..5b50755 100644 --- a/app/services/idp_catalog_sync.py +++ b/app/services/idp_catalog_sync.py @@ -31,6 +31,7 @@ BUILTIN_CLIENT_IDS = { _sync_lock = threading.Lock() _last_synced_at = 0.0 +_last_systems_synced_at = 0.0 _min_sync_interval_sec = 30.0 @@ -304,3 +305,102 @@ def sync_from_provider(db: Session, *, force: bool = False) -> dict[str, int]: } finally: _sync_lock.release() + + +def sync_systems_from_provider(db: Session, *, force: bool = False) -> dict[str, int]: + global _last_systems_synced_at + now = time.time() + if not force and now - _last_systems_synced_at < _min_sync_interval_sec: + return {"synced": 0} + + if not _sync_lock.acquire(blocking=False): + return {"synced": 0} + + try: + now = time.time() + if not force and now - _last_systems_synced_at < _min_sync_interval_sec: + return {"synced": 0} + + idp = ProviderAdminService(get_settings()) + systems_repo = SystemsRepository(db) + roles_repo = RolesRepository(db) + + systems_created = 0 + systems_updated = 0 + roles_created = 0 + roles_updated = 0 + + client_rows = idp.list_clients() + for client in client_rows: + client_uuid = str(client.get("id", "")).strip() + client_id = str(client.get("clientId", "")).strip() + if not client_uuid or not client_id: + continue + if client_id in BUILTIN_CLIENT_IDS: + continue + + system = db.scalar(select(System).where(System.provider_client_id == client_id)) + system_name = str(client.get("name", "")).strip() or client_id + system_status = "active" if client.get("enabled", True) else "inactive" + if system is None: + system_key = _generate_unique_key("SY", lambda key: systems_repo.get_by_key(key) is not None) + system = systems_repo.create( + system_key=system_key, + name=system_name, + provider_client_id=client_id, + status=system_status, + ) + systems_created += 1 + else: + system = systems_repo.update( + system, + name=system_name, + status=system_status, + ) + systems_updated += 1 + + client_roles = idp.list_client_roles(client_uuid) + for role_row in client_roles: + if not isinstance(role_row, dict): + continue + role_name = str(role_row.get("name", "")).strip() + if not role_name: + continue + role_desc = str(role_row.get("description", "")).strip() or None + role_status = "active" if not role_row.get("composite", False) else "active" + role = db.scalar( + select(Role).where( + Role.system_id == system.id, + Role.provider_role_name == role_name, + ) + ) + if role is None: + role_key = _generate_unique_key("RL", lambda key: roles_repo.get_by_key(key) is not None) + roles_repo.create( + role_key=role_key, + system_id=system.id, + name=role_name, + description=role_desc, + provider_role_name=role_name, + status=role_status, + ) + roles_created += 1 + else: + roles_repo.update( + role, + name=role_name, + description=role_desc, + status=role_status, + ) + roles_updated += 1 + + _last_systems_synced_at = time.time() + return { + "synced": 1, + "systems_created": systems_created, + "systems_updated": systems_updated, + "roles_created": roles_created, + "roles_updated": roles_updated, + } + finally: + _sync_lock.release()