Merge branch 'account-notes' into 'develop'
authorAlex Gleason <alex@alexgleason.me>
Sat, 25 Dec 2021 01:41:12 +0000 (01:41 +0000)
committerAlex Gleason <alex@alexgleason.me>
Sat, 25 Dec 2021 01:41:12 +0000 (01:41 +0000)
MastoAPI: Add user notes on accounts

See merge request pleroma/pleroma!3540

12 files changed:
docs/development/API/pleroma_api.md
lib/pleroma/user_note.ex [new file with mode: 0644]
lib/pleroma/web/api_spec/operations/account_operation.ex
lib/pleroma/web/api_spec/schemas/account.ex
lib/pleroma/web/api_spec/schemas/account_relationship.ex
lib/pleroma/web/api_spec/schemas/status.ex
lib/pleroma/web/mastodon_api/controllers/account_controller.ex
lib/pleroma/web/mastodon_api/views/account_view.ex
lib/pleroma/web/router.ex
priv/repo/migrations/20211121000000_create_user_notes.exs [new file with mode: 0644]
test/pleroma/web/mastodon_api/controllers/account_controller_test.exs
test/pleroma/web/mastodon_api/views/account_view_test.exs

index 74a1ad206347c1b2c9bffd0fe1cc955beba73047..0e7367a726fb086875295684c1c6fdbeb75acfe3 100644 (file)
@@ -163,7 +163,8 @@ See [Admin-API](admin_api.md)
   "requested": false,
   "domain_blocking": false,
   "showing_reblogs": true,
-  "endorsed": false
+  "endorsed": false,
+  "note": ""
 }
 ```
 
@@ -188,7 +189,8 @@ See [Admin-API](admin_api.md)
   "requested": false,
   "domain_blocking": false,
   "showing_reblogs": true,
-  "endorsed": false
+  "endorsed": false,
+  "note": ""
 }
 ```
 
diff --git a/lib/pleroma/user_note.ex b/lib/pleroma/user_note.ex
new file mode 100644 (file)
index 0000000..5e82d35
--- /dev/null
@@ -0,0 +1,52 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.UserNote do
+  use Ecto.Schema
+
+  import Ecto.Changeset
+  import Ecto.Query
+
+  alias Pleroma.Repo
+  alias Pleroma.User
+  alias Pleroma.UserNote
+
+  schema "user_notes" do
+    belongs_to(:source, User, type: FlakeId.Ecto.CompatType)
+    belongs_to(:target, User, type: FlakeId.Ecto.CompatType)
+    field(:comment, :string)
+
+    timestamps()
+  end
+
+  def changeset(%UserNote{} = user_note, params \\ %{}) do
+    user_note
+    |> cast(params, [:source_id, :target_id, :comment])
+    |> validate_required([:source_id, :target_id])
+  end
+
+  def show(%User{} = source, %User{} = target) do
+    with %UserNote{} = note <-
+           UserNote
+           |> where(source_id: ^source.id, target_id: ^target.id)
+           |> Repo.one() do
+      note.comment
+    else
+      _ -> ""
+    end
+  end
+
+  def create(%User{} = source, %User{} = target, comment) do
+    %UserNote{}
+    |> changeset(%{
+      source_id: source.id,
+      target_id: target.id,
+      comment: comment
+    })
+    |> Repo.insert(
+      on_conflict: {:replace, [:comment]},
+      conflict_target: [:source_id, :target_id]
+    )
+  end
+end
index 4fe5a3c033b94938423c842ddf3399f3dbb0d4a8..8cd2e824d1e47d07eb5c1c24cefc7a869c043cc3 100644 (file)
@@ -334,6 +334,29 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
     }
   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"],
@@ -691,6 +714,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
           "blocked_by" => true,
           "muting" => false,
           "muting_notifications" => false,
+          "note" => "",
           "requested" => false,
           "domain_blocking" => false,
           "subscribing" => false,
@@ -706,6 +730,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
           "blocked_by" => true,
           "muting" => true,
           "muting_notifications" => false,
+          "note" => "",
           "requested" => true,
           "domain_blocking" => false,
           "subscribing" => false,
@@ -721,6 +746,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
           "blocked_by" => false,
           "muting" => true,
           "muting_notifications" => false,
+          "note" => "",
           "requested" => false,
           "domain_blocking" => true,
           "subscribing" => true,
@@ -769,6 +795,23 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
     }
   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 ad1a855441374cd5587757c93dd85372faf40d8b..548e7054412f32a3874ef7bce139828bed0593ee 100644 (file)
