diff --git a/lib/federation/activity_pub/activity_pub.ex b/lib/federation/activity_pub/activity_pub.ex
index 94a65a962..caf110f81 100644
--- a/lib/federation/activity_pub/activity_pub.ex
+++ b/lib/federation/activity_pub/activity_pub.ex
@@ -41,7 +41,7 @@ defmodule Mobilizon.Federation.ActivityPub do
alias Mobilizon.Federation.ActivityPub.Actor, as: ActivityPubActor
- alias Mobilizon.Federation.ActivityPub.Types.{Managable, Ownable}
+ alias Mobilizon.Federation.ActivityPub.Types.{Entity, Managable, Ownable}
alias Mobilizon.Federation.ActivityStream.Convertible
alias Mobilizon.Federation.HTTPSignatures.Signature
@@ -56,11 +56,9 @@ defmodule Mobilizon.Federation.ActivityPub do
@public_ap_adress "https://www.w3.org/ns/activitystreams#Public"
- @doc """
- Wraps an object into an activity
- """
+ # Wraps an object into an activity
@spec create_activity(map(), boolean()) :: {:ok, Activity.t()}
- def create_activity(map, local \\ true) when is_map(map) do
+ defp create_activity(map, local) when is_map(map) do
with map <- lazy_put_activity_defaults(map) do
{:ok,
%Activity{
@@ -168,7 +166,7 @@ defmodule Mobilizon.Federation.ActivityPub do
* Federates (asynchronously) the activity
* Returns the activity
"""
- @spec create(atom(), map(), boolean, map()) :: {:ok, Activity.t(), struct()} | any()
+ @spec create(atom(), map(), boolean, map()) :: {:ok, Activity.t(), Entity.entities()} | any()
def create(type, args, local \\ false, additional \\ %{}) do
Logger.debug("creating an activity")
Logger.debug(inspect(args))
@@ -206,7 +204,8 @@ defmodule Mobilizon.Federation.ActivityPub do
* Federates (asynchronously) the activity
* Returns the activity
"""
- @spec update(struct(), map(), boolean, map()) :: {:ok, Activity.t(), struct()} | any()
+ @spec update(Entity.entities(), map(), boolean, map()) ::
+ {:ok, Activity.t(), Entity.entities()} | any()
def update(old_entity, args, local \\ false, additional \\ %{}) do
Logger.debug("updating an activity")
Logger.debug(inspect(args))
@@ -224,6 +223,12 @@ defmodule Mobilizon.Federation.ActivityPub do
end
end
+ @type acceptable_types :: :join | :follow | :invite
+ @type acceptable_entities ::
+ accept_join_entities | accept_follow_entities | accept_invite_entities
+
+ @spec accept(acceptable_types, acceptable_entities, boolean, map) ::
+ {:ok, ActivityStream.t(), acceptable_entities}
def accept(type, entity, local \\ true, additional \\ %{}) do
Logger.debug("We're accepting something")
@@ -246,6 +251,8 @@ defmodule Mobilizon.Federation.ActivityPub do
end
end
+ @spec reject(acceptable_types, acceptable_entities, boolean, map) ::
+ {:ok, ActivityStream.t(), acceptable_entities}
def reject(type, entity, local \\ true, additional \\ %{}) do
{:ok, entity, update_data} =
case type do
@@ -266,6 +273,8 @@ defmodule Mobilizon.Federation.ActivityPub do
end
end
+ @spec announce(Actor.t(), ActivityStream.t(), String.t() | nil, boolean, boolean) ::
+ {:ok, Activity.t(), ActivityStream.t()}
def announce(
%Actor{} = actor,
object,
@@ -286,6 +295,8 @@ defmodule Mobilizon.Federation.ActivityPub do
end
end
+ @spec unannounce(Actor.t(), ActivityStream.t(), String.t() | nil, String.t() | nil, boolean) ::
+ {:ok, Activity.t(), ActivityStream.t()}
def unannounce(
%Actor{} = actor,
object,
@@ -306,6 +317,8 @@ defmodule Mobilizon.Federation.ActivityPub do
@doc """
Make an actor follow another
"""
+ @spec follow(Actor.t(), Actor.t(), String.t() | nil, boolean, map) ::
+ {:ok, Activity.t(), Follower.t()} | {:error, String.t()}
def follow(
%Actor{} = follower,
%Actor{} = followed,
@@ -336,7 +349,8 @@ defmodule Mobilizon.Federation.ActivityPub do
@doc """
Make an actor unfollow another
"""
- @spec unfollow(Actor.t(), Actor.t(), String.t(), boolean()) :: {:ok, map()} | any()
+ @spec unfollow(Actor.t(), Actor.t(), String.t() | nil, boolean()) ::
+ {:ok, Activity.t(), Follower.t()}
def unfollow(%Actor{} = follower, %Actor{} = followed, activity_id \\ nil, local \\ true) do
with {:ok, %Follower{id: follow_id} = follow} <- Actors.unfollow(followed, follower),
# We recreate the follow activity
@@ -357,6 +371,7 @@ defmodule Mobilizon.Federation.ActivityPub do
end
end
+ @spec delete(Entity.t(), Actor.t(), boolean, map) :: {:ok, Activity.t(), Entity.t()}
def delete(object, actor, local \\ true, additional \\ %{}) do
with {:ok, activity_data, actor, object} <-
Managable.delete(object, actor, local, additional),
@@ -369,6 +384,9 @@ defmodule Mobilizon.Federation.ActivityPub do
end
end
+ @spec join(Event.t(), Actor.t(), boolean, map) ::
+ {:ok, Activity.t(), Participant.t()} | {:maximum_attendee_capacity, any}
+ @spec join(Actor.t(), Actor.t(), boolean, map) :: {:ok, Activity.t(), Member.t()}
def join(entity_to_join, actor_joining, local \\ true, additional \\ %{})
def join(%Event{} = event, %Actor{} = actor, local, additional) do
@@ -397,6 +415,8 @@ defmodule Mobilizon.Federation.ActivityPub do
end
end
+ @spec leave(Event.t(), Actor.t(), boolean, map) :: {:ok, Activity.t(), Participant.t()}
+ @spec leave(Actor.t(), Actor.t(), boolean, map) :: {:ok, Activity.t(), Member.t()}
def leave(object, actor, local \\ true, additional \\ %{})
@doc """
@@ -462,6 +482,7 @@ defmodule Mobilizon.Federation.ActivityPub do
end
end
+ @spec remove(Member.t(), Actor.t(), Actor.t(), boolean, map) :: {:ok, Activity.t(), Member.t()}
def remove(
%Member{} = member,
%Actor{type: :Group, url: group_url, members_url: group_members_url},
@@ -502,7 +523,7 @@ defmodule Mobilizon.Federation.ActivityPub do
) do
Logger.debug("Handling #{actor_url} invite to #{group_url} sent to #{target_actor_url}")
- with {:is_able_to_invite, true} <- {:is_able_to_invite, is_able_to_invite(actor, group)},
+ with {:is_able_to_invite, true} <- {:is_able_to_invite, is_able_to_invite?(actor, group)},
{:ok, %Member{url: member_url} = member} <-
Actors.create_member(%{
parent_id: group_id,
@@ -538,7 +559,8 @@ defmodule Mobilizon.Federation.ActivityPub do
end
end
- defp is_able_to_invite(%Actor{domain: actor_domain, id: actor_id}, %Actor{
+ @spec is_able_to_invite?(Actor.t(), Actor.t()) :: boolean
+ defp is_able_to_invite?(%Actor{domain: actor_domain, id: actor_id}, %Actor{
domain: group_domain,
id: group_id
}) do
@@ -547,12 +569,17 @@ defmodule Mobilizon.Federation.ActivityPub do
true
else
# If local group, we'll send the invite
- with {:ok, %Member{} = admin_member} <- Actors.get_member(actor_id, group_id) do
- Member.is_administrator(admin_member)
+ case Actors.get_member(actor_id, group_id) do
+ {:ok, %Member{} = admin_member} ->
+ Member.is_administrator(admin_member)
+
+ _ ->
+ false
end
end
end
+ @spec move(:resource, Resource.t(), map, boolean, map) :: {:ok, Activity.t(), Resource.t()}
def move(type, old_entity, args, local \\ false, additional \\ %{}) do
Logger.debug("We're moving something")
Logger.debug(inspect(args))
@@ -572,6 +599,7 @@ defmodule Mobilizon.Federation.ActivityPub do
end
end
+ @spec flag(map, boolean, map) :: {:ok, Activity.t(), Report.t()}
def flag(args, local \\ false, additional \\ %{}) do
with {report, report_as_data} <- Types.Reports.flag(args, local, additional),
{:ok, activity} <- create_activity(report_as_data, local),
@@ -615,6 +643,7 @@ defmodule Mobilizon.Federation.ActivityPub do
end)
end
+ @spec convert_followers_in_recipients(list(String.t())) :: {list(String.t()), list(String.t())}
defp convert_followers_in_recipients(recipients) do
Enum.reduce(recipients, {recipients, []}, fn recipient, {recipients, follower_actors} = acc ->
case Actors.get_actor_by_followers_url(recipient) do
@@ -678,6 +707,8 @@ defmodule Mobilizon.Federation.ActivityPub do
@doc """
Publish an activity to a specific inbox
"""
+ @spec publish_one(%{inbox: String.t(), json: String.t(), actor: Actor.t(), id: String.t()}) ::
+ Tesla.Env.result()
def publish_one(%{inbox: inbox, json: json, actor: actor, id: id}) do
Logger.info("Federating #{id} to #{inbox}")
%URI{host: host, path: path} = URI.parse(inbox)
@@ -711,7 +742,7 @@ defmodule Mobilizon.Federation.ActivityPub do
@doc """
Return all public activities (events & comments) for an actor
"""
- @spec fetch_public_activities_for_actor(Actor.t(), integer(), integer()) :: map()
+ @spec fetch_public_activities_for_actor(Actor.t(), pos_integer(), pos_integer()) :: map()
def fetch_public_activities_for_actor(%Actor{id: actor_id} = actor, page \\ 1, limit \\ 10) do
%Actor{id: relay_actor_id} = Relay.get_actor()
@@ -769,6 +800,8 @@ defmodule Mobilizon.Federation.ActivityPub do
defp check_for_tombstones(%{url: url}), do: Tombstone.find_tombstone(url)
defp check_for_tombstones(_), do: nil
+ @typep accept_follow_entities :: Follower.t()
+
@spec accept_follow(Follower.t(), map) :: {:ok, Follower.t(), Activity.t()} | any
defp accept_follow(%Follower{} = follower, additional) do
with {:ok, %Follower{} = follower} <- Actors.update_follower(follower, %{approved: true}),
@@ -792,7 +825,10 @@ defmodule Mobilizon.Federation.ActivityPub do
end
end
- @spec accept_join(Participant.t(), map) :: {:ok, Participant.t(), Activity.t()} | any
+ @typep accept_join_entities :: Participant.t() | Member.t()
+
+ @spec accept_join(Participant.t(), map) :: {:ok, Participant.t(), Activity.t()}
+ @spec accept_join(Member.t(), map) :: {:ok, Member.t(), Activity.t()}
defp accept_join(%Participant{} = participant, additional) do
with {:ok, %Participant{} = participant} <-
Events.update_participant(participant, %{role: :participant}),
@@ -820,7 +856,6 @@ defmodule Mobilizon.Federation.ActivityPub do
end
end
- @spec accept_join(Member.t(), map) :: {:ok, Member.t(), Activity.t()} | any
defp accept_join(%Member{} = member, additional) do
with {:ok, %Member{} = member} <-
Actors.update_member(member, %{role: :member}),
@@ -854,6 +889,8 @@ defmodule Mobilizon.Federation.ActivityPub do
end
end
+ @typep accept_invite_entities :: Member.t()
+
@spec accept_invite(Member.t(), map()) :: {:ok, Member.t(), Activity.t()} | any
defp accept_invite(
%Member{invited_by_id: invited_by_id, actor_id: actor_id} = member,
@@ -881,6 +918,7 @@ defmodule Mobilizon.Federation.ActivityPub do
end
end
+ @spec maybe_refresh_group(Member.t()) :: :ok | nil
defp maybe_refresh_group(%Member{
parent: %Actor{domain: parent_domain, url: parent_url},
actor: %Actor{} = actor
diff --git a/lib/federation/activity_pub/actor.ex b/lib/federation/activity_pub/actor.ex
index 3ac28f57d..cd02fa4d2 100644
--- a/lib/federation/activity_pub/actor.ex
+++ b/lib/federation/activity_pub/actor.ex
@@ -108,7 +108,7 @@ defmodule Mobilizon.Federation.ActivityPub.Actor do
{:ok, url} when is_binary(url) ->
make_actor_from_url(url, preload)
- _e ->
+ {:error, _e} ->
{:error, "No ActivityPub URL found in WebFinger"}
end
end
diff --git a/lib/federation/activity_pub/audience.ex b/lib/federation/activity_pub/audience.ex
index 2b6a6be5b..f88325086 100644
--- a/lib/federation/activity_pub/audience.ex
+++ b/lib/federation/activity_pub/audience.ex
@@ -99,6 +99,8 @@ defmodule Mobilizon.Federation.ActivityPub.Audience do
}
end
+ @spec get_to_and_cc(Actor.t(), list(), :direct | :private | :public | :unlisted | {:list, any}) ::
+ {list(), list()}
@doc """
Determines the full audience based on mentions for an audience
@@ -118,7 +120,6 @@ defmodule Mobilizon.Federation.ActivityPub.Audience do
* `to` : the mentioned actors and the eventual actor we're replying to
* `cc` : none
"""
- @spec get_to_and_cc(Actor.t(), list(), String.t()) :: {list(), list()}
def get_to_and_cc(%Actor{} = actor, mentions, :public) do
to = [@ap_public | mentions]
cc = [actor.followers_url]
@@ -128,7 +129,6 @@ defmodule Mobilizon.Federation.ActivityPub.Audience do
{to, cc}
end
- @spec get_to_and_cc(Actor.t(), list(), String.t()) :: {list(), list()}
def get_to_and_cc(%Actor{} = actor, mentions, :unlisted) do
to = [actor.followers_url | mentions]
cc = [@ap_public]
@@ -138,7 +138,6 @@ defmodule Mobilizon.Federation.ActivityPub.Audience do
{to, cc}
end
- @spec get_to_and_cc(Actor.t(), list(), String.t()) :: {list(), list()}
def get_to_and_cc(%Actor{} = actor, mentions, :private) do
{to, cc} = get_to_and_cc(actor, mentions, :direct)
@@ -147,7 +146,6 @@ defmodule Mobilizon.Federation.ActivityPub.Audience do
{to, cc}
end
- @spec get_to_and_cc(Actor.t(), list(), String.t()) :: {list(), list()}
def get_to_and_cc(_actor, mentions, :direct) do
{mentions, []}
end
diff --git a/lib/federation/activity_pub/permission.ex b/lib/federation/activity_pub/permission.ex
index 41a14afca..2d43c6ebb 100644
--- a/lib/federation/activity_pub/permission.ex
+++ b/lib/federation/activity_pub/permission.ex
@@ -13,6 +13,8 @@ defmodule Mobilizon.Federation.ActivityPub.Permission do
@member_roles [:member, :moderator, :administrator]
+ @type object :: %{id: String.t(), url: String.t()}
+
@doc """
Check that actor can access the object
"""
@@ -66,8 +68,8 @@ defmodule Mobilizon.Federation.ActivityPub.Permission do
@spec can_manage_group_object?(
existing_object_permissions(),
- Actor.t(),
- any()
+ %Actor{url: String.t()},
+ object()
) :: boolean()
defp can_manage_group_object?(permission, %Actor{url: actor_url} = actor, object) do
if Ownable.group_actor(object) != nil do
@@ -94,7 +96,7 @@ defmodule Mobilizon.Federation.ActivityPub.Permission do
end
end
- @spec activity_actor_is_group_member?(Actor.t(), Entity.t(), atom()) :: boolean()
+ @spec activity_actor_is_group_member?(Actor.t(), object(), atom()) :: boolean()
defp activity_actor_is_group_member?(
%Actor{id: actor_id, url: actor_url},
object,
diff --git a/lib/federation/activity_pub/transmogrifier.ex b/lib/federation/activity_pub/transmogrifier.ex
index 0ebd70a04..cd2c96f7a 100644
--- a/lib/federation/activity_pub/transmogrifier.ex
+++ b/lib/federation/activity_pub/transmogrifier.ex
@@ -951,7 +951,7 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
defp do_handle_incoming_reject_invite(invite_object, %Actor{} = actor_rejecting) do
with {:invite, {:ok, %Member{role: :invited, actor_id: actor_id} = member}} <-
{:invite, get_member(invite_object)},
- {:same_actor, true} <- {:same_actor, actor_rejecting.id === actor_id},
+ {:same_actor, true} <- {:same_actor, actor_rejecting.id == actor_id},
{:ok, activity, member} <-
ActivityPub.reject(:invite, member, false) do
{:ok, activity, member}
diff --git a/lib/federation/activity_pub/types/actors.ex b/lib/federation/activity_pub/types/actors.ex
index f7d7d4d95..0dd34cb63 100644
--- a/lib/federation/activity_pub/types/actors.ex
+++ b/lib/federation/activity_pub/types/actors.ex
@@ -1,10 +1,11 @@
defmodule Mobilizon.Federation.ActivityPub.Types.Actors do
@moduledoc false
alias Mobilizon.Actors
- alias Mobilizon.Actors.{Actor, Follower, Member}
+ alias Mobilizon.Actors.{Actor, Follower, Member, MemberRole}
alias Mobilizon.Federation.ActivityPub
alias Mobilizon.Federation.ActivityPub.{Audience, Permission, Relay}
alias Mobilizon.Federation.ActivityPub.Types.Entity
+ alias Mobilizon.Federation.ActivityStream
alias Mobilizon.Federation.ActivityStream.Convertible
alias Mobilizon.GraphQL.API.Utils, as: APIUtils
alias Mobilizon.Service.Activity.Group, as: GroupActivity
@@ -17,7 +18,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Actors do
@behaviour Entity
@impl Entity
- @spec create(map(), map()) :: {:ok, map()}
+ @spec create(map(), map()) :: {:ok, Actor.t(), ActivityStream.t()}
def create(args, additional) do
with args <- prepare_args_for_actor(args),
{:ok, %Actor{} = actor} <- Actors.create_actor(args),
@@ -35,7 +36,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Actors do
end
@impl Entity
- @spec update(Actor.t(), map, map) :: {:ok, Actor.t(), Activity.t()} | any
+ @spec update(Actor.t(), map, map) :: {:ok, Actor.t(), ActivityStream.t()}
def update(%Actor{} = old_actor, args, additional) do
with {:ok, %Actor{} = new_actor} <- Actors.update_actor(old_actor, args),
{:ok, _} <-
@@ -57,6 +58,8 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Actors do
@public_ap "https://www.w3.org/ns/activitystreams#Public"
@impl Entity
+ @spec delete(Actor.t(), Actor.t(), boolean, map) ::
+ {:ok, ActivityStream.t(), Actor.t(), Actor.t()}
def delete(
%Actor{
followers_url: followers_url,
@@ -100,10 +103,13 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Actors do
end
end
+ @spec actor(Actor.t()) :: Actor.t() | nil
def actor(%Actor{} = actor), do: actor
+ @spec actor(Actor.t()) :: Actor.t() | nil
def group_actor(%Actor{} = actor), do: actor
+ @spec permissions(Actor.t()) :: Permission.t()
def permissions(%Actor{} = _group) do
%Permission{
access: :member,
@@ -113,7 +119,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Actors do
}
end
- @spec join(Actor.t(), Actor.t(), boolean(), map()) :: {:ok, map(), Member.t()}
+ @spec join(Actor.t(), Actor.t(), boolean(), map()) :: {:ok, ActivityStreams.t(), Member.t()}
def join(%Actor{type: :Group} = group, %Actor{} = actor, _local, additional) do
with role <-
additional
@@ -153,6 +159,10 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Actors do
end
end
+ @spec follow(Actor.t(), Actor.t(), boolean, map) ::
+ {:accept, any}
+ | {:ok, ActivityStreams.t(), Follower.t()}
+ | {:error, :no_person, String.t()}
def follow(%Actor{} = follower_actor, %Actor{type: type} = followed, _local, additional)
when type != :Person do
with {:ok, %Follower{} = follower} <-
@@ -165,6 +175,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Actors do
def follow(_, _, _, _), do: {:error, :no_person, "Only group and instances can be followed"}
+ @spec prepare_args_for_actor(map) :: map
defp prepare_args_for_actor(args) do
args
|> maybe_sanitize_username()
@@ -191,8 +202,14 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Actors do
defp maybe_sanitize_summary(args), do: args
# Set the participant to approved if the default role for new participants is :participant
- @spec approve_if_default_role_is_member(Actor.t(), Actor.t(), map(), Member.t(), atom()) ::
- {:ok, map(), Member.t()}
+ @spec approve_if_default_role_is_member(
+ Actor.t(),
+ Actor.t(),
+ ActivityStreams.t(),
+ Member.t(),
+ MemberRole.t()
+ ) ::
+ {:ok, ActivityStreams.t(), Member.t()}
defp approve_if_default_role_is_member(
%Actor{type: :Group} = group,
%Actor{} = actor,
@@ -202,7 +219,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Actors do
) do
if is_nil(group.domain) && !is_nil(actor.domain) do
cond do
- Mobilizon.Actors.get_default_member_role(group) === :member &&
+ Mobilizon.Actors.get_default_member_role(group) == :member &&
role == :member ->
{:accept,
ActivityPub.accept(
@@ -212,7 +229,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Actors do
%{"actor" => group.url}
)}
- Mobilizon.Actors.get_default_member_role(group) === :not_approved &&
+ Mobilizon.Actors.get_default_member_role(group) == :not_approved &&
role == :not_approved ->
Scheduler.pending_membership_notification(group)
{:ok, activity_data, member}
@@ -225,6 +242,8 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Actors do
end
end
+ @spec approve_if_manually_approves_followers(Follower.t(), ActivityStreams.t()) ::
+ {:accept, any} | {:ok, ActivityStreams.t(), Follower.t()}
defp approve_if_manually_approves_followers(
%Follower{} = follower,
follow_as_data
diff --git a/lib/federation/activity_pub/types/comments.ex b/lib/federation/activity_pub/types/comments.ex
index 2fedb789f..578ba6ed0 100644
--- a/lib/federation/activity_pub/types/comments.ex
+++ b/lib/federation/activity_pub/types/comments.ex
@@ -6,6 +6,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Comments do
alias Mobilizon.Events.{Event, EventOptions}
alias Mobilizon.Federation.ActivityPub.{Audience, Permission}
alias Mobilizon.Federation.ActivityPub.Types.Entity
+ alias Mobilizon.Federation.ActivityStream
alias Mobilizon.Federation.ActivityStream.Converter.Utils, as: ConverterUtils
alias Mobilizon.Federation.ActivityStream.Convertible
alias Mobilizon.GraphQL.API.Utils, as: APIUtils
@@ -20,7 +21,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Comments do
@behaviour Entity
@impl Entity
- @spec create(map(), map()) :: {:ok, map()}
+ @spec create(map(), map()) :: {:ok, Comment.t(), ActivityStream.t()}
def create(args, additional) do
with args <- prepare_args_for_comment(args),
:ok <- make_sure_event_allows_commenting(args),
@@ -41,7 +42,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Comments do
end
@impl Entity
- @spec update(Comment.t(), map(), map()) :: {:ok, Comment.t(), Activity.t()} | any()
+ @spec update(Comment.t(), map(), map()) :: {:ok, Comment.t(), ActivityStream.t()}
def update(%Comment{} = old_comment, args, additional) do
with args <- prepare_args_for_comment_update(args),
{:ok, %Comment{} = new_comment} <- Discussions.update_comment(old_comment, args),
@@ -60,7 +61,8 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Comments do
end
@impl Entity
- @spec delete(Comment.t(), Actor.t(), boolean, map()) :: {:ok, Comment.t()}
+ @spec delete(Comment.t(), Actor.t(), boolean, map()) ::
+ {:ok, ActivityStream.t(), Actor.t(), Comment.t()}
def delete(
%Comment{url: url, id: comment_id},
%Actor{} = actor,
@@ -91,6 +93,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Comments do
end
end
+ @spec actor(Comment.t()) :: Actor.t() | nil
def actor(%Comment{actor: %Actor{} = actor}), do: actor
def actor(%Comment{actor_id: actor_id}) when not is_nil(actor_id),
@@ -98,6 +101,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Comments do
def actor(_), do: nil
+ @spec group_actor(Comment.t()) :: Actor.t() | nil
def group_actor(%Comment{attributed_to: %Actor{} = group}), do: group
def group_actor(%Comment{attributed_to_id: attributed_to_id}) when not is_nil(attributed_to_id),
@@ -105,6 +109,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Comments do
def group_actor(_), do: nil
+ @spec permissions(Comment.t()) :: Permission.t()
def permissions(%Comment{}),
do: %Permission{
access: :member,
@@ -114,6 +119,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Comments do
}
# Prepare and sanitize arguments for comments
+ @spec prepare_args_for_comment(map) :: map
defp prepare_args_for_comment(args) do
with in_reply_to_comment <-
args |> Map.get(:in_reply_to_comment_id) |> Discussions.get_comment_with_preload(),
@@ -150,6 +156,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Comments do
end
end
+ @spec prepare_args_for_comment_update(map) :: map
defp prepare_args_for_comment_update(args) do
with {text, mentions, tags} <-
APIUtils.make_content_html(
@@ -174,6 +181,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Comments do
defp handle_event_for_comment(nil), do: nil
+ @spec maybe_publish_graphql_subscription(String.t() | integer() | nil) :: :ok
defp maybe_publish_graphql_subscription(nil), do: :ok
defp maybe_publish_graphql_subscription(discussion_id) do
@@ -186,6 +194,8 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Comments do
end
end
+ @spec make_sure_event_allows_commenting(%{actor_id: String.t() | integer, event: Event.t()}) ::
+ :ok | {:error, :event_comments_are_closed}
defp make_sure_event_allows_commenting(%{
actor_id: actor_id,
event: %Event{
diff --git a/lib/federation/activity_pub/types/discussions.ex b/lib/federation/activity_pub/types/discussions.ex
index 1fb013872..dd1bfde38 100644
--- a/lib/federation/activity_pub/types/discussions.ex
+++ b/lib/federation/activity_pub/types/discussions.ex
@@ -6,6 +6,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Discussions do
alias Mobilizon.Discussions.{Comment, Discussion}
alias Mobilizon.Federation.ActivityPub.{Audience, Permission}
alias Mobilizon.Federation.ActivityPub.Types.Entity
+ alias Mobilizon.Federation.ActivityStream
alias Mobilizon.Federation.ActivityStream.Convertible
alias Mobilizon.GraphQL.API.Utils, as: APIUtils
alias Mobilizon.Service.Activity.Discussion, as: DiscussionActivity
@@ -16,7 +17,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Discussions do
@behaviour Entity
@impl Entity
- @spec create(map(), map()) :: {:ok, map()}
+ @spec create(map(), map()) :: {:ok, Discussion.t(), ActivityStream.t()}
def create(%{discussion_id: discussion_id} = args, additional) when not is_nil(discussion_id) do
with args <- prepare_args(args),
%Discussion{} = discussion <- Discussions.get_discussion(discussion_id),
@@ -39,7 +40,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Discussions do
end
@impl Entity
- @spec create(map(), map()) :: {:ok, map()}
+ @spec create(map(), map()) :: {:ok, Discussion.t(), ActivityStream.t()}
def create(args, additional) do
with args <- prepare_args(args),
{:ok, %Discussion{} = discussion} <-
@@ -56,7 +57,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Discussions do
end
@impl Entity
- @spec update(Discussion.t(), map(), map()) :: {:ok, Discussion.t(), Activity.t()} | any()
+ @spec update(Discussion.t(), map(), map()) :: {:ok, Discussion.t(), ActivityStream.t()}
def update(%Discussion{} = old_discussion, args, additional) do
with {:ok, %Discussion{} = new_discussion} <-
Discussions.update_discussion(old_discussion, args),
@@ -80,7 +81,8 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Discussions do
end
@impl Entity
- @spec delete(Discussion.t(), Actor.t(), boolean, map()) :: {:ok, Discussion.t()}
+ @spec delete(Discussion.t(), Actor.t(), boolean, map()) ::
+ {:ok, ActivityStream.t(), Actor.t(), Discussion.t()}
def delete(
%Discussion{actor: group, url: url} = discussion,
%Actor{} = actor,
@@ -106,10 +108,13 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Discussions do
end
end
+ @spec actor(Discussion.t()) :: Actor.t() | nil
def actor(%Discussion{creator_id: creator_id}), do: Actors.get_actor(creator_id)
+ @spec group_actor(Discussion.t()) :: Actor.t() | nil
def group_actor(%Discussion{actor_id: actor_id}), do: Actors.get_actor(actor_id)
+ @spec permissions(Discussion.t()) :: Permission.t()
def permissions(%Discussion{}) do
%Permission{access: :member, create: :member, update: :moderator, delete: :moderator}
end
@@ -123,6 +128,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Discussions do
:ok
end
+ @spec prepare_args(map) :: map
defp prepare_args(args) do
{text, _mentions, _tags} =
APIUtils.make_content_html(
diff --git a/lib/federation/activity_pub/types/entity.ex b/lib/federation/activity_pub/types/entity.ex
index 3500294a5..30335e76a 100644
--- a/lib/federation/activity_pub/types/entity.ex
+++ b/lib/federation/activity_pub/types/entity.ex
@@ -15,7 +15,7 @@ alias Mobilizon.Federation.ActivityPub.Types.{
}
alias Mobilizon.Actors.{Actor, Member}
-alias Mobilizon.Events.Event
+alias Mobilizon.Events.{Event, Participant}
alias Mobilizon.Discussions.{Comment, Discussion}
alias Mobilizon.Federation.ActivityPub.Permission
alias Mobilizon.Posts.Post
@@ -28,7 +28,19 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Entity do
@moduledoc """
ActivityPub entity behaviour
"""
- @type t :: %{id: String.t()}
+ @type t :: %{id: String.t(), url: String.t()}
+
+ @type entities ::
+ Actor.t()
+ | Member.t()
+ | Event.t()
+ | Participant.t()
+ | Comment.t()
+ | Discussion.t()
+ | Post.t()
+ | Resource.t()
+ | Todo.t()
+ | TodoList.t()
@callback create(data :: any(), additionnal :: map()) ::
{:ok, t(), ActivityStream.t()}
diff --git a/lib/federation/activity_pub/types/events.ex b/lib/federation/activity_pub/types/events.ex
index 221c1e888..3ec862348 100644
--- a/lib/federation/activity_pub/types/events.ex
+++ b/lib/federation/activity_pub/types/events.ex
@@ -3,8 +3,8 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Events do
alias Mobilizon.Actors
alias Mobilizon.Actors.Actor
alias Mobilizon.Events, as: EventsManager
- alias Mobilizon.Events.{Event, Participant}
- alias Mobilizon.Federation.ActivityPub
+ alias Mobilizon.Events.{Event, Participant, ParticipantRole}
+ alias Mobilizon.Federation.{ActivityPub, ActivityStream}
alias Mobilizon.Federation.ActivityPub.{Audience, Permission}
alias Mobilizon.Federation.ActivityPub.Types.Entity
alias Mobilizon.Federation.ActivityStream.Converter.Utils, as: ConverterUtils
@@ -22,7 +22,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Events do
@behaviour Entity
@impl Entity
- @spec create(map(), map()) :: {:ok, map()}
+ @spec create(map(), map()) :: {:ok, Event.t(), ActivityStream.t()}
def create(args, additional) do
with args <- prepare_args_for_event(args),
{:ok, %Event{} = event} <- EventsManager.create_event(args),
@@ -38,7 +38,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Events do
end
@impl Entity
- @spec update(Event.t(), map(), map()) :: {:ok, Event.t(), Activity.t()} | any()
+ @spec update(Event.t(), map(), map()) :: {:ok, Event.t(), ActivityStream.t()}
def update(%Event{} = old_event, args, additional) do
with args <- prepare_args_for_event(args),
{:ok, %Event{} = new_event} <- EventsManager.update_event(old_event, args),
@@ -59,7 +59,8 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Events do
end
@impl Entity
- @spec delete(Event.t(), Actor.t(), boolean, map()) :: {:ok, Event.t()}
+ @spec delete(Event.t(), Actor.t(), boolean, map()) ::
+ {:ok, ActivityStream.t(), Actor.t(), Event.t()}
def delete(%Event{url: url} = event, %Actor{} = actor, _local, _additionnal) do
activity_data = %{
"type" => "Delete",
@@ -82,6 +83,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Events do
end
end
+ @spec actor(Event.t()) :: Actor.t() | nil
def actor(%Event{organizer_actor: %Actor{} = actor}), do: actor
def actor(%Event{organizer_actor_id: organizer_actor_id}),
@@ -89,6 +91,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Events do
def actor(_), do: nil
+ @spec group_actor(Event.t()) :: Actor.t() | nil
def group_actor(%Event{attributed_to: %Actor{} = group}), do: group
def group_actor(%Event{attributed_to_id: attributed_to_id}) when not is_nil(attributed_to_id),
@@ -96,6 +99,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Events do
def group_actor(_), do: nil
+ @spec permissions(Event.t()) :: Permission.t()
def permissions(%Event{draft: draft, attributed_to_id: _attributed_to_id}) do
%Permission{
access: if(draft, do: nil, else: :member),
@@ -105,9 +109,12 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Events do
}
end
+ @spec join(Event.t(), Actor.t(), boolean, map) ::
+ {:ok, ActivityStreams.t(), Participant.t()}
+ | {:error, :maximum_attendee_capacity_reached}
def join(%Event{} = event, %Actor{} = actor, _local, additional) do
with {:maximum_attendee_capacity, true} <-
- {:maximum_attendee_capacity, check_attendee_capacity(event)},
+ {:maximum_attendee_capacity, check_attendee_capacity?(event)},
role <-
additional
|> Map.get(:metadata, %{})
@@ -133,12 +140,13 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Events do
role
)
else
- {:maximum_attendee_capacity, err} ->
- {:maximum_attendee_capacity, err}
+ {:maximum_attendee_capacity, false} ->
+ {:error, :maximum_attendee_capacity_reached}
end
end
- defp check_attendee_capacity(%Event{options: options} = event) do
+ @spec check_attendee_capacity?(Event.t()) :: boolean
+ defp check_attendee_capacity?(%Event{options: options} = event) do
with maximum_attendee_capacity <-
Map.get(options, :maximum_attendee_capacity) || 0 do
maximum_attendee_capacity == 0 ||
@@ -147,6 +155,12 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Events do
end
# Set the participant to approved if the default role for new participants is :participant
+ @spec approve_if_default_role_is_participant(
+ Event.t(),
+ ActivityStreams.t(),
+ Participant.t(),
+ ParticipantRole.t()
+ ) :: {:ok, ActivityStreams.t(), Participant.t()}
defp approve_if_default_role_is_participant(event, activity_data, participant, role) do
case event do
%Event{attributed_to: %Actor{id: group_id, url: group_url}} ->
@@ -171,9 +185,11 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Events do
end
end
+ @spec do_approve(Event.t(), ActivityStreams.t(), Particpant.t(), ParticipantRole.t(), map()) ::
+ {:accept, any} | {:ok, ActivityStreams.t(), Participant.t()}
defp do_approve(event, activity_data, participant, role, additionnal) do
cond do
- Mobilizon.Events.get_default_participant_role(event) === :participant &&
+ Mobilizon.Events.get_default_participant_role(event) == :participant &&
role == :participant ->
{:accept,
ActivityPub.accept(
@@ -183,7 +199,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Events do
additionnal
)}
- Mobilizon.Events.get_default_participant_role(event) === :not_approved &&
+ Mobilizon.Events.get_default_participant_role(event) == :not_approved &&
role == :not_approved ->
Scheduler.pending_participation_notification(event)
{:ok, activity_data, participant}
@@ -194,6 +210,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Events do
end
# Prepare and sanitize arguments for events
+ @spec prepare_args_for_event(map) :: map
defp prepare_args_for_event(args) do
# If title is not set: we are not updating it
args =
diff --git a/lib/federation/activity_pub/types/members.ex b/lib/federation/activity_pub/types/members.ex
index 8c55fa72a..a0e5f1e54 100644
--- a/lib/federation/activity_pub/types/members.ex
+++ b/lib/federation/activity_pub/types/members.ex
@@ -1,14 +1,15 @@
defmodule Mobilizon.Federation.ActivityPub.Types.Members do
@moduledoc false
alias Mobilizon.Actors
- alias Mobilizon.Actors.{Actor, Member}
- alias Mobilizon.Federation.ActivityPub
+ alias Mobilizon.Actors.{Actor, Member, MemberRole}
+ alias Mobilizon.Federation.{ActivityPub, ActivityStream}
alias Mobilizon.Federation.ActivityStream.Convertible
alias Mobilizon.Service.Activity.Member, as: MemberActivity
alias Mobilizon.Web.Endpoint
require Logger
import Mobilizon.Federation.ActivityPub.Utils, only: [make_update_data: 2]
+ @spec update(Member.t(), map, map) :: {:ok, Member.t(), ActivityStream.t()}
def update(
%Member{
parent: %Actor{id: group_id} = group,
@@ -24,7 +25,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Members do
when moderator_role in [:moderator, :administrator, :creator] <-
{:has_rights_to_update_role, Actors.get_member(moderator_id, group_id)},
{:is_only_admin, false} <-
- {:is_only_admin, check_admins_left(member_id, group_id, current_role, updated_role)},
+ {:is_only_admin, check_admins_left?(member_id, group_id, current_role, updated_role)},
{:ok, %Member{} = member} <-
Actors.update_member(old_member, args),
{:ok, _} <-
@@ -56,6 +57,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Members do
end
# Used only when a group is suspended
+ @spec delete(Member.t(), Actor.t(), boolean(), map()) :: {:ok, Activity.t(), Member.t()}
def delete(
%Member{parent: %Actor{} = group, actor: %Actor{} = actor} = _member,
%Actor{},
@@ -66,13 +68,21 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Members do
ActivityPub.leave(group, actor, local, %{force_member_removal: true})
end
+ @spec actor(Member.t()) :: Actor.t() | nil
def actor(%Member{actor_id: actor_id}),
do: Actors.get_actor(actor_id)
+ @spec group_actor(Member.t()) :: Actor.t() | nil
def group_actor(%Member{parent_id: parent_id}),
do: Actors.get_actor(parent_id)
- defp check_admins_left(member_id, group_id, current_role, updated_role) do
+ @spec check_admins_left?(
+ String.t() | integer,
+ String.t() | integer,
+ MemberRole.t(),
+ MemberRole.t()
+ ) :: boolean
+ defp check_admins_left?(member_id, group_id, current_role, updated_role) do
Actors.is_only_administrator?(member_id, group_id) && current_role == :administrator &&
updated_role != :administrator
end
diff --git a/lib/federation/activity_pub/types/posts.ex b/lib/federation/activity_pub/types/posts.ex
index 279a8ba5e..1bca51fee 100644
--- a/lib/federation/activity_pub/types/posts.ex
+++ b/lib/federation/activity_pub/types/posts.ex
@@ -4,6 +4,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Posts do
alias Mobilizon.Actors.Actor
alias Mobilizon.Federation.ActivityPub.{Audience, Permission}
alias Mobilizon.Federation.ActivityPub.Types.Entity
+ alias Mobilizon.Federation.ActivityStream
alias Mobilizon.Federation.ActivityStream.Converter.Utils, as: ConverterUtils
alias Mobilizon.Federation.ActivityStream.Convertible
alias Mobilizon.Posts.Post
@@ -17,6 +18,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Posts do
@public_ap "https://www.w3.org/ns/activitystreams#Public"
@impl Entity
+ @spec create(map(), map()) :: {:ok, Post.t(), ActivityStream.t()}
def create(args, additional) do
with args <- prepare_args(args),
{:ok, %Post{attributed_to_id: group_id, author_id: creator_id} = post} <-
@@ -37,6 +39,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Posts do
end
@impl Entity
+ @spec update(Post.t(), map(), map()) :: {:ok, Post.t(), ActivityStream.t()}
def update(%Post{} = post, args, additional) do
with args <- prepare_args(args),
{:ok, %Post{attributed_to_id: group_id, author_id: creator_id} = post} <-
@@ -60,6 +63,8 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Posts do
end
@impl Entity
+ @spec delete(Post.t(), Actor.t(), boolean, map) ::
+ {:ok, ActivityStream.t(), Actor.t(), Post.t()}
def delete(
%Post{
url: url,
@@ -86,12 +91,15 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Posts do
end
end
+ @spec actor(Post.t()) :: Actor.t() | nil
def actor(%Post{author_id: author_id}),
do: Actors.get_actor(author_id)
+ @spec group_actor(Post.t()) :: Actor.t() | nil
def group_actor(%Post{attributed_to_id: attributed_to_id}),
do: Actors.get_actor(attributed_to_id)
+ @spec permissions(Post.t()) :: Permission.t()
def permissions(%Post{}) do
%Permission{
access: :member,
@@ -101,6 +109,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Posts do
}
end
+ @spec prepare_args(map()) :: map
defp prepare_args(args) do
args
|> Map.update(:tags, [], &ConverterUtils.fetch_tags/1)
diff --git a/lib/federation/activity_pub/types/reports.ex b/lib/federation/activity_pub/types/reports.ex
index ded8a1882..2d9577776 100644
--- a/lib/federation/activity_pub/types/reports.ex
+++ b/lib/federation/activity_pub/types/reports.ex
@@ -2,11 +2,13 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Reports do
@moduledoc false
alias Mobilizon.{Actors, Discussions, Reports}
alias Mobilizon.Actors.Actor
+ alias Mobilizon.Federation.ActivityStream
alias Mobilizon.Federation.ActivityStream.Convertible
alias Mobilizon.Reports.Report
alias Mobilizon.Service.Formatter.HTML
require Logger
+ @spec flag(map(), boolean(), map()) :: {Report.t(), ActivityStream.t()}
def flag(args, local \\ false, _additional \\ %{}) do
with {:build_args, args} <- {:build_args, prepare_args_for_report(args)},
{:create_report, {:ok, %Report{} = report}} <-
@@ -18,6 +20,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Reports do
end
end
+ @spec prepare_args_for_report(map()) :: map()
defp prepare_args_for_report(args) do
with {:reporter, %Actor{} = reporter_actor} <-
{:reporter, Actors.get_actor!(args.reporter_id)},
diff --git a/lib/federation/activity_pub/types/resources.ex b/lib/federation/activity_pub/types/resources.ex
index 38f3b409e..07bfe2fa2 100644
--- a/lib/federation/activity_pub/types/resources.ex
+++ b/lib/federation/activity_pub/types/resources.ex
@@ -4,6 +4,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Resources do
alias Mobilizon.Actors.Actor
alias Mobilizon.Federation.ActivityPub.Permission
alias Mobilizon.Federation.ActivityPub.Types.Entity
+ alias alias Mobilizon.Federation.ActivityStream
alias Mobilizon.Federation.ActivityStream.Convertible
alias Mobilizon.Resources.Resource
alias Mobilizon.Service.Activity.Resource, as: ResourceActivity
@@ -16,6 +17,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Resources do
@behaviour Entity
@impl Entity
+ @spec create(map(), map()) :: {:ok, Resource.t(), ActivityStream.t()}
def create(%{type: type} = args, additional) do
args =
case type do
@@ -66,6 +68,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Resources do
end
@impl Entity
+ @spec update(Resource.t(), map(), map()) :: {:ok, Resource.t(), ActivityStream.t()}
def update(
%Resource{parent_id: old_parent_id} = old_resource,
%{parent_id: parent_id} = args,
@@ -104,6 +107,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Resources do
end
end
+ @spec update(Resource.t(), map(), map()) :: {:ok, Resource.t(), ActivityStream.t()}
def move(
%Resource{parent_id: old_parent_id} = old_resource,
%{parent_id: _new_parent_id} = args,
@@ -142,6 +146,8 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Resources do
end
@impl Entity
+ @spec delete(Resource.t(), Actor.t(), boolean, map()) ::
+ {:ok, ActivityStream.t(), Actor.t(), Resource.t()}
def delete(
%Resource{url: url, actor: %Actor{url: group_url, members_url: members_url}} = resource,
%Actor{url: actor_url} = actor,
@@ -166,11 +172,14 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Resources do
end
end
+ @spec actor(Todo.t()) :: Actor.t() | nil
def actor(%Resource{creator_id: creator_id}),
do: Actors.get_actor(creator_id)
+ @spec group_actor(Todo.t()) :: Actor.t() | nil
def group_actor(%Resource{actor_id: actor_id}), do: Actors.get_actor(actor_id)
+ @spec permissions(TodoList.t()) :: Permission.t()
def permissions(%Resource{}) do
%Permission{access: :member, create: :member, update: :member, delete: :member}
end
diff --git a/lib/federation/activity_pub/types/todo_lists.ex b/lib/federation/activity_pub/types/todo_lists.ex
index fcabf1020..4ff9f1f9b 100644
--- a/lib/federation/activity_pub/types/todo_lists.ex
+++ b/lib/federation/activity_pub/types/todo_lists.ex
@@ -13,7 +13,9 @@ defmodule Mobilizon.Federation.ActivityPub.Types.TodoLists do
@behaviour Entity
@impl Entity
- @spec create(map(), map()) :: {:ok, map()}
+ @spec create(map(), map()) ::
+ {:ok, TodoList.t(), ActivityStream.t()}
+ | {:error, :group_not_found | Ecto.Changeset.t()}
def create(args, additional) do
with {:ok, %TodoList{actor_id: group_id} = todo_list} <- Todos.create_todo_list(args),
{:ok, %Actor{} = group} <- Actors.get_group_by_actor_id(group_id),
@@ -26,7 +28,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.TodoLists do
end
@impl Entity
- @spec update(TodoList.t(), map, map) :: {:ok, TodoList.t(), Activity.t()} | any
+ @spec update(TodoList.t(), map, map) :: {:ok, TodoList.t(), ActivityStream.t()} | any
def update(%TodoList{} = old_todo_list, args, additional) do
with {:ok, %TodoList{actor_id: group_id} = todo_list} <-
Todos.update_todo_list(old_todo_list, args),
@@ -65,10 +67,13 @@ defmodule Mobilizon.Federation.ActivityPub.Types.TodoLists do
end
end
+ @spec actor(TodoList.t()) :: nil
def actor(%TodoList{}), do: nil
+ @spec group_actor(TodoList.t()) :: Actor.t() | nil
def group_actor(%TodoList{actor_id: actor_id}), do: Actors.get_actor(actor_id)
+ @spec permissions(TodoList.t()) :: Permission.t()
def permissions(%TodoList{}) do
%Permission{access: :member, create: :member, update: :member, delete: :member}
end
diff --git a/lib/federation/activity_pub/types/todos.ex b/lib/federation/activity_pub/types/todos.ex
index b12463cea..3a3e0948e 100644
--- a/lib/federation/activity_pub/types/todos.ex
+++ b/lib/federation/activity_pub/types/todos.ex
@@ -4,6 +4,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Todos do
alias Mobilizon.Actors.Actor
alias Mobilizon.Federation.ActivityPub.Permission
alias Mobilizon.Federation.ActivityPub.Types.Entity
+ alias Mobilizon.Federation.ActivityStream
alias Mobilizon.Federation.ActivityStream.Convertible
alias Mobilizon.Todos.{Todo, TodoList}
import Mobilizon.Federation.ActivityPub.Utils, only: [make_create_data: 2, make_update_data: 2]
@@ -12,7 +13,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Todos do
@behaviour Entity
@impl Entity
- @spec create(map(), map()) :: {:ok, map()}
+ @spec create(map(), map()) :: {:ok, Todo.t(), ActivityStream.t()}
def create(args, additional) do
with {:ok, %Todo{todo_list_id: todo_list_id, creator_id: creator_id} = todo} <-
Todos.create_todo(args),
@@ -30,7 +31,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Todos do
end
@impl Entity
- @spec update(Todo.t(), map, map) :: {:ok, Todo.t(), Activity.t()} | any
+ @spec update(Todo.t(), map, map) :: {:ok, Todo.t(), ActivityStream.t()}
def update(%Todo{} = old_todo, args, additional) do
with {:ok, %Todo{todo_list_id: todo_list_id} = todo} <- Todos.update_todo(old_todo, args),
%TodoList{actor_id: group_id} = todo_list <- Todos.get_todo_list(todo_list_id),
@@ -69,8 +70,10 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Todos do
end
end
+ @spec actor(Todo.t()) :: Actor.t() | nil
def actor(%Todo{creator_id: creator_id}), do: Actors.get_actor(creator_id)
+ @spec group_actor(Todo.t()) :: Actor.t() | nil
def group_actor(%Todo{todo_list_id: todo_list_id}) do
case Todos.get_todo_list(todo_list_id) do
%TodoList{actor_id: group_id} ->
@@ -81,6 +84,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Todos do
end
end
+ @spec permissions(TodoList.t()) :: Permission.t()
def permissions(%Todo{}) do
%Permission{access: :member, create: :member, update: :member, delete: :member}
end
diff --git a/lib/federation/activity_stream/converter/actor.ex b/lib/federation/activity_stream/converter/actor.ex
index 54c087a7c..59cc713f5 100644
--- a/lib/federation/activity_stream/converter/actor.ex
+++ b/lib/federation/activity_stream/converter/actor.ex
@@ -29,7 +29,7 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Actor do
Converts an AP object data to our internal data structure.
"""
@impl Converter
- @spec as_to_model_data(map()) :: {:ok, map()}
+ @spec as_to_model_data(map()) :: map() | {:error, :actor_not_allowed_type}
def as_to_model_data(%{"type" => type} = data) when type in @allowed_types do
avatar =
download_picture(get_in(data, ["icon", "url"]), get_in(data, ["icon", "name"]), "avatar")
@@ -64,7 +64,7 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Actor do
}
end
- def as_to_model_data(_), do: :error
+ def as_to_model_data(_), do: {:error, :actor_not_allowed_type}
@doc """
Convert an actor struct to an ActivityStream representation.
@@ -135,7 +135,7 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Actor do
end
end
- @spec download_picture(String.t() | nil, String.t(), String.t()) :: map()
+ @spec download_picture(String.t() | nil, String.t(), String.t()) :: map() | nil
defp download_picture(nil, _name, _default_name), do: nil
defp download_picture(url, name, default_name) do
diff --git a/lib/federation/activity_stream/converter/comment.ex b/lib/federation/activity_stream/converter/comment.ex
index d4ec756d9..f653009e1 100644
--- a/lib/federation/activity_stream/converter/comment.ex
+++ b/lib/federation/activity_stream/converter/comment.ex
@@ -38,7 +38,7 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Comment do
Converts an AP object data to our internal data structure.
"""
@impl Converter
- @spec as_to_model_data(map) :: {:ok, map} | {:error, any()}
+ @spec as_to_model_data(map) :: map | {:error, any()}
def as_to_model_data(object) do
Logger.debug("We're converting raw ActivityStream data to a comment entity")
Logger.debug(inspect(object))
diff --git a/lib/federation/activity_stream/converter/converter.ex b/lib/federation/activity_stream/converter/converter.ex
index 73b1ca69b..ceef41ff5 100644
--- a/lib/federation/activity_stream/converter/converter.ex
+++ b/lib/federation/activity_stream/converter/converter.ex
@@ -8,6 +8,6 @@ defmodule Mobilizon.Federation.ActivityStream.Converter do
@type model_data :: map()
- @callback as_to_model_data(as_data :: ActivityStream.t()) :: model_data()
+ @callback as_to_model_data(as_data :: ActivityStream.t()) :: model_data() | {:error, any()}
@callback model_to_as(model :: struct()) :: ActivityStream.t()
end
diff --git a/lib/federation/activity_stream/converter/discussion.ex b/lib/federation/activity_stream/converter/discussion.ex
index 2029923d6..9cfe48893 100644
--- a/lib/federation/activity_stream/converter/discussion.ex
+++ b/lib/federation/activity_stream/converter/discussion.ex
@@ -12,6 +12,7 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Discussion do
alias Mobilizon.Federation.ActivityStream.{Converter, Convertible}
alias Mobilizon.Federation.ActivityStream.Converter.Discussion, as: DiscussionConverter
alias Mobilizon.Storage.Repo
+ import Mobilizon.Service.Guards, only: [is_valid_string: 1]
require Logger
@@ -45,20 +46,27 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Discussion do
end
@impl Converter
- @spec as_to_model_data(map) :: {:ok, map} | {:error, any()}
- def as_to_model_data(%{"type" => "Note", "name" => name} = object) when not is_nil(name) do
- with creator_url <- Map.get(object, "actor"),
- {:ok, %Actor{id: creator_id, suspended: false}} <-
+ @spec as_to_model_data(map) :: map() | {:error, any()}
+ def as_to_model_data(%{"type" => "Note", "name" => name} = object) when is_valid_string(name) do
+ case extract_actors(object) do
+ %{actor_id: actor_id, creator_id: creator_id} ->
+ %{actor_id: actor_id, creator_id: creator_id, title: name, url: object["id"]}
+
+ {:error, error} ->
+ {:error, error}
+ end
+ end
+
+ @spec extract_actors(map()) :: %{actor_id: String.t(), creator_id: String.t()} | {:error, any()}
+ defp extract_actors(%{"actor" => creator_url, "attributedTo" => actor_url} = _object)
+ when is_valid_string(creator_url) and is_valid_string(actor_url) do
+ with {:ok, %Actor{id: creator_id, suspended: false}} <-
ActivityPubActor.get_or_fetch_actor_by_url(creator_url),
- actor_url <- Map.get(object, "attributedTo"),
{:ok, %Actor{id: actor_id, suspended: false}} <-
ActivityPubActor.get_or_fetch_actor_by_url(actor_url) do
- %{
- title: name,
- actor_id: actor_id,
- creator_id: creator_id,
- url: object["id"]
- }
+ %{actor_id: actor_id, creator_id: creator_id}
+ else
+ {:error, error} -> {:error, error}
end
end
end
diff --git a/lib/federation/activity_stream/converter/event.ex b/lib/federation/activity_stream/converter/event.ex
index 65825a32e..b7d2c187c 100644
--- a/lib/federation/activity_stream/converter/event.ex
+++ b/lib/federation/activity_stream/converter/event.ex
@@ -45,7 +45,7 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Event do
Converts an AP object data to our internal data structure.
"""
@impl Converter
- @spec as_to_model_data(map) :: {:ok, map()} | {:error, any()}
+ @spec as_to_model_data(map) :: map() | {:error, any()} | :error
def as_to_model_data(object) do
with {%Actor{id: actor_id}, attributed_to} <-
maybe_fetch_actor_and_attributed_to_id(object),
diff --git a/lib/federation/activity_stream/converter/member.ex b/lib/federation/activity_stream/converter/member.ex
index be4db50a5..3d33d1ae2 100644
--- a/lib/federation/activity_stream/converter/member.ex
+++ b/lib/federation/activity_stream/converter/member.ex
@@ -33,6 +33,7 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Member do
}
end
+ @spec as_to_model_data(map()) :: map()
def as_to_model_data(%{
"type" => "Member",
"actor" => actor,
diff --git a/lib/federation/activity_stream/converter/post.ex b/lib/federation/activity_stream/converter/post.ex
index 0cb0dfee4..40f3eab43 100644
--- a/lib/federation/activity_stream/converter/post.ex
+++ b/lib/federation/activity_stream/converter/post.ex
@@ -18,6 +18,8 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Post do
process_pictures: 2
]
+ import Mobilizon.Service.Guards, only: [is_valid_string: 1]
+
@behaviour Converter
defimpl Convertible, for: Post do
@@ -63,15 +65,15 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Post do
Converts an AP object data to our internal data structure.
"""
@impl Converter
- @spec as_to_model_data(map) :: {:ok, map} | {:error, any()}
+ @spec as_to_model_data(map) :: map() | {:error, any()}
def as_to_model_data(
%{"type" => "Article", "actor" => creator, "attributedTo" => group_uri} = object
) do
with {:ok, %Actor{id: attributed_to_id} = group} <- get_actor(group_uri),
- {:ok, %Actor{id: author_id}} <- get_actor(creator),
- {:visibility, visibility} <- {:visibility, get_visibility(object, group)},
- [description: description, picture_id: picture_id, medias: medias] <-
- process_pictures(object, attributed_to_id) do
+ {:ok, %Actor{id: author_id}} <- get_actor(creator) do
+ [description: description, picture_id: picture_id, medias: medias] =
+ process_pictures(object, attributed_to_id)
+
%{
title: object["name"],
body: description,
@@ -82,7 +84,7 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Post do
publish_at: object["published"],
picture_id: picture_id,
medias: medias,
- visibility: visibility,
+ visibility: get_visibility(object, group),
draft: object["draft"] == true
}
else
@@ -92,11 +94,12 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Post do
end
@spec get_actor(String.t() | map() | nil) :: {:ok, Actor.t()} | {:error, String.t()}
- defp get_actor(nil), do: {:error, "nil property found for actor data"}
-
- defp get_actor(actor),
+ defp get_actor(actor) when is_valid_string(actor),
do: actor |> Utils.get_url() |> ActivityPubActor.get_or_fetch_actor_by_url()
+ defp get_actor(_), do: {:error, "nil property found for actor data"}
+
+ @spec to_date(DateTime.t() | NaiveDateTime.t() | nil) :: String.t() | nil
defp to_date(nil), do: nil
defp to_date(%DateTime{} = date), do: DateTime.to_iso8601(date)
defp to_date(%NaiveDateTime{} = date), do: NaiveDateTime.to_iso8601(date)
diff --git a/lib/federation/activity_stream/converter/resource.ex b/lib/federation/activity_stream/converter/resource.ex
index a0d8ca6e7..becf41ded 100644
--- a/lib/federation/activity_stream/converter/resource.ex
+++ b/lib/federation/activity_stream/converter/resource.ex
@@ -56,18 +56,17 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Resource do
Converts an AP object data to our internal data structure.
"""
@impl Converter
- @spec as_to_model_data(map) :: {:ok, map} | {:error, any()}
+ @spec as_to_model_data(map) :: map() | {:error, any()}
def as_to_model_data(%{"type" => type, "actor" => creator, "attributedTo" => group} = object) do
with {:ok, %Actor{id: actor_id, resources_url: resources_url}} <- get_actor(group),
- {:ok, %Actor{id: creator_id}} <- get_actor(creator),
- parent_id <- get_parent_id(object["context"], resources_url) do
+ {:ok, %Actor{id: creator_id}} <- get_actor(creator) do
data = %{
title: object["name"],
summary: object["summary"],
url: object["id"],
actor_id: actor_id,
creator_id: creator_id,
- parent_id: parent_id,
+ parent_id: get_parent_id(object["context"], resources_url),
published_at: object["published"]
}
diff --git a/lib/federation/activity_stream/converter/todo.ex b/lib/federation/activity_stream/converter/todo.ex
index fdd6f38c8..0ac0eebb1 100644
--- a/lib/federation/activity_stream/converter/todo.ex
+++ b/lib/federation/activity_stream/converter/todo.ex
@@ -47,7 +47,7 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Todo do
Converts an AP object data to our internal data structure.
"""
@impl Converter
- @spec as_to_model_data(map) :: {:ok, map} | {:error, any()}
+ @spec as_to_model_data(map) :: map() | {:error, any()}
def as_to_model_data(
%{"type" => "Todo", "actor" => actor_url, "todoList" => todo_list_url} = object
) do
diff --git a/lib/federation/activity_stream/converter/todo_list.ex b/lib/federation/activity_stream/converter/todo_list.ex
index 76ad46fc7..4491ab389 100644
--- a/lib/federation/activity_stream/converter/todo_list.ex
+++ b/lib/federation/activity_stream/converter/todo_list.ex
@@ -37,7 +37,7 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.TodoList do
Converts an AP object data to our internal data structure.
"""
@impl Converter
- @spec as_to_model_data(map) :: {:ok, map} | {:error, any()}
+ @spec as_to_model_data(map) :: map() | {:error, :group_not_found}
def as_to_model_data(%{"type" => "TodoList", "actor" => actor_url} = object) do
case ActivityPubActor.get_or_fetch_actor_by_url(actor_url) do
{:ok, %Actor{type: :Group, id: group_id} = _group} ->
diff --git a/lib/federation/http_signatures/signature.ex b/lib/federation/http_signatures/signature.ex
index e68251d3b..07ec0a82b 100644
--- a/lib/federation/http_signatures/signature.ex
+++ b/lib/federation/http_signatures/signature.ex
@@ -19,7 +19,7 @@ defmodule Mobilizon.Federation.HTTPSignatures.Signature do
@spec key_id_to_actor_url(String.t()) :: String.t()
def key_id_to_actor_url(key_id) do
- %{path: path} =
+ %URI{path: path} =
uri =
key_id
|> URI.parse()
@@ -29,7 +29,7 @@ defmodule Mobilizon.Federation.HTTPSignatures.Signature do
if is_nil(path) do
uri
else
- Map.put(uri, :path, String.trim_trailing(path, "/publickey"))
+ %URI{uri | path: String.trim_trailing(path, "/publickey")}
end
URI.to_string(uri)
@@ -78,6 +78,9 @@ defmodule Mobilizon.Federation.HTTPSignatures.Signature do
end
end
+ @spec fetch_public_key(Plug.Conn.t()) ::
+ {:ok, String.t()}
+ | {:error, :actor_fetch_error | :actor_not_fetchable | :pem_decode_error}
def fetch_public_key(conn) do
with %{"keyId" => kid} <- HTTPSignatures.signature_for_conn(conn),
actor_id <- key_id_to_actor_url(kid),
@@ -87,6 +90,9 @@ defmodule Mobilizon.Federation.HTTPSignatures.Signature do
end
end
+ @spec refetch_public_key(Plug.Conn.t()) ::
+ {:ok, String.t()}
+ | {:error, :actor_fetch_error | :actor_not_fetchable | :pem_decode_error}
def refetch_public_key(conn) do
with %{"keyId" => kid} <- HTTPSignatures.signature_for_conn(conn),
actor_id <- key_id_to_actor_url(kid),
@@ -97,6 +103,7 @@ defmodule Mobilizon.Federation.HTTPSignatures.Signature do
end
end
+ @spec sign(Actor.t(), map()) :: String.t()
def sign(%Actor{domain: domain, keys: keys} = actor, headers) when is_nil(domain) do
Logger.debug("Signing a payload on behalf of #{actor.url}")
Logger.debug("headers")
@@ -112,14 +119,17 @@ defmodule Mobilizon.Federation.HTTPSignatures.Signature do
raise ArgumentError, message: "Can't do a signature on remote actor #{url}"
end
+ @spec generate_date_header :: String.t()
def generate_date_header, do: generate_date_header(NaiveDateTime.utc_now())
def generate_date_header(%NaiveDateTime{} = date) do
Timex.format!(date, "{WDshort}, {0D} {Mshort} {YYYY} {h24}:{m}:{s} GMT")
end
+ @spec generate_request_target(String.t(), String.t()) :: String.t()
def generate_request_target(method, path), do: "#{method} #{path}"
+ @spec build_digest(String.t()) :: String.t()
def build_digest(body) do
"SHA-256=#{:sha256 |> :crypto.hash(body) |> Base.encode64()}"
end
diff --git a/lib/federation/web_finger/web_finger.ex b/lib/federation/web_finger/web_finger.ex
index 1eddb189b..872cd5892 100644
--- a/lib/federation/web_finger/web_finger.ex
+++ b/lib/federation/web_finger/web_finger.ex
@@ -66,10 +66,10 @@ defmodule Mobilizon.Federation.WebFinger do
end
end
- @spec represent_actor(Actor.t()) :: struct()
+ @spec represent_actor(Actor.t()) :: map()
+ @spec represent_actor(Actor.t(), String.t()) :: map()
def represent_actor(%Actor{} = actor), do: represent_actor(actor, "JSON")
- @spec represent_actor(Actor.t(), String.t()) :: struct()
def represent_actor(%Actor{} = actor, "JSON") do
links =
[
@@ -141,11 +141,15 @@ defmodule Mobilizon.Federation.WebFinger do
@doc """
Fetches the Extensible Resource Descriptor endpoint `/.well-known/host-meta` to find the Webfinger endpoint (usually `/.well-known/webfinger?resource=`)
"""
- @spec find_webfinger_endpoint(String.t()) :: String.t()
+ @spec find_webfinger_endpoint(String.t()) ::
+ {:ok, String.t()} | {:error, :link_not_found} | {:error, any()}
def find_webfinger_endpoint(domain) when is_binary(domain) do
with {:ok, %{body: body}} <- fetch_document("http://#{domain}/.well-known/host-meta"),
link_template when is_binary(link_template) <- find_link_from_template(body) do
{:ok, link_template}
+ else
+ {:error, :link_not_found} -> {:error, :link_not_found}
+ {:error, error} -> {:error, error}
end
end
diff --git a/lib/graphql/api/reports.ex b/lib/graphql/api/reports.ex
index e56547654..218467eb5 100644
--- a/lib/graphql/api/reports.ex
+++ b/lib/graphql/api/reports.ex
@@ -15,12 +15,13 @@ defmodule Mobilizon.GraphQL.API.Reports do
@doc """
Create a report/flag on an actor, and optionally on an event or on comments.
"""
+ @spec report(map()) :: {:ok, Activity.t(), Report.t()} | {:error, any()}
def report(args) do
- case {:make_activity, ActivityPub.flag(args, Map.get(args, :forward, false) == true)} do
- {:make_activity, {:ok, %Activity{} = activity, %Report{} = report}} ->
+ case ActivityPub.flag(args, Map.get(args, :forward, false) == true) do
+ {:ok, %Activity{} = activity, %Report{} = report} ->
{:ok, activity, report}
- {:make_activity, err} ->
+ err ->
{:error, err}
end
end
@@ -28,10 +29,12 @@ defmodule Mobilizon.GraphQL.API.Reports do
@doc """
Update the state of a report
"""
+ @spec update_report_status(Actor.t(), Report.t(), ReportStatus.t()) ::
+ {:ok, Report.t()} | {:error, String.t()}
def update_report_status(%Actor{} = actor, %Report{} = report, state) do
with {:valid_state, true} <-
{:valid_state, ReportStatus.valid_value?(state)},
- {:ok, report} <- ReportsAction.update_report(report, %{"status" => state}),
+ {:ok, %Report{} = report} <- ReportsAction.update_report(report, %{"status" => state}),
{:ok, _} <- Admin.log_action(actor, "update", report) do
{:ok, report}
else
@@ -42,7 +45,8 @@ defmodule Mobilizon.GraphQL.API.Reports do
@doc """
Create a note on a report
"""
- @spec create_report_note(Report.t(), Actor.t(), String.t()) :: {:ok, Note.t()}
+ @spec create_report_note(Report.t(), Actor.t(), String.t()) ::
+ {:ok, Note.t()} | {:error, String.t()}
def create_report_note(
%Report{id: report_id},
%Actor{id: moderator_id, user_id: user_id} = moderator,
@@ -67,7 +71,7 @@ defmodule Mobilizon.GraphQL.API.Reports do
@doc """
Delete a report note
"""
- @spec delete_report_note(Note.t(), Actor.t()) :: {:ok, Note.t()}
+ @spec delete_report_note(Note.t(), Actor.t()) :: {:ok, Note.t()} | {:error, String.t()}
def delete_report_note(
%Note{moderator_id: note_moderator_id} = note,
%Actor{id: moderator_id, user_id: user_id} = moderator
diff --git a/lib/graphql/api/utils.ex b/lib/graphql/api/utils.ex
index e15b0e0b9..1effea583 100644
--- a/lib/graphql/api/utils.ex
+++ b/lib/graphql/api/utils.ex
@@ -10,7 +10,7 @@ defmodule Mobilizon.GraphQL.API.Utils do
@doc """
Creates HTML content from text and mentions
"""
- @spec make_content_html(String.t(), list(), String.t()) :: String.t()
+ @spec make_content_html(String.t(), list(), String.t()) :: {String.t(), list(), list()}
def make_content_html(text, additional_tags, content_type) do
with {text, mentions, tags} <- format_input(text, content_type, []) do
{text, mentions, additional_tags ++ Enum.map(tags, fn {_, tag} -> tag end)}
diff --git a/lib/graphql/resolvers/comment.ex b/lib/graphql/resolvers/comment.ex
index 0e2998bb6..26435deb9 100644
--- a/lib/graphql/resolvers/comment.ex
+++ b/lib/graphql/resolvers/comment.ex
@@ -66,7 +66,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Comment do
with {:actor, %Actor{id: actor_id} = _actor} <- {:actor, Users.get_actor_for_user(user)},
%CommentModel{actor_id: comment_actor_id} = comment <-
Mobilizon.Discussions.get_comment_with_preload(comment_id),
- true <- actor_id === comment_actor_id,
+ true <- actor_id == comment_actor_id,
{:ok, _, %CommentModel{} = comment} <- Comments.update_comment(comment, %{text: text}) do
{:ok, comment}
end
diff --git a/lib/graphql/resolvers/event/utils.ex b/lib/graphql/resolvers/event/utils.ex
index e6c7e8c24..613dcadd4 100644
--- a/lib/graphql/resolvers/event/utils.ex
+++ b/lib/graphql/resolvers/event/utils.ex
@@ -6,7 +6,10 @@ defmodule Mobilizon.GraphQL.Resolvers.Event.Utils do
alias Mobilizon.Actors.Actor
alias Mobilizon.Events.Event
alias Mobilizon.Federation.ActivityPub.Permission
+ import Mobilizon.Service.Guards, only: [is_valid_string: 1]
+ @spec can_event_be_updated_by?(%Event{id: String.t()}, Actor.t()) ::
+ boolean
def can_event_be_updated_by?(
%Event{attributed_to: %Actor{type: :Group}} = event,
%Actor{} = actor_member
@@ -21,10 +24,13 @@ defmodule Mobilizon.GraphQL.Resolvers.Event.Utils do
Event.can_be_managed_by?(event, actor_member_id)
end
+ @spec can_event_be_deleted_by?(%Event{id: String.t(), url: String.t()}, Actor.t()) ::
+ boolean
def can_event_be_deleted_by?(
- %Event{attributed_to: %Actor{type: :Group}} = event,
+ %Event{attributed_to: %Actor{type: :Group}, id: event_id, url: event_url} = event,
%Actor{} = actor_member
- ) do
+ )
+ when is_valid_string(event_id) and is_valid_string(event_url) do
Permission.can_delete_group_object?(actor_member, event)
end
diff --git a/lib/graphql/resolvers/member.ex b/lib/graphql/resolvers/member.ex
index 44b9caed4..c41e19f4a 100644
--- a/lib/graphql/resolvers/member.ex
+++ b/lib/graphql/resolvers/member.ex
@@ -101,7 +101,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Member do
with %Actor{id: actor_id} <- Users.get_actor_for_user(user),
%Member{actor: %Actor{id: member_actor_id}} = member <-
Actors.get_member(member_id),
- {:is_same_actor, true} <- {:is_same_actor, member_actor_id === actor_id},
+ {:is_same_actor, true} <- {:is_same_actor, member_actor_id == actor_id},
{:ok, _activity, %Member{} = member} <-
ActivityPub.accept(
:invite,
@@ -119,7 +119,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Member do
with %Actor{id: actor_id} <- Users.get_actor_for_user(user),
{:invitation_exists, %Member{actor: %Actor{id: member_actor_id}} = member} <-
{:invitation_exists, Actors.get_member(member_id)},
- {:is_same_actor, true} <- {:is_same_actor, member_actor_id === actor_id},
+ {:is_same_actor, true} <- {:is_same_actor, member_actor_id == actor_id},
{:ok, _activity, %Member{} = member} <-
ActivityPub.reject(
:invite,
diff --git a/lib/graphql/resolvers/todos.ex b/lib/graphql/resolvers/todos.ex
index e6c2d504f..825500531 100644
--- a/lib/graphql/resolvers/todos.ex
+++ b/lib/graphql/resolvers/todos.ex
@@ -95,6 +95,9 @@ defmodule Mobilizon.GraphQL.Resolvers.Todos do
ActivityPub.create(:todo_list, Map.put(args, :actor_id, group_id), true, %{}) do
{:ok, todo_list}
else
+ {:actor, nil} ->
+ {:error, dgettext("errors", "No profile found for user")}
+
{:member, _} ->
{:error, dgettext("errors", "Profile is not member of group")}
end
@@ -187,6 +190,9 @@ defmodule Mobilizon.GraphQL.Resolvers.Todos do
ActivityPub.create(:todo, Map.put(args, :creator_id, actor_id), true, %{}) do
{:ok, todo}
else
+ {:actor, nil} ->
+ {:error, dgettext("errors", "No profile found for user")}
+
{:todo_list, _} ->
{:error, dgettext("errors", "Todo list doesn't exist")}
@@ -212,6 +218,9 @@ defmodule Mobilizon.GraphQL.Resolvers.Todos do
ActivityPub.update(todo, args, true, %{}) do
{:ok, todo}
else
+ {:actor, nil} ->
+ {:error, dgettext("errors", "No profile found for user")}
+
{:todo_list, _} ->
{:error, dgettext("errors", "Todo list doesn't exist")}
diff --git a/lib/graphql/resolvers/user.ex b/lib/graphql/resolvers/user.ex
index 24ba9c7c8..a141b014e 100644
--- a/lib/graphql/resolvers/user.ex
+++ b/lib/graphql/resolvers/user.ex
@@ -65,9 +65,8 @@ defmodule Mobilizon.GraphQL.Resolvers.User do
refresh_token: _refresh_token,
user: %User{} = user
} = user_and_tokens} <- Authenticator.authenticate(email, password),
- {:ok, %User{} = user} <- update_user_login_information(user, context),
- user_and_tokens <- Map.put(user_and_tokens, :user, user) do
- {:ok, user_and_tokens}
+ {:ok, %User{} = user} <- update_user_login_information(user, context) do
+ {:ok, %{user_and_tokens | user: user}}
else
{:error, :user_not_found} ->
{:error, :user_not_found}
@@ -133,7 +132,7 @@ defmodule Mobilizon.GraphQL.Resolvers.User do
- create the user
- send a validation email to the user
"""
- @spec create_user(any, map, any) :: tuple
+ @spec create_user(any, %{email: String.t()}, any) :: tuple
def create_user(_parent, %{email: email} = args, _resolution) do
with :registration_ok <- check_registration_config(email),
:not_deny_listed <- check_registration_denylist(email),
@@ -160,20 +159,21 @@ defmodule Mobilizon.GraphQL.Resolvers.User do
end
end
- @spec check_registration_config(map) :: atom
+ @spec check_registration_config(String.t()) :: atom
defp check_registration_config(email) do
cond do
Config.instance_registrations_open?() ->
:registration_ok
Config.instance_registrations_allowlist?() ->
- check_allow_listed_email?(email)
+ check_allow_listed_email(email)
true ->
:registration_closed
end
end
+ @spec check_registration_denylist(String.t()) :: :deny_listed | :not_deny_listed
defp check_registration_denylist(email) do
# Remove everything behind the +
email = String.replace(email, ~r/(\+.*)(?=\@)/, "")
@@ -183,8 +183,8 @@ defmodule Mobilizon.GraphQL.Resolvers.User do
else: :not_deny_listed
end
- @spec check_allow_listed_email?(String.t()) :: :registration_ok | :not_allowlisted
- defp check_allow_listed_email?(email) do
+ @spec check_allow_listed_email(String.t()) :: :registration_ok | :not_allowlisted
+ defp check_allow_listed_email(email) do
if email_in_list(email, Config.instance_registrations_allowlist()),
do: :registration_ok,
else: :not_allowlisted
@@ -199,12 +199,14 @@ defmodule Mobilizon.GraphQL.Resolvers.User do
@doc """
Validate an user, get its actor and a token
"""
+ @spec validate_user(map(), %{token: String.t()}, map()) :: {:ok, map()}
def validate_user(_parent, %{token: token}, _resolution) do
with {:check_confirmation_token, {:ok, %User{} = user}} <-
{:check_confirmation_token, Email.User.check_confirmation_token(token)},
- {:get_actor, actor} <- {:get_actor, Users.get_actor_for_user(user)},
- {:ok, %{access_token: access_token, refresh_token: refresh_token}} <-
- Authenticator.generate_tokens(user) do
+ {:get_actor, actor} <- {:get_actor, Users.get_actor_for_user(user)} do
+ {:ok, %{access_token: access_token, refresh_token: refresh_token}} =
+ Authenticator.generate_tokens(user)
+
{:ok,
%{
access_token: access_token,
@@ -267,12 +269,16 @@ defmodule Mobilizon.GraphQL.Resolvers.User do
@doc """
Reset the password from an user
"""
+ @spec reset_password(map(), %{password: String.t(), token: String.t()}, map()) ::
+ {:ok, map()} | {:error, String.t()}
def reset_password(_parent, %{password: password, token: token}, _resolution) do
- with {:ok, %User{email: email} = user} <-
- Email.User.check_reset_password_token(password, token),
- {:ok, %{access_token: access_token, refresh_token: refresh_token}} <-
- Authenticator.authenticate(email, password) do
- {:ok, %{access_token: access_token, refresh_token: refresh_token, user: user}}
+ case Email.User.check_reset_password_token(password, token) do
+ {:ok, %User{email: email} = user} ->
+ {:ok, tokens} = Authenticator.authenticate(email, password)
+ {:ok, Map.put(tokens, :user, user)}
+
+ {:error, error} ->
+ {:error, error}
end
end
@@ -369,6 +375,9 @@ defmodule Mobilizon.GraphQL.Resolvers.User do
|> Repo.update() do
{:ok, user}
else
+ {:can_change_password, false} ->
+ {:error, dgettext("errors", "You cannot change your password.")}
+
{:current_password, _} ->
{:error, dgettext("errors", "The current password is invalid")}
@@ -408,14 +417,18 @@ defmodule Mobilizon.GraphQL.Resolvers.User do
{:ok, user}
else
- {:current_password, _} ->
+ {:current_password, {:error, _}} ->
{:error, dgettext("errors", "The password provided is invalid")}
{:same_email, true} ->
{:error, dgettext("errors", "The new email must be different")}
- {:email_valid, _} ->
+ {:email_valid, false} ->
{:error, dgettext("errors", "The new email doesn't seem to be valid")}
+
+ {:error, %Ecto.Changeset{} = err} ->
+ Logger.debug(inspect(err))
+ {:error, dgettext("errors", "Failed to update user email")}
end
end
@@ -423,12 +436,21 @@ defmodule Mobilizon.GraphQL.Resolvers.User do
{:error, dgettext("errors", "You need to be logged-in to change your email")}
end
+ @spec validate_email(map(), %{token: String.t()}, map()) ::
+ {:ok, User.t()} | {:error, String.t()}
def validate_email(_parent, %{token: token}, _resolution) do
- with {:get, %User{} = user} <- {:get, Users.get_user_by_activation_token(token)},
- {:ok, %User{} = user} <- Users.validate_email(user) do
- {:ok, user}
- else
- {:get, nil} ->
+ case Users.get_user_by_activation_token(token) do
+ %User{} = user ->
+ case Users.validate_email(user) do
+ {:ok, %User{} = user} ->
+ {:ok, user}
+
+ {:error, %Ecto.Changeset{} = err} ->
+ Logger.debug(inspect(err))
+ {:error, dgettext("errors", "Failed to validate user email")}
+ end
+
+ nil ->
{:error, dgettext("errors", "Invalid activation token")}
end
end
@@ -547,12 +569,17 @@ defmodule Mobilizon.GraphQL.Resolvers.User do
def update_locale(_parent, %{locale: locale}, %{
context: %{current_user: %User{locale: current_locale} = user}
}) do
- with true <- current_locale != locale,
- {:ok, %User{} = updated_user} <- Users.update_user(user, %{locale: locale}) do
- {:ok, updated_user}
+ if current_locale != locale do
+ case Users.update_user(user, %{locale: locale}) do
+ {:ok, %User{} = updated_user} ->
+ {:ok, updated_user}
+
+ {:error, %Ecto.Changeset{} = err} ->
+ Logger.debug(err)
+ {:error, dgettext("errors", "Error while updating locale")}
+ end
else
- false ->
- {:ok, user}
+ {:ok, user}
end
end
diff --git a/lib/graphql/schema/tag.ex b/lib/graphql/schema/tag.ex
index f2db09449..d995c8cff 100644
--- a/lib/graphql/schema/tag.ex
+++ b/lib/graphql/schema/tag.ex
@@ -23,6 +23,7 @@ defmodule Mobilizon.GraphQL.Schema.TagType do
object :tag_queries do
@desc "Get the list of tags"
field :tags, non_null(list_of(:tag)) do
+ arg(:filter, :string, description: "The filter to apply to the search")
arg(:page, :integer, default_value: 1, description: "The page in the paginated tags list")
arg(:limit, :integer, default_value: 10, description: "The limit of tags per page")
resolve(&Tag.list_tags/3)
diff --git a/lib/mix/tasks/mobilizon/actors/refresh.ex b/lib/mix/tasks/mobilizon/actors/refresh.ex
index 893af5f2b..9c1b20d06 100644
--- a/lib/mix/tasks/mobilizon/actors/refresh.ex
+++ b/lib/mix/tasks/mobilizon/actors/refresh.ex
@@ -71,9 +71,6 @@ defmodule Mix.Tasks.Mobilizon.Actors.Refresh do
Actor #{preferred_username} refreshed
""")
- {:actor, nil} ->
- shell_error("Error: No such actor")
-
{:error, err} when is_binary(err) ->
shell_error(err)
diff --git a/lib/mix/tasks/mobilizon/actors/utils.ex b/lib/mix/tasks/mobilizon/actors/utils.ex
index fd7e2e911..38e09e176 100644
--- a/lib/mix/tasks/mobilizon/actors/utils.ex
+++ b/lib/mix/tasks/mobilizon/actors/utils.ex
@@ -25,7 +25,7 @@ defmodule Mix.Tasks.Mobilizon.Actors.Utils do
end
# Profile from name
- @spec username_and_name(String.t() | nil, String.t() | nil) :: String.t()
+ @spec username_and_name(String.t() | nil, String.t() | nil) :: {String.t(), String.t()}
def username_and_name(nil, profile_name) do
{generate_username(profile_name), profile_name}
end
diff --git a/lib/mix/tasks/mobilizon/common.ex b/lib/mix/tasks/mobilizon/common.ex
index 8cc3ccffe..cee4f6b8a 100644
--- a/lib/mix/tasks/mobilizon/common.ex
+++ b/lib/mix/tasks/mobilizon/common.ex
@@ -61,7 +61,7 @@ defmodule Mix.Tasks.Mobilizon.Common do
else: IO.puts(message)
end
- @spec shell_error(String.t()) :: :ok
+ @spec shell_error(String.t(), Keyword.t()) :: nil | no_return
def shell_error(message, options \\ []) do
if mix_shell?() do
Mix.shell().error(message)
diff --git a/lib/mix/tasks/mobilizon/instance.ex b/lib/mix/tasks/mobilizon/instance.ex
index af4d844b1..c2529b117 100644
--- a/lib/mix/tasks/mobilizon/instance.ex
+++ b/lib/mix/tasks/mobilizon/instance.ex
@@ -35,6 +35,7 @@ defmodule Mix.Tasks.Mobilizon.Instance do
@preferred_cli_env "prod"
@shortdoc "Generates a new config"
+ @spec run(list(binary())) :: no_return
def run(["gen" | options]) do
{options, [], []} =
OptionParser.parse(
diff --git a/lib/mix/tasks/mobilizon/users/modify.ex b/lib/mix/tasks/mobilizon/users/modify.ex
index 42309b54f..ae02ed564 100644
--- a/lib/mix/tasks/mobilizon/users/modify.ex
+++ b/lib/mix/tasks/mobilizon/users/modify.ex
@@ -54,13 +54,20 @@ defmodule Mix.Tasks.Mobilizon.Users.Modify do
),
{:makes_changes, true} <- {:makes_changes, attrs != %{}},
{:ok, %User{} = user} <- Users.update_user(user, attrs) do
+ status =
+ case user.confirmed_at do
+ %DateTime{} = confirmed_at ->
+ "Activated on #{DateTime.to_string(confirmed_at)} (UTC)"
+
+ _ ->
+ "disabled"
+ end
+
shell_info("""
An user has been modified with the following information:
- email: #{user.email}
- Role: #{user.role}
- - account status: #{if user.confirmed_at,
- do: "activated on #{DateTime.to_string(user.confirmed_at)} (UTC)",
- else: "disabled"}
+ - account status: #{status}
""")
else
{:makes_changes, false} ->
diff --git a/lib/mix/tasks/mobilizon/users/new.ex b/lib/mix/tasks/mobilizon/users/new.ex
index b34f730d5..db013f7c1 100644
--- a/lib/mix/tasks/mobilizon/users/new.ex
+++ b/lib/mix/tasks/mobilizon/users/new.ex
@@ -78,6 +78,8 @@ defmodule Mix.Tasks.Mobilizon.Users.New do
shell_error("mobilizon.users.new requires an email as argument")
end
+ @spec create_user(String.t(), String.t() | nil, String.t(), Keyword.t()) ::
+ {:ok, User.t()} | {:error, Ecto.Changeset.t()}
defp create_user(email, provider, password, options) do
role = get_role(options)
@@ -96,6 +98,8 @@ defmodule Mix.Tasks.Mobilizon.Users.New do
end
end
+ @spec create_database_user(String.t(), String.t(), role()) ::
+ {:ok, User.t()} | {:error, Ecto.Changeset.t()}
defp create_database_user(email, password, role) do
Users.register(%{
email: email,
@@ -107,6 +111,8 @@ defmodule Mix.Tasks.Mobilizon.Users.New do
})
end
+ @spec create_user_from_provider(String.t(), String.t(), role()) ::
+ {:ok, User.t()} | {:error, Ecto.Changeset.t()}
defp create_user_from_provider(email, provider, role) do
Users.create_external(email, provider, %{role: role})
end
@@ -137,6 +143,7 @@ defmodule Mix.Tasks.Mobilizon.Users.New do
end
end
+ @spec check_password_and_provider_options(Keyword.t()) :: nil | no_return()
defp check_password_and_provider_options(options) do
if Keyword.get(options, :password) != nil && Keyword.get(options, :provider) != nil do
shell_error("""
diff --git a/lib/mix/tasks/mobilizon/users/show.ex b/lib/mix/tasks/mobilizon/users/show.ex
index 60ed9accc..c559f87f7 100644
--- a/lib/mix/tasks/mobilizon/users/show.ex
+++ b/lib/mix/tasks/mobilizon/users/show.ex
@@ -17,11 +17,18 @@ defmodule Mix.Tasks.Mobilizon.Users.Show do
with {:ok, %User{} = user} <- Users.get_user_by_email(email),
actors <- Users.get_actors_for_user(user) do
+ status =
+ case user.confirmed_at do
+ %DateTime{} = confirmed_at ->
+ "Activated on #{DateTime.to_string(confirmed_at)} (UTC)"
+
+ _ ->
+ "disabled"
+ end
+
shell_info("""
Informations for the user #{user.email}:
- - account status: #{if user.confirmed_at,
- do: "Activated on #{DateTime.to_string(user.confirmed_at)} (UTC)",
- else: "disabled"}
+ - account status: #{status}
- Role: #{user.role}
#{display_actors(actors)}
""")
diff --git a/lib/mobilizon/actors/actors.ex b/lib/mobilizon/actors/actors.ex
index 07e7e5d05..66d4f0c37 100644
--- a/lib/mobilizon/actors/actors.ex
+++ b/lib/mobilizon/actors/actors.ex
@@ -1466,7 +1466,7 @@ defmodule Mobilizon.Actors do
@spec actors_for_location(Ecto.Query.t(), String.t(), integer()) :: Ecto.Query.t()
defp actors_for_location(query, location, radius)
- when is_valid_string?(location) and not is_nil(radius) do
+ when is_valid_string(location) and not is_nil(radius) do
with {lon, lat} <- Geohax.decode(location),
point <- Geo.WKT.decode!("SRID=4326;POINT(#{lon} #{lat})") do
query
diff --git a/lib/mobilizon/config.ex b/lib/mobilizon/config.ex
index 4b7496902..bf0ce8c65 100644
--- a/lib/mobilizon/config.ex
+++ b/lib/mobilizon/config.ex
@@ -4,7 +4,6 @@ defmodule Mobilizon.Config do
"""
alias Mobilizon.Actors
- alias Mobilizon.Actors.Actor
alias Mobilizon.Service.GitStatus
@spec instance_config :: keyword
@@ -317,14 +316,14 @@ defmodule Mobilizon.Config do
@spec create_cache(atom()) :: integer()
defp create_cache(:anonymous_actor_id) do
- with {:ok, %Actor{id: actor_id}} <- Actors.get_or_create_internal_actor("anonymous") do
+ with {:ok, %{id: actor_id}} <- Actors.get_or_create_internal_actor("anonymous") do
actor_id
end
end
@spec create_cache(atom()) :: integer()
defp create_cache(:relay_actor_id) do
- with {:ok, %Actor{id: actor_id}} <- Actors.get_or_create_internal_actor("relay") do
+ with {:ok, %{id: actor_id}} <- Actors.get_or_create_internal_actor("relay") do
actor_id
end
end
diff --git a/lib/mobilizon/discussions/discussions.ex b/lib/mobilizon/discussions/discussions.ex
index 5c10d0e92..77106c470 100644
--- a/lib/mobilizon/discussions/discussions.ex
+++ b/lib/mobilizon/discussions/discussions.ex
@@ -249,8 +249,7 @@ defmodule Mobilizon.Discussions do
{:ok, comment}
end
else
- comment
- |> Repo.delete()
+ Repo.delete(comment)
end
end
diff --git a/lib/mobilizon/events/event.ex b/lib/mobilizon/events/event.ex
index b4a46d7b1..52b0f5ce7 100644
--- a/lib/mobilizon/events/event.ex
+++ b/lib/mobilizon/events/event.ex
@@ -206,6 +206,7 @@ defmodule Mobilizon.Events.Event do
defp put_tags(%Changeset{} = changeset, _), do: changeset
+ @spec process_tag(map() | Tag.t()) :: Tag.t() | Ecto.Changeset.t()
# We need a changeset instead of a raw struct because of slug which is generated in changeset
defp process_tag(%{id: id} = _tag) do
Events.get_tag(id)
@@ -248,13 +249,8 @@ defmodule Mobilizon.Events.Event do
# In case the provided picture is an existing one
@spec put_picture(Changeset.t(), map) :: Changeset.t()
defp put_picture(%Changeset{} = changeset, %{picture: %{media_id: id} = _picture}) do
- case Medias.get_media!(id) do
- %Media{} = picture ->
- put_assoc(changeset, :picture, picture)
-
- _ ->
- changeset
- end
+ %Media{} = picture = Medias.get_media!(id)
+ put_assoc(changeset, :picture, picture)
end
# In case it's a new picture
diff --git a/lib/mobilizon/events/events.ex b/lib/mobilizon/events/events.ex
index af3bb0f25..4fc85511e 100644
--- a/lib/mobilizon/events/events.ex
+++ b/lib/mobilizon/events/events.ex
@@ -631,9 +631,10 @@ defmodule Mobilizon.Events do
@doc """
Returns the list of tags.
"""
- @spec list_tags(integer | nil, integer | nil) :: [Tag.t()]
- def list_tags(page \\ nil, limit \\ nil) do
+ @spec list_tags(String.t() | nil, integer | nil, integer | nil) :: [Tag.t()]
+ def list_tags(filter \\ nil, page \\ nil, limit \\ nil) do
Tag
+ |> tag_filter(filter)
|> Page.paginate(page, limit)
|> Repo.all()
end
@@ -1396,7 +1397,7 @@ defmodule Mobilizon.Events do
end
@spec events_for_tags(Ecto.Query.t(), map()) :: Ecto.Query.t()
- defp events_for_tags(query, %{tags: tags}) when is_valid_string?(tags) do
+ defp events_for_tags(query, %{tags: tags}) when is_valid_string(tags) do
query
|> join(:inner, [q], te in "events_tags", on: q.id == te.event_id)
|> join(:inner, [q, ..., te], t in Tag, on: te.tag_id == t.id)
@@ -1410,7 +1411,7 @@ defmodule Mobilizon.Events do
do: query
defp events_for_location(query, %{location: location, radius: radius})
- when is_valid_string?(location) and not is_nil(radius) do
+ when is_valid_string(location) and not is_nil(radius) do
with {lon, lat} <- Geohax.decode(location),
point <- Geo.WKT.decode!("SRID=4326;POINT(#{lon} #{lat})") do
query
@@ -1471,6 +1472,16 @@ defmodule Mobilizon.Events do
from(t in Tag, where: t.title == ^title, limit: 1)
end
+ @spec tag_filter(Ecto.Query.t(), String.t() | nil) :: Ecto.Query.t()
+ defp tag_filter(query, nil), do: query
+ defp tag_filter(query, ""), do: query
+
+ defp tag_filter(query, filter) when is_binary(filter) do
+ query
+ |> where([q], ilike(q.slug, ^"%#{filter}%"))
+ |> or_where([q], ilike(q.title, ^"%#{filter}%"))
+ end
+
@spec tags_for_event_query(integer) :: Ecto.Query.t()
defp tags_for_event_query(event_id) do
from(
diff --git a/lib/mobilizon/events/participant.ex b/lib/mobilizon/events/participant.ex
index 1670e786e..b490a379d 100644
--- a/lib/mobilizon/events/participant.ex
+++ b/lib/mobilizon/events/participant.ex
@@ -10,7 +10,7 @@ defmodule Mobilizon.Events.Participant do
alias Mobilizon.Actors.Actor
alias Mobilizon.Events
alias Mobilizon.Events.{Event, ParticipantRole}
- alias Mobilizon.Web.Email.Checker
+ alias Mobilizon.Events.Participant.Metadata
alias Mobilizon.Web.Endpoint
@@ -24,7 +24,6 @@ defmodule Mobilizon.Events.Participant do
@required_attrs [:url, :role, :event_id, :actor_id]
@attrs @required_attrs
- @metadata_attrs [:email, :confirmation_token, :cancellation_token, :message, :locale]
@timestamps_opts [type: :utc_datetime]
@@ -33,13 +32,7 @@ defmodule Mobilizon.Events.Participant do
field(:role, ParticipantRole, default: :participant)
field(:url, :string)
- embeds_one :metadata, Metadata, on_replace: :delete do
- field(:email, :string)
- field(:confirmation_token, :string)
- field(:cancellation_token, :string)
- field(:message, :string)
- field(:locale, :string)
- end
+ embeds_one(:metadata, Metadata, on_replace: :delete)
belongs_to(:event, Event, primary_key: true)
belongs_to(:actor, Actor, primary_key: true)
@@ -68,18 +61,12 @@ defmodule Mobilizon.Events.Participant do
def changeset(%__MODULE__{} = participant, attrs) do
participant
|> cast(attrs, @attrs)
- |> cast_embed(:metadata, with: &metadata_changeset/2)
+ |> cast_embed(:metadata)
|> ensure_url()
|> validate_required(@required_attrs)
|> unique_constraint(:actor_id, name: :participants_event_id_actor_id_index)
end
- defp metadata_changeset(schema, params) do
- schema
- |> cast(params, @metadata_attrs)
- |> Checker.validate_changeset()
- end
-
# If there's a blank URL that's because we're doing the first insert
@spec ensure_url(Ecto.Changeset.t()) :: Ecto.Changeset.t()
defp ensure_url(%Ecto.Changeset{data: %__MODULE__{url: nil}} = changeset) do
diff --git a/lib/mobilizon/events/participant_metadata.ex b/lib/mobilizon/events/participant_metadata.ex
new file mode 100644
index 000000000..bca00a0cb
--- /dev/null
+++ b/lib/mobilizon/events/participant_metadata.ex
@@ -0,0 +1,36 @@
+defmodule Mobilizon.Events.Participant.Metadata do
+ @moduledoc """
+ Participation stats on event
+ """
+
+ use Ecto.Schema
+ import Ecto.Changeset
+ alias Mobilizon.Web.Email.Checker
+
+ @type t :: %__MODULE__{
+ email: String.t(),
+ confirmation_token: String.t(),
+ cancellation_token: String.t(),
+ message: String.t(),
+ locale: String.t()
+ }
+
+ @attrs [:email, :confirmation_token, :cancellation_token, :message, :locale]
+
+ @derive Jason.Encoder
+ embedded_schema do
+ field(:email, :string)
+ field(:confirmation_token, :string)
+ field(:cancellation_token, :string)
+ field(:message, :string)
+ field(:locale, :string)
+ end
+
+ @doc false
+ @spec changeset(t, map) :: Ecto.Changeset.t()
+ def changeset(schema, params) do
+ schema
+ |> cast(params, @attrs)
+ |> Checker.validate_changeset()
+ end
+end
diff --git a/lib/mobilizon/medias/media.ex b/lib/mobilizon/medias/media.ex
index 0133b5bfc..5feb5e944 100644
--- a/lib/mobilizon/medias/media.ex
+++ b/lib/mobilizon/medias/media.ex
@@ -51,7 +51,7 @@ defmodule Mobilizon.Medias.Media do
end
@doc false
- @spec changeset(struct(), map) :: Ecto.Changeset.t()
+ @spec metadata_changeset(Metadata.t(), map) :: Ecto.Changeset.t()
def metadata_changeset(metadata, attrs) do
metadata
|> cast(attrs, @metadata_attrs)
diff --git a/lib/service/activity/renderer/renderer.ex b/lib/service/activity/renderer/renderer.ex
index 87e1d56d1..14eb6693f 100644
--- a/lib/service/activity/renderer/renderer.ex
+++ b/lib/service/activity/renderer/renderer.ex
@@ -19,9 +19,17 @@ defmodule Mobilizon.Service.Activity.Renderer do
require Logger
import Mobilizon.Web.Gettext, only: [dgettext: 3]
- @type render :: %{body: String.t(), url: String.t()}
+ @type render :: %{
+ body: String.t(),
+ url: String.t(),
+ timestamp: String.t(),
+ locale: String.t(),
+ title: String.t()
+ }
- @callback render(entity :: Activity.t(), Keyword.t()) :: render()
+ @type common_render :: %{body: String.t(), url: String.t()}
+
+ @callback render(entity :: Activity.t(), Keyword.t()) :: common_render()
@spec render(Activity.t()) :: render()
def render(%Activity{} = activity, options \\ []) do
@@ -43,6 +51,7 @@ defmodule Mobilizon.Service.Activity.Renderer do
res
end
+ @spec do_render(Activity.t(), Keyword.t()) :: common_render()
defp do_render(%Activity{type: type} = activity, options) do
case type do
:discussion -> Discussion.render(activity, options)
diff --git a/lib/service/activity/utils.ex b/lib/service/activity/utils.ex
index 7a0f15754..6441ea6c6 100644
--- a/lib/service/activity/utils.ex
+++ b/lib/service/activity/utils.ex
@@ -12,7 +12,7 @@ defmodule Mobilizon.Service.Activity.Utils do
|> add_activity_object()
end
- @spec add_activity_object(Activity.t()) :: Activity.t()
+ @spec add_activity_object(Activity.t()) :: map()
def add_activity_object(%Activity{} = activity) do
Map.put(activity, :object, ActivityService.object(activity))
end
diff --git a/lib/service/geospatial/nominatim.ex b/lib/service/geospatial/nominatim.ex
index 9b8754d58..c32e7ebcd 100644
--- a/lib/service/geospatial/nominatim.ex
+++ b/lib/service/geospatial/nominatim.ex
@@ -143,15 +143,13 @@ defmodule Mobilizon.Service.Geospatial.Nominatim do
if is_nil(value), do: url, else: do_add_parameter(url, key, value)
end
- @spec do_add_parameter(String.t(), atom(), any()) :: String.t()
+ @spec do_add_parameter(String.t(), :zoom | :country_code | :api_key, any()) :: String.t()
defp do_add_parameter(url, :zoom, zoom),
do: "#{url}&zoom=#{zoom}"
- @spec do_add_parameter(String.t(), atom(), any()) :: String.t()
defp do_add_parameter(url, :country_code, country_code),
do: "#{url}&countrycodes=#{country_code}"
- @spec do_add_parameter(String.t(), atom(), any()) :: String.t()
defp do_add_parameter(url, :api_key, api_key),
do: "#{url}&key=#{api_key}"
diff --git a/lib/service/geospatial/provider.ex b/lib/service/geospatial/provider.ex
index 4d9fadd11..dd2f4d890 100644
--- a/lib/service/geospatial/provider.ex
+++ b/lib/service/geospatial/provider.ex
@@ -67,7 +67,7 @@ defmodule Mobilizon.Service.Geospatial.Provider do
@doc """
Returns a `Geo.Point` for given coordinates
"""
- @spec coordinates([number], number) :: Geo.Point.t()
+ @spec coordinates([number], number) :: Geo.Point.t() | nil
def coordinates(coords, srid \\ 4326)
def coordinates([x, y], srid) when is_number(x) and is_number(y) do
@@ -78,7 +78,6 @@ defmodule Mobilizon.Service.Geospatial.Provider do
%Geo.Point{coordinates: {String.to_float(x), String.to_float(y)}, srid: srid}
end
- @spec coordinates(any) :: nil
def coordinates(_, _), do: nil
@spec endpoint(atom()) :: String.t()
diff --git a/lib/service/guards.ex b/lib/service/guards.ex
index e48e65076..0db60d099 100644
--- a/lib/service/guards.ex
+++ b/lib/service/guards.ex
@@ -3,7 +3,31 @@ defmodule Mobilizon.Service.Guards do
Various guards
"""
- defguard is_valid_string?(value) when is_binary(value) and value != ""
+ @doc """
+ Returns `true` if `term` is a valid string and not empty.
- defguard is_valid_list?(value) when is_list(value) and length(value) > 0
+ ## Examples
+
+ iex> is_valid_string("one")
+ true
+ iex> is_valid_string("")
+ false
+ iex> is_valid_string(2)
+ false
+ """
+ defguard is_valid_string(term) when is_binary(term) and term != ""
+
+ @doc """
+ Returns `true` if `term` is a valid list and not empty.
+
+ ## Examples
+
+ iex> is_valid_list(["one"])
+ true
+ iex> is_valid_list([])
+ false
+ iex> is_valid_list("foo")
+ false
+ """
+ defguard is_valid_list(term) when is_list(term) and length(term) > 0
end
diff --git a/lib/service/language_detection/language_detection.ex b/lib/service/language_detection/language_detection.ex
index 96d50d498..87b8af0cb 100644
--- a/lib/service/language_detection/language_detection.ex
+++ b/lib/service/language_detection/language_detection.ex
@@ -64,7 +64,7 @@ defmodule Mobilizon.Service.LanguageDetection do
def normalize(language) do
case Cldr.AcceptLanguage.parse(language, Mobilizon.Cldr) do
- {:ok, [{_, tag}]} ->
+ {:ok, [{_, %Cldr.LanguageTag{} = tag}]} ->
tag.language
_ ->
diff --git a/lib/web/auth/context.ex b/lib/web/auth/context.ex
index 68a1c4ffe..1d472156c 100644
--- a/lib/web/auth/context.ex
+++ b/lib/web/auth/context.ex
@@ -9,17 +9,20 @@ defmodule Mobilizon.Web.Auth.Context do
alias Mobilizon.Service.ErrorReporting.Sentry, as: SentryAdapter
alias Mobilizon.Users.User
+ @spec init(Plug.opts()) :: Plug.opts()
def init(opts) do
opts
end
+ @spec call(Plug.Conn.t(), Plug.opts()) :: Plug.Conn.t()
def call(%{assigns: %{ip: _}} = conn, _opts), do: conn
def call(conn, _opts) do
set_user_information_in_context(conn)
end
- def set_user_information_in_context(conn) do
+ @spec set_user_information_in_context(Plug.Conn.t()) :: Plug.Conn.t()
+ defp set_user_information_in_context(conn) do
context = %{ip: conn.remote_ip |> :inet.ntoa() |> to_string()}
{conn, context} =
diff --git a/lib/web/cache/cache.ex b/lib/web/cache/cache.ex
index 5afcd4690..08e3068f0 100644
--- a/lib/web/cache/cache.ex
+++ b/lib/web/cache/cache.ex
@@ -4,16 +4,19 @@ defmodule Mobilizon.Web.Cache do
"""
alias Mobilizon.Actors.Actor
-
alias Mobilizon.Web.Cache.ActivityPub
+ import Mobilizon.Service.Guards, only: [is_valid_string: 1]
@caches [:activity_pub, :feed, :ics]
+ @type local_actor :: %Actor{domain: nil}
+
@doc """
- Clears all caches for an actor.
+ Clears all caches for a local actor.
"""
- @spec clear_cache(Actor.t()) :: {:ok, true}
- def clear_cache(%Actor{preferred_username: preferred_username, domain: nil}) do
+ @spec clear_cache(%Actor{domain: nil, preferred_username: String.t()}) :: :ok
+ def clear_cache(%Actor{preferred_username: preferred_username, domain: nil})
+ when is_valid_string(preferred_username) do
Enum.each(@caches, &Cachex.del(&1, "actor_" <> preferred_username))
end
diff --git a/lib/web/channels/graphql_socket.ex b/lib/web/channels/graphql_socket.ex
index b694416a1..0bf0e7244 100644
--- a/lib/web/channels/graphql_socket.ex
+++ b/lib/web/channels/graphql_socket.ex
@@ -6,6 +6,7 @@ defmodule Mobilizon.Web.GraphQLSocket do
alias Mobilizon.Users.User
+ @spec connect(map, Phoenix.Socket.t()) :: {:ok, Phoenix.Socket.t()} | :error
def connect(%{"token" => token}, socket) do
with {:ok, authed_socket} <-
Guardian.Phoenix.Socket.authenticate(socket, Mobilizon.Web.Auth.Guardian, token),
@@ -26,5 +27,6 @@ defmodule Mobilizon.Web.GraphQLSocket do
def connect(_args, _socket), do: :error
+ @spec id(any) :: nil
def id(_socket), do: nil
end
diff --git a/lib/web/email/activity.ex b/lib/web/email/activity.ex
index 17268424c..15570e5c3 100644
--- a/lib/web/email/activity.ex
+++ b/lib/web/email/activity.ex
@@ -12,7 +12,7 @@ defmodule Mobilizon.Web.Email.Activity do
alias Mobilizon.Config
alias Mobilizon.Web.Email
- @spec direct_activity(String.t(), list(), String.t()) ::
+ @spec direct_activity(String.t(), list(), Keyword.t()) ::
Bamboo.Email.t()
def direct_activity(
email,
diff --git a/lib/web/email/mailer.ex b/lib/web/email/mailer.ex
index 6f4693746..68f9d905a 100644
--- a/lib/web/email/mailer.ex
+++ b/lib/web/email/mailer.ex
@@ -5,6 +5,7 @@ defmodule Mobilizon.Web.Email.Mailer do
use Bamboo.Mailer, otp_app: :mobilizon
alias Mobilizon.Service.ErrorReporting.Sentry
+ @spec send_email_later(Bamboo.Email.t()) :: Bamboo.Email.t()
def send_email_later(email) do
Mobilizon.Web.Email.Mailer.deliver_later!(email)
rescue
@@ -17,6 +18,7 @@ defmodule Mobilizon.Web.Email.Mailer do
reraise error, __STACKTRACE__
end
+ @spec send_email(Bamboo.Email.t()) :: Bamboo.Email.t() | {Bamboo.Email.t(), any()}
def send_email(email) do
Mobilizon.Web.Email.Mailer.deliver_now!(email)
rescue
diff --git a/lib/web/email/participation.ex b/lib/web/email/participation.ex
index edf319906..7fea008fb 100644
--- a/lib/web/email/participation.ex
+++ b/lib/web/email/participation.ex
@@ -50,7 +50,6 @@ defmodule Mobilizon.Web.Email.Participation do
Bamboo.Email.t()
def participation_updated(user, participant, locale \\ "en")
- @spec participation_updated(User.t(), Participant.t(), String.t()) :: Bamboo.Email.t()
def participation_updated(
%User{email: email},
%Participant{} = participant,
@@ -58,7 +57,6 @@ defmodule Mobilizon.Web.Email.Participation do
),
do: participation_updated(email, participant, locale)
- @spec participation_updated(String.t(), Participant.t(), String.t()) :: Bamboo.Email.t()
def participation_updated(
email,
%Participant{event: event, role: :rejected},
@@ -79,7 +77,6 @@ defmodule Mobilizon.Web.Email.Participation do
|> render(:event_participation_rejected)
end
- @spec participation_updated(String.t(), Participant.t(), String.t()) :: Bamboo.Email.t()
def participation_updated(
email,
%Participant{event: %Event{join_options: :free} = event, role: :participant},
@@ -100,7 +97,6 @@ defmodule Mobilizon.Web.Email.Participation do
|> render(:event_participation_confirmed)
end
- @spec participation_updated(String.t(), Participant.t(), String.t()) :: Bamboo.Email.t()
def participation_updated(
email,
%Participant{event: event, role: :participant},
diff --git a/lib/web/email/user.ex b/lib/web/email/user.ex
index 54cf253ee..638f0dcca 100644
--- a/lib/web/email/user.ex
+++ b/lib/web/email/user.ex
@@ -57,6 +57,7 @@ defmodule Mobilizon.Web.Email.User do
|> render(:password_reset)
end
+ @spec check_confirmation_token(String.t()) :: {:ok, User.t()} | {:error, :invalid_token}
def check_confirmation_token(token) when is_binary(token) do
with %User{} = user <- Users.get_user_by_activation_token(token),
{:ok, %User{} = user} <-
@@ -86,7 +87,7 @@ defmodule Mobilizon.Web.Email.User do
end
end
- @spec send_confirmation_email(User.t(), String.t()) :: {:ok, Bamboo.Email.t()} | {:error, any()}
+ @spec send_confirmation_email(User.t(), String.t()) :: Bamboo.Email.t()
def send_confirmation_email(%User{} = user, locale \\ "en") do
user
|> Email.User.confirmation_email(locale)
@@ -96,7 +97,8 @@ defmodule Mobilizon.Web.Email.User do
@doc """
Check that the provided token is correct and update provided password
"""
- @spec check_reset_password_token(String.t(), String.t()) :: tuple
+ @spec check_reset_password_token(String.t(), String.t()) ::
+ {:ok, User.t()} | {:error, String.t()}
def check_reset_password_token(password, token) do
with %User{} = user <- Users.get_user_by_reset_password_token(token),
{:ok, %User{} = user} <-
diff --git a/lib/web/mobilizon_web.ex b/lib/web/mobilizon_web.ex
index 76f5b5fe1..059b9eb92 100644
--- a/lib/web/mobilizon_web.ex
+++ b/lib/web/mobilizon_web.ex
@@ -21,8 +21,8 @@ defmodule Mobilizon.Web do
quote do
use Phoenix.Controller, namespace: Mobilizon.Web
import Plug.Conn
- import Mobilizon.Web.Router.Helpers
import Mobilizon.Web.Gettext
+ alias Mobilizon.Web.Router.Helpers, as: Routes
end
end
@@ -33,14 +33,13 @@ defmodule Mobilizon.Web do
pattern: "**/*",
namespace: Mobilizon.Web
- # Import convenience functions from controllers
- import Phoenix.Controller, only: [get_flash: 2, view_module: 1]
-
# Use all HTML functionality (forms, tags, etc)
use Phoenix.HTML
- import Mobilizon.Web.Router.Helpers
+ import Phoenix.View
+ import Mobilizon.Web.ErrorHelpers
import Mobilizon.Web.Gettext
+ alias Mobilizon.Web.Router.Helpers, as: Routes
end
end
diff --git a/lib/web/plugs/http_security_plug.ex b/lib/web/plugs/http_security_plug.ex
index 00f0eede8..fd2ddba65 100644
--- a/lib/web/plugs/http_security_plug.ex
+++ b/lib/web/plugs/http_security_plug.ex
@@ -19,12 +19,13 @@ defmodule Mobilizon.Web.Plugs.HTTPSecurityPlug do
if Config.get([:http_security, :enabled]) do
conn
|> merge_resp_headers(headers(options))
- |> maybe_send_sts_header(Config.get([:http_security, :sts]))
+ |> maybe_send_sts_header(Config.get([:http_security, :sts], false))
else
conn
end
end
+ @spec headers(Keyword.t()) :: list({String.t(), String.t()})
defp headers(options) do
referrer_policy =
Keyword.get(options, :referrer_policy, Config.get([:http_security, :referrer_policy]))
@@ -55,6 +56,7 @@ defmodule Mobilizon.Web.Plugs.HTTPSecurityPlug do
@style_src "style-src 'self' "
@font_src "font-src 'self' "
+ @spec csp_string(Keyword.t()) :: String.t()
defp csp_string(options) do
scheme = Keyword.get(options, :scheme, Config.get([Pleroma.Web.Endpoint, :url])[:scheme])
static_url = Mobilizon.Web.Endpoint.static_url()
@@ -115,10 +117,11 @@ defmodule Mobilizon.Web.Plugs.HTTPSecurityPlug do
|> to_string()
end
+ @spec add_csp_param(list(), list(String.t()) | String.t() | nil) :: list()
defp add_csp_param(csp_iodata, nil), do: csp_iodata
-
defp add_csp_param(csp_iodata, param), do: [[param, ?;] | csp_iodata]
+ @spec maybe_send_sts_header(Plug.Conn.t(), boolean()) :: Plug.Conn.t()
defp maybe_send_sts_header(conn, true) do
max_age_sts = Config.get([:http_security, :sts_max_age])
@@ -127,8 +130,9 @@ defmodule Mobilizon.Web.Plugs.HTTPSecurityPlug do
])
end
- defp maybe_send_sts_header(conn, _), do: conn
+ defp maybe_send_sts_header(conn, false), do: conn
+ @spec get_csp_config(atom(), Keyword.t()) :: String.t()
defp get_csp_config(type, options) do
options
|> Keyword.get(type, Config.get([:http_security, :csp_policy, type]))
diff --git a/lib/web/plugs/http_signatures.ex b/lib/web/plugs/http_signatures.ex
index 80333a782..f57b4d4c9 100644
--- a/lib/web/plugs/http_signatures.ex
+++ b/lib/web/plugs/http_signatures.ex
@@ -16,6 +16,7 @@ defmodule Mobilizon.Web.Plugs.HTTPSignatures do
options
end
+ @spec call(Plug.Conn.t(), any) :: Plug.Conn.t()
def call(%{assigns: %{valid_signature: true}} = conn, _opts) do
conn
end
diff --git a/lib/web/plugs/uploaded_media.ex b/lib/web/plugs/uploaded_media.ex
index 14538249f..150386f29 100644
--- a/lib/web/plugs/uploaded_media.ex
+++ b/lib/web/plugs/uploaded_media.ex
@@ -45,11 +45,13 @@ defmodule Mobilizon.Web.Plugs.UploadedMedia do
config = Config.get([Upload])
- with uploader <- Keyword.fetch!(config, :uploader),
- proxy_remote = Keyword.get(config, :proxy_remote, false),
- {:ok, get_method} <- uploader.get_file(file) do
- get_media(conn, get_method, proxy_remote, opts)
- else
+ uploader = Keyword.fetch!(config, :uploader)
+ proxy_remote = Keyword.get(config, :proxy_remote, false)
+
+ case uploader.get_file(file) do
+ {:ok, get_method} ->
+ get_media(conn, get_method, proxy_remote, opts)
+
_ ->
conn
|> send_resp(500, "Failed")
@@ -59,6 +61,12 @@ defmodule Mobilizon.Web.Plugs.UploadedMedia do
def call(conn, _opts), do: conn
+ @spec get_media(
+ Plug.Conn.t(),
+ {:static_dir, String.t()} | {:url, String.t()} | any(),
+ boolean,
+ any()
+ ) :: Plug.Conn.t()
defp get_media(conn, {:static_dir, directory}, _, opts) do
static_opts =
opts
diff --git a/lib/web/proxy/reverse_proxy.ex b/lib/web/proxy/reverse_proxy.ex
index f09d751a0..766dd5e06 100644
--- a/lib/web/proxy/reverse_proxy.ex
+++ b/lib/web/proxy/reverse_proxy.ex
@@ -381,6 +381,10 @@ defmodule Mobilizon.Web.ReverseProxy do
defp body_size_constraint(_, _), do: :ok
+ @spec check_read_duration(any(), integer()) ::
+ {:ok, {integer(), integer()}}
+ | {:ok, :no_duration_limit, :no_duration_limit}
+ | {:error, :read_duration_exceeded}
defp check_read_duration(duration, max)
when is_integer(duration) and is_integer(max) and max > 0 do
if duration > max do
diff --git a/lib/web/templates/email/activity/_comment_activity_item.html.eex b/lib/web/templates/email/activity/_comment_activity_item.html.eex
index 4a9abd54b..7b648bb46 100644
--- a/lib/web/templates/email/activity/_comment_activity_item.html.eex
+++ b/lib/web/templates/email/activity/_comment_activity_item.html.eex
@@ -5,7 +5,7 @@
%{
profile: "#{Mobilizon.Actors.Actor.display_name_and_username(@activity.author)}",
event: "#{Mobilizon.Actors.Actor.display_name_and_username(@activity.author)}",
event: "#{Mobilizon.Actors.Actor.display_name_and_username(@activity.author)}",
event: "#{Mobilizon.Actors.Actor.display_name_and_username(@activity.author)}",
event: "
-<%= page_url(Mobilizon.Web.Endpoint, :event, @activity.subject_params["event_uuid"]) |> URI.decode() %><% :participation_event_comment -> %><%= dgettext("activity", "%{profile} has posted an announcement under event %{event}.",
+<%= Routes.page_url(Mobilizon.Web.Endpoint, :event, @activity.subject_params["event_uuid"]) |> URI.decode() %><% :participation_event_comment -> %><%= dgettext("activity", "%{profile} has posted an announcement under event %{event}.",
%{
profile: Mobilizon.Actors.Actor.display_name_and_username(@activity.author),
event: @activity.subject_params["event_title"]
}
) %>
-<%= page_url(Mobilizon.Web.Endpoint, :event, @activity.subject_params["event_uuid"]) |> URI.decode() %><% :event_new_comment -> %><%= if @activity.subject_params["comment_reply_to"] do %><%=dgettext("activity", "%{profile} has posted a new reply under your event %{event}.",
+<%= Routes.page_url(Mobilizon.Web.Endpoint, :event, @activity.subject_params["event_uuid"]) |> URI.decode() %><% :event_new_comment -> %><%= if @activity.subject_params["comment_reply_to"] do %><%=dgettext("activity", "%{profile} has posted a new reply under your event %{event}.",
%{
profile: Mobilizon.Actors.Actor.display_name_and_username(@activity.author),
event: @activity.subject_params["event_title"]
}
) %>
-<%= "#{page_url(Mobilizon.Web.Endpoint, :event, @activity.subject_params["event_uuid"]) |> URI.decode()}#comment-#{@activity.subject_params["comment_reply_to_uuid"]}-#{@activity.subject_params["comment_uuid"]}" %><% else %><%= dgettext("activity", "%{profile} has posted a new comment under your event %{event}.",
+<%= "#{Routes.page_url(Mobilizon.Web.Endpoint, :event, @activity.subject_params["event_uuid"]) |> URI.decode()}#comment-#{@activity.subject_params["comment_reply_to_uuid"]}-#{@activity.subject_params["comment_uuid"]}" %><% else %><%= dgettext("activity", "%{profile} has posted a new comment under your event %{event}.",
%{
profile: Mobilizon.Actors.Actor.display_name_and_username(@activity.author),
event: @activity.subject_params["event_title"]
}
) %>
-<%= "#{page_url(Mobilizon.Web.Endpoint, :event, @activity.subject_params["event_uuid"]) |> URI.decode()}#comment-#{@activity.subject_params["comment_uuid"]}"%><% end %><% end %>
\ No newline at end of file
+<%= "#{Routes.page_url(Mobilizon.Web.Endpoint, :event, @activity.subject_params["event_uuid"]) |> URI.decode()}#comment-#{@activity.subject_params["comment_uuid"]}"%><% end %><% end %>
\ No newline at end of file
diff --git a/lib/web/templates/email/activity/_discussion_activity_item.html.eex b/lib/web/templates/email/activity/_discussion_activity_item.html.eex
index b678ae5ab..3a74f1168 100644
--- a/lib/web/templates/email/activity/_discussion_activity_item.html.eex
+++ b/lib/web/templates/email/activity/_discussion_activity_item.html.eex
@@ -5,7 +5,7 @@
%{
profile: "#{Mobilizon.Actors.Actor.display_name_and_username(@activity.author)}",
discussion: "#{Mobilizon.Actors.Actor.display_name_and_username(@activity.author)}",
discussion: "#{Mobilizon.Actors.Actor.display_name_and_username(@activity.author)}",
discussion: "#{Mobilizon.Actors.Actor.display_name_and_username(@activity.author)}",
discussion: "
-<%= page_url(Mobilizon.Web.Endpoint, :discussion, Mobilizon.Actors.Actor.preferred_username_and_domain(@activity.group), @activity.subject_params["discussion_slug"]) |> URI.decode() %><% :discussion_replied -> %><%= dgettext("activity", "%{profile} replied to the discussion %{discussion}.",
+<%= Routes.page_url(Mobilizon.Web.Endpoint, :discussion, Mobilizon.Actors.Actor.preferred_username_and_domain(@activity.group), @activity.subject_params["discussion_slug"]) |> URI.decode() %><% :discussion_replied -> %><%= dgettext("activity", "%{profile} replied to the discussion %{discussion}.",
%{
profile: Mobilizon.Actors.Actor.display_name_and_username(@activity.author),
discussion: @activity.subject_params["discussion_title"]
}
) %>
-<%= page_url(Mobilizon.Web.Endpoint, :discussion, Mobilizon.Actors.Actor.preferred_username_and_domain(@activity.group), @activity.subject_params["discussion_slug"]) |> URI.decode() %><% :discussion_renamed -> %><%= dgettext("activity", "%{profile} renamed the discussion %{discussion}.",
+<%= Routes.page_url(Mobilizon.Web.Endpoint, :discussion, Mobilizon.Actors.Actor.preferred_username_and_domain(@activity.group), @activity.subject_params["discussion_slug"]) |> URI.decode() %><% :discussion_renamed -> %><%= dgettext("activity", "%{profile} renamed the discussion %{discussion}.",
%{
profile: Mobilizon.Actors.Actor.display_name_and_username(@activity.author),
discussion: @activity.subject_params["discussion_title"]
}
) %>
-<%= page_url(Mobilizon.Web.Endpoint, :discussion, Mobilizon.Actors.Actor.preferred_username_and_domain(@activity.group), @activity.subject_params["discussion_slug"]) |> URI.decode() %><% :discussion_archived -> %><%= dgettext("activity", "%{profile} archived the discussion %{discussion}.",
+<%= Routes.page_url(Mobilizon.Web.Endpoint, :discussion, Mobilizon.Actors.Actor.preferred_username_and_domain(@activity.group), @activity.subject_params["discussion_slug"]) |> URI.decode() %><% :discussion_archived -> %><%= dgettext("activity", "%{profile} archived the discussion %{discussion}.",
%{
profile: Mobilizon.Actors.Actor.display_name_and_username(@activity.author),
discussion: @activity.subject_params["discussion_title"]
}
) %>
-<%= page_url(Mobilizon.Web.Endpoint, :discussion, Mobilizon.Actors.Actor.preferred_username_and_domain(@activity.group), @activity.subject_params["discussion_slug"]) |> URI.decode() %><% :discussion_deleted -> %><%= dgettext("activity", "%{profile} deleted the discussion %{discussion}.",
+<%= Routes.page_url(Mobilizon.Web.Endpoint, :discussion, Mobilizon.Actors.Actor.preferred_username_and_domain(@activity.group), @activity.subject_params["discussion_slug"]) |> URI.decode() %><% :discussion_deleted -> %><%= dgettext("activity", "%{profile} deleted the discussion %{discussion}.",
%{
profile: Mobilizon.Actors.Actor.display_name_and_username(@activity.author),
discussion: @activity.subject_params["discussion_title"]
diff --git a/lib/web/templates/email/activity/_event_activity_item.html.eex b/lib/web/templates/email/activity/_event_activity_item.html.eex
index 074e9c4a6..5f32ab87b 100644
--- a/lib/web/templates/email/activity/_event_activity_item.html.eex
+++ b/lib/web/templates/email/activity/_event_activity_item.html.eex
@@ -5,7 +5,7 @@
%{
profile: "#{Mobilizon.Actors.Actor.display_name_and_username(@activity.author)}",
event: "#{Mobilizon.Actors.Actor.display_name_and_username(@activity.author)}",
event: "#{Mobilizon.Actors.Actor.display_name_and_username(@activity.author)}",
event: "#{Mobilizon.Actors.Actor.display_name_and_username(@activity.author)}",
event: "
-<%= page_url(Mobilizon.Web.Endpoint, :event, @activity.subject_params["event_uuid"]) |> URI.decode() %><% :event_updated -> %><%= dgettext("activity", "The event %{event} was updated by %{profile}.",
+<%= Routes.page_url(Mobilizon.Web.Endpoint, :event, @activity.subject_params["event_uuid"]) |> URI.decode() %><% :event_updated -> %><%= dgettext("activity", "The event %{event} was updated by %{profile}.",
%{
profile: Mobilizon.Actors.Actor.display_name_and_username(@activity.author),
event: @activity.subject_params["event_title"]
}
) %>
-<%= page_url(Mobilizon.Web.Endpoint, :event, @activity.subject_params["event_uuid"]) |> URI.decode() %><% :event_deleted -> %><%= dgettext("activity", "The event %{event} was deleted by %{profile}.",
+<%= Routes.page_url(Mobilizon.Web.Endpoint, :event, @activity.subject_params["event_uuid"]) |> URI.decode() %><% :event_deleted -> %><%= dgettext("activity", "The event %{event} was deleted by %{profile}.",
%{
profile: Mobilizon.Actors.Actor.display_name_and_username(@activity.author),
event: @activity.subject_params["event_title"]
@@ -22,10 +22,10 @@
event: @activity.subject_params["event_title"]
}
) %>
-<%= page_url(Mobilizon.Web.Endpoint, :event, @activity.subject_params["event_uuid"]) |> URI.decode() %><% else %><%= dgettext("activity", "%{profile} posted a comment on the event %{event}.",
+<%= Routes.page_url(Mobilizon.Web.Endpoint, :event, @activity.subject_params["event_uuid"]) |> URI.decode() %><% else %><%= dgettext("activity", "%{profile} posted a comment on the event %{event}.",
%{
profile: Mobilizon.Actors.Actor.display_name_and_username(@activity.author),
event: @activity.subject_params["event_title"]
}
) %>
-<%= page_url(Mobilizon.Web.Endpoint, :event, @activity.subject_params["event_uuid"]) |> URI.decode() %><% end %><% end %>
\ No newline at end of file
+<%= Routes.page_url(Mobilizon.Web.Endpoint, :event, @activity.subject_params["event_uuid"]) |> URI.decode() %><% end %><% end %>
\ No newline at end of file
diff --git a/lib/web/templates/email/activity/_group_activity_item.html.eex b/lib/web/templates/email/activity/_group_activity_item.html.eex
index a17c7956f..bf4be4e8e 100644
--- a/lib/web/templates/email/activity/_group_activity_item.html.eex
+++ b/lib/web/templates/email/activity/_group_activity_item.html.eex
@@ -5,7 +5,7 @@
%{
profile: "#{Mobilizon.Actors.Actor.display_name_and_username(@activity.author)}",
group: "#{Mobilizon.Actors.Actor.display_name_and_username(@activity.author)}",
group: "
-<%= page_url(Mobilizon.Web.Endpoint, :actor, @activity.subject_params["group_federated_username"]) |> URI.decode() %><% :group_updated -> %><%= dgettext("activity", "%{profile} updated the group %{group}.",
+<%= Routes.page_url(Mobilizon.Web.Endpoint, :actor, @activity.subject_params["group_federated_username"]) |> URI.decode() %><% :group_updated -> %><%= dgettext("activity", "%{profile} updated the group %{group}.",
%{
profile: Mobilizon.Actors.Actor.display_name_and_username(@activity.author),
group: @activity.subject_params["group_name"]
}
) %>
-<%= page_url(Mobilizon.Web.Endpoint, :actor, @activity.subject_params["group_federated_username"]) |> URI.decode() %><% end %>
\ No newline at end of file
+<%= Routes.page_url(Mobilizon.Web.Endpoint, :actor, @activity.subject_params["group_federated_username"]) |> URI.decode() %><% end %>
\ No newline at end of file
diff --git a/lib/web/templates/email/activity/_post_activity_item.html.eex b/lib/web/templates/email/activity/_post_activity_item.html.eex
index 856056be9..81733b4ab 100644
--- a/lib/web/templates/email/activity/_post_activity_item.html.eex
+++ b/lib/web/templates/email/activity/_post_activity_item.html.eex
@@ -5,7 +5,7 @@
%{
profile: "#{Mobilizon.Actors.Actor.display_name_and_username(@activity.author)}",
post: "#{Mobilizon.Actors.Actor.display_name_and_username(@activity.author)}",
post: "
-<%= page_url(Mobilizon.Web.Endpoint, :post, @activity.subject_params["post_slug"]) |> URI.decode() %><% :post_updated -> %><%= dgettext("activity", "The post %{post} was updated by %{profile}.",
+<%= Routes.page_url(Mobilizon.Web.Endpoint, :post, @activity.subject_params["post_slug"]) |> URI.decode() %><% :post_updated -> %><%= dgettext("activity", "The post %{post} was updated by %{profile}.",
%{
profile: Mobilizon.Actors.Actor.display_name_and_username(@activity.author),
post: @activity.subject_params["post_title"]
}
) %>
-<%= page_url(Mobilizon.Web.Endpoint, :post, @activity.subject_params["post_slug"]) |> URI.decode() %><% :post_deleted -> %><%= dgettext("activity", "The post %{post} was deleted by %{profile}.",
+<%= Routes.page_url(Mobilizon.Web.Endpoint, :post, @activity.subject_params["post_slug"]) |> URI.decode() %><% :post_deleted -> %><%= dgettext("activity", "The post %{post} was deleted by %{profile}.",
%{
profile: Mobilizon.Actors.Actor.display_name_and_username(@activity.author),
post: @activity.subject_params["post_title"]
diff --git a/lib/web/templates/email/activity/_resource_activity_item.html.eex b/lib/web/templates/email/activity/_resource_activity_item.html.eex
index ad5b42d5e..6a894c83a 100644
--- a/lib/web/templates/email/activity/_resource_activity_item.html.eex
+++ b/lib/web/templates/email/activity/_resource_activity_item.html.eex
@@ -6,7 +6,7 @@
%{
profile: "#{Mobilizon.Actors.Actor.display_name_and_username(@activity.author)}",
resource: "#{Mobilizon.Actors.Actor.display_name_and_username(@activity.author)}",
resource: "#{Mobilizon.Actors.Actor.display_name_and_username(@activity.author)}",
resource: "#{Mobilizon.Actors.Actor.display_name_and_username(@activity.author)}",
resource: "#{Mobilizon.Actors.Actor.display_name_and_username(@activity.author)}",
resource: "#{Mobilizon.Actors.Actor.display_name_and_username(@activity.author)}",
resource: "
-<%= page_url(Mobilizon.Web.Endpoint, :resource, @activity.subject_params["resource_uuid"]) |> URI.decode() %><% else %><%= dgettext("activity", "%{profile} created the resource %{resource}.",
+<%= Routes.page_url(Mobilizon.Web.Endpoint, :resource, @activity.subject_params["resource_uuid"]) |> URI.decode() %><% else %><%= dgettext("activity", "%{profile} created the resource %{resource}.",
%{
profile: Mobilizon.Actors.Actor.display_name_and_username(@activity.author),
resource: @activity.subject_params["resource_title"]
}
) %>
-<%= page_url(Mobilizon.Web.Endpoint, :resource, @activity.subject_params["resource_uuid"]) |> URI.decode() %><% end %><% :resource_renamed -> %><%= if @activity.subject_params["is_folder"] do %><%= dgettext("activity", "%{profile} renamed the folder from %{old_resource_title} to %{resource}.",
+<%= Routes.page_url(Mobilizon.Web.Endpoint, :resource, @activity.subject_params["resource_uuid"]) |> URI.decode() %><% end %><% :resource_renamed -> %><%= if @activity.subject_params["is_folder"] do %><%= dgettext("activity", "%{profile} renamed the folder from %{old_resource_title} to %{resource}.",
%{
profile: Mobilizon.Actors.Actor.display_name_and_username(@activity.author),
resource: @activity.subject_params["resource_title"],
old_resource_title: @activity.subject_params["old_resource_title"]
}
) %>
-<%= page_url(Mobilizon.Web.Endpoint, :resource, @activity.subject_params["resource_uuid"]) |> URI.decode() %><% else %><%= dgettext("activity", "%{profile} renamed the resource from %{old_resource_title} to %{resource}.",
+<%= Routes.page_url(Mobilizon.Web.Endpoint, :resource, @activity.subject_params["resource_uuid"]) |> URI.decode() %><% else %><%= dgettext("activity", "%{profile} renamed the resource from %{old_resource_title} to %{resource}.",
%{
profile: Mobilizon.Actors.Actor.display_name_and_username(@activity.author),
resource: @activity.subject_params["resource_title"],
old_resource_title: @activity.subject_params["old_resource_title"]
}
) %>
-<%= page_url(Mobilizon.Web.Endpoint, :resource, @activity.subject_params["resource_uuid"]) |> URI.decode() %><% end %><% :resource_moved -> %><%= if @activity.subject_params["is_folder"] do %><%= dgettext("activity", "%{profile} moved the folder %{resource}.",
+<%= Routes.page_url(Mobilizon.Web.Endpoint, :resource, @activity.subject_params["resource_uuid"]) |> URI.decode() %><% end %><% :resource_moved -> %><%= if @activity.subject_params["is_folder"] do %><%= dgettext("activity", "%{profile} moved the folder %{resource}.",
%{
profile: Mobilizon.Actors.Actor.display_name_and_username(@activity.author),
resource: @activity.subject_params["resource_title"]
}
) %>
-<%= page_url(Mobilizon.Web.Endpoint, :resource, @activity.subject_params["resource_uuid"]) |> URI.decode() %><% else %><%= dgettext("activity", "%{profile} moved the resource %{resource}.",
+<%= Routes.page_url(Mobilizon.Web.Endpoint, :resource, @activity.subject_params["resource_uuid"]) |> URI.decode() %><% else %><%= dgettext("activity", "%{profile} moved the resource %{resource}.",
%{
profile: Mobilizon.Actors.Actor.display_name_and_username(@activity.author),
resource: @activity.subject_params["resource_title"]
}
) %>
-<%= page_url(Mobilizon.Web.Endpoint, :resource, @activity.subject_params["resource_uuid"]) |> URI.decode() %><% end %><% :resource_deleted -> %><%= if @activity.subject_params["is_folder"] do %><%= dgettext("activity", "%{profile} deleted the folder %{resource}.",
+<%= Routes.page_url(Mobilizon.Web.Endpoint, :resource, @activity.subject_params["resource_uuid"]) |> URI.decode() %><% end %><% :resource_deleted -> %><%= if @activity.subject_params["is_folder"] do %><%= dgettext("activity", "%{profile} deleted the folder %{resource}.",
%{
profile: Mobilizon.Actors.Actor.display_name_and_username(@activity.author),
resource: @activity.subject_params["resource_title"]
diff --git a/lib/web/templates/email/anonymous_participation_confirmation.html.eex b/lib/web/templates/email/anonymous_participation_confirmation.html.eex
index cbb6ce477..8cbb1a8b7 100644
--- a/lib/web/templates/email/anonymous_participation_confirmation.html.eex
+++ b/lib/web/templates/email/anonymous_participation_confirmation.html.eex
@@ -47,7 +47,7 @@
-
+ |
<%= gettext "Confirm my e-mail address" %>
|
diff --git a/lib/web/templates/email/anonymous_participation_confirmation.text.eex b/lib/web/templates/email/anonymous_participation_confirmation.text.eex
index b5e46e1da..ae4943c15 100644
--- a/lib/web/templates/email/anonymous_participation_confirmation.text.eex
+++ b/lib/web/templates/email/anonymous_participation_confirmation.text.eex
@@ -2,5 +2,5 @@
==
<%= gettext "Hi there! You just registered to join this event: « %{title} ». Please confirm the e-mail address you provided:", title: @participant.event.title %>
<%= gettext "If you didn't trigger this email, you may safely ignore it." %>
-<%= page_url(Mobilizon.Web.Endpoint, :participation_email_confirmation, @participant.metadata.confirmation_token) %>
+<%= Routes.page_url(Mobilizon.Web.Endpoint, :participation_email_confirmation, @participant.metadata.confirmation_token) %>
<%= ngettext "Would you wish to cancel your attendance, visit the event page through the link above and click the « Attending » button.", "Would you wish to cancel your attendance to one or several events, visit the event pages through the links above and click the « Attending » button.", 1 %>
diff --git a/lib/web/templates/email/before_event_notification.html.eex b/lib/web/templates/email/before_event_notification.html.eex
index 213b038c2..ab4f54c83 100644
--- a/lib/web/templates/email/before_event_notification.html.eex
+++ b/lib/web/templates/email/before_event_notification.html.eex
@@ -47,7 +47,7 @@
-
+ |
<%= gettext "Go to event page" %>
|
diff --git a/lib/web/templates/email/before_event_notification.text.eex b/lib/web/templates/email/before_event_notification.text.eex
index f1f561c32..cb72a7cce 100644
--- a/lib/web/templates/email/before_event_notification.text.eex
+++ b/lib/web/templates/email/before_event_notification.text.eex
@@ -2,5 +2,5 @@
==
<%= gettext "Get ready for %{title}", title: @participant.event.title %>
<%= gettext "Go to event page" %>
-<%= gettext "View the event on: %{link}", link: page_url(Mobilizon.Web.Endpoint, :event, @participant.event.uuid) %>
+<%= gettext "View the event on: %{link}", link: Routes.page_url(Mobilizon.Web.Endpoint, :event, @participant.event.uuid) %>
<%= gettext "If you wish to cancel your attendance, visit the event page through the link above and click the « Attending » button." %>
diff --git a/lib/web/templates/email/email_anonymous_activity.html.eex b/lib/web/templates/email/email_anonymous_activity.html.eex
index 66b95852a..52f624712 100644
--- a/lib/web/templates/email/email_anonymous_activity.html.eex
+++ b/lib/web/templates/email/email_anonymous_activity.html.eex
@@ -42,7 +42,7 @@
%{
profile: "#{Mobilizon.Actors.Actor.display_name_and_username(@activity.author)}",
event: "
- " target="_blank" style="font-size: 20px; font-family: Helvetica, Arial, sans-serif; color: #ffffff; text-decoration: none; padding: 15px 25px; border-radius: 2px; border: 1px solid #3C376E; display: inline-block;">
+ | " target="_blank" style="font-size: 20px; font-family: Helvetica, Arial, sans-serif; color: #ffffff; text-decoration: none; padding: 15px 25px; border-radius: 2px; border: 1px solid #3C376E; display: inline-block;">
<%= gettext "Visit event page" %>
|
diff --git a/lib/web/templates/email/email_anonymous_activity.text.eex b/lib/web/templates/email/email_anonymous_activity.text.eex
index 0df709a1c..c19b43834 100644
--- a/lib/web/templates/email/email_anonymous_activity.text.eex
+++ b/lib/web/templates/email/email_anonymous_activity.text.eex
@@ -8,4 +8,4 @@
event: @activity.subject_params["event_title"]
}
) %>
-<%= page_url(Mobilizon.Web.Endpoint, :event, @activity.subject_params["event_uuid"]) |> URI.decode() %>
\ No newline at end of file
+<%= Routes.page_url(Mobilizon.Web.Endpoint, :event, @activity.subject_params["event_uuid"]) |> URI.decode() %>
\ No newline at end of file
diff --git a/lib/web/templates/email/email_changed_new.html.eex b/lib/web/templates/email/email_changed_new.html.eex
index f3eb0267b..75fd87b81 100644
--- a/lib/web/templates/email/email_changed_new.html.eex
+++ b/lib/web/templates/email/email_changed_new.html.eex
@@ -47,7 +47,7 @@
-
+
<%= gettext "Verify your email address" %>
|
diff --git a/lib/web/templates/email/email_changed_new.text.eex b/lib/web/templates/email/email_changed_new.text.eex
index 65d5c4fd6..5ae1b016e 100644
--- a/lib/web/templates/email/email_changed_new.text.eex
+++ b/lib/web/templates/email/email_changed_new.text.eex
@@ -1,5 +1,5 @@
<%= gettext "Confirm new email" %>
==
<%= gettext "Hi there! It seems like you wanted to change the email address linked to your account on %{instance}. If you still wish to do so, please click the button below to confirm the change. You will then be able to log in to %{instance} with this new email address.", %{instance: @instance_name} %>
-<%= page_url(Mobilizon.Web.Endpoint, :user_email_validation, @token) %>
+<%= Routes.page_url(Mobilizon.Web.Endpoint, :user_email_validation, @token) %>
<%= gettext "If you didn't trigger the change yourself, please ignore this message." %>
diff --git a/lib/web/templates/email/email_direct_activity.html.eex b/lib/web/templates/email/email_direct_activity.html.eex
index be3d997b5..23115ea33 100644
--- a/lib/web/templates/email/email_direct_activity.html.eex
+++ b/lib/web/templates/email/email_direct_activity.html.eex
@@ -64,7 +64,7 @@
<%= if hd(group_activities).group.avatar do %>
-
+
|
@@ -73,7 +73,7 @@
-
+
<%= hd(group_activities).group.name || "@#{Mobilizon.Actors.Actor.preferred_username_and_domain(hd(group_activities).group)}" %>
|
@@ -81,7 +81,7 @@
<%= if hd(group_activities).group.name do %>
-
+
@<%= Mobilizon.Actors.Actor.preferred_username_and_domain(hd(group_activities).group) %>
|
@@ -131,7 +131,7 @@
-
+
<%= dngettext "activity", "View one more activity", "View %{count} more activities", length(group_activities) - 5, %{count: length(group_activities) - 5} %>
|
diff --git a/lib/web/templates/email/email_direct_activity.text.eex b/lib/web/templates/email/email_direct_activity.text.eex
index 44572d5a2..e795ec0ab 100644
--- a/lib/web/templates/email/email_direct_activity.text.eex
+++ b/lib/web/templates/email/email_direct_activity.text.eex
@@ -21,7 +21,7 @@
<% end %>
<%= if length(group_activities) > 5 do %>
<%= dngettext "activity", "View one more activity", "View %{count} more activities", length(group_activities) - 5, %{count: length(group_activities) - 5} %>
-<%= page_url(Mobilizon.Web.Endpoint, :actor, Mobilizon.Actors.Actor.preferred_username_and_domain(hd(group_activities).group)) |> URI.decode() %>/timeline
+<%= Routes.page_url(Mobilizon.Web.Endpoint, :actor, Mobilizon.Actors.Actor.preferred_username_and_domain(hd(group_activities).group)) |> URI.decode() %>/timeline
<% end %>
<% end %>
<%= dgettext("activity", "Don't want to receive activity notifications? You may change frequency or disable them in your settings.") %>
diff --git a/lib/web/templates/email/event_participation_approved.html.eex b/lib/web/templates/email/event_participation_approved.html.eex
index 7ec2ed034..1a1f2f80a 100644
--- a/lib/web/templates/email/event_participation_approved.html.eex
+++ b/lib/web/templates/email/event_participation_approved.html.eex
@@ -54,7 +54,7 @@
-
+ |
<%= gettext "Visit event page" %>
|
diff --git a/lib/web/templates/email/event_participation_approved.text.eex b/lib/web/templates/email/event_participation_approved.text.eex
index 906cfbfa2..5b71ca3f1 100644
--- a/lib/web/templates/email/event_participation_approved.text.eex
+++ b/lib/web/templates/email/event_participation_approved.text.eex
@@ -6,6 +6,6 @@
<%= gettext "Good news: one of the event organizers just approved your request. Update your calendar, because you're on the guest list now!" %>
-<%= page_url(Mobilizon.Web.Endpoint, :event, @event.uuid) %>
+<%= Routes.page_url(Mobilizon.Web.Endpoint, :event, @event.uuid) %>
<%= gettext "Would you wish to update or cancel your attendance, simply access the event page through the link above and click on the Attending button." %>
diff --git a/lib/web/templates/email/event_participation_confirmed.html.eex b/lib/web/templates/email/event_participation_confirmed.html.eex
index 3dbbab91c..d38056a4d 100644
--- a/lib/web/templates/email/event_participation_confirmed.html.eex
+++ b/lib/web/templates/email/event_participation_confirmed.html.eex
@@ -54,7 +54,7 @@
-
+ |
<%= gettext "Visit event page" %>
|
diff --git a/lib/web/templates/email/event_participation_confirmed.text.eex b/lib/web/templates/email/event_participation_confirmed.text.eex
index 869df0b4d..56f9edad8 100644
--- a/lib/web/templates/email/event_participation_confirmed.text.eex
+++ b/lib/web/templates/email/event_participation_confirmed.text.eex
@@ -2,5 +2,5 @@
==
<%= gettext "You recently requested to attend %{title}.", title: @event.title %>
<%= gettext "You have confirmed your participation. Update your calendar, because you're on the guest list now!" %>
-<%= page_url(Mobilizon.Web.Endpoint, :event, @event.uuid) %>
+<%= Routes.page_url(Mobilizon.Web.Endpoint, :event, @event.uuid) %>
<%= gettext "Would you wish to update or cancel your attendance, simply access the event page through the link above and click on the Attending button." %>
diff --git a/lib/web/templates/email/event_updated.html.eex b/lib/web/templates/email/event_updated.html.eex
index f6539b2e8..61b1318fd 100644
--- a/lib/web/templates/email/event_updated.html.eex
+++ b/lib/web/templates/email/event_updated.html.eex
@@ -117,7 +117,7 @@
-
+ |
<%= gettext "Visit the updated event page" %>
|
diff --git a/lib/web/templates/email/event_updated.text.eex b/lib/web/templates/email/event_updated.text.eex
index d65225f13..676cbae61 100644
--- a/lib/web/templates/email/event_updated.text.eex
+++ b/lib/web/templates/email/event_updated.text.eex
@@ -20,5 +20,5 @@
<%= if MapSet.member?(@changes, :ends_on) && !is_nil(@event.ends_on) do %>
<%= gettext "End %{ends_on}", ends_on: @event.ends_on |> datetime_tz_convert(@timezone) |> datetime_to_string(@locale) %>
<% end %>
-<%= gettext "Visit the updated event page: %{link}", link: page_url(Mobilizon.Web.Endpoint, :event, @event.uuid) %>
+<%= gettext "Visit the updated event page: %{link}", link: Routes.page_url(Mobilizon.Web.Endpoint, :event, @event.uuid) %>
<%= ngettext "Would you wish to cancel your attendance, visit the event page through the link above and click the « Attending » button.", "Would you wish to cancel your attendance to one or several events, visit the event pages through the links above and click the « Attending » button.", 1 %>
diff --git a/lib/web/templates/email/group_invite.html.eex b/lib/web/templates/email/group_invite.html.eex
index 6c134ba02..129478107 100644
--- a/lib/web/templates/email/group_invite.html.eex
+++ b/lib/web/templates/email/group_invite.html.eex
@@ -55,7 +55,7 @@
-
+
<%= gettext "See my groups" %>
|
diff --git a/lib/web/templates/email/group_invite.text.eex b/lib/web/templates/email/group_invite.text.eex
index 70d1c983f..72b03aeb5 100644
--- a/lib/web/templates/email/group_invite.text.eex
+++ b/lib/web/templates/email/group_invite.text.eex
@@ -3,4 +3,4 @@
<%= gettext "%{inviter} just invited you to join their group %{group}", group: @group.name, inviter: @inviter.name %>
<%= @group.url %>
<%= gettext "To accept this invitation, head over to your groups." %>
-<%= page_url(Mobilizon.Web.Endpoint, :my_groups) %>
+<%= Routes.page_url(Mobilizon.Web.Endpoint, :my_groups) %>
diff --git a/lib/web/templates/email/notification_each_week.html.eex b/lib/web/templates/email/notification_each_week.html.eex
index c27c23018..270785fa9 100644
--- a/lib/web/templates/email/notification_each_week.html.eex
+++ b/lib/web/templates/email/notification_each_week.html.eex
@@ -48,7 +48,7 @@
<%= participation.event.begins_on |> datetime_tz_convert(@timezone) |> datetime_to_string(@locale) %>
-
+
<%= participation.event.title %>
@@ -58,7 +58,7 @@
<%= @participation.event.begins_on |> datetime_tz_convert(@timezone) |> datetime_to_string(@locale) %>
-
+
<%= @participation.event.title %>
<% end %>
diff --git a/lib/web/templates/email/notification_each_week.text.eex b/lib/web/templates/email/notification_each_week.text.eex
index e0c2c3cbe..f0f2fc303 100644
--- a/lib/web/templates/email/notification_each_week.text.eex
+++ b/lib/web/templates/email/notification_each_week.text.eex
@@ -3,9 +3,9 @@
<%= ngettext "You have one event this week:", "You have %{total} events this week:", @total, total: @total %>
<%= if @total > 1 do %>
<%= for participation <- @participations do %>
- - <%= participation.event.begins_on |> datetime_tz_convert(@timezone) |> datetime_to_string(@locale) %> - <%= participation.event.title %> <%= page_url(Mobilizon.Web.Endpoint, :event, participation.event.uuid) %>
+ - <%= participation.event.begins_on |> datetime_tz_convert(@timezone) |> datetime_to_string(@locale) %> - <%= participation.event.title %> <%= Routes.page_url(Mobilizon.Web.Endpoint, :event, participation.event.uuid) %>
<% end %>
<% else %>
- <%= @participation.event.begins_on |> datetime_tz_convert(@timezone) |> datetime_to_string(@locale) %> - <%= @participation.event.title %> <%= page_url(Mobilizon.Web.Endpoint, :event, @participation.event.uuid) %>
+ <%= @participation.event.begins_on |> datetime_tz_convert(@timezone) |> datetime_to_string(@locale) %> - <%= @participation.event.title %> <%= Routes.page_url(Mobilizon.Web.Endpoint, :event, @participation.event.uuid) %>
<% end %>
<%= ngettext "Would you wish to cancel your attendance, visit the event page through the link above and click the « Attending » button.", "Would you wish to cancel your attendance to one or several events, visit the event pages through the links above and click the « Attending » button.", @total %>
diff --git a/lib/web/templates/email/on_day_notification.html.eex b/lib/web/templates/email/on_day_notification.html.eex
index dd31dce02..7e6b9441d 100644
--- a/lib/web/templates/email/on_day_notification.html.eex
+++ b/lib/web/templates/email/on_day_notification.html.eex
@@ -48,7 +48,7 @@
<%= participation.event.begins_on |> DateTime.shift_zone!(@timezone) |> datetime_to_time_string(@locale) %>
-
+
<%= participation.event.title %>
@@ -58,7 +58,7 @@
<%= @participation.event.begins_on |> DateTime.shift_zone!(@timezone) |> datetime_to_time_string(@locale) %>
-
+
<%= @participation.event.title %>
<% end %>
diff --git a/lib/web/templates/email/on_day_notification.text.eex b/lib/web/templates/email/on_day_notification.text.eex
index 6d1b0f3e0..b902f2646 100644
--- a/lib/web/templates/email/on_day_notification.text.eex
+++ b/lib/web/templates/email/on_day_notification.text.eex
@@ -5,10 +5,10 @@
<%= if @total > 1 do %>
<%= for participation <- @participations do %>
- - <%= participation.event.begins_on |> DateTime.shift_zone!(@timezone) |> datetime_to_time_string(@locale) %> - <%= participation.event.title %> <%= page_url(Mobilizon.Web.Endpoint, :event, participation.event.uuid) %>
+ - <%= participation.event.begins_on |> DateTime.shift_zone!(@timezone) |> datetime_to_time_string(@locale) %> - <%= participation.event.title %> <%= Routes.page_url(Mobilizon.Web.Endpoint, :event, participation.event.uuid) %>
<% end %>
<% else %>
- <%= DateTime.shift_zone!(@participation.event.begins_on, @timezone) |> datetime_to_time_string(@locale) %> - <%= @participation.event.title %> <%= page_url(Mobilizon.Web.Endpoint, :event, @participation.event.uuid) %>
+ <%= DateTime.shift_zone!(@participation.event.begins_on, @timezone) |> datetime_to_time_string(@locale) %> - <%= @participation.event.title %> <%= Routes.page_url(Mobilizon.Web.Endpoint, :event, @participation.event.uuid) %>
<% end %>
<%= ngettext "Would you wish to cancel your attendance, visit the event page through the link above and click the « Attending » button.", "Would you wish to cancel your attendance to one or several events, visit the event pages through the links above and click the « Attending » button.", @total %>
diff --git a/lib/web/templates/email/pending_participation_notification.html.eex b/lib/web/templates/email/pending_participation_notification.html.eex
index 56c4c5ea7..79aeb85ab 100644
--- a/lib/web/templates/email/pending_participation_notification.html.eex
+++ b/lib/web/templates/email/pending_participation_notification.html.eex
@@ -47,7 +47,7 @@
- " target="_blank" style="font-size: 20px; font-family: Helvetica, Arial, sans-serif; color: #ffffff; text-decoration: none; color: #ffffff; text-decoration: none; padding: 15px 25px; border-radius: 2px; border: 1px solid #3C376E; display: inline-block;">
+ " target="_blank" style="font-size: 20px; font-family: Helvetica, Arial, sans-serif; color: #ffffff; text-decoration: none; color: #ffffff; text-decoration: none; padding: 15px 25px; border-radius: 2px; border: 1px solid #3C376E; display: inline-block;">
<%= gettext "Manage pending requests" %>
|
diff --git a/lib/web/templates/email/pending_participation_notification.text.eex b/lib/web/templates/email/pending_participation_notification.text.eex
index a1de9a579..6a0f31c77 100644
--- a/lib/web/templates/email/pending_participation_notification.text.eex
+++ b/lib/web/templates/email/pending_participation_notification.text.eex
@@ -3,6 +3,6 @@
<%= ngettext "You have one pending attendance request to process:", "You have %{number_participation_requests} attendance requests to process:", @total, number_participation_requests: @total %>
-<%= gettext "Manage pending requests" %> <%= page_url(Mobilizon.Web.Endpoint, :event, @event.uuid) <> "/participations" %>
+<%= gettext "Manage pending requests" %> <%= Routes.page_url(Mobilizon.Web.Endpoint, :event, @event.uuid) <> "/participations" %>
<%= gettext "You are receiving this email because you chose to get notifications for pending attendance requests to your events. You can disable or change your notification settings in your user account settings under « Notifications »." %>s
diff --git a/lib/web/templates/email/report.html.eex b/lib/web/templates/email/report.html.eex
index 1d1775ef5..b25939438 100644
--- a/lib/web/templates/email/report.html.eex
+++ b/lib/web/templates/email/report.html.eex
@@ -122,7 +122,7 @@
-
+ |
<%= gettext "View report" %>
|
diff --git a/lib/web/templates/email/report.text.eex b/lib/web/templates/email/report.text.eex
index b5521c4db..7cd119b57 100644
--- a/lib/web/templates/email/report.text.eex
+++ b/lib/web/templates/email/report.text.eex
@@ -21,4 +21,4 @@
<%= gettext "Reason" %>
<%= @report.content %>
<% end %>
-<%= gettext "View report:" %> <%= page_url(Mobilizon.Web.Endpoint, :moderation_report, @report.id) %>
+<%= gettext "View report:" %> <%= Routes.page_url(Mobilizon.Web.Endpoint, :moderation_report, @report.id) %>
diff --git a/lib/web/upload/filter/analyze_metadata.ex b/lib/web/upload/filter/analyze_metadata.ex
index ea12fb487..72fb44203 100644
--- a/lib/web/upload/filter/analyze_metadata.ex
+++ b/lib/web/upload/filter/analyze_metadata.ex
@@ -13,7 +13,7 @@ defmodule Mobilizon.Web.Upload.Filter.AnalyzeMetadata do
@behaviour Mobilizon.Web.Upload.Filter
@spec filter(Upload.t()) ::
- {:ok, :filtered, Upload.t()} | {:ok, :noop} | {:error, String.t()}
+ {:ok, :filtered, Upload.t()} | {:ok, :noop}
def filter(%Upload{tempfile: file, content_type: "image" <> _} = upload) do
image =
file
diff --git a/lib/web/upload/filter/anonymize_filename.ex b/lib/web/upload/filter/anonymize_filename.ex
index 093479448..108635995 100644
--- a/lib/web/upload/filter/anonymize_filename.ex
+++ b/lib/web/upload/filter/anonymize_filename.ex
@@ -14,19 +14,27 @@ defmodule Mobilizon.Web.Upload.Filter.AnonymizeFilename do
alias Mobilizon.Config
alias Mobilizon.Web.Upload
+ alias Mobilizon.Web.Upload.Filter
+ import Mobilizon.Service.Guards, only: [is_valid_string: 1]
+ @impl Filter
+ @spec filter(any) :: {:ok, :filtered, Upload.t()} | {:ok, :noop}
def filter(%Upload{name: name} = upload) do
extension = List.last(String.split(name, "."))
- name = predefined_name(extension) || random(extension)
+ name = predefined_name(extension)
+ name = if is_nil(name), do: random(extension), else: name
{:ok, :filtered, %Upload{upload | name: name}}
end
+ @impl Filter
def filter(_), do: {:ok, :noop}
@spec predefined_name(String.t()) :: String.t() | nil
defp predefined_name(extension) do
- with name when not is_nil(name) <- Config.get([__MODULE__, :text]),
- do: String.replace(name, "{extension}", extension)
+ case Config.get([__MODULE__, :text]) do
+ name when is_valid_string(name) -> String.replace(name, "{extension}", extension)
+ _ -> nil
+ end
end
defp random(extension) do
diff --git a/lib/web/upload/filter/blurhash.ex b/lib/web/upload/filter/blurhash.ex
index b2454dea6..a05c790a9 100644
--- a/lib/web/upload/filter/blurhash.ex
+++ b/lib/web/upload/filter/blurhash.ex
@@ -8,7 +8,7 @@ defmodule Mobilizon.Web.Upload.Filter.BlurHash do
@behaviour Mobilizon.Web.Upload.Filter
@spec filter(Upload.t()) ::
- {:ok, :filtered, Upload.t()} | {:ok, :noop} | {:error, String.t()}
+ {:ok, :filtered, Upload.t()} | {:ok, :noop}
def filter(%Upload{tempfile: file, content_type: "image" <> _} = upload) do
{:ok, :filtered, %Upload{upload | blurhash: generate_blurhash(file)}}
rescue
diff --git a/lib/web/upload/filter/dedupe.ex b/lib/web/upload/filter/dedupe.ex
index 1e7731a5d..d6ca08860 100644
--- a/lib/web/upload/filter/dedupe.ex
+++ b/lib/web/upload/filter/dedupe.ex
@@ -10,6 +10,7 @@ defmodule Mobilizon.Web.Upload.Filter.Dedupe do
@behaviour Mobilizon.Web.Upload.Filter
alias Mobilizon.Web.Upload
+ @spec filter(Upload.t()) :: {:ok, :filtered, Upload.t()} | {:ok, :noop}
def filter(%Upload{name: name, tempfile: tempfile} = upload) do
extension = name |> String.split(".") |> List.last()
shasum = :crypto.hash(:sha256, File.read!(tempfile)) |> Base.encode16(case: :lower)
diff --git a/lib/web/upload/filter/exiftool.ex b/lib/web/upload/filter/exiftool.ex
index fd7391f27..4f5a1773c 100644
--- a/lib/web/upload/filter/exiftool.ex
+++ b/lib/web/upload/filter/exiftool.ex
@@ -11,7 +11,7 @@ defmodule Mobilizon.Web.Upload.Filter.Exiftool do
@behaviour Mobilizon.Web.Upload.Filter
- @spec filter(Upload.t()) :: {:ok, any()} | {:error, String.t()}
+ @spec filter(Upload.t()) :: {:ok, :filtered | :noop} | {:error, String.t()}
# webp is not compatible with exiftool at this time
def filter(%Upload{content_type: "image/webp"}), do: {:ok, :noop}
diff --git a/lib/web/upload/filter/filter.ex b/lib/web/upload/filter/filter.ex
index be85909a8..a2f63a9df 100644
--- a/lib/web/upload/filter/filter.ex
+++ b/lib/web/upload/filter/filter.ex
@@ -20,11 +20,10 @@ defmodule Mobilizon.Web.Upload.Filter do
{:ok, :filtered}
| {:ok, :noop}
| {:ok, :filtered, Mobilizon.Web.Upload.t()}
- | {:error, any()}
+ | {:error, String.t() | atom}
@spec filter([module()], Mobilizon.Web.Upload.t()) ::
- {:ok, Mobilizon.Web.Upload.t()} | {:error, any()}
-
+ {:ok, Mobilizon.Web.Upload.t()} | {:error, String.t() | atom}
def filter([], upload) do
{:ok, upload}
end
diff --git a/lib/web/upload/filter/mogrify.ex b/lib/web/upload/filter/mogrify.ex
index f10c49728..587fb5bdd 100644
--- a/lib/web/upload/filter/mogrify.ex
+++ b/lib/web/upload/filter/mogrify.ex
@@ -15,7 +15,7 @@ defmodule Mobilizon.Web.Upload.Filter.Mogrify do
@type conversion :: action :: String.t() | {action :: String.t(), opts :: String.t()}
@type conversions :: conversion() | [conversion()]
- @spec filter(Mobilizon.Web.Upload.t()) :: {:ok, :atom} | {:error, String.t()}
+ @spec filter(Mobilizon.Web.Upload.t()) :: {:ok, :filtered | :noop} | {:error, String.t()}
def filter(%Mobilizon.Web.Upload{tempfile: file, content_type: "image" <> _}) do
do_filter(file, Config.get!([__MODULE__, :args]))
{:ok, :filtered}
diff --git a/lib/web/upload/filter/optimize.ex b/lib/web/upload/filter/optimize.ex
index c19c9fa04..fc1f73f9e 100644
--- a/lib/web/upload/filter/optimize.ex
+++ b/lib/web/upload/filter/optimize.ex
@@ -6,6 +6,8 @@ defmodule Mobilizon.Web.Upload.Filter.Optimize do
@behaviour Mobilizon.Web.Upload.Filter
alias Mobilizon.Config
+ alias Mobilizon.Web.Upload
+ require Logger
@default_optimizers [
JpegOptim,
@@ -16,24 +18,24 @@ defmodule Mobilizon.Web.Upload.Filter.Optimize do
Cwebp
]
- def filter(%Mobilizon.Web.Upload{tempfile: file, content_type: "image" <> _}) do
+ @spec filter(Upload.t()) :: {:ok, :filtered | :noop} | {:error, :file_not_found}
+ def filter(%Upload{tempfile: file, content_type: "image" <> _}) do
optimizers = Config.get([__MODULE__, :optimizers], @default_optimizers)
case ExOptimizer.optimize(file, deps: optimizers) do
{:ok, _res} ->
{:ok, :filtered}
- {:error, err} ->
- require Logger
+ {:error, :file_not_found} ->
+ Logger.warn("Unable to optimize file #{file}. File was not found")
+ {:error, :file_not_found}
+ {:error, err} ->
Logger.warn(
"Unable to optimize file #{file}. The return from the process was #{inspect(err)}"
)
{:ok, :noop}
-
- err ->
- {:error, err}
end
end
diff --git a/lib/web/upload/filter/resize.ex b/lib/web/upload/filter/resize.ex
index 12c34ea7a..be6e9e6c7 100644
--- a/lib/web/upload/filter/resize.ex
+++ b/lib/web/upload/filter/resize.ex
@@ -11,6 +11,7 @@ defmodule Mobilizon.Web.Upload.Filter.Resize do
@maximum_width 1_920
@maximum_height 1_080
+ @spec filter(Upload.t()) :: {:ok, :filtered, Upload.t()} | {:ok, :noop}
def filter(
%Upload{
tempfile: file,
@@ -31,6 +32,7 @@ defmodule Mobilizon.Web.Upload.Filter.Resize do
def filter(_), do: {:ok, :noop}
+ @spec limit_sizes({non_neg_integer, non_neg_integer}) :: {non_neg_integer, non_neg_integer}
def limit_sizes({width, height}) when width > @maximum_width do
new_height = round(@maximum_width * height / width)
limit_sizes({@maximum_width, new_height})
@@ -43,5 +45,6 @@ defmodule Mobilizon.Web.Upload.Filter.Resize do
def limit_sizes({width, height}), do: {width, height}
+ @spec string({non_neg_integer, non_neg_integer}) :: String.t()
defp string({width, height}), do: "#{width}x#{height}"
end
diff --git a/lib/web/upload/mime.ex b/lib/web/upload/mime.ex
index 8a570c12f..eb871d2f8 100644
--- a/lib/web/upload/mime.ex
+++ b/lib/web/upload/mime.ex
@@ -10,8 +10,9 @@ defmodule Mobilizon.Web.Upload.MIME do
@default "application/octet-stream"
@read_bytes 35
- @spec file_mime_type(String.t()) ::
+ @spec file_mime_type(String.t(), String.t()) ::
{:ok, content_type :: String.t(), filename :: String.t()} | {:error, any()} | :error
+ @spec file_mime_type(String.t()) :: {:ok, String.t()} | {:error, any()} | :error
def file_mime_type(path, filename) do
with {:ok, content_type} <- file_mime_type(path),
filename when is_binary(filename) <- fix_extension(filename, content_type) do
@@ -19,7 +20,6 @@ defmodule Mobilizon.Web.Upload.MIME do
end
end
- @spec file_mime_type(String.t()) :: {:ok, String.t()} | {:error, any()} | :error
def file_mime_type(filename) do
File.open(filename, [:read], fn f ->
check_mime_type(IO.binread(f, @read_bytes))
diff --git a/lib/web/upload/upload.ex b/lib/web/upload/upload.ex
index 16b2f389b..85e14af9c 100644
--- a/lib/web/upload/upload.ex
+++ b/lib/web/upload/upload.ex
@@ -53,6 +53,7 @@ defmodule Mobilizon.Web.Upload do
| {:size_limit, nil | non_neg_integer()}
| {:uploader, module()}
| {:filters, [module()]}
+ | {:allow_list_mime_types, boolean()}
@type t :: %__MODULE__{
id: String.t(),
@@ -65,33 +66,36 @@ defmodule Mobilizon.Web.Upload do
height: integer(),
blurhash: String.t()
}
- defstruct [:id, :name, :tempfile, :content_type, :path, :size, :width, :height, :blurhash]
+ defstruct [:id, :name, :url, :tempfile, :content_type, :path, :size, :width, :height, :blurhash]
- @spec store(source, options :: [option()]) :: {:ok, map()} | {:error, any()}
+ @typep internal_options :: %{
+ activity_type: String.t() | nil,
+ size_limit: integer(),
+ uploader: module(),
+ filters: [module()],
+ description: String.t(),
+ allow_list_mime_types: list(String.t()),
+ base_url: String.t()
+ }
+
+ @spec store(source, options :: [option()]) ::
+ {:ok, map()} | {:error, String.t()} | {:error, atom()}
def store(upload, opts \\ []) do
opts = get_opts(opts)
- with {:ok, upload} <- prepare_upload(upload, opts),
- %__MODULE__{} = upload <- %__MODULE__{
- upload
- | path: upload.path || "#{upload.id}/#{upload.name}"
- },
- {:ok, upload} <- Filter.filter(opts.filters, upload),
- {:ok, url_spec} <- Uploader.put_file(opts.uploader, upload) do
- {:ok,
- upload
- |> Map.put(:name, Map.get(opts, :description) || upload.name)
- |> Map.put(:url, url_from_spec(upload, opts.base_url, url_spec))}
- else
- {:error, error} ->
- Logger.error(
- "#{__MODULE__} store (using #{inspect(opts.uploader)}) failed: #{inspect(error)}"
- )
+ case prepare_upload(upload, opts) do
+ {:ok, upload} ->
+ upload
+ |> set_default_upload_path()
+ |> perform_filter_and_put_file(opts)
- {:error, error}
+ {:error, error} ->
+ error
end
end
+ @spec remove(String.t(), Keyword.t()) ::
+ {:ok, String.t()} | {:error, atom} | {:error, String.t()}
def remove(url, opts \\ []) do
with opts <- get_opts(opts),
%URI{path: "/media/" <> path, host: host} <- URI.parse(url),
@@ -106,10 +110,52 @@ defmodule Mobilizon.Web.Upload do
end
end
- def char_unescaped?(char) do
+ @spec char_unescaped?(byte()) :: boolean()
+ defp char_unescaped?(char) do
URI.char_unreserved?(char) or char == ?/
end
+ @spec set_default_upload_path(t) :: t
+ defp set_default_upload_path(%__MODULE__{} = upload) do
+ %__MODULE__{
+ upload
+ | path: upload.path || "#{upload.id}/#{upload.name}"
+ }
+ end
+
+ @spec perform_filter_and_put_file(t, map) ::
+ {:ok, t} | {:error, String.t()} | {:error, atom()}
+ defp perform_filter_and_put_file(%__MODULE__{} = upload, opts) do
+ case Filter.filter(opts.filters, upload) do
+ {:ok, upload} ->
+ perform_put_file(upload, opts)
+
+ {:error, error} ->
+ {:error, error}
+ end
+ end
+
+ @spec perform_put_file(t, map) :: {:ok, t} | {:error, atom()}
+ defp perform_put_file(%__MODULE__{} = upload, opts) do
+ case Uploader.put_file(opts.uploader, upload) do
+ {:ok, url_spec} ->
+ {:ok,
+ %__MODULE__{
+ upload
+ | name: Map.get(opts, :description) || upload.name,
+ url: url_from_spec(upload, opts.base_url, url_spec)
+ }}
+
+ {:error, error} ->
+ Logger.error(
+ "#{__MODULE__} store (using #{inspect(opts.uploader)}) failed: #{inspect(error)}"
+ )
+
+ {:error, error}
+ end
+ end
+
+ @spec get_opts(Keyword.t()) :: internal_options()
defp get_opts(opts) do
{size_limit, activity_type} =
case Keyword.get(opts, :type) do
@@ -144,6 +190,7 @@ defmodule Mobilizon.Web.Upload do
}
end
+ @spec prepare_upload(t(), internal_options()) :: {:ok, t()}
defp prepare_upload(%Plug.Upload{} = file, opts) do
with {:ok, size} <- check_file_size(file.path, opts.size_limit),
{:ok, content_type, name} <- MIME.file_mime_type(file.path, file.filename),
@@ -159,6 +206,7 @@ defmodule Mobilizon.Web.Upload do
end
end
+ @spec prepare_upload(%{body: String.t(), name: String.t()}, internal_options()) :: {:ok, t()}
defp prepare_upload(%{body: body, name: name} = _file, opts) do
with :ok <- check_binary_size(body, opts.size_limit),
tmp_path <- tempfile_for_image(body),
@@ -175,8 +223,10 @@ defmodule Mobilizon.Web.Upload do
end
end
+ @spec check_file_size(String.t(), non_neg_integer()) ::
+ {:ok, non_neg_integer()} | {:error, :file_too_large} | {:error, :file.posix()}
defp check_file_size(path, size_limit) when is_integer(size_limit) and size_limit > 0 do
- with {:ok, %{size: size}} <- File.stat(path),
+ with {:ok, %File.Stat{size: size}} <- File.stat(path),
true <- size <= size_limit do
{:ok, size}
else
@@ -185,8 +235,7 @@ defmodule Mobilizon.Web.Upload do
end
end
- defp check_file_size(_, _), do: :ok
-
+ @spec check_binary_size(String.t(), non_neg_integer()) :: :ok | {:error, :file_too_large}
defp check_binary_size(binary, size_limit)
when is_integer(size_limit) and size_limit > 0 and byte_size(binary) >= size_limit do
{:error, :file_too_large}
@@ -196,6 +245,7 @@ defmodule Mobilizon.Web.Upload do
# Creates a tempfile using the Plug.Upload Genserver which cleans them up
# automatically.
+ @spec tempfile_for_image(iodata) :: String.t()
defp tempfile_for_image(data) do
{:ok, tmp_path} = Plug.Upload.random_file("temp_files")
{:ok, tmp_file} = File.open(tmp_path, [:write, :raw, :binary])
@@ -204,6 +254,7 @@ defmodule Mobilizon.Web.Upload do
tmp_path
end
+ @spec url_from_spec(t, String.t(), {:file | :url, String.t()}) :: String.t()
defp url_from_spec(%__MODULE__{name: name}, base_url, {:file, path}) do
path =
URI.encode(path, &char_unescaped?/1) <>
diff --git a/lib/web/upload/uploader/local.ex b/lib/web/upload/uploader/local.ex
index 97ff58fa6..978cf59a2 100644
--- a/lib/web/upload/uploader/local.ex
+++ b/lib/web/upload/uploader/local.ex
@@ -19,10 +19,24 @@ defmodule Mobilizon.Web.Upload.Uploader.Local do
end
@impl true
+ @spec put_file(Upload.t()) ::
+ :ok | {:ok, {:file, String.t()}} | {:error, :tempfile_no_longer_exists}
def put_file(%Upload{path: path, tempfile: tempfile}) do
{path, file} = local_path(path)
result_file = Path.join(path, file)
+ if File.exists?(result_file) do
+ # If the resulting file already exists, it's because of the Dedupe filter
+ :ok
+ else
+ if File.exists?(tempfile) do
+ File.cp!(tempfile, result_file)
+ {:ok, {:file, result_file}}
+ else
+ {:error, :tempfile_no_longer_exists}
+ end
+ end
+
with {:result_exists, false} <- {:result_exists, File.exists?(result_file)},
{:temp_file_exists, true} <- {:temp_file_exists, File.exists?(tempfile)} do
File.cp!(tempfile, result_file)
@@ -37,28 +51,54 @@ defmodule Mobilizon.Web.Upload.Uploader.Local do
end
@impl true
+ @spec remove_file(String.t()) ::
+ {:ok, {:file, String.t()}}
+ | {:error, :folder_not_empty}
+ | {:error, :enofile}
+ | {:error, File.posix()}
def remove_file(path) do
- with {path, file} <- local_path(path),
- full_path <- Path.join(path, file),
- true <- File.exists?(full_path),
- :ok <- File.rm(full_path),
- :ok <- remove_folder(path) do
- {:ok, path}
+ {path, file} = local_path(path)
+ full_path = Path.join(path, file)
+
+ if File.exists?(full_path) do
+ do_remove_file(path, full_path)
else
- false -> {:error, "File #{path} doesn't exist"}
+ {:error, :enofile}
end
end
+ @spec do_remove_file(String.t(), String.t()) ::
+ {:ok, {:file, String.t()}}
+ | {:error, :folder_not_empty}
+ | {:error, File.posix()}
+ defp do_remove_file(path, full_path) do
+ case File.rm(full_path) do
+ :ok ->
+ case remove_folder(path) do
+ :ok ->
+ {:ok, {:file, path}}
+
+ {:error, err} ->
+ {:error, err}
+ end
+
+ {:error, err} ->
+ {:error, err}
+ end
+ end
+
+ @spec remove_folder(String.t()) :: :ok | {:error, :folder_not_empty} | {:error, File.posix()}
defp remove_folder(path) do
with {:subfolder, true} <- {:subfolder, path != upload_path()},
{:empty_folder, {:ok, [] = _files}} <- {:empty_folder, File.ls(path)} do
File.rmdir(path)
else
{:subfolder, _} -> :ok
- {:empty_folder, _} -> {:error, "Error: Folder is not empty"}
+ {:empty_folder, _} -> {:error, :folder_not_empty}
end
end
+ @spec local_path(String.t()) :: {String.t(), String.t()}
defp local_path(path) do
case Enum.reverse(String.split(path, "/", trim: true)) do
[file] ->
@@ -71,6 +111,7 @@ defmodule Mobilizon.Web.Upload.Uploader.Local do
end
end
+ @spec upload_path :: String.t()
def upload_path do
Config.get!([__MODULE__, :uploads])
end
diff --git a/lib/web/upload/uploader/uploader.ex b/lib/web/upload/uploader/uploader.ex
index 57c9de994..f5372a36c 100644
--- a/lib/web/upload/uploader/uploader.ex
+++ b/lib/web/upload/uploader/uploader.ex
@@ -33,9 +33,9 @@ defmodule Mobilizon.Web.Upload.Uploader do
"""
@type file_spec :: {:file | :url, String.t()}
@callback put_file(Mobilizon.Web.Upload.t()) ::
- :ok | {:ok, file_spec()} | {:error, String.t()} | :wait_callback
+ :ok | {:ok, file_spec()} | {:error, atom()} | :wait_callback
- @callback remove_file(file_spec()) :: :ok | {:ok, file_spec()} | {:error, String.t()}
+ @callback remove_file(file_spec()) :: :ok | {:ok, file_spec()} | {:error, atom()}
@callback http_callback(Plug.Conn.t(), map()) ::
{:ok, Plug.Conn.t()}
@@ -43,7 +43,7 @@ defmodule Mobilizon.Web.Upload.Uploader do
| {:error, Plug.Conn.t(), String.t()}
@optional_callbacks http_callback: 2
- @spec put_file(module(), Mobilizon.Web.Upload.t()) :: {:ok, file_spec()} | {:error, String.t()}
+ @spec put_file(module(), Mobilizon.Web.Upload.t()) :: {:ok, file_spec()} | {:error, atom()}
def put_file(uploader, upload) do
case uploader.put_file(upload) do
:ok -> {:ok, {:file, upload.path}}
@@ -53,6 +53,7 @@ defmodule Mobilizon.Web.Upload.Uploader do
end
end
+ @spec remove_file(module(), String.t()) :: {:ok, String.t()} | {:error, atom()}
def remove_file(uploader, path) do
uploader.remove_file(path)
end
diff --git a/lib/web/views/activity_pub/actor_view.ex b/lib/web/views/activity_pub/actor_view.ex
index 0e61e75a6..eba9dd8a3 100644
--- a/lib/web/views/activity_pub/actor_view.ex
+++ b/lib/web/views/activity_pub/actor_view.ex
@@ -70,7 +70,18 @@ defmodule Mobilizon.Web.ActivityPub.ActorView do
}
end
- @spec fetch_collection(atom(), Actor.t(), integer()) :: Page.t()
+ @type collection ::
+ :following
+ | :followers
+ | :members
+ | :resources
+ | :discussions
+ | :posts
+ | :events
+ | :todos
+ | :outbox
+
+ @spec fetch_collection(collection(), Actor.t(), integer()) :: Page.t()
defp fetch_collection(:following, actor, page) do
Actors.build_followings_for_actor(actor, page)
end
@@ -103,7 +114,6 @@ defmodule Mobilizon.Web.ActivityPub.ActorView do
Todos.get_todo_lists_for_group(actor, page)
end
- @spec fetch_collection(atom(), Actor.t(), integer()) :: %{total: integer(), elements: Enum.t()}
defp fetch_collection(:outbox, actor, page) do
ActivityPub.fetch_public_activities_for_actor(actor, page)
end
@@ -179,8 +189,7 @@ defmodule Mobilizon.Web.ActivityPub.ActorView do
def item(%Event{} = event), do: Convertible.model_to_as(event)
def item(%TodoList{} = todo_list), do: Convertible.model_to_as(todo_list)
- defp actor_applicant_group_member?(%Actor{}, nil), do: false
-
+ @spec actor_applicant_group_member?(Actor.t(), Actor.t()) :: boolean()
defp actor_applicant_group_member?(%Actor{id: group_id}, %Actor{id: actor_applicant_id}),
do:
Actors.get_member(actor_applicant_id, group_id, [
diff --git a/lib/web/views/utils.ex b/lib/web/views/utils.ex
index ecc1018c4..93336f44f 100644
--- a/lib/web/views/utils.ex
+++ b/lib/web/views/utils.ex
@@ -8,7 +8,7 @@ defmodule Mobilizon.Web.Views.Utils do
import Plug.Conn, only: [put_status: 2, halt: 1]
# sobelow_skip ["Traversal.FileModule"]
- @spec inject_tags(Enum.t(), String.t()) :: {:ok, {:safe, String.t()}}
+ @spec inject_tags(Enum.t(), String.t()) :: {:ok, {:safe, String.t()}} | {:error, atom()}
def inject_tags(tags, locale \\ "en") do
with path <- Path.join(Application.app_dir(:mobilizon, "priv/static"), "index.html"),
{:exists, true} <- {:exists, File.exists?(path)},
@@ -17,6 +17,7 @@ defmodule Mobilizon.Web.Views.Utils do
{:ok, {:safe, safe}}
else
{:exists, false} -> {:error, :index_not_found}
+ {:error, error} when is_atom(error) -> {:error, error}
end
end
diff --git a/mix.exs b/mix.exs
index 01805dd1e..6ee9c9793 100644
--- a/mix.exs
+++ b/mix.exs
@@ -15,6 +15,7 @@ defmodule Mobilizon.Mixfile do
aliases: aliases(),
deps: deps(),
test_coverage: [tool: ExCoveralls],
+ dialyzer: [plt_add_apps: [:mix]],
preferred_cli_env: [
coveralls: :test,
"coveralls.detail": :test,
diff --git a/test/tasks/users_test.exs b/test/tasks/users_test.exs
index 89575e030..bff6acce2 100644
--- a/test/tasks/users_test.exs
+++ b/test/tasks/users_test.exs
@@ -277,7 +277,7 @@ defmodule Mix.Tasks.Mobilizon.UsersTest do
assert {:ok, %User{confirmed_at: confirmed_at}} = Users.get_user_by_email(@email)
assert output_received ==
- "An user has been modified with the following information:\n - email: #{user.email}\n - Role: #{user.role}\n - account status: activated on #{confirmed_at} (UTC)\n"
+ "An user has been modified with the following information:\n - email: #{user.email}\n - Role: #{user.role}\n - account status: Activated on #{confirmed_at} (UTC)\n"
refute is_nil(confirmed_at)
@@ -308,7 +308,7 @@ defmodule Mix.Tasks.Mobilizon.UsersTest do
Users.get_user_by_email(@modified_email)
assert output_received ==
- "An user has been modified with the following information:\n - email: #{@modified_email}\n - Role: #{user.role}\n - account status: activated on #{confirmed_at} (UTC)\n"
+ "An user has been modified with the following information:\n - email: #{@modified_email}\n - Role: #{user.role}\n - account status: Activated on #{confirmed_at} (UTC)\n"
end
end
end
diff --git a/test/web/upload/upload_test.exs b/test/web/upload/upload_test.exs
index b9aeadbeb..871a6e10e 100644
--- a/test/web/upload/upload_test.exs
+++ b/test/web/upload/upload_test.exs
@@ -127,7 +127,8 @@ defmodule Mobilizon.UploadTest do
filename: "test.txt"
}
- assert {:error, :mime_type_not_allowed} == Upload.store(file)
+ res = Upload.store(file)
+ assert match?({:error, :mime_type_not_allowed}, res)
end
test "copies the file to the configured folder with anonymizing filename" do
@@ -189,7 +190,7 @@ defmodule Mobilizon.UploadTest do
refute File.exists?(file)
- assert {:error, "File not_existing/definitely.jpg doesn't exist"} =
+ assert {:error, :enofile} =
Upload.remove("https://mobilizon.test/media/not_existing/definitely.jpg")
end
end
| | | | | |