Merge branch 'fixes' into 'main'

Fix various instance view stuff and legacy cleaning

Closes #1393

See merge request framasoft/mobilizon!1520
This commit is contained in:
Thomas Citharel 2024-01-04 13:00:30 +00:00
commit b317fe6163
61 changed files with 288 additions and 256 deletions

View file

@ -33,7 +33,7 @@
# If you want to enforce a style guide and need a more traditional linting # If you want to enforce a style guide and need a more traditional linting
# experience, you can change `strict` to `true` below: # experience, you can change `strict` to `true` below:
# #
strict: false, strict: true,
# #
# If you want to use uncolored output by default, you can change `color` # If you want to use uncolored output by default, you can change `color`
# to `false` below: # to `false` below:
@ -160,6 +160,7 @@
# #
{Credo.Check.Warning.LazyLogging, false}, {Credo.Check.Warning.LazyLogging, false},
{Credo.Check.Refactor.MapInto, false}, {Credo.Check.Refactor.MapInto, false},
{Credo.Check.Warning.MissedMetadataKeyInLoggerConfig, false}
] ]
} }
] ]

View file

@ -25,7 +25,7 @@ defmodule Mobilizon.Federation.ActivityPub.Actions.Invite do
) do ) do
Logger.debug("Handling #{actor_url} invite to #{group_url} sent to #{target_actor_url}") Logger.debug("Handling #{actor_url} invite to #{group_url} sent to #{target_actor_url}")
if is_able_to_invite?(actor, group) do if able_to_invite?(actor, group) do
with {:ok, %Member{url: member_url} = member} <- with {:ok, %Member{url: member_url} = member} <-
Actors.create_member(%{ Actors.create_member(%{
parent_id: group_id, parent_id: group_id,
@ -64,8 +64,8 @@ defmodule Mobilizon.Federation.ActivityPub.Actions.Invite do
end end
end end
@spec is_able_to_invite?(Actor.t(), Actor.t()) :: boolean @spec able_to_invite?(Actor.t(), Actor.t()) :: boolean
defp is_able_to_invite?(%Actor{domain: actor_domain, id: actor_id}, %Actor{ defp able_to_invite?(%Actor{domain: actor_domain, id: actor_id}, %Actor{
domain: group_domain, domain: group_domain,
id: group_id id: group_id
}) do }) do
@ -76,7 +76,7 @@ defmodule Mobilizon.Federation.ActivityPub.Actions.Invite do
# If local group, we'll send the invite # If local group, we'll send the invite
case Actors.get_member(actor_id, group_id) do case Actors.get_member(actor_id, group_id) do
{:ok, %Member{} = admin_member} -> {:ok, %Member{} = admin_member} ->
Member.is_administrator(admin_member) Member.administrator?(admin_member)
_ -> _ ->
false false

View file

@ -34,7 +34,7 @@ defmodule Mobilizon.Federation.ActivityPub.Actions.Leave do
local, local,
additional additional
) do ) do
if Participant.is_not_only_organizer(event_id, actor_id) do if Participant.not_only_organizer?(event_id, actor_id) do
{:error, :is_only_organizer} {:error, :is_only_organizer}
else else
case Mobilizon.Events.get_participant( case Mobilizon.Events.get_participant(
@ -83,7 +83,7 @@ defmodule Mobilizon.Federation.ActivityPub.Actions.Leave do
case Actors.get_member(actor_id, group_id) do case Actors.get_member(actor_id, group_id) do
{:ok, %Member{id: member_id} = member} -> {:ok, %Member{id: member_id} = member} ->
if Map.get(additional, :force_member_removal, false) || group_domain != actor_domain || if Map.get(additional, :force_member_removal, false) || group_domain != actor_domain ||
!Actors.is_only_administrator?(member_id, group_id) do !Actors.only_administrator?(member_id, group_id) do
with {:ok, %Member{} = member} <- Actors.delete_member(member) do with {:ok, %Member{} = member} <- Actors.delete_member(member) do
Mobilizon.Service.Activity.Member.insert_activity(member, subject: "member_quit") Mobilizon.Service.Activity.Member.insert_activity(member, subject: "member_quit")

View file

@ -44,13 +44,13 @@ defmodule Mobilizon.Federation.ActivityPub.Permission do
) do ) do
case object |> Ownable.permissions() |> get_in([:create]) do case object |> Ownable.permissions() |> get_in([:create]) do
:member -> :member ->
Actors.is_member?(actor_id, group_id) Actors.member?(actor_id, group_id)
:moderator -> :moderator ->
Actors.is_moderator?(actor_id, group_id) Actors.moderator?(actor_id, group_id)
:administrator -> :administrator ->
Actors.is_administrator?(actor_id, group_id) Actors.administrator?(actor_id, group_id)
_ -> _ ->
false false
@ -122,21 +122,21 @@ defmodule Mobilizon.Federation.ActivityPub.Permission do
"Checking if activity actor #{actor_url} is a moderator from group from #{object.url}" "Checking if activity actor #{actor_url} is a moderator from group from #{object.url}"
) )
Actors.is_moderator?(actor_id, group_id) Actors.moderator?(actor_id, group_id)
:administrator -> :administrator ->
Logger.debug( Logger.debug(
"Checking if activity actor #{actor_url} is an administrator from group from #{object.url}" "Checking if activity actor #{actor_url} is an administrator from group from #{object.url}"
) )
Actors.is_administrator?(actor_id, group_id) Actors.administrator?(actor_id, group_id)
_ -> _ ->
Logger.debug( Logger.debug(
"Checking if activity actor #{actor_url} is a member from group from #{object.url}" "Checking if activity actor #{actor_url} is a member from group from #{object.url}"
) )
Actors.is_member?(actor_id, group_id) Actors.member?(actor_id, group_id)
end end
_ -> _ ->

View file

@ -21,10 +21,10 @@ defmodule Mobilizon.Federation.ActivityPub.Publisher do
Logger.debug("Publishing an activity") Logger.debug("Publishing an activity")
Logger.debug(inspect(activity, pretty: true)) Logger.debug(inspect(activity, pretty: true))
public = Visibility.is_public?(activity) public = Visibility.public?(activity)
Logger.debug("is public ? #{public}") Logger.debug("is public ? #{public}")
if public && is_create_activity?(activity) && Config.get([:instance, :allow_relay]) do if public && create_activity?(activity) && Config.get([:instance, :allow_relay]) do
Logger.info(fn -> "Relaying #{activity.data["id"]} out" end) Logger.info(fn -> "Relaying #{activity.data["id"]} out" end)
Relay.publish(activity) Relay.publish(activity)
@ -125,9 +125,9 @@ defmodule Mobilizon.Federation.ActivityPub.Publisher do
end) end)
end end
@spec is_create_activity?(Activity.t()) :: boolean @spec create_activity?(Activity.t()) :: boolean
defp is_create_activity?(%Activity{data: %{"type" => "Create"}}), do: true defp create_activity?(%Activity{data: %{"type" => "Create"}}), do: true
defp is_create_activity?(_), do: false defp create_activity?(_), do: false
@spec convert_members_in_recipients(list(String.t())) :: {list(String.t()), list(Actor.t())} @spec convert_members_in_recipients(list(String.t())) :: {list(String.t()), list(Actor.t())}
defp convert_members_in_recipients(recipients) do defp convert_members_in_recipients(recipients) do

View file

@ -285,7 +285,7 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
object_data when is_map(object_data) <- object_data when is_map(object_data) <-
object |> Converter.Resource.as_to_model_data(), object |> Converter.Resource.as_to_model_data(),
{:member, true} <- {:member, true} <-
{:member, Actors.is_member?(object_data.creator_id, object_data.actor_id)}, {:member, Actors.member?(object_data.creator_id, object_data.actor_id)},
{:ok, %Activity{} = activity, %Resource{} = resource} <- {:ok, %Activity{} = activity, %Resource{} = resource} <-
Actions.Create.create(:resource, object_data, false) do Actions.Create.create(:resource, object_data, false) do
{:ok, activity, resource} {:ok, activity, resource}
@ -1005,14 +1005,14 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
end end
# Comment initiates a whole discussion only if it has full title # Comment initiates a whole discussion only if it has full title
@spec is_data_for_comment_or_discussion?(map()) :: boolean() @spec data_for_comment_or_discussion?(map()) :: boolean()
defp is_data_for_comment_or_discussion?(object_data) do defp data_for_comment_or_discussion?(object_data) do
is_data_a_discussion_initialization?(object_data) and data_a_discussion_initialization?(object_data) and
is_nil(object_data.discussion_id) is_nil(object_data.discussion_id)
end end
# Comment initiates a whole discussion only if it has full title # Comment initiates a whole discussion only if it has full title
defp is_data_a_discussion_initialization?(object_data) do defp data_a_discussion_initialization?(object_data) do
not Map.has_key?(object_data, :title) or not Map.has_key?(object_data, :title) or
is_nil(object_data.title) or object_data.title == "" is_nil(object_data.title) or object_data.title == ""
end end
@ -1034,7 +1034,7 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
@spec transform_object_data_for_discussion(map()) :: map() @spec transform_object_data_for_discussion(map()) :: map()
defp transform_object_data_for_discussion(object_data) do defp transform_object_data_for_discussion(object_data) do
# Basic comment # Basic comment
if is_data_a_discussion_initialization?(object_data) do if data_a_discussion_initialization?(object_data) do
object_data object_data
else else
# Conversation # Conversation
@ -1138,8 +1138,8 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
end end
end end
defp is_group_object_gone(object_id) do defp group_object_gone_check(object_id) do
Logger.debug("is_group_object_gone #{object_id}") Logger.debug("Checking if group object #{object_id} is gone")
case ActivityPub.fetch_object_from_url(object_id, force: true) do case ActivityPub.fetch_object_from_url(object_id, force: true) do
# comments are just emptied # comments are just emptied
@ -1163,14 +1163,10 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
end end
end end
# Before 1.0.4 the object of a "Remove" activity was an actor's URL
# instead of the member's URL.
# TODO: Remove in 1.2
@spec get_remove_object(map() | String.t()) :: {:ok, integer()} @spec get_remove_object(map() | String.t()) :: {:ok, integer()}
defp get_remove_object(object) do defp get_remove_object(object) do
case object |> Utils.get_url() |> ActivityPub.fetch_object_from_url() do case object |> Utils.get_url() |> ActivityPub.fetch_object_from_url() do
{:ok, %Member{actor: %Actor{id: person_id}}} -> {:ok, person_id} {:ok, %Member{actor: %Actor{id: person_id}}} -> {:ok, person_id}
{:ok, %Actor{id: person_id}} -> {:ok, person_id}
_ -> {:error, :remove_object_not_found} _ -> {:error, :remove_object_not_found}
end end
end end
@ -1196,7 +1192,7 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
@spec create_comment_or_discussion(map()) :: @spec create_comment_or_discussion(map()) ::
{:ok, Activity.t(), struct()} | {:error, atom() | Ecto.Changeset.t()} {:ok, Activity.t(), struct()} | {:error, atom() | Ecto.Changeset.t()}
defp create_comment_or_discussion(object_data) do defp create_comment_or_discussion(object_data) do
if is_data_for_comment_or_discussion?(object_data) do if data_for_comment_or_discussion?(object_data) do
Logger.debug("Chosing to create a regular comment") Logger.debug("Chosing to create a regular comment")
Actions.Create.create(:comment, object_data, false) Actions.Create.create(:comment, object_data, false)
else else
@ -1248,7 +1244,7 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
end end
defp handle_group_being_gone(actor, actor_url, object_id) do defp handle_group_being_gone(actor, actor_url, object_id) do
case is_group_object_gone(object_id) do case group_object_gone_check(object_id) do
# The group object is no longer there, we can remove the element # The group object is no longer there, we can remove the element
{:ok, entity} -> {:ok, entity} ->
if Utils.origin_check_from_id?(actor_url, object_id) || if Utils.origin_check_from_id?(actor_url, object_id) ||

