# `Chime`
[🔗](https://github.com/hattoriai/chime/blob/v0.1.0-dev/lib/chime.ex#L1)

Chime is notifications for Phoenix: something happens in the app, and
Chime tells the people it concerns, in the app's feed (the bell), by
email and by the app's own channels, as each person wants.

Add Chime to the app's supervision tree, after the repo, the PubSub and
Oban (`Chime.Config` has the options):

    {Chime, repo: MyApp.Repo, pubsub: MyApp.PubSub, resolver: MyApp.ChimeResolver}

Write a `Chime.Notifier` for each kind of notification, and send one:

    Chime.notify(MyApp.Notifications.NewComment, %{comment: comment})

The functions of this module read and change a person's feed, by the
person's id: `feed/2`, `counts/1`, `mark_seen/1`, `mark_read/2`,
`mark_unread/2`, `archive/2`, `unarchive/2`; their preferences:
`preferences/1`, `put_preference/4`; and the log of what was decided:
`explain/1`.

Start with the [Quickstart](quickstart.md).

## Live

`subscribe/1` subscribes the caller to a person's topic, on the app's
PubSub. It receives:

  * `{Chime, :notified, counts}` - a notification came, or an event
    joined one of their groups;
  * `{Chime, :updated, counts}` - they saw, read or archived
    notifications, in another tab or on another device.

`counts` is `%{unseen: n, unread: n, workspaces: %{id => %{unseen: n,
unread: n}}}`: the whole feed's, and each workspace's. `Chime.Live` and the JSON API's
stream are subscribers.

# `counts`

```elixir
@type counts() :: %{unseen: non_neg_integer(), unread: non_neg_integer()}
```

A person's counts: what they have not seen, and not read.

# `act`

```elixir
@spec act(String.t(), Ecto.UUID.t(), String.t()) ::
  {:ok, %{action: String.t(), url: String.t() | nil}} | {:error, term()}
```

Takes an action of a notification for its person: one of the
`:actions` of the notifier's `feed/2` (`%{key: "accept", label:
"Accept"}`), which calls the notifier's `act/3` with the key, the data
and the notification. When it says `:ok` or `{:ok, url}`, the
notification keeps the action and is read, and the person's pages are
told. Returns `{:ok, %{action: key, url: url | nil}}`, or
`{:error, :not_found | :unknown_action | :already | reason}`.

# `actors`

```elixir
@spec actors(Chime.Notification.t()) :: String.t()
```

Who did a notification's events, in words: "Ana", "Ana and Rui", "Ana
and 4 others", in the app's languages (`Chime.Messages`). An app can
write its own from `notification.actors`, the latest first, and
`notification.actor_count`.

# `archive`

```elixir
@spec archive(String.t(), [String.t()] | :all, keyword()) :: counts()
```

Archives notifications of a person: out of the inbox. Returns the counts.

# `cancel`

```elixir
@spec cancel(String.t() | Chime.Event.t(), keyword()) ::
  {:ok, %{deliveries: non_neg_integer(), removed: non_neg_integer()}}
  | {:error, :not_found}
```

Cancels an event, by its idempotency key or the event:

    Chime.cancel("comment:#{comment.id}")

A scheduled event (`:at`) is not sent. One sent has its deliveries that
wait (an email in ten minutes, a digest) skipped, with "the
notification was cancelled" in the log. With `feed: :remove`, its
notifications are taken out of the feeds too, but those of a group that
holds other events. Returns `{:ok, %{deliveries: n, removed: n}}`, or
`{:error, :not_found}`.

# `child_spec`

Returns a specification to start this module under a supervisor.

See `Supervisor`.

# `counts`

```elixir
@spec counts(String.t(), keyword()) :: counts()
```

A person's counts: `%{unseen: n, unread: n}`, of the notifications of
their feed not archived, or of a workspace's (`workspace: id`). One row,
kept as notifications come and go.

# `devices`

```elixir
@spec devices(String.t()) :: [Chime.Device.t()]
```

A person's devices, the latest first.

# `explain`

```elixir
@spec explain(Ecto.UUID.t() | Chime.Notification.t()) :: Chime.Explain.t() | nil
```

The log of a notification: when it came, what each channel decided
and why. See `Chime.Explain`.

# `feed`

```elixir
@spec feed(String.t(), keyword()) :: [Chime.Notification.t()]
```

A person's feed, the latest first, each notification with its event:
`Chime.Feed.render/1` gives what it shows.

Options:

  * `:limit` - how many (20, up to 100).
  * `:before` - the id of a notification: the page after it.
  * `:filter` - `:inbox`, those not archived (the default), `:unread`,
    `:archived` or `:all`.
  * `:workspace` - only the notifications of a workspace.

# `link`

```elixir
@spec link(Chime.Notification.t(), String.t()) :: String.t()
```

A link to a notification, for an email or a channel, that reads it in
the feed when it is opened, then goes to `url` (a path goes on the
config's `:url`). See `Chime.Links`. Without the config's `:mail_url`
and `:secret_key_base`, the address itself.

    ~s(<a href="#{Chime.link(notification, "/stories/42")}">Read it</a>)

# `mark_read`

```elixir
@spec mark_read(String.t(), [String.t()] | :all, keyword()) :: counts()
```

Marks notifications of a person as read (and seen): a list of ids, or
`:all`. Option `:workspace`. Returns the counts.

# `mark_seen`

```elixir
@spec mark_seen(String.t(), keyword()) :: counts()
```

Marks a person's feed as seen: the bell's dot goes. Option `:workspace`,
only a workspace's. Returns the counts, of the workspace with one.

# `mark_unread`

```elixir
@spec mark_unread(String.t(), [String.t()] | :all, keyword()) :: counts()
```

Marks notifications of a person as unread. Returns the counts.

# `notify`

```elixir
@spec notify(module(), term(), keyword()) ::
  {:ok, Chime.Event.t()} | {:error, :duplicate, Chime.Event.t()}
