2019-09-28 22:30:55 +00:00
|
|
|
defmodule Clacks.Activity do
|
2019-09-30 21:02:03 +00:00
|
|
|
require Logger
|
2019-09-28 22:30:55 +00:00
|
|
|
use Ecto.Schema
|
|
|
|
import Ecto.Changeset
|
2021-08-25 20:11:08 +00:00
|
|
|
alias Clacks.{Repo, Object}
|
2019-09-30 21:02:03 +00:00
|
|
|
import Ecto.Query
|
2019-09-28 22:30:55 +00:00
|
|
|
|
|
|
|
@type t() :: %__MODULE__{}
|
|
|
|
|
|
|
|
@primary_key {:id, FlakeId.Ecto.Type, autogenerate: true}
|
|
|
|
|
|
|
|
schema "activities" do
|
|
|
|
field :data, :map
|
|
|
|
field :local, :boolean
|
|
|
|
field :actor, :string
|
|
|
|
|
|
|
|
has_one :object, Clacks.Object, on_delete: :nothing, foreign_key: :id
|
|
|
|
|
|
|
|
timestamps()
|
|
|
|
end
|
|
|
|
|
|
|
|
def changeset(%__MODULE__{} = schema, attrs) do
|
|
|
|
schema
|
2019-09-30 20:52:37 +00:00
|
|
|
|> cast(attrs, [:data, :local, :actor])
|
|
|
|
|> validate_required([:data, :local, :actor])
|
2019-09-28 22:30:55 +00:00
|
|
|
end
|
2019-09-30 21:02:03 +00:00
|
|
|
|
2020-04-21 02:42:39 +00:00
|
|
|
@spec changeset_for_creating(activity :: map(), local :: boolean()) :: Ecto.Changeset.t()
|
2019-10-06 23:41:18 +00:00
|
|
|
def changeset_for_creating(activity, local \\ false) do
|
|
|
|
changeset(%__MODULE__{}, %{
|
|
|
|
data: activity,
|
|
|
|
local: local,
|
|
|
|
actor: activity["actor"]
|
|
|
|
})
|
|
|
|
end
|
|
|
|
|
2021-08-25 20:11:08 +00:00
|
|
|
@spec get(id :: String.t(), opts :: Keyword.t()) :: t() | nil
|
|
|
|
def get(id, opts \\ []) do
|
|
|
|
if Keyword.get(opts, :with_object, false) do
|
|
|
|
__MODULE__
|
|
|
|
|> where([a], a.id == ^id)
|
|
|
|
|> preload_object()
|
|
|
|
|> Repo.one()
|
|
|
|
else
|
|
|
|
Repo.get(__MODULE__, id)
|
|
|
|
end
|
2019-10-06 23:41:18 +00:00
|
|
|
end
|
|
|
|
|
2019-09-30 21:02:03 +00:00
|
|
|
@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.one(from a in __MODULE__, where: fragment("?->>'id'", a.data) == ^ap_id)
|
|
|
|
end
|
|
|
|
|
2020-04-21 02:42:39 +00:00
|
|
|
@spec get_by_object_ap_id(object_id :: String.t()) :: t() | nil
|
|
|
|
def get_by_object_ap_id(object_id) do
|
2021-08-25 20:11:08 +00:00
|
|
|
__MODULE__
|
|
|
|
|> where(
|
|
|
|
[a],
|
|
|
|
fragment("COALESCE(?->'object'->>'id', ?->>'object')", a.data, a.data) == ^object_id
|
|
|
|
)
|
|
|
|
|> preload_object()
|
|
|
|
|> Repo.one()
|
|
|
|
end
|
|
|
|
|
|
|
|
@spec join_with_object(Ecto.Queryable.t()) :: Ecto.Query.t()
|
|
|
|
def join_with_object(query) do
|
|
|
|
join(query, :inner, [a], o in Object,
|
|
|
|
as: :object,
|
|
|
|
on:
|
|
|
|
fragment("?->>'id' = COALESCE(?->'object'->>'id', ?->>'object')", o.data, a.data, a.data)
|
2020-04-21 02:42:39 +00:00
|
|
|
)
|
|
|
|
end
|
|
|
|
|
2021-08-25 20:11:08 +00:00
|
|
|
@spec preload_object(Ecto.Queryable.t()) :: Ecto.Query.t()
|
|
|
|
def preload_object(query) do
|
|
|
|
if Ecto.Query.has_named_binding?(query, :object) do
|
|
|
|
query
|
|
|
|
else
|
|
|
|
join_with_object(query)
|
|
|
|
end
|
|
|
|
|> preload([_a, object: object], object: object)
|
|
|
|
end
|
|
|
|
|
2019-09-30 21:02:03 +00:00
|
|
|
@spec fetch(ap_id :: String.t()) :: t() | nil
|
|
|
|
def fetch(ap_id) do
|
|
|
|
case Clacks.ActivityPub.Fetcher.fetch_activity(ap_id) do
|
|
|
|
nil ->
|
|
|
|
nil
|
|
|
|
|
|
|
|
data ->
|
|
|
|
actor = data["actor"] || data["attributedTo"]
|
|
|
|
|
|
|
|
existing = get_cached_by_ap_id(data["id"])
|
|
|
|
|
|
|
|
changeset =
|
|
|
|
changeset(existing || %__MODULE__{}, %{
|
|
|
|
data: data,
|
|
|
|
local: false,
|
|
|
|
actor: actor
|
|
|
|
})
|
|
|
|
|
|
|
|
case Repo.insert_or_update(changeset) do
|
|
|
|
{:ok, activity} ->
|
|
|
|
_ = Clacks.Actor.get_by_ap_id(actor)
|
|
|
|
|
|
|
|
activity
|
|
|
|
|
|
|
|
{:error, changeset} ->
|
|
|
|
Logger.error("Couldn't store remote activity #{ap_id}: #{inspect(changeset)}")
|
|
|
|
nil
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2021-08-25 20:11:08 +00:00
|
|
|
|
|
|
|
@spec data_with_object(activity :: Activity.t()) :: map()
|
|
|
|
def data_with_object(%__MODULE__{
|
|
|
|
data: %{"object" => object_id} = data,
|
|
|
|
object: %Object{data: object_data}
|
|
|
|
})
|
|
|
|
when is_binary(object_id) do
|
|
|
|
Map.put(data, "object", object_data)
|
|
|
|
end
|
|
|
|
|
|
|
|
def data_with_object(%___MODULE__{data: data}), do: data
|
2019-09-28 22:30:55 +00:00
|
|
|
end
|