View file

@ -93,7 +93,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Members do
atom() atom()
) :: boolean ) :: boolean
defp check_admins_left?(member_id, group_id, current_role, updated_role) do defp check_admins_left?(member_id, group_id, current_role, updated_role) do
Actors.is_only_administrator?(member_id, group_id) && current_role == :administrator && Actors.only_administrator?(member_id, group_id) && current_role == :administrator &&
updated_role != :administrator updated_role != :administrator
end end
end end

View file

@ -14,17 +14,17 @@ defmodule Mobilizon.Federation.ActivityPub.Visibility do
@public "https://www.w3.org/ns/activitystreams#Public" @public "https://www.w3.org/ns/activitystreams#Public"
@spec is_public?(Activity.t() | map()) :: boolean() @spec public?(Activity.t() | map()) :: boolean()
def is_public?(%{data: %{"type" => "Tombstone"}}), do: false def public?(%{data: %{"type" => "Tombstone"}}), do: false
def is_public?(%{data: data}), do: is_public?(data) def public?(%{data: data}), do: public?(data)
def is_public?(%Activity{data: data}), do: is_public?(data) def public?(%Activity{data: data}), do: public?(data)
def is_public?(data) when is_map(data) do def public?(data) when is_map(data) do
@public in make_list(Map.get(data, "to", [])) @public in make_list(Map.get(data, "to", []))
end end
def is_public?(%Comment{deleted_at: deleted_at}), do: !is_nil(deleted_at) def public?(%Comment{deleted_at: deleted_at}), do: !is_nil(deleted_at)
def is_public?(err), do: raise(ArgumentError, message: "Invalid argument #{inspect(err)}") def public?(err), do: raise(ArgumentError, message: "Invalid argument #{inspect(err)}")
defp make_list(data) when is_list(data), do: data defp make_list(data) when is_list(data), do: data
defp make_list(data), do: [data] defp make_list(data), do: [data]

View file

@ -76,14 +76,13 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Actor do
def as_to_model_data(_), do: {:error, :actor_not_allowed_type} def as_to_model_data(_), do: {:error, :actor_not_allowed_type}
defp add_endpoints_to_model(actor, data) do defp add_endpoints_to_model(actor, data) do
# TODO: Remove fallbacks in 3.0
endpoints = %{ endpoints = %{
members_url: get_in(data, ["endpoints", "members"]) || data["members"], members_url: get_in(data, ["endpoints", "members"]),
resources_url: get_in(data, ["endpoints", "resources"]) || data["resources"], resources_url: get_in(data, ["endpoints", "resources"]),
todos_url: get_in(data, ["endpoints", "todos"]) || data["todos"], todos_url: get_in(data, ["endpoints", "todos"]),
events_url: get_in(data, ["endpoints", "events"]) || data["events"], events_url: get_in(data, ["endpoints", "events"]),
posts_url: get_in(data, ["endpoints", "posts"]) || data["posts"], posts_url: get_in(data, ["endpoints", "posts"]),
discussions_url: get_in(data, ["endpoints", "discussions"]) || data["discussions"], discussions_url: get_in(data, ["endpoints", "discussions"]),
shared_inbox_url: data["endpoints"]["sharedInbox"] shared_inbox_url: data["endpoints"]["sharedInbox"]
} }

View file

@ -62,7 +62,7 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Comment do
tags: fetch_tags(tag_object), tags: fetch_tags(tag_object),
mentions: fetch_mentions(tag_object), mentions: fetch_mentions(tag_object),
local: is_nil(actor_domain), local: is_nil(actor_domain),
visibility: if(Visibility.is_public?(object), do: :public, else: :private), visibility: if(Visibility.public?(object), do: :public, else: :private),
published_at: object["published"], published_at: object["published"],
is_announcement: Map.get(object, "isAnnouncement", false) is_announcement: Map.get(object, "isAnnouncement", false)
} }

View file

@ -77,7 +77,7 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Event do
category: Categories.get_category(object["category"]), category: Categories.get_category(object["category"]),
visibility: visibility, visibility: visibility,
join_options: Map.get(object, "joinMode", "free"), join_options: Map.get(object, "joinMode", "free"),
local: is_local?(object["id"]), local: local?(object["id"]),
external_participation_url: object["externalParticipationUrl"], external_participation_url: object["externalParticipationUrl"],
options: options, options: options,
metadata: metadata, metadata: metadata,
@ -305,8 +305,8 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Event do
) )
end end
@spec is_local?(String.t()) :: boolean() @spec local?(String.t()) :: boolean()
defp is_local?(url) do defp local?(url) do
%URI{host: url_domain} = URI.parse(url) %URI{host: url_domain} = URI.parse(url)
%URI{host: local_domain} = URI.parse(Endpoint.url()) %URI{host: local_domain} = URI.parse(Endpoint.url())
url_domain == local_domain url_domain == local_domain

View file

@ -5,7 +5,7 @@ defmodule Mobilizon.Federation.NodeInfo do
alias Mobilizon.Service.HTTP.WebfingerClient alias Mobilizon.Service.HTTP.WebfingerClient
require Logger require Logger
import Mobilizon.Service.HTTP.Utils, only: [is_content_type?: 2] import Mobilizon.Service.HTTP.Utils, only: [content_type_matches?: 2]
@application_uri "https://www.w3.org/ns/activitystreams#Application" @application_uri "https://www.w3.org/ns/activitystreams#Application"
@nodeinfo_rel_2_0 "http://nodeinfo.diaspora.software/ns/schema/2.0" @nodeinfo_rel_2_0 "http://nodeinfo.diaspora.software/ns/schema/2.0"
@ -110,7 +110,7 @@ defmodule Mobilizon.Federation.NodeInfo do
{:ok, String.t()} | {:error, :bad_content_type | :body_not_json} {:ok, String.t()} | {:error, :bad_content_type | :body_not_json}
defp validate_json_response(body, headers) do defp validate_json_response(body, headers) do
cond do cond do
!is_content_type?(headers, "application/json") -> !content_type_matches?(headers, "application/json") ->
{:error, :bad_content_type} {:error, :bad_content_type}
!is_map(body) -> !is_map(body) ->

View file

@ -25,8 +25,8 @@ defmodule Mobilizon.GraphQL.API.Search do
cond do cond do
# Some URLs could be domain.tld/@username, so keep this condition above # Some URLs could be domain.tld/@username, so keep this condition above
# the `is_handle` function # the `handle?` function
is_url(term) -> url?(term) ->
# skip, if it's not an actor # skip, if it's not an actor
case process_from_url(term) do case process_from_url(term) do
%Page{total: _total, elements: [%Actor{} = _actor]} = page -> %Page{total: _total, elements: [%Actor{} = _actor]} = page ->
@ -36,11 +36,11 @@ defmodule Mobilizon.GraphQL.API.Search do
{:ok, %{total: 0, elements: []}} {:ok, %{total: 0, elements: []}}
end end
is_handle(term) -> handle?(term) ->
{:ok, process_from_username(term)} {:ok, process_from_username(term)}
true -> true ->
if is_global_search(args) do if global_search?(args) do
service = GlobalSearch.service() service = GlobalSearch.service()
{:ok, service.search_groups(Keyword.new(args, fn {k, v} -> {k, v} end))} {:ok, service.search_groups(Keyword.new(args, fn {k, v} -> {k, v} end))}
@ -75,7 +75,7 @@ defmodule Mobilizon.GraphQL.API.Search do
def search_events(%{term: term} = args, page \\ 1, limit \\ 10) do def search_events(%{term: term} = args, page \\ 1, limit \\ 10) do
term = String.trim(term) term = String.trim(term)
if is_url(term) do if url?(term) do
# skip, if it's not an event # skip, if it's not an event
case process_from_url(term) do case process_from_url(term) do
%Page{total: _total, elements: [%Event{} = event]} = page -> %Page{total: _total, elements: [%Event{} = event]} = page ->
@ -89,7 +89,7 @@ defmodule Mobilizon.GraphQL.API.Search do
{:ok, %{total: 0, elements: []}} {:ok, %{total: 0, elements: []}}
end end
else else
if is_global_search(args) do if global_search?(args) do
service = GlobalSearch.service() service = GlobalSearch.service()
{:ok, service.search_events(Keyword.new(args, fn {k, v} -> {k, v} end))} {:ok, service.search_events(Keyword.new(args, fn {k, v} -> {k, v} end))}
@ -140,17 +140,17 @@ defmodule Mobilizon.GraphQL.API.Search do
end end
end end
@spec is_url(String.t()) :: boolean @spec url?(String.t()) :: boolean
defp is_url(search), do: String.starts_with?(search, ["http://", "https://"]) defp url?(search), do: String.starts_with?(search, ["http://", "https://"])
@spec is_handle(String.t()) :: boolean @spec handle?(String.t()) :: boolean
defp is_handle(search), do: String.match?(search, ~r/@/) defp handle?(search), do: String.match?(search, ~r/@/)
defp is_global_search(%{search_target: :global}) do defp global_search?(%{search_target: :global}) do
global_search_enabled?() global_search_enabled?()
end end
defp is_global_search(_), do: global_search_enabled?() && global_search_default?() defp global_search?(_), do: global_search_enabled?() && global_search_default?()
defp global_search_enabled? do defp global_search_enabled? do
Application.get_env(:mobilizon, :search) |> get_in([:global]) |> get_in([:is_enabled]) Application.get_env(:mobilizon, :search) |> get_in([:global]) |> get_in([:is_enabled])

View file

@ -68,6 +68,6 @@ defmodule Mobilizon.GraphQL.API.Utils do
@spec check_actor_owns_media?(integer() | String.t(), integer() | String.t()) :: boolean() @spec check_actor_owns_media?(integer() | String.t(), integer() | String.t()) :: boolean()
defp check_actor_owns_media?(actor_id, media_actor_id) do defp check_actor_owns_media?(actor_id, media_actor_id) do
actor_id == media_actor_id || Mobilizon.Actors.is_member?(media_actor_id, actor_id) actor_id == media_actor_id || Mobilizon.Actors.member?(media_actor_id, actor_id)
end end
end end

View file