```

Notifies the people of an event, by the notifier's channels.

The notifier's `recipients/1` says who, unless `:to` does. Each person
gets a notification in their feed at once (unless they turned the feed
off for the category), or the event joins their open notification of
its group; each other channel is a delivery, which goes at its time as
an Oban job, in the same transaction.

Options:

  * `:to` - the people, instead of the notifier's `recipients/1`: the
    app's users, `Chime.Recipient`s or ids.
  * `:workspace` - the workspace it happens in, instead of the
    notifier's `workspace/1` (see the [Workspaces](workspaces.md) guide).
  * `:at` - a time to notify at, instead of now: the event is
    `"scheduled"`, and a job notifies its people then. The guard
    (`notify?/2`) and the people are decided now; the feed, the
    preferences and the channels at its time. `cancel/2` stops it.
  * `:key` - an idempotency key: a second call with the same key does
    nothing, and returns `{:error, :duplicate, event}`. Give one when
    the call may run twice (a retried job): `"comment:#{comment.id}"`.

Returns `{:ok, %Chime.Event{}}`. For many people, it works a batch of
1000 at a time, each its own transaction: call it from a job of the
app for an event to everyone.

# `preferences`

```elixir
@spec preferences(String.t(), keyword()) :: [Chime.Preferences.t()]
```

A person's preferences: for each category of the config, its settings
by channel (`Chime.Preferences`). Option `:workspace`: the settings in a
workspace, its own where the person set them.

# `put_preference`

```elixir
@spec put_preference(
  String.t(),
  String.t() | atom(),
  String.t() | atom(),
  String.t() | atom(),
  keyword()
) :: :ok | {:error, atom()}
```

Changes a person's setting of a channel for a category: `"on"`,
`"off"`, or for email `"daily"` or `"weekly"`. Option `:workspace`: for
that workspace only, over the person's setting for all. See
`Chime.Preferences`.

# `register_device`

```elixir
@spec register_device(String.t(), map()) :: {:ok, Chime.Device.t()} | {:error, atom()}
```

Registers a device of a person, for the `:push` channel: `%{kind:
"web", token: endpoint, keys: %{"p256dh" => ..., "auth" => ...}}` for
a browser's subscription, `%{kind: "apns" | "fcm", token: token}` for a
phone's; and `:name`. A token registered before is the same device, now
of this person. Errors: `:unknown_kind`, `:invalid_token`,
`:invalid_subscription`.

# `remove_device`

```elixir
@spec remove_device(String.t(), Ecto.UUID.t()) :: :ok | {:error, :not_found}
```

Removes a person's device.

# `resend`

```elixir
@spec resend(Ecto.UUID.t()) :: :ok | {:error, :not_found | :waiting}
```

Sends a delivery again: one that failed or was skipped is scheduled now,
its attempts from nought, with "sent again" in the log; what is decided
at its time (the preferences, the conditions, the rules) is decided
again. `{:error, :not_found}` for an unknown one, `{:error, :waiting}`
for one that waits already.

# `start_link`

```elixir
@spec start_link(keyword()) :: Supervisor.on_start()
```

Starts Chime with the options of `Chime.Config`.

# `subscribe`

```elixir
@spec subscribe(String.t()) :: :ok | {:error, term()}
```

Subscribes the caller to a person's live news (see Live, above).

# `unarchive`

```elixir
@spec unarchive(String.t(), [String.t()] | :all, keyword()) :: counts()
```

Brings archived notifications of a person back. Returns the counts.

# `unsubscribe_url`

```elixir
@spec unsubscribe_url(
  Chime.Recipient.t() | String.t(),
  Chime.Notification.t() | String.t()
) ::
  String.t() | nil
```

The address to unsubscribe a person from a category's emails, for the
footer of a notifier's email; nil without `:mail_url` and
`:secret_key_base` in the config.

# `update_device`

```elixir
@spec update_device(String.t(), Ecto.UUID.t(), keyword() | map()) ::
  {:ok, Chime.Device.t()} | {:error, :not_found}
```

Turns a person's device on or off (`enabled:`), or names it (`name:`).

---

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