defmodule GitGudWeb.ActorLive.Show do
@moduledoc """
Show a (remote) ActivityPub actor's local cached profile.
Useful endpoints we surface here:
* canonical actor URL (link out)
* preferred username / display name / summary
* last fetched + status
* Report this actor button
The URL form is `/actors/:id` rather than `/@handle@host` because
multiple remote handles can share the same Person under our local
`actors.id`. We may add a `/@handle@host` redirector later.
"""
use GitGudWeb, :live_view
alias GitGud.Federation.Actor
alias GitGud.Repo
@impl true
def mount(%{"id" => id}, _session, socket) do
actor = Repo.get!(Actor, id)
{:ok,
socket
|> assign(:actor, actor)
|> assign(:host, host_of(actor))
|> assign(:page_title, profile_title(actor))}
end
@impl true
def handle_event(
"report",
%{"target_type" => type, "target_id" => id, "reason" => reason},
socket
) do
case socket.assigns.current_scope && socket.assigns.current_scope.user do
nil ->
{:noreply, put_flash(socket, :error, "Sign in to report.")}
user ->
case GitGud.Reports.open(user, {type, String.to_integer(id)}, reason) do
{:ok, _} -> {:noreply, put_flash(socket, :info, "Report submitted. Thanks.")}
{:error, %Ecto.Changeset{}} -> {:noreply, put_flash(socket, :info, "Already reported.")}
end
end
end
defp host_of(%Actor{actor_url: url}) when is_binary(url) do
URI.parse(url).host || "?"
end
defp host_of(_), do: "?"
defp profile_title(actor) do
name = actor.preferred_username || actor.name || "actor"
name <> "@" <> host_of(actor)
end
@impl true
def render(assigns) do
~H"""
<Layouts.app flash={@flash} current_scope={@current_scope}>
<div class="space-y-6 max-w-2xl">
<header class="flex items-start justify-between gap-3">
<div>
<h1 class="text-2xl font-semibold">
{@actor.name || @actor.preferred_username || "actor"}
</h1>
<p class="text-sm opacity-70 font-mono">
{@actor.preferred_username}@{@host}
<span class="badge badge-xs ml-2">{@actor.kind}</span>
<span :if={@actor.status != "active"} class="badge badge-xs badge-warning ml-1">
{@actor.status}
</span>
</p>
<p class="text-xs opacity-60 mt-1">
<.link href={@actor.actor_url} class="link link-hover">{@actor.actor_url}</.link>
</p>
</div>
<button
:if={@current_scope && @current_scope.user && @actor.kind == "remote"}
type="button"
phx-click="report"
phx-value-target_type="actor"
phx-value-target_id={@actor.id}
phx-value-reason="abuse"
class="btn btn-xs btn-ghost opacity-70 hover:opacity-100"
data-confirm={"Report #{@actor.preferred_username || @actor.name} for abuse?"}
title="Report this actor"
>
<.icon name="hero-flag" class="size-4" /> Report
</button>
</header>
<section :if={@actor.summary} class="prose max-w-none border border-base-300 rounded p-4">
<p>{@actor.summary}</p>
</section>
<section class="text-xs opacity-70 grid grid-cols-2 gap-x-4 gap-y-1">
<span>Type</span><span>{@actor.actor_type}</span>
<span :if={@actor.last_fetched_at}>Last fetched</span>
<span :if={@actor.last_fetched_at}>
{Calendar.strftime(@actor.last_fetched_at, "%Y-%m-%d %H:%M UTC")}
</span>
<span :if={@actor.inbox_url}>Inbox</span>
<span :if={@actor.inbox_url} class="font-mono break-all">{@actor.inbox_url}</span>
<span :if={@actor.fetch_error}>Last error</span>
<span :if={@actor.fetch_error} class="font-mono break-all">{@actor.fetch_error}</span>
</section>
</div>
</Layouts.app>
"""
end
end
4.0 KiB · text
5af55d9