@ -5,8 +5,8 @@ defmodule Mobilizon.GraphQL.Error do
require Logger require Logger
alias __MODULE__ alias __MODULE__
alias Mobilizon.Web.Gettext, as: GettextBackend
import Mobilizon.Web.Gettext, only: [dgettext: 2] import Mobilizon.Web.Gettext, only: [dgettext: 2]
import Mobilizon.Storage.Ecto, only: [convert_ecto_errors: 1]
@type t :: %{code: atom(), message: String.t(), status_code: pos_integer(), field: atom()} @type t :: %{code: atom(), message: String.t(), status_code: pos_integer(), field: atom()}
@ -64,7 +64,7 @@ defmodule Mobilizon.GraphQL.Error do
defp handle(%Ecto.Changeset{} = changeset) do defp handle(%Ecto.Changeset{} = changeset) do
changeset changeset
|> Ecto.Changeset.traverse_errors(&translate_error/1) |> convert_ecto_errors()
|> Enum.map(fn {k, v} -> |> Enum.map(fn {k, v} ->
%Error{ %Error{
code: :validation, code: :validation,
@ -126,27 +126,4 @@ defmodule Mobilizon.GraphQL.Error do
Logger.warning("Unhandled error code: #{inspect(code)}") Logger.warning("Unhandled error code: #{inspect(code)}")
{422, to_string(code)} {422, to_string(code)}
end end
# Translates an error message using gettext.
defp translate_error({msg, opts}) do
# Because error messages were defined within Ecto, we must
# call the Gettext module passing our Gettext backend. We
# also use the "errors" domain as translations are placed
# in the errors.po file.
# Ecto will pass the :count keyword if the error message is
# meant to be pluralized.
# On your own code and templates, depending on whether you
# need the message to be pluralized or not, this could be
# written simply as:
#
# dngettext "errors", "1 file", "%{count} files", count
# dgettext "errors", "is invalid"
#
if count = opts[:count] do
Gettext.dngettext(GettextBackend, "errors", msg, msg, count, opts)
else
Gettext.dgettext(GettextBackend, "errors", msg, opts)
end
end
end end

View file

@ -18,7 +18,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Activity do
def group_activity(%Actor{type: :Group, id: group_id}, %{page: page, limit: limit} = args, %{ def group_activity(%Actor{type: :Group, id: group_id}, %{page: page, limit: limit} = args, %{
context: %{current_user: %User{role: role}, current_actor: %Actor{id: actor_id}} context: %{current_user: %User{role: role}, current_actor: %Actor{id: actor_id}}
}) do }) do
if Actors.is_member?(actor_id, group_id) or is_moderator(role) do if Actors.member?(actor_id, group_id) or is_moderator(role) do
%Page{total: total, elements: elements} = %Page{total: total, elements: elements} =
Activities.list_group_activities_for_member( Activities.list_group_activities_for_member(
group_id, group_id,

View file

@ -26,7 +26,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Conversation do
} }
) )
when not is_nil(attributed_to_id) do when not is_nil(attributed_to_id) do
if Actors.is_member?(actor_id, attributed_to_id) do if Actors.member?(actor_id, attributed_to_id) do
{:ok, {:ok,
event_id event_id
|> Conversations.find_conversations_for_event(attributed_to_id, page, limit) |> Conversations.find_conversations_for_event(attributed_to_id, page, limit)
@ -103,7 +103,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Conversation do
{:error, :not_found} {:error, :not_found}
%ConversationParticipant{actor_id: actor_id} = conversation_participant -> %ConversationParticipant{actor_id: actor_id} = conversation_participant ->
if actor_id == performing_actor_id or Actors.is_member?(performing_actor_id, actor_id) do if actor_id == performing_actor_id or Actors.member?(performing_actor_id, actor_id) do
{:ok, conversation_participant_to_view(conversation_participant)} {:ok, conversation_participant_to_view(conversation_participant)}
else else
{:error, :not_found} {:error, :not_found}
@ -121,7 +121,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Conversation do
} }
) do ) do
if conversation_actor_id == performing_actor_id or if conversation_actor_id == performing_actor_id or
Actors.is_member?(performing_actor_id, conversation_actor_id) do Actors.member?(performing_actor_id, conversation_actor_id) do
{:ok, {:ok,
Mobilizon.Discussions.get_comments_in_reply_to_comment_id(origin_comment_id, page, limit)} Mobilizon.Discussions.get_comments_in_reply_to_comment_id(origin_comment_id, page, limit)}
else else
@ -184,7 +184,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Conversation do
{:valid_actor, true} <- {:valid_actor, true} <-
{:valid_actor, {:valid_actor,
actor_id == current_actor_id or actor_id == current_actor_id or
Actors.is_member?(current_actor_id, actor_id)}, Actors.member?(current_actor_id, actor_id)},
{:ok, %ConversationParticipant{} = conversation_participant} <- {:ok, %ConversationParticipant{} = conversation_participant} <-
Conversations.update_conversation_participant(conversation_participant, %{ Conversations.update_conversation_participant(conversation_participant, %{
unread: !read unread: !read
@ -269,7 +269,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Conversation do
to_string(current_actor_id) in participant_ids or to_string(current_actor_id) in participant_ids or
Enum.any?(participant_ids, fn participant_id -> Enum.any?(participant_ids, fn participant_id ->
Actors.is_member?(current_actor_id, participant_id) and Actors.member?(current_actor_id, participant_id) and
attributed_to_id == participant_id attributed_to_id == participant_id
end) end)
end end

View file

@ -23,7 +23,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Discussion do
} }
} }
) do ) do
with {:member, true} <- {:member, Actors.is_member?(actor_id, group_id)}, with {:member, true} <- {:member, Actors.member?(actor_id, group_id)},
{:ok, %Actor{type: :Group} = group} <- Actors.get_group_by_actor_id(group_id) do {:ok, %Actor{type: :Group} = group} <- Actors.get_group_by_actor_id(group_id) do
{:ok, Discussions.find_discussions_for_actor(group, page, limit)} {:ok, Discussions.find_discussions_for_actor(group, page, limit)}
else else
@ -45,7 +45,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Discussion do
}) do }) do
case Discussions.get_discussion(id) do case Discussions.get_discussion(id) do
%Discussion{actor_id: actor_id} = discussion -> %Discussion{actor_id: actor_id} = discussion ->
if Actors.is_member?(creator_id, actor_id) do if Actors.member?(creator_id, actor_id) do
{:ok, discussion} {:ok, discussion}
else else
{:error, :unauthorized} {:error, :unauthorized}
@ -63,7 +63,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Discussion do
}) do }) do
with %Discussion{actor_id: actor_id} = discussion <- with %Discussion{actor_id: actor_id} = discussion <-
Discussions.get_discussion_by_slug(slug), Discussions.get_discussion_by_slug(slug),
{:member, true} <- {:member, Actors.is_member?(creator_id, actor_id)} do {:member, true} <- {:member, Actors.member?(creator_id, actor_id)} do
{:ok, discussion} {:ok, discussion}
else else
nil -> {:error, dgettext("errors", "Discussion not found")} nil -> {:error, dgettext("errors", "Discussion not found")}
@ -105,7 +105,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Discussion do
} }
} }
) do ) do
if Actors.is_member?(creator_id, group_id) do if Actors.member?(creator_id, group_id) do
case Comments.create_discussion(%{ case Comments.create_discussion(%{
title: title, title: title,
text: text, text: text,
@ -150,7 +150,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Discussion do
} }
} = _discussion} <- } = _discussion} <-
{:no_discussion, Discussions.get_discussion(discussion_id)}, {:no_discussion, Discussions.get_discussion(discussion_id)},
{:member, true} <- {:member, Actors.is_member?(creator_id, actor_id)}, {:member, true} <- {:member, Actors.member?(creator_id, actor_id)},
{:ok, _activity, %Discussion{} = discussion} <- {:ok, _activity, %Discussion{} = discussion} <-
Comments.create_discussion(%{ Comments.create_discussion(%{
text: text, text: text,
@ -183,7 +183,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Discussion do
) do ) do
with {:no_discussion, %Discussion{actor_id: actor_id} = discussion} <- with {:no_discussion, %Discussion{actor_id: actor_id} = discussion} <-
{:no_discussion, Discussions.get_discussion(discussion_id)}, {:no_discussion, Discussions.get_discussion(discussion_id)},
{:member, true} <- {:member, Actors.is_member?(creator_id, actor_id)}, {:member, true} <- {:member, Actors.member?(creator_id, actor_id)},
{:ok, _activity, %Discussion{} = discussion} <- {:ok, _activity, %Discussion{} = discussion} <-
Actions.Update.update( Actions.Update.update(
discussion, discussion,
@ -213,7 +213,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Discussion do
}) do }) do
with {:no_discussion, %Discussion{actor_id: actor_id} = discussion} <- with {:no_discussion, %Discussion{actor_id: actor_id} = discussion} <-
{:no_discussion, Discussions.get_discussion(discussion_id)}, {:no_discussion, Discussions.get_discussion(discussion_id)},
{:member, true} <- {:member, Actors.is_member?(creator_id, actor_id)}, {:member, true} <- {:member, Actors.member?(creator_id, actor_id)},
{:ok, _activity, %Discussion{} = discussion} <- {:ok, _activity, %Discussion{} = discussion} <-
Actions.Delete.delete(discussion, actor) do Actions.Delete.delete(discussion, actor) do
{:ok, discussion} {:ok, discussion}

View file

