forked from AkkomaGang/akkoma
Merge branch 'openapi/pleroma-api/emojis' into 'develop'
Add OpenAPI spec for EmojiPackController See merge request pleroma/pleroma!2549
This commit is contained in:
commit
265746b21f
7 changed files with 958 additions and 483 deletions
|
@ -426,7 +426,7 @@ The status posting endpoint takes an additional parameter, `in_reply_to_conversa
|
|||
* Authentication: required
|
||||
* Params:
|
||||
* `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.
|
||||
* Response: JSON, list of files for updated pack (hashmap -> shortcode => filename) with status 200, either error status with error message.
|
||||
|
||||
|
|
|
@ -16,162 +16,78 @@ defmodule Pleroma.Emoji.Pack do
|
|||
|
||||
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}
|
||||
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()
|
||||
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
|
||||
|
||||
def show(_), do: {:error, :empty_values}
|
||||
|
||||
@spec delete(String.t()) ::
|
||||
{: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
|
||||
|
||||
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
|
||||
|
||||
@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
|
||||
|
||||
def delete_file(_, _), do: {:error, :empty_values}
|
||||
|
||||
@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
|
||||
|
||||
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
|
||||
emoji_path = emoji_path()
|
||||
|
||||
|
@ -184,7 +100,7 @@ def import_from_filesystem do
|
|||
File.dir?(path) and File.exists?(Path.join(path, "pack.json"))
|
||||
end)
|
||||
|> Enum.map(&write_pack_contents/1)
|
||||
|> Enum.filter(& &1)
|
||||
|> Enum.reject(&is_nil/1)
|
||||
|
||||
{:ok, names}
|
||||
else
|
||||
|
@ -193,6 +109,117 @@ def import_from_filesystem do
|
|||
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
|
||||
pack = %__MODULE__{
|
||||
files: files_from_path(path),
|
||||
|
@ -201,7 +228,7 @@ defp write_pack_contents(path) do
|
|||
}
|
||||
|
||||
case save_pack(pack) do
|
||||
:ok -> Path.basename(path)
|
||||
{:ok, _pack} -> Path.basename(path)
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
|
@ -216,7 +243,8 @@ defp files_from_path(path) do
|
|||
# FIXME: Copy-pasted from Pleroma.Emoji/load_from_file_stream/2
|
||||
|
||||
# Create a map of shortcodes to filenames from emoji.txt
|
||||
File.read!(txt_path)
|
||||
txt_path
|
||||
|> File.read!()
|
||||
|> String.split("\n")
|
||||
|> Enum.map(&String.trim/1)
|
||||
|> Enum.map(fn line ->
|
||||
|
@ -226,21 +254,18 @@ defp files_from_path(path) do
|
|||
[name, 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
|
||||
end
|
||||
end)
|
||||
|> Enum.filter(& &1)
|
||||
|> Enum.into(%{})
|
||||
|> Enum.reject(&is_nil/1)
|
||||
|> Map.new()
|
||||
else
|
||||
# If there's no emoji.txt, assume all files
|
||||
# that are of certain extensions from the config are emojis and import them all
|
||||
|
@ -249,60 +274,20 @@ defp files_from_path(path) do
|
|||
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
|
||||
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
|
||||
|> Map.put("can-download", true)
|
||||
|> 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
|
||||
|
||||
defp downloadable?(pack) do
|
||||
|
@ -315,26 +300,6 @@ defp downloadable?(pack) do
|
|||
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
|
||||
files = ['pack.json' | Enum.map(pack.files, fn {_, file} -> to_charlist(file) end)]
|
||||
|
||||
|
@ -356,152 +321,221 @@ defp create_archive_and_cache(pack, hash) do
|
|||
result
|
||||
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
|
||||
}}
|
||||
|
||||
_ ->
|
||||
{:error,
|
||||
"The pack was not set as shared and there is no fallback src to download from"}
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
path = Path.join(emoji_path(), local_name)
|
||||
|
||||
pack = %__MODULE__{
|
||||
name: local_name,
|
||||
path: path,
|
||||
files: remote_pack["files"],
|
||||
pack_file: Path.join(path, "pack.json")
|
||||
}
|
||||
|
||||
File.mkdir_p!(pack.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]
|
||||
|
||||
{:ok, _} = :zip.unzip(archive, cwd: to_charlist(pack.path), file_list: files)
|
||||
|
||||
# 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
|
||||
|
||||
:ok
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp save_pack(pack), do: File.write(pack.pack_file, Jason.encode!(pack, pretty: true))
|
||||
|
||||
@spec save_metadata(map(), t()) :: {:ok, t()} | {:error, File.posix()}
|
||||
def save_metadata(metadata, %__MODULE__{} = pack) do
|
||||
pack = Map.put(pack, :pack, metadata)
|
||||
|
||||
with :ok <- save_pack(pack) do
|
||||
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
|
||||
|
||||
@spec update_metadata(String.t(), map()) :: {:ok, t()} | {:error, File.posix()}
|
||||
def update_metadata(name, data) do
|
||||
pack = load_pack(name)
|
||||
|
||||
fb_sha_changed? =
|
||||
not is_nil(data["fallback-src"]) and data["fallback-src"] != pack.pack["fallback-src"]
|
||||
|
||||
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()
|
||||
|
||||
data
|
||||
|> Map.put("fallback-src-sha256", fallback_sha)
|
||||
|> save_metadata(pack)
|
||||
else
|
||||
{:update?, _} -> save_metadata(data, pack)
|
||||
e -> e
|
||||
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)
|
||||
end
|
||||
|
||||
@spec load_pack(String.t()) :: t() | nil
|
||||
def load_pack(name) do
|
||||
pack_file = Path.join([emoji_path(), name, "pack.json"])
|
||||
|
||||
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)
|
||||
end
|
||||
end
|
||||
|
||||
defp from_json(json) do
|
||||
map = Jason.decode!(json)
|
||||
|
||||
struct(__MODULE__, %{files: map["files"], pack: map["pack"]})
|
||||
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_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
|
||||
|
||||
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
|
||||
|
||||
defp save_file(file, 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)
|
||||
end
|
||||
end
|
||||
|
||||
defp put_emoji(pack, shortcode, filename) do
|
||||
files = Map.put(pack.files, shortcode, filename)
|
||||
%{pack | files: files}
|
||||
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
|
||||
|
||||
defp create_subdirs(file_path) do
|
||||
if String.contains?(file_path, "/") do
|
||||
file_path
|
||||
|> Path.dirname()
|
||||
|> File.mkdir_p!()
|
||||
end
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
defp http_get(%URI{} = url), do: url |> to_string() |> http_get()
|
||||
|
||||
defp http_get(url) do
|
||||
with {:ok, %{body: body}} <- url |> Pleroma.HTTP.get() do
|
||||
Jason.decode(body)
|
||||
end
|
||||
end
|
||||
|
||||
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
|
||||
{:create_dir, {:error, e}} -> {:error, :create_dir, e}
|
||||
{:ls, {:error, e}} -> {:error, :ls, e}
|
||||
end
|
||||
end
|
||||
|
||||
defp validate_downloadable(pack) do
|
||||
if downloadable?(pack), do: :ok, else: {:error, :cant_download}
|
||||
end
|
||||
|
||||
defp copy_as(remote_pack, local_name) do
|
||||
path = Path.join(emoji_path(), local_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
|
||||
|
||||
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
|
||||
}}
|
||||
|
||||
_ ->
|
||||
{: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
|
||||
|
||||
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
|
||||
|
|
|
@ -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
|
|
@ -1,8 +1,10 @@
|
|||
defmodule Pleroma.Web.PleromaAPI.EmojiAPIController do
|
||||
defmodule Pleroma.Web.PleromaAPI.EmojiPackController do
|
||||
use Pleroma.Web, :controller
|
||||
|
||||
alias Pleroma.Emoji.Pack
|
||||
|
||||
plug(Pleroma.Web.ApiSpec.CastAndValidate)
|
||||
|
||||
plug(
|
||||
Pleroma.Plugs.OAuthScopesPlug,
|
||||
%{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])
|
||||
|
||||
def remote(conn, %{"url" => url}) do
|
||||
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaEmojiPackOperation
|
||||
|
||||
def remote(conn, %{url: url}) do
|
||||
with {:ok, packs} <- Pack.list_remote(url) do
|
||||
json(conn, packs)
|
||||
else
|
||||
{:shareable, _} ->
|
||||
{:error, :not_shareable} ->
|
||||
conn
|
||||
|> put_status(:internal_server_error)
|
||||
|> json(%{error: "The requested instance does not support sharing emoji packs"})
|
||||
end
|
||||
end
|
||||
|
||||
def list(conn, _params) do
|
||||
def index(conn, _params) do
|
||||
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
|
||||
json(conn, packs)
|
||||
else
|
||||
{:create_dir, {:error, e}} ->
|
||||
{:error, :create_dir, e} ->
|
||||
conn
|
||||
|> put_status(:internal_server_error)
|
||||
|> json(%{error: "Failed to create the emoji pack directory at #{emoji_path}: #{e}"})
|
||||
|
||||
{:ls, {:error, e}} ->
|
||||
{:error, :ls, e} ->
|
||||
conn
|
||||
|> put_status(:internal_server_error)
|
||||
|> json(%{
|
||||
|
@ -60,13 +60,13 @@ def list(conn, _params) do
|
|||
end
|
||||
end
|
||||
|
||||
def show(conn, %{"name" => name}) do
|
||||
def show(conn, %{name: name}) do
|
||||
name = String.trim(name)
|
||||
|
||||
with {:ok, pack} <- Pack.show(name) do
|
||||
json(conn, pack)
|
||||
else
|
||||
{:loaded, _} ->
|
||||
{:error, :not_found} ->
|
||||
conn
|
||||
|> put_status(:not_found)
|
||||
|> json(%{error: "Pack #{name} does not exist"})
|
||||
|
@ -78,11 +78,11 @@ def show(conn, %{"name" => name}) do
|
|||
end
|
||||
end
|
||||
|
||||
def archive(conn, %{"name" => name}) do
|
||||
def archive(conn, %{name: name}) do
|
||||
with {:ok, archive} <- Pack.get_archive(name) do
|
||||
send_download(conn, {:binary, archive}, filename: "#{name}.zip")
|
||||
else
|
||||
{:can_download?, _} ->
|
||||
{:error, :cant_download} ->
|
||||
conn
|
||||
|> put_status(:forbidden)
|
||||
|> json(%{
|
||||
|
@ -90,23 +90,23 @@ def archive(conn, %{"name" => name}) do
|
|||
"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
|
||||
|> put_status(:not_found)
|
||||
|> json(%{error: "Pack #{name} does not exist"})
|
||||
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")
|
||||
else
|
||||
{:shareable, _} ->
|
||||
{:error, :not_shareable} ->
|
||||
conn
|
||||
|> put_status(:internal_server_error)
|
||||
|> json(%{error: "The requested instance does not support sharing emoji packs"})
|
||||
|
||||
{:checksum, _} ->
|
||||
{:error, :imvalid_checksum} ->
|
||||
conn
|
||||
|> put_status(:internal_server_error)
|
||||
|> json(%{error: "SHA256 for the pack doesn't match the one sent by the server"})
|
||||
|
@ -118,10 +118,10 @@ def download(conn, %{"url" => url, "name" => name} = params) do
|
|||
end
|
||||
end
|
||||
|
||||
def create(conn, %{"name" => name}) do
|
||||
def create(conn, %{name: name}) do
|
||||
name = String.trim(name)
|
||||
|
||||
with :ok <- Pack.create(name) do
|
||||
with {:ok, _pack} <- Pack.create(name) do
|
||||
json(conn, "ok")
|
||||
else
|
||||
{:error, :eexist} ->
|
||||
|
@ -143,7 +143,7 @@ def create(conn, %{"name" => name}) do
|
|||
end
|
||||
end
|
||||
|
||||
def delete(conn, %{"name" => name}) do
|
||||
def delete(conn, %{name: name}) do
|
||||
name = String.trim(name)
|
||||
|
||||
with {:ok, deleted} when deleted != [] <- Pack.delete(name) do
|
||||
|
@ -166,11 +166,11 @@ def delete(conn, %{"name" => name}) do
|
|||
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
|
||||
json(conn, pack.pack)
|
||||
else
|
||||
{:has_all_files?, _} ->
|
||||
{:error, :incomplete} ->
|
||||
conn
|
||||
|> put_status(:bad_request)
|
||||
|> json(%{error: "The fallback archive does not have all files specified in pack.json"})
|
||||
|
@ -184,19 +184,19 @@ def update(conn, %{"name" => name, "metadata" => metadata}) do
|
|||
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)
|
||||
else
|
||||
{:exists, _} ->
|
||||
{:error, :already_exists} ->
|
||||
conn
|
||||
|> put_status(:conflict)
|
||||
|> json(%{error: "An emoji with the \"#{shortcode}\" shortcode already exists"})
|
||||
|
||||
{:loaded, _} ->
|
||||
{:error, :not_found} ->
|
||||
conn
|
||||
|> put_status(:bad_request)
|
||||
|> json(%{error: "pack \"#{name}\" is not found"})
|
||||
|
@ -215,20 +215,20 @@ def add_file(conn, %{"name" => name} = params) do
|
|||
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
|
||||
json(conn, pack.files)
|
||||
else
|
||||
{:exists, _} ->
|
||||
{:error, :doesnt_exist} ->
|
||||
conn
|
||||
|> put_status(:bad_request)
|
||||
|> json(%{error: "Emoji \"#{shortcode}\" does not exist"})
|
||||
|
||||
{:not_used, _} ->
|
||||
{:error, :already_exists} ->
|
||||
conn
|
||||
|> put_status(:conflict)
|
||||
|> json(%{
|
||||
|
@ -236,7 +236,7 @@ def update_file(conn, %{"name" => name, "shortcode" => shortcode} = params) do
|
|||
"New shortcode \"#{new_shortcode}\" is already used. If you want to override emoji use 'force' option"
|
||||
})
|
||||
|
||||
{:loaded, _} ->
|
||||
{:error, :not_found} ->
|
||||
conn
|
||||
|> put_status(:bad_request)
|
||||
|> json(%{error: "pack \"#{name}\" is not found"})
|
||||
|
@ -255,16 +255,16 @@ def update_file(conn, %{"name" => name, "shortcode" => shortcode} = params) do
|
|||
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
|
||||
json(conn, pack.files)
|
||||
else
|
||||
{:exists, _} ->
|
||||
{:error, :doesnt_exist} ->
|
||||
conn
|
||||
|> put_status(:bad_request)
|
||||
|> json(%{error: "Emoji \"#{shortcode}\" does not exist"})
|
||||
|
||||
{:loaded, _} ->
|
||||
{:error, :not_found} ->
|
||||
conn
|
||||
|> put_status(:bad_request)
|
||||
|> json(%{error: "pack \"#{name}\" is not found"})
|
|
@ -216,24 +216,25 @@ defmodule Pleroma.Web.Router do
|
|||
scope "/packs" do
|
||||
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
|
||||
|
||||
# Pack info / downloading
|
||||
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
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@ test "request body schema example matches schema" do
|
|||
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
|
||||
@schema resolve_schema(response.content[@content_type].schema)
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.PleromaAPI.EmojiAPIControllerTest do
|
||||
defmodule Pleroma.Web.PleromaAPI.EmojiPackControllerTest do
|
||||
use Pleroma.Web.ConnCase
|
||||
|
||||
import Tesla.Mock
|
||||
|
@ -28,7 +28,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIControllerTest do
|
|||
end
|
||||
|
||||
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"]
|
||||
assert shared["files"] == %{"blank" => "blank.png"}
|
||||
|
@ -46,7 +46,7 @@ test "shareable instance", %{admin_conn: admin_conn, conn: conn} do
|
|||
resp =
|
||||
conn
|
||||
|> get("/api/pleroma/emoji/packs")
|
||||
|> json_response(200)
|
||||
|> json_response_and_validate_schema(200)
|
||||
|
||||
mock(fn
|
||||
%{method: :get, url: "https://example.com/.well-known/nodeinfo"} ->
|
||||
|
@ -60,10 +60,8 @@ test "shareable instance", %{admin_conn: admin_conn, conn: conn} do
|
|||
end)
|
||||
|
||||
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
|
||||
|
||||
test "non shareable instance", %{admin_conn: admin_conn} do
|
||||
|
@ -76,8 +74,8 @@ test "non shareable instance", %{admin_conn: admin_conn} do
|
|||
end)
|
||||
|
||||
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"
|
||||
}
|
||||
end
|
||||
|
@ -99,7 +97,7 @@ test "download shared pack", %{conn: conn} do
|
|||
test "non existing pack", %{conn: conn} do
|
||||
assert conn
|
||||
|> 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"
|
||||
}
|
||||
end
|
||||
|
@ -107,7 +105,7 @@ test "non existing pack", %{conn: conn} do
|
|||
test "non downloadable pack", %{conn: conn} do
|
||||
assert conn
|
||||
|> get("/api/pleroma/emoji/packs/test_pack_nonshared/archive")
|
||||
|> json_response(:forbidden) == %{
|
||||
|> json_response_and_validate_schema(:forbidden) == %{
|
||||
"error" =>
|
||||
"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 @@ test "shared pack from remote and non shared from fallback-src", %{
|
|||
} ->
|
||||
conn
|
||||
|> get("/api/pleroma/emoji/packs/test_pack")
|
||||
|> json_response(200)
|
||||
|> json_response_and_validate_schema(200)
|
||||
|> json()
|
||||
|
||||
%{
|
||||
|
@ -150,7 +148,7 @@ test "shared pack from remote and non shared from fallback-src", %{
|
|||
} ->
|
||||
conn
|
||||
|> get("/api/pleroma/emoji/packs/test_pack_nonshared")
|
||||
|> json_response(200)
|
||||
|> json_response_and_validate_schema(200)
|
||||
|> json()
|
||||
|
||||
%{
|
||||
|
@ -161,23 +159,25 @@ test "shared pack from remote and non shared from fallback-src", %{
|
|||
end)
|
||||
|
||||
assert admin_conn
|
||||
|> put_req_header("content-type", "multipart/form-data")
|
||||
|> post("/api/pleroma/emoji/packs/download", %{
|
||||
url: "https://example.com",
|
||||
name: "test_pack",
|
||||
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/blank.png")
|
||||
|
||||
assert admin_conn
|
||||
|> 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")
|
||||
|
||||
assert admin_conn
|
||||
|> put_req_header("content-type", "multipart/form-data")
|
||||
|> post(
|
||||
"/api/pleroma/emoji/packs/download",
|
||||
%{
|
||||
|
@ -186,14 +186,14 @@ test "shared pack from remote and non shared from fallback-src", %{
|
|||
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/blank.png")
|
||||
|
||||
assert admin_conn
|
||||
|> 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")
|
||||
end
|
||||
|
@ -208,6 +208,7 @@ test "nonshareable instance", %{admin_conn: admin_conn} do
|
|||
end)
|
||||
|
||||
assert admin_conn
|
||||
|> put_req_header("content-type", "multipart/form-data")
|
||||
|> post(
|
||||
"/api/pleroma/emoji/packs/download",
|
||||
%{
|
||||
|
@ -216,7 +217,7 @@ test "nonshareable instance", %{admin_conn: admin_conn} do
|
|||
as: "test_pack2"
|
||||
}
|
||||
)
|
||||
|> json_response(500) == %{
|
||||
|> json_response_and_validate_schema(500) == %{
|
||||
"error" => "The requested instance does not support sharing emoji packs"
|
||||
}
|
||||
end
|
||||
|
@ -233,10 +234,8 @@ test "checksum fail", %{admin_conn: admin_conn} do
|
|||
method: :get,
|
||||
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,
|
||||
|
@ -249,12 +248,13 @@ test "checksum fail", %{admin_conn: admin_conn} do
|
|||
end)
|
||||
|
||||
assert admin_conn
|
||||
|> put_req_header("content-type", "multipart/form-data")
|
||||
|> post("/api/pleroma/emoji/packs/download", %{
|
||||
url: "https://example.com",
|
||||
name: "pack_bad_sha",
|
||||
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"
|
||||
}
|
||||
end
|
||||
|
@ -271,19 +271,18 @@ test "other error", %{admin_conn: admin_conn} do
|
|||
method: :get,
|
||||
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)
|
||||
|
||||
assert admin_conn
|
||||
|> put_req_header("content-type", "multipart/form-data")
|
||||
|> post("/api/pleroma/emoji/packs/download", %{
|
||||
url: "https://example.com",
|
||||
name: "test_pack",
|
||||
as: "test_pack2"
|
||||
})
|
||||
|> json_response(:internal_server_error) == %{
|
||||
|> json_response_and_validate_schema(:internal_server_error) == %{
|
||||
"error" =>
|
||||
"The pack was not set as shared and there is no fallback src to download from"
|
||||
}
|
||||
|
@ -311,8 +310,9 @@ test "other error", %{admin_conn: admin_conn} do
|
|||
|
||||
test "for a pack without a fallback source", ctx do
|
||||
assert ctx[:admin_conn]
|
||||
|> put_req_header("content-type", "multipart/form-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]
|
||||
end
|
||||
|
@ -336,8 +336,9 @@ test "for a pack with a fallback source", ctx do
|
|||
)
|
||||
|
||||
assert ctx[:admin_conn]
|
||||
|> put_req_header("content-type", "multipart/form-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
|
||||
end
|
||||
|
@ -355,8 +356,9 @@ test "when the fallback source doesn't have all the files", ctx do
|
|||
new_data = Map.put(ctx[:new_data], "fallback-src", "https://nonshared-pack")
|
||||
|
||||
assert ctx[:admin_conn]
|
||||
|> put_req_header("content-type", "multipart/form-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"
|
||||
}
|
||||
end
|
||||
|
@ -376,6 +378,7 @@ test "when the fallback source doesn't have all the files", ctx do
|
|||
|
||||
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",
|
||||
|
@ -384,7 +387,7 @@ test "create shortcode exists", %{admin_conn: admin_conn} do
|
|||
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"
|
||||
}
|
||||
end
|
||||
|
@ -393,6 +396,7 @@ 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: "blank2",
|
||||
filename: "dir/blank.png",
|
||||
|
@ -401,17 +405,21 @@ test "don't rewrite old emoji", %{admin_conn: admin_conn} do
|
|||
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 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(:conflict) == %{
|
||||
|> json_response_and_validate_schema(:conflict) == %{
|
||||
"error" =>
|
||||
"New shortcode \"blank2\" is already used. If you want to override emoji use 'force' option"
|
||||
}
|
||||
|
@ -421,6 +429,7 @@ 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: "blank2",
|
||||
filename: "dir/blank.png",
|
||||
|
@ -429,18 +438,22 @@ test "rewrite old emoji with force option", %{admin_conn: admin_conn} do
|
|||
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 admin_conn
|
||||
|> put_req_header("content-type", "multipart/form-data")
|
||||
|> patch("/api/pleroma/emoji/packs/test_pack/files", %{
|
||||
shortcode: "blank2",
|
||||
new_shortcode: "blank3",
|
||||
new_filename: "dir_2/blank_3.png",
|
||||
force: true
|
||||
})
|
||||
|> json_response(200) == %{
|
||||
|> json_response_and_validate_schema(200) == %{
|
||||
"blank" => "blank.png",
|
||||
"blank3" => "dir_2/blank_3.png"
|
||||
}
|
||||
|
@ -450,6 +463,7 @@ test "rewrite old emoji with force option", %{admin_conn: admin_conn} do
|
|||
|
||||
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: "",
|
||||
|
@ -458,13 +472,14 @@ test "with empty filename", %{admin_conn: admin_conn} do
|
|||
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"
|
||||
}
|
||||
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: "blank2",
|
||||
filename: "dir/blank.png",
|
||||
|
@ -473,37 +488,43 @@ test "add file with not loaded pack", %{admin_conn: admin_conn} do
|
|||
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"
|
||||
}
|
||||
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(: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
|
||||
|
||||
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(: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"
|
||||
}
|
||||
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(:bad_request) == %{"error" => "pack \"not_loaded\" is not found"}
|
||||
|> 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",
|
||||
|
@ -512,24 +533,31 @@ test "new with shortcode as file with update", %{admin_conn: admin_conn} do
|
|||
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 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(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/")
|
||||
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(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/")
|
||||
|
||||
|
@ -546,11 +574,12 @@ test "new with shortcode from url", %{admin_conn: admin_conn} do
|
|||
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(200) == %{
|
||||
|> json_response_and_validate_schema(200) == %{
|
||||
"blank_url" => "blank_url.png",
|
||||
"blank" => "blank.png"
|
||||
}
|
||||
|
@ -564,40 +593,51 @@ 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(200) == %{"shortcode" => "shortcode.png", "blank" => "blank.png"}
|
||||
|> json_response_and_validate_schema(200) == %{
|
||||
"shortcode" => "shortcode.png",
|
||||
"blank" => "blank.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: "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
|
||||
|
||||
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: "blank2",
|
||||
new_shortcode: "blank3",
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
|
@ -605,7 +645,7 @@ test "update with empty shortcode", %{admin_conn: admin_conn} do
|
|||
test "creating and deleting a pack", %{admin_conn: admin_conn} do
|
||||
assert admin_conn
|
||||
|> 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")
|
||||
|
||||
|
@ -616,7 +656,7 @@ test "creating and deleting a pack", %{admin_conn: admin_conn} do
|
|||
|
||||
assert admin_conn
|
||||
|> 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")
|
||||
end
|
||||
|
@ -629,7 +669,7 @@ test "if pack exists", %{admin_conn: admin_conn} do
|
|||
|
||||
assert admin_conn
|
||||
|> post("/api/pleroma/emoji/packs/test_created")
|
||||
|> json_response(:conflict) == %{
|
||||
|> json_response_and_validate_schema(:conflict) == %{
|
||||
"error" => "A pack named \"test_created\" already exists"
|
||||
}
|
||||
|
||||
|
@ -639,20 +679,26 @@ test "if pack exists", %{admin_conn: admin_conn} do
|
|||
test "with empty name", %{admin_conn: admin_conn} do
|
||||
assert admin_conn
|
||||
|> 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
|
||||
|
||||
test "deleting nonexisting pack", %{admin_conn: admin_conn} do
|
||||
assert admin_conn
|
||||
|> 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
|
||||
|
||||
test "deleting with empty name", %{admin_conn: admin_conn} do
|
||||
assert admin_conn
|
||||
|> 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
|
||||
|
||||
test "filesystem import", %{admin_conn: admin_conn, conn: conn} do
|
||||
|
@ -661,15 +707,15 @@ test "filesystem import", %{admin_conn: admin_conn, conn: conn} do
|
|||
File.rm!("#{@emoji_path}/test_pack_for_import/pack.json")
|
||||
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")
|
||||
|
||||
assert admin_conn
|
||||
|> 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"}
|
||||
|
||||
File.rm!("#{@emoji_path}/test_pack_for_import/pack.json")
|
||||
|
@ -686,9 +732,9 @@ test "filesystem import", %{admin_conn: admin_conn, conn: conn} do
|
|||
|
||||
assert admin_conn
|
||||
|> 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",
|
||||
|
@ -712,19 +758,23 @@ test "shows pack.json", %{conn: conn} do
|
|||
} =
|
||||
conn
|
||||
|> get("/api/pleroma/emoji/packs/test_pack")
|
||||
|> json_response(200)
|
||||
|> json_response_and_validate_schema(200)
|
||||
end
|
||||
|
||||
test "non existing pack", %{conn: conn} do
|
||||
assert conn
|
||||
|> 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
|
||||
|
||||
test "error name", %{conn: conn} do
|
||||
assert conn
|
||||
|> 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
|
Loading…
Reference in a new issue