@@ -194,6 +194,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
           "id" => "9tKi3esbG7OQgZ2920",
           "muting" => false,
           "muting_notifications" => false,
+          "note" => "",
           "requested" => false,
           "showing_reblogs" => true,
           "subscribing" => false,
index b4f6d25b091de440d132e7d5548f2c8769574a68..5d9e3b56e3310f2893fb784cc2921d9c4aacfdce 100644 (file)
@@ -22,6 +22,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.AccountRelationship do
       id: FlakeID,
       muting: %Schema{type: :boolean},
       muting_notifications: %Schema{type: :boolean},
+      note: %Schema{type: :string},
       requested: %Schema{type: :boolean},
       showing_reblogs: %Schema{type: :boolean},
       subscribing: %Schema{type: :boolean},
@@ -37,6 +38,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.AccountRelationship do
       "id" => "9tKi3esbG7OQgZ2920",
       "muting" => false,
       "muting_notifications" => false,
+      "note" => "",
       "requested" => false,
       "showing_reblogs" => true,
       "subscribing" => false,
index 0bf3312d1c9f6b663c0c5738e5e63bfa84eeb415..3caab0f00dd7f723144e73d89d092569b30a115b 100644 (file)
@@ -282,6 +282,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do
             "id" => "9toJCsKN7SmSf3aj5c",
             "muting" => false,
             "muting_notifications" => false,
+            "note" => "",
             "requested" => false,
             "showing_reblogs" => true,
             "subscribing" => false,
index 5fcbffc34d682d79cb801de8cea1c2e7abb61cf6..5dfbecf5a3835b51b9d2eb254fd6456c377a7d62 100644 (file)
@@ -15,6 +15,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
 
   alias Pleroma.Maps
   alias Pleroma.User
+  alias Pleroma.UserNote
   alias Pleroma.Web.ActivityPub.ActivityPub
   alias Pleroma.Web.ActivityPub.Builder
   alias Pleroma.Web.ActivityPub.Pipeline
@@ -53,7 +54,11 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
     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 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
   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,
@@ -435,6 +440,16 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
     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
index 4290d11aeccd721c432880fb48aade501e36978c..3c8dd03537b5d79860ffec4a0c9e58e733b43700 100644 (file)
@@ -7,6 +7,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
 
   alias Pleroma.FollowingRelationship
   alias Pleroma.User
+  alias Pleroma.UserNote
   alias Pleroma.UserRelationship
   alias Pleroma.Web.CommonAPI.Utils
   alias Pleroma.Web.MastodonAPI.AccountView
@@ -159,7 +160,12 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
           target,
           &User.muting_reblogs?(&1, &2)
         ),
-      endorsed: false
+      endorsed: false,
+      note:
+        UserNote.show(
+          reading_user,
+          target
+        )
     }
   end
 
index 965cd507ff4707d00c2b2383e718d1eb1288ca96..9ce35ad6bd597d68ead6fd7af1bc72b4b139bfbc 100644 (file)
@@ -475,6 +475,7 @@ defmodule Pleroma.Web.Router do
     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)
diff --git a/priv/repo/migrations/20211121000000_create_user_notes.exs b/priv/repo/migrations/20211121000000_create_user_notes.exs
new file mode 100644 (file)
index 0000000..b75e116
--- /dev/null
@@ -0,0 +1,15 @@
+defmodule Pleroma.Repo.Migrations.CreateUserNotes do
+  use Ecto.Migration
+
+  def change do
+    create_if_not_exists table(:user_notes) do
+      add(:source_id, references(:users, type: :uuid, on_delete: :delete_all))
+      add(:target_id, references(:users, type: :uuid, on_delete: :delete_all))
+      add(:comment, :string)
+
+      timestamps()
+    end
+
+    create_if_not_exists(unique_index(:user_notes, [:source_id, :target_id]))
+  end
+end
index 581944b8a1b5aceaff68a2198de31767f90685b6..966a4072d4665c92c42bd8a5247a62b49d05bb17 100644 (file)
@@ -1797,4 +1797,21 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
 
     assert [%{"id" => ^id2}] = result
   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
index a37169bf9d802513562f53b3b2d9ef4ec95ee86e..39b9b0cef5278faaef45f70bd1c08284c3c37766 100644 (file)
@@ -274,7 +274,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
       requested: false,
       domain_blocking: false,
       showing_reblogs: true,
-      endorsed: false
+      endorsed: false,
+      note: ""
     }
 
     test "represent a relationship for the following and followed user" do