@ -36,7 +36,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Event do
when not is_nil(attributed_to_id) do when not is_nil(attributed_to_id) do
with %Actor{id: group_id} <- Actors.get_actor(attributed_to_id), with %Actor{id: group_id} <- Actors.get_actor(attributed_to_id),
{:member, true} <- {:member, true} <-
{:member, Actors.is_member?(actor_id, group_id) or is_moderator(user_role)}, {:member, Actors.member?(actor_id, group_id) or is_moderator(user_role)},
%Actor{} = actor <- Actors.get_actor(organizer_actor_id) do %Actor{} = actor <- Actors.get_actor(organizer_actor_id) do
{:ok, actor} {:ok, actor}
else else
@ -176,7 +176,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Event do
_args, _args,
%{context: %{current_user: %User{id: user_id} = _user}} = _resolution %{context: %{current_user: %User{id: user_id} = _user}} = _resolution
) do ) do
if Events.is_user_moderator_for_event?(user_id, event_id) do if Events.user_moderator_for_event?(user_id, event_id) do
{:ok, {:ok,
Map.put( Map.put(
stats, stats,
@ -256,7 +256,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Event do
{:can_create_event, true} <- can_create_event(args), {:can_create_event, true} <- can_create_event(args),
{:event_external, true} <- edit_event_external_checker(args), {:event_external, true} <- edit_event_external_checker(args),
{:organizer_group_member, true} <- {:organizer_group_member, true} <-
{:organizer_group_member, is_organizer_group_member?(args)}, {:organizer_group_member, organizer_group_member?(args)},
args_with_organizer <- args_with_organizer <-
args |> Map.put(:organizer_actor, organizer_actor) |> extract_timezone(user.id), args |> Map.put(:organizer_actor, organizer_actor) |> extract_timezone(user.id),
{:askismet, :ham} <- {:askismet, :ham} <-
@ -447,17 +447,17 @@ defmodule Mobilizon.GraphQL.Resolvers.Event do
end end
end end
@spec is_organizer_group_member?(map()) :: boolean() @spec organizer_group_member?(map()) :: boolean()
defp is_organizer_group_member?(%{ defp organizer_group_member?(%{
attributed_to_id: attributed_to_id, attributed_to_id: attributed_to_id,
organizer_actor_id: organizer_actor_id organizer_actor_id: organizer_actor_id
}) })
when not is_nil(attributed_to_id) do when not is_nil(attributed_to_id) do
Actors.is_member?(organizer_actor_id, attributed_to_id) && Actors.member?(organizer_actor_id, attributed_to_id) &&
Permission.can_create_group_object?(organizer_actor_id, attributed_to_id, %Event{}) Permission.can_create_group_object?(organizer_actor_id, attributed_to_id, %Event{})
end end
defp is_organizer_group_member?(_), do: true defp organizer_group_member?(_), do: true
@spec verify_profile_change(map(), Event.t(), User.t(), Actor.t()) :: {:ok, map()} @spec verify_profile_change(map(), Event.t(), User.t(), Actor.t()) :: {:ok, map()}
defp verify_profile_change( defp verify_profile_change(

View file

@ -23,7 +23,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Followers do
) do ) do
followers = group_followers(group, args) followers = group_followers(group, args)
if Actors.is_moderator?(actor_id, group_id) or is_moderator(user_role) do if Actors.moderator?(actor_id, group_id) or is_moderator(user_role) do
{:ok, followers} {:ok, followers}
else else
{:ok, %Page{followers | elements: []}} {:ok, %Page{followers | elements: []}}
@ -48,7 +48,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Followers do
with %Follower{target_actor: %Actor{type: :Group, id: group_id}} = follower <- with %Follower{target_actor: %Actor{type: :Group, id: group_id}} = follower <-
Actors.get_follower(follower_id), Actors.get_follower(follower_id),
{:member, true} <- {:member, true} <-
{:member, Actors.is_moderator?(actor_id, group_id)}, {:member, Actors.moderator?(actor_id, group_id)},
{:ok, _activity, %Follower{} = follower} <- {:ok, _activity, %Follower{} = follower} <-
(if approved do (if approved do
Actions.Accept.accept(:follow, follower) Actions.Accept.accept(:follow, follower)

View file

@ -36,7 +36,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Group do
) do ) do
case ActivityPubActor.find_or_make_group_from_nickname(name) do case ActivityPubActor.find_or_make_group_from_nickname(name) do
{:ok, %Actor{id: group_id, suspended: false} = group} -> {:ok, %Actor{id: group_id, suspended: false} = group} ->
if Actors.is_member?(actor_id, group_id) do if Actors.member?(actor_id, group_id) do
{:ok, group} {:ok, group}
else else
find_group(parent, args, nil) find_group(parent, args, nil)
@ -72,7 +72,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Group do
} }
}) do }) do
with %Actor{suspended: false, id: group_id} = group <- Actors.get_actor_with_preload(id), with %Actor{suspended: false, id: group_id} = group <- Actors.get_actor_with_preload(id),
true <- Actors.is_member?(actor_id, group_id) do true <- Actors.member?(actor_id, group_id) do
{:ok, group} {:ok, group}
else else
_ -> _ ->
@ -215,7 +215,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Group do
} }
} }
) do ) do
if Actors.is_administrator?(updater_actor.id, group_id) do if Actors.administrator?(updater_actor.id, group_id) do
args = Map.put(args, :updater_actor, updater_actor) args = Map.put(args, :updater_actor, updater_actor)
case save_attached_pictures(args) do case save_attached_pictures(args) do
@ -265,7 +265,7 @@ defmodule Mobilizon.GraphQL.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),
{:ok, %Member{} = member} <- Actors.get_member(actor_id, group.id), {:ok, %Member{} = member} <- Actors.get_member(actor_id, group.id),
{:is_admin, true} <- {:is_admin, Member.is_administrator(member)}, {:is_admin, true} <- {:is_admin, Member.administrator?(member)},
{:ok, _activity, group} <- Actions.Delete.delete(group, actor, true) do {:ok, _activity, group} <- Actions.Delete.delete(group, actor, true) do
{:ok, %{id: group.id}} {:ok, %{id: group.id}}
else else
@ -448,7 +448,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Group do
} }
} }
) do ) do
if Actors.is_member?(actor_id, group_id) do if Actors.member?(actor_id, group_id) do
{:ok, {:ok,
Events.list_organized_events_for_group( Events.list_organized_events_for_group(
group, group,

View file

@ -26,7 +26,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Member do
context: %{current_user: %User{role: user_role}, current_actor: %Actor{id: actor_id}} context: %{current_user: %User{role: user_role}, current_actor: %Actor{id: actor_id}}
} = _resolution } = _resolution
) do ) do
if Actors.is_member?(actor_id, group_id) or is_moderator(user_role) do if Actors.member?(actor_id, group_id) or is_moderator(user_role) do
roles = roles =
case roles do case roles do
"" -> "" ->

View file

@ -384,7 +384,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Participant do
with {:member, true} <- with {:member, true} <-
{:member, {:member,
to_string(current_actor_id) == to_string(actor_id) or to_string(current_actor_id) == to_string(actor_id) or
Actors.is_member?(current_actor_id, actor_id)}, Actors.member?(current_actor_id, actor_id)},
{:ok, _activity, %Conversation{} = conversation} <- Comments.create_conversation(args) do {:ok, _activity, %Conversation{} = conversation} <- Comments.create_conversation(args) do
{:ok, conversation_to_view(conversation, Actors.get_actor(actor_id))} {:ok, conversation_to_view(conversation, Actors.get_actor(actor_id))}
else else

View file

@ -32,7 +32,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Post do
} }
} = _resolution } = _resolution
) do ) do
if Actors.is_member?(actor_id, group_id) or is_moderator(user_role) do if Actors.member?(actor_id, group_id) or is_moderator(user_role) do
%Page{} = page = Posts.get_posts_for_group(group, page, limit) %Page{} = page = Posts.get_posts_for_group(group, page, limit)
{:ok, page} {:ok, page}
else else
@ -111,7 +111,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Post do
} }
} = _resolution } = _resolution
) do ) do
with {:member, true} <- {:member, Actors.is_member?(actor_id, group_id)}, with {:member, true} <- {:member, Actors.member?(actor_id, group_id)},
%Actor{} = group <- Actors.get_actor(group_id), %Actor{} = group <- Actors.get_actor(group_id),
args <- args <-
Map.update(args, :picture, nil, fn picture -> Map.update(args, :picture, nil, fn picture ->
@ -160,7 +160,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Post do
process_picture(picture, group) process_picture(picture, group)
end), end),
args <- extract_pictures_from_post_body(args, actor_id), args <- extract_pictures_from_post_body(args, actor_id),
{:member, true} <- {:member, Actors.is_member?(actor_id, group_id)}, {:member, true} <- {:member, Actors.member?(actor_id, group_id)},
{:ok, _, %Post{} = post} <- {:ok, _, %Post{} = post} <-
Actions.Update.update(post, args, true, %{"actor" => actor_url}) do Actions.Update.update(post, args, true, %{"actor" => actor_url}) do
{:ok, post} {:ok, post}
@ -194,7 +194,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Post do
with {:uuid, {:ok, _uuid}} <- {:uuid, Ecto.UUID.cast(post_id)}, with {:uuid, {:ok, _uuid}} <- {:uuid, Ecto.UUID.cast(post_id)},
{:post, %Post{attributed_to: %Actor{id: group_id}} = post} <- {:post, %Post{attributed_to: %Actor{id: group_id}} = post} <-
{:post, Posts.get_post_with_preloads(post_id)}, {:post, Posts.get_post_with_preloads(post_id)},
{:member, true} <- {:member, Actors.is_member?(actor_id, group_id)}, {:member, true} <- {:member, Actors.member?(actor_id, group_id)},
{:ok, _, %Post{} = post} <- {:ok, _, %Post{} = post} <-
Actions.Delete.delete(post, actor) do Actions.Delete.delete(post, actor) do
{:ok, post} {:ok, post}

View file

@ -32,7 +32,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Resource do
} }
} = _resolution } = _resolution
) do ) do
with {:member, true} <- {:member, Actors.is_member?(actor_id, group_id)}, with {:member, true} <- {:member, Actors.member?(actor_id, group_id)},
%Page{} = page <- Resources.get_resources_for_group(group, page, limit) do %Page{} = page <- Resources.get_resources_for_group(group, page, limit) do
{:ok, page} {:ok, page}
else else
@ -60,7 +60,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Resource do
} }
} = _resolution } = _resolution
) do ) do
with {:member, true} <- {:member, Actors.is_member?(actor_id, group_id)}, with {:member, true} <- {:member, Actors.member?(actor_id, group_id)},
%Page{} = page <- Resources.get_resources_for_folder(parent, page, limit) do %Page{} = page <- Resources.get_resources_for_folder(parent, page, limit) do
{:ok, page} {:ok, page}
end end
@ -83,7 +83,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Resource do
Logger.debug("Getting resource for group with username #{username}") Logger.debug("Getting resource for group with username #{username}")
with {:group, %Actor{id: group_id}} <- {:group, Actors.get_actor_by_name(username, :Group)}, with {:group, %Actor{id: group_id}} <- {:group, Actors.get_actor_by_name(username, :Group)},
{:member, true} <- {:member, Actors.is_member?(actor_id, group_id)}, {:member, true} <- {:member, Actors.member?(actor_id, group_id)},
{:resource, %Resource{} = resource} <- {:resource, %Resource{} = resource} <-
{:resource, Resources.get_resource_by_group_and_path_with_preloads(group_id, path)} do {:resource, Resources.get_resource_by_group_and_path_with_preloads(group_id, path)} do
{:ok, resource} {:ok, resource}
@ -109,7 +109,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Resource do
} }
} = _resolution } = _resolution
) do ) do
if Actors.is_member?(actor_id, group_id) do if Actors.member?(actor_id, group_id) do
parent = get_eventual_parent(args) parent = get_eventual_parent(args)
if check_resource_owned_by_group(parent, group_id) do if check_resource_owned_by_group(parent, group_id) do
@ -155,7 +155,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Resource do
) do ) do
case Resources.get_resource_with_preloads(resource_id) do case Resources.get_resource_with_preloads(resource_id) do
%Resource{actor_id: group_id} = resource -> %Resource{actor_id: group_id} = resource ->
if Actors.is_member?(actor_id, group_id) do if Actors.member?(actor_id, group_id) do
case Actions.Update.update(resource, args, true, %{"actor" => actor_url}) do case Actions.Update.update(resource, args, true, %{"actor" => actor_url}) do
{:ok, _, %Resource{} = resource} -> {:ok, _, %Resource{} = resource} ->
{:ok, resource} {:ok, resource}
@ -192,7 +192,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Resource do
) do ) do
with {:resource, %Resource{parent_id: _parent_id, actor_id: group_id} = resource} <- with {:resource, %Resource{parent_id: _parent_id, actor_id: group_id} = resource} <-
{:resource, Resources.get_resource_with_preloads(resource_id)}, {:resource, Resources.get_resource_with_preloads(resource_id)},
{:member, true} <- {:member, Actors.is_member?(actor_id, group_id)}, {:member, true} <- {:member, Actors.member?(actor_id, group_id)},
{:ok, _, %Resource{} = resource} <- {:ok, _, %Resource{} = resource} <-
Actions.Delete.delete(resource, actor) do Actions.Delete.delete(resource, actor) do
{:ok, resource} {:ok, resource}

View file

