Browse Source

Merge branch 'openapi/pleroma-api/emojis' into 'develop'

Add OpenAPI spec for EmojiPackController

See merge request pleroma/pleroma!2549
chore/expose-invalidation-to-adminfe
lain 4 years ago
parent
commit
265746b21f
7 changed files with 935 additions and 460 deletions
  1. +1
    -1
      docs/API/pleroma_api.md
  2. +361
    -327
      lib/pleroma/emoji/pack.ex
  3. +390
    -0
      lib/pleroma/web/api_spec/operations/pleroma_emoji_pack_operation.ex
  4. +45
    -45
      lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex
  5. +13
    -12
      lib/pleroma/web/router.ex
  6. +1
    -1
      test/web/api_spec/schema_examples_test.exs
  7. +124
    -74
      test/web/pleroma_api/controllers/emoji_pack_controller_test.exs

+ 1
- 1
docs/API/pleroma_api.md View File

@@ -426,7 +426,7 @@ The status posting endpoint takes an additional parameter, `in_reply_to_conversa
* Authentication: required * Authentication: required
* Params: * Params:
* `file`: file needs to be uploaded with the multipart request or link to remote file. * `file`: file needs to be uploaded with the multipart request or link to remote file.
* `shortcode`: (*optional*) shortcode for new emoji, must be uniq for all emoji. If not sended, shortcode will be taken from original filename.
* `shortcode`: (*optional*) shortcode for new emoji, must be unique for all emoji. If not sended, shortcode will be taken from original filename.
* `filename`: (*optional*) new emoji file name. If not specified will be taken from original filename. * `filename`: (*optional*) new emoji file name. If not specified will be taken from original filename.
* Response: JSON, list of files for updated pack (hashmap -> shortcode => filename) with status 200, either error status with error message. * Response: JSON, list of files for updated pack (hashmap -> shortcode => filename) with status 200, either error status with error message.




+ 361
- 327
lib/pleroma/emoji/pack.ex View File

@@ -16,162 +16,78 @@ defmodule Pleroma.Emoji.Pack do


alias Pleroma.Emoji alias Pleroma.Emoji


@spec emoji_path() :: Path.t()
def emoji_path do
static = Pleroma.Config.get!([:instance, :static_dir])
Path.join(static, "emoji")
end

@spec create(String.t()) :: :ok | {:error, File.posix()} | {:error, :empty_values} @spec create(String.t()) :: :ok | {:error, File.posix()} | {:error, :empty_values}
def create(name) when byte_size(name) > 0 do
dir = Path.join(emoji_path(), name)

with :ok <- File.mkdir(dir) do
%__MODULE__{
pack_file: Path.join(dir, "pack.json")
}
def create(name) do
with :ok <- validate_not_empty([name]),
dir <- Path.join(emoji_path(), name),
:ok <- File.mkdir(dir) do
%__MODULE__{pack_file: Path.join(dir, "pack.json")}
|> save_pack() |> save_pack()
end end
end end


def create(_), do: {:error, :empty_values}

@spec show(String.t()) :: {:ok, t()} | {:loaded, nil} | {:error, :empty_values}
def show(name) when byte_size(name) > 0 do
with {_, %__MODULE__{} = pack} <- {:loaded, load_pack(name)},
{_, pack} <- validate_pack(pack) do
{:ok, pack}
@spec show(String.t()) :: {:ok, t()} | {:error, atom()}
def show(name) do
with :ok <- validate_not_empty([name]),
{:ok, pack} <- load_pack(name) do
{:ok, validate_pack(pack)}
end end
end end


def show(_), do: {:error, :empty_values}

@spec delete(String.t()) :: @spec delete(String.t()) ::
{:ok, [binary()]} | {:error, File.posix(), binary()} | {:error, :empty_values} {:ok, [binary()]} | {:error, File.posix(), binary()} | {:error, :empty_values}
def delete(name) when byte_size(name) > 0 do
emoji_path()
|> Path.join(name)
|> File.rm_rf()
end

def delete(_), do: {:error, :empty_values}

@spec add_file(String.t(), String.t(), Path.t(), Plug.Upload.t() | String.t()) ::
{:ok, t()} | {:error, File.posix()} | {:error, :empty_values}
def add_file(name, shortcode, filename, file)
when byte_size(name) > 0 and byte_size(shortcode) > 0 and byte_size(filename) > 0 do
with {_, nil} <- {:exists, Emoji.get(shortcode)},
{_, %__MODULE__{} = pack} <- {:loaded, load_pack(name)} 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
File.copy!(upload_path, file_path)

url when is_binary(url) ->
# Download and write the file
file_contents = Tesla.get!(url).body
File.write!(file_path, file_contents)
end

files = Map.put(pack.files, shortcode, filename)

updated_pack = %{pack | files: files}

case save_pack(updated_pack) do
:ok ->
Emoji.reload()
{:ok, updated_pack}

e ->
e
end
def delete(name) do
with :ok <- validate_not_empty([name]) do
emoji_path()
|> Path.join(name)
|> File.rm_rf()
end end
end end


def add_file(_, _, _, _), do: {:error, :empty_values}

defp create_subdirs(file_path) do
if String.contains?(file_path, "/") do
file_path
|> Path.dirname()
|> File.mkdir_p!()
@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]),
: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
Emoji.reload()
{:ok, updated_pack}
end end
end end


@spec delete_file(String.t(), String.t()) :: @spec delete_file(String.t(), String.t()) ::
{:ok, t()} | {:error, File.posix()} | {:error, :empty_values}
def delete_file(name, shortcode) when byte_size(name) > 0 and byte_size(shortcode) > 0 do
with {_, %__MODULE__{} = pack} <- {:loaded, load_pack(name)},
{_, {filename, files}} when not is_nil(filename) <-
{:exists, Map.pop(pack.files, shortcode)},
emoji <- Path.join(pack.path, filename),
{_, true} <- {:exists, File.exists?(emoji)} do
emoji_dir = Path.dirname(emoji)

File.rm!(emoji)

if String.contains?(filename, "/") and File.ls!(emoji_dir) == [] do
File.rmdir!(emoji_dir)
end

updated_pack = %{pack | files: files}

case save_pack(updated_pack) do
:ok ->
Emoji.reload()
{:ok, updated_pack}

e ->
e
end
{:ok, t()} | {:error, File.posix() | atom()}
def delete_file(name, shortcode) do
with :ok <- validate_not_empty([name, shortcode]),
{:ok, pack} <- load_pack(name),
:ok <- remove_file(pack, shortcode),
{:ok, updated_pack} <- pack |> delete_emoji(shortcode) |> save_pack() do
Emoji.reload()
{:ok, updated_pack}
end end
end end


def delete_file(_, _), do: {:error, :empty_values}

@spec update_file(String.t(), String.t(), String.t(), String.t(), boolean()) :: @spec update_file(String.t(), String.t(), String.t(), String.t(), boolean()) ::
{:ok, t()} | {:error, File.posix()} | {:error, :empty_values}
def update_file(name, shortcode, new_shortcode, new_filename, force)
when byte_size(name) > 0 and byte_size(shortcode) > 0 and byte_size(new_shortcode) > 0 and
byte_size(new_filename) > 0 do
with {_, %__MODULE__{} = pack} <- {:loaded, load_pack(name)},
{_, {filename, files}} when not is_nil(filename) <-
{:exists, Map.pop(pack.files, shortcode)},
{_, true} <- {:not_used, force or is_nil(Emoji.get(new_shortcode))} do
old_path = Path.join(pack.path, filename)
old_dir = Path.dirname(old_path)
new_path = Path.join(pack.path, new_filename)

create_subdirs(new_path)

:ok = File.rename(old_path, new_path)

if String.contains?(filename, "/") and File.ls!(old_dir) == [] do
File.rmdir!(old_dir)
end

files = Map.put(files, new_shortcode, new_filename)

updated_pack = %{pack | files: files}

case save_pack(updated_pack) do
:ok ->
Emoji.reload()
{:ok, updated_pack}

e ->
e
end
{: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),
{:ok, filename} <- get_filename(pack, shortcode),
:ok <- validate_emoji_not_exists(new_shortcode, force),
:ok <- rename_file(pack, filename, new_filename),
{:ok, updated_pack} <-
pack
|> delete_emoji(shortcode)
|> put_emoji(new_shortcode, new_filename)
|> save_pack() do
Emoji.reload()
{:ok, updated_pack}
end end
end end


def update_file(_, _, _, _, _), do: {:error, :empty_values}

@spec import_from_filesystem() :: {:ok, [String.t()]} | {:error, atom()}
@spec import_from_filesystem() :: {:ok, [String.t()]} | {:error, File.posix() | atom()}
def import_from_filesystem do def import_from_filesystem do
emoji_path = emoji_path() emoji_path = emoji_path()


@@ -184,7 +100,7 @@ defmodule Pleroma.Emoji.Pack do
File.dir?(path) and File.exists?(Path.join(path, "pack.json")) File.dir?(path) and File.exists?(Path.join(path, "pack.json"))
end) end)
|> Enum.map(&write_pack_contents/1) |> Enum.map(&write_pack_contents/1)
|> Enum.filter(& &1)
|> Enum.reject(&is_nil/1)


{:ok, names} {:ok, names}
else else
@@ -193,6 +109,117 @@ defmodule Pleroma.Emoji.Pack do
end end
end end


@spec list_remote(String.t()) :: {:ok, map()} | {:error, atom()}
def list_remote(url) do
uri = url |> String.trim() |> URI.parse()

with :ok <- validate_shareable_packs_available(uri) do
uri
|> URI.merge("/api/pleroma/emoji/packs")
|> http_get()
end
end

@spec list_local() :: {:ok, map()}
def list_local do
with {:ok, results} <- list_packs_dir() do
packs =
results
|> Enum.map(fn name ->
case load_pack(name) do
{:ok, pack} -> pack
_ -> nil
end
end)
|> Enum.reject(&is_nil/1)
|> Map.new(fn pack -> {pack.name, validate_pack(pack)} end)

{:ok, packs}
end
end

@spec get_archive(String.t()) :: {:ok, binary()} | {:error, atom()}
def get_archive(name) do
with {:ok, pack} <- load_pack(name),
:ok <- validate_downloadable(pack) do
{:ok, fetch_archive(pack)}
end
end

@spec download(String.t(), String.t(), String.t()) :: :ok | {:error, atom()}
def download(name, url, as) do
uri = url |> String.trim() |> URI.parse()

with :ok <- validate_shareable_packs_available(uri),
{:ok, remote_pack} <- uri |> URI.merge("/api/pleroma/emoji/packs/#{name}") |> http_get(),
{:ok, %{sha: sha, url: url} = pack_info} <- fetch_pack_info(remote_pack, uri, name),
{:ok, archive} <- download_archive(url, sha),
pack <- copy_as(remote_pack, as || name),
{:ok, _} = unzip(archive, pack_info, remote_pack, pack) do
# Fallback can't contain a pack.json file, since that would cause the fallback-src-sha256
# in it to depend on itself
if pack_info[:fallback] do
save_pack(pack)
else
{:ok, pack}
end
end
end

@spec save_metadata(map(), t()) :: {:ok, t()} | {:error, File.posix()}
def save_metadata(metadata, %__MODULE__{} = pack) do
pack
|> Map.put(:pack, metadata)
|> save_pack()
end

@spec update_metadata(String.t(), map()) :: {:ok, t()} | {:error, File.posix()}
def update_metadata(name, data) do
with {:ok, pack} <- load_pack(name) do
if fallback_sha_changed?(pack, data) do
update_sha_and_save_metadata(pack, data)
else
save_metadata(data, pack)
end
end
end

@spec load_pack(String.t()) :: {:ok, t()} | {:error, :not_found}
def load_pack(name) do
pack_file = Path.join([emoji_path(), name, "pack.json"])

if File.exists?(pack_file) do
pack =
pack_file
|> File.read!()
|> from_json()
|> Map.put(:pack_file, pack_file)
|> Map.put(:path, Path.dirname(pack_file))
|> Map.put(:name, name)

{:ok, pack}
else
{:error, :not_found}
end
end

@spec emoji_path() :: Path.t()
defp emoji_path do
[:instance, :static_dir]
|> Pleroma.Config.get!()
|> Path.join("emoji")
end

defp validate_emoji_not_exists(shortcode, force \\ false)
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}
end
end

defp write_pack_contents(path) do defp write_pack_contents(path) do
pack = %__MODULE__{ pack = %__MODULE__{
files: files_from_path(path), files: files_from_path(path),
@@ -201,7 +228,7 @@ defmodule Pleroma.Emoji.Pack do
} }


case save_pack(pack) do case save_pack(pack) do
:ok -> Path.basename(path)
{:ok, _pack} -> Path.basename(path)
_ -> nil _ -> nil
end end
end end
@@ -216,7 +243,8 @@ defmodule Pleroma.Emoji.Pack do
# FIXME: Copy-pasted from Pleroma.Emoji/load_from_file_stream/2 # FIXME: Copy-pasted from Pleroma.Emoji/load_from_file_stream/2


# Create a map of shortcodes to filenames from emoji.txt # Create a map of shortcodes to filenames from emoji.txt
File.read!(txt_path)
txt_path
|> File.read!()
|> String.split("\n") |> String.split("\n")
|> Enum.map(&String.trim/1) |> Enum.map(&String.trim/1)
|> Enum.map(fn line -> |> Enum.map(fn line ->
@@ -226,21 +254,18 @@ defmodule Pleroma.Emoji.Pack do
[name, file | _] -> [name, file | _] ->
file_dir_name = Path.dirname(file) file_dir_name = Path.dirname(file)


file =
if String.ends_with?(path, file_dir_name) do
Path.basename(file)
else
file
end

{name, file}
if String.ends_with?(path, file_dir_name) do
{name, Path.basename(file)}
else
{name, file}
end


_ -> _ ->
nil nil
end end
end) end)
|> Enum.filter(& &1)
|> Enum.into(%{})
|> Enum.reject(&is_nil/1)
|> Map.new()
else else
# If there's no emoji.txt, assume all files # If there's no emoji.txt, assume all files
# that are of certain extensions from the config are emojis and import them all # that are of certain extensions from the config are emojis and import them all
@@ -249,60 +274,20 @@ defmodule Pleroma.Emoji.Pack do
end end
end end


@spec list_remote(String.t()) :: {:ok, map()}
def list_remote(url) do
uri =
url
|> String.trim()
|> URI.parse()

with {_, true} <- {:shareable, shareable_packs_available?(uri)} do
packs =
uri
|> URI.merge("/api/pleroma/emoji/packs")
|> to_string()
|> Tesla.get!()
|> Map.get(:body)
|> Jason.decode!()

{:ok, packs}
end
end

@spec list_local() :: {:ok, map()}
def list_local do
emoji_path = emoji_path()

# Create the directory first if it does not exist. This is probably the first request made
# with the API so it should be sufficient
with {:create_dir, :ok} <- {:create_dir, File.mkdir_p(emoji_path)},
{:ls, {:ok, results}} <- {:ls, File.ls(emoji_path)} do
packs =
results
|> Enum.map(&load_pack/1)
|> Enum.filter(& &1)
|> Enum.map(&validate_pack/1)
|> Map.new()

{:ok, packs}
end
end

defp validate_pack(pack) do defp validate_pack(pack) do
if downloadable?(pack) do
archive = fetch_archive(pack)
archive_sha = :crypto.hash(:sha256, archive) |> Base.encode16()
info =
if downloadable?(pack) do
archive = fetch_archive(pack)
archive_sha = :crypto.hash(:sha256, archive) |> Base.encode16()


info =
pack.pack pack.pack
|> Map.put("can-download", true) |> Map.put("can-download", true)
|> Map.put("download-sha256", archive_sha) |> Map.put("download-sha256", archive_sha)
else
Map.put(pack.pack, "can-download", false)
end


{pack.name, Map.put(pack, :pack, info)}
else
info = Map.put(pack.pack, "can-download", false)
{pack.name, Map.put(pack, :pack, info)}
end
Map.put(pack, :pack, info)
end end


defp downloadable?(pack) do defp downloadable?(pack) do
@@ -315,26 +300,6 @@ defmodule Pleroma.Emoji.Pack do
end) end)
end end


@spec get_archive(String.t()) :: {:ok, binary()}
def get_archive(name) do
with {_, %__MODULE__{} = pack} <- {:exists?, load_pack(name)},
{_, true} <- {:can_download?, downloadable?(pack)} do
{:ok, fetch_archive(pack)}
end
end

defp fetch_archive(pack) do
hash = :crypto.hash(:md5, File.read!(pack.pack_file))

case Cachex.get!(:emoji_packs_cache, pack.name) do
%{hash: ^hash, pack_data: archive} ->
archive

_ ->
create_archive_and_cache(pack, hash)
end
end

defp create_archive_and_cache(pack, hash) do defp create_archive_and_cache(pack, hash) do
files = ['pack.json' | Enum.map(pack.files, fn {_, file} -> to_charlist(file) end)] files = ['pack.json' | Enum.map(pack.files, fn {_, file} -> to_charlist(file) end)]


@@ -356,152 +321,221 @@ defmodule Pleroma.Emoji.Pack do
result result
end end


@spec download(String.t(), String.t(), String.t()) :: :ok
def download(name, url, as) do
uri =
url
|> String.trim()
|> URI.parse()

with {_, true} <- {:shareable, shareable_packs_available?(uri)} do
remote_pack =
uri
|> URI.merge("/api/pleroma/emoji/packs/#{name}")
|> to_string()
|> Tesla.get!()
|> Map.get(:body)
|> Jason.decode!()

result =
case remote_pack["pack"] do
%{"share-files" => true, "can-download" => true, "download-sha256" => sha} ->
{:ok,
%{
sha: sha,
url: URI.merge(uri, "/api/pleroma/emoji/packs/#{name}/archive") |> to_string()
}}

%{"fallback-src" => src, "fallback-src-sha256" => sha} when is_binary(src) ->
{:ok,
%{
sha: sha,
url: src,
fallback: true
}}
defp save_pack(pack) do
with {:ok, json} <- Jason.encode(pack, pretty: true),
:ok <- File.write(pack.pack_file, json) do
{:ok, pack}
end
end


_ ->
{:error,
"The pack was not set as shared and there is no fallback src to download from"}
end
defp from_json(json) do
map = Jason.decode!(json)


with {:ok, %{sha: sha, url: url} = pinfo} <- result,
%{body: archive} <- Tesla.get!(url),
{_, true} <- {:checksum, Base.decode16!(sha) == :crypto.hash(:sha256, archive)} do
local_name = as || name
struct(__MODULE__, %{files: map["files"], pack: map["pack"]})
end


path = Path.join(emoji_path(), local_name)
defp validate_shareable_packs_available(uri) do
with {:ok, %{"links" => links}} <- uri |> URI.merge("/.well-known/nodeinfo") |> http_get(),
# Get the actual nodeinfo address and fetch it
{:ok, %{"metadata" => %{"features" => features}}} <-
links |> List.last() |> Map.get("href") |> http_get() do
if Enum.member?(features, "shareable_emoji_packs") do
:ok
else
{:error, :not_shareable}
end
end
end


pack = %__MODULE__{
name: local_name,
path: path,
files: remote_pack["files"],
pack_file: Path.join(path, "pack.json")
}
defp validate_not_empty(list) do
if Enum.all?(list, fn i -> is_binary(i) and i != "" end) do
:ok
else
{:error, :empty_values}
end
end


File.mkdir_p!(pack.path)
defp save_file(file, pack, filename) do
file_path = Path.join(pack.path, filename)
create_subdirs(file_path)


files = Enum.map(remote_pack["files"], fn {_, path} -> to_charlist(path) end)
# Fallback cannot contain a pack.json file
files = if pinfo[:fallback], do: files, else: ['pack.json' | files]
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


{:ok, _} = :zip.unzip(archive, cwd: to_charlist(pack.path), file_list: files)
url when is_binary(url) ->
# Download and write the file
file_contents = Tesla.get!(url).body
File.write(file_path, file_contents)
end
end


# Fallback can't contain a pack.json file, since that would cause the fallback-src-sha256
# in it to depend on itself
if pinfo[:fallback] do
save_pack(pack)
end
defp put_emoji(pack, shortcode, filename) do
files = Map.put(pack.files, shortcode, filename)
%{pack | files: files}
end


:ok
end
defp delete_emoji(pack, shortcode) do
files = Map.delete(pack.files, shortcode)
%{pack | files: files}
end

defp rename_file(pack, filename, new_filename) do
old_path = Path.join(pack.path, filename)
new_path = Path.join(pack.path, new_filename)
create_subdirs(new_path)

with :ok <- File.rename(old_path, new_path) do
remove_dir_if_empty(old_path, filename)
end end
end end


defp save_pack(pack), do: File.write(pack.pack_file, Jason.encode!(pack, pretty: true))
defp create_subdirs(file_path) do
if String.contains?(file_path, "/") do
file_path
|> Path.dirname()
|> File.mkdir_p!()
end
end


@spec save_metadata(map(), t()) :: {:ok, t()} | {:error, File.posix()}
def save_metadata(metadata, %__MODULE__{} = pack) do
pack = Map.put(pack, :pack, metadata)
defp remove_file(pack, shortcode) do
with {:ok, filename} <- get_filename(pack, shortcode),
emoji <- Path.join(pack.path, filename),
:ok <- File.rm(emoji) do
remove_dir_if_empty(emoji, filename)
end
end


with :ok <- save_pack(pack) do
{:ok, pack}
defp remove_dir_if_empty(emoji, filename) do
dir = Path.dirname(emoji)

if String.contains?(filename, "/") and File.ls!(dir) == [] do
File.rmdir!(dir)
else
:ok
end end
end end


@spec update_metadata(String.t(), map()) :: {:ok, t()} | {:error, File.posix()}
def update_metadata(name, data) do
pack = load_pack(name)
defp get_filename(pack, shortcode) do
with %{^shortcode => filename} when is_binary(filename) <- pack.files,
true <- pack.path |> Path.join(filename) |> File.exists?() do
{:ok, filename}
else
_ -> {:error, :doesnt_exist}
end
end


fb_sha_changed? =
not is_nil(data["fallback-src"]) and data["fallback-src"] != pack.pack["fallback-src"]
defp http_get(%URI{} = url), do: url |> to_string() |> http_get()


with {_, true} <- {:update?, fb_sha_changed?},
{:ok, %{body: zip}} <- Tesla.get(data["fallback-src"]),
{:ok, f_list} <- :zip.unzip(zip, [:memory]),
{_, true} <- {:has_all_files?, has_all_files?(pack.files, f_list)} do
fallback_sha = :crypto.hash(:sha256, zip) |> Base.encode16()
defp http_get(url) do
with {:ok, %{body: body}} <- url |> Pleroma.HTTP.get() do
Jason.decode(body)
end
end


data
|> Map.put("fallback-src-sha256", fallback_sha)
|> save_metadata(pack)
defp list_packs_dir do
emoji_path = emoji_path()
# Create the directory first if it does not exist. This is probably the first request made
# with the API so it should be sufficient
with {:create_dir, :ok} <- {:create_dir, File.mkdir_p(emoji_path)},
{:ls, {:ok, results}} <- {:ls, File.ls(emoji_path)} do
{:ok, results}
else else
{:update?, _} -> save_metadata(data, pack)
e -> e
{:create_dir, {:error, e}} -> {:error, :create_dir, e}
{:ls, {:error, e}} -> {:error, :ls, e}
end end
end end


# Check if all files from the pack.json are in the archive
defp has_all_files?(files, f_list) do
Enum.all?(files, fn {_, from_manifest} ->
List.keyfind(f_list, to_charlist(from_manifest), 0)
end)
defp validate_downloadable(pack) do
if downloadable?(pack), do: :ok, else: {:error, :cant_download}
end end


@spec load_pack(String.t()) :: t() | nil
def load_pack(name) do
pack_file = Path.join([emoji_path(), name, "pack.json"])
defp copy_as(remote_pack, local_name) do
path = Path.join(emoji_path(), local_name)


if File.exists?(pack_file) do
pack_file
|> File.read!()
|> from_json()
|> Map.put(:pack_file, pack_file)
|> Map.put(:path, Path.dirname(pack_file))
|> Map.put(:name, name)
%__MODULE__{
name: local_name,
path: path,
files: remote_pack["files"],
pack_file: Path.join(path, "pack.json")
}
end

defp unzip(archive, pack_info, remote_pack, local_pack) do
with :ok <- File.mkdir_p!(local_pack.path) do
files = Enum.map(remote_pack["files"], fn {_, path} -> to_charlist(path) end)
# Fallback cannot contain a pack.json file
files = if pack_info[:fallback], do: files, else: ['pack.json' | files]

:zip.unzip(archive, cwd: to_charlist(local_pack.path), file_list: files)
end end
end end


defp from_json(json) do
map = Jason.decode!(json)
defp fetch_pack_info(remote_pack, uri, name) do
case remote_pack["pack"] do
%{"share-files" => true, "can-download" => true, "download-sha256" => sha} ->
{:ok,
%{
sha: sha,
url: URI.merge(uri, "/api/pleroma/emoji/packs/#{name}/archive") |> to_string()
}}

%{"fallback-src" => src, "fallback-src-sha256" => sha} when is_binary(src) ->
{:ok,
%{
sha: sha,
url: src,
fallback: true
}}


struct(__MODULE__, %{files: map["files"], pack: map["pack"]})
_ ->
{:error, "The pack was not set as shared and there is no fallback src to download from"}
end
end

defp download_archive(url, sha) do
with {:ok, %{body: archive}} <- Tesla.get(url) do
if Base.decode16!(sha) == :crypto.hash(:sha256, archive) do
{:ok, archive}
else
{:error, :imvalid_checksum}
end
end
end

defp fetch_archive(pack) do
hash = :crypto.hash(:md5, File.read!(pack.pack_file))

case Cachex.get!(:emoji_packs_cache, pack.name) do
%{hash: ^hash, pack_data: archive} -> archive
_ -> create_archive_and_cache(pack, hash)
end
end

defp fallback_sha_changed?(pack, data) do
is_binary(data[:"fallback-src"]) and data[:"fallback-src"] != pack.pack["fallback-src"]
end

defp update_sha_and_save_metadata(pack, data) do
with {:ok, %{body: zip}} <- Tesla.get(data[:"fallback-src"]),
:ok <- validate_has_all_files(pack, zip) do
fallback_sha = :sha256 |> :crypto.hash(zip) |> Base.encode16()

data
|> Map.put("fallback-src-sha256", fallback_sha)
|> save_metadata(pack)
end
end end


defp shareable_packs_available?(uri) do
uri
|> URI.merge("/.well-known/nodeinfo")
|> to_string()
|> Tesla.get!()
|> Map.get(:body)
|> Jason.decode!()
|> Map.get("links")
|> List.last()
|> Map.get("href")
# Get the actual nodeinfo address and fetch it
|> Tesla.get!()
|> Map.get(:body)
|> Jason.decode!()
|> get_in(["metadata", "features"])
|> Enum.member?("shareable_emoji_packs")
defp validate_has_all_files(pack, zip) do
with {:ok, f_list} <- :zip.unzip(zip, [:memory]) do
# Check if all files from the pack.json are in the archive
pack.files
|> Enum.all?(fn {_, from_manifest} ->
List.keyfind(f_list, to_charlist(from_manifest), 0)
end)
|> if(do: :ok, else: {:error, :incomplete})
end
end end
end end

+ 390
- 0
lib/pleroma/web/api_spec/operations/pleroma_emoji_pack_operation.ex View File

@@ -0,0 +1,390 @@
# 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.PleromaEmojiPackOperation 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 remote_operation do
%Operation{
tags: ["Emoji Packs"],
summary: "Make request to another instance for emoji packs list",
security: [%{"oAuth" => ["write"]}],
parameters: [url_param()],
operationId: "PleromaAPI.EmojiPackController.remote",
responses: %{
200 => emoji_packs_response(),
500 => Operation.response("Error", "application/json", ApiError)
}
}
end

def index_operation do
%Operation{
tags: ["Emoji Packs"],
summary: "Lists local custom emoji packs",
operationId: "PleromaAPI.EmojiPackController.index",
responses: %{
200 => emoji_packs_response()
}
}
end

def show_operation do
%Operation{
tags: ["Emoji Packs"],
summary: "Show emoji pack",
operationId: "PleromaAPI.EmojiPackController.show",
parameters: [name_param()],
responses: %{
200 => Operation.response("Emoji Pack", "application/json", emoji_pack()),
400 => Operation.response("Bad Request", "application/json", ApiError),
404 => Operation.response("Not Found", "application/json", ApiError)
}
}
end

def archive_operation do
%Operation{
tags: ["Emoji Packs"],
summary: "Requests a local pack archive from the instance",
operationId: "PleromaAPI.EmojiPackController.archive",
parameters: [name_param()],
responses: %{
200 =>
Operation.response("Archive file", "application/octet-stream", %Schema{
type: :string,
format: :binary
}),
403 => Operation.response("Forbidden", "application/json", ApiError),
404 => Operation.response("Not Found", "application/json", ApiError)
}
}
end

def download_operation do
%Operation{
tags: ["Emoji Packs"],
summary: "Download pack from another instance",
operationId: "PleromaAPI.EmojiPackController.download",
security: [%{"oAuth" => ["write"]}],
requestBody: request_body("Parameters", download_request(), required: true),
responses: %{
200 => ok_response(),
500 => Operation.response("Error", "application/json", ApiError)
}
}
end

defp download_request do
%Schema{
type: :object,
required: [:url, :name],
properties: %{
url: %Schema{
type: :string,
format: :uri,
description: "URL of the instance to download from"
},
name: %Schema{type: :string, format: :uri, description: "Pack Name"},
as: %Schema{type: :string, format: :uri, description: "Save as"}
}
}
end

def create_operation do
%Operation{
tags: ["Emoji Packs"],
summary: "Create an empty pack",
operationId: "PleromaAPI.EmojiPackController.create",
security: [%{"oAuth" => ["write"]}],
parameters: [name_param()],
responses: %{
200 => ok_response(),
400 => Operation.response("Not Found", "application/json", ApiError),
409 => Operation.response("Conflict", "application/json", ApiError),
500 => Operation.response("Error", "application/json", ApiError)
}
}
end

def delete_operation do
%Operation{
tags: ["Emoji Packs"],
summary: "Delete a custom emoji pack",
operationId: "PleromaAPI.EmojiPackController.delete",
security: [%{"oAuth" => ["write"]}],
parameters: [name_param()],
responses: %{
200 => ok_response(),
400 => Operation.response("Bad Request", "application/json", ApiError),
404 => Operation.response("Not Found", "application/json", ApiError)
}
}
end

def update_operation do
%Operation{
tags: ["Emoji Packs"],
summary: "Updates (replaces) pack metadata",
operationId: "PleromaAPI.EmojiPackController.update",
security: [%{"oAuth" => ["write"]}],
requestBody: request_body("Parameters", update_request(), required: true),
parameters: [name_param()],
responses: %{
200 => Operation.response("Metadata", "application/json", metadata()),
400 => Operation.response("Bad Request", "application/json", ApiError)
}
}
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"],
summary: "Imports packs from filesystem",
operationId: "PleromaAPI.EmojiPackController.import",
security: [%{"oAuth" => ["write"]}],
responses: %{
200 =>
Operation.response("Array of imported pack names", "application/json", %Schema{
type: :array,
items: %Schema{type: :string}
})
}
}
end

defp name_param do
Operation.parameter(:name, :path, :string, "Pack Name", example: "cofe", required: true)
end

defp url_param do
Operation.parameter(
:url,
:query,
%Schema{type: :string, format: :uri},
"URL of the instance",
required: true
)
end

defp ok_response do
Operation.response("Ok", "application/json", %Schema{type: :string, example: "ok"})
end

defp emoji_packs_response do
Operation.response(
"Object with pack names as keys and pack contents as values",
"application/json",
%Schema{
type: :object,
additionalProperties: emoji_pack(),
example: %{
"emojos" => emoji_pack().example
}
}
)
end

defp emoji_pack do
%Schema{
title: "EmojiPack",
type: :object,
properties: %{
files: files_object(),
pack: %Schema{
type: :object,
properties: %{
license: %Schema{type: :string},
homepage: %Schema{type: :string, format: :uri},
description: %Schema{type: :string},
"can-download": %Schema{type: :boolean},
"share-files": %Schema{type: :boolean},
"download-sha256": %Schema{type: :string}
}
}
},
example: %{
"files" => %{"emacs" => "emacs.png", "guix" => "guix.png"},
"pack" => %{
"license" => "Test license",
"homepage" => "https://pleroma.social",
"description" => "Test description",
"can-download" => true,
"share-files" => true,
"download-sha256" => "57482F30674FD3DE821FF48C81C00DA4D4AF1F300209253684ABA7075E5FC238"
}
}
}
end

defp files_object do
%Schema{
type: :object,
additionalProperties: %Schema{type: :string},
description: "Object with emoji names as keys and filenames as values"
}
end

defp update_request do
%Schema{
type: :object,
properties: %{
metadata: %Schema{
type: :object,
description: "Metadata to replace the old one",
properties: %{
license: %Schema{type: :string},
homepage: %Schema{type: :string, format: :uri},
description: %Schema{type: :string},
"fallback-src": %Schema{
type: :string,
format: :uri,
description: "Fallback url to download pack from"
},
"fallback-src-sha256": %Schema{
type: :string,
description: "SHA256 encoded for fallback pack archive"
},
"share-files": %Schema{type: :boolean, description: "Is pack allowed for sharing?"}
}
}
}
}
end

defp metadata do
%Schema{
type: :object,
properties: %{
license: %Schema{type: :string},
homepage: %Schema{type: :string, format: :uri},
description: %Schema{type: :string},
"fallback-src": %Schema{
type: :string,
format: :uri,
description: "Fallback url to download pack from"
},
"fallback-src-sha256": %Schema{
type: :string,
description: "SHA256 encoded for fallback pack archive"
},
"share-files": %Schema{type: :boolean, description: "Is pack allowed for sharing?"}
}
}
end
end

lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex → lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex View File

@@ -1,8 +1,10 @@
defmodule Pleroma.Web.PleromaAPI.EmojiAPIController do
defmodule Pleroma.Web.PleromaAPI.EmojiPackController do
use Pleroma.Web, :controller use Pleroma.Web, :controller


alias Pleroma.Emoji.Pack alias Pleroma.Emoji.Pack


plug(Pleroma.Web.ApiSpec.CastAndValidate)

plug( plug(
Pleroma.Plugs.OAuthScopesPlug, Pleroma.Plugs.OAuthScopesPlug,
%{scopes: ["write"], admin: true} %{scopes: ["write"], admin: true}
@@ -19,39 +21,37 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIController do
] ]
) )


plug(
:skip_plug,
[Pleroma.Plugs.OAuthScopesPlug, Pleroma.Plugs.ExpectPublicOrAuthenticatedCheckPlug]
when action in [:archive, :show, :list]
)
@skip_plugs [Pleroma.Plugs.OAuthScopesPlug, Pleroma.Plugs.ExpectPublicOrAuthenticatedCheckPlug]
plug(:skip_plug, @skip_plugs when action in [:archive, :show, :list])

defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaEmojiPackOperation


def remote(conn, %{"url" => url}) do
def remote(conn, %{url: url}) do
with {:ok, packs} <- Pack.list_remote(url) do with {:ok, packs} <- Pack.list_remote(url) do
json(conn, packs) json(conn, packs)
else else
{:shareable, _} ->
{:error, :not_shareable} ->
conn conn
|> put_status(:internal_server_error) |> put_status(:internal_server_error)
|> json(%{error: "The requested instance does not support sharing emoji packs"}) |> json(%{error: "The requested instance does not support sharing emoji packs"})
end end
end end


def list(conn, _params) do
def index(conn, _params) do
emoji_path = emoji_path =
Path.join(
Pleroma.Config.get!([:instance, :static_dir]),
"emoji"
)
[:instance, :static_dir]
|> Pleroma.Config.get!()
|> Path.join("emoji")


with {:ok, packs} <- Pack.list_local() do with {:ok, packs} <- Pack.list_local() do
json(conn, packs) json(conn, packs)
else else
{:create_dir, {:error, e}} ->
{:error, :create_dir, e} ->
conn conn
|> put_status(:internal_server_error) |> put_status(:internal_server_error)
|> json(%{error: "Failed to create the emoji pack directory at #{emoji_path}: #{e}"}) |> json(%{error: "Failed to create the emoji pack directory at #{emoji_path}: #{e}"})


{:ls, {:error, e}} ->
{:error, :ls, e} ->
conn conn
|> put_status(:internal_server_error) |> put_status(:internal_server_error)
|> json(%{ |> json(%{
@@ -60,13 +60,13 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIController do
end end
end end


def show(conn, %{"name" => name}) do
def show(conn, %{name: name}) do
name = String.trim(name) name = String.trim(name)


with {:ok, pack} <- Pack.show(name) do with {:ok, pack} <- Pack.show(name) do
json(conn, pack) json(conn, pack)
else else
{:loaded, _} ->
{:error, :not_found} ->
conn conn
|> put_status(:not_found) |> put_status(:not_found)
|> json(%{error: "Pack #{name} does not exist"}) |> json(%{error: "Pack #{name} does not exist"})
@@ -78,11 +78,11 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIController do
end end
end end


def archive(conn, %{"name" => name}) do
def archive(conn, %{name: name}) do
with {:ok, archive} <- Pack.get_archive(name) do with {:ok, archive} <- Pack.get_archive(name) do
send_download(conn, {:binary, archive}, filename: "#{name}.zip") send_download(conn, {:binary, archive}, filename: "#{name}.zip")
else else
{:can_download?, _} ->
{:error, :cant_download} ->
conn conn
|> put_status(:forbidden) |> put_status(:forbidden)
|> json(%{ |> json(%{
@@ -90,23 +90,23 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIController do
"Pack #{name} cannot be downloaded from this instance, either pack sharing was disabled for this pack or some files are missing" "Pack #{name} cannot be downloaded from this instance, either pack sharing was disabled for this pack or some files are missing"
}) })


{:exists?, _} ->
{:error, :not_found} ->
conn conn
|> put_status(:not_found) |> put_status(:not_found)
|> json(%{error: "Pack #{name} does not exist"}) |> json(%{error: "Pack #{name} does not exist"})
end end
end end


def download(conn, %{"url" => url, "name" => name} = params) do
with :ok <- Pack.download(name, url, params["as"]) do
def download(%{body_params: %{url: url, name: name} = params} = conn, _) do
with {:ok, _pack} <- Pack.download(name, url, params[:as]) do
json(conn, "ok") json(conn, "ok")
else else
{:shareable, _} ->
{:error, :not_shareable} ->
conn conn
|> put_status(:internal_server_error) |> put_status(:internal_server_error)
|> json(%{error: "The requested instance does not support sharing emoji packs"}) |> json(%{error: "The requested instance does not support sharing emoji packs"})


{:checksum, _} ->
{:error, :imvalid_checksum} ->
conn conn
|> put_status(:internal_server_error) |> put_status(:internal_server_error)
|> json(%{error: "SHA256 for the pack doesn't match the one sent by the server"}) |> json(%{error: "SHA256 for the pack doesn't match the one sent by the server"})
@@ -118,10 +118,10 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIController do
end end
end end


def create(conn, %{"name" => name}) do
def create(conn, %{name: name}) do
name = String.trim(name) name = String.trim(name)


with :ok <- Pack.create(name) do
with {:ok, _pack} <- Pack.create(name) do
json(conn, "ok") json(conn, "ok")
else else
{:error, :eexist} -> {:error, :eexist} ->
@@ -143,7 +143,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIController do
end end
end end


def delete(conn, %{"name" => name}) do
def delete(conn, %{name: name}) do
name = String.trim(name) name = String.trim(name)


with {:ok, deleted} when deleted != [] <- Pack.delete(name) do with {:ok, deleted} when deleted != [] <- Pack.delete(name) do
@@ -166,11 +166,11 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIController do
end end
end end


def update(conn, %{"name" => name, "metadata" => metadata}) do
def update(%{body_params: %{metadata: metadata}} = conn, %{name: name}) do
with {:ok, pack} <- Pack.update_metadata(name, metadata) do with {:ok, pack} <- Pack.update_metadata(name, metadata) do
json(conn, pack.pack) json(conn, pack.pack)
else else
{:has_all_files?, _} ->
{:error, :incomplete} ->
conn conn
|> put_status(:bad_request) |> put_status(:bad_request)
|> json(%{error: "The fallback archive does not have all files specified in pack.json"}) |> json(%{error: "The fallback archive does not have all files specified in pack.json"})
@@ -184,19 +184,19 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIController do
end end
end end


def add_file(conn, %{"name" => name} = params) do
filename = params["filename"] || get_filename(params["file"])
shortcode = params["shortcode"] || Path.basename(filename, Path.extname(filename))
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
with {:ok, pack} <- Pack.add_file(name, shortcode, filename, params[:file]) do
json(conn, pack.files) json(conn, pack.files)
else else
{:exists, _} ->
{:error, :already_exists} ->
conn conn
|> put_status(:conflict) |> put_status(:conflict)
|> json(%{error: "An emoji with the \"#{shortcode}\" shortcode already exists"}) |> json(%{error: "An emoji with the \"#{shortcode}\" shortcode already exists"})


{:loaded, _} ->
{:error, :not_found} ->
conn conn
|> put_status(:bad_request) |> put_status(:bad_request)
|> json(%{error: "pack \"#{name}\" is not found"}) |> json(%{error: "pack \"#{name}\" is not found"})
@@ -215,20 +215,20 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIController do
end end
end end


def update_file(conn, %{"name" => name, "shortcode" => shortcode} = params) do
new_shortcode = params["new_shortcode"]
new_filename = params["new_filename"]
force = params["force"] == true
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 with {:ok, pack} <- Pack.update_file(name, shortcode, new_shortcode, new_filename, force) do
json(conn, pack.files) json(conn, pack.files)
else else
{:exists, _} ->
{:error, :doesnt_exist} ->
conn conn
|> put_status(:bad_request) |> put_status(:bad_request)
|> json(%{error: "Emoji \"#{shortcode}\" does not exist"}) |> json(%{error: "Emoji \"#{shortcode}\" does not exist"})


{:not_used, _} ->
{:error, :already_exists} ->
conn conn
|> put_status(:conflict) |> put_status(:conflict)
|> json(%{ |> json(%{
@@ -236,7 +236,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIController do
"New shortcode \"#{new_shortcode}\" is already used. If you want to override emoji use 'force' option" "New shortcode \"#{new_shortcode}\" is already used. If you want to override emoji use 'force' option"
}) })


{:loaded, _} ->
{:error, :not_found} ->
conn conn
|> put_status(:bad_request) |> put_status(:bad_request)
|> json(%{error: "pack \"#{name}\" is not found"}) |> json(%{error: "pack \"#{name}\" is not found"})
@@ -255,16 +255,16 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIController do
end end
end end


def delete_file(conn, %{"name" => name, "shortcode" => shortcode}) do
def delete_file(conn, %{name: name, shortcode: shortcode}) do
with {:ok, pack} <- Pack.delete_file(name, shortcode) do with {:ok, pack} <- Pack.delete_file(name, shortcode) do
json(conn, pack.files) json(conn, pack.files)
else else
{:exists, _} ->
{:error, :doesnt_exist} ->
conn conn
|> put_status(:bad_request) |> put_status(:bad_request)
|> json(%{error: "Emoji \"#{shortcode}\" does not exist"}) |> json(%{error: "Emoji \"#{shortcode}\" does not exist"})


{:loaded, _} ->
{:error, :not_found} ->
conn conn
|> put_status(:bad_request) |> put_status(:bad_request)
|> json(%{error: "pack \"#{name}\" is not found"}) |> json(%{error: "pack \"#{name}\" is not found"})

+ 13
- 12
lib/pleroma/web/router.ex View File

@@ -216,24 +216,25 @@ defmodule Pleroma.Web.Router do
scope "/packs" do scope "/packs" do
pipe_through(:admin_api) pipe_through(:admin_api)


get("/import", EmojiAPIController, :import_from_filesystem)
get("/remote", EmojiAPIController, :remote)
post("/download", EmojiAPIController, :download)
get("/import", EmojiPackController, :import_from_filesystem)
get("/remote", EmojiPackController, :remote)
post("/download", EmojiPackController, :download)


post("/:name", EmojiAPIController, :create)
patch("/:name", EmojiAPIController, :update)
delete("/:name", EmojiAPIController, :delete)
post("/:name", EmojiPackController, :create)
patch("/:name", EmojiPackController, :update)
delete("/:name", EmojiPackController, :delete)


post("/:name/files", EmojiAPIController, :add_file)
patch("/:name/files", EmojiAPIController, :update_file)
delete("/:name/files", EmojiAPIController, :delete_file)
post("/:name/files", EmojiPackController, :add_file)
patch("/:name/files", EmojiPackController, :update_file)
delete("/:name/files", EmojiPackController, :delete_file)
end end


# Pack info / downloading # Pack info / downloading
scope "/packs" do scope "/packs" do
get("/", EmojiAPIController, :list)
get("/:name", EmojiAPIController, :show)
get("/:name/archive", EmojiAPIController, :archive)
pipe_through(:api)
get("/", EmojiPackController, :index)
get("/:name", EmojiPackController, :show)
get("/:name/archive", EmojiPackController, :archive)
end end
end end




+ 1
- 1
test/web/api_spec/schema_examples_test.exs View File

@@ -28,7 +28,7 @@ defmodule Pleroma.Web.ApiSpec.SchemaExamplesTest do
end end
end end


for {status, response} <- operation.responses do
for {status, response} <- operation.responses, is_map(response.content[@content_type]) do
describe "#{operation.operationId} - #{status} Response" do describe "#{operation.operationId} - #{status} Response" do
@schema resolve_schema(response.content[@content_type].schema) @schema resolve_schema(response.content[@content_type].schema)




test/web/pleroma_api/controllers/emoji_api_controller_test.exs → test/web/pleroma_api/controllers/emoji_pack_controller_test.exs View File

@@ -2,7 +2,7 @@
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only


defmodule Pleroma.Web.PleromaAPI.EmojiAPIControllerTest do
defmodule Pleroma.Web.PleromaAPI.EmojiPackControllerTest do
use Pleroma.Web.ConnCase use Pleroma.Web.ConnCase


import Tesla.Mock import Tesla.Mock
@@ -28,7 +28,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIControllerTest do
end end


test "GET /api/pleroma/emoji/packs", %{conn: conn} do test "GET /api/pleroma/emoji/packs", %{conn: conn} do
resp = conn |> get("/api/pleroma/emoji/packs") |> json_response(200)
resp = conn |> get("/api/pleroma/emoji/packs") |> json_response_and_validate_schema(200)


shared = resp["test_pack"] shared = resp["test_pack"]
assert shared["files"] == %{"blank" => "blank.png"} assert shared["files"] == %{"blank" => "blank.png"}
@@ -46,7 +46,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIControllerTest do
resp = resp =
conn conn
|> get("/api/pleroma/emoji/packs") |> get("/api/pleroma/emoji/packs")
|> json_response(200)
|> json_response_and_validate_schema(200)


mock(fn mock(fn
%{method: :get, url: "https://example.com/.well-known/nodeinfo"} -> %{method: :get, url: "https://example.com/.well-known/nodeinfo"} ->
@@ -60,10 +60,8 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIControllerTest do
end) end)


assert admin_conn assert admin_conn
|> get("/api/pleroma/emoji/packs/remote", %{
url: "https://example.com"
})
|> json_response(200) == resp
|> get("/api/pleroma/emoji/packs/remote?url=https://example.com")
|> json_response_and_validate_schema(200) == resp
end end


test "non shareable instance", %{admin_conn: admin_conn} do test "non shareable instance", %{admin_conn: admin_conn} do
@@ -76,8 +74,8 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIControllerTest do
end) end)


assert admin_conn assert admin_conn
|> get("/api/pleroma/emoji/packs/remote", %{url: "https://example.com"})
|> json_response(500) == %{
|> get("/api/pleroma/emoji/packs/remote?url=https://example.com")
|> json_response_and_validate_schema(500) == %{
"error" => "The requested instance does not support sharing emoji packs" "error" => "The requested instance does not support sharing emoji packs"
} }
end end
@@ -99,7 +97,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIControllerTest do
test "non existing pack", %{conn: conn} do test "non existing pack", %{conn: conn} do
assert conn assert conn
|> get("/api/pleroma/emoji/packs/test_pack_for_import/archive") |> get("/api/pleroma/emoji/packs/test_pack_for_import/archive")
|> json_response(:not_found) == %{
|> json_response_and_validate_schema(:not_found) == %{
"error" => "Pack test_pack_for_import does not exist" "error" => "Pack test_pack_for_import does not exist"
} }
end end
@@ -107,7 +105,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIControllerTest do
test "non downloadable pack", %{conn: conn} do test "non downloadable pack", %{conn: conn} do
assert conn assert conn
|> get("/api/pleroma/emoji/packs/test_pack_nonshared/archive") |> get("/api/pleroma/emoji/packs/test_pack_nonshared/archive")
|> json_response(:forbidden) == %{
|> json_response_and_validate_schema(:forbidden) == %{
"error" => "error" =>
"Pack test_pack_nonshared cannot be downloaded from this instance, either pack sharing was disabled for this pack or some files are missing" "Pack test_pack_nonshared cannot be downloaded from this instance, either pack sharing was disabled for this pack or some files are missing"
} }
@@ -132,7 +130,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIControllerTest do
} -> } ->
conn conn
|> get("/api/pleroma/emoji/packs/test_pack") |> get("/api/pleroma/emoji/packs/test_pack")
|> json_response(200)
|> json_response_and_validate_schema(200)
|> json() |> json()


%{ %{
@@ -150,7 +148,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIControllerTest do
} -> } ->
conn conn
|> get("/api/pleroma/emoji/packs/test_pack_nonshared") |> get("/api/pleroma/emoji/packs/test_pack_nonshared")
|> json_response(200)
|> json_response_and_validate_schema(200)
|> json() |> json()


%{ %{
@@ -161,23 +159,25 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIControllerTest do
end) end)


assert admin_conn assert admin_conn
|> put_req_header("content-type", "multipart/form-data")
|> post("/api/pleroma/emoji/packs/download", %{ |> post("/api/pleroma/emoji/packs/download", %{
url: "https://example.com", url: "https://example.com",
name: "test_pack", name: "test_pack",
as: "test_pack2" as: "test_pack2"
}) })
|> json_response(200) == "ok"
|> json_response_and_validate_schema(200) == "ok"


assert File.exists?("#{@emoji_path}/test_pack2/pack.json") assert File.exists?("#{@emoji_path}/test_pack2/pack.json")
assert File.exists?("#{@emoji_path}/test_pack2/blank.png") assert File.exists?("#{@emoji_path}/test_pack2/blank.png")


assert admin_conn assert admin_conn
|> delete("/api/pleroma/emoji/packs/test_pack2") |> delete("/api/pleroma/emoji/packs/test_pack2")
|> json_response(200) == "ok"
|> json_response_and_validate_schema(200) == "ok"


refute File.exists?("#{@emoji_path}/test_pack2") refute File.exists?("#{@emoji_path}/test_pack2")


assert admin_conn assert admin_conn
|> put_req_header("content-type", "multipart/form-data")
|> post( |> post(
"/api/pleroma/emoji/packs/download", "/api/pleroma/emoji/packs/download",
%{ %{
@@ -186,14 +186,14 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIControllerTest do
as: "test_pack_nonshared2" as: "test_pack_nonshared2"
} }
) )
|> json_response(200) == "ok"
|> json_response_and_validate_schema(200) == "ok"


assert File.exists?("#{@emoji_path}/test_pack_nonshared2/pack.json") assert File.exists?("#{@emoji_path}/test_pack_nonshared2/pack.json")
assert File.exists?("#{@emoji_path}/test_pack_nonshared2/blank.png") assert File.exists?("#{@emoji_path}/test_pack_nonshared2/blank.png")


assert admin_conn assert admin_conn
|> delete("/api/pleroma/emoji/packs/test_pack_nonshared2") |> delete("/api/pleroma/emoji/packs/test_pack_nonshared2")
|> json_response(200) == "ok"
|> json_response_and_validate_schema(200) == "ok"


refute File.exists?("#{@emoji_path}/test_pack_nonshared2") refute File.exists?("#{@emoji_path}/test_pack_nonshared2")
end end
@@ -208,6 +208,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIControllerTest do
end) end)


assert admin_conn assert admin_conn
|> put_req_header("content-type", "multipart/form-data")
|> post( |> post(
"/api/pleroma/emoji/packs/download", "/api/pleroma/emoji/packs/download",
%{ %{
@@ -216,7 +217,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIControllerTest do
as: "test_pack2" as: "test_pack2"
} }
) )
|> json_response(500) == %{
|> json_response_and_validate_schema(500) == %{
"error" => "The requested instance does not support sharing emoji packs" "error" => "The requested instance does not support sharing emoji packs"
} }
end end
@@ -233,10 +234,8 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIControllerTest do
method: :get, method: :get,
url: "https://example.com/api/pleroma/emoji/packs/pack_bad_sha" url: "https://example.com/api/pleroma/emoji/packs/pack_bad_sha"
} -> } ->
%Tesla.Env{
status: 200,
body: Pleroma.Emoji.Pack.load_pack("pack_bad_sha") |> Jason.encode!()
}
{:ok, pack} = Pleroma.Emoji.Pack.load_pack("pack_bad_sha")
%Tesla.Env{status: 200, body: Jason.encode!(pack)}


%{ %{
method: :get, method: :get,
@@ -249,12 +248,13 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIControllerTest do
end) end)


assert admin_conn assert admin_conn
|> put_req_header("content-type", "multipart/form-data")
|> post("/api/pleroma/emoji/packs/download", %{ |> post("/api/pleroma/emoji/packs/download", %{
url: "https://example.com", url: "https://example.com",
name: "pack_bad_sha", name: "pack_bad_sha",
as: "pack_bad_sha2" as: "pack_bad_sha2"
}) })
|> json_response(:internal_server_error) == %{
|> json_response_and_validate_schema(:internal_server_error) == %{
"error" => "SHA256 for the pack doesn't match the one sent by the server" "error" => "SHA256 for the pack doesn't match the one sent by the server"
} }
end end
@@ -271,19 +271,18 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIControllerTest do
method: :get, method: :get,
url: "https://example.com/api/pleroma/emoji/packs/test_pack" url: "https://example.com/api/pleroma/emoji/packs/test_pack"
} -> } ->
%Tesla.Env{
status: 200,
body: Pleroma.Emoji.Pack.load_pack("test_pack") |> Jason.encode!()
}
{:ok, pack} = Pleroma.Emoji.Pack.load_pack("test_pack")
%Tesla.Env{status: 200, body: Jason.encode!(pack)}
end) end)


assert admin_conn assert admin_conn
|> put_req_header("content-type", "multipart/form-data")
|> post("/api/pleroma/emoji/packs/download", %{ |> post("/api/pleroma/emoji/packs/download", %{
url: "https://example.com", url: "https://example.com",
name: "test_pack", name: "test_pack",
as: "test_pack2" as: "test_pack2"
}) })
|> json_response(:internal_server_error) == %{
|> json_response_and_validate_schema(:internal_server_error) == %{
"error" => "error" =>
"The pack was not set as shared and there is no fallback src to download from" "The pack was not set as shared and there is no fallback src to download from"
} }
@@ -311,8 +310,9 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIControllerTest do


test "for a pack without a fallback source", ctx do test "for a pack without a fallback source", ctx do
assert ctx[:admin_conn] assert ctx[:admin_conn]
|> put_req_header("content-type", "multipart/form-data")
|> patch("/api/pleroma/emoji/packs/test_pack", %{"metadata" => ctx[:new_data]}) |> patch("/api/pleroma/emoji/packs/test_pack", %{"metadata" => ctx[:new_data]})
|> json_response(200) == ctx[:new_data]
|> json_response_and_validate_schema(200) == ctx[:new_data]


assert Jason.decode!(File.read!(ctx[:pack_file]))["pack"] == ctx[:new_data] assert Jason.decode!(File.read!(ctx[:pack_file]))["pack"] == ctx[:new_data]
end end
@@ -336,8 +336,9 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIControllerTest do
) )


assert ctx[:admin_conn] assert ctx[:admin_conn]
|> put_req_header("content-type", "multipart/form-data")
|> patch("/api/pleroma/emoji/packs/test_pack", %{metadata: new_data}) |> patch("/api/pleroma/emoji/packs/test_pack", %{metadata: new_data})
|> json_response(200) == new_data_with_sha
|> json_response_and_validate_schema(200) == new_data_with_sha


assert Jason.decode!(File.read!(ctx[:pack_file]))["pack"] == new_data_with_sha assert Jason.decode!(File.read!(ctx[:pack_file]))["pack"] == new_data_with_sha
end end
@@ -355,8 +356,9 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIControllerTest do
new_data = Map.put(ctx[:new_data], "fallback-src", "https://nonshared-pack") new_data = Map.put(ctx[:new_data], "fallback-src", "https://nonshared-pack")


assert ctx[:admin_conn] assert ctx[:admin_conn]
|> put_req_header("content-type", "multipart/form-data")
|> patch("/api/pleroma/emoji/packs/test_pack", %{metadata: new_data}) |> patch("/api/pleroma/emoji/packs/test_pack", %{metadata: new_data})
|> json_response(:bad_request) == %{
|> json_response_and_validate_schema(:bad_request) == %{
"error" => "The fallback archive does not have all files specified in pack.json" "error" => "The fallback archive does not have all files specified in pack.json"
} }
end end
@@ -376,6 +378,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIControllerTest do


test "create shortcode exists", %{admin_conn: admin_conn} do test "create shortcode exists", %{admin_conn: admin_conn} do
assert admin_conn assert admin_conn
|> put_req_header("content-type", "multipart/form-data")
|> post("/api/pleroma/emoji/packs/test_pack/files", %{ |> post("/api/pleroma/emoji/packs/test_pack/files", %{
shortcode: "blank", shortcode: "blank",
filename: "dir/blank.png", filename: "dir/blank.png",
@@ -384,7 +387,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIControllerTest do
path: "#{@emoji_path}/test_pack/blank.png" path: "#{@emoji_path}/test_pack/blank.png"
} }
}) })
|> json_response(:conflict) == %{
|> json_response_and_validate_schema(:conflict) == %{
"error" => "An emoji with the \"blank\" shortcode already exists" "error" => "An emoji with the \"blank\" shortcode already exists"
} }
end end
@@ -393,6 +396,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIControllerTest do
on_exit(fn -> File.rm_rf!("#{@emoji_path}/test_pack/dir/") end) on_exit(fn -> File.rm_rf!("#{@emoji_path}/test_pack/dir/") end)


assert admin_conn assert admin_conn
|> put_req_header("content-type", "multipart/form-data")
|> post("/api/pleroma/emoji/packs/test_pack/files", %{ |> post("/api/pleroma/emoji/packs/test_pack/files", %{
shortcode: "blank2", shortcode: "blank2",
filename: "dir/blank.png", filename: "dir/blank.png",
@@ -401,17 +405,21 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIControllerTest do
path: "#{@emoji_path}/test_pack/blank.png" path: "#{@emoji_path}/test_pack/blank.png"
} }
}) })
|> json_response(200) == %{"blank" => "blank.png", "blank2" => "dir/blank.png"}
|> json_response_and_validate_schema(200) == %{
"blank" => "blank.png",
"blank2" => "dir/blank.png"
}


assert File.exists?("#{@emoji_path}/test_pack/dir/blank.png") assert File.exists?("#{@emoji_path}/test_pack/dir/blank.png")


assert admin_conn assert admin_conn
|> put_req_header("content-type", "multipart/form-data")
|> patch("/api/pleroma/emoji/packs/test_pack/files", %{ |> patch("/api/pleroma/emoji/packs/test_pack/files", %{
shortcode: "blank", shortcode: "blank",
new_shortcode: "blank2", new_shortcode: "blank2",
new_filename: "dir_2/blank_3.png" new_filename: "dir_2/blank_3.png"
}) })
|> json_response(:conflict) == %{
|> json_response_and_validate_schema(:conflict) == %{
"error" => "error" =>
"New shortcode \"blank2\" is already used. If you want to override emoji use 'force' option" "New shortcode \"blank2\" is already used. If you want to override emoji use 'force' option"
} }
@@ -421,6 +429,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIControllerTest do
on_exit(fn -> File.rm_rf!("#{@emoji_path}/test_pack/dir_2/") end) on_exit(fn -> File.rm_rf!("#{@emoji_path}/test_pack/dir_2/") end)


assert admin_conn assert admin_conn
|> put_req_header("content-type", "multipart/form-data")
|> post("/api/pleroma/emoji/packs/test_pack/files", %{ |> post("/api/pleroma/emoji/packs/test_pack/files", %{
shortcode: "blank2", shortcode: "blank2",
filename: "dir/blank.png", filename: "dir/blank.png",
@@ -429,18 +438,22 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIControllerTest do
path: "#{@emoji_path}/test_pack/blank.png" path: "#{@emoji_path}/test_pack/blank.png"
} }
}) })
|> json_response(200) == %{"blank" => "blank.png", "blank2" => "dir/blank.png"}
|> json_response_and_validate_schema(200) == %{
"blank" => "blank.png",
"blank2" => "dir/blank.png"
}


assert File.exists?("#{@emoji_path}/test_pack/dir/blank.png") assert File.exists?("#{@emoji_path}/test_pack/dir/blank.png")


assert admin_conn assert admin_conn
|> put_req_header("content-type", "multipart/form-data")
|> patch("/api/pleroma/emoji/packs/test_pack/files", %{ |> patch("/api/pleroma/emoji/packs/test_pack/files", %{
shortcode: "blank2", shortcode: "blank2",
new_shortcode: "blank3", new_shortcode: "blank3",
new_filename: "dir_2/blank_3.png", new_filename: "dir_2/blank_3.png",
force: true force: true
}) })
|> json_response(200) == %{
|> json_response_and_validate_schema(200) == %{
"blank" => "blank.png", "blank" => "blank.png",
"blank3" => "dir_2/blank_3.png" "blank3" => "dir_2/blank_3.png"
} }
@@ -450,6 +463,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIControllerTest do


test "with empty filename", %{admin_conn: admin_conn} do test "with empty filename", %{admin_conn: admin_conn} do
assert admin_conn assert admin_conn
|> put_req_header("content-type", "multipart/form-data")
|> post("/api/pleroma/emoji/packs/test_pack/files", %{ |> post("/api/pleroma/emoji/packs/test_pack/files", %{
shortcode: "blank2", shortcode: "blank2",
filename: "", filename: "",
@@ -458,13 +472,14 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIControllerTest do
path: "#{@emoji_path}/test_pack/blank.png" path: "#{@emoji_path}/test_pack/blank.png"
} }
}) })
|> json_response(:bad_request) == %{
|> json_response_and_validate_schema(:bad_request) == %{
"error" => "pack name, shortcode or filename cannot be empty" "error" => "pack name, shortcode or filename cannot be empty"
} }
end end


test "add file with not loaded pack", %{admin_conn: admin_conn} do test "add file with not loaded pack", %{admin_conn: admin_conn} do
assert admin_conn assert admin_conn
|> put_req_header("content-type", "multipart/form-data")
|> post("/api/pleroma/emoji/packs/not_loaded/files", %{ |> post("/api/pleroma/emoji/packs/not_loaded/files", %{
shortcode: "blank2", shortcode: "blank2",
filename: "dir/blank.png", filename: "dir/blank.png",
@@ -473,37 +488,43 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIControllerTest do
path: "#{@emoji_path}/test_pack/blank.png" path: "#{@emoji_path}/test_pack/blank.png"
} }
}) })
|> json_response(:bad_request) == %{
|> json_response_and_validate_schema(:bad_request) == %{
"error" => "pack \"not_loaded\" is not found" "error" => "pack \"not_loaded\" is not found"
} }
end end


test "remove file with not loaded pack", %{admin_conn: admin_conn} do test "remove file with not loaded pack", %{admin_conn: admin_conn} do
assert admin_conn assert admin_conn
|> delete("/api/pleroma/emoji/packs/not_loaded/files", %{shortcode: "blank3"})
|> json_response(:bad_request) == %{"error" => "pack \"not_loaded\" is not found"}
|> 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 end


test "remove file with empty shortcode", %{admin_conn: admin_conn} do test "remove file with empty shortcode", %{admin_conn: admin_conn} do
assert admin_conn assert admin_conn
|> delete("/api/pleroma/emoji/packs/not_loaded/files", %{shortcode: ""})
|> json_response(:bad_request) == %{
|> delete("/api/pleroma/emoji/packs/not_loaded/files?shortcode=")
|> json_response_and_validate_schema(:bad_request) == %{
"error" => "pack name or shortcode cannot be empty" "error" => "pack name or shortcode cannot be empty"
} }
end end


test "update file with not loaded pack", %{admin_conn: admin_conn} do test "update file with not loaded pack", %{admin_conn: admin_conn} do
assert admin_conn assert admin_conn
|> put_req_header("content-type", "multipart/form-data")
|> patch("/api/pleroma/emoji/packs/not_loaded/files", %{ |> patch("/api/pleroma/emoji/packs/not_loaded/files", %{
shortcode: "blank4", shortcode: "blank4",
new_shortcode: "blank3", new_shortcode: "blank3",
new_filename: "dir_2/blank_3.png" new_filename: "dir_2/blank_3.png"
}) })
|> json_response(:bad_request) == %{"error" => "pack \"not_loaded\" is not found"}
|> json_response_and_validate_schema(:bad_request) == %{
"error" => "pack \"not_loaded\" is not found"
}
end end


test "new with shortcode as file with update", %{admin_conn: admin_conn} do test "new with shortcode as file with update", %{admin_conn: admin_conn} do
assert admin_conn assert admin_conn
|> put_req_header("content-type", "multipart/form-data")
|> post("/api/pleroma/emoji/packs/test_pack/files", %{ |> post("/api/pleroma/emoji/packs/test_pack/files", %{
shortcode: "blank4", shortcode: "blank4",
filename: "dir/blank.png", filename: "dir/blank.png",
@@ -512,24 +533,31 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIControllerTest do
path: "#{@emoji_path}/test_pack/blank.png" path: "#{@emoji_path}/test_pack/blank.png"
} }
}) })
|> json_response(200) == %{"blank" => "blank.png", "blank4" => "dir/blank.png"}
|> json_response_and_validate_schema(200) == %{
"blank" => "blank.png",
"blank4" => "dir/blank.png"
}


assert File.exists?("#{@emoji_path}/test_pack/dir/blank.png") assert File.exists?("#{@emoji_path}/test_pack/dir/blank.png")


assert admin_conn assert admin_conn
|> put_req_header("content-type", "multipart/form-data")
|> patch("/api/pleroma/emoji/packs/test_pack/files", %{ |> patch("/api/pleroma/emoji/packs/test_pack/files", %{
shortcode: "blank4", shortcode: "blank4",
new_shortcode: "blank3", new_shortcode: "blank3",
new_filename: "dir_2/blank_3.png" new_filename: "dir_2/blank_3.png"
}) })
|> json_response(200) == %{"blank3" => "dir_2/blank_3.png", "blank" => "blank.png"}
|> json_response_and_validate_schema(200) == %{
"blank3" => "dir_2/blank_3.png",
"blank" => "blank.png"
}


refute File.exists?("#{@emoji_path}/test_pack/dir/") refute File.exists?("#{@emoji_path}/test_pack/dir/")
assert File.exists?("#{@emoji_path}/test_pack/dir_2/blank_3.png") assert File.exists?("#{@emoji_path}/test_pack/dir_2/blank_3.png")


assert admin_conn assert admin_conn
|> delete("/api/pleroma/emoji/packs/test_pack/files", %{shortcode: "blank3"})
|> json_response(200) == %{"blank" => "blank.png"}
|> delete("/api/pleroma/emoji/packs/test_pack/files?shortcode=blank3")
|> json_response_and_validate_schema(200) == %{"blank" => "blank.png"}


refute File.exists?("#{@emoji_path}/test_pack/dir_2/") refute File.exists?("#{@emoji_path}/test_pack/dir_2/")


@@ -546,11 +574,12 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIControllerTest do
end) end)


assert admin_conn assert admin_conn
|> put_req_header("content-type", "multipart/form-data")
|> post("/api/pleroma/emoji/packs/test_pack/files", %{ |> post("/api/pleroma/emoji/packs/test_pack/files", %{
shortcode: "blank_url", shortcode: "blank_url",
file: "https://test-blank/blank_url.png" file: "https://test-blank/blank_url.png"
}) })
|> json_response(200) == %{
|> json_response_and_validate_schema(200) == %{
"blank_url" => "blank_url.png", "blank_url" => "blank_url.png",
"blank" => "blank.png" "blank" => "blank.png"
} }
@@ -564,40 +593,51 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIControllerTest do
on_exit(fn -> File.rm_rf!("#{@emoji_path}/test_pack/shortcode.png") end) on_exit(fn -> File.rm_rf!("#{@emoji_path}/test_pack/shortcode.png") end)


assert admin_conn assert admin_conn
|> put_req_header("content-type", "multipart/form-data")
|> post("/api/pleroma/emoji/packs/test_pack/files", %{ |> post("/api/pleroma/emoji/packs/test_pack/files", %{
file: %Plug.Upload{ file: %Plug.Upload{
filename: "shortcode.png", filename: "shortcode.png",
path: "#{Pleroma.Config.get([:instance, :static_dir])}/add/shortcode.png" path: "#{Pleroma.Config.get([:instance, :static_dir])}/add/shortcode.png"
} }
}) })
|> json_response(200) == %{"shortcode" => "shortcode.png", "blank" => "blank.png"}
|> json_response_and_validate_schema(200) == %{
"shortcode" => "shortcode.png",
"blank" => "blank.png"
}
end end


test "remove non existing shortcode in pack.json", %{admin_conn: admin_conn} do test "remove non existing shortcode in pack.json", %{admin_conn: admin_conn} do
assert admin_conn assert admin_conn
|> delete("/api/pleroma/emoji/packs/test_pack/files", %{shortcode: "blank2"})
|> json_response(:bad_request) == %{"error" => "Emoji \"blank2\" does not exist"}
|> delete("/api/pleroma/emoji/packs/test_pack/files?shortcode=blank2")
|> json_response_and_validate_schema(:bad_request) == %{
"error" => "Emoji \"blank2\" does not exist"
}
end end


test "update non existing emoji", %{admin_conn: admin_conn} do test "update non existing emoji", %{admin_conn: admin_conn} do
assert admin_conn assert admin_conn
|> put_req_header("content-type", "multipart/form-data")
|> patch("/api/pleroma/emoji/packs/test_pack/files", %{ |> patch("/api/pleroma/emoji/packs/test_pack/files", %{
shortcode: "blank2", shortcode: "blank2",
new_shortcode: "blank3", new_shortcode: "blank3",
new_filename: "dir_2/blank_3.png" new_filename: "dir_2/blank_3.png"
}) })
|> json_response(:bad_request) == %{"error" => "Emoji \"blank2\" does not exist"}
|> json_response_and_validate_schema(:bad_request) == %{
"error" => "Emoji \"blank2\" does not exist"
}
end end


test "update with empty shortcode", %{admin_conn: admin_conn} do test "update with empty shortcode", %{admin_conn: admin_conn} do
assert admin_conn
|> patch("/api/pleroma/emoji/packs/test_pack/files", %{
shortcode: "blank",
new_filename: "dir_2/blank_3.png"
})
|> json_response(:bad_request) == %{
"error" => "new_shortcode or new_filename cannot be empty"
}
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 end


@@ -605,7 +645,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIControllerTest do
test "creating and deleting a pack", %{admin_conn: admin_conn} do test "creating and deleting a pack", %{admin_conn: admin_conn} do
assert admin_conn assert admin_conn
|> post("/api/pleroma/emoji/packs/test_created") |> post("/api/pleroma/emoji/packs/test_created")
|> json_response(200) == "ok"
|> json_response_and_validate_schema(200) == "ok"


assert File.exists?("#{@emoji_path}/test_created/pack.json") assert File.exists?("#{@emoji_path}/test_created/pack.json")


@@ -616,7 +656,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIControllerTest do


assert admin_conn assert admin_conn
|> delete("/api/pleroma/emoji/packs/test_created") |> delete("/api/pleroma/emoji/packs/test_created")
|> json_response(200) == "ok"
|> json_response_and_validate_schema(200) == "ok"


refute File.exists?("#{@emoji_path}/test_created/pack.json") refute File.exists?("#{@emoji_path}/test_created/pack.json")
end end
@@ -629,7 +669,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIControllerTest do


assert admin_conn assert admin_conn
|> post("/api/pleroma/emoji/packs/test_created") |> post("/api/pleroma/emoji/packs/test_created")
|> json_response(:conflict) == %{
|> json_response_and_validate_schema(:conflict) == %{
"error" => "A pack named \"test_created\" already exists" "error" => "A pack named \"test_created\" already exists"
} }


@@ -639,20 +679,26 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIControllerTest do
test "with empty name", %{admin_conn: admin_conn} do test "with empty name", %{admin_conn: admin_conn} do
assert admin_conn assert admin_conn
|> post("/api/pleroma/emoji/packs/ ") |> post("/api/pleroma/emoji/packs/ ")
|> json_response(:bad_request) == %{"error" => "pack name cannot be empty"}
|> json_response_and_validate_schema(:bad_request) == %{
"error" => "pack name cannot be empty"
}
end end
end end


test "deleting nonexisting pack", %{admin_conn: admin_conn} do test "deleting nonexisting pack", %{admin_conn: admin_conn} do
assert admin_conn assert admin_conn
|> delete("/api/pleroma/emoji/packs/non_existing") |> delete("/api/pleroma/emoji/packs/non_existing")
|> json_response(:not_found) == %{"error" => "Pack non_existing does not exist"}
|> json_response_and_validate_schema(:not_found) == %{
"error" => "Pack non_existing does not exist"
}
end end


test "deleting with empty name", %{admin_conn: admin_conn} do test "deleting with empty name", %{admin_conn: admin_conn} do
assert admin_conn assert admin_conn
|> delete("/api/pleroma/emoji/packs/ ") |> delete("/api/pleroma/emoji/packs/ ")
|> json_response(:bad_request) == %{"error" => "pack name cannot be empty"}
|> json_response_and_validate_schema(:bad_request) == %{
"error" => "pack name cannot be empty"
}
end end


test "filesystem import", %{admin_conn: admin_conn, conn: conn} do test "filesystem import", %{admin_conn: admin_conn, conn: conn} do
@@ -661,15 +707,15 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIControllerTest do
File.rm!("#{@emoji_path}/test_pack_for_import/pack.json") File.rm!("#{@emoji_path}/test_pack_for_import/pack.json")
end) end)


resp = conn |> get("/api/pleroma/emoji/packs") |> json_response(200)
resp = conn |> get("/api/pleroma/emoji/packs") |> json_response_and_validate_schema(200)


refute Map.has_key?(resp, "test_pack_for_import") refute Map.has_key?(resp, "test_pack_for_import")


assert admin_conn assert admin_conn
|> get("/api/pleroma/emoji/packs/import") |> get("/api/pleroma/emoji/packs/import")
|> json_response(200) == ["test_pack_for_import"]
|> json_response_and_validate_schema(200) == ["test_pack_for_import"]


resp = conn |> get("/api/pleroma/emoji/packs") |> json_response(200)
resp = conn |> get("/api/pleroma/emoji/packs") |> json_response_and_validate_schema(200)
assert resp["test_pack_for_import"]["files"] == %{"blank" => "blank.png"} assert resp["test_pack_for_import"]["files"] == %{"blank" => "blank.png"}


File.rm!("#{@emoji_path}/test_pack_for_import/pack.json") File.rm!("#{@emoji_path}/test_pack_for_import/pack.json")
@@ -686,9 +732,9 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIControllerTest do


assert admin_conn assert admin_conn
|> get("/api/pleroma/emoji/packs/import") |> get("/api/pleroma/emoji/packs/import")
|> json_response(200) == ["test_pack_for_import"]
|> json_response_and_validate_schema(200) == ["test_pack_for_import"]


resp = conn |> get("/api/pleroma/emoji/packs") |> json_response(200)
resp = conn |> get("/api/pleroma/emoji/packs") |> json_response_and_validate_schema(200)


assert resp["test_pack_for_import"]["files"] == %{ assert resp["test_pack_for_import"]["files"] == %{
"blank" => "blank.png", "blank" => "blank.png",
@@ -712,19 +758,23 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIControllerTest do
} = } =
conn conn
|> get("/api/pleroma/emoji/packs/test_pack") |> get("/api/pleroma/emoji/packs/test_pack")
|> json_response(200)
|> json_response_and_validate_schema(200)
end end


test "non existing pack", %{conn: conn} do test "non existing pack", %{conn: conn} do
assert conn assert conn
|> get("/api/pleroma/emoji/packs/non_existing") |> get("/api/pleroma/emoji/packs/non_existing")
|> json_response(:not_found) == %{"error" => "Pack non_existing does not exist"}
|> json_response_and_validate_schema(:not_found) == %{
"error" => "Pack non_existing does not exist"
}
end end


test "error name", %{conn: conn} do test "error name", %{conn: conn} do
assert conn assert conn
|> get("/api/pleroma/emoji/packs/ ") |> get("/api/pleroma/emoji/packs/ ")
|> json_response(:bad_request) == %{"error" => "pack name cannot be empty"}
|> json_response_and_validate_schema(:bad_request) == %{
"error" => "pack name cannot be empty"
}
end end
end end
end end

Loading…
Cancel
Save