# Resource modules

The convenience modules are thin wrappers over `GhEx.REST` that fill in the
endpoint path. Each function returns the same `{:ok, body, meta}` /
`{:error, reason}` shape as the core and passes `opts` through to `Req` (so
`:params`, headers, and a `Req.Test` plug all work). They cover the common paths;
for anything else, call `GhEx.REST` directly or follow the
[Extending gh_ex](extending.md) guide to build an application-owned resource
module.

On the write wrappers (`create`, `update`, `merge`, and the like), the `attrs`
argument is the sole request body: each sets `:json` to `attrs`, so a `:json`
passed in `opts` is ignored.

The `list_*` functions return a single page. Each has a `stream_*` companion
that auto-paginates into a lazy `Stream` of individual items, following
`Link: rel="next"` (see the [Pagination](pagination.md) guide). The wrapped
endpoints (Search, Actions runs/workflows/jobs, Checks) unwrap their array key
for you.

```elixir
# every open issue, not just the first page
client
|> GhEx.Issues.stream("elixir-lang", "elixir", params: [state: "open", per_page: 100])
|> Stream.map(& &1["number"])
|> Enum.to_list()
```

For a path without a `stream_*` wrapper, call `GhEx.REST.stream/3` directly with
the path and, for an object-wrapped response, the `items:` key.

## Issues

`GhEx.Issues` — list, get, create, update, create or edit comments, mutate
assignees, manage the repository label vocabulary, and add, remove, or replace
labels on issues.

```elixir
GhEx.Issues.list(client, "elixir-lang", "elixir", params: [state: "open"])
GhEx.Issues.create(client, "o", "r", %{title: "Bug", body: "..."})
GhEx.Issues.create_comment(client, "o", "r", 7, "thanks for the report")
GhEx.Issues.update_comment(client, "o", "r", comment_id, "build is green")
GhEx.Issues.add_assignees(client, "o", "r", 7, ["octocat"])
GhEx.Issues.remove_assignees(client, "o", "r", 7, ["hubot"])
GhEx.Issues.stream_labels(client, "o", "r") |> Enum.take(100)
GhEx.Issues.create_label(client, "o", "r", %{name: "priority:high", color: "d93f0b"})
GhEx.Issues.update_label(client, "o", "r", "priority:high", %{description: "Address next"})
GhEx.Issues.add_labels(client, "o", "r", 7, ["bug", "p1"])
GhEx.Issues.remove_label(client, "o", "r", 7, "needs triage")
GhEx.Issues.replace_all_labels(client, "o", "r", 7, ["confirmed", "p1"])
```

## Pull requests

`GhEx.PullRequests` — list, get, create, update, synchronous and asynchronous
merge, merge status, commits, raw diff/patch, files, review requests, reviews,
and line-anchored review comments.
`GhEx.Stacks` — list, get, create, add, and unstack pull request stacks. See the
[Stacked pull requests](stacks.md) guide for the asynchronous merge flow and
GraphQL/webhook fields.

```elixir
GhEx.PullRequests.create(client, "o", "r", %{title: "Fix", head: "fix", base: "main"})
{:ok, merged?, _meta} = GhEx.PullRequests.is_merged(client, "o", "r", 42)
GhEx.PullRequests.list_commits(client, "o", "r", 42)
GhEx.PullRequests.list_files(client, "o", "r", 42)
GhEx.PullRequests.get_diff(client, "o", "r", 42)
GhEx.PullRequests.merge(client, "o", "r", 42, %{merge_method: "squash"})
GhEx.PullRequests.request_reviewers(client, "o", "r", 42, %{reviewers: ["octocat"]})
GhEx.PullRequests.create_review(client, "o", "r", 42, %{event: "APPROVE"})
GhEx.Stacks.create(client, "o", "r", %{pull_requests: [41, 42]})
GhEx.PullRequests.merge_async(client, "o", "r", 42, %{merge_action: "default"})

GhEx.PullRequests.create_comment(client, "o", "r", 42, %{
  body: "Please handle nil.", commit_id: sha, path: "lib/a.ex", line: 12, side: "RIGHT"
})
```

## Repositories and contents

`GhEx.Repositories` — get, list (org/user), create, update, delete, collaborator
authorization, commits, branches, and ETag-conditional event polling.
`GhEx.Contents` — read and write files.