@ -26,7 +26,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Todos do
context: %{current_actor: %Actor{id: actor_id}} context: %{current_actor: %Actor{id: actor_id}}
} = _resolution } = _resolution
) do ) do
with {:member, true} <- {:member, Actors.is_member?(actor_id, group_id)}, with {:member, true} <- {:member, Actors.member?(actor_id, group_id)},
%Page{} = page <- Todos.get_todo_lists_for_group(group, page, limit) do %Page{} = page <- Todos.get_todo_lists_for_group(group, page, limit) do
{:ok, page} {:ok, page}
else else
@ -50,7 +50,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Todos do
context: %{current_actor: %Actor{id: actor_id}} context: %{current_actor: %Actor{id: actor_id}}
} = _resolution } = _resolution
) do ) do
with {:member, true} <- {:member, Actors.is_member?(actor_id, group_id)}, with {:member, true} <- {:member, Actors.member?(actor_id, group_id)},
%Page{} = page <- Todos.get_todos_for_todo_list(todo_list, page, limit) do %Page{} = page <- Todos.get_todos_for_todo_list(todo_list, page, limit) do
{:ok, page} {:ok, page}
else else
@ -70,7 +70,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Todos do
) do ) do
with {:todo, %TodoList{actor_id: group_id} = todo} <- with {:todo, %TodoList{actor_id: group_id} = todo} <-
{:todo, Todos.get_todo_list(todo_list_id)}, {:todo, Todos.get_todo_list(todo_list_id)},
{:member, true} <- {:member, Actors.is_member?(actor_id, group_id)} do {:member, true} <- {:member, Actors.member?(actor_id, group_id)} do
{:ok, todo} {:ok, todo}
else else
{:todo, nil} -> {:todo, nil} ->
@ -93,7 +93,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Todos do
context: %{current_actor: %Actor{id: actor_id}} context: %{current_actor: %Actor{id: actor_id}}
} = _resolution } = _resolution
) do ) do
with {:member, true} <- {:member, Actors.is_member?(actor_id, group_id)}, with {:member, true} <- {:member, Actors.member?(actor_id, group_id)},
{:ok, _, %TodoList{} = todo_list} <- {:ok, _, %TodoList{} = todo_list} <-
Actions.Create.create( Actions.Create.create(
:todo_list, :todo_list,
@ -121,7 +121,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Todos do
# with {:is_owned, %Actor{} = actor} <- User.owns_actor(user, actor_id), # with {:is_owned, %Actor{} = actor} <- User.owns_actor(user, actor_id),
# {:todo_list, %TodoList{actor_id: group_id} = todo_list} <- # {:todo_list, %TodoList{actor_id: group_id} = todo_list} <-
# {:todo_list, Todos.get_todo_list(todo_list_id)}, # {:todo_list, Todos.get_todo_list(todo_list_id)},
# {:member, true} <- {:member, Actors.is_member?(actor_id, group_id)}, # {:member, true} <- {:member, Actors.member?(actor_id, group_id)},
# {:ok, _, %TodoList{} = todo} <- # {:ok, _, %TodoList{} = todo} <-
# Actions.Update.update_todo_list(todo_list, actor, true, %{}) do # Actions.Update.update_todo_list(todo_list, actor, true, %{}) do
# {:ok, todo} # {:ok, todo}
@ -144,7 +144,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Todos do
# with {:is_owned, %Actor{} = actor} <- User.owns_actor(user, actor_id), # with {:is_owned, %Actor{} = actor} <- User.owns_actor(user, actor_id),
# {:todo_list, %TodoList{actor_id: group_id} = todo_list} <- # {:todo_list, %TodoList{actor_id: group_id} = todo_list} <-
# {:todo_list, Todos.get_todo_list(todo_list_id)}, # {:todo_list, Todos.get_todo_list(todo_list_id)},
# {:member, true} <- {:member, Actors.is_member?(actor_id, group_id)}, # {:member, true} <- {:member, Actors.member?(actor_id, group_id)},
# {:ok, _, %TodoList{} = todo} <- # {:ok, _, %TodoList{} = todo} <-
# Actions.Delete.delete_todo_list(todo_list, actor, true, %{}) do # Actions.Delete.delete_todo_list(todo_list, actor, true, %{}) do
# {:ok, todo} # {:ok, todo}
@ -169,7 +169,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Todos do
{:todo, Todos.get_todo(todo_id)}, {:todo, Todos.get_todo(todo_id)},
{:todo_list, %TodoList{actor_id: group_id}} <- {:todo_list, %TodoList{actor_id: group_id}} <-
{:todo_list, Todos.get_todo_list(todo_list_id)}, {:todo_list, Todos.get_todo_list(todo_list_id)},
{:member, true} <- {:member, Actors.is_member?(actor_id, group_id)} do {:member, true} <- {:member, Actors.member?(actor_id, group_id)} do
{:ok, todo} {:ok, todo}
else else
{:todo, nil} -> {:todo, nil} ->
@ -194,7 +194,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Todos do
) do ) do
with {:todo_list, %TodoList{actor_id: group_id} = _todo_list} <- with {:todo_list, %TodoList{actor_id: group_id} = _todo_list} <-
{:todo_list, Todos.get_todo_list(todo_list_id)}, {:todo_list, Todos.get_todo_list(todo_list_id)},
{:member, true} <- {:member, Actors.is_member?(actor_id, group_id)}, {:member, true} <- {:member, Actors.member?(actor_id, group_id)},
{:ok, _, %Todo{} = todo} <- {:ok, _, %Todo{} = todo} <-
Actions.Create.create( Actions.Create.create(
:todo, :todo,
@ -228,7 +228,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Todos do
{:todo, Todos.get_todo(todo_id)}, {:todo, Todos.get_todo(todo_id)},
{:todo_list, %TodoList{actor_id: group_id}} <- {:todo_list, %TodoList{actor_id: group_id}} <-
{:todo_list, Todos.get_todo_list(todo_list_id)}, {:todo_list, Todos.get_todo_list(todo_list_id)},
{:member, true} <- {:member, Actors.is_member?(actor_id, group_id)}, {:member, true} <- {:member, Actors.member?(actor_id, group_id)},
{:ok, _, %Todo{} = todo} <- {:ok, _, %Todo{} = todo} <-
Actions.Update.update(todo, args, true, %{}) do Actions.Update.update(todo, args, true, %{}) do
{:ok, todo} {:ok, todo}
@ -259,7 +259,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Todos do
# {:todo, Todos.get_todo(todo_id)}, # {:todo, Todos.get_todo(todo_id)},
# {:todo_list, %TodoList{actor_id: group_id}} <- # {:todo_list, %TodoList{actor_id: group_id}} <-
# {:todo_list, Todos.get_todo_list(todo_list_id)}, # {:todo_list, Todos.get_todo_list(todo_list_id)},
# {:member, true} <- {:member, Actors.is_member?(actor_id, group_id)}, # {:member, true} <- {:member, Actors.member?(actor_id, group_id)},
# {:ok, _, %Todo{} = todo} <- # {:ok, _, %Todo{} = todo} <-
# Actions.Delete.delete_todo(todo, actor, true, %{}) do # Actions.Delete.delete_todo(todo, actor, true, %{}) do
# {:ok, todo} # {:ok, todo}

View file

@ -209,8 +209,8 @@ defmodule Mobilizon.Actors.Actor do
@doc """ @doc """
Checks whether actor visibility is public. Checks whether actor visibility is public.
""" """
@spec is_public_visibility?(t) :: boolean @spec public_visibility?(t) :: boolean
def is_public_visibility?(%__MODULE__{visibility: visibility}) do def public_visibility?(%__MODULE__{visibility: visibility}) do
visibility in [:public, :unlisted] visibility in [:public, :unlisted]
end end

View file

@ -710,8 +710,8 @@ defmodule Mobilizon.Actors do
@doc """ @doc """
Returns whether the `actor_id` is a confirmed member for the group `parent_id` Returns whether the `actor_id` is a confirmed member for the group `parent_id`
""" """
@spec is_member?(integer | String.t(), integer | String.t()) :: boolean() @spec member?(integer | String.t(), integer | String.t()) :: boolean()
def is_member?(actor_id, parent_id) do def member?(actor_id, parent_id) do
match?( match?(
{:ok, %Member{}}, {:ok, %Member{}},
get_member(actor_id, parent_id, @member_roles) get_member(actor_id, parent_id, @member_roles)
@ -721,8 +721,8 @@ defmodule Mobilizon.Actors do
@doc """ @doc """
Returns whether the `actor_id` is a moderator for the group `parent_id` Returns whether the `actor_id` is a moderator for the group `parent_id`
""" """
@spec is_moderator?(integer | String.t(), integer | String.t()) :: boolean() @spec moderator?(integer | String.t(), integer | String.t()) :: boolean()
def is_moderator?(actor_id, parent_id) do def moderator?(actor_id, parent_id) do
match?( match?(
{:ok, %Member{}}, {:ok, %Member{}},
get_member(actor_id, parent_id, @moderator_roles) get_member(actor_id, parent_id, @moderator_roles)
@ -732,8 +732,8 @@ defmodule Mobilizon.Actors do
@doc """ @doc """
Returns whether the `actor_id` is an administrator for the group `parent_id` Returns whether the `actor_id` is an administrator for the group `parent_id`
""" """
@spec is_administrator?(integer | String.t(), integer | String.t()) :: boolean() @spec administrator?(integer | String.t(), integer | String.t()) :: boolean()
def is_administrator?(actor_id, parent_id) do def administrator?(actor_id, parent_id) do
match?( match?(
{:ok, %Member{}}, {:ok, %Member{}},
get_member(actor_id, parent_id, @administrator_roles) get_member(actor_id, parent_id, @administrator_roles)
@ -922,8 +922,8 @@ defmodule Mobilizon.Actors do
@doc """ @doc """
Returns whether the member is the last administrator for a group Returns whether the member is the last administrator for a group
""" """
@spec is_only_administrator?(integer | String.t(), integer | String.t()) :: boolean() @spec only_administrator?(integer | String.t(), integer | String.t()) :: boolean()
def is_only_administrator?(member_id, group_id) do def only_administrator?(member_id, group_id) do
Member Member
|> where( |> where(
[m], [m],

View file

@ -55,9 +55,10 @@ defmodule Mobilizon.Actors.Member do
@doc """ @doc """
Checks whether the member is an administrator (admin or creator) of the group. Checks whether the member is an administrator (admin or creator) of the group.
""" """
def is_administrator(%__MODULE__{role: :administrator}), do: true @spec administrator?(t()) :: boolean()
def is_administrator(%__MODULE__{role: :creator}), do: true def administrator?(%__MODULE__{role: :administrator}), do: true
def is_administrator(%__MODULE__{}), do: false def administrator?(%__MODULE__{role: :creator}), do: true
def administrator?(%__MODULE__{}), do: false
@doc false @doc false
@spec changeset(t | Ecto.Schema.t(), map) :: Ecto.Changeset.t() @spec changeset(t | Ecto.Schema.t(), map) :: Ecto.Changeset.t()

View file

@ -77,7 +77,7 @@ defmodule Mobilizon.Discussions do
|> join(:left, [c], r in Comment, on: r.origin_comment_id == c.id) |> join(:left, [c], r in Comment, on: r.origin_comment_id == c.id)
|> where([c, _], is_nil(c.in_reply_to_comment_id)) |> where([c, _], is_nil(c.in_reply_to_comment_id))
|> where([c], c.visibility in ^@public_visibility) |> where([c], c.visibility in ^@public_visibility)
# TODO: This was added because we don't want to count deleted comments in total_replies. # This was added because we don't want to count deleted comments in total_replies.
# However, it also excludes all top-level comments with deleted replies from being selected # However, it also excludes all top-level comments with deleted replies from being selected
# |> where([_, r], is_nil(r.deleted_at)) # |> where([_, r], is_nil(r.deleted_at))
|> group_by([c], c.id) |> group_by([c], c.id)

View file

@ -515,8 +515,8 @@ defmodule Mobilizon.Events do
|> Page.build_page(page, limit) |> Page.build_page(page, limit)
end end
@spec is_user_moderator_for_event?(integer | String.t(), integer | String.t()) :: boolean @spec user_moderator_for_event?(integer | String.t(), integer | String.t()) :: boolean
def is_user_moderator_for_event?(user_id, event_id) do def user_moderator_for_event?(user_id, event_id) do
Participant Participant
|> join(:inner, [p], a in Actor, on: p.actor_id == a.id) |> join(:inner, [p], a in Actor, on: p.actor_id == a.id)
|> where([p, _a], p.event_id == ^event_id) |> where([p, _a], p.event_id == ^event_id)
@ -1492,14 +1492,14 @@ defmodule Mobilizon.Events do
end end
@spec filter_online(Ecto.Query.t(), map()) :: Ecto.Query.t() @spec filter_online(Ecto.Query.t(), map()) :: Ecto.Query.t()
defp filter_online(query, %{type: :online}), do: is_online_fragment(query, true) defp filter_online(query, %{type: :online}), do: online_fragment_check(query, true)
defp filter_online(query, %{type: :in_person}), do: is_online_fragment(query, false) defp filter_online(query, %{type: :in_person}), do: online_fragment_check(query, false)
defp filter_online(query, _), do: query defp filter_online(query, _), do: query
@spec is_online_fragment(Ecto.Query.t(), boolean()) :: Ecto.Query.t() @spec online_fragment_check(Ecto.Query.t(), boolean()) :: Ecto.Query.t()
defp is_online_fragment(query, value) do defp online_fragment_check(query, value) do
where(query, [q], fragment("(?->>'is_online')::bool = ?", q.options, ^value)) where(query, [q], fragment("(?->>'is_online')::bool = ?", q.options, ^value))
end end

View file

@ -49,8 +49,8 @@ defmodule Mobilizon.Events.Participant do
We start by fetching the list of organizers and if there's only one of them We start by fetching the list of organizers and if there's only one of them
and that it's the actor requesting leaving the event we return true. and that it's the actor requesting leaving the event we return true.
""" """
@spec is_not_only_organizer(integer | String.t(), integer | String.t()) :: boolean @spec not_only_organizer?(integer | String.t(), integer | String.t()) :: boolean
def is_not_only_organizer(event_id, actor_id) do def not_only_organizer?(event_id, actor_id) do
case Events.list_organizers_participants_for_event(event_id) do case Events.list_organizers_participants_for_event(event_id) do
[%__MODULE__{actor: %Actor{id: participant_actor_id}}] -> [%__MODULE__{actor: %Actor{id: participant_actor_id}}] ->
participant_actor_id == actor_id participant_actor_id == actor_id

View file

@ -34,6 +34,10 @@ defmodule Mobilizon.Instances.InstanceActor do
instance_actor instance_actor
|> cast(attrs, @attrs) |> cast(attrs, @attrs)
|> validate_required(@required_attrs) |> validate_required(@required_attrs)
|> validate_length(:domain, max: 254)
|> validate_length(:instance_name, max: 254)
|> validate_length(:software, max: 254)
|> validate_length(:software_version, max: 254)
|> unique_constraint(:domain) |> unique_constraint(:domain)
end end
end end

View file

@ -4,6 +4,7 @@ defmodule Mobilizon.Instances do
""" """
alias Ecto.Adapters.SQL alias Ecto.Adapters.SQL
alias Mobilizon.Actors.{Actor, Follower} alias Mobilizon.Actors.{Actor, Follower}
alias Mobilizon.Federation.ActivityPub.Relay
alias Mobilizon.Instances.{Instance, InstanceActor} alias Mobilizon.Instances.{Instance, InstanceActor}
alias Mobilizon.Storage.{Page, Repo} alias Mobilizon.Storage.{Page, Repo}
import Ecto.Query import Ecto.Query
@ -22,11 +23,15 @@ defmodule Mobilizon.Instances do
order_by_options = Keyword.new([{direction, order_by}]) order_by_options = Keyword.new([{direction, order_by}])
%Actor{id: relay_id} = Relay.get_actor()
query = query =
Instance Instance
|> join(:left, [i], ia in InstanceActor, on: i.domain == ia.domain) |> join(:left, [i], ia in InstanceActor, on: i.domain == ia.domain)
|> join(:left, [_i, ia], a in Actor, on: ia.actor_id == a.id) |> join(:left, [_i, ia], a in Actor, on: ia.actor_id == a.id)
# following
|> join(:left, [_i, _ia, a], f1 in Follower, on: f1.target_actor_id == a.id) |> join(:left, [_i, _ia, a], f1 in Follower, on: f1.target_actor_id == a.id)
# followed
|> join(:left, [_i, _ia, a], f2 in Follower, on: f2.actor_id == a.id) |> join(:left, [_i, _ia, a], f2 in Follower, on: f2.actor_id == a.id)
|> select([i, ia, a, f1, f2], %{ |> select([i, ia, a, f1, f2], %{
instance: i, instance: i,
@ -45,14 +50,27 @@ defmodule Mobilizon.Instances do
if is_nil(filter_domain) or filter_domain == "" do if is_nil(filter_domain) or filter_domain == "" do
query query
else else
where(query, [i], like(i.domain, ^"%#{filter_domain}%")) where(
query,
[i, ia],
like(i.domain, ^"%#{filter_domain}%") or like(ia.instance_name, ^"%#{filter_domain}%")
)
end end
query = query =
case follow_status do case follow_status do
:following -> where(query, [i, s], s.following == true) :following ->
:followed -> where(query, [i, s], s.follower == true) where(query, [_i, _ia, _a, f1], f1.actor_id == ^relay_id and f1.approved == true)
:all -> query
:followed ->
where(
query,
[_i, _ia, _a, _f1, f2],
f2.target_actor_id == ^relay_id and f2.approved == true
)
:all ->
query
end end
%Page{elements: elements} = paged_instances = Page.build_page(query, page, limit, :domain) %Page{elements: elements} = paged_instances = Page.build_page(query, page, limit, :domain)

View file

@ -214,7 +214,7 @@ defmodule Mobilizon.Medias do
query query
|> Repo.all(timeout: :infinity) |> Repo.all(timeout: :infinity)
|> Enum.filter(fn %Media{file: %File{url: url}} -> |> Enum.filter(fn %Media{file: %File{url: url}} ->
!url_is_also_a_profile_file?(url) && is_all_media_orphan?(url, expiration_date) !url_is_also_a_profile_file?(url) && all_media_orphan?(url, expiration_date)
end) end)
|> Enum.chunk_by(fn %Media{file: %File{url: url}} -> |> Enum.chunk_by(fn %Media{file: %File{url: url}} ->
url url
@ -223,14 +223,14 @@ defmodule Mobilizon.Medias do
end) end)
end end
defp is_all_media_orphan?(url, expiration_date) do defp all_media_orphan?(url, expiration_date) do
url url
|> get_all_media_by_url() |> get_all_media_by_url()
|> Enum.all?(&is_media_orphan?(&1, expiration_date)) |> Enum.all?(&media_orphan?(&1, expiration_date))
end end
@spec is_media_orphan?(Media.t(), DateTime.t()) :: boolean() @spec media_orphan?(Media.t(), DateTime.t()) :: boolean()
defp is_media_orphan?(%Media{id: media_id}, expiration_date) do defp media_orphan?(%Media{id: media_id}, expiration_date) do
media_query = media_query =
from(m in Media, from(m in Media,
as: :media, as: :media,

View file

@ -7,6 +7,7 @@ defmodule Mobilizon.Storage.Ecto do
import Ecto.Changeset, only: [fetch_change: 2, put_change: 3, get_field: 2] import Ecto.Changeset, only: [fetch_change: 2, put_change: 3, get_field: 2]
alias Ecto.{Changeset, Query} alias Ecto.{Changeset, Query}
alias Mobilizon.Web.Endpoint alias Mobilizon.Web.Endpoint
alias Mobilizon.Web.Gettext, as: GettextBackend
alias Mobilizon.Web.Router.Helpers, as: Routes alias Mobilizon.Web.Router.Helpers, as: Routes
@doc """ @doc """
@ -56,4 +57,30 @@ defmodule Mobilizon.Storage.Ecto do
changeset changeset
end end
end end
def convert_ecto_errors(%Ecto.Changeset{} = changeset),
do: Ecto.Changeset.traverse_errors(changeset, &translate_error/1)
# Translates an error message using gettext.
defp translate_error({msg, opts}) do
# Because error messages were defined within Ecto, we must
# call the Gettext module passing our Gettext backend. We
# also use the "errors" domain as translations are placed
# in the errors.po file.
# Ecto will pass the :count keyword if the error message is
# meant to be pluralized.
# On your own code and templates, depending on whether you
# need the message to be pluralized or not, this could be
# written simply as:
#
# dngettext "errors", "1 file", "%{count} files", count
# dgettext "errors", "is invalid"
#
if count = opts[:count] do
Gettext.dngettext(GettextBackend, "errors", msg, msg, count, opts)
else
Gettext.dgettext(GettextBackend, "errors", msg, opts)
end
end
end end

View file

@ -55,14 +55,14 @@ defmodule Mobilizon.Service.DateTime do
) )
end end
@spec is_first_day_of_week(Date.t(), String.t()) :: boolean() @spec first_day_of_week?(Date.t(), String.t()) :: boolean()
defp is_first_day_of_week(%Date{} = date, locale) do defp first_day_of_week?(%Date{} = date, locale) do
Date.day_of_week(date) == Cldr.Calendar.first_day_for_locale(locale) Date.day_of_week(date) == Cldr.Calendar.first_day_for_locale(locale)
end end
@spec calculate_first_day_of_week(Date.t(), String.t()) :: Date.t() @spec calculate_first_day_of_week(Date.t(), String.t()) :: Date.t()
def calculate_first_day_of_week(%Date{} = date, locale \\ "en") do def calculate_first_day_of_week(%Date{} = date, locale \\ "en") do
if is_first_day_of_week(date, locale), if first_day_of_week?(date, locale),
do: date, do: date,
else: calculate_first_day_of_week(Date.add(date, -1), locale) else: calculate_first_day_of_week(Date.add(date, -1), locale)
end end
@ -204,11 +204,11 @@ defmodule Mobilizon.Service.DateTime do
compare_to_day = Keyword.get(options, :compare_to_day, Date.utc_today()) compare_to_day = Keyword.get(options, :compare_to_day, Date.utc_today())
locale = Keyword.get(options, :locale, "en") locale = Keyword.get(options, :locale, "en")
is_first_day_of_week(compare_to_day, locale) && is_between_hours?(options) first_day_of_week?(compare_to_day, locale) && is_between_hours?(options)
end end
@spec is_delay_ok_since_last_notification_sent?(DateTime.t(), pos_integer()) :: boolean() @spec delay_ok_since_last_notification_sent?(DateTime.t(), pos_integer()) :: boolean()
def is_delay_ok_since_last_notification_sent?( def delay_ok_since_last_notification_sent?(
%DateTime{} = last_notification_sent, %DateTime{} = last_notification_sent,
delay \\ 3_600 delay \\ 3_600
) do ) do
@ -216,8 +216,8 @@ defmodule Mobilizon.Service.DateTime do
:lt :lt
end end
@spec is_same_day?(DateTime.t(), DateTime.t()) :: boolean() @spec same_day?(DateTime.t(), DateTime.t()) :: boolean()
def is_same_day?(%DateTime{} = one, %DateTime{} = two) do def same_day?(%DateTime{} = one, %DateTime{} = two) do
DateTime.to_date(one) == DateTime.to_date(two) DateTime.to_date(one) == DateTime.to_date(two)
end end

View file

@ -16,7 +16,7 @@ defmodule Mobilizon.Service.Export.Common do
def fetch_actor_event_feed(name, limit) do def fetch_actor_event_feed(name, limit) do
case Actors.get_actor_by_name(name) do case Actors.get_actor_by_name(name) do
%Actor{} = actor -> %Actor{} = actor ->
if Actor.is_public_visibility?(actor) do if Actor.public_visibility?(actor) do
%Page{elements: events} = Events.list_public_upcoming_events_for_actor(actor, 1, limit) %Page{elements: events} = Events.list_public_upcoming_events_for_actor(actor, 1, limit)
%Page{elements: posts} = Posts.get_public_posts_for_group(actor, 1, limit) %Page{elements: posts} = Posts.get_public_posts_for_group(actor, 1, limit)
{:ok, actor, events, posts} {:ok, actor, events, posts}

View file

@ -265,7 +265,7 @@ defmodule Mobilizon.Service.Export.Feed do
end end
defp clear_actor_feed(%Actor{preferred_username: preferred_username} = actor) do defp clear_actor_feed(%Actor{preferred_username: preferred_username} = actor) do
if Actor.is_public_visibility?(actor) do if Actor.public_visibility?(actor) do
Cachex.del(:feed, "actor_#{preferred_username}") Cachex.del(:feed, "actor_#{preferred_username}")
end end
end end

View file

@ -216,7 +216,7 @@ defmodule Mobilizon.Service.Export.ICalendar do
end end
defp clear_actor_feed(%Actor{preferred_username: preferred_username} = actor) do defp clear_actor_feed(%Actor{preferred_username: preferred_username} = actor) do
if Actor.is_public_visibility?(actor) do if Actor.public_visibility?(actor) do
Cachex.del(:ics, "actor_#{preferred_username}") Cachex.del(:ics, "actor_#{preferred_username}")
end end
end end

View file

@ -13,8 +13,8 @@ defmodule Mobilizon.Service.HTTP.Utils do
end end
end end
@spec is_content_type?(Enum.t(), String.t() | list(String.t())) :: boolean @spec content_type_matches?(Enum.t(), String.t() | list(String.t())) :: boolean
def is_content_type?(headers, content_type) do def content_type_matches?(headers, content_type) do
headers headers
|> get_header("Content-Type") |> get_header("Content-Type")
|> content_type_header_matches(content_type) |> content_type_header_matches(content_type)

View file

@ -12,8 +12,8 @@ defmodule Mobilizon.Service.Notifier.Email do
import Mobilizon.Service.DateTime, import Mobilizon.Service.DateTime,
only: [ only: [
is_delay_ok_since_last_notification_sent?: 1, delay_ok_since_last_notification_sent?: 1,
is_delay_ok_since_last_notification_sent?: 2 delay_ok_since_last_notification_sent?: 2
] ]
require Logger require Logger
@ -129,7 +129,7 @@ defmodule Mobilizon.Service.Notifier.Email do
# Delay ok since last notification # Delay ok since last notification
defp match_group_notifications_setting(:one_hour, _, %DateTime{} = last_notification_sent, _) do defp match_group_notifications_setting(:one_hour, _, %DateTime{} = last_notification_sent, _) do
is_delay_ok_since_last_notification_sent?(last_notification_sent) delay_ok_since_last_notification_sent?(last_notification_sent)
end end
# Delay ok since last notification # Delay ok since last notification
@ -139,7 +139,7 @@ defmodule Mobilizon.Service.Notifier.Email do
%DateTime{} = last_notification_sent, %DateTime{} = last_notification_sent,
options options
) do ) do
is_delay_ok_since_last_notification_sent?(last_notification_sent, 3_600 * 23) and delay_ok_since_last_notification_sent?(last_notification_sent, 3_600 * 23) and
Keyword.get(options, :recap, false) != false Keyword.get(options, :recap, false) != false
end end
@ -149,7 +149,7 @@ defmodule Mobilizon.Service.Notifier.Email do
%DateTime{} = last_notification_sent, %DateTime{} = last_notification_sent,
options options
) do ) do
is_delay_ok_since_last_notification_sent?(last_notification_sent, 3_600 * 24 * 6) and delay_ok_since_last_notification_sent?(last_notification_sent, 3_600 * 24 * 6) and
Keyword.get(options, :recap, false) != false Keyword.get(options, :recap, false) != false
end end

View file

@ -11,9 +11,7 @@ defmodule Mobilizon.Service.RichMedia.Parser do
max_body: 2_000_000, max_body: 2_000_000,
timeout: 10_000, timeout: 10_000,
recv_timeout: 20_000, recv_timeout: 20_000,
follow_redirect: true, follow_redirect: true
# TODO: Remove me once Hackney/HTTPoison fixes their issue with TLS1.3 and OTP 23
ssl: [{:versions, [:"tlsv1.2"]}]
] ]
alias Mobilizon.Config alias Mobilizon.Config
@ -75,7 +73,7 @@ defmodule Mobilizon.Service.RichMedia.Parser do
opts: @options opts: @options
)}, )},
{:is_html, _response_headers, true} <- {:is_html, _response_headers, true} <-
{:is_html, response_headers, is_html?(response_headers)} do {:is_html, response_headers, html?(response_headers)} do
body body
|> convert_utf8(response_headers) |> convert_utf8(response_headers)
|> maybe_parse() |> maybe_parse()
@ -108,21 +106,21 @@ defmodule Mobilizon.Service.RichMedia.Parser do
defp get_data_for_media(response_headers, url) do defp get_data_for_media(response_headers, url) do
data = %{title: get_filename_from_headers(response_headers) || get_filename_from_url(url)} data = %{title: get_filename_from_headers(response_headers) || get_filename_from_url(url)}
if is_image?(response_headers) do if image?(response_headers) do
Map.put(data, :image_remote_url, url) Map.put(data, :image_remote_url, url)
else else
data data
end end
end end
@spec is_html?(Enum.t()) :: boolean @spec html?(Enum.t()) :: boolean
defp is_html?(headers) do defp html?(headers) do
is_content_type?(headers, ["text/html", "application/xhtml"]) content_type_matches?(headers, ["text/html", "application/xhtml"])
end end
@spec is_image?(Enum.t()) :: boolean @spec image?(Enum.t()) :: boolean
defp is_image?(headers) do defp image?(headers) do
is_content_type?(headers, ["image/"]) content_type_matches?(headers, ["image/"])
end end
@spec get_filename_from_headers(Enum.t()) :: String.t() | nil @spec get_filename_from_headers(Enum.t()) :: String.t() | nil

View file

@ -81,7 +81,7 @@ defmodule Mobilizon.Service.Workers.LegacyNotifierBuilder do
options options
) do ) do
mentionned_actor_ids mentionned_actor_ids
|> Enum.filter(&Actors.is_member?(&1, Keyword.fetch!(options, :group_id))) |> Enum.filter(&Actors.member?(&1, Keyword.fetch!(options, :group_id)))
|> users_from_actor_ids(Keyword.fetch!(options, :author_id)) |> users_from_actor_ids(Keyword.fetch!(options, :author_id))
end end

View file

@ -13,6 +13,7 @@ defmodule Mobilizon.Service.Workers.RefreshInstances do
alias Mobilizon.Instances.{Instance, InstanceActor} alias Mobilizon.Instances.{Instance, InstanceActor}
alias Oban.Job alias Oban.Job
require Logger require Logger
import Mobilizon.Storage.Ecto, only: [convert_ecto_errors: 1]
@impl Oban.Worker @impl Oban.Worker
@spec perform(Oban.Job.t()) :: :ok @spec perform(Oban.Job.t()) :: :ok
@ -56,6 +57,10 @@ defmodule Mobilizon.Service.Workers.RefreshInstances do
Instances.create_instance_actor(args) do Instances.create_instance_actor(args) do
Logger.info("Saved instance actor details for domain #{host}") Logger.info("Saved instance actor details for domain #{host}")
else else
{:error, %Ecto.Changeset{} = changeset} ->
Logger.error("Unable to save instance \"#{domain}\" metadata")
Logger.debug(convert_ecto_errors(changeset))
err -> err ->
Logger.error(inspect(err)) Logger.error(inspect(err))
end end

View file

@ -16,7 +16,7 @@ defmodule Mobilizon.Service.Workers.SendActivityRecapWorker do
only: [ only: [
is_between_hours?: 1, is_between_hours?: 1,
is_between_hours_on_first_day?: 1, is_between_hours_on_first_day?: 1,
is_delay_ok_since_last_notification_sent?: 1 delay_ok_since_last_notification_sent?: 1
] ]
@impl Oban.Worker @impl Oban.Worker
@ -108,7 +108,7 @@ defmodule Mobilizon.Service.Workers.SendActivityRecapWorker do
"Testing if it's less than an hour since the last time we sent an activity recap" "Testing if it's less than an hour since the last time we sent an activity recap"
) )
is_delay_ok_since_last_notification_sent?(last_notification_sent) delay_ok_since_last_notification_sent?(last_notification_sent)
end end
# If we're between notification hours # If we're between notification hours

