MastoAPI: Add user notes on accounts
authormarcin mikołajczak <git@mkljczk.pl>
Sun, 21 Nov 2021 15:53:30 +0000 (16:53 +0100)
committermarcin mikołajczak <git@mkljczk.pl>
Sun, 21 Nov 2021 15:56:26 +0000 (16:56 +0100)
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
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 8f6422da04bef9d546b3e5f15bb1064b9f62368b..b401a7cc74b0ff3608c1d8596e6c4cd1d0542da5 100644 (file)
@@ -162,7 +162,8 @@ See [Admin-API](admin_api.md)
   "requested": false,
   "domain_blocking": false,
   "showing_reblogs": true,
-  "endorsed": false
+  "endorsed": false,
+  "note": ""
 }
 ```
 
@@ -186,7 +187,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 54e5ebc76e2ae943e02b3026d85799f7cf511641..6bec9f1783dbc342ef50818885983d6b4e247818 100644 (file)
@@ -328,6 +328,29 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
     }
   end
 
+  def note_operation do
+    %Operation{
+      tags: ["Account actions"],
+      summary: "Create note",
+      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"],
@@ -685,6 +708,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
           "blocked_by" => true,
           "muting" => false,
           "muting_notifications" => false,
+          "note" => "",
           "requested" => false,
           "domain_blocking" => false,
           "subscribing" => false,
@@ -699,6 +723,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
           "blocked_by" => true,
           "muting" => true,
           "muting_notifications" => false,
+          "note" => "",
           "requested" => true,
           "domain_blocking" => false,
           "subscribing" => false,
@@ -713,6 +738,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
           "blocked_by" => false,
           "muting" => true,
           "muting_notifications" => false,
+          "note" => "",
           "requested" => false,
           "domain_blocking" => true,
           "subscribing" => true,
@@ -760,6 +786,23 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
     }
   end
 
+  defp note_request do
+    %Schema{
+      title: "AccountNoteRequest",
+      description: "POST body for adding anote 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 bd7143ab959420bb00e9b2ab2264cf3777f27fa1..e0bd2728b8731864696aca6cec008a2cea885468 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 16b73ebb4191e02d32be50bcbc8e34b7ba13a6ee..163066032a9154d690b977dfe84ce06cfac48433 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}
@@ -36,6 +37,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.AccountRelationship do
       "id" => "9tKi3esbG7OQgZ2920",
       "muting" => false,
       "muting_notifications" => false,
+      "note" => "",
       "requested" => false,
       "showing_reblogs" => true,
       "subscribing" => false
index 3d042dc19a225f2d4e268a97695241f9a1c2840d..60801f3227e1f103b1441b61d8d695b115eb5bac 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..8a43d49d3380d51510d42f9065e428b07eb7c4eb 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,15 @@ 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)
+    else
+      {:error, message} -> json_response(conn, :forbidden, %{error: message})
+    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 9e9de33f6167c615158440da34d0f6b3dd877a98..a3a9f95487a00e56e6087dd05df91528ace7513c 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
@@ -156,7 +157,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 abb332ec2f1f9eed9b232f83fa34f50b0fa7b654..ca5db8ea359bf1a7433abea01c2e320540d1286c 100644 (file)
@@ -456,6 +456,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..8fc2374
--- /dev/null
@@ -0,0 +1,17 @@
+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 a92a582247ddf252eb83e6c61c9722107f6c870d..48e658dd2f4889317fac246b17bd00215d4dd4dc 100644 (file)
@@ -1776,4 +1776,18 @@ 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"])
+    other_user = insert(:user)
+
+    ret_conn =
+      conn
+      |> put_req_header("content-type", "application/json")
+      |> post("/api/v1/accounts/#{other_user.id}/note", %{
+        "comment" => "Example note"
+      })
+
+    assert %{"note" => "Example note"} = json_response_and_validate_schema(ret_conn, 200)
+  end
 end
index 60881756d9b402a3d624f44b17d6239c66d10994..9fe9d73bc7556e42b2b2c58865f76fa383e4f978 100644 (file)
@@ -271,7 +271,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