From 5d8d2e80a5a1e11824709059a02dbf5320e0a876 Mon Sep 17 00:00:00 2001
From: Thomas Citharel <tcit@tcit.fr>
Date: Tue, 5 Oct 2021 15:29:23 +0200
Subject: [PATCH 1/3] Add mix_unused to detect unused functions

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
---
 mix.exs  | 43 +++++++++++++++++++++++++++++++++++++++++--
 mix.lock |  1 +
 2 files changed, 42 insertions(+), 2 deletions(-)

diff --git a/mix.exs b/mix.exs
index 22eb16856..c4693fc4e 100644
--- a/mix.exs
+++ b/mix.exs
@@ -9,7 +9,7 @@ defmodule Mobilizon.Mixfile do
       version: @version,
       elixir: "~> 1.11",
       elixirc_paths: elixirc_paths(Mix.env()),
-      compilers: [:gettext] ++ Mix.compilers(),
+      compilers: [:gettext, :unused] ++ Mix.compilers(),
       xref: [exclude: [:eldap]],
       start_permanent: Mix.env() == :prod,
       aliases: aliases(),
@@ -37,6 +37,44 @@ defmodule Mobilizon.Mixfile do
           config_providers: [{Mobilizon.ConfigProvider, "/etc/mobilizon/config.exs"}],
           steps: [:assemble, &copy_files/1, &copy_config/1]
         ]
+      ],
+      unused: [
+        ignore: [
+          Mobilizon.Storage.Repo,
+          Mobilizon.Storage.PostgresTypes,
+          Mobilizon.Factory,
+          Mobilizon.Web.Router.Helpers,
+          Mobilizon.Web.Email.Mailer,
+          Mobilizon.Web.Auth.Guardian.Plug,
+          Mobilizon.Web.Gettext,
+          Mobilizon.Web.Endpoint,
+          Mobilizon.Web.Auth.Guardian,
+          Mobilizon.Web,
+          Mobilizon.GraphQL.Schema.Compiled,
+          Mobilizon.GraphQL.Schema,
+          Mobilizon.Web.Router,
+          Mobilizon.Users.Setting.Location,
+          {:_, :start_link, 1},
+          {:_, :child_spec, 1},
+          {:_, :__impl__, 1},
+          {:_, :__schema__, :_},
+          {:_, :__struct__, 0..1},
+          {:_, :__changeset__, 0},
+          {:_, :create_type, 0},
+          {:_, :drop_type, 0},
+          {:_, :schema, 0},
+          {:_, :schemaless_type, 0},
+          {:_, :valid_value?, 0..1},
+          {:_, :__enum_map__, 0},
+          {:_, :__absinthe_blueprint__, :_},
+          {:_, :__absinthe_function__, :_},
+          {~r/^Mobilizon.Web.*Controller/, :_, 2},
+          {~r/^Mobilizon.Web.*View/, :_, :_},
+          {~r/^Mobilizon.Web.Email.*/, :render, 3},
+          {~r/^Mobilizon.Service.HTTP.*Client/, :_, :_},
+          {~r/^Mobilizon.Cldr.*/, :_, :_},
+          {Mobilizon.Web.GraphQLSocket, :__channel__, 1}
+        ]
       ]
     ]
   end
@@ -183,7 +221,8 @@ defmodule Mobilizon.Mixfile do
       {:mox, "~> 1.0", only: :test},
       {:junit_formatter, "~> 3.1", only: [:test]},
       {:sobelow, "~> 0.8", only: [:dev, :test]},
-      {:doctor, "~> 0.18.0", only: :dev}
+      {:doctor, "~> 0.18.0", only: :dev},
+      {:mix_unused, "~> 0.2.0"}
     ] ++ oauth_deps()
   end
 
diff --git a/mix.lock b/mix.lock
index c6e1bddd0..6b3969f44 100644
--- a/mix.lock
+++ b/mix.lock
@@ -94,6 +94,7 @@
   "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"},
   "mimetype_parser": {:hex, :mimetype_parser, "0.1.3", "628ac9fe56aa7edcedb534d68397dd66674ab82493c8ebe39acb9a19b666099d", [:mix], [], "hexpm", "7d8f80c567807ce78cd93c938e7f4b0a20b1aaaaab914bf286f68457d9f7a852"},
   "mix_test_watch": {:hex, :mix_test_watch, "1.1.0", "330bb91c8ed271fe408c42d07e0773340a7938d8a0d281d57a14243eae9dc8c3", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}], "hexpm", "52b6b1c476cbb70fd899ca5394506482f12e5f6b0d6acff9df95c7f1e0812ec3"},