View file

@ -173,26 +173,26 @@ defmodule Mobilizon.Web.PageController do
end end
end end
@spec is_visible?(map) :: boolean() @spec visible?(map) :: boolean()
defp is_visible?(%{visibility: v}), do: v in [:public, :unlisted] defp visible?(%{visibility: v}), do: v in [:public, :unlisted]
defp is_visible?(%Tombstone{}), do: true defp visible?(%Tombstone{}), do: true
defp is_visible?(_), do: true defp visible?(_), do: true
@spec ok_status?(cache_status) :: boolean() @spec ok_status?(cache_status) :: boolean()
defp ok_status?(status), do: status in [:ok, :commit] defp ok_status?(status), do: status in [:ok, :commit]
@typep cache_status :: :ok | :commit | :ignore @typep cache_status :: :ok | :commit | :ignore
@spec ok_status_and_is_visible?(Plug.Conn.t(), cache_status, map()) :: boolean() @spec ok_status_and_visible?(Plug.Conn.t(), cache_status, map()) :: boolean()
defp ok_status_and_is_visible?(_conn, status, o), defp ok_status_and_visible?(_conn, status, o),
do: ok_status?(status) and is_visible?(o) do: ok_status?(status) and visible?(o)
defp checks?(conn, status, o) do defp checks?(conn, status, o) do
cond do cond do
ok_status_and_is_visible?(conn, status, o) -> ok_status_and_visible?(conn, status, o) ->
if is_local?(o) == :remote && get_format(conn) == "activity-json", do: :remote, else: true if local?(o) == :remote && get_format(conn) == "activity-json", do: :remote, else: true
is_person?(o) && get_format(conn) == "activity-json" -> person?(o) && get_format(conn) == "activity-json" ->
true true
true -> true ->
@ -200,9 +200,9 @@ defmodule Mobilizon.Web.PageController do
end end
end end
@spec is_local?(map()) :: boolean | :remote @spec local?(map()) :: boolean | :remote
defp is_local?(%{local: local}), do: if(local, do: true, else: :remote) defp local?(%{local: local}), do: if(local, do: true, else: :remote)
defp is_local?(_), do: false defp local?(_), do: false
@spec maybe_add_noindex_header(Plug.Conn.t(), map()) :: Plug.Conn.t() @spec maybe_add_noindex_header(Plug.Conn.t(), map()) :: Plug.Conn.t()
defp maybe_add_noindex_header(conn, %{visibility: visibility}) defp maybe_add_noindex_header(conn, %{visibility: visibility})
@ -212,9 +212,9 @@ defmodule Mobilizon.Web.PageController do
defp maybe_add_noindex_header(conn, _), do: conn defp maybe_add_noindex_header(conn, _), do: conn
@spec is_person?(Actor.t()) :: boolean() @spec person?(Actor.t()) :: boolean()
defp is_person?(%Actor{type: :Person}), do: true defp person?(%Actor{type: :Person}), do: true
defp is_person?(_), do: false defp person?(_), do: false
defp maybe_add_content_type_header(conn) do defp maybe_add_content_type_header(conn) do
case get_format(conn) do case get_format(conn) do

