From 93370b870a8d16b5dada5ef7c7e3ef2de378395e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Sun, 7 Aug 2022 23:56:52 +0200 Subject: [PATCH 01/28] Expose nonAnonymous field from Smithereen polls MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- .../object_validators/question_validator.ex | 1 + lib/pleroma/web/api_spec/schemas/poll.ex | 11 ++++++++++- lib/pleroma/web/mastodon_api/views/poll_view.ex | 5 ++++- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/lib/pleroma/web/activity_pub/object_validators/question_validator.ex b/lib/pleroma/web/activity_pub/object_validators/question_validator.ex index 621085e6c..7f9d4d648 100644 --- a/lib/pleroma/web/activity_pub/object_validators/question_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/question_validator.ex @@ -29,6 +29,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.QuestionValidator do field(:closed, ObjectValidators.DateTime) field(:voters, {:array, ObjectValidators.ObjectID}, default: []) + field(:nonAnonymous, :boolean) embeds_many(:anyOf, QuestionOptionsValidator) embeds_many(:oneOf, QuestionOptionsValidator) end diff --git a/lib/pleroma/web/api_spec/schemas/poll.ex b/lib/pleroma/web/api_spec/schemas/poll.ex index 91570582b..cb2ffdc68 100644 --- a/lib/pleroma/web/api_spec/schemas/poll.ex +++ b/lib/pleroma/web/api_spec/schemas/poll.ex @@ -56,6 +56,12 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Poll do } }, description: "Possible answers for the poll." + }, + pleroma: %Schema{ + type: :object, + properties: %{ + non_anonymous: %Schema{type: :boolean, description: "Is the voters collection public?"} + } } }, example: %{ @@ -79,7 +85,10 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Poll do votes_count: 4 } ], - emojis: [] + emojis: [], + pleroma: %{ + non_anonymous: false + } } }) end diff --git a/lib/pleroma/web/mastodon_api/views/poll_view.ex b/lib/pleroma/web/mastodon_api/views/poll_view.ex index 34e23873e..1e3c9f36d 100644 --- a/lib/pleroma/web/mastodon_api/views/poll_view.ex +++ b/lib/pleroma/web/mastodon_api/views/poll_view.ex @@ -21,7 +21,10 @@ def render("show.json", %{object: object, multiple: multiple, options: options} votes_count: votes_count, voters_count: voters_count(object), options: options, - emojis: Pleroma.Web.MastodonAPI.StatusView.build_emojis(object.data["emoji"]) + emojis: Pleroma.Web.MastodonAPI.StatusView.build_emojis(object.data["emoji"]), + pleroma: %{ + non_anonymous: object.data["nonAnonymous"] || false + } } if params[:for] do From e5bd1ee80154b31457567c3d31de6c63e0146352 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Mon, 8 Aug 2022 10:45:43 +0200 Subject: [PATCH 02/28] Add entry to @context, tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- changelog.d/public-polls.add | 1 + priv/static/schemas/litepub-0.1.jsonld | 4 +++- .../smithereen_non_anonymous_poll.json | 1 + test/fixtures/tesla_mock/smithereen_user.json | 1 + .../web/mastodon_api/views/poll_view_test.exs | 10 +++++++++- test/support/http_request_mock.ex | 18 ++++++++++++++++++ 6 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 changelog.d/public-polls.add create mode 100644 test/fixtures/tesla_mock/smithereen_non_anonymous_poll.json create mode 100644 test/fixtures/tesla_mock/smithereen_user.json diff --git a/changelog.d/public-polls.add b/changelog.d/public-polls.add new file mode 100644 index 000000000..0dae0c38e --- /dev/null +++ b/changelog.d/public-polls.add @@ -0,0 +1 @@ +Expose nonAnonymous field from Smithereen polls \ No newline at end of file diff --git a/priv/static/schemas/litepub-0.1.jsonld b/priv/static/schemas/litepub-0.1.jsonld index b499a96f5..d156cba55 100644 --- a/priv/static/schemas/litepub-0.1.jsonld +++ b/priv/static/schemas/litepub-0.1.jsonld @@ -40,7 +40,9 @@ "@type": "@id" }, "vcard": "http://www.w3.org/2006/vcard/ns#", - "formerRepresentations": "litepub:formerRepresentations" + "formerRepresentations": "litepub:formerRepresentations", + "sm": "http://smithereen.software/ns#", + "nonAnonymous": "sm:nonAnonymous" } ] } diff --git a/test/fixtures/tesla_mock/smithereen_non_anonymous_poll.json b/test/fixtures/tesla_mock/smithereen_non_anonymous_poll.json new file mode 100644 index 000000000..2b343ea64 --- /dev/null +++ b/test/fixtures/tesla_mock/smithereen_non_anonymous_poll.json @@ -0,0 +1 @@ +{"type":"Question","id":"https://friends.grishka.me/posts/54642","attributedTo":"https://friends.grishka.me/users/1","content":"

здесь тоже можно что-то написать отдельно от опроса

","published":"2021-09-04T00:22:16Z","url":"https://friends.grishka.me/posts/54642","to":["https://www.w3.org/ns/activitystreams#Public"],"cc":["https://friends.grishka.me/users/1/followers"],"replies":{"type":"Collection","id":"https://friends.grishka.me/posts/54642/replies","first":{"type":"CollectionPage","items":[],"partOf":"https://friends.grishka.me/posts/54642/replies","next":"https://friends.grishka.me/posts/54642/replies?page=1"}},"sensitive":false,"likes":"https://friends.grishka.me/posts/54642/likes","name":"тестовый опрос","oneOf":[{"type":"Note","id":"https://friends.grishka.me/posts/54642#options/76","name":"тестовый ответ 1","replies":{"type":"Collection","id":"https://friends.grishka.me/activitypub/objects/polls/24/options/76/votes","totalItems":4,"items":[]}},{"type":"Note","id":"https://friends.grishka.me/posts/54642#options/77","name":"тестовый ответ 2","replies":{"type":"Collection","id":"https://friends.grishka.me/activitypub/objects/polls/24/options/77/votes","totalItems":4,"items":[]}},{"type":"Note","id":"https://friends.grishka.me/posts/54642#options/78","name":"тестовый ответ 3","replies":{"type":"Collection","id":"https://friends.grishka.me/activitypub/objects/polls/24/options/78/votes","totalItems":6,"items":[]}}],"votersCount":14,"nonAnonymous":true,"@context":["https://www.w3.org/ns/activitystreams",{"sensitive":"as:sensitive","toot":"http://joinmastodon.org/ns#","sm":"http://smithereen.software/ns#","votersCount":"toot:votersCount","nonAnonymous":"sm:nonAnonymous"}]} \ No newline at end of file diff --git a/test/fixtures/tesla_mock/smithereen_user.json b/test/fixtures/tesla_mock/smithereen_user.json new file mode 100644 index 000000000..6468fc519 --- /dev/null +++ b/test/fixtures/tesla_mock/smithereen_user.json @@ -0,0 +1 @@ +{"type":"Person","id":"https://friends.grishka.me/users/1","name":"Григорий Клюшников","icon":{"type":"Image","image":{"type":"Image","url":"https://friends.grishka.me/i/6QLsOws97AWp5N_osd74C1IC1ijnFopyCBD9MSEeXNQ/q:93/bG9jYWw6Ly8vcy91cGxvYWRzL2F2YXRhcnMvNTYzODRhODEwODk5ZTRjMzI4YmY4YmQwM2Q2MWM3NmMud2VicA.jpg","mediaType":"image/jpeg","width":1280,"height":960},"width":573,"height":572,"cropRegion":[0.26422762870788574,0.3766937553882599,0.7113820910453796,0.9728997349739075],"url":"https://friends.grishka.me/i/ql_49PQcETAWgY_nC-Qj63H_Oa6FyOAEoWFkUSSkUvQ/c:573:572:nowe:338:362/q:93/bG9jYWw6Ly8vcy91cGxvYWRzL2F2YXRhcnMvNTYzODRhODEwODk5ZTRjMzI4YmY4YmQwM2Q2MWM3NmMud2VicA.jpg","mediaType":"image/jpeg"},"summary":"

Делаю эту хрень, пытаюсь вырвать социальные сети из жадных лап корпораций

\n

\n

\n

\n

\n

\n

\n

\n

This server does NOT support direct messages. Please write me on Telegram or Matrix.

","url":"https://friends.grishka.me/grishka","preferredUsername":"grishka","inbox":"https://friends.grishka.me/users/1/inbox","outbox":"https://friends.grishka.me/users/1/outbox","followers":"https://friends.grishka.me/users/1/followers","following":"https://friends.grishka.me/users/1/following","endpoints":{"sharedInbox":"https://friends.grishka.me/activitypub/sharedInbox","collectionSimpleQuery":"https://friends.grishka.me/users/1/collectionQuery"},"publicKey":{"id":"https://friends.grishka.me/users/1#main-key","owner":"https://friends.grishka.me/users/1","publicKeyPem":"-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAjlakm+i/d9ER/hIeR7KfiFW+SdLZj2SkKIeM8cmR+YFJuh9ghFqXrkFEjcaqUnAFqe5gYDNSQACnDLA8y4DnzjfGNIohKAnRoa9x6GORmfKQvcnjaTZ53S1NvUiPPyc0Pv/vfCtY/Ab0CEXe5BLqL38oZn817Jf7pBrPRTYH7m012kvwAUTT6k0Y8lPITBEG7nzYbbuGcrN9Y/RDdwE08jmBXlZ45bahRH3VNXVpQE17dCzJB+7k+iJ1R7YCoI+DuMlBYGXGE2KVk46NZTuLnOjFV9SyXfWX4/SrJM4oxev+SX2N75tQgmNZmVVHeqg2ZcbC0WCfNjJOi2HHS9MujwIDAQAB\n-----END PUBLIC KEY-----\n"},"wall":"https://friends.grishka.me/users/1/wall","firstName":"Григорий","lastName":"Клюшников","middleName":"Александрович","vcard:bday":"1993-01-22","gender":"http://schema.org#Male","supportsFriendRequests":true,"friends":"https://friends.grishka.me/users/1/friends","groups":"https://friends.grishka.me/users/1/groups","capabilities":{"supportsFriendRequests":true},"@context":["https://www.w3.org/ns/activitystreams",{"sm":"http://smithereen.software/ns#","cropRegion":{"@id":"sm:cropRegion","@container":"@list"},"wall":{"@id":"sm:wall","@type":"@id"},"collectionSimpleQuery":"sm:collectionSimpleQuery","sc":"http://schema.org#","firstName":"sc:givenName","lastName":"sc:familyName","middleName":"sc:additionalName","gender":{"@id":"sc:gender","@type":"sc:GenderType"},"maidenName":"sm:maidenName","friends":{"@id":"sm:friends","@type":"@id"},"groups":{"@id":"sm:groups","@type":"@id"},"vcard":"http://www.w3.org/2006/vcard/ns#","capabilities":"litepub:capabilities","supportsFriendRequests":"sm:supportsFriendRequests","litepub":"http://litepub.social/ns#"},"https://w3id.org/security/v1"]} \ No newline at end of file diff --git a/test/pleroma/web/mastodon_api/views/poll_view_test.exs b/test/pleroma/web/mastodon_api/views/poll_view_test.exs index a73d862fd..3aa73c224 100644 --- a/test/pleroma/web/mastodon_api/views/poll_view_test.exs +++ b/test/pleroma/web/mastodon_api/views/poll_view_test.exs @@ -43,7 +43,8 @@ test "renders a poll" do %{title: "why are you even asking?", votes_count: 0} ], votes_count: 0, - voters_count: 0 + voters_count: 0, + pleroma: %{non_anonymous: false} } result = PollView.render("show.json", %{object: object}) @@ -165,4 +166,11 @@ test "doesn't strips HTML tags" do ] } = PollView.render("show.json", %{object: object}) end + + test "that poll is non anonymous" do + object = Object.normalize("https://friends.grishka.me/posts/54642", fetch: true) + result = PollView.render("show.json", %{object: object}) + + assert result[:pleroma][:non_anonymous] == true + end end diff --git a/test/support/http_request_mock.ex b/test/support/http_request_mock.ex index 78a367024..2707804bc 100644 --- a/test/support/http_request_mock.ex +++ b/test/support/http_request_mock.ex @@ -1464,6 +1464,24 @@ def get("https://misskey.io/notes/8vs6wxufd0", _, _, _) do }} end + def get("https://friends.grishka.me/posts/54642", _, _, _) do + {:ok, + %Tesla.Env{ + status: 200, + body: File.read!("test/fixtures/tesla_mock/smithereen_non_anonymous_poll.json"), + headers: activitypub_object_headers() + }} + end + + def get("https://friends.grishka.me/users/1", _, _, _) do + {:ok, + %Tesla.Env{ + status: 200, + body: File.read!("test/fixtures/tesla_mock/smithereen_user.json"), + headers: activitypub_object_headers() + }} + end + def get(url, query, body, headers) do {:error, "Mock response not implemented for GET #{inspect(url)}, #{query}, #{inspect(body)}, #{inspect(headers)}"} From fa02a1e634632bb672b53c244179d3088ffe8423 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Fri, 19 Jan 2024 18:05:46 +0100 Subject: [PATCH 03/28] Update MastoAPI responses docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- docs/development/API/differences_in_mastoapi_responses.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/development/API/differences_in_mastoapi_responses.md b/docs/development/API/differences_in_mastoapi_responses.md index 48a9c104c..70c31272d 100644 --- a/docs/development/API/differences_in_mastoapi_responses.md +++ b/docs/development/API/differences_in_mastoapi_responses.md @@ -39,6 +39,7 @@ Has these additional fields under the `pleroma` object: - `emoji_reactions`: A list with emoji / reaction maps. The format is `{name: "☕", count: 1, me: true}`. Contains no information about the reacting users, for that use the `/statuses/:id/reactions` endpoint. - `parent_visible`: If the parent of this post is visible to the user or not. - `pinned_at`: a datetime (iso8601) when status was pinned, `null` otherwise. +- `non_anonymous`: true if the source post specifies the poll results are not anonymous. Currently only implemented by Smithereen. The `GET /api/v1/statuses/:id/source` endpoint additionally has the following attributes: From a5f64ffd0cba5e183744cf46506656ba61d36eda Mon Sep 17 00:00:00 2001 From: Haelwenn Date: Fri, 19 Jan 2024 21:28:15 +0000 Subject: [PATCH 04/28] =?UTF-8?q?Apply=20lanodan=E2=80=99s=20suggestion=20?= =?UTF-8?q?to=201=20file?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pleroma/web/api_spec/schemas/poll.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pleroma/web/api_spec/schemas/poll.ex b/lib/pleroma/web/api_spec/schemas/poll.ex index cb2ffdc68..eeb1a5490 100644 --- a/lib/pleroma/web/api_spec/schemas/poll.ex +++ b/lib/pleroma/web/api_spec/schemas/poll.ex @@ -60,7 +60,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Poll do pleroma: %Schema{ type: :object, properties: %{ - non_anonymous: %Schema{type: :boolean, description: "Is the voters collection public?"} + non_anonymous: %Schema{type: :boolean, description: "Can voters be publicly identified?"} } } }, From def088ce520ffac3e200cbf63b049f1d918d2b79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Sun, 21 Jan 2024 18:23:24 +0100 Subject: [PATCH 05/28] format MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- lib/pleroma/web/api_spec/schemas/poll.ex | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/web/api_spec/schemas/poll.ex b/lib/pleroma/web/api_spec/schemas/poll.ex index eeb1a5490..20cf5b061 100644 --- a/lib/pleroma/web/api_spec/schemas/poll.ex +++ b/lib/pleroma/web/api_spec/schemas/poll.ex @@ -60,7 +60,10 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Poll do pleroma: %Schema{ type: :object, properties: %{ - non_anonymous: %Schema{type: :boolean, description: "Can voters be publicly identified?"} + non_anonymous: %Schema{ + type: :boolean, + description: "Can voters be publicly identified?" + } } } }, From d415686bb9248e1c5b8913b7c3b96f74e9b8f230 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Mon, 26 Feb 2024 23:45:02 +0100 Subject: [PATCH 06/28] Allow to group bookmarks in folders MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- changelog.d/bookmark-folders.add | 1 + .../API/differences_in_mastoapi_responses.md | 7 + docs/development/API/pleroma_api.md | 46 ++++++ lib/pleroma/bookmark.ex | 31 +++- lib/pleroma/bookmark_folder.ex | 115 ++++++++++++++ lib/pleroma/web/api_spec.ex | 3 +- .../pleroma_bookmark_folder_operation.ex | 125 +++++++++++++++ .../api_spec/operations/status_operation.ex | 22 ++- .../web/api_spec/schemas/bookmark_folder.ex | 26 +++ .../controllers/status_controller.ex | 17 +- .../web/mastodon_api/views/status_view.ex | 28 +++- .../controllers/bookmark_folder_controller.ex | 68 ++++++++ .../pleroma_api/views/bookmark_folder_view.ex | 44 ++++++ lib/pleroma/web/router.ex | 5 + ...20240223165000_create_bookmark_folders.exs | 27 ++++ test/pleroma/bookmark_folder_test.exs | 60 +++++++ test/pleroma/bookmark_test.exs | 17 +- .../controllers/status_controller_test.exs | 54 +++++++ .../mastodon_api/views/status_view_test.exs | 3 +- .../bookmark_folder_controller_test.ex | 148 ++++++++++++++++++ uploads/.gitignore | 3 - 21 files changed, 829 insertions(+), 21 deletions(-) create mode 100644 changelog.d/bookmark-folders.add create mode 100644 lib/pleroma/bookmark_folder.ex create mode 100644 lib/pleroma/web/api_spec/operations/pleroma_bookmark_folder_operation.ex create mode 100644 lib/pleroma/web/api_spec/schemas/bookmark_folder.ex create mode 100644 lib/pleroma/web/pleroma_api/controllers/bookmark_folder_controller.ex create mode 100644 lib/pleroma/web/pleroma_api/views/bookmark_folder_view.ex create mode 100644 priv/repo/migrations/20240223165000_create_bookmark_folders.exs create mode 100644 test/pleroma/bookmark_folder_test.exs create mode 100644 test/pleroma/web/pleroma_api/controllers/bookmark_folder_controller_test.ex delete mode 100644 uploads/.gitignore diff --git a/changelog.d/bookmark-folders.add b/changelog.d/bookmark-folders.add new file mode 100644 index 000000000..d9b03cecc --- /dev/null +++ b/changelog.d/bookmark-folders.add @@ -0,0 +1 @@ +Allow to group bookmarks in folders \ No newline at end of file diff --git a/docs/development/API/differences_in_mastoapi_responses.md b/docs/development/API/differences_in_mastoapi_responses.md index 2937b2301..d72717483 100644 --- a/docs/development/API/differences_in_mastoapi_responses.md +++ b/docs/development/API/differences_in_mastoapi_responses.md @@ -40,6 +40,7 @@ Has these additional fields under the `pleroma` object: - `parent_visible`: If the parent of this post is visible to the user or not. - `pinned_at`: a datetime (iso8601) when status was pinned, `null` otherwise. - `quotes_count`: the count of status quotes. +- `bookmark_folder`: the ID of the folder bookmark is stored within (if any). The `GET /api/v1/statuses/:id/source` endpoint additionally has the following attributes: @@ -65,6 +66,12 @@ Some apps operate under the assumption that no more than 4 attachments can be re Pleroma does not process remote images and therefore cannot include fields such as `meta` and `blurhash`. It does not support focal points or aspect ratios. The frontend is expected to handle it. +## Bookmarks + +The `GET /api/v1/bookmarks` endpoint accepts optional parameter `folder_id` for bookmark folder ID. + +The `POST /api/v1/statuses/:id/bookmark` endpoint accepts optional parameter `folder_id` for bookmark folder ID. + ## Accounts The `id` parameter can also be the `nickname` of the user. This only works in these endpoints, not the deeper nested ones for following etc. diff --git a/docs/development/API/pleroma_api.md b/docs/development/API/pleroma_api.md index 060af5c14..267dfc1ec 100644 --- a/docs/development/API/pleroma_api.md +++ b/docs/development/API/pleroma_api.md @@ -283,6 +283,52 @@ See [Admin-API](admin_api.md) * `id`: the id of the status * Response: JSON, returns a list of Mastodon Status entities +## `GET /api/v1/pleroma/bookmark_folders` +### Gets user bookmark folders +* Authentication: required + +* Response: JSON. Returns a list of bookmark folders. +* Example response: +```json +[ + { + "id": "9umDrYheeY451cQnEe", + "name": "Read later", + "emoji": "🕓", + "source": { + "emoji": "🕓" + } + } +] +``` + +## `POST /api/v1/pleroma/bookmark_folders` +### Creates a bookmark folder +* Authentication: required + +* Params: + * `name`: folder name + * `emoji`: folder emoji (optional) +* Response: JSON. Returns a single bookmark folder. + +## `PATCH /api/v1/pleroma/bookmark_folders/:id` +### Updates a bookmark folder +* Authentication: required + +* Params: + * `id`: folder id + * `name`: folder name (optional) + * `emoji`: folder emoji (optional) +* Response: JSON. Returns a single bookmark folder. + +## `DELETE /api/v1/pleroma/bookmark_folders/:id` +### Deletes a bookmark folder +* Authentication: required + +* Params: + * `id`: folder id +* Response: JSON. Returns a single bookmark folder. + ## `/api/v1/pleroma/mascot` ### Gets user mascot image * Method `GET` diff --git a/lib/pleroma/bookmark.ex b/lib/pleroma/bookmark.ex index b83d72446..1a2a63b82 100644 --- a/lib/pleroma/bookmark.ex +++ b/lib/pleroma/bookmark.ex @@ -10,6 +10,7 @@ defmodule Pleroma.Bookmark do alias Pleroma.Activity alias Pleroma.Bookmark + alias Pleroma.BookmarkFolder alias Pleroma.Repo alias Pleroma.User @@ -18,33 +19,46 @@ defmodule Pleroma.Bookmark do schema "bookmarks" do belongs_to(:user, User, type: FlakeId.Ecto.CompatType) belongs_to(:activity, Activity, type: FlakeId.Ecto.CompatType) + belongs_to(:folder, BookmarkFolder, type: FlakeId.Ecto.CompatType) timestamps() end @spec create(Ecto.UUID.t(), Ecto.UUID.t()) :: {:ok, Bookmark.t()} | {:error, Ecto.Changeset.t()} - def create(user_id, activity_id) do + def create(user_id, activity_id, folder_id \\ nil) do attrs = %{ user_id: user_id, - activity_id: activity_id + activity_id: activity_id, + folder_id: folder_id } %Bookmark{} - |> cast(attrs, [:user_id, :activity_id]) + |> cast(attrs, [:user_id, :activity_id, :folder_id]) |> validate_required([:user_id, :activity_id]) |> unique_constraint(:activity_id, name: :bookmarks_user_id_activity_id_index) - |> Repo.insert() + |> Repo.insert( + on_conflict: [set: [folder_id: folder_id]], + conflict_target: [:user_id, :activity_id] + ) end @spec for_user_query(Ecto.UUID.t()) :: Ecto.Query.t() - def for_user_query(user_id) do + def for_user_query(user_id, folder_id \\ nil) do Bookmark |> where(user_id: ^user_id) + |> maybe_filter_by_folder(folder_id) |> join(:inner, [b], activity in assoc(b, :activity)) |> preload([b, a], activity: a) end + defp maybe_filter_by_folder(query, nil), do: query + + defp maybe_filter_by_folder(query, folder_id) do + query + |> where(folder_id: ^folder_id) + end + def get(user_id, activity_id) do Bookmark |> where(user_id: ^user_id) @@ -62,4 +76,11 @@ def destroy(user_id, activity_id) do |> Repo.one() |> Repo.delete() end + + def set_folder(bookmark, folder_id) do + bookmark + |> cast(%{folder_id: folder_id}, [:folder_id]) + |> validate_required([:folder_id]) + |> Repo.update() + end end diff --git a/lib/pleroma/bookmark_folder.ex b/lib/pleroma/bookmark_folder.ex new file mode 100644 index 000000000..14d37e197 --- /dev/null +++ b/lib/pleroma/bookmark_folder.ex @@ -0,0 +1,115 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2024 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.BookmarkFolder do + use Ecto.Schema + + import Ecto.Changeset + import Ecto.Query + + alias Pleroma.BookmarkFolder + alias Pleroma.Emoji + alias Pleroma.Repo + alias Pleroma.User + + @type t :: %__MODULE__{} + @primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true} + + schema "bookmark_folders" do + field(:name, :string) + field(:emoji, :string) + + belongs_to(:user, User, type: FlakeId.Ecto.CompatType) + + timestamps() + end + + def get_by_id(id), do: Repo.get_by(BookmarkFolder, id: id) + + def create(user_id, name, emoji \\ nil) do + %BookmarkFolder{} + |> cast( + %{ + user_id: user_id, + name: name, + emoji: emoji + }, + [:user_id, :name, :emoji] + ) + |> validate_required([:user_id, :name]) + |> fix_emoji() + |> validate_emoji() + |> unique_constraint([:user_id, :name]) + |> Repo.insert() + end + + def update(folder_id, name, emoji \\ nil) do + get_by_id(folder_id) + |> cast( + %{ + name: name, + emoji: emoji + }, + [:name, :emoji] + ) + |> fix_emoji() + |> validate_emoji() + |> unique_constraint([:user_id, :name]) + |> Repo.update() + end + + defp fix_emoji(changeset) do + with {:emoji_field, emoji} when is_binary(emoji) <- + {:emoji_field, get_field(changeset, :emoji)}, + {:fixed_emoji, emoji} <- + {:fixed_emoji, + emoji + |> Pleroma.Emoji.fully_qualify_emoji() + |> Pleroma.Emoji.maybe_quote()} do + put_change(changeset, :emoji, emoji) + else + {:emoji_field, _} -> changeset + end + end + + defp validate_emoji(changeset) do + validate_change(changeset, :emoji, fn + :emoji, nil -> + [] + + :emoji, emoji -> + if Emoji.unicode?(emoji) or valid_local_custom_emoji?(emoji) do + [] + else + [emoji: "Invalid emoji"] + end + end) + end + + defp valid_local_custom_emoji?(emoji) do + with %{file: _path} <- Emoji.get(emoji) do + true + else + _ -> false + end + end + + def delete(folder_id) do + BookmarkFolder + |> Repo.get_by(id: folder_id) + |> Repo.delete() + end + + def for_user(user_id) do + BookmarkFolder + |> where(user_id: ^user_id) + |> Repo.all() + end + + def belongs_to_user?(folder_id, user_id) do + BookmarkFolder + |> where(id: ^folder_id, user_id: ^user_id) + |> Repo.exists?() + end +end diff --git a/lib/pleroma/web/api_spec.ex b/lib/pleroma/web/api_spec.ex index 3588608f2..10d221571 100644 --- a/lib/pleroma/web/api_spec.ex +++ b/lib/pleroma/web/api_spec.ex @@ -137,7 +137,8 @@ def spec(opts \\ []) do "Scheduled statuses", "Search", "Status actions", - "Media attachments" + "Media attachments", + "Bookmark folders" ] }, %{ diff --git a/lib/pleroma/web/api_spec/operations/pleroma_bookmark_folder_operation.ex b/lib/pleroma/web/api_spec/operations/pleroma_bookmark_folder_operation.ex new file mode 100644 index 000000000..eaa683125 --- /dev/null +++ b/lib/pleroma/web/api_spec/operations/pleroma_bookmark_folder_operation.ex @@ -0,0 +1,125 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2024 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.PleromaBookmarkFolderOperation do + alias OpenApiSpex.Operation + alias OpenApiSpex.Schema + alias Pleroma.Web.ApiSpec.Schemas.ApiError + alias Pleroma.Web.ApiSpec.Schemas.BookmarkFolder + alias Pleroma.Web.ApiSpec.Schemas.FlakeID + + import Pleroma.Web.ApiSpec.Helpers + + @spec open_api_operation(any()) :: any() + def open_api_operation(action) do + operation = String.to_existing_atom("#{action}_operation") + apply(__MODULE__, operation, []) + end + + def index_operation do + %Operation{ + tags: ["Bookmark folders"], + summary: "All bookmark folders", + security: [%{"oAuth" => ["read:bookmarks"]}], + operationId: "PleromaAPI.BookmarkFolderController.index", + responses: %{ + 200 => + Operation.response("Array of Bookmark Folders", "application/json", %Schema{ + type: :array, + items: BookmarkFolder + }) + } + } + end + + def create_operation do + %Operation{ + tags: ["Bookmark folders"], + summary: "Create a bookmark folder", + security: [%{"oAuth" => ["write:bookmarks"]}], + operationId: "PleromaAPI.BookmarkFolderController.create", + requestBody: request_body("Parameters", create_request(), required: true), + responses: %{ + 200 => Operation.response("Bookmark Folder", "application/json", BookmarkFolder), + 422 => Operation.response("Error", "application/json", ApiError) + } + } + end + + def update_operation do + %Operation{ + tags: ["Bookmark folders"], + summary: "Update a bookmark folder", + security: [%{"oAuth" => ["write:bookmarks"]}], + operationId: "PleromaAPI.BookmarkFolderController.update", + parameters: [id_param()], + requestBody: request_body("Parameters", update_request(), required: true), + responses: %{ + 200 => Operation.response("Bookmark Folder", "application/json", BookmarkFolder), + 403 => Operation.response("Forbidden", "application/json", ApiError), + 404 => Operation.response("Not Found", "application/json", ApiError), + 422 => Operation.response("Error", "application/json", ApiError) + } + } + end + + def delete_operation do + %Operation{ + tags: ["Bookmark folders"], + summary: "Delete a bookmark folder", + security: [%{"oAuth" => ["write:bookmarks"]}], + operationId: "PleromaAPI.BookmarkFolderController.delete", + parameters: [id_param()], + responses: %{ + 200 => Operation.response("Bookmark Folder", "application/json", BookmarkFolder), + 403 => Operation.response("Forbidden", "application/json", ApiError), + 404 => Operation.response("Not Found", "application/json", ApiError) + } + } + end + + defp create_request do + %Schema{ + title: "BookmarkFolderCreateRequest", + type: :object, + properties: %{ + name: %Schema{ + type: :string, + description: "Folder name" + }, + emoji: %Schema{ + type: :string, + nullable: true, + description: "Folder emoji" + } + } + } + end + + defp update_request do + %Schema{ + title: "BookmarkFolderUpdateRequest", + type: :object, + properties: %{ + name: %Schema{ + type: :string, + nullable: true, + description: "Folder name" + }, + emoji: %Schema{ + type: :string, + nullable: true, + description: "Folder emoji" + } + } + } + end + + def id_param do + Operation.parameter(:id, :path, FlakeID.schema(), "Bookmark Folder ID", + example: "9umDrYheeY451cQnEe", + required: true + ) + end +end diff --git a/lib/pleroma/web/api_spec/operations/status_operation.ex b/lib/pleroma/web/api_spec/operations/status_operation.ex index ef4e34044..1717c68c8 100644 --- a/lib/pleroma/web/api_spec/operations/status_operation.ex +++ b/lib/pleroma/web/api_spec/operations/status_operation.ex @@ -256,6 +256,18 @@ def bookmark_operation do description: "Privately bookmark a status", operationId: "StatusController.bookmark", parameters: [id_param()], + requestBody: + request_body("Parameters", %Schema{ + title: "StatusUpdateRequest", + type: :object, + properties: %{ + folder_id: %Schema{ + nullable: true, + allOf: [FlakeID], + description: "ID of bookmarks folder, if any" + } + } + }), responses: %{ 200 => status_response() } @@ -430,7 +442,15 @@ def bookmarks_operation do summary: "Bookmarked statuses", description: "Statuses the user has bookmarked", operationId: "StatusController.bookmarks", - parameters: pagination_params(), + parameters: [ + Operation.parameter( + :folder_id, + :query, + FlakeID.schema(), + "If provided, only display bookmarks from given folder" + ) + | pagination_params() + ], security: [%{"oAuth" => ["read:bookmarks"]}], responses: %{ 200 => Operation.response("Array of Statuses", "application/json", array_of_statuses()) diff --git a/lib/pleroma/web/api_spec/schemas/bookmark_folder.ex b/lib/pleroma/web/api_spec/schemas/bookmark_folder.ex new file mode 100644 index 000000000..e8b4f43b7 --- /dev/null +++ b/lib/pleroma/web/api_spec/schemas/bookmark_folder.ex @@ -0,0 +1,26 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2024 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.Schemas.BookmarkFolder do + alias OpenApiSpex.Schema + alias Pleroma.Web.ApiSpec.Schemas.FlakeID + + require OpenApiSpex + + OpenApiSpex.schema(%{ + title: "BookmarkFolder", + description: "Response schema for a bookmark folder", + type: :object, + properties: %{ + id: FlakeID, + name: %Schema{type: :string, description: "Folder name"}, + emoji: %Schema{type: :string, description: "Folder emoji", nullable: true} + }, + example: %{ + "id" => "9toJCu5YZW7O7gfvH6", + "name" => "Read later", + "emoji" => nil + } + }) +end diff --git a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex index 5aa7bddf0..4f6de8a00 100644 --- a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex @@ -12,6 +12,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do alias Pleroma.Activity alias Pleroma.Bookmark + alias Pleroma.BookmarkFolder alias Pleroma.Object alias Pleroma.Repo alias Pleroma.ScheduledActivity @@ -411,13 +412,22 @@ def unpin( @doc "POST /api/v1/statuses/:id/bookmark" def bookmark( - %{assigns: %{user: user}, private: %{open_api_spex: %{params: %{id: id}}}} = conn, + %{ + assigns: %{user: user}, + private: %{open_api_spex: %{body_params: body_params, params: %{id: id}}} + } = conn, _ ) do with %Activity{} = activity <- Activity.get_by_id_with_object(id), %User{} = user <- User.get_cached_by_nickname(user.nickname), true <- Visibility.visible_for_user?(activity, user), - {:ok, _bookmark} <- Bookmark.create(user.id, activity.id) do + folder_id <- Map.get(body_params, :folder_id, nil), + folder_id <- + if(folder_id && BookmarkFolder.belongs_to_user?(folder_id, user.id), + do: folder_id, + else: nil + ), + {:ok, _bookmark} <- Bookmark.create(user.id, activity.id, folder_id) do try_render(conn, "show.json", activity: activity, for: user, as: :activity) end end @@ -573,10 +583,11 @@ def favourites( @doc "GET /api/v1/bookmarks" def bookmarks(%{assigns: %{user: user}, private: %{open_api_spex: %{params: params}}} = conn, _) do user = User.get_cached_by_id(user.id) + folder_id = Map.get(params, :folder_id) bookmarks = user.id - |> Bookmark.for_user_query() + |> Bookmark.for_user_query(folder_id) |> Pleroma.Pagination.fetch_paginated(params) activities = diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index 6303e72ce..e464f60dc 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -184,7 +184,14 @@ def render( favorited = opts[:for] && opts[:for].ap_id in (object.data["likes"] || []) - bookmarked = Activity.get_bookmark(reblogged_parent_activity, opts[:for]) != nil + bookmark = Activity.get_bookmark(reblogged_parent_activity, opts[:for]) + + bookmark_folder = + if bookmark != nil do + bookmark.folder_id + else + nil + end mentions = activity.recipients @@ -213,7 +220,7 @@ def render( favourites_count: 0, reblogged: reblogged?(reblogged_parent_activity, opts[:for]), favourited: present?(favorited), - bookmarked: present?(bookmarked), + bookmarked: present?(bookmark), muted: false, pinned: pinned?, sensitive: false, @@ -227,7 +234,8 @@ def render( emojis: [], pleroma: %{ local: activity.local, - pinned_at: pinned_at + pinned_at: pinned_at, + bookmark_folder: bookmark_folder } } end @@ -264,7 +272,14 @@ def render("show.json", %{activity: %{data: %{"object" => _object}} = activity} favorited = opts[:for] && opts[:for].ap_id in (object.data["likes"] || []) - bookmarked = Activity.get_bookmark(activity, opts[:for]) != nil + bookmark = Activity.get_bookmark(activity, opts[:for]) + + bookmark_folder = + if bookmark != nil do + bookmark.folder_id + else + nil + end client_posted_this_activity = opts[:for] && user.id == opts[:for].id @@ -418,7 +433,7 @@ def render("show.json", %{activity: %{data: %{"object" => _object}} = activity} favourites_count: like_count, reblogged: reblogged?(activity, opts[:for]), favourited: present?(favorited), - bookmarked: present?(bookmarked), + bookmarked: present?(bookmark), muted: muted, pinned: pinned?, sensitive: sensitive, @@ -448,7 +463,8 @@ def render("show.json", %{activity: %{data: %{"object" => _object}} = activity} emoji_reactions: emoji_reactions, parent_visible: visible_for_user?(reply_to, opts[:for]), pinned_at: pinned_at, - quotes_count: object.data["quotesCount"] || 0 + quotes_count: object.data["quotesCount"] || 0, + bookmark_folder: bookmark_folder } } end diff --git a/lib/pleroma/web/pleroma_api/controllers/bookmark_folder_controller.ex b/lib/pleroma/web/pleroma_api/controllers/bookmark_folder_controller.ex new file mode 100644 index 000000000..6d6e2e940 --- /dev/null +++ b/lib/pleroma/web/pleroma_api/controllers/bookmark_folder_controller.ex @@ -0,0 +1,68 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2024 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.PleromaAPI.BookmarkFolderController do + use Pleroma.Web, :controller + + alias Pleroma.BookmarkFolder + alias Pleroma.Web.Plugs.OAuthScopesPlug + + plug(Pleroma.Web.ApiSpec.CastAndValidate) + + # Note: scope not present in Mastodon: read:bookmarks + plug(OAuthScopesPlug, %{scopes: ["read:bookmarks"]} when action == :index) + + # Note: scope not present in Mastodon: write:bookmarks + plug( + OAuthScopesPlug, + %{scopes: ["write:bookmarks"]} when action in [:create, :update, :delete] + ) + + defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaBookmarkFolderOperation + + action_fallback(Pleroma.Web.MastodonAPI.FallbackController) + + def index(%{assigns: %{user: user}} = conn, _params) do + with folders <- BookmarkFolder.for_user(user.id) do + conn + |> render("index.json", %{folders: folders, as: :folder}) + end + end + + def create( + %{assigns: %{user: user}, private: %{open_api_spex: %{body_params: params}}} = conn, + _ + ) do + with {:ok, folder} <- BookmarkFolder.create(user.id, params[:name], params[:emoji]) do + render(conn, "show.json", folder: folder) + end + end + + def update( + %{ + assigns: %{user: user}, + private: %{open_api_spex: %{body_params: params, params: %{id: id}}} + } = conn, + _ + ) do + with true <- BookmarkFolder.belongs_to_user?(id, user.id), + {:ok, folder} <- BookmarkFolder.update(id, params[:name], params[:emoji]) do + render(conn, "show.json", folder: folder) + else + false -> {:error, :forbidden} + end + end + + def delete( + %{assigns: %{user: user}, private: %{open_api_spex: %{params: %{id: id}}}} = conn, + _ + ) do + with true <- BookmarkFolder.belongs_to_user?(id, user.id), + {:ok, folder} <- BookmarkFolder.delete(id) do + render(conn, "show.json", folder: folder) + else + false -> {:error, :forbidden} + end + end +end diff --git a/lib/pleroma/web/pleroma_api/views/bookmark_folder_view.ex b/lib/pleroma/web/pleroma_api/views/bookmark_folder_view.ex new file mode 100644 index 000000000..fc6ad59d0 --- /dev/null +++ b/lib/pleroma/web/pleroma_api/views/bookmark_folder_view.ex @@ -0,0 +1,44 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2024 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.PleromaAPI.BookmarkFolderView do + use Pleroma.Web, :view + + alias Pleroma.BookmarkFolder + alias Pleroma.Emoji + alias Pleroma.Web.Endpoint + + def render("show.json", %{folder: %BookmarkFolder{} = folder}) do + %{ + id: folder.id |> to_string(), + name: folder.name, + emoji: get_emoji(folder.emoji), + source: %{ + emoji: folder.emoji + } + } + end + + def render("index.json", %{folders: folders} = opts) do + render_many(folders, __MODULE__, "show.json", Map.delete(opts, :folders)) + end + + defp get_emoji(nil) do + nil + end + + defp get_emoji(emoji) do + if Emoji.unicode?(emoji) do + emoji + else + emoji = Emoji.get(emoji) + + if emoji != nil do + Endpoint.url() |> URI.merge(emoji.relative_url) |> to_string() + else + nil + end + end + end +end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 8ba845364..4fe0cb02f 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -580,6 +580,11 @@ defmodule Pleroma.Web.Router do get("/backups", BackupController, :index) post("/backups", BackupController, :create) + + get("/bookmark_folders", BookmarkFolderController, :index) + post("/bookmark_folders", BookmarkFolderController, :create) + patch("/bookmark_folders/:id", BookmarkFolderController, :update) + delete("/bookmark_folders/:id", BookmarkFolderController, :delete) end scope [] do diff --git a/priv/repo/migrations/20240223165000_create_bookmark_folders.exs b/priv/repo/migrations/20240223165000_create_bookmark_folders.exs new file mode 100644 index 000000000..016916968 --- /dev/null +++ b/priv/repo/migrations/20240223165000_create_bookmark_folders.exs @@ -0,0 +1,27 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2024 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Repo.Migrations.CreateBookmarkFolders do + use Ecto.Migration + + def change do + create_if_not_exists table(:bookmark_folders, primary_key: false) do + add(:id, :uuid, primary_key: true) + add(:name, :string, null: false) + add(:emoji, :string) + add(:user_id, references(:users, type: :uuid, on_delete: :delete_all)) + + timestamps() + end + + alter table(:bookmarks) do + add_if_not_exists( + :folder_id, + references(:bookmark_folders, type: :uuid, on_delete: :nilify_all) + ) + end + + create_if_not_exists(unique_index(:bookmark_folders, [:user_id, :name])) + end +end diff --git a/test/pleroma/bookmark_folder_test.exs b/test/pleroma/bookmark_folder_test.exs new file mode 100644 index 000000000..c45129b0e --- /dev/null +++ b/test/pleroma/bookmark_folder_test.exs @@ -0,0 +1,60 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2024 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.BookmarkFolderTest do + use Pleroma.DataCase, async: true + import Pleroma.Factory + alias Pleroma.BookmarkFolder + + describe "create/3" do + test "with valid params" do + user = insert(:user) + {:ok, folder} = BookmarkFolder.create(user.id, "Read later", "🕓") + assert folder.user_id == user.id + assert folder.name == "Read later" + assert folder.emoji == "🕓" + end + + test "with invalid params" do + {:error, changeset} = BookmarkFolder.create(nil, "", "not an emoji") + refute changeset.valid? + + assert changeset.errors == [ + emoji: {"Invalid emoji", []}, + user_id: {"can't be blank", [validation: :required]}, + name: {"can't be blank", [validation: :required]} + ] + end + end + + test "update/3" do + user = insert(:user) + {:ok, folder} = BookmarkFolder.create(user.id, "Read ltaer") + {:ok, folder} = BookmarkFolder.update(folder.id, "Read later") + assert folder.name == "Read later" + end + + test "for_user/1" do + user = insert(:user) + other_user = insert(:user) + + {:ok, _} = BookmarkFolder.create(user.id, "Folder 1") + {:ok, _} = BookmarkFolder.create(user.id, "Folder 2") + {:ok, _} = BookmarkFolder.create(other_user.id, "Folder 3") + + folders = BookmarkFolder.for_user(user.id) + + assert length(folders) == 2 + end + + test "belongs_to_user?/2" do + user = insert(:user) + other_user = insert(:user) + + {:ok, folder} = BookmarkFolder.create(user.id, "Folder") + + assert true == BookmarkFolder.belongs_to_user?(folder.id, user.id) + assert false == BookmarkFolder.belongs_to_user?(folder.id, other_user.id) + end +end diff --git a/test/pleroma/bookmark_test.exs b/test/pleroma/bookmark_test.exs index 57144ded3..a2ed24c26 100644 --- a/test/pleroma/bookmark_test.exs +++ b/test/pleroma/bookmark_test.exs @@ -6,15 +6,17 @@ defmodule Pleroma.BookmarkTest do use Pleroma.DataCase, async: true import Pleroma.Factory alias Pleroma.Bookmark + alias Pleroma.BookmarkFolder alias Pleroma.Web.CommonAPI - describe "create/2" do + describe "create/3" do test "with valid params" do user = insert(:user) {:ok, activity} = CommonAPI.post(user, %{status: "Some cool information"}) {:ok, bookmark} = Bookmark.create(user.id, activity.id) assert bookmark.user_id == user.id assert bookmark.activity_id == activity.id + assert bookmark.folder_id == nil end test "with invalid params" do @@ -26,6 +28,19 @@ test "with invalid params" do activity_id: {"can't be blank", [validation: :required]} ] end + + test "update existing bookmark folder" do + user = insert(:user) + {:ok, activity} = CommonAPI.post(user, %{status: "Some cool information"}) + + {:ok, bookmark} = Bookmark.create(user.id, activity.id) + assert bookmark.folder_id == nil + + {:ok, bookmark_folder} = BookmarkFolder.create(user.id, "Read later") + + {:ok, bookmark} = Bookmark.create(user.id, activity.id, bookmark_folder.id) + assert bookmark.folder_id == bookmark_folder.id + end end describe "destroy/2" do diff --git a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs index f95f15ec3..3d8a0fa99 100644 --- a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs @@ -1828,6 +1828,60 @@ test "bookmarks" do json_response_and_validate_schema(bookmarks, 200) end + test "bookmark folders" do + %{conn: conn, user: user} = oauth_access(["write:bookmarks", "read:bookmarks"]) + + {:ok, folder} = Pleroma.BookmarkFolder.create(user.id, "folder") + author = insert(:user) + + folder_bookmarks_uri = "/api/v1/bookmarks?folder_id=#{folder.id}" + + {:ok, activity1} = CommonAPI.post(author, %{status: "heweoo?"}) + {:ok, activity2} = CommonAPI.post(author, %{status: "heweoo!"}) + + # Add bookmark with a folder + response = + conn + |> put_req_header("content-type", "application/json") + |> post("/api/v1/statuses/#{activity1.id}/bookmark", %{folder_id: folder.id}) + + assert json_response_and_validate_schema(response, 200)["bookmarked"] == true + + assert json_response_and_validate_schema(response, 200)["pleroma"]["bookmark_folder"] == + folder.id + + response = + conn + |> put_req_header("content-type", "application/json") + |> post("/api/v1/statuses/#{activity2.id}/bookmark") + + assert json_response_and_validate_schema(response, 200)["bookmarked"] == true + assert json_response_and_validate_schema(response, 200)["pleroma"]["bookmark_folder"] == nil + + bookmarks = + get(conn, folder_bookmarks_uri) + |> json_response_and_validate_schema(200) + + assert length(bookmarks) == 1 + + # Update folder for existing bookmark + response = + conn + |> put_req_header("content-type", "application/json") + |> post("/api/v1/statuses/#{activity2.id}/bookmark", %{folder_id: folder.id}) + + assert json_response_and_validate_schema(response, 200)["bookmarked"] == true + + assert json_response_and_validate_schema(response, 200)["pleroma"]["bookmark_folder"] == + folder.id + + bookmarks = + get(conn, folder_bookmarks_uri) + |> json_response_and_validate_schema(200) + + assert length(bookmarks) == 2 + end + describe "conversation muting" do setup do: oauth_access(["write:mutes"]) diff --git a/test/pleroma/web/mastodon_api/views/status_view_test.exs b/test/pleroma/web/mastodon_api/views/status_view_test.exs index d82eeb7af..f007bb317 100644 --- a/test/pleroma/web/mastodon_api/views/status_view_test.exs +++ b/test/pleroma/web/mastodon_api/views/status_view_test.exs @@ -341,7 +341,8 @@ test "a note activity" do emoji_reactions: [], parent_visible: false, pinned_at: nil, - quotes_count: 0 + quotes_count: 0, + bookmark_folder: nil } } diff --git a/test/pleroma/web/pleroma_api/controllers/bookmark_folder_controller_test.ex b/test/pleroma/web/pleroma_api/controllers/bookmark_folder_controller_test.ex new file mode 100644 index 000000000..71b850859 --- /dev/null +++ b/test/pleroma/web/pleroma_api/controllers/bookmark_folder_controller_test.ex @@ -0,0 +1,148 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2024 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only +defmodule Pleroma.Web.PleromaAPI.BookmarkFolderControllerTest do + use Pleroma.Web.ConnCase + + alias Pleroma.BookmarkFolder + # alias Pleroma.Object + # alias Pleroma.Tests.Helpers + # alias Pleroma.UnstubbedConfigMock, as: ConfigMock + # alias Pleroma.User + # alias Pleroma.Web.ActivityPub.ActivityPub + # alias Pleroma.Web.CommonAPI + + # import Mox + import Pleroma.Factory + + describe "GET /api/v1/pleroma/bookmark_folders" do + setup do: oauth_access(["read:bookmarks"]) + + test "it lists bookmark folders", %{conn: conn, user: user} do + {:ok, folder} = BookmarkFolder.create(user.id, "Bookmark folder") + + folder_id = folder.id + + result = + conn + |> get("/api/v1/pleroma/bookmark_folders") + |> json_response_and_validate_schema(200) + + assert [ + %{ + "id" => ^folder_id, + "name" => "Bookmark folder", + "emoji" => nil, + "source" => %{ + "emoji" => nil + } + } + ] = result + end + end + + describe "POST /api/v1/pleroma/bookmark_folders" do + setup do: oauth_access(["write:bookmarks"]) + + test "it creates a bookmark folder", %{conn: conn} do + result = + conn + |> put_req_header("content-type", "application/json") + |> post("/api/v1/pleroma/bookmark_folders", %{ + name: "Bookmark folder", + emoji: "📁" + }) + |> json_response_and_validate_schema(200) + + assert %{ + "name" => "Bookmark folder", + "emoji" => "📁", + "source" => %{ + "emoji" => "📁" + } + } = result + end + + test "it returns error for invalid emoji", %{conn: conn} do + result = + conn + |> put_req_header("content-type", "application/json") + |> post("/api/v1/pleroma/bookmark_folders", %{ + name: "Bookmark folder", + emoji: "not an emoji" + }) + |> json_response_and_validate_schema(422) + + assert %{"error" => "Invalid emoji"} = result + end + end + + describe "PATCH /api/v1/pleroma/bookmark_folders/:id" do + setup do: oauth_access(["write:bookmarks"]) + + test "it updates a bookmark folder", %{conn: conn, user: user} do + {:ok, folder} = BookmarkFolder.create(user.id, "Bookmark folder") + + result = + conn + |> put_req_header("content-type", "application/json") + |> patch("/api/v1/pleroma/bookmark_folders/#{folder.id}", %{ + name: "bookmark folder" + }) + |> json_response_and_validate_schema(200) + + assert %{ + "name" => "bookmark folder" + } = result + end + + test "it returns error when updating others' folders", %{conn: conn} do + other_user = insert(:user) + + {:ok, folder} = BookmarkFolder.create(other_user.id, "Bookmark folder") + + result = + conn + |> put_req_header("content-type", "application/json") + |> patch("/api/v1/pleroma/bookmark_folders/#{folder.id}", %{ + name: "bookmark folder" + }) + |> json_response_and_validate_schema(403) + + assert %{ + "error" => "Access denied" + } = result + end + end + + describe "DELETE /api/v1/pleroma/bookmark_folders/:id" do + setup do: oauth_access(["write:bookmarks"]) + + test "it deleting a bookmark folder", %{conn: conn, user: user} do + {:ok, folder} = BookmarkFolder.create(user.id, "Bookmark folder") + + assert conn + |> delete("/api/v1/pleroma/bookmark_folders/#{folder.id}") + |> json_response_and_validate_schema(200) + + folders = BookmarkFolder.for_user(user.id) + + assert length(folders) == 0 + end + + test "it returns error when deleting others' folders", %{conn: conn} do + other_user = insert(:user) + + {:ok, folder} = BookmarkFolder.create(other_user.id, "Bookmark folder") + + result = + conn + |> patch("/api/v1/pleroma/bookmark_folders/#{folder.id}") + |> json_response_and_validate_schema(403) + + assert %{ + "error" => "Access denied" + } = result + end + end +end diff --git a/uploads/.gitignore b/uploads/.gitignore deleted file mode 100644 index 523e584a7..000000000 --- a/uploads/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -# Git will ignore everything in this directory except this file. -* -!.gitignore From 9cfa4e67b11e5a1a7d09330581383dc67fcf6fd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Fri, 1 Mar 2024 18:14:31 +0100 Subject: [PATCH 07/28] Add ForceMention mrf MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- changelog.d/force-mention-mrf.add | 1 + config/config.exs | 4 + docs/configuration/cheatsheet.md | 7 +- .../web/activity_pub/mrf/force_mention.ex | 59 +++++++++++++++ test/fixtures/minds-invalid-mention-post.json | 1 + .../minds-pleroma-mentioned-post.json | 1 + .../activity_pub/mrf/force_mention_test.exs | 73 +++++++++++++++++++ 7 files changed, 145 insertions(+), 1 deletion(-) create mode 100644 changelog.d/force-mention-mrf.add create mode 100644 lib/pleroma/web/activity_pub/mrf/force_mention.ex create mode 100644 test/fixtures/minds-invalid-mention-post.json create mode 100644 test/fixtures/minds-pleroma-mentioned-post.json create mode 100644 test/pleroma/web/activity_pub/mrf/force_mention_test.exs diff --git a/changelog.d/force-mention-mrf.add b/changelog.d/force-mention-mrf.add new file mode 100644 index 000000000..46ac14244 --- /dev/null +++ b/changelog.d/force-mention-mrf.add @@ -0,0 +1 @@ +Add ForceMention MRF \ No newline at end of file diff --git a/config/config.exs b/config/config.exs index 435387a64..d0496cef8 100644 --- a/config/config.exs +++ b/config/config.exs @@ -415,6 +415,10 @@ config :pleroma, :mrf_inline_quote, template: "RT: {url}" +config :pleroma, :mrf_force_mention, + mention_parent: true, + mention_quoted: true + config :pleroma, :rich_media, enabled: true, ignore_hosts: [], diff --git a/docs/configuration/cheatsheet.md b/docs/configuration/cheatsheet.md index 7bba7b26e..89a461b47 100644 --- a/docs/configuration/cheatsheet.md +++ b/docs/configuration/cheatsheet.md @@ -161,7 +161,8 @@ To add configuration to your config file, you can copy it from the base config. * `Pleroma.Web.ActivityPub.MRF.KeywordPolicy`: Rejects or removes from the federated timeline or replaces keywords. (See [`:mrf_keyword`](#mrf_keyword)). * `Pleroma.Web.ActivityPub.MRF.ForceMentionsInContent`: Forces every mentioned user to be reflected in the post content. * `Pleroma.Web.ActivityPub.MRF.InlineQuotePolicy`: Forces quote post URLs to be reflected in the message content inline. - * `Pleroma.Web.ActivityPub.MRF.QuoteToLinkTagPolicy`: Force a Link tag for posts quoting another post. (may break outgoing federation of quote posts with older Pleroma versions) + * `Pleroma.Web.ActivityPub.MRF.QuoteToLinkTagPolicy`: Force a Link tag for posts quoting another post. (may break outgoing federation of quote posts with older Pleroma versions). + * `Pleroma.Web.ActivityPub.MRF.ForceMention`: Forces posts to include a mention of the author of parent post or the author of quoted post. * `transparency`: Make the content of your Message Rewrite Facility settings public (via nodeinfo). * `transparency_exclusions`: Exclude specific instance names from MRF transparency. The use of the exclusions feature will be disclosed in nodeinfo as a boolean value. @@ -272,6 +273,10 @@ Notes: #### :mrf_inline_quote * `template`: The template to append to the post. `{url}` will be replaced with the actual link to the quoted post. Default: `RT: {url}` +#### :mrf_force_mention +* `mention_parent`: Whether to append mention of parent post author +* `mention_quoted`: Whether to append mention of parent quoted author + ### :activitypub * `unfollow_blocked`: Whether blocks result in people getting unfollowed * `outgoing_blocks`: Whether to federate blocks to other instances diff --git a/lib/pleroma/web/activity_pub/mrf/force_mention.ex b/lib/pleroma/web/activity_pub/mrf/force_mention.ex new file mode 100644 index 000000000..3853489fc --- /dev/null +++ b/lib/pleroma/web/activity_pub/mrf/force_mention.ex @@ -0,0 +1,59 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2024 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.MRF.ForceMention do + require Pleroma.Constants + + alias Pleroma.Config + alias Pleroma.Object + alias Pleroma.User + + @behaviour Pleroma.Web.ActivityPub.MRF.Policy + + defp get_author(url) do + with %Object{data: %{"actor" => actor}} <- Object.normalize(url, fetch: false), + %User{ap_id: ap_id, nickname: nickname} <- User.get_cached_by_ap_id(actor) do + %{"type" => "Mention", "href" => ap_id, "name" => "@#{nickname}"} + else + _ -> nil + end + end + + defp prepend_author(tags, _, false), do: tags + + defp prepend_author(tags, nil, _), do: tags + + defp prepend_author(tags, url, _) do + actor = get_author(url) + + if not is_nil(actor) do + [actor | tags] + else + tags + end + end + + @impl true + def filter(%{"type" => "Create", "object" => %{"tag" => tag} = object} = activity) do + tag = + tag + |> prepend_author( + object["inReplyTo"], + Config.get([:mrf_force_mention, :mention_parent, true]) + ) + |> prepend_author( + object["quoteUrl"], + Config.get([:mrf_force_mention, :mention_quoted, true]) + ) + |> Enum.uniq() + + {:ok, put_in(activity["object"]["tag"], tag)} + end + + @impl true + def filter(object), do: {:ok, object} + + @impl true + def describe, do: {:ok, %{}} +end diff --git a/test/fixtures/minds-invalid-mention-post.json b/test/fixtures/minds-invalid-mention-post.json new file mode 100644 index 000000000..ea2cb2739 --- /dev/null +++ b/test/fixtures/minds-invalid-mention-post.json @@ -0,0 +1 @@ +{"@context":"https://www.w3.org/ns/activitystreams","type":"Note","id":"https://www.minds.com/api/activitypub/users/1198929502760083472/entities/urn:comment:1600926863310458883:0:0:0:1600932467852709903","attributedTo":"https://www.minds.com/api/activitypub/users/1198929502760083472","content":"\u003Ca class=\u0022u-url mention\u0022 href=\u0022https://www.minds.com/lain\u0022 target=\u0022_blank\u0022\u003E@lain\u003C/a\u003E corn syrup.","to":["https://www.w3.org/ns/activitystreams#Public"],"cc":["https://www.minds.com/api/activitypub/users/1198929502760083472/followers","https://lain.com/users/lain"],"tag":[{"type":"Mention","href":"https://www.minds.com/api/activitypub/users/464237775479123984","name":"@lain"}],"url":"https://www.minds.com/newsfeed/1600926863310458883?focusedCommentUrn=urn:comment:1600926863310458883:0:0:0:1600932467852709903","published":"2024-02-04T17:34:03+00:00","inReplyTo":"https://lain.com/objects/36254095-c839-4167-bcc2-b361d5de9198","source":{"content":"@lain corn syrup.","mediaType":"text/plain"}} \ No newline at end of file diff --git a/test/fixtures/minds-pleroma-mentioned-post.json b/test/fixtures/minds-pleroma-mentioned-post.json new file mode 100644 index 000000000..9dfa42c90 --- /dev/null +++ b/test/fixtures/minds-pleroma-mentioned-post.json @@ -0,0 +1 @@ +{"@context":["https://www.w3.org/ns/activitystreams","https://lain.com/schemas/litepub-0.1.jsonld",{"@language":"und"}],"actor":"https://lain.com/users/lain","attachment":[],"attributedTo":"https://lain.com/users/lain","cc":["https://lain.com/users/lain/followers"],"content":"which diet is the best for cognitive dissonance","context":"https://lain.com/contexts/98c8a130-e813-4797-8973-600e80114317","conversation":"https://lain.com/contexts/98c8a130-e813-4797-8973-600e80114317","id":"https://lain.com/objects/36254095-c839-4167-bcc2-b361d5de9198","published":"2024-02-04T17:11:23.931890Z","repliesCount":11,"sensitive":null,"source":{"content":"which diet is the best for cognitive dissonance","mediaType":"text/plain"},"summary":"","tag":[],"to":["https://www.w3.org/ns/activitystreams#Public"],"type":"Note"} \ No newline at end of file diff --git a/test/pleroma/web/activity_pub/mrf/force_mention_test.exs b/test/pleroma/web/activity_pub/mrf/force_mention_test.exs new file mode 100644 index 000000000..b026bab66 --- /dev/null +++ b/test/pleroma/web/activity_pub/mrf/force_mention_test.exs @@ -0,0 +1,73 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2024 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.MRF.ForceMentionTest do + use Pleroma.DataCase + require Pleroma.Constants + + alias Pleroma.Web.ActivityPub.MRF.ForceMention + + import Pleroma.Factory + + test "adds mention to a reply" do + lain = + insert(:user, ap_id: "https://lain.com/users/lain", nickname: "lain@lain.com", local: false) + + niobleoum = + insert(:user, + ap_id: "https://www.minds.com/api/activitypub/users/1198929502760083472", + nickname: "niobleoum@minds.com", + local: false + ) + + status = File.read!("test/fixtures/minds-pleroma-mentioned-post.json") |> Jason.decode!() + + status_activity = %{ + "type" => "Create", + "actor" => lain.ap_id, + "object" => status + } + + Pleroma.Web.ActivityPub.Transmogrifier.handle_incoming(status_activity) + + reply = File.read!("test/fixtures/minds-invalid-mention-post.json") |> Jason.decode!() + + reply_activity = %{ + "type" => "Create", + "actor" => niobleoum.ap_id, + "object" => reply + } + + {:ok, %{"object" => %{"tag" => tag}}} = ForceMention.filter(reply_activity) + + assert Enum.find(tag, fn %{"href" => href} -> href == lain.ap_id end) + end + + test "adds mention to a quote" do + user1 = insert(:user, ap_id: "https://misskey.io/users/83ssedkv53") + user2 = insert(:user, ap_id: "https://misskey.io/users/7rkrarq81i") + + status = File.read!("test/fixtures/tesla_mock/misskey.io_8vs6wxufd0.json") |> Jason.decode!() + + status_activity = %{ + "type" => "Create", + "actor" => user1.ap_id, + "object" => status + } + + Pleroma.Web.ActivityPub.Transmogrifier.handle_incoming(status_activity) + + quote_post = File.read!("test/fixtures/quote_post/misskey_quote_post.json") |> Jason.decode!() + + quote_activity = %{ + "type" => "Create", + "actor" => user2.ap_id, + "object" => quote_post + } + + {:ok, %{"object" => %{"tag" => tag}}} = ForceMention.filter(quote_activity) + + assert Enum.find(tag, fn %{"href" => href} -> href == user1.ap_id end) + end +end From 6ad4acea50ab0ac682f0f207d60a640a819c7748 Mon Sep 17 00:00:00 2001 From: Kaede Fujisaki Date: Sat, 2 Mar 2024 18:09:08 +0900 Subject: [PATCH 08/28] Consider a case when inbox is nil --- lib/pleroma/web/activity_pub/publisher.ex | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/lib/pleroma/web/activity_pub/publisher.ex b/lib/pleroma/web/activity_pub/publisher.ex index 9e7d00519..6267d0760 100644 --- a/lib/pleroma/web/activity_pub/publisher.ex +++ b/lib/pleroma/web/activity_pub/publisher.ex @@ -159,17 +159,20 @@ defp signature_host(%URI{port: port, scheme: scheme, host: host}) do end defp should_federate?(inbox, public) do - if public do - true - else - %{host: host} = URI.parse(inbox) + cond do + inbox == nil -> + false + public -> + true + true -> + %{host: host} = URI.parse(inbox) - quarantined_instances = - Config.get([:instance, :quarantined_instances], []) - |> Pleroma.Web.ActivityPub.MRF.instance_list_from_tuples() - |> Pleroma.Web.ActivityPub.MRF.subdomains_regex() + quarantined_instances = + Config.get([:instance, :quarantined_instances], []) + |> Pleroma.Web.ActivityPub.MRF.instance_list_from_tuples() + |> Pleroma.Web.ActivityPub.MRF.subdomains_regex() - !Pleroma.Web.ActivityPub.MRF.subdomain_match?(quarantined_instances, host) + !Pleroma.Web.ActivityPub.MRF.subdomain_match?(quarantined_instances, host) end end From 2e548c31d4756bcd97d52f9b55dd9d700656b832 Mon Sep 17 00:00:00 2001 From: Kaede Fujisaki Date: Sat, 2 Mar 2024 18:13:36 +0900 Subject: [PATCH 09/28] Add changelog --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 063d51d4c..cc994348e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). +## [Unreleased] + +### Fixed +- Federation: Do not tend to send activities when user's inbox is nil. + ## 2.6.2 ### Security From fb1873b6ec05cb7a0f4fb39267d493d2e2d9d0b1 Mon Sep 17 00:00:00 2001 From: Kaede Fujisaki Date: Sat, 2 Mar 2024 18:23:56 +0900 Subject: [PATCH 10/28] add changelog.d --- CHANGELOG.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cc994348e..063d51d4c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,11 +4,6 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). -## [Unreleased] - -### Fixed -- Federation: Do not tend to send activities when user's inbox is nil. - ## 2.6.2 ### Security From 1311f8314e90753bf8a8f781f81f89317657b727 Mon Sep 17 00:00:00 2001 From: Kaede Fujisaki Date: Sat, 2 Mar 2024 18:24:39 +0900 Subject: [PATCH 11/28] add changelog.d --- changelog.d/issue-3241.fix | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/issue-3241.fix diff --git a/changelog.d/issue-3241.fix b/changelog.d/issue-3241.fix new file mode 100644 index 000000000..d46db9805 --- /dev/null +++ b/changelog.d/issue-3241.fix @@ -0,0 +1 @@ +Handle cases when users.inbox is nil. From 0242c1f691fffd6c45a2b33763a4a7c25f02e65f Mon Sep 17 00:00:00 2001 From: Kaede Fujisaki Date: Sat, 2 Mar 2024 18:34:12 +0900 Subject: [PATCH 12/28] fmt --- lib/pleroma/web/activity_pub/publisher.ex | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/pleroma/web/activity_pub/publisher.ex b/lib/pleroma/web/activity_pub/publisher.ex index 6267d0760..f4665275c 100644 --- a/lib/pleroma/web/activity_pub/publisher.ex +++ b/lib/pleroma/web/activity_pub/publisher.ex @@ -162,8 +162,10 @@ defp should_federate?(inbox, public) do cond do inbox == nil -> false + public -> true + true -> %{host: host} = URI.parse(inbox) From 1422082bf238b09927e036f4de9deebb164e0653 Mon Sep 17 00:00:00 2001 From: tusooa Date: Thu, 7 Mar 2024 04:43:56 +0000 Subject: [PATCH 13/28] Apply ledyba's suggestion(s) to 1 file(s) --- lib/pleroma/web/activity_pub/publisher.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pleroma/web/activity_pub/publisher.ex b/lib/pleroma/web/activity_pub/publisher.ex index f4665275c..792c6a1c5 100644 --- a/lib/pleroma/web/activity_pub/publisher.ex +++ b/lib/pleroma/web/activity_pub/publisher.ex @@ -160,7 +160,7 @@ defp signature_host(%URI{port: port, scheme: scheme, host: host}) do defp should_federate?(inbox, public) do cond do - inbox == nil -> + is_nil(inbox) -> false public -> From bb0b17f4d9a235f79b4b6fabdc5e3dbe472cd259 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Thu, 7 Mar 2024 20:14:05 +0100 Subject: [PATCH 14/28] Include following/followers in backups MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- changelog.d/backups-follows.add | 1 + lib/pleroma/user/backup.ex | 21 ++++++++++++++++++++- test/pleroma/user/backup_test.exs | 13 +++++++++++++ 3 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 changelog.d/backups-follows.add diff --git a/changelog.d/backups-follows.add b/changelog.d/backups-follows.add new file mode 100644 index 000000000..a55c436f6 --- /dev/null +++ b/changelog.d/backups-follows.add @@ -0,0 +1 @@ +Include following/followers in backups \ No newline at end of file diff --git a/lib/pleroma/user/backup.ex b/lib/pleroma/user/backup.ex index b7f00bbf7..65e0baccd 100644 --- a/lib/pleroma/user/backup.ex +++ b/lib/pleroma/user/backup.ex @@ -196,7 +196,14 @@ defp wait_backup(backup, current_processed, task) do end end - @files ['actor.json', 'outbox.json', 'likes.json', 'bookmarks.json'] + @files [ + 'actor.json', + 'outbox.json', + 'likes.json', + 'bookmarks.json', + 'followers.json', + 'following.json' + ] @spec export(Pleroma.User.Backup.t(), pid()) :: {:ok, String.t()} | :error def export(%__MODULE__{} = backup, caller_pid) do backup = Repo.preload(backup, :user) @@ -207,6 +214,8 @@ def export(%__MODULE__{} = backup, caller_pid) do :ok <- statuses(dir, backup.user, caller_pid), :ok <- likes(dir, backup.user, caller_pid), :ok <- bookmarks(dir, backup.user, caller_pid), + :ok <- followers(dir, backup.user, caller_pid), + :ok <- following(dir, backup.user, caller_pid), {:ok, zip_path} <- :zip.create(backup.file_name, @files, cwd: dir), {:ok, _} <- File.rm_rf(dir) do {:ok, zip_path} @@ -357,6 +366,16 @@ defp statuses(dir, user, caller_pid) do caller_pid ) end + + defp followers(dir, user, caller_pid) do + User.get_followers_query(user) + |> write(dir, "followers", fn a -> {:ok, a.ap_id} end, caller_pid) + end + + defp following(dir, user, caller_pid) do + User.get_friends_query(user) + |> write(dir, "following", fn a -> {:ok, a.ap_id} end, caller_pid) + end end defmodule Pleroma.User.Backup.ProcessorAPI do diff --git a/test/pleroma/user/backup_test.exs b/test/pleroma/user/backup_test.exs index 0ac57e334..5503d15bc 100644 --- a/test/pleroma/user/backup_test.exs +++ b/test/pleroma/user/backup_test.exs @@ -166,6 +166,7 @@ test "it removes outdated backups after creating a fresh one" do test "it creates a zip archive with user data" do user = insert(:user, %{nickname: "cofe", name: "Cofe", ap_id: "http://cofe.io/users/cofe"}) + %{ap_id: other_ap_id} = other_user = insert(:user) {:ok, %{object: %{data: %{"id" => id1}}} = status1} = CommonAPI.post(user, %{status: "status1"}) @@ -182,6 +183,8 @@ test "it creates a zip archive with user data" do Bookmark.create(user.id, status2.id) Bookmark.create(user.id, status3.id) + CommonAPI.follow(user, other_user) + assert {:ok, backup} = user |> Backup.new() |> Repo.insert() assert {:ok, path} = Backup.export(backup, self()) assert {:ok, zipfile} = :zip.zip_open(String.to_charlist(path), [:memory]) @@ -261,6 +264,16 @@ test "it creates a zip archive with user data" do "type" => "OrderedCollection" } = Jason.decode!(json) + assert {:ok, {'following.json', json}} = :zip.zip_get('following.json', zipfile) + + assert %{ + "@context" => "https://www.w3.org/ns/activitystreams", + "id" => "following.json", + "orderedItems" => [^other_ap_id], + "totalItems" => 1, + "type" => "OrderedCollection" + } = Jason.decode!(json) + :zip.zip_close(zipfile) File.rm!(path) end From f0cca36e07fd3e0b4677e4a5daf626f3ef74dfec Mon Sep 17 00:00:00 2001 From: Lain Soykaf Date: Sun, 17 Mar 2024 15:39:24 +0400 Subject: [PATCH 15/28] CI: Remove RUM tests. --- .gitlab-ci.yml | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 8f1839c42..09ce2efd9 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -169,25 +169,6 @@ unit-testing-1.12-erratic: - mix ecto.migrate - mix test --only=erratic -unit-testing-1.12-rum: - extends: - - .build_changes_policy - - .using-ci-base - stage: test - cache: *testing_cache_policy - services: - - name: git.pleroma.social:5050/pleroma/pleroma/postgres-with-rum-13 - alias: postgres - command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"] - variables: - <<: *global_variables - RUM_ENABLED: "true" - script: - - mix ecto.create - - mix ecto.migrate - - "mix ecto.migrate --migrations-path priv/repo/optional_migrations/rum_indexing/" - - mix test --preload-modules - formatting-1.13: extends: .build_changes_policy image: &formatting_elixir elixir:1.13-alpine From caf855cf9cb9a5cadd2519237c9e7916007f4850 Mon Sep 17 00:00:00 2001 From: Lain Soykaf Date: Sun, 17 Mar 2024 16:57:45 +0400 Subject: [PATCH 16/28] ActivityPub.Publisher: Don't try federating if a user doesn't have an inbox. --- lib/pleroma/web/activity_pub/publisher.ex | 24 +++++++------------ .../web/activity_pub/publisher_test.exs | 12 ++++++++++ 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/lib/pleroma/web/activity_pub/publisher.ex b/lib/pleroma/web/activity_pub/publisher.ex index 792c6a1c5..a42b4844e 100644 --- a/lib/pleroma/web/activity_pub/publisher.ex +++ b/lib/pleroma/web/activity_pub/publisher.ex @@ -158,24 +158,18 @@ defp signature_host(%URI{port: port, scheme: scheme, host: host}) do end end - defp should_federate?(inbox, public) do - cond do - is_nil(inbox) -> - false + def should_federate?(nil, _), do: false + def should_federate?(_, true), do: true - public -> - true + def should_federate?(inbox, _) do + %{host: host} = URI.parse(inbox) - true -> - %{host: host} = URI.parse(inbox) + quarantined_instances = + Config.get([:instance, :quarantined_instances], []) + |> Pleroma.Web.ActivityPub.MRF.instance_list_from_tuples() + |> Pleroma.Web.ActivityPub.MRF.subdomains_regex() - quarantined_instances = - Config.get([:instance, :quarantined_instances], []) - |> Pleroma.Web.ActivityPub.MRF.instance_list_from_tuples() - |> Pleroma.Web.ActivityPub.MRF.subdomains_regex() - - !Pleroma.Web.ActivityPub.MRF.subdomain_match?(quarantined_instances, host) - end + !Pleroma.Web.ActivityPub.MRF.subdomain_match?(quarantined_instances, host) end @spec recipients(User.t(), Activity.t()) :: [[User.t()]] diff --git a/test/pleroma/web/activity_pub/publisher_test.exs b/test/pleroma/web/activity_pub/publisher_test.exs index 7aa06a5c4..870f1f77a 100644 --- a/test/pleroma/web/activity_pub/publisher_test.exs +++ b/test/pleroma/web/activity_pub/publisher_test.exs @@ -25,6 +25,17 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do setup_all do: clear_config([:instance, :federating], true) + describe "should_federate?/1" do + test "it returns false when the inbox is nil" do + refute Publisher.should_federate?(nil, false) + refute Publisher.should_federate?(nil, true) + end + + test "it returns true when public is true" do + assert Publisher.should_federate?(false, true) + end + end + describe "gather_webfinger_links/1" do test "it returns links" do user = insert(:user) @@ -205,6 +216,7 @@ test "publish to url with with different ports" do refute called(Instances.set_reachable(inbox)) end + @tag capture_log: true test_with_mock "calls `Instances.set_unreachable` on target inbox on non-2xx HTTP response code", Instances, [:passthrough], From a48f5f860e41ef8a04a0184d2c28cb35a86bbaaa Mon Sep 17 00:00:00 2001 From: Matthieu Rakotojaona Date: Wed, 13 Mar 2024 13:46:37 +0100 Subject: [PATCH 17/28] Notifications: filter on users rather than activities --- changelog.d/notifications.fix | 1 + lib/pleroma/following_relationship.ex | 10 +++++----- lib/pleroma/notification.ex | 6 +++--- 3 files changed, 9 insertions(+), 8 deletions(-) create mode 100644 changelog.d/notifications.fix diff --git a/changelog.d/notifications.fix b/changelog.d/notifications.fix new file mode 100644 index 000000000..a2d2eaea9 --- /dev/null +++ b/changelog.d/notifications.fix @@ -0,0 +1 @@ +Notifications: improve performance by filtering on users table instead of activities table \ No newline at end of file diff --git a/lib/pleroma/following_relationship.ex b/lib/pleroma/following_relationship.ex index 15664c876..f38c2fce9 100644 --- a/lib/pleroma/following_relationship.ex +++ b/lib/pleroma/following_relationship.ex @@ -241,13 +241,13 @@ def find(following_relationships, follower, following) do end @doc """ - For a query with joined activity, - keeps rows where activity's actor is followed by user -or- is NOT domain-blocked by user. + For a query with joined activity's actor, + keeps rows where actor is followed by user -or- is NOT domain-blocked by user. """ def keep_following_or_not_domain_blocked(query, user) do where( query, - [_, activity], + [_, user_actor: user_actor], fragment( # "(actor's domain NOT in domain_blocks) OR (actor IS in followed AP IDs)" """ @@ -255,9 +255,9 @@ def keep_following_or_not_domain_blocked(query, user) do ? = ANY(SELECT ap_id FROM users AS u INNER JOIN following_relationships AS fr ON u.id = fr.following_id WHERE fr.follower_id = ? AND fr.state = ?) """, - activity.actor, + user_actor.ap_id, ^user.domain_blocks, - activity.actor, + user_actor.ap_id, ^User.binary_id(user.id), ^accept_state_code() ) diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex index 368e609d2..710b19866 100644 --- a/lib/pleroma/notification.ex +++ b/lib/pleroma/notification.ex @@ -137,7 +137,7 @@ defp exclude_blocked(query, user, opts) do blocked_ap_ids = opts[:blocked_users_ap_ids] || User.blocked_users_ap_ids(user) query - |> where([n, a], a.actor not in ^blocked_ap_ids) + |> where([..., user_actor: user_actor], user_actor.ap_id not in ^blocked_ap_ids) |> FollowingRelationship.keep_following_or_not_domain_blocked(user) end @@ -148,7 +148,7 @@ defp exclude_blockers(query, user) do blocker_ap_ids = User.incoming_relationships_ungrouped_ap_ids(user, [:block]) query - |> where([n, a], a.actor not in ^blocker_ap_ids) + |> where([..., user_actor: user_actor], user_actor.ap_id not in ^blocker_ap_ids) end end @@ -161,7 +161,7 @@ defp exclude_notification_muted(query, user, opts) do opts[:notification_muted_users_ap_ids] || User.notification_muted_users_ap_ids(user) query - |> where([n, a], a.actor not in ^notification_muted_ap_ids) + |> where([..., user_actor: user_actor], user_actor.ap_id not in ^notification_muted_ap_ids) |> join(:left, [n, a], tm in ThreadMute, on: tm.user_id == ^user.id and tm.context == fragment("?->>'context'", a.data), as: :thread_mute From 60c4cb21ea329e7256297242b05b7f1e9d53e314 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Mon, 18 Mar 2024 13:54:15 +0100 Subject: [PATCH 18/28] InstanceView: Update features MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- lib/pleroma/web/mastodon_api/views/instance_view.ex | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/web/mastodon_api/views/instance_view.ex b/lib/pleroma/web/mastodon_api/views/instance_view.ex index 84e9a0d3c..210b46d2c 100644 --- a/lib/pleroma/web/mastodon_api/views/instance_view.ex +++ b/lib/pleroma/web/mastodon_api/views/instance_view.ex @@ -130,7 +130,8 @@ def features do "profile_directory" end, "pleroma:get:main/ostatus", - "pleroma:group_actors" + "pleroma:group_actors", + "pleroma:bookmark_folders" ] |> Enum.filter(& &1) end From 7f97fbc1ae8d38c88d32739e968b4f2572c755a8 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Mon, 18 Mar 2024 15:36:26 -0400 Subject: [PATCH 19/28] Update minimum Postgres version to 11.0; disable JIT This release is where JIT was introduced and it should be disabled. Pleroma's queries do not benefit from JIT, but it can increase latency of queries. --- .gitlab-ci.yml | 2 +- changelog.d/postgres-jit.change | 1 + config/config.exs | 2 +- docs/installation/debian_based_jp.md | 4 ++-- docs/installation/generic_dependencies.include | 2 +- 5 files changed, 6 insertions(+), 5 deletions(-) create mode 100644 changelog.d/postgres-jit.change diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 09ce2efd9..79bb2fd7e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -113,7 +113,7 @@ benchmark: variables: MIX_ENV: benchmark services: - - name: postgres:9.6-alpine + - name: postgres:11.22-alpine alias: postgres command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"] script: diff --git a/changelog.d/postgres-jit.change b/changelog.d/postgres-jit.change new file mode 100644 index 000000000..38225b06b --- /dev/null +++ b/changelog.d/postgres-jit.change @@ -0,0 +1 @@ +Disable jit by default for PostgreSQL diff --git a/config/config.exs b/config/config.exs index d0496cef8..32c8509be 100644 --- a/config/config.exs +++ b/config/config.exs @@ -799,7 +799,7 @@ config :pleroma, configurable_from_database: false config :pleroma, Pleroma.Repo, - parameters: [gin_fuzzy_search_limit: "500"], + parameters: [gin_fuzzy_search_limit: "500", jit: "off"], prepare: :unnamed config :pleroma, :connections_pool, diff --git a/docs/installation/debian_based_jp.md b/docs/installation/debian_based_jp.md index 1424ad7f4..b6b5c9c07 100644 --- a/docs/installation/debian_based_jp.md +++ b/docs/installation/debian_based_jp.md @@ -12,8 +12,8 @@ Note: This article is potentially outdated because at this time we may not have ### 必要なソフトウェア -- PostgreSQL 9.6以上 (Ubuntu16.04では9.5しか提供されていないので,[](https://www.postgresql.org/download/linux/ubuntu/)こちらから新しいバージョンを入手してください) -- `postgresql-contrib` 9.6以上 (同上) +- PostgreSQL 11.0以上 (Ubuntu16.04では9.5しか提供されていないので,[](https://www.postgresql.org/download/linux/ubuntu/)こちらから新しいバージョンを入手してください) +- `postgresql-contrib` 11.0以上 (同上) - Elixir 1.8 以上 ([Debianのリポジトリからインストールしないこと!!! ここからインストールすること!](https://elixir-lang.org/install.html#unix-and-unix-like)。または [asdf](https://github.com/asdf-vm/asdf) をpleromaユーザーでインストールしてください) - `erlang-dev` - `erlang-nox` diff --git a/docs/installation/generic_dependencies.include b/docs/installation/generic_dependencies.include index aebf21e7c..6572716ed 100644 --- a/docs/installation/generic_dependencies.include +++ b/docs/installation/generic_dependencies.include @@ -1,6 +1,6 @@ ## Required dependencies -* PostgreSQL >=9.6 +* PostgreSQL >=11.0 * Elixir >=1.11.0 <1.15 * Erlang OTP >=22.2.0 (supported: <27) * git From 1413d2e517e51a71c71d6365f93ea94fabc61593 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Mon, 18 Mar 2024 15:42:15 -0400 Subject: [PATCH 20/28] Remove vestiges of old Postgres support --- lib/pleroma/application.ex | 23 +---------------------- lib/pleroma/search/database_search.ex | 9 +-------- 2 files changed, 2 insertions(+), 30 deletions(-) diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex index de668052f..f2b234022 100644 --- a/lib/pleroma/application.ex +++ b/lib/pleroma/application.ex @@ -119,28 +119,7 @@ def start(_type, _args) do max_restarts = Application.get_env(:pleroma, __MODULE__)[:max_restarts] opts = [strategy: :one_for_one, name: Pleroma.Supervisor, max_restarts: max_restarts] - result = Supervisor.start_link(children, opts) - - set_postgres_server_version() - - result - end - - defp set_postgres_server_version do - version = - with %{rows: [[version]]} <- Ecto.Adapters.SQL.query!(Pleroma.Repo, "show server_version"), - {num, _} <- Float.parse(version) do - num - else - e -> - Logger.warning( - "Could not get the postgres version: #{inspect(e)}.\nSetting the default value of 9.6" - ) - - 9.6 - end - - :persistent_term.put({Pleroma.Repo, :postgres_version}, version) + Supervisor.start_link(children, opts) end def load_custom_modules do diff --git a/lib/pleroma/search/database_search.ex b/lib/pleroma/search/database_search.ex index c6311e0c7..31bfc7e33 100644 --- a/lib/pleroma/search/database_search.ex +++ b/lib/pleroma/search/database_search.ex @@ -23,19 +23,12 @@ def search(user, search_query, options \\ []) do offset = Keyword.get(options, :offset, 0) author = Keyword.get(options, :author) - search_function = - if :persistent_term.get({Pleroma.Repo, :postgres_version}) >= 11 do - :websearch - else - :plain - end - try do Activity |> Activity.with_preloaded_object() |> Activity.restrict_deactivated_users() |> restrict_public(user) - |> query_with(index_type, search_query, search_function) + |> query_with(index_type, search_query, :websearch) |> maybe_restrict_local(user) |> maybe_restrict_author(author) |> maybe_restrict_blocked(user) From b822a912ad1a597484fab3465e1bd90e756abdc8 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Mon, 18 Mar 2024 16:15:40 -0400 Subject: [PATCH 21/28] Remove test for postgres < 11 --- test/pleroma/search/database_search_test.exs | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/test/pleroma/search/database_search_test.exs b/test/pleroma/search/database_search_test.exs index 6c47ff425..d8dd09063 100644 --- a/test/pleroma/search/database_search_test.exs +++ b/test/pleroma/search/database_search_test.exs @@ -35,21 +35,6 @@ test "it does not find local-only posts for anonymous users" do assert [] = Search.search(nil, "wednesday") end - test "using plainto_tsquery on postgres < 11" do - old_version = :persistent_term.get({Pleroma.Repo, :postgres_version}) - :persistent_term.put({Pleroma.Repo, :postgres_version}, 10.0) - on_exit(fn -> :persistent_term.put({Pleroma.Repo, :postgres_version}, old_version) end) - - user = insert(:user) - {:ok, post} = CommonAPI.post(user, %{status: "it's wednesday my dudes"}) - {:ok, _post2} = CommonAPI.post(user, %{status: "it's wednesday my bros"}) - - # plainto doesn't understand complex queries - assert [result] = Search.search(nil, "wednesday -dudes") - - assert result.id == post.id - end - test "using websearch_to_tsquery" do user = insert(:user) {:ok, _post} = CommonAPI.post(user, %{status: "it's wednesday my dudes"}) From 357553a64acf8edf180621b3984e596664815248 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Mon, 18 Mar 2024 16:24:23 -0400 Subject: [PATCH 22/28] Remove usage of :persistent_term for Postgres version storage, fix test This test should not have been passing. The search result's activity id should not be the same id as the local post. capture_log was not being used. Removed. --- .../controllers/search_controller_test.exs | 30 ++++++++----------- 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/test/pleroma/web/mastodon_api/controllers/search_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/search_controller_test.exs index b05487abe..ad4144da4 100644 --- a/test/pleroma/web/mastodon_api/controllers/search_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/search_controller_test.exs @@ -322,26 +322,20 @@ test "search", %{conn: conn} do end test "search fetches remote statuses and prefers them over other results", %{conn: conn} do - old_version = :persistent_term.get({Pleroma.Repo, :postgres_version}) - :persistent_term.put({Pleroma.Repo, :postgres_version}, 10.0) - on_exit(fn -> :persistent_term.put({Pleroma.Repo, :postgres_version}, old_version) end) + {:ok, %{id: activity_id}} = + CommonAPI.post(insert(:user), %{ + status: "check out http://mastodon.example.org/@admin/99541947525187367" + }) - capture_log(fn -> - {:ok, %{id: activity_id}} = - CommonAPI.post(insert(:user), %{ - status: "check out http://mastodon.example.org/@admin/99541947525187367" - }) + %{"url" => result_url, "id" => result_id} = + conn + |> get("/api/v1/search?q=http://mastodon.example.org/@admin/99541947525187367") + |> json_response_and_validate_schema(200) + |> Map.get("statuses") + |> List.first() - results = - conn - |> get("/api/v1/search?q=http://mastodon.example.org/@admin/99541947525187367") - |> json_response_and_validate_schema(200) - - assert [ - %{"url" => "http://mastodon.example.org/@admin/99541947525187367"}, - %{"id" => ^activity_id} - ] = results["statuses"] - end) + refute match?(^result_id, activity_id) + assert match?(^result_url, "http://mastodon.example.org/@admin/99541947525187367") end test "search doesn't show statuses that it shouldn't", %{conn: conn} do From 923803a5332f97a7ad4eaa1d77e38b3a7e6e2367 Mon Sep 17 00:00:00 2001 From: Lain Soykaf Date: Tue, 19 Mar 2024 10:34:37 +0400 Subject: [PATCH 23/28] Tests: Explicitly set db pool size and max cases to the same value. --- config/test.exs | 2 +- test/test_helper.exs | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/config/test.exs b/config/test.exs index 9d752bdf8..80b01932c 100644 --- a/config/test.exs +++ b/config/test.exs @@ -49,7 +49,7 @@ hostname: System.get_env("DB_HOST") || "localhost", port: System.get_env("DB_PORT") || "5432", pool: Ecto.Adapters.SQL.Sandbox, - pool_size: 50 + pool_size: System.schedulers_online() * 2 config :pleroma, :dangerzone, override_repo_pool_size: true diff --git a/test/test_helper.exs b/test/test_helper.exs index e65f7c1d1..4de50483c 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -4,6 +4,8 @@ Code.put_compiler_option(:warnings_as_errors, true) +ExUnit.configure(max_cases: System.schedulers_online() * 2) + ExUnit.start(exclude: [:federated, :erratic]) if match?({:unix, :darwin}, :os.type()) do From 3cc8414c2ec06e221d8157ecedd55edc6e8def8a Mon Sep 17 00:00:00 2001 From: Lain Soykaf Date: Tue, 19 Mar 2024 10:38:29 +0400 Subject: [PATCH 24/28] Add changelog --- changelog.d/test-improvements.skip | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 changelog.d/test-improvements.skip diff --git a/changelog.d/test-improvements.skip b/changelog.d/test-improvements.skip new file mode 100644 index 000000000..e69de29bb From 665947ab2a55f71d8e0f457c75f48213f868f5df Mon Sep 17 00:00:00 2001 From: Lain Soykaf Date: Tue, 19 Mar 2024 11:03:05 +0400 Subject: [PATCH 25/28] Tests: Reduced the max case number to make tests more stable. --- test/test_helper.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_helper.exs b/test/test_helper.exs index 4de50483c..a117584ae 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -4,7 +4,7 @@ Code.put_compiler_option(:warnings_as_errors, true) -ExUnit.configure(max_cases: System.schedulers_online() * 2) +ExUnit.configure(max_cases: System.schedulers_online()) ExUnit.start(exclude: [:federated, :erratic]) From 9617189e963b89f4353168e20d48ff086a0c53bb Mon Sep 17 00:00:00 2001 From: Lain Soykaf Date: Tue, 19 Mar 2024 13:51:04 +0400 Subject: [PATCH 26/28] Tests: Actually run the bookmark folder tests. --- ...der_controller_test.ex => bookmark_folder_controller_test.exs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test/pleroma/web/pleroma_api/controllers/{bookmark_folder_controller_test.ex => bookmark_folder_controller_test.exs} (100%) diff --git a/test/pleroma/web/pleroma_api/controllers/bookmark_folder_controller_test.ex b/test/pleroma/web/pleroma_api/controllers/bookmark_folder_controller_test.exs similarity index 100% rename from test/pleroma/web/pleroma_api/controllers/bookmark_folder_controller_test.ex rename to test/pleroma/web/pleroma_api/controllers/bookmark_folder_controller_test.exs From afae3a94a4d91627ec204f9e1909d0400ec6497f Mon Sep 17 00:00:00 2001 From: Lain Soykaf Date: Tue, 19 Mar 2024 13:54:35 +0400 Subject: [PATCH 27/28] CI: Move changelog check to later in the pipeline No reason to not run tests. --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 79bb2fd7e..dab52e4c6 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -26,10 +26,10 @@ cache: &global_cache_policy - _build stages: - - check-changelog - build - lint - test + - check-changelog - benchmark - deploy - release From 040a980277947ee2eb7ae8b7432a125536dc2f06 Mon Sep 17 00:00:00 2001 From: Lain Soykaf Date: Tue, 19 Mar 2024 14:59:25 +0400 Subject: [PATCH 28/28] Add changelog --- changelog.d/fix-bookmark-folder-tests.skip | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 changelog.d/fix-bookmark-folder-tests.skip diff --git a/changelog.d/fix-bookmark-folder-tests.skip b/changelog.d/fix-bookmark-folder-tests.skip new file mode 100644 index 000000000..e69de29bb