# `MishkaBlog.Comment`
[🔗](https://github.com/mishka-group/mishka_blog/blob/v0.0.3-alpha.2/lib/mishka_blog/resources/comment.ex#L5)

Manages blog post comments in the MishkaBlog plugin with full versioning support.

This resource represents comments on blog posts with support for nested threading
through parent-child relationships. Each comment belongs to a specific site (via site_id)
and a blog post, and can have replies, creating conversation threads. Comments
support multiple statuses for moderation workflows (pending, approved, flagged, spam).

Comments feature full version control through PaperTrail integration, tracking
all changes with snapshot-based history including content edits, status changes,
and moderation actions. The resource implements soft deletion via archival and
provides comprehensive metadata tracking including sender information (IP address,
user agent) for moderation and security purposes.

Comments can be pinned to highlight important discussions, and the threading system
enables rich conversation structures. This resource uses multi-tenancy with site_id
as the tenant attribute, ensuring comments are scoped to their respective sites.

## Multi-Tenancy

Comments are site-specific and use attribute-based multi-tenancy:
- Tenant actions require a site_id context
- Master actions (master_read, master_permanent_destroy) can bypass tenant filtering
- Public actions (public_list, public_get) show only approved comments

## Public Access

- `public_list` - Lists all approved comments (paginated)
- `public_get` - Gets a single approved comment by ID
- `approved_by_post` - Lists approved comments for a specific post

## Guest Comments

A comment is written by a user (`author_id`) or by a guest who has no account here, such as a
visitor whose comment was moved in from another system. A guest's comment carries the name the
guest gave (`guest_name`) and, when given, an email (`guest_email`).

- Only `:create`, the admin's create, writes them. Given a `guest_name` and no `author_id`, it
  leaves the author empty instead of making the acting admin the author. `:create_public`
  takes no guest fields: a reader's comment is always the reader's own.
- `:create` refuses a comment with neither an author nor a guest name, and every create or
  update that sets `author_id` refuses one with both. A guest's email without the guest's
  name is refused, and so is an email that is not one.
- The name and the email are plain text, as `content` is: their tags are taken out
  (`MishkaCmsCore.PlainText`) and an `&` is kept as typed. The email is then lowercased and
  checked as an email.
- The email is for moderators only. Like `sender_data`, a field policy gives it to an actor
  with the comment permission and `%Ash.ForbiddenField{}` to everyone else, so a public read
  and the public JSON:API never return it, and a filter or sort on it matches nothing for
  them. It is `sensitive?`, so it never shows when a record is inspected or logged.

# `t`

```elixir
@type t() :: %MishkaBlog.Comment{
  __lateral_join_source__: term(),
  __meta__: term(),
  __metadata__: term(),
  __order__: term(),
  aggregates: term(),
  archived_at: term(),
  author: term(),
  author_id: term(),
  author_master?: term(),
  author_profile: term(),
  calculations: term(),
  content: term(),
  downvote_count: term(),
  effective_permissions: term(),
  guest_email: term(),
  guest_name: term(),
  id: term(),
  inserted_at: term(),
  is_pinned: term(),
  locked_collections: term(),
  locked_posts: term(),
  locks_apply?: term(),
  master_post: term(),
  parent_comment: term(),
  parent_comment_id: term(),
  post: term(),
  post_id: term(),
  rendered_content: term(),
  replies: term(),
  sender_data: term(),
  site: term(),
  site_id: term(),
  status: term(),
  updated_at: term(),
  upvote_count: term(),
  votes: term()
}
```

# `__guarded_change__`

Apply the full GuardedStruct pipeline (sanitize → validate → derive →
main_validator) to `attrs` and return either `{:ok, transformed_attrs}`
or `{:error, errors}`. Wire this into an `Ash.Resource.Change` to plug
guardedstruct rules into Ash's changeset pipeline.

The function is named `__guarded_change__` because it does more than
validate — it can also transform values (trim, downcase, slugify,
auto-fill, etc.) before they reach the data layer.

# `__guarded_field_meta__`

O(1) lookup of a field's compile-time metadata by name. Returns
`nil` for unknown fields.

# `__guarded_field_name_set__`

Compile-time-baked `MapSet` of every field name owned by the
`guardedstruct` block. Used by `GuardedStruct.AshResource.Change`
to decide, in O(1), whether a key in `changeset.atomics`
belongs to the pipeline.

# `default_short_name`

# `input`

```elixir
@spec input(values :: map() | Keyword.t()) :: map() | no_return()
```

Validates that the keys in the provided input are valid for at least one action on the resource.

Raises a KeyError error at compile time if not. This exists because generally a struct should only ever
be created by Ash as a result of a successful action. You should not be creating records manually in code,
e.g `%MyResource{value: 1, value: 2}`. Generally that is fine, but often with embedded resources it is nice
to be able to validate the keys that are being provided, e.g

```elixir
Resource
|> Ash.Changeset.for_create(:create, %{embedded: EmbeddedResource.input(foo: 1, bar: 2)})
|> Ash.create()
```

# `input`

```elixir
@spec input(values :: map() | Keyword.t(), action :: atom()) :: map() | no_return()
```

Same as `input/1`, except restricts the keys to values accepted by the action provided.

# `json_api_match_route`

# `primary_key_matches?`

---

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