""" Wiki.js Integration Service (read-only) """ import logging from typing import Any, Dict, List, Optional import aiohttp from app.core.config import settings logger = logging.getLogger(__name__) class WikiService: def __init__(self) -> None: self.base_url = settings.WIKI_BASE_URL.rstrip("/") self.api_token = settings.WIKI_API_KEY or settings.WIKI_API_TOKEN self.timeout = settings.WIKI_TIMEOUT_SECONDS self.read_only = settings.WIKI_READ_ONLY if self.read_only: logger.warning("\ud83d\udd12 Wiki.js READ_ONLY MODE ENABLED") def _normalize_slug(self, slug: str) -> str: normalized = (slug or "").strip().strip("/") prefix = "en/Kunder/" if normalized.lower().startswith(prefix.lower()): normalized = normalized[len(prefix):] return normalized def build_customer_path(self, slug: str) -> str: normalized = self._normalize_slug(slug) base_path = "en/Kunder" return f"{base_path}/{normalized}" if normalized else base_path def _headers(self) -> Dict[str, str]: if not self.api_token: return {"Content-Type": "application/json"} return { "Authorization": f"Bearer {self.api_token}", "Content-Type": "application/json", } async def _graphql_request(self, query: str, variables: Optional[dict] = None) -> Dict[str, Any]: if not self.api_token: return {"errors": ["missing_token"]} url = f"{self.base_url}/graphql" payload = {"query": query, "variables": variables or {}} try: async with aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=self.timeout)) as session: async with session.post(url, json=payload, headers=self._headers()) as resp: data = await resp.json() if resp.status >= 400: return {"errors": [data], "status": resp.status} return data except Exception as exc: logger.error("\u274c Wiki.js request failed: %s", exc) return {"errors": [str(exc)]} async def list_customer_pages( self, slug: str, tag: Optional[str] = "guide", query: Optional[str] = None, limit: int = 20, ) -> Dict[str, Any]: path = self.build_customer_path(slug) path_prefix = path.lstrip("/") tag_list = [tag] if tag else None query_value = query.strip() if query else None gql_list = """ query WikiCustomerList($tags: [String!], $limit: Int) { pages { list( limit: $limit, orderBy: TITLE, orderByDirection: ASC, tags: $tags ) { id title path description updatedAt } } } """ gql_search = """ query WikiCustomerSearch($query: String!, $path: String) { pages { search(query: $query, path: $path) { results { id title path description } } } } """ if query_value: response = await self._graphql_request( gql_search, { "query": query_value, }, ) pages_raw = (((response.get("data") or {}).get("pages") or {}).get("search") or {}).get("results") or [] else: response = await self._graphql_request( gql_list, { "tags": tag_list, "limit": max(1, min(limit, 100)), }, ) pages_raw = (((response.get("data") or {}).get("pages") or {}).get("list")) or [] errors = response.get("errors") if errors: logger.error("\u274c Wiki.js query failed: %s", errors) return { "pages": [], "path": path, "tag": tag, "query": query_value, "base_url": self.base_url, "errors": errors, } if not pages_raw and query_value: response = await self._graphql_request( gql_search, { "query": query_value, "path": f"/{path}" if not path.startswith("/") else path, }, ) if not response.get("errors"): pages_raw = (((response.get("data") or {}).get("pages") or {}).get("search") or {}).get("results") or [] if path_prefix: prefixes = [path_prefix] if path_prefix.lower().startswith("en/"): prefixes.append(path_prefix[3:]) prefix_lowers = [value.lower() for value in prefixes] pages_raw = [ item for item in pages_raw if any( (item.get("path") or "").lstrip("/").lower().startswith(prefix) for prefix in prefix_lowers ) ] pages: List[Dict[str, Any]] = [] for item in pages_raw: raw_path = item.get("path") if isinstance(item, dict) else None if not raw_path: continue url = f"{self.base_url}{raw_path if raw_path.startswith('/') else '/' + raw_path}" pages.append({ "id": item.get("id"), "title": item.get("title") or raw_path, "path": raw_path, "description": item.get("description"), "updated_at": item.get("updatedAt"), "url": url, }) return { "pages": pages, "path": f"/{path}", "tag": tag, "query": query_value, "base_url": self.base_url, }