# `PhoenixKitComments.ResourceHandler`
[🔗](https://github.com/BeamLabEU/phoenix_kit_comments/blob/v0.4.5/lib/phoenix_kit_comments/resource_handler.ex#L1)

The contract a host module implements to hook into comments.

Register one per `resource_type`:

    config :phoenix_kit, :comment_resource_handlers, %{
      "post" => MyApp.Posts
    }

Every callback is optional — implement only the events you care about — so
a handler is free to be a two-line module that reacts to one thing.

## Why adopt the behaviour

Dispatch is by `function_exported?/3`, and it always will be: a handler is
named in host config, may live in another application, and may legitimately
implement none of these. That is a good property, and it has one bad
consequence — a callback this package never finds is indistinguishable from
one you chose not to write. Misname `on_comment_create` (no `d`), take two
arguments instead of three, and nothing happens: no error, no warning, no
notification, and nothing to grep for. The host discovers it when someone
reports that notifications stopped.

`@behaviour PhoenixKitComments.ResourceHandler` turns that into a compile
warning at the point of the mistake. It is not required and changes nothing
at runtime; it only makes the failure loud. Handlers registered today keep
working untouched.

    defmodule MyApp.Posts do
      @behaviour PhoenixKitComments.ResourceHandler

      @impl true
      def on_comment_created(_type, post_uuid, comment) do
        MyApp.Posts.notify_author(post_uuid, comment)
      end
    end

## What is passed

The event callbacks all receive the `resource_type` and `resource_uuid` the
comment was filed against, so one handler can serve several types.

Reaction callbacks take a map rather than the comment alone because the
comment row carries its *author*, not the person reacting — `:liker_uuid`
is the only place that appears.

## Paths are RAW

`resolve_comment_resources/1` returns `path` WITHOUT the PhoenixKit url
prefix — the renderer applies `Routes.path/1` once. A handler that
pre-prefixes gets a doubled prefix.

## What is NOT decided here

Reaction callbacks fire only when the state actually changed
(`{:ok, :liked}`), never on an `:already_liked` no-op. Self-action skipping
— not notifying someone who liked their own comment — is left to the
handler, which is the only party that knows whether that matters.

A callback that raises is caught and logged; it cannot fail the comment
that triggered it.

# `reaction`

```elixir
@type reaction() :: %{
  comment: PhoenixKitComments.Comment.t(),
  liker_uuid: Ecto.UUID.t()
}
```

A reaction event. The comment plus who reacted — the row itself only knows
its author.

# `resource_type`

```elixir
@type resource_type() :: String.t()
```

The host's own type key for a commentable record, e.g. `"order"`.

# `resource_uuid`

```elixir
@type resource_uuid() :: Ecto.UUID.t()
```

The record's uuid.

# `on_comment_created`
*optional* 

```elixir
@callback on_comment_created(
  resource_type(),
  resource_uuid(),
  PhoenixKitComments.Comment.t()
) :: any()
```

A comment was created. Check `comment.parent_uuid` to tell a reply apart.

# `on_comment_deleted`
*optional* 

```elixir
@callback on_comment_deleted(
  resource_type(),
  resource_uuid(),
  PhoenixKitComments.Comment.t()
) :: any()
```

A comment was soft-deleted.

# `on_comment_disliked`
*optional* 

```elixir
@callback on_comment_disliked(resource_type(), resource_uuid(), reaction()) :: any()
```

Someone disliked a comment.

# `on_comment_liked`
*optional* 

```elixir
@callback on_comment_liked(resource_type(), resource_uuid(), reaction()) :: any()
```

Someone liked a comment. Fires only on an actual change of state.

# `on_comment_undisliked`
*optional* 

```elixir
@callback on_comment_undisliked(resource_type(), resource_uuid(), reaction()) :: any()
```

Someone withdrew a dislike.

# `on_comment_unliked`
*optional* 

```elixir
@callback on_comment_unliked(resource_type(), resource_uuid(), reaction()) :: any()
```

Someone withdrew a like.

# `resolve_comment_resources`
*optional* 

```elixir
@callback resolve_comment_resources([resource_uuid()]) :: %{
  optional(resource_uuid()) =&gt; map()
}
```

Titles and RAW deep-links for the given uuids.

Returns `%{uuid => %{title: String.t(), path: String.t()}}`. May also
carry `:full_title` (tooltip) and `:thumb_url` (chip thumbnail); a uuid
the handler does not recognise is simply absent from the map.

# `callbacks`

```elixir
@spec callbacks() :: [{atom(), arity()}]
```

The callbacks this package dispatches, as `{name, arity}`.

Exposed so the dispatcher and the contract can be checked against each
other — a callback added to one and not the other is exactly the silent
mismatch this module exists to prevent.

# `event_callbacks`

```elixir
@spec event_callbacks() :: [{atom(), arity()}]
```

The comment-event callbacks, i.e. everything except
`resolve_comment_resources/1`.

These are the ones the dispatcher fires by name with three arguments;
`resolve_comment_resources/1` is a lookup the renderer calls directly and
answers a different question, so checks over "the events" have to exclude
it rather than assume the contract is uniform.

---

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