```elixir
GhEx.Repositories.get(client, "elixir-lang", "elixir")
GhEx.Repositories.list_for_org(client, "elixir-lang", params: [type: "public"])

{:ok, is_collaborator, _meta} = GhEx.Repositories.is_collaborator(client, "o", "r", actor)
{:ok, permission, _meta} = GhEx.Repositories.get_collaborator_permission(client, "o", "r", actor)

{:ok, events, meta} = GhEx.Repositories.events(client, "o", "r")

case GhEx.Repositories.events(client, "o", "r", headers: [{"if-none-match", meta.etag}]) do
  {:ok, :not_modified, poll_meta} -> poll_meta.headers["x-poll-interval"]
  {:ok, new_events, poll_meta} -> {new_events, poll_meta.etag}
end

{:ok, file, _meta} = GhEx.Contents.get(client, "o", "r", "mix.exs", params: [ref: "main"])

GhEx.Contents.create_or_update_file(client, "o", "r", "NOTES.md", %{
  message: "add notes",
  content: Base.encode64("hello"),
  sha: file["sha"]
})
```

`content` is Base64-encoded, and updating an existing file needs its blob `sha`.

## Commits

`GhEx.Commits` — get a commit, compare refs, find associated pull requests, and
list or create commit comments. Repository-wide listing remains in
`GhEx.Repositories.list_commits/4` and `GhEx.Repositories.stream_commits/4`.

```elixir
GhEx.Commits.get(client, "o", "r", sha)
GhEx.Commits.compare(client, "o", "r", "main", "feature/agent")
GhEx.Commits.list_pulls(client, "o", "r", sha)
GhEx.Commits.create_comment(client, "o", "r", sha, %{body: "Looks good"})
```

## Git references

`GhEx.Git` — get, create, and delete branch or tag references. Read and delete
helpers accept both `heads/name` and fully qualified `refs/heads/name` forms;
create always sends GitHub the required fully qualified form.

```elixir
GhEx.Git.get_ref(client, "o", "r", "heads/main")
GhEx.Git.create_ref(client, "o", "r", %{ref: "heads/release", sha: sha})
GhEx.Git.delete_ref(client, "o", "r", "refs/heads/release")
```

## Releases

`GhEx.Releases` — list, get, create, update, delete, generate notes, and upload
raw release assets to GitHub's dedicated upload endpoint.

```elixir
GhEx.Releases.get_latest(client, "o", "r")

GhEx.Releases.create(client, "o", "r", %{
  tag_name: "v1.0.0",
  name: "v1.0.0",
  generate_release_notes: true
})

asset = %{
  name: "gh_ex.tar.gz",
  content_type: "application/gzip",
  data: File.read!("gh_ex.tar.gz"),
  label: "Linux archive"
}

GhEx.Releases.upload_asset(client, "o", "r", release_id, asset)
```

## Actions

`GhEx.Actions` — workflows, runs, jobs, artifacts, logs, dispatch, deployment
approvals, cancel, and rerun. A workflow is its numeric id or its file name
(`"ci.yml"`). Artifact and run-log downloads return ZIP bytes; job-log downloads
return plain-text bytes.

```elixir
GhEx.Actions.list_workflows(client, "o", "r")
GhEx.Actions.dispatch_workflow(client, "o", "r", "ci.yml", %{ref: "main", inputs: %{env: "prod"}})
GhEx.Actions.list_runs(client, "o", "r", params: [branch: "main", status: "failure"])
GhEx.Actions.rerun(client, "o", "r", run_id)
GhEx.Actions.list_pending_deployments(client, "o", "r", run_id)
GhEx.Actions.review_pending_deployments(client, "o", "r", run_id, %{
  environment_ids: [environment_id],
  state: "approved",
  comment: "Ship it"
})
{:ok, zip_bytes, _meta} = GhEx.Actions.download_artifact(client, "o", "r", artifact_id, "zip")
{:ok, logs, _meta} = GhEx.Actions.download_job_logs(client, "o", "r", job_id)
GhEx.Actions.rerun_failed_jobs(client, "o", "r", run_id)
```

## Deployments