+  "mix_unused": {:hex, :mix_unused, "0.2.0", "430d011f7c86eec8e4824c66fb69127f192ca3b2b2dd0e0f8f58bf535d319167", [:mix], [], "hexpm", "f3eb53bf416d89be1db801865b3f2a6c8c084e138ad290c82b5136a31e5622ad"},
   "mmdb2_decoder": {:hex, :mmdb2_decoder, "3.0.0", "54828676a36e75e9a25bc9a0bb0598d4c7fcc767bf0b40674850b22e05b7b6cc", [:mix], [], "hexpm", "359dc9242915538d1dceb9f6d96c72201dca76ce62e49d22e2ed1e86f20bea8e"},
   "mock": {:hex, :mock, "0.3.7", "75b3bbf1466d7e486ea2052a73c6e062c6256fb429d6797999ab02fa32f29e03", [:mix], [{:meck, "~> 0.9.2", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "4da49a4609e41fd99b7836945c26f373623ea968cfb6282742bcb94440cf7e5c"},
   "mogrify": {:hex, :mogrify, "0.9.1", "a26f107c4987477769f272bd0f7e3ac4b7b75b11ba597fd001b877beffa9c068", [:mix], [], "hexpm", "134edf189337d2125c0948bf0c228fdeef975c594317452d536224069a5b7f05"},

From f4284e1d3a2d4260e219b448232db527aa7c4dbb Mon Sep 17 00:00:00 2001
From: Thomas Citharel <tcit@tcit.fr>
Date: Tue, 5 Oct 2021 15:29:06 +0200
Subject: [PATCH 2/3] Remove unused functions

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
---
 lib/federation/activity_pub/utils.ex          |  16 +-
 .../activity_stream/converter/utils.ex        |   8 -
 lib/federation/web_finger/web_finger.ex       |   3 -
 lib/graphql/api/events.ex                     |   6 +-
 lib/graphql/api/utils.ex                      |  14 +-
 lib/graphql/resolvers/comment.ex              |   2 +-
 lib/graphql/resolvers/todos.ex                |  12 +-
 lib/graphql/schema/actors/group.ex            |   6 +
 lib/graphql/schema/todos/todo_list.ex         |  18 +-
 lib/mix/tasks/mobilizon/actors/utils.ex       |   4 +-
 lib/mobilizon/activities/activities.ex        |   6 -
 lib/mobilizon/actors/actors.ex                | 125 +-------------
 lib/mobilizon/config.ex                       |   7 -
 lib/mobilizon/discussions/discussions.ex      |  39 -----
 lib/mobilizon/events/events.ex                | 159 +-----------------
 lib/mobilizon/medias/medias.ex                |  20 ---
 lib/mobilizon/posts/posts.ex                  |   4 -
 lib/mobilizon/reports/reports.ex              |  58 -------
 lib/mobilizon/resources/resources.ex          |   7 -
 lib/mobilizon/users/user.ex                   |   7 -
 lib/mobilizon/users/users.ex                  |  76 ---------
 lib/mobilizon/utils.ex                        |  25 ---
 lib/service/activity/activity.ex              |   5 -
 lib/service/export/common.ex                  |   2 +-
 lib/service/export/icalendar.ex               |  16 +-
 lib/web/cache/cache.ex                        |  12 --
 lib/web/email/admin.ex                        |   6 +-
 lib/web/email/checker.ex                      |   6 +-
 lib/web/email/group.ex                        |  10 +-
 lib/web/email/user.ex                         |  22 ++-
 lib/web/proxy/reverse_proxy.ex                |   3 -
 lib/web/upload/mime.ex                        |  16 --
 lib/web/upload/upload.ex                      |  26 +--
 test/mobilizon/actors/actors_test.exs         |   6 -
 test/mobilizon/discussions_test.exs           |   6 -
 test/mobilizon/events/events_test.exs         |   6 +-
 test/mobilizon/media/media_test.exs           |  18 --
 test/mobilizon/users/users_test.exs           |  19 ---
 test/support/data_case.ex                     |  15 ++
 .../controllers/webfinger_controller_test.exs |   2 +-
 test/web/upload/filter/exiftool_test.exs      |  10 +-
 41 files changed, 114 insertions(+), 714 deletions(-)
 delete mode 100644 lib/mobilizon/utils.ex

diff --git a/lib/federation/activity_pub/utils.ex b/lib/federation/activity_pub/utils.ex
index 08879e01b..77a3e5f78 100644
--- a/lib/federation/activity_pub/utils.ex
+++ b/lib/federation/activity_pub/utils.ex
@@ -12,7 +12,7 @@ defmodule Mobilizon.Federation.ActivityPub.Utils do
   alias Mobilizon.Actors.Actor
   alias Mobilizon.Medias.Media
 
-  alias Mobilizon.Federation.ActivityPub.{Actions, Activity, Federator, Relay}
+  alias Mobilizon.Federation.ActivityPub.{Actions, Activity, Federator}
   alias Mobilizon.Federation.ActivityPub.Actor, as: ActivityPubActor
   alias Mobilizon.Federation.ActivityStream.Converter
   alias Mobilizon.Federation.HTTPSignatures
@@ -345,10 +345,6 @@ defmodule Mobilizon.Federation.ActivityPub.Utils do
     end
   end
 
-  def make_media_data(%Media{} = media) do
-    Converter.Media.model_to_as(media)
-  end
-
   def make_media_data(media) when is_map(media) do
     with {:ok, %{url: url} = uploaded} <-
            Mobilizon.Web.Upload.store(media.file),
@@ -618,16 +614,6 @@ defmodule Mobilizon.Federation.ActivityPub.Utils do
     [{:Signature, signature}]
   end
 
-  @doc """
-  Sign a request with the instance Relay actor.
-  """
-  @spec sign_fetch_relay(Enum.t(), String.t(), String.t()) :: Enum.t()
-  def sign_fetch_relay(headers, id, date) do
-    with %Actor{} = actor <- Relay.get_actor() do
-      sign_fetch(headers, actor, id, date)
-    end
-  end
-
   @doc """
   Sign a request with an actor.
   """
diff --git a/lib/federation/activity_stream/converter/utils.ex b/lib/federation/activity_stream/converter/utils.ex
index a01be5bde..b05eb6d30 100644
--- a/lib/federation/activity_stream/converter/utils.ex
+++ b/lib/federation/activity_stream/converter/utils.ex
@@ -38,14 +38,6 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Utils do
 
   def fetch_mentions(_), do: []
 
-  def fetch_address(%{id: id}) do
-    with {id, ""} <- Integer.parse(id), do: %{id: id}
-  end
-
-  def fetch_address(address) when is_map(address) do
-    address
-  end
-
   def fetch_actors(actors) when is_list(actors) do
     Logger.debug("fetching contacts")
     actors |> Enum.map(& &1.id) |> Enum.filter(& &1) |> Enum.map(&Actors.get_actor/1)
diff --git a/lib/federation/web_finger/web_finger.ex b/lib/federation/web_finger/web_finger.ex
index 65790eb25..e7bcd49dd 100644
--- a/lib/federation/web_finger/web_finger.ex
+++ b/lib/federation/web_finger/web_finger.ex
@@ -76,10 +76,7 @@ defmodule Mobilizon.Federation.WebFinger do
   @doc """
   Return an `Mobilizon.Actors.Actor` Webfinger representation (as JSON)
   """
-  @spec represent_actor(Actor.t()) :: map()
   @spec represent_actor(Actor.t(), String.t()) :: map()
-  def represent_actor(%Actor{} = actor), do: represent_actor(actor, "JSON")
-
   def represent_actor(%Actor{} = actor, "JSON") do
     links =
       [
diff --git a/lib/graphql/api/events.ex b/lib/graphql/api/events.ex
index d71a37f48..92d4b1116 100644
--- a/lib/graphql/api/events.ex
+++ b/lib/graphql/api/events.ex
@@ -29,9 +29,9 @@ defmodule Mobilizon.GraphQL.API.Events do
   @doc """
   Trigger the deletion of an event
   """
-  @spec delete_event(Event.t(), Actor.t(), boolean()) :: {:ok, Activity.t(), Entity.t()} | any()
-  def delete_event(%Event{} = event, %Actor{} = actor, federate \\ true) do
-    Actions.Delete.delete(event, actor, federate)
+  @spec delete_event(Event.t(), Actor.t()) :: {:ok, Activity.t(), Entity.t()} | any()
+  def delete_event(%Event{} = event, %Actor{} = actor) do
+    Actions.Delete.delete(event, actor, true)
   end
 
   @spec prepare_args(map) :: map
diff --git a/lib/graphql/api/utils.ex b/lib/graphql/api/utils.ex
index 1effea583..bde6ce86a 100644
--- a/lib/graphql/api/utils.ex
+++ b/lib/graphql/api/utils.ex
@@ -3,7 +3,7 @@ defmodule Mobilizon.GraphQL.API.Utils do
   Utils for API.
   """
 
-  alias Mobilizon.{Config, Medias}
+  alias Mobilizon.Medias
   alias Mobilizon.Medias.Media
   alias Mobilizon.Service.Formatter
 
@@ -30,18 +30,6 @@ defmodule Mobilizon.GraphQL.API.Utils do
     |> Formatter.linkify(options)
   end
 
-  def make_report_content_text(nil), do: {:ok, nil}
-
-  def make_report_content_text(comment) do
-    max_size = Config.get([:instance, :max_report_comment_size], 1000)
-
-    if String.length(comment) <= max_size do
-      {:ok, Formatter.html_escape(comment, "text/plain")}
-    else
-      {:error, "Comment must be up to #{max_size} characters"}
-    end
-  end
-
   @doc """
   Use the data-media-id attributes to extract media from body text
   """
diff --git a/lib/graphql/resolvers/comment.ex b/lib/graphql/resolvers/comment.ex
index 115b654e1..247138f83 100644
--- a/lib/graphql/resolvers/comment.ex
+++ b/lib/graphql/resolvers/comment.ex
@@ -89,7 +89,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Comment do
     end
   end
 
-  def edit_comment(_parent, _args, _context) do
+  def update_comment(_parent, _args, _context) do
     {:error, dgettext("errors", "You are not allowed to update a comment if not connected")}
   end
 
diff --git a/lib/graphql/resolvers/todos.ex b/lib/graphql/resolvers/todos.ex
index f8a269ea5..502dda86d 100644
--- a/lib/graphql/resolvers/todos.ex
+++ b/lib/graphql/resolvers/todos.ex
@@ -21,17 +21,17 @@ defmodule Mobilizon.GraphQL.Resolvers.Todos do
           {:ok, Page.t(TodoList.t())}
   def find_todo_lists_for_group(
         %Actor{id: group_id} = group,
-        _args,
+        %{page: page, limit: limit},
         %{
           context: %{current_actor: %Actor{id: actor_id}}
         } = _resolution
       ) do
     with {:member, true} <- {:member, Actors.is_member?(actor_id, group_id)},
-         %Page{} = page <- Todos.get_todo_lists_for_group(group) do
+         %Page{} = page <- Todos.get_todo_lists_for_group(group, page, limit) do
       {:ok, page}
     else
       {:member, _} ->
-        with %Page{} = page <- Todos.get_todo_lists_for_group(group) do
+        with %Page{} = page <- Todos.get_todo_lists_for_group(group, page, limit) do
           {:ok, %Page{page | elements: []}}
         end
     end
@@ -41,17 +41,17 @@ defmodule Mobilizon.GraphQL.Resolvers.Todos do
     {:ok, %Page{total: 0, elements: []}}
   end
 
-  @spec find_todo_lists_for_group(TodoList.t(), map(), Absinthe.Resolution.t()) ::
+  @spec find_todos_for_todo_list(TodoList.t(), map(), Absinthe.Resolution.t()) ::
           {:ok, Page.t(Todo.t())} | {:error, String.t()}
   def find_todos_for_todo_list(
         %TodoList{actor_id: group_id} = todo_list,
-        _args,
+        %{page: page, limit: limit},
         %{
           context: %{current_actor: %Actor{id: actor_id}}
         } = _resolution
       ) do
     with {:member, true} <- {:member, Actors.is_member?(actor_id, group_id)},
-         %Page{} = page <- Todos.get_todos_for_todo_list(todo_list) do
+         %Page{} = page <- Todos.get_todos_for_todo_list(todo_list, page, limit) do
       {:ok, page}
     else
       {:member, _} ->
diff --git a/lib/graphql/schema/actors/group.ex b/lib/graphql/schema/actors/group.ex
index b0917a49a..32e12a78d 100644
--- a/lib/graphql/schema/actors/group.ex
+++ b/lib/graphql/schema/actors/group.ex
@@ -129,6 +129,12 @@ defmodule Mobilizon.GraphQL.Schema.Actors.GroupType do
     end
 
     field :todo_lists, :paginated_todo_list_list do
+      arg(:page, :integer,
+        default_value: 1,
+        description: "The page in the paginated todo-lists list"
+      )
+
+      arg(:limit, :integer, default_value: 10, description: "The limit of todo-lists per page")
       resolve(&Todos.find_todo_lists_for_group/3)
       description("A paginated list of the todo lists this group has")
     end
diff --git a/lib/graphql/schema/todos/todo_list.ex b/lib/graphql/schema/todos/todo_list.ex
index 7d9b24100..d1b299b8b 100644
--- a/lib/graphql/schema/todos/todo_list.ex
+++ b/lib/graphql/schema/todos/todo_list.ex
@@ -17,10 +17,20 @@ defmodule Mobilizon.GraphQL.Schema.Todos.TodoListType do
       description: "The actor that owns this todo list"
     )
 
-    field(:todos, :paginated_todo_list,
-      resolve: &Todos.find_todos_for_todo_list/3,
-      description: "The todo-list's todos"
-    )
+    field :todos, :paginated_todo_list do
+      arg(:page, :integer,
+        default_value: 1,
+        description: "The page in the paginated todos list"
+      )
+
+      arg(:limit, :integer,
+        default_value: 10,
+        description: "The limit of todos per page"
+      )
+
+      resolve(&Todos.find_todos_for_todo_list/3)
+      description("The todo-list's todos")
+    end
   end
 
   @desc """
diff --git a/lib/mix/tasks/mobilizon/actors/utils.ex b/lib/mix/tasks/mobilizon/actors/utils.ex
index 28daefdf7..2da7fab42 100644
--- a/lib/mix/tasks/mobilizon/actors/utils.ex
+++ b/lib/mix/tasks/mobilizon/actors/utils.ex
@@ -50,9 +50,9 @@ defmodule Mix.Tasks.Mobilizon.Actors.Utils do
     new_person
   end
 
-  @spec create_group(Actor.t(), String.t(), String.t(), Keyword.t()) ::
+  @spec create_group(Actor.t(), String.t(), String.t()) ::
           {:ok, Actor.t()} | {:error, Ecto.Changeset.t()}
-  def create_group(%Actor{id: admin_id}, username, name, _options \\ []) do
+  def create_group(%Actor{id: admin_id}, username, name) do
     {username, name} = username_and_name(username, name)
 
     Actors.create_group(%{creator_actor_id: admin_id, preferred_username: username, name: name})
diff --git a/lib/mobilizon/activities/activities.ex b/lib/mobilizon/activities/activities.ex
index 3989463b8..9d826bd73 100644
--- a/lib/mobilizon/activities/activities.ex
+++ b/lib/mobilizon/activities/activities.ex
@@ -189,12 +189,6 @@ defmodule Mobilizon.Activities do
     Repo.preload(activity, @activity_preloads)
   end
 
-  @spec object_types :: list(String.t())
-  def object_types, do: @object_type
-
-  @spec subjects :: list(String.t())
-  def subjects, do: @subjects
-
   @spec activity_types :: list(String.t())
   def activity_types, do: @activity_types
 
diff --git a/lib/mobilizon/actors/actors.ex b/lib/mobilizon/actors/actors.ex
index 5a2530248..68fe1fdcb 100644
--- a/lib/mobilizon/actors/actors.ex
+++ b/lib/mobilizon/actors/actors.ex
@@ -82,10 +82,10 @@ defmodule Mobilizon.Actors do
     |> Repo.one()
   end
 
-  @spec get_actor_with_preload!(integer | String.t(), boolean) :: Actor.t()
-  def get_actor_with_preload!(id, include_suspended \\ false) do
+  @spec get_actor_with_preload!(integer | String.t()) :: Actor.t()
+  def get_actor_with_preload!(id) do
     id
-    |> actor_with_preload_query(include_suspended)
+    |> actor_with_preload_query(false)
     |> Repo.one!()
   end
 
@@ -100,14 +100,6 @@ defmodule Mobilizon.Actors do
     |> Repo.one()
   end
 
-  @spec get_remote_actor_with_preload(integer | String.t(), boolean()) :: Actor.t() | nil
-  def get_remote_actor_with_preload(id, include_suspended \\ false) do
-    id
-    |> actor_with_preload_query(include_suspended)
-    |> filter_external()
-    |> Repo.one()
-  end
-
   @doc """
   Gets an actor by its URL (ActivityPub ID). The `:preload` option allows to
   preload the followers relation.
@@ -131,11 +123,9 @@ defmodule Mobilizon.Actors do
   New function to replace `Mobilizon.Actors.get_actor_by_url/1` with
   better signature
   """
-  @spec get_actor_by_url_2(String.t(), boolean) :: Actor.t() | nil
-  def get_actor_by_url_2(url, preload \\ false) do
-    Actor
-    |> Repo.get_by(url: url)
-    |> preload_followers(preload)
+  @spec get_actor_by_url_2(String.t()) :: Actor.t() | nil
+  def get_actor_by_url_2(url) do
+    Repo.get_by(Actor, url: url)
   end
 
   @doc """
@@ -495,16 +485,6 @@ defmodule Mobilizon.Actors do
     |> Repo.one()
   end
 
-  @doc """
-  Gets a local group by its title.
-  """
-  @spec get_local_group_by_title(String.t()) :: Actor.t() | nil
-  def get_local_group_by_title(title) do
-    group_query()
-    |> filter_by_name([title])
-    |> Repo.one()
-  end
-
   @doc """
   Gets a group by its actor id.
   """
@@ -575,11 +555,6 @@ defmodule Mobilizon.Actors do
     end
   end
 
-  @doc """
-  Deletes a group.
-  """
-  def delete_group!(%Actor{type: :Group} = group), do: Repo.delete!(group)
-
   @doc """
   Counts the local groups
   """
@@ -599,15 +574,6 @@ defmodule Mobilizon.Actors do
     |> Repo.aggregate(:count)
   end
 
-  @doc """
-  Lists the groups.
-  """
-  @spec list_groups(integer | nil, integer | nil) :: Page.t()
-  def list_groups(page \\ nil, limit \\ nil) do
-    groups_query()
-    |> Page.build_page(page, limit)
-  end
-
   @doc """
   Lists the groups.
   """
@@ -620,10 +586,10 @@ defmodule Mobilizon.Actors do
   @doc """
   Lists the groups.
   """
-  @spec list_external_groups(non_neg_integer()) :: list(Actor.t())
-  def list_external_groups(limit \\ 100) when limit > 0 do
+  @spec list_external_groups :: list(Actor.t())
+  def list_external_groups do
     external_groups_query()
-    |> limit(^limit)
+    |> limit(100)
     |> Repo.all()
   end
 
@@ -833,30 +799,6 @@ defmodule Mobilizon.Actors do
     |> Repo.all()
   end
 
-  @spec list_local_members_for_group(Actor.t(), integer | nil, integer | nil) ::
-          Page.t(Member.t())
-  def list_local_members_for_group(
-        %Actor{id: group_id, type: :Group} = _group,
-        page \\ nil,
-        limit \\ nil
-      ) do
-    group_id
-    |> group_internal_member_query()
-    |> Page.build_page(page, limit)
-  end
-
-  @spec list_remote_members_for_group(Actor.t(), integer | nil, integer | nil) ::
-          Page.t(Member.t())
-  def list_remote_members_for_group(
-        %Actor{id: group_id, type: :Group} = _group,
-        page \\ nil,
-        limit \\ nil
-      ) do
-    group_id
-    |> group_external_member_query()
-    |> Page.build_page(page, limit)
-  end
-
   @doc """
   Returns a paginated list of members for a group.
   """
@@ -888,17 +830,6 @@ defmodule Mobilizon.Actors do
     |> Repo.all()
   end
 
-  @doc """
-  Returns a paginated list of administrator members for a group.
-  """
-  @spec list_administrator_members_for_group(integer | String.t(), integer | nil, integer | nil) ::
-          Page.t()
-  def list_administrator_members_for_group(id, page \\ nil, limit \\ nil) do
-    id
-    |> administrator_members_for_group_query()
-    |> Page.build_page(page, limit)
-  end
-
   @doc """
   Returns the complete list of administrator members for a group.
   """
@@ -1012,12 +943,6 @@ defmodule Mobilizon.Actors do
   @spec delete_bot(Bot.t()) :: {:ok, Bot.t()} | {:error, Ecto.Changeset.t()}
   def delete_bot(%Bot{} = bot), do: Repo.delete(bot)
 
-  @doc """
-  Returns the list of bots.
-  """
-  @spec list_bots :: [Bot.t()]
-  def list_bots, do: Repo.all(Bot)
-
   @doc """
   Gets a single follower.
   """
@@ -1105,17 +1030,6 @@ defmodule Mobilizon.Actors do
     |> Repo.delete()
   end
 
-  @doc """
-  Returns the list of followers for an actor.
-  If actor A and C both follow actor B, actor B's followers are A and C.
-  """
-  @spec list_followers_actors_for_actor(Actor.t()) :: [Actor.t()]
-  def list_followers_actors_for_actor(%Actor{id: actor_id}) do
-    actor_id
-    |> follower_actors_for_actor_query()
-    |> Repo.all()
-  end
-
   @doc """
   Returns the list of external followers for an actor.
   """
@@ -1177,17 +1091,6 @@ defmodule Mobilizon.Actors do
     |> Page.build_page(page, limit)
   end
 
-  @doc """
-  Returns the list of followings for an actor.
-  If actor A follows actor B and C, actor A's followings are B and C.
-  """
-  @spec list_followings_for_actor(Actor.t()) :: [Follower.t()]
-  def list_followings_for_actor(%Actor{id: actor_id}) do
-    actor_id
-    |> followings_actors_for_actor_query()
-    |> Repo.all()
-  end
-
   @doc """
   Returns the number of followings for an actor
   """
@@ -1485,16 +1388,6 @@ defmodule Mobilizon.Actors do
     |> select([_m, a], a)
   end
 
-  @spec group_external_member_query(integer()) :: Ecto.Query.t()
-  defp group_external_member_query(group_id) do
-    Member
-    |> where([m], m.parent_id == ^group_id)
-    |> join(:inner, [m], a in Actor, on: m.actor_id == a.id)
-    |> where([_m, a], not is_nil(a.domain))
-    |> preload([m], [:parent, :actor])
-    |> select([m, _a], m)
-  end
-
   @spec group_internal_member_actor_query(integer(), list()) :: Ecto.Query.t()
   defp group_internal_member_actor_query(group_id, role) do
     Member
diff --git a/lib/mobilizon/config.ex b/lib/mobilizon/config.ex
index 792eb41b2..4cdefb972 100644
--- a/lib/mobilizon/config.ex
+++ b/lib/mobilizon/config.ex
@@ -223,10 +223,6 @@ defmodule Mobilizon.Config do
         :confirmation_required
       ]
 
-  @spec anonymous_participation_email_captcha_required? :: boolean
-  def anonymous_participation_email_captcha_required?,
-    do: anonymous_config()[:participation][:validation][:captcha][:enabled]
-
   @spec anonymous_event_creation? :: boolean
   def anonymous_event_creation?,
     do: anonymous_config()[:event_creation][:allowed]
@@ -265,9 +261,6 @@ defmodule Mobilizon.Config do
     end)
   end
 
-  @spec oauth_consumer_enabled? :: boolean()
-  def oauth_consumer_enabled?, do: oauth_consumer_strategies() != []
-
   @spec ldap_enabled? :: boolean()
   def ldap_enabled?, do: get([:ldap, :enabled], false)
 
diff --git a/lib/mobilizon/discussions/discussions.ex b/lib/mobilizon/discussions/discussions.ex
index cc4d15814..cfb080e27 100644
--- a/lib/mobilizon/discussions/discussions.ex
+++ b/lib/mobilizon/discussions/discussions.ex
@@ -125,13 +125,6 @@ defmodule Mobilizon.Discussions do
   @spec get_comment_from_url(String.t()) :: Comment.t() | nil
   def get_comment_from_url(url), do: Repo.get_by(Comment, url: url)
 
-  @doc """
-  Gets a comment by its URL.
-  Raises `Ecto.NoResultsError` if the comment does not exist.
-  """
-  @spec get_comment_from_url!(String.t()) :: Comment.t()
-  def get_comment_from_url!(url), do: Repo.get_by!(Comment, url: url)
-
   @doc """
   Gets a comment by its URL, with all associations loaded.
   """
@@ -175,19 +168,6 @@ defmodule Mobilizon.Discussions do
     |> Repo.preload(@comment_preloads)
   end
 
-  @doc """
-  Get all comment threads under an event
-  """
-  @spec get_threads(String.t() | integer()) :: [Comment.t()]
-  def get_threads(event_id) do
-    Comment
-    |> where([c, _], c.event_id == ^event_id and is_nil(c.origin_comment_id))
-    |> join(:left, [c], r in Comment, on: r.origin_comment_id == c.id)
-    |> group_by([c], c.id)
-    |> select([c, r], %{c | total_replies: count(r.id)})
-    |> Repo.all()
-  end
-
   @doc """
   Gets paginated replies for root comment
   """
@@ -198,17 +178,6 @@ defmodule Mobilizon.Discussions do
     |> Repo.all()
   end
 
-  @doc """
-  Get a comment or create it
-  """
-  @spec get_or_create_comment(map()) :: {:ok, Comment.t()}
-  def get_or_create_comment(%{"url" => url} = attrs) do
-    case Repo.get_by(Comment, url: url) do
-      %Comment{} = comment -> {:ok, Repo.preload(comment, @comment_preloads)}
-      nil -> create_comment(attrs)
-    end
-  end
-
   @doc """
   Creates a comment.
   """
@@ -253,14 +222,6 @@ defmodule Mobilizon.Discussions do
     end
   end
 
-  @doc """
-  Returns the list of public comments.
-  """
-  @spec list_comments :: [Comment.t()]
-  def list_comments do
-    Repo.all(from(c in Comment, where: c.visibility == ^:public))
-  end
-
   @doc """
   Returns a paginated list of local comments
   """
diff --git a/lib/mobilizon/events/events.ex b/lib/mobilizon/events/events.ex
index b4dec2748..25982f68a 100644
--- a/lib/mobilizon/events/events.ex
+++ b/lib/mobilizon/events/events.ex
@@ -159,34 +159,11 @@ defmodule Mobilizon.Events do
     |> Repo.one!()
   end
 
-  @doc """
-  Gets an event by its URL, with all associations loaded.
-  """
-  @spec get_public_event_by_url_with_preload(String.t()) ::
-          {:ok, Event.t()} | {:error, :event_not_found}
-  def get_public_event_by_url_with_preload(url) do
-    event =
-      url
-      |> event_by_url_query()
-      |> filter_unlisted_and_public_visibility()
-      |> filter_draft()
-      |> preload_for_event()
-      |> Repo.one()
-
-    case event do
-      %Event{} = event ->
-        {:ok, event}
-
-      nil ->
-        {:error, :event_not_found}
-    end
-  end
-
   @doc """
   Gets an event by its URL, with all associations loaded.
   Raises `Ecto.NoResultsError` if the event does not exist.
   """
-  @spec get_public_event_by_url_with_preload(String.t()) :: Event.t()
+  @spec get_public_event_by_url_with_preload!(String.t()) :: Event.t()
   def get_public_event_by_url_with_preload!(url) do
     url
     |> event_by_url_query()
@@ -228,18 +205,6 @@ defmodule Mobilizon.Events do
     |> Repo.one()
   end
 
-  @doc """
-  Gets an event by its UUID, with all associations loaded.
-  """
-  @spec get_own_event_by_uuid_with_preload(String.t(), integer()) :: Event.t() | nil
-  def get_own_event_by_uuid_with_preload(uuid, user_id) do
-    uuid
-    |> event_by_uuid_query()
-    |> user_events_query(user_id)
-    |> preload_for_event()
-    |> Repo.one()
-  end
-
   @doc """
   Gets an actor's eventual upcoming public event.
   """
@@ -253,13 +218,6 @@ defmodule Mobilizon.Events do
     |> Repo.one()
   end
 
-  def get_or_create_event(%{"url" => url} = attrs) do
-    case Repo.get_by(Event, url: url) do
-      %Event{} = event -> {:ok, Repo.preload(event, @event_preloads)}
-      nil -> create_event(attrs)
-    end
-  end
-
   @doc """
   Creates an event.
   """
@@ -502,9 +460,9 @@ defmodule Mobilizon.Events do
   Finds close events to coordinates.
   Radius is in meters and defaults to 50km.
   """
-  @spec find_close_events(number, number, number, number) :: [Event.t()]
-  def find_close_events(lon, lat, radius \\ 50_000, srid \\ 4326) do
-    "SRID=#{srid};POINT(#{lon} #{lat})"
+  @spec find_close_events(float(), float(), non_neg_integer()) :: [Event.t()]
+  def find_close_events(lon, lat, radius \\ 50_000) do
+    "SRID=#{4326};POINT(#{lon} #{lat})"
     |> Geo.WKT.decode!()
     |> close_events_query(radius)
     |> filter_draft()
@@ -585,33 +543,6 @@ defmodule Mobilizon.Events do
     |> Repo.one()
   end
 
-  @doc """
-  Gets an existing tag or creates the new one.
-
-  From a map containing a %{"name" => "#mytag"} or a direct binary
-  """
-  @spec get_or_create_tag(map) :: {:ok, Tag.t()} | {:error, Changeset.t()}
-  def get_or_create_tag(%{"name" => "#" <> title}) do
-    case Repo.get_by(Tag, title: title) do
-      %Tag{} = tag ->
-        {:ok, tag}
-
-      nil ->
-        create_tag(%{"title" => title})
-    end
-  end
-
-  @spec get_or_create_tag(String.t()) :: {:ok, Tag.t()} | {:error, Changeset.t()}
-  def get_or_create_tag(title) do
-    case Repo.get_by(Tag, title: title) do
-      %Tag{} = tag ->
-        {:ok, tag}
-
-      nil ->
-        create_tag(%{"title" => title})
-    end
-  end
-
   @doc """
   Creates a tag.
   """
@@ -663,8 +594,8 @@ defmodule Mobilizon.Events do
   @doc """
   Checks whether two tags are linked or not.
   """
-  @spec are_tags_linked(Tag.t(), Tag.t()) :: boolean
-  def are_tags_linked(%Tag{id: tag1_id}, %Tag{id: tag2_id}) do
+  @spec are_tags_linked?(Tag.t(), Tag.t()) :: boolean
+  def are_tags_linked?(%Tag{id: tag1_id}, %Tag{id: tag2_id}) do
     tag_relation =
       tag1_id
       |> tags_linked_query(tag2_id)
@@ -833,8 +764,6 @@ defmodule Mobilizon.Events do
     |> Repo.one()
   end
 
-  @moderator_roles [:moderator, :administrator, :creator]
-
   @doc """
   Returns the number of participations for all local events
   """
@@ -894,28 +823,6 @@ defmodule Mobilizon.Events do
     |> Page.build_page(page, limit)
   end
 
-  @doc """
-  Returns the list of moderator participants for an event.
-
-  ## Examples
-
-      iex> moderator_for_event?(5, 3)
-      true
-
-  """
-  @spec moderator_for_event?(integer, integer) :: boolean
-  def moderator_for_event?(event_id, actor_id) do
-    !(Repo.one(
-        from(
-          p in Participant,
-          where:
-            p.event_id == ^event_id and
-              p.actor_id ==
-                ^actor_id and p.role in ^@moderator_roles
-        )
-      ) == nil)
-  end
-
   @doc """
   Returns the list of organizers participants for an event.
 
@@ -957,17 +864,6 @@ defmodule Mobilizon.Events do
     |> Page.build_page(page, limit)
   end
 
-  @doc """
-  Counts approved participants.
-  """
-  @spec count_approved_participants(integer | String.t()) :: integer
-  def count_approved_participants(event_id) do
-    event_id
-    |> count_participants_query()
-    |> filter_approved_role()
-    |> Repo.aggregate(:count, :id)
-  end
-
   @doc """
   Counts participant participants (participants with no extra role)
   """
@@ -979,17 +875,6 @@ defmodule Mobilizon.Events do
     |> Repo.aggregate(:count, :id)
   end
 
-  @doc """
-  Counts rejected participants.
-  """
-  @spec count_rejected_participants(integer | String.t()) :: integer
-  def count_rejected_participants(event_id) do
-    event_id
-    |> count_participants_query()
-    |> filter_rejected_role()
-    |> Repo.aggregate(:count, :id)
-  end
-
   @doc """
   Gets the default participant role depending on the event join options.
   """
@@ -1221,17 +1106,6 @@ defmodule Mobilizon.Events do
     |> Repo.one()
   end
 
-  @doc """
-  Gets a single feed token.
-  Raises `Ecto.NoResultsError` if the feed token does not exist.
-  """
-  @spec get_feed_token!(String.t()) :: FeedToken.t()
-  def get_feed_token!(token) do
-    token
-    |> feed_token_query()
-    |> Repo.one!()
-  end
-
   @doc """
   Creates a feed token.
   """
@@ -1244,17 +1118,6 @@ defmodule Mobilizon.Events do
     |> Repo.insert()
   end
 
-  @doc """
-  Updates a feed token.
-  """
-  @spec update_feed_token(FeedToken.t(), map) ::
-          {:ok, FeedToken.t()} | {:error, Changeset.t()}
-  def update_feed_token(%FeedToken{} = feed_token, attrs) do
-    feed_token
-    |> FeedToken.changeset(attrs)
-    |> Repo.update()
-  end
-
   @doc """
   Deletes a feed token.
   """
@@ -1746,21 +1609,11 @@ defmodule Mobilizon.Events do
     )
   end
 
-  @spec filter_approved_role(Ecto.Queryable.t()) :: Ecto.Query.t()
-  defp filter_approved_role(query) do
-    filter_role(query, [:not_approved, :rejected])
-  end
-
   @spec filter_participant_role(Ecto.Queryable.t()) :: Ecto.Query.t()
   defp filter_participant_role(query) do
     filter_role(query, :participant)
   end
 
-  @spec filter_rejected_role(Ecto.Queryable.t()) :: Ecto.Query.t()
-  defp filter_rejected_role(query) do
-    filter_role(query, :rejected)
-  end
-
   @spec filter_role(Ecto.Queryable.t(), list(atom()) | atom()) :: Ecto.Query.t()
   def filter_role(query, []), do: query
 
diff --git a/lib/mobilizon/medias/medias.ex b/lib/mobilizon/medias/medias.ex
index a706e8b1c..d781de872 100644
--- a/lib/mobilizon/medias/medias.ex
+++ b/lib/mobilizon/medias/medias.ex
@@ -52,16 +52,6 @@ defmodule Mobilizon.Medias do
     |> Repo.all()
   end
 
-  @doc """
-  List the paginated media for an actor
-  """
-  @spec medias_for_actor(integer | String.t(), integer | nil, integer | nil) :: Page.t()
-  def medias_for_actor(actor_id, page, limit) do
-    actor_id
-    |> medias_for_actor_query()
-    |> Page.build_page(page, limit)
-  end
-
   @doc """
   List the paginated media for user
   """
@@ -109,16 +99,6 @@ defmodule Mobilizon.Medias do
     |> Repo.insert()
   end
 
-  @doc """
-  Updates a media.
-  """
-  @spec update_media(Media.t(), map) :: {:ok, Media.t()} | {:error, Ecto.Changeset.t()}
-  def update_media(%Media{} = media, attrs) do
-    media
-    |> Media.changeset(attrs)
-    |> Repo.update()
-  end
-
   @doc """
   Deletes a media.
   """
diff --git a/lib/mobilizon/posts/posts.ex b/lib/mobilizon/posts/posts.ex
index d7bd9294f..2f4d7897b 100644
--- a/lib/mobilizon/posts/posts.ex
+++ b/lib/mobilizon/posts/posts.ex
@@ -69,10 +69,6 @@ defmodule Mobilizon.Posts do
     |> Repo.preload(@post_preloads)
   end
 
-  @spec get_post_by_slug(String.t()) :: Post.t() | nil
-  def get_post_by_slug(nil), do: nil
-  def get_post_by_slug(slug), do: Repo.get_by(Post, slug: slug)
-
   @spec get_post_by_slug_with_preloads(String.t()) :: Post.t() | nil
   def get_post_by_slug_with_preloads(slug) do
     Post
diff --git a/lib/mobilizon/reports/reports.ex b/lib/mobilizon/reports/reports.ex
index d774c616a..419dd3b9d 100644
--- a/lib/mobilizon/reports/reports.ex
+++ b/lib/mobilizon/reports/reports.ex
@@ -23,27 +23,6 @@ defmodule Mobilizon.Reports do
     |> Repo.preload([:reported, :reporter, :manager, :event, :comments, :notes])
   end
 
-  @doc """
-  Gets a single report.
-  Raises `Ecto.NoResultsError` if the report does not exist.
-  """
-  @spec get_report!(integer | String.t()) :: Report.t()
-  def get_report!(id) do
-    Report
-    |> Repo.get!(id)
-    |> Repo.preload([:reported, :reporter, :manager, :event, :comments, :notes])
-  end
-
-  @doc """
-  Get a report by its URL
-  """
-  @spec get_report_by_url(String.t()) :: Report.t() | nil
-  def get_report_by_url(url) do
-    url
-    |> report_by_url_query()
-    |> Repo.one()
-  end
-
   @doc """
   Creates a report.
   """
@@ -67,12 +46,6 @@ defmodule Mobilizon.Reports do
     |> Repo.update()
   end
 
-  @doc """
-  Deletes a report.
-  """
-  @spec delete_report(Report.t()) :: {:ok, Report.t()} | {:error, Ecto.Changeset.t()}
-  def delete_report(%Report{} = report), do: Repo.delete(report)
-
   @doc """
   Returns the list of reports.
   """
@@ -104,13 +77,6 @@ defmodule Mobilizon.Reports do
   @spec get_note(integer | String.t()) :: Note.t() | nil
   def get_note(id), do: Repo.get(Note, id)
 
-  @doc """
-  Gets a single note.
-  Raises `Ecto.NoResultsError` if the Note does not exist.
-  """
-  @spec get_note!(integer | String.t()) :: Note.t()
-  def get_note!(id), do: Repo.get!(Note, id)
-
   @doc """
   Creates a note.
   """
@@ -130,21 +96,6 @@ defmodule Mobilizon.Reports do
   @spec delete_note(Note.t()) :: {:ok, Note.t()} | {:error, Ecto.Changeset.t()}
   def delete_note(%Note{} = note), do: Repo.delete(note)
 
-  @doc """
-  Returns the list of notes for a report.
-  """
-  @spec list_notes_for_report(Report.t()) :: [Note.t()]
-  def list_notes_for_report(%Report{id: report_id}) do
-    report_id
-    |> list_notes_for_report_query()
-    |> Repo.all()
-  end
-
-  @spec report_by_url_query(String.t()) :: Ecto.Query.t()
-  defp report_by_url_query(url) do
-    from(r in Report, where: r.uri == ^url)
-  end
-
   @spec list_reports_query(ReportStatus.t()) :: Ecto.Query.t()
   defp list_reports_query(status) do
     from(
@@ -158,13 +109,4 @@ defmodule Mobilizon.Reports do
   defp count_reports_query do
     from(r in Report, where: r.status == ^:open)
   end
-
-  @spec list_notes_for_report_query(integer | String.t()) :: Ecto.Query.t()
-  defp list_notes_for_report_query(report_id) do
-    from(
-      n in Note,
-      where: n.report_id == ^report_id,
-      preload: [:report, :moderator]
-    )
-  end
 end
diff --git a/lib/mobilizon/resources/resources.ex b/lib/mobilizon/resources/resources.ex
index 1293a9dae..9ef6302fd 100644
--- a/lib/mobilizon/resources/resources.ex
+++ b/lib/mobilizon/resources/resources.ex
@@ -27,13 +27,6 @@ defmodule Mobilizon.Resources do
     |> Page.build_page(page, limit)
   end
 
-  @doc """
-  Returns the list of top-level resources for a group
-  """
-  def get_top_level_resources_for_group(%Actor{id: group_id}, page \\ nil, limit \\ nil) do
-    get_resources_for_folder(%Resource{id: "root_something", actor_id: group_id}, page, limit)
-  end
-
   @doc """
   Returns the list of resources for a resource folder.
   """
diff --git a/lib/mobilizon/users/user.ex b/lib/mobilizon/users/user.ex
index cc61aaeec..c0e50b6a9 100644
--- a/lib/mobilizon/users/user.ex
+++ b/lib/mobilizon/users/user.ex
@@ -188,13 +188,6 @@ defmodule Mobilizon.Users.User do
     |> hash_password()
   end
 
-  @doc """
-  Checks whether an user is confirmed.
-  """
-  @spec is_confirmed(t) :: boolean
-  def is_confirmed(%__MODULE__{confirmed_at: nil}), do: false
-  def is_confirmed(%__MODULE__{}), do: true
-
   @doc """
   Returns whether an user owns an actor.
   """
diff --git a/lib/mobilizon/users/users.ex b/lib/mobilizon/users/users.ex
index 1d8b746bb..380d6ac3e 100644
--- a/lib/mobilizon/users/users.ex
+++ b/lib/mobilizon/users/users.ex
@@ -91,19 +91,6 @@ defmodule Mobilizon.Users do
     end
   end
 
-  @doc """
-  Gets an user by its email.
-  """
-  @spec get_user_by_email!(String.t(), Keyword.t()) :: User.t()
-  def get_user_by_email!(email, options \\ []) do
-    activated = Keyword.get(options, :activated, nil)
-    unconfirmed = Keyword.get(options, :unconfirmed, true)
-
-    email
-    |> user_by_email_query(activated, unconfirmed)
-    |> Repo.one!()
-  end
-
   @doc """
   Get an user by its activation token.
   """
@@ -326,22 +313,6 @@ defmodule Mobilizon.Users do
   @spec count_users :: integer
   def count_users, do: Repo.one(from(u in User, select: count(u.id), where: u.disabled == false))
 
-  @doc """
-  Gets a settings for an user.
-
-  Raises `Ecto.NoResultsError` if the Setting does not exist.
-
-  ## Examples
-
-      iex> get_setting!(123)
-      %Setting{}
-
-      iex> get_setting!(456)
-      ** (Ecto.NoResultsError)
-
-  """
-  def get_setting!(user_id), do: Repo.get!(Setting, user_id)
-
   @spec get_setting(User.t()) :: Setting.t() | nil
   def get_setting(%User{id: user_id}), do: get_setting(user_id)
 
@@ -388,35 +359,6 @@ defmodule Mobilizon.Users do
     |> Repo.update()
   end
 
-  @doc """
-  Deletes a setting.
-
-  ## Examples
-
-      iex> delete_setting(setting)
-      {:ok, %Setting{}}
-
-      iex> delete_setting(setting)
-      {:error, %Ecto.Changeset{}}
-
-  """
-  def delete_setting(%Setting{} = setting) do
-    Repo.delete(setting)
-  end
-
-  @doc """
-  Returns an `%Ecto.Changeset{}` for tracking setting changes.
-
-  ## Examples
-
-      iex> change_setting(setting)
-      %Ecto.Changeset{source: %Setting{}}
-
-  """
-  def change_setting(%Setting{} = setting) do
-    Setting.changeset(setting, %{})
-  end
-
   @doc """
   Get a paginated list of all of a user's subscriptions
   """
@@ -458,24 +400,6 @@ defmodule Mobilizon.Users do
     |> Repo.insert()
   end
 
-  @doc """
-  Updates a push subscription.
-
-  ## Examples
-
-      iex> update_push_subscription(push_subscription, %{field: new_value})
-      {:ok, %PushSubscription{}}
-
-      iex> update_push_subscription(push_subscription, %{field: bad_value})
-      {:error, %Ecto.Changeset{}}
-
-  """
-  def update_push_subscription(%PushSubscription{} = push_subscription, attrs) do
-    push_subscription
-    |> PushSubscription.changeset(attrs)
-    |> Repo.update()
-  end
-
   @doc """
   Deletes a push subscription.
 
diff --git a/lib/mobilizon/utils.ex b/lib/mobilizon/utils.ex
deleted file mode 100644
index 400edf4cb..000000000
--- a/lib/mobilizon/utils.ex
+++ /dev/null
@@ -1,25 +0,0 @@
-# Portions of this file are derived from Pleroma:
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Mobilizon.Utils do
-  @moduledoc """
-  Module that provide generic utils functions for Mobilizon
-  """
-
-  @doc """
-  POSIX-compliant check if command is available in the system
-
-  ## Examples
-      iex> command_available?("git")
-      true
-      iex> command_available?("wrongcmd")
-      false
-
-  """
-  @spec command_available?(String.t()) :: boolean()
-  def command_available?(command) do
-    match?({_output, 0}, System.cmd("sh", ["-c", "command -v #{command}"]))
-  end
-end
diff --git a/lib/service/activity/activity.ex b/lib/service/activity/activity.ex
index 3435f44e6..6287f86b4 100644
--- a/lib/service/activity/activity.ex
+++ b/lib/service/activity/activity.ex
@@ -16,11 +16,6 @@ defmodule Mobilizon.Service.Activity do
     do_get_object(object_type, object_id)
   end
 
-  @spec has_object?(Activity.t()) :: boolean()
-  def has_object?(%Activity{} = activity) do
-    !is_nil(object(activity))
-  end
-
   defp do_get_object(_, nil), do: nil
 
   defp do_get_object(:event, event_id) do
diff --git a/lib/service/export/common.ex b/lib/service/export/common.ex
index 8b11870b3..0a39b1ce6 100644
--- a/lib/service/export/common.ex
+++ b/lib/service/export/common.ex
@@ -102,7 +102,7 @@ defmodule Mobilizon.Service.Export.Common do
   end
 
   @spec fetch_actor_private_events(Actor.t(), integer()) :: list(Event.t())
-  def fetch_actor_private_events(%Actor{} = actor, limit) do
+  defp fetch_actor_private_events(%Actor{} = actor, limit) do
     actor |> fetch_identity_participations(limit) |> participations_to_events()
   end
 
diff --git a/lib/service/export/icalendar.ex b/lib/service/export/icalendar.ex
index 74e2ee3d7..83125bdeb 100644
--- a/lib/service/export/icalendar.ex
+++ b/lib/service/export/icalendar.ex
@@ -3,7 +3,6 @@ defmodule Mobilizon.Service.Export.ICalendar do
   Export an event to iCalendar format.
   """
 
-  alias Mobilizon.Actors.Actor
   alias Mobilizon.Addresses.Address
   alias Mobilizon.{Config, Events}
   alias Mobilizon.Events.Event
@@ -79,14 +78,11 @@ defmodule Mobilizon.Service.Export.ICalendar do
 
   def export_public_event(%Event{}), do: {:error, :event_not_public}
 
-  @doc """
-  Export a public actor's events to iCalendar format.
-
-  The actor must have a visibility of `:public` or `:unlisted`, as well as the events
-  """
+  # Export a public actor's events to iCalendar format.
+  # The actor must have a visibility of `:public` or `:unlisted`, as well as the events
   @spec export_public_actor(String.t()) ::
           {:ok, String.t()} | {:error, :actor_not_public | :actor_not_found}
-  def export_public_actor(name, limit \\ @item_limit) do
+  defp export_public_actor(name, limit \\ @item_limit) do
     case Common.fetch_actor_event_feed(name, limit) do
       {:ok, _actor, events, _posts} ->
         {:ok, events_to_ics(events)}
@@ -96,12 +92,6 @@ defmodule Mobilizon.Service.Export.ICalendar do
     end
   end
 
-  @spec export_private_actor(Actor.t(), integer()) :: {:ok, String.t()}
-  def export_private_actor(%Actor{} = actor, limit \\ @item_limit) do
-    events = Common.fetch_actor_private_events(actor, limit)
-    {:ok, events_to_ics(events)}
-  end
-
   @spec fetch_events_from_token(String.t(), integer()) ::
           {:ok, String.t()} | {:error, :actor_not_public | :actor_not_found}
   defp fetch_events_from_token(token, limit \\ @item_limit) do
diff --git a/lib/web/cache/cache.ex b/lib/web/cache/cache.ex
index d90007a05..391806672 100644
--- a/lib/web/cache/cache.ex
+++ b/lib/web/cache/cache.ex
@@ -10,21 +10,9 @@ defmodule Mobilizon.Web.Cache do
   alias Mobilizon.Resources.Resource
   alias Mobilizon.Todos.{Todo, TodoList}
   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 a local actor.
-  """
-  @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
-
   @spec get_actor_by_name(binary) :: {:commit, Actor.t()} | {:ignore, nil}
   defdelegate get_actor_by_name(name), to: ActivityPub
   @spec get_local_actor_by_name(binary) :: {:commit, Actor.t()} | {:ignore, nil}
diff --git a/lib/web/email/admin.ex b/lib/web/email/admin.ex
index bde93cf0d..d9e2cce3d 100644
--- a/lib/web/email/admin.ex
+++ b/lib/web/email/admin.ex
@@ -15,9 +15,9 @@ defmodule Mobilizon.Web.Email.Admin do
 
   alias Mobilizon.Web.Email
 
-  @spec report(User.t(), Report.t(), String.t()) :: Bamboo.Email.t()
-  def report(%User{email: email} = user, %Report{} = report, default_locale \\ "en") do
-    locale = Map.get(user, :locale, default_locale)
+  @spec report(User.t(), Report.t()) :: Bamboo.Email.t()
+  def report(%User{email: email} = user, %Report{} = report) do
+    locale = Map.get(user, :locale, "en")
     Gettext.put_locale(locale)
 
     subject =
diff --git a/lib/web/email/checker.ex b/lib/web/email/checker.ex
index 977eb58c9..fd30f567b 100644
--- a/lib/web/email/checker.ex
+++ b/lib/web/email/checker.ex
@@ -11,11 +11,11 @@ defmodule Mobilizon.Web.Email.Checker do
   @spec valid?(String.t()) :: boolean
   def valid?(email), do: email =~ @email_regex
 
-  @spec validate_changeset(Ecto.Changeset.t(), atom()) :: Ecto.Changeset.t()
-  def validate_changeset(%Ecto.Changeset{} = changeset, key \\ :email) do
+  @spec validate_changeset(Ecto.Changeset.t()) :: Ecto.Changeset.t()
+  def validate_changeset(%Ecto.Changeset{} = changeset) do
     changeset = Ecto.Changeset.validate_length(changeset, :email, min: 3, max: 250)
 
-    case Ecto.Changeset.fetch_change(changeset, key) do
+    case Ecto.Changeset.fetch_change(changeset, :email) do
       {:ok, email} ->
         if valid?(email),
           do: changeset,
diff --git a/lib/web/email/group.ex b/lib/web/email/group.ex
index 6a81fd399..faad3e2ad 100644
--- a/lib/web/email/group.ex
+++ b/lib/web/email/group.ex
@@ -15,17 +15,15 @@ defmodule Mobilizon.Web.Email.Group do
   @doc """
   Send emails to local user
   """
-  @spec send_invite_to_user(Member.t(), String.t()) :: :ok
-  def send_invite_to_user(member, locale \\ "en")
-  def send_invite_to_user(%Member{actor: %Actor{user_id: nil}}, _locale), do: :ok
+  @spec send_invite_to_user(Member.t()) :: :ok
+  def send_invite_to_user(%Member{actor: %Actor{user_id: nil}}), do: :ok
 
   def send_invite_to_user(
         %Member{actor: %Actor{user_id: user_id}, parent: %Actor{} = group, role: :invited} =
-          member,
-        locale
+          member
       ) do
     with %User{email: email} = user <- Users.get_user!(user_id) do
-      locale = Map.get(user, :locale, locale)
+      locale = Map.get(user, :locale, "en")
       Gettext.put_locale(locale)
       %Actor{name: invited_by_name} = inviter = Actors.get_actor(member.invited_by_id)
 
diff --git a/lib/web/email/user.ex b/lib/web/email/user.ex
index 1ad29b711..413fb81b6 100644
--- a/lib/web/email/user.ex
+++ b/lib/web/email/user.ex
@@ -144,10 +144,11 @@ defmodule Mobilizon.Web.Email.User do
     end
   end
 
-  def send_email_reset_old_email(
-        %User{locale: user_locale, email: email, unconfirmed_email: unconfirmed_email} = _user,
-        _locale \\ "en"
-      ) do
+  def send_email_reset_old_email(%User{
+        locale: user_locale,
+        email: email,
+        unconfirmed_email: unconfirmed_email
+      }) do
     Gettext.put_locale(user_locale)
 
     subject =
@@ -163,14 +164,11 @@ defmodule Mobilizon.Web.Email.User do
     |> render(:email_changed_old)
   end
 
-  def send_email_reset_new_email(
-        %User{
-          locale: user_locale,
-          unconfirmed_email: unconfirmed_email,
-          confirmation_token: confirmation_token
-        } = _user,
-        _locale \\ "en"
-      ) do
+  def send_email_reset_new_email(%User{
+        locale: user_locale,
+        unconfirmed_email: unconfirmed_email,
+        confirmation_token: confirmation_token
+      }) do
     Gettext.put_locale(user_locale)
 
     subject =
diff --git a/lib/web/proxy/reverse_proxy.ex b/lib/web/proxy/reverse_proxy.ex
index 9bb83b5a7..9ac999386 100644
--- a/lib/web/proxy/reverse_proxy.ex
+++ b/lib/web/proxy/reverse_proxy.ex
@@ -17,9 +17,6 @@ defmodule Mobilizon.Web.ReverseProxy do
   @max_body_length :infinity
   @methods ~w(GET HEAD)
 
-  def max_read_duration_default, do: @max_read_duration
-  def default_cache_control_header, do: @default_cache_control_header
-
   @moduledoc """
   A reverse proxy.
 
diff --git a/lib/web/upload/mime.ex b/lib/web/upload/mime.ex
index 2f5478035..3f07a0e72 100644
--- a/lib/web/upload/mime.ex
+++ b/lib/web/upload/mime.ex
@@ -36,22 +36,6 @@ defmodule Mobilizon.Web.Upload.MIME do
     end)
   end
 
-  def bin_mime_type(binary, filename) do
-    with {:ok, content_type} <- bin_mime_type(binary),
-         filename <- fix_extension(filename, content_type) do
-      {:ok, content_type, filename}
-    end
-  end
-
-  @spec bin_mime_type(binary()) :: {:ok, String.t()} | {:error, :unknown_mime}
-  def bin_mime_type(<<head::binary-size(@read_bytes), _::binary>>) do
-    {:ok, check_mime_type(head)}
-  end
-
-  def bin_mime_type(_), do: {:error, :unknown_mime}
-
-  def mime_type(<<_::binary>>), do: {:ok, @default}
-
   defp fix_extension(filename, content_type)
        when is_binary(filename) and is_binary(content_type) do
     parts = String.split(filename, ".")
diff --git a/lib/web/upload/upload.ex b/lib/web/upload/upload.ex
index 5a4ec274c..f762b0f9e 100644
--- a/lib/web/upload/upload.ex
+++ b/lib/web/upload/upload.ex
@@ -94,19 +94,21 @@ defmodule Mobilizon.Web.Upload do
     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),
-         {:same_host, true} <- {:same_host, host == Endpoint.host()} do
-      Uploader.remove_file(opts.uploader, path)
-    else
-      %URI{} = _uri ->
-        {:error, "URL doesn't match pattern"}
+  @spec remove(String.t()) :: {:ok, String.t()} | {:error, atom}
+  def remove(url) do
+    %{uploader: uploader} = get_opts([])
 
-      {:same_host, _} ->
-        Logger.error("Media can't be deleted because its URL doesn't match current host")
+    case URI.parse(url) do
+      %URI{path: "/media/" <> path, host: host} ->
+        if host == Endpoint.host() do
+          Uploader.remove_file(uploader, path)
+        else
+          Logger.error("Media can't be deleted because its URL doesn't match current host")
+          {:error, :not_same_host}
+        end
+
+      %URI{} = _uri ->
+        {:error, :url_invalid}
     end
   end
 
diff --git a/test/mobilizon/actors/actors_test.exs b/test/mobilizon/actors/actors_test.exs
index 1a8417c0d..0e79e680a 100644
--- a/test/mobilizon/actors/actors_test.exs
+++ b/test/mobilizon/actors/actors_test.exs
@@ -413,12 +413,6 @@ defmodule Mobilizon.ActorsTest do
     @update_attrs %{source: "some updated source", type: "some updated type"}
     @invalid_attrs %{source: nil, type: nil}
 
-    test "list_bots/0 returns all bots" do
-      bot = insert(:bot)
-      bot_found_id = Actors.list_bots() |> hd |> Map.get(:id)
-      assert bot_found_id == bot.id
-    end
-
     test "get_bot!/1 returns the bot with given id" do
       %Bot{id: bot_id} = bot = insert(:bot)
       assert bot_id == Actors.get_bot!(bot.id).id
diff --git a/test/mobilizon/discussions_test.exs b/test/mobilizon/discussions_test.exs
index b1801ac70..a86cd95f1 100644
--- a/test/mobilizon/discussions_test.exs
+++ b/test/mobilizon/discussions_test.exs
@@ -12,12 +12,6 @@ defmodule Mobilizon.DiscussionsTest do
     @update_attrs %{text: "some updated text"}
     @invalid_attrs %{text: nil, url: nil}
 
-    test "list_comments/0 returns all comments" do
-      %Comment{id: comment_id} = insert(:comment)
-      comment_ids = Discussions.list_comments() |> Enum.map(& &1.id)
-      assert comment_ids == [comment_id]
-    end
-
     test "get_comment!/1 returns the comment with given id" do
       %Comment{id: comment_id} = insert(:comment)
       comment_fetched = Discussions.get_comment!(comment_id)
diff --git a/test/mobilizon/events/events_test.exs b/test/mobilizon/events/events_test.exs
index c9b6e9b55..efc0ae550 100644
--- a/test/mobilizon/events/events_test.exs
+++ b/test/mobilizon/events/events_test.exs
@@ -301,8 +301,8 @@ defmodule Mobilizon.EventsTest do
       assert {:ok, %TagRelation{}} =
                Events.create_tag_relation(%{tag_id: tag1_id, link_id: tag2_id})
 
-      assert Events.are_tags_linked(tag1, tag2)
-      assert Events.are_tags_linked(tag2, tag1)
+      assert Events.are_tags_linked?(tag1, tag2)
+      assert Events.are_tags_linked?(tag2, tag1)
     end
 
     test "create_tag_relation/1 with invalid data returns error changeset", %{
@@ -312,7 +312,7 @@ defmodule Mobilizon.EventsTest do
       assert {:error, %Ecto.Changeset{}} =
                Events.create_tag_relation(%{tag_id: nil, link_id: nil})
 
-      refute Events.are_tags_linked(tag1, tag2)
+      refute Events.are_tags_linked?(tag1, tag2)
     end
 
     test "delete_tag_relation/1 deletes the tag relation" do
diff --git a/test/mobilizon/media/media_test.exs b/test/mobilizon/media/media_test.exs
index 065ef99bf..d6b6bdfdd 100644
--- a/test/mobilizon/media/media_test.exs
+++ b/test/mobilizon/media/media_test.exs
@@ -17,12 +17,6 @@ defmodule Mobilizon.MediaTest do
         name: "something old"
       }
     }
-    @update_attrs %{
-      file: %{
-        url: "https://something.tld/media/something_updated",
-        name: "something new"
-      }
-    }
 
     test "get_media!/1 returns the media with given id" do
       media = insert(:media)
@@ -36,18 +30,6 @@ defmodule Mobilizon.MediaTest do
       assert media.file.name == "something old"
     end
 
-    test "update_media/2 with valid data updates the media" do
-      media = insert(:media)
-
-      assert {:ok, %Media{} = media} =
-               Medias.update_media(
-                 media,
-                 Map.put(@update_attrs, :actor_id, insert(:actor).id)
-               )
-
-      assert media.file.name == "something new"
-    end
-
     test "delete_media/1 deletes the media" do
       media = insert(:media)
 
diff --git a/test/mobilizon/users/users_test.exs b/test/mobilizon/users/users_test.exs
index 0a1ba31f6..f9e878fd0 100644
--- a/test/mobilizon/users/users_test.exs
+++ b/test/mobilizon/users/users_test.exs
@@ -128,12 +128,6 @@ defmodule Mobilizon.UsersTest do
       setting
     end
 
-    test "get_setting!/1 returns the setting with given id" do
-      %User{id: user_id} = insert(:user)
-      setting = setting_fixture(user_id: user_id)
-      assert Users.get_setting!(setting.user_id) == setting
-    end
-
     test "create_setting/1 with valid data creates a setting" do
       %User{id: user_id} = insert(:user)
 
@@ -155,18 +149,5 @@ defmodule Mobilizon.UsersTest do
       assert setting.timezone == "Atlantic/Cape_Verde"
       assert setting.notification_each_week == false
     end
-
-    test "delete_setting/1 deletes the setting" do
-      %User{id: user_id} = insert(:user)
-      setting = setting_fixture(user_id: user_id)
-      assert {:ok, %Setting{}} = Users.delete_setting(setting)
-      assert_raise Ecto.NoResultsError, fn -> Users.get_setting!(setting.user_id) end
-    end
-
-    test "change_setting/1 returns a setting changeset" do
-      %User{id: user_id} = insert(:user)
-      setting = setting_fixture(user_id: user_id)
-      assert %Ecto.Changeset{} = Users.change_setting(setting)
-    end
   end
 end
diff --git a/test/support/data_case.ex b/test/support/data_case.ex
index 0423a9777..0da48e944 100644
--- a/test/support/data_case.ex
+++ b/test/support/data_case.ex
@@ -74,6 +74,21 @@ defmodule Mobilizon.DataCase do
     :ok
   end
 
+  @doc """
+  POSIX-compliant check if command is available in the system
+
+  ## Examples
+      iex> command_available?("git")
+      true
+      iex> command_available?("wrongcmd")
+      false
+
+  """
+  @spec command_available?(String.t()) :: boolean()
+  def command_available?(command) do
+    match?({_output, 0}, System.cmd("sh", ["-c", "command -v #{command}"]))
+  end
+
   Mox.defmock(Mobilizon.Service.HTTP.ActivityPub.Mock, for: Tesla.Adapter)
 
   Mox.defmock(Mobilizon.Service.HTTP.GeospatialClient.Mock,
diff --git a/test/web/controllers/webfinger_controller_test.exs b/test/web/controllers/webfinger_controller_test.exs
index bfbe7d4c8..c4d2a8942 100644
--- a/test/web/controllers/webfinger_controller_test.exs
+++ b/test/web/controllers/webfinger_controller_test.exs
@@ -31,7 +31,7 @@ defmodule Mobilizon.Web.WebFingerControllerTest do
   test "GET /.well-known/webfinger with local actor", %{conn: conn} do
     %Actor{preferred_username: username} = actor = insert(:actor)
     conn = get(conn, "/.well-known/webfinger?resource=acct:#{username}@mobilizon.test")
-    assert json_response(conn, 200) == WebFinger.represent_actor(actor)
+    assert json_response(conn, 200) == WebFinger.represent_actor(actor, "JSON")
   end
 
   test "GET /.well-known/webfinger with non existent actor", %{conn: conn} do
diff --git a/test/web/upload/filter/exiftool_test.exs b/test/web/upload/filter/exiftool_test.exs
index 49f770c70..56dc1ce89 100644
--- a/test/web/upload/filter/exiftool_test.exs
+++ b/test/web/upload/filter/exiftool_test.exs
@@ -8,9 +8,15 @@ defmodule Mobilizon.Web.Upload.Filter.ExiftoolTest do
   alias Mobilizon.Web.Upload
   alias Mobilizon.Web.Upload.Filter
 
-  test "apply exiftool filter" do
-    assert Mobilizon.Utils.command_available?("exiftool")
+  setup do
+    if command_available?("exiftool") do
+      {:ok, skip: true}
+    else
+      :ok
+    end
+  end
 
+  test "apply exiftool filter" do
     File.cp!(
       "test/fixtures/DSCN0010.jpg",
       "test/fixtures/DSCN0010_tmp.jpg"

From 98449b9cfd07b0804c7a500f9d17ac4f4fc4fae0 Mon Sep 17 00:00:00 2001
From: Thomas Citharel <tcit@tcit.fr>
Date: Tue, 5 Oct 2021 16:04:50 +0200
Subject: [PATCH 3/3] Spec fixes

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
---
 lib/mobilizon/cli.ex                          |  4 ++--
 lib/service/http/activity_pub.ex              |  4 ++--
 .../rich_media/parsers/twitter_card.ex        | 19 +++++++++++++++----
 lib/web/controllers/feed_controller.ex        |  1 -
 4 files changed, 19 insertions(+), 9 deletions(-)

diff --git a/lib/mobilizon/cli.ex b/lib/mobilizon/cli.ex
index 56016063c..fe032a841 100644
--- a/lib/mobilizon/cli.ex
+++ b/lib/mobilizon/cli.ex
@@ -44,12 +44,12 @@ defmodule Mobilizon.CLI do
     end
   end
 
-  @spec migrate(String.t()) :: any()
+  @spec migrate(any()) :: any()
   defp migrate(args) do
     Migrate.run(args)
   end
 
-  @spec rollback(String.t()) :: any()
+  @spec rollback(any()) :: any()
   defp rollback(args) do
     Rollback.run(args)
   end
diff --git a/lib/service/http/activity_pub.ex b/lib/service/http/activity_pub.ex
index 57e4ea832..a84b28a89 100644
--- a/lib/service/http/activity_pub.ex
+++ b/lib/service/http/activity_pub.ex
@@ -28,12 +28,12 @@ defmodule Mobilizon.Service.HTTP.ActivityPub do
     Tesla.client(middleware, {adapter, opts})
   end
 
-  @spec get(Tesla.Client.t(), String.t()) :: Tesla.Env.t()
+  @spec get(Tesla.Client.t(), String.t()) :: Tesla.Env.result()
   def get(client, url) do
     Tesla.get(client, url)
   end
 
-  @spec post(Tesla.Client.t(), String.t(), map() | String.t()) :: Tesla.Env.t()
+  @spec post(Tesla.Client.t(), String.t(), map() | String.t()) :: Tesla.Env.result()
   def post(client, url, data) do
     Tesla.post(client, url, data)
   end
diff --git a/lib/service/rich_media/parsers/twitter_card.ex b/lib/service/rich_media/parsers/twitter_card.ex
index 6d2d5297d..70740d162 100644
--- a/lib/service/rich_media/parsers/twitter_card.ex
+++ b/lib/service/rich_media/parsers/twitter_card.ex
@@ -25,19 +25,29 @@ defmodule Mobilizon.Service.RichMedia.Parsers.TwitterCard do
     Logger.debug("Using Twitter card parser")
 
     with {:ok, data} <- parse_name_attrs(data, html),
-         {:ok, data} <- parse_property_attrs(data, html),
-         data <- transform_tags(data) do
+         {:ok, data} <- parse_property_attrs(data, html) do
+      data = transform_tags(data)
       Logger.debug("Data found with Twitter card parser")
       Logger.debug(inspect(data))
       data
     end
   end
 
+  @spec parse_name_attrs(map(), String.t()) :: {:ok, map()} | {:error, String.t()}
   defp parse_name_attrs(data, html) do
-    MetaTagsParser.parse(html, data, "twitter", %{}, :name, :content, [:"twitter:card"])
+    MetaTagsParser.parse(
+      html,
+      data,
+      "twitter",
+      "No twitter card metadata found",
+      :name,
+      :content,
+      [:"twitter:card"]
+    )
   end
 
-  defp parse_property_attrs({_, data}, html) do
+  @spec parse_property_attrs(map(), String.t()) :: {:ok, map()} | {:error, String.t()}
+  defp parse_property_attrs(data, html) do
     MetaTagsParser.parse(
       html,
       data,
@@ -49,6 +59,7 @@ defmodule Mobilizon.Service.RichMedia.Parsers.TwitterCard do
     )
   end
 
+  @spec transform_tags(map()) :: map()
   defp transform_tags(data) do
     data
     |> Enum.reject(fn {_, v} -> is_nil(v) end)
diff --git a/lib/web/controllers/feed_controller.ex b/lib/web/controllers/feed_controller.ex
index cf4646263..8e8e2b221 100644
--- a/lib/web/controllers/feed_controller.ex
+++ b/lib/web/controllers/feed_controller.ex
@@ -30,7 +30,6 @@ defmodule Mobilizon.Web.FeedController do
     return_data(conn, "ics", "event_" <> uuid, "event")
   end
 
-  @spec instance(Plug.Conn.t(), map()) :: Plug.Conn.t()
   def event(_conn, _) do
     {:error, :not_found}
   end