defmodule Jido.Chat.GitHub.Transport.ReqClient do
@moduledoc "Req-backed GitHub REST transport."
@behaviour Jido.Chat.GitHub.Transport
@api_version "2026-03-10"
@impl true
def create_issue(owner, repo, title, body, opts) do
request(:post, "/repos/#{owner}/#{repo}/issues", opts,
json: issue_create_body(title, body, opts)
)
end
@impl true
def create_issue_comment(owner, repo, issue_number, body, opts) do
request(:post, "/repos/#{owner}/#{repo}/issues/#{issue_number}/comments", opts,
json: %{body: body}
)
end
@impl true
def update_issue_comment(owner, repo, comment_id, body, opts) do
request(:patch, "/repos/#{owner}/#{repo}/issues/comments/#{comment_id}", opts,
json: %{body: body}
)
end
@impl true
def delete_issue_comment(owner, repo, comment_id, opts) do
case request(:delete, "/repos/#{owner}/#{repo}/issues/comments/#{comment_id}", opts) do
{:ok, _} -> :ok
{:error, _} = error -> error
end
end
@impl true
def get_issue(owner, repo, issue_number, opts) do
request(:get, "/repos/#{owner}/#{repo}/issues/#{issue_number}", opts)
end
@impl true
def list_issue_comments(owner, repo, issue_number, opts) do
request(:get, "/repos/#{owner}/#{repo}/issues/#{issue_number}/comments", opts)
end
@impl true
def get_issue_comment(owner, repo, comment_id, opts) do
request(:get, "/repos/#{owner}/#{repo}/issues/comments/#{comment_id}", opts)
end
@impl true
def list_issues(owner, repo, opts) do
request(:get, "/repos/#{owner}/#{repo}/issues", opts, params: issue_list_params(opts))
end
@impl true
def create_issue_reaction(owner, repo, issue_number, content, opts) do
request(:post, "/repos/#{owner}/#{repo}/issues/#{issue_number}/reactions", opts,
json: %{content: content}
)
end
@impl true
def create_issue_comment_reaction(owner, repo, comment_id, content, opts) do
request(:post, "/repos/#{owner}/#{repo}/issues/comments/#{comment_id}/reactions", opts,
json: %{content: content}
)
end
@impl true
def list_issue_reactions(owner, repo, issue_number, opts) do
request(:get, "/repos/#{owner}/#{repo}/issues/#{issue_number}/reactions", opts)
end
@impl true
def list_issue_comment_reactions(owner, repo, comment_id, opts) do
request(:get, "/repos/#{owner}/#{repo}/issues/comments/#{comment_id}/reactions", opts)
end
@impl true
def delete_issue_reaction(owner, repo, issue_number, reaction_id, opts) do
case request(
:delete,
"/repos/#{owner}/#{repo}/issues/#{issue_number}/reactions/#{reaction_id}",
opts
) do
{:ok, _} -> :ok
{:error, _} = error -> error
end
end
@impl true
def delete_issue_comment_reaction(owner, repo, comment_id, reaction_id, opts) do
case request(
:delete,
"/repos/#{owner}/#{repo}/issues/comments/#{comment_id}/reactions/#{reaction_id}",
opts
) do
{:ok, _} -> :ok
{:error, _} = error -> error
end
end
defp request(method, path, opts, req_opts \\ []) do
token = Keyword.get(opts, :token) || System.get_env("GITHUB_TOKEN")
base_url = Keyword.get(opts, :base_url, "https://api.github.com")
headers = [
{"accept", "application/vnd.github+json"},
{"x-github-api-version", @api_version}
]
headers =
if token in [nil, ""], do: headers, else: [{"authorization", "Bearer #{token}"} | headers]
[method: method, url: base_url <> path, headers: headers]
|> Keyword.merge(req_opts)
|> Req.request()
|> case do
{:ok, %{status: status, body: body}} when status in 200..299 -> {:ok, body || %{}}
{:ok, %{status: status, body: body}} -> {:error, {:github_api_error, status, body}}
{:error, reason} -> {:error, reason}
end
end
defp issue_list_params(opts) do
[]
|> maybe_param(:state, Keyword.get(opts, :state, "open"))
|> maybe_param(:per_page, Keyword.get(opts, :limit, Keyword.get(opts, :per_page)))
|> maybe_param(:page, Keyword.get(opts, :page))
|> maybe_param(:since, Keyword.get(opts, :since))
|> maybe_param(:labels, Keyword.get(opts, :labels))
|> maybe_param(:sort, Keyword.get(opts, :sort))
|> maybe_param(:direction, Keyword.get(opts, :direction))
end
defp issue_create_body(title, body, opts) do
%{title: title}
|> maybe_json(:body, body)
|> maybe_json(:labels, Keyword.get(opts, :labels))
|> maybe_json(:assignees, Keyword.get(opts, :assignees))
|> maybe_json(:milestone, Keyword.get(opts, :milestone))
end
defp maybe_json(body, _key, value) when value in [nil, "", []], do: body
defp maybe_json(body, key, value), do: Map.put(body, key, value)
defp maybe_param(params, _key, value) when value in [nil, ""], do: params
defp maybe_param(params, key, value), do: Keyword.put(params, key, value)
end