`GhEx.Deployments` — deployment requests and their append-only status history.
The deployment object identifies the ref and environment; automation waiting for
completion should inspect the latest deployment status rather than polling the
deployment object for a state change.

```elixir
{:ok, deployment, _meta} =
  GhEx.Deployments.create(client, "o", "r", %{
    ref: "main",
    environment: "production",
    required_contexts: []
  })

GhEx.Deployments.create_status(client, "o", "r", deployment["id"], %{
  state: "in_progress",
  log_url: "https://deploys.example.test/42"
})

{:ok, statuses, _meta} = GhEx.Deployments.list_statuses(client, "o", "r", deployment["id"])
latest_status = List.first(statuses)
```

## Activity

`GhEx.Activity` — repository, organization, user, and public event feeds plus
repository starring. Each feed/list has a lazy stream companion. Watching stays
separate from starring and is not part of this module.

```elixir
GhEx.Activity.stream_repo_events(client, "o", "r") |> Enum.take(100)
GhEx.Activity.list_starred(client, "octocat")
{:ok, starred?, _meta} = GhEx.Activity.starred?(client, "o", "r")
GhEx.Activity.star(client, "o", "r")
```

## Checks and statuses

`GhEx.Checks` — check runs, annotations, and run rerequests. `GhEx.Statuses` —
commit statuses.

```elixir
GhEx.Checks.create_run(client, "o", "r", %{name: "lint", head_sha: sha, status: "in_progress"})
GhEx.Checks.list_for_ref(client, "o", "r", sha)
{:ok, run_or_nil, _meta} = GhEx.Checks.find_run_for_ref(client, "o", "r", sha, "lint", app_id)
GhEx.Checks.stream_annotations(client, "o", "r", check_run_id) |> Enum.take(100)
GhEx.Checks.rerequest_run(client, "o", "r", check_run_id)

GhEx.Statuses.create(client, "o", "r", sha, %{state: "success", context: "ci/lint"})
GhEx.Statuses.get_combined(client, "o", "r", "main")
```

## Security alerts

`GhEx.CodeSecurity` covers repository code-scanning, Dependabot, and
secret-scanning alerts. The corresponding security feature must be available
and enabled, and the token needs the matching repository alert permission.
Updates require write permission.

```elixir
GhEx.CodeSecurity.list_alerts(client, "o", "r", params: [state: "open"])
GhEx.CodeSecurity.stream_dependabot_alerts(client, "o", "r") |> Enum.take(100)
GhEx.CodeSecurity.get_secret_alert(client, "o", "r", alert_number, params: [hide_secret: true])
GhEx.CodeSecurity.update_alert(client, "o", "r", alert_number, %{
  state: "dismissed",
  dismissed_reason: "false positive"
})
```

## Search

`GhEx.Search` — repositories, code, issues_and_pull_requests, users, commits. The
first argument is the `q` query; pass `params:` for `sort` and `order`.

```elixir
GhEx.Search.repositories(client, "tetris language:elixir", params: [sort: "stars"])
GhEx.Search.issues_and_pull_requests(client, "repo:o/r is:open label:bug")
```

## Users, organizations, and teams

`GhEx.Users`, `GhEx.Organizations`, `GhEx.Teams`.

```elixir
GhEx.Users.get_authenticated(client)
GhEx.Users.get(client, "joshrotenberg")
GhEx.Organizations.list_members(client, "elixir-lang")
GhEx.Teams.list(client, "elixir-lang")
```

## Gists

`GhEx.Gists` — list, get, create, update, delete.

```elixir
GhEx.Gists.create(client, %{
  description: "example",
  public: false,
  files: %{"hello.txt" => %{content: "hi"}}
})
```

## Webhooks

`GhEx.Hooks` manages repository webhook configurations. `GhEx.Webhooks` is the
receiving side: verify a delivery signature and parse the payload. See the
[Webhooks](webhooks.md) guide for the full receiver pattern.

```elixir
GhEx.Hooks.create(client, "o", "r", %{
  active: true,
  events: ["push", "pull_request"],
  config: %{url: "https://example.test/github", content_type: "json", secret: secret}
})

with :ok <- GhEx.Webhooks.verify(body, signature, secret),
     {:ok, payload} <- GhEx.Webhooks.parse(body) do
  handle(event_name, payload)
end
```
