From 6d814938614916d369194df437618fa8629421be Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sat, 25 Dec 2021 01:41:12 +0000 Subject: [PATCH] Merge branch 'account-notes' into 'develop' MastoAPI: Add user notes on accounts See merge request pleroma/pleroma!3540 --- docs/development/API/pleroma_api.md | 6 ++- lib/pleroma/user_note.ex | 52 +++++++++++++++++++ .../api_spec/operations/account_operation.ex | 43 +++++++++++++++ lib/pleroma/web/api_spec/schemas/account.ex | 1 + .../api_spec/schemas/account_relationship.ex | 2 + lib/pleroma/web/api_spec/schemas/status.ex | 1 + .../controllers/account_controller.ex | 19 ++++++- .../web/mastodon_api/views/account_view.ex | 8 ++- lib/pleroma/web/router.ex | 1 + .../20211121000000_create_user_notes.exs | 15 ++++++ .../controllers/account_controller_test.exs | 17 ++++++ .../mastodon_api/views/account_view_test.exs | 3 +- 12 files changed, 162 insertions(+), 6 deletions(-) create mode 100644 lib/pleroma/user_note.ex create mode 100644 priv/repo/migrations/20211121000000_create_user_notes.exs diff --git a/docs/development/API/pleroma_api.md b/docs/development/API/pleroma_api.md index 74a1ad206..0e7367a72 100644 --- a/docs/development/API/pleroma_api.md +++ b/docs/development/API/pleroma_api.md @@ -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 index 000000000..5e82d359f --- /dev/null +++ b/lib/pleroma/user_note.ex @@ -0,0 +1,52 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2021 Pleroma Authors +# 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 diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex index 4fe5a3c03..8cd2e824d 100644 --- a/lib/pleroma/web/api_spec/operations/account_operation.ex +++ b/lib/pleroma/web/api_spec/operations/account_operation.ex @@ -334,6 +334,29 @@ def unblock_operation 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 @@ defp array_of_relationships do "blocked_by" => true, "muting" => false, "muting_notifications" => false, + "note" => "", "requested" => false, "domain_blocking" => false, "subscribing" => false, @@ -706,6 +730,7 @@ defp array_of_relationships do "blocked_by" => true, "muting" => true, "muting_notifications" => false, + "note" => "", "requested" => true, "domain_blocking" => false, "subscribing" => false, @@ -721,6 +746,7 @@ defp array_of_relationships do "blocked_by" => false, "muting" => true, "muting_notifications" => false, + "note" => "", "requested" => false, "domain_blocking" => true, "subscribing" => true, @@ -769,6 +795,23 @@ defp mute_request 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", diff --git a/lib/pleroma/web/api_spec/schemas/account.ex b/lib/pleroma/web/api_spec/schemas/account.ex index ad1a85544..548e70544 100644 --- a/lib/pleroma/web/api_spec/schemas/account.ex +++ b/lib/pleroma/web/api_spec/schemas/account.ex @@ -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, diff --git a/lib/pleroma/web/api_spec/schemas/account_relationship.ex b/lib/pleroma/web/api_spec/schemas/account_relationship.ex index b4f6d25b0..5d9e3b56e 100644 --- a/lib/pleroma/web/api_spec/schemas/account_relationship.ex +++ b/lib/pleroma/web/api_spec/schemas/account_relationship.ex @@ -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, diff --git a/lib/pleroma/web/api_spec/schemas/status.ex b/lib/pleroma/web/api_spec/schemas/status.ex index 0bf3312d1..3caab0f00 100644 --- a/lib/pleroma/web/api_spec/schemas/status.ex +++ b/lib/pleroma/web/api_spec/schemas/status.ex @@ -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, diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex index 5fcbffc34..5dfbecf5a 100644 --- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex @@ -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 @@ def unblock(%{assigns: %{user: blocker, account: blocked}} = conn, _params) 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 diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex index 25752cf56..1420edbd9 100644 --- a/lib/pleroma/web/mastodon_api/views/account_view.ex +++ b/lib/pleroma/web/mastodon_api/views/account_view.ex @@ -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 @@ def render( target, &User.muting_reblogs?(&1, &2) ), - endorsed: false + endorsed: false, + note: + UserNote.show( + reading_user, + target + ) } end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index dae113617..a02b495e5 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -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 index 000000000..b75e11695 --- /dev/null +++ b/priv/repo/migrations/20211121000000_create_user_notes.exs @@ -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 diff --git a/test/pleroma/web/mastodon_api/controllers/account_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/account_controller_test.exs index 581944b8a..966a4072d 100644 --- a/test/pleroma/web/mastodon_api/controllers/account_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/account_controller_test.exs @@ -1797,4 +1797,21 @@ test "getting a list of blocks" 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 diff --git a/test/pleroma/web/mastodon_api/views/account_view_test.exs b/test/pleroma/web/mastodon_api/views/account_view_test.exs index aeddd6b4c..9bf584bb0 100644 --- a/test/pleroma/web/mastodon_api/views/account_view_test.exs +++ b/test/pleroma/web/mastodon_api/views/account_view_test.exs @@ -272,7 +272,8 @@ defp test_relationship_rendering(user, other_user, expected_result) 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