Merge remote-tracking branch 'pleroma/develop' into mastodon-lookup
authormarcin mikołajczak <git@mkljczk.pl>
Tue, 28 Dec 2021 15:27:27 +0000 (16:27 +0100)
committermarcin mikołajczak <git@mkljczk.pl>
Tue, 28 Dec 2021 15:27:27 +0000 (16:27 +0100)
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
1  2 
lib/pleroma/web/api_spec/operations/account_operation.ex
lib/pleroma/web/mastodon_api/controllers/account_controller.ex
lib/pleroma/web/router.ex
test/pleroma/web/mastodon_api/controllers/account_controller_test.exs

index 5836cab50dcaca2b44759357699a7316094ff8ce,8cd2e824d1e47d07eb5c1c24cefc7a869c043cc3..f5304d7d6fd4d79cdcadbc51b0df680618daad46
@@@ -226,6 -226,12 +226,12 @@@ defmodule Pleroma.Web.ApiSpec.AccountOp
                  type: :boolean,
                  description: "Receive this account's reblogs in home timeline? Defaults to true.",
                  default: true
+               },
+               notify: %Schema{
+                 type: :boolean,
+                 description:
+                   "Receive notifications for all statuses posted by the account? Defaults to false.",
+                 default: false
                }
              }
            },
      }
    end
  
+   def note_operation do
+     %Operation{
+       tags: ["Account actions"],
+       summary: "Set a private note about a user.",
+       operationId: "AccountController.note",
+       security: [%{"oAuth" => ["follow", "write:accounts"]}],
+       requestBody: request_body("Parameters", note_request()),
+       description: "Create a note for the given account.",
+       parameters: [
+         %Reference{"$ref": "#/components/parameters/accountIdOrNickname"},
+         Operation.parameter(
+           :comment,
+           :query,
+           %Schema{type: :string},
+           "Account note body"
+         )
+       ],
+       responses: %{
+         200 => Operation.response("Relationship", "application/json", AccountRelationship)
+       }
+     }
+   end
    def follow_by_uri_operation do
      %Operation{
        tags: ["Account actions"],
      }
    end
  
 +  def lookup_operation do
 +    %Operation{
 +      tags: ["Account lookup"],
 +      summary: "Find a user by nickname",
 +      operationId: "AccountController.lookup",
 +      parameters: [
 +        Operation.parameter(
 +          :acct,
 +          :query,
 +          :string,
 +          "User nickname"
 +        )
 +      ],
 +      responses: %{
 +        200 => Operation.response("Account", "application/json", Account),
 +        404 => Operation.response("Error", "application/json", ApiError)
 +      }
 +    }
 +  end
 +
    def endorsements_operation do
      %Operation{
        tags: ["Retrieve account information"],
            "blocked_by" => true,
            "muting" => false,
            "muting_notifications" => false,
+           "note" => "",
            "requested" => false,
            "domain_blocking" => false,
            "subscribing" => false,
+           "notifying" => false,
            "endorsed" => true
          },
          %{
            "blocked_by" => true,
            "muting" => true,
            "muting_notifications" => false,
+           "note" => "",
            "requested" => true,
            "domain_blocking" => false,
            "subscribing" => false,
+           "notifying" => false,
            "endorsed" => false
          },
          %{
            "blocked_by" => false,
            "muting" => true,
            "muting_notifications" => false,
+           "note" => "",
            "requested" => false,
            "domain_blocking" => true,
            "subscribing" => true,
+           "notifying" => true,
            "endorsed" => false
          }
        ]
      }
    end
  
