# `Ltix.MembershipsService`
[🔗](https://github.com/DecoyLex/ltix/blob/main/lib/ltix/memberships_service.ex#L1)

Query context membership (roster) from a platform using the
[Names and Roles Provisioning Service (NRPS) v2.0](https://www.imsglobal.org/spec/lti-nrps/v2p0/).

Given a successful LTI 1.3 launch that includes the memberships endpoint
claim, retrieve the list of users enrolled in the course or resource link.

## From a launch context

    {:ok, client} = Ltix.MembershipsService.authenticate(launch_context)
    {:ok, roster} = Ltix.MembershipsService.list_members(client)

    Enum.each(roster, fn member ->
      IO.puts("#{member.name}: #{inspect(member.roles)}")
    end)

## From a registration

    alias Ltix.LaunchClaims.MembershipsEndpoint

    {:ok, client} = Ltix.MembershipsService.authenticate(registration,
      endpoint: MembershipsEndpoint.new!("https://lms.example.com/memberships")
    )

    {:ok, roster} = Ltix.MembershipsService.list_members(client, role: :learner)

## Streaming large rosters

    {:ok, stream} = Ltix.MembershipsService.stream_members(client)

    stream
    |> Stream.filter(&(&1.status == :active))
    |> Enum.each(&process_member/1)

# `authenticate`

```elixir
@spec authenticate(Ltix.LaunchContext.t() | Ltix.Registerable.t(), keyword()) ::
  {:ok, Ltix.OAuth.Client.t()} | {:error, Exception.t()}
```

Acquire an OAuth token for the memberships service.

Accepts a `%LaunchContext{}` or any struct implementing `Ltix.Registerable`
(including `Ltix.Registration`). With a launch context, the endpoint is
extracted from the launch claims. With a registration, pass the endpoint
via the `:endpoint` option.

## From a launch context

    {:ok, client} = Ltix.MembershipsService.authenticate(launch_context)

## From a registration

    {:ok, client} = Ltix.MembershipsService.authenticate(registration,
      endpoint: MembershipsEndpoint.new!("https://lms.example.com/memberships")
    )

## Options (launch context)

* `:req_options` (`t:keyword/0`) - Options passed through to `Req.request/2`. The default value is `[]`.

* `:local_network_access` (`nil` | `:none` | `:local` | `:loopback`) - Addresses platform requests may connect to besides public ones. `:local`
  adds private network addresses, and `:loopback` adds those and this
  machine. Defaults to `config :ltix, :local_network_access`, then `:none`.

* `:storage_adapter` (`t:module/0`) - Module implementing `Ltix.StorageAdapter`, which signs token requests. Defaults to the application configuration.

* `:storage_adapter_options` (`t:keyword/0`) - Options passed to the storage adapter's signing callbacks. The default value is `[]`.

* `:require_all_scopes` (`t:boolean/0`) - Return a `ScopeMismatch` error unless every scope the endpoints need was granted. The default value is `false`.

## Options (registration)

* `:endpoint` (struct of type `Ltix.LaunchClaims.MembershipsEndpoint`) - Required. MembershipsEndpoint struct for the service endpoint.

* `:deployment` (`t:term/0`) - The deployment the token is for, as a struct implementing `Ltix.Deployable` or a deployment ID. Token requests include it.

* `:req_options` (`t:keyword/0`) - Options passed through to `Req.request/2`. The default value is `[]`.

* `:local_network_access` (`nil` | `:none` | `:local` | `:loopback`) - Addresses platform requests may connect to besides public ones. `:local`
  adds private network addresses, and `:loopback` adds those and this
  machine. Defaults to `config :ltix, :local_network_access`, then `:none`.

* `:storage_adapter` (`t:module/0`) - Module implementing `Ltix.StorageAdapter`, which signs token requests. Defaults to the application configuration.

* `:storage_adapter_options` (`t:keyword/0`) - Options passed to the storage adapter's signing callbacks. The default value is `[]`.

* `:require_all_scopes` (`t:boolean/0`) - Return a `ScopeMismatch` error unless every scope the endpoints need was granted. The default value is `false`.

# `authenticate!`

```elixir
@spec authenticate!(Ltix.LaunchContext.t() | Ltix.Registerable.t(), keyword()) ::
  Ltix.OAuth.Client.t()
```

Same as `authenticate/2` but raises on error.

# `list_members`

```elixir
@spec list_members(Ltix.OAuth.Client.t(), keyword()) ::
  {:ok, Ltix.MembershipsService.MembershipContainer.t()}
  | {:error, Exception.t()}
```

Fetch all members from the memberships endpoint.

Follows all `rel="next"` pagination links and returns a complete
`%MembershipContainer{}`. The container implements `Enumerable`,
so you can pipe it directly into `Enum` or `Stream` functions.

## Options

* `:endpoint` (struct of type `Ltix.LaunchClaims.MembershipsEndpoint`) - Override the endpoint stored on the client.

* `:role` (`t:atom/0` | `t:String.t/0` | struct of type `Ltix.LaunchClaims.Role`) - Filter by role. Accepts a role atom (e.g., `:learner`), URI string, `%Role{}` struct, or short name string (e.g., `"Learner"`).

* `:resource_link_id` (`t:String.t/0`) - Query resource link membership.

* `:per_page` (`t:integer/0`) - Page size hint. The platform may return more or fewer than requested.

* `:max_members` (`t:integer/0` | `:infinity`) - Safety limit for eager fetch. Returns a `CollectionTooLarge` error if exceeded. Set to `:infinity` to disable. The default value is `10000`.

# `list_members!`

```elixir
@spec list_members!(Ltix.OAuth.Client.t(), keyword()) ::
  Ltix.MembershipsService.MembershipContainer.t()
```

Same as `list_members/2` but raises on error.

# `stream_members`

```elixir
@spec stream_members(Ltix.OAuth.Client.t(), keyword()) ::
  {:ok, Enumerable.t()} | {:error, Exception.t()}
```

Fetch members as a lazy stream.

Returns `{:ok, stream}` where each element is a `%Member{}`.
Use this for large rosters where you want to process members
incrementally or stop early. `list_members/2` returns the whole roster.
Later page failures and member parsing errors raise during enumeration.

Streams take no `:max_members` limit because they fetch one page at a
time. Use `Stream.take/2` to stop after a number of members.

## Options

* `:endpoint` (struct of type `Ltix.LaunchClaims.MembershipsEndpoint`) - Override the endpoint stored on the client.

* `:role` (`t:atom/0` | `t:String.t/0` | struct of type `Ltix.LaunchClaims.Role`) - Filter by role. Accepts a role atom (e.g., `:learner`), URI string, `%Role{}` struct, or short name string (e.g., `"Learner"`).

* `:resource_link_id` (`t:String.t/0`) - Query resource link membership.

* `:per_page` (`t:integer/0`) - Page size hint. The platform may return more or fewer than requested.

# `stream_members!`

```elixir
@spec stream_members!(Ltix.OAuth.Client.t(), keyword()) :: Enumerable.t()
```

Same as `stream_members/2` but raises on error.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