View file

@ -87,8 +87,6 @@ defmodule Mobilizon.Web.Email.Group do
end end
end end
# TODO : def send_confirmation_to_inviter()
@member_roles [:administrator, :moderator, :member] @member_roles [:administrator, :moderator, :member]
@spec send_group_suspension_notification(Member.t()) :: :ok @spec send_group_suspension_notification(Member.t()) :: :ok
def send_group_suspension_notification(%Member{actor: %Actor{user_id: nil}}), do: :ok def send_group_suspension_notification(%Member{actor: %Actor{user_id: nil}}), do: :ok

View file

@ -124,6 +124,4 @@ defmodule Mobilizon.Web.Email.Member do
:ok :ok
end end
end end
# TODO : def send_confirmation_to_inviter()
end end

View file

@ -71,13 +71,7 @@ defmodule Mobilizon.Web.MediaProxy do
@compile {:no_warn_undefined, {:crypto, :mac, 4}} @compile {:no_warn_undefined, {:crypto, :mac, 4}}
@compile {:no_warn_undefined, {:crypto, :hmac, 3}} @compile {:no_warn_undefined, {:crypto, :hmac, 3}}
defp sha_hmac(key, url) do defp sha_hmac(key, url) do
# :crypto.hmac was removed in OTP24, but :crypto.mac was added in OTP 22.1
# TODO: Remove me when we don't support OTP 21/22 anymore
if function_exported?(:crypto, :mac, 4) do
:crypto.mac(:hmac, :sha, key, url) :crypto.mac(:hmac, :sha, key, url)
else
:crypto.hmac(:sha, key, url)
end
end end
@spec filename(String.t()) :: String.t() | nil @spec filename(String.t()) :: String.t() | nil