+   defp note_request do
+     %Schema{
+       title: "AccountNoteRequest",
+       description: "POST body for adding a note for an account",
+       type: :object,
+       properties: %{
+         comment: %Schema{
+           type: :string,
+           description: "Account note body"
+         }
+       },
+       example: %{
+         "comment" => "Example note"
+       }
+     }
+   end
    defp array_of_lists do
      %Schema{
        title: "ArrayOfLists",
index 3eae0a64618728d261ed04ce3fc707d14deb9830,5dfbecf5a3835b51b9d2eb254fd6456c377a7d62..399a34217e7a1c37278cd0bc6d18e7bd217d3287
@@@ -15,6 -15,7 +15,7 @@@ defmodule Pleroma.Web.MastodonAPI.Accou
  
    alias Pleroma.Maps
    alias Pleroma.User
+   alias Pleroma.UserNote
    alias Pleroma.Web.ActivityPub.ActivityPub
    alias Pleroma.Web.ActivityPub.Builder
    alias Pleroma.Web.ActivityPub.Pipeline
      when action in [:verify_credentials, :endorsements, :identity_proofs]
    )
  
-   plug(OAuthScopesPlug, %{scopes: ["write:accounts"]} when action == :update_credentials)
+   plug(
+     OAuthScopesPlug,
+     %{scopes: ["write:accounts"]}
+     when action in [:update_credentials, :note]
+   )
  
    plug(OAuthScopesPlug, %{scopes: ["read:lists"]} when action == :lists)
  
@@@ -79,7 -84,7 +84,7 @@@
    plug(OAuthScopesPlug, %{scopes: ["follow", "write:mutes"]} when action in [:mute, :unmute])
  
    @relationship_actions [:follow, :unfollow]
