Refactoring of Actors context
This commit is contained in:
parent
3a4a006c44
commit
4418275223
@ -1,44 +1,17 @@
|
|||||||
import EctoEnum
|
|
||||||
|
|
||||||
defenum(Mobilizon.Actors.ActorTypeEnum, :actor_type, [
|
|
||||||
:Person,
|
|
||||||
:Application,
|
|
||||||
:Group,
|
|
||||||
:Organization,
|
|
||||||
:Service
|
|
||||||
])
|
|
||||||
|
|
||||||
defenum(Mobilizon.Actors.ActorOpennessEnum, :actor_openness, [
|
|
||||||
:invite_only,
|
|
||||||
:moderated,
|
|
||||||
:open
|
|
||||||
])
|
|
||||||
|
|
||||||
defenum(Mobilizon.Actors.ActorVisibilityEnum, :actor_visibility_type, [
|
|
||||||
:public,
|
|
||||||
:unlisted,
|
|
||||||
# Probably unused
|
|
||||||
:restricted,
|
|
||||||
:private
|
|
||||||
])
|
|
||||||
|
|
||||||
defmodule Mobilizon.Actors.Actor do
|
defmodule Mobilizon.Actors.Actor do
|
||||||
@moduledoc """
|
@moduledoc """
|
||||||
Represents an actor (local and remote actors)
|
Represents an actor (local and remote).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
use Ecto.Schema
|
use Ecto.Schema
|
||||||
|
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
import Ecto.Query
|
|
||||||
|
|
||||||
alias Mobilizon.Actors
|
alias Mobilizon.{Actors, Config, Crypto}
|
||||||
alias Mobilizon.Actors.{Actor, Follower, Member}
|
alias Mobilizon.Actors.{Actor, ActorOpenness, ActorType, ActorVisibility, Follower, Member}
|
||||||
alias Mobilizon.Config
|
|
||||||
alias Mobilizon.Events.{Event, FeedToken}
|
alias Mobilizon.Events.{Event, FeedToken}
|
||||||
alias Mobilizon.Media.File
|
alias Mobilizon.Media.File
|
||||||
alias Mobilizon.Reports.{Report, Note}
|
alias Mobilizon.Reports.{Report, Note}
|
||||||
alias Mobilizon.Storage.{Page, Repo}
|
|
||||||
alias Mobilizon.Users.User
|
alias Mobilizon.Users.User
|
||||||
|
|
||||||
alias MobilizonWeb.Router.Helpers, as: Routes
|
alias MobilizonWeb.Router.Helpers, as: Routes
|
||||||
@ -46,46 +19,38 @@ defmodule Mobilizon.Actors.Actor do
|
|||||||
|
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
# @type t :: %Actor{description: String.t, id: integer(), inserted_at: DateTime.t, updated_at: DateTime.t, display_name: String.t, domain: String.t, keys: String.t, suspended: boolean(), url: String.t, username: String.t, organized_events: list(), groups: list(), group_request: list(), user: User.t, field: ActorTypeEnum.t}
|
@type t :: %__MODULE__{
|
||||||
|
url: String.t(),
|
||||||
|
outbox_url: String.t(),
|
||||||
|
inbox_url: String.t(),
|
||||||
|
following_url: String.t(),
|
||||||
|
followers_url: String.t(),
|
||||||
|
shared_inbox_url: String.t(),
|
||||||
|
type: ActorType.t(),
|
||||||
|
name: String.t(),
|
||||||
|
domain: String.t(),
|
||||||
|
summary: String.t(),
|
||||||
|
preferred_username: String.t(),
|
||||||
|
keys: String.t(),
|
||||||
|
manually_approves_followers: boolean,
|
||||||
|
openness: ActorOpenness.t(),
|
||||||
|
visibility: ActorVisibility.t(),
|
||||||
|
suspended: boolean,
|
||||||
|
avatar: File.t(),
|
||||||
|
banner: File.t(),
|
||||||
|
user: User.t(),
|
||||||
|
followers: [Follower.t()],
|
||||||
|
followings: [Follower.t()],
|
||||||
|
organized_events: [Event.t()],
|
||||||
|
feed_tokens: [FeedToken.t()],
|
||||||
|
created_reports: [Report.t()],
|
||||||
|
subject_reports: [Report.t()],
|
||||||
|
report_notes: [Note.t()],
|
||||||
|
memberships: [Actor.t()]
|
||||||
|
}
|
||||||
|
|
||||||
schema "actors" do
|
@required_attrs [:preferred_username, :keys, :suspended, :url]
|
||||||
field(:url, :string)
|
@optional_attrs [
|
||||||
field(:outbox_url, :string)
|
|
||||||
field(:inbox_url, :string)
|
|
||||||
field(:following_url, :string)
|
|
||||||
field(:followers_url, :string)
|
|
||||||
field(:shared_inbox_url, :string)
|
|
||||||
field(:type, Mobilizon.Actors.ActorTypeEnum, default: :Person)
|
|
||||||
field(:name, :string)
|
|
||||||
field(:domain, :string, default: nil)
|
|
||||||
field(:summary, :string)
|
|
||||||
field(:preferred_username, :string)
|
|
||||||
field(:keys, :string)
|
|
||||||
field(:manually_approves_followers, :boolean, default: false)
|
|
||||||
field(:openness, Mobilizon.Actors.ActorOpennessEnum, default: :moderated)
|
|
||||||
field(:visibility, Mobilizon.Actors.ActorVisibilityEnum, default: :private)
|
|
||||||
field(:suspended, :boolean, default: false)
|
|
||||||
# field(:openness, Mobilizon.Actors.ActorOpennessEnum, default: :moderated)
|
|
||||||
has_many(:followers, Follower, foreign_key: :target_actor_id)
|
|
||||||
has_many(:followings, Follower, foreign_key: :actor_id)
|
|
||||||
has_many(:organized_events, Event, foreign_key: :organizer_actor_id)
|
|
||||||
many_to_many(:memberships, Actor, join_through: Member)
|
|
||||||
belongs_to(:user, User)
|
|
||||||
has_many(:feed_tokens, FeedToken, foreign_key: :actor_id)
|
|
||||||
embeds_one(:avatar, File, on_replace: :update)
|
|
||||||
embeds_one(:banner, File, on_replace: :update)
|
|
||||||
has_many(:created_reports, Report, foreign_key: :reporter_id)
|
|
||||||
has_many(:subject_reports, Report, foreign_key: :reported_id)
|
|
||||||
has_many(:report_notes, Note, foreign_key: :moderator_id)
|
|
||||||
|
|
||||||
timestamps()
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc false
|
|
||||||
def changeset(%Actor{} = actor, attrs) do
|
|
||||||
actor
|
|
||||||
|> Ecto.Changeset.cast(attrs, [
|
|
||||||
:url,
|
|
||||||
:outbox_url,
|
:outbox_url,
|
||||||
:inbox_url,
|
:inbox_url,
|
||||||
:shared_inbox_url,
|
:shared_inbox_url,
|
||||||
@ -95,135 +60,40 @@ defmodule Mobilizon.Actors.Actor do
|
|||||||
:name,
|
:name,
|
||||||
:domain,
|
:domain,
|
||||||
:summary,
|
:summary,
|
||||||
:preferred_username,
|
|
||||||
:keys,
|
|
||||||
:manually_approves_followers,
|
:manually_approves_followers,
|
||||||
:suspended,
|
|
||||||
:user_id
|
:user_id
|
||||||
])
|
]
|
||||||
|> build_urls()
|
@attrs @required_attrs ++ @optional_attrs
|
||||||
|> cast_embed(:avatar)
|
|
||||||
|> cast_embed(:banner)
|
|
||||||
|> unique_username_validator()
|
|
||||||
|> validate_required([:preferred_username, :keys, :suspended, :url])
|
|
||||||
|> unique_constraint(:preferred_username, name: :actors_preferred_username_domain_type_index)
|
|
||||||
|> unique_constraint(:url, name: :actors_url_index)
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc false
|
@update_required_attrs @required_attrs
|
||||||
def update_changeset(%Actor{} = actor, attrs) do
|
@update_optional_attrs [:name, :summary, :manually_approves_followers, :user_id]
|
||||||
actor
|
@update_attrs @update_required_attrs ++ @update_optional_attrs
|
||||||
|> Ecto.Changeset.cast(attrs, [
|
|
||||||
:name,
|
|
||||||
:summary,
|
|
||||||
:keys,
|
|
||||||
:manually_approves_followers,
|
|
||||||
:suspended,
|
|
||||||
:user_id
|
|
||||||
])
|
|
||||||
|> cast_embed(:avatar)
|
|
||||||
|> cast_embed(:banner)
|
|
||||||
|> validate_required([:preferred_username, :keys, :suspended, :url])
|
|
||||||
|> unique_constraint(:preferred_username, name: :actors_preferred_username_domain_type_index)
|
|
||||||
|> unique_constraint(:url, name: :actors_url_index)
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
@registration_required_attrs [:preferred_username, :keys, :suspended, :url, :type]
|
||||||
Changeset for person registration
|
@registration_optional_attrs [:domain, :name, :summary, :user_id]
|
||||||
"""
|
@registration_attrs @registration_required_attrs ++ @registration_optional_attrs
|
||||||
@spec registration_changeset(struct(), map()) :: Ecto.Changeset.t()
|
|
||||||
def registration_changeset(%Actor{} = actor, attrs) do
|
|
||||||
actor
|
|
||||||
|> Ecto.Changeset.cast(attrs, [
|
|
||||||
:preferred_username,
|
|
||||||
:domain,
|
|
||||||
:name,
|
|
||||||
:summary,
|
|
||||||
:keys,
|
|
||||||
:suspended,
|
|
||||||
:url,
|
|
||||||
:type,
|
|
||||||
:user_id
|
|
||||||
])
|
|
||||||
|> build_urls()
|
|
||||||
|> cast_embed(:avatar)
|
|
||||||
|> cast_embed(:banner)
|
|
||||||
# Needed because following constraint can't work for domain null values (local)
|
|
||||||
|> unique_username_validator()
|
|
||||||
|> unique_constraint(:preferred_username, name: :actors_preferred_username_domain_type_index)
|
|
||||||
|> unique_constraint(:url, name: :actors_url_index)
|
|
||||||
|> validate_required([:preferred_username, :keys, :suspended, :url, :type])
|
|
||||||
end
|
|
||||||
|
|
||||||
# TODO : Use me !
|
@remote_actor_creation_required_attrs [
|
||||||
# @email_regex ~r/^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/
|
|
||||||
@doc """
|
|
||||||
Changeset for remote actor creation
|
|
||||||
"""
|
|
||||||
@spec remote_actor_creation(map()) :: Ecto.Changeset.t()
|
|
||||||
def remote_actor_creation(params) do
|
|
||||||
changes =
|
|
||||||
%Actor{}
|
|
||||||
|> Ecto.Changeset.cast(params, [
|
|
||||||
:url,
|
|
||||||
:outbox_url,
|
|
||||||
:inbox_url,
|
|
||||||
:shared_inbox_url,
|
|
||||||
:following_url,
|
|
||||||
:followers_url,
|
|
||||||
:type,
|
|
||||||
:name,
|
|
||||||
:domain,
|
|
||||||
:summary,
|
|
||||||
:preferred_username,
|
|
||||||
:keys,
|
|
||||||
:manually_approves_followers
|
|
||||||
])
|
|
||||||
|> validate_required([
|
|
||||||
:url,
|
:url,
|
||||||
:inbox_url,
|
:inbox_url,
|
||||||
:type,
|
:type,
|
||||||
:domain,
|
:domain,
|
||||||
:preferred_username,
|
:preferred_username,
|
||||||
:keys
|
:keys
|
||||||
])
|
]
|
||||||
|> cast_embed(:avatar)
|
@remote_actor_creation_optional_attrs [
|
||||||
|> cast_embed(:banner)
|
:outbox_url,
|
||||||
# Needed because following constraint can't work for domain null values (local)
|
:shared_inbox_url,
|
||||||
|> unique_username_validator()
|
:following_url,
|
||||||
|> unique_constraint(:preferred_username, name: :actors_preferred_username_domain_type_index)
|
:followers_url,
|
||||||
|> unique_constraint(:url, name: :actors_url_index)
|
:name,
|
||||||
|> validate_length(:summary, max: 5000)
|
:summary,
|
||||||
|> validate_length(:preferred_username, max: 100)
|
:manually_approves_followers
|
||||||
|
]
|
||||||
|
@remote_actor_creation_attrs @remote_actor_creation_required_attrs ++
|
||||||
|
@remote_actor_creation_optional_attrs
|
||||||
|
|
||||||
Logger.debug("Remote actor creation")
|
@relay_creation_attrs [
|
||||||
Logger.debug(inspect(changes))
|
|
||||||
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" => Config.get([:instance, :name], "Mobilizon"),
|
|
||||||
"summary" => Config.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,
|
:type,
|
||||||
:name,
|
:name,
|
||||||
:summary,
|
:summary,
|
||||||
@ -235,7 +105,169 @@ defmodule Mobilizon.Actors.Actor do
|
|||||||
:followers_url,
|
:followers_url,
|
||||||
:following_url,
|
:following_url,
|
||||||
:shared_inbox_url
|
:shared_inbox_url
|
||||||
])
|
]
|
||||||
|
|
||||||
|
@group_creation_required_attrs [:url, :outbox_url, :inbox_url, :type, :preferred_username]
|
||||||
|
@group_creation_optional_attrs [:shared_inbox_url, :name, :domain, :summary]
|
||||||
|
@group_creation_attrs @group_creation_required_attrs ++ @group_creation_optional_attrs
|
||||||
|
|
||||||
|
schema "actors" do
|
||||||
|
field(:url, :string)
|
||||||
|
field(:outbox_url, :string)
|
||||||
|
field(:inbox_url, :string)
|
||||||
|
field(:following_url, :string)
|
||||||
|
field(:followers_url, :string)
|
||||||
|
field(:shared_inbox_url, :string)
|
||||||
|
field(:type, ActorType, default: :Person)
|
||||||
|
field(:name, :string)
|
||||||
|
field(:domain, :string, default: nil)
|
||||||
|
field(:summary, :string)
|
||||||
|
field(:preferred_username, :string)
|
||||||
|
field(:keys, :string)
|
||||||
|
field(:manually_approves_followers, :boolean, default: false)
|
||||||
|
field(:openness, ActorOpenness, default: :moderated)
|
||||||
|
field(:visibility, ActorVisibility, default: :private)
|
||||||
|
field(:suspended, :boolean, default: false)
|
||||||
|
|
||||||
|
embeds_one(:avatar, File, on_replace: :update)
|
||||||
|
embeds_one(:banner, File, on_replace: :update)
|
||||||
|
belongs_to(:user, User)
|
||||||
|
has_many(:followers, Follower, foreign_key: :target_actor_id)
|
||||||
|
has_many(:followings, Follower, foreign_key: :actor_id)
|
||||||
|
has_many(:organized_events, Event, foreign_key: :organizer_actor_id)
|
||||||
|
has_many(:feed_tokens, FeedToken, foreign_key: :actor_id)
|
||||||
|
has_many(:created_reports, Report, foreign_key: :reporter_id)
|
||||||
|
has_many(:subject_reports, Report, foreign_key: :reported_id)
|
||||||
|
has_many(:report_notes, Note, foreign_key: :moderator_id)
|
||||||
|
many_to_many(:memberships, Actor, join_through: Member)
|
||||||
|
|
||||||
|
timestamps()
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Checks whether actor visibility is public.
|
||||||
|
"""
|
||||||
|
@spec is_public_visibility(Actor.t()) :: boolean
|
||||||
|
def is_public_visibility(%Actor{visibility: visibility}) do
|
||||||
|
visibility in [:public, :unlisted]
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Returns the display name if available, or the preferred username
|
||||||
|
(with the eventual @domain suffix if it's a distant actor).
|
||||||
|
"""
|
||||||
|
@spec display_name(Actor.t()) :: String.t()
|
||||||
|
def display_name(%Actor{name: name} = actor) when name in [nil, ""] do
|
||||||
|
preferred_username_and_domain(actor)
|
||||||
|
end
|
||||||
|
|
||||||
|
def display_name(%Actor{name: name}), do: name
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Returns display name and username.
|
||||||
|
"""
|
||||||
|
@spec display_name_and_username(Actor.t()) :: String.t()
|
||||||
|
def display_name_and_username(%Actor{name: name} = actor) when name in [nil, ""] do
|
||||||
|
preferred_username_and_domain(actor)
|
||||||
|
end
|
||||||
|
|
||||||
|
def display_name_and_username(%Actor{name: name} = actor) do
|
||||||
|
"#{name} (#{preferred_username_and_domain(actor)})"
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Returns the preferred username with the eventual @domain suffix if it's
|
||||||
|
a distant actor.
|
||||||
|
"""
|
||||||
|
@spec preferred_username_and_domain(Actor.t()) :: String.t()
|
||||||
|
def preferred_username_and_domain(%Actor{preferred_username: preferred_username, domain: nil}) do
|
||||||
|
preferred_username
|
||||||
|
end
|
||||||
|
|
||||||
|
def preferred_username_and_domain(%Actor{preferred_username: preferred_username, domain: domain}) do
|
||||||
|
"#{preferred_username}@#{domain}"
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc false
|
||||||
|
@spec changeset(t | Ecto.Changeset.t(), map) :: Ecto.Changeset.t()
|
||||||
|
def changeset(%Actor{} = actor, attrs) do
|
||||||
|
actor
|
||||||
|
|> cast(attrs, @attrs)
|
||||||
|
|> build_urls()
|
||||||
|
|> cast_embed(:avatar)
|
||||||
|
|> cast_embed(:banner)
|
||||||
|
|> unique_username_validator()
|
||||||
|
|> validate_required(@required_attrs)
|
||||||
|
|> unique_constraint(:preferred_username,
|
||||||
|
name: :actors_preferred_username_domain_type_index
|
||||||
|
)
|
||||||
|
|> unique_constraint(:url, name: :actors_url_index)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc false
|
||||||
|
@spec update_changeset(t | Ecto.Changeset.t(), map) :: Ecto.Changeset.t()
|
||||||
|
def update_changeset(%Actor{} = actor, attrs) do
|
||||||
|
actor
|
||||||
|
|> cast(attrs, @update_attrs)
|
||||||
|
|> cast_embed(:avatar)
|
||||||
|
|> cast_embed(:banner)
|
||||||
|
|> validate_required(@update_required_attrs)
|
||||||
|
|> unique_constraint(:preferred_username,
|
||||||
|
name: :actors_preferred_username_domain_type_index
|
||||||
|
)
|
||||||
|
|> unique_constraint(:url, name: :actors_url_index)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Changeset for person registration.
|
||||||
|
"""
|
||||||
|
@spec registration_changeset(t | Ecto.Changeset.t(), map) :: Ecto.Changeset.t()
|
||||||
|
def registration_changeset(%Actor{} = actor, attrs) do
|
||||||
|
actor
|
||||||
|
|> cast(attrs, @registration_attrs)
|
||||||
|
|> build_urls()
|
||||||
|
|> cast_embed(:avatar)
|
||||||
|
|> cast_embed(:banner)
|
||||||
|
|> unique_username_validator()
|
||||||
|
|> unique_constraint(:preferred_username,
|
||||||
|
name: :actors_preferred_username_domain_type_index
|
||||||
|
)
|
||||||
|
|> unique_constraint(:url, name: :actors_url_index)
|
||||||
|
|> validate_required(@registration_required_attrs)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Changeset for remote actor creation.
|
||||||
|
"""
|
||||||
|
@spec remote_actor_creation_changeset(map) :: Ecto.Changeset.t()
|
||||||
|
def remote_actor_creation_changeset(attrs) do
|
||||||
|
changeset =
|
||||||
|
%Actor{}
|
||||||
|
|> cast(attrs, @remote_actor_creation_attrs)
|
||||||
|
|> validate_required(@remote_actor_creation_required_attrs)
|
||||||
|
|> cast_embed(:avatar)
|
||||||
|
|> cast_embed(:banner)
|
||||||
|
|> unique_username_validator()
|
||||||
|
|> unique_constraint(:preferred_username,
|
||||||
|
name: :actors_preferred_username_domain_type_index
|
||||||
|
)
|
||||||
|
|> unique_constraint(:url, name: :actors_url_index)
|
||||||
|
|> validate_length(:summary, max: 5000)
|
||||||
|
|> validate_length(:preferred_username, max: 100)
|
||||||
|
|
||||||
|
Logger.debug("Remote actor creation: #{inspect(changeset)}")
|
||||||
|
|
||||||
|
changeset
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Changeset for relay creation.
|
||||||
|
"""
|
||||||
|
@spec relay_creation_changeset(map) :: Ecto.Changeset.t()
|
||||||
|
def relay_creation_changeset(attrs) do
|
||||||
|
relay_creation_attrs = build_relay_creation_attrs(attrs)
|
||||||
|
|
||||||
|
cast(%Actor{}, relay_creation_attrs, @relay_creation_attrs)
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
@ -244,68 +276,48 @@ defmodule Mobilizon.Actors.Actor do
|
|||||||
@spec group_creation(struct(), map()) :: Ecto.Changeset.t()
|
@spec group_creation(struct(), map()) :: Ecto.Changeset.t()
|
||||||
def group_creation(%Actor{} = actor, params) do
|
def group_creation(%Actor{} = actor, params) do
|
||||||
actor
|
actor
|
||||||
|> Ecto.Changeset.cast(params, [
|
|> cast(params, @group_creation_attrs)
|
||||||
:url,
|
|
||||||
:outbox_url,
|
|
||||||
:inbox_url,
|
|
||||||
:shared_inbox_url,
|
|
||||||
:type,
|
|
||||||
:name,
|
|
||||||
:domain,
|
|
||||||
:summary,
|
|
||||||
:preferred_username
|
|
||||||
])
|
|
||||||
|> cast_embed(:avatar)
|
|> cast_embed(:avatar)
|
||||||
|> cast_embed(:banner)
|
|> cast_embed(:banner)
|
||||||
|> build_urls(:Group)
|
|> build_urls(:Group)
|
||||||
|> put_change(:domain, nil)
|
|> put_change(:domain, nil)
|
||||||
|> put_change(:keys, Actors.create_keys())
|
|> put_change(:keys, Crypto.generate_rsa_2048_private_key())
|
||||||
|> put_change(:type, :Group)
|
|> put_change(:type, :Group)
|
||||||
|> unique_username_validator()
|
|> unique_username_validator()
|
||||||
|> validate_required([:url, :outbox_url, :inbox_url, :type, :preferred_username])
|
|> validate_required(@group_creation_required_attrs)
|
||||||
|> unique_constraint(:preferred_username, name: :actors_preferred_username_domain_type_index)
|
|> unique_constraint(:preferred_username,
|
||||||
|
name: :actors_preferred_username_domain_type_index
|
||||||
|
)
|
||||||
|> unique_constraint(:url, name: :actors_url_index)
|
|> unique_constraint(:url, name: :actors_url_index)
|
||||||
|> validate_length(:summary, max: 5000)
|
|> validate_length(:summary, max: 5000)
|
||||||
|> validate_length(:preferred_username, max: 100)
|
|> validate_length(:preferred_username, max: 100)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Needed because following constraint can't work for domain null values (local)
|
||||||
|
@spec unique_username_validator(Ecto.Changeset.t()) :: Ecto.Changeset.t()
|
||||||
defp unique_username_validator(
|
defp unique_username_validator(
|
||||||
%Ecto.Changeset{changes: %{preferred_username: username} = changes} = changeset
|
%Ecto.Changeset{changes: %{preferred_username: username} = changes} = changeset
|
||||||
) do
|
) do
|
||||||
with nil <- Map.get(changes, :domain, nil),
|
with nil <- Map.get(changes, :domain, nil),
|
||||||
%Actor{preferred_username: _username} <- Actors.get_local_actor_by_name(username) do
|
%Actor{preferred_username: _} <- Actors.get_local_actor_by_name(username) do
|
||||||
changeset |> add_error(:preferred_username, "Username is already taken")
|
add_error(changeset, :preferred_username, "Username is already taken")
|
||||||
else
|
else
|
||||||
_ -> changeset
|
_ -> changeset
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# When we don't even have any preferred_username, don't even try validating preferred_username
|
# When we don't even have any preferred_username, don't even try validating preferred_username
|
||||||
defp unique_username_validator(changeset) do
|
defp unique_username_validator(changeset), do: changeset
|
||||||
changeset
|
|
||||||
end
|
|
||||||
|
|
||||||
@spec build_urls(Ecto.Changeset.t(), atom()) :: Ecto.Changeset.t()
|
@spec build_urls(Ecto.Changeset.t(), ActorType.t()) :: Ecto.Changeset.t()
|
||||||
defp build_urls(changeset, type \\ :Person)
|
defp build_urls(changeset, type \\ :Person)
|
||||||
|
|
||||||
defp build_urls(%Ecto.Changeset{changes: %{preferred_username: username}} = changeset, _type) do
|
defp build_urls(%Ecto.Changeset{changes: %{preferred_username: username}} = changeset, _type) do
|
||||||
changeset
|
changeset
|
||||||
|> put_change(
|
|> put_change(:outbox_url, build_url(username, :outbox))
|
||||||
:outbox_url,
|
|> put_change(:followers_url, build_url(username, :followers))
|
||||||
build_url(username, :outbox)
|
|> put_change(:following_url, build_url(username, :following))
|
||||||
)
|
|> put_change(:inbox_url, build_url(username, :inbox))
|
||||||
|> put_change(
|
|
||||||
:followers_url,
|
|
||||||
build_url(username, :followers)
|
|
||||||
)
|
|
||||||
|> put_change(
|
|
||||||
:following_url,
|
|
||||||
build_url(username, :following)
|
|
||||||
)
|
|
||||||
|> put_change(
|
|
||||||
:inbox_url,
|
|
||||||
build_url(username, :inbox)
|
|
||||||
)
|
|
||||||
|> put_change(:shared_inbox_url, "#{MobilizonWeb.Endpoint.url()}/inbox")
|
|> put_change(:shared_inbox_url, "#{MobilizonWeb.Endpoint.url()}/inbox")
|
||||||
|> put_change(:url, build_url(username, :page))
|
|> put_change(:url, build_url(username, :page))
|
||||||
end
|
end
|
||||||
@ -313,19 +325,19 @@ defmodule Mobilizon.Actors.Actor do
|
|||||||
defp build_urls(%Ecto.Changeset{} = changeset, _type), do: changeset
|
defp build_urls(%Ecto.Changeset{} = changeset, _type), do: changeset
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Build an AP URL for an actor
|
Builds an AP URL for an actor.
|
||||||
"""
|
"""
|
||||||
@spec build_url(String.t(), atom()) :: String.t()
|
@spec build_url(String.t(), atom, keyword) :: String.t()
|
||||||
def build_url(preferred_username, endpoint, args \\ [])
|
def build_url(preferred_username, endpoint, args \\ [])
|
||||||
|
|
||||||
|
def build_url(username, :inbox, _args), do: "#{build_url(username, :page)}/inbox"
|
||||||
|
|
||||||
def build_url(preferred_username, :page, args) do
|
def build_url(preferred_username, :page, args) do
|
||||||
Endpoint
|
Endpoint
|
||||||
|> Routes.page_url(:actor, preferred_username, args)
|
|> Routes.page_url(:actor, preferred_username, args)
|
||||||
|> URI.decode()
|
|> URI.decode()
|
||||||
end
|
end
|
||||||
|
|
||||||
def build_url(username, :inbox, _args), do: "#{build_url(username, :page)}/inbox"
|
|
||||||
|
|
||||||
def build_url(preferred_username, endpoint, args)
|
def build_url(preferred_username, endpoint, args)
|
||||||
when endpoint in [:outbox, :following, :followers] do
|
when endpoint in [:outbox, :following, :followers] do
|
||||||
Endpoint
|
Endpoint
|
||||||
@ -333,267 +345,35 @@ defmodule Mobilizon.Actors.Actor do
|
|||||||
|> URI.decode()
|
|> URI.decode()
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
|
||||||
Get a public key for a given ActivityPub actor ID (url)
|
|
||||||
"""
|
|
||||||
@spec get_public_key_for_url(String.t()) :: {:ok, String.t()} | {:error, atom()}
|
|
||||||
def get_public_key_for_url(url) do
|
|
||||||
with {:ok, %Actor{keys: keys}} <- Actors.get_or_fetch_by_url(url),
|
|
||||||
{:ok, public_key} <- prepare_public_key(keys) do
|
|
||||||
{:ok, public_key}
|
|
||||||
else
|
|
||||||
{:error, :pem_decode_error} ->
|
|
||||||
Logger.error("Error while decoding PEM")
|
|
||||||
{:error, :pem_decode_error}
|
|
||||||
|
|
||||||
_ ->
|
|
||||||
Logger.error("Unable to fetch actor, so no keys for you")
|
|
||||||
{:error, :actor_fetch_error}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
Convert internal PEM encoded keys to public key format
|
|
||||||
"""
|
|
||||||
@spec prepare_public_key(String.t()) :: {:ok, tuple()} | {:error, :pem_decode_error}
|
|
||||||
def prepare_public_key(public_key_code) do
|
|
||||||
case :public_key.pem_decode(public_key_code) do
|
|
||||||
[public_key_entry] ->
|
|
||||||
{:ok, :public_key.pem_entry_decode(public_key_entry)}
|
|
||||||
|
|
||||||
_err ->
|
|
||||||
{:error, :pem_decode_error}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
Get followers from an actor
|
|
||||||
|
|
||||||
If actor A and C both follow actor B, actor B's followers are A and C
|
|
||||||
"""
|
|
||||||
@spec get_followers(struct(), number(), number()) :: map()
|
|
||||||
def get_followers(%Actor{id: actor_id} = _actor, page \\ nil, limit \\ nil) do
|
|
||||||
query =
|
|
||||||
from(
|
|
||||||
a in Actor,
|
|
||||||
join: f in Follower,
|
|
||||||
on: a.id == f.actor_id,
|
|
||||||
where: f.target_actor_id == ^actor_id
|
|
||||||
)
|
|
||||||
|
|
||||||
total = Task.async(fn -> Repo.aggregate(query, :count, :id) end)
|
|
||||||
elements = Task.async(fn -> Repo.all(Page.paginate(query, page, limit)) end)
|
|
||||||
|
|
||||||
%{total: Task.await(total), elements: Task.await(elements)}
|
|
||||||
end
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
If actor A follows actor B and C, actor A's followings are B and B
|
|
||||||
"""
|
|
||||||
@spec get_followings(struct(), number(), number()) :: list()
|
|
||||||
def get_followings(%Actor{id: actor_id} = _actor, page \\ nil, limit \\ nil) do
|
|
||||||
query =
|
|
||||||
from(
|
|
||||||
a in Actor,
|
|
||||||
join: f in Follower,
|
|
||||||
on: a.id == f.target_actor_id,
|
|
||||||
where: f.actor_id == ^actor_id
|
|
||||||
)
|
|
||||||
|
|
||||||
total = Task.async(fn -> Repo.aggregate(query, :count, :id) end)
|
|
||||||
elements = Task.async(fn -> Repo.all(Page.paginate(query, page, limit)) end)
|
|
||||||
|
|
||||||
%{total: Task.await(total), elements: Task.await(elements)}
|
|
||||||
end
|
|
||||||
|
|
||||||
@spec get_full_followings(struct()) :: list()
|
|
||||||
def get_full_followings(%Actor{id: actor_id} = _actor) do
|
|
||||||
Repo.all(
|
|
||||||
from(
|
|
||||||
a in Actor,
|
|
||||||
join: f in Follower,
|
|
||||||
on: a.id == f.target_actor_id,
|
|
||||||
where: f.actor_id == ^actor_id
|
|
||||||
)
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
Returns the groups an actor is member of
|
|
||||||
"""
|
|
||||||
@spec get_groups_member_of(struct()) :: list()
|
|
||||||
def get_groups_member_of(%Actor{id: actor_id}) do
|
|
||||||
Repo.all(
|
|
||||||
from(
|
|
||||||
a in Actor,
|
|
||||||
join: m in Member,
|
|
||||||
on: a.id == m.parent_id,
|
|
||||||
where: m.actor_id == ^actor_id
|
|
||||||
)
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
Returns the members for a group actor
|
|
||||||
"""
|
|
||||||
@spec get_members_for_group(struct()) :: list()
|
|
||||||
def get_members_for_group(%Actor{id: actor_id}) do
|
|
||||||
Repo.all(
|
|
||||||
from(
|
|
||||||
a in Actor,
|
|
||||||
join: m in Member,
|
|
||||||
on: a.id == m.actor_id,
|
|
||||||
where: m.parent_id == ^actor_id
|
|
||||||
)
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
Make an actor follow another
|
|
||||||
"""
|
|
||||||
@spec follow(struct(), struct(), boolean()) :: Follower.t() | {:error, String.t()}
|
|
||||||
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, url)
|
|
||||||
else
|
|
||||||
{:already_following, %Follower{}} ->
|
|
||||||
{:error, :already_following,
|
|
||||||
"Could not follow actor: you are already following #{followed.preferred_username}"}
|
|
||||||
|
|
||||||
{:suspended, _} ->
|
|
||||||
{:error, :suspended,
|
|
||||||
"Could not follow actor: #{followed.preferred_username} has been suspended"}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
Unfollow an actor (remove a `Mobilizon.Actors.Follower`)
|
|
||||||
"""
|
|
||||||
@spec unfollow(struct(), struct()) :: {:ok, Follower.t()} | {:error, Ecto.Changeset.t()}
|
|
||||||
def unfollow(%Actor{} = followed, %Actor{} = follower) do
|
|
||||||
case {:already_following, following?(follower, followed)} do
|
|
||||||
{:already_following, %Follower{} = follow} ->
|
|
||||||
Actors.delete_follower(follow)
|
|
||||||
|
|
||||||
{:already_following, false} ->
|
|
||||||
{:error, "Could not unfollow actor: you are not following #{followed.preferred_username}"}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@spec do_follow(struct(), struct(), boolean(), String.t()) ::
|
|
||||||
{:ok, Follower.t()} | {:error, Ecto.Changeset.t()}
|
|
||||||
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,
|
|
||||||
"url" => url
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
Returns whether an actor is following another
|
|
||||||
"""
|
|
||||||
@spec following?(struct(), struct()) :: Follower.t() | false
|
|
||||||
def following?(
|
|
||||||
%Actor{} = follower_actor,
|
|
||||||
%Actor{} = followed_actor
|
|
||||||
) do
|
|
||||||
case Actors.get_follower(followed_actor, follower_actor) do
|
|
||||||
nil -> false
|
|
||||||
%Follower{} = follow -> follow
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@spec public_visibility?(struct()) :: boolean()
|
|
||||||
def public_visibility?(%Actor{visibility: visibility}), do: visibility in [:public, :unlisted]
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
Return the preferred_username with the eventual @domain suffix if it's a distant actor
|
|
||||||
"""
|
|
||||||
@spec actor_acct_from_actor(struct()) :: String.t()
|
|
||||||
def actor_acct_from_actor(%Actor{preferred_username: preferred_username, domain: domain}) do
|
|
||||||
if is_nil(domain) do
|
|
||||||
preferred_username
|
|
||||||
else
|
|
||||||
"#{preferred_username}@#{domain}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
Returns the display name if available, or the preferred_username (with the eventual @domain suffix if it's a distant actor).
|
|
||||||
"""
|
|
||||||
@spec display_name(struct()) :: String.t()
|
|
||||||
def display_name(%Actor{name: name} = actor) do
|
|
||||||
case name do
|
|
||||||
nil -> actor_acct_from_actor(actor)
|
|
||||||
"" -> actor_acct_from_actor(actor)
|
|
||||||
name -> name
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
Return display name and username
|
|
||||||
|
|
||||||
## Examples
|
|
||||||
iex> display_name_and_username(%Actor{name: "Thomas C", preferred_username: "tcit", domain: nil})
|
|
||||||
"Thomas (tcit)"
|
|
||||||
|
|
||||||
iex> display_name_and_username(%Actor{name: "Thomas C", preferred_username: "tcit", domain: "framapiaf.org"})
|
|
||||||
"Thomas (tcit@framapiaf.org)"
|
|
||||||
|
|
||||||
iex> display_name_and_username(%Actor{name: nil, preferred_username: "tcit", domain: "framapiaf.org"})
|
|
||||||
"tcit@framapiaf.org"
|
|
||||||
|
|
||||||
"""
|
|
||||||
@spec display_name_and_username(struct()) :: String.t()
|
|
||||||
def display_name_and_username(%Actor{name: nil} = actor), do: actor_acct_from_actor(actor)
|
|
||||||
def display_name_and_username(%Actor{name: ""} = actor), do: actor_acct_from_actor(actor)
|
|
||||||
|
|
||||||
def display_name_and_username(%Actor{name: name} = actor),
|
|
||||||
do: name <> " (" <> actor_acct_from_actor(actor) <> ")"
|
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Clear multiple caches for an actor
|
Clear multiple caches for an actor
|
||||||
"""
|
"""
|
||||||
|
# TODO: move to MobilizonWeb
|
||||||
@spec clear_cache(struct()) :: {:ok, true}
|
@spec clear_cache(struct()) :: {:ok, true}
|
||||||
def clear_cache(%Actor{preferred_username: preferred_username, domain: nil}) do
|
def clear_cache(%Actor{preferred_username: preferred_username, domain: nil}) do
|
||||||
Cachex.del(:activity_pub, "actor_" <> preferred_username)
|
Cachex.del(:activity_pub, "actor_" <> preferred_username)
|
||||||
Cachex.del(:feed, "actor_" <> preferred_username)
|
Cachex.del(:feed, "actor_" <> preferred_username)
|
||||||
Cachex.del(:ics, "actor_" <> preferred_username)
|
Cachex.del(:ics, "actor_" <> preferred_username)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec build_relay_creation_attrs(map) :: map
|
||||||
|
defp build_relay_creation_attrs(%{url: url, preferred_username: preferred_username}) do
|
||||||
|
%{
|
||||||
|
"name" => Config.get([:instance, :name], "Mobilizon"),
|
||||||
|
"summary" =>
|
||||||
|
Config.get(
|
||||||
|
[:instance, :description],
|
||||||
|
"An internal service actor for this Mobilizon instance"
|
||||||
|
),
|
||||||
|
"url" => url,
|
||||||
|
"keys" => Crypto.generate_rsa_2048_private_key(),
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,15 +1,30 @@
|
|||||||
defmodule Mobilizon.Actors.Bot do
|
defmodule Mobilizon.Actors.Bot do
|
||||||
@moduledoc """
|
@moduledoc """
|
||||||
Represents a local bot
|
Represents a local bot.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
use Ecto.Schema
|
use Ecto.Schema
|
||||||
|
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
|
|
||||||
alias Mobilizon.Actors.Actor
|
alias Mobilizon.Actors.Actor
|
||||||
alias Mobilizon.Users.User
|
alias Mobilizon.Users.User
|
||||||
|
|
||||||
|
@type t :: %__MODULE__{
|
||||||
|
source: String.t(),
|
||||||
|
type: String.t(),
|
||||||
|
actor: Actor.t(),
|
||||||
|
user: User.t()
|
||||||
|
}
|
||||||
|
|
||||||
|
@required_attrs [:source]
|
||||||
|
@optional_attrs [:type, :actor_id, :user_id]
|
||||||
|
@attrs @required_attrs ++ @optional_attrs
|
||||||
|
|
||||||
schema "bots" do
|
schema "bots" do
|
||||||
field(:source, :string)
|
field(:source, :string)
|
||||||
field(:type, :string, default: :ics)
|
field(:type, :string, default: :ics)
|
||||||
|
|
||||||
belongs_to(:actor, Actor)
|
belongs_to(:actor, Actor)
|
||||||
belongs_to(:user, User)
|
belongs_to(:user, User)
|
||||||
|
|
||||||
@ -17,9 +32,10 @@ defmodule Mobilizon.Actors.Bot do
|
|||||||
end
|
end
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
|
@spec changeset(t | Ecto.Changeset.t(), map) :: Ecto.Changeset.t()
|
||||||
def changeset(bot, attrs) do
|
def changeset(bot, attrs) do
|
||||||
bot
|
bot
|
||||||
|> cast(attrs, [:source, :type, :actor_id, :user_id])
|
|> cast(attrs, @attrs)
|
||||||
|> validate_required([:source])
|
|> validate_required(@required_attrs)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,52 +1,63 @@
|
|||||||
defmodule Mobilizon.Actors.Follower do
|
defmodule Mobilizon.Actors.Follower do
|
||||||
@moduledoc """
|
@moduledoc """
|
||||||
Represents the following of an actor to another actor
|
Represents the following of an actor to another actor.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
use Ecto.Schema
|
use Ecto.Schema
|
||||||
|
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
alias Mobilizon.Actors.Follower
|
|
||||||
alias Mobilizon.Actors.Actor
|
alias Mobilizon.Actors.Actor
|
||||||
|
|
||||||
@primary_key {:id, :binary_id, autogenerate: true}
|
@type t :: %__MODULE__{
|
||||||
|
approved: boolean,
|
||||||
|
url: String.t(),
|
||||||
|
target_actor: Actor.t(),
|
||||||
|
actor: Actor.t()
|
||||||
|
}
|
||||||
|
|
||||||
|
@required_attrs [:url, :approved, :target_actor_id, :actor_id]
|
||||||
|
@attrs @required_attrs
|
||||||
|
|
||||||
|
@primary_key {:id, :binary_id, autogenerate: true}
|
||||||
schema "followers" do
|
schema "followers" do
|
||||||
field(:approved, :boolean, default: false)
|
field(:approved, :boolean, default: false)
|
||||||
field(:url, :string)
|
field(:url, :string)
|
||||||
|
|
||||||
belongs_to(:target_actor, Actor)
|
belongs_to(:target_actor, Actor)
|
||||||
belongs_to(:actor, Actor)
|
belongs_to(:actor, Actor)
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
def changeset(%Follower{} = member, attrs) do
|
@spec changeset(t | Ecto.Changeset.t(), map) :: Ecto.Changeset.t()
|
||||||
member
|
def changeset(follower, attrs) do
|
||||||
|> cast(attrs, [:url, :approved, :target_actor_id, :actor_id])
|
follower
|
||||||
|> generate_url()
|
|> cast(attrs, @attrs)
|
||||||
|> validate_required([:url, :approved, :target_actor_id, :actor_id])
|
|> ensure_url()
|
||||||
|> unique_constraint(:target_actor_id, name: :followers_actor_target_actor_unique_index)
|
|> validate_required(@required_attrs)
|
||||||
|
|> unique_constraint(:target_actor_id,
|
||||||
|
name: :followers_actor_target_actor_unique_index
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
# If there's a blank URL that's because we're doing the first insert
|
# 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
|
@spec ensure_url(Ecto.Changeset.t()) :: Ecto.Changeset.t()
|
||||||
|
defp ensure_url(%Ecto.Changeset{data: %__MODULE__{url: nil}} = changeset) do
|
||||||
case fetch_change(changeset, :url) do
|
case fetch_change(changeset, :url) do
|
||||||
{:ok, _url} -> changeset
|
{:ok, _url} -> changeset
|
||||||
:error -> do_generate_url(changeset)
|
:error -> generate_url(changeset)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Most time just go with the given URL
|
# Most time just go with the given URL
|
||||||
defp generate_url(%Ecto.Changeset{} = changeset), do: changeset
|
defp ensure_url(%Ecto.Changeset{} = changeset), do: changeset
|
||||||
|
|
||||||
defp do_generate_url(%Ecto.Changeset{} = changeset) do
|
@spec generate_url(Ecto.Changeset.t()) :: Ecto.Changeset.t()
|
||||||
|
defp generate_url(%Ecto.Changeset{} = changeset) do
|
||||||
uuid = Ecto.UUID.generate()
|
uuid = Ecto.UUID.generate()
|
||||||
|
|
||||||
changeset
|
changeset
|
||||||
|> put_change(
|
|> put_change(:id, uuid)
|
||||||
:url,
|
|> put_change(:url, "#{MobilizonWeb.Endpoint.url()}/follow/#{uuid}")
|
||||||
"#{MobilizonWeb.Endpoint.url()}/follow/#{uuid}"
|
|
||||||
)
|
|
||||||
|> put_change(
|
|
||||||
:id,
|
|
||||||
uuid
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,100 +1,59 @@
|
|||||||
import EctoEnum
|
|
||||||
|
|
||||||
defenum(Mobilizon.Actors.MemberRoleEnum, :member_role_type, [
|
|
||||||
:not_approved,
|
|
||||||
:member,
|
|
||||||
:moderator,
|
|
||||||
:administrator,
|
|
||||||
:creator
|
|
||||||
])
|
|
||||||
|
|
||||||
defmodule Mobilizon.Actors.Member do
|
defmodule Mobilizon.Actors.Member do
|
||||||
@moduledoc """
|
@moduledoc """
|
||||||
Represents the membership of an actor to a group
|
Represents the membership of an actor to a group.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
use Ecto.Schema
|
use Ecto.Schema
|
||||||
|
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
import Ecto.Query
|
|
||||||
|
|
||||||
alias Mobilizon.Actors.{Actor, Member}
|
alias Mobilizon.Actors.{Actor, Member, MemberRole}
|
||||||
alias Mobilizon.Storage.{Page, Repo}
|
|
||||||
|
@type t :: %__MODULE__{
|
||||||
|
role: MemberRole.t(),
|
||||||
|
parent: Actor.t(),
|
||||||
|
actor: Actor.t()
|
||||||
|
}
|
||||||
|
|
||||||
|
@required_attrs [:parent_id, :actor_id]
|
||||||
|
@optional_attrs [:role]
|
||||||
|
@attrs @required_attrs ++ @optional_attrs
|
||||||
|
|
||||||
schema "members" do
|
schema "members" do
|
||||||
field(:role, Mobilizon.Actors.MemberRoleEnum, default: :member)
|
field(:role, MemberRole, default: :member)
|
||||||
|
|
||||||
belongs_to(:parent, Actor)
|
belongs_to(:parent, Actor)
|
||||||
belongs_to(:actor, Actor)
|
belongs_to(:actor, Actor)
|
||||||
|
|
||||||
timestamps()
|
timestamps()
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc false
|
|
||||||
def changeset(%Member{} = member, attrs) do
|
|
||||||
member
|
|
||||||
|> cast(attrs, [:role, :parent_id, :actor_id])
|
|
||||||
|> validate_required([:parent_id, :actor_id])
|
|
||||||
|> unique_constraint(:parent_id, name: :members_actor_parent_unique_index)
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Gets a single member of an actor (for example a group)
|
Gets the default member role depending on the actor openness.
|
||||||
"""
|
"""
|
||||||
def get_member(actor_id, parent_id) do
|
@spec get_default_member_role(Actor.t()) :: atom
|
||||||
case Repo.get_by(Member, actor_id: actor_id, parent_id: parent_id) do
|
def get_default_member_role(%Actor{openness: :open}), do: :member
|
||||||
nil -> {:error, :member_not_found}
|
def get_default_member_role(%Actor{}), do: :not_approved
|
||||||
member -> {:ok, member}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Gets a single member of an actor (for example a group)
|
Checks whether the actor can be joined to the group.
|
||||||
"""
|
"""
|
||||||
def can_be_joined(%Actor{type: :Group, openness: :invite_only}), do: false
|
def can_be_joined(%Actor{type: :Group, openness: :invite_only}), do: false
|
||||||
def can_be_joined(%Actor{type: :Group}), do: true
|
def can_be_joined(%Actor{type: :Group}), do: true
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Returns the list of administrator members for a group.
|
Checks whether the member is an administrator (admin or creator) of the group.
|
||||||
"""
|
|
||||||
def list_administrator_members_for_group(id, page \\ nil, limit \\ nil) do
|
|
||||||
Repo.all(
|
|
||||||
from(
|
|
||||||
m in Member,
|
|
||||||
where: m.parent_id == ^id and (m.role == ^:creator or m.role == ^:administrator),
|
|
||||||
preload: [:actor]
|
|
||||||
)
|
|
||||||
|> Page.paginate(page, limit)
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
Get all group ids where the actor_id is the last administrator
|
|
||||||
"""
|
|
||||||
def list_group_id_where_last_administrator(actor_id) do
|
|
||||||
in_query =
|
|
||||||
from(
|
|
||||||
m in Member,
|
|
||||||
where: m.actor_id == ^actor_id and (m.role == ^:creator or m.role == ^:administrator),
|
|
||||||
select: m.parent_id
|
|
||||||
)
|
|
||||||
|
|
||||||
Repo.all(
|
|
||||||
from(
|
|
||||||
m in Member,
|
|
||||||
where: m.role == ^:creator or m.role == ^:administrator,
|
|
||||||
join: m2 in subquery(in_query),
|
|
||||||
on: m.parent_id == m2.parent_id,
|
|
||||||
group_by: m.parent_id,
|
|
||||||
select: m.parent_id,
|
|
||||||
having: count(m.actor_id) == 1
|
|
||||||
)
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
Returns true if the member is an administrator (admin or creator) of the group
|
|
||||||
"""
|
"""
|
||||||
def is_administrator(%Member{role: :administrator}), do: {:is_admin, true}
|
def is_administrator(%Member{role: :administrator}), do: {:is_admin, true}
|
||||||
def is_administrator(%Member{role: :creator}), do: {:is_admin, true}
|
def is_administrator(%Member{role: :creator}), do: {:is_admin, true}
|
||||||
def is_administrator(%Member{}), do: {:is_admin, false}
|
def is_administrator(%Member{}), do: {:is_admin, false}
|
||||||
|
|
||||||
|
@doc false
|
||||||
|
@spec changeset(t | Ecto.Changeset.t(), map) :: Ecto.Changeset.t()
|
||||||
|
def changeset(member, attrs) do
|
||||||
|
member
|
||||||
|
|> cast(attrs, @attrs)
|
||||||
|
|> validate_required(@required_attrs)
|
||||||
|
|> unique_constraint(:parent_id, name: :members_actor_parent_unique_index)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@ -18,12 +18,6 @@ defmodule Mobilizon.Addresses do
|
|||||||
@spec query(Ecto.Query.t(), map) :: Ecto.Query.t()
|
@spec query(Ecto.Query.t(), map) :: Ecto.Query.t()
|
||||||
def query(queryable, _params), do: queryable
|
def query(queryable, _params), do: queryable
|
||||||
|
|
||||||
@doc """
|
|
||||||
Returns the list of addresses.
|
|
||||||
"""
|
|
||||||
@spec list_addresses :: [Address.t()]
|
|
||||||
def list_addresses, do: Repo.all(Address)
|
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Gets a single address.
|
Gets a single address.
|
||||||
"""
|
"""
|
||||||
@ -72,6 +66,12 @@ defmodule Mobilizon.Addresses do
|
|||||||
@spec delete_address(Address.t()) :: {:ok, Address.t()} | {:error, Ecto.Changeset.t()}
|
@spec delete_address(Address.t()) :: {:ok, Address.t()} | {:error, Ecto.Changeset.t()}
|
||||||
def delete_address(%Address{} = address), do: Repo.delete(address)
|
def delete_address(%Address{} = address), do: Repo.delete(address)
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Returns the list of addresses.
|
||||||
|
"""
|
||||||
|
@spec list_addresses :: [Address.t()]
|
||||||
|
def list_addresses, do: Repo.all(Address)
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Searches addresses.
|
Searches addresses.
|
||||||
|
|
||||||
|
@ -8,16 +8,6 @@ defmodule Mobilizon.Admin do
|
|||||||
alias Mobilizon.Admin.ActionLog
|
alias Mobilizon.Admin.ActionLog
|
||||||
alias Mobilizon.Storage.{Page, Repo}
|
alias Mobilizon.Storage.{Page, Repo}
|
||||||
|
|
||||||
@doc """
|
|
||||||
Returns the list of action logs.
|
|
||||||
"""
|
|
||||||
@spec list_action_logs(integer | nil, integer | nil) :: [ActionLog.t()]
|
|
||||||
def list_action_logs(page \\ nil, limit \\ nil) do
|
|
||||||
list_action_logs_query()
|
|
||||||
|> Page.paginate(page, limit)
|
|
||||||
|> Repo.all()
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Creates a action_log.
|
Creates a action_log.
|
||||||
"""
|
"""
|
||||||
@ -28,6 +18,16 @@ defmodule Mobilizon.Admin do
|
|||||||
|> Repo.insert()
|
|> Repo.insert()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Returns the list of action logs.
|
||||||
|
"""
|
||||||
|
@spec list_action_logs(integer | nil, integer | nil) :: [ActionLog.t()]
|
||||||
|
def list_action_logs(page \\ nil, limit \\ nil) do
|
||||||
|
list_action_logs_query()
|
||||||
|
|> Page.paginate(page, limit)
|
||||||
|
|> Repo.all()
|
||||||
|
end
|
||||||
|
|
||||||
@spec list_action_logs_query :: Ecto.Query.t()
|
@spec list_action_logs_query :: Ecto.Query.t()
|
||||||
defp list_action_logs_query do
|
defp list_action_logs_query do
|
||||||
from(r in ActionLog, preload: [:actor])
|
from(r in ActionLog, preload: [:actor])
|
||||||
|
@ -12,4 +12,17 @@ defmodule Mobilizon.Crypto do
|
|||||||
|> :crypto.strong_rand_bytes()
|
|> :crypto.strong_rand_bytes()
|
||||||
|> Base.url_encode64()
|
|> Base.url_encode64()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Generate RSA 2048-bit private key.
|
||||||
|
"""
|
||||||
|
@spec generate_rsa_2048_private_key :: String.t()
|
||||||
|
def generate_rsa_2048_private_key do
|
||||||
|
key = :public_key.generate_key({:rsa, 2048, 65_537})
|
||||||
|
entry = :public_key.pem_entry_encode(:RSAPrivateKey, key)
|
||||||
|
|
||||||
|
[entry]
|
||||||
|
|> :public_key.pem_encode()
|
||||||
|
|> String.trim_trailing()
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@ -21,17 +21,6 @@ defmodule Mobilizon.Reports do
|
|||||||
@spec query(Ecto.Query.t(), map) :: Ecto.Query.t()
|
@spec query(Ecto.Query.t(), map) :: Ecto.Query.t()
|
||||||
def query(queryable, _params), do: queryable
|
def query(queryable, _params), do: queryable
|
||||||
|
|
||||||
@doc """
|
|
||||||
Returns the list of reports.
|
|
||||||
"""
|
|
||||||
@spec list_reports(integer | nil, integer | nil, atom, atom) :: [Report.t()]
|
|
||||||
def list_reports(page \\ nil, limit \\ nil, sort \\ :updated_at, direction \\ :asc) do
|
|
||||||
list_reports_query()
|
|
||||||
|> Page.paginate(page, limit)
|
|
||||||
|> sort(sort, direction)
|
|
||||||
|> Repo.all()
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Gets a single report.
|
Gets a single report.
|
||||||
"""
|
"""
|
||||||
@ -90,17 +79,16 @@ defmodule Mobilizon.Reports do
|
|||||||
Deletes a report.
|
Deletes a report.
|
||||||
"""
|
"""
|
||||||
@spec delete_report(Report.t()) :: {:ok, Report.t()} | {:error, Ecto.Changeset.t()}
|
@spec delete_report(Report.t()) :: {:ok, Report.t()} | {:error, Ecto.Changeset.t()}
|
||||||
def delete_report(%Report{} = report) do
|
def delete_report(%Report{} = report), do: Repo.delete(report)
|
||||||
Repo.delete(report)
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Returns the list of notes for a report.
|
Returns the list of reports.
|
||||||
"""
|
"""
|
||||||
@spec list_notes_for_report(Report.t()) :: [Note.t()]
|
@spec list_reports(integer | nil, integer | nil, atom, atom) :: [Report.t()]
|
||||||
def list_notes_for_report(%Report{id: report_id}) do
|
def list_reports(page \\ nil, limit \\ nil, sort \\ :updated_at, direction \\ :asc) do
|
||||||
report_id
|
list_reports_query()
|
||||||
|> list_notes_for_report_query()
|
|> Page.paginate(page, limit)
|
||||||
|
|> sort(sort, direction)
|
||||||
|> Repo.all()
|
|> Repo.all()
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -134,8 +122,21 @@ defmodule Mobilizon.Reports do
|
|||||||
Deletes a note.
|
Deletes a note.
|
||||||
"""
|
"""
|
||||||
@spec delete_note(Note.t()) :: {:ok, Note.t()} | {:error, Ecto.Changeset.t()}
|
@spec delete_note(Note.t()) :: {:ok, Note.t()} | {:error, Ecto.Changeset.t()}
|
||||||
def delete_note(%Note{} = note) do
|
def delete_note(%Note{} = note), do: Repo.delete(note)
|
||||||
Repo.delete(note)
|
|
||||||
|
@doc """
|
||||||
|
Returns the list of notes for a report.
|
||||||
|
"""
|
||||||
|
@spec list_notes_for_report(Report.t()) :: [Note.t()]
|
||||||
|
def list_notes_for_report(%Report{id: report_id}) do
|
||||||
|
report_id
|
||||||
|
|> list_notes_for_report_query()
|
||||||
|
|> Repo.all()
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec report_by_url_query(String.t()) :: Ecto.Query.t()
|
||||||
|
defp report_by_url_query(url) do
|
||||||
|
from(r in Report, where: r.uri == ^url)
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec list_reports_query :: Ecto.Query.t()
|
@spec list_reports_query :: Ecto.Query.t()
|
||||||
@ -146,11 +147,6 @@ defmodule Mobilizon.Reports do
|
|||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec report_by_url_query(String.t()) :: Ecto.Query.t()
|
|
||||||
defp report_by_url_query(url) do
|
|
||||||
from(r in Report, where: r.uri == ^url)
|
|
||||||
end
|
|
||||||
|
|
||||||
@spec list_notes_for_report_query(integer | String.t()) :: Ecto.Query.t()
|
@spec list_notes_for_report_query(integer | String.t()) :: Ecto.Query.t()
|
||||||
defp list_notes_for_report_query(report_id) do
|
defp list_notes_for_report_query(report_id) do
|
||||||
from(
|
from(
|
||||||
|
@ -101,9 +101,7 @@ defmodule Mobilizon.Users do
|
|||||||
Deletes an user.
|
Deletes an user.
|
||||||
"""
|
"""
|
||||||
@spec delete_user(User.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
|
@spec delete_user(User.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
|
||||||
def delete_user(%User{} = user) do
|
def delete_user(%User{} = user), do: Repo.delete(user)
|
||||||
Repo.delete(user)
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Get an user with its actors
|
Get an user with its actors
|
||||||
@ -219,9 +217,7 @@ defmodule Mobilizon.Users do
|
|||||||
Counts users.
|
Counts users.
|
||||||
"""
|
"""
|
||||||
@spec count_users :: integer
|
@spec count_users :: integer
|
||||||
def count_users do
|
def count_users, do: Repo.one(from(u in User, select: count(u.id)))
|
||||||
Repo.one(from(u in User, select: count(u.id)))
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Authenticate an user.
|
Authenticate an user.
|
||||||
|
@ -32,7 +32,7 @@ defmodule MobilizonWeb.API.Follows do
|
|||||||
|
|
||||||
def accept(%Actor{} = follower, %Actor{} = followed) do
|
def accept(%Actor{} = follower, %Actor{} = followed) do
|
||||||
with %Follower{approved: false, id: follow_id, url: follow_url} = follow <-
|
with %Follower{approved: false, id: follow_id, url: follow_url} = follow <-
|
||||||
Actor.following?(follower, followed),
|
Actors.following?(follower, followed),
|
||||||
activity_follow_url <- "#{MobilizonWeb.Endpoint.url()}/accept/follow/#{follow_id}",
|
activity_follow_url <- "#{MobilizonWeb.Endpoint.url()}/accept/follow/#{follow_id}",
|
||||||
data <-
|
data <-
|
||||||
ActivityPub.Utils.make_follow_data(followed, follower, follow_url),
|
ActivityPub.Utils.make_follow_data(followed, follower, follow_url),
|
||||||
|
@ -1,20 +1,21 @@
|
|||||||
defmodule MobilizonWeb.API.Search do
|
defmodule MobilizonWeb.API.Search do
|
||||||
@moduledoc """
|
@moduledoc """
|
||||||
API for Search
|
API for search.
|
||||||
"""
|
"""
|
||||||
alias Mobilizon.Service.ActivityPub
|
|
||||||
alias Mobilizon.Actors
|
alias Mobilizon.Actors
|
||||||
alias Mobilizon.Actors.Actor
|
alias Mobilizon.Actors.ActorType
|
||||||
alias Mobilizon.Events
|
alias Mobilizon.Events
|
||||||
alias Mobilizon.Events.{Event, Comment}
|
alias Mobilizon.Service.ActivityPub
|
||||||
|
alias Mobilizon.Storage.Page
|
||||||
|
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Search actors
|
Searches actors.
|
||||||
"""
|
"""
|
||||||
@spec search_actors(String.t(), integer(), integer(), String.t()) ::
|
@spec search_actors(String.t(), integer | nil, integer | nil, ActorType.t()) ::
|
||||||
{:ok, %{total: integer(), elements: list(Actor.t())}} | {:error, any()}
|
{:ok, Page.t()} | {:error, String.t()}
|
||||||
def search_actors(search, page \\ 1, limit \\ 10, result_type) do
|
def search_actors(search, page \\ 1, limit \\ 10, result_type) do
|
||||||
search = String.trim(search)
|
search = String.trim(search)
|
||||||
|
|
||||||
@ -22,31 +23,33 @@ defmodule MobilizonWeb.API.Search do
|
|||||||
search == "" ->
|
search == "" ->
|
||||||
{:error, "Search can't be empty"}
|
{:error, "Search can't be empty"}
|
||||||
|
|
||||||
# Some URLs could be domain.tld/@username, so keep this condition above handle_search? function
|
# Some URLs could be domain.tld/@username, so keep this condition above
|
||||||
url_search?(search) ->
|
# the `is_handle` function
|
||||||
# If this is not an actor, skip
|
is_url(search) ->
|
||||||
|
# skip, if it's not an actor
|
||||||
case process_from_url(search) do
|
case process_from_url(search) do
|
||||||
%{:total => total, :elements => [%Actor{}] = elements} ->
|
%Page{total: _total, elements: _elements} = page ->
|
||||||
{:ok, %{total: total, elements: elements}}
|
{:ok, page}
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
{:ok, %{total: 0, elements: []}}
|
{:ok, %{total: 0, elements: []}}
|
||||||
end
|
end
|
||||||
|
|
||||||
handle_search?(search) ->
|
is_handle(search) ->
|
||||||
{:ok, process_from_username(search)}
|
{:ok, process_from_username(search)}
|
||||||
|
|
||||||
true ->
|
true ->
|
||||||
{:ok,
|
page = Actors.build_actors_by_username_or_name_page(search, [result_type], page, limit)
|
||||||
Actors.find_and_count_actors_by_username_or_name(search, [result_type], page, limit)}
|
|
||||||
|
{:ok, page}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Search events
|
Search events
|
||||||
"""
|
"""
|
||||||
@spec search_events(String.t(), integer(), integer()) ::
|
@spec search_events(String.t(), integer | nil, integer | nil) ::
|
||||||
{:ok, %{total: integer(), elements: list(Event.t())}} | {:error, any()}
|
{:ok, Page.t()} | {:error, String.t()}
|
||||||
def search_events(search, page \\ 1, limit \\ 10) do
|
def search_events(search, page \\ 1, limit \\ 10) do
|
||||||
search = String.trim(search)
|
search = String.trim(search)
|
||||||
|
|
||||||
@ -54,11 +57,11 @@ defmodule MobilizonWeb.API.Search do
|
|||||||
search == "" ->
|
search == "" ->
|
||||||
{:error, "Search can't be empty"}
|
{:error, "Search can't be empty"}
|
||||||
|
|
||||||
url_search?(search) ->
|
is_url(search) ->
|
||||||
# If this is not an event, skip
|
# skip, if it's w not an actor
|
||||||
case process_from_url(search) do
|
case process_from_url(search) do
|
||||||
{total = total, [%Event{} = elements]} ->
|
%Page{total: _total, elements: _elements} = page ->
|
||||||
{:ok, %{total: total, elements: elements}}
|
{:ok, page}
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
{:ok, %{total: 0, elements: []}}
|
{:ok, %{total: 0, elements: []}}
|
||||||
@ -70,43 +73,36 @@ defmodule MobilizonWeb.API.Search do
|
|||||||
end
|
end
|
||||||
|
|
||||||
# If the search string is an username
|
# If the search string is an username
|
||||||
@spec process_from_username(String.t()) :: %{total: integer(), elements: [Actor.t()]}
|
@spec process_from_username(String.t()) :: Page.t()
|
||||||
defp process_from_username(search) do
|
defp process_from_username(search) do
|
||||||
case ActivityPub.find_or_make_actor_from_nickname(search) do
|
case ActivityPub.find_or_make_actor_from_nickname(search) do
|
||||||
{:ok, actor} ->
|
{:ok, actor} ->
|
||||||
%{total: 1, elements: [actor]}
|
%Page{total: 1, elements: [actor]}
|
||||||
|
|
||||||
{:error, _err} ->
|
{:error, _err} ->
|
||||||
Logger.debug(fn -> "Unable to find or make actor '#{search}'" end)
|
Logger.debug(fn -> "Unable to find or make actor '#{search}'" end)
|
||||||
%{total: 0, elements: []}
|
|
||||||
|
%Page{total: 0, elements: []}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# If the search string is an URL
|
# If the search string is an URL
|
||||||
@spec process_from_url(String.t()) :: %{
|
@spec process_from_url(String.t()) :: Page.t()
|
||||||
total: integer(),
|
|
||||||
elements: [Actor.t() | Event.t() | Comment.t()]
|
|
||||||
}
|
|
||||||
defp process_from_url(search) do
|
defp process_from_url(search) do
|
||||||
case ActivityPub.fetch_object_from_url(search) do
|
case ActivityPub.fetch_object_from_url(search) do
|
||||||
{:ok, object} ->
|
{:ok, object} ->
|
||||||
%{total: 1, elements: [object]}
|
%Page{total: 1, elements: [object]}
|
||||||
|
|
||||||
{:error, _err} ->
|
{:error, _err} ->
|
||||||
Logger.debug(fn -> "Unable to find or make object from URL '#{search}'" end)
|
Logger.debug(fn -> "Unable to find or make object from URL '#{search}'" end)
|
||||||
%{total: 0, elements: []}
|
|
||||||
|
%Page{total: 0, elements: []}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Is the search an URL search?
|
@spec is_url(String.t()) :: boolean
|
||||||
@spec url_search?(String.t()) :: boolean
|
defp is_url(search), do: String.starts_with?(search, ["http://", "https://"])
|
||||||
defp url_search?(search) do
|
|
||||||
String.starts_with?(search, "https://") or String.starts_with?(search, "http://")
|
|
||||||
end
|
|
||||||
|
|
||||||
# Is the search an handle search?
|
@spec is_handle(String.t()) :: boolean
|
||||||
@spec handle_search?(String.t()) :: boolean
|
defp is_handle(search), do: String.match?(search, ~r/@/)
|
||||||
defp handle_search?(search) do
|
|
||||||
String.match?(search, ~r/@/)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
@ -31,7 +31,7 @@ defmodule MobilizonWeb.ActivityPubController do
|
|||||||
|
|
||||||
def following(conn, %{"name" => name, "page" => page}) do
|
def following(conn, %{"name" => name, "page" => page}) do
|
||||||
with {page, ""} <- Integer.parse(page),
|
with {page, ""} <- Integer.parse(page),
|
||||||
%Actor{} = actor <- Actors.get_local_actor_by_name_with_everything(name) do
|
%Actor{} = actor <- Actors.get_local_actor_by_name_with_preload(name) do
|
||||||
conn
|
conn
|
||||||
|> put_resp_header("content-type", "application/activity+json")
|
|> put_resp_header("content-type", "application/activity+json")
|
||||||
|> json(ActorView.render("following.json", %{actor: actor, page: page}))
|
|> json(ActorView.render("following.json", %{actor: actor, page: page}))
|
||||||
@ -39,7 +39,7 @@ defmodule MobilizonWeb.ActivityPubController do
|
|||||||
end
|
end
|
||||||
|
|
||||||
def following(conn, %{"name" => name}) do
|
def following(conn, %{"name" => name}) do
|
||||||
with %Actor{} = actor <- Actors.get_local_actor_by_name_with_everything(name) do
|
with %Actor{} = actor <- Actors.get_local_actor_by_name_with_preload(name) do
|
||||||
conn
|
conn
|
||||||
|> put_resp_header("content-type", "application/activity+json")
|
|> put_resp_header("content-type", "application/activity+json")
|
||||||
|> json(ActorView.render("following.json", %{actor: actor}))
|
|> json(ActorView.render("following.json", %{actor: actor}))
|
||||||
@ -48,7 +48,7 @@ defmodule MobilizonWeb.ActivityPubController do
|
|||||||
|
|
||||||
def followers(conn, %{"name" => name, "page" => page}) do
|
def followers(conn, %{"name" => name, "page" => page}) do
|
||||||
with {page, ""} <- Integer.parse(page),
|
with {page, ""} <- Integer.parse(page),
|
||||||
%Actor{} = actor <- Actors.get_local_actor_by_name_with_everything(name) do
|
%Actor{} = actor <- Actors.get_local_actor_by_name_with_preload(name) do
|
||||||
conn
|
conn
|
||||||
|> put_resp_header("content-type", "application/activity+json")
|
|> put_resp_header("content-type", "application/activity+json")
|
||||||
|> json(ActorView.render("followers.json", %{actor: actor, page: page}))
|
|> json(ActorView.render("followers.json", %{actor: actor, page: page}))
|
||||||
@ -56,7 +56,7 @@ defmodule MobilizonWeb.ActivityPubController do
|
|||||||
end
|
end
|
||||||
|
|
||||||
def followers(conn, %{"name" => name}) do
|
def followers(conn, %{"name" => name}) do
|
||||||
with %Actor{} = actor <- Actors.get_local_actor_by_name_with_everything(name) do
|
with %Actor{} = actor <- Actors.get_local_actor_by_name_with_preload(name) do
|
||||||
conn
|
conn
|
||||||
|> put_resp_header("content-type", "application/activity+json")
|
|> put_resp_header("content-type", "application/activity+json")
|
||||||
|> json(ActorView.render("followers.json", %{actor: actor}))
|
|> json(ActorView.render("followers.json", %{actor: actor}))
|
||||||
|
@ -76,7 +76,7 @@ defmodule MobilizonWeb.Resolvers.Group do
|
|||||||
) do
|
) do
|
||||||
with {:ok, %Actor{} = group} <- Actors.get_group_by_actor_id(group_id),
|
with {:ok, %Actor{} = group} <- Actors.get_group_by_actor_id(group_id),
|
||||||
{:is_owned, %Actor{}} <- User.owns_actor(user, actor_id),
|
{:is_owned, %Actor{}} <- User.owns_actor(user, actor_id),
|
||||||
{:ok, %Member{} = member} <- Member.get_member(actor_id, group.id),
|
{:ok, %Member{} = member} <- Actors.get_member(actor_id, group.id),
|
||||||
{:is_admin, true} <- Member.is_administrator(member),
|
{:is_admin, true} <- Member.is_administrator(member),
|
||||||
group <- Actors.delete_group!(group) do
|
group <- Actors.delete_group!(group) do
|
||||||
{:ok, %{id: group.id}}
|
{:ok, %{id: group.id}}
|
||||||
@ -109,9 +109,9 @@ defmodule MobilizonWeb.Resolvers.Group do
|
|||||||
) do
|
) do
|
||||||
with {:is_owned, %Actor{} = actor} <- User.owns_actor(user, actor_id),
|
with {:is_owned, %Actor{} = actor} <- User.owns_actor(user, actor_id),
|
||||||
{:ok, %Actor{} = group} <- Actors.get_group_by_actor_id(group_id),
|
{:ok, %Actor{} = group} <- Actors.get_group_by_actor_id(group_id),
|
||||||
{:error, :member_not_found} <- Member.get_member(actor.id, group.id),
|
{:error, :member_not_found} <- Actors.get_member(actor.id, group.id),
|
||||||
{:is_able_to_join, true} <- {:is_able_to_join, Member.can_be_joined(group)},
|
{:is_able_to_join, true} <- {:is_able_to_join, Member.can_be_joined(group)},
|
||||||
role <- Mobilizon.Actors.get_default_member_role(group),
|
role <- Member.get_default_member_role(group),
|
||||||
{:ok, _} <- Actors.create_member(%{parent_id: group.id, actor_id: actor.id, role: role}) do
|
{:ok, _} <- Actors.create_member(%{parent_id: group.id, actor_id: actor.id, role: role}) do
|
||||||
{
|
{
|
||||||
:ok,
|
:ok,
|
||||||
@ -149,7 +149,7 @@ defmodule MobilizonWeb.Resolvers.Group do
|
|||||||
%{context: %{current_user: user}}
|
%{context: %{current_user: user}}
|
||||||
) do
|
) do
|
||||||
with {:is_owned, %Actor{} = actor} <- User.owns_actor(user, actor_id),
|
with {:is_owned, %Actor{} = actor} <- User.owns_actor(user, actor_id),
|
||||||
{:ok, %Member{} = member} <- Member.get_member(actor.id, group_id),
|
{:ok, %Member{} = member} <- Actors.get_member(actor.id, group_id),
|
||||||
{:only_administrator, false} <-
|
{:only_administrator, false} <-
|
||||||
{:only_administrator, check_that_member_is_not_last_administrator(group_id, actor_id)},
|
{:only_administrator, check_that_member_is_not_last_administrator(group_id, actor_id)},
|
||||||
{:ok, _} <-
|
{:ok, _} <-
|
||||||
@ -176,7 +176,7 @@ defmodule MobilizonWeb.Resolvers.Group do
|
|||||||
# and that it's the actor requesting leaving the group we return true
|
# and that it's the actor requesting leaving the group we return true
|
||||||
@spec check_that_member_is_not_last_administrator(integer(), integer()) :: boolean()
|
@spec check_that_member_is_not_last_administrator(integer(), integer()) :: boolean()
|
||||||
defp check_that_member_is_not_last_administrator(group_id, actor_id) do
|
defp check_that_member_is_not_last_administrator(group_id, actor_id) do
|
||||||
case Member.list_administrator_members_for_group(group_id) do
|
case Actors.list_administrator_members_for_group(group_id) do
|
||||||
[%Member{actor: %Actor{id: member_actor_id}}] ->
|
[%Member{actor: %Actor{id: member_actor_id}}] ->
|
||||||
actor_id == member_actor_id
|
actor_id == member_actor_id
|
||||||
|
|
||||||
|
@ -2,12 +2,13 @@ defmodule MobilizonWeb.Resolvers.Person do
|
|||||||
@moduledoc """
|
@moduledoc """
|
||||||
Handles the person-related GraphQL calls
|
Handles the person-related GraphQL calls
|
||||||
"""
|
"""
|
||||||
|
|
||||||
alias Mobilizon.Actors
|
alias Mobilizon.Actors
|
||||||
alias Mobilizon.Actors.{Actor, Member}
|
alias Mobilizon.Actors.Actor
|
||||||
alias Mobilizon.Users.User
|
|
||||||
alias Mobilizon.Users
|
|
||||||
alias Mobilizon.Events
|
alias Mobilizon.Events
|
||||||
alias Mobilizon.Service.ActivityPub
|
alias Mobilizon.Service.ActivityPub
|
||||||
|
alias Mobilizon.Users
|
||||||
|
alias Mobilizon.Users.User
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Find a person
|
Find a person
|
||||||
@ -206,7 +207,7 @@ defmodule MobilizonWeb.Resolvers.Person do
|
|||||||
# We check that the actor is not the last administrator/creator of a group
|
# We check that the actor is not the last administrator/creator of a group
|
||||||
@spec last_admin_of_a_group?(integer()) :: boolean()
|
@spec last_admin_of_a_group?(integer()) :: boolean()
|
||||||
defp last_admin_of_a_group?(actor_id) do
|
defp last_admin_of_a_group?(actor_id) do
|
||||||
length(Member.list_group_id_where_last_administrator(actor_id)) > 0
|
length(Actors.list_group_id_where_last_administrator(actor_id)) > 0
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec proxify_avatar(Actor.t()) :: Actor.t()
|
@spec proxify_avatar(Actor.t()) :: Actor.t()
|
||||||
|
@ -91,7 +91,7 @@ defmodule MobilizonWeb.Resolvers.Report do
|
|||||||
when is_moderator(role) do
|
when is_moderator(role) do
|
||||||
with {:is_owned, %Actor{}} <- User.owns_actor(user, moderator_id),
|
with {:is_owned, %Actor{}} <- User.owns_actor(user, moderator_id),
|
||||||
%Report{} = report <- Reports.get_report(report_id),
|
%Report{} = report <- Reports.get_report(report_id),
|
||||||
%Actor{} = moderator <- Actors.get_local_actor_with_everything(moderator_id),
|
%Actor{} = moderator <- Actors.get_local_actor_with_preload(moderator_id),
|
||||||
{:ok, %Note{} = note} <-
|
{:ok, %Note{} = note} <-
|
||||||
MobilizonWeb.API.Reports.create_report_note(report, moderator, content) do
|
MobilizonWeb.API.Reports.create_report_note(report, moderator, content) do
|
||||||
{:ok, note}
|
{:ok, note}
|
||||||
@ -106,7 +106,7 @@ defmodule MobilizonWeb.Resolvers.Report do
|
|||||||
when is_moderator(role) do
|
when is_moderator(role) do
|
||||||
with {:is_owned, %Actor{}} <- User.owns_actor(user, moderator_id),
|
with {:is_owned, %Actor{}} <- User.owns_actor(user, moderator_id),
|
||||||
%Note{} = note <- Reports.get_note(note_id),
|
%Note{} = note <- Reports.get_note(note_id),
|
||||||
%Actor{} = moderator <- Actors.get_local_actor_with_everything(moderator_id),
|
%Actor{} = moderator <- Actors.get_local_actor_with_preload(moderator_id),
|
||||||
{:ok, %Note{} = note} <-
|
{:ok, %Note{} = note} <-
|
||||||
MobilizonWeb.API.Reports.delete_report_note(note, moderator) do
|
MobilizonWeb.API.Reports.delete_report_note(note, moderator) do
|
||||||
{:ok, %{id: note.id}}
|
{:ok, %{id: note.id}}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
defmodule MobilizonWeb.ActivityPub.ActorView do
|
defmodule MobilizonWeb.ActivityPub.ActorView do
|
||||||
use MobilizonWeb, :view
|
use MobilizonWeb, :view
|
||||||
|
|
||||||
|
alias Mobilizon.Actors
|
||||||
alias Mobilizon.Actors.Actor
|
alias Mobilizon.Actors.Actor
|
||||||
alias Mobilizon.Service.ActivityPub
|
alias Mobilizon.Service.ActivityPub
|
||||||
alias Mobilizon.Service.ActivityPub.Utils
|
alias Mobilizon.Service.ActivityPub.Utils
|
||||||
@ -47,8 +48,8 @@ defmodule MobilizonWeb.ActivityPub.ActorView do
|
|||||||
|
|
||||||
def render("following.json", %{actor: actor, page: page}) do
|
def render("following.json", %{actor: actor, page: page}) do
|
||||||
%{total: total, elements: following} =
|
%{total: total, elements: following} =
|
||||||
if Actor.public_visibility?(actor),
|
if Actor.is_public_visibility(actor),
|
||||||
do: Actor.get_followings(actor, page),
|
do: Actors.get_followings(actor, page),
|
||||||
else: @private_visibility_empty_collection
|
else: @private_visibility_empty_collection
|
||||||
|
|
||||||
following
|
following
|
||||||
@ -58,8 +59,8 @@ defmodule MobilizonWeb.ActivityPub.ActorView do
|
|||||||
|
|
||||||
def render("following.json", %{actor: actor}) do
|
def render("following.json", %{actor: actor}) do
|
||||||
%{total: total, elements: following} =
|
%{total: total, elements: following} =
|
||||||
if Actor.public_visibility?(actor),
|
if Actor.is_public_visibility(actor),
|
||||||
do: Actor.get_followings(actor),
|
do: Actors.get_followings(actor),
|
||||||
else: @private_visibility_empty_collection
|
else: @private_visibility_empty_collection
|
||||||
|
|
||||||
%{
|
%{
|
||||||
@ -73,8 +74,8 @@ defmodule MobilizonWeb.ActivityPub.ActorView do
|
|||||||
|
|
||||||
def render("followers.json", %{actor: actor, page: page}) do
|
def render("followers.json", %{actor: actor, page: page}) do
|
||||||
%{total: total, elements: followers} =
|
%{total: total, elements: followers} =
|
||||||
if Actor.public_visibility?(actor),
|
if Actor.is_public_visibility(actor),
|
||||||
do: Actor.get_followers(actor, page),
|
do: Actors.get_followers(actor, page),
|
||||||
else: @private_visibility_empty_collection
|
else: @private_visibility_empty_collection
|
||||||
|
|
||||||
followers
|
followers
|
||||||
@ -84,8 +85,8 @@ defmodule MobilizonWeb.ActivityPub.ActorView do
|
|||||||
|
|
||||||
def render("followers.json", %{actor: actor}) do
|
def render("followers.json", %{actor: actor}) do
|
||||||
%{total: total, elements: followers} =
|
%{total: total, elements: followers} =
|
||||||
if Actor.public_visibility?(actor),
|
if Actor.is_public_visibility(actor),
|
||||||
do: Actor.get_followers(actor),
|
do: Actors.get_followers(actor),
|
||||||
else: @private_visibility_empty_collection
|
else: @private_visibility_empty_collection
|
||||||
|
|
||||||
%{
|
%{
|
||||||
@ -99,7 +100,7 @@ defmodule MobilizonWeb.ActivityPub.ActorView do
|
|||||||
|
|
||||||
def render("outbox.json", %{actor: actor, page: page}) do
|
def render("outbox.json", %{actor: actor, page: page}) do
|
||||||
%{total: total, elements: followers} =
|
%{total: total, elements: followers} =
|
||||||
if Actor.public_visibility?(actor),
|
if Actor.is_public_visibility(actor),
|
||||||
do: ActivityPub.fetch_public_activities_for_actor(actor, page),
|
do: ActivityPub.fetch_public_activities_for_actor(actor, page),
|
||||||
else: @private_visibility_empty_collection
|
else: @private_visibility_empty_collection
|
||||||
|
|
||||||
@ -110,7 +111,7 @@ defmodule MobilizonWeb.ActivityPub.ActorView do
|
|||||||
|
|
||||||
def render("outbox.json", %{actor: actor}) do
|
def render("outbox.json", %{actor: actor}) do
|
||||||
%{total: total, elements: followers} =
|
%{total: total, elements: followers} =
|
||||||
if Actor.public_visibility?(actor),
|
if Actor.is_public_visibility(actor),
|
||||||
do: ActivityPub.fetch_public_activities_for_actor(actor),
|
do: ActivityPub.fetch_public_activities_for_actor(actor),
|
||||||
else: @private_visibility_empty_collection
|
else: @private_visibility_empty_collection
|
||||||
|
|
||||||
|
@ -113,6 +113,29 @@ defmodule Mobilizon.Service.ActivityPub do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Getting an actor from url, eventually creating it
|
||||||
|
"""
|
||||||
|
@spec get_or_fetch_by_url(String.t(), boolean) :: {:ok, Actor.t()} | {:error, String.t()}
|
||||||
|
def get_or_fetch_by_url(url, preload \\ false) do
|
||||||
|
case Actors.get_actor_by_url(url, preload) do
|
||||||
|
{:ok, %Actor{} = actor} ->
|
||||||
|
{:ok, actor}
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
case make_actor_from_url(url, preload) do
|
||||||
|
{:ok, %Actor{} = actor} ->
|
||||||
|
{:ok, actor}
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
Logger.warn("Could not fetch by AP id")
|
||||||
|
|
||||||
|
{:error, "Could not fetch by AP id"}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Create an activity of type "Create"
|
Create an activity of type "Create"
|
||||||
"""
|
"""
|
||||||
@ -279,7 +302,7 @@ defmodule Mobilizon.Service.ActivityPub do
|
|||||||
"""
|
"""
|
||||||
def follow(%Actor{} = follower, %Actor{} = followed, activity_id \\ nil, local \\ true) do
|
def follow(%Actor{} = follower, %Actor{} = followed, activity_id \\ nil, local \\ true) do
|
||||||
with {:ok, %Follower{url: follow_url}} <-
|
with {:ok, %Follower{url: follow_url}} <-
|
||||||
Actor.follow(followed, follower, activity_id, false),
|
Actors.follow(followed, follower, activity_id, false),
|
||||||
activity_follow_id <-
|
activity_follow_id <-
|
||||||
activity_id || follow_url,
|
activity_id || follow_url,
|
||||||
data <- make_follow_data(followed, follower, activity_follow_id),
|
data <- make_follow_data(followed, follower, activity_follow_id),
|
||||||
@ -298,7 +321,7 @@ defmodule Mobilizon.Service.ActivityPub do
|
|||||||
"""
|
"""
|
||||||
@spec unfollow(Actor.t(), Actor.t(), String.t(), boolean()) :: {:ok, map()} | any()
|
@spec unfollow(Actor.t(), Actor.t(), String.t(), boolean()) :: {:ok, map()} | any()
|
||||||
def unfollow(%Actor{} = follower, %Actor{} = followed, activity_id \\ nil, local \\ true) do
|
def unfollow(%Actor{} = follower, %Actor{} = followed, activity_id \\ nil, local \\ true) do
|
||||||
with {:ok, %Follower{id: follow_id}} <- Actor.unfollow(followed, follower),
|
with {:ok, %Follower{id: follow_id}} <- Actors.unfollow(followed, follower),
|
||||||
# We recreate the follow activity
|
# We recreate the follow activity
|
||||||
data <-
|
data <-
|
||||||
make_follow_data(
|
make_follow_data(
|
||||||
@ -466,7 +489,7 @@ defmodule Mobilizon.Service.ActivityPub do
|
|||||||
def make_actor_from_url(url, preload \\ false) do
|
def make_actor_from_url(url, preload \\ false) do
|
||||||
case fetch_and_prepare_actor_from_url(url) do
|
case fetch_and_prepare_actor_from_url(url) do
|
||||||
{:ok, data} ->
|
{:ok, data} ->
|
||||||
Actors.insert_or_update_actor(data, preload)
|
Actors.upsert_actor(data, preload)
|
||||||
|
|
||||||
# Request returned 410
|
# Request returned 410
|
||||||
{:error, :actor_deleted} ->
|
{:error, :actor_deleted} ->
|
||||||
@ -529,7 +552,7 @@ defmodule Mobilizon.Service.ActivityPub do
|
|||||||
|
|
||||||
followers =
|
followers =
|
||||||
if actor.followers_url in activity.recipients do
|
if actor.followers_url in activity.recipients do
|
||||||
Actor.get_full_external_followers(actor)
|
Actors.get_full_external_followers(actor)
|
||||||
else
|
else
|
||||||
[]
|
[]
|
||||||
end
|
end
|
||||||
|
@ -4,7 +4,6 @@ defmodule Mobilizon.Service.ActivityPub.Converters.Comment do
|
|||||||
|
|
||||||
This module allows to convert events from ActivityStream format to our own internal one, and back
|
This module allows to convert events from ActivityStream format to our own internal one, and back
|
||||||
"""
|
"""
|
||||||
alias Mobilizon.Actors
|
|
||||||
alias Mobilizon.Actors.Actor
|
alias Mobilizon.Actors.Actor
|
||||||
alias Mobilizon.Events.Comment, as: CommentModel
|
alias Mobilizon.Events.Comment, as: CommentModel
|
||||||
alias Mobilizon.Events.Event
|
alias Mobilizon.Events.Event
|
||||||
@ -20,7 +19,7 @@ defmodule Mobilizon.Service.ActivityPub.Converters.Comment do
|
|||||||
@impl Converter
|
@impl Converter
|
||||||
@spec as_to_model_data(map()) :: map()
|
@spec as_to_model_data(map()) :: map()
|
||||||
def as_to_model_data(object) do
|
def as_to_model_data(object) do
|
||||||
{:ok, %Actor{id: actor_id}} = Actors.get_or_fetch_by_url(object["actor"])
|
{:ok, %Actor{id: actor_id}} = ActivityPub.get_or_fetch_by_url(object["actor"])
|
||||||
Logger.debug("Inserting full comment")
|
Logger.debug("Inserting full comment")
|
||||||
Logger.debug(inspect(object))
|
Logger.debug(inspect(object))
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ defmodule Mobilizon.Service.ActivityPub.Relay do
|
|||||||
|
|
||||||
def follow(target_instance) do
|
def follow(target_instance) do
|
||||||
with %Actor{} = local_actor <- get_actor(),
|
with %Actor{} = local_actor <- get_actor(),
|
||||||
{:ok, %Actor{} = target_actor} <- Actors.get_or_fetch_by_url(target_instance),
|
{:ok, %Actor{} = target_actor} <- ActivityPub.get_or_fetch_by_url(target_instance),
|
||||||
{:ok, activity} <- Follows.follow(local_actor, target_actor) do
|
{:ok, activity} <- Follows.follow(local_actor, target_actor) do
|
||||||
Logger.info("Relay: followed instance #{target_instance}; id=#{activity.data["id"]}")
|
Logger.info("Relay: followed instance #{target_instance}; id=#{activity.data["id"]}")
|
||||||
{:ok, activity}
|
{:ok, activity}
|
||||||
@ -37,7 +37,7 @@ defmodule Mobilizon.Service.ActivityPub.Relay do
|
|||||||
|
|
||||||
def unfollow(target_instance) do
|
def unfollow(target_instance) do
|
||||||
with %Actor{} = local_actor <- get_actor(),
|
with %Actor{} = local_actor <- get_actor(),
|
||||||
{:ok, %Actor{} = target_actor} <- Actors.get_or_fetch_by_url(target_instance),
|
{:ok, %Actor{} = target_actor} <- ActivityPub.get_or_fetch_by_url(target_instance),
|
||||||
{:ok, activity} <- Follows.unfollow(local_actor, target_actor) do
|
{:ok, activity} <- Follows.unfollow(local_actor, target_actor) do
|
||||||
Logger.info("Relay: unfollowed instance #{target_instance}: id=#{activity.data["id"]}")
|
Logger.info("Relay: unfollowed instance #{target_instance}: id=#{activity.data["id"]}")
|
||||||
{:ok, activity}
|
{:ok, activity}
|
||||||
@ -50,7 +50,7 @@ defmodule Mobilizon.Service.ActivityPub.Relay do
|
|||||||
|
|
||||||
def accept(target_instance) do
|
def accept(target_instance) do
|
||||||
with %Actor{} = local_actor <- get_actor(),
|
with %Actor{} = local_actor <- get_actor(),
|
||||||
{:ok, %Actor{} = target_actor} <- Actors.get_or_fetch_by_url(target_instance),
|
{:ok, %Actor{} = target_actor} <- ActivityPub.get_or_fetch_by_url(target_instance),
|
||||||
{:ok, activity} <- Follows.accept(target_actor, local_actor) do
|
{:ok, activity} <- Follows.accept(target_actor, local_actor) do
|
||||||
{:ok, activity}
|
{:ok, activity}
|
||||||
end
|
end
|
||||||
@ -58,7 +58,7 @@ defmodule Mobilizon.Service.ActivityPub.Relay do
|
|||||||
|
|
||||||
# def reject(target_instance) do
|
# def reject(target_instance) do
|
||||||
# with %Actor{} = local_actor <- get_actor(),
|
# with %Actor{} = local_actor <- get_actor(),
|
||||||
# {:ok, %Actor{} = target_actor} <- Actors.get_or_fetch_by_url(target_instance),
|
# {:ok, %Actor{} = target_actor} <- Activity.get_or_fetch_by_url(target_instance),
|
||||||
# {:ok, activity} <- Follows.reject(target_actor, local_actor) do
|
# {:ok, activity} <- Follows.reject(target_actor, local_actor) do
|
||||||
# {:ok, activity}
|
# {:ok, activity}
|
||||||
# end
|
# end
|
||||||
|
@ -139,7 +139,7 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
|
|||||||
def handle_incoming(%{"type" => "Create", "object" => %{"type" => "Note"} = object} = data) do
|
def handle_incoming(%{"type" => "Create", "object" => %{"type" => "Note"} = object} = data) do
|
||||||
Logger.info("Handle incoming to create notes")
|
Logger.info("Handle incoming to create notes")
|
||||||
|
|
||||||
with {:ok, %Actor{} = actor} <- Actors.get_or_fetch_by_url(data["actor"]) do
|
with {:ok, %Actor{} = actor} <- ActivityPub.get_or_fetch_by_url(data["actor"]) do
|
||||||
Logger.debug("found actor")
|
Logger.debug("found actor")
|
||||||
Logger.debug(inspect(actor))
|
Logger.debug(inspect(actor))
|
||||||
|
|
||||||
@ -163,7 +163,7 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
|
|||||||
def handle_incoming(%{"type" => "Create", "object" => %{"type" => "Event"} = object} = data) do
|
def handle_incoming(%{"type" => "Create", "object" => %{"type" => "Event"} = object} = data) do
|
||||||
Logger.info("Handle incoming to create event")
|
Logger.info("Handle incoming to create event")
|
||||||
|
|
||||||
with {:ok, %Actor{} = actor} <- Actors.get_or_fetch_by_url(data["actor"]) do
|
with {:ok, %Actor{} = actor} <- ActivityPub.get_or_fetch_by_url(data["actor"]) do
|
||||||
Logger.debug("found actor")
|
Logger.debug("found actor")
|
||||||
Logger.debug(inspect(actor))
|
Logger.debug(inspect(actor))
|
||||||
|
|
||||||
@ -187,8 +187,8 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
|
|||||||
def handle_incoming(
|
def handle_incoming(
|
||||||
%{"type" => "Follow", "object" => followed, "actor" => follower, "id" => id} = _data
|
%{"type" => "Follow", "object" => followed, "actor" => follower, "id" => id} = _data
|
||||||
) do
|
) do
|
||||||
with {:ok, %Actor{} = followed} <- Actors.get_or_fetch_by_url(followed, true),
|
with {:ok, %Actor{} = followed} <- ActivityPub.get_or_fetch_by_url(followed, true),
|
||||||
{:ok, %Actor{} = follower} <- Actors.get_or_fetch_by_url(follower),
|
{:ok, %Actor{} = follower} <- ActivityPub.get_or_fetch_by_url(follower),
|
||||||
{:ok, activity, object} <- ActivityPub.follow(follower, followed, id, false) do
|
{:ok, activity, object} <- ActivityPub.follow(follower, followed, id, false) do
|
||||||
{:ok, activity, object}
|
{:ok, activity, object}
|
||||||
else
|
else
|
||||||
@ -207,7 +207,7 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
|
|||||||
} = data
|
} = data
|
||||||
) do
|
) do
|
||||||
with actor_url <- get_actor(data),
|
with actor_url <- get_actor(data),
|
||||||
{:ok, %Actor{} = actor} <- Actors.get_or_fetch_by_url(actor_url),
|
{:ok, %Actor{} = actor} <- ActivityPub.get_or_fetch_by_url(actor_url),
|
||||||
{:object_not_found, {:ok, activity, object}} <-
|
{:object_not_found, {:ok, activity, object}} <-
|
||||||
{:object_not_found,
|
{:object_not_found,
|
||||||
do_handle_incoming_accept_following(accepted_object, actor) ||
|
do_handle_incoming_accept_following(accepted_object, actor) ||
|
||||||
@ -236,7 +236,7 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
|
|||||||
%{"type" => "Reject", "object" => rejected_object, "actor" => _actor, "id" => id} = data
|
%{"type" => "Reject", "object" => rejected_object, "actor" => _actor, "id" => id} = data
|
||||||
) do
|
) do
|
||||||
with actor_url <- get_actor(data),
|
with actor_url <- get_actor(data),
|
||||||
{:ok, %Actor{} = actor} <- Actors.get_or_fetch_by_url(actor_url),
|
{:ok, %Actor{} = actor} <- ActivityPub.get_or_fetch_by_url(actor_url),
|
||||||
{:object_not_found, {:ok, activity, object}} <-
|
{:object_not_found, {:ok, activity, object}} <-
|
||||||
{:object_not_found,
|
{:object_not_found,
|
||||||
do_handle_incoming_reject_following(rejected_object, actor) ||
|
do_handle_incoming_reject_following(rejected_object, actor) ||
|
||||||
@ -279,7 +279,7 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
|
|||||||
%{"type" => "Announce", "object" => object_id, "actor" => _actor, "id" => id} = data
|
%{"type" => "Announce", "object" => object_id, "actor" => _actor, "id" => id} = data
|
||||||
) do
|
) do
|
||||||
with actor <- get_actor(data),
|
with actor <- get_actor(data),
|
||||||
{:ok, %Actor{} = actor} <- Actors.get_or_fetch_by_url(actor),
|
{:ok, %Actor{} = actor} <- ActivityPub.get_or_fetch_by_url(actor),
|
||||||
{:ok, object} <- fetch_obj_helper_as_activity_streams(object_id),
|
{:ok, object} <- fetch_obj_helper_as_activity_streams(object_id),
|
||||||
public <- Visibility.is_public?(data),
|
public <- Visibility.is_public?(data),
|
||||||
{:ok, activity, object} <- ActivityPub.announce(actor, object, id, false, public) do
|
{:ok, activity, object} <- ActivityPub.announce(actor, object, id, false, public) do
|
||||||
@ -347,7 +347,7 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
|
|||||||
} = data
|
} = data
|
||||||
) do
|
) do
|
||||||
with actor <- get_actor(data),
|
with actor <- get_actor(data),
|
||||||
{:ok, %Actor{} = actor} <- Actors.get_or_fetch_by_url(actor),
|
{:ok, %Actor{} = actor} <- ActivityPub.get_or_fetch_by_url(actor),
|
||||||
{:ok, object} <- fetch_obj_helper_as_activity_streams(object_id),
|
{:ok, object} <- fetch_obj_helper_as_activity_streams(object_id),
|
||||||
{:ok, activity, object} <-
|
{:ok, activity, object} <-
|
||||||
ActivityPub.unannounce(actor, object, id, cancelled_activity_id, false) do
|
ActivityPub.unannounce(actor, object, id, cancelled_activity_id, false) do
|
||||||
@ -451,7 +451,7 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
|
|||||||
# } = data
|
# } = data
|
||||||
# ) do
|
# ) do
|
||||||
# with actor <- get_actor(data),
|
# with actor <- get_actor(data),
|
||||||
# %Actor{} = actor <- Actors.get_or_fetch_by_url(actor),
|
# %Actor{} = actor <- ActivityPub.get_or_fetch_by_url(actor),
|
||||||
# {:ok, object} <- fetch_obj_helper(object_id) || fetch_obj_helper(object_id),
|
# {:ok, object} <- fetch_obj_helper(object_id) || fetch_obj_helper(object_id),
|
||||||
# {:ok, activity, _, _} <- ActivityPub.unlike(actor, object, id, false) do
|
# {:ok, activity, _, _} <- ActivityPub.unlike(actor, object, id, false) do
|
||||||
# {:ok, activity}
|
# {:ok, activity}
|
||||||
|
@ -44,7 +44,7 @@ defmodule Mobilizon.Service.Export.Feed do
|
|||||||
@spec fetch_actor_event_feed(String.t()) :: String.t()
|
@spec fetch_actor_event_feed(String.t()) :: String.t()
|
||||||
defp fetch_actor_event_feed(name) do
|
defp fetch_actor_event_feed(name) do
|
||||||
with %Actor{} = actor <- Actors.get_local_actor_by_name(name),
|
with %Actor{} = actor <- Actors.get_local_actor_by_name(name),
|
||||||
{:visibility, true} <- {:visibility, Actor.public_visibility?(actor)},
|
{:visibility, true} <- {:visibility, Actor.is_public_visibility(actor)},
|
||||||
{:ok, events, _count} <- Events.get_public_events_for_actor(actor) do
|
{:ok, events, _count} <- Events.get_public_events_for_actor(actor) do
|
||||||
{:ok, build_actor_feed(actor, events)}
|
{:ok, build_actor_feed(actor, events)}
|
||||||
else
|
else
|
||||||
|
@ -44,7 +44,7 @@ defmodule Mobilizon.Service.Export.ICalendar do
|
|||||||
"""
|
"""
|
||||||
@spec export_public_actor(Actor.t()) :: String.t()
|
@spec export_public_actor(Actor.t()) :: String.t()
|
||||||
def export_public_actor(%Actor{} = actor) do
|
def export_public_actor(%Actor{} = actor) do
|
||||||
with true <- Actor.public_visibility?(actor),
|
with true <- Actor.is_public_visibility(actor),
|
||||||
{:ok, events, _} <- Events.get_public_events_for_actor(actor) do
|
{:ok, events, _} <- Events.get_public_events_for_actor(actor) do
|
||||||
{:ok, %ICalendar{events: events |> Enum.map(&do_export_event/1)} |> ICalendar.to_ics()}
|
{:ok, %ICalendar{events: events |> Enum.map(&do_export_event/1)} |> ICalendar.to_ics()}
|
||||||
end
|
end
|
||||||
|
@ -7,20 +7,23 @@ defmodule Mobilizon.Service.HTTPSignatures.Signature do
|
|||||||
@moduledoc """
|
@moduledoc """
|
||||||
Adapter for the `HTTPSignatures` lib that handles signing and providing public keys to verify HTTPSignatures
|
Adapter for the `HTTPSignatures` lib that handles signing and providing public keys to verify HTTPSignatures
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@behaviour HTTPSignatures.Adapter
|
@behaviour HTTPSignatures.Adapter
|
||||||
|
|
||||||
alias Mobilizon.Actors.Actor
|
alias Mobilizon.Actors.Actor
|
||||||
alias Mobilizon.Service.ActivityPub
|
alias Mobilizon.Service.ActivityPub
|
||||||
|
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
def key_id_to_actor_url(key_id) do
|
def key_id_to_actor_url(key_id) do
|
||||||
uri =
|
%{path: path} = uri =
|
||||||
URI.parse(key_id)
|
key_id
|
||||||
|
|> URI.parse()
|
||||||
|> Map.put(:fragment, nil)
|
|> Map.put(:fragment, nil)
|
||||||
|
|
||||||
uri =
|
uri =
|
||||||
if not is_nil(uri.path) and String.ends_with?(uri.path, "/publickey") do
|
if not is_nil(path) do
|
||||||
Map.put(uri, :path, String.replace(uri.path, "/publickey", ""))
|
Map.put(uri, :path, String.trim_trailing(path, "/publickey"))
|
||||||
else
|
else
|
||||||
uri
|
uri
|
||||||
end
|
end
|
||||||
@ -28,11 +31,47 @@ defmodule Mobilizon.Service.HTTPSignatures.Signature do
|
|||||||
URI.to_string(uri)
|
URI.to_string(uri)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Convert internal PEM encoded keys to public key format.
|
||||||
|
"""
|
||||||
|
@spec prepare_public_key(String.t()) :: {:ok, tuple} | {:error, :pem_decode_error}
|
||||||
|
def prepare_public_key(public_key_code) do
|
||||||
|
case :public_key.pem_decode(public_key_code) do
|
||||||
|
[public_key_entry] ->
|
||||||
|
{:ok, :public_key.pem_entry_decode(public_key_entry)}
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
{:error, :pem_decode_error}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Gets a public key for a given ActivityPub actor ID (url).
|
||||||
|
"""
|
||||||
|
@spec get_public_key_for_url(String.t()) ::
|
||||||
|
{:ok, String.t()} | {:error, :actor_fetch_error | :pem_decode_error}
|
||||||
|
def get_public_key_for_url(url) do
|
||||||
|
with {:ok, %Actor{keys: keys}} <- ActivityPub.get_or_fetch_by_url(url),
|
||||||
|
{:ok, public_key} <- prepare_public_key(keys) do
|
||||||
|
{:ok, public_key}
|
||||||
|
else
|
||||||
|
{:error, :pem_decode_error} ->
|
||||||
|
Logger.error("Error while decoding PEM")
|
||||||
|
|
||||||
|
{:error, :pem_decode_error}
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
Logger.error("Unable to fetch actor, so no keys for you")
|
||||||
|
|
||||||
|
{:error, :actor_fetch_error}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def fetch_public_key(conn) do
|
def fetch_public_key(conn) do
|
||||||
with %{"keyId" => kid} <- HTTPSignatures.signature_for_conn(conn),
|
with %{"keyId" => kid} <- HTTPSignatures.signature_for_conn(conn),
|
||||||
actor_id <- key_id_to_actor_url(kid),
|
actor_id <- key_id_to_actor_url(kid),
|
||||||
:ok <- Logger.debug("Fetching public key for #{actor_id}"),
|
:ok <- Logger.debug("Fetching public key for #{actor_id}"),
|
||||||
{:ok, public_key} <- Actor.get_public_key_for_url(actor_id) do
|
{:ok, public_key} <- get_public_key_for_url(actor_id) do
|
||||||
{:ok, public_key}
|
{:ok, public_key}
|
||||||
else
|
else
|
||||||
e ->
|
e ->
|
||||||
@ -45,7 +84,7 @@ defmodule Mobilizon.Service.HTTPSignatures.Signature do
|
|||||||
actor_id <- key_id_to_actor_url(kid),
|
actor_id <- key_id_to_actor_url(kid),
|
||||||
:ok <- Logger.debug("Refetching public key for #{actor_id}"),
|
:ok <- Logger.debug("Refetching public key for #{actor_id}"),
|
||||||
{:ok, _actor} <- ActivityPub.make_actor_from_url(actor_id),
|
{:ok, _actor} <- ActivityPub.make_actor_from_url(actor_id),
|
||||||
{:ok, public_key} <- Actor.get_public_key_for_url(actor_id) do
|
{:ok, public_key} <- get_public_key_for_url(actor_id) do
|
||||||
{:ok, public_key}
|
{:ok, public_key}
|
||||||
else
|
else
|
||||||
e ->
|
e ->
|
||||||
@ -53,12 +92,12 @@ defmodule Mobilizon.Service.HTTPSignatures.Signature do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def sign(%Actor{} = actor, headers) do
|
def sign(%Actor{keys: keys} = actor, headers) do
|
||||||
Logger.debug("Signing on behalf of #{actor.url}")
|
Logger.debug("Signing on behalf of #{actor.url}")
|
||||||
Logger.debug("headers")
|
Logger.debug("headers")
|
||||||
Logger.debug(inspect(headers))
|
Logger.debug(inspect(headers))
|
||||||
|
|
||||||
with {:ok, key} <- actor.keys |> Actor.prepare_public_key() do
|
with {:ok, key} <- prepare_public_key(keys) do
|
||||||
HTTPSignatures.sign(key, actor.url <> "#main-key", headers)
|
HTTPSignatures.sign(key, actor.url <> "#main-key", headers)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
6
mix.exs
6
mix.exs
@ -184,9 +184,9 @@ defmodule Mobilizon.Mixfile do
|
|||||||
Models: [
|
Models: [
|
||||||
Mobilizon.Actors,
|
Mobilizon.Actors,
|
||||||
Mobilizon.Actors.Actor,
|
Mobilizon.Actors.Actor,
|
||||||
Mobilizon.Actors.ActorOpennessEnum,
|
Mobilizon.Actors.ActorOpenness,
|
||||||
Mobilizon.Actors.ActorTypeEnum,
|
Mobilizon.Actors.ActorType,
|
||||||
Mobilizon.Actors.MemberRoleEnum,
|
Mobilizon.Actors.MemberRole,
|
||||||
Mobilizon.Actors.Bot,
|
Mobilizon.Actors.Bot,
|
||||||
Mobilizon.Actors.Follower,
|
Mobilizon.Actors.Follower,
|
||||||
Mobilizon.Actors.Member,
|
Mobilizon.Actors.Member,
|
||||||
|
@ -18,7 +18,7 @@ defmodule Mobilizon.Repo.Migrations.MoveFromAccountToActor do
|
|||||||
|
|
||||||
drop(table("groups"))
|
drop(table("groups"))
|
||||||
rename(table("accounts"), to: table("actors"))
|
rename(table("accounts"), to: table("actors"))
|
||||||
Mobilizon.Actors.ActorTypeEnum.create_type()
|
Mobilizon.Actors.ActorType.create_type()
|
||||||
rename(table("actors"), :username, to: :name)
|
rename(table("actors"), :username, to: :name)
|
||||||
rename(table("actors"), :description, to: :summary)
|
rename(table("actors"), :description, to: :summary)
|
||||||
rename(table("actors"), :display_name, to: :preferred_username)
|
rename(table("actors"), :display_name, to: :preferred_username)
|
||||||
@ -86,7 +86,7 @@ defmodule Mobilizon.Repo.Migrations.MoveFromAccountToActor do
|
|||||||
modify(:display_name, :string, null: true)
|
modify(:display_name, :string, null: true)
|
||||||
end
|
end
|
||||||
|
|
||||||
Mobilizon.Actors.ActorTypeEnum.drop_type()
|
Mobilizon.Actors.ActorType.drop_type()
|
||||||
|
|
||||||
rename(table("events"), :organizer_actor_id, to: :organizer_account_id)
|
rename(table("events"), :organizer_actor_id, to: :organizer_account_id)
|
||||||
|
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
defmodule Mobilizon.Repo.Migrations.MoveMemberRoleToEnum do
|
defmodule Mobilizon.Repo.Migrations.MoveMemberRoleToEnum do
|
||||||
use Ecto.Migration
|
use Ecto.Migration
|
||||||
alias Mobilizon.Actors.MemberRoleEnum
|
alias Mobilizon.Actors.MemberRole
|
||||||
|
|
||||||
def up do
|
def up do
|
||||||
MemberRoleEnum.create_type()
|
MemberRole.create_type()
|
||||||
|
|
||||||
alter table(:members) do
|
alter table(:members) do
|
||||||
add(:role_tmp, MemberRoleEnum.type(), default: "member")
|
add(:role_tmp, MemberRole.type(), default: "member")
|
||||||
end
|
end
|
||||||
|
|
||||||
execute("UPDATE members set role_tmp = 'member' where role = 0")
|
execute("UPDATE members set role_tmp = 'member' where role = 0")
|
||||||
@ -39,7 +39,7 @@ defmodule Mobilizon.Repo.Migrations.MoveMemberRoleToEnum do
|
|||||||
remove(:role)
|
remove(:role)
|
||||||
end
|
end
|
||||||
|
|
||||||
MemberRoleEnum.drop_type()
|
MemberRole.drop_type()
|
||||||
|
|
||||||
rename(table(:members), :role_tmp, to: :role)
|
rename(table(:members), :role_tmp, to: :role)
|
||||||
end
|
end
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
defmodule Mobilizon.Repo.Migrations.ActorGroupOpenness do
|
defmodule Mobilizon.Repo.Migrations.ActorGroupOpenness do
|
||||||
use Ecto.Migration
|
use Ecto.Migration
|
||||||
alias Mobilizon.Actors.ActorOpennessEnum
|
alias Mobilizon.Actors.ActorOpenness
|
||||||
|
|
||||||
def up do
|
def up do
|
||||||
ActorOpennessEnum.create_type()
|
ActorOpenness.create_type()
|
||||||
|
|
||||||
alter table(:actors) do
|
alter table(:actors) do
|
||||||
add(:openness, ActorOpennessEnum.type(), default: "moderated")
|
add(:openness, ActorOpenness.type(), default: "moderated")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
defmodule Mobilizon.Repo.Migrations.AddVisibilityToActor do
|
defmodule Mobilizon.Repo.Migrations.AddVisibilityToActor do
|
||||||
use Ecto.Migration
|
use Ecto.Migration
|
||||||
|
|
||||||
alias Mobilizon.Actors.ActorVisibilityEnum
|
alias Mobilizon.Actors.ActorVisibility
|
||||||
|
|
||||||
def up do
|
def up do
|
||||||
ActorVisibilityEnum.create_type()
|
ActorVisibility.create_type()
|
||||||
|
|
||||||
alter table(:actors) do
|
alter table(:actors) do
|
||||||
add(:visibility, ActorVisibilityEnum.type(), default: "private")
|
add(:visibility, ActorVisibility.type(), default: "private")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -16,6 +16,6 @@ defmodule Mobilizon.Repo.Migrations.AddVisibilityToActor do
|
|||||||
remove(:visibility)
|
remove(:visibility)
|
||||||
end
|
end
|
||||||
|
|
||||||
ActorVisibilityEnum.drop_type()
|
ActorVisibility.drop_type()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,11 +1,15 @@
|
|||||||
defmodule Mobilizon.ActorsTest do
|
defmodule Mobilizon.ActorsTest do
|
||||||
|
use ExVCR.Mock, adapter: ExVCR.Adapter.Hackney
|
||||||
|
|
||||||
use Mobilizon.DataCase
|
use Mobilizon.DataCase
|
||||||
|
|
||||||
|
import Mobilizon.Factory
|
||||||
|
|
||||||
alias Mobilizon.{Actors, Config, Users}
|
alias Mobilizon.{Actors, Config, Users}
|
||||||
alias Mobilizon.Actors.{Actor, Member, Follower, Bot}
|
alias Mobilizon.Actors.{Actor, Member, Follower, Bot}
|
||||||
alias Mobilizon.Media.File, as: FileModel
|
alias Mobilizon.Media.File, as: FileModel
|
||||||
import Mobilizon.Factory
|
alias Mobilizon.Service.ActivityPub
|
||||||
use ExVCR.Mock, adapter: ExVCR.Adapter.Hackney
|
alias Mobilizon.Storage.Page
|
||||||
|
|
||||||
describe "actors" do
|
describe "actors" do
|
||||||
@valid_attrs %{
|
@valid_attrs %{
|
||||||
@ -40,8 +44,6 @@ defmodule Mobilizon.ActorsTest do
|
|||||||
}
|
}
|
||||||
|
|
||||||
@remote_account_url "https://social.tcit.fr/users/tcit"
|
@remote_account_url "https://social.tcit.fr/users/tcit"
|
||||||
@remote_account_username "tcit"
|
|
||||||
@remote_account_domain "social.tcit.fr"
|
|
||||||
|
|
||||||
setup do
|
setup do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
@ -70,14 +72,14 @@ defmodule Mobilizon.ActorsTest do
|
|||||||
assert actor_id == Users.get_actor_for_user(user).id
|
assert actor_id == Users.get_actor_for_user(user).id
|
||||||
end
|
end
|
||||||
|
|
||||||
test "get_actor_with_everything/1 returns the actor with it's organized events", %{
|
test "get_actor_with_preload/1 returns the actor with it's organized events", %{
|
||||||
actor: actor
|
actor: actor
|
||||||
} do
|
} do
|
||||||
assert Actors.get_actor_with_everything(actor.id).organized_events == []
|
assert Actors.get_actor_with_preload(actor.id).organized_events == []
|
||||||
event = insert(:event, organizer_actor: actor)
|
event = insert(:event, organizer_actor: actor)
|
||||||
|
|
||||||
event_found_id =
|
event_found_id =
|
||||||
Actors.get_actor_with_everything(actor.id).organized_events |> hd |> Map.get(:id)
|
Actors.get_actor_with_preload(actor.id).organized_events |> hd |> Map.get(:id)
|
||||||
|
|
||||||
assert event_found_id == event.id
|
assert event_found_id == event.id
|
||||||
end
|
end
|
||||||
@ -97,7 +99,7 @@ defmodule Mobilizon.ActorsTest do
|
|||||||
preferred_username: preferred_username,
|
preferred_username: preferred_username,
|
||||||
domain: domain,
|
domain: domain,
|
||||||
avatar: %FileModel{name: picture_name} = _picture
|
avatar: %FileModel{name: picture_name} = _picture
|
||||||
} = _actor} = Actors.get_or_fetch_by_url(@remote_account_url)
|
} = _actor} = ActivityPub.get_or_fetch_by_url(@remote_account_url)
|
||||||
|
|
||||||
assert picture_name == "avatar"
|
assert picture_name == "avatar"
|
||||||
|
|
||||||
@ -111,51 +113,51 @@ defmodule Mobilizon.ActorsTest do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test "get_local_actor_by_name_with_everything!/1 returns the local actor with it's organized events",
|
test "get_local_actor_by_name_with_preload!/1 returns the local actor with it's organized events",
|
||||||
%{
|
%{
|
||||||
actor: actor
|
actor: actor
|
||||||
} do
|
} do
|
||||||
assert Actors.get_local_actor_by_name_with_everything(actor.preferred_username).organized_events ==
|
assert Actors.get_local_actor_by_name_with_preload(actor.preferred_username).organized_events ==
|
||||||
[]
|
[]
|
||||||
|
|
||||||
event = insert(:event, organizer_actor: actor)
|
event = insert(:event, organizer_actor: actor)
|
||||||
|
|
||||||
event_found_id =
|
event_found_id =
|
||||||
Actors.get_local_actor_by_name_with_everything(actor.preferred_username).organized_events
|
Actors.get_local_actor_by_name_with_preload(actor.preferred_username).organized_events
|
||||||
|> hd
|
|> hd
|
||||||
|> Map.get(:id)
|
|> Map.get(:id)
|
||||||
|
|
||||||
assert event_found_id == event.id
|
assert event_found_id == event.id
|
||||||
end
|
end
|
||||||
|
|
||||||
test "get_actor_by_name_with_everything!/1 returns the local actor with it's organized events",
|
test "get_actor_by_name_with_preload!/1 returns the local actor with it's organized events",
|
||||||
%{
|
%{
|
||||||
actor: actor
|
actor: actor
|
||||||
} do
|
} do
|
||||||
assert Actors.get_actor_by_name_with_everything(actor.preferred_username).organized_events ==
|
assert Actors.get_actor_by_name_with_preload(actor.preferred_username).organized_events ==
|
||||||
[]
|
[]
|
||||||
|
|
||||||
event = insert(:event, organizer_actor: actor)
|
event = insert(:event, organizer_actor: actor)
|
||||||
|
|
||||||
event_found_id =
|
event_found_id =
|
||||||
Actors.get_actor_by_name_with_everything(actor.preferred_username).organized_events
|
Actors.get_actor_by_name_with_preload(actor.preferred_username).organized_events
|
||||||
|> hd
|
|> hd
|
||||||
|> Map.get(:id)
|
|> Map.get(:id)
|
||||||
|
|
||||||
assert event_found_id == event.id
|
assert event_found_id == event.id
|
||||||
end
|
end
|
||||||
|
|
||||||
test "get_actor_by_name_with_everything!/1 returns the remote actor with it's organized events" do
|
test "get_actor_by_name_with_preload!/1 returns the remote actor with it's organized events" do
|
||||||
use_cassette "actors/remote_actor_mastodon_tcit" do
|
use_cassette "actors/remote_actor_mastodon_tcit" do
|
||||||
with {:ok, %Actor{} = actor} <- Actors.get_or_fetch_by_url(@remote_account_url) do
|
with {:ok, %Actor{} = actor} <- ActivityPub.get_or_fetch_by_url(@remote_account_url) do
|
||||||
assert Actors.get_actor_by_name_with_everything(
|
assert Actors.get_actor_by_name_with_preload(
|
||||||
"#{actor.preferred_username}@#{actor.domain}"
|
"#{actor.preferred_username}@#{actor.domain}"
|
||||||
).organized_events == []
|
).organized_events == []
|
||||||
|
|
||||||
event = insert(:event, organizer_actor: actor)
|
event = insert(:event, organizer_actor: actor)
|
||||||
|
|
||||||
event_found_id =
|
event_found_id =
|
||||||
Actors.get_actor_by_name_with_everything(
|
Actors.get_actor_by_name_with_preload(
|
||||||
"#{actor.preferred_username}@#{actor.domain}"
|
"#{actor.preferred_username}@#{actor.domain}"
|
||||||
).organized_events
|
).organized_events
|
||||||
|> hd
|
|> hd
|
||||||
@ -166,42 +168,21 @@ defmodule Mobilizon.ActorsTest do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test "get_or_fetch_by_url/1 returns the local actor for the url", %{
|
test "test get_local_actor_by_username/1 returns local actors with similar usernames", %{
|
||||||
actor: %Actor{preferred_username: preferred_username} = actor
|
|
||||||
} do
|
|
||||||
with {:ok, %Actor{domain: domain} = actor} <- Actors.get_or_fetch_by_url(actor.url) do
|
|
||||||
assert preferred_username == actor.preferred_username
|
|
||||||
assert is_nil(domain)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
test "get_or_fetch_by_url/1 returns the remote actor for the url" do
|
|
||||||
use_cassette "actors/remote_actor_mastodon_tcit" do
|
|
||||||
with {:ok, %Actor{preferred_username: preferred_username, domain: domain}} <-
|
|
||||||
Actors.get_or_fetch_by_url!(@remote_account_url) do
|
|
||||||
assert preferred_username == @remote_account_username
|
|
||||||
assert domain == @remote_account_domain
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
test "test find_local_by_username/1 returns local actors with similar usernames", %{
|
|
||||||
actor: actor
|
actor: actor
|
||||||
} do
|
} do
|
||||||
actor2 = insert(:actor, preferred_username: "tcit")
|
actor2 = insert(:actor, preferred_username: "tcit")
|
||||||
[%Actor{id: actor_found_id} | tail] = Actors.find_local_by_username("tcit")
|
[%Actor{id: actor_found_id} | tail] = Actors.get_local_actor_by_username("tcit")
|
||||||
%Actor{id: actor2_found_id} = hd(tail)
|
%Actor{id: actor2_found_id} = hd(tail)
|
||||||
assert MapSet.new([actor_found_id, actor2_found_id]) == MapSet.new([actor.id, actor2.id])
|
assert MapSet.new([actor_found_id, actor2_found_id]) == MapSet.new([actor.id, actor2.id])
|
||||||
end
|
end
|
||||||
|
|
||||||
test "test find_and_count_actors_by_username_or_name/4 returns actors with similar usernames",
|
test "test build_actors_by_username_or_name_page/4 returns actors with similar usernames",
|
||||||
%{
|
%{actor: %Actor{id: actor_id}} do
|
||||||
actor: %Actor{id: actor_id}
|
|
||||||
} do
|
|
||||||
use_cassette "actors/remote_actor_mastodon_tcit" do
|
use_cassette "actors/remote_actor_mastodon_tcit" do
|
||||||
with {:ok, %Actor{id: actor2_id}} <- Actors.get_or_fetch_by_url(@remote_account_url) do
|
with {:ok, %Actor{id: actor2_id}} <- ActivityPub.get_or_fetch_by_url(@remote_account_url) do
|
||||||
%{total: 2, elements: actors} =
|
%Page{total: 2, elements: actors} =
|
||||||
Actors.find_and_count_actors_by_username_or_name("tcit", [:Person])
|
Actors.build_actors_by_username_or_name_page("tcit", [:Person])
|
||||||
|
|
||||||
actors_ids = actors |> Enum.map(& &1.id)
|
actors_ids = actors |> Enum.map(& &1.id)
|
||||||
|
|
||||||
@ -210,35 +191,13 @@ defmodule Mobilizon.ActorsTest do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test "test find_and_count_actors_by_username_or_name/4 returns actors with similar names" do
|
test "test build_actors_by_username_or_name_page/4 returns actors with similar names" do
|
||||||
%{total: 0, elements: actors} =
|
%{total: 0, elements: actors} =
|
||||||
Actors.find_and_count_actors_by_username_or_name("ohno", [:Person])
|
Actors.build_actors_by_username_or_name_page("ohno", [:Person])
|
||||||
|
|
||||||
assert actors == []
|
assert actors == []
|
||||||
end
|
end
|
||||||
|
|
||||||
test "test get_public_key_for_url/1 with local actor", %{actor: actor} do
|
|
||||||
assert Actor.get_public_key_for_url(actor.url) ==
|
|
||||||
actor.keys |> Mobilizon.Actors.Actor.prepare_public_key()
|
|
||||||
end
|
|
||||||
|
|
||||||
@remote_actor_key {:ok,
|
|
||||||
{:RSAPublicKey,
|
|
||||||
20_890_513_599_005_517_665_557_846_902_571_022_168_782_075_040_010_449_365_706_450_877_170_130_373_892_202_874_869_873_999_284_399_697_282_332_064_948_148_602_583_340_776_692_090_472_558_740_998_357_203_838_580_321_412_679_020_304_645_826_371_196_718_081_108_049_114_160_630_664_514_340_729_769_453_281_682_773_898_619_827_376_232_969_899_348_462_205_389_310_883_299_183_817_817_999_273_916_446_620_095_414_233_374_619_948_098_516_821_650_069_821_783_810_210_582_035_456_563_335_930_330_252_551_528_035_801_173_640_288_329_718_719_895_926_309_416_142_129_926_226_047_930_429_802_084_560_488_897_717_417_403_272_782_469_039_131_379_953_278_833_320_195_233_761_955_815_307_522_871_787_339_192_744_439_894_317_730_207_141_881_699_363_391_788_150_650_217_284_777_541_358_381_165_360_697_136_307_663_640_904_621_178_632_289_787,
|
|
||||||
65_537}}
|
|
||||||
test "test get_public_key_for_url/1 with remote actor" do
|
|
||||||
use_cassette "actors/remote_actor_mastodon_tcit" do
|
|
||||||
assert Actor.get_public_key_for_url(@remote_account_url) == @remote_actor_key
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
test "test get_public_key_for_url/1 with remote actor and bad key" do
|
|
||||||
use_cassette "actors/remote_actor_mastodon_tcit_actor_deleted" do
|
|
||||||
assert Actor.get_public_key_for_url(@remote_account_url) ==
|
|
||||||
{:error, :actor_fetch_error}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
test "create_actor/1 with valid data creates a actor" do
|
test "create_actor/1 with valid data creates a actor" do
|
||||||
assert {:ok, %Actor{} = actor} = Actors.create_actor(@valid_attrs)
|
assert {:ok, %Actor{} = actor} = Actors.create_actor(@valid_attrs)
|
||||||
assert actor.summary == "some description"
|
assert actor.summary == "some description"
|
||||||
@ -351,10 +310,6 @@ defmodule Mobilizon.ActorsTest do
|
|||||||
"/" <> banner_path
|
"/" <> banner_path
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "change_actor/1 returns a actor changeset", %{actor: actor} do
|
|
||||||
assert %Ecto.Changeset{} = Actors.change_actor(actor)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "groups" do
|
describe "groups" do
|
||||||
@ -505,8 +460,8 @@ defmodule Mobilizon.ActorsTest do
|
|||||||
assert {:ok, %Follower{} = follower} = Actors.create_follower(valid_attrs)
|
assert {:ok, %Follower{} = follower} = Actors.create_follower(valid_attrs)
|
||||||
assert follower.approved == true
|
assert follower.approved == true
|
||||||
|
|
||||||
assert %{total: 1, elements: [target_actor]} = Actor.get_followings(actor)
|
assert %{total: 1, elements: [target_actor]} = Actors.get_followings(actor)
|
||||||
assert %{total: 1, elements: [actor]} = Actor.get_followers(target_actor)
|
assert %{total: 1, elements: [actor]} = Actors.get_followers(target_actor)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "create_follower/1 with valid data but same actors fails to create a follower", %{
|
test "create_follower/1 with valid data but same actors fails to create a follower", %{
|
||||||
@ -554,33 +509,28 @@ defmodule Mobilizon.ActorsTest do
|
|||||||
assert_raise Ecto.NoResultsError, fn -> Actors.get_follower!(follower.id) end
|
assert_raise Ecto.NoResultsError, fn -> Actors.get_follower!(follower.id) end
|
||||||
end
|
end
|
||||||
|
|
||||||
test "change_follower/1 returns a follower changeset", context do
|
|
||||||
follower = create_test_follower(context)
|
|
||||||
assert %Ecto.Changeset{} = Actors.change_follower(follower)
|
|
||||||
end
|
|
||||||
|
|
||||||
test "follow/3 makes an actor follow another", %{actor: actor, target_actor: target_actor} do
|
test "follow/3 makes an actor follow another", %{actor: actor, target_actor: target_actor} do
|
||||||
# Preloading followers/followings
|
# Preloading followers/followings
|
||||||
actor = Actors.get_actor_with_everything(actor.id)
|
actor = Actors.get_actor_with_preload(actor.id)
|
||||||
target_actor = Actors.get_actor_with_everything(target_actor.id)
|
target_actor = Actors.get_actor_with_preload(target_actor.id)
|
||||||
|
|
||||||
{:ok, follower} = Actor.follow(target_actor, actor)
|
{:ok, follower} = Actors.follow(target_actor, actor)
|
||||||
assert follower.actor.id == actor.id
|
assert follower.actor.id == actor.id
|
||||||
|
|
||||||
# Referesh followers/followings
|
# Referesh followers/followings
|
||||||
actor = Actors.get_actor_with_everything(actor.id)
|
actor = Actors.get_actor_with_preload(actor.id)
|
||||||
target_actor = Actors.get_actor_with_everything(target_actor.id)
|
target_actor = Actors.get_actor_with_preload(target_actor.id)
|
||||||
|
|
||||||
assert target_actor.followers |> Enum.map(& &1.actor_id) == [actor.id]
|
assert target_actor.followers |> Enum.map(& &1.actor_id) == [actor.id]
|
||||||
assert actor.followings |> Enum.map(& &1.target_actor_id) == [target_actor.id]
|
assert actor.followings |> Enum.map(& &1.target_actor_id) == [target_actor.id]
|
||||||
|
|
||||||
# Test if actor is already following target actor
|
# Test if actor is already following target actor
|
||||||
assert {:error, :already_following, msg} = Actor.follow(target_actor, actor)
|
assert {:error, :already_following, msg} = Actors.follow(target_actor, actor)
|
||||||
assert msg =~ "already following"
|
assert msg =~ "already following"
|
||||||
|
|
||||||
# Test if target actor is suspended
|
# Test if target actor is suspended
|
||||||
target_actor = %{target_actor | suspended: true}
|
target_actor = %{target_actor | suspended: true}
|
||||||
assert {:error, :suspended, msg} = Actor.follow(target_actor, actor)
|
assert {:error, :suspended, msg} = Actors.follow(target_actor, actor)
|
||||||
assert msg =~ "suspended"
|
assert msg =~ "suspended"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -620,8 +570,8 @@ defmodule Mobilizon.ActorsTest do
|
|||||||
assert {:ok, %Member{} = member} = Actors.create_member(valid_attrs)
|
assert {:ok, %Member{} = member} = Actors.create_member(valid_attrs)
|
||||||
assert member.role == :member
|
assert member.role == :member
|
||||||
|
|
||||||
assert [group] = Actor.get_groups_member_of(actor)
|
assert [group] = Actors.get_groups_member_of(actor)
|
||||||
assert [actor] = Actor.get_members_for_group(group)
|
assert [actor] = Actors.get_members_for_group(group)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "create_member/1 with valid data but same actors fails to create a member", %{
|
test "create_member/1 with valid data but same actors fails to create a member", %{
|
||||||
@ -666,10 +616,5 @@ defmodule Mobilizon.ActorsTest do
|
|||||||
assert {:ok, %Member{}} = Actors.delete_member(member)
|
assert {:ok, %Member{}} = Actors.delete_member(member)
|
||||||
assert_raise Ecto.NoResultsError, fn -> Actors.get_member!(member.id) end
|
assert_raise Ecto.NoResultsError, fn -> Actors.get_member!(member.id) end
|
||||||
end
|
end
|
||||||
|
|
||||||
test "change_member/1 returns a member changeset", context do
|
|
||||||
member = create_test_member(context)
|
|
||||||
assert %Ecto.Changeset{} = Actors.change_member(member)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -11,7 +11,6 @@ defmodule Mobilizon.Service.ActivityPub.ActivityPubTest do
|
|||||||
alias Mobilizon.Events
|
alias Mobilizon.Events
|
||||||
alias Mobilizon.Events.Event
|
alias Mobilizon.Events.Event
|
||||||
alias Mobilizon.Actors.Actor
|
alias Mobilizon.Actors.Actor
|
||||||
alias Mobilizon.Actors
|
|
||||||
alias Mobilizon.Service.HTTPSignatures.Signature
|
alias Mobilizon.Service.HTTPSignatures.Signature
|
||||||
alias Mobilizon.Service.ActivityPub
|
alias Mobilizon.Service.ActivityPub
|
||||||
use ExVCR.Mock, adapter: ExVCR.Adapter.Hackney
|
use ExVCR.Mock, adapter: ExVCR.Adapter.Hackney
|
||||||
@ -48,7 +47,7 @@ defmodule Mobilizon.Service.ActivityPub.ActivityPubTest do
|
|||||||
test "returns an actor from url" do
|
test "returns an actor from url" do
|
||||||
use_cassette "activity_pub/fetch_framapiaf.org_users_tcit" do
|
use_cassette "activity_pub/fetch_framapiaf.org_users_tcit" do
|
||||||
assert {:ok, %Actor{preferred_username: "tcit", domain: "framapiaf.org"}} =
|
assert {:ok, %Actor{preferred_username: "tcit", domain: "framapiaf.org"}} =
|
||||||
Actors.get_or_fetch_by_url("https://framapiaf.org/users/tcit")
|
ActivityPub.get_or_fetch_by_url("https://framapiaf.org/users/tcit")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -222,8 +222,8 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do
|
|||||||
assert data["type"] == "Follow"
|
assert data["type"] == "Follow"
|
||||||
assert data["id"] == "https://social.tcit.fr/users/tcit#follows/2"
|
assert data["id"] == "https://social.tcit.fr/users/tcit#follows/2"
|
||||||
|
|
||||||
actor = Actors.get_actor_with_everything(actor.id)
|
actor = Actors.get_actor_with_preload(actor.id)
|
||||||
assert Actor.following?(Actors.get_actor_by_url!(data["actor"], true), actor)
|
assert Actors.following?(Actors.get_actor_by_url!(data["actor"], true), actor)
|
||||||
end
|
end
|
||||||
|
|
||||||
# test "it works for incoming follow requests from hubzilla" do
|
# test "it works for incoming follow requests from hubzilla" do
|
||||||
@ -498,7 +498,7 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do
|
|||||||
assert data["actor"] == "https://social.tcit.fr/users/tcit"
|
assert data["actor"] == "https://social.tcit.fr/users/tcit"
|
||||||
|
|
||||||
{:ok, followed} = Actors.get_actor_by_url(data["actor"])
|
{:ok, followed} = Actors.get_actor_by_url(data["actor"])
|
||||||
refute Actor.following?(followed, actor)
|
refute Actors.following?(followed, actor)
|
||||||
end
|
end
|
||||||
|
|
||||||
# test "it works for incoming blocks" do
|
# test "it works for incoming blocks" do
|
||||||
@ -581,10 +581,10 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do
|
|||||||
follower = insert(:actor)
|
follower = insert(:actor)
|
||||||
followed = insert(:actor)
|
followed = insert(:actor)
|
||||||
|
|
||||||
refute Actor.following?(follower, followed)
|
refute Actors.following?(follower, followed)
|
||||||
|
|
||||||
{:ok, follow_activity, _} = ActivityPub.follow(follower, followed)
|
{:ok, follow_activity, _} = ActivityPub.follow(follower, followed)
|
||||||
assert Actor.following?(follower, followed)
|
assert Actors.following?(follower, followed)
|
||||||
|
|
||||||
accept_data =
|
accept_data =
|
||||||
File.read!("test/fixtures/mastodon-accept-activity.json")
|
File.read!("test/fixtures/mastodon-accept-activity.json")
|
||||||
@ -605,7 +605,7 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do
|
|||||||
|
|
||||||
{:ok, follower} = Actors.get_actor_by_url(follower.url)
|
{:ok, follower} = Actors.get_actor_by_url(follower.url)
|
||||||
|
|
||||||
assert Actor.following?(follower, followed)
|
assert Actors.following?(follower, followed)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it works for incoming accepts which are referenced by IRI only" do
|
test "it works for incoming accepts which are referenced by IRI only" do
|
||||||
@ -627,7 +627,7 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do
|
|||||||
|
|
||||||
{:ok, follower} = Actors.get_actor_by_url(follower.url)
|
{:ok, follower} = Actors.get_actor_by_url(follower.url)
|
||||||
|
|
||||||
assert Actor.following?(follower, followed)
|
assert Actors.following?(follower, followed)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it fails for incoming accepts which cannot be correlated" do
|
test "it fails for incoming accepts which cannot be correlated" do
|
||||||
@ -646,7 +646,7 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do
|
|||||||
|
|
||||||
{:ok, follower} = Actors.get_actor_by_url(follower.url)
|
{:ok, follower} = Actors.get_actor_by_url(follower.url)
|
||||||
|
|
||||||
refute Actor.following?(follower, followed)
|
refute Actors.following?(follower, followed)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it fails for incoming rejects which cannot be correlated" do
|
test "it fails for incoming rejects which cannot be correlated" do
|
||||||
@ -665,7 +665,7 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do
|
|||||||
|
|
||||||
{:ok, follower} = Actors.get_actor_by_url(follower.url)
|
{:ok, follower} = Actors.get_actor_by_url(follower.url)
|
||||||
|
|
||||||
refute Actor.following?(follower, followed)
|
refute Actors.following?(follower, followed)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it works for incoming rejects which are referenced by IRI only" do
|
test "it works for incoming rejects which are referenced by IRI only" do
|
||||||
@ -674,7 +674,7 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do
|
|||||||
|
|
||||||
{:ok, follow_activity, _} = ActivityPub.follow(follower, followed)
|
{:ok, follow_activity, _} = ActivityPub.follow(follower, followed)
|
||||||
|
|
||||||
assert Actor.following?(follower, followed)
|
assert Actors.following?(follower, followed)
|
||||||
|
|
||||||
reject_data =
|
reject_data =
|
||||||
File.read!("test/fixtures/mastodon-reject-activity.json")
|
File.read!("test/fixtures/mastodon-reject-activity.json")
|
||||||
@ -684,7 +684,7 @@ defmodule Mobilizon.Service.ActivityPub.TransmogrifierTest do
|
|||||||
|
|
||||||
{:ok, %Activity{data: _}, _} = Transmogrifier.handle_incoming(reject_data)
|
{:ok, %Activity{data: _}, _} = Transmogrifier.handle_incoming(reject_data)
|
||||||
|
|
||||||
refute Actor.following?(follower, followed)
|
refute Actors.following?(follower, followed)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it rejects activities without a valid ID" do
|
test "it rejects activities without a valid ID" do
|
||||||
|
@ -6,6 +6,8 @@ defmodule MobilizonWeb.API.SearchTest do
|
|||||||
alias Mobilizon.Actors
|
alias Mobilizon.Actors
|
||||||
alias Mobilizon.Actors.Actor
|
alias Mobilizon.Actors.Actor
|
||||||
alias Mobilizon.Service.ActivityPub
|
alias Mobilizon.Service.ActivityPub
|
||||||
|
alias Mobilizon.Storage.Page
|
||||||
|
|
||||||
alias MobilizonWeb.API.Search
|
alias MobilizonWeb.API.Search
|
||||||
|
|
||||||
import Mock
|
import Mock
|
||||||
@ -13,7 +15,7 @@ defmodule MobilizonWeb.API.SearchTest do
|
|||||||
test "search an user by username" do
|
test "search an user by username" do
|
||||||
with_mock ActivityPub,
|
with_mock ActivityPub,
|
||||||
find_or_make_actor_from_nickname: fn "toto@domain.tld" -> {:ok, %Actor{id: 42}} end do
|
find_or_make_actor_from_nickname: fn "toto@domain.tld" -> {:ok, %Actor{id: 42}} end do
|
||||||
assert {:ok, %{total: 1, elements: [%Actor{id: 42}]}} ==
|
assert {:ok, %Page{total: 1, elements: [%Actor{id: 42}]}} ==
|
||||||
Search.search_actors("toto@domain.tld", 1, 10, :Person)
|
Search.search_actors("toto@domain.tld", 1, 10, :Person)
|
||||||
|
|
||||||
assert_called(ActivityPub.find_or_make_actor_from_nickname("toto@domain.tld"))
|
assert_called(ActivityPub.find_or_make_actor_from_nickname("toto@domain.tld"))
|
||||||
@ -23,7 +25,7 @@ defmodule MobilizonWeb.API.SearchTest do
|
|||||||
test "search something by URL" do
|
test "search something by URL" do
|
||||||
with_mock ActivityPub,
|
with_mock ActivityPub,
|
||||||
fetch_object_from_url: fn "https://social.tcit.fr/users/tcit" -> {:ok, %Actor{id: 42}} end do
|
fetch_object_from_url: fn "https://social.tcit.fr/users/tcit" -> {:ok, %Actor{id: 42}} end do
|
||||||
assert {:ok, %{total: 1, elements: [%Actor{id: 42}]}} ==
|
assert {:ok, %Page{total: 1, elements: [%Actor{id: 42}]}} ==
|
||||||
Search.search_actors("https://social.tcit.fr/users/tcit", 1, 10, :Person)
|
Search.search_actors("https://social.tcit.fr/users/tcit", 1, 10, :Person)
|
||||||
|
|
||||||
assert_called(ActivityPub.fetch_object_from_url("https://social.tcit.fr/users/tcit"))
|
assert_called(ActivityPub.fetch_object_from_url("https://social.tcit.fr/users/tcit"))
|
||||||
@ -32,20 +34,20 @@ defmodule MobilizonWeb.API.SearchTest do
|
|||||||
|
|
||||||
test "search actors" do
|
test "search actors" do
|
||||||
with_mock Actors,
|
with_mock Actors,
|
||||||
find_and_count_actors_by_username_or_name: fn "toto", _type, 1, 10 ->
|
build_actors_by_username_or_name_page: fn "toto", _type, 1, 10 ->
|
||||||
%{total: 1, elements: [%Actor{id: 42}]}
|
%Page{total: 1, elements: [%Actor{id: 42}]}
|
||||||
end do
|
end do
|
||||||
assert {:ok, %{total: 1, elements: [%Actor{id: 42}]}} =
|
assert {:ok, %{total: 1, elements: [%Actor{id: 42}]}} =
|
||||||
Search.search_actors("toto", 1, 10, :Person)
|
Search.search_actors("toto", 1, 10, :Person)
|
||||||
|
|
||||||
assert_called(Actors.find_and_count_actors_by_username_or_name("toto", [:Person], 1, 10))
|
assert_called(Actors.build_actors_by_username_or_name_page("toto", [:Person], 1, 10))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test "search events" do
|
test "search events" do
|
||||||
with_mock Events,
|
with_mock Events,
|
||||||
find_and_count_events_by_name: fn "toto", 1, 10 ->
|
find_and_count_events_by_name: fn "toto", 1, 10 ->
|
||||||
%{total: 1, elements: [%Event{title: "super toto event"}]}
|
%Page{total: 1, elements: [%Event{title: "super toto event"}]}
|
||||||
end do
|
end do
|
||||||
assert {:ok, %{total: 1, elements: [%Event{title: "super toto event"}]}} =
|
assert {:ok, %{total: 1, elements: [%Event{title: "super toto event"}]}} =
|
||||||
Search.search_events("toto", 1, 10)
|
Search.search_events("toto", 1, 10)
|
||||||
|
@ -177,7 +177,7 @@ defmodule MobilizonWeb.ActivityPubControllerTest do
|
|||||||
test "it returns the followers in a collection", %{conn: conn} do
|
test "it returns the followers in a collection", %{conn: conn} do
|
||||||
actor = insert(:actor, visibility: :public)
|
actor = insert(:actor, visibility: :public)
|
||||||
actor2 = insert(:actor)
|
actor2 = insert(:actor)
|
||||||
Actor.follow(actor, actor2)
|
Actors.follow(actor, actor2)
|
||||||
|
|
||||||
result =
|
result =
|
||||||
conn
|
conn
|
||||||
@ -190,7 +190,7 @@ defmodule MobilizonWeb.ActivityPubControllerTest do
|
|||||||
test "it returns no followers for a private actor", %{conn: conn} do
|
test "it returns no followers for a private actor", %{conn: conn} do
|
||||||
actor = insert(:actor, visibility: :private)
|
actor = insert(:actor, visibility: :private)
|
||||||
actor2 = insert(:actor)
|
actor2 = insert(:actor)
|
||||||
Actor.follow(actor, actor2)
|
Actors.follow(actor, actor2)
|
||||||
|
|
||||||
result =
|
result =
|
||||||
conn
|
conn
|
||||||
@ -205,7 +205,7 @@ defmodule MobilizonWeb.ActivityPubControllerTest do
|
|||||||
|
|
||||||
Enum.each(1..15, fn _ ->
|
Enum.each(1..15, fn _ ->
|
||||||
other_actor = insert(:actor)
|
other_actor = insert(:actor)
|
||||||
Actor.follow(actor, other_actor)
|
Actors.follow(actor, other_actor)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
result =
|
result =
|
||||||
@ -229,7 +229,7 @@ defmodule MobilizonWeb.ActivityPubControllerTest do
|
|||||||
test "it returns the followings in a collection", %{conn: conn} do
|
test "it returns the followings in a collection", %{conn: conn} do
|
||||||
actor = insert(:actor)
|
actor = insert(:actor)
|
||||||
actor2 = insert(:actor, visibility: :public)
|
actor2 = insert(:actor, visibility: :public)
|
||||||
Actor.follow(actor, actor2)
|
Actors.follow(actor, actor2)
|
||||||
|
|
||||||
result =
|
result =
|
||||||
conn
|
conn
|
||||||
@ -242,7 +242,7 @@ defmodule MobilizonWeb.ActivityPubControllerTest do
|
|||||||
test "it returns no followings for a private actor", %{conn: conn} do
|
test "it returns no followings for a private actor", %{conn: conn} do
|
||||||
actor = insert(:actor)
|
actor = insert(:actor)
|
||||||
actor2 = insert(:actor, visibility: :private)
|
actor2 = insert(:actor, visibility: :private)
|
||||||
Actor.follow(actor, actor2)
|
Actors.follow(actor, actor2)
|
||||||
|
|
||||||
result =
|
result =
|
||||||
conn
|
conn
|
||||||
@ -257,7 +257,7 @@ defmodule MobilizonWeb.ActivityPubControllerTest do
|
|||||||
|
|
||||||
Enum.each(1..15, fn _ ->
|
Enum.each(1..15, fn _ ->
|
||||||
other_actor = insert(:actor)
|
other_actor = insert(:actor)
|
||||||
Actor.follow(other_actor, actor)
|
Actors.follow(other_actor, actor)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
result =
|
result =
|
||||||
@ -322,7 +322,7 @@ defmodule MobilizonWeb.ActivityPubControllerTest do
|
|||||||
# Enum.each(1..15, fn _ ->
|
# Enum.each(1..15, fn _ ->
|
||||||
# actor = Repo.get(Actor, actor.id)
|
# actor = Repo.get(Actor, actor.id)
|
||||||
# other_actor = insert(:actor)
|
# other_actor = insert(:actor)
|
||||||
# Actor.follow(actor, other_actor)
|
# Actors.follow(actor, other_actor)
|
||||||
# end)
|
# end)
|
||||||
|
|
||||||
# result =
|
# result =
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
defmodule Mobilizon.Factory do
|
defmodule Mobilizon.Factory do
|
||||||
@moduledoc """
|
@moduledoc """
|
||||||
Factory for fixtures with ExMachina
|
Factory for fixtures with ExMachina.
|
||||||
"""
|
"""
|
||||||
# with Ecto
|
|
||||||
use ExMachina.Ecto, repo: Mobilizon.Storage.Repo
|
use ExMachina.Ecto, repo: Mobilizon.Storage.Repo
|
||||||
|
|
||||||
alias Mobilizon.Actors.Actor
|
alias Mobilizon.Actors.Actor
|
||||||
|
alias Mobilizon.Crypto
|
||||||
|
|
||||||
alias MobilizonWeb.Router.Helpers, as: Routes
|
alias MobilizonWeb.Router.Helpers, as: Routes
|
||||||
alias MobilizonWeb.Endpoint
|
alias MobilizonWeb.Endpoint
|
||||||
alias MobilizonWeb.Upload
|
alias MobilizonWeb.Upload
|
||||||
@ -21,10 +24,6 @@ defmodule Mobilizon.Factory do
|
|||||||
end
|
end
|
||||||
|
|
||||||
def actor_factory do
|
def actor_factory 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()
|
|
||||||
|
|
||||||
preferred_username = sequence("thomas")
|
preferred_username = sequence("thomas")
|
||||||
|
|
||||||
%Mobilizon.Actors.Actor{
|
%Mobilizon.Actors.Actor{
|
||||||
@ -32,7 +31,7 @@ defmodule Mobilizon.Factory do
|
|||||||
domain: nil,
|
domain: nil,
|
||||||
followers: [],
|
followers: [],
|
||||||
followings: [],
|
followings: [],
|
||||||
keys: pem,
|
keys: Crypto.generate_rsa_2048_private_key(),
|
||||||
type: :Person,
|
type: :Person,
|
||||||
avatar: build(:file, name: "Avatar"),
|
avatar: build(:file, name: "Avatar"),
|
||||||
banner: build(:file, name: "Banner"),
|
banner: build(:file, name: "Banner"),
|
||||||
|
@ -22,7 +22,7 @@ defmodule Mix.Tasks.Mobilizon.RelayTest do
|
|||||||
{:ok, target_actor} = Actors.get_actor_by_url(target_instance)
|
{:ok, target_actor} = Actors.get_actor_by_url(target_instance)
|
||||||
refute is_nil(target_actor.domain)
|
refute is_nil(target_actor.domain)
|
||||||
|
|
||||||
assert Actor.following?(local_actor, target_actor)
|
assert Actors.following?(local_actor, target_actor)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -36,11 +36,11 @@ defmodule Mix.Tasks.Mobilizon.RelayTest do
|
|||||||
|
|
||||||
%Actor{} = local_actor = Relay.get_actor()
|
%Actor{} = local_actor = Relay.get_actor()
|
||||||
{:ok, %Actor{} = target_actor} = Actors.get_actor_by_url(target_instance)
|
{:ok, %Actor{} = target_actor} = Actors.get_actor_by_url(target_instance)
|
||||||
assert %Follower{} = Actor.following?(local_actor, target_actor)
|
assert %Follower{} = Actors.following?(local_actor, target_actor)
|
||||||
|
|
||||||
Mix.Tasks.Mobilizon.Relay.run(["unfollow", target_instance])
|
Mix.Tasks.Mobilizon.Relay.run(["unfollow", target_instance])
|
||||||
|
|
||||||
refute Actor.following?(local_actor, target_actor)
|
refute Actors.following?(local_actor, target_actor)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
Loading…
Reference in New Issue
Block a user