From a31af93e1d10d9db8796d86ccda35873697b5a4c Mon Sep 17 00:00:00 2001
From: Maksim Pechnikov
Date: Tue, 10 Sep 2019 16:43:10 +0300
Subject: [PATCH 01/15] added tests /activity_pub/transmogrifier.ex
---
.../web/activity_pub/transmogrifier.ex | 264 +++++++-----------
test/web/activity_pub/transmogrifier_test.exs | 162 +++++++++++
2 files changed, 270 insertions(+), 156 deletions(-)
diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex
index 468961bd0..93b3a1f97 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -41,8 +41,7 @@ def fix_object(object, options \\ []) do
end
def fix_summary(%{"summary" => nil} = object) do
- object
- |> Map.put("summary", "")
+ Map.put(object, "summary", "")
end
def fix_summary(%{"summary" => _} = object) do
@@ -50,10 +49,7 @@ def fix_summary(%{"summary" => _} = object) do
object
end
- def fix_summary(object) do
- object
- |> Map.put("summary", "")
- end
+ def fix_summary(object), do: Map.put(object, "summary", "")
def fix_addressing_list(map, field) do
cond do
@@ -73,13 +69,9 @@ def fix_explicit_addressing(
explicit_mentions,
follower_collection
) do
- explicit_to =
- to
- |> Enum.filter(fn x -> x in explicit_mentions end)
+ explicit_to = Enum.filter(to, fn x -> x in explicit_mentions end)
- explicit_cc =
- to
- |> Enum.filter(fn x -> x not in explicit_mentions end)
+ explicit_cc = Enum.filter(to, fn x -> x not in explicit_mentions end)
final_cc =
(cc ++ explicit_cc)
@@ -97,13 +89,19 @@ def fix_explicit_addressing(object, _explicit_mentions, _followers_collection),
def fix_explicit_addressing(%{"directMessage" => true} = object), do: object
def fix_explicit_addressing(object) do
- explicit_mentions =
+ explicit_mentions = Utils.determine_explicit_mentions(object)
+
+ %User{follower_address: follower_collection} =
object
- |> Utils.determine_explicit_mentions()
+ |> Containment.get_actor()
+ |> User.get_cached_by_ap_id()
- follower_collection = User.get_cached_by_ap_id(Containment.get_actor(object)).follower_address
-
- explicit_mentions = explicit_mentions ++ [Pleroma.Constants.as_public(), follower_collection]
+ explicit_mentions =
+ explicit_mentions ++
+ [
+ Pleroma.Constants.as_public(),
+ follower_collection
+ ]
fix_explicit_addressing(object, explicit_mentions, follower_collection)
end
@@ -147,48 +145,25 @@ def fix_addressing(object) do
end
def fix_actor(%{"attributedTo" => actor} = object) do
- object
- |> Map.put("actor", Containment.get_actor(%{"actor" => actor}))
+ Map.put(object, "actor", Containment.get_actor(%{"actor" => actor}))
end
def fix_in_reply_to(object, options \\ [])
def fix_in_reply_to(%{"inReplyTo" => in_reply_to} = object, options)
when not is_nil(in_reply_to) do
- in_reply_to_id =
- cond do
- is_bitstring(in_reply_to) ->
- in_reply_to
-
- is_map(in_reply_to) && is_bitstring(in_reply_to["id"]) ->
- in_reply_to["id"]
-
- is_list(in_reply_to) && is_bitstring(Enum.at(in_reply_to, 0)) ->
- Enum.at(in_reply_to, 0)
-
- # Maybe I should output an error too?
- true ->
- ""
- end
-
+ in_reply_to_id = prepare_in_reply_to(in_reply_to)
object = Map.put(object, "inReplyToAtomUri", in_reply_to_id)
if Federator.allowed_incoming_reply_depth?(options[:depth]) do
- case get_obj_helper(in_reply_to_id, options) do
- {:ok, replied_object} ->
- with %Activity{} = _activity <-
- Activity.get_create_by_object_ap_id(replied_object.data["id"]) do
- object
- |> Map.put("inReplyTo", replied_object.data["id"])
- |> Map.put("inReplyToAtomUri", object["inReplyToAtomUri"] || in_reply_to_id)
- |> Map.put("conversation", replied_object.data["context"] || object["conversation"])
- |> Map.put("context", replied_object.data["context"] || object["conversation"])
- else
- e ->
- Logger.error("Couldn't fetch \"#{inspect(in_reply_to_id)}\", error: #{inspect(e)}")
- object
- end
-
+ with {:ok, replied_object} <- get_obj_helper(in_reply_to_id, options),
+ %Activity{} = _ <- Activity.get_create_by_object_ap_id(replied_object.data["id"]) do
+ object
+ |> Map.put("inReplyTo", replied_object.data["id"])
+ |> Map.put("inReplyToAtomUri", object["inReplyToAtomUri"] || in_reply_to_id)
+ |> Map.put("conversation", replied_object.data["context"] || object["conversation"])
+ |> Map.put("context", replied_object.data["context"] || object["conversation"])
+ else
e ->
Logger.error("Couldn't fetch \"#{inspect(in_reply_to_id)}\", error: #{inspect(e)}")
object
@@ -200,6 +175,22 @@ def fix_in_reply_to(%{"inReplyTo" => in_reply_to} = object, options)
def fix_in_reply_to(object, _options), do: object
+ defp prepare_in_reply_to(in_reply_to) do
+ cond do
+ is_bitstring(in_reply_to) ->
+ in_reply_to
+
+ is_map(in_reply_to) && is_bitstring(in_reply_to["id"]) ->
+ in_reply_to["id"]
+
+ is_list(in_reply_to) && is_bitstring(Enum.at(in_reply_to, 0)) ->
+ Enum.at(in_reply_to, 0)
+
+ true ->
+ ""
+ end
+ end
+
def fix_context(object) do
context = object["context"] || object["conversation"] || Utils.generate_context_id()
@@ -210,8 +201,7 @@ def fix_context(object) do
def fix_attachments(%{"attachment" => attachment} = object) when is_list(attachment) do
attachments =
- attachment
- |> Enum.map(fn data ->
+ Enum.map(attachment, fn data ->
media_type = data["mediaType"] || data["mimeType"]
href = data["url"] || data["href"]
@@ -222,30 +212,23 @@ def fix_attachments(%{"attachment" => attachment} = object) when is_list(attachm
|> Map.put("url", url)
end)
- object
- |> Map.put("attachment", attachments)
+ Map.put(object, "attachment", attachments)
end
def fix_attachments(%{"attachment" => attachment} = object) when is_map(attachment) do
- Map.put(object, "attachment", [attachment])
- |> fix_attachments()
+ fix_attachments(Map.put(object, "attachment", [attachment]))
end
def fix_attachments(object), do: object
def fix_url(%{"url" => url} = object) when is_map(url) do
- object
- |> Map.put("url", url["href"])
+ Map.put(object, "url", url["href"])
end
def fix_url(%{"type" => "Video", "url" => url} = object) when is_list(url) do
first_element = Enum.at(url, 0)
- link_element =
- url
- |> Enum.filter(fn x -> is_map(x) end)
- |> Enum.filter(fn x -> x["mimeType"] == "text/html" end)
- |> Enum.at(0)
+ link_element = Enum.find(url, fn x -> is_map(x) and x["mimeType"] == "text/html" end)
object
|> Map.put("attachment", [first_element])
@@ -263,36 +246,32 @@ def fix_url(%{"type" => object_type, "url" => url} = object)
true -> ""
end
- object
- |> Map.put("url", url_string)
+ Map.put(object, "url", url_string)
end
def fix_url(object), do: object
def fix_emoji(%{"tag" => tags} = object) when is_list(tags) do
- emoji = tags |> Enum.filter(fn data -> data["type"] == "Emoji" and data["icon"] end)
-
emoji =
- emoji
+ tags
+ |> Enum.filter(fn data -> data["type"] == "Emoji" and data["icon"] end)
|> Enum.reduce(%{}, fn data, mapping ->
name = String.trim(data["name"], ":")
- mapping |> Map.put(name, data["icon"]["url"])
+ Map.put(mapping, name, data["icon"]["url"])
end)
# we merge mastodon and pleroma emoji into a single mapping, to allow for both wire formats
emoji = Map.merge(object["emoji"] || %{}, emoji)
- object
- |> Map.put("emoji", emoji)
+ Map.put(object, "emoji", emoji)
end
def fix_emoji(%{"tag" => %{"type" => "Emoji"} = tag} = object) do
name = String.trim(tag["name"], ":")
emoji = %{name => tag["icon"]["url"]}
- object
- |> Map.put("emoji", emoji)
+ Map.put(object, "emoji", emoji)
end
def fix_emoji(object), do: object
@@ -303,17 +282,13 @@ def fix_tag(%{"tag" => tag} = object) when is_list(tag) do
|> Enum.filter(fn data -> data["type"] == "Hashtag" and data["name"] end)
|> Enum.map(fn data -> String.slice(data["name"], 1..-1) end)
- combined = tag ++ tags
-
- object
- |> Map.put("tag", combined)
+ Map.put(object, "tag", tag ++ tags)
end
def fix_tag(%{"tag" => %{"type" => "Hashtag", "name" => hashtag} = tag} = object) do
combined = [tag, String.slice(hashtag, 1..-1)]
- object
- |> Map.put("tag", combined)
+ Map.put(object, "tag", combined)
end
def fix_tag(%{"tag" => %{} = tag} = object), do: Map.put(object, "tag", [tag])
@@ -325,8 +300,7 @@ def fix_content_map(%{"contentMap" => content_map} = object) do
content_groups = Map.to_list(content_map)
{_, content} = Enum.at(content_groups, 0)
- object
- |> Map.put("content", content)
+ Map.put(object, "content", content)
end
def fix_content_map(object), do: object
@@ -335,16 +309,11 @@ def fix_type(object, options \\ [])
def fix_type(%{"inReplyTo" => reply_id, "name" => _} = object, options)
when is_binary(reply_id) do
- reply =
- with true <- Federator.allowed_incoming_reply_depth?(options[:depth]),
- {:ok, object} <- get_obj_helper(reply_id, options) do
- object
- end
-
- if reply && reply.data["type"] == "Question" do
+ with true <- Federator.allowed_incoming_reply_depth?(options[:depth]),
+ {:ok, %{data: %{"type" => "Question"} = _} = _} <- get_obj_helper(reply_id, options) do
Map.put(object, "type", "Answer")
else
- object
+ _ -> object
end
end
@@ -376,6 +345,17 @@ defp get_follow_activity(follow_object, followed) do
end
end
+ # Reduce the object list to find the reported user.
+ defp get_reported(objects) do
+ Enum.reduce_while(objects, nil, fn ap_id, _ ->
+ with %User{} = user <- User.get_cached_by_ap_id(ap_id) do
+ {:halt, user}
+ else
+ _ -> {:cont, nil}
+ end
+ end)
+ end
+
def handle_incoming(data, options \\ [])
# Flag objects are placed ahead of the ID check because Mastodon 2.8 and earlier send them
@@ -384,31 +364,19 @@ def handle_incoming(%{"type" => "Flag", "object" => objects, "actor" => actor} =
with context <- data["context"] || Utils.generate_context_id(),
content <- data["content"] || "",
%User{} = actor <- User.get_cached_by_ap_id(actor),
-
# Reduce the object list to find the reported user.
- %User{} = account <-
- Enum.reduce_while(objects, nil, fn ap_id, _ ->
- with %User{} = user <- User.get_cached_by_ap_id(ap_id) do
- {:halt, user}
- else
- _ -> {:cont, nil}
- end
- end),
-
+ %User{} = account <- get_reported(objects),
# Remove the reported user from the object list.
statuses <- Enum.filter(objects, fn ap_id -> ap_id != account.ap_id end) do
- params = %{
+ %{
actor: actor,
context: context,
account: account,
statuses: statuses,
content: content,
- additional: %{
- "cc" => [account.ap_id]
- }
+ additional: %{"cc" => [account.ap_id]}
}
-
- ActivityPub.flag(params)
+ |> ActivityPub.flag()
end
end
@@ -755,8 +723,13 @@ def handle_incoming(
def handle_incoming(_, _), do: :error
+ @spec get_obj_helper(String.t(), Keyword.t()) :: {:ok, Object.t()} | nil
def get_obj_helper(id, options \\ []) do
- if object = Object.normalize(id, true, options), do: {:ok, object}, else: nil
+ if object = Object.normalize(id, true, options) do
+ {:ok, object}
+ else
+ nil
+ end
end
def set_reply_to_uri(%{"inReplyTo" => in_reply_to} = object) when is_binary(in_reply_to) do
@@ -855,27 +828,24 @@ def prepare_outgoing(%{"type" => _type} = data) do
{:ok, data}
end
- def maybe_fix_object_url(data) do
- if is_binary(data["object"]) and not String.starts_with?(data["object"], "http") do
- case get_obj_helper(data["object"]) do
- {:ok, relative_object} ->
- if relative_object.data["external_url"] do
- _data =
- data
- |> Map.put("object", relative_object.data["external_url"])
- else
- data
- end
-
- e ->
- Logger.error("Couldn't fetch #{data["object"]} #{inspect(e)}")
- data
- end
+ def maybe_fix_object_url(%{"object" => object} = data) when is_binary(object) do
+ with false <- String.starts_with?(object, "http"),
+ {:fetch, {:ok, relative_object}} <- {:fetch, get_obj_helper(object)},
+ %{data: %{"external_url" => external_url}} when not is_nil(external_url) <-
+ relative_object do
+ Map.put(data, "object", external_url)
else
- data
+ {:fetch, e} ->
+ Logger.error("Couldn't fetch #{object} #{inspect(e)}")
+ data
+
+ _ ->
+ data
end
end
+ def maybe_fix_object_url(data), do: data
+
def add_hashtags(object) do
tags =
(object["tag"] || [])
@@ -893,8 +863,7 @@ def add_hashtags(object) do
tag
end)
- object
- |> Map.put("tag", tags)
+ Map.put(object, "tag", tags)
end
def add_mention_tags(object) do
@@ -907,15 +876,13 @@ def add_mention_tags(object) do
tags = object["tag"] || []
- object
- |> Map.put("tag", tags ++ mentions)
+ Map.put(object, "tag", tags ++ mentions)
end
def add_emoji_tags(%User{info: %{"emoji" => _emoji} = user_info} = object) do
user_info = add_emoji_tags(user_info)
- object
- |> Map.put(:info, user_info)
+ Map.put(object, :info, user_info)
end
# TODO: we should probably send mtime instead of unix epoch time for updated
@@ -923,8 +890,7 @@ def add_emoji_tags(%{"emoji" => emoji} = object) do
tags = object["tag"] || []
out =
- emoji
- |> Enum.map(fn {name, url} ->
+ Enum.map(emoji, fn {name, url} ->
%{
"icon" => %{"url" => url, "type" => "Image"},
"name" => ":" <> name <> ":",
@@ -934,13 +900,10 @@ def add_emoji_tags(%{"emoji" => emoji} = object) do
}
end)
- object
- |> Map.put("tag", tags ++ out)
+ Map.put(object, "tag", tags ++ out)
end
- def add_emoji_tags(object) do
- object
- end
+ def add_emoji_tags(object), do: object
def set_conversation(object) do
Map.put(object, "conversation", object["context"])
@@ -959,9 +922,7 @@ def set_type(object), do: object
def add_attributed_to(object) do
attributed_to = object["attributedTo"] || object["actor"]
-
- object
- |> Map.put("attributedTo", attributed_to)
+ Map.put(object, "attributedTo", attributed_to)
end
def prepare_attachments(object) do
@@ -972,8 +933,7 @@ def prepare_attachments(object) do
%{"url" => href, "mediaType" => media_type, "name" => data["name"], "type" => "Document"}
end)
- object
- |> Map.put("attachment", attachments)
+ Map.put(object, "attachment", attachments)
end
defp strip_internal_fields(object) do
@@ -990,12 +950,9 @@ defp strip_internal_fields(object) do
end
defp strip_internal_tags(%{"tag" => tags} = object) do
- tags =
- tags
- |> Enum.filter(fn x -> is_map(x) end)
+ tags = Enum.filter(tags, fn x -> is_map(x) end)
- object
- |> Map.put("tag", tags)
+ Map.put(object, "tag", tags)
end
defp strip_internal_tags(object), do: object
@@ -1074,16 +1031,11 @@ def maybe_retire_websub(ap_id) do
end
end
- def maybe_fix_user_url(data) do
- if is_map(data["url"]) do
- Map.put(data, "url", data["url"]["href"])
- else
- data
- end
+ def maybe_fix_user_url(%{"url" => url} = data) when is_map(url) do
+ Map.put(data, "url", url["href"])
end
- def maybe_fix_user_object(data) do
- data
- |> maybe_fix_user_url
- end
+ def maybe_fix_user_url(data), do: data
+
+ def maybe_fix_user_object(data), do: maybe_fix_user_url(data)
end
diff --git a/test/web/activity_pub/transmogrifier_test.exs b/test/web/activity_pub/transmogrifier_test.exs
index 0661d5d7c..63c869d35 100644
--- a/test/web/activity_pub/transmogrifier_test.exs
+++ b/test/web/activity_pub/transmogrifier_test.exs
@@ -1451,4 +1451,166 @@ test "removes recipient's follower collection from cc", %{user: user} do
refute recipient.follower_address in fixed_object["to"]
end
end
+
+ describe "fix_summary/1" do
+ test "returns fixed object" do
+ assert Transmogrifier.fix_summary(%{"summary" => nil}) == %{"summary" => ""}
+ assert Transmogrifier.fix_summary(%{"summary" => "ok"}) == %{"summary" => "ok"}
+ assert Transmogrifier.fix_summary(%{}) == %{"summary" => ""}
+ end
+ end
+
+ describe "fix_in_reply_to/2" do
+ clear_config([:instance, :federation_incoming_replies_max_depth])
+
+ setup do
+ data = Poison.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
+ [data: data]
+ end
+
+ test "returns not modified object when hasn't containts inReplyTo field", %{data: data} do
+ assert Transmogrifier.fix_in_reply_to(data) == data
+ end
+
+ test "returns object with inReplyToAtomUri when denied incoming reply", %{data: data} do
+ Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 0)
+
+ object_with_reply =
+ Map.put(data["object"], "inReplyTo", "https://shitposter.club/notice/2827873")
+
+ modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
+ assert modified_object["inReplyTo"] == "https://shitposter.club/notice/2827873"
+ assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
+
+ object_with_reply =
+ Map.put(data["object"], "inReplyTo", %{"id" => "https://shitposter.club/notice/2827873"})
+
+ modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
+ assert modified_object["inReplyTo"] == %{"id" => "https://shitposter.club/notice/2827873"}
+ assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
+
+ object_with_reply =
+ Map.put(data["object"], "inReplyTo", ["https://shitposter.club/notice/2827873"])
+
+ modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
+ assert modified_object["inReplyTo"] == ["https://shitposter.club/notice/2827873"]
+ assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
+
+ object_with_reply = Map.put(data["object"], "inReplyTo", [])
+ modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
+ assert modified_object["inReplyTo"] == []
+ assert modified_object["inReplyToAtomUri"] == ""
+ end
+
+ test "returns modified object when allowed incoming reply", %{data: data} do
+ object_with_reply =
+ Map.put(
+ data["object"],
+ "inReplyTo",
+ "https://shitposter.club/notice/2827873"
+ )
+
+ Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 5)
+ modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
+
+ assert modified_object["inReplyTo"] ==
+ "tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment"
+
+ assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
+
+ assert modified_object["conversation"] ==
+ "tag:shitposter.club,2017-05-05:objectType=thread:nonce=3c16e9c2681f6d26"
+
+ assert modified_object["context"] ==
+ "tag:shitposter.club,2017-05-05:objectType=thread:nonce=3c16e9c2681f6d26"
+ end
+ end
+
+ describe "fix_url/1" do
+ test "fixes data for object when url is map" do
+ object = %{
+ "url" => %{
+ "type" => "Link",
+ "mimeType" => "video/mp4",
+ "href" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4"
+ }
+ }
+
+ assert Transmogrifier.fix_url(object) == %{
+ "url" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4"
+ }
+ end
+
+ test "fixes data for video object" do
+ object = %{
+ "type" => "Video",
+ "url" => [
+ %{
+ "type" => "Link",
+ "mimeType" => "video/mp4",
+ "href" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4"
+ },
+ %{
+ "type" => "Link",
+ "mimeType" => "video/mp4",
+ "href" => "https://peertube46fb-ad81-2d4c2d1630e3-240.mp4"
+ },
+ %{
+ "type" => "Link",
+ "mimeType" => "text/html",
+ "href" => "https://peertube.-2d4c2d1630e3"
+ },
+ %{
+ "type" => "Link",
+ "mimeType" => "text/html",
+ "href" => "https://peertube.-2d4c2d16377-42"
+ }
+ ]
+ }
+
+ assert Transmogrifier.fix_url(object) == %{
+ "attachment" => [
+ %{
+ "href" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4",
+ "mimeType" => "video/mp4",
+ "type" => "Link"
+ }
+ ],
+ "type" => "Video",
+ "url" => "https://peertube.-2d4c2d1630e3"
+ }
+ end
+
+ test "fixes url for not Video object" do
+ object = %{
+ "type" => "Text",
+ "url" => [
+ %{
+ "type" => "Link",
+ "mimeType" => "text/html",
+ "href" => "https://peertube.-2d4c2d1630e3"
+ },
+ %{
+ "type" => "Link",
+ "mimeType" => "text/html",
+ "href" => "https://peertube.-2d4c2d16377-42"
+ }
+ ]
+ }
+
+ assert Transmogrifier.fix_url(object) == %{
+ "type" => "Text",
+ "url" => "https://peertube.-2d4c2d1630e3"
+ }
+
+ assert Transmogrifier.fix_url(%{"type" => "Text", "url" => []}) == %{
+ "type" => "Text",
+ "url" => ""
+ }
+ end
+
+ test "retunrs not modified object" do
+ assert Transmogrifier.fix_url(%{"type" => "Text"}) == %{"type" => "Text"}
+ end
+ end
end
From fcf604fa43031be747b33c05866a192d9651322c Mon Sep 17 00:00:00 2001
From: Maksim Pechnikov
Date: Wed, 11 Sep 2019 07:23:33 +0300
Subject: [PATCH 02/15] added tests
---
lib/pleroma/object/fetcher.ex | 77 ++++++++++---------
.../web/activity_pub/transmogrifier.ex | 12 +--
test/web/activity_pub/transmogrifier_test.exs | 74 ++++++++++++++++++
3 files changed, 121 insertions(+), 42 deletions(-)
diff --git a/lib/pleroma/object/fetcher.ex b/lib/pleroma/object/fetcher.ex
index c1795ae0f..2217d1eb3 100644
--- a/lib/pleroma/object/fetcher.ex
+++ b/lib/pleroma/object/fetcher.ex
@@ -13,6 +13,7 @@ defmodule Pleroma.Object.Fetcher do
require Logger
+ @spec reinject_object(map()) :: {:ok, Object.t()} | {:error, any()}
defp reinject_object(data) do
Logger.debug("Reinjecting object #{data["id"]}")
@@ -29,50 +30,54 @@ defp reinject_object(data) do
# TODO:
# This will create a Create activity, which we need internally at the moment.
def fetch_object_from_id(id, options \\ []) do
- if object = Object.get_cached_by_ap_id(id) do
+ with {:fetch_object, nil} <- {:fetch_object, Object.get_cached_by_ap_id(id)},
+ {:fetch, {:ok, data}} <- {:fetch, fetch_and_contain_remote_object_from_id(id)},
+ {:normalize, nil} <- {:normalize, Object.normalize(data, false)},
+ params <- prepare_activity_params(data),
+ {:containment, :ok} <- {:containment, Containment.contain_origin(id, params)},
+ {:ok, activity} <- Transmogrifier.handle_incoming(params, options),
+ {:object, _data, %Object{} = object} <-
+ {:object, data, Object.normalize(activity, false)} do
{:ok, object}
else
- Logger.info("Fetching #{id} via AP")
+ {:containment, _} ->
+ {:error, "Object containment failed."}
- with {:fetch, {:ok, data}} <- {:fetch, fetch_and_contain_remote_object_from_id(id)},
- {:normalize, nil} <- {:normalize, Object.normalize(data, false)},
- params <- %{
- "type" => "Create",
- "to" => data["to"],
- "cc" => data["cc"],
- # Should we seriously keep this attributedTo thing?
- "actor" => data["actor"] || data["attributedTo"],
- "object" => data
- },
- {:containment, :ok} <- {:containment, Containment.contain_origin(id, params)},
- {:ok, activity} <- Transmogrifier.handle_incoming(params, options),
- {:object, _data, %Object{} = object} <-
- {:object, data, Object.normalize(activity, false)} do
+ {:error, {:reject, nil}} ->
+ {:reject, nil}
+
+ {:object, data, nil} ->
+ reinject_object(data)
+
+ {:normalize, object = %Object{}} ->
{:ok, object}
- else
- {:containment, _} ->
- {:error, "Object containment failed."}
- {:error, {:reject, nil}} ->
- {:reject, nil}
+ {:fetch_object, %Object{} = object} ->
+ {:ok, object}
- {:object, data, nil} ->
- reinject_object(data)
+ _e ->
+ # Only fallback when receiving a fetch/normalization error with ActivityPub
+ Logger.info("Couldn't get object via AP, trying out OStatus fetching...")
- {:normalize, object = %Object{}} ->
- {:ok, object}
-
- _e ->
- # Only fallback when receiving a fetch/normalization error with ActivityPub
- Logger.info("Couldn't get object via AP, trying out OStatus fetching...")
-
- # FIXME: OStatus Object Containment?
- case OStatus.fetch_activity_from_url(id) do
- {:ok, [activity | _]} -> {:ok, Object.normalize(activity, false)}
- e -> e
- end
- end
+ # FIXME: OStatus Object Containment?
+ case OStatus.fetch_activity_from_url(id) do
+ {:ok, [activity | _]} -> {:ok, Object.normalize(activity, false)}
+ e -> e
+ end
end
+
+ # end
+ end
+
+ defp prepare_activity_params(data) do
+ %{
+ "type" => "Create",
+ "to" => data["to"],
+ "cc" => data["cc"],
+ # Should we seriously keep this attributedTo thing?
+ "actor" => data["actor"] || data["attributedTo"],
+ "object" => data
+ }
end
def fetch_object_from_id!(id, options \\ []) do
diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex
index 93b3a1f97..18a3c3f39 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -204,7 +204,6 @@ def fix_attachments(%{"attachment" => attachment} = object) when is_list(attachm
Enum.map(attachment, fn data ->
media_type = data["mediaType"] || data["mimeType"]
href = data["url"] || data["href"]
-
url = [%{"type" => "Link", "mediaType" => media_type, "href" => href}]
data
@@ -216,7 +215,9 @@ def fix_attachments(%{"attachment" => attachment} = object) when is_list(attachm
end
def fix_attachments(%{"attachment" => attachment} = object) when is_map(attachment) do
- fix_attachments(Map.put(object, "attachment", [attachment]))
+ object
+ |> Map.put("attachment", [attachment])
+ |> fix_attachments()
end
def fix_attachments(object), do: object
@@ -725,10 +726,9 @@ def handle_incoming(_, _), do: :error
@spec get_obj_helper(String.t(), Keyword.t()) :: {:ok, Object.t()} | nil
def get_obj_helper(id, options \\ []) do
- if object = Object.normalize(id, true, options) do
- {:ok, object}
- else
- nil
+ case Object.normalize(id, true, options) do
+ %Object{} = object -> {:ok, object}
+ _ -> nil
end
end
diff --git a/test/web/activity_pub/transmogrifier_test.exs b/test/web/activity_pub/transmogrifier_test.exs
index 63c869d35..ab6e76056 100644
--- a/test/web/activity_pub/transmogrifier_test.exs
+++ b/test/web/activity_pub/transmogrifier_test.exs
@@ -1613,4 +1613,78 @@ test "retunrs not modified object" do
assert Transmogrifier.fix_url(%{"type" => "Text"}) == %{"type" => "Text"}
end
end
+
+ describe "get_obj_helper/2" do
+ test "returns nil when cannot normalize object" do
+ refute Transmogrifier.get_obj_helper("test-obj-id")
+ end
+
+ test "returns {:ok, %Object{}} for success case" do
+ assert {:ok, %Object{}} =
+ Transmogrifier.get_obj_helper("https://shitposter.club/notice/2827873")
+ end
+ end
+
+ describe "fix_attachments/1" do
+ test "returns not modified object" do
+ data = Poison.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
+ assert Transmogrifier.fix_attachments(data) == data
+ end
+
+ test "returns modified object when attachment is map" do
+ assert Transmogrifier.fix_attachments(%{
+ "attachment" => %{
+ "mediaType" => "video/mp4",
+ "url" => "https://peertube.moe/stat-480.mp4"
+ }
+ }) == %{
+ "attachment" => [
+ %{
+ "mediaType" => "video/mp4",
+ "url" => [
+ %{
+ "href" => "https://peertube.moe/stat-480.mp4",
+ "mediaType" => "video/mp4",
+ "type" => "Link"
+ }
+ ]
+ }
+ ]
+ }
+ end
+
+ test "returns modified object when attachment is list" do
+ assert Transmogrifier.fix_attachments(%{
+ "attachment" => [
+ %{"mediaType" => "video/mp4", "url" => "https://pe.er/stat-480.mp4"},
+ %{"mimeType" => "video/mp4", "href" => "https://pe.er/stat-480.mp4"}
+ ]
+ }) == %{
+ "attachment" => [
+ %{
+ "mediaType" => "video/mp4",
+ "url" => [
+ %{
+ "href" => "https://pe.er/stat-480.mp4",
+ "mediaType" => "video/mp4",
+ "type" => "Link"
+ }
+ ]
+ },
+ %{
+ "href" => "https://pe.er/stat-480.mp4",
+ "mediaType" => "video/mp4",
+ "mimeType" => "video/mp4",
+ "url" => [
+ %{
+ "href" => "https://pe.er/stat-480.mp4",
+ "mediaType" => "video/mp4",
+ "type" => "Link"
+ }
+ ]
+ }
+ ]
+ }
+ end
+ end
end
From 007e0c1ce158bdfc11738a194944534837ae0258 Mon Sep 17 00:00:00 2001
From: Maksim Pechnikov
Date: Wed, 11 Sep 2019 23:19:06 +0300
Subject: [PATCH 03/15] added tests
---
.../web/activity_pub/transmogrifier.ex | 35 ++++++++++---------
.../web/activity_pub/views/user_view.ex | 7 ++--
test/web/activity_pub/transmogrifier_test.exs | 31 ++++++++++++++++
.../web/activity_pub/views/user_view_test.exs | 16 +++++++++
4 files changed, 68 insertions(+), 21 deletions(-)
diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex
index 18a3c3f39..9f699de9e 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -870,41 +870,44 @@ def add_mention_tags(object) do
mentions =
object
|> Utils.get_notified_from_object()
- |> Enum.map(fn user ->
- %{"type" => "Mention", "href" => user.ap_id, "name" => "@#{user.nickname}"}
- end)
+ |> Enum.map(&build_mention_tag/1)
tags = object["tag"] || []
Map.put(object, "tag", tags ++ mentions)
end
- def add_emoji_tags(%User{info: %{"emoji" => _emoji} = user_info} = object) do
- user_info = add_emoji_tags(user_info)
+ defp build_mention_tag(%{ap_id: ap_id, nickname: nickname} = _) do
+ %{"type" => "Mention", "href" => ap_id, "name" => "@#{nickname}"}
+ end
- Map.put(object, :info, user_info)
+ def take_emoji_tags(%User{info: %{emoji: emoji} = _user_info} = _user) do
+ emoji
+ |> Enum.flat_map(&Map.to_list/1)
+ |> Enum.map(&build_emoji_tag/1)
end
# TODO: we should probably send mtime instead of unix epoch time for updated
def add_emoji_tags(%{"emoji" => emoji} = object) do
tags = object["tag"] || []
- out =
- Enum.map(emoji, fn {name, url} ->
- %{
- "icon" => %{"url" => url, "type" => "Image"},
- "name" => ":" <> name <> ":",
- "type" => "Emoji",
- "updated" => "1970-01-01T00:00:00Z",
- "id" => url
- }
- end)
+ out = Enum.map(emoji, &build_emoji_tag/1)
Map.put(object, "tag", tags ++ out)
end
def add_emoji_tags(object), do: object
+ defp build_emoji_tag({name, url}) do
+ %{
+ "icon" => %{"url" => url, "type" => "Image"},
+ "name" => ":" <> name <> ":",
+ "type" => "Emoji",
+ "updated" => "1970-01-01T00:00:00Z",
+ "id" => url
+ }
+ end
+
def set_conversation(object) do
Map.put(object, "conversation", object["context"])
end
diff --git a/lib/pleroma/web/activity_pub/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex
index 7be734b26..8abfa1fcd 100644
--- a/lib/pleroma/web/activity_pub/views/user_view.ex
+++ b/lib/pleroma/web/activity_pub/views/user_view.ex
@@ -75,10 +75,7 @@ def render("user.json", %{user: user}) do
endpoints = render("endpoints.json", %{user: user})
- user_tags =
- user
- |> Transmogrifier.add_emoji_tags()
- |> Map.get("tag", [])
+ emoji_tags = Transmogrifier.take_emoji_tags(user)
fields =
user.info
@@ -110,7 +107,7 @@ def render("user.json", %{user: user}) do
},
"endpoints" => endpoints,
"attachment" => fields,
- "tag" => (user.info.source_data["tag"] || []) ++ user_tags
+ "tag" => (user.info.source_data["tag"] || []) ++ emoji_tags
}
|> Map.merge(maybe_make_image(&User.avatar_url/2, "icon", user))
|> Map.merge(maybe_make_image(&User.banner_url/2, "image", user))
diff --git a/test/web/activity_pub/transmogrifier_test.exs b/test/web/activity_pub/transmogrifier_test.exs
index ab6e76056..87ef843c6 100644
--- a/test/web/activity_pub/transmogrifier_test.exs
+++ b/test/web/activity_pub/transmogrifier_test.exs
@@ -1687,4 +1687,35 @@ test "returns modified object when attachment is list" do
}
end
end
+
+ describe "fix_emoji/1" do
+ test "returns not modified object when object not contains tags" do
+ data = Poison.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
+ assert Transmogrifier.fix_emoji(data) == data
+ end
+
+ test "returns object with emoji when object contains list tags" do
+ assert Transmogrifier.fix_emoji(%{
+ "tag" => [
+ %{"type" => "Emoji", "name" => ":bib:", "icon" => %{"url" => "/test"}},
+ %{"type" => "Hashtag"}
+ ]
+ }) == %{
+ "emoji" => %{"bib" => "/test"},
+ "tag" => [
+ %{"icon" => %{"url" => "/test"}, "name" => ":bib:", "type" => "Emoji"},
+ %{"type" => "Hashtag"}
+ ]
+ }
+ end
+
+ test "returns object with emoji when object contains map tag" do
+ assert Transmogrifier.fix_emoji(%{
+ "tag" => %{"type" => "Emoji", "name" => ":bib:", "icon" => %{"url" => "/test"}}
+ }) == %{
+ "emoji" => %{"bib" => "/test"},
+ "tag" => %{"icon" => %{"url" => "/test"}, "name" => ":bib:", "type" => "Emoji"}
+ }
+ end
+ end
end
diff --git a/test/web/activity_pub/views/user_view_test.exs b/test/web/activity_pub/views/user_view_test.exs
index fb7fd9e79..4390f9272 100644
--- a/test/web/activity_pub/views/user_view_test.exs
+++ b/test/web/activity_pub/views/user_view_test.exs
@@ -37,6 +37,22 @@ test "Renders profile fields" do
} = UserView.render("user.json", %{user: user})
end
+ test "Renders with emoji tags" do
+ user = insert(:user, %{info: %{emoji: [%{"bib" => "/test"}]}})
+
+ assert %{
+ "tag" => [
+ %{
+ "icon" => %{"type" => "Image", "url" => "/test"},
+ "id" => "/test",
+ "name" => ":bib:",
+ "type" => "Emoji",
+ "updated" => "1970-01-01T00:00:00Z"
+ }
+ ]
+ } = UserView.render("user.json", %{user: user})
+ end
+
test "Does not add an avatar image if the user hasn't set one" do
user = insert(:user)
{:ok, user} = User.ensure_keys_present(user)
From cf3041220a7a14dc3fac24177fac1f4aecc77f5f Mon Sep 17 00:00:00 2001
From: Egor Kislitsyn
Date: Tue, 17 Sep 2019 15:22:46 +0700
Subject: [PATCH 04/15] Add support for `rel="ugc"`
---
config/config.exs | 2 +-
config/description.exs | 2 +-
docs/config.md | 2 +-
lib/pleroma/html.ex | 6 +++--
test/formatter_test.exs | 24 ++++++++++---------
test/web/common_api/common_api_utils_test.exs | 6 ++---
.../update_credentials_test.exs | 2 +-
7 files changed, 24 insertions(+), 20 deletions(-)
diff --git a/config/config.exs b/config/config.exs
index c7e0cf09f..26dc4d16d 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -507,7 +507,7 @@
class: false,
strip_prefix: false,
new_window: false,
- rel: false
+ rel: "ugc"
]
config :pleroma, :ldap,
diff --git a/config/description.exs b/config/description.exs
index 65ea6bf01..abfb6370f 100644
--- a/config/description.exs
+++ b/config/description.exs
@@ -1900,7 +1900,7 @@
key: :rel,
type: [:string, false],
description: "override the rel attribute. false to clear",
- suggestions: ["noopener noreferrer", false]
+ suggestions: ["ugc", false]
},
%{
key: :new_window,
diff --git a/docs/config.md b/docs/config.md
index 3f37fa561..def462900 100644
--- a/docs/config.md
+++ b/docs/config.md
@@ -521,7 +521,7 @@ config :auto_linker,
class: false,
strip_prefix: false,
new_window: false,
- rel: false
+ rel: "ugc"
]
```
diff --git a/lib/pleroma/html.ex b/lib/pleroma/html.ex
index 3951f0f51..937bafed5 100644
--- a/lib/pleroma/html.ex
+++ b/lib/pleroma/html.ex
@@ -184,7 +184,8 @@ defmodule Pleroma.HTML.Scrubber.Default do
"tag",
"nofollow",
"noopener",
- "noreferrer"
+ "noreferrer",
+ "ugc"
])
Meta.allow_tag_with_these_attributes("a", ["name", "title"])
@@ -304,7 +305,8 @@ defmodule Pleroma.HTML.Scrubber.LinksOnly do
"nofollow",
"noopener",
"noreferrer",
- "me"
+ "me",
+ "ugc"
])
Meta.allow_tag_with_these_attributes("a", ["name", "title"])
diff --git a/test/formatter_test.exs b/test/formatter_test.exs
index c443dfe7c..3674577d6 100644
--- a/test/formatter_test.exs
+++ b/test/formatter_test.exs
@@ -39,21 +39,21 @@ test "turning urls into links" do
text = "Hey, check out https://www.youtube.com/watch?v=8Zg1-TufF%20zY?x=1&y=2#blabla ."
expected =
- "Hey, check out https://www.youtube.com/watch?v=8Zg1-TufF%20zY?x=1&y=2#blabla ."
+ ~S(Hey, check out https://www.youtube.com/watch?v=8Zg1-TufF%20zY?x=1&y=2#blabla .)
assert {^expected, [], []} = Formatter.linkify(text)
text = "https://mastodon.social/@lambadalambda"
expected =
- "https://mastodon.social/@lambadalambda"
+ ~S(https://mastodon.social/@lambadalambda)
assert {^expected, [], []} = Formatter.linkify(text)
text = "https://mastodon.social:4000/@lambadalambda"
expected =
- "https://mastodon.social:4000/@lambadalambda"
+ ~S(https://mastodon.social:4000/@lambadalambda)
assert {^expected, [], []} = Formatter.linkify(text)
@@ -63,55 +63,57 @@ test "turning urls into links" do
assert {^expected, [], []} = Formatter.linkify(text)
text = "http://www.cs.vu.nl/~ast/intel/"
- expected = "http://www.cs.vu.nl/~ast/intel/"
+
+ expected =
+ ~S(http://www.cs.vu.nl/~ast/intel/)
assert {^expected, [], []} = Formatter.linkify(text)
text = "https://forum.zdoom.org/viewtopic.php?f=44&t=57087"
expected =
- "https://forum.zdoom.org/viewtopic.php?f=44&t=57087"
+ "https://forum.zdoom.org/viewtopic.php?f=44&t=57087"
assert {^expected, [], []} = Formatter.linkify(text)
text = "https://en.wikipedia.org/wiki/Sophia_(Gnosticism)#Mythos_of_the_soul"
expected =
- "https://en.wikipedia.org/wiki/Sophia_(Gnosticism)#Mythos_of_the_soul"
+ "https://en.wikipedia.org/wiki/Sophia_(Gnosticism)#Mythos_of_the_soul"
assert {^expected, [], []} = Formatter.linkify(text)
text = "https://www.google.co.jp/search?q=Nasim+Aghdam"
expected =
- "https://www.google.co.jp/search?q=Nasim+Aghdam"
+ "https://www.google.co.jp/search?q=Nasim+Aghdam"
assert {^expected, [], []} = Formatter.linkify(text)
text = "https://en.wikipedia.org/wiki/Duff's_device"
expected =
- "https://en.wikipedia.org/wiki/Duff's_device"
+ "https://en.wikipedia.org/wiki/Duff's_device"
assert {^expected, [], []} = Formatter.linkify(text)
text = "https://pleroma.com https://pleroma.com/sucks"
expected =
- "https://pleroma.com https://pleroma.com/sucks"
+ "https://pleroma.com https://pleroma.com/sucks"
assert {^expected, [], []} = Formatter.linkify(text)
text = "xmpp:contact@hacktivis.me"
- expected = "xmpp:contact@hacktivis.me"
+ expected = "xmpp:contact@hacktivis.me"
assert {^expected, [], []} = Formatter.linkify(text)
text =
"magnet:?xt=urn:btih:7ec9d298e91d6e4394d1379caf073c77ff3e3136&tr=udp%3A%2F%2Fopentor.org%3A2710&tr=udp%3A%2F%2Ftracker.blackunicorn.xyz%3A6969&tr=udp%3A%2F%2Ftracker.ccc.de%3A80&tr=udp%3A%2F%2Ftracker.coppersurfer.tk%3A6969&tr=udp%3A%2F%2Ftracker.leechers-paradise.org%3A6969&tr=udp%3A%2F%2Ftracker.openbittorrent.com%3A80&tr=wss%3A%2F%2Ftracker.btorrent.xyz&tr=wss%3A%2F%2Ftracker.fastcast.nz&tr=wss%3A%2F%2Ftracker.openwebtorrent.com"
- expected = "#{text}"
+ expected = "#{text}"
assert {^expected, [], []} = Formatter.linkify(text)
end
diff --git a/test/web/common_api/common_api_utils_test.exs b/test/web/common_api/common_api_utils_test.exs
index 230146451..78cfe3c5f 100644
--- a/test/web/common_api/common_api_utils_test.exs
+++ b/test/web/common_api/common_api_utils_test.exs
@@ -157,11 +157,11 @@ test "works for text/markdown with mentions" do
text = "**hello world**\n\n*another @user__test and @user__test google.com paragraph*"
expected =
- "hello world
\nanother hello world
\nanother @user__test and @user__test and @user__test google.com paragraph
\n"
+ }" class="u-url mention" href="http://foo.com/user__test">@user__test google.com paragraph
\n)
{output, _, _} = Utils.format_input(text, "text/markdown")
diff --git a/test/web/mastodon_api/controllers/mastodon_api_controller/update_credentials_test.exs b/test/web/mastodon_api/controllers/mastodon_api_controller/update_credentials_test.exs
index 89d4ca37e..1e8d0d03b 100644
--- a/test/web/mastodon_api/controllers/mastodon_api_controller/update_credentials_test.exs
+++ b/test/web/mastodon_api/controllers/mastodon_api_controller/update_credentials_test.exs
@@ -334,7 +334,7 @@ test "update fields", %{conn: conn} do
assert account["fields"] == [
%{"name" => "foo", "value" => "bar"},
- %{"name" => "link", "value" => "cofe.io"}
+ %{"name" => "link", "value" => ~S(cofe.io)}
]
assert account["source"]["fields"] == [
From d639cdcecb1b9cd2326b98c926dff8b0f4c27e3c Mon Sep 17 00:00:00 2001
From: Egor Kislitsyn
Date: Thu, 19 Sep 2019 14:04:13 +0700
Subject: [PATCH 05/15] Update "config/description.exs"
---
config/description.exs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/config/description.exs b/config/description.exs
index abfb6370f..510e285df 100644
--- a/config/description.exs
+++ b/config/description.exs
@@ -1900,7 +1900,7 @@
key: :rel,
type: [:string, false],
description: "override the rel attribute. false to clear",
- suggestions: ["ugc", false]
+ suggestions: ["ugc", "noopener noreferrer", false]
},
%{
key: :new_window,
From 95c948110ca130559fd6a5302011aa58900274ac Mon Sep 17 00:00:00 2001
From: Egor Kislitsyn
Date: Thu, 19 Sep 2019 14:39:52 +0700
Subject: [PATCH 06/15] Add `rel="ugc"` to hashtags and mentions
---
lib/pleroma/formatter.ex | 6 ++--
test/formatter_test.exs | 30 +++++++++++--------
test/user_test.exs | 4 +--
test/web/common_api/common_api_utils_test.exs | 4 +--
.../update_credentials_test.exs | 7 ++---
.../mastodon_api_controller_test.exs | 8 ++---
test/web/twitter_api/twitter_api_test.exs | 4 ++-
7 files changed, 35 insertions(+), 28 deletions(-)
diff --git a/lib/pleroma/formatter.ex b/lib/pleroma/formatter.ex
index 607843a5b..23a5ac8fe 100644
--- a/lib/pleroma/formatter.ex
+++ b/lib/pleroma/formatter.ex
@@ -36,9 +36,9 @@ def mention_handler("@" <> nickname, buffer, opts, acc) do
nickname_text = get_nickname_text(nickname, opts)
link =
- "@#{
+ ~s(@#{
nickname_text
- }"
+ })
{link, %{acc | mentions: MapSet.put(acc.mentions, {"@" <> nickname, user})}}
@@ -50,7 +50,7 @@ def mention_handler("@" <> nickname, buffer, opts, acc) do
def hashtag_handler("#" <> tag = tag_text, _buffer, _opts, acc) do
tag = String.downcase(tag)
url = "#{Pleroma.Web.base_url()}/tag/#{tag}"
- link = "#{tag_text}"
+ link = ~s(#{tag_text})
{link, %{acc | tags: MapSet.put(acc.tags, {tag_text, tag})}}
end
diff --git a/test/formatter_test.exs b/test/formatter_test.exs
index 3674577d6..2e4280fc2 100644
--- a/test/formatter_test.exs
+++ b/test/formatter_test.exs
@@ -19,7 +19,7 @@ test "turns hashtags into links" do
text = "I love #cofe and #2hu"
expected_text =
- "I love #cofe and #2hu"
+ ~s(I love #cofe and #2hu)
assert {^expected_text, [], _tags} = Formatter.linkify(text)
end
@@ -28,7 +28,7 @@ test "does not turn html characters to tags" do
text = "#fact_3: pleroma does what mastodon't"
expected_text =
- "#fact_3: pleroma does what mastodon't"
+ ~s(#fact_3: pleroma does what mastodon't)
assert {^expected_text, [], _tags} = Formatter.linkify(text)
end
@@ -137,13 +137,13 @@ test "gives a replacement for user links, using local nicknames in user links te
assert length(mentions) == 3
expected_text =
- "@gsimg According to @archa_eme_, that is @daggsy. Also hello @archaeme"
+ }" class="u-url mention" href="#{archaeme_remote.ap_id}" rel="ugc">@archaeme)
assert expected_text == text
end
@@ -158,7 +158,9 @@ test "gives a replacement for user links when the user is using Osada" do
assert length(mentions) == 1
expected_text =
- "@mike test"
+ ~s(@mike test)
assert expected_text == text
end
@@ -172,7 +174,7 @@ test "gives a replacement for single-character local nicknames" do
assert length(mentions) == 1
expected_text =
- "@o hi"
+ ~s(@o hi)
assert expected_text == text
end
@@ -194,13 +196,17 @@ test "given the 'safe_mention' option, it will only mention people in the beginn
assert mentions == [{"@#{user.nickname}", user}, {"@#{other_user.nickname}", other_user}]
assert expected_text ==
- "@#{user.nickname} @#{other_user.nickname} hey dudes i hate @#{third_user.nickname}"
+ }" class="u-url mention" href="#{third_user.ap_id}" rel="ugc">@#{
+ third_user.nickname
+ })
end
test "given the 'safe_mention' option, it will still work without any mention" do
diff --git a/test/user_test.exs b/test/user_test.exs
index 39ba69668..6852fcd40 100644
--- a/test/user_test.exs
+++ b/test/user_test.exs
@@ -1294,9 +1294,9 @@ test "preserves hosts in user links text" do
bio = "A.k.a. @nick@domain.com"
expected_text =
- "A.k.a. @nick@domain.com"
+ }" rel="ugc">@nick@domain.com)
assert expected_text == User.parse_bio(bio, user)
end
diff --git a/test/web/common_api/common_api_utils_test.exs b/test/web/common_api/common_api_utils_test.exs
index 78cfe3c5f..2588898d0 100644
--- a/test/web/common_api/common_api_utils_test.exs
+++ b/test/web/common_api/common_api_utils_test.exs
@@ -159,9 +159,9 @@ test "works for text/markdown with mentions" do
expected =
~s(hello world
\nanother @user__test and @user__test and @user__test google.com paragraph
\n)
+ }" class="u-url mention" href="http://foo.com/user__test" rel="ugc">@user__test google.com paragraph\n)
{output, _, _} = Utils.format_input(text, "text/markdown")
diff --git a/test/web/mastodon_api/controllers/mastodon_api_controller/update_credentials_test.exs b/test/web/mastodon_api/controllers/mastodon_api_controller/update_credentials_test.exs
index 1e8d0d03b..560f55137 100644
--- a/test/web/mastodon_api/controllers/mastodon_api_controller/update_credentials_test.exs
+++ b/test/web/mastodon_api/controllers/mastodon_api_controller/update_credentials_test.exs
@@ -86,10 +86,9 @@ test "updates the user's bio", %{conn: conn} do
assert user = json_response(conn, 200)
assert user["note"] ==
- ~s(I drink #cofe with @) <> user2.nickname <> ~s()
+ ~s(I drink #cofe with @#{user2.nickname})
end
test "updates the user's locking status", %{conn: conn} do
diff --git a/test/web/mastodon_api/mastodon_api_controller_test.exs b/test/web/mastodon_api/mastodon_api_controller_test.exs
index fb04748bb..b85f3e758 100644
--- a/test/web/mastodon_api/mastodon_api_controller_test.exs
+++ b/test/web/mastodon_api/mastodon_api_controller_test.exs
@@ -996,9 +996,9 @@ test "list of notifications", %{conn: conn} do
|> get("/api/v1/notifications")
expected_response =
- "hi @#{user.nickname}"
+ }" rel="ugc">@#{user.nickname})
assert [%{"status" => %{"content" => response}} | _rest] = json_response(conn, 200)
assert response == expected_response
@@ -1018,9 +1018,9 @@ test "getting a single notification", %{conn: conn} do
|> get("/api/v1/notifications/#{notification.id}")
expected_response =
- "hi @#{user.nickname}"
+ }" rel="ugc">@#{user.nickname})
assert %{"status" => %{"content" => response}} = json_response(conn, 200)
assert response == expected_response
diff --git a/test/web/twitter_api/twitter_api_test.exs b/test/web/twitter_api/twitter_api_test.exs
index 08f264431..bf1e233f5 100644
--- a/test/web/twitter_api/twitter_api_test.exs
+++ b/test/web/twitter_api/twitter_api_test.exs
@@ -109,7 +109,9 @@ test "it registers a new user and parses mentions in the bio" do
{:ok, user2} = TwitterAPI.register_user(data2)
expected_text =
- "@john test"
+ ~s(@john test)
assert user2.bio == expected_text
end
From ae1d371428e16b738b8ec638e411e5e8c1ac4937 Mon Sep 17 00:00:00 2001
From: Egor Kislitsyn
Date: Thu, 19 Sep 2019 14:53:34 +0700
Subject: [PATCH 07/15] Update CHANGELOG
---
CHANGELOG.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 906aa985e..f84b0ac68 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -38,6 +38,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- AdminAPI: Add "godmode" while fetching user statuses (i.e. admin can see private statuses)
- Improve digest email template
– Pagination: (optional) return `total` alongside with `items` when paginating
+- Add `rel="ugc"` to all links in statuses, to prevent SEO spam
### Fixed
- Following from Osada
From 7cf125245512eb49a118535eda52ddbdd0c4c6bf Mon Sep 17 00:00:00 2001
From: eugenijm
Date: Fri, 20 Sep 2019 17:54:38 +0300
Subject: [PATCH 08/15] Mastodon API: Fix private and direct statuses not being
filtered out from the public timeline for an authenticated user (`GET
/api/v1/timelines/public`)
---
CHANGELOG.md | 2 ++
lib/pleroma/web/activity_pub/activity_pub.ex | 5 +++--
.../controllers/mastodon_api_controller.ex | 1 -
.../mastodon_api_controller_test.exs | 16 ++++++++++++++++
4 files changed, 21 insertions(+), 3 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 84b64e2b9..93b7e2a10 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,6 +12,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Introduced [quantum](https://github.com/quantum-elixir/quantum-core) job scheduler
- Admin API: Return `total` when querying for reports
- Mastodon API: Return `pleroma.direct_conversation_id` when creating a direct message (`POST /api/v1/statuses`)
+### Fixed
+- Mastodon API: Fix private and direct statuses not being filtered out from the public timeline for an authenticated user (`GET /api/v1/timelines/public`)
## [1.1.0] - 2019-??-??
### Security
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index e1e90d667..1cf8b6151 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -520,9 +520,10 @@ def fetch_latest_activity_id_for_context(context, opts \\ %{}) do
end
def fetch_public_activities(opts \\ %{}) do
- q = fetch_activities_query([Pleroma.Constants.as_public()], opts)
+ opts = Map.drop(opts, ["user"])
- q
+ [Pleroma.Constants.as_public()]
+ |> fetch_activities_query(opts)
|> restrict_unlisted()
|> Pagination.fetch_paginated(opts)
|> Enum.reverse()
diff --git a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex
index 6704ee7e8..6421c2c53 100644
--- a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex
@@ -381,7 +381,6 @@ def public_timeline(%{assigns: %{user: user}} = conn, params) do
|> Map.put("local_only", local_only)
|> Map.put("blocking_user", user)
|> Map.put("muting_user", user)
- |> Map.put("user", user)
|> ActivityPub.fetch_public_activities()
|> Enum.reverse()
diff --git a/test/web/mastodon_api/mastodon_api_controller_test.exs b/test/web/mastodon_api/mastodon_api_controller_test.exs
index 35a0d3fe1..51f5215c2 100644
--- a/test/web/mastodon_api/mastodon_api_controller_test.exs
+++ b/test/web/mastodon_api/mastodon_api_controller_test.exs
@@ -97,6 +97,22 @@ test "the public timeline when public is set to false", %{conn: conn} do
|> json_response(403) == %{"error" => "This resource requires authentication."}
end
+ test "the public timeline includes only public statuses for an authenticated user" do
+ user = insert(:user)
+
+ conn =
+ build_conn()
+ |> assign(:user, user)
+
+ {:ok, _activity} = CommonAPI.post(user, %{"status" => "test"})
+ {:ok, _activity} = CommonAPI.post(user, %{"status" => "test", "visibility" => "private"})
+ {:ok, _activity} = CommonAPI.post(user, %{"status" => "test", "visibility" => "unlisted"})
+ {:ok, _activity} = CommonAPI.post(user, %{"status" => "test", "visibility" => "direct"})
+
+ res_conn = get(conn, "/api/v1/timelines/public")
+ assert length(json_response(res_conn, 200)) == 1
+ end
+
describe "posting statuses" do
setup do
user = insert(:user)
From 6f25668215f7f9fe20bfaf3dd72e2262a6d8915e Mon Sep 17 00:00:00 2001
From: Maxim Filippov
Date: Sun, 22 Sep 2019 16:08:07 +0300
Subject: [PATCH 09/15] Admin API: Add ability to force user's password reset
---
CHANGELOG.md | 2 ++
docs/api/admin_api.md | 8 ++++++
lib/pleroma/user.ex | 17 ++++++++++++
lib/pleroma/user/info.ex | 13 ++++++---
.../web/admin_api/admin_api_controller.ex | 9 +++++++
lib/pleroma/web/oauth/oauth_controller.ex | 5 ++++
lib/pleroma/web/router.ex | 1 +
lib/pleroma/workers/background_worker.ex | 5 ++++
test/user_test.exs | 17 ++++++++++++
.../admin_api/admin_api_controller_test.exs | 26 ++++++++++++++++++
test/web/oauth/oauth_controller_test.exs | 27 +++++++++++++++++++
.../twitter_api/password_controller_test.exs | 21 +++++++++++++++
12 files changed, 148 insertions(+), 3 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 84b64e2b9..e5a84f5ae 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [Unreleased]
### Added
- Refreshing poll results for remote polls
+- Admin API: Add ability to force user's password reset
+
### Changed
- **Breaking:** Elixir >=1.8 is now required (was >= 1.7)
- Replaced [pleroma_job_queue](https://git.pleroma.social/pleroma/pleroma_job_queue) and `Pleroma.Web.Federator.RetryQueue` with [Oban](https://github.com/sorentwo/oban) (see [`docs/config.md`](docs/config.md) on migrating customized worker / retry settings)
diff --git a/docs/api/admin_api.md b/docs/api/admin_api.md
index 7637fa0d4..c6b9dd2b6 100644
--- a/docs/api/admin_api.md
+++ b/docs/api/admin_api.md
@@ -310,6 +310,14 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
- Params: none
- Response: password reset token (base64 string)
+## `/api/pleroma/admin/users/:nickname/force_password_reset`
+
+### Force passord reset for a user with a given nickname
+
+- Methods: `PATCH`
+- Params: none
+- Response: none (code `204`)
+
## `/api/pleroma/admin/reports`
### Get a list of reports
- Method `GET`
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index fb1f24254..ab253a274 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -269,6 +269,7 @@ def password_update_changeset(struct, params) do
|> validate_required([:password, :password_confirmation])
|> validate_confirmation(:password)
|> put_password_hash
+ |> put_embed(:info, User.Info.set_password_reset_pending(struct.info, false))
end
@spec reset_password(User.t(), map) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
@@ -285,6 +286,20 @@ def reset_password(%User{id: user_id} = user, data) do
end
end
+ def force_password_reset_async(user) do
+ BackgroundWorker.enqueue("force_password_reset", %{"user_id" => user.id})
+ end
+
+ @spec force_password_reset(User.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
+ def force_password_reset(user) do
+ info_cng = User.Info.set_password_reset_pending(user.info, true)
+
+ user
+ |> change()
+ |> put_embed(:info, info_cng)
+ |> update_and_set_cache()
+ end
+
def register_changeset(struct, params \\ %{}, opts \\ []) do
bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
@@ -1115,6 +1130,8 @@ def delete(%User{} = user) do
BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id})
end
+ def perform(:force_password_reset, user), do: force_password_reset(user)
+
@spec perform(atom(), User.t()) :: {:ok, User.t()}
def perform(:delete, %User{} = user) do
{:ok, _user} = ActivityPub.delete(user)
diff --git a/lib/pleroma/user/info.ex b/lib/pleroma/user/info.ex
index b150a57cd..67abc3ecd 100644
--- a/lib/pleroma/user/info.ex
+++ b/lib/pleroma/user/info.ex
@@ -20,6 +20,7 @@ defmodule Pleroma.User.Info do
field(:following_count, :integer, default: nil)
field(:locked, :boolean, default: false)
field(:confirmation_pending, :boolean, default: false)
+ field(:password_reset_pending, :boolean, default: false)
field(:confirmation_token, :string, default: nil)
field(:default_scope, :string, default: "public")
field(:blocks, {:array, :string}, default: [])
@@ -82,6 +83,14 @@ def set_activation_status(info, deactivated) do
|> validate_required([:deactivated])
end
+ def set_password_reset_pending(info, pending) do
+ params = %{password_reset_pending: pending}
+
+ info
+ |> cast(params, [:password_reset_pending])
+ |> validate_required([:password_reset_pending])
+ end
+
def update_notification_settings(info, settings) do
settings =
settings
@@ -333,9 +342,7 @@ defp valid_field?(%{"name" => name, "value" => value}) do
name_limit = Pleroma.Config.get([:instance, :account_field_name_length], 255)
value_limit = Pleroma.Config.get([:instance, :account_field_value_length], 255)
- is_binary(name) &&
- is_binary(value) &&
- String.length(name) <= name_limit &&
+ is_binary(name) && is_binary(value) && String.length(name) <= name_limit &&
String.length(value) <= value_limit
end
diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex
index 8a8091daa..711e4dfc2 100644
--- a/lib/pleroma/web/admin_api/admin_api_controller.ex
+++ b/lib/pleroma/web/admin_api/admin_api_controller.ex
@@ -447,6 +447,15 @@ def get_password_reset(conn, %{"nickname" => nickname}) do
|> json(token.token)
end
+ @doc "Force password reset for a given user"
+ def force_password_reset(conn, %{"nickname" => nickname}) do
+ (%User{local: true} = user) = User.get_cached_by_nickname(nickname)
+
+ User.force_password_reset_async(user)
+
+ json_response(conn, :no_content, "")
+ end
+
def list_reports(conn, params) do
params =
params
diff --git a/lib/pleroma/web/oauth/oauth_controller.ex b/lib/pleroma/web/oauth/oauth_controller.ex
index 81eae2c8b..a57670e02 100644
--- a/lib/pleroma/web/oauth/oauth_controller.ex
+++ b/lib/pleroma/web/oauth/oauth_controller.ex
@@ -202,6 +202,8 @@ def token_exchange(
{:ok, app} <- Token.Utils.fetch_app(conn),
{:auth_active, true} <- {:auth_active, User.auth_active?(user)},
{:user_active, true} <- {:user_active, !user.info.deactivated},
+ {:password_reset_pending, false} <-
+ {:password_reset_pending, user.info.password_reset_pending},
{:ok, scopes} <- validate_scopes(app, params),
{:ok, auth} <- Authorization.create_authorization(app, user, scopes),
{:ok, token} <- Token.exchange_token(app, auth) do
@@ -215,6 +217,9 @@ def token_exchange(
{:user_active, false} ->
render_error(conn, :forbidden, "Your account is currently disabled")
+ {:password_reset_pending, true} ->
+ render_error(conn, :forbidden, "Password reset is required")
+
_error ->
render_invalid_credentials_error(conn)
end
diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex
index b9b85fd67..a306c1b80 100644
--- a/lib/pleroma/web/router.ex
+++ b/lib/pleroma/web/router.ex
@@ -186,6 +186,7 @@ defmodule Pleroma.Web.Router do
post("/users/email_invite", AdminAPIController, :email_invite)
get("/users/:nickname/password_reset", AdminAPIController, :get_password_reset)
+ patch("/users/:nickname/force_password_reset", AdminAPIController, :force_password_reset)
get("/users", AdminAPIController, :list_users)
get("/users/:nickname", AdminAPIController, :user_show)
diff --git a/lib/pleroma/workers/background_worker.ex b/lib/pleroma/workers/background_worker.ex
index 082f20ab7..7ffc8eabe 100644
--- a/lib/pleroma/workers/background_worker.ex
+++ b/lib/pleroma/workers/background_worker.ex
@@ -26,6 +26,11 @@ def perform(%{"op" => "delete_user", "user_id" => user_id}, _job) do
User.perform(:delete, user)
end
+ def perform(%{"op" => "force_password_reset", "user_id" => user_id}, _job) do
+ user = User.get_cached_by_id(user_id)
+ User.perform(:force_password_reset, user)
+ end
+
def perform(
%{
"op" => "blocks_import",
diff --git a/test/user_test.exs b/test/user_test.exs
index 39ba69668..164172405 100644
--- a/test/user_test.exs
+++ b/test/user_test.exs
@@ -1690,4 +1690,21 @@ test "changes email", %{user: user} do
assert {:ok, %User{email: "cofe@cofe.party"}} = User.change_email(user, "cofe@cofe.party")
end
end
+
+ describe "set_password_reset_pending/2" do
+ setup do
+ [user: insert(:user)]
+ end
+
+ test "sets password_reset_pending to true", %{user: user} do
+ %{password_reset_pending: password_reset_pending} = user.info
+
+ refute password_reset_pending
+
+ {:ok, %{info: %{password_reset_pending: password_reset_pending}}} =
+ User.force_password_reset(user)
+
+ assert password_reset_pending
+ end
+ end
end
diff --git a/test/web/admin_api/admin_api_controller_test.exs b/test/web/admin_api/admin_api_controller_test.exs
index 108143f6a..f00e02a7a 100644
--- a/test/web/admin_api/admin_api_controller_test.exs
+++ b/test/web/admin_api/admin_api_controller_test.exs
@@ -4,11 +4,13 @@
defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
use Pleroma.Web.ConnCase
+ use Oban.Testing, repo: Pleroma.Repo
alias Pleroma.Activity
alias Pleroma.HTML
alias Pleroma.ModerationLog
alias Pleroma.Repo
+ alias Pleroma.Tests.ObanHelpers
alias Pleroma.User
alias Pleroma.UserInviteToken
alias Pleroma.Web.CommonAPI
@@ -2351,6 +2353,30 @@ test "returns the log with pagination", %{conn: conn, admin: admin} do
"@#{admin.nickname} followed relay: https://example.org/relay"
end
end
+
+ describe "PATCH /users/:nickname/force_password_reset" do
+ setup %{conn: conn} do
+ admin = insert(:user, info: %{is_admin: true})
+ user = insert(:user)
+
+ %{conn: assign(conn, :user, admin), admin: admin, user: user}
+ end
+
+ test "sets password_reset_pending to true", %{admin: admin, user: user} do
+ assert user.info.password_reset_pending == false
+
+ conn =
+ build_conn()
+ |> assign(:user, admin)
+ |> patch("/api/pleroma/admin/users/#{user.nickname}/force_password_reset")
+
+ assert json_response(conn, 204) == ""
+
+ ObanHelpers.perform_all()
+
+ assert User.get_by_id(user.id).info.password_reset_pending == true
+ end
+ end
end
# Needed for testing
diff --git a/test/web/oauth/oauth_controller_test.exs b/test/web/oauth/oauth_controller_test.exs
index 2780e1746..8b88fd784 100644
--- a/test/web/oauth/oauth_controller_test.exs
+++ b/test/web/oauth/oauth_controller_test.exs
@@ -831,6 +831,33 @@ test "rejects token exchange for valid credentials belonging to deactivated user
refute Map.has_key?(resp, "access_token")
end
+ test "rejects token exchange for user with password_reset_pending set to true" do
+ password = "testpassword"
+
+ user =
+ insert(:user,
+ password_hash: Comeonin.Pbkdf2.hashpwsalt(password),
+ info: %{password_reset_pending: true}
+ )
+
+ app = insert(:oauth_app, scopes: ["read", "write"])
+
+ conn =
+ build_conn()
+ |> post("/oauth/token", %{
+ "grant_type" => "password",
+ "username" => user.nickname,
+ "password" => password,
+ "client_id" => app.client_id,
+ "client_secret" => app.client_secret
+ })
+
+ assert resp = json_response(conn, 403)
+
+ assert resp["error"] == "Password reset is required"
+ refute Map.has_key?(resp, "access_token")
+ end
+
test "rejects an invalid authorization code" do
app = insert(:oauth_app)
diff --git a/test/web/twitter_api/password_controller_test.exs b/test/web/twitter_api/password_controller_test.exs
index 3a7246ea8..dc6d4e3e3 100644
--- a/test/web/twitter_api/password_controller_test.exs
+++ b/test/web/twitter_api/password_controller_test.exs
@@ -6,6 +6,7 @@ defmodule Pleroma.Web.TwitterAPI.PasswordControllerTest do
use Pleroma.Web.ConnCase
alias Pleroma.PasswordResetToken
+ alias Pleroma.User
alias Pleroma.Web.OAuth.Token
import Pleroma.Factory
@@ -56,5 +57,25 @@ test "it returns HTTP 200", %{conn: conn} do
assert Comeonin.Pbkdf2.checkpw("test", user.password_hash)
assert length(Token.get_user_tokens(user)) == 0
end
+
+ test "it sets password_reset_pending to false", %{conn: conn} do
+ user = insert(:user, info: %{password_reset_pending: true})
+
+ {:ok, token} = PasswordResetToken.create_token(user)
+ {:ok, _access_token} = Token.create_token(insert(:oauth_app), user, %{})
+
+ params = %{
+ "password" => "test",
+ password_confirmation: "test",
+ token: token.token
+ }
+
+ conn
+ |> assign(:user, user)
+ |> post("/api/pleroma/password_reset", %{data: params})
+ |> html_response(:ok)
+
+ assert User.get_by_id(user.id).info.password_reset_pending == false
+ end
end
end
From d72d4757a8e66c29d58e0a3b7fb36356ae419a54 Mon Sep 17 00:00:00 2001
From: Maxim Filippov
Date: Sun, 22 Sep 2019 23:13:48 +0300
Subject: [PATCH 10/15] Format
---
lib/pleroma/user/info.ex | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/lib/pleroma/user/info.ex b/lib/pleroma/user/info.ex
index 67abc3ecd..99745f496 100644
--- a/lib/pleroma/user/info.ex
+++ b/lib/pleroma/user/info.ex
@@ -342,7 +342,9 @@ defp valid_field?(%{"name" => name, "value" => value}) do
name_limit = Pleroma.Config.get([:instance, :account_field_name_length], 255)
value_limit = Pleroma.Config.get([:instance, :account_field_value_length], 255)
- is_binary(name) && is_binary(value) && String.length(name) <= name_limit &&
+ is_binary(name) &&
+ is_binary(value) &&
+ String.length(name) <= name_limit &&
String.length(value) <= value_limit
end
From cf1960d5961a3a01a6d92c44ab4a6d0ce9570a09 Mon Sep 17 00:00:00 2001
From: Maxim Filippov
Date: Sun, 22 Sep 2019 23:14:18 +0300
Subject: [PATCH 11/15] Better changelog wording
---
CHANGELOG.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e5a84f5ae..f28299666 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,7 +6,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [Unreleased]
### Added
- Refreshing poll results for remote polls
-- Admin API: Add ability to force user's password reset
+- Admin API: Add ability to require password reset
### Changed
- **Breaking:** Elixir >=1.8 is now required (was >= 1.7)
From 646bf0160893f01fe14d1d38f24420ac6c962804 Mon Sep 17 00:00:00 2001
From: "Haelwenn (lanodan) Monnier"
Date: Mon, 23 Sep 2019 21:13:39 +0200
Subject: [PATCH 12/15] Update AdminFE bundle
---
.../{app.34fc670f.css => app.40438ff5.css} | 2 +-
priv/static/adminfe/chunk-06db.75709645.css | 1 +
priv/static/adminfe/chunk-15fa.bcc01554.css | 1 +
priv/static/adminfe/chunk-1a7d.38eb00cf.css | 1 +
...1.6aaab273.css => chunk-1f27.c0efd1fc.css} | 0
priv/static/adminfe/chunk-2325.0d22684d.css | 1 -
...8.e12401fb.css => chunk-3d1c.2880a519.css} | 0
priv/static/adminfe/chunk-5913.33f0e7ff.css | 1 +
...f.d7a1893c.css => chunk-598f.dc5869e7.css} | 0
...7.ac97b15a.css => chunk-6292.d1c82a11.css} | 0
priv/static/adminfe/chunk-7c6b.4a8663a9.css | 1 +
priv/static/adminfe/chunk-8b70.9ba0945c.css | 1 -
priv/static/adminfe/chunk-e547.e4b6230b.css | 1 -
...d8da6.css => chunk-elementUI.f35d8ab1.css} | 0
...s.4e8c4664.css => chunk-libs.00388c73.css} | 0
priv/static/adminfe/index.html | 2 +-
.../static/adminfe/static/js/7zzA.e1ae1c94.js | Bin 374 -> 416 bytes
.../adminfe/static/js/7zzA.e1ae1c94.js.map | Bin 0 -> 1913 bytes
.../static/adminfe/static/js/JEtC.f9ba4594.js | Bin 388 -> 430 bytes
.../adminfe/static/js/JEtC.f9ba4594.js.map | Bin 0 -> 1903 bytes
priv/static/adminfe/static/js/app.8e186193.js | Bin 137815 -> 0 bytes
priv/static/adminfe/static/js/app.90c455c5.js | Bin 0 -> 161629 bytes
.../adminfe/static/js/app.90c455c5.js.map | Bin 0 -> 354948 bytes
.../adminfe/static/js/chunk-02a0.db6ec114.js | Bin 266229 -> 0 bytes
.../adminfe/static/js/chunk-0620.c765c190.js | Bin 12982 -> 13030 bytes
.../static/js/chunk-0620.c765c190.js.map | Bin 0 -> 63567 bytes
.../adminfe/static/js/chunk-06db.12facc20.js | Bin 0 -> 5112 bytes
.../static/js/chunk-06db.12facc20.js.map | Bin 0 -> 19586 bytes
.../adminfe/static/js/chunk-15fa.b0633695.js | Bin 0 -> 7919 bytes
.../static/js/chunk-15fa.b0633695.js.map | Bin 0 -> 17438 bytes
.../adminfe/static/js/chunk-16d0.6ce78978.js | Bin 0 -> 1576 bytes
.../static/js/chunk-16d0.6ce78978.js.map | Bin 0 -> 4426 bytes
.../adminfe/static/js/chunk-1a7d.8173d81f.js | Bin 0 -> 16157 bytes
.../static/js/chunk-1a7d.8173d81f.js.map | Bin 0 -> 57112 bytes
...8e1.7f9c377c.js => chunk-1f27.d3c35fbc.js} | Bin 2032 -> 2080 bytes
.../static/js/chunk-1f27.d3c35fbc.js.map | Bin 0 -> 9090 bytes
.../adminfe/static/js/chunk-2325.154a537b.js | Bin 8220 -> 0 bytes
...e18.208cd826.js => chunk-3d1c.20303ef7.js} | Bin 4774 -> 4822 bytes
.../static/js/chunk-3d1c.20303ef7.js.map | Bin 0 -> 18519 bytes
.../adminfe/static/js/chunk-5913.1d21a547.js | Bin 0 -> 27091 bytes
.../static/js/chunk-5913.1d21a547.js.map | Bin 0 -> 88770 bytes
...fbf.616fb309.js => chunk-598f.dd8089ce.js} | Bin 17717 -> 17765 bytes
.../static/js/chunk-598f.dd8089ce.js.map | Bin 0 -> 66937 bytes
.../adminfe/static/js/chunk-5e57.7313703a.js | Bin 217441 -> 0 bytes
.../adminfe/static/js/chunk-6292.0e668979.js | Bin 0 -> 231394 bytes
.../static/js/chunk-6292.0e668979.js.map | Bin 0 -> 689117 bytes
.../adminfe/static/js/chunk-7c6b.c306c730.js | Bin 0 -> 7947 bytes
.../static/js/chunk-7c6b.c306c730.js.map | Bin 0 -> 26432 bytes
.../adminfe/static/js/chunk-7fe2.458f9da5.js | Bin 408401 -> 408449 bytes
.../static/js/chunk-7fe2.458f9da5.js.map | Bin 0 -> 1242154 bytes
.../adminfe/static/js/chunk-8b70.46525646.js | Bin 3190 -> 0 bytes
.../adminfe/static/js/chunk-df62.6c5105a6.js | Bin 0 -> 265970 bytes
.../static/js/chunk-df62.6c5105a6.js.map | Bin 0 -> 796489 bytes
.../adminfe/static/js/chunk-e547.d57d1b91.js | Bin 23125 -> 0 bytes
...911151b.js => chunk-elementUI.708d6b68.js} | Bin 638883 -> 638936 bytes
.../static/js/chunk-elementUI.708d6b68.js.map | Bin 0 -> 2312798 bytes
.../adminfe/static/js/chunk-libs.14514767.js | Bin 0 -> 275816 bytes
.../static/js/chunk-libs.14514767.js.map | Bin 0 -> 1641569 bytes
.../adminfe/static/js/chunk-libs.fb0b7f4a.js | Bin 204635 -> 0 bytes
.../static/adminfe/static/js/oAJy.840fb1c2.js | Bin 0 -> 28900 bytes
.../adminfe/static/js/oAJy.840fb1c2.js.map | Bin 0 -> 135594 bytes
.../adminfe/static/js/runtime.e85850af.js | Bin 0 -> 3859 bytes
.../adminfe/static/js/runtime.e85850af.js.map | Bin 0 -> 16537 bytes
.../adminfe/static/js/runtime.f40c8ec4.js | Bin 3608 -> 0 bytes
64 files changed, 7 insertions(+), 5 deletions(-)
rename priv/static/adminfe/{app.34fc670f.css => app.40438ff5.css} (92%)
create mode 100644 priv/static/adminfe/chunk-06db.75709645.css
create mode 100644 priv/static/adminfe/chunk-15fa.bcc01554.css
create mode 100644 priv/static/adminfe/chunk-1a7d.38eb00cf.css
rename priv/static/adminfe/{chunk-18e1.6aaab273.css => chunk-1f27.c0efd1fc.css} (100%)
delete mode 100644 priv/static/adminfe/chunk-2325.0d22684d.css
rename priv/static/adminfe/{chunk-0e18.e12401fb.css => chunk-3d1c.2880a519.css} (100%)
create mode 100644 priv/static/adminfe/chunk-5913.33f0e7ff.css
rename priv/static/adminfe/{chunk-1fbf.d7a1893c.css => chunk-598f.dc5869e7.css} (100%)
rename priv/static/adminfe/{chunk-5e57.ac97b15a.css => chunk-6292.d1c82a11.css} (100%)
create mode 100644 priv/static/adminfe/chunk-7c6b.4a8663a9.css
delete mode 100644 priv/static/adminfe/chunk-8b70.9ba0945c.css
delete mode 100644 priv/static/adminfe/chunk-e547.e4b6230b.css
rename priv/static/adminfe/{chunk-elementUI.e5cd8da6.css => chunk-elementUI.f35d8ab1.css} (100%)
rename priv/static/adminfe/{chunk-libs.4e8c4664.css => chunk-libs.00388c73.css} (100%)
create mode 100644 priv/static/adminfe/static/js/7zzA.e1ae1c94.js.map
create mode 100644 priv/static/adminfe/static/js/JEtC.f9ba4594.js.map
delete mode 100644 priv/static/adminfe/static/js/app.8e186193.js
create mode 100644 priv/static/adminfe/static/js/app.90c455c5.js
create mode 100644 priv/static/adminfe/static/js/app.90c455c5.js.map
delete mode 100644 priv/static/adminfe/static/js/chunk-02a0.db6ec114.js
create mode 100644 priv/static/adminfe/static/js/chunk-0620.c765c190.js.map
create mode 100644 priv/static/adminfe/static/js/chunk-06db.12facc20.js
create mode 100644 priv/static/adminfe/static/js/chunk-06db.12facc20.js.map
create mode 100644 priv/static/adminfe/static/js/chunk-15fa.b0633695.js
create mode 100644 priv/static/adminfe/static/js/chunk-15fa.b0633695.js.map
create mode 100644 priv/static/adminfe/static/js/chunk-16d0.6ce78978.js
create mode 100644 priv/static/adminfe/static/js/chunk-16d0.6ce78978.js.map
create mode 100644 priv/static/adminfe/static/js/chunk-1a7d.8173d81f.js
create mode 100644 priv/static/adminfe/static/js/chunk-1a7d.8173d81f.js.map
rename priv/static/adminfe/static/js/{chunk-18e1.7f9c377c.js => chunk-1f27.d3c35fbc.js} (83%)
create mode 100644 priv/static/adminfe/static/js/chunk-1f27.d3c35fbc.js.map
delete mode 100644 priv/static/adminfe/static/js/chunk-2325.154a537b.js
rename priv/static/adminfe/static/js/{chunk-0e18.208cd826.js => chunk-3d1c.20303ef7.js} (96%)
create mode 100644 priv/static/adminfe/static/js/chunk-3d1c.20303ef7.js.map
create mode 100644 priv/static/adminfe/static/js/chunk-5913.1d21a547.js
create mode 100644 priv/static/adminfe/static/js/chunk-5913.1d21a547.js.map
rename priv/static/adminfe/static/js/{chunk-1fbf.616fb309.js => chunk-598f.dd8089ce.js} (99%)
create mode 100644 priv/static/adminfe/static/js/chunk-598f.dd8089ce.js.map
delete mode 100644 priv/static/adminfe/static/js/chunk-5e57.7313703a.js
create mode 100644 priv/static/adminfe/static/js/chunk-6292.0e668979.js
create mode 100644 priv/static/adminfe/static/js/chunk-6292.0e668979.js.map
create mode 100644 priv/static/adminfe/static/js/chunk-7c6b.c306c730.js
create mode 100644 priv/static/adminfe/static/js/chunk-7c6b.c306c730.js.map
create mode 100644 priv/static/adminfe/static/js/chunk-7fe2.458f9da5.js.map
delete mode 100644 priv/static/adminfe/static/js/chunk-8b70.46525646.js
create mode 100644 priv/static/adminfe/static/js/chunk-df62.6c5105a6.js
create mode 100644 priv/static/adminfe/static/js/chunk-df62.6c5105a6.js.map
delete mode 100644 priv/static/adminfe/static/js/chunk-e547.d57d1b91.js
rename priv/static/adminfe/static/js/{chunk-elementUI.1911151b.js => chunk-elementUI.708d6b68.js} (99%)
create mode 100644 priv/static/adminfe/static/js/chunk-elementUI.708d6b68.js.map
create mode 100644 priv/static/adminfe/static/js/chunk-libs.14514767.js
create mode 100644 priv/static/adminfe/static/js/chunk-libs.14514767.js.map
delete mode 100644 priv/static/adminfe/static/js/chunk-libs.fb0b7f4a.js
create mode 100644 priv/static/adminfe/static/js/oAJy.840fb1c2.js
create mode 100644 priv/static/adminfe/static/js/oAJy.840fb1c2.js.map
create mode 100644 priv/static/adminfe/static/js/runtime.e85850af.js
create mode 100644 priv/static/adminfe/static/js/runtime.e85850af.js.map
delete mode 100644 priv/static/adminfe/static/js/runtime.f40c8ec4.js
diff --git a/priv/static/adminfe/app.34fc670f.css b/priv/static/adminfe/app.40438ff5.css
similarity index 92%
rename from priv/static/adminfe/app.34fc670f.css
rename to priv/static/adminfe/app.40438ff5.css
index 136aa8bb1..b82fcc39e 100644
--- a/priv/static/adminfe/app.34fc670f.css
+++ b/priv/static/adminfe/app.40438ff5.css
@@ -1 +1 @@
-.fade-enter-active,.fade-leave-active{-webkit-transition:opacity .28s;transition:opacity .28s}.fade-enter,.fade-leave-active{opacity:0}.fade-transform-enter-active,.fade-transform-leave-active{-webkit-transition:all .5s;transition:all .5s}.fade-transform-enter{opacity:0;-webkit-transform:translateX(-30px);transform:translateX(-30px)}.fade-transform-leave-to{opacity:0;-webkit-transform:translateX(30px);transform:translateX(30px)}.breadcrumb-enter-active,.breadcrumb-leave-active{-webkit-transition:all .5s;transition:all .5s}.breadcrumb-enter,.breadcrumb-leave-active{opacity:0;-webkit-transform:translateX(20px);transform:translateX(20px)}.breadcrumb-move{-webkit-transition:all .5s;transition:all .5s}.breadcrumb-leave-active{position:absolute}.el-breadcrumb__inner,.el-breadcrumb__inner a{font-weight:400!important}.el-upload input[type=file]{display:none!important}.el-upload__input{display:none}.cell .el-tag{margin-right:0}.small-padding .cell{padding-left:5px;padding-right:5px}.fixed-width .el-button--mini{padding:7px 10px;width:60px}.status-col .cell{padding:0 10px;text-align:center}.status-col .cell .el-tag{margin-right:0}.el-dialog{-webkit-transform:none;transform:none;left:0;position:relative;margin:0 auto}.article-textarea textarea{padding-right:40px;resize:none;border-radius:0;border:none;border-bottom:1px solid #bfcbd9}.upload-container .el-upload{width:100%}.upload-container .el-upload .el-upload-dragger{width:100%;height:200px}.el-dropdown-menu a{display:block}#app .main-container{min-height:100%;-webkit-transition:margin-left .28s;transition:margin-left .28s;margin-left:180px;position:relative}#app .sidebar-container{-webkit-transition:width .28s;transition:width .28s;width:180px!important;height:100%;position:fixed;font-size:0;top:0;bottom:0;left:0;z-index:1001;overflow:hidden}#app .sidebar-container .horizontal-collapse-transition{-webkit-transition:width 0s ease-in-out,padding-left 0s ease-in-out,padding-right 0s ease-in-out;transition:width 0s ease-in-out,padding-left 0s ease-in-out,padding-right 0s ease-in-out}#app .sidebar-container .scrollbar-wrapper{overflow-x:hidden!important}#app .sidebar-container .scrollbar-wrapper .el-scrollbar__view{height:100%}#app .sidebar-container .el-scrollbar__bar.is-vertical{right:0}#app .sidebar-container .is-horizontal{display:none}#app .sidebar-container a{display:inline-block;width:100%;overflow:hidden}#app .sidebar-container .svg-icon{margin-right:16px}#app .sidebar-container .el-menu{border:none;height:100%;width:100%!important}#app .sidebar-container .el-submenu__title:hover,#app .sidebar-container .submenu-title-noDropdown:hover{background-color:#263445!important}#app .sidebar-container .is-active>.el-submenu__title{color:#f4f4f5!important}#app .sidebar-container .el-submenu .el-menu-item,#app .sidebar-container .nest-menu .el-submenu>.el-submenu__title{min-width:180px!important;background-color:#1f2d3d!important}#app .sidebar-container .el-submenu .el-menu-item:hover,#app .sidebar-container .nest-menu .el-submenu>.el-submenu__title:hover{background-color:#001528!important}#app .hideSidebar .sidebar-container{width:36px!important}#app .hideSidebar .main-container{margin-left:36px}#app .hideSidebar .submenu-title-noDropdown{padding-left:10px!important;position:relative}#app .hideSidebar .submenu-title-noDropdown .el-tooltip{padding:0 10px!important}#app .hideSidebar .el-submenu{overflow:hidden}#app .hideSidebar .el-submenu>.el-submenu__title{padding-left:10px!important}#app .hideSidebar .el-submenu>.el-submenu__title .el-submenu__icon-arrow{display:none}#app .hideSidebar .el-menu--collapse .el-submenu>.el-submenu__title>span{height:0;width:0;overflow:hidden;visibility:hidden;display:inline-block}#app .el-menu--collapse .el-menu .el-submenu{min-width:180px!important}#app .mobile .main-container{margin-left:0}#app .mobile .sidebar-container{-webkit-transition:-webkit-transform .28s;transition:-webkit-transform .28s;transition:transform .28s;transition:transform .28s,-webkit-transform .28s;width:180px!important}#app .mobile.hideSidebar .sidebar-container{pointer-events:none;-webkit-transition-duration:.3s;transition-duration:.3s;-webkit-transform:translate3d(-180px,0,0);transform:translate3d(-180px,0,0)}#app .withoutAnimation .main-container,#app .withoutAnimation .sidebar-container{-webkit-transition:none;transition:none}.el-menu--vertical>.el-menu .svg-icon{margin-right:16px}.el-menu--vertical .el-menu-item:hover,.el-menu--vertical .nest-menu .el-submenu>.el-submenu__title:hover{background-color:#263445!important}.blue-btn{background:#324157}.blue-btn:hover{color:#324157}.blue-btn:hover:after,.blue-btn:hover:before{background:#324157}.light-blue-btn{background:#3a71a8}.light-blue-btn:hover{color:#3a71a8}.light-blue-btn:hover:after,.light-blue-btn:hover:before{background:#3a71a8}.red-btn{background:#c03639}.red-btn:hover{color:#c03639}.red-btn:hover:after,.red-btn:hover:before{background:#c03639}.pink-btn{background:#e65d6e}.pink-btn:hover{color:#e65d6e}.pink-btn:hover:after,.pink-btn:hover:before{background:#e65d6e}.green-btn{background:#30b08f}.green-btn:hover{color:#30b08f}.green-btn:hover:after,.green-btn:hover:before{background:#30b08f}.tiffany-btn{background:#4ab7bd}.tiffany-btn:hover{color:#4ab7bd}.tiffany-btn:hover:after,.tiffany-btn:hover:before{background:#4ab7bd}.yellow-btn{background:#fec171}.yellow-btn:hover{color:#fec171}.yellow-btn:hover:after,.yellow-btn:hover:before{background:#fec171}.pan-btn{font-size:14px;color:#fff;padding:14px 36px;border-radius:8px;border:none;outline:none;-webkit-transition:all .6s ease;transition:all .6s ease;position:relative;display:inline-block}.pan-btn:hover{background:#fff}.pan-btn:hover:after,.pan-btn:hover:before{width:100%;-webkit-transition:all .6s ease;transition:all .6s ease}.pan-btn:after,.pan-btn:before{content:"";position:absolute;top:0;right:0;height:2px;width:0;-webkit-transition:all .4s ease;transition:all .4s ease}.pan-btn:after{right:inherit;top:inherit;left:0;bottom:0}.custom-button{display:inline-block;line-height:1;white-space:nowrap;cursor:pointer;background:#fff;color:#fff;-webkit-appearance:none;text-align:center;-webkit-box-sizing:border-box;box-sizing:border-box;outline:0;margin:0;padding:10px 15px;font-size:14px;border-radius:4px}body{height:100%;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;font-family:Helvetica Neue,Helvetica,PingFang SC,Hiragino Sans GB,Microsoft YaHei,Arial,sans-serif}label{font-weight:700}html{-webkit-box-sizing:border-box;box-sizing:border-box}#app,html{height:100%}*,:after,:before{-webkit-box-sizing:inherit;box-sizing:inherit}.no-padding{padding:0!important}.padding-content{padding:4px 0}a:active,a:focus{outline:none}a,a:focus,a:hover{cursor:pointer;color:inherit;text-decoration:none}div:focus{outline:none}.fr{float:right}.fl{float:left}.pr-5{padding-right:5px}.pl-5{padding-left:5px}.block{display:block}.pointer{cursor:pointer}.inlineBlock{display:block}.clearfix:after{visibility:hidden;display:block;font-size:0;content:" ";clear:both;height:0}code{background:#eef1f6;padding:15px 16px;margin-bottom:20px;display:block;line-height:36px;font-size:15px;font-family:Source Sans Pro,Helvetica Neue,Arial,sans-serif}code a{color:#337ab7;cursor:pointer}code a:hover{color:#20a0ff}.warn-content{background:rgba(66,185,131,.1);border-radius:2px;padding:1rem;line-height:1.6rem;word-spacing:.05rem}.warn-content a{color:#42b983;font-weight:600}.app-container{padding:20px}.components-container{margin:30px 50px;position:relative}.pagination-container{margin-top:30px}.text-center{text-align:center}.sub-navbar{height:50px;line-height:50px;position:relative;width:100%;text-align:right;padding-right:20px;-webkit-transition:position .6s ease;transition:position .6s ease;background:-webkit-gradient(linear,left top,right top,from(#20b6f9),color-stop(0,#20b6f9),color-stop(100%,#2178f1),to(#2178f1));background:linear-gradient(90deg,#20b6f9,#20b6f9 0,#2178f1 100%,#2178f1 0)}.sub-navbar .subtitle{font-size:20px;color:#fff}.sub-navbar.deleted,.sub-navbar.draft{background:#d0d0d0}.link-type,.link-type:focus{color:#337ab7;cursor:pointer}.link-type:focus:hover,.link-type:hover{color:#20a0ff}.filter-container{padding-bottom:10px}.filter-container .filter-item{display:inline-block;vertical-align:middle;margin-bottom:10px}.multiselect{line-height:16px}.multiselect--active{z-index:1000!important}.hamburger[data-v-3ee86d44]{display:inline-block;vertical-align:middle;width:20px;height:20px}.hamburger.is-active[data-v-3ee86d44]{-webkit-transform:rotate(180deg);transform:rotate(180deg)}.navbar[data-v-5e8599dc]{height:50px;overflow:hidden}.navbar .hamburger-container[data-v-5e8599dc]{line-height:46px;height:100%;float:left;cursor:pointer;-webkit-transition:background .3s;transition:background .3s}.navbar .hamburger-container[data-v-5e8599dc]:hover{background:rgba(0,0,0,.025)}.navbar .breadcrumb-container[data-v-5e8599dc]{float:left}.navbar .errLog-container[data-v-5e8599dc]{display:inline-block;vertical-align:top}.navbar .right-menu[data-v-5e8599dc]{float:right;height:100%;line-height:50px}.navbar .right-menu[data-v-5e8599dc]:focus{outline:none}.navbar .right-menu .right-menu-item[data-v-5e8599dc]{display:inline-block;padding:0 8px;height:100%;font-size:18px;color:#5a5e66;vertical-align:text-bottom}.navbar .right-menu .right-menu-item.hover-effect[data-v-5e8599dc]{cursor:pointer;-webkit-transition:background .3s;transition:background .3s}.navbar .right-menu .right-menu-item.hover-effect[data-v-5e8599dc]:hover{background:rgba(0,0,0,.025)}.navbar .right-menu .avatar-container .avatar-wrapper[data-v-5e8599dc]{margin-top:5px;position:relative}.navbar .right-menu .avatar-container .avatar-wrapper .user-avatar[data-v-5e8599dc]{cursor:pointer;width:40px;height:40px;border-radius:10px}.navbar .right-menu .avatar-container .avatar-wrapper .el-icon-caret-bottom[data-v-5e8599dc]{cursor:pointer;position:absolute;right:-20px;top:25px;font-size:12px}.scroll-container[data-v-547b5dea]{white-space:nowrap;position:relative;overflow:hidden;width:100%}.scroll-container[data-v-547b5dea] .el-scrollbar__bar{bottom:0}.scroll-container[data-v-547b5dea] .el-scrollbar__wrap{height:49px}.tags-view-container[data-v-67e96c42]{height:34px;width:100%;background:#fff;border-bottom:1px solid #d8dce5;-webkit-box-shadow:0 1px 3px 0 rgba(0,0,0,.12),0 0 3px 0 rgba(0,0,0,.04);box-shadow:0 1px 3px 0 rgba(0,0,0,.12),0 0 3px 0 rgba(0,0,0,.04)}.tags-view-container .tags-view-wrapper .tags-view-item[data-v-67e96c42]{display:inline-block;position:relative;cursor:pointer;height:26px;line-height:26px;border:1px solid #d8dce5;color:#495060;background:#fff;padding:0 8px;font-size:12px;margin-left:5px;margin-top:4px}.tags-view-container .tags-view-wrapper .tags-view-item[data-v-67e96c42]:first-of-type{margin-left:15px}.tags-view-container .tags-view-wrapper .tags-view-item[data-v-67e96c42]:last-of-type{margin-right:15px}.tags-view-container .tags-view-wrapper .tags-view-item.active[data-v-67e96c42]{background-color:#42b983;color:#fff;border-color:#42b983}.tags-view-container .tags-view-wrapper .tags-view-item.active[data-v-67e96c42]:before{content:"";background:#fff;display:inline-block;width:8px;height:8px;border-radius:50%;position:relative;margin-right:2px}.tags-view-container .contextmenu[data-v-67e96c42]{margin:0;background:#fff;z-index:100;position:absolute;list-style-type:none;padding:5px 0;border-radius:4px;font-size:12px;font-weight:400;color:#333;-webkit-box-shadow:2px 2px 3px 0 rgba(0,0,0,.3);box-shadow:2px 2px 3px 0 rgba(0,0,0,.3)}.tags-view-container .contextmenu li[data-v-67e96c42]{margin:0;padding:7px 16px;cursor:pointer}.tags-view-container .contextmenu li[data-v-67e96c42]:hover{background:#eee}.tags-view-wrapper .tags-view-item .el-icon-close{width:16px;height:16px;vertical-align:2px;border-radius:50%;text-align:center;-webkit-transition:all .3s cubic-bezier(.645,.045,.355,1);transition:all .3s cubic-bezier(.645,.045,.355,1);-webkit-transform-origin:100% 50%;transform-origin:100% 50%}.tags-view-wrapper .tags-view-item .el-icon-close:before{-webkit-transform:scale(.6);transform:scale(.6);display:inline-block;vertical-align:-3px}.tags-view-wrapper .tags-view-item .el-icon-close:hover{background-color:#b4bccc;color:#fff}.app-main[data-v-f852c4f2]{min-height:calc(100vh - 84px);width:100%;position:relative;overflow:hidden}.app-wrapper[data-v-767d264f]{position:relative;height:100%;width:100%}.app-wrapper[data-v-767d264f]:after{content:"";display:table;clear:both}.app-wrapper.mobile.openSidebar[data-v-767d264f]{position:fixed;top:0}.drawer-bg[data-v-767d264f]{background:#000;opacity:.3;width:100%;top:0;height:100%;position:absolute;z-index:999}.svg-icon[data-v-4e710b96]{width:1em;height:1em;vertical-align:-.15em;fill:currentColor;overflow:hidden}
\ No newline at end of file
+.fade-enter-active,.fade-leave-active{-webkit-transition:opacity .28s;transition:opacity .28s}.fade-enter,.fade-leave-active{opacity:0}.fade-transform-enter-active,.fade-transform-leave-active{-webkit-transition:all .5s;transition:all .5s}.fade-transform-enter{opacity:0;-webkit-transform:translateX(-30px);transform:translateX(-30px)}.fade-transform-leave-to{opacity:0;-webkit-transform:translateX(30px);transform:translateX(30px)}.breadcrumb-enter-active,.breadcrumb-leave-active{-webkit-transition:all .5s;transition:all .5s}.breadcrumb-enter,.breadcrumb-leave-active{opacity:0;-webkit-transform:translateX(20px);transform:translateX(20px)}.breadcrumb-move{-webkit-transition:all .5s;transition:all .5s}.breadcrumb-leave-active{position:absolute}.el-breadcrumb__inner,.el-breadcrumb__inner a{font-weight:400!important}.el-upload input[type=file]{display:none!important}.el-upload__input{display:none}.cell .el-tag{margin-right:0}.small-padding .cell{padding-left:5px;padding-right:5px}.fixed-width .el-button--mini{padding:7px 10px;width:60px}.status-col .cell{padding:0 10px;text-align:center}.status-col .cell .el-tag{margin-right:0}.el-dialog{-webkit-transform:none;transform:none;left:0;position:relative;margin:0 auto}.article-textarea textarea{padding-right:40px;resize:none;border-radius:0;border:none;border-bottom:1px solid #bfcbd9}.upload-container .el-upload{width:100%}.upload-container .el-upload .el-upload-dragger{width:100%;height:200px}.el-dropdown-menu a{display:block}#app .main-container{min-height:100%;-webkit-transition:margin-left .28s;transition:margin-left .28s;margin-left:180px;position:relative}#app .sidebar-container{-webkit-transition:width .28s;transition:width .28s;width:180px!important;height:100%;position:fixed;font-size:0;top:0;bottom:0;left:0;z-index:1001;overflow:hidden}#app .sidebar-container .horizontal-collapse-transition{-webkit-transition:width 0s ease-in-out,padding-left 0s ease-in-out,padding-right 0s ease-in-out;transition:width 0s ease-in-out,padding-left 0s ease-in-out,padding-right 0s ease-in-out}#app .sidebar-container .scrollbar-wrapper{overflow-x:hidden!important}#app .sidebar-container .scrollbar-wrapper .el-scrollbar__view{height:100%}#app .sidebar-container .el-scrollbar__bar.is-vertical{right:0}#app .sidebar-container .is-horizontal{display:none}#app .sidebar-container a{display:inline-block;width:100%;overflow:hidden}#app .sidebar-container .svg-icon{margin-right:16px}#app .sidebar-container .el-menu{border:none;height:100%;width:100%!important}#app .sidebar-container .el-submenu__title:hover,#app .sidebar-container .submenu-title-noDropdown:hover{background-color:#263445!important}#app .sidebar-container .is-active>.el-submenu__title{color:#f4f4f5!important}#app .sidebar-container .el-submenu .el-menu-item,#app .sidebar-container .nest-menu .el-submenu>.el-submenu__title{min-width:180px!important;background-color:#1f2d3d!important}#app .sidebar-container .el-submenu .el-menu-item:hover,#app .sidebar-container .nest-menu .el-submenu>.el-submenu__title:hover{background-color:#001528!important}#app .hideSidebar .sidebar-container{width:36px!important}#app .hideSidebar .main-container{margin-left:36px}#app .hideSidebar .submenu-title-noDropdown{padding-left:10px!important;position:relative}#app .hideSidebar .submenu-title-noDropdown .el-tooltip{padding:0 10px!important}#app .hideSidebar .el-submenu{overflow:hidden}#app .hideSidebar .el-submenu>.el-submenu__title{padding-left:10px!important}#app .hideSidebar .el-submenu>.el-submenu__title .el-submenu__icon-arrow{display:none}#app .hideSidebar .el-menu--collapse .el-submenu>.el-submenu__title>span{height:0;width:0;overflow:hidden;visibility:hidden;display:inline-block}#app .el-menu--collapse .el-menu .el-submenu{min-width:180px!important}#app .mobile .main-container{margin-left:0}#app .mobile .sidebar-container{-webkit-transition:-webkit-transform .28s;transition:-webkit-transform .28s;transition:transform .28s;transition:transform .28s,-webkit-transform .28s;width:180px!important}#app .mobile.hideSidebar .sidebar-container{pointer-events:none;-webkit-transition-duration:.3s;transition-duration:.3s;-webkit-transform:translate3d(-180px,0,0);transform:translate3d(-180px,0,0)}#app .withoutAnimation .main-container,#app .withoutAnimation .sidebar-container{-webkit-transition:none;transition:none}.el-menu--vertical>.el-menu .svg-icon{margin-right:16px}.el-menu--vertical .el-menu-item:hover,.el-menu--vertical .nest-menu .el-submenu>.el-submenu__title:hover{background-color:#263445!important}.blue-btn{background:#324157}.blue-btn:hover{color:#324157}.blue-btn:hover:after,.blue-btn:hover:before{background:#324157}.light-blue-btn{background:#3a71a8}.light-blue-btn:hover{color:#3a71a8}.light-blue-btn:hover:after,.light-blue-btn:hover:before{background:#3a71a8}.red-btn{background:#c03639}.red-btn:hover{color:#c03639}.red-btn:hover:after,.red-btn:hover:before{background:#c03639}.pink-btn{background:#e65d6e}.pink-btn:hover{color:#e65d6e}.pink-btn:hover:after,.pink-btn:hover:before{background:#e65d6e}.green-btn{background:#30b08f}.green-btn:hover{color:#30b08f}.green-btn:hover:after,.green-btn:hover:before{background:#30b08f}.tiffany-btn{background:#4ab7bd}.tiffany-btn:hover{color:#4ab7bd}.tiffany-btn:hover:after,.tiffany-btn:hover:before{background:#4ab7bd}.yellow-btn{background:#fec171}.yellow-btn:hover{color:#fec171}.yellow-btn:hover:after,.yellow-btn:hover:before{background:#fec171}.pan-btn{font-size:14px;color:#fff;padding:14px 36px;border-radius:8px;border:none;outline:none;-webkit-transition:all .6s ease;transition:all .6s ease;position:relative;display:inline-block}.pan-btn:hover{background:#fff}.pan-btn:hover:after,.pan-btn:hover:before{width:100%;-webkit-transition:all .6s ease;transition:all .6s ease}.pan-btn:after,.pan-btn:before{content:"";position:absolute;top:0;right:0;height:2px;width:0;-webkit-transition:all .4s ease;transition:all .4s ease}.pan-btn:after{right:inherit;top:inherit;left:0;bottom:0}.custom-button{display:inline-block;line-height:1;white-space:nowrap;cursor:pointer;background:#fff;color:#fff;-webkit-appearance:none;text-align:center;-webkit-box-sizing:border-box;box-sizing:border-box;outline:0;margin:0;padding:10px 15px;font-size:14px;border-radius:4px}body{height:100%;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;font-family:Helvetica Neue,Helvetica,PingFang SC,Hiragino Sans GB,Microsoft YaHei,Arial,sans-serif}label{font-weight:700}html{-webkit-box-sizing:border-box;box-sizing:border-box}#app,html{height:100%}*,:after,:before{-webkit-box-sizing:inherit;box-sizing:inherit}.no-padding{padding:0!important}.padding-content{padding:4px 0}a:active,a:focus{outline:none}a,a:focus,a:hover{cursor:pointer;color:inherit;text-decoration:none}div:focus{outline:none}.fr{float:right}.fl{float:left}.pr-5{padding-right:5px}.pl-5{padding-left:5px}.block{display:block}.pointer{cursor:pointer}.inlineBlock{display:block}.clearfix:after{visibility:hidden;display:block;font-size:0;content:" ";clear:both;height:0}code{background:#eef1f6;padding:15px 16px;margin-bottom:20px;display:block;line-height:36px;font-size:15px;font-family:Source Sans Pro,Helvetica Neue,Arial,sans-serif}code a{color:#337ab7;cursor:pointer}code a:hover{color:#20a0ff}.warn-content{background:rgba(66,185,131,.1);border-radius:2px;padding:1rem;line-height:1.6rem;word-spacing:.05rem}.warn-content a{color:#42b983;font-weight:600}.app-container{padding:20px}.components-container{margin:30px 50px;position:relative}.pagination-container{margin-top:30px}.text-center{text-align:center}.sub-navbar{height:50px;line-height:50px;position:relative;width:100%;text-align:right;padding-right:20px;-webkit-transition:position .6s ease;transition:position .6s ease;background:-webkit-gradient(linear,left top,right top,from(#20b6f9),color-stop(0,#20b6f9),color-stop(100%,#2178f1),to(#2178f1));background:linear-gradient(90deg,#20b6f9,#20b6f9 0,#2178f1 100%,#2178f1 0)}.sub-navbar .subtitle{font-size:20px;color:#fff}.sub-navbar.deleted,.sub-navbar.draft{background:#d0d0d0}.link-type,.link-type:focus{color:#337ab7;cursor:pointer}.link-type:focus:hover,.link-type:hover{color:#20a0ff}.filter-container{padding-bottom:10px}.filter-container .filter-item{display:inline-block;vertical-align:middle;margin-bottom:10px}.multiselect{line-height:16px}.multiselect--active{z-index:1000!important}.hamburger[data-v-3ee86d44]{display:inline-block;vertical-align:middle;width:20px;height:20px}.hamburger.is-active[data-v-3ee86d44]{-webkit-transform:rotate(180deg);transform:rotate(180deg)}.navbar[data-v-b535c57a]{height:50px;overflow:hidden}.navbar .hamburger-container[data-v-b535c57a]{line-height:46px;height:100%;float:left;cursor:pointer;-webkit-transition:background .3s;transition:background .3s}.navbar .hamburger-container[data-v-b535c57a]:hover{background:rgba(0,0,0,.025)}.navbar .breadcrumb-container[data-v-b535c57a]{float:left}.navbar .errLog-container[data-v-b535c57a]{display:inline-block;vertical-align:top}.navbar .right-menu[data-v-b535c57a]{float:right;height:100%;line-height:50px}.navbar .right-menu[data-v-b535c57a]:focus{outline:none}.navbar .right-menu .right-menu-item[data-v-b535c57a]{display:inline-block;padding:0 8px;height:100%;font-size:18px;color:#5a5e66;vertical-align:text-bottom}.navbar .right-menu .right-menu-item.hover-effect[data-v-b535c57a]{cursor:pointer;-webkit-transition:background .3s;transition:background .3s}.navbar .right-menu .right-menu-item.hover-effect[data-v-b535c57a]:hover{background:rgba(0,0,0,.025)}.navbar .right-menu .avatar-container .avatar-wrapper[data-v-b535c57a]{margin-top:5px;position:relative}.navbar .right-menu .avatar-container .avatar-wrapper .user-avatar[data-v-b535c57a]{cursor:pointer;width:40px;height:40px;border-radius:10px}.navbar .right-menu .avatar-container .avatar-wrapper .el-icon-caret-bottom[data-v-b535c57a]{cursor:pointer;position:absolute;right:-20px;top:25px;font-size:12px}.scroll-container[data-v-547b5dea]{white-space:nowrap;position:relative;overflow:hidden;width:100%}.scroll-container[data-v-547b5dea] .el-scrollbar__bar{bottom:0}.scroll-container[data-v-547b5dea] .el-scrollbar__wrap{height:49px}.tags-view-container[data-v-67e96c42]{height:34px;width:100%;background:#fff;border-bottom:1px solid #d8dce5;-webkit-box-shadow:0 1px 3px 0 rgba(0,0,0,.12),0 0 3px 0 rgba(0,0,0,.04);box-shadow:0 1px 3px 0 rgba(0,0,0,.12),0 0 3px 0 rgba(0,0,0,.04)}.tags-view-container .tags-view-wrapper .tags-view-item[data-v-67e96c42]{display:inline-block;position:relative;cursor:pointer;height:26px;line-height:26px;border:1px solid #d8dce5;color:#495060;background:#fff;padding:0 8px;font-size:12px;margin-left:5px;margin-top:4px}.tags-view-container .tags-view-wrapper .tags-view-item[data-v-67e96c42]:first-of-type{margin-left:15px}.tags-view-container .tags-view-wrapper .tags-view-item[data-v-67e96c42]:last-of-type{margin-right:15px}.tags-view-container .tags-view-wrapper .tags-view-item.active[data-v-67e96c42]{background-color:#42b983;color:#fff;border-color:#42b983}.tags-view-container .tags-view-wrapper .tags-view-item.active[data-v-67e96c42]:before{content:"";background:#fff;display:inline-block;width:8px;height:8px;border-radius:50%;position:relative;margin-right:2px}.tags-view-container .contextmenu[data-v-67e96c42]{margin:0;background:#fff;z-index:100;position:absolute;list-style-type:none;padding:5px 0;border-radius:4px;font-size:12px;font-weight:400;color:#333;-webkit-box-shadow:2px 2px 3px 0 rgba(0,0,0,.3);box-shadow:2px 2px 3px 0 rgba(0,0,0,.3)}.tags-view-container .contextmenu li[data-v-67e96c42]{margin:0;padding:7px 16px;cursor:pointer}.tags-view-container .contextmenu li[data-v-67e96c42]:hover{background:#eee}.tags-view-wrapper .tags-view-item .el-icon-close{width:16px;height:16px;vertical-align:2px;border-radius:50%;text-align:center;-webkit-transition:all .3s cubic-bezier(.645,.045,.355,1);transition:all .3s cubic-bezier(.645,.045,.355,1);-webkit-transform-origin:100% 50%;transform-origin:100% 50%}.tags-view-wrapper .tags-view-item .el-icon-close:before{-webkit-transform:scale(.6);transform:scale(.6);display:inline-block;vertical-align:-3px}.tags-view-wrapper .tags-view-item .el-icon-close:hover{background-color:#b4bccc;color:#fff}.app-main[data-v-f852c4f2]{min-height:calc(100vh - 84px);width:100%;position:relative;overflow:hidden}.app-wrapper[data-v-767d264f]{position:relative;height:100%;width:100%}.app-wrapper[data-v-767d264f]:after{content:"";display:table;clear:both}.app-wrapper.mobile.openSidebar[data-v-767d264f]{position:fixed;top:0}.drawer-bg[data-v-767d264f]{background:#000;opacity:.3;width:100%;top:0;height:100%;position:absolute;z-index:999}.svg-icon[data-v-4e710b96]{width:1em;height:1em;vertical-align:-.15em;fill:currentColor;overflow:hidden}
\ No newline at end of file
diff --git a/priv/static/adminfe/chunk-06db.75709645.css b/priv/static/adminfe/chunk-06db.75709645.css
new file mode 100644
index 000000000..9e23d0fdb
--- /dev/null
+++ b/priv/static/adminfe/chunk-06db.75709645.css
@@ -0,0 +1 @@
+@supports (-webkit-mask:none) and (not (cater-color:#fff)){.login-container .el-input input{color:#fff}.login-container .el-input input:first-line{color:#eee}}.login-container .el-input{display:inline-block;height:47px;width:85%}.login-container .el-input input{background:transparent;border:0;-webkit-appearance:none;border-radius:0;padding:12px 5px 12px 15px;color:#eee;height:47px;caret-color:#fff}.login-container .el-input input:-webkit-autofill{-webkit-box-shadow:0 0 0 1000px #283443 inset!important;-webkit-text-fill-color:#fff!important}.login-container .el-form-item{border:1px solid hsla(0,0%,100%,.1);background:rgba(0,0,0,.1);border-radius:5px;color:#454545}.login-container .login-button{width:100%;margin:0 0 10px}.login-container .omit-host-note{color:#596f8c;font-size:.8em;font-style:italic;margin:-20px 0 15px;padding:3px 0 0 15px}.login-container[data-v-d027d802]{min-height:100%;width:100%;background-color:#2d3a4b;overflow:hidden}.login-container .login-form[data-v-d027d802]{position:relative;width:520px;max-width:100%;padding:160px 35px 0;margin:0 auto;overflow:hidden}.login-container .tips[data-v-d027d802]{font-size:14px;color:#fff;margin-bottom:10px}.login-container .tips span[data-v-d027d802]:first-of-type{margin-right:16px}.login-container .svg-container[data-v-d027d802]{padding:6px 5px 6px 15px;color:#889aa4;vertical-align:middle;width:30px;display:inline-block}.login-container .title-container[data-v-d027d802]{position:relative}.login-container .title-container .title[data-v-d027d802]{font-size:26px;color:#eee;margin:0 auto 40px;text-align:center;font-weight:700}.login-container .title-container .set-language[data-v-d027d802]{color:#fff;position:absolute;top:3px;font-size:18px;right:0;cursor:pointer}.login-container .show-pwd[data-v-d027d802]{position:absolute;right:10px;top:7px;font-size:16px;color:#889aa4;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.login-container .thirdparty-button[data-v-d027d802]{position:absolute;right:0;bottom:6px}
\ No newline at end of file
diff --git a/priv/static/adminfe/chunk-15fa.bcc01554.css b/priv/static/adminfe/chunk-15fa.bcc01554.css
new file mode 100644
index 000000000..30bf7de23
--- /dev/null
+++ b/priv/static/adminfe/chunk-15fa.bcc01554.css
@@ -0,0 +1 @@
+.wscn-http404-container[data-v-1d6b2d2a]{-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%);position:absolute;top:40%;left:50%}.wscn-http404[data-v-1d6b2d2a]{position:relative;width:1200px;padding:0 50px;overflow:hidden}.wscn-http404 .pic-404[data-v-1d6b2d2a]{position:relative;float:left;width:600px;overflow:hidden}.wscn-http404 .pic-404__parent[data-v-1d6b2d2a]{width:100%}.wscn-http404 .pic-404__child[data-v-1d6b2d2a]{position:absolute}.wscn-http404 .pic-404__child.left[data-v-1d6b2d2a]{width:80px;top:17px;left:220px;opacity:0;-webkit-animation-name:cloudLeft-data-v-1d6b2d2a;animation-name:cloudLeft-data-v-1d6b2d2a;-webkit-animation-duration:2s;animation-duration:2s;-webkit-animation-timing-function:linear;animation-timing-function:linear;-webkit-animation-fill-mode:forwards;animation-fill-mode:forwards;-webkit-animation-delay:1s;animation-delay:1s}.wscn-http404 .pic-404__child.mid[data-v-1d6b2d2a]{width:46px;top:10px;left:420px;opacity:0;-webkit-animation-name:cloudMid-data-v-1d6b2d2a;animation-name:cloudMid-data-v-1d6b2d2a;-webkit-animation-duration:2s;animation-duration:2s;-webkit-animation-timing-function:linear;animation-timing-function:linear;-webkit-animation-fill-mode:forwards;animation-fill-mode:forwards;-webkit-animation-delay:1.2s;animation-delay:1.2s}.wscn-http404 .pic-404__child.right[data-v-1d6b2d2a]{width:62px;top:100px;left:500px;opacity:0;-webkit-animation-name:cloudRight-data-v-1d6b2d2a;animation-name:cloudRight-data-v-1d6b2d2a;-webkit-animation-duration:2s;animation-duration:2s;-webkit-animation-timing-function:linear;animation-timing-function:linear;-webkit-animation-fill-mode:forwards;animation-fill-mode:forwards;-webkit-animation-delay:1s;animation-delay:1s}@-webkit-keyframes cloudLeft-data-v-1d6b2d2a{0%{top:17px;left:220px;opacity:0}20%{top:33px;left:188px;opacity:1}80%{top:81px;left:92px;opacity:1}to{top:97px;left:60px;opacity:0}}@keyframes cloudLeft-data-v-1d6b2d2a{0%{top:17px;left:220px;opacity:0}20%{top:33px;left:188px;opacity:1}80%{top:81px;left:92px;opacity:1}to{top:97px;left:60px;opacity:0}}@-webkit-keyframes cloudMid-data-v-1d6b2d2a{0%{top:10px;left:420px;opacity:0}20%{top:40px;left:360px;opacity:1}70%{top:130px;left:180px;opacity:1}to{top:160px;left:120px;opacity:0}}@keyframes cloudMid-data-v-1d6b2d2a{0%{top:10px;left:420px;opacity:0}20%{top:40px;left:360px;opacity:1}70%{top:130px;left:180px;opacity:1}to{top:160px;left:120px;opacity:0}}@-webkit-keyframes cloudRight-data-v-1d6b2d2a{0%{top:100px;left:500px;opacity:0}20%{top:120px;left:460px;opacity:1}80%{top:180px;left:340px;opacity:1}to{top:200px;left:300px;opacity:0}}@keyframes cloudRight-data-v-1d6b2d2a{0%{top:100px;left:500px;opacity:0}20%{top:120px;left:460px;opacity:1}80%{top:180px;left:340px;opacity:1}to{top:200px;left:300px;opacity:0}}.wscn-http404 .bullshit[data-v-1d6b2d2a]{position:relative;float:left;width:300px;padding:30px 0;overflow:hidden}.wscn-http404 .bullshit__oops[data-v-1d6b2d2a]{font-size:32px;line-height:40px;color:#1482f0;margin-bottom:20px;-webkit-animation-fill-mode:forwards;animation-fill-mode:forwards}.wscn-http404 .bullshit__headline[data-v-1d6b2d2a],.wscn-http404 .bullshit__oops[data-v-1d6b2d2a]{font-weight:700;opacity:0;-webkit-animation-name:slideUp-data-v-1d6b2d2a;animation-name:slideUp-data-v-1d6b2d2a;-webkit-animation-duration:.5s;animation-duration:.5s}.wscn-http404 .bullshit__headline[data-v-1d6b2d2a]{font-size:20px;line-height:24px;color:#222;margin-bottom:10px;-webkit-animation-delay:.1s;animation-delay:.1s;-webkit-animation-fill-mode:forwards;animation-fill-mode:forwards}.wscn-http404 .bullshit__info[data-v-1d6b2d2a]{font-size:13px;line-height:21px;color:grey;margin-bottom:30px;-webkit-animation-delay:.2s;animation-delay:.2s;-webkit-animation-fill-mode:forwards;animation-fill-mode:forwards}.wscn-http404 .bullshit__info[data-v-1d6b2d2a],.wscn-http404 .bullshit__return-home[data-v-1d6b2d2a]{opacity:0;-webkit-animation-name:slideUp-data-v-1d6b2d2a;animation-name:slideUp-data-v-1d6b2d2a;-webkit-animation-duration:.5s;animation-duration:.5s}.wscn-http404 .bullshit__return-home[data-v-1d6b2d2a]{display:block;float:left;width:165px;height:36px;background:#1482f0;border-radius:100px;text-align:center;color:#fff;font-size:14px;line-height:36px;cursor:pointer;-webkit-animation-delay:.3s;animation-delay:.3s;-webkit-animation-fill-mode:forwards;animation-fill-mode:forwards}@-webkit-keyframes slideUp-data-v-1d6b2d2a{0%{-webkit-transform:translateY(60px);transform:translateY(60px);opacity:0}to{-webkit-transform:translateY(0);transform:translateY(0);opacity:1}}@keyframes slideUp-data-v-1d6b2d2a{0%{-webkit-transform:translateY(60px);transform:translateY(60px);opacity:0}to{-webkit-transform:translateY(0);transform:translateY(0);opacity:1}}
\ No newline at end of file
diff --git a/priv/static/adminfe/chunk-1a7d.38eb00cf.css b/priv/static/adminfe/chunk-1a7d.38eb00cf.css
new file mode 100644
index 000000000..cbf59cfb5
--- /dev/null
+++ b/priv/static/adminfe/chunk-1a7d.38eb00cf.css
@@ -0,0 +1 @@
+.prop-row{margin-bottom:1em}.emoji-preview-img{max-width:5em}.copy-to-local-button{margin-top:2em;float:right}.new-emoji-col{margin-top:8em}.or,.shared-pack-dl-box{margin:1em}.dl-as-input{margin:1em;max-width:30%}.contents-collapse{margin:1em}.pack-actions{margin-top:1em}.new-emoji-uploader{margin-bottom:3em}.emoji-packs-container{margin:22px 0 0 15px}.local-packs-actions{margin-top:1em;margin-bottom:1em}.remote-instance-input{max-width:10%}.create-pack-button{margin-top:1em}
\ No newline at end of file
diff --git a/priv/static/adminfe/chunk-18e1.6aaab273.css b/priv/static/adminfe/chunk-1f27.c0efd1fc.css
similarity index 100%
rename from priv/static/adminfe/chunk-18e1.6aaab273.css
rename to priv/static/adminfe/chunk-1f27.c0efd1fc.css
diff --git a/priv/static/adminfe/chunk-2325.0d22684d.css b/priv/static/adminfe/chunk-2325.0d22684d.css
deleted file mode 100644
index bdb738700..000000000
--- a/priv/static/adminfe/chunk-2325.0d22684d.css
+++ /dev/null
@@ -1 +0,0 @@
-.wscn-http404-container[data-v-b8c8aa9a]{-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%);position:absolute;top:40%;left:50%}.wscn-http404[data-v-b8c8aa9a]{position:relative;width:1200px;padding:0 50px;overflow:hidden}.wscn-http404 .pic-404[data-v-b8c8aa9a]{position:relative;float:left;width:600px;overflow:hidden}.wscn-http404 .pic-404__parent[data-v-b8c8aa9a]{width:100%}.wscn-http404 .pic-404__child[data-v-b8c8aa9a]{position:absolute}.wscn-http404 .pic-404__child.left[data-v-b8c8aa9a]{width:80px;top:17px;left:220px;opacity:0;-webkit-animation-name:cloudLeft-data-v-b8c8aa9a;animation-name:cloudLeft-data-v-b8c8aa9a;-webkit-animation-duration:2s;animation-duration:2s;-webkit-animation-timing-function:linear;animation-timing-function:linear;-webkit-animation-fill-mode:forwards;animation-fill-mode:forwards;-webkit-animation-delay:1s;animation-delay:1s}.wscn-http404 .pic-404__child.mid[data-v-b8c8aa9a]{width:46px;top:10px;left:420px;opacity:0;-webkit-animation-name:cloudMid-data-v-b8c8aa9a;animation-name:cloudMid-data-v-b8c8aa9a;-webkit-animation-duration:2s;animation-duration:2s;-webkit-animation-timing-function:linear;animation-timing-function:linear;-webkit-animation-fill-mode:forwards;animation-fill-mode:forwards;-webkit-animation-delay:1.2s;animation-delay:1.2s}.wscn-http404 .pic-404__child.right[data-v-b8c8aa9a]{width:62px;top:100px;left:500px;opacity:0;-webkit-animation-name:cloudRight-data-v-b8c8aa9a;animation-name:cloudRight-data-v-b8c8aa9a;-webkit-animation-duration:2s;animation-duration:2s;-webkit-animation-timing-function:linear;animation-timing-function:linear;-webkit-animation-fill-mode:forwards;animation-fill-mode:forwards;-webkit-animation-delay:1s;animation-delay:1s}@-webkit-keyframes cloudLeft-data-v-b8c8aa9a{0%{top:17px;left:220px;opacity:0}20%{top:33px;left:188px;opacity:1}80%{top:81px;left:92px;opacity:1}to{top:97px;left:60px;opacity:0}}@keyframes cloudLeft-data-v-b8c8aa9a{0%{top:17px;left:220px;opacity:0}20%{top:33px;left:188px;opacity:1}80%{top:81px;left:92px;opacity:1}to{top:97px;left:60px;opacity:0}}@-webkit-keyframes cloudMid-data-v-b8c8aa9a{0%{top:10px;left:420px;opacity:0}20%{top:40px;left:360px;opacity:1}70%{top:130px;left:180px;opacity:1}to{top:160px;left:120px;opacity:0}}@keyframes cloudMid-data-v-b8c8aa9a{0%{top:10px;left:420px;opacity:0}20%{top:40px;left:360px;opacity:1}70%{top:130px;left:180px;opacity:1}to{top:160px;left:120px;opacity:0}}@-webkit-keyframes cloudRight-data-v-b8c8aa9a{0%{top:100px;left:500px;opacity:0}20%{top:120px;left:460px;opacity:1}80%{top:180px;left:340px;opacity:1}to{top:200px;left:300px;opacity:0}}@keyframes cloudRight-data-v-b8c8aa9a{0%{top:100px;left:500px;opacity:0}20%{top:120px;left:460px;opacity:1}80%{top:180px;left:340px;opacity:1}to{top:200px;left:300px;opacity:0}}.wscn-http404 .bullshit[data-v-b8c8aa9a]{position:relative;float:left;width:300px;padding:30px 0;overflow:hidden}.wscn-http404 .bullshit__oops[data-v-b8c8aa9a]{font-size:32px;line-height:40px;color:#1482f0;margin-bottom:20px;-webkit-animation-fill-mode:forwards;animation-fill-mode:forwards}.wscn-http404 .bullshit__headline[data-v-b8c8aa9a],.wscn-http404 .bullshit__oops[data-v-b8c8aa9a]{font-weight:700;opacity:0;-webkit-animation-name:slideUp-data-v-b8c8aa9a;animation-name:slideUp-data-v-b8c8aa9a;-webkit-animation-duration:.5s;animation-duration:.5s}.wscn-http404 .bullshit__headline[data-v-b8c8aa9a]{font-size:20px;line-height:24px;color:#222;margin-bottom:10px;-webkit-animation-delay:.1s;animation-delay:.1s;-webkit-animation-fill-mode:forwards;animation-fill-mode:forwards}.wscn-http404 .bullshit__info[data-v-b8c8aa9a]{font-size:13px;line-height:21px;color:grey;margin-bottom:30px;-webkit-animation-delay:.2s;animation-delay:.2s;-webkit-animation-fill-mode:forwards;animation-fill-mode:forwards}.wscn-http404 .bullshit__info[data-v-b8c8aa9a],.wscn-http404 .bullshit__return-home[data-v-b8c8aa9a]{opacity:0;-webkit-animation-name:slideUp-data-v-b8c8aa9a;animation-name:slideUp-data-v-b8c8aa9a;-webkit-animation-duration:.5s;animation-duration:.5s}.wscn-http404 .bullshit__return-home[data-v-b8c8aa9a]{display:block;float:left;width:110px;height:36px;background:#1482f0;border-radius:100px;text-align:center;color:#fff;font-size:14px;line-height:36px;cursor:pointer;-webkit-animation-delay:.3s;animation-delay:.3s;-webkit-animation-fill-mode:forwards;animation-fill-mode:forwards}@-webkit-keyframes slideUp-data-v-b8c8aa9a{0%{-webkit-transform:translateY(60px);transform:translateY(60px);opacity:0}to{-webkit-transform:translateY(0);transform:translateY(0);opacity:1}}@keyframes slideUp-data-v-b8c8aa9a{0%{-webkit-transform:translateY(60px);transform:translateY(60px);opacity:0}to{-webkit-transform:translateY(0);transform:translateY(0);opacity:1}}
\ No newline at end of file
diff --git a/priv/static/adminfe/chunk-0e18.e12401fb.css b/priv/static/adminfe/chunk-3d1c.2880a519.css
similarity index 100%
rename from priv/static/adminfe/chunk-0e18.e12401fb.css
rename to priv/static/adminfe/chunk-3d1c.2880a519.css
diff --git a/priv/static/adminfe/chunk-5913.33f0e7ff.css b/priv/static/adminfe/chunk-5913.33f0e7ff.css
new file mode 100644
index 000000000..f98c967ee
--- /dev/null
+++ b/priv/static/adminfe/chunk-5913.33f0e7ff.css
@@ -0,0 +1 @@
+.select-field[data-v-71bc6b38]{width:350px}@media (min-device-width:768px) and (max-device-width:1024px),only screen and (max-width:760px){.select-field[data-v-71bc6b38]{width:100%;margin-bottom:5px}}.actions-button[data-v-19afabea]{text-align:left;width:350px;padding:10px}.actions-button-container[data-v-19afabea]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.el-dropdown[data-v-19afabea]{float:right}.el-icon-edit[data-v-19afabea]{margin-right:5px}.tag-container[data-v-19afabea]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.tag-text[data-v-19afabea]{padding-right:20px}.no-hover[data-v-19afabea]:hover{color:#606266;background-color:#fff;cursor:auto}.el-dialog__body{padding:20px}.create-account-form-item{margin-bottom:20px}.create-account-form-item-without-margin{margin-bottom:0}@media (min-device-width:768px) and (max-device-width:1024px),only screen and (max-width:760px){.create-user-dialog{width:85%}.create-account-form-item{margin-bottom:20px}.el-dialog__body{padding:20px}}.actions-button{text-align:left;width:350px;padding:10px}.actions-container{display:-webkit-box;display:-ms-flexbox;display:flex;height:36px;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin:0 15px 10px}.active-tag{color:#409eff;font-weight:700}.active-tag .el-icon-check{color:#409eff;float:right;margin:7px 0 0 15px}.el-dropdown-link:hover{cursor:pointer;color:#409eff}.el-icon-plus{margin-right:5px}.password-reset-token{margin:0 0 14px}.password-reset-token-dialog{width:50%}.reset-password-link{text-decoration:underline}.users-container h1{margin:22px 0 0 15px}.users-container .pagination{margin:25px 0;text-align:center}.users-container .search{width:350px;float:right}.users-container .filter-container{display:-webkit-box;display:-ms-flexbox;display:flex;height:36px;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin:22px 15px 15px}.users-container .user-count{color:grey;font-size:28px}@media (min-device-width:768px) and (max-device-width:1024px),only screen and (max-width:760px){.password-reset-token-dialog{width:85%}.users-container h1{margin:7px 10px 15px}.users-container .actions-container{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;margin:0 10px 7px}.users-container .create-account{width:100%}.users-container .el-icon-arrow-down{font-size:12px}.users-container .search{width:100%}.users-container .filter-container{display:-webkit-box;display:-ms-flexbox;display:flex;height:82px;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;margin:0 10px}.users-container .el-tag{width:30px;display:inline-block;margin-bottom:4px;font-weight:700}.users-container .el-tag.el-tag--danger,.users-container .el-tag.el-tag--success{padding-left:8px}}
\ No newline at end of file
diff --git a/priv/static/adminfe/chunk-1fbf.d7a1893c.css b/priv/static/adminfe/chunk-598f.dc5869e7.css
similarity index 100%
rename from priv/static/adminfe/chunk-1fbf.d7a1893c.css
rename to priv/static/adminfe/chunk-598f.dc5869e7.css
diff --git a/priv/static/adminfe/chunk-5e57.ac97b15a.css b/priv/static/adminfe/chunk-6292.d1c82a11.css
similarity index 100%
rename from priv/static/adminfe/chunk-5e57.ac97b15a.css
rename to priv/static/adminfe/chunk-6292.d1c82a11.css
diff --git a/priv/static/adminfe/chunk-7c6b.4a8663a9.css b/priv/static/adminfe/chunk-7c6b.4a8663a9.css
new file mode 100644
index 000000000..48784b9d2
--- /dev/null
+++ b/priv/static/adminfe/chunk-7c6b.4a8663a9.css
@@ -0,0 +1 @@
+.invites-container .actions-container{display:-webkit-box;display:-ms-flexbox;display:flex;height:36px;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin:20px 15px 15px}.invites-container .create-invite-token{text-align:left;width:350px;padding:10px}.invites-container .create-new-token-dialog{width:40%}.invites-container .el-dialog__body{padding:5px 20px 0}.invites-container h1{margin:22px 0 0 15px}.invites-container .icon{margin-right:5px}.invites-container .invite-token-table{width:100%;margin:0 15px}.invites-container .invite-via-email{text-align:left;width:350px;padding:10px}.invites-container .invite-via-email-dialog{width:50%}.invites-container .info{color:#666;font-size:13px;line-height:22px;margin:0 0 10px}@media (min-device-width:768px) and (max-device-width:1024px),only screen and (max-width:760px){.invites-container .actions-container{display:-webkit-box;display:-ms-flexbox;display:flex;height:82px;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin:15px 10px 7px}.invites-container .create-invite-token{width:100%}.invites-container .create-new-token-dialog{width:85%}.invites-container .el-date-editor{width:150px}.invites-container .el-dialog__body{padding:5px 15px 0}.invites-container h1{margin:7px 10px 15px}.invites-container .invite-token-table{width:100%;margin:0}.invites-container .invite-via-email{width:100%;margin:10px 0 0}.invites-container .invite-via-email-dialog{width:85%}.invites-container .info{margin:0 0 10px 5px}.create-invite-token,.invite-via-email{width:100%}}
\ No newline at end of file
diff --git a/priv/static/adminfe/chunk-8b70.9ba0945c.css b/priv/static/adminfe/chunk-8b70.9ba0945c.css
deleted file mode 100644
index 7fa43bf28..000000000
--- a/priv/static/adminfe/chunk-8b70.9ba0945c.css
+++ /dev/null
@@ -1 +0,0 @@
-@supports (-webkit-mask:none) and (not (cater-color:#fff)){.login-container .el-input input{color:#fff}.login-container .el-input input:first-line{color:#eee}}.login-container .el-input{display:inline-block;height:47px;width:85%}.login-container .el-input input{background:transparent;border:0;-webkit-appearance:none;border-radius:0;padding:12px 5px 12px 15px;color:#eee;height:47px;caret-color:#fff}.login-container .el-input input:-webkit-autofill{-webkit-box-shadow:0 0 0 1000px #283443 inset!important;-webkit-text-fill-color:#fff!important}.login-container .el-form-item{border:1px solid hsla(0,0%,100%,.1);background:rgba(0,0,0,.1);border-radius:5px;color:#454545}.login-container[data-v-57350b8e]{min-height:100%;width:100%;background-color:#2d3a4b;overflow:hidden}.login-container .login-form[data-v-57350b8e]{position:relative;width:520px;max-width:100%;padding:160px 35px 0;margin:0 auto;overflow:hidden}.login-container .tips[data-v-57350b8e]{font-size:14px;color:#fff;margin-bottom:10px}.login-container .tips span[data-v-57350b8e]:first-of-type{margin-right:16px}.login-container .svg-container[data-v-57350b8e]{padding:6px 5px 6px 15px;color:#889aa4;vertical-align:middle;width:30px;display:inline-block}.login-container .title-container[data-v-57350b8e]{position:relative}.login-container .title-container .title[data-v-57350b8e]{font-size:26px;color:#eee;margin:0 auto 40px;text-align:center;font-weight:700}.login-container .title-container .set-language[data-v-57350b8e]{color:#fff;position:absolute;top:3px;font-size:18px;right:0;cursor:pointer}.login-container .show-pwd[data-v-57350b8e]{position:absolute;right:10px;top:7px;font-size:16px;color:#889aa4;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.login-container .thirdparty-button[data-v-57350b8e]{position:absolute;right:0;bottom:6px}
\ No newline at end of file
diff --git a/priv/static/adminfe/chunk-e547.e4b6230b.css b/priv/static/adminfe/chunk-e547.e4b6230b.css
deleted file mode 100644
index f740543a0..000000000
--- a/priv/static/adminfe/chunk-e547.e4b6230b.css
+++ /dev/null
@@ -1 +0,0 @@
-.select-field[data-v-71bc6b38]{width:350px}@media (min-device-width:768px) and (max-device-width:1024px),only screen and (max-width:760px){.select-field[data-v-71bc6b38]{width:100%;margin-bottom:5px}}.actions-button[data-v-94227b1e]{text-align:left;width:350px;padding:10px}.actions-button-container[data-v-94227b1e]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.el-dropdown[data-v-94227b1e]{float:right}.el-icon-edit[data-v-94227b1e]{margin-right:5px}.tag-container[data-v-94227b1e]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.tag-text[data-v-94227b1e]{padding-right:20px}.no-hover[data-v-94227b1e]:hover{color:#606266;background-color:#fff;cursor:auto}@media (min-device-width:768px) and (max-device-width:1024px),only screen and (max-width:760px){.create-user-dialog{width:80%}.create-account-form-item{margin-bottom:30px}.el-dialog__body{padding:20px 20px 0}}.actions-button[data-v-c51cd8ee]{text-align:left;width:350px;padding:10px}.actions-container[data-v-c51cd8ee]{display:-webkit-box;display:-ms-flexbox;display:flex;height:36px;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin:0 15px 10px}.active-tag[data-v-c51cd8ee]{color:#409eff;font-weight:700}.active-tag .el-icon-check[data-v-c51cd8ee]{color:#409eff;float:right;margin:7px 0 0 15px}.el-dropdown-link[data-v-c51cd8ee]:hover{cursor:pointer;color:#409eff}.el-icon-plus[data-v-c51cd8ee]{margin-right:5px}.users-container h1[data-v-c51cd8ee]{margin:22px 0 0 15px}.users-container .pagination[data-v-c51cd8ee]{margin:25px 0;text-align:center}.users-container .search[data-v-c51cd8ee]{width:350px;float:right}.users-container .filter-container[data-v-c51cd8ee]{display:-webkit-box;display:-ms-flexbox;display:flex;height:36px;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin:22px 15px 15px}.users-container .user-count[data-v-c51cd8ee]{color:grey;font-size:28px}@media (min-device-width:768px) and (max-device-width:1024px),only screen and (max-width:760px){.users-container h1[data-v-c51cd8ee]{margin:7px 10px 15px}.users-container .actions-container[data-v-c51cd8ee]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;margin:0 10px 7px}.users-container .create-account[data-v-c51cd8ee]{width:100%}.users-container .el-icon-arrow-down[data-v-c51cd8ee]{font-size:12px}.users-container .search[data-v-c51cd8ee]{width:100%}.users-container .filter-container[data-v-c51cd8ee]{display:-webkit-box;display:-ms-flexbox;display:flex;height:82px;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;margin:0 10px}.users-container .el-tag[data-v-c51cd8ee]{width:30px;display:inline-block;margin-bottom:4px;font-weight:700}.users-container .el-tag.el-tag--danger[data-v-c51cd8ee],.users-container .el-tag.el-tag--success[data-v-c51cd8ee]{padding-left:8px}}
\ No newline at end of file
diff --git a/priv/static/adminfe/chunk-elementUI.e5cd8da6.css b/priv/static/adminfe/chunk-elementUI.f35d8ab1.css
similarity index 100%
rename from priv/static/adminfe/chunk-elementUI.e5cd8da6.css
rename to priv/static/adminfe/chunk-elementUI.f35d8ab1.css
diff --git a/priv/static/adminfe/chunk-libs.4e8c4664.css b/priv/static/adminfe/chunk-libs.00388c73.css
similarity index 100%
rename from priv/static/adminfe/chunk-libs.4e8c4664.css
rename to priv/static/adminfe/chunk-libs.00388c73.css
diff --git a/priv/static/adminfe/index.html b/priv/static/adminfe/index.html
index c31247c03..ce53d8318 100644
--- a/priv/static/adminfe/index.html
+++ b/priv/static/adminfe/index.html
@@ -1 +1 @@
-Admin FE
\ No newline at end of file
+Admin FE
\ No newline at end of file
diff --git a/priv/static/adminfe/static/js/7zzA.e1ae1c94.js b/priv/static/adminfe/static/js/7zzA.e1ae1c94.js
index 4387b832165fe39a626396494ee4962069f51ebf..526e228f59b19d4984b7336dd89c10f5b5caa51c 100644
GIT binary patch
delta 50
zcmeyyw19a-8KV}LzP_?Taeir0a;k4)K|y9-dT5Z3t$9_Iqh6|EVya=XrHNivv0iRs
F0RX8a5R(7^
delta 7
OcmZ3${EcZt86yA;Oaj9I
diff --git a/priv/static/adminfe/static/js/7zzA.e1ae1c94.js.map b/priv/static/adminfe/static/js/7zzA.e1ae1c94.js.map
new file mode 100644
index 0000000000000000000000000000000000000000..840e8a26be5fde3f8c660ba8cc8b85a38d9275c9
GIT binary patch
literal 1913
zcmdT_ZBN@U5dJGBLcGnyW1BS5!H`J{E2y9`O=w!C$W7fgU212JQz)qaeRp<9tL{bY
zbC<_pCQX@X6|`Q{pwyK~Wf`=-26wVwi1d4lF;<$C%`$mcG9w3>k*Q^w
z8p!)_R>^>_dWL`XJ$ZE~`~MOUe4~L9QxC`?NS+Wt>BP3K>EHM
zY9k{fg++LY9cSg{rqPvq#W&D4?}ad8ikcslG;{bbgdH~y1|%bkT%?%FNH{jC&>_{+
zLMy2($k+V|bY)d}rOj02*@JU0t*}{o0e
zr=ln_HG(?{=I1=(v>UZw_BcIncMf_H6}-*qtE@4d)$*jrfJja7jP944<48bJ8w6xN~en)ei5vloUN8>fm7sPQRaa+71mL
zokmUBCKNCZ`k=l!`|OOD5POSx;e1%%KYG0DkaH;GkzjKAg+~$(3LK#A*?%Bi5WB+LHmBjU`h(
zAVZ_4Bo6jB!jSo&(gT@*smf)^`l2uM{ag!}1(s+1dKpa0lT@T**>)R3oB1EYVRC;P
ze6lp`*W
z+wgR{=XI)KdefEX#>FaDY>%j?wFZMwuRpTK8cdDZJmGpC2~4GIHkGVNx?G_}sUXK}P##tc-^f*IMUvLy?!#Nvg{8Uj@t-D;T#}HTA-pkD*`IP(BWtFCd~@
Wlmc!5
diff --git a/priv/static/adminfe/static/js/JEtC.f9ba4594.js.map b/priv/static/adminfe/static/js/JEtC.f9ba4594.js.map
new file mode 100644
index 0000000000000000000000000000000000000000..633bbc5d634ce6a65e3922e8b7e6a15740db351a
GIT binary patch
literal 1903
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