-   @needs_account ~W(followers following lists follow unfollow mute unmute block unblock)a
+   @needs_account ~W(followers following lists follow unfollow mute unmute block unblock note)a
  
    plug(
      RateLimiter,
      end
    end
  
+   @doc "POST /api/v1/accounts/:id/note"
+   def note(
+         %{assigns: %{user: noter, account: target}, body_params: %{comment: comment}} = conn,
+         _params
+       ) do
+     with {:ok, _user_note} <- UserNote.create(noter, target, comment) do
+       render(conn, "relationship.json", user: noter, target: target)
+     end
+   end
    @doc "POST /api/v1/follows"
    def follow_by_uri(%{body_params: %{uri: uri}} = conn, _) do
      case User.get_cached_by_nickname(uri) do
      |> render("index.json", users: users, for: user, as: :user)
    end
  
 +  @doc "GET /api/v1/accounts/lookup"
 +  def lookup(%{assigns: %{user: for_user}} = conn, %{acct: nickname} = _params) do
 +    with %User{} = user <- User.get_by_nickname(nickname) do
 +      render(conn, "show.json",
 +        user: user,
 +        for: for_user
 +      )
 +    else
 +      error -> user_visibility_error(conn, error)
 +    end
 +  end
 +
    @doc "GET /api/v1/endorsements"
    def endorsements(conn, params), do: MastodonAPIController.empty_array(conn, params)
  
index ae373e58c7d4502b9ce7f72564a4f1daaf9dd14c,e439a279d0ceb22e011d4fcc9d430bfc836b7f3e..b9b52b1e54d75577f3008f84b86ebba81fc0a53e
@@@ -101,6 -101,10 +101,10 @@@ defmodule Pleroma.Web.Router d
      plug(Pleroma.Web.Plugs.IdempotencyPlug)
    end
  
+   pipeline :require_privileged_staff do
+     plug(Pleroma.Web.Plugs.EnsureStaffPrivilegedPlug)
+   end
    pipeline :require_admin do
      plug(Pleroma.Web.Plugs.UserIsAdminPlug)
    end
      get("/emoji", UtilController, :emoji)
      get("/captcha", UtilController, :captcha)
      get("/healthcheck", UtilController, :healthcheck)
+     post("/remote_interaction", UtilController, :remote_interaction)
    end
  
    scope "/api/v1/pleroma", Pleroma.Web do
      post("/relay", RelayController, :follow)
      delete("/relay", RelayController, :unfollow)
  
-     get("/users/:nickname/password_reset", AdminAPIController, :get_password_reset)
      patch("/users/force_password_reset", AdminAPIController, :force_password_reset)
      get("/users/:nickname/credentials", AdminAPIController, :show_user_credentials)
      patch("/users/:nickname/credentials", AdminAPIController, :update_user_credentials)
      post("/backups", AdminAPIController, :create_backup)
    end
  
+   # AdminAPI: admins and mods (staff) can perform these actions (if enabled by config)
+   scope "/api/v1/pleroma/admin", Pleroma.Web.AdminAPI do
+     pipe_through([:admin_api, :require_privileged_staff])
+     delete("/users", UserController, :delete)
+     get("/users/:nickname/password_reset", AdminAPIController, :get_password_reset)
+     patch("/users/:nickname/credentials", AdminAPIController, :update_user_credentials)
+     get("/users/:nickname/statuses", AdminAPIController, :list_user_statuses)
+     get("/users/:nickname/chats", AdminAPIController, :list_user_chats)
+     get("/statuses", StatusController, :index)
+     get("/chats/:id", ChatController, :show)
+     get("/chats/:id/messages", ChatController, :messages)
+   end
    # AdminAPI: admins and mods (staff) can perform these actions
    scope "/api/v1/pleroma/admin", Pleroma.Web.AdminAPI do
      pipe_through(:admin_api)
      patch("/users/deactivate", UserController, :deactivate)
      patch("/users/approve", UserController, :approve)
  
-     delete("/users", UserController, :delete)
      post("/users/invite_token", InviteController, :create)
      get("/users/invites", InviteController, :index)
      post("/users/revoke_invite", InviteController, :revoke)
      post("/users/email_invite", InviteController, :email)
  
-     get("/users/:nickname/password_reset", AdminAPIController, :get_password_reset)
-     patch("/users/force_password_reset", AdminAPIController, :force_password_reset)
-     get("/users/:nickname/credentials", AdminAPIController, :show_user_credentials)
-     patch("/users/:nickname/credentials", AdminAPIController, :update_user_credentials)
      get("/users", UserController, :index)
      get("/users/:nickname", UserController, :show)
-     get("/users/:nickname/statuses", AdminAPIController, :list_user_statuses)
-     get("/users/:nickname/chats", AdminAPIController, :list_user_chats)
  
      get("/instances/:instance/statuses", InstanceController, :list_statuses)
      delete("/instances/:instance", InstanceController, :delete)
      get("/statuses/:id", StatusController, :show)
      put("/statuses/:id", StatusController, :update)
      delete("/statuses/:id", StatusController, :delete)
-     get("/statuses", StatusController, :index)
  
      get("/moderation_log", AdminAPIController, :list_log)
  
      post("/reload_emoji", AdminAPIController, :reload_emoji)
      get("/stats", AdminAPIController, :stats)
  
-     get("/chats/:id", ChatController, :show)
-     get("/chats/:id/messages", ChatController, :messages)
      delete("/chats/:id/messages/:message_id", ChatController, :delete_message)
    end
  
    scope "/api/v1/pleroma", Pleroma.Web.PleromaAPI do
      pipe_through(:api)
  
+     get("/apps", AppController, :index)
      get("/statuses/:id/reactions/:emoji", EmojiReactionController, :index)
      get("/statuses/:id/reactions", EmojiReactionController, :index)
    end
      post("/accounts/:id/unblock", AccountController, :unblock)
      post("/accounts/:id/mute", AccountController, :mute)
      post("/accounts/:id/unmute", AccountController, :unmute)
+     post("/accounts/:id/note", AccountController, :note)
  
      get("/conversations", ConversationController, :index)
      post("/conversations/:id/read", ConversationController, :mark_as_read)
      get("/accounts/search", SearchController, :account_search)
      get("/search", SearchController, :search)
  
 +    get("/accounts/lookup", AccountController, :lookup)
 +
      get("/accounts/:id/statuses", AccountController, :statuses)
      get("/accounts/:id/followers", AccountController, :followers)
      get("/accounts/:id/following", AccountController, :following)
      get("/timelines/tag/:tag", TimelineController, :hashtag)
  
      get("/polls/:id", PollController, :show)
+     get("/directory", DirectoryController, :index)
    end
  
    scope "/api/v2", Pleroma.Web.MastodonAPI do
      get("/activities/:uuid", OStatus.OStatusController, :activity)
      get("/notice/:id", OStatus.OStatusController, :notice)
  
+     # Notice compatibility routes for other frontends
+     get("/@:nickname/:id", OStatus.OStatusController, :notice)
+     get("/@:nickname/posts/:id", OStatus.OStatusController, :notice)
+     get("/:nickname/status/:id", OStatus.OStatusController, :notice)
      # Mastodon compatibility routes
      get("/users/:nickname/statuses/:id", OStatus.OStatusController, :object)
      get("/users/:nickname/statuses/:id/activity", OStatus.OStatusController, :activity)
index 86349619e0fda677001388aeae8c0c33045ea0fe,966a4072d4665c92c42bd8a5247a62b49d05bb17..374e2048a732114a60c521bcd1a2073d24201a83
@@@ -922,6 -922,27 +922,27 @@@ defmodule Pleroma.Web.MastodonAPI.Accou
                 |> json_response_and_validate_schema(200)
      end
  
+     test "following with subscription and unsubscribing" do
+       %{conn: conn} = oauth_access(["follow"])
+       followed = insert(:user)
+       ret_conn =
+         conn
+         |> put_req_header("content-type", "application/json")
+         |> post("/api/v1/accounts/#{followed.id}/follow", %{notify: true})
+       assert %{"id" => _id, "subscribing" => true} =
+                json_response_and_validate_schema(ret_conn, 200)
+       ret_conn =
+         conn
+         |> put_req_header("content-type", "application/json")
+         |> post("/api/v1/accounts/#{followed.id}/follow", %{notify: false})
+       assert %{"id" => _id, "subscribing" => false} =
+                json_response_and_validate_schema(ret_conn, 200)
+     end
      test "following / unfollowing errors", %{user: user, conn: conn} do
        # self follow
        conn_res = post(conn, "/api/v1/accounts/#{user.id}/follow")
      assert [%{"id" => ^id2}] = result
    end
  
 +  test "account lookup", %{conn: conn} do
 +    %{nickname: acct} = insert(:user, %{nickname: "nickname"})
 +    %{nickname: acct_two} = insert(:user, %{nickname: "nickname@notlocaldoma.in"})
 +
 +    result =
 +      conn
 +      |> get("/api/v1/accounts/lookup?acct=#{acct}")
 +      |> json_response_and_validate_schema(200)
 +
 +    assert %{"acct" => ^acct} = result
 +
 +    result =
 +      conn
 +      |> get("/api/v1/accounts/lookup?acct=#{acct_two}")
 +      |> json_response_and_validate_schema(200)
 +
 +    assert %{"acct" => ^acct_two} = result
 +
 +    _result =
 +      conn
 +      |> get("/api/v1/accounts/lookup?acct=unexisting_nickname")
 +      |> json_response_and_validate_schema(404)
 +  end
++
+   test "create a note on a user" do
+     %{conn: conn} = oauth_access(["write:accounts", "read:follows"])
+     other_user = insert(:user)
+     conn
+     |> put_req_header("content-type", "application/json")
+     |> post("/api/v1/accounts/#{other_user.id}/note", %{
+       "comment" => "Example note"
+     })
+     assert [%{"note" => "Example note"}] =
+              conn
+              |> put_req_header("content-type", "application/json")
+              |> get("/api/v1/accounts/relationships?id=#{other_user.id}")
+              |> json_response_and_validate_schema(200)
+   end
  end