From e7aef27c0011d3fd0b569ebdb9196a1e011eae5d Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Mon, 30 Sep 2019 19:10:54 +0700 Subject: [PATCH 01/31] Fix merge --- lib/pleroma/list.ex | 21 +- .../web/admin_api/views/report_view.ex | 2 +- lib/pleroma/web/chat_channel.ex | 2 +- .../controllers/account_controller.ex | 227 +++++ .../controllers/follow_request_controller.ex | 2 +- .../controllers/list_controller.ex | 2 +- .../controllers/mastodon_api_controller.ex | 249 +----- .../controllers/search_controller.ex | 4 +- .../controllers/status_controller.ex | 4 +- .../web/mastodon_api/views/account_view.ex | 10 +- .../mastodon_api/views/conversation_view.ex | 2 +- .../mastodon_api/views/notification_view.ex | 2 +- .../web/mastodon_api/views/status_view.ex | 6 +- lib/pleroma/web/router.ex | 31 +- test/list_test.exs | 4 +- test/web/admin_api/views/report_view_test.exs | 8 +- .../controllers/account_controller_test.exs | 810 ++++++++++++++++++ .../mastodon_api_controller_test.exs | 773 ----------------- .../mastodon_api/views/account_view_test.exs | 48 +- .../views/notification_view_test.exs | 8 +- .../mastodon_api/views/status_view_test.exs | 2 +- test/web/twitter_api/twitter_api_test.exs | 28 +- 22 files changed, 1131 insertions(+), 1114 deletions(-) create mode 100644 lib/pleroma/web/mastodon_api/controllers/account_controller.ex create mode 100644 test/web/mastodon_api/controllers/account_controller_test.exs diff --git a/lib/pleroma/list.ex b/lib/pleroma/list.ex index c5db1cb62..08a94c62c 100644 --- a/lib/pleroma/list.ex +++ b/lib/pleroma/list.ex @@ -84,22 +84,11 @@ def get_lists_from_activity(%Activity{actor: ap_id}) do end # Get lists to which the account belongs. - def get_lists_account_belongs(%User{} = owner, account_id) do - user = User.get_cached_by_id(account_id) - - query = - from( - l in Pleroma.List, - where: - l.user_id == ^owner.id and - fragment( - "? = ANY(?)", - ^user.follower_address, - l.following - ) - ) - - Repo.all(query) + def get_lists_account_belongs(%User{} = owner, user) do + Pleroma.List + |> where([l], l.user_id == ^owner.id) + |> where([l], fragment("? = ANY(?)", ^user.follower_address, l.following)) + |> Repo.all() end def rename(%Pleroma.List{} = list, title) do diff --git a/lib/pleroma/web/admin_api/views/report_view.ex b/lib/pleroma/web/admin_api/views/report_view.ex index 8c06364a3..101a74c63 100644 --- a/lib/pleroma/web/admin_api/views/report_view.ex +++ b/lib/pleroma/web/admin_api/views/report_view.ex @@ -43,7 +43,7 @@ def render("show.json", %{report: report, user: user, account: account, statuses end defp merge_account_views(%User{} = user) do - Pleroma.Web.MastodonAPI.AccountView.render("account.json", %{user: user}) + Pleroma.Web.MastodonAPI.AccountView.render("show.json", %{user: user}) |> Map.merge(Pleroma.Web.AdminAPI.AccountView.render("show.json", %{user: user})) end diff --git a/lib/pleroma/web/chat_channel.ex b/lib/pleroma/web/chat_channel.ex index b543909f1..08841a3e8 100644 --- a/lib/pleroma/web/chat_channel.ex +++ b/lib/pleroma/web/chat_channel.ex @@ -22,7 +22,7 @@ def handle_in("new_msg", %{"text" => text}, %{assigns: %{user_name: user_name}} if String.length(text) > 0 do author = User.get_cached_by_nickname(user_name) - author = Pleroma.Web.MastodonAPI.AccountView.render("account.json", user: author) + author = Pleroma.Web.MastodonAPI.AccountView.render("show.json", user: author) message = ChatChannelState.add_message(%{text: text, author: author}) broadcast!(socket, "new_msg", message) diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex new file mode 100644 index 000000000..844de2e79 --- /dev/null +++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex @@ -0,0 +1,227 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.AccountController do + use Pleroma.Web, :controller + + import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2, truthy_param?: 1] + + alias Pleroma.User + alias Pleroma.Web.CommonAPI + alias Pleroma.Web.ActivityPub.ActivityPub + alias Pleroma.Web.MastodonAPI.StatusView + alias Pleroma.Web.MastodonAPI.MastodonAPI + alias Pleroma.Web.MastodonAPI.ListView + alias Pleroma.Plugs.RateLimiter + + require Pleroma.Constants + + @relations ~w(follow unfollow)a + + plug(RateLimiter, {:relations_id_action, params: ["id", "uri"]} when action in @relations) + plug(RateLimiter, :relations_actions when action in @relations) + plug(:assign_account when action not in [:show, :statuses, :follows]) + + action_fallback(Pleroma.Web.MastodonAPI.FallbackController) + + @doc "GET /api/v1/accounts/:id" + def show(%{assigns: %{user: for_user}} = conn, %{"id" => nickname_or_id}) do + with %User{} = user <- User.get_cached_by_nickname_or_id(nickname_or_id, for: for_user), + true <- User.auth_active?(user) || user.id == for_user.id || User.superuser?(for_user) do + render(conn, "show.json", user: user, for: for_user) + else + _e -> render_error(conn, :not_found, "Can't find user") + end + end + + @doc "GET /api/v1/accounts/:id/statuses" + def statuses(%{assigns: %{user: reading_user}} = conn, params) do + with %User{} = user <- User.get_cached_by_nickname_or_id(params["id"], for: reading_user) do + params = Map.put(params, "tag", params["tagged"]) + activities = ActivityPub.fetch_user_activities(user, reading_user, params) + + conn + |> add_link_headers(activities) + |> put_view(StatusView) + |> render("index.json", activities: activities, for: reading_user, as: :activity) + end + end + + @doc "GET /api/v1/accounts/:id/followers" + def followers(%{assigns: %{user: for_user, account: user}} = conn, params) do + followers = + cond do + for_user && user.id == for_user.id -> MastodonAPI.get_followers(user, params) + user.info.hide_followers -> [] + true -> MastodonAPI.get_followers(user, params) + end + + conn + |> add_link_headers(followers) + |> render("index.json", for: for_user, users: followers, as: :user) + end + + @doc "GET /api/v1/accounts/:id/following" + def following(%{assigns: %{user: for_user, account: user}} = conn, params) do + followers = + cond do + for_user && user.id == for_user.id -> MastodonAPI.get_friends(user, params) + user.info.hide_follows -> [] + true -> MastodonAPI.get_friends(user, params) + end + + conn + |> add_link_headers(followers) + |> render("index.json", for: for_user, users: followers, as: :user) + end + + @doc "GET /api/v1/accounts/:id/lists" + def lists(%{assigns: %{user: user, account: account}} = conn, _params) do + lists = Pleroma.List.get_lists_account_belongs(user, account) + + conn + |> put_view(ListView) + |> render("index.json", lists: lists) + end + + @doc "GET /api/v1/pleroma/accounts/:id/favourites" + def favourites(%{assigns: %{account: %{info: %{hide_favorites: true}}}} = conn, _params) do + render_error(conn, :forbidden, "Can't get favorites") + end + + def favourites(%{assigns: %{user: for_user, account: user}} = conn, params) do + params = + params + |> Map.put("type", "Create") + |> Map.put("favorited_by", user.ap_id) + |> Map.put("blocking_user", for_user) + + recipients = + if for_user do + [Pleroma.Constants.as_public()] ++ [for_user.ap_id | for_user.following] + else + [Pleroma.Constants.as_public()] + end + + activities = + recipients + |> ActivityPub.fetch_activities(params) + |> Enum.reverse() + + conn + |> add_link_headers(activities) + |> put_view(StatusView) + |> render("index.json", activities: activities, for: for_user, as: :activity) + end + + @doc "POST /api/v1/pleroma/accounts/:id/subscribe" + def subscribe(%{assigns: %{user: user, account: subscription_target}} = conn, _params) do + with {:ok, subscription_target} <- User.subscribe(user, subscription_target) do + render(conn, "relationship.json", user: user, target: subscription_target) + else + {:error, message} -> + conn + |> put_status(:forbidden) + |> json(%{error: message}) + end + end + + @doc "POST /api/v1/pleroma/accounts/:id/unsubscribe" + def unsubscribe(%{assigns: %{user: user, account: subscription_target}} = conn, _params) do + with {:ok, subscription_target} <- User.unsubscribe(user, subscription_target) do + render(conn, "relationship.json", user: user, target: subscription_target) + else + {:error, message} -> + conn + |> put_status(:forbidden) + |> json(%{error: message}) + end + end + + @doc "POST /api/v1/accounts/:id/follow" + def follow(%{assigns: %{user: %{id: id}, account: %{id: id}}}, _params) do + {:error, :not_found} + end + + def follow(%{assigns: %{user: follower, account: followed}} = conn, _params) do + with {:ok, follower} <- MastodonAPI.follow(follower, followed, conn.params) do + render(conn, "relationship.json", user: follower, target: followed) + else + {:error, message} -> + conn + |> put_status(:forbidden) + |> json(%{error: message}) + end + end + + @doc "POST /api/v1/pleroma/:id/unfollow" + def unfollow(%{assigns: %{user: %{id: id}, account: %{id: id}}}, _params) do + {:error, :not_found} + end + + def unfollow(%{assigns: %{user: follower, account: followed}} = conn, _params) do + with {:ok, follower} <- CommonAPI.unfollow(follower, followed) do + render(conn, "relationship.json", user: follower, target: followed) + end + end + + @doc "POST /api/v1/accounts/:id/mute" + def mute(%{assigns: %{user: muter, account: muted}} = conn, params) do + notifications? = params |> Map.get("notifications", true) |> truthy_param?() + + with {:ok, muter} <- User.mute(muter, muted, notifications?) do + render(conn, "relationship.json", user: muter, target: muted) + else + {:error, message} -> + conn + |> put_status(:forbidden) + |> json(%{error: message}) + end + end + + @doc "POST /api/v1/accounts/:id/unmute" + def unmute(%{assigns: %{user: muter, account: muted}} = conn, _params) do + with {:ok, muter} <- User.unmute(muter, muted) do + render(conn, "relationship.json", user: muter, target: muted) + else + {:error, message} -> + conn + |> put_status(:forbidden) + |> json(%{error: message}) + end + end + + @doc "POST /api/v1/accounts/:id/block" + def block(%{assigns: %{user: blocker, account: blocked}} = conn, _params) do + with {:ok, blocker} <- User.block(blocker, blocked), + {:ok, _activity} <- ActivityPub.block(blocker, blocked) do + render(conn, "relationship.json", user: blocker, target: blocked) + else + {:error, message} -> + conn + |> put_status(:forbidden) + |> json(%{error: message}) + end + end + + @doc "POST /api/v1/accounts/:id/unblock" + def unblock(%{assigns: %{user: blocker, account: blocked}} = conn, _params) do + with {:ok, blocker} <- User.unblock(blocker, blocked), + {:ok, _activity} <- ActivityPub.unblock(blocker, blocked) do + render(conn, "relationship.json", user: blocker, target: blocked) + else + {:error, message} -> + conn + |> put_status(:forbidden) + |> json(%{error: message}) + end + end + + defp assign_account(%{params: %{"id" => id}} = conn, _) do + case User.get_cached_by_id(id) do + %User{} = account -> assign(conn, :account, account) + nil -> Pleroma.Web.MastodonAPI.FallbackController.call(conn, {:error, :not_found}) |> halt() + end + end +end diff --git a/lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex b/lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex index 267014b97..ce7b625ee 100644 --- a/lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex @@ -17,7 +17,7 @@ defmodule Pleroma.Web.MastodonAPI.FollowRequestController do def index(%{assigns: %{user: followed}} = conn, _params) do follow_requests = User.get_follow_requests(followed) - render(conn, "accounts.json", for: followed, users: follow_requests, as: :user) + render(conn, "index.json", for: followed, users: follow_requests, as: :user) end @doc "POST /api/v1/follow_requests/:id/authorize" diff --git a/lib/pleroma/web/mastodon_api/controllers/list_controller.ex b/lib/pleroma/web/mastodon_api/controllers/list_controller.ex index 2873deda8..50f42bee5 100644 --- a/lib/pleroma/web/mastodon_api/controllers/list_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/list_controller.ex @@ -49,7 +49,7 @@ def list_accounts(%{assigns: %{user: user, list: list}} = conn, _) do with {:ok, users} <- Pleroma.List.get_following(list) do conn |> put_view(AccountView) - |> render("accounts.json", for: user, users: users, as: :user) + |> render("index.json", for: user, users: users, as: :user) end end 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 3bdcea0f7..394599146 100644 --- a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex @@ -26,8 +26,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do alias Pleroma.Web.CommonAPI alias Pleroma.Web.MastodonAPI.AccountView alias Pleroma.Web.MastodonAPI.AppView - alias Pleroma.Web.MastodonAPI.ListView - alias Pleroma.Web.MastodonAPI.MastodonAPI alias Pleroma.Web.MastodonAPI.MastodonView alias Pleroma.Web.MastodonAPI.StatusView alias Pleroma.Web.MediaProxy @@ -38,16 +36,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do alias Pleroma.Web.TwitterAPI.TwitterAPI require Logger - require Pleroma.Constants - @rate_limited_relations_actions ~w(follow unfollow)a - - plug( - RateLimiter, - {:relations_id_action, params: ["id", "uri"]} when action in @rate_limited_relations_actions - ) - - plug(RateLimiter, :relations_actions when action in @rate_limited_relations_actions) plug(RateLimiter, :app_account_creation when action == :account_register) plug(RateLimiter, :search when action in [:search, :search2, :account_search]) plug(RateLimiter, :password_reset when action == :password_reset) @@ -171,7 +160,7 @@ def update_credentials(%{assigns: %{user: user}} = conn, params) do json( conn, - AccountView.render("account.json", %{user: user, for: user, with_pleroma_settings: true}) + AccountView.render("show.json", %{user: user, for: user, with_pleroma_settings: true}) ) else _e -> render_error(conn, :forbidden, "Invalid request") @@ -238,7 +227,7 @@ def verify_credentials(%{assigns: %{user: user}} = conn, _) do chat_token = Phoenix.Token.sign(conn, "user socket", user.id) account = - AccountView.render("account.json", %{ + AccountView.render("show.json", %{ user: user, for: user, with_pleroma_settings: true, @@ -256,16 +245,6 @@ def verify_app_credentials(%{assigns: %{user: _user, token: token}} = conn, _) d end end - def user(%{assigns: %{user: for_user}} = conn, %{"id" => nickname_or_id}) do - with %User{} = user <- User.get_cached_by_nickname_or_id(nickname_or_id, for: for_user), - true <- User.auth_active?(user) || user.id == for_user.id || User.superuser?(for_user) do - account = AccountView.render("account.json", %{user: user, for: for_user}) - json(conn, account) - else - _e -> render_error(conn, :not_found, "Can't find user") - end - end - @mastodon_api_level "2.7.2" def masto_instance(conn, _params) do @@ -318,25 +297,6 @@ def custom_emojis(conn, _params) do json(conn, mastodon_emoji) end - def user_statuses(%{assigns: %{user: reading_user}} = conn, params) do - with %User{} = user <- User.get_cached_by_nickname_or_id(params["id"], for: reading_user) do - params = - params - |> Map.put("tag", params["tagged"]) - - activities = ActivityPub.fetch_user_activities(user, reading_user, params) - - conn - |> add_link_headers(activities) - |> put_view(StatusView) - |> render("index.json", %{ - activities: activities, - for: reading_user, - as: :activity - }) - end - end - def get_poll(%{assigns: %{user: user}} = conn, %{"id" => id}) do with %Object{} = object <- Object.get_by_id_and_maybe_refetch(id, interval: 60), %Activity{} = activity <- Activity.get_create_by_object_ap_id(object.data["id"]), @@ -453,65 +413,13 @@ def get_mascot(%{assigns: %{user: user}} = conn, _params) do json(conn, mascot) end - def followers(%{assigns: %{user: for_user}} = conn, %{"id" => id} = params) do - with %User{} = user <- User.get_cached_by_id(id), - followers <- MastodonAPI.get_followers(user, params) do - followers = - cond do - for_user && user.id == for_user.id -> followers - user.info.hide_followers -> [] - true -> followers - end - - conn - |> add_link_headers(followers) - |> put_view(AccountView) - |> render("accounts.json", %{for: for_user, users: followers, as: :user}) - end - end - - def following(%{assigns: %{user: for_user}} = conn, %{"id" => id} = params) do - with %User{} = user <- User.get_cached_by_id(id), - followers <- MastodonAPI.get_friends(user, params) do - followers = - cond do - for_user && user.id == for_user.id -> followers - user.info.hide_follows -> [] - true -> followers - end - - conn - |> add_link_headers(followers) - |> put_view(AccountView) - |> render("accounts.json", %{for: for_user, users: followers, as: :user}) - end - end - - def follow(%{assigns: %{user: follower}} = conn, %{"id" => id}) do - with {_, %User{} = followed} <- {:followed, User.get_cached_by_id(id)}, - {_, true} <- {:followed, follower.id != followed.id}, - {:ok, follower} <- MastodonAPI.follow(follower, followed, conn.params) do - conn - |> put_view(AccountView) - |> render("relationship.json", %{user: follower, target: followed}) - else - {:followed, _} -> - {:error, :not_found} - - {:error, message} -> - conn - |> put_status(:forbidden) - |> json(%{error: message}) - end - end - - def follow(%{assigns: %{user: follower}} = conn, %{"uri" => uri}) do + def follows(%{assigns: %{user: follower}} = conn, %{"uri" => uri}) do with {_, %User{} = followed} <- {:followed, User.get_cached_by_nickname(uri)}, {_, true} <- {:followed, follower.id != followed.id}, {:ok, follower, followed, _} <- CommonAPI.follow(follower, followed) do conn |> put_view(AccountView) - |> render("account.json", %{user: followed, for: follower}) + |> render("show.json", %{user: followed, for: follower}) else {:followed, _} -> {:error, :not_found} @@ -523,123 +431,20 @@ def follow(%{assigns: %{user: follower}} = conn, %{"uri" => uri}) do end end - def unfollow(%{assigns: %{user: follower}} = conn, %{"id" => id}) do - with {_, %User{} = followed} <- {:followed, User.get_cached_by_id(id)}, - {_, true} <- {:followed, follower.id != followed.id}, - {:ok, follower} <- CommonAPI.unfollow(follower, followed) do - conn - |> put_view(AccountView) - |> render("relationship.json", %{user: follower, target: followed}) - else - {:followed, _} -> - {:error, :not_found} - - error -> - error - end - end - - def mute(%{assigns: %{user: muter}} = conn, %{"id" => id} = params) do - notifications = - if Map.has_key?(params, "notifications"), - do: params["notifications"] in [true, "True", "true", "1"], - else: true - - with %User{} = muted <- User.get_cached_by_id(id), - {:ok, muter} <- User.mute(muter, muted, notifications) do - conn - |> put_view(AccountView) - |> render("relationship.json", %{user: muter, target: muted}) - else - {:error, message} -> - conn - |> put_status(:forbidden) - |> json(%{error: message}) - end - end - - def unmute(%{assigns: %{user: muter}} = conn, %{"id" => id}) do - with %User{} = muted <- User.get_cached_by_id(id), - {:ok, muter} <- User.unmute(muter, muted) do - conn - |> put_view(AccountView) - |> render("relationship.json", %{user: muter, target: muted}) - else - {:error, message} -> - conn - |> put_status(:forbidden) - |> json(%{error: message}) - end - end - def mutes(%{assigns: %{user: user}} = conn, _) do with muted_accounts <- User.muted_users(user) do - res = AccountView.render("accounts.json", users: muted_accounts, for: user, as: :user) + res = AccountView.render("index.json", users: muted_accounts, for: user, as: :user) json(conn, res) end end - def block(%{assigns: %{user: blocker}} = conn, %{"id" => id}) do - with %User{} = blocked <- User.get_cached_by_id(id), - {:ok, blocker} <- User.block(blocker, blocked), - {:ok, _activity} <- ActivityPub.block(blocker, blocked) do - conn - |> put_view(AccountView) - |> render("relationship.json", %{user: blocker, target: blocked}) - else - {:error, message} -> - conn - |> put_status(:forbidden) - |> json(%{error: message}) - end - end - - def unblock(%{assigns: %{user: blocker}} = conn, %{"id" => id}) do - with %User{} = blocked <- User.get_cached_by_id(id), - {:ok, blocker} <- User.unblock(blocker, blocked), - {:ok, _activity} <- ActivityPub.unblock(blocker, blocked) do - conn - |> put_view(AccountView) - |> render("relationship.json", %{user: blocker, target: blocked}) - else - {:error, message} -> - conn - |> put_status(:forbidden) - |> json(%{error: message}) - end - end - def blocks(%{assigns: %{user: user}} = conn, _) do with blocked_accounts <- User.blocked_users(user) do - res = AccountView.render("accounts.json", users: blocked_accounts, for: user, as: :user) + res = AccountView.render("index.json", users: blocked_accounts, for: user, as: :user) json(conn, res) end end - def subscribe(%{assigns: %{user: user}} = conn, %{"id" => id}) do - with %User{} = subscription_target <- User.get_cached_by_id(id), - {:ok, subscription_target} = User.subscribe(user, subscription_target) do - conn - |> put_view(AccountView) - |> render("relationship.json", %{user: user, target: subscription_target}) - else - nil -> {:error, :not_found} - e -> e - end - end - - def unsubscribe(%{assigns: %{user: user}} = conn, %{"id" => id}) do - with %User{} = subscription_target <- User.get_cached_by_id(id), - {:ok, subscription_target} = User.unsubscribe(user, subscription_target) do - conn - |> put_view(AccountView) - |> render("relationship.json", %{user: user, target: subscription_target}) - else - nil -> {:error, :not_found} - e -> e - end - end - def favourites(%{assigns: %{user: user}} = conn, params) do params = params @@ -657,37 +462,6 @@ def favourites(%{assigns: %{user: user}} = conn, params) do |> render("index.json", %{activities: activities, for: user, as: :activity}) end - def user_favourites(%{assigns: %{user: for_user}} = conn, %{"id" => id} = params) do - with %User{} = user <- User.get_by_id(id), - false <- user.info.hide_favorites do - params = - params - |> Map.put("type", "Create") - |> Map.put("favorited_by", user.ap_id) - |> Map.put("blocking_user", for_user) - - recipients = - if for_user do - [Pleroma.Constants.as_public()] ++ [for_user.ap_id | for_user.following] - else - [Pleroma.Constants.as_public()] - end - - activities = - recipients - |> ActivityPub.fetch_activities(params) - |> Enum.reverse() - - conn - |> add_link_headers(activities) - |> put_view(StatusView) - |> render("index.json", %{activities: activities, for: for_user, as: :activity}) - else - nil -> {:error, :not_found} - true -> render_error(conn, :forbidden, "Can't get favorites") - end - end - def bookmarks(%{assigns: %{user: user}} = conn, params) do user = User.get_cached_by_id(user.id) @@ -705,14 +479,6 @@ def bookmarks(%{assigns: %{user: user}} = conn, params) do |> render("index.json", %{activities: activities, for: user, as: :activity}) end - def account_lists(%{assigns: %{user: user}} = conn, %{"id" => account_id}) do - lists = Pleroma.List.get_lists_account_belongs(user, account_id) - - conn - |> put_view(ListView) - |> render("index.json", %{lists: lists}) - end - def index(%{assigns: %{user: user}} = conn, _params) do token = get_session(conn, :oauth_token) @@ -721,8 +487,7 @@ def index(%{assigns: %{user: user}} = conn, _params) do limit = Config.get([:instance, :limit]) - accounts = - Map.put(%{}, user.id, AccountView.render("account.json", %{user: user, for: user})) + accounts = Map.put(%{}, user.id, AccountView.render("show.json", %{user: user, for: user})) initial_state = %{ diff --git a/lib/pleroma/web/mastodon_api/controllers/search_controller.ex b/lib/pleroma/web/mastodon_api/controllers/search_controller.ex index c91713773..3fc89d645 100644 --- a/lib/pleroma/web/mastodon_api/controllers/search_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/search_controller.ex @@ -22,7 +22,7 @@ def account_search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) d conn |> put_view(AccountView) - |> render("accounts.json", users: accounts, for: user, as: :user) + |> render("index.json", users: accounts, for: user, as: :user) end def search2(conn, params), do: do_search(:v2, conn, params) @@ -72,7 +72,7 @@ defp search_options(params, user) do defp resource_search(_, "accounts", query, options) do accounts = with_fallback(fn -> User.search(query, options) end) - AccountView.render("accounts.json", users: accounts, for: options[:for_user], as: :user) + AccountView.render("index.json", users: accounts, for: options[:for_user], as: :user) end defp resource_search(_, "statuses", query, options) do diff --git a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex index f4de9285b..3c6987a5f 100644 --- a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex @@ -231,7 +231,7 @@ def favourited_by(%{assigns: %{user: user}} = conn, %{"id" => id}) do conn |> put_view(AccountView) - |> render("accounts.json", for: user, users: users, as: :user) + |> render("index.json", for: user, users: users, as: :user) else {:visible, false} -> {:error, :not_found} _ -> json(conn, []) @@ -251,7 +251,7 @@ def reblogged_by(%{assigns: %{user: user}} = conn, %{"id" => id}) do conn |> put_view(AccountView) - |> render("accounts.json", for: user, users: users, as: :user) + |> render("index.json", for: user, users: users, as: :user) else {:visible, false} -> {:error, :not_found} _ -> json(conn, []) diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex index 8cf9e9d5c..99169ef95 100644 --- a/lib/pleroma/web/mastodon_api/views/account_view.ex +++ b/lib/pleroma/web/mastodon_api/views/account_view.ex @@ -11,15 +11,15 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do alias Pleroma.Web.MastodonAPI.AccountView alias Pleroma.Web.MediaProxy - def render("accounts.json", %{users: users} = opts) do + def render("index.json", %{users: users} = opts) do users - |> render_many(AccountView, "account.json", opts) + |> render_many(AccountView, "show.json", opts) |> Enum.filter(&Enum.any?/1) end - def render("account.json", %{user: user} = opts) do + def render("show.json", %{user: user} = opts) do if User.visible_for?(user, opts[:for]), - do: do_render("account.json", opts), + do: do_render("show.json", opts), else: %{} end @@ -66,7 +66,7 @@ def render("relationships.json", %{user: user, targets: targets}) do render_many(targets, AccountView, "relationship.json", user: user, as: :target) end - defp do_render("account.json", %{user: user} = opts) do + defp do_render("show.json", %{user: user} = opts) do display_name = HTML.strip_tags(user.name || user.nickname) image = User.avatar_url(user) |> MediaProxy.url() diff --git a/lib/pleroma/web/mastodon_api/views/conversation_view.ex b/lib/pleroma/web/mastodon_api/views/conversation_view.ex index 2c5767dd8..e9d2735b3 100644 --- a/lib/pleroma/web/mastodon_api/views/conversation_view.ex +++ b/lib/pleroma/web/mastodon_api/views/conversation_view.ex @@ -32,7 +32,7 @@ def render("participation.json", %{participation: participation, for: user}) do %{ id: participation.id |> to_string(), - accounts: render(AccountView, "accounts.json", users: users, as: :user), + accounts: render(AccountView, "index.json", users: users, as: :user), unread: !participation.read, last_status: render(StatusView, "show.json", activity: activity, for: user) } diff --git a/lib/pleroma/web/mastodon_api/views/notification_view.ex b/lib/pleroma/web/mastodon_api/views/notification_view.ex index 05110a192..60b58dc90 100644 --- a/lib/pleroma/web/mastodon_api/views/notification_view.ex +++ b/lib/pleroma/web/mastodon_api/views/notification_view.ex @@ -29,7 +29,7 @@ def render("show.json", %{ id: to_string(notification.id), type: mastodon_type, created_at: CommonAPI.Utils.to_masto_date(notification.inserted_at), - account: AccountView.render("account.json", %{user: actor, for: user}), + account: AccountView.render("show.json", %{user: actor, for: user}), pleroma: %{ is_seen: notification.seen } diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index d398f7853..bc527ad1b 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -108,7 +108,7 @@ def render( id: to_string(activity.id), uri: activity_object.data["id"], url: activity_object.data["id"], - account: AccountView.render("account.json", %{user: user, for: opts[:for]}), + account: AccountView.render("show.json", %{user: user, for: opts[:for]}), in_reply_to_id: nil, in_reply_to_account_id: nil, reblog: reblogged, @@ -258,7 +258,7 @@ def render("show.json", %{activity: %{data: %{"object" => _object}} = activity} id: to_string(activity.id), uri: object.data["id"], url: url, - account: AccountView.render("account.json", %{user: user, for: opts[:for]}), + account: AccountView.render("show.json", %{user: user, for: opts[:for]}), in_reply_to_id: reply_to && to_string(reply_to.id), in_reply_to_account_id: reply_to_user && to_string(reply_to_user.id), reblog: nil, @@ -376,7 +376,7 @@ def render("listen.json", %{activity: %Activity{data: %{"type" => "Listen"}} = a %{ id: activity.id, - account: AccountView.render("account.json", %{user: user, for: opts[:for]}), + account: AccountView.render("show.json", %{user: user, for: opts[:for]}), created_at: created_at, title: object.data["title"] |> HTML.strip_tags(), artist: object.data["artist"] |> HTML.strip_tags(), diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 9fd13c2fd..a57bc75d7 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -323,7 +323,7 @@ defmodule Pleroma.Web.Router do get("/accounts/relationships", MastodonAPIController, :relationships) - get("/accounts/:id/lists", MastodonAPIController, :account_lists) + get("/accounts/:id/lists", AccountController, :lists) get("/accounts/:id/identity_proofs", MastodonAPIController, :empty_array) get("/follow_requests", FollowRequestController, :index) @@ -413,14 +413,13 @@ defmodule Pleroma.Web.Router do scope [] do pipe_through(:oauth_follow) - post("/follows", MastodonAPIController, :follow) - post("/accounts/:id/follow", MastodonAPIController, :follow) - - post("/accounts/:id/unfollow", MastodonAPIController, :unfollow) - post("/accounts/:id/block", MastodonAPIController, :block) - post("/accounts/:id/unblock", MastodonAPIController, :unblock) - post("/accounts/:id/mute", MastodonAPIController, :mute) - post("/accounts/:id/unmute", MastodonAPIController, :unmute) + post("/follows", MastodonAPIController, :follows) + post("/accounts/:id/follow", AccountController, :follow) + post("/accounts/:id/unfollow", AccountController, :unfollow) + post("/accounts/:id/block", AccountController, :block) + post("/accounts/:id/unblock", AccountController, :unblock) + post("/accounts/:id/mute", AccountController, :mute) + post("/accounts/:id/unmute", AccountController, :unmute) post("/follow_requests/:id/authorize", FollowRequestController, :authorize) post("/follow_requests/:id/reject", FollowRequestController, :reject) @@ -428,8 +427,8 @@ defmodule Pleroma.Web.Router do post("/domain_blocks", DomainBlockController, :create) delete("/domain_blocks", DomainBlockController, :delete) - post("/pleroma/accounts/:id/subscribe", MastodonAPIController, :subscribe) - post("/pleroma/accounts/:id/unsubscribe", MastodonAPIController, :unsubscribe) + post("/pleroma/accounts/:id/subscribe", AccountController, :subscribe) + post("/pleroma/accounts/:id/unsubscribe", AccountController, :unsubscribe) end scope [] do @@ -487,14 +486,14 @@ defmodule Pleroma.Web.Router do get("/polls/:id", MastodonAPIController, :get_poll) - get("/accounts/:id/statuses", MastodonAPIController, :user_statuses) - get("/accounts/:id/followers", MastodonAPIController, :followers) - get("/accounts/:id/following", MastodonAPIController, :following) - get("/accounts/:id", MastodonAPIController, :user) + get("/accounts/:id/statuses", AccountController, :statuses) + get("/accounts/:id/followers", AccountController, :followers) + get("/accounts/:id/following", AccountController, :following) + get("/accounts/:id", AccountController, :show) get("/search", SearchController, :search) - get("/pleroma/accounts/:id/favourites", MastodonAPIController, :user_favourites) + get("/pleroma/accounts/:id/favourites", AccountController, :favourites) end end diff --git a/test/list_test.exs b/test/list_test.exs index ba79251da..e7b23915b 100644 --- a/test/list_test.exs +++ b/test/list_test.exs @@ -113,10 +113,10 @@ test "getting own lists a given user belongs to" do {:ok, not_owned_list} = Pleroma.List.follow(not_owned_list, member_1) {:ok, not_owned_list} = Pleroma.List.follow(not_owned_list, member_2) - lists_1 = Pleroma.List.get_lists_account_belongs(owner, member_1.id) + lists_1 = Pleroma.List.get_lists_account_belongs(owner, member_1) assert owned_list in lists_1 refute not_owned_list in lists_1 - lists_2 = Pleroma.List.get_lists_account_belongs(owner, member_2.id) + lists_2 = Pleroma.List.get_lists_account_belongs(owner, member_2) assert owned_list in lists_2 refute not_owned_list in lists_2 end diff --git a/test/web/admin_api/views/report_view_test.exs b/test/web/admin_api/views/report_view_test.exs index 35b6947a0..475705857 100644 --- a/test/web/admin_api/views/report_view_test.exs +++ b/test/web/admin_api/views/report_view_test.exs @@ -21,12 +21,12 @@ test "renders a report" do content: nil, actor: Map.merge( - AccountView.render("account.json", %{user: user}), + AccountView.render("show.json", %{user: user}), Pleroma.Web.AdminAPI.AccountView.render("show.json", %{user: user}) ), account: Map.merge( - AccountView.render("account.json", %{user: other_user}), + AccountView.render("show.json", %{user: other_user}), Pleroma.Web.AdminAPI.AccountView.render("show.json", %{user: other_user}) ), statuses: [], @@ -53,12 +53,12 @@ test "includes reported statuses" do content: nil, actor: Map.merge( - AccountView.render("account.json", %{user: user}), + AccountView.render("show.json", %{user: user}), Pleroma.Web.AdminAPI.AccountView.render("show.json", %{user: user}) ), account: Map.merge( - AccountView.render("account.json", %{user: other_user}), + AccountView.render("show.json", %{user: other_user}), Pleroma.Web.AdminAPI.AccountView.render("show.json", %{user: other_user}) ), statuses: [StatusView.render("show.json", %{activity: activity})], diff --git a/test/web/mastodon_api/controllers/account_controller_test.exs b/test/web/mastodon_api/controllers/account_controller_test.exs new file mode 100644 index 000000000..6cf929011 --- /dev/null +++ b/test/web/mastodon_api/controllers/account_controller_test.exs @@ -0,0 +1,810 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do + use Pleroma.Web.ConnCase + + alias Pleroma.User + alias Pleroma.Web.ActivityPub.ActivityPub + alias Pleroma.Web.CommonAPI + + import Pleroma.Factory + + describe "account fetching" do + test "works by id" do + user = insert(:user) + + conn = + build_conn() + |> get("/api/v1/accounts/#{user.id}") + + assert %{"id" => id} = json_response(conn, 200) + assert id == to_string(user.id) + + conn = + build_conn() + |> get("/api/v1/accounts/-1") + + assert %{"error" => "Can't find user"} = json_response(conn, 404) + end + + test "works by nickname" do + user = insert(:user) + + conn = + build_conn() + |> get("/api/v1/accounts/#{user.nickname}") + + assert %{"id" => id} = json_response(conn, 200) + assert id == user.id + end + + test "works by nickname for remote users" do + limit_to_local = Pleroma.Config.get([:instance, :limit_to_local_content]) + Pleroma.Config.put([:instance, :limit_to_local_content], false) + user = insert(:user, nickname: "user@example.com", local: false) + + conn = + build_conn() + |> get("/api/v1/accounts/#{user.nickname}") + + Pleroma.Config.put([:instance, :limit_to_local_content], limit_to_local) + assert %{"id" => id} = json_response(conn, 200) + assert id == user.id + end + + test "respects limit_to_local_content == :all for remote user nicknames" do + limit_to_local = Pleroma.Config.get([:instance, :limit_to_local_content]) + Pleroma.Config.put([:instance, :limit_to_local_content], :all) + + user = insert(:user, nickname: "user@example.com", local: false) + + conn = + build_conn() + |> get("/api/v1/accounts/#{user.nickname}") + + Pleroma.Config.put([:instance, :limit_to_local_content], limit_to_local) + assert json_response(conn, 404) + end + + test "respects limit_to_local_content == :unauthenticated for remote user nicknames" do + limit_to_local = Pleroma.Config.get([:instance, :limit_to_local_content]) + Pleroma.Config.put([:instance, :limit_to_local_content], :unauthenticated) + + user = insert(:user, nickname: "user@example.com", local: false) + reading_user = insert(:user) + + conn = + build_conn() + |> get("/api/v1/accounts/#{user.nickname}") + + assert json_response(conn, 404) + + conn = + build_conn() + |> assign(:user, reading_user) + |> get("/api/v1/accounts/#{user.nickname}") + + Pleroma.Config.put([:instance, :limit_to_local_content], limit_to_local) + assert %{"id" => id} = json_response(conn, 200) + assert id == user.id + end + + test "accounts fetches correct account for nicknames beginning with numbers", %{conn: conn} do + # Need to set an old-style integer ID to reproduce the problem + # (these are no longer assigned to new accounts but were preserved + # for existing accounts during the migration to flakeIDs) + user_one = insert(:user, %{id: 1212}) + user_two = insert(:user, %{nickname: "#{user_one.id}garbage"}) + + resp_one = + conn + |> get("/api/v1/accounts/#{user_one.id}") + + resp_two = + conn + |> get("/api/v1/accounts/#{user_two.nickname}") + + resp_three = + conn + |> get("/api/v1/accounts/#{user_two.id}") + + acc_one = json_response(resp_one, 200) + acc_two = json_response(resp_two, 200) + acc_three = json_response(resp_three, 200) + refute acc_one == acc_two + assert acc_two == acc_three + end + end + + describe "user timelines" do + test "gets a users statuses", %{conn: conn} do + user_one = insert(:user) + user_two = insert(:user) + user_three = insert(:user) + + {:ok, user_three} = User.follow(user_three, user_one) + + {:ok, activity} = CommonAPI.post(user_one, %{"status" => "HI!!!"}) + + {:ok, direct_activity} = + CommonAPI.post(user_one, %{ + "status" => "Hi, @#{user_two.nickname}.", + "visibility" => "direct" + }) + + {:ok, private_activity} = + CommonAPI.post(user_one, %{"status" => "private", "visibility" => "private"}) + + resp = + conn + |> get("/api/v1/accounts/#{user_one.id}/statuses") + + assert [%{"id" => id}] = json_response(resp, 200) + assert id == to_string(activity.id) + + resp = + conn + |> assign(:user, user_two) + |> get("/api/v1/accounts/#{user_one.id}/statuses") + + assert [%{"id" => id_one}, %{"id" => id_two}] = json_response(resp, 200) + assert id_one == to_string(direct_activity.id) + assert id_two == to_string(activity.id) + + resp = + conn + |> assign(:user, user_three) + |> get("/api/v1/accounts/#{user_one.id}/statuses") + + assert [%{"id" => id_one}, %{"id" => id_two}] = json_response(resp, 200) + assert id_one == to_string(private_activity.id) + assert id_two == to_string(activity.id) + end + + test "unimplemented pinned statuses feature", %{conn: conn} do + note = insert(:note_activity) + user = User.get_cached_by_ap_id(note.data["actor"]) + + conn = + conn + |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true") + + assert json_response(conn, 200) == [] + end + + test "gets an users media", %{conn: conn} do + note = insert(:note_activity) + user = User.get_cached_by_ap_id(note.data["actor"]) + + file = %Plug.Upload{ + content_type: "image/jpg", + path: Path.absname("test/fixtures/image.jpg"), + filename: "an_image.jpg" + } + + {:ok, %{id: media_id}} = ActivityPub.upload(file, actor: user.ap_id) + + {:ok, image_post} = CommonAPI.post(user, %{"status" => "cofe", "media_ids" => [media_id]}) + + conn = + conn + |> get("/api/v1/accounts/#{user.id}/statuses", %{"only_media" => "true"}) + + assert [%{"id" => id}] = json_response(conn, 200) + assert id == to_string(image_post.id) + + conn = + build_conn() + |> get("/api/v1/accounts/#{user.id}/statuses", %{"only_media" => "1"}) + + assert [%{"id" => id}] = json_response(conn, 200) + assert id == to_string(image_post.id) + end + + test "gets a user's statuses without reblogs", %{conn: conn} do + user = insert(:user) + {:ok, post} = CommonAPI.post(user, %{"status" => "HI!!!"}) + {:ok, _, _} = CommonAPI.repeat(post.id, user) + + conn = + conn + |> get("/api/v1/accounts/#{user.id}/statuses", %{"exclude_reblogs" => "true"}) + + assert [%{"id" => id}] = json_response(conn, 200) + assert id == to_string(post.id) + + conn = + conn + |> get("/api/v1/accounts/#{user.id}/statuses", %{"exclude_reblogs" => "1"}) + + assert [%{"id" => id}] = json_response(conn, 200) + assert id == to_string(post.id) + end + + test "filters user's statuses by a hashtag", %{conn: conn} do + user = insert(:user) + {:ok, post} = CommonAPI.post(user, %{"status" => "#hashtag"}) + {:ok, _post} = CommonAPI.post(user, %{"status" => "hashtag"}) + + conn = + conn + |> get("/api/v1/accounts/#{user.id}/statuses", %{"tagged" => "hashtag"}) + + assert [%{"id" => id}] = json_response(conn, 200) + assert id == to_string(post.id) + end + end + + describe "followers" do + test "getting followers", %{conn: conn} do + user = insert(:user) + other_user = insert(:user) + {:ok, user} = User.follow(user, other_user) + + conn = + conn + |> get("/api/v1/accounts/#{other_user.id}/followers") + + assert [%{"id" => id}] = json_response(conn, 200) + assert id == to_string(user.id) + end + + test "getting followers, hide_followers", %{conn: conn} do + user = insert(:user) + other_user = insert(:user, %{info: %{hide_followers: true}}) + {:ok, _user} = User.follow(user, other_user) + + conn = + conn + |> get("/api/v1/accounts/#{other_user.id}/followers") + + assert [] == json_response(conn, 200) + end + + test "getting followers, hide_followers, same user requesting", %{conn: conn} do + user = insert(:user) + other_user = insert(:user, %{info: %{hide_followers: true}}) + {:ok, _user} = User.follow(user, other_user) + + conn = + conn + |> assign(:user, other_user) + |> get("/api/v1/accounts/#{other_user.id}/followers") + + refute [] == json_response(conn, 200) + end + + test "getting followers, pagination", %{conn: conn} do + user = insert(:user) + follower1 = insert(:user) + follower2 = insert(:user) + follower3 = insert(:user) + {:ok, _} = User.follow(follower1, user) + {:ok, _} = User.follow(follower2, user) + {:ok, _} = User.follow(follower3, user) + + conn = + conn + |> assign(:user, user) + + res_conn = + conn + |> get("/api/v1/accounts/#{user.id}/followers?since_id=#{follower1.id}") + + assert [%{"id" => id3}, %{"id" => id2}] = json_response(res_conn, 200) + assert id3 == follower3.id + assert id2 == follower2.id + + res_conn = + conn + |> get("/api/v1/accounts/#{user.id}/followers?max_id=#{follower3.id}") + + assert [%{"id" => id2}, %{"id" => id1}] = json_response(res_conn, 200) + assert id2 == follower2.id + assert id1 == follower1.id + + res_conn = + conn + |> get("/api/v1/accounts/#{user.id}/followers?limit=1&max_id=#{follower3.id}") + + assert [%{"id" => id2}] = json_response(res_conn, 200) + assert id2 == follower2.id + + assert [link_header] = get_resp_header(res_conn, "link") + assert link_header =~ ~r/min_id=#{follower2.id}/ + assert link_header =~ ~r/max_id=#{follower2.id}/ + end + end + + describe "following" do + test "getting following", %{conn: conn} do + user = insert(:user) + other_user = insert(:user) + {:ok, user} = User.follow(user, other_user) + + conn = + conn + |> get("/api/v1/accounts/#{user.id}/following") + + assert [%{"id" => id}] = json_response(conn, 200) + assert id == to_string(other_user.id) + end + + test "getting following, hide_follows", %{conn: conn} do + user = insert(:user, %{info: %{hide_follows: true}}) + other_user = insert(:user) + {:ok, user} = User.follow(user, other_user) + + conn = + conn + |> get("/api/v1/accounts/#{user.id}/following") + + assert [] == json_response(conn, 200) + end + + test "getting following, hide_follows, same user requesting", %{conn: conn} do + user = insert(:user, %{info: %{hide_follows: true}}) + other_user = insert(:user) + {:ok, user} = User.follow(user, other_user) + + conn = + conn + |> assign(:user, user) + |> get("/api/v1/accounts/#{user.id}/following") + + refute [] == json_response(conn, 200) + end + + test "getting following, pagination", %{conn: conn} do + user = insert(:user) + following1 = insert(:user) + following2 = insert(:user) + following3 = insert(:user) + {:ok, _} = User.follow(user, following1) + {:ok, _} = User.follow(user, following2) + {:ok, _} = User.follow(user, following3) + + conn = + conn + |> assign(:user, user) + + res_conn = + conn + |> get("/api/v1/accounts/#{user.id}/following?since_id=#{following1.id}") + + assert [%{"id" => id3}, %{"id" => id2}] = json_response(res_conn, 200) + assert id3 == following3.id + assert id2 == following2.id + + res_conn = + conn + |> get("/api/v1/accounts/#{user.id}/following?max_id=#{following3.id}") + + assert [%{"id" => id2}, %{"id" => id1}] = json_response(res_conn, 200) + assert id2 == following2.id + assert id1 == following1.id + + res_conn = + conn + |> get("/api/v1/accounts/#{user.id}/following?limit=1&max_id=#{following3.id}") + + assert [%{"id" => id2}] = json_response(res_conn, 200) + assert id2 == following2.id + + assert [link_header] = get_resp_header(res_conn, "link") + assert link_header =~ ~r/min_id=#{following2.id}/ + assert link_header =~ ~r/max_id=#{following2.id}/ + end + end + + describe "follow/unfollow" do + test "following / unfollowing a user", %{conn: conn} do + user = insert(:user) + other_user = insert(:user) + + conn = + conn + |> assign(:user, user) + |> post("/api/v1/accounts/#{other_user.id}/follow") + + assert %{"id" => _id, "following" => true} = json_response(conn, 200) + + user = User.get_cached_by_id(user.id) + + conn = + build_conn() + |> assign(:user, user) + |> post("/api/v1/accounts/#{other_user.id}/unfollow") + + assert %{"id" => _id, "following" => false} = json_response(conn, 200) + + user = User.get_cached_by_id(user.id) + + conn = + build_conn() + |> assign(:user, user) + |> post("/api/v1/follows", %{"uri" => other_user.nickname}) + + assert %{"id" => id} = json_response(conn, 200) + assert id == to_string(other_user.id) + end + + test "following without reblogs" do + follower = insert(:user) + followed = insert(:user) + other_user = insert(:user) + + conn = + build_conn() + |> assign(:user, follower) + |> post("/api/v1/accounts/#{followed.id}/follow?reblogs=false") + + assert %{"showing_reblogs" => false} = json_response(conn, 200) + + {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hey"}) + {:ok, reblog, _} = CommonAPI.repeat(activity.id, followed) + + conn = + build_conn() + |> assign(:user, User.get_cached_by_id(follower.id)) + |> get("/api/v1/timelines/home") + + assert [] == json_response(conn, 200) + + conn = + build_conn() + |> assign(:user, follower) + |> post("/api/v1/accounts/#{followed.id}/follow?reblogs=true") + + assert %{"showing_reblogs" => true} = json_response(conn, 200) + + conn = + build_conn() + |> assign(:user, User.get_cached_by_id(follower.id)) + |> get("/api/v1/timelines/home") + + expected_activity_id = reblog.id + assert [%{"id" => ^expected_activity_id}] = json_response(conn, 200) + end + + test "following / unfollowing errors" do + user = insert(:user) + + conn = + build_conn() + |> assign(:user, user) + + # self follow + conn_res = post(conn, "/api/v1/accounts/#{user.id}/follow") + assert %{"error" => "Record not found"} = json_response(conn_res, 404) + + # self unfollow + user = User.get_cached_by_id(user.id) + conn_res = post(conn, "/api/v1/accounts/#{user.id}/unfollow") + assert %{"error" => "Record not found"} = json_response(conn_res, 404) + + # self follow via uri + user = User.get_cached_by_id(user.id) + conn_res = post(conn, "/api/v1/follows", %{"uri" => user.nickname}) + assert %{"error" => "Record not found"} = json_response(conn_res, 404) + + # follow non existing user + conn_res = post(conn, "/api/v1/accounts/doesntexist/follow") + assert %{"error" => "Record not found"} = json_response(conn_res, 404) + + # follow non existing user via uri + conn_res = post(conn, "/api/v1/follows", %{"uri" => "doesntexist"}) + assert %{"error" => "Record not found"} = json_response(conn_res, 404) + + # unfollow non existing user + conn_res = post(conn, "/api/v1/accounts/doesntexist/unfollow") + assert %{"error" => "Record not found"} = json_response(conn_res, 404) + end + end + + describe "mute/unmute" do + test "with notifications", %{conn: conn} do + user = insert(:user) + other_user = insert(:user) + + conn = + conn + |> assign(:user, user) + |> post("/api/v1/accounts/#{other_user.id}/mute") + + response = json_response(conn, 200) + + assert %{"id" => _id, "muting" => true, "muting_notifications" => true} = response + user = User.get_cached_by_id(user.id) + + conn = + build_conn() + |> assign(:user, user) + |> post("/api/v1/accounts/#{other_user.id}/unmute") + + response = json_response(conn, 200) + assert %{"id" => _id, "muting" => false, "muting_notifications" => false} = response + end + + test "without notifications", %{conn: conn} do + user = insert(:user) + other_user = insert(:user) + + conn = + conn + |> assign(:user, user) + |> post("/api/v1/accounts/#{other_user.id}/mute", %{"notifications" => "false"}) + + response = json_response(conn, 200) + + assert %{"id" => _id, "muting" => true, "muting_notifications" => false} = response + user = User.get_cached_by_id(user.id) + + conn = + build_conn() + |> assign(:user, user) + |> post("/api/v1/accounts/#{other_user.id}/unmute") + + response = json_response(conn, 200) + assert %{"id" => _id, "muting" => false, "muting_notifications" => false} = response + end + end + + describe "getting favorites timeline of specified user" do + setup do + [current_user, user] = insert_pair(:user, %{info: %{hide_favorites: false}}) + [current_user: current_user, user: user] + end + + test "returns list of statuses favorited by specified user", %{ + conn: conn, + current_user: current_user, + user: user + } do + [activity | _] = insert_pair(:note_activity) + CommonAPI.favorite(activity.id, user) + + response = + conn + |> assign(:user, current_user) + |> get("/api/v1/pleroma/accounts/#{user.id}/favourites") + |> json_response(:ok) + + [like] = response + + assert length(response) == 1 + assert like["id"] == activity.id + end + + test "returns favorites for specified user_id when user is not logged in", %{ + conn: conn, + user: user + } do + activity = insert(:note_activity) + CommonAPI.favorite(activity.id, user) + + response = + conn + |> get("/api/v1/pleroma/accounts/#{user.id}/favourites") + |> json_response(:ok) + + assert length(response) == 1 + end + + test "returns favorited DM only when user is logged in and he is one of recipients", %{ + conn: conn, + current_user: current_user, + user: user + } do + {:ok, direct} = + CommonAPI.post(current_user, %{ + "status" => "Hi @#{user.nickname}!", + "visibility" => "direct" + }) + + CommonAPI.favorite(direct.id, user) + + response = + conn + |> assign(:user, current_user) + |> get("/api/v1/pleroma/accounts/#{user.id}/favourites") + |> json_response(:ok) + + assert length(response) == 1 + + anonymous_response = + conn + |> get("/api/v1/pleroma/accounts/#{user.id}/favourites") + |> json_response(:ok) + + assert Enum.empty?(anonymous_response) + end + + test "does not return others' favorited DM when user is not one of recipients", %{ + conn: conn, + current_user: current_user, + user: user + } do + user_two = insert(:user) + + {:ok, direct} = + CommonAPI.post(user_two, %{ + "status" => "Hi @#{user.nickname}!", + "visibility" => "direct" + }) + + CommonAPI.favorite(direct.id, user) + + response = + conn + |> assign(:user, current_user) + |> get("/api/v1/pleroma/accounts/#{user.id}/favourites") + |> json_response(:ok) + + assert Enum.empty?(response) + end + + test "paginates favorites using since_id and max_id", %{ + conn: conn, + current_user: current_user, + user: user + } do + activities = insert_list(10, :note_activity) + + Enum.each(activities, fn activity -> + CommonAPI.favorite(activity.id, user) + end) + + third_activity = Enum.at(activities, 2) + seventh_activity = Enum.at(activities, 6) + + response = + conn + |> assign(:user, current_user) + |> get("/api/v1/pleroma/accounts/#{user.id}/favourites", %{ + since_id: third_activity.id, + max_id: seventh_activity.id + }) + |> json_response(:ok) + + assert length(response) == 3 + refute third_activity in response + refute seventh_activity in response + end + + test "limits favorites using limit parameter", %{ + conn: conn, + current_user: current_user, + user: user + } do + 7 + |> insert_list(:note_activity) + |> Enum.each(fn activity -> + CommonAPI.favorite(activity.id, user) + end) + + response = + conn + |> assign(:user, current_user) + |> get("/api/v1/pleroma/accounts/#{user.id}/favourites", %{limit: "3"}) + |> json_response(:ok) + + assert length(response) == 3 + end + + test "returns empty response when user does not have any favorited statuses", %{ + conn: conn, + current_user: current_user, + user: user + } do + response = + conn + |> assign(:user, current_user) + |> get("/api/v1/pleroma/accounts/#{user.id}/favourites") + |> json_response(:ok) + + assert Enum.empty?(response) + end + + test "returns 404 error when specified user is not exist", %{conn: conn} do + conn = get(conn, "/api/v1/pleroma/accounts/test/favourites") + + assert json_response(conn, 404) == %{"error" => "Record not found"} + end + + test "returns 403 error when user has hidden own favorites", %{ + conn: conn, + current_user: current_user + } do + user = insert(:user, %{info: %{hide_favorites: true}}) + activity = insert(:note_activity) + CommonAPI.favorite(activity.id, user) + + conn = + conn + |> assign(:user, current_user) + |> get("/api/v1/pleroma/accounts/#{user.id}/favourites") + + assert json_response(conn, 403) == %{"error" => "Can't get favorites"} + end + + test "hides favorites for new users by default", %{conn: conn, current_user: current_user} do + user = insert(:user) + activity = insert(:note_activity) + CommonAPI.favorite(activity.id, user) + + conn = + conn + |> assign(:user, current_user) + |> get("/api/v1/pleroma/accounts/#{user.id}/favourites") + + assert user.info.hide_favorites + assert json_response(conn, 403) == %{"error" => "Can't get favorites"} + end + end + + describe "pinned statuses" do + setup do + user = insert(:user) + {:ok, activity} = CommonAPI.post(user, %{"status" => "HI!!!"}) + + [user: user, activity: activity] + end + + test "returns pinned statuses", %{conn: conn, user: user, activity: activity} do + {:ok, _} = CommonAPI.pin(activity.id, user) + + result = + conn + |> assign(:user, user) + |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true") + |> json_response(200) + + id_str = to_string(activity.id) + + assert [%{"id" => ^id_str, "pinned" => true}] = result + end + end + + test "subscribing / unsubscribing to a user", %{conn: conn} do + user = insert(:user) + subscription_target = insert(:user) + + conn = + conn + |> assign(:user, user) + |> post("/api/v1/pleroma/accounts/#{subscription_target.id}/subscribe") + + assert %{"id" => _id, "subscribing" => true} = json_response(conn, 200) + + conn = + build_conn() + |> assign(:user, user) + |> post("/api/v1/pleroma/accounts/#{subscription_target.id}/unsubscribe") + + assert %{"id" => _id, "subscribing" => false} = json_response(conn, 200) + end + + test "blocking / unblocking a user", %{conn: conn} do + user = insert(:user) + other_user = insert(:user) + + conn = + conn + |> assign(:user, user) + |> post("/api/v1/accounts/#{other_user.id}/block") + + assert %{"id" => _id, "blocking" => true} = json_response(conn, 200) + + user = User.get_cached_by_id(user.id) + + conn = + build_conn() + |> assign(:user, user) + |> post("/api/v1/accounts/#{other_user.id}/unblock") + + assert %{"id" => _id, "blocking" => false} = json_response(conn, 200) + end +end diff --git a/test/web/mastodon_api/mastodon_api_controller_test.exs b/test/web/mastodon_api/mastodon_api_controller_test.exs index 46b035770..7cdefdcdd 100644 --- a/test/web/mastodon_api/mastodon_api_controller_test.exs +++ b/test/web/mastodon_api/mastodon_api_controller_test.exs @@ -202,125 +202,6 @@ test "creates an oauth app", %{conn: conn} do assert expected == json_response(conn, 200) end - describe "user timelines" do - test "gets a users statuses", %{conn: conn} do - user_one = insert(:user) - user_two = insert(:user) - user_three = insert(:user) - - {:ok, user_three} = User.follow(user_three, user_one) - - {:ok, activity} = CommonAPI.post(user_one, %{"status" => "HI!!!"}) - - {:ok, direct_activity} = - CommonAPI.post(user_one, %{ - "status" => "Hi, @#{user_two.nickname}.", - "visibility" => "direct" - }) - - {:ok, private_activity} = - CommonAPI.post(user_one, %{"status" => "private", "visibility" => "private"}) - - resp = - conn - |> get("/api/v1/accounts/#{user_one.id}/statuses") - - assert [%{"id" => id}] = json_response(resp, 200) - assert id == to_string(activity.id) - - resp = - conn - |> assign(:user, user_two) - |> get("/api/v1/accounts/#{user_one.id}/statuses") - - assert [%{"id" => id_one}, %{"id" => id_two}] = json_response(resp, 200) - assert id_one == to_string(direct_activity.id) - assert id_two == to_string(activity.id) - - resp = - conn - |> assign(:user, user_three) - |> get("/api/v1/accounts/#{user_one.id}/statuses") - - assert [%{"id" => id_one}, %{"id" => id_two}] = json_response(resp, 200) - assert id_one == to_string(private_activity.id) - assert id_two == to_string(activity.id) - end - - test "unimplemented pinned statuses feature", %{conn: conn} do - note = insert(:note_activity) - user = User.get_cached_by_ap_id(note.data["actor"]) - - conn = - conn - |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true") - - assert json_response(conn, 200) == [] - end - - test "gets an users media", %{conn: conn} do - note = insert(:note_activity) - user = User.get_cached_by_ap_id(note.data["actor"]) - - file = %Plug.Upload{ - content_type: "image/jpg", - path: Path.absname("test/fixtures/image.jpg"), - filename: "an_image.jpg" - } - - {:ok, %{id: media_id}} = ActivityPub.upload(file, actor: user.ap_id) - - {:ok, image_post} = CommonAPI.post(user, %{"status" => "cofe", "media_ids" => [media_id]}) - - conn = - conn - |> get("/api/v1/accounts/#{user.id}/statuses", %{"only_media" => "true"}) - - assert [%{"id" => id}] = json_response(conn, 200) - assert id == to_string(image_post.id) - - conn = - build_conn() - |> get("/api/v1/accounts/#{user.id}/statuses", %{"only_media" => "1"}) - - assert [%{"id" => id}] = json_response(conn, 200) - assert id == to_string(image_post.id) - end - - test "gets a user's statuses without reblogs", %{conn: conn} do - user = insert(:user) - {:ok, post} = CommonAPI.post(user, %{"status" => "HI!!!"}) - {:ok, _, _} = CommonAPI.repeat(post.id, user) - - conn = - conn - |> get("/api/v1/accounts/#{user.id}/statuses", %{"exclude_reblogs" => "true"}) - - assert [%{"id" => id}] = json_response(conn, 200) - assert id == to_string(post.id) - - conn = - conn - |> get("/api/v1/accounts/#{user.id}/statuses", %{"exclude_reblogs" => "1"}) - - assert [%{"id" => id}] = json_response(conn, 200) - assert id == to_string(post.id) - end - - test "filters user's statuses by a hashtag", %{conn: conn} do - user = insert(:user) - {:ok, post} = CommonAPI.post(user, %{"status" => "#hashtag"}) - {:ok, _post} = CommonAPI.post(user, %{"status" => "hashtag"}) - - conn = - conn - |> get("/api/v1/accounts/#{user.id}/statuses", %{"tagged" => "hashtag"}) - - assert [%{"id" => id}] = json_response(conn, 200) - assert id == to_string(post.id) - end - end - describe "user relationships" do test "returns the relationships for the current user", %{conn: conn} do user = insert(:user) @@ -400,87 +281,6 @@ test "verify_credentials", %{conn: conn} do end end - describe "account fetching" do - test "works by id" do - user = insert(:user) - - conn = - build_conn() - |> get("/api/v1/accounts/#{user.id}") - - assert %{"id" => id} = json_response(conn, 200) - assert id == to_string(user.id) - - conn = - build_conn() - |> get("/api/v1/accounts/-1") - - assert %{"error" => "Can't find user"} = json_response(conn, 404) - end - - test "works by nickname" do - user = insert(:user) - - conn = - build_conn() - |> get("/api/v1/accounts/#{user.nickname}") - - assert %{"id" => id} = json_response(conn, 200) - assert id == user.id - end - - test "works by nickname for remote users" do - limit_to_local = Pleroma.Config.get([:instance, :limit_to_local_content]) - Pleroma.Config.put([:instance, :limit_to_local_content], false) - user = insert(:user, nickname: "user@example.com", local: false) - - conn = - build_conn() - |> get("/api/v1/accounts/#{user.nickname}") - - Pleroma.Config.put([:instance, :limit_to_local_content], limit_to_local) - assert %{"id" => id} = json_response(conn, 200) - assert id == user.id - end - - test "respects limit_to_local_content == :all for remote user nicknames" do - limit_to_local = Pleroma.Config.get([:instance, :limit_to_local_content]) - Pleroma.Config.put([:instance, :limit_to_local_content], :all) - - user = insert(:user, nickname: "user@example.com", local: false) - - conn = - build_conn() - |> get("/api/v1/accounts/#{user.nickname}") - - Pleroma.Config.put([:instance, :limit_to_local_content], limit_to_local) - assert json_response(conn, 404) - end - - test "respects limit_to_local_content == :unauthenticated for remote user nicknames" do - limit_to_local = Pleroma.Config.get([:instance, :limit_to_local_content]) - Pleroma.Config.put([:instance, :limit_to_local_content], :unauthenticated) - - user = insert(:user, nickname: "user@example.com", local: false) - reading_user = insert(:user) - - conn = - build_conn() - |> get("/api/v1/accounts/#{user.nickname}") - - assert json_response(conn, 404) - - conn = - build_conn() - |> assign(:user, reading_user) - |> get("/api/v1/accounts/#{user.nickname}") - - Pleroma.Config.put([:instance, :limit_to_local_content], limit_to_local) - assert %{"id" => id} = json_response(conn, 200) - assert id == user.id - end - end - describe "/api/v1/pleroma/mascot" do test "mascot upload", %{conn: conn} do user = insert(:user) @@ -548,316 +348,6 @@ test "mascot retrieving", %{conn: conn} do assert url =~ "an_image" end end - - test "getting followers", %{conn: conn} do - user = insert(:user) - other_user = insert(:user) - {:ok, user} = User.follow(user, other_user) - - conn = - conn - |> get("/api/v1/accounts/#{other_user.id}/followers") - - assert [%{"id" => id}] = json_response(conn, 200) - assert id == to_string(user.id) - end - - test "getting followers, hide_followers", %{conn: conn} do - user = insert(:user) - other_user = insert(:user, %{info: %{hide_followers: true}}) - {:ok, _user} = User.follow(user, other_user) - - conn = - conn - |> get("/api/v1/accounts/#{other_user.id}/followers") - - assert [] == json_response(conn, 200) - end - - test "getting followers, hide_followers, same user requesting", %{conn: conn} do - user = insert(:user) - other_user = insert(:user, %{info: %{hide_followers: true}}) - {:ok, _user} = User.follow(user, other_user) - - conn = - conn - |> assign(:user, other_user) - |> get("/api/v1/accounts/#{other_user.id}/followers") - - refute [] == json_response(conn, 200) - end - - test "getting followers, pagination", %{conn: conn} do - user = insert(:user) - follower1 = insert(:user) - follower2 = insert(:user) - follower3 = insert(:user) - {:ok, _} = User.follow(follower1, user) - {:ok, _} = User.follow(follower2, user) - {:ok, _} = User.follow(follower3, user) - - conn = - conn - |> assign(:user, user) - - res_conn = - conn - |> get("/api/v1/accounts/#{user.id}/followers?since_id=#{follower1.id}") - - assert [%{"id" => id3}, %{"id" => id2}] = json_response(res_conn, 200) - assert id3 == follower3.id - assert id2 == follower2.id - - res_conn = - conn - |> get("/api/v1/accounts/#{user.id}/followers?max_id=#{follower3.id}") - - assert [%{"id" => id2}, %{"id" => id1}] = json_response(res_conn, 200) - assert id2 == follower2.id - assert id1 == follower1.id - - res_conn = - conn - |> get("/api/v1/accounts/#{user.id}/followers?limit=1&max_id=#{follower3.id}") - - assert [%{"id" => id2}] = json_response(res_conn, 200) - assert id2 == follower2.id - - assert [link_header] = get_resp_header(res_conn, "link") - assert link_header =~ ~r/min_id=#{follower2.id}/ - assert link_header =~ ~r/max_id=#{follower2.id}/ - end - - test "getting following", %{conn: conn} do - user = insert(:user) - other_user = insert(:user) - {:ok, user} = User.follow(user, other_user) - - conn = - conn - |> get("/api/v1/accounts/#{user.id}/following") - - assert [%{"id" => id}] = json_response(conn, 200) - assert id == to_string(other_user.id) - end - - test "getting following, hide_follows", %{conn: conn} do - user = insert(:user, %{info: %{hide_follows: true}}) - other_user = insert(:user) - {:ok, user} = User.follow(user, other_user) - - conn = - conn - |> get("/api/v1/accounts/#{user.id}/following") - - assert [] == json_response(conn, 200) - end - - test "getting following, hide_follows, same user requesting", %{conn: conn} do - user = insert(:user, %{info: %{hide_follows: true}}) - other_user = insert(:user) - {:ok, user} = User.follow(user, other_user) - - conn = - conn - |> assign(:user, user) - |> get("/api/v1/accounts/#{user.id}/following") - - refute [] == json_response(conn, 200) - end - - test "getting following, pagination", %{conn: conn} do - user = insert(:user) - following1 = insert(:user) - following2 = insert(:user) - following3 = insert(:user) - {:ok, _} = User.follow(user, following1) - {:ok, _} = User.follow(user, following2) - {:ok, _} = User.follow(user, following3) - - conn = - conn - |> assign(:user, user) - - res_conn = - conn - |> get("/api/v1/accounts/#{user.id}/following?since_id=#{following1.id}") - - assert [%{"id" => id3}, %{"id" => id2}] = json_response(res_conn, 200) - assert id3 == following3.id - assert id2 == following2.id - - res_conn = - conn - |> get("/api/v1/accounts/#{user.id}/following?max_id=#{following3.id}") - - assert [%{"id" => id2}, %{"id" => id1}] = json_response(res_conn, 200) - assert id2 == following2.id - assert id1 == following1.id - - res_conn = - conn - |> get("/api/v1/accounts/#{user.id}/following?limit=1&max_id=#{following3.id}") - - assert [%{"id" => id2}] = json_response(res_conn, 200) - assert id2 == following2.id - - assert [link_header] = get_resp_header(res_conn, "link") - assert link_header =~ ~r/min_id=#{following2.id}/ - assert link_header =~ ~r/max_id=#{following2.id}/ - end - - test "following / unfollowing a user", %{conn: conn} do - user = insert(:user) - other_user = insert(:user) - - conn = - conn - |> assign(:user, user) - |> post("/api/v1/accounts/#{other_user.id}/follow") - - assert %{"id" => _id, "following" => true} = json_response(conn, 200) - - user = User.get_cached_by_id(user.id) - - conn = - build_conn() - |> assign(:user, user) - |> post("/api/v1/accounts/#{other_user.id}/unfollow") - - assert %{"id" => _id, "following" => false} = json_response(conn, 200) - - user = User.get_cached_by_id(user.id) - - conn = - build_conn() - |> assign(:user, user) - |> post("/api/v1/follows", %{"uri" => other_user.nickname}) - - assert %{"id" => id} = json_response(conn, 200) - assert id == to_string(other_user.id) - end - - test "following without reblogs" do - follower = insert(:user) - followed = insert(:user) - other_user = insert(:user) - - conn = - build_conn() - |> assign(:user, follower) - |> post("/api/v1/accounts/#{followed.id}/follow?reblogs=false") - - assert %{"showing_reblogs" => false} = json_response(conn, 200) - - {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hey"}) - {:ok, reblog, _} = CommonAPI.repeat(activity.id, followed) - - conn = - build_conn() - |> assign(:user, User.get_cached_by_id(follower.id)) - |> get("/api/v1/timelines/home") - - assert [] == json_response(conn, 200) - - conn = - build_conn() - |> assign(:user, follower) - |> post("/api/v1/accounts/#{followed.id}/follow?reblogs=true") - - assert %{"showing_reblogs" => true} = json_response(conn, 200) - - conn = - build_conn() - |> assign(:user, User.get_cached_by_id(follower.id)) - |> get("/api/v1/timelines/home") - - expected_activity_id = reblog.id - assert [%{"id" => ^expected_activity_id}] = json_response(conn, 200) - end - - test "following / unfollowing errors" do - user = insert(:user) - - conn = - build_conn() - |> assign(:user, user) - - # self follow - conn_res = post(conn, "/api/v1/accounts/#{user.id}/follow") - assert %{"error" => "Record not found"} = json_response(conn_res, 404) - - # self unfollow - user = User.get_cached_by_id(user.id) - conn_res = post(conn, "/api/v1/accounts/#{user.id}/unfollow") - assert %{"error" => "Record not found"} = json_response(conn_res, 404) - - # self follow via uri - user = User.get_cached_by_id(user.id) - conn_res = post(conn, "/api/v1/follows", %{"uri" => user.nickname}) - assert %{"error" => "Record not found"} = json_response(conn_res, 404) - - # follow non existing user - conn_res = post(conn, "/api/v1/accounts/doesntexist/follow") - assert %{"error" => "Record not found"} = json_response(conn_res, 404) - - # follow non existing user via uri - conn_res = post(conn, "/api/v1/follows", %{"uri" => "doesntexist"}) - assert %{"error" => "Record not found"} = json_response(conn_res, 404) - - # unfollow non existing user - conn_res = post(conn, "/api/v1/accounts/doesntexist/unfollow") - assert %{"error" => "Record not found"} = json_response(conn_res, 404) - end - - describe "mute/unmute" do - test "with notifications", %{conn: conn} do - user = insert(:user) - other_user = insert(:user) - - conn = - conn - |> assign(:user, user) - |> post("/api/v1/accounts/#{other_user.id}/mute") - - response = json_response(conn, 200) - - assert %{"id" => _id, "muting" => true, "muting_notifications" => true} = response - user = User.get_cached_by_id(user.id) - - conn = - build_conn() - |> assign(:user, user) - |> post("/api/v1/accounts/#{other_user.id}/unmute") - - response = json_response(conn, 200) - assert %{"id" => _id, "muting" => false, "muting_notifications" => false} = response - end - - test "without notifications", %{conn: conn} do - user = insert(:user) - other_user = insert(:user) - - conn = - conn - |> assign(:user, user) - |> post("/api/v1/accounts/#{other_user.id}/mute", %{"notifications" => "false"}) - - response = json_response(conn, 200) - - assert %{"id" => _id, "muting" => true, "muting_notifications" => false} = response - user = User.get_cached_by_id(user.id) - - conn = - build_conn() - |> assign(:user, user) - |> post("/api/v1/accounts/#{other_user.id}/unmute") - - response = json_response(conn, 200) - assert %{"id" => _id, "muting" => false, "muting_notifications" => false} = response - end - end - describe "subscribing / unsubscribing" do test "subscribing / unsubscribing to a user", %{conn: conn} do user = insert(:user) @@ -920,27 +410,6 @@ test "getting a list of mutes", %{conn: conn} do assert [%{"id" => ^other_user_id}] = json_response(conn, 200) end - test "blocking / unblocking a user", %{conn: conn} do - user = insert(:user) - other_user = insert(:user) - - conn = - conn - |> assign(:user, user) - |> post("/api/v1/accounts/#{other_user.id}/block") - - assert %{"id" => _id, "blocking" => true} = json_response(conn, 200) - - user = User.get_cached_by_id(user.id) - - conn = - build_conn() - |> assign(:user, user) - |> post("/api/v1/accounts/#{other_user.id}/unblock") - - assert %{"id" => _id, "blocking" => false} = json_response(conn, 200) - end - test "getting a list of blocks", %{conn: conn} do user = insert(:user) other_user = insert(:user) @@ -1017,199 +486,6 @@ test "returns the favorites of a user", %{conn: conn} do assert [] = json_response(third_conn, 200) end - describe "getting favorites timeline of specified user" do - setup do - [current_user, user] = insert_pair(:user, %{info: %{hide_favorites: false}}) - [current_user: current_user, user: user] - end - - test "returns list of statuses favorited by specified user", %{ - conn: conn, - current_user: current_user, - user: user - } do - [activity | _] = insert_pair(:note_activity) - CommonAPI.favorite(activity.id, user) - - response = - conn - |> assign(:user, current_user) - |> get("/api/v1/pleroma/accounts/#{user.id}/favourites") - |> json_response(:ok) - - [like] = response - - assert length(response) == 1 - assert like["id"] == activity.id - end - - test "returns favorites for specified user_id when user is not logged in", %{ - conn: conn, - user: user - } do - activity = insert(:note_activity) - CommonAPI.favorite(activity.id, user) - - response = - conn - |> get("/api/v1/pleroma/accounts/#{user.id}/favourites") - |> json_response(:ok) - - assert length(response) == 1 - end - - test "returns favorited DM only when user is logged in and he is one of recipients", %{ - conn: conn, - current_user: current_user, - user: user - } do - {:ok, direct} = - CommonAPI.post(current_user, %{ - "status" => "Hi @#{user.nickname}!", - "visibility" => "direct" - }) - - CommonAPI.favorite(direct.id, user) - - response = - conn - |> assign(:user, current_user) - |> get("/api/v1/pleroma/accounts/#{user.id}/favourites") - |> json_response(:ok) - - assert length(response) == 1 - - anonymous_response = - conn - |> get("/api/v1/pleroma/accounts/#{user.id}/favourites") - |> json_response(:ok) - - assert Enum.empty?(anonymous_response) - end - - test "does not return others' favorited DM when user is not one of recipients", %{ - conn: conn, - current_user: current_user, - user: user - } do - user_two = insert(:user) - - {:ok, direct} = - CommonAPI.post(user_two, %{ - "status" => "Hi @#{user.nickname}!", - "visibility" => "direct" - }) - - CommonAPI.favorite(direct.id, user) - - response = - conn - |> assign(:user, current_user) - |> get("/api/v1/pleroma/accounts/#{user.id}/favourites") - |> json_response(:ok) - - assert Enum.empty?(response) - end - - test "paginates favorites using since_id and max_id", %{ - conn: conn, - current_user: current_user, - user: user - } do - activities = insert_list(10, :note_activity) - - Enum.each(activities, fn activity -> - CommonAPI.favorite(activity.id, user) - end) - - third_activity = Enum.at(activities, 2) - seventh_activity = Enum.at(activities, 6) - - response = - conn - |> assign(:user, current_user) - |> get("/api/v1/pleroma/accounts/#{user.id}/favourites", %{ - since_id: third_activity.id, - max_id: seventh_activity.id - }) - |> json_response(:ok) - - assert length(response) == 3 - refute third_activity in response - refute seventh_activity in response - end - - test "limits favorites using limit parameter", %{ - conn: conn, - current_user: current_user, - user: user - } do - 7 - |> insert_list(:note_activity) - |> Enum.each(fn activity -> - CommonAPI.favorite(activity.id, user) - end) - - response = - conn - |> assign(:user, current_user) - |> get("/api/v1/pleroma/accounts/#{user.id}/favourites", %{limit: "3"}) - |> json_response(:ok) - - assert length(response) == 3 - end - - test "returns empty response when user does not have any favorited statuses", %{ - conn: conn, - current_user: current_user, - user: user - } do - response = - conn - |> assign(:user, current_user) - |> get("/api/v1/pleroma/accounts/#{user.id}/favourites") - |> json_response(:ok) - - assert Enum.empty?(response) - end - - test "returns 404 error when specified user is not exist", %{conn: conn} do - conn = get(conn, "/api/v1/pleroma/accounts/test/favourites") - - assert json_response(conn, 404) == %{"error" => "Record not found"} - end - - test "returns 403 error when user has hidden own favorites", %{ - conn: conn, - current_user: current_user - } do - user = insert(:user, %{info: %{hide_favorites: true}}) - activity = insert(:note_activity) - CommonAPI.favorite(activity.id, user) - - conn = - conn - |> assign(:user, current_user) - |> get("/api/v1/pleroma/accounts/#{user.id}/favourites") - - assert json_response(conn, 403) == %{"error" => "Can't get favorites"} - end - - test "hides favorites for new users by default", %{conn: conn, current_user: current_user} do - user = insert(:user) - activity = insert(:note_activity) - CommonAPI.favorite(activity.id, user) - - conn = - conn - |> assign(:user, current_user) - |> get("/api/v1/pleroma/accounts/#{user.id}/favourites") - - assert user.info.hide_favorites - assert json_response(conn, 403) == %{"error" => "Can't get favorites"} - end - end - test "get instance information", %{conn: conn} do conn = get(conn, "/api/v1/instance") assert result = json_response(conn, 200) @@ -1294,29 +570,6 @@ test "put settings", %{conn: conn} do assert user.info.settings == %{"programming" => "socks"} end - describe "pinned statuses" do - setup do - user = insert(:user) - {:ok, activity} = CommonAPI.post(user, %{"status" => "HI!!!"}) - - [user: user, activity: activity] - end - - test "returns pinned statuses", %{conn: conn, user: user, activity: activity} do - {:ok, _} = CommonAPI.pin(activity.id, user) - - result = - conn - |> assign(:user, user) - |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true") - |> json_response(200) - - id_str = to_string(activity.id) - - assert [%{"id" => ^id_str, "pinned" => true}] = result - end - end - describe "link headers" do test "preserves parameters in link headers", %{conn: conn} do user = insert(:user) @@ -1349,32 +602,6 @@ test "preserves parameters in link headers", %{conn: conn} do end end - test "accounts fetches correct account for nicknames beginning with numbers", %{conn: conn} do - # Need to set an old-style integer ID to reproduce the problem - # (these are no longer assigned to new accounts but were preserved - # for existing accounts during the migration to flakeIDs) - user_one = insert(:user, %{id: 1212}) - user_two = insert(:user, %{nickname: "#{user_one.id}garbage"}) - - resp_one = - conn - |> get("/api/v1/accounts/#{user_one.id}") - - resp_two = - conn - |> get("/api/v1/accounts/#{user_two.nickname}") - - resp_three = - conn - |> get("/api/v1/accounts/#{user_two.id}") - - acc_one = json_response(resp_one, 200) - acc_two = json_response(resp_two, 200) - acc_three = json_response(resp_three, 200) - refute acc_one == acc_two - assert acc_two == acc_three - end - describe "custom emoji" do test "with tags", %{conn: conn} do [emoji | _body] = diff --git a/test/web/mastodon_api/views/account_view_test.exs b/test/web/mastodon_api/views/account_view_test.exs index d965f76bf..62b2ab7e3 100644 --- a/test/web/mastodon_api/views/account_view_test.exs +++ b/test/web/mastodon_api/views/account_view_test.exs @@ -88,7 +88,7 @@ test "Represent a user account" do } } - assert expected == AccountView.render("account.json", %{user: user}) + assert expected == AccountView.render("show.json", %{user: user}) end test "Represent the user account for the account owner" do @@ -106,7 +106,7 @@ test "Represent the user account for the account owner" do assert %{ pleroma: %{notification_settings: ^notification_settings}, source: %{privacy: ^privacy} - } = AccountView.render("account.json", %{user: user, for: user}) + } = AccountView.render("show.json", %{user: user, for: user}) end test "Represent a Service(bot) account" do @@ -160,13 +160,13 @@ test "Represent a Service(bot) account" do } } - assert expected == AccountView.render("account.json", %{user: user}) + assert expected == AccountView.render("show.json", %{user: user}) end test "Represent a deactivated user for an admin" do admin = insert(:user, %{info: %{is_admin: true}}) deactivated_user = insert(:user, %{info: %{deactivated: true}}) - represented = AccountView.render("account.json", %{user: deactivated_user, for: admin}) + represented = AccountView.render("show.json", %{user: deactivated_user, for: admin}) assert represented[:pleroma][:deactivated] == true end @@ -348,27 +348,27 @@ test "represent an embedded relationship" do } } - assert expected == AccountView.render("account.json", %{user: user, for: other_user}) + assert expected == AccountView.render("show.json", %{user: user, for: other_user}) end test "returns the settings store if the requesting user is the represented user and it's requested specifically" do user = insert(:user, %{info: %User.Info{pleroma_settings_store: %{fe: "test"}}}) result = - AccountView.render("account.json", %{user: user, for: user, with_pleroma_settings: true}) + AccountView.render("show.json", %{user: user, for: user, with_pleroma_settings: true}) assert result.pleroma.settings_store == %{:fe => "test"} - result = AccountView.render("account.json", %{user: user, with_pleroma_settings: true}) + result = AccountView.render("show.json", %{user: user, with_pleroma_settings: true}) assert result.pleroma[:settings_store] == nil - result = AccountView.render("account.json", %{user: user, for: user}) + result = AccountView.render("show.json", %{user: user, for: user}) assert result.pleroma[:settings_store] == nil end test "sanitizes display names" do user = insert(:user, name: " username ") - result = AccountView.render("account.json", %{user: user}) + result = AccountView.render("show.json", %{user: user}) refute result.display_name == " username " end @@ -391,7 +391,7 @@ test "shows when follows/followers stats are hidden and sets follow/follower cou followers_count: 0, following_count: 0, pleroma: %{hide_follows_count: true, hide_followers_count: true} - } = AccountView.render("account.json", %{user: user}) + } = AccountView.render("show.json", %{user: user}) end test "shows when follows/followers are hidden" do @@ -404,7 +404,7 @@ test "shows when follows/followers are hidden" do followers_count: 1, following_count: 1, pleroma: %{hide_follows: true, hide_followers: true} - } = AccountView.render("account.json", %{user: user}) + } = AccountView.render("show.json", %{user: user}) end test "shows actual follower/following count to the account owner" do @@ -416,7 +416,7 @@ test "shows actual follower/following count to the account owner" do assert %{ followers_count: 1, following_count: 1 - } = AccountView.render("account.json", %{user: user, for: user}) + } = AccountView.render("show.json", %{user: user, for: user}) end end @@ -425,65 +425,65 @@ test "shows zero when no follow requests are pending" do user = insert(:user) assert %{follow_requests_count: 0} = - AccountView.render("account.json", %{user: user, for: user}) + AccountView.render("show.json", %{user: user, for: user}) other_user = insert(:user) {:ok, _other_user, user, _activity} = CommonAPI.follow(other_user, user) assert %{follow_requests_count: 0} = - AccountView.render("account.json", %{user: user, for: user}) + AccountView.render("show.json", %{user: user, for: user}) end test "shows non-zero when follow requests are pending" do user = insert(:user, %{info: %{locked: true}}) - assert %{locked: true} = AccountView.render("account.json", %{user: user, for: user}) + assert %{locked: true} = AccountView.render("show.json", %{user: user, for: user}) other_user = insert(:user) {:ok, _other_user, user, _activity} = CommonAPI.follow(other_user, user) assert %{locked: true, follow_requests_count: 1} = - AccountView.render("account.json", %{user: user, for: user}) + AccountView.render("show.json", %{user: user, for: user}) end test "decreases when accepting a follow request" do user = insert(:user, %{info: %{locked: true}}) - assert %{locked: true} = AccountView.render("account.json", %{user: user, for: user}) + assert %{locked: true} = AccountView.render("show.json", %{user: user, for: user}) other_user = insert(:user) {:ok, other_user, user, _activity} = CommonAPI.follow(other_user, user) assert %{locked: true, follow_requests_count: 1} = - AccountView.render("account.json", %{user: user, for: user}) + AccountView.render("show.json", %{user: user, for: user}) {:ok, _other_user} = CommonAPI.accept_follow_request(other_user, user) assert %{locked: true, follow_requests_count: 0} = - AccountView.render("account.json", %{user: user, for: user}) + AccountView.render("show.json", %{user: user, for: user}) end test "decreases when rejecting a follow request" do user = insert(:user, %{info: %{locked: true}}) - assert %{locked: true} = AccountView.render("account.json", %{user: user, for: user}) + assert %{locked: true} = AccountView.render("show.json", %{user: user, for: user}) other_user = insert(:user) {:ok, other_user, user, _activity} = CommonAPI.follow(other_user, user) assert %{locked: true, follow_requests_count: 1} = - AccountView.render("account.json", %{user: user, for: user}) + AccountView.render("show.json", %{user: user, for: user}) {:ok, _other_user} = CommonAPI.reject_follow_request(other_user, user) assert %{locked: true, follow_requests_count: 0} = - AccountView.render("account.json", %{user: user, for: user}) + AccountView.render("show.json", %{user: user, for: user}) end test "shows non-zero when historical unapproved requests are present" do user = insert(:user, %{info: %{locked: true}}) - assert %{locked: true} = AccountView.render("account.json", %{user: user, for: user}) + assert %{locked: true} = AccountView.render("show.json", %{user: user, for: user}) other_user = insert(:user) {:ok, _other_user, user, _activity} = CommonAPI.follow(other_user, user) @@ -491,7 +491,7 @@ test "shows non-zero when historical unapproved requests are present" do {:ok, user} = User.update_info(user, &User.Info.user_upgrade(&1, %{locked: false})) assert %{locked: false, follow_requests_count: 1} = - AccountView.render("account.json", %{user: user, for: user}) + AccountView.render("show.json", %{user: user, for: user}) end end end diff --git a/test/web/mastodon_api/views/notification_view_test.exs b/test/web/mastodon_api/views/notification_view_test.exs index 86268fcfa..81ab82e2b 100644 --- a/test/web/mastodon_api/views/notification_view_test.exs +++ b/test/web/mastodon_api/views/notification_view_test.exs @@ -27,7 +27,7 @@ test "Mention notification" do id: to_string(notification.id), pleroma: %{is_seen: false}, type: "mention", - account: AccountView.render("account.json", %{user: user, for: mentioned_user}), + account: AccountView.render("show.json", %{user: user, for: mentioned_user}), status: StatusView.render("show.json", %{activity: activity, for: mentioned_user}), created_at: Utils.to_masto_date(notification.inserted_at) } @@ -50,7 +50,7 @@ test "Favourite notification" do id: to_string(notification.id), pleroma: %{is_seen: false}, type: "favourite", - account: AccountView.render("account.json", %{user: another_user, for: user}), + account: AccountView.render("show.json", %{user: another_user, for: user}), status: StatusView.render("show.json", %{activity: create_activity, for: user}), created_at: Utils.to_masto_date(notification.inserted_at) } @@ -72,7 +72,7 @@ test "Reblog notification" do id: to_string(notification.id), pleroma: %{is_seen: false}, type: "reblog", - account: AccountView.render("account.json", %{user: another_user, for: user}), + account: AccountView.render("show.json", %{user: another_user, for: user}), status: StatusView.render("show.json", %{activity: reblog_activity, for: user}), created_at: Utils.to_masto_date(notification.inserted_at) } @@ -92,7 +92,7 @@ test "Follow notification" do id: to_string(notification.id), pleroma: %{is_seen: false}, type: "follow", - account: AccountView.render("account.json", %{user: follower, for: followed}), + account: AccountView.render("show.json", %{user: follower, for: followed}), created_at: Utils.to_masto_date(notification.inserted_at) } diff --git a/test/web/mastodon_api/views/status_view_test.exs b/test/web/mastodon_api/views/status_view_test.exs index 683132f8d..8df23d0a8 100644 --- a/test/web/mastodon_api/views/status_view_test.exs +++ b/test/web/mastodon_api/views/status_view_test.exs @@ -103,7 +103,7 @@ test "a note activity" do id: to_string(note.id), uri: object_data["id"], url: Pleroma.Web.Router.Helpers.o_status_url(Pleroma.Web.Endpoint, :notice, note), - account: AccountView.render("account.json", %{user: user}), + account: AccountView.render("show.json", %{user: user}), in_reply_to_id: nil, in_reply_to_account_id: nil, card: nil, diff --git a/test/web/twitter_api/twitter_api_test.exs b/test/web/twitter_api/twitter_api_test.exs index bf1e233f5..d1d61d11a 100644 --- a/test/web/twitter_api/twitter_api_test.exs +++ b/test/web/twitter_api/twitter_api_test.exs @@ -29,8 +29,8 @@ test "it registers a new user and returns the user." do fetched_user = User.get_cached_by_nickname("lain") - assert AccountView.render("account.json", %{user: user}) == - AccountView.render("account.json", %{user: fetched_user}) + assert AccountView.render("show.json", %{user: user}) == + AccountView.render("show.json", %{user: fetched_user}) end test "it registers a new user with empty string in bio and returns the user." do @@ -47,8 +47,8 @@ test "it registers a new user with empty string in bio and returns the user." do fetched_user = User.get_cached_by_nickname("lain") - assert AccountView.render("account.json", %{user: user}) == - AccountView.render("account.json", %{user: fetched_user}) + assert AccountView.render("show.json", %{user: user}) == + AccountView.render("show.json", %{user: fetched_user}) end test "it sends confirmation email if :account_activation_required is specified in instance config" do @@ -148,8 +148,8 @@ test "returns user on success" do assert invite.used == true - assert AccountView.render("account.json", %{user: user}) == - AccountView.render("account.json", %{user: fetched_user}) + assert AccountView.render("show.json", %{user: user}) == + AccountView.render("show.json", %{user: fetched_user}) end test "returns error on invalid token" do @@ -213,8 +213,8 @@ test "returns error on expired token" do {:ok, user} = TwitterAPI.register_user(data) fetched_user = User.get_cached_by_nickname("vinny") - assert AccountView.render("account.json", %{user: user}) == - AccountView.render("account.json", %{user: fetched_user}) + assert AccountView.render("show.json", %{user: user}) == + AccountView.render("show.json", %{user: fetched_user}) end {:ok, data: data, check_fn: check_fn} @@ -288,8 +288,8 @@ test "returns user on success, after him registration fails" do assert invite.used == true - assert AccountView.render("account.json", %{user: user}) == - AccountView.render("account.json", %{user: fetched_user}) + assert AccountView.render("show.json", %{user: user}) == + AccountView.render("show.json", %{user: fetched_user}) data = %{ "nickname" => "GrimReaper", @@ -339,8 +339,8 @@ test "returns user on success" do refute invite.used - assert AccountView.render("account.json", %{user: user}) == - AccountView.render("account.json", %{user: fetched_user}) + assert AccountView.render("show.json", %{user: user}) == + AccountView.render("show.json", %{user: fetched_user}) end test "error after max uses" do @@ -363,8 +363,8 @@ test "error after max uses" do invite = Repo.get_by(UserInviteToken, token: invite.token) assert invite.used == true - assert AccountView.render("account.json", %{user: user}) == - AccountView.render("account.json", %{user: fetched_user}) + assert AccountView.render("show.json", %{user: user}) == + AccountView.render("show.json", %{user: fetched_user}) data = %{ "nickname" => "GrimReaper", From 3c5ecb70b45ae3db193e58b9a3b4a6100b411e4d Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Mon, 30 Sep 2019 14:28:12 +0700 Subject: [PATCH 02/31] Add PleromaAPI.AccountController --- lib/pleroma/web/controller_helper.ex | 7 + .../controllers/account_controller.ex | 95 +---- .../controllers/mastodon_api_controller.ex | 71 +--- .../controllers/account_controller.ex | 143 +++++++ lib/pleroma/web/router.ex | 39 +- .../controllers/account_controller_test.exs | 212 ---------- .../mastodon_api_controller_test.exs | 179 -------- .../controllers/account_controller_test.exs | 395 ++++++++++++++++++ .../emoji_api_controller_test.exs | 4 + .../pleroma_api_controller_test.exs | 0 10 files changed, 579 insertions(+), 566 deletions(-) create mode 100644 lib/pleroma/web/pleroma_api/controllers/account_controller.ex create mode 100644 test/web/pleroma_api/controllers/account_controller_test.exs rename test/web/pleroma_api/{ => controllers}/emoji_api_controller_test.exs (98%) rename test/web/pleroma_api/{ => controllers}/pleroma_api_controller_test.exs (100%) diff --git a/lib/pleroma/web/controller_helper.ex b/lib/pleroma/web/controller_helper.ex index e90bf842e..83b884ba9 100644 --- a/lib/pleroma/web/controller_helper.ex +++ b/lib/pleroma/web/controller_helper.ex @@ -68,4 +68,11 @@ def add_link_headers(conn, activities, extra_params \\ %{}) do conn end end + + def assign_account_by_id(%{params: %{"id" => id}} = conn, _) do + case Pleroma.User.get_cached_by_id(id) do + %Pleroma.User{} = account -> assign(conn, :account, account) + nil -> Pleroma.Web.MastodonAPI.FallbackController.call(conn, {:error, :not_found}) |> halt() + end + end end diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex index 844de2e79..38d53fd10 100644 --- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex @@ -5,7 +5,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do use Pleroma.Web, :controller - import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2, truthy_param?: 1] + import Pleroma.Web.ControllerHelper, + only: [add_link_headers: 2, truthy_param?: 1, assign_account_by_id: 2, json_response: 3] alias Pleroma.User alias Pleroma.Web.CommonAPI @@ -15,13 +16,11 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do alias Pleroma.Web.MastodonAPI.ListView alias Pleroma.Plugs.RateLimiter - require Pleroma.Constants - @relations ~w(follow unfollow)a plug(RateLimiter, {:relations_id_action, params: ["id", "uri"]} when action in @relations) plug(RateLimiter, :relations_actions when action in @relations) - plug(:assign_account when action not in [:show, :statuses, :follows]) + plug(:assign_account_by_id when action not in [:show, :statuses]) action_fallback(Pleroma.Web.MastodonAPI.FallbackController) @@ -85,60 +84,6 @@ def lists(%{assigns: %{user: user, account: account}} = conn, _params) do |> render("index.json", lists: lists) end - @doc "GET /api/v1/pleroma/accounts/:id/favourites" - def favourites(%{assigns: %{account: %{info: %{hide_favorites: true}}}} = conn, _params) do - render_error(conn, :forbidden, "Can't get favorites") - end - - def favourites(%{assigns: %{user: for_user, account: user}} = conn, params) do - params = - params - |> Map.put("type", "Create") - |> Map.put("favorited_by", user.ap_id) - |> Map.put("blocking_user", for_user) - - recipients = - if for_user do - [Pleroma.Constants.as_public()] ++ [for_user.ap_id | for_user.following] - else - [Pleroma.Constants.as_public()] - end - - activities = - recipients - |> ActivityPub.fetch_activities(params) - |> Enum.reverse() - - conn - |> add_link_headers(activities) - |> put_view(StatusView) - |> render("index.json", activities: activities, for: for_user, as: :activity) - end - - @doc "POST /api/v1/pleroma/accounts/:id/subscribe" - def subscribe(%{assigns: %{user: user, account: subscription_target}} = conn, _params) do - with {:ok, subscription_target} <- User.subscribe(user, subscription_target) do - render(conn, "relationship.json", user: user, target: subscription_target) - else - {:error, message} -> - conn - |> put_status(:forbidden) - |> json(%{error: message}) - end - end - - @doc "POST /api/v1/pleroma/accounts/:id/unsubscribe" - def unsubscribe(%{assigns: %{user: user, account: subscription_target}} = conn, _params) do - with {:ok, subscription_target} <- User.unsubscribe(user, subscription_target) do - render(conn, "relationship.json", user: user, target: subscription_target) - else - {:error, message} -> - conn - |> put_status(:forbidden) - |> json(%{error: message}) - end - end - @doc "POST /api/v1/accounts/:id/follow" def follow(%{assigns: %{user: %{id: id}, account: %{id: id}}}, _params) do {:error, :not_found} @@ -148,14 +93,11 @@ def follow(%{assigns: %{user: follower, account: followed}} = conn, _params) do with {:ok, follower} <- MastodonAPI.follow(follower, followed, conn.params) do render(conn, "relationship.json", user: follower, target: followed) else - {:error, message} -> - conn - |> put_status(:forbidden) - |> json(%{error: message}) + {:error, message} -> json_response(conn, :forbidden, %{error: message}) end end - @doc "POST /api/v1/pleroma/:id/unfollow" + @doc "POST /api/v1/accounts/:id/unfollow" def unfollow(%{assigns: %{user: %{id: id}, account: %{id: id}}}, _params) do {:error, :not_found} end @@ -173,10 +115,7 @@ def mute(%{assigns: %{user: muter, account: muted}} = conn, params) do with {:ok, muter} <- User.mute(muter, muted, notifications?) do render(conn, "relationship.json", user: muter, target: muted) else - {:error, message} -> - conn - |> put_status(:forbidden) - |> json(%{error: message}) + {:error, message} -> json_response(conn, :forbidden, %{error: message}) end end @@ -185,10 +124,7 @@ def unmute(%{assigns: %{user: muter, account: muted}} = conn, _params) do with {:ok, muter} <- User.unmute(muter, muted) do render(conn, "relationship.json", user: muter, target: muted) else - {:error, message} -> - conn - |> put_status(:forbidden) - |> json(%{error: message}) + {:error, message} -> json_response(conn, :forbidden, %{error: message}) end end @@ -198,10 +134,7 @@ def block(%{assigns: %{user: blocker, account: blocked}} = conn, _params) do {:ok, _activity} <- ActivityPub.block(blocker, blocked) do render(conn, "relationship.json", user: blocker, target: blocked) else - {:error, message} -> - conn - |> put_status(:forbidden) - |> json(%{error: message}) + {:error, message} -> json_response(conn, :forbidden, %{error: message}) end end @@ -211,17 +144,7 @@ def unblock(%{assigns: %{user: blocker, account: blocked}} = conn, _params) do {:ok, _activity} <- ActivityPub.unblock(blocker, blocked) do render(conn, "relationship.json", user: blocker, target: blocked) else - {:error, message} -> - conn - |> put_status(:forbidden) - |> json(%{error: message}) - end - end - - defp assign_account(%{params: %{"id" => id}} = conn, _) do - case User.get_cached_by_id(id) do - %User{} = account -> assign(conn, :account, account) - nil -> Pleroma.Web.MastodonAPI.FallbackController.call(conn, {:error, :not_found}) |> halt() + {:error, message} -> json_response(conn, :forbidden, %{error: message}) end end end 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 394599146..197316794 100644 --- a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex @@ -5,10 +5,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do use Pleroma.Web, :controller - import Pleroma.Web.ControllerHelper, - only: [json_response: 3, add_link_headers: 2, truthy_param?: 1] + import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2, truthy_param?: 1] - alias Ecto.Changeset alias Pleroma.Activity alias Pleroma.Bookmark alias Pleroma.Config @@ -40,7 +38,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do plug(RateLimiter, :app_account_creation when action == :account_register) plug(RateLimiter, :search when action in [:search, :search2, :account_search]) plug(RateLimiter, :password_reset when action == :password_reset) - plug(RateLimiter, :account_confirmation_resend when action == :account_confirmation_resend) @local_mastodon_name "Mastodon-Local" @@ -167,61 +164,6 @@ def update_credentials(%{assigns: %{user: user}} = conn, params) do end end - def update_avatar(%{assigns: %{user: user}} = conn, %{"img" => ""}) do - change = Changeset.change(user, %{avatar: nil}) - {:ok, user} = User.update_and_set_cache(change) - CommonAPI.update(user) - - json(conn, %{url: nil}) - end - - def update_avatar(%{assigns: %{user: user}} = conn, params) do - {:ok, object} = ActivityPub.upload(params, type: :avatar) - change = Changeset.change(user, %{avatar: object.data}) - {:ok, user} = User.update_and_set_cache(change) - CommonAPI.update(user) - %{"url" => [%{"href" => href} | _]} = object.data - - json(conn, %{url: href}) - end - - def update_banner(%{assigns: %{user: user}} = conn, %{"banner" => ""}) do - new_info = %{"banner" => %{}} - - with {:ok, user} <- User.update_info(user, &User.Info.profile_update(&1, new_info)) do - CommonAPI.update(user) - json(conn, %{url: nil}) - end - end - - def update_banner(%{assigns: %{user: user}} = conn, params) do - with {:ok, object} <- ActivityPub.upload(%{"img" => params["banner"]}, type: :banner), - new_info <- %{"banner" => object.data}, - {:ok, user} <- User.update_info(user, &User.Info.profile_update(&1, new_info)) do - CommonAPI.update(user) - %{"url" => [%{"href" => href} | _]} = object.data - - json(conn, %{url: href}) - end - end - - def update_background(%{assigns: %{user: user}} = conn, %{"img" => ""}) do - new_info = %{"background" => %{}} - - with {:ok, _user} <- User.update_info(user, &User.Info.profile_update(&1, new_info)) do - json(conn, %{url: nil}) - end - end - - def update_background(%{assigns: %{user: user}} = conn, params) do - with {:ok, object} <- ActivityPub.upload(params, type: :background), - new_info <- %{"background" => object.data}, - {:ok, _user} <- User.update_info(user, &User.Info.profile_update(&1, new_info)) do - %{"url" => [%{"href" => href} | _]} = object.data - - json(conn, %{url: href}) - end - end def verify_credentials(%{assigns: %{user: user}} = conn, _) do chat_token = Phoenix.Token.sign(conn, "user socket", user.id) @@ -236,7 +178,6 @@ def verify_credentials(%{assigns: %{user: user}} = conn, _) do json(conn, account) end - def verify_app_credentials(%{assigns: %{user: _user, token: token}} = conn, _) do with %Token{app: %App{} = app} <- Repo.preload(token, :app) do conn @@ -767,16 +708,6 @@ def password_reset(conn, params) do end end - def account_confirmation_resend(conn, params) do - nickname_or_email = params["email"] || params["nickname"] - - with %User{} = user <- User.get_by_nickname_or_email(nickname_or_email), - {:ok, _} <- User.try_send_confirmation_email(user) do - conn - |> json_response(:no_content, "") - end - end - def try_render(conn, target, params) when is_binary(target) do case render(conn, target, params) do diff --git a/lib/pleroma/web/pleroma_api/controllers/account_controller.ex b/lib/pleroma/web/pleroma_api/controllers/account_controller.ex new file mode 100644 index 000000000..63c44086c --- /dev/null +++ b/lib/pleroma/web/pleroma_api/controllers/account_controller.ex @@ -0,0 +1,143 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.PleromaAPI.AccountController do + use Pleroma.Web, :controller + + import Pleroma.Web.ControllerHelper, + only: [json_response: 3, add_link_headers: 2, assign_account_by_id: 2] + + alias Ecto.Changeset + alias Pleroma.Plugs.RateLimiter + alias Pleroma.User + alias Pleroma.Web.ActivityPub.ActivityPub + alias Pleroma.Web.CommonAPI + alias Pleroma.Web.MastodonAPI.StatusView + + require Pleroma.Constants + + plug(RateLimiter, :account_confirmation_resend when action == :confirmation_resend) + plug(:assign_account_by_id when action in [:favourites, :subscribe, :unsubscribe]) + plug(:put_view, Pleroma.Web.MastodonAPI.AccountView) + + @doc "POST /api/v1/pleroma/accounts/confirmation_resend" + def confirmation_resend(conn, params) do + nickname_or_email = params["email"] || params["nickname"] + + with %User{} = user <- User.get_by_nickname_or_email(nickname_or_email), + {:ok, _} <- User.try_send_confirmation_email(user) do + json_response(conn, :no_content, "") + end + end + + @doc "PATCH /api/v1/pleroma/accounts/update_avatar" + def update_avatar(%{assigns: %{user: user}} = conn, %{"img" => ""}) do + {:ok, user} = + user + |> Changeset.change(%{avatar: nil}) + |> User.update_and_set_cache() + + CommonAPI.update(user) + + json(conn, %{url: nil}) + end + + def update_avatar(%{assigns: %{user: user}} = conn, params) do + {:ok, %{data: data}} = ActivityPub.upload(params, type: :avatar) + {:ok, user} = user |> Changeset.change(%{avatar: data}) |> User.update_and_set_cache() + %{"url" => [%{"href" => href} | _]} = data + + CommonAPI.update(user) + + json(conn, %{url: href}) + end + + @doc "PATCH /api/v1/pleroma/accounts/update_banner" + def update_banner(%{assigns: %{user: user}} = conn, %{"banner" => ""}) do + new_info = %{"banner" => %{}} + + with {:ok, user} <- User.update_info(user, &User.Info.profile_update(&1, new_info)) do + CommonAPI.update(user) + json(conn, %{url: nil}) + end + end + + def update_banner(%{assigns: %{user: user}} = conn, params) do + with {:ok, object} <- ActivityPub.upload(%{"img" => params["banner"]}, type: :banner), + new_info <- %{"banner" => object.data}, + {:ok, user} <- User.update_info(user, &User.Info.profile_update(&1, new_info)) do + CommonAPI.update(user) + %{"url" => [%{"href" => href} | _]} = object.data + + json(conn, %{url: href}) + end + end + + @doc "PATCH /api/v1/pleroma/accounts/update_background" + def update_background(%{assigns: %{user: user}} = conn, %{"img" => ""}) do + new_info = %{"background" => %{}} + + with {:ok, _user} <- User.update_info(user, &User.Info.profile_update(&1, new_info)) do + json(conn, %{url: nil}) + end + end + + def update_background(%{assigns: %{user: user}} = conn, params) do + with {:ok, object} <- ActivityPub.upload(params, type: :background), + new_info <- %{"background" => object.data}, + {:ok, _user} <- User.update_info(user, &User.Info.profile_update(&1, new_info)) do + %{"url" => [%{"href" => href} | _]} = object.data + + json(conn, %{url: href}) + end + end + + @doc "GET /api/v1/pleroma/accounts/:id/favourites" + def favourites(%{assigns: %{account: %{info: %{hide_favorites: true}}}} = conn, _params) do + render_error(conn, :forbidden, "Can't get favorites") + end + + def favourites(%{assigns: %{user: for_user, account: user}} = conn, params) do + params = + params + |> Map.put("type", "Create") + |> Map.put("favorited_by", user.ap_id) + |> Map.put("blocking_user", for_user) + + recipients = + if for_user do + [Pleroma.Constants.as_public()] ++ [for_user.ap_id | for_user.following] + else + [Pleroma.Constants.as_public()] + end + + activities = + recipients + |> ActivityPub.fetch_activities(params) + |> Enum.reverse() + + conn + |> add_link_headers(activities) + |> put_view(StatusView) + |> render("index.json", activities: activities, for: for_user, as: :activity) + end + + @doc "POST /api/v1/pleroma/accounts/:id/subscribe" + def subscribe(%{assigns: %{user: user, account: subscription_target}} = conn, _params) do + with {:ok, subscription_target} <- User.subscribe(user, subscription_target) do + render(conn, "relationship.json", user: user, target: subscription_target) + else + {:error, message} -> json_response(conn, :forbidden, %{error: message}) + end + end + + @doc "POST /api/v1/pleroma/accounts/:id/unsubscribe" + def unsubscribe(%{assigns: %{user: user, account: subscription_target}} = conn, _params) do + with {:ok, subscription_target} <- User.unsubscribe(user, subscription_target) do + render(conn, "relationship.json", user: user, target: subscription_target) + else + {:error, message} -> json_response(conn, :forbidden, %{error: message}) + end + end +end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index a57bc75d7..5c3fe34e5 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -287,24 +287,40 @@ defmodule Pleroma.Web.Router do end scope "/api/v1/pleroma", Pleroma.Web.PleromaAPI do - pipe_through(:authenticated_api) - scope [] do + pipe_through(:authenticated_api) pipe_through(:oauth_read) get("/conversations/:id/statuses", PleromaAPIController, :conversation_statuses) get("/conversations/:id", PleromaAPIController, :conversation) end scope [] do + pipe_through(:authenticated_api) pipe_through(:oauth_write) patch("/conversations/:id", PleromaAPIController, :update_conversation) post("/notifications/read", PleromaAPIController, :read_notification) + + patch("/accounts/update_avatar", AccountController, :update_avatar) + patch("/accounts/update_banner", AccountController, :update_banner) + patch("/accounts/update_background", AccountController, :update_background) + post("/scrobble", ScrobbleController, :new_scrobble) end scope [] do - pipe_through(:oauth_write) - post("/scrobble", ScrobbleController, :new_scrobble) + pipe_through(:api) + pipe_through(:oauth_read_or_public) + get("/accounts/:id/favourites", AccountController, :favourites) end + + scope [] do + pipe_through(:authenticated_api) + pipe_through(:oauth_follow) + + post("/accounts/:id/subscribe", AccountController, :subscribe) + post("/accounts/:id/unsubscribe", AccountController, :unsubscribe) + end + + post("/accounts/confirmation_resend", AccountController, :confirmation_resend) end scope "/api/v1/pleroma", Pleroma.Web.PleromaAPI do @@ -400,10 +416,6 @@ defmodule Pleroma.Web.Router do put("/filters/:id", FilterController, :update) delete("/filters/:id", FilterController, :delete) - patch("/pleroma/accounts/update_avatar", MastodonAPIController, :update_avatar) - patch("/pleroma/accounts/update_banner", MastodonAPIController, :update_banner) - patch("/pleroma/accounts/update_background", MastodonAPIController, :update_background) - get("/pleroma/mascot", MastodonAPIController, :get_mascot) put("/pleroma/mascot", MastodonAPIController, :set_mascot) @@ -426,9 +438,6 @@ defmodule Pleroma.Web.Router do post("/domain_blocks", DomainBlockController, :create) delete("/domain_blocks", DomainBlockController, :delete) - - post("/pleroma/accounts/:id/subscribe", AccountController, :subscribe) - post("/pleroma/accounts/:id/unsubscribe", AccountController, :unsubscribe) end scope [] do @@ -467,12 +476,6 @@ defmodule Pleroma.Web.Router do get("/accounts/search", SearchController, :account_search) - post( - "/pleroma/accounts/confirmation_resend", - MastodonAPIController, - :account_confirmation_resend - ) - scope [] do pipe_through(:oauth_read_or_public) @@ -492,8 +495,6 @@ defmodule Pleroma.Web.Router do get("/accounts/:id", AccountController, :show) get("/search", SearchController, :search) - - get("/pleroma/accounts/:id/favourites", AccountController, :favourites) end end diff --git a/test/web/mastodon_api/controllers/account_controller_test.exs b/test/web/mastodon_api/controllers/account_controller_test.exs index 6cf929011..32ccc5351 100644 --- a/test/web/mastodon_api/controllers/account_controller_test.exs +++ b/test/web/mastodon_api/controllers/account_controller_test.exs @@ -552,199 +552,6 @@ test "without notifications", %{conn: conn} do end end - describe "getting favorites timeline of specified user" do - setup do - [current_user, user] = insert_pair(:user, %{info: %{hide_favorites: false}}) - [current_user: current_user, user: user] - end - - test "returns list of statuses favorited by specified user", %{ - conn: conn, - current_user: current_user, - user: user - } do - [activity | _] = insert_pair(:note_activity) - CommonAPI.favorite(activity.id, user) - - response = - conn - |> assign(:user, current_user) - |> get("/api/v1/pleroma/accounts/#{user.id}/favourites") - |> json_response(:ok) - - [like] = response - - assert length(response) == 1 - assert like["id"] == activity.id - end - - test "returns favorites for specified user_id when user is not logged in", %{ - conn: conn, - user: user - } do - activity = insert(:note_activity) - CommonAPI.favorite(activity.id, user) - - response = - conn - |> get("/api/v1/pleroma/accounts/#{user.id}/favourites") - |> json_response(:ok) - - assert length(response) == 1 - end - - test "returns favorited DM only when user is logged in and he is one of recipients", %{ - conn: conn, - current_user: current_user, - user: user - } do - {:ok, direct} = - CommonAPI.post(current_user, %{ - "status" => "Hi @#{user.nickname}!", - "visibility" => "direct" - }) - - CommonAPI.favorite(direct.id, user) - - response = - conn - |> assign(:user, current_user) - |> get("/api/v1/pleroma/accounts/#{user.id}/favourites") - |> json_response(:ok) - - assert length(response) == 1 - - anonymous_response = - conn - |> get("/api/v1/pleroma/accounts/#{user.id}/favourites") - |> json_response(:ok) - - assert Enum.empty?(anonymous_response) - end - - test "does not return others' favorited DM when user is not one of recipients", %{ - conn: conn, - current_user: current_user, - user: user - } do - user_two = insert(:user) - - {:ok, direct} = - CommonAPI.post(user_two, %{ - "status" => "Hi @#{user.nickname}!", - "visibility" => "direct" - }) - - CommonAPI.favorite(direct.id, user) - - response = - conn - |> assign(:user, current_user) - |> get("/api/v1/pleroma/accounts/#{user.id}/favourites") - |> json_response(:ok) - - assert Enum.empty?(response) - end - - test "paginates favorites using since_id and max_id", %{ - conn: conn, - current_user: current_user, - user: user - } do - activities = insert_list(10, :note_activity) - - Enum.each(activities, fn activity -> - CommonAPI.favorite(activity.id, user) - end) - - third_activity = Enum.at(activities, 2) - seventh_activity = Enum.at(activities, 6) - - response = - conn - |> assign(:user, current_user) - |> get("/api/v1/pleroma/accounts/#{user.id}/favourites", %{ - since_id: third_activity.id, - max_id: seventh_activity.id - }) - |> json_response(:ok) - - assert length(response) == 3 - refute third_activity in response - refute seventh_activity in response - end - - test "limits favorites using limit parameter", %{ - conn: conn, - current_user: current_user, - user: user - } do - 7 - |> insert_list(:note_activity) - |> Enum.each(fn activity -> - CommonAPI.favorite(activity.id, user) - end) - - response = - conn - |> assign(:user, current_user) - |> get("/api/v1/pleroma/accounts/#{user.id}/favourites", %{limit: "3"}) - |> json_response(:ok) - - assert length(response) == 3 - end - - test "returns empty response when user does not have any favorited statuses", %{ - conn: conn, - current_user: current_user, - user: user - } do - response = - conn - |> assign(:user, current_user) - |> get("/api/v1/pleroma/accounts/#{user.id}/favourites") - |> json_response(:ok) - - assert Enum.empty?(response) - end - - test "returns 404 error when specified user is not exist", %{conn: conn} do - conn = get(conn, "/api/v1/pleroma/accounts/test/favourites") - - assert json_response(conn, 404) == %{"error" => "Record not found"} - end - - test "returns 403 error when user has hidden own favorites", %{ - conn: conn, - current_user: current_user - } do - user = insert(:user, %{info: %{hide_favorites: true}}) - activity = insert(:note_activity) - CommonAPI.favorite(activity.id, user) - - conn = - conn - |> assign(:user, current_user) - |> get("/api/v1/pleroma/accounts/#{user.id}/favourites") - - assert json_response(conn, 403) == %{"error" => "Can't get favorites"} - end - - test "hides favorites for new users by default", %{conn: conn, current_user: current_user} do - user = insert(:user) - activity = insert(:note_activity) - CommonAPI.favorite(activity.id, user) - - conn = - conn - |> assign(:user, current_user) - |> get("/api/v1/pleroma/accounts/#{user.id}/favourites") - - assert user.info.hide_favorites - assert json_response(conn, 403) == %{"error" => "Can't get favorites"} - end - end - describe "pinned statuses" do setup do user = insert(:user) @@ -768,25 +575,6 @@ test "returns pinned statuses", %{conn: conn, user: user, activity: activity} do end end - test "subscribing / unsubscribing to a user", %{conn: conn} do - user = insert(:user) - subscription_target = insert(:user) - - conn = - conn - |> assign(:user, user) - |> post("/api/v1/pleroma/accounts/#{subscription_target.id}/subscribe") - - assert %{"id" => _id, "subscribing" => true} = json_response(conn, 200) - - conn = - build_conn() - |> assign(:user, user) - |> post("/api/v1/pleroma/accounts/#{subscription_target.id}/unsubscribe") - - assert %{"id" => _id, "subscribing" => false} = json_response(conn, 200) - end - test "blocking / unblocking a user", %{conn: conn} do user = insert(:user) other_user = insert(:user) diff --git a/test/web/mastodon_api/mastodon_api_controller_test.exs b/test/web/mastodon_api/mastodon_api_controller_test.exs index 7cdefdcdd..671f9f254 100644 --- a/test/web/mastodon_api/mastodon_api_controller_test.exs +++ b/test/web/mastodon_api/mastodon_api_controller_test.exs @@ -23,8 +23,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do import Swoosh.TestAssertions import Tesla.Mock - @image "data:image/gif;base64,R0lGODlhEAAQAMQAAORHHOVSKudfOulrSOp3WOyDZu6QdvCchPGolfO0o/XBs/fNwfjZ0frl3/zy7////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkAABAALAAAAAAQABAAAAVVICSOZGlCQAosJ6mu7fiyZeKqNKToQGDsM8hBADgUXoGAiqhSvp5QAnQKGIgUhwFUYLCVDFCrKUE1lBavAViFIDlTImbKC5Gm2hB0SlBCBMQiB0UjIQA7" - setup do mock(fn env -> apply(HttpRequestMock, :request, [env]) end) :ok @@ -80,101 +78,6 @@ test "apps/verify_credentials", %{conn: conn} do assert expected == json_response(conn, 200) end - test "user avatar can be set", %{conn: conn} do - user = insert(:user) - avatar_image = File.read!("test/fixtures/avatar_data_uri") - - conn = - conn - |> assign(:user, user) - |> patch("/api/v1/pleroma/accounts/update_avatar", %{img: avatar_image}) - - user = refresh_record(user) - - assert %{ - "name" => _, - "type" => _, - "url" => [ - %{ - "href" => _, - "mediaType" => _, - "type" => _ - } - ] - } = user.avatar - - assert %{"url" => _} = json_response(conn, 200) - end - - test "user avatar can be reset", %{conn: conn} do - user = insert(:user) - - conn = - conn - |> assign(:user, user) - |> patch("/api/v1/pleroma/accounts/update_avatar", %{img: ""}) - - user = User.get_cached_by_id(user.id) - - assert user.avatar == nil - - assert %{"url" => nil} = json_response(conn, 200) - end - - test "can set profile banner", %{conn: conn} do - user = insert(:user) - - conn = - conn - |> assign(:user, user) - |> patch("/api/v1/pleroma/accounts/update_banner", %{"banner" => @image}) - - user = refresh_record(user) - assert user.info.banner["type"] == "Image" - - assert %{"url" => _} = json_response(conn, 200) - end - - test "can reset profile banner", %{conn: conn} do - user = insert(:user) - - conn = - conn - |> assign(:user, user) - |> patch("/api/v1/pleroma/accounts/update_banner", %{"banner" => ""}) - - user = refresh_record(user) - assert user.info.banner == %{} - - assert %{"url" => nil} = json_response(conn, 200) - end - - test "background image can be set", %{conn: conn} do - user = insert(:user) - - conn = - conn - |> assign(:user, user) - |> patch("/api/v1/pleroma/accounts/update_background", %{"img" => @image}) - - user = refresh_record(user) - assert user.info.background["type"] == "Image" - assert %{"url" => _} = json_response(conn, 200) - end - - test "background image can be reset", %{conn: conn} do - user = insert(:user) - - conn = - conn - |> assign(:user, user) - |> patch("/api/v1/pleroma/accounts/update_background", %{"img" => ""}) - - user = refresh_record(user) - assert user.info.background == %{} - assert %{"url" => nil} = json_response(conn, 200) - end - test "creates an oauth app", %{conn: conn} do user = insert(:user) app_attrs = build(:oauth_app) @@ -348,52 +251,6 @@ test "mascot retrieving", %{conn: conn} do assert url =~ "an_image" end end - describe "subscribing / unsubscribing" do - test "subscribing / unsubscribing to a user", %{conn: conn} do - user = insert(:user) - subscription_target = insert(:user) - - conn = - conn - |> assign(:user, user) - |> post("/api/v1/pleroma/accounts/#{subscription_target.id}/subscribe") - - assert %{"id" => _id, "subscribing" => true} = json_response(conn, 200) - - conn = - build_conn() - |> assign(:user, user) - |> post("/api/v1/pleroma/accounts/#{subscription_target.id}/unsubscribe") - - assert %{"id" => _id, "subscribing" => false} = json_response(conn, 200) - end - end - - describe "subscribing" do - test "returns 404 when subscription_target not found", %{conn: conn} do - user = insert(:user) - - conn = - conn - |> assign(:user, user) - |> post("/api/v1/pleroma/accounts/target_id/subscribe") - - assert %{"error" => "Record not found"} = json_response(conn, 404) - end - end - - describe "unsubscribing" do - test "returns 404 when subscription_target not found", %{conn: conn} do - user = insert(:user) - - conn = - conn - |> assign(:user, user) - |> post("/api/v1/pleroma/accounts/target_id/unsubscribe") - - assert %{"error" => "Record not found"} = json_response(conn, 404) - end - end test "getting a list of mutes", %{conn: conn} do user = insert(:user) @@ -1088,42 +945,6 @@ test "it returns 400 when user is not local", %{conn: conn, user: user} do end end - describe "POST /api/v1/pleroma/accounts/confirmation_resend" do - setup do - {:ok, user} = - insert(:user) - |> User.change_info(&User.Info.confirmation_changeset(&1, need_confirmation: true)) - |> Repo.update() - - assert user.info.confirmation_pending - - [user: user] - end - - clear_config([:instance, :account_activation_required]) do - Config.put([:instance, :account_activation_required], true) - end - - test "resend account confirmation email", %{conn: conn, user: user} do - conn - |> assign(:user, user) - |> post("/api/v1/pleroma/accounts/confirmation_resend?email=#{user.email}") - |> json_response(:no_content) - - ObanHelpers.perform_all() - - email = Pleroma.Emails.UserEmail.account_confirmation_email(user) - notify_email = Config.get([:instance, :notify_email]) - instance_name = Config.get([:instance, :name]) - - assert_email_sent( - from: {instance_name, notify_email}, - to: {user.name, user.email}, - html_body: email.html_body - ) - end - end - describe "GET /api/v1/suggestions" do setup do user = insert(:user) diff --git a/test/web/pleroma_api/controllers/account_controller_test.exs b/test/web/pleroma_api/controllers/account_controller_test.exs new file mode 100644 index 000000000..3b4665afd --- /dev/null +++ b/test/web/pleroma_api/controllers/account_controller_test.exs @@ -0,0 +1,395 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.PleromaAPI.AccountControllerTest do + use Pleroma.Web.ConnCase + + alias Pleroma.Config + alias Pleroma.Repo + alias Pleroma.Tests.ObanHelpers + alias Pleroma.User + alias Pleroma.Web.CommonAPI + + import Pleroma.Factory + import Swoosh.TestAssertions + + @image "data:image/gif;base64,R0lGODlhEAAQAMQAAORHHOVSKudfOulrSOp3WOyDZu6QdvCchPGolfO0o/XBs/fNwfjZ0frl3/zy7////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkAABAALAAAAAAQABAAAAVVICSOZGlCQAosJ6mu7fiyZeKqNKToQGDsM8hBADgUXoGAiqhSvp5QAnQKGIgUhwFUYLCVDFCrKUE1lBavAViFIDlTImbKC5Gm2hB0SlBCBMQiB0UjIQA7" + + describe "POST /api/v1/pleroma/accounts/confirmation_resend" do + setup do + {:ok, user} = + insert(:user) + |> User.change_info(&User.Info.confirmation_changeset(&1, need_confirmation: true)) + |> Repo.update() + + assert user.info.confirmation_pending + + [user: user] + end + + clear_config([:instance, :account_activation_required]) do + Config.put([:instance, :account_activation_required], true) + end + + test "resend account confirmation email", %{conn: conn, user: user} do + conn + |> assign(:user, user) + |> post("/api/v1/pleroma/accounts/confirmation_resend?email=#{user.email}") + |> json_response(:no_content) + + ObanHelpers.perform_all() + + email = Pleroma.Emails.UserEmail.account_confirmation_email(user) + notify_email = Config.get([:instance, :notify_email]) + instance_name = Config.get([:instance, :name]) + + assert_email_sent( + from: {instance_name, notify_email}, + to: {user.name, user.email}, + html_body: email.html_body + ) + end + end + + describe "PATCH /api/v1/pleroma/accounts/update_avatar" do + test "user avatar can be set", %{conn: conn} do + user = insert(:user) + avatar_image = File.read!("test/fixtures/avatar_data_uri") + + conn = + conn + |> assign(:user, user) + |> patch("/api/v1/pleroma/accounts/update_avatar", %{img: avatar_image}) + + user = refresh_record(user) + + assert %{ + "name" => _, + "type" => _, + "url" => [ + %{ + "href" => _, + "mediaType" => _, + "type" => _ + } + ] + } = user.avatar + + assert %{"url" => _} = json_response(conn, 200) + end + + test "user avatar can be reset", %{conn: conn} do + user = insert(:user) + + conn = + conn + |> assign(:user, user) + |> patch("/api/v1/pleroma/accounts/update_avatar", %{img: ""}) + + user = User.get_cached_by_id(user.id) + + assert user.avatar == nil + + assert %{"url" => nil} = json_response(conn, 200) + end + end + + describe "PATCH /api/v1/pleroma/accounts/update_banner" do + test "can set profile banner", %{conn: conn} do + user = insert(:user) + + conn = + conn + |> assign(:user, user) + |> patch("/api/v1/pleroma/accounts/update_banner", %{"banner" => @image}) + + user = refresh_record(user) + assert user.info.banner["type"] == "Image" + + assert %{"url" => _} = json_response(conn, 200) + end + + test "can reset profile banner", %{conn: conn} do + user = insert(:user) + + conn = + conn + |> assign(:user, user) + |> patch("/api/v1/pleroma/accounts/update_banner", %{"banner" => ""}) + + user = refresh_record(user) + assert user.info.banner == %{} + + assert %{"url" => nil} = json_response(conn, 200) + end + end + + describe "PATCH /api/v1/pleroma/accounts/update_background" do + test "background image can be set", %{conn: conn} do + user = insert(:user) + + conn = + conn + |> assign(:user, user) + |> patch("/api/v1/pleroma/accounts/update_background", %{"img" => @image}) + + user = refresh_record(user) + assert user.info.background["type"] == "Image" + assert %{"url" => _} = json_response(conn, 200) + end + + test "background image can be reset", %{conn: conn} do + user = insert(:user) + + conn = + conn + |> assign(:user, user) + |> patch("/api/v1/pleroma/accounts/update_background", %{"img" => ""}) + + user = refresh_record(user) + assert user.info.background == %{} + assert %{"url" => nil} = json_response(conn, 200) + end + end + + describe "getting favorites timeline of specified user" do + setup do + [current_user, user] = insert_pair(:user, %{info: %{hide_favorites: false}}) + [current_user: current_user, user: user] + end + + test "returns list of statuses favorited by specified user", %{ + conn: conn, + current_user: current_user, + user: user + } do + [activity | _] = insert_pair(:note_activity) + CommonAPI.favorite(activity.id, user) + + response = + conn + |> assign(:user, current_user) + |> get("/api/v1/pleroma/accounts/#{user.id}/favourites") + |> json_response(:ok) + + [like] = response + + assert length(response) == 1 + assert like["id"] == activity.id + end + + test "returns favorites for specified user_id when user is not logged in", %{ + conn: conn, + user: user + } do + activity = insert(:note_activity) + CommonAPI.favorite(activity.id, user) + + response = + conn + |> get("/api/v1/pleroma/accounts/#{user.id}/favourites") + |> json_response(:ok) + + assert length(response) == 1 + end + + test "returns favorited DM only when user is logged in and he is one of recipients", %{ + conn: conn, + current_user: current_user, + user: user + } do + {:ok, direct} = + CommonAPI.post(current_user, %{ + "status" => "Hi @#{user.nickname}!", + "visibility" => "direct" + }) + + CommonAPI.favorite(direct.id, user) + + response = + conn + |> assign(:user, current_user) + |> get("/api/v1/pleroma/accounts/#{user.id}/favourites") + |> json_response(:ok) + + assert length(response) == 1 + + anonymous_response = + conn + |> get("/api/v1/pleroma/accounts/#{user.id}/favourites") + |> json_response(:ok) + + assert Enum.empty?(anonymous_response) + end + + test "does not return others' favorited DM when user is not one of recipients", %{ + conn: conn, + current_user: current_user, + user: user + } do + user_two = insert(:user) + + {:ok, direct} = + CommonAPI.post(user_two, %{ + "status" => "Hi @#{user.nickname}!", + "visibility" => "direct" + }) + + CommonAPI.favorite(direct.id, user) + + response = + conn + |> assign(:user, current_user) + |> get("/api/v1/pleroma/accounts/#{user.id}/favourites") + |> json_response(:ok) + + assert Enum.empty?(response) + end + + test "paginates favorites using since_id and max_id", %{ + conn: conn, + current_user: current_user, + user: user + } do + activities = insert_list(10, :note_activity) + + Enum.each(activities, fn activity -> + CommonAPI.favorite(activity.id, user) + end) + + third_activity = Enum.at(activities, 2) + seventh_activity = Enum.at(activities, 6) + + response = + conn + |> assign(:user, current_user) + |> get("/api/v1/pleroma/accounts/#{user.id}/favourites", %{ + since_id: third_activity.id, + max_id: seventh_activity.id + }) + |> json_response(:ok) + + assert length(response) == 3 + refute third_activity in response + refute seventh_activity in response + end + + test "limits favorites using limit parameter", %{ + conn: conn, + current_user: current_user, + user: user + } do + 7 + |> insert_list(:note_activity) + |> Enum.each(fn activity -> + CommonAPI.favorite(activity.id, user) + end) + + response = + conn + |> assign(:user, current_user) + |> get("/api/v1/pleroma/accounts/#{user.id}/favourites", %{limit: "3"}) + |> json_response(:ok) + + assert length(response) == 3 + end + + test "returns empty response when user does not have any favorited statuses", %{ + conn: conn, + current_user: current_user, + user: user + } do + response = + conn + |> assign(:user, current_user) + |> get("/api/v1/pleroma/accounts/#{user.id}/favourites") + |> json_response(:ok) + + assert Enum.empty?(response) + end + + test "returns 404 error when specified user is not exist", %{conn: conn} do + conn = get(conn, "/api/v1/pleroma/accounts/test/favourites") + + assert json_response(conn, 404) == %{"error" => "Record not found"} + end + + test "returns 403 error when user has hidden own favorites", %{ + conn: conn, + current_user: current_user + } do + user = insert(:user, %{info: %{hide_favorites: true}}) + activity = insert(:note_activity) + CommonAPI.favorite(activity.id, user) + + conn = + conn + |> assign(:user, current_user) + |> get("/api/v1/pleroma/accounts/#{user.id}/favourites") + + assert json_response(conn, 403) == %{"error" => "Can't get favorites"} + end + + test "hides favorites for new users by default", %{conn: conn, current_user: current_user} do + user = insert(:user) + activity = insert(:note_activity) + CommonAPI.favorite(activity.id, user) + + conn = + conn + |> assign(:user, current_user) + |> get("/api/v1/pleroma/accounts/#{user.id}/favourites") + + assert user.info.hide_favorites + assert json_response(conn, 403) == %{"error" => "Can't get favorites"} + end + end + + describe "subscribing / unsubscribing" do + test "subscribing / unsubscribing to a user", %{conn: conn} do + user = insert(:user) + subscription_target = insert(:user) + + conn = + conn + |> assign(:user, user) + |> post("/api/v1/pleroma/accounts/#{subscription_target.id}/subscribe") + + assert %{"id" => _id, "subscribing" => true} = json_response(conn, 200) + + conn = + build_conn() + |> assign(:user, user) + |> post("/api/v1/pleroma/accounts/#{subscription_target.id}/unsubscribe") + + assert %{"id" => _id, "subscribing" => false} = json_response(conn, 200) + end + end + + describe "subscribing" do + test "returns 404 when subscription_target not found", %{conn: conn} do + user = insert(:user) + + conn = + conn + |> assign(:user, user) + |> post("/api/v1/pleroma/accounts/target_id/subscribe") + + assert %{"error" => "Record not found"} = json_response(conn, 404) + end + end + + describe "unsubscribing" do + test "returns 404 when subscription_target not found", %{conn: conn} do + user = insert(:user) + + conn = + conn + |> assign(:user, user) + |> post("/api/v1/pleroma/accounts/target_id/unsubscribe") + + assert %{"error" => "Record not found"} = json_response(conn, 404) + end + end +end diff --git a/test/web/pleroma_api/emoji_api_controller_test.exs b/test/web/pleroma_api/controllers/emoji_api_controller_test.exs similarity index 98% rename from test/web/pleroma_api/emoji_api_controller_test.exs rename to test/web/pleroma_api/controllers/emoji_api_controller_test.exs index 93a507a01..5f74460e8 100644 --- a/test/web/pleroma_api/emoji_api_controller_test.exs +++ b/test/web/pleroma_api/controllers/emoji_api_controller_test.exs @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Web.PleromaAPI.EmojiAPIControllerTest do use Pleroma.Web.ConnCase diff --git a/test/web/pleroma_api/pleroma_api_controller_test.exs b/test/web/pleroma_api/controllers/pleroma_api_controller_test.exs similarity index 100% rename from test/web/pleroma_api/pleroma_api_controller_test.exs rename to test/web/pleroma_api/controllers/pleroma_api_controller_test.exs From 38db4878a451b5e78a8a74bd916390dea5c21643 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Mon, 30 Sep 2019 14:28:37 +0700 Subject: [PATCH 03/31] Disable async in DomainBlockControllerTest --- .../mastodon_api/controllers/domain_block_controller_test.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/web/mastodon_api/controllers/domain_block_controller_test.exs b/test/web/mastodon_api/controllers/domain_block_controller_test.exs index 3c3558385..25a279cdc 100644 --- a/test/web/mastodon_api/controllers/domain_block_controller_test.exs +++ b/test/web/mastodon_api/controllers/domain_block_controller_test.exs @@ -3,7 +3,7 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.MastodonAPI.DomainBlockControllerTest do - use Pleroma.Web.ConnCase, async: true + use Pleroma.Web.ConnCase alias Pleroma.User From 122cc050abb84b94fa57a15576c2a38b2912d559 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Mon, 30 Sep 2019 14:43:54 +0700 Subject: [PATCH 04/31] Move account_lists test to MastodonAPI.AccountControllerTest --- .../mastodon_api_controller_test.exs | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/test/web/mastodon_api/mastodon_api_controller_test.exs b/test/web/mastodon_api/mastodon_api_controller_test.exs index 671f9f254..0acc5d067 100644 --- a/test/web/mastodon_api/mastodon_api_controller_test.exs +++ b/test/web/mastodon_api/mastodon_api_controller_test.exs @@ -1082,23 +1082,6 @@ test "redirect to root page", %{conn: conn} do end end - describe "GET /api/v1/accounts/:id/lists - account_lists" do - test "returns lists to which the account belongs", %{conn: conn} do - user = insert(:user) - other_user = insert(:user) - assert {:ok, %Pleroma.List{} = list} = Pleroma.List.create("Test List", user) - {:ok, %{following: _following}} = Pleroma.List.follow(list, other_user) - - res = - conn - |> assign(:user, user) - |> get("/api/v1/accounts/#{other_user.id}/lists") - |> json_response(200) - - assert res == [%{"id" => to_string(list.id), "title" => "Test List"}] - end - end - describe "empty_array, stubs for mastodon api" do test "GET /api/v1/accounts/:id/identity_proofs", %{conn: conn} do user = insert(:user) From c0ce2d5faf872da219e724f4fc2e4deecb89e978 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Mon, 30 Sep 2019 16:08:29 +0700 Subject: [PATCH 05/31] Move account_register, relationships and verify_credentials to MastodonAPI.AccountController --- .../controllers/account_controller.ex | 83 +++++- .../controllers/mastodon_api_controller.ex | 70 ----- lib/pleroma/web/router.ex | 6 +- .../controllers/account_controller_test.exs | 254 ++++++++++++++++++ .../mastodon_api_controller_test.exs | 236 ---------------- 5 files changed, 332 insertions(+), 317 deletions(-) diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex index 38d53fd10..be863d8ed 100644 --- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex @@ -8,22 +8,89 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2, truthy_param?: 1, assign_account_by_id: 2, json_response: 3] - alias Pleroma.User - alias Pleroma.Web.CommonAPI - alias Pleroma.Web.ActivityPub.ActivityPub - alias Pleroma.Web.MastodonAPI.StatusView - alias Pleroma.Web.MastodonAPI.MastodonAPI - alias Pleroma.Web.MastodonAPI.ListView alias Pleroma.Plugs.RateLimiter + alias Pleroma.User + alias Pleroma.Web.ActivityPub.ActivityPub + alias Pleroma.Web.CommonAPI + alias Pleroma.Web.MastodonAPI.ListView + alias Pleroma.Web.MastodonAPI.MastodonAPI + alias Pleroma.Web.MastodonAPI.StatusView + alias Pleroma.Web.OAuth.Token + alias Pleroma.Web.TwitterAPI.TwitterAPI - @relations ~w(follow unfollow)a + @relations [:follow, :unfollow] + @needs_account ~W(followers following lists follow unfollow mute unmute block unblock)a plug(RateLimiter, {:relations_id_action, params: ["id", "uri"]} when action in @relations) plug(RateLimiter, :relations_actions when action in @relations) - plug(:assign_account_by_id when action not in [:show, :statuses]) + plug(RateLimiter, :app_account_creation when action == :create) + plug(:assign_account_by_id when action in @needs_account) action_fallback(Pleroma.Web.MastodonAPI.FallbackController) + @doc "POST /api/v1/accounts" + def create( + %{assigns: %{app: app}} = conn, + %{"username" => nickname, "email" => _, "password" => _, "agreement" => true} = params + ) do + params = + params + |> Map.take([ + "email", + "captcha_solution", + "captcha_token", + "captcha_answer_data", + "token", + "password" + ]) + |> Map.put("nickname", nickname) + |> Map.put("fullname", params["fullname"] || nickname) + |> Map.put("bio", params["bio"] || "") + |> Map.put("confirm", params["password"]) + + with {:ok, user} <- TwitterAPI.register_user(params, need_confirmation: true), + {:ok, token} <- Token.create_token(app, user, %{scopes: app.scopes}) do + json(conn, %{ + token_type: "Bearer", + access_token: token.token, + scope: app.scopes, + created_at: Token.Utils.format_created_at(token) + }) + else + {:error, errors} -> json_response(conn, :bad_request, errors) + end + end + + def create(%{assigns: %{app: _app}} = conn, _) do + render_error(conn, :bad_request, "Missing parameters") + end + + def create(conn, _) do + render_error(conn, :forbidden, "Invalid credentials") + end + + @doc "GET /api/v1/accounts/verify_credentials" + def verify_credentials(%{assigns: %{user: user}} = conn, _) do + chat_token = Phoenix.Token.sign(conn, "user socket", user.id) + + render(conn, "show.json", + user: user, + for: user, + with_pleroma_settings: true, + with_chat_token: chat_token + ) + end + + @doc "GET /api/v1/accounts/relationships" + def relationships(%{assigns: %{user: user}} = conn, %{"id" => id}) do + targets = User.get_all_by_ids(List.wrap(id)) + + render(conn, "relationships.json", user: user, targets: targets) + end + + # Instead of returning a 400 when no "id" params is present, Mastodon returns an empty array. + def relationships(%{assigns: %{user: _user}} = conn, _), do: json(conn, []) + @doc "GET /api/v1/accounts/:id" def show(%{assigns: %{user: for_user}} = conn, %{"id" => nickname_or_id}) do with %User{} = user <- User.get_cached_by_nickname_or_id(nickname_or_id, for: for_user), 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 197316794..32a58d929 100644 --- a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex @@ -35,8 +35,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do require Logger - plug(RateLimiter, :app_account_creation when action == :account_register) - plug(RateLimiter, :search when action in [:search, :search2, :account_search]) plug(RateLimiter, :password_reset when action == :password_reset) @local_mastodon_name "Mastodon-Local" @@ -164,20 +162,6 @@ def update_credentials(%{assigns: %{user: user}} = conn, params) do end end - - def verify_credentials(%{assigns: %{user: user}} = conn, _) do - chat_token = Phoenix.Token.sign(conn, "user socket", user.id) - - account = - AccountView.render("show.json", %{ - user: user, - for: user, - with_pleroma_settings: true, - with_chat_token: chat_token - }) - - json(conn, account) - end def verify_app_credentials(%{assigns: %{user: _user, token: token}} = conn, _) do with %Token{app: %App{} = app} <- Repo.preload(token, :app) do conn @@ -288,17 +272,6 @@ def poll_vote(%{assigns: %{user: user}} = conn, %{"id" => id, "choices" => choic end end - def relationships(%{assigns: %{user: user}} = conn, %{"id" => id}) do - targets = User.get_all_by_ids(List.wrap(id)) - - conn - |> put_view(AccountView) - |> render("relationships.json", %{user: user, targets: targets}) - end - - # Instead of returning a 400 when no "id" params is present, Mastodon returns an empty array. - def relationships(%{assigns: %{user: _user}} = conn, _), do: json(conn, []) - def update_media( %{assigns: %{user: user}} = conn, %{"id" => id, "description" => description} = _ @@ -649,49 +622,6 @@ defp fetch_suggestion_id(attrs) do end end - def account_register( - %{assigns: %{app: app}} = conn, - %{"username" => nickname, "email" => _, "password" => _, "agreement" => true} = params - ) do - params = - params - |> Map.take([ - "email", - "captcha_solution", - "captcha_token", - "captcha_answer_data", - "token", - "password" - ]) - |> Map.put("nickname", nickname) - |> Map.put("fullname", params["fullname"] || nickname) - |> Map.put("bio", params["bio"] || "") - |> Map.put("confirm", params["password"]) - - with {:ok, user} <- TwitterAPI.register_user(params, need_confirmation: true), - {:ok, token} <- Token.create_token(app, user, %{scopes: app.scopes}) do - json(conn, %{ - token_type: "Bearer", - access_token: token.token, - scope: app.scopes, - created_at: Token.Utils.format_created_at(token) - }) - else - {:error, errors} -> - conn - |> put_status(:bad_request) - |> json(errors) - end - end - - def account_register(%{assigns: %{app: _app}} = conn, _) do - render_error(conn, :bad_request, "Missing parameters") - end - - def account_register(conn, _) do - render_error(conn, :forbidden, "Invalid credentials") - end - def password_reset(conn, params) do nickname_or_email = params["email"] || params["nickname"] diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 5c3fe34e5..a4db5564d 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -335,9 +335,9 @@ defmodule Pleroma.Web.Router do scope [] do pipe_through(:oauth_read) - get("/accounts/verify_credentials", MastodonAPIController, :verify_credentials) + get("/accounts/verify_credentials", AccountController, :verify_credentials) - get("/accounts/relationships", MastodonAPIController, :relationships) + get("/accounts/relationships", AccountController, :relationships) get("/accounts/:id/lists", AccountController, :lists) get("/accounts/:id/identity_proofs", MastodonAPIController, :empty_array) @@ -459,7 +459,7 @@ defmodule Pleroma.Web.Router do scope "/api/v1", Pleroma.Web.MastodonAPI do pipe_through(:api) - post("/accounts", MastodonAPIController, :account_register) + post("/accounts", AccountController, :create) get("/instance", MastodonAPIController, :masto_instance) get("/instance/peers", MastodonAPIController, :peers) diff --git a/test/web/mastodon_api/controllers/account_controller_test.exs b/test/web/mastodon_api/controllers/account_controller_test.exs index 32ccc5351..8c8017838 100644 --- a/test/web/mastodon_api/controllers/account_controller_test.exs +++ b/test/web/mastodon_api/controllers/account_controller_test.exs @@ -5,9 +5,11 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do use Pleroma.Web.ConnCase + alias Pleroma.Repo alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.CommonAPI + alias Pleroma.Web.OAuth.Token import Pleroma.Factory @@ -595,4 +597,256 @@ test "blocking / unblocking a user", %{conn: conn} do assert %{"id" => _id, "blocking" => false} = json_response(conn, 200) end + + describe "create account by app" do + setup do + valid_params = %{ + username: "lain", + email: "lain@example.org", + password: "PlzDontHackLain", + agreement: true + } + + [valid_params: valid_params] + end + + test "Account registration via Application", %{conn: conn} do + conn = + conn + |> post("/api/v1/apps", %{ + client_name: "client_name", + redirect_uris: "urn:ietf:wg:oauth:2.0:oob", + scopes: "read, write, follow" + }) + + %{ + "client_id" => client_id, + "client_secret" => client_secret, + "id" => _, + "name" => "client_name", + "redirect_uri" => "urn:ietf:wg:oauth:2.0:oob", + "vapid_key" => _, + "website" => nil + } = json_response(conn, 200) + + conn = + conn + |> post("/oauth/token", %{ + grant_type: "client_credentials", + client_id: client_id, + client_secret: client_secret + }) + + assert %{"access_token" => token, "refresh_token" => refresh, "scope" => scope} = + json_response(conn, 200) + + assert token + token_from_db = Repo.get_by(Token, token: token) + assert token_from_db + assert refresh + assert scope == "read write follow" + + conn = + build_conn() + |> put_req_header("authorization", "Bearer " <> token) + |> post("/api/v1/accounts", %{ + username: "lain", + email: "lain@example.org", + password: "PlzDontHackLain", + bio: "Test Bio", + agreement: true + }) + + %{ + "access_token" => token, + "created_at" => _created_at, + "scope" => _scope, + "token_type" => "Bearer" + } = json_response(conn, 200) + + token_from_db = Repo.get_by(Token, token: token) + assert token_from_db + token_from_db = Repo.preload(token_from_db, :user) + assert token_from_db.user + + assert token_from_db.user.info.confirmation_pending + end + + test "returns error when user already registred", %{conn: conn, valid_params: valid_params} do + _user = insert(:user, email: "lain@example.org") + app_token = insert(:oauth_token, user: nil) + + conn = + conn + |> put_req_header("authorization", "Bearer " <> app_token.token) + + res = post(conn, "/api/v1/accounts", valid_params) + assert json_response(res, 400) == %{"error" => "{\"email\":[\"has already been taken\"]}"} + end + + test "rate limit", %{conn: conn} do + app_token = insert(:oauth_token, user: nil) + + conn = + put_req_header(conn, "authorization", "Bearer " <> app_token.token) + |> Map.put(:remote_ip, {15, 15, 15, 15}) + + for i <- 1..5 do + conn = + conn + |> post("/api/v1/accounts", %{ + username: "#{i}lain", + email: "#{i}lain@example.org", + password: "PlzDontHackLain", + agreement: true + }) + + %{ + "access_token" => token, + "created_at" => _created_at, + "scope" => _scope, + "token_type" => "Bearer" + } = json_response(conn, 200) + + token_from_db = Repo.get_by(Token, token: token) + assert token_from_db + token_from_db = Repo.preload(token_from_db, :user) + assert token_from_db.user + + assert token_from_db.user.info.confirmation_pending + end + + conn = + conn + |> post("/api/v1/accounts", %{ + username: "6lain", + email: "6lain@example.org", + password: "PlzDontHackLain", + agreement: true + }) + + assert json_response(conn, :too_many_requests) == %{"error" => "Throttled"} + end + + test "returns bad_request if missing required params", %{ + conn: conn, + valid_params: valid_params + } do + app_token = insert(:oauth_token, user: nil) + + conn = + conn + |> put_req_header("authorization", "Bearer " <> app_token.token) + + res = post(conn, "/api/v1/accounts", valid_params) + assert json_response(res, 200) + + [{127, 0, 0, 1}, {127, 0, 0, 2}, {127, 0, 0, 3}, {127, 0, 0, 4}] + |> Stream.zip(valid_params) + |> Enum.each(fn {ip, {attr, _}} -> + res = + conn + |> Map.put(:remote_ip, ip) + |> post("/api/v1/accounts", Map.delete(valid_params, attr)) + |> json_response(400) + + assert res == %{"error" => "Missing parameters"} + end) + end + + test "returns forbidden if token is invalid", %{conn: conn, valid_params: valid_params} do + conn = + conn + |> put_req_header("authorization", "Bearer " <> "invalid-token") + + res = post(conn, "/api/v1/accounts", valid_params) + assert json_response(res, 403) == %{"error" => "Invalid credentials"} + end + end + + describe "GET /api/v1/accounts/:id/lists - account_lists" do + test "returns lists to which the account belongs", %{conn: conn} do + user = insert(:user) + other_user = insert(:user) + assert {:ok, %Pleroma.List{} = list} = Pleroma.List.create("Test List", user) + {:ok, %{following: _following}} = Pleroma.List.follow(list, other_user) + + res = + conn + |> assign(:user, user) + |> get("/api/v1/accounts/#{other_user.id}/lists") + |> json_response(200) + + assert res == [%{"id" => to_string(list.id), "title" => "Test List"}] + end + end + + describe "verify_credentials" do + test "verify_credentials", %{conn: conn} do + user = insert(:user) + + conn = + conn + |> assign(:user, user) + |> get("/api/v1/accounts/verify_credentials") + + response = json_response(conn, 200) + + assert %{"id" => id, "source" => %{"privacy" => "public"}} = response + assert response["pleroma"]["chat_token"] + assert id == to_string(user.id) + end + + test "verify_credentials default scope unlisted", %{conn: conn} do + user = insert(:user, %{info: %User.Info{default_scope: "unlisted"}}) + + conn = + conn + |> assign(:user, user) + |> get("/api/v1/accounts/verify_credentials") + + assert %{"id" => id, "source" => %{"privacy" => "unlisted"}} = json_response(conn, 200) + assert id == to_string(user.id) + end + + test "locked accounts", %{conn: conn} do + user = insert(:user, %{info: %User.Info{default_scope: "private"}}) + + conn = + conn + |> assign(:user, user) + |> get("/api/v1/accounts/verify_credentials") + + assert %{"id" => id, "source" => %{"privacy" => "private"}} = json_response(conn, 200) + assert id == to_string(user.id) + end + end + + describe "user relationships" do + test "returns the relationships for the current user", %{conn: conn} do + user = insert(:user) + other_user = insert(:user) + {:ok, user} = User.follow(user, other_user) + + conn = + conn + |> assign(:user, user) + |> get("/api/v1/accounts/relationships", %{"id" => [other_user.id]}) + + assert [relationship] = json_response(conn, 200) + + assert to_string(other_user.id) == relationship["id"] + end + + test "returns an empty list on a bad request", %{conn: conn} do + user = insert(:user) + + conn = + conn + |> assign(:user, user) + |> get("/api/v1/accounts/relationships", %{}) + + assert [] = json_response(conn, 200) + end + end end diff --git a/test/web/mastodon_api/mastodon_api_controller_test.exs b/test/web/mastodon_api/mastodon_api_controller_test.exs index 0acc5d067..f2f8c0578 100644 --- a/test/web/mastodon_api/mastodon_api_controller_test.exs +++ b/test/web/mastodon_api/mastodon_api_controller_test.exs @@ -15,7 +15,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.CommonAPI alias Pleroma.Web.OAuth.App - alias Pleroma.Web.OAuth.Token alias Pleroma.Web.Push import ExUnit.CaptureLog @@ -31,33 +30,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do clear_config([:instance, :public]) clear_config([:rich_media, :enabled]) - test "verify_credentials", %{conn: conn} do - user = insert(:user) - - conn = - conn - |> assign(:user, user) - |> get("/api/v1/accounts/verify_credentials") - - response = json_response(conn, 200) - - assert %{"id" => id, "source" => %{"privacy" => "public"}} = response - assert response["pleroma"]["chat_token"] - assert id == to_string(user.id) - end - - test "verify_credentials default scope unlisted", %{conn: conn} do - user = insert(:user, %{info: %User.Info{default_scope: "unlisted"}}) - - conn = - conn - |> assign(:user, user) - |> get("/api/v1/accounts/verify_credentials") - - assert %{"id" => id, "source" => %{"privacy" => "unlisted"}} = json_response(conn, 200) - assert id == to_string(user.id) - end - test "apps/verify_credentials", %{conn: conn} do token = insert(:oauth_token) @@ -105,34 +77,6 @@ test "creates an oauth app", %{conn: conn} do assert expected == json_response(conn, 200) end - describe "user relationships" do - test "returns the relationships for the current user", %{conn: conn} do - user = insert(:user) - other_user = insert(:user) - {:ok, user} = User.follow(user, other_user) - - conn = - conn - |> assign(:user, user) - |> get("/api/v1/accounts/relationships", %{"id" => [other_user.id]}) - - assert [relationship] = json_response(conn, 200) - - assert to_string(other_user.id) == relationship["id"] - end - - test "returns an empty list on a bad request", %{conn: conn} do - user = insert(:user) - - conn = - conn - |> assign(:user, user) - |> get("/api/v1/accounts/relationships", %{}) - - assert [] = json_response(conn, 200) - end - end - describe "media upload" do setup do user = insert(:user) @@ -170,20 +114,6 @@ test "returns uploaded image", %{conn: conn, image: image} do end end - describe "locked accounts" do - test "verify_credentials", %{conn: conn} do - user = insert(:user, %{info: %User.Info{default_scope: "private"}}) - - conn = - conn - |> assign(:user, user) - |> get("/api/v1/accounts/verify_credentials") - - assert %{"id" => id, "source" => %{"privacy" => "private"}} = json_response(conn, 200) - assert id == to_string(user.id) - end - end - describe "/api/v1/pleroma/mascot" do test "mascot upload", %{conn: conn} do user = insert(:user) @@ -555,172 +485,6 @@ test "redirects to the getting-started page when referer is not present", %{conn end end - describe "create account by app" do - setup do - valid_params = %{ - username: "lain", - email: "lain@example.org", - password: "PlzDontHackLain", - agreement: true - } - - [valid_params: valid_params] - end - - test "Account registration via Application", %{conn: conn} do - conn = - conn - |> post("/api/v1/apps", %{ - client_name: "client_name", - redirect_uris: "urn:ietf:wg:oauth:2.0:oob", - scopes: "read, write, follow" - }) - - %{ - "client_id" => client_id, - "client_secret" => client_secret, - "id" => _, - "name" => "client_name", - "redirect_uri" => "urn:ietf:wg:oauth:2.0:oob", - "vapid_key" => _, - "website" => nil - } = json_response(conn, 200) - - conn = - conn - |> post("/oauth/token", %{ - grant_type: "client_credentials", - client_id: client_id, - client_secret: client_secret - }) - - assert %{"access_token" => token, "refresh_token" => refresh, "scope" => scope} = - json_response(conn, 200) - - assert token - token_from_db = Repo.get_by(Token, token: token) - assert token_from_db - assert refresh - assert scope == "read write follow" - - conn = - build_conn() - |> put_req_header("authorization", "Bearer " <> token) - |> post("/api/v1/accounts", %{ - username: "lain", - email: "lain@example.org", - password: "PlzDontHackLain", - bio: "Test Bio", - agreement: true - }) - - %{ - "access_token" => token, - "created_at" => _created_at, - "scope" => _scope, - "token_type" => "Bearer" - } = json_response(conn, 200) - - token_from_db = Repo.get_by(Token, token: token) - assert token_from_db - token_from_db = Repo.preload(token_from_db, :user) - assert token_from_db.user - - assert token_from_db.user.info.confirmation_pending - end - - test "returns error when user already registred", %{conn: conn, valid_params: valid_params} do - _user = insert(:user, email: "lain@example.org") - app_token = insert(:oauth_token, user: nil) - - conn = - conn - |> put_req_header("authorization", "Bearer " <> app_token.token) - - res = post(conn, "/api/v1/accounts", valid_params) - assert json_response(res, 400) == %{"error" => "{\"email\":[\"has already been taken\"]}"} - end - - test "rate limit", %{conn: conn} do - app_token = insert(:oauth_token, user: nil) - - conn = - put_req_header(conn, "authorization", "Bearer " <> app_token.token) - |> Map.put(:remote_ip, {15, 15, 15, 15}) - - for i <- 1..5 do - conn = - conn - |> post("/api/v1/accounts", %{ - username: "#{i}lain", - email: "#{i}lain@example.org", - password: "PlzDontHackLain", - agreement: true - }) - - %{ - "access_token" => token, - "created_at" => _created_at, - "scope" => _scope, - "token_type" => "Bearer" - } = json_response(conn, 200) - - token_from_db = Repo.get_by(Token, token: token) - assert token_from_db - token_from_db = Repo.preload(token_from_db, :user) - assert token_from_db.user - - assert token_from_db.user.info.confirmation_pending - end - - conn = - conn - |> post("/api/v1/accounts", %{ - username: "6lain", - email: "6lain@example.org", - password: "PlzDontHackLain", - agreement: true - }) - - assert json_response(conn, :too_many_requests) == %{"error" => "Throttled"} - end - - test "returns bad_request if missing required params", %{ - conn: conn, - valid_params: valid_params - } do - app_token = insert(:oauth_token, user: nil) - - conn = - conn - |> put_req_header("authorization", "Bearer " <> app_token.token) - - res = post(conn, "/api/v1/accounts", valid_params) - assert json_response(res, 200) - - [{127, 0, 0, 1}, {127, 0, 0, 2}, {127, 0, 0, 3}, {127, 0, 0, 4}] - |> Stream.zip(valid_params) - |> Enum.each(fn {ip, {attr, _}} -> - res = - conn - |> Map.put(:remote_ip, ip) - |> post("/api/v1/accounts", Map.delete(valid_params, attr)) - |> json_response(400) - - assert res == %{"error" => "Missing parameters"} - end) - end - - test "returns forbidden if token is invalid", %{conn: conn, valid_params: valid_params} do - conn = - conn - |> put_req_header("authorization", "Bearer " <> "invalid-token") - - res = post(conn, "/api/v1/accounts", valid_params) - assert json_response(res, 403) == %{"error" => "Invalid credentials"} - end - end - describe "GET /api/v1/polls/:id" do test "returns poll entity for object id", %{conn: conn} do user = insert(:user) From 987e0b8be8a7e0c40eacc96aaec53a6534563cab Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Mon, 30 Sep 2019 15:47:01 +0700 Subject: [PATCH 06/31] Move update_credentials to MastodonAPI.AccountController --- .../controllers/account_controller.ex | 87 ++++++++++++++ .../controllers/mastodon_api_controller.ex | 107 +----------------- lib/pleroma/web/router.ex | 2 +- .../update_credentials_test.exs | 0 4 files changed, 89 insertions(+), 107 deletions(-) rename test/web/mastodon_api/controllers/{mastodon_api_controller => account_controller}/update_credentials_test.exs (100%) diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex index be863d8ed..df14ad66f 100644 --- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex @@ -8,6 +8,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2, truthy_param?: 1, assign_account_by_id: 2, json_response: 3] + alias Pleroma.Emoji alias Pleroma.Plugs.RateLimiter alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub @@ -81,6 +82,92 @@ def verify_credentials(%{assigns: %{user: user}} = conn, _) do ) end + @doc "PATCH /api/v1/accounts/update_credentials" + def update_credentials(%{assigns: %{user: original_user}} = conn, params) do + user = original_user + + user_params = + %{} + |> add_if_present(params, "display_name", :name) + |> add_if_present(params, "note", :bio, fn value -> {:ok, User.parse_bio(value, user)} end) + |> add_if_present(params, "avatar", :avatar, fn value -> + with %Plug.Upload{} <- value, + {:ok, object} <- ActivityPub.upload(value, type: :avatar) do + {:ok, object.data} + end + end) + + emojis_text = (user_params["display_name"] || "") <> (user_params["note"] || "") + + user_info_emojis = + user.info + |> Map.get(:emoji, []) + |> Enum.concat(Emoji.Formatter.get_emoji_map(emojis_text)) + |> Enum.dedup() + + info_params = + [ + :no_rich_text, + :locked, + :hide_followers_count, + :hide_follows_count, + :hide_followers, + :hide_follows, + :hide_favorites, + :show_role, + :skip_thread_containment, + :discoverable + ] + |> Enum.reduce(%{}, fn key, acc -> + add_if_present(acc, params, to_string(key), key, &{:ok, truthy_param?(&1)}) + end) + |> add_if_present(params, "default_scope", :default_scope) + |> add_if_present(params, "fields", :fields, fn fields -> + fields = Enum.map(fields, fn f -> Map.update!(f, "value", &AutoLinker.link(&1)) end) + + {:ok, fields} + end) + |> add_if_present(params, "fields", :raw_fields) + |> add_if_present(params, "pleroma_settings_store", :pleroma_settings_store, fn value -> + {:ok, Map.merge(user.info.pleroma_settings_store, value)} + end) + |> add_if_present(params, "header", :banner, fn value -> + with %Plug.Upload{} <- value, + {:ok, object} <- ActivityPub.upload(value, type: :banner) do + {:ok, object.data} + end + end) + |> add_if_present(params, "pleroma_background_image", :background, fn value -> + with %Plug.Upload{} <- value, + {:ok, object} <- ActivityPub.upload(value, type: :background) do + {:ok, object.data} + end + end) + |> Map.put(:emoji, user_info_emojis) + + changeset = + user + |> User.update_changeset(user_params) + |> User.change_info(&User.Info.profile_update(&1, info_params)) + + with {:ok, user} <- User.update_and_set_cache(changeset) do + if original_user != user, do: CommonAPI.update(user) + + render(conn, "show.json", user: user, for: user, with_pleroma_settings: true) + else + _e -> render_error(conn, :forbidden, "Invalid request") + end + end + + defp add_if_present(map, params, params_field, map_field, value_function \\ &{:ok, &1}) do + with true <- Map.has_key?(params, params_field), + {:ok, new_value} <- value_function.(params[params_field]) do + Map.put(map, map_field, new_value) + else + _ -> map + end + end + @doc "GET /api/v1/accounts/relationships" def relationships(%{assigns: %{user: user}} = conn, %{"id" => id}) do targets = User.get_all_by_ids(List.wrap(id)) 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 32a58d929..30a2bf0e0 100644 --- a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex @@ -5,12 +5,11 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do use Pleroma.Web, :controller - import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2, truthy_param?: 1] + import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2] alias Pleroma.Activity alias Pleroma.Bookmark alias Pleroma.Config - alias Pleroma.Emoji alias Pleroma.HTTP alias Pleroma.Object alias Pleroma.Pagination @@ -58,110 +57,6 @@ def create_app(conn, params) do end end - defp add_if_present( - map, - params, - params_field, - map_field, - value_function \\ fn x -> {:ok, x} end - ) do - if Map.has_key?(params, params_field) do - case value_function.(params[params_field]) do - {:ok, new_value} -> Map.put(map, map_field, new_value) - :error -> map - end - else - map - end - end - - def update_credentials(%{assigns: %{user: user}} = conn, params) do - original_user = user - - user_params = - %{} - |> add_if_present(params, "display_name", :name) - |> add_if_present(params, "note", :bio, fn value -> {:ok, User.parse_bio(value, user)} end) - |> add_if_present(params, "avatar", :avatar, fn value -> - with %Plug.Upload{} <- value, - {:ok, object} <- ActivityPub.upload(value, type: :avatar) do - {:ok, object.data} - else - _ -> :error - end - end) - - emojis_text = (user_params["display_name"] || "") <> (user_params["note"] || "") - - user_info_emojis = - user.info - |> Map.get(:emoji, []) - |> Enum.concat(Emoji.Formatter.get_emoji_map(emojis_text)) - |> Enum.dedup() - - info_params = - [ - :no_rich_text, - :locked, - :hide_followers_count, - :hide_follows_count, - :hide_followers, - :hide_follows, - :hide_favorites, - :show_role, - :skip_thread_containment, - :discoverable - ] - |> Enum.reduce(%{}, fn key, acc -> - add_if_present(acc, params, to_string(key), key, fn value -> - {:ok, truthy_param?(value)} - end) - end) - |> add_if_present(params, "default_scope", :default_scope) - |> add_if_present(params, "fields", :fields, fn fields -> - fields = Enum.map(fields, fn f -> Map.update!(f, "value", &AutoLinker.link(&1)) end) - - {:ok, fields} - end) - |> add_if_present(params, "fields", :raw_fields) - |> add_if_present(params, "pleroma_settings_store", :pleroma_settings_store, fn value -> - {:ok, Map.merge(user.info.pleroma_settings_store, value)} - end) - |> add_if_present(params, "header", :banner, fn value -> - with %Plug.Upload{} <- value, - {:ok, object} <- ActivityPub.upload(value, type: :banner) do - {:ok, object.data} - else - _ -> :error - end - end) - |> add_if_present(params, "pleroma_background_image", :background, fn value -> - with %Plug.Upload{} <- value, - {:ok, object} <- ActivityPub.upload(value, type: :background) do - {:ok, object.data} - else - _ -> :error - end - end) - |> Map.put(:emoji, user_info_emojis) - - changeset = - user - |> User.update_changeset(user_params) - |> User.change_info(&User.Info.profile_update(&1, info_params)) - - with {:ok, user} <- User.update_and_set_cache(changeset) do - if original_user != user, do: CommonAPI.update(user) - - json( - conn, - AccountView.render("show.json", %{user: user, for: user, with_pleroma_settings: true}) - ) - else - _e -> render_error(conn, :forbidden, "Invalid request") - end - end - def verify_app_credentials(%{assigns: %{user: _user, token: token}} = conn, _) do with %Token{app: %App{} = app} <- Repo.preload(token, :app) do conn diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index a4db5564d..f6c74896f 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -380,7 +380,7 @@ defmodule Pleroma.Web.Router do scope [] do pipe_through(:oauth_write) - patch("/accounts/update_credentials", MastodonAPIController, :update_credentials) + patch("/accounts/update_credentials", AccountController, :update_credentials) post("/statuses", StatusController, :create) delete("/statuses/:id", StatusController, :delete) diff --git a/test/web/mastodon_api/controllers/mastodon_api_controller/update_credentials_test.exs b/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs similarity index 100% rename from test/web/mastodon_api/controllers/mastodon_api_controller/update_credentials_test.exs rename to test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs From c55facf78b6714947d8c5b02b76846f5f2ae7744 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Mon, 30 Sep 2019 19:06:17 +0700 Subject: [PATCH 07/31] Fix warning in TransmogrifierTest --- test/web/activity_pub/transmogrifier_test.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/web/activity_pub/transmogrifier_test.exs b/test/web/activity_pub/transmogrifier_test.exs index 2c6357fe6..193d6d301 100644 --- a/test/web/activity_pub/transmogrifier_test.exs +++ b/test/web/activity_pub/transmogrifier_test.exs @@ -1231,7 +1231,7 @@ test "it can handle Listen activities" do {:ok, activity} = CommonAPI.listen(user, %{"title" => "lain radio episode 1"}) - {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data) + {:ok, _modified} = Transmogrifier.prepare_outgoing(activity.data) end end From 0c6009dd2e475d3487123390885c46bf3fc5dea8 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Mon, 30 Sep 2019 19:32:43 +0700 Subject: [PATCH 08/31] Extract mascot actions from `MastodonAPIController` to MascotController --- .../controllers/mastodon_api_controller.ex | 22 ------ .../controllers/mascot_controller.ex | 35 +++++++++ lib/pleroma/web/router.ex | 7 +- .../mastodon_api_controller_test.exs | 68 ---------------- .../controllers/mascot_controller_test.exs | 77 +++++++++++++++++++ 5 files changed, 116 insertions(+), 93 deletions(-) create mode 100644 lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex create mode 100644 test/web/pleroma_api/controllers/mascot_controller_test.exs 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 30a2bf0e0..1484a0174 100644 --- a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex @@ -200,28 +200,6 @@ def upload(%{assigns: %{user: user}} = conn, %{"file" => file} = data) do end end - def set_mascot(%{assigns: %{user: user}} = conn, %{"file" => file}) do - with {:ok, object} <- ActivityPub.upload(file, actor: User.ap_id(user)), - %{} = attachment_data <- Map.put(object.data, "id", object.id), - # Reject if not an image - %{type: "image"} = rendered <- - StatusView.render("attachment.json", %{attachment: attachment_data}) do - # Sure! - # Save to the user's info - {:ok, _user} = User.update_info(user, &User.Info.mascot_update(&1, rendered)) - - json(conn, rendered) - else - %{type: _} -> render_error(conn, :unsupported_media_type, "mascots can only be images") - end - end - - def get_mascot(%{assigns: %{user: user}} = conn, _params) do - mascot = User.get_mascot(user) - - json(conn, mascot) - end - def follows(%{assigns: %{user: follower}} = conn, %{"uri" => uri}) do with {_, %User{} = followed} <- {:followed, User.get_cached_by_nickname(uri)}, {_, true} <- {:followed, follower.id != followed.id}, diff --git a/lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex b/lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex new file mode 100644 index 000000000..7f6a76c0e --- /dev/null +++ b/lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex @@ -0,0 +1,35 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.PleromaAPI.MascotController do + use Pleroma.Web, :controller + + alias Pleroma.User + alias Pleroma.Web.ActivityPub.ActivityPub + + @doc "GET /api/v1/pleroma/mascot" + def show(%{assigns: %{user: user}} = conn, _params) do + json(conn, User.get_mascot(user)) + end + + @doc "PUT /api/v1/pleroma/mascot" + def update(%{assigns: %{user: user}} = conn, %{"file" => file}) do + with {:ok, object} <- ActivityPub.upload(file, actor: User.ap_id(user)), + # Reject if not an image + %{type: "image"} = attachment <- render_attachment(object) do + # Sure! + # Save to the user's info + {:ok, _user} = User.update_info(user, &User.Info.mascot_update(&1, attachment)) + + json(conn, attachment) + else + %{type: _} -> render_error(conn, :unsupported_media_type, "mascots can only be images") + end + end + + defp render_attachment(object) do + attachment_data = Map.put(object.data, "id", object.id) + Pleroma.Web.MastodonAPI.StatusView.render("attachment.json", %{attachment: attachment_data}) + end +end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index f6c74896f..eab55a27c 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -303,6 +303,10 @@ defmodule Pleroma.Web.Router do patch("/accounts/update_avatar", AccountController, :update_avatar) patch("/accounts/update_banner", AccountController, :update_banner) patch("/accounts/update_background", AccountController, :update_background) + + get("/mascot", MascotController, :show) + put("/mascot", MascotController, :update) + post("/scrobble", ScrobbleController, :new_scrobble) end @@ -416,9 +420,6 @@ defmodule Pleroma.Web.Router do put("/filters/:id", FilterController, :update) delete("/filters/:id", FilterController, :delete) - get("/pleroma/mascot", MastodonAPIController, :get_mascot) - put("/pleroma/mascot", MastodonAPIController, :set_mascot) - post("/reports", ReportController, :create) end diff --git a/test/web/mastodon_api/mastodon_api_controller_test.exs b/test/web/mastodon_api/mastodon_api_controller_test.exs index f2f8c0578..feeaf079b 100644 --- a/test/web/mastodon_api/mastodon_api_controller_test.exs +++ b/test/web/mastodon_api/mastodon_api_controller_test.exs @@ -114,74 +114,6 @@ test "returns uploaded image", %{conn: conn, image: image} do end end - describe "/api/v1/pleroma/mascot" do - test "mascot upload", %{conn: conn} do - user = insert(:user) - - non_image_file = %Plug.Upload{ - content_type: "audio/mpeg", - path: Path.absname("test/fixtures/sound.mp3"), - filename: "sound.mp3" - } - - conn = - conn - |> assign(:user, user) - |> put("/api/v1/pleroma/mascot", %{"file" => non_image_file}) - - assert json_response(conn, 415) - - file = %Plug.Upload{ - content_type: "image/jpg", - path: Path.absname("test/fixtures/image.jpg"), - filename: "an_image.jpg" - } - - conn = - build_conn() - |> assign(:user, user) - |> put("/api/v1/pleroma/mascot", %{"file" => file}) - - assert %{"id" => _, "type" => image} = json_response(conn, 200) - end - - test "mascot retrieving", %{conn: conn} do - user = insert(:user) - # When user hasn't set a mascot, we should just get pleroma tan back - conn = - conn - |> assign(:user, user) - |> get("/api/v1/pleroma/mascot") - - assert %{"url" => url} = json_response(conn, 200) - assert url =~ "pleroma-fox-tan-smol" - - # When a user sets their mascot, we should get that back - file = %Plug.Upload{ - content_type: "image/jpg", - path: Path.absname("test/fixtures/image.jpg"), - filename: "an_image.jpg" - } - - conn = - build_conn() - |> assign(:user, user) - |> put("/api/v1/pleroma/mascot", %{"file" => file}) - - assert json_response(conn, 200) - - user = User.get_cached_by_id(user.id) - - conn = - build_conn() - |> assign(:user, user) - |> get("/api/v1/pleroma/mascot") - - assert %{"url" => url, "type" => "image"} = json_response(conn, 200) - assert url =~ "an_image" - end - end - test "getting a list of mutes", %{conn: conn} do user = insert(:user) other_user = insert(:user) diff --git a/test/web/pleroma_api/controllers/mascot_controller_test.exs b/test/web/pleroma_api/controllers/mascot_controller_test.exs new file mode 100644 index 000000000..ae9539b04 --- /dev/null +++ b/test/web/pleroma_api/controllers/mascot_controller_test.exs @@ -0,0 +1,77 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.PleromaAPI.MascotControllerTest do + use Pleroma.Web.ConnCase + + alias Pleroma.User + + import Pleroma.Factory + + test "mascot upload", %{conn: conn} do + user = insert(:user) + + non_image_file = %Plug.Upload{ + content_type: "audio/mpeg", + path: Path.absname("test/fixtures/sound.mp3"), + filename: "sound.mp3" + } + + conn = + conn + |> assign(:user, user) + |> put("/api/v1/pleroma/mascot", %{"file" => non_image_file}) + + assert json_response(conn, 415) + + file = %Plug.Upload{ + content_type: "image/jpg", + path: Path.absname("test/fixtures/image.jpg"), + filename: "an_image.jpg" + } + + conn = + build_conn() + |> assign(:user, user) + |> put("/api/v1/pleroma/mascot", %{"file" => file}) + + assert %{"id" => _, "type" => image} = json_response(conn, 200) + end + + test "mascot retrieving", %{conn: conn} do + user = insert(:user) + # When user hasn't set a mascot, we should just get pleroma tan back + conn = + conn + |> assign(:user, user) + |> get("/api/v1/pleroma/mascot") + + assert %{"url" => url} = json_response(conn, 200) + assert url =~ "pleroma-fox-tan-smol" + + # When a user sets their mascot, we should get that back + file = %Plug.Upload{ + content_type: "image/jpg", + path: Path.absname("test/fixtures/image.jpg"), + filename: "an_image.jpg" + } + + conn = + build_conn() + |> assign(:user, user) + |> put("/api/v1/pleroma/mascot", %{"file" => file}) + + assert json_response(conn, 200) + + user = User.get_cached_by_id(user.id) + + conn = + build_conn() + |> assign(:user, user) + |> get("/api/v1/pleroma/mascot") + + assert %{"url" => url, "type" => "image"} = json_response(conn, 200) + assert url =~ "an_image" + end +end From 28be12b38e0eb68ac702bf5e214f32f5bec4695b Mon Sep 17 00:00:00 2001 From: Ariadne Conill Date: Mon, 30 Sep 2019 12:52:28 +0000 Subject: [PATCH 09/31] update admin fe --- .../{app.40438ff5.css => app.f774664e.css} | Bin ...a.bcc01554.css => chunk-15fa.9e804910.css} | Bin ...7.c0efd1fc.css => chunk-1bbd.dc6c5fb2.css} | Bin ...3.33f0e7ff.css => chunk-3871.820645ae.css} | Bin 3252 -> 3252 bytes ...c.2880a519.css => chunk-3d1c.a6b92ca7.css} | Bin ...b.75709645.css => chunk-538a.6ef5bd70.css} | Bin ...f.dc5869e7.css => chunk-598f.14eeccbb.css} | Bin ...2.d1c82a11.css => chunk-6292.8ee9eaaa.css} | Bin ...b.4a8663a9.css => chunk-7c6b.dece6ace.css} | Bin priv/static/adminfe/chunk-7f8e.52359c55.css | Bin 0 -> 314 bytes ...d.38eb00cf.css => chunk-a9e5.15079754.css} | Bin ...d8ab1.css => chunk-elementUI.d2a55ce6.css} | Bin ...s.00388c73.css => chunk-libs.36b859a1.css} | Bin priv/static/adminfe/index.html | 2 +- priv/static/adminfe/static/js/app.90c455c5.js | Bin 161629 -> 0 bytes .../adminfe/static/js/app.90c455c5.js.map | Bin 354948 -> 0 bytes priv/static/adminfe/static/js/app.9d5375ac.js | Bin 0 -> 167236 bytes .../adminfe/static/js/app.9d5375ac.js.map | Bin 0 -> 366548 bytes ...5fa.b0633695.js => chunk-15fa.6dcb4448.js} | Bin 7919 -> 7919 bytes ...3695.js.map => chunk-15fa.6dcb4448.js.map} | Bin 17438 -> 17438 bytes ...f27.d3c35fbc.js => chunk-1bbd.bc68e218.js} | Bin 2080 -> 2080 bytes ...5fbc.js.map => chunk-1bbd.bc68e218.js.map} | Bin 9090 -> 9090 bytes .../adminfe/static/js/chunk-3871.4ac23900.js | Bin 0 -> 28092 bytes .../static/js/chunk-3871.4ac23900.js.map | Bin 0 -> 91362 bytes ...d1c.20303ef7.js => chunk-3d1c.47c8fa87.js} | Bin 4822 -> 4822 bytes ...3ef7.js.map => chunk-3d1c.47c8fa87.js.map} | Bin 18519 -> 18519 bytes ...6db.12facc20.js => chunk-538a.18908e98.js} | Bin 5112 -> 5112 bytes ...cc20.js.map => chunk-538a.18908e98.js.map} | Bin 19586 -> 19586 bytes .../adminfe/static/js/chunk-5913.1d21a547.js | Bin 27091 -> 0 bytes .../static/js/chunk-5913.1d21a547.js.map | Bin 88770 -> 0 bytes ...98f.dd8089ce.js => chunk-598f.b02acd71.js} | Bin 17765 -> 17765 bytes ...89ce.js.map => chunk-598f.b02acd71.js.map} | Bin 66937 -> 66937 bytes ...292.0e668979.js => chunk-6292.b3aa39da.js} | Bin 231394 -> 231394 bytes ...8979.js.map => chunk-6292.b3aa39da.js.map} | Bin 689117 -> 689117 bytes ...c6b.c306c730.js => chunk-7c6b.24877470.js} | Bin 7947 -> 7947 bytes ...c730.js.map => chunk-7c6b.24877470.js.map} | Bin 26432 -> 26432 bytes .../adminfe/static/js/chunk-7f8e.b2353c0a.js | Bin 0 -> 9618 bytes .../static/js/chunk-7f8e.b2353c0a.js.map | Bin 0 -> 39890 bytes ...a7d.8173d81f.js => chunk-a9e5.f5bb9b33.js} | Bin 16157 -> 16157 bytes ...d81f.js.map => chunk-a9e5.f5bb9b33.js.map} | Bin 57112 -> 57112 bytes ...08d6b68.js => chunk-elementUI.374aa2ca.js} | Bin 638936 -> 638936 bytes ...js.map => chunk-elementUI.374aa2ca.js.map} | Bin 2312798 -> 2312798 bytes ...ibs.14514767.js => chunk-libs.3ed10ef6.js} | Bin 275816 -> 275816 bytes ...4767.js.map => chunk-libs.3ed10ef6.js.map} | Bin 1641569 -> 1641569 bytes .../adminfe/static/js/runtime.c6b7511a.js | Bin 0 -> 3922 bytes .../adminfe/static/js/runtime.c6b7511a.js.map | Bin 0 -> 16658 bytes .../adminfe/static/js/runtime.e85850af.js | Bin 3859 -> 0 bytes .../adminfe/static/js/runtime.e85850af.js.map | Bin 16537 -> 0 bytes 48 files changed, 1 insertion(+), 1 deletion(-) rename priv/static/adminfe/{app.40438ff5.css => app.f774664e.css} (100%) rename priv/static/adminfe/{chunk-15fa.bcc01554.css => chunk-15fa.9e804910.css} (100%) rename priv/static/adminfe/{chunk-1f27.c0efd1fc.css => chunk-1bbd.dc6c5fb2.css} (100%) rename priv/static/adminfe/{chunk-5913.33f0e7ff.css => chunk-3871.820645ae.css} (88%) rename priv/static/adminfe/{chunk-3d1c.2880a519.css => chunk-3d1c.a6b92ca7.css} (100%) rename priv/static/adminfe/{chunk-06db.75709645.css => chunk-538a.6ef5bd70.css} (100%) rename priv/static/adminfe/{chunk-598f.dc5869e7.css => chunk-598f.14eeccbb.css} (100%) rename priv/static/adminfe/{chunk-6292.d1c82a11.css => chunk-6292.8ee9eaaa.css} (100%) rename priv/static/adminfe/{chunk-7c6b.4a8663a9.css => chunk-7c6b.dece6ace.css} (100%) create mode 100644 priv/static/adminfe/chunk-7f8e.52359c55.css rename priv/static/adminfe/{chunk-1a7d.38eb00cf.css => chunk-a9e5.15079754.css} (100%) rename priv/static/adminfe/{chunk-elementUI.f35d8ab1.css => chunk-elementUI.d2a55ce6.css} (100%) rename priv/static/adminfe/{chunk-libs.00388c73.css => chunk-libs.36b859a1.css} (100%) delete mode 100644 priv/static/adminfe/static/js/app.90c455c5.js delete mode 100644 priv/static/adminfe/static/js/app.90c455c5.js.map create mode 100644 priv/static/adminfe/static/js/app.9d5375ac.js create mode 100644 priv/static/adminfe/static/js/app.9d5375ac.js.map rename priv/static/adminfe/static/js/{chunk-15fa.b0633695.js => chunk-15fa.6dcb4448.js} (99%) rename priv/static/adminfe/static/js/{chunk-15fa.b0633695.js.map => chunk-15fa.6dcb4448.js.map} (99%) rename priv/static/adminfe/static/js/{chunk-1f27.d3c35fbc.js => chunk-1bbd.bc68e218.js} (94%) rename priv/static/adminfe/static/js/{chunk-1f27.d3c35fbc.js.map => chunk-1bbd.bc68e218.js.map} (98%) create mode 100644 priv/static/adminfe/static/js/chunk-3871.4ac23900.js create mode 100644 priv/static/adminfe/static/js/chunk-3871.4ac23900.js.map rename priv/static/adminfe/static/js/{chunk-3d1c.20303ef7.js => chunk-3d1c.47c8fa87.js} (99%) rename priv/static/adminfe/static/js/{chunk-3d1c.20303ef7.js.map => chunk-3d1c.47c8fa87.js.map} (99%) rename priv/static/adminfe/static/js/{chunk-06db.12facc20.js => chunk-538a.18908e98.js} (97%) rename priv/static/adminfe/static/js/{chunk-06db.12facc20.js.map => chunk-538a.18908e98.js.map} (99%) delete mode 100644 priv/static/adminfe/static/js/chunk-5913.1d21a547.js delete mode 100644 priv/static/adminfe/static/js/chunk-5913.1d21a547.js.map rename priv/static/adminfe/static/js/{chunk-598f.dd8089ce.js => chunk-598f.b02acd71.js} (99%) rename priv/static/adminfe/static/js/{chunk-598f.dd8089ce.js.map => chunk-598f.b02acd71.js.map} (99%) rename priv/static/adminfe/static/js/{chunk-6292.0e668979.js => chunk-6292.b3aa39da.js} (99%) rename priv/static/adminfe/static/js/{chunk-6292.0e668979.js.map => chunk-6292.b3aa39da.js.map} (99%) rename priv/static/adminfe/static/js/{chunk-7c6b.c306c730.js => chunk-7c6b.24877470.js} (99%) rename priv/static/adminfe/static/js/{chunk-7c6b.c306c730.js.map => chunk-7c6b.24877470.js.map} (99%) create mode 100644 priv/static/adminfe/static/js/chunk-7f8e.b2353c0a.js create mode 100644 priv/static/adminfe/static/js/chunk-7f8e.b2353c0a.js.map rename priv/static/adminfe/static/js/{chunk-1a7d.8173d81f.js => chunk-a9e5.f5bb9b33.js} (99%) rename priv/static/adminfe/static/js/{chunk-1a7d.8173d81f.js.map => chunk-a9e5.f5bb9b33.js.map} (99%) rename priv/static/adminfe/static/js/{chunk-elementUI.708d6b68.js => chunk-elementUI.374aa2ca.js} (99%) rename priv/static/adminfe/static/js/{chunk-elementUI.708d6b68.js.map => chunk-elementUI.374aa2ca.js.map} (99%) rename priv/static/adminfe/static/js/{chunk-libs.14514767.js => chunk-libs.3ed10ef6.js} (99%) rename priv/static/adminfe/static/js/{chunk-libs.14514767.js.map => chunk-libs.3ed10ef6.js.map} (99%) create mode 100644 priv/static/adminfe/static/js/runtime.c6b7511a.js create mode 100644 priv/static/adminfe/static/js/runtime.c6b7511a.js.map delete mode 100644 priv/static/adminfe/static/js/runtime.e85850af.js delete mode 100644 priv/static/adminfe/static/js/runtime.e85850af.js.map diff --git a/priv/static/adminfe/app.40438ff5.css b/priv/static/adminfe/app.f774664e.css similarity index 100% rename from priv/static/adminfe/app.40438ff5.css rename to priv/static/adminfe/app.f774664e.css diff --git a/priv/static/adminfe/chunk-15fa.bcc01554.css b/priv/static/adminfe/chunk-15fa.9e804910.css similarity index 100% rename from priv/static/adminfe/chunk-15fa.bcc01554.css rename to priv/static/adminfe/chunk-15fa.9e804910.css diff --git a/priv/static/adminfe/chunk-1f27.c0efd1fc.css b/priv/static/adminfe/chunk-1bbd.dc6c5fb2.css similarity index 100% rename from priv/static/adminfe/chunk-1f27.c0efd1fc.css rename to priv/static/adminfe/chunk-1bbd.dc6c5fb2.css diff --git a/priv/static/adminfe/chunk-5913.33f0e7ff.css b/priv/static/adminfe/chunk-3871.820645ae.css similarity index 88% rename from priv/static/adminfe/chunk-5913.33f0e7ff.css rename to priv/static/adminfe/chunk-3871.820645ae.css index f98c967ee21dd1b937e79796330c7c4162d9c1d4..172bce31757c9858b49e0291c6e5ecb51690ee23 100644 GIT binary patch delta 117 zcmdlYxkYlqea=Lq#MBfEqvY7hH(87)OEXF!iP&)Y0pFiE43>F}tA`i)O zog#scSS|(#_=6vZ+i;T8bbZZGtdB~te#~oi{VpM8UYTH0G_1qG$He6&i`?!I8*3kKA*D>r4UIzPQ3rw-#*yC4+p{>>p=i9_%Admin FE
\ No newline at end of file +Admin FE
\ No newline at end of file diff --git a/priv/static/adminfe/static/js/app.90c455c5.js b/priv/static/adminfe/static/js/app.90c455c5.js deleted file mode 100644 index d4c607af87dafe7afd49a344d48799dfb78d042c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 161629 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 E0hSkb{{R30 diff --git a/priv/static/adminfe/static/js/app.90c455c5.js.map b/priv/static/adminfe/static/js/app.90c455c5.js.map deleted file mode 100644 index 242ad185b85359e60f6499e2f86014718ff6c5eb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 354948 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

^}Az41G!oxspNf106?* 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 delta 23 ecmaEF``&iL13CUA12bb|GfPvwtYW>~!~y_uj|g7? diff --git a/priv/static/adminfe/static/js/chunk-15fa.b0633695.js.map b/priv/static/adminfe/static/js/chunk-15fa.6dcb4448.js.map similarity index 99% rename from priv/static/adminfe/static/js/chunk-15fa.b0633695.js.map rename to priv/static/adminfe/static/js/chunk-15fa.6dcb4448.js.map index 5caa78e074be372059a0f14d2e5de5779654cb43..9a7d1241ae773d0c11ad5f0ee12b2bba103bb6ef 100644 GIT binary patch delta 23 ecmbQ&!8osjal;`F4zrZxBoh-8i_NDvLaYI1od_5J delta 23 ecmbQ&!8osjal;`FjwAy!V`DQ*)6J(iLaYH|ya%@c diff --git a/priv/static/adminfe/static/js/chunk-1f27.d3c35fbc.js b/priv/static/adminfe/static/js/chunk-1bbd.bc68e218.js similarity index 94% rename from priv/static/adminfe/static/js/chunk-1f27.d3c35fbc.js rename to priv/static/adminfe/static/js/chunk-1bbd.bc68e218.js index 14fa24f54e6c5a4e267867944bf1fb902c5a4e68..ecce144d91e4633a891c27351f9b427c37bb2879 100644 GIT binary patch delta 34 ocmZ1=us~pfEpt**%0`E??7~1=FDco~BGt&yLNBXWFE_CO0LZutF#rGn delta 34 ocmZ1=us~pfEpwWY`9_Dc?7~1=FU2_7*fcFESud+tFE_CO0K8%f*#H0l diff --git a/priv/static/adminfe/static/js/chunk-1f27.d3c35fbc.js.map b/priv/static/adminfe/static/js/chunk-1bbd.bc68e218.js.map similarity index 98% rename from priv/static/adminfe/static/js/chunk-1f27.d3c35fbc.js.map rename to priv/static/adminfe/static/js/chunk-1bbd.bc68e218.js.map index 1ddd765a5c8654ec38f7ab00bf78f24d04a2ace1..c901677be02bbede1933e44a6c3603ab72c8712d 100644 GIT binary patch delta 25 gcmZp2Z*t%8jf*EKDMc?S*~}u<$k1Xl6L*6I0C*n=FaQ7m delta 25 gcmZp2Z*t%8jf*GE$XqYQIN8`VEh%|36L*6I0Cb=TF#rGn diff --git a/priv/static/adminfe/static/js/chunk-3871.4ac23900.js b/priv/static/adminfe/static/js/chunk-3871.4ac23900.js new file mode 100644 index 0000000000000000000000000000000000000000..e957e4552174d28ddfefae63819467f07fa577a5 GIT binary patch literal 28092 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 M-VT2%=?{@~ literal 0 HcmV?d00001 diff --git a/priv/static/adminfe/static/js/chunk-3871.4ac23900.js.map b/priv/static/adminfe/static/js/chunk-3871.4ac23900.js.map new file mode 100644 index 0000000000000000000000000000000000000000..8bb213374d36bdbbba08d7ccc645fe9e350cb393 GIT binary patch literal 91362 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 literal 0 HcmV?d00001 diff --git a/priv/static/adminfe/static/js/chunk-3d1c.20303ef7.js b/priv/static/adminfe/static/js/chunk-3d1c.47c8fa87.js similarity index 99% rename from priv/static/adminfe/static/js/chunk-3d1c.20303ef7.js rename to priv/static/adminfe/static/js/chunk-3d1c.47c8fa87.js index 2128c604d70d6dcc83f4140d4b0f19f1d86c9aef..d3a26d496e5bca06e8dbb6f9d59b31381c44c4d4 100644 GIT binary patch delta 23 ecmcbndQEl1aUp&a^JI&(L<@7htYW>~!~y_n;0Qth delta 23 ecmcbndQEl1aUp&q17ic@)HHLwtYW>~!~y_kB?tfj diff --git a/priv/static/adminfe/static/js/chunk-3d1c.20303ef7.js.map b/priv/static/adminfe/static/js/chunk-3d1c.47c8fa87.js.map similarity index 99% rename from priv/static/adminfe/static/js/chunk-3d1c.20303ef7.js.map rename to priv/static/adminfe/static/js/chunk-3d1c.47c8fa87.js.map index b3d1eb3ae5d3bea2f9886b4071a2eade2a57946a..d10007b9158c47d2a97749252f77ccbe1256114e 100644 GIT binary patch delta 22 ecmcaUf${nT#toI?>?Y>P7HNqVn;XQh8vp=hx(IFn delta 22 ecmcaUf${nT#toI?>_!I02F9srn;XQh8vp=e#Rx0_ diff --git a/priv/static/adminfe/static/js/chunk-06db.12facc20.js b/priv/static/adminfe/static/js/chunk-538a.18908e98.js similarity index 97% rename from priv/static/adminfe/static/js/chunk-06db.12facc20.js rename to priv/static/adminfe/static/js/chunk-538a.18908e98.js index c8b2a5ce99bf70e27ef9b300e062a0a712533e77..334e111c10e52090e8a52149bbc2bd062d14ce0c 100644 GIT binary patch delta 36 pcmeyN{zH9&4U4ICBh;ghMu8?rGZ7NrG;Krv0iRs0RZ4R3sL|8 delta 36 pcmeyN{zH9&4U2(UO43G$CBh;ghMu8OT4Hjtk%3-Tv0iRs0RZhk3>p9c diff --git a/priv/static/adminfe/static/js/chunk-06db.12facc20.js.map b/priv/static/adminfe/static/js/chunk-538a.18908e98.js.map similarity index 99% rename from priv/static/adminfe/static/js/chunk-06db.12facc20.js.map rename to priv/static/adminfe/static/js/chunk-538a.18908e98.js.map index b07a40083fe91a8a81dc36e365dd72ba0c9ad26e..4bb072450dde46da4dcb16ca1e8d8de1741f9b24 100644 GIT binary patch delta 28 jcmZpg$=EcLaYK|8uc@&`qMo6JrGZ7NrN!oSDQP1BeM1NO delta 28 jcmZpg$=EcLaYK|8uYp-glAfVaT4Hjtk-_G4DQP1BgpLT8 diff --git a/priv/static/adminfe/static/js/chunk-5913.1d21a547.js b/priv/static/adminfe/static/js/chunk-5913.1d21a547.js deleted file mode 100644 index 8730899632a1cce824d0413cd382162d9359cdaa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 27091 zcmeHQeS6zBlK=lcg~Ijv#0|yRY2Bpqqx#&W+3Q}D=JL|LtLydAA|$h>NF7PpaTVQX zzu#a00(_HYC+XIGy3ezzO#;KgU@-3k_b;O)OfUUQaXJsi=YPx7WIk$n_Wu3RvFpzl z`C0$y$Qhq4lJmjdm%E=i$KKL;@Z-)yXE<3T<049vzVHe!cb6;A`9eH-)qLSBa?#6+ zEE*Ti{zZ`WvQgf5{ua(Bj=P`xNk4P_VC6l1`R1$vw0-Wybg!t+{UHuFoGX#Hze|WT z=B~U~fBpHg@wkFc?q~hNH7iPY(p|^(k`b@Ti)fiw|Ml`M9tQGZEt@WS<`qh%Nrfx*^Y zW};YR$?_tI7h-sHyjpqVbT(fUA{;KG{He&#i*(+c;Dr3f8I4B8S(N)*d68zq&x@cC zelVZ=p}2^~0`O*{I7`EPh)>PZ3-SGYp5{>@o<(sXvfKtuTP{Z5pZ+Yyg?}!t@_rLE zSR~8+2~T$YSR~Wp%qtWUv^rm|#m{4WzVCYZsPMtc;@$TXP$?Y;F({JR55+qmlE~+Y z{X78IUI>hi?5ClS&#}x9oE1&_g+c<95H6GR=$KyQUk!G*w=?|fg1%V~Tl-aZkPY+w z!q4Xty};XbS0V=6V|*gVN9Fi*lzbH(M8m|4PiZ`VqgSlFBArg-HcB$x3^kZMtsBs1 z#uQBoW+GgGg!b{({L-8C-98So@aFtO;oaMD-Icdox%>8}g;72ait${~W9NmxHO>U~qOJiv2sm*XvoQ={>dILPy%!%qj3NB|2jC<-v>Rvet-GfmV_uV{1LT+PAqbr@zg3kr3RXc%aH(IwgWFSmYb5UP*@QP}BnCChNDw%XRSWu_YCV64;hw z@7}JLEaKR69_^lvzc~Huu_J$X_fLGLeeR!}K#+=&W1UL>Vj)=ho{dg?a95fYxpz5Q z&a-rmL%}CSgmTJoS%6)JhglX}d7+pDV0FW&T3C=)vDHd`mR>#uG?*93vow3I&Id!Z zPPvwIQoYE}`<1UNx%<`5$}5j&^Xs?KR5K{BR^3FR4I-yRQra^L&zD&`i*hKxIEGFD zh<@(6esLxeBSPuuZq0CBgQ@5<@tk}y9^;7vMJb=#N7E-|91E#yR>a_2VirN<0KfQZ zK>U?L!LM9@EQM@|mvEQkG|AIg_;EVzL(sY_B?p(&EJ%u1(e%vPDp+M|6(tCkvYA;c zKPW~4B!1!tSYdWvuB=zUIMXob?PUf6l)xafCTZ5+-*@xNh}1#hLu_3@uO>LhKsJC1 z+!^W}mrq9zX}xS#)YXVYAF^Wsi8ai;Os*z}?va2V8bHBmwwQxp4B7FZd7&cIxX*?( z+!0CWm>KvWbQ&180MT5nJj#`TvL%*s7o?5i1=(~#$|uK67#$H*P?cBNVyRH1gd@e| z_V&HP&#+_pQGUF=T`rw1Dq5%?h4)}>;XkBg`;nIsnjm9(F8K_!NSU6b{mF===m>_$HW|&44wa6l1%g zk@uBl;ze3HHzg6IRMpI`B zhXl!0^ZfytUhv+a*Wu{}(;U9xI9?!bf>_Y7uOHpOWkh#d^m~qold{)xc6W}B9(s?CkI8t`C;fNMUoGs_eDo2x>y?b(ftY_V zm}W58-^QJKlXkeW5GrKCsUW3tF8i);rgF>-=WG&Xv**e`uBITV|qWevX$FKyd)AjDKsfk;CoJ!=zW^w`Q5Drmupt{82V zkwy!3;9`ulO|UVeD_?2Fhm><=)U~*<6|)&04d^X6up;gb$aHZr&)Wo#_Ak^>DS4QZ z1rRp3;$Ss*6Qr1oZ69h!Y+N~{W|xrMVx~dD0Z}6U^p+Zp)E5P0=yZR*D2goI4zir@+h5IZZ*>*#Pm9tdK!)D{6nh0r68wdw{M4|4C@T!WpQy96m?d0v_kO zfdg!45kv_LYI_RB-lO_V#JeL5ABZplZaPx^Q_@rabMDt8=sslwGGsN#VnZ*4har$xLt_QHC|)Sw%;^Ndr)RVeM3$VjuKN zP$jQFP>BgWWPm*24D%DHGGqoRH$vNL4wZ2Rgr)y#iWcF}a28P<6b#Dw2t%tTVWrkR zMbUjV~wT><56<^OF>%qB}+2UYg3WW8U3&!{eI{u~BOa7P$TB78DqjPE3;-FgLg zoYka=c$>sgE)(B(NUSYNr0*odv24EGBJMcPEDPz5FyFe&SeqO3+c)Vx+KPT3OnR7H{XRIfk(j;P?D=8K*@!pE3mMit zkB<}NyFM_-m%YO6!LN|jvmc^7qMC57-uya6#a@tf7r)-yB_-F< zb&-fmS}uVdE_H1aLP?&O1yM{t%u$|xnPwbc$`+^&v<4ngt#}03X~ww(^{b2-(}GO6 z{4`!5Ons~XcTW6Bg5{(a0_dq#zI*)5XmgP^Op6 z=f9DIfD@|L(e0wDoVl&ToJlI*-J+vpuSzaNBPRLR&{j2^sp4N*wu1}?NYaxZRP9A2 zK4La6t|SPBkLz0JO1?mV5fDg3c0!2~iA@#G%F`(7lk#C$YA6O#+S2kPBvqtDE7|5Y zqx?`PP%`XSjdadf*o#r<}CH zRt~Gk_$uiS*x`Z89WOI z>R3wWB0=7<6m1}nN()Y)2NAV}v>7^<+$sa>l;WYzCF`BXIuYPVH+qh2ZW>&Yymm0M zBEUfa+2A8nu~?1ODMeq_$;e8CRjoMSbj!dR*VRqs6Lsk9fqtZduoQKStnbM-F%zI> zuCho` zOrzhToqIwS0_!^>? zqZH$j?Qhj5z&BB)8R`IXjlm>EkuDWt)P7Zxr7ZlA{g<-FK==)#jEL8sWYbkQYeLcH zG%HeP5%%#)3CJ~d4aN%USEQcht2UB;e8xCl)ao3`&OiDGElh74ZqbXHn`6A9C)yJt z3j#f9#1d&jjZMR)e8j!G!!xhpS`AOVI^5w~?P7JhKskDB$7kf~RJvQxith0uEB@H` zq&uLxr1E&Fd=56-)hq0waW~>L2vHqnbhTF1J+`^Ixr_w9kYYPW$+nz z&VuZWx30Wsx{v5a4`cCEoPx#2;<%C?Q3 zIG`?nF1C|S<*@u+H`;2add~9Hy`dfuXbO8gIa*9tBkyb&e2qnW z_lkhOr2AW%Z7Wy?WQ5fwE4b@}EVyQyta`Zv?MSajpLzDVH$;6RdBEUG`j_&$Zk3V2=o5|~ME&bo9) zhY~7yvZN^8?T!n6(0xyy3LP7AA|)iKPc7ma)>FfM=J{O?Grz6h=%c>b8iQ#e3dwQm zWTM<6aToHNLH=QIQWrKw1jiI{FGj*Yij8o>d9tCyPlL+}O1qmy1Sj96d7p!1R)dFnYk3?K*%IV-} zdFK*Zg(-i?@xwAm+DS-8f>&+qHbSFj%CXAee36lmQa)B_W?W<#UD%@<#lRm08M=^8 zVU*#1EOcY!!y}axRX(xfWx?eO88*5Yl+Tb1z{PQ-CZywBi6`no$UDlIt~{L5qK3CG zuituITu~~OT^eV1XXnol6}pU?ZmLs$x#v(H3$BDi*ft7POhs)aIDWuaYSCu$d8}z* z4(ymj9VfWul5tkz#f(~g)Mq(*!#n}d4n7V4HoFk&>M(jn;R=QG=buAx#L*PjZ;jz# zQVa^#1LRJ%t!CX99h6m@khO&CHDi8Qwhky01-iDdv7RQRj^MPHj}e*=U!!Lyrv)tf zM@vu~4Ly9ZljBV+SE)3J>5SuoF|2$*9=t`!6sc;-E^jH)@fx-U<+HP3hFDS*4(WQ7 zOS;TP$_y!~@ejJ=<#TOw)*^1ro-Wt{);H-oDu)}|xNnS0);Yz%fkP|A$DfjI`e@fT zc@TAP8K_{M!NXFw1QJSR>(udTbxe6niNB?3+L>#!t){af?;-nuEs;mMP8nXWY0n4` zoRG}w3>>W;td_IVK{p$YO)2ZSLr1~Tkkk#`s7Mr~Dii8kler~~lJ8_oGr9JP3oK9| zaz)VdTI7XEzU==}y=jskh$$Ob+q_f3ihIF;L)Cw?FSO%N5a#ygTf1(fLveGo?xoqS zQ7R!w0*sJ}_I4SpC)>TU*eV%K$d5-XM)Sxqv5y)`Tr zJEeuGyCgfS4i(8rjvFhMN0U8)J*_B;TlH3gmq-Cr7#1tBvA%It6?bM@?i!yFsz;~* zzOJXz+~5Q!s=Sm_dM6CgThmWj(uw;jZLa4-XdkRf|6)ZDwDw?@ut_>F0j&e}D=yzU zR7N+IEu|%zO%jw7E83*dXxJphb1gAiS+biPPwOtWlABT3#TMGQQf-S}R9(}|X!DQu zevI%|Ot~rDECFAknF6Rg^lEy<%u+tcCnsBHYiAoZINY@{CF~=E0teHVxbdpmB>-#I zr6Xx6VBt?4Wpv?3D|S+C$t%t8kHaBl07uIf1h~iIy8b^1;%4KvoT)k;b@V|WMk%>j z8m(CziONoSj^r9{6j*7|vrcv^5xX>BqXUeZn>~H3(M)!e$z--tCf0Hdo21x_57L z(pA3r&!Qdki`A2KOY$|~+=zm5w$+lhIT7pM{b$k9G~L{ifCj*gC|6D^BZk+X*>cXU ziN&ITJ-ZK-j_aF4Zb{7bKyO4~1*+Lu)S^DR#pGIqZnh0g+wBwVMoTZm2W~|R#M|t{ zVYPYYR-4d7<! zF%!01&q!M=r7c>0xvO?Q}QZ-Lt+B>o1QYeZ1P#7Y*PLi z8LpM2`cA1r@u$=|G!Aqn*aa+ diff --git a/priv/static/adminfe/static/js/chunk-5913.1d21a547.js.map b/priv/static/adminfe/static/js/chunk-5913.1d21a547.js.map deleted file mode 100644 index 3841396c45564bf1a7e72fad1aa2cb8cbbef1d01..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 88770 zcmeHw3u6;UvhH6|IBaYd$!`pJ!xCA3;DCW(SWY%@eaF&R9;}DaNHz}3{q6Vrs`@pf z(aRV@vdOZtG}B#Oudc4HuI~PL<2>%ANw;%jW3_xE?GAeNIK8p)>y3-}=q#$AZY(Y? zE-a?K`r>&KU!;qJ6vY>lP9y%Xa6X71E=R}ljq>=?^=|vD+lf2Tw%Vz~PPY*sw!4i%D^3sFNhdk1r|Dt*q2G(@{ll}?pqX?I8%f$fY;~hX z+&g@iX6vxHc_nzVKrcQ%TwXq0&X$D1vn5-}k@=b)rv1xS{Jfj?pA{gv`XoJx4^XXE1o|w6JFAdyS|c;m>*08gK|;{Xsu&;A@g@#p!9kdxr1*lO)BT zn`yt>W3jX!_4zkC@bvJ!%}*A-Sw{=~c)Jz1!81N38i#4zqQ^!7CiPki;|YF6{l4(L-io7Mbkrh4 z&RS7DKIyhFGUC@wLTDQw5MMDb01hY6BbLP|8M zQc7=Z1y2oH`c*NJ?;rSS`!l{aGM$^XSAA{$s_cH?Qz$;5g~E_njUFjNgG0=gV6r=bw9?4Zmr{{W$+r zc~H;;9X*Au`#wk-*)u8mn}!N}HQzP|{XY7s>SjIa)Z

Q3f4Oa=|YXq5zN-Mizhi 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 UZ=~G;5^G=J1lb!KH*S3T|BmKn5C8xG diff --git a/priv/static/adminfe/static/js/chunk-598f.dd8089ce.js b/priv/static/adminfe/static/js/chunk-598f.b02acd71.js similarity index 99% rename from priv/static/adminfe/static/js/chunk-598f.dd8089ce.js rename to priv/static/adminfe/static/js/chunk-598f.b02acd71.js index 618a2ee9f9fcdb66358fe2ebc2df8202de3813eb..fb2374e3bf0730e33889eeb49442937c9f524331 100644 GIT binary patch delta 25 gcmaFb#rU*~aYL9Zf0BVwVseVPp~!~y`wvkR#J delta 31 mcmaFV&-bXGuc3vpg=q`(<#v99R5LRROLI%TtYW>~!~y`punPkK diff --git a/priv/static/adminfe/static/js/chunk-6292.0e668979.js.map b/priv/static/adminfe/static/js/chunk-6292.b3aa39da.js.map similarity index 99% rename from priv/static/adminfe/static/js/chunk-6292.0e668979.js.map rename to priv/static/adminfe/static/js/chunk-6292.b3aa39da.js.map index ecc2a300373be0bf645cb4ccb1d80e949f81bfe4..577df8f956b636635318b4152c749f4a21db9390 100644 GIT binary patch delta 56 zcmccHu64IvtD%K)3)72V97)EBiN=;GiR~YLF#$0%5VHU=D-g2*F*^`*Z2$0!^KAzJ DC$Ao( delta 56 zcmccHu64IvtD%K)3)72V90sXoW)_y_mhB&YF#$0%5VHU=D-g2*F*^`*Z2$0!^KAzJ D4=Nq~ diff --git a/priv/static/adminfe/static/js/chunk-7c6b.c306c730.js b/priv/static/adminfe/static/js/chunk-7c6b.24877470.js similarity index 99% rename from priv/static/adminfe/static/js/chunk-7c6b.c306c730.js rename to priv/static/adminfe/static/js/chunk-7c6b.24877470.js index 24d1d447a55c29cfd7bbb909753650a8888604b3..059bcf3223073733caee5a87825e1315313ee296 100644 GIT binary patch delta 23 ecmeCS>$cnQM~>gf#KPR%#N0qHt5`2Lu>b&I5eJF@ delta 23 ecmeCS>$cnQM~*+)*uX5=+}J=bt5`2Lu>b&MOb6Tm diff --git a/priv/static/adminfe/static/js/chunk-7c6b.c306c730.js.map b/priv/static/adminfe/static/js/chunk-7c6b.24877470.js.map similarity index 99% rename from priv/static/adminfe/static/js/chunk-7c6b.c306c730.js.map rename to priv/static/adminfe/static/js/chunk-7c6b.24877470.js.map index 0384ad316856cf09bb091056341117487ce61340..cb00fc3ebf9d73ae9e6b644b7f6921233c517e33 100644 GIT binary patch delta 22 ecmX?bj`6@b#tpv|*o{mq%*{>AH!~{Qy8!@e=?B38 delta 22 ecmX?bj`6@b#tpv|*prP7%#zKGH!~{Qy8!@iE(j9< diff --git a/priv/static/adminfe/static/js/chunk-7f8e.b2353c0a.js b/priv/static/adminfe/static/js/chunk-7f8e.b2353c0a.js new file mode 100644 index 0000000000000000000000000000000000000000..9a0afaf67616d6957479e1da58d923d3acbd1caf GIT binary patch literal 9618 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 qTYGcV5?*XP$n8%I$A$?`-=WY#H!_d-gvlF~)2F literal 0 HcmV?d00001 diff --git a/priv/static/adminfe/static/js/chunk-7f8e.b2353c0a.js.map b/priv/static/adminfe/static/js/chunk-7f8e.b2353c0a.js.map new file mode 100644 index 0000000000000000000000000000000000000000..7b1d18c708c340b2cb3fcad2507909b1e80c3385 GIT binary patch literal 39890 zcmeHw347Z(lJ;L=yuEEX(Pig$cP1^%vg0I<6CXJ`&L>k6EzzPxMWlRX^56HZ10YDr zIeK?yXTN7MJr;=qstQ$wLIK3_W$8SaCSg2YTEAOfO5)kH8zf8XAD1qI&cyEy*K4&} zwKk4>L3JvTqPXWLgK976#ItcXsDaMQ-g<_p<@Hk)W`?T`;tF@mmkrTow9tGnRvgrCz;3Ze%uJ`|rkE_SW zwd(C(exBCTd-qoFFMTR6js20xzaPaNKLVz$qgTy#Ykz-lzkS$xe~6SC0X>Wt6AWAb9OhlVE3@0yN1U39@Y{?{&_C zE@hz9gCIJ^VR_?eo;V3$VU8k9ADsh(@zOSm{@ks{bnO=rN$^hMv1jFQjWrfE$ z9dJfQu}2k}29wC|3WW_LtTafddPx*AL@XZ(!Q~{Lrex`-s5yQv{E#xn6Osa@M}Cqv zrhW5xI0)$xHGC&Ir$I7{s7M-)g6U3Ai!{OMAyV{pXM7$H1CtQ=JrOC;$U{2y!$=+v zA%xa+8guoz7%4>w;KvtcQZNF(kT64YJq^bJk@hE(h>Rl{#IvX;RPjo@9fWAxFHsv} zP1B~W%Skxpl7@p;uTM-TK}y`lGpgD&82RBC>}j2iryz=9!c>HJ@HV4nL?R*v@e@g9 z6>ZlC6GXj>^l3)nG@2!3IqY7@F*)u9ex*%j*G{_AaFVtof81}MC2eHy`Kd2I!9G%j zaevw+RI`K)qdS{U!O?4V}^F80oWkfwpE4$f82|L zP4&+QkX(1LGe$*_N1S)ts00tEz8K`^S-_5hSp#G5!gR0|Myc>HBA%E^FH9yrxF zbP)H5#c@0x`B8YyHk+jWXo5sCBl9f&(jj+BC5WR_OT+SJSR!S`qcGiI8%_Ob+J*{A z^l7|vuxDLOFzy)u$*dicG$x`#v~;|XBI7X>>ps6SOjFxwpOgP=Q?-5(k+!0MjFVES zJ+;;KwTjbGa8vfI@D zM?Q(FTJy>A(~m?gcp@FpPo;OAJ!R0!ka{A{FuZV**?Dqubmb(E$Z$D%}EK*JIIpl zlThqgiri|dwIDJ%f^p?wo4Md8C)uBwWPj!)Yc-KOt0`Xk9h1A?adL;kKQe??9rQvi z6Kv3vlY1`WLH0xr2&7Y6LhETOc`8ac zwMsY*xyWf~iku4JQ%m(!tmAYFp-5hOvf1%MZnIjW3|392)|S`>0&4A`FIf5(MV}m2 zwbt($oPCjC--@pvk1CO`O~t&}+)~HBt>ZxSGO$V+NF4{ZjsvOEz%n@y9D@wURmGQv zG@vI#i+yXg7Ft$0Y(aum0Y`*LNR3$d$(Fa8ITIlhgO+Gsp!yFbSTYqXw?&mu}9X@Bf$|_92~(`Ymu!>B&4kMz$^^(fP>gd?a-l*}9BGX(Ow&k+g+TQW;4l_{j)oWK}Yf zRxz^tn(VgDW2y7l)_E)@G`1BO3wz_Jq8^x^46U){dMsRzE!Pvj!o!(rjh{@3iQt>q zf)inKl2$rWPkyr5E$>s&#?)$KDjJNNkkio)kY%PNUSyzVK1@l zC7;>$lFx>tM7T`Q=&z=lTb{EV_DK|#SdAoNFo`voM0(i7cCQ?&S8J&?eh&PQU@MUZ zA~F0LI;r%9sqG7SfQdrZEu~RqBHoIhY{AGRW;vyPskOHBT55@3TkKjIV0!JiC-dTD zGLN%nBJ-5oetW1(%ru~U|G3Bnr zoUW`nT@MV8{ABXsWgu+wlari^>X4kO_B8lv#uzhPl9HwYSx-KBKS<)}oGdeo){t+8 z5hh1##1}!$=(dZf7_W$Q$(Rrao6_#YOdF%Eu-#1(1b!H!cGLDGn)SnR8^aci1u-nc z_yD89Tp7x)AP#9M!!$T;udcRN^Eqj7sX3#tqmM~DNw1>db)2Lx7ZaEO>bUs&@NbYvXmJ`J%nE`+9Hxu(jFV-P=5R*=pBWp<|lhLJU5^ zXaIAJOZT=KjYjzZe;VcO#!;iZgQsS>MeoT*W7|CMWRF89+0T+cFHY{-98Hk1_`(fP zcw2shzwgTY@*YID8y|!;r!)nyB|ok53tRj-cx^OV{CAtVFL#k3RUqzPH(r+akg!`m zYV4s78RaI>Pz`q)O|9D&r)(nyHPfoU1C$ZGWq#gqmQAGG+ZAcGIK2rDkWpktLfk|$ z5~x~|4EjWk*e!qH)a}N-mf#RtTY@7dfi$EzaZZoOx7`>3pV1EumUCb{3G-Ml{OlGim*t)J{#C{69v(D$xafHbAgFQqr-)| z_$$vZNY2mLVoA58bOJUThn$79Yo0~BkQqr!1VseV;+f(O7f|D&wjh=vrN{!%X2|xB zSfwIQpKChVz)!Ogi#9lkz^@vD@)e$U8_$7ZTPTGanGJ}U4WuP=(a$1&$BN%DXZwI| zH}?4%7|6z`64%Q9Yk=9VT3U5Nu*Je41Kw`zh%4A;1Zs0^2-M!UA;TkvyaI1{!!s-z z!lH)vmeW{D880mP=U|9r{to$f(0)kUB>5(%h=*#irt{kQ6FCEs2E=AtgtTpy#yt^) zNQi0!1#CB-D>krx1Tx(A*`mqjr=o&AaL_VNm|Hsel|#^JG>8P`wAVO?9g?NJ5zKqQ zEV?GWx8?=iAne-HNGGjl&js7?M^h*^svZ$HiLTYizOsG#ZZP6k!wAPYA_K zU}B^R;gsYeUZ+@W2YkFhF;?p3Hu?$EAgD1>X(Tio1Ca+xlXTuNKrDz{QVYel!I`>1 zGVVhpSfPqRNL$%EP} zTC*Itn%D4hqgJy~--XVbpIfWD6=+`+;PKA6krk zSF!Il-c$Au)U``-)a3wtMVm-zG}dlHaKP+rArsY`Y6bm$zt|CoQMY~6c!Bovrf~*a zc-IKoO_5+oMR$oNnK`k3Z=+nNu7L!>tkEMG4EZB!bBG&IdY8FJx5fBokgIfZB6c_w zYS+}sG(pmliGjc#srpp&CiVPKpga>PRUI=xf58I{(?bf^ zn!P^S^|Qk*vVl7J_(tQA2xrW={aBi+{G|qfY?8rfn+$D}OLBJ&|4Ix>fpcnkje6KZ z{m3?65WlNoOCum;>^HZ$!!ADn+q0%1W#g5{J(j<8FVqQ;*a(w^UF8vsQoB;g$pWEn z93q07xVNR~NoPC6*}aYPZO+7OUTm{=II_HI3Ti4B>=Ag61_G@1#*u_7Pz*wM#YrZ? zI73uIeG;_=_C8dft4(erFT_o$pN9h=6@^V06==MvZF6-WsZ5CAP#Xs^&HAr5HJDr# z19`yq?l-yVlAXjGKueYWOx~J1Efzt5=Y*4DVM6h6g&%Biw{DaL|3bQi#euEaMk+pJ zD4G=K^LYb}-ytJfHG?J)hMrN_bnzsunIR&1#hgzm*T|4i8*8z_Up^BqMEz_(S6pf8 zr*yDDy$d%55>}?{p|K?%C$MIc=DZ4_o^V6-OD-DvB}HzE2GIi6PzFCQJ}@J2eN^G| zjq)0_mNYLGYK>T%)7m|wH8>Z_@QAe*sMdVe45h2;7S?HG>h!Y)tTxI6G!{j?Kol5- zLO~)6un(6DY4EV``k9zM$*2B|d?_G4{xr)}i2N!&O9@Ep-VV&0q$XAUF zMr!w^qrbya8^Y@lNv4R%(fiS?yzobkNCXfu=;N%ctFx_(QCwqoUEa#zx6Mba?lwGX;$#?_!5Uu~#! zB$XzrG)RJ)_*igUi|={R5*;$H>W-+nOj>Nwcciv88Do9UI?cw#GgOz92wu4x;TFk~ zN}34Ms39l~NRFC2w8Fzg&dBw-xFKUpyaXH=VS&kCpc)AIK5Ty&$0Rs(>J(!@%lNa4 zpc$4yS?@~+di9J|g$CP13nGB&S7)2e$%aIicZgSt7sx#D#|De?xH#2cnD?KXx|p+n zr~Xgr?Pny(J-HD6=NiFxsr^hXzKhJM=Fi~$N9Fxj;Md_nI<{|_-#O!Z7zH@ETRLU*M#i1?t7s&%TSK4I&uMzR)wm-As{Mz{^Y5Y2|Dh zc57!ztvi@uaqPp>M?tl-cK80>Ze7=_=vEqD0)a)u*ilovefxOq-S%XevsY?jd2l@P zHfCYeTk(D$r0FEVb{hd#v0}OQH_p!)*1Z!ghsD9O$e&{Qtfy;RpByiZC!^yfuM&IJ z+6fnG#*?dQ*dL_c*&ABe?QLNJH&)nUn}Ubs;T~^<@VZ!_F%3I2END+M4$gQfcr~8( zm4p2N%kPt*=h5DQskggx=+VxIaS~*O9JUpv+;c%tJ)*K$E-`*Nq*dUene6j@!K2iRM1;3ArdLh-5#8W#SACI9A?}%#UCmyzicqeu%#fhgYnmw#i_m1s0mjp7_HSbkM za_&#P!u}lZiMNbx64*FHm_6DjLPB5utw<6JOi&x_Z%CL5P=)a<@lIqj%L(ZQW`@lX z87XZ10s6XE^XeAeiENAc53|MQ#FH&1_%Zl11a{95arsfU?0B*{=2L;gH1@DQzdP`B zM+OVm?E3I723Ts2eE{7cLoS<;fVqa9P|2OY)1O~zeak7!8fbe#3h8(qzvm@k9~)7; zL2!w6Io%Lj2)u;%4|%*n$Eo@>FdLhgz{kp+PxZ@>^~z(va{38m!W+r(o}O=_l^n!( zz4NiY`b~5(ZK3)WHr`{!Jl13I_b`u*+H#8JfNDmLlDrG-x4=?(*i0{&U>QD4Bc5X}^JYBHPJMywfRM&MT~>LLwkMuGTG)43qWu(wdeJ7cj04J0q|a%fK);J=6U0(B z?c2{J?ZE`8xoiETZkZ@Z+F&8u2lc`GeXgLF0>16i0Xuas8uM9Fh> zZq#;0Pw2m<&2k_sBCOiIa|S2-bkl%z=^NF8(?6b=#*f{AC!b2xlfbD4-H{JdgZHDY zb@AAb*tK%<+Jm*)!)m?uA4$-ys0b|4YKrA*w2N;Phx`IqbDKzXT>D`DDY_K3OQ!!r?l@Qzsx(1GV$5%gkXWT6IQWDt_(i$ z&u%ERpm3a%=XQ{a=eHY`Jk*tJW6T>0>1`B7Q3xT$;~wSpQ*Yp(qvwr$EU6?11_#bk zqGCtm@X|w%1Fu@v>Bm61i3dp%8%(?}!tz)2cx04?dND!o54hRUB;t}>x?RPNpkP`u zom2_jj3z7I7lwM=1;tTN#m2N!yNd0Yr4_FN=SP_!NyIW=%bo%>p;q&r{LRDmG2!3_ zL53+>VLarHj|9hTU}K>dVh1B?32SAR(NB}HfiiSxxfBM`qPh7|gRPQ4ghBB2HK*k! z?~hB^ugZ?)o<$`Z#bzu2Dq-3~SiN^blui)rq+#Uwr)WUl32pS0$SP+M^TxpTL%Tpd zvMPQvo9!IB7e2=!LNn-9y+fA59Dj%c5g%~V@Vyh+4ry8eIWkFMRd6jJLghY_B&v>p z55;-cq+2>1je=eX(M4Cv3Gq7;Yk^BT$M!AnMAwa;5Iw9b4ao`-0H7t3U_@53fz;~+ zrbg6rAp>*fL=ZJL6!OkCN1nSqD5UMJ zLsb44<=|*~GxGT?yMi{TAQz^Za7*w)M5=YIjjbCcp88SZR>C%DDq`9>Ba`-}(jQ~F zHniUzmf=&;#L(&iImz}FpO{u)>k3}#Cz-qt5bu24^kD;DGoE7GPH9_p1p})HTbP3$p4eq3HR%hfyF&bm-Ph=vaB_lJ zT6k(gZM1;&A_x$*k#7|PR4KKqN+58U{sI4AuddGd|1BL=upIwD2mv7=;bA;#0^r#i`gKiR(brEb_eB+9@v-9nhw9#eax~O?&D` zvyph~%;Uf_#24aB;M9z7?F29Y__!37MQwfv*ZrM(nWp}P8uxeVW%A}cW4>axzt6Po z_W0xA!YVrJZj{Gj_m951aUP?+bP?~taljpRF||MjgFa_~owRJ;7jXLMfN8@nwP6fi z(2-mUPMKUNNtTsrJf^6aaxPO~U`(!LFylZ0+U{HSR`1tWBw}Fkfs-8;C$;!-Zx`(g z?R|iD)Wu{1q+B+&A}dI&7bljZB*zM-DrncX@WQ3@tQh=d?{2+bwxQ0l+TZdzJnSr> z5b`!+$YrAvq_bsm`qu*Qt#L9V|J|VhGh&|;krUg8_*RJ`Rxxa$NRm1|BO6FRs{;i) z8Q_U4LW8^Pt=%WSy=Ap!aJE{n*S)VAfe{Xwd_v4b_Ryj|)CYXBOKG`=$Ug8#%bHpB zC;3HYI`sjmSx)krYVx5BY#K&7SypZqP6VgZPdN*j#1jg8$wn-RszfqE31Of0TqXwx zsb!DcRN3^J8VFL2sYk~77Kz6G9w!`PWM&goT=rdhbo)xp4Cis!cS6{NW)6{_OB6$; zWXQLkfuw_{IF))pn@;`(Hd{=#lEZi#_QnybM_qSGhu`eOZ(;ClB(bcu^&t5v+9Rpf z&RCTSvrsHY?<=g8B?j_o{E7!PC6eF@4P zpIkp{kK`dVZMS)~r2NzTFty+OvWUbkBBGOgUBPRivaP5hgljF7jXeu&I-$HrpZ-; zqf$hZgrkqcVK5`z9bWiTO!UxzSx`}LnZkivWYCU&FDRhJ$N)t!H-xEirjfOz2qT&_ zZ>}ZRdU*EKdIj>$Ec(3N{Y`9x+@dh!Ez0$~S7*mrQv1MEY0i|?Ac0KVxr8#*RS;I| z3aOB3keK!PA|y?VfSvMe5Vt7lL*@;~ma@*tNGx+fihw&R&x5Yui1NNf#m4-6+iNUn z$+jEJ*G>C?j15!2jEdEUYn6LEyp})}UG)WYO_PBqrc#g##H07WwdZca%k8ZWdud6fp zhC`6JEHeS-kBkG#xP?i7i_u}wJ*`Vz`%cgFQ>tp>DRYG2OrnvwF4Kfy`D!fIlQ(C@ zJmZz|uC=wLUG@<$h!o%-b9TyxzSxkgCNmPn7UH=0?C3L8X1=*tLvu7|XSYgdO!1S+E1b(+ODSye;)5jp%MGm3d(+w!vJuR|UfgS0W$ zrZuT5+b)3o%P>V~vmg_?)D zi;Ly?6PzlsQTimCOo?N{(9(?NKc2L0eNz{3d* zWps2>U%><@b-$b_pOH$-%PS{Lf@|^@=geNB{!(7&%Wi*_?^-gr^~mEZ9j4OXW%Y~Loq69&VW{1u(7F@z{j?riCC9i|P_86Y?sy1(P>A31EasXB%@oU`oyF1sU$k;i z`2oT^1tIY(dU4@bPsPTR(jmNEnV4YWRQ)iKbFz_{SLN~Yxhfr*t-|7*Dp&|!bEBuH z?7TkC%}s$F{ypVIRjz{EA;NVr61@>+n z0lP(mjAN8|?Bu|*J**0yV_}B#oGSuz`LHJ`GhJrHImeh2}rViO+Lh`J(2cx{)s<6^21VI00{+pR1AI38G9p{EF&0 zKuFk-lT`i6;*g|6qZd-_yc~$;=!oy??#E@b8WLZC zm)GvD-+zq%mOnu+*1r&@DIJH->H6cT zb)&bt%vu*^fw)H#2#C;+%^~@R22e6Is+4zyn;|lyCpum_md@Nm&vJ*EOtRd%uwdnm z&4%vhlX!;(o?%hHh?CDo=%(Bx;Tf+!`2oYPLF&vrUB5N zQI907w}55RB@K#)pOGeq*CP@6r@?s{i}Cxxw2V*lmuY5N`LpX^3p>`~%Mtf0+;KSk zJ(2XUppq|7wWa#}1Og+8?{;^Y_hHqygVG9uCJJf9C+u@%w1saQPrQ$xXzEeoD6B@2 zpb3ljc%CH7_!U7_h`Sf?c^G}EY(F?&s@$qpYx19a5NoYN{TV_YI;FLSQ&R2SLQlI` z&lhasXxJ_Wp;tE)$9bj;3qQ>O7483S;ec*3{3<{M{*^q>6Peunl_oczoF%^=Fa4%_ ztPm!$3!_TdwA=sZssm;Gj+T$!F%4*MmG?VZnRi|ZPqByWmWHK>3wiDXNm=w*j6=CJ z$`4lm_T`KB-MG{H`t|o3@``)-UE)oi`2!V+B`r}Cb$S|FB1a*xR5Tzjp{2J@F#+;D zK3=jgfokZ;Lkt>bV8~VBpbohuATu6|V5Z&#u~BC}0|FD;QR5~$!3MB?Bkpty{UNOp z1r`LJxoSv7+!)ZsLlwfJMBw50j+Vd>_XuF3870C%CqlJl?fP%<{kAaUF!RED^L*BZi5I z*i=qKOqwMWjO>BaKqPQ;<0D&4WF-ORLi000pDW0`DIIVWtWm3*Eel^A%zrQ~NaDu8 z6^v)HDb$l+(e#x6e`FK2&5VMdZ^(|p5>2|5Uw6YvRQINEGH)wcvv)!+7cF-H$8<-O z2|QgW`n7xn3c?3Y{ORT`1fs~9g`sY~d_3xE^4q@Qr<7`*g$iP8&jnQZU82Y z`ZeRt5i!}QF((EGV5M(vAmTWyN*DW~9!_AEtLuUg61jO=+;P9_6teM!M*!Zh=P+<6 zNo6FwG?k@w5VTSnkOh%tmg|pvY=`U%dlPFhztzH^nKFT1}w+3(T-7@FjD3Y;0?P0Ux;7fw9Q_ba|8Vxx7*^<_{D=t^ehqt1%oQ=eiZ*zE8)i&wY#@sLCc3%l78@4Wsg;Fn zEGVbZ3RkuMZK5QNUpcOQQqNU{3@@|0|CGCKWHN~CZmy!T_6{qd_3l#+B>0w5J{(3_ zvS4hBpu;yX(Xx2*E1RU^sjn&Rj~xXydv)n6Lx(PfB0n; z5=(M@6yt+NR)DTiTMSVH^>=!(?W};bW@5GoQ9RGz9=ImKSF#F)0;H?PuzLBY+XVQg zPAO1?D23OJH85vc;wxRHKoN2-$&EXgu9W!7*ToQR4blAoACxi*bnl`RC_s8zht113 zpk)j2m9bKw2vJ)~x1;xN0lHFF3M6-C!y?3zp9gl)Y)KolyW2bYVtlvh4M2;q$cIYo z-G}L!i;eC*6jTLBKp&Yv{_H6D;1u91Z5Ko6id%tP`{%BQ!$q?P+m_Be(4R@M&Cz-2 zk{Va|O5B@(#?1D1J`{>MmNpOUcLd>{??22_<12M<0*0#Jd%Zn4WNQ#~w=SJwCB+s1 zRgB&1T{P5OF}l1_ip>MZ>dHE+o3LEB7+={d#fm`n1b_BsIZ~M?C_YuBAK#Bx0koGHS$-RQH+HC+~Li_HNoq{xT1n}-``O&1$ovMKBpA;yks&v)Gd zbRVe{*vvytwUM8l9=H_vN@6Kcguu|KQS^i1hC_j`EM5#zL2g`axvr3}G?oHIh5cv>K<(+{p0nJcLm0z*~h(dJ;^p~F{JOMxOp zo9V{ezAIe5(pm}>A?hMJlh6%v`J!tn(7Al!cE{!l>>GgA>F66A_H??-Mo##eGLhNuP~Y}7xv z6tYmX2+>gV^<>BG@%Tz_p-_aVv)efj-6q9Xerp{H5H-Z-&8?ctQr4J?5OqIMA17yJ zmdjU$3xxtC($0Rraq-3}z*mY(fg(ifkWB8P3$|^8uN;>GMTnZ<^YiGPTOjMDiV!u8 z+53wfw?Gz&6(A$^ATJKD@y*7v#8;XNOGSu!5Pv6e9Rgo@E(MAZwI%<}@W`b=*FbB5 z0z`don=oBGpvx9iN<&w{67a(J`+0NdsYvg<- zyih1Y)Ij#mhKCLXzA|2ARe-4a?j&_uRc(gde385qSi2g!e#=}je*@6Us)$gvK5Jcp z#f(lim@Po~VtOI+sCLKgG|ZLti-B67v$s7rj^iusr9cs)N_;fha0mW;(Y+L?r~7CW zmSu+6YR&;Iq=0a$-XyxZ#FND~%|h}bL_K2k{yJA6^N0mVqJ8Y2_ujZMZ_=|Y6(MRY zwc2CXt0&nYrU+4Q^3Z?#*r`K8x4Mh0iV*cC!5!b#dXjmQB1F~KfBW`@OCcK|6(Ja1 zP~Ug;dKWBMu_m7_OGSu!lk3X|2X29BrF%71>a9D`+j*G9I0v?nB2tyg(R9_dEo`SU z@+v~KLwNtNo)d7^Arv7{996&f>Z0LtOqbp-grpi=ybg}sJ}RXn;-x?lqDJ!m<@qaz z0zNaF0&@^;QE%5S-AbfE)(;jzI+wP0dH%qq(YG{;5FN?1_WHS63~YfS1WR(w=;u0g z`_7agw%BG7sD-ZYzD4V@=9NZSks?U#Ycqa^r>sOar&feqtJPhvZn-Wiz0Qn&4x|Cx z#hYPH#MdM2f;Tg%>ET^>k}g%Ct?NA8f(m$N*zcqM`@_C$veD5VLu|1kP+R}hf9Z;1 zmbLyOL}hn0dE=^emdUON(YcYAan7mEt{pKn-aL5ZC_-VD=Q*&26p+${6g9OM2U{+_ z^UV1bAeR-K$2>FhC$;zY-EPzXEwuvtO1q2G>(_3-e3f+<1<3Wl>gfIE#4W=ROJ^Q9 zP&FO+>7FAf6dPokng^!pdWL%w*D;`2nt2}q;wvXU{L%l+5^)?<)4@^w!zQ+o;bS6p z%zfr>?#C&8LBjcuI9Vu{1BdX$(Rsib2^S7Q;-i+^ZOzvPiWwGTx7($VPjlFr}4COyoB=yX*)l5p7v-t_6(nl zm$9<(4wb~;<0ZV&em^Q7;E-3GVT>=?yL+2QFI(+bdx!0)C@PZeuvY2CmLRek}eth2D#WOpZ*qgwKIL|`tU#a=Oz`%U^l0jaOE zeKC(=9wlq{cxit;Ksf(f_5~P-%;(6P7UPewwYNgY<-%sBIB}~oiDvkmjum7{id#x( zjXn(hhL4$4PV!`H_@ClRib`d5wX%9cp~4qXDWAyq>(@A=^yPPmPLRhToZCQPiLc#2 zJi*pW!!nh$HGFD|-o2q{fp7Wzd6qo=@qGvXL*JzB)&%pl6>7F=_M7^@6%L;Pv-W|a diff --git a/priv/static/adminfe/static/js/chunk-1a7d.8173d81f.js.map b/priv/static/adminfe/static/js/chunk-a9e5.f5bb9b33.js.map similarity index 99% rename from priv/static/adminfe/static/js/chunk-1a7d.8173d81f.js.map rename to priv/static/adminfe/static/js/chunk-a9e5.f5bb9b33.js.map index d5a2b4a2007fdd81c6add44ce450be1435a75edd..1bde6592f1a5dce1874569501566b2023109e81a 100644 GIT binary patch delta 28 kcmbQSk9o#E<_&djyor{nrg~|nNlBJT#>Shw-FjyL0Gs;?p8x;= delta 28 kcmbQSk9o#E<_&djyoQP9DS8%$=Ef-&hH0C--FjyL0G37yJ^%m! diff --git a/priv/static/adminfe/static/js/chunk-elementUI.708d6b68.js b/priv/static/adminfe/static/js/chunk-elementUI.374aa2ca.js similarity index 99% rename from priv/static/adminfe/static/js/chunk-elementUI.708d6b68.js rename to priv/static/adminfe/static/js/chunk-elementUI.374aa2ca.js index 9ead2e76397abbf4d756da2ecceadbb4b73e1278..b221f866c0a926a4b135bee418f1356742b95492 100644 GIT binary patch delta 43 zcmccdU+uF7M2#)7Pc1l7LF~PC-?Iko0}vi8YL&{Wfkk?CKdnyec}(` delta 43 zcmccdU+uF7M2#)7Pc1l7LF~PC-?K48(5^6C7D_1Wfkk?CKdnydp-}7 diff --git a/priv/static/adminfe/static/js/chunk-elementUI.708d6b68.js.map b/priv/static/adminfe/static/js/chunk-elementUI.374aa2ca.js.map similarity index 99% rename from priv/static/adminfe/static/js/chunk-elementUI.708d6b68.js.map rename to priv/static/adminfe/static/js/chunk-elementUI.374aa2ca.js.map index b49ada1f78a23cbb7bebab63294afa66ca7af744..b58957727727c3da9697ab9dd8d61d42a8039edd 100644 GIT binary patch delta 142 zcmWN=yA6U+0EW>ZDi>5lQ64@J7(q>(=f=vC!rtOftn3`c#6@n+2JYZ@lGDFmUeAX- z4diR6k;V#S6n>(cGpEbh+T+zeI~Q@nDQAp1XTk-SOu6Ek8FLmax#5;O?pg7`n$5R! H?eG2vNGLu} delta 142 zcmWN=yA6U+0EW?^q6mtL;9Eo{pu#&3v7x*Ly~Uqc**S`di`<$G+`;c8r+>e^o)38% zXsD5VjWtoA=_hPmaI4I{Zff(iE@Z?R=Zv}Fk}D=$GiAmNx7=~h1CKoM%$ye%ynd}K Gf88G_@je0o diff --git a/priv/static/adminfe/static/js/chunk-libs.14514767.js b/priv/static/adminfe/static/js/chunk-libs.3ed10ef6.js similarity index 99% rename from priv/static/adminfe/static/js/chunk-libs.14514767.js rename to priv/static/adminfe/static/js/chunk-libs.3ed10ef6.js index f1452865b1af0334cbc57c53ae0f3691d00b5823..b31c6cd5b7b053c709327382aff35cb72c746e4b 100644 GIT binary patch delta 33 ocmaFyO5nvSfrb{w7N!>FEi93y{Kly%h6br=W_nr0dbx=O0OdUl0RR91 delta 33 ocmaFyO5nvSfrb{w7N!>FEi93y{Dvl`h9>4_=6YGhdbx=O0Nln4MgRZ+ diff --git a/priv/static/adminfe/static/js/chunk-libs.14514767.js.map b/priv/static/adminfe/static/js/chunk-libs.3ed10ef6.js.map similarity index 99% rename from priv/static/adminfe/static/js/chunk-libs.14514767.js.map rename to priv/static/adminfe/static/js/chunk-libs.3ed10ef6.js.map index b0a81d9bc2e6b252324dc579d4cf07fbd2d32d51..61fd05273efccadb908e1cf006ef1f95246e1f47 100644 GIT binary patch delta 108 zcmaDjBjw?Yl!g|@7N!>F7M2#)Eo}QHaTuqj7#gIenYACA#0JFdK+FNeoIuP4#N0s4 n1H`;Q%m>8$Kr8^nfq*~EC$5l+mB6>m|Y0~5F{}? delta 108 zcmWN=w+Vny6hKkT-<-o5f?*+-u(Nnz|0}^Y1UpOja1-x=^Bv1~ESnf&iY2xoCQl(tl6-=U)`PS57WUgfdBvi diff --git a/priv/static/adminfe/static/js/runtime.c6b7511a.js b/priv/static/adminfe/static/js/runtime.c6b7511a.js new file mode 100644 index 0000000000000000000000000000000000000000..0e13fe45aab0135a56a601af9ecd6ea99bdaecf6 GIT binary patch literal 3922 zcmai1ZI9cy5&qs^;bj3sgt?4lTefUR2+|;5g1cVPTtB(Sfucq$C0f#@sN37K_1`p)Q>01Q#jMc`(2ocVUp=s?fnDG@FzI10)OTQ zmd8FTwSJ;^_= zS&w>jXTa7*O~ge5E{@0Y*%PcdpZBN-e$ou~;$$!vm_q&3ThYr@XwtB2W3g8UX5u@a z$vD`_x3>RIy6+rdS!~hToqrd{^*%!kJp4SMjlq%N)v+ujcpAngRXF^4~n+SqF6WZKv*#iwGc zSh>9mShu-7HBAo~xI-7zZGVd8GE-l^6J+?Z}1 zE^aiPo`}qA_4R%ctpl`Tv^4A$4f>(NX@>^s{1h4@i#2}}Im;|A}-mJsru%2eC7v;2X zK!PQJlu0st-#!Kj=ZkP7!;moj{?qdddIYQVMZDVABP+qk(QKY;yMf*V%Yy9{8K!FN6JEdC6ozd0P(i&BU_A-?d2HL;y~T`_tu&s zsV$yMg%$U?fsD}eIsay}UEg77lFB}pP~`xY#BrQ+10|9|hGTO+yB|sW&{arKE{cl3 z()l8jw1n6yUVNu`?rz6>Q2pY^>eFjoIu_|wNk>(r0b!v-u*YqeI)mbCD_P1Nr^m#? zANI}VF;cEosYMzq+|&w;(Prchy z^5OI6$%RlRWCk-b$wW~?%Kxi8GBYzm5u+Lv{9g?f(jaDlnl`9{d!AUbzwnG4L`o>K zv`ZwQ(hqr2rd~=L%54s6fRMHej>zstkT$l=MOlq6mM}%eEB^O=WIGFoP zb0w$KjN=zq?ya7N!9}LO9a}yV1`1ZmU^39j(4Vk^hi4X_~lD#L6$2CnjoVXP1JMZmx?r) zq><*vY=*gP48|cs>#}6B1Yv${eZ(tXc4}95U^*Nk7a0MA(s3NdFvkBog2}VOASK&& zfg&fr8sVte3*r5P(B5Li%QnJA%QuFJA=D+aShaeBHkT0kT^H%Z$8x7-SM>DWxB(4% z65h$_y(L|65Y|%BHfxYHf;lW*EDh|{113fqafETh71>bUb+rU{g{?wynm@*X|0S}L z=IQtf>xd%GZW5zyFru^OrS3_Onjw5=cYYYlq2izd_Y~ z)*rh=#&qi18}ly}Z7@Yw@m#44l(J~5`Wy+4R+ny<=t~!Y9@p6f)2aL8<4xls_wY9E zlxvyNQ|Ri{i9^89m0wZaGAx)fQJ0o{i+F21pw;t>Le&Ms5oDl4L*@D&p!m|BWrhu2 zhpDfx|FHsZcv2|rZd?c&%q88Hbr`8DjQU#p8*ASUVn?aG)4kcgusM|fPA|m>vW=ol zx$E@Rb-37T*#UU`VdLif*2y@=#3c}Y+a}oD=% zjzWI@RB3g5Y0b5C*p+5&r+2|EbMfg~Pb@Ps+^H^IS6d~y_s8ZH`_`NM8KB;tVB>aroA4B6m|NbfN+A@v}$T|!H!M{}eQ5^pdV7~|- literal 0 HcmV?d00001 diff --git a/priv/static/adminfe/static/js/runtime.c6b7511a.js.map b/priv/static/adminfe/static/js/runtime.c6b7511a.js.map new file mode 100644 index 0000000000000000000000000000000000000000..0eadd3e06b2d64ca6d6f0fda6e6a4e4de128644c GIT binary patch literal 16658 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 qH|y7Exc252-y%y Date: Mon, 30 Sep 2019 18:23:29 +0300 Subject: [PATCH 10/31] CI: Enable OTP release building for maint/* branches --- .gitlab-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d0c540b16..7bee30e08 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -151,6 +151,7 @@ amd64: only: &release-only - master@pleroma/pleroma - develop@pleroma/pleroma + - /^maint/.*$/@pleroma/pleroma artifacts: &release-artifacts name: "pleroma-$CI_COMMIT_REF_NAME-$CI_COMMIT_SHORT_SHA-$CI_JOB_NAME" paths: From dae744478e7a5b789f2fb541b47eea558a0f2d53 Mon Sep 17 00:00:00 2001 From: lain Date: Mon, 30 Sep 2019 18:13:05 +0200 Subject: [PATCH 11/31] Transmogrifier: Handle compact objects in undos. --- .../web/activity_pub/transmogrifier.ex | 18 ++++++++++++ .../mastodon-undo-like-compact-object.json | 29 +++++++++++++++++++ test/web/activity_pub/transmogrifier_test.exs | 25 ++++++++++++++++ 3 files changed, 72 insertions(+) create mode 100644 test/fixtures/mastodon-undo-like-compact-object.json diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 63877248a..3ca2e8773 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -753,6 +753,24 @@ def handle_incoming( end end + # For Undos that don't have the complete object attached, try to find it in our database. + def handle_incoming( + %{ + "type" => "Undo", + "object" => object + } = activity, + options + ) + when is_binary(object) do + with %Activity{data: data} <- Activity.get_by_ap_id(object) do + activity + |> Map.put("object", data) + |> handle_incoming(options) + else + _e -> :error + end + end + def handle_incoming(_, _), do: :error @spec get_obj_helper(String.t(), Keyword.t()) :: {:ok, Object.t()} | nil diff --git a/test/fixtures/mastodon-undo-like-compact-object.json b/test/fixtures/mastodon-undo-like-compact-object.json new file mode 100644 index 000000000..ae66a0d19 --- /dev/null +++ b/test/fixtures/mastodon-undo-like-compact-object.json @@ -0,0 +1,29 @@ +{ + "type": "Undo", + "signature": { + "type": "RsaSignature2017", + "signatureValue": "fdxMfQSMwbC6wP6sh6neS/vM5879K67yQkHTbiT5Npr5wAac0y6+o3Ij+41tN3rL6wfuGTosSBTHOtta6R4GCOOhCaCSLMZKypnp1VltCzLDoyrZELnYQIC8gpUXVmIycZbREk22qWUe/w7DAFaKK4UscBlHDzeDVcA0K3Se5Sluqi9/Zh+ldAnEzj/rSEPDjrtvf5wGNf3fHxbKSRKFt90JvKK6hS+vxKUhlRFDf6/SMETw+EhwJSNW4d10yMUakqUWsFv4Acq5LW7l+HpYMvlYY1FZhNde1+uonnCyuQDyvzkff8zwtEJmAXC4RivO/VVLa17SmqheJZfI8oluVg==", + "creator": "http://mastodon.example.org/users/admin#main-key", + "created": "2018-05-19T16:36:58Z" + }, + "object": "http://mastodon.example.org/users/admin#likes/2", + "nickname": "lain", + "id": "http://mastodon.example.org/users/admin#likes/2/undo", + "actor": "http://mastodon.example.org/users/admin", + "@context": [ + "https://www.w3.org/ns/activitystreams", + "https://w3id.org/security/v1", + { + "toot": "http://joinmastodon.org/ns#", + "sensitive": "as:sensitive", + "ostatus": "http://ostatus.org#", + "movedTo": "as:movedTo", + "manuallyApprovesFollowers": "as:manuallyApprovesFollowers", + "inReplyToAtomUri": "ostatus:inReplyToAtomUri", + "conversation": "ostatus:conversation", + "atomUri": "ostatus:atomUri", + "Hashtag": "as:Hashtag", + "Emoji": "toot:Emoji" + } + ] +} diff --git a/test/web/activity_pub/transmogrifier_test.exs b/test/web/activity_pub/transmogrifier_test.exs index 193d6d301..6c64be10b 100644 --- a/test/web/activity_pub/transmogrifier_test.exs +++ b/test/web/activity_pub/transmogrifier_test.exs @@ -378,6 +378,31 @@ test "it works for incoming unlikes with an existing like activity" do assert data["object"]["id"] == "http://mastodon.example.org/users/admin#likes/2" end + test "it works for incoming unlikes with an existing like activity and a compact object" do + user = insert(:user) + {:ok, activity} = CommonAPI.post(user, %{"status" => "leave a like pls"}) + + like_data = + File.read!("test/fixtures/mastodon-like.json") + |> Poison.decode!() + |> Map.put("object", activity.data["object"]) + + {:ok, %Activity{data: like_data, local: false}} = Transmogrifier.handle_incoming(like_data) + + data = + File.read!("test/fixtures/mastodon-undo-like.json") + |> Poison.decode!() + |> Map.put("object", like_data["id"]) + |> Map.put("actor", like_data["actor"]) + + {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) + + assert data["actor"] == "http://mastodon.example.org/users/admin" + assert data["type"] == "Undo" + assert data["id"] == "http://mastodon.example.org/users/admin#likes/2/undo" + assert data["object"]["id"] == "http://mastodon.example.org/users/admin#likes/2" + end + test "it works for incoming announces" do data = File.read!("test/fixtures/mastodon-announce.json") |> Poison.decode!() From 36a34c36fe518dae23fb19d02ccb43de8c2621dd Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Tue, 1 Oct 2019 11:44:34 +0700 Subject: [PATCH 12/31] Extract poll actions from `MastodonAPIController` to `PollController` --- lib/pleroma/web/controller_helper.ex | 12 ++ .../controllers/mastodon_api_controller.ex | 64 ------ .../controllers/poll_controller.ex | 53 +++++ .../controllers/status_controller.ex | 2 +- .../web/mastodon_api/views/poll_view.ex | 74 +++++++ .../web/mastodon_api/views/status_view.ex | 72 +------ lib/pleroma/web/router.ex | 4 +- .../controllers/poll_controller_test.exs | 184 ++++++++++++++++++ .../mastodon_api_controller_test.exs | 172 ---------------- .../web/mastodon_api/views/poll_view_test.exs | 126 ++++++++++++ .../mastodon_api/views/status_view_test.exs | 110 ----------- 11 files changed, 454 insertions(+), 419 deletions(-) create mode 100644 lib/pleroma/web/mastodon_api/controllers/poll_controller.ex create mode 100644 lib/pleroma/web/mastodon_api/views/poll_view.ex create mode 100644 test/web/mastodon_api/controllers/poll_controller_test.exs create mode 100644 test/web/mastodon_api/views/poll_view_test.exs diff --git a/lib/pleroma/web/controller_helper.ex b/lib/pleroma/web/controller_helper.ex index 83b884ba9..9a4e322c9 100644 --- a/lib/pleroma/web/controller_helper.ex +++ b/lib/pleroma/web/controller_helper.ex @@ -75,4 +75,16 @@ def assign_account_by_id(%{params: %{"id" => id}} = conn, _) do nil -> Pleroma.Web.MastodonAPI.FallbackController.call(conn, {:error, :not_found}) |> halt() end end + + def try_render(conn, target, params) + when is_binary(target) do + case render(conn, target, params) do + nil -> render_error(conn, :not_implemented, "Can't display this activity") + res -> res + end + end + + def try_render(conn, _, _) do + render_error(conn, :not_implemented, "Can't display this activity") + end end 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 1484a0174..912dd181f 100644 --- a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex @@ -7,7 +7,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2] - alias Pleroma.Activity alias Pleroma.Bookmark alias Pleroma.Config alias Pleroma.HTTP @@ -19,7 +18,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do alias Pleroma.User alias Pleroma.Web alias Pleroma.Web.ActivityPub.ActivityPub - alias Pleroma.Web.ActivityPub.Visibility alias Pleroma.Web.CommonAPI alias Pleroma.Web.MastodonAPI.AccountView alias Pleroma.Web.MastodonAPI.AppView @@ -117,56 +115,6 @@ def custom_emojis(conn, _params) do json(conn, mastodon_emoji) end - def get_poll(%{assigns: %{user: user}} = conn, %{"id" => id}) do - with %Object{} = object <- Object.get_by_id_and_maybe_refetch(id, interval: 60), - %Activity{} = activity <- Activity.get_create_by_object_ap_id(object.data["id"]), - true <- Visibility.visible_for_user?(activity, user) do - conn - |> put_view(StatusView) - |> try_render("poll.json", %{object: object, for: user}) - else - error when is_nil(error) or error == false -> - render_error(conn, :not_found, "Record not found") - end - end - - defp get_cached_vote_or_vote(user, object, choices) do - idempotency_key = "polls:#{user.id}:#{object.data["id"]}" - - {_, res} = - Cachex.fetch(:idempotency_cache, idempotency_key, fn _ -> - case CommonAPI.vote(user, object, choices) do - {:error, _message} = res -> {:ignore, res} - res -> {:commit, res} - end - end) - - res - end - - def poll_vote(%{assigns: %{user: user}} = conn, %{"id" => id, "choices" => choices}) do - with %Object{} = object <- Object.get_by_id(id), - true <- object.data["type"] == "Question", - %Activity{} = activity <- Activity.get_create_by_object_ap_id(object.data["id"]), - true <- Visibility.visible_for_user?(activity, user), - {:ok, _activities, object} <- get_cached_vote_or_vote(user, object, choices) do - conn - |> put_view(StatusView) - |> try_render("poll.json", %{object: object, for: user}) - else - nil -> - render_error(conn, :not_found, "Record not found") - - false -> - render_error(conn, :not_found, "Record not found") - - {:error, message} -> - conn - |> put_status(:unprocessable_entity) - |> json(%{error: message}) - end - end - def update_media( %{assigns: %{user: user}} = conn, %{"id" => id, "description" => description} = _ @@ -511,18 +459,6 @@ def password_reset(conn, params) do end end - def try_render(conn, target, params) - when is_binary(target) do - case render(conn, target, params) do - nil -> render_error(conn, :not_implemented, "Can't display this activity") - res -> res - end - end - - def try_render(conn, _, _) do - render_error(conn, :not_implemented, "Can't display this activity") - end - defp present?(nil), do: false defp present?(false), do: false defp present?(_), do: true diff --git a/lib/pleroma/web/mastodon_api/controllers/poll_controller.ex b/lib/pleroma/web/mastodon_api/controllers/poll_controller.ex new file mode 100644 index 000000000..fbf7f8673 --- /dev/null +++ b/lib/pleroma/web/mastodon_api/controllers/poll_controller.ex @@ -0,0 +1,53 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.PollController do + use Pleroma.Web, :controller + + import Pleroma.Web.ControllerHelper, only: [try_render: 3, json_response: 3] + + alias Pleroma.Activity + alias Pleroma.Object + alias Pleroma.Web.ActivityPub.Visibility + alias Pleroma.Web.CommonAPI + + action_fallback(Pleroma.Web.MastodonAPI.FallbackController) + + @doc "GET /api/v1/polls/:id" + def show(%{assigns: %{user: user}} = conn, %{"id" => id}) do + with %Object{} = object <- Object.get_by_id_and_maybe_refetch(id, interval: 60), + %Activity{} = activity <- Activity.get_create_by_object_ap_id(object.data["id"]), + true <- Visibility.visible_for_user?(activity, user) do + try_render(conn, "show.json", %{object: object, for: user}) + else + error when is_nil(error) or error == false -> + render_error(conn, :not_found, "Record not found") + end + end + + @doc "POST /api/v1/polls/:id/votes" + def vote(%{assigns: %{user: user}} = conn, %{"id" => id, "choices" => choices}) do + with %Object{data: %{"type" => "Question"}} = object <- Object.get_by_id(id), + %Activity{} = activity <- Activity.get_create_by_object_ap_id(object.data["id"]), + true <- Visibility.visible_for_user?(activity, user), + {:ok, _activities, object} <- get_cached_vote_or_vote(user, object, choices) do + try_render(conn, "show.json", %{object: object, for: user}) + else + nil -> render_error(conn, :not_found, "Record not found") + false -> render_error(conn, :not_found, "Record not found") + {:error, message} -> json_response(conn, :unprocessable_entity, %{error: message}) + end + end + + defp get_cached_vote_or_vote(user, object, choices) do + idempotency_key = "polls:#{user.id}:#{object.data["id"]}" + + Cachex.fetch!(:idempotency_cache, idempotency_key, fn -> + case CommonAPI.vote(user, object, choices) do + {:error, _message} = res -> {:ignore, res} + res -> {:commit, res} + end + end) + end +end diff --git a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex index 3c6987a5f..fb6fd7676 100644 --- a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex @@ -5,7 +5,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do use Pleroma.Web, :controller - import Pleroma.Web.MastodonAPI.MastodonAPIController, only: [try_render: 3] + import Pleroma.Web.ControllerHelper, only: [try_render: 3] require Ecto.Query diff --git a/lib/pleroma/web/mastodon_api/views/poll_view.ex b/lib/pleroma/web/mastodon_api/views/poll_view.ex new file mode 100644 index 000000000..753039da3 --- /dev/null +++ b/lib/pleroma/web/mastodon_api/views/poll_view.ex @@ -0,0 +1,74 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.PollView do + use Pleroma.Web, :view + + alias Pleroma.HTML + alias Pleroma.Web.CommonAPI.Utils + + def render("show.json", %{object: object, multiple: multiple, options: options} = params) do + {end_time, expired} = end_time_and_expired(object) + {options, votes_count} = options_and_votes_count(options) + + %{ + # Mastodon uses separate ids for polls, but an object can't have + # more than one poll embedded so object id is fine + id: to_string(object.id), + expires_at: end_time, + expired: expired, + multiple: multiple, + votes_count: votes_count, + options: options, + voted: voted?(params), + emojis: Pleroma.Web.MastodonAPI.StatusView.build_emojis(object.data["emoji"]) + } + end + + def render("show.json", %{object: object} = params) do + case object.data do + %{"anyOf" => options} when is_list(options) -> + render(__MODULE__, "show.json", Map.merge(params, %{multiple: true, options: options})) + + %{"oneOf" => options} when is_list(options) -> + render(__MODULE__, "show.json", Map.merge(params, %{multiple: false, options: options})) + + _ -> + nil + end + end + + defp end_time_and_expired(object) do + case object.data["closed"] || object.data["endTime"] do + end_time when is_binary(end_time) -> + end_time = NaiveDateTime.from_iso8601!(end_time) + expired = NaiveDateTime.compare(end_time, NaiveDateTime.utc_now()) == :lt + + {Utils.to_masto_date(end_time), expired} + + _ -> + {nil, false} + end + end + + defp options_and_votes_count(options) do + Enum.map_reduce(options, 0, fn %{"name" => name} = option, count -> + current_count = option["replies"]["totalItems"] || 0 + + {%{ + title: HTML.strip_tags(name), + votes_count: current_count + }, current_count + count} + end) + end + + defp voted?(%{object: object} = opts) do + if opts[:for] do + existing_votes = Pleroma.Web.ActivityPub.Utils.get_existing_votes(opts[:for].ap_id, object) + existing_votes != [] or opts[:for].ap_id == object.data["actor"] + else + false + end + end +end diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index bc527ad1b..3262427ec 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -18,6 +18,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do alias Pleroma.Web.CommonAPI alias Pleroma.Web.CommonAPI.Utils alias Pleroma.Web.MastodonAPI.AccountView + alias Pleroma.Web.MastodonAPI.PollView alias Pleroma.Web.MastodonAPI.StatusView alias Pleroma.Web.MediaProxy @@ -277,7 +278,7 @@ def render("show.json", %{activity: %{data: %{"object" => _object}} = activity} spoiler_text: summary_html, visibility: get_visibility(object), media_attachments: attachments, - poll: render("poll.json", %{object: object, for: opts[:for]}), + poll: render(PollView, "show.json", object: object, for: opts[:for]), mentions: mentions, tags: build_tags(tags), application: %{ @@ -389,75 +390,6 @@ def render("listens.json", opts) do safe_render_many(opts.activities, StatusView, "listen.json", opts) end - def render("poll.json", %{object: object} = opts) do - {multiple, options} = - case object.data do - %{"anyOf" => options} when is_list(options) -> {true, options} - %{"oneOf" => options} when is_list(options) -> {false, options} - _ -> {nil, nil} - end - - if options do - {end_time, expired} = - case object.data["closed"] || object.data["endTime"] do - end_time when is_binary(end_time) -> - end_time = - (object.data["closed"] || object.data["endTime"]) - |> NaiveDateTime.from_iso8601!() - - expired = - end_time - |> NaiveDateTime.compare(NaiveDateTime.utc_now()) - |> case do - :lt -> true - _ -> false - end - - end_time = Utils.to_masto_date(end_time) - - {end_time, expired} - - _ -> - {nil, false} - end - - voted = - if opts[:for] do - existing_votes = - Pleroma.Web.ActivityPub.Utils.get_existing_votes(opts[:for].ap_id, object) - - existing_votes != [] or opts[:for].ap_id == object.data["actor"] - else - false - end - - {options, votes_count} = - Enum.map_reduce(options, 0, fn %{"name" => name} = option, count -> - current_count = option["replies"]["totalItems"] || 0 - - {%{ - title: HTML.strip_tags(name), - votes_count: current_count - }, current_count + count} - end) - - %{ - # Mastodon uses separate ids for polls, but an object can't have - # more than one poll embedded so object id is fine - id: to_string(object.id), - expires_at: end_time, - expired: expired, - multiple: multiple, - votes_count: votes_count, - options: options, - voted: voted, - emojis: build_emojis(object.data["emoji"]) - } - else - nil - end - end - def render("context.json", %{activity: activity, activities: activities, user: user}) do %{ancestors: ancestors, descendants: descendants} = activities diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index eab55a27c..7af44c6be 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -403,7 +403,7 @@ defmodule Pleroma.Web.Router do put("/scheduled_statuses/:id", ScheduledActivityController, :update) delete("/scheduled_statuses/:id", ScheduledActivityController, :delete) - post("/polls/:id/votes", MastodonAPIController, :poll_vote) + post("/polls/:id/votes", PollController, :vote) post("/media", MastodonAPIController, :upload) put("/media/:id", MastodonAPIController, :update_media) @@ -488,7 +488,7 @@ defmodule Pleroma.Web.Router do get("/statuses/:id", StatusController, :show) get("/statuses/:id/context", StatusController, :context) - get("/polls/:id", MastodonAPIController, :get_poll) + get("/polls/:id", PollController, :show) get("/accounts/:id/statuses", AccountController, :statuses) get("/accounts/:id/followers", AccountController, :followers) diff --git a/test/web/mastodon_api/controllers/poll_controller_test.exs b/test/web/mastodon_api/controllers/poll_controller_test.exs new file mode 100644 index 000000000..40cf3e879 --- /dev/null +++ b/test/web/mastodon_api/controllers/poll_controller_test.exs @@ -0,0 +1,184 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.PollControllerTest do + use Pleroma.Web.ConnCase + + alias Pleroma.Object + alias Pleroma.Web.CommonAPI + + import Pleroma.Factory + + describe "GET /api/v1/polls/:id" do + test "returns poll entity for object id", %{conn: conn} do + user = insert(:user) + + {:ok, activity} = + CommonAPI.post(user, %{ + "status" => "Pleroma does", + "poll" => %{"options" => ["what Mastodon't", "n't what Mastodoes"], "expires_in" => 20} + }) + + object = Object.normalize(activity) + + conn = + conn + |> assign(:user, user) + |> get("/api/v1/polls/#{object.id}") + + response = json_response(conn, 200) + id = to_string(object.id) + assert %{"id" => ^id, "expired" => false, "multiple" => false} = response + end + + test "does not expose polls for private statuses", %{conn: conn} do + user = insert(:user) + other_user = insert(:user) + + {:ok, activity} = + CommonAPI.post(user, %{ + "status" => "Pleroma does", + "poll" => %{"options" => ["what Mastodon't", "n't what Mastodoes"], "expires_in" => 20}, + "visibility" => "private" + }) + + object = Object.normalize(activity) + + conn = + conn + |> assign(:user, other_user) + |> get("/api/v1/polls/#{object.id}") + + assert json_response(conn, 404) + end + end + + describe "POST /api/v1/polls/:id/votes" do + test "votes are added to the poll", %{conn: conn} do + user = insert(:user) + other_user = insert(:user) + + {:ok, activity} = + CommonAPI.post(user, %{ + "status" => "A very delicious sandwich", + "poll" => %{ + "options" => ["Lettuce", "Grilled Bacon", "Tomato"], + "expires_in" => 20, + "multiple" => true + } + }) + + object = Object.normalize(activity) + + conn = + conn + |> assign(:user, other_user) + |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [0, 1, 2]}) + + assert json_response(conn, 200) + object = Object.get_by_id(object.id) + + assert Enum.all?(object.data["anyOf"], fn %{"replies" => %{"totalItems" => total_items}} -> + total_items == 1 + end) + end + + test "author can't vote", %{conn: conn} do + user = insert(:user) + + {:ok, activity} = + CommonAPI.post(user, %{ + "status" => "Am I cute?", + "poll" => %{"options" => ["Yes", "No"], "expires_in" => 20} + }) + + object = Object.normalize(activity) + + assert conn + |> assign(:user, user) + |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [1]}) + |> json_response(422) == %{"error" => "Poll's author can't vote"} + + object = Object.get_by_id(object.id) + + refute Enum.at(object.data["oneOf"], 1)["replies"]["totalItems"] == 1 + end + + test "does not allow multiple choices on a single-choice question", %{conn: conn} do + user = insert(:user) + other_user = insert(:user) + + {:ok, activity} = + CommonAPI.post(user, %{ + "status" => "The glass is", + "poll" => %{"options" => ["half empty", "half full"], "expires_in" => 20} + }) + + object = Object.normalize(activity) + + assert conn + |> assign(:user, other_user) + |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [0, 1]}) + |> json_response(422) == %{"error" => "Too many choices"} + + object = Object.get_by_id(object.id) + + refute Enum.any?(object.data["oneOf"], fn %{"replies" => %{"totalItems" => total_items}} -> + total_items == 1 + end) + end + + test "does not allow choice index to be greater than options count", %{conn: conn} do + user = insert(:user) + other_user = insert(:user) + + {:ok, activity} = + CommonAPI.post(user, %{ + "status" => "Am I cute?", + "poll" => %{"options" => ["Yes", "No"], "expires_in" => 20} + }) + + object = Object.normalize(activity) + + conn = + conn + |> assign(:user, other_user) + |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [2]}) + + assert json_response(conn, 422) == %{"error" => "Invalid indices"} + end + + test "returns 404 error when object is not exist", %{conn: conn} do + user = insert(:user) + + conn = + conn + |> assign(:user, user) + |> post("/api/v1/polls/1/votes", %{"choices" => [0]}) + + assert json_response(conn, 404) == %{"error" => "Record not found"} + end + + test "returns 404 when poll is private and not available for user", %{conn: conn} do + user = insert(:user) + other_user = insert(:user) + + {:ok, activity} = + CommonAPI.post(user, %{ + "status" => "Am I cute?", + "poll" => %{"options" => ["Yes", "No"], "expires_in" => 20}, + "visibility" => "private" + }) + + object = Object.normalize(activity) + + conn = + conn + |> assign(:user, other_user) + |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [0]}) + + assert json_response(conn, 404) == %{"error" => "Record not found"} + end + end +end diff --git a/test/web/mastodon_api/mastodon_api_controller_test.exs b/test/web/mastodon_api/mastodon_api_controller_test.exs index feeaf079b..2ec46bc90 100644 --- a/test/web/mastodon_api/mastodon_api_controller_test.exs +++ b/test/web/mastodon_api/mastodon_api_controller_test.exs @@ -417,178 +417,6 @@ test "redirects to the getting-started page when referer is not present", %{conn end end - describe "GET /api/v1/polls/:id" do - test "returns poll entity for object id", %{conn: conn} do - user = insert(:user) - - {:ok, activity} = - CommonAPI.post(user, %{ - "status" => "Pleroma does", - "poll" => %{"options" => ["what Mastodon't", "n't what Mastodoes"], "expires_in" => 20} - }) - - object = Object.normalize(activity) - - conn = - conn - |> assign(:user, user) - |> get("/api/v1/polls/#{object.id}") - - response = json_response(conn, 200) - id = to_string(object.id) - assert %{"id" => ^id, "expired" => false, "multiple" => false} = response - end - - test "does not expose polls for private statuses", %{conn: conn} do - user = insert(:user) - other_user = insert(:user) - - {:ok, activity} = - CommonAPI.post(user, %{ - "status" => "Pleroma does", - "poll" => %{"options" => ["what Mastodon't", "n't what Mastodoes"], "expires_in" => 20}, - "visibility" => "private" - }) - - object = Object.normalize(activity) - - conn = - conn - |> assign(:user, other_user) - |> get("/api/v1/polls/#{object.id}") - - assert json_response(conn, 404) - end - end - - describe "POST /api/v1/polls/:id/votes" do - test "votes are added to the poll", %{conn: conn} do - user = insert(:user) - other_user = insert(:user) - - {:ok, activity} = - CommonAPI.post(user, %{ - "status" => "A very delicious sandwich", - "poll" => %{ - "options" => ["Lettuce", "Grilled Bacon", "Tomato"], - "expires_in" => 20, - "multiple" => true - } - }) - - object = Object.normalize(activity) - - conn = - conn - |> assign(:user, other_user) - |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [0, 1, 2]}) - - assert json_response(conn, 200) - object = Object.get_by_id(object.id) - - assert Enum.all?(object.data["anyOf"], fn %{"replies" => %{"totalItems" => total_items}} -> - total_items == 1 - end) - end - - test "author can't vote", %{conn: conn} do - user = insert(:user) - - {:ok, activity} = - CommonAPI.post(user, %{ - "status" => "Am I cute?", - "poll" => %{"options" => ["Yes", "No"], "expires_in" => 20} - }) - - object = Object.normalize(activity) - - assert conn - |> assign(:user, user) - |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [1]}) - |> json_response(422) == %{"error" => "Poll's author can't vote"} - - object = Object.get_by_id(object.id) - - refute Enum.at(object.data["oneOf"], 1)["replies"]["totalItems"] == 1 - end - - test "does not allow multiple choices on a single-choice question", %{conn: conn} do - user = insert(:user) - other_user = insert(:user) - - {:ok, activity} = - CommonAPI.post(user, %{ - "status" => "The glass is", - "poll" => %{"options" => ["half empty", "half full"], "expires_in" => 20} - }) - - object = Object.normalize(activity) - - assert conn - |> assign(:user, other_user) - |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [0, 1]}) - |> json_response(422) == %{"error" => "Too many choices"} - - object = Object.get_by_id(object.id) - - refute Enum.any?(object.data["oneOf"], fn %{"replies" => %{"totalItems" => total_items}} -> - total_items == 1 - end) - end - - test "does not allow choice index to be greater than options count", %{conn: conn} do - user = insert(:user) - other_user = insert(:user) - - {:ok, activity} = - CommonAPI.post(user, %{ - "status" => "Am I cute?", - "poll" => %{"options" => ["Yes", "No"], "expires_in" => 20} - }) - - object = Object.normalize(activity) - - conn = - conn - |> assign(:user, other_user) - |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [2]}) - - assert json_response(conn, 422) == %{"error" => "Invalid indices"} - end - - test "returns 404 error when object is not exist", %{conn: conn} do - user = insert(:user) - - conn = - conn - |> assign(:user, user) - |> post("/api/v1/polls/1/votes", %{"choices" => [0]}) - - assert json_response(conn, 404) == %{"error" => "Record not found"} - end - - test "returns 404 when poll is private and not available for user", %{conn: conn} do - user = insert(:user) - other_user = insert(:user) - - {:ok, activity} = - CommonAPI.post(user, %{ - "status" => "Am I cute?", - "poll" => %{"options" => ["Yes", "No"], "expires_in" => 20}, - "visibility" => "private" - }) - - object = Object.normalize(activity) - - conn = - conn - |> assign(:user, other_user) - |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [0]}) - - assert json_response(conn, 404) == %{"error" => "Record not found"} - end - end - describe "POST /auth/password, with valid parameters" do setup %{conn: conn} do user = insert(:user) diff --git a/test/web/mastodon_api/views/poll_view_test.exs b/test/web/mastodon_api/views/poll_view_test.exs new file mode 100644 index 000000000..8cd7636a5 --- /dev/null +++ b/test/web/mastodon_api/views/poll_view_test.exs @@ -0,0 +1,126 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.PollViewTest do + use Pleroma.DataCase + + alias Pleroma.Object + alias Pleroma.Web.CommonAPI + alias Pleroma.Web.MastodonAPI.PollView + + import Pleroma.Factory + import Tesla.Mock + + setup do + mock(fn env -> apply(HttpRequestMock, :request, [env]) end) + :ok + end + + test "renders a poll" do + user = insert(:user) + + {:ok, activity} = + CommonAPI.post(user, %{ + "status" => "Is Tenshi eating a corndog cute?", + "poll" => %{ + "options" => ["absolutely!", "sure", "yes", "why are you even asking?"], + "expires_in" => 20 + } + }) + + object = Object.normalize(activity) + + expected = %{ + emojis: [], + expired: false, + id: to_string(object.id), + multiple: false, + options: [ + %{title: "absolutely!", votes_count: 0}, + %{title: "sure", votes_count: 0}, + %{title: "yes", votes_count: 0}, + %{title: "why are you even asking?", votes_count: 0} + ], + voted: false, + votes_count: 0 + } + + result = PollView.render("show.json", %{object: object}) + expires_at = result.expires_at + result = Map.delete(result, :expires_at) + + assert result == expected + + expires_at = NaiveDateTime.from_iso8601!(expires_at) + assert NaiveDateTime.diff(expires_at, NaiveDateTime.utc_now()) in 15..20 + end + + test "detects if it is multiple choice" do + user = insert(:user) + + {:ok, activity} = + CommonAPI.post(user, %{ + "status" => "Which Mastodon developer is your favourite?", + "poll" => %{ + "options" => ["Gargron", "Eugen"], + "expires_in" => 20, + "multiple" => true + } + }) + + object = Object.normalize(activity) + + assert %{multiple: true} = PollView.render("show.json", %{object: object}) + end + + test "detects emoji" do + user = insert(:user) + + {:ok, activity} = + CommonAPI.post(user, %{ + "status" => "What's with the smug face?", + "poll" => %{ + "options" => [":blank: sip", ":blank::blank: sip", ":blank::blank::blank: sip"], + "expires_in" => 20 + } + }) + + object = Object.normalize(activity) + + assert %{emojis: [%{shortcode: "blank"}]} = PollView.render("show.json", %{object: object}) + end + + test "detects vote status" do + user = insert(:user) + other_user = insert(:user) + + {:ok, activity} = + CommonAPI.post(user, %{ + "status" => "Which input devices do you use?", + "poll" => %{ + "options" => ["mouse", "trackball", "trackpoint"], + "multiple" => true, + "expires_in" => 20 + } + }) + + object = Object.normalize(activity) + + {:ok, _, object} = CommonAPI.vote(other_user, object, [1, 2]) + + result = PollView.render("show.json", %{object: object, for: other_user}) + + assert result[:voted] == true + assert Enum.at(result[:options], 1)[:votes_count] == 1 + assert Enum.at(result[:options], 2)[:votes_count] == 1 + end + + test "does not crash on polls with no end date" do + object = Object.normalize("https://skippers-bin.com/notes/7x9tmrp97i") + result = PollView.render("show.json", %{object: object}) + + assert result[:expires_at] == nil + assert result[:expired] == false + end +end diff --git a/test/web/mastodon_api/views/status_view_test.exs b/test/web/mastodon_api/views/status_view_test.exs index 8df23d0a8..1d5a6e956 100644 --- a/test/web/mastodon_api/views/status_view_test.exs +++ b/test/web/mastodon_api/views/status_view_test.exs @@ -451,116 +451,6 @@ test "a rich media card with all relevant data renders correctly" do end end - describe "poll view" do - test "renders a poll" do - user = insert(:user) - - {:ok, activity} = - CommonAPI.post(user, %{ - "status" => "Is Tenshi eating a corndog cute?", - "poll" => %{ - "options" => ["absolutely!", "sure", "yes", "why are you even asking?"], - "expires_in" => 20 - } - }) - - object = Object.normalize(activity) - - expected = %{ - emojis: [], - expired: false, - id: to_string(object.id), - multiple: false, - options: [ - %{title: "absolutely!", votes_count: 0}, - %{title: "sure", votes_count: 0}, - %{title: "yes", votes_count: 0}, - %{title: "why are you even asking?", votes_count: 0} - ], - voted: false, - votes_count: 0 - } - - result = StatusView.render("poll.json", %{object: object}) - expires_at = result.expires_at - result = Map.delete(result, :expires_at) - - assert result == expected - - expires_at = NaiveDateTime.from_iso8601!(expires_at) - assert NaiveDateTime.diff(expires_at, NaiveDateTime.utc_now()) in 15..20 - end - - test "detects if it is multiple choice" do - user = insert(:user) - - {:ok, activity} = - CommonAPI.post(user, %{ - "status" => "Which Mastodon developer is your favourite?", - "poll" => %{ - "options" => ["Gargron", "Eugen"], - "expires_in" => 20, - "multiple" => true - } - }) - - object = Object.normalize(activity) - - assert %{multiple: true} = StatusView.render("poll.json", %{object: object}) - end - - test "detects emoji" do - user = insert(:user) - - {:ok, activity} = - CommonAPI.post(user, %{ - "status" => "What's with the smug face?", - "poll" => %{ - "options" => [":blank: sip", ":blank::blank: sip", ":blank::blank::blank: sip"], - "expires_in" => 20 - } - }) - - object = Object.normalize(activity) - - assert %{emojis: [%{shortcode: "blank"}]} = - StatusView.render("poll.json", %{object: object}) - end - - test "detects vote status" do - user = insert(:user) - other_user = insert(:user) - - {:ok, activity} = - CommonAPI.post(user, %{ - "status" => "Which input devices do you use?", - "poll" => %{ - "options" => ["mouse", "trackball", "trackpoint"], - "multiple" => true, - "expires_in" => 20 - } - }) - - object = Object.normalize(activity) - - {:ok, _, object} = CommonAPI.vote(other_user, object, [1, 2]) - - result = StatusView.render("poll.json", %{object: object, for: other_user}) - - assert result[:voted] == true - assert Enum.at(result[:options], 1)[:votes_count] == 1 - assert Enum.at(result[:options], 2)[:votes_count] == 1 - end - - test "does not crash on polls with no end date" do - object = Object.normalize("https://skippers-bin.com/notes/7x9tmrp97i") - result = StatusView.render("poll.json", %{object: object}) - - assert result[:expires_at] == nil - assert result[:expired] == false - end - end - test "embeds a relationship in the account" do user = insert(:user) other_user = insert(:user) From 585bc57edbe10dcd19d2294824e0a0600f4bfe4c Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Tue, 1 Oct 2019 14:36:35 +0700 Subject: [PATCH 13/31] Extract media actions from `MastodonAPIController` to `MediaController` --- .../controllers/mastodon_api_controller.ex | 34 ------- .../controllers/media_controller.ex | 42 +++++++++ lib/pleroma/web/router.ex | 4 +- .../controllers/media_controller_test.exs | 92 +++++++++++++++++++ .../mastodon_api_controller_test.exs | 80 ---------------- 5 files changed, 136 insertions(+), 116 deletions(-) create mode 100644 lib/pleroma/web/mastodon_api/controllers/media_controller.ex create mode 100644 test/web/mastodon_api/controllers/media_controller_test.exs 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 912dd181f..f466ecbff 100644 --- a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex @@ -10,7 +10,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do alias Pleroma.Bookmark alias Pleroma.Config alias Pleroma.HTTP - alias Pleroma.Object alias Pleroma.Pagination alias Pleroma.Plugs.RateLimiter alias Pleroma.Repo @@ -115,39 +114,6 @@ def custom_emojis(conn, _params) do json(conn, mastodon_emoji) end - def update_media( - %{assigns: %{user: user}} = conn, - %{"id" => id, "description" => description} = _ - ) - when is_binary(description) do - with %Object{} = object <- Repo.get(Object, id), - true <- Object.authorize_mutation(object, user), - {:ok, %Object{data: data}} <- Object.update_data(object, %{"name" => description}) do - attachment_data = Map.put(data, "id", object.id) - - conn - |> put_view(StatusView) - |> render("attachment.json", %{attachment: attachment_data}) - end - end - - def update_media(_conn, _data), do: {:error, :bad_request} - - def upload(%{assigns: %{user: user}} = conn, %{"file" => file} = data) do - with {:ok, object} <- - ActivityPub.upload( - file, - actor: User.ap_id(user), - description: Map.get(data, "description") - ) do - attachment_data = Map.put(object.data, "id", object.id) - - conn - |> put_view(StatusView) - |> render("attachment.json", %{attachment: attachment_data}) - end - end - def follows(%{assigns: %{user: follower}} = conn, %{"uri" => uri}) do with {_, %User{} = followed} <- {:followed, User.get_cached_by_nickname(uri)}, {_, true} <- {:followed, follower.id != followed.id}, diff --git a/lib/pleroma/web/mastodon_api/controllers/media_controller.ex b/lib/pleroma/web/mastodon_api/controllers/media_controller.ex new file mode 100644 index 000000000..57a5b60fb --- /dev/null +++ b/lib/pleroma/web/mastodon_api/controllers/media_controller.ex @@ -0,0 +1,42 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.MediaController do + use Pleroma.Web, :controller + + alias Pleroma.Object + alias Pleroma.User + alias Pleroma.Web.ActivityPub.ActivityPub + + action_fallback(Pleroma.Web.MastodonAPI.FallbackController) + plug(:put_view, Pleroma.Web.MastodonAPI.StatusView) + + @doc "POST /api/v1/media" + def create(%{assigns: %{user: user}} = conn, %{"file" => file} = data) do + with {:ok, object} <- + ActivityPub.upload( + file, + actor: User.ap_id(user), + description: Map.get(data, "description") + ) do + attachment_data = Map.put(object.data, "id", object.id) + + render(conn, "attachment.json", %{attachment: attachment_data}) + end + end + + @doc "PUT /api/v1/media/:id" + def update(%{assigns: %{user: user}} = conn, %{"id" => id, "description" => description}) + when is_binary(description) do + with %Object{} = object <- Object.get_by_id(id), + true <- Object.authorize_mutation(object, user), + {:ok, %Object{data: data}} <- Object.update_data(object, %{"name" => description}) do + attachment_data = Map.put(data, "id", object.id) + + render(conn, "attachment.json", %{attachment: attachment_data}) + end + end + + def update(_conn, _data), do: {:error, :bad_request} +end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 7af44c6be..8b482528b 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -405,8 +405,8 @@ defmodule Pleroma.Web.Router do post("/polls/:id/votes", PollController, :vote) - post("/media", MastodonAPIController, :upload) - put("/media/:id", MastodonAPIController, :update_media) + post("/media", MediaController, :create) + put("/media/:id", MediaController, :update) delete("/lists/:id", ListController, :delete) post("/lists", ListController, :create) diff --git a/test/web/mastodon_api/controllers/media_controller_test.exs b/test/web/mastodon_api/controllers/media_controller_test.exs new file mode 100644 index 000000000..06c6a1cb3 --- /dev/null +++ b/test/web/mastodon_api/controllers/media_controller_test.exs @@ -0,0 +1,92 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.MediaControllerTest do + use Pleroma.Web.ConnCase + + alias Pleroma.Object + alias Pleroma.User + alias Pleroma.Web.ActivityPub.ActivityPub + + import Pleroma.Factory + + describe "media upload" do + setup do + user = insert(:user) + + conn = + build_conn() + |> assign(:user, user) + + image = %Plug.Upload{ + content_type: "image/jpg", + path: Path.absname("test/fixtures/image.jpg"), + filename: "an_image.jpg" + } + + [conn: conn, image: image] + end + + clear_config([:media_proxy]) + clear_config([Pleroma.Upload]) + + test "returns uploaded image", %{conn: conn, image: image} do + desc = "Description of the image" + + media = + conn + |> post("/api/v1/media", %{"file" => image, "description" => desc}) + |> json_response(:ok) + + assert media["type"] == "image" + assert media["description"] == desc + assert media["id"] + + object = Object.get_by_id(media["id"]) + assert object.data["actor"] == User.ap_id(conn.assigns[:user]) + end + end + + describe "PUT /api/v1/media/:id" do + setup do + actor = insert(:user) + + file = %Plug.Upload{ + content_type: "image/jpg", + path: Path.absname("test/fixtures/image.jpg"), + filename: "an_image.jpg" + } + + {:ok, %Object{} = object} = + ActivityPub.upload( + file, + actor: User.ap_id(actor), + description: "test-m" + ) + + [actor: actor, object: object] + end + + test "updates name of media", %{conn: conn, actor: actor, object: object} do + media = + conn + |> assign(:user, actor) + |> put("/api/v1/media/#{object.id}", %{"description" => "test-media"}) + |> json_response(:ok) + + assert media["description"] == "test-media" + assert refresh_record(object).data["name"] == "test-media" + end + + test "returns error wheb request is bad", %{conn: conn, actor: actor, object: object} do + media = + conn + |> assign(:user, actor) + |> put("/api/v1/media/#{object.id}", %{}) + |> json_response(400) + + assert media == %{"error" => "bad_request"} + end + end +end diff --git a/test/web/mastodon_api/mastodon_api_controller_test.exs b/test/web/mastodon_api/mastodon_api_controller_test.exs index 2ec46bc90..da5606165 100644 --- a/test/web/mastodon_api/mastodon_api_controller_test.exs +++ b/test/web/mastodon_api/mastodon_api_controller_test.exs @@ -12,7 +12,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do alias Pleroma.Repo alias Pleroma.Tests.ObanHelpers alias Pleroma.User - alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.CommonAPI alias Pleroma.Web.OAuth.App alias Pleroma.Web.Push @@ -77,43 +76,6 @@ test "creates an oauth app", %{conn: conn} do assert expected == json_response(conn, 200) end - describe "media upload" do - setup do - user = insert(:user) - - conn = - build_conn() - |> assign(:user, user) - - image = %Plug.Upload{ - content_type: "image/jpg", - path: Path.absname("test/fixtures/image.jpg"), - filename: "an_image.jpg" - } - - [conn: conn, image: image] - end - - clear_config([:media_proxy]) - clear_config([Pleroma.Upload]) - - test "returns uploaded image", %{conn: conn, image: image} do - desc = "Description of the image" - - media = - conn - |> post("/api/v1/media", %{"file" => image, "description" => desc}) - |> json_response(:ok) - - assert media["type"] == "image" - assert media["description"] == desc - assert media["id"] - - object = Repo.get(Object, media["id"]) - assert object.data["actor"] == User.ap_id(conn.assigns[:user]) - end - end - test "getting a list of mutes", %{conn: conn} do user = insert(:user) other_user = insert(:user) @@ -550,48 +512,6 @@ test "returns suggestions", %{conn: conn, user: user, other_user: other_user} do end end - describe "PUT /api/v1/media/:id" do - setup do - actor = insert(:user) - - file = %Plug.Upload{ - content_type: "image/jpg", - path: Path.absname("test/fixtures/image.jpg"), - filename: "an_image.jpg" - } - - {:ok, %Object{} = object} = - ActivityPub.upload( - file, - actor: User.ap_id(actor), - description: "test-m" - ) - - [actor: actor, object: object] - end - - test "updates name of media", %{conn: conn, actor: actor, object: object} do - media = - conn - |> assign(:user, actor) - |> put("/api/v1/media/#{object.id}", %{"description" => "test-media"}) - |> json_response(:ok) - - assert media["description"] == "test-media" - assert refresh_record(object).data["name"] == "test-media" - end - - test "returns error wheb request is bad", %{conn: conn, actor: actor, object: object} do - media = - conn - |> assign(:user, actor) - |> put("/api/v1/media/#{object.id}", %{}) - |> json_response(400) - - assert media == %{"error" => "bad_request"} - end - end - describe "DELETE /auth/sign_out" do test "redirect to root page", %{conn: conn} do user = insert(:user) From 39695c4436056db0b25cfa5e361630791923df84 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Tue, 1 Oct 2019 14:45:04 +0700 Subject: [PATCH 14/31] Extract suggestions actions from `MastodonAPIController` to `SuggestionController` --- .../controllers/mastodon_api_controller.ex | 49 ---------- .../controllers/suggestion_controller.ex | 63 +++++++++++++ lib/pleroma/web/router.ex | 2 +- .../suggestion_controller_test.exs | 92 +++++++++++++++++++ .../mastodon_api_controller_test.exs | 83 ----------------- 5 files changed, 156 insertions(+), 133 deletions(-) create mode 100644 lib/pleroma/web/mastodon_api/controllers/suggestion_controller.ex create mode 100644 test/web/mastodon_api/controllers/suggestion_controller_test.exs 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 f466ecbff..ff6de425f 100644 --- a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex @@ -9,7 +9,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do alias Pleroma.Bookmark alias Pleroma.Config - alias Pleroma.HTTP alias Pleroma.Pagination alias Pleroma.Plugs.RateLimiter alias Pleroma.Repo @@ -22,7 +21,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do alias Pleroma.Web.MastodonAPI.AppView alias Pleroma.Web.MastodonAPI.MastodonView alias Pleroma.Web.MastodonAPI.StatusView - alias Pleroma.Web.MediaProxy alias Pleroma.Web.OAuth.App alias Pleroma.Web.OAuth.Authorization alias Pleroma.Web.OAuth.Scopes @@ -362,53 +360,6 @@ def empty_object(conn, _) do json(conn, %{}) end - def suggestions(%{assigns: %{user: user}} = conn, _) do - suggestions = Config.get(:suggestions) - - if Keyword.get(suggestions, :enabled, false) do - api = Keyword.get(suggestions, :third_party_engine, "") - timeout = Keyword.get(suggestions, :timeout, 5000) - limit = Keyword.get(suggestions, :limit, 23) - - host = Config.get([Pleroma.Web.Endpoint, :url, :host]) - - user = user.nickname - - url = - api - |> String.replace("{{host}}", host) - |> String.replace("{{user}}", user) - - with {:ok, %{status: 200, body: body}} <- - HTTP.get(url, [], adapter: [recv_timeout: timeout, pool: :default]), - {:ok, data} <- Jason.decode(body) do - data = - data - |> Enum.slice(0, limit) - |> Enum.map(fn x -> - x - |> Map.put("id", fetch_suggestion_id(x)) - |> Map.put("avatar", MediaProxy.url(x["avatar"])) - |> Map.put("avatar_static", MediaProxy.url(x["avatar_static"])) - end) - - json(conn, data) - else - e -> - Logger.error("Could not retrieve suggestions at fetch #{url}, #{inspect(e)}") - end - else - json(conn, []) - end - end - - defp fetch_suggestion_id(attrs) do - case User.get_or_fetch(attrs["acct"]) do - {:ok, %User{id: id}} -> id - _ -> 0 - end - end - def password_reset(conn, params) do nickname_or_email = params["email"] || params["nickname"] diff --git a/lib/pleroma/web/mastodon_api/controllers/suggestion_controller.ex b/lib/pleroma/web/mastodon_api/controllers/suggestion_controller.ex new file mode 100644 index 000000000..9076bb849 --- /dev/null +++ b/lib/pleroma/web/mastodon_api/controllers/suggestion_controller.ex @@ -0,0 +1,63 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.SuggestionController do + use Pleroma.Web, :controller + + require Logger + + alias Pleroma.Config + alias Pleroma.User + alias Pleroma.Web.MediaProxy + + action_fallback(Pleroma.Web.MastodonAPI.FallbackController) + + @doc "GET /api/v1/suggestions" + def index(%{assigns: %{user: user}} = conn, _) do + if Config.get([:suggestions, :enabled], false) do + with {:ok, data} <- fetch_suggestions(user) do + limit = Config.get([:suggestions, :limit], 23) + + data = + data + |> Enum.slice(0, limit) + |> Enum.map(fn x -> + x + |> Map.put("id", fetch_suggestion_id(x)) + |> Map.put("avatar", MediaProxy.url(x["avatar"])) + |> Map.put("avatar_static", MediaProxy.url(x["avatar_static"])) + end) + + json(conn, data) + end + else + json(conn, []) + end + end + + defp fetch_suggestions(user) do + api = Config.get([:suggestions, :third_party_engine], "") + timeout = Config.get([:suggestions, :timeout], 5000) + host = Config.get([Pleroma.Web.Endpoint, :url, :host]) + + url = + api + |> String.replace("{{host}}", host) + |> String.replace("{{user}}", user.nickname) + + with {:ok, %{status: 200, body: body}} <- + Pleroma.HTTP.get(url, [], adapter: [recv_timeout: timeout, pool: :default]) do + Jason.decode(body) + else + e -> Logger.error("Could not retrieve suggestions at fetch #{url}, #{inspect(e)}") + end + end + + defp fetch_suggestion_id(attrs) do + case User.get_or_fetch(attrs["acct"]) do + {:ok, %User{id: id}} -> id + _ -> 0 + end + end +end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 8b482528b..bb8e7bd72 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -373,7 +373,7 @@ defmodule Pleroma.Web.Router do get("/filters", FilterController, :index) - get("/suggestions", MastodonAPIController, :suggestions) + get("/suggestions", SuggestionController, :index) get("/conversations", ConversationController, :index) post("/conversations/:id/read", ConversationController, :read) diff --git a/test/web/mastodon_api/controllers/suggestion_controller_test.exs b/test/web/mastodon_api/controllers/suggestion_controller_test.exs new file mode 100644 index 000000000..78620a873 --- /dev/null +++ b/test/web/mastodon_api/controllers/suggestion_controller_test.exs @@ -0,0 +1,92 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.SuggestionControllerTest do + use Pleroma.Web.ConnCase + + alias Pleroma.Config + + import ExUnit.CaptureLog + import Pleroma.Factory + import Tesla.Mock + + setup do + user = insert(:user) + other_user = insert(:user) + host = Config.get([Pleroma.Web.Endpoint, :url, :host]) + url500 = "http://test500?#{host}&#{user.nickname}" + url200 = "http://test200?#{host}&#{user.nickname}" + + mock(fn + %{method: :get, url: ^url500} -> + %Tesla.Env{status: 500, body: "bad request"} + + %{method: :get, url: ^url200} -> + %Tesla.Env{ + status: 200, + body: + ~s([{"acct":"yj455","avatar":"https://social.heldscal.la/avatar/201.jpeg","avatar_static":"https://social.heldscal.la/avatar/s/201.jpeg"}, {"acct":"#{ + other_user.ap_id + }","avatar":"https://social.heldscal.la/avatar/202.jpeg","avatar_static":"https://social.heldscal.la/avatar/s/202.jpeg"}]) + } + end) + + [user: user, other_user: other_user] + end + + clear_config(:suggestions) + + test "returns empty result when suggestions disabled", %{conn: conn, user: user} do + Config.put([:suggestions, :enabled], false) + + res = + conn + |> assign(:user, user) + |> get("/api/v1/suggestions") + |> json_response(200) + + assert res == [] + end + + test "returns error", %{conn: conn, user: user} do + Config.put([:suggestions, :enabled], true) + Config.put([:suggestions, :third_party_engine], "http://test500?{{host}}&{{user}}") + + assert capture_log(fn -> + res = + conn + |> assign(:user, user) + |> get("/api/v1/suggestions") + |> json_response(500) + + assert res == "Something went wrong" + end) =~ "Could not retrieve suggestions" + end + + test "returns suggestions", %{conn: conn, user: user, other_user: other_user} do + Config.put([:suggestions, :enabled], true) + Config.put([:suggestions, :third_party_engine], "http://test200?{{host}}&{{user}}") + + res = + conn + |> assign(:user, user) + |> get("/api/v1/suggestions") + |> json_response(200) + + assert res == [ + %{ + "acct" => "yj455", + "avatar" => "https://social.heldscal.la/avatar/201.jpeg", + "avatar_static" => "https://social.heldscal.la/avatar/s/201.jpeg", + "id" => 0 + }, + %{ + "acct" => other_user.ap_id, + "avatar" => "https://social.heldscal.la/avatar/202.jpeg", + "avatar_static" => "https://social.heldscal.la/avatar/s/202.jpeg", + "id" => other_user.id + } + ] + end +end diff --git a/test/web/mastodon_api/mastodon_api_controller_test.exs b/test/web/mastodon_api/mastodon_api_controller_test.exs index da5606165..47357863c 100644 --- a/test/web/mastodon_api/mastodon_api_controller_test.exs +++ b/test/web/mastodon_api/mastodon_api_controller_test.exs @@ -8,7 +8,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do alias Ecto.Changeset alias Pleroma.Config alias Pleroma.Notification - alias Pleroma.Object alias Pleroma.Repo alias Pleroma.Tests.ObanHelpers alias Pleroma.User @@ -16,7 +15,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do alias Pleroma.Web.OAuth.App alias Pleroma.Web.Push - import ExUnit.CaptureLog import Pleroma.Factory import Swoosh.TestAssertions import Tesla.Mock @@ -431,87 +429,6 @@ test "it returns 400 when user is not local", %{conn: conn, user: user} do end end - describe "GET /api/v1/suggestions" do - setup do - user = insert(:user) - other_user = insert(:user) - host = Config.get([Pleroma.Web.Endpoint, :url, :host]) - url500 = "http://test500?#{host}&#{user.nickname}" - url200 = "http://test200?#{host}&#{user.nickname}" - - mock(fn - %{method: :get, url: ^url500} -> - %Tesla.Env{status: 500, body: "bad request"} - - %{method: :get, url: ^url200} -> - %Tesla.Env{ - status: 200, - body: - ~s([{"acct":"yj455","avatar":"https://social.heldscal.la/avatar/201.jpeg","avatar_static":"https://social.heldscal.la/avatar/s/201.jpeg"}, {"acct":"#{ - other_user.ap_id - }","avatar":"https://social.heldscal.la/avatar/202.jpeg","avatar_static":"https://social.heldscal.la/avatar/s/202.jpeg"}]) - } - end) - - [user: user, other_user: other_user] - end - - clear_config(:suggestions) - - test "returns empty result when suggestions disabled", %{conn: conn, user: user} do - Config.put([:suggestions, :enabled], false) - - res = - conn - |> assign(:user, user) - |> get("/api/v1/suggestions") - |> json_response(200) - - assert res == [] - end - - test "returns error", %{conn: conn, user: user} do - Config.put([:suggestions, :enabled], true) - Config.put([:suggestions, :third_party_engine], "http://test500?{{host}}&{{user}}") - - assert capture_log(fn -> - res = - conn - |> assign(:user, user) - |> get("/api/v1/suggestions") - |> json_response(500) - - assert res == "Something went wrong" - end) =~ "Could not retrieve suggestions" - end - - test "returns suggestions", %{conn: conn, user: user, other_user: other_user} do - Config.put([:suggestions, :enabled], true) - Config.put([:suggestions, :third_party_engine], "http://test200?{{host}}&{{user}}") - - res = - conn - |> assign(:user, user) - |> get("/api/v1/suggestions") - |> json_response(200) - - assert res == [ - %{ - "acct" => "yj455", - "avatar" => "https://social.heldscal.la/avatar/201.jpeg", - "avatar_static" => "https://social.heldscal.la/avatar/s/201.jpeg", - "id" => 0 - }, - %{ - "acct" => other_user.ap_id, - "avatar" => "https://social.heldscal.la/avatar/202.jpeg", - "avatar_static" => "https://social.heldscal.la/avatar/s/202.jpeg", - "id" => other_user.id - } - ] - end - end - describe "DELETE /auth/sign_out" do test "redirect to root page", %{conn: conn} do user = insert(:user) From 2dad6dd0201135f5ab8ff50448b0787f36db0607 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Tue, 1 Oct 2019 15:21:46 +0700 Subject: [PATCH 15/31] Extract apps actions from `MastodonAPIController` to `AppController` --- .../web/activity_pub/views/user_view.ex | 2 +- .../controllers/app_controller.ex | 39 ++++++++++++ .../controllers/mastodon_api_controller.ex | 31 +--------- lib/pleroma/web/router.ex | 4 +- .../controllers/app_controller_test.exs | 60 +++++++++++++++++++ .../mastodon_api_controller_test.exs | 49 --------------- 6 files changed, 103 insertions(+), 82 deletions(-) create mode 100644 lib/pleroma/web/mastodon_api/controllers/app_controller.ex create mode 100644 test/web/mastodon_api/controllers/app_controller_test.exs diff --git a/lib/pleroma/web/activity_pub/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex index c94c5a225..6bc55c85b 100644 --- a/lib/pleroma/web/activity_pub/views/user_view.ex +++ b/lib/pleroma/web/activity_pub/views/user_view.ex @@ -22,7 +22,7 @@ def render("endpoints.json", %{user: %User{nickname: nil, local: true} = _user}) def render("endpoints.json", %{user: %User{local: true} = _user}) do %{ "oauthAuthorizationEndpoint" => Helpers.o_auth_url(Endpoint, :authorize), - "oauthRegistrationEndpoint" => Helpers.mastodon_api_url(Endpoint, :create_app), + "oauthRegistrationEndpoint" => Helpers.app_url(Endpoint, :create), "oauthTokenEndpoint" => Helpers.o_auth_url(Endpoint, :token_exchange), "sharedInbox" => Helpers.activity_pub_url(Endpoint, :inbox), "uploadMedia" => Helpers.activity_pub_url(Endpoint, :upload_media) diff --git a/lib/pleroma/web/mastodon_api/controllers/app_controller.ex b/lib/pleroma/web/mastodon_api/controllers/app_controller.ex new file mode 100644 index 000000000..abbe16a88 --- /dev/null +++ b/lib/pleroma/web/mastodon_api/controllers/app_controller.ex @@ -0,0 +1,39 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.AppController do + use Pleroma.Web, :controller + + alias Pleroma.Repo + alias Pleroma.Web.OAuth.App + alias Pleroma.Web.OAuth.Scopes + alias Pleroma.Web.OAuth.Token + + action_fallback(Pleroma.Web.MastodonAPI.FallbackController) + + @local_mastodon_name "Mastodon-Local" + + @doc "POST /api/v1/apps" + def create(conn, params) do + scopes = Scopes.fetch_scopes(params, ["read"]) + + app_attrs = + params + |> Map.drop(["scope", "scopes"]) + |> Map.put("scopes", scopes) + + with cs <- App.register_changeset(%App{}, app_attrs), + false <- cs.changes[:client_name] == @local_mastodon_name, + {:ok, app} <- Repo.insert(cs) do + render(conn, "show.json", app: app) + end + end + + @doc "GET /api/v1/apps/verify_credentials" + def verify_credentials(%{assigns: %{user: _user, token: token}} = conn, _) do + with %Token{app: %App{} = app} <- Repo.preload(token, :app) do + render(conn, "short.json", app: app) + end + end +end 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 ff6de425f..80a7b5bef 100644 --- a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex @@ -11,19 +11,16 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do alias Pleroma.Config alias Pleroma.Pagination alias Pleroma.Plugs.RateLimiter - alias Pleroma.Repo alias Pleroma.Stats alias Pleroma.User alias Pleroma.Web alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.CommonAPI alias Pleroma.Web.MastodonAPI.AccountView - alias Pleroma.Web.MastodonAPI.AppView alias Pleroma.Web.MastodonAPI.MastodonView alias Pleroma.Web.MastodonAPI.StatusView alias Pleroma.Web.OAuth.App alias Pleroma.Web.OAuth.Authorization - alias Pleroma.Web.OAuth.Scopes alias Pleroma.Web.OAuth.Token alias Pleroma.Web.TwitterAPI.TwitterAPI @@ -31,35 +28,9 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do plug(RateLimiter, :password_reset when action == :password_reset) - @local_mastodon_name "Mastodon-Local" - action_fallback(Pleroma.Web.MastodonAPI.FallbackController) - def create_app(conn, params) do - scopes = Scopes.fetch_scopes(params, ["read"]) - - app_attrs = - params - |> Map.drop(["scope", "scopes"]) - |> Map.put("scopes", scopes) - - with cs <- App.register_changeset(%App{}, app_attrs), - false <- cs.changes[:client_name] == @local_mastodon_name, - {:ok, app} <- Repo.insert(cs) do - conn - |> put_view(AppView) - |> render("show.json", %{app: app}) - end - end - - def verify_app_credentials(%{assigns: %{user: _user, token: token}} = conn, _) do - with %Token{app: %App{} = app} <- Repo.preload(token, :app) do - conn - |> put_view(AppView) - |> render("short.json", %{app: app}) - end - end - + @local_mastodon_name "Mastodon-Local" @mastodon_api_level "2.7.2" def masto_instance(conn, _params) do diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index bb8e7bd72..29f53108c 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -464,8 +464,8 @@ defmodule Pleroma.Web.Router do get("/instance", MastodonAPIController, :masto_instance) get("/instance/peers", MastodonAPIController, :peers) - post("/apps", MastodonAPIController, :create_app) - get("/apps/verify_credentials", MastodonAPIController, :verify_app_credentials) + post("/apps", AppController, :create) + get("/apps/verify_credentials", AppController, :verify_credentials) get("/custom_emojis", MastodonAPIController, :custom_emojis) get("/statuses/:id/card", StatusController, :card) diff --git a/test/web/mastodon_api/controllers/app_controller_test.exs b/test/web/mastodon_api/controllers/app_controller_test.exs new file mode 100644 index 000000000..51788155b --- /dev/null +++ b/test/web/mastodon_api/controllers/app_controller_test.exs @@ -0,0 +1,60 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.AppControllerTest do + use Pleroma.Web.ConnCase, async: true + + alias Pleroma.Repo + alias Pleroma.Web.OAuth.App + alias Pleroma.Web.Push + + import Pleroma.Factory + + test "apps/verify_credentials", %{conn: conn} do + token = insert(:oauth_token) + + conn = + conn + |> assign(:user, token.user) + |> assign(:token, token) + |> get("/api/v1/apps/verify_credentials") + + app = Repo.preload(token, :app).app + + expected = %{ + "name" => app.client_name, + "website" => app.website, + "vapid_key" => Push.vapid_config() |> Keyword.get(:public_key) + } + + assert expected == json_response(conn, 200) + end + + test "creates an oauth app", %{conn: conn} do + user = insert(:user) + app_attrs = build(:oauth_app) + + conn = + conn + |> assign(:user, user) + |> post("/api/v1/apps", %{ + client_name: app_attrs.client_name, + redirect_uris: app_attrs.redirect_uris + }) + + [app] = Repo.all(App) + + expected = %{ + "name" => app.client_name, + "website" => app.website, + "client_id" => app.client_id, + "client_secret" => app.client_secret, + "id" => app.id |> to_string(), + "redirect_uri" => app.redirect_uris, + "vapid_key" => Push.vapid_config() |> Keyword.get(:public_key) + } + + assert expected == json_response(conn, 200) + end +end diff --git a/test/web/mastodon_api/mastodon_api_controller_test.exs b/test/web/mastodon_api/mastodon_api_controller_test.exs index 47357863c..68fe751e7 100644 --- a/test/web/mastodon_api/mastodon_api_controller_test.exs +++ b/test/web/mastodon_api/mastodon_api_controller_test.exs @@ -12,8 +12,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do alias Pleroma.Tests.ObanHelpers alias Pleroma.User alias Pleroma.Web.CommonAPI - alias Pleroma.Web.OAuth.App - alias Pleroma.Web.Push import Pleroma.Factory import Swoosh.TestAssertions @@ -27,53 +25,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do clear_config([:instance, :public]) clear_config([:rich_media, :enabled]) - test "apps/verify_credentials", %{conn: conn} do - token = insert(:oauth_token) - - conn = - conn - |> assign(:user, token.user) - |> assign(:token, token) - |> get("/api/v1/apps/verify_credentials") - - app = Repo.preload(token, :app).app - - expected = %{ - "name" => app.client_name, - "website" => app.website, - "vapid_key" => Push.vapid_config() |> Keyword.get(:public_key) - } - - assert expected == json_response(conn, 200) - end - - test "creates an oauth app", %{conn: conn} do - user = insert(:user) - app_attrs = build(:oauth_app) - - conn = - conn - |> assign(:user, user) - |> post("/api/v1/apps", %{ - client_name: app_attrs.client_name, - redirect_uris: app_attrs.redirect_uris - }) - - [app] = Repo.all(App) - - expected = %{ - "name" => app.client_name, - "website" => app.website, - "client_id" => app.client_id, - "client_secret" => app.client_secret, - "id" => app.id |> to_string(), - "redirect_uri" => app.redirect_uris, - "vapid_key" => Push.vapid_config() |> Keyword.get(:public_key) - } - - assert expected == json_response(conn, 200) - end - test "getting a list of mutes", %{conn: conn} do user = insert(:user) other_user = insert(:user) From af690d10336124968e2a0fe0e73decb2d48819cb Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Tue, 1 Oct 2019 15:54:45 +0700 Subject: [PATCH 16/31] Extract auth actions from `MastodonAPIController` to `AuthController` --- .../controllers/auth_controller.ex | 91 +++++++++++++ .../controllers/mastodon_api_controller.ex | 79 ------------ lib/pleroma/web/router.ex | 6 +- .../controllers/auth_controller_test.exs | 121 ++++++++++++++++++ .../mastodon_api_controller_test.exs | 91 ------------- 5 files changed, 215 insertions(+), 173 deletions(-) create mode 100644 lib/pleroma/web/mastodon_api/controllers/auth_controller.ex create mode 100644 test/web/mastodon_api/controllers/auth_controller_test.exs diff --git a/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex b/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex new file mode 100644 index 000000000..0dee670af --- /dev/null +++ b/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex @@ -0,0 +1,91 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.AuthController do + use Pleroma.Web, :controller + + alias Pleroma.User + alias Pleroma.Web.OAuth.App + alias Pleroma.Web.OAuth.Authorization + alias Pleroma.Web.OAuth.Token + alias Pleroma.Web.TwitterAPI.TwitterAPI + + action_fallback(Pleroma.Web.MastodonAPI.FallbackController) + + @local_mastodon_name "Mastodon-Local" + + plug(Pleroma.Plugs.RateLimiter, :password_reset when action == :password_reset) + + @doc "GET /web/login" + def login(%{assigns: %{user: %User{}}} = conn, _params) do + redirect(conn, to: local_mastodon_root_path(conn)) + end + + @doc "Local Mastodon FE login init action" + def login(conn, %{"code" => auth_token}) do + with {:ok, app} <- get_or_make_app(), + {:ok, auth} <- Authorization.get_by_token(app, auth_token), + {:ok, token} <- Token.exchange_token(app, auth) do + conn + |> put_session(:oauth_token, token.token) + |> redirect(to: local_mastodon_root_path(conn)) + end + end + + @doc "Local Mastodon FE callback action" + def login(conn, _) do + with {:ok, app} <- get_or_make_app() do + path = + o_auth_path(conn, :authorize, + response_type: "code", + client_id: app.client_id, + redirect_uri: ".", + scope: Enum.join(app.scopes, " ") + ) + + redirect(conn, to: path) + end + end + + @doc "DELETE /auth/sign_out" + def logout(conn, _) do + conn + |> clear_session + |> redirect(to: "/") + end + + @doc "POST /auth/password" + def password_reset(conn, params) do + nickname_or_email = params["email"] || params["nickname"] + + with {:ok, _} <- TwitterAPI.password_reset(nickname_or_email) do + conn + |> put_status(:no_content) + |> json("") + else + {:error, "unknown user"} -> + send_resp(conn, :not_found, "") + + {:error, _} -> + send_resp(conn, :bad_request, "") + end + end + + defp local_mastodon_root_path(conn) do + case get_session(conn, :return_to) do + nil -> + mastodon_api_path(conn, :index, ["getting-started"]) + + return_to -> + delete_session(conn, :return_to) + return_to + end + end + + @spec get_or_make_app() :: {:ok, App.t()} | {:error, Ecto.Changeset.t()} + defp get_or_make_app do + %{client_name: @local_mastodon_name, redirect_uris: "."} + |> App.get_or_make(["read", "write", "follow", "push"]) + end +end 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 80a7b5bef..4fa0e1bcc 100644 --- a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex @@ -10,7 +10,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do alias Pleroma.Bookmark alias Pleroma.Config alias Pleroma.Pagination - alias Pleroma.Plugs.RateLimiter alias Pleroma.Stats alias Pleroma.User alias Pleroma.Web @@ -19,18 +18,11 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do alias Pleroma.Web.MastodonAPI.AccountView alias Pleroma.Web.MastodonAPI.MastodonView alias Pleroma.Web.MastodonAPI.StatusView - alias Pleroma.Web.OAuth.App - alias Pleroma.Web.OAuth.Authorization - alias Pleroma.Web.OAuth.Token - alias Pleroma.Web.TwitterAPI.TwitterAPI require Logger - plug(RateLimiter, :password_reset when action == :password_reset) - action_fallback(Pleroma.Web.MastodonAPI.FallbackController) - @local_mastodon_name "Mastodon-Local" @mastodon_api_level "2.7.2" def masto_instance(conn, _params) do @@ -264,61 +256,6 @@ def put_settings(%{assigns: %{user: user}} = conn, %{"data" => settings} = _para end end - def login(%{assigns: %{user: %User{}}} = conn, _params) do - redirect(conn, to: local_mastodon_root_path(conn)) - end - - @doc "Local Mastodon FE login init action" - def login(conn, %{"code" => auth_token}) do - with {:ok, app} <- get_or_make_app(), - {:ok, auth} <- Authorization.get_by_token(app, auth_token), - {:ok, token} <- Token.exchange_token(app, auth) do - conn - |> put_session(:oauth_token, token.token) - |> redirect(to: local_mastodon_root_path(conn)) - end - end - - @doc "Local Mastodon FE callback action" - def login(conn, _) do - with {:ok, app} <- get_or_make_app() do - path = - o_auth_path(conn, :authorize, - response_type: "code", - client_id: app.client_id, - redirect_uri: ".", - scope: Enum.join(app.scopes, " ") - ) - - redirect(conn, to: path) - end - end - - defp local_mastodon_root_path(conn) do - case get_session(conn, :return_to) do - nil -> - mastodon_api_path(conn, :index, ["getting-started"]) - - return_to -> - delete_session(conn, :return_to) - return_to - end - end - - @spec get_or_make_app() :: {:ok, App.t()} | {:error, Ecto.Changeset.t()} - defp get_or_make_app do - App.get_or_make( - %{client_name: @local_mastodon_name, redirect_uris: "."}, - ["read", "write", "follow", "push"] - ) - end - - def logout(conn, _) do - conn - |> clear_session - |> redirect(to: "/") - end - # Stubs for unimplemented mastodon api # def empty_array(conn, _) do @@ -331,22 +268,6 @@ def empty_object(conn, _) do json(conn, %{}) end - def password_reset(conn, params) do - nickname_or_email = params["email"] || params["nickname"] - - with {:ok, _} <- TwitterAPI.password_reset(nickname_or_email) do - conn - |> put_status(:no_content) - |> json("") - else - {:error, "unknown user"} -> - send_resp(conn, :not_found, "") - - {:error, _} -> - send_resp(conn, :bad_request, "") - end - end - defp present?(nil), do: false defp present?(false), do: false defp present?(_), do: true diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 29f53108c..501978994 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -661,10 +661,10 @@ defmodule Pleroma.Web.Router do scope "/", Pleroma.Web.MastodonAPI do pipe_through(:mastodon_html) - get("/web/login", MastodonAPIController, :login) - delete("/auth/sign_out", MastodonAPIController, :logout) + get("/web/login", AuthController, :login) + delete("/auth/sign_out", AuthController, :logout) - post("/auth/password", MastodonAPIController, :password_reset) + post("/auth/password", AuthController, :password_reset) scope [] do pipe_through(:oauth_read) diff --git a/test/web/mastodon_api/controllers/auth_controller_test.exs b/test/web/mastodon_api/controllers/auth_controller_test.exs new file mode 100644 index 000000000..98b2a82e7 --- /dev/null +++ b/test/web/mastodon_api/controllers/auth_controller_test.exs @@ -0,0 +1,121 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.AuthControllerTest do + use Pleroma.Web.ConnCase + + alias Pleroma.Config + alias Pleroma.Repo + alias Pleroma.Tests.ObanHelpers + + import Pleroma.Factory + import Swoosh.TestAssertions + + describe "GET /web/login" do + setup %{conn: conn} do + session_opts = [ + store: :cookie, + key: "_test", + signing_salt: "cooldude" + ] + + conn = + conn + |> Plug.Session.call(Plug.Session.init(session_opts)) + |> fetch_session() + + test_path = "/web/statuses/test" + %{conn: conn, path: test_path} + end + + test "redirects to the saved path after log in", %{conn: conn, path: path} do + app = insert(:oauth_app, client_name: "Mastodon-Local", redirect_uris: ".") + auth = insert(:oauth_authorization, app: app) + + conn = + conn + |> put_session(:return_to, path) + |> get("/web/login", %{code: auth.token}) + + assert conn.status == 302 + assert redirected_to(conn) == path + end + + test "redirects to the getting-started page when referer is not present", %{conn: conn} do + app = insert(:oauth_app, client_name: "Mastodon-Local", redirect_uris: ".") + auth = insert(:oauth_authorization, app: app) + + conn = get(conn, "/web/login", %{code: auth.token}) + + assert conn.status == 302 + assert redirected_to(conn) == "/web/getting-started" + end + end + + describe "POST /auth/password, with valid parameters" do + setup %{conn: conn} do + user = insert(:user) + conn = post(conn, "/auth/password?email=#{user.email}") + %{conn: conn, user: user} + end + + test "it returns 204", %{conn: conn} do + assert json_response(conn, :no_content) + end + + test "it creates a PasswordResetToken record for user", %{user: user} do + token_record = Repo.get_by(Pleroma.PasswordResetToken, user_id: user.id) + assert token_record + end + + test "it sends an email to user", %{user: user} do + ObanHelpers.perform_all() + token_record = Repo.get_by(Pleroma.PasswordResetToken, user_id: user.id) + + email = Pleroma.Emails.UserEmail.password_reset_email(user, token_record.token) + notify_email = Config.get([:instance, :notify_email]) + instance_name = Config.get([:instance, :name]) + + assert_email_sent( + from: {instance_name, notify_email}, + to: {user.name, user.email}, + html_body: email.html_body + ) + end + end + + describe "POST /auth/password, with invalid parameters" do + setup do + user = insert(:user) + {:ok, user: user} + end + + test "it returns 404 when user is not found", %{conn: conn, user: user} do + conn = post(conn, "/auth/password?email=nonexisting_#{user.email}") + assert conn.status == 404 + assert conn.resp_body == "" + end + + test "it returns 400 when user is not local", %{conn: conn, user: user} do + {:ok, user} = Repo.update(Ecto.Changeset.change(user, local: false)) + conn = post(conn, "/auth/password?email=#{user.email}") + assert conn.status == 400 + assert conn.resp_body == "" + end + end + + describe "DELETE /auth/sign_out" do + test "redirect to root page", %{conn: conn} do + user = insert(:user) + + conn = + conn + |> assign(:user, user) + |> delete("/auth/sign_out") + + assert conn.status == 302 + assert redirected_to(conn) == "/" + end + end +end diff --git a/test/web/mastodon_api/mastodon_api_controller_test.exs b/test/web/mastodon_api/mastodon_api_controller_test.exs index 68fe751e7..2ec5ad2be 100644 --- a/test/web/mastodon_api/mastodon_api_controller_test.exs +++ b/test/web/mastodon_api/mastodon_api_controller_test.exs @@ -9,12 +9,10 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do alias Pleroma.Config alias Pleroma.Notification alias Pleroma.Repo - alias Pleroma.Tests.ObanHelpers alias Pleroma.User alias Pleroma.Web.CommonAPI import Pleroma.Factory - import Swoosh.TestAssertions import Tesla.Mock setup do @@ -303,95 +301,6 @@ test "saves referer path to session", %{conn: conn, path: path} do assert return_to == path end - - test "redirects to the saved path after log in", %{conn: conn, path: path} do - app = insert(:oauth_app, client_name: "Mastodon-Local", redirect_uris: ".") - auth = insert(:oauth_authorization, app: app) - - conn = - conn - |> put_session(:return_to, path) - |> get("/web/login", %{code: auth.token}) - - assert conn.status == 302 - assert redirected_to(conn) == path - end - - test "redirects to the getting-started page when referer is not present", %{conn: conn} do - app = insert(:oauth_app, client_name: "Mastodon-Local", redirect_uris: ".") - auth = insert(:oauth_authorization, app: app) - - conn = get(conn, "/web/login", %{code: auth.token}) - - assert conn.status == 302 - assert redirected_to(conn) == "/web/getting-started" - end - end - - describe "POST /auth/password, with valid parameters" do - setup %{conn: conn} do - user = insert(:user) - conn = post(conn, "/auth/password?email=#{user.email}") - %{conn: conn, user: user} - end - - test "it returns 204", %{conn: conn} do - assert json_response(conn, :no_content) - end - - test "it creates a PasswordResetToken record for user", %{user: user} do - token_record = Repo.get_by(Pleroma.PasswordResetToken, user_id: user.id) - assert token_record - end - - test "it sends an email to user", %{user: user} do - ObanHelpers.perform_all() - token_record = Repo.get_by(Pleroma.PasswordResetToken, user_id: user.id) - - email = Pleroma.Emails.UserEmail.password_reset_email(user, token_record.token) - notify_email = Config.get([:instance, :notify_email]) - instance_name = Config.get([:instance, :name]) - - assert_email_sent( - from: {instance_name, notify_email}, - to: {user.name, user.email}, - html_body: email.html_body - ) - end - end - - describe "POST /auth/password, with invalid parameters" do - setup do - user = insert(:user) - {:ok, user: user} - end - - test "it returns 404 when user is not found", %{conn: conn, user: user} do - conn = post(conn, "/auth/password?email=nonexisting_#{user.email}") - assert conn.status == 404 - assert conn.resp_body == "" - end - - test "it returns 400 when user is not local", %{conn: conn, user: user} do - {:ok, user} = Repo.update(Changeset.change(user, local: false)) - conn = post(conn, "/auth/password?email=#{user.email}") - assert conn.status == 400 - assert conn.resp_body == "" - end - end - - describe "DELETE /auth/sign_out" do - test "redirect to root page", %{conn: conn} do - user = insert(:user) - - conn = - conn - |> assign(:user, user) - |> delete("/auth/sign_out") - - assert conn.status == 302 - assert redirected_to(conn) == "/" - end end describe "empty_array, stubs for mastodon api" do From 0f9c2c8b87672517aa040a2cbe1c297b29acc317 Mon Sep 17 00:00:00 2001 From: Maxim Filippov Date: Tue, 1 Oct 2019 18:10:04 +0300 Subject: [PATCH 17/31] Send an identifier alongside with error message in OAuthController --- lib/pleroma/web/oauth/oauth_controller.ex | 24 ++++++++++++++++++++--- lib/pleroma/web/translation_helpers.ex | 11 +++++++++-- test/web/oauth/oauth_controller_test.exs | 1 + 3 files changed, 31 insertions(+), 5 deletions(-) diff --git a/lib/pleroma/web/oauth/oauth_controller.ex b/lib/pleroma/web/oauth/oauth_controller.ex index a57670e02..e418dc70d 100644 --- a/lib/pleroma/web/oauth/oauth_controller.ex +++ b/lib/pleroma/web/oauth/oauth_controller.ex @@ -212,13 +212,31 @@ def token_exchange( {:auth_active, false} -> # Per https://github.com/tootsuite/mastodon/blob/ # 51e154f5e87968d6bb115e053689767ab33e80cd/app/controllers/api/base_controller.rb#L76 - render_error(conn, :forbidden, "Your login is missing a confirmed e-mail address") + render_error( + conn, + :forbidden, + "Your login is missing a confirmed e-mail address", + %{}, + "missing_confirmed_email" + ) {:user_active, false} -> - render_error(conn, :forbidden, "Your account is currently disabled") + render_error( + conn, + :forbidden, + "Your account is currently disabled", + %{}, + "account_is_disabled" + ) {:password_reset_pending, true} -> - render_error(conn, :forbidden, "Password reset is required") + render_error( + conn, + :forbidden, + "Password reset is required", + %{}, + "password_reset_required" + ) _error -> render_invalid_credentials_error(conn) diff --git a/lib/pleroma/web/translation_helpers.ex b/lib/pleroma/web/translation_helpers.ex index 8f5a43bf6..7a2ddc008 100644 --- a/lib/pleroma/web/translation_helpers.ex +++ b/lib/pleroma/web/translation_helpers.ex @@ -3,14 +3,21 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.TranslationHelpers do - defmacro render_error(conn, status, msgid, bindings \\ Macro.escape(%{})) do + defmacro render_error( + conn, + status, + msgid, + bindings \\ Macro.escape(%{}), + identifier \\ Macro.escape("") + ) do quote do require Pleroma.Web.Gettext unquote(conn) |> Plug.Conn.put_status(unquote(status)) |> Phoenix.Controller.json(%{ - error: Pleroma.Web.Gettext.dgettext("errors", unquote(msgid), unquote(bindings)) + error: Pleroma.Web.Gettext.dgettext("errors", unquote(msgid), unquote(bindings)), + identifier: unquote(identifier) }) end end diff --git a/test/web/oauth/oauth_controller_test.exs b/test/web/oauth/oauth_controller_test.exs index 0cf755806..4d0741d14 100644 --- a/test/web/oauth/oauth_controller_test.exs +++ b/test/web/oauth/oauth_controller_test.exs @@ -852,6 +852,7 @@ test "rejects token exchange for user with password_reset_pending set to true" d assert resp = json_response(conn, 403) assert resp["error"] == "Password reset is required" + assert resp["identifier"] == "password_reset_required" refute Map.has_key?(resp, "access_token") end From 1f0be71ea433971de874a71ba1dafd101f4301b6 Mon Sep 17 00:00:00 2001 From: KokaKiwi Date: Sun, 24 Feb 2019 18:45:29 +0100 Subject: [PATCH 18/31] Make activity announceable by its author. --- lib/pleroma/web/activity_pub/activity_pub.ex | 2 +- lib/pleroma/web/activity_pub/visibility.ex | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 95f994c17..c58b48443 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -346,7 +346,7 @@ def announce( local \\ true, public \\ true ) do - with true <- is_public?(object), + with true <- is_announceable?(object, user), announce_data <- make_announce_data(user, object, activity_id, public), {:ok, activity} <- insert(announce_data, local), {:ok, object} <- add_announce_to_object(activity, object), diff --git a/lib/pleroma/web/activity_pub/visibility.ex b/lib/pleroma/web/activity_pub/visibility.ex index dfb166b65..021efd30f 100644 --- a/lib/pleroma/web/activity_pub/visibility.ex +++ b/lib/pleroma/web/activity_pub/visibility.ex @@ -27,6 +27,10 @@ def is_private?(activity) do end end + def is_announceable?(activity, user) do + is_public?(activity) || activity.data["actor"] == user.ap_id + end + def is_direct?(%Activity{data: %{"directMessage" => true}}), do: true def is_direct?(%Object{data: %{"directMessage" => true}}), do: true From fe538973ddbdb7b3216e8da1defaa57adb63e890 Mon Sep 17 00:00:00 2001 From: Thibaut Girka Date: Tue, 1 Oct 2019 17:49:52 +0200 Subject: [PATCH 19/31] Ensure self-announces do not widen the audience of the original post --- lib/pleroma/web/activity_pub/activity_pub.ex | 2 +- lib/pleroma/web/activity_pub/visibility.ex | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index c58b48443..c52efb578 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -346,7 +346,7 @@ def announce( local \\ true, public \\ true ) do - with true <- is_announceable?(object, user), + with true <- is_announceable?(object, user, public), announce_data <- make_announce_data(user, object, activity_id, public), {:ok, activity} <- insert(announce_data, local), {:ok, object} <- add_announce_to_object(activity, object), diff --git a/lib/pleroma/web/activity_pub/visibility.ex b/lib/pleroma/web/activity_pub/visibility.ex index 021efd30f..270d0fa02 100644 --- a/lib/pleroma/web/activity_pub/visibility.ex +++ b/lib/pleroma/web/activity_pub/visibility.ex @@ -27,8 +27,9 @@ def is_private?(activity) do end end - def is_announceable?(activity, user) do - is_public?(activity) || activity.data["actor"] == user.ap_id + def is_announceable?(activity, user, public \\ true) do + is_public?(activity) || + (!public && is_private?(activity) && activity.data["actor"] == user.ap_id) end def is_direct?(%Activity{data: %{"directMessage" => true}}), do: true From e0b654e202554f001a5de3df4ccb8021fd3a517a Mon Sep 17 00:00:00 2001 From: Thibaut Girka Date: Tue, 1 Oct 2019 17:51:27 +0200 Subject: [PATCH 20/31] Add tests --- test/web/activity_pub/activity_pub_test.exs | 33 +++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/test/web/activity_pub/activity_pub_test.exs b/test/web/activity_pub/activity_pub_test.exs index a203d1d30..f29497847 100644 --- a/test/web/activity_pub/activity_pub_test.exs +++ b/test/web/activity_pub/activity_pub_test.exs @@ -839,6 +839,39 @@ test "adds an announce activity to the db" do end end + describe "announcing a private object" do + test "adds an announce activity to the db if the audience is not widened" do + user = insert(:user) + {:ok, note_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "private"}) + object = Object.normalize(note_activity) + + {:ok, announce_activity, object} = ActivityPub.announce(user, object, nil, true, false) + + assert announce_activity.data["to"] == [User.ap_followers(user)] + + assert announce_activity.data["object"] == object.data["id"] + assert announce_activity.data["actor"] == user.ap_id + assert announce_activity.data["context"] == object.data["context"] + end + + test "does not add an announce activity to the db if the audience is widened" do + user = insert(:user) + {:ok, note_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "private"}) + object = Object.normalize(note_activity) + + assert {:error, _} = ActivityPub.announce(user, object, nil, true, true) + end + + test "does not add an announce activity to the db if the announcer is not the author" do + user = insert(:user) + announcer = insert(:user) + {:ok, note_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "private"}) + object = Object.normalize(note_activity) + + assert {:error, _} = ActivityPub.announce(announcer, object, nil, true, false) + end + end + describe "unannouncing an object" do test "unannouncing a previously announced object" do note_activity = insert(:note_activity) From b2273c695ec3a84dfb7a3a83019a71cade08b8d4 Mon Sep 17 00:00:00 2001 From: Maxim Filippov Date: Tue, 1 Oct 2019 19:43:22 +0300 Subject: [PATCH 21/31] Discard identifier, if empty --- lib/pleroma/web/translation_helpers.ex | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/pleroma/web/translation_helpers.ex b/lib/pleroma/web/translation_helpers.ex index 7a2ddc008..a104ea6b8 100644 --- a/lib/pleroma/web/translation_helpers.ex +++ b/lib/pleroma/web/translation_helpers.ex @@ -13,12 +13,17 @@ defmacro render_error( quote do require Pleroma.Web.Gettext + error_map = + %{ + error: Pleroma.Web.Gettext.dgettext("errors", unquote(msgid), unquote(bindings)), + identifier: unquote(identifier) + } + |> Enum.reject(fn {_k, v} -> v == "" end) + |> Map.new() + unquote(conn) |> Plug.Conn.put_status(unquote(status)) - |> Phoenix.Controller.json(%{ - error: Pleroma.Web.Gettext.dgettext("errors", unquote(msgid), unquote(bindings)), - identifier: unquote(identifier) - }) + |> Phoenix.Controller.json(error_map) end end end From 4c1f158f5de95581f1489be32614e0e75bc77ba4 Mon Sep 17 00:00:00 2001 From: Thibaut Girka Date: Tue, 1 Oct 2019 18:38:23 +0200 Subject: [PATCH 22/31] Allow users to announce privately, including own private notes --- lib/pleroma/web/common_api/common_api.ex | 15 ++++++++++++--- .../mastodon_api/controllers/status_controller.ex | 4 ++-- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex index 2ec017ff8..677a53ddf 100644 --- a/lib/pleroma/web/common_api/common_api.ex +++ b/lib/pleroma/web/common_api/common_api.ex @@ -76,11 +76,12 @@ def delete(activity_id, user) do end end - def repeat(id_or_ap_id, user) do + def repeat(id_or_ap_id, user, params \\ %{}) do with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id), object <- Object.normalize(activity), - nil <- Utils.get_existing_announce(user.ap_id, object) do - ActivityPub.announce(user, object) + nil <- Utils.get_existing_announce(user.ap_id, object), + public <- get_announce_visibility(object, params) do + ActivityPub.announce(user, object, nil, true, public) else _ -> {:error, dgettext("errors", "Could not repeat")} end @@ -169,6 +170,14 @@ defp normalize_and_validate_choices(choices, object) do end end + def get_announce_visibility(_, %{"visibility" => visibility}) + when visibility in ~w{public unlisted private direct}, + do: visibility in ~w(public unlisted) + + def get_announce_visibility(object, _) do + Visibility.is_public?(object) + end + def get_visibility(_, _, %Participation{}), do: {"direct", "direct"} def get_visibility(%{"visibility" => visibility}, in_reply_to, _) diff --git a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex index fb6fd7676..51456d453 100644 --- a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex @@ -125,8 +125,8 @@ def delete(%{assigns: %{user: user}} = conn, %{"id" => id}) do end @doc "POST /api/v1/statuses/:id/reblog" - def reblog(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do - with {:ok, announce, _activity} <- CommonAPI.repeat(ap_id_or_id, user), + def reblog(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id} = params) do + with {:ok, announce, _activity} <- CommonAPI.repeat(ap_id_or_id, user, params), %Activity{} = announce <- Activity.normalize(announce.data) do try_render(conn, "show.json", %{activity: announce, for: user, as: :activity}) end From 7d5a9f3f6d393c744364148568cfb9b0249789fc Mon Sep 17 00:00:00 2001 From: Thibaut Girka Date: Tue, 1 Oct 2019 19:08:25 +0200 Subject: [PATCH 23/31] Add tests for privately announcing statuses via API --- test/web/common_api/common_api_test.exs | 12 ++++++++++++ .../controllers/status_controller_test.exs | 18 ++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/test/web/common_api/common_api_test.exs b/test/web/common_api/common_api_test.exs index 0f4a5eb25..2d3c41e82 100644 --- a/test/web/common_api/common_api_test.exs +++ b/test/web/common_api/common_api_test.exs @@ -231,6 +231,18 @@ test "repeating a status" do {:ok, %Activity{}, _} = CommonAPI.repeat(activity.id, user) end + test "repeating a status privately" do + user = insert(:user) + other_user = insert(:user) + + {:ok, activity} = CommonAPI.post(other_user, %{"status" => "cofe"}) + + {:ok, %Activity{} = announce_activity, _} = + CommonAPI.repeat(activity.id, user, %{"visibility" => "private"}) + + assert Visibility.is_private?(announce_activity) + end + test "favoriting a status" do user = insert(:user) other_user = insert(:user) diff --git a/test/web/mastodon_api/controllers/status_controller_test.exs b/test/web/mastodon_api/controllers/status_controller_test.exs index b194feae6..727a233e7 100644 --- a/test/web/mastodon_api/controllers/status_controller_test.exs +++ b/test/web/mastodon_api/controllers/status_controller_test.exs @@ -547,6 +547,24 @@ test "reblogs and returns the reblogged status", %{conn: conn} do assert to_string(activity.id) == id end + test "reblogs privately and returns the reblogged status", %{conn: conn} do + activity = insert(:note_activity) + user = insert(:user) + + conn = + conn + |> assign(:user, user) + |> post("/api/v1/statuses/#{activity.id}/reblog", %{"visibility" => "private"}) + + assert %{ + "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 0}, + "reblogged" => true, + "visibility" => "private" + } = json_response(conn, 200) + + assert to_string(activity.id) == id + end + test "reblogged status for another user", %{conn: conn} do activity = insert(:note_activity) user1 = insert(:user) From 43e3db0951c34859932f20d8c82284343a82fcf1 Mon Sep 17 00:00:00 2001 From: Thibaut Girka Date: Tue, 1 Oct 2019 19:28:51 +0200 Subject: [PATCH 24/31] Fix returned visibility of announces in MastodonAPI --- lib/pleroma/web/mastodon_api/views/status_view.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index 3262427ec..9b8dd3086 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -125,7 +125,7 @@ def render( pinned: pinned?(activity, user), sensitive: false, spoiler_text: "", - visibility: "public", + visibility: get_visibility(activity), media_attachments: reblogged[:media_attachments] || [], mentions: mentions, tags: reblogged[:tags] || [], From c541b83befdaaea93304cc3a33782222e6dee1d1 Mon Sep 17 00:00:00 2001 From: Sergey Suprunenko Date: Tue, 1 Oct 2019 20:00:27 +0000 Subject: [PATCH 25/31] Track failed proxy urls and don't request them again --- CHANGELOG.md | 1 + lib/pleroma/application.ex | 3 +- lib/pleroma/reverse_proxy/reverse_proxy.ex | 27 +++++++++- test/reverse_proxy_test.exs | 58 +++++++++++++++++++--- 4 files changed, 80 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d9424c8f..f61efcc22 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -117,6 +117,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Mastodon API: Added an endpoint to get multiple statuses by IDs (`GET /api/v1/statuses/?ids[]=1&ids[]=2`) - ActivityPub: Add ActivityPub actor's `discoverable` parameter. - Admin API: Added moderation log filters (user/start date/end date/search/pagination) +- Reverse Proxy: Do not retry failed requests to limit pressure on the peer ### Changed - Configuration: Filter.AnonymizeFilename added ability to retain file extension with custom text diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex index 7aec2c545..9e35b02c0 100644 --- a/lib/pleroma/application.ex +++ b/lib/pleroma/application.ex @@ -102,7 +102,8 @@ defp cachex_children do build_cachex("scrubber", limit: 2500), build_cachex("idempotency", expiration: idempotency_expiration(), limit: 2500), build_cachex("web_resp", limit: 2500), - build_cachex("emoji_packs", expiration: emoji_packs_expiration(), limit: 10) + build_cachex("emoji_packs", expiration: emoji_packs_expiration(), limit: 10), + build_cachex("failed_proxy_url", limit: 2500) ] end diff --git a/lib/pleroma/reverse_proxy/reverse_proxy.ex b/lib/pleroma/reverse_proxy/reverse_proxy.ex index 03efad30a..78144cae3 100644 --- a/lib/pleroma/reverse_proxy/reverse_proxy.ex +++ b/lib/pleroma/reverse_proxy/reverse_proxy.ex @@ -15,6 +15,7 @@ defmodule Pleroma.ReverseProxy do @valid_resp_codes [200, 206, 304] @max_read_duration :timer.seconds(30) @max_body_length :infinity + @failed_request_ttl :timer.seconds(60) @methods ~w(GET HEAD) @moduledoc """ @@ -48,6 +49,8 @@ defmodule Pleroma.ReverseProxy do * `max_read_duration` (default `#{inspect(@max_read_duration)}` ms): the total time the connection is allowed to read from the remote upstream. + * `failed_request_ttl` (default `#{inspect(@failed_request_ttl)}` ms): the time the failed request is cached and cannot be retried. + * `inline_content_types`: * `true` will not alter `content-disposition` (up to the upstream), * `false` will add `content-disposition: attachment` to any request, @@ -83,6 +86,7 @@ defmodule Pleroma.ReverseProxy do {:keep_user_agent, boolean} | {:max_read_duration, :timer.time() | :infinity} | {:max_body_length, non_neg_integer() | :infinity} + | {:failed_request_ttl, :timer.time() | :infinity} | {:http, []} | {:req_headers, [{String.t(), String.t()}]} | {:resp_headers, [{String.t(), String.t()}]} @@ -108,7 +112,8 @@ def call(conn = %{method: method}, url, opts) when method in @methods do opts end - with {:ok, code, headers, client} <- request(method, url, req_headers, hackney_opts), + with {:ok, nil} <- Cachex.get(:failed_proxy_url_cache, url), + {:ok, code, headers, client} <- request(method, url, req_headers, hackney_opts), :ok <- header_length_constraint( headers, @@ -116,12 +121,18 @@ def call(conn = %{method: method}, url, opts) when method in @methods do ) do response(conn, client, url, code, headers, opts) else + {:ok, true} -> + conn + |> error_or_redirect(url, 500, "Request failed", opts) + |> halt() + {:ok, code, headers} -> head_response(conn, url, code, headers, opts) |> halt() {:error, {:invalid_http_response, code}} -> Logger.error("#{__MODULE__}: request to #{inspect(url)} failed with HTTP status #{code}") + track_failed_url(url, code, opts) conn |> error_or_redirect( @@ -134,6 +145,7 @@ def call(conn = %{method: method}, url, opts) when method in @methods do {:error, error} -> Logger.error("#{__MODULE__}: request to #{inspect(url)} failed: #{inspect(error)}") + track_failed_url(url, error, opts) conn |> error_or_redirect(url, 500, "Request failed", opts) @@ -388,4 +400,17 @@ defp increase_read_duration(_) do end defp client, do: Pleroma.ReverseProxy.Client + + defp track_failed_url(url, code, opts) do + code = to_string(code) + + ttl = + if code in ["403", "404"] or String.starts_with?(code, "5") do + Keyword.get(opts, :failed_request_ttl, @failed_request_ttl) + else + nil + end + + Cachex.put(:failed_proxy_url_cache, url, true, ttl: ttl) + end end diff --git a/test/reverse_proxy_test.exs b/test/reverse_proxy_test.exs index 3a83c4c48..0672f57db 100644 --- a/test/reverse_proxy_test.exs +++ b/test/reverse_proxy_test.exs @@ -42,6 +42,18 @@ defp user_agent_mock(user_agent, invokes) do end) end + describe "reverse proxy" do + test "do not track successful request", %{conn: conn} do + user_agent_mock("hackney/1.15.1", 2) + url = "/success" + + conn = ReverseProxy.call(conn, url) + + assert conn.status == 200 + assert Cachex.get(:failed_proxy_url_cache, url) == {:ok, nil} + end + end + describe "user-agent" do test "don't keep", %{conn: conn} do user_agent_mock("hackney/1.15.1", 2) @@ -71,9 +83,15 @@ test "length returns error if content-length more than option", %{conn: conn} do user_agent_mock("hackney/1.15.1", 0) assert capture_log(fn -> - ReverseProxy.call(conn, "/user-agent", max_body_length: 4) + ReverseProxy.call(conn, "/huge-file", max_body_length: 4) end) =~ - "[error] Elixir.Pleroma.ReverseProxy: request to \"/user-agent\" failed: :body_too_large" + "[error] Elixir.Pleroma.ReverseProxy: request to \"/huge-file\" failed: :body_too_large" + + assert {:ok, true} == Cachex.get(:failed_proxy_url_cache, "/huge-file") + + assert capture_log(fn -> + ReverseProxy.call(conn, "/huge-file", max_body_length: 4) + end) == "" end defp stream_mock(invokes, with_close? \\ false) do @@ -140,28 +158,54 @@ defp error_mock(status) when is_integer(status) do describe "returns error on" do test "500", %{conn: conn} do error_mock(500) + url = "/status/500" - capture_log(fn -> ReverseProxy.call(conn, "/status/500") end) =~ + capture_log(fn -> ReverseProxy.call(conn, url) end) =~ "[error] Elixir.Pleroma.ReverseProxy: request to /status/500 failed with HTTP status 500" + + assert Cachex.get(:failed_proxy_url_cache, url) == {:ok, true} + + {:ok, ttl} = Cachex.ttl(:failed_proxy_url_cache, url) + assert ttl <= 60_000 end test "400", %{conn: conn} do error_mock(400) + url = "/status/400" - capture_log(fn -> ReverseProxy.call(conn, "/status/400") end) =~ + capture_log(fn -> ReverseProxy.call(conn, url) end) =~ "[error] Elixir.Pleroma.ReverseProxy: request to /status/400 failed with HTTP status 400" + + assert Cachex.get(:failed_proxy_url_cache, url) == {:ok, true} + assert Cachex.ttl(:failed_proxy_url_cache, url) == {:ok, nil} + end + + test "403", %{conn: conn} do + error_mock(403) + url = "/status/403" + + capture_log(fn -> + ReverseProxy.call(conn, url, failed_request_ttl: :timer.seconds(120)) + end) =~ + "[error] Elixir.Pleroma.ReverseProxy: request to /status/403 failed with HTTP status 403" + + {:ok, ttl} = Cachex.ttl(:failed_proxy_url_cache, url) + assert ttl > 100_000 end test "204", %{conn: conn} do - ClientMock - |> expect(:request, fn :get, "/status/204", _, _, _ -> {:ok, 204, [], %{}} end) + url = "/status/204" + expect(ClientMock, :request, fn :get, _url, _, _, _ -> {:ok, 204, [], %{}} end) capture_log(fn -> - conn = ReverseProxy.call(conn, "/status/204") + conn = ReverseProxy.call(conn, url) assert conn.resp_body == "Request failed: No Content" assert conn.halted end) =~ "[error] Elixir.Pleroma.ReverseProxy: request to \"/status/204\" failed with HTTP status 204" + + assert Cachex.get(:failed_proxy_url_cache, url) == {:ok, true} + assert Cachex.ttl(:failed_proxy_url_cache, url) == {:ok, nil} end end From 427d0c2a007db6c8424c64a8f3504420e5203bef Mon Sep 17 00:00:00 2001 From: Thibaut Girka Date: Tue, 1 Oct 2019 21:40:35 +0200 Subject: [PATCH 26/31] Store private announcements in object.data["announcements"], filter them on display --- lib/pleroma/web/activity_pub/utils.ex | 2 +- .../controllers/status_controller.ex | 14 +++++++++++++- .../controllers/status_controller_test.exs | 19 ++++++++++++++++++- 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex index 2ba182f4e..0828591ee 100644 --- a/lib/pleroma/web/activity_pub/utils.ex +++ b/lib/pleroma/web/activity_pub/utils.ex @@ -494,7 +494,7 @@ def make_unlike_data( @spec add_announce_to_object(Activity.t(), Object.t()) :: {:ok, Object.t()} | {:error, Ecto.Changeset.t()} def add_announce_to_object( - %Activity{data: %{"actor" => actor, "cc" => [Pleroma.Constants.as_public()]}}, + %Activity{data: %{"actor" => actor}}, object ) do announcements = take_announcements(object) diff --git a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex index 51456d453..79cced163 100644 --- a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex @@ -242,7 +242,19 @@ def favourited_by(%{assigns: %{user: user}} = conn, %{"id" => id}) do def reblogged_by(%{assigns: %{user: user}} = conn, %{"id" => id}) do with %Activity{} = activity <- Activity.get_by_id_with_object(id), {:visible, true} <- {:visible, Visibility.visible_for_user?(activity, user)}, - %Object{data: %{"announcements" => announces}} <- Object.normalize(activity) do + %Object{data: %{"announcements" => announces, "id" => ap_id}} <- + Object.normalize(activity) do + announces = + "Announce" + |> Activity.Queries.by_type() + |> Ecto.Query.where([a], a.actor in ^announces) + # this is to use the index + |> Activity.Queries.by_object_id(ap_id) + |> Repo.all() + |> Enum.filter(&Visibility.visible_for_user?(&1, user)) + |> Enum.map(& &1.actor) + |> Enum.uniq() + users = User |> Ecto.Query.where([u], u.ap_id in ^announces) diff --git a/test/web/mastodon_api/controllers/status_controller_test.exs b/test/web/mastodon_api/controllers/status_controller_test.exs index 727a233e7..b648ad6ff 100644 --- a/test/web/mastodon_api/controllers/status_controller_test.exs +++ b/test/web/mastodon_api/controllers/status_controller_test.exs @@ -557,7 +557,7 @@ test "reblogs privately and returns the reblogged status", %{conn: conn} do |> post("/api/v1/statuses/#{activity.id}/reblog", %{"visibility" => "private"}) assert %{ - "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 0}, + "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1}, "reblogged" => true, "visibility" => "private" } = json_response(conn, 200) @@ -1167,6 +1167,23 @@ test "does not return users who have reblogged the status but are blocked", %{ assert Enum.empty?(response) end + test "does not return users who have reblogged the status privately", %{ + conn: %{assigns: %{user: user}} = conn, + activity: activity + } do + other_user = insert(:user) + + {:ok, _, _} = CommonAPI.repeat(activity.id, other_user, %{"visibility" => "private"}) + + response = + conn + |> assign(:user, user) + |> get("/api/v1/statuses/#{activity.id}/reblogged_by") + |> json_response(:ok) + + assert Enum.empty?(response) + end + test "does not fail on an unauthenticated request", %{conn: conn, activity: activity} do other_user = insert(:user) {:ok, _, _} = CommonAPI.repeat(activity.id, other_user) From 1255ec888d5f1a186b499bf9e5c23c8c7332ed4d Mon Sep 17 00:00:00 2001 From: feld Date: Tue, 1 Oct 2019 22:16:29 +0000 Subject: [PATCH 27/31] Revert "Add upload limits to /api/v1/instance" This reverts commit db27c0dd8b18763ff2abb124ee8d641a4580cdaa. --- CHANGELOG.md | 1 + .../web/mastodon_api/controllers/mastodon_api_controller.ex | 6 +++++- test/web/mastodon_api/mastodon_api_controller_test.exs | 6 +++++- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f61efcc22..a71a9dae6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Mastodon API: Account entities now include `follow_requests_count` (planned Mastodon 3.x addition) - Pleroma API: `GET /api/v1/pleroma/accounts/:id/scrobbles` to get a list of recently scrobbled items - Pleroma API: `POST /api/v1/pleroma/scrobble` to scrobble a media item +- Mastodon API: Add `upload_limit`, `avatar_upload_limit`, `background_upload_limit`, and `banner_upload_limit` to `/api/v1/instance` ### Changed - **Breaking:** Elixir >=1.8 is now required (was >= 1.7) 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 80a7b5bef..33988bbbd 100644 --- a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex @@ -51,7 +51,11 @@ def masto_instance(conn, _params) do registrations: Pleroma.Config.get([:instance, :registrations_open]), # Extra (not present in Mastodon): max_toot_chars: Keyword.get(instance, :limit), - poll_limits: Keyword.get(instance, :poll_limits) + poll_limits: Keyword.get(instance, :poll_limits), + upload_limit: Keyword.get(instance, :upload_limit), + avatar_upload_limit: Keyword.get(instance, :avatar_upload_limit), + background_upload_limit: Keyword.get(instance, :background_upload_limit), + banner_upload_limit: Keyword.get(instance, :banner_upload_limit) } json(conn, response) diff --git a/test/web/mastodon_api/mastodon_api_controller_test.exs b/test/web/mastodon_api/mastodon_api_controller_test.exs index 68fe751e7..ae67ee89d 100644 --- a/test/web/mastodon_api/mastodon_api_controller_test.exs +++ b/test/web/mastodon_api/mastodon_api_controller_test.exs @@ -135,7 +135,11 @@ test "get instance information", %{conn: conn} do "thumbnail" => _, "languages" => _, "registrations" => _, - "poll_limits" => _ + "poll_limits" => _, + "upload_limit" => _, + "avatar_upload_limit" => _, + "background_upload_limit" => _, + "banner_upload_limit" => _ } = result assert email == from_config_email From a562cb2c3404f707f574d54024c7ee61f808e827 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Tue, 1 Oct 2019 18:29:39 -0500 Subject: [PATCH 28/31] Update AdminFE bundle --- .../{app.f774664e.css => app.8589ec81.css} | Bin ...5.15079754.css => chunk-0cb6.8d811a09.css} | Bin ...a.9e804910.css => chunk-15fa.6e185c68.css} | Bin ...d.dc6c5fb2.css => chunk-18e1.5bd2ca85.css} | Bin ...1.820645ae.css => chunk-23b2.723b6cc5.css} | Bin ...2.8ee9eaaa.css => chunk-2943.1b6fd9a7.css} | Bin ...c.a6b92ca7.css => chunk-3d1c.b2eb7234.css} | Bin ...f.14eeccbb.css => chunk-4df4.e217dea0.css} | Bin ...a.6ef5bd70.css => chunk-538a.062aa087.css} | Bin ...b.dece6ace.css => chunk-7c6b.c7882778.css} | Bin ...e.52359c55.css => chunk-7f8e.b6944d38.css} | Bin ...55ce6.css => chunk-elementUI.a842fb0a.css} | Bin ...s.36b859a1.css => chunk-libs.57fe98a3.css} | Bin priv/static/adminfe/index.html | 2 +- .../js/{app.9d5375ac.js => app.9c4316f1.js} | Bin 167236 -> 167236 bytes ...pp.9d5375ac.js.map => app.9c4316f1.js.map} | Bin 366548 -> 366548 bytes ...9e5.f5bb9b33.js => chunk-0cb6.b9f32e0c.js} | Bin 16157 -> 16157 bytes ...9b33.js.map => chunk-0cb6.b9f32e0c.js.map} | Bin 57112 -> 57112 bytes ...5fa.6dcb4448.js => chunk-15fa.34dcb9d8.js} | Bin 7919 -> 7919 bytes ...4448.js.map => chunk-15fa.34dcb9d8.js.map} | Bin 17438 -> 17438 bytes ...bbd.bc68e218.js => chunk-18e1.f8bb78f3.js} | Bin 2080 -> 2080 bytes ...e218.js.map => chunk-18e1.f8bb78f3.js.map} | Bin 9090 -> 9090 bytes ...871.4ac23900.js => chunk-23b2.442bb8df.js} | Bin 28092 -> 28092 bytes ...3900.js.map => chunk-23b2.442bb8df.js.map} | Bin 91362 -> 91362 bytes ...292.b3aa39da.js => chunk-2943.8ab5d0d9.js} | Bin 231394 -> 231394 bytes ...39da.js.map => chunk-2943.8ab5d0d9.js.map} | Bin 689117 -> 689117 bytes ...d1c.47c8fa87.js => chunk-3d1c.3334d3f1.js} | Bin 4822 -> 4822 bytes ...fa87.js.map => chunk-3d1c.3334d3f1.js.map} | Bin 18519 -> 18519 bytes ...98f.b02acd71.js => chunk-4df4.9655f394.js} | Bin 17765 -> 17765 bytes ...cd71.js.map => chunk-4df4.9655f394.js.map} | Bin 66937 -> 66937 bytes ...38a.18908e98.js => chunk-538a.04530055.js} | Bin 5112 -> 5112 bytes ...8e98.js.map => chunk-538a.04530055.js.map} | Bin 19586 -> 19586 bytes ...c6b.24877470.js => chunk-7c6b.5240e052.js} | Bin 7947 -> 7947 bytes ...7470.js.map => chunk-7c6b.5240e052.js.map} | Bin 26432 -> 26432 bytes ...f8e.b2353c0a.js => chunk-7f8e.c1eb619d.js} | Bin 9618 -> 9618 bytes ...3c0a.js.map => chunk-7f8e.c1eb619d.js.map} | Bin 39890 -> 39890 bytes ...74aa2ca.js => chunk-elementUI.fa319e7b.js} | Bin 638936 -> 638936 bytes ...js.map => chunk-elementUI.fa319e7b.js.map} | Bin 2312798 -> 2312798 bytes ...ibs.3ed10ef6.js => chunk-libs.35c18287.js} | Bin 275816 -> 275816 bytes ...0ef6.js.map => chunk-libs.35c18287.js.map} | Bin 1641569 -> 1641569 bytes .../adminfe/static/js/runtime.46db235c.js | Bin 0 -> 3922 bytes ...6b7511a.js.map => runtime.46db235c.js.map} | Bin 16658 -> 16658 bytes .../adminfe/static/js/runtime.c6b7511a.js | Bin 3922 -> 0 bytes 43 files changed, 1 insertion(+), 1 deletion(-) rename priv/static/adminfe/{app.f774664e.css => app.8589ec81.css} (100%) rename priv/static/adminfe/{chunk-a9e5.15079754.css => chunk-0cb6.8d811a09.css} (100%) rename priv/static/adminfe/{chunk-15fa.9e804910.css => chunk-15fa.6e185c68.css} (100%) rename priv/static/adminfe/{chunk-1bbd.dc6c5fb2.css => chunk-18e1.5bd2ca85.css} (100%) rename priv/static/adminfe/{chunk-3871.820645ae.css => chunk-23b2.723b6cc5.css} (100%) rename priv/static/adminfe/{chunk-6292.8ee9eaaa.css => chunk-2943.1b6fd9a7.css} (100%) rename priv/static/adminfe/{chunk-3d1c.a6b92ca7.css => chunk-3d1c.b2eb7234.css} (100%) rename priv/static/adminfe/{chunk-598f.14eeccbb.css => chunk-4df4.e217dea0.css} (100%) rename priv/static/adminfe/{chunk-538a.6ef5bd70.css => chunk-538a.062aa087.css} (100%) rename priv/static/adminfe/{chunk-7c6b.dece6ace.css => chunk-7c6b.c7882778.css} (100%) rename priv/static/adminfe/{chunk-7f8e.52359c55.css => chunk-7f8e.b6944d38.css} (100%) rename priv/static/adminfe/{chunk-elementUI.d2a55ce6.css => chunk-elementUI.a842fb0a.css} (100%) rename priv/static/adminfe/{chunk-libs.36b859a1.css => chunk-libs.57fe98a3.css} (100%) rename priv/static/adminfe/static/js/{app.9d5375ac.js => app.9c4316f1.js} (99%) rename priv/static/adminfe/static/js/{app.9d5375ac.js.map => app.9c4316f1.js.map} (99%) rename priv/static/adminfe/static/js/{chunk-a9e5.f5bb9b33.js => chunk-0cb6.b9f32e0c.js} (99%) rename priv/static/adminfe/static/js/{chunk-a9e5.f5bb9b33.js.map => chunk-0cb6.b9f32e0c.js.map} (99%) rename priv/static/adminfe/static/js/{chunk-15fa.6dcb4448.js => chunk-15fa.34dcb9d8.js} (99%) rename priv/static/adminfe/static/js/{chunk-15fa.6dcb4448.js.map => chunk-15fa.34dcb9d8.js.map} (99%) rename priv/static/adminfe/static/js/{chunk-1bbd.bc68e218.js => chunk-18e1.f8bb78f3.js} (85%) rename priv/static/adminfe/static/js/{chunk-1bbd.bc68e218.js.map => chunk-18e1.f8bb78f3.js.map} (98%) rename priv/static/adminfe/static/js/{chunk-3871.4ac23900.js => chunk-23b2.442bb8df.js} (99%) rename priv/static/adminfe/static/js/{chunk-3871.4ac23900.js.map => chunk-23b2.442bb8df.js.map} (99%) rename priv/static/adminfe/static/js/{chunk-6292.b3aa39da.js => chunk-2943.8ab5d0d9.js} (99%) rename priv/static/adminfe/static/js/{chunk-6292.b3aa39da.js.map => chunk-2943.8ab5d0d9.js.map} (99%) rename priv/static/adminfe/static/js/{chunk-3d1c.47c8fa87.js => chunk-3d1c.3334d3f1.js} (99%) rename priv/static/adminfe/static/js/{chunk-3d1c.47c8fa87.js.map => chunk-3d1c.3334d3f1.js.map} (99%) rename priv/static/adminfe/static/js/{chunk-598f.b02acd71.js => chunk-4df4.9655f394.js} (99%) rename priv/static/adminfe/static/js/{chunk-598f.b02acd71.js.map => chunk-4df4.9655f394.js.map} (99%) rename priv/static/adminfe/static/js/{chunk-538a.18908e98.js => chunk-538a.04530055.js} (99%) rename priv/static/adminfe/static/js/{chunk-538a.18908e98.js.map => chunk-538a.04530055.js.map} (99%) rename priv/static/adminfe/static/js/{chunk-7c6b.24877470.js => chunk-7c6b.5240e052.js} (99%) rename priv/static/adminfe/static/js/{chunk-7c6b.24877470.js.map => chunk-7c6b.5240e052.js.map} (99%) rename priv/static/adminfe/static/js/{chunk-7f8e.b2353c0a.js => chunk-7f8e.c1eb619d.js} (99%) rename priv/static/adminfe/static/js/{chunk-7f8e.b2353c0a.js.map => chunk-7f8e.c1eb619d.js.map} (99%) rename priv/static/adminfe/static/js/{chunk-elementUI.374aa2ca.js => chunk-elementUI.fa319e7b.js} (99%) rename priv/static/adminfe/static/js/{chunk-elementUI.374aa2ca.js.map => chunk-elementUI.fa319e7b.js.map} (99%) rename priv/static/adminfe/static/js/{chunk-libs.3ed10ef6.js => chunk-libs.35c18287.js} (99%) rename priv/static/adminfe/static/js/{chunk-libs.3ed10ef6.js.map => chunk-libs.35c18287.js.map} (99%) create mode 100644 priv/static/adminfe/static/js/runtime.46db235c.js rename priv/static/adminfe/static/js/{runtime.c6b7511a.js.map => runtime.46db235c.js.map} (92%) delete mode 100644 priv/static/adminfe/static/js/runtime.c6b7511a.js diff --git a/priv/static/adminfe/app.f774664e.css b/priv/static/adminfe/app.8589ec81.css similarity index 100% rename from priv/static/adminfe/app.f774664e.css rename to priv/static/adminfe/app.8589ec81.css diff --git a/priv/static/adminfe/chunk-a9e5.15079754.css b/priv/static/adminfe/chunk-0cb6.8d811a09.css similarity index 100% rename from priv/static/adminfe/chunk-a9e5.15079754.css rename to priv/static/adminfe/chunk-0cb6.8d811a09.css diff --git a/priv/static/adminfe/chunk-15fa.9e804910.css b/priv/static/adminfe/chunk-15fa.6e185c68.css similarity index 100% rename from priv/static/adminfe/chunk-15fa.9e804910.css rename to priv/static/adminfe/chunk-15fa.6e185c68.css diff --git a/priv/static/adminfe/chunk-1bbd.dc6c5fb2.css b/priv/static/adminfe/chunk-18e1.5bd2ca85.css similarity index 100% rename from priv/static/adminfe/chunk-1bbd.dc6c5fb2.css rename to priv/static/adminfe/chunk-18e1.5bd2ca85.css diff --git a/priv/static/adminfe/chunk-3871.820645ae.css b/priv/static/adminfe/chunk-23b2.723b6cc5.css similarity index 100% rename from priv/static/adminfe/chunk-3871.820645ae.css rename to priv/static/adminfe/chunk-23b2.723b6cc5.css diff --git a/priv/static/adminfe/chunk-6292.8ee9eaaa.css b/priv/static/adminfe/chunk-2943.1b6fd9a7.css similarity index 100% rename from priv/static/adminfe/chunk-6292.8ee9eaaa.css rename to priv/static/adminfe/chunk-2943.1b6fd9a7.css diff --git a/priv/static/adminfe/chunk-3d1c.a6b92ca7.css b/priv/static/adminfe/chunk-3d1c.b2eb7234.css similarity index 100% rename from priv/static/adminfe/chunk-3d1c.a6b92ca7.css rename to priv/static/adminfe/chunk-3d1c.b2eb7234.css diff --git a/priv/static/adminfe/chunk-598f.14eeccbb.css b/priv/static/adminfe/chunk-4df4.e217dea0.css similarity index 100% rename from priv/static/adminfe/chunk-598f.14eeccbb.css rename to priv/static/adminfe/chunk-4df4.e217dea0.css diff --git a/priv/static/adminfe/chunk-538a.6ef5bd70.css b/priv/static/adminfe/chunk-538a.062aa087.css similarity index 100% rename from priv/static/adminfe/chunk-538a.6ef5bd70.css rename to priv/static/adminfe/chunk-538a.062aa087.css diff --git a/priv/static/adminfe/chunk-7c6b.dece6ace.css b/priv/static/adminfe/chunk-7c6b.c7882778.css similarity index 100% rename from priv/static/adminfe/chunk-7c6b.dece6ace.css rename to priv/static/adminfe/chunk-7c6b.c7882778.css diff --git a/priv/static/adminfe/chunk-7f8e.52359c55.css b/priv/static/adminfe/chunk-7f8e.b6944d38.css similarity index 100% rename from priv/static/adminfe/chunk-7f8e.52359c55.css rename to priv/static/adminfe/chunk-7f8e.b6944d38.css diff --git a/priv/static/adminfe/chunk-elementUI.d2a55ce6.css b/priv/static/adminfe/chunk-elementUI.a842fb0a.css similarity index 100% rename from priv/static/adminfe/chunk-elementUI.d2a55ce6.css rename to priv/static/adminfe/chunk-elementUI.a842fb0a.css diff --git a/priv/static/adminfe/chunk-libs.36b859a1.css b/priv/static/adminfe/chunk-libs.57fe98a3.css similarity index 100% rename from priv/static/adminfe/chunk-libs.36b859a1.css rename to priv/static/adminfe/chunk-libs.57fe98a3.css diff --git a/priv/static/adminfe/index.html b/priv/static/adminfe/index.html index 47901efe8..70bb8bd3b 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/app.9d5375ac.js b/priv/static/adminfe/static/js/app.9c4316f1.js similarity index 99% rename from priv/static/adminfe/static/js/app.9d5375ac.js rename to priv/static/adminfe/static/js/app.9c4316f1.js index 9f86a99578e572425dbdf5ff834406e31cd8a594..6af94c36b294d4fe94092fe13b31e39466c577d7 100644 GIT binary patch delta 83 zcmV-Z0IdJSnhM043b2gs1Tr}^Gn0_+{{%E-W;BzK?iaJF@5urLFk@mivv>0U0s}Z@ pF|&E~76Ah?Gh&mE?ihor_P46`0gRRoV>B}{HfAv{YI81aVQ?6OBZmM0 delta 84 zcmX>yi|fcNt_?l!Sj>zpjV4cc_n*bo(jpDW5Z}D&{YfU4M9Wmu&E=o|GchM6rEISJ nD#plSY+-H)Qqa8W+xAu87<;DirI;FOV delta 39 scmcbzUhK+xv4$4LEli1w>?x+k=BA0s?HPShw-FjyL0Gs;?p8x;= diff --git a/priv/static/adminfe/static/js/chunk-15fa.6dcb4448.js b/priv/static/adminfe/static/js/chunk-15fa.34dcb9d8.js similarity index 99% rename from priv/static/adminfe/static/js/chunk-15fa.6dcb4448.js rename to priv/static/adminfe/static/js/chunk-15fa.34dcb9d8.js index 70df4d3a2b7de00d491607f8276939b87dc417a3..b0819b13814e652c4d30a53b0d9af0904098b58f 100644 GIT binary patch delta 23 ecmaEF``&iL137+Ula%Bn%M=T}tYW>~!~y_!S_tw0 delta 23 ecmaEF``&iL137-Pl;k866B7%)tYW>~!~y_zG6=u` diff --git a/priv/static/adminfe/static/js/chunk-15fa.6dcb4448.js.map b/priv/static/adminfe/static/js/chunk-15fa.34dcb9d8.js.map similarity index 99% rename from priv/static/adminfe/static/js/chunk-15fa.6dcb4448.js.map rename to priv/static/adminfe/static/js/chunk-15fa.34dcb9d8.js.map index 9a7d1241ae773d0c11ad5f0ee12b2bba103bb6ef..2ec54c8aa2f83f9d2b6b922d61b8b96b54073658 100644 GIT binary patch delta 22 dcmbQ&!8osjal;`Fc4L#2 hRMNBtOHE$Ro+b>`sh4Jvlw@v^W~`T0te2Zu002-48SnrA delta 84 zcmZ1=us~pfEpt**%0ve*;bPlN4W%mg@+c)uomAUQy*!QLiF1t@Q#W2X#wG}sbaFE> hRMNBtOHE$Ro+b>`sh5;&W|3-SXrY%?te2Zu0047<8i@b^ diff --git a/priv/static/adminfe/static/js/chunk-1bbd.bc68e218.js.map b/priv/static/adminfe/static/js/chunk-18e1.f8bb78f3.js.map similarity index 98% rename from priv/static/adminfe/static/js/chunk-1bbd.bc68e218.js.map rename to priv/static/adminfe/static/js/chunk-18e1.f8bb78f3.js.map index c901677be02bbede1933e44a6c3603ab72c8712d..b61e3bc20bd74f947ae4df312e94541d3e635fca 100644 GIT binary patch delta 25 gcmZp2Z*t%8jf=-3)le_ZA}Pt-BF%U+6L*6I0CXn^4FCWD delta 25 gcmZp2Z*t%8jf*EKDMc?S*~}u<$k1Xl6L*6I0C*n=FaQ7m diff --git a/priv/static/adminfe/static/js/chunk-3871.4ac23900.js b/priv/static/adminfe/static/js/chunk-23b2.442bb8df.js similarity index 99% rename from priv/static/adminfe/static/js/chunk-3871.4ac23900.js rename to priv/static/adminfe/static/js/chunk-23b2.442bb8df.js index e957e4552174d28ddfefae63819467f07fa577a5..61cfc7826ab266058c442af7e28736b9f9cf2fb3 100644 GIT binary patch delta 38 rcmdmUn{m%=#tAkoM#f1-8yzxoML-Na6BDDPB#V?Zy{uxr+{6L^3hxZk delta 38 rcmdmUn{m%=#tAko#uny=8yzxoML-Nalf+~rV@m@Ay{uxr+{6L^0fh`4 diff --git a/priv/static/adminfe/static/js/chunk-3871.4ac23900.js.map b/priv/static/adminfe/static/js/chunk-23b2.442bb8df.js.map similarity index 99% rename from priv/static/adminfe/static/js/chunk-3871.4ac23900.js.map rename to priv/static/adminfe/static/js/chunk-23b2.442bb8df.js.map index 8bb213374d36bdbbba08d7ccc645fe9e350cb393..474d1086ec7955c933c7d8ecc812ccfe1c81ceda 100644 GIT binary patch delta 28 kcmaEKlJ(I^)(t)-yhg@JMtUYDMoCE)DQTM{OFBOQ0Ho<(AkX^KILrCwICUT$Im0C(ID A=l}o! delta 47 zcmaFV&-bXGZ-NbrnUSSYqeH6$W2*zxRtM(3b`g+>UXpQQqOoO4qFz?9UT$Im0C|cJ A5dZ)H diff --git a/priv/static/adminfe/static/js/chunk-6292.b3aa39da.js.map b/priv/static/adminfe/static/js/chunk-2943.8ab5d0d9.js.map similarity index 99% rename from priv/static/adminfe/static/js/chunk-6292.b3aa39da.js.map rename to priv/static/adminfe/static/js/chunk-2943.8ab5d0d9.js.map index 577df8f956b636635318b4152c749f4a21db9390..0ecc45de4a905986aac70cdb59387c2ae2a24edf 100644 GIT binary patch delta 61 zcmccHu64IvtD%K)3)ACYyhfHL#(EZsNv0_VDVFUYelY~!~y_kg$Mxv delta 23 ecmcbndQEl1aUp&a^JI&(L<@7htYW>~!~y_n;0Qth diff --git a/priv/static/adminfe/static/js/chunk-3d1c.47c8fa87.js.map b/priv/static/adminfe/static/js/chunk-3d1c.3334d3f1.js.map similarity index 99% rename from priv/static/adminfe/static/js/chunk-3d1c.47c8fa87.js.map rename to priv/static/adminfe/static/js/chunk-3d1c.3334d3f1.js.map index d10007b9158c47d2a97749252f77ccbe1256114e..3dd0d77a91c5c7f2a108859b07f8cbb89eec519a 100644 GIT binary patch delta 23 fcmcaUf${nT#toI?9LC1RCMm{ghMOD3uNeRUYFG$m delta 23 fcmcaUf${nT#toI?946+;7HNqV=9?SEuNeRUZ7~R@ diff --git a/priv/static/adminfe/static/js/chunk-598f.b02acd71.js b/priv/static/adminfe/static/js/chunk-4df4.9655f394.js similarity index 99% rename from priv/static/adminfe/static/js/chunk-598f.b02acd71.js rename to priv/static/adminfe/static/js/chunk-4df4.9655f394.js index fb2374e3bf0730e33889eeb49442937c9f524331..afed4bab68aef7058b0a9ebe7e5ce2426be8ec31 100644 GIT binary patch delta 38 rcmaFb#rU*~ae@ttNlKc@Mh8Jx5fDSq(#+H}&DhdJFRNHDH?aT!`kf2( delta 38 rcmaFb#rU*~ae@ttsij5QMh8Jx5fDQ!$-pQvImO&iFRNHDH?aT!_|yy? diff --git a/priv/static/adminfe/static/js/chunk-598f.b02acd71.js.map b/priv/static/adminfe/static/js/chunk-4df4.9655f394.js.map similarity index 99% rename from priv/static/adminfe/static/js/chunk-598f.b02acd71.js.map rename to priv/static/adminfe/static/js/chunk-4df4.9655f394.js.map index da8e8c4ad497d772165dc4482b493aa0b1b41eed..a1e9bca7a714b35ebb21bde786ea8cb8ccc79d82 100644 GIT binary patch delta 28 kcmey_#qzU@Wy8#1UXzqG6Fo~aQ`0nKOOwsZf-i3b0HkRPCjbBd delta 28 kcmey_#qzU@Wy8#1UQ~!~y_pA_xZn delta 23 ecmeyN{zHAkOJROP3rhowR7(rJtYW>~!~y_s9|%zZ diff --git a/priv/static/adminfe/static/js/chunk-538a.18908e98.js.map b/priv/static/adminfe/static/js/chunk-538a.04530055.js.map similarity index 99% rename from priv/static/adminfe/static/js/chunk-538a.18908e98.js.map rename to priv/static/adminfe/static/js/chunk-538a.04530055.js.map index 4bb072450dde46da4dcb16ca1e8d8de1741f9b24..d3741c30a8623898659c2acc2652830f5efbd51d 100644 GIT binary patch delta 23 ecmZpg$=EcLaYMWmhk=Qyv4Mf9>E?7PX(Iqv0tSHq delta 23 ecmZpg$=EcLaYMWmhoOa~fkmpN#pZM=X(IqxYX-{z diff --git a/priv/static/adminfe/static/js/chunk-7c6b.24877470.js b/priv/static/adminfe/static/js/chunk-7c6b.5240e052.js similarity index 99% rename from priv/static/adminfe/static/js/chunk-7c6b.24877470.js rename to priv/static/adminfe/static/js/chunk-7c6b.5240e052.js index 059bcf3223073733caee5a87825e1315313ee296..12eb54a3218f85db6682af398d1500ef4e5d3de5 100644 GIT binary patch delta 23 ecmeCS>$cnQM~>gr$iyJkz|=@Dt5`2Lu>b&JY6q$S delta 23 ecmeCS>$cnQM~>gf#KPR%#N0qHt5`2Lu>b&I5eJF@ diff --git a/priv/static/adminfe/static/js/chunk-7c6b.24877470.js.map b/priv/static/adminfe/static/js/chunk-7c6b.5240e052.js.map similarity index 99% rename from priv/static/adminfe/static/js/chunk-7c6b.24877470.js.map rename to priv/static/adminfe/static/js/chunk-7c6b.5240e052.js.map index cb00fc3ebf9d73ae9e6b644b7f6921233c517e33..1463b8ba42b9b24554f433f4e2d88a7f2a6ba8cd 100644 GIT binary patch delta 23 fcmX?bj`6@b#tpv|I82R93{nkDjW#nW+PMJ$beRYe delta 23 fcmX?bj`6@b#tpv|IE+jz%*{>A4K_0=+PMJ$bASi( diff --git a/priv/static/adminfe/static/js/chunk-7f8e.b2353c0a.js b/priv/static/adminfe/static/js/chunk-7f8e.c1eb619d.js similarity index 99% rename from priv/static/adminfe/static/js/chunk-7f8e.b2353c0a.js rename to priv/static/adminfe/static/js/chunk-7f8e.c1eb619d.js index 9a0afaf67616d6957479e1da58d923d3acbd1caf..56ce1d5efd036969bcae6e75f3e904039c7df589 100644 GIT binary patch delta 23 ecmbQ_J;{4RvnqeGVQP|@p=F9*RoWjyWC+Xv delta 23 ecmcb#o$1ncrVZZ497#sTrpCzziJOCs>oWjvp9qiu diff --git a/priv/static/adminfe/static/js/chunk-elementUI.374aa2ca.js b/priv/static/adminfe/static/js/chunk-elementUI.fa319e7b.js similarity index 99% rename from priv/static/adminfe/static/js/chunk-elementUI.374aa2ca.js rename to priv/static/adminfe/static/js/chunk-elementUI.fa319e7b.js index b221f866c0a926a4b135bee418f1356742b95492..90ae35a35dbe03f30c43c5fa6dec711114d84e76 100644 GIT binary patch delta 43 zcmccdU+uF7M2#)7Pc1l7LF~PC-?KGB^n!ArkW?|Wfkk?CKdnyf1nTS delta 43 zcmccdU+uF7M2#)7Pc1l7LF~PC-?Iko0}vi8YL&{Wfkk?CKdnyec}(` diff --git a/priv/static/adminfe/static/js/chunk-elementUI.374aa2ca.js.map b/priv/static/adminfe/static/js/chunk-elementUI.fa319e7b.js.map similarity index 99% rename from priv/static/adminfe/static/js/chunk-elementUI.374aa2ca.js.map rename to priv/static/adminfe/static/js/chunk-elementUI.fa319e7b.js.map index b58957727727c3da9697ab9dd8d61d42a8039edd..678122a98dbc772133199ba1ae1b5b9c87f406e4 100644 GIT binary patch delta 142 zcmWN=KMI0i0Eb~LE48xH{%VsThxaE#}9vCs^ktZfRGv%dg IO!j;J0Zv*zTL1t6 delta 142 zcmWN=yA6U+0EW>ZDi>5lQ64@J7(q>(=f=vC!rtOftn3`c#6@n+2JYZ@lGDFmUeAX- z4diR6k;V#S6n>(cGpEbh+T+zeI~Q@nDQAp1XTk-SOu6Ek8FLmax#5;O?pg7`n$5R! H?eG2vNGLu} diff --git a/priv/static/adminfe/static/js/chunk-libs.3ed10ef6.js b/priv/static/adminfe/static/js/chunk-libs.35c18287.js similarity index 99% rename from priv/static/adminfe/static/js/chunk-libs.3ed10ef6.js rename to priv/static/adminfe/static/js/chunk-libs.35c18287.js index b31c6cd5b7b053c709327382aff35cb72c746e4b..4b76d98e60506d3cbe105610495b04ae18cc44ec 100644 GIT binary patch delta 32 ncmaFyO5nvSfrb{w7N!>FEi6%{e5T2U7Dg84dRfJKxrqe;)h!D} delta 32 ncmaFyO5nvSfrb{w7N!>FEi6%{e5omh2B~RgdRfJKxrqe;+ZGGX diff --git a/priv/static/adminfe/static/js/chunk-libs.3ed10ef6.js.map b/priv/static/adminfe/static/js/chunk-libs.35c18287.js.map similarity index 99% rename from priv/static/adminfe/static/js/chunk-libs.3ed10ef6.js.map rename to priv/static/adminfe/static/js/chunk-libs.35c18287.js.map index 61fd05273efccadb908e1cf006ef1f95246e1f47..0a3580834454c3d7793040f9c76c0912e491c255 100644 GIT binary patch delta 107 zcmWN=w+Vny6hKkTIma~=MEB7p>?|JG|4MKT!P3q>+{Am}e8=(~%O;xWVu&f$Wk1Rp j=5FezaetLfhb}$(3>Y$E%!Da3<}6sUV$J4$ReP>K${jF( delta 107 zcmWN=xe0(k6hJ||@B1u66DwNn!OY-;`9^|m2!>|%VJE(U`406RY7;|DvBVbVav$x@ jY8>We+F$L`qtAdLBgRaaGGoqyB`el!*s{A{-Jk0Z_60Es diff --git a/priv/static/adminfe/static/js/runtime.46db235c.js b/priv/static/adminfe/static/js/runtime.46db235c.js new file mode 100644 index 0000000000000000000000000000000000000000..898c5b505f5c39fa557cbfd14f3ebfbb59aed333 GIT binary patch literal 3922 zcmai1TW{mG5`OQmaCiVigc(M%Eniny>{;x~qL+iS?UQR16fco>71E`srm5}!-l6W< zCR-qgNs1iKe4P1a=0;VTyWHqN;&V5i=s?5F*x(OlCzf!+5`74dM8QvVBsu)dk3=LP zDU!o+FWxX(NPX|Jy_n4~PuM{mNp;}I>9nAys>}jE0SUNXO~SnwZYQ~(DEw3US#n2D z);QyyPLgJ<{OM8u&zh68?ul|%6oDcZPp5%A*khvLAFd^2(`g2(?u+hvT+lMhl?!ly z_g1>f>WQf}pzL%io&rPi%jE!iz?N%oFHCxKCDEXteq%bB8cgcHy2Kpxk=W!WBs_`s z{Eh4Oalg+4rV!@&xMSJxZxBR$z1hsy>p{+1NqD4@J)NF4#3)KvN^IG>mm}2b#r@42AUTiL zLNdUmmD4;1N>|(EQY_$J^X}u*3-?7U@giQ$>!ENpXDr;Tk$(<&3SO27f{4-6%tH7G z!>b3W9le+CLm~aJ{O{Ax?EXjgAOlq7`U}Fu00WpeRTtT2i0nW>Oq(P2=ifPJ^R#ks zPB6!AGb%g#f85e?g91lZ>x{Ty0k3PsgezxJAlI)?$gt$rjA(vdKl5t^2>$8L;9Hf;Ex9Adk~iE`^o!H zpC&b-Owdfu&_uDkkb?fI12nTUgaM-p6#TuCCD0&bfC^th1^+w=C;#FZnPdWCU}>L7 zq!#b9ybwW#ub8?_>VX&V*^^rmUNLSNw}QFH-xHt1FmSkK7YAt+?`2WS&ghq!#^SRB zzXP!cP3-ZC_agy`Z7wp9o_iy{dY_Wp{N%ioP@XYcN|&I@saps$MSXHHl}n;|NEp?8 zI#m>QjNDrr%_XWoVmJVgFgzHLed{JZ#^CLl%lX~s@(#^j<3t%T8SyP}PQ!_6o3dr> z=g&EjuU9dTwgX2ri2^N`^EBNEHSDi521>VTF_$6l*IQ0pECo-uV&Jl<;Y77b)AdFz z`u!~@E*5ZNp`w1h<-~Zt3}qP4hxLXNm+?kz1sk}vNjqr6(pZFI;OLeUS3Hiwn5}v_ zEhlbQajX_299mA~kxW<7R&<=WS%xZ&_@GD2i7PJGo9&3RJ5F52i!hAijuStZ=n6?1 zXl}@6kjsW(>?5=)3c?G??9Yvl1o_KO?duLmhkfJ%BS27kj)NG6_dU*fmY}|{)gX@1heX1E4y?qe z7+wWxQD0>+Y62KSYH4d_AyuwrS0VdvMjC2uC+-{r~Z!)Hw}mU!@Hza zu3^eRgR5gN4gdpJenoXh5TM7Z!lADbZ;Y2<^}JT7tT7xwmbh=IeBUE6yma7*CB4@{ z>g(!%sKBo@&9$iYg5Ia^W}L@Jk1* zu(t8+(IhKap800ypZRt=X-p@L_fE4*D3I(lGq(@-cOzs#gstX&OF10!=AcV7qf5%H zXXS0vw!_QzX873HD?c{oYl}xLT$lShkLztFED*{~`MgIut_m1u=i`hq0-&rPXQ$&@ zUSgm{oP|qV=MKMECZwwkQzB*$RSR`RXZ_4846UaD%rxu-flR&%^^i5cubU~R)^|FVr3rwDOgUL4I^z28Ie@}p$4 zF+5B*2g9>oWiw7$btI!t!?(uDl8j@7F}xOD&G}i^DvS%HYiwyrIIOr4|61Z;i76CM zmVh{jA%Cu0STa&6BBctidCd|A;jJ!Y_{wXR9Kq7M0@aq03riq0i@!pJo0DiskfK0J Jma~(GgKs(Lvi1M~ delta 735 zcmZvZy-Qp{6vf%6gw+kkO07g+@PoMGe1F)4ZERBp!fWo_nTHTaC)x@A2@cq8VJ(Ef z>uvHErM7Y2+jaICwi%dvI5X$|&i&os{%-JRt=Q#8({fzm<<+mBr&QLkxH#vdC_e8h zW?7zs4KiP(HFa}Y_~Azw!(=iUe^1Ac`1t80KCO?MjnQ;kmUzPD@Im zdDvoWw{4iJJg-Ut$hak>D#ukXTc;_HP6%Q8T8i3f4wVUv_BHM)&fe5$YB0Lz)9L$8 zW5Q@H3Qc0zJ34I_o;}9_xb@JpwEK5jEuFKD^Ege87R1a!7@%*r$ia1O!c1daawHJ{ z?OhD1vBU&xny>rMD~I#NX*RV@BvWtDwQ+EEI1jf_=7|=Au9cgE--88D!-x_RQjY(B zs7f0Q5thDV{l?RyLRq^+Bl|K>P|h-hr96GWprF7I{~q;tnh2ZAr;~^X{N_lJFkx>(nSCO diff --git a/priv/static/adminfe/static/js/runtime.c6b7511a.js b/priv/static/adminfe/static/js/runtime.c6b7511a.js deleted file mode 100644 index 0e13fe45aab0135a56a601af9ecd6ea99bdaecf6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3922 zcmai1ZI9cy5&qs^;bj3sgt?4lTefUR2+|;5g1cVPTtB(Sfucq$C0f#@sN37K_1`p)Q>01Q#jMc`(2ocVUp=s?fnDG@FzI10)OTQ zmd8FTwSJ;^_= zS&w>jXTa7*O~ge5E{@0Y*%PcdpZBN-e$ou~;$$!vm_q&3ThYr@XwtB2W3g8UX5u@a z$vD`_x3>RIy6+rdS!~hToqrd{^*%!kJp4SMjlq%N)v+ujcpAngRXF^4~n+SqF6WZKv*#iwGc zSh>9mShu-7HBAo~xI-7zZGVd8GE-l^6J+?Z}1 zE^aiPo`}qA_4R%ctpl`Tv^4A$4f>(NX@>^s{1h4@i#2}}Im;|A}-mJsru%2eC7v;2X zK!PQJlu0st-#!Kj=ZkP7!;moj{?qdddIYQVMZDVABP+qk(QKY;yMf*V%Yy9{8K!FN6JEdC6ozd0P(i&BU_A-?d2HL;y~T`_tu&s zsV$yMg%$U?fsD}eIsay}UEg77lFB}pP~`xY#BrQ+10|9|hGTO+yB|sW&{arKE{cl3 z()l8jw1n6yUVNu`?rz6>Q2pY^>eFjoIu_|wNk>(r0b!v-u*YqeI)mbCD_P1Nr^m#? zANI}VF;cEosYMzq+|&w;(Prchy z^5OI6$%RlRWCk-b$wW~?%Kxi8GBYzm5u+Lv{9g?f(jaDlnl`9{d!AUbzwnG4L`o>K zv`ZwQ(hqr2rd~=L%54s6fRMHej>zstkT$l=MOlq6mM}%eEB^O=WIGFoP zb0w$KjN=zq?ya7N!9}LO9a}yV1`1ZmU^39j(4Vk^hi4X_~lD#L6$2CnjoVXP1JMZmx?r) zq><*vY=*gP48|cs>#}6B1Yv${eZ(tXc4}95U^*Nk7a0MA(s3NdFvkBog2}VOASK&& zfg&fr8sVte3*r5P(B5Li%QnJA%QuFJA=D+aShaeBHkT0kT^H%Z$8x7-SM>DWxB(4% z65h$_y(L|65Y|%BHfxYHf;lW*EDh|{113fqafETh71>bUb+rU{g{?wynm@*X|0S}L z=IQtf>xd%GZW5zyFru^OrS3_Onjw5=cYYYlq2izd_Y~ z)*rh=#&qi18}ly}Z7@Yw@m#44l(J~5`Wy+4R+ny<=t~!Y9@p6f)2aL8<4xls_wY9E zlxvyNQ|Ri{i9^89m0wZaGAx)fQJ0o{i+F21pw;t>Le&Ms5oDl4L*@D&p!m|BWrhu2 zhpDfx|FHsZcv2|rZd?c&%q88Hbr`8DjQU#p8*ASUVn?aG)4kcgusM|fPA|m>vW=ol zx$E@Rb-37T*#UU`VdLif*2y@=#3c}Y+a}oD=% zjzWI@RB3g5Y0b5C*p+5&r+2|EbMfg~Pb@Ps+^H^IS6d~y_s8ZH`_`NM8KB;tVB>aroA4B6m|NbfN+A@v}$T|!H!M{}eQ5^pdV7~|- From c8b01f6667a9b5b158103de449a7769c9274bcc3 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Wed, 2 Oct 2019 14:13:52 +0700 Subject: [PATCH 29/31] Extract instance actions from `MastodonAPIController` to `InstanceController` --- .../controllers/instance_controller.ex | 17 ++++ .../controllers/mastodon_api_controller.ex | 35 -------- .../web/mastodon_api/views/instance_view.ex | 35 ++++++++ lib/pleroma/web/router.ex | 7 +- .../controllers/instance_controller_test.exs | 84 +++++++++++++++++++ .../mastodon_api_controller_test.exs | 75 ----------------- 6 files changed, 140 insertions(+), 113 deletions(-) create mode 100644 lib/pleroma/web/mastodon_api/controllers/instance_controller.ex create mode 100644 lib/pleroma/web/mastodon_api/views/instance_view.ex create mode 100644 test/web/mastodon_api/controllers/instance_controller_test.exs diff --git a/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex b/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex new file mode 100644 index 000000000..a55f60fec --- /dev/null +++ b/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex @@ -0,0 +1,17 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.InstanceController do + use Pleroma.Web, :controller + + @doc "GET /api/v1/instance" + def show(conn, _params) do + render(conn, "show.json") + end + + @doc "GET /api/v1/instance/peers" + def peers(conn, _params) do + json(conn, Pleroma.Stats.get_peers()) + end +end 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 81a95bc4a..98dd9f375 100644 --- a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex @@ -10,7 +10,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do alias Pleroma.Bookmark alias Pleroma.Config alias Pleroma.Pagination - alias Pleroma.Stats alias Pleroma.User alias Pleroma.Web alias Pleroma.Web.ActivityPub.ActivityPub @@ -23,40 +22,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do action_fallback(Pleroma.Web.MastodonAPI.FallbackController) - @mastodon_api_level "2.7.2" - - def masto_instance(conn, _params) do - instance = Config.get(:instance) - - response = %{ - uri: Web.base_url(), - title: Keyword.get(instance, :name), - description: Keyword.get(instance, :description), - version: "#{@mastodon_api_level} (compatible; #{Pleroma.Application.named_version()})", - email: Keyword.get(instance, :email), - urls: %{ - streaming_api: Pleroma.Web.Endpoint.websocket_url() - }, - stats: Stats.get_stats(), - thumbnail: Web.base_url() <> "/instance/thumbnail.jpeg", - languages: ["en"], - registrations: Pleroma.Config.get([:instance, :registrations_open]), - # Extra (not present in Mastodon): - max_toot_chars: Keyword.get(instance, :limit), - poll_limits: Keyword.get(instance, :poll_limits), - upload_limit: Keyword.get(instance, :upload_limit), - avatar_upload_limit: Keyword.get(instance, :avatar_upload_limit), - background_upload_limit: Keyword.get(instance, :background_upload_limit), - banner_upload_limit: Keyword.get(instance, :banner_upload_limit) - } - - json(conn, response) - end - - def peers(conn, _params) do - json(conn, Stats.get_peers()) - end - defp mastodonized_emoji do Pleroma.Emoji.get_all() |> Enum.map(fn {shortcode, %Pleroma.Emoji{file: relative_url, tags: tags}} -> diff --git a/lib/pleroma/web/mastodon_api/views/instance_view.ex b/lib/pleroma/web/mastodon_api/views/instance_view.ex new file mode 100644 index 000000000..c4866e510 --- /dev/null +++ b/lib/pleroma/web/mastodon_api/views/instance_view.ex @@ -0,0 +1,35 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.InstanceView do + use Pleroma.Web, :view + + @mastodon_api_level "2.7.2" + + def render("show.json", _) do + instance = Pleroma.Config.get(:instance) + + %{ + uri: Pleroma.Web.base_url(), + title: Keyword.get(instance, :name), + description: Keyword.get(instance, :description), + version: "#{@mastodon_api_level} (compatible; #{Pleroma.Application.named_version()})", + email: Keyword.get(instance, :email), + urls: %{ + streaming_api: Pleroma.Web.Endpoint.websocket_url() + }, + stats: Pleroma.Stats.get_stats(), + thumbnail: Pleroma.Web.base_url() <> "/instance/thumbnail.jpeg", + languages: ["en"], + registrations: Keyword.get(instance, :registrations_open), + # Extra (not present in Mastodon): + max_toot_chars: Keyword.get(instance, :limit), + poll_limits: Keyword.get(instance, :poll_limits), + upload_limit: Keyword.get(instance, :upload_limit), + avatar_upload_limit: Keyword.get(instance, :avatar_upload_limit), + background_upload_limit: Keyword.get(instance, :background_upload_limit), + banner_upload_limit: Keyword.get(instance, :banner_upload_limit) + } + end +end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 501978994..a355a14bd 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -462,14 +462,15 @@ defmodule Pleroma.Web.Router do post("/accounts", AccountController, :create) - get("/instance", MastodonAPIController, :masto_instance) - get("/instance/peers", MastodonAPIController, :peers) + get("/instance", InstanceController, :show) + get("/instance/peers", InstanceController, :peers) + post("/apps", AppController, :create) get("/apps/verify_credentials", AppController, :verify_credentials) + get("/custom_emojis", MastodonAPIController, :custom_emojis) get("/statuses/:id/card", StatusController, :card) - get("/statuses/:id/favourited_by", StatusController, :favourited_by) get("/statuses/:id/reblogged_by", StatusController, :reblogged_by) diff --git a/test/web/mastodon_api/controllers/instance_controller_test.exs b/test/web/mastodon_api/controllers/instance_controller_test.exs new file mode 100644 index 000000000..f8049f81f --- /dev/null +++ b/test/web/mastodon_api/controllers/instance_controller_test.exs @@ -0,0 +1,84 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.InstanceControllerTest do + use Pleroma.Web.ConnCase + + alias Pleroma.User + import Pleroma.Factory + + test "get instance information", %{conn: conn} do + conn = get(conn, "/api/v1/instance") + assert result = json_response(conn, 200) + + email = Pleroma.Config.get([:instance, :email]) + # Note: not checking for "max_toot_chars" since it's optional + assert %{ + "uri" => _, + "title" => _, + "description" => _, + "version" => _, + "email" => from_config_email, + "urls" => %{ + "streaming_api" => _ + }, + "stats" => _, + "thumbnail" => _, + "languages" => _, + "registrations" => _, + "poll_limits" => _, + "upload_limit" => _, + "avatar_upload_limit" => _, + "background_upload_limit" => _, + "banner_upload_limit" => _ + } = result + + assert email == from_config_email + end + + test "get instance stats", %{conn: conn} do + user = insert(:user, %{local: true}) + + user2 = insert(:user, %{local: true}) + {:ok, _user2} = User.deactivate(user2, !user2.info.deactivated) + + insert(:user, %{local: false, nickname: "u@peer1.com"}) + insert(:user, %{local: false, nickname: "u@peer2.com"}) + + {:ok, _} = Pleroma.Web.CommonAPI.post(user, %{"status" => "cofe"}) + + # Stats should count users with missing or nil `info.deactivated` value + + {:ok, _user} = + user.id + |> User.get_cached_by_id() + |> User.update_info(&Ecto.Changeset.change(&1, %{deactivated: nil})) + + Pleroma.Stats.force_update() + + conn = get(conn, "/api/v1/instance") + + assert result = json_response(conn, 200) + + stats = result["stats"] + + assert stats + assert stats["user_count"] == 1 + assert stats["status_count"] == 1 + assert stats["domain_count"] == 2 + end + + test "get peers", %{conn: conn} do + insert(:user, %{local: false, nickname: "u@peer1.com"}) + insert(:user, %{local: false, nickname: "u@peer2.com"}) + + Pleroma.Stats.force_update() + + conn = get(conn, "/api/v1/instance/peers") + + assert result = json_response(conn, 200) + + assert ["peer1.com", "peer2.com"] == Enum.sort(result) + end +end diff --git a/test/web/mastodon_api/mastodon_api_controller_test.exs b/test/web/mastodon_api/mastodon_api_controller_test.exs index e642e3c1a..7a58b13dc 100644 --- a/test/web/mastodon_api/mastodon_api_controller_test.exs +++ b/test/web/mastodon_api/mastodon_api_controller_test.exs @@ -5,7 +5,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do use Pleroma.Web.ConnCase - alias Ecto.Changeset alias Pleroma.Config alias Pleroma.Notification alias Pleroma.Repo @@ -114,80 +113,6 @@ test "returns the favorites of a user", %{conn: conn} do assert [] = json_response(third_conn, 200) end - test "get instance information", %{conn: conn} do - conn = get(conn, "/api/v1/instance") - assert result = json_response(conn, 200) - - email = Config.get([:instance, :email]) - # Note: not checking for "max_toot_chars" since it's optional - assert %{ - "uri" => _, - "title" => _, - "description" => _, - "version" => _, - "email" => from_config_email, - "urls" => %{ - "streaming_api" => _ - }, - "stats" => _, - "thumbnail" => _, - "languages" => _, - "registrations" => _, - "poll_limits" => _, - "upload_limit" => _, - "avatar_upload_limit" => _, - "background_upload_limit" => _, - "banner_upload_limit" => _ - } = result - - assert email == from_config_email - end - - test "get instance stats", %{conn: conn} do - user = insert(:user, %{local: true}) - - user2 = insert(:user, %{local: true}) - {:ok, _user2} = User.deactivate(user2, !user2.info.deactivated) - - insert(:user, %{local: false, nickname: "u@peer1.com"}) - insert(:user, %{local: false, nickname: "u@peer2.com"}) - - {:ok, _} = CommonAPI.post(user, %{"status" => "cofe"}) - - # Stats should count users with missing or nil `info.deactivated` value - - {:ok, _user} = - user.id - |> User.get_cached_by_id() - |> User.update_info(&Changeset.change(&1, %{deactivated: nil})) - - Pleroma.Stats.force_update() - - conn = get(conn, "/api/v1/instance") - - assert result = json_response(conn, 200) - - stats = result["stats"] - - assert stats - assert stats["user_count"] == 1 - assert stats["status_count"] == 1 - assert stats["domain_count"] == 2 - end - - test "get peers", %{conn: conn} do - insert(:user, %{local: false, nickname: "u@peer1.com"}) - insert(:user, %{local: false, nickname: "u@peer2.com"}) - - Pleroma.Stats.force_update() - - conn = get(conn, "/api/v1/instance/peers") - - assert result = json_response(conn, 200) - - assert ["peer1.com", "peer2.com"] == Enum.sort(result) - end - test "put settings", %{conn: conn} do user = insert(:user) From 3d61efa7c99e1ee9626bf77b3866cfc1562c663b Mon Sep 17 00:00:00 2001 From: Thibaut Girka Date: Wed, 2 Oct 2019 10:48:34 +0200 Subject: [PATCH 30/31] Rename misleading `get_announce_visibility` to `public_announce?` --- lib/pleroma/web/common_api/common_api.ex | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex index 677a53ddf..ce73b3270 100644 --- a/lib/pleroma/web/common_api/common_api.ex +++ b/lib/pleroma/web/common_api/common_api.ex @@ -80,7 +80,7 @@ def repeat(id_or_ap_id, user, params \\ %{}) do with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id), object <- Object.normalize(activity), nil <- Utils.get_existing_announce(user.ap_id, object), - public <- get_announce_visibility(object, params) do + public <- public_announce?(object, params) do ActivityPub.announce(user, object, nil, true, public) else _ -> {:error, dgettext("errors", "Could not repeat")} @@ -170,11 +170,11 @@ defp normalize_and_validate_choices(choices, object) do end end - def get_announce_visibility(_, %{"visibility" => visibility}) + def public_announce?(_, %{"visibility" => visibility}) when visibility in ~w{public unlisted private direct}, do: visibility in ~w(public unlisted) - def get_announce_visibility(object, _) do + def public_announce?(object, _) do Visibility.is_public?(object) end From 86880b9821671ee07a65ca7e65b68b900759b483 Mon Sep 17 00:00:00 2001 From: Thibaut Girka Date: Wed, 2 Oct 2019 12:14:08 +0200 Subject: [PATCH 31/31] Inline object when Announcing a self-owned private object --- .../web/activity_pub/transmogrifier.ex | 21 +++++++++++++++++++ test/web/activity_pub/transmogrifier_test.exs | 14 +++++++++++++ 2 files changed, 35 insertions(+) diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 3ca2e8773..64c470fc8 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -830,6 +830,27 @@ def prepare_outgoing(%{"type" => activity_type, "object" => object_id} = data) {:ok, data} end + def prepare_outgoing(%{"type" => "Announce", "actor" => ap_id, "object" => object_id} = data) do + object = + object_id + |> Object.normalize() + + data = + if Visibility.is_private?(object) && object.data["actor"] == ap_id do + data |> Map.put("object", object |> Map.get(:data) |> prepare_object) + else + data |> maybe_fix_object_url + end + + data = + data + |> strip_internal_fields + |> Map.merge(Utils.make_json_ld_header()) + |> Map.delete("bcc") + + {:ok, data} + end + # Mastodon Accept/Reject requires a non-normalized object containing the actor URIs, # because of course it does. def prepare_outgoing(%{"type" => "Accept"} = data) do diff --git a/test/web/activity_pub/transmogrifier_test.exs b/test/web/activity_pub/transmogrifier_test.exs index 6c64be10b..b995f0224 100644 --- a/test/web/activity_pub/transmogrifier_test.exs +++ b/test/web/activity_pub/transmogrifier_test.exs @@ -1076,6 +1076,20 @@ test "it accepts Flag activities" do end describe "prepare outgoing" do + test "it inlines private announced objects" do + user = insert(:user) + + {:ok, activity} = CommonAPI.post(user, %{"status" => "hey", "visibility" => "private"}) + + {:ok, announce_activity, _} = CommonAPI.repeat(activity.id, user) + + {:ok, modified} = Transmogrifier.prepare_outgoing(announce_activity.data) + object = modified["object"] + + assert modified["object"]["content"] == "hey" + assert modified["object"]["actor"] == modified["object"]["attributedTo"] + end + test "it turns mentions into tags" do user = insert(:user) other_user = insert(:user)