from __future__ import annotations import logging import httpx logger = logging.getLogger(__name__) GRAPHQL_ENDPOINT = "https://api.github.com/graphql" class GitHubGraphQLClient: def __init__(self, token_getter): self._get_token = token_getter async def _execute(self, query: str, variables: dict | None = None) -> dict: token = await self._get_token() headers = { "Authorization": f"Bearer {token}", "Accept": "application/vnd.github+json", } async with httpx.AsyncClient(timeout=httpx.Timeout(30.0)) as client: resp = await client.post( GRAPHQL_ENDPOINT, json={"query": query, "variables": variables or {}}, headers=headers, ) resp.raise_for_status() return resp.json() async def get_discussion(self, owner: str, repo: str, number: int) -> dict | None: query = """ query($owner: String!, $repo: String!, $number: Int!) { repository(owner: $owner, name: $repo) { discussion(number: $number) { id number title body url createdAt category { name } author { login } comments(first: 50) { nodes { id body author { login } createdAt } } } } } """ result = await self._execute(query, {"owner": owner, "repo": repo, "number": number}) data = result.get("data", {}) repo_data = data.get("repository", {}) discussion = repo_data.get("discussion") return discussion async def get_discussion_categories(self, owner: str, repo: str) -> list[dict]: query = """ query($owner: String!, $repo: String!) { repository(owner: $owner, name: $repo) { discussionCategories(first: 50) { nodes { id name emoji } } } } """ result = await self._execute(query, {"owner": owner, "repo": repo}) data = result.get("data", {}) repo_data = data.get("repository", {}) cats = repo_data.get("discussionCategories", {}) return cats.get("nodes", []) async def create_discussion( self, repository_id: str, category_id: str, title: str, body: str, ) -> dict | None: mutation = """ mutation($repoId: ID!, $categoryId: ID!, $title: String!, $body: String!) { createDiscussion(input: { repositoryId: $repoId, categoryId: $categoryId, title: $title, body: $body }) { discussion { id number url title } } } """ result = await self._execute(mutation, { "repoId": repository_id, "categoryId": category_id, "title": title, "body": body, }) if "errors" in result: logger.error("GraphQL createDiscussion error: %s", result["errors"]) return None return result.get("data", {}).get("createDiscussion", {}).get("discussion") async def close_discussion(self, discussion_node_id: str, reason: str = "RESOLVED") -> dict | None: mutation = """ mutation($discussionId: ID!, $reason: DiscussionCloseReason!) { closeDiscussion(input: { discussionId: $discussionId, reason: $reason }) { discussion { id number closed } } } """ result = await self._execute(mutation, { "discussionId": discussion_node_id, "reason": reason, }) if "errors" in result: logger.error("GraphQL closeDiscussion error: %s", result["errors"]) return None return result.get("data", {}).get("closeDiscussion", {}).get("discussion") async def update_discussion( self, discussion_node_id: str, title: str | None = None, body: str | None = None, ) -> dict | None: mutation = """ mutation($discussionId: ID!, $title: String, $body: String) { updateDiscussion(input: { discussionId: $discussionId, title: $title, body: $body }) { discussion { id number title body url } } } """ result = await self._execute(mutation, { "discussionId": discussion_node_id, "title": title, "body": body, }) if "errors" in result: logger.error("GraphQL updateDiscussion error: %s", result["errors"]) return None return result.get("data", {}).get("updateDiscussion", {}).get("discussion") async def delete_discussion(self, discussion_node_id: str) -> dict: mutation = """ mutation($discussionId: ID!) { deleteDiscussion(input: { discussionId: $discussionId }) { discussion { id } } } """ result = await self._execute(mutation, {"discussionId": discussion_node_id}) if "errors" in result: logger.error("GraphQL deleteDiscussion error: %s", result["errors"]) return {"success": False, "error": result["errors"][0].get("message", "GraphQL error")} return {"success": True}