from typing import Any import httpx class IrisAdapter: def __init__(self, base_url: str, api_key: str) -> None: self.base_url = base_url.rstrip("/") self.api_key = api_key def _headers(self) -> dict[str, str]: return {"Authorization": f"Bearer {self.api_key}"} if self.api_key else {} async def create_case(self, payload: dict[str, Any]) -> dict[str, Any]: headers = self._headers() async with httpx.AsyncClient(verify=False, timeout=20.0) as client: v2_url = f"{self.base_url}/api/v2/cases" response = await client.post(v2_url, json=payload, headers=headers) if response.status_code == 404: legacy_url = f"{self.base_url}/manage/cases/add" response = await client.post(legacy_url, json=payload, headers=headers) active_url = legacy_url else: active_url = v2_url try: response.raise_for_status() except httpx.HTTPStatusError as exc: detail = response.text.strip() raise RuntimeError( f"IRIS returned {response.status_code} for {active_url}. Response: {detail}" ) from exc return response.json() if response.content else {"status_code": response.status_code} async def update_case(self, case_id: str, payload: dict[str, Any]) -> dict[str, Any]: headers = self._headers() async with httpx.AsyncClient(verify=False, timeout=20.0) as client: v2_url = f"{self.base_url}/api/v2/cases/{case_id}" response = await client.put(v2_url, json=payload, headers=headers) if response.status_code == 404: legacy_url = f"{self.base_url}/manage/cases/update/{case_id}" response = await client.post(legacy_url, json=payload, headers=headers) active_url = legacy_url else: active_url = v2_url try: response.raise_for_status() except httpx.HTTPStatusError as exc: detail = response.text.strip() raise RuntimeError( f"IRIS returned {response.status_code} for {active_url}. Response: {detail}" ) from exc return response.json() if response.content else {"status_code": response.status_code} async def whoami(self) -> dict[str, Any]: headers = self._headers() url = f"{self.base_url}/user/whoami" async with httpx.AsyncClient(verify=False, timeout=20.0) as client: response = await client.get(url, headers=headers) response.raise_for_status() return response.json() if response.content else {"status_code": response.status_code} async def list_cases(self, limit: int = 50, offset: int = 0) -> dict[str, Any]: headers = self._headers() safe_limit = max(1, limit) safe_offset = max(0, offset) page = (safe_offset // safe_limit) + 1 v2_url = f"{self.base_url}/api/v2/cases" params = {"page": page, "per_page": safe_limit} async with httpx.AsyncClient(verify=False, timeout=20.0) as client: response = await client.get(v2_url, params=params, headers=headers) active_url = v2_url # Older IRIS releases may not expose /api/v2/cases for listing. if response.status_code == 404: legacy_url = f"{self.base_url}/manage/cases/list" legacy_attempts: list[tuple[str, dict[str, Any] | None, dict[str, Any] | None]] = [ ("post", None, {"start": safe_offset, "length": safe_limit, "draw": 1}), ("post", None, {"page": (safe_offset // safe_limit) + 1, "per_page": safe_limit}), ("get", {"limit": safe_limit, "offset": safe_offset}, None), ] for method, legacy_params, legacy_payload in legacy_attempts: if method == "post": response = await client.post( legacy_url, params=legacy_params, json=legacy_payload, headers=headers, ) else: response = await client.get( legacy_url, params=legacy_params, headers=headers, ) active_url = legacy_url if response.status_code not in {404, 405, 415}: break try: response.raise_for_status() except httpx.HTTPStatusError as exc: detail = response.text.strip() raise RuntimeError( f"IRIS returned {response.status_code} for {active_url}. Response: {detail}" ) from exc if not response.content: return {"status_code": response.status_code} result = response.json() # IRIS v2 response: { "total": int, "data": [...], "last_page": int, ... } if isinstance(result, dict) and isinstance(result.get("total"), int) and isinstance(result.get("data"), list): return result # pass v2 response through directly if isinstance(result, dict) and isinstance(result.get("data"), list): total = result.get("recordsTotal") if not isinstance(total, int): total = len(result["data"]) return { "items": result["data"], "total": total, "limit": safe_limit, "offset": safe_offset, "source": "legacy_manage_cases_list", "raw": result, } return result async def create_alert(self, payload: dict[str, Any]) -> dict[str, Any]: headers = self._headers() url = f"{self.base_url}/alerts/add" async with httpx.AsyncClient(verify=False, timeout=20.0) as client: response = await client.post(url, json=payload, headers=headers) try: response.raise_for_status() except httpx.HTTPStatusError as exc: detail = response.text.strip() raise RuntimeError( f"IRIS returned {response.status_code} for {url}. Response: {detail}" ) from exc return response.json() if response.content else {"status_code": response.status_code} async def get_case(self, case_id: int) -> dict[str, Any]: headers = self._headers() url = f"{self.base_url}/api/v2/cases/{case_id}" async with httpx.AsyncClient(verify=False, timeout=20.0) as client: response = await client.get(url, headers=headers) try: response.raise_for_status() except httpx.HTTPStatusError as exc: detail = response.text.strip() raise RuntimeError( f"IRIS returned {response.status_code} for {url}. Response: {detail}" ) from exc return response.json() if response.content else {} async def get_alert(self, alert_id: int) -> dict[str, Any]: headers = self._headers() url = f"{self.base_url}/alerts/{alert_id}" async with httpx.AsyncClient(verify=False, timeout=20.0) as client: response = await client.get(url, headers=headers) try: response.raise_for_status() except httpx.HTTPStatusError as exc: detail = response.text.strip() raise RuntimeError( f"IRIS returned {response.status_code} for {url}. Response: {detail}" ) from exc result = response.json() if response.content else {} # Legacy endpoint returns {"status": "success", "data": {...}} return result.get("data", result) async def list_alerts( self, page: int = 1, per_page: int = 50, sort_by: str = "alert_id", sort_dir: str = "desc", filter_title: str | None = None, filter_owner_id: int | None = None, ) -> dict[str, Any]: headers = self._headers() url = f"{self.base_url}/api/v2/alerts" params: dict[str, Any] = { "page": max(1, page), "per_page": max(1, per_page), "sort": f"{sort_by}:{sort_dir}", } if filter_title: params["alert_title"] = filter_title if filter_owner_id: params["alert_owner_id"] = filter_owner_id async with httpx.AsyncClient(verify=False, timeout=30.0) as client: response = await client.get(url, params=params, headers=headers) try: response.raise_for_status() except httpx.HTTPStatusError as exc: detail = response.text.strip() raise RuntimeError( f"IRIS returned {response.status_code} for {url}. Response: {detail}" ) from exc return response.json() if response.content else {"status_code": response.status_code} async def assign_alert(self, alert_id: int, owner_id: int) -> dict[str, Any]: headers = self._headers() url = f"{self.base_url}/api/v2/alerts/{alert_id}" payload = {"alert_owner_id": owner_id} async with httpx.AsyncClient(verify=False, timeout=20.0) as client: response = await client.put(url, json=payload, headers=headers) try: response.raise_for_status() except httpx.HTTPStatusError as exc: detail = response.text.strip() raise RuntimeError( f"IRIS returned {response.status_code} for {url}. Response: {detail}" ) from exc return response.json() if response.content else {"status_code": response.status_code}