defmodule Clacks.Actor do require Logger use Ecto.Schema import Ecto.Changeset import Ecto.Query alias Clacks.Repo @type t() :: %__MODULE__{} @primary_key {:id, FlakeId.Ecto.Type, autogenerate: true} schema "actors" do field :ap_id, :string field :nickname, :string field :local, :boolean field :data, :map belongs_to :user, Clacks.User timestamps() end def changeset(%__MODULE__{} = schema, attrs) do schema |> cast(attrs, [:ap_id, :nickname, :local, :data]) |> validate_required([:ap_id, :nickname, :local, :data]) 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) when is_binary(ap_id) 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) when is_binary(ap_id) do Repo.one(from a in __MODULE__, where: a.ap_id == ^ap_id) end @spec fetch(ap_id :: String.t()) :: t() | nil def fetch(ap_id) when is_binary(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 end