@@ -163,7 +163,6 @@ defmodule Mobilizon.Actors.Actor do
|
||||
])
|
||||
|> validate_required([
|
||||
:url,
|
||||
:outbox_url,
|
||||
:inbox_url,
|
||||
:type,
|
||||
:domain,
|
||||
@@ -184,6 +183,44 @@ defmodule Mobilizon.Actors.Actor do
|
||||
changes
|
||||
end
|
||||
|
||||
def relay_creation(%{url: url, preferred_username: preferred_username} = _params) do
|
||||
key = :public_key.generate_key({:rsa, 2048, 65_537})
|
||||
entry = :public_key.pem_entry_encode(:RSAPrivateKey, key)
|
||||
pem = [entry] |> :public_key.pem_encode() |> String.trim_trailing()
|
||||
|
||||
vars = %{
|
||||
"name" => Mobilizon.CommonConfig.get([:instance, :name], "Mobilizon"),
|
||||
"summary" =>
|
||||
Mobilizon.CommonConfig.get(
|
||||
[:instance, :description],
|
||||
"An internal service actor for this Mobilizon instance"
|
||||
),
|
||||
"url" => url,
|
||||
"keys" => pem,
|
||||
"preferred_username" => preferred_username,
|
||||
"domain" => nil,
|
||||
"inbox_url" => "#{MobilizonWeb.Endpoint.url()}/inbox",
|
||||
"followers_url" => "#{url}/followers",
|
||||
"following_url" => "#{url}/following",
|
||||
"shared_inbox_url" => "#{MobilizonWeb.Endpoint.url()}/inbox",
|
||||
"type" => :Application
|
||||
}
|
||||
|
||||
cast(%Actor{}, vars, [
|
||||
:type,
|
||||
:name,
|
||||
:summary,
|
||||
:url,
|
||||
:keys,
|
||||
:preferred_username,
|
||||
:domain,
|
||||
:inbox_url,
|
||||
:followers_url,
|
||||
:following_url,
|
||||
:shared_inbox_url
|
||||
])
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changeset for group creation
|
||||
"""
|
||||
@@ -240,6 +277,14 @@ defmodule Mobilizon.Actors.Actor do
|
||||
:outbox_url,
|
||||
build_url(username, :outbox)
|
||||
)
|
||||
|> put_change(
|
||||
:followers_url,
|
||||
build_url(username, :followers)
|
||||
)
|
||||
|> put_change(
|
||||
:following_url,
|
||||
build_url(username, :following)
|
||||
)
|
||||
|> put_change(
|
||||
:inbox_url,
|
||||
build_url(username, :inbox)
|
||||
@@ -325,18 +370,30 @@ defmodule Mobilizon.Actors.Actor do
|
||||
%{total: Task.await(total), elements: Task.await(elements)}
|
||||
end
|
||||
|
||||
@spec get_full_followers(struct()) :: list()
|
||||
def get_full_followers(%Actor{id: actor_id} = _actor) do
|
||||
Repo.all(
|
||||
from(
|
||||
a in Actor,
|
||||
join: f in Follower,
|
||||
on: a.id == f.actor_id,
|
||||
where: f.target_actor_id == ^actor_id
|
||||
)
|
||||
defp get_full_followers_query(%Actor{id: actor_id} = _actor) do
|
||||
from(
|
||||
a in Actor,
|
||||
join: f in Follower,
|
||||
on: a.id == f.actor_id,
|
||||
where: f.target_actor_id == ^actor_id
|
||||
)
|
||||
end
|
||||
|
||||
@spec get_full_followers(struct()) :: list()
|
||||
def get_full_followers(%Actor{} = actor) do
|
||||
actor
|
||||
|> get_full_followers_query()
|
||||
|> Repo.all()
|
||||
end
|
||||
|
||||
@spec get_full_external_followers(struct()) :: list()
|
||||
def get_full_external_followers(%Actor{} = actor) do
|
||||
actor
|
||||
|> get_full_followers_query()
|
||||
|> where([a], not is_nil(a.domain))
|
||||
|> Repo.all()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Get followings from an actor
|
||||
|
||||
@@ -404,18 +461,19 @@ defmodule Mobilizon.Actors.Actor do
|
||||
Make an actor follow another
|
||||
"""
|
||||
@spec follow(struct(), struct(), boolean()) :: Follower.t() | {:error, String.t()}
|
||||
def follow(%Actor{} = followed, %Actor{} = follower, approved \\ true) do
|
||||
def follow(%Actor{} = followed, %Actor{} = follower, url \\ nil, approved \\ true) do
|
||||
with {:suspended, false} <- {:suspended, followed.suspended},
|
||||
# Check if followed has blocked follower
|
||||
{:already_following, false} <- {:already_following, following?(follower, followed)} do
|
||||
do_follow(follower, followed, approved)
|
||||
do_follow(follower, followed, approved, url)
|
||||
else
|
||||
{:already_following, %Follower{}} ->
|
||||
{:error,
|
||||
{:error, :already_following,
|
||||
"Could not follow actor: you are already following #{followed.preferred_username}"}
|
||||
|
||||
{:suspended, _} ->
|
||||
{:error, "Could not follow actor: #{followed.preferred_username} has been suspended"}
|
||||
{:error, :suspended,
|
||||
"Could not follow actor: #{followed.preferred_username} has been suspended"}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -433,13 +491,20 @@ defmodule Mobilizon.Actors.Actor do
|
||||
end
|
||||
end
|
||||
|
||||
@spec do_follow(struct(), struct(), boolean) ::
|
||||
@spec do_follow(struct(), struct(), boolean(), String.t()) ::
|
||||
{:ok, Follower.t()} | {:error, Ecto.Changeset.t()}
|
||||
defp do_follow(%Actor{} = follower, %Actor{} = followed, approved) do
|
||||
defp do_follow(%Actor{} = follower, %Actor{} = followed, approved, url) do
|
||||
Logger.info(
|
||||
"Making #{follower.preferred_username} follow #{followed.preferred_username} (approved: #{
|
||||
approved
|
||||
})"
|
||||
)
|
||||
|
||||
Actors.create_follower(%{
|
||||
"actor_id" => follower.id,
|
||||
"target_actor_id" => followed.id,
|
||||
"approved" => approved
|
||||
"approved" => approved,
|
||||
"url" => url
|
||||
})
|
||||
end
|
||||
|
||||
|
||||
@@ -297,7 +297,7 @@ defmodule Mobilizon.Actors do
|
||||
{:ok, actor}
|
||||
|
||||
err ->
|
||||
Logger.error(inspect(err))
|
||||
Logger.debug(inspect(err))
|
||||
{:error, err}
|
||||
end
|
||||
end
|
||||
@@ -475,16 +475,16 @@ defmodule Mobilizon.Actors do
|
||||
@spec get_or_fetch_by_url(String.t(), bool()) :: {:ok, Actor.t()} | {:error, String.t()}
|
||||
def get_or_fetch_by_url(url, preload \\ false) do
|
||||
case get_actor_by_url(url, preload) do
|
||||
{:ok, actor} ->
|
||||
{:ok, %Actor{} = actor} ->
|
||||
{:ok, actor}
|
||||
|
||||
_ ->
|
||||
case ActivityPub.make_actor_from_url(url, preload) do
|
||||
{:ok, actor} ->
|
||||
{:ok, %Actor{} = actor} ->
|
||||
{:ok, actor}
|
||||
|
||||
_ ->
|
||||
Logger.error("Could not fetch by AP id")
|
||||
Logger.warn("Could not fetch by AP id")
|
||||
{:error, "Could not fetch by AP id"}
|
||||
end
|
||||
end
|
||||
@@ -655,6 +655,18 @@ defmodule Mobilizon.Actors do
|
||||
end
|
||||
end
|
||||
|
||||
def get_or_create_service_actor_by_url(url, preferred_username \\ "relay") do
|
||||
case get_actor_by_url(url) do
|
||||
{:ok, %Actor{} = actor} ->
|
||||
{:ok, actor}
|
||||
|
||||
_ ->
|
||||
%{url: url, preferred_username: preferred_username}
|
||||
|> Actor.relay_creation()
|
||||
|> Repo.insert()
|
||||
end
|
||||
end
|
||||
|
||||
alias Mobilizon.Actors.Member
|
||||
|
||||
@doc """
|
||||
@@ -895,7 +907,7 @@ defmodule Mobilizon.Actors do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Get a follower by the followed actor and following actor
|
||||
Get a follow by the followed actor and following actor
|
||||
"""
|
||||
@spec get_follower(Actor.t(), Actor.t()) :: Follower.t()
|
||||
def get_follower(%Actor{id: followed_id}, %Actor{id: follower_id}) do
|
||||
@@ -904,6 +916,19 @@ defmodule Mobilizon.Actors do
|
||||
)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Get a follow by the followed actor and following actor
|
||||
"""
|
||||
@spec get_follow_by_url(String.t()) :: Follower.t()
|
||||
def get_follow_by_url(url) do
|
||||
Repo.one(
|
||||
from(f in Follower,
|
||||
where: f.url == ^url,
|
||||
preload: [:actor, :target_actor]
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a follower.
|
||||
|
||||
@@ -1009,7 +1034,7 @@ defmodule Mobilizon.Actors do
|
||||
|
||||
{:error, error} ->
|
||||
Logger.error("Error while removing an upload file")
|
||||
Logger.error(inspect(error))
|
||||
Logger.debug(inspect(error))
|
||||
{:ok, actor}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -7,9 +7,11 @@ defmodule Mobilizon.Actors.Follower do
|
||||
alias Mobilizon.Actors.Follower
|
||||
alias Mobilizon.Actors.Actor
|
||||
|
||||
@primary_key {:id, :binary_id, autogenerate: true}
|
||||
|
||||
schema "followers" do
|
||||
field(:approved, :boolean, default: false)
|
||||
field(:score, :integer, default: 1000)
|
||||
field(:url, :string)
|
||||
belongs_to(:target_actor, Actor)
|
||||
belongs_to(:actor, Actor)
|
||||
end
|
||||
@@ -17,8 +19,34 @@ defmodule Mobilizon.Actors.Follower do
|
||||
@doc false
|
||||
def changeset(%Follower{} = member, attrs) do
|
||||
member
|
||||
|> cast(attrs, [:score, :approved, :target_actor_id, :actor_id])
|
||||
|> validate_required([:score, :approved, :target_actor_id, :actor_id])
|
||||
|> cast(attrs, [:url, :approved, :target_actor_id, :actor_id])
|
||||
|> generate_url()
|
||||
|> validate_required([:url, :approved, :target_actor_id, :actor_id])
|
||||
|> unique_constraint(:target_actor_id, name: :followers_actor_target_actor_unique_index)
|
||||
end
|
||||
|
||||
# If there's a blank URL that's because we're doing the first insert
|
||||
defp generate_url(%Ecto.Changeset{data: %Follower{url: nil}} = changeset) do
|
||||
case fetch_change(changeset, :url) do
|
||||
{:ok, _url} -> changeset
|
||||
:error -> do_generate_url(changeset)
|
||||
end
|
||||
end
|
||||
|
||||
# Most time just go with the given URL
|
||||
defp generate_url(%Ecto.Changeset{} = changeset), do: changeset
|
||||
|
||||
defp do_generate_url(%Ecto.Changeset{} = changeset) do
|
||||
uuid = Ecto.UUID.generate()
|
||||
|
||||
changeset
|
||||
|> put_change(
|
||||
:url,
|
||||
"#{MobilizonWeb.Endpoint.url()}/follow/#{uuid}"
|
||||
)
|
||||
|> put_change(
|
||||
:id,
|
||||
uuid
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -32,7 +32,8 @@ defmodule Mobilizon.Events do
|
||||
:tracks,
|
||||
:tags,
|
||||
:participants,
|
||||
:physical_address
|
||||
:physical_address,
|
||||
:picture
|
||||
]
|
||||
)
|
||||
|> paginate(page, limit)
|
||||
@@ -248,7 +249,8 @@ defmodule Mobilizon.Events do
|
||||
:tracks,
|
||||
:tags,
|
||||
:participants,
|
||||
:physical_address
|
||||
:physical_address,
|
||||
:picture
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user