clacks/lib/clacks/actor.ex

90 lines
2.3 KiB
Elixir

defmodule Clacks.Actor do
require Logger
use Ecto.Schema
import Ecto.Changeset
import Ecto.Query
alias Clacks.Repo
@type t() :: %__MODULE__{
ap_id: String.t(),
nickname: String.t(),
local: boolean(),
data: map(),
followers: [String.t()],
user: Clacks.User.t()
}
@primary_key {:id, FlakeId.Ecto.Type, autogenerate: true}
schema "actors" do
field :ap_id, :string
field :nickname, :string
field :local, :boolean
field :data, :map
field :followers, {:array, :string}, default: []
belongs_to :user, Clacks.User
timestamps()
end
def changeset(%__MODULE__{} = schema, attrs) do
schema
|> cast(attrs, [:ap_id, :nickname, :local, :data, :followers])
|> validate_required([:ap_id, :nickname, :local, :data])
end
@spec get_by_nickname(nickname :: String.t()) :: t() | nil
def get_by_nickname(nickname) do
Repo.get_by(__MODULE__, nickname: nickname)
end
@spec get_by_ap_id(ap_id :: String.t(), force_refetch :: boolean()) :: t() | nil
def get_by_ap_id(ap_id, force_refetch \\ false) do
if force_refetch do
fetch(ap_id)
else
get_cached_by_ap_id(ap_id) || fetch(ap_id)
end
end
@spec get_cached_by_ap_id(ap_id :: String.t()) :: t() | nil
def get_cached_by_ap_id(ap_id) do
Repo.get_by(__MODULE__, ap_id: ap_id)
end
@spec fetch(ap_id :: String.t()) :: t() | nil
def fetch(ap_id) do
case Clacks.ActivityPub.Fetcher.fetch_actor(ap_id) do
nil ->
nil
data ->
ap_id = data["id"]
existing = get_cached_by_ap_id(ap_id) |> Repo.preload(:user)
changeset =
changeset(existing || %__MODULE__{}, %{
ap_id: ap_id,
nickname: data["preferredUsername"] <> "@" <> URI.parse(ap_id).host,
local: false,
data: data
})
case Repo.insert_or_update(changeset) do
{:ok, actor} ->
actor
{:error, changeset} ->
Logger.error("Couldn't store remote actor #{ap_id}: #{inspect(changeset)}")
nil
end
end
end
@spec get_all_following(actor :: t()) :: [t()]
def get_all_following(actor) do
Repo.all(from a in __MODULE__, where: ^actor.ap_id in a.followers)
end
end