Merge branch 'account-notes' into 'develop'

MastoAPI: Add user notes on accounts

See merge request pleroma/pleroma!3540
This commit is contained in:
Alex Gleason 2021-12-25 01:41:12 +00:00
commit 73609211a4
12 changed files with 162 additions and 6 deletions

View File

@ -163,7 +163,8 @@ See [Admin-API](admin_api.md)
"requested": false, "requested": false,
"domain_blocking": false, "domain_blocking": false,
"showing_reblogs": true, "showing_reblogs": true,
"endorsed": false "endorsed": false,
"note": ""
} }
``` ```
@ -188,7 +189,8 @@ See [Admin-API](admin_api.md)
"requested": false, "requested": false,
"domain_blocking": false, "domain_blocking": false,
"showing_reblogs": true, "showing_reblogs": true,
"endorsed": false "endorsed": false,
"note": ""
} }
``` ```

52
lib/pleroma/user_note.ex Normal file
View File

@ -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

View File

@ -334,6 +334,29 @@ def unblock_operation do
} }
end 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 def follow_by_uri_operation do
%Operation{ %Operation{
tags: ["Account actions"], tags: ["Account actions"],
@ -691,6 +714,7 @@ defp array_of_relationships do
"blocked_by" => true, "blocked_by" => true,
"muting" => false, "muting" => false,
"muting_notifications" => false, "muting_notifications" => false,
"note" => "",
"requested" => false, "requested" => false,
"domain_blocking" => false, "domain_blocking" => false,
"subscribing" => false, "subscribing" => false,
@ -706,6 +730,7 @@ defp array_of_relationships do
"blocked_by" => true, "blocked_by" => true,
"muting" => true, "muting" => true,
"muting_notifications" => false, "muting_notifications" => false,
"note" => "",
"requested" => true, "requested" => true,
"domain_blocking" => false, "domain_blocking" => false,
"subscribing" => false, "subscribing" => false,
@ -721,6 +746,7 @@ defp array_of_relationships do
"blocked_by" => false, "blocked_by" => false,
"muting" => true, "muting" => true,
"muting_notifications" => false, "muting_notifications" => false,
"note" => "",
"requested" => false, "requested" => false,
"domain_blocking" => true, "domain_blocking" => true,
"subscribing" => true, "subscribing" => true,
@ -769,6 +795,23 @@ defp mute_request do
} }
end 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 defp array_of_lists do
%Schema{ %Schema{
title: "ArrayOfLists", title: "ArrayOfLists",

View File

@ -194,6 +194,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
"id" => "9tKi3esbG7OQgZ2920", "id" => "9tKi3esbG7OQgZ2920",
"muting" => false, "muting" => false,
"muting_notifications" => false, "muting_notifications" => false,
"note" => "",
"requested" => false, "requested" => false,
"showing_reblogs" => true, "showing_reblogs" => true,
"subscribing" => false, "subscribing" => false,

View File

@ -22,6 +22,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.AccountRelationship do
id: FlakeID, id: FlakeID,
muting: %Schema{type: :boolean}, muting: %Schema{type: :boolean},
muting_notifications: %Schema{type: :boolean}, muting_notifications: %Schema{type: :boolean},
note: %Schema{type: :string},
requested: %Schema{type: :boolean}, requested: %Schema{type: :boolean},
showing_reblogs: %Schema{type: :boolean}, showing_reblogs: %Schema{type: :boolean},
subscribing: %Schema{type: :boolean}, subscribing: %Schema{type: :boolean},
@ -37,6 +38,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.AccountRelationship do
"id" => "9tKi3esbG7OQgZ2920", "id" => "9tKi3esbG7OQgZ2920",
"muting" => false, "muting" => false,
"muting_notifications" => false, "muting_notifications" => false,
"note" => "",
"requested" => false, "requested" => false,
"showing_reblogs" => true, "showing_reblogs" => true,
"subscribing" => false, "subscribing" => false,

View File

@ -282,6 +282,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do
"id" => "9toJCsKN7SmSf3aj5c", "id" => "9toJCsKN7SmSf3aj5c",
"muting" => false, "muting" => false,
"muting_notifications" => false, "muting_notifications" => false,
"note" => "",
"requested" => false, "requested" => false,
"showing_reblogs" => true, "showing_reblogs" => true,
"subscribing" => false, "subscribing" => false,

View File

@ -15,6 +15,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
alias Pleroma.Maps alias Pleroma.Maps
alias Pleroma.User alias Pleroma.User
alias Pleroma.UserNote
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Builder alias Pleroma.Web.ActivityPub.Builder
alias Pleroma.Web.ActivityPub.Pipeline alias Pleroma.Web.ActivityPub.Pipeline
@ -53,7 +54,11 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
when action in [:verify_credentials, :endorsements, :identity_proofs] 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) 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]) plug(OAuthScopesPlug, %{scopes: ["follow", "write:mutes"]} when action in [:mute, :unmute])
@relationship_actions [:follow, :unfollow] @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( plug(
RateLimiter, RateLimiter,
@ -435,6 +440,16 @@ def unblock(%{assigns: %{user: blocker, account: blocked}} = conn, _params) do
end end
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" @doc "POST /api/v1/follows"
def follow_by_uri(%{body_params: %{uri: uri}} = conn, _) do def follow_by_uri(%{body_params: %{uri: uri}} = conn, _) do
case User.get_cached_by_nickname(uri) do case User.get_cached_by_nickname(uri) do

View File

@ -7,6 +7,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
alias Pleroma.FollowingRelationship alias Pleroma.FollowingRelationship
alias Pleroma.User alias Pleroma.User
alias Pleroma.UserNote
alias Pleroma.UserRelationship alias Pleroma.UserRelationship
alias Pleroma.Web.CommonAPI.Utils alias Pleroma.Web.CommonAPI.Utils
alias Pleroma.Web.MastodonAPI.AccountView alias Pleroma.Web.MastodonAPI.AccountView
@ -159,7 +160,12 @@ def render(
target, target,
&User.muting_reblogs?(&1, &2) &User.muting_reblogs?(&1, &2)
), ),
endorsed: false endorsed: false,
note:
UserNote.show(
reading_user,
target
)
} }
end end

View File

@ -475,6 +475,7 @@ defmodule Pleroma.Web.Router do
post("/accounts/:id/unblock", AccountController, :unblock) post("/accounts/:id/unblock", AccountController, :unblock)
post("/accounts/:id/mute", AccountController, :mute) post("/accounts/:id/mute", AccountController, :mute)
post("/accounts/:id/unmute", AccountController, :unmute) post("/accounts/:id/unmute", AccountController, :unmute)
post("/accounts/:id/note", AccountController, :note)
get("/conversations", ConversationController, :index) get("/conversations", ConversationController, :index)
post("/conversations/:id/read", ConversationController, :mark_as_read) post("/conversations/:id/read", ConversationController, :mark_as_read)

View File

@ -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

View File

@ -1797,4 +1797,21 @@ test "getting a list of blocks" do
assert [%{"id" => ^id2}] = result assert [%{"id" => ^id2}] = result
end 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 end

View File

@ -274,7 +274,8 @@ defp test_relationship_rendering(user, other_user, expected_result) do
requested: false, requested: false,
domain_blocking: false, domain_blocking: false,
showing_reblogs: true, showing_reblogs: true,
endorsed: false endorsed: false,
note: ""
} }
test "represent a relationship for the following and followed user" do test "represent a relationship for the following and followed user" do