diff options
35 files changed, 1168 insertions, 634 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index e61b1d144..1a1de99a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,11 +5,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).  ## Unreleased -### Added -- Experimental websocket-based federation between Pleroma instances. -  ### Changed +- Search: Users are now findable by their urls.  - Renamed `:await_up_timeout` in `:connections_pool` namespace to `:connect_timeout`, old name is deprecated.  - Renamed `:timeout` in `pools` namespace to `:recv_timeout`, old name is deprecated.  - The `discoverable` field in the `User` struct will now add a NOINDEX metatag to profile pages when false. @@ -19,6 +17,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).  ### Added  - Media preview proxy (requires media proxy be enabled; see `:media_preview_proxy` config for more details).  - Pleroma API: Importing the mutes users from CSV files. +- Experimental websocket-based federation between Pleroma instances. +- Admin API: Importing emoji from a zip file  ### Removed @@ -28,6 +28,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).  - Removed `:managed_config` option. In practice, it was accidentally removed with 2.0.0 release when frontends were  switched to a new configuration mechanism, however it was not officially removed until now. +### Fixed + +- Add documented-but-missing chat pagination. +- Allow sending out emails again.  ## [2.1.2] - 2020-09-17 @@ -18,15 +18,16 @@ If you are running Linux (glibc or musl) on x86/arm, the recommended way to inst  ### From Source  If your platform is not supported, or you just want to be able to edit the source code easily, you may install Pleroma from source. -- [Debian-based](https://docs-develop.pleroma.social/backend/installation/debian_based_en/) -- [Debian-based (jp)](https://docs-develop.pleroma.social/backend/installation/debian_based_jp/)  - [Alpine Linux](https://docs-develop.pleroma.social/backend/installation/alpine_linux_en/)  - [Arch Linux](https://docs-develop.pleroma.social/backend/installation/arch_linux_en/) +- [CentOS 7](https://docs-develop.pleroma.social/backend/installation/centos7_en/) +- [Debian-based](https://docs-develop.pleroma.social/backend/installation/debian_based_en/) +- [Debian-based (jp)](https://docs-develop.pleroma.social/backend/installation/debian_based_jp/) +- [FreeBSD](https://docs-develop.pleroma.social/backend/installation/freebsd_en/)  - [Gentoo Linux](https://docs-develop.pleroma.social/backend/installation/gentoo_en/)  - [NetBSD](https://docs-develop.pleroma.social/backend/installation/netbsd_en/)  - [OpenBSD](https://docs-develop.pleroma.social/backend/installation/openbsd_en/)  - [OpenBSD (fi)](https://docs-develop.pleroma.social/backend/installation/openbsd_fi/) -- [CentOS 7](https://docs-develop.pleroma.social/backend/installation/centos7_en/)  ### OS/Distro packages  Currently Pleroma is not packaged by any OS/Distros, but if you want to package it for one, we can guide you through the process on our [community channels](#community-channels). If you want to change default options in your Pleroma package, please **discuss it with us first**. diff --git a/lib/pleroma/chat.ex b/lib/pleroma/chat.ex index 84f8806a0..28007cd9f 100644 --- a/lib/pleroma/chat.ex +++ b/lib/pleroma/chat.ex @@ -18,6 +18,7 @@ defmodule Pleroma.Chat do    It is a helper only, to make it easy to display a list of chats with other people, ordered by last bump. The actual messages are retrieved by querying the recipients of the ChatMessages.    """ +  @type t :: %__MODULE__{}    @primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true}    schema "chats" do @@ -41,16 +42,28 @@ defmodule Pleroma.Chat do      |> unique_constraint(:user_id, name: :chats_user_id_recipient_index)    end +  @spec get_by_user_and_id(User.t(), FlakeId.Ecto.CompatType.t()) :: +          {:ok, t()} | {:error, :not_found} +  def get_by_user_and_id(%User{id: user_id}, id) do +    from(c in __MODULE__, +      where: c.id == ^id, +      where: c.user_id == ^user_id +    ) +    |> Repo.find_resource() +  end + +  @spec get_by_id(FlakeId.Ecto.CompatType.t()) :: t() | nil    def get_by_id(id) do -    __MODULE__ -    |> Repo.get(id) +    Repo.get(__MODULE__, id)    end +  @spec get(FlakeId.Ecto.CompatType.t(), String.t()) :: t() | nil    def get(user_id, recipient) do -    __MODULE__ -    |> Repo.get_by(user_id: user_id, recipient: recipient) +    Repo.get_by(__MODULE__, user_id: user_id, recipient: recipient)    end +  @spec get_or_create(FlakeId.Ecto.CompatType.t(), String.t()) :: +          {:ok, t()} | {:error, Ecto.Changeset.t()}    def get_or_create(user_id, recipient) do      %__MODULE__{}      |> changeset(%{user_id: user_id, recipient: recipient}) @@ -62,6 +75,8 @@ defmodule Pleroma.Chat do      )    end +  @spec bump_or_create(FlakeId.Ecto.CompatType.t(), String.t()) :: +          {:ok, t()} | {:error, Ecto.Changeset.t()}    def bump_or_create(user_id, recipient) do      %__MODULE__{}      |> changeset(%{user_id: user_id, recipient: recipient}) diff --git a/lib/pleroma/emails/mailer.ex b/lib/pleroma/emails/mailer.ex index 8b1bdef75..5108c71c8 100644 --- a/lib/pleroma/emails/mailer.ex +++ b/lib/pleroma/emails/mailer.ex @@ -35,6 +35,11 @@ defmodule Pleroma.Emails.Mailer do    def deliver(email, config \\ [])    def deliver(email, config) do +    # temporary hackney fix until hackney max_connections bug is fixed +    # https://git.pleroma.social/pleroma/pleroma/-/issues/2101 +    email = +      Swoosh.Email.put_private(email, :hackney_options, ssl_options: [versions: [:"tlsv1.2"]]) +      case enabled?() do        true -> Swoosh.Mailer.deliver(email, parse_config(config))        false -> {:error, :deliveries_disabled} diff --git a/lib/pleroma/emoji.ex b/lib/pleroma/emoji.ex index f6016d73f..04936155b 100644 --- a/lib/pleroma/emoji.ex +++ b/lib/pleroma/emoji.ex @@ -56,6 +56,9 @@ defmodule Pleroma.Emoji do      end    end +  @spec exist?(String.t()) :: boolean() +  def exist?(name), do: not is_nil(get(name)) +    @doc "Returns all the emojos!!"    @spec get_all() :: list({String.t(), String.t(), String.t()})    def get_all do diff --git a/lib/pleroma/emoji/pack.ex b/lib/pleroma/emoji/pack.ex index d076ae312..0b3f8f00b 100644 --- a/lib/pleroma/emoji/pack.ex +++ b/lib/pleroma/emoji/pack.ex @@ -17,6 +17,7 @@ defmodule Pleroma.Emoji.Pack do          }    alias Pleroma.Emoji +  alias Pleroma.Emoji.Pack    @spec create(String.t()) :: {:ok, t()} | {:error, File.posix()} | {:error, :empty_values}    def create(name) do @@ -64,24 +65,93 @@ defmodule Pleroma.Emoji.Pack do      end    end -  @spec add_file(String.t(), String.t(), Path.t(), Plug.Upload.t() | String.t()) :: -          {:ok, t()} | {:error, File.posix() | atom()} -  def add_file(name, shortcode, filename, file) do -    with :ok <- validate_not_empty([name, shortcode, filename]), +  @spec unpack_zip_emojies(list(tuple())) :: list(map()) +  defp unpack_zip_emojies(zip_files) do +    Enum.reduce(zip_files, [], fn +      {_, path, s, _, _, _}, acc when elem(s, 2) == :regular -> +        with( +          filename <- Path.basename(path), +          shortcode <- Path.basename(filename, Path.extname(filename)), +          false <- Emoji.exist?(shortcode) +        ) do +          [%{path: path, filename: path, shortcode: shortcode} | acc] +        else +          _ -> acc +        end + +      _, acc -> +        acc +    end) +  end + +  @spec add_file(t(), String.t(), Path.t(), Plug.Upload.t()) :: +          {:ok, t()} +          | {:error, File.posix() | atom()} +  def add_file(%Pack{} = pack, _, _, %Plug.Upload{content_type: "application/zip"} = file) do +    with {:ok, zip_files} <- :zip.table(to_charlist(file.path)), +         [_ | _] = emojies <- unpack_zip_emojies(zip_files), +         {:ok, tmp_dir} <- Pleroma.Utils.tmp_dir("emoji") do +      try do +        {:ok, _emoji_files} = +          :zip.unzip( +            to_charlist(file.path), +            [{:file_list, Enum.map(emojies, & &1[:path])}, {:cwd, tmp_dir}] +          ) + +        {_, updated_pack} = +          Enum.map_reduce(emojies, pack, fn item, emoji_pack -> +            emoji_file = %Plug.Upload{ +              filename: item[:filename], +              path: Path.join(tmp_dir, item[:path]) +            } + +            {:ok, updated_pack} = +              do_add_file( +                emoji_pack, +                item[:shortcode], +                to_string(item[:filename]), +                emoji_file +              ) + +            {item, updated_pack} +          end) + +        Emoji.reload() + +        {:ok, updated_pack} +      after +        File.rm_rf(tmp_dir) +      end +    else +      {:error, _} = error -> +        error + +      _ -> +        {:ok, pack} +    end +  end + +  def add_file(%Pack{} = pack, shortcode, filename, %Plug.Upload{} = file) do +    with :ok <- validate_not_empty([shortcode, filename]),           :ok <- validate_emoji_not_exists(shortcode), -         {:ok, pack} <- load_pack(name), -         :ok <- save_file(file, pack, filename), -         {:ok, updated_pack} <- pack |> put_emoji(shortcode, filename) |> save_pack() do +         {:ok, updated_pack} <- do_add_file(pack, shortcode, filename, file) do        Emoji.reload()        {:ok, updated_pack}      end    end -  @spec delete_file(String.t(), String.t()) :: +  defp do_add_file(pack, shortcode, filename, file) do +    with :ok <- save_file(file, pack, filename) do +      pack +      |> put_emoji(shortcode, filename) +      |> save_pack() +    end +  end + +  @spec delete_file(t(), String.t()) ::            {:ok, t()} | {:error, File.posix() | atom()} -  def delete_file(name, shortcode) do -    with :ok <- validate_not_empty([name, shortcode]), -         {:ok, pack} <- load_pack(name), +  def delete_file(%Pack{} = pack, shortcode) do +    with :ok <- validate_not_empty([shortcode]),           :ok <- remove_file(pack, shortcode),           {:ok, updated_pack} <- pack |> delete_emoji(shortcode) |> save_pack() do        Emoji.reload() @@ -89,11 +159,10 @@ defmodule Pleroma.Emoji.Pack do      end    end -  @spec update_file(String.t(), String.t(), String.t(), String.t(), boolean()) :: +  @spec update_file(t(), String.t(), String.t(), String.t(), boolean()) ::            {:ok, t()} | {:error, File.posix() | atom()} -  def update_file(name, shortcode, new_shortcode, new_filename, force) do -    with :ok <- validate_not_empty([name, shortcode, new_shortcode, new_filename]), -         {:ok, pack} <- load_pack(name), +  def update_file(%Pack{} = pack, shortcode, new_shortcode, new_filename, force) do +    with :ok <- validate_not_empty([shortcode, new_shortcode, new_filename]),           {:ok, filename} <- get_filename(pack, shortcode),           :ok <- validate_emoji_not_exists(new_shortcode, force),           :ok <- rename_file(pack, filename, new_filename), @@ -243,9 +312,10 @@ defmodule Pleroma.Emoji.Pack do    defp validate_emoji_not_exists(_shortcode, true), do: :ok    defp validate_emoji_not_exists(shortcode, _) do -    case Emoji.get(shortcode) do -      nil -> :ok -      _ -> {:error, :already_exists} +    if Emoji.exist?(shortcode) do +      {:error, :already_exists} +    else +      :ok      end    end @@ -386,25 +456,18 @@ defmodule Pleroma.Emoji.Pack do      end    end -  defp save_file(file, pack, filename) do +  defp save_file(%Plug.Upload{path: upload_path}, pack, filename) do      file_path = Path.join(pack.path, filename)      create_subdirs(file_path) -    case file do -      %Plug.Upload{path: upload_path} -> -        # Copy the uploaded file from the temporary directory -        with {:ok, _} <- File.copy(upload_path, file_path), do: :ok - -      url when is_binary(url) -> -        # Download and write the file -        file_contents = Tesla.get!(url).body -        File.write(file_path, file_contents) +    with {:ok, _} <- File.copy(upload_path, file_path) do +      :ok      end    end    defp put_emoji(pack, shortcode, filename) do      files = Map.put(pack.files, shortcode, filename) -    %{pack | files: files} +    %{pack | files: files, files_count: length(Map.keys(files))}    end    defp delete_emoji(pack, shortcode) do diff --git a/lib/pleroma/user/query.ex b/lib/pleroma/user/query.ex index d618432ff..193b90d9d 100644 --- a/lib/pleroma/user/query.ex +++ b/lib/pleroma/user/query.ex @@ -47,6 +47,7 @@ defmodule Pleroma.User.Query do              is_moderator: boolean(),              super_users: boolean(),              invisible: boolean(), +            internal: boolean(),              followers: User.t(),              friends: User.t(),              recipients_from_activity: [String.t()], @@ -80,7 +81,9 @@ defmodule Pleroma.User.Query do    end    defp prepare_query(query, criteria) do -    Enum.reduce(criteria, query, &compose_query/2) +    criteria +    |> Map.put_new(:internal, false) +    |> Enum.reduce(query, &compose_query/2)    end    defp compose_query({key, value}, query) @@ -129,14 +132,12 @@ defmodule Pleroma.User.Query do    defp compose_query({:active, _}, query) do      User.restrict_deactivated(query) -    |> where([u], not is_nil(u.nickname))      |> where([u], u.approval_pending == false)    end    defp compose_query({:legacy_active, _}, query) do      query      |> where([u], fragment("not (?->'deactivated' @> 'true')", u.info)) -    |> where([u], not is_nil(u.nickname))    end    defp compose_query({:deactivated, false}, query) do @@ -145,7 +146,6 @@ defmodule Pleroma.User.Query do    defp compose_query({:deactivated, true}, query) do      where(query, [u], u.deactivated == ^true) -    |> where([u], not is_nil(u.nickname))    end    defp compose_query({:need_approval, _}, query) do @@ -199,10 +199,15 @@ defmodule Pleroma.User.Query do      limit(query, ^limit)    end +  defp compose_query({:internal, false}, query) do +    query +    |> where([u], not is_nil(u.nickname)) +    |> where([u], not like(u.nickname, "internal.%")) +  end +    defp compose_query(_unsupported_param, query), do: query    defp location_query(query, local) do      where(query, [u], u.local == ^local) -    |> where([u], not is_nil(u.nickname))    end  end diff --git a/lib/pleroma/user/search.ex b/lib/pleroma/user/search.ex index b8c648672..03f2c552f 100644 --- a/lib/pleroma/user/search.ex +++ b/lib/pleroma/user/search.ex @@ -3,8 +3,10 @@  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.User.Search do +  alias Pleroma.EctoType.ActivityPub.ObjectValidators.Uri, as: UriType    alias Pleroma.Pagination    alias Pleroma.User +    import Ecto.Query    @limit 20 @@ -19,16 +21,46 @@ defmodule Pleroma.User.Search do      query_string = format_query(query_string) -    maybe_resolve(resolve, for_user, query_string) +    # If this returns anything, it should bounce to the top +    maybe_resolved = maybe_resolve(resolve, for_user, query_string) + +    top_user_ids = +      [] +      |> maybe_add_resolved(maybe_resolved) +      |> maybe_add_ap_id_match(query_string) +      |> maybe_add_uri_match(query_string)      results =        query_string -      |> search_query(for_user, following) +      |> search_query(for_user, following, top_user_ids)        |> Pagination.fetch_paginated(%{"offset" => offset, "limit" => result_limit}, :offset)      results    end +  defp maybe_add_resolved(list, {:ok, %User{} = user}) do +    [user.id | list] +  end + +  defp maybe_add_resolved(list, _), do: list + +  defp maybe_add_ap_id_match(list, query) do +    if user = User.get_cached_by_ap_id(query) do +      [user.id | list] +    else +      list +    end +  end + +  defp maybe_add_uri_match(list, query) do +    with {:ok, query} <- UriType.cast(query), +         %User{} = user <- Pleroma.Repo.get_by(User, uri: query) do +      [user.id | list] +    else +      _ -> list +    end +  end +    defp format_query(query_string) do      # Strip the beginning @ off if there is a query      query_string = String.trim_leading(query_string, "@") @@ -47,7 +79,7 @@ defmodule Pleroma.User.Search do      end    end -  defp search_query(query_string, for_user, following) do +  defp search_query(query_string, for_user, following, top_user_ids) do      for_user      |> base_query(following)      |> filter_blocked_user(for_user) @@ -56,13 +88,20 @@ defmodule Pleroma.User.Search do      |> filter_internal_users()      |> filter_blocked_domains(for_user)      |> fts_search(query_string) +    |> select_top_users(top_user_ids)      |> trigram_rank(query_string) -    |> boost_search_rank(for_user) +    |> boost_search_rank(for_user, top_user_ids)      |> subquery()      |> order_by(desc: :search_rank)      |> maybe_restrict_local(for_user)    end +  defp select_top_users(query, top_user_ids) do +    from(u in query, +      or_where: u.id in ^top_user_ids +    ) +  end +    defp fts_search(query, query_string) do      query_string = to_tsquery(query_string) @@ -180,7 +219,7 @@ defmodule Pleroma.User.Search do    defp local_domain, do: Pleroma.Config.get([Pleroma.Web.Endpoint, :url, :host]) -  defp boost_search_rank(query, %User{} = for_user) do +  defp boost_search_rank(query, %User{} = for_user, top_user_ids) do      friends_ids = User.get_friends_ids(for_user)      followers_ids = User.get_followers_ids(for_user) @@ -192,6 +231,7 @@ defmodule Pleroma.User.Search do               CASE WHEN (?) THEN (?) * 1.5               WHEN (?) THEN (?) * 1.3               WHEN (?) THEN (?) * 1.1 +             WHEN (?) THEN 9001               ELSE (?) END              """,              u.id in ^friends_ids and u.id in ^followers_ids, @@ -200,11 +240,26 @@ defmodule Pleroma.User.Search do              u.search_rank,              u.id in ^followers_ids,              u.search_rank, +            u.id in ^top_user_ids,              u.search_rank            )        }      )    end -  defp boost_search_rank(query, _for_user), do: query +  defp boost_search_rank(query, _for_user, top_user_ids) do +    from(u in subquery(query), +      select_merge: %{ +        search_rank: +          fragment( +            """ +             CASE WHEN (?) THEN 9001 +             ELSE (?) END +            """, +            u.id in ^top_user_ids, +            u.search_rank +          ) +      } +    ) +  end  end diff --git a/lib/pleroma/utils.ex b/lib/pleroma/utils.ex index 21d1159be..e95766223 100644 --- a/lib/pleroma/utils.ex +++ b/lib/pleroma/utils.ex @@ -24,4 +24,24 @@ defmodule Pleroma.Utils do    def command_available?(command) do      match?({_output, 0}, System.cmd("sh", ["-c", "command -v #{command}"]))    end + +  @doc "creates the uniq temporary directory" +  @spec tmp_dir(String.t()) :: {:ok, String.t()} | {:error, :file.posix()} +  def tmp_dir(prefix \\ "") do +    sub_dir = +      [ +        prefix, +        Timex.to_unix(Timex.now()), +        :os.getpid(), +        String.downcase(Integer.to_string(:rand.uniform(0x100000000), 36)) +      ] +      |> Enum.join("-") + +    tmp_dir = Path.join(System.tmp_dir!(), sub_dir) + +    case File.mkdir(tmp_dir) do +      :ok -> {:ok, tmp_dir} +      error -> error +    end +  end  end diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index aa6a69463..d7dd9fe6b 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -515,15 +515,19 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do    end    def handle_incoming( -        %{"type" => "Create", "object" => %{"type" => objtype}} = data, +        %{"type" => "Create", "object" => %{"type" => objtype, "id" => obj_id}} = data,          _options        )        when objtype in ~w{Question Answer ChatMessage Audio Video Event Article} do      data = Map.put(data, "object", strip_internal_fields(data["object"]))      with {:ok, %User{}} <- ObjectValidator.fetch_actor(data), +         nil <- Activity.get_create_by_object_ap_id(obj_id),           {:ok, activity, _} <- Pipeline.common_pipeline(data, local: false) do        {:ok, activity} +    else +      %Activity{} = activity -> {:ok, activity} +      e -> e      end    end diff --git a/lib/pleroma/web/api_spec/operations/chat_operation.ex b/lib/pleroma/web/api_spec/operations/chat_operation.ex index 56554d5b4..0dcfdb354 100644 --- a/lib/pleroma/web/api_spec/operations/chat_operation.ex +++ b/lib/pleroma/web/api_spec/operations/chat_operation.ex @@ -158,7 +158,8 @@ defmodule Pleroma.Web.ApiSpec.ChatOperation do              "The messages in the chat",              "application/json",              chat_messages_response() -          ) +          ), +        404 => Operation.response("Not Found", "application/json", ApiError)        },        security: [          %{ diff --git a/lib/pleroma/web/api_spec/operations/pleroma_emoji_file_operation.ex b/lib/pleroma/web/api_spec/operations/pleroma_emoji_file_operation.ex new file mode 100644 index 000000000..efbfce75f --- /dev/null +++ b/lib/pleroma/web/api_spec/operations/pleroma_emoji_file_operation.ex @@ -0,0 +1,139 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.PleromaEmojiFileOperation do +  alias OpenApiSpex.Operation +  alias OpenApiSpex.Schema +  alias Pleroma.Web.ApiSpec.Schemas.ApiError + +  import Pleroma.Web.ApiSpec.Helpers + +  def open_api_operation(action) do +    operation = String.to_existing_atom("#{action}_operation") +    apply(__MODULE__, operation, []) +  end + +  def create_operation do +    %Operation{ +      tags: ["Emoji Packs"], +      summary: "Add new file to the pack", +      operationId: "PleromaAPI.EmojiPackController.add_file", +      security: [%{"oAuth" => ["write"]}], +      requestBody: request_body("Parameters", create_request(), required: true), +      parameters: [name_param()], +      responses: %{ +        200 => Operation.response("Files Object", "application/json", files_object()), +        422 => Operation.response("Unprocessable Entity", "application/json", ApiError), +        404 => Operation.response("Not Found", "application/json", ApiError), +        400 => Operation.response("Bad Request", "application/json", ApiError), +        409 => Operation.response("Conflict", "application/json", ApiError) +      } +    } +  end + +  defp create_request do +    %Schema{ +      type: :object, +      required: [:file], +      properties: %{ +        file: %Schema{ +          description: +            "File needs to be uploaded with the multipart request or link to remote file", +          anyOf: [ +            %Schema{type: :string, format: :binary}, +            %Schema{type: :string, format: :uri} +          ] +        }, +        shortcode: %Schema{ +          type: :string, +          description: +            "Shortcode for new emoji, must be unique for all emoji. If not sended, shortcode will be taken from original filename." +        }, +        filename: %Schema{ +          type: :string, +          description: +            "New emoji file name. If not specified will be taken from original filename." +        } +      } +    } +  end + +  def update_operation do +    %Operation{ +      tags: ["Emoji Packs"], +      summary: "Add new file to the pack", +      operationId: "PleromaAPI.EmojiPackController.update_file", +      security: [%{"oAuth" => ["write"]}], +      requestBody: request_body("Parameters", update_request(), required: true), +      parameters: [name_param()], +      responses: %{ +        200 => Operation.response("Files Object", "application/json", files_object()), +        404 => Operation.response("Not Found", "application/json", ApiError), +        400 => Operation.response("Bad Request", "application/json", ApiError), +        409 => Operation.response("Conflict", "application/json", ApiError), +        422 => Operation.response("Unprocessable Entity", "application/json", ApiError) +      } +    } +  end + +  defp update_request do +    %Schema{ +      type: :object, +      required: [:shortcode, :new_shortcode, :new_filename], +      properties: %{ +        shortcode: %Schema{ +          type: :string, +          description: "Emoji file shortcode" +        }, +        new_shortcode: %Schema{ +          type: :string, +          description: "New emoji file shortcode" +        }, +        new_filename: %Schema{ +          type: :string, +          description: "New filename for emoji file" +        }, +        force: %Schema{ +          type: :boolean, +          description: "With true value to overwrite existing emoji with new shortcode", +          default: false +        } +      } +    } +  end + +  def delete_operation do +    %Operation{ +      tags: ["Emoji Packs"], +      summary: "Delete emoji file from pack", +      operationId: "PleromaAPI.EmojiPackController.delete_file", +      security: [%{"oAuth" => ["write"]}], +      parameters: [ +        name_param(), +        Operation.parameter(:shortcode, :query, :string, "File shortcode", +          example: "cofe", +          required: true +        ) +      ], +      responses: %{ +        200 => Operation.response("Files Object", "application/json", files_object()), +        400 => Operation.response("Bad Request", "application/json", ApiError), +        404 => Operation.response("Not Found", "application/json", ApiError), +        422 => Operation.response("Unprocessable Entity", "application/json", ApiError) +      } +    } +  end + +  defp name_param do +    Operation.parameter(:name, :path, :string, "Pack Name", example: "cofe", required: true) +  end + +  defp files_object do +    %Schema{ +      type: :object, +      additionalProperties: %Schema{type: :string}, +      description: "Object with emoji names as keys and filenames as values" +    } +  end +end diff --git a/lib/pleroma/web/api_spec/operations/pleroma_emoji_pack_operation.ex b/lib/pleroma/web/api_spec/operations/pleroma_emoji_pack_operation.ex index b2b4f8713..59548af13 100644 --- a/lib/pleroma/web/api_spec/operations/pleroma_emoji_pack_operation.ex +++ b/lib/pleroma/web/api_spec/operations/pleroma_emoji_pack_operation.ex @@ -175,111 +175,6 @@ defmodule Pleroma.Web.ApiSpec.PleromaEmojiPackOperation do      }    end -  def add_file_operation do -    %Operation{ -      tags: ["Emoji Packs"], -      summary: "Add new file to the pack", -      operationId: "PleromaAPI.EmojiPackController.add_file", -      security: [%{"oAuth" => ["write"]}], -      requestBody: request_body("Parameters", add_file_request(), required: true), -      parameters: [name_param()], -      responses: %{ -        200 => Operation.response("Files Object", "application/json", files_object()), -        400 => Operation.response("Bad Request", "application/json", ApiError), -        409 => Operation.response("Conflict", "application/json", ApiError) -      } -    } -  end - -  defp add_file_request do -    %Schema{ -      type: :object, -      required: [:file], -      properties: %{ -        file: %Schema{ -          description: -            "File needs to be uploaded with the multipart request or link to remote file", -          anyOf: [ -            %Schema{type: :string, format: :binary}, -            %Schema{type: :string, format: :uri} -          ] -        }, -        shortcode: %Schema{ -          type: :string, -          description: -            "Shortcode for new emoji, must be unique for all emoji. If not sended, shortcode will be taken from original filename." -        }, -        filename: %Schema{ -          type: :string, -          description: -            "New emoji file name. If not specified will be taken from original filename." -        } -      } -    } -  end - -  def update_file_operation do -    %Operation{ -      tags: ["Emoji Packs"], -      summary: "Add new file to the pack", -      operationId: "PleromaAPI.EmojiPackController.update_file", -      security: [%{"oAuth" => ["write"]}], -      requestBody: request_body("Parameters", update_file_request(), required: true), -      parameters: [name_param()], -      responses: %{ -        200 => Operation.response("Files Object", "application/json", files_object()), -        400 => Operation.response("Bad Request", "application/json", ApiError), -        409 => Operation.response("Conflict", "application/json", ApiError) -      } -    } -  end - -  defp update_file_request do -    %Schema{ -      type: :object, -      required: [:shortcode, :new_shortcode, :new_filename], -      properties: %{ -        shortcode: %Schema{ -          type: :string, -          description: "Emoji file shortcode" -        }, -        new_shortcode: %Schema{ -          type: :string, -          description: "New emoji file shortcode" -        }, -        new_filename: %Schema{ -          type: :string, -          description: "New filename for emoji file" -        }, -        force: %Schema{ -          type: :boolean, -          description: "With true value to overwrite existing emoji with new shortcode", -          default: false -        } -      } -    } -  end - -  def delete_file_operation do -    %Operation{ -      tags: ["Emoji Packs"], -      summary: "Delete emoji file from pack", -      operationId: "PleromaAPI.EmojiPackController.delete_file", -      security: [%{"oAuth" => ["write"]}], -      parameters: [ -        name_param(), -        Operation.parameter(:shortcode, :query, :string, "File shortcode", -          example: "cofe", -          required: true -        ) -      ], -      responses: %{ -        200 => Operation.response("Files Object", "application/json", files_object()), -        400 => Operation.response("Bad Request", "application/json", ApiError) -      } -    } -  end -    def import_from_filesystem_operation do      %Operation{        tags: ["Emoji Packs"], diff --git a/lib/pleroma/web/controller_helper.ex b/lib/pleroma/web/controller_helper.ex index 6445966e0..69188a882 100644 --- a/lib/pleroma/web/controller_helper.ex +++ b/lib/pleroma/web/controller_helper.ex @@ -48,13 +48,13 @@ defmodule Pleroma.Web.ControllerHelper do    defp param_to_integer(_, default), do: default -  def add_link_headers(conn, activities, extra_params \\ %{}) +  def add_link_headers(conn, entries, extra_params \\ %{}) -  def add_link_headers(%{assigns: %{skip_link_headers: true}} = conn, _activities, _extra_params), +  def add_link_headers(%{assigns: %{skip_link_headers: true}} = conn, _entries, _extra_params),      do: conn -  def add_link_headers(conn, activities, extra_params) do -    case get_pagination_fields(conn, activities, extra_params) do +  def add_link_headers(conn, entries, extra_params) do +    case get_pagination_fields(conn, entries, extra_params) do        %{"next" => next_url, "prev" => prev_url} ->          put_resp_header(conn, "link", "<#{next_url}>; rel=\"next\", <#{prev_url}>; rel=\"prev\"") @@ -78,19 +78,15 @@ defmodule Pleroma.Web.ControllerHelper do      }    end -  def get_pagination_fields(conn, activities, extra_params \\ %{}) do -    case List.last(activities) do +  def get_pagination_fields(conn, entries, extra_params \\ %{}) do +    case List.last(entries) do        %{pagination_id: max_id} when not is_nil(max_id) -> -        %{pagination_id: min_id} = -          activities -          |> List.first() +        %{pagination_id: min_id} = List.first(entries)          build_pagination_fields(conn, min_id, max_id, extra_params)        %{id: max_id} -> -        %{id: min_id} = -          activities -          |> List.first() +        %{id: min_id} = List.first(entries)          build_pagination_fields(conn, min_id, max_id, extra_params) diff --git a/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex b/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex index 9f09550e1..57c0be5fe 100644 --- a/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex @@ -5,6 +5,8 @@  defmodule Pleroma.Web.MastodonAPI.AuthController do    use Pleroma.Web, :controller +  import Pleroma.Web.ControllerHelper, only: [json_response: 3] +    alias Pleroma.User    alias Pleroma.Web.OAuth.App    alias Pleroma.Web.OAuth.Authorization @@ -61,9 +63,7 @@ defmodule Pleroma.Web.MastodonAPI.AuthController do      TwitterAPI.password_reset(nickname_or_email) -    conn -    |> put_status(:no_content) -    |> json("") +    json_response(conn, :no_content, "")    end    defp local_mastodon_root_path(conn) do diff --git a/lib/pleroma/web/pleroma_api/controllers/chat_controller.ex b/lib/pleroma/web/pleroma_api/controllers/chat_controller.ex index 867cff829..e667831c5 100644 --- a/lib/pleroma/web/pleroma_api/controllers/chat_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/chat_controller.ex @@ -4,6 +4,8 @@  defmodule Pleroma.Web.PleromaAPI.ChatController do    use Pleroma.Web, :controller +  import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2] +    alias Pleroma.Activity    alias Pleroma.Chat    alias Pleroma.Chat.MessageReference @@ -47,7 +49,7 @@ defmodule Pleroma.Web.PleromaAPI.ChatController do        }) do      with %MessageReference{} = cm_ref <-             MessageReference.get_by_id(message_id), -         ^chat_id <- cm_ref.chat_id |> to_string(), +         ^chat_id <- to_string(cm_ref.chat_id),           %Chat{user_id: ^user_id} <- Chat.get_by_id(chat_id),           {:ok, _} <- remove_or_delete(cm_ref, user) do        conn @@ -68,18 +70,13 @@ defmodule Pleroma.Web.PleromaAPI.ChatController do      end    end -  defp remove_or_delete(cm_ref, _) do -    cm_ref -    |> MessageReference.delete() -  end +  defp remove_or_delete(cm_ref, _), do: MessageReference.delete(cm_ref)    def post_chat_message( -        %{body_params: params, assigns: %{user: %{id: user_id} = user}} = conn, -        %{ -          id: id -        } +        %{body_params: params, assigns: %{user: user}} = conn, +        %{id: id}        ) do -    with %Chat{} = chat <- Repo.get_by(Chat, id: id, user_id: user_id), +    with {:ok, chat} <- Chat.get_by_user_and_id(user, id),           %User{} = recipient <- User.get_cached_by_ap_id(chat.recipient),           {:ok, activity} <-             CommonAPI.post_chat_message(user, recipient, params[:content], @@ -103,13 +100,12 @@ defmodule Pleroma.Web.PleromaAPI.ChatController do      end    end -  def mark_message_as_read(%{assigns: %{user: %{id: user_id}}} = conn, %{ -        id: chat_id, -        message_id: message_id -      }) do -    with %MessageReference{} = cm_ref <- -           MessageReference.get_by_id(message_id), -         ^chat_id <- cm_ref.chat_id |> to_string(), +  def mark_message_as_read( +        %{assigns: %{user: %{id: user_id}}} = conn, +        %{id: chat_id, message_id: message_id} +      ) do +    with %MessageReference{} = cm_ref <- MessageReference.get_by_id(message_id), +         ^chat_id <- to_string(cm_ref.chat_id),           %Chat{user_id: ^user_id} <- Chat.get_by_id(chat_id),           {:ok, cm_ref} <- MessageReference.mark_as_read(cm_ref) do        conn @@ -119,36 +115,28 @@ defmodule Pleroma.Web.PleromaAPI.ChatController do    end    def mark_as_read( -        %{ -          body_params: %{last_read_id: last_read_id}, -          assigns: %{user: %{id: user_id}} -        } = conn, +        %{body_params: %{last_read_id: last_read_id}, assigns: %{user: user}} = conn,          %{id: id}        ) do -    with %Chat{} = chat <- Repo.get_by(Chat, id: id, user_id: user_id), -         {_n, _} <- -           MessageReference.set_all_seen_for_chat(chat, last_read_id) do +    with {:ok, chat} <- Chat.get_by_user_and_id(user, id), +         {_n, _} <- MessageReference.set_all_seen_for_chat(chat, last_read_id) do        conn        |> put_view(ChatView)        |> render("show.json", chat: chat)      end    end -  def messages(%{assigns: %{user: %{id: user_id}}} = conn, %{id: id} = params) do -    with %Chat{} = chat <- Repo.get_by(Chat, id: id, user_id: user_id) do -      cm_refs = +  def messages(%{assigns: %{user: user}} = conn, %{id: id} = params) do +    with {:ok, chat} <- Chat.get_by_user_and_id(user, id) do +      chat_message_refs =          chat          |> MessageReference.for_chat_query()          |> Pagination.fetch_paginated(params)        conn +      |> add_link_headers(chat_message_refs)        |> put_view(MessageReferenceView) -      |> render("index.json", chat_message_references: cm_refs) -    else -      _ -> -        conn -        |> put_status(:not_found) -        |> json(%{error: "not found"}) +      |> render("index.json", chat_message_references: chat_message_refs)      end    end @@ -165,8 +153,8 @@ defmodule Pleroma.Web.PleromaAPI.ChatController do      |> render("index.json", chats: chats)    end -  def create(%{assigns: %{user: user}} = conn, params) do -    with %User{ap_id: recipient} <- User.get_by_id(params[:id]), +  def create(%{assigns: %{user: user}} = conn, %{id: id}) do +    with %User{ap_id: recipient} <- User.get_cached_by_id(id),           {:ok, %Chat{} = chat} <- Chat.get_or_create(user.id, recipient) do        conn        |> put_view(ChatView) @@ -174,8 +162,8 @@ defmodule Pleroma.Web.PleromaAPI.ChatController do      end    end -  def show(%{assigns: %{user: user}} = conn, params) do -    with %Chat{} = chat <- Repo.get_by(Chat, user_id: user.id, id: params[:id]) do +  def show(%{assigns: %{user: user}} = conn, %{id: id}) do +    with {:ok, chat} <- Chat.get_by_user_and_id(user, id) do        conn        |> put_view(ChatView)        |> render("show.json", chat: chat) diff --git a/lib/pleroma/web/pleroma_api/controllers/emoji_file_controller.ex b/lib/pleroma/web/pleroma_api/controllers/emoji_file_controller.ex new file mode 100644 index 000000000..71c53df1d --- /dev/null +++ b/lib/pleroma/web/pleroma_api/controllers/emoji_file_controller.ex @@ -0,0 +1,133 @@ +defmodule Pleroma.Web.PleromaAPI.EmojiFileController do +  use Pleroma.Web, :controller + +  alias Pleroma.Emoji.Pack +  alias Pleroma.Web.ApiSpec + +  plug(Pleroma.Web.ApiSpec.CastAndValidate) + +  plug( +    Pleroma.Plugs.OAuthScopesPlug, +    %{scopes: ["write"], admin: true} +    when action in [ +           :create, +           :update, +           :delete +         ] +  ) + +  defdelegate open_api_operation(action), to: ApiSpec.PleromaEmojiFileOperation + +  def create(%{body_params: params} = conn, %{name: pack_name}) do +    filename = params[:filename] || get_filename(params[:file]) +    shortcode = params[:shortcode] || Path.basename(filename, Path.extname(filename)) + +    with {:ok, pack} <- Pack.load_pack(pack_name), +         {:ok, file} <- get_file(params[:file]), +         {:ok, pack} <- Pack.add_file(pack, shortcode, filename, file) do +      json(conn, pack.files) +    else +      {:error, :already_exists} -> +        conn +        |> put_status(:conflict) +        |> json(%{error: "An emoji with the \"#{shortcode}\" shortcode already exists"}) + +      {:error, :empty_values} -> +        conn +        |> put_status(:unprocessable_entity) +        |> json(%{error: "pack name, shortcode or filename cannot be empty"}) + +      {:error, _} = error -> +        handle_error(conn, error, %{pack_name: pack_name}) +    end +  end + +  def update(%{body_params: %{shortcode: shortcode} = params} = conn, %{name: pack_name}) do +    new_shortcode = params[:new_shortcode] +    new_filename = params[:new_filename] +    force = params[:force] + +    with {:ok, pack} <- Pack.load_pack(pack_name), +         {:ok, pack} <- Pack.update_file(pack, shortcode, new_shortcode, new_filename, force) do +      json(conn, pack.files) +    else +      {:error, :already_exists} -> +        conn +        |> put_status(:conflict) +        |> json(%{ +          error: +            "New shortcode \"#{new_shortcode}\" is already used. If you want to override emoji use 'force' option" +        }) + +      {:error, :empty_values} -> +        conn +        |> put_status(:unprocessable_entity) +        |> json(%{error: "new_shortcode or new_filename cannot be empty"}) + +      {:error, _} = error -> +        handle_error(conn, error, %{pack_name: pack_name, code: shortcode}) +    end +  end + +  def delete(conn, %{name: pack_name, shortcode: shortcode}) do +    with {:ok, pack} <- Pack.load_pack(pack_name), +         {:ok, pack} <- Pack.delete_file(pack, shortcode) do +      json(conn, pack.files) +    else +      {:error, :empty_values} -> +        conn +        |> put_status(:unprocessable_entity) +        |> json(%{error: "pack name or shortcode cannot be empty"}) + +      {:error, _} = error -> +        handle_error(conn, error, %{pack_name: pack_name, code: shortcode}) +    end +  end + +  defp handle_error(conn, {:error, :doesnt_exist}, %{code: emoji_code}) do +    conn +    |> put_status(:bad_request) +    |> json(%{error: "Emoji \"#{emoji_code}\" does not exist"}) +  end + +  defp handle_error(conn, {:error, :not_found}, %{pack_name: pack_name}) do +    conn +    |> put_status(:not_found) +    |> json(%{error: "pack \"#{pack_name}\" is not found"}) +  end + +  defp handle_error(conn, {:error, _}, _) do +    render_error( +      conn, +      :internal_server_error, +      "Unexpected error occurred while adding file to pack." +    ) +  end + +  defp get_filename(%Plug.Upload{filename: filename}), do: filename +  defp get_filename(url) when is_binary(url), do: Path.basename(url) + +  def get_file(%Plug.Upload{} = file), do: {:ok, file} + +  def get_file(url) when is_binary(url) do +    with {:ok, %Tesla.Env{body: body, status: code, headers: headers}} +         when code in 200..299 <- Pleroma.HTTP.get(url) do +      path = Plug.Upload.random_file!("emoji") + +      content_type = +        case List.keyfind(headers, "content-type", 0) do +          {"content-type", value} -> value +          nil -> nil +        end + +      File.write(path, body) + +      {:ok, +       %Plug.Upload{ +         filename: Path.basename(url), +         path: path, +         content_type: content_type +       }} +    end +  end +end diff --git a/lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex b/lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex index 657f46324..e3969fee1 100644 --- a/lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex @@ -14,10 +14,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackController do             :download,             :create,             :update, -           :delete, -           :add_file, -           :update_file, -           :delete_file +           :delete           ]    ) @@ -184,105 +181,6 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackController do      end    end -  def add_file(%{body_params: params} = conn, %{name: name}) do -    filename = params[:filename] || get_filename(params[:file]) -    shortcode = params[:shortcode] || Path.basename(filename, Path.extname(filename)) - -    with {:ok, pack} <- Pack.add_file(name, shortcode, filename, params[:file]) do -      json(conn, pack.files) -    else -      {:error, :already_exists} -> -        conn -        |> put_status(:conflict) -        |> json(%{error: "An emoji with the \"#{shortcode}\" shortcode already exists"}) - -      {:error, :not_found} -> -        conn -        |> put_status(:bad_request) -        |> json(%{error: "pack \"#{name}\" is not found"}) - -      {:error, :empty_values} -> -        conn -        |> put_status(:bad_request) -        |> json(%{error: "pack name, shortcode or filename cannot be empty"}) - -      {:error, _} -> -        render_error( -          conn, -          :internal_server_error, -          "Unexpected error occurred while adding file to pack." -        ) -    end -  end - -  def update_file(%{body_params: %{shortcode: shortcode} = params} = conn, %{name: name}) do -    new_shortcode = params[:new_shortcode] -    new_filename = params[:new_filename] -    force = params[:force] - -    with {:ok, pack} <- Pack.update_file(name, shortcode, new_shortcode, new_filename, force) do -      json(conn, pack.files) -    else -      {:error, :doesnt_exist} -> -        conn -        |> put_status(:bad_request) -        |> json(%{error: "Emoji \"#{shortcode}\" does not exist"}) - -      {:error, :already_exists} -> -        conn -        |> put_status(:conflict) -        |> json(%{ -          error: -            "New shortcode \"#{new_shortcode}\" is already used. If you want to override emoji use 'force' option" -        }) - -      {:error, :not_found} -> -        conn -        |> put_status(:bad_request) -        |> json(%{error: "pack \"#{name}\" is not found"}) - -      {:error, :empty_values} -> -        conn -        |> put_status(:bad_request) -        |> json(%{error: "new_shortcode or new_filename cannot be empty"}) - -      {:error, _} -> -        render_error( -          conn, -          :internal_server_error, -          "Unexpected error occurred while updating file in pack." -        ) -    end -  end - -  def delete_file(conn, %{name: name, shortcode: shortcode}) do -    with {:ok, pack} <- Pack.delete_file(name, shortcode) do -      json(conn, pack.files) -    else -      {:error, :doesnt_exist} -> -        conn -        |> put_status(:bad_request) -        |> json(%{error: "Emoji \"#{shortcode}\" does not exist"}) - -      {:error, :not_found} -> -        conn -        |> put_status(:bad_request) -        |> json(%{error: "pack \"#{name}\" is not found"}) - -      {:error, :empty_values} -> -        conn -        |> put_status(:bad_request) -        |> json(%{error: "pack name or shortcode cannot be empty"}) - -      {:error, _} -> -        render_error( -          conn, -          :internal_server_error, -          "Unexpected error occurred while removing file from pack." -        ) -    end -  end -    def import_from_filesystem(conn, _params) do      with {:ok, names} <- Pack.import_from_filesystem() do        json(conn, names) @@ -298,7 +196,4 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackController do          |> json(%{error: "Error accessing emoji pack directory"})      end    end - -  defp get_filename(%Plug.Upload{filename: filename}), do: filename -  defp get_filename(url) when is_binary(url), do: Path.basename(url)  end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index f924e1e91..42a9db21d 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -238,9 +238,9 @@ defmodule Pleroma.Web.Router do        patch("/:name", EmojiPackController, :update)        delete("/:name", EmojiPackController, :delete) -      post("/:name/files", EmojiPackController, :add_file) -      patch("/:name/files", EmojiPackController, :update_file) -      delete("/:name/files", EmojiPackController, :delete_file) +      post("/:name/files", EmojiFileController, :create) +      patch("/:name/files", EmojiFileController, :update) +      delete("/:name/files", EmojiFileController, :delete)      end      # Pack info / downloading diff --git a/priv/repo/migrations/20200925065249_make_user_ids_ci.exs b/priv/repo/migrations/20200925065249_make_user_ids_ci.exs new file mode 100644 index 000000000..b7305f137 --- /dev/null +++ b/priv/repo/migrations/20200925065249_make_user_ids_ci.exs @@ -0,0 +1,19 @@ +defmodule Pleroma.Repo.Migrations.MakeUserIdsCI do +  use Ecto.Migration + +  def up do +    alter table(:users) do +      modify(:uri, :citext) +    end + +    create(unique_index(:users, :uri)) +  end + +  def don do +    drop(unique_index(:users, :uri)) + +    alter table(:users) do +      modify(:uri, :text) +    end +  end +end diff --git a/test/emoji/pack_test.exs b/test/emoji/pack_test.exs new file mode 100644 index 000000000..70d1eaa1b --- /dev/null +++ b/test/emoji/pack_test.exs @@ -0,0 +1,93 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Emoji.PackTest do +  use ExUnit.Case, async: true +  alias Pleroma.Emoji.Pack + +  @emoji_path Path.join( +                Pleroma.Config.get!([:instance, :static_dir]), +                "emoji" +              ) + +  setup do +    pack_path = Path.join(@emoji_path, "dump_pack") +    File.mkdir(pack_path) + +    File.write!(Path.join(pack_path, "pack.json"), """ +    { +    "files": { }, +    "pack": { +    "description": "Dump pack", "homepage": "https://pleroma.social", +    "license": "Test license", "share-files": true +    }} +    """) + +    {:ok, pack} = Pleroma.Emoji.Pack.load_pack("dump_pack") + +    on_exit(fn -> +      File.rm_rf!(pack_path) +    end) + +    {:ok, pack: pack} +  end + +  describe "add_file/4" do +    test "add emojies from zip file", %{pack: pack} do +      file = %Plug.Upload{ +        content_type: "application/zip", +        filename: "emojis.zip", +        path: Path.absname("test/fixtures/emojis.zip") +      } + +      {:ok, updated_pack} = Pack.add_file(pack, nil, nil, file) + +      assert updated_pack.files == %{ +               "a_trusted_friend-128" => "128px/a_trusted_friend-128.png", +               "auroraborealis" => "auroraborealis.png", +               "baby_in_a_box" => "1000px/baby_in_a_box.png", +               "bear" => "1000px/bear.png", +               "bear-128" => "128px/bear-128.png" +             } + +      assert updated_pack.files_count == 5 +    end +  end + +  test "returns error when zip file is bad", %{pack: pack} do +    file = %Plug.Upload{ +      content_type: "application/zip", +      filename: "emojis.zip", +      path: Path.absname("test/instance_static/emoji/test_pack/blank.png") +    } + +    assert Pack.add_file(pack, nil, nil, file) == {:error, :einval} +  end + +  test "returns pack when zip file is empty", %{pack: pack} do +    file = %Plug.Upload{ +      content_type: "application/zip", +      filename: "emojis.zip", +      path: Path.absname("test/fixtures/empty.zip") +    } + +    {:ok, updated_pack} = Pack.add_file(pack, nil, nil, file) +    assert updated_pack == pack +  end + +  test "add emoji file", %{pack: pack} do +    file = %Plug.Upload{ +      filename: "blank.png", +      path: "#{@emoji_path}/test_pack/blank.png" +    } + +    {:ok, updated_pack} = Pack.add_file(pack, "test_blank", "test_blank.png", file) + +    assert updated_pack.files == %{ +             "test_blank" => "test_blank.png" +           } + +    assert updated_pack.files_count == 1 +  end +end diff --git a/test/emoji_test.exs b/test/emoji_test.exs index b36047578..1dd3c58c6 100644 --- a/test/emoji_test.exs +++ b/test/emoji_test.exs @@ -3,7 +3,7 @@  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.EmojiTest do -  use ExUnit.Case, async: true +  use ExUnit.Case    alias Pleroma.Emoji    describe "is_unicode_emoji?/1" do diff --git a/test/fixtures/emojis.zip b/test/fixtures/emojis.zip Binary files differnew file mode 100644 index 000000000..d7fc4732b --- /dev/null +++ b/test/fixtures/emojis.zip diff --git a/test/fixtures/empty.zip b/test/fixtures/empty.zip Binary files differnew file mode 100644 index 000000000..15cb0ecb3 --- /dev/null +++ b/test/fixtures/empty.zip diff --git a/test/user/query_test.exs b/test/user/query_test.exs new file mode 100644 index 000000000..e2f5c7d81 --- /dev/null +++ b/test/user/query_test.exs @@ -0,0 +1,37 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.User.QueryTest do +  use Pleroma.DataCase, async: true + +  alias Pleroma.Repo +  alias Pleroma.User +  alias Pleroma.User.Query +  alias Pleroma.Web.ActivityPub.InternalFetchActor + +  import Pleroma.Factory + +  describe "internal users" do +    test "it filters out internal users by default" do +      %User{nickname: "internal.fetch"} = InternalFetchActor.get_actor() + +      assert [_user] = User |> Repo.all() +      assert [] == %{} |> Query.build() |> Repo.all() +    end + +    test "it filters out users without nickname by default" do +      insert(:user, %{nickname: nil}) + +      assert [_user] = User |> Repo.all() +      assert [] == %{} |> Query.build() |> Repo.all() +    end + +    test "it returns internal users when enabled" do +      %User{nickname: "internal.fetch"} = InternalFetchActor.get_actor() +      insert(:user, %{nickname: nil}) + +      assert %{internal: true} |> Query.build() |> Repo.aggregate(:count) == 2 +    end +  end +end diff --git a/test/user_search_test.exs b/test/user_search_test.exs index 8529ce6db..b99a77b57 100644 --- a/test/user_search_test.exs +++ b/test/user_search_test.exs @@ -17,6 +17,40 @@ defmodule Pleroma.UserSearchTest do    describe "User.search" do      setup do: clear_config([:instance, :limit_to_local_content]) +    test "returns a resolved user as the first result" do +      Pleroma.Config.put([:instance, :limit_to_local_content], false) +      user = insert(:user, %{nickname: "no_relation", ap_id: "https://lain.com/users/lain"}) +      _user = insert(:user, %{nickname: "com_user"}) + +      [first_user, _second_user] = User.search("https://lain.com/users/lain", resolve: true) + +      assert first_user.id == user.id +    end + +    test "returns a user with matching ap_id as the first result" do +      user = insert(:user, %{nickname: "no_relation", ap_id: "https://lain.com/users/lain"}) +      _user = insert(:user, %{nickname: "com_user"}) + +      [first_user, _second_user] = User.search("https://lain.com/users/lain") + +      assert first_user.id == user.id +    end + +    test "returns a user with matching uri as the first result" do +      user = +        insert(:user, %{ +          nickname: "no_relation", +          ap_id: "https://lain.com/users/lain", +          uri: "https://lain.com/@Lain" +        }) + +      _user = insert(:user, %{nickname: "com_user"}) + +      [first_user, _second_user] = User.search("https://lain.com/@lain") + +      assert first_user.id == user.id +    end +      test "excludes invisible users from results" do        user = insert(:user, %{nickname: "john t1000"})        insert(:user, %{invisible: true, nickname: "john t800"}) diff --git a/test/user_test.exs b/test/user_test.exs index cceb14eb9..d506f7047 100644 --- a/test/user_test.exs +++ b/test/user_test.exs @@ -509,7 +509,12 @@ defmodule Pleroma.UserTest do        cng = User.register_changeset(%User{}, @full_user_data)        {:ok, registered_user} = User.register(cng)        ObanHelpers.perform_all() -      assert_email_sent(Pleroma.Emails.UserEmail.account_confirmation_email(registered_user)) + +      Pleroma.Emails.UserEmail.account_confirmation_email(registered_user) +      # temporary hackney fix until hackney max_connections bug is fixed +      # https://git.pleroma.social/pleroma/pleroma/-/issues/2101 +      |> Swoosh.Email.put_private(:hackney_options, ssl_options: [versions: [:"tlsv1.2"]]) +      |> assert_email_sent()      end      test "it requires an email, name, nickname and password, bio is optional when account_activation_required is enabled" do diff --git a/test/utils_test.exs b/test/utils_test.exs new file mode 100644 index 000000000..460f7e0b5 --- /dev/null +++ b/test/utils_test.exs @@ -0,0 +1,15 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.UtilsTest do +  use ExUnit.Case, async: true + +  describe "tmp_dir/1" do +    test "returns unique temporary directory" do +      {:ok, path} = Pleroma.Utils.tmp_dir("emoji") +      assert path =~ ~r/\/emoji-(.*)-#{:os.getpid()}-(.*)/ +      File.rm_rf(path) +    end +  end +end diff --git a/test/web/activity_pub/transmogrifier/question_handling_test.exs b/test/web/activity_pub/transmogrifier/question_handling_test.exs index 74ee79543..d2822ce75 100644 --- a/test/web/activity_pub/transmogrifier/question_handling_test.exs +++ b/test/web/activity_pub/transmogrifier/question_handling_test.exs @@ -157,12 +157,12 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.QuestionHandlingTest do             }    end -  test "returns an error if received a second time" do +  test "returns same activity if received a second time" do      data = File.read!("test/fixtures/mastodon-question-activity.json") |> Poison.decode!()      assert {:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data) -    assert {:error, {:validate_object, {:error, _}}} = Transmogrifier.handle_incoming(data) +    assert {:ok, ^activity} = Transmogrifier.handle_incoming(data)    end    test "accepts a Question with no content" do diff --git a/test/web/admin_api/controllers/admin_api_controller_test.exs b/test/web/admin_api/controllers/admin_api_controller_test.exs index e4d3512de..cba6b43d3 100644 --- a/test/web/admin_api/controllers/admin_api_controller_test.exs +++ b/test/web/admin_api/controllers/admin_api_controller_test.exs @@ -1977,7 +1977,12 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do                 }"        ObanHelpers.perform_all() -      assert_email_sent(Pleroma.Emails.UserEmail.account_confirmation_email(first_user)) + +      Pleroma.Emails.UserEmail.account_confirmation_email(first_user) +      # temporary hackney fix until hackney max_connections bug is fixed +      # https://git.pleroma.social/pleroma/pleroma/-/issues/2101 +      |> Swoosh.Email.put_private(:hackney_options, ssl_options: [versions: [:"tlsv1.2"]]) +      |> assert_email_sent()      end    end diff --git a/test/web/common_api/common_api_test.exs b/test/web/common_api/common_api_test.exs index 2eab64e8b..e34f5a49b 100644 --- a/test/web/common_api/common_api_test.exs +++ b/test/web/common_api/common_api_test.exs @@ -29,6 +29,23 @@ defmodule Pleroma.Web.CommonAPITest do    setup do: clear_config([:instance, :limit])    setup do: clear_config([:instance, :max_pinned_statuses]) +  describe "posting polls" do +    test "it posts a poll" do +      user = insert(:user) + +      {:ok, activity} = +        CommonAPI.post(user, %{ +          status: "who is the best", +          poll: %{expires_in: 600, options: ["reimu", "marisa"]} +        }) + +      object = Object.normalize(activity) + +      assert object.data["type"] == "Question" +      assert object.data["oneOf"] |> length() == 2 +    end +  end +    describe "blocking" do      setup do        blocker = insert(:user) diff --git a/test/web/mastodon_api/controllers/auth_controller_test.exs b/test/web/mastodon_api/controllers/auth_controller_test.exs index 4fa95fce1..bf2438fe2 100644 --- a/test/web/mastodon_api/controllers/auth_controller_test.exs +++ b/test/web/mastodon_api/controllers/auth_controller_test.exs @@ -61,7 +61,7 @@ defmodule Pleroma.Web.MastodonAPI.AuthControllerTest do      end      test "it returns 204", %{conn: conn} do -      assert json_response(conn, :no_content) +      assert empty_json_response(conn)      end      test "it creates a PasswordResetToken record for user", %{user: user} do @@ -91,7 +91,7 @@ defmodule Pleroma.Web.MastodonAPI.AuthControllerTest do        assert conn               |> post("/auth/password?nickname=#{user.nickname}") -             |> json_response(:no_content) +             |> empty_json_response()        ObanHelpers.perform_all()        token_record = Repo.get_by(Pleroma.PasswordResetToken, user_id: user.id) @@ -112,7 +112,7 @@ defmodule Pleroma.Web.MastodonAPI.AuthControllerTest do        assert conn               |> post("/auth/password?nickname=#{user.nickname}") -             |> json_response(:no_content) +             |> empty_json_response()      end    end @@ -125,24 +125,21 @@ defmodule Pleroma.Web.MastodonAPI.AuthControllerTest do      test "it returns 204 when user is not found", %{conn: conn, user: user} do        conn = post(conn, "/auth/password?email=nonexisting_#{user.email}") -      assert conn -             |> json_response(:no_content) +      assert empty_json_response(conn)      end      test "it returns 204 when user is not local", %{conn: conn, user: user} do        {:ok, user} = Repo.update(Ecto.Changeset.change(user, local: false))        conn = post(conn, "/auth/password?email=#{user.email}") -      assert conn -             |> json_response(:no_content) +      assert empty_json_response(conn)      end      test "it returns 204 when user is deactivated", %{conn: conn, user: user} do        {:ok, user} = Repo.update(Ecto.Changeset.change(user, deactivated: true, local: true))        conn = post(conn, "/auth/password?email=#{user.email}") -      assert conn -             |> json_response(:no_content) +      assert empty_json_response(conn)      end    end diff --git a/test/web/pleroma_api/controllers/chat_controller_test.exs b/test/web/pleroma_api/controllers/chat_controller_test.exs index 44a78a738..11d5ba373 100644 --- a/test/web/pleroma_api/controllers/chat_controller_test.exs +++ b/test/web/pleroma_api/controllers/chat_controller_test.exs @@ -2,7 +2,7 @@  # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.PleromaAPI.ChatControllerTest do -  use Pleroma.Web.ConnCase, async: true +  use Pleroma.Web.ConnCase    alias Pleroma.Chat    alias Pleroma.Chat.MessageReference @@ -201,17 +201,39 @@ defmodule Pleroma.Web.PleromaAPI.ChatControllerTest do        chat = Chat.get(user.id, recipient.ap_id) -      result = -        conn -        |> get("/api/v1/pleroma/chats/#{chat.id}/messages") -        |> json_response_and_validate_schema(200) +      response = get(conn, "/api/v1/pleroma/chats/#{chat.id}/messages") +      result = json_response_and_validate_schema(response, 200) + +      [next, prev] = get_resp_header(response, "link") |> hd() |> String.split(", ") +      api_endpoint = "/api/v1/pleroma/chats/" + +      assert String.match?( +               next, +               ~r(#{api_endpoint}.*/messages\?id=.*&limit=\d+&max_id=.*; rel=\"next\"$) +             ) + +      assert String.match?( +               prev, +               ~r(#{api_endpoint}.*/messages\?id=.*&limit=\d+&min_id=.*; rel=\"prev\"$) +             )        assert length(result) == 20 -      result = -        conn -        |> get("/api/v1/pleroma/chats/#{chat.id}/messages?max_id=#{List.last(result)["id"]}") -        |> json_response_and_validate_schema(200) +      response = +        get(conn, "/api/v1/pleroma/chats/#{chat.id}/messages?max_id=#{List.last(result)["id"]}") + +      result = json_response_and_validate_schema(response, 200) +      [next, prev] = get_resp_header(response, "link") |> hd() |> String.split(", ") + +      assert String.match?( +               next, +               ~r(#{api_endpoint}.*/messages\?id=.*&limit=\d+&max_id=.*; rel=\"next\"$) +             ) + +      assert String.match?( +               prev, +               ~r(#{api_endpoint}.*/messages\?id=.*&limit=\d+&max_id=.*&min_id=.*; rel=\"prev\"$) +             )        assert length(result) == 10      end @@ -240,12 +262,10 @@ defmodule Pleroma.Web.PleromaAPI.ChatControllerTest do        assert length(result) == 3        # Trying to get the chat of a different user -      result = -        conn -        |> assign(:user, other_user) -        |> get("/api/v1/pleroma/chats/#{chat.id}/messages") - -      assert result |> json_response(404) +      conn +      |> assign(:user, other_user) +      |> get("/api/v1/pleroma/chats/#{chat.id}/messages") +      |> json_response_and_validate_schema(404)      end    end diff --git a/test/web/pleroma_api/controllers/emoji_file_controller_test.exs b/test/web/pleroma_api/controllers/emoji_file_controller_test.exs new file mode 100644 index 000000000..39b4e1dac --- /dev/null +++ b/test/web/pleroma_api/controllers/emoji_file_controller_test.exs @@ -0,0 +1,357 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.PleromaAPI.EmojiFileControllerTest do +  use Pleroma.Web.ConnCase + +  import Tesla.Mock +  import Pleroma.Factory + +  @emoji_path Path.join( +                Pleroma.Config.get!([:instance, :static_dir]), +                "emoji" +              ) +  setup do: clear_config([:auth, :enforce_oauth_admin_scope_usage], false) + +  setup do: clear_config([:instance, :public], true) + +  setup do +    admin = insert(:user, is_admin: true) +    token = insert(:oauth_admin_token, user: admin) + +    admin_conn = +      build_conn() +      |> assign(:user, admin) +      |> assign(:token, token) + +    Pleroma.Emoji.reload() +    {:ok, %{admin_conn: admin_conn}} +  end + +  describe "POST/PATCH/DELETE /api/pleroma/emoji/packs/:name/files" do +    setup do +      pack_file = "#{@emoji_path}/test_pack/pack.json" +      original_content = File.read!(pack_file) + +      on_exit(fn -> +        File.write!(pack_file, original_content) +      end) + +      :ok +    end + +    test "upload zip file with emojies", %{admin_conn: admin_conn} do +      on_exit(fn -> +        [ +          "128px/a_trusted_friend-128.png", +          "auroraborealis.png", +          "1000px/baby_in_a_box.png", +          "1000px/bear.png", +          "128px/bear-128.png" +        ] +        |> Enum.each(fn path -> File.rm_rf!("#{@emoji_path}/test_pack/#{path}") end) +      end) + +      resp = +        admin_conn +        |> put_req_header("content-type", "multipart/form-data") +        |> post("/api/pleroma/emoji/packs/test_pack/files", %{ +          file: %Plug.Upload{ +            content_type: "application/zip", +            filename: "emojis.zip", +            path: Path.absname("test/fixtures/emojis.zip") +          } +        }) +        |> json_response_and_validate_schema(200) + +      assert resp == %{ +               "a_trusted_friend-128" => "128px/a_trusted_friend-128.png", +               "auroraborealis" => "auroraborealis.png", +               "baby_in_a_box" => "1000px/baby_in_a_box.png", +               "bear" => "1000px/bear.png", +               "bear-128" => "128px/bear-128.png", +               "blank" => "blank.png", +               "blank2" => "blank2.png" +             } + +      Enum.each(Map.values(resp), fn path -> +        assert File.exists?("#{@emoji_path}/test_pack/#{path}") +      end) +    end + +    test "create shortcode exists", %{admin_conn: admin_conn} do +      assert admin_conn +             |> put_req_header("content-type", "multipart/form-data") +             |> post("/api/pleroma/emoji/packs/test_pack/files", %{ +               shortcode: "blank", +               filename: "dir/blank.png", +               file: %Plug.Upload{ +                 filename: "blank.png", +                 path: "#{@emoji_path}/test_pack/blank.png" +               } +             }) +             |> json_response_and_validate_schema(:conflict) == %{ +               "error" => "An emoji with the \"blank\" shortcode already exists" +             } +    end + +    test "don't rewrite old emoji", %{admin_conn: admin_conn} do +      on_exit(fn -> File.rm_rf!("#{@emoji_path}/test_pack/dir/") end) + +      assert admin_conn +             |> put_req_header("content-type", "multipart/form-data") +             |> post("/api/pleroma/emoji/packs/test_pack/files", %{ +               shortcode: "blank3", +               filename: "dir/blank.png", +               file: %Plug.Upload{ +                 filename: "blank.png", +                 path: "#{@emoji_path}/test_pack/blank.png" +               } +             }) +             |> json_response_and_validate_schema(200) == %{ +               "blank" => "blank.png", +               "blank2" => "blank2.png", +               "blank3" => "dir/blank.png" +             } + +      assert File.exists?("#{@emoji_path}/test_pack/dir/blank.png") + +      assert admin_conn +             |> put_req_header("content-type", "multipart/form-data") +             |> patch("/api/pleroma/emoji/packs/test_pack/files", %{ +               shortcode: "blank", +               new_shortcode: "blank2", +               new_filename: "dir_2/blank_3.png" +             }) +             |> json_response_and_validate_schema(:conflict) == %{ +               "error" => +                 "New shortcode \"blank2\" is already used. If you want to override emoji use 'force' option" +             } +    end + +    test "rewrite old emoji with force option", %{admin_conn: admin_conn} do +      on_exit(fn -> File.rm_rf!("#{@emoji_path}/test_pack/dir_2/") end) + +      assert admin_conn +             |> put_req_header("content-type", "multipart/form-data") +             |> post("/api/pleroma/emoji/packs/test_pack/files", %{ +               shortcode: "blank3", +               filename: "dir/blank.png", +               file: %Plug.Upload{ +                 filename: "blank.png", +                 path: "#{@emoji_path}/test_pack/blank.png" +               } +             }) +             |> json_response_and_validate_schema(200) == %{ +               "blank" => "blank.png", +               "blank2" => "blank2.png", +               "blank3" => "dir/blank.png" +             } + +      assert File.exists?("#{@emoji_path}/test_pack/dir/blank.png") + +      assert admin_conn +             |> put_req_header("content-type", "multipart/form-data") +             |> patch("/api/pleroma/emoji/packs/test_pack/files", %{ +               shortcode: "blank3", +               new_shortcode: "blank4", +               new_filename: "dir_2/blank_3.png", +               force: true +             }) +             |> json_response_and_validate_schema(200) == %{ +               "blank" => "blank.png", +               "blank2" => "blank2.png", +               "blank4" => "dir_2/blank_3.png" +             } + +      assert File.exists?("#{@emoji_path}/test_pack/dir_2/blank_3.png") +    end + +    test "with empty filename", %{admin_conn: admin_conn} do +      assert admin_conn +             |> put_req_header("content-type", "multipart/form-data") +             |> post("/api/pleroma/emoji/packs/test_pack/files", %{ +               shortcode: "blank2", +               filename: "", +               file: %Plug.Upload{ +                 filename: "blank.png", +                 path: "#{@emoji_path}/test_pack/blank.png" +               } +             }) +             |> json_response_and_validate_schema(422) == %{ +               "error" => "pack name, shortcode or filename cannot be empty" +             } +    end + +    test "add file with not loaded pack", %{admin_conn: admin_conn} do +      assert admin_conn +             |> put_req_header("content-type", "multipart/form-data") +             |> post("/api/pleroma/emoji/packs/not_loaded/files", %{ +               shortcode: "blank3", +               filename: "dir/blank.png", +               file: %Plug.Upload{ +                 filename: "blank.png", +                 path: "#{@emoji_path}/test_pack/blank.png" +               } +             }) +             |> json_response_and_validate_schema(:not_found) == %{ +               "error" => "pack \"not_loaded\" is not found" +             } +    end + +    test "remove file with not loaded pack", %{admin_conn: admin_conn} do +      assert admin_conn +             |> delete("/api/pleroma/emoji/packs/not_loaded/files?shortcode=blank3") +             |> json_response_and_validate_schema(:not_found) == %{ +               "error" => "pack \"not_loaded\" is not found" +             } +    end + +    test "remove file with empty shortcode", %{admin_conn: admin_conn} do +      assert admin_conn +             |> delete("/api/pleroma/emoji/packs/not_loaded/files?shortcode=") +             |> json_response_and_validate_schema(:not_found) == %{ +               "error" => "pack \"not_loaded\" is not found" +             } +    end + +    test "update file with not loaded pack", %{admin_conn: admin_conn} do +      assert admin_conn +             |> put_req_header("content-type", "multipart/form-data") +             |> patch("/api/pleroma/emoji/packs/not_loaded/files", %{ +               shortcode: "blank4", +               new_shortcode: "blank3", +               new_filename: "dir_2/blank_3.png" +             }) +             |> json_response_and_validate_schema(:not_found) == %{ +               "error" => "pack \"not_loaded\" is not found" +             } +    end + +    test "new with shortcode as file with update", %{admin_conn: admin_conn} do +      assert admin_conn +             |> put_req_header("content-type", "multipart/form-data") +             |> post("/api/pleroma/emoji/packs/test_pack/files", %{ +               shortcode: "blank4", +               filename: "dir/blank.png", +               file: %Plug.Upload{ +                 filename: "blank.png", +                 path: "#{@emoji_path}/test_pack/blank.png" +               } +             }) +             |> json_response_and_validate_schema(200) == %{ +               "blank" => "blank.png", +               "blank4" => "dir/blank.png", +               "blank2" => "blank2.png" +             } + +      assert File.exists?("#{@emoji_path}/test_pack/dir/blank.png") + +      assert admin_conn +             |> put_req_header("content-type", "multipart/form-data") +             |> patch("/api/pleroma/emoji/packs/test_pack/files", %{ +               shortcode: "blank4", +               new_shortcode: "blank3", +               new_filename: "dir_2/blank_3.png" +             }) +             |> json_response_and_validate_schema(200) == %{ +               "blank3" => "dir_2/blank_3.png", +               "blank" => "blank.png", +               "blank2" => "blank2.png" +             } + +      refute File.exists?("#{@emoji_path}/test_pack/dir/") +      assert File.exists?("#{@emoji_path}/test_pack/dir_2/blank_3.png") + +      assert admin_conn +             |> delete("/api/pleroma/emoji/packs/test_pack/files?shortcode=blank3") +             |> json_response_and_validate_schema(200) == %{ +               "blank" => "blank.png", +               "blank2" => "blank2.png" +             } + +      refute File.exists?("#{@emoji_path}/test_pack/dir_2/") + +      on_exit(fn -> File.rm_rf!("#{@emoji_path}/test_pack/dir") end) +    end + +    test "new with shortcode from url", %{admin_conn: admin_conn} do +      mock(fn +        %{ +          method: :get, +          url: "https://test-blank/blank_url.png" +        } -> +          text(File.read!("#{@emoji_path}/test_pack/blank.png")) +      end) + +      assert admin_conn +             |> put_req_header("content-type", "multipart/form-data") +             |> post("/api/pleroma/emoji/packs/test_pack/files", %{ +               shortcode: "blank_url", +               file: "https://test-blank/blank_url.png" +             }) +             |> json_response_and_validate_schema(200) == %{ +               "blank_url" => "blank_url.png", +               "blank" => "blank.png", +               "blank2" => "blank2.png" +             } + +      assert File.exists?("#{@emoji_path}/test_pack/blank_url.png") + +      on_exit(fn -> File.rm_rf!("#{@emoji_path}/test_pack/blank_url.png") end) +    end + +    test "new without shortcode", %{admin_conn: admin_conn} do +      on_exit(fn -> File.rm_rf!("#{@emoji_path}/test_pack/shortcode.png") end) + +      assert admin_conn +             |> put_req_header("content-type", "multipart/form-data") +             |> post("/api/pleroma/emoji/packs/test_pack/files", %{ +               file: %Plug.Upload{ +                 filename: "shortcode.png", +                 path: "#{Pleroma.Config.get([:instance, :static_dir])}/add/shortcode.png" +               } +             }) +             |> json_response_and_validate_schema(200) == %{ +               "shortcode" => "shortcode.png", +               "blank" => "blank.png", +               "blank2" => "blank2.png" +             } +    end + +    test "remove non existing shortcode in pack.json", %{admin_conn: admin_conn} do +      assert admin_conn +             |> delete("/api/pleroma/emoji/packs/test_pack/files?shortcode=blank3") +             |> json_response_and_validate_schema(:bad_request) == %{ +               "error" => "Emoji \"blank3\" does not exist" +             } +    end + +    test "update non existing emoji", %{admin_conn: admin_conn} do +      assert admin_conn +             |> put_req_header("content-type", "multipart/form-data") +             |> patch("/api/pleroma/emoji/packs/test_pack/files", %{ +               shortcode: "blank3", +               new_shortcode: "blank4", +               new_filename: "dir_2/blank_3.png" +             }) +             |> json_response_and_validate_schema(:bad_request) == %{ +               "error" => "Emoji \"blank3\" does not exist" +             } +    end + +    test "update with empty shortcode", %{admin_conn: admin_conn} do +      assert %{ +               "error" => "Missing field: new_shortcode." +             } = +               admin_conn +               |> put_req_header("content-type", "multipart/form-data") +               |> patch("/api/pleroma/emoji/packs/test_pack/files", %{ +                 shortcode: "blank", +                 new_filename: "dir_2/blank_3.png" +               }) +               |> json_response_and_validate_schema(:bad_request) +    end +  end +end diff --git a/test/web/pleroma_api/controllers/emoji_pack_controller_test.exs b/test/web/pleroma_api/controllers/emoji_pack_controller_test.exs index e113bb15f..a34df2c18 100644 --- a/test/web/pleroma_api/controllers/emoji_pack_controller_test.exs +++ b/test/web/pleroma_api/controllers/emoji_pack_controller_test.exs @@ -411,293 +411,6 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackControllerTest do      end    end -  describe "POST/PATCH/DELETE /api/pleroma/emoji/packs/:name/files" do -    setup do -      pack_file = "#{@emoji_path}/test_pack/pack.json" -      original_content = File.read!(pack_file) - -      on_exit(fn -> -        File.write!(pack_file, original_content) -      end) - -      :ok -    end - -    test "create shortcode exists", %{admin_conn: admin_conn} do -      assert admin_conn -             |> put_req_header("content-type", "multipart/form-data") -             |> post("/api/pleroma/emoji/packs/test_pack/files", %{ -               shortcode: "blank", -               filename: "dir/blank.png", -               file: %Plug.Upload{ -                 filename: "blank.png", -                 path: "#{@emoji_path}/test_pack/blank.png" -               } -             }) -             |> json_response_and_validate_schema(:conflict) == %{ -               "error" => "An emoji with the \"blank\" shortcode already exists" -             } -    end - -    test "don't rewrite old emoji", %{admin_conn: admin_conn} do -      on_exit(fn -> File.rm_rf!("#{@emoji_path}/test_pack/dir/") end) - -      assert admin_conn -             |> put_req_header("content-type", "multipart/form-data") -             |> post("/api/pleroma/emoji/packs/test_pack/files", %{ -               shortcode: "blank3", -               filename: "dir/blank.png", -               file: %Plug.Upload{ -                 filename: "blank.png", -                 path: "#{@emoji_path}/test_pack/blank.png" -               } -             }) -             |> json_response_and_validate_schema(200) == %{ -               "blank" => "blank.png", -               "blank2" => "blank2.png", -               "blank3" => "dir/blank.png" -             } - -      assert File.exists?("#{@emoji_path}/test_pack/dir/blank.png") - -      assert admin_conn -             |> put_req_header("content-type", "multipart/form-data") -             |> patch("/api/pleroma/emoji/packs/test_pack/files", %{ -               shortcode: "blank", -               new_shortcode: "blank2", -               new_filename: "dir_2/blank_3.png" -             }) -             |> json_response_and_validate_schema(:conflict) == %{ -               "error" => -                 "New shortcode \"blank2\" is already used. If you want to override emoji use 'force' option" -             } -    end - -    test "rewrite old emoji with force option", %{admin_conn: admin_conn} do -      on_exit(fn -> File.rm_rf!("#{@emoji_path}/test_pack/dir_2/") end) - -      assert admin_conn -             |> put_req_header("content-type", "multipart/form-data") -             |> post("/api/pleroma/emoji/packs/test_pack/files", %{ -               shortcode: "blank3", -               filename: "dir/blank.png", -               file: %Plug.Upload{ -                 filename: "blank.png", -                 path: "#{@emoji_path}/test_pack/blank.png" -               } -             }) -             |> json_response_and_validate_schema(200) == %{ -               "blank" => "blank.png", -               "blank2" => "blank2.png", -               "blank3" => "dir/blank.png" -             } - -      assert File.exists?("#{@emoji_path}/test_pack/dir/blank.png") - -      assert admin_conn -             |> put_req_header("content-type", "multipart/form-data") -             |> patch("/api/pleroma/emoji/packs/test_pack/files", %{ -               shortcode: "blank3", -               new_shortcode: "blank4", -               new_filename: "dir_2/blank_3.png", -               force: true -             }) -             |> json_response_and_validate_schema(200) == %{ -               "blank" => "blank.png", -               "blank2" => "blank2.png", -               "blank4" => "dir_2/blank_3.png" -             } - -      assert File.exists?("#{@emoji_path}/test_pack/dir_2/blank_3.png") -    end - -    test "with empty filename", %{admin_conn: admin_conn} do -      assert admin_conn -             |> put_req_header("content-type", "multipart/form-data") -             |> post("/api/pleroma/emoji/packs/test_pack/files", %{ -               shortcode: "blank2", -               filename: "", -               file: %Plug.Upload{ -                 filename: "blank.png", -                 path: "#{@emoji_path}/test_pack/blank.png" -               } -             }) -             |> json_response_and_validate_schema(:bad_request) == %{ -               "error" => "pack name, shortcode or filename cannot be empty" -             } -    end - -    test "add file with not loaded pack", %{admin_conn: admin_conn} do -      assert admin_conn -             |> put_req_header("content-type", "multipart/form-data") -             |> post("/api/pleroma/emoji/packs/not_loaded/files", %{ -               shortcode: "blank3", -               filename: "dir/blank.png", -               file: %Plug.Upload{ -                 filename: "blank.png", -                 path: "#{@emoji_path}/test_pack/blank.png" -               } -             }) -             |> json_response_and_validate_schema(:bad_request) == %{ -               "error" => "pack \"not_loaded\" is not found" -             } -    end - -    test "remove file with not loaded pack", %{admin_conn: admin_conn} do -      assert admin_conn -             |> delete("/api/pleroma/emoji/packs/not_loaded/files?shortcode=blank3") -             |> json_response_and_validate_schema(:bad_request) == %{ -               "error" => "pack \"not_loaded\" is not found" -             } -    end - -    test "remove file with empty shortcode", %{admin_conn: admin_conn} do -      assert admin_conn -             |> delete("/api/pleroma/emoji/packs/not_loaded/files?shortcode=") -             |> json_response_and_validate_schema(:bad_request) == %{ -               "error" => "pack name or shortcode cannot be empty" -             } -    end - -    test "update file with not loaded pack", %{admin_conn: admin_conn} do -      assert admin_conn -             |> put_req_header("content-type", "multipart/form-data") -             |> patch("/api/pleroma/emoji/packs/not_loaded/files", %{ -               shortcode: "blank4", -               new_shortcode: "blank3", -               new_filename: "dir_2/blank_3.png" -             }) -             |> json_response_and_validate_schema(:bad_request) == %{ -               "error" => "pack \"not_loaded\" is not found" -             } -    end - -    test "new with shortcode as file with update", %{admin_conn: admin_conn} do -      assert admin_conn -             |> put_req_header("content-type", "multipart/form-data") -             |> post("/api/pleroma/emoji/packs/test_pack/files", %{ -               shortcode: "blank4", -               filename: "dir/blank.png", -               file: %Plug.Upload{ -                 filename: "blank.png", -                 path: "#{@emoji_path}/test_pack/blank.png" -               } -             }) -             |> json_response_and_validate_schema(200) == %{ -               "blank" => "blank.png", -               "blank4" => "dir/blank.png", -               "blank2" => "blank2.png" -             } - -      assert File.exists?("#{@emoji_path}/test_pack/dir/blank.png") - -      assert admin_conn -             |> put_req_header("content-type", "multipart/form-data") -             |> patch("/api/pleroma/emoji/packs/test_pack/files", %{ -               shortcode: "blank4", -               new_shortcode: "blank3", -               new_filename: "dir_2/blank_3.png" -             }) -             |> json_response_and_validate_schema(200) == %{ -               "blank3" => "dir_2/blank_3.png", -               "blank" => "blank.png", -               "blank2" => "blank2.png" -             } - -      refute File.exists?("#{@emoji_path}/test_pack/dir/") -      assert File.exists?("#{@emoji_path}/test_pack/dir_2/blank_3.png") - -      assert admin_conn -             |> delete("/api/pleroma/emoji/packs/test_pack/files?shortcode=blank3") -             |> json_response_and_validate_schema(200) == %{ -               "blank" => "blank.png", -               "blank2" => "blank2.png" -             } - -      refute File.exists?("#{@emoji_path}/test_pack/dir_2/") - -      on_exit(fn -> File.rm_rf!("#{@emoji_path}/test_pack/dir") end) -    end - -    test "new with shortcode from url", %{admin_conn: admin_conn} do -      mock(fn -        %{ -          method: :get, -          url: "https://test-blank/blank_url.png" -        } -> -          text(File.read!("#{@emoji_path}/test_pack/blank.png")) -      end) - -      assert admin_conn -             |> put_req_header("content-type", "multipart/form-data") -             |> post("/api/pleroma/emoji/packs/test_pack/files", %{ -               shortcode: "blank_url", -               file: "https://test-blank/blank_url.png" -             }) -             |> json_response_and_validate_schema(200) == %{ -               "blank_url" => "blank_url.png", -               "blank" => "blank.png", -               "blank2" => "blank2.png" -             } - -      assert File.exists?("#{@emoji_path}/test_pack/blank_url.png") - -      on_exit(fn -> File.rm_rf!("#{@emoji_path}/test_pack/blank_url.png") end) -    end - -    test "new without shortcode", %{admin_conn: admin_conn} do -      on_exit(fn -> File.rm_rf!("#{@emoji_path}/test_pack/shortcode.png") end) - -      assert admin_conn -             |> put_req_header("content-type", "multipart/form-data") -             |> post("/api/pleroma/emoji/packs/test_pack/files", %{ -               file: %Plug.Upload{ -                 filename: "shortcode.png", -                 path: "#{Pleroma.Config.get([:instance, :static_dir])}/add/shortcode.png" -               } -             }) -             |> json_response_and_validate_schema(200) == %{ -               "shortcode" => "shortcode.png", -               "blank" => "blank.png", -               "blank2" => "blank2.png" -             } -    end - -    test "remove non existing shortcode in pack.json", %{admin_conn: admin_conn} do -      assert admin_conn -             |> delete("/api/pleroma/emoji/packs/test_pack/files?shortcode=blank3") -             |> json_response_and_validate_schema(:bad_request) == %{ -               "error" => "Emoji \"blank3\" does not exist" -             } -    end - -    test "update non existing emoji", %{admin_conn: admin_conn} do -      assert admin_conn -             |> put_req_header("content-type", "multipart/form-data") -             |> patch("/api/pleroma/emoji/packs/test_pack/files", %{ -               shortcode: "blank3", -               new_shortcode: "blank4", -               new_filename: "dir_2/blank_3.png" -             }) -             |> json_response_and_validate_schema(:bad_request) == %{ -               "error" => "Emoji \"blank3\" does not exist" -             } -    end - -    test "update with empty shortcode", %{admin_conn: admin_conn} do -      assert %{ -               "error" => "Missing field: new_shortcode." -             } = -               admin_conn -               |> put_req_header("content-type", "multipart/form-data") -               |> patch("/api/pleroma/emoji/packs/test_pack/files", %{ -                 shortcode: "blank", -                 new_filename: "dir_2/blank_3.png" -               }) -               |> json_response_and_validate_schema(:bad_request) -    end -  end -    describe "POST/DELETE /api/pleroma/emoji/packs/:name" do      test "creating and deleting a pack", %{admin_conn: admin_conn} do        assert admin_conn  | 
