# `MishkaCmsCore.Accounts.Helper`
[🔗](https://github.com/mishka-group/mishka_cms_core/blob/v0.0.1/lib/mishka_cms_core/accounts/_helper.ex#L5)

Helper functions for account management including user role checks
and email domain configuration for multi-tenant environments.

# `admin_surface`

```elixir
@type admin_surface() :: :dashboard | :api | :any
```

A surface, or `:any` for either one.

# `admin?`

```elixir
@spec admin?(map() | nil, admin_surface()) :: boolean()
```

An admin of `surface` — `:dashboard`, `:api`, or `:any` (the default) for either one.

# `api_admin?`

```elixir
@spec api_admin?(map() | nil) :: boolean()
```

An API admin: holds anything under `admin:api`, whatever their site.

# `auth_link`

```elixir
@spec auth_link(String.t(), String.t(), String.t(), term()) :: String.t()
```

The link a sign-in email carries: `<site>/auth/<path>/<token>/`, marked for the sign-in pool the
token belongs to — a master's when there is no tenant.

# `create_sender_email_link_domain`

Builds a complete URL structure with schema, domain, and port
for email link generation, handling localhost development environments.

# `dashboard_admin?`

```elixir
@spec dashboard_admin?(map() | nil) :: boolean()
```

A dashboard admin: holds anything under `admin:dashboard`, whatever their site.

Every predicate below reads the permissions the user was loaded with, so a signed-in user costs no
query, and also takes the viewer `MishkaCmsCore.Runtime.States.ViewerState` caches for a user.

# `full_admin?`

```elixir
@spec full_admin?(map() | nil, :dashboard | :api) :: boolean()
```

A full admin of `surface`: holds `admin:<surface>:*` or a wildcard covering it — never a set of
narrower names.

# `get_email_sender_domain`

Determines the appropriate email sender domain based on user/email
and tenant context for multi-site email delivery.
Uses frontend_domain if available, otherwise falls back to host.

# `label`

```elixir
@spec label(:master | :guest | :system | :unknown | :deleted) :: String.t()
```

The word a screen shows for an account it does not name, in the `mishka_gervaz` Gettext domain:
`:master` "Master", `:guest` "Guest" (written signed out), `:system` "System" (no user did it),
`:unknown` "Unknown" (an account the reader's read did not return) and `:deleted` "Deleted user"
(a person's work whose account has been deleted, so its author was released).

# `master_admin?`

```elixir
@spec master_admin?(map() | nil, admin_surface()) :: boolean()
```

A master admin: a user whose `site_id` is nil and who is an admin of `surface` (`admin?/2`),
either surface by default. A permission never makes a master.

# `master_ids`

```elixir
@spec master_ids([Ash.UUID.t() | nil]) :: {:ok, MapSet.t()} | {:error, term()}
```

The ids among `user_ids` that belong to a master, as a `MapSet`, in one read.

Reads with no tenant and selects only `:id`, so it answers for a site user's screen too, where a
master's account does not load. `nil` ids are ignored, and a list with nothing left in it reads
nothing.

# `master_user?`

```elixir
@spec master_user?(map() | nil) :: boolean()
```

Whether `user` is a master: a user whose `site_id` is nil.

False for `nil` and for anything that does not carry a `site_id`. Permissions play no part: a site
user holding `*` is not a master.

# `may_email?`

```elixir
@spec may_email?(map() | String.t(), map() | keyword()) :: boolean()
```

Whether a sign-in email — a magic link, a password reset, a confirmation — may go to
`user_or_email` for the tenant in `context`.

A site that is up sends to anyone; an offline site to its own admins and the masters only, of
either surface, since a link can be followed on the screens or its token used over the API; an
inactive site to nobody. Without a tenant, as when a master signs in, it may. An email held back is
announced on `auth:email:held` with `%{to: user_or_email, tenant: tenant}`.

# `names_of`

```elixir
@spec names_of([String.t()]) :: [String.t()]
```

The names of the catalogue rows whose ids are `permission_ids`, read with no authorization. An id
that names no row names nothing.

# `oban_admin?`

```elixir
@spec oban_admin?(map() | nil) :: boolean()
```

Who may see every site's jobs — Oban Web and the Sites screen's job panel: a master
(`master_user?/1`) who is a full dashboard admin (`full_admin?/2`). A site's full admin never is.

# `permission_ids`

```elixir
@spec permission_ids(String.t() | nil) :: MapSet.t(String.t())
```

The ids of the permissions the account `user_id` holds now, as a `MapSet` of strings, read with no
authorization: what an account holds is not a question of who asks. Empty for no account.

# `permission_names`

```elixir
@spec permission_names(String.t() | nil) :: [String.t()]
```

The names of the permissions the account `user_id` holds now, read with no authorization, as
`permission_ids/1` reads their ids. Empty for no account.

# `send_auth_email`

```elixir
@spec send_auth_email(map() | String.t(), keyword() | map(), keyword()) :: :ok
```

Sends one of the sign-in emails through `MishkaCmsCore.Mail`, from the scope the tenant names —
the installation's for a master — with that scope's provider and sender. `email` holds `:purpose`,
`:token`, `:subject`, `:html` and `:text`. The token names the delivery, hashed, so a retry is
never sent twice and the token itself goes nowhere but the body. Always `:ok`:
AshAuthentication ignores a sender's answer, and an email is never worth failing the sign-in.

# `site_admin?`

```elixir
@spec site_admin?(map() | nil, String.t() | nil, admin_surface()) :: boolean()
```

An admin of `site_id`: a user of that site who is an admin of `surface` (`admin?/2`), for that
site only. `surface` defaults to `:dashboard`; pass `:api` for a request to the admin API, or
`:any` for either.

# `visible_to`

```elixir
@spec visible_to(term(), map() | nil) :: map() | nil
```

`user` when `reader` may see its email, else `nil`.

A master (`site_id` nil) reads everyone's; a site user reads its own site's. Pass the result to
`who/4` on a screen that draws an account it did not read as the reader.

# `who`

```elixir
@spec who(
  term(),
  boolean(),
  :guest | :system | :unknown | :deleted | String.t(),
  String.t() | nil
) ::
  String.t()
```

What a screen calls whoever did something.

  * `user` is the account as the reader's own read returned it: a user, `nil`,
    `%Ash.NotLoaded{}` or `%Ash.ForbiddenField{}`.
  * `master?` says whether the account's id is a master's (see `master_ids/1`).
  * `no_account` is what to call a record with no readable account: `:guest`, `:system`,
    `:unknown` or `:deleted` (see `label/1`), or the words themselves.
  * `master_name` replaces `label(:master)`, such as a master's blogger name on a comment.

Returns the email when `user` carries a readable one, else `master_name` or `label(:master)` for
a master, else `no_account`. Never returns any part of an unreadable email.

---

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