View file

@ -6,7 +6,7 @@
timezone: @timezone, timezone: @timezone,
locale: @locale locale: @locale
) %> ) %>
<% is_same_day?(@start_date, @end_date) -> %> <% same_day?(@start_date, @end_date) -> %>
<strong> <strong>
<%= gettext("On %{date} from %{start_time} to %{end_time}", <%= gettext("On %{date} from %{start_time} to %{end_time}",
date: datetime_to_date_string(@start_date, @locale), date: datetime_to_date_string(@start_date, @locale),

View file

@ -1 +1 @@
<%= cond do %><% @end_date == nil -> %><%= render("date/event_tz_date.text", date: @start_date, event: @event, timezone: @timezone, locale: @locale) %><% is_same_day?(@start_date, @end_date) -> %><%= gettext "On %{date} from %{start_time} to %{end_time}", date: datetime_to_date_string(@start_date, @locale), start_time: datetime_to_time_string(@start_date, @locale), end_time: datetime_to_time_string(@end_date, @locale) %><%= if @event.options.timezone != @timezone do %> <%= gettext "🌐 %{timezone} %{offset}", timezone: @event.options.timezone, offset: Cldr.DateTime.Formatter.zone_gmt(@start_date) %><% end %><% true -> %><%= gettext "From the %{start} to the %{end}", start: datetime_to_string(@start_date, @locale, :short), end: datetime_to_string(@end_date, @locale, :short) %><%= if @event.options.timezone != @timezone do %> <%= gettext "🌐 %{timezone} %{offset}", timezone: @event.options.timezone, offset: Cldr.DateTime.Formatter.zone_gmt(@start_date) %><% end %><% end %> <%= cond do %><% @end_date == nil -> %><%= render("date/event_tz_date.text", date: @start_date, event: @event, timezone: @timezone, locale: @locale) %><% same_day?(@start_date, @end_date) -> %><%= gettext "On %{date} from %{start_time} to %{end_time}", date: datetime_to_date_string(@start_date, @locale), start_time: datetime_to_time_string(@start_date, @locale), end_time: datetime_to_time_string(@end_date, @locale) %><%= if @event.options.timezone != @timezone do %> <%= gettext "🌐 %{timezone} %{offset}", timezone: @event.options.timezone, offset: Cldr.DateTime.Formatter.zone_gmt(@start_date) %><% end %><% true -> %><%= gettext "From the %{start} to the %{end}", start: datetime_to_string(@start_date, @locale, :short), end: datetime_to_string(@end_date, @locale, :short) %><%= if @event.options.timezone != @timezone do %> <%= gettext "🌐 %{timezone} %{offset}", timezone: @event.options.timezone, offset: Cldr.DateTime.Formatter.zone_gmt(@start_date) %><% end %><% end %>

View file

@ -15,7 +15,7 @@
document.documentElement.classList.remove('dark') document.documentElement.classList.remove('dark')
} }
</script> </script>
<%= if is_root(assigns) do %> <%= if root?(assigns) do %>
<link rel="preload" href="/img/shape-1.svg" as="image" /> <link rel="preload" href="/img/shape-1.svg" as="image" />
<link rel="preload" href="/img/shape-2.svg" as="image" /> <link rel="preload" href="/img/shape-2.svg" as="image" />
<link rel="preload" href="/img/shape-3.svg" as="image" /> <link rel="preload" href="/img/shape-3.svg" as="image" />

View file

@ -25,7 +25,7 @@ defmodule Mobilizon.Web.EmailView do
defdelegate datetime_tz_convert(datetime, timezone), to: DateTimeRenderer defdelegate datetime_tz_convert(datetime, timezone), to: DateTimeRenderer
defdelegate datetime_relative(datetime, locale \\ "en"), to: DateTimeRenderer defdelegate datetime_relative(datetime, locale \\ "en"), to: DateTimeRenderer
defdelegate render_address(address), to: Address defdelegate render_address(address), to: Address
defdelegate is_same_day?(one, two), to: DateTimeRenderer defdelegate same_day?(one, two), to: DateTimeRenderer
defdelegate display_name(actor), to: Actor defdelegate display_name(actor), to: Actor
defdelegate preferred_username_and_domain(actor), to: Actor defdelegate preferred_username_and_domain(actor), to: Actor

View file

@ -87,8 +87,8 @@ defmodule Mobilizon.Web.PageView do
assigns |> Map.get(:locale, "en") |> get_language_direction() assigns |> Map.get(:locale, "en") |> get_language_direction()
end end
@spec is_root(map()) :: boolean() @spec root?(map()) :: boolean()
def is_root(assigns) do def root?(assigns) do
assigns |> Map.get(:conn, %{request_path: "/"}) |> Map.get(:request_path, "/") == "/" assigns |> Map.get(:conn, %{request_path: "/"}) |> Map.get(:request_path, "/") == "/"
end end
end end

View file

@ -22,6 +22,10 @@
"**/*.{js,ts,vue}": [ "**/*.{js,ts,vue}": [
"eslint --fix", "eslint --fix",
"prettier --write" "prettier --write"
],
"**/*.{ex,exs,eex,heex}": [
"mix format",
"mix credo"
] ]
}, },
"dependencies": { "dependencies": {

View file

@ -0,0 +1,15 @@
defmodule Mobilizon.Storage.Repo.Migrations.ChangeActorInstanceDescriptionTypeToText do
use Ecto.Migration
def up do
alter table(:instance_actors) do
modify(:instance_description, :text)
end
end
def down do
alter table(:instance_actors) do
modify(:instance_description, :string)
end
end
end

View file

@ -1641,5 +1641,6 @@
"Announcements for {eventTitle}": "Announcements for {eventTitle}", "Announcements for {eventTitle}": "Announcements for {eventTitle}",
"Visit {instance_domain}": "Visit {instance_domain}", "Visit {instance_domain}": "Visit {instance_domain}",
"Software details: {software_details}": "Software details: {software_details}", "Software details: {software_details}": "Software details: {software_details}",
"Only instances with an application actor can be followed": "Only instances with an application actor can be followed" "Only instances with an application actor can be followed": "Only instances with an application actor can be followed",
"Domain or instance name": "Domain or instance name"
} }

View file

@ -1635,5 +1635,6 @@
"Announcements for {eventTitle}": "Annonces pour {eventTitle}", "Announcements for {eventTitle}": "Annonces pour {eventTitle}",
"Visit {instance_domain}": "Visiter {instance_domain}", "Visit {instance_domain}": "Visiter {instance_domain}",
"Software details: {software_details}": "Détails du logiciel : {software_details}", "Software details: {software_details}": "Détails du logiciel : {software_details}",
"Only instances with an application actor can be followed": "Seules les instances avec un acteur application peuvent être suivies" "Only instances with an application actor can be followed": "Seules les instances avec un acteur application peuvent être suivies",
"Domain or instance name": "Domaine ou nom de l'instance"
} }

View file

@ -54,15 +54,14 @@
> >
</o-field> </o-field>
<o-field <o-field
:label="t('Domain')" :label="t('Domain or instance name')"
label-for="domain-filter" label-for="domain-filter"
class="flex-auto" class="flex-auto"
> >
<o-input <o-input
id="domain-filter" id="domain-filter"
:placeholder="t('mobilizon-instance.tld')" :placeholder="t('mobilizon-instance.tld')"
:value="filterDomain" v-model="filterDomain"
@input="debouncedUpdateDomainFilter"
/> />
</o-field> </o-field>
</div> </div>
@ -223,7 +222,6 @@ import { Paginate } from "@/types/paginate";
import RouteName from "../../router/name"; import RouteName from "../../router/name";
import { IInstance } from "@/types/instance.model"; import { IInstance } from "@/types/instance.model";
import EmptyContent from "@/components/Utils/EmptyContent.vue"; import EmptyContent from "@/components/Utils/EmptyContent.vue";
import debounce from "lodash/debounce";
import { import {
InstanceFilterFollowStatus, InstanceFilterFollowStatus,
InstanceFollowStatus, InstanceFollowStatus,
@ -254,12 +252,16 @@ const followStatus = useRouteQuery(
const { result: instancesResult } = useQuery<{ const { result: instancesResult } = useQuery<{
instances: Paginate<IInstance>; instances: Paginate<IInstance>;
}>(INSTANCES, () => ({ }>(
INSTANCES,
() => ({
page: instancePage.value, page: instancePage.value,
limit: INSTANCES_PAGE_LIMIT, limit: INSTANCES_PAGE_LIMIT,
filterDomain: filterDomain.value, filterDomain: filterDomain.value,
filterFollowStatus: followStatus.value, filterFollowStatus: followStatus.value,
})); }),
{ debounce: 500 }
);
const instances = computed(() => instancesResult.value?.instances); const instances = computed(() => instancesResult.value?.instances);
@ -276,13 +278,6 @@ const newRelayAddress = ref("");
// relayFollowers: Paginate<IFollower> = { elements: [], total: 0 }; // relayFollowers: Paginate<IFollower> = { elements: [], total: 0 };
const updateDomainFilter = (event: InputEvent) => {
const newValue = (event.target as HTMLInputElement).value;
filterDomain.value = newValue;
};
const debouncedUpdateDomainFilter = debounce(updateDomainFilter, 500);
const hasFilter = computed((): boolean => { const hasFilter = computed((): boolean => {
return ( return (
followStatus.value !== InstanceFilterFollowStatus.ALL || followStatus.value !== InstanceFilterFollowStatus.ALL ||

View file

@ -179,7 +179,7 @@ defmodule Mix.Tasks.Mobilizon.Actors.NewTest do
assert %Actor{name: @group_name, preferred_username: @group_username, id: group_id} = assert %Actor{name: @group_name, preferred_username: @group_username, id: group_id} =
Actors.get_group_by_title(@group_username) Actors.get_group_by_title(@group_username)
assert Actors.is_administrator?(admin_id, group_id) assert Actors.administrator?(admin_id, group_id)
